vpoio.c revision 103736
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 103736 2002-09-21 08:44:51Z phk $
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#include <machine/clock.h>
39
40#endif
41
42#include "opt_vpo.h"
43
44#include <dev/ppbus/ppbio.h>
45#include <dev/ppbus/ppbconf.h>
46#include <dev/ppbus/ppb_msq.h>
47#include <dev/ppbus/vpoio.h>
48
49#include "ppbus_if.h"
50
51/*
52 * The driver pools the drive. We may add a timeout queue to avoid
53 * active polling on nACK. I've tried this but it leads to unreliable
54 * transfers
55 */
56#define VP0_SELTMO		5000	/* select timeout */
57#define VP0_FAST_SPINTMO	500000	/* wait status timeout */
58#define VP0_LOW_SPINTMO		5000000	/* wait status timeout */
59
60/*
61 * Actually, VP0 timings are more accurate (about few 16MHZ cycles),
62 * but succeeding in respecting such timings leads to architecture
63 * dependent considerations.
64 */
65#define VP0_PULSE		1
66
67#define VP0_SECTOR_SIZE	512
68#define VP0_BUFFER_SIZE	0x12000
69
70#define n(flags) (~(flags) & (flags))
71
72/*
73 * VP0 connections.
74 */
75#define H_AUTO		n(AUTOFEED)
76#define H_nAUTO		AUTOFEED
77#define H_STROBE	n(STROBE)
78#define H_nSTROBE	STROBE
79#define H_BSY		n(nBUSY)
80#define H_nBSY		nBUSY
81#define H_SEL		SELECT
82#define H_nSEL		n(SELECT)
83#define H_ERR		PERROR
84#define H_nERR		n(PERROR)
85#define H_ACK		nACK
86#define H_nACK		n(nACK)
87#define H_FLT		nFAULT
88#define H_nFLT		n(nFAULT)
89#define H_SELIN		n(SELECTIN)
90#define H_nSELIN	SELECTIN
91#define H_INIT		nINIT
92#define H_nINIT		n(nINIT)
93
94/*
95 * Microcode to execute very fast I/O sequences at the lowest bus level.
96 */
97
98#define WAIT_RET	MS_PARAM(4, 2, MS_TYP_PTR)
99#define WAIT_TMO	MS_PARAM(0, 0, MS_TYP_INT)
100
101#define DECLARE_WAIT_MICROSEQUENCE			\
102struct ppb_microseq wait_microseq[] = {			\
103	MS_SET(MS_UNKNOWN),				\
104	/* loop */					\
105	MS_BRSET(nBUSY, 2 /* ready */),			\
106	MS_DBRA(-2 /* loop */),				\
107	MS_RET(1), /* timed out */			\
108	/* ready */					\
109	MS_RFETCH(MS_REG_STR, 0xf0, MS_UNKNOWN),	\
110	MS_RET(0) /* no error */			\
111}
112
113/* call this macro to initialize connect/disconnect microsequences */
114#define INIT_TRIG_MICROSEQ {						\
115	int i;								\
116	for (i=1; i <= 7; i+=2) {					\
117		disconnect_microseq[i].arg[2] = (union ppb_insarg)d_pulse; \
118		connect_epp_microseq[i].arg[2] = 			\
119		connect_spp_microseq[i].arg[2] = (union ppb_insarg)c_pulse; \
120	}								\
121}
122
123#define trig_d_pulse MS_TRIG(MS_REG_CTR,5,MS_UNKNOWN /* d_pulse */)
124static char d_pulse[] = {
125	 H_AUTO | H_nSELIN | H_INIT | H_STROBE, 0,
126	H_nAUTO | H_nSELIN | H_INIT | H_STROBE, VP0_PULSE,
127	 H_AUTO | H_nSELIN | H_INIT | H_STROBE, 0,
128	 H_AUTO |  H_SELIN | H_INIT | H_STROBE, VP0_PULSE,
129	 H_AUTO | H_nSELIN | H_INIT | H_STROBE, VP0_PULSE
130};
131
132#define trig_c_pulse MS_TRIG(MS_REG_CTR,5,MS_UNKNOWN /* c_pulse */)
133static char c_pulse[] = {
134	 H_AUTO | H_nSELIN | H_INIT | H_STROBE, 0,
135	 H_AUTO |  H_SELIN | H_INIT | H_STROBE, 0,
136	H_nAUTO |  H_SELIN | H_INIT | H_STROBE, VP0_PULSE,
137	 H_AUTO |  H_SELIN | H_INIT | H_STROBE, 0,
138	 H_AUTO | H_nSELIN | H_INIT | H_STROBE, VP0_PULSE
139};
140
141static struct ppb_microseq disconnect_microseq[] = {
142	  MS_DASS(0x0), trig_d_pulse, MS_DASS(0x3c), trig_d_pulse,
143	  MS_DASS(0x20), trig_d_pulse, MS_DASS(0xf), trig_d_pulse, MS_RET(0)
144};
145
146static struct ppb_microseq connect_epp_microseq[] = {
147	  MS_DASS(0x0), trig_c_pulse, MS_DASS(0x3c), trig_c_pulse,
148	  MS_DASS(0x20), trig_c_pulse, MS_DASS(0xcf), trig_c_pulse, MS_RET(0)
149};
150
151static struct ppb_microseq connect_spp_microseq[] = {
152	  MS_DASS(0x0), trig_c_pulse, MS_DASS(0x3c), trig_c_pulse,
153	  MS_DASS(0x20), trig_c_pulse, MS_DASS(0x8f), trig_c_pulse, MS_RET(0)
154};
155
156/*
157 * nibble_inbyte_hook()
158 *
159 * Formats high and low nibble into a character
160 */
161static int
162nibble_inbyte_hook (void *p, char *ptr)
163{
164	struct vpo_nibble *s = (struct vpo_nibble *)p;
165
166	/* increment the buffer pointer */
167	*ptr++ = ((s->l >> 4) & 0x0f) + (s->h & 0xf0);
168
169	return (0);
170}
171
172#define INB_NIBBLE_H MS_PARAM(2, 2, MS_TYP_PTR)
173#define INB_NIBBLE_L MS_PARAM(4, 2, MS_TYP_PTR)
174#define INB_NIBBLE_F MS_PARAM(5, 0, MS_TYP_FUN)
175#define INB_NIBBLE_P MS_PARAM(5, 1, MS_TYP_PTR)
176
177/*
178 * This is the sub-microseqence for MS_GET in NIBBLE mode
179 * Retrieve the two nibbles and call the C function to generate the character
180 * and store it in the buffer (see nibble_inbyte_hook())
181 */
182
183#define DECLARE_NIBBLE_INBYTE_SUBMICROSEQ			\
184struct ppb_microseq nibble_inbyte_submicroseq[] = {		\
185/* loop: */							\
186	  MS_CASS( H_AUTO | H_SELIN | H_INIT | H_STROBE),	\
187	  MS_DELAY(VP0_PULSE),					\
188	  MS_RFETCH(MS_REG_STR, MS_FETCH_ALL, MS_UNKNOWN /* high nibble */),\
189	  MS_CASS(H_nAUTO | H_SELIN | H_INIT | H_STROBE),	\
190	  MS_RFETCH(MS_REG_STR, MS_FETCH_ALL, MS_UNKNOWN /* low nibble */),\
191	  /* do a C call to format the received nibbles */	\
192	  MS_C_CALL(MS_UNKNOWN /* C hook */, MS_UNKNOWN /* param */),\
193	  MS_DBRA(-7 /* loop */),				\
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", __func__);
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	/* Force disconnection */
368	ppb_MS_microseq(ppbus, vpo->vpo_dev, disconnect_microseq, &ret);
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	DECLARE_WAIT_MICROSEQUENCE;
521
522	device_t ppbus = device_get_parent(vpo->vpo_dev);
523	int ret, err;
524
525#if 0	/* broken */
526	if (ppb_poll_device(ppbus, 150, nBUSY, nBUSY, PPB_INTR))
527		return (0);
528
529	return (ppb_rstr(ppbus) & 0xf0);
530#endif
531
532	/*
533	 * Return some status information.
534	 * Semantics :	0xc0 = ZIP wants more data
535	 *		0xd0 = ZIP wants to send more data
536	 *		0xe0 = ZIP wants command
537	 *		0xf0 = end of transfer, ZIP is sending status
538	 */
539
540	ppb_MS_init_msq(wait_microseq, 2,
541			WAIT_RET, (void *)&ret,
542			WAIT_TMO, tmo);
543
544	ppb_MS_microseq(ppbus, vpo->vpo_dev, wait_microseq, &err);
545
546	if (err)
547		return (0);	 /* command timed out */
548
549	return(ret);
550}
551
552/*
553 * vpoio_probe()
554 *
555 * Low level probe of vpo device
556 *
557 */
558int
559vpoio_probe(device_t dev, struct vpoio_data *vpo)
560{
561	int error;
562
563	/* ppbus dependent initialisation */
564	vpo->vpo_dev = dev;
565
566	/*
567	 * Initialize microsequence code
568	 */
569	INIT_TRIG_MICROSEQ;
570
571	/* now, try to initialise the drive */
572	if ((error = vpoio_detect(vpo))) {
573		return (error);
574	}
575
576	return (0);
577}
578
579/*
580 * vpoio_attach()
581 *
582 * Low level attachment of vpo device
583 *
584 */
585int
586vpoio_attach(struct vpoio_data *vpo)
587{
588	DECLARE_NIBBLE_INBYTE_SUBMICROSEQ;
589	device_t ppbus = device_get_parent(vpo->vpo_dev);
590	int error = 0;
591
592	vpo->vpo_nibble_inbyte_msq = (struct ppb_microseq *)malloc(
593		sizeof(nibble_inbyte_submicroseq), M_DEVBUF, M_NOWAIT);
594
595	if (!vpo->vpo_nibble_inbyte_msq)
596		return (ENXIO);
597
598	bcopy((void *)nibble_inbyte_submicroseq,
599		(void *)vpo->vpo_nibble_inbyte_msq,
600		sizeof(nibble_inbyte_submicroseq));
601
602	ppb_MS_init_msq(vpo->vpo_nibble_inbyte_msq, 4,
603		INB_NIBBLE_H, (void *)&(vpo)->vpo_nibble.h,
604		INB_NIBBLE_L, (void *)&(vpo)->vpo_nibble.l,
605		INB_NIBBLE_F, nibble_inbyte_hook,
606		INB_NIBBLE_P, (void *)&(vpo)->vpo_nibble);
607
608	/*
609	 * Initialize mode dependent in/out microsequences
610	 */
611	if ((error = ppb_request_bus(ppbus, vpo->vpo_dev, PPB_WAIT)))
612		goto error;
613
614	/* ppbus sets automatically the last mode entered during detection */
615	switch (vpo->vpo_mode_found) {
616	case VP0_MODE_EPP:
617		ppb_MS_GET_init(ppbus, vpo->vpo_dev, epp17_instr_body);
618		ppb_MS_PUT_init(ppbus, vpo->vpo_dev, epp17_outstr_body);
619		printf("vpo%d: EPP mode\n", vpo->vpo_unit);
620		break;
621	case VP0_MODE_PS2:
622		ppb_MS_GET_init(ppbus, vpo->vpo_dev, ps2_inbyte_submicroseq);
623		ppb_MS_PUT_init(ppbus, vpo->vpo_dev, spp_outbyte_submicroseq);
624		printf("vpo%d: PS2 mode\n", vpo->vpo_unit);
625		break;
626	case VP0_MODE_NIBBLE:
627		ppb_MS_GET_init(ppbus, vpo->vpo_dev, vpo->vpo_nibble_inbyte_msq);
628		ppb_MS_PUT_init(ppbus, vpo->vpo_dev, spp_outbyte_submicroseq);
629		printf("vpo%d: NIBBLE mode\n", vpo->vpo_unit);
630		break;
631	default:
632		panic("vpo: unknown mode %d", vpo->vpo_mode_found);
633	}
634
635	ppb_release_bus(ppbus, vpo->vpo_dev);
636
637error:
638	return (error);
639}
640
641/*
642 * vpoio_reset_bus()
643 *
644 */
645int
646vpoio_reset_bus(struct vpoio_data *vpo)
647{
648	/* first, connect to the drive */
649	if (vpoio_connect(vpo, PPB_WAIT|PPB_INTR) || !vpoio_in_disk_mode(vpo)) {
650
651#ifdef VP0_DEBUG
652		printf("%s: not in disk mode!\n", __func__);
653#endif
654		/* release ppbus */
655		vpoio_disconnect(vpo);
656		return (1);
657	}
658
659	/* reset the SCSI bus */
660	vpoio_reset(vpo);
661
662	/* then disconnect */
663	vpoio_disconnect(vpo);
664
665	return (0);
666}
667
668/*
669 * vpoio_do_scsi()
670 *
671 * Send an SCSI command
672 *
673 */
674int
675vpoio_do_scsi(struct vpoio_data *vpo, int host, int target, char *command,
676		int clen, char *buffer, int blen, int *result, int *count,
677		int *ret)
678{
679	device_t ppbus = device_get_parent(vpo->vpo_dev);
680	register char r;
681	char l, h = 0;
682	int len, error = 0;
683	register int k;
684
685	/*
686	 * enter disk state, allocate the ppbus
687	 *
688	 * XXX
689	 * Should we allow this call to be interruptible?
690	 * The only way to report the interruption is to return
691	 * EIO do upper SCSI code :^(
692	 */
693	if ((error = vpoio_connect(vpo, PPB_WAIT|PPB_INTR)))
694		return (error);
695
696	if (!vpoio_in_disk_mode(vpo)) {
697		*ret = VP0_ECONNECT; goto error;
698	}
699
700	if ((*ret = vpoio_select(vpo,host,target)))
701		goto error;
702
703	/*
704	 * Send the command ...
705	 *
706	 * set H_SELIN low for vpoio_wait().
707	 */
708	ppb_wctr(ppbus, H_AUTO | H_nSELIN | H_INIT | H_STROBE);
709
710	for (k = 0; k < clen; k++) {
711		if (vpoio_wait(vpo, VP0_FAST_SPINTMO) != (char)0xe0) {
712			*ret = VP0_ECMD_TIMEOUT;
713			goto error;
714		}
715		if (vpoio_outstr(vpo, &command[k], 1)) {
716			*ret = VP0_EPPDATA_TIMEOUT;
717			goto error;
718		}
719	}
720
721	/*
722	 * Completion ...
723	 */
724
725	*count = 0;
726	for (;;) {
727
728		if (!(r = vpoio_wait(vpo, VP0_LOW_SPINTMO))) {
729			*ret = VP0_ESTATUS_TIMEOUT; goto error;
730		}
731
732		/* stop when the ZIP wants to send status */
733		if (r == (char)0xf0)
734			break;
735
736		if (*count >= blen) {
737			*ret = VP0_EDATA_OVERFLOW;
738			goto error;
739		}
740
741		/* if in EPP mode or writing bytes, try to transfer a sector
742		 * otherwise, just send one byte
743		 */
744		if (PPB_IN_EPP_MODE(ppbus) || r == (char)0xc0)
745			len = (((blen - *count) >= VP0_SECTOR_SIZE)) ?
746				VP0_SECTOR_SIZE : 1;
747		else
748			len = 1;
749
750		/* ZIP wants to send data? */
751		if (r == (char)0xc0)
752			error = vpoio_outstr(vpo, &buffer[*count], len);
753		else
754			error = vpoio_instr(vpo, &buffer[*count], len);
755
756		if (error) {
757			*ret = error;
758			goto error;
759		}
760
761		*count += len;
762	}
763
764	if (vpoio_instr(vpo, &l, 1)) {
765		*ret = VP0_EOTHER; goto error;
766	}
767
768	/* check if the ZIP wants to send more status */
769	if (vpoio_wait(vpo, VP0_FAST_SPINTMO) == (char)0xf0)
770		if (vpoio_instr(vpo, &h, 1)) {
771			*ret = VP0_EOTHER+2; goto error;
772		}
773
774	*result = ((int) h << 8) | ((int) l & 0xff);
775
776error:
777	/* return to printer state, release the ppbus */
778	vpoio_disconnect(vpo);
779	return (0);
780}
781