1 /* $OpenBSD: puc.c,v 1.8 2003/02/28 15:14:08 mickey Exp $ */
2 /* $NetBSD: puc.c,v 1.3 1999/02/06 06:29:54 cgd Exp $ */
3
4 /*
5 * Copyright (c) 1996, 1998, 1999
6 * Christopher G. Demetriou. 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, this list of conditions and the following 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 * 3. All advertising materials mentioning features or use of this software
17 * must display the following acknowledgement:
18 * This product includes software developed by Christopher G. Demetriou
19 * for the NetBSD Project.
20 * 4. The name of the author may not be used to endorse or promote products
21 * derived from this software without specific prior written permission
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
24 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
25 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
26 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
27 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
28 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
32 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 */
34
35 /*
36 * PCI "universal" communication card device driver, glues com, lpt,
37 * and similar ports to PCI via bridge chip often much larger than
38 * the devices being glued.
39 *
40 * Author: Christopher G. Demetriou, May 14, 1998 (derived from NetBSD
41 * sys/dev/pci/pciide.c, revision 1.6).
42 *
43 * These devices could be (and some times are) described as
44 * communications/{serial,parallel}, etc. devices with known
45 * programming interfaces, but those programming interfaces (in
46 * particular the BAR assignments for devices, etc.) in fact are not
47 * particularly well defined.
48 *
49 * After I/we have seen more of these devices, it may be possible
50 * to generalize some of these bits. In particular, devices which
51 * describe themselves as communications/serial/16[45]50, and
52 * communications/parallel/??? might be attached via direct
53 * 'com' and 'lpt' attachments to pci.
54 */
55
56 #include <sys/param.h>
57 #include <sys/systm.h>
58 #include <sys/device.h>
59
60 #include <dev/pci/pcireg.h>
61 #include <dev/pci/pcivar.h>
62 #include <dev/pci/pucvar.h>
63
64 struct puc_softc {
65 struct device sc_dev;
66
67 /* static configuration data */
68 const struct puc_device_description *sc_desc;
69
70 /* card-global dynamic data */
71 void *sc_ih;
72 struct {
73 int mapped;
74 bus_addr_t a;
75 bus_size_t s;
76 bus_space_tag_t t;
77 bus_space_handle_t h;
78 } sc_bar_mappings[6]; /* XXX constant */
79
80 /* per-port dynamic data */
81 struct {
82 struct device *dev;
83
84 /* filled in by port attachments */
85 int (*ihand)(void *);
86 void *ihandarg;
87 } sc_ports[PUC_MAX_PORTS];
88 };
89
90 int puc_match(struct device *, void *, void *);
91 void puc_attach(struct device *, struct device *, void *);
92 int puc_print(void *, const char *);
93 int puc_submatch(struct device *, void *, void *);
94
95 struct cfattach puc_ca = {
96 sizeof(struct puc_softc), puc_match, puc_attach
97 };
98
99 struct cfdriver puc_cd = {
100 NULL, "puc", DV_DULL
101 };
102
103 static const struct puc_device_description *
104 puc_find_description(pcireg_t, pcireg_t, pcireg_t, pcireg_t);
105 static const char *
106 puc_port_type_name(int);
107
108 int
puc_match(parent,match,aux)109 puc_match(parent, match, aux)
110 struct device *parent;
111 void *match, *aux;
112 {
113 struct pci_attach_args *pa = aux;
114 const struct puc_device_description *desc;
115 pcireg_t bhlc, subsys;
116
117 bhlc = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_BHLC_REG);
118 if (PCI_HDRTYPE_TYPE(bhlc) != 0)
119 return (0);
120
121 subsys = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_SUBSYS_ID_REG);
122
123 desc = puc_find_description(PCI_VENDOR(pa->pa_id),
124 PCI_PRODUCT(pa->pa_id), PCI_VENDOR(subsys), PCI_PRODUCT(subsys));
125 if (desc != NULL)
126 return (10);
127
128 /*
129 * Match class/subclass, so we can tell people to compile kernel
130 * with options that cause this driver to spew.
131 */
132 if (PCI_CLASS(pa->pa_class) == PCI_CLASS_COMMUNICATIONS &&
133 PCI_SUBCLASS(pa->pa_class) == PCI_SUBCLASS_BRIDGE_PCI)
134 return (1);
135
136 return (0);
137 }
138
139 void
puc_attach(parent,self,aux)140 puc_attach(parent, self, aux)
141 struct device *parent, *self;
142 void *aux;
143 {
144 struct puc_softc *sc = (struct puc_softc *)self;
145 struct pci_attach_args *pa = aux;
146 struct puc_attach_args paa;
147 pci_intr_handle_t intrhandle;
148 pcireg_t subsys;
149 int i, barindex;
150
151 subsys = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_SUBSYS_ID_REG);
152 sc->sc_desc = puc_find_description(PCI_VENDOR(pa->pa_id),
153 PCI_PRODUCT(pa->pa_id), PCI_VENDOR(subsys), PCI_PRODUCT(subsys));
154 if (sc->sc_desc == NULL) {
155 /*
156 * This was a class/subclass match, so tell people to compile
157 * kernel with options that cause this driver to spew.
158 */
159 #ifdef PUC_PRINT_REGS
160 printf(":\n");
161 pci_conf_print(pa->pa_pc, pa->pa_tag, NULL);
162 #else
163 printf(": unknown PCI communications device\n");
164 printf("%s: compile kernel with PUC_PRINT_REGS and larger\n",
165 sc->sc_dev.dv_xname);
166 printf("%s: message buffer (via 'options MSGBUFSIZE=...'),\n",
167 sc->sc_dev.dv_xname);
168 printf("%s: and report the result with sendbug(1)\n",
169 sc->sc_dev.dv_xname);
170 #endif
171 return;
172 }
173
174 printf(": ");
175 for (i = 0; PUC_PORT_VALID(sc->sc_desc, i); i++)
176 printf("%s%s", i ? ", " : "",
177 puc_port_type_name(sc->sc_desc->ports[i].type));
178 printf("\n");
179
180 /*
181 * XXX This driver assumes that 'com' ports attached to it
182 * XXX can not be console. That isn't unreasonable, because PCI
183 * XXX devices are supposed to be dynamically mapped, and com
184 * XXX console ports want fixed addresses. When/if baseboard
185 * XXX 'com' ports are identified as PCI/communications/serial
186 * XXX devices and are known to be mapped at the standard
187 * XXX addresses, if they can be the system console then we have
188 * XXX to cope with doing the mapping right. Then this will get
189 * XXX really ugly. Of course, by then we might know the real
190 * XXX definition of PCI/communications/serial, and attach 'com'
191 * XXX directly on PCI.
192 */
193 for (i = 0; i < 6; i++) {
194 pcireg_t bar, type;
195
196 sc->sc_bar_mappings[i].mapped = 0;
197
198 bar = pci_conf_read(pa->pa_pc, pa->pa_tag,
199 PCI_MAPREG_START + 4 * i); /* XXX const */
200 if (bar == 0) /* BAR not implemented(?) */
201 continue;
202
203 type = (PCI_MAPREG_TYPE(bar) == PCI_MAPREG_TYPE_IO ?
204 PCI_MAPREG_TYPE_IO : PCI_MAPREG_MEM_TYPE(bar));
205 sc->sc_bar_mappings[i].mapped = (pci_mapreg_map(pa,
206 PCI_MAPREG_START + 4 * i, type, 0,
207 &sc->sc_bar_mappings[i].t, &sc->sc_bar_mappings[i].h,
208 &sc->sc_bar_mappings[i].a, &sc->sc_bar_mappings[i].s, 0)
209 == 0);
210 if (sc->sc_bar_mappings[i].mapped)
211 continue;
212
213 printf("%s: couldn't map BAR at offset 0x%lx\n",
214 sc->sc_dev.dv_xname, (long)(PCI_MAPREG_START + 4 * i));
215 }
216
217 /* Map interrupt. */
218 if (pci_intr_map(pa, &intrhandle)) {
219 printf("%s: couldn't map interrupt\n", sc->sc_dev.dv_xname);
220 return;
221 }
222 /*
223 * XXX the sub-devices establish the interrupts, for the
224 * XXX following reasons:
225 * XXX
226 * XXX * we can't really know what IPLs they'd want
227 * XXX
228 * XXX * the MD dispatching code can ("should") dispatch
229 * XXX chained interrupts better than we can.
230 * XXX
231 * XXX It would be nice if we could indicate to the MD interrupt
232 * XXX handling code that the interrupt line used by the device
233 * XXX was a PCI (level triggered) interrupt.
234 * XXX
235 * XXX It's not pretty, but hey, what is?
236 */
237
238 /* Configure each port. */
239 for (i = 0; PUC_PORT_VALID(sc->sc_desc, i); i++) {
240 bus_space_handle_t subregion_handle;
241
242 /* make sure the base address register is mapped */
243 barindex = PUC_PORT_BAR_INDEX(sc->sc_desc->ports[i].bar);
244 if (!sc->sc_bar_mappings[barindex].mapped) {
245 printf("%s: %s port uses unmapped BAR (0x%x)\n",
246 sc->sc_dev.dv_xname,
247 puc_port_type_name(sc->sc_desc->ports[i].type),
248 sc->sc_desc->ports[i].bar);
249 continue;
250 }
251
252 /* set up to configure the child device */
253 paa.port = i;
254 paa.type = sc->sc_desc->ports[i].type;
255 paa.flags = sc->sc_desc->ports[i].flags;
256 paa.pc = pa->pa_pc;
257 paa.intrhandle = intrhandle;
258 paa.a = sc->sc_bar_mappings[barindex].a;
259 paa.t = sc->sc_bar_mappings[barindex].t;
260
261 if (bus_space_subregion(sc->sc_bar_mappings[barindex].t,
262 sc->sc_bar_mappings[barindex].h,
263 sc->sc_desc->ports[i].offset,
264 sc->sc_bar_mappings[barindex].s -
265 sc->sc_desc->ports[i].offset,
266 &subregion_handle)) {
267 printf("%s: couldn't get subregion for port %d\n",
268 sc->sc_dev.dv_xname, i);
269 continue;
270 }
271 paa.h = subregion_handle;
272
273 #if 0
274 printf("%s: port %d: %s @ (index %d) 0x%x (0x%lx, 0x%lx)\n",
275 sc->sc_dev.dv_xname, paa.port,
276 puc_port_type_name(paa.type), barindex, (int)paa.a,
277 (long)paa.t, (long)paa.h);
278 #endif
279
280 /* and configure it */
281 sc->sc_ports[i].dev = config_found_sm(self, &paa, puc_print,
282 puc_submatch);
283 }
284 }
285
286 int
puc_print(aux,pnp)287 puc_print(aux, pnp)
288 void *aux;
289 const char *pnp;
290 {
291 struct puc_attach_args *paa = aux;
292
293 if (pnp)
294 printf("%s at %s", puc_port_type_name(paa->type), pnp);
295 printf(" port %d", paa->port);
296 return (UNCONF);
297 }
298
299 int
puc_submatch(parent,vcf,aux)300 puc_submatch(parent, vcf, aux)
301 struct device *parent;
302 void *vcf, *aux;
303 {
304 struct cfdata *cf = (struct cfdata *)vcf;
305 struct puc_attach_args *aa = aux;
306
307 if (cf->cf_loc[0] != -1 && cf->cf_loc[0] != aa->port)
308 return 0;
309 return ((*cf->cf_attach->ca_match)(parent, cf, aux));
310 }
311
312 static const struct puc_device_description *
puc_find_description(vend,prod,svend,sprod)313 puc_find_description(vend, prod, svend, sprod)
314 pcireg_t vend, prod, svend, sprod;
315 {
316 int i;
317
318 #define checkreg(val, index) \
319 (((val) & puc_devices[i].rmask[(index)]) == puc_devices[i].rval[(index)])
320 #define pucdevdone(idx) \
321 (puc_devices[idx].rval[0] == 0 && puc_devices[idx].rval[1] == 0 \
322 && puc_devices[idx].rval[2] == 0 && puc_devices[idx].rval[3] == 0)
323
324 for (i = 0; !pucdevdone(i); i++) {
325 if (checkreg(vend, PUC_REG_VEND) &&
326 checkreg(prod, PUC_REG_PROD) &&
327 checkreg(svend, PUC_REG_SVEND) &&
328 checkreg(sprod, PUC_REG_SPROD))
329 return (&puc_devices[i]);
330 }
331
332 #undef devdone
333 #undef checkreg
334
335 return (NULL);
336 }
337
338 static const char *
puc_port_type_name(type)339 puc_port_type_name(type)
340 int type;
341 {
342
343 switch (type) {
344 case PUC_PORT_TYPE_COM:
345 return "com";
346 case PUC_PORT_TYPE_LPT:
347 return "lpt";
348 default:
349 panic("puc_port_type_name %d", type);
350 }
351 }
352