immio.c revision 71633
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 * $FreeBSD: head/sys/dev/ppbus/immio.c 71633 2001-01-25 13:07:55Z nsouch $
28 *
29 */
30
31/*
32 * Iomega ZIP+ Matchmaker Parallel Port Interface driver
33 *
34 * Thanks to David Campbell work on the Linux driver and the Iomega specs
35 * Thanks to Thiebault Moeglin for the drive
36 */
37#ifdef _KERNEL
38#include <sys/param.h>
39#include <sys/systm.h>
40#include <sys/module.h>
41#include <sys/bus.h>
42#include <sys/malloc.h>
43
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	vpo->vpo_mode_found = VP0_MODE_UNDEFINED;
377	error = 1;
378
379	/* try to enter EPP mode since vpoio failure put the bus in NIBBLE */
380	if (ppb_set_mode(ppbus, PPB_EPP) != -1) {
381		imm_connect(vpo, PPB_DONTWAIT, &error, 0);
382	}
383
384	/* if connection failed try PS/2 then NIBBLE modes */
385	if (error) {
386		if (ppb_set_mode(ppbus, PPB_PS2) != -1) {
387			imm_connect(vpo, PPB_DONTWAIT, &error, 0);
388		}
389		if (error) {
390			if (ppb_set_mode(ppbus, PPB_NIBBLE) != -1) {
391				imm_connect(vpo, PPB_DONTWAIT, &error, 0);
392				if (error)
393					goto error;
394				vpo->vpo_mode_found = VP0_MODE_NIBBLE;
395			} else {
396				printf("imm%d: NIBBLE mode unavailable!\n", vpo->vpo_unit);
397				goto error;
398			}
399		} else {
400			vpo->vpo_mode_found = VP0_MODE_PS2;
401		}
402	} else {
403		vpo->vpo_mode_found = VP0_MODE_EPP;
404	}
405
406	/* send SCSI reset signal */
407	ppb_MS_microseq(ppbus, vpo->vpo_dev, reset_microseq, NULL);
408
409	/* release the bus now */
410	imm_disconnect(vpo, &error, 1);
411
412	/* ensure we are disconnected or daisy chained peripheral
413	 * may cause serious problem to the disk */
414
415	if (error) {
416		if (bootverbose)
417			printf("imm%d: can't disconnect from the drive\n",
418				vpo->vpo_unit);
419		goto error;
420	}
421
422	return (0);
423
424error:
425	ppb_release_bus(ppbus, vpo->vpo_dev);
426	return (VP0_EINITFAILED);
427}
428
429/*
430 * imm_outstr()
431 */
432static int
433imm_outstr(struct vpoio_data *vpo, char *buffer, int size)
434{
435	device_t ppbus = device_get_parent(vpo->vpo_dev);
436	int error = 0;
437
438	if (PPB_IN_EPP_MODE(ppbus))
439		ppb_reset_epp_timeout(ppbus);
440
441	ppb_MS_exec(ppbus, vpo->vpo_dev, MS_OP_PUT, (union ppb_insarg)buffer,
442		(union ppb_insarg)size, (union ppb_insarg)MS_UNKNOWN, &error);
443
444	return (error);
445}
446
447/*
448 * imm_instr()
449 */
450static int
451imm_instr(struct vpoio_data *vpo, char *buffer, int size)
452{
453	device_t ppbus = device_get_parent(vpo->vpo_dev);
454	int error = 0;
455
456	if (PPB_IN_EPP_MODE(ppbus))
457		ppb_reset_epp_timeout(ppbus);
458
459	ppb_MS_exec(ppbus, vpo->vpo_dev, MS_OP_GET, (union ppb_insarg)buffer,
460		(union ppb_insarg)size, (union ppb_insarg)MS_UNKNOWN, &error);
461
462	return (error);
463}
464
465static char
466imm_select(struct vpoio_data *vpo, int initiator, int target)
467{
468	DECLARE_SELECT_MICROSEQUENCE;
469	device_t ppbus = device_get_parent(vpo->vpo_dev);
470	int ret;
471
472	/* initialize the select microsequence */
473	ppb_MS_init_msq(select_microseq, 1,
474			SELECT_TARGET, 1 << initiator | 1 << target);
475
476	ppb_MS_microseq(ppbus, vpo->vpo_dev, select_microseq, &ret);
477
478	return (ret);
479}
480
481/*
482 * imm_wait()
483 *
484 * H_SELIN must be low.
485 *
486 * XXX should be ported to microseq
487 */
488static char
489imm_wait(struct vpoio_data *vpo, int tmo)
490{
491	device_t ppbus = device_get_parent(vpo->vpo_dev);
492	register int	k;
493	register char	r;
494
495	ppb_wctr(ppbus, 0xc);
496
497	/* XXX should be ported to microseq */
498	k = 0;
499	while (!((r = ppb_rstr(ppbus)) & 0x80) && (k++ < tmo))
500		DELAY(1);
501
502	/*
503	 * Return some status information.
504	 * Semantics :	0x88 = ZIP+ wants more data
505	 *		0x98 = ZIP+ wants to send more data
506	 *		0xa8 = ZIP+ wants command
507	 *		0xb8 = end of transfer, ZIP+ is sending status
508	 */
509	ppb_wctr(ppbus, 0x4);
510	if (k < tmo)
511	  return (r & 0xb8);
512
513	return (0);			   /* command timed out */
514}
515
516static int
517imm_negociate(struct vpoio_data *vpo)
518{
519	DECLARE_NEGOCIATE_MICROSEQ;
520	device_t ppbus = device_get_parent(vpo->vpo_dev);
521	int negociate_mode;
522	int ret;
523
524	if (PPB_IN_NIBBLE_MODE(ppbus))
525		negociate_mode = 0;
526	else if (PPB_IN_PS2_MODE(ppbus))
527		negociate_mode = 1;
528	else
529		return (0);
530
531#if 0 /* XXX use standalone code not to depend on ppb_1284 code yet */
532	ret = ppb_1284_negociate(ppbus, negociate_mode);
533
534	if (ret)
535		return (VP0_ENEGOCIATE);
536#endif
537
538	ppb_MS_init_msq(negociate_microseq, 1,
539			NEGOCIATED_MODE, negociate_mode);
540
541	ppb_MS_microseq(ppbus, vpo->vpo_dev, negociate_microseq, &ret);
542
543	return (ret);
544}
545
546/*
547 * imm_probe()
548 *
549 * Low level probe of vpo device
550 *
551 */
552int
553imm_probe(device_t dev, struct vpoio_data *vpo)
554{
555	int error;
556
557	/* ppbus dependent initialisation */
558	vpo->vpo_dev = dev;
559
560	/* now, try to initialise the drive */
561	if ((error = imm_detect(vpo))) {
562		return (error);
563	}
564
565	return (0);
566}
567
568/*
569 * imm_attach()
570 *
571 * Low level attachment of vpo device
572 *
573 */
574int
575imm_attach(struct vpoio_data *vpo)
576{
577	device_t ppbus = device_get_parent(vpo->vpo_dev);
578
579	/*
580	 * Initialize microsequence code
581	 */
582	vpo->vpo_nibble_inbyte_msq = (struct ppb_microseq *)malloc(
583		sizeof(nibble_inbyte_submicroseq), M_DEVBUF, M_NOWAIT);
584
585	if (!vpo->vpo_nibble_inbyte_msq)
586		return (ENXIO);
587
588	bcopy((void *)nibble_inbyte_submicroseq,
589		(void *)vpo->vpo_nibble_inbyte_msq,
590		sizeof(nibble_inbyte_submicroseq));
591
592	INIT_NIBBLE_INBYTE_SUBMICROSEQ(vpo);
593
594	/*
595	 * Initialize mode dependent in/out microsequences
596	 */
597	ppb_request_bus(ppbus, vpo->vpo_dev, PPB_WAIT);
598
599	/* ppbus automatically restore the last mode entered during detection */
600	switch (vpo->vpo_mode_found) {
601	case VP0_MODE_EPP:
602		ppb_MS_GET_init(ppbus, vpo->vpo_dev, epp17_instr);
603		ppb_MS_PUT_init(ppbus, vpo->vpo_dev, epp17_outstr);
604		printf("imm%d: EPP mode\n", vpo->vpo_unit);
605		break;
606	case VP0_MODE_PS2:
607		ppb_MS_GET_init(ppbus, vpo->vpo_dev, ps2_inbyte_submicroseq);
608		ppb_MS_PUT_init(ppbus, vpo->vpo_dev, spp_outbyte_submicroseq);
609		printf("imm%d: PS2 mode\n", vpo->vpo_unit);
610		break;
611	case VP0_MODE_NIBBLE:
612		ppb_MS_GET_init(ppbus, vpo->vpo_dev, vpo->vpo_nibble_inbyte_msq);
613		ppb_MS_PUT_init(ppbus, vpo->vpo_dev, spp_outbyte_submicroseq);
614		printf("imm%d: NIBBLE mode\n", vpo->vpo_unit);
615		break;
616	default:
617		panic("imm: unknown mode %d", vpo->vpo_mode_found);
618	}
619
620	ppb_release_bus(ppbus, vpo->vpo_dev);
621
622	return (0);
623}
624
625/*
626 * imm_reset_bus()
627 *
628 */
629int
630imm_reset_bus(struct vpoio_data *vpo)
631{
632	device_t ppbus = device_get_parent(vpo->vpo_dev);
633	int disconnected;
634
635	/* first, connect to the drive and request the bus */
636	imm_connect(vpo, PPB_WAIT|PPB_INTR, &disconnected, 1);
637
638	if (!disconnected) {
639
640		/* reset the SCSI bus */
641		ppb_MS_microseq(ppbus, vpo->vpo_dev, reset_microseq, NULL);
642
643		/* then disconnect */
644		imm_disconnect(vpo, NULL, 1);
645	}
646
647	return (0);
648}
649
650/*
651 * imm_do_scsi()
652 *
653 * Send an SCSI command
654 *
655 */
656int
657imm_do_scsi(struct vpoio_data *vpo, int host, int target, char *command,
658		int clen, char *buffer, int blen, int *result, int *count,
659		int *ret)
660{
661	device_t ppbus = device_get_parent(vpo->vpo_dev);
662	register char r;
663	char l, h = 0;
664	int len, error = 0, not_connected = 0;
665	register int k;
666	int negociated = 0;
667
668	/*
669	 * enter disk state, allocate the ppbus
670	 *
671	 * XXX
672	 * Should we allow this call to be interruptible?
673	 * The only way to report the interruption is to return
674	 * EIO do upper SCSI code :^(
675	 */
676	if ((error = imm_connect(vpo, PPB_WAIT|PPB_INTR, &not_connected, 1)))
677		return (error);
678
679	if (not_connected) {
680		*ret = VP0_ECONNECT; goto error;
681	}
682
683	/*
684	 * Select the drive ...
685	 */
686	if ((*ret = imm_select(vpo,host,target)))
687		goto error;
688
689	/*
690	 * Send the command ...
691	 */
692	for (k = 0; k < clen; k+=2) {
693		if (imm_wait(vpo, VP0_FAST_SPINTMO) != (char)0xa8) {
694			*ret = VP0_ECMD_TIMEOUT;
695			goto error;
696		}
697		if (imm_outstr(vpo, &command[k], 2)) {
698			*ret = VP0_EPPDATA_TIMEOUT;
699			goto error;
700		}
701	}
702
703	if (!(r = imm_wait(vpo, VP0_LOW_SPINTMO))) {
704		*ret = VP0_ESTATUS_TIMEOUT; goto error;
705	}
706
707	if ((r & 0x30) == 0x10) {
708		if (imm_negociate(vpo)) {
709			*ret = VP0_ENEGOCIATE;
710			goto error;
711		} else
712			negociated = 1;
713	}
714
715	/*
716	 * Complete transfer ...
717	 */
718	*count = 0;
719	for (;;) {
720
721		if (!(r = imm_wait(vpo, VP0_LOW_SPINTMO))) {
722			*ret = VP0_ESTATUS_TIMEOUT; goto error;
723		}
724
725		/* stop when the ZIP+ wants to send status */
726		if (r == (char)0xb8)
727			break;
728
729		if (*count >= blen) {
730			*ret = VP0_EDATA_OVERFLOW;
731			goto error;
732		}
733
734		/* ZIP+ wants to send data? */
735		if (r == (char)0x88) {
736			len = (((blen - *count) >= VP0_SECTOR_SIZE)) ?
737				VP0_SECTOR_SIZE : 2;
738
739			error = imm_outstr(vpo, &buffer[*count], len);
740		} else {
741			if (!PPB_IN_EPP_MODE(ppbus))
742				len = 1;
743			else
744				len = (((blen - *count) >= VP0_SECTOR_SIZE)) ?
745					VP0_SECTOR_SIZE : 1;
746
747			error = imm_instr(vpo, &buffer[*count], len);
748		}
749
750		if (error) {
751			*ret = error;
752			goto error;
753		}
754
755		*count += len;
756	}
757
758	if ((PPB_IN_NIBBLE_MODE(ppbus) ||
759			PPB_IN_PS2_MODE(ppbus)) && negociated)
760		ppb_MS_microseq(ppbus, vpo->vpo_dev, transfer_epilog, NULL);
761
762	/*
763	 * Retrieve status ...
764	 */
765	if (imm_negociate(vpo)) {
766		*ret = VP0_ENEGOCIATE;
767		goto error;
768	} else
769		negociated = 1;
770
771	if (imm_instr(vpo, &l, 1)) {
772		*ret = VP0_EOTHER; goto error;
773	}
774
775	/* check if the ZIP+ wants to send more status */
776	if (imm_wait(vpo, VP0_FAST_SPINTMO) == (char)0xb8)
777		if (imm_instr(vpo, &h, 1)) {
778			*ret = VP0_EOTHER+2; goto error;
779		}
780
781	/* Experience showed that we should discard this */
782	if (h == -1)
783		h = 0;
784
785	*result = ((int) h << 8) | ((int) l & 0xff);
786
787error:
788	if ((PPB_IN_NIBBLE_MODE(ppbus) ||
789			PPB_IN_PS2_MODE(ppbus)) && negociated)
790		ppb_MS_microseq(ppbus, vpo->vpo_dev, transfer_epilog, NULL);
791
792	/* return to printer state, release the ppbus */
793	imm_disconnect(vpo, NULL, 1);
794
795	return (0);
796}
797