1 /* SPDX-License-Identifier: GPL-2.0-only */
2
3 #include <console/console.h>
4 #include <device/mmio.h>
5 #include <types.h>
6
7 #include "i945.h"
8 #include "raminit.h"
9
10 /**
11 * sample the strobes signal
12 */
sample_strobes(int channel_offset,struct sys_info * sysinfo)13 static u32 sample_strobes(int channel_offset, struct sys_info *sysinfo)
14 {
15 u32 reg32;
16 uintptr_t addr;
17 int i;
18
19 mchbar_setbits32(C0DRC1 + channel_offset, 1 << 6);
20 mchbar_clrbits32(C0DRC1 + channel_offset, 1 << 6);
21
22 addr = 0;
23
24 if (channel_offset != 0) { /* must be dual channel */
25 if (sysinfo->interleaved == 1)
26 addr |= (1 << 6);
27 else
28 addr = ((u32)mchbar_read8(C0DRB3)) << 25;
29 }
30
31 for (i = 0; i < 28; i++) {
32 read32p(addr);
33 read32p(addr + 0x80);
34 }
35
36 reg32 = mchbar_read32(RCVENMT);
37 if (channel_offset == 0)
38 reg32 = reg32 << 2;
39
40 /**
41 * [19] = 1: all bits are high
42 * [18] = 1: all bits are low
43 * [19:18] = 00: bits are mixed high, low
44 */
45 return reg32;
46 }
47
48 /**
49 * This function sets receive enable coarse and medium timing parameters
50 */
51
set_receive_enable(int channel_offset,u8 medium,u8 coarse)52 static void set_receive_enable(int channel_offset, u8 medium, u8 coarse)
53 {
54 u32 reg32;
55
56 printk(BIOS_SPEW, " %s() medium=0x%x, coarse=0x%x\n", __func__, medium, coarse);
57
58 reg32 = mchbar_read32(C0DRT1 + channel_offset);
59 reg32 &= 0xf0ffffff;
60 reg32 |= ((u32)coarse & 0x0f) << 24;
61 mchbar_write32(C0DRT1 + channel_offset, reg32);
62
63 /* This should never happen: */
64 if (coarse > 0x0f)
65 printk(BIOS_DEBUG, "%s: coarse overflow: 0x%02x.\n", __func__, coarse);
66
67 /* medium control
68 *
69 * 00 - 1/4 clock
70 * 01 - 1/2 clock
71 * 10 - 3/4 clock
72 * 11 - 1 clock
73 */
74
75 reg32 = mchbar_read32(RCVENMT);
76 if (!channel_offset) {
77 /* Channel 0 */
78 reg32 &= ~(3 << 2);
79 reg32 |= medium << 2;
80 } else {
81 /* Channel 1 */
82 reg32 &= ~(3 << 0);
83 reg32 |= medium;
84 }
85 mchbar_write32(RCVENMT, reg32);
86 }
87
normalize(int channel_offset,u8 * mediumcoarse,u8 * fine)88 static int normalize(int channel_offset, u8 *mediumcoarse, u8 *fine)
89 {
90 printk(BIOS_SPEW, " %s()\n", __func__);
91
92 if (*fine < 0x80)
93 return 0;
94
95 *fine -= 0x80;
96 *mediumcoarse += 1;
97
98 if (*mediumcoarse >= 0x40) {
99 printk(BIOS_DEBUG, "Normalize Error\n");
100 return -1;
101 }
102
103 set_receive_enable(channel_offset, *mediumcoarse & 3, *mediumcoarse >> 2);
104
105 mchbar_write8(C0WL0REOST + channel_offset, *fine);
106
107 return 0;
108 }
109
find_preamble(int channel_offset,u8 * mediumcoarse,struct sys_info * sysinfo)110 static int find_preamble(int channel_offset, u8 *mediumcoarse,
111 struct sys_info *sysinfo)
112 {
113 /* find start of the data phase */
114 u32 reg32;
115
116 printk(BIOS_SPEW, " %s()\n", __func__);
117
118 do {
119 if (*mediumcoarse < 4) {
120 printk(BIOS_DEBUG, "No Preamble found.\n");
121 return -1;
122 }
123 *mediumcoarse -= 4;
124
125 set_receive_enable(channel_offset, *mediumcoarse & 3, *mediumcoarse >> 2);
126
127 reg32 = sample_strobes(channel_offset, sysinfo);
128
129 } while (reg32 & (1 << 19));
130
131 if (!(reg32 & (1 << 18))) {
132 printk(BIOS_DEBUG, "No Preamble found (neither high nor low).\n");
133 return -1;
134 }
135
136 return 0;
137 }
138
139 /**
140 * add a quarter clock to the current receive enable settings
141 */
142
add_quarter_clock(int channel_offset,u8 * mediumcoarse,u8 * fine)143 static int add_quarter_clock(int channel_offset, u8 *mediumcoarse, u8 *fine)
144 {
145 printk(BIOS_SPEW, " %s() mediumcoarse=%02x fine=%02x\n", __func__,
146 *mediumcoarse, *fine);
147 if (*fine >= 0x80) {
148 *fine -= 0x80;
149
150 *mediumcoarse += 2;
151 if (*mediumcoarse >= 0x40) {
152 printk(BIOS_DEBUG, "clocks at max.\n");
153 return -1;
154 }
155
156 set_receive_enable(channel_offset, *mediumcoarse & 3, *mediumcoarse >> 2);
157 } else {
158 *fine += 0x80;
159 }
160
161 mchbar_write8(C0WL0REOST + channel_offset, *fine);
162
163 return 0;
164 }
165
find_strobes_low(int channel_offset,u8 * mediumcoarse,u8 * fine,struct sys_info * sysinfo)166 static int find_strobes_low(int channel_offset, u8 *mediumcoarse, u8 *fine,
167 struct sys_info *sysinfo)
168 {
169 u32 rcvenmt;
170
171 printk(BIOS_SPEW, " %s()\n", __func__);
172
173 for (;;) {
174 mchbar_write8(C0WL0REOST + channel_offset, *fine);
175
176 set_receive_enable(channel_offset, *mediumcoarse & 3, *mediumcoarse >> 2);
177
178 rcvenmt = sample_strobes(channel_offset, sysinfo);
179
180 if (((rcvenmt & (1 << 18)) != 0))
181 return 0;
182
183 *fine -= 0x80;
184 if (*fine == 0)
185 continue;
186
187 *mediumcoarse -= 2;
188 if (*mediumcoarse < 0xfe)
189 continue;
190
191 break;
192 }
193
194 printk(BIOS_DEBUG, "Could not find low strobe\n");
195 return 0;
196 }
197
find_strobes_edge(int channel_offset,u8 * mediumcoarse,u8 * fine,struct sys_info * sysinfo)198 static int find_strobes_edge(int channel_offset, u8 *mediumcoarse, u8 *fine,
199 struct sys_info *sysinfo)
200 {
201 int counter;
202 u32 rcvenmt;
203
204 printk(BIOS_SPEW, " %s()\n", __func__);
205
206 counter = 8;
207 set_receive_enable(channel_offset, *mediumcoarse & 3, *mediumcoarse >> 2);
208
209 for (;;) {
210 mchbar_write8(C0WL0REOST + channel_offset, *fine);
211 rcvenmt = sample_strobes(channel_offset, sysinfo);
212
213 if ((rcvenmt & (1 << 19)) == 0) {
214 counter = 8;
215 } else {
216 counter--;
217 if (!counter)
218 break;
219 }
220
221 *fine = *fine + 1;
222 if (*fine < 0xf8) {
223 if (*fine & (1 << 3)) {
224 *fine &= ~(1 << 3);
225 *fine += 0x10;
226 }
227 continue;
228 }
229
230 *fine = 0;
231 *mediumcoarse += 2;
232 if (*mediumcoarse <= 0x40) {
233 set_receive_enable(channel_offset, *mediumcoarse & 3,
234 *mediumcoarse >> 2);
235 continue;
236 }
237
238 printk(BIOS_DEBUG, "Could not find rising edge.\n");
239 return -1;
240 }
241
242 *fine -= 7;
243 if (*fine >= 0xf9) {
244 *mediumcoarse -= 2;
245 set_receive_enable(channel_offset, *mediumcoarse & 3, *mediumcoarse >> 2);
246 }
247
248 *fine &= ~(1 << 3);
249 mchbar_write8(C0WL0REOST + channel_offset, *fine);
250
251 return 0;
252 }
253
254 /**
255 * Here we use a trick. The RCVEN channel 1 registers are all at an
256 * offset of 0x80 to the channel 0 registers. We don't want to waste
257 * a lot of if ()s so let's just pass 0 or 0x80 for the channel offset.
258 */
259
receive_enable_autoconfig(int channel_offset,struct sys_info * sysinfo)260 static int receive_enable_autoconfig(int channel_offset, struct sys_info *sysinfo)
261 {
262 u8 mediumcoarse;
263 u8 fine;
264
265 printk(BIOS_SPEW, "%s() for channel %d\n", __func__, channel_offset ? 1 : 0);
266
267 /* Set initial values */
268 mediumcoarse = (sysinfo->cas << 2) | 3;
269 fine = 0;
270
271 if (find_strobes_low(channel_offset, &mediumcoarse, &fine, sysinfo))
272 return -1;
273
274 if (find_strobes_edge(channel_offset, &mediumcoarse, &fine, sysinfo))
275 return -1;
276
277 if (add_quarter_clock(channel_offset, &mediumcoarse, &fine))
278 return -1;
279
280 if (find_preamble(channel_offset, &mediumcoarse, sysinfo))
281 return -1;
282
283 if (add_quarter_clock(channel_offset, &mediumcoarse, &fine))
284 return -1;
285
286 if (normalize(channel_offset, &mediumcoarse, &fine))
287 return -1;
288
289 /* This is a debug check to see if the rcven code is fully working.
290 * It can be removed when the output message is not printed anymore
291 */
292 if (mchbar_read8(C0WL0REOST + channel_offset) == 0)
293 printk(BIOS_DEBUG, "Weird. No C%sWL0REOST\n", channel_offset ? "1" : "0");
294
295 return 0;
296 }
297
receive_enable_adjust(struct sys_info * sysinfo)298 void receive_enable_adjust(struct sys_info *sysinfo)
299 {
300 /* Is channel 0 populated? */
301 if (sysinfo->dimm[0] != SYSINFO_DIMM_NOT_POPULATED
302 || sysinfo->dimm[1] != SYSINFO_DIMM_NOT_POPULATED)
303 if (receive_enable_autoconfig(0, sysinfo))
304 return;
305
306 /* Is channel 1 populated? */
307 if (sysinfo->dimm[2] != SYSINFO_DIMM_NOT_POPULATED
308 || sysinfo->dimm[3] != SYSINFO_DIMM_NOT_POPULATED)
309 if (receive_enable_autoconfig(0x80, sysinfo))
310 return;
311 }
312