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