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