/*- * Copyright (c) 2008 John Hay. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include "lib.h" #include "cf_ata.h" #include #include #include struct board_config { const char *desc; int (*probe)(int boardtype_hint); void (*init)(void); }; /* set of registered boards */ SET_DECLARE(boards, struct board_config); #define BOARD_CONFIG(name, _desc) \ static struct board_config name##config = { \ .desc = _desc, \ .probe = name##_probe, \ .init = name##_init, \ }; \ DATA_SET(boards, name##config) static u_int cputype; #define cpu_is_ixp43x() (cputype == CPU_ID_IXP435) static u_int8_t *ubase; static u_int8_t uart_getreg(u_int8_t *, int); static void uart_setreg(u_int8_t *, int, u_int8_t); static void cf_init(void); static void cf_clr(void); #ifdef DEBUG #define DPRINTF(fmt, ...) printf(fmt, __VA_ARGS__) #else #define DPRINTF(fmt, ...) #endif const char * board_init(void) { struct board_config **pbp; cputype = cpu_id() & CPU_ID_CPU_MASK; SET_FOREACH(pbp, boards) /* XXX pass down redboot board type */ if ((*pbp)->probe(0)) { (*pbp)->init(); return (*pbp)->desc; } /* XXX panic, unknown board type */ return "???"; } /* * This should be called just before starting the kernel. This is so * that one can undo incompatible hardware settings. */ void clr_board(void) { cf_clr(); } /* * General support functions. */ /* * DELAY should delay for the number of microseconds. * The idea is that the inner loop should take 1us, so val is the * number of usecs to delay. */ void DELAY(int val) { volatile int sub; volatile int subsub; sub = val; while(sub) { subsub = 3; while(subsub) subsub--; sub--; } } u_int32_t swap32(u_int32_t a) { return (((a & 0xff) << 24) | ((a & 0xff00) << 8) | ((a & 0xff0000) >> 8) | ((a & 0xff000000) >> 24)); } u_int16_t swap16(u_int16_t val) { return (val << 8) | (val >> 8); } /* * uart related funcs */ static u_int8_t uart_getreg(u_int8_t *bas, int off) { return *((volatile u_int32_t *)(bas + (off << 2))) & 0xff; } static void uart_setreg(u_int8_t *bas, int off, u_int8_t val) { *((volatile u_int32_t *)(bas + (off << 2))) = (u_int32_t)val; } int getc(int seconds) { int c, delay, limit; c = 0; delay = 10000; limit = seconds * 1000000/10000; while ((uart_getreg(ubase, REG_LSR) & LSR_RXRDY) == 0 && --limit) DELAY(delay); if ((uart_getreg(ubase, REG_LSR) & LSR_RXRDY) == LSR_RXRDY) c = uart_getreg(ubase, REG_DATA); return c; } void putchar(int ch) { int delay, limit; delay = 500; limit = 20; while ((uart_getreg(ubase, REG_LSR) & LSR_THRE) == 0 && --limit) DELAY(delay); uart_setreg(ubase, REG_DATA, ch); limit = 40; while ((uart_getreg(ubase, REG_LSR) & LSR_TEMT) == 0 && --limit) DELAY(delay); } void xputchar(int ch) { if (ch == '\n') putchar('\r'); putchar(ch); } void putstr(const char *str) { while(*str) xputchar(*str++); } void puthex8(u_int8_t ch) { const char *hex = "0123456789abcdef"; putchar(hex[ch >> 4]); putchar(hex[ch & 0xf]); } void puthexlist(const u_int8_t *str, int length) { while(length) { puthex8(*str); putchar(' '); str++; length--; } } /* * * CF/IDE functions. * */ struct { u_int64_t dsize; u_int64_t total_secs; u_int8_t heads; u_int8_t sectors; u_int32_t cylinders; u_int32_t *cs1to; u_int32_t *cs2to; u_int8_t *cs1; u_int8_t *cs2; u_int32_t use_lba; u_int32_t use_stream8; u_int32_t debug; u_int8_t status; u_int8_t error; } dskinf; static void cfenable16(void); static void cfdisable16(void); static u_int8_t cfread8(u_int32_t off); static u_int16_t cfread16(u_int32_t off); static void cfreadstream8(void *buf, int length); static void cfreadstream16(void *buf, int length); static void cfwrite8(u_int32_t off, u_int8_t val); static u_int8_t cfaltread8(u_int32_t off); static void cfaltwrite8(u_int32_t off, u_int8_t val); static int cfwait(u_int8_t mask); static int cfaltwait(u_int8_t mask); static int cfcmd(u_int32_t cmd, u_int32_t cylinder, u_int32_t head, u_int32_t sector, u_int32_t count, u_int32_t feature); static void cfreset(void); #ifdef DEBUG static int cfgetparams(void); #endif static void cfprintregs(void); static void cf_init(void) { u_int8_t status; #ifdef DEBUG int rval; #endif /* NB: board init routines setup other parts of dskinf */ dskinf.use_stream8 = 0; dskinf.use_lba = 0; dskinf.debug = 1; DPRINTF("cs1 %x, cs2 %x\n", dskinf.cs1, dskinf.cs2); /* Setup the CF window */ *dskinf.cs1to |= (EXP_BYTE_EN | EXP_WR_EN | EXP_BYTE_RD16 | EXP_CS_EN); DPRINTF("t1 %x, ", *dskinf.cs1to); *dskinf.cs2to |= (EXP_BYTE_EN | EXP_WR_EN | EXP_BYTE_RD16 | EXP_CS_EN); DPRINTF("t2 %x\n", *dskinf.cs2to); /* Detect if there is a disk. */ cfwrite8(CF_DRV_HEAD, CF_D_IBM); DELAY(1000); status = cfread8(CF_STATUS); if (status != 0x50) printf("cf-ata0 %x\n", (u_int32_t)status); if (status == 0xff) { printf("cf_ata0: No disk!\n"); return; } cfreset(); if (dskinf.use_stream8) { DPRINTF("setting %d bit mode.\n", 8); cfwrite8(CF_FEATURE, 0x01); /* Enable 8 bit transfers */ cfwrite8(CF_COMMAND, ATA_SETFEATURES); cfaltwait(CF_S_READY); } #ifdef DEBUG rval = cfgetparams(); if (rval) return; #endif dskinf.use_lba = 1; dskinf.debug = 0; } static void cf_clr(void) { cfwrite8(CF_DRV_HEAD, CF_D_IBM); cfaltwait(CF_S_READY); cfwrite8(CF_FEATURE, 0x81); /* Enable 8 bit transfers */ cfwrite8(CF_COMMAND, ATA_SETFEATURES); cfaltwait(CF_S_READY); } static void cfenable16(void) { u_int32_t val; val = *dskinf.cs1to; *dskinf.cs1to = val &~ EXP_BYTE_EN; DELAY(100); #if 0 DPRINTF("%s: cs1 timing reg %x\n", *dskinf.cs1to, __func__); #endif } static void cfdisable16(void) { u_int32_t val; DELAY(100); val = *dskinf.cs1to; *dskinf.cs1to = val | EXP_BYTE_EN; #if 0 DPRINTF("%s: cs1 timing reg %x\n", *dskinf.cs1to, __func__); #endif } static u_int8_t cfread8(u_int32_t off) { volatile u_int8_t *vp; vp = (volatile u_int8_t *)(dskinf.cs1 + off); return *vp; } static void cfreadstream8(void *buf, int length) { u_int8_t *lbuf; u_int8_t tmp; lbuf = buf; while (length) { tmp = cfread8(CF_DATA); *lbuf = tmp; #ifdef DEBUG if (dskinf.debug && (length > (512 - 32))) { if ((length % 16) == 0) xputchar('\n'); puthex8(tmp); putchar(' '); } #endif lbuf++; length--; } #ifdef DEBUG if (dskinf.debug) xputchar('\n'); #endif } static u_int16_t cfread16(u_int32_t off) { volatile u_int16_t *vp; vp = (volatile u_int16_t *)(dskinf.cs1 + off); return swap16(*vp); } static void cfreadstream16(void *buf, int length) { u_int16_t *lbuf; length = length / 2; cfenable16(); lbuf = buf; while (length--) { *lbuf = cfread16(CF_DATA); lbuf++; } cfdisable16(); } static void cfwrite8(u_int32_t off, u_int8_t val) { volatile u_int8_t *vp; vp = (volatile u_int8_t *)(dskinf.cs1 + off); *vp = val; } #if 0 static void cfwrite16(u_int32_t off, u_int16_t val) { volatile u_int16_t *vp; vp = (volatile u_int16_t *)(dskinf.cs1 + off); *vp = val; } #endif static u_int8_t cfaltread8(u_int32_t off) { volatile u_int8_t *vp; off &= 0x0f; vp = (volatile u_int8_t *)(dskinf.cs2 + off); return *vp; } static void cfaltwrite8(u_int32_t off, u_int8_t val) { volatile u_int8_t *vp; /* * This is documented in the Intel appnote 302456. */ off &= 0x0f; vp = (volatile u_int8_t *)(dskinf.cs2 + off); *vp = val; } static int cfwait(u_int8_t mask) { u_int8_t status; u_int32_t tout; tout = 0; while (tout <= 5000000) { status = cfread8(CF_STATUS); if (status == 0xff) { printf("%s: master: no status, reselecting\n", __func__); cfwrite8(CF_DRV_HEAD, CF_D_IBM); DELAY(1); status = cfread8(CF_STATUS); } if (status == 0xff) return -1; dskinf.status = status; if (!(status & CF_S_BUSY)) { if (status & CF_S_ERROR) { dskinf.error = cfread8(CF_ERROR); printf("%s: error, status 0x%x error 0x%x\n", __func__, status, dskinf.error); } if ((status & mask) == mask) { DPRINTF("%s: status 0x%x mask 0x%x tout %u\n", __func__, status, mask, tout); return (status & CF_S_ERROR); } } if (tout > 1000) { tout += 1000; DELAY(1000); } else { tout += 10; DELAY(10); } } return -1; } static int cfaltwait(u_int8_t mask) { u_int8_t status; u_int32_t tout; tout = 0; while (tout <= 5000000) { status = cfaltread8(CF_ALT_STATUS); if (status == 0xff) { printf("cfaltwait: master: no status, reselecting\n"); cfwrite8(CF_DRV_HEAD, CF_D_IBM); DELAY(1); status = cfread8(CF_STATUS); } if (status == 0xff) return -1; dskinf.status = status; if (!(status & CF_S_BUSY)) { if (status & CF_S_ERROR) dskinf.error = cfread8(CF_ERROR); if ((status & mask) == mask) { DPRINTF("cfaltwait: tout %u\n", tout); return (status & CF_S_ERROR); } } if (tout > 1000) { tout += 1000; DELAY(1000); } else { tout += 10; DELAY(10); } } return -1; } static int cfcmd(u_int32_t cmd, u_int32_t cylinder, u_int32_t head, u_int32_t sector, u_int32_t count, u_int32_t feature) { if (cfwait(0) < 0) { printf("cfcmd: timeout\n"); return -1; } cfwrite8(CF_FEATURE, feature); cfwrite8(CF_CYL_L, cylinder); cfwrite8(CF_CYL_H, cylinder >> 8); if (dskinf.use_lba) cfwrite8(CF_DRV_HEAD, CF_D_IBM | CF_D_LBA | head); else cfwrite8(CF_DRV_HEAD, CF_D_IBM | head); cfwrite8(CF_SECT_NUM, sector); cfwrite8(CF_SECT_CNT, count); cfwrite8(CF_COMMAND, cmd); return 0; } static void cfreset(void) { u_int8_t status; u_int32_t tout; cfwrite8(CF_DRV_HEAD, CF_D_IBM); DELAY(1); #ifdef DEBUG cfprintregs(); #endif cfread8(CF_STATUS); cfaltwrite8(CF_ALT_DEV_CTR, CF_A_IDS | CF_A_RESET); DELAY(10000); cfaltwrite8(CF_ALT_DEV_CTR, CF_A_IDS); DELAY(10000); cfread8(CF_ERROR); DELAY(3000); for (tout = 0; tout < 310000; tout++) { cfwrite8(CF_DRV_HEAD, CF_D_IBM); DELAY(1); status = cfread8(CF_STATUS); if (!(status & CF_S_BUSY)) break; DELAY(100); } DELAY(1); if (status & CF_S_BUSY) { cfprintregs(); printf("cfreset: Status stayed busy after reset.\n"); } DPRINTF("cfreset: finished, tout %u\n", tout); } #ifdef DEBUG static int cfgetparams(void) { u_int8_t *buf; buf = (u_int8_t *)(0x170000); p_memset((char *)buf, 0, 1024); /* Select the drive. */ cfwrite8(CF_DRV_HEAD, CF_D_IBM); DELAY(1); cfcmd(ATA_ATA_IDENTIFY, 0, 0, 0, 0, 0); if (cfaltwait(CF_S_READY | CF_S_DSC | CF_S_DRQ)) { printf("cfgetparams: ATA_IDENTIFY failed.\n"); return -1; } if (dskinf.use_stream8) cfreadstream8(buf, 512); else cfreadstream16(buf, 512); if (dskinf.debug) cfprintregs(); #if 0 memcpy(&dskinf.ata_params, buf, sizeof(struct ata_params)); dskinf.cylinders = dskinf.ata_params.cylinders; dskinf.heads = dskinf.ata_params.heads; dskinf.sectors = dskinf.ata_params.sectors; printf("dsk0: sec %x, hd %x, cyl %x, stat %x, err %x\n", (u_int32_t)dskinf.ata_params.sectors, (u_int32_t)dskinf.ata_params.heads, (u_int32_t)dskinf.ata_params.cylinders, (u_int32_t)dskinf.status, (u_int32_t)dskinf.error); #endif dskinf.status = cfread8(CF_STATUS); if (dskinf.debug) printf("cfgetparams: ata_params * %x, stat %x\n", (u_int32_t)buf, (u_int32_t)dskinf.status); return 0; } #endif /* DEBUG */ static void cfprintregs(void) { u_int8_t rv; putstr("cfprintregs: regs error "); rv = cfread8(CF_ERROR); puthex8(rv); putstr(", count "); rv = cfread8(CF_SECT_CNT); puthex8(rv); putstr(", sect "); rv = cfread8(CF_SECT_NUM); puthex8(rv); putstr(", cyl low "); rv = cfread8(CF_CYL_L); puthex8(rv); putstr(", cyl high "); rv = cfread8(CF_CYL_H); puthex8(rv); putstr(", drv head "); rv = cfread8(CF_DRV_HEAD); puthex8(rv); putstr(", status "); rv = cfread8(CF_STATUS); puthex8(rv); putstr("\n"); } int avila_read(char *dest, unsigned source, unsigned length) { if (dskinf.use_lba == 0 && source == 0) source++; if (dskinf.debug) printf("avila_read: 0x%x, sect %d num secs %d\n", (u_int32_t)dest, source, length); while (length) { cfwait(CF_S_READY); /* cmd, cyl, head, sect, count, feature */ cfcmd(ATA_READ, (source >> 8) & 0xffff, source >> 24, source & 0xff, 1, 0); cfwait(CF_S_READY | CF_S_DRQ | CF_S_DSC); if (dskinf.use_stream8) cfreadstream8(dest, 512); else cfreadstream16(dest, 512); length--; source++; dest += 512; } return 0; } /* * Gateworks Avila Support. */ static int avila_probe(int boardtype_hint) { volatile u_int32_t *cs; /* * Redboot only configure the chip selects that are needed, so * use that to figure out if it is an Avila or ADI board. The * Avila boards use CS2 and ADI does not. */ cs = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS2_OFFSET); return (*cs != 0); } static void avila_init(void) { /* Config the serial port. RedBoot should do the rest. */ ubase = (u_int8_t *)(IXP425_UART0_HWBASE); dskinf.cs1to = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS1_OFFSET); dskinf.cs2to = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS2_OFFSET); dskinf.cs1 = (u_int8_t *)IXP425_EXP_BUS_CS1_HWBASE; dskinf.cs2 = (u_int8_t *)IXP425_EXP_BUS_CS2_HWBASE; cf_init(); } BOARD_CONFIG(avila, "Gateworks Avila"); /* * Gateworks Cambria Support. */ static int cambria_probe(int boardtype_hint) { return cpu_is_ixp43x(); } static void cambria_init(void) { /* Config the serial port. RedBoot should do the rest. */ ubase = (u_int8_t *)(IXP425_UART0_HWBASE); dskinf.cs1to = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS3_OFFSET); dskinf.cs2to = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS4_OFFSET); dskinf.cs1 = (u_int8_t *)CAMBRIA_CFSEL0_HWBASE; dskinf.cs2 = (u_int8_t *)CAMBRIA_CFSEL1_HWBASE; cf_init(); } BOARD_CONFIG(cambria, "Gateworks Cambria"); /* * Pronghorn Metro Support. */ static int pronghorn_probe(int boardtype_hint) { volatile u_int32_t *cs; /* * Redboot only configure the chip selects that are needed, so * use that to figure out if it is an Avila or ADI board. The * Avila boards use CS2 and ADI does not. */ cs = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS2_OFFSET); return (*cs == 0); } static void pronghorn_init(void) { /* Config the serial port. RedBoot should do the rest. */ ubase = (u_int8_t *)(IXP425_UART1_HWBASE); dskinf.cs1to = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS3_OFFSET); dskinf.cs2to = (u_int32_t *)(IXP425_EXP_HWBASE + EXP_TIMING_CS4_OFFSET); dskinf.cs1 = (u_int8_t *)IXP425_EXP_BUS_CS3_HWBASE; dskinf.cs2 = (u_int8_t *)IXP425_EXP_BUS_CS4_HWBASE; cf_init(); } BOARD_CONFIG(pronghorn, "Pronghorn Metro");