summaryrefslogtreecommitdiff
path: root/usr/src/uts/common/io/chxge/com/mc4.c
blob: d0fc7765d00339e094da539f52b13c4432774a29 (plain)
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
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * This file is part of the Chelsio T1 Ethernet driver.
 *
 * Copyright (C) 2003-2005 Chelsio Communications.  All rights reserved.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"	/* mc4.c */

#include "common.h"
#include "regs.h"
#include "mc4.h"

struct pemc4 {
	adapter_t *adapter;
	unsigned int size;
	unsigned int nwords;           /* MC4 width in terms of 32-bit words */
	struct pemc4_intr_counts intr_cnt;
};

void t1_mc4_destroy(struct pemc4 *mc4)
{
	t1_os_free((void *)mc4, sizeof(*mc4));
}

#define is_MC4A(adapter) (!t1_is_T1B(adapter))

/* Calculate amount of MC4 memory. */
static unsigned int __devinit mc4_calc_size(adapter_t *adapter)
{
	u32 mc4_cfg = t1_read_reg_4(adapter, A_MC4_CFG);
	unsigned int width = is_MC4A(adapter) ? G_MC4A_WIDTH(mc4_cfg) :
		                                !!(mc4_cfg & F_MC4_NARROW);

	return (256 * 1024 * 1024) >> width;
}

/*
 * Write a value to a register and check that the write completed.  These
 * writes normally complete in a cycle or two, so one read should suffice but
 * just in case we give them a bit of grace period.  Note that the very first
 * read exists to flush the posted write to the device.
 */
static int wrreg_wait(adapter_t *adapter, unsigned int addr, u32 val)
{
	int attempts = 2;

	t1_write_reg_4(adapter,	addr, val);
	val = t1_read_reg_4(adapter, addr);                   /* flush */
	while (attempts--) {
		if (!(t1_read_reg_4(adapter, addr) & F_BUSY))
			return 0;
		if (attempts)
			DELAY_US(1);
	}
	CH_ERR("%s: write to MC4 register 0x%x timed out\n",
	       adapter_name(adapter), addr);
	return -EIO;
}

#define MC4_DLL_DONE (F_MASTER_DLL_LOCKED | F_MASTER_DLL_MAX_TAP_COUNT)

