1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 1997-2000 Nicolas Souchu
5 * Copyright (c) 2001 Alcove - Nicolas Souchu
6 * Copyright (c) 2006 Marcel Moolenaar
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 *    notice, this list of conditions and the following disclaimer in the
16 *    documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28 * SUCH DAMAGE.
29 */
30
31#include <sys/cdefs.h>
32__FBSDID("$FreeBSD$");
33
34#include <sys/param.h>
35#include <sys/systm.h>
36#include <sys/kernel.h>
37#include <sys/lock.h>
38#include <sys/module.h>
39#include <sys/mutex.h>
40#include <sys/bus.h>
41#include <machine/bus.h>
42#include <sys/malloc.h>
43#include <sys/rman.h>
44
45#include <isa/isavar.h>
46
47#include <dev/ppbus/ppbconf.h>
48#include <dev/ppbus/ppb_msq.h>
49#include <dev/ppc/ppcvar.h>
50#include <dev/ppc/ppcreg.h>
51
52#include "ppbus_if.h"
53
54static int ppc_isa_probe(device_t dev);
55
56int ppc_isa_attach(device_t dev);
57int ppc_isa_write(device_t, char *, int, int);
58
59static device_method_t ppc_isa_methods[] = {
60	/* device interface */
61	DEVMETHOD(device_probe,		ppc_isa_probe),
62	DEVMETHOD(device_attach,	ppc_isa_attach),
63	DEVMETHOD(device_detach,	ppc_detach),
64
65	/* bus interface */
66	DEVMETHOD(bus_read_ivar,	ppc_read_ivar),
67	DEVMETHOD(bus_write_ivar,	ppc_write_ivar),
68	DEVMETHOD(bus_alloc_resource,	ppc_alloc_resource),
69	DEVMETHOD(bus_release_resource,	ppc_release_resource),
70
71	/* ppbus interface */
72	DEVMETHOD(ppbus_io,		ppc_io),
73	DEVMETHOD(ppbus_exec_microseq,	ppc_exec_microseq),
74	DEVMETHOD(ppbus_reset_epp,	ppc_reset_epp),
75	DEVMETHOD(ppbus_setmode,	ppc_setmode),
76	DEVMETHOD(ppbus_ecp_sync,	ppc_ecp_sync),
77	DEVMETHOD(ppbus_read,		ppc_read),
78	DEVMETHOD(ppbus_write,		ppc_isa_write),
79
80	{ 0, 0 }
81};
82
83static driver_t ppc_isa_driver = {
84	ppc_driver_name,
85	ppc_isa_methods,
86	sizeof(struct ppc_data),
87};
88
89static struct isa_pnp_id lpc_ids[] = {
90	{ 0x0004d041, "Standard parallel printer port" },	/* PNP0400 */
91	{ 0x0104d041, "ECP parallel printer port" },		/* PNP0401 */
92	{ 0 }
93};
94
95static void
96ppc_isa_dmadone(struct ppc_data *ppc)
97{
98	isa_dmadone(ppc->ppc_dmaflags, ppc->ppc_dmaddr, ppc->ppc_dmacnt,
99	    ppc->ppc_dmachan);
100}
101
102int
103ppc_isa_attach(device_t dev)
104{
105	struct ppc_data *ppc = device_get_softc(dev);
106
107	if ((ppc->ppc_avm & PPB_ECP) && (ppc->ppc_dmachan > 0)) {
108		/* acquire the DMA channel forever */   /* XXX */
109		isa_dma_acquire(ppc->ppc_dmachan);
110		isa_dmainit(ppc->ppc_dmachan, 1024); /* nlpt.BUFSIZE */
111		ppc->ppc_dmadone = ppc_isa_dmadone;
112	}
113
114	return (ppc_attach(dev));
115}
116
117static int
118ppc_isa_probe(device_t dev)
119{
120	device_t parent;
121	int error;
122
123	parent = device_get_parent(dev);
124
125	error = ISA_PNP_PROBE(parent, dev, lpc_ids);
126	if (error == ENXIO)
127		return (ENXIO);
128	if (error != 0)		/* XXX shall be set after detection */
129		device_set_desc(dev, "Parallel port");
130
131	return (ppc_probe(dev, 0));
132}
133
134/*
135 * Call this function if you want to send data in any advanced mode
136 * of your parallel port: FIFO, DMA
137 *
138 * If what you want is not possible (no ECP, no DMA...),
139 * EINVAL is returned
140 */
141int
142ppc_isa_write(device_t dev, char *buf, int len, int how)
143{
144	struct ppc_data *ppc = device_get_softc(dev);
145	char ecr, ecr_sav, ctr, ctr_sav;
146	int error = 0;
147	int spin;
148
149	PPC_ASSERT_LOCKED(ppc);
150	if (!(ppc->ppc_avm & PPB_ECP))
151		return (EINVAL);
152	if (ppc->ppc_dmachan == 0)
153		return (EINVAL);
154
155#ifdef PPC_DEBUG
156	printf("w");
157#endif
158
159	ecr_sav = r_ecr(ppc);
160	ctr_sav = r_ctr(ppc);
161
162	/*
163	 * Send buffer with DMA, FIFO and interrupts
164	 */
165
166	/* byte mode, no intr, no DMA, dir=0, flush fifo */
167	ecr = PPC_ECR_STD | PPC_DISABLE_INTR;
168	w_ecr(ppc, ecr);
169
170	/* disable nAck interrupts */
171	ctr = r_ctr(ppc);
172	ctr &= ~IRQENABLE;
173	w_ctr(ppc, ctr);
174
175	ppc->ppc_dmaflags = 0;
176	ppc->ppc_dmaddr = (caddr_t)buf;
177	ppc->ppc_dmacnt = (u_int)len;
178
179	switch (ppc->ppc_mode) {
180	case PPB_COMPATIBLE:
181		/* compatible mode with FIFO, no intr, DMA, dir=0 */
182		ecr = PPC_ECR_FIFO | PPC_DISABLE_INTR | PPC_ENABLE_DMA;
183		break;
184	case PPB_ECP:
185		ecr = PPC_ECR_ECP | PPC_DISABLE_INTR | PPC_ENABLE_DMA;
186		break;
187	default:
188		error = EINVAL;
189		goto error;
190	}
191
192	w_ecr(ppc, ecr);
193	ecr = r_ecr(ppc);
194
195	ppc->ppc_dmastat = PPC_DMA_INIT;
196
197	/* enable interrupts */
198	ecr &= ~PPC_SERVICE_INTR;
199	ppc->ppc_irqstat = PPC_IRQ_DMA;
200	w_ecr(ppc, ecr);
201
202	isa_dmastart(ppc->ppc_dmaflags, ppc->ppc_dmaddr, ppc->ppc_dmacnt,
203		     ppc->ppc_dmachan);
204	ppc->ppc_dmastat = PPC_DMA_STARTED;
205
206#ifdef PPC_DEBUG
207	printf("s%d", ppc->ppc_dmacnt);
208#endif
209
210	/* Wait for the DMA completed interrupt. We hope we won't
211	 * miss it, otherwise a signal will be necessary to unlock the
212	 * process.
213	 */
214	do {
215		/* release CPU */
216		error = mtx_sleep(ppc, &ppc->ppc_lock, PPBPRI | PCATCH,
217		    "ppcdma", 0);
218	} while (error == EWOULDBLOCK);
219
220	if (error) {
221#ifdef PPC_DEBUG
222		printf("i");
223#endif
224		/* stop DMA */
225		isa_dmadone(ppc->ppc_dmaflags, ppc->ppc_dmaddr,
226			    ppc->ppc_dmacnt, ppc->ppc_dmachan);
227
228		/* no dma, no interrupt, flush the fifo */
229		w_ecr(ppc, PPC_ECR_RESET);
230
231		ppc->ppc_dmastat = PPC_DMA_INTERRUPTED;
232		goto error;
233	}
234
235	/* wait for an empty fifo */
236	while (!(r_ecr(ppc) & PPC_FIFO_EMPTY)) {
237
238		for (spin=100; spin; spin--)
239			if (r_ecr(ppc) & PPC_FIFO_EMPTY)
240				goto fifo_empty;
241#ifdef PPC_DEBUG
242		printf("Z");
243#endif
244		error = mtx_sleep(ppc, &ppc->ppc_lock, PPBPRI | PCATCH,
245		    "ppcfifo", hz / 100);
246		if (error != EWOULDBLOCK) {
247#ifdef PPC_DEBUG
248			printf("I");
249#endif
250			/* no dma, no interrupt, flush the fifo */
251			w_ecr(ppc, PPC_ECR_RESET);
252
253			ppc->ppc_dmastat = PPC_DMA_INTERRUPTED;
254			error = EINTR;
255			goto error;
256		}
257	}
258
259fifo_empty:
260	/* no dma, no interrupt, flush the fifo */
261	w_ecr(ppc, PPC_ECR_RESET);
262
263error:
264	/* PDRQ must be kept unasserted until nPDACK is
265	 * deasserted for a minimum of 350ns (SMC datasheet)
266	 *
267	 * Consequence may be a FIFO that never empty
268	 */
269	DELAY(1);
270
271	w_ecr(ppc, ecr_sav);
272	w_ctr(ppc, ctr_sav);
273	return (error);
274}
275
276DRIVER_MODULE(ppc, isa, ppc_isa_driver, ppc_devclass, 0, 0);
277ISA_PNP_INFO(lpc_ids);
278