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