| 1 | /* Blackfin External Bus Interface Unit (EBIU) Asynchronous Memory Controller |
| 2 | (AMC) model. |
| 3 | |
| 4 | Copyright (C) 2010-2011 Free Software Foundation, Inc. |
| 5 | Contributed by Analog Devices, Inc. |
| 6 | |
| 7 | This file is part of simulators. |
| 8 | |
| 9 | This program is free software; you can redistribute it and/or modify |
| 10 | it under the terms of the GNU General Public License as published by |
| 11 | the Free Software Foundation; either version 3 of the License, or |
| 12 | (at your option) any later version. |
| 13 | |
| 14 | This program is distributed in the hope that it will be useful, |
| 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 17 | GNU General Public License for more details. |
| 18 | |
| 19 | You should have received a copy of the GNU General Public License |
| 20 | along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
| 21 | |
| 22 | #include "config.h" |
| 23 | |
| 24 | #include "sim-main.h" |
| 25 | #include "devices.h" |
| 26 | #include "dv-bfin_ebiu_amc.h" |
| 27 | |
| 28 | struct bfin_ebiu_amc |
| 29 | { |
| 30 | bu32 base; |
| 31 | int type; |
| 32 | bu32 bank_size; |
| 33 | unsigned (*io_write) (struct hw *, const void *, int, address_word, |
| 34 | unsigned, struct bfin_ebiu_amc *, bu32, bu32); |
| 35 | unsigned (*io_read) (struct hw *, void *, int, address_word, unsigned, |
| 36 | struct bfin_ebiu_amc *, bu32, void *, bu16 *, bu32 *); |
| 37 | struct hw *slaves[4]; |
| 38 | |
| 39 | /* Order after here is important -- matches hardware MMR layout. */ |
| 40 | bu16 BFIN_MMR_16(amgctl); |
| 41 | union { |
| 42 | struct { |
| 43 | bu32 ambctl0, ambctl1; |
| 44 | bu32 _pad0[5]; |
| 45 | bu16 BFIN_MMR_16(mode); |
| 46 | bu16 BFIN_MMR_16(fctl); |
| 47 | } bf50x; |
| 48 | struct { |
| 49 | bu32 ambctl0, ambctl1; |
| 50 | } bf53x; |
| 51 | struct { |
| 52 | bu32 ambctl0, ambctl1; |
| 53 | bu32 mbsctl, arbstat, mode, fctl; |
| 54 | } bf54x; |
| 55 | }; |
| 56 | }; |
| 57 | #define mmr_base() offsetof(struct bfin_ebiu_amc, amgctl) |
| 58 | #define mmr_offset(mmr) (offsetof(struct bfin_ebiu_amc, mmr) - mmr_base()) |
| 59 | #define mmr_idx(mmr) (mmr_offset (mmr) / 4) |
| 60 | |
| 61 | static const char * const bf50x_mmr_names[] = |
| 62 | { |
| 63 | "EBIU_AMGCTL", "EBIU_AMBCTL0", "EBIU_AMBCTL1", |
| 64 | [mmr_idx (bf50x.mode)] = "EBIU_MODE", "EBIU_FCTL", |
| 65 | }; |
| 66 | static const char * const bf53x_mmr_names[] = |
| 67 | { |
| 68 | "EBIU_AMGCTL", "EBIU_AMBCTL0", "EBIU_AMBCTL1", |
| 69 | }; |
| 70 | static const char * const bf54x_mmr_names[] = |
| 71 | { |
| 72 | "EBIU_AMGCTL", "EBIU_AMBCTL0", "EBIU_AMBCTL1", |
| 73 | "EBIU_MSBCTL", "EBIU_ARBSTAT", "EBIU_MODE", "EBIU_FCTL", |
| 74 | }; |
| 75 | static const char * const *mmr_names; |
| 76 | #define mmr_name(off) (mmr_names[(off) / 4] ? : "<INV>") |
| 77 | |
| 78 | static void |
| 79 | bfin_ebiu_amc_write_amgctl (struct hw *me, struct bfin_ebiu_amc *amc, |
| 80 | bu16 amgctl) |
| 81 | { |
| 82 | bu32 amben_old, amben, addr, i; |
| 83 | |
| 84 | amben_old = MIN ((amc->amgctl >> 1) & 0x7, 4); |
| 85 | amben = MIN ((amgctl >> 1) & 0x7, 4); |
| 86 | |
| 87 | HW_TRACE ((me, "reattaching banks: AMGCTL 0x%04x[%u] -> 0x%04x[%u]", |
| 88 | amc->amgctl, amben_old, amgctl, amben)); |
| 89 | |
| 90 | for (i = 0; i < 4; ++i) |
| 91 | { |
| 92 | addr = BFIN_EBIU_AMC_BASE + i * amc->bank_size; |
| 93 | |
| 94 | if (i < amben_old) |
| 95 | { |
| 96 | HW_TRACE ((me, "detaching bank %u (%#x base)", i, addr)); |
| 97 | sim_core_detach (hw_system (me), NULL, 0, 0, addr); |
| 98 | } |
| 99 | |
| 100 | if (i < amben) |
| 101 | { |
| 102 | struct hw *slave = amc->slaves[i]; |
| 103 | |
| 104 | HW_TRACE ((me, "attaching bank %u (%#x base) to %s", i, addr, |
| 105 | slave ? hw_path (slave) : "<floating pins>")); |
| 106 | |
| 107 | sim_core_attach (hw_system (me), NULL, 0, access_read_write_exec, |
| 108 | 0, addr, amc->bank_size, 0, slave, NULL); |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | amc->amgctl = amgctl; |
| 113 | } |
| 114 | |
| 115 | static unsigned |
| 116 | bf50x_ebiu_amc_io_write_buffer (struct hw *me, const void *source, int space, |
| 117 | address_word addr, unsigned nr_bytes, |
| 118 | struct bfin_ebiu_amc *amc, bu32 mmr_off, |
| 119 | bu32 value) |
| 120 | { |
| 121 | switch (mmr_off) |
| 122 | { |
| 123 | case mmr_offset(amgctl): |
| 124 | dv_bfin_mmr_require_16 (me, addr, nr_bytes, true); |
| 125 | bfin_ebiu_amc_write_amgctl (me, amc, value); |
| 126 | break; |
| 127 | case mmr_offset(bf50x.ambctl0): |
| 128 | amc->bf50x.ambctl0 = value; |
| 129 | break; |
| 130 | case mmr_offset(bf50x.ambctl1): |
| 131 | amc->bf50x.ambctl1 = value; |
| 132 | break; |
| 133 | case mmr_offset(bf50x.mode): |
| 134 | /* XXX: implement this. */ |
| 135 | dv_bfin_mmr_require_16 (me, addr, nr_bytes, true); |
| 136 | break; |
| 137 | case mmr_offset(bf50x.fctl): |
| 138 | /* XXX: implement this. */ |
| 139 | dv_bfin_mmr_require_16 (me, addr, nr_bytes, true); |
| 140 | break; |
| 141 | default: |
| 142 | dv_bfin_mmr_invalid (me, addr, nr_bytes, true); |
| 143 | break; |
| 144 | } |
| 145 | |
| 146 | return nr_bytes; |
| 147 | } |
| 148 | |
| 149 | static unsigned |
| 150 | bf53x_ebiu_amc_io_write_buffer (struct hw *me, const void *source, int space, |
| 151 | address_word addr, unsigned nr_bytes, |
| 152 | struct bfin_ebiu_amc *amc, bu32 mmr_off, |
| 153 | bu32 value) |
| 154 | { |
| 155 | switch (mmr_off) |
| 156 | { |
| 157 | case mmr_offset(amgctl): |
| 158 | dv_bfin_mmr_require_16 (me, addr, nr_bytes, true); |
| 159 | bfin_ebiu_amc_write_amgctl (me, amc, value); |
| 160 | break; |
| 161 | case mmr_offset(bf53x.ambctl0): |
| 162 | amc->bf53x.ambctl0 = value; |
| 163 | break; |
| 164 | case mmr_offset(bf53x.ambctl1): |
| 165 | amc->bf53x.ambctl1 = value; |
| 166 | break; |
| 167 | default: |
| 168 | dv_bfin_mmr_invalid (me, addr, nr_bytes, true); |
| 169 | break; |
| 170 | } |
| 171 | |
| 172 | return nr_bytes; |
| 173 | } |
| 174 | |
| 175 | static unsigned |
| 176 | bf54x_ebiu_amc_io_write_buffer (struct hw *me, const void *source, int space, |
| 177 | address_word addr, unsigned nr_bytes, |
| 178 | struct bfin_ebiu_amc *amc, bu32 mmr_off, |
| 179 | bu32 value) |
| 180 | { |
| 181 | switch (mmr_off) |
| 182 | { |
| 183 | case mmr_offset(amgctl): |
| 184 | dv_bfin_mmr_require_16 (me, addr, nr_bytes, true); |
| 185 | bfin_ebiu_amc_write_amgctl (me, amc, value); |
| 186 | break; |
| 187 | case mmr_offset(bf54x.ambctl0): |
| 188 | amc->bf54x.ambctl0 = value; |
| 189 | break; |
| 190 | case mmr_offset(bf54x.ambctl1): |
| 191 | amc->bf54x.ambctl1 = value; |
| 192 | break; |
| 193 | case mmr_offset(bf54x.mbsctl): |
| 194 | /* XXX: implement this. */ |
| 195 | break; |
| 196 | case mmr_offset(bf54x.arbstat): |
| 197 | /* XXX: implement this. */ |
| 198 | break; |
| 199 | case mmr_offset(bf54x.mode): |
| 200 | /* XXX: implement this. */ |
| 201 | break; |
| 202 | case mmr_offset(bf54x.fctl): |
| 203 | /* XXX: implement this. */ |
| 204 | break; |
| 205 | default: |
| 206 | dv_bfin_mmr_invalid (me, addr, nr_bytes, true); |
| 207 | break; |
| 208 | } |
| 209 | |
| 210 | return nr_bytes; |
| 211 | } |
| 212 | |
| 213 | static unsigned |
| 214 | bfin_ebiu_amc_io_write_buffer (struct hw *me, const void *source, int space, |
| 215 | address_word addr, unsigned nr_bytes) |
| 216 | { |
| 217 | struct bfin_ebiu_amc *amc = hw_data (me); |
| 218 | bu32 mmr_off; |
| 219 | bu32 value; |
| 220 | |
| 221 | value = dv_load_4 (source); |
| 222 | mmr_off = addr - amc->base; |
| 223 | |
| 224 | HW_TRACE_WRITE (); |
| 225 | |
| 226 | return amc->io_write (me, source, space, addr, nr_bytes, |
| 227 | amc, mmr_off, value); |
| 228 | } |
| 229 | |
| 230 | static unsigned |
| 231 | bf50x_ebiu_amc_io_read_buffer (struct hw *me, void *dest, int space, |
| 232 | address_word addr, unsigned nr_bytes, |
| 233 | struct bfin_ebiu_amc *amc, bu32 mmr_off, |
| 234 | void *valuep, bu16 *value16, bu32 *value32) |
| 235 | { |
| 236 | switch (mmr_off) |
| 237 | { |
| 238 | case mmr_offset(amgctl): |
| 239 | case mmr_offset(bf50x.fctl): |
| 240 | dv_bfin_mmr_require_16 (me, addr, nr_bytes, false); |
| 241 | dv_store_2 (dest, *value16); |
| 242 | break; |
| 243 | case mmr_offset(bf50x.ambctl0): |
| 244 | case mmr_offset(bf50x.ambctl1): |
| 245 | case mmr_offset(bf50x.mode): |
| 246 | dv_store_4 (dest, *value32); |
| 247 | break; |
| 248 | default: |
| 249 | dv_bfin_mmr_invalid (me, addr, nr_bytes, false); |
| 250 | break; |
| 251 | } |
| 252 | |
| 253 | return nr_bytes; |
| 254 | } |
| 255 | |
| 256 | static unsigned |
| 257 | bf53x_ebiu_amc_io_read_buffer (struct hw *me, void *dest, int space, |
| 258 | address_word addr, unsigned nr_bytes, |
| 259 | struct bfin_ebiu_amc *amc, bu32 mmr_off, |
| 260 | void *valuep, bu16 *value16, bu32 *value32) |
| 261 | { |
| 262 | switch (mmr_off) |
| 263 | { |
| 264 | case mmr_offset(amgctl): |
| 265 | dv_bfin_mmr_require_16 (me, addr, nr_bytes, false); |
| 266 | dv_store_2 (dest, *value16); |
| 267 | break; |
| 268 | case mmr_offset(bf53x.ambctl0): |
| 269 | case mmr_offset(bf53x.ambctl1): |
| 270 | dv_store_4 (dest, *value32); |
| 271 | break; |
| 272 | default: |
| 273 | dv_bfin_mmr_invalid (me, addr, nr_bytes, false); |
| 274 | break; |
| 275 | } |
| 276 | |
| 277 | return nr_bytes; |
| 278 | } |
| 279 | |
| 280 | static unsigned |
| 281 | bf54x_ebiu_amc_io_read_buffer (struct hw *me, void *dest, int space, |
| 282 | address_word addr, unsigned nr_bytes, |
| 283 | struct bfin_ebiu_amc *amc, bu32 mmr_off, |
| 284 | void *valuep, bu16 *value16, bu32 *value32) |
| 285 | { |
| 286 | switch (mmr_off) |
| 287 | { |
| 288 | case mmr_offset(amgctl): |
| 289 | dv_bfin_mmr_require_16 (me, addr, nr_bytes, false); |
| 290 | dv_store_2 (dest, *value16); |
| 291 | break; |
| 292 | case mmr_offset(bf54x.ambctl0): |
| 293 | case mmr_offset(bf54x.ambctl1): |
| 294 | case mmr_offset(bf54x.mbsctl): |
| 295 | case mmr_offset(bf54x.arbstat): |
| 296 | case mmr_offset(bf54x.mode): |
| 297 | case mmr_offset(bf54x.fctl): |
| 298 | dv_store_4 (dest, *value32); |
| 299 | break; |
| 300 | default: |
| 301 | dv_bfin_mmr_invalid (me, addr, nr_bytes, false); |
| 302 | break; |
| 303 | } |
| 304 | |
| 305 | return nr_bytes; |
| 306 | } |
| 307 | |
| 308 | static unsigned |
| 309 | bfin_ebiu_amc_io_read_buffer (struct hw *me, void *dest, int space, |
| 310 | address_word addr, unsigned nr_bytes) |
| 311 | { |
| 312 | struct bfin_ebiu_amc *amc = hw_data (me); |
| 313 | bu32 mmr_off; |
| 314 | void *valuep; |
| 315 | |
| 316 | mmr_off = addr - amc->base; |
| 317 | valuep = (void *)((unsigned long)amc + mmr_base() + mmr_off); |
| 318 | |
| 319 | HW_TRACE_READ (); |
| 320 | |
| 321 | return amc->io_read (me, dest, space, addr, nr_bytes, amc, |
| 322 | mmr_off, valuep, valuep, valuep); |
| 323 | } |
| 324 | |
| 325 | static void |
| 326 | bfin_ebiu_amc_attach_address_callback (struct hw *me, |
| 327 | int level, |
| 328 | int space, |
| 329 | address_word addr, |
| 330 | address_word nr_bytes, |
| 331 | struct hw *client) |
| 332 | { |
| 333 | struct bfin_ebiu_amc *amc = hw_data (me); |
| 334 | |
| 335 | HW_TRACE ((me, "attach - level=%d, space=%d, addr=0x%lx, nr_bytes=%lu, client=%s", |
| 336 | level, space, (unsigned long) addr, (unsigned long) nr_bytes, hw_path (client))); |
| 337 | |
| 338 | if (addr + nr_bytes > 4) |
| 339 | hw_abort (me, "ebiu amc attaches are done in terms of banks"); |
| 340 | |
| 341 | while (nr_bytes--) |
| 342 | amc->slaves[addr + nr_bytes] = client; |
| 343 | |
| 344 | bfin_ebiu_amc_write_amgctl (me, amc, amc->amgctl); |
| 345 | } |
| 346 | |
| 347 | static void |
| 348 | attach_bfin_ebiu_amc_regs (struct hw *me, struct bfin_ebiu_amc *amc, |
| 349 | unsigned reg_size) |
| 350 | { |
| 351 | address_word attach_address; |
| 352 | int attach_space; |
| 353 | unsigned attach_size; |
| 354 | reg_property_spec reg; |
| 355 | |
| 356 | if (hw_find_property (me, "reg") == NULL) |
| 357 | hw_abort (me, "Missing \"reg\" property"); |
| 358 | |
| 359 | if (!hw_find_reg_array_property (me, "reg", 0, ®)) |
| 360 | hw_abort (me, "\"reg\" property must contain three addr/size entries"); |
| 361 | |
| 362 | if (hw_find_property (me, "type") == NULL) |
| 363 | hw_abort (me, "Missing \"type\" property"); |
| 364 | |
| 365 | hw_unit_address_to_attach_address (hw_parent (me), |
| 366 | ®.address, |
| 367 | &attach_space, &attach_address, me); |
| 368 | hw_unit_size_to_attach_size (hw_parent (me), ®.size, &attach_size, me); |
| 369 | |
| 370 | if (attach_size != reg_size) |
| 371 | hw_abort (me, "\"reg\" size must be %#x", reg_size); |
| 372 | |
| 373 | hw_attach_address (hw_parent (me), |
| 374 | 0, attach_space, attach_address, attach_size, me); |
| 375 | |
| 376 | amc->base = attach_address; |
| 377 | } |
| 378 | |
| 379 | static void |
| 380 | bfin_ebiu_amc_finish (struct hw *me) |
| 381 | { |
| 382 | struct bfin_ebiu_amc *amc; |
| 383 | bu32 amgctl; |
| 384 | unsigned reg_size; |
| 385 | |
| 386 | amc = HW_ZALLOC (me, struct bfin_ebiu_amc); |
| 387 | |
| 388 | set_hw_data (me, amc); |
| 389 | set_hw_io_read_buffer (me, bfin_ebiu_amc_io_read_buffer); |
| 390 | set_hw_io_write_buffer (me, bfin_ebiu_amc_io_write_buffer); |
| 391 | set_hw_attach_address (me, bfin_ebiu_amc_attach_address_callback); |
| 392 | |
| 393 | amc->type = hw_find_integer_property (me, "type"); |
| 394 | |
| 395 | switch (amc->type) |
| 396 | { |
| 397 | case 500 ... 509: |
| 398 | amc->io_write = bf50x_ebiu_amc_io_write_buffer; |
| 399 | amc->io_read = bf50x_ebiu_amc_io_read_buffer; |
| 400 | mmr_names = bf50x_mmr_names; |
| 401 | reg_size = sizeof (amc->bf50x) + 4; |
| 402 | |
| 403 | /* Initialize the AMC. */ |
| 404 | amc->bank_size = 1 * 1024 * 1024; |
| 405 | amgctl = 0x00F3; |
| 406 | amc->bf50x.ambctl0 = 0x0000FFC2; |
| 407 | amc->bf50x.ambctl1 = 0x0000FFC2; |
| 408 | amc->bf50x.mode = 0x0001; |
| 409 | amc->bf50x.fctl = 0x0002; |
| 410 | break; |
| 411 | case 540 ... 549: |
| 412 | amc->io_write = bf54x_ebiu_amc_io_write_buffer; |
| 413 | amc->io_read = bf54x_ebiu_amc_io_read_buffer; |
| 414 | mmr_names = bf54x_mmr_names; |
| 415 | reg_size = sizeof (amc->bf54x) + 4; |
| 416 | |
| 417 | /* Initialize the AMC. */ |
| 418 | amc->bank_size = 64 * 1024 * 1024; |
| 419 | amgctl = 0x0002; |
| 420 | amc->bf54x.ambctl0 = 0xFFC2FFC2; |
| 421 | amc->bf54x.ambctl1 = 0xFFC2FFC2; |
| 422 | amc->bf54x.fctl = 0x0006; |
| 423 | break; |
| 424 | case 510 ... 519: |
| 425 | case 522 ... 527: |
| 426 | case 531 ... 533: |
| 427 | case 534: |
| 428 | case 536: |
| 429 | case 537: |
| 430 | case 538 ... 539: |
| 431 | case 561: |
| 432 | amc->io_write = bf53x_ebiu_amc_io_write_buffer; |
| 433 | amc->io_read = bf53x_ebiu_amc_io_read_buffer; |
| 434 | mmr_names = bf53x_mmr_names; |
| 435 | reg_size = sizeof (amc->bf53x) + 4; |
| 436 | |
| 437 | /* Initialize the AMC. */ |
| 438 | if (amc->type == 561) |
| 439 | amc->bank_size = 64 * 1024 * 1024; |
| 440 | else |
| 441 | amc->bank_size = 1 * 1024 * 1024; |
| 442 | amgctl = 0x00F2; |
| 443 | amc->bf53x.ambctl0 = 0xFFC2FFC2; |
| 444 | amc->bf53x.ambctl1 = 0xFFC2FFC2; |
| 445 | break; |
| 446 | case 590 ... 599: /* BF59x has no AMC. */ |
| 447 | default: |
| 448 | hw_abort (me, "no support for EBIU AMC on this Blackfin model yet"); |
| 449 | } |
| 450 | |
| 451 | attach_bfin_ebiu_amc_regs (me, amc, reg_size); |
| 452 | |
| 453 | bfin_ebiu_amc_write_amgctl (me, amc, amgctl); |
| 454 | } |
| 455 | |
| 456 | const struct hw_descriptor dv_bfin_ebiu_amc_descriptor[] = |
| 457 | { |
| 458 | {"bfin_ebiu_amc", bfin_ebiu_amc_finish,}, |
| 459 | {NULL, NULL}, |
| 460 | }; |