]>
Commit | Line | Data |
---|---|---|
f3354b0e | 1 | /* |
2 | * TI OMAP general purpose memory controller emulation. | |
3 | * | |
4 | * Copyright (C) 2007-2009 Nokia Corporation | |
5 | * Original code written by Andrzej Zaborowski <[email protected]> | |
6 | * Enhancements for OMAP3 and NAND support written by Juha Riihimäki | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU General Public License as | |
10 | * published by the Free Software Foundation; either version 2 or | |
11 | * (at your option) any later version of the License. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, | |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 | * GNU General Public License for more details. | |
17 | * | |
18 | * You should have received a copy of the GNU General Public License along | |
19 | * with this program; if not, see <http://www.gnu.org/licenses/>. | |
20 | */ | |
21 | #include "hw.h" | |
22 | #include "flash.h" | |
23 | #include "omap.h" | |
24 | ||
25 | /* General-Purpose Memory Controller */ | |
26 | struct omap_gpmc_s { | |
27 | qemu_irq irq; | |
28 | ||
29 | uint8_t sysconfig; | |
30 | uint16_t irqst; | |
31 | uint16_t irqen; | |
32 | uint16_t timeout; | |
33 | uint16_t config; | |
34 | uint32_t prefconfig[2]; | |
35 | int prefcontrol; | |
36 | int preffifo; | |
37 | int prefcount; | |
38 | struct omap_gpmc_cs_file_s { | |
39 | uint32_t config[7]; | |
40 | target_phys_addr_t base; | |
41 | size_t size; | |
42 | int iomemtype; | |
43 | void (*base_update)(void *opaque, target_phys_addr_t new); | |
44 | void (*unmap)(void *opaque); | |
45 | void *opaque; | |
46 | } cs_file[8]; | |
47 | int ecc_cs; | |
48 | int ecc_ptr; | |
49 | uint32_t ecc_cfg; | |
50 | ECCState ecc[9]; | |
51 | }; | |
52 | ||
53 | static void omap_gpmc_int_update(struct omap_gpmc_s *s) | |
54 | { | |
55 | qemu_set_irq(s->irq, s->irqen & s->irqst); | |
56 | } | |
57 | ||
58 | static void omap_gpmc_cs_map(struct omap_gpmc_cs_file_s *f, int base, int mask) | |
59 | { | |
60 | /* TODO: check for overlapping regions and report access errors */ | |
61 | if ((mask != 0x8 && mask != 0xc && mask != 0xe && mask != 0xf) || | |
62 | (base < 0 || base >= 0x40) || | |
63 | (base & 0x0f & ~mask)) { | |
64 | fprintf(stderr, "%s: wrong cs address mapping/decoding!\n", | |
65 | __FUNCTION__); | |
66 | return; | |
67 | } | |
68 | ||
69 | if (!f->opaque) | |
70 | return; | |
71 | ||
72 | f->base = base << 24; | |
73 | f->size = (0x0fffffff & ~(mask << 24)) + 1; | |
74 | /* TODO: rather than setting the size of the mapping (which should be | |
75 | * constant), the mask should cause wrapping of the address space, so | |
76 | * that the same memory becomes accessible at every <i>size</i> bytes | |
77 | * starting from <i>base</i>. */ | |
78 | if (f->iomemtype) | |
79 | cpu_register_physical_memory(f->base, f->size, f->iomemtype); | |
80 | ||
81 | if (f->base_update) | |
82 | f->base_update(f->opaque, f->base); | |
83 | } | |
84 | ||
85 | static void omap_gpmc_cs_unmap(struct omap_gpmc_cs_file_s *f) | |
86 | { | |
87 | if (f->size) { | |
88 | if (f->unmap) | |
89 | f->unmap(f->opaque); | |
90 | if (f->iomemtype) | |
91 | cpu_register_physical_memory(f->base, f->size, IO_MEM_UNASSIGNED); | |
92 | f->base = 0; | |
93 | f->size = 0; | |
94 | } | |
95 | } | |
96 | ||
97 | void omap_gpmc_reset(struct omap_gpmc_s *s) | |
98 | { | |
99 | int i; | |
100 | ||
101 | s->sysconfig = 0; | |
102 | s->irqst = 0; | |
103 | s->irqen = 0; | |
104 | omap_gpmc_int_update(s); | |
105 | s->timeout = 0; | |
106 | s->config = 0xa00; | |
107 | s->prefconfig[0] = 0x00004000; | |
108 | s->prefconfig[1] = 0x00000000; | |
109 | s->prefcontrol = 0; | |
110 | s->preffifo = 0; | |
111 | s->prefcount = 0; | |
112 | for (i = 0; i < 8; i ++) { | |
113 | if (s->cs_file[i].config[6] & (1 << 6)) /* CSVALID */ | |
114 | omap_gpmc_cs_unmap(s->cs_file + i); | |
115 | s->cs_file[i].config[0] = i ? 1 << 12 : 0; | |
116 | s->cs_file[i].config[1] = 0x101001; | |
117 | s->cs_file[i].config[2] = 0x020201; | |
118 | s->cs_file[i].config[3] = 0x10031003; | |
119 | s->cs_file[i].config[4] = 0x10f1111; | |
120 | s->cs_file[i].config[5] = 0; | |
121 | s->cs_file[i].config[6] = 0xf00 | (i ? 0 : 1 << 6); | |
122 | if (s->cs_file[i].config[6] & (1 << 6)) /* CSVALID */ | |
123 | omap_gpmc_cs_map(&s->cs_file[i], | |
124 | s->cs_file[i].config[6] & 0x1f, /* MASKADDR */ | |
125 | (s->cs_file[i].config[6] >> 8 & 0xf)); /* BASEADDR */ | |
126 | } | |
127 | omap_gpmc_cs_map(s->cs_file, 0, 0xf); | |
128 | s->ecc_cs = 0; | |
129 | s->ecc_ptr = 0; | |
130 | s->ecc_cfg = 0x3fcff000; | |
131 | for (i = 0; i < 9; i ++) | |
132 | ecc_reset(&s->ecc[i]); | |
133 | } | |
134 | ||
135 | static uint32_t omap_gpmc_read(void *opaque, target_phys_addr_t addr) | |
136 | { | |
137 | struct omap_gpmc_s *s = (struct omap_gpmc_s *) opaque; | |
138 | int cs; | |
139 | struct omap_gpmc_cs_file_s *f; | |
140 | ||
141 | switch (addr) { | |
142 | case 0x000: /* GPMC_REVISION */ | |
143 | return 0x20; | |
144 | ||
145 | case 0x010: /* GPMC_SYSCONFIG */ | |
146 | return s->sysconfig; | |
147 | ||
148 | case 0x014: /* GPMC_SYSSTATUS */ | |
149 | return 1; /* RESETDONE */ | |
150 | ||
151 | case 0x018: /* GPMC_IRQSTATUS */ | |
152 | return s->irqst; | |
153 | ||
154 | case 0x01c: /* GPMC_IRQENABLE */ | |
155 | return s->irqen; | |
156 | ||
157 | case 0x040: /* GPMC_TIMEOUT_CONTROL */ | |
158 | return s->timeout; | |
159 | ||
160 | case 0x044: /* GPMC_ERR_ADDRESS */ | |
161 | case 0x048: /* GPMC_ERR_TYPE */ | |
162 | return 0; | |
163 | ||
164 | case 0x050: /* GPMC_CONFIG */ | |
165 | return s->config; | |
166 | ||
167 | case 0x054: /* GPMC_STATUS */ | |
168 | return 0x001; | |
169 | ||
170 | case 0x060 ... 0x1d4: | |
171 | cs = (addr - 0x060) / 0x30; | |
172 | addr -= cs * 0x30; | |
173 | f = s->cs_file + cs; | |
174 | switch (addr) { | |
175 | case 0x60: /* GPMC_CONFIG1 */ | |
176 | return f->config[0]; | |
177 | case 0x64: /* GPMC_CONFIG2 */ | |
178 | return f->config[1]; | |
179 | case 0x68: /* GPMC_CONFIG3 */ | |
180 | return f->config[2]; | |
181 | case 0x6c: /* GPMC_CONFIG4 */ | |
182 | return f->config[3]; | |
183 | case 0x70: /* GPMC_CONFIG5 */ | |
184 | return f->config[4]; | |
185 | case 0x74: /* GPMC_CONFIG6 */ | |
186 | return f->config[5]; | |
187 | case 0x78: /* GPMC_CONFIG7 */ | |
188 | return f->config[6]; | |
189 | case 0x84: /* GPMC_NAND_DATA */ | |
190 | return 0; | |
191 | } | |
192 | break; | |
193 | ||
194 | case 0x1e0: /* GPMC_PREFETCH_CONFIG1 */ | |
195 | return s->prefconfig[0]; | |
196 | case 0x1e4: /* GPMC_PREFETCH_CONFIG2 */ | |
197 | return s->prefconfig[1]; | |
198 | case 0x1ec: /* GPMC_PREFETCH_CONTROL */ | |
199 | return s->prefcontrol; | |
200 | case 0x1f0: /* GPMC_PREFETCH_STATUS */ | |
201 | return (s->preffifo << 24) | | |
202 | ((s->preffifo > | |
203 | ((s->prefconfig[0] >> 8) & 0x7f) ? 1 : 0) << 16) | | |
204 | s->prefcount; | |
205 | ||
206 | case 0x1f4: /* GPMC_ECC_CONFIG */ | |
207 | return s->ecc_cs; | |
208 | case 0x1f8: /* GPMC_ECC_CONTROL */ | |
209 | return s->ecc_ptr; | |
210 | case 0x1fc: /* GPMC_ECC_SIZE_CONFIG */ | |
211 | return s->ecc_cfg; | |
212 | case 0x200 ... 0x220: /* GPMC_ECC_RESULT */ | |
213 | cs = (addr & 0x1f) >> 2; | |
214 | /* TODO: check correctness */ | |
215 | return | |
216 | ((s->ecc[cs].cp & 0x07) << 0) | | |
217 | ((s->ecc[cs].cp & 0x38) << 13) | | |
218 | ((s->ecc[cs].lp[0] & 0x1ff) << 3) | | |
219 | ((s->ecc[cs].lp[1] & 0x1ff) << 19); | |
220 | ||
221 | case 0x230: /* GPMC_TESTMODE_CTRL */ | |
222 | return 0; | |
223 | case 0x234: /* GPMC_PSA_LSB */ | |
224 | case 0x238: /* GPMC_PSA_MSB */ | |
225 | return 0x00000000; | |
226 | } | |
227 | ||
228 | OMAP_BAD_REG(addr); | |
229 | return 0; | |
230 | } | |
231 | ||
232 | static void omap_gpmc_write(void *opaque, target_phys_addr_t addr, | |
233 | uint32_t value) | |
234 | { | |
235 | struct omap_gpmc_s *s = (struct omap_gpmc_s *) opaque; | |
236 | int cs; | |
237 | struct omap_gpmc_cs_file_s *f; | |
238 | ||
239 | switch (addr) { | |
240 | case 0x000: /* GPMC_REVISION */ | |
241 | case 0x014: /* GPMC_SYSSTATUS */ | |
242 | case 0x054: /* GPMC_STATUS */ | |
243 | case 0x1f0: /* GPMC_PREFETCH_STATUS */ | |
244 | case 0x200 ... 0x220: /* GPMC_ECC_RESULT */ | |
245 | case 0x234: /* GPMC_PSA_LSB */ | |
246 | case 0x238: /* GPMC_PSA_MSB */ | |
247 | OMAP_RO_REG(addr); | |
248 | break; | |
249 | ||
250 | case 0x010: /* GPMC_SYSCONFIG */ | |
251 | if ((value >> 3) == 0x3) | |
252 | fprintf(stderr, "%s: bad SDRAM idle mode %i\n", | |
253 | __FUNCTION__, value >> 3); | |
254 | if (value & 2) | |
255 | omap_gpmc_reset(s); | |
256 | s->sysconfig = value & 0x19; | |
257 | break; | |
258 | ||
259 | case 0x018: /* GPMC_IRQSTATUS */ | |
260 | s->irqen = ~value; | |
261 | omap_gpmc_int_update(s); | |
262 | break; | |
263 | ||
264 | case 0x01c: /* GPMC_IRQENABLE */ | |
265 | s->irqen = value & 0xf03; | |
266 | omap_gpmc_int_update(s); | |
267 | break; | |
268 | ||
269 | case 0x040: /* GPMC_TIMEOUT_CONTROL */ | |
270 | s->timeout = value & 0x1ff1; | |
271 | break; | |
272 | ||
273 | case 0x044: /* GPMC_ERR_ADDRESS */ | |
274 | case 0x048: /* GPMC_ERR_TYPE */ | |
275 | break; | |
276 | ||
277 | case 0x050: /* GPMC_CONFIG */ | |
278 | s->config = value & 0xf13; | |
279 | break; | |
280 | ||
281 | case 0x060 ... 0x1d4: | |
282 | cs = (addr - 0x060) / 0x30; | |
283 | addr -= cs * 0x30; | |
284 | f = s->cs_file + cs; | |
285 | switch (addr) { | |
286 | case 0x60: /* GPMC_CONFIG1 */ | |
287 | f->config[0] = value & 0xffef3e13; | |
288 | break; | |
289 | case 0x64: /* GPMC_CONFIG2 */ | |
290 | f->config[1] = value & 0x001f1f8f; | |
291 | break; | |
292 | case 0x68: /* GPMC_CONFIG3 */ | |
293 | f->config[2] = value & 0x001f1f8f; | |
294 | break; | |
295 | case 0x6c: /* GPMC_CONFIG4 */ | |
296 | f->config[3] = value & 0x1f8f1f8f; | |
297 | break; | |
298 | case 0x70: /* GPMC_CONFIG5 */ | |
299 | f->config[4] = value & 0x0f1f1f1f; | |
300 | break; | |
301 | case 0x74: /* GPMC_CONFIG6 */ | |
302 | f->config[5] = value & 0x00000fcf; | |
303 | break; | |
304 | case 0x78: /* GPMC_CONFIG7 */ | |
305 | if ((f->config[6] ^ value) & 0xf7f) { | |
306 | if (f->config[6] & (1 << 6)) /* CSVALID */ | |
307 | omap_gpmc_cs_unmap(f); | |
308 | if (value & (1 << 6)) /* CSVALID */ | |
309 | omap_gpmc_cs_map(f, value & 0x1f, /* MASKADDR */ | |
310 | (value >> 8 & 0xf)); /* BASEADDR */ | |
311 | } | |
312 | f->config[6] = value & 0x00000f7f; | |
313 | break; | |
314 | case 0x7c: /* GPMC_NAND_COMMAND */ | |
315 | case 0x80: /* GPMC_NAND_ADDRESS */ | |
316 | case 0x84: /* GPMC_NAND_DATA */ | |
317 | break; | |
318 | ||
319 | default: | |
320 | goto bad_reg; | |
321 | } | |
322 | break; | |
323 | ||
324 | case 0x1e0: /* GPMC_PREFETCH_CONFIG1 */ | |
325 | s->prefconfig[0] = value & 0x7f8f7fbf; | |
326 | /* TODO: update interrupts, fifos, dmas */ | |
327 | break; | |
328 | ||
329 | case 0x1e4: /* GPMC_PREFETCH_CONFIG2 */ | |
330 | s->prefconfig[1] = value & 0x3fff; | |
331 | break; | |
332 | ||
333 | case 0x1ec: /* GPMC_PREFETCH_CONTROL */ | |
334 | s->prefcontrol = value & 1; | |
335 | if (s->prefcontrol) { | |
336 | if (s->prefconfig[0] & 1) | |
337 | s->preffifo = 0x40; | |
338 | else | |
339 | s->preffifo = 0x00; | |
340 | } | |
341 | /* TODO: start */ | |
342 | break; | |
343 | ||
344 | case 0x1f4: /* GPMC_ECC_CONFIG */ | |
345 | s->ecc_cs = 0x8f; | |
346 | break; | |
347 | case 0x1f8: /* GPMC_ECC_CONTROL */ | |
348 | if (value & (1 << 8)) | |
349 | for (cs = 0; cs < 9; cs ++) | |
350 | ecc_reset(&s->ecc[cs]); | |
351 | s->ecc_ptr = value & 0xf; | |
352 | if (s->ecc_ptr == 0 || s->ecc_ptr > 9) { | |
353 | s->ecc_ptr = 0; | |
354 | s->ecc_cs &= ~1; | |
355 | } | |
356 | break; | |
357 | case 0x1fc: /* GPMC_ECC_SIZE_CONFIG */ | |
358 | s->ecc_cfg = value & 0x3fcff1ff; | |
359 | break; | |
360 | case 0x230: /* GPMC_TESTMODE_CTRL */ | |
361 | if (value & 7) | |
362 | fprintf(stderr, "%s: test mode enable attempt\n", __FUNCTION__); | |
363 | break; | |
364 | ||
365 | default: | |
366 | bad_reg: | |
367 | OMAP_BAD_REG(addr); | |
368 | return; | |
369 | } | |
370 | } | |
371 | ||
372 | static CPUReadMemoryFunc * const omap_gpmc_readfn[] = { | |
373 | omap_badwidth_read32, /* TODO */ | |
374 | omap_badwidth_read32, /* TODO */ | |
375 | omap_gpmc_read, | |
376 | }; | |
377 | ||
378 | static CPUWriteMemoryFunc * const omap_gpmc_writefn[] = { | |
379 | omap_badwidth_write32, /* TODO */ | |
380 | omap_badwidth_write32, /* TODO */ | |
381 | omap_gpmc_write, | |
382 | }; | |
383 | ||
384 | struct omap_gpmc_s *omap_gpmc_init(target_phys_addr_t base, qemu_irq irq) | |
385 | { | |
386 | int iomemtype; | |
387 | struct omap_gpmc_s *s = (struct omap_gpmc_s *) | |
388 | qemu_mallocz(sizeof(struct omap_gpmc_s)); | |
389 | ||
390 | omap_gpmc_reset(s); | |
391 | ||
392 | iomemtype = cpu_register_io_memory(omap_gpmc_readfn, | |
2507c12a | 393 | omap_gpmc_writefn, s, DEVICE_NATIVE_ENDIAN); |
f3354b0e | 394 | cpu_register_physical_memory(base, 0x1000, iomemtype); |
395 | ||
396 | return s; | |
397 | } | |
398 | ||
399 | void omap_gpmc_attach(struct omap_gpmc_s *s, int cs, int iomemtype, | |
400 | void (*base_upd)(void *opaque, target_phys_addr_t new), | |
401 | void (*unmap)(void *opaque), void *opaque) | |
402 | { | |
403 | struct omap_gpmc_cs_file_s *f; | |
404 | ||
405 | if (cs < 0 || cs >= 8) { | |
406 | fprintf(stderr, "%s: bad chip-select %i\n", __FUNCTION__, cs); | |
407 | exit(-1); | |
408 | } | |
409 | f = &s->cs_file[cs]; | |
410 | ||
411 | f->iomemtype = iomemtype; | |
412 | f->base_update = base_upd; | |
413 | f->unmap = unmap; | |
414 | f->opaque = opaque; | |
415 | ||
416 | if (f->config[6] & (1 << 6)) /* CSVALID */ | |
417 | omap_gpmc_cs_map(f, f->config[6] & 0x1f, /* MASKADDR */ | |
418 | (f->config[6] >> 8 & 0xf)); /* BASEADDR */ | |
419 | } |