immio.c revision 153586
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: head/sys/dev/ppbus/immio.c 153586 2005-12-21 00:09:11Z sam $");
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: */
25339136Snsouch	  MS_RASSERT_P(1, MS_REG_DTR),
25439136Snsouch	  MS_CASS(0x5),
25543433Snsouch	  MS_DBRA(0),				/* decrement counter */
25639136Snsouch	  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),
26843433Snsouch	  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),
27543433Snsouch	  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)
30140783Snsouch			printf("imm%d: (disconnect) s1=0x%x s2=0x%x, s3=0x%x\n",
30240783Snsouch				vpo->vpo_unit, s1 & 0xff, s2 & 0xff, s3 & 0xff);
30340783Snsouch		if (connected)
30440783Snsouch			*connected = VP0_ECONNECT;
30540783Snsouch	}
30639136Snsouch
30739520Snsouch	if (release_bus)
30855939Snsouch		return (ppb_release_bus(ppbus, vpo->vpo_dev));
30939520Snsouch	else
31039520Snsouch		return (0);
31139136Snsouch}
31239136Snsouch
31339136Snsouch/*
31439136Snsouch * how	: PPB_WAIT or PPB_DONTWAIT
31539136Snsouch */
31639136Snsouchstatic int
31739520Snsouchimm_connect(struct vpoio_data *vpo, int how, int *disconnected, int request_bus)
31839136Snsouch{
31939136Snsouch	DECLARE_CPP_MICROSEQ;
32039136Snsouch
32155939Snsouch	device_t ppbus = device_get_parent(vpo->vpo_dev);
32239136Snsouch	char s1, s2, s3;
32339136Snsouch	int error;
32439136Snsouch	int ret;
32539136Snsouch
32639136Snsouch	/* all should be ok */
32739520Snsouch	if (disconnected)
32839520Snsouch		*disconnected = 0;
32939136Snsouch
33039520Snsouch	if (request_bus)
33155939Snsouch		if ((error = ppb_request_bus(ppbus, vpo->vpo_dev, how)))
33239520Snsouch			return (error);
33339136Snsouch
33439136Snsouch	ppb_MS_init_msq(cpp_microseq, 3, CPP_S1, (void *)&s1,
33539136Snsouch			CPP_S2, (void *)&s2, CPP_S3, (void *)&s3);
33639136Snsouch
33739136Snsouch	/* select device 0 in compatible mode */
33839136Snsouch	ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0xe0);
33955939Snsouch	ppb_MS_microseq(ppbus, vpo->vpo_dev, cpp_microseq, &ret);
34039136Snsouch
34139136Snsouch	/* disconnect all devices */
34239136Snsouch	ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0x30);
34355939Snsouch	ppb_MS_microseq(ppbus, vpo->vpo_dev, cpp_microseq, &ret);
34439136Snsouch
34555939Snsouch	if (PPB_IN_EPP_MODE(ppbus))
34639136Snsouch		ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0x28);
34739136Snsouch	else
34839136Snsouch		ppb_MS_init_msq(cpp_microseq, 1, CPP_PARAM, 0xe0);
34939136Snsouch
35055939Snsouch	ppb_MS_microseq(ppbus, vpo->vpo_dev, cpp_microseq, &ret);
35139136Snsouch
35240783Snsouch	if ((s1 != (char)0xb8 || s2 != (char)0x18 || s3 != (char)0x30)) {
35340783Snsouch		if (bootverbose)
35440783Snsouch			printf("imm%d: (connect) s1=0x%x s2=0x%x, s3=0x%x\n",
35540783Snsouch				vpo->vpo_unit, s1 & 0xff, s2 & 0xff, s3 & 0xff);
35640783Snsouch		if (disconnected)
35740783Snsouch			*disconnected = VP0_ECONNECT;
35840783Snsouch	}
35939136Snsouch
36039136Snsouch	return (0);
36139136Snsouch}
36239136Snsouch
36339136Snsouch/*
36439136Snsouch * imm_detect()
36539136Snsouch *
36639136Snsouch * Detect and initialise the VP0 adapter.
36739136Snsouch */
36839136Snsouchstatic int
36939136Snsouchimm_detect(struct vpoio_data *vpo)
37039136Snsouch{
37155939Snsouch	device_t ppbus = device_get_parent(vpo->vpo_dev);
37239136Snsouch	int error;
37339136Snsouch
37455939Snsouch	if ((error = ppb_request_bus(ppbus, vpo->vpo_dev, PPB_DONTWAIT)))
37539520Snsouch		return (error);
37639136Snsouch
37739520Snsouch	/* disconnect the drive, keep the bus */
37839520Snsouch	imm_disconnect(vpo, NULL, 0);
37939136Snsouch
38070608Snsouch	vpo->vpo_mode_found = VP0_MODE_UNDEFINED;
38170608Snsouch	error = 1;
38239520Snsouch
38370608Snsouch	/* try to enter EPP mode since vpoio failure put the bus in NIBBLE */
38470608Snsouch	if (ppb_set_mode(ppbus, PPB_EPP) != -1) {
38570608Snsouch		imm_connect(vpo, PPB_DONTWAIT, &error, 0);
38670608Snsouch	}
38770608Snsouch
38870608Snsouch	/* if connection failed try PS/2 then NIBBLE modes */
38939520Snsouch	if (error) {
39070608Snsouch		if (ppb_set_mode(ppbus, PPB_PS2) != -1) {
39170608Snsouch			imm_connect(vpo, PPB_DONTWAIT, &error, 0);
39270608Snsouch		}
39370608Snsouch		if (error) {
39470608Snsouch			if (ppb_set_mode(ppbus, PPB_NIBBLE) != -1) {
39570608Snsouch				imm_connect(vpo, PPB_DONTWAIT, &error, 0);
39670608Snsouch				if (error)
39770608Snsouch					goto error;
39870608Snsouch				vpo->vpo_mode_found = VP0_MODE_NIBBLE;
39970608Snsouch			} else {
40070608Snsouch				printf("imm%d: NIBBLE mode unavailable!\n", vpo->vpo_unit);
40170608Snsouch				goto error;
40270608Snsouch			}
40370608Snsouch		} else {
40470608Snsouch			vpo->vpo_mode_found = VP0_MODE_PS2;
40570608Snsouch		}
40670608Snsouch	} else {
40770608Snsouch		vpo->vpo_mode_found = VP0_MODE_EPP;
40839520Snsouch	}
40939520Snsouch
41039136Snsouch	/* send SCSI reset signal */
41155939Snsouch	ppb_MS_microseq(ppbus, vpo->vpo_dev, reset_microseq, NULL);
41239136Snsouch
41339520Snsouch	/* release the bus now */
41439520Snsouch	imm_disconnect(vpo, &error, 1);
41539136Snsouch
41639136Snsouch	/* ensure we are disconnected or daisy chained peripheral
41739136Snsouch	 * may cause serious problem to the disk */
41839136Snsouch
41939520Snsouch	if (error) {
42039520Snsouch		if (bootverbose)
42139520Snsouch			printf("imm%d: can't disconnect from the drive\n",
42239520Snsouch				vpo->vpo_unit);
42339900Snsouch		goto error;
42439520Snsouch	}
42539136Snsouch
42639136Snsouch	return (0);
42739520Snsouch
42839520Snsoucherror:
42955939Snsouch	ppb_release_bus(ppbus, vpo->vpo_dev);
43039520Snsouch	return (VP0_EINITFAILED);
43139136Snsouch}
43239136Snsouch
43339136Snsouch/*
43439136Snsouch * imm_outstr()
43539136Snsouch */
43639136Snsouchstatic int
43739136Snsouchimm_outstr(struct vpoio_data *vpo, char *buffer, int size)
43839136Snsouch{
43955939Snsouch	device_t ppbus = device_get_parent(vpo->vpo_dev);
44039136Snsouch	int error = 0;
44139136Snsouch
44255939Snsouch	if (PPB_IN_EPP_MODE(ppbus))
44355939Snsouch		ppb_reset_epp_timeout(ppbus);
44439136Snsouch
44555939Snsouch	ppb_MS_exec(ppbus, vpo->vpo_dev, MS_OP_PUT, (union ppb_insarg)buffer,
44645342Speter		(union ppb_insarg)size, (union ppb_insarg)MS_UNKNOWN, &error);
44739136Snsouch
44839136Snsouch	return (error);
44939136Snsouch}
45039136Snsouch
45139136Snsouch/*
45239136Snsouch * imm_instr()
45339136Snsouch */
45439136Snsouchstatic int
45539136Snsouchimm_instr(struct vpoio_data *vpo, char *buffer, int size)
45639136Snsouch{
45755939Snsouch	device_t ppbus = device_get_parent(vpo->vpo_dev);
45839136Snsouch	int error = 0;
45939136Snsouch
46055939Snsouch	if (PPB_IN_EPP_MODE(ppbus))
46155939Snsouch		ppb_reset_epp_timeout(ppbus);
46239136Snsouch
46355939Snsouch	ppb_MS_exec(ppbus, vpo->vpo_dev, MS_OP_GET, (union ppb_insarg)buffer,
46445342Speter		(union ppb_insarg)size, (union ppb_insarg)MS_UNKNOWN, &error);
46539136Snsouch
46639136Snsouch	return (error);
46739136Snsouch}
46839136Snsouch
46939136Snsouchstatic char
47039136Snsouchimm_select(struct vpoio_data *vpo, int initiator, int target)
47139136Snsouch{
47239136Snsouch	DECLARE_SELECT_MICROSEQUENCE;
47355939Snsouch	device_t ppbus = device_get_parent(vpo->vpo_dev);
47439136Snsouch	int ret;
47539136Snsouch
47639136Snsouch	/* initialize the select microsequence */
47739136Snsouch	ppb_MS_init_msq(select_microseq, 1,
47839136Snsouch			SELECT_TARGET, 1 << initiator | 1 << target);
47939136Snsouch
48055939Snsouch	ppb_MS_microseq(ppbus, vpo->vpo_dev, select_microseq, &ret);
48139136Snsouch
48239136Snsouch	return (ret);
48339136Snsouch}
48439136Snsouch
48539136Snsouch/*
48639136Snsouch * imm_wait()
48739136Snsouch *
48839136Snsouch * H_SELIN must be low.
48939136Snsouch *
49039136Snsouch */
49139136Snsouchstatic char
49239136Snsouchimm_wait(struct vpoio_data *vpo, int tmo)
49339136Snsouch{
49478645Snsouch	DECLARE_WAIT_MICROSEQUENCE;
49578645Snsouch
49655939Snsouch	device_t ppbus = device_get_parent(vpo->vpo_dev);
49778645Snsouch	int ret, err;
49839136Snsouch
49939136Snsouch	/*
50039136Snsouch	 * Return some status information.
50139136Snsouch	 * Semantics :	0x88 = ZIP+ wants more data
50239136Snsouch	 *		0x98 = ZIP+ wants to send more data
50339136Snsouch	 *		0xa8 = ZIP+ wants command
50439136Snsouch	 *		0xb8 = end of transfer, ZIP+ is sending status
50539136Snsouch	 */
50639136Snsouch
50778645Snsouch	ppb_MS_init_msq(wait_microseq, 2,
50878645Snsouch			WAIT_RET, (void *)&ret,
50978645Snsouch			WAIT_TMO, tmo);
51078645Snsouch
51178645Snsouch	ppb_MS_microseq(ppbus, vpo->vpo_dev, wait_microseq, &err);
51278645Snsouch
51378645Snsouch	if (err)
51478645Snsouch		return (0);			   /* command timed out */
51578645Snsouch
51678645Snsouch	return(ret);
51739136Snsouch}
51839136Snsouch
51939136Snsouchstatic int
52039136Snsouchimm_negociate(struct vpoio_data *vpo)
52139136Snsouch{
52239136Snsouch	DECLARE_NEGOCIATE_MICROSEQ;
52355939Snsouch	device_t ppbus = device_get_parent(vpo->vpo_dev);
52439136Snsouch	int negociate_mode;
52539136Snsouch	int ret;
52639136Snsouch
52755939Snsouch	if (PPB_IN_NIBBLE_MODE(ppbus))
52839136Snsouch		negociate_mode = 0;
52955939Snsouch	else if (PPB_IN_PS2_MODE(ppbus))
53039136Snsouch		negociate_mode = 1;
53139136Snsouch	else
53239136Snsouch		return (0);
53339136Snsouch
53439136Snsouch#if 0 /* XXX use standalone code not to depend on ppb_1284 code yet */
53555939Snsouch	ret = ppb_1284_negociate(ppbus, negociate_mode);
53639136Snsouch
53739136Snsouch	if (ret)
53839136Snsouch		return (VP0_ENEGOCIATE);
53939136Snsouch#endif
54039136Snsouch
54155939Snsouch	ppb_MS_init_msq(negociate_microseq, 1,
54255939Snsouch			NEGOCIATED_MODE, negociate_mode);
54339136Snsouch
54455939Snsouch	ppb_MS_microseq(ppbus, vpo->vpo_dev, negociate_microseq, &ret);
54539136Snsouch
54639136Snsouch	return (ret);
54739136Snsouch}
54839136Snsouch
54939136Snsouch/*
55039136Snsouch * imm_probe()
55139136Snsouch *
55239136Snsouch * Low level probe of vpo device
55339136Snsouch *
55439136Snsouch */
55555939Snsouchint
55655939Snsouchimm_probe(device_t dev, struct vpoio_data *vpo)
55739136Snsouch{
55855939Snsouch	int error;
55939136Snsouch
56039136Snsouch	/* ppbus dependent initialisation */
56155939Snsouch	vpo->vpo_dev = dev;
56239136Snsouch
56339136Snsouch	/* now, try to initialise the drive */
56455939Snsouch	if ((error = imm_detect(vpo))) {
56555939Snsouch		return (error);
56639136Snsouch	}
56739136Snsouch
56855939Snsouch	return (0);
56939136Snsouch}
57039136Snsouch
57139136Snsouch/*
57239136Snsouch * imm_attach()
57339136Snsouch *
57439136Snsouch * Low level attachment of vpo device
57539136Snsouch *
57639136Snsouch */
57739136Snsouchint
57839136Snsouchimm_attach(struct vpoio_data *vpo)
57939136Snsouch{
58078645Snsouch	DECLARE_NIBBLE_INBYTE_SUBMICROSEQ;
58155939Snsouch	device_t ppbus = device_get_parent(vpo->vpo_dev);
58278645Snsouch	int error = 0;
58339136Snsouch
58439136Snsouch	/*
58539136Snsouch	 * Initialize microsequence code
58639136Snsouch	 */
58739136Snsouch	vpo->vpo_nibble_inbyte_msq = (struct ppb_microseq *)malloc(
58839136Snsouch		sizeof(nibble_inbyte_submicroseq), M_DEVBUF, M_NOWAIT);
58939136Snsouch
59039136Snsouch	if (!vpo->vpo_nibble_inbyte_msq)
59155939Snsouch		return (ENXIO);
59239136Snsouch
59339136Snsouch	bcopy((void *)nibble_inbyte_submicroseq,
59439136Snsouch		(void *)vpo->vpo_nibble_inbyte_msq,
59539136Snsouch		sizeof(nibble_inbyte_submicroseq));
59639136Snsouch
59778645Snsouch	ppb_MS_init_msq(vpo->vpo_nibble_inbyte_msq, 4,
59878645Snsouch		INB_NIBBLE_H, (void *)&(vpo)->vpo_nibble.h,
59978645Snsouch		INB_NIBBLE_L, (void *)&(vpo)->vpo_nibble.l,
60078645Snsouch		INB_NIBBLE_F, nibble_inbyte_hook,
60178645Snsouch		INB_NIBBLE_P, (void *)&(vpo)->vpo_nibble);
60239136Snsouch
60339136Snsouch	/*
60439136Snsouch	 * Initialize mode dependent in/out microsequences
60539136Snsouch	 */
60678645Snsouch	if ((error = ppb_request_bus(ppbus, vpo->vpo_dev, PPB_WAIT)))
60778645Snsouch	    goto error;
60839136Snsouch
60970608Snsouch	/* ppbus automatically restore the last mode entered during detection */
61070608Snsouch	switch (vpo->vpo_mode_found) {
61170608Snsouch	case VP0_MODE_EPP:
61270608Snsouch		ppb_MS_GET_init(ppbus, vpo->vpo_dev, epp17_instr);
61370608Snsouch		ppb_MS_PUT_init(ppbus, vpo->vpo_dev, epp17_outstr);
61470608Snsouch		printf("imm%d: EPP mode\n", vpo->vpo_unit);
61570608Snsouch		break;
61670608Snsouch	case VP0_MODE_PS2:
61755939Snsouch		ppb_MS_GET_init(ppbus, vpo->vpo_dev, ps2_inbyte_submicroseq);
61855939Snsouch		ppb_MS_PUT_init(ppbus, vpo->vpo_dev, spp_outbyte_submicroseq);
61939136Snsouch		printf("imm%d: PS2 mode\n", vpo->vpo_unit);
62070608Snsouch		break;
62170608Snsouch	case VP0_MODE_NIBBLE:
62270608Snsouch		ppb_MS_GET_init(ppbus, vpo->vpo_dev, vpo->vpo_nibble_inbyte_msq);
62370608Snsouch		ppb_MS_PUT_init(ppbus, vpo->vpo_dev, spp_outbyte_submicroseq);
62439136Snsouch		printf("imm%d: NIBBLE mode\n", vpo->vpo_unit);
62570608Snsouch		break;
62670608Snsouch	default:
62770608Snsouch		panic("imm: unknown mode %d", vpo->vpo_mode_found);
62839136Snsouch	}
62939136Snsouch
63055939Snsouch	ppb_release_bus(ppbus, vpo->vpo_dev);
63178645Snsouch error:
63278645Snsouch	return (error);
63339136Snsouch}
63439136Snsouch
63539136Snsouch/*
63639136Snsouch * imm_reset_bus()
63739136Snsouch *
63839136Snsouch */
63939136Snsouchint
64039136Snsouchimm_reset_bus(struct vpoio_data *vpo)
64139136Snsouch{
64255939Snsouch	device_t ppbus = device_get_parent(vpo->vpo_dev);
64339520Snsouch	int disconnected;
64439136Snsouch
64539520Snsouch	/* first, connect to the drive and request the bus */
64639520Snsouch	imm_connect(vpo, PPB_WAIT|PPB_INTR, &disconnected, 1);
64739136Snsouch
64839520Snsouch	if (!disconnected) {
64939136Snsouch
65039136Snsouch		/* reset the SCSI bus */
65155939Snsouch		ppb_MS_microseq(ppbus, vpo->vpo_dev, reset_microseq, NULL);
65239136Snsouch
65339136Snsouch		/* then disconnect */
65439520Snsouch		imm_disconnect(vpo, NULL, 1);
65539136Snsouch	}
65639136Snsouch
65739136Snsouch	return (0);
65839136Snsouch}
65939136Snsouch
66039136Snsouch/*
66139136Snsouch * imm_do_scsi()
66239136Snsouch *
66339136Snsouch * Send an SCSI command
66439136Snsouch *
66539136Snsouch */
66639136Snsouchint
66739136Snsouchimm_do_scsi(struct vpoio_data *vpo, int host, int target, char *command,
66839136Snsouch		int clen, char *buffer, int blen, int *result, int *count,
66939136Snsouch		int *ret)
67039136Snsouch{
67155939Snsouch	device_t ppbus = device_get_parent(vpo->vpo_dev);
67239136Snsouch	register char r;
67339136Snsouch	char l, h = 0;
67439136Snsouch	int len, error = 0, not_connected = 0;
67539136Snsouch	register int k;
67639136Snsouch	int negociated = 0;
67739136Snsouch
67839136Snsouch	/*
67939136Snsouch	 * enter disk state, allocate the ppbus
68039136Snsouch	 *
68139136Snsouch	 * XXX
68239136Snsouch	 * Should we allow this call to be interruptible?
68339136Snsouch	 * The only way to report the interruption is to return
68478645Snsouch	 * EIO to upper SCSI code :^(
68539136Snsouch	 */
68639520Snsouch	if ((error = imm_connect(vpo, PPB_WAIT|PPB_INTR, &not_connected, 1)))
68739136Snsouch		return (error);
68839136Snsouch
68939136Snsouch	if (not_connected) {
69039136Snsouch		*ret = VP0_ECONNECT; goto error;
69139136Snsouch	}
69239136Snsouch
69339136Snsouch	/*
69439136Snsouch	 * Select the drive ...
69539136Snsouch	 */
69639136Snsouch	if ((*ret = imm_select(vpo,host,target)))
69739136Snsouch		goto error;
69839136Snsouch
69939136Snsouch	/*
70039136Snsouch	 * Send the command ...
70139136Snsouch	 */
70239136Snsouch	for (k = 0; k < clen; k+=2) {
70339136Snsouch		if (imm_wait(vpo, VP0_FAST_SPINTMO) != (char)0xa8) {
70439136Snsouch			*ret = VP0_ECMD_TIMEOUT;
70539136Snsouch			goto error;
70639136Snsouch		}
70739136Snsouch		if (imm_outstr(vpo, &command[k], 2)) {
70839136Snsouch			*ret = VP0_EPPDATA_TIMEOUT;
70939136Snsouch			goto error;
71039136Snsouch		}
71139136Snsouch	}
71239136Snsouch
71339136Snsouch	if (!(r = imm_wait(vpo, VP0_LOW_SPINTMO))) {
71439136Snsouch		*ret = VP0_ESTATUS_TIMEOUT; goto error;
71539136Snsouch	}
71639136Snsouch
71739136Snsouch	if ((r & 0x30) == 0x10) {
71839136Snsouch		if (imm_negociate(vpo)) {
71939136Snsouch			*ret = VP0_ENEGOCIATE;
72039136Snsouch			goto error;
72139136Snsouch		} else
72239136Snsouch			negociated = 1;
72339136Snsouch	}
72439136Snsouch
72539136Snsouch	/*
72639136Snsouch	 * Complete transfer ...
72739136Snsouch	 */
72839136Snsouch	*count = 0;
72939136Snsouch	for (;;) {
73039136Snsouch
73139136Snsouch		if (!(r = imm_wait(vpo, VP0_LOW_SPINTMO))) {
73239136Snsouch			*ret = VP0_ESTATUS_TIMEOUT; goto error;
73339136Snsouch		}
73439136Snsouch
73539136Snsouch		/* stop when the ZIP+ wants to send status */
73639136Snsouch		if (r == (char)0xb8)
73739136Snsouch			break;
73839136Snsouch
73939136Snsouch		if (*count >= blen) {
74039136Snsouch			*ret = VP0_EDATA_OVERFLOW;
74139136Snsouch			goto error;
74239136Snsouch		}
74339136Snsouch
74439136Snsouch		/* ZIP+ wants to send data? */
74539136Snsouch		if (r == (char)0x88) {
74639136Snsouch			len = (((blen - *count) >= VP0_SECTOR_SIZE)) ?
74739136Snsouch				VP0_SECTOR_SIZE : 2;
74839136Snsouch
74939136Snsouch			error = imm_outstr(vpo, &buffer[*count], len);
75039136Snsouch		} else {
75155939Snsouch			if (!PPB_IN_EPP_MODE(ppbus))
75239136Snsouch				len = 1;
75339136Snsouch			else
75439136Snsouch				len = (((blen - *count) >= VP0_SECTOR_SIZE)) ?
75539136Snsouch					VP0_SECTOR_SIZE : 1;
75639136Snsouch
75739136Snsouch			error = imm_instr(vpo, &buffer[*count], len);
75839136Snsouch		}
75939136Snsouch
76039136Snsouch		if (error) {
76139136Snsouch			*ret = error;
76239136Snsouch			goto error;
76339136Snsouch		}
76439136Snsouch
76539136Snsouch		*count += len;
76639136Snsouch	}
76739136Snsouch
76855939Snsouch	if ((PPB_IN_NIBBLE_MODE(ppbus) ||
76955939Snsouch			PPB_IN_PS2_MODE(ppbus)) && negociated)
77055939Snsouch		ppb_MS_microseq(ppbus, vpo->vpo_dev, transfer_epilog, NULL);
77139136Snsouch
77239136Snsouch	/*
77339136Snsouch	 * Retrieve status ...
77439136Snsouch	 */
77539136Snsouch	if (imm_negociate(vpo)) {
77639136Snsouch		*ret = VP0_ENEGOCIATE;
77739136Snsouch		goto error;
77839136Snsouch	} else
77939136Snsouch		negociated = 1;
78039136Snsouch
78139136Snsouch	if (imm_instr(vpo, &l, 1)) {
78239136Snsouch		*ret = VP0_EOTHER; goto error;
78339136Snsouch	}
78439136Snsouch
78539136Snsouch	/* check if the ZIP+ wants to send more status */
78639136Snsouch	if (imm_wait(vpo, VP0_FAST_SPINTMO) == (char)0xb8)
78739136Snsouch		if (imm_instr(vpo, &h, 1)) {
78839136Snsouch			*ret = VP0_EOTHER+2; goto error;
78939136Snsouch		}
79039136Snsouch
79171633Snsouch	/* Experience showed that we should discard this */
792153586Ssam	if (h == (char) -1)
79371633Snsouch		h = 0;
79471633Snsouch
79539136Snsouch	*result = ((int) h << 8) | ((int) l & 0xff);
79639136Snsouch
79739136Snsoucherror:
79855939Snsouch	if ((PPB_IN_NIBBLE_MODE(ppbus) ||
79955939Snsouch			PPB_IN_PS2_MODE(ppbus)) && negociated)
80055939Snsouch		ppb_MS_microseq(ppbus, vpo->vpo_dev, transfer_epilog, NULL);
80139136Snsouch
80239136Snsouch	/* return to printer state, release the ppbus */
80339520Snsouch	imm_disconnect(vpo, NULL, 1);
80439136Snsouch
80539136Snsouch	return (0);
80639136Snsouch}
807