immio.c revision 40783
1/*-
2 * Copyright (c) 1998 Nicolas Souchu
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 *
26 *	$Id: immio.c,v 1.3 1998/10/02 20:44:58 nsouch Exp $
27 *
28 */
29
30/*
31 * Iomega ZIP+ Matchmaker Parallel Port Interface driver
32 *
33 * Thanks to David Campbell work on the Linux driver and the Iomega specs
34 * Thanks to Thiebault Moeglin for the drive
35 */
36#ifdef KERNEL
37#include <sys/param.h>
38#include <sys/systm.h>
39#include <sys/malloc.h>
40#include <sys/buf.h>
41
42#include <machine/clock.h>
43
44#endif	/* KERNEL */
45
46#ifdef	KERNEL
47#include <sys/kernel.h>
48#endif /*KERNEL */
49
50#include <dev/ppbus/ppbconf.h>
51#include <dev/ppbus/ppb_msq.h>
52#include <dev/ppbus/vpoio.h>
53#include <dev/ppbus/ppb_1284.h>
54
55#define VP0_SELTMO		5000	/* select timeout */
56#define VP0_FAST_SPINTMO	500000	/* wait status timeout */
57#define VP0_LOW_SPINTMO		5000000	/* wait status timeout */
58
59#define VP0_SECTOR_SIZE	512
60
61/*
62 * Microcode to execute very fast I/O sequences at the lowest bus level.
63 */
64
65#define SELECT_TARGET		MS_PARAM(6, 1, MS_TYP_CHA)
66
67#define DECLARE_SELECT_MICROSEQUENCE					\
68struct ppb_microseq select_microseq[] = {				\
69	MS_CASS(0xc),							\
70	/* first, check there is nothing holding onto the bus */	\
71	MS_SET(VP0_SELTMO),						\
72/* _loop: */								\
73	MS_BRCLEAR(0x8, 3 /* _ready */),				\
74	MS_DBRA(-1 /* _loop */),					\
75	MS_RET(2),			/* bus busy */			\
76/* _ready: */								\
77	MS_CASS(0x4),							\
78	MS_DASS(MS_UNKNOWN /* 0x80 | 1 << target */),			\
79	MS_DELAY(1),							\
80	MS_CASS(0xc),							\
81	MS_CASS(0xd),							\
82	/* now, wait until the drive is ready */			\
83	MS_SET(VP0_SELTMO),						\
84/* loop: */								\
85	MS_BRSET(0x8, 4 /* ready */),					\
86	MS_DBRA(-1 /* loop */),						\
87/* error: */								\
88	MS_CASS(0xc),							\
89	MS_RET(VP0_ESELECT_TIMEOUT),					\
90/* ready: */								\
91	MS_CASS(0xc),							\
92	MS_RET(0)							\
93}
94
95static struct ppb_microseq transfer_epilog[] = {
96	MS_CASS(0x4),
97	MS_CASS(0xc),
98	MS_CASS(0xe),
99	MS_CASS(0x4),
100	MS_RET(0)
101};
102
103#define CPP_S1		MS_PARAM(10, 2, MS_TYP_PTR)
104#define CPP_S2		MS_PARAM(13, 2, MS_TYP_PTR)
105#define CPP_S3		MS_PARAM(16, 2, MS_TYP_PTR)
106#define CPP_PARAM	MS_PARAM(17, 1, MS_TYP_CHA)
107
108#define DECLARE_CPP_MICROSEQ \
109struct ppb_microseq cpp_microseq[] = {					\
110	MS_CASS(0x0c), MS_DELAY(2),					\
111	MS_DASS(0xaa), MS_DELAY(10),					\
112	MS_DASS(0x55), MS_DELAY(10),					\
113	MS_DASS(0x00), MS_DELAY(10),					\
114	MS_DASS(0xff), MS_DELAY(10),					\
115	MS_RFETCH(MS_REG_STR, 0xb8, MS_UNKNOWN /* &s1 */),		\
116	MS_DASS(0x87), MS_DELAY(10),					\
117	MS_RFETCH(MS_REG_STR, 0xb8, MS_UNKNOWN /* &s2 */),		\
118	MS_DASS(0x78), MS_DELAY(10),					\
119	MS_RFETCH(MS_REG_STR, 0x38, MS_UNKNOWN /* &s3 */),		\
120	MS_DASS(MS_UNKNOWN /* param */),				\
121	MS_DELAY(2),							\
122	MS_CASS(0x0c), MS_DELAY(10),					\
123	MS_CASS(0x0d), MS_DELAY(2),					\
124	MS_CASS(0x0c), MS_DELAY(10),					\
125	MS_DASS(0xff), MS_DELAY(10),					\
126	MS_RET(0)							\
127}
128
129#define NEGOCIATED_MODE		MS_PARAM(2, 1, MS_TYP_CHA)
130
131#define DECLARE_NEGOCIATE_MICROSEQ \
132static struct ppb_microseq negociate_microseq[] = { 			\
133	MS_CASS(0x4),							\
134	MS_DELAY(5),							\
135	MS_DASS(MS_UNKNOWN /* mode */),					\
136	MS_DELAY(100),							\
137	MS_CASS(0x6),							\
138	MS_DELAY(5),							\
139	MS_BRSET(0x20, 6 /* continue */),				\
140	MS_DELAY(5),							\
141	MS_CASS(0x7),							\
142	MS_DELAY(5),							\
143	MS_CASS(0x6),							\
144	MS_RET(VP0_ENEGOCIATE),						\
145/* continue: */								\
146	MS_DELAY(5),							\
147	MS_CASS(0x7),							\
148	MS_DELAY(5),							\
149	MS_CASS(0x6),							\
150	MS_RET(0)							\
151}
152
153static struct ppb_microseq reset_microseq[] = {
154	MS_CASS(0x04),
155	MS_DASS(0x40),
156	MS_DELAY(1),
157	MS_CASS(0x0c),
158	MS_CASS(0x0d),
159	MS_DELAY(50),
160	MS_CASS(0x0c),
161	MS_CASS(0x04),
162	MS_RET(0)
163};
164
165/*
166 * nibble_inbyte_hook()
167 *
168 * Formats high and low nibble into a character
169 */
170static int
171nibble_inbyte_hook (void *p, char *ptr)
172{
173	struct vpo_nibble *s = (struct vpo_nibble *)p;
174
175	/* increment the buffer pointer */
176	*ptr = ((s->l >> 4) & 0x0f) + (s->h & 0xf0);
177
178	return (0);
179}
180
181/*
182 * Macro used to initialize each vpoio_data structure during
183 * low level attachment
184 *
185 * XXX should be converted to ppb_MS_init_msq()
186 */
187#define INIT_NIBBLE_INBYTE_SUBMICROSEQ(vpo) {		    	\
188	(vpo)->vpo_nibble_inbyte_msq[6].arg[2].p =		\
189			(void *)&(vpo)->vpo_nibble.h;		\
190	(vpo)->vpo_nibble_inbyte_msq[3].arg[2].p =		\
191			(void *)&(vpo)->vpo_nibble.l;		\
192	(vpo)->vpo_nibble_inbyte_msq[9].arg[0].f =		\
193			nibble_inbyte_hook;			\
194	(vpo)->vpo_nibble_inbyte_msq[9].arg[1].p =		\
195			(void *)&(vpo)->vpo_nibble;		\
196}
197
198/*
199 * This is the sub-microseqence for MS_GET in NIBBLE mode
200 * Retrieve the two nibbles and call the C function to generate the character
201 * and store it in the buffer (see nibble_inbyte_hook())
202 */
203static struct ppb_microseq nibble_inbyte_submicroseq[] = {
204	  MS_CASS(0x4),
205
206/* loop: */
207	  MS_CASS(0x6),
208	  MS_DELAY(1),
209	  MS_RFETCH(MS_REG_STR, MS_FETCH_ALL, MS_UNKNOWN /* low nibble */),
210	  MS_CASS(0x5),
211	  MS_DELAY(1),
212	  MS_RFETCH(MS_REG_STR, MS_FETCH_ALL, MS_UNKNOWN /* high nibble */),
213	  MS_CASS(0x4),
214	  MS_DELAY(1),
215
216	  /* do a C call to format the received nibbles */
217	  MS_C_CALL(MS_UNKNOWN /* C hook */, MS_UNKNOWN /* param */),
218	  MS_DBRA(-6 /* loop */),
219	  MS_RET(0)
220};
221
222/*
223 * This is the sub-microseqence for MS_GET in PS2 mode
224 */
225static struct ppb_microseq ps2_inbyte_submicroseq[] = {
226	  MS_CASS(0x4),
227
228/* loop: */
229	  MS_CASS(PCD | 0x6),
230	  MS_RFETCH_P(1, MS_REG_DTR, MS_FETCH_ALL),
231	  MS_CASS(PCD | 0x5),
232	  MS_DBRA(-3 /* loop */),
233
234	  MS_RET(0)
235};
236
237/*
238 * This is the sub-microsequence for MS_PUT in both NIBBLE and PS2 modes
239 */
240static struct ppb_microseq spp_outbyte_submicroseq[] = {
241	  MS_CASS(0x4),
242
243/* loop: */
244	  MS_RASSERT_P(1, MS_REG_DTR),
245	  MS_CASS(0x5),
246	  MS_DBRA(1),				/* decrement counter */
247	  MS_RASSERT_P(1, MS_REG_DTR),
248	  MS_CASS(0x0),
249	  MS_DBRA(-5 /* loop */),
250
251	  /* return from the put call */
252	  MS_CASS(0x4),
253	  MS_RET(0)
254};
255
256/* EPP 1.7 microsequences, ptr and len set at runtime */
257static struct ppb_microseq epp17_outstr[] = {
258	  MS_CASS(0x4),
259	  MS_RASSERT_P(MS_ACCUM, MS_REG_EPP),
260	  MS_CASS(0xc),
261	  MS_RET(0),
262};
263
264static struct ppb_microseq epp17_instr[] = {
265	  MS_CASS(PCD | 0x4),
266	  MS_RFETCH_P(MS_ACCUM, MS_REG_EPP, MS_FETCH_ALL),
267	  MS_CASS(PCD | 0xc),
268	  MS_RET(0),
269};
270
271static int
272imm_disconnect(struct vpoio_data *vpo, int *connected, int release_bus)
273{
274	DECLARE_CPP_MICROSEQ;
275
276	char s1, s2, s3;
277	int ret;
278
279	/* all should be ok */
280	if (connected)
281		*connected = 0;
282
283	ppb_MS_init_msq(cpp_microseq, 4, CPP_S1, (void *)&s1,
284			CPP_S2, (void *)&s2, CPP_S3, (void *)&s3,
285			CPP_PARAM, 0x30);
286
287	ppb_MS_microseq(&vpo->vpo_dev, cpp_microseq, &ret);
288
289	if ((s1 != (char)0xb8 || s2 != (char)0x18 || s3 != (char)0x38)) {
290		if (bootverbose)
291			printf("imm%d: (disconnect) s1=0x%x s2=0x%x, s3=0x%x\n",
292				vpo->vpo_unit, s1 & 0xff, s2 & 0xff, s3 & 0xff);
293		if (connected)
294			*connected = VP0_ECONNECT;
295	}
296
297	if (release_bus)
298		return (ppb_release_bus(&vpo->vpo_dev));
299	else
300		return (0);
301}
302
303/*
304 * how	: PPB_WAIT or PPB_DONTWAIT
305 */
306static int
307imm_connect(struct vpoio_data *vpo, int how, int *disconnected, int request_bus)
308{
309	DECLARE_CPP_MICROSEQ;
310
311	char s1, s2, s3;
312	int error;
313	int ret;
314
315	/* all should be ok */
316	if (disconnected)
317		*disconnected = 0;
318
319	if (request_bus)
320		if ((error = ppb_request_bus(&vpo->vpo_dev, how)))
321			return (error);
322
323	ppb_MS_init_msq(cpp_microseq, 3, CPP_S1, (void *)&s1,
324			CPP_S2, (void *)&s2, CPP_S3, (void *)&s3);
325
326	/* select device 0 in compatible mode */
327	ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0xe0);
328	ppb_MS_microseq(&vpo->vpo_dev, cpp_microseq, &ret);
329
330	/* disconnect all devices */
331	ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0x30);
332	ppb_MS_microseq(&vpo->vpo_dev, cpp_microseq, &ret);
333
334	if (PPB_IN_EPP_MODE(&vpo->vpo_dev))
335		ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0x28);
336	else
337		ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0xe0);
338
339	ppb_MS_microseq(&vpo->vpo_dev, cpp_microseq, &ret);
340
341	if ((s1 != (char)0xb8 || s2 != (char)0x18 || s3 != (char)0x30)) {
342		if (bootverbose)
343			printf("imm%d: (connect) s1=0x%x s2=0x%x, s3=0x%x\n",
344				vpo->vpo_unit, s1 & 0xff, s2 & 0xff, s3 & 0xff);
345		if (disconnected)
346			*disconnected = VP0_ECONNECT;
347	}
348
349	return (0);
350}
351
352/*
353 * imm_detect()
354 *
355 * Detect and initialise the VP0 adapter.
356 */
357static int
358imm_detect(struct vpoio_data *vpo)
359{
360	int error;
361
362	if ((error = ppb_request_bus(&vpo->vpo_dev, PPB_DONTWAIT)))
363		return (error);
364
365	/* disconnect the drive, keep the bus */
366	imm_disconnect(vpo, NULL, 0);
367
368	/* we already have the bus, just connect */
369	imm_connect(vpo, PPB_DONTWAIT, &error, 0);
370
371	if (error) {
372		if (bootverbose)
373			printf("imm%d: can't connect to the drive\n",
374				vpo->vpo_unit);
375		goto error;
376	}
377
378	/* send SCSI reset signal */
379	ppb_MS_microseq(&vpo->vpo_dev, reset_microseq, NULL);
380
381	/* release the bus now */
382	imm_disconnect(vpo, &error, 1);
383
384	/* ensure we are disconnected or daisy chained peripheral
385	 * may cause serious problem to the disk */
386
387	if (error) {
388		if (bootverbose)
389			printf("imm%d: can't disconnect from the drive\n",
390				vpo->vpo_unit);
391		goto error;
392	}
393
394	return (0);
395
396error:
397	ppb_release_bus(&vpo->vpo_dev);
398	return (VP0_EINITFAILED);
399}
400
401/*
402 * imm_outstr()
403 */
404static int
405imm_outstr(struct vpoio_data *vpo, char *buffer, int size)
406{
407	int error = 0;
408
409	if (PPB_IN_EPP_MODE(&vpo->vpo_dev))
410		ppb_reset_epp_timeout(&vpo->vpo_dev);
411
412	ppb_MS_exec(&vpo->vpo_dev, MS_OP_PUT, buffer, size, MS_UNKNOWN, &error);
413
414	return (error);
415}
416
417/*
418 * imm_instr()
419 */
420static int
421imm_instr(struct vpoio_data *vpo, char *buffer, int size)
422{
423	int error = 0;
424
425	if (PPB_IN_EPP_MODE(&vpo->vpo_dev))
426		ppb_reset_epp_timeout(&vpo->vpo_dev);
427
428	ppb_MS_exec(&vpo->vpo_dev, MS_OP_GET, buffer, size, MS_UNKNOWN, &error);
429
430	return (error);
431}
432
433static char
434imm_select(struct vpoio_data *vpo, int initiator, int target)
435{
436	DECLARE_SELECT_MICROSEQUENCE;
437	int ret;
438
439	/* initialize the select microsequence */
440	ppb_MS_init_msq(select_microseq, 1,
441			SELECT_TARGET, 1 << initiator | 1 << target);
442
443	ppb_MS_microseq(&vpo->vpo_dev, select_microseq, &ret);
444
445	return (ret);
446}
447
448/*
449 * imm_wait()
450 *
451 * H_SELIN must be low.
452 *
453 * XXX should be ported to microseq
454 */
455static char
456imm_wait(struct vpoio_data *vpo, int tmo)
457{
458
459	register int	k;
460	register char	r;
461
462	ppb_wctr(&vpo->vpo_dev, 0xc);
463
464	/* XXX should be ported to microseq */
465	k = 0;
466	while (!((r = ppb_rstr(&vpo->vpo_dev)) & 0x80) && (k++ < tmo))
467		DELAY(1);
468
469	/*
470	 * Return some status information.
471	 * Semantics :	0x88 = ZIP+ wants more data
472	 *		0x98 = ZIP+ wants to send more data
473	 *		0xa8 = ZIP+ wants command
474	 *		0xb8 = end of transfer, ZIP+ is sending status
475	 */
476	ppb_wctr(&vpo->vpo_dev, 0x4);
477	if (k < tmo)
478	  return (r & 0xb8);
479
480	return (0);			   /* command timed out */
481}
482
483static int
484imm_negociate(struct vpoio_data *vpo)
485{
486	DECLARE_NEGOCIATE_MICROSEQ;
487	int negociate_mode;
488	int ret;
489
490	if (PPB_IN_NIBBLE_MODE(&vpo->vpo_dev))
491		negociate_mode = 0;
492	else if (PPB_IN_PS2_MODE(&vpo->vpo_dev))
493		negociate_mode = 1;
494	else
495		return (0);
496
497#if 0 /* XXX use standalone code not to depend on ppb_1284 code yet */
498	ret = ppb_1284_negociate(&vpo->vpo_dev, negociate_mode);
499
500	if (ret)
501		return (VP0_ENEGOCIATE);
502#endif
503
504	ppb_MS_init_msq(negociate_microseq, 1, NEGOCIATED_MODE, negociate_mode);
505
506	ppb_MS_microseq(&vpo->vpo_dev, negociate_microseq, &ret);
507
508	return (ret);
509}
510
511/*
512 * imm_probe()
513 *
514 * Low level probe of vpo device
515 *
516 */
517struct ppb_device *
518imm_probe(struct ppb_data *ppb, struct vpoio_data *vpo)
519{
520
521	/* ppbus dependent initialisation */
522	vpo->vpo_dev.id_unit = vpo->vpo_unit;
523	vpo->vpo_dev.name = "vpo";
524	vpo->vpo_dev.ppb = ppb;
525
526	/* now, try to initialise the drive */
527	if (imm_detect(vpo)) {
528		return (NULL);
529	}
530
531	return (&vpo->vpo_dev);
532}
533
534/*
535 * imm_attach()
536 *
537 * Low level attachment of vpo device
538 *
539 */
540int
541imm_attach(struct vpoio_data *vpo)
542{
543	int epp;
544
545	/*
546	 * Report ourselves
547	 */
548	printf("imm%d: <Iomega Matchmaker Parallel to SCSI interface> on ppbus %d\n",
549		vpo->vpo_dev.id_unit, vpo->vpo_dev.ppb->ppb_link->adapter_unit);
550
551	/*
552	 * Initialize microsequence code
553	 */
554	vpo->vpo_nibble_inbyte_msq = (struct ppb_microseq *)malloc(
555		sizeof(nibble_inbyte_submicroseq), M_DEVBUF, M_NOWAIT);
556
557	if (!vpo->vpo_nibble_inbyte_msq)
558		return (0);
559
560	bcopy((void *)nibble_inbyte_submicroseq,
561		(void *)vpo->vpo_nibble_inbyte_msq,
562		sizeof(nibble_inbyte_submicroseq));
563
564	INIT_NIBBLE_INBYTE_SUBMICROSEQ(vpo);
565
566	/*
567	 * Initialize mode dependent in/out microsequences
568	 */
569	ppb_request_bus(&vpo->vpo_dev, PPB_WAIT);
570
571	/* enter NIBBLE mode to configure submsq */
572	if (ppb_set_mode(&vpo->vpo_dev, PPB_NIBBLE) != -1) {
573
574		ppb_MS_GET_init(&vpo->vpo_dev, vpo->vpo_nibble_inbyte_msq);
575		ppb_MS_PUT_init(&vpo->vpo_dev, spp_outbyte_submicroseq);
576	}
577
578	/* enter PS2 mode to configure submsq */
579	if (ppb_set_mode(&vpo->vpo_dev, PPB_PS2) != -1) {
580
581		ppb_MS_GET_init(&vpo->vpo_dev, ps2_inbyte_submicroseq);
582		ppb_MS_PUT_init(&vpo->vpo_dev, spp_outbyte_submicroseq);
583	}
584
585	epp = ppb_get_epp_protocol(&vpo->vpo_dev);
586
587	/* enter EPP mode to configure submsq */
588	if (ppb_set_mode(&vpo->vpo_dev, PPB_EPP) != -1) {
589
590		switch (epp) {
591		case EPP_1_9:
592		case EPP_1_7:
593			ppb_MS_GET_init(&vpo->vpo_dev, epp17_instr);
594			ppb_MS_PUT_init(&vpo->vpo_dev, epp17_outstr);
595			break;
596		default:
597			panic("%s: unknown EPP protocol (0x%x)", __FUNCTION__,
598				epp);
599		}
600	}
601
602	/* try to enter EPP or PS/2 mode, NIBBLE otherwise */
603	if (ppb_set_mode(&vpo->vpo_dev, PPB_EPP) != -1) {
604		switch (epp) {
605		case EPP_1_9:
606			printf("imm%d: EPP 1.9 mode\n", vpo->vpo_unit);
607			break;
608		case EPP_1_7:
609			printf("imm%d: EPP 1.7 mode\n", vpo->vpo_unit);
610			break;
611		default:
612			panic("%s: unknown EPP protocol (0x%x)", __FUNCTION__,
613				epp);
614		}
615	} else if (ppb_set_mode(&vpo->vpo_dev, PPB_PS2) != -1)
616		printf("imm%d: PS2 mode\n", vpo->vpo_unit);
617
618	else if (ppb_set_mode(&vpo->vpo_dev, PPB_NIBBLE) != -1)
619		printf("imm%d: NIBBLE mode\n", vpo->vpo_unit);
620
621	else {
622		printf("imm%d: can't enter NIBBLE, PS2 or EPP mode\n",
623			vpo->vpo_unit);
624
625		ppb_release_bus(&vpo->vpo_dev);
626
627		free(vpo->vpo_nibble_inbyte_msq, M_DEVBUF);
628		return (0);
629	}
630
631	ppb_release_bus(&vpo->vpo_dev);
632
633	return (1);
634}
635
636/*
637 * imm_reset_bus()
638 *
639 */
640int
641imm_reset_bus(struct vpoio_data *vpo)
642{
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(&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
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 do 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(&vpo->vpo_dev))
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(&vpo->vpo_dev) ||
769			PPB_IN_PS2_MODE(&vpo->vpo_dev)) && negociated)
770		ppb_MS_microseq(&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	*result = ((int) h << 8) | ((int) l & 0xff);
792
793error:
794	if ((PPB_IN_NIBBLE_MODE(&vpo->vpo_dev) ||
795			PPB_IN_PS2_MODE(&vpo->vpo_dev)) && negociated)
796		ppb_MS_microseq(&vpo->vpo_dev, transfer_epilog, NULL);
797
798	/* return to printer state, release the ppbus */
799	imm_disconnect(vpo, NULL, 1);
800
801	return (0);
802}
803