xref: /freebsd/sys/dev/puc/puc.c (revision f9218d3d4fd34f082473b3a021c6d4d109fb47cf)
1 /*	$NetBSD: puc.c,v 1.7 2000/07/29 17:43:38 jlam Exp $	*/
2 
3 /*-
4  * Copyright (c) 2002 JF Hay.  All rights reserved.
5  * Copyright (c) 2000 M. Warner Losh.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice unmodified, this list of conditions, and the following
12  *    disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 /*
30  * Copyright (c) 1996, 1998, 1999
31  *	Christopher G. Demetriou.  All rights reserved.
32  *
33  * Redistribution and use in source and binary forms, with or without
34  * modification, are permitted provided that the following conditions
35  * are met:
36  * 1. Redistributions of source code must retain the above copyright
37  *    notice, this list of conditions and the following disclaimer.
38  * 2. Redistributions in binary form must reproduce the above copyright
39  *    notice, this list of conditions and the following disclaimer in the
40  *    documentation and/or other materials provided with the distribution.
41  * 3. All advertising materials mentioning features or use of this software
42  *    must display the following acknowledgement:
43  *      This product includes software developed by Christopher G. Demetriou
44  *	for the NetBSD Project.
45  * 4. The name of the author may not be used to endorse or promote products
46  *    derived from this software without specific prior written permission
47  *
48  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
49  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
50  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
51  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
52  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
53  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
54  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
55  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
56  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
57  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
58  */
59 
60 #include <sys/cdefs.h>
61 __FBSDID("$FreeBSD$");
62 
63 /*
64  * PCI "universal" communication card device driver, glues com, lpt,
65  * and similar ports to PCI via bridge chip often much larger than
66  * the devices being glued.
67  *
68  * Author: Christopher G. Demetriou, May 14, 1998 (derived from NetBSD
69  * sys/dev/pci/pciide.c, revision 1.6).
70  *
71  * These devices could be (and some times are) described as
72  * communications/{serial,parallel}, etc. devices with known
73  * programming interfaces, but those programming interfaces (in
74  * particular the BAR assignments for devices, etc.) in fact are not
75  * particularly well defined.
76  *
77  * After I/we have seen more of these devices, it may be possible
78  * to generalize some of these bits.  In particular, devices which
79  * describe themselves as communications/serial/16[45]50, and
80  * communications/parallel/??? might be attached via direct
81  * 'com' and 'lpt' attachments to pci.
82  */
83 
84 #include "opt_puc.h"
85 
86 #include <sys/param.h>
87 #include <sys/systm.h>
88 #include <sys/kernel.h>
89 #include <sys/bus.h>
90 #include <sys/conf.h>
91 #include <sys/malloc.h>
92 
93 #include <machine/bus.h>
94 #include <machine/resource.h>
95 #include <sys/rman.h>
96 
97 #include <dev/pci/pcireg.h>
98 #include <dev/pci/pcivar.h>
99 
100 #define PUC_ENTRAILS	1
101 #include <dev/puc/pucvar.h>
102 
103 struct puc_device {
104 	struct resource_list resources;
105 	u_int serialfreq;
106 };
107 
108 static void puc_intr(void *arg);
109 
110 static int puc_find_free_unit(char *);
111 #ifdef PUC_DEBUG
112 static void puc_print_resource_list(struct resource_list *);
113 #endif
114 
115 devclass_t puc_devclass;
116 
117 static int
118 puc_port_bar_index(struct puc_softc *sc, int bar)
119 {
120 	int i;
121 
122 	for (i = 0; i < PUC_MAX_BAR; i += 1) {
123 		if (!sc->sc_bar_mappings[i].used)
124 			break;
125 		if (sc->sc_bar_mappings[i].bar == bar)
126 			return (i);
127 	}
128 	sc->sc_bar_mappings[i].bar = bar;
129 	sc->sc_bar_mappings[i].used = 1;
130 	return (i);
131 }
132 
133 int
134 puc_attach(device_t dev, const struct puc_device_description *desc)
135 {
136 	char *typestr;
137 	int bidx, childunit, i, irq_setup, rid, type;
138 	struct puc_softc *sc;
139 	struct puc_device *pdev;
140 	struct resource *res;
141 	struct resource_list_entry *rle;
142 
143 	sc = (struct puc_softc *)device_get_softc(dev);
144 	bzero(sc, sizeof(*sc));
145 	sc->sc_desc = desc;
146 	if (sc->sc_desc == NULL)
147 		return (ENXIO);
148 
149 #ifdef PUC_DEBUG
150 	bootverbose = 1;
151 
152 	printf("puc: name: %s\n", sc->sc_desc->name);
153 #endif
154 	rid = 0;
155 	res = bus_alloc_resource(dev, SYS_RES_IRQ, &rid, 0, ~0, 1,
156 	    RF_ACTIVE | RF_SHAREABLE);
157 	if (!res)
158 		return (ENXIO);
159 
160 	sc->irqres = res;
161 	sc->irqrid = rid;
162 #ifdef PUC_FASTINTR
163 	irq_setup = BUS_SETUP_INTR(device_get_parent(dev), dev, res,
164 	    INTR_TYPE_TTY | INTR_FAST, puc_intr, sc, &sc->intr_cookie);
165 	if (irq_setup == 0)
166 		sc->fastintr = INTR_FAST;
167 	else
168 		irq_setup = BUS_SETUP_INTR(device_get_parent(dev), dev, res,
169 		    INTR_TYPE_TTY, puc_intr, sc, &sc->intr_cookie);
170 #else
171 	irq_setup = BUS_SETUP_INTR(device_get_parent(dev), dev, res,
172 	    INTR_TYPE_TTY, puc_intr, sc, &sc->intr_cookie);
173 #endif
174 	if (irq_setup != 0)
175 		return (ENXIO);
176 
177 	rid = 0;
178 	for (i = 0; PUC_PORT_VALID(sc->sc_desc, i); i++) {
179 		if (i > 0 && rid == sc->sc_desc->ports[i].bar)
180 			sc->barmuxed = 1;
181 		rid = sc->sc_desc->ports[i].bar;
182 		bidx = puc_port_bar_index(sc, rid);
183 
184 		if (sc->sc_bar_mappings[bidx].res != NULL)
185 			continue;
186 
187 		type = (sc->sc_desc->ports[i].flags & PUC_FLAGS_MEMORY)
188 		    ? SYS_RES_MEMORY : SYS_RES_IOPORT;
189 
190 		res = bus_alloc_resource(dev, type, &rid, 0ul, ~0ul, 1,
191 		    RF_ACTIVE);
192 		if (res == NULL) {
193 			printf("could not get resource\n");
194 			continue;
195 		}
196 		sc->sc_bar_mappings[bidx].type = type;
197 		sc->sc_bar_mappings[bidx].res = res;
198 #ifdef PUC_DEBUG
199 		printf("%s rid %d bst %x, start %x, end %x\n",
200 		    (type == SYS_RES_MEMORY) ? "memory" : "port", rid,
201 		    (u_int)rman_get_bustag(res), (u_int)rman_get_start(res),
202 		    (u_int)rman_get_end(res));
203 #endif
204 	}
205 
206 	if (desc->init != NULL) {
207 		i = desc->init(sc);
208 		if (i != 0)
209 			return (i);
210 	}
211 
212 	for (i = 0; PUC_PORT_VALID(sc->sc_desc, i); i++) {
213 		rid = sc->sc_desc->ports[i].bar;
214 		bidx = puc_port_bar_index(sc, rid);
215 		if (sc->sc_bar_mappings[bidx].res == NULL)
216 			continue;
217 
218 		switch (sc->sc_desc->ports[i].type) {
219 		case PUC_PORT_TYPE_COM:
220 			typestr = "sio";
221 			break;
222 		default:
223 			continue;
224 		}
225 		pdev = malloc(sizeof(struct puc_device), M_DEVBUF,
226 		    M_NOWAIT | M_ZERO);
227 		if (!pdev)
228 			continue;
229 		resource_list_init(&pdev->resources);
230 
231 		/* First fake up an IRQ resource. */
232 		resource_list_add(&pdev->resources, SYS_RES_IRQ, 0,
233 		    rman_get_start(sc->irqres), rman_get_end(sc->irqres),
234 		    rman_get_end(sc->irqres) - rman_get_start(sc->irqres) + 1);
235 		rle = resource_list_find(&pdev->resources, SYS_RES_IRQ, 0);
236 		rle->res = sc->irqres;
237 
238 		/* Now fake an IOPORT or MEMORY resource */
239 		res = sc->sc_bar_mappings[bidx].res;
240 		type = sc->sc_bar_mappings[bidx].type;
241 		resource_list_add(&pdev->resources, type, 0,
242 		    rman_get_start(res) + sc->sc_desc->ports[i].offset,
243 		    rman_get_start(res) + sc->sc_desc->ports[i].offset + 8 - 1,
244 		    8);
245 		rle = resource_list_find(&pdev->resources, type, 0);
246 
247 		if (sc->barmuxed == 0) {
248 			rle->res = sc->sc_bar_mappings[bidx].res;
249 		} else {
250 			rle->res = malloc(sizeof(struct resource), M_DEVBUF,
251 			    M_WAITOK | M_ZERO);
252 			if (rle->res == NULL) {
253 				free(pdev, M_DEVBUF);
254 				return (ENOMEM);
255 			}
256 
257 			rle->res->r_start = rman_get_start(res) +
258 			    sc->sc_desc->ports[i].offset;
259 			rle->res->r_end = rle->res->r_start + 8 - 1;
260 			rle->res->r_bustag = rman_get_bustag(res);
261 			bus_space_subregion(rle->res->r_bustag,
262 			    rman_get_bushandle(res),
263 			    sc->sc_desc->ports[i].offset, 8,
264 			    &rle->res->r_bushandle);
265 		}
266 
267 		pdev->serialfreq = sc->sc_desc->ports[i].serialfreq;
268 
269 		childunit = puc_find_free_unit(typestr);
270 		sc->sc_ports[i].dev = device_add_child(dev, typestr, childunit);
271 		if (sc->sc_ports[i].dev == NULL) {
272 			if (sc->barmuxed) {
273 				bus_space_unmap(rman_get_bustag(rle->res),
274 				    rman_get_bushandle(rle->res), 8);
275 				free(rle->res, M_DEVBUF);
276 				free(pdev, M_DEVBUF);
277 			}
278 			continue;
279 		}
280 		device_set_ivars(sc->sc_ports[i].dev, pdev);
281 		device_set_desc(sc->sc_ports[i].dev, sc->sc_desc->name);
282 		if (!bootverbose)
283 			device_quiet(sc->sc_ports[i].dev);
284 #ifdef PUC_DEBUG
285 		printf("puc: type %d, bar %x, offset %x\n",
286 		    sc->sc_desc->ports[i].type,
287 		    sc->sc_desc->ports[i].bar,
288 		    sc->sc_desc->ports[i].offset);
289 		puc_print_resource_list(&pdev->resources);
290 #endif
291 		device_set_flags(sc->sc_ports[i].dev,
292 		    sc->sc_desc->ports[i].flags);
293 		if (device_probe_and_attach(sc->sc_ports[i].dev) != 0) {
294 			if (sc->barmuxed) {
295 				bus_space_unmap(rman_get_bustag(rle->res),
296 						rman_get_bushandle(rle->res),
297 						8);
298 				free(rle->res, M_DEVBUF);
299 				free(pdev, M_DEVBUF);
300 			}
301 		}
302 	}
303 
304 #ifdef PUC_DEBUG
305 	bootverbose = 0;
306 #endif
307 	return (0);
308 }
309 
310 /*
311  * This is just a brute force interrupt handler. It just calls all the
312  * registered handlers sequencially.
313  *
314  * Later on we should maybe have a different handler for boards that can
315  * tell us which device generated the interrupt.
316  */
317 static void
318 puc_intr(void *arg)
319 {
320 	int i;
321 	struct puc_softc *sc;
322 
323 	sc = (struct puc_softc *)arg;
324 	for (i = 0; i < PUC_MAX_PORTS; i++)
325 		if (sc->sc_ports[i].ihand != NULL)
326 			(sc->sc_ports[i].ihand)(sc->sc_ports[i].ihandarg);
327 }
328 
329 const struct puc_device_description *
330 puc_find_description(uint32_t vend, uint32_t prod, uint32_t svend,
331     uint32_t sprod)
332 {
333 	int i;
334 
335 #define checkreg(val, index) \
336     (((val) & puc_devices[i].rmask[(index)]) == puc_devices[i].rval[(index)])
337 
338 	for (i = 0; puc_devices[i].name != NULL; i++) {
339 		if (checkreg(vend, PUC_REG_VEND) &&
340 		    checkreg(prod, PUC_REG_PROD) &&
341 		    checkreg(svend, PUC_REG_SVEND) &&
342 		    checkreg(sprod, PUC_REG_SPROD))
343 			return (&puc_devices[i]);
344 	}
345 
346 #undef checkreg
347 
348 	return (NULL);
349 }
350 
351 static int
352 puc_find_free_unit(char *name)
353 {
354 	devclass_t dc;
355 	int start;
356 	int unit;
357 
358 	unit = 0;
359 	start = 0;
360 	while (resource_int_value(name, unit, "port", &start) == 0 &&
361 	    start > 0)
362 		unit++;
363 	dc = devclass_find(name);
364 	if (dc == NULL)
365 		return (-1);
366 	while (devclass_get_device(dc, unit))
367 		unit++;
368 #ifdef PUC_DEBUG
369 	printf("puc: Using %s%d\n", name, unit);
370 #endif
371 	return (unit);
372 }
373 
374 #ifdef PUC_DEBUG
375 static void
376 puc_print_resource_list(struct resource_list *rl)
377 {
378 #if 0
379 	struct resource_list_entry *rle;
380 
381 	printf("print_resource_list: rl %p\n", rl);
382 	SLIST_FOREACH(rle, rl, link)
383 		printf("  type %x, rid %x start %x end %x count %x\n",
384 		    rle->type, rle->rid, rle->start, rle->end, rle->count);
385 	printf("print_resource_list: end.\n");
386 #endif
387 }
388 #endif
389 
390 struct resource *
391 puc_alloc_resource(device_t dev, device_t child, int type, int *rid,
392     u_long start, u_long end, u_long count, u_int flags)
393 {
394 	struct puc_device *pdev;
395 	struct resource *retval;
396 	struct resource_list *rl;
397 	struct resource_list_entry *rle;
398 
399 	pdev = device_get_ivars(child);
400 	rl = &pdev->resources;
401 
402 #ifdef PUC_DEBUG
403 	printf("puc_alloc_resource: pdev %p, looking for t %x, r %x\n",
404 	    pdev, type, *rid);
405 	puc_print_resource_list(rl);
406 #endif
407 	retval = NULL;
408 	rle = resource_list_find(rl, type, *rid);
409 	if (rle) {
410 		start = rle->start;
411 		end = rle->end;
412 		count = rle->count;
413 #ifdef PUC_DEBUG
414 		printf("found rle, %lx, %lx, %lx\n", start, end, count);
415 #endif
416 		retval = rle->res;
417 	} else
418 		printf("oops rle is gone\n");
419 
420 	return (retval);
421 }
422 
423 int
424 puc_release_resource(device_t dev, device_t child, int type, int rid,
425     struct resource *res)
426 {
427 	return (0);
428 }
429 
430 int
431 puc_get_resource(device_t dev, device_t child, int type, int rid,
432     u_long *startp, u_long *countp)
433 {
434 	struct puc_device *pdev;
435 	struct resource_list *rl;
436 	struct resource_list_entry *rle;
437 
438 	pdev = device_get_ivars(child);
439 	rl = &pdev->resources;
440 
441 #ifdef PUC_DEBUG
442 	printf("puc_get_resource: pdev %p, looking for t %x, r %x\n", pdev,
443 	    type, rid);
444 	puc_print_resource_list(rl);
445 #endif
446 	rle = resource_list_find(rl, type, rid);
447 	if (rle) {
448 #ifdef PUC_DEBUG
449 		printf("found rle %p,", rle);
450 #endif
451 		if (startp != NULL)
452 			*startp = rle->start;
453 		if (countp != NULL)
454 			*countp = rle->count;
455 #ifdef PUC_DEBUG
456 		printf(" %lx, %lx\n", rle->start, rle->count);
457 #endif
458 		return (0);
459 	} else
460 		printf("oops rle is gone\n");
461 	return (ENXIO);
462 }
463 
464 int
465 puc_setup_intr(device_t dev, device_t child, struct resource *r, int flags,
466 	       void (*ihand)(void *), void *arg, void **cookiep)
467 {
468 	int i;
469 	struct puc_softc *sc;
470 
471 	sc = (struct puc_softc *)device_get_softc(dev);
472 	if ((flags & INTR_FAST) != sc->fastintr)
473 		return (ENXIO);
474 	for (i = 0; PUC_PORT_VALID(sc->sc_desc, i); i++) {
475 		if (sc->sc_ports[i].dev == child) {
476 			if (sc->sc_ports[i].ihand != 0)
477 				return (ENXIO);
478 			sc->sc_ports[i].ihand = ihand;
479 			sc->sc_ports[i].ihandarg = arg;
480 			*cookiep = arg;
481 			return (0);
482 		}
483 	}
484 	return (ENXIO);
485 }
486 
487 int
488 puc_teardown_intr(device_t dev, device_t child, struct resource *r,
489 		  void *cookie)
490 {
491 	int i;
492 	struct puc_softc *sc;
493 
494 	sc = (struct puc_softc *)device_get_softc(dev);
495 	for (i = 0; PUC_PORT_VALID(sc->sc_desc, i); i++) {
496 		if (sc->sc_ports[i].dev == child) {
497 			sc->sc_ports[i].ihand = NULL;
498 			sc->sc_ports[i].ihandarg = NULL;
499 			return (0);
500 		}
501 	}
502 	return (ENXIO);
503 }
504 
505 int
506 puc_read_ivar(device_t dev, device_t child, int index, uintptr_t *result)
507 {
508 	struct puc_device *pdev;
509 
510 	pdev = device_get_ivars(child);
511 	if (pdev == NULL)
512 		return (ENOENT);
513 
514 	switch(index) {
515 	case PUC_IVAR_FREQ:
516 		*result = pdev->serialfreq;
517 		break;
518 	default:
519 		return (ENOENT);
520 	}
521 	return (0);
522 }
523