int t1_mc4_init(struct pemc4 *mc4, unsigned int mc4_clock)
{
	int attempts;
	u32 val;
	unsigned int width, ext_mode, slow_mode;
	adapter_t *adapter = mc4->adapter;

	/* Power up the FCRAMs. */
	val = t1_read_reg_4(adapter, A_MC4_CFG);
	t1_write_reg_4(adapter, A_MC4_CFG, val | F_POWER_UP);
	val = t1_read_reg_4(adapter, A_MC4_CFG);               /* flush */

	if (is_MC4A(adapter)) {
		slow_mode = val & F_MC4A_SLOW;
		width = G_MC4A_WIDTH(val);

		/* If we're not in slow mode, we are using the DLLs */
		if (!slow_mode) {
			/* Clear Reset */
			val = t1_read_reg_4(adapter, A_MC4_STROBE);
			t1_write_reg_4(adapter, A_MC4_STROBE,
				       val & ~F_SLAVE_DLL_RESET);

			/* Wait for slave DLLs to lock */
			DELAY_US(2 * 512 / (mc4_clock / 1000000) + 1);
		}
	} else {
		slow_mode = val & F_MC4_SLOW;
		width = !!(val & F_MC4_NARROW);

		/* Initializes the master DLL and slave delay lines. */
		if (t1_is_asic(adapter) && !slow_mode) {
			val = t1_read_reg_4(adapter, A_MC4_STROBE);
			t1_write_reg_4(adapter, A_MC4_STROBE,
				       val & ~F_MASTER_DLL_RESET);

			/* Wait for the master DLL to lock. */
			attempts = 100;
			do {
				DELAY_US(1);
				val = t1_read_reg_4(adapter, A_MC4_STROBE);
			} while (!(val & MC4_DLL_DONE) && --attempts);
			if (!(val & MC4_DLL_DONE)) {
				CH_ERR("%s: MC4 DLL lock failed\n",
				       adapter_name(adapter));
				goto out_fail;
			}
		}
	}

	mc4->nwords = 4 >> width;

	/* Set the FCRAM output drive strength and enable DLLs if needed */
	ext_mode = t1_is_asic(adapter) && !slow_mode ? 0 : 1;
	if (wrreg_wait(adapter, A_MC4_EXT_MODE, ext_mode))
		goto out_fail;

	/* Specify the FCRAM operating parameters */
	if (wrreg_wait(adapter, A_MC4_MODE, 0x32))
		goto out_fail;

	/* Initiate an immediate refresh and wait for the write to complete. */
	val = t1_read_reg_4(adapter, A_MC4_REFRESH);
	if (wrreg_wait(adapter, A_MC4_REFRESH, val & ~F_REFRESH_ENABLE))
		goto out_fail;

	/* 2nd immediate refresh as before */
	if (wrreg_wait(adapter, A_MC4_REFRESH, val & ~F_REFRESH_ENABLE))
		goto out_fail;

	/* Convert to KHz first to avoid 64-bit division. */
	mc4_clock /= 1000;                            /* Hz->KHz */
	mc4_clock = mc4_clock * 7812 + mc4_clock / 2; /* ns */
	mc4_clock /= 1000000;                         /* KHz->MHz, ns->us */

	/* Enable periodic refresh. */
	t1_write_reg_4(adapter, A_MC4_REFRESH,
		       F_REFRESH_ENABLE | V_REFRESH_DIVISOR(mc4_clock));
	(void) t1_read_reg_4(adapter, A_MC4_REFRESH);    /* flush */

	t1_write_reg_4(adapter, A_MC4_ECC_CNTL,
		       F_ECC_GENERATION_ENABLE | F_ECC_CHECK_ENABLE);

	/* Use the BIST engine to clear all of the MC4 memory. */
	t1_write_reg_4(adapter, A_MC4_BIST_ADDR_BEG, 0);
	t1_write_reg_4(adapter, A_MC4_BIST_ADDR_END, (mc4->size << width) - 1);
	t1_write_reg_4(adapter, A_MC4_BIST_DATA, 0);
	t1_write_reg_4(adapter, A_MC4_BIST_OP, V_OP(1) | 0x1f0);
	(void) t1_read_reg_4(adapter, A_MC4_BIST_OP);              /* flush */

	attempts = 100;
	do {
		DELAY_MS(100);
		val = t1_read_reg_4(adapter, A_MC4_BIST_OP);
	} while ((val & F_BUSY) && --attempts);
	if (val & F_BUSY) {
		CH_ERR("%s: MC4 BIST timed out\n", adapter_name(adapter));
		goto out_fail;
	}

	/* Enable normal memory accesses. */
	val = t1_read_reg_4(adapter, A_MC4_CFG);
	t1_write_reg_4(adapter, A_MC4_CFG, val | F_READY);
	val = t1_read_reg_4(adapter, A_MC4_CFG);               /* flush */
	return 0;

 out_fail:
	return -1;
}

struct pemc4 * __devinit t1_mc4_create(adapter_t *adapter)
{
	struct pemc4 *mc4 = t1_os_malloc_wait_zero(sizeof(*mc4));

	if (mc4) {
		mc4->adapter = adapter;
		mc4->size = mc4_calc_size(adapter);
	}
	return mc4;
}

unsigned int t1_mc4_get_size(struct pemc4 *mc4)
{
	return mc4->size;
}

#define MC4_INT_MASK (F_MC4_CORR_ERR | F_MC4_UNCORR_ERR | F_MC4_ADDR_ERR)
#define MC4_INT_FATAL (F_MC4_UNCORR_ERR | F_MC4_ADDR_ERR)

void t1_mc4_intr_enable(struct pemc4 *mc4)
{
	u32 pl_intr;

	if (t1_is_asic(mc4->adapter)) {
		t1_write_reg_4(mc4->adapter, A_MC4_INT_ENABLE, MC4_INT_MASK);

		pl_intr = t1_read_reg_4(mc4->adapter, A_PL_ENABLE);
		t1_write_reg_4(mc4->adapter, A_PL_ENABLE,
			       pl_intr | F_PL_INTR_MC4);
	}
}

void t1_mc4_intr_disable(struct pemc4 *mc4)
{
	u32 pl_intr;

	if (t1_is_asic(mc4->adapter)) {
		t1_write_reg_4(mc4->adapter, A_MC4_INT_ENABLE, 0);

		pl_intr = t1_read_reg_4(mc4->adapter, A_PL_ENABLE);
		t1_write_reg_4(mc4->adapter, A_PL_ENABLE,
			       pl_intr & ~F_PL_INTR_MC4);
	}
}

