aboutsummaryrefslogtreecommitdiff
path: root/arch/arm/mach-imx/ddrmc-vf610-calibration.c
blob: cd7e95e61d002139e358e68ee57b8217a5759881 (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
334
335
336
337
338
339
340
341
342
343
// SPDX-License-Identifier: GPL-2.0+
/*
 * ddrmc DDR3 calibration code for NXP's VF610
 *
 * Copyright (C) 2018 DENX Software Engineering
 * Lukasz Majewski, DENX Software Engineering, lukma@denx.de
 *
 */
/* #define DEBUG */
#include <common.h>
#include <log.h>
#include <asm/io.h>
#include <asm/arch/imx-regs.h>
#include <linux/bitmap.h>

#include "ddrmc-vf610-calibration.h"

/*
 * Documents:
 *
 * [1] "Vybrid: About DDR leveling feature on DDRMC."
 * https://community.nxp.com/thread/395323
 *
 * [2] VFxxx Controller Reference Manual, Rev. 0, 10/2016
 *
 *
 * NOTE
 * ====
 *
 * NXP recommends setting 'fixed' parameters instead of performing the
 * training at each boot.
 *
 * Use those functions to determine those values on new HW, read the
 * calculated value from registers and add them to the board specific
 * struct ddrmc_cr_setting.
 *
 * SW leveling supported operations - CR93[SW_LVL_MODE]:
 *
 * - 0x0 (b'00) - No leveling
 *
 * - 0x1 (b'01) - WRLVL_DL_X - It is not recommended to perform this tuning
 *                             on HW designs utilizing non-flyback topology
 *                             (Single DDR3 with x16).
 *                             Instead the WRLVL_DL_0/1 fields shall be set
 *                             based on trace length differences from their
 *                             layout.
 *                             Mismatches up to 25% or tCK (clock period) are
 *                             allowed, so the value in the filed doesn’t have
 *                             to be very accurate.
 *
 * - 0x2 (b'10) - RDLVL_DL_0/1 - refers to adjusting the DQS strobe in relation
 *                             to the DQ signals so that the strobe edge is
 *                             centered in the window of valid read data.
 *
 * - 0x3 (b'11) - RDLVL_GTDL_0/1 - refers to the delay the PHY uses to un-gate
 *                             the Read DQS strobe pad from the time that the
 *                             PHY enables the pad to input the strobe signal.
 *
 */
static int ddr_cal_get_first_edge_index(unsigned long *bmap, enum edge e,
					int samples, int start, int max)
{
	int i, ret = -1;

	/*
	 * We look only for the first value (and filter out
	 * some wrong data)
	 */
	switch (e) {
	case RISING_EDGE:
		for (i = start; i <= max - samples; i++) {
			if (test_bit(i, bmap)) {
				if (!test_bit(i - 1, bmap) &&
				    test_bit(i + 1, bmap) &&
				    test_bit(i + 2, bmap) &&
				    test_bit(i + 3, bmap)) {
					return i;
				}
			}
		}
		break;
	case FALLING_EDGE:
		for (i = start; i <= max - samples; i++) {
			if (!test_bit(i, bmap)) {
				if (test_bit(i - 1, bmap) &&
				    test_bit(i - 2, bmap) &&
				    test_bit(i - 3, bmap)) {
					return i;
				}
			}
		}
	}

	return ret;
}

static void bitmap_print(unsigned long *bmap, int max)
{
	int i;

	debug("BITMAP [0x%p]:\n", bmap);
	for (i = 0; i <= max; i++) {
		debug("%d ", test_bit(i, bmap) ? 1 : 0);
		if (i && (i % 32) == (32 - 1))
			debug("\n");
	}
	debug("\n");
}

#define sw_leveling_op_done \
	while (!(readl(&ddrmr->cr[94]) & DDRMC_CR94_SWLVL_OP_DONE))

#define sw_leveling_load_value \
	do { clrsetbits_le32(&ddrmr->cr[93], DDRMC_CR93_SWLVL_LOAD, \
			     DDRMC_CR93_SWLVL_LOAD); } while (0)

#define sw_leveling_start \
	do { clrsetbits_le32(&ddrmr->cr[93], DDRMC_CR93_SWLVL_START, \
			     DDRMC_CR93_SWLVL_START); } while (0)

#define sw_leveling_exit \
	do { clrsetbits_le32(&ddrmr->cr[94], DDRMC_CR94_SWLVL_EXIT, \
			     DDRMC_CR94_SWLVL_EXIT); } while (0)

/*
 * RDLVL_DL calibration:
 *
 * NXP is _NOT_ recommending performing the leveling at each
 * boot. Instead - one shall run this procedure on new boards
 * and then use hardcoded values.
 *
 */
static int ddrmc_cal_dqs_to_dq(struct ddrmr_regs *ddrmr)
{
	DECLARE_BITMAP(rdlvl_rsp, DDRMC_DQS_DQ_MAX_DELAY + 1);
	int rdlvl_dl_0_min = -1, rdlvl_dl_0_max = -1;
	int rdlvl_dl_1_min = -1, rdlvl_dl_1_max = -1;
	int rdlvl_dl_0, rdlvl_dl_1;
	u8 swlvl_rsp;
	u32 tmp;
	int i;

	/* Read defaults */
	u16 rdlvl_dl_0_def =
		(readl(&ddrmr->cr[105]) >> DDRMC_CR105_RDLVL_DL_0_OFF) & 0xFFFF;
	u16 rdlvl_dl_1_def = readl(&ddrmr->cr[110]) & 0xFFFF;

	debug("\nRDLVL: ======================\n");
	debug("RDLVL: DQS to DQ (RDLVL)\n");

	debug("RDLVL: RDLVL_DL_0_DFL:\t 0x%x\n", rdlvl_dl_0_def);
	debug("RDLVL: RDLVL_DL_1_DFL:\t 0x%x\n", rdlvl_dl_1_def);

	/*
	 * Set/Read setup for calibration
	 *
	 * Values necessary for leveling from Vybrid RM [2] - page 1600
	 */
	writel(0x40703030, &ddrmr->cr[144]);
	writel(0x40, &ddrmr->cr[145]);
	writel(0x40, &ddrmr->cr[146]);

	tmp = readl(&ddrmr->cr[144]);
	debug("RDLVL: PHY_RDLVL_RES:\t 0x%x\n", (tmp >> 24) & 0xFF);// set 0x40
	debug("RDLVL: PHY_RDLV_LOAD:\t 0x%x\n", (tmp >> 16) & 0xFF);// set 0x70
	debug("RDLVL: PHY_RDLV_DLL:\t 0x%x\n", (tmp >> 8) & 0xFF); // set 0x30
	debug("RDLVL: PHY_RDLV_EN:\t 0x%x\n", tmp & 0xFF); //set 0x30

	tmp = readl(&ddrmr->cr[145]);
	debug("RDLVL: PHY_RDLV_RR:\t 0x%x\n", tmp & 0x3FF); //set 0x40

	tmp = readl(&ddrmr->cr[146]);
	debug("RDLVL: PHY_RDLV_RESP:\t 0x%x\n", tmp); //set 0x40

	/*
	 * Program/read the leveling edge RDLVL_EDGE = 0
	 *
	 * 0x00 is the correct output on SWLVL_RSP_X
	 * If by any chance 1s are visible -> wrong number read
	 */
	clrbits_le32(&ddrmr->cr[101], DDRMC_CR101_PHY_RDLVL_EDGE);

	tmp = readl(&ddrmr->cr[101]);
	debug("RDLVL: PHY_RDLVL_EDGE:\t 0x%x\n",
	      (tmp >> DDRMC_CR101_PHY_RDLVL_EDGE_OFF) & 0x1); //set 0

	/* Program Leveling mode - CR93[SW_LVL_MODE] to ’b10 */
	clrsetbits_le32(&ddrmr->cr[93], DDRMC_CR93_SW_LVL_MODE(0x3),
			DDRMC_CR93_SW_LVL_MODE(0x2));
	tmp = readl(&ddrmr->cr[93]);
	debug("RDLVL: SW_LVL_MODE:\t 0x%x\n",
	      (tmp >> DDRMC_CR93_SW_LVL_MODE_OFF) & 0x3);

	/* Start procedure - CR93[SWLVL_START] to ’b1 */
	sw_leveling_start;

	/* Poll CR94[SWLVL_OP_DONE] */
	sw_leveling_op_done;

	/*
	 * Program delays for RDLVL_DL_0
	 *
	 * The procedure is to increase the delay values from 0 to 0xFF
	 * and read the response from the DDRMC
	 */
	debug("\nRDLVL: ---> RDLVL_DL_0\n");
	bitmap_zero(rdlvl_rsp, DDRMC_DQS_DQ_MAX_DELAY + 1);

	for (i = 0; i <= DDRMC_DQS_DQ_MAX_DELAY; i++) {
		clrsetbits_le32(&ddrmr->cr[105],
				0xFFFF << DDRMC_CR105_RDLVL_DL_0_OFF,
				i << DDRMC_CR105_RDLVL_DL_0_OFF);

		/* Load values CR93[SWLVL_LOAD] to ’b1 */
		sw_leveling_load_value;

		/* Poll CR94[SWLVL_OP_DONE] */
		sw_leveling_op_done;

		/*
		 * Read Responses - SWLVL_RESP_0
		 *
		 * The 0x00 (correct response when PHY_RDLVL_EDGE = 0)
		 * -> 1 in the bit vector
		 */
		swlvl_rsp = (readl(&ddrmr->cr[94]) >>
			     DDRMC_CR94_SWLVL_RESP_0_OFF) & 0xF;
		if (swlvl_rsp == 0)
			generic_set_bit(i, rdlvl_rsp);
	}

	bitmap_print(rdlvl_rsp, DDRMC_DQS_DQ_MAX_DELAY);

	/*
	 * First test for rising edge 0x0 -> 0x1 in bitmap
	 */
	rdlvl_dl_0_min = ddr_cal_get_first_edge_index(rdlvl_rsp, RISING_EDGE,
						      N_SAMPLES, N_SAMPLES,
						      DDRMC_DQS_DQ_MAX_DELAY);

	/*
	 * Secondly test for falling edge 0x1 -> 0x0 in bitmap
	 */
	rdlvl_dl_0_max = ddr_cal_get_first_edge_index(rdlvl_rsp, FALLING_EDGE,
						      N_SAMPLES, rdlvl_dl_0_min,
						      DDRMC_DQS_DQ_MAX_DELAY);

	debug("RDLVL: DL_0 min: %d [0x%x] DL_0 max: %d [0x%x]\n",
	      rdlvl_dl_0_min, rdlvl_dl_0_min, rdlvl_dl_0_max, rdlvl_dl_0_max);
	rdlvl_dl_0 = (rdlvl_dl_0_max - rdlvl_dl_0_min) / 2;

	if (rdlvl_dl_0_max == -1 || rdlvl_dl_0_min == -1 || rdlvl_dl_0 <= 0) {
		debug("RDLVL: The DQS to DQ delay cannot be found!\n");
		debug("RDLVL: Using default - slice 0: %d!\n", rdlvl_dl_0_def);
		rdlvl_dl_0 = rdlvl_dl_0_def;
	}

	debug("\nRDLVL: ---> RDLVL_DL_1\n");
	bitmap_zero(rdlvl_rsp, DDRMC_DQS_DQ_MAX_DELAY + 1);

	for (i = 0; i <= DDRMC_DQS_DQ_MAX_DELAY; i++) {
		clrsetbits_le32(&ddrmr->cr[110],
				0xFFFF << DDRMC_CR110_RDLVL_DL_1_OFF,
				i << DDRMC_CR110_RDLVL_DL_1_OFF);

		/* Load values CR93[SWLVL_LOAD] to ’b1 */
		sw_leveling_load_value;

		/* Poll CR94[SWLVL_OP_DONE] */
		sw_leveling_op_done;

		/*
		 * Read Responses - SWLVL_RESP_1
		 *
		 * The 0x00 (correct response when PHY_RDLVL_EDGE = 0)
		 * -> 1 in the bit vector
		 */
		swlvl_rsp = (readl(&ddrmr->cr[95]) >>
			     DDRMC_CR95_SWLVL_RESP_1_OFF) & 0xF;
		if (swlvl_rsp == 0)
			generic_set_bit(i, rdlvl_rsp);
	}

	bitmap_print(rdlvl_rsp, DDRMC_DQS_DQ_MAX_DELAY);

	/*
	 * First test for rising edge 0x0 -> 0x1 in bitmap
	 */
	rdlvl_dl_1_min = ddr_cal_get_first_edge_index(rdlvl_rsp, RISING_EDGE,
						      N_SAMPLES, N_SAMPLES,
						      DDRMC_DQS_DQ_MAX_DELAY);

	/*
	 * Secondly test for falling edge 0x1 -> 0x0 in bitmap
	 */
	rdlvl_dl_1_max = ddr_cal_get_first_edge_index(rdlvl_rsp, FALLING_EDGE,
						      N_SAMPLES, rdlvl_dl_1_min,
						      DDRMC_DQS_DQ_MAX_DELAY);

	debug("RDLVL: DL_1 min: %d [0x%x] DL_1 max: %d [0x%x]\n",
	      rdlvl_dl_1_min, rdlvl_dl_1_min, rdlvl_dl_1_max, rdlvl_dl_1_max);
	rdlvl_dl_1 = (rdlvl_dl_1_max - rdlvl_dl_1_min) / 2;

	if (rdlvl_dl_1_max == -1 || rdlvl_dl_1_min == -1 || rdlvl_dl_1 <= 0) {
		debug("RDLVL: The DQS to DQ delay cannot be found!\n");
		debug("RDLVL: Using default - slice 1: %d!\n", rdlvl_dl_1_def);
		rdlvl_dl_1 = rdlvl_dl_1_def;
	}

	debug("RDLVL: CALIBRATED: rdlvl_dl_0: 0x%x\t rdlvl_dl_1: 0x%x\n",
	      rdlvl_dl_0, rdlvl_dl_1);

	/* Write new delay values */
	writel(DDRMC_CR105_RDLVL_DL_0(rdlvl_dl_0), &ddrmr->cr[105]);
	writel(DDRMC_CR110_RDLVL_DL_1(rdlvl_dl_1), &ddrmr->cr[110]);

	sw_leveling_load_value;
	sw_leveling_op_done;

	/* Exit procedure - CR94[SWLVL_EXIT] to ’b1 */
	sw_leveling_exit;

	/* Poll CR94[SWLVL_OP_DONE] */
	sw_leveling_op_done;

	return 0;
}

/*
 * WRLVL_DL calibration:
 *
 * For non-flyback memory architecture - where one have a single DDR3 x16
 * memory - it is NOT necessary to perform "Write Leveling"
 * [3] 'Vybrid DDR3 write leveling' https://community.nxp.com/thread/429362
 *
 */

int ddrmc_calibration(struct ddrmr_regs *ddrmr)
{
	ddrmc_cal_dqs_to_dq(ddrmr);

	return 0;
}