immio.c revision 39520
1/*-
2 * Copyright (c) 1998 Nicolas Souchu
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * 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: immio.c,v 1.1 1998/09/13 18:28:15 nsouch Exp $
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 *connected, int release_bus)
273{
274	DECLARE_CPP_MICROSEQ;
275
276	char s1, s2, s3;
277	int ret;
278
279	/* all should be ok */
280	if (connected)
281		*connected = 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								connected)
291		*connected = VP0_ECONNECT;
292
293	if (release_bus)
294		return (ppb_release_bus(&vpo->vpo_dev));
295	else
296		return (0);
297}
298
299/*
300 * how	: PPB_WAIT or PPB_DONTWAIT
301 */
302static int
303imm_connect(struct vpoio_data *vpo, int how, int *disconnected, int request_bus)
304{
305	DECLARE_CPP_MICROSEQ;
306
307	char s1, s2, s3;
308	int error;
309	int ret;
310
311	/* all should be ok */
312	if (disconnected)
313		*disconnected = 0;
314
315	if (request_bus)
316		if ((error = ppb_request_bus(&vpo->vpo_dev, how)))
317			return (error);
318
319	ppb_MS_init_msq(cpp_microseq, 3, CPP_S1, (void *)&s1,
320			CPP_S2, (void *)&s2, CPP_S3, (void *)&s3);
321
322	/* select device 0 in compatible mode */
323	ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0xe0);
324	ppb_MS_microseq(&vpo->vpo_dev, cpp_microseq, &ret);
325
326	/* disconnect all devices */
327	ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0x30);
328	ppb_MS_microseq(&vpo->vpo_dev, cpp_microseq, &ret);
329
330	if (PPB_IN_EPP_MODE(&vpo->vpo_dev))
331		ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0x28);
332	else
333		ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0xe0);
334
335	ppb_MS_microseq(&vpo->vpo_dev, cpp_microseq, &ret);
336
337	if ((s1 != (char)0xb8 || s2 != (char)0x18 || s3 != (char)0x30)
338							&& disconnected)
339		*disconnected = VP0_ECONNECT;
340
341	return (0);
342}
343
344/*
345 * imm_detect()
346 *
347 * Detect and initialise the VP0 adapter.
348 */
349static int
350imm_detect(struct vpoio_data *vpo)
351{
352	int error;
353
354	if ((error = ppb_request_bus(&vpo->vpo_dev, PPB_DONTWAIT)))
355		return (error);
356
357	/* disconnect the drive, keep the bus */
358	imm_disconnect(vpo, NULL, 0);
359
360	/* we already have the bus, just connect */
361	imm_connect(vpo, PPB_DONTWAIT, &error, 0);
362
363	if (error) {
364		if (bootverbose)
365			printf("imm%d: can't connect to the drive\n",
366				vpo->vpo_unit);
367		goto error;
368	}
369
370	/* send SCSI reset signal */
371	ppb_MS_microseq(&vpo->vpo_dev, reset_microseq, NULL);
372
373	/* release the bus now */
374	imm_disconnect(vpo, &error, 1);
375
376	/* ensure we are disconnected or daisy chained peripheral
377	 * may cause serious problem to the disk */
378
379	if (error) {
380		if (bootverbose)
381			printf("imm%d: can't disconnect from the drive\n",
382				vpo->vpo_unit);
383	}
384
385	return (0);
386
387error:
388	ppb_release_bus(&vpo->vpo_dev);
389	return (VP0_EINITFAILED);
390}
391
392/*
393 * imm_outstr()
394 */
395static int
396imm_outstr(struct vpoio_data *vpo, char *buffer, int size)
397{
398	int error = 0;
399
400	if (PPB_IN_EPP_MODE(&vpo->vpo_dev))
401		ppb_reset_epp_timeout(&vpo->vpo_dev);
402
403	ppb_MS_exec(&vpo->vpo_dev, MS_OP_PUT, buffer, size, MS_UNKNOWN, &error);
404
405	return (error);
406}
407
408/*
409 * imm_instr()
410 */
411static int
412imm_instr(struct vpoio_data *vpo, char *buffer, int size)
413{
414	int error = 0;
415
416	if (PPB_IN_EPP_MODE(&vpo->vpo_dev))
417		ppb_reset_epp_timeout(&vpo->vpo_dev);
418
419	ppb_MS_exec(&vpo->vpo_dev, MS_OP_GET, buffer, size, MS_UNKNOWN, &error);
420
421	return (error);
422}
423
424static char
425imm_select(struct vpoio_data *vpo, int initiator, int target)
426{
427	DECLARE_SELECT_MICROSEQUENCE;
428	int ret;
429
430	/* initialize the select microsequence */
431	ppb_MS_init_msq(select_microseq, 1,
432			SELECT_TARGET, 1 << initiator | 1 << target);
433
434	ppb_MS_microseq(&vpo->vpo_dev, select_microseq, &ret);
435
436	return (ret);
437}
438
439/*
440 * imm_wait()
441 *
442 * H_SELIN must be low.
443 *
444 * XXX should be ported to microseq
445 */
446static char
447imm_wait(struct vpoio_data *vpo, int tmo)
448{
449
450	register int	k;
451	register char	r;
452
453	ppb_wctr(&vpo->vpo_dev, 0xc);
454
455	/* XXX should be ported to microseq */
456	k = 0;
457	while (!((r = ppb_rstr(&vpo->vpo_dev)) & 0x80) && (k++ < tmo))
458		DELAY(1);
459
460	/*
461	 * Return some status information.
462	 * Semantics :	0x88 = ZIP+ wants more data
463	 *		0x98 = ZIP+ wants to send more data
464	 *		0xa8 = ZIP+ wants command
465	 *		0xb8 = end of transfer, ZIP+ is sending status
466	 */
467	ppb_wctr(&vpo->vpo_dev, 0x4);
468	if (k < tmo)
469	  return (r & 0xb8);
470
471	return (0);			   /* command timed out */
472}
473
474static int
475imm_negociate(struct vpoio_data *vpo)
476{
477	DECLARE_NEGOCIATE_MICROSEQ;
478	int negociate_mode;
479	int ret;
480
481	if (PPB_IN_NIBBLE_MODE(&vpo->vpo_dev))
482		negociate_mode = 0;
483	else if (PPB_IN_PS2_MODE(&vpo->vpo_dev))
484		negociate_mode = 1;
485	else
486		return (0);
487
488#if 0 /* XXX use standalone code not to depend on ppb_1284 code yet */
489	ret = ppb_1284_negociate(&vpo->vpo_dev, negociate_mode);
490
491	if (ret)
492		return (VP0_ENEGOCIATE);
493#endif
494
495	ppb_MS_init_msq(negociate_microseq, 1, NEGOCIATED_MODE, negociate_mode);
496
497	ppb_MS_microseq(&vpo->vpo_dev, negociate_microseq, &ret);
498
499	return (ret);
500}
501
502/*
503 * imm_probe()
504 *
505 * Low level probe of vpo device
506 *
507 */
508struct ppb_device *
509imm_probe(struct ppb_data *ppb, struct vpoio_data *vpo)
510{
511
512	/* ppbus dependent initialisation */
513	vpo->vpo_dev.id_unit = vpo->vpo_unit;
514	vpo->vpo_dev.name = "vpo";
515	vpo->vpo_dev.ppb = ppb;
516
517	/* now, try to initialise the drive */
518	if (imm_detect(vpo)) {
519		return (NULL);
520	}
521
522	return (&vpo->vpo_dev);
523}
524
525/*
526 * imm_attach()
527 *
528 * Low level attachment of vpo device
529 *
530 */
531int
532imm_attach(struct vpoio_data *vpo)
533{
534	int epp;
535
536	/*
537	 * Report ourselves
538	 */
539	printf("imm%d: <Iomega Matchmaker Parallel to SCSI interface> on ppbus %d\n",
540		vpo->vpo_dev.id_unit, vpo->vpo_dev.ppb->ppb_link->adapter_unit);
541
542	/*
543	 * Initialize microsequence code
544	 */
545	vpo->vpo_nibble_inbyte_msq = (struct ppb_microseq *)malloc(
546		sizeof(nibble_inbyte_submicroseq), M_DEVBUF, M_NOWAIT);
547
548	if (!vpo->vpo_nibble_inbyte_msq)
549		return (0);
550
551	bcopy((void *)nibble_inbyte_submicroseq,
552		(void *)vpo->vpo_nibble_inbyte_msq,
553		sizeof(nibble_inbyte_submicroseq));
554
555	INIT_NIBBLE_INBYTE_SUBMICROSEQ(vpo);
556
557	/*
558	 * Initialize mode dependent in/out microsequences
559	 */
560	ppb_request_bus(&vpo->vpo_dev, PPB_WAIT);
561
562	/* enter NIBBLE mode to configure submsq */
563	if (ppb_set_mode(&vpo->vpo_dev, PPB_NIBBLE) != -1) {
564
565		ppb_MS_GET_init(&vpo->vpo_dev, vpo->vpo_nibble_inbyte_msq);
566		ppb_MS_PUT_init(&vpo->vpo_dev, spp_outbyte_submicroseq);
567	}
568
569	/* enter PS2 mode to configure submsq */
570	if (ppb_set_mode(&vpo->vpo_dev, PPB_PS2) != -1) {
571
572		ppb_MS_GET_init(&vpo->vpo_dev, ps2_inbyte_submicroseq);
573		ppb_MS_PUT_init(&vpo->vpo_dev, spp_outbyte_submicroseq);
574	}
575
576	epp = ppb_get_epp_protocol(&vpo->vpo_dev);
577
578	/* enter EPP mode to configure submsq */
579	if (ppb_set_mode(&vpo->vpo_dev, PPB_EPP) != -1) {
580
581		switch (epp) {
582		case EPP_1_9:
583		case EPP_1_7:
584			ppb_MS_GET_init(&vpo->vpo_dev, epp17_instr);
585			ppb_MS_PUT_init(&vpo->vpo_dev, epp17_outstr);
586			break;
587		default:
588			panic("%s: unknown EPP protocol (0x%x)", __FUNCTION__,
589				epp);
590		}
591	}
592
593	/* try to enter EPP or PS/2 mode, NIBBLE otherwise */
594	if (ppb_set_mode(&vpo->vpo_dev, PPB_EPP) != -1) {
595		switch (epp) {
596		case EPP_1_9:
597			printf("imm%d: EPP 1.9 mode\n", vpo->vpo_unit);
598			break;
599		case EPP_1_7:
600			printf("imm%d: EPP 1.7 mode\n", vpo->vpo_unit);
601			break;
602		default:
603			panic("%s: unknown EPP protocol (0x%x)", __FUNCTION__,
604				epp);
605		}
606	} else if (ppb_set_mode(&vpo->vpo_dev, PPB_PS2) != -1)
607		printf("imm%d: PS2 mode\n", vpo->vpo_unit);
608
609	else if (ppb_set_mode(&vpo->vpo_dev, PPB_NIBBLE) != -1)
610		printf("imm%d: NIBBLE mode\n", vpo->vpo_unit);
611
612	else {
613		printf("imm%d: can't enter NIBBLE, PS2 or EPP mode\n",
614			vpo->vpo_unit);
615
616		ppb_release_bus(&vpo->vpo_dev);
617
618		free(vpo->vpo_nibble_inbyte_msq, M_DEVBUF);
619		return (0);
620	}
621
622	ppb_release_bus(&vpo->vpo_dev);
623
624	return (1);
625}
626
627/*
628 * imm_reset_bus()
629 *
630 */
631int
632imm_reset_bus(struct vpoio_data *vpo)
633{
634	int disconnected;
635
636	/* first, connect to the drive and request the bus */
637	imm_connect(vpo, PPB_WAIT|PPB_INTR, &disconnected, 1);
638
639	if (!disconnected) {
640
641		/* reset the SCSI bus */
642		ppb_MS_microseq(&vpo->vpo_dev, reset_microseq, NULL);
643
644		/* then disconnect */
645		imm_disconnect(vpo, NULL, 1);
646	}
647
648	return (0);
649}
650
651/*
652 * imm_do_scsi()
653 *
654 * Send an SCSI command
655 *
656 */
657int
658imm_do_scsi(struct vpoio_data *vpo, int host, int target, char *command,
659		int clen, char *buffer, int blen, int *result, int *count,
660		int *ret)
661{
662
663	register char r;
664	char l, h = 0;
665	int len, error = 0, not_connected = 0;
666	register int k;
667	int negociated = 0;
668
669	/*
670	 * enter disk state, allocate the ppbus
671	 *
672	 * XXX
673	 * Should we allow this call to be interruptible?
674	 * The only way to report the interruption is to return
675	 * EIO do upper SCSI code :^(
676	 */
677	if ((error = imm_connect(vpo, PPB_WAIT|PPB_INTR, &not_connected, 1)))
678		return (error);
679
680	if (not_connected) {
681		*ret = VP0_ECONNECT; goto error;
682	}
683
684	/*
685	 * Select the drive ...
686	 */
687	if ((*ret = imm_select(vpo,host,target)))
688		goto error;
689
690	/*
691	 * Send the command ...
692	 */
693	for (k = 0; k < clen; k+=2) {
694		if (imm_wait(vpo, VP0_FAST_SPINTMO) != (char)0xa8) {
695			*ret = VP0_ECMD_TIMEOUT;
696			goto error;
697		}
698		if (imm_outstr(vpo, &command[k], 2)) {
699			*ret = VP0_EPPDATA_TIMEOUT;
700			goto error;
701		}
702	}
703
704	if (!(r = imm_wait(vpo, VP0_LOW_SPINTMO))) {
705		*ret = VP0_ESTATUS_TIMEOUT; goto error;
706	}
707
708	if ((r & 0x30) == 0x10) {
709		if (imm_negociate(vpo)) {
710			*ret = VP0_ENEGOCIATE;
711			goto error;
712		} else
713			negociated = 1;
714	}
715
716	/*
717	 * Complete transfer ...
718	 */
719	*count = 0;
720	for (;;) {
721
722		if (!(r = imm_wait(vpo, VP0_LOW_SPINTMO))) {
723			*ret = VP0_ESTATUS_TIMEOUT; goto error;
724		}
725
726		/* stop when the ZIP+ wants to send status */
727		if (r == (char)0xb8)
728			break;
729
730		if (*count >= blen) {
731			*ret = VP0_EDATA_OVERFLOW;
732			goto error;
733		}
734
735		/* ZIP+ wants to send data? */
736		if (r == (char)0x88) {
737			len = (((blen - *count) >= VP0_SECTOR_SIZE)) ?
738				VP0_SECTOR_SIZE : 2;
739
740			error = imm_outstr(vpo, &buffer[*count], len);
741		} else {
742			if (!PPB_IN_EPP_MODE(&vpo->vpo_dev))
743				len = 1;
744			else
745				len = (((blen - *count) >= VP0_SECTOR_SIZE)) ?
746					VP0_SECTOR_SIZE : 1;
747
748			error = imm_instr(vpo, &buffer[*count], len);
749		}
750
751		if (error) {
752			*ret = error;
753			goto error;
754		}
755
756		*count += len;
757	}
758
759	if ((PPB_IN_NIBBLE_MODE(&vpo->vpo_dev) ||
760			PPB_IN_PS2_MODE(&vpo->vpo_dev)) && negociated)
761		ppb_MS_microseq(&vpo->vpo_dev, transfer_epilog, NULL);
762
763	/*
764	 * Retrieve status ...
765	 */
766	if (imm_negociate(vpo)) {
767		*ret = VP0_ENEGOCIATE;
768		goto error;
769	} else
770		negociated = 1;
771
772	if (imm_instr(vpo, &l, 1)) {
773		*ret = VP0_EOTHER; goto error;
774	}
775
776	/* check if the ZIP+ wants to send more status */
777	if (imm_wait(vpo, VP0_FAST_SPINTMO) == (char)0xb8)
778		if (imm_instr(vpo, &h, 1)) {
779			*ret = VP0_EOTHER+2; goto error;
780		}
781
782	*result = ((int) h << 8) | ((int) l & 0xff);
783
784error:
785	if ((PPB_IN_NIBBLE_MODE(&vpo->vpo_dev) ||
786			PPB_IN_PS2_MODE(&vpo->vpo_dev)) && negociated)
787		ppb_MS_microseq(&vpo->vpo_dev, transfer_epilog, NULL);
788
789	/* return to printer state, release the ppbus */
790	imm_disconnect(vpo, NULL, 1);
791
792	return (0);
793}
794