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
61static int ciphymatch(device_t, cfdata_t, void *);
62static void ciphyattach(device_t, device_t, void *);
63
64CFATTACH_DECL_NEW(ciphy, sizeof(struct mii_softc),
65 ciphymatch, ciphyattach, mii_phy_detach, mii_phy_activate);
66
67static int ciphy_service(struct mii_softc *, struct mii_data *, int);
68static void ciphy_status(struct mii_softc *);
69static void ciphy_reset(struct mii_softc *);
70static void ciphy_fixup(struct mii_softc *);
71
72static const struct mii_phy_funcs ciphy_funcs = {
73 ciphy_service, ciphy_status, mii_phy_reset,
74};
75
76static 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
99static int
100ciphymatch(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
111static void
112ciphyattach(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
146static int
147ciphy_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;
199setit:
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
319static void
320ciphy_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
371static void
372ciphy_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
385static void
386ciphy_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