aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/net/sfc/tenxpress.c
blob: 38e96ed33d333a1c6c53a7fa3296e7573c231956 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
/****************************************************************************
 * Driver for Solarflare 802.3an compliant PHY
 * Copyright 2007 Solarflare Communications Inc.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published
 * by the Free Software Foundation, incorporated herein by reference.
 */

#include <linux/delay.h>
#include <linux/seq_file.h>
#include "efx.h"
#include "gmii.h"
#include "mdio_10g.h"
#include "falcon.h"
#include "phy.h"
#include "falcon_hwdefs.h"
#include "boards.h"
#include "mac.h"

/* We expect these MMDs to be in the package */
/* AN not here as mdio_check_mmds() requires STAT2 support */
#define TENXPRESS_REQUIRED_DEVS (MDIO_MMDREG_DEVS0_PMAPMD | \
				 MDIO_MMDREG_DEVS0_PCS    | \
				 MDIO_MMDREG_DEVS0_PHYXS)

#define TENXPRESS_LOOPBACKS ((1 << LOOPBACK_PHYXS) |	\
			     (1 << LOOPBACK_PCS) |	\
			     (1 << LOOPBACK_PMAPMD) |	\
			     (1 << LOOPBACK_NETWORK))

/* We complain if we fail to see the link partner as 10G capable this many
 * times in a row (must be > 1 as sampling the autoneg. registers is racy)
 */
#define MAX_BAD_LP_TRIES	(5)

/* Extended control register */
#define	PMA_PMD_XCONTROL_REG 0xc000
#define	PMA_PMD_LNPGA_POWERDOWN_LBN 8
#define	PMA_PMD_LNPGA_POWERDOWN_WIDTH 1

/* extended status register */
#define PMA_PMD_XSTATUS_REG 0xc001
#define PMA_PMD_XSTAT_FLP_LBN   (12)

/* LED control register */
#define PMA_PMD_LED_CTRL_REG	(0xc007)
#define PMA_PMA_LED_ACTIVITY_LBN	(3)

/* LED function override register */
#define PMA_PMD_LED_OVERR_REG	(0xc009)
/* Bit positions for different LEDs (there are more but not wired on SFE4001)*/
#define PMA_PMD_LED_LINK_LBN	(0)
#define PMA_PMD_LED_SPEED_LBN	(2)
#define PMA_PMD_LED_TX_LBN	(4)
#define PMA_PMD_LED_RX_LBN	(6)
/* Override settings */
#define	PMA_PMD_LED_AUTO	(0)	/* H/W control */
#define	PMA_PMD_LED_ON		(1)
#define	PMA_PMD_LED_OFF		(2)
#define PMA_PMD_LED_FLASH	(3)
/* All LEDs under hardware control */
#define PMA_PMD_LED_FULL_AUTO	(0)
/* Green and Amber under hardware control, Red off */
#define PMA_PMD_LED_DEFAULT	(PMA_PMD_LED_OFF << PMA_PMD_LED_RX_LBN)


/* Self test (BIST) control register */
#define PMA_PMD_BIST_CTRL_REG	(0xc014)
#define PMA_PMD_BIST_BER_LBN	(2)	/* Run BER test */
#define PMA_PMD_BIST_CONT_LBN	(1)	/* Run continuous BIST until cleared */
#define PMA_PMD_BIST_SINGLE_LBN	(0)	/* Run 1 BIST iteration (self clears) */
/* Self test status register */
#define PMA_PMD_BIST_STAT_REG	(0xc015)
#define PMA_PMD_BIST_ENX_LBN	(3)
#define PMA_PMD_BIST_PMA_LBN	(2)
#define PMA_PMD_BIST_RXD_LBN	(1)
#define PMA_PMD_BIST_AFE_LBN	(0)

/* Special Software reset register */
#define PMA_PMD_EXT_CTRL_REG 49152
#define PMA_PMD_EXT_SSR_LBN 15

#define BIST_MAX_DELAY	(1000)
#define BIST_POLL_DELAY	(10)

/* Misc register defines */
#define PCS_CLOCK_CTRL_REG 0xd801
#define PLL312_RST_N_LBN 2

#define PCS_SOFT_RST2_REG 0xd806
#define SERDES_RST_N_LBN 13
#define XGXS_RST_N_LBN 12

