GNU Linux-libre 4.19.314-gnu1
[releases.git] / drivers / net / phy / meson-gxl.c
1 /*
2  * Amlogic Meson GXL Internal PHY Driver
3  *
4  * Copyright (C) 2015 Amlogic, Inc. All rights reserved.
5  * Copyright (C) 2016 BayLibre, SAS. All rights reserved.
6  * Author: Neil Armstrong <narmstrong@baylibre.com>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
16  * more details.
17  *
18  */
19 #include <linux/kernel.h>
20 #include <linux/module.h>
21 #include <linux/mii.h>
22 #include <linux/ethtool.h>
23 #include <linux/phy.h>
24 #include <linux/netdevice.h>
25 #include <linux/bitfield.h>
26
27 #define TSTCNTL         20
28 #define  TSTCNTL_READ           BIT(15)
29 #define  TSTCNTL_WRITE          BIT(14)
30 #define  TSTCNTL_REG_BANK_SEL   GENMASK(12, 11)
31 #define  TSTCNTL_TEST_MODE      BIT(10)
32 #define  TSTCNTL_READ_ADDRESS   GENMASK(9, 5)
33 #define  TSTCNTL_WRITE_ADDRESS  GENMASK(4, 0)
34 #define TSTREAD1        21
35 #define TSTWRITE        23
36 #define INTSRC_FLAG     29
37 #define  INTSRC_ANEG_PR         BIT(1)
38 #define  INTSRC_PARALLEL_FAULT  BIT(2)
39 #define  INTSRC_ANEG_LP_ACK     BIT(3)
40 #define  INTSRC_LINK_DOWN       BIT(4)
41 #define  INTSRC_REMOTE_FAULT    BIT(5)
42 #define  INTSRC_ANEG_COMPLETE   BIT(6)
43 #define INTSRC_MASK     30
44
45 #define BANK_ANALOG_DSP         0
46 #define BANK_WOL                1
47 #define BANK_BIST               3
48
49 /* WOL Registers */
50 #define LPI_STATUS      0xc
51 #define  LPI_STATUS_RSV12       BIT(12)
52
53 /* BIST Registers */
54 #define FR_PLL_CONTROL  0x1b
55 #define FR_PLL_DIV0     0x1c
56 #define FR_PLL_DIV1     0x1d
57
58 static int meson_gxl_open_banks(struct phy_device *phydev)
59 {
60         int ret;
61
62         /* Enable Analog and DSP register Bank access by
63          * toggling TSTCNTL_TEST_MODE bit in the TSTCNTL register
64          */
65         ret = phy_write(phydev, TSTCNTL, 0);
66         if (ret)
67                 return ret;
68         ret = phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE);
69         if (ret)
70                 return ret;
71         ret = phy_write(phydev, TSTCNTL, 0);
72         if (ret)
73                 return ret;
74         return phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE);
75 }
76
77 static void meson_gxl_close_banks(struct phy_device *phydev)
78 {
79         phy_write(phydev, TSTCNTL, 0);
80 }
81
82 static int meson_gxl_read_reg(struct phy_device *phydev,
83                               unsigned int bank, unsigned int reg)
84 {
85         int ret;
86
87         ret = meson_gxl_open_banks(phydev);
88         if (ret)
89                 goto out;
90
91         ret = phy_write(phydev, TSTCNTL, TSTCNTL_READ |
92                         FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) |
93                         TSTCNTL_TEST_MODE |
94                         FIELD_PREP(TSTCNTL_READ_ADDRESS, reg));
95         if (ret)
96                 goto out;
97
98         ret = phy_read(phydev, TSTREAD1);
99 out:
100         /* Close the bank access on our way out */
101         meson_gxl_close_banks(phydev);
102         return ret;
103 }
104
105 static int meson_gxl_write_reg(struct phy_device *phydev,
106                                unsigned int bank, unsigned int reg,
107                                uint16_t value)
108 {
109         int ret;
110
111         ret = meson_gxl_open_banks(phydev);
112         if (ret)
113                 goto out;
114
115         ret = phy_write(phydev, TSTWRITE, value);
116         if (ret)
117                 goto out;
118
119         ret = phy_write(phydev, TSTCNTL, TSTCNTL_WRITE |
120                         FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) |
121                         TSTCNTL_TEST_MODE |
122                         FIELD_PREP(TSTCNTL_WRITE_ADDRESS, reg));
123
124 out:
125         /* Close the bank access on our way out */
126         meson_gxl_close_banks(phydev);
127         return ret;
128 }
129
130 static int meson_gxl_config_init(struct phy_device *phydev)
131 {
132         int ret;
133
134         /* Enable fractional PLL */
135         ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_CONTROL, 0x5);
136         if (ret)
137                 return ret;
138
139         /* Program fraction FR_PLL_DIV1 */
140         ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV1, 0x029a);
141         if (ret)
142                 return ret;
143
144         /* Program fraction FR_PLL_DIV1 */
145         ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV0, 0xaaaa);
146         if (ret)
147                 return ret;
148
149         return genphy_config_init(phydev);
150 }
151
152 /* This function is provided to cope with the possible failures of this phy
153  * during aneg process. When aneg fails, the PHY reports that aneg is done
154  * but the value found in MII_LPA is wrong:
155  *  - Early failures: MII_LPA is just 0x0001. if MII_EXPANSION reports that
156  *    the link partner (LP) supports aneg but the LP never acked our base
157  *    code word, it is likely that we never sent it to begin with.
158  *  - Late failures: MII_LPA is filled with a value which seems to make sense
159  *    but it actually is not what the LP is advertising. It seems that we
160  *    can detect this using a magic bit in the WOL bank (reg 12 - bit 12).
161  *    If this particular bit is not set when aneg is reported being done,
162  *    it means MII_LPA is likely to be wrong.
163  *
164  * In both case, forcing a restart of the aneg process solve the problem.
165  * When this failure happens, the first retry is usually successful but,
166  * in some cases, it may take up to 6 retries to get a decent result
167  */
168 static int meson_gxl_read_status(struct phy_device *phydev)
169 {
170         int ret, wol, lpa, exp;
171
172         if (phydev->autoneg == AUTONEG_ENABLE) {
173                 ret = genphy_aneg_done(phydev);
174                 if (ret < 0)
175                         return ret;
176                 else if (!ret)
177                         goto read_status_continue;
178
179                 /* Aneg is done, let's check everything is fine */
180                 wol = meson_gxl_read_reg(phydev, BANK_WOL, LPI_STATUS);
181                 if (wol < 0)
182                         return wol;
183
184                 lpa = phy_read(phydev, MII_LPA);
185                 if (lpa < 0)
186                         return lpa;
187
188                 exp = phy_read(phydev, MII_EXPANSION);
189                 if (exp < 0)
190                         return exp;
191
192                 if (!(wol & LPI_STATUS_RSV12) ||
193                     ((exp & EXPANSION_NWAY) && !(lpa & LPA_LPACK))) {
194                         /* Looks like aneg failed after all */
195                         phydev_dbg(phydev, "LPA corruption - aneg restart\n");
196                         return genphy_restart_aneg(phydev);
197                 }
198         }
199
200 read_status_continue:
201         return genphy_read_status(phydev);
202 }
203
204 static int meson_gxl_ack_interrupt(struct phy_device *phydev)
205 {
206         int ret = phy_read(phydev, INTSRC_FLAG);
207
208         return ret < 0 ? ret : 0;
209 }
210
211 static int meson_gxl_config_intr(struct phy_device *phydev)
212 {
213         u16 val;
214         int ret;
215
216         if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
217                 val = INTSRC_ANEG_PR
218                         | INTSRC_PARALLEL_FAULT
219                         | INTSRC_ANEG_LP_ACK
220                         | INTSRC_LINK_DOWN
221                         | INTSRC_REMOTE_FAULT
222                         | INTSRC_ANEG_COMPLETE;
223         } else {
224                 val = 0;
225         }
226
227         /* Ack any pending IRQ */
228         ret = meson_gxl_ack_interrupt(phydev);
229         if (ret)
230                 return ret;
231
232         return phy_write(phydev, INTSRC_MASK, val);
233 }
234
235 static struct phy_driver meson_gxl_phy[] = {
236         {
237                 .phy_id         = 0x01814400,
238                 .phy_id_mask    = 0xfffffff0,
239                 .name           = "Meson GXL Internal PHY",
240                 .features       = PHY_BASIC_FEATURES,
241                 .flags          = PHY_IS_INTERNAL | PHY_HAS_INTERRUPT,
242                 .config_init    = meson_gxl_config_init,
243                 .aneg_done      = genphy_aneg_done,
244                 .read_status    = meson_gxl_read_status,
245                 .ack_interrupt  = meson_gxl_ack_interrupt,
246                 .config_intr    = meson_gxl_config_intr,
247                 .suspend        = genphy_suspend,
248                 .resume         = genphy_resume,
249                 .read_mmd       = genphy_read_mmd_unsupported,
250                 .write_mmd      = genphy_write_mmd_unsupported,
251         }, {
252                 PHY_ID_MATCH_EXACT(0x01803301),
253                 .name           = "Meson G12A Internal PHY",
254                 .features       = PHY_BASIC_FEATURES,
255                 .flags          = PHY_IS_INTERNAL,
256                 .soft_reset     = genphy_soft_reset,
257                 .ack_interrupt  = meson_gxl_ack_interrupt,
258                 .config_intr    = meson_gxl_config_intr,
259                 .suspend        = genphy_suspend,
260                 .resume         = genphy_resume,
261                 .read_mmd       = genphy_read_mmd_unsupported,
262                 .write_mmd      = genphy_write_mmd_unsupported,
263         },
264 };
265
266 static struct mdio_device_id __maybe_unused meson_gxl_tbl[] = {
267         { 0x01814400, 0xfffffff0 },
268         { PHY_ID_MATCH_VENDOR(0x01803301) },
269         { }
270 };
271
272 module_phy_driver(meson_gxl_phy);
273
274 MODULE_DEVICE_TABLE(mdio, meson_gxl_tbl);
275
276 MODULE_DESCRIPTION("Amlogic Meson GXL Internal PHY driver");
277 MODULE_AUTHOR("Baoqi wang");
278 MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
279 MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
280 MODULE_LICENSE("GPL");