void t1_mc4_intr_clear(struct pemc4 *mc4)
{
	if (t1_is_asic(mc4->adapter)) {
		t1_write_reg_4(mc4->adapter, A_MC4_INT_CAUSE, 0xffffffff);
		t1_write_reg_4(mc4->adapter, A_PL_CAUSE, F_PL_INTR_MC4);
	}
}

int t1_mc4_intr_handler(struct pemc4 *mc4)
{
	adapter_t *adapter = mc4->adapter;
	u32 cause = t1_read_reg_4(adapter, A_MC4_INT_CAUSE);

	if (cause & F_MC4_CORR_ERR) {
		mc4->intr_cnt.corr_err++;
		CH_WARN("%s: MC4 correctable error at addr 0x%x, "
			"data 0x%x 0x%x 0x%x 0x%x 0x%x\n",
			adapter_name(adapter),
			G_MC4_CE_ADDR(t1_read_reg_4(adapter, A_MC4_CE_ADDR)),
			t1_read_reg_4(adapter, A_MC4_CE_DATA0),
			t1_read_reg_4(adapter, A_MC4_CE_DATA1),
			t1_read_reg_4(adapter, A_MC4_CE_DATA2),
			t1_read_reg_4(adapter, A_MC4_CE_DATA3),
			t1_read_reg_4(adapter, A_MC4_CE_DATA4));
	}

	if (cause & F_MC4_UNCORR_ERR) {
		mc4->intr_cnt.uncorr_err++;
		CH_ALERT("%s: MC4 uncorrectable error at addr 0x%x, "
			 "data 0x%x 0x%x 0x%x 0x%x 0x%x\n",
			 adapter_name(adapter),
			 G_MC4_UE_ADDR(t1_read_reg_4(adapter, A_MC4_UE_ADDR)),
			 t1_read_reg_4(adapter, A_MC4_UE_DATA0),
			 t1_read_reg_4(adapter, A_MC4_UE_DATA1),
			 t1_read_reg_4(adapter, A_MC4_UE_DATA2),
			 t1_read_reg_4(adapter, A_MC4_UE_DATA3),
			 t1_read_reg_4(adapter, A_MC4_UE_DATA4));
	}

	if (cause & F_MC4_ADDR_ERR) {
		mc4->intr_cnt.addr_err++;
		CH_ALERT("%s: MC4 address error\n", adapter_name(adapter));
	}

	if (cause & MC4_INT_FATAL)
		t1_fatal_err(adapter);

	t1_write_reg_4(mc4->adapter, A_MC4_INT_CAUSE, cause);
	return 0;
}

const struct pemc4_intr_counts *t1_mc4_get_intr_counts(struct pemc4 *mc4)
{
	return &mc4->intr_cnt;
}

/*
 * Read n 256-bit words from MC4 starting at word start, using backdoor
 * accesses.
 */
int t1_mc4_bd_read(struct pemc4 *mc4, unsigned int start, unsigned int n,
		   u32 *buf)
{
	adapter_t *adap = mc4->adapter;
	unsigned int size256 = mc4->size / 32, c = 8 / mc4->nwords, i;

	if (start >= size256 || start + n > size256)
		return -EINVAL;

	for (i = 8, start *= 16 * c, n *= c; n; --n, start += 16) {
		int attempts = 10;
		u32 val;

		t1_write_reg_4(adap, A_MC4_BD_ADDR, start);
		t1_write_reg_4(adap, A_MC4_BD_OP, 0);
		val = t1_read_reg_4(adap, A_MC4_BD_OP);
		while ((val & F_BUSY) && attempts--)
			val = t1_read_reg_4(adap, A_MC4_BD_OP);

		if (val & F_BUSY)
			return -EIO;

		buf[--i] = t1_read_reg_4(adap, A_MC4_BD_DATA3);
		if (mc4->nwords >= 2)
			buf[--i] = t1_read_reg_4(adap, A_MC4_BD_DATA2);
		if (mc4->nwords == 4) {
			buf[--i] = t1_read_reg_4(adap, A_MC4_BD_DATA1);
			buf[--i] = t1_read_reg_4(adap, A_MC4_BD_DATA0);
		}
		if (i == 0) {
			i = 8;
			buf += 8;
		}
	}
	return 0;
}