#define	PCS_TEST_SELECT_REG 0xd807	/* PRM 10.5.8 */
#define	CLK312_EN_LBN 3

/* PHYXS registers */
#define PHYXS_TEST1         (49162)
#define LOOPBACK_NEAR_LBN   (8)
#define LOOPBACK_NEAR_WIDTH (1)

/* Boot status register */
#define PCS_BOOT_STATUS_REG	(0xd000)
#define PCS_BOOT_FATAL_ERR_LBN	(0)
#define PCS_BOOT_PROGRESS_LBN	(1)
#define PCS_BOOT_PROGRESS_WIDTH	(2)
#define PCS_BOOT_COMPLETE_LBN	(3)
#define PCS_BOOT_MAX_DELAY	(100)
#define PCS_BOOT_POLL_DELAY	(10)

/* Time to wait between powering down the LNPGA and turning off the power
 * rails */
#define LNPGA_PDOWN_WAIT	(HZ / 5)

static int crc_error_reset_threshold = 100;
module_param(crc_error_reset_threshold, int, 0644);
MODULE_PARM_DESC(crc_error_reset_threshold,
		 "Max number of CRC errors before XAUI reset");

struct tenxpress_phy_data {
	enum tenxpress_state state;
	enum efx_loopback_mode loopback_mode;
	atomic_t bad_crc_count;
	int tx_disabled;
	int bad_lp_tries;
};

static int tenxpress_state_is(struct efx_nic *efx, int state)
{
	struct tenxpress_phy_data *phy_data = efx->phy_data;
	return (phy_data != NULL) && (state == phy_data->state);
}

void tenxpress_set_state(struct efx_nic *efx,
				enum tenxpress_state state)
{
	struct tenxpress_phy_data *phy_data = efx->phy_data;
	if (phy_data != NULL)
		phy_data->state = state;
}

void tenxpress_crc_err(struct efx_nic *efx)
{
	struct tenxpress_phy_data *phy_data = efx->phy_data;
	if (phy_data != NULL)
		atomic_inc(&phy_data->bad_crc_count);
}

/* Check that the C166 has booted successfully */
static int tenxpress_phy_check(struct efx_nic *efx)
{
	int phy_id = efx->mii.phy_id;
	int count = PCS_BOOT_MAX_DELAY / PCS_BOOT_POLL_DELAY;
	int boot_stat;

	/* Wait for the boot to complete (or not) */
	while (count) {
		boot_stat = mdio_clause45_read(efx, phy_id,
					       MDIO_MMD_PCS,
					       PCS_BOOT_STATUS_REG);
		if (boot_stat & (1 << PCS_BOOT_COMPLETE_LBN))
			break;
		count--;
		udelay(PCS_BOOT_POLL_DELAY);
	}

	if (!count) {
		EFX_ERR(efx, "%s: PHY boot timed out. Last status "
			"%x\n", __func__,
			(boot_stat >> PCS_BOOT_PROGRESS_LBN) &
			((1 << PCS_BOOT_PROGRESS_WIDTH) - 1));
		return -ETIMEDOUT;
	}

	return 0;
}

static void tenxpress_reset_xaui(struct efx_nic *efx);

static int tenxpress_init(struct efx_nic *efx)
{
	int rc, reg;

	/* Turn on the clock  */
	reg = (1 << CLK312_EN_LBN);
	mdio_clause45_write(efx, efx->mii.phy_id,
			    MDIO_MMD_PCS, PCS_TEST_SELECT_REG, reg);

	rc = tenxpress_phy_check(efx);
	if (rc < 0)
		return rc;

	/* Set the LEDs up as: Green = Link, Amber = Link/Act, Red = Off */
	reg = mdio_clause45_read(efx, efx->mii.phy_id,
				 MDIO_MMD_PMAPMD, PMA_PMD_LED_CTRL_REG);
	reg |= (1 << PMA_PMA_LED_ACTIVITY_LBN);
	mdio_clause45_write(efx, efx->mii.phy_id, MDIO_MMD_PMAPMD,
			    PMA_PMD_LED_CTRL_REG, reg);

	reg = PMA_PMD_LED_DEFAULT;
	mdio_clause45_write(efx, efx->mii.phy_id, MDIO_MMD_PMAPMD,
			    PMA_PMD_LED_OVERR_REG, reg);

	return rc;
}

