xref: /freebsd/sys/dev/cxgb/common/cxgb_vsc8211.c (revision 79775f8f)
1 /**************************************************************************
2 
3 Copyright (c) 2007, Chelsio Inc.
4 All rights reserved.
5 
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions are met:
8 
9  1. Redistributions of source code must retain the above copyright notice,
10     this list of conditions and the following disclaimer.
11 
12  2. Neither the name of the Chelsio Corporation nor the names of its
13     contributors may be used to endorse or promote products derived from
14     this software without specific prior written permission.
15 
16 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 POSSIBILITY OF SUCH DAMAGE.
27 
28 ***************************************************************************/
29 
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
32 
33 #include <cxgb_include.h>
34 
35 #undef msleep
36 #define msleep t3_os_sleep
37 
38 /* VSC8211 PHY specific registers. */
39 enum {
40 	VSC8211_SIGDET_CTRL   = 19,
41 	VSC8211_EXT_CTRL      = 23,
42 	VSC8211_INTR_ENABLE   = 25,
43 	VSC8211_INTR_STATUS   = 26,
44 	VSC8211_LED_CTRL      = 27,
45 	VSC8211_AUX_CTRL_STAT = 28,
46 	VSC8211_EXT_PAGE_AXS  = 31,
47 };
48 
49 enum {
50 	VSC_INTR_RX_ERR     = 1 << 0,
51 	VSC_INTR_MS_ERR     = 1 << 1,  /* master/slave resolution error */
52 	VSC_INTR_CABLE      = 1 << 2,  /* cable impairment */
53 	VSC_INTR_FALSE_CARR = 1 << 3,  /* false carrier */
54 	VSC_INTR_MEDIA_CHG  = 1 << 4,  /* AMS media change */
55 	VSC_INTR_RX_FIFO    = 1 << 5,  /* Rx FIFO over/underflow */
56 	VSC_INTR_TX_FIFO    = 1 << 6,  /* Tx FIFO over/underflow */
57 	VSC_INTR_DESCRAMBL  = 1 << 7,  /* descrambler lock-lost */
58 	VSC_INTR_SYMBOL_ERR = 1 << 8,  /* symbol error */
59 	VSC_INTR_NEG_DONE   = 1 << 10, /* autoneg done */
60 	VSC_INTR_NEG_ERR    = 1 << 11, /* autoneg error */
61 	VSC_INTR_DPLX_CHG   = 1 << 12, /* duplex change */
62 	VSC_INTR_LINK_CHG   = 1 << 13, /* link change */
63 	VSC_INTR_SPD_CHG    = 1 << 14, /* speed change */
64 	VSC_INTR_ENABLE     = 1 << 15, /* interrupt enable */
65 };
66 
67 enum {
68 	VSC_CTRL_CLAUSE37_VIEW = 1 << 4,   /* Switch to Clause 37 view */
69 	VSC_CTRL_MEDIA_MODE_HI = 0xf000    /* High part of media mode select */
70 };
71 
72 #define CFG_CHG_INTR_MASK (VSC_INTR_LINK_CHG | VSC_INTR_NEG_ERR | \
73 			   VSC_INTR_DPLX_CHG | VSC_INTR_SPD_CHG | \
74 	 		   VSC_INTR_NEG_DONE)
75 #define INTR_MASK (CFG_CHG_INTR_MASK | VSC_INTR_TX_FIFO | VSC_INTR_RX_FIFO | \
76 		   VSC_INTR_ENABLE)
77 
78 /* PHY specific auxiliary control & status register fields */
79 #define S_ACSR_ACTIPHY_TMR    0
80 #define M_ACSR_ACTIPHY_TMR    0x3
81 #define V_ACSR_ACTIPHY_TMR(x) ((x) << S_ACSR_ACTIPHY_TMR)
82 
83 #define S_ACSR_SPEED    3
84 #define M_ACSR_SPEED    0x3
85 #define G_ACSR_SPEED(x) (((x) >> S_ACSR_SPEED) & M_ACSR_SPEED)
86 
87 #define S_ACSR_DUPLEX 5
88 #define F_ACSR_DUPLEX (1 << S_ACSR_DUPLEX)
89 
90 #define S_ACSR_ACTIPHY 6
91 #define F_ACSR_ACTIPHY (1 << S_ACSR_ACTIPHY)
92 
93 /*
94  * Reset the PHY.  This PHY completes reset immediately so we never wait.
95  */
96 static int vsc8211_reset(struct cphy *cphy, int wait)
97 {
98 	return t3_phy_reset(cphy, 0, 0);
99 }
100 
101 static int vsc8211_intr_enable(struct cphy *cphy)
102 {
103 	return mdio_write(cphy, 0, VSC8211_INTR_ENABLE, INTR_MASK);
104 }
105 
106 static int vsc8211_intr_disable(struct cphy *cphy)
107 {
108 	return mdio_write(cphy, 0, VSC8211_INTR_ENABLE, 0);
109 }
110 
111 static int vsc8211_intr_clear(struct cphy *cphy)
112 {
113 	u32 val;
114 
115 	/* Clear PHY interrupts by reading the register. */
116 	return mdio_read(cphy, 0, VSC8211_INTR_STATUS, &val);
117 }
118 
119 static int vsc8211_autoneg_enable(struct cphy *cphy)
120 {
121 	return t3_mdio_change_bits(cphy, 0, MII_BMCR, BMCR_PDOWN | BMCR_ISOLATE,
122 				   BMCR_ANENABLE | BMCR_ANRESTART);
123 }
124 
125 static int vsc8211_autoneg_restart(struct cphy *cphy)
126 {
127 	return t3_mdio_change_bits(cphy, 0, MII_BMCR, BMCR_PDOWN | BMCR_ISOLATE,
128 				   BMCR_ANRESTART);
129 }
130 
131 static int vsc8211_get_link_status(struct cphy *cphy, int *link_ok,
132 				     int *speed, int *duplex, int *fc)
133 {
134 	unsigned int bmcr, status, lpa, adv;
135 	int err, sp = -1, dplx = -1, pause = 0;
136 
137 	err = mdio_read(cphy, 0, MII_BMCR, &bmcr);
138 	if (!err)
139 		err = mdio_read(cphy, 0, MII_BMSR, &status);
140 	if (err)
141 		return err;
142 
143 	if (link_ok) {
144 		/*
145 		 * BMSR_LSTATUS is latch-low, so if it is 0 we need to read it
146 		 * once more to get the current link state.
147 		 */
148 		if (!(status & BMSR_LSTATUS))
149 			err = mdio_read(cphy, 0, MII_BMSR, &status);
150 		if (err)
151 			return err;
152 		*link_ok = (status & BMSR_LSTATUS) != 0;
153 	}
154 	if (!(bmcr & BMCR_ANENABLE)) {
155 		dplx = (bmcr & BMCR_FULLDPLX) ? DUPLEX_FULL : DUPLEX_HALF;
156 		if (bmcr & BMCR_SPEED1000)
157 			sp = SPEED_1000;
158 		else if (bmcr & BMCR_SPEED100)
159 			sp = SPEED_100;
160 		else
161 			sp = SPEED_10;
162 	} else if (status & BMSR_ANEGCOMPLETE) {
163 		err = mdio_read(cphy, 0, VSC8211_AUX_CTRL_STAT, &status);
164 		if (err)
165 			return err;
166 
167 		dplx = (status & F_ACSR_DUPLEX) ? DUPLEX_FULL : DUPLEX_HALF;
168 		sp = G_ACSR_SPEED(status);
169 		if (sp == 0)
170 			sp = SPEED_10;
171 		else if (sp == 1)
172 			sp = SPEED_100;
173 		else
174 			sp = SPEED_1000;
175 
176 		if (fc && dplx == DUPLEX_FULL) {
177 			err = mdio_read(cphy, 0, MII_LPA, &lpa);
178 			if (!err)
179 				err = mdio_read(cphy, 0, MII_ADVERTISE, &adv);
180 			if (err)
181 				return err;
182 
183 			if (lpa & adv & ADVERTISE_PAUSE_CAP)
184 				pause = PAUSE_RX | PAUSE_TX;
185 			else if ((lpa & ADVERTISE_PAUSE_CAP) &&
186 				 (lpa & ADVERTISE_PAUSE_ASYM) &&
187 				 (adv & ADVERTISE_PAUSE_ASYM))
188 				pause = PAUSE_TX;
189 			else if ((lpa & ADVERTISE_PAUSE_ASYM) &&
190 				 (adv & ADVERTISE_PAUSE_CAP))
191 				pause = PAUSE_RX;
192 		}
193 	}
194 	if (speed)
195 		*speed = sp;
196 	if (duplex)
197 		*duplex = dplx;
198 	if (fc)
199 		*fc = pause;
200 	return 0;
201 }
202 
203 static int vsc8211_get_link_status_fiber(struct cphy *cphy, int *link_ok,
204 					 int *speed, int *duplex, int *fc)
205 {
206 	unsigned int bmcr, status, lpa, adv;
207 	int err, sp = -1, dplx = -1, pause = 0;
208 
209 	err = mdio_read(cphy, 0, MII_BMCR, &bmcr);
210 	if (!err)
211 		err = mdio_read(cphy, 0, MII_BMSR, &status);
212 	if (err)
213 		return err;
214 
215 	if (link_ok) {
216 		/*
217 		 * BMSR_LSTATUS is latch-low, so if it is 0 we need to read it
218 		 * once more to get the current link state.
219 		 */
220 		if (!(status & BMSR_LSTATUS))
221 			err = mdio_read(cphy, 0, MII_BMSR, &status);
222 		if (err)
223 			return err;
224 		*link_ok = (status & BMSR_LSTATUS) != 0;
225 	}
226 	if (!(bmcr & BMCR_ANENABLE)) {
227 		dplx = (bmcr & BMCR_FULLDPLX) ? DUPLEX_FULL : DUPLEX_HALF;
228 		if (bmcr & BMCR_SPEED1000)
229 			sp = SPEED_1000;
230 		else if (bmcr & BMCR_SPEED100)
231 			sp = SPEED_100;
232 		else
233 			sp = SPEED_10;
234 	} else if (status & BMSR_ANEGCOMPLETE) {
235 		err = mdio_read(cphy, 0, MII_LPA, &lpa);
236 		if (!err)
237 			err = mdio_read(cphy, 0, MII_ADVERTISE, &adv);
238 		if (err)
239 			return err;
240 
241 		if (adv & lpa & ADVERTISE_1000XFULL) {
242 			dplx = DUPLEX_FULL;
243 			sp = SPEED_1000;
244 		} else if (adv & lpa & ADVERTISE_1000XHALF) {
245 			dplx = DUPLEX_HALF;
246 			sp = SPEED_1000;
247 		}
248 
249 		if (fc && dplx == DUPLEX_FULL) {
250 			if (lpa & adv & ADVERTISE_1000XPAUSE)
251 				pause = PAUSE_RX | PAUSE_TX;
252 			else if ((lpa & ADVERTISE_1000XPAUSE) &&
253 				 (adv & lpa & ADVERTISE_1000XPSE_ASYM))
254 				pause = PAUSE_TX;
255 			else if ((lpa & ADVERTISE_1000XPSE_ASYM) &&
256 				 (adv & ADVERTISE_1000XPAUSE))
257 				pause = PAUSE_RX;
258 		}
259 	}
260 	if (speed)
261 		*speed = sp;
262 	if (duplex)
263 		*duplex = dplx;
264 	if (fc)
265 		*fc = pause;
266 	return 0;
267 }
268 
269 /*
270  * Enable/disable auto MDI/MDI-X in forced link speed mode.
271  */
272 static int vsc8211_set_automdi(struct cphy *phy, int enable)
273 {
274 	int err;
275 
276 	if ((err = mdio_write(phy, 0, VSC8211_EXT_PAGE_AXS, 0x52b5)) != 0 ||
277 	    (err = mdio_write(phy, 0, 18, 0x12)) != 0 ||
278 	    (err = mdio_write(phy, 0, 17, enable ? 0x2803 : 0x3003)) != 0 ||
279 	    (err = mdio_write(phy, 0, 16, 0x87fa)) != 0 ||
280 	    (err = mdio_write(phy, 0, VSC8211_EXT_PAGE_AXS, 0)) != 0)
281 		return err;
282 	return 0;
283 }
284 
285 static int vsc8211_set_speed_duplex(struct cphy *phy, int speed, int duplex)
286 {
287 	int err;
288 
289 	err = t3_set_phy_speed_duplex(phy, speed, duplex);
290 	if (!err)
291 		err = vsc8211_set_automdi(phy, 1);
292 	return err;
293 }
294 
295 static int vsc8211_power_down(struct cphy *cphy, int enable)
296 {
297 	return t3_mdio_change_bits(cphy, 0, MII_BMCR, BMCR_PDOWN,
298 				   enable ? BMCR_PDOWN : 0);
299 }
300 
301 static int vsc8211_intr_handler(struct cphy *cphy)
302 {
303 	unsigned int cause;
304 	int err, cphy_cause = 0;
305 
306 	err = mdio_read(cphy, 0, VSC8211_INTR_STATUS, &cause);
307 	if (err)
308 		return err;
309 
310 	cause &= INTR_MASK;
311 	if (cause & CFG_CHG_INTR_MASK)
312 		cphy_cause |= cphy_cause_link_change;
313 	if (cause & (VSC_INTR_RX_FIFO | VSC_INTR_TX_FIFO))
314 		cphy_cause |= cphy_cause_fifo_error;
315 	return cphy_cause;
316 }
317 
318 #ifdef C99_NOT_SUPPORTED
319 static struct cphy_ops vsc8211_ops = {
320 	vsc8211_reset,
321 	vsc8211_intr_enable,
322 	vsc8211_intr_disable,
323 	vsc8211_intr_clear,
324 	vsc8211_intr_handler,
325 	vsc8211_autoneg_enable,
326 	vsc8211_autoneg_restart,
327 	t3_phy_advertise,
328 	NULL,
329 	vsc8211_set_speed_duplex,
330 	vsc8211_get_link_status,
331 	vsc8211_power_down,
332 };
333 
334 static struct cphy_ops vsc8211_fiber_ops = {
335 	vsc8211_reset,
336 	vsc8211_intr_enable,
337 	vsc8211_intr_disable,
338 	vsc8211_intr_clear,
339 	vsc8211_intr_handler,
340 	vsc8211_autoneg_enable,
341 	vsc8211_autoneg_restart,
342 	t3_phy_advertise_fiber,
343 	NULL,
344 	t3_set_phy_speed_duplex,
345 	vsc8211_get_link_status_fiber,
346 	vsc8211_power_down,
347 };
348 #else
349 static struct cphy_ops vsc8211_ops = {
350 	.reset             = vsc8211_reset,
351 	.intr_enable       = vsc8211_intr_enable,
352 	.intr_disable      = vsc8211_intr_disable,
353 	.intr_clear        = vsc8211_intr_clear,
354 	.intr_handler      = vsc8211_intr_handler,
355 	.autoneg_enable    = vsc8211_autoneg_enable,
356 	.autoneg_restart   = vsc8211_autoneg_restart,
357 	.advertise         = t3_phy_advertise,
358 	.set_speed_duplex  = vsc8211_set_speed_duplex,
359 	.get_link_status   = vsc8211_get_link_status,
360 	.power_down        = vsc8211_power_down,
361 };
362 
363 static struct cphy_ops vsc8211_fiber_ops = {
364 	.reset             = vsc8211_reset,
365 	.intr_enable       = vsc8211_intr_enable,
366 	.intr_disable      = vsc8211_intr_disable,
367 	.intr_clear        = vsc8211_intr_clear,
368 	.intr_handler      = vsc8211_intr_handler,
369 	.autoneg_enable    = vsc8211_autoneg_enable,
370 	.autoneg_restart   = vsc8211_autoneg_restart,
371 	.advertise         = t3_phy_advertise_fiber,
372 	.set_speed_duplex  = t3_set_phy_speed_duplex,
373 	.get_link_status   = vsc8211_get_link_status_fiber,
374 	.power_down        = vsc8211_power_down,
375 };
376 #endif
377 
378 int t3_vsc8211_phy_prep(struct cphy *phy, adapter_t *adapter, int phy_addr,
379 			const struct mdio_ops *mdio_ops)
380 {
381 	int err;
382 	unsigned int val;
383 
384 	cphy_init(phy, adapter, phy_addr, &vsc8211_ops, mdio_ops,
385 		  SUPPORTED_10baseT_Full | SUPPORTED_100baseT_Full |
386 		  SUPPORTED_1000baseT_Full | SUPPORTED_Autoneg | SUPPORTED_MII |
387 		  SUPPORTED_TP | SUPPORTED_IRQ, "10/100/1000BASE-T");
388 	msleep(20);       /* PHY needs ~10ms to start responding to MDIO */
389 
390 	err = mdio_read(phy, 0, VSC8211_EXT_CTRL, &val);
391 	if (err)
392 		return err;
393 	if (val & VSC_CTRL_MEDIA_MODE_HI) {
394 		/* copper interface, just need to configure the LEDs */
395 		return mdio_write(phy, 0, VSC8211_LED_CTRL, 0x100);
396 	}
397 
398 	phy->caps = SUPPORTED_1000baseT_Full | SUPPORTED_Autoneg |
399 		    SUPPORTED_MII | SUPPORTED_FIBRE | SUPPORTED_IRQ;
400 	phy->desc = "1000BASE-X";
401 	phy->ops = &vsc8211_fiber_ops;
402 
403 	if ((err = mdio_write(phy, 0, VSC8211_EXT_PAGE_AXS, 1)) != 0 ||
404 	    (err = mdio_write(phy, 0, VSC8211_SIGDET_CTRL, 1)) != 0 ||
405 	    (err = mdio_write(phy, 0, VSC8211_EXT_PAGE_AXS, 0)) != 0 ||
406 	    (err = mdio_write(phy, 0, VSC8211_EXT_CTRL,
407 			      val | VSC_CTRL_CLAUSE37_VIEW)) != 0 ||
408 	    (err = vsc8211_reset(phy, 0)) != 0)
409 		return err;
410 
411 	udelay(5); /* delay after reset before next SMI */
412 	return 0;
413 }
414