1/**************************************************************************
2SPDX-License-Identifier: BSD-2-Clause
3
4Copyright (c) 2007, Chelsio Inc.
5All rights reserved.
6
7Redistribution and use in source and binary forms, with or without
8modification, are permitted provided that the following conditions are met:
9
10 1. Redistributions of source code must retain the above copyright notice,
11    this list of conditions and the following disclaimer.
12
13 2. Neither the name of the Chelsio Corporation nor the names of its
14    contributors may be used to endorse or promote products derived from
15    this software without specific prior written permission.
16
17THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27POSSIBILITY OF SUCH DAMAGE.
28
29***************************************************************************/
30
31#include <sys/cdefs.h>
32#include <cxgb_include.h>
33
34/* Marvell PHY interrupt status bits. */
35#define MV_INTR_JABBER          0x0001
36#define MV_INTR_POLARITY_CHNG   0x0002
37#define MV_INTR_ENG_DETECT_CHNG 0x0010
38#define MV_INTR_DOWNSHIFT       0x0020
39#define MV_INTR_MDI_XOVER_CHNG  0x0040
40#define MV_INTR_FIFO_OVER_UNDER 0x0080
41#define MV_INTR_FALSE_CARRIER   0x0100
42#define MV_INTR_SYMBOL_ERROR    0x0200
43#define MV_INTR_LINK_CHNG       0x0400
44#define MV_INTR_AUTONEG_DONE    0x0800
45#define MV_INTR_PAGE_RECV       0x1000
46#define MV_INTR_DUPLEX_CHNG     0x2000
47#define MV_INTR_SPEED_CHNG      0x4000
48#define MV_INTR_AUTONEG_ERR     0x8000
49
50/* Marvell PHY specific registers. */
51#define MV88E1XXX_SPECIFIC_CNTRL          16
52#define MV88E1XXX_SPECIFIC_STATUS         17
53#define MV88E1XXX_INTR_ENABLE             18
54#define MV88E1XXX_INTR_STATUS             19
55#define MV88E1XXX_EXT_SPECIFIC_CNTRL      20
56#define MV88E1XXX_RECV_ERR                21
57#define MV88E1XXX_EXT_ADDR                22
58#define MV88E1XXX_GLOBAL_STATUS           23
59#define MV88E1XXX_LED_CNTRL               24
60#define MV88E1XXX_LED_OVERRIDE            25
61#define MV88E1XXX_EXT_SPECIFIC_CNTRL2     26
62#define MV88E1XXX_EXT_SPECIFIC_STATUS     27
63#define MV88E1XXX_VIRTUAL_CABLE_TESTER    28
64#define MV88E1XXX_EXTENDED_ADDR           29
65#define MV88E1XXX_EXTENDED_DATA           30
66
67/* PHY specific control register fields */
68#define S_PSCR_MDI_XOVER_MODE    5
69#define M_PSCR_MDI_XOVER_MODE    0x3
70#define V_PSCR_MDI_XOVER_MODE(x) ((x) << S_PSCR_MDI_XOVER_MODE)
71
72/* Extended PHY specific control register fields */
73#define S_DOWNSHIFT_ENABLE 8
74#define V_DOWNSHIFT_ENABLE (1 << S_DOWNSHIFT_ENABLE)
75
76#define S_DOWNSHIFT_CNT    9
77#define M_DOWNSHIFT_CNT    0x7
78#define V_DOWNSHIFT_CNT(x) ((x) << S_DOWNSHIFT_CNT)
79
80/* PHY specific status register fields */
81#define S_PSSR_JABBER 0
82#define V_PSSR_JABBER (1 << S_PSSR_JABBER)
83
84#define S_PSSR_POLARITY 1
85#define V_PSSR_POLARITY (1 << S_PSSR_POLARITY)
86
87#define S_PSSR_RX_PAUSE 2
88#define V_PSSR_RX_PAUSE (1 << S_PSSR_RX_PAUSE)
89
90#define S_PSSR_TX_PAUSE 3
91#define V_PSSR_TX_PAUSE (1 << S_PSSR_TX_PAUSE)
92
93#define S_PSSR_ENERGY_DETECT 4
94#define V_PSSR_ENERGY_DETECT (1 << S_PSSR_ENERGY_DETECT)
95
96#define S_PSSR_DOWNSHIFT_STATUS 5
97#define V_PSSR_DOWNSHIFT_STATUS (1 << S_PSSR_DOWNSHIFT_STATUS)
98
99#define S_PSSR_MDI 6
100#define V_PSSR_MDI (1 << S_PSSR_MDI)
101
102#define S_PSSR_CABLE_LEN    7
103#define M_PSSR_CABLE_LEN    0x7
104#define V_PSSR_CABLE_LEN(x) ((x) << S_PSSR_CABLE_LEN)
105#define G_PSSR_CABLE_LEN(x) (((x) >> S_PSSR_CABLE_LEN) & M_PSSR_CABLE_LEN)
106
107#define S_PSSR_LINK 10
108#define V_PSSR_LINK (1 << S_PSSR_LINK)
109
110#define S_PSSR_STATUS_RESOLVED 11
111#define V_PSSR_STATUS_RESOLVED (1 << S_PSSR_STATUS_RESOLVED)
112
113#define S_PSSR_PAGE_RECEIVED 12
114#define V_PSSR_PAGE_RECEIVED (1 << S_PSSR_PAGE_RECEIVED)
115
116#define S_PSSR_DUPLEX 13
117#define V_PSSR_DUPLEX (1 << S_PSSR_DUPLEX)
118
119#define S_PSSR_SPEED    14
120#define M_PSSR_SPEED    0x3
121#define V_PSSR_SPEED(x) ((x) << S_PSSR_SPEED)
122#define G_PSSR_SPEED(x) (((x) >> S_PSSR_SPEED) & M_PSSR_SPEED)
123
124/* MV88E1XXX MDI crossover register values */
125#define CROSSOVER_MDI   0
126#define CROSSOVER_MDIX  1
127#define CROSSOVER_AUTO  3
128
129#define INTR_ENABLE_MASK (MV_INTR_SPEED_CHNG | MV_INTR_DUPLEX_CHNG | \
130	MV_INTR_AUTONEG_DONE | MV_INTR_LINK_CHNG | MV_INTR_FIFO_OVER_UNDER | \
131	MV_INTR_ENG_DETECT_CHNG)
132
133/*
134 * Reset the PHY.  If 'wait' is set wait until the reset completes.
135 */
136static int mv88e1xxx_reset(struct cphy *cphy, int wait)
137{
138	return t3_phy_reset(cphy, 0, wait);
139}
140
141static int mv88e1xxx_intr_enable(struct cphy *cphy)
142{
143	return mdio_write(cphy, 0, MV88E1XXX_INTR_ENABLE, INTR_ENABLE_MASK);
144}
145
146static int mv88e1xxx_intr_disable(struct cphy *cphy)
147{
148	return mdio_write(cphy, 0, MV88E1XXX_INTR_ENABLE, 0);
149}
150
151static int mv88e1xxx_intr_clear(struct cphy *cphy)
152{
153	u32 val;
154
155	/* Clear PHY interrupts by reading the register. */
156	return mdio_read(cphy, 0, MV88E1XXX_INTR_STATUS, &val);
157}
158
159static int mv88e1xxx_crossover_set(struct cphy *cphy, int crossover)
160{
161	return t3_mdio_change_bits(cphy, 0, MV88E1XXX_SPECIFIC_CNTRL,
162				   V_PSCR_MDI_XOVER_MODE(M_PSCR_MDI_XOVER_MODE),
163				   V_PSCR_MDI_XOVER_MODE(crossover));
164}
165
166static int mv88e1xxx_autoneg_enable(struct cphy *cphy)
167{
168	mv88e1xxx_crossover_set(cphy, CROSSOVER_AUTO);
169
170	/* restart autoneg for change to take effect */
171	return t3_mdio_change_bits(cphy, 0, MII_BMCR, BMCR_PDOWN | BMCR_ISOLATE,
172			 	   BMCR_ANENABLE | BMCR_ANRESTART);
173}
174
175static int mv88e1xxx_autoneg_restart(struct cphy *cphy)
176{
177	return t3_mdio_change_bits(cphy, 0, MII_BMCR, BMCR_PDOWN | BMCR_ISOLATE,
178			 	   BMCR_ANRESTART);
179}
180
181static int mv88e1xxx_set_loopback(struct cphy *cphy, int mmd, int dir, int on)
182{
183	return t3_mdio_change_bits(cphy, 0, MII_BMCR, BMCR_LOOPBACK,
184			 	   on ? BMCR_LOOPBACK : 0);
185}
186
187static int mv88e1xxx_get_link_status(struct cphy *cphy, int *link_state,
188				     int *speed, int *duplex, int *fc)
189{
190	u32 status;
191	int sp = -1, dplx = -1, pause = 0;
192
193	mdio_read(cphy, 0, MV88E1XXX_SPECIFIC_STATUS, &status);
194	if ((status & V_PSSR_STATUS_RESOLVED) != 0) {
195		if (status & V_PSSR_RX_PAUSE)
196			pause |= PAUSE_RX;
197		if (status & V_PSSR_TX_PAUSE)
198			pause |= PAUSE_TX;
199		dplx = (status & V_PSSR_DUPLEX) ? DUPLEX_FULL : DUPLEX_HALF;
200		sp = G_PSSR_SPEED(status);
201		if (sp == 0)
202			sp = SPEED_10;
203		else if (sp == 1)
204			sp = SPEED_100;
205		else
206			sp = SPEED_1000;
207	}
208	if (link_state)
209		*link_state = status & V_PSSR_LINK ? PHY_LINK_UP :
210		    PHY_LINK_DOWN;
211	if (speed)
212		*speed = sp;
213	if (duplex)
214		*duplex = dplx;
215	if (fc)
216		*fc = pause;
217	return 0;
218}
219
220static int mv88e1xxx_set_speed_duplex(struct cphy *phy, int speed, int duplex)
221{
222	int err = t3_set_phy_speed_duplex(phy, speed, duplex);
223
224	/* PHY needs reset for new settings to take effect */
225	if (!err)
226		err = mv88e1xxx_reset(phy, 0);
227	return err;
228}
229
230static int mv88e1xxx_downshift_set(struct cphy *cphy, int downshift_enable)
231{
232	/*
233	 * Set the downshift counter to 2 so we try to establish Gb link
234	 * twice before downshifting.
235	 */
236	return t3_mdio_change_bits(cphy, 0, MV88E1XXX_EXT_SPECIFIC_CNTRL,
237		V_DOWNSHIFT_ENABLE | V_DOWNSHIFT_CNT(M_DOWNSHIFT_CNT),
238		downshift_enable ? V_DOWNSHIFT_ENABLE | V_DOWNSHIFT_CNT(2) : 0);
239}
240
241static int mv88e1xxx_power_down(struct cphy *cphy, int enable)
242{
243	return t3_mdio_change_bits(cphy, 0, MII_BMCR, BMCR_PDOWN,
244				   enable ? BMCR_PDOWN : 0);
245}
246
247static int mv88e1xxx_intr_handler(struct cphy *cphy)
248{
249	const u32 link_change_intrs = MV_INTR_LINK_CHNG |
250		MV_INTR_AUTONEG_DONE | MV_INTR_DUPLEX_CHNG |
251		MV_INTR_SPEED_CHNG | MV_INTR_DOWNSHIFT;
252
253	u32 cause;
254	int cphy_cause = 0;
255
256	mdio_read(cphy, 0, MV88E1XXX_INTR_STATUS, &cause);
257	cause &= INTR_ENABLE_MASK;
258	if (cause & link_change_intrs)
259		cphy_cause |= cphy_cause_link_change;
260	if (cause & MV_INTR_FIFO_OVER_UNDER)
261		cphy_cause |= cphy_cause_fifo_error;
262	return cphy_cause;
263}
264
265#ifdef C99_NOT_SUPPORTED
266static struct cphy_ops mv88e1xxx_ops = {
267	mv88e1xxx_reset,
268	mv88e1xxx_intr_enable,
269	mv88e1xxx_intr_disable,
270	mv88e1xxx_intr_clear,
271	mv88e1xxx_intr_handler,
272	mv88e1xxx_autoneg_enable,
273	mv88e1xxx_autoneg_restart,
274	t3_phy_advertise,
275	mv88e1xxx_set_loopback,
276	mv88e1xxx_set_speed_duplex,
277	mv88e1xxx_get_link_status,
278	mv88e1xxx_power_down,
279};
280#else
281static struct cphy_ops mv88e1xxx_ops = {
282	.reset             = mv88e1xxx_reset,
283	.intr_enable       = mv88e1xxx_intr_enable,
284	.intr_disable      = mv88e1xxx_intr_disable,
285	.intr_clear        = mv88e1xxx_intr_clear,
286	.intr_handler      = mv88e1xxx_intr_handler,
287	.autoneg_enable    = mv88e1xxx_autoneg_enable,
288	.autoneg_restart   = mv88e1xxx_autoneg_restart,
289	.advertise         = t3_phy_advertise,
290	.set_loopback      = mv88e1xxx_set_loopback,
291	.set_speed_duplex  = mv88e1xxx_set_speed_duplex,
292	.get_link_status   = mv88e1xxx_get_link_status,
293	.power_down        = mv88e1xxx_power_down,
294};
295#endif
296
297int t3_mv88e1xxx_phy_prep(pinfo_t *pinfo, int phy_addr,
298			  const struct mdio_ops *mdio_ops)
299{
300	struct cphy *phy = &pinfo->phy;
301	int err;
302
303	cphy_init(phy, pinfo->adapter, pinfo, phy_addr, &mv88e1xxx_ops, mdio_ops,
304		  SUPPORTED_10baseT_Full | SUPPORTED_100baseT_Full |
305		  SUPPORTED_1000baseT_Full | SUPPORTED_Autoneg | SUPPORTED_MII |
306		  SUPPORTED_TP | SUPPORTED_IRQ, "10/100/1000BASE-T");
307
308	/* Configure copper PHY transmitter as class A to reduce EMI. */
309	err = mdio_write(phy, 0, MV88E1XXX_EXTENDED_ADDR, 0xb);
310	if (!err)
311		err = mdio_write(phy, 0, MV88E1XXX_EXTENDED_DATA, 0x8004);
312
313	if (!err)
314		err = mv88e1xxx_downshift_set(phy, 1);   /* Enable downshift */
315	return err;
316}
317