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_ok,
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_ok) {
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_ok = (status & BMSR_LSTATUS) != 0;
154	}
155	if (!(bmcr & BMCR_ANENABLE)) {
156		dplx = (bmcr & BMCR_FULLDPLX) ? DUPLEX_FULL : DUPLEX_HALF;
157		if (bmcr & BMCR_SPEED1000)
158			sp = SPEED_1000;
159		else if (bmcr & BMCR_SPEED100)
160			sp = SPEED_100;
161		else
162			sp = SPEED_10;
163	} else if (status & BMSR_ANEGCOMPLETE) {
164		err = mdio_read(cphy, 0, VSC8211_AUX_CTRL_STAT, &status);
165		if (err)
166			return err;
167
168		dplx = (status & F_ACSR_DUPLEX) ? DUPLEX_FULL : DUPLEX_HALF;
169		sp = G_ACSR_SPEED(status);
170		if (sp == 0)
171			sp = SPEED_10;
172		else if (sp == 1)
173			sp = SPEED_100;
174		else
175			sp = SPEED_1000;
176
177		if (fc && dplx == DUPLEX_FULL) {
178			err = mdio_read(cphy, 0, MII_LPA, &lpa);
179			if (!err)
180				err = mdio_read(cphy, 0, MII_ADVERTISE, &adv);
181			if (err)
182				return err;
183
184			if (lpa & adv & ADVERTISE_PAUSE_CAP)
185				pause = PAUSE_RX | PAUSE_TX;
186			else if ((lpa & ADVERTISE_PAUSE_CAP) &&
187				 (lpa & ADVERTISE_PAUSE_ASYM) &&
188				 (adv & ADVERTISE_PAUSE_ASYM))
189				pause = PAUSE_TX;
190			else if ((lpa & ADVERTISE_PAUSE_ASYM) &&
191				 (adv & ADVERTISE_PAUSE_CAP))
192				pause = PAUSE_RX;
193		}
194	}
195	if (speed)
196		*speed = sp;
197	if (duplex)
198		*duplex = dplx;
199	if (fc)
200		*fc = pause;
201	return 0;
202}
203
204static int vsc8211_get_link_status_fiber(struct cphy *cphy, int *link_ok,
205					 int *speed, int *duplex, int *fc)
206{
207	unsigned int bmcr, status, lpa, adv;
208	int err, sp = -1, dplx = -1, pause = 0;
209
210	err = mdio_read(cphy, 0, MII_BMCR, &bmcr);
211	if (!err)
212		err = mdio_read(cphy, 0, MII_BMSR, &status);
213	if (err)
214		return err;
215
216	if (link_ok) {
217		/*
218		 * BMSR_LSTATUS is latch-low, so if it is 0 we need to read it
219		 * once more to get the current link state.
220		 */
221		if (!(status & BMSR_LSTATUS))
222			err = mdio_read(cphy, 0, MII_BMSR, &status);
223		if (err)
224			return err;
225		*link_ok = (status & BMSR_LSTATUS) != 0;
226	}
227	if (!(bmcr & BMCR_ANENABLE)) {
228		dplx = (bmcr & BMCR_FULLDPLX) ? DUPLEX_FULL : DUPLEX_HALF;
229		if (bmcr & BMCR_SPEED1000)
230			sp = SPEED_1000;
231		else if (bmcr & BMCR_SPEED100)
232			sp = SPEED_100;
233		else
234			sp = SPEED_10;
235	} else if (status & BMSR_ANEGCOMPLETE) {
236		err = mdio_read(cphy, 0, MII_LPA, &lpa);
237		if (!err)
238			err = mdio_read(cphy, 0, MII_ADVERTISE, &adv);
239		if (err)
240			return err;
241
242		if (adv & lpa & ADVERTISE_1000XFULL) {
243			dplx = DUPLEX_FULL;
244			sp = SPEED_1000;
245		} else if (adv & lpa & ADVERTISE_1000XHALF) {
246			dplx = DUPLEX_HALF;
247			sp = SPEED_1000;
248		}
249
250		if (fc && dplx == DUPLEX_FULL) {
251			if (lpa & adv & ADVERTISE_1000XPAUSE)
252				pause = PAUSE_RX | PAUSE_TX;
253			else if ((lpa & ADVERTISE_1000XPAUSE) &&
254				 (adv & lpa & ADVERTISE_1000XPSE_ASYM))
255				pause = PAUSE_TX;
256			else if ((lpa & ADVERTISE_1000XPSE_ASYM) &&
257				 (adv & ADVERTISE_1000XPAUSE))
258				pause = PAUSE_RX;
259		}
260	}
261	if (speed)
262		*speed = sp;
263	if (duplex)
264		*duplex = dplx;
265	if (fc)
266		*fc = pause;
267	return 0;
268}
269
270/*
271 * Enable/disable auto MDI/MDI-X in forced link speed mode.
272 */
273static int vsc8211_set_automdi(struct cphy *phy, int enable)
274{
275	int err;
276
277	if ((err = mdio_write(phy, 0, VSC8211_EXT_PAGE_AXS, 0x52b5)) != 0 ||
278	    (err = mdio_write(phy, 0, 18, 0x12)) != 0 ||
279	    (err = mdio_write(phy, 0, 17, enable ? 0x2803 : 0x3003)) != 0 ||
280	    (err = mdio_write(phy, 0, 16, 0x87fa)) != 0 ||
281	    (err = mdio_write(phy, 0, VSC8211_EXT_PAGE_AXS, 0)) != 0)
282		return err;
283	return 0;
284}
285
286static int vsc8211_set_speed_duplex(struct cphy *phy, int speed, int duplex)
287{
288	int err;
289
290	err = t3_set_phy_speed_duplex(phy, speed, duplex);
291	if (!err)
292		err = vsc8211_set_automdi(phy, 1);
293	return err;
294}
295
296static int vsc8211_power_down(struct cphy *cphy, int enable)
297{
298	return t3_mdio_change_bits(cphy, 0, MII_BMCR, BMCR_PDOWN,
299				   enable ? BMCR_PDOWN : 0);
300}
301
302static int vsc8211_intr_handler(struct cphy *cphy)
303{
304	unsigned int cause;
305	int err, cphy_cause = 0;
306
307	err = mdio_read(cphy, 0, VSC8211_INTR_STATUS, &cause);
308	if (err)
309		return err;
310
311	cause &= INTR_MASK;
312	if (cause & CFG_CHG_INTR_MASK)
313		cphy_cause |= cphy_cause_link_change;
314	if (cause & (VSC_INTR_RX_FIFO | VSC_INTR_TX_FIFO))
315		cphy_cause |= cphy_cause_fifo_error;
316	return cphy_cause;
317}
318
319#ifdef C99_NOT_SUPPORTED
320static struct cphy_ops vsc8211_ops = {
321	vsc8211_reset,
322	vsc8211_intr_enable,
323	vsc8211_intr_disable,
324	vsc8211_intr_clear,
325	vsc8211_intr_handler,
326	vsc8211_autoneg_enable,
327	vsc8211_autoneg_restart,
328	t3_phy_advertise,
329	NULL,
330	vsc8211_set_speed_duplex,
331	vsc8211_get_link_status,
332	vsc8211_power_down,
333};
334
335static struct cphy_ops vsc8211_fiber_ops = {
336	vsc8211_reset,
337	vsc8211_intr_enable,
338	vsc8211_intr_disable,
339	vsc8211_intr_clear,
340	vsc8211_intr_handler,
341	vsc8211_autoneg_enable,
342	vsc8211_autoneg_restart,
343	t3_phy_advertise_fiber,
344	NULL,
345	t3_set_phy_speed_duplex,
346	vsc8211_get_link_status_fiber,
347	vsc8211_power_down,
348};
349#else
350static struct cphy_ops vsc8211_ops = {
351	.reset             = vsc8211_reset,
352	.intr_enable       = vsc8211_intr_enable,
353	.intr_disable      = vsc8211_intr_disable,
354	.intr_clear        = vsc8211_intr_clear,
355	.intr_handler      = vsc8211_intr_handler,
356	.autoneg_enable    = vsc8211_autoneg_enable,
357	.autoneg_restart   = vsc8211_autoneg_restart,
358	.advertise         = t3_phy_advertise,
359	.set_speed_duplex  = vsc8211_set_speed_duplex,
360	.get_link_status   = vsc8211_get_link_status,
361	.power_down        = vsc8211_power_down,
362};
363
364static struct cphy_ops vsc8211_fiber_ops = {
365	.reset             = vsc8211_reset,
366	.intr_enable       = vsc8211_intr_enable,
367	.intr_disable      = vsc8211_intr_disable,
368	.intr_clear        = vsc8211_intr_clear,
369	.intr_handler      = vsc8211_intr_handler,
370	.autoneg_enable    = vsc8211_autoneg_enable,
371	.autoneg_restart   = vsc8211_autoneg_restart,
372	.advertise         = t3_phy_advertise_fiber,
373	.set_speed_duplex  = t3_set_phy_speed_duplex,
374	.get_link_status   = vsc8211_get_link_status_fiber,
375	.power_down        = vsc8211_power_down,
376};
377#endif
378
379#define VSC8211_PHY_CTRL 24
380
381#define S_VSC8211_TXFIFODEPTH    7
382#define M_VSC8211_TXFIFODEPTH    0x7
383#define V_VSC8211_TXFIFODEPTH(x) ((x) << S_VSC8211_TXFIFODEPTH)
384#define G_VSC8211_TXFIFODEPTH(x) (((x) >> S_VSC8211_TXFIFODEPTH) & M_VSC8211_TXFIFODEPTH)
385
386#define S_VSC8211_RXFIFODEPTH    4
387#define M_VSC8211_RXFIFODEPTH    0x7
388#define V_VSC8211_RXFIFODEPTH(x) ((x) << S_VSC8211_RXFIFODEPTH)
389#define G_VSC8211_RXFIFODEPTH(x) (((x) >> S_VSC8211_RXFIFODEPTH) & M_VSC8211_RXFIFODEPTH)
390
391int t3_vsc8211_fifo_depth(adapter_t *adap, unsigned int mtu, int port)
392{
393	/* TX FIFO Depth set bits 9:7 to 100 (IEEE mode) */
394	unsigned int val = 4;
395	unsigned int currentregval;
396	unsigned int regval;
397	int err;
398
399	/* Retrieve the port info structure from adater_t */
400	struct port_info *portinfo = adap2pinfo(adap, port);
401
402	/* What phy is this */
403	struct cphy *phy = &portinfo->phy;
404
405	/* Read the current value of the PHY control Register */
406	err = mdio_read(phy, 0, VSC8211_PHY_CTRL, &currentregval);
407
408	if (err)
409		return err;
410
411	/* IEEE mode supports up to 1518 bytes */
412	/* mtu does not contain the header + FCS (18 bytes) */
413	if (mtu > 1500)
414		/*
415		 * If using a packet size > 1500  set TX FIFO Depth bits
416		 * 9:7 to 011 (Jumbo packet mode)
417		 */
418		val = 3;
419
420	regval = V_VSC8211_TXFIFODEPTH(val) | V_VSC8211_RXFIFODEPTH(val) |
421		(currentregval & ~V_VSC8211_TXFIFODEPTH(M_VSC8211_TXFIFODEPTH) &
422		~V_VSC8211_RXFIFODEPTH(M_VSC8211_RXFIFODEPTH));
423
424	return  mdio_write(phy, 0, VSC8211_PHY_CTRL, regval);
425}
426
427int t3_vsc8211_phy_prep(pinfo_t *pinfo, int phy_addr,
428			const struct mdio_ops *mdio_ops)
429{
430	struct cphy *phy = &pinfo->phy;
431	int err;
432	unsigned int val;
433
434	cphy_init(&pinfo->phy, pinfo->adapter, pinfo, phy_addr, &vsc8211_ops, mdio_ops,
435		  SUPPORTED_10baseT_Full | SUPPORTED_100baseT_Full |
436		  SUPPORTED_1000baseT_Full | SUPPORTED_Autoneg | SUPPORTED_MII |
437		  SUPPORTED_TP | SUPPORTED_IRQ, "10/100/1000BASE-T");
438	msleep(20);       /* PHY needs ~10ms to start responding to MDIO */
439
440	err = mdio_read(phy, 0, VSC8211_EXT_CTRL, &val);
441	if (err)
442		return err;
443	if (val & VSC_CTRL_MEDIA_MODE_HI) {
444		/* copper interface, just need to configure the LEDs */
445		return mdio_write(phy, 0, VSC8211_LED_CTRL, 0x100);
446	}
447
448	phy->caps = SUPPORTED_1000baseT_Full | SUPPORTED_Autoneg |
449		    SUPPORTED_MII | SUPPORTED_FIBRE | SUPPORTED_IRQ;
450	phy->desc = "1000BASE-X";
451	phy->ops = &vsc8211_fiber_ops;
452
453	if ((err = mdio_write(phy, 0, VSC8211_EXT_PAGE_AXS, 1)) != 0 ||
454	    (err = mdio_write(phy, 0, VSC8211_SIGDET_CTRL, 1)) != 0 ||
455	    (err = mdio_write(phy, 0, VSC8211_EXT_PAGE_AXS, 0)) != 0 ||
456	    (err = mdio_write(phy, 0, VSC8211_EXT_CTRL,
457			      val | VSC_CTRL_CLAUSE37_VIEW)) != 0 ||
458	    (err = vsc8211_reset(phy, 0)) != 0)
459		return err;
460
461	udelay(5); /* delay after reset before next SMI */
462	return 0;
463}
464