From 7dcba2c8a1b1233299e69eb7622e7b4fd1c35dcf Mon Sep 17 00:00:00 2001 From: Nicolas Souchu <nsouch@FreeBSD.org> Date: Sun, 13 Sep 1998 18:28:15 +0000 Subject: [PATCH] Lowlevel i/o routines for ZIP+ support. Mostly written with microseq mechanism --- sys/dev/ppbus/immio.c | 771 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 771 insertions(+) create mode 100644 sys/dev/ppbus/immio.c diff --git a/sys/dev/ppbus/immio.c b/sys/dev/ppbus/immio.c new file mode 100644 index 000000000000..47ac638903dd --- /dev/null +++ b/sys/dev/ppbus/immio.c @@ -0,0 +1,771 @@ +/*- + * 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. + * + * $Id$ + * + */ + +/* + * 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/malloc.h> +#include <sys/buf.h> + +#include <machine/clock.h> + +#endif /* KERNEL */ + +#ifdef KERNEL +#include <sys/kernel.h> +#endif /*KERNEL */ + +#include <dev/ppbus/ppbconf.h> +#include <dev/ppbus/ppb_msq.h> +#include <dev/ppbus/vpoio.h> +#include <dev/ppbus/ppb_1284.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 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, 3 /* _ready */), \ + MS_DBRA(-1 /* _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, 4 /* ready */), \ + MS_DBRA(-1 /* 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 \ +static 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, 6 /* 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) \ +} + +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); +} + +/* + * Macro used to initialize each vpoio_data structure during + * low level attachment + * + * XXX should be converted to ppb_MS_init_msq() + */ +#define INIT_NIBBLE_INBYTE_SUBMICROSEQ(vpo) { \ + (vpo)->vpo_nibble_inbyte_msq[6].arg[2].p = \ + (void *)&(vpo)->vpo_nibble.h; \ + (vpo)->vpo_nibble_inbyte_msq[3].arg[2].p = \ + (void *)&(vpo)->vpo_nibble.l; \ + (vpo)->vpo_nibble_inbyte_msq[9].arg[0].f = \ + nibble_inbyte_hook; \ + (vpo)->vpo_nibble_inbyte_msq[9].arg[1].p = \ + (void *)&(vpo)->vpo_nibble; \ +} + +/* + * 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()) + */ +static 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(-6 /* loop */), + MS_RET(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(-3 /* 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(1), /* decrement counter */ + MS_RASSERT_P(1, MS_REG_DTR), + MS_CASS(0x0), + MS_DBRA(-5 /* 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), + MS_CASS(0xc), + MS_RET(0), +}; + +static struct ppb_microseq epp17_instr[] = { + MS_CASS(PCD | 0x4), + MS_RFETCH_P(MS_ACCUM, MS_REG_EPP, MS_FETCH_ALL), + MS_CASS(PCD | 0xc), + MS_RET(0), +}; + +static int +imm_disconnect(struct vpoio_data *vpo, int *disconnected) +{ + DECLARE_CPP_MICROSEQ; + + char s1, s2, s3; + int ret; + + /* all should be ok */ + if (disconnected) + *disconnected = 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(&vpo->vpo_dev, cpp_microseq, &ret); + + if ((s1 != (char)0xb8 || s2 != (char)0x18 || s3 != (char)0x38) && + disconnected) + *disconnected = VP0_ECONNECT; + + return (ppb_release_bus(&vpo->vpo_dev)); +} + +/* + * how : PPB_WAIT or PPB_DONTWAIT + */ +static int +imm_connect(struct vpoio_data *vpo, int how, int *not_connected) +{ + DECLARE_CPP_MICROSEQ; + + char s1, s2, s3; + int error; + int ret; + + /* all should be ok */ + if (not_connected) + *not_connected = 0; + + if ((error = ppb_request_bus(&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(&vpo->vpo_dev, cpp_microseq, &ret); + + /* disconnect all devices */ + ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0x30); + ppb_MS_microseq(&vpo->vpo_dev, cpp_microseq, &ret); + + if (PPB_IN_EPP_MODE(&vpo->vpo_dev)) + ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0x28); + else + ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0xe0); + + ppb_MS_microseq(&vpo->vpo_dev, cpp_microseq, &ret); + + if ((s1 != (char)0xb8 || s2 != (char)0x18 || s3 != (char)0x30) + && not_connected) + *not_connected = VP0_ECONNECT; + + return (0); +} + +/* + * imm_detect() + * + * Detect and initialise the VP0 adapter. + */ +static int +imm_detect(struct vpoio_data *vpo) +{ + int error; + + imm_disconnect(vpo, NULL); + imm_connect(vpo, PPB_DONTWAIT, &error); + + if (error) + return (VP0_EINITFAILED); + + /* send SCSI reset signal */ + ppb_MS_microseq(&vpo->vpo_dev, reset_microseq, NULL); + + imm_disconnect(vpo, &error); + + /* ensure we are disconnected or daisy chained peripheral + * may cause serious problem to the disk */ + + if (error) + return (VP0_EINITFAILED); + + return (0); +} + +/* + * imm_outstr() + */ +static int +imm_outstr(struct vpoio_data *vpo, char *buffer, int size) +{ + int error = 0; + + if (PPB_IN_EPP_MODE(&vpo->vpo_dev)) + ppb_reset_epp_timeout(&vpo->vpo_dev); + + ppb_MS_exec(&vpo->vpo_dev, MS_OP_PUT, buffer, size, MS_UNKNOWN, &error); + + return (error); +} + +/* + * imm_instr() + */ +static int +imm_instr(struct vpoio_data *vpo, char *buffer, int size) +{ + int error = 0; + + if (PPB_IN_EPP_MODE(&vpo->vpo_dev)) + ppb_reset_epp_timeout(&vpo->vpo_dev); + + ppb_MS_exec(&vpo->vpo_dev, MS_OP_GET, buffer, size, MS_UNKNOWN, &error); + + return (error); +} + +static char +imm_select(struct vpoio_data *vpo, int initiator, int target) +{ + DECLARE_SELECT_MICROSEQUENCE; + int ret; + + /* initialize the select microsequence */ + ppb_MS_init_msq(select_microseq, 1, + SELECT_TARGET, 1 << initiator | 1 << target); + + ppb_MS_microseq(&vpo->vpo_dev, select_microseq, &ret); + + return (ret); +} + +/* + * imm_wait() + * + * H_SELIN must be low. + * + * XXX should be ported to microseq + */ +static char +imm_wait(struct vpoio_data *vpo, int tmo) +{ + + register int k; + register char r; + + ppb_wctr(&vpo->vpo_dev, 0xc); + + /* XXX should be ported to microseq */ + k = 0; + while (!((r = ppb_rstr(&vpo->vpo_dev)) & 0x80) && (k++ < tmo)) + DELAY(1); + + /* + * 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_wctr(&vpo->vpo_dev, 0x4); + if (k < tmo) + return (r & 0xb8); + + return (0); /* command timed out */ +} + +static int +imm_negociate(struct vpoio_data *vpo) +{ + DECLARE_NEGOCIATE_MICROSEQ; + int negociate_mode; + int ret; + + if (PPB_IN_NIBBLE_MODE(&vpo->vpo_dev)) + negociate_mode = 0; + else if (PPB_IN_PS2_MODE(&vpo->vpo_dev)) + negociate_mode = 1; + else + return (0); + +#if 0 /* XXX use standalone code not to depend on ppb_1284 code yet */ + ret = ppb_1284_negociate(&vpo->vpo_dev, negociate_mode); + + if (ret) + return (VP0_ENEGOCIATE); +#endif + + ppb_MS_init_msq(negociate_microseq, 1, NEGOCIATED_MODE, negociate_mode); + + ppb_MS_microseq(&vpo->vpo_dev, negociate_microseq, &ret); + + return (ret); +} + +/* + * imm_probe() + * + * Low level probe of vpo device + * + */ +struct ppb_device * +imm_probe(struct ppb_data *ppb, struct vpoio_data *vpo) +{ + + /* ppbus dependent initialisation */ + vpo->vpo_dev.id_unit = vpo->vpo_unit; + vpo->vpo_dev.name = "vpo"; + vpo->vpo_dev.ppb = ppb; + + /* now, try to initialise the drive */ + if (imm_detect(vpo)) { + return (NULL); + } + + return (&vpo->vpo_dev); +} + +/* + * imm_attach() + * + * Low level attachment of vpo device + * + */ +int +imm_attach(struct vpoio_data *vpo) +{ + int epp; + + /* + * Report ourselves + */ + printf("imm%d: <Iomega Matchmaker Parallel to SCSI interface> on ppbus %d\n", + vpo->vpo_dev.id_unit, vpo->vpo_dev.ppb->ppb_link->adapter_unit); + + /* + * 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 (0); + + bcopy((void *)nibble_inbyte_submicroseq, + (void *)vpo->vpo_nibble_inbyte_msq, + sizeof(nibble_inbyte_submicroseq)); + + INIT_NIBBLE_INBYTE_SUBMICROSEQ(vpo); + + /* + * Initialize mode dependent in/out microsequences + */ + ppb_request_bus(&vpo->vpo_dev, PPB_WAIT); + + /* enter NIBBLE mode to configure submsq */ + if (ppb_set_mode(&vpo->vpo_dev, PPB_NIBBLE) != -1) { + + ppb_MS_GET_init(&vpo->vpo_dev, vpo->vpo_nibble_inbyte_msq); + ppb_MS_PUT_init(&vpo->vpo_dev, spp_outbyte_submicroseq); + } + + /* enter PS2 mode to configure submsq */ + if (ppb_set_mode(&vpo->vpo_dev, PPB_PS2) != -1) { + + ppb_MS_GET_init(&vpo->vpo_dev, ps2_inbyte_submicroseq); + ppb_MS_PUT_init(&vpo->vpo_dev, spp_outbyte_submicroseq); + } + + epp = ppb_get_epp_protocol(&vpo->vpo_dev); + + /* enter EPP mode to configure submsq */ + if (ppb_set_mode(&vpo->vpo_dev, PPB_EPP) != -1) { + + switch (epp) { + case EPP_1_9: + case EPP_1_7: + ppb_MS_GET_init(&vpo->vpo_dev, epp17_instr); + ppb_MS_PUT_init(&vpo->vpo_dev, epp17_outstr); + break; + default: + panic("%s: unknown EPP protocol (0x%x)", __FUNCTION__, + epp); + } + } + + /* try to enter EPP or PS/2 mode, NIBBLE otherwise */ + if (ppb_set_mode(&vpo->vpo_dev, PPB_EPP) != -1) { + switch (epp) { + case EPP_1_9: + printf("imm%d: EPP 1.9 mode\n", vpo->vpo_unit); + break; + case EPP_1_7: + printf("imm%d: EPP 1.7 mode\n", vpo->vpo_unit); + break; + default: + panic("%s: unknown EPP protocol (0x%x)", __FUNCTION__, + epp); + } + } else if (ppb_set_mode(&vpo->vpo_dev, PPB_PS2) != -1) + printf("imm%d: PS2 mode\n", vpo->vpo_unit); + + else if (ppb_set_mode(&vpo->vpo_dev, PPB_NIBBLE) != -1) + printf("imm%d: NIBBLE mode\n", vpo->vpo_unit); + + else { + printf("imm%d: can't enter NIBBLE, PS2 or EPP mode\n", + vpo->vpo_unit); + + ppb_release_bus(&vpo->vpo_dev); + + free(vpo->vpo_nibble_inbyte_msq, M_DEVBUF); + return (0); + } + + ppb_release_bus(&vpo->vpo_dev); + + return (1); +} + +/* + * imm_reset_bus() + * + */ +int +imm_reset_bus(struct vpoio_data *vpo) +{ + int not_connected; + + /* first, connect to the drive */ + imm_connect(vpo, PPB_WAIT|PPB_INTR, ¬_connected); + + if (!not_connected) { + + /* reset the SCSI bus */ + ppb_MS_microseq(&vpo->vpo_dev, reset_microseq, NULL); + + /* then disconnect */ + imm_disconnect(vpo, NULL); + } + + 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) +{ + + 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 do upper SCSI code :^( + */ + if ((error = imm_connect(vpo, PPB_WAIT|PPB_INTR, ¬_connected))) + 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(&vpo->vpo_dev)) + 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(&vpo->vpo_dev) || + PPB_IN_PS2_MODE(&vpo->vpo_dev)) && negociated) + ppb_MS_microseq(&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; + } + + *result = ((int) h << 8) | ((int) l & 0xff); + +error: + if ((PPB_IN_NIBBLE_MODE(&vpo->vpo_dev) || + PPB_IN_PS2_MODE(&vpo->vpo_dev)) && negociated) + ppb_MS_microseq(&vpo->vpo_dev, transfer_epilog, NULL); + + /* return to printer state, release the ppbus */ + imm_disconnect(vpo, NULL); + + return (0); +}