diff options
author | kato <kato@FreeBSD.org> | 1999-06-18 14:48:28 +0000 |
---|---|---|
committer | kato <kato@FreeBSD.org> | 1999-06-18 14:48:28 +0000 |
commit | fb2478efdbf1997a3b9b01a3485c4dd51a61921e (patch) | |
tree | cc97d58275fb62a88a84c35f8771d9b62e60716f /sys | |
parent | 72f8c1311e27806ce9eb0083b1c7fc2f556fd3a2 (diff) | |
download | FreeBSD-src-fb2478efdbf1997a3b9b01a3485c4dd51a61921e.zip FreeBSD-src-fb2478efdbf1997a3b9b01a3485c4dd51a61921e.tar.gz |
New parallel port support for PC98. Old PC98s which have
uni-directional parallel port should use olpt driver instead of lpt
driver.
Files ppc.c and ppcreg.h are copied form i386/isa directory with PC98
change.
Submitted by: Akio Morita <amorita@meadow.scphys.kyoto-u.ac.jp>
Diffstat (limited to 'sys')
-rw-r--r-- | sys/conf/files.pc98 | 6 | ||||
-rw-r--r-- | sys/pc98/cbus/olpt.c (renamed from sys/pc98/pc98/lpt.c) | 31 | ||||
-rw-r--r-- | sys/pc98/cbus/ppc.c | 1846 | ||||
-rw-r--r-- | sys/pc98/cbus/ppcreg.h | 233 | ||||
-rw-r--r-- | sys/pc98/conf/GENERIC | 13 | ||||
-rw-r--r-- | sys/pc98/conf/GENERIC98 | 13 | ||||
-rw-r--r-- | sys/pc98/conf/files.pc98 | 6 | ||||
-rw-r--r-- | sys/pc98/pc98/olpt.c | 1473 | ||||
-rw-r--r-- | sys/pc98/pc98/ppc.c | 1846 | ||||
-rw-r--r-- | sys/pc98/pc98/ppcreg.h | 233 |
10 files changed, 5684 insertions, 16 deletions
diff --git a/sys/conf/files.pc98 b/sys/conf/files.pc98 index 943ec0f..a03636a 100644 --- a/sys/conf/files.pc98 +++ b/sys/conf/files.pc98 @@ -3,7 +3,7 @@ # # modified for PC-9801 # -# $Id: files.pc98,v 1.96 1999/05/17 12:07:00 kato Exp $ +# $Id: files.pc98,v 1.97 1999/06/03 13:03:58 kato Exp $ # # The long compile-with and dependency lines are required because of # limitations in config: backslash-newline doesn't work in strings, and @@ -175,7 +175,7 @@ i386/isa/istallion.c optional stli device-driver i386/isa/joy.c optional joy device-driver pc98/pc98/pc98kbd.c optional pckbd device-driver i386/isa/loran.c optional loran device-driver -pc98/pc98/lpt.c optional lpt device-driver +pc98/pc98/olpt.c optional olpt device-driver i386/isa/labpc.c optional labpc device-driver i386/isa/mcd.c optional mcd device-driver pc98/pc98/mse.c optional mse device-driver @@ -196,7 +196,7 @@ i386/isa/pcvt/pcvt_sup.c optional vt device-driver i386/isa/pcvt/pcvt_vtf.c optional vt device-driver i386/isa/pnp.c optional pnp device-driver i386/isa/prof_machdep.c optional profiling-routine -i386/isa/ppc.c optional ppc device-driver +pc98/pc98/ppc.c optional ppc device-driver i386/isa/pcf.c optional pcf device-driver isa/psm.c optional psm device-driver i386/isa/random_machdep.c standard diff --git a/sys/pc98/pc98/lpt.c b/sys/pc98/cbus/olpt.c index b6d71b9..57caf91 100644 --- a/sys/pc98/pc98/lpt.c +++ b/sys/pc98/cbus/olpt.c @@ -46,7 +46,7 @@ * SUCH DAMAGE. * * from: unknown origin, 386BSD 0.1 - * $Id: lpt.c,v 1.27 1999/05/30 16:53:17 phk Exp $ + * $Id$ */ /* @@ -101,9 +101,12 @@ * Poul-Henning Kamp <phk@freebsd.org> */ -#include "lpt.h" +#include "olpt.h" #include "opt_devfs.h" #include "opt_inet.h" +#ifdef PC98 +#undef INET /* PLIP is not supported for old PC-98 */ +#endif #include <sys/param.h> #include <sys/systm.h> @@ -239,7 +242,7 @@ static struct lpt_softc { void *devfs_token; void *devfs_token_ctl; #endif -} lpt_sc[NLPT] ; +} lpt_sc[NOLPT] ; /* bits for state */ #define OPEN (1<<0) /* device is open */ @@ -298,8 +301,8 @@ static int lpoutput(struct ifnet *, struct mbuf *, struct sockaddr *, static void lpintr(int); #endif /* INET */ -struct isa_driver lptdriver = { - lptprobe, lptattach, "lpt" +struct isa_driver olptdriver = { + lptprobe, lptattach, "olpt" }; static d_open_t lptopen; @@ -402,6 +405,22 @@ int lptprobe(struct isa_device *dvp) { #ifdef PC98 +#define PC98_OLD_LPT 0x40 +#define PC98_IEEE_1284_FUNCTION 0x149 + unsigned int pc98_ieee_mode, tmp; + + if (dvp->id_iobase == PC98_OLD_LPT) { + tmp = inb(PC98_IEEE_1284_FUNCTION); + pc98_ieee_mode = tmp; + if ((tmp & 0x10) == 0x10) { + outb(PC98_IEEE_1284_FUNCTION, tmp & ~0x10); + tmp = inb(PC98_IEEE_1284_FUNCTION); + if ((tmp & 0x10) != 0x10) { + outb(PC98_IEEE_1284_FUNCTION, pc98_ieee_mode); + return 0; + } + } + } return 8; #else int port; @@ -521,7 +540,7 @@ lptopen (dev_t dev, int flags, int fmt, struct proc *p) u_int unit = LPTUNIT(minor(dev)); sc = lpt_sc + unit; - if ((unit >= NLPT) || (sc->sc_port == 0)) + if ((unit >= NOLPT) || (sc->sc_port == 0)) return (ENXIO); #ifdef INET diff --git a/sys/pc98/cbus/ppc.c b/sys/pc98/cbus/ppc.c new file mode 100644 index 0000000..e21d841 --- /dev/null +++ b/sys/pc98/cbus/ppc.c @@ -0,0 +1,1846 @@ +/*- + * Copyright (c) 1997, 1998 Nicolas Souchu + * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + * + * $Id$ + * + */ +#include "ppc.h" + +#if NPPC > 0 + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/conf.h> +#include <sys/malloc.h> +#include <sys/kernel.h> + +#include <machine/clock.h> + +#include <vm/vm.h> +#include <vm/vm_param.h> +#include <vm/pmap.h> + +#include <i386/isa/isa_device.h> +#include <pc98/pc98/pc98.h> + +#include <dev/ppbus/ppbconf.h> +#include <dev/ppbus/ppb_msq.h> + +#include <pc98/pc98/ppcreg.h> + +#include "opt_ppc.h" + +#define LOG_PPC(function, ppc, string) \ + if (bootverbose) printf("%s: %s\n", function, string) + +static int ppcprobe(struct isa_device *); +static int ppcattach(struct isa_device *); + +struct isa_driver ppcdriver = { + ppcprobe, ppcattach, "ppc" +}; + +static struct ppc_data *ppcdata[NPPC]; +static int nppc = 0; + +static char *ppc_types[] = { + "SMC-like", "SMC FDC37C665GT", "SMC FDC37C666GT", "PC87332", "PC87306", + "82091AA", "Generic", "W83877F", "W83877AF", "Winbond", "PC87334", 0 +}; + +/* list of available modes */ +static char *ppc_avms[] = { + "COMPATIBLE", "NIBBLE-only", "PS2-only", "PS2/NIBBLE", "EPP-only", + "EPP/NIBBLE", "EPP/PS2", "EPP/PS2/NIBBLE", "ECP-only", + "ECP/NIBBLE", "ECP/PS2", "ECP/PS2/NIBBLE", "ECP/EPP", + "ECP/EPP/NIBBLE", "ECP/EPP/PS2", "ECP/EPP/PS2/NIBBLE", 0 +}; + +/* list of current executing modes + * Note that few modes do not actually exist. + */ +static char *ppc_modes[] = { + "COMPATIBLE", "NIBBLE", "PS/2", "PS/2", "EPP", + "EPP", "EPP", "EPP", "ECP", + "ECP", "ECP+PS2", "ECP+PS2", "ECP+EPP", + "ECP+EPP", "ECP+EPP", "ECP+EPP", 0 +}; + +static char *ppc_epp_protocol[] = { " (EPP 1.9)", " (EPP 1.7)", 0 }; + +/* + * BIOS printer list - used by BIOS probe. + */ +#define BIOS_PPC_PORTS 0x408 +#define BIOS_PORTS (short *)(KERNBASE+BIOS_PPC_PORTS) +#define BIOS_MAX_PPC 4 + +/* + * All these functions are default actions for IN/OUT operations. + * They may be redefined if needed. + */ +static void ppc_outsb_epp(int unit, char *addr, int cnt) { + outsb(ppcdata[unit]->ppc_base + PPC_EPP_DATA, addr, cnt); } +static void ppc_outsw_epp(int unit, char *addr, int cnt) { + outsw(ppcdata[unit]->ppc_base + PPC_EPP_DATA, addr, cnt); } +static void ppc_outsl_epp(int unit, char *addr, int cnt) { + outsl(ppcdata[unit]->ppc_base + PPC_EPP_DATA, addr, cnt); } +static void ppc_insb_epp(int unit, char *addr, int cnt) { + insb(ppcdata[unit]->ppc_base + PPC_EPP_DATA, addr, cnt); } +static void ppc_insw_epp(int unit, char *addr, int cnt) { + insw(ppcdata[unit]->ppc_base + PPC_EPP_DATA, addr, cnt); } +static void ppc_insl_epp(int unit, char *addr, int cnt) { + insl(ppcdata[unit]->ppc_base + PPC_EPP_DATA, addr, cnt); } + +static u_char ppc_rdtr(int unit) { return r_dtr(ppcdata[unit]); } +static u_char ppc_rstr(int unit) { return r_str(ppcdata[unit]); } +static u_char ppc_rctr(int unit) { return r_ctr(ppcdata[unit]); } +static u_char ppc_repp_A(int unit) { return r_epp_A(ppcdata[unit]); } +static u_char ppc_repp_D(int unit) { return r_epp_D(ppcdata[unit]); } +static u_char ppc_recr(int unit) { return r_ecr(ppcdata[unit]); } +static u_char ppc_rfifo(int unit) { return r_fifo(ppcdata[unit]); } + +static void ppc_wdtr(int unit, char byte) { w_dtr(ppcdata[unit], byte); } +static void ppc_wstr(int unit, char byte) { w_str(ppcdata[unit], byte); } +static void ppc_wctr(int unit, char byte) { w_ctr(ppcdata[unit], byte); } +static void ppc_wepp_A(int unit, char byte) { w_epp_A(ppcdata[unit], byte); } +static void ppc_wepp_D(int unit, char byte) { w_epp_D(ppcdata[unit], byte); } +static void ppc_wecr(int unit, char byte) { w_ecr(ppcdata[unit], byte); } +static void ppc_wfifo(int unit, char byte) { w_fifo(ppcdata[unit], byte); } + +static void ppc_reset_epp_timeout(int); +static void ppc_ecp_sync(int); +static ointhand2_t ppcintr; + +static int ppc_exec_microseq(int, struct ppb_microseq **); +static int ppc_generic_setmode(int, int); +static int ppc_smclike_setmode(int, int); + +static int ppc_read(int, char *, int, int); +static int ppc_write(int, char *, int, int); + +static struct ppb_adapter ppc_smclike_adapter = { + + 0, /* no intr handler, filled by chipset dependent code */ + + ppc_reset_epp_timeout, ppc_ecp_sync, + + ppc_exec_microseq, + + ppc_smclike_setmode, ppc_read, ppc_write, + + ppc_outsb_epp, ppc_outsw_epp, ppc_outsl_epp, + ppc_insb_epp, ppc_insw_epp, ppc_insl_epp, + + ppc_rdtr, ppc_rstr, ppc_rctr, ppc_repp_A, ppc_repp_D, ppc_recr, ppc_rfifo, + ppc_wdtr, ppc_wstr, ppc_wctr, ppc_wepp_A, ppc_wepp_D, ppc_wecr, ppc_wfifo +}; + +static struct ppb_adapter ppc_generic_adapter = { + + 0, /* no intr handler, filled by chipset dependent code */ + + ppc_reset_epp_timeout, ppc_ecp_sync, + + ppc_exec_microseq, + + ppc_generic_setmode, ppc_read, ppc_write, + + ppc_outsb_epp, ppc_outsw_epp, ppc_outsl_epp, + ppc_insb_epp, ppc_insw_epp, ppc_insl_epp, + + ppc_rdtr, ppc_rstr, ppc_rctr, ppc_repp_A, ppc_repp_D, ppc_recr, ppc_rfifo, + ppc_wdtr, ppc_wstr, ppc_wctr, ppc_wepp_A, ppc_wepp_D, ppc_wecr, ppc_wfifo +}; + +/* + * ppc_ecp_sync() XXX + */ +static void +ppc_ecp_sync(int unit) { + + struct ppc_data *ppc = ppcdata[unit]; + int i, r; + + if (!(ppc->ppc_avm & PPB_ECP)) + return; + + r = r_ecr(ppc); + if ((r & 0xe0) != PPC_ECR_EPP) + return; + + for (i = 0; i < 100; i++) { + r = r_ecr(ppc); + if (r & 0x1) + return; + DELAY(100); + } + + printf("ppc%d: ECP sync failed as data still " \ + "present in FIFO.\n", unit); + + return; +} + +/* + * ppc_detect_fifo() + * + * Detect parallel port FIFO + */ +static int +ppc_detect_fifo(struct ppc_data *ppc) +{ + char ecr_sav; + char ctr_sav, ctr, cc; + short i; + + /* save registers */ + ecr_sav = r_ecr(ppc); + ctr_sav = r_ctr(ppc); + + /* enter ECP configuration mode, no interrupt, no DMA */ + w_ecr(ppc, 0xf4); + + /* read PWord size - transfers in FIFO mode must be PWord aligned */ + ppc->ppc_pword = (r_cnfgA(ppc) & PPC_PWORD_MASK); + + /* XXX 16 and 32 bits implementations not supported */ + if (ppc->ppc_pword != PPC_PWORD_8) { + LOG_PPC(__FUNCTION__, ppc, "PWord not supported"); + goto error; + } + + w_ecr(ppc, 0x34); /* byte mode, no interrupt, no DMA */ + ctr = r_ctr(ppc); + w_ctr(ppc, ctr | PCD); /* set direction to 1 */ + + /* enter ECP test mode, no interrupt, no DMA */ + w_ecr(ppc, 0xd4); + + /* flush the FIFO */ + for (i=0; i<1024; i++) { + if (r_ecr(ppc) & PPC_FIFO_EMPTY) + break; + cc = r_fifo(ppc); + } + + if (i >= 1024) { + LOG_PPC(__FUNCTION__, ppc, "can't flush FIFO"); + goto error; + } + + /* enable interrupts, no DMA */ + w_ecr(ppc, 0xd0); + + /* determine readIntrThreshold + * fill the FIFO until serviceIntr is set + */ + for (i=0; i<1024; i++) { + w_fifo(ppc, (char)i); + if (!ppc->ppc_rthr && (r_ecr(ppc) & PPC_SERVICE_INTR)) { + /* readThreshold reached */ + ppc->ppc_rthr = i+1; + } + if (r_ecr(ppc) & PPC_FIFO_FULL) { + ppc->ppc_fifo = i+1; + break; + } + } + + if (i >= 1024) { + LOG_PPC(__FUNCTION__, ppc, "can't fill FIFO"); + goto error; + } + + w_ecr(ppc, 0xd4); /* test mode, no interrupt, no DMA */ + w_ctr(ppc, ctr & ~PCD); /* set direction to 0 */ + w_ecr(ppc, 0xd0); /* enable interrupts */ + + /* determine writeIntrThreshold + * empty the FIFO until serviceIntr is set + */ + for (i=ppc->ppc_fifo; i>0; i--) { + if (r_fifo(ppc) != (char)(ppc->ppc_fifo-i)) { + LOG_PPC(__FUNCTION__, ppc, "invalid data in FIFO"); + goto error; + } + if (r_ecr(ppc) & PPC_SERVICE_INTR) { + /* writeIntrThreshold reached */ + ppc->ppc_wthr = ppc->ppc_fifo - i+1; + } + /* if FIFO empty before the last byte, error */ + if (i>1 && (r_ecr(ppc) & PPC_FIFO_EMPTY)) { + LOG_PPC(__FUNCTION__, ppc, "data lost in FIFO"); + goto error; + } + } + + /* FIFO must be empty after the last byte */ + if (!(r_ecr(ppc) & PPC_FIFO_EMPTY)) { + LOG_PPC(__FUNCTION__, ppc, "can't empty the FIFO"); + goto error; + } + + w_ctr(ppc, ctr_sav); + w_ecr(ppc, ecr_sav); + + return (0); + +error: + w_ctr(ppc, ctr_sav); + w_ecr(ppc, ecr_sav); + + return (EINVAL); +} + +static int +ppc_detect_port(struct ppc_data *ppc) +{ + + w_ctr(ppc, 0x0c); /* To avoid missing PS2 ports */ + w_dtr(ppc, 0xaa); + if (r_dtr(ppc) != 0xaa) + return (0); + + return (1); +} + +/* + * ppc_pc873xx_detect + * + * Probe for a Natsemi PC873xx-family part. + * + * References in this function are to the National Semiconductor + * PC87332 datasheet TL/C/11930, May 1995 revision. + */ +static int pc873xx_basetab[] = {0x0398, 0x026e, 0x015c, 0x002e, 0}; +static int pc873xx_porttab[] = {0x0378, 0x03bc, 0x0278, 0}; +static int pc873xx_irqtab[] = {5, 7, 5, 0}; + +static int pc873xx_regstab[] = { + PC873_FER, PC873_FAR, PC873_PTR, + PC873_FCR, PC873_PCR, PC873_PMC, + PC873_TUP, PC873_SID, PC873_PNP0, + PC873_PNP1, PC873_LPTBA, -1 +}; + +static char *pc873xx_rnametab[] = { + "FER", "FAR", "PTR", "FCR", "PCR", + "PMC", "TUP", "SID", "PNP0", "PNP1", + "LPTBA", NULL +}; + +static int +ppc_pc873xx_detect(struct ppc_data *ppc, int chipset_mode) /* XXX mode never forced */ +{ + static int index = 0; + int idport, irq; + int ptr, pcr, val, i; + + while ((idport = pc873xx_basetab[index++])) { + + /* XXX should check first to see if this location is already claimed */ + + /* + * Pull the 873xx through the power-on ID cycle (2.2,1.). + * We can't use this to locate the chip as it may already have + * been used by the BIOS. + */ + (void)inb(idport); (void)inb(idport); + (void)inb(idport); (void)inb(idport); + + /* + * Read the SID byte. Possible values are : + * + * 01010xxx PC87334 + * 0001xxxx PC87332 + * 01110xxx PC87306 + */ + outb(idport, PC873_SID); + val = inb(idport + 1); + if ((val & 0xf0) == 0x10) { + ppc->ppc_type = NS_PC87332; + } else if ((val & 0xf8) == 0x70) { + ppc->ppc_type = NS_PC87306; + } else if ((val & 0xf8) == 0x50) { + ppc->ppc_type = NS_PC87334; + } else { + if (bootverbose && (val != 0xff)) + printf("PC873xx probe at 0x%x got unknown ID 0x%x\n", idport, val); + continue ; /* not recognised */ + } + + /* print registers */ + if (bootverbose) { + printf("PC873xx"); + for (i=0; pc873xx_regstab[i] != -1; i++) { + outb(idport, pc873xx_regstab[i]); + printf(" %s=0x%x", pc873xx_rnametab[i], + inb(idport + 1) & 0xff); + } + printf("\n"); + } + + /* + * We think we have one. Is it enabled and where we want it to be? + */ + outb(idport, PC873_FER); + val = inb(idport + 1); + if (!(val & PC873_PPENABLE)) { + if (bootverbose) + printf("PC873xx parallel port disabled\n"); + continue; + } + outb(idport, PC873_FAR); + val = inb(idport + 1) & 0x3; + /* XXX we should create a driver instance for every port found */ + if (pc873xx_porttab[val] != ppc->ppc_base) { + if (bootverbose) + printf("PC873xx at 0x%x not for driver at port 0x%x\n", + pc873xx_porttab[val], ppc->ppc_base); + continue; + } + + outb(idport, PC873_PTR); + ptr = inb(idport + 1); + + /* get irq settings */ + if (ppc->ppc_base == 0x378) + irq = (ptr & PC873_LPTBIRQ7) ? 7 : 5; + else + irq = pc873xx_irqtab[val]; + + if (bootverbose) + printf("PC873xx irq %d at 0x%x\n", irq, ppc->ppc_base); + + /* + * Check if irq settings are correct + */ + if (irq != ppc->ppc_irq) { + /* + * If the chipset is not locked and base address is 0x378, + * we have another chance + */ + if (ppc->ppc_base == 0x378 && !(ptr & PC873_CFGLOCK)) { + if (ppc->ppc_irq == 7) { + outb(idport + 1, (ptr | PC873_LPTBIRQ7)); + outb(idport + 1, (ptr | PC873_LPTBIRQ7)); + } else { + outb(idport + 1, (ptr & ~PC873_LPTBIRQ7)); + outb(idport + 1, (ptr & ~PC873_LPTBIRQ7)); + } + if (bootverbose) + printf("PC873xx irq set to %d\n", ppc->ppc_irq); + } else { + if (bootverbose) + printf("PC873xx sorry, can't change irq setting\n"); + } + } else { + if (bootverbose) + printf("PC873xx irq settings are correct\n"); + } + + outb(idport, PC873_PCR); + pcr = inb(idport + 1); + + if ((ptr & PC873_CFGLOCK) || !chipset_mode) { + if (bootverbose) + printf("PC873xx %s", (ptr & PC873_CFGLOCK)?"locked":"unlocked"); + + ppc->ppc_avm |= PPB_NIBBLE; + if (bootverbose) + printf(", NIBBLE"); + + if (pcr & PC873_EPPEN) { + ppc->ppc_avm |= PPB_EPP; + + if (bootverbose) + printf(", EPP"); + + if (pcr & PC873_EPP19) + ppc->ppc_epp = EPP_1_9; + else + ppc->ppc_epp = EPP_1_7; + + if ((ppc->ppc_type == NS_PC87332) && bootverbose) { + outb(idport, PC873_PTR); + ptr = inb(idport + 1); + if (ptr & PC873_EPPRDIR) + printf(", Regular mode"); + else + printf(", Automatic mode"); + } + } else if (pcr & PC873_ECPEN) { + ppc->ppc_avm |= PPB_ECP; + if (bootverbose) + printf(", ECP"); + + if (pcr & PC873_ECPCLK) { /* XXX */ + ppc->ppc_avm |= PPB_PS2; + if (bootverbose) + printf(", PS/2"); + } + } else { + outb(idport, PC873_PTR); + ptr = inb(idport + 1); + if (ptr & PC873_EXTENDED) { + ppc->ppc_avm |= PPB_SPP; + if (bootverbose) + printf(", SPP"); + } + } + } else { + if (bootverbose) + printf("PC873xx unlocked"); + + if (chipset_mode & PPB_ECP) { + if ((chipset_mode & PPB_EPP) && bootverbose) + printf(", ECP+EPP not supported"); + + pcr &= ~PC873_EPPEN; + pcr |= (PC873_ECPEN | PC873_ECPCLK); /* XXX */ + outb(idport + 1, pcr); + outb(idport + 1, pcr); + + if (bootverbose) + printf(", ECP"); + + } else if (chipset_mode & PPB_EPP) { + pcr &= ~(PC873_ECPEN | PC873_ECPCLK); + pcr |= (PC873_EPPEN | PC873_EPP19); + outb(idport + 1, pcr); + outb(idport + 1, pcr); + + ppc->ppc_epp = EPP_1_9; /* XXX */ + + if (bootverbose) + printf(", EPP1.9"); + + /* enable automatic direction turnover */ + if (ppc->ppc_type == NS_PC87332) { + outb(idport, PC873_PTR); + ptr = inb(idport + 1); + ptr &= ~PC873_EPPRDIR; + outb(idport + 1, ptr); + outb(idport + 1, ptr); + + if (bootverbose) + printf(", Automatic mode"); + } + } else { + pcr &= ~(PC873_ECPEN | PC873_ECPCLK | PC873_EPPEN); + outb(idport + 1, pcr); + outb(idport + 1, pcr); + + /* configure extended bit in PTR */ + outb(idport, PC873_PTR); + ptr = inb(idport + 1); + + if (chipset_mode & PPB_PS2) { + ptr |= PC873_EXTENDED; + + if (bootverbose) + printf(", PS/2"); + + } else { + /* default to NIBBLE mode */ + ptr &= ~PC873_EXTENDED; + + if (bootverbose) + printf(", NIBBLE"); + } + outb(idport + 1, ptr); + outb(idport + 1, ptr); + } + + ppc->ppc_avm = chipset_mode; + } + + if (bootverbose) + printf("\n"); + + ppc->ppc_link.adapter = &ppc_generic_adapter; + ppc_generic_setmode(ppc->ppc_unit, chipset_mode); + + return(chipset_mode); + } + return(-1); +} + +static int +ppc_check_epp_timeout(struct ppc_data *ppc) +{ + ppc_reset_epp_timeout(ppc->ppc_unit); + + return (!(r_str(ppc) & TIMEOUT)); +} + +/* + * ppc_smc37c66xgt_detect + * + * SMC FDC37C66xGT configuration. + */ +static int +ppc_smc37c66xgt_detect(struct ppc_data *ppc, int chipset_mode) +{ + int s, i; + u_char r; + int type = -1; + int csr = SMC66x_CSR; /* initial value is 0x3F0 */ + + int port_address[] = { -1 /* disabled */ , 0x3bc, 0x378, 0x278 }; + + +#define cio csr+1 /* config IO port is either 0x3F1 or 0x371 */ + + /* + * Detection: enter configuration mode and read CRD register. + */ + + s = splhigh(); + outb(csr, SMC665_iCODE); + outb(csr, SMC665_iCODE); + splx(s); + + outb(csr, 0xd); + if (inb(cio) == 0x65) { + type = SMC_37C665GT; + goto config; + } + + for (i = 0; i < 2; i++) { + s = splhigh(); + outb(csr, SMC666_iCODE); + outb(csr, SMC666_iCODE); + splx(s); + + outb(csr, 0xd); + if (inb(cio) == 0x66) { + type = SMC_37C666GT; + break; + } + + /* Another chance, CSR may be hard-configured to be at 0x370 */ + csr = SMC666_CSR; + } + +config: + /* + * If chipset not found, do not continue. + */ + if (type == -1) + return (-1); + + /* select CR1 */ + outb(csr, 0x1); + + /* read the port's address: bits 0 and 1 of CR1 */ + r = inb(cio) & SMC_CR1_ADDR; + if (port_address[(int)r] != ppc->ppc_base) + return (-1); + + ppc->ppc_type = type; + + /* + * CR1 and CR4 registers bits 3 and 0/1 for mode configuration + * If SPP mode is detected, try to set ECP+EPP mode + */ + + if (bootverbose) { + outb(csr, 0x1); + printf("ppc%d: SMC registers CR1=0x%x", ppc->ppc_unit, + inb(cio) & 0xff); + + outb(csr, 0x4); + printf(" CR4=0x%x", inb(cio) & 0xff); + } + + /* select CR1 */ + outb(csr, 0x1); + + if (!chipset_mode) { + /* autodetect mode */ + + /* 666GT is ~certainly~ hardwired to an extended ECP+EPP mode */ + if (type == SMC_37C666GT) { + ppc->ppc_avm |= PPB_ECP | PPB_EPP | PPB_SPP; + if (bootverbose) + printf(" configuration hardwired, supposing " \ + "ECP+EPP SPP"); + + } else + if ((inb(cio) & SMC_CR1_MODE) == 0) { + /* already in extended parallel port mode, read CR4 */ + outb(csr, 0x4); + r = (inb(cio) & SMC_CR4_EMODE); + + switch (r) { + case SMC_SPP: + ppc->ppc_avm |= PPB_SPP; + if (bootverbose) + printf(" SPP"); + break; + + case SMC_EPPSPP: + ppc->ppc_avm |= PPB_EPP | PPB_SPP; + if (bootverbose) + printf(" EPP SPP"); + break; + + case SMC_ECP: + ppc->ppc_avm |= PPB_ECP | PPB_SPP; + if (bootverbose) + printf(" ECP SPP"); + break; + + case SMC_ECPEPP: + ppc->ppc_avm |= PPB_ECP | PPB_EPP | PPB_SPP; + if (bootverbose) + printf(" ECP+EPP SPP"); + break; + } + } else { + /* not an extended port mode */ + ppc->ppc_avm |= PPB_SPP; + if (bootverbose) + printf(" SPP"); + } + + } else { + /* mode forced */ + ppc->ppc_avm = chipset_mode; + + /* 666GT is ~certainly~ hardwired to an extended ECP+EPP mode */ + if (type == SMC_37C666GT) + goto end_detect; + + r = inb(cio); + if ((chipset_mode & (PPB_ECP | PPB_EPP)) == 0) { + /* do not use ECP when the mode is not forced to */ + outb(cio, r | SMC_CR1_MODE); + if (bootverbose) + printf(" SPP"); + } else { + /* an extended mode is selected */ + outb(cio, r & ~SMC_CR1_MODE); + + /* read CR4 register and reset mode field */ + outb(csr, 0x4); + r = inb(cio) & ~SMC_CR4_EMODE; + + if (chipset_mode & PPB_ECP) { + if (chipset_mode & PPB_EPP) { + outb(cio, r | SMC_ECPEPP); + if (bootverbose) + printf(" ECP+EPP"); + } else { + outb(cio, r | SMC_ECP); + if (bootverbose) + printf(" ECP"); + } + } else { + /* PPB_EPP is set */ + outb(cio, r | SMC_EPPSPP); + if (bootverbose) + printf(" EPP SPP"); + } + } + ppc->ppc_avm = chipset_mode; + } + + /* set FIFO threshold to 16 */ + if (ppc->ppc_avm & PPB_ECP) { + /* select CRA */ + outb(csr, 0xa); + outb(cio, 16); + } + +end_detect: + + if (bootverbose) + printf ("\n"); + + if (ppc->ppc_avm & PPB_EPP) { + /* select CR4 */ + outb(csr, 0x4); + r = inb(cio); + + /* + * Set the EPP protocol... + * Low=EPP 1.9 (1284 standard) and High=EPP 1.7 + */ + if (ppc->ppc_epp == EPP_1_9) + outb(cio, (r & ~SMC_CR4_EPPTYPE)); + else + outb(cio, (r | SMC_CR4_EPPTYPE)); + } + + /* end config mode */ + outb(csr, 0xaa); + + ppc->ppc_link.adapter = &ppc_smclike_adapter; + ppc_smclike_setmode(ppc->ppc_unit, chipset_mode); + + return (chipset_mode); +} + +/* + * Winbond W83877F stuff + * + * EFER: extended function enable register + * EFIR: extended function index register + * EFDR: extended function data register + */ +#define efir ((efer == 0x250) ? 0x251 : 0x3f0) +#define efdr ((efer == 0x250) ? 0x252 : 0x3f1) + +static int w83877f_efers[] = { 0x250, 0x3f0, 0x3f0, 0x250 }; +static int w83877f_keys[] = { 0x89, 0x86, 0x87, 0x88 }; +static int w83877f_keyiter[] = { 1, 2, 2, 1 }; +static int w83877f_hefs[] = { WINB_HEFERE, WINB_HEFRAS, WINB_HEFERE | WINB_HEFRAS, 0 }; + +static int +ppc_w83877f_detect(struct ppc_data *ppc, int chipset_mode) +{ + int i, j, efer; + unsigned char r, hefere, hefras; + + for (i = 0; i < 4; i ++) { + /* first try to enable configuration registers */ + efer = w83877f_efers[i]; + + /* write the key to the EFER */ + for (j = 0; j < w83877f_keyiter[i]; j ++) + outb (efer, w83877f_keys[i]); + + /* then check HEFERE and HEFRAS bits */ + outb (efir, 0x0c); + hefere = inb(efdr) & WINB_HEFERE; + + outb (efir, 0x16); + hefras = inb(efdr) & WINB_HEFRAS; + + /* + * HEFRAS HEFERE + * 0 1 write 89h to 250h (power-on default) + * 1 0 write 86h twice to 3f0h + * 1 1 write 87h twice to 3f0h + * 0 0 write 88h to 250h + */ + if ((hefere | hefras) == w83877f_hefs[i]) + goto found; + } + + return (-1); /* failed */ + +found: + /* check base port address - read from CR23 */ + outb(efir, 0x23); + if (ppc->ppc_base != inb(efdr) * 4) /* 4 bytes boundaries */ + return (-1); + + /* read CHIP ID from CR9/bits0-3 */ + outb(efir, 0x9); + + switch (inb(efdr) & WINB_CHIPID) { + case WINB_W83877F_ID: + ppc->ppc_type = WINB_W83877F; + break; + + case WINB_W83877AF_ID: + ppc->ppc_type = WINB_W83877AF; + break; + + default: + ppc->ppc_type = WINB_UNKNOWN; + } + + if (bootverbose) { + /* dump of registers */ + printf("ppc%d: 0x%x - ", ppc->ppc_unit, w83877f_keys[i]); + for (i = 0; i <= 0xd; i ++) { + outb(efir, i); + printf("0x%x ", inb(efdr)); + } + for (i = 0x10; i <= 0x17; i ++) { + outb(efir, i); + printf("0x%x ", inb(efdr)); + } + outb(efir, 0x1e); + printf("0x%x ", inb(efdr)); + for (i = 0x20; i <= 0x29; i ++) { + outb(efir, i); + printf("0x%x ", inb(efdr)); + } + printf("\n"); + printf("ppc%d:", ppc->ppc_unit); + } + + ppc->ppc_link.adapter = &ppc_generic_adapter; + + if (!chipset_mode) { + /* autodetect mode */ + + /* select CR0 */ + outb(efir, 0x0); + r = inb(efdr) & (WINB_PRTMODS0 | WINB_PRTMODS1); + + /* select CR9 */ + outb(efir, 0x9); + r |= (inb(efdr) & WINB_PRTMODS2); + + switch (r) { + case WINB_W83757: + if (bootverbose) + printf("ppc%d: W83757 compatible mode\n", + ppc->ppc_unit); + return (-1); /* generic or SMC-like */ + + case WINB_EXTFDC: + case WINB_EXTADP: + case WINB_EXT2FDD: + case WINB_JOYSTICK: + if (bootverbose) + printf(" not in parallel port mode\n"); + return (-1); + + case (WINB_PARALLEL | WINB_EPP_SPP): + ppc->ppc_avm |= PPB_EPP | PPB_SPP; + if (bootverbose) + printf(" EPP SPP"); + break; + + case (WINB_PARALLEL | WINB_ECP): + ppc->ppc_avm |= PPB_ECP | PPB_SPP; + if (bootverbose) + printf(" ECP SPP"); + break; + + case (WINB_PARALLEL | WINB_ECP_EPP): + ppc->ppc_avm |= PPB_ECP | PPB_EPP | PPB_SPP; + ppc->ppc_link.adapter = &ppc_smclike_adapter; + + if (bootverbose) + printf(" ECP+EPP SPP"); + break; + default: + printf("%s: unknown case (0x%x)!\n", __FUNCTION__, r); + } + + } else { + /* mode forced */ + + /* select CR9 and set PRTMODS2 bit */ + outb(efir, 0x9); + outb(efdr, inb(efdr) & ~WINB_PRTMODS2); + + /* select CR0 and reset PRTMODSx bits */ + outb(efir, 0x0); + outb(efdr, inb(efdr) & ~(WINB_PRTMODS0 | WINB_PRTMODS1)); + + if (chipset_mode & PPB_ECP) { + if (chipset_mode & PPB_EPP) { + outb(efdr, inb(efdr) | WINB_ECP_EPP); + if (bootverbose) + printf(" ECP+EPP"); + + ppc->ppc_link.adapter = &ppc_smclike_adapter; + + } else { + outb(efdr, inb(efdr) | WINB_ECP); + if (bootverbose) + printf(" ECP"); + } + } else { + /* select EPP_SPP otherwise */ + outb(efdr, inb(efdr) | WINB_EPP_SPP); + if (bootverbose) + printf(" EPP SPP"); + } + ppc->ppc_avm = chipset_mode; + } + + if (bootverbose) + printf("\n"); + + /* exit configuration mode */ + outb(efer, 0xaa); + + ppc->ppc_link.adapter->setmode(ppc->ppc_unit, chipset_mode); + + return (chipset_mode); +} + +/* + * ppc_generic_detect + */ +static int +ppc_generic_detect(struct ppc_data *ppc, int chipset_mode) +{ + /* default to generic */ + ppc->ppc_link.adapter = &ppc_generic_adapter; + + if (bootverbose) + printf("ppc%d:", ppc->ppc_unit); + + if (!chipset_mode) { + /* first, check for ECP */ + w_ecr(ppc, PPC_ECR_PS2); + if ((r_ecr(ppc) & 0xe0) == PPC_ECR_PS2) { + ppc->ppc_avm |= PPB_ECP | PPB_SPP; + if (bootverbose) + printf(" ECP SPP"); + + /* search for SMC style ECP+EPP mode */ + w_ecr(ppc, PPC_ECR_EPP); + } + + /* try to reset EPP timeout bit */ + if (ppc_check_epp_timeout(ppc)) { + ppc->ppc_avm |= PPB_EPP; + + if (ppc->ppc_avm & PPB_ECP) { + /* SMC like chipset found */ + ppc->ppc_type = SMC_LIKE; + ppc->ppc_link.adapter = &ppc_smclike_adapter; + + if (bootverbose) + printf(" ECP+EPP"); + } else { + if (bootverbose) + printf(" EPP"); + } + } else { + /* restore to standard mode */ + w_ecr(ppc, PPC_ECR_STD); + } + + /* XXX try to detect NIBBLE and PS2 modes */ + ppc->ppc_avm |= PPB_NIBBLE; + + if (bootverbose) + printf(" SPP"); + + } else { + ppc->ppc_avm = chipset_mode; + } + + if (bootverbose) + printf("\n"); + + ppc->ppc_link.adapter->setmode(ppc->ppc_unit, chipset_mode); + + return (chipset_mode); +} + +/* + * ppc_detect() + * + * mode is the mode suggested at boot + */ +static int +ppc_detect(struct ppc_data *ppc, int chipset_mode) { + + int i, mode; + + /* list of supported chipsets */ + int (*chipset_detect[])(struct ppc_data *, int) = { + ppc_pc873xx_detect, + ppc_smc37c66xgt_detect, + ppc_w83877f_detect, + ppc_generic_detect, + NULL + }; + + /* if can't find the port and mode not forced return error */ + if (!ppc_detect_port(ppc) && chipset_mode == 0) + return (EIO); /* failed, port not present */ + + /* assume centronics compatible mode is supported */ + ppc->ppc_avm = PPB_COMPATIBLE; + + /* we have to differenciate available chipset modes, + * chipset running modes and IEEE-1284 operating modes + * + * after detection, the port must support running in compatible mode + */ + if (ppc->ppc_flags & 0x40) { + if (bootverbose) + printf("ppc: chipset forced to generic\n"); + + ppc->ppc_mode = ppc_generic_detect(ppc, chipset_mode); + + } else { + for (i=0; chipset_detect[i] != NULL; i++) { + if ((mode = chipset_detect[i](ppc, chipset_mode)) != -1) { + ppc->ppc_mode = mode; + break; + } + } + } + + /* configure/detect ECP FIFO */ + if ((ppc->ppc_avm & PPB_ECP) && !(ppc->ppc_flags & 0x80)) + ppc_detect_fifo(ppc); + + return (0); +} + +/* + * ppc_exec_microseq() + * + * Execute a microsequence. + * Microsequence mechanism is supposed to handle fast I/O operations. + */ +static int +ppc_exec_microseq(int unit, struct ppb_microseq **p_msq) +{ + struct ppc_data *ppc = ppcdata[unit]; + struct ppb_microseq *mi; + char cc, *p; + int i, iter, len; + int error; + + register int reg; + register char mask; + register int accum = 0; + register char *ptr = 0; + + struct ppb_microseq *stack = 0; + +/* microsequence registers are equivalent to PC-like port registers */ +#define r_reg(register,ppc) (inb((ppc)->ppc_base + register)) +#define w_reg(register,ppc,byte) outb((ppc)->ppc_base + register, byte) + +#define INCR_PC (mi ++) /* increment program counter */ + + mi = *p_msq; + for (;;) { + switch (mi->opcode) { + case MS_OP_RSET: + cc = r_reg(mi->arg[0].i, ppc); + cc &= (char)mi->arg[2].i; /* clear mask */ + cc |= (char)mi->arg[1].i; /* assert mask */ + w_reg(mi->arg[0].i, ppc, cc); + INCR_PC; + break; + + case MS_OP_RASSERT_P: + reg = mi->arg[1].i; + ptr = ppc->ppc_ptr; + + if ((len = mi->arg[0].i) == MS_ACCUM) { + accum = ppc->ppc_accum; + for (; accum; accum--) + w_reg(reg, ppc, *ptr++); + ppc->ppc_accum = accum; + } else + for (i=0; i<len; i++) + w_reg(reg, ppc, *ptr++); + ppc->ppc_ptr = ptr; + + INCR_PC; + break; + + case MS_OP_RFETCH_P: + reg = mi->arg[1].i; + mask = (char)mi->arg[2].i; + ptr = ppc->ppc_ptr; + + if ((len = mi->arg[0].i) == MS_ACCUM) { + accum = ppc->ppc_accum; + for (; accum; accum--) + *ptr++ = r_reg(reg, ppc) & mask; + ppc->ppc_accum = accum; + } else + for (i=0; i<len; i++) + *ptr++ = r_reg(reg, ppc) & mask; + ppc->ppc_ptr = ptr; + + INCR_PC; + break; + + case MS_OP_RFETCH: + *((char *) mi->arg[2].p) = r_reg(mi->arg[0].i, ppc) & + (char)mi->arg[1].i; + INCR_PC; + break; + + case MS_OP_RASSERT: + case MS_OP_DELAY: + + /* let's suppose the next instr. is the same */ + prefetch: + for (;mi->opcode == MS_OP_RASSERT; INCR_PC) + w_reg(mi->arg[0].i, ppc, (char)mi->arg[1].i); + + if (mi->opcode == MS_OP_DELAY) { + DELAY(mi->arg[0].i); + INCR_PC; + goto prefetch; + } + break; + + case MS_OP_ADELAY: + if (mi->arg[0].i) + tsleep(NULL, PPBPRI, "ppbdelay", + mi->arg[0].i * (hz/1000)); + INCR_PC; + break; + + case MS_OP_TRIG: + reg = mi->arg[0].i; + iter = mi->arg[1].i; + p = (char *)mi->arg[2].p; + + /* XXX delay limited to 255 us */ + for (i=0; i<iter; i++) { + w_reg(reg, ppc, *p++); + DELAY((unsigned char)*p++); + } + INCR_PC; + break; + + case MS_OP_SET: + ppc->ppc_accum = mi->arg[0].i; + INCR_PC; + break; + + case MS_OP_DBRA: + if (--ppc->ppc_accum > 0) + mi += mi->arg[0].i; + INCR_PC; + break; + + case MS_OP_BRSET: + cc = r_str(ppc); + if ((cc & (char)mi->arg[0].i) == (char)mi->arg[0].i) + mi += mi->arg[1].i; + INCR_PC; + break; + + case MS_OP_BRCLEAR: + cc = r_str(ppc); + if ((cc & (char)mi->arg[0].i) == 0) + mi += mi->arg[1].i; + INCR_PC; + break; + + case MS_OP_BRSTAT: + cc = r_str(ppc); + if ((cc & ((char)mi->arg[0].i | (char)mi->arg[1].i)) == + (char)mi->arg[0].i) + mi += mi->arg[2].i; + INCR_PC; + break; + + case MS_OP_C_CALL: + /* + * If the C call returns !0 then end the microseq. + * The current state of ptr is passed to the C function + */ + if ((error = mi->arg[0].f(mi->arg[1].p, ppc->ppc_ptr))) + return (error); + + INCR_PC; + break; + + case MS_OP_PTR: + ppc->ppc_ptr = (char *)mi->arg[0].p; + INCR_PC; + break; + + case MS_OP_CALL: + if (stack) + panic("%s: too much calls", __FUNCTION__); + + if (mi->arg[0].p) { + /* store the state of the actual + * microsequence + */ + stack = mi; + + /* jump to the new microsequence */ + mi = (struct ppb_microseq *)mi->arg[0].p; + } else + INCR_PC; + + break; + + case MS_OP_SUBRET: + /* retrieve microseq and pc state before the call */ + mi = stack; + + /* reset the stack */ + stack = 0; + + /* XXX return code */ + + INCR_PC; + break; + + case MS_OP_PUT: + case MS_OP_GET: + case MS_OP_RET: + /* can't return to ppb level during the execution + * of a submicrosequence */ + if (stack) + panic("%s: can't return to ppb level", + __FUNCTION__); + + /* update pc for ppb level of execution */ + *p_msq = mi; + + /* return to ppb level of execution */ + return (0); + + default: + panic("%s: unknown microsequence opcode 0x%x", + __FUNCTION__, mi->opcode); + } + } + + /* unreached */ +} + +static void +ppcintr(int unit) +{ + struct ppc_data *ppc = ppcdata[unit]; + u_char ctr, ecr, str; + + str = r_str(ppc); + ctr = r_ctr(ppc); + ecr = r_ecr(ppc); + +#if PPC_DEBUG > 1 + printf("![%x/%x/%x]", ctr, ecr, str); +#endif + + /* don't use ecp mode with IRQENABLE set */ + if (ctr & IRQENABLE) { + /* call upper code */ + ppb_intr(&ppc->ppc_link); + return; + } + + /* interrupts are generated by nFault signal + * only in ECP mode */ + if ((str & nFAULT) && (ppc->ppc_mode & PPB_ECP)) { + /* check if ppc driver has programmed the + * nFault interrupt */ + if (ppc->ppc_irqstat & PPC_IRQ_nFAULT) { + + w_ecr(ppc, ecr | PPC_nFAULT_INTR); + ppc->ppc_irqstat &= ~PPC_IRQ_nFAULT; + } else { + /* call upper code */ + ppb_intr(&ppc->ppc_link); + return; + } + } + + if (ppc->ppc_irqstat & PPC_IRQ_DMA) { + /* disable interrupts (should be done by hardware though) */ + w_ecr(ppc, ecr | PPC_SERVICE_INTR); + ppc->ppc_irqstat &= ~PPC_IRQ_DMA; + ecr = r_ecr(ppc); + + /* check if DMA completed */ + if ((ppc->ppc_avm & PPB_ECP) && (ecr & PPC_ENABLE_DMA)) { +#ifdef PPC_DEBUG + printf("a"); +#endif + /* stop DMA */ + w_ecr(ppc, ecr & ~PPC_ENABLE_DMA); + ecr = r_ecr(ppc); + + if (ppc->ppc_dmastat == PPC_DMA_STARTED) { +#ifdef PPC_DEBUG + printf("d"); +#endif + isa_dmadone( + ppc->ppc_dmaflags, + ppc->ppc_dmaddr, + ppc->ppc_dmacnt, + ppc->ppc_dmachan); + + ppc->ppc_dmastat = PPC_DMA_COMPLETE; + + /* wakeup the waiting process */ + wakeup((caddr_t)ppc); + } + } + } else if (ppc->ppc_irqstat & PPC_IRQ_FIFO) { + + /* classic interrupt I/O */ + ppc->ppc_irqstat &= ~PPC_IRQ_FIFO; + + } + + return; +} + +static int +ppc_read(int unit, char *buf, int len, int mode) +{ + return (EINVAL); +} + +/* + * Call this function if you want to send data in any advanced mode + * of your parallel port: FIFO, DMA + * + * If what you want is not possible (no ECP, no DMA...), + * EINVAL is returned + */ +static int +ppc_write(int unit, char *buf, int len, int how) +{ + struct ppc_data *ppc = ppcdata[unit]; + char ecr, ecr_sav, ctr, ctr_sav; + int s, error = 0; + int spin; + +#ifdef PPC_DEBUG + printf("w"); +#endif + + ecr_sav = r_ecr(ppc); + ctr_sav = r_ctr(ppc); + + /* + * Send buffer with DMA, FIFO and interrupts + */ + if (ppc->ppc_avm & PPB_ECP) { + + if (ppc->ppc_dmachan >= 0) { + + /* byte mode, no intr, no DMA, dir=0, flush fifo + */ + ecr = PPC_ECR_STD | PPC_DISABLE_INTR; + w_ecr(ppc, ecr); + + /* disable nAck interrupts */ + ctr = r_ctr(ppc); + ctr &= ~IRQENABLE; + w_ctr(ppc, ctr); + + ppc->ppc_dmaflags = 0; + ppc->ppc_dmaddr = (caddr_t)buf; + ppc->ppc_dmacnt = (u_int)len; + + switch (ppc->ppc_mode) { + case PPB_COMPATIBLE: + /* compatible mode with FIFO, no intr, DMA, dir=0 */ + ecr = PPC_ECR_FIFO | PPC_DISABLE_INTR | PPC_ENABLE_DMA; + break; + case PPB_ECP: + ecr = PPC_ECR_ECP | PPC_DISABLE_INTR | PPC_ENABLE_DMA; + break; + default: + error = EINVAL; + goto error; + } + + w_ecr(ppc, ecr); + ecr = r_ecr(ppc); + + /* enter splhigh() not to be preempted + * by the dma interrupt, we may miss + * the wakeup otherwise + */ + s = splhigh(); + + ppc->ppc_dmastat = PPC_DMA_INIT; + + /* enable interrupts */ + ecr &= ~PPC_SERVICE_INTR; + ppc->ppc_irqstat = PPC_IRQ_DMA; + w_ecr(ppc, ecr); + + isa_dmastart( + ppc->ppc_dmaflags, + ppc->ppc_dmaddr, + ppc->ppc_dmacnt, + ppc->ppc_dmachan); +#ifdef PPC_DEBUG + printf("s%d", ppc->ppc_dmacnt); +#endif + ppc->ppc_dmastat = PPC_DMA_STARTED; + + /* Wait for the DMA completed interrupt. We hope we won't + * miss it, otherwise a signal will be necessary to unlock the + * process. + */ + do { + /* release CPU */ + error = tsleep((caddr_t)ppc, + PPBPRI | PCATCH, "ppcdma", 0); + + } while (error == EWOULDBLOCK); + + splx(s); + + if (error) { +#ifdef PPC_DEBUG + printf("i"); +#endif + /* stop DMA */ + isa_dmadone( + ppc->ppc_dmaflags, ppc->ppc_dmaddr, + ppc->ppc_dmacnt, ppc->ppc_dmachan); + + /* no dma, no interrupt, flush the fifo */ + w_ecr(ppc, PPC_ECR_RESET); + + ppc->ppc_dmastat = PPC_DMA_INTERRUPTED; + goto error; + } + + /* wait for an empty fifo */ + while (!(r_ecr(ppc) & PPC_FIFO_EMPTY)) { + + for (spin=100; spin; spin--) + if (r_ecr(ppc) & PPC_FIFO_EMPTY) + goto fifo_empty; +#ifdef PPC_DEBUG + printf("Z"); +#endif + error = tsleep((caddr_t)ppc, PPBPRI | PCATCH, "ppcfifo", hz/100); + if (error != EWOULDBLOCK) { +#ifdef PPC_DEBUG + printf("I"); +#endif + /* no dma, no interrupt, flush the fifo */ + w_ecr(ppc, PPC_ECR_RESET); + + ppc->ppc_dmastat = PPC_DMA_INTERRUPTED; + error = EINTR; + goto error; + } + } + +fifo_empty: + /* no dma, no interrupt, flush the fifo */ + w_ecr(ppc, PPC_ECR_RESET); + + } else + error = EINVAL; /* XXX we should FIFO and + * interrupts */ + } else + error = EINVAL; + +error: + + /* PDRQ must be kept unasserted until nPDACK is + * deasserted for a minimum of 350ns (SMC datasheet) + * + * Consequence may be a FIFO that never empty + */ + DELAY(1); + + w_ecr(ppc, ecr_sav); + w_ctr(ppc, ctr_sav); + + return (error); +} + +/* + * Configure current operating mode + */ +static int +ppc_generic_setmode(int unit, int mode) +{ + struct ppc_data *ppc = ppcdata[unit]; + u_char ecr = 0; + + /* check if mode is available */ + if (mode && !(ppc->ppc_avm & mode)) + return (EINVAL); + + /* if ECP mode, configure ecr register */ + if (ppc->ppc_avm & PPB_ECP) { + /* return to byte mode (keeping direction bit), + * no interrupt, no DMA to be able to change to + * ECP + */ + w_ecr(ppc, PPC_ECR_RESET); + ecr = PPC_DISABLE_INTR; + + if (mode & PPB_EPP) + return (EINVAL); + else if (mode & PPB_ECP) + /* select ECP mode */ + ecr |= PPC_ECR_ECP; + else if (mode & PPB_PS2) + /* select PS2 mode with ECP */ + ecr |= PPC_ECR_PS2; + else + /* select COMPATIBLE/NIBBLE mode */ + ecr |= PPC_ECR_STD; + + w_ecr(ppc, ecr); + } + + ppc->ppc_mode = mode; + + return (0); +} + +/* + * The ppc driver is free to choose options like FIFO or DMA + * if ECP mode is available. + * + * The 'RAW' option allows the upper drivers to force the ppc mode + * even with FIFO, DMA available. + */ +int +ppc_smclike_setmode(int unit, int mode) +{ + struct ppc_data *ppc = ppcdata[unit]; + u_char ecr = 0; + + /* check if mode is available */ + if (mode && !(ppc->ppc_avm & mode)) + return (EINVAL); + + /* if ECP mode, configure ecr register */ + if (ppc->ppc_avm & PPB_ECP) { + /* return to byte mode (keeping direction bit), + * no interrupt, no DMA to be able to change to + * ECP or EPP mode + */ + w_ecr(ppc, PPC_ECR_RESET); + ecr = PPC_DISABLE_INTR; + + if (mode & PPB_EPP) + /* select EPP mode */ + ecr |= PPC_ECR_EPP; + else if (mode & PPB_ECP) + /* select ECP mode */ + ecr |= PPC_ECR_ECP; + else if (mode & PPB_PS2) + /* select PS2 mode with ECP */ + ecr |= PPC_ECR_PS2; + else + /* select COMPATIBLE/NIBBLE mode */ + ecr |= PPC_ECR_STD; + + w_ecr(ppc, ecr); + } + + ppc->ppc_mode = mode; + + return (0); +} + +/* + * EPP timeout, according to the PC87332 manual + * Semantics of clearing EPP timeout bit. + * PC87332 - reading SPP_STR does it... + * SMC - write 1 to EPP timeout bit XXX + * Others - (?) write 0 to EPP timeout bit + */ +static void +ppc_reset_epp_timeout(int unit) +{ + struct ppc_data *ppc = ppcdata[unit]; + register char r; + + r = r_str(ppc); + w_str(ppc, r | 0x1); + w_str(ppc, r & 0xfe); + + return; +} + +static int +ppcprobe(struct isa_device *dvp) +{ + static short next_bios_ppc = 0; + struct ppc_data *ppc; +#ifdef PC98 +#define PC98_IEEE_1284_DISABLE 0x100 +#define PC98_IEEE_1284_PORT 0x140 + + unsigned int pc98_ieee_mode = 0x00; + unsigned int tmp; +#endif + + /* + * If port not specified, use bios list. + */ + if(dvp->id_iobase < 0) { +#ifndef PC98 + if((next_bios_ppc < BIOS_MAX_PPC) && + (*(BIOS_PORTS+next_bios_ppc) != 0) ) { + dvp->id_iobase = *(BIOS_PORTS+next_bios_ppc++); + if (bootverbose) + printf("ppc: parallel port found at 0x%x\n", + dvp->id_iobase); + } else +#else + if(next_bios_ppc == 0) { + /* Use default IEEE-1284 port of NEC PC-98x1 */ + dvp->id_iobase = PC98_IEEE_1284_PORT; + next_bios_ppc += 1; + if (bootverbose) + printf("ppc: parallel port found at 0x%x\n", + dvp->id_iobase); + } else +#endif + return (0); + } + + /* + * Port was explicitly specified. + * This allows probing of ports unknown to the BIOS. + */ + + /* + * Allocate the ppc_data structure. + */ + ppc = malloc(sizeof(struct ppc_data), M_DEVBUF, M_NOWAIT); + if (!ppc) { + printf("ppc: cannot malloc!\n"); + goto error; + } + bzero(ppc, sizeof(struct ppc_data)); + + ppc->ppc_base = dvp->id_iobase; + ppc->ppc_unit = dvp->id_unit; + ppc->ppc_type = GENERIC; + + /* store boot flags */ + ppc->ppc_flags = dvp->id_flags; + + ppc->ppc_mode = PPB_COMPATIBLE; + ppc->ppc_epp = (dvp->id_flags & 0x10) >> 4; + + /* + * XXX Try and detect if interrupts are working + */ + if (!(dvp->id_flags & 0x20) && dvp->id_irq) + ppc->ppc_irq = ffs(dvp->id_irq) - 1; + + ppc->ppc_dmachan = dvp->id_drq; + + ppcdata[ppc->ppc_unit] = ppc; + nppc ++; + + /* + * Link the Parallel Port Chipset (adapter) to + * the future ppbus. Default to a generic chipset + */ + ppc->ppc_link.adapter_unit = ppc->ppc_unit; + ppc->ppc_link.adapter = &ppc_generic_adapter; + +#ifdef PC98 + /* + * IEEE STD 1284 Function Check and Enable + * for default IEEE-1284 port of NEC PC-98x1 + */ + if ((ppc->ppc_base == PC98_IEEE_1284_PORT) && + !(dvp->id_flags & PC98_IEEE_1284_DISABLE)) { + tmp = inb(ppc->ppc_base + PPC_1284_ENABLE); + pc98_ieee_mode = tmp; + if ((tmp & 0x10) == 0x10) { + outb(ppc->ppc_base + PPC_1284_ENABLE, tmp & ~0x10); + tmp = inb(ppc->ppc_base + PPC_1284_ENABLE); + if ((tmp & 0x10) == 0x10) + goto error; + } else { + outb(ppc->ppc_base + PPC_1284_ENABLE, tmp | 0x10); + tmp = inb(ppc->ppc_base + PPC_1284_ENABLE); + if ((tmp & 0x10) != 0x10) + goto error; + } + outb(ppc->ppc_base + PPC_1284_ENABLE, pc98_ieee_mode | 0x10); + } +#endif + + /* + * Try to detect the chipset and its mode. + */ + if (ppc_detect(ppc, dvp->id_flags & 0xf)) + goto error; + + return (IO_LPTSIZE); + +error: +#ifdef PC98 + if ((ppc->ppc_base == PC98_IEEE_1284_PORT) && + !(dvp->id_flags & PC98_IEEE_1284_DISABLE)) { + outb(ppc->ppc_base + PPC_1284_ENABLE, pc98_ieee_mode); + } +#endif + return (0); +} + +static int +ppcattach(struct isa_device *isdp) +{ + struct ppc_data *ppc = ppcdata[isdp->id_unit]; + struct ppb_data *ppbus; + + printf("ppc%d: %s chipset (%s) in %s mode%s\n", ppc->ppc_unit, + ppc_types[ppc->ppc_type], ppc_avms[ppc->ppc_avm], + ppc_modes[ppc->ppc_mode], (PPB_IS_EPP(ppc->ppc_mode)) ? + ppc_epp_protocol[ppc->ppc_epp] : ""); + + if (ppc->ppc_fifo) + printf("ppc%d: FIFO with %d/%d/%d bytes threshold\n", + ppc->ppc_unit, ppc->ppc_fifo, ppc->ppc_wthr, + ppc->ppc_rthr); + + isdp->id_ointr = ppcintr; + + /* + * Prepare ppbus data area for upper level code. + */ + ppbus = ppb_alloc_bus(); + + if (!ppbus) + return (0); + + ppc->ppc_link.ppbus = ppbus; + ppbus->ppb_link = &ppc->ppc_link; + + if ((ppc->ppc_avm & PPB_ECP) && (ppc->ppc_dmachan > 0)) { + + /* acquire the DMA channel forever */ + isa_dma_acquire(ppc->ppc_dmachan); + isa_dmainit(ppc->ppc_dmachan, 1024); /* nlpt.BUFSIZE */ + } + + /* + * Probe the ppbus and attach devices found. + */ + ppb_attachdevs(ppbus); + + return (1); +} +#endif diff --git a/sys/pc98/cbus/ppcreg.h b/sys/pc98/cbus/ppcreg.h new file mode 100644 index 0000000..3acbbf6 --- /dev/null +++ b/sys/pc98/cbus/ppcreg.h @@ -0,0 +1,233 @@ +/*- + * Copyright (c) 1997 Nicolas Souchu + * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + * + * $Id$ + * + */ +#ifndef __PPCREG_H +#define __PPCREG_H + +/* + * Parallel Port Chipset type. + */ +#define SMC_LIKE 0 +#define SMC_37C665GT 1 +#define SMC_37C666GT 2 +#define NS_PC87332 3 +#define NS_PC87306 4 +#define INTEL_820191AA 5 /* XXX not implemented */ +#define GENERIC 6 +#define WINB_W83877F 7 +#define WINB_W83877AF 8 +#define WINB_UNKNOWN 9 +#define NS_PC87334 10 + +/* + * Generic structure to hold parallel port chipset info. + */ +struct ppc_data { + + int ppc_unit; + int ppc_type; + + int ppc_mode; /* chipset current mode */ + int ppc_avm; /* chipset available modes */ + +#define PPC_IRQ_NONE 0x0 +#define PPC_IRQ_nACK 0x1 +#define PPC_IRQ_DMA 0x2 +#define PPC_IRQ_FIFO 0x4 +#define PPC_IRQ_nFAULT 0x8 + int ppc_irqstat; /* remind irq settings */ + +#define PPC_DMA_INIT 0x01 +#define PPC_DMA_STARTED 0x02 +#define PPC_DMA_COMPLETE 0x03 +#define PPC_DMA_INTERRUPTED 0x04 +#define PPC_DMA_ERROR 0x05 + int ppc_dmastat; /* dma state */ + int ppc_dmachan; /* dma channel */ + int ppc_dmaflags; /* dma transfer flags */ + caddr_t ppc_dmaddr; /* buffer address */ + u_int ppc_dmacnt; /* count of bytes sent with dma */ + +#define PPC_PWORD_MASK 0x30 +#define PPC_PWORD_16 0x00 +#define PPC_PWORD_8 0x10 +#define PPC_PWORD_32 0x20 + char ppc_pword; /* PWord size */ + short ppc_fifo; /* FIFO threshold */ + + short ppc_wthr; /* writeIntrThresold */ + short ppc_rthr; /* readIntrThresold */ + +#define ppc_base ppc_link.base +#define ppc_epp ppc_link.epp_protocol +#define ppc_irq ppc_link.id_irq +#define ppc_subm ppc_link.submicroseq +#define ppc_ptr ppc_link.ptr +#define ppc_accum ppc_link.accum + + unsigned char ppc_flags; + + struct ppb_link ppc_link; +}; + +/* + * Parallel Port Chipset registers. + */ +#define PPC_SPP_DTR 0 /* SPP data register */ +#define PPC_ECP_A_FIFO 0 /* ECP Address fifo register */ +#define PPC_SPP_STR 1 /* SPP status register */ +#define PPC_SPP_CTR 2 /* SPP control register */ +#define PPC_EPP_ADDR 3 /* EPP address register (8 bit) */ +#define PPC_EPP_DATA 4 /* EPP data register (8, 16 or 32 bit) */ +#ifdef PC98 +#define PPC_1284_ENABLE 0x09 /* IEEE STD 1284 Enable register */ +#define PPC_ECP_D_FIFO 0x0c /* ECP Data fifo register */ +#define PPC_ECP_CNFGA 0x0c /* Configuration register A */ +#define PPC_ECP_CNFGB 0x0d /* Configuration register B */ +#define PPC_ECP_ECR 0x0e /* ECP extended control register */ +#else +#define PPC_ECP_D_FIFO 0x400 /* ECP Data fifo register */ +#define PPC_ECP_CNFGA 0x400 /* Configuration register A */ +#define PPC_ECP_CNFGB 0x401 /* Configuration register B */ +#define PPC_ECP_ECR 0x402 /* ECP extended control register */ +#endif + +#define PPC_FIFO_EMPTY 0x1 /* ecr register - bit 0 */ +#define PPC_FIFO_FULL 0x2 /* ecr register - bit 1 */ +#define PPC_SERVICE_INTR 0x4 /* ecr register - bit 2 */ +#define PPC_ENABLE_DMA 0x8 /* ecr register - bit 3 */ +#define PPC_nFAULT_INTR 0x10 /* ecr register - bit 4 */ +#define PPC_ECR_STD 0x0 +#define PPC_ECR_PS2 0x20 +#define PPC_ECR_FIFO 0x40 +#define PPC_ECR_ECP 0x60 +#define PPC_ECR_EPP 0x80 + +#define PPC_DISABLE_INTR (PPC_SERVICE_INTR | PPC_nFAULT_INTR) +#define PPC_ECR_RESET (PPC_ECR_PS2 | PPC_DISABLE_INTR) + +#define r_dtr(ppc) (inb((ppc)->ppc_base + PPC_SPP_DTR)) +#define r_str(ppc) (inb((ppc)->ppc_base + PPC_SPP_STR)) +#define r_ctr(ppc) (inb((ppc)->ppc_base + PPC_SPP_CTR)) +#define r_epp_A(ppc) (inb((ppc)->ppc_base + PPC_EPP_ADDR)) +#define r_epp_D(ppc) (inb((ppc)->ppc_base + PPC_EPP_DATA)) +#define r_cnfgA(ppc) (inb((ppc)->ppc_base + PPC_ECP_CNFGA)) +#define r_cnfgB(ppc) (inb((ppc)->ppc_base + PPC_ECP_CNFGB)) +#define r_ecr(ppc) (inb((ppc)->ppc_base + PPC_ECP_ECR)) +#define r_fifo(ppc) (inb((ppc)->ppc_base + PPC_ECP_D_FIFO)) + +#define w_dtr(ppc,byte) outb((ppc)->ppc_base + PPC_SPP_DTR, byte) +#define w_str(ppc,byte) outb((ppc)->ppc_base + PPC_SPP_STR, byte) +#define w_ctr(ppc,byte) outb((ppc)->ppc_base + PPC_SPP_CTR, byte) +#define w_epp_A(ppc,byte) outb((ppc)->ppc_base + PPC_EPP_ADDR, byte) +#define w_epp_D(ppc,byte) outb((ppc)->ppc_base + PPC_EPP_DATA, byte) +#define w_ecr(ppc,byte) outb((ppc)->ppc_base + PPC_ECP_ECR, byte) +#define w_fifo(ppc,byte) outb((ppc)->ppc_base + PPC_ECP_D_FIFO, byte) + +/* + * Register defines for the PC873xx parts + */ + +#define PC873_FER 0x00 +#define PC873_PPENABLE (1<<0) +#define PC873_FAR 0x01 +#define PC873_PTR 0x02 +#define PC873_CFGLOCK (1<<6) +#define PC873_EPPRDIR (1<<7) +#define PC873_EXTENDED (1<<7) +#define PC873_LPTBIRQ7 (1<<3) +#define PC873_FCR 0x03 +#define PC873_ZWS (1<<5) +#define PC873_ZWSPWDN (1<<6) +#define PC873_PCR 0x04 +#define PC873_EPPEN (1<<0) +#define PC873_EPP19 (1<<1) +#define PC873_ECPEN (1<<2) +#define PC873_ECPCLK (1<<3) +#define PC873_PMC 0x06 +#define PC873_TUP 0x07 +#define PC873_SID 0x08 +#define PC873_PNP0 0x1b +#define PC873_PNP1 0x1c +#define PC873_LPTBA 0x19 + +/* + * Register defines for the SMC FDC37C66xGT parts + */ + +/* Init codes */ +#define SMC665_iCODE 0x55 +#define SMC666_iCODE 0x44 + +/* Base configuration ports */ +#define SMC66x_CSR 0x3F0 +#define SMC666_CSR 0x370 /* hard-configured value for 666 */ + +/* Bits */ +#define SMC_CR1_ADDR 0x3 /* bit 0 and 1 */ +#define SMC_CR1_MODE (1<<3) /* bit 3 */ +#define SMC_CR4_EMODE 0x3 /* bits 0 and 1 */ +#define SMC_CR4_EPPTYPE (1<<6) /* bit 6 */ + +/* Extended modes */ +#define SMC_SPP 0x0 /* SPP */ +#define SMC_EPPSPP 0x1 /* EPP and SPP */ +#define SMC_ECP 0x2 /* ECP */ +#define SMC_ECPEPP 0x3 /* ECP and EPP */ + +/* + * Register defines for the Winbond W83877F parts + */ + +#define WINB_W83877F_ID 0xa +#define WINB_W83877AF_ID 0xb + +/* Configuration bits */ +#define WINB_HEFERE (1<<5) /* CROC bit 5 */ +#define WINB_HEFRAS (1<<0) /* CR16 bit 0 */ + +#define WINB_PNPCVS (1<<2) /* CR16 bit 2 */ +#define WINB_CHIPID 0xf /* CR9 bits 0-3 */ + +#define WINB_PRTMODS0 (1<<2) /* CR0 bit 2 */ +#define WINB_PRTMODS1 (1<<3) /* CR0 bit 3 */ +#define WINB_PRTMODS2 (1<<7) /* CR9 bit 7 */ + +/* W83877F modes: CR9/bit7 | CR0/bit3 | CR0/bit2 */ +#define WINB_W83757 0x0 +#define WINB_EXTFDC 0x4 +#define WINB_EXTADP 0x8 +#define WINB_EXT2FDD 0xc +#define WINB_JOYSTICK 0x80 + +#define WINB_PARALLEL 0x80 +#define WINB_EPP_SPP 0x4 +#define WINB_ECP 0x8 +#define WINB_ECP_EPP 0xc + +#endif diff --git a/sys/pc98/conf/GENERIC b/sys/pc98/conf/GENERIC index 7014114..6d4e4d4 100644 --- a/sys/pc98/conf/GENERIC +++ b/sys/pc98/conf/GENERIC @@ -11,7 +11,7 @@ # device lines is present in the ./LINT configuration file. If you are # in doubt as to the purpose or necessity of a line, check first in LINT. # -# $Id: GENERIC98,v 1.75 1999/05/10 09:00:28 kato Exp $ +# $Id: GENERIC98,v 1.76 1999/06/15 13:14:50 des Exp $ # GENERIC98 -- Generic PC98 machine with WD/SCSI disks @@ -170,8 +170,17 @@ device sio1 at isa? port 0x238 irq 5 flags 0x12000000 #options COM_ESP #device sio1 at isa? port 0x18b0 irq 5 flags 0x19000000 -#device lpt0 at isa? port IO_LPT device mse0 at isa? port IO_MSE irq 13 +# NEW Parallel port +device ppc0 at isa? port? tty irq 14 +controller ppbus0 +device lpt0 at ppbus? +device plip0 at ppbus? +device ppi0 at ppbus? +#controller vpo0 at ppbus? +# OLD Parallel port +# Please stay olpt driver after ppc driver +device olpt0 at isa? port IO_LPT tty # # The following Ethernet NICs are all PCI devices. diff --git a/sys/pc98/conf/GENERIC98 b/sys/pc98/conf/GENERIC98 index 7014114..6d4e4d4 100644 --- a/sys/pc98/conf/GENERIC98 +++ b/sys/pc98/conf/GENERIC98 @@ -11,7 +11,7 @@ # device lines is present in the ./LINT configuration file. If you are # in doubt as to the purpose or necessity of a line, check first in LINT. # -# $Id: GENERIC98,v 1.75 1999/05/10 09:00:28 kato Exp $ +# $Id: GENERIC98,v 1.76 1999/06/15 13:14:50 des Exp $ # GENERIC98 -- Generic PC98 machine with WD/SCSI disks @@ -170,8 +170,17 @@ device sio1 at isa? port 0x238 irq 5 flags 0x12000000 #options COM_ESP #device sio1 at isa? port 0x18b0 irq 5 flags 0x19000000 -#device lpt0 at isa? port IO_LPT device mse0 at isa? port IO_MSE irq 13 +# NEW Parallel port +device ppc0 at isa? port? tty irq 14 +controller ppbus0 +device lpt0 at ppbus? +device plip0 at ppbus? +device ppi0 at ppbus? +#controller vpo0 at ppbus? +# OLD Parallel port +# Please stay olpt driver after ppc driver +device olpt0 at isa? port IO_LPT tty # # The following Ethernet NICs are all PCI devices. diff --git a/sys/pc98/conf/files.pc98 b/sys/pc98/conf/files.pc98 index 943ec0f..a03636a 100644 --- a/sys/pc98/conf/files.pc98 +++ b/sys/pc98/conf/files.pc98 @@ -3,7 +3,7 @@ # # modified for PC-9801 # -# $Id: files.pc98,v 1.96 1999/05/17 12:07:00 kato Exp $ +# $Id: files.pc98,v 1.97 1999/06/03 13:03:58 kato Exp $ # # The long compile-with and dependency lines are required because of # limitations in config: backslash-newline doesn't work in strings, and @@ -175,7 +175,7 @@ i386/isa/istallion.c optional stli device-driver i386/isa/joy.c optional joy device-driver pc98/pc98/pc98kbd.c optional pckbd device-driver i386/isa/loran.c optional loran device-driver -pc98/pc98/lpt.c optional lpt device-driver +pc98/pc98/olpt.c optional olpt device-driver i386/isa/labpc.c optional labpc device-driver i386/isa/mcd.c optional mcd device-driver pc98/pc98/mse.c optional mse device-driver @@ -196,7 +196,7 @@ i386/isa/pcvt/pcvt_sup.c optional vt device-driver i386/isa/pcvt/pcvt_vtf.c optional vt device-driver i386/isa/pnp.c optional pnp device-driver i386/isa/prof_machdep.c optional profiling-routine -i386/isa/ppc.c optional ppc device-driver +pc98/pc98/ppc.c optional ppc device-driver i386/isa/pcf.c optional pcf device-driver isa/psm.c optional psm device-driver i386/isa/random_machdep.c standard diff --git a/sys/pc98/pc98/olpt.c b/sys/pc98/pc98/olpt.c new file mode 100644 index 0000000..57caf91 --- /dev/null +++ b/sys/pc98/pc98/olpt.c @@ -0,0 +1,1473 @@ +/* + * Copyright (c) 1990 William F. Jolitz, TeleMuse + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This software is a component of "386BSD" developed by + * William F. Jolitz, TeleMuse. + * 4. Neither the name of the developer nor the name "386BSD" + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS A COMPONENT OF 386BSD DEVELOPED BY WILLIAM F. JOLITZ + * AND IS INTENDED FOR RESEARCH AND EDUCATIONAL PURPOSES ONLY. THIS + * SOFTWARE SHOULD NOT BE CONSIDERED TO BE A COMMERCIAL PRODUCT. + * THE DEVELOPER URGES THAT USERS WHO REQUIRE A COMMERCIAL PRODUCT + * NOT MAKE USE OF THIS WORK. + * + * FOR USERS WHO WISH TO UNDERSTAND THE 386BSD SYSTEM DEVELOPED + * BY WILLIAM F. JOLITZ, WE RECOMMEND THE USER STUDY WRITTEN + * REFERENCES SUCH AS THE "PORTING UNIX TO THE 386" SERIES + * (BEGINNING JANUARY 1991 "DR. DOBBS JOURNAL", USA AND BEGINNING + * JUNE 1991 "UNIX MAGAZIN", GERMANY) BY WILLIAM F. JOLITZ AND + * LYNNE GREER JOLITZ, AS WELL AS OTHER BOOKS ON UNIX AND THE + * ON-LINE 386BSD USER MANUAL BEFORE USE. A BOOK DISCUSSING THE INTERNALS + * OF 386BSD ENTITLED "386BSD FROM THE INSIDE OUT" WILL BE AVAILABLE LATE 1992. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPER ``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 DEVELOPER 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. + * + * from: unknown origin, 386BSD 0.1 + * $Id$ + */ + +/* + * Device Driver for AT parallel printer port + * Written by William Jolitz 12/18/90 + */ + +/* + * Parallel port TCP/IP interfaces added. I looked at the driver from + * MACH but this is a complete rewrite, and btw. incompatible, and it + * should perform better too. I have never run the MACH driver though. + * + * This driver sends two bytes (0x08, 0x00) in front of each packet, + * to allow us to distinguish another format later. + * + * Now added an Linux/Crynwr compatibility mode which is enabled using + * IF_LINK0 - Tim Wilkinson. + * + * TODO: + * Make HDLC/PPP mode, use IF_LLC1 to enable. + * + * Connect the two computers using a Laplink parallel cable to use this + * feature: + * + * +----------------------------------------+ + * |A-name A-End B-End Descr. Port/Bit | + * +----------------------------------------+ + * |DATA0 2 15 Data 0/0x01 | + * |-ERROR 15 2 1/0x08 | + * +----------------------------------------+ + * |DATA1 3 13 Data 0/0x02 | + * |+SLCT 13 3 1/0x10 | + * +----------------------------------------+ + * |DATA2 4 12 Data 0/0x04 | + * |+PE 12 4 1/0x20 | + * +----------------------------------------+ + * |DATA3 5 10 Strobe 0/0x08 | + * |-ACK 10 5 1/0x40 | + * +----------------------------------------+ + * |DATA4 6 11 Data 0/0x10 | + * |BUSY 11 6 1/~0x80 | + * +----------------------------------------+ + * |GND 18-25 18-25 GND - | + * +----------------------------------------+ + * + * Expect transfer-rates up to 75 kbyte/sec. + * + * If GCC could correctly grok + * register int port asm("edx") + * the code would be cleaner + * + * Poul-Henning Kamp <phk@freebsd.org> + */ + +#include "olpt.h" +#include "opt_devfs.h" +#include "opt_inet.h" +#ifdef PC98 +#undef INET /* PLIP is not supported for old PC-98 */ +#endif + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/conf.h> +#include <sys/buf.h> +#include <sys/kernel.h> +#include <sys/uio.h> +#include <sys/syslog.h> +#ifdef DEVFS +#include <sys/devfsext.h> +#endif /*DEVFS*/ + +#include <machine/clock.h> +#include <machine/lpt.h> + +#include <vm/vm.h> +#include <vm/vm_param.h> +#include <vm/pmap.h> + +#ifdef PC98 +#include <pc98/pc98/pc98.h> +#else +#include <i386/isa/isa.h> +#endif +#include <i386/isa/isa_device.h> +#include <i386/isa/lptreg.h> + +#ifdef INET +#include <sys/malloc.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/sockio.h> + +#include <net/if.h> +#include <net/if_types.h> +#include <net/netisr.h> +#include <netinet/in.h> +#include <netinet/in_var.h> +#include "bpfilter.h" +#if NBPFILTER > 0 +#include <net/bpf.h> +#endif +#endif /* INET */ + + +#define LPINITRDY 4 /* wait up to 4 seconds for a ready */ +#define LPTOUTINITIAL 10 /* initial timeout to wait for ready 1/10 s */ +#define LPTOUTMAX 1 /* maximal timeout 1 s */ +#define LPPRI (PZERO+8) +#define BUFSIZE 1024 + +#ifdef INET +#ifndef LPMTU /* MTU for the lp# interfaces */ +#define LPMTU 1500 +#endif + +#ifndef LPMAXSPIN1 /* DELAY factor for the lp# interfaces */ +#define LPMAXSPIN1 8000 /* Spinning for remote intr to happen */ +#endif + +#ifndef LPMAXSPIN2 /* DELAY factor for the lp# interfaces */ +#define LPMAXSPIN2 500 /* Spinning for remote handshake to happen */ +#endif + +#ifndef LPMAXERRS /* Max errors before !RUNNING */ +#define LPMAXERRS 100 +#endif + +#define CLPIPHDRLEN 14 /* We send dummy ethernet addresses (two) + packet type in front of packet */ +#define CLPIP_SHAKE 0x80 /* This bit toggles between nibble reception */ +#define MLPIPHDRLEN CLPIPHDRLEN + +#define LPIPHDRLEN 2 /* We send 0x08, 0x00 in front of packet */ +#define LPIP_SHAKE 0x40 /* This bit toggles between nibble reception */ +#if !defined(MLPIPHDRLEN) || LPIPHDRLEN > MLPIPHDRLEN +#define MLPIPHDRLEN LPIPHDRLEN +#endif + +#define LPIPTBLSIZE 256 /* Size of octet translation table */ + +#endif /* INET */ + +#ifndef PC98 +/* BIOS printer list - used by BIOS probe*/ +#define BIOS_LPT_PORTS 0x408 +#define BIOS_PORTS (short *)(KERNBASE+BIOS_LPT_PORTS) +#define BIOS_MAX_LPT 4 +#endif + + +#ifndef DEBUG +#define lprintf(args) +#else +#define lprintf(args) do { \ + if (lptflag) \ + printf args; \ + } while (0) +static int volatile lptflag = 1; +#endif + +#define LPTUNIT(s) ((s)&0x03) +#define LPTFLAGS(s) ((s)&0xfc) + +static struct lpt_softc { + int sc_port; + short sc_state; + /* default case: negative prime, negative ack, handshake strobe, + prime once */ + u_char sc_control; + char sc_flags; +#define LP_POS_INIT 0x04 /* if we are a postive init signal */ +#define LP_POS_ACK 0x08 /* if we are a positive going ack */ +#define LP_NO_PRIME 0x10 /* don't prime the printer at all */ +#define LP_PRIMEOPEN 0x20 /* prime on every open */ +#define LP_AUTOLF 0x40 /* tell printer to do an automatic lf */ +#define LP_BYPASS 0x80 /* bypass printer ready checks */ + struct buf *sc_inbuf; + short sc_xfercnt ; + char sc_primed; + char *sc_cp ; + u_char sc_irq ; /* IRQ status of port */ +#define LP_HAS_IRQ 0x01 /* we have an irq available */ +#define LP_USE_IRQ 0x02 /* we are using our irq */ +#define LP_ENABLE_IRQ 0x04 /* enable IRQ on open */ + u_char sc_backoff ; /* time to call lptout() again */ + +#ifdef INET + struct ifnet sc_if; + u_char *sc_ifbuf; + int sc_iferrs; +#endif +#ifdef DEVFS + void *devfs_token; + void *devfs_token_ctl; +#endif +} lpt_sc[NOLPT] ; + +/* bits for state */ +#define OPEN (1<<0) /* device is open */ +#define ASLP (1<<1) /* awaiting draining of printer */ +#define ERROR (1<<2) /* error was received from printer */ +#define OBUSY (1<<3) /* printer is busy doing output */ +#define LPTOUT (1<<4) /* timeout while not selected */ +#define TOUT (1<<5) /* timeout while not selected */ +#define INIT (1<<6) /* waiting to initialize for open */ +#define INTERRUPTED (1<<7) /* write call was interrupted */ + + +/* status masks to interrogate printer status */ +#define RDY_MASK (LPS_SEL|LPS_OUT|LPS_NBSY|LPS_NERR) /* ready ? */ +#define LP_READY (LPS_SEL|LPS_NBSY|LPS_NERR) + +/* Printer Ready condition - from lpa.c */ +/* Only used in polling code */ +#ifdef PC98 +#define NOT_READY(x) ((inb(x) & LPS_NBSY) != LPS_NBSY) +#else /* IBM-PC */ +#define LPS_INVERT (LPS_NBSY | LPS_NACK | LPS_SEL | LPS_NERR) +#define LPS_MASK (LPS_NBSY | LPS_NACK | LPS_OUT | LPS_SEL | LPS_NERR) +#define NOT_READY(x) ((inb(x)^LPS_INVERT)&LPS_MASK) +#endif + +#define MAX_SLEEP (hz*5) /* Timeout while waiting for device ready */ +#define MAX_SPIN 20 /* Max delay for device ready in usecs */ + +static timeout_t lptout; +static int lptprobe (struct isa_device *dvp); +static int lptattach (struct isa_device *isdp); +static ointhand2_t lptintr; + +#ifdef INET + +/* Tables for the lp# interface */ +static u_char *txmith; +#define txmitl (txmith+(1*LPIPTBLSIZE)) +#define trecvh (txmith+(2*LPIPTBLSIZE)) +#define trecvl (txmith+(3*LPIPTBLSIZE)) + +static u_char *ctxmith; +#define ctxmitl (ctxmith+(1*LPIPTBLSIZE)) +#define ctrecvh (ctxmith+(2*LPIPTBLSIZE)) +#define ctrecvl (ctxmith+(3*LPIPTBLSIZE)) + +/* Functions for the lp# interface */ +static void lpattach(struct lpt_softc *,int); +#ifndef PC98 +static int lpinittables(void); +#endif +static int lpioctl(struct ifnet *, u_long, caddr_t); +static int lpoutput(struct ifnet *, struct mbuf *, struct sockaddr *, + struct rtentry *); +static void lpintr(int); +#endif /* INET */ + +struct isa_driver olptdriver = { + lptprobe, lptattach, "olpt" +}; + +static d_open_t lptopen; +static d_close_t lptclose; +static d_write_t lptwrite; +static d_ioctl_t lptioctl; + +#define CDEV_MAJOR 16 +static struct cdevsw lpt_cdevsw = { + /* open */ lptopen, + /* close */ lptclose, + /* read */ noread, + /* write */ lptwrite, + /* ioctl */ lptioctl, + /* stop */ nostop, + /* reset */ noreset, + /* devtotty */ nodevtotty, + /* poll */ nopoll, + /* mmap */ nommap, + /* strategy */ nostrategy, + /* name */ "lpt", + /* parms */ noparms, + /* maj */ CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ 0, + /* maxio */ 0, + /* bmaj */ -1 +}; + +#ifndef PC98 +/* + * Internal routine to lptprobe to do port tests of one byte value + */ +static int +lpt_port_test (int port, u_char data, u_char mask) +{ + int temp, timeout; + + data = data & mask; + outb(port, data); + timeout = 10000; + do { + DELAY(10); + temp = inb(port) & mask; + } + while (temp != data && --timeout); + lprintf(("Port 0x%x\tout=%x\tin=%x\ttout=%d\n", + port, data, temp, timeout)); + return (temp == data); +} +#endif /* PC98 */ + +/* + * New lpt port probe Geoff Rehmet - Rhodes University - 14/2/94 + * Based partially on Rod Grimes' printer probe + * + * Logic: + * 1) If no port address was given, use the bios detected ports + * and autodetect what ports the printers are on. + * 2) Otherwise, probe the data port at the address given, + * using the method in Rod Grimes' port probe. + * (Much code ripped off directly from Rod's probe.) + * + * Comments from Rod's probe: + * Logic: + * 1) You should be able to write to and read back the same value + * to the data port. Do an alternating zeros, alternating ones, + * walking zero, and walking one test to check for stuck bits. + * + * 2) You should be able to write to and read back the same value + * to the control port lower 5 bits, the upper 3 bits are reserved + * per the IBM PC technical reference manauls and different boards + * do different things with them. Do an alternating zeros, alternating + * ones, walking zero, and walking one test to check for stuck bits. + * + * Some printers drag the strobe line down when the are powered off + * so this bit has been masked out of the control port test. + * + * XXX Some printers may not like a fast pulse on init or strobe, I + * don't know at this point, if that becomes a problem these bits + * should be turned off in the mask byte for the control port test. + * + * We are finally left with a mask of 0x14, due to some printers + * being adamant about holding other bits high ........ + * + * Before probing the control port, we write a 0 to the data port - + * If not, some printers chuck out garbage when the strobe line + * gets toggled. + * + * 3) Set the data and control ports to a value of 0 + * + * This probe routine has been tested on Epson Lx-800, HP LJ3P, + * Epson FX-1170 and C.Itoh 8510RM + * printers. + * Quick exit on fail added. + */ + +int +lptprobe(struct isa_device *dvp) +{ +#ifdef PC98 +#define PC98_OLD_LPT 0x40 +#define PC98_IEEE_1284_FUNCTION 0x149 + unsigned int pc98_ieee_mode, tmp; + + if (dvp->id_iobase == PC98_OLD_LPT) { + tmp = inb(PC98_IEEE_1284_FUNCTION); + pc98_ieee_mode = tmp; + if ((tmp & 0x10) == 0x10) { + outb(PC98_IEEE_1284_FUNCTION, tmp & ~0x10); + tmp = inb(PC98_IEEE_1284_FUNCTION); + if ((tmp & 0x10) != 0x10) { + outb(PC98_IEEE_1284_FUNCTION, pc98_ieee_mode); + return 0; + } + } + } + return 8; +#else + int port; + static short next_bios_lpt = 0; + int status; + static u_char testbyte[18] = { + 0x55, /* alternating zeros */ + 0xaa, /* alternating ones */ + 0xfe, 0xfd, 0xfb, 0xf7, + 0xef, 0xdf, 0xbf, 0x7f, /* walking zero */ + 0x01, 0x02, 0x04, 0x08, + 0x10, 0x20, 0x40, 0x80 /* walking one */ + }; + int i; + + /* + * Make sure there is some way for lptopen to see that + * the port is not configured + * This 0 will remain if the port isn't attached + */ + (lpt_sc + dvp->id_unit)->sc_port = 0; + + status = IO_LPTSIZE; + /* If port not specified, use bios list */ + if(dvp->id_iobase < 0) { /* port? */ + if((next_bios_lpt < BIOS_MAX_LPT) && + (*(BIOS_PORTS+next_bios_lpt) != 0) ) { + dvp->id_iobase = *(BIOS_PORTS+next_bios_lpt++); + goto end_probe; + } else + return (0); + } + + /* Port was explicitly specified */ + /* This allows probing of ports unknown to the BIOS */ + port = dvp->id_iobase + lpt_data; + for (i = 0; i < 18; i++) { + if (!lpt_port_test(port, testbyte[i], 0xff)) { + status = 0; + goto end_probe; + } + } + +end_probe: + /* write 0's to control and data ports */ + outb(dvp->id_iobase+lpt_data, 0); + outb(dvp->id_iobase+lpt_control, 0); + + return (status); +#endif +} + +/* XXX Todo - try and detect if interrupt is working */ +int +lptattach(struct isa_device *isdp) +{ + struct lpt_softc *sc; + int unit; + + isdp->id_ointr = lptintr; + unit = isdp->id_unit; + sc = lpt_sc + unit; + sc->sc_port = isdp->id_iobase; + sc->sc_primed = 0; /* not primed yet */ +#ifdef PC98 + outb(sc->sc_port+lpt_pstb_ctrl, LPC_DIS_PSTB); /* PSTB disable */ + outb(sc->sc_port+lpt_control, LPC_MODE8255); /* 8255 mode set */ + outb(sc->sc_port+lpt_control, LPC_NIRQ8); /* IRQ8 inactive */ + outb(sc->sc_port+lpt_control, LPC_NPSTB); /* PSTB inactive */ + outb(sc->sc_port+lpt_pstb_ctrl, LPC_EN_PSTB); /* PSTB enable */ +#else + outb(sc->sc_port+lpt_control, LPC_NINIT); +#endif + + /* check if we can use interrupt */ + lprintf(("oldirq %x\n", sc->sc_irq)); + if (isdp->id_irq) { + sc->sc_irq = LP_HAS_IRQ | LP_USE_IRQ | LP_ENABLE_IRQ; + printf("lpt%d: Interrupt-driven port\n", unit); +#ifdef INET + lpattach(sc, unit); +#endif + } else { + sc->sc_irq = 0; + lprintf(("lpt%d: Polled port\n", unit)); + } + lprintf(("irq %x\n", sc->sc_irq)); + +#ifdef DEVFS + /* XXX what to do about the flags in the minor number? */ + sc->devfs_token = devfs_add_devswf(&lpt_cdevsw, + unit, DV_CHR, + UID_ROOT, GID_WHEEL, 0600, "lpt%d", unit); + sc->devfs_token_ctl = devfs_add_devswf(&lpt_cdevsw, + unit | LP_BYPASS, DV_CHR, + UID_ROOT, GID_WHEEL, 0600, "lpctl%d", unit); +#endif + return (1); +} + +/* + * lptopen -- reset the printer, then wait until it's selected and not busy. + * If LP_BYPASS flag is selected, then we do not try to select the + * printer -- this is just used for passing ioctls. + */ + +static int +lptopen (dev_t dev, int flags, int fmt, struct proc *p) +{ + struct lpt_softc *sc; + int s; +#ifdef PC98 + int port; +#else + int trys, port; +#endif + u_int unit = LPTUNIT(minor(dev)); + + sc = lpt_sc + unit; + if ((unit >= NOLPT) || (sc->sc_port == 0)) + return (ENXIO); + +#ifdef INET + if (sc->sc_if.if_flags & IFF_UP) + return(EBUSY); +#endif + + if (sc->sc_state) { + lprintf(("lp: still open %x\n", sc->sc_state)); + return(EBUSY); + } else + sc->sc_state |= INIT; + + sc->sc_flags = LPTFLAGS(minor(dev)); + + /* Check for open with BYPASS flag set. */ + if (sc->sc_flags & LP_BYPASS) { + sc->sc_state = OPEN; + return(0); + } + + s = spltty(); + lprintf(("lp flags 0x%x\n", sc->sc_flags)); + port = sc->sc_port; + + /* set IRQ status according to ENABLE_IRQ flag */ + if (sc->sc_irq & LP_ENABLE_IRQ) + sc->sc_irq |= LP_USE_IRQ; + else + sc->sc_irq &= ~LP_USE_IRQ; + + /* init printer */ +#ifndef PC98 + if ((sc->sc_flags & LP_NO_PRIME) == 0) { + if((sc->sc_flags & LP_PRIMEOPEN) || sc->sc_primed == 0) { + outb(port+lpt_control, 0); + sc->sc_primed++; + DELAY(500); + } + } + + outb (port+lpt_control, LPC_SEL|LPC_NINIT); + + /* wait till ready (printer running diagnostics) */ + trys = 0; + do { + /* ran out of waiting for the printer */ + if (trys++ >= LPINITRDY*4) { + splx(s); + sc->sc_state = 0; + lprintf(("status %x\n", inb(port+lpt_status))); + return (EBUSY); + } + + /* wait 1/4 second, give up if we get a signal */ + if (tsleep ((caddr_t)sc, LPPRI|PCATCH, "lptinit", hz/4) != + EWOULDBLOCK) { + sc->sc_state = 0; + splx(s); + return (EBUSY); + } + + /* is printer online and ready for output */ + } while ((inb(port+lpt_status) & (LPS_SEL|LPS_OUT|LPS_NBSY|LPS_NERR)) != + (LPS_SEL|LPS_NBSY|LPS_NERR)); + + sc->sc_control = LPC_SEL|LPC_NINIT; + if (sc->sc_flags & LP_AUTOLF) + sc->sc_control |= LPC_AUTOL; + + /* enable interrupt if interrupt-driven */ + if (sc->sc_irq & LP_USE_IRQ) + sc->sc_control |= LPC_ENA; + + outb(port+lpt_control, sc->sc_control); +#endif + + sc->sc_state = OPEN; + sc->sc_inbuf = geteblk(BUFSIZE); + sc->sc_xfercnt = 0; + splx(s); + + /* only use timeout if using interrupt */ + lprintf(("irq %x\n", sc->sc_irq)); + if (sc->sc_irq & LP_USE_IRQ) { + sc->sc_state |= TOUT; + timeout (lptout, (caddr_t)sc, + (sc->sc_backoff = hz/LPTOUTINITIAL)); + } + + lprintf(("opened.\n")); + return(0); +} + +static void +lptout (void *arg) +{ + struct lpt_softc *sc = arg; + int pl; + + lprintf(("T %x ", inb(sc->sc_port+lpt_status))); + if (sc->sc_state & OPEN) { + sc->sc_backoff++; + if (sc->sc_backoff > hz/LPTOUTMAX) + sc->sc_backoff = sc->sc_backoff > hz/LPTOUTMAX; + timeout (lptout, (caddr_t)sc, sc->sc_backoff); + } else + sc->sc_state &= ~TOUT; + + if (sc->sc_state & ERROR) + sc->sc_state &= ~ERROR; + + /* + * Avoid possible hangs do to missed interrupts + */ + if (sc->sc_xfercnt) { + pl = spltty(); + lptintr(sc - lpt_sc); + splx(pl); + } else { + sc->sc_state &= ~OBUSY; + wakeup((caddr_t)sc); + } +} + +/* + * lptclose -- close the device, free the local line buffer. + * + * Check for interrupted write call added. + */ + +static int +lptclose(dev_t dev, int flags, int fmt, struct proc *p) +{ + struct lpt_softc *sc = lpt_sc + LPTUNIT(minor(dev)); +#ifndef PC98 + int port = sc->sc_port; +#endif + + if(sc->sc_flags & LP_BYPASS) + goto end_close; + + sc->sc_state &= ~OPEN; + +#ifndef PC98 + /* if the last write was interrupted, don't complete it */ + if((!(sc->sc_state & INTERRUPTED)) && (sc->sc_irq & LP_USE_IRQ)) + while ((inb(port+lpt_status) & (LPS_SEL|LPS_OUT|LPS_NBSY|LPS_NERR)) != + (LPS_SEL|LPS_NBSY|LPS_NERR) || sc->sc_xfercnt) + /* wait 1/4 second, give up if we get a signal */ + if (tsleep ((caddr_t)sc, LPPRI|PCATCH, + "lpclose", hz) != EWOULDBLOCK) + break; + + outb(sc->sc_port+lpt_control, LPC_NINIT); +#endif + brelse(sc->sc_inbuf); + +end_close: + sc->sc_state = 0; + sc->sc_xfercnt = 0; + lprintf(("closed.\n")); + return(0); +} + +/* + * pushbytes() + * Workhorse for actually spinning and writing bytes to printer + * Derived from lpa.c + * Originally by ? + * + * This code is only used when we are polling the port + */ +static int +pushbytes(struct lpt_softc * sc) +{ + int spin, err, tic; + char ch; + int port = sc->sc_port; + + lprintf(("p")); + /* loop for every character .. */ + while (sc->sc_xfercnt > 0) { + /* printer data */ + ch = *(sc->sc_cp); + sc->sc_cp++; + sc->sc_xfercnt--; + + /* + * Wait for printer ready. + * Loop 20 usecs testing BUSY bit, then sleep + * for exponentially increasing timeout. (vak) + */ + for (spin=0; NOT_READY(port+lpt_status) && spin<MAX_SPIN; ++spin) + DELAY(1); /* XXX delay is NOT this accurate! */ + if (spin >= MAX_SPIN) { + tic = 0; + while (NOT_READY(port+lpt_status)) { + /* + * Now sleep, every cycle a + * little longer .. + */ + tic = tic + tic + 1; + /* + * But no more than 10 seconds. (vak) + */ + if (tic > MAX_SLEEP) + tic = MAX_SLEEP; + err = tsleep((caddr_t)sc, LPPRI, + "lptpoll", tic); + if (err != EWOULDBLOCK) { + return (err); + } + } + } + + /* output data */ + outb(port+lpt_data, ch); +#ifdef PC98 + DELAY(1); + outb(port+lpt_control, LPC_PSTB); + DELAY(1); + outb(port+lpt_control, LPC_NPSTB); +#else + /* strobe */ + outb(port+lpt_control, sc->sc_control|LPC_STB); + outb(port+lpt_control, sc->sc_control); +#endif + + } + return(0); +} + +/* + * lptwrite --copy a line from user space to a local buffer, then call + * putc to get the chars moved to the output queue. + * + * Flagging of interrupted write added. + */ + +static int +lptwrite(dev_t dev, struct uio * uio, int ioflag) +{ + register unsigned n; + int pl, err; + struct lpt_softc *sc = lpt_sc + LPTUNIT(minor(dev)); + + if(sc->sc_flags & LP_BYPASS) { + /* we can't do writes in bypass mode */ + return(EPERM); + } + + sc->sc_state &= ~INTERRUPTED; + while ((n = min(BUFSIZE, uio->uio_resid)) != 0) { + sc->sc_cp = sc->sc_inbuf->b_data ; + uiomove(sc->sc_cp, n, uio); + sc->sc_xfercnt = n ; + while ((sc->sc_xfercnt > 0)&&(sc->sc_irq & LP_USE_IRQ)) { + lprintf(("i")); + /* if the printer is ready for a char, */ + /* give it one */ + if ((sc->sc_state & OBUSY) == 0){ + lprintf(("\nC %d. ", sc->sc_xfercnt)); + pl = spltty(); + lptintr(sc - lpt_sc); + (void) splx(pl); + } + lprintf(("W ")); + if (sc->sc_state & OBUSY) + if ((err = tsleep ((caddr_t)sc, + LPPRI|PCATCH, "lpwrite", 0))) { + sc->sc_state |= INTERRUPTED; + return(err); + } + } + /* check to see if we must do a polled write */ + if(!(sc->sc_irq & LP_USE_IRQ) && (sc->sc_xfercnt)) { + lprintf(("p")); + if((err = pushbytes(sc))) + return(err); + } + } + return(0); +} + +/* + * lptintr -- handle printer interrupts which occur when the printer is + * ready to accept another char. + * + * do checking for interrupted write call. + */ + +static void +lptintr(int unit) +{ + struct lpt_softc *sc = lpt_sc + unit; +#ifndef PC98 + int port = sc->sc_port, sts; + int i; +#endif + +#ifdef INET + if(sc->sc_if.if_flags & IFF_UP) { + lpintr(unit); + return; + } +#endif /* INET */ + +#ifndef PC98 + /* + * Is printer online and ready for output? + * + * Avoid falling back to lptout() too quickly. First spin-loop + * to see if the printer will become ready ``really soon now''. + */ + for (i = 0; + i < 100 && + ((sts=inb(port+lpt_status)) & RDY_MASK) != LP_READY; + i++) ; + if ((sts & RDY_MASK) == LP_READY) { + sc->sc_state = (sc->sc_state | OBUSY) & ~ERROR; + sc->sc_backoff = hz/LPTOUTINITIAL; + + if (sc->sc_xfercnt) { + /* send char */ + /*lprintf(("%x ", *sc->sc_cp)); */ + outb(port+lpt_data, *sc->sc_cp++) ; + outb(port+lpt_control, sc->sc_control|LPC_STB); + /* DELAY(X) */ + outb(port+lpt_control, sc->sc_control); + + /* any more data for printer */ + if(--(sc->sc_xfercnt) > 0) return; + } + + /* + * No more data waiting for printer. + * Wakeup is not done if write call was interrupted. + */ + sc->sc_state &= ~OBUSY; + if(!(sc->sc_state & INTERRUPTED)) + wakeup((caddr_t)sc); + lprintf(("w ")); + return; + } else { /* check for error */ + if(((sts & (LPS_NERR | LPS_OUT) ) != LPS_NERR) && + (sc->sc_state & OPEN)) + sc->sc_state |= ERROR; + /* lptout() will jump in and try to restart. */ + } +#endif + lprintf(("sts %x ", sts)); +} + +static int +lptioctl(dev_t dev, u_long cmd, caddr_t data, int flags, struct proc *p) +{ + int error = 0; + struct lpt_softc *sc; + u_int unit = LPTUNIT(minor(dev)); + u_char old_sc_irq; /* old printer IRQ status */ + + sc = lpt_sc + unit; + + switch (cmd) { + case LPT_IRQ : + if(sc->sc_irq & LP_HAS_IRQ) { + /* + * NOTE: + * If the IRQ status is changed, + * this will only be visible on the + * next open. + * + * If interrupt status changes, + * this gets syslog'd. + */ + old_sc_irq = sc->sc_irq; + if(*(int*)data == 0) + sc->sc_irq &= (~LP_ENABLE_IRQ); + else + sc->sc_irq |= LP_ENABLE_IRQ; + if (old_sc_irq != sc->sc_irq ) + log(LOG_NOTICE, "lpt%c switched to %s mode\n", + (char)unit+'0', + (sc->sc_irq & LP_ENABLE_IRQ)? + "interrupt-driven":"polled"); + } else /* polled port */ + error = EOPNOTSUPP; + break; + default: + error = ENODEV; + } + + return(error); +} + +#ifdef INET + +static void +lpattach (struct lpt_softc *sc, int unit) +{ + struct ifnet *ifp = &sc->sc_if; + + ifp->if_softc = sc; + ifp->if_name = "lp"; + ifp->if_unit = unit; + ifp->if_mtu = LPMTU; + ifp->if_flags = IFF_SIMPLEX | IFF_POINTOPOINT | IFF_MULTICAST; + ifp->if_ioctl = lpioctl; + ifp->if_output = lpoutput; + ifp->if_type = IFT_PARA; + ifp->if_hdrlen = 0; + ifp->if_addrlen = 0; + ifp->if_snd.ifq_maxlen = IFQ_MAXLEN; + if_attach(ifp); + printf("lp%d: TCP/IP capable interface\n", unit); + +#if NBPFILTER > 0 + bpfattach(ifp, DLT_NULL, LPIPHDRLEN); +#endif +} + +#ifndef PC98 +/* + * Build the translation tables for the LPIP (BSD unix) protocol. + * We don't want to calculate these nasties in our tight loop, so we + * precalculate them when we initialize. + */ +static int +lpinittables (void) +{ + int i; + + if (!txmith) + txmith = malloc(4*LPIPTBLSIZE, M_DEVBUF, M_NOWAIT); + + if (!txmith) + return 1; + + if (!ctxmith) + ctxmith = malloc(4*LPIPTBLSIZE, M_DEVBUF, M_NOWAIT); + + if (!ctxmith) + return 1; + + for (i=0; i < LPIPTBLSIZE; i++) { + ctxmith[i] = (i & 0xF0) >> 4; + ctxmitl[i] = 0x10 | (i & 0x0F); + ctrecvh[i] = (i & 0x78) << 1; + ctrecvl[i] = (i & 0x78) >> 3; + } + + for (i=0; i < LPIPTBLSIZE; i++) { + txmith[i] = ((i & 0x80) >> 3) | ((i & 0x70) >> 4) | 0x08; + txmitl[i] = ((i & 0x08) << 1) | (i & 0x07); + trecvh[i] = ((~i) & 0x80) | ((i & 0x38) << 1); + trecvl[i] = (((~i) & 0x80) >> 4) | ((i & 0x38) >> 3); + } + + return 0; +} +#endif /* PC98 */ + +/* + * Process an ioctl request. + */ + +static int +lpioctl (struct ifnet *ifp, u_long cmd, caddr_t data) +{ + struct lpt_softc *sc = lpt_sc + ifp->if_unit; + struct ifaddr *ifa = (struct ifaddr *)data; + struct ifreq *ifr = (struct ifreq *)data; + u_char *ptr; + + switch (cmd) { + + case SIOCSIFDSTADDR: + case SIOCAIFADDR: + case SIOCSIFADDR: + if (ifa->ifa_addr->sa_family != AF_INET) + return EAFNOSUPPORT; + ifp->if_flags |= IFF_UP; + /* FALLTHROUGH */ + case SIOCSIFFLAGS: + if ((!(ifp->if_flags & IFF_UP)) && (ifp->if_flags & IFF_RUNNING)) { + outb(sc->sc_port + lpt_control, 0x00); + ifp->if_flags &= ~IFF_RUNNING; + break; + } +#ifdef PC98 + /* XXX */ + return ENOBUFS; +#else + if (((ifp->if_flags & IFF_UP)) && (!(ifp->if_flags & IFF_RUNNING))) { + if (lpinittables()) + return ENOBUFS; + sc->sc_ifbuf = malloc(sc->sc_if.if_mtu + MLPIPHDRLEN, + M_DEVBUF, M_WAITOK); + if (!sc->sc_ifbuf) + return ENOBUFS; + + outb(sc->sc_port + lpt_control, LPC_ENA); + ifp->if_flags |= IFF_RUNNING; + } + break; +#endif + case SIOCSIFMTU: + ptr = sc->sc_ifbuf; + sc->sc_ifbuf = malloc(ifr->ifr_mtu+MLPIPHDRLEN, M_DEVBUF, M_NOWAIT); + if (!sc->sc_ifbuf) { + sc->sc_ifbuf = ptr; + return ENOBUFS; + } + if (ptr) + free(ptr,M_DEVBUF); + sc->sc_if.if_mtu = ifr->ifr_mtu; + break; + + case SIOCGIFMTU: + ifr->ifr_mtu = sc->sc_if.if_mtu; + break; + + case SIOCADDMULTI: + case SIOCDELMULTI: + if (ifr == 0) { + return EAFNOSUPPORT; /* XXX */ + } + switch (ifr->ifr_addr.sa_family) { + +#ifdef INET + case AF_INET: + break; +#endif + + default: + return EAFNOSUPPORT; + } + break; + + default: + lprintf(("LP:ioctl(0x%lx)\n", cmd)); + return EINVAL; + } + return 0; +} + +static __inline int +clpoutbyte (u_char byte, int spin, int data_port, int status_port) +{ + outb(data_port, ctxmitl[byte]); + while (inb(status_port) & CLPIP_SHAKE) + if (--spin == 0) { + return 1; + } + outb(data_port, ctxmith[byte]); + while (!(inb(status_port) & CLPIP_SHAKE)) + if (--spin == 0) { + return 1; + } + return 0; +} + +static __inline int +clpinbyte (int spin, int data_port, int status_port) +{ + int c, cl; + + while((inb(status_port) & CLPIP_SHAKE)) + if(!--spin) { + return -1; + } + cl = inb(status_port); + outb(data_port, 0x10); + + while(!(inb(status_port) & CLPIP_SHAKE)) + if(!--spin) { + return -1; + } + c = inb(status_port); + outb(data_port, 0x00); + + return (ctrecvl[cl] | ctrecvh[c]); +} + +static void +lpintr (int unit) +{ + struct lpt_softc *sc = lpt_sc + unit; + register int lpt_data_port = sc->sc_port + lpt_data; + register int lpt_stat_port = sc->sc_port + lpt_status; + int lpt_ctrl_port = sc->sc_port + lpt_control; + int len, s, j; + u_char *bp; + u_char c, cl; + struct mbuf *top; + + s = splhigh(); + + if (sc->sc_if.if_flags & IFF_LINK0) { + + /* Ack. the request */ + outb(lpt_data_port, 0x01); + + /* Get the packet length */ + j = clpinbyte(LPMAXSPIN2, lpt_data_port, lpt_stat_port); + if (j == -1) + goto err; + len = j; + j = clpinbyte(LPMAXSPIN2, lpt_data_port, lpt_stat_port); + if (j == -1) + goto err; + len = len + (j << 8); + if (len > sc->sc_if.if_mtu + MLPIPHDRLEN) + goto err; + + bp = sc->sc_ifbuf; + + while (len--) { + j = clpinbyte(LPMAXSPIN2, lpt_data_port, lpt_stat_port); + if (j == -1) { + goto err; + } + *bp++ = j; + } + /* Get and ignore checksum */ + j = clpinbyte(LPMAXSPIN2, lpt_data_port, lpt_stat_port); + if (j == -1) { + goto err; + } + + len = bp - sc->sc_ifbuf; + if (len <= CLPIPHDRLEN) + goto err; + + sc->sc_iferrs = 0; + + if (IF_QFULL(&ipintrq)) { + lprintf(("DROP")); + IF_DROP(&ipintrq); + goto done; + } + len -= CLPIPHDRLEN; + sc->sc_if.if_ipackets++; + sc->sc_if.if_ibytes += len; + top = m_devget(sc->sc_ifbuf + CLPIPHDRLEN, len, 0, &sc->sc_if, 0); + if (top) { + IF_ENQUEUE(&ipintrq, top); + schednetisr(NETISR_IP); + } + goto done; + } + while ((inb(lpt_stat_port) & LPIP_SHAKE)) { + len = sc->sc_if.if_mtu + LPIPHDRLEN; + bp = sc->sc_ifbuf; + while (len--) { + + cl = inb(lpt_stat_port); + outb(lpt_data_port, 8); + + j = LPMAXSPIN2; + while((inb(lpt_stat_port) & LPIP_SHAKE)) + if(!--j) goto err; + + c = inb(lpt_stat_port); + outb(lpt_data_port, 0); + + *bp++= trecvh[cl] | trecvl[c]; + + j = LPMAXSPIN2; + while (!((cl=inb(lpt_stat_port)) & LPIP_SHAKE)) { + if (cl != c && + (((cl = inb(lpt_stat_port)) ^ 0xb8) & 0xf8) == + (c & 0xf8)) + goto end; + if (!--j) goto err; + } + } + + end: + len = bp - sc->sc_ifbuf; + if (len <= LPIPHDRLEN) + goto err; + + sc->sc_iferrs = 0; + + if (IF_QFULL(&ipintrq)) { + lprintf(("DROP")); + IF_DROP(&ipintrq); + goto done; + } +#if NBPFILTER > 0 + if (sc->sc_if.if_bpf) { + bpf_tap(&sc->sc_if, sc->sc_ifbuf, len); + } +#endif + len -= LPIPHDRLEN; + sc->sc_if.if_ipackets++; + sc->sc_if.if_ibytes += len; + top = m_devget(sc->sc_ifbuf + LPIPHDRLEN, len, 0, &sc->sc_if, 0); + if (top) { + IF_ENQUEUE(&ipintrq, top); + schednetisr(NETISR_IP); + } + } + goto done; + + err: + outb(lpt_data_port, 0); + lprintf(("R")); + sc->sc_if.if_ierrors++; + sc->sc_iferrs++; + + /* + * We are not able to send receive anything for now, + * so stop wasting our time + */ + if (sc->sc_iferrs > LPMAXERRS) { + printf("lp%d: Too many errors, Going off-line.\n", unit); + outb(lpt_ctrl_port, 0x00); + sc->sc_if.if_flags &= ~IFF_RUNNING; + sc->sc_iferrs=0; + } + + done: + splx(s); + return; +} + +static __inline int +lpoutbyte (u_char byte, int spin, int data_port, int status_port) +{ + outb(data_port, txmith[byte]); + while (!(inb(status_port) & LPIP_SHAKE)) + if (--spin == 0) + return 1; + outb(data_port, txmitl[byte]); + while (inb(status_port) & LPIP_SHAKE) + if (--spin == 0) + return 1; + return 0; +} + +static int +lpoutput (struct ifnet *ifp, struct mbuf *m, + struct sockaddr *dst, struct rtentry *rt) +{ + register int lpt_data_port = lpt_sc[ifp->if_unit].sc_port + lpt_data; + register int lpt_stat_port = lpt_sc[ifp->if_unit].sc_port + lpt_status; +#ifndef PC98 + int lpt_ctrl_port = lpt_sc[ifp->if_unit].sc_port + lpt_control; +#endif + + int s, err; + struct mbuf *mm; + u_char *cp = "\0\0"; + u_char chksum = 0; + int count = 0; + int i; + int spin; + + /* We need a sensible value if we abort */ + cp++; + ifp->if_flags |= IFF_RUNNING; + + err = 1; /* assume we're aborting because of an error */ + + s = splhigh(); + +#ifndef PC98 + /* Suspend (on laptops) or receive-errors might have taken us offline */ + outb(lpt_ctrl_port, LPC_ENA); +#endif + + if (ifp->if_flags & IFF_LINK0) { + + if (!(inb(lpt_stat_port) & CLPIP_SHAKE)) { + lprintf(("&")); + lptintr(ifp->if_unit); + } + + /* Alert other end to pending packet */ + spin = LPMAXSPIN1; + outb(lpt_data_port, 0x08); + while ((inb(lpt_stat_port) & 0x08) == 0) + if (--spin == 0) { + goto nend; + } + + /* Calculate length of packet, then send that */ + + count += 14; /* Ethernet header len */ + + mm = m; + for (mm = m; mm; mm = mm->m_next) { + count += mm->m_len; + } + if (clpoutbyte(count & 0xFF, LPMAXSPIN1, lpt_data_port, lpt_stat_port)) + goto nend; + if (clpoutbyte((count >> 8) & 0xFF, LPMAXSPIN1, lpt_data_port, lpt_stat_port)) + goto nend; + + /* Send dummy ethernet header */ + for (i = 0; i < 12; i++) { + if (clpoutbyte(i, LPMAXSPIN1, lpt_data_port, lpt_stat_port)) + goto nend; + chksum += i; + } + + if (clpoutbyte(0x08, LPMAXSPIN1, lpt_data_port, lpt_stat_port)) + goto nend; + if (clpoutbyte(0x00, LPMAXSPIN1, lpt_data_port, lpt_stat_port)) + goto nend; + chksum += 0x08 + 0x00; /* Add into checksum */ + + mm = m; + do { + cp = mtod(mm, u_char *); + while (mm->m_len--) { + chksum += *cp; + if (clpoutbyte(*cp++, LPMAXSPIN2, lpt_data_port, lpt_stat_port)) + goto nend; + } + } while ((mm = mm->m_next)); + + /* Send checksum */ + if (clpoutbyte(chksum, LPMAXSPIN2, lpt_data_port, lpt_stat_port)) + goto nend; + + /* Go quiescent */ + outb(lpt_data_port, 0); + + err = 0; /* No errors */ + + nend: + if (err) { /* if we didn't timeout... */ + ifp->if_oerrors++; + lprintf(("X")); + } else { + ifp->if_opackets++; + ifp->if_obytes += m->m_pkthdr.len; + } + + m_freem(m); + + if (!(inb(lpt_stat_port) & CLPIP_SHAKE)) { + lprintf(("^")); + lptintr(ifp->if_unit); + } + (void) splx(s); + return 0; + } + + if (inb(lpt_stat_port) & LPIP_SHAKE) { + lprintf(("&")); + lptintr(ifp->if_unit); + } + + if (lpoutbyte(0x08, LPMAXSPIN1, lpt_data_port, lpt_stat_port)) + goto end; + if (lpoutbyte(0x00, LPMAXSPIN2, lpt_data_port, lpt_stat_port)) + goto end; + + mm = m; + do { + cp = mtod(mm,u_char *); + while (mm->m_len--) + if (lpoutbyte(*cp++, LPMAXSPIN2, lpt_data_port, lpt_stat_port)) + goto end; + } while ((mm = mm->m_next)); + + err = 0; /* no errors were encountered */ + + end: + --cp; + outb(lpt_data_port, txmitl[*cp] ^ 0x17); + + if (err) { /* if we didn't timeout... */ + ifp->if_oerrors++; + lprintf(("X")); + } else { + ifp->if_opackets++; + ifp->if_obytes += m->m_pkthdr.len; +#if NBPFILTER > 0 + if (ifp->if_bpf) { + /* + * We need to prepend the packet type as + * a two byte field. Cons up a dummy header + * to pacify bpf. This is safe because bpf + * will only read from the mbuf (i.e., it won't + * try to free it or keep a pointer to it). + */ + struct mbuf m0; + u_short hdr = 0x800; + + m0.m_next = m; + m0.m_len = 2; + m0.m_data = (char *)&hdr; + + bpf_mtap(ifp, &m0); + } +#endif + } + + m_freem(m); + + if (inb(lpt_stat_port) & LPIP_SHAKE) { + lprintf(("^")); + lptintr(ifp->if_unit); + } + + (void) splx(s); + return 0; +} + +#endif /* INET */ + +static int lpt_devsw_installed; + +static void lpt_drvinit(void *unused) +{ + + if( ! lpt_devsw_installed ) { + cdevsw_add(&lpt_cdevsw); + lpt_devsw_installed = 1; + } +} + +SYSINIT(lptdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,lpt_drvinit,NULL) + diff --git a/sys/pc98/pc98/ppc.c b/sys/pc98/pc98/ppc.c new file mode 100644 index 0000000..e21d841 --- /dev/null +++ b/sys/pc98/pc98/ppc.c @@ -0,0 +1,1846 @@ +/*- + * Copyright (c) 1997, 1998 Nicolas Souchu + * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + * + * $Id$ + * + */ +#include "ppc.h" + +#if NPPC > 0 + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/conf.h> +#include <sys/malloc.h> +#include <sys/kernel.h> + +#include <machine/clock.h> + +#include <vm/vm.h> +#include <vm/vm_param.h> +#include <vm/pmap.h> + +#include <i386/isa/isa_device.h> +#include <pc98/pc98/pc98.h> + +#include <dev/ppbus/ppbconf.h> +#include <dev/ppbus/ppb_msq.h> + +#include <pc98/pc98/ppcreg.h> + +#include "opt_ppc.h" + +#define LOG_PPC(function, ppc, string) \ + if (bootverbose) printf("%s: %s\n", function, string) + +static int ppcprobe(struct isa_device *); +static int ppcattach(struct isa_device *); + +struct isa_driver ppcdriver = { + ppcprobe, ppcattach, "ppc" +}; + +static struct ppc_data *ppcdata[NPPC]; +static int nppc = 0; + +static char *ppc_types[] = { + "SMC-like", "SMC FDC37C665GT", "SMC FDC37C666GT", "PC87332", "PC87306", + "82091AA", "Generic", "W83877F", "W83877AF", "Winbond", "PC87334", 0 +}; + +/* list of available modes */ +static char *ppc_avms[] = { + "COMPATIBLE", "NIBBLE-only", "PS2-only", "PS2/NIBBLE", "EPP-only", + "EPP/NIBBLE", "EPP/PS2", "EPP/PS2/NIBBLE", "ECP-only", + "ECP/NIBBLE", "ECP/PS2", "ECP/PS2/NIBBLE", "ECP/EPP", + "ECP/EPP/NIBBLE", "ECP/EPP/PS2", "ECP/EPP/PS2/NIBBLE", 0 +}; + +/* list of current executing modes + * Note that few modes do not actually exist. + */ +static char *ppc_modes[] = { + "COMPATIBLE", "NIBBLE", "PS/2", "PS/2", "EPP", + "EPP", "EPP", "EPP", "ECP", + "ECP", "ECP+PS2", "ECP+PS2", "ECP+EPP", + "ECP+EPP", "ECP+EPP", "ECP+EPP", 0 +}; + +static char *ppc_epp_protocol[] = { " (EPP 1.9)", " (EPP 1.7)", 0 }; + +/* + * BIOS printer list - used by BIOS probe. + */ +#define BIOS_PPC_PORTS 0x408 +#define BIOS_PORTS (short *)(KERNBASE+BIOS_PPC_PORTS) +#define BIOS_MAX_PPC 4 + +/* + * All these functions are default actions for IN/OUT operations. + * They may be redefined if needed. + */ +static void ppc_outsb_epp(int unit, char *addr, int cnt) { + outsb(ppcdata[unit]->ppc_base + PPC_EPP_DATA, addr, cnt); } +static void ppc_outsw_epp(int unit, char *addr, int cnt) { + outsw(ppcdata[unit]->ppc_base + PPC_EPP_DATA, addr, cnt); } +static void ppc_outsl_epp(int unit, char *addr, int cnt) { + outsl(ppcdata[unit]->ppc_base + PPC_EPP_DATA, addr, cnt); } +static void ppc_insb_epp(int unit, char *addr, int cnt) { + insb(ppcdata[unit]->ppc_base + PPC_EPP_DATA, addr, cnt); } +static void ppc_insw_epp(int unit, char *addr, int cnt) { + insw(ppcdata[unit]->ppc_base + PPC_EPP_DATA, addr, cnt); } +static void ppc_insl_epp(int unit, char *addr, int cnt) { + insl(ppcdata[unit]->ppc_base + PPC_EPP_DATA, addr, cnt); } + +static u_char ppc_rdtr(int unit) { return r_dtr(ppcdata[unit]); } +static u_char ppc_rstr(int unit) { return r_str(ppcdata[unit]); } +static u_char ppc_rctr(int unit) { return r_ctr(ppcdata[unit]); } +static u_char ppc_repp_A(int unit) { return r_epp_A(ppcdata[unit]); } +static u_char ppc_repp_D(int unit) { return r_epp_D(ppcdata[unit]); } +static u_char ppc_recr(int unit) { return r_ecr(ppcdata[unit]); } +static u_char ppc_rfifo(int unit) { return r_fifo(ppcdata[unit]); } + +static void ppc_wdtr(int unit, char byte) { w_dtr(ppcdata[unit], byte); } +static void ppc_wstr(int unit, char byte) { w_str(ppcdata[unit], byte); } +static void ppc_wctr(int unit, char byte) { w_ctr(ppcdata[unit], byte); } +static void ppc_wepp_A(int unit, char byte) { w_epp_A(ppcdata[unit], byte); } +static void ppc_wepp_D(int unit, char byte) { w_epp_D(ppcdata[unit], byte); } +static void ppc_wecr(int unit, char byte) { w_ecr(ppcdata[unit], byte); } +static void ppc_wfifo(int unit, char byte) { w_fifo(ppcdata[unit], byte); } + +static void ppc_reset_epp_timeout(int); +static void ppc_ecp_sync(int); +static ointhand2_t ppcintr; + +static int ppc_exec_microseq(int, struct ppb_microseq **); +static int ppc_generic_setmode(int, int); +static int ppc_smclike_setmode(int, int); + +static int ppc_read(int, char *, int, int); +static int ppc_write(int, char *, int, int); + +static struct ppb_adapter ppc_smclike_adapter = { + + 0, /* no intr handler, filled by chipset dependent code */ + + ppc_reset_epp_timeout, ppc_ecp_sync, + + ppc_exec_microseq, + + ppc_smclike_setmode, ppc_read, ppc_write, + + ppc_outsb_epp, ppc_outsw_epp, ppc_outsl_epp, + ppc_insb_epp, ppc_insw_epp, ppc_insl_epp, + + ppc_rdtr, ppc_rstr, ppc_rctr, ppc_repp_A, ppc_repp_D, ppc_recr, ppc_rfifo, + ppc_wdtr, ppc_wstr, ppc_wctr, ppc_wepp_A, ppc_wepp_D, ppc_wecr, ppc_wfifo +}; + +static struct ppb_adapter ppc_generic_adapter = { + + 0, /* no intr handler, filled by chipset dependent code */ + + ppc_reset_epp_timeout, ppc_ecp_sync, + + ppc_exec_microseq, + + ppc_generic_setmode, ppc_read, ppc_write, + + ppc_outsb_epp, ppc_outsw_epp, ppc_outsl_epp, + ppc_insb_epp, ppc_insw_epp, ppc_insl_epp, + + ppc_rdtr, ppc_rstr, ppc_rctr, ppc_repp_A, ppc_repp_D, ppc_recr, ppc_rfifo, + ppc_wdtr, ppc_wstr, ppc_wctr, ppc_wepp_A, ppc_wepp_D, ppc_wecr, ppc_wfifo +}; + +/* + * ppc_ecp_sync() XXX + */ +static void +ppc_ecp_sync(int unit) { + + struct ppc_data *ppc = ppcdata[unit]; + int i, r; + + if (!(ppc->ppc_avm & PPB_ECP)) + return; + + r = r_ecr(ppc); + if ((r & 0xe0) != PPC_ECR_EPP) + return; + + for (i = 0; i < 100; i++) { + r = r_ecr(ppc); + if (r & 0x1) + return; + DELAY(100); + } + + printf("ppc%d: ECP sync failed as data still " \ + "present in FIFO.\n", unit); + + return; +} + +/* + * ppc_detect_fifo() + * + * Detect parallel port FIFO + */ +static int +ppc_detect_fifo(struct ppc_data *ppc) +{ + char ecr_sav; + char ctr_sav, ctr, cc; + short i; + + /* save registers */ + ecr_sav = r_ecr(ppc); + ctr_sav = r_ctr(ppc); + + /* enter ECP configuration mode, no interrupt, no DMA */ + w_ecr(ppc, 0xf4); + + /* read PWord size - transfers in FIFO mode must be PWord aligned */ + ppc->ppc_pword = (r_cnfgA(ppc) & PPC_PWORD_MASK); + + /* XXX 16 and 32 bits implementations not supported */ + if (ppc->ppc_pword != PPC_PWORD_8) { + LOG_PPC(__FUNCTION__, ppc, "PWord not supported"); + goto error; + } + + w_ecr(ppc, 0x34); /* byte mode, no interrupt, no DMA */ + ctr = r_ctr(ppc); + w_ctr(ppc, ctr | PCD); /* set direction to 1 */ + + /* enter ECP test mode, no interrupt, no DMA */ + w_ecr(ppc, 0xd4); + + /* flush the FIFO */ + for (i=0; i<1024; i++) { + if (r_ecr(ppc) & PPC_FIFO_EMPTY) + break; + cc = r_fifo(ppc); + } + + if (i >= 1024) { + LOG_PPC(__FUNCTION__, ppc, "can't flush FIFO"); + goto error; + } + + /* enable interrupts, no DMA */ + w_ecr(ppc, 0xd0); + + /* determine readIntrThreshold + * fill the FIFO until serviceIntr is set + */ + for (i=0; i<1024; i++) { + w_fifo(ppc, (char)i); + if (!ppc->ppc_rthr && (r_ecr(ppc) & PPC_SERVICE_INTR)) { + /* readThreshold reached */ + ppc->ppc_rthr = i+1; + } + if (r_ecr(ppc) & PPC_FIFO_FULL) { + ppc->ppc_fifo = i+1; + break; + } + } + + if (i >= 1024) { + LOG_PPC(__FUNCTION__, ppc, "can't fill FIFO"); + goto error; + } + + w_ecr(ppc, 0xd4); /* test mode, no interrupt, no DMA */ + w_ctr(ppc, ctr & ~PCD); /* set direction to 0 */ + w_ecr(ppc, 0xd0); /* enable interrupts */ + + /* determine writeIntrThreshold + * empty the FIFO until serviceIntr is set + */ + for (i=ppc->ppc_fifo; i>0; i--) { + if (r_fifo(ppc) != (char)(ppc->ppc_fifo-i)) { + LOG_PPC(__FUNCTION__, ppc, "invalid data in FIFO"); + goto error; + } + if (r_ecr(ppc) & PPC_SERVICE_INTR) { + /* writeIntrThreshold reached */ + ppc->ppc_wthr = ppc->ppc_fifo - i+1; + } + /* if FIFO empty before the last byte, error */ + if (i>1 && (r_ecr(ppc) & PPC_FIFO_EMPTY)) { + LOG_PPC(__FUNCTION__, ppc, "data lost in FIFO"); + goto error; + } + } + + /* FIFO must be empty after the last byte */ + if (!(r_ecr(ppc) & PPC_FIFO_EMPTY)) { + LOG_PPC(__FUNCTION__, ppc, "can't empty the FIFO"); + goto error; + } + + w_ctr(ppc, ctr_sav); + w_ecr(ppc, ecr_sav); + + return (0); + +error: + w_ctr(ppc, ctr_sav); + w_ecr(ppc, ecr_sav); + + return (EINVAL); +} + +static int +ppc_detect_port(struct ppc_data *ppc) +{ + + w_ctr(ppc, 0x0c); /* To avoid missing PS2 ports */ + w_dtr(ppc, 0xaa); + if (r_dtr(ppc) != 0xaa) + return (0); + + return (1); +} + +/* + * ppc_pc873xx_detect + * + * Probe for a Natsemi PC873xx-family part. + * + * References in this function are to the National Semiconductor + * PC87332 datasheet TL/C/11930, May 1995 revision. + */ +static int pc873xx_basetab[] = {0x0398, 0x026e, 0x015c, 0x002e, 0}; +static int pc873xx_porttab[] = {0x0378, 0x03bc, 0x0278, 0}; +static int pc873xx_irqtab[] = {5, 7, 5, 0}; + +static int pc873xx_regstab[] = { + PC873_FER, PC873_FAR, PC873_PTR, + PC873_FCR, PC873_PCR, PC873_PMC, + PC873_TUP, PC873_SID, PC873_PNP0, + PC873_PNP1, PC873_LPTBA, -1 +}; + +static char *pc873xx_rnametab[] = { + "FER", "FAR", "PTR", "FCR", "PCR", + "PMC", "TUP", "SID", "PNP0", "PNP1", + "LPTBA", NULL +}; + +static int +ppc_pc873xx_detect(struct ppc_data *ppc, int chipset_mode) /* XXX mode never forced */ +{ + static int index = 0; + int idport, irq; + int ptr, pcr, val, i; + + while ((idport = pc873xx_basetab[index++])) { + + /* XXX should check first to see if this location is already claimed */ + + /* + * Pull the 873xx through the power-on ID cycle (2.2,1.). + * We can't use this to locate the chip as it may already have + * been used by the BIOS. + */ + (void)inb(idport); (void)inb(idport); + (void)inb(idport); (void)inb(idport); + + /* + * Read the SID byte. Possible values are : + * + * 01010xxx PC87334 + * 0001xxxx PC87332 + * 01110xxx PC87306 + */ + outb(idport, PC873_SID); + val = inb(idport + 1); + if ((val & 0xf0) == 0x10) { + ppc->ppc_type = NS_PC87332; + } else if ((val & 0xf8) == 0x70) { + ppc->ppc_type = NS_PC87306; + } else if ((val & 0xf8) == 0x50) { + ppc->ppc_type = NS_PC87334; + } else { + if (bootverbose && (val != 0xff)) + printf("PC873xx probe at 0x%x got unknown ID 0x%x\n", idport, val); + continue ; /* not recognised */ + } + + /* print registers */ + if (bootverbose) { + printf("PC873xx"); + for (i=0; pc873xx_regstab[i] != -1; i++) { + outb(idport, pc873xx_regstab[i]); + printf(" %s=0x%x", pc873xx_rnametab[i], + inb(idport + 1) & 0xff); + } + printf("\n"); + } + + /* + * We think we have one. Is it enabled and where we want it to be? + */ + outb(idport, PC873_FER); + val = inb(idport + 1); + if (!(val & PC873_PPENABLE)) { + if (bootverbose) + printf("PC873xx parallel port disabled\n"); + continue; + } + outb(idport, PC873_FAR); + val = inb(idport + 1) & 0x3; + /* XXX we should create a driver instance for every port found */ + if (pc873xx_porttab[val] != ppc->ppc_base) { + if (bootverbose) + printf("PC873xx at 0x%x not for driver at port 0x%x\n", + pc873xx_porttab[val], ppc->ppc_base); + continue; + } + + outb(idport, PC873_PTR); + ptr = inb(idport + 1); + + /* get irq settings */ + if (ppc->ppc_base == 0x378) + irq = (ptr & PC873_LPTBIRQ7) ? 7 : 5; + else + irq = pc873xx_irqtab[val]; + + if (bootverbose) + printf("PC873xx irq %d at 0x%x\n", irq, ppc->ppc_base); + + /* + * Check if irq settings are correct + */ + if (irq != ppc->ppc_irq) { + /* + * If the chipset is not locked and base address is 0x378, + * we have another chance + */ + if (ppc->ppc_base == 0x378 && !(ptr & PC873_CFGLOCK)) { + if (ppc->ppc_irq == 7) { + outb(idport + 1, (ptr | PC873_LPTBIRQ7)); + outb(idport + 1, (ptr | PC873_LPTBIRQ7)); + } else { + outb(idport + 1, (ptr & ~PC873_LPTBIRQ7)); + outb(idport + 1, (ptr & ~PC873_LPTBIRQ7)); + } + if (bootverbose) + printf("PC873xx irq set to %d\n", ppc->ppc_irq); + } else { + if (bootverbose) + printf("PC873xx sorry, can't change irq setting\n"); + } + } else { + if (bootverbose) + printf("PC873xx irq settings are correct\n"); + } + + outb(idport, PC873_PCR); + pcr = inb(idport + 1); + + if ((ptr & PC873_CFGLOCK) || !chipset_mode) { + if (bootverbose) + printf("PC873xx %s", (ptr & PC873_CFGLOCK)?"locked":"unlocked"); + + ppc->ppc_avm |= PPB_NIBBLE; + if (bootverbose) + printf(", NIBBLE"); + + if (pcr & PC873_EPPEN) { + ppc->ppc_avm |= PPB_EPP; + + if (bootverbose) + printf(", EPP"); + + if (pcr & PC873_EPP19) + ppc->ppc_epp = EPP_1_9; + else + ppc->ppc_epp = EPP_1_7; + + if ((ppc->ppc_type == NS_PC87332) && bootverbose) { + outb(idport, PC873_PTR); + ptr = inb(idport + 1); + if (ptr & PC873_EPPRDIR) + printf(", Regular mode"); + else + printf(", Automatic mode"); + } + } else if (pcr & PC873_ECPEN) { + ppc->ppc_avm |= PPB_ECP; + if (bootverbose) + printf(", ECP"); + + if (pcr & PC873_ECPCLK) { /* XXX */ + ppc->ppc_avm |= PPB_PS2; + if (bootverbose) + printf(", PS/2"); + } + } else { + outb(idport, PC873_PTR); + ptr = inb(idport + 1); + if (ptr & PC873_EXTENDED) { + ppc->ppc_avm |= PPB_SPP; + if (bootverbose) + printf(", SPP"); + } + } + } else { + if (bootverbose) + printf("PC873xx unlocked"); + + if (chipset_mode & PPB_ECP) { + if ((chipset_mode & PPB_EPP) && bootverbose) + printf(", ECP+EPP not supported"); + + pcr &= ~PC873_EPPEN; + pcr |= (PC873_ECPEN | PC873_ECPCLK); /* XXX */ + outb(idport + 1, pcr); + outb(idport + 1, pcr); + + if (bootverbose) + printf(", ECP"); + + } else if (chipset_mode & PPB_EPP) { + pcr &= ~(PC873_ECPEN | PC873_ECPCLK); + pcr |= (PC873_EPPEN | PC873_EPP19); + outb(idport + 1, pcr); + outb(idport + 1, pcr); + + ppc->ppc_epp = EPP_1_9; /* XXX */ + + if (bootverbose) + printf(", EPP1.9"); + + /* enable automatic direction turnover */ + if (ppc->ppc_type == NS_PC87332) { + outb(idport, PC873_PTR); + ptr = inb(idport + 1); + ptr &= ~PC873_EPPRDIR; + outb(idport + 1, ptr); + outb(idport + 1, ptr); + + if (bootverbose) + printf(", Automatic mode"); + } + } else { + pcr &= ~(PC873_ECPEN | PC873_ECPCLK | PC873_EPPEN); + outb(idport + 1, pcr); + outb(idport + 1, pcr); + + /* configure extended bit in PTR */ + outb(idport, PC873_PTR); + ptr = inb(idport + 1); + + if (chipset_mode & PPB_PS2) { + ptr |= PC873_EXTENDED; + + if (bootverbose) + printf(", PS/2"); + + } else { + /* default to NIBBLE mode */ + ptr &= ~PC873_EXTENDED; + + if (bootverbose) + printf(", NIBBLE"); + } + outb(idport + 1, ptr); + outb(idport + 1, ptr); + } + + ppc->ppc_avm = chipset_mode; + } + + if (bootverbose) + printf("\n"); + + ppc->ppc_link.adapter = &ppc_generic_adapter; + ppc_generic_setmode(ppc->ppc_unit, chipset_mode); + + return(chipset_mode); + } + return(-1); +} + +static int +ppc_check_epp_timeout(struct ppc_data *ppc) +{ + ppc_reset_epp_timeout(ppc->ppc_unit); + + return (!(r_str(ppc) & TIMEOUT)); +} + +/* + * ppc_smc37c66xgt_detect + * + * SMC FDC37C66xGT configuration. + */ +static int +ppc_smc37c66xgt_detect(struct ppc_data *ppc, int chipset_mode) +{ + int s, i; + u_char r; + int type = -1; + int csr = SMC66x_CSR; /* initial value is 0x3F0 */ + + int port_address[] = { -1 /* disabled */ , 0x3bc, 0x378, 0x278 }; + + +#define cio csr+1 /* config IO port is either 0x3F1 or 0x371 */ + + /* + * Detection: enter configuration mode and read CRD register. + */ + + s = splhigh(); + outb(csr, SMC665_iCODE); + outb(csr, SMC665_iCODE); + splx(s); + + outb(csr, 0xd); + if (inb(cio) == 0x65) { + type = SMC_37C665GT; + goto config; + } + + for (i = 0; i < 2; i++) { + s = splhigh(); + outb(csr, SMC666_iCODE); + outb(csr, SMC666_iCODE); + splx(s); + + outb(csr, 0xd); + if (inb(cio) == 0x66) { + type = SMC_37C666GT; + break; + } + + /* Another chance, CSR may be hard-configured to be at 0x370 */ + csr = SMC666_CSR; + } + +config: + /* + * If chipset not found, do not continue. + */ + if (type == -1) + return (-1); + + /* select CR1 */ + outb(csr, 0x1); + + /* read the port's address: bits 0 and 1 of CR1 */ + r = inb(cio) & SMC_CR1_ADDR; + if (port_address[(int)r] != ppc->ppc_base) + return (-1); + + ppc->ppc_type = type; + + /* + * CR1 and CR4 registers bits 3 and 0/1 for mode configuration + * If SPP mode is detected, try to set ECP+EPP mode + */ + + if (bootverbose) { + outb(csr, 0x1); + printf("ppc%d: SMC registers CR1=0x%x", ppc->ppc_unit, + inb(cio) & 0xff); + + outb(csr, 0x4); + printf(" CR4=0x%x", inb(cio) & 0xff); + } + + /* select CR1 */ + outb(csr, 0x1); + + if (!chipset_mode) { + /* autodetect mode */ + + /* 666GT is ~certainly~ hardwired to an extended ECP+EPP mode */ + if (type == SMC_37C666GT) { + ppc->ppc_avm |= PPB_ECP | PPB_EPP | PPB_SPP; + if (bootverbose) + printf(" configuration hardwired, supposing " \ + "ECP+EPP SPP"); + + } else + if ((inb(cio) & SMC_CR1_MODE) == 0) { + /* already in extended parallel port mode, read CR4 */ + outb(csr, 0x4); + r = (inb(cio) & SMC_CR4_EMODE); + + switch (r) { + case SMC_SPP: + ppc->ppc_avm |= PPB_SPP; + if (bootverbose) + printf(" SPP"); + break; + + case SMC_EPPSPP: + ppc->ppc_avm |= PPB_EPP | PPB_SPP; + if (bootverbose) + printf(" EPP SPP"); + break; + + case SMC_ECP: + ppc->ppc_avm |= PPB_ECP | PPB_SPP; + if (bootverbose) + printf(" ECP SPP"); + break; + + case SMC_ECPEPP: + ppc->ppc_avm |= PPB_ECP | PPB_EPP | PPB_SPP; + if (bootverbose) + printf(" ECP+EPP SPP"); + break; + } + } else { + /* not an extended port mode */ + ppc->ppc_avm |= PPB_SPP; + if (bootverbose) + printf(" SPP"); + } + + } else { + /* mode forced */ + ppc->ppc_avm = chipset_mode; + + /* 666GT is ~certainly~ hardwired to an extended ECP+EPP mode */ + if (type == SMC_37C666GT) + goto end_detect; + + r = inb(cio); + if ((chipset_mode & (PPB_ECP | PPB_EPP)) == 0) { + /* do not use ECP when the mode is not forced to */ + outb(cio, r | SMC_CR1_MODE); + if (bootverbose) + printf(" SPP"); + } else { + /* an extended mode is selected */ + outb(cio, r & ~SMC_CR1_MODE); + + /* read CR4 register and reset mode field */ + outb(csr, 0x4); + r = inb(cio) & ~SMC_CR4_EMODE; + + if (chipset_mode & PPB_ECP) { + if (chipset_mode & PPB_EPP) { + outb(cio, r | SMC_ECPEPP); + if (bootverbose) + printf(" ECP+EPP"); + } else { + outb(cio, r | SMC_ECP); + if (bootverbose) + printf(" ECP"); + } + } else { + /* PPB_EPP is set */ + outb(cio, r | SMC_EPPSPP); + if (bootverbose) + printf(" EPP SPP"); + } + } + ppc->ppc_avm = chipset_mode; + } + + /* set FIFO threshold to 16 */ + if (ppc->ppc_avm & PPB_ECP) { + /* select CRA */ + outb(csr, 0xa); + outb(cio, 16); + } + +end_detect: + + if (bootverbose) + printf ("\n"); + + if (ppc->ppc_avm & PPB_EPP) { + /* select CR4 */ + outb(csr, 0x4); + r = inb(cio); + + /* + * Set the EPP protocol... + * Low=EPP 1.9 (1284 standard) and High=EPP 1.7 + */ + if (ppc->ppc_epp == EPP_1_9) + outb(cio, (r & ~SMC_CR4_EPPTYPE)); + else + outb(cio, (r | SMC_CR4_EPPTYPE)); + } + + /* end config mode */ + outb(csr, 0xaa); + + ppc->ppc_link.adapter = &ppc_smclike_adapter; + ppc_smclike_setmode(ppc->ppc_unit, chipset_mode); + + return (chipset_mode); +} + +/* + * Winbond W83877F stuff + * + * EFER: extended function enable register + * EFIR: extended function index register + * EFDR: extended function data register + */ +#define efir ((efer == 0x250) ? 0x251 : 0x3f0) +#define efdr ((efer == 0x250) ? 0x252 : 0x3f1) + +static int w83877f_efers[] = { 0x250, 0x3f0, 0x3f0, 0x250 }; +static int w83877f_keys[] = { 0x89, 0x86, 0x87, 0x88 }; +static int w83877f_keyiter[] = { 1, 2, 2, 1 }; +static int w83877f_hefs[] = { WINB_HEFERE, WINB_HEFRAS, WINB_HEFERE | WINB_HEFRAS, 0 }; + +static int +ppc_w83877f_detect(struct ppc_data *ppc, int chipset_mode) +{ + int i, j, efer; + unsigned char r, hefere, hefras; + + for (i = 0; i < 4; i ++) { + /* first try to enable configuration registers */ + efer = w83877f_efers[i]; + + /* write the key to the EFER */ + for (j = 0; j < w83877f_keyiter[i]; j ++) + outb (efer, w83877f_keys[i]); + + /* then check HEFERE and HEFRAS bits */ + outb (efir, 0x0c); + hefere = inb(efdr) & WINB_HEFERE; + + outb (efir, 0x16); + hefras = inb(efdr) & WINB_HEFRAS; + + /* + * HEFRAS HEFERE + * 0 1 write 89h to 250h (power-on default) + * 1 0 write 86h twice to 3f0h + * 1 1 write 87h twice to 3f0h + * 0 0 write 88h to 250h + */ + if ((hefere | hefras) == w83877f_hefs[i]) + goto found; + } + + return (-1); /* failed */ + +found: + /* check base port address - read from CR23 */ + outb(efir, 0x23); + if (ppc->ppc_base != inb(efdr) * 4) /* 4 bytes boundaries */ + return (-1); + + /* read CHIP ID from CR9/bits0-3 */ + outb(efir, 0x9); + + switch (inb(efdr) & WINB_CHIPID) { + case WINB_W83877F_ID: + ppc->ppc_type = WINB_W83877F; + break; + + case WINB_W83877AF_ID: + ppc->ppc_type = WINB_W83877AF; + break; + + default: + ppc->ppc_type = WINB_UNKNOWN; + } + + if (bootverbose) { + /* dump of registers */ + printf("ppc%d: 0x%x - ", ppc->ppc_unit, w83877f_keys[i]); + for (i = 0; i <= 0xd; i ++) { + outb(efir, i); + printf("0x%x ", inb(efdr)); + } + for (i = 0x10; i <= 0x17; i ++) { + outb(efir, i); + printf("0x%x ", inb(efdr)); + } + outb(efir, 0x1e); + printf("0x%x ", inb(efdr)); + for (i = 0x20; i <= 0x29; i ++) { + outb(efir, i); + printf("0x%x ", inb(efdr)); + } + printf("\n"); + printf("ppc%d:", ppc->ppc_unit); + } + + ppc->ppc_link.adapter = &ppc_generic_adapter; + + if (!chipset_mode) { + /* autodetect mode */ + + /* select CR0 */ + outb(efir, 0x0); + r = inb(efdr) & (WINB_PRTMODS0 | WINB_PRTMODS1); + + /* select CR9 */ + outb(efir, 0x9); + r |= (inb(efdr) & WINB_PRTMODS2); + + switch (r) { + case WINB_W83757: + if (bootverbose) + printf("ppc%d: W83757 compatible mode\n", + ppc->ppc_unit); + return (-1); /* generic or SMC-like */ + + case WINB_EXTFDC: + case WINB_EXTADP: + case WINB_EXT2FDD: + case WINB_JOYSTICK: + if (bootverbose) + printf(" not in parallel port mode\n"); + return (-1); + + case (WINB_PARALLEL | WINB_EPP_SPP): + ppc->ppc_avm |= PPB_EPP | PPB_SPP; + if (bootverbose) + printf(" EPP SPP"); + break; + + case (WINB_PARALLEL | WINB_ECP): + ppc->ppc_avm |= PPB_ECP | PPB_SPP; + if (bootverbose) + printf(" ECP SPP"); + break; + + case (WINB_PARALLEL | WINB_ECP_EPP): + ppc->ppc_avm |= PPB_ECP | PPB_EPP | PPB_SPP; + ppc->ppc_link.adapter = &ppc_smclike_adapter; + + if (bootverbose) + printf(" ECP+EPP SPP"); + break; + default: + printf("%s: unknown case (0x%x)!\n", __FUNCTION__, r); + } + + } else { + /* mode forced */ + + /* select CR9 and set PRTMODS2 bit */ + outb(efir, 0x9); + outb(efdr, inb(efdr) & ~WINB_PRTMODS2); + + /* select CR0 and reset PRTMODSx bits */ + outb(efir, 0x0); + outb(efdr, inb(efdr) & ~(WINB_PRTMODS0 | WINB_PRTMODS1)); + + if (chipset_mode & PPB_ECP) { + if (chipset_mode & PPB_EPP) { + outb(efdr, inb(efdr) | WINB_ECP_EPP); + if (bootverbose) + printf(" ECP+EPP"); + + ppc->ppc_link.adapter = &ppc_smclike_adapter; + + } else { + outb(efdr, inb(efdr) | WINB_ECP); + if (bootverbose) + printf(" ECP"); + } + } else { + /* select EPP_SPP otherwise */ + outb(efdr, inb(efdr) | WINB_EPP_SPP); + if (bootverbose) + printf(" EPP SPP"); + } + ppc->ppc_avm = chipset_mode; + } + + if (bootverbose) + printf("\n"); + + /* exit configuration mode */ + outb(efer, 0xaa); + + ppc->ppc_link.adapter->setmode(ppc->ppc_unit, chipset_mode); + + return (chipset_mode); +} + +/* + * ppc_generic_detect + */ +static int +ppc_generic_detect(struct ppc_data *ppc, int chipset_mode) +{ + /* default to generic */ + ppc->ppc_link.adapter = &ppc_generic_adapter; + + if (bootverbose) + printf("ppc%d:", ppc->ppc_unit); + + if (!chipset_mode) { + /* first, check for ECP */ + w_ecr(ppc, PPC_ECR_PS2); + if ((r_ecr(ppc) & 0xe0) == PPC_ECR_PS2) { + ppc->ppc_avm |= PPB_ECP | PPB_SPP; + if (bootverbose) + printf(" ECP SPP"); + + /* search for SMC style ECP+EPP mode */ + w_ecr(ppc, PPC_ECR_EPP); + } + + /* try to reset EPP timeout bit */ + if (ppc_check_epp_timeout(ppc)) { + ppc->ppc_avm |= PPB_EPP; + + if (ppc->ppc_avm & PPB_ECP) { + /* SMC like chipset found */ + ppc->ppc_type = SMC_LIKE; + ppc->ppc_link.adapter = &ppc_smclike_adapter; + + if (bootverbose) + printf(" ECP+EPP"); + } else { + if (bootverbose) + printf(" EPP"); + } + } else { + /* restore to standard mode */ + w_ecr(ppc, PPC_ECR_STD); + } + + /* XXX try to detect NIBBLE and PS2 modes */ + ppc->ppc_avm |= PPB_NIBBLE; + + if (bootverbose) + printf(" SPP"); + + } else { + ppc->ppc_avm = chipset_mode; + } + + if (bootverbose) + printf("\n"); + + ppc->ppc_link.adapter->setmode(ppc->ppc_unit, chipset_mode); + + return (chipset_mode); +} + +/* + * ppc_detect() + * + * mode is the mode suggested at boot + */ +static int +ppc_detect(struct ppc_data *ppc, int chipset_mode) { + + int i, mode; + + /* list of supported chipsets */ + int (*chipset_detect[])(struct ppc_data *, int) = { + ppc_pc873xx_detect, + ppc_smc37c66xgt_detect, + ppc_w83877f_detect, + ppc_generic_detect, + NULL + }; + + /* if can't find the port and mode not forced return error */ + if (!ppc_detect_port(ppc) && chipset_mode == 0) + return (EIO); /* failed, port not present */ + + /* assume centronics compatible mode is supported */ + ppc->ppc_avm = PPB_COMPATIBLE; + + /* we have to differenciate available chipset modes, + * chipset running modes and IEEE-1284 operating modes + * + * after detection, the port must support running in compatible mode + */ + if (ppc->ppc_flags & 0x40) { + if (bootverbose) + printf("ppc: chipset forced to generic\n"); + + ppc->ppc_mode = ppc_generic_detect(ppc, chipset_mode); + + } else { + for (i=0; chipset_detect[i] != NULL; i++) { + if ((mode = chipset_detect[i](ppc, chipset_mode)) != -1) { + ppc->ppc_mode = mode; + break; + } + } + } + + /* configure/detect ECP FIFO */ + if ((ppc->ppc_avm & PPB_ECP) && !(ppc->ppc_flags & 0x80)) + ppc_detect_fifo(ppc); + + return (0); +} + +/* + * ppc_exec_microseq() + * + * Execute a microsequence. + * Microsequence mechanism is supposed to handle fast I/O operations. + */ +static int +ppc_exec_microseq(int unit, struct ppb_microseq **p_msq) +{ + struct ppc_data *ppc = ppcdata[unit]; + struct ppb_microseq *mi; + char cc, *p; + int i, iter, len; + int error; + + register int reg; + register char mask; + register int accum = 0; + register char *ptr = 0; + + struct ppb_microseq *stack = 0; + +/* microsequence registers are equivalent to PC-like port registers */ +#define r_reg(register,ppc) (inb((ppc)->ppc_base + register)) +#define w_reg(register,ppc,byte) outb((ppc)->ppc_base + register, byte) + +#define INCR_PC (mi ++) /* increment program counter */ + + mi = *p_msq; + for (;;) { + switch (mi->opcode) { + case MS_OP_RSET: + cc = r_reg(mi->arg[0].i, ppc); + cc &= (char)mi->arg[2].i; /* clear mask */ + cc |= (char)mi->arg[1].i; /* assert mask */ + w_reg(mi->arg[0].i, ppc, cc); + INCR_PC; + break; + + case MS_OP_RASSERT_P: + reg = mi->arg[1].i; + ptr = ppc->ppc_ptr; + + if ((len = mi->arg[0].i) == MS_ACCUM) { + accum = ppc->ppc_accum; + for (; accum; accum--) + w_reg(reg, ppc, *ptr++); + ppc->ppc_accum = accum; + } else + for (i=0; i<len; i++) + w_reg(reg, ppc, *ptr++); + ppc->ppc_ptr = ptr; + + INCR_PC; + break; + + case MS_OP_RFETCH_P: + reg = mi->arg[1].i; + mask = (char)mi->arg[2].i; + ptr = ppc->ppc_ptr; + + if ((len = mi->arg[0].i) == MS_ACCUM) { + accum = ppc->ppc_accum; + for (; accum; accum--) + *ptr++ = r_reg(reg, ppc) & mask; + ppc->ppc_accum = accum; + } else + for (i=0; i<len; i++) + *ptr++ = r_reg(reg, ppc) & mask; + ppc->ppc_ptr = ptr; + + INCR_PC; + break; + + case MS_OP_RFETCH: + *((char *) mi->arg[2].p) = r_reg(mi->arg[0].i, ppc) & + (char)mi->arg[1].i; + INCR_PC; + break; + + case MS_OP_RASSERT: + case MS_OP_DELAY: + + /* let's suppose the next instr. is the same */ + prefetch: + for (;mi->opcode == MS_OP_RASSERT; INCR_PC) + w_reg(mi->arg[0].i, ppc, (char)mi->arg[1].i); + + if (mi->opcode == MS_OP_DELAY) { + DELAY(mi->arg[0].i); + INCR_PC; + goto prefetch; + } + break; + + case MS_OP_ADELAY: + if (mi->arg[0].i) + tsleep(NULL, PPBPRI, "ppbdelay", + mi->arg[0].i * (hz/1000)); + INCR_PC; + break; + + case MS_OP_TRIG: + reg = mi->arg[0].i; + iter = mi->arg[1].i; + p = (char *)mi->arg[2].p; + + /* XXX delay limited to 255 us */ + for (i=0; i<iter; i++) { + w_reg(reg, ppc, *p++); + DELAY((unsigned char)*p++); + } + INCR_PC; + break; + + case MS_OP_SET: + ppc->ppc_accum = mi->arg[0].i; + INCR_PC; + break; + + case MS_OP_DBRA: + if (--ppc->ppc_accum > 0) + mi += mi->arg[0].i; + INCR_PC; + break; + + case MS_OP_BRSET: + cc = r_str(ppc); + if ((cc & (char)mi->arg[0].i) == (char)mi->arg[0].i) + mi += mi->arg[1].i; + INCR_PC; + break; + + case MS_OP_BRCLEAR: + cc = r_str(ppc); + if ((cc & (char)mi->arg[0].i) == 0) + mi += mi->arg[1].i; + INCR_PC; + break; + + case MS_OP_BRSTAT: + cc = r_str(ppc); + if ((cc & ((char)mi->arg[0].i | (char)mi->arg[1].i)) == + (char)mi->arg[0].i) + mi += mi->arg[2].i; + INCR_PC; + break; + + case MS_OP_C_CALL: + /* + * If the C call returns !0 then end the microseq. + * The current state of ptr is passed to the C function + */ + if ((error = mi->arg[0].f(mi->arg[1].p, ppc->ppc_ptr))) + return (error); + + INCR_PC; + break; + + case MS_OP_PTR: + ppc->ppc_ptr = (char *)mi->arg[0].p; + INCR_PC; + break; + + case MS_OP_CALL: + if (stack) + panic("%s: too much calls", __FUNCTION__); + + if (mi->arg[0].p) { + /* store the state of the actual + * microsequence + */ + stack = mi; + + /* jump to the new microsequence */ + mi = (struct ppb_microseq *)mi->arg[0].p; + } else + INCR_PC; + + break; + + case MS_OP_SUBRET: + /* retrieve microseq and pc state before the call */ + mi = stack; + + /* reset the stack */ + stack = 0; + + /* XXX return code */ + + INCR_PC; + break; + + case MS_OP_PUT: + case MS_OP_GET: + case MS_OP_RET: + /* can't return to ppb level during the execution + * of a submicrosequence */ + if (stack) + panic("%s: can't return to ppb level", + __FUNCTION__); + + /* update pc for ppb level of execution */ + *p_msq = mi; + + /* return to ppb level of execution */ + return (0); + + default: + panic("%s: unknown microsequence opcode 0x%x", + __FUNCTION__, mi->opcode); + } + } + + /* unreached */ +} + +static void +ppcintr(int unit) +{ + struct ppc_data *ppc = ppcdata[unit]; + u_char ctr, ecr, str; + + str = r_str(ppc); + ctr = r_ctr(ppc); + ecr = r_ecr(ppc); + +#if PPC_DEBUG > 1 + printf("![%x/%x/%x]", ctr, ecr, str); +#endif + + /* don't use ecp mode with IRQENABLE set */ + if (ctr & IRQENABLE) { + /* call upper code */ + ppb_intr(&ppc->ppc_link); + return; + } + + /* interrupts are generated by nFault signal + * only in ECP mode */ + if ((str & nFAULT) && (ppc->ppc_mode & PPB_ECP)) { + /* check if ppc driver has programmed the + * nFault interrupt */ + if (ppc->ppc_irqstat & PPC_IRQ_nFAULT) { + + w_ecr(ppc, ecr | PPC_nFAULT_INTR); + ppc->ppc_irqstat &= ~PPC_IRQ_nFAULT; + } else { + /* call upper code */ + ppb_intr(&ppc->ppc_link); + return; + } + } + + if (ppc->ppc_irqstat & PPC_IRQ_DMA) { + /* disable interrupts (should be done by hardware though) */ + w_ecr(ppc, ecr | PPC_SERVICE_INTR); + ppc->ppc_irqstat &= ~PPC_IRQ_DMA; + ecr = r_ecr(ppc); + + /* check if DMA completed */ + if ((ppc->ppc_avm & PPB_ECP) && (ecr & PPC_ENABLE_DMA)) { +#ifdef PPC_DEBUG + printf("a"); +#endif + /* stop DMA */ + w_ecr(ppc, ecr & ~PPC_ENABLE_DMA); + ecr = r_ecr(ppc); + + if (ppc->ppc_dmastat == PPC_DMA_STARTED) { +#ifdef PPC_DEBUG + printf("d"); +#endif + isa_dmadone( + ppc->ppc_dmaflags, + ppc->ppc_dmaddr, + ppc->ppc_dmacnt, + ppc->ppc_dmachan); + + ppc->ppc_dmastat = PPC_DMA_COMPLETE; + + /* wakeup the waiting process */ + wakeup((caddr_t)ppc); + } + } + } else if (ppc->ppc_irqstat & PPC_IRQ_FIFO) { + + /* classic interrupt I/O */ + ppc->ppc_irqstat &= ~PPC_IRQ_FIFO; + + } + + return; +} + +static int +ppc_read(int unit, char *buf, int len, int mode) +{ + return (EINVAL); +} + +/* + * Call this function if you want to send data in any advanced mode + * of your parallel port: FIFO, DMA + * + * If what you want is not possible (no ECP, no DMA...), + * EINVAL is returned + */ +static int +ppc_write(int unit, char *buf, int len, int how) +{ + struct ppc_data *ppc = ppcdata[unit]; + char ecr, ecr_sav, ctr, ctr_sav; + int s, error = 0; + int spin; + +#ifdef PPC_DEBUG + printf("w"); +#endif + + ecr_sav = r_ecr(ppc); + ctr_sav = r_ctr(ppc); + + /* + * Send buffer with DMA, FIFO and interrupts + */ + if (ppc->ppc_avm & PPB_ECP) { + + if (ppc->ppc_dmachan >= 0) { + + /* byte mode, no intr, no DMA, dir=0, flush fifo + */ + ecr = PPC_ECR_STD | PPC_DISABLE_INTR; + w_ecr(ppc, ecr); + + /* disable nAck interrupts */ + ctr = r_ctr(ppc); + ctr &= ~IRQENABLE; + w_ctr(ppc, ctr); + + ppc->ppc_dmaflags = 0; + ppc->ppc_dmaddr = (caddr_t)buf; + ppc->ppc_dmacnt = (u_int)len; + + switch (ppc->ppc_mode) { + case PPB_COMPATIBLE: + /* compatible mode with FIFO, no intr, DMA, dir=0 */ + ecr = PPC_ECR_FIFO | PPC_DISABLE_INTR | PPC_ENABLE_DMA; + break; + case PPB_ECP: + ecr = PPC_ECR_ECP | PPC_DISABLE_INTR | PPC_ENABLE_DMA; + break; + default: + error = EINVAL; + goto error; + } + + w_ecr(ppc, ecr); + ecr = r_ecr(ppc); + + /* enter splhigh() not to be preempted + * by the dma interrupt, we may miss + * the wakeup otherwise + */ + s = splhigh(); + + ppc->ppc_dmastat = PPC_DMA_INIT; + + /* enable interrupts */ + ecr &= ~PPC_SERVICE_INTR; + ppc->ppc_irqstat = PPC_IRQ_DMA; + w_ecr(ppc, ecr); + + isa_dmastart( + ppc->ppc_dmaflags, + ppc->ppc_dmaddr, + ppc->ppc_dmacnt, + ppc->ppc_dmachan); +#ifdef PPC_DEBUG + printf("s%d", ppc->ppc_dmacnt); +#endif + ppc->ppc_dmastat = PPC_DMA_STARTED; + + /* Wait for the DMA completed interrupt. We hope we won't + * miss it, otherwise a signal will be necessary to unlock the + * process. + */ + do { + /* release CPU */ + error = tsleep((caddr_t)ppc, + PPBPRI | PCATCH, "ppcdma", 0); + + } while (error == EWOULDBLOCK); + + splx(s); + + if (error) { +#ifdef PPC_DEBUG + printf("i"); +#endif + /* stop DMA */ + isa_dmadone( + ppc->ppc_dmaflags, ppc->ppc_dmaddr, + ppc->ppc_dmacnt, ppc->ppc_dmachan); + + /* no dma, no interrupt, flush the fifo */ + w_ecr(ppc, PPC_ECR_RESET); + + ppc->ppc_dmastat = PPC_DMA_INTERRUPTED; + goto error; + } + + /* wait for an empty fifo */ + while (!(r_ecr(ppc) & PPC_FIFO_EMPTY)) { + + for (spin=100; spin; spin--) + if (r_ecr(ppc) & PPC_FIFO_EMPTY) + goto fifo_empty; +#ifdef PPC_DEBUG + printf("Z"); +#endif + error = tsleep((caddr_t)ppc, PPBPRI | PCATCH, "ppcfifo", hz/100); + if (error != EWOULDBLOCK) { +#ifdef PPC_DEBUG + printf("I"); +#endif + /* no dma, no interrupt, flush the fifo */ + w_ecr(ppc, PPC_ECR_RESET); + + ppc->ppc_dmastat = PPC_DMA_INTERRUPTED; + error = EINTR; + goto error; + } + } + +fifo_empty: + /* no dma, no interrupt, flush the fifo */ + w_ecr(ppc, PPC_ECR_RESET); + + } else + error = EINVAL; /* XXX we should FIFO and + * interrupts */ + } else + error = EINVAL; + +error: + + /* PDRQ must be kept unasserted until nPDACK is + * deasserted for a minimum of 350ns (SMC datasheet) + * + * Consequence may be a FIFO that never empty + */ + DELAY(1); + + w_ecr(ppc, ecr_sav); + w_ctr(ppc, ctr_sav); + + return (error); +} + +/* + * Configure current operating mode + */ +static int +ppc_generic_setmode(int unit, int mode) +{ + struct ppc_data *ppc = ppcdata[unit]; + u_char ecr = 0; + + /* check if mode is available */ + if (mode && !(ppc->ppc_avm & mode)) + return (EINVAL); + + /* if ECP mode, configure ecr register */ + if (ppc->ppc_avm & PPB_ECP) { + /* return to byte mode (keeping direction bit), + * no interrupt, no DMA to be able to change to + * ECP + */ + w_ecr(ppc, PPC_ECR_RESET); + ecr = PPC_DISABLE_INTR; + + if (mode & PPB_EPP) + return (EINVAL); + else if (mode & PPB_ECP) + /* select ECP mode */ + ecr |= PPC_ECR_ECP; + else if (mode & PPB_PS2) + /* select PS2 mode with ECP */ + ecr |= PPC_ECR_PS2; + else + /* select COMPATIBLE/NIBBLE mode */ + ecr |= PPC_ECR_STD; + + w_ecr(ppc, ecr); + } + + ppc->ppc_mode = mode; + + return (0); +} + +/* + * The ppc driver is free to choose options like FIFO or DMA + * if ECP mode is available. + * + * The 'RAW' option allows the upper drivers to force the ppc mode + * even with FIFO, DMA available. + */ +int +ppc_smclike_setmode(int unit, int mode) +{ + struct ppc_data *ppc = ppcdata[unit]; + u_char ecr = 0; + + /* check if mode is available */ + if (mode && !(ppc->ppc_avm & mode)) + return (EINVAL); + + /* if ECP mode, configure ecr register */ + if (ppc->ppc_avm & PPB_ECP) { + /* return to byte mode (keeping direction bit), + * no interrupt, no DMA to be able to change to + * ECP or EPP mode + */ + w_ecr(ppc, PPC_ECR_RESET); + ecr = PPC_DISABLE_INTR; + + if (mode & PPB_EPP) + /* select EPP mode */ + ecr |= PPC_ECR_EPP; + else if (mode & PPB_ECP) + /* select ECP mode */ + ecr |= PPC_ECR_ECP; + else if (mode & PPB_PS2) + /* select PS2 mode with ECP */ + ecr |= PPC_ECR_PS2; + else + /* select COMPATIBLE/NIBBLE mode */ + ecr |= PPC_ECR_STD; + + w_ecr(ppc, ecr); + } + + ppc->ppc_mode = mode; + + return (0); +} + +/* + * EPP timeout, according to the PC87332 manual + * Semantics of clearing EPP timeout bit. + * PC87332 - reading SPP_STR does it... + * SMC - write 1 to EPP timeout bit XXX + * Others - (?) write 0 to EPP timeout bit + */ +static void +ppc_reset_epp_timeout(int unit) +{ + struct ppc_data *ppc = ppcdata[unit]; + register char r; + + r = r_str(ppc); + w_str(ppc, r | 0x1); + w_str(ppc, r & 0xfe); + + return; +} + +static int +ppcprobe(struct isa_device *dvp) +{ + static short next_bios_ppc = 0; + struct ppc_data *ppc; +#ifdef PC98 +#define PC98_IEEE_1284_DISABLE 0x100 +#define PC98_IEEE_1284_PORT 0x140 + + unsigned int pc98_ieee_mode = 0x00; + unsigned int tmp; +#endif + + /* + * If port not specified, use bios list. + */ + if(dvp->id_iobase < 0) { +#ifndef PC98 + if((next_bios_ppc < BIOS_MAX_PPC) && + (*(BIOS_PORTS+next_bios_ppc) != 0) ) { + dvp->id_iobase = *(BIOS_PORTS+next_bios_ppc++); + if (bootverbose) + printf("ppc: parallel port found at 0x%x\n", + dvp->id_iobase); + } else +#else + if(next_bios_ppc == 0) { + /* Use default IEEE-1284 port of NEC PC-98x1 */ + dvp->id_iobase = PC98_IEEE_1284_PORT; + next_bios_ppc += 1; + if (bootverbose) + printf("ppc: parallel port found at 0x%x\n", + dvp->id_iobase); + } else +#endif + return (0); + } + + /* + * Port was explicitly specified. + * This allows probing of ports unknown to the BIOS. + */ + + /* + * Allocate the ppc_data structure. + */ + ppc = malloc(sizeof(struct ppc_data), M_DEVBUF, M_NOWAIT); + if (!ppc) { + printf("ppc: cannot malloc!\n"); + goto error; + } + bzero(ppc, sizeof(struct ppc_data)); + + ppc->ppc_base = dvp->id_iobase; + ppc->ppc_unit = dvp->id_unit; + ppc->ppc_type = GENERIC; + + /* store boot flags */ + ppc->ppc_flags = dvp->id_flags; + + ppc->ppc_mode = PPB_COMPATIBLE; + ppc->ppc_epp = (dvp->id_flags & 0x10) >> 4; + + /* + * XXX Try and detect if interrupts are working + */ + if (!(dvp->id_flags & 0x20) && dvp->id_irq) + ppc->ppc_irq = ffs(dvp->id_irq) - 1; + + ppc->ppc_dmachan = dvp->id_drq; + + ppcdata[ppc->ppc_unit] = ppc; + nppc ++; + + /* + * Link the Parallel Port Chipset (adapter) to + * the future ppbus. Default to a generic chipset + */ + ppc->ppc_link.adapter_unit = ppc->ppc_unit; + ppc->ppc_link.adapter = &ppc_generic_adapter; + +#ifdef PC98 + /* + * IEEE STD 1284 Function Check and Enable + * for default IEEE-1284 port of NEC PC-98x1 + */ + if ((ppc->ppc_base == PC98_IEEE_1284_PORT) && + !(dvp->id_flags & PC98_IEEE_1284_DISABLE)) { + tmp = inb(ppc->ppc_base + PPC_1284_ENABLE); + pc98_ieee_mode = tmp; + if ((tmp & 0x10) == 0x10) { + outb(ppc->ppc_base + PPC_1284_ENABLE, tmp & ~0x10); + tmp = inb(ppc->ppc_base + PPC_1284_ENABLE); + if ((tmp & 0x10) == 0x10) + goto error; + } else { + outb(ppc->ppc_base + PPC_1284_ENABLE, tmp | 0x10); + tmp = inb(ppc->ppc_base + PPC_1284_ENABLE); + if ((tmp & 0x10) != 0x10) + goto error; + } + outb(ppc->ppc_base + PPC_1284_ENABLE, pc98_ieee_mode | 0x10); + } +#endif + + /* + * Try to detect the chipset and its mode. + */ + if (ppc_detect(ppc, dvp->id_flags & 0xf)) + goto error; + + return (IO_LPTSIZE); + +error: +#ifdef PC98 + if ((ppc->ppc_base == PC98_IEEE_1284_PORT) && + !(dvp->id_flags & PC98_IEEE_1284_DISABLE)) { + outb(ppc->ppc_base + PPC_1284_ENABLE, pc98_ieee_mode); + } +#endif + return (0); +} + +static int +ppcattach(struct isa_device *isdp) +{ + struct ppc_data *ppc = ppcdata[isdp->id_unit]; + struct ppb_data *ppbus; + + printf("ppc%d: %s chipset (%s) in %s mode%s\n", ppc->ppc_unit, + ppc_types[ppc->ppc_type], ppc_avms[ppc->ppc_avm], + ppc_modes[ppc->ppc_mode], (PPB_IS_EPP(ppc->ppc_mode)) ? + ppc_epp_protocol[ppc->ppc_epp] : ""); + + if (ppc->ppc_fifo) + printf("ppc%d: FIFO with %d/%d/%d bytes threshold\n", + ppc->ppc_unit, ppc->ppc_fifo, ppc->ppc_wthr, + ppc->ppc_rthr); + + isdp->id_ointr = ppcintr; + + /* + * Prepare ppbus data area for upper level code. + */ + ppbus = ppb_alloc_bus(); + + if (!ppbus) + return (0); + + ppc->ppc_link.ppbus = ppbus; + ppbus->ppb_link = &ppc->ppc_link; + + if ((ppc->ppc_avm & PPB_ECP) && (ppc->ppc_dmachan > 0)) { + + /* acquire the DMA channel forever */ + isa_dma_acquire(ppc->ppc_dmachan); + isa_dmainit(ppc->ppc_dmachan, 1024); /* nlpt.BUFSIZE */ + } + + /* + * Probe the ppbus and attach devices found. + */ + ppb_attachdevs(ppbus); + + return (1); +} +#endif diff --git a/sys/pc98/pc98/ppcreg.h b/sys/pc98/pc98/ppcreg.h new file mode 100644 index 0000000..3acbbf6 --- /dev/null +++ b/sys/pc98/pc98/ppcreg.h @@ -0,0 +1,233 @@ +/*- + * Copyright (c) 1997 Nicolas Souchu + * 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 AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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. + * + * $Id$ + * + */ +#ifndef __PPCREG_H +#define __PPCREG_H + +/* + * Parallel Port Chipset type. + */ +#define SMC_LIKE 0 +#define SMC_37C665GT 1 +#define SMC_37C666GT 2 +#define NS_PC87332 3 +#define NS_PC87306 4 +#define INTEL_820191AA 5 /* XXX not implemented */ +#define GENERIC 6 +#define WINB_W83877F 7 +#define WINB_W83877AF 8 +#define WINB_UNKNOWN 9 +#define NS_PC87334 10 + +/* + * Generic structure to hold parallel port chipset info. + */ +struct ppc_data { + + int ppc_unit; + int ppc_type; + + int ppc_mode; /* chipset current mode */ + int ppc_avm; /* chipset available modes */ + +#define PPC_IRQ_NONE 0x0 +#define PPC_IRQ_nACK 0x1 +#define PPC_IRQ_DMA 0x2 +#define PPC_IRQ_FIFO 0x4 +#define PPC_IRQ_nFAULT 0x8 + int ppc_irqstat; /* remind irq settings */ + +#define PPC_DMA_INIT 0x01 +#define PPC_DMA_STARTED 0x02 +#define PPC_DMA_COMPLETE 0x03 +#define PPC_DMA_INTERRUPTED 0x04 +#define PPC_DMA_ERROR 0x05 + int ppc_dmastat; /* dma state */ + int ppc_dmachan; /* dma channel */ + int ppc_dmaflags; /* dma transfer flags */ + caddr_t ppc_dmaddr; /* buffer address */ + u_int ppc_dmacnt; /* count of bytes sent with dma */ + +#define PPC_PWORD_MASK 0x30 +#define PPC_PWORD_16 0x00 +#define PPC_PWORD_8 0x10 +#define PPC_PWORD_32 0x20 + char ppc_pword; /* PWord size */ + short ppc_fifo; /* FIFO threshold */ + + short ppc_wthr; /* writeIntrThresold */ + short ppc_rthr; /* readIntrThresold */ + +#define ppc_base ppc_link.base +#define ppc_epp ppc_link.epp_protocol +#define ppc_irq ppc_link.id_irq +#define ppc_subm ppc_link.submicroseq +#define ppc_ptr ppc_link.ptr +#define ppc_accum ppc_link.accum + + unsigned char ppc_flags; + + struct ppb_link ppc_link; +}; + +/* + * Parallel Port Chipset registers. + */ +#define PPC_SPP_DTR 0 /* SPP data register */ +#define PPC_ECP_A_FIFO 0 /* ECP Address fifo register */ +#define PPC_SPP_STR 1 /* SPP status register */ +#define PPC_SPP_CTR 2 /* SPP control register */ +#define PPC_EPP_ADDR 3 /* EPP address register (8 bit) */ +#define PPC_EPP_DATA 4 /* EPP data register (8, 16 or 32 bit) */ +#ifdef PC98 +#define PPC_1284_ENABLE 0x09 /* IEEE STD 1284 Enable register */ +#define PPC_ECP_D_FIFO 0x0c /* ECP Data fifo register */ +#define PPC_ECP_CNFGA 0x0c /* Configuration register A */ +#define PPC_ECP_CNFGB 0x0d /* Configuration register B */ +#define PPC_ECP_ECR 0x0e /* ECP extended control register */ +#else +#define PPC_ECP_D_FIFO 0x400 /* ECP Data fifo register */ +#define PPC_ECP_CNFGA 0x400 /* Configuration register A */ +#define PPC_ECP_CNFGB 0x401 /* Configuration register B */ +#define PPC_ECP_ECR 0x402 /* ECP extended control register */ +#endif + +#define PPC_FIFO_EMPTY 0x1 /* ecr register - bit 0 */ +#define PPC_FIFO_FULL 0x2 /* ecr register - bit 1 */ +#define PPC_SERVICE_INTR 0x4 /* ecr register - bit 2 */ +#define PPC_ENABLE_DMA 0x8 /* ecr register - bit 3 */ +#define PPC_nFAULT_INTR 0x10 /* ecr register - bit 4 */ +#define PPC_ECR_STD 0x0 +#define PPC_ECR_PS2 0x20 +#define PPC_ECR_FIFO 0x40 +#define PPC_ECR_ECP 0x60 +#define PPC_ECR_EPP 0x80 + +#define PPC_DISABLE_INTR (PPC_SERVICE_INTR | PPC_nFAULT_INTR) +#define PPC_ECR_RESET (PPC_ECR_PS2 | PPC_DISABLE_INTR) + +#define r_dtr(ppc) (inb((ppc)->ppc_base + PPC_SPP_DTR)) +#define r_str(ppc) (inb((ppc)->ppc_base + PPC_SPP_STR)) +#define r_ctr(ppc) (inb((ppc)->ppc_base + PPC_SPP_CTR)) +#define r_epp_A(ppc) (inb((ppc)->ppc_base + PPC_EPP_ADDR)) +#define r_epp_D(ppc) (inb((ppc)->ppc_base + PPC_EPP_DATA)) +#define r_cnfgA(ppc) (inb((ppc)->ppc_base + PPC_ECP_CNFGA)) +#define r_cnfgB(ppc) (inb((ppc)->ppc_base + PPC_ECP_CNFGB)) +#define r_ecr(ppc) (inb((ppc)->ppc_base + PPC_ECP_ECR)) +#define r_fifo(ppc) (inb((ppc)->ppc_base + PPC_ECP_D_FIFO)) + +#define w_dtr(ppc,byte) outb((ppc)->ppc_base + PPC_SPP_DTR, byte) +#define w_str(ppc,byte) outb((ppc)->ppc_base + PPC_SPP_STR, byte) +#define w_ctr(ppc,byte) outb((ppc)->ppc_base + PPC_SPP_CTR, byte) +#define w_epp_A(ppc,byte) outb((ppc)->ppc_base + PPC_EPP_ADDR, byte) +#define w_epp_D(ppc,byte) outb((ppc)->ppc_base + PPC_EPP_DATA, byte) +#define w_ecr(ppc,byte) outb((ppc)->ppc_base + PPC_ECP_ECR, byte) +#define w_fifo(ppc,byte) outb((ppc)->ppc_base + PPC_ECP_D_FIFO, byte) + +/* + * Register defines for the PC873xx parts + */ + +#define PC873_FER 0x00 +#define PC873_PPENABLE (1<<0) +#define PC873_FAR 0x01 +#define PC873_PTR 0x02 +#define PC873_CFGLOCK (1<<6) +#define PC873_EPPRDIR (1<<7) +#define PC873_EXTENDED (1<<7) +#define PC873_LPTBIRQ7 (1<<3) +#define PC873_FCR 0x03 +#define PC873_ZWS (1<<5) +#define PC873_ZWSPWDN (1<<6) +#define PC873_PCR 0x04 +#define PC873_EPPEN (1<<0) +#define PC873_EPP19 (1<<1) +#define PC873_ECPEN (1<<2) +#define PC873_ECPCLK (1<<3) +#define PC873_PMC 0x06 +#define PC873_TUP 0x07 +#define PC873_SID 0x08 +#define PC873_PNP0 0x1b +#define PC873_PNP1 0x1c +#define PC873_LPTBA 0x19 + +/* + * Register defines for the SMC FDC37C66xGT parts + */ + +/* Init codes */ +#define SMC665_iCODE 0x55 +#define SMC666_iCODE 0x44 + +/* Base configuration ports */ +#define SMC66x_CSR 0x3F0 +#define SMC666_CSR 0x370 /* hard-configured value for 666 */ + +/* Bits */ +#define SMC_CR1_ADDR 0x3 /* bit 0 and 1 */ +#define SMC_CR1_MODE (1<<3) /* bit 3 */ +#define SMC_CR4_EMODE 0x3 /* bits 0 and 1 */ +#define SMC_CR4_EPPTYPE (1<<6) /* bit 6 */ + +/* Extended modes */ +#define SMC_SPP 0x0 /* SPP */ +#define SMC_EPPSPP 0x1 /* EPP and SPP */ +#define SMC_ECP 0x2 /* ECP */ +#define SMC_ECPEPP 0x3 /* ECP and EPP */ + +/* + * Register defines for the Winbond W83877F parts + */ + +#define WINB_W83877F_ID 0xa +#define WINB_W83877AF_ID 0xb + +/* Configuration bits */ +#define WINB_HEFERE (1<<5) /* CROC bit 5 */ +#define WINB_HEFRAS (1<<0) /* CR16 bit 0 */ + +#define WINB_PNPCVS (1<<2) /* CR16 bit 2 */ +#define WINB_CHIPID 0xf /* CR9 bits 0-3 */ + +#define WINB_PRTMODS0 (1<<2) /* CR0 bit 2 */ +#define WINB_PRTMODS1 (1<<3) /* CR0 bit 3 */ +#define WINB_PRTMODS2 (1<<7) /* CR9 bit 7 */ + +/* W83877F modes: CR9/bit7 | CR0/bit3 | CR0/bit2 */ +#define WINB_W83757 0x0 +#define WINB_EXTFDC 0x4 +#define WINB_EXTADP 0x8 +#define WINB_EXT2FDD 0xc +#define WINB_JOYSTICK 0x80 + +#define WINB_PARALLEL 0x80 +#define WINB_EPP_SPP 0x4 +#define WINB_ECP 0x8 +#define WINB_ECP_EPP 0xc + +#endif |