139136Snsouch/*-
255939Snsouch * Copyright (c) 1998, 1999 Nicolas Souchu
371633Snsouch * Copyright (c) 2001 Alcove - Nicolas Souchu
439136Snsouch * All rights reserved.
539136Snsouch *
639136Snsouch * Redistribution and use in source and binary forms, with or without
739136Snsouch * modification, are permitted provided that the following conditions
839136Snsouch * are met:
939136Snsouch * 1. Redistributions of source code must retain the above copyright
1039136Snsouch *    notice, this list of conditions and the following disclaimer.
1139136Snsouch * 2. Redistributions in binary form must reproduce the above copyright
1239136Snsouch *    notice, this list of conditions and the following disclaimer in the
1339136Snsouch *    documentation and/or other materials provided with the distribution.
1439136Snsouch *
1539136Snsouch * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1639136Snsouch * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1739136Snsouch * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1839136Snsouch * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
1939136Snsouch * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2039136Snsouch * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2139136Snsouch * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2239136Snsouch * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2339136Snsouch * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2439136Snsouch * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2539136Snsouch * SUCH DAMAGE.
2639136Snsouch *
2739136Snsouch *
2839136Snsouch */
2939136Snsouch
30119418Sobrien#include <sys/cdefs.h>
31119418Sobrien__FBSDID("$FreeBSD$");
32119418Sobrien
3339136Snsouch/*
3439136Snsouch * Iomega ZIP+ Matchmaker Parallel Port Interface driver
3539136Snsouch *
3639136Snsouch * Thanks to David Campbell work on the Linux driver and the Iomega specs
3739136Snsouch * Thanks to Thiebault Moeglin for the drive
3839136Snsouch */
3955205Speter#ifdef _KERNEL
4039136Snsouch#include <sys/param.h>
4139136Snsouch#include <sys/systm.h>
4255939Snsouch#include <sys/module.h>
4355939Snsouch#include <sys/bus.h>
4439136Snsouch#include <sys/malloc.h>
4539136Snsouch
4655205Speter#endif	/* _KERNEL */
4739136Snsouch
4842475Snsouch#include "opt_vpo.h"
4942475Snsouch
5055939Snsouch#include <dev/ppbus/ppbio.h>
5139136Snsouch#include <dev/ppbus/ppbconf.h>
5239136Snsouch#include <dev/ppbus/ppb_msq.h>
5339136Snsouch#include <dev/ppbus/vpoio.h>
5439136Snsouch#include <dev/ppbus/ppb_1284.h>
5539136Snsouch
5655939Snsouch#include "ppbus_if.h"
5755939Snsouch
5839136Snsouch#define VP0_SELTMO		5000	/* select timeout */
5939136Snsouch#define VP0_FAST_SPINTMO	500000	/* wait status timeout */
6039136Snsouch#define VP0_LOW_SPINTMO		5000000	/* wait status timeout */
6139136Snsouch
6239136Snsouch#define VP0_SECTOR_SIZE	512
6339136Snsouch
6439136Snsouch/*
6539136Snsouch * Microcode to execute very fast I/O sequences at the lowest bus level.
6639136Snsouch */
6739136Snsouch
6878645Snsouch#define WAIT_RET		MS_PARAM(7, 2, MS_TYP_PTR)
6978645Snsouch#define WAIT_TMO		MS_PARAM(1, 0, MS_TYP_INT)
7078645Snsouch
7178645Snsouch#define DECLARE_WAIT_MICROSEQUENCE \
7278645Snsouchstruct ppb_microseq wait_microseq[] = {					\
7378645Snsouch	MS_CASS(0x0c),							\
7478645Snsouch	MS_SET(MS_UNKNOWN),						\
7578645Snsouch	/* loop */							\
7678645Snsouch	MS_BRSET(nBUSY, 4 /* ready */),					\
7778645Snsouch	MS_DBRA(-2 /* loop */),						\
7878645Snsouch	MS_CASS(0x04),							\
7978645Snsouch	MS_RET(1), /* timed out */					\
8078645Snsouch	/* ready */							\
8178645Snsouch	MS_CASS(0x04),							\
8278645Snsouch	MS_RFETCH(MS_REG_STR, 0xb8, MS_UNKNOWN ),			\
8378645Snsouch	MS_RET(0) /* no error */					\
8478645Snsouch}
8578645Snsouch
8639136Snsouch#define SELECT_TARGET		MS_PARAM(6, 1, MS_TYP_CHA)
8739136Snsouch
8878645Snsouch#define DECLARE_SELECT_MICROSEQUENCE \
8939136Snsouchstruct ppb_microseq select_microseq[] = {				\
9039136Snsouch	MS_CASS(0xc),							\
9139136Snsouch	/* first, check there is nothing holding onto the bus */	\
9239136Snsouch	MS_SET(VP0_SELTMO),						\
9339136Snsouch/* _loop: */								\
9443433Snsouch	MS_BRCLEAR(0x8, 2 /* _ready */),				\
9543433Snsouch	MS_DBRA(-2 /* _loop */),					\
9639136Snsouch	MS_RET(2),			/* bus busy */			\
9739136Snsouch/* _ready: */								\
9839136Snsouch	MS_CASS(0x4),							\
9939136Snsouch	MS_DASS(MS_UNKNOWN /* 0x80 | 1 << target */),			\
10039136Snsouch	MS_DELAY(1),							\
10139136Snsouch	MS_CASS(0xc),							\
10239136Snsouch	MS_CASS(0xd),							\
10339136Snsouch	/* now, wait until the drive is ready */			\
10439136Snsouch	MS_SET(VP0_SELTMO),						\
10539136Snsouch/* loop: */								\
10643433Snsouch	MS_BRSET(0x8, 3 /* ready */),					\
10743433Snsouch	MS_DBRA(-2 /* loop */),						\
10839136Snsouch/* error: */								\
10939136Snsouch	MS_CASS(0xc),							\
11039136Snsouch	MS_RET(VP0_ESELECT_TIMEOUT),					\
11139136Snsouch/* ready: */								\
11239136Snsouch	MS_CASS(0xc),							\
11339136Snsouch	MS_RET(0)							\
11439136Snsouch}
11539136Snsouch
11639136Snsouchstatic struct ppb_microseq transfer_epilog[] = {
11739136Snsouch	MS_CASS(0x4),
11839136Snsouch	MS_CASS(0xc),
11939136Snsouch	MS_CASS(0xe),
12039136Snsouch	MS_CASS(0x4),
12139136Snsouch	MS_RET(0)
12239136Snsouch};
12339136Snsouch
12439136Snsouch#define CPP_S1		MS_PARAM(10, 2, MS_TYP_PTR)
12539136Snsouch#define CPP_S2		MS_PARAM(13, 2, MS_TYP_PTR)
12639136Snsouch#define CPP_S3		MS_PARAM(16, 2, MS_TYP_PTR)
12739136Snsouch#define CPP_PARAM	MS_PARAM(17, 1, MS_TYP_CHA)
12839136Snsouch
12939136Snsouch#define DECLARE_CPP_MICROSEQ \
13039136Snsouchstruct ppb_microseq cpp_microseq[] = {					\
13139136Snsouch	MS_CASS(0x0c), MS_DELAY(2),					\
13239136Snsouch	MS_DASS(0xaa), MS_DELAY(10),					\
13339136Snsouch	MS_DASS(0x55), MS_DELAY(10),					\
13439136Snsouch	MS_DASS(0x00), MS_DELAY(10),					\
13539136Snsouch	MS_DASS(0xff), MS_DELAY(10),					\
13639136Snsouch	MS_RFETCH(MS_REG_STR, 0xb8, MS_UNKNOWN /* &s1 */),		\
13739136Snsouch	MS_DASS(0x87), MS_DELAY(10),					\
13839136Snsouch	MS_RFETCH(MS_REG_STR, 0xb8, MS_UNKNOWN /* &s2 */),		\
13939136Snsouch	MS_DASS(0x78), MS_DELAY(10),					\
14039136Snsouch	MS_RFETCH(MS_REG_STR, 0x38, MS_UNKNOWN /* &s3 */),		\
14139136Snsouch	MS_DASS(MS_UNKNOWN /* param */),				\
14239136Snsouch	MS_DELAY(2),							\
14339136Snsouch	MS_CASS(0x0c), MS_DELAY(10),					\
14439136Snsouch	MS_CASS(0x0d), MS_DELAY(2),					\
14539136Snsouch	MS_CASS(0x0c), MS_DELAY(10),					\
14639136Snsouch	MS_DASS(0xff), MS_DELAY(10),					\
14739136Snsouch	MS_RET(0)							\
14839136Snsouch}
14939136Snsouch
15039136Snsouch#define NEGOCIATED_MODE		MS_PARAM(2, 1, MS_TYP_CHA)
15139136Snsouch
15239136Snsouch#define DECLARE_NEGOCIATE_MICROSEQ \
15378645Snsouchstruct ppb_microseq negociate_microseq[] = {				\
15439136Snsouch	MS_CASS(0x4),							\
15539136Snsouch	MS_DELAY(5),							\
15639136Snsouch	MS_DASS(MS_UNKNOWN /* mode */),					\
15739136Snsouch	MS_DELAY(100),							\
15839136Snsouch	MS_CASS(0x6),							\
15939136Snsouch	MS_DELAY(5),							\
16043433Snsouch	MS_BRSET(0x20, 5 /* continue */),				\
16139136Snsouch	MS_DELAY(5),							\
16239136Snsouch	MS_CASS(0x7),							\
16339136Snsouch	MS_DELAY(5),							\
16439136Snsouch	MS_CASS(0x6),							\
16539136Snsouch	MS_RET(VP0_ENEGOCIATE),						\
16639136Snsouch/* continue: */								\
16739136Snsouch	MS_DELAY(5),							\
16839136Snsouch	MS_CASS(0x7),							\
16939136Snsouch	MS_DELAY(5),							\
17039136Snsouch	MS_CASS(0x6),							\
17139136Snsouch	MS_RET(0)							\
17239136Snsouch}
17339136Snsouch
17478645Snsouch#define INB_NIBBLE_L MS_PARAM(3, 2, MS_TYP_PTR)
17578645Snsouch#define INB_NIBBLE_H MS_PARAM(6, 2, MS_TYP_PTR)
17678645Snsouch#define INB_NIBBLE_F MS_PARAM(9, 0, MS_TYP_FUN)
17778645Snsouch#define INB_NIBBLE_P MS_PARAM(9, 1, MS_TYP_PTR)
17878645Snsouch
17978645Snsouch/*
18078645Snsouch * This is the sub-microseqence for MS_GET in NIBBLE mode
18178645Snsouch * Retrieve the two nibbles and call the C function to generate the character
18278645Snsouch * and store it in the buffer (see nibble_inbyte_hook())
18378645Snsouch */
18478645Snsouch
18578645Snsouch#define DECLARE_NIBBLE_INBYTE_SUBMICROSEQ \
18678645Snsouchstruct ppb_microseq nibble_inbyte_submicroseq[] = {			\
18778645Snsouch	MS_CASS(0x4),							\
18878645Snsouch/* loop: */								\
18978645Snsouch	MS_CASS(0x6),							\
19078645Snsouch	MS_DELAY(1),							\
19178645Snsouch	MS_RFETCH(MS_REG_STR, MS_FETCH_ALL, MS_UNKNOWN /* low nibble */),\
19278645Snsouch	MS_CASS(0x5),							\
19378645Snsouch	MS_DELAY(1),							\
19478645Snsouch	MS_RFETCH(MS_REG_STR, MS_FETCH_ALL, MS_UNKNOWN /* high nibble */),\
19578645Snsouch	MS_CASS(0x4),							\
19678645Snsouch	MS_DELAY(1),							\
19778645Snsouch	/* do a C call to format the received nibbles */		\
19878645Snsouch	MS_C_CALL(MS_UNKNOWN /* C hook */, MS_UNKNOWN /* param */),	\
19978645Snsouch	MS_DBRA(-7 /* loop */),						\
20078645Snsouch	MS_RET(0)							\
20178645Snsouch}
20278645Snsouch
20339136Snsouchstatic struct ppb_microseq reset_microseq[] = {
20439136Snsouch	MS_CASS(0x04),
20539136Snsouch	MS_DASS(0x40),
20639136Snsouch	MS_DELAY(1),
20739136Snsouch	MS_CASS(0x0c),
20839136Snsouch	MS_CASS(0x0d),
20939136Snsouch	MS_DELAY(50),
21039136Snsouch	MS_CASS(0x0c),
21139136Snsouch	MS_CASS(0x04),
21239136Snsouch	MS_RET(0)
21339136Snsouch};
21439136Snsouch
21539136Snsouch/*
21639136Snsouch * nibble_inbyte_hook()
21739136Snsouch *
21839136Snsouch * Formats high and low nibble into a character
21939136Snsouch */
22039136Snsouchstatic int
22139136Snsouchnibble_inbyte_hook (void *p, char *ptr)
22239136Snsouch{
22339136Snsouch	struct vpo_nibble *s = (struct vpo_nibble *)p;
22439136Snsouch
22539136Snsouch	/* increment the buffer pointer */
22639136Snsouch	*ptr = ((s->l >> 4) & 0x0f) + (s->h & 0xf0);
22739136Snsouch
22839136Snsouch	return (0);
22939136Snsouch}
23039136Snsouch
23139136Snsouch/*
23239136Snsouch * This is the sub-microseqence for MS_GET in PS2 mode
23339136Snsouch */
23439136Snsouchstatic struct ppb_microseq ps2_inbyte_submicroseq[] = {
23539136Snsouch	  MS_CASS(0x4),
23639136Snsouch
23739136Snsouch/* loop: */
23839136Snsouch	  MS_CASS(PCD | 0x6),
23939136Snsouch	  MS_RFETCH_P(1, MS_REG_DTR, MS_FETCH_ALL),
24039136Snsouch	  MS_CASS(PCD | 0x5),
24143433Snsouch	  MS_DBRA(-4 /* loop */),
24239136Snsouch
24339136Snsouch	  MS_RET(0)
24439136Snsouch};
24539136Snsouch
24639136Snsouch/*
24739136Snsouch * This is the sub-microsequence for MS_PUT in both NIBBLE and PS2 modes
24839136Snsouch */
24939136Snsouchstatic struct ppb_microseq spp_outbyte_submicroseq[] = {
25039136Snsouch	  MS_CASS(0x4),
25139136Snsouch
25239136Snsouch/* loop: */
253185003Sjhb	  MS_RASSERT_P(1, MS_REG_DTR),
25439136Snsouch	  MS_CASS(0x5),
25543433Snsouch	  MS_DBRA(0),				/* decrement counter */
256185003Sjhb	  MS_RASSERT_P(1, MS_REG_DTR),
25739136Snsouch	  MS_CASS(0x0),
25843433Snsouch	  MS_DBRA(-6 /* loop */),
25939136Snsouch
26039136Snsouch	  /* return from the put call */
26139136Snsouch	  MS_CASS(0x4),
26239136Snsouch	  MS_RET(0)
26339136Snsouch};
26439136Snsouch
26539136Snsouch/* EPP 1.7 microsequences, ptr and len set at runtime */
26639136Snsouchstatic struct ppb_microseq epp17_outstr[] = {
26739136Snsouch	  MS_CASS(0x4),
268185003Sjhb	  MS_RASSERT_P(MS_ACCUM, MS_REG_EPP_D),
26939136Snsouch	  MS_CASS(0xc),
27039136Snsouch	  MS_RET(0),
27139136Snsouch};
27239136Snsouch
27339136Snsouchstatic struct ppb_microseq epp17_instr[] = {
27439136Snsouch	  MS_CASS(PCD | 0x4),
275185003Sjhb	  MS_RFETCH_P(MS_ACCUM, MS_REG_EPP_D, MS_FETCH_ALL),
27639136Snsouch	  MS_CASS(PCD | 0xc),
27739136Snsouch	  MS_RET(0),
27839136Snsouch};
27939136Snsouch
28039136Snsouchstatic int
28139520Snsouchimm_disconnect(struct vpoio_data *vpo, int *connected, int release_bus)
28239136Snsouch{
28339136Snsouch	DECLARE_CPP_MICROSEQ;
28439136Snsouch
28555939Snsouch	device_t ppbus = device_get_parent(vpo->vpo_dev);
28639136Snsouch	char s1, s2, s3;
28739136Snsouch	int ret;
28839136Snsouch
28939136Snsouch	/* all should be ok */
29039520Snsouch	if (connected)
29139520Snsouch		*connected = 0;
29239136Snsouch
29339136Snsouch	ppb_MS_init_msq(cpp_microseq, 4, CPP_S1, (void *)&s1,
29439136Snsouch			CPP_S2, (void *)&s2, CPP_S3, (void *)&s3,
29539136Snsouch			CPP_PARAM, 0x30);
29639136Snsouch
29755939Snsouch	ppb_MS_microseq(ppbus, vpo->vpo_dev, cpp_microseq, &ret);
29839136Snsouch
29940783Snsouch	if ((s1 != (char)0xb8 || s2 != (char)0x18 || s3 != (char)0x38)) {
30040783Snsouch		if (bootverbose)
301184130Sjhb			device_printf(vpo->vpo_dev,
302184130Sjhb			    "(disconnect) s1=0x%x s2=0x%x, s3=0x%x\n",
303184130Sjhb			    s1 & 0xff, s2 & 0xff, s3 & 0xff);
30440783Snsouch		if (connected)
30540783Snsouch			*connected = VP0_ECONNECT;
30640783Snsouch	}
30739136Snsouch
30839520Snsouch	if (release_bus)
30955939Snsouch		return (ppb_release_bus(ppbus, vpo->vpo_dev));
31039520Snsouch	else
31139520Snsouch		return (0);
31239136Snsouch}
31339136Snsouch
31439136Snsouch/*
31539136Snsouch * how	: PPB_WAIT or PPB_DONTWAIT
31639136Snsouch */
31739136Snsouchstatic int
31839520Snsouchimm_connect(struct vpoio_data *vpo, int how, int *disconnected, int request_bus)
31939136Snsouch{
32039136Snsouch	DECLARE_CPP_MICROSEQ;
32139136Snsouch
32255939Snsouch	device_t ppbus = device_get_parent(vpo->vpo_dev);
32339136Snsouch	char s1, s2, s3;
32439136Snsouch	int error;
32539136Snsouch	int ret;
32639136Snsouch
32739136Snsouch	/* all should be ok */
32839520Snsouch	if (disconnected)
32939520Snsouch		*disconnected = 0;
33039136Snsouch
33139520Snsouch	if (request_bus)
33255939Snsouch		if ((error = ppb_request_bus(ppbus, vpo->vpo_dev, how)))
33339520Snsouch			return (error);
33439136Snsouch
33539136Snsouch	ppb_MS_init_msq(cpp_microseq, 3, CPP_S1, (void *)&s1,
33639136Snsouch			CPP_S2, (void *)&s2, CPP_S3, (void *)&s3);
33739136Snsouch
33839136Snsouch	/* select device 0 in compatible mode */
33939136Snsouch	ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0xe0);
34055939Snsouch	ppb_MS_microseq(ppbus, vpo->vpo_dev, cpp_microseq, &ret);
34139136Snsouch
34239136Snsouch	/* disconnect all devices */
34339136Snsouch	ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0x30);
34455939Snsouch	ppb_MS_microseq(ppbus, vpo->vpo_dev, cpp_microseq, &ret);
34539136Snsouch
34655939Snsouch	if (PPB_IN_EPP_MODE(ppbus))
34739136Snsouch		ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0x28);
34839136Snsouch	else
34939136Snsouch		ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0xe0);
35039136Snsouch
35155939Snsouch	ppb_MS_microseq(ppbus, vpo->vpo_dev, cpp_microseq, &ret);
35239136Snsouch
35340783Snsouch	if ((s1 != (char)0xb8 || s2 != (char)0x18 || s3 != (char)0x30)) {
35440783Snsouch		if (bootverbose)
355184130Sjhb			device_printf(vpo->vpo_dev,
356184130Sjhb			    "(connect) s1=0x%x s2=0x%x, s3=0x%x\n",
357184130Sjhb			    s1 & 0xff, s2 & 0xff, s3 & 0xff);
35840783Snsouch		if (disconnected)
35940783Snsouch			*disconnected = VP0_ECONNECT;
36040783Snsouch	}
36139136Snsouch
36239136Snsouch	return (0);
36339136Snsouch}
36439136Snsouch
36539136Snsouch/*
36639136Snsouch * imm_detect()
36739136Snsouch *
36839136Snsouch * Detect and initialise the VP0 adapter.
36939136Snsouch */
37039136Snsouchstatic int
37139136Snsouchimm_detect(struct vpoio_data *vpo)
37239136Snsouch{
37355939Snsouch	device_t ppbus = device_get_parent(vpo->vpo_dev);
37439136Snsouch	int error;
37539136Snsouch
37655939Snsouch	if ((error = ppb_request_bus(ppbus, vpo->vpo_dev, PPB_DONTWAIT)))
37739520Snsouch		return (error);
37839136Snsouch
37939520Snsouch	/* disconnect the drive, keep the bus */
38039520Snsouch	imm_disconnect(vpo, NULL, 0);
38139136Snsouch
38270608Snsouch	vpo->vpo_mode_found = VP0_MODE_UNDEFINED;
38370608Snsouch	error = 1;
38439520Snsouch
38570608Snsouch	/* try to enter EPP mode since vpoio failure put the bus in NIBBLE */
38670608Snsouch	if (ppb_set_mode(ppbus, PPB_EPP) != -1) {
38770608Snsouch		imm_connect(vpo, PPB_DONTWAIT, &error, 0);
38870608Snsouch	}
38970608Snsouch
39070608Snsouch	/* if connection failed try PS/2 then NIBBLE modes */
39139520Snsouch	if (error) {
39270608Snsouch		if (ppb_set_mode(ppbus, PPB_PS2) != -1) {
39370608Snsouch			imm_connect(vpo, PPB_DONTWAIT, &error, 0);
39470608Snsouch		}
39570608Snsouch		if (error) {
39670608Snsouch			if (ppb_set_mode(ppbus, PPB_NIBBLE) != -1) {
39770608Snsouch				imm_connect(vpo, PPB_DONTWAIT, &error, 0);
39870608Snsouch				if (error)
39970608Snsouch					goto error;
40070608Snsouch				vpo->vpo_mode_found = VP0_MODE_NIBBLE;
40170608Snsouch			} else {
402184130Sjhb				device_printf(vpo->vpo_dev,
403184130Sjhb				    "NIBBLE mode unavailable!\n");
40470608Snsouch				goto error;
40570608Snsouch			}
40670608Snsouch		} else {
40770608Snsouch			vpo->vpo_mode_found = VP0_MODE_PS2;
40870608Snsouch		}
40970608Snsouch	} else {
41070608Snsouch		vpo->vpo_mode_found = VP0_MODE_EPP;
41139520Snsouch	}
41239520Snsouch
41339136Snsouch	/* send SCSI reset signal */
41455939Snsouch	ppb_MS_microseq(ppbus, vpo->vpo_dev, reset_microseq, NULL);
41539136Snsouch
41639520Snsouch	/* release the bus now */
41739520Snsouch	imm_disconnect(vpo, &error, 1);
41839136Snsouch
419185003Sjhb	/* ensure we are disconnected or daisy chained peripheral
42039136Snsouch	 * may cause serious problem to the disk */
42139136Snsouch
42239520Snsouch	if (error) {
42339520Snsouch		if (bootverbose)
424184130Sjhb			device_printf(vpo->vpo_dev,
425184130Sjhb			    "can't disconnect from the drive\n");
42639900Snsouch		goto error;
42739520Snsouch	}
42839136Snsouch
42939136Snsouch	return (0);
43039520Snsouch
43139520Snsoucherror:
43255939Snsouch	ppb_release_bus(ppbus, vpo->vpo_dev);
43339520Snsouch	return (VP0_EINITFAILED);
43439136Snsouch}
43539136Snsouch
43639136Snsouch/*
43739136Snsouch * imm_outstr()
43839136Snsouch */
43939136Snsouchstatic int
44039136Snsouchimm_outstr(struct vpoio_data *vpo, char *buffer, int size)
44139136Snsouch{
44255939Snsouch	device_t ppbus = device_get_parent(vpo->vpo_dev);
44339136Snsouch	int error = 0;
44439136Snsouch
44555939Snsouch	if (PPB_IN_EPP_MODE(ppbus))
44655939Snsouch		ppb_reset_epp_timeout(ppbus);
44739136Snsouch
44855939Snsouch	ppb_MS_exec(ppbus, vpo->vpo_dev, MS_OP_PUT, (union ppb_insarg)buffer,
44945342Speter		(union ppb_insarg)size, (union ppb_insarg)MS_UNKNOWN, &error);
45039136Snsouch
45139136Snsouch	return (error);
45239136Snsouch}
45339136Snsouch
45439136Snsouch/*
45539136Snsouch * imm_instr()
45639136Snsouch */
45739136Snsouchstatic int
45839136Snsouchimm_instr(struct vpoio_data *vpo, char *buffer, int size)
45939136Snsouch{
46055939Snsouch	device_t ppbus = device_get_parent(vpo->vpo_dev);
46139136Snsouch	int error = 0;
46239136Snsouch
46355939Snsouch	if (PPB_IN_EPP_MODE(ppbus))
46455939Snsouch		ppb_reset_epp_timeout(ppbus);
46539136Snsouch
46655939Snsouch	ppb_MS_exec(ppbus, vpo->vpo_dev, MS_OP_GET, (union ppb_insarg)buffer,
46745342Speter		(union ppb_insarg)size, (union ppb_insarg)MS_UNKNOWN, &error);
46839136Snsouch
46939136Snsouch	return (error);
47039136Snsouch}
47139136Snsouch
47239136Snsouchstatic char
47339136Snsouchimm_select(struct vpoio_data *vpo, int initiator, int target)
47439136Snsouch{
47539136Snsouch	DECLARE_SELECT_MICROSEQUENCE;
47655939Snsouch	device_t ppbus = device_get_parent(vpo->vpo_dev);
47739136Snsouch	int ret;
47839136Snsouch
47939136Snsouch	/* initialize the select microsequence */
48039136Snsouch	ppb_MS_init_msq(select_microseq, 1,
48139136Snsouch			SELECT_TARGET, 1 << initiator | 1 << target);
482185003Sjhb
48355939Snsouch	ppb_MS_microseq(ppbus, vpo->vpo_dev, select_microseq, &ret);
48439136Snsouch
48539136Snsouch	return (ret);
48639136Snsouch}
48739136Snsouch
48839136Snsouch/*
48939136Snsouch * imm_wait()
49039136Snsouch *
49139136Snsouch * H_SELIN must be low.
49239136Snsouch *
49339136Snsouch */
49439136Snsouchstatic char
49539136Snsouchimm_wait(struct vpoio_data *vpo, int tmo)
49639136Snsouch{
49778645Snsouch	DECLARE_WAIT_MICROSEQUENCE;
49878645Snsouch
49955939Snsouch	device_t ppbus = device_get_parent(vpo->vpo_dev);
50078645Snsouch	int ret, err;
50139136Snsouch
50239136Snsouch	/*
50339136Snsouch	 * Return some status information.
50439136Snsouch	 * Semantics :	0x88 = ZIP+ wants more data
50539136Snsouch	 *		0x98 = ZIP+ wants to send more data
50639136Snsouch	 *		0xa8 = ZIP+ wants command
50739136Snsouch	 *		0xb8 = end of transfer, ZIP+ is sending status
50839136Snsouch	 */
50939136Snsouch
51078645Snsouch	ppb_MS_init_msq(wait_microseq, 2,
51178645Snsouch			WAIT_RET, (void *)&ret,
51278645Snsouch			WAIT_TMO, tmo);
51378645Snsouch
51478645Snsouch	ppb_MS_microseq(ppbus, vpo->vpo_dev, wait_microseq, &err);
51578645Snsouch
51678645Snsouch	if (err)
517185003Sjhb		return (0);			   /* command timed out */
51878645Snsouch
51978645Snsouch	return(ret);
52039136Snsouch}
52139136Snsouch
52239136Snsouchstatic int
52339136Snsouchimm_negociate(struct vpoio_data *vpo)
52439136Snsouch{
52539136Snsouch	DECLARE_NEGOCIATE_MICROSEQ;
52655939Snsouch	device_t ppbus = device_get_parent(vpo->vpo_dev);
52739136Snsouch	int negociate_mode;
52839136Snsouch	int ret;
52939136Snsouch
53055939Snsouch	if (PPB_IN_NIBBLE_MODE(ppbus))
53139136Snsouch		negociate_mode = 0;
53255939Snsouch	else if (PPB_IN_PS2_MODE(ppbus))
53339136Snsouch		negociate_mode = 1;
53439136Snsouch	else
53539136Snsouch		return (0);
53639136Snsouch
53739136Snsouch#if 0 /* XXX use standalone code not to depend on ppb_1284 code yet */
53855939Snsouch	ret = ppb_1284_negociate(ppbus, negociate_mode);
53939136Snsouch
54039136Snsouch	if (ret)
54139136Snsouch		return (VP0_ENEGOCIATE);
54239136Snsouch#endif
543185003Sjhb
54455939Snsouch	ppb_MS_init_msq(negociate_microseq, 1,
54555939Snsouch			NEGOCIATED_MODE, negociate_mode);
54639136Snsouch
54755939Snsouch	ppb_MS_microseq(ppbus, vpo->vpo_dev, negociate_microseq, &ret);
54839136Snsouch
54939136Snsouch	return (ret);
55039136Snsouch}
55139136Snsouch
55239136Snsouch/*
55339136Snsouch * imm_probe()
55439136Snsouch *
55539136Snsouch * Low level probe of vpo device
55639136Snsouch *
55739136Snsouch */
55855939Snsouchint
55955939Snsouchimm_probe(device_t dev, struct vpoio_data *vpo)
56039136Snsouch{
56155939Snsouch	int error;
56239136Snsouch
56339136Snsouch	/* ppbus dependent initialisation */
56455939Snsouch	vpo->vpo_dev = dev;
56539136Snsouch
56639136Snsouch	/* now, try to initialise the drive */
56755939Snsouch	if ((error = imm_detect(vpo))) {
56855939Snsouch		return (error);
56939136Snsouch	}
57039136Snsouch
57155939Snsouch	return (0);
57239136Snsouch}
57339136Snsouch
57439136Snsouch/*
57539136Snsouch * imm_attach()
57639136Snsouch *
57739136Snsouch * Low level attachment of vpo device
57839136Snsouch *
57939136Snsouch */
58039136Snsouchint
58139136Snsouchimm_attach(struct vpoio_data *vpo)
58239136Snsouch{
583185003Sjhb	DECLARE_NIBBLE_INBYTE_SUBMICROSEQ;
58455939Snsouch	device_t ppbus = device_get_parent(vpo->vpo_dev);
58578645Snsouch	int error = 0;
58639136Snsouch
58739136Snsouch	/*
58839136Snsouch	 * Initialize microsequence code
58939136Snsouch	 */
59039136Snsouch	vpo->vpo_nibble_inbyte_msq = (struct ppb_microseq *)malloc(
59139136Snsouch		sizeof(nibble_inbyte_submicroseq), M_DEVBUF, M_NOWAIT);
59239136Snsouch
59339136Snsouch	if (!vpo->vpo_nibble_inbyte_msq)
59455939Snsouch		return (ENXIO);
59539136Snsouch
59639136Snsouch	bcopy((void *)nibble_inbyte_submicroseq,
59739136Snsouch		(void *)vpo->vpo_nibble_inbyte_msq,
59839136Snsouch		sizeof(nibble_inbyte_submicroseq));
59939136Snsouch
60078645Snsouch	ppb_MS_init_msq(vpo->vpo_nibble_inbyte_msq, 4,
60178645Snsouch		INB_NIBBLE_H, (void *)&(vpo)->vpo_nibble.h,
60278645Snsouch		INB_NIBBLE_L, (void *)&(vpo)->vpo_nibble.l,
60378645Snsouch		INB_NIBBLE_F, nibble_inbyte_hook,
604185003Sjhb		INB_NIBBLE_P, (void *)&(vpo)->vpo_nibble);
60539136Snsouch
60639136Snsouch	/*
60739136Snsouch	 * Initialize mode dependent in/out microsequences
60839136Snsouch	 */
609187576Sjhb	ppb_lock(ppbus);
61078645Snsouch	if ((error = ppb_request_bus(ppbus, vpo->vpo_dev, PPB_WAIT)))
61178645Snsouch	    goto error;
61239136Snsouch
61370608Snsouch	/* ppbus automatically restore the last mode entered during detection */
61470608Snsouch	switch (vpo->vpo_mode_found) {
61570608Snsouch	case VP0_MODE_EPP:
61670608Snsouch		ppb_MS_GET_init(ppbus, vpo->vpo_dev, epp17_instr);
61770608Snsouch		ppb_MS_PUT_init(ppbus, vpo->vpo_dev, epp17_outstr);
618184130Sjhb		device_printf(vpo->vpo_dev, "EPP mode\n");
61970608Snsouch		break;
62070608Snsouch	case VP0_MODE_PS2:
62155939Snsouch		ppb_MS_GET_init(ppbus, vpo->vpo_dev, ps2_inbyte_submicroseq);
62255939Snsouch		ppb_MS_PUT_init(ppbus, vpo->vpo_dev, spp_outbyte_submicroseq);
623184130Sjhb		device_printf(vpo->vpo_dev, "PS2 mode\n");
62470608Snsouch		break;
62570608Snsouch	case VP0_MODE_NIBBLE:
62670608Snsouch		ppb_MS_GET_init(ppbus, vpo->vpo_dev, vpo->vpo_nibble_inbyte_msq);
62770608Snsouch		ppb_MS_PUT_init(ppbus, vpo->vpo_dev, spp_outbyte_submicroseq);
628184130Sjhb		device_printf(vpo->vpo_dev, "NIBBLE mode\n");
62970608Snsouch		break;
63070608Snsouch	default:
63170608Snsouch		panic("imm: unknown mode %d", vpo->vpo_mode_found);
63239136Snsouch	}
63339136Snsouch
63455939Snsouch	ppb_release_bus(ppbus, vpo->vpo_dev);
63578645Snsouch error:
636187576Sjhb	ppb_unlock(ppbus);
63778645Snsouch	return (error);
63839136Snsouch}
63939136Snsouch
64039136Snsouch/*
64139136Snsouch * imm_reset_bus()
64239136Snsouch *
64339136Snsouch */
64439136Snsouchint
64539136Snsouchimm_reset_bus(struct vpoio_data *vpo)
64639136Snsouch{
64755939Snsouch	device_t ppbus = device_get_parent(vpo->vpo_dev);
64839520Snsouch	int disconnected;
64939136Snsouch
65039520Snsouch	/* first, connect to the drive and request the bus */
65139520Snsouch	imm_connect(vpo, PPB_WAIT|PPB_INTR, &disconnected, 1);
65239136Snsouch
65339520Snsouch	if (!disconnected) {
65439136Snsouch
65539136Snsouch		/* reset the SCSI bus */
65655939Snsouch		ppb_MS_microseq(ppbus, vpo->vpo_dev, reset_microseq, NULL);
65739136Snsouch
65839136Snsouch		/* then disconnect */
65939520Snsouch		imm_disconnect(vpo, NULL, 1);
66039136Snsouch	}
66139136Snsouch
66239136Snsouch	return (0);
66339136Snsouch}
66439136Snsouch
66539136Snsouch/*
66639136Snsouch * imm_do_scsi()
66739136Snsouch *
66839136Snsouch * Send an SCSI command
66939136Snsouch *
67039136Snsouch */
671185003Sjhbint
67239136Snsouchimm_do_scsi(struct vpoio_data *vpo, int host, int target, char *command,
67339136Snsouch		int clen, char *buffer, int blen, int *result, int *count,
67439136Snsouch		int *ret)
67539136Snsouch{
67655939Snsouch	device_t ppbus = device_get_parent(vpo->vpo_dev);
67739136Snsouch	register char r;
67839136Snsouch	char l, h = 0;
67939136Snsouch	int len, error = 0, not_connected = 0;
68039136Snsouch	register int k;
68139136Snsouch	int negociated = 0;
68239136Snsouch
68339136Snsouch	/*
68439136Snsouch	 * enter disk state, allocate the ppbus
68539136Snsouch	 *
68639136Snsouch	 * XXX
68739136Snsouch	 * Should we allow this call to be interruptible?
68839136Snsouch	 * The only way to report the interruption is to return
68978645Snsouch	 * EIO to upper SCSI code :^(
69039136Snsouch	 */
69139520Snsouch	if ((error = imm_connect(vpo, PPB_WAIT|PPB_INTR, &not_connected, 1)))
69239136Snsouch		return (error);
69339136Snsouch
69439136Snsouch	if (not_connected) {
695185003Sjhb		*ret = VP0_ECONNECT;
696185003Sjhb		goto error;
69739136Snsouch	}
69839136Snsouch
69939136Snsouch	/*
70039136Snsouch	 * Select the drive ...
70139136Snsouch	 */
70239136Snsouch	if ((*ret = imm_select(vpo,host,target)))
70339136Snsouch		goto error;
70439136Snsouch
70539136Snsouch	/*
70639136Snsouch	 * Send the command ...
70739136Snsouch	 */
70839136Snsouch	for (k = 0; k < clen; k+=2) {
70939136Snsouch		if (imm_wait(vpo, VP0_FAST_SPINTMO) != (char)0xa8) {
71039136Snsouch			*ret = VP0_ECMD_TIMEOUT;
71139136Snsouch			goto error;
71239136Snsouch		}
71339136Snsouch		if (imm_outstr(vpo, &command[k], 2)) {
71439136Snsouch			*ret = VP0_EPPDATA_TIMEOUT;
71539136Snsouch			goto error;
71639136Snsouch		}
71739136Snsouch	}
71839136Snsouch
71939136Snsouch	if (!(r = imm_wait(vpo, VP0_LOW_SPINTMO))) {
720185003Sjhb		*ret = VP0_ESTATUS_TIMEOUT;
721185003Sjhb		goto error;
72239136Snsouch	}
72339136Snsouch
72439136Snsouch	if ((r & 0x30) == 0x10) {
72539136Snsouch		if (imm_negociate(vpo)) {
72639136Snsouch			*ret = VP0_ENEGOCIATE;
72739136Snsouch			goto error;
72839136Snsouch		} else
72939136Snsouch			negociated = 1;
73039136Snsouch	}
73139136Snsouch
732185003Sjhb	/*
733185003Sjhb	 * Complete transfer ...
73439136Snsouch	 */
73539136Snsouch	*count = 0;
73639136Snsouch	for (;;) {
73739136Snsouch
73839136Snsouch		if (!(r = imm_wait(vpo, VP0_LOW_SPINTMO))) {
739185003Sjhb			*ret = VP0_ESTATUS_TIMEOUT;
740185003Sjhb			goto error;
74139136Snsouch		}
74239136Snsouch
74339136Snsouch		/* stop when the ZIP+ wants to send status */
74439136Snsouch		if (r == (char)0xb8)
74539136Snsouch			break;
74639136Snsouch
74739136Snsouch		if (*count >= blen) {
74839136Snsouch			*ret = VP0_EDATA_OVERFLOW;
74939136Snsouch			goto error;
75039136Snsouch		}
75139136Snsouch
75239136Snsouch		/* ZIP+ wants to send data? */
75339136Snsouch		if (r == (char)0x88) {
75439136Snsouch			len = (((blen - *count) >= VP0_SECTOR_SIZE)) ?
75539136Snsouch				VP0_SECTOR_SIZE : 2;
75639136Snsouch
75739136Snsouch			error = imm_outstr(vpo, &buffer[*count], len);
75839136Snsouch		} else {
75955939Snsouch			if (!PPB_IN_EPP_MODE(ppbus))
76039136Snsouch				len = 1;
76139136Snsouch			else
76239136Snsouch				len = (((blen - *count) >= VP0_SECTOR_SIZE)) ?
76339136Snsouch					VP0_SECTOR_SIZE : 1;
76439136Snsouch
76539136Snsouch			error = imm_instr(vpo, &buffer[*count], len);
76639136Snsouch		}
76739136Snsouch
76839136Snsouch		if (error) {
76939136Snsouch			*ret = error;
77039136Snsouch			goto error;
77139136Snsouch		}
77239136Snsouch
77339136Snsouch		*count += len;
77439136Snsouch	}
77539136Snsouch
77655939Snsouch	if ((PPB_IN_NIBBLE_MODE(ppbus) ||
77755939Snsouch			PPB_IN_PS2_MODE(ppbus)) && negociated)
77855939Snsouch		ppb_MS_microseq(ppbus, vpo->vpo_dev, transfer_epilog, NULL);
77939136Snsouch
78039136Snsouch	/*
78139136Snsouch	 * Retrieve status ...
78239136Snsouch	 */
78339136Snsouch	if (imm_negociate(vpo)) {
78439136Snsouch		*ret = VP0_ENEGOCIATE;
78539136Snsouch		goto error;
78639136Snsouch	} else
78739136Snsouch		negociated = 1;
78839136Snsouch
78939136Snsouch	if (imm_instr(vpo, &l, 1)) {
790185003Sjhb		*ret = VP0_EOTHER;
791185003Sjhb		goto error;
79239136Snsouch	}
79339136Snsouch
79439136Snsouch	/* check if the ZIP+ wants to send more status */
79539136Snsouch	if (imm_wait(vpo, VP0_FAST_SPINTMO) == (char)0xb8)
79639136Snsouch		if (imm_instr(vpo, &h, 1)) {
797185003Sjhb			*ret = VP0_EOTHER + 2;
798185003Sjhb			goto error;
79939136Snsouch		}
80039136Snsouch
80171633Snsouch	/* Experience showed that we should discard this */
802153586Ssam	if (h == (char) -1)
80371633Snsouch		h = 0;
80471633Snsouch
80539136Snsouch	*result = ((int) h << 8) | ((int) l & 0xff);
80639136Snsouch
80739136Snsoucherror:
80855939Snsouch	if ((PPB_IN_NIBBLE_MODE(ppbus) ||
80955939Snsouch			PPB_IN_PS2_MODE(ppbus)) && negociated)
81055939Snsouch		ppb_MS_microseq(ppbus, vpo->vpo_dev, transfer_epilog, NULL);
81139136Snsouch
81239136Snsouch	/* return to printer state, release the ppbus */
81339520Snsouch	imm_disconnect(vpo, NULL, 1);
81439136Snsouch
81539136Snsouch	return (0);
81639136Snsouch}
817