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