73f7962a37d335508b4a09e82d01132822c3b98f
[platform/kernel/linux-starfive.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
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_ENERGY_DETECT   BIT(7)
34 #define INTSRC_MASK     30
35
36 #define INT_SOURCES (INTSRC_LINK_DOWN | INTSRC_ANEG_COMPLETE | \
37                      INTSRC_ENERGY_DETECT)
38
39 #define BANK_ANALOG_DSP         0
40 #define BANK_WOL                1
41 #define BANK_BIST               3
42
43 /* WOL Registers */
44 #define LPI_STATUS      0xc
45 #define  LPI_STATUS_RSV12       BIT(12)
46
47 /* BIST Registers */
48 #define FR_PLL_CONTROL  0x1b
49 #define FR_PLL_DIV0     0x1c
50 #define FR_PLL_DIV1     0x1d
51
52 static int meson_gxl_open_banks(struct phy_device *phydev)
53 {
54         int ret;
55
56         /* Enable Analog and DSP register Bank access by
57          * toggling TSTCNTL_TEST_MODE bit in the TSTCNTL register
58          */
59         ret = phy_write(phydev, TSTCNTL, 0);
60         if (ret)
61                 return ret;
62         ret = phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE);
63         if (ret)
64                 return ret;
65         ret = phy_write(phydev, TSTCNTL, 0);
66         if (ret)
67                 return ret;
68         return phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE);
69 }
70
71 static void meson_gxl_close_banks(struct phy_device *phydev)
72 {
73         phy_write(phydev, TSTCNTL, 0);
74 }
75
76 static int meson_gxl_read_reg(struct phy_device *phydev,
77                               unsigned int bank, unsigned int reg)
78 {
79         int ret;
80
81         ret = meson_gxl_open_banks(phydev);
82         if (ret)
83                 goto out;
84
85         ret = phy_write(phydev, TSTCNTL, TSTCNTL_READ |
86                         FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) |
87                         TSTCNTL_TEST_MODE |
88                         FIELD_PREP(TSTCNTL_READ_ADDRESS, reg));
89         if (ret)
90                 goto out;
91
92         ret = phy_read(phydev, TSTREAD1);
93 out:
94         /* Close the bank access on our way out */
95         meson_gxl_close_banks(phydev);
96         return ret;
97 }
98
99 static int meson_gxl_write_reg(struct phy_device *phydev,
100                                unsigned int bank, unsigned int reg,
101                                uint16_t value)
102 {
103         int ret;
104
105         ret = meson_gxl_open_banks(phydev);
106         if (ret)
107                 goto out;
108
109         ret = phy_write(phydev, TSTWRITE, value);
110         if (ret)
111                 goto out;
112
113         ret = phy_write(phydev, TSTCNTL, TSTCNTL_WRITE |
114                         FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) |
115                         TSTCNTL_TEST_MODE |
116                         FIELD_PREP(TSTCNTL_WRITE_ADDRESS, reg));
117
118 out:
119         /* Close the bank access on our way out */
120         meson_gxl_close_banks(phydev);
121         return ret;
122 }
123
124 static int meson_gxl_config_init(struct phy_device *phydev)
125 {
126         int ret;
127
128         /* Enable fractional PLL */
129         ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_CONTROL, 0x5);
130         if (ret)
131                 return ret;
132
133         /* Program fraction FR_PLL_DIV1 */
134         ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV1, 0x029a);
135         if (ret)
136                 return ret;
137
138         /* Program fraction FR_PLL_DIV1 */
139         ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV0, 0xaaaa);
140         if (ret)
141                 return ret;
142
143         return 0;
144 }
145
146 /* This function is provided to cope with the possible failures of this phy
147  * during aneg process. When aneg fails, the PHY reports that aneg is done
148  * but the value found in MII_LPA is wrong:
149  *  - Early failures: MII_LPA is just 0x0001. if MII_EXPANSION reports that
150  *    the link partner (LP) supports aneg but the LP never acked our base
151  *    code word, it is likely that we never sent it to begin with.
152  *  - Late failures: MII_LPA is filled with a value which seems to make sense
153  *    but it actually is not what the LP is advertising. It seems that we
154  *    can detect this using a magic bit in the WOL bank (reg 12 - bit 12).
155  *    If this particular bit is not set when aneg is reported being done,
156  *    it means MII_LPA is likely to be wrong.
157  *
158  * In both case, forcing a restart of the aneg process solve the problem.
159  * When this failure happens, the first retry is usually successful but,
160  * in some cases, it may take up to 6 retries to get a decent result
161  */
162 static int meson_gxl_read_status(struct phy_device *phydev)
163 {
164         int ret, wol, lpa, exp;
165
166         if (phydev->autoneg == AUTONEG_ENABLE) {
167                 ret = genphy_aneg_done(phydev);
168                 if (ret < 0)
169                         return ret;
170                 else if (!ret)
171                         goto read_status_continue;
172
173                 /* Aneg is done, let's check everything is fine */
174                 wol = meson_gxl_read_reg(phydev, BANK_WOL, LPI_STATUS);
175                 if (wol < 0)
176                         return wol;
177
178                 lpa = phy_read(phydev, MII_LPA);
179                 if (lpa < 0)
180                         return lpa;
181
182                 exp = phy_read(phydev, MII_EXPANSION);
183                 if (exp < 0)
184                         return exp;
185
186                 if (!(wol & LPI_STATUS_RSV12) ||
187                     ((exp & EXPANSION_NWAY) && !(lpa & LPA_LPACK))) {
188                         /* Looks like aneg failed after all */
189                         phydev_dbg(phydev, "LPA corruption - aneg restart\n");
190                         return genphy_restart_aneg(phydev);
191                 }
192         }
193
194 read_status_continue:
195         return genphy_read_status(phydev);
196 }
197
198 static int meson_gxl_ack_interrupt(struct phy_device *phydev)
199 {
200         int ret = phy_read(phydev, INTSRC_FLAG);
201
202         return ret < 0 ? ret : 0;
203 }
204
205 static int meson_gxl_config_intr(struct phy_device *phydev)
206 {
207         int ret;
208
209         if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
210                 /* Ack any pending IRQ */
211                 ret = meson_gxl_ack_interrupt(phydev);
212                 if (ret)
213                         return ret;
214
215                 ret = phy_write(phydev, INTSRC_MASK, INT_SOURCES);
216         } else {
217                 ret = phy_write(phydev, INTSRC_MASK, 0);
218
219                 /* Ack any pending IRQ */
220                 ret = meson_gxl_ack_interrupt(phydev);
221         }
222
223         return ret;
224 }
225
226 static irqreturn_t meson_gxl_handle_interrupt(struct phy_device *phydev)
227 {
228         int irq_status;
229
230         irq_status = phy_read(phydev, INTSRC_FLAG);
231         if (irq_status < 0) {
232                 phy_error(phydev);
233                 return IRQ_NONE;
234         }
235
236         irq_status &= INT_SOURCES;
237
238         if (irq_status == 0)
239                 return IRQ_NONE;
240
241         /* Aneg-complete interrupt is used for link-up detection */
242         if (phydev->autoneg == AUTONEG_ENABLE &&
243             irq_status == INTSRC_ENERGY_DETECT)
244                 return IRQ_HANDLED;
245
246         /* Give PHY some time before MAC starts sending data. This works
247          * around an issue where network doesn't come up properly.
248          */
249         if (!(irq_status & INTSRC_LINK_DOWN))
250                 phy_queue_state_machine(phydev, msecs_to_jiffies(100));
251         else
252                 phy_trigger_machine(phydev);
253
254         return IRQ_HANDLED;
255 }
256
257 static struct phy_driver meson_gxl_phy[] = {
258         {
259                 PHY_ID_MATCH_EXACT(0x01814400),
260                 .name           = "Meson GXL Internal PHY",
261                 /* PHY_BASIC_FEATURES */
262                 .flags          = PHY_IS_INTERNAL,
263                 .soft_reset     = genphy_soft_reset,
264                 .config_init    = meson_gxl_config_init,
265                 .read_status    = meson_gxl_read_status,
266                 .config_intr    = meson_gxl_config_intr,
267                 .handle_interrupt = meson_gxl_handle_interrupt,
268                 .suspend        = genphy_suspend,
269                 .resume         = genphy_resume,
270         }, {
271                 PHY_ID_MATCH_EXACT(0x01803301),
272                 .name           = "Meson G12A Internal PHY",
273                 /* PHY_BASIC_FEATURES */
274                 .flags          = PHY_IS_INTERNAL,
275                 .soft_reset     = genphy_soft_reset,
276                 .config_intr    = meson_gxl_config_intr,
277                 .handle_interrupt = meson_gxl_handle_interrupt,
278                 .suspend        = genphy_suspend,
279                 .resume         = genphy_resume,
280         },
281 };
282
283 static struct mdio_device_id __maybe_unused meson_gxl_tbl[] = {
284         { PHY_ID_MATCH_VENDOR(0x01814400) },
285         { PHY_ID_MATCH_VENDOR(0x01803301) },
286         { }
287 };
288
289 module_phy_driver(meson_gxl_phy);
290
291 MODULE_DEVICE_TABLE(mdio, meson_gxl_tbl);
292
293 MODULE_DESCRIPTION("Amlogic Meson GXL Internal PHY driver");
294 MODULE_AUTHOR("Baoqi wang");
295 MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
296 MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
297 MODULE_LICENSE("GPL");