]>
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 <andrew@openedhand.com> | |
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" | |
64066a8f AK |
24 | #include "memory.h" |
25 | #include "exec-memory.h" | |
f3354b0e | 26 | |
27 | /* General-Purpose Memory Controller */ | |
28 | struct omap_gpmc_s { | |
29 | qemu_irq irq; | |
64066a8f | 30 | MemoryRegion iomem; |
f3354b0e | 31 | |
32 | uint8_t sysconfig; | |
33 | uint16_t irqst; | |
34 | uint16_t irqen; | |
35 | uint16_t timeout; | |
36 | uint16_t config; | |
37 | uint32_t prefconfig[2]; | |
38 | int prefcontrol; | |
39 | int preffifo; | |
40 | int prefcount; | |
41 | struct omap_gpmc_cs_file_s { | |
42 | uint32_t config[7]; | |
64066a8f AK |
43 | MemoryRegion *iomem; |
44 | MemoryRegion container; | |
f3354b0e | 45 | } cs_file[8]; |
46 | int ecc_cs; | |
47 | int ecc_ptr; | |
48 | uint32_t ecc_cfg; | |
49 | ECCState ecc[9]; | |
50 | }; | |
51 | ||
52 | static void omap_gpmc_int_update(struct omap_gpmc_s *s) | |
53 | { | |
54 | qemu_set_irq(s->irq, s->irqen & s->irqst); | |
55 | } | |
56 | ||
57 | static void omap_gpmc_cs_map(struct omap_gpmc_cs_file_s *f, int base, int mask) | |
58 | { | |
07bc2f80 PM |
59 | uint32_t size; |
60 | ||
61 | if (!f->iomem) { | |
62 | return; | |
63 | } | |
64 | ||
f3354b0e | 65 | /* TODO: check for overlapping regions and report access errors */ |
66 | if ((mask != 0x8 && mask != 0xc && mask != 0xe && mask != 0xf) || | |
67 | (base < 0 || base >= 0x40) || | |
68 | (base & 0x0f & ~mask)) { | |
69 | fprintf(stderr, "%s: wrong cs address mapping/decoding!\n", | |
70 | __FUNCTION__); | |
71 | return; | |
72 | } | |
73 | ||
07bc2f80 PM |
74 | base <<= 24; |
75 | size = (0x0fffffff & ~(mask << 24)) + 1; | |
f3354b0e | 76 | /* TODO: rather than setting the size of the mapping (which should be |
77 | * constant), the mask should cause wrapping of the address space, so | |
78 | * that the same memory becomes accessible at every <i>size</i> bytes | |
79 | * starting from <i>base</i>. */ | |
07bc2f80 PM |
80 | memory_region_init(&f->container, "omap-gpmc-file", size); |
81 | memory_region_add_subregion(&f->container, 0, f->iomem); | |
82 | memory_region_add_subregion(get_system_memory(), base, | |
83 | &f->container); | |
f3354b0e | 84 | } |
85 | ||
86 | static void omap_gpmc_cs_unmap(struct omap_gpmc_cs_file_s *f) | |
87 | { | |
07bc2f80 PM |
88 | if (!f->iomem) { |
89 | return; | |
f3354b0e | 90 | } |
07bc2f80 PM |
91 | |
92 | memory_region_del_subregion(get_system_memory(), &f->container); | |
93 | memory_region_del_subregion(&f->container, f->iomem); | |
94 | memory_region_destroy(&f->container); | |
f3354b0e | 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 | } | |
f3354b0e | 127 | s->ecc_cs = 0; |
128 | s->ecc_ptr = 0; | |
129 | s->ecc_cfg = 0x3fcff000; | |
130 | for (i = 0; i < 9; i ++) | |
131 | ecc_reset(&s->ecc[i]); | |
132 | } | |
133 | ||
64066a8f AK |
134 | static uint64_t omap_gpmc_read(void *opaque, target_phys_addr_t addr, |
135 | unsigned size) | |
f3354b0e | 136 | { |
137 | struct omap_gpmc_s *s = (struct omap_gpmc_s *) opaque; | |
138 | int cs; | |
139 | struct omap_gpmc_cs_file_s *f; | |
140 | ||
64066a8f AK |
141 | if (size != 4) { |
142 | return omap_badwidth_read32(opaque, addr); | |
143 | } | |
144 | ||
f3354b0e | 145 | switch (addr) { |
146 | case 0x000: /* GPMC_REVISION */ | |
147 | return 0x20; | |
148 | ||
149 | case 0x010: /* GPMC_SYSCONFIG */ | |
150 | return s->sysconfig; | |
151 | ||
152 | case 0x014: /* GPMC_SYSSTATUS */ | |
153 | return 1; /* RESETDONE */ | |
154 | ||
155 | case 0x018: /* GPMC_IRQSTATUS */ | |
156 | return s->irqst; | |
157 | ||
158 | case 0x01c: /* GPMC_IRQENABLE */ | |
159 | return s->irqen; | |
160 | ||
161 | case 0x040: /* GPMC_TIMEOUT_CONTROL */ | |
162 | return s->timeout; | |
163 | ||
164 | case 0x044: /* GPMC_ERR_ADDRESS */ | |
165 | case 0x048: /* GPMC_ERR_TYPE */ | |
166 | return 0; | |
167 | ||
168 | case 0x050: /* GPMC_CONFIG */ | |
169 | return s->config; | |
170 | ||
171 | case 0x054: /* GPMC_STATUS */ | |
172 | return 0x001; | |
173 | ||
174 | case 0x060 ... 0x1d4: | |
175 | cs = (addr - 0x060) / 0x30; | |
176 | addr -= cs * 0x30; | |
177 | f = s->cs_file + cs; | |
178 | switch (addr) { | |
179 | case 0x60: /* GPMC_CONFIG1 */ | |
180 | return f->config[0]; | |
181 | case 0x64: /* GPMC_CONFIG2 */ | |
182 | return f->config[1]; | |
183 | case 0x68: /* GPMC_CONFIG3 */ | |
184 | return f->config[2]; | |
185 | case 0x6c: /* GPMC_CONFIG4 */ | |
186 | return f->config[3]; | |
187 | case 0x70: /* GPMC_CONFIG5 */ | |
188 | return f->config[4]; | |
189 | case 0x74: /* GPMC_CONFIG6 */ | |
190 | return f->config[5]; | |
191 | case 0x78: /* GPMC_CONFIG7 */ | |
192 | return f->config[6]; | |
193 | case 0x84: /* GPMC_NAND_DATA */ | |
194 | return 0; | |
195 | } | |
196 | break; | |
197 | ||
198 | case 0x1e0: /* GPMC_PREFETCH_CONFIG1 */ | |
199 | return s->prefconfig[0]; | |
200 | case 0x1e4: /* GPMC_PREFETCH_CONFIG2 */ | |
201 | return s->prefconfig[1]; | |
202 | case 0x1ec: /* GPMC_PREFETCH_CONTROL */ | |
203 | return s->prefcontrol; | |
204 | case 0x1f0: /* GPMC_PREFETCH_STATUS */ | |
205 | return (s->preffifo << 24) | | |
206 | ((s->preffifo > | |
207 | ((s->prefconfig[0] >> 8) & 0x7f) ? 1 : 0) << 16) | | |
208 | s->prefcount; | |
209 | ||
210 | case 0x1f4: /* GPMC_ECC_CONFIG */ | |
211 | return s->ecc_cs; | |
212 | case 0x1f8: /* GPMC_ECC_CONTROL */ | |
213 | return s->ecc_ptr; | |
214 | case 0x1fc: /* GPMC_ECC_SIZE_CONFIG */ | |
215 | return s->ecc_cfg; | |
216 | case 0x200 ... 0x220: /* GPMC_ECC_RESULT */ | |
217 | cs = (addr & 0x1f) >> 2; | |
218 | /* TODO: check correctness */ | |
219 | return | |
220 | ((s->ecc[cs].cp & 0x07) << 0) | | |
221 | ((s->ecc[cs].cp & 0x38) << 13) | | |
222 | ((s->ecc[cs].lp[0] & 0x1ff) << 3) | | |
223 | ((s->ecc[cs].lp[1] & 0x1ff) << 19); | |
224 | ||
225 | case 0x230: /* GPMC_TESTMODE_CTRL */ | |
226 | return 0; | |
227 | case 0x234: /* GPMC_PSA_LSB */ | |
228 | case 0x238: /* GPMC_PSA_MSB */ | |
229 | return 0x00000000; | |
230 | } | |
231 | ||
232 | OMAP_BAD_REG(addr); | |
233 | return 0; | |
234 | } | |
235 | ||
236 | static void omap_gpmc_write(void *opaque, target_phys_addr_t addr, | |
64066a8f | 237 | uint64_t value, unsigned size) |
f3354b0e | 238 | { |
239 | struct omap_gpmc_s *s = (struct omap_gpmc_s *) opaque; | |
240 | int cs; | |
241 | struct omap_gpmc_cs_file_s *f; | |
242 | ||
64066a8f AK |
243 | if (size != 4) { |
244 | return omap_badwidth_write32(opaque, addr, value); | |
245 | } | |
246 | ||
f3354b0e | 247 | switch (addr) { |
248 | case 0x000: /* GPMC_REVISION */ | |
249 | case 0x014: /* GPMC_SYSSTATUS */ | |
250 | case 0x054: /* GPMC_STATUS */ | |
251 | case 0x1f0: /* GPMC_PREFETCH_STATUS */ | |
252 | case 0x200 ... 0x220: /* GPMC_ECC_RESULT */ | |
253 | case 0x234: /* GPMC_PSA_LSB */ | |
254 | case 0x238: /* GPMC_PSA_MSB */ | |
255 | OMAP_RO_REG(addr); | |
256 | break; | |
257 | ||
258 | case 0x010: /* GPMC_SYSCONFIG */ | |
259 | if ((value >> 3) == 0x3) | |
64066a8f | 260 | fprintf(stderr, "%s: bad SDRAM idle mode %"PRIi64"\n", |
f3354b0e | 261 | __FUNCTION__, value >> 3); |
262 | if (value & 2) | |
263 | omap_gpmc_reset(s); | |
264 | s->sysconfig = value & 0x19; | |
265 | break; | |
266 | ||
267 | case 0x018: /* GPMC_IRQSTATUS */ | |
268 | s->irqen = ~value; | |
269 | omap_gpmc_int_update(s); | |
270 | break; | |
271 | ||
272 | case 0x01c: /* GPMC_IRQENABLE */ | |
273 | s->irqen = value & 0xf03; | |
274 | omap_gpmc_int_update(s); | |
275 | break; | |
276 | ||
277 | case 0x040: /* GPMC_TIMEOUT_CONTROL */ | |
278 | s->timeout = value & 0x1ff1; | |
279 | break; | |
280 | ||
281 | case 0x044: /* GPMC_ERR_ADDRESS */ | |
282 | case 0x048: /* GPMC_ERR_TYPE */ | |
283 | break; | |
284 | ||
285 | case 0x050: /* GPMC_CONFIG */ | |
286 | s->config = value & 0xf13; | |
287 | break; | |
288 | ||
289 | case 0x060 ... 0x1d4: | |
290 | cs = (addr - 0x060) / 0x30; | |
291 | addr -= cs * 0x30; | |
292 | f = s->cs_file + cs; | |
293 | switch (addr) { | |
294 | case 0x60: /* GPMC_CONFIG1 */ | |
295 | f->config[0] = value & 0xffef3e13; | |
296 | break; | |
297 | case 0x64: /* GPMC_CONFIG2 */ | |
298 | f->config[1] = value & 0x001f1f8f; | |
299 | break; | |
300 | case 0x68: /* GPMC_CONFIG3 */ | |
301 | f->config[2] = value & 0x001f1f8f; | |
302 | break; | |
303 | case 0x6c: /* GPMC_CONFIG4 */ | |
304 | f->config[3] = value & 0x1f8f1f8f; | |
305 | break; | |
306 | case 0x70: /* GPMC_CONFIG5 */ | |
307 | f->config[4] = value & 0x0f1f1f1f; | |
308 | break; | |
309 | case 0x74: /* GPMC_CONFIG6 */ | |
310 | f->config[5] = value & 0x00000fcf; | |
311 | break; | |
312 | case 0x78: /* GPMC_CONFIG7 */ | |
313 | if ((f->config[6] ^ value) & 0xf7f) { | |
314 | if (f->config[6] & (1 << 6)) /* CSVALID */ | |
315 | omap_gpmc_cs_unmap(f); | |
316 | if (value & (1 << 6)) /* CSVALID */ | |
317 | omap_gpmc_cs_map(f, value & 0x1f, /* MASKADDR */ | |
318 | (value >> 8 & 0xf)); /* BASEADDR */ | |
319 | } | |
320 | f->config[6] = value & 0x00000f7f; | |
321 | break; | |
322 | case 0x7c: /* GPMC_NAND_COMMAND */ | |
323 | case 0x80: /* GPMC_NAND_ADDRESS */ | |
324 | case 0x84: /* GPMC_NAND_DATA */ | |
325 | break; | |
326 | ||
327 | default: | |
328 | goto bad_reg; | |
329 | } | |
330 | break; | |
331 | ||
332 | case 0x1e0: /* GPMC_PREFETCH_CONFIG1 */ | |
333 | s->prefconfig[0] = value & 0x7f8f7fbf; | |
334 | /* TODO: update interrupts, fifos, dmas */ | |
335 | break; | |
336 | ||
337 | case 0x1e4: /* GPMC_PREFETCH_CONFIG2 */ | |
338 | s->prefconfig[1] = value & 0x3fff; | |
339 | break; | |
340 | ||
341 | case 0x1ec: /* GPMC_PREFETCH_CONTROL */ | |
342 | s->prefcontrol = value & 1; | |
343 | if (s->prefcontrol) { | |
344 | if (s->prefconfig[0] & 1) | |
345 | s->preffifo = 0x40; | |
346 | else | |
347 | s->preffifo = 0x00; | |
348 | } | |
349 | /* TODO: start */ | |
350 | break; | |
351 | ||
352 | case 0x1f4: /* GPMC_ECC_CONFIG */ | |
353 | s->ecc_cs = 0x8f; | |
354 | break; | |
355 | case 0x1f8: /* GPMC_ECC_CONTROL */ | |
356 | if (value & (1 << 8)) | |
357 | for (cs = 0; cs < 9; cs ++) | |
358 | ecc_reset(&s->ecc[cs]); | |
359 | s->ecc_ptr = value & 0xf; | |
360 | if (s->ecc_ptr == 0 || s->ecc_ptr > 9) { | |
361 | s->ecc_ptr = 0; | |
362 | s->ecc_cs &= ~1; | |
363 | } | |
364 | break; | |
365 | case 0x1fc: /* GPMC_ECC_SIZE_CONFIG */ | |
366 | s->ecc_cfg = value & 0x3fcff1ff; | |
367 | break; | |
368 | case 0x230: /* GPMC_TESTMODE_CTRL */ | |
369 | if (value & 7) | |
370 | fprintf(stderr, "%s: test mode enable attempt\n", __FUNCTION__); | |
371 | break; | |
372 | ||
373 | default: | |
374 | bad_reg: | |
375 | OMAP_BAD_REG(addr); | |
376 | return; | |
377 | } | |
378 | } | |
379 | ||
64066a8f AK |
380 | static const MemoryRegionOps omap_gpmc_ops = { |
381 | .read = omap_gpmc_read, | |
382 | .write = omap_gpmc_write, | |
383 | .endianness = DEVICE_NATIVE_ENDIAN, | |
f3354b0e | 384 | }; |
385 | ||
386 | struct omap_gpmc_s *omap_gpmc_init(target_phys_addr_t base, qemu_irq irq) | |
387 | { | |
f3354b0e | 388 | struct omap_gpmc_s *s = (struct omap_gpmc_s *) |
7267c094 | 389 | g_malloc0(sizeof(struct omap_gpmc_s)); |
f3354b0e | 390 | |
64066a8f AK |
391 | memory_region_init_io(&s->iomem, &omap_gpmc_ops, s, "omap-gpmc", 0x1000); |
392 | memory_region_add_subregion(get_system_memory(), base, &s->iomem); | |
f3354b0e | 393 | |
07bc2f80 PM |
394 | omap_gpmc_reset(s); |
395 | ||
f3354b0e | 396 | return s; |
397 | } | |
398 | ||
07bc2f80 | 399 | void omap_gpmc_attach(struct omap_gpmc_s *s, int cs, MemoryRegion *iomem) |
f3354b0e | 400 | { |
401 | struct omap_gpmc_cs_file_s *f; | |
07bc2f80 | 402 | assert(iomem); |
f3354b0e | 403 | |
404 | if (cs < 0 || cs >= 8) { | |
405 | fprintf(stderr, "%s: bad chip-select %i\n", __FUNCTION__, cs); | |
406 | exit(-1); | |
407 | } | |
408 | f = &s->cs_file[cs]; | |
409 | ||
64066a8f | 410 | f->iomem = iomem; |
f3354b0e | 411 | |
412 | if (f->config[6] & (1 << 6)) /* CSVALID */ | |
413 | omap_gpmc_cs_map(f, f->config[6] & 0x1f, /* MASKADDR */ | |
414 | (f->config[6] >> 8 & 0xf)); /* BASEADDR */ | |
415 | } |