]>
Commit | Line | Data |
---|---|---|
a2443fd1 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
097c2aa8 JL |
2 | /* |
3 | * drivers/net/phy/realtek.c | |
4 | * | |
5 | * Driver for Realtek PHYs | |
6 | * | |
7 | * Author: Johnson Leung <r58129@freescale.com> | |
8 | * | |
9 | * Copyright (c) 2004 Freescale Semiconductor, Inc. | |
097c2aa8 | 10 | */ |
8cc5baef | 11 | #include <linux/bitops.h> |
097c2aa8 | 12 | #include <linux/phy.h> |
9d9779e7 | 13 | #include <linux/module.h> |
097c2aa8 | 14 | |
f609ab0e MB |
15 | #define RTL821x_PHYSR 0x11 |
16 | #define RTL821x_PHYSR_DUPLEX BIT(13) | |
17 | #define RTL821x_PHYSR_SPEED GENMASK(15, 14) | |
a82f266d | 18 | |
f609ab0e MB |
19 | #define RTL821x_INER 0x12 |
20 | #define RTL8211B_INER_INIT 0x6400 | |
21 | #define RTL8211E_INER_LINK_STATUS BIT(10) | |
22 | #define RTL8211F_INER_LINK_STATUS BIT(4) | |
a82f266d | 23 | |
f609ab0e | 24 | #define RTL821x_INSR 0x13 |
a82f266d | 25 | |
f81dadbc | 26 | #define RTL821x_EXT_PAGE_SELECT 0x1e |
f609ab0e | 27 | #define RTL821x_PAGE_SELECT 0x1f |
097c2aa8 | 28 | |
f609ab0e | 29 | #define RTL8211F_INSR 0x1d |
ef3d9049 | 30 | |
f609ab0e | 31 | #define RTL8211F_TX_DELAY BIT(8) |
f81dadbc SS |
32 | #define RTL8211E_TX_DELAY BIT(1) |
33 | #define RTL8211E_RX_DELAY BIT(2) | |
34 | #define RTL8211E_MODE_MII_GMII BIT(3) | |
f609ab0e MB |
35 | |
36 | #define RTL8201F_ISR 0x1e | |
37 | #define RTL8201F_IER 0x13 | |
513588dd | 38 | |
d8545825 LW |
39 | #define RTL8366RB_POWER_SAVE 0x15 |
40 | #define RTL8366RB_POWER_SAVE_ON BIT(12) | |
41 | ||
097c2aa8 JL |
42 | MODULE_DESCRIPTION("Realtek PHY driver"); |
43 | MODULE_AUTHOR("Johnson Leung"); | |
44 | MODULE_LICENSE("GPL"); | |
45 | ||
d98c8ccd | 46 | static int rtl821x_read_page(struct phy_device *phydev) |
136819a6 | 47 | { |
d98c8ccd | 48 | return __phy_read(phydev, RTL821x_PAGE_SELECT); |
136819a6 MB |
49 | } |
50 | ||
d98c8ccd | 51 | static int rtl821x_write_page(struct phy_device *phydev, int page) |
136819a6 | 52 | { |
d98c8ccd | 53 | return __phy_write(phydev, RTL821x_PAGE_SELECT, page); |
136819a6 MB |
54 | } |
55 | ||
513588dd JB |
56 | static int rtl8201_ack_interrupt(struct phy_device *phydev) |
57 | { | |
58 | int err; | |
59 | ||
60 | err = phy_read(phydev, RTL8201F_ISR); | |
61 | ||
62 | return (err < 0) ? err : 0; | |
63 | } | |
64 | ||
097c2aa8 JL |
65 | static int rtl821x_ack_interrupt(struct phy_device *phydev) |
66 | { | |
67 | int err; | |
68 | ||
69 | err = phy_read(phydev, RTL821x_INSR); | |
70 | ||
71 | return (err < 0) ? err : 0; | |
72 | } | |
73 | ||
3447cf2e SL |
74 | static int rtl8211f_ack_interrupt(struct phy_device *phydev) |
75 | { | |
76 | int err; | |
77 | ||
d98c8ccd | 78 | err = phy_read_paged(phydev, 0xa43, RTL8211F_INSR); |
3447cf2e SL |
79 | |
80 | return (err < 0) ? err : 0; | |
81 | } | |
82 | ||
513588dd JB |
83 | static int rtl8201_config_intr(struct phy_device *phydev) |
84 | { | |
136819a6 | 85 | u16 val; |
513588dd JB |
86 | |
87 | if (phydev->interrupts == PHY_INTERRUPT_ENABLED) | |
136819a6 | 88 | val = BIT(13) | BIT(12) | BIT(11); |
513588dd | 89 | else |
136819a6 | 90 | val = 0; |
513588dd | 91 | |
d98c8ccd | 92 | return phy_write_paged(phydev, 0x7, RTL8201F_IER, val); |
513588dd JB |
93 | } |
94 | ||
ef3d9049 | 95 | static int rtl8211b_config_intr(struct phy_device *phydev) |
097c2aa8 JL |
96 | { |
97 | int err; | |
98 | ||
99 | if (phydev->interrupts == PHY_INTERRUPT_ENABLED) | |
100 | err = phy_write(phydev, RTL821x_INER, | |
69021e32 | 101 | RTL8211B_INER_INIT); |
097c2aa8 JL |
102 | else |
103 | err = phy_write(phydev, RTL821x_INER, 0); | |
104 | ||
105 | return err; | |
106 | } | |
107 | ||
ef3d9049 GC |
108 | static int rtl8211e_config_intr(struct phy_device *phydev) |
109 | { | |
110 | int err; | |
111 | ||
112 | if (phydev->interrupts == PHY_INTERRUPT_ENABLED) | |
113 | err = phy_write(phydev, RTL821x_INER, | |
8b64fd61 | 114 | RTL8211E_INER_LINK_STATUS); |
ef3d9049 GC |
115 | else |
116 | err = phy_write(phydev, RTL821x_INER, 0); | |
117 | ||
118 | return err; | |
119 | } | |
120 | ||
3447cf2e SL |
121 | static int rtl8211f_config_intr(struct phy_device *phydev) |
122 | { | |
136819a6 | 123 | u16 val; |
3447cf2e SL |
124 | |
125 | if (phydev->interrupts == PHY_INTERRUPT_ENABLED) | |
136819a6 | 126 | val = RTL8211F_INER_LINK_STATUS; |
3447cf2e | 127 | else |
136819a6 | 128 | val = 0; |
3447cf2e | 129 | |
d98c8ccd | 130 | return phy_write_paged(phydev, 0xa42, RTL821x_INER, val); |
3447cf2e SL |
131 | } |
132 | ||
d241d4aa HK |
133 | static int rtl8211_config_aneg(struct phy_device *phydev) |
134 | { | |
135 | int ret; | |
136 | ||
137 | ret = genphy_config_aneg(phydev); | |
138 | if (ret < 0) | |
139 | return ret; | |
140 | ||
141 | /* Quirk was copied from vendor driver. Unfortunately it includes no | |
142 | * description of the magic numbers. | |
143 | */ | |
144 | if (phydev->speed == SPEED_100 && phydev->autoneg == AUTONEG_DISABLE) { | |
145 | phy_write(phydev, 0x17, 0x2138); | |
146 | phy_write(phydev, 0x0e, 0x0260); | |
147 | } else { | |
148 | phy_write(phydev, 0x17, 0x2108); | |
149 | phy_write(phydev, 0x0e, 0x0000); | |
150 | } | |
151 | ||
152 | return 0; | |
153 | } | |
154 | ||
cf87915c HK |
155 | static int rtl8211c_config_init(struct phy_device *phydev) |
156 | { | |
157 | /* RTL8211C has an issue when operating in Gigabit slave mode */ | |
48e4adf9 HK |
158 | return phy_set_bits(phydev, MII_CTRL1000, |
159 | CTL1000_ENABLE_MASTER | CTL1000_AS_MASTER); | |
cf87915c HK |
160 | } |
161 | ||
3447cf2e SL |
162 | static int rtl8211f_config_init(struct phy_device *phydev) |
163 | { | |
1da7756e | 164 | u16 val; |
3447cf2e | 165 | |
1da7756e SS |
166 | /* enable TX-delay for rgmii-{id,txid}, and disable it for rgmii and |
167 | * rgmii-rxid. The RX-delay can be enabled by the external RXDLY pin. | |
168 | */ | |
169 | switch (phydev->interface) { | |
170 | case PHY_INTERFACE_MODE_RGMII: | |
171 | case PHY_INTERFACE_MODE_RGMII_RXID: | |
172 | val = 0; | |
173 | break; | |
174 | case PHY_INTERFACE_MODE_RGMII_ID: | |
175 | case PHY_INTERFACE_MODE_RGMII_TXID: | |
d98c8ccd | 176 | val = RTL8211F_TX_DELAY; |
1da7756e SS |
177 | break; |
178 | default: /* the rest of the modes imply leaving delay as is. */ | |
179 | return 0; | |
180 | } | |
3447cf2e | 181 | |
d98c8ccd | 182 | return phy_modify_paged(phydev, 0xd08, 0x11, RTL8211F_TX_DELAY, val); |
3447cf2e SL |
183 | } |
184 | ||
f81dadbc SS |
185 | static int rtl8211e_config_init(struct phy_device *phydev) |
186 | { | |
187 | int ret = 0, oldpage; | |
188 | u16 val; | |
189 | ||
190 | /* enable TX/RX delay for rgmii-* modes, and disable them for rgmii. */ | |
191 | switch (phydev->interface) { | |
192 | case PHY_INTERFACE_MODE_RGMII: | |
193 | val = 0; | |
194 | break; | |
195 | case PHY_INTERFACE_MODE_RGMII_ID: | |
196 | val = RTL8211E_TX_DELAY | RTL8211E_RX_DELAY; | |
197 | break; | |
198 | case PHY_INTERFACE_MODE_RGMII_RXID: | |
199 | val = RTL8211E_RX_DELAY; | |
200 | break; | |
201 | case PHY_INTERFACE_MODE_RGMII_TXID: | |
202 | val = RTL8211E_TX_DELAY; | |
203 | break; | |
204 | default: /* the rest of the modes imply leaving delays as is. */ | |
205 | return 0; | |
206 | } | |
207 | ||
208 | /* According to a sample driver there is a 0x1c config register on the | |
209 | * 0xa4 extension page (0x7) layout. It can be used to disable/enable | |
210 | * the RX/TX delays otherwise controlled by RXDLY/TXDLY pins. It can | |
211 | * also be used to customize the whole configuration register: | |
212 | * 8:6 = PHY Address, 5:4 = Auto-Negotiation, 3 = Interface Mode Select, | |
213 | * 2 = RX Delay, 1 = TX Delay, 0 = SELRGV (see original PHY datasheet | |
214 | * for details). | |
215 | */ | |
216 | oldpage = phy_select_page(phydev, 0x7); | |
217 | if (oldpage < 0) | |
218 | goto err_restore_page; | |
219 | ||
dffe7d2e | 220 | ret = __phy_write(phydev, RTL821x_EXT_PAGE_SELECT, 0xa4); |
f81dadbc SS |
221 | if (ret) |
222 | goto err_restore_page; | |
223 | ||
dffe7d2e KH |
224 | ret = __phy_modify(phydev, 0x1c, RTL8211E_TX_DELAY | RTL8211E_RX_DELAY, |
225 | val); | |
f81dadbc SS |
226 | |
227 | err_restore_page: | |
228 | return phy_restore_page(phydev, oldpage, ret); | |
229 | } | |
230 | ||
049ff57a HK |
231 | static int rtl8211b_suspend(struct phy_device *phydev) |
232 | { | |
233 | phy_write(phydev, MII_MMD_DATA, BIT(9)); | |
234 | ||
235 | return genphy_suspend(phydev); | |
236 | } | |
237 | ||
238 | static int rtl8211b_resume(struct phy_device *phydev) | |
239 | { | |
240 | phy_write(phydev, MII_MMD_DATA, 0); | |
241 | ||
242 | return genphy_resume(phydev); | |
243 | } | |
244 | ||
d8545825 LW |
245 | static int rtl8366rb_config_init(struct phy_device *phydev) |
246 | { | |
247 | int ret; | |
248 | ||
d8545825 LW |
249 | ret = phy_set_bits(phydev, RTL8366RB_POWER_SAVE, |
250 | RTL8366RB_POWER_SAVE_ON); | |
251 | if (ret) { | |
252 | dev_err(&phydev->mdio.dev, | |
253 | "error enabling power management\n"); | |
254 | } | |
255 | ||
256 | return ret; | |
257 | } | |
258 | ||
71b9c4a8 JK |
259 | static struct phy_driver realtek_drvs[] = { |
260 | { | |
ca494936 | 261 | PHY_ID_MATCH_EXACT(0x00008201), |
71b9c4a8 | 262 | .name = "RTL8201CP Ethernet", |
513588dd | 263 | }, { |
ca494936 | 264 | PHY_ID_MATCH_EXACT(0x001cc816), |
0432e833 | 265 | .name = "RTL8201F Fast Ethernet", |
513588dd JB |
266 | .ack_interrupt = &rtl8201_ack_interrupt, |
267 | .config_intr = &rtl8201_config_intr, | |
268 | .suspend = genphy_suspend, | |
269 | .resume = genphy_resume, | |
d98c8ccd HK |
270 | .read_page = rtl821x_read_page, |
271 | .write_page = rtl821x_write_page, | |
d241d4aa | 272 | }, { |
ca494936 | 273 | PHY_ID_MATCH_EXACT(0x001cc910), |
d241d4aa | 274 | .name = "RTL8211 Gigabit Ethernet", |
d241d4aa HK |
275 | .config_aneg = rtl8211_config_aneg, |
276 | .read_mmd = &genphy_read_mmd_unsupported, | |
277 | .write_mmd = &genphy_write_mmd_unsupported, | |
daf3ddbe HK |
278 | .read_page = rtl821x_read_page, |
279 | .write_page = rtl821x_write_page, | |
71b9c4a8 | 280 | }, { |
ca494936 | 281 | PHY_ID_MATCH_EXACT(0x001cc912), |
71b9c4a8 | 282 | .name = "RTL8211B Gigabit Ethernet", |
71b9c4a8 JK |
283 | .ack_interrupt = &rtl821x_ack_interrupt, |
284 | .config_intr = &rtl8211b_config_intr, | |
0231b1a0 KH |
285 | .read_mmd = &genphy_read_mmd_unsupported, |
286 | .write_mmd = &genphy_write_mmd_unsupported, | |
049ff57a HK |
287 | .suspend = rtl8211b_suspend, |
288 | .resume = rtl8211b_resume, | |
daf3ddbe HK |
289 | .read_page = rtl821x_read_page, |
290 | .write_page = rtl821x_write_page, | |
cf87915c | 291 | }, { |
ca494936 | 292 | PHY_ID_MATCH_EXACT(0x001cc913), |
cf87915c | 293 | .name = "RTL8211C Gigabit Ethernet", |
cf87915c HK |
294 | .config_init = rtl8211c_config_init, |
295 | .read_mmd = &genphy_read_mmd_unsupported, | |
296 | .write_mmd = &genphy_write_mmd_unsupported, | |
daf3ddbe HK |
297 | .read_page = rtl821x_read_page, |
298 | .write_page = rtl821x_write_page, | |
0024f892 | 299 | }, { |
ca494936 | 300 | PHY_ID_MATCH_EXACT(0x001cc914), |
0024f892 | 301 | .name = "RTL8211DN Gigabit Ethernet", |
0024f892 SX |
302 | .ack_interrupt = rtl821x_ack_interrupt, |
303 | .config_intr = rtl8211e_config_intr, | |
304 | .suspend = genphy_suspend, | |
305 | .resume = genphy_resume, | |
daf3ddbe HK |
306 | .read_page = rtl821x_read_page, |
307 | .write_page = rtl821x_write_page, | |
71b9c4a8 | 308 | }, { |
ca494936 | 309 | PHY_ID_MATCH_EXACT(0x001cc915), |
71b9c4a8 | 310 | .name = "RTL8211E Gigabit Ethernet", |
f81dadbc | 311 | .config_init = &rtl8211e_config_init, |
71b9c4a8 JK |
312 | .ack_interrupt = &rtl821x_ack_interrupt, |
313 | .config_intr = &rtl8211e_config_intr, | |
314 | .suspend = genphy_suspend, | |
315 | .resume = genphy_resume, | |
daf3ddbe HK |
316 | .read_page = rtl821x_read_page, |
317 | .write_page = rtl821x_write_page, | |
3447cf2e | 318 | }, { |
ca494936 | 319 | PHY_ID_MATCH_EXACT(0x001cc916), |
3447cf2e | 320 | .name = "RTL8211F Gigabit Ethernet", |
3447cf2e | 321 | .config_init = &rtl8211f_config_init, |
3447cf2e SL |
322 | .ack_interrupt = &rtl8211f_ack_interrupt, |
323 | .config_intr = &rtl8211f_config_intr, | |
324 | .suspend = genphy_suspend, | |
325 | .resume = genphy_resume, | |
d98c8ccd HK |
326 | .read_page = rtl821x_read_page, |
327 | .write_page = rtl821x_write_page, | |
f66ebd14 HK |
328 | }, { |
329 | PHY_ID_MATCH_EXACT(0x001cc800), | |
330 | .name = "Generic Realtek PHY", | |
f66ebd14 HK |
331 | .suspend = genphy_suspend, |
332 | .resume = genphy_resume, | |
333 | .read_page = rtl821x_read_page, | |
334 | .write_page = rtl821x_write_page, | |
d8545825 | 335 | }, { |
ca494936 | 336 | PHY_ID_MATCH_EXACT(0x001cc961), |
d8545825 | 337 | .name = "RTL8366RB Gigabit Ethernet", |
d8545825 | 338 | .config_init = &rtl8366rb_config_init, |
4c8e0459 LW |
339 | /* These interrupts are handled by the irq controller |
340 | * embedded inside the RTL8366RB, they get unmasked when the | |
341 | * irq is requested and ACKed by reading the status register, | |
342 | * which is done by the irqchip code. | |
343 | */ | |
344 | .ack_interrupt = genphy_no_ack_interrupt, | |
345 | .config_intr = genphy_no_config_intr, | |
d8545825 LW |
346 | .suspend = genphy_suspend, |
347 | .resume = genphy_resume, | |
71b9c4a8 | 348 | }, |
097c2aa8 JL |
349 | }; |
350 | ||
50fd7150 | 351 | module_phy_driver(realtek_drvs); |
4e4f10f6 | 352 | |
3b73e842 | 353 | static const struct mdio_device_id __maybe_unused realtek_tbl[] = { |
ca494936 | 354 | { PHY_ID_MATCH_VENDOR(0x001cc800) }, |
4e4f10f6 DW |
355 | { } |
356 | }; | |
357 | ||
358 | MODULE_DEVICE_TABLE(mdio, realtek_tbl); |