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