1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright (C) 2003-2005 Chelsio Communications. All rights reserved.
24 */
25
26 #pragma ident "%Z%%M% %I% %E% SMI" /* mv88e1xxx.c */
27
28 #include "common.h"
29 #include "mv88e1xxx.h"
30 #include "cphy.h"
31 #include "elmer0.h"
32
33 /* MV88E1XXX MDI crossover register values */
34 #define CROSSOVER_MDI 0
35 #define CROSSOVER_MDIX 1
36 #define CROSSOVER_AUTO 3
37
38 #define INTR_ENABLE_MASK 0x6CA0
39
40 /*
41 * Set the bits given by 'bitval' in PHY register 'reg'.
42 */
mdio_set_bit(struct cphy * cphy,int reg,u32 bitval)43 static void mdio_set_bit(struct cphy *cphy, int reg, u32 bitval)
44 {
45 u32 val;
46
47 (void) simple_mdio_read(cphy, reg, &val);
48 (void) simple_mdio_write(cphy, reg, val | bitval);
49 }
50
51 /*
52 * Clear the bits given by 'bitval' in PHY register 'reg'.
53 */
mdio_clear_bit(struct cphy * cphy,int reg,u32 bitval)54 static void mdio_clear_bit(struct cphy *cphy, int reg, u32 bitval)
55 {
56 u32 val;
57
58 (void) simple_mdio_read(cphy, reg, &val);
59 (void) simple_mdio_write(cphy, reg, val & ~bitval);
60 }
61
62 /*
63 * NAME: phy_reset
64 *
65 * DESC: Reset the given PHY's port. NOTE: This is not a global
66 * chip reset.
67 *
68 * PARAMS: cphy - Pointer to PHY instance data.
69 *
70 * RETURN: 0 - Successfull reset.
71 * -1 - Timeout.
72 */
73 /* ARGSUSED */
mv88e1xxx_reset(struct cphy * cphy,int wait)74 static int mv88e1xxx_reset(struct cphy *cphy, int wait)
75 {
76 u32 ctl;
77 int time_out = 1000;
78
79 mdio_set_bit(cphy, MII_BMCR, BMCR_RESET);
80
81 do {
82 (void) simple_mdio_read(cphy, MII_BMCR, &ctl);
83 ctl &= BMCR_RESET;
84 if (ctl)
85 DELAY_US(1);
86 } while (ctl && --time_out);
87
88 return ctl ? -1 : 0;
89 }
90
mv88e1xxx_interrupt_enable(struct cphy * cphy)91 static int mv88e1xxx_interrupt_enable(struct cphy *cphy)
92 {
93 /* Enable PHY interrupts. */
94 (void) simple_mdio_write(cphy, MV88E1XXX_INTERRUPT_ENABLE_REGISTER,
95 INTR_ENABLE_MASK);
96
97 /* Enable Marvell interrupts through Elmer0. */
98 if (t1_is_asic(cphy->adapter)) {
99 u32 elmer;
100
101 (void) t1_tpi_read(cphy->adapter, A_ELMER0_INT_ENABLE, &elmer);
102 elmer |= ELMER0_GP_BIT1;
103 if (is_T2(cphy->adapter)) {
104 elmer |= ELMER0_GP_BIT2|ELMER0_GP_BIT3|ELMER0_GP_BIT4;
105 }
106 (void) t1_tpi_write(cphy->adapter, A_ELMER0_INT_ENABLE, elmer);
107 }
108 return 0;
109 }
110
mv88e1xxx_interrupt_disable(struct cphy * cphy)111 static int mv88e1xxx_interrupt_disable(struct cphy *cphy)
112 {
113 /* Disable all phy interrupts. */
114 (void) simple_mdio_write(cphy, MV88E1XXX_INTERRUPT_ENABLE_REGISTER, 0);
115
116 /* Disable Marvell interrupts through Elmer0. */
117 if (t1_is_asic(cphy->adapter)) {
118 u32 elmer;
119
120 (void) t1_tpi_read(cphy->adapter, A_ELMER0_INT_ENABLE, &elmer);
121 elmer &= ~ELMER0_GP_BIT1;
122 if (is_T2(cphy->adapter)) {
123 elmer &= ~(ELMER0_GP_BIT2|ELMER0_GP_BIT3|ELMER0_GP_BIT4);
124 }
125 (void) t1_tpi_write(cphy->adapter, A_ELMER0_INT_ENABLE, elmer);
126 }
127 return 0;
128 }
129
mv88e1xxx_interrupt_clear(struct cphy * cphy)130 static int mv88e1xxx_interrupt_clear(struct cphy *cphy)
131 {
132 u32 elmer;
133
134 /* Clear PHY interrupts by reading the register. */
135 (void) simple_mdio_read(cphy, MV88E1XXX_INTERRUPT_STATUS_REGISTER, &elmer);
136
137 /* Clear Marvell interrupts through Elmer0. */
138 if (t1_is_asic(cphy->adapter)) {
139 (void) t1_tpi_read(cphy->adapter, A_ELMER0_INT_CAUSE, &elmer);
140 elmer |= ELMER0_GP_BIT1;
141 if (is_T2(cphy->adapter)) {
142 elmer |= ELMER0_GP_BIT2|ELMER0_GP_BIT3|ELMER0_GP_BIT4;
143 }
144 (void) t1_tpi_write(cphy->adapter, A_ELMER0_INT_CAUSE, elmer);
145 }
146 return 0;
147 }
148
149 /*
150 * Set the PHY speed and duplex. This also disables auto-negotiation, except
151 * for 1Gb/s, where auto-negotiation is mandatory.
152 */
mv88e1xxx_set_speed_duplex(struct cphy * phy,int speed,int duplex)153 static int mv88e1xxx_set_speed_duplex(struct cphy *phy, int speed, int duplex)
154 {
155 u32 ctl;
156
157 (void) simple_mdio_read(phy, MII_BMCR, &ctl);
158 if (speed >= 0) {
159 ctl &= ~(BMCR_SPEED100 | BMCR_SPEED1000 | BMCR_ANENABLE);
160 if (speed == SPEED_100)
161 ctl |= BMCR_SPEED100;
162 else if (speed == SPEED_1000)
163 ctl |= BMCR_SPEED1000;
164 }
165 if (duplex >= 0) {
166 ctl &= ~(BMCR_FULLDPLX | BMCR_ANENABLE);
167 if (duplex == DUPLEX_FULL)
168 ctl |= BMCR_FULLDPLX;
169 }
170 if (ctl & BMCR_SPEED1000) /* auto-negotiation required for 1Gb/s */
171 ctl |= BMCR_ANENABLE;
172 (void) simple_mdio_write(phy, MII_BMCR, ctl);
173 return 0;
174 }
175
mv88e1xxx_crossover_set(struct cphy * cphy,int crossover)176 static int mv88e1xxx_crossover_set(struct cphy *cphy, int crossover)
177 {
178 u32 data32;
179
180 (void) simple_mdio_read(cphy, MV88E1XXX_SPECIFIC_CNTRL_REGISTER, &data32);
181 data32 &= ~V_PSCR_MDI_XOVER_MODE(M_PSCR_MDI_XOVER_MODE);
182 data32 |= V_PSCR_MDI_XOVER_MODE(crossover);
183 (void) simple_mdio_write(cphy, MV88E1XXX_SPECIFIC_CNTRL_REGISTER, data32);
184 return 0;
185 }
186
mv88e1xxx_autoneg_enable(struct cphy * cphy)187 static int mv88e1xxx_autoneg_enable(struct cphy *cphy)
188 {
189 u32 ctl;
190
191 (void) mv88e1xxx_crossover_set(cphy, CROSSOVER_AUTO);
192
193 (void) simple_mdio_read(cphy, MII_BMCR, &ctl);
194 /* restart autoneg for change to take effect */
195 ctl |= BMCR_ANENABLE | BMCR_ANRESTART;
196 (void) simple_mdio_write(cphy, MII_BMCR, ctl);
197 return 0;
198 }
199
mv88e1xxx_autoneg_disable(struct cphy * cphy)200 static int mv88e1xxx_autoneg_disable(struct cphy *cphy)
201 {
202 u32 ctl;
203
204 /*
205 * Crossover *must* be set to manual in order to disable auto-neg.
206 * The Alaska FAQs document highlights this point.
207 */
208 (void) mv88e1xxx_crossover_set(cphy, CROSSOVER_MDI);
209
210 /*
211 * Must include autoneg reset when disabling auto-neg. This
212 * is described in the Alaska FAQ document.
213 */
214 (void) simple_mdio_read(cphy, MII_BMCR, &ctl);
215 ctl &= ~BMCR_ANENABLE;
216 (void) simple_mdio_write(cphy, MII_BMCR, ctl | BMCR_ANRESTART);
217 return 0;
218 }
219
mv88e1xxx_autoneg_restart(struct cphy * cphy)220 static int mv88e1xxx_autoneg_restart(struct cphy *cphy)
221 {
222 mdio_set_bit(cphy, MII_BMCR, BMCR_ANRESTART);
223 return 0;
224 }
225
mv88e1xxx_advertise(struct cphy * phy,unsigned int advertise_map)226 static int mv88e1xxx_advertise(struct cphy *phy, unsigned int advertise_map)
227 {
228 u32 val = 0;
229
230 if (advertise_map &
231 (ADVERTISED_1000baseT_Half | ADVERTISED_1000baseT_Full)) {
232 (void) simple_mdio_read(phy, MII_GBCR, &val);
233 val &= ~(GBCR_ADV_1000HALF | GBCR_ADV_1000FULL);
234 if (advertise_map & ADVERTISED_1000baseT_Half)
235 val |= GBCR_ADV_1000HALF;
236 if (advertise_map & ADVERTISED_1000baseT_Full)
237 val |= GBCR_ADV_1000FULL;
238 }
239 (void) simple_mdio_write(phy, MII_GBCR, val);
240
241 val = 1;
242 if (advertise_map & ADVERTISED_10baseT_Half)
243 val |= ADVERTISE_10HALF;
244 if (advertise_map & ADVERTISED_10baseT_Full)
245 val |= ADVERTISE_10FULL;
246 if (advertise_map & ADVERTISED_100baseT_Half)
247 val |= ADVERTISE_100HALF;
248 if (advertise_map & ADVERTISED_100baseT_Full)
249 val |= ADVERTISE_100FULL;
250 if (advertise_map & ADVERTISED_PAUSE)
251 val |= ADVERTISE_PAUSE;
252 if (advertise_map & ADVERTISED_ASYM_PAUSE)
253 val |= ADVERTISE_PAUSE_ASYM;
254 (void) simple_mdio_write(phy, MII_ADVERTISE, val);
255 return 0;
256 }
257
mv88e1xxx_set_loopback(struct cphy * cphy,int on)258 static int mv88e1xxx_set_loopback(struct cphy *cphy, int on)
259 {
260 if (on)
261 mdio_set_bit(cphy, MII_BMCR, BMCR_LOOPBACK);
262 else
263 mdio_clear_bit(cphy, MII_BMCR, BMCR_LOOPBACK);
264 return 0;
265 }
266
mv88e1xxx_get_link_status(struct cphy * cphy,int * link_ok,int * speed,int * duplex,int * fc)267 static int mv88e1xxx_get_link_status(struct cphy *cphy, int *link_ok,
268 int *speed, int *duplex, int *fc)
269 {
270 u32 status;
271 int sp = -1, dplx = -1, pause = 0;
272
273 (void) simple_mdio_read(cphy, MV88E1XXX_SPECIFIC_STATUS_REGISTER, &status);
274 if ((status & V_PSSR_STATUS_RESOLVED) != 0) {
275 if (status & V_PSSR_RX_PAUSE)
276 pause |= PAUSE_RX;
277 if (status & V_PSSR_TX_PAUSE)
278 pause |= PAUSE_TX;
279 dplx = (status & V_PSSR_DUPLEX) ? DUPLEX_FULL : DUPLEX_HALF;
280 sp = G_PSSR_SPEED(status);
281 if (sp == 0)
282 sp = SPEED_10;
283 else if (sp == 1)
284 sp = SPEED_100;
285 else
286 sp = SPEED_1000;
287 }
288 if (link_ok)
289 *link_ok = (status & V_PSSR_LINK) != 0;
290 if (speed)
291 *speed = sp;
292 if (duplex)
293 *duplex = dplx;
294 if (fc)
295 *fc = pause;
296 return 0;
297 }
298
mv88e1xxx_downshift_set(struct cphy * cphy,int downshift_enable)299 static int mv88e1xxx_downshift_set(struct cphy *cphy, int downshift_enable)
300 {
301 u32 val;
302
303 (void) simple_mdio_read(cphy, MV88E1XXX_EXT_PHY_SPECIFIC_CNTRL_REGISTER, &val);
304
305 /*
306 * Set the downshift counter to 2 so we try to establish Gb link
307 * twice before downshifting.
308 */
309 val &= ~(V_DOWNSHIFT_ENABLE | V_DOWNSHIFT_CNT(M_DOWNSHIFT_CNT));
310
311 if (downshift_enable)
312 val |= V_DOWNSHIFT_ENABLE | V_DOWNSHIFT_CNT(2);
313 (void) simple_mdio_write(cphy, MV88E1XXX_EXT_PHY_SPECIFIC_CNTRL_REGISTER, val);
314 return 0;
315 }
316
mv88e1xxx_interrupt_handler(struct cphy * cphy)317 static int mv88e1xxx_interrupt_handler(struct cphy *cphy)
318 {
319 int cphy_cause = 0;
320 u32 status;
321
322 /*
323 * Loop until cause reads zero. Need to handle bouncing interrupts.
324 */
325 /*CONSTCOND*/
326 while (1) {
327 u32 cause;
328
329 (void) simple_mdio_read(cphy, MV88E1XXX_INTERRUPT_STATUS_REGISTER,
330 &cause);
331 cause &= INTR_ENABLE_MASK;
332 if (!cause) break;
333
334 if (cause & MV88E1XXX_INTR_LINK_CHNG) {
335 (void) simple_mdio_read(cphy,
336 MV88E1XXX_SPECIFIC_STATUS_REGISTER, &status);
337
338 if (status & MV88E1XXX_INTR_LINK_CHNG) {
339 cphy->state |= PHY_LINK_UP;
340 } else {
341 cphy->state &= ~PHY_LINK_UP;
342 if (cphy->state & PHY_AUTONEG_EN)
343 cphy->state &= ~PHY_AUTONEG_RDY;
344 cphy_cause |= cphy_cause_link_change;
345 }
346 }
347
348 if (cause & MV88E1XXX_INTR_AUTONEG_DONE)
349 cphy->state |= PHY_AUTONEG_RDY;
350
351 if ((cphy->state & (PHY_LINK_UP | PHY_AUTONEG_RDY)) ==
352 (PHY_LINK_UP | PHY_AUTONEG_RDY))
353 cphy_cause |= cphy_cause_link_change;
354 }
355 return cphy_cause;
356 }
357
mv88e1xxx_destroy(struct cphy * cphy)358 static void mv88e1xxx_destroy(struct cphy *cphy)
359 {
360 t1_os_free((void *)cphy, sizeof(*cphy));
361 }
362
363 #ifdef C99_NOT_SUPPORTED
364 static struct cphy_ops mv88e1xxx_ops = {
365 mv88e1xxx_destroy,
366 mv88e1xxx_reset,
367 mv88e1xxx_interrupt_enable,
368 mv88e1xxx_interrupt_disable,
369 mv88e1xxx_interrupt_clear,
370 mv88e1xxx_interrupt_handler,
371 mv88e1xxx_autoneg_enable,
372 mv88e1xxx_autoneg_disable,
373 mv88e1xxx_autoneg_restart,
374 mv88e1xxx_advertise,
375 mv88e1xxx_set_loopback,
376 mv88e1xxx_set_speed_duplex,
377 mv88e1xxx_get_link_status,
378 };
379 #else
380 static struct cphy_ops mv88e1xxx_ops = {
381 .destroy = mv88e1xxx_destroy,
382 .reset = mv88e1xxx_reset,
383 .interrupt_enable = mv88e1xxx_interrupt_enable,
384 .interrupt_disable = mv88e1xxx_interrupt_disable,
385 .interrupt_clear = mv88e1xxx_interrupt_clear,
386 .interrupt_handler = mv88e1xxx_interrupt_handler,
387 .autoneg_enable = mv88e1xxx_autoneg_enable,
388 .autoneg_disable = mv88e1xxx_autoneg_disable,
389 .autoneg_restart = mv88e1xxx_autoneg_restart,
390 .advertise = mv88e1xxx_advertise,
391 .set_loopback = mv88e1xxx_set_loopback,
392 .set_speed_duplex = mv88e1xxx_set_speed_duplex,
393 .get_link_status = mv88e1xxx_get_link_status,
394 };
395 #endif
396
mv88e1xxx_phy_create(adapter_t * adapter,int phy_addr,struct mdio_ops * mdio_ops)397 static struct cphy *mv88e1xxx_phy_create(adapter_t *adapter, int phy_addr,
398 struct mdio_ops *mdio_ops)
399 {
400 struct cphy *cphy = t1_os_malloc_wait_zero(sizeof(*cphy));
401
402 if (!cphy) return NULL;
403
404 cphy_init(cphy, adapter, phy_addr, &mv88e1xxx_ops, mdio_ops);
405
406 /* Configure particular PHY's to run in a different mode. */
407 if ((board_info(adapter)->caps & SUPPORTED_TP) &&
408 board_info(adapter)->chip_phy == CHBT_PHY_88E1111) {
409 /*
410 * Configure the PHY transmitter as class A to reduce EMI.
411 */
412 (void) simple_mdio_write(cphy, MV88E1XXX_EXTENDED_ADDR_REGISTER, 0xB);
413 (void) simple_mdio_write(cphy, MV88E1XXX_EXTENDED_REGISTER, 0x8004);
414 }
415 (void) mv88e1xxx_downshift_set(cphy, 1); /* Enable downshift */
416
417 /* LED */
418 if (is_T2(adapter)) {
419 (void) simple_mdio_write(cphy,
420 MV88E1XXX_LED_CONTROL_REGISTER, 0x1);
421 }
422
423 return cphy;
424 }
425
426 /* ARGSUSED */
mv88e1xxx_phy_reset(adapter_t * adapter)427 static int mv88e1xxx_phy_reset(adapter_t* adapter)
428 {
429 return 0;
430 }
431
432 struct gphy t1_mv88e1xxx_ops = {
433 mv88e1xxx_phy_create,
434 mv88e1xxx_phy_reset
435 };
436