pcmcom.c revision 1.2 1 /* $NetBSD: pcmcom.c,v 1.2 1998/10/14 18:05:45 thorpej Exp $ */
2
3 /*-
4 * Copyright (c) 1998 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by RedBack Networks, Inc.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by the NetBSD
21 * Foundation, Inc. and its contributors.
22 * 4. Neither the name of The NetBSD Foundation nor the names of its
23 * contributors may be used to endorse or promote products derived
24 * from this software without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36 * POSSIBILITY OF SUCH DAMAGE.
37 */
38
39 /*
40 * Device driver for multi-port PCMCIA serial cards, written by
41 * Jason R. Thorpe for RedBack Networks, Inc.
42 *
43 * Most of these cards are simply multiple UARTs sharing a single interrupt
44 * line, and rely on the fact that PCMCIA level-triggered interrupts can
45 * be shared. There are no special interrupt registers on them, as there
46 * are on most ISA multi-port serial cards.
47 *
48 * If there are other cards that have interrupt registers, they should not
49 * be glued into this driver. Rather, separate drivers should be written
50 * for those devices, as we have in the ISA multi-port serial card case.
51 */
52
53 #include <sys/param.h>
54 #include <sys/systm.h>
55 #include <sys/device.h>
56 #include <sys/termios.h>
57 #include <sys/malloc.h>
58
59 #include <machine/bus.h>
60 #include <machine/intr.h>
61
62 #include <dev/ic/comreg.h>
63 #include <dev/ic/comvar.h>
64
65 #include <dev/pcmcia/pcmciavar.h>
66 #include <dev/pcmcia/pcmciareg.h>
67 #include <dev/pcmcia/pcmciadevs.h>
68
69 #include "com.h"
70 #include "pcmcom.h"
71
72 #include "locators.h"
73
74 struct pcmcom_slave_info {
75 struct pcmcia_io_handle psi_pcioh; /* PCMCIA i/o space info */
76 int psi_io_window; /* our i/o window */
77 struct device *psi_child; /* child's device */
78 };
79
80 struct pcmcom_softc {
81 struct device sc_dev; /* generic device glue */
82
83 struct pcmcia_function *sc_pf; /* our PCMCIA function */
84 void *sc_ih; /* interrupt handle */
85 int sc_enabled_count; /* enabled count */
86
87 struct pcmcom_slave_info *sc_slaves; /* slave info */
88 int sc_nslaves; /* slave count */
89 };
90
91 struct pcmcom_attach_args {
92 bus_space_tag_t pca_iot; /* I/O tag */
93 bus_space_handle_t pca_ioh; /* I/O handle */
94 int pca_slave; /* slave # */
95 };
96
97 int pcmcom_match __P((struct device *, struct cfdata *, void *));
98 void pcmcom_attach __P((struct device *, struct device *, void *));
99
100 struct cfattach pcmcom_ca = {
101 sizeof(struct pcmcom_softc), pcmcom_match, pcmcom_attach
102 };
103
104 struct pcmcom_product {
105 u_int32_t pp_vendor; /* PCMCIA vendor ID */
106 u_int32_t pp_product; /* PCMCIA product ID */
107 int pp_expfunc; /* expected function number */
108 int pp_nslaves; /* number of slaves */
109 const char *pp_name; /* device name */
110 } pcmcom_products[] = {
111 { PCMCIA_VENDOR_SOCKET, PCMCIA_PRODUCT_SOCKET_DUAL_RS232,
112 0, 2,
113 PCMCIA_STR_SOCKET_DUAL_RS232 },
114
115 { 0, 0,
116 0, 0,
117 NULL },
118 };
119
120 int pcmcom_print __P((void *, const char *));
121 int pcmcom_submatch __P((struct device *, struct cfdata *, void *));
122
123 int pcmcom_check_cfe __P((struct pcmcom_softc *,
124 struct pcmcia_config_entry *));
125 void pcmcom_attach_slave __P((struct pcmcom_softc *, int));
126
127 int pcmcom_enable __P((struct pcmcom_softc *));
128 void pcmcom_disable __P((struct pcmcom_softc *));
129
130 int pcmcom_intr __P((void *));
131
132 struct pcmcom_product *pcmcom_lookup __P((struct pcmcia_attach_args *));
133
134 struct pcmcom_product *
135 pcmcom_lookup(pa)
136 struct pcmcia_attach_args *pa;
137 {
138 struct pcmcom_product *pp;
139
140 for (pp = pcmcom_products; pp->pp_name != NULL; pp++)
141 if (pa->manufacturer == pp->pp_vendor &&
142 pa->product == pp->pp_product &&
143 pa->pf->number == pp->pp_expfunc)
144 return (pp);
145
146 return (NULL);
147 }
148
149 int
150 pcmcom_match(parent, cf, aux)
151 struct device *parent;
152 struct cfdata *cf;
153 void *aux;
154 {
155 struct pcmcia_attach_args *pa = aux;
156
157 if (pcmcom_lookup(pa) != NULL)
158 return (10); /* beat com_pcmcia */
159 return (0);
160 }
161
162 void
163 pcmcom_attach(parent, self, aux)
164 struct device *parent, *self;
165 void *aux;
166 {
167 struct pcmcom_softc *sc = (struct pcmcom_softc *)self;
168 struct pcmcia_attach_args *pa = aux;
169 struct pcmcia_config_entry *cfe;
170 struct pcmcom_product *pp;
171 size_t size;
172 int i;
173
174 sc->sc_pf = pa->pf;
175
176 pp = pcmcom_lookup(pa);
177 if (pp == NULL) {
178 printf("\n");
179 panic("pcmcom_attach: impossible");
180 }
181
182 printf(": %s\n", pp->pp_name);
183
184 /* Allocate the slave info. */
185 sc->sc_nslaves = pp->pp_nslaves;
186 size = sizeof(struct pcmcom_slave_info) * sc->sc_nslaves;
187 sc->sc_slaves = malloc(size, M_DEVBUF, M_NOWAIT);
188 if (sc->sc_slaves == NULL) {
189 printf("%s: unable to allocate slave info\n",
190 sc->sc_dev.dv_xname);
191 return;
192 }
193 memset(sc->sc_slaves, 0, size);
194
195 /*
196 * The address decoders on these cards are stupid. They decode
197 * (usually) 10 bits of address, so you need to allocate the
198 * regions they request in order for the card to differentiate
199 * between serial ports.
200 */
201 for (cfe = pa->pf->cfe_head.sqh_first; cfe != NULL;
202 cfe = cfe->cfe_list.sqe_next) {
203 if (pcmcom_check_cfe(sc, cfe)) {
204 /* Found one! */
205 break;
206 }
207 }
208 if (cfe == NULL) {
209 printf("%s: unable to find a suitable config table entry\n",
210 sc->sc_dev.dv_xname);
211 return;
212 }
213
214 /* Enable the card. */
215 pcmcia_function_init(pa->pf, cfe);
216 if (pcmcia_function_enable(sc->sc_pf)) {
217 printf("%s: function enable failed\n", sc->sc_dev.dv_xname);
218 return;
219 }
220
221 sc->sc_enabled_count = 1;
222
223 /* Attach the children. */
224 for (i = 0; i < sc->sc_nslaves; i++)
225 pcmcom_attach_slave(sc, i);
226
227 sc->sc_enabled_count = 0;
228 pcmcia_function_disable(sc->sc_pf);
229 }
230
231 int
232 pcmcom_check_cfe(sc, cfe)
233 struct pcmcom_softc *sc;
234 struct pcmcia_config_entry *cfe;
235 {
236 struct pcmcom_slave_info *psi;
237 int slave;
238
239 /* We need to have the same number of I/O spaces as we do slaves. */
240 if (cfe->num_iospace != sc->sc_nslaves)
241 return (0);
242
243 for (slave = 0; slave < sc->sc_nslaves; slave++) {
244 psi = &sc->sc_slaves[slave];
245 if (pcmcia_io_alloc(sc->sc_pf,
246 cfe->iospace[slave].start,
247 cfe->iospace[slave].length,
248 cfe->iospace[slave].length,
249 &psi->psi_pcioh))
250 goto release;
251 }
252
253 /* If we got here, we were able to allocate space for all slaves! */
254 return (1);
255
256 release:
257 /* Release the i/o spaces we've allocated. */
258 for (slave--; slave >= 0; slave--) {
259 psi = &sc->sc_slaves[slave];
260 pcmcia_io_free(sc->sc_pf, &psi->psi_pcioh);
261 }
262 return (0);
263 }
264
265 void
266 pcmcom_attach_slave(sc, slave)
267 struct pcmcom_softc *sc;
268 int slave;
269 {
270 struct pcmcom_slave_info *psi = &sc->sc_slaves[slave];
271 struct pcmcom_attach_args pca;
272
273 printf("%s: slave %d", sc->sc_dev.dv_xname, slave);
274
275 if (pcmcia_io_map(sc->sc_pf, PCMCIA_WIDTH_IO8, 0, psi->psi_pcioh.size,
276 &psi->psi_pcioh, &psi->psi_io_window)) {
277 printf(": can't map i/o space\n");
278 return;
279 }
280
281 printf("\n");
282
283 pca.pca_iot = psi->psi_pcioh.iot;
284 pca.pca_ioh = psi->psi_pcioh.ioh;
285 pca.pca_slave = slave;
286
287 psi->psi_child = config_found_sm(&sc->sc_dev, &pca, pcmcom_print,
288 pcmcom_submatch);
289 }
290
291 int
292 pcmcom_print(aux, pnp)
293 void *aux;
294 const char *pnp;
295 {
296 struct pcmcom_attach_args *pca = aux;
297
298 /* only com's can attach to pcmcom's; easy... */
299 if (pnp)
300 printf("com at %s", pnp);
301
302 printf(" slave %d", pca->pca_slave);
303
304 return (UNCONF);
305 }
306
307 int
308 pcmcom_submatch(parent, cf, aux)
309 struct device *parent;
310 struct cfdata *cf;
311 void *aux;
312 {
313 struct pcmcom_attach_args *pca = aux;
314
315 if (cf->cf_loc[PCMCOMCF_SLAVE] != pca->pca_slave &&
316 cf->cf_loc[PCMCOMCF_SLAVE] != PCMCOMCF_SLAVE_DEFAULT)
317 return (0);
318
319 return ((*cf->cf_attach->ca_match)(parent, cf, aux));
320 }
321
322 int
323 pcmcom_intr(arg)
324 void *arg;
325 {
326 #if NCOM > 0
327 struct pcmcom_softc *sc = arg;
328 int i, rval = 0;
329
330 if (sc->sc_enabled_count == 0)
331 return (0);
332
333 for (i = 0; i < sc->sc_nslaves; i++) {
334 if (sc->sc_slaves[i].psi_child != NULL)
335 rval |= comintr(sc->sc_slaves[i].psi_child);
336 }
337
338 return (rval);
339 #else
340 return (0);
341 #endif /* NCOM > 0 */
342 }
343
344 int
345 pcmcom_enable(sc)
346 struct pcmcom_softc *sc;
347 {
348
349 sc->sc_enabled_count++;
350 if (sc->sc_enabled_count > 1)
351 return (0);
352
353 /* Establish the interrupt. */
354 sc->sc_ih = pcmcia_intr_establish(sc->sc_pf, IPL_SERIAL,
355 pcmcom_intr, sc);
356 if (sc->sc_ih == NULL) {
357 printf("%s: couldn't establish interrupt\n",
358 sc->sc_dev.dv_xname);
359 return (1);
360 }
361
362 return (pcmcia_function_enable(sc->sc_pf));
363 }
364
365 void
366 pcmcom_disable(sc)
367 struct pcmcom_softc *sc;
368 {
369
370 sc->sc_enabled_count--;
371 if (sc->sc_enabled_count != 0)
372 return;
373
374 pcmcia_function_disable(sc->sc_pf);
375 pcmcia_intr_disestablish(sc->sc_pf, sc->sc_ih);
376 }
377
378 /****** Here begins the com attachment code. ******/
379
380 #if NCOM_PCMCOM > 0
381 int com_pcmcom_match __P((struct device *, struct cfdata *, void *));
382 void com_pcmcom_attach __P((struct device *, struct device *, void *));
383
384 /* No pcmcom-specific goo in the softc; it's all in the parent. */
385 struct cfattach com_pcmcom_ca = {
386 sizeof(struct com_softc), com_pcmcom_match, com_pcmcom_attach
387 };
388
389 int com_pcmcom_enable __P((struct com_softc *));
390 void com_pcmcom_disable __P((struct com_softc *));
391
392 int
393 com_pcmcom_match(parent, cf, aux)
394 struct device *parent;
395 struct cfdata *cf;
396 void *aux;
397 {
398
399 /* Device is always present. */
400 return (1);
401 }
402
403 void
404 com_pcmcom_attach(parent, self, aux)
405 struct device *parent, *self;
406 void *aux;
407 {
408 struct com_softc *sc = (struct com_softc *)self;
409 struct pcmcom_attach_args *pca = aux;
410
411 sc->sc_iot = pca->pca_iot;
412 sc->sc_ioh = pca->pca_ioh;
413
414 sc->enabled = 1;
415
416 sc->sc_iobase = -1;
417 sc->sc_frequency = COM_FREQ;
418
419 sc->enable = com_pcmcom_enable;
420 sc->disable = com_pcmcom_disable;
421
422 com_attach_subr(sc);
423
424 sc->enabled = 0;
425 }
426
427 int
428 com_pcmcom_enable(sc)
429 struct com_softc *sc;
430 {
431
432 return (pcmcom_enable((struct pcmcom_softc *)sc->sc_dev.dv_parent));
433 }
434
435 void
436 com_pcmcom_disable(sc)
437 struct com_softc *sc;
438 {
439
440 pcmcom_disable((struct pcmcom_softc *)sc->sc_dev.dv_parent);
441 }
442 #endif /* NCOM_PCMCOM > 0 */
443