static int tenxpress_phy_init(struct efx_nic *efx)
{
	struct tenxpress_phy_data *phy_data;
	int rc = 0;

	phy_data = kzalloc(sizeof(*phy_data), GFP_KERNEL);
	efx->phy_data = phy_data;

	tenxpress_set_state(efx, TENXPRESS_STATUS_NORMAL);

	if (!sfe4001_phy_flash_cfg) {
		rc = mdio_clause45_wait_reset_mmds(efx,
						   TENXPRESS_REQUIRED_DEVS);
		if (rc < 0)
			goto fail;
	}

	rc = mdio_clause45_check_mmds(efx, TENXPRESS_REQUIRED_DEVS, 0);
	if (rc < 0)
		goto fail;

	rc = tenxpress_init(efx);
	if (rc < 0)
		goto fail;

	schedule_timeout_uninterruptible(HZ / 5); /* 200ms */

	/* Let XGXS and SerDes out of reset and resets 10XPress */
	falcon_reset_xaui(efx);

	return 0;

 fail:
	kfree(efx->phy_data);
	efx->phy_data = NULL;
	return rc;
}

static int tenxpress_special_reset(struct efx_nic *efx)
{
	int rc, reg;

	EFX_TRACE(efx, "%s\n", __func__);

	/* Initiate reset */
	reg = mdio_clause45_read(efx, efx->mii.phy_id,
				 MDIO_MMD_PMAPMD, PMA_PMD_EXT_CTRL_REG);
	reg |= (1 << PMA_PMD_EXT_SSR_LBN);
	mdio_clause45_write(efx, efx->mii.phy_id, MDIO_MMD_PMAPMD,
			    PMA_PMD_EXT_CTRL_REG, reg);

	msleep(200);

	/* Wait for the blocks to come out of reset */
	rc = mdio_clause45_wait_reset_mmds(efx,
					   TENXPRESS_REQUIRED_DEVS);
	if (rc < 0)
		return rc;

	/* Try and reconfigure the device */
	rc = tenxpress_init(efx);
	if (rc < 0)
		return rc;

	return 0;
}

static void tenxpress_set_bad_lp(struct efx_nic *efx, int bad_lp)
{
	struct tenxpress_phy_data *pd = efx->phy_data;
	int reg;

	/* Nothing to do if all is well and was previously so. */
	if (!(bad_lp || pd->bad_lp_tries))
		return;

	reg = mdio_clause45_read(efx, efx->mii.phy_id,
				 MDIO_MMD_PMAPMD, PMA_PMD_LED_OVERR_REG);

	if (bad_lp)
		pd->bad_lp_tries++;
	else
		pd->bad_lp_tries = 0;

	if (pd->bad_lp_tries == MAX_BAD_LP_TRIES) {
		pd->bad_lp_tries = 0;	/* Restart count */
		reg &= ~(PMA_PMD_LED_FLASH << PMA_PMD_LED_RX_LBN);
		reg |= (PMA_PMD_LED_FLASH << PMA_PMD_LED_RX_LBN);
		EFX_ERR(efx, "This NIC appears to be plugged into"
			" a port that is not 10GBASE-T capable.\n"
			" This PHY is 10GBASE-T ONLY, so no link can"
			" be established.\n");
	} else {
		reg |= (PMA_PMD_LED_OFF << PMA_PMD_LED_RX_LBN);
	}
	mdio_clause45_write(efx, efx->mii.phy_id, MDIO_MMD_PMAPMD,
			    PMA_PMD_LED_OVERR_REG, reg);
}

/* Check link status and return a boolean OK value. If the link is NOT
 * OK we have a quick rummage round to see if we appear to be plugged
 * into a non-10GBT port and if so warn the user that they won't get
 * link any time soon as we are 10GBT only, unless caller specified
 * not to do this check (it isn't useful in loopback) */
