vpoio.c revision 70608
1/*-
2 * Copyright (c) 1998, 1999 Nicolas Souchu
3 * Copyright (c) 2000 Alcove - Nicolas Souchu
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25 * SUCH DAMAGE.
26 *
27 * $FreeBSD: head/sys/dev/ppbus/vpoio.c 70608 2001-01-02 21:29:06Z nsouch $
28 *
29 */
30
31#ifdef _KERNEL
32#include <sys/param.h>
33#include <sys/systm.h>
34#include <sys/module.h>
35#include <sys/bus.h>
36#include <sys/malloc.h>
37
38
39#endif
40
41#ifdef	_KERNEL
42#endif
43
44#include "opt_vpo.h"
45
46#include <dev/ppbus/ppbio.h>
47#include <dev/ppbus/ppbconf.h>
48#include <dev/ppbus/ppb_msq.h>
49#include <dev/ppbus/vpoio.h>
50
51#include "ppbus_if.h"
52
53/*
54 * The driver pools the drive. We may add a timeout queue to avoid
55 * active polling on nACK. I've tried this but it leads to unreliable
56 * transfers
57 */
58#define VP0_SELTMO		5000	/* select timeout */
59#define VP0_FAST_SPINTMO	500000	/* wait status timeout */
60#define VP0_LOW_SPINTMO		5000000	/* wait status timeout */
61
62/*
63 * Actually, VP0 timings are more accurate (about few 16MHZ cycles),
64 * but succeeding in respecting such timings leads to architecture
65 * dependent considerations.
66 */
67#define VP0_PULSE		1
68
69#define VP0_SECTOR_SIZE	512
70#define VP0_BUFFER_SIZE	0x12000
71
72#define n(flags) (~(flags) & (flags))
73
74/*
75 * VP0 connections.
76 */
77#define H_AUTO		n(AUTOFEED)
78#define H_nAUTO		AUTOFEED
79#define H_STROBE	n(STROBE)
80#define H_nSTROBE	STROBE
81#define H_BSY		n(nBUSY)
82#define H_nBSY		nBUSY
83#define H_SEL		SELECT
84#define H_nSEL		n(SELECT)
85#define H_ERR		PERROR
86#define H_nERR		n(PERROR)
87#define H_ACK		nACK
88#define H_nACK		n(nACK)
89#define H_FLT		nFAULT
90#define H_nFLT		n(nFAULT)
91#define H_SELIN		n(SELECTIN)
92#define H_nSELIN	SELECTIN
93#define H_INIT		nINIT
94#define H_nINIT		n(nINIT)
95
96/*
97 * Microcode to execute very fast I/O sequences at the lowest bus level.
98 */
99
100/* call this macro to initialize connect/disconnect microsequences */
101#define INIT_TRIG_MICROSEQ {						\
102	int i;								\
103	for (i=1; i <= 7; i+=2) {					\
104		disconnect_microseq[i].arg[2] = (union ppb_insarg)d_pulse; \
105		connect_epp_microseq[i].arg[2] = 			\
106		connect_spp_microseq[i].arg[2] = (union ppb_insarg)c_pulse; \
107	}								\
108}
109
110#define trig_d_pulse MS_TRIG(MS_REG_CTR,5,MS_UNKNOWN /* d_pulse */)
111static char d_pulse[] = {
112	 H_AUTO | H_nSELIN | H_INIT | H_STROBE, 0,
113	H_nAUTO | H_nSELIN | H_INIT | H_STROBE, VP0_PULSE,
114	 H_AUTO | H_nSELIN | H_INIT | H_STROBE, 0,
115	 H_AUTO |  H_SELIN | H_INIT | H_STROBE, VP0_PULSE,
116	 H_AUTO | H_nSELIN | H_INIT | H_STROBE, VP0_PULSE
117};
118
119#define trig_c_pulse MS_TRIG(MS_REG_CTR,5,MS_UNKNOWN /* c_pulse */)
120static char c_pulse[] = {
121	 H_AUTO | H_nSELIN | H_INIT | H_STROBE, 0,
122	 H_AUTO |  H_SELIN | H_INIT | H_STROBE, 0,
123	H_nAUTO |  H_SELIN | H_INIT | H_STROBE, VP0_PULSE,
124	 H_AUTO |  H_SELIN | H_INIT | H_STROBE, 0,
125	 H_AUTO | H_nSELIN | H_INIT | H_STROBE, VP0_PULSE
126};
127
128static struct ppb_microseq disconnect_microseq[] = {
129	  MS_DASS(0x0), trig_d_pulse, MS_DASS(0x3c), trig_d_pulse,
130	  MS_DASS(0x20), trig_d_pulse, MS_DASS(0xf), trig_d_pulse, MS_RET(0)
131};
132
133static struct ppb_microseq connect_epp_microseq[] = {
134	  MS_DASS(0x0), trig_c_pulse, MS_DASS(0x3c), trig_c_pulse,
135	  MS_DASS(0x20), trig_c_pulse, MS_DASS(0xcf), trig_c_pulse, MS_RET(0)
136};
137
138static struct ppb_microseq connect_spp_microseq[] = {
139	  MS_DASS(0x0), trig_c_pulse, MS_DASS(0x3c), trig_c_pulse,
140	  MS_DASS(0x20), trig_c_pulse, MS_DASS(0x8f), trig_c_pulse, MS_RET(0)
141};
142
143/*
144 * nibble_inbyte_hook()
145 *
146 * Formats high and low nibble into a character
147 */
148static int
149nibble_inbyte_hook (void *p, char *ptr)
150{
151	struct vpo_nibble *s = (struct vpo_nibble *)p;
152
153	/* increment the buffer pointer */
154	*ptr++ = ((s->l >> 4) & 0x0f) + (s->h & 0xf0);
155
156	return (0);
157}
158
159/*
160 * Macro used to initialize each vpoio_data structure during
161 * low level attachment
162 *
163 * XXX should be converted to ppb_MS_init_msq()
164 */
165#define INIT_NIBBLE_INBYTE_SUBMICROSEQ(vpo) {		    	\
166	(vpo)->vpo_nibble_inbyte_msq[2].arg[2].p =		\
167			(void *)&(vpo)->vpo_nibble.h;		\
168	(vpo)->vpo_nibble_inbyte_msq[4].arg[2].p =		\
169			(void *)&(vpo)->vpo_nibble.l;		\
170	(vpo)->vpo_nibble_inbyte_msq[5].arg[0].f =		\
171			nibble_inbyte_hook;			\
172	(vpo)->vpo_nibble_inbyte_msq[5].arg[1].p =		\
173			(void *)&(vpo)->vpo_nibble;		\
174}
175
176/*
177 * This is the sub-microseqence for MS_GET in NIBBLE mode
178 * Retrieve the two nibbles and call the C function to generate the character
179 * and store it in the buffer (see nibble_inbyte_hook())
180 */
181static struct ppb_microseq nibble_inbyte_submicroseq[] = {
182
183/* loop: */
184	  MS_CASS( H_AUTO | H_SELIN | H_INIT | H_STROBE),
185	  MS_DELAY(VP0_PULSE),
186	  MS_RFETCH(MS_REG_STR, MS_FETCH_ALL, MS_UNKNOWN /* high nibble */),
187	  MS_CASS(H_nAUTO | H_SELIN | H_INIT | H_STROBE),
188	  MS_RFETCH(MS_REG_STR, MS_FETCH_ALL, MS_UNKNOWN /* low nibble */),
189
190	  /* do a C call to format the received nibbles */
191	  MS_C_CALL(MS_UNKNOWN /* C hook */, MS_UNKNOWN /* param */),
192	  MS_DBRA(-7 /* loop */),
193
194	  MS_CASS(H_AUTO | H_nSELIN | H_INIT | H_STROBE),
195	  MS_RET(0)
196};
197
198/*
199 * This is the sub-microseqence for MS_GET in PS2 mode
200 */
201static struct ppb_microseq ps2_inbyte_submicroseq[] = {
202	  MS_CASS(PCD | H_AUTO | H_SELIN | H_INIT | H_nSTROBE),
203
204/* loop: */
205	  MS_RFETCH_P(1, MS_REG_DTR, MS_FETCH_ALL),
206	  MS_CASS(PCD | H_nAUTO | H_SELIN | H_INIT | H_nSTROBE),
207	  MS_CASS(PCD |  H_AUTO | H_SELIN | H_INIT | H_nSTROBE),
208	  MS_DBRA(-4 /* loop */),
209
210	  MS_CASS(H_AUTO | H_nSELIN | H_INIT | H_STROBE),
211	  MS_RET(0)
212};
213
214/*
215 * This is the sub-microsequence for MS_PUT in both NIBBLE and PS2 modes
216 */
217static struct ppb_microseq spp_outbyte_submicroseq[] = {
218
219/* loop: */
220	  MS_RASSERT_P(1, MS_REG_DTR),
221	  MS_CASS(H_nAUTO | H_nSELIN | H_INIT | H_STROBE),
222	  MS_CASS( H_AUTO | H_nSELIN | H_INIT | H_STROBE),
223	  MS_DELAY(VP0_PULSE),
224	  MS_DBRA(-5 /* loop */),
225
226	  /* return from the put call */
227	  MS_RET(0)
228};
229
230/* EPP 1.7 microsequences, ptr and len set at runtime */
231static struct ppb_microseq epp17_outstr_body[] = {
232	  MS_CASS(H_AUTO | H_SELIN | H_INIT | H_STROBE),
233
234/* loop: */
235	  MS_RASSERT_P(1, MS_REG_EPP_D),
236	  MS_BRSET(TIMEOUT, 3 /* error */),	/* EPP timeout? */
237	  MS_DBRA(-3 /* loop */),
238
239	  MS_CASS(H_AUTO | H_nSELIN | H_INIT | H_STROBE),
240	  MS_RET(0),
241/* error: */
242	  MS_CASS(H_AUTO | H_nSELIN | H_INIT | H_STROBE),
243	  MS_RET(1)
244};
245
246static struct ppb_microseq epp17_instr_body[] = {
247	  MS_CASS(PCD | H_AUTO | H_SELIN | H_INIT | H_STROBE),
248
249/* loop: */
250	  MS_RFETCH_P(1, MS_REG_EPP_D, MS_FETCH_ALL),
251	  MS_BRSET(TIMEOUT, 3 /* error */),	/* EPP timeout? */
252	  MS_DBRA(-3 /* loop */),
253
254	  MS_CASS(PCD | H_AUTO | H_nSELIN | H_INIT | H_STROBE),
255	  MS_RET(0),
256/* error: */
257	  MS_CASS(PCD | H_AUTO | H_nSELIN | H_INIT | H_STROBE),
258	  MS_RET(1)
259};
260
261static struct ppb_microseq in_disk_mode[] = {
262	  MS_CASS( H_AUTO | H_nSELIN | H_INIT | H_STROBE),
263	  MS_CASS(H_nAUTO | H_nSELIN | H_INIT | H_STROBE),
264
265	  MS_BRCLEAR(H_FLT, 3 /* error */),
266	  MS_CASS( H_AUTO | H_nSELIN | H_INIT | H_STROBE),
267	  MS_BRSET(H_FLT, 1 /* error */),
268
269	  MS_RET(1),
270/* error: */
271	  MS_RET(0)
272};
273
274static int
275vpoio_disconnect(struct vpoio_data *vpo)
276{
277	device_t ppbus = device_get_parent(vpo->vpo_dev);
278	int ret;
279
280	ppb_MS_microseq(ppbus, vpo->vpo_dev, disconnect_microseq, &ret);
281	return (ppb_release_bus(ppbus, vpo->vpo_dev));
282}
283
284/*
285 * how	: PPB_WAIT or PPB_DONTWAIT
286 */
287static int
288vpoio_connect(struct vpoio_data *vpo, int how)
289{
290	device_t ppbus = device_get_parent(vpo->vpo_dev);
291	int error;
292	int ret;
293
294	if ((error = ppb_request_bus(ppbus, vpo->vpo_dev, how))) {
295
296#ifdef VP0_DEBUG
297		printf("%s: can't request bus!\n", __FUNCTION__);
298#endif
299		return error;
300	}
301
302	if (PPB_IN_EPP_MODE(ppbus))
303		ppb_MS_microseq(ppbus, vpo->vpo_dev, connect_epp_microseq, &ret);
304	else
305		ppb_MS_microseq(ppbus, vpo->vpo_dev, connect_spp_microseq, &ret);
306
307	return (0);
308}
309
310/*
311 * vpoio_reset()
312 *
313 * SCSI reset signal, the drive must be in disk mode
314 */
315static void
316vpoio_reset (struct vpoio_data *vpo)
317{
318	device_t ppbus = device_get_parent(vpo->vpo_dev);
319	int ret;
320
321	struct ppb_microseq reset_microseq[] = {
322
323		#define INITIATOR	MS_PARAM(0, 1, MS_TYP_INT)
324
325		MS_DASS(MS_UNKNOWN),
326		MS_CASS(H_AUTO | H_nSELIN | H_nINIT | H_STROBE),
327		MS_DELAY(25),
328		MS_CASS(H_AUTO | H_nSELIN |  H_INIT | H_STROBE),
329		MS_RET(0)
330	};
331
332	ppb_MS_init_msq(reset_microseq, 1, INITIATOR, 1 << VP0_INITIATOR);
333	ppb_MS_microseq(ppbus, vpo->vpo_dev, reset_microseq, &ret);
334
335	return;
336}
337
338/*
339 * vpoio_in_disk_mode()
340 */
341static int
342vpoio_in_disk_mode(struct vpoio_data *vpo)
343{
344	device_t ppbus = device_get_parent(vpo->vpo_dev);
345	int ret;
346
347	ppb_MS_microseq(ppbus, vpo->vpo_dev, in_disk_mode, &ret);
348
349	return (ret);
350}
351
352/*
353 * vpoio_detect()
354 *
355 * Detect and initialise the VP0 adapter.
356 */
357static int
358vpoio_detect(struct vpoio_data *vpo)
359{
360	device_t ppbus = device_get_parent(vpo->vpo_dev);
361	int error, ret;
362
363	/* allocate the bus, then apply microsequences */
364	if ((error = ppb_request_bus(ppbus, vpo->vpo_dev, PPB_DONTWAIT)))
365                return (error);
366
367	ppb_MS_microseq(ppbus, vpo->vpo_dev, disconnect_microseq, &ret);
368	/* Force disconnection */
369
370	/* Try to enter EPP mode, then connect to the drive in EPP mode */
371	if (ppb_set_mode(ppbus, PPB_EPP) != -1) {
372		/* call manually the microseq instead of using the appropriate function
373		 * since we already requested the ppbus */
374		ppb_MS_microseq(ppbus, vpo->vpo_dev, connect_epp_microseq, &ret);
375	}
376
377	/* If EPP mode switch failed or ZIP connection in EPP mode failed,
378	 * try to connect in NIBBLE mode */
379	if (!vpoio_in_disk_mode(vpo)) {
380
381		/* The interface must be at least PS/2 or NIBBLE capable.
382		 * There is no way to know if the ZIP will work with
383		 * PS/2 mode since PS/2 and SPP both use the same connect
384		 * sequence. One must supress PS/2 with boot flags if
385		 * PS/2 mode fails (see ppc(4)).
386		 */
387		if (ppb_set_mode(ppbus, PPB_PS2) != -1) {
388			vpo->vpo_mode_found = VP0_MODE_PS2;
389		} else {
390			if (ppb_set_mode(ppbus, PPB_NIBBLE) == -1)
391				goto error;
392
393			vpo->vpo_mode_found = VP0_MODE_NIBBLE;
394		}
395
396		/* Can't know if the interface is capable of PS/2 yet */
397		ppb_MS_microseq(ppbus, vpo->vpo_dev, connect_spp_microseq, &ret);
398		if (!vpoio_in_disk_mode(vpo)) {
399			vpo->vpo_mode_found = VP0_MODE_UNDEFINED;
400			if (bootverbose)
401				printf("vpo%d: can't connect to the drive\n",
402					vpo->vpo_unit);
403
404			/* disconnect and release the bus */
405			ppb_MS_microseq(ppbus, vpo->vpo_dev, disconnect_microseq,
406					&ret);
407			goto error;
408		}
409	} else {
410		vpo->vpo_mode_found = VP0_MODE_EPP;
411	}
412
413	/* send SCSI reset signal */
414	vpoio_reset(vpo);
415
416	ppb_MS_microseq(ppbus, vpo->vpo_dev, disconnect_microseq, &ret);
417
418	/* ensure we are disconnected or daisy chained peripheral
419	 * may cause serious problem to the disk */
420	if (vpoio_in_disk_mode(vpo)) {
421		if (bootverbose)
422			printf("vpo%d: can't disconnect from the drive\n",
423				vpo->vpo_unit);
424		goto error;
425	}
426
427	ppb_release_bus(ppbus, vpo->vpo_dev);
428	return (0);
429
430error:
431	ppb_release_bus(ppbus, vpo->vpo_dev);
432	return (VP0_EINITFAILED);
433}
434
435/*
436 * vpoio_outstr()
437 */
438static int
439vpoio_outstr(struct vpoio_data *vpo, char *buffer, int size)
440{
441	device_t ppbus = device_get_parent(vpo->vpo_dev);
442	int error = 0;
443
444	ppb_MS_exec(ppbus, vpo->vpo_dev, MS_OP_PUT, (union ppb_insarg)buffer,
445		(union ppb_insarg)size, (union ppb_insarg)MS_UNKNOWN, &error);
446
447	ppb_ecp_sync(ppbus);
448
449	return (error);
450}
451
452/*
453 * vpoio_instr()
454 */
455static int
456vpoio_instr(struct vpoio_data *vpo, char *buffer, int size)
457{
458	device_t ppbus = device_get_parent(vpo->vpo_dev);
459	int error = 0;
460
461	ppb_MS_exec(ppbus, vpo->vpo_dev, MS_OP_GET, (union ppb_insarg)buffer,
462		(union ppb_insarg)size, (union ppb_insarg)MS_UNKNOWN, &error);
463
464	ppb_ecp_sync(ppbus);
465
466	return (error);
467}
468
469static char
470vpoio_select(struct vpoio_data *vpo, int initiator, int target)
471{
472	device_t ppbus = device_get_parent(vpo->vpo_dev);
473	int ret;
474
475	struct ppb_microseq select_microseq[] = {
476
477		/* parameter list
478		 */
479		#define SELECT_TARGET		MS_PARAM(0, 1, MS_TYP_INT)
480		#define SELECT_INITIATOR	MS_PARAM(3, 1, MS_TYP_INT)
481
482		/* send the select command to the drive */
483		MS_DASS(MS_UNKNOWN),
484		MS_CASS(H_nAUTO | H_nSELIN |  H_INIT | H_STROBE),
485		MS_CASS( H_AUTO | H_nSELIN |  H_INIT | H_STROBE),
486		MS_DASS(MS_UNKNOWN),
487		MS_CASS( H_AUTO | H_nSELIN | H_nINIT | H_STROBE),
488
489		/* now, wait until the drive is ready */
490		MS_SET(VP0_SELTMO),
491/* loop: */	MS_BRSET(H_ACK, 2 /* ready */),
492		MS_DBRA(-2 /* loop */),
493/* error: */	MS_RET(1),
494/* ready: */	MS_RET(0)
495	};
496
497	/* initialize the select microsequence */
498	ppb_MS_init_msq(select_microseq, 2,
499			SELECT_TARGET, 1 << target,
500			SELECT_INITIATOR, 1 << initiator);
501
502	ppb_MS_microseq(ppbus, vpo->vpo_dev, select_microseq, &ret);
503
504	if (ret)
505		return (VP0_ESELECT_TIMEOUT);
506
507	return (0);
508}
509
510/*
511 * vpoio_wait()
512 *
513 * H_SELIN must be low.
514 *
515 * XXX should be ported to microseq
516 */
517static char
518vpoio_wait(struct vpoio_data *vpo, int tmo)
519{
520	device_t ppbus = device_get_parent(vpo->vpo_dev);
521	register int	k;
522	register char	r;
523
524#if 0	/* broken */
525	if (ppb_poll_device(ppbus, 150, nBUSY, nBUSY, PPB_INTR))
526		return (0);
527
528	return (ppb_rstr(ppbus) & 0xf0);
529#endif
530
531	/* XXX should be ported to microseq */
532	k = 0;
533	while (!((r = ppb_rstr(ppbus)) & nBUSY) && (k++ < tmo))
534		;
535
536	/*
537	 * Return some status information.
538	 * Semantics :	0xc0 = ZIP wants more data
539	 *		0xd0 = ZIP wants to send more data
540	 *		0xe0 = ZIP wants command
541	 *		0xf0 = end of transfer, ZIP is sending status
542	 */
543	if (k < tmo)
544	  return (r & 0xf0);
545
546	return (0);			   /* command timed out */
547}
548
549/*
550 * vpoio_probe()
551 *
552 * Low level probe of vpo device
553 *
554 */
555int
556vpoio_probe(device_t dev, struct vpoio_data *vpo)
557{
558	int error;
559
560	/* ppbus dependent initialisation */
561	vpo->vpo_dev = dev;
562
563	/*
564	 * Initialize microsequence code
565	 */
566	INIT_TRIG_MICROSEQ;
567
568	/* now, try to initialise the drive */
569	if ((error = vpoio_detect(vpo))) {
570		return (error);
571	}
572
573	return (0);
574}
575
576/*
577 * vpoio_attach()
578 *
579 * Low level attachment of vpo device
580 *
581 */
582int
583vpoio_attach(struct vpoio_data *vpo)
584{
585	device_t ppbus = device_get_parent(vpo->vpo_dev);
586	int error = 0;
587
588	vpo->vpo_nibble_inbyte_msq = (struct ppb_microseq *)malloc(
589		sizeof(nibble_inbyte_submicroseq), M_DEVBUF, M_NOWAIT);
590
591	if (!vpo->vpo_nibble_inbyte_msq)
592		return (ENXIO);
593
594	bcopy((void *)nibble_inbyte_submicroseq,
595		(void *)vpo->vpo_nibble_inbyte_msq,
596		sizeof(nibble_inbyte_submicroseq));
597
598	INIT_NIBBLE_INBYTE_SUBMICROSEQ(vpo);
599
600	/*
601	 * Initialize mode dependent in/out microsequences
602	 */
603	if ((error = ppb_request_bus(ppbus, vpo->vpo_dev, PPB_WAIT)))
604		goto error;
605
606	/* ppbus sets automatically the last mode entered during detection */
607	switch (vpo->vpo_mode_found) {
608	case VP0_MODE_EPP:
609		ppb_MS_GET_init(ppbus, vpo->vpo_dev, epp17_instr_body);
610		ppb_MS_PUT_init(ppbus, vpo->vpo_dev, epp17_outstr_body);
611		printf("vpo%d: EPP mode\n", vpo->vpo_unit);
612		break;
613	case VP0_MODE_PS2:
614		ppb_MS_GET_init(ppbus, vpo->vpo_dev, ps2_inbyte_submicroseq);
615		ppb_MS_PUT_init(ppbus, vpo->vpo_dev, spp_outbyte_submicroseq);
616		printf("vpo%d: PS2 mode\n", vpo->vpo_unit);
617		break;
618	case VP0_MODE_NIBBLE:
619		ppb_MS_GET_init(ppbus, vpo->vpo_dev, vpo->vpo_nibble_inbyte_msq);
620		ppb_MS_PUT_init(ppbus, vpo->vpo_dev, spp_outbyte_submicroseq);
621		printf("vpo%d: NIBBLE mode\n", vpo->vpo_unit);
622		break;
623	default:
624		panic("vpo: unknown mode %d", vpo->vpo_mode_found);
625	}
626
627	ppb_release_bus(ppbus, vpo->vpo_dev);
628
629error:
630	return (error);
631}
632
633/*
634 * vpoio_reset_bus()
635 *
636 */
637int
638vpoio_reset_bus(struct vpoio_data *vpo)
639{
640	/* first, connect to the drive */
641	if (vpoio_connect(vpo, PPB_WAIT|PPB_INTR) || !vpoio_in_disk_mode(vpo)) {
642
643#ifdef VP0_DEBUG
644		printf("%s: not in disk mode!\n", __FUNCTION__);
645#endif
646		/* release ppbus */
647		vpoio_disconnect(vpo);
648		return (1);
649	}
650
651	/* reset the SCSI bus */
652	vpoio_reset(vpo);
653
654	/* then disconnect */
655	vpoio_disconnect(vpo);
656
657	return (0);
658}
659
660/*
661 * vpoio_do_scsi()
662 *
663 * Send an SCSI command
664 *
665 */
666int
667vpoio_do_scsi(struct vpoio_data *vpo, int host, int target, char *command,
668		int clen, char *buffer, int blen, int *result, int *count,
669		int *ret)
670{
671	device_t ppbus = device_get_parent(vpo->vpo_dev);
672	register char r;
673	char l, h = 0;
674	int len, error = 0;
675	register int k;
676
677	/*
678	 * enter disk state, allocate the ppbus
679	 *
680	 * XXX
681	 * Should we allow this call to be interruptible?
682	 * The only way to report the interruption is to return
683	 * EIO do upper SCSI code :^(
684	 */
685	if ((error = vpoio_connect(vpo, PPB_WAIT|PPB_INTR)))
686		return (error);
687
688	if (!vpoio_in_disk_mode(vpo)) {
689		*ret = VP0_ECONNECT; goto error;
690	}
691
692	if ((*ret = vpoio_select(vpo,host,target)))
693		goto error;
694
695	/*
696	 * Send the command ...
697	 *
698	 * set H_SELIN low for vpoio_wait().
699	 */
700	ppb_wctr(ppbus, H_AUTO | H_nSELIN | H_INIT | H_STROBE);
701
702	for (k = 0; k < clen; k++) {
703		if (vpoio_wait(vpo, VP0_FAST_SPINTMO) != (char)0xe0) {
704			*ret = VP0_ECMD_TIMEOUT;
705			goto error;
706		}
707		if (vpoio_outstr(vpo, &command[k], 1)) {
708			*ret = VP0_EPPDATA_TIMEOUT;
709			goto error;
710		}
711	}
712
713	/*
714	 * Completion ...
715	 */
716
717	*count = 0;
718	for (;;) {
719
720		if (!(r = vpoio_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)0xf0)
726			break;
727
728		if (*count >= blen) {
729			*ret = VP0_EDATA_OVERFLOW;
730			goto error;
731		}
732
733		/* if in EPP mode or writing bytes, try to transfer a sector
734		 * otherwise, just send one byte
735		 */
736		if (PPB_IN_EPP_MODE(ppbus) || r == (char)0xc0)
737			len = (((blen - *count) >= VP0_SECTOR_SIZE)) ?
738				VP0_SECTOR_SIZE : 1;
739		else
740			len = 1;
741
742		/* ZIP wants to send data? */
743		if (r == (char)0xc0)
744			error = vpoio_outstr(vpo, &buffer[*count], len);
745		else
746			error = vpoio_instr(vpo, &buffer[*count], len);
747
748		if (error) {
749			*ret = error;
750			goto error;
751		}
752
753		*count += len;
754	}
755
756	if (vpoio_instr(vpo, &l, 1)) {
757		*ret = VP0_EOTHER; goto error;
758	}
759
760	/* check if the ZIP wants to send more status */
761	if (vpoio_wait(vpo, VP0_FAST_SPINTMO) == (char)0xf0)
762		if (vpoio_instr(vpo, &h, 1)) {
763			*ret = VP0_EOTHER+2; goto error;
764		}
765
766	*result = ((int) h << 8) | ((int) l & 0xff);
767
768error:
769	/* return to printer state, release the ppbus */
770	vpoio_disconnect(vpo);
771	return (0);
772}
773