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