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