immio.c revision 39136
16841Swpaul/*-
280029Sobrien * Copyright (c) 1998 Nicolas Souchu
36841Swpaul * All rights reserved.
46841Swpaul *
5201390Sed * Redistribution and use in source and binary forms, with or without
6201390Sed * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 *	$Id$
27 *
28 */
29
30/*
31 * Iomega ZIP+ Matchmaker Parallel Port Interface driver
32 *
33 * Thanks to David Campbell work on the Linux driver and the Iomega specs
34 * Thanks to Thiebault Moeglin for the drive
35 */
36#ifdef KERNEL
37#include <sys/param.h>
38#include <sys/systm.h>
39#include <sys/malloc.h>
40#include <sys/buf.h>
41
42#include <machine/clock.h>
43
44#endif	/* KERNEL */
45
46#ifdef	KERNEL
47#include <sys/kernel.h>
48#endif /*KERNEL */
49
50#include <dev/ppbus/ppbconf.h>
51#include <dev/ppbus/ppb_msq.h>
52#include <dev/ppbus/vpoio.h>
53#include <dev/ppbus/ppb_1284.h>
54
55#define VP0_SELTMO		5000	/* select timeout */
56#define VP0_FAST_SPINTMO	500000	/* wait status timeout */
57#define VP0_LOW_SPINTMO		5000000	/* wait status timeout */
58
59#define VP0_SECTOR_SIZE	512
60
61/*
62 * Microcode to execute very fast I/O sequences at the lowest bus level.
63 */
64
65#define SELECT_TARGET		MS_PARAM(6, 1, MS_TYP_CHA)
66
67#define DECLARE_SELECT_MICROSEQUENCE					\
68struct ppb_microseq select_microseq[] = {				\
69	MS_CASS(0xc),							\
70	/* first, check there is nothing holding onto the bus */	\
71	MS_SET(VP0_SELTMO),						\
72/* _loop: */								\
73	MS_BRCLEAR(0x8, 3 /* _ready */),				\
74	MS_DBRA(-1 /* _loop */),					\
75	MS_RET(2),			/* bus busy */			\
76/* _ready: */								\
77	MS_CASS(0x4),							\
78	MS_DASS(MS_UNKNOWN /* 0x80 | 1 << target */),			\
79	MS_DELAY(1),							\
80	MS_CASS(0xc),							\
81	MS_CASS(0xd),							\
82	/* now, wait until the drive is ready */			\
83	MS_SET(VP0_SELTMO),						\
84/* loop: */								\
85	MS_BRSET(0x8, 4 /* ready */),					\
86	MS_DBRA(-1 /* loop */),						\
87/* error: */								\
88	MS_CASS(0xc),							\
89	MS_RET(VP0_ESELECT_TIMEOUT),					\
90/* ready: */								\
91	MS_CASS(0xc),							\
92	MS_RET(0)							\
93}
94
95static struct ppb_microseq transfer_epilog[] = {
96	MS_CASS(0x4),
97	MS_CASS(0xc),
98	MS_CASS(0xe),
99	MS_CASS(0x4),
100	MS_RET(0)
101};
102
103#define CPP_S1		MS_PARAM(10, 2, MS_TYP_PTR)
104#define CPP_S2		MS_PARAM(13, 2, MS_TYP_PTR)
105#define CPP_S3		MS_PARAM(16, 2, MS_TYP_PTR)
106#define CPP_PARAM	MS_PARAM(17, 1, MS_TYP_CHA)
107
108#define DECLARE_CPP_MICROSEQ \
109struct ppb_microseq cpp_microseq[] = {					\
110	MS_CASS(0x0c), MS_DELAY(2),					\
111	MS_DASS(0xaa), MS_DELAY(10),					\
112	MS_DASS(0x55), MS_DELAY(10),					\
113	MS_DASS(0x00), MS_DELAY(10),					\
114	MS_DASS(0xff), MS_DELAY(10),					\
115	MS_RFETCH(MS_REG_STR, 0xb8, MS_UNKNOWN /* &s1 */),		\
116	MS_DASS(0x87), MS_DELAY(10),					\
117	MS_RFETCH(MS_REG_STR, 0xb8, MS_UNKNOWN /* &s2 */),		\
118	MS_DASS(0x78), MS_DELAY(10),					\
119	MS_RFETCH(MS_REG_STR, 0x38, MS_UNKNOWN /* &s3 */),		\
120	MS_DASS(MS_UNKNOWN /* param */),				\
121	MS_DELAY(2),							\
122	MS_CASS(0x0c), MS_DELAY(10),					\
123	MS_CASS(0x0d), MS_DELAY(2),					\
124	MS_CASS(0x0c), MS_DELAY(10),					\
125	MS_DASS(0xff), MS_DELAY(10),					\
126	MS_RET(0)							\
127}
128
129#define NEGOCIATED_MODE		MS_PARAM(2, 1, MS_TYP_CHA)
130
131#define DECLARE_NEGOCIATE_MICROSEQ \
132static struct ppb_microseq negociate_microseq[] = { 			\
133	MS_CASS(0x4),							\
134	MS_DELAY(5),							\
135	MS_DASS(MS_UNKNOWN /* mode */),					\
136	MS_DELAY(100),							\
137	MS_CASS(0x6),							\
138	MS_DELAY(5),							\
139	MS_BRSET(0x20, 6 /* continue */),				\
140	MS_DELAY(5),							\
141	MS_CASS(0x7),							\
142	MS_DELAY(5),							\
143	MS_CASS(0x6),							\
144	MS_RET(VP0_ENEGOCIATE),						\
145/* continue: */								\
146	MS_DELAY(5),							\
147	MS_CASS(0x7),							\
148	MS_DELAY(5),							\
149	MS_CASS(0x6),							\
150	MS_RET(0)							\
151}
152
153static struct ppb_microseq reset_microseq[] = {
154	MS_CASS(0x04),
155	MS_DASS(0x40),
156	MS_DELAY(1),
157	MS_CASS(0x0c),
158	MS_CASS(0x0d),
159	MS_DELAY(50),
160	MS_CASS(0x0c),
161	MS_CASS(0x04),
162	MS_RET(0)
163};
164
165/*
166 * nibble_inbyte_hook()
167 *
168 * Formats high and low nibble into a character
169 */
170static int
171nibble_inbyte_hook (void *p, char *ptr)
172{
173	struct vpo_nibble *s = (struct vpo_nibble *)p;
174
175	/* increment the buffer pointer */
176	*ptr = ((s->l >> 4) & 0x0f) + (s->h & 0xf0);
177
178	return (0);
179}
180
181/*
182 * Macro used to initialize each vpoio_data structure during
183 * low level attachment
184 *
185 * XXX should be converted to ppb_MS_init_msq()
186 */
187#define INIT_NIBBLE_INBYTE_SUBMICROSEQ(vpo) {		    	\
188	(vpo)->vpo_nibble_inbyte_msq[6].arg[2].p =		\
189			(void *)&(vpo)->vpo_nibble.h;		\
190	(vpo)->vpo_nibble_inbyte_msq[3].arg[2].p =		\
191			(void *)&(vpo)->vpo_nibble.l;		\
192	(vpo)->vpo_nibble_inbyte_msq[9].arg[0].f =		\
193			nibble_inbyte_hook;			\
194	(vpo)->vpo_nibble_inbyte_msq[9].arg[1].p =		\
195			(void *)&(vpo)->vpo_nibble;		\
196}
197
198/*
199 * This is the sub-microseqence for MS_GET in NIBBLE mode
200 * Retrieve the two nibbles and call the C function to generate the character
201 * and store it in the buffer (see nibble_inbyte_hook())
202 */
203static struct ppb_microseq nibble_inbyte_submicroseq[] = {
204	  MS_CASS(0x4),
205
206/* loop: */
207	  MS_CASS(0x6),
208	  MS_DELAY(1),
209	  MS_RFETCH(MS_REG_STR, MS_FETCH_ALL, MS_UNKNOWN /* low nibble */),
210	  MS_CASS(0x5),
211	  MS_DELAY(1),
212	  MS_RFETCH(MS_REG_STR, MS_FETCH_ALL, MS_UNKNOWN /* high nibble */),
213	  MS_CASS(0x4),
214	  MS_DELAY(1),
215
216	  /* do a C call to format the received nibbles */
217	  MS_C_CALL(MS_UNKNOWN /* C hook */, MS_UNKNOWN /* param */),
218	  MS_DBRA(-6 /* loop */),
219	  MS_RET(0)
220};
221
222/*
223 * This is the sub-microseqence for MS_GET in PS2 mode
224 */
225static struct ppb_microseq ps2_inbyte_submicroseq[] = {
226	  MS_CASS(0x4),
227
228/* loop: */
229	  MS_CASS(PCD | 0x6),
230	  MS_RFETCH_P(1, MS_REG_DTR, MS_FETCH_ALL),
231	  MS_CASS(PCD | 0x5),
232	  MS_DBRA(-3 /* loop */),
233
234	  MS_RET(0)
235};
236
237/*
238 * This is the sub-microsequence for MS_PUT in both NIBBLE and PS2 modes
239 */
240static struct ppb_microseq spp_outbyte_submicroseq[] = {
241	  MS_CASS(0x4),
242
243/* loop: */
244	  MS_RASSERT_P(1, MS_REG_DTR),
245	  MS_CASS(0x5),
246	  MS_DBRA(1),				/* decrement counter */
247	  MS_RASSERT_P(1, MS_REG_DTR),
248	  MS_CASS(0x0),
249	  MS_DBRA(-5 /* loop */),
250
251	  /* return from the put call */
252	  MS_CASS(0x4),
253	  MS_RET(0)
254};
255
256/* EPP 1.7 microsequences, ptr and len set at runtime */
257static struct ppb_microseq epp17_outstr[] = {
258	  MS_CASS(0x4),
259	  MS_RASSERT_P(MS_ACCUM, MS_REG_EPP),
260	  MS_CASS(0xc),
261	  MS_RET(0),
262};
263
264static struct ppb_microseq epp17_instr[] = {
265	  MS_CASS(PCD | 0x4),
266	  MS_RFETCH_P(MS_ACCUM, MS_REG_EPP, MS_FETCH_ALL),
267	  MS_CASS(PCD | 0xc),
268	  MS_RET(0),
269};
270
271static int
272imm_disconnect(struct vpoio_data *vpo, int *disconnected)
273{
274	DECLARE_CPP_MICROSEQ;
275
276	char s1, s2, s3;
277	int ret;
278
279	/* all should be ok */
280	if (disconnected)
281		*disconnected = 0;
282
283	ppb_MS_init_msq(cpp_microseq, 4, CPP_S1, (void *)&s1,
284			CPP_S2, (void *)&s2, CPP_S3, (void *)&s3,
285			CPP_PARAM, 0x30);
286
287	ppb_MS_microseq(&vpo->vpo_dev, cpp_microseq, &ret);
288
289	if ((s1 != (char)0xb8 || s2 != (char)0x18 || s3 != (char)0x38) &&
290								disconnected)
291		*disconnected = VP0_ECONNECT;
292
293	return (ppb_release_bus(&vpo->vpo_dev));
294}
295
296/*
297 * how	: PPB_WAIT or PPB_DONTWAIT
298 */
299static int
300imm_connect(struct vpoio_data *vpo, int how, int *not_connected)
301{
302	DECLARE_CPP_MICROSEQ;
303
304	char s1, s2, s3;
305	int error;
306	int ret;
307
308	/* all should be ok */
309	if (not_connected)
310		*not_connected = 0;
311
312	if ((error = ppb_request_bus(&vpo->vpo_dev, how)))
313		return (error);
314
315	ppb_MS_init_msq(cpp_microseq, 3, CPP_S1, (void *)&s1,
316			CPP_S2, (void *)&s2, CPP_S3, (void *)&s3);
317
318	/* select device 0 in compatible mode */
319	ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0xe0);
320	ppb_MS_microseq(&vpo->vpo_dev, cpp_microseq, &ret);
321
322	/* disconnect all devices */
323	ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0x30);
324	ppb_MS_microseq(&vpo->vpo_dev, cpp_microseq, &ret);
325
326	if (PPB_IN_EPP_MODE(&vpo->vpo_dev))
327		ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0x28);
328	else
329		ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0xe0);
330
331	ppb_MS_microseq(&vpo->vpo_dev, cpp_microseq, &ret);
332
333	if ((s1 != (char)0xb8 || s2 != (char)0x18 || s3 != (char)0x30)
334							&& not_connected)
335		*not_connected = VP0_ECONNECT;
336
337	return (0);
338}
339
340/*
341 * imm_detect()
342 *
343 * Detect and initialise the VP0 adapter.
344 */
345static int
346imm_detect(struct vpoio_data *vpo)
347{
348	int error;
349
350	imm_disconnect(vpo, NULL);
351	imm_connect(vpo, PPB_DONTWAIT, &error);
352
353	if (error)
354		return (VP0_EINITFAILED);
355
356	/* send SCSI reset signal */
357	ppb_MS_microseq(&vpo->vpo_dev, reset_microseq, NULL);
358
359	imm_disconnect(vpo, &error);
360
361	/* ensure we are disconnected or daisy chained peripheral
362	 * may cause serious problem to the disk */
363
364	if (error)
365		return (VP0_EINITFAILED);
366
367	return (0);
368}
369
370/*
371 * imm_outstr()
372 */
373static int
374imm_outstr(struct vpoio_data *vpo, char *buffer, int size)
375{
376	int error = 0;
377
378	if (PPB_IN_EPP_MODE(&vpo->vpo_dev))
379		ppb_reset_epp_timeout(&vpo->vpo_dev);
380
381	ppb_MS_exec(&vpo->vpo_dev, MS_OP_PUT, buffer, size, MS_UNKNOWN, &error);
382
383	return (error);
384}
385
386/*
387 * imm_instr()
388 */
389static int
390imm_instr(struct vpoio_data *vpo, char *buffer, int size)
391{
392	int error = 0;
393
394	if (PPB_IN_EPP_MODE(&vpo->vpo_dev))
395		ppb_reset_epp_timeout(&vpo->vpo_dev);
396
397	ppb_MS_exec(&vpo->vpo_dev, MS_OP_GET, buffer, size, MS_UNKNOWN, &error);
398
399	return (error);
400}
401
402static char
403imm_select(struct vpoio_data *vpo, int initiator, int target)
404{
405	DECLARE_SELECT_MICROSEQUENCE;
406	int ret;
407
408	/* initialize the select microsequence */
409	ppb_MS_init_msq(select_microseq, 1,
410			SELECT_TARGET, 1 << initiator | 1 << target);
411
412	ppb_MS_microseq(&vpo->vpo_dev, select_microseq, &ret);
413
414	return (ret);
415}
416
417/*
418 * imm_wait()
419 *
420 * H_SELIN must be low.
421 *
422 * XXX should be ported to microseq
423 */
424static char
425imm_wait(struct vpoio_data *vpo, int tmo)
426{
427
428	register int	k;
429	register char	r;
430
431	ppb_wctr(&vpo->vpo_dev, 0xc);
432
433	/* XXX should be ported to microseq */
434	k = 0;
435	while (!((r = ppb_rstr(&vpo->vpo_dev)) & 0x80) && (k++ < tmo))
436		DELAY(1);
437
438	/*
439	 * Return some status information.
440	 * Semantics :	0x88 = ZIP+ wants more data
441	 *		0x98 = ZIP+ wants to send more data
442	 *		0xa8 = ZIP+ wants command
443	 *		0xb8 = end of transfer, ZIP+ is sending status
444	 */
445	ppb_wctr(&vpo->vpo_dev, 0x4);
446	if (k < tmo)
447	  return (r & 0xb8);
448
449	return (0);			   /* command timed out */
450}
451
452static int
453imm_negociate(struct vpoio_data *vpo)
454{
455	DECLARE_NEGOCIATE_MICROSEQ;
456	int negociate_mode;
457	int ret;
458
459	if (PPB_IN_NIBBLE_MODE(&vpo->vpo_dev))
460		negociate_mode = 0;
461	else if (PPB_IN_PS2_MODE(&vpo->vpo_dev))
462		negociate_mode = 1;
463	else
464		return (0);
465
466#if 0 /* XXX use standalone code not to depend on ppb_1284 code yet */
467	ret = ppb_1284_negociate(&vpo->vpo_dev, negociate_mode);
468
469	if (ret)
470		return (VP0_ENEGOCIATE);
471#endif
472
473	ppb_MS_init_msq(negociate_microseq, 1, NEGOCIATED_MODE, negociate_mode);
474
475	ppb_MS_microseq(&vpo->vpo_dev, negociate_microseq, &ret);
476
477	return (ret);
478}
479
480/*
481 * imm_probe()
482 *
483 * Low level probe of vpo device
484 *
485 */
486struct ppb_device *
487imm_probe(struct ppb_data *ppb, struct vpoio_data *vpo)
488{
489
490	/* ppbus dependent initialisation */
491	vpo->vpo_dev.id_unit = vpo->vpo_unit;
492	vpo->vpo_dev.name = "vpo";
493	vpo->vpo_dev.ppb = ppb;
494
495	/* now, try to initialise the drive */
496	if (imm_detect(vpo)) {
497		return (NULL);
498	}
499
500	return (&vpo->vpo_dev);
501}
502
503/*
504 * imm_attach()
505 *
506 * Low level attachment of vpo device
507 *
508 */
509int
510imm_attach(struct vpoio_data *vpo)
511{
512	int epp;
513
514	/*
515	 * Report ourselves
516	 */
517	printf("imm%d: <Iomega Matchmaker Parallel to SCSI interface> on ppbus %d\n",
518		vpo->vpo_dev.id_unit, vpo->vpo_dev.ppb->ppb_link->adapter_unit);
519
520	/*
521	 * Initialize microsequence code
522	 */
523	vpo->vpo_nibble_inbyte_msq = (struct ppb_microseq *)malloc(
524		sizeof(nibble_inbyte_submicroseq), M_DEVBUF, M_NOWAIT);
525
526	if (!vpo->vpo_nibble_inbyte_msq)
527		return (0);
528
529	bcopy((void *)nibble_inbyte_submicroseq,
530		(void *)vpo->vpo_nibble_inbyte_msq,
531		sizeof(nibble_inbyte_submicroseq));
532
533	INIT_NIBBLE_INBYTE_SUBMICROSEQ(vpo);
534
535	/*
536	 * Initialize mode dependent in/out microsequences
537	 */
538	ppb_request_bus(&vpo->vpo_dev, PPB_WAIT);
539
540	/* enter NIBBLE mode to configure submsq */
541	if (ppb_set_mode(&vpo->vpo_dev, PPB_NIBBLE) != -1) {
542
543		ppb_MS_GET_init(&vpo->vpo_dev, vpo->vpo_nibble_inbyte_msq);
544		ppb_MS_PUT_init(&vpo->vpo_dev, spp_outbyte_submicroseq);
545	}
546
547	/* enter PS2 mode to configure submsq */
548	if (ppb_set_mode(&vpo->vpo_dev, PPB_PS2) != -1) {
549
550		ppb_MS_GET_init(&vpo->vpo_dev, ps2_inbyte_submicroseq);
551		ppb_MS_PUT_init(&vpo->vpo_dev, spp_outbyte_submicroseq);
552	}
553
554	epp = ppb_get_epp_protocol(&vpo->vpo_dev);
555
556	/* enter EPP mode to configure submsq */
557	if (ppb_set_mode(&vpo->vpo_dev, PPB_EPP) != -1) {
558
559		switch (epp) {
560		case EPP_1_9:
561		case EPP_1_7:
562			ppb_MS_GET_init(&vpo->vpo_dev, epp17_instr);
563			ppb_MS_PUT_init(&vpo->vpo_dev, epp17_outstr);
564			break;
565		default:
566			panic("%s: unknown EPP protocol (0x%x)", __FUNCTION__,
567				epp);
568		}
569	}
570
571	/* try to enter EPP or PS/2 mode, NIBBLE otherwise */
572	if (ppb_set_mode(&vpo->vpo_dev, PPB_EPP) != -1) {
573		switch (epp) {
574		case EPP_1_9:
575			printf("imm%d: EPP 1.9 mode\n", vpo->vpo_unit);
576			break;
577		case EPP_1_7:
578			printf("imm%d: EPP 1.7 mode\n", vpo->vpo_unit);
579			break;
580		default:
581			panic("%s: unknown EPP protocol (0x%x)", __FUNCTION__,
582				epp);
583		}
584	} else if (ppb_set_mode(&vpo->vpo_dev, PPB_PS2) != -1)
585		printf("imm%d: PS2 mode\n", vpo->vpo_unit);
586
587	else if (ppb_set_mode(&vpo->vpo_dev, PPB_NIBBLE) != -1)
588		printf("imm%d: NIBBLE mode\n", vpo->vpo_unit);
589
590	else {
591		printf("imm%d: can't enter NIBBLE, PS2 or EPP mode\n",
592			vpo->vpo_unit);
593
594		ppb_release_bus(&vpo->vpo_dev);
595
596		free(vpo->vpo_nibble_inbyte_msq, M_DEVBUF);
597		return (0);
598	}
599
600	ppb_release_bus(&vpo->vpo_dev);
601
602	return (1);
603}
604
605/*
606 * imm_reset_bus()
607 *
608 */
609int
610imm_reset_bus(struct vpoio_data *vpo)
611{
612	int not_connected;
613
614	/* first, connect to the drive */
615	imm_connect(vpo, PPB_WAIT|PPB_INTR, &not_connected);
616
617	if (!not_connected) {
618
619		/* reset the SCSI bus */
620		ppb_MS_microseq(&vpo->vpo_dev, reset_microseq, NULL);
621
622		/* then disconnect */
623		imm_disconnect(vpo, NULL);
624	}
625
626	return (0);
627}
628
629/*
630 * imm_do_scsi()
631 *
632 * Send an SCSI command
633 *
634 */
635int
636imm_do_scsi(struct vpoio_data *vpo, int host, int target, char *command,
637		int clen, char *buffer, int blen, int *result, int *count,
638		int *ret)
639{
640
641	register char r;
642	char l, h = 0;
643	int len, error = 0, not_connected = 0;
644	register int k;
645	int negociated = 0;
646
647	/*
648	 * enter disk state, allocate the ppbus
649	 *
650	 * XXX
651	 * Should we allow this call to be interruptible?
652	 * The only way to report the interruption is to return
653	 * EIO do upper SCSI code :^(
654	 */
655	if ((error = imm_connect(vpo, PPB_WAIT|PPB_INTR, &not_connected)))
656		return (error);
657
658	if (not_connected) {
659		*ret = VP0_ECONNECT; goto error;
660	}
661
662	/*
663	 * Select the drive ...
664	 */
665	if ((*ret = imm_select(vpo,host,target)))
666		goto error;
667
668	/*
669	 * Send the command ...
670	 */
671	for (k = 0; k < clen; k+=2) {
672		if (imm_wait(vpo, VP0_FAST_SPINTMO) != (char)0xa8) {
673			*ret = VP0_ECMD_TIMEOUT;
674			goto error;
675		}
676		if (imm_outstr(vpo, &command[k], 2)) {
677			*ret = VP0_EPPDATA_TIMEOUT;
678			goto error;
679		}
680	}
681
682	if (!(r = imm_wait(vpo, VP0_LOW_SPINTMO))) {
683		*ret = VP0_ESTATUS_TIMEOUT; goto error;
684	}
685
686	if ((r & 0x30) == 0x10) {
687		if (imm_negociate(vpo)) {
688			*ret = VP0_ENEGOCIATE;
689			goto error;
690		} else
691			negociated = 1;
692	}
693
694	/*
695	 * Complete transfer ...
696	 */
697	*count = 0;
698	for (;;) {
699
700		if (!(r = imm_wait(vpo, VP0_LOW_SPINTMO))) {
701			*ret = VP0_ESTATUS_TIMEOUT; goto error;
702		}
703
704		/* stop when the ZIP+ wants to send status */
705		if (r == (char)0xb8)
706			break;
707
708		if (*count >= blen) {
709			*ret = VP0_EDATA_OVERFLOW;
710			goto error;
711		}
712
713		/* ZIP+ wants to send data? */
714		if (r == (char)0x88) {
715			len = (((blen - *count) >= VP0_SECTOR_SIZE)) ?
716				VP0_SECTOR_SIZE : 2;
717
718			error = imm_outstr(vpo, &buffer[*count], len);
719		} else {
720			if (!PPB_IN_EPP_MODE(&vpo->vpo_dev))
721				len = 1;
722			else
723				len = (((blen - *count) >= VP0_SECTOR_SIZE)) ?
724					VP0_SECTOR_SIZE : 1;
725
726			error = imm_instr(vpo, &buffer[*count], len);
727		}
728
729		if (error) {
730			*ret = error;
731			goto error;
732		}
733
734		*count += len;
735	}
736
737	if ((PPB_IN_NIBBLE_MODE(&vpo->vpo_dev) ||
738			PPB_IN_PS2_MODE(&vpo->vpo_dev)) && negociated)
739		ppb_MS_microseq(&vpo->vpo_dev, transfer_epilog, NULL);
740
741	/*
742	 * Retrieve status ...
743	 */
744	if (imm_negociate(vpo)) {
745		*ret = VP0_ENEGOCIATE;
746		goto error;
747	} else
748		negociated = 1;
749
750	if (imm_instr(vpo, &l, 1)) {
751		*ret = VP0_EOTHER; goto error;
752	}
753
754	/* check if the ZIP+ wants to send more status */
755	if (imm_wait(vpo, VP0_FAST_SPINTMO) == (char)0xb8)
756		if (imm_instr(vpo, &h, 1)) {
757			*ret = VP0_EOTHER+2; goto error;
758		}
759
760	*result = ((int) h << 8) | ((int) l & 0xff);
761
762error:
763	if ((PPB_IN_NIBBLE_MODE(&vpo->vpo_dev) ||
764			PPB_IN_PS2_MODE(&vpo->vpo_dev)) && negociated)
765		ppb_MS_microseq(&vpo->vpo_dev, transfer_epilog, NULL);
766
767	/* return to printer state, release the ppbus */
768	imm_disconnect(vpo, NULL);
769
770	return (0);
771}
772