immio.c revision 331643
1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 1998, 1999 Nicolas Souchu
5 * Copyright (c) 2001 Alcove - Nicolas Souchu
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 *
29 *
30 */
31
32#include <sys/cdefs.h>
33__FBSDID("$FreeBSD: stable/11/sys/dev/ppbus/immio.c 331643 2018-03-27 18:52:27Z dim $");
34
35/*
36 * Iomega ZIP+ Matchmaker Parallel Port Interface driver
37 *
38 * Thanks to David Campbell work on the Linux driver and the Iomega specs
39 * Thanks to Thiebault Moeglin for the drive
40 */
41#ifdef _KERNEL
42#include <sys/param.h>
43#include <sys/systm.h>
44#include <sys/module.h>
45#include <sys/bus.h>
46#include <sys/malloc.h>
47
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 WAIT_RET		MS_PARAM(7, 2, MS_TYP_PTR)
71#define WAIT_TMO		MS_PARAM(1, 0, MS_TYP_INT)
72
73#define DECLARE_WAIT_MICROSEQUENCE \
74struct ppb_microseq wait_microseq[] = {					\
75	MS_CASS(0x0c),							\
76	MS_SET(MS_UNKNOWN),						\
77	/* loop */							\
78	MS_BRSET(nBUSY, 4 /* ready */),					\
79	MS_DBRA(-2 /* loop */),						\
80	MS_CASS(0x04),							\
81	MS_RET(1), /* timed out */					\
82	/* ready */							\
83	MS_CASS(0x04),							\
84	MS_RFETCH(MS_REG_STR, 0xb8, MS_UNKNOWN ),			\
85	MS_RET(0) /* no error */					\
86}
87
88#define SELECT_TARGET		MS_PARAM(6, 1, MS_TYP_CHA)
89
90#define DECLARE_SELECT_MICROSEQUENCE \
91struct ppb_microseq select_microseq[] = {				\
92	MS_CASS(0xc),							\
93	/* first, check there is nothing holding onto the bus */	\
94	MS_SET(VP0_SELTMO),						\
95/* _loop: */								\
96	MS_BRCLEAR(0x8, 2 /* _ready */),				\
97	MS_DBRA(-2 /* _loop */),					\
98	MS_RET(2),			/* bus busy */			\
99/* _ready: */								\
100	MS_CASS(0x4),							\
101	MS_DASS(MS_UNKNOWN /* 0x80 | 1 << target */),			\
102	MS_DELAY(1),							\
103	MS_CASS(0xc),							\
104	MS_CASS(0xd),							\
105	/* now, wait until the drive is ready */			\
106	MS_SET(VP0_SELTMO),						\
107/* loop: */								\
108	MS_BRSET(0x8, 3 /* ready */),					\
109	MS_DBRA(-2 /* loop */),						\
110/* error: */								\
111	MS_CASS(0xc),							\
112	MS_RET(VP0_ESELECT_TIMEOUT),					\
113/* ready: */								\
114	MS_CASS(0xc),							\
115	MS_RET(0)							\
116}
117
118static struct ppb_microseq transfer_epilog[] = {
119	MS_CASS(0x4),
120	MS_CASS(0xc),
121	MS_CASS(0xe),
122	MS_CASS(0x4),
123	MS_RET(0)
124};
125
126#define CPP_S1		MS_PARAM(10, 2, MS_TYP_PTR)
127#define CPP_S2		MS_PARAM(13, 2, MS_TYP_PTR)
128#define CPP_S3		MS_PARAM(16, 2, MS_TYP_PTR)
129#define CPP_PARAM	MS_PARAM(17, 1, MS_TYP_CHA)
130
131#define DECLARE_CPP_MICROSEQ \
132struct ppb_microseq cpp_microseq[] = {					\
133	MS_CASS(0x0c), MS_DELAY(2),					\
134	MS_DASS(0xaa), MS_DELAY(10),					\
135	MS_DASS(0x55), MS_DELAY(10),					\
136	MS_DASS(0x00), MS_DELAY(10),					\
137	MS_DASS(0xff), MS_DELAY(10),					\
138	MS_RFETCH(MS_REG_STR, 0xb8, MS_UNKNOWN /* &s1 */),		\
139	MS_DASS(0x87), MS_DELAY(10),					\
140	MS_RFETCH(MS_REG_STR, 0xb8, MS_UNKNOWN /* &s2 */),		\
141	MS_DASS(0x78), MS_DELAY(10),					\
142	MS_RFETCH(MS_REG_STR, 0x38, MS_UNKNOWN /* &s3 */),		\
143	MS_DASS(MS_UNKNOWN /* param */),				\
144	MS_DELAY(2),							\
145	MS_CASS(0x0c), MS_DELAY(10),					\
146	MS_CASS(0x0d), MS_DELAY(2),					\
147	MS_CASS(0x0c), MS_DELAY(10),					\
148	MS_DASS(0xff), MS_DELAY(10),					\
149	MS_RET(0)							\
150}
151
152#define NEGOCIATED_MODE		MS_PARAM(2, 1, MS_TYP_CHA)
153
154#define DECLARE_NEGOCIATE_MICROSEQ \
155struct ppb_microseq negociate_microseq[] = {				\
156	MS_CASS(0x4),							\
157	MS_DELAY(5),							\
158	MS_DASS(MS_UNKNOWN /* mode */),					\
159	MS_DELAY(100),							\
160	MS_CASS(0x6),							\
161	MS_DELAY(5),							\
162	MS_BRSET(0x20, 5 /* continue */),				\
163	MS_DELAY(5),							\
164	MS_CASS(0x7),							\
165	MS_DELAY(5),							\
166	MS_CASS(0x6),							\
167	MS_RET(VP0_ENEGOCIATE),						\
168/* continue: */								\
169	MS_DELAY(5),							\
170	MS_CASS(0x7),							\
171	MS_DELAY(5),							\
172	MS_CASS(0x6),							\
173	MS_RET(0)							\
174}
175
176#define INB_NIBBLE_L MS_PARAM(3, 2, MS_TYP_PTR)
177#define INB_NIBBLE_H MS_PARAM(6, 2, MS_TYP_PTR)
178#define INB_NIBBLE_F MS_PARAM(9, 0, MS_TYP_FUN)
179#define INB_NIBBLE_P MS_PARAM(9, 1, MS_TYP_PTR)
180
181/*
182 * This is the sub-microseqence for MS_GET in NIBBLE mode
183 * Retrieve the two nibbles and call the C function to generate the character
184 * and store it in the buffer (see nibble_inbyte_hook())
185 */
186
187#define DECLARE_NIBBLE_INBYTE_SUBMICROSEQ \
188struct ppb_microseq nibble_inbyte_submicroseq[] = {			\
189	MS_CASS(0x4),							\
190/* loop: */								\
191	MS_CASS(0x6),							\
192	MS_DELAY(1),							\
193	MS_RFETCH(MS_REG_STR, MS_FETCH_ALL, MS_UNKNOWN /* low nibble */),\
194	MS_CASS(0x5),							\
195	MS_DELAY(1),							\
196	MS_RFETCH(MS_REG_STR, MS_FETCH_ALL, MS_UNKNOWN /* high nibble */),\
197	MS_CASS(0x4),							\
198	MS_DELAY(1),							\
199	/* do a C call to format the received nibbles */		\
200	MS_C_CALL(MS_UNKNOWN /* C hook */, MS_UNKNOWN /* param */),	\
201	MS_DBRA(-7 /* loop */),						\
202	MS_RET(0)							\
203}
204
205static struct ppb_microseq reset_microseq[] = {
206	MS_CASS(0x04),
207	MS_DASS(0x40),
208	MS_DELAY(1),
209	MS_CASS(0x0c),
210	MS_CASS(0x0d),
211	MS_DELAY(50),
212	MS_CASS(0x0c),
213	MS_CASS(0x04),
214	MS_RET(0)
215};
216
217/*
218 * nibble_inbyte_hook()
219 *
220 * Formats high and low nibble into a character
221 */
222static int
223nibble_inbyte_hook (void *p, char *ptr)
224{
225	struct vpo_nibble *s = (struct vpo_nibble *)p;
226
227	/* increment the buffer pointer */
228	*ptr = ((s->l >> 4) & 0x0f) + (s->h & 0xf0);
229
230	return (0);
231}
232
233/*
234 * This is the sub-microseqence for MS_GET in PS2 mode
235 */
236static struct ppb_microseq ps2_inbyte_submicroseq[] = {
237	  MS_CASS(0x4),
238
239/* loop: */
240	  MS_CASS(PCD | 0x6),
241	  MS_RFETCH_P(1, MS_REG_DTR, MS_FETCH_ALL),
242	  MS_CASS(PCD | 0x5),
243	  MS_DBRA(-4 /* loop */),
244
245	  MS_RET(0)
246};
247
248/*
249 * This is the sub-microsequence for MS_PUT in both NIBBLE and PS2 modes
250 */
251static struct ppb_microseq spp_outbyte_submicroseq[] = {
252	  MS_CASS(0x4),
253
254/* loop: */
255	  MS_RASSERT_P(1, MS_REG_DTR),
256	  MS_CASS(0x5),
257	  MS_DBRA(0),				/* decrement counter */
258	  MS_RASSERT_P(1, MS_REG_DTR),
259	  MS_CASS(0x0),
260	  MS_DBRA(-6 /* loop */),
261
262	  /* return from the put call */
263	  MS_CASS(0x4),
264	  MS_RET(0)
265};
266
267/* EPP 1.7 microsequences, ptr and len set at runtime */
268static struct ppb_microseq epp17_outstr[] = {
269	  MS_CASS(0x4),
270	  MS_RASSERT_P(MS_ACCUM, MS_REG_EPP_D),
271	  MS_CASS(0xc),
272	  MS_RET(0),
273};
274
275static struct ppb_microseq epp17_instr[] = {
276	  MS_CASS(PCD | 0x4),
277	  MS_RFETCH_P(MS_ACCUM, MS_REG_EPP_D, MS_FETCH_ALL),
278	  MS_CASS(PCD | 0xc),
279	  MS_RET(0),
280};
281
282static int
283imm_disconnect(struct vpoio_data *vpo, int *connected, int release_bus)
284{
285	DECLARE_CPP_MICROSEQ;
286
287	device_t ppbus = device_get_parent(vpo->vpo_dev);
288	char s1, s2, s3;
289	int ret;
290
291	/* all should be ok */
292	if (connected)
293		*connected = 0;
294
295	ppb_MS_init_msq(cpp_microseq, 4, CPP_S1, (void *)&s1,
296			CPP_S2, (void *)&s2, CPP_S3, (void *)&s3,
297			CPP_PARAM, 0x30);
298
299	ppb_MS_microseq(ppbus, vpo->vpo_dev, cpp_microseq, &ret);
300
301	if ((s1 != (char)0xb8 || s2 != (char)0x18 || s3 != (char)0x38)) {
302		if (bootverbose)
303			device_printf(vpo->vpo_dev,
304			    "(disconnect) s1=0x%x s2=0x%x, s3=0x%x\n",
305			    s1 & 0xff, s2 & 0xff, s3 & 0xff);
306		if (connected)
307			*connected = VP0_ECONNECT;
308	}
309
310	if (release_bus)
311		return (ppb_release_bus(ppbus, vpo->vpo_dev));
312	else
313		return (0);
314}
315
316/*
317 * how	: PPB_WAIT or PPB_DONTWAIT
318 */
319static int
320imm_connect(struct vpoio_data *vpo, int how, int *disconnected, int request_bus)
321{
322	DECLARE_CPP_MICROSEQ;
323
324	device_t ppbus = device_get_parent(vpo->vpo_dev);
325	char s1, s2, s3;
326	int error;
327	int ret;
328
329	/* all should be ok */
330	if (disconnected)
331		*disconnected = 0;
332
333	if (request_bus)
334		if ((error = ppb_request_bus(ppbus, vpo->vpo_dev, how)))
335			return (error);
336
337	ppb_MS_init_msq(cpp_microseq, 3, CPP_S1, (void *)&s1,
338			CPP_S2, (void *)&s2, CPP_S3, (void *)&s3);
339
340	/* select device 0 in compatible mode */
341	ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0xe0);
342	ppb_MS_microseq(ppbus, vpo->vpo_dev, cpp_microseq, &ret);
343
344	/* disconnect all devices */
345	ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0x30);
346	ppb_MS_microseq(ppbus, vpo->vpo_dev, cpp_microseq, &ret);
347
348	if (PPB_IN_EPP_MODE(ppbus))
349		ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0x28);
350	else
351		ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0xe0);
352
353	ppb_MS_microseq(ppbus, vpo->vpo_dev, cpp_microseq, &ret);
354
355	if ((s1 != (char)0xb8 || s2 != (char)0x18 || s3 != (char)0x30)) {
356		if (bootverbose)
357			device_printf(vpo->vpo_dev,
358			    "(connect) s1=0x%x s2=0x%x, s3=0x%x\n",
359			    s1 & 0xff, s2 & 0xff, s3 & 0xff);
360		if (disconnected)
361			*disconnected = VP0_ECONNECT;
362	}
363
364	return (0);
365}
366
367/*
368 * imm_detect()
369 *
370 * Detect and initialise the VP0 adapter.
371 */
372static int
373imm_detect(struct vpoio_data *vpo)
374{
375	device_t ppbus = device_get_parent(vpo->vpo_dev);
376	int error;
377
378	if ((error = ppb_request_bus(ppbus, vpo->vpo_dev, PPB_DONTWAIT)))
379		return (error);
380
381	/* disconnect the drive, keep the bus */
382	imm_disconnect(vpo, NULL, 0);
383
384	vpo->vpo_mode_found = VP0_MODE_UNDEFINED;
385	error = 1;
386
387	/* try to enter EPP mode since vpoio failure put the bus in NIBBLE */
388	if (ppb_set_mode(ppbus, PPB_EPP) != -1) {
389		imm_connect(vpo, PPB_DONTWAIT, &error, 0);
390	}
391
392	/* if connection failed try PS/2 then NIBBLE modes */
393	if (error) {
394		if (ppb_set_mode(ppbus, PPB_PS2) != -1) {
395			imm_connect(vpo, PPB_DONTWAIT, &error, 0);
396		}
397		if (error) {
398			if (ppb_set_mode(ppbus, PPB_NIBBLE) != -1) {
399				imm_connect(vpo, PPB_DONTWAIT, &error, 0);
400				if (error)
401					goto error;
402				vpo->vpo_mode_found = VP0_MODE_NIBBLE;
403			} else {
404				device_printf(vpo->vpo_dev,
405				    "NIBBLE mode unavailable!\n");
406				goto error;
407			}
408		} else {
409			vpo->vpo_mode_found = VP0_MODE_PS2;
410		}
411	} else {
412		vpo->vpo_mode_found = VP0_MODE_EPP;
413	}
414
415	/* send SCSI reset signal */
416	ppb_MS_microseq(ppbus, vpo->vpo_dev, reset_microseq, NULL);
417
418	/* release the bus now */
419	imm_disconnect(vpo, &error, 1);
420
421	/* ensure we are disconnected or daisy chained peripheral
422	 * may cause serious problem to the disk */
423
424	if (error) {
425		if (bootverbose)
426			device_printf(vpo->vpo_dev,
427			    "can't disconnect from the drive\n");
428		goto error;
429	}
430
431	return (0);
432
433error:
434	ppb_release_bus(ppbus, vpo->vpo_dev);
435	return (VP0_EINITFAILED);
436}
437
438/*
439 * imm_outstr()
440 */
441static int
442imm_outstr(struct vpoio_data *vpo, char *buffer, int size)
443{
444	device_t ppbus = device_get_parent(vpo->vpo_dev);
445	int error = 0;
446
447	if (PPB_IN_EPP_MODE(ppbus))
448		ppb_reset_epp_timeout(ppbus);
449
450	ppb_MS_exec(ppbus, vpo->vpo_dev, MS_OP_PUT, (union ppb_insarg)buffer,
451		(union ppb_insarg)size, (union ppb_insarg)MS_UNKNOWN, &error);
452
453	return (error);
454}
455
456/*
457 * imm_instr()
458 */
459static int
460imm_instr(struct vpoio_data *vpo, char *buffer, int size)
461{
462	device_t ppbus = device_get_parent(vpo->vpo_dev);
463	int error = 0;
464
465	if (PPB_IN_EPP_MODE(ppbus))
466		ppb_reset_epp_timeout(ppbus);
467
468	ppb_MS_exec(ppbus, vpo->vpo_dev, MS_OP_GET, (union ppb_insarg)buffer,
469		(union ppb_insarg)size, (union ppb_insarg)MS_UNKNOWN, &error);
470
471	return (error);
472}
473
474static char
475imm_select(struct vpoio_data *vpo, int initiator, int target)
476{
477	DECLARE_SELECT_MICROSEQUENCE;
478	device_t ppbus = device_get_parent(vpo->vpo_dev);
479	int ret;
480
481	/* initialize the select microsequence */
482	ppb_MS_init_msq(select_microseq, 1,
483			SELECT_TARGET, 1 << initiator | 1 << target);
484
485	ppb_MS_microseq(ppbus, vpo->vpo_dev, select_microseq, &ret);
486
487	return (ret);
488}
489
490/*
491 * imm_wait()
492 *
493 * H_SELIN must be low.
494 *
495 */
496static char
497imm_wait(struct vpoio_data *vpo, int tmo)
498{
499	DECLARE_WAIT_MICROSEQUENCE;
500
501	device_t ppbus = device_get_parent(vpo->vpo_dev);
502	int ret, err;
503
504	/*
505	 * Return some status information.
506	 * Semantics :	0x88 = ZIP+ wants more data
507	 *		0x98 = ZIP+ wants to send more data
508	 *		0xa8 = ZIP+ wants command
509	 *		0xb8 = end of transfer, ZIP+ is sending status
510	 */
511
512	ppb_MS_init_msq(wait_microseq, 2,
513			WAIT_RET, (void *)&ret,
514			WAIT_TMO, tmo);
515
516	ppb_MS_microseq(ppbus, vpo->vpo_dev, wait_microseq, &err);
517
518	if (err)
519		return (0);			   /* command timed out */
520
521	return(ret);
522}
523
524static int
525imm_negociate(struct vpoio_data *vpo)
526{
527	DECLARE_NEGOCIATE_MICROSEQ;
528	device_t ppbus = device_get_parent(vpo->vpo_dev);
529	int negociate_mode;
530	int ret;
531
532	if (PPB_IN_NIBBLE_MODE(ppbus))
533		negociate_mode = 0;
534	else if (PPB_IN_PS2_MODE(ppbus))
535		negociate_mode = 1;
536	else
537		return (0);
538
539#if 0 /* XXX use standalone code not to depend on ppb_1284 code yet */
540	ret = ppb_1284_negociate(ppbus, negociate_mode);
541
542	if (ret)
543		return (VP0_ENEGOCIATE);
544#endif
545
546	ppb_MS_init_msq(negociate_microseq, 1,
547			NEGOCIATED_MODE, negociate_mode);
548
549	ppb_MS_microseq(ppbus, vpo->vpo_dev, negociate_microseq, &ret);
550
551	return (ret);
552}
553
554/*
555 * imm_probe()
556 *
557 * Low level probe of vpo device
558 *
559 */
560int
561imm_probe(device_t dev, struct vpoio_data *vpo)
562{
563	int error;
564
565	/* ppbus dependent initialisation */
566	vpo->vpo_dev = dev;
567
568	/* now, try to initialise the drive */
569	if ((error = imm_detect(vpo))) {
570		return (error);
571	}
572
573	return (0);
574}
575
576/*
577 * imm_attach()
578 *
579 * Low level attachment of vpo device
580 *
581 */
582int
583imm_attach(struct vpoio_data *vpo)
584{
585	DECLARE_NIBBLE_INBYTE_SUBMICROSEQ;
586	device_t ppbus = device_get_parent(vpo->vpo_dev);
587	int error = 0;
588
589	/*
590	 * Initialize microsequence code
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	ppb_lock(ppbus);
612	if ((error = ppb_request_bus(ppbus, vpo->vpo_dev, PPB_WAIT)))
613	    goto error;
614
615	/* ppbus automatically restore the last mode entered during detection */
616	switch (vpo->vpo_mode_found) {
617	case VP0_MODE_EPP:
618		ppb_MS_GET_init(ppbus, vpo->vpo_dev, epp17_instr);
619		ppb_MS_PUT_init(ppbus, vpo->vpo_dev, epp17_outstr);
620		device_printf(vpo->vpo_dev, "EPP mode\n");
621		break;
622	case VP0_MODE_PS2:
623		ppb_MS_GET_init(ppbus, vpo->vpo_dev, ps2_inbyte_submicroseq);
624		ppb_MS_PUT_init(ppbus, vpo->vpo_dev, spp_outbyte_submicroseq);
625		device_printf(vpo->vpo_dev, "PS2 mode\n");
626		break;
627	case VP0_MODE_NIBBLE:
628		ppb_MS_GET_init(ppbus, vpo->vpo_dev, vpo->vpo_nibble_inbyte_msq);
629		ppb_MS_PUT_init(ppbus, vpo->vpo_dev, spp_outbyte_submicroseq);
630		device_printf(vpo->vpo_dev, "NIBBLE mode\n");
631		break;
632	default:
633		panic("imm: unknown mode %d", vpo->vpo_mode_found);
634	}
635
636	ppb_release_bus(ppbus, vpo->vpo_dev);
637 error:
638	ppb_unlock(ppbus);
639	return (error);
640}
641
642/*
643 * imm_reset_bus()
644 *
645 */
646int
647imm_reset_bus(struct vpoio_data *vpo)
648{
649	device_t ppbus = device_get_parent(vpo->vpo_dev);
650	int disconnected;
651
652	/* first, connect to the drive and request the bus */
653	imm_connect(vpo, PPB_WAIT|PPB_INTR, &disconnected, 1);
654
655	if (!disconnected) {
656
657		/* reset the SCSI bus */
658		ppb_MS_microseq(ppbus, vpo->vpo_dev, reset_microseq, NULL);
659
660		/* then disconnect */
661		imm_disconnect(vpo, NULL, 1);
662	}
663
664	return (0);
665}
666
667/*
668 * imm_do_scsi()
669 *
670 * Send an SCSI command
671 *
672 */
673int
674imm_do_scsi(struct vpoio_data *vpo, int host, int target, char *command,
675		int clen, char *buffer, int blen, int *result, int *count,
676		int *ret)
677{
678	device_t ppbus = device_get_parent(vpo->vpo_dev);
679	char r;
680	char l, h = 0;
681	int len, error = 0, not_connected = 0;
682	int k;
683	int negociated = 0;
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 to upper SCSI code :^(
692	 */
693	if ((error = imm_connect(vpo, PPB_WAIT|PPB_INTR, &not_connected, 1)))
694		return (error);
695
696	if (not_connected) {
697		*ret = VP0_ECONNECT;
698		goto error;
699	}
700
701	/*
702	 * Select the drive ...
703	 */
704	if ((*ret = imm_select(vpo,host,target)))
705		goto error;
706
707	/*
708	 * Send the command ...
709	 */
710	for (k = 0; k < clen; k+=2) {
711		if (imm_wait(vpo, VP0_FAST_SPINTMO) != (char)0xa8) {
712			*ret = VP0_ECMD_TIMEOUT;
713			goto error;
714		}
715		if (imm_outstr(vpo, &command[k], 2)) {
716			*ret = VP0_EPPDATA_TIMEOUT;
717			goto error;
718		}
719	}
720
721	if (!(r = imm_wait(vpo, VP0_LOW_SPINTMO))) {
722		*ret = VP0_ESTATUS_TIMEOUT;
723		goto error;
724	}
725
726	if ((r & 0x30) == 0x10) {
727		if (imm_negociate(vpo)) {
728			*ret = VP0_ENEGOCIATE;
729			goto error;
730		} else
731			negociated = 1;
732	}
733
734	/*
735	 * Complete transfer ...
736	 */
737	*count = 0;
738	for (;;) {
739
740		if (!(r = imm_wait(vpo, VP0_LOW_SPINTMO))) {
741			*ret = VP0_ESTATUS_TIMEOUT;
742			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;
793		goto error;
794	}
795
796	/* check if the ZIP+ wants to send more status */
797	if (imm_wait(vpo, VP0_FAST_SPINTMO) == (char)0xb8)
798		if (imm_instr(vpo, &h, 1)) {
799			*ret = VP0_EOTHER + 2;
800			goto error;
801		}
802
803	/* Experience showed that we should discard this */
804	if (h == (char) -1)
805		h = 0;
806
807	*result = ((int) h << 8) | ((int) l & 0xff);
808
809error:
810	if ((PPB_IN_NIBBLE_MODE(ppbus) ||
811			PPB_IN_PS2_MODE(ppbus)) && negociated)
812		ppb_MS_microseq(ppbus, vpo->vpo_dev, transfer_epilog, NULL);
813
814	/* return to printer state, release the ppbus */
815	imm_disconnect(vpo, NULL, 1);
816
817	return (0);
818}
819