1 | /* $NetBSD: ciphy.c,v 1.26 2016/07/07 06:55:41 msaitoh Exp $ */ |
2 | |
3 | /*- |
4 | * Copyright (c) 2004 |
5 | * Bill Paul <wpaul@windriver.com>. All rights reserved. |
6 | * |
7 | * Redistribution and use in source and binary forms, with or without |
8 | * modification, are permitted provided that the following conditions |
9 | * are met: |
10 | * 1. Redistributions of source code must retain the above copyright |
11 | * notice, this list of conditions and the following disclaimer. |
12 | * 2. Redistributions in binary form must reproduce the above copyright |
13 | * notice, this list of conditions and the following disclaimer in the |
14 | * documentation and/or other materials provided with the distribution. |
15 | * 3. All advertising materials mentioning features or use of this software |
16 | * must display the following acknowledgement: |
17 | * This product includes software developed by Bill Paul. |
18 | * 4. Neither the name of the author nor the names of any co-contributors |
19 | * may be used to endorse or promote products derived from this software |
20 | * without specific prior written permission. |
21 | * |
22 | * THIS SOFTWARE IS PROVIDED BY Bill Paul AND CONTRIBUTORS ``AS IS'' AND |
23 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
24 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
25 | * ARE DISCLAIMED. IN NO EVENT SHALL Bill Paul OR THE VOICES IN HIS HEAD |
26 | * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
27 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
28 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
29 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
30 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
31 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
32 | * THE POSSIBILITY OF SUCH DAMAGE. |
33 | * |
34 | * FreeBSD: src/sys/dev/mii/ciphy.c,v 1.2 2005/01/06 01:42:55 imp Exp |
35 | */ |
36 | |
37 | #include <sys/cdefs.h> |
38 | __KERNEL_RCSID(0, "$NetBSD: ciphy.c,v 1.26 2016/07/07 06:55:41 msaitoh Exp $" ); |
39 | |
40 | /* |
41 | * Driver for the Cicada CS8201 10/100/1000 copper PHY. |
42 | */ |
43 | |
44 | #include <sys/param.h> |
45 | #include <sys/systm.h> |
46 | #include <sys/device.h> |
47 | #include <sys/kernel.h> |
48 | #include <sys/socket.h> |
49 | #include <sys/bus.h> |
50 | |
51 | #include <net/if.h> |
52 | #include <net/if_arp.h> |
53 | #include <net/if_media.h> |
54 | |
55 | #include <dev/mii/mii.h> |
56 | #include <dev/mii/miivar.h> |
57 | #include <dev/mii/miidevs.h> |
58 | |
59 | #include <dev/mii/ciphyreg.h> |
60 | |
61 | static int ciphymatch(device_t, cfdata_t, void *); |
62 | static void ciphyattach(device_t, device_t, void *); |
63 | |
64 | CFATTACH_DECL_NEW(ciphy, sizeof(struct mii_softc), |
65 | ciphymatch, ciphyattach, mii_phy_detach, mii_phy_activate); |
66 | |
67 | static int ciphy_service(struct mii_softc *, struct mii_data *, int); |
68 | static void ciphy_status(struct mii_softc *); |
69 | static void ciphy_reset(struct mii_softc *); |
70 | static void ciphy_fixup(struct mii_softc *); |
71 | |
72 | static const struct mii_phy_funcs ciphy_funcs = { |
73 | ciphy_service, ciphy_status, mii_phy_reset, |
74 | }; |
75 | |
76 | static const struct mii_phydesc ciphys[] = { |
77 | { MII_OUI_CICADA, MII_MODEL_CICADA_CS8201, |
78 | MII_STR_CICADA_CS8201 }, |
79 | |
80 | { MII_OUI_CICADA, MII_MODEL_CICADA_CS8201A, |
81 | MII_STR_CICADA_CS8201A }, |
82 | |
83 | { MII_OUI_CICADA, MII_MODEL_CICADA_CS8201B, |
84 | MII_STR_CICADA_CS8201B }, |
85 | |
86 | { MII_OUI_xxCICADA, MII_MODEL_CICADA_CS8201, |
87 | MII_STR_CICADA_CS8201 }, |
88 | |
89 | { MII_OUI_xxCICADA, MII_MODEL_CICADA_CS8201A, |
90 | MII_STR_CICADA_CS8201A }, |
91 | |
92 | { MII_OUI_xxCICADA, MII_MODEL_xxCICADA_CS8201B, |
93 | MII_STR_xxCICADA_CS8201B }, |
94 | |
95 | { 0, 0, |
96 | NULL }, |
97 | }; |
98 | |
99 | static int |
100 | ciphymatch(device_t parent, cfdata_t match, |
101 | void *aux) |
102 | { |
103 | struct mii_attach_args *ma = aux; |
104 | |
105 | if (mii_phy_match(ma, ciphys) != NULL) |
106 | return (10); |
107 | |
108 | return (0); |
109 | } |
110 | |
111 | static void |
112 | ciphyattach(device_t parent, device_t self, void *aux) |
113 | { |
114 | struct mii_softc *sc = device_private(self); |
115 | struct mii_attach_args *ma = aux; |
116 | struct mii_data *mii = ma->mii_data; |
117 | const struct mii_phydesc *mpd; |
118 | |
119 | mpd = mii_phy_match(ma, ciphys); |
120 | aprint_naive(": Media interface\n" ); |
121 | aprint_normal(": %s, rev. %d\n" , mpd->mpd_name, MII_REV(ma->mii_id2)); |
122 | |
123 | sc->mii_dev = self; |
124 | sc->mii_inst = mii->mii_instance; |
125 | sc->mii_phy = ma->mii_phyno; |
126 | sc->mii_funcs = &ciphy_funcs; |
127 | sc->mii_pdata = mii; |
128 | sc->mii_flags = ma->mii_flags; |
129 | sc->mii_anegticks = MII_ANEGTICKS; |
130 | |
131 | sc->mii_flags |= MIIF_NOISOLATE; |
132 | |
133 | ciphy_reset(sc); |
134 | |
135 | sc->mii_capabilities = PHY_READ(sc, MII_BMSR) & ma->mii_capmask; |
136 | if (sc->mii_capabilities & BMSR_EXTSTAT) |
137 | sc->mii_extcapabilities = PHY_READ(sc, MII_EXTSR); |
138 | aprint_normal_dev(self, "" ); |
139 | if ((sc->mii_capabilities & BMSR_MEDIAMASK) == 0) |
140 | aprint_error("no media present" ); |
141 | else |
142 | mii_phy_add_media(sc); |
143 | aprint_normal("\n" ); |
144 | } |
145 | |
146 | static int |
147 | ciphy_service(struct mii_softc *sc, struct mii_data *mii, int cmd) |
148 | { |
149 | struct ifmedia_entry *ife = mii->mii_media.ifm_cur; |
150 | int reg, speed, gig; |
151 | |
152 | switch (cmd) { |
153 | case MII_POLLSTAT: |
154 | /* |
155 | * If we're not polling our PHY instance, just return. |
156 | */ |
157 | if (IFM_INST(ife->ifm_media) != sc->mii_inst) |
158 | return (0); |
159 | break; |
160 | |
161 | case MII_MEDIACHG: |
162 | /* |
163 | * If the media indicates a different PHY instance, |
164 | * isolate ourselves. |
165 | */ |
166 | if (IFM_INST(ife->ifm_media) != sc->mii_inst) { |
167 | reg = PHY_READ(sc, MII_BMCR); |
168 | PHY_WRITE(sc, MII_BMCR, reg | BMCR_ISO); |
169 | return (0); |
170 | } |
171 | |
172 | /* |
173 | * If the interface is not up, don't do anything. |
174 | */ |
175 | if ((mii->mii_ifp->if_flags & IFF_UP) == 0) |
176 | break; |
177 | |
178 | ciphy_fixup(sc); /* XXX hardware bug work-around */ |
179 | |
180 | switch (IFM_SUBTYPE(ife->ifm_media)) { |
181 | case IFM_AUTO: |
182 | #ifdef foo |
183 | /* |
184 | * If we're already in auto mode, just return. |
185 | */ |
186 | if (PHY_READ(sc, CIPHY_MII_BMCR) & CIPHY_BMCR_AUTOEN) |
187 | return (0); |
188 | #endif |
189 | (void) mii_phy_auto(sc, 0); |
190 | break; |
191 | case IFM_1000_T: |
192 | speed = CIPHY_S1000; |
193 | goto setit; |
194 | case IFM_100_TX: |
195 | speed = CIPHY_S100; |
196 | goto setit; |
197 | case IFM_10_T: |
198 | speed = CIPHY_S10; |
199 | setit: |
200 | if ((ife->ifm_media & IFM_GMASK) == IFM_FDX) { |
201 | speed |= CIPHY_BMCR_FDX; |
202 | gig = CIPHY_1000CTL_AFD; |
203 | } else { |
204 | gig = CIPHY_1000CTL_AHD; |
205 | } |
206 | |
207 | PHY_WRITE(sc, CIPHY_MII_1000CTL, 0); |
208 | PHY_WRITE(sc, CIPHY_MII_BMCR, speed); |
209 | PHY_WRITE(sc, CIPHY_MII_ANAR, CIPHY_SEL_TYPE); |
210 | |
211 | if (IFM_SUBTYPE(ife->ifm_media) != IFM_1000_T) |
212 | break; |
213 | |
214 | PHY_WRITE(sc, CIPHY_MII_1000CTL, gig); |
215 | PHY_WRITE(sc, CIPHY_MII_BMCR, |
216 | speed|CIPHY_BMCR_AUTOEN|CIPHY_BMCR_STARTNEG); |
217 | |
218 | /* |
219 | * When setting the link manually, one side must |
220 | * be the master and the other the slave. However |
221 | * ifmedia doesn't give us a good way to specify |
222 | * this, so we fake it by using one of the LINK |
223 | * flags. If LINK0 is set, we program the PHY to |
224 | * be a master, otherwise it's a slave. |
225 | */ |
226 | if ((mii->mii_ifp->if_flags & IFF_LINK0)) { |
227 | PHY_WRITE(sc, CIPHY_MII_1000CTL, |
228 | gig|CIPHY_1000CTL_MSE|CIPHY_1000CTL_MSC); |
229 | } else { |
230 | PHY_WRITE(sc, CIPHY_MII_1000CTL, |
231 | gig|CIPHY_1000CTL_MSE); |
232 | } |
233 | break; |
234 | case IFM_NONE: |
235 | PHY_WRITE(sc, MII_BMCR, BMCR_ISO|BMCR_PDOWN); |
236 | break; |
237 | case IFM_100_T4: |
238 | default: |
239 | return (EINVAL); |
240 | } |
241 | break; |
242 | |
243 | case MII_TICK: |
244 | /* |
245 | * If we're not currently selected, just return. |
246 | */ |
247 | if (IFM_INST(ife->ifm_media) != sc->mii_inst) |
248 | return (0); |
249 | |
250 | /* |
251 | * Is the interface even up? |
252 | */ |
253 | if ((mii->mii_ifp->if_flags & IFF_UP) == 0) |
254 | return (0); |
255 | |
256 | /* |
257 | * Only used for autonegotiation. |
258 | */ |
259 | if ((IFM_SUBTYPE(ife->ifm_media) != IFM_AUTO) && |
260 | (IFM_SUBTYPE(ife->ifm_media) != IFM_1000_T)) { |
261 | /* |
262 | * Reset autonegotiation timer to 0 just to make sure |
263 | * the future autonegotiation start with 0. |
264 | */ |
265 | sc->mii_ticks = 0; |
266 | break; |
267 | } |
268 | |
269 | /* |
270 | * Check to see if we have link. If we do, we don't |
271 | * need to restart the autonegotiation process. Read |
272 | * the BMSR twice in case it's latched. |
273 | */ |
274 | reg = PHY_READ(sc, MII_BMSR) | PHY_READ(sc, MII_BMSR); |
275 | if (reg & BMSR_LINK) { |
276 | /* |
277 | * Reset autonegotiation timer to 0 in case the link |
278 | * goes down in the next tick. |
279 | */ |
280 | sc->mii_ticks = 0; |
281 | /* See above. */ |
282 | break; |
283 | } |
284 | |
285 | /* |
286 | * mii_ticks == 0 means it's the first tick after changing the |
287 | * media or the link became down since the last tick |
288 | * (see above), so return with 0 to update the status. |
289 | */ |
290 | if (sc->mii_ticks++ == 0) |
291 | break; |
292 | |
293 | /* |
294 | * Only retry autonegotiation every N seconds. |
295 | */ |
296 | if (sc->mii_ticks <= MII_ANEGTICKS_GIGE) |
297 | break; |
298 | |
299 | mii_phy_auto(sc, 0); |
300 | return (0); |
301 | } |
302 | |
303 | /* Update the media status. */ |
304 | ciphy_status(sc); |
305 | |
306 | /* |
307 | * Callback if something changed. Note that we need to poke |
308 | * apply fixups for certain PHY revs. |
309 | */ |
310 | if (sc->mii_media_active != mii->mii_media_active || |
311 | sc->mii_media_status != mii->mii_media_status || |
312 | cmd == MII_MEDIACHG) { |
313 | ciphy_fixup(sc); |
314 | } |
315 | mii_phy_update(sc, cmd); |
316 | return (0); |
317 | } |
318 | |
319 | static void |
320 | ciphy_status(struct mii_softc *sc) |
321 | { |
322 | struct mii_data *mii = sc->mii_pdata; |
323 | int bmsr, bmcr; |
324 | |
325 | mii->mii_media_status = IFM_AVALID; |
326 | mii->mii_media_active = IFM_ETHER; |
327 | |
328 | bmsr = PHY_READ(sc, MII_BMSR) | PHY_READ(sc, MII_BMSR); |
329 | |
330 | if (bmsr & BMSR_LINK) |
331 | mii->mii_media_status |= IFM_ACTIVE; |
332 | |
333 | bmcr = PHY_READ(sc, CIPHY_MII_BMCR); |
334 | |
335 | if (bmcr & CIPHY_BMCR_LOOP) |
336 | mii->mii_media_active |= IFM_LOOP; |
337 | |
338 | if (bmcr & CIPHY_BMCR_AUTOEN) { |
339 | if ((bmsr & CIPHY_BMSR_ACOMP) == 0) { |
340 | /* Erg, still trying, I guess... */ |
341 | mii->mii_media_active |= IFM_NONE; |
342 | return; |
343 | } |
344 | } |
345 | |
346 | bmsr = PHY_READ(sc, CIPHY_MII_AUXCSR); |
347 | switch (bmsr & CIPHY_AUXCSR_SPEED) { |
348 | case CIPHY_SPEED10: |
349 | mii->mii_media_active |= IFM_10_T; |
350 | break; |
351 | case CIPHY_SPEED100: |
352 | mii->mii_media_active |= IFM_100_TX; |
353 | break; |
354 | case CIPHY_SPEED1000: |
355 | mii->mii_media_active |= IFM_1000_T; |
356 | break; |
357 | default: |
358 | aprint_error_dev(sc->mii_dev, "unknown PHY speed %x\n" , |
359 | bmsr & CIPHY_AUXCSR_SPEED); |
360 | break; |
361 | } |
362 | |
363 | if (bmsr & CIPHY_AUXCSR_FDX) |
364 | mii->mii_media_active |= IFM_FDX; |
365 | else |
366 | mii->mii_media_active |= IFM_HDX; |
367 | |
368 | return; |
369 | } |
370 | |
371 | static void |
372 | ciphy_reset(struct mii_softc *sc) |
373 | { |
374 | mii_phy_reset(sc); |
375 | DELAY(1000); |
376 | |
377 | return; |
378 | } |
379 | |
380 | #define PHY_SETBIT(x, y, z) \ |
381 | PHY_WRITE(x, y, (PHY_READ(x, y) | (z))) |
382 | #define PHY_CLRBIT(x, y, z) \ |
383 | PHY_WRITE(x, y, (PHY_READ(x, y) & ~(z))) |
384 | |
385 | static void |
386 | ciphy_fixup(struct mii_softc *sc) |
387 | { |
388 | uint16_t model; |
389 | uint16_t status, speed; |
390 | |
391 | model = MII_MODEL(PHY_READ(sc, CIPHY_MII_PHYIDR2)); |
392 | status = PHY_READ(sc, CIPHY_MII_AUXCSR); |
393 | speed = status & CIPHY_AUXCSR_SPEED; |
394 | |
395 | if (device_is_a(device_parent(sc->mii_dev), "nfe" )) { |
396 | /* need to set for 2.5V RGMII for NVIDIA adapters */ |
397 | PHY_SETBIT(sc, CIPHY_MII_ECTL1, CIPHY_INTSEL_RGMII); |
398 | PHY_SETBIT(sc, CIPHY_MII_ECTL1, CIPHY_IOVOL_2500MV); |
399 | } |
400 | |
401 | switch (model) { |
402 | case MII_MODEL_CICADA_CS8201: |
403 | |
404 | /* Turn off "aux mode" (whatever that means) */ |
405 | PHY_SETBIT(sc, CIPHY_MII_AUXCSR, CIPHY_AUXCSR_MDPPS); |
406 | |
407 | /* |
408 | * Work around speed polling bug in VT3119/VT3216 |
409 | * when using MII in full duplex mode. |
410 | */ |
411 | if ((speed == CIPHY_SPEED10 || speed == CIPHY_SPEED100) && |
412 | (status & CIPHY_AUXCSR_FDX)) { |
413 | PHY_SETBIT(sc, CIPHY_MII_10BTCSR, CIPHY_10BTCSR_ECHO); |
414 | } else { |
415 | PHY_CLRBIT(sc, CIPHY_MII_10BTCSR, CIPHY_10BTCSR_ECHO); |
416 | } |
417 | |
418 | /* Enable link/activity LED blink. */ |
419 | PHY_SETBIT(sc, CIPHY_MII_LED, CIPHY_LED_LINKACTBLINK); |
420 | |
421 | break; |
422 | |
423 | case MII_MODEL_CICADA_CS8201A: |
424 | case MII_MODEL_CICADA_CS8201B: |
425 | |
426 | /* |
427 | * Work around speed polling bug in VT3119/VT3216 |
428 | * when using MII in full duplex mode. |
429 | */ |
430 | if ((speed == CIPHY_SPEED10 || speed == CIPHY_SPEED100) && |
431 | (status & CIPHY_AUXCSR_FDX)) { |
432 | PHY_SETBIT(sc, CIPHY_MII_10BTCSR, CIPHY_10BTCSR_ECHO); |
433 | } else { |
434 | PHY_CLRBIT(sc, CIPHY_MII_10BTCSR, CIPHY_10BTCSR_ECHO); |
435 | } |
436 | |
437 | break; |
438 | default: |
439 | aprint_error_dev(sc->mii_dev, "unknown CICADA PHY model %x\n" , |
440 | model); |
441 | break; |
442 | } |
443 | |
444 | return; |
445 | } |
446 | |