diff options
Diffstat (limited to 'sys/dev/ppbus')
-rw-r--r-- | sys/dev/ppbus/if_plip.c | 784 | ||||
-rw-r--r-- | sys/dev/ppbus/immio.c | 804 | ||||
-rw-r--r-- | sys/dev/ppbus/lpbb.c | 224 | ||||
-rw-r--r-- | sys/dev/ppbus/lpt.c | 974 | ||||
-rw-r--r-- | sys/dev/ppbus/lpt.h | 66 | ||||
-rw-r--r-- | sys/dev/ppbus/lptio.h | 24 | ||||
-rw-r--r-- | sys/dev/ppbus/pcfclock.c | 342 | ||||
-rw-r--r-- | sys/dev/ppbus/ppb_1284.c | 857 | ||||
-rw-r--r-- | sys/dev/ppbus/ppb_1284.h | 126 | ||||
-rw-r--r-- | sys/dev/ppbus/ppb_base.c | 195 | ||||
-rw-r--r-- | sys/dev/ppbus/ppb_msq.c | 337 | ||||
-rw-r--r-- | sys/dev/ppbus/ppb_msq.h | 205 | ||||
-rw-r--r-- | sys/dev/ppbus/ppbconf.c | 558 | ||||
-rw-r--r-- | sys/dev/ppbus/ppbconf.h | 276 | ||||
-rw-r--r-- | sys/dev/ppbus/ppbio.h | 83 | ||||
-rw-r--r-- | sys/dev/ppbus/ppbus_if.m | 93 | ||||
-rw-r--r-- | sys/dev/ppbus/ppi.c | 578 | ||||
-rw-r--r-- | sys/dev/ppbus/ppi.h | 54 | ||||
-rw-r--r-- | sys/dev/ppbus/pps.c | 320 | ||||
-rw-r--r-- | sys/dev/ppbus/vpo.c | 463 | ||||
-rw-r--r-- | sys/dev/ppbus/vpoio.c | 780 | ||||
-rw-r--r-- | sys/dev/ppbus/vpoio.h | 100 |
22 files changed, 8243 insertions, 0 deletions
diff --git a/sys/dev/ppbus/if_plip.c b/sys/dev/ppbus/if_plip.c new file mode 100644 index 0000000..6f72096 --- /dev/null +++ b/sys/dev/ppbus/if_plip.c @@ -0,0 +1,784 @@ +/*- + * Copyright (c) 1997 Poul-Henning Kamp + * 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. + * + * From Id: lpt.c,v 1.55.2.1 1996/11/12 09:08:38 phk Exp + * $FreeBSD$ + */ + +/* + * 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> + */ + +/* + * Update for ppbus, PLIP support only - Nicolas Souchu + */ + +#include "opt_plip.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/kernel.h> +#include <sys/malloc.h> + +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include <net/if.h> +#include <net/if_types.h> +#include <net/netisr.h> + +#include <netinet/in.h> +#include <netinet/in_var.h> + +#include <net/bpf.h> + +#include <dev/ppbus/ppbconf.h> +#include "ppbus_if.h" +#include <dev/ppbus/ppbio.h> + +#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 */ + +#define lprintf if (lptflag) printf + +#ifdef PLIP_DEBUG +static int volatile lptflag = 1; +#else +static int volatile lptflag = 0; +#endif + +struct lp_data { + unsigned short lp_unit; + + struct ifnet sc_if; + u_char *sc_ifbuf; + int sc_iferrs; + + struct resource *res_irq; +}; + +/* 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 int lpinittables(void); +static int lpioctl(struct ifnet *, u_long, caddr_t); +static int lpoutput(struct ifnet *, struct mbuf *, struct sockaddr *, + struct rtentry *); +static void lp_intr(void *); + +#define DEVTOSOFTC(dev) \ + ((struct lp_data *)device_get_softc(dev)) +#define UNITOSOFTC(unit) \ + ((struct lp_data *)devclass_get_softc(lp_devclass, (unit))) +#define UNITODEVICE(unit) \ + (devclass_get_device(lp_devclass, (unit))) + +static devclass_t lp_devclass; + +static void +lp_identify(driver_t *driver, device_t parent) +{ + + BUS_ADD_CHILD(parent, 0, "plip", -1); +} +/* + * lpprobe() + */ +static int +lp_probe(device_t dev) +{ + device_t ppbus = device_get_parent(dev); + struct lp_data *lp; + int zero = 0; + uintptr_t irq; + + lp = DEVTOSOFTC(dev); + bzero(lp, sizeof(struct lp_data)); + + /* retrieve the ppbus irq */ + BUS_READ_IVAR(ppbus, dev, PPBUS_IVAR_IRQ, &irq); + + /* if we haven't interrupts, the probe fails */ + if (irq == -1) { + device_printf(dev, "not an interrupt driven port, failed.\n"); + return (ENXIO); + } + + /* reserve the interrupt resource, expecting irq is available to continue */ + lp->res_irq = bus_alloc_resource(dev, SYS_RES_IRQ, &zero, irq, irq, 1, + RF_SHAREABLE); + if (lp->res_irq == 0) { + device_printf(dev, "cannot reserve interrupt, failed.\n"); + return (ENXIO); + } + + /* + * lp dependent initialisation. + */ + lp->lp_unit = device_get_unit(dev); + + device_set_desc(dev, "PLIP network interface"); + + return (0); +} + +static int +lp_attach (device_t dev) +{ + struct lp_data *lp = DEVTOSOFTC(dev); + struct ifnet *ifp = &lp->sc_if; + + ifp->if_softc = lp; + ifp->if_name = "lp"; + ifp->if_unit = device_get_unit(dev); + 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); + + bpfattach(ifp, DLT_NULL, sizeof(u_int32_t)); + + return (0); +} +/* + * 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; +} + +/* + * Process an ioctl request. + */ + +static int +lpioctl (struct ifnet *ifp, u_long cmd, caddr_t data) +{ + device_t dev = UNITODEVICE(ifp->if_unit); + device_t ppbus = device_get_parent(dev); + struct lp_data *sc = DEVTOSOFTC(dev); + struct ifaddr *ifa = (struct ifaddr *)data; + struct ifreq *ifr = (struct ifreq *)data; + u_char *ptr; + void *ih; + int error; + + 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)) { + + ppb_wctr(ppbus, 0x00); + ifp->if_flags &= ~IFF_RUNNING; + + /* IFF_UP is not set, try to release the bus anyway */ + ppb_release_bus(ppbus, dev); + break; + } + if (((ifp->if_flags & IFF_UP)) && (!(ifp->if_flags & IFF_RUNNING))) { + + /* XXX + * Should the request be interruptible? + */ + if ((error = ppb_request_bus(ppbus, dev, PPB_WAIT|PPB_INTR))) + return (error); + + /* Now IFF_UP means that we own the bus */ + + ppb_set_mode(ppbus, PPB_COMPATIBLE); + + if (lpinittables()) { + ppb_release_bus(ppbus, dev); + return ENOBUFS; + } + + sc->sc_ifbuf = malloc(sc->sc_if.if_mtu + MLPIPHDRLEN, + M_DEVBUF, M_WAITOK); + if (!sc->sc_ifbuf) { + ppb_release_bus(ppbus, dev); + return ENOBUFS; + } + + /* attach our interrupt handler, later detached when the bus is released */ + if ((error = BUS_SETUP_INTR(ppbus, dev, sc->res_irq, + INTR_TYPE_NET, lp_intr, dev, &ih))) { + ppb_release_bus(ppbus, dev); + return (error); + } + + ppb_wctr(ppbus, IRQENABLE); + ifp->if_flags |= IFF_RUNNING; + } + break; + + 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) { + + case AF_INET: + break; + + default: + return EAFNOSUPPORT; + } + break; + + case SIOCGIFMEDIA: + /* + * No ifmedia support at this stage; maybe use it + * in future for eg. protocol selection. + */ + return EINVAL; + + default: + lprintf("LP:ioctl(0x%lx)\n", cmd); + return EINVAL; + } + return 0; +} + +static __inline int +clpoutbyte (u_char byte, int spin, device_t ppbus) +{ + ppb_wdtr(ppbus, ctxmitl[byte]); + while (ppb_rstr(ppbus) & CLPIP_SHAKE) + if (--spin == 0) { + return 1; + } + ppb_wdtr(ppbus, ctxmith[byte]); + while (!(ppb_rstr(ppbus) & CLPIP_SHAKE)) + if (--spin == 0) { + return 1; + } + return 0; +} + +static __inline int +clpinbyte (int spin, device_t ppbus) +{ + u_char c, cl; + + while((ppb_rstr(ppbus) & CLPIP_SHAKE)) + if(!--spin) { + return -1; + } + cl = ppb_rstr(ppbus); + ppb_wdtr(ppbus, 0x10); + + while(!(ppb_rstr(ppbus) & CLPIP_SHAKE)) + if(!--spin) { + return -1; + } + c = ppb_rstr(ppbus); + ppb_wdtr(ppbus, 0x00); + + return (ctrecvl[cl] | ctrecvh[c]); +} + +static void +lptap(struct ifnet *ifp, struct mbuf *m) +{ + /* + * Send a packet through bpf. We need to prepend the address family + * as a four 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). + */ + u_int32_t af = AF_INET; + struct mbuf m0; + + m0.m_next = m; + m0.m_len = sizeof(u_int32_t); + m0.m_data = (char *)⁡ + BPF_MTAP(ifp, &m0); +} + +static void +lp_intr (void *arg) +{ + device_t dev = (device_t)arg; + device_t ppbus = device_get_parent(dev); + struct lp_data *sc = DEVTOSOFTC(dev); + 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 */ + ppb_wdtr(ppbus, 0x01); + + /* Get the packet length */ + j = clpinbyte(LPMAXSPIN2, ppbus); + if (j == -1) + goto err; + len = j; + j = clpinbyte(LPMAXSPIN2, ppbus); + 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, ppbus); + if (j == -1) { + goto err; + } + *bp++ = j; + } + /* Get and ignore checksum */ + j = clpinbyte(LPMAXSPIN2, ppbus); + if (j == -1) { + goto err; + } + + len = bp - sc->sc_ifbuf; + if (len <= CLPIPHDRLEN) + goto err; + + sc->sc_iferrs = 0; + + 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 (sc->sc_if.if_bpf) + lptap(&sc->sc_if, top); + if (! IF_HANDOFF(&ipintrq, top, NULL)) { + lprintf("DROP"); + } else { + schednetisr(NETISR_IP); + } + } + goto done; + } + while ((ppb_rstr(ppbus) & LPIP_SHAKE)) { + len = sc->sc_if.if_mtu + LPIPHDRLEN; + bp = sc->sc_ifbuf; + while (len--) { + + cl = ppb_rstr(ppbus); + ppb_wdtr(ppbus, 8); + + j = LPMAXSPIN2; + while((ppb_rstr(ppbus) & LPIP_SHAKE)) + if(!--j) goto err; + + c = ppb_rstr(ppbus); + ppb_wdtr(ppbus, 0); + + *bp++= trecvh[cl] | trecvl[c]; + + j = LPMAXSPIN2; + while (!((cl=ppb_rstr(ppbus)) & LPIP_SHAKE)) { + if (cl != c && + (((cl = ppb_rstr(ppbus)) ^ 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; + + 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 (sc->sc_if.if_bpf) + lptap(&sc->sc_if, top); + if (! IF_HANDOFF(&ipintrq, top, NULL)) { + lprintf("DROP"); + } else { + schednetisr(NETISR_IP); + } + } + } + goto done; + + err: + ppb_wdtr(ppbus, 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", device_get_unit(dev)); + ppb_wctr(ppbus, 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, device_t ppbus) +{ + ppb_wdtr(ppbus, txmith[byte]); + while (!(ppb_rstr(ppbus) & LPIP_SHAKE)) + if (--spin == 0) + return 1; + ppb_wdtr(ppbus, txmitl[byte]); + while (ppb_rstr(ppbus) & LPIP_SHAKE) + if (--spin == 0) + return 1; + return 0; +} + +static int +lpoutput (struct ifnet *ifp, struct mbuf *m, + struct sockaddr *dst, struct rtentry *rt) +{ + device_t dev = UNITODEVICE(ifp->if_unit); + device_t ppbus = device_get_parent(dev); + int s, err; + struct mbuf *mm; + u_char *cp = "\0\0"; + u_char chksum = 0; + int count = 0; + int i, len, 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(); + + /* Suspend (on laptops) or receive-errors might have taken us offline */ + ppb_wctr(ppbus, IRQENABLE); + + if (ifp->if_flags & IFF_LINK0) { + + if (!(ppb_rstr(ppbus) & CLPIP_SHAKE)) { + lprintf("&"); + lp_intr(dev); + } + + /* Alert other end to pending packet */ + spin = LPMAXSPIN1; + ppb_wdtr(ppbus, 0x08); + while ((ppb_rstr(ppbus) & 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, ppbus)) + goto nend; + if (clpoutbyte((count >> 8) & 0xFF, LPMAXSPIN1, ppbus)) + goto nend; + + /* Send dummy ethernet header */ + for (i = 0; i < 12; i++) { + if (clpoutbyte(i, LPMAXSPIN1, ppbus)) + goto nend; + chksum += i; + } + + if (clpoutbyte(0x08, LPMAXSPIN1, ppbus)) + goto nend; + if (clpoutbyte(0x00, LPMAXSPIN1, ppbus)) + goto nend; + chksum += 0x08 + 0x00; /* Add into checksum */ + + mm = m; + do { + cp = mtod(mm, u_char *); + len = mm->m_len; + while (len--) { + chksum += *cp; + if (clpoutbyte(*cp++, LPMAXSPIN2, ppbus)) + goto nend; + } + } while ((mm = mm->m_next)); + + /* Send checksum */ + if (clpoutbyte(chksum, LPMAXSPIN2, ppbus)) + goto nend; + + /* Go quiescent */ + ppb_wdtr(ppbus, 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; + if (ifp->if_bpf) + lptap(ifp, m); + } + + m_freem(m); + + if (!(ppb_rstr(ppbus) & CLPIP_SHAKE)) { + lprintf("^"); + lp_intr(dev); + } + (void) splx(s); + return 0; + } + + if (ppb_rstr(ppbus) & LPIP_SHAKE) { + lprintf("&"); + lp_intr(dev); + } + + if (lpoutbyte(0x08, LPMAXSPIN1, ppbus)) + goto end; + if (lpoutbyte(0x00, LPMAXSPIN2, ppbus)) + goto end; + + mm = m; + do { + cp = mtod(mm,u_char *); + len = mm->m_len; + while (len--) + if (lpoutbyte(*cp++, LPMAXSPIN2, ppbus)) + goto end; + } while ((mm = mm->m_next)); + + err = 0; /* no errors were encountered */ + + end: + --cp; + ppb_wdtr(ppbus, 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 (ifp->if_bpf) + lptap(ifp, m); + } + + m_freem(m); + + if (ppb_rstr(ppbus) & LPIP_SHAKE) { + lprintf("^"); + lp_intr(dev); + } + + (void) splx(s); + return 0; +} + +static device_method_t lp_methods[] = { + /* device interface */ + DEVMETHOD(device_identify, lp_identify), + DEVMETHOD(device_probe, lp_probe), + DEVMETHOD(device_attach, lp_attach), + + { 0, 0 } +}; + +static driver_t lp_driver = { + "plip", + lp_methods, + sizeof(struct lp_data), +}; + +DRIVER_MODULE(plip, ppbus, lp_driver, lp_devclass, 0, 0); diff --git a/sys/dev/ppbus/immio.c b/sys/dev/ppbus/immio.c new file mode 100644 index 0000000..dcdc34b --- /dev/null +++ b/sys/dev/ppbus/immio.c @@ -0,0 +1,804 @@ +/*- + * Copyright (c) 1998, 1999 Nicolas Souchu + * Copyright (c) 2001 Alcove - 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. + * + * $FreeBSD$ + * + */ + +/* + * Iomega ZIP+ Matchmaker Parallel Port Interface driver + * + * Thanks to David Campbell work on the Linux driver and the Iomega specs + * Thanks to Thiebault Moeglin for the drive + */ +#ifdef _KERNEL +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/malloc.h> + +#endif /* _KERNEL */ + +#include "opt_vpo.h" + +#include <dev/ppbus/ppbio.h> +#include <dev/ppbus/ppbconf.h> +#include <dev/ppbus/ppb_msq.h> +#include <dev/ppbus/vpoio.h> +#include <dev/ppbus/ppb_1284.h> + +#include "ppbus_if.h" + +#define VP0_SELTMO 5000 /* select timeout */ +#define VP0_FAST_SPINTMO 500000 /* wait status timeout */ +#define VP0_LOW_SPINTMO 5000000 /* wait status timeout */ + +#define VP0_SECTOR_SIZE 512 + +/* + * Microcode to execute very fast I/O sequences at the lowest bus level. + */ + +#define WAIT_RET MS_PARAM(7, 2, MS_TYP_PTR) +#define WAIT_TMO MS_PARAM(1, 0, MS_TYP_INT) + +#define DECLARE_WAIT_MICROSEQUENCE \ +struct ppb_microseq wait_microseq[] = { \ + MS_CASS(0x0c), \ + MS_SET(MS_UNKNOWN), \ + /* loop */ \ + MS_BRSET(nBUSY, 4 /* ready */), \ + MS_DBRA(-2 /* loop */), \ + MS_CASS(0x04), \ + MS_RET(1), /* timed out */ \ + /* ready */ \ + MS_CASS(0x04), \ + MS_RFETCH(MS_REG_STR, 0xb8, MS_UNKNOWN ), \ + MS_RET(0) /* no error */ \ +} + +#define SELECT_TARGET MS_PARAM(6, 1, MS_TYP_CHA) + +#define DECLARE_SELECT_MICROSEQUENCE \ +struct ppb_microseq select_microseq[] = { \ + MS_CASS(0xc), \ + /* first, check there is nothing holding onto the bus */ \ + MS_SET(VP0_SELTMO), \ +/* _loop: */ \ + MS_BRCLEAR(0x8, 2 /* _ready */), \ + MS_DBRA(-2 /* _loop */), \ + MS_RET(2), /* bus busy */ \ +/* _ready: */ \ + MS_CASS(0x4), \ + MS_DASS(MS_UNKNOWN /* 0x80 | 1 << target */), \ + MS_DELAY(1), \ + MS_CASS(0xc), \ + MS_CASS(0xd), \ + /* now, wait until the drive is ready */ \ + MS_SET(VP0_SELTMO), \ +/* loop: */ \ + MS_BRSET(0x8, 3 /* ready */), \ + MS_DBRA(-2 /* loop */), \ +/* error: */ \ + MS_CASS(0xc), \ + MS_RET(VP0_ESELECT_TIMEOUT), \ +/* ready: */ \ + MS_CASS(0xc), \ + MS_RET(0) \ +} + +static struct ppb_microseq transfer_epilog[] = { + MS_CASS(0x4), + MS_CASS(0xc), + MS_CASS(0xe), + MS_CASS(0x4), + MS_RET(0) +}; + +#define CPP_S1 MS_PARAM(10, 2, MS_TYP_PTR) +#define CPP_S2 MS_PARAM(13, 2, MS_TYP_PTR) +#define CPP_S3 MS_PARAM(16, 2, MS_TYP_PTR) +#define CPP_PARAM MS_PARAM(17, 1, MS_TYP_CHA) + +#define DECLARE_CPP_MICROSEQ \ +struct ppb_microseq cpp_microseq[] = { \ + MS_CASS(0x0c), MS_DELAY(2), \ + MS_DASS(0xaa), MS_DELAY(10), \ + MS_DASS(0x55), MS_DELAY(10), \ + MS_DASS(0x00), MS_DELAY(10), \ + MS_DASS(0xff), MS_DELAY(10), \ + MS_RFETCH(MS_REG_STR, 0xb8, MS_UNKNOWN /* &s1 */), \ + MS_DASS(0x87), MS_DELAY(10), \ + MS_RFETCH(MS_REG_STR, 0xb8, MS_UNKNOWN /* &s2 */), \ + MS_DASS(0x78), MS_DELAY(10), \ + MS_RFETCH(MS_REG_STR, 0x38, MS_UNKNOWN /* &s3 */), \ + MS_DASS(MS_UNKNOWN /* param */), \ + MS_DELAY(2), \ + MS_CASS(0x0c), MS_DELAY(10), \ + MS_CASS(0x0d), MS_DELAY(2), \ + MS_CASS(0x0c), MS_DELAY(10), \ + MS_DASS(0xff), MS_DELAY(10), \ + MS_RET(0) \ +} + +#define NEGOCIATED_MODE MS_PARAM(2, 1, MS_TYP_CHA) + +#define DECLARE_NEGOCIATE_MICROSEQ \ +struct ppb_microseq negociate_microseq[] = { \ + MS_CASS(0x4), \ + MS_DELAY(5), \ + MS_DASS(MS_UNKNOWN /* mode */), \ + MS_DELAY(100), \ + MS_CASS(0x6), \ + MS_DELAY(5), \ + MS_BRSET(0x20, 5 /* continue */), \ + MS_DELAY(5), \ + MS_CASS(0x7), \ + MS_DELAY(5), \ + MS_CASS(0x6), \ + MS_RET(VP0_ENEGOCIATE), \ +/* continue: */ \ + MS_DELAY(5), \ + MS_CASS(0x7), \ + MS_DELAY(5), \ + MS_CASS(0x6), \ + MS_RET(0) \ +} + +#define INB_NIBBLE_L MS_PARAM(3, 2, MS_TYP_PTR) +#define INB_NIBBLE_H MS_PARAM(6, 2, MS_TYP_PTR) +#define INB_NIBBLE_F MS_PARAM(9, 0, MS_TYP_FUN) +#define INB_NIBBLE_P MS_PARAM(9, 1, MS_TYP_PTR) + +/* + * This is the sub-microseqence for MS_GET in NIBBLE mode + * Retrieve the two nibbles and call the C function to generate the character + * and store it in the buffer (see nibble_inbyte_hook()) + */ + +#define DECLARE_NIBBLE_INBYTE_SUBMICROSEQ \ +struct ppb_microseq nibble_inbyte_submicroseq[] = { \ + MS_CASS(0x4), \ +/* loop: */ \ + MS_CASS(0x6), \ + MS_DELAY(1), \ + MS_RFETCH(MS_REG_STR, MS_FETCH_ALL, MS_UNKNOWN /* low nibble */),\ + MS_CASS(0x5), \ + MS_DELAY(1), \ + MS_RFETCH(MS_REG_STR, MS_FETCH_ALL, MS_UNKNOWN /* high nibble */),\ + MS_CASS(0x4), \ + MS_DELAY(1), \ + /* do a C call to format the received nibbles */ \ + MS_C_CALL(MS_UNKNOWN /* C hook */, MS_UNKNOWN /* param */), \ + MS_DBRA(-7 /* loop */), \ + MS_RET(0) \ +} + +static struct ppb_microseq reset_microseq[] = { + MS_CASS(0x04), + MS_DASS(0x40), + MS_DELAY(1), + MS_CASS(0x0c), + MS_CASS(0x0d), + MS_DELAY(50), + MS_CASS(0x0c), + MS_CASS(0x04), + MS_RET(0) +}; + +/* + * nibble_inbyte_hook() + * + * Formats high and low nibble into a character + */ +static int +nibble_inbyte_hook (void *p, char *ptr) +{ + struct vpo_nibble *s = (struct vpo_nibble *)p; + + /* increment the buffer pointer */ + *ptr = ((s->l >> 4) & 0x0f) + (s->h & 0xf0); + + return (0); +} + +/* + * This is the sub-microseqence for MS_GET in PS2 mode + */ +static struct ppb_microseq ps2_inbyte_submicroseq[] = { + MS_CASS(0x4), + +/* loop: */ + MS_CASS(PCD | 0x6), + MS_RFETCH_P(1, MS_REG_DTR, MS_FETCH_ALL), + MS_CASS(PCD | 0x5), + MS_DBRA(-4 /* loop */), + + MS_RET(0) +}; + +/* + * This is the sub-microsequence for MS_PUT in both NIBBLE and PS2 modes + */ +static struct ppb_microseq spp_outbyte_submicroseq[] = { + MS_CASS(0x4), + +/* loop: */ + MS_RASSERT_P(1, MS_REG_DTR), + MS_CASS(0x5), + MS_DBRA(0), /* decrement counter */ + MS_RASSERT_P(1, MS_REG_DTR), + MS_CASS(0x0), + MS_DBRA(-6 /* loop */), + + /* return from the put call */ + MS_CASS(0x4), + MS_RET(0) +}; + +/* EPP 1.7 microsequences, ptr and len set at runtime */ +static struct ppb_microseq epp17_outstr[] = { + MS_CASS(0x4), + MS_RASSERT_P(MS_ACCUM, MS_REG_EPP_D), + MS_CASS(0xc), + MS_RET(0), +}; + +static struct ppb_microseq epp17_instr[] = { + MS_CASS(PCD | 0x4), + MS_RFETCH_P(MS_ACCUM, MS_REG_EPP_D, MS_FETCH_ALL), + MS_CASS(PCD | 0xc), + MS_RET(0), +}; + +static int +imm_disconnect(struct vpoio_data *vpo, int *connected, int release_bus) +{ + DECLARE_CPP_MICROSEQ; + + device_t ppbus = device_get_parent(vpo->vpo_dev); + char s1, s2, s3; + int ret; + + /* all should be ok */ + if (connected) + *connected = 0; + + ppb_MS_init_msq(cpp_microseq, 4, CPP_S1, (void *)&s1, + CPP_S2, (void *)&s2, CPP_S3, (void *)&s3, + CPP_PARAM, 0x30); + + ppb_MS_microseq(ppbus, vpo->vpo_dev, cpp_microseq, &ret); + + if ((s1 != (char)0xb8 || s2 != (char)0x18 || s3 != (char)0x38)) { + if (bootverbose) + printf("imm%d: (disconnect) s1=0x%x s2=0x%x, s3=0x%x\n", + vpo->vpo_unit, s1 & 0xff, s2 & 0xff, s3 & 0xff); + if (connected) + *connected = VP0_ECONNECT; + } + + if (release_bus) + return (ppb_release_bus(ppbus, vpo->vpo_dev)); + else + return (0); +} + +/* + * how : PPB_WAIT or PPB_DONTWAIT + */ +static int +imm_connect(struct vpoio_data *vpo, int how, int *disconnected, int request_bus) +{ + DECLARE_CPP_MICROSEQ; + + device_t ppbus = device_get_parent(vpo->vpo_dev); + char s1, s2, s3; + int error; + int ret; + + /* all should be ok */ + if (disconnected) + *disconnected = 0; + + if (request_bus) + if ((error = ppb_request_bus(ppbus, vpo->vpo_dev, how))) + return (error); + + ppb_MS_init_msq(cpp_microseq, 3, CPP_S1, (void *)&s1, + CPP_S2, (void *)&s2, CPP_S3, (void *)&s3); + + /* select device 0 in compatible mode */ + ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0xe0); + ppb_MS_microseq(ppbus, vpo->vpo_dev, cpp_microseq, &ret); + + /* disconnect all devices */ + ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0x30); + ppb_MS_microseq(ppbus, vpo->vpo_dev, cpp_microseq, &ret); + + if (PPB_IN_EPP_MODE(ppbus)) + ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0x28); + else + ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0xe0); + + ppb_MS_microseq(ppbus, vpo->vpo_dev, cpp_microseq, &ret); + + if ((s1 != (char)0xb8 || s2 != (char)0x18 || s3 != (char)0x30)) { + if (bootverbose) + printf("imm%d: (connect) s1=0x%x s2=0x%x, s3=0x%x\n", + vpo->vpo_unit, s1 & 0xff, s2 & 0xff, s3 & 0xff); + if (disconnected) + *disconnected = VP0_ECONNECT; + } + + return (0); +} + +/* + * imm_detect() + * + * Detect and initialise the VP0 adapter. + */ +static int +imm_detect(struct vpoio_data *vpo) +{ + device_t ppbus = device_get_parent(vpo->vpo_dev); + int error; + + if ((error = ppb_request_bus(ppbus, vpo->vpo_dev, PPB_DONTWAIT))) + return (error); + + /* disconnect the drive, keep the bus */ + imm_disconnect(vpo, NULL, 0); + + vpo->vpo_mode_found = VP0_MODE_UNDEFINED; + error = 1; + + /* try to enter EPP mode since vpoio failure put the bus in NIBBLE */ + if (ppb_set_mode(ppbus, PPB_EPP) != -1) { + imm_connect(vpo, PPB_DONTWAIT, &error, 0); + } + + /* if connection failed try PS/2 then NIBBLE modes */ + if (error) { + if (ppb_set_mode(ppbus, PPB_PS2) != -1) { + imm_connect(vpo, PPB_DONTWAIT, &error, 0); + } + if (error) { + if (ppb_set_mode(ppbus, PPB_NIBBLE) != -1) { + imm_connect(vpo, PPB_DONTWAIT, &error, 0); + if (error) + goto error; + vpo->vpo_mode_found = VP0_MODE_NIBBLE; + } else { + printf("imm%d: NIBBLE mode unavailable!\n", vpo->vpo_unit); + goto error; + } + } else { + vpo->vpo_mode_found = VP0_MODE_PS2; + } + } else { + vpo->vpo_mode_found = VP0_MODE_EPP; + } + + /* send SCSI reset signal */ + ppb_MS_microseq(ppbus, vpo->vpo_dev, reset_microseq, NULL); + + /* release the bus now */ + imm_disconnect(vpo, &error, 1); + + /* ensure we are disconnected or daisy chained peripheral + * may cause serious problem to the disk */ + + if (error) { + if (bootverbose) + printf("imm%d: can't disconnect from the drive\n", + vpo->vpo_unit); + goto error; + } + + return (0); + +error: + ppb_release_bus(ppbus, vpo->vpo_dev); + return (VP0_EINITFAILED); +} + +/* + * imm_outstr() + */ +static int +imm_outstr(struct vpoio_data *vpo, char *buffer, int size) +{ + device_t ppbus = device_get_parent(vpo->vpo_dev); + int error = 0; + + if (PPB_IN_EPP_MODE(ppbus)) + ppb_reset_epp_timeout(ppbus); + + ppb_MS_exec(ppbus, vpo->vpo_dev, MS_OP_PUT, (union ppb_insarg)buffer, + (union ppb_insarg)size, (union ppb_insarg)MS_UNKNOWN, &error); + + return (error); +} + +/* + * imm_instr() + */ +static int +imm_instr(struct vpoio_data *vpo, char *buffer, int size) +{ + device_t ppbus = device_get_parent(vpo->vpo_dev); + int error = 0; + + if (PPB_IN_EPP_MODE(ppbus)) + ppb_reset_epp_timeout(ppbus); + + ppb_MS_exec(ppbus, vpo->vpo_dev, MS_OP_GET, (union ppb_insarg)buffer, + (union ppb_insarg)size, (union ppb_insarg)MS_UNKNOWN, &error); + + return (error); +} + +static char +imm_select(struct vpoio_data *vpo, int initiator, int target) +{ + DECLARE_SELECT_MICROSEQUENCE; + device_t ppbus = device_get_parent(vpo->vpo_dev); + int ret; + + /* initialize the select microsequence */ + ppb_MS_init_msq(select_microseq, 1, + SELECT_TARGET, 1 << initiator | 1 << target); + + ppb_MS_microseq(ppbus, vpo->vpo_dev, select_microseq, &ret); + + return (ret); +} + +/* + * imm_wait() + * + * H_SELIN must be low. + * + */ +static char +imm_wait(struct vpoio_data *vpo, int tmo) +{ + DECLARE_WAIT_MICROSEQUENCE; + + device_t ppbus = device_get_parent(vpo->vpo_dev); + int ret, err; + + /* + * Return some status information. + * Semantics : 0x88 = ZIP+ wants more data + * 0x98 = ZIP+ wants to send more data + * 0xa8 = ZIP+ wants command + * 0xb8 = end of transfer, ZIP+ is sending status + */ + + ppb_MS_init_msq(wait_microseq, 2, + WAIT_RET, (void *)&ret, + WAIT_TMO, tmo); + + ppb_MS_microseq(ppbus, vpo->vpo_dev, wait_microseq, &err); + + if (err) + return (0); /* command timed out */ + + return(ret); +} + +static int +imm_negociate(struct vpoio_data *vpo) +{ + DECLARE_NEGOCIATE_MICROSEQ; + device_t ppbus = device_get_parent(vpo->vpo_dev); + int negociate_mode; + int ret; + + if (PPB_IN_NIBBLE_MODE(ppbus)) + negociate_mode = 0; + else if (PPB_IN_PS2_MODE(ppbus)) + negociate_mode = 1; + else + return (0); + +#if 0 /* XXX use standalone code not to depend on ppb_1284 code yet */ + ret = ppb_1284_negociate(ppbus, negociate_mode); + + if (ret) + return (VP0_ENEGOCIATE); +#endif + + ppb_MS_init_msq(negociate_microseq, 1, + NEGOCIATED_MODE, negociate_mode); + + ppb_MS_microseq(ppbus, vpo->vpo_dev, negociate_microseq, &ret); + + return (ret); +} + +/* + * imm_probe() + * + * Low level probe of vpo device + * + */ +int +imm_probe(device_t dev, struct vpoio_data *vpo) +{ + int error; + + /* ppbus dependent initialisation */ + vpo->vpo_dev = dev; + + /* now, try to initialise the drive */ + if ((error = imm_detect(vpo))) { + return (error); + } + + return (0); +} + +/* + * imm_attach() + * + * Low level attachment of vpo device + * + */ +int +imm_attach(struct vpoio_data *vpo) +{ + DECLARE_NIBBLE_INBYTE_SUBMICROSEQ; + device_t ppbus = device_get_parent(vpo->vpo_dev); + int error = 0; + + /* + * Initialize microsequence code + */ + vpo->vpo_nibble_inbyte_msq = (struct ppb_microseq *)malloc( + sizeof(nibble_inbyte_submicroseq), M_DEVBUF, M_NOWAIT); + + if (!vpo->vpo_nibble_inbyte_msq) + return (ENXIO); + + bcopy((void *)nibble_inbyte_submicroseq, + (void *)vpo->vpo_nibble_inbyte_msq, + sizeof(nibble_inbyte_submicroseq)); + + ppb_MS_init_msq(vpo->vpo_nibble_inbyte_msq, 4, + INB_NIBBLE_H, (void *)&(vpo)->vpo_nibble.h, + INB_NIBBLE_L, (void *)&(vpo)->vpo_nibble.l, + INB_NIBBLE_F, nibble_inbyte_hook, + INB_NIBBLE_P, (void *)&(vpo)->vpo_nibble); + + /* + * Initialize mode dependent in/out microsequences + */ + if ((error = ppb_request_bus(ppbus, vpo->vpo_dev, PPB_WAIT))) + goto error; + + /* ppbus automatically restore the last mode entered during detection */ + switch (vpo->vpo_mode_found) { + case VP0_MODE_EPP: + ppb_MS_GET_init(ppbus, vpo->vpo_dev, epp17_instr); + ppb_MS_PUT_init(ppbus, vpo->vpo_dev, epp17_outstr); + printf("imm%d: EPP mode\n", vpo->vpo_unit); + break; + case VP0_MODE_PS2: + ppb_MS_GET_init(ppbus, vpo->vpo_dev, ps2_inbyte_submicroseq); + ppb_MS_PUT_init(ppbus, vpo->vpo_dev, spp_outbyte_submicroseq); + printf("imm%d: PS2 mode\n", vpo->vpo_unit); + break; + case VP0_MODE_NIBBLE: + ppb_MS_GET_init(ppbus, vpo->vpo_dev, vpo->vpo_nibble_inbyte_msq); + ppb_MS_PUT_init(ppbus, vpo->vpo_dev, spp_outbyte_submicroseq); + printf("imm%d: NIBBLE mode\n", vpo->vpo_unit); + break; + default: + panic("imm: unknown mode %d", vpo->vpo_mode_found); + } + + ppb_release_bus(ppbus, vpo->vpo_dev); + error: + return (error); +} + +/* + * imm_reset_bus() + * + */ +int +imm_reset_bus(struct vpoio_data *vpo) +{ + device_t ppbus = device_get_parent(vpo->vpo_dev); + int disconnected; + + /* first, connect to the drive and request the bus */ + imm_connect(vpo, PPB_WAIT|PPB_INTR, &disconnected, 1); + + if (!disconnected) { + + /* reset the SCSI bus */ + ppb_MS_microseq(ppbus, vpo->vpo_dev, reset_microseq, NULL); + + /* then disconnect */ + imm_disconnect(vpo, NULL, 1); + } + + return (0); +} + +/* + * imm_do_scsi() + * + * Send an SCSI command + * + */ +int +imm_do_scsi(struct vpoio_data *vpo, int host, int target, char *command, + int clen, char *buffer, int blen, int *result, int *count, + int *ret) +{ + device_t ppbus = device_get_parent(vpo->vpo_dev); + register char r; + char l, h = 0; + int len, error = 0, not_connected = 0; + register int k; + int negociated = 0; + + /* + * enter disk state, allocate the ppbus + * + * XXX + * Should we allow this call to be interruptible? + * The only way to report the interruption is to return + * EIO to upper SCSI code :^( + */ + if ((error = imm_connect(vpo, PPB_WAIT|PPB_INTR, ¬_connected, 1))) + return (error); + + if (not_connected) { + *ret = VP0_ECONNECT; goto error; + } + + /* + * Select the drive ... + */ + if ((*ret = imm_select(vpo,host,target))) + goto error; + + /* + * Send the command ... + */ + for (k = 0; k < clen; k+=2) { + if (imm_wait(vpo, VP0_FAST_SPINTMO) != (char)0xa8) { + *ret = VP0_ECMD_TIMEOUT; + goto error; + } + if (imm_outstr(vpo, &command[k], 2)) { + *ret = VP0_EPPDATA_TIMEOUT; + goto error; + } + } + + if (!(r = imm_wait(vpo, VP0_LOW_SPINTMO))) { + *ret = VP0_ESTATUS_TIMEOUT; goto error; + } + + if ((r & 0x30) == 0x10) { + if (imm_negociate(vpo)) { + *ret = VP0_ENEGOCIATE; + goto error; + } else + negociated = 1; + } + + /* + * Complete transfer ... + */ + *count = 0; + for (;;) { + + if (!(r = imm_wait(vpo, VP0_LOW_SPINTMO))) { + *ret = VP0_ESTATUS_TIMEOUT; goto error; + } + + /* stop when the ZIP+ wants to send status */ + if (r == (char)0xb8) + break; + + if (*count >= blen) { + *ret = VP0_EDATA_OVERFLOW; + goto error; + } + + /* ZIP+ wants to send data? */ + if (r == (char)0x88) { + len = (((blen - *count) >= VP0_SECTOR_SIZE)) ? + VP0_SECTOR_SIZE : 2; + + error = imm_outstr(vpo, &buffer[*count], len); + } else { + if (!PPB_IN_EPP_MODE(ppbus)) + len = 1; + else + len = (((blen - *count) >= VP0_SECTOR_SIZE)) ? + VP0_SECTOR_SIZE : 1; + + error = imm_instr(vpo, &buffer[*count], len); + } + + if (error) { + *ret = error; + goto error; + } + + *count += len; + } + + if ((PPB_IN_NIBBLE_MODE(ppbus) || + PPB_IN_PS2_MODE(ppbus)) && negociated) + ppb_MS_microseq(ppbus, vpo->vpo_dev, transfer_epilog, NULL); + + /* + * Retrieve status ... + */ + if (imm_negociate(vpo)) { + *ret = VP0_ENEGOCIATE; + goto error; + } else + negociated = 1; + + if (imm_instr(vpo, &l, 1)) { + *ret = VP0_EOTHER; goto error; + } + + /* check if the ZIP+ wants to send more status */ + if (imm_wait(vpo, VP0_FAST_SPINTMO) == (char)0xb8) + if (imm_instr(vpo, &h, 1)) { + *ret = VP0_EOTHER+2; goto error; + } + + /* Experience showed that we should discard this */ + if (h == -1) + h = 0; + + *result = ((int) h << 8) | ((int) l & 0xff); + +error: + if ((PPB_IN_NIBBLE_MODE(ppbus) || + PPB_IN_PS2_MODE(ppbus)) && negociated) + ppb_MS_microseq(ppbus, vpo->vpo_dev, transfer_epilog, NULL); + + /* return to printer state, release the ppbus */ + imm_disconnect(vpo, NULL, 1); + + return (0); +} diff --git a/sys/dev/ppbus/lpbb.c b/sys/dev/ppbus/lpbb.c new file mode 100644 index 0000000..2b3dfff --- /dev/null +++ b/sys/dev/ppbus/lpbb.c @@ -0,0 +1,224 @@ +/*- + * Copyright (c) 1998, 2001 Nicolas Souchu, Marc Bouget + * 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. + * + * $FreeBSD$ + * + */ + +/* + * I2C Bit-Banging over parallel port + * + * See the Official Philips interface description in lpbb(4) + */ + +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/systm.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/uio.h> + + +#include <dev/ppbus/ppbconf.h> +#include "ppbus_if.h" +#include <dev/ppbus/ppbio.h> + +#include <dev/iicbus/iiconf.h> +#include <dev/iicbus/iicbus.h> + +#include "iicbb_if.h" + +static int lpbb_detect(device_t dev); + +static void +lpbb_identify(driver_t *driver, device_t parent) +{ + + BUS_ADD_CHILD(parent, 0, "lpbb", -1); +} + +static int +lpbb_probe(device_t dev) +{ + + /* Perhaps call this during identify instead? */ + if (!lpbb_detect(dev)) + return (ENXIO); + + device_set_desc(dev, "Parallel I2C bit-banging interface"); + + return (0); +} + +static int +lpbb_attach(device_t dev) +{ + device_t bitbang; + + /* add generic bit-banging code */ + bitbang = device_add_child(dev, "iicbb", -1); + device_probe_and_attach(bitbang); + + return (0); +} + +static int +lpbb_callback(device_t dev, int index, caddr_t *data) +{ + device_t ppbus = device_get_parent(dev); + int error = 0; + int how; + + switch (index) { + case IIC_REQUEST_BUS: + /* request the ppbus */ + how = *(int *)data; + error = ppb_request_bus(ppbus, dev, how); + break; + + case IIC_RELEASE_BUS: + /* release the ppbus */ + error = ppb_release_bus(ppbus, dev); + break; + + default: + error = EINVAL; + } + + return (error); +} + +#define SDA_out 0x80 +#define SCL_out 0x08 +#define SDA_in 0x80 +#define SCL_in 0x08 +#define ALIM 0x20 +#define I2CKEY 0x50 + +static int lpbb_getscl(device_t dev) +{ + return ((ppb_rstr(device_get_parent(dev)) & SCL_in) == SCL_in); +} + +static int lpbb_getsda(device_t dev) +{ + return ((ppb_rstr(device_get_parent(dev)) & SDA_in) == SDA_in); +} + +static void lpbb_setsda(device_t dev, char val) +{ + device_t ppbus = device_get_parent(dev); + + if(val==0) + ppb_wdtr(ppbus, (u_char)SDA_out); + else + ppb_wdtr(ppbus, (u_char)~SDA_out); +} + +static void lpbb_setscl(device_t dev, unsigned char val) +{ + device_t ppbus = device_get_parent(dev); + + if(val==0) + ppb_wctr(ppbus, (u_char)(ppb_rctr(ppbus)&~SCL_out)); + else + ppb_wctr(ppbus, (u_char)(ppb_rctr(ppbus)|SCL_out)); +} + +static int lpbb_detect(device_t dev) +{ + device_t ppbus = device_get_parent(dev); + + if (ppb_request_bus(ppbus, dev, PPB_DONTWAIT)) { + device_printf(dev, "can't allocate ppbus\n"); + return (0); + } + + /* reset bus */ + lpbb_setsda(dev, 1); + lpbb_setscl(dev, 1); + + if ((ppb_rstr(ppbus) & I2CKEY) || + ((ppb_rstr(ppbus) & ALIM) != ALIM)) { + + ppb_release_bus(ppbus, dev); + return (0); + } + + ppb_release_bus(ppbus, dev); + + return (1); +} + +static int +lpbb_reset(device_t dev, u_char speed, u_char addr, u_char * oldaddr) +{ + device_t ppbus = device_get_parent(dev); + + if (ppb_request_bus(ppbus, dev, PPB_DONTWAIT)) { + device_printf(dev, "can't allocate ppbus\n"); + return (0); + } + + /* reset bus */ + lpbb_setsda(dev, 1); + lpbb_setscl(dev, 1); + + ppb_release_bus(ppbus, dev); + + return (IIC_ENOADDR); +} + +static devclass_t lpbb_devclass; + +static device_method_t lpbb_methods[] = { + /* device interface */ + DEVMETHOD(device_identify, lpbb_identify), + DEVMETHOD(device_probe, lpbb_probe), + DEVMETHOD(device_attach, lpbb_attach), + + /* bus interface */ + DEVMETHOD(bus_print_child, bus_generic_print_child), + + /* iicbb interface */ + DEVMETHOD(iicbb_callback, lpbb_callback), + DEVMETHOD(iicbb_setsda, lpbb_setsda), + DEVMETHOD(iicbb_setscl, lpbb_setscl), + DEVMETHOD(iicbb_getsda, lpbb_getsda), + DEVMETHOD(iicbb_getscl, lpbb_getscl), + DEVMETHOD(iicbb_reset, lpbb_reset), + + { 0, 0 } +}; + +static driver_t lpbb_driver = { + "lpbb", + lpbb_methods, + 1, +}; + +DRIVER_MODULE(lpbb, ppbus, lpbb_driver, lpbb_devclass, 0, 0); +MODULE_DEPEND(lpbb, iicbb, IICBB_MINVER, IICBB_PREFVER, IICBB_MAXVER); +MODULE_VERSION(lpbb, 1); diff --git a/sys/dev/ppbus/lpt.c b/sys/dev/ppbus/lpt.c new file mode 100644 index 0000000..9364c50 --- /dev/null +++ b/sys/dev/ppbus/lpt.c @@ -0,0 +1,974 @@ +/* + * 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$ + */ + +/* + * 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_lpt.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/kernel.h> +#include <sys/uio.h> +#include <sys/syslog.h> +#include <sys/malloc.h> + +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include <dev/ppbus/lptio.h> +#include <dev/ppbus/ppbconf.h> +#include <dev/ppbus/ppb_1284.h> +#include <dev/ppbus/lpt.h> +#include "ppbus_if.h" +#include <dev/ppbus/ppbio.h> + +#ifndef LPT_DEBUG +#define lprintf(args) +#else +#define lprintf(args) \ + do { \ + if (lptflag) \ + printf args; \ + } while (0) +static int volatile lptflag = 1; +#endif + +#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 +#define BUFSTATSIZE 32 + +#define LPTUNIT(s) ((s)&0x03) +#define LPTFLAGS(s) ((s)&0xfc) + +struct lpt_data { + + 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 */ + void *sc_inbuf; + void *sc_statbuf; + short sc_xfercnt ; + char sc_primed; + char *sc_cp ; + u_short 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 */ +#define LP_ENABLE_EXT 0x10 /* we shall use advanced mode when possible */ + u_char sc_backoff ; /* time to call lptout() again */ + + struct resource *intr_resource; /* interrupt resource */ + void *intr_cookie; /* interrupt registration cookie */ + +}; + +#define LPT_NAME "lpt" /* our official name */ + +static timeout_t lptout; +static int lpt_port_test(device_t dev, u_char data, u_char mask); +static int lpt_detect(device_t dev); + +#define DEVTOSOFTC(dev) \ + ((struct lpt_data *)device_get_softc(dev)) +#define UNITOSOFTC(unit) \ + ((struct lpt_data *)devclass_get_softc(lpt_devclass, (unit))) +#define UNITODEVICE(unit) \ + (devclass_get_device(lpt_devclass, (unit))) + +static void lptintr(device_t dev); +static void lpt_intr(void *arg); /* without spls */ + +static devclass_t lpt_devclass; + + +/* bits for state */ +#define OPEN (1<<0) /* device is open */ +#define ASLP (1<<1) /* awaiting draining of printer */ +#define EERROR (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 LPTINIT (1<<6) /* waiting to initialize for open */ +#define INTERRUPTED (1<<7) /* write call was interrupted */ + +#define HAVEBUS (1<<8) /* the driver owns the bus */ + + +/* 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 */ +#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(ppbus) ((ppb_rstr(ppbus)^LPS_INVERT)&LPS_MASK) + +#define MAX_SLEEP (hz*5) /* Timeout while waiting for device ready */ +#define MAX_SPIN 20 /* Max delay for device ready in usecs */ + + +static d_open_t lptopen; +static d_close_t lptclose; +static d_write_t lptwrite; +static d_read_t lptread; +static d_ioctl_t lptioctl; + +#define CDEV_MAJOR 16 +static struct cdevsw lpt_cdevsw = { + /* open */ lptopen, + /* close */ lptclose, + /* read */ lptread, + /* write */ lptwrite, + /* ioctl */ lptioctl, + /* poll */ nopoll, + /* mmap */ nommap, + /* strategy */ nostrategy, + /* name */ LPT_NAME, + /* maj */ CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ 0, +}; + +static int +lpt_request_ppbus(device_t dev, int how) +{ + device_t ppbus = device_get_parent(dev); + struct lpt_data *sc = DEVTOSOFTC(dev); + int error; + + if (sc->sc_state & HAVEBUS) + return (0); + + /* we have the bus only if the request succeded */ + if ((error = ppb_request_bus(ppbus, dev, how)) == 0) + sc->sc_state |= HAVEBUS; + + return (error); +} + +static int +lpt_release_ppbus(device_t dev) +{ + device_t ppbus = device_get_parent(dev); + struct lpt_data *sc = DEVTOSOFTC(dev); + int error = 0; + + if ((error = ppb_release_bus(ppbus, dev)) == 0) + sc->sc_state &= ~HAVEBUS; + + return (error); +} + +/* + * Internal routine to lptprobe to do port tests of one byte value + */ +static int +lpt_port_test(device_t ppbus, u_char data, u_char mask) +{ + int temp, timeout; + + data = data & mask; + ppb_wdtr(ppbus, data); + timeout = 10000; + do { + DELAY(10); + temp = ppb_rdtr(ppbus) & mask; + } + while (temp != data && --timeout); + lprintf(("out=%x\tin=%x\ttout=%d\n", data, temp, timeout)); + return (temp == data); +} + +/* + * 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(device_t dev) +{ + device_t ppbus = device_get_parent(dev); + + 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, error, status; + + status = 1; /* assume success */ + + if ((error = lpt_request_ppbus(dev, PPB_DONTWAIT))) { + printf(LPT_NAME ": cannot alloc ppbus (%d)!\n", error); + status = 0; + goto end_probe; + } + + for (i = 0; i < 18 && status; i++) + if (!lpt_port_test(ppbus, testbyte[i], 0xff)) { + status = 0; + goto end_probe; + } + +end_probe: + /* write 0's to control and data ports */ + ppb_wdtr(ppbus, 0); + ppb_wctr(ppbus, 0); + + lpt_release_ppbus(dev); + + return (status); +} + +static void +lpt_identify(driver_t *driver, device_t parent) +{ + + BUS_ADD_CHILD(parent, 0, LPT_NAME, -1); +} + +/* + * lpt_probe() + */ +static int +lpt_probe(device_t dev) +{ + struct lpt_data *sc; + + sc = DEVTOSOFTC(dev); + bzero(sc, sizeof(struct lpt_data)); + + /* + * Now, try to detect the printer. + */ + if (!lpt_detect(dev)) + return (ENXIO); + + device_set_desc(dev, "Printer"); + + return (0); +} + +static int +lpt_attach(device_t dev) +{ + device_t ppbus = device_get_parent(dev); + struct lpt_data *sc = DEVTOSOFTC(dev); + int zero = 0, unit = device_get_unit(dev); + int error; + intptr_t irq; + + sc->sc_primed = 0; /* not primed yet */ + + if ((error = lpt_request_ppbus(dev, PPB_DONTWAIT))) { + printf(LPT_NAME ": cannot alloc ppbus (%d)!\n", error); + return (0); + } + + ppb_wctr(ppbus, LPC_NINIT); + + /* check if we can use interrupt, should be done by ppc stuff */ + lprintf(("oldirq %x\n", sc->sc_irq)); + + /* retrieve the ppbus irq */ + BUS_READ_IVAR(ppbus, dev, PPBUS_IVAR_IRQ, &irq); + + if (irq > 0) { + /* declare our interrupt handler */ + sc->intr_resource = bus_alloc_resource(dev, SYS_RES_IRQ, + &zero, irq, irq, 1, RF_SHAREABLE); + } + if (sc->intr_resource) { + sc->sc_irq = LP_HAS_IRQ | LP_USE_IRQ | LP_ENABLE_IRQ; + device_printf(dev, "Interrupt-driven port\n"); + } else { + sc->sc_irq = 0; + device_printf(dev, "Polled port\n"); + } + lprintf(("irq %x %x\n", (int)irq, sc->sc_irq)); + + lpt_release_ppbus(dev); + + make_dev(&lpt_cdevsw, unit, + UID_ROOT, GID_WHEEL, 0600, LPT_NAME "%d", unit); + make_dev(&lpt_cdevsw, unit | LP_BYPASS, + UID_ROOT, GID_WHEEL, 0600, LPT_NAME "%d.ctl", unit); + return (0); +} + +static void +lptout(void *arg) +{ + device_t dev = (device_t)arg; + struct lpt_data *sc = DEVTOSOFTC(dev); +#ifdef LPT_DEBUG + device_t ppbus = device_get_parent(dev); +#endif + + lprintf(("T %x ", ppb_rstr(ppbus))); + 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)dev, sc->sc_backoff); + } else + sc->sc_state &= ~TOUT; + + if (sc->sc_state & EERROR) + sc->sc_state &= ~EERROR; + + /* + * Avoid possible hangs due to missed interrupts + */ + if (sc->sc_xfercnt) { + lptintr(dev); + } else { + sc->sc_state &= ~OBUSY; + wakeup((caddr_t)dev); + } +} + +/* + * 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 thread *td) +{ + int s; + int trys, err; + u_int unit = LPTUNIT(minor(dev)); + struct lpt_data *sc = UNITOSOFTC(unit); + device_t lptdev = UNITODEVICE(unit); + device_t ppbus = device_get_parent(lptdev); + + if (!sc) + return (ENXIO); + + if (sc->sc_state) { + lprintf((LPT_NAME ": still open %x\n", sc->sc_state)); + return(EBUSY); + } else + sc->sc_state |= LPTINIT; + + 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); + } + + /* request the ppbus only if we don't have it already */ + if ((err = lpt_request_ppbus(lptdev, PPB_WAIT|PPB_INTR)) != 0) { + /* give it a chance to try later */ + sc->sc_state = 0; + return (err); + } + + s = spltty(); + lprintf((LPT_NAME " flags 0x%x\n", sc->sc_flags)); + + /* 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 */ + if ((sc->sc_flags & LP_NO_PRIME) == 0) { + if((sc->sc_flags & LP_PRIMEOPEN) || sc->sc_primed == 0) { + ppb_wctr(ppbus, 0); + sc->sc_primed++; + DELAY(500); + } + } + + ppb_wctr(ppbus, 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", ppb_rstr(ppbus))); + + lpt_release_ppbus(lptdev); + return (EBUSY); + } + + /* wait 1/4 second, give up if we get a signal */ + if (tsleep((caddr_t)lptdev, LPPRI|PCATCH, "lptinit", hz/4) != + EWOULDBLOCK) { + sc->sc_state = 0; + splx(s); + + lpt_release_ppbus(lptdev); + return (EBUSY); + } + + /* is printer online and ready for output */ + } while ((ppb_rstr(ppbus) & + (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; + + ppb_wctr(ppbus, sc->sc_control); + + sc->sc_state = OPEN; + sc->sc_inbuf = malloc(BUFSIZE, M_DEVBUF, M_WAITOK); + sc->sc_statbuf = malloc(BUFSTATSIZE, M_DEVBUF, M_WAITOK); + sc->sc_xfercnt = 0; + splx(s); + + /* release the ppbus */ + lpt_release_ppbus(lptdev); + + /* 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)lptdev, + (sc->sc_backoff = hz/LPTOUTINITIAL)); + } + + lprintf(("opened.\n")); + return(0); +} + +/* + * 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 thread *td) +{ + u_int unit = LPTUNIT(minor(dev)); + struct lpt_data *sc = UNITOSOFTC(unit); + device_t lptdev = UNITODEVICE(unit); + device_t ppbus = device_get_parent(lptdev); + int err; + + if(sc->sc_flags & LP_BYPASS) + goto end_close; + + if ((err = lpt_request_ppbus(lptdev, PPB_WAIT|PPB_INTR)) != 0) + return (err); + + sc->sc_state &= ~OPEN; + + /* if the last write was interrupted, don't complete it */ + if((!(sc->sc_state & INTERRUPTED)) && (sc->sc_irq & LP_USE_IRQ)) + while ((ppb_rstr(ppbus) & + (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)lptdev, LPPRI|PCATCH, + "lpclose", hz) != EWOULDBLOCK) + break; + + ppb_wctr(ppbus, LPC_NINIT); + free(sc->sc_inbuf, M_DEVBUF); + free(sc->sc_statbuf, M_DEVBUF); + +end_close: + /* release the bus anyway + * unregistration of interrupt forced by release + */ + lpt_release_ppbus(lptdev); + + sc->sc_state = 0; + sc->sc_xfercnt = 0; + lprintf(("closed.\n")); + return(0); +} + +/* + * lpt_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 +lpt_pushbytes(device_t dev) +{ + struct lpt_data *sc = DEVTOSOFTC(dev); + device_t ppbus = device_get_parent(dev); + int spin, err, tic; + char ch; + + 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(ppbus) && spin < MAX_SPIN; ++spin) + DELAY(1); /* XXX delay is NOT this accurate! */ + if (spin >= MAX_SPIN) { + tic = 0; + while (NOT_READY(ppbus)) { + /* + * 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)dev, LPPRI, + LPT_NAME "poll", tic); + if (err != EWOULDBLOCK) { + return (err); + } + } + } + + /* output data */ + ppb_wdtr(ppbus, ch); + /* strobe */ + ppb_wctr(ppbus, sc->sc_control|LPC_STB); + ppb_wctr(ppbus, sc->sc_control); + + } + return(0); +} + +/* + * lptread --retrieve printer status in IEEE1284 NIBBLE mode + */ + +static int +lptread(dev_t dev, struct uio *uio, int ioflag) +{ + u_int unit = LPTUNIT(minor(dev)); + struct lpt_data *sc = UNITOSOFTC(unit); + device_t lptdev = UNITODEVICE(unit); + device_t ppbus = device_get_parent(lptdev); + int error = 0, len; + + if (sc->sc_flags & LP_BYPASS) { + /* we can't do reads in bypass mode */ + return (EPERM); + } + + if ((error = ppb_1284_negociate(ppbus, PPB_NIBBLE, 0))) + return (error); + + /* read data in an other buffer, read/write may be simultaneous */ + len = 0; + while (uio->uio_resid) { + if ((error = ppb_1284_read(ppbus, PPB_NIBBLE, + sc->sc_statbuf, min(BUFSTATSIZE, + uio->uio_resid), &len))) { + goto error; + } + + if (!len) + goto error; /* no more data */ + + if ((error = uiomove(sc->sc_statbuf, len, uio))) + goto error; + } + +error: + ppb_1284_terminate(ppbus); + 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. + */ + +static int +lptwrite(dev_t dev, struct uio *uio, int ioflag) +{ + register unsigned n; + int err; + u_int unit = LPTUNIT(minor(dev)); + struct lpt_data *sc = UNITOSOFTC(unit); + device_t lptdev = UNITODEVICE(unit); + device_t ppbus = device_get_parent(lptdev); + + if(sc->sc_flags & LP_BYPASS) { + /* we can't do writes in bypass mode */ + return(EPERM); + } + + /* request the ppbus only if we don't have it already */ + /* XXX interrupt registration?! */ + if ((err = lpt_request_ppbus(lptdev, PPB_WAIT|PPB_INTR)) != 0) + return (err); + + /* if interrupts are working, register the handler */ + if (sc->sc_irq & LP_USE_IRQ) { + /* register our interrupt handler */ + err = BUS_SETUP_INTR(ppbus, lptdev, sc->intr_resource, + INTR_TYPE_TTY, lpt_intr, lptdev, + &sc->intr_cookie); + if (err) { + device_printf(lptdev, "handler registration failed, polled mode.\n"); + sc->sc_irq &= ~LP_USE_IRQ; + } + } + + sc->sc_state &= ~INTERRUPTED; + while ((n = min(BUFSIZE, uio->uio_resid)) != 0) { + sc->sc_cp = sc->sc_inbuf; + uiomove(sc->sc_cp, n, uio); + sc->sc_xfercnt = n ; + + if (sc->sc_irq & LP_ENABLE_EXT) { + /* try any extended mode */ + err = ppb_write(ppbus, sc->sc_cp, + sc->sc_xfercnt, 0); + switch (err) { + case 0: + /* if not all data was sent, we could rely + * on polling for the last bytes */ + sc->sc_xfercnt = 0; + break; + case EINTR: + sc->sc_state |= INTERRUPTED; + return(err); + case EINVAL: + /* advanced mode not avail */ + log(LOG_NOTICE, LPT_NAME "%d: advanced mode not avail, polling\n", unit); + break; + default: + return(err); + } + } else 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)); + lptintr(lptdev); + } + lprintf(("W ")); + if (sc->sc_state & OBUSY) + if ((err = tsleep((caddr_t)lptdev, + LPPRI|PCATCH, LPT_NAME "write", 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")); + + err = lpt_pushbytes(lptdev); + + if (err) + return(err); + } + } + + /* we have not been interrupted, release the ppbus */ + lpt_release_ppbus(lptdev); + + return(0); +} + +/* + * lpt_intr -- handle printer interrupts which occur when the printer is + * ready to accept another char. + * + * do checking for interrupted write call. + */ + +static void +lpt_intr(void *arg) +{ + device_t lptdev = (device_t)arg; + device_t ppbus = device_get_parent(lptdev); + struct lpt_data *sc = DEVTOSOFTC(lptdev); + int sts = 0; + int i; + + /* we must own the bus to use it */ + if ((sc->sc_state & HAVEBUS) == 0) + return; + + /* + * 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=ppb_rstr(ppbus)) & RDY_MASK) != LP_READY; i++) ; + + if ((sts & RDY_MASK) == LP_READY) { + sc->sc_state = (sc->sc_state | OBUSY) & ~EERROR; + sc->sc_backoff = hz/LPTOUTINITIAL; + + if (sc->sc_xfercnt) { + /* send char */ + /*lprintf(("%x ", *sc->sc_cp)); */ + ppb_wdtr(ppbus, *sc->sc_cp++) ; + ppb_wctr(ppbus, sc->sc_control|LPC_STB); + /* DELAY(X) */ + ppb_wctr(ppbus, 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 not interrupted. + */ + sc->sc_state &= ~OBUSY; + + if(!(sc->sc_state & INTERRUPTED)) + wakeup((caddr_t)lptdev); + lprintf(("w ")); + return; + } else { /* check for error */ + if(((sts & (LPS_NERR | LPS_OUT) ) != LPS_NERR) && + (sc->sc_state & OPEN)) + sc->sc_state |= EERROR; + /* lptout() will jump in and try to restart. */ + } + lprintf(("sts %x ", sts)); +} + +static void +lptintr(device_t dev) +{ + /* call the interrupt at required spl level */ + int s = spltty(); + + lpt_intr(dev); + + splx(s); + return; +} + +static int +lptioctl(dev_t dev, u_long cmd, caddr_t data, int flags, struct thread *td) +{ + int error = 0; + u_int unit = LPTUNIT(minor(dev)); + struct lpt_data *sc = UNITOSOFTC(unit); + u_char old_sc_irq; /* old printer IRQ status */ + + 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; + switch(*(int*)data) { + case 0: + sc->sc_irq &= (~LP_ENABLE_IRQ); + break; + case 1: + sc->sc_irq &= (~LP_ENABLE_EXT); + sc->sc_irq |= LP_ENABLE_IRQ; + break; + case 2: + /* classic irq based transfer and advanced + * modes are in conflict + */ + sc->sc_irq &= (~LP_ENABLE_IRQ); + sc->sc_irq |= LP_ENABLE_EXT; + break; + case 3: + sc->sc_irq &= (~LP_ENABLE_EXT); + break; + default: + break; + } + + if (old_sc_irq != sc->sc_irq ) + log(LOG_NOTICE, LPT_NAME "%d: switched to %s %s mode\n", + unit, + (sc->sc_irq & LP_ENABLE_IRQ)? + "interrupt-driven":"polled", + (sc->sc_irq & LP_ENABLE_EXT)? + "extended":"standard"); + } else /* polled port */ + error = EOPNOTSUPP; + break; + default: + error = ENODEV; + } + + return(error); +} + +static device_method_t lpt_methods[] = { + /* device interface */ + DEVMETHOD(device_identify, lpt_identify), + DEVMETHOD(device_probe, lpt_probe), + DEVMETHOD(device_attach, lpt_attach), + + { 0, 0 } +}; + +static driver_t lpt_driver = { + LPT_NAME, + lpt_methods, + sizeof(struct lpt_data), +}; + +DRIVER_MODULE(lpt, ppbus, lpt_driver, lpt_devclass, 0, 0); diff --git a/sys/dev/ppbus/lpt.h b/sys/dev/ppbus/lpt.h new file mode 100644 index 0000000..9075b4e --- /dev/null +++ b/sys/dev/ppbus/lpt.h @@ -0,0 +1,66 @@ +/*- + * Copyright (c) 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * William Jolitz. + * + * Copyright (c) 1997, 1999 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. + * + * Distantly from : + * @(#)lptreg.h 1.1 (Berkeley) 12/19/90 + * Id: lptreg.h,v 1.6 1997/02/22 09:36:52 peter Exp + * From Id: nlpt.h,v 1.3 1999/01/10 12:04:54 nsouch Exp + * + * $FreeBSD$ + */ + +/* + * AT Parallel Port (for lineprinter) + * Interface port and bit definitions + * Written by William Jolitz 12/18/90 + * Copyright (C) William Jolitz 1990 + */ + +#ifndef __LPT_H +#define __LPT_H + +/* machine independent definitions, it shall only depend on the ppbus + * parallel port model */ + + /* PIN */ +#define LPS_NERR 0x08 /* 15 printer no error */ +#define LPS_SEL 0x10 /* 13 printer selected */ +#define LPS_OUT 0x20 /* 12 printer out of paper */ +#define LPS_NACK 0x40 /* 10 printer no ack of data */ +#define LPS_NBSY 0x80 /* 11 printer busy */ + +#define LPC_STB 0x01 /* 1 strobe data to printer */ +#define LPC_AUTOL 0x02 /* 14 automatic linefeed */ +#define LPC_NINIT 0x04 /* 16 initialize printer */ +#define LPC_SEL 0x08 /* 17 printer selected */ +#define LPC_ENA 0x10 /* - enable IRQ */ + +#endif diff --git a/sys/dev/ppbus/lptio.h b/sys/dev/ppbus/lptio.h new file mode 100644 index 0000000..8458bc6 --- /dev/null +++ b/sys/dev/ppbus/lptio.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 1994 Geoffrey M. Rehmet + * + * This program is free software; you may redistribute it and/or + * modify it, provided that it retain the above copyright notice + * and the following disclaimer. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * + * Geoff Rehmet, Rhodes University, South Africa <csgr@cs.ru.ac.za> + * + * $FreeBSD$ + */ + +#ifndef _DEV_PPBUS_LPT_H_ +#define _DEV_PPBUS_LPT_H_ + +#include <sys/ioccom.h> + +#define LPT_IRQ _IOW('p', 1, long) /* set interrupt status */ + +#endif /* !_DEV_PPBUS_LPT_H_ */ diff --git a/sys/dev/ppbus/pcfclock.c b/sys/dev/ppbus/pcfclock.c new file mode 100644 index 0000000..bd49ebb --- /dev/null +++ b/sys/dev/ppbus/pcfclock.c @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2000 Sascha Schumann. 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 SASCHA SCHUMANN ``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. + * + * $FreeBSD$ + * + */ + +#include "opt_pcfclock.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/sockio.h> +#include <sys/mbuf.h> +#include <sys/kernel.h> +#include <sys/conf.h> +#include <sys/fcntl.h> +#include <sys/uio.h> + +#include <machine/bus.h> +#include <machine/resource.h> + +#include <dev/ppbus/ppbconf.h> +#include <dev/ppbus/ppb_msq.h> +#include <dev/ppbus/ppbio.h> + +#include "ppbus_if.h" + +#define PCFCLOCK_NAME "pcfclock" + +struct pcfclock_data { + int count; +}; + +#define DEVTOSOFTC(dev) \ + ((struct pcfclock_data *)device_get_softc(dev)) +#define UNITOSOFTC(unit) \ + ((struct pcfclock_data *)devclass_get_softc(pcfclock_devclass, (unit))) +#define UNITODEVICE(unit) \ + (devclass_get_device(pcfclock_devclass, (unit))) + +static devclass_t pcfclock_devclass; + +static d_open_t pcfclock_open; +static d_close_t pcfclock_close; +static d_read_t pcfclock_read; + +#define CDEV_MAJOR 140 +static struct cdevsw pcfclock_cdevsw = { + /* open */ pcfclock_open, + /* close */ pcfclock_close, + /* read */ pcfclock_read, + /* write */ nowrite, + /* ioctl */ noioctl, + /* poll */ nopoll, + /* mmap */ nommap, + /* strategy */ nostrategy, + /* name */ PCFCLOCK_NAME, + /* maj */ CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ 0, +}; + +#ifndef PCFCLOCK_MAX_RETRIES +#define PCFCLOCK_MAX_RETRIES 10 +#endif + +#define AFC_HI 0 +#define AFC_LO AUTOFEED + +/* AUTO FEED is used as clock */ +#define AUTOFEED_CLOCK(val) \ + ctr = (ctr & ~(AUTOFEED)) ^ (val); ppb_wctr(ppbus, ctr) + +/* SLCT is used as clock */ +#define CLOCK_OK \ + ((ppb_rstr(ppbus) & SELECT) == (i & 1 ? SELECT : 0)) + +/* PE is used as data */ +#define BIT_SET (ppb_rstr(ppbus)&PERROR) + +/* the first byte sent as reply must be 00001001b */ +#define PCFCLOCK_CORRECT_SYNC(buf) (buf[0] == 9) + +#define NR(buf, off) (buf[off+1]*10+buf[off]) + +/* check for correct input values */ +#define PCFCLOCK_CORRECT_FORMAT(buf) (\ + NR(buf, 14) <= 99 && \ + NR(buf, 12) <= 12 && \ + NR(buf, 10) <= 31 && \ + NR(buf, 6) <= 23 && \ + NR(buf, 4) <= 59 && \ + NR(buf, 2) <= 59) + +#define PCFCLOCK_BATTERY_STATUS_LOW(buf) (buf[8] & 4) + +#define PCFCLOCK_CMD_TIME 0 /* send current time */ +#define PCFCLOCK_CMD_COPY 7 /* copy received signal to PC */ + +static void +pcfclock_identify(driver_t *driver, device_t parent) +{ + + BUS_ADD_CHILD(parent, 0, PCFCLOCK_NAME, -1); +} + +static int +pcfclock_probe(device_t dev) +{ + struct pcfclock_data *sc; + + device_set_desc(dev, "PCF-1.0"); + + sc = DEVTOSOFTC(dev); + bzero(sc, sizeof(struct pcfclock_data)); + + return (0); +} + +static int +pcfclock_attach(device_t dev) +{ + int unit; + + unit = device_get_unit(dev); + + make_dev(&pcfclock_cdevsw, unit, + UID_ROOT, GID_WHEEL, 0444, PCFCLOCK_NAME "%d", unit); + + return (0); +} + +static int +pcfclock_open(dev_t dev, int flag, int fms, struct thread *td) +{ + u_int unit = minor(dev); + struct pcfclock_data *sc = UNITOSOFTC(unit); + device_t pcfclockdev = UNITODEVICE(unit); + device_t ppbus = device_get_parent(pcfclockdev); + int res; + + if (!sc) + return (ENXIO); + + if ((res = ppb_request_bus(ppbus, pcfclockdev, + (flag & O_NONBLOCK) ? PPB_DONTWAIT : PPB_WAIT))) + return (res); + + sc->count++; + + return (0); +} + +static int +pcfclock_close(dev_t dev, int flags, int fmt, struct thread *td) +{ + u_int unit = minor(dev); + struct pcfclock_data *sc = UNITOSOFTC(unit); + device_t pcfclockdev = UNITODEVICE(unit); + device_t ppbus = device_get_parent(pcfclockdev); + + sc->count--; + if (sc->count == 0) + ppb_release_bus(ppbus, pcfclockdev); + + return (0); +} + +static void +pcfclock_write_cmd(dev_t dev, unsigned char command) +{ + u_int unit = minor(dev); + device_t ppidev = UNITODEVICE(unit); + device_t ppbus = device_get_parent(ppidev); + unsigned char ctr = 14; + char i; + + for (i = 0; i <= 7; i++) { + ppb_wdtr(ppbus, i); + AUTOFEED_CLOCK(i & 1 ? AFC_HI : AFC_LO); + DELAY(3000); + } + ppb_wdtr(ppbus, command); + AUTOFEED_CLOCK(AFC_LO); + DELAY(3000); + AUTOFEED_CLOCK(AFC_HI); +} + +static void +pcfclock_display_data(dev_t dev, char buf[18]) +{ + u_int unit = minor(dev); +#ifdef PCFCLOCK_VERBOSE + int year; + + year = NR(buf, 14); + if (year < 70) + year += 100; + + printf(PCFCLOCK_NAME "%d: %02d.%02d.%4d %02d:%02d:%02d, " + "battery status: %s\n", + unit, + NR(buf, 10), NR(buf, 12), 1900 + year, + NR(buf, 6), NR(buf, 4), NR(buf, 2), + PCFCLOCK_BATTERY_STATUS_LOW(buf) ? "LOW" : "ok"); +#else + if (PCFCLOCK_BATTERY_STATUS_LOW(buf)) + printf(PCFCLOCK_NAME "%d: BATTERY STATUS LOW ON\n", + unit); +#endif +} + +static int +pcfclock_read_data(dev_t dev, char *buf, ssize_t bits) +{ + u_int unit = minor(dev); + device_t ppidev = UNITODEVICE(unit); + device_t ppbus = device_get_parent(ppidev); + int i; + char waitfor; + int offset; + + /* one byte per four bits */ + bzero(buf, ((bits + 3) >> 2) + 1); + + waitfor = 100; + for (i = 0; i <= bits; i++) { + /* wait for clock, maximum (waitfor*100) usec */ + while(!CLOCK_OK && --waitfor > 0) + DELAY(100); + + /* timed out? */ + if (!waitfor) + return (EIO); + + waitfor = 100; /* reload */ + + /* give it some time */ + DELAY(500); + + /* calculate offset into buffer */ + offset = i >> 2; + buf[offset] <<= 1; + + if (BIT_SET) + buf[offset] |= 1; + } + + return (0); +} + +static int +pcfclock_read_dev(dev_t dev, char *buf, int maxretries) +{ + u_int unit = minor(dev); + device_t ppidev = UNITODEVICE(unit); + device_t ppbus = device_get_parent(ppidev); + int error = 0; + + ppb_set_mode(ppbus, PPB_COMPATIBLE); + + while (--maxretries > 0) { + pcfclock_write_cmd(dev, PCFCLOCK_CMD_TIME); + if (pcfclock_read_data(dev, buf, 68)) + continue; + + if (!PCFCLOCK_CORRECT_SYNC(buf)) + continue; + + if (!PCFCLOCK_CORRECT_FORMAT(buf)) + continue; + + break; + } + + if (!maxretries) + error = EIO; + + return (error); +} + +static int +pcfclock_read(dev_t dev, struct uio *uio, int ioflag) +{ + u_int unit = minor(dev); + char buf[18]; + int error = 0; + + if (uio->uio_resid < 18) + return (ERANGE); + + error = pcfclock_read_dev(dev, buf, PCFCLOCK_MAX_RETRIES); + + if (error) { + printf(PCFCLOCK_NAME "%d: no PCF found\n", unit); + } else { + pcfclock_display_data(dev, buf); + + uiomove(buf, 18, uio); + } + + return (error); +} + +static device_method_t pcfclock_methods[] = { + /* device interface */ + DEVMETHOD(device_identify, pcfclock_identify), + DEVMETHOD(device_probe, pcfclock_probe), + DEVMETHOD(device_attach, pcfclock_attach), + + { 0, 0 } +}; + +static driver_t pcfclock_driver = { + PCFCLOCK_NAME, + pcfclock_methods, + sizeof(struct pcfclock_data), +}; + +DRIVER_MODULE(pcfclock, ppbus, pcfclock_driver, pcfclock_devclass, 0, 0); diff --git a/sys/dev/ppbus/ppb_1284.c b/sys/dev/ppbus/ppb_1284.c new file mode 100644 index 0000000..3fdd11b --- /dev/null +++ b/sys/dev/ppbus/ppb_1284.c @@ -0,0 +1,857 @@ +/*- + * 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. + * + * $FreeBSD$ + * + */ + +/* + * General purpose routines for the IEEE1284-1994 Standard + */ + +#include "opt_ppb_1284.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> + + +#include <dev/ppbus/ppbconf.h> +#include <dev/ppbus/ppb_1284.h> + +#include "ppbus_if.h" + +#include <dev/ppbus/ppbio.h> + +#define DEVTOSOFTC(dev) ((struct ppb_data *)device_get_softc(dev)) + +/* + * do_1284_wait() + * + * Wait for the peripherial up to 40ms + */ +static int +do_1284_wait(device_t bus, char mask, char status) +{ + return (ppb_poll_bus(bus, 4, mask, status, PPB_NOINTR | PPB_POLL)); +} + +static int +do_peripheral_wait(device_t bus, char mask, char status) +{ + return (ppb_poll_bus(bus, 100, mask, status, PPB_NOINTR | PPB_POLL)); +} + +#define nibble2char(s) (((s & ~nACK) >> 3) | (~s & nBUSY) >> 4) + +/* + * ppb_1284_reset_error() + * + * Unconditionaly reset the error field + */ +static int +ppb_1284_reset_error(device_t bus, int state) +{ + struct ppb_data *ppb = DEVTOSOFTC(bus); + + ppb->error = PPB_NO_ERROR; + ppb->state = state; + + return (0); +} + +/* + * ppb_1284_get_state() + * + * Get IEEE1284 state + */ +int +ppb_1284_get_state(device_t bus) +{ + return (DEVTOSOFTC(bus)->state); +} + +/* + * ppb_1284_set_state() + * + * Change IEEE1284 state if no error occured + */ +int +ppb_1284_set_state(device_t bus, int state) +{ + struct ppb_data *ppb = DEVTOSOFTC(bus); + + /* call ppb_1284_reset_error() if you absolutly want to change + * the state from PPB_ERROR to another */ + if ((ppb->state != PPB_ERROR) && + (ppb->error == PPB_NO_ERROR)) { + ppb->state = state; + ppb->error = PPB_NO_ERROR; + } + + return (0); +} + +static int +ppb_1284_set_error(device_t bus, int error, int event) +{ + struct ppb_data *ppb = DEVTOSOFTC(bus); + + /* do not accumulate errors */ + if ((ppb->error == PPB_NO_ERROR) && + (ppb->state != PPB_ERROR)) { + ppb->error = error; + ppb->state = PPB_ERROR; + } + +#ifdef DEBUG_1284 + printf("ppb1284: error=%d status=0x%x event=%d\n", error, + ppb_rstr(bus) & 0xff, event); +#endif + + return (0); +} + +/* + * ppb_request_mode() + * + * Converts mode+options into ext. value + */ +static int +ppb_request_mode(int mode, int options) +{ + int request_mode = 0; + + if (options & PPB_EXTENSIBILITY_LINK) { + request_mode = EXT_LINK_1284_NORMAL; + + } else { + switch (mode) { + case PPB_NIBBLE: + request_mode = (options & PPB_REQUEST_ID) ? + NIBBLE_1284_REQUEST_ID : + NIBBLE_1284_NORMAL; + break; + case PPB_PS2: + request_mode = (options & PPB_REQUEST_ID) ? + BYTE_1284_REQUEST_ID : + BYTE_1284_NORMAL; + break; + case PPB_ECP: + if (options & PPB_USE_RLE) + request_mode = (options & PPB_REQUEST_ID) ? + ECP_1284_RLE_REQUEST_ID : + ECP_1284_RLE; + else + request_mode = (options & PPB_REQUEST_ID) ? + ECP_1284_REQUEST_ID : + ECP_1284_NORMAL; + break; + case PPB_EPP: + request_mode = EPP_1284_NORMAL; + break; + default: + panic("%s: unsupported mode %d\n", __func__, mode); + } + } + + return (request_mode); +} + +/* + * ppb_peripheral_negociate() + * + * Negociate the peripheral side + */ +int +ppb_peripheral_negociate(device_t bus, int mode, int options) +{ + int spin, request_mode, error = 0; + char r; + + ppb_set_mode(bus, PPB_COMPATIBLE); + ppb_1284_set_state(bus, PPB_PERIPHERAL_NEGOCIATION); + + /* compute ext. value */ + request_mode = ppb_request_mode(mode, options); + + /* wait host */ + spin = 10; + while (spin-- && (ppb_rstr(bus) & nBUSY)) + DELAY(1); + + /* check termination */ + if (!(ppb_rstr(bus) & SELECT) || !spin) { + error = ENODEV; + goto error; + } + + /* Event 4 - read ext. value */ + r = ppb_rdtr(bus); + + /* nibble mode is not supported */ + if ((r == (char)request_mode) || + (r == NIBBLE_1284_NORMAL)) { + + /* Event 5 - restore direction bit, no data avail */ + ppb_wctr(bus, (STROBE | nINIT) & ~(SELECTIN)); + DELAY(1); + + /* Event 6 */ + ppb_wctr(bus, (nINIT) & ~(SELECTIN | STROBE)); + + if (r == NIBBLE_1284_NORMAL) { +#ifdef DEBUG_1284 + printf("R"); +#endif + ppb_1284_set_error(bus, PPB_MODE_UNSUPPORTED, 4); + error = EINVAL; + goto error; + } else { + ppb_1284_set_state(bus, PPB_PERIPHERAL_IDLE); + switch (r) { + case BYTE_1284_NORMAL: + ppb_set_mode(bus, PPB_BYTE); + break; + default: + break; + } +#ifdef DEBUG_1284 + printf("A"); +#endif + /* negociation succeeds */ + } + } else { + /* Event 5 - mode not supported */ + ppb_wctr(bus, SELECTIN); + DELAY(1); + + /* Event 6 */ + ppb_wctr(bus, (SELECTIN) & ~(STROBE | nINIT)); + ppb_1284_set_error(bus, PPB_MODE_UNSUPPORTED, 4); + +#ifdef DEBUG_1284 + printf("r"); +#endif + error = EINVAL; + goto error; + } + + return (0); + +error: + ppb_peripheral_terminate(bus, PPB_WAIT); + return (error); +} + +/* + * ppb_peripheral_terminate() + * + * Terminate peripheral transfer side + * + * Always return 0 in compatible mode + */ +int +ppb_peripheral_terminate(device_t bus, int how) +{ + int error = 0; + +#ifdef DEBUG_1284 + printf("t"); +#endif + + ppb_1284_set_state(bus, PPB_PERIPHERAL_TERMINATION); + + /* Event 22 - wait up to host response time (1s) */ + if ((error = do_peripheral_wait(bus, SELECT | nBUSY, 0))) { + ppb_1284_set_error(bus, PPB_TIMEOUT, 22); + goto error; + } + + /* Event 24 */ + ppb_wctr(bus, (nINIT | STROBE) & ~(AUTOFEED | SELECTIN)); + + /* Event 25 - wait up to host response time (1s) */ + if ((error = do_peripheral_wait(bus, nBUSY, nBUSY))) { + ppb_1284_set_error(bus, PPB_TIMEOUT, 25); + goto error; + } + + /* Event 26 */ + ppb_wctr(bus, (SELECTIN | nINIT | STROBE) & ~(AUTOFEED)); + DELAY(1); + /* Event 27 */ + ppb_wctr(bus, (SELECTIN | nINIT) & ~(STROBE | AUTOFEED)); + + /* Event 28 - wait up to host response time (1s) */ + if ((error = do_peripheral_wait(bus, nBUSY, 0))) { + ppb_1284_set_error(bus, PPB_TIMEOUT, 28); + goto error; + } + +error: + ppb_set_mode(bus, PPB_COMPATIBLE); + ppb_1284_set_state(bus, PPB_FORWARD_IDLE); + + return (0); +} + +/* + * byte_peripheral_outbyte() + * + * Write 1 byte in BYTE mode + */ +static int +byte_peripheral_outbyte(device_t bus, char *buffer, int last) +{ + int error = 0; + + /* Event 7 */ + if ((error = do_1284_wait(bus, nBUSY, nBUSY))) { + ppb_1284_set_error(bus, PPB_TIMEOUT, 7); + goto error; + } + + /* check termination */ + if (!(ppb_rstr(bus) & SELECT)) { + ppb_peripheral_terminate(bus, PPB_WAIT); + goto error; + } + + /* Event 15 - put byte on data lines */ +#ifdef DEBUG_1284 + printf("B"); +#endif + ppb_wdtr(bus, *buffer); + + /* Event 9 */ + ppb_wctr(bus, (AUTOFEED | STROBE) & ~(nINIT | SELECTIN)); + + /* Event 10 - wait data read */ + if ((error = do_peripheral_wait(bus, nBUSY, 0))) { + ppb_1284_set_error(bus, PPB_TIMEOUT, 16); + goto error; + } + + /* Event 11 */ + if (!last) { + ppb_wctr(bus, (AUTOFEED) & ~(nINIT | STROBE | SELECTIN)); + } else { + ppb_wctr(bus, (nINIT) & ~(STROBE | SELECTIN | AUTOFEED)); + } + +#if 0 + /* Event 16 - wait strobe */ + if ((error = do_peripheral_wait(bus, nACK | nBUSY, 0))) { + ppb_1284_set_error(bus, PPB_TIMEOUT, 16); + goto error; + } +#endif + + /* check termination */ + if (!(ppb_rstr(bus) & SELECT)) { + ppb_peripheral_terminate(bus, PPB_WAIT); + goto error; + } + +error: + return (error); +} + +/* + * byte_peripheral_write() + * + * Write n bytes in BYTE mode + */ +int +byte_peripheral_write(device_t bus, char *buffer, int len, int *sent) +{ + int error = 0, i; + char r; + + ppb_1284_set_state(bus, PPB_PERIPHERAL_TRANSFER); + + /* wait forever, the remote host is master and should initiate + * termination + */ + for (i=0; i<len; i++) { + /* force remote nFAULT low to release the remote waiting + * process, if any + */ + r = ppb_rctr(bus); + ppb_wctr(bus, r & ~nINIT); + +#ifdef DEBUG_1284 + printf("y"); +#endif + /* Event 7 */ + error = ppb_poll_bus(bus, PPB_FOREVER, nBUSY, nBUSY, + PPB_INTR); + + if (error && error != EWOULDBLOCK) + goto error; + +#ifdef DEBUG_1284 + printf("b"); +#endif + if ((error = byte_peripheral_outbyte(bus, buffer+i, (i == len-1)))) + goto error; + } +error: + if (!error) + ppb_1284_set_state(bus, PPB_PERIPHERAL_IDLE); + + *sent = i; + return (error); +} + +/* + * byte_1284_inbyte() + * + * Read 1 byte in BYTE mode + */ +int +byte_1284_inbyte(device_t bus, char *buffer) +{ + int error = 0; + + /* Event 7 - ready to take data (nAUTO low) */ + ppb_wctr(bus, (PCD | nINIT | AUTOFEED) & ~(STROBE | SELECTIN)); + + /* Event 9 - peripheral set nAck low */ + if ((error = do_1284_wait(bus, nACK, 0))) { + ppb_1284_set_error(bus, PPB_TIMEOUT, 9); + goto error; + } + + /* read the byte */ + *buffer = ppb_rdtr(bus); + + /* Event 10 - data received, can't accept more */ + ppb_wctr(bus, (nINIT) & ~(AUTOFEED | STROBE | SELECTIN)); + + /* Event 11 - peripheral ack */ + if ((error = do_1284_wait(bus, nACK, nACK))) { + ppb_1284_set_error(bus, PPB_TIMEOUT, 11); + goto error; + } + + /* Event 16 - strobe */ + ppb_wctr(bus, (nINIT | STROBE) & ~(AUTOFEED | SELECTIN)); + DELAY(3); + ppb_wctr(bus, (nINIT) & ~(AUTOFEED | STROBE | SELECTIN)); + +error: + return (error); +} + +/* + * nibble_1284_inbyte() + * + * Read 1 byte in NIBBLE mode + */ +int +nibble_1284_inbyte(device_t bus, char *buffer) +{ + char nibble[2]; + int i, error; + + for (i = 0; i < 2; i++) { + + /* Event 7 - ready to take data (nAUTO low) */ + ppb_wctr(bus, (nINIT | AUTOFEED) & ~(STROBE | SELECTIN)); + + /* Event 8 - peripheral writes the first nibble */ + + /* Event 9 - peripheral set nAck low */ + if ((error = do_1284_wait(bus, nACK, 0))) { + ppb_1284_set_error(bus, PPB_TIMEOUT, 9); + goto error; + } + + /* read nibble */ + nibble[i] = ppb_rstr(bus); + + /* Event 10 - ack, nibble received */ + ppb_wctr(bus, nINIT & ~(AUTOFEED | STROBE | SELECTIN)); + + /* Event 11 - wait ack from peripherial */ + if ((error = do_1284_wait(bus, nACK, nACK))) { + ppb_1284_set_error(bus, PPB_TIMEOUT, 11); + goto error; + } + } + + *buffer = ((nibble2char(nibble[1]) << 4) & 0xf0) | + (nibble2char(nibble[0]) & 0x0f); + +error: + return (error); +} + +/* + * spp_1284_read() + * + * Read in IEEE1284 NIBBLE/BYTE mode + */ +int +spp_1284_read(device_t bus, int mode, char *buffer, int max, int *read) +{ + int error = 0, len = 0; + int terminate_after_transfer = 1; + int state; + + *read = len = 0; + + state = ppb_1284_get_state(bus); + + switch (state) { + case PPB_FORWARD_IDLE: + if ((error = ppb_1284_negociate(bus, mode, 0))) + return (error); + break; + + case PPB_REVERSE_IDLE: + terminate_after_transfer = 0; + break; + + default: + ppb_1284_terminate(bus); + if ((error = ppb_1284_negociate(bus, mode, 0))) + return (error); + break; + } + + while ((len < max) && !(ppb_rstr(bus) & (nFAULT))) { + + ppb_1284_set_state(bus, PPB_REVERSE_TRANSFER); + +#ifdef DEBUG_1284 + printf("B"); +#endif + + switch (mode) { + case PPB_NIBBLE: + /* read a byte, error means no more data */ + if (nibble_1284_inbyte(bus, buffer+len)) + goto end_while; + break; + case PPB_BYTE: + if (byte_1284_inbyte(bus, buffer+len)) + goto end_while; + break; + default: + error = EINVAL; + goto end_while; + } + len ++; + } +end_while: + + if (!error) + ppb_1284_set_state(bus, PPB_REVERSE_IDLE); + + *read = len; + + if (terminate_after_transfer || error) + ppb_1284_terminate(bus); + + return (error); +} + +/* + * ppb_1284_read_id() + * + */ +int +ppb_1284_read_id(device_t bus, int mode, char *buffer, + int max, int *read) +{ + int error = 0; + + /* fill the buffer with 0s */ + bzero(buffer, max); + + switch (mode) { + case PPB_NIBBLE: + case PPB_ECP: + if ((error = ppb_1284_negociate(bus, PPB_NIBBLE, PPB_REQUEST_ID))) + return (error); + error = spp_1284_read(bus, PPB_NIBBLE, buffer, max, read); + break; + case PPB_BYTE: + if ((error = ppb_1284_negociate(bus, PPB_BYTE, PPB_REQUEST_ID))) + return (error); + error = spp_1284_read(bus, PPB_BYTE, buffer, max, read); + break; + default: + panic("%s: unsupported mode %d\n", __func__, mode); + } + + ppb_1284_terminate(bus); + return (error); +} + +/* + * ppb_1284_read() + * + * IEEE1284 read + */ +int +ppb_1284_read(device_t bus, int mode, char *buffer, + int max, int *read) +{ + int error = 0; + + switch (mode) { + case PPB_NIBBLE: + case PPB_BYTE: + error = spp_1284_read(bus, mode, buffer, max, read); + break; + default: + return (EINVAL); + } + + return (error); +} + +/* + * ppb_1284_negociate() + * + * IEEE1284 negociation phase + * + * Normal nibble mode or request device id mode (see ppb_1284.h) + * + * After negociation, nFAULT is low if data is available + */ +int +ppb_1284_negociate(device_t bus, int mode, int options) +{ + int error; + int request_mode; + +#ifdef DEBUG_1284 + printf("n"); +#endif + + if (ppb_1284_get_state(bus) >= PPB_PERIPHERAL_NEGOCIATION) + ppb_peripheral_terminate(bus, PPB_WAIT); + + if (ppb_1284_get_state(bus) != PPB_FORWARD_IDLE) + ppb_1284_terminate(bus); + +#ifdef DEBUG_1284 + printf("%d", mode); +#endif + + /* ensure the host is in compatible mode */ + ppb_set_mode(bus, PPB_COMPATIBLE); + + /* reset error to catch the actual negociation error */ + ppb_1284_reset_error(bus, PPB_FORWARD_IDLE); + + /* calculate ext. value */ + request_mode = ppb_request_mode(mode, options); + + /* default state */ + ppb_wctr(bus, (nINIT | SELECTIN) & ~(STROBE | AUTOFEED)); + DELAY(1); + + /* enter negociation phase */ + ppb_1284_set_state(bus, PPB_NEGOCIATION); + + /* Event 0 - put the exten. value on the data lines */ + ppb_wdtr(bus, request_mode); + +#ifdef PERIPH_1284 + /* request remote host attention */ + ppb_wctr(bus, (nINIT | STROBE) & ~(AUTOFEED | SELECTIN)); + DELAY(1); + ppb_wctr(bus, (nINIT) & ~(STROBE | AUTOFEED | SELECTIN)); +#else + DELAY(1); + +#endif /* !PERIPH_1284 */ + + /* Event 1 - enter IEEE1284 mode */ + ppb_wctr(bus, (nINIT | AUTOFEED) & ~(STROBE | SELECTIN)); + +#ifdef PERIPH_1284 + /* ignore the PError line, wait a bit more, remote host's + * interrupts don't respond fast enough */ + if (ppb_poll_bus(bus, 40, nACK | SELECT | nFAULT, + SELECT | nFAULT, PPB_NOINTR | PPB_POLL)) { + ppb_1284_set_error(bus, PPB_NOT_IEEE1284, 2); + error = ENODEV; + goto error; + } +#else + /* Event 2 - trying IEEE1284 dialog */ + if (do_1284_wait(bus, nACK | PERROR | SELECT | nFAULT, + PERROR | SELECT | nFAULT)) { + ppb_1284_set_error(bus, PPB_NOT_IEEE1284, 2); + error = ENODEV; + goto error; + } +#endif /* !PERIPH_1284 */ + + /* Event 3 - latch the ext. value to the peripheral */ + ppb_wctr(bus, (nINIT | STROBE | AUTOFEED) & ~SELECTIN); + DELAY(1); + + /* Event 4 - IEEE1284 device recognized */ + ppb_wctr(bus, nINIT & ~(SELECTIN | AUTOFEED | STROBE)); + + /* Event 6 - waiting for status lines */ + if (do_1284_wait(bus, nACK, nACK)) { + ppb_1284_set_error(bus, PPB_TIMEOUT, 6); + error = EBUSY; + goto error; + } + + /* Event 7 - quering result consider nACK not to misunderstand + * a remote computer terminate sequence */ + if (options & PPB_EXTENSIBILITY_LINK) { + + /* XXX not fully supported yet */ + ppb_1284_terminate(bus); + return (0); + + } + if (request_mode == NIBBLE_1284_NORMAL) { + if (do_1284_wait(bus, nACK | SELECT, nACK)) { + ppb_1284_set_error(bus, PPB_MODE_UNSUPPORTED, 7); + error = ENODEV; + goto error; + } + } else { + if (do_1284_wait(bus, nACK | SELECT, SELECT | nACK)) { + ppb_1284_set_error(bus, PPB_MODE_UNSUPPORTED, 7); + error = ENODEV; + goto error; + } + } + + switch (mode) { + case PPB_NIBBLE: + case PPB_PS2: + /* enter reverse idle phase */ + ppb_1284_set_state(bus, PPB_REVERSE_IDLE); + break; + case PPB_ECP: + /* negociation ok, now setup the communication */ + ppb_1284_set_state(bus, PPB_SETUP); + ppb_wctr(bus, (nINIT | AUTOFEED) & ~(SELECTIN | STROBE)); + +#ifdef PERIPH_1284 + /* ignore PError line */ + if (do_1284_wait(bus, nACK | SELECT | nBUSY, + nACK | SELECT | nBUSY)) { + ppb_1284_set_error(bus, PPB_TIMEOUT, 30); + error = ENODEV; + goto error; + } +#else + if (do_1284_wait(bus, nACK | SELECT | PERROR | nBUSY, + nACK | SELECT | PERROR | nBUSY)) { + ppb_1284_set_error(bus, PPB_TIMEOUT, 30); + error = ENODEV; + goto error; + } +#endif /* !PERIPH_1284 */ + + /* ok, the host enters the ForwardIdle state */ + ppb_1284_set_state(bus, PPB_ECP_FORWARD_IDLE); + break; + case PPB_EPP: + ppb_1284_set_state(bus, PPB_EPP_IDLE); + break; + + default: + panic("%s: unknown mode (%d)!", __func__, mode); + } + ppb_set_mode(bus, mode); + + return (0); + +error: + ppb_1284_terminate(bus); + + return (error); +} + +/* + * ppb_1284_terminate() + * + * IEEE1284 termination phase, return code should ignored since the host + * is _always_ in compatible mode after ppb_1284_terminate() + */ +int +ppb_1284_terminate(device_t bus) +{ + +#ifdef DEBUG_1284 + printf("T"); +#endif + + /* do not reset error here to keep the error that + * may occured before the ppb_1284_terminate() call */ + ppb_1284_set_state(bus, PPB_TERMINATION); + +#ifdef PERIPH_1284 + /* request remote host attention */ + ppb_wctr(bus, (nINIT | STROBE | SELECTIN) & ~(AUTOFEED)); + DELAY(1); +#endif /* PERIPH_1284 */ + + /* Event 22 - set nSelectin low and nAutoFeed high */ + ppb_wctr(bus, (nINIT | SELECTIN) & ~(STROBE | AUTOFEED)); + + /* Event 24 - waiting for peripheral, Xflag ignored */ + if (do_1284_wait(bus, nACK | nBUSY | nFAULT, nFAULT)) { + ppb_1284_set_error(bus, PPB_TIMEOUT, 24); + goto error; + } + + /* Event 25 - set nAutoFd low */ + ppb_wctr(bus, (nINIT | SELECTIN | AUTOFEED) & ~STROBE); + + /* Event 26 - compatible mode status is set */ + + /* Event 27 - peripheral set nAck high */ + if (do_1284_wait(bus, nACK, nACK)) { + ppb_1284_set_error(bus, PPB_TIMEOUT, 27); + } + + /* Event 28 - end termination, return to idle phase */ + ppb_wctr(bus, (nINIT | SELECTIN) & ~(STROBE | AUTOFEED)); + +error: + /* return to compatible mode */ + ppb_set_mode(bus, PPB_COMPATIBLE); + ppb_1284_set_state(bus, PPB_FORWARD_IDLE); + + return (0); +} diff --git a/sys/dev/ppbus/ppb_1284.h b/sys/dev/ppbus/ppb_1284.h new file mode 100644 index 0000000..5c4010f --- /dev/null +++ b/sys/dev/ppbus/ppb_1284.h @@ -0,0 +1,126 @@ +/*- + * 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. + * + * $FreeBSD$ + * + */ +#ifndef __1284_H +#define __1284_H + +/* + * IEEE1284 signals + */ + +/* host driven signals */ + +#define nHostClk STROBE +#define Write STROBE + +#define nHostBusy AUTOFEED +#define nHostAck AUTOFEED +#define DStrb AUTOFEED + +#define nReveseRequest nINIT + +#define nActive1284 SELECTIN +#define AStrb SELECTIN + +/* peripheral driven signals */ + +#define nDataAvail nFAULT +#define nPeriphRequest nFAULT + +#define Xflag SELECT + +#define AckDataReq PERROR +#define nAckReverse PERROR + +#define nPtrBusy nBUSY +#define nPeriphAck nBUSY +#define Wait nBUSY + +#define PtrClk nACK +#define PeriphClk nACK +#define Intr nACK + +/* request mode values */ +#define NIBBLE_1284_NORMAL 0x0 +#define NIBBLE_1284_REQUEST_ID 0x4 +#define BYTE_1284_NORMAL 0x1 +#define BYTE_1284_REQUEST_ID 0x5 +#define ECP_1284_NORMAL 0x10 +#define ECP_1284_REQUEST_ID 0x14 +#define ECP_1284_RLE 0x30 +#define ECP_1284_RLE_REQUEST_ID 0x34 +#define EPP_1284_NORMAL 0x40 +#define EXT_LINK_1284_NORMAL 0x80 + +/* ieee1284 mode options */ +#define PPB_REQUEST_ID 0x1 +#define PPB_USE_RLE 0x2 +#define PPB_EXTENSIBILITY_LINK 0x4 + +/* ieee1284 errors */ +#define PPB_NO_ERROR 0 +#define PPB_MODE_UNSUPPORTED 1 /* mode not supported by peripheral */ +#define PPB_NOT_IEEE1284 2 /* not an IEEE1284 compliant periph. */ +#define PPB_TIMEOUT 3 /* timeout */ +#define PPB_INVALID_MODE 4 /* current mode is incorrect */ + +/* ieee1284 host side states */ +#define PPB_ERROR 0 +#define PPB_FORWARD_IDLE 1 +#define PPB_NEGOCIATION 2 +#define PPB_SETUP 3 +#define PPB_ECP_FORWARD_IDLE 4 +#define PPB_FWD_TO_REVERSE 5 +#define PPB_REVERSE_IDLE 6 +#define PPB_REVERSE_TRANSFER 7 +#define PPB_REVERSE_TO_FWD 8 +#define PPB_EPP_IDLE 9 +#define PPB_TERMINATION 10 + +/* peripheral side states */ +#define PPB_PERIPHERAL_NEGOCIATION 11 +#define PPB_PERIPHERAL_IDLE 12 +#define PPB_PERIPHERAL_TRANSFER 13 +#define PPB_PERIPHERAL_TERMINATION 14 + +extern int nibble_1284_inbyte(device_t, char *); +extern int byte_1284_inbyte(device_t, char *); +extern int spp_1284_read(device_t, int, char *, int, int *); + +extern int ppb_1284_negociate(device_t, int, int); +extern int ppb_1284_terminate(device_t); +extern int ppb_1284_read_id(device_t, int, char *, int, int *); +extern int ppb_1284_read(device_t, int, char *, int, int *); +extern int ppb_1284_get_state(device_t bus); +extern int ppb_1284_set_state(device_t bus, int state); + +extern int ppb_peripheral_terminate(device_t, int); +extern int ppb_peripheral_negociate(device_t, int, int); +extern int byte_peripheral_write(device_t, char *, int, int *); + +#endif diff --git a/sys/dev/ppbus/ppb_base.c b/sys/dev/ppbus/ppb_base.c new file mode 100644 index 0000000..d74216c --- /dev/null +++ b/sys/dev/ppbus/ppb_base.c @@ -0,0 +1,195 @@ +/*- + * Copyright (c) 1997, 1998, 1999 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. + * + * $FreeBSD$ + * + */ +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/bus.h> + + +#include <dev/ppbus/ppbconf.h> + +#include "ppbus_if.h" + +#include <dev/ppbus/ppbio.h> + +#define DEVTOSOFTC(dev) ((struct ppb_data *)device_get_softc(dev)) + +/* + * ppb_poll_bus() + * + * Polls the bus + * + * max is a delay in 10-milliseconds + */ +int +ppb_poll_bus(device_t bus, int max, + char mask, char status, int how) +{ + int i, j, error; + char r; + + /* try at least up to 10ms */ + for (j = 0; j < ((how & PPB_POLL) ? max : 1); j++) { + for (i = 0; i < 10000; i++) { + r = ppb_rstr(bus); + DELAY(1); + if ((r & mask) == status) + return (0); + } + } + + if (!(how & PPB_POLL)) { + for (i = 0; max == PPB_FOREVER || i < max-1; i++) { + if ((ppb_rstr(bus) & mask) == status) + return (0); + + switch (how) { + case PPB_NOINTR: + /* wait 10 ms */ + tsleep((caddr_t)bus, PPBPRI, "ppbpoll", hz/100); + break; + + case PPB_INTR: + default: + /* wait 10 ms */ + if (((error = tsleep((caddr_t)bus, PPBPRI | PCATCH, + "ppbpoll", hz/100)) != EWOULDBLOCK) != 0) { + return (error); + } + break; + } + } + } + + return (EWOULDBLOCK); +} + +/* + * ppb_get_epp_protocol() + * + * Return the chipset EPP protocol + */ +int +ppb_get_epp_protocol(device_t bus) +{ + uintptr_t protocol; + + BUS_READ_IVAR(device_get_parent(bus), bus, PPC_IVAR_EPP_PROTO, &protocol); + + return (protocol); +} + +/* + * ppb_get_mode() + * + */ +int +ppb_get_mode(device_t bus) +{ + struct ppb_data *ppb = DEVTOSOFTC(bus); + + /* XXX yet device mode = ppbus mode = chipset mode */ + return (ppb->mode); +} + +/* + * ppb_set_mode() + * + * Set the operating mode of the chipset, return the previous mode + */ +int +ppb_set_mode(device_t bus, int mode) +{ + struct ppb_data *ppb = DEVTOSOFTC(bus); + int old_mode = ppb_get_mode(bus); + + if (PPBUS_SETMODE(device_get_parent(bus), mode)) + return -1; + + /* XXX yet device mode = ppbus mode = chipset mode */ + ppb->mode = (mode & PPB_MASK); + + return (old_mode); +} + +/* + * ppb_write() + * + * Write charaters to the port + */ +int +ppb_write(device_t bus, char *buf, int len, int how) +{ + return (PPBUS_WRITE(device_get_parent(bus), buf, len, how)); +} + +/* + * ppb_reset_epp_timeout() + * + * Reset the EPP timeout bit in the status register + */ +int +ppb_reset_epp_timeout(device_t bus) +{ + return(PPBUS_RESET_EPP(device_get_parent(bus))); +} + +/* + * ppb_ecp_sync() + * + * Wait for the ECP FIFO to be empty + */ +int +ppb_ecp_sync(device_t bus) +{ + return (PPBUS_ECP_SYNC(device_get_parent(bus))); +} + +/* + * ppb_get_status() + * + * Read the status register and update the status info + */ +int +ppb_get_status(device_t bus, struct ppb_status *status) +{ + register char r; + + r = status->status = ppb_rstr(bus); + + status->timeout = r & TIMEOUT; + status->error = !(r & nFAULT); + status->select = r & SELECT; + status->paper_end = r & PERROR; + status->ack = !(r & nACK); + status->busy = !(r & nBUSY); + + return (0); +} diff --git a/sys/dev/ppbus/ppb_msq.c b/sys/dev/ppbus/ppb_msq.c new file mode 100644 index 0000000..6ed7ccd --- /dev/null +++ b/sys/dev/ppbus/ppb_msq.c @@ -0,0 +1,337 @@ +/*- + * Copyright (c) 1998, 1999 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. + * + * $FreeBSD$ + * + */ +#include <machine/stdarg.h> + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> + +#include <dev/ppbus/ppbconf.h> +#include <dev/ppbus/ppb_msq.h> + +#include "ppbus_if.h" + +/* msq index (see PPB_MAX_XFER) + * These are device modes + */ +#define COMPAT_MSQ 0x0 +#define NIBBLE_MSQ 0x1 +#define PS2_MSQ 0x2 +#define EPP17_MSQ 0x3 +#define EPP19_MSQ 0x4 +#define ECP_MSQ 0x5 + +/* + * Device mode to submsq conversion + */ +static struct ppb_xfer * +mode2xfer(device_t bus, struct ppb_device *ppbdev, int opcode) +{ + int index, epp; + struct ppb_xfer *table; + + switch (opcode) { + case MS_OP_GET: + table = ppbdev->get_xfer; + break; + + case MS_OP_PUT: + table = ppbdev->put_xfer; + break; + + default: + panic("%s: unknown opcode (%d)", __func__, opcode); + } + + /* retrieve the device operating mode */ + switch (ppb_get_mode(bus)) { + case PPB_COMPATIBLE: + index = COMPAT_MSQ; + break; + case PPB_NIBBLE: + index = NIBBLE_MSQ; + break; + case PPB_PS2: + index = PS2_MSQ; + break; + case PPB_EPP: + switch ((epp = ppb_get_epp_protocol(bus))) { + case EPP_1_7: + index = EPP17_MSQ; + break; + case EPP_1_9: + index = EPP19_MSQ; + break; + default: + panic("%s: unknown EPP protocol (0x%x)!", __func__, + epp); + } + break; + case PPB_ECP: + index = ECP_MSQ; + break; + default: + panic("%s: unknown mode (%d)", __func__, ppbdev->mode); + } + + return (&table[index]); +} + +/* + * ppb_MS_init() + * + * Initialize device dependent submicrosequence of the current mode + * + */ +int +ppb_MS_init(device_t bus, device_t dev, struct ppb_microseq *loop, int opcode) +{ + struct ppb_device *ppbdev = (struct ppb_device *)device_get_ivars(dev); + struct ppb_xfer *xfer = mode2xfer(bus, ppbdev, opcode); + + xfer->loop = loop; + + return (0); +} + +/* + * ppb_MS_exec() + * + * Execute any microsequence opcode - expensive + * + */ +int +ppb_MS_exec(device_t bus, device_t dev, int opcode, union ppb_insarg param1, + union ppb_insarg param2, union ppb_insarg param3, int *ret) +{ + struct ppb_microseq msq[] = { + { MS_UNKNOWN, { { MS_UNKNOWN }, { MS_UNKNOWN }, { MS_UNKNOWN } } }, + MS_RET(0) + }; + + /* initialize the corresponding microseq */ + msq[0].opcode = opcode; + msq[0].arg[0] = param1; + msq[0].arg[1] = param2; + msq[0].arg[2] = param3; + + /* execute the microseq */ + return (ppb_MS_microseq(bus, dev, msq, ret)); +} + +/* + * ppb_MS_loop() + * + * Execute a microseq loop + * + */ +int +ppb_MS_loop(device_t bus, device_t dev, struct ppb_microseq *prolog, + struct ppb_microseq *body, struct ppb_microseq *epilog, + int iter, int *ret) +{ + struct ppb_microseq loop_microseq[] = { + MS_CALL(0), /* execute prolog */ + + MS_SET(MS_UNKNOWN), /* set size of transfer */ + /* loop: */ + MS_CALL(0), /* execute body */ + MS_DBRA(-1 /* loop: */), + + MS_CALL(0), /* execute epilog */ + MS_RET(0) + }; + + /* initialize the structure */ + loop_microseq[0].arg[0].p = (void *)prolog; + loop_microseq[1].arg[0].i = iter; + loop_microseq[2].arg[0].p = (void *)body; + loop_microseq[4].arg[0].p = (void *)epilog; + + /* execute the loop */ + return (ppb_MS_microseq(bus, dev, loop_microseq, ret)); +} + +/* + * ppb_MS_init_msq() + * + * Initialize a microsequence - see macros in ppb_msq.h + * + */ +int +ppb_MS_init_msq(struct ppb_microseq *msq, int nbparam, ...) +{ + int i; + int param, ins, arg, type; + va_list p_list; + + va_start(p_list, nbparam); + + for (i=0; i<nbparam; i++) { + /* retrieve the parameter descriptor */ + param = va_arg(p_list, int); + + ins = MS_INS(param); + arg = MS_ARG(param); + type = MS_TYP(param); + + /* check the instruction position */ + if (arg >= PPB_MS_MAXARGS) + panic("%s: parameter out of range (0x%x)!", + __func__, param); + +#if 0 + printf("%s: param = %d, ins = %d, arg = %d, type = %d\n", + __func__, param, ins, arg, type); +#endif + + /* properly cast the parameter */ + switch (type) { + case MS_TYP_INT: + msq[ins].arg[arg].i = va_arg(p_list, int); + break; + + case MS_TYP_CHA: + msq[ins].arg[arg].i = (int)va_arg(p_list, int); + break; + + case MS_TYP_PTR: + msq[ins].arg[arg].p = va_arg(p_list, void *); + break; + + case MS_TYP_FUN: + msq[ins].arg[arg].f = va_arg(p_list, void *); + break; + + default: + panic("%s: unknown parameter (0x%x)!", __func__, + param); + } + } + + return (0); +} + +/* + * ppb_MS_microseq() + * + * Interprete a microsequence. Some microinstructions are executed at adapter + * level to avoid function call overhead between ppbus and the adapter + */ +int +ppb_MS_microseq(device_t bus, device_t dev, struct ppb_microseq *msq, int *ret) +{ + struct ppb_data *ppb = (struct ppb_data *)device_get_softc(bus); + struct ppb_device *ppbdev = (struct ppb_device *)device_get_ivars(dev); + + struct ppb_microseq *mi; /* current microinstruction */ + int error; + + struct ppb_xfer *xfer; + + /* microsequence executed to initialize the transfer */ + struct ppb_microseq initxfer[] = { + MS_PTR(MS_UNKNOWN), /* set ptr to buffer */ + MS_SET(MS_UNKNOWN), /* set transfer size */ + MS_RET(0) + }; + + if (ppb->ppb_owner != dev) + return (EACCES); + +#define INCR_PC (mi ++) + + mi = msq; + for (;;) { + switch (mi->opcode) { + case MS_OP_PUT: + case MS_OP_GET: + + /* attempt to choose the best mode for the device */ + xfer = mode2xfer(bus, ppbdev, mi->opcode); + + /* figure out if we should use ieee1284 code */ + if (!xfer->loop) { + if (mi->opcode == MS_OP_PUT) { + if ((error = PPBUS_WRITE( + device_get_parent(bus), + (char *)mi->arg[0].p, + mi->arg[1].i, 0))) + goto error; + + INCR_PC; + goto next; + } else + panic("%s: IEEE1284 read not supported", __func__); + } + + /* XXX should use ppb_MS_init_msq() */ + initxfer[0].arg[0].p = mi->arg[0].p; + initxfer[1].arg[0].i = mi->arg[1].i; + + /* initialize transfer */ + ppb_MS_microseq(bus, dev, initxfer, &error); + + if (error) + goto error; + + /* the xfer microsequence should not contain any + * MS_OP_PUT or MS_OP_GET! + */ + ppb_MS_microseq(bus, dev, xfer->loop, &error); + + if (error) + goto error; + + INCR_PC; + break; + + case MS_OP_RET: + if (ret) + *ret = mi->arg[0].i; /* return code */ + return (0); + break; + + default: + /* executing microinstructions at ppc level is + * faster. This is the default if the microinstr + * is unknown here + */ + if ((error = PPBUS_EXEC_MICROSEQ( + device_get_parent(bus), &mi))) + goto error; + break; + } + next: + continue; + } +error: + return (error); +} + diff --git a/sys/dev/ppbus/ppb_msq.h b/sys/dev/ppbus/ppb_msq.h new file mode 100644 index 0000000..1ee55e5 --- /dev/null +++ b/sys/dev/ppbus/ppb_msq.h @@ -0,0 +1,205 @@ +/*- + * Copyright (c) 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. + * + * $FreeBSD$ + * + */ +#ifndef __PPB_MSQ_H +#define __PPB_MSQ_H + +/* + * Basic definitions + */ + +/* microsequence parameter descriptor */ +#define MS_INS_MASK 0x00ff /* mask to retrieve the instruction position < 256 XXX */ +#define MS_ARG_MASK 0x0f00 /* mask to retrieve the argument number */ +#define MS_TYP_MASK 0xf000 /* mask to retrieve the type of the param */ + +/* offset of each mask (see above) */ +#define MS_INS_OFFSET 0 +#define MS_ARG_OFFSET 8 +#define MS_TYP_OFFSET 12 + +/* list of parameter types */ +#define MS_TYP_INT 0x0 /* integer */ +#define MS_TYP_CHA 0x1 /* character */ +#define MS_TYP_PTR 0x2 /* void pointer */ +#define MS_TYP_FUN 0x3 /* function pointer */ + +#define MS_PARAM(ins,arg,typ) \ + (((ins<<MS_INS_OFFSET) & MS_INS_MASK) | \ + ((arg<<MS_ARG_OFFSET) & MS_ARG_MASK) | \ + ((typ<<MS_TYP_OFFSET) & MS_TYP_MASK)) + +#define MS_INS(param) ((param & MS_INS_MASK) >> MS_INS_OFFSET) +#define MS_ARG(param) ((param & MS_ARG_MASK) >> MS_ARG_OFFSET) +#define MS_TYP(param) ((param & MS_TYP_MASK) >> MS_TYP_OFFSET) + +/* microsequence opcodes - do not change! */ +#define MS_OP_GET 0 /* get <ptr>, <len> */ +#define MS_OP_PUT 1 /* put <ptr>, <len> */ + +#define MS_OP_RFETCH 2 /* rfetch <reg>, <mask>, <ptr> */ +#define MS_OP_RSET 3 /* rset <reg>, <mask>, <mask> */ +#define MS_OP_RASSERT 4 /* rassert <reg>, <mask> */ +#define MS_OP_DELAY 5 /* delay <val> */ +#define MS_OP_SET 6 /* set <val> */ +#define MS_OP_DBRA 7 /* dbra <offset> */ +#define MS_OP_BRSET 8 /* brset <mask>, <offset> */ +#define MS_OP_BRCLEAR 9 /* brclear <mask>, <offset> */ +#define MS_OP_RET 10 /* ret <retcode> */ +#define MS_OP_C_CALL 11 /* c_call <function>, <parameter> */ +#define MS_OP_PTR 12 /* ptr <pointer> */ +#define MS_OP_ADELAY 13 /* adelay <val> */ +#define MS_OP_BRSTAT 14 /* brstat <mask>, <mask>, <offset> */ +#define MS_OP_SUBRET 15 /* subret <code> */ +#define MS_OP_CALL 16 /* call <microsequence> */ +#define MS_OP_RASSERT_P 17 /* rassert_p <iter>, <reg> */ +#define MS_OP_RFETCH_P 18 /* rfetch_p <iter>, <reg>, <mask> */ +#define MS_OP_TRIG 19 /* trigger <reg>, <len>, <array> */ + +/* common masks */ +#define MS_CLEAR_ALL 0x0 +#define MS_ASSERT_NONE 0x0 +#define MS_ASSERT_ALL 0xff +#define MS_FETCH_ALL 0xff + +/* undefined parameter value */ +#define MS_NULL 0 +#define MS_UNKNOWN MS_NULL + +/* predifined parameters */ +#define MS_ACCUM -1 /* use accum previously set by MS_OP_SET */ + +/* these are register numbers according to our PC-like parallel port model */ +#define MS_REG_DTR 0x0 +#define MS_REG_STR 0x1 +#define MS_REG_CTR 0x2 +#define MS_REG_EPP_A 0x3 +#define MS_REG_EPP_D 0x4 + +/* + * Microsequence macro abstraction level + */ + +/* register operations */ +#define MS_RSET(reg,assert,clear) { MS_OP_RSET, {{ (reg) }, { (assert) }, { (clear) }}} +#define MS_RASSERT(reg,byte) { MS_OP_RASSERT, { { (reg) }, { (byte) }}} +#define MS_RCLR(reg,clear) { MS_OP_RSET, {{ (reg) }, { MS_ASSERT_NONE }, { (clear) }}} + +#define MS_RFETCH(reg,mask,ptr) { MS_OP_RFETCH, {{ (reg) }, { (mask) }, { (ptr) }}} + +/* trigger the port with array[char, delay,...] */ +#define MS_TRIG(reg,len,array) { MS_OP_TRIG, {{ (reg) }, { (len) }, { (array) }}} + +/* assert/fetch from/to ptr */ +#define MS_RASSERT_P(n,reg) { MS_OP_RASSERT_P, {{ (n) }, { (reg) }}} +#define MS_RFETCH_P(n,reg,mask) { MS_OP_RFETCH_P, {{ (n) }, { (reg) }, { (mask) }}} + +/* ptr manipulation */ +#define MS_PTR(ptr) { MS_OP_PTR, {{ (ptr) }}} + +#define MS_DASS(byte) MS_RASSERT(MS_REG_DTR,byte) +#define MS_SASS(byte) MS_RASSERT(MS_REG_STR,byte) +#define MS_CASS(byte) MS_RASSERT(MS_REG_CTR,byte) + +#define MS_SET(accum) { MS_OP_SET, {{ (accum) }}} +#define MS_BRSET(mask,offset) { MS_OP_BRSET, {{ (mask) }, { (offset) }}} +#define MS_DBRA(offset) { MS_OP_DBRA, {{ (offset) }}} +#define MS_BRCLEAR(mask,offset) { MS_OP_BRCLEAR, {{ (mask) }, { (offset) }}} +#define MS_BRSTAT(mask_set,mask_clr,offset) \ + { MS_OP_BRSTAT, {{ mask_set }, { mask_clr }, { (offset) }}} + +/* C function or submicrosequence call */ +#define MS_C_CALL(function,parameter) \ + { MS_OP_C_CALL, {{ (function) }, { (parameter) }}} +#define MS_CALL(microseq) { MS_OP_CALL, {{ (microseq) }}} + +/* mode dependent read/write operations + * ppb_MS_xxx_init() call required otherwise default is + * IEEE1284 operating mode */ +#define MS_PUT(ptr,len) { MS_OP_PUT, {{ (ptr) }, { (len) }}} +#define MS_GET(ptr,len) { MS_OP_GET, {{ (ptr) }, { (len) }}} + +/* delay in microseconds */ +#define MS_DELAY(udelay) { MS_OP_DELAY, {{ (udelay) }}} + +/* asynchroneous delay in ms */ +#define MS_ADELAY(mdelay) { MS_OP_ADELAY, {{ (mdelay) }}} + +/* return from submicrosequence execution or microseqence execution */ +#define MS_SUBRET(code) { MS_OP_SUBRET, {{ (code) }}} +#define MS_RET(code) { MS_OP_RET, {{ (code) }}} + +/* + * Function abstraction level + */ + +#define ppb_MS_GET_init(bus,dev,body) ppb_MS_init(bus, dev, body, MS_OP_GET) + +#define ppb_MS_PUT_init(bus,dev,body) ppb_MS_init(bus, dev, body, MS_OP_PUT) + +extern int ppb_MS_init( + device_t, /* ppbus bus */ + device_t, /* ppbus device */ + struct ppb_microseq *, /* loop msq to assign */ + int opcode /* MS_OP_GET, MS_OP_PUT */ + ); + +extern int ppb_MS_init_msq( + struct ppb_microseq *, + int, /* number of parameters */ + ... /* descriptor, value, ... */ + ); + +extern int ppb_MS_exec( + device_t, /* ppbus bus */ + device_t, /* ppbus device */ + int, /* microseq opcode */ + union ppb_insarg, /* param1 */ + union ppb_insarg, /* param2 */ + union ppb_insarg, /* param3 */ + int * /* returned value */ + ); + +extern int ppb_MS_loop( + device_t, /* ppbus bus */ + device_t, /* ppbus device */ + struct ppb_microseq *, /* prologue msq of loop */ + struct ppb_microseq *, /* body msq of loop */ + struct ppb_microseq *, /* epilogue msq of loop */ + int, /* number of iter */ + int * /* returned value */ + ); + +extern int ppb_MS_microseq( + device_t, /* ppbus bus */ + device_t, /* ppbus device */ + struct ppb_microseq *, /* msq to execute */ + int * /* returned value */ + ); + +#endif diff --git a/sys/dev/ppbus/ppbconf.c b/sys/dev/ppbus/ppbconf.c new file mode 100644 index 0000000..ca6407f --- /dev/null +++ b/sys/dev/ppbus/ppbconf.c @@ -0,0 +1,558 @@ +/*- + * Copyright (c) 1997, 1998, 1999 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. + * + * $FreeBSD$ + * + */ +#include "opt_ppb_1284.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/malloc.h> + +#include <dev/ppbus/ppbconf.h> +#include <dev/ppbus/ppb_1284.h> + +#include "ppbus_if.h" + +#define DEVTOSOFTC(dev) ((struct ppb_data *)device_get_softc(dev)) + +static MALLOC_DEFINE(M_PPBUSDEV, "ppbusdev", "Parallel Port bus device"); + + +/* + * Device methods + */ + +static void +ppbus_print_child(device_t bus, device_t dev) +{ + struct ppb_device *ppbdev; + + bus_print_child_header(bus, dev); + + ppbdev = (struct ppb_device *)device_get_ivars(dev); + + if (ppbdev->flags != 0) + printf(" flags 0x%x", ppbdev->flags); + + printf(" on %s%d\n", device_get_name(bus), device_get_unit(bus)); + + return; +} + +static int +ppbus_probe(device_t dev) +{ + device_set_desc(dev, "Parallel port bus"); + + return (0); +} + +/* + * ppbus_add_child() + * + * Add a ppbus device, allocate/initialize the ivars + */ +static device_t +ppbus_add_child(device_t dev, int order, const char *name, int unit) +{ + struct ppb_device *ppbdev; + device_t child; + + /* allocate ivars for the new ppbus child */ + ppbdev = malloc(sizeof(struct ppb_device), M_PPBUSDEV, + M_NOWAIT | M_ZERO); + if (!ppbdev) + return NULL; + + /* initialize the ivars */ + ppbdev->name = name; + + /* add the device as a child to the ppbus bus with the allocated + * ivars */ + child = device_add_child_ordered(dev, order, name, unit); + device_set_ivars(child, ppbdev); + + return child; +} + +static int +ppbus_read_ivar(device_t bus, device_t dev, int index, uintptr_t* val) +{ + struct ppb_device *ppbdev = (struct ppb_device *)device_get_ivars(dev); + + switch (index) { + case PPBUS_IVAR_MODE: + /* XXX yet device mode = ppbus mode = chipset mode */ + *val = (u_long)ppb_get_mode(bus); + ppbdev->mode = (u_short)*val; + break; + case PPBUS_IVAR_AVM: + *val = (u_long)ppbdev->avm; + break; + case PPBUS_IVAR_IRQ: + BUS_READ_IVAR(device_get_parent(bus), bus, PPC_IVAR_IRQ, val); + break; + default: + return (ENOENT); + } + + return (0); +} + +static int +ppbus_write_ivar(device_t bus, device_t dev, int index, u_long val) +{ + struct ppb_device *ppbdev = (struct ppb_device *)device_get_ivars(dev); + + switch (index) { + case PPBUS_IVAR_MODE: + /* XXX yet device mode = ppbus mode = chipset mode */ + ppb_set_mode(bus,val); + ppbdev->mode = ppb_get_mode(bus); + break; + default: + return (ENOENT); + } + + return (0); + } + +#define PPB_PNP_PRINTER 0 +#define PPB_PNP_MODEM 1 +#define PPB_PNP_NET 2 +#define PPB_PNP_HDC 3 +#define PPB_PNP_PCMCIA 4 +#define PPB_PNP_MEDIA 5 +#define PPB_PNP_FDC 6 +#define PPB_PNP_PORTS 7 +#define PPB_PNP_SCANNER 8 +#define PPB_PNP_DIGICAM 9 + +#ifndef DONTPROBE_1284 + +static char *pnp_tokens[] = { + "PRINTER", "MODEM", "NET", "HDC", "PCMCIA", "MEDIA", + "FDC", "PORTS", "SCANNER", "DIGICAM", "", NULL }; + +#if 0 +static char *pnp_classes[] = { + "printer", "modem", "network device", + "hard disk", "PCMCIA", "multimedia device", + "floppy disk", "ports", "scanner", + "digital camera", "unknown device", NULL }; +#endif + +/* + * search_token() + * + * Search the first occurence of a token within a string + */ +static char * +search_token(char *str, int slen, char *token) +{ + int tlen, i; + +#define UNKNOWN_LENGTH -1 + + if (slen == UNKNOWN_LENGTH) + /* get string's length */ + slen = strlen(str); + + /* get token's length */ + tlen = strlen(token); + if (tlen == 0) + return (str); + + for (i = 0; i <= slen-tlen; i++) { + if (strncmp(str + i, token, tlen) == 0) + return (&str[i]); + } + + return (NULL); +} + +/* + * ppb_pnp_detect() + * + * Returns the class id. of the peripherial, -1 otherwise + */ +static int +ppb_pnp_detect(device_t bus) +{ + char *token, *class = 0; + int i, len, error; + int class_id = -1; + char str[PPB_PnP_STRING_SIZE+1]; + int unit = device_get_unit(bus); + + printf("Probing for PnP devices on ppbus%d:\n", unit); + + if ((error = ppb_1284_read_id(bus, PPB_NIBBLE, str, + PPB_PnP_STRING_SIZE, &len))) + goto end_detect; + +#ifdef DEBUG_1284 + printf("ppb: <PnP> %d characters: ", len); + for (i = 0; i < len; i++) + printf("%c(0x%x) ", str[i], str[i]); + printf("\n"); +#endif + + /* replace ';' characters by '\0' */ + for (i = 0; i < len; i++) + str[i] = (str[i] == ';') ? '\0' : str[i]; + + if ((token = search_token(str, len, "MFG")) != NULL || + (token = search_token(str, len, "MANUFACTURER")) != NULL) + printf("ppbus%d: <%s", unit, + search_token(token, UNKNOWN_LENGTH, ":") + 1); + else + printf("ppbus%d: <unknown", unit); + + if ((token = search_token(str, len, "MDL")) != NULL || + (token = search_token(str, len, "MODEL")) != NULL) + printf(" %s", + search_token(token, UNKNOWN_LENGTH, ":") + 1); + else + printf(" unknown"); + + if ((token = search_token(str, len, "VER")) != NULL) + printf("/%s", + search_token(token, UNKNOWN_LENGTH, ":") + 1); + + if ((token = search_token(str, len, "REV")) != NULL) + printf(".%s", + search_token(token, UNKNOWN_LENGTH, ":") + 1); + + printf(">"); + + if ((token = search_token(str, len, "CLS")) != NULL) { + class = search_token(token, UNKNOWN_LENGTH, ":") + 1; + printf(" %s", class); + } + + if ((token = search_token(str, len, "CMD")) != NULL || + (token = search_token(str, len, "COMMAND")) != NULL) + printf(" %s", + search_token(token, UNKNOWN_LENGTH, ":") + 1); + + printf("\n"); + + if (class) + /* identify class ident */ + for (i = 0; pnp_tokens[i] != NULL; i++) { + if (search_token(class, len, pnp_tokens[i]) != NULL) { + class_id = i; + goto end_detect; + } + } + + class_id = PPB_PnP_UNKNOWN; + +end_detect: + return (class_id); +} + +/* + * ppb_scan_bus() + * + * Scan the ppbus for IEEE1284 compliant devices + */ +static int +ppb_scan_bus(device_t bus) +{ + struct ppb_data * ppb = (struct ppb_data *)device_get_softc(bus); + int error = 0; + int unit = device_get_unit(bus); + + /* try all IEEE1284 modes, for one device only + * + * XXX We should implement the IEEE1284.3 standard to detect + * daisy chained devices + */ + + error = ppb_1284_negociate(bus, PPB_NIBBLE, PPB_REQUEST_ID); + + if ((ppb->state == PPB_ERROR) && (ppb->error == PPB_NOT_IEEE1284)) + goto end_scan; + + ppb_1284_terminate(bus); + + printf("ppbus%d: IEEE1284 device found ", unit); + + if (!(error = ppb_1284_negociate(bus, PPB_NIBBLE, 0))) { + printf("/NIBBLE"); + ppb_1284_terminate(bus); + } + + if (!(error = ppb_1284_negociate(bus, PPB_PS2, 0))) { + printf("/PS2"); + ppb_1284_terminate(bus); + } + + if (!(error = ppb_1284_negociate(bus, PPB_ECP, 0))) { + printf("/ECP"); + ppb_1284_terminate(bus); + } + + if (!(error = ppb_1284_negociate(bus, PPB_ECP, PPB_USE_RLE))) { + printf("/ECP_RLE"); + ppb_1284_terminate(bus); + } + + if (!(error = ppb_1284_negociate(bus, PPB_EPP, 0))) { + printf("/EPP"); + ppb_1284_terminate(bus); + } + + /* try more IEEE1284 modes */ + if (bootverbose) { + if (!(error = ppb_1284_negociate(bus, PPB_NIBBLE, + PPB_REQUEST_ID))) { + printf("/NIBBLE_ID"); + ppb_1284_terminate(bus); + } + + if (!(error = ppb_1284_negociate(bus, PPB_PS2, + PPB_REQUEST_ID))) { + printf("/PS2_ID"); + ppb_1284_terminate(bus); + } + + if (!(error = ppb_1284_negociate(bus, PPB_ECP, + PPB_REQUEST_ID))) { + printf("/ECP_ID"); + ppb_1284_terminate(bus); + } + + if (!(error = ppb_1284_negociate(bus, PPB_ECP, + PPB_REQUEST_ID | PPB_USE_RLE))) { + printf("/ECP_RLE_ID"); + ppb_1284_terminate(bus); + } + + if (!(error = ppb_1284_negociate(bus, PPB_COMPATIBLE, + PPB_EXTENSIBILITY_LINK))) { + printf("/Extensibility Link"); + ppb_1284_terminate(bus); + } + } + + printf("\n"); + + /* detect PnP devices */ + ppb->class_id = ppb_pnp_detect(bus); + + return (0); + +end_scan: + return (error); +} + +#endif /* !DONTPROBE_1284 */ + +static int +ppbus_attach(device_t dev) +{ + + /* Locate our children */ + bus_generic_probe(dev); + +#ifndef DONTPROBE_1284 + /* detect IEEE1284 compliant devices */ + ppb_scan_bus(dev); +#endif /* !DONTPROBE_1284 */ + + /* launch attachement of the added children */ + bus_generic_attach(dev); + + return 0; +} + +static int +ppbus_setup_intr(device_t bus, device_t child, struct resource *r, int flags, + void (*ihand)(void *), void *arg, void **cookiep) +{ + int error; + struct ppb_data *ppb = DEVTOSOFTC(bus); + struct ppb_device *ppbdev = (struct ppb_device *)device_get_ivars(child); + + /* a device driver must own the bus to register an interrupt */ + if (ppb->ppb_owner != child) + return (EINVAL); + + if ((error = BUS_SETUP_INTR(device_get_parent(bus), child, r, flags, + ihand, arg, cookiep))) + return (error); + + /* store the resource and the cookie for eventually forcing + * handler unregistration + */ + ppbdev->intr_cookie = *cookiep; + ppbdev->intr_resource = r; + + return (0); +} + +static int +ppbus_teardown_intr(device_t bus, device_t child, struct resource *r, void *ih) +{ + struct ppb_data *ppb = DEVTOSOFTC(bus); + struct ppb_device *ppbdev = (struct ppb_device *)device_get_ivars(child); + + /* a device driver must own the bus to unregister an interrupt */ + if ((ppb->ppb_owner != child) || (ppbdev->intr_cookie != ih) || + (ppbdev->intr_resource != r)) + return (EINVAL); + + ppbdev->intr_cookie = 0; + ppbdev->intr_resource = 0; + + /* pass unregistration to the upper layer */ + return (BUS_TEARDOWN_INTR(device_get_parent(bus), child, r, ih)); +} + +/* + * ppb_request_bus() + * + * Allocate the device to perform transfers. + * + * how : PPB_WAIT or PPB_DONTWAIT + */ +int +ppb_request_bus(device_t bus, device_t dev, int how) +{ + int s, error = 0; + struct ppb_data *ppb = DEVTOSOFTC(bus); + struct ppb_device *ppbdev = (struct ppb_device *)device_get_ivars(dev); + + while (!error) { + s = splhigh(); + if (ppb->ppb_owner) { + splx(s); + + switch (how) { + case (PPB_WAIT | PPB_INTR): + error = tsleep(ppb, PPBPRI|PCATCH, "ppbreq", 0); + break; + + case (PPB_WAIT | PPB_NOINTR): + error = tsleep(ppb, PPBPRI, "ppbreq", 0); + break; + + default: + return (EWOULDBLOCK); + break; + } + + } else { + ppb->ppb_owner = dev; + + /* restore the context of the device + * The first time, ctx.valid is certainly false + * then do not change anything. This is usefull for + * drivers that do not set there operating mode + * during attachement + */ + if (ppbdev->ctx.valid) + ppb_set_mode(bus, ppbdev->ctx.mode); + + splx(s); + return (0); + } + } + + return (error); +} + +/* + * ppb_release_bus() + * + * Release the device allocated with ppb_request_bus() + */ +int +ppb_release_bus(device_t bus, device_t dev) +{ + int s, error; + struct ppb_data *ppb = DEVTOSOFTC(bus); + struct ppb_device *ppbdev = (struct ppb_device *)device_get_ivars(dev); + + if (ppbdev->intr_resource != 0) + /* force interrupt handler unregistration when the ppbus is released */ + if ((error = BUS_TEARDOWN_INTR(bus, dev, ppbdev->intr_resource, + ppbdev->intr_cookie))) + return (error); + + s = splhigh(); + if (ppb->ppb_owner != dev) { + splx(s); + return (EACCES); + } + + ppb->ppb_owner = 0; + splx(s); + + /* save the context of the device */ + ppbdev->ctx.mode = ppb_get_mode(bus); + + /* ok, now the context of the device is valid */ + ppbdev->ctx.valid = 1; + + /* wakeup waiting processes */ + wakeup(ppb); + + return (0); +} + +static devclass_t ppbus_devclass; + +static device_method_t ppbus_methods[] = { + /* device interface */ + DEVMETHOD(device_probe, ppbus_probe), + DEVMETHOD(device_attach, ppbus_attach), + + /* bus interface */ + DEVMETHOD(bus_add_child, ppbus_add_child), + DEVMETHOD(bus_print_child, ppbus_print_child), + DEVMETHOD(bus_read_ivar, ppbus_read_ivar), + DEVMETHOD(bus_write_ivar, ppbus_write_ivar), + DEVMETHOD(bus_setup_intr, ppbus_setup_intr), + DEVMETHOD(bus_teardown_intr, ppbus_teardown_intr), + DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), + + { 0, 0 } +}; + +static driver_t ppbus_driver = { + "ppbus", + ppbus_methods, + sizeof(struct ppb_data), +}; +DRIVER_MODULE(ppbus, ppc, ppbus_driver, ppbus_devclass, 0, 0); diff --git a/sys/dev/ppbus/ppbconf.h b/sys/dev/ppbus/ppbconf.h new file mode 100644 index 0000000..96b7b1b --- /dev/null +++ b/sys/dev/ppbus/ppbconf.h @@ -0,0 +1,276 @@ +/*- + * Copyright (c) 1997, 1998, 1999 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. + * + * $FreeBSD$ + * + */ +#ifndef __PPBCONF_H +#define __PPBCONF_H + +#include <sys/queue.h> + +/* + * Parallel Port Bus sleep/wakeup queue. + */ +#define PPBPRI (PZERO+8) + +/* + * Parallel Port Chipset mode masks. + * NIBBLE mode is supposed to be available under each other modes. + */ +#define PPB_COMPATIBLE 0x0 /* Centronics compatible mode */ + +#define PPB_NIBBLE 0x1 /* reverse 4 bit mode */ +#define PPB_PS2 0x2 /* PS/2 byte mode */ +#define PPB_EPP 0x4 /* EPP mode, 32 bit */ +#define PPB_ECP 0x8 /* ECP mode */ + +/* mode aliases */ +#define PPB_SPP PPB_NIBBLE|PPB_PS2 +#define PPB_BYTE PPB_PS2 + +#define PPB_MASK 0x0f +#define PPB_OPTIONS_MASK 0xf0 + +#define PPB_IS_EPP(mode) (mode & PPB_EPP) +#define PPB_IN_EPP_MODE(bus) (PPB_IS_EPP (ppb_get_mode (bus))) +#define PPB_IN_NIBBLE_MODE(bus) (ppb_get_mode (bus) & PPB_NIBBLE) +#define PPB_IN_PS2_MODE(bus) (ppb_get_mode (bus) & PPB_PS2) + +#define n(flags) (~(flags) & (flags)) + +/* + * Parallel Port Chipset control bits. + */ +#define STROBE 0x01 +#define AUTOFEED 0x02 +#define nINIT 0x04 +#define SELECTIN 0x08 +#define IRQENABLE 0x10 +#define PCD 0x20 + +#define nSTROBE n(STROBE) +#define nAUTOFEED n(AUTOFEED) +#define INIT n(nINIT) +#define nSELECTIN n(SELECTIN) +#define nPCD n(PCD) + +/* + * Parallel Port Chipset status bits. + */ +#define TIMEOUT 0x01 +#define nFAULT 0x08 +#define SELECT 0x10 +#define PERROR 0x20 +#define nACK 0x40 +#define nBUSY 0x80 + +/* + * Structure to store status information. + */ +struct ppb_status { + unsigned char status; + + unsigned int timeout:1; + unsigned int error:1; + unsigned int select:1; + unsigned int paper_end:1; + unsigned int ack:1; + unsigned int busy:1; +}; + +/* Parallel port bus I/O opcodes */ +#define PPB_OUTSB_EPP 1 +#define PPB_OUTSW_EPP 2 +#define PPB_OUTSL_EPP 3 +#define PPB_INSB_EPP 4 +#define PPB_INSW_EPP 5 +#define PPB_INSL_EPP 6 +#define PPB_RDTR 7 +#define PPB_RSTR 8 +#define PPB_RCTR 9 +#define PPB_REPP_A 10 +#define PPB_REPP_D 11 +#define PPB_RECR 12 +#define PPB_RFIFO 13 +#define PPB_WDTR 14 +#define PPB_WSTR 15 +#define PPB_WCTR 16 +#define PPB_WEPP_A 17 +#define PPB_WEPP_D 18 +#define PPB_WECR 19 +#define PPB_WFIFO 20 + +/* + * How tsleep() is called in ppb_request_bus(). + */ +#define PPB_DONTWAIT 0 +#define PPB_NOINTR 0 +#define PPB_WAIT 0x1 +#define PPB_INTR 0x2 +#define PPB_POLL 0x4 +#define PPB_FOREVER -1 + +/* + * Microsequence stuff. + */ +#define PPB_MS_MAXLEN 64 /* XXX according to MS_INS_MASK */ +#define PPB_MS_MAXARGS 3 /* according to MS_ARG_MASK */ + +/* maximum number of mode dependent + * submicrosequences for in/out operations + */ +#define PPB_MAX_XFER 6 + +union ppb_insarg { + int i; + void *p; + char *c; + int (* f)(void *, char *); +}; + +struct ppb_microseq { + int opcode; /* microins. opcode */ + union ppb_insarg arg[PPB_MS_MAXARGS]; /* arguments */ +}; + +/* microseqences used for GET/PUT operations */ +struct ppb_xfer { + struct ppb_microseq *loop; /* the loop microsequence */ +}; + +/* + * Parallel Port Bus Device structure. + */ +struct ppb_data; /* see below */ + +struct ppb_context { + int valid; /* 1 if the struct is valid */ + int mode; /* XXX chipset operating mode */ + + struct microseq *curpc; /* pc in curmsq */ + struct microseq *curmsq; /* currently executed microseqence */ +}; + +/* + * List of IVARS available to ppb device drivers + */ +#define PPBUS_IVAR_MODE 0 +#define PPBUS_IVAR_AVM 1 +#define PPBUS_IVAR_IRQ 2 + +/* other fields are reserved to the ppbus internals */ + +struct ppb_device { + + const char *name; /* name of the device */ + + ushort mode; /* current mode of the device */ + ushort avm; /* available IEEE1284 modes of + * the device */ + uint flags; /* flags */ + + struct ppb_context ctx; /* context of the device */ + + /* mode dependent get msq. If NULL, + * IEEE1284 code is used */ + struct ppb_xfer + get_xfer[PPB_MAX_XFER]; + + /* mode dependent put msq. If NULL, + * IEEE1284 code is used */ + struct ppb_xfer + put_xfer[PPB_MAX_XFER]; + + struct resource *intr_resource; + void *intr_cookie; + + void *drv1, *drv2; /* drivers private data */ +}; + +/* EPP standards */ +#define EPP_1_9 0x0 /* default */ +#define EPP_1_7 0x1 + +/* Parallel Port Chipset IVARS */ /* elsewhere XXX */ +#define PPC_IVAR_EPP_PROTO 0 +#define PPC_IVAR_IRQ 1 + +/* + * Maximum size of the PnP info string + */ +#define PPB_PnP_STRING_SIZE 256 /* XXX */ + +/* + * Parallel Port Bus structure. + */ +struct ppb_data { + +#define PPB_PnP_PRINTER 0 +#define PPB_PnP_MODEM 1 +#define PPB_PnP_NET 2 +#define PPB_PnP_HDC 3 +#define PPB_PnP_PCMCIA 4 +#define PPB_PnP_MEDIA 5 +#define PPB_PnP_FDC 6 +#define PPB_PnP_PORTS 7 +#define PPB_PnP_SCANNER 8 +#define PPB_PnP_DIGICAM 9 +#define PPB_PnP_UNKNOWN 10 + int class_id; /* not a PnP device if class_id < 0 */ + + int state; /* current IEEE1284 state */ + int error; /* last IEEE1284 error */ + + int mode; /* IEEE 1284-1994 mode + * NIBBLE, PS2, EPP or ECP */ + + void *ppb_owner; /* device which owns the bus */ +}; + +#ifdef _KERNEL +extern int ppb_attach_device(device_t); +extern int ppb_request_bus(device_t, device_t, int); +extern int ppb_release_bus(device_t, device_t); + +/* bus related functions */ +extern int ppb_get_status(device_t, struct ppb_status *); +extern int ppb_poll_bus(device_t, int, char, char, int); +extern int ppb_reset_epp_timeout(device_t); +extern int ppb_ecp_sync(device_t); +extern int ppb_get_epp_protocol(device_t); +extern int ppb_set_mode(device_t, int); /* returns old mode */ +extern int ppb_get_mode(device_t); /* returns current mode */ +extern int ppb_write(device_t, char *, int, int); +#endif /* _KERNEL */ + +/* + * These are defined as macros for speedup. +#define ppb_get_base_addr(dev) ((dev)->ppb->ppb_link->base) +#define ppb_get_epp_protocol(dev) ((dev)->ppb->ppb_link->epp_protocol) +#define ppb_get_irq(dev) ((dev)->ppb->ppb_link->id_irq) + */ + +#endif diff --git a/sys/dev/ppbus/ppbio.h b/sys/dev/ppbus/ppbio.h new file mode 100644 index 0000000..2794edd --- /dev/null +++ b/sys/dev/ppbus/ppbio.h @@ -0,0 +1,83 @@ +/*- + * Copyright (c) 1999 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. + * + * $FreeBSD$ + * + */ + +#ifndef __PPBIO_H +#define __PPBIO_H + +/* + * Set of ppbus i/o routines callable from ppbus device drivers + */ + +#define ppb_outsb_epp(dev,buf,cnt) \ + (PPBUS_IO(device_get_parent(dev), PPB_OUTSB_EPP, buf, cnt, 0)) +#define ppb_outsw_epp(dev,buf,cnt) \ + (PPBUS_IO(device_get_parent(dev), PPB_OUTSW_EPP, buf, cnt, 0)) +#define ppb_outsl_epp(dev,buf,cnt) \ + (PPBUS_IO(device_get_parent(dev), PPB_OUTSL_EPP, buf, cnt, 0)) + +#define ppb_insb_epp(dev,buf,cnt) \ + (PPBUS_IO(device_get_parent(dev), PPB_INSB_EPP, buf, cnt, 0)) +#define ppb_insw_epp(dev,buf,cnt) \ + (PPBUS_IO(device_get_parent(dev), PPB_INSW_EPP, buf, cnt, 0)) +#define ppb_insl_epp(dev,buf,cnt) \ + (PPBUS_IO(device_get_parent(dev), PPB_INSL_EPP, buf, cnt, 0)) + +#define ppb_repp_A(dev) \ + (PPBUS_IO(device_get_parent(dev), PPB_REPP_A, 0, 0, 0)) +#define ppb_repp_D(dev) \ + (PPBUS_IO(device_get_parent(dev), PPB_REPP_D, 0, 0, 0)) +#define ppb_recr(dev) \ + (PPBUS_IO(device_get_parent(dev), PPB_RECR, 0, 0, 0)) +#define ppb_rfifo(dev) \ + (PPBUS_IO(device_get_parent(dev), PPB_RFIFO, 0, 0, 0)) + +#define ppb_wepp_A(dev,byte) \ + (PPBUS_IO(device_get_parent(dev), PPB_WEPP_A, 0, 0, byte)) +#define ppb_wepp_D(dev,byte) \ + (PPBUS_IO(device_get_parent(dev), PPB_WEPP_D, 0, 0, byte)) +#define ppb_wecr(dev,byte) \ + (PPBUS_IO(device_get_parent(dev), PPB_WECR, 0, 0, byte)) +#define ppb_wfifo(dev,byte) \ + (PPBUS_IO(device_get_parent(dev), PPB_WFIFO, 0, 0, byte)) + +#define ppb_rdtr(dev) \ + (PPBUS_IO(device_get_parent(dev), PPB_RDTR, 0, 0, 0)) +#define ppb_rstr(dev) \ + (PPBUS_IO(device_get_parent(dev), PPB_RSTR, 0, 0, 0)) +#define ppb_rctr(dev) \ + (PPBUS_IO(device_get_parent(dev), PPB_RCTR, 0, 0, 0)) + +#define ppb_wdtr(dev,byte) \ + (PPBUS_IO(device_get_parent(dev), PPB_WDTR, 0, 0, byte)) +#define ppb_wstr(dev,byte) \ + (PPBUS_IO(device_get_parent(dev), PPB_WSTR, 0, 0, byte)) +#define ppb_wctr(dev,byte) \ + (PPBUS_IO(device_get_parent(dev), PPB_WCTR, 0, 0, byte)) + +#endif diff --git a/sys/dev/ppbus/ppbus_if.m b/sys/dev/ppbus/ppbus_if.m new file mode 100644 index 0000000..f21dd83 --- /dev/null +++ b/sys/dev/ppbus/ppbus_if.m @@ -0,0 +1,93 @@ +# +# Copyright (c) 1999 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. +# +# $FreeBSD$ +# + +#include <sys/bus.h> +#include <dev/ppbus/ppbconf.h> + +INTERFACE ppbus; + +# +# Do low level i/o operations +# +METHOD u_char io { + device_t dev; + int opcode; + u_char *addr; + int cnt; + u_char byte; +}; + +# +# Execution of a microsequence +# +METHOD int exec_microseq { + device_t dev; + struct ppb_microseq **ppb_microseq; +}; + +# +# Reset EPP timeout +# +METHOD int reset_epp { + device_t dev; +} + +# +# Set chipset mode +# +METHOD int setmode { + device_t dev; + int mode; +} + +# +# Synchronize ECP FIFO +# +METHOD int ecp_sync { + device_t dev; +} + +# +# Do chipset dependent low level read +# +METHOD int read { + device_t dev; + char *buf; + int len; + int how; +} + +# +# Do chipset dependent low level write +# +METHOD int write { + device_t dev; + char *buf; + int len; + int how; +} diff --git a/sys/dev/ppbus/ppi.c b/sys/dev/ppbus/ppi.c new file mode 100644 index 0000000..d2cf4fd --- /dev/null +++ b/sys/dev/ppbus/ppi.c @@ -0,0 +1,578 @@ +/*- + * Copyright (c) 1997, 1998, 1999 Nicolas Souchu, Michael Smith + * 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. + * + * $FreeBSD$ + * + */ +#include "opt_ppb_1284.h" + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/kernel.h> +#include <sys/uio.h> +#include <sys/fcntl.h> + +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include <dev/ppbus/ppbconf.h> +#include <dev/ppbus/ppb_msq.h> + +#ifdef PERIPH_1284 +#include <dev/ppbus/ppb_1284.h> +#endif + +#include <dev/ppbus/ppi.h> + +#include "ppbus_if.h" + +#include <dev/ppbus/ppbio.h> + +#define BUFSIZE 512 + +struct ppi_data { + + int ppi_unit; + int ppi_flags; +#define HAVE_PPBUS (1<<0) +#define HAD_PPBUS (1<<1) + + int ppi_count; + int ppi_mode; /* IEEE1284 mode */ + char ppi_buffer[BUFSIZE]; + +#ifdef PERIPH_1284 + struct resource *intr_resource; /* interrupt resource */ + void *intr_cookie; /* interrupt registration cookie */ +#endif /* PERIPH_1284 */ +}; + +#define DEVTOSOFTC(dev) \ + ((struct ppi_data *)device_get_softc(dev)) +#define UNITOSOFTC(unit) \ + ((struct ppi_data *)devclass_get_softc(ppi_devclass, (unit))) +#define UNITODEVICE(unit) \ + (devclass_get_device(ppi_devclass, (unit))) + +static devclass_t ppi_devclass; + +static d_open_t ppiopen; +static d_close_t ppiclose; +static d_ioctl_t ppiioctl; +static d_write_t ppiwrite; +static d_read_t ppiread; + +#define CDEV_MAJOR 82 +static struct cdevsw ppi_cdevsw = { + /* open */ ppiopen, + /* close */ ppiclose, + /* read */ ppiread, + /* write */ ppiwrite, + /* ioctl */ ppiioctl, + /* poll */ nopoll, + /* mmap */ nommap, + /* strategy */ nostrategy, + /* name */ "ppi", + /* maj */ CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ 0, +}; + +#ifdef PERIPH_1284 + +static void +ppi_enable_intr(device_t ppidev) +{ + char r; + device_t ppbus = device_get_parent(ppidev); + + r = ppb_rctr(ppbus); + ppb_wctr(ppbus, r | IRQENABLE); + + return; +} + +static void +ppi_disable_intr(device_t ppidev) +{ + char r; + device_t ppbus = device_get_parent(ppidev); + + r = ppb_rctr(ppbus); + ppb_wctr(ppbus, r & ~IRQENABLE); + + return; +} + +#endif /* PERIPH_1284 */ + +static void +ppi_identify(driver_t *driver, device_t parent) +{ + + BUS_ADD_CHILD(parent, 0, "ppi", -1); +} + +/* + * ppi_probe() + */ +static int +ppi_probe(device_t dev) +{ + struct ppi_data *ppi; + + /* probe is always ok */ + device_set_desc(dev, "Parallel I/O"); + + ppi = DEVTOSOFTC(dev); + bzero(ppi, sizeof(struct ppi_data)); + + return (0); +} + +/* + * ppi_attach() + */ +static int +ppi_attach(device_t dev) +{ +#ifdef PERIPH_1284 + uintptr_t irq; + int zero = 0; + struct ppi_data *ppi = DEVTOSOFTC(dev); + + /* retrive the irq */ + BUS_READ_IVAR(device_get_parent(dev), dev, PPBUS_IVAR_IRQ, &irq); + + /* declare our interrupt handler */ + ppi->intr_resource = bus_alloc_resource(dev, SYS_RES_IRQ, + &zero, irq, irq, 1, RF_ACTIVE); +#endif /* PERIPH_1284 */ + + make_dev(&ppi_cdevsw, device_get_unit(dev), /* XXX cleanup */ + UID_ROOT, GID_WHEEL, + 0600, "ppi%d", device_get_unit(dev)); + + return (0); +} + +#ifdef PERIPH_1284 +/* + * Cable + * ----- + * + * Use an IEEE1284 compliant (DB25/DB25) cable with the following tricks: + * + * nStrobe <-> nAck 1 <-> 10 + * nAutofd <-> Busy 11 <-> 14 + * nSelectin <-> Select 17 <-> 13 + * nInit <-> nFault 15 <-> 16 + * + */ +static void +ppiintr(void *arg) +{ + device_t ppidev = (device_t)arg; + device_t ppbus = device_get_parent(ppidev); + struct ppi_data *ppi = DEVTOSOFTC(ppidev); + + ppi_disable_intr(ppidev); + + switch (ppb_1284_get_state(ppbus)) { + + /* accept IEEE1284 negociation then wakeup an waiting process to + * continue negociation at process level */ + case PPB_FORWARD_IDLE: + /* Event 1 */ + if ((ppb_rstr(ppbus) & (SELECT | nBUSY)) == + (SELECT | nBUSY)) { + /* IEEE1284 negociation */ +#ifdef DEBUG_1284 + printf("N"); +#endif + + /* Event 2 - prepare for reading the ext. value */ + ppb_wctr(ppbus, (PCD | STROBE | nINIT) & ~SELECTIN); + + ppb_1284_set_state(ppbus, PPB_NEGOCIATION); + + } else { +#ifdef DEBUG_1284 + printf("0x%x", ppb_rstr(ppbus)); +#endif + ppb_peripheral_terminate(ppbus, PPB_DONTWAIT); + break; + } + + /* wake up any process waiting for negociation from + * remote master host */ + + /* XXX should set a variable to warn the process about + * the interrupt */ + + wakeup(ppi); + break; + default: +#ifdef DEBUG_1284 + printf("?%d", ppb_1284_get_state(ppbus)); +#endif + ppb_1284_set_state(ppbus, PPB_FORWARD_IDLE); + ppb_set_mode(ppbus, PPB_COMPATIBLE); + break; + } + + ppi_enable_intr(ppidev); + + return; +} +#endif /* PERIPH_1284 */ + +static int +ppiopen(dev_t dev, int flags, int fmt, struct thread *td) +{ + u_int unit = minor(dev); + struct ppi_data *ppi = UNITOSOFTC(unit); + device_t ppidev = UNITODEVICE(unit); + device_t ppbus = device_get_parent(ppidev); + int res; + + if (!ppi) + return (ENXIO); + + if (!(ppi->ppi_flags & HAVE_PPBUS)) { + if ((res = ppb_request_bus(ppbus, ppidev, + (flags & O_NONBLOCK) ? PPB_DONTWAIT : + (PPB_WAIT | PPB_INTR)))) + return (res); + + ppi->ppi_flags |= HAVE_PPBUS; + +#ifdef PERIPH_1284 + if (ppi->intr_resource) { + /* register our interrupt handler */ + BUS_SETUP_INTR(device_get_parent(ppidev), ppidev, ppi->intr_resource, + INTR_TYPE_TTY, ppiintr, dev, &ppi->intr_cookie); + } +#endif /* PERIPH_1284 */ + } + ppi->ppi_count += 1; + + return (0); +} + +static int +ppiclose(dev_t dev, int flags, int fmt, struct thread *td) +{ + u_int unit = minor(dev); + struct ppi_data *ppi = UNITOSOFTC(unit); + device_t ppidev = UNITODEVICE(unit); + device_t ppbus = device_get_parent(ppidev); + + ppi->ppi_count --; + if (!ppi->ppi_count) { + +#ifdef PERIPH_1284 + switch (ppb_1284_get_state(ppbus)) { + case PPB_PERIPHERAL_IDLE: + ppb_peripheral_terminate(ppbus, 0); + break; + case PPB_REVERSE_IDLE: + case PPB_EPP_IDLE: + case PPB_ECP_FORWARD_IDLE: + default: + ppb_1284_terminate(ppbus); + break; + } +#endif /* PERIPH_1284 */ + + /* unregistration of interrupt forced by release */ + ppb_release_bus(ppbus, ppidev); + + ppi->ppi_flags &= ~HAVE_PPBUS; + } + + return (0); +} + +/* + * ppiread() + * + * IEEE1284 compliant read. + * + * First, try negociation to BYTE then NIBBLE mode + * If no data is available, wait for it otherwise transfer as much as possible + */ +static int +ppiread(dev_t dev, struct uio *uio, int ioflag) +{ +#ifdef PERIPH_1284 + u_int unit = minor(dev); + struct ppi_data *ppi = UNITOSOFTC(unit); + device_t ppidev = UNITODEVICE(unit); + device_t ppbus = device_get_parent(ppidev); + int len, error = 0; + + switch (ppb_1284_get_state(ppbus)) { + case PPB_PERIPHERAL_IDLE: + ppb_peripheral_terminate(ppbus, 0); + /* FALLTHROUGH */ + + case PPB_FORWARD_IDLE: + /* if can't negociate NIBBLE mode then try BYTE mode, + * the peripheral may be a computer + */ + if ((ppb_1284_negociate(ppbus, + ppi->ppi_mode = PPB_NIBBLE, 0))) { + + /* XXX Wait 2 seconds to let the remote host some + * time to terminate its interrupt + */ + tsleep(ppi, PPBPRI, "ppiread", 2*hz); + + if ((error = ppb_1284_negociate(ppbus, + ppi->ppi_mode = PPB_BYTE, 0))) + return (error); + } + break; + + case PPB_REVERSE_IDLE: + case PPB_EPP_IDLE: + case PPB_ECP_FORWARD_IDLE: + default: + break; + } + +#ifdef DEBUG_1284 + printf("N"); +#endif + /* read data */ + len = 0; + while (uio->uio_resid) { + if ((error = ppb_1284_read(ppbus, ppi->ppi_mode, + ppi->ppi_buffer, min(BUFSIZE, uio->uio_resid), + &len))) { + goto error; + } + + if (!len) + goto error; /* no more data */ + +#ifdef DEBUG_1284 + printf("d"); +#endif + if ((error = uiomove(ppi->ppi_buffer, len, uio))) + goto error; + } + +error: + +#else /* PERIPH_1284 */ + int error = ENODEV; +#endif + + return (error); +} + +/* + * ppiwrite() + * + * IEEE1284 compliant write + * + * Actually, this is the peripheral side of a remote IEEE1284 read + * + * The first part of the negociation (IEEE1284 device detection) is + * done at interrupt level, then the remaining is done by the writing + * process + * + * Once negociation done, transfer data + */ +static int +ppiwrite(dev_t dev, struct uio *uio, int ioflag) +{ +#ifdef PERIPH_1284 + u_int unit = minor(dev); + struct ppi_data *ppi = UNITOSOFTC(unit); + device_t ppidev = UNITODEVICE(unit); + device_t ppbus = device_get_parent(ppidev); + int len, error = 0, sent; + +#if 0 + int ret; + + #define ADDRESS MS_PARAM(0, 0, MS_TYP_PTR) + #define LENGTH MS_PARAM(0, 1, MS_TYP_INT) + + struct ppb_microseq msq[] = { + { MS_OP_PUT, { MS_UNKNOWN, MS_UNKNOWN, MS_UNKNOWN } }, + MS_RET(0) + }; + + /* negociate ECP mode */ + if (ppb_1284_negociate(ppbus, PPB_ECP, 0)) { + printf("ppiwrite: ECP negociation failed\n"); + } + + while (!error && (len = min(uio->uio_resid, BUFSIZE))) { + uiomove(ppi->ppi_buffer, len, uio); + + ppb_MS_init_msq(msq, 2, ADDRESS, ppi->ppi_buffer, LENGTH, len); + + error = ppb_MS_microseq(ppbus, msq, &ret); + } +#endif + + /* we have to be peripheral to be able to send data, so + * wait for the appropriate state + */ + if (ppb_1284_get_state(ppbus) < PPB_PERIPHERAL_NEGOCIATION) + ppb_1284_terminate(ppbus); + + while (ppb_1284_get_state(ppbus) != PPB_PERIPHERAL_IDLE) { + /* XXX should check a variable before sleeping */ +#ifdef DEBUG_1284 + printf("s"); +#endif + + ppi_enable_intr(ppidev); + + /* sleep until IEEE1284 negociation starts */ + error = tsleep(ppi, PCATCH | PPBPRI, "ppiwrite", 0); + + switch (error) { + case 0: + /* negociate peripheral side with BYTE mode */ + ppb_peripheral_negociate(ppbus, PPB_BYTE, 0); + break; + case EWOULDBLOCK: + break; + default: + goto error; + } + } +#ifdef DEBUG_1284 + printf("N"); +#endif + + /* negociation done, write bytes to master host */ + while ((len = min(uio->uio_resid, BUFSIZE)) != 0) { + uiomove(ppi->ppi_buffer, len, uio); + if ((error = byte_peripheral_write(ppbus, + ppi->ppi_buffer, len, &sent))) + goto error; +#ifdef DEBUG_1284 + printf("d"); +#endif + } + +error: + +#else /* PERIPH_1284 */ + int error = ENODEV; +#endif + + return (error); +} + +static int +ppiioctl(dev_t dev, u_long cmd, caddr_t data, int flags, struct thread *td) +{ + u_int unit = minor(dev); + device_t ppidev = UNITODEVICE(unit); + device_t ppbus = device_get_parent(ppidev); + int error = 0; + u_int8_t *val = (u_int8_t *)data; + + switch (cmd) { + + case PPIGDATA: /* get data register */ + *val = ppb_rdtr(ppbus); + break; + case PPIGSTATUS: /* get status bits */ + *val = ppb_rstr(ppbus); + break; + case PPIGCTRL: /* get control bits */ + *val = ppb_rctr(ppbus); + break; + case PPIGEPPD: /* get EPP data bits */ + *val = ppb_repp_D(ppbus); + break; + case PPIGECR: /* get ECP bits */ + *val = ppb_recr(ppbus); + break; + case PPIGFIFO: /* read FIFO */ + *val = ppb_rfifo(ppbus); + break; + case PPISDATA: /* set data register */ + ppb_wdtr(ppbus, *val); + break; + case PPISSTATUS: /* set status bits */ + ppb_wstr(ppbus, *val); + break; + case PPISCTRL: /* set control bits */ + ppb_wctr(ppbus, *val); + break; + case PPISEPPD: /* set EPP data bits */ + ppb_wepp_D(ppbus, *val); + break; + case PPISECR: /* set ECP bits */ + ppb_wecr(ppbus, *val); + break; + case PPISFIFO: /* write FIFO */ + ppb_wfifo(ppbus, *val); + break; + case PPIGEPPA: /* get EPP address bits */ + *val = ppb_repp_A(ppbus); + break; + case PPISEPPA: /* set EPP address bits */ + ppb_wepp_A(ppbus, *val); + break; + default: + error = ENOTTY; + break; + } + + return (error); +} + +static device_method_t ppi_methods[] = { + /* device interface */ + DEVMETHOD(device_identify, ppi_identify), + DEVMETHOD(device_probe, ppi_probe), + DEVMETHOD(device_attach, ppi_attach), + + { 0, 0 } +}; + +static driver_t ppi_driver = { + "ppi", + ppi_methods, + sizeof(struct ppi_data), +}; +DRIVER_MODULE(ppi, ppbus, ppi_driver, ppi_devclass, 0, 0); diff --git a/sys/dev/ppbus/ppi.h b/sys/dev/ppbus/ppi.h new file mode 100644 index 0000000..8ff2fb4 --- /dev/null +++ b/sys/dev/ppbus/ppi.h @@ -0,0 +1,54 @@ +/*- + * 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. + * + * $FreeBSD$ + * + */ +#ifndef __PPI_H +#define __PPI_H + +#ifndef _KERNEL +# include <sys/types.h> +#endif +#include <sys/ioccom.h> + +#define PPIGDATA _IOR('P', 10, u_int8_t) +#define PPIGSTATUS _IOR('P', 11, u_int8_t) +#define PPIGCTRL _IOR('P', 12, u_int8_t) +#define PPIGEPPD _IOR('P', 13, u_int8_t) +#define PPIGECR _IOR('P', 14, u_int8_t) +#define PPIGFIFO _IOR('P', 15, u_int8_t) + +#define PPISDATA _IOW('P', 16, u_int8_t) +#define PPISSTATUS _IOW('P', 17, u_int8_t) +#define PPISCTRL _IOW('P', 18, u_int8_t) +#define PPISEPPD _IOW('P', 19, u_int8_t) +#define PPISECR _IOW('P', 20, u_int8_t) +#define PPISFIFO _IOW('P', 21, u_int8_t) + +#define PPIGEPPA _IOR('P', 22, u_int8_t) +#define PPISEPPA _IOR('P', 23, u_int8_t) + +#endif diff --git a/sys/dev/ppbus/pps.c b/sys/dev/ppbus/pps.c new file mode 100644 index 0000000..b201757 --- /dev/null +++ b/sys/dev/ppbus/pps.c @@ -0,0 +1,320 @@ +/* + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 42): + * <phk@FreeBSD.org> wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp + * ---------------------------------------------------------------------------- + * + * $FreeBSD$ + * + * This driver implements a draft-mogul-pps-api-02.txt PPS source. + * + * The input pin is pin#10 + * The echo output pin is pin#14 + * + */ + +#include <sys/param.h> +#include <sys/kernel.h> +#include <sys/systm.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/conf.h> +#include <sys/timepps.h> +#include <machine/bus.h> +#include <machine/resource.h> +#include <sys/rman.h> + +#include <dev/ppbus/ppbconf.h> +#include "ppbus_if.h" +#include <dev/ppbus/ppbio.h> + +#define PPS_NAME "pps" /* our official name */ + +#define PRVERBOSE(fmt, arg...) if (bootverbose) printf(fmt, ##arg); + +struct pps_data { + struct ppb_device pps_dev; + struct pps_state pps[9]; + dev_t devs[9]; + device_t ppsdev; + device_t ppbus; + int busy; + struct callout_handle timeout; + int lastdata; + + struct resource *intr_resource; /* interrupt resource */ + void *intr_cookie; /* interrupt registration cookie */ +}; + +static void ppsintr(void *arg); +static void ppshcpoll(void *arg); + +#define DEVTOSOFTC(dev) \ + ((struct pps_data *)device_get_softc(dev)) + +static devclass_t pps_devclass; + +static d_open_t ppsopen; +static d_close_t ppsclose; +static d_ioctl_t ppsioctl; + +#define CDEV_MAJOR 89 +static struct cdevsw pps_cdevsw = { + /* open */ ppsopen, + /* close */ ppsclose, + /* read */ noread, + /* write */ nowrite, + /* ioctl */ ppsioctl, + /* poll */ nopoll, + /* mmap */ nommap, + /* strategy */ nostrategy, + /* name */ PPS_NAME, + /* maj */ CDEV_MAJOR, + /* dump */ nodump, + /* psize */ nopsize, + /* flags */ 0, +}; + +static void +ppsidentify(driver_t *driver, device_t parent) +{ + + BUS_ADD_CHILD(parent, 0, PPS_NAME, -1); +} + +static int +ppstry(device_t ppbus, int send, int expect) +{ + int i; + + ppb_wdtr(ppbus, send); + i = ppb_rdtr(ppbus); + PRVERBOSE("S: %02x E: %02x G: %02x\n", send, expect, i); + return (i != expect); +} + +static int +ppsprobe(device_t ppsdev) +{ + device_set_desc(ppsdev, "Pulse per second Timing Interface"); + + return (0); +} + +static int +ppsattach(device_t dev) +{ + struct pps_data *sc = DEVTOSOFTC(dev); + device_t ppbus = device_get_parent(dev); + dev_t d; + intptr_t irq; + int i, unit, zero = 0; + + bzero(sc, sizeof(struct pps_data)); /* XXX doesn't newbus do this? */ + + /* retrieve the ppbus irq */ + BUS_READ_IVAR(ppbus, dev, PPBUS_IVAR_IRQ, &irq); + + if (irq > 0) { + /* declare our interrupt handler */ + sc->intr_resource = bus_alloc_resource(dev, SYS_RES_IRQ, + &zero, irq, irq, 1, RF_SHAREABLE); + } + /* interrupts seem mandatory */ + if (sc->intr_resource == NULL) + return (ENXIO); + + sc->ppsdev = dev; + sc->ppbus = ppbus; + unit = device_get_unit(ppbus); + d = make_dev(&pps_cdevsw, unit, + UID_ROOT, GID_WHEEL, 0644, PPS_NAME "%d", unit); + sc->devs[0] = d; + sc->pps[0].ppscap = PPS_CAPTUREASSERT | PPS_ECHOASSERT; + d->si_drv1 = sc; + d->si_drv2 = (void*)0; + pps_init(&sc->pps[0]); + + if (ppb_request_bus(ppbus, dev, PPB_DONTWAIT)) + return (0); + + do { + i = ppb_set_mode(sc->ppbus, PPB_EPP); + PRVERBOSE("EPP: %d %d\n", i, PPB_IN_EPP_MODE(sc->ppbus)); + if (i == -1) + break; + i = 0; + ppb_wctr(ppbus, i); + if (ppstry(ppbus, 0x00, 0x00)) + break; + if (ppstry(ppbus, 0x55, 0x55)) + break; + if (ppstry(ppbus, 0xaa, 0xaa)) + break; + if (ppstry(ppbus, 0xff, 0xff)) + break; + + i = IRQENABLE | PCD | STROBE | nINIT | SELECTIN; + ppb_wctr(ppbus, i); + PRVERBOSE("CTR = %02x (%02x)\n", ppb_rctr(ppbus), i); + if (ppstry(ppbus, 0x00, 0x00)) + break; + if (ppstry(ppbus, 0x55, 0x00)) + break; + if (ppstry(ppbus, 0xaa, 0x00)) + break; + if (ppstry(ppbus, 0xff, 0x00)) + break; + + i = IRQENABLE | PCD | nINIT | SELECTIN; + ppb_wctr(ppbus, i); + PRVERBOSE("CTR = %02x (%02x)\n", ppb_rctr(ppbus), i); + ppstry(ppbus, 0x00, 0xff); + ppstry(ppbus, 0x55, 0xff); + ppstry(ppbus, 0xaa, 0xff); + ppstry(ppbus, 0xff, 0xff); + + for (i = 1; i < 9; i++) { + d = make_dev(&pps_cdevsw, unit + 0x10000 * i, + UID_ROOT, GID_WHEEL, 0644, PPS_NAME "%db%d", unit, i - 1); + sc->devs[i] = d; + sc->pps[i].ppscap = PPS_CAPTUREASSERT | PPS_CAPTURECLEAR; + d->si_drv1 = sc; + d->si_drv2 = (void *)(intptr_t)i; + pps_init(&sc->pps[i]); + } + } while (0); + i = ppb_set_mode(sc->ppbus, PPB_COMPATIBLE); + ppb_release_bus(ppbus, dev); + + return (0); +} + +static int +ppsopen(dev_t dev, int flags, int fmt, struct thread *td) +{ + struct pps_data *sc = dev->si_drv1; + int subdev = (intptr_t)dev->si_drv2; + int error, i; + + if (!sc->busy) { + device_t ppsdev = sc->ppsdev; + device_t ppbus = sc->ppbus; + + if (ppb_request_bus(ppbus, ppsdev, PPB_WAIT|PPB_INTR)) + return (EINTR); + + /* attach the interrupt handler */ + if ((error = BUS_SETUP_INTR(ppbus, ppsdev, sc->intr_resource, + INTR_TYPE_TTY, ppsintr, ppsdev, + &sc->intr_cookie))) { + ppb_release_bus(ppbus, ppsdev); + return (error); + } + + i = ppb_set_mode(sc->ppbus, PPB_PS2); + PRVERBOSE("EPP: %d %d\n", i, PPB_IN_EPP_MODE(sc->ppbus)); + + i = IRQENABLE | PCD | nINIT | SELECTIN; + ppb_wctr(ppbus, i); + } + if (subdev > 0 && !(sc->busy & ~1)) { + sc->timeout = timeout(ppshcpoll, sc, 1); + sc->lastdata = ppb_rdtr(sc->ppbus); + } + sc->busy |= (1 << subdev); + return(0); +} + +static int +ppsclose(dev_t dev, int flags, int fmt, struct thread *td) +{ + struct pps_data *sc = dev->si_drv1; + int subdev = (intptr_t)dev->si_drv2; + + sc->pps[subdev].ppsparam.mode = 0; /* PHK ??? */ + sc->busy &= ~(1 << subdev); + if (subdev > 0 && !(sc->busy & ~1)) + untimeout(ppshcpoll, sc, sc->timeout); + if (!sc->busy) { + device_t ppsdev = sc->ppsdev; + device_t ppbus = sc->ppbus; + + ppb_wdtr(ppbus, 0); + ppb_wctr(ppbus, 0); + + /* Note: the interrupt handler is automatically detached */ + ppb_set_mode(ppbus, PPB_COMPATIBLE); + ppb_release_bus(ppbus, ppsdev); + } + return(0); +} + +static void +ppshcpoll(void *arg) +{ + struct pps_data *sc = arg; + int i, j, k, l; + + if (!(sc->busy & ~1)) + return; + sc->timeout = timeout(ppshcpoll, sc, 1); + i = ppb_rdtr(sc->ppbus); + if (i == sc->lastdata) + return; + l = sc->lastdata ^ i; + k = 1; + for (j = 1; j < 9; j ++) { + if (l & k) { + pps_capture(&sc->pps[j]); + pps_event(&sc->pps[j], + i & k ? PPS_CAPTUREASSERT : PPS_CAPTURECLEAR); + } + k += k; + } + sc->lastdata = i; +} + +static void +ppsintr(void *arg) +{ + device_t ppsdev = (device_t)arg; + struct pps_data *sc = DEVTOSOFTC(ppsdev); + device_t ppbus = sc->ppbus; + + pps_capture(&sc->pps[0]); + if (!(ppb_rstr(ppbus) & nACK)) + return; + if (sc->pps[0].ppsparam.mode & PPS_ECHOASSERT) + ppb_wctr(ppbus, IRQENABLE | AUTOFEED); + pps_event(&sc->pps[0], PPS_CAPTUREASSERT); + if (sc->pps[0].ppsparam.mode & PPS_ECHOASSERT) + ppb_wctr(ppbus, IRQENABLE); +} + +static int +ppsioctl(dev_t dev, u_long cmd, caddr_t data, int flags, struct thread *td) +{ + struct pps_data *sc = dev->si_drv1; + int subdev = (intptr_t)dev->si_drv2; + + return (pps_ioctl(cmd, data, &sc->pps[subdev])); +} + +static device_method_t pps_methods[] = { + /* device interface */ + DEVMETHOD(device_identify, ppsidentify), + DEVMETHOD(device_probe, ppsprobe), + DEVMETHOD(device_attach, ppsattach), + + { 0, 0 } +}; + +static driver_t pps_driver = { + PPS_NAME, + pps_methods, + sizeof(struct pps_data), +}; +DRIVER_MODULE(pps, ppbus, pps_driver, pps_devclass, 0, 0); diff --git a/sys/dev/ppbus/vpo.c b/sys/dev/ppbus/vpo.c new file mode 100644 index 0000000..ad83e2f --- /dev/null +++ b/sys/dev/ppbus/vpo.c @@ -0,0 +1,463 @@ +/*- + * Copyright (c) 1997, 1998, 1999 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. + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/malloc.h> +#include <sys/devicestat.h> /* for struct devstat */ + + +#include <cam/cam.h> +#include <cam/cam_ccb.h> +#include <cam/cam_sim.h> +#include <cam/cam_xpt_sim.h> +#include <cam/cam_debug.h> +#include <cam/cam_periph.h> + +#include <cam/scsi/scsi_all.h> +#include <cam/scsi/scsi_message.h> +#include <cam/scsi/scsi_da.h> + +#include <sys/kernel.h> + +#include "opt_vpo.h" + +#include <dev/ppbus/ppbconf.h> +#include <dev/ppbus/vpoio.h> + +#include "ppbus_if.h" + +struct vpo_sense { + struct scsi_sense cmd; + unsigned int stat; + unsigned int count; +}; + +struct vpo_data { + unsigned short vpo_unit; + + int vpo_stat; + int vpo_count; + int vpo_error; + + int vpo_isplus; + + struct cam_sim *sim; + + struct vpo_sense vpo_sense; + + struct vpoio_data vpo_io; /* interface to low level functions */ +}; + +#define DEVTOSOFTC(dev) \ + ((struct vpo_data *)device_get_softc(dev)) + +/* cam related functions */ +static void vpo_action(struct cam_sim *sim, union ccb *ccb); +static void vpo_poll(struct cam_sim *sim); +static void vpo_cam_rescan_callback(struct cam_periph *periph, + union ccb *ccb); +static void vpo_cam_rescan(struct vpo_data *vpo); + +static void +vpo_identify(driver_t *driver, device_t parent) +{ + + BUS_ADD_CHILD(parent, 0, "vpo", -1); +} + +/* + * vpo_probe() + */ +static int +vpo_probe(device_t dev) +{ + struct vpo_data *vpo; + int error; + + vpo = DEVTOSOFTC(dev); + bzero(vpo, sizeof(struct vpo_data)); + + /* vpo dependent initialisation */ + vpo->vpo_unit = device_get_unit(dev); + + /* low level probe */ + vpoio_set_unit(&vpo->vpo_io, vpo->vpo_unit); + + /* check ZIP before ZIP+ or imm_probe() will send controls to + * the printer or whatelse connected to the port */ + if ((error = vpoio_probe(dev, &vpo->vpo_io)) == 0) { + vpo->vpo_isplus = 0; + device_set_desc(dev, + "Iomega VPI0 Parallel to SCSI interface"); + } else if ((error = imm_probe(dev, &vpo->vpo_io)) == 0) { + vpo->vpo_isplus = 1; + device_set_desc(dev, + "Iomega Matchmaker Parallel to SCSI interface"); + } else { + return (error); + } + + return (0); +} + +/* + * vpo_attach() + */ +static int +vpo_attach(device_t dev) +{ + struct vpo_data *vpo = DEVTOSOFTC(dev); + struct cam_devq *devq; + int error; + + /* low level attachment */ + if (vpo->vpo_isplus) { + if ((error = imm_attach(&vpo->vpo_io))) + return (error); + } else { + if ((error = vpoio_attach(&vpo->vpo_io))) + return (error); + } + + /* + ** Now tell the generic SCSI layer + ** about our bus. + */ + devq = cam_simq_alloc(/*maxopenings*/1); + /* XXX What about low-level detach on error? */ + if (devq == NULL) + return (ENXIO); + + vpo->sim = cam_sim_alloc(vpo_action, vpo_poll, "vpo", vpo, + device_get_unit(dev), + /*untagged*/1, /*tagged*/0, devq); + if (vpo->sim == NULL) { + cam_simq_free(devq); + return (ENXIO); + } + + if (xpt_bus_register(vpo->sim, /*bus*/0) != CAM_SUCCESS) { + cam_sim_free(vpo->sim, /*free_devq*/TRUE); + return (ENXIO); + } + + /* all went ok */ + + vpo_cam_rescan(vpo); /* have CAM rescan the bus */ + + return (0); +} + +static void +vpo_cam_rescan_callback(struct cam_periph *periph, union ccb *ccb) +{ + free(ccb, M_TEMP); +} + +static void +vpo_cam_rescan(struct vpo_data *vpo) +{ + struct cam_path *path; + union ccb *ccb = malloc(sizeof(union ccb), M_TEMP, M_WAITOK | M_ZERO); + + if (xpt_create_path(&path, xpt_periph, cam_sim_path(vpo->sim), 0, 0) + != CAM_REQ_CMP) { + /* A failure is benign as the user can do a manual rescan */ + return; + } + + xpt_setup_ccb(&ccb->ccb_h, path, 5/*priority (low)*/); + ccb->ccb_h.func_code = XPT_SCAN_BUS; + ccb->ccb_h.cbfcnp = vpo_cam_rescan_callback; + ccb->crcn.flags = CAM_FLAG_NONE; + xpt_action(ccb); + + /* The scan is in progress now. */ +} + +/* + * vpo_intr() + */ +static void +vpo_intr(struct vpo_data *vpo, struct ccb_scsiio *csio) +{ + int errno; /* error in errno.h */ + int s; +#ifdef VP0_DEBUG + int i; +#endif + + s = splcam(); + + if (vpo->vpo_isplus) { + errno = imm_do_scsi(&vpo->vpo_io, VP0_INITIATOR, + csio->ccb_h.target_id, + (char *)&csio->cdb_io.cdb_bytes, csio->cdb_len, + (char *)csio->data_ptr, csio->dxfer_len, + &vpo->vpo_stat, &vpo->vpo_count, &vpo->vpo_error); + } else { + errno = vpoio_do_scsi(&vpo->vpo_io, VP0_INITIATOR, + csio->ccb_h.target_id, + (char *)&csio->cdb_io.cdb_bytes, csio->cdb_len, + (char *)csio->data_ptr, csio->dxfer_len, + &vpo->vpo_stat, &vpo->vpo_count, &vpo->vpo_error); + } + +#ifdef VP0_DEBUG + printf("vpo_do_scsi = %d, status = 0x%x, count = %d, vpo_error = %d\n", + errno, vpo->vpo_stat, vpo->vpo_count, vpo->vpo_error); + + /* dump of command */ + for (i=0; i<csio->cdb_len; i++) + printf("%x ", ((char *)&csio->cdb_io.cdb_bytes)[i]); + + printf("\n"); +#endif + + if (errno) { + /* connection to ppbus interrupted */ + csio->ccb_h.status = CAM_CMD_TIMEOUT; + goto error; + } + + /* if a timeout occured, no sense */ + if (vpo->vpo_error) { + if (vpo->vpo_error != VP0_ESELECT_TIMEOUT) + printf("vpo%d: VP0 error/timeout (%d)\n", + vpo->vpo_unit, vpo->vpo_error); + + csio->ccb_h.status = CAM_CMD_TIMEOUT; + goto error; + } + + /* check scsi status */ + if (vpo->vpo_stat != SCSI_STATUS_OK) { + csio->scsi_status = vpo->vpo_stat; + + /* check if we have to sense the drive */ + if ((vpo->vpo_stat & SCSI_STATUS_CHECK_COND) != 0) { + + vpo->vpo_sense.cmd.opcode = REQUEST_SENSE; + vpo->vpo_sense.cmd.length = csio->sense_len; + vpo->vpo_sense.cmd.control = 0; + + if (vpo->vpo_isplus) { + errno = imm_do_scsi(&vpo->vpo_io, VP0_INITIATOR, + csio->ccb_h.target_id, + (char *)&vpo->vpo_sense.cmd, + sizeof(vpo->vpo_sense.cmd), + (char *)&csio->sense_data, csio->sense_len, + &vpo->vpo_sense.stat, &vpo->vpo_sense.count, + &vpo->vpo_error); + } else { + errno = vpoio_do_scsi(&vpo->vpo_io, VP0_INITIATOR, + csio->ccb_h.target_id, + (char *)&vpo->vpo_sense.cmd, + sizeof(vpo->vpo_sense.cmd), + (char *)&csio->sense_data, csio->sense_len, + &vpo->vpo_sense.stat, &vpo->vpo_sense.count, + &vpo->vpo_error); + } + + +#ifdef VP0_DEBUG + printf("(sense) vpo_do_scsi = %d, status = 0x%x, count = %d, vpo_error = %d\n", + errno, vpo->vpo_sense.stat, vpo->vpo_sense.count, vpo->vpo_error); +#endif + + /* check sense return status */ + if (errno == 0 && vpo->vpo_sense.stat == SCSI_STATUS_OK) { + /* sense ok */ + csio->ccb_h.status = CAM_AUTOSNS_VALID | CAM_SCSI_STATUS_ERROR; + csio->sense_resid = csio->sense_len - vpo->vpo_sense.count; + +#ifdef VP0_DEBUG + /* dump of sense info */ + printf("(sense) "); + for (i=0; i<vpo->vpo_sense.count; i++) + printf("%x ", ((char *)&csio->sense_data)[i]); + printf("\n"); +#endif + + } else { + /* sense failed */ + csio->ccb_h.status = CAM_AUTOSENSE_FAIL; + } + } else { + /* no sense */ + csio->ccb_h.status = CAM_SCSI_STATUS_ERROR; + } + + goto error; + } + + csio->resid = csio->dxfer_len - vpo->vpo_count; + csio->ccb_h.status = CAM_REQ_CMP; + +error: + splx(s); + + return; +} + +static void +vpo_action(struct cam_sim *sim, union ccb *ccb) +{ + + struct vpo_data *vpo = (struct vpo_data *)sim->softc; + + switch (ccb->ccb_h.func_code) { + case XPT_SCSI_IO: + { + struct ccb_scsiio *csio; + + csio = &ccb->csio; + +#ifdef VP0_DEBUG + printf("vpo%d: XPT_SCSI_IO (0x%x) request\n", + vpo->vpo_unit, csio->cdb_io.cdb_bytes[0]); +#endif + + vpo_intr(vpo, csio); + + xpt_done(ccb); + + break; + } + case XPT_CALC_GEOMETRY: + { + struct ccb_calc_geometry *ccg; + + ccg = &ccb->ccg; + +#ifdef VP0_DEBUG + printf("vpo%d: XPT_CALC_GEOMETRY (bs=%d,vs=%d,c=%d,h=%d,spt=%d) request\n", + vpo->vpo_unit, + ccg->block_size, + ccg->volume_size, + ccg->cylinders, + ccg->heads, + ccg->secs_per_track); +#endif + + ccg->heads = 64; + ccg->secs_per_track = 32; + ccg->cylinders = ccg->volume_size / + (ccg->heads * ccg->secs_per_track); + + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_RESET_BUS: /* Reset the specified SCSI bus */ + { + +#ifdef VP0_DEBUG + printf("vpo%d: XPT_RESET_BUS request\n", vpo->vpo_unit); +#endif + + if (vpo->vpo_isplus) { + if (imm_reset_bus(&vpo->vpo_io)) { + ccb->ccb_h.status = CAM_REQ_CMP_ERR; + xpt_done(ccb); + return; + } + } else { + if (vpoio_reset_bus(&vpo->vpo_io)) { + ccb->ccb_h.status = CAM_REQ_CMP_ERR; + xpt_done(ccb); + return; + } + } + + ccb->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + case XPT_PATH_INQ: /* Path routing inquiry */ + { + struct ccb_pathinq *cpi = &ccb->cpi; + +#ifdef VP0_DEBUG + printf("vpo%d: XPT_PATH_INQ request\n", vpo->vpo_unit); +#endif + cpi->version_num = 1; /* XXX??? */ + cpi->hba_inquiry = 0; + cpi->target_sprt = 0; + cpi->hba_misc = 0; + cpi->hba_eng_cnt = 0; + cpi->max_target = 7; + cpi->max_lun = 0; + cpi->initiator_id = VP0_INITIATOR; + cpi->bus_id = sim->bus_id; + cpi->base_transfer_speed = 93; + strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN); + strncpy(cpi->hba_vid, "Iomega", HBA_IDLEN); + strncpy(cpi->dev_name, sim->sim_name, DEV_IDLEN); + cpi->unit_number = sim->unit_number; + + cpi->ccb_h.status = CAM_REQ_CMP; + xpt_done(ccb); + break; + } + default: + ccb->ccb_h.status = CAM_REQ_INVALID; + xpt_done(ccb); + break; + } + + return; +} + +static void +vpo_poll(struct cam_sim *sim) +{ + /* The ZIP is actually always polled throw vpo_action() */ + return; +} + +static devclass_t vpo_devclass; + +static device_method_t vpo_methods[] = { + /* device interface */ + DEVMETHOD(device_identify, vpo_identify), + DEVMETHOD(device_probe, vpo_probe), + DEVMETHOD(device_attach, vpo_attach), + + { 0, 0 } +}; + +static driver_t vpo_driver = { + "vpo", + vpo_methods, + sizeof(struct vpo_data), +}; +DRIVER_MODULE(vpo, ppbus, vpo_driver, vpo_devclass, 0, 0); diff --git a/sys/dev/ppbus/vpoio.c b/sys/dev/ppbus/vpoio.c new file mode 100644 index 0000000..2a2a0dd --- /dev/null +++ b/sys/dev/ppbus/vpoio.c @@ -0,0 +1,780 @@ +/*- + * Copyright (c) 1998, 1999 Nicolas Souchu + * Copyright (c) 2000 Alcove - 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. + * + * $FreeBSD$ + * + */ + +#ifdef _KERNEL +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/module.h> +#include <sys/bus.h> +#include <sys/malloc.h> + +#include <machine/clock.h> + +#endif + +#include "opt_vpo.h" + +#include <dev/ppbus/ppbio.h> +#include <dev/ppbus/ppbconf.h> +#include <dev/ppbus/ppb_msq.h> +#include <dev/ppbus/vpoio.h> + +#include "ppbus_if.h" + +/* + * The driver pools the drive. We may add a timeout queue to avoid + * active polling on nACK. I've tried this but it leads to unreliable + * transfers + */ +#define VP0_SELTMO 5000 /* select timeout */ +#define VP0_FAST_SPINTMO 500000 /* wait status timeout */ +#define VP0_LOW_SPINTMO 5000000 /* wait status timeout */ + +/* + * Actually, VP0 timings are more accurate (about few 16MHZ cycles), + * but succeeding in respecting such timings leads to architecture + * dependent considerations. + */ +#define VP0_PULSE 1 + +#define VP0_SECTOR_SIZE 512 +#define VP0_BUFFER_SIZE 0x12000 + +#define n(flags) (~(flags) & (flags)) + +/* + * VP0 connections. + */ +#define H_AUTO n(AUTOFEED) +#define H_nAUTO AUTOFEED +#define H_STROBE n(STROBE) +#define H_nSTROBE STROBE +#define H_BSY n(nBUSY) +#define H_nBSY nBUSY +#define H_SEL SELECT +#define H_nSEL n(SELECT) +#define H_ERR PERROR +#define H_nERR n(PERROR) +#define H_ACK nACK +#define H_nACK n(nACK) +#define H_FLT nFAULT +#define H_nFLT n(nFAULT) +#define H_SELIN n(SELECTIN) +#define H_nSELIN SELECTIN +#define H_INIT nINIT +#define H_nINIT n(nINIT) + +/* + * Microcode to execute very fast I/O sequences at the lowest bus level. + */ + +#define WAIT_RET MS_PARAM(4, 2, MS_TYP_PTR) +#define WAIT_TMO MS_PARAM(0, 0, MS_TYP_INT) + +#define DECLARE_WAIT_MICROSEQUENCE \ +struct ppb_microseq wait_microseq[] = { \ + MS_SET(MS_UNKNOWN), \ + /* loop */ \ + MS_BRSET(nBUSY, 2 /* ready */), \ + MS_DBRA(-2 /* loop */), \ + MS_RET(1), /* timed out */ \ + /* ready */ \ + MS_RFETCH(MS_REG_STR, 0xf0, MS_UNKNOWN), \ + MS_RET(0) /* no error */ \ +} + +/* call this macro to initialize connect/disconnect microsequences */ +#define INIT_TRIG_MICROSEQ { \ + int i; \ + for (i=1; i <= 7; i+=2) { \ + disconnect_microseq[i].arg[2] = (union ppb_insarg)d_pulse; \ + connect_epp_microseq[i].arg[2] = \ + connect_spp_microseq[i].arg[2] = (union ppb_insarg)c_pulse; \ + } \ +} + +#define trig_d_pulse MS_TRIG(MS_REG_CTR,5,MS_UNKNOWN /* d_pulse */) +static char d_pulse[] = { + H_AUTO | H_nSELIN | H_INIT | H_STROBE, 0, + H_nAUTO | H_nSELIN | H_INIT | H_STROBE, VP0_PULSE, + H_AUTO | H_nSELIN | H_INIT | H_STROBE, 0, + H_AUTO | H_SELIN | H_INIT | H_STROBE, VP0_PULSE, + H_AUTO | H_nSELIN | H_INIT | H_STROBE, VP0_PULSE +}; + +#define trig_c_pulse MS_TRIG(MS_REG_CTR,5,MS_UNKNOWN /* c_pulse */) +static char c_pulse[] = { + H_AUTO | H_nSELIN | H_INIT | H_STROBE, 0, + H_AUTO | H_SELIN | H_INIT | H_STROBE, 0, + H_nAUTO | H_SELIN | H_INIT | H_STROBE, VP0_PULSE, + H_AUTO | H_SELIN | H_INIT | H_STROBE, 0, + H_AUTO | H_nSELIN | H_INIT | H_STROBE, VP0_PULSE +}; + +static struct ppb_microseq disconnect_microseq[] = { + MS_DASS(0x0), trig_d_pulse, MS_DASS(0x3c), trig_d_pulse, + MS_DASS(0x20), trig_d_pulse, MS_DASS(0xf), trig_d_pulse, MS_RET(0) +}; + +static struct ppb_microseq connect_epp_microseq[] = { + MS_DASS(0x0), trig_c_pulse, MS_DASS(0x3c), trig_c_pulse, + MS_DASS(0x20), trig_c_pulse, MS_DASS(0xcf), trig_c_pulse, MS_RET(0) +}; + +static struct ppb_microseq connect_spp_microseq[] = { + MS_DASS(0x0), trig_c_pulse, MS_DASS(0x3c), trig_c_pulse, + MS_DASS(0x20), trig_c_pulse, MS_DASS(0x8f), trig_c_pulse, MS_RET(0) +}; + +/* + * nibble_inbyte_hook() + * + * Formats high and low nibble into a character + */ +static int +nibble_inbyte_hook (void *p, char *ptr) +{ + struct vpo_nibble *s = (struct vpo_nibble *)p; + + /* increment the buffer pointer */ + *ptr++ = ((s->l >> 4) & 0x0f) + (s->h & 0xf0); + + return (0); +} + +#define INB_NIBBLE_H MS_PARAM(2, 2, MS_TYP_PTR) +#define INB_NIBBLE_L MS_PARAM(4, 2, MS_TYP_PTR) +#define INB_NIBBLE_F MS_PARAM(5, 0, MS_TYP_FUN) +#define INB_NIBBLE_P MS_PARAM(5, 1, MS_TYP_PTR) + +/* + * This is the sub-microseqence for MS_GET in NIBBLE mode + * Retrieve the two nibbles and call the C function to generate the character + * and store it in the buffer (see nibble_inbyte_hook()) + */ + +#define DECLARE_NIBBLE_INBYTE_SUBMICROSEQ \ +struct ppb_microseq nibble_inbyte_submicroseq[] = { \ +/* loop: */ \ + MS_CASS( H_AUTO | H_SELIN | H_INIT | H_STROBE), \ + MS_DELAY(VP0_PULSE), \ + MS_RFETCH(MS_REG_STR, MS_FETCH_ALL, MS_UNKNOWN /* high nibble */),\ + MS_CASS(H_nAUTO | H_SELIN | H_INIT | H_STROBE), \ + MS_RFETCH(MS_REG_STR, MS_FETCH_ALL, MS_UNKNOWN /* low nibble */),\ + /* do a C call to format the received nibbles */ \ + MS_C_CALL(MS_UNKNOWN /* C hook */, MS_UNKNOWN /* param */),\ + MS_DBRA(-7 /* loop */), \ + MS_CASS(H_AUTO | H_nSELIN | H_INIT | H_STROBE), \ + MS_RET(0) \ +} + +/* + * This is the sub-microseqence for MS_GET in PS2 mode + */ +static struct ppb_microseq ps2_inbyte_submicroseq[] = { + MS_CASS(PCD | H_AUTO | H_SELIN | H_INIT | H_nSTROBE), + +/* loop: */ + MS_RFETCH_P(1, MS_REG_DTR, MS_FETCH_ALL), + MS_CASS(PCD | H_nAUTO | H_SELIN | H_INIT | H_nSTROBE), + MS_CASS(PCD | H_AUTO | H_SELIN | H_INIT | H_nSTROBE), + MS_DBRA(-4 /* loop */), + + MS_CASS(H_AUTO | H_nSELIN | H_INIT | H_STROBE), + MS_RET(0) +}; + +/* + * This is the sub-microsequence for MS_PUT in both NIBBLE and PS2 modes + */ +static struct ppb_microseq spp_outbyte_submicroseq[] = { + +/* loop: */ + MS_RASSERT_P(1, MS_REG_DTR), + MS_CASS(H_nAUTO | H_nSELIN | H_INIT | H_STROBE), + MS_CASS( H_AUTO | H_nSELIN | H_INIT | H_STROBE), + MS_DELAY(VP0_PULSE), + MS_DBRA(-5 /* loop */), + + /* return from the put call */ + MS_RET(0) +}; + +/* EPP 1.7 microsequences, ptr and len set at runtime */ +static struct ppb_microseq epp17_outstr_body[] = { + MS_CASS(H_AUTO | H_SELIN | H_INIT | H_STROBE), + +/* loop: */ + MS_RASSERT_P(1, MS_REG_EPP_D), + MS_BRSET(TIMEOUT, 3 /* error */), /* EPP timeout? */ + MS_DBRA(-3 /* loop */), + + MS_CASS(H_AUTO | H_nSELIN | H_INIT | H_STROBE), + MS_RET(0), +/* error: */ + MS_CASS(H_AUTO | H_nSELIN | H_INIT | H_STROBE), + MS_RET(1) +}; + +static struct ppb_microseq epp17_instr_body[] = { + MS_CASS(PCD | H_AUTO | H_SELIN | H_INIT | H_STROBE), + +/* loop: */ + MS_RFETCH_P(1, MS_REG_EPP_D, MS_FETCH_ALL), + MS_BRSET(TIMEOUT, 3 /* error */), /* EPP timeout? */ + MS_DBRA(-3 /* loop */), + + MS_CASS(PCD | H_AUTO | H_nSELIN | H_INIT | H_STROBE), + MS_RET(0), +/* error: */ + MS_CASS(PCD | H_AUTO | H_nSELIN | H_INIT | H_STROBE), + MS_RET(1) +}; + +static struct ppb_microseq in_disk_mode[] = { + MS_CASS( H_AUTO | H_nSELIN | H_INIT | H_STROBE), + MS_CASS(H_nAUTO | H_nSELIN | H_INIT | H_STROBE), + + MS_BRCLEAR(H_FLT, 3 /* error */), + MS_CASS( H_AUTO | H_nSELIN | H_INIT | H_STROBE), + MS_BRSET(H_FLT, 1 /* error */), + + MS_RET(1), +/* error: */ + MS_RET(0) +}; + +static int +vpoio_disconnect(struct vpoio_data *vpo) +{ + device_t ppbus = device_get_parent(vpo->vpo_dev); + int ret; + + ppb_MS_microseq(ppbus, vpo->vpo_dev, disconnect_microseq, &ret); + return (ppb_release_bus(ppbus, vpo->vpo_dev)); +} + +/* + * how : PPB_WAIT or PPB_DONTWAIT + */ +static int +vpoio_connect(struct vpoio_data *vpo, int how) +{ + device_t ppbus = device_get_parent(vpo->vpo_dev); + int error; + int ret; + + if ((error = ppb_request_bus(ppbus, vpo->vpo_dev, how))) { + +#ifdef VP0_DEBUG + printf("%s: can't request bus!\n", __func__); +#endif + return error; + } + + if (PPB_IN_EPP_MODE(ppbus)) + ppb_MS_microseq(ppbus, vpo->vpo_dev, connect_epp_microseq, &ret); + else + ppb_MS_microseq(ppbus, vpo->vpo_dev, connect_spp_microseq, &ret); + + return (0); +} + +/* + * vpoio_reset() + * + * SCSI reset signal, the drive must be in disk mode + */ +static void +vpoio_reset (struct vpoio_data *vpo) +{ + device_t ppbus = device_get_parent(vpo->vpo_dev); + int ret; + + struct ppb_microseq reset_microseq[] = { + + #define INITIATOR MS_PARAM(0, 1, MS_TYP_INT) + + MS_DASS(MS_UNKNOWN), + MS_CASS(H_AUTO | H_nSELIN | H_nINIT | H_STROBE), + MS_DELAY(25), + MS_CASS(H_AUTO | H_nSELIN | H_INIT | H_STROBE), + MS_RET(0) + }; + + ppb_MS_init_msq(reset_microseq, 1, INITIATOR, 1 << VP0_INITIATOR); + ppb_MS_microseq(ppbus, vpo->vpo_dev, reset_microseq, &ret); + + return; +} + +/* + * vpoio_in_disk_mode() + */ +static int +vpoio_in_disk_mode(struct vpoio_data *vpo) +{ + device_t ppbus = device_get_parent(vpo->vpo_dev); + int ret; + + ppb_MS_microseq(ppbus, vpo->vpo_dev, in_disk_mode, &ret); + + return (ret); +} + +/* + * vpoio_detect() + * + * Detect and initialise the VP0 adapter. + */ +static int +vpoio_detect(struct vpoio_data *vpo) +{ + device_t ppbus = device_get_parent(vpo->vpo_dev); + int error, ret; + + /* allocate the bus, then apply microsequences */ + if ((error = ppb_request_bus(ppbus, vpo->vpo_dev, PPB_DONTWAIT))) + return (error); + + /* Force disconnection */ + ppb_MS_microseq(ppbus, vpo->vpo_dev, disconnect_microseq, &ret); + + /* Try to enter EPP mode, then connect to the drive in EPP mode */ + if (ppb_set_mode(ppbus, PPB_EPP) != -1) { + /* call manually the microseq instead of using the appropriate function + * since we already requested the ppbus */ + ppb_MS_microseq(ppbus, vpo->vpo_dev, connect_epp_microseq, &ret); + } + + /* If EPP mode switch failed or ZIP connection in EPP mode failed, + * try to connect in NIBBLE mode */ + if (!vpoio_in_disk_mode(vpo)) { + + /* The interface must be at least PS/2 or NIBBLE capable. + * There is no way to know if the ZIP will work with + * PS/2 mode since PS/2 and SPP both use the same connect + * sequence. One must supress PS/2 with boot flags if + * PS/2 mode fails (see ppc(4)). + */ + if (ppb_set_mode(ppbus, PPB_PS2) != -1) { + vpo->vpo_mode_found = VP0_MODE_PS2; + } else { + if (ppb_set_mode(ppbus, PPB_NIBBLE) == -1) + goto error; + + vpo->vpo_mode_found = VP0_MODE_NIBBLE; + } + + /* Can't know if the interface is capable of PS/2 yet */ + ppb_MS_microseq(ppbus, vpo->vpo_dev, connect_spp_microseq, &ret); + if (!vpoio_in_disk_mode(vpo)) { + vpo->vpo_mode_found = VP0_MODE_UNDEFINED; + if (bootverbose) + printf("vpo%d: can't connect to the drive\n", + vpo->vpo_unit); + + /* disconnect and release the bus */ + ppb_MS_microseq(ppbus, vpo->vpo_dev, disconnect_microseq, + &ret); + goto error; + } + } else { + vpo->vpo_mode_found = VP0_MODE_EPP; + } + + /* send SCSI reset signal */ + vpoio_reset(vpo); + + ppb_MS_microseq(ppbus, vpo->vpo_dev, disconnect_microseq, &ret); + + /* ensure we are disconnected or daisy chained peripheral + * may cause serious problem to the disk */ + if (vpoio_in_disk_mode(vpo)) { + if (bootverbose) + printf("vpo%d: can't disconnect from the drive\n", + vpo->vpo_unit); + goto error; + } + + ppb_release_bus(ppbus, vpo->vpo_dev); + return (0); + +error: + ppb_release_bus(ppbus, vpo->vpo_dev); + return (VP0_EINITFAILED); +} + +/* + * vpoio_outstr() + */ +static int +vpoio_outstr(struct vpoio_data *vpo, char *buffer, int size) +{ + device_t ppbus = device_get_parent(vpo->vpo_dev); + int error = 0; + + ppb_MS_exec(ppbus, vpo->vpo_dev, MS_OP_PUT, (union ppb_insarg)buffer, + (union ppb_insarg)size, (union ppb_insarg)MS_UNKNOWN, &error); + + ppb_ecp_sync(ppbus); + + return (error); +} + +/* + * vpoio_instr() + */ +static int +vpoio_instr(struct vpoio_data *vpo, char *buffer, int size) +{ + device_t ppbus = device_get_parent(vpo->vpo_dev); + int error = 0; + + ppb_MS_exec(ppbus, vpo->vpo_dev, MS_OP_GET, (union ppb_insarg)buffer, + (union ppb_insarg)size, (union ppb_insarg)MS_UNKNOWN, &error); + + ppb_ecp_sync(ppbus); + + return (error); +} + +static char +vpoio_select(struct vpoio_data *vpo, int initiator, int target) +{ + device_t ppbus = device_get_parent(vpo->vpo_dev); + int ret; + + struct ppb_microseq select_microseq[] = { + + /* parameter list + */ + #define SELECT_TARGET MS_PARAM(0, 1, MS_TYP_INT) + #define SELECT_INITIATOR MS_PARAM(3, 1, MS_TYP_INT) + + /* send the select command to the drive */ + MS_DASS(MS_UNKNOWN), + MS_CASS(H_nAUTO | H_nSELIN | H_INIT | H_STROBE), + MS_CASS( H_AUTO | H_nSELIN | H_INIT | H_STROBE), + MS_DASS(MS_UNKNOWN), + MS_CASS( H_AUTO | H_nSELIN | H_nINIT | H_STROBE), + + /* now, wait until the drive is ready */ + MS_SET(VP0_SELTMO), +/* loop: */ MS_BRSET(H_ACK, 2 /* ready */), + MS_DBRA(-2 /* loop */), +/* error: */ MS_RET(1), +/* ready: */ MS_RET(0) + }; + + /* initialize the select microsequence */ + ppb_MS_init_msq(select_microseq, 2, + SELECT_TARGET, 1 << target, + SELECT_INITIATOR, 1 << initiator); + + ppb_MS_microseq(ppbus, vpo->vpo_dev, select_microseq, &ret); + + if (ret) + return (VP0_ESELECT_TIMEOUT); + + return (0); +} + +/* + * vpoio_wait() + * + * H_SELIN must be low. + * + * XXX should be ported to microseq + */ +static char +vpoio_wait(struct vpoio_data *vpo, int tmo) +{ + DECLARE_WAIT_MICROSEQUENCE; + + device_t ppbus = device_get_parent(vpo->vpo_dev); + int ret, err; + +#if 0 /* broken */ + if (ppb_poll_device(ppbus, 150, nBUSY, nBUSY, PPB_INTR)) + return (0); + + return (ppb_rstr(ppbus) & 0xf0); +#endif + + /* + * Return some status information. + * Semantics : 0xc0 = ZIP wants more data + * 0xd0 = ZIP wants to send more data + * 0xe0 = ZIP wants command + * 0xf0 = end of transfer, ZIP is sending status + */ + + ppb_MS_init_msq(wait_microseq, 2, + WAIT_RET, (void *)&ret, + WAIT_TMO, tmo); + + ppb_MS_microseq(ppbus, vpo->vpo_dev, wait_microseq, &err); + + if (err) + return (0); /* command timed out */ + + return(ret); +} + +/* + * vpoio_probe() + * + * Low level probe of vpo device + * + */ +int +vpoio_probe(device_t dev, struct vpoio_data *vpo) +{ + int error; + + /* ppbus dependent initialisation */ + vpo->vpo_dev = dev; + + /* + * Initialize microsequence code + */ + INIT_TRIG_MICROSEQ; + + /* now, try to initialise the drive */ + if ((error = vpoio_detect(vpo))) { + return (error); + } + + return (0); +} + +/* + * vpoio_attach() + * + * Low level attachment of vpo device + * + */ +int +vpoio_attach(struct vpoio_data *vpo) +{ + DECLARE_NIBBLE_INBYTE_SUBMICROSEQ; + device_t ppbus = device_get_parent(vpo->vpo_dev); + int error = 0; + + vpo->vpo_nibble_inbyte_msq = (struct ppb_microseq *)malloc( + sizeof(nibble_inbyte_submicroseq), M_DEVBUF, M_NOWAIT); + + if (!vpo->vpo_nibble_inbyte_msq) + return (ENXIO); + + bcopy((void *)nibble_inbyte_submicroseq, + (void *)vpo->vpo_nibble_inbyte_msq, + sizeof(nibble_inbyte_submicroseq)); + + ppb_MS_init_msq(vpo->vpo_nibble_inbyte_msq, 4, + INB_NIBBLE_H, (void *)&(vpo)->vpo_nibble.h, + INB_NIBBLE_L, (void *)&(vpo)->vpo_nibble.l, + INB_NIBBLE_F, nibble_inbyte_hook, + INB_NIBBLE_P, (void *)&(vpo)->vpo_nibble); + + /* + * Initialize mode dependent in/out microsequences + */ + if ((error = ppb_request_bus(ppbus, vpo->vpo_dev, PPB_WAIT))) + goto error; + + /* ppbus sets automatically the last mode entered during detection */ + switch (vpo->vpo_mode_found) { + case VP0_MODE_EPP: + ppb_MS_GET_init(ppbus, vpo->vpo_dev, epp17_instr_body); + ppb_MS_PUT_init(ppbus, vpo->vpo_dev, epp17_outstr_body); + printf("vpo%d: EPP mode\n", vpo->vpo_unit); + break; + case VP0_MODE_PS2: + ppb_MS_GET_init(ppbus, vpo->vpo_dev, ps2_inbyte_submicroseq); + ppb_MS_PUT_init(ppbus, vpo->vpo_dev, spp_outbyte_submicroseq); + printf("vpo%d: PS2 mode\n", vpo->vpo_unit); + break; + case VP0_MODE_NIBBLE: + ppb_MS_GET_init(ppbus, vpo->vpo_dev, vpo->vpo_nibble_inbyte_msq); + ppb_MS_PUT_init(ppbus, vpo->vpo_dev, spp_outbyte_submicroseq); + printf("vpo%d: NIBBLE mode\n", vpo->vpo_unit); + break; + default: + panic("vpo: unknown mode %d", vpo->vpo_mode_found); + } + + ppb_release_bus(ppbus, vpo->vpo_dev); + +error: + return (error); +} + +/* + * vpoio_reset_bus() + * + */ +int +vpoio_reset_bus(struct vpoio_data *vpo) +{ + /* first, connect to the drive */ + if (vpoio_connect(vpo, PPB_WAIT|PPB_INTR) || !vpoio_in_disk_mode(vpo)) { + +#ifdef VP0_DEBUG + printf("%s: not in disk mode!\n", __func__); +#endif + /* release ppbus */ + vpoio_disconnect(vpo); + return (1); + } + + /* reset the SCSI bus */ + vpoio_reset(vpo); + + /* then disconnect */ + vpoio_disconnect(vpo); + + return (0); +} + +/* + * vpoio_do_scsi() + * + * Send an SCSI command + * + */ +int +vpoio_do_scsi(struct vpoio_data *vpo, int host, int target, char *command, + int clen, char *buffer, int blen, int *result, int *count, + int *ret) +{ + device_t ppbus = device_get_parent(vpo->vpo_dev); + register char r; + char l, h = 0; + int len, error = 0; + register int k; + + /* + * enter disk state, allocate the ppbus + * + * XXX + * Should we allow this call to be interruptible? + * The only way to report the interruption is to return + * EIO do upper SCSI code :^( + */ + if ((error = vpoio_connect(vpo, PPB_WAIT|PPB_INTR))) + return (error); + + if (!vpoio_in_disk_mode(vpo)) { + *ret = VP0_ECONNECT; goto error; + } + + if ((*ret = vpoio_select(vpo,host,target))) + goto error; + + /* + * Send the command ... + * + * set H_SELIN low for vpoio_wait(). + */ + ppb_wctr(ppbus, H_AUTO | H_nSELIN | H_INIT | H_STROBE); + + for (k = 0; k < clen; k++) { + if (vpoio_wait(vpo, VP0_FAST_SPINTMO) != (char)0xe0) { + *ret = VP0_ECMD_TIMEOUT; + goto error; + } + if (vpoio_outstr(vpo, &command[k], 1)) { + *ret = VP0_EPPDATA_TIMEOUT; + goto error; + } + } + + /* + * Completion ... + */ + + *count = 0; + for (;;) { + + if (!(r = vpoio_wait(vpo, VP0_LOW_SPINTMO))) { + *ret = VP0_ESTATUS_TIMEOUT; goto error; + } + + /* stop when the ZIP wants to send status */ + if (r == (char)0xf0) + break; + + if (*count >= blen) { + *ret = VP0_EDATA_OVERFLOW; + goto error; + } + + /* if in EPP mode or writing bytes, try to transfer a sector + * otherwise, just send one byte + */ + if (PPB_IN_EPP_MODE(ppbus) || r == (char)0xc0) + len = (((blen - *count) >= VP0_SECTOR_SIZE)) ? + VP0_SECTOR_SIZE : 1; + else + len = 1; + + /* ZIP wants to send data? */ + if (r == (char)0xc0) + error = vpoio_outstr(vpo, &buffer[*count], len); + else + error = vpoio_instr(vpo, &buffer[*count], len); + + if (error) { + *ret = error; + goto error; + } + + *count += len; + } + + if (vpoio_instr(vpo, &l, 1)) { + *ret = VP0_EOTHER; goto error; + } + + /* check if the ZIP wants to send more status */ + if (vpoio_wait(vpo, VP0_FAST_SPINTMO) == (char)0xf0) + if (vpoio_instr(vpo, &h, 1)) { + *ret = VP0_EOTHER+2; goto error; + } + + *result = ((int) h << 8) | ((int) l & 0xff); + +error: + /* return to printer state, release the ppbus */ + vpoio_disconnect(vpo); + return (0); +} diff --git a/sys/dev/ppbus/vpoio.h b/sys/dev/ppbus/vpoio.h new file mode 100644 index 0000000..fe66fa5 --- /dev/null +++ b/sys/dev/ppbus/vpoio.h @@ -0,0 +1,100 @@ +/*- + * Copyright (c) 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. + * + * $FreeBSD$ + * + */ +#ifndef __VP0IO_H +#define __VP0IO_H + +/* + * The ZIP drive cannot act as an initiator. + */ +#define VP0_INITIATOR 0x7 + +#define VP0_ESELECT_TIMEOUT 1 +#define VP0_ECMD_TIMEOUT 2 +#define VP0_ECONNECT 3 +#define VP0_ESTATUS_TIMEOUT 4 +#define VP0_EDATA_OVERFLOW 5 +#define VP0_EDISCONNECT 6 +#define VP0_EPPDATA_TIMEOUT 7 +#define VP0_ENEGOCIATE 8 +#define VP0_ENOPORT 9 +#define VP0_EINITFAILED 10 +#define VP0_EINTR 12 + +#define VP0_EOTHER 13 + +#define VP0_OPENNINGS 1 + +/* + * Data structure used during microsequence execution + * when characters are received in nibble mode + */ +struct vpo_nibble { + char h; /* most significant nibble */ + char l; /* less significant nibble */ +}; + +/* Mode found during initialisation */ +#define VP0_MODE_UNDEFINED 0x0 +#define VP0_MODE_NIBBLE 0x1 +#define VP0_MODE_PS2 0x2 +#define VP0_MODE_EPP 0x3 + +struct vpoio_data { + unsigned short int vpo_unit; + int vpo_mode_found; /* Mode found during init */ + + struct vpo_nibble vpo_nibble; + + /* each device must have its own nibble inbyte microsequence */ + struct ppb_microseq *vpo_nibble_inbyte_msq; + + device_t vpo_dev; +}; + +#define vpoio_set_unit(vpo,unit) ((vpo)->vpo_unit = unit) + +int vpoio_probe(device_t dev, struct vpoio_data *vpo); + +int vpoio_attach(struct vpoio_data *vpo); +int vpoio_reset_bus(struct vpoio_data *vpo); + +int vpoio_do_scsi(struct vpoio_data *vpo, int host, int target, char *command, + int clen, char *buffer, int blen, int *result, int *count, + int *ret); + +int imm_probe(device_t dev, struct vpoio_data *vpo); + +int imm_attach(struct vpoio_data *vpo); +int imm_reset_bus(struct vpoio_data *vpo); + +int imm_do_scsi(struct vpoio_data *vpo, int host, int target, char *command, + int clen, char *buffer, int blen, int *result, int *count, + int *ret); + +#endif |