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