Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0+
0002 /*
0003  * Amlogic Meson GXL Internal PHY Driver
0004  *
0005  * Copyright (C) 2015 Amlogic, Inc. All rights reserved.
0006  * Copyright (C) 2016 BayLibre, SAS. All rights reserved.
0007  * Author: Neil Armstrong <narmstrong@baylibre.com>
0008  */
0009 #include <linux/kernel.h>
0010 #include <linux/module.h>
0011 #include <linux/mii.h>
0012 #include <linux/ethtool.h>
0013 #include <linux/phy.h>
0014 #include <linux/netdevice.h>
0015 #include <linux/bitfield.h>
0016 
0017 #define TSTCNTL     20
0018 #define  TSTCNTL_READ       BIT(15)
0019 #define  TSTCNTL_WRITE      BIT(14)
0020 #define  TSTCNTL_REG_BANK_SEL   GENMASK(12, 11)
0021 #define  TSTCNTL_TEST_MODE  BIT(10)
0022 #define  TSTCNTL_READ_ADDRESS   GENMASK(9, 5)
0023 #define  TSTCNTL_WRITE_ADDRESS  GENMASK(4, 0)
0024 #define TSTREAD1    21
0025 #define TSTWRITE    23
0026 #define INTSRC_FLAG 29
0027 #define  INTSRC_ANEG_PR     BIT(1)
0028 #define  INTSRC_PARALLEL_FAULT  BIT(2)
0029 #define  INTSRC_ANEG_LP_ACK BIT(3)
0030 #define  INTSRC_LINK_DOWN   BIT(4)
0031 #define  INTSRC_REMOTE_FAULT    BIT(5)
0032 #define  INTSRC_ANEG_COMPLETE   BIT(6)
0033 #define  INTSRC_ENERGY_DETECT   BIT(7)
0034 #define INTSRC_MASK 30
0035 
0036 #define INT_SOURCES (INTSRC_LINK_DOWN | INTSRC_ANEG_COMPLETE | \
0037              INTSRC_ENERGY_DETECT)
0038 
0039 #define BANK_ANALOG_DSP     0
0040 #define BANK_WOL        1
0041 #define BANK_BIST       3
0042 
0043 /* WOL Registers */
0044 #define LPI_STATUS  0xc
0045 #define  LPI_STATUS_RSV12   BIT(12)
0046 
0047 /* BIST Registers */
0048 #define FR_PLL_CONTROL  0x1b
0049 #define FR_PLL_DIV0 0x1c
0050 #define FR_PLL_DIV1 0x1d
0051 
0052 static int meson_gxl_open_banks(struct phy_device *phydev)
0053 {
0054     int ret;
0055 
0056     /* Enable Analog and DSP register Bank access by
0057      * toggling TSTCNTL_TEST_MODE bit in the TSTCNTL register
0058      */
0059     ret = phy_write(phydev, TSTCNTL, 0);
0060     if (ret)
0061         return ret;
0062     ret = phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE);
0063     if (ret)
0064         return ret;
0065     ret = phy_write(phydev, TSTCNTL, 0);
0066     if (ret)
0067         return ret;
0068     return phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE);
0069 }
0070 
0071 static void meson_gxl_close_banks(struct phy_device *phydev)
0072 {
0073     phy_write(phydev, TSTCNTL, 0);
0074 }
0075 
0076 static int meson_gxl_read_reg(struct phy_device *phydev,
0077                   unsigned int bank, unsigned int reg)
0078 {
0079     int ret;
0080 
0081     ret = meson_gxl_open_banks(phydev);
0082     if (ret)
0083         goto out;
0084 
0085     ret = phy_write(phydev, TSTCNTL, TSTCNTL_READ |
0086             FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) |
0087             TSTCNTL_TEST_MODE |
0088             FIELD_PREP(TSTCNTL_READ_ADDRESS, reg));
0089     if (ret)
0090         goto out;
0091 
0092     ret = phy_read(phydev, TSTREAD1);
0093 out:
0094     /* Close the bank access on our way out */
0095     meson_gxl_close_banks(phydev);
0096     return ret;
0097 }
0098 
0099 static int meson_gxl_write_reg(struct phy_device *phydev,
0100                    unsigned int bank, unsigned int reg,
0101                    uint16_t value)
0102 {
0103     int ret;
0104 
0105     ret = meson_gxl_open_banks(phydev);
0106     if (ret)
0107         goto out;
0108 
0109     ret = phy_write(phydev, TSTWRITE, value);
0110     if (ret)
0111         goto out;
0112 
0113     ret = phy_write(phydev, TSTCNTL, TSTCNTL_WRITE |
0114             FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) |
0115             TSTCNTL_TEST_MODE |
0116             FIELD_PREP(TSTCNTL_WRITE_ADDRESS, reg));
0117 
0118 out:
0119     /* Close the bank access on our way out */
0120     meson_gxl_close_banks(phydev);
0121     return ret;
0122 }
0123 
0124 static int meson_gxl_config_init(struct phy_device *phydev)
0125 {
0126     int ret;
0127 
0128     /* Enable fractional PLL */
0129     ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_CONTROL, 0x5);
0130     if (ret)
0131         return ret;
0132 
0133     /* Program fraction FR_PLL_DIV1 */
0134     ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV1, 0x029a);
0135     if (ret)
0136         return ret;
0137 
0138     /* Program fraction FR_PLL_DIV1 */
0139     ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV0, 0xaaaa);
0140     if (ret)
0141         return ret;
0142 
0143     return 0;
0144 }
0145 
0146 /* This function is provided to cope with the possible failures of this phy
0147  * during aneg process. When aneg fails, the PHY reports that aneg is done
0148  * but the value found in MII_LPA is wrong:
0149  *  - Early failures: MII_LPA is just 0x0001. if MII_EXPANSION reports that
0150  *    the link partner (LP) supports aneg but the LP never acked our base
0151  *    code word, it is likely that we never sent it to begin with.
0152  *  - Late failures: MII_LPA is filled with a value which seems to make sense
0153  *    but it actually is not what the LP is advertising. It seems that we
0154  *    can detect this using a magic bit in the WOL bank (reg 12 - bit 12).
0155  *    If this particular bit is not set when aneg is reported being done,
0156  *    it means MII_LPA is likely to be wrong.
0157  *
0158  * In both case, forcing a restart of the aneg process solve the problem.
0159  * When this failure happens, the first retry is usually successful but,
0160  * in some cases, it may take up to 6 retries to get a decent result
0161  */
0162 static int meson_gxl_read_status(struct phy_device *phydev)
0163 {
0164     int ret, wol, lpa, exp;
0165 
0166     if (phydev->autoneg == AUTONEG_ENABLE) {
0167         ret = genphy_aneg_done(phydev);
0168         if (ret < 0)
0169             return ret;
0170         else if (!ret)
0171             goto read_status_continue;
0172 
0173         /* Aneg is done, let's check everything is fine */
0174         wol = meson_gxl_read_reg(phydev, BANK_WOL, LPI_STATUS);
0175         if (wol < 0)
0176             return wol;
0177 
0178         lpa = phy_read(phydev, MII_LPA);
0179         if (lpa < 0)
0180             return lpa;
0181 
0182         exp = phy_read(phydev, MII_EXPANSION);
0183         if (exp < 0)
0184             return exp;
0185 
0186         if (!(wol & LPI_STATUS_RSV12) ||
0187             ((exp & EXPANSION_NWAY) && !(lpa & LPA_LPACK))) {
0188             /* Looks like aneg failed after all */
0189             phydev_dbg(phydev, "LPA corruption - aneg restart\n");
0190             return genphy_restart_aneg(phydev);
0191         }
0192     }
0193 
0194 read_status_continue:
0195     return genphy_read_status(phydev);
0196 }
0197 
0198 static int meson_gxl_ack_interrupt(struct phy_device *phydev)
0199 {
0200     int ret = phy_read(phydev, INTSRC_FLAG);
0201 
0202     return ret < 0 ? ret : 0;
0203 }
0204 
0205 static int meson_gxl_config_intr(struct phy_device *phydev)
0206 {
0207     int ret;
0208 
0209     if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
0210         /* Ack any pending IRQ */
0211         ret = meson_gxl_ack_interrupt(phydev);
0212         if (ret)
0213             return ret;
0214 
0215         ret = phy_write(phydev, INTSRC_MASK, INT_SOURCES);
0216     } else {
0217         ret = phy_write(phydev, INTSRC_MASK, 0);
0218 
0219         /* Ack any pending IRQ */
0220         ret = meson_gxl_ack_interrupt(phydev);
0221     }
0222 
0223     return ret;
0224 }
0225 
0226 static irqreturn_t meson_gxl_handle_interrupt(struct phy_device *phydev)
0227 {
0228     int irq_status;
0229 
0230     irq_status = phy_read(phydev, INTSRC_FLAG);
0231     if (irq_status < 0) {
0232         phy_error(phydev);
0233         return IRQ_NONE;
0234     }
0235 
0236     irq_status &= INT_SOURCES;
0237 
0238     if (irq_status == 0)
0239         return IRQ_NONE;
0240 
0241     /* Aneg-complete interrupt is used for link-up detection */
0242     if (phydev->autoneg == AUTONEG_ENABLE &&
0243         irq_status == INTSRC_ENERGY_DETECT)
0244         return IRQ_HANDLED;
0245 
0246     phy_trigger_machine(phydev);
0247 
0248     return IRQ_HANDLED;
0249 }
0250 
0251 static struct phy_driver meson_gxl_phy[] = {
0252     {
0253         PHY_ID_MATCH_EXACT(0x01814400),
0254         .name       = "Meson GXL Internal PHY",
0255         /* PHY_BASIC_FEATURES */
0256         .flags      = PHY_IS_INTERNAL,
0257         .soft_reset     = genphy_soft_reset,
0258         .config_init    = meson_gxl_config_init,
0259         .read_status    = meson_gxl_read_status,
0260         .config_intr    = meson_gxl_config_intr,
0261         .handle_interrupt = meson_gxl_handle_interrupt,
0262         .suspend        = genphy_suspend,
0263         .resume         = genphy_resume,
0264     }, {
0265         PHY_ID_MATCH_EXACT(0x01803301),
0266         .name       = "Meson G12A Internal PHY",
0267         /* PHY_BASIC_FEATURES */
0268         .flags      = PHY_IS_INTERNAL,
0269         .soft_reset     = genphy_soft_reset,
0270         .config_intr    = meson_gxl_config_intr,
0271         .handle_interrupt = meson_gxl_handle_interrupt,
0272         .suspend        = genphy_suspend,
0273         .resume         = genphy_resume,
0274     },
0275 };
0276 
0277 static struct mdio_device_id __maybe_unused meson_gxl_tbl[] = {
0278     { PHY_ID_MATCH_VENDOR(0x01814400) },
0279     { PHY_ID_MATCH_VENDOR(0x01803301) },
0280     { }
0281 };
0282 
0283 module_phy_driver(meson_gxl_phy);
0284 
0285 MODULE_DEVICE_TABLE(mdio, meson_gxl_tbl);
0286 
0287 MODULE_DESCRIPTION("Amlogic Meson GXL Internal PHY driver");
0288 MODULE_AUTHOR("Baoqi wang");
0289 MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
0290 MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
0291 MODULE_LICENSE("GPL");