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