static int tenxpress_link_ok(struct efx_nic *efx, int check_lp)
{
	int ok = mdio_clause45_links_ok(efx, TENXPRESS_REQUIRED_DEVS);

	if (ok) {
		tenxpress_set_bad_lp(efx, 0);
	} else if (check_lp) {
		/* Are we plugged into the wrong sort of link? */
		int bad_lp = 0;
		int phy_id = efx->mii.phy_id;
		int an_stat = mdio_clause45_read(efx, phy_id, MDIO_MMD_AN,
						 MDIO_AN_STATUS);
		int xphy_stat = mdio_clause45_read(efx, phy_id,
						   MDIO_MMD_PMAPMD,
						   PMA_PMD_XSTATUS_REG);
		/* Are we plugged into anything that sends FLPs? If
		 * not we can't distinguish between not being plugged
		 * in and being plugged into a non-AN antique. The FLP
		 * bit has the advantage of not clearing when autoneg
		 * restarts. */
		if (!(xphy_stat & (1 << PMA_PMD_XSTAT_FLP_LBN))) {
			tenxpress_set_bad_lp(efx, 0);
			return ok;
		}

		/* If it can do 10GBT it must be XNP capable */
		bad_lp = !(an_stat & (1 << MDIO_AN_STATUS_XNP_LBN));
		if (!bad_lp && (an_stat & (1 << MDIO_AN_STATUS_PAGE_LBN))) {
			bad_lp = !(mdio_clause45_read(efx, phy_id,
					MDIO_MMD_AN, MDIO_AN_10GBT_STATUS) &
					(1 << MDIO_AN_10GBT_STATUS_LP_10G_LBN));
		}
		tenxpress_set_bad_lp(efx, bad_lp);
	}
	return ok;
}

static void tenxpress_phyxs_loopback(struct efx_nic *efx)
{
	int phy_id = efx->mii.phy_id;
	int ctrl1, ctrl2;

	ctrl1 = ctrl2 = mdio_clause45_read(efx, phy_id, MDIO_MMD_PHYXS,
					   PHYXS_TEST1);
	if (efx->loopback_mode == LOOPBACK_PHYXS)
		ctrl2 |= (1 << LOOPBACK_NEAR_LBN);
	else
		ctrl2 &= ~(1 << LOOPBACK_NEAR_LBN);
	if (ctrl1 != ctrl2)
		mdio_clause45_write(efx, phy_id, MDIO_MMD_PHYXS,
				    PHYXS_TEST1, ctrl2);
}

static void tenxpress_phy_reconfigure(struct efx_nic *efx)
{
	struct tenxpress_phy_data *phy_data = efx->phy_data;
	int loop_change = LOOPBACK_OUT_OF(phy_data, efx,
					  TENXPRESS_LOOPBACKS);

	if (!tenxpress_state_is(efx, TENXPRESS_STATUS_NORMAL))
		return;

	/* When coming out of transmit disable, coming out of low power
	 * mode, or moving out of any PHY internal loopback mode,
	 * perform a special software reset */
	if ((phy_data->tx_disabled && !efx->tx_disabled) ||
	    loop_change) {
		tenxpress_special_reset(efx);
		falcon_reset_xaui(efx);
	}

	mdio_clause45_transmit_disable(efx);
	mdio_clause45_phy_reconfigure(efx);
	tenxpress_phyxs_loopback(efx);

	phy_data->tx_disabled = efx->tx_disabled;
	phy_data->loopback_mode = efx->loopback_mode;
	efx->link_up = tenxpress_link_ok(efx, 0);
	efx->link_options = GM_LPA_10000FULL;
}

static void tenxpress_phy_clear_interrupt(struct efx_nic *efx)
{
	/* Nothing done here - LASI interrupts aren't reliable so poll  */
}


/* Poll PHY for interrupt */
static int tenxpress_phy_check_hw(struct efx_nic *efx)
{
	struct tenxpress_phy_data *phy_data = efx->phy_data;
	int phy_up = tenxpress_state_is(efx, TENXPRESS_STATUS_NORMAL);
	int link_ok;

	link_ok = phy_up && tenxpress_link_ok(efx, 1);

	if (link_ok != efx->link_up)
		falcon_xmac_sim_phy_event(efx);

	/* Nothing to check if we've already shut down the PHY */
	if (!phy_up)
		return 0;

	if (atomic_read(&phy_data->bad_crc_count) > crc_error_reset_threshold) {
		EFX_ERR(efx, "Resetting XAUI due to too many CRC errors\n");
		falcon_reset_xaui(efx);
		atomic_set(&phy_data->bad_crc_count, 0);
	}

	return 0;
}

