/* $NetBSD: lpt.c,v 1.4 2004/01/22 01:18:54 bjh21 Exp $ */ /* * 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 * From Id: lpt.c,v 1.55.2.1 1996/11/12 09:08:38 phk Exp * From Id: nlpt.c,v 1.14 1999/02/08 13:55:43 des Exp * $FreeBSD: src/sys/dev/ppbus/lpt.c,v 1.15.2.3 2000/07/07 00:30:40 obrien Exp $ */ /* * Device Driver for AT parallel printer port * Written by William Jolitz 12/18/90 */ /* * Updated for ppbus by Nicolas Souchu * [Mon Jul 28 1997] */ #include "opt_ppbus_lpt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Autoconf functions */ static int lpt_probe(struct device *, struct cfdata *, void *); static void lpt_attach(struct device *, struct device *, void *); static int lpt_detach(struct device *, int); /* Autoconf structure */ CFATTACH_DECL(lpt_ppbus, sizeof(struct lpt_softc), lpt_probe, lpt_attach, lpt_detach, NULL); extern struct cfdriver lpt_cd; dev_type_open(lptopen); dev_type_close(lptclose); dev_type_read(lptread); dev_type_write(lptwrite); dev_type_ioctl(lptioctl); const struct cdevsw lpt_cdevsw = { lptopen, lptclose, lptread, lptwrite, lptioctl, nostop, notty, nopoll, nommap, nokqfilter }; /* Function prototypes */ static int lpt_detect(struct device *); static int lpt_request_ppbus(struct lpt_softc *, int); static int lpt_release_ppbus(struct lpt_softc *, int); static int lpt_logstatus(const struct device * const, const unsigned char); /* * lpt_probe() */ static int lpt_probe(struct device * parent, struct cfdata * match, void * aux) { /* Test ppbus's capability */ return lpt_detect(parent); } static void lpt_attach(struct device * parent, struct device * self, void * aux) { struct lpt_softc * sc = (struct lpt_softc *) self; struct ppbus_device_softc * ppbdev = &(sc->ppbus_dev); struct ppbus_attach_args * args = aux; char buf[64]; int error; sc->sc_dev_ok = LPT_NOK; error = lpt_request_ppbus(sc, 0); if(error) { printf("%s(%s): error (%d) requesting bus(%s). Device not " "properly attached.\n", __func__, self->dv_xname, error, parent->dv_xname); return; } /* Record capabilities */ ppbdev->capabilities = args->capabilities; /* Allocate memory buffers */ if(ppbdev->capabilities & PPBUS_HAS_DMA) { if(ppbus_dma_malloc(parent, &(sc->sc_inbuf), &(sc->sc_in_baddr), BUFSIZE)) { printf(" : cannot allocate input DMA buffer. Device " "not properly attached!\n"); return; } if(ppbus_dma_malloc(parent, &(sc->sc_outbuf), &(sc->sc_out_baddr), BUFSIZE)) { ppbus_dma_free(parent, &(sc->sc_inbuf), &(sc->sc_in_baddr), BUFSIZE); printf(" : cannot allocate output DMA buffer. Device " "not properly attached!\n"); return; } } else { sc->sc_inbuf = malloc(BUFSIZE, M_DEVBUF, M_WAITOK); sc->sc_outbuf = malloc(BUFSIZE, M_DEVBUF, M_WAITOK); } /* Print out mode */ ppbdev->ctx.mode = ppbus_get_mode(parent); bitmask_snprintf(ppbdev->ctx.mode, "\20\1COMPATIBLE\2NIBBLE" "\3PS2\4EPP\5ECP\6FAST_CENTR", buf, sizeof(buf)); printf(": mode = %s\n", buf); /* Set ok flag */ sc->sc_dev_ok = LPT_OK; lpt_release_ppbus(sc, 0); return; } static int lpt_detach(struct device * self, int flags) { struct lpt_softc * lpt = (struct lpt_softc *) self; struct ppbus_device_softc * ppbdev = (struct ppbus_device_softc *) lpt; int err; if(lpt->sc_dev_ok == LPT_NOK) { printf("%s: device not properly attached,\n", self->dv_xname); if(flags & DETACH_FORCE) { printf(", continuing (DETACH_FORCE)!\n"); } else { printf(", terminating!\n"); return 0; } } if(lpt->sc_state & HAVEBUS) { err = lpt_release_ppbus(lpt, 0); if(err) { printf("%s error (%d) while releasing bus", self->dv_xname, err); if(flags & DETACH_FORCE) { printf(", continuing (DETACH_FORCE)!\n"); } else { printf(", terminating!\n"); return 0; } } lpt->sc_state &= ~HAVEBUS; } lpt->sc_dev_ok = LPT_NOK; lpt->sc_irq = 0; ppbdev->ctx.valid = 0; /* Free memory buffers */ if(ppbdev->capabilities & PPBUS_HAS_DMA) { ppbus_dma_free(self->dv_parent, &(lpt->sc_inbuf), &(lpt->sc_in_baddr), BUFSIZE); ppbus_dma_free(self->dv_parent, &(lpt->sc_outbuf), &(lpt->sc_out_baddr), BUFSIZE); } else { free(lpt->sc_inbuf, M_DEVBUF); free(lpt->sc_outbuf, M_DEVBUF); } if(!(flags & DETACH_QUIET)) { printf("%s detached", self->dv_xname); } return 1; } /* Grab bus for lpt device */ static int lpt_request_ppbus(struct lpt_softc * lpt, int how) { struct device * dev = (struct device *) lpt; int error; error = ppbus_request_bus(dev->dv_parent, dev, how, (hz)); if (!(error)) { lpt->sc_state |= HAVEBUS; } else { LPT_DPRINTF(("%s(%s): error %d requesting bus.\n", __func__, dev->dv_xname, error)); } return error; } /* Release ppbus to enable other devices to use it. */ static int lpt_release_ppbus(struct lpt_softc * lpt, int how) { struct device * dev = (struct device *) lpt; int error; if(lpt->sc_state & HAVEBUS) { error = ppbus_release_bus(dev->dv_parent, dev, how, (hz)); if(!(error)) lpt->sc_state &= ~HAVEBUS; else LPT_DPRINTF(("%s(%s): error releasing bus.\n", __func__, dev->dv_xname)); } else { error = EINVAL; LPT_DPRINTF(("%s(%s): device does not own bus.\n", __func__, dev->dv_xname)); } return error; } /* * Probe simplified by replacing multiple loops with a hardcoded * test pattern - 1999/02/08 des@freebsd.org * * 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. */ static int lpt_detect(struct device * dev) { 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, status; u_char dtr, ctr, str, var; /* Save register contents */ dtr = ppbus_rdtr(dev); ctr = ppbus_rctr(dev); str = ppbus_rstr(dev); status = 1; /* assume success */ /* Test data port */ for(i = 0; i < 18; i++) { ppbus_wdtr(dev, testbyte[i]); if((var = ppbus_rdtr(dev)) != testbyte[i]) { status = 0; LPT_DPRINTF(("%s(%s): byte value %x cannot be written " "and read from data port (got %x instead).\n", __func__, dev->dv_xname, testbyte[i], var)); goto end; } } /* Test control port */ ppbus_wdtr(dev, 0); for(i = 0; i < 18; i++) { ppbus_wctr(dev, (testbyte[i] & 0x14)); if(((var = ppbus_rctr(dev)) & 0x14) != (testbyte[i] & 0x14)) { status = 0; LPT_DPRINTF(("%s(%s): byte value %x (unmasked value " "%x) cannot be written and read from control " "port (got %x instead).\n", __func__, dev->dv_xname, (testbyte[i] & 0x14), testbyte[i], (var & 0x14))); break; } } end: /* Restore contents of registers */ ppbus_wdtr(dev, dtr); ppbus_wctr(dev, ctr); ppbus_wstr(dev, str); return status; } /* Log status of status register for printer port */ static int lpt_logstatus(const struct device * const dev, const unsigned char status) { int err; err = EIO; if(!(status & LPS_SEL)) { log(LOG_ERR, "%s: offline.", dev->dv_xname); } else if(!(status & LPS_NBSY)) { log(LOG_ERR, "%s: busy.", dev->dv_xname); } else if(status & LPS_OUT) { log(LOG_ERR, "%s: out of paper.", dev->dv_xname); err = EAGAIN; } else if(!(status & LPS_NERR)) { log(LOG_ERR, "%s: output error.", dev->dv_xname); } else { log(LOG_ERR, "%s: no error indication.", dev->dv_xname); err = 0; } return err; } /* * lptopen -- reset the printer, then wait until it's selected and not busy. */ int lptopen(dev_t dev_id, int flags, int fmt, struct proc *p) { int trys, err, val; u_int8_t status; struct device * dev; struct lpt_softc * lpt; struct ppbus_device_softc * ppbus_dev; struct device * ppbus; dev = device_lookup(&lpt_cd, LPTUNIT(dev_id)); if(!dev) { LPT_DPRINTF(("%s(): device not configured.\n", __func__)); return ENXIO; } lpt = (struct lpt_softc *) dev; if(lpt->sc_dev_ok != LPT_OK) { LPT_DPRINTF(("%s(): device not attached properly [sc = %p, " "sc_dev_ok = %x].\n", __func__, dev, lpt->sc_dev_ok)); return ENODEV; } ppbus = dev->dv_parent; ppbus_dev = &(lpt->ppbus_dev); /* Request the ppbus */ err = lpt_request_ppbus(lpt, PPBUS_WAIT|PPBUS_INTR); if(err) { LPT_DPRINTF(("%s(%s): error (%d) while requesting bus.\n", __func__, dev->dv_xname, err)); return (err); } /* Get device flags */ lpt->sc_flags = LPTFLAGS(dev_id); /* Update bus mode */ ppbus_dev->ctx.mode = ppbus_get_mode(ppbus); /* Configure interrupts/polling */ if(lpt->sc_flags & LPT_NOINTR) { val = 0; err = ppbus_write_ivar(ppbus, PPBUS_IVAR_INTR, &val); if(err) { lpt_release_ppbus(lpt, PPBUS_WAIT); return err; } } else { val = 1; err = ppbus_write_ivar(ppbus, PPBUS_IVAR_INTR, &val); if(err) { lpt_release_ppbus(lpt, PPBUS_WAIT); return err; } } if(err) { lpt_release_ppbus(lpt, PPBUS_WAIT); return err; } /* init printer */ if(!(lpt->sc_flags & LPT_NOPRIME)) { LPT_VPRINTF(("%s(%s): initializing printer.\n", __func__, dev->dv_xname)); lpt->sc_state |= LPTINIT; ppbus_wctr(ppbus, LPC_SEL | LPC_NINIT); /* wait till ready (printer running diagnostics) */ for(trys = 0, status = ppbus_rstr(ppbus); (status & RDY_MASK) != LP_READY; trys += LPT_STEP, status = ppbus_rstr(ppbus)) { /* Time up waiting for the printer */ if(trys >= LPT_TIMEOUT) break; /* wait LPT_STEP ticks, give up if we get a signal */ else { err = tsleep((caddr_t)lpt, LPPRI|PCATCH, "lptinit", LPT_STEP); if((err) && (err != EWOULDBLOCK)) { lpt->sc_state &= ~LPTINIT; LPT_DPRINTF(("%s(%s): interrupted " "during initialization.\n", __func__, dev->dv_xname)); lpt_release_ppbus(lpt, PPBUS_WAIT); return (err); } } } lpt->sc_state &= ~LPTINIT; if(trys >= LPT_TIMEOUT) { LPT_DPRINTF(("%s(%s): timed out while initializing " "printer. [status %x]\n", __func__, dev->dv_xname, status)); err = lpt_logstatus(dev, status); lpt_release_ppbus(lpt, PPBUS_WAIT); return (err); } else LPT_VPRINTF(("%s(%s): printer ready.\n", __func__, dev->dv_xname)); } /* Set autolinefeed */ if(lpt->sc_flags & LPT_AUTOLF) { lpt->sc_control |= LPC_AUTOL; } /* Write out the control register */ ppbus_wctr(ppbus, lpt->sc_control); lpt->sc_xfercnt = 0; lpt->sc_state |= OPEN; return 0; } /* * lptclose -- close the device, free the local line buffer. * * Check for interrupted write call added. */ int lptclose(dev_t dev_id, int flags, int fmt, struct proc *p) { struct device * dev = device_lookup(&lpt_cd, LPTUNIT(dev_id)); struct lpt_softc * sc = (struct lpt_softc *) dev; int err; err = lpt_release_ppbus(sc, PPBUS_WAIT|PPBUS_INTR); if(err) { LPT_DPRINTF(("%s(%s): error (%d) while releasing ppbus.\n", __func__, dev->dv_xname, err)); } sc->sc_state = 0; sc->sc_xfercnt = 0; return err; } /* * lptread --retrieve printer status in IEEE1284 NIBBLE mode */ int lptread(dev_t dev_id, struct uio *uio, int ioflag) { size_t len = 0; int error = 0; struct device * dev = device_lookup(&lpt_cd, LPTUNIT(dev_id)); struct lpt_softc * sc = (struct lpt_softc *) dev; if(!(sc->sc_state & HAVEBUS)) { LPT_DPRINTF(("%s(%s): attempt to read using device which does " "not own the bus(%s).\n", __func__, dev->dv_xname, dev->dv_parent->dv_xname)); return (ENODEV); } sc->sc_state &= ~INTERRUPTED; while (uio->uio_resid) { error = ppbus_read(dev->dv_parent, sc->sc_outbuf, min(BUFSIZE, uio->uio_resid), 0, &len); /* If error or no more data, stop */ if(error) { if(error != EWOULDBLOCK) sc->sc_state |= INTERRUPTED; break; } else if(len == 0) break; error = uiomove(sc->sc_outbuf, len, uio); if (error) break; } return error; } /* * 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. */ int lptwrite(dev_t dev_id, struct uio * uio, int ioflag) { unsigned n; int err = 0; size_t cnt; struct device * dev = device_lookup(&lpt_cd, LPTUNIT(dev_id)); struct lpt_softc * sc = (struct lpt_softc *) dev; /* Check state and flags */ if(!(sc->sc_state & HAVEBUS)) { LPT_DPRINTF(("%s(%s): attempt to write using device which does " "not own the bus(%s).\n", __func__, dev->dv_xname, dev->dv_parent->dv_xname)); return EINVAL; } /* Write the data */ sc->sc_state &= ~INTERRUPTED; while(uio->uio_resid) { n = min(BUFSIZE, uio->uio_resid); err = uiomove(sc->sc_inbuf, n, uio); if(err) break; err = ppbus_write(dev->dv_parent, sc->sc_inbuf, n, ioflag, &cnt); sc->sc_xfercnt += cnt; if(err) { if(err != EWOULDBLOCK) sc->sc_state |= INTERRUPTED; break; } } LPT_VPRINTF(("%s(%s): %d bytes sent.\n", __func__, dev->dv_xname, sc->sc_xfercnt)); return err; } /* Printer ioctl */ int lptioctl(dev_t dev_id, u_long cmd, caddr_t data, int flags, struct proc *p) { struct device * dev = device_lookup(&lpt_cd, LPTUNIT(dev_id)); struct lpt_softc * sc = (struct lpt_softc *) dev; int val; int error = 0; if(!(sc->sc_state & HAVEBUS)) { LPT_DPRINTF(("%s(%s): attempt to perform ioctl on device which " "does not own the bus(%s).\n", __func__, dev->dv_xname, dev->dv_parent->dv_xname)); return EBUSY; } switch (cmd) { case LPTIO_ENABLE_DMA : if((sc->ppbus_dev).capabilities & PPBUS_HAS_DMA) { val = 1; error = ppbus_write_ivar(dev->dv_parent, PPBUS_IVAR_DMA, &val); } else { LPT_DPRINTF(("%s(%s): device does not have DMA " "capability.\n", __func__, dev->dv_xname)); error = ENODEV; } break; case LPTIO_DISABLE_DMA : if((sc->ppbus_dev).capabilities & PPBUS_HAS_DMA) { val = 0; error = ppbus_write_ivar(dev->dv_parent, PPBUS_IVAR_DMA, &val); } else { LPT_DPRINTF(("%s(%s): device does not have DMA " "capability.\n", __func__, dev->dv_xname)); error = ENODEV; } break; case LPTIO_MODE_STD: error = ppbus_set_mode(dev->dv_parent, PPBUS_COMPATIBLE, 0); break; case LPTIO_MODE_NIBBLE: error = ppbus_set_mode(dev->dv_parent, PPBUS_NIBBLE, 0); break; case LPTIO_MODE_PS2: error = ppbus_set_mode(dev->dv_parent, PPBUS_PS2, 0); break; case LPTIO_MODE_FAST: error = ppbus_set_mode(dev->dv_parent, PPBUS_FAST, 0); break; case LPTIO_MODE_ECP: error = ppbus_set_mode(dev->dv_parent, PPBUS_ECP, 0); break; case LPTIO_MODE_EPP: error = ppbus_set_mode(dev->dv_parent, PPBUS_EPP, 0); break; case LPTIO_ENABLE_IEEE: val = 1; error = ppbus_write_ivar(dev->dv_parent, PPBUS_IVAR_IEEE, &val); break; case LPTIO_DISABLE_IEEE: val = 0; error = ppbus_write_ivar(dev->dv_parent, PPBUS_IVAR_IEEE, &val); break; case LPTIO_GET_STATUS: { LPT_INFO_T * status = (LPT_INFO_T *)data; error = ppbus_read_ivar(dev->dv_parent, PPBUS_IVAR_DMA, &val); if(error) { break; } else if(val) { status->dma_status = true; } else { status->dma_status = false; } error = ppbus_read_ivar(dev->dv_parent, PPBUS_IVAR_IEEE, &val); if(error) { break; } else if(val) { status->ieee_status = true; } else { status->ieee_status = false; } switch(ppbus_get_mode(dev->dv_parent)) { case PPBUS_COMPATIBLE: status->mode_status = standard; break; case PPBUS_NIBBLE: status->mode_status = nibble; break; case PPBUS_PS2: status->mode_status = ps2; break; case PPBUS_FAST: status->mode_status = fast; break; case PPBUS_EPP: status->mode_status = epp; break; case PPBUS_ECP: status->mode_status = ecp; break; } break; } default: error = EINVAL; } return error; }