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