diff options
Diffstat (limited to 'arch/sparc64/kernel/chmc.c')
-rw-r--r-- | arch/sparc64/kernel/chmc.c | 458 |
1 files changed, 458 insertions, 0 deletions
diff --git a/arch/sparc64/kernel/chmc.c b/arch/sparc64/kernel/chmc.c new file mode 100644 index 000000000000..97cf912f0853 --- /dev/null +++ b/arch/sparc64/kernel/chmc.c | |||
@@ -0,0 +1,458 @@ | |||
1 | /* $Id: chmc.c,v 1.4 2002/01/08 16:00:14 davem Exp $ | ||
2 | * memctrlr.c: Driver for UltraSPARC-III memory controller. | ||
3 | * | ||
4 | * Copyright (C) 2001 David S. Miller (davem@redhat.com) | ||
5 | */ | ||
6 | |||
7 | #include <linux/module.h> | ||
8 | #include <linux/kernel.h> | ||
9 | #include <linux/types.h> | ||
10 | #include <linux/slab.h> | ||
11 | #include <linux/list.h> | ||
12 | #include <linux/string.h> | ||
13 | #include <linux/sched.h> | ||
14 | #include <linux/smp.h> | ||
15 | #include <linux/errno.h> | ||
16 | #include <linux/init.h> | ||
17 | #include <asm/spitfire.h> | ||
18 | #include <asm/chmctrl.h> | ||
19 | #include <asm/oplib.h> | ||
20 | #include <asm/io.h> | ||
21 | |||
22 | #define CHMCTRL_NDGRPS 2 | ||
23 | #define CHMCTRL_NDIMMS 4 | ||
24 | |||
25 | #define DIMMS_PER_MC (CHMCTRL_NDGRPS * CHMCTRL_NDIMMS) | ||
26 | |||
27 | /* OBP memory-layout property format. */ | ||
28 | struct obp_map { | ||
29 | unsigned char dimm_map[144]; | ||
30 | unsigned char pin_map[576]; | ||
31 | }; | ||
32 | |||
33 | #define DIMM_LABEL_SZ 8 | ||
34 | |||
35 | struct obp_mem_layout { | ||
36 | /* One max 8-byte string label per DIMM. Usually | ||
37 | * this matches the label on the motherboard where | ||
38 | * that DIMM resides. | ||
39 | */ | ||
40 | char dimm_labels[DIMMS_PER_MC][DIMM_LABEL_SZ]; | ||
41 | |||
42 | /* If symmetric use map[0], else it is | ||
43 | * asymmetric and map[1] should be used. | ||
44 | */ | ||
45 | char symmetric; | ||
46 | |||
47 | struct obp_map map[2]; | ||
48 | }; | ||
49 | |||
50 | #define CHMCTRL_NBANKS 4 | ||
51 | |||
52 | struct bank_info { | ||
53 | struct mctrl_info *mp; | ||
54 | int bank_id; | ||
55 | |||
56 | u64 raw_reg; | ||
57 | int valid; | ||
58 | int uk; | ||
59 | int um; | ||
60 | int lk; | ||
61 | int lm; | ||
62 | int interleave; | ||
63 | unsigned long base; | ||
64 | unsigned long size; | ||
65 | }; | ||
66 | |||
67 | struct mctrl_info { | ||
68 | struct list_head list; | ||
69 | int portid; | ||
70 | int index; | ||
71 | |||
72 | struct obp_mem_layout layout_prop; | ||
73 | int layout_size; | ||
74 | |||
75 | void __iomem *regs; | ||
76 | |||
77 | u64 timing_control1; | ||
78 | u64 timing_control2; | ||
79 | u64 timing_control3; | ||
80 | u64 timing_control4; | ||
81 | u64 memaddr_control; | ||
82 | |||
83 | struct bank_info logical_banks[CHMCTRL_NBANKS]; | ||
84 | }; | ||
85 | |||
86 | static LIST_HEAD(mctrl_list); | ||
87 | |||
88 | /* Does BANK decode PHYS_ADDR? */ | ||
89 | static int bank_match(struct bank_info *bp, unsigned long phys_addr) | ||
90 | { | ||
91 | unsigned long upper_bits = (phys_addr & PA_UPPER_BITS) >> PA_UPPER_BITS_SHIFT; | ||
92 | unsigned long lower_bits = (phys_addr & PA_LOWER_BITS) >> PA_LOWER_BITS_SHIFT; | ||
93 | |||
94 | /* Bank must be enabled to match. */ | ||
95 | if (bp->valid == 0) | ||
96 | return 0; | ||
97 | |||
98 | /* Would BANK match upper bits? */ | ||
99 | upper_bits ^= bp->um; /* What bits are different? */ | ||
100 | upper_bits = ~upper_bits; /* Invert. */ | ||
101 | upper_bits |= bp->uk; /* What bits don't matter for matching? */ | ||
102 | upper_bits = ~upper_bits; /* Invert. */ | ||
103 | |||
104 | if (upper_bits) | ||
105 | return 0; | ||
106 | |||
107 | /* Would BANK match lower bits? */ | ||
108 | lower_bits ^= bp->lm; /* What bits are different? */ | ||
109 | lower_bits = ~lower_bits; /* Invert. */ | ||
110 | lower_bits |= bp->lk; /* What bits don't matter for matching? */ | ||
111 | lower_bits = ~lower_bits; /* Invert. */ | ||
112 | |||
113 | if (lower_bits) | ||
114 | return 0; | ||
115 | |||
116 | /* I always knew you'd be the one. */ | ||
117 | return 1; | ||
118 | } | ||
119 | |||
120 | /* Given PHYS_ADDR, search memory controller banks for a match. */ | ||
121 | static struct bank_info *find_bank(unsigned long phys_addr) | ||
122 | { | ||
123 | struct list_head *mctrl_head = &mctrl_list; | ||
124 | struct list_head *mctrl_entry = mctrl_head->next; | ||
125 | |||
126 | for (;;) { | ||
127 | struct mctrl_info *mp = | ||
128 | list_entry(mctrl_entry, struct mctrl_info, list); | ||
129 | int bank_no; | ||
130 | |||
131 | if (mctrl_entry == mctrl_head) | ||
132 | break; | ||
133 | mctrl_entry = mctrl_entry->next; | ||
134 | |||
135 | for (bank_no = 0; bank_no < CHMCTRL_NBANKS; bank_no++) { | ||
136 | struct bank_info *bp; | ||
137 | |||
138 | bp = &mp->logical_banks[bank_no]; | ||
139 | if (bank_match(bp, phys_addr)) | ||
140 | return bp; | ||
141 | } | ||
142 | } | ||
143 | |||
144 | return NULL; | ||
145 | } | ||
146 | |||
147 | /* This is the main purpose of this driver. */ | ||
148 | #define SYNDROME_MIN -1 | ||
149 | #define SYNDROME_MAX 144 | ||
150 | int chmc_getunumber(int syndrome_code, | ||
151 | unsigned long phys_addr, | ||
152 | char *buf, int buflen) | ||
153 | { | ||
154 | struct bank_info *bp; | ||
155 | struct obp_mem_layout *prop; | ||
156 | int bank_in_controller, first_dimm; | ||
157 | |||
158 | bp = find_bank(phys_addr); | ||
159 | if (bp == NULL || | ||
160 | syndrome_code < SYNDROME_MIN || | ||
161 | syndrome_code > SYNDROME_MAX) { | ||
162 | buf[0] = '?'; | ||
163 | buf[1] = '?'; | ||
164 | buf[2] = '?'; | ||
165 | buf[3] = '\0'; | ||
166 | return 0; | ||
167 | } | ||
168 | |||
169 | prop = &bp->mp->layout_prop; | ||
170 | bank_in_controller = bp->bank_id & (CHMCTRL_NBANKS - 1); | ||
171 | first_dimm = (bank_in_controller & (CHMCTRL_NDGRPS - 1)); | ||
172 | first_dimm *= CHMCTRL_NDIMMS; | ||
173 | |||
174 | if (syndrome_code != SYNDROME_MIN) { | ||
175 | struct obp_map *map; | ||
176 | int qword, where_in_line, where, map_index, map_offset; | ||
177 | unsigned int map_val; | ||
178 | |||
179 | /* Yaay, single bit error so we can figure out | ||
180 | * the exact dimm. | ||
181 | */ | ||
182 | if (prop->symmetric) | ||
183 | map = &prop->map[0]; | ||
184 | else | ||
185 | map = &prop->map[1]; | ||
186 | |||
187 | /* Covert syndrome code into the way the bits are | ||
188 | * positioned on the bus. | ||
189 | */ | ||
190 | if (syndrome_code < 144 - 16) | ||
191 | syndrome_code += 16; | ||
192 | else if (syndrome_code < 144) | ||
193 | syndrome_code -= (144 - 7); | ||
194 | else if (syndrome_code < (144 + 3)) | ||
195 | syndrome_code -= (144 + 3 - 4); | ||
196 | else | ||
197 | syndrome_code -= 144 + 3; | ||
198 | |||
199 | /* All this magic has to do with how a cache line | ||
200 | * comes over the wire on Safari. A 64-bit line | ||
201 | * comes over in 4 quadword cycles, each of which | ||
202 | * transmit ECC/MTAG info as well as the actual | ||
203 | * data. 144 bits per quadword, 576 total. | ||
204 | */ | ||
205 | #define LINE_SIZE 64 | ||
206 | #define LINE_ADDR_MSK (LINE_SIZE - 1) | ||
207 | #define QW_PER_LINE 4 | ||
208 | #define QW_BYTES (LINE_SIZE / QW_PER_LINE) | ||
209 | #define QW_BITS 144 | ||
210 | #define LAST_BIT (576 - 1) | ||
211 | |||
212 | qword = (phys_addr & LINE_ADDR_MSK) / QW_BYTES; | ||
213 | where_in_line = ((3 - qword) * QW_BITS) + syndrome_code; | ||
214 | where = (LAST_BIT - where_in_line); | ||
215 | map_index = where >> 2; | ||
216 | map_offset = where & 0x3; | ||
217 | map_val = map->dimm_map[map_index]; | ||
218 | map_val = ((map_val >> ((3 - map_offset) << 1)) & (2 - 1)); | ||
219 | |||
220 | sprintf(buf, "%s, pin %3d", | ||
221 | prop->dimm_labels[first_dimm + map_val], | ||
222 | map->pin_map[where_in_line]); | ||
223 | } else { | ||
224 | int dimm; | ||
225 | |||
226 | /* Multi-bit error, we just dump out all the | ||
227 | * dimm labels associated with this bank. | ||
228 | */ | ||
229 | for (dimm = 0; dimm < CHMCTRL_NDIMMS; dimm++) { | ||
230 | sprintf(buf, "%s ", | ||
231 | prop->dimm_labels[first_dimm + dimm]); | ||
232 | buf += strlen(buf); | ||
233 | } | ||
234 | } | ||
235 | return 0; | ||
236 | } | ||
237 | |||
238 | /* Accessing the registers is slightly complicated. If you want | ||
239 | * to get at the memory controller which is on the same processor | ||
240 | * the code is executing, you must use special ASI load/store else | ||
241 | * you go through the global mapping. | ||
242 | */ | ||
243 | static u64 read_mcreg(struct mctrl_info *mp, unsigned long offset) | ||
244 | { | ||
245 | unsigned long ret; | ||
246 | int this_cpu = get_cpu(); | ||
247 | |||
248 | if (mp->portid == this_cpu) { | ||
249 | __asm__ __volatile__("ldxa [%1] %2, %0" | ||
250 | : "=r" (ret) | ||
251 | : "r" (offset), "i" (ASI_MCU_CTRL_REG)); | ||
252 | } else { | ||
253 | __asm__ __volatile__("ldxa [%1] %2, %0" | ||
254 | : "=r" (ret) | ||
255 | : "r" (mp->regs + offset), | ||
256 | "i" (ASI_PHYS_BYPASS_EC_E)); | ||
257 | } | ||
258 | put_cpu(); | ||
259 | |||
260 | return ret; | ||
261 | } | ||
262 | |||
263 | #if 0 /* currently unused */ | ||
264 | static void write_mcreg(struct mctrl_info *mp, unsigned long offset, u64 val) | ||
265 | { | ||
266 | if (mp->portid == smp_processor_id()) { | ||
267 | __asm__ __volatile__("stxa %0, [%1] %2" | ||
268 | : : "r" (val), | ||
269 | "r" (offset), "i" (ASI_MCU_CTRL_REG)); | ||
270 | } else { | ||
271 | __asm__ __volatile__("ldxa %0, [%1] %2" | ||
272 | : : "r" (val), | ||
273 | "r" (mp->regs + offset), | ||
274 | "i" (ASI_PHYS_BYPASS_EC_E)); | ||
275 | } | ||
276 | } | ||
277 | #endif | ||
278 | |||
279 | static void interpret_one_decode_reg(struct mctrl_info *mp, int which_bank, u64 val) | ||
280 | { | ||
281 | struct bank_info *p = &mp->logical_banks[which_bank]; | ||
282 | |||
283 | p->mp = mp; | ||
284 | p->bank_id = (CHMCTRL_NBANKS * mp->portid) + which_bank; | ||
285 | p->raw_reg = val; | ||
286 | p->valid = (val & MEM_DECODE_VALID) >> MEM_DECODE_VALID_SHIFT; | ||
287 | p->uk = (val & MEM_DECODE_UK) >> MEM_DECODE_UK_SHIFT; | ||
288 | p->um = (val & MEM_DECODE_UM) >> MEM_DECODE_UM_SHIFT; | ||
289 | p->lk = (val & MEM_DECODE_LK) >> MEM_DECODE_LK_SHIFT; | ||
290 | p->lm = (val & MEM_DECODE_LM) >> MEM_DECODE_LM_SHIFT; | ||
291 | |||
292 | p->base = (p->um); | ||
293 | p->base &= ~(p->uk); | ||
294 | p->base <<= PA_UPPER_BITS_SHIFT; | ||
295 | |||
296 | switch(p->lk) { | ||
297 | case 0xf: | ||
298 | default: | ||
299 | p->interleave = 1; | ||
300 | break; | ||
301 | |||
302 | case 0xe: | ||
303 | p->interleave = 2; | ||
304 | break; | ||
305 | |||
306 | case 0xc: | ||
307 | p->interleave = 4; | ||
308 | break; | ||
309 | |||
310 | case 0x8: | ||
311 | p->interleave = 8; | ||
312 | break; | ||
313 | |||
314 | case 0x0: | ||
315 | p->interleave = 16; | ||
316 | break; | ||
317 | }; | ||
318 | |||
319 | /* UK[10] is reserved, and UK[11] is not set for the SDRAM | ||
320 | * bank size definition. | ||
321 | */ | ||
322 | p->size = (((unsigned long)p->uk & | ||
323 | ((1UL << 10UL) - 1UL)) + 1UL) << PA_UPPER_BITS_SHIFT; | ||
324 | p->size /= p->interleave; | ||
325 | } | ||
326 | |||
327 | static void fetch_decode_regs(struct mctrl_info *mp) | ||
328 | { | ||
329 | if (mp->layout_size == 0) | ||
330 | return; | ||
331 | |||
332 | interpret_one_decode_reg(mp, 0, | ||
333 | read_mcreg(mp, CHMCTRL_DECODE1)); | ||
334 | interpret_one_decode_reg(mp, 1, | ||
335 | read_mcreg(mp, CHMCTRL_DECODE2)); | ||
336 | interpret_one_decode_reg(mp, 2, | ||
337 | read_mcreg(mp, CHMCTRL_DECODE3)); | ||
338 | interpret_one_decode_reg(mp, 3, | ||
339 | read_mcreg(mp, CHMCTRL_DECODE4)); | ||
340 | } | ||
341 | |||
342 | static int init_one_mctrl(int node, int index) | ||
343 | { | ||
344 | struct mctrl_info *mp = kmalloc(sizeof(*mp), GFP_KERNEL); | ||
345 | int portid = prom_getintdefault(node, "portid", -1); | ||
346 | struct linux_prom64_registers p_reg_prop; | ||
347 | int t; | ||
348 | |||
349 | if (!mp) | ||
350 | return -1; | ||
351 | memset(mp, 0, sizeof(*mp)); | ||
352 | if (portid == -1) | ||
353 | goto fail; | ||
354 | |||
355 | mp->portid = portid; | ||
356 | mp->layout_size = prom_getproplen(node, "memory-layout"); | ||
357 | if (mp->layout_size < 0) | ||
358 | mp->layout_size = 0; | ||
359 | if (mp->layout_size > sizeof(mp->layout_prop)) | ||
360 | goto fail; | ||
361 | |||
362 | if (mp->layout_size > 0) | ||
363 | prom_getproperty(node, "memory-layout", | ||
364 | (char *) &mp->layout_prop, | ||
365 | mp->layout_size); | ||
366 | |||
367 | t = prom_getproperty(node, "reg", | ||
368 | (char *) &p_reg_prop, | ||
369 | sizeof(p_reg_prop)); | ||
370 | if (t < 0 || p_reg_prop.reg_size != 0x48) | ||
371 | goto fail; | ||
372 | |||
373 | mp->regs = ioremap(p_reg_prop.phys_addr, p_reg_prop.reg_size); | ||
374 | if (mp->regs == NULL) | ||
375 | goto fail; | ||
376 | |||
377 | if (mp->layout_size != 0UL) { | ||
378 | mp->timing_control1 = read_mcreg(mp, CHMCTRL_TCTRL1); | ||
379 | mp->timing_control2 = read_mcreg(mp, CHMCTRL_TCTRL2); | ||
380 | mp->timing_control3 = read_mcreg(mp, CHMCTRL_TCTRL3); | ||
381 | mp->timing_control4 = read_mcreg(mp, CHMCTRL_TCTRL4); | ||
382 | mp->memaddr_control = read_mcreg(mp, CHMCTRL_MACTRL); | ||
383 | } | ||
384 | |||
385 | fetch_decode_regs(mp); | ||
386 | |||
387 | mp->index = index; | ||
388 | |||
389 | list_add(&mp->list, &mctrl_list); | ||
390 | |||
391 | /* Report the device. */ | ||
392 | printk(KERN_INFO "chmc%d: US3 memory controller at %p [%s]\n", | ||
393 | mp->index, | ||
394 | mp->regs, (mp->layout_size ? "ACTIVE" : "INACTIVE")); | ||
395 | |||
396 | return 0; | ||
397 | |||
398 | fail: | ||
399 | if (mp) { | ||
400 | if (mp->regs != NULL) | ||
401 | iounmap(mp->regs); | ||
402 | kfree(mp); | ||
403 | } | ||
404 | return -1; | ||
405 | } | ||
406 | |||
407 | static int __init probe_for_string(char *name, int index) | ||
408 | { | ||
409 | int node = prom_getchild(prom_root_node); | ||
410 | |||
411 | while ((node = prom_searchsiblings(node, name)) != 0) { | ||
412 | int ret = init_one_mctrl(node, index); | ||
413 | |||
414 | if (!ret) | ||
415 | index++; | ||
416 | |||
417 | node = prom_getsibling(node); | ||
418 | if (!node) | ||
419 | break; | ||
420 | } | ||
421 | |||
422 | return index; | ||
423 | } | ||
424 | |||
425 | static int __init chmc_init(void) | ||
426 | { | ||
427 | int index; | ||
428 | |||
429 | /* This driver is only for cheetah platforms. */ | ||
430 | if (tlb_type != cheetah && tlb_type != cheetah_plus) | ||
431 | return -ENODEV; | ||
432 | |||
433 | index = probe_for_string("memory-controller", 0); | ||
434 | index = probe_for_string("mc-us3", index); | ||
435 | |||
436 | return 0; | ||
437 | } | ||
438 | |||
439 | static void __exit chmc_cleanup(void) | ||
440 | { | ||
441 | struct list_head *head = &mctrl_list; | ||
442 | struct list_head *tmp = head->next; | ||
443 | |||
444 | for (;;) { | ||
445 | struct mctrl_info *p = | ||
446 | list_entry(tmp, struct mctrl_info, list); | ||
447 | if (tmp == head) | ||
448 | break; | ||
449 | tmp = tmp->next; | ||
450 | |||
451 | list_del(&p->list); | ||
452 | iounmap(p->regs); | ||
453 | kfree(p); | ||
454 | } | ||
455 | } | ||
456 | |||
457 | module_init(chmc_init); | ||
458 | module_exit(chmc_cleanup); | ||