/* $NetBSD: fd.c,v 1.11 2014/12/12 15:57:30 phx Exp $ */ /*- * Copyright (C) 1997-1998 Kazuki Sakamoto (sakamoto@NetBSD.org) * All rights reserved. * * Floppy Disk Drive standalone device driver * * 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. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Kazuki Sakamoto. * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * 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 #include #include "boot.h" /*---------------------------------------------------------------------------* * Floppy Disk Controller Define * *---------------------------------------------------------------------------*/ /* Floppy Disk Controller Registers */ int FDC_PORT[] = { /* fdc base I/O port */ 0x3f0, /* primary */ }; #define FDC_DOR(x) (FDC_PORT[x] + 0x2) /* motor drive control bits */ #define FDC_STATUS(x) (FDC_PORT[x] + 0x4) /* fdc main status register */ #define FDC_DATA(x) (FDC_PORT[x] + 0x5) /* fdc data register */ #define FDC_RATE(x) (FDC_PORT[x] + 0x7) /* transfer rate register */ #define FDC_IRQ 6 #define FD_DMA_CHAN 2 /* fdc main status register */ #define RQM 0x80 /* the host can transfer data if set */ #define DIO 0x40 /* direction of data transfer. write required if set */ #define NON_DMA 0x20 /* fdc have date for transfer in non dma mode */ #define CMD_BUSY 0x10 /* command busy if set */ /* fdc result status */ #define ST0_IC_MASK 0xc0 /* interrupt code 00:normal terminate */ #define ST1_EN 0x80 /* end of cylinder */ /* fdc digtal output register */ #define DOR_DMAEN 0x08 /* DRQ, nDACK, TC and FINTR output enable */ #define DOR_RESET 0x04 /* fdc software reset */ /* fdc command */ #define CMD_RECALIBRATE 0x07 /* recalibrate */ #define CMD_SENSE_INT 0x08 /* sense interrupt status */ #define CMD_DRV_SENSE 0x04 /* sense drive status */ #define CMD_SEEK 0x0f /* seek */ #define CMD_FORMAT 0x4d /* format */ #define CMD_READ 0x46 /* read e6 */ #define CMD_WRITE 0xc5 /* write */ #define CMD_VERIFY 0xf6 /* verify */ #define CMD_READID 0x4a /* readID */ #define CMD_SPECIFY 0x03 /* specify */ #define CMD_CONFIG 0x13 /* config */ #define CMD_VERSION 0x10 /* version */ /* command specify value */ #define SPECIFY1 ((0x0d<<4)|0x0f) #define SPECIFY2 ((0x01<<1)|0) /* DMA MODE */ /* fdc result */ #define STATUS_MAX 16 /* result status max number */ #define RESULT_VERSION 0x90 /* enhanced controller */ #define RESULT_SEEK 0x20 /* seek & recalibrate complete flag on status0 */ /*---------------------------------------------------------------------------* * Floppy Disk Type Define * *---------------------------------------------------------------------------*/ struct fdd_type { int seccount; /* sector per track */ int secsize; /* byte per sector (uPD765 paramater) */ int datalen; /* data length */ int gap; /* gap */ int gaplen; /* gap length */ int cylinder; /* track per media */ int maxseccount; /* media max sector count */ int step; /* seek step */ int rate; /* drive rate (250 or 500kbps) */ int heads; /* heads */ int f_gap; /* format gap */ int mselect; /* drive mode select */ char *type_name; /* media type name */ }; typedef struct fdd_type FDDTYPE; #define FDTYPE_MAX 5 FDDTYPE fdd_types[FDTYPE_MAX] = { { 18,2,0xff,0x1b,0x54,80,2880,1,0,2,0x6c,0,"2HQ" }, /* 2HD (PC/AT) */ { 8,3,0xff,0x35,0x74,77,1232,1,0,2,0x54,1,"2HD" }, /* 2HD (98) */ { 15,2,0xff,0x1b,0x54,80,2400,1,0,2,0x54,1,"2HC" }, /* 2HC */ { 9,2,0xff,0x23,0x50,80,1440,1,2,2,0x50,1,"2DD9" },/* 2DD 9 sector */ { 8,2,0xff,0x3a,0x50,80,1280,1,2,2,0x50,1,"2DD8" },/* 2DD 8 sector */ }; int fdsectors[] = {128, 256, 512, 1024, 2048, 4096}; #define SECTOR_MAX 4096 #define FDBLK (fdsectors[un->un_type->secsize]) #define START_CYL 0 #define START_SECTOR 1 #define DELAY(x) delay(100000 * x) /* about 100ms */ #define INT_TIMEOUT 3000000 /*---------------------------------------------------------------------------* * FDC Device Driver Define * *---------------------------------------------------------------------------*/ #define CTLR_MAX 1 #define UNIT_MAX 2 struct fd_unit { int ctlr; int unit; u_int un_flags; /* unit status flag */ int stat[STATUS_MAX]; /* result code */ FDDTYPE *un_type; /* floppy type (pointer) */ }; typedef struct fd_unit FD_UNIT; FD_UNIT fd_unit[CTLR_MAX][UNIT_MAX]; /* * un_flags flags */ #define INT_ALIVE 0x00000001 /* Device is Alive and Available */ #define INT_READY 0x00000002 /* Device is Ready */ #define INT_BUSY 0x00000004 /* Device is busy */ /*---------------------------------------------------------------------------* * Misc define * *---------------------------------------------------------------------------*/ #define TIMEOUT 10000000 #define ND_TIMEOUT 10000000 #define SUCCESS 0 #define FAIL -1 /* * function declaration */ int fdinit(FD_UNIT *); int fdopen(struct open_file *, int, int); int fdclose(struct open_file *); int fdioctl(struct open_file *, u_long, void *); int fdstrategy(void *, int, daddr_t, size_t, void *, size_t *); int fdc_out(int, int); int fdc_in(int, u_char *); int fdc_intr_wait(void); int fd_check(FD_UNIT *); void motor_on(int, int); void motor_off(int, int); void fdReset(int); void fdRecalibrate(int, int); void fdSpecify(int); void fdDriveStatus(int, int, int, int *); int fdSeek(int, int, int); int fdSenseInt(int, int *); int fdReadWrite(FD_UNIT *, int, int, int, int, u_char *); void irq_init(void); int irq_polling(int, int); void dma_setup(u_char *, int, int, int); int dma_finished(int); /*===========================================================================* * fdinit * *===========================================================================*/ int fdinit(FD_UNIT *un) { int ctlr = un->ctlr; u_char result; #if 0 irq_init(); #endif fdReset(ctlr); if (fdc_out(ctlr, CMD_VERSION) != SUCCESS) { /* version check */ printf ("fdc%d:fatal error: CMD_VERSION cmd fail\n", ctlr); return (FAIL); } if (fdc_in(ctlr, &result) != SUCCESS) { printf ("fdc%d:fatal error: CMD_VERSION exec fail\n", ctlr); return (FAIL); } if (result != (u_char)RESULT_VERSION) { printf ("fdc%d:fatal error: unknown version fdc\n", ctlr); return (FAIL); } un->un_flags = INT_ALIVE; return (SUCCESS); } /*===========================================================================* * fdopen * *===========================================================================*/ int fdopen(struct open_file *f, int ctlr, int unit) { FD_UNIT *un; int *stat; if (ctlr >= CTLR_MAX) return (ENXIO); if (unit >= UNIT_MAX) return (ENXIO); un = &fd_unit[ctlr][unit]; stat = un->stat; if (!(un->un_flags & INT_ALIVE)) { if (fdinit(un) != SUCCESS) return (ENXIO); } motor_on(ctlr, unit); fdRecalibrate(ctlr, unit); fdSenseInt(ctlr, stat); if (stat[1] != START_CYL) { printf("fdc%d: unit:%d recalibrate failed. status:0x%x cyl:%d\n", ctlr, unit, stat[0], stat[1]); motor_off(ctlr, unit); return (EIO); } if (fd_check(un) != SUCCESS) /* research disk type */ return (EIO); f->f_devdata = (void *)un; return (SUCCESS); } /*===========================================================================* * fdclose * *===========================================================================*/ int fdclose(struct open_file *f) { FD_UNIT *un = f->f_devdata; fdRecalibrate(un->ctlr, un->unit); fdSenseInt(un->ctlr, un->stat); motor_off(un->ctlr, un->unit); un->un_flags = 0; return (SUCCESS); } /*===========================================================================* * fdioctl * *===========================================================================*/ int fdioctl(struct open_file *f, u_long cmd, void *arg) { switch (cmd) { default: return (EIO); } return (SUCCESS); } /*===========================================================================* * fdstrategy * *===========================================================================*/ int fdstrategy(void *devdata, int func, daddr_t blk, size_t size, void *buf, size_t *rsize) { int sectrac, cyl, head, sec; FD_UNIT *un = devdata; int ctlr = un->ctlr; int unit = un->unit; int *stat = un->stat; long blknum; int fd_skip = 0; u_char *cbuf = (u_char *)buf; if (un->un_flags & INT_BUSY) { return (ENXIO); } fdDriveStatus(ctlr, unit, 0, stat); sectrac = un->un_type->seccount; /* sector per track */ *rsize = 0; while (fd_skip < size) { blknum = (u_long)blk * DEV_BSIZE/FDBLK + fd_skip/FDBLK; cyl = blknum / (sectrac * 2); fdSeek(ctlr, unit, cyl); fdSenseInt(ctlr, stat); if (!(stat[0] & RESULT_SEEK)) { printf("fdc%d: unit:%d seek failed." "status:0x%x cyl:%d pcyl:%d\n", ctlr, unit, stat[0], cyl, stat[1]); goto bad; } sec = blknum % (sectrac * 2); head = sec / sectrac; sec = sec % sectrac + 1; if (fdReadWrite(un, func, cyl, head, sec, cbuf) == FAIL) { printf("fdc%d: unit%d fdReadWrite error [%s]\n", ctlr, unit, (func==F_READ?"READ":"WRITE")); goto bad; } *rsize += FDBLK; cbuf += FDBLK; fd_skip += FDBLK; } return (SUCCESS); bad: return (FAIL); } /*===========================================================================* * fd_check * *===========================================================================*/ /* * this function is Check floppy disk Type */ int fd_check(FD_UNIT *un) { int ctlr = un->ctlr; int unit = un->unit; int *stat = un->stat; int type; static u_char sec_buff[SECTOR_MAX]; un->un_type = (FDDTYPE *)FAIL; for (type = 0; type < FDTYPE_MAX; type++) { un->un_type = &fdd_types[type]; /* try read start sector */ outb(FDC_RATE(ctlr), un->un_type->rate); /* rate set */ fdSpecify(ctlr); fdSeek(ctlr, unit, START_CYL); fdSenseInt(ctlr, stat); if (!(stat[0] & RESULT_SEEK) || stat[1] != START_CYL) { printf("fdc%d: unit:%d seek failed. status:0x%x\n", ctlr, unit, stat[0]); goto bad; } if (fdReadWrite(un, F_READ, START_CYL, 0, START_SECTOR, sec_buff) == FAIL) { continue; /* bad disk type */ } break; } if (un->un_type == (FDDTYPE *)FAIL) { printf("fdc%d: unit:%d check disk type failed.\n", ctlr, unit); goto bad; } return (SUCCESS); bad: return (FAIL); } /* * for FDC routines. */ /*===========================================================================* * fdc_out * *===========================================================================*/ int fdc_out(int ctlr, int cmd) { volatile int status; int time_out; time_out = TIMEOUT; while (((status = inb(FDC_STATUS(ctlr))) & (RQM | DIO)) != (RQM | 0) && time_out-- > 0); if (time_out <= 0) { printf("fdc_out: timeout status = 0x%x\n", status); return (FAIL); } outb(FDC_DATA(ctlr), cmd); return (SUCCESS); } /*===========================================================================* * fdc_in * *===========================================================================*/ int fdc_in(int ctlr, u_char *data) { volatile int status; int time_out; time_out = TIMEOUT; while ((status = inb(FDC_STATUS(ctlr)) & (RQM | DIO)) != (RQM | DIO) && time_out-- > 0) { if (status == RQM) { printf("fdc_in:error:ready for output\n"); return (FAIL); } } if (time_out <= 0) { printf("fdc_in:input ready timeout\n"); return (FAIL); } if (data) *data = (u_char)inb(FDC_DATA(ctlr)); return (SUCCESS); } /*===========================================================================* * fdc_intr_wait * *===========================================================================*/ int fdc_intr_wait(void) { return (irq_polling(FDC_IRQ, INT_TIMEOUT)); /* wait interrupt */ } /*===========================================================================* * fdc command function * *===========================================================================*/ void motor_on(int ctlr, int unit) { outb(FDC_DOR(ctlr), DOR_RESET | DOR_DMAEN | unit | (1 << (unit + 4))); /* reset & unit motor on */ DELAY(1); /* wait 100msec */ } void motor_off(int ctlr, int unit) { outb(FDC_DOR(ctlr), DOR_RESET); /* reset & motor off */ if (fdc_intr_wait() == FAIL) /* wait interrupt */ printf("fdc: motor off failed.\n"); } void fdReset(int ctlr) { outb(FDC_DOR(ctlr), 0); /* fdc reset */ DELAY(3); outb(FDC_DOR(ctlr), DOR_RESET); DELAY(8); } void fdRecalibrate(int ctlr, int unit) { fdc_out(ctlr, CMD_RECALIBRATE); fdc_out(ctlr, unit); if (fdc_intr_wait() == FAIL) /* wait interrupt */ printf("fdc: recalibrate Timeout\n"); } void fdSpecify(int ctlr) { fdc_out(ctlr, CMD_SPECIFY); fdc_out(ctlr, SPECIFY1); fdc_out(ctlr, SPECIFY2); } void fdDriveStatus(int ctlr, register int unit, register int head, register int *stat) { u_char result; fdc_out(ctlr, CMD_DRV_SENSE); fdc_out(ctlr, (head << 2) | unit); fdc_in(ctlr, &result); *stat = (int)(result & 0xff); } int fdSeek(int ctlr, int unit, int cyl) { int ret_val = 0; fdc_out(ctlr, CMD_SEEK); fdc_out(ctlr, unit); fdc_out(ctlr, cyl); if (fdc_intr_wait() == FAIL) { /* wait interrupt */ printf("fdc: fdSeek Timeout\n"); ret_val = FAIL; } return(ret_val); } int fdSenseInt(int ctlr, int *stat) { u_char result; fdc_out(ctlr, CMD_SENSE_INT); fdc_in(ctlr, &result); *stat++ = (int)(result & 0xff); fdc_in(ctlr, &result); *stat++ = (int)(result & 0xff); return (0); } int fdReadWrite(FD_UNIT *un, int func, int cyl, int head, int sec, u_char *adrs) { int i; int ctlr = un->ctlr; int unit = un->unit; int *stat = un->stat; u_char result; #if 0 printf("%s:", (func == F_READ ? "READ" : "WRITE")); printf("cyl = %d", cyl); printf("head = %d", head); printf("sec = %d", sec); printf("secsize = %d", un->un_type->secsize); printf("seccount = %d", un->un_type->seccount); printf("gap = %d", un->un_type->gap); printf("datalen = %d\n", un->un_type->datalen); #endif dma_setup(adrs, FDBLK, func, FD_DMA_CHAN); fdc_out(ctlr, (func == F_READ ? CMD_READ : CMD_WRITE)); fdc_out(ctlr, (head<<2) | unit); fdc_out(ctlr, cyl); /* cyl */ fdc_out(ctlr, head); /* head */ fdc_out(ctlr, sec); /* sec */ fdc_out(ctlr, un->un_type->secsize); /* secsize */ fdc_out(ctlr, un->un_type->seccount); /* EOT (end of track) */ fdc_out(ctlr, un->un_type->gap); /* GAP3 */ fdc_out(ctlr, un->un_type->datalen); /* DTL (data length) */ if (fdc_intr_wait() == FAIL) { /* wait interrupt */ printf("fdc: DMA transfer Timeout\n"); return (FAIL); } for (i = 0; i < 7; i++) { fdc_in(ctlr, &result); stat[i] = (int)(result & 0xff); } if (stat[0] & ST0_IC_MASK) { /* not normal terminate */ if ((stat[1] & ~ST1_EN) || stat[2]) goto bad; } if (!dma_finished(FD_DMA_CHAN)) { printf("DMA not finished\n"); goto bad; } return (SUCCESS); bad: printf(" func: %s\n", (func == F_READ ? "F_READ" : "F_WRITE")); printf(" st0 = 0x%x\n", stat[0]); printf(" st1 = 0x%x\n", stat[1]); printf(" st2 = 0x%x\n", stat[2]); printf(" c = 0x%x\n", stat[3]); printf(" h = 0x%x\n", stat[4]); printf(" r = 0x%x\n", stat[5]); printf(" n = 0x%x\n", stat[6]); return (FAIL); } /*----------------------------------------------------------------------- * Interrupt Controller Operation Functions *----------------------------------------------------------------------- */ /* 8259A interrupt controller register */ #define INT_CTL0 0x20 #define INT_CTL1 0x21 #define INT2_CTL0 0xA0 #define INT2_CTL1 0xA1 #define CASCADE_IRQ 2 #define ICW1_AT 0x11 /* edge triggered, cascade, need ICW4 */ #define ICW4_AT 0x01 /* not SFNM, not buffered, normal EOI, 8086 */ #define OCW3_PL 0x0e /* polling mode */ #define OCW2_CLEAR 0x20 /* interrupt clear */ /* * IRC programing sequence * * after reset * 1. ICW1 (write port:INT_CTL0 data:bit4=1) * 2. ICW2 (write port:INT_CTL1) * 3. ICW3 (write port:INT_CTL1) * 4. ICW4 (write port:INT_CTL1) * * after ICW * OCW1 (write port:INT_CTL1) * OCW2 (write port:INT_CTL0 data:bit3=0,bit4=0) * OCW3 (write port:INT_CTL0 data:bit3=1,bit4=0) * * IMR (read port:INT_CTL1) * IRR (read port:INT_CTL0) OCW3(bit1=1,bit0=0) * ISR (read port:INT_CTL0) OCW3(bit1=1,bit0=1) * PL (read port:INT_CTL0) OCW3(bit2=1,bit1=1) */ u_int INT_MASK; u_int INT2_MASK; /*===========================================================================* * irq initialize * *===========================================================================*/ void irq_init(void) { outb(INT_CTL0, ICW1_AT); /* ICW1 */ outb(INT_CTL1, 0); /* ICW2 for master */ outb(INT_CTL1, (1 << CASCADE_IRQ)); /* ICW3 tells slaves */ outb(INT_CTL1, ICW4_AT); /* ICW4 */ outb(INT_CTL1, (INT_MASK = ~(1 << CASCADE_IRQ))); /* IRQ mask(exclusive of cascade) */ outb(INT2_CTL0, ICW1_AT); /* ICW1 */ outb(INT2_CTL1, 8); /* ICW2 for slave */ outb(INT2_CTL1, CASCADE_IRQ); /* ICW3 is slave nr */ outb(INT2_CTL1, ICW4_AT); /* ICW4 */ outb(INT2_CTL1, (INT2_MASK = ~0)); /* IRQ 8-15 mask */ } /*===========================================================================* * irq polling check * *===========================================================================*/ int irq_polling(int irq_no, int timeout) { int irc_no; int data; int ret; if (irq_no > 8) irc_no = 1; else irc_no = 0; outb(irc_no ? INT2_CTL1 : INT_CTL1, ~(1 << (irq_no >> (irc_no * 3)))); while (--timeout > 0) { outb(irc_no ? INT2_CTL0 : INT_CTL0, OCW3_PL); /* set polling mode */ data = inb(irc_no ? INT2_CTL0 : INT_CTL0); if (data & 0x80) { /* if interrupt request */ if ((irq_no >> (irc_no * 3)) == (data & 0x7)) { ret = SUCCESS; break; } } } if (!timeout) ret = FAIL; if (irc_no) { /* interrupt clear */ outb(INT2_CTL0, OCW2_CLEAR | (irq_no >> 3)); outb(INT_CTL0, OCW2_CLEAR | CASCADE_IRQ); } else { outb(INT_CTL0, OCW2_CLEAR | irq_no); } outb(INT_CTL1, INT_MASK); outb(INT2_CTL1, INT2_MASK); return (ret); } /*---------------------------------------------------------------------------* * DMA Controller Define * *---------------------------------------------------------------------------*/ /* DMA Controller Registers */ #define DMA_ADDR 0x004 /* port for low 16 bits of DMA address */ #define DMA_LTOP 0x081 /* port for top low 8bit DMA addr(ch2) */ #define DMA_HTOP 0x481 /* port for top high 8bit DMA addr(ch2) */ #define DMA_COUNT 0x005 /* port for DMA count (count = bytes - 1) */ #define DMA_DEVCON 0x008 /* DMA device control register */ #define DMA_SR 0x008 /* DMA status register */ #define DMA_RESET 0x00D /* DMA software reset register */ #define DMA_FLIPFLOP 0x00C /* DMA byte pointer flip-flop */ #define DMA_MODE 0x00B /* DMA mode port */ #define DMA_INIT 0x00A /* DMA init port */ #define DMA_RESET_VAL 0x06 /* DMA channel commands. */ #define DMA_READ 0x46 /* DMA read opcode */ #define DMA_WRITE 0x4A /* DMA write opcode */ /*===========================================================================* * dma_setup * *===========================================================================*/ void dma_setup(u_char *buf, int size, int func, int chan) { u_long pbuf = local_to_PCI((u_long)buf); #if 0 outb(DMA_RESET, 0); DELAY(1); outb(DMA_DEVCON, 0x00); outb(DMA_INIT, DMA_RESET_VAL); /* reset the dma controller */ #endif outb(DMA_MODE, func == F_READ ? DMA_READ : DMA_WRITE); outb(DMA_FLIPFLOP, 0); /* write anything to reset it */ outb(DMA_ADDR, (int)pbuf >> 0); outb(DMA_ADDR, (int)pbuf >> 8); outb(DMA_LTOP, (int)pbuf >> 16); outb(DMA_HTOP, (int)pbuf >> 24); outb(DMA_COUNT, (size - 1) >> 0); outb(DMA_COUNT, (size - 1) >> 8); outb(DMA_INIT, chan); /* some sort of enable */ } int dma_finished(int chan) { return ((inb(DMA_SR) & 0x0f) == (1 << chan)); }