GNU Linux-libre 6.8.9-gnu
[releases.git] / drivers / net / phy / meson-gxl.c
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 #include <linux/smscphy.h>
17
18 #define TSTCNTL         20
19 #define  TSTCNTL_READ           BIT(15)
20 #define  TSTCNTL_WRITE          BIT(14)
21 #define  TSTCNTL_REG_BANK_SEL   GENMASK(12, 11)
22 #define  TSTCNTL_TEST_MODE      BIT(10)
23 #define  TSTCNTL_READ_ADDRESS   GENMASK(9, 5)
24 #define  TSTCNTL_WRITE_ADDRESS  GENMASK(4, 0)
25 #define TSTREAD1        21
26 #define TSTWRITE        23
27
28 #define BANK_ANALOG_DSP         0
29 #define BANK_WOL                1
30 #define BANK_BIST               3
31
32 /* WOL Registers */
33 #define LPI_STATUS      0xc
34 #define  LPI_STATUS_RSV12       BIT(12)
35
36 /* BIST Registers */
37 #define FR_PLL_CONTROL  0x1b
38 #define FR_PLL_DIV0     0x1c
39 #define FR_PLL_DIV1     0x1d
40
41 static int meson_gxl_open_banks(struct phy_device *phydev)
42 {
43         int ret;
44
45         /* Enable Analog and DSP register Bank access by
46          * toggling TSTCNTL_TEST_MODE bit in the TSTCNTL register
47          */
48         ret = phy_write(phydev, TSTCNTL, 0);
49         if (ret)
50                 return ret;
51         ret = phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE);
52         if (ret)
53                 return ret;
54         ret = phy_write(phydev, TSTCNTL, 0);
55         if (ret)
56                 return ret;
57         return phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE);
58 }
59
60 static void meson_gxl_close_banks(struct phy_device *phydev)
61 {
62         phy_write(phydev, TSTCNTL, 0);
63 }
64
65 static int meson_gxl_read_reg(struct phy_device *phydev,
66                               unsigned int bank, unsigned int reg)
67 {
68         int ret;
69
70         ret = meson_gxl_open_banks(phydev);
71         if (ret)
72                 goto out;
73
74         ret = phy_write(phydev, TSTCNTL, TSTCNTL_READ |
75                         FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) |
76                         TSTCNTL_TEST_MODE |
77                         FIELD_PREP(TSTCNTL_READ_ADDRESS, reg));
78         if (ret)
79                 goto out;
80
81         ret = phy_read(phydev, TSTREAD1);
82 out:
83         /* Close the bank access on our way out */
84         meson_gxl_close_banks(phydev);
85         return ret;
86 }
87
88 static int meson_gxl_write_reg(struct phy_device *phydev,
89                                unsigned int bank, unsigned int reg,
90                                uint16_t value)
91 {
92         int ret;
93
94         ret = meson_gxl_open_banks(phydev);
95         if (ret)
96                 goto out;
97
98         ret = phy_write(phydev, TSTWRITE, value);
99         if (ret)
100                 goto out;
101
102         ret = phy_write(phydev, TSTCNTL, TSTCNTL_WRITE |
103                         FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) |
104                         TSTCNTL_TEST_MODE |
105                         FIELD_PREP(TSTCNTL_WRITE_ADDRESS, reg));
106
107 out:
108         /* Close the bank access on our way out */
109         meson_gxl_close_banks(phydev);
110         return ret;
111 }
112
113 static int meson_gxl_config_init(struct phy_device *phydev)
114 {
115         int ret;
116
117         /* Enable fractional PLL */
118         ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_CONTROL, 0x5);
119         if (ret)
120                 return ret;
121
122         /* Program fraction FR_PLL_DIV1 */
123         ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV1, 0x029a);
124         if (ret)
125                 return ret;
126
127         /* Program fraction FR_PLL_DIV1 */
128         ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV0, 0xaaaa);
129         if (ret)
130                 return ret;
131
132         return 0;
133 }
134
135 /* This function is provided to cope with the possible failures of this phy
136  * during aneg process. When aneg fails, the PHY reports that aneg is done
137  * but the value found in MII_LPA is wrong:
138  *  - Early failures: MII_LPA is just 0x0001. if MII_EXPANSION reports that
139  *    the link partner (LP) supports aneg but the LP never acked our base
140  *    code word, it is likely that we never sent it to begin with.
141  *  - Late failures: MII_LPA is filled with a value which seems to make sense
142  *    but it actually is not what the LP is advertising. It seems that we
143  *    can detect this using a magic bit in the WOL bank (reg 12 - bit 12).
144  *    If this particular bit is not set when aneg is reported being done,
145  *    it means MII_LPA is likely to be wrong.
146  *
147  * In both case, forcing a restart of the aneg process solve the problem.
148  * When this failure happens, the first retry is usually successful but,
149  * in some cases, it may take up to 6 retries to get a decent result
150  */
151 static int meson_gxl_read_status(struct phy_device *phydev)
152 {
153         int ret, wol, lpa, exp;
154
155         if (phydev->autoneg == AUTONEG_ENABLE) {
156                 ret = genphy_aneg_done(phydev);
157                 if (ret < 0)
158                         return ret;
159                 else if (!ret)
160                         goto read_status_continue;
161
162                 /* Aneg is done, let's check everything is fine */
163                 wol = meson_gxl_read_reg(phydev, BANK_WOL, LPI_STATUS);
164                 if (wol < 0)
165                         return wol;
166
167                 lpa = phy_read(phydev, MII_LPA);
168                 if (lpa < 0)
169                         return lpa;
170
171                 exp = phy_read(phydev, MII_EXPANSION);
172                 if (exp < 0)
173                         return exp;
174
175                 if (!(wol & LPI_STATUS_RSV12) ||
176                     ((exp & EXPANSION_NWAY) && !(lpa & LPA_LPACK))) {
177                         /* Looks like aneg failed after all */
178                         phydev_dbg(phydev, "LPA corruption - aneg restart\n");
179                         return genphy_restart_aneg(phydev);
180                 }
181         }
182
183 read_status_continue:
184         return genphy_read_status(phydev);
185 }
186
187 static struct phy_driver meson_gxl_phy[] = {
188         {
189                 PHY_ID_MATCH_EXACT(0x01814400),
190                 .name           = "Meson GXL Internal PHY",
191                 /* PHY_BASIC_FEATURES */
192                 .flags          = PHY_IS_INTERNAL,
193                 .soft_reset     = genphy_soft_reset,
194                 .config_init    = meson_gxl_config_init,
195                 .read_status    = meson_gxl_read_status,
196                 .config_intr    = smsc_phy_config_intr,
197                 .handle_interrupt = smsc_phy_handle_interrupt,
198                 .suspend        = genphy_suspend,
199                 .resume         = genphy_resume,
200                 .read_mmd       = genphy_read_mmd_unsupported,
201                 .write_mmd      = genphy_write_mmd_unsupported,
202         }, {
203                 PHY_ID_MATCH_EXACT(0x01803301),
204                 .name           = "Meson G12A Internal PHY",
205                 /* PHY_BASIC_FEATURES */
206                 .flags          = PHY_IS_INTERNAL,
207                 .probe          = smsc_phy_probe,
208                 .config_init    = smsc_phy_config_init,
209                 .soft_reset     = genphy_soft_reset,
210                 .read_status    = lan87xx_read_status,
211                 .config_intr    = smsc_phy_config_intr,
212                 .handle_interrupt = smsc_phy_handle_interrupt,
213
214                 .get_tunable    = smsc_phy_get_tunable,
215                 .set_tunable    = smsc_phy_set_tunable,
216
217                 .suspend        = genphy_suspend,
218                 .resume         = genphy_resume,
219                 .read_mmd       = genphy_read_mmd_unsupported,
220                 .write_mmd      = genphy_write_mmd_unsupported,
221         },
222 };
223
224 static struct mdio_device_id __maybe_unused meson_gxl_tbl[] = {
225         { PHY_ID_MATCH_VENDOR(0x01814400) },
226         { PHY_ID_MATCH_VENDOR(0x01803301) },
227         { }
228 };
229
230 module_phy_driver(meson_gxl_phy);
231
232 MODULE_DEVICE_TABLE(mdio, meson_gxl_tbl);
233
234 MODULE_DESCRIPTION("Amlogic Meson GXL Internal PHY driver");
235 MODULE_AUTHOR("Baoqi wang");
236 MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
237 MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
238 MODULE_LICENSE("GPL");