static void tenxpress_phy_fini(struct efx_nic *efx)
{
	int reg;

	/* Power down the LNPGA */
	reg = (1 << PMA_PMD_LNPGA_POWERDOWN_LBN);
	mdio_clause45_write(efx, efx->mii.phy_id, MDIO_MMD_PMAPMD,
			    PMA_PMD_XCONTROL_REG, reg);

	/* Waiting here ensures that the board fini, which can turn off the
	 * power to the PHY, won't get run until the LNPGA powerdown has been
	 * given long enough to complete. */
	schedule_timeout_uninterruptible(LNPGA_PDOWN_WAIT); /* 200 ms */

	kfree(efx->phy_data);
	efx->phy_data = NULL;
}


/* Set the RX and TX LEDs and Link LED flashing. The other LEDs
 * (which probably aren't wired anyway) are left in AUTO mode */
void tenxpress_phy_blink(struct efx_nic *efx, int blink)
{
	int reg;

	if (blink)
		reg = (PMA_PMD_LED_FLASH << PMA_PMD_LED_TX_LBN) |
			(PMA_PMD_LED_FLASH << PMA_PMD_LED_RX_LBN) |
			(PMA_PMD_LED_FLASH << PMA_PMD_LED_LINK_LBN);
	else
		reg = PMA_PMD_LED_DEFAULT;

	mdio_clause45_write(efx, efx->mii.phy_id, MDIO_MMD_PMAPMD,
			    PMA_PMD_LED_OVERR_REG, reg);
}

static void tenxpress_reset_xaui(struct efx_nic *efx)
{
	int phy = efx->mii.phy_id;
	int clk_ctrl, test_select, soft_rst2;

	/* Real work is done on clock_ctrl other resets are thought to be
	 * optional but make the reset more reliable
	 */

	/* Read */
	clk_ctrl = mdio_clause45_read(efx, phy, MDIO_MMD_PCS,
				      PCS_CLOCK_CTRL_REG);
	test_select = mdio_clause45_read(efx, phy, MDIO_MMD_PCS,
					 PCS_TEST_SELECT_REG);
	soft_rst2 = mdio_clause45_read(efx, phy, MDIO_MMD_PCS,
				       PCS_SOFT_RST2_REG);

	/* Put in reset */
	test_select &= ~(1 << CLK312_EN_LBN);
	mdio_clause45_write(efx, phy, MDIO_MMD_PCS,
			    PCS_TEST_SELECT_REG, test_select);

	soft_rst2 &= ~((1 << XGXS_RST_N_LBN) | (1 << SERDES_RST_N_LBN));
	mdio_clause45_write(efx, phy, MDIO_MMD_PCS,
			    PCS_SOFT_RST2_REG, soft_rst2);

	clk_ctrl &= ~(1 << PLL312_RST_N_LBN);
	mdio_clause45_write(efx, phy, MDIO_MMD_PCS,
			    PCS_CLOCK_CTRL_REG, clk_ctrl);
	udelay(10);

	/* Remove reset */
	clk_ctrl |= (1 << PLL312_RST_N_LBN);
	mdio_clause45_write(efx, phy, MDIO_MMD_PCS,
			    PCS_CLOCK_CTRL_REG, clk_ctrl);
	udelay(10);

	soft_rst2 |= ((1 << XGXS_RST_N_LBN) | (1 << SERDES_RST_N_LBN));
	mdio_clause45_write(efx, phy, MDIO_MMD_PCS,
			    PCS_SOFT_RST2_REG, soft_rst2);
	udelay(10);

	test_select |= (1 << CLK312_EN_LBN);
	mdio_clause45_write(efx, phy, MDIO_MMD_PCS,
			    PCS_TEST_SELECT_REG, test_select);
	udelay(10);
}

struct efx_phy_operations falcon_tenxpress_phy_ops = {
	.init             = tenxpress_phy_init,
	.reconfigure      = tenxpress_phy_reconfigure,
	.check_hw         = tenxpress_phy_check_hw,
	.fini             = tenxpress_phy_fini,
	.clear_interrupt  = tenxpress_phy_clear_interrupt,
	.reset_xaui       = tenxpress_reset_xaui,
	.mmds             = TENXPRESS_REQUIRED_DEVS,
	.loopbacks        = TENXPRESS_LOOPBACKS,
};