xref: /linux/drivers/net/phy/meson-gxl.c (revision 83439a0f1ce6a592f95e41338320b5f01b98a356)
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 	phy_trigger_machine(phydev);
247 
248 	return IRQ_HANDLED;
249 }
250 
251 static struct phy_driver meson_gxl_phy[] = {
252 	{
253 		PHY_ID_MATCH_EXACT(0x01814400),
254 		.name		= "Meson GXL Internal PHY",
255 		/* PHY_BASIC_FEATURES */
256 		.flags		= PHY_IS_INTERNAL,
257 		.soft_reset     = genphy_soft_reset,
258 		.config_init	= meson_gxl_config_init,
259 		.read_status	= meson_gxl_read_status,
260 		.config_intr	= meson_gxl_config_intr,
261 		.handle_interrupt = meson_gxl_handle_interrupt,
262 		.suspend        = genphy_suspend,
263 		.resume         = genphy_resume,
264 	}, {
265 		PHY_ID_MATCH_EXACT(0x01803301),
266 		.name		= "Meson G12A Internal PHY",
267 		/* PHY_BASIC_FEATURES */
268 		.flags		= PHY_IS_INTERNAL,
269 		.soft_reset     = genphy_soft_reset,
270 		.config_intr	= meson_gxl_config_intr,
271 		.handle_interrupt = meson_gxl_handle_interrupt,
272 		.suspend        = genphy_suspend,
273 		.resume         = genphy_resume,
274 	},
275 };
276 
277 static struct mdio_device_id __maybe_unused meson_gxl_tbl[] = {
278 	{ PHY_ID_MATCH_VENDOR(0x01814400) },
279 	{ PHY_ID_MATCH_VENDOR(0x01803301) },
280 	{ }
281 };
282 
283 module_phy_driver(meson_gxl_phy);
284 
285 MODULE_DEVICE_TABLE(mdio, meson_gxl_tbl);
286 
287 MODULE_DESCRIPTION("Amlogic Meson GXL Internal PHY driver");
288 MODULE_AUTHOR("Baoqi wang");
289 MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>");
290 MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");
291 MODULE_LICENSE("GPL");
292