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/* Marvell PHY interrupt status bits. */
36#define MV_INTR_JABBER          0x0001
37#define MV_INTR_POLARITY_CHNG   0x0002
38#define MV_INTR_ENG_DETECT_CHNG 0x0010
39#define MV_INTR_DOWNSHIFT       0x0020
40#define MV_INTR_MDI_XOVER_CHNG  0x0040
41#define MV_INTR_FIFO_OVER_UNDER 0x0080
42#define MV_INTR_FALSE_CARRIER   0x0100
43#define MV_INTR_SYMBOL_ERROR    0x0200
44#define MV_INTR_LINK_CHNG       0x0400
45#define MV_INTR_AUTONEG_DONE    0x0800
46#define MV_INTR_PAGE_RECV       0x1000
47#define MV_INTR_DUPLEX_CHNG     0x2000
48#define MV_INTR_SPEED_CHNG      0x4000
49#define MV_INTR_AUTONEG_ERR     0x8000
50
51/* Marvell PHY specific registers. */
52#define MV88E1XXX_SPECIFIC_CNTRL          16
53#define MV88E1XXX_SPECIFIC_STATUS         17
54#define MV88E1XXX_INTR_ENABLE             18
55#define MV88E1XXX_INTR_STATUS             19
56#define MV88E1XXX_EXT_SPECIFIC_CNTRL      20
57#define MV88E1XXX_RECV_ERR                21
58#define MV88E1XXX_EXT_ADDR                22
59#define MV88E1XXX_GLOBAL_STATUS           23
60#define MV88E1XXX_LED_CNTRL               24
61#define MV88E1XXX_LED_OVERRIDE            25
62#define MV88E1XXX_EXT_SPECIFIC_CNTRL2     26
63#define MV88E1XXX_EXT_SPECIFIC_STATUS     27
64#define MV88E1XXX_VIRTUAL_CABLE_TESTER    28
65#define MV88E1XXX_EXTENDED_ADDR           29
66#define MV88E1XXX_EXTENDED_DATA           30
67
68/* PHY specific control register fields */
69#define S_PSCR_MDI_XOVER_MODE    5
70#define M_PSCR_MDI_XOVER_MODE    0x3
71#define V_PSCR_MDI_XOVER_MODE(x) ((x) << S_PSCR_MDI_XOVER_MODE)
72
73/* Extended PHY specific control register fields */
74#define S_DOWNSHIFT_ENABLE 8
75#define V_DOWNSHIFT_ENABLE (1 << S_DOWNSHIFT_ENABLE)
76
77#define S_DOWNSHIFT_CNT    9
78#define M_DOWNSHIFT_CNT    0x7
79#define V_DOWNSHIFT_CNT(x) ((x) << S_DOWNSHIFT_CNT)
80
81/* PHY specific status register fields */
82#define S_PSSR_JABBER 0
83#define V_PSSR_JABBER (1 << S_PSSR_JABBER)
84
85#define S_PSSR_POLARITY 1
86#define V_PSSR_POLARITY (1 << S_PSSR_POLARITY)
87
88#define S_PSSR_RX_PAUSE 2
89#define V_PSSR_RX_PAUSE (1 << S_PSSR_RX_PAUSE)
90
91#define S_PSSR_TX_PAUSE 3
92#define V_PSSR_TX_PAUSE (1 << S_PSSR_TX_PAUSE)
93
94#define S_PSSR_ENERGY_DETECT 4
95#define V_PSSR_ENERGY_DETECT (1 << S_PSSR_ENERGY_DETECT)
96
97#define S_PSSR_DOWNSHIFT_STATUS 5
98#define V_PSSR_DOWNSHIFT_STATUS (1 << S_PSSR_DOWNSHIFT_STATUS)
99
100#define S_PSSR_MDI 6
101#define V_PSSR_MDI (1 << S_PSSR_MDI)
102
103#define S_PSSR_CABLE_LEN    7
104#define M_PSSR_CABLE_LEN    0x7
105#define V_PSSR_CABLE_LEN(x) ((x) << S_PSSR_CABLE_LEN)
106#define G_PSSR_CABLE_LEN(x) (((x) >> S_PSSR_CABLE_LEN) & M_PSSR_CABLE_LEN)
107
108#define S_PSSR_LINK 10
109#define V_PSSR_LINK (1 << S_PSSR_LINK)
110
111#define S_PSSR_STATUS_RESOLVED 11
112#define V_PSSR_STATUS_RESOLVED (1 << S_PSSR_STATUS_RESOLVED)
113
114#define S_PSSR_PAGE_RECEIVED 12
115#define V_PSSR_PAGE_RECEIVED (1 << S_PSSR_PAGE_RECEIVED)
116
117#define S_PSSR_DUPLEX 13
118#define V_PSSR_DUPLEX (1 << S_PSSR_DUPLEX)
119
120#define S_PSSR_SPEED    14
121#define M_PSSR_SPEED    0x3
122#define V_PSSR_SPEED(x) ((x) << S_PSSR_SPEED)
123#define G_PSSR_SPEED(x) (((x) >> S_PSSR_SPEED) & M_PSSR_SPEED)
124
125/* MV88E1XXX MDI crossover register values */
126#define CROSSOVER_MDI   0
127#define CROSSOVER_MDIX  1
128#define CROSSOVER_AUTO  3
129
130#define INTR_ENABLE_MASK (MV_INTR_SPEED_CHNG | MV_INTR_DUPLEX_CHNG | \
131	MV_INTR_AUTONEG_DONE | MV_INTR_LINK_CHNG | MV_INTR_FIFO_OVER_UNDER | \
132	MV_INTR_ENG_DETECT_CHNG)
133
134/*
135 * Reset the PHY.  If 'wait' is set wait until the reset completes.
136 */
137static int mv88e1xxx_reset(struct cphy *cphy, int wait)
138{
139	return t3_phy_reset(cphy, 0, wait);
140}
141
142static int mv88e1xxx_intr_enable(struct cphy *cphy)
143{
144	return mdio_write(cphy, 0, MV88E1XXX_INTR_ENABLE, INTR_ENABLE_MASK);
145}
146
147static int mv88e1xxx_intr_disable(struct cphy *cphy)
148{
149	return mdio_write(cphy, 0, MV88E1XXX_INTR_ENABLE, 0);
150}
151
152static int mv88e1xxx_intr_clear(struct cphy *cphy)
153{
154	u32 val;
155
156	/* Clear PHY interrupts by reading the register. */
157	return mdio_read(cphy, 0, MV88E1XXX_INTR_STATUS, &val);
158}
159
160static int mv88e1xxx_crossover_set(struct cphy *cphy, int crossover)
161{
162	return t3_mdio_change_bits(cphy, 0, MV88E1XXX_SPECIFIC_CNTRL,
163				   V_PSCR_MDI_XOVER_MODE(M_PSCR_MDI_XOVER_MODE),
164				   V_PSCR_MDI_XOVER_MODE(crossover));
165}
166
167static int mv88e1xxx_autoneg_enable(struct cphy *cphy)
168{
169	mv88e1xxx_crossover_set(cphy, CROSSOVER_AUTO);
170
171	/* restart autoneg for change to take effect */
172	return t3_mdio_change_bits(cphy, 0, MII_BMCR, BMCR_PDOWN | BMCR_ISOLATE,
173			 	   BMCR_ANENABLE | BMCR_ANRESTART);
174}
175
176static int mv88e1xxx_autoneg_restart(struct cphy *cphy)
177{
178	return t3_mdio_change_bits(cphy, 0, MII_BMCR, BMCR_PDOWN | BMCR_ISOLATE,
179			 	   BMCR_ANRESTART);
180}
181
182static int mv88e1xxx_set_loopback(struct cphy *cphy, int mmd, int dir, int on)
183{
184	return t3_mdio_change_bits(cphy, 0, MII_BMCR, BMCR_LOOPBACK,
185			 	   on ? BMCR_LOOPBACK : 0);
186}
187
188static int mv88e1xxx_get_link_status(struct cphy *cphy, int *link_state,
189				     int *speed, int *duplex, int *fc)
190{
191	u32 status;
192	int sp = -1, dplx = -1, pause = 0;
193
194	mdio_read(cphy, 0, MV88E1XXX_SPECIFIC_STATUS, &status);
195	if ((status & V_PSSR_STATUS_RESOLVED) != 0) {
196		if (status & V_PSSR_RX_PAUSE)
197			pause |= PAUSE_RX;
198		if (status & V_PSSR_TX_PAUSE)
199			pause |= PAUSE_TX;
200		dplx = (status & V_PSSR_DUPLEX) ? DUPLEX_FULL : DUPLEX_HALF;
201		sp = G_PSSR_SPEED(status);
202		if (sp == 0)
203			sp = SPEED_10;
204		else if (sp == 1)
205			sp = SPEED_100;
206		else
207			sp = SPEED_1000;
208	}
209	if (link_state)
210		*link_state = status & V_PSSR_LINK ? PHY_LINK_UP :
211		    PHY_LINK_DOWN;
212	if (speed)
213		*speed = sp;
214	if (duplex)
215		*duplex = dplx;
216	if (fc)
217		*fc = pause;
218	return 0;
219}
220
221static int mv88e1xxx_set_speed_duplex(struct cphy *phy, int speed, int duplex)
222{
223	int err = t3_set_phy_speed_duplex(phy, speed, duplex);
224
225	/* PHY needs reset for new settings to take effect */
226	if (!err)
227		err = mv88e1xxx_reset(phy, 0);
228	return err;
229}
230
231static int mv88e1xxx_downshift_set(struct cphy *cphy, int downshift_enable)
232{
233	/*
234	 * Set the downshift counter to 2 so we try to establish Gb link
235	 * twice before downshifting.
236	 */
237	return t3_mdio_change_bits(cphy, 0, MV88E1XXX_EXT_SPECIFIC_CNTRL,
238		V_DOWNSHIFT_ENABLE | V_DOWNSHIFT_CNT(M_DOWNSHIFT_CNT),
239		downshift_enable ? V_DOWNSHIFT_ENABLE | V_DOWNSHIFT_CNT(2) : 0);
240}
241
242static int mv88e1xxx_power_down(struct cphy *cphy, int enable)
243{
244	return t3_mdio_change_bits(cphy, 0, MII_BMCR, BMCR_PDOWN,
245				   enable ? BMCR_PDOWN : 0);
246}
247
248static int mv88e1xxx_intr_handler(struct cphy *cphy)
249{
250	const u32 link_change_intrs = MV_INTR_LINK_CHNG |
251		MV_INTR_AUTONEG_DONE | MV_INTR_DUPLEX_CHNG |
252		MV_INTR_SPEED_CHNG | MV_INTR_DOWNSHIFT;
253
254	u32 cause;
255	int cphy_cause = 0;
256
257	mdio_read(cphy, 0, MV88E1XXX_INTR_STATUS, &cause);
258	cause &= INTR_ENABLE_MASK;
259	if (cause & link_change_intrs)
260		cphy_cause |= cphy_cause_link_change;
261	if (cause & MV_INTR_FIFO_OVER_UNDER)
262		cphy_cause |= cphy_cause_fifo_error;
263	return cphy_cause;
264}
265
266#ifdef C99_NOT_SUPPORTED
267static struct cphy_ops mv88e1xxx_ops = {
268	mv88e1xxx_reset,
269	mv88e1xxx_intr_enable,
270	mv88e1xxx_intr_disable,
271	mv88e1xxx_intr_clear,
272	mv88e1xxx_intr_handler,
273	mv88e1xxx_autoneg_enable,
274	mv88e1xxx_autoneg_restart,
275	t3_phy_advertise,
276	mv88e1xxx_set_loopback,
277	mv88e1xxx_set_speed_duplex,
278	mv88e1xxx_get_link_status,
279	mv88e1xxx_power_down,
280};
281#else
282static struct cphy_ops mv88e1xxx_ops = {
283	.reset             = mv88e1xxx_reset,
284	.intr_enable       = mv88e1xxx_intr_enable,
285	.intr_disable      = mv88e1xxx_intr_disable,
286	.intr_clear        = mv88e1xxx_intr_clear,
287	.intr_handler      = mv88e1xxx_intr_handler,
288	.autoneg_enable    = mv88e1xxx_autoneg_enable,
289	.autoneg_restart   = mv88e1xxx_autoneg_restart,
290	.advertise         = t3_phy_advertise,
291	.set_loopback      = mv88e1xxx_set_loopback,
292	.set_speed_duplex  = mv88e1xxx_set_speed_duplex,
293	.get_link_status   = mv88e1xxx_get_link_status,
294	.power_down        = mv88e1xxx_power_down,
295};
296#endif
297
298int t3_mv88e1xxx_phy_prep(pinfo_t *pinfo, int phy_addr,
299			  const struct mdio_ops *mdio_ops)
300{
301	struct cphy *phy = &pinfo->phy;
302	int err;
303
304	cphy_init(phy, pinfo->adapter, pinfo, phy_addr, &mv88e1xxx_ops, mdio_ops,
305		  SUPPORTED_10baseT_Full | SUPPORTED_100baseT_Full |
306		  SUPPORTED_1000baseT_Full | SUPPORTED_Autoneg | SUPPORTED_MII |
307		  SUPPORTED_TP | SUPPORTED_IRQ, "10/100/1000BASE-T");
308
309	/* Configure copper PHY transmitter as class A to reduce EMI. */
310	err = mdio_write(phy, 0, MV88E1XXX_EXTENDED_ADDR, 0xb);
311	if (!err)
312		err = mdio_write(phy, 0, MV88E1XXX_EXTENDED_DATA, 0x8004);
313
314	if (!err)
315		err = mv88e1xxx_downshift_set(phy, 1);   /* Enable downshift */
316	return err;
317}
318