1/*- 2 * Copyright (c) 2008 John Hay. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 14 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 16 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 17 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 18 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 * 24 */ 25 26#include <sys/cdefs.h> 27__FBSDID("$FreeBSD$"); 28#include <sys/param.h> 29#include <sys/ata.h> 30#include <sys/linker_set.h> 31 32#include <stdarg.h> 33 34#include "lib.h" 35#include "cf_ata.h" 36 37#include <machine/armreg.h> 38#include <arm/xscale/ixp425/ixp425reg.h> 39#include <dev/ic/ns16550.h> 40 41struct board_config { 42 const char *desc; 43 int (*probe)(int boardtype_hint); 44 void (*init)(void); 45}; 46/* set of registered boards */ 47SET_DECLARE(boards, struct board_config); 48#define BOARD_CONFIG(name, _desc) \ 49static struct board_config name##config = { \ 50 .desc = _desc, \ 51 .probe = name##_probe, \ 52 .init = name##_init, \ 53}; \ 54DATA_SET(boards, name##config) 55 56static u_int cputype; 57#define cpu_is_ixp43x() (cputype == CPU_ID_IXP435) 58static u_int8_t *ubase; 59 60static u_int8_t uart_getreg(u_int8_t *, int); 61static void uart_setreg(u_int8_t *, int, u_int8_t); 62 63static void cf_init(void); 64static void cf_clr(void); 65 66#ifdef DEBUG 67#define DPRINTF(fmt, ...) printf(fmt, __VA_ARGS__) 68#else 69#define DPRINTF(fmt, ...) 70#endif 71 72const char * 73board_init(void) 74{ 75 struct board_config **pbp; 76 77 cputype = cpu_id() & CPU_ID_CPU_MASK; 78 79 SET_FOREACH(pbp, boards) 80 /* XXX pass down redboot board type */ 81 if ((*pbp)->probe(0)) { 82 (*pbp)->init(); 83 return (*pbp)->desc; 84 } 85 /* XXX panic, unknown board type */ 86 return "???"; 87} 88 89/* 90 * This should be called just before starting the kernel. This is so 91 * that one can undo incompatible hardware settings. 92 */ 93void 94clr_board(void) 95{ 96 cf_clr(); 97} 98 99/* 100 * General support functions. 101 */ 102 103/* 104 * DELAY should delay for the number of microseconds. 105 * The idea is that the inner loop should take 1us, so val is the 106 * number of usecs to delay. 107 */ 108void 109DELAY(int val) 110{ 111 volatile int sub; 112 volatile int subsub; 113 114 sub = val; 115 while(sub) { 116 subsub = 3; 117 while(subsub) 118 subsub--; 119 sub--; 120 } 121} 122 123u_int32_t 124swap32(u_int32_t a) 125{ 126 return (((a & 0xff) << 24) | ((a & 0xff00) << 8) | 127 ((a & 0xff0000) >> 8) | ((a & 0xff000000) >> 24)); 128} 129 130u_int16_t 131swap16(u_int16_t val) 132{ 133 return (val << 8) | (val >> 8); 134} 135 136/* 137 * uart related funcs 138 */ 139static u_int8_t 140uart_getreg(u_int8_t *bas, int off) 141{ 142 return *((volatile u_int32_t *)(bas + (off << 2))) & 0xff; 143} 144 145static void 146uart_setreg(u_int8_t *bas, int off, u_int8_t val) 147{ 148 *((volatile u_int32_t *)(bas + (off << 2))) = (u_int32_t)val; 149} 150 151int 152getc(int seconds) 153{ 154 int c, delay, limit; 155 156 c = 0; 157 delay = 10000; 158 limit = seconds * 1000000/10000; 159 while ((uart_getreg(ubase, REG_LSR) & LSR_RXRDY) == 0 && --limit) 160 DELAY(delay); 161 162 if ((uart_getreg(ubase, REG_LSR) & LSR_RXRDY) == LSR_RXRDY) 163 c = uart_getreg(ubase, REG_DATA); 164 165 return c; 166} 167 168void 169putchar(int ch) 170{ 171 int delay, limit; 172 173 delay = 500; 174 limit = 20; 175 while ((uart_getreg(ubase, REG_LSR) & LSR_THRE) == 0 && --limit) 176 DELAY(delay); 177 uart_setreg(ubase, REG_DATA, ch); 178 179 limit = 40; 180 while ((uart_getreg(ubase, REG_LSR) & LSR_TEMT) == 0 && --limit) 181 DELAY(delay); 182} 183 184void 185xputchar(int ch) 186{ 187 if (ch == '\n') 188 putchar('\r'); 189 putchar(ch); 190} 191 192void 193putstr(const char *str) 194{ 195 while(*str) 196 xputchar(*str++); 197} 198 199void 200puthex8(u_int8_t ch) 201{ 202 const char *hex = "0123456789abcdef"; 203 204 putchar(hex[ch >> 4]); 205 putchar(hex[ch & 0xf]); 206} 207 208void 209puthexlist(const u_int8_t *str, int length) 210{ 211 while(length) { 212 puthex8(*str); 213 putchar(' '); 214 str++; 215 length--; 216 } 217} 218 219/* 220 * 221 * CF/IDE functions. 222 * 223 */ 224 225struct { 226 u_int64_t dsize; 227 u_int64_t total_secs; 228 u_int8_t heads; 229 u_int8_t sectors; 230 u_int32_t cylinders; 231 232 u_int32_t *cs1to; 233 u_int32_t *cs2to; 234 235 u_int8_t *cs1; 236 u_int8_t *cs2; 237 238 u_int32_t use_lba; 239 u_int32_t use_stream8; 240 u_int32_t debug; 241 242 u_int8_t status; 243 u_int8_t error; 244} dskinf; 245 246static void cfenable16(void); 247static void cfdisable16(void); 248static u_int8_t cfread8(u_int32_t off); 249static u_int16_t cfread16(u_int32_t off); 250static void cfreadstream8(void *buf, int length); 251static void cfreadstream16(void *buf, int length); 252static void cfwrite8(u_int32_t off, u_int8_t val); 253static u_int8_t cfaltread8(u_int32_t off); 254static void cfaltwrite8(u_int32_t off, u_int8_t val); 255static int cfwait(u_int8_t mask); 256static int cfaltwait(u_int8_t mask); 257static int cfcmd(u_int32_t cmd, u_int32_t cylinder, u_int32_t head, 258 u_int32_t sector, u_int32_t count, u_int32_t feature); 259static void cfreset(void); 260#ifdef DEBUG 261static int cfgetparams(void); 262#endif 263static void cfprintregs(void); 264 265static void 266cf_init(void) 267{ 268 u_int8_t status; 269#ifdef DEBUG 270 int rval; 271#endif 272 273 /* NB: board init routines setup other parts of dskinf */ 274 dskinf.use_stream8 = 0; 275 dskinf.use_lba = 0; 276 dskinf.debug = 1; 277 278 DPRINTF("cs1 %x, cs2 %x\n", dskinf.cs1, dskinf.cs2); 279 280 /* Setup the CF window */ 281 *dskinf.cs1to |= (EXP_BYTE_EN | EXP_WR_EN | EXP_BYTE_RD16 | EXP_CS_EN); 282 DPRINTF("t1 %x, ", *dskinf.cs1to); 283 284 *dskinf.cs2to |= (EXP_BYTE_EN | EXP_WR_EN | EXP_BYTE_RD16 | EXP_CS_EN); 285 DPRINTF("t2 %x\n", *dskinf.cs2to); 286 287 /* Detect if there is a disk. */ 288 cfwrite8(CF_DRV_HEAD, CF_D_IBM); 289 DELAY(1000); 290 status = cfread8(CF_STATUS); 291 if (status != 0x50) 292 printf("cf-ata0 %x\n", (u_int32_t)status); 293 if (status == 0xff) { 294 printf("cf_ata0: No disk!\n"); 295 return; 296 } 297 298 cfreset(); 299 300 if (dskinf.use_stream8) { 301 DPRINTF("setting %d bit mode.\n", 8); 302 cfwrite8(CF_FEATURE, 0x01); /* Enable 8 bit transfers */ 303 cfwrite8(CF_COMMAND, ATA_SETFEATURES); 304 cfaltwait(CF_S_READY); 305 } 306 307#ifdef DEBUG 308 rval = cfgetparams(); 309 if (rval) 310 return; 311#endif 312 dskinf.use_lba = 1; 313 dskinf.debug = 0; 314} 315 316static void 317cf_clr(void) 318{ 319 cfwrite8(CF_DRV_HEAD, CF_D_IBM); 320 cfaltwait(CF_S_READY); 321 cfwrite8(CF_FEATURE, 0x81); /* Enable 8 bit transfers */ 322 cfwrite8(CF_COMMAND, ATA_SETFEATURES); 323 cfaltwait(CF_S_READY); 324} 325 326static void 327cfenable16(void) 328{ 329 u_int32_t val; 330 331 val = *dskinf.cs1to; 332 *dskinf.cs1to = val &~ EXP_BYTE_EN; 333 DELAY(100); 334#if 0 335 DPRINTF("%s: cs1 timing reg %x\n", *dskinf.cs1to, __func__); 336#endif 337} 338 339static void 340cfdisable16(void) 341{ 342 u_int32_t val; 343 344 DELAY(100); 345 val = *dskinf.cs1to; 346 *dskinf.cs1to = val | EXP_BYTE_EN; 347#if 0 348 DPRINTF("%s: cs1 timing reg %x\n", *dskinf.cs1to, __func__); 349#endif 350} 351 352static u_int8_t 353cfread8(u_int32_t off) 354{ 355 volatile u_int8_t *vp; 356 357 vp = (volatile u_int8_t *)(dskinf.cs1 + off); 358 return *vp; 359} 360 361static void 362cfreadstream8(void *buf, int length) 363{ 364 u_int8_t *lbuf; 365 u_int8_t tmp; 366 367 lbuf = buf; 368 while (length) { 369 tmp = cfread8(CF_DATA); 370 *lbuf = tmp; 371#ifdef DEBUG 372 if (dskinf.debug && (length > (512 - 32))) { 373 if ((length % 16) == 0) 374 xputchar('\n'); 375 puthex8(tmp); 376 putchar(' '); 377 } 378#endif 379 lbuf++; 380 length--; 381 } 382#ifdef DEBUG 383 if (dskinf.debug) 384 xputchar('\n'); 385#endif 386} 387 388static u_int16_t 389cfread16(u_int32_t off) 390{ 391 volatile u_int16_t *vp; 392 393 vp = (volatile u_int16_t *)(dskinf.cs1 + off); 394 return swap16(*vp); 395} 396 397static void 398cfreadstream16(void *buf, int length) 399{ 400 u_int16_t *lbuf; 401 402 length = length / 2; 403 cfenable16(); 404 lbuf = buf; 405 while (length--) { 406 *lbuf = cfread16(CF_DATA); 407 lbuf++; 408 } 409 cfdisable16(); 410} 411 412static void 413cfwrite8(u_int32_t off, u_int8_t val) 414{ 415 volatile u_int8_t *vp; 416 417 vp = (volatile u_int8_t *)(dskinf.cs1 + off); 418 *vp = val; 419} 420 421#if 0 422static void 423cfwrite16(u_int32_t off, u_int16_t val) 424{ 425 volatile u_int16_t *vp; 426 427 vp = (volatile u_int16_t *)(dskinf.cs1 + off); 428 *vp = val; 429} 430#endif 431 432static u_int8_t 433cfaltread8(u_int32_t off) 434{ 435 volatile u_int8_t *vp; 436 437 off &= 0x0f; 438 vp = (volatile u_int8_t *)(dskinf.cs2 + off); 439 return *vp; 440} 441 442static void 443cfaltwrite8(u_int32_t off, u_int8_t val) 444{ 445 volatile u_int8_t *vp; 446 447 /* 448 * This is documented in the Intel appnote 302456. 449 */ 450 off &= 0x0f; 451 vp = (volatile u_int8_t *)(dskinf.cs2 + off); 452 *vp = val; 453} 454 455static int 456cfwait(u_int8_t mask) 457{ 458 u_int8_t status; 459 u_int32_t tout; 460 461 tout = 0; 462 while (tout <= 5000000) { 463 status = cfread8(CF_STATUS); 464 if (status == 0xff) { 465 printf("%s: master: no status, reselecting\n", 466 __func__); 467 cfwrite8(CF_DRV_HEAD, CF_D_IBM); 468 DELAY(1); 469 status = cfread8(CF_STATUS); 470 } 471 if (status == 0xff) 472 return -1; 473 dskinf.status = status; 474 if (!(status & CF_S_BUSY)) { 475 if (status & CF_S_ERROR) { 476 dskinf.error = cfread8(CF_ERROR); 477 printf("%s: error, status 0x%x error 0x%x\n", 478 __func__, status, dskinf.error); 479 } 480 if ((status & mask) == mask) { 481 DPRINTF("%s: status 0x%x mask 0x%x tout %u\n", 482 __func__, status, mask, tout); 483 return (status & CF_S_ERROR); 484 } 485 } 486 if (tout > 1000) { 487 tout += 1000; 488 DELAY(1000); 489 } else { 490 tout += 10; 491 DELAY(10); 492 } 493 } 494 return -1; 495} 496 497static int 498cfaltwait(u_int8_t mask) 499{ 500 u_int8_t status; 501 u_int32_t tout; 502 503 tout = 0; 504 while (tout <= 5000000) { 505 status = cfaltread8(CF_ALT_STATUS); 506 if (status == 0xff) { 507 printf("cfaltwait: master: no status, reselecting\n"); 508 cfwrite8(CF_DRV_HEAD, CF_D_IBM); 509 DELAY(1); 510 status = cfread8(CF_STATUS); 511 } 512 if (status == 0xff) 513 return -1; 514 dskinf.status = status; 515 if (!(status & CF_S_BUSY)) { 516 if (status & CF_S_ERROR) 517 dskinf.error = cfread8(CF_ERROR); 518 if ((status & mask) == mask) { 519 DPRINTF("cfaltwait: tout %u\n", tout); 520 return (status & CF_S_ERROR); 521 } 522 } 523 if (tout > 1000) { 524 tout += 1000; 525 DELAY(1000); 526 } else { 527 tout += 10; 528 DELAY(10); 529 } 530 } 531 return -1; 532} 533 534static int 535cfcmd(u_int32_t cmd, u_int32_t cylinder, u_int32_t head, u_int32_t sector, 536 u_int32_t count, u_int32_t feature) 537{ 538 if (cfwait(0) < 0) { 539 printf("cfcmd: timeout\n"); 540 return -1; 541 } 542 cfwrite8(CF_FEATURE, feature); 543 cfwrite8(CF_CYL_L, cylinder); 544 cfwrite8(CF_CYL_H, cylinder >> 8); 545 if (dskinf.use_lba) 546 cfwrite8(CF_DRV_HEAD, CF_D_IBM | CF_D_LBA | head); 547 else 548 cfwrite8(CF_DRV_HEAD, CF_D_IBM | head); 549 cfwrite8(CF_SECT_NUM, sector); 550 cfwrite8(CF_SECT_CNT, count); 551 cfwrite8(CF_COMMAND, cmd); 552 return 0; 553} 554 555static void 556cfreset(void) 557{ 558 u_int8_t status; 559 u_int32_t tout; 560 561 cfwrite8(CF_DRV_HEAD, CF_D_IBM); 562 DELAY(1); 563#ifdef DEBUG 564 cfprintregs(); 565#endif 566 cfread8(CF_STATUS); 567 cfaltwrite8(CF_ALT_DEV_CTR, CF_A_IDS | CF_A_RESET); 568 DELAY(10000); 569 cfaltwrite8(CF_ALT_DEV_CTR, CF_A_IDS); 570 DELAY(10000); 571 cfread8(CF_ERROR); 572 DELAY(3000); 573 574 for (tout = 0; tout < 310000; tout++) { 575 cfwrite8(CF_DRV_HEAD, CF_D_IBM); 576 DELAY(1); 577 status = cfread8(CF_STATUS); 578 if (!(status & CF_S_BUSY)) 579 break; 580 DELAY(100); 581 } 582 DELAY(1); 583 if (status & CF_S_BUSY) { 584 cfprintregs(); 585 printf("cfreset: Status stayed busy after reset.\n"); 586 } 587 DPRINTF("cfreset: finished, tout %u\n", tout); 588} 589 590#ifdef DEBUG 591static int 592cfgetparams(void) 593{ 594 u_int8_t *buf; 595 596 buf = (u_int8_t *)(0x170000); 597 p_memset((char *)buf, 0, 1024); 598 /* Select the drive. */ 599 cfwrite8(CF_DRV_HEAD, CF_D_IBM); 600 DELAY(1); 601 cfcmd(ATA_ATA_IDENTIFY, 0, 0, 0, 0, 0); 602 if (cfaltwait(CF_S_READY | CF_S_DSC | CF_S_DRQ)) { 603 printf("cfgetparams: ATA_IDENTIFY failed.\n"); 604 return -1; 605 } 606 if (dskinf.use_stream8) 607 cfreadstream8(buf, 512); 608 else 609 cfreadstream16(buf, 512); 610 if (dskinf.debug) 611 cfprintregs(); 612#if 0 613 memcpy(&dskinf.ata_params, buf, sizeof(struct ata_params)); 614 dskinf.cylinders = dskinf.ata_params.cylinders; 615 dskinf.heads = dskinf.ata_params.heads; 616 dskinf.sectors = dskinf.ata_params.sectors; 617 printf("dsk0: sec %x, hd %x, cyl %x, stat %x, err %x\n", 618 (u_int32_t)dskinf.ata_params.sectors, 619 (u_int32_t)dskinf.ata_params.heads, 620 (u_int32_t)dskinf.ata_params.cylinders, 621 (u_int32_t)dskinf.status, 622 (u_int32_t)dskinf.error); 623#endif 624 dskinf.status = cfread8(CF_STATUS); 625 if (dskinf.debug) 626 printf("cfgetparams: ata_params * %x, stat %x\n", 627 (u_int32_t)buf, (u_int32_t)dskinf.status); 628 return 0; 629} 630#endif /* DEBUG */ 631 632static void 633cfprintregs(void) 634{ 635 u_int8_t rv; 636 637 putstr("cfprintregs: regs error "); 638 rv = cfread8(CF_ERROR); 639 puthex8(rv); 640 putstr(", count "); 641 rv = cfread8(CF_SECT_CNT); 642 puthex8(rv); 643 putstr(", sect "); 644 rv = cfread8(CF_SECT_NUM); 645 puthex8(rv); 646 putstr(", cyl low "); 647 rv = cfread8(CF_CYL_L); 648 puthex8(rv); 649 putstr(", cyl high "); 650 rv = cfread8(CF_CYL_H); 651 puthex8(rv); 652 putstr(", drv head "); 653 rv = cfread8(CF_DRV_HEAD); 654 puthex8(rv); 655 putstr(", status "); 656 rv = cfread8(CF_STATUS); 657 puthex8(rv); 658 putstr("\n"); 659} 660 661int 662avila_read(char *dest, unsigned source, unsigned length) 663{ 664 if (dskinf.use_lba == 0 && source == 0) 665 source++; 666 if (dskinf.debug) 667 printf("avila_read: 0x%x, sect %d num secs %d\n", 668 (u_int32_t)dest, source, length); 669 while (length) { 670 cfwait(CF_S_READY); 671 /* cmd, cyl, head, sect, count, feature */ 672 cfcmd(ATA_READ, (source >> 8) & 0xffff, source >> 24, 673 source & 0xff, 1, 0); 674 675 cfwait(CF_S_READY | CF_S_DRQ | CF_S_DSC); 676 if (dskinf.use_stream8) 677 cfreadstream8(dest, 512); 678 else 679 cfreadstream16(dest, 512); 680 length--; 681 source++; 682 dest += 512; 683 } 684 return 0; 685} 686 687/* 688 * Gateworks Avila Support. 689 */ 690static int 691avila_probe(int boardtype_hint) 692{ 693 volatile u_int32_t *cs; 694 /* 695 * Redboot only configure the chip selects that are needed, so 696 * use that to figure out if it is an Avila or ADI board. The 697 * Avila boards use CS2 and ADI does not. 698 */ 699 cs = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS2_OFFSET); 700 return (*cs != 0); 701} 702 703static void 704avila_init(void) 705{ 706 /* Config the serial port. RedBoot should do the rest. */ 707 ubase = (u_int8_t *)(IXP425_UART0_HWBASE); 708 709 dskinf.cs1to = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS1_OFFSET); 710 dskinf.cs2to = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS2_OFFSET); 711 dskinf.cs1 = (u_int8_t *)IXP425_EXP_BUS_CS1_HWBASE; 712 dskinf.cs2 = (u_int8_t *)IXP425_EXP_BUS_CS2_HWBASE; 713 714 cf_init(); 715} 716BOARD_CONFIG(avila, "Gateworks Avila"); 717 718/* 719 * Gateworks Cambria Support. 720 */ 721static int 722cambria_probe(int boardtype_hint) 723{ 724 return cpu_is_ixp43x(); 725} 726 727static void 728cambria_init(void) 729{ 730 /* Config the serial port. RedBoot should do the rest. */ 731 ubase = (u_int8_t *)(IXP425_UART0_HWBASE); 732 733 dskinf.cs1to = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS3_OFFSET); 734 dskinf.cs2to = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS4_OFFSET); 735 dskinf.cs1 = (u_int8_t *)CAMBRIA_CFSEL0_HWBASE; 736 dskinf.cs2 = (u_int8_t *)CAMBRIA_CFSEL1_HWBASE; 737 738 cf_init(); 739} 740BOARD_CONFIG(cambria, "Gateworks Cambria"); 741 742/* 743 * Pronghorn Metro Support. 744 */ 745static int 746pronghorn_probe(int boardtype_hint) 747{ 748 volatile u_int32_t *cs; 749 /* 750 * Redboot only configure the chip selects that are needed, so 751 * use that to figure out if it is an Avila or ADI board. The 752 * Avila boards use CS2 and ADI does not. 753 */ 754 cs = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS2_OFFSET); 755 return (*cs == 0); 756} 757 758static void 759pronghorn_init(void) 760{ 761 /* Config the serial port. RedBoot should do the rest. */ 762 ubase = (u_int8_t *)(IXP425_UART1_HWBASE); 763 764 dskinf.cs1to = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS3_OFFSET); 765 dskinf.cs2to = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS4_OFFSET); 766 dskinf.cs1 = (u_int8_t *)IXP425_EXP_BUS_CS3_HWBASE; 767 dskinf.cs2 = (u_int8_t *)IXP425_EXP_BUS_CS4_HWBASE; 768 769 cf_init(); 770} 771BOARD_CONFIG(pronghorn, "Pronghorn Metro"); 772