1 /* $NetBSD: pcmcom.c,v 1.34 2008/04/28 20:23:56 martin Exp $ */ 2 3 /*- 4 * Copyright (c) 1998, 2000, 2004 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 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 /* 33 * Device driver for multi-port PCMCIA serial cards, written by 34 * Jason R. Thorpe for RedBack Networks, Inc. 35 * 36 * Most of these cards are simply multiple UARTs sharing a single interrupt 37 * line, and rely on the fact that PCMCIA level-triggered interrupts can 38 * be shared. There are no special interrupt registers on them, as there 39 * are on most ISA multi-port serial cards. 40 * 41 * If there are other cards that have interrupt registers, they should not 42 * be glued into this driver. Rather, separate drivers should be written 43 * for those devices, as we have in the ISA multi-port serial card case. 44 */ 45 46 #include <sys/cdefs.h> 47 __KERNEL_RCSID(0, "$NetBSD: pcmcom.c,v 1.34 2008/04/28 20:23:56 martin Exp $"); 48 49 #include <sys/param.h> 50 #include <sys/systm.h> 51 #include <sys/device.h> 52 #include <sys/termios.h> 53 #include <sys/malloc.h> 54 55 #include <sys/bus.h> 56 #include <sys/intr.h> 57 58 #include <dev/ic/comreg.h> 59 #include <dev/ic/comvar.h> 60 61 #include <dev/pcmcia/pcmciavar.h> 62 #include <dev/pcmcia/pcmciareg.h> 63 #include <dev/pcmcia/pcmciadevs.h> 64 65 #include "com.h" 66 #include "pcmcom.h" 67 68 #include "locators.h" 69 70 struct pcmcom_softc { 71 struct device sc_dev; /* generic device glue */ 72 73 struct pcmcia_function *sc_pf; /* our PCMCIA function */ 74 void *sc_ih; /* interrupt handle */ 75 int sc_enabled_count; /* enabled count */ 76 77 #define NSLAVES 8 78 struct device *sc_slaves[NSLAVES]; /* slave info */ 79 int sc_nslaves; /* slave count */ 80 81 int sc_state; 82 #define PCMCOM_ATTACHED 3 83 }; 84 85 struct pcmcom_attach_args { 86 bus_space_tag_t pca_iot; /* I/O tag */ 87 bus_space_handle_t pca_ioh; /* I/O handle */ 88 int pca_slave; /* slave # */ 89 }; 90 91 int pcmcom_match(struct device *, struct cfdata *, void *); 92 int pcmcom_validate_config(struct pcmcia_config_entry *); 93 void pcmcom_attach(struct device *, struct device *, void *); 94 int pcmcom_detach(struct device *, int); 95 int pcmcom_activate(struct device *, enum devact); 96 97 CFATTACH_DECL(pcmcom, sizeof(struct pcmcom_softc), 98 pcmcom_match, pcmcom_attach, pcmcom_detach, pcmcom_activate); 99 100 const struct pcmcia_product pcmcom_products[] = { 101 { PCMCIA_VENDOR_SOCKET, PCMCIA_PRODUCT_SOCKET_DUAL_RS232, 102 PCMCIA_CIS_INVALID }, 103 #if 0 /* does not work */ 104 { PCMCIA_VENDOR_SOCKET, PCMCIA_PRODUCT_SOCKET_DUAL_RS232_A, 105 PCMCIA_CIS_INVALID }, 106 #endif 107 }; 108 const size_t pcmcom_nproducts = 109 sizeof(pcmcom_products) / sizeof(pcmcom_products[0]); 110 111 int pcmcom_print(void *, const char *); 112 113 int pcmcom_enable(struct pcmcom_softc *); 114 void pcmcom_disable(struct pcmcom_softc *); 115 116 int pcmcom_intr(void *); 117 118 int 119 pcmcom_match(struct device *parent, struct cfdata *cf, 120 void *aux) 121 { 122 struct pcmcia_attach_args *pa = aux; 123 124 if (pcmcia_product_lookup(pa, pcmcom_products, pcmcom_nproducts, 125 sizeof(pcmcom_products[0]), NULL)) 126 return (2); /* beat com_pcmcia */ 127 return (0); 128 } 129 130 int 131 pcmcom_validate_config(cfe) 132 struct pcmcia_config_entry *cfe; 133 { 134 if (cfe->iftype != PCMCIA_IFTYPE_IO || 135 cfe->num_iospace < 1 || cfe->num_iospace > NSLAVES) 136 return (EINVAL); 137 return (0); 138 } 139 140 void 141 pcmcom_attach(struct device *parent, struct device *self, void *aux) 142 { 143 struct pcmcom_softc *sc = (void *)self; 144 struct pcmcia_attach_args *pa = aux; 145 struct pcmcia_config_entry *cfe; 146 int slave; 147 int error; 148 int locs[PCMCOMCF_NLOCS]; 149 150 sc->sc_pf = pa->pf; 151 152 error = pcmcia_function_configure(pa->pf, pcmcom_validate_config); 153 if (error) { 154 aprint_error_dev(self, "configure failed, error=%d\n", 155 error); 156 return; 157 } 158 159 cfe = pa->pf->cfe; 160 sc->sc_nslaves = cfe->num_iospace; 161 162 error = pcmcom_enable(sc); 163 if (error) 164 goto fail; 165 166 /* Attach the children. */ 167 for (slave = 0; slave < sc->sc_nslaves; slave++) { 168 struct pcmcom_attach_args pca; 169 170 printf("%s: slave %d\n", device_xname(self), slave); 171 172 pca.pca_iot = cfe->iospace[slave].handle.iot; 173 pca.pca_ioh = cfe->iospace[slave].handle.ioh; 174 pca.pca_slave = slave; 175 176 locs[PCMCOMCF_SLAVE] = slave; 177 178 sc->sc_slaves[slave] = config_found_sm_loc(&sc->sc_dev, 179 "pcmcom", locs, 180 &pca, pcmcom_print, config_stdsubmatch); 181 } 182 183 pcmcom_disable(sc); 184 sc->sc_state = PCMCOM_ATTACHED; 185 return; 186 187 fail: 188 pcmcia_function_unconfigure(pa->pf); 189 } 190 191 int 192 pcmcom_detach(self, flags) 193 struct device *self; 194 int flags; 195 { 196 struct pcmcom_softc *sc = (void *)self; 197 int slave, error; 198 199 if (sc->sc_state != PCMCOM_ATTACHED) 200 return (0); 201 202 for (slave = sc->sc_nslaves - 1; slave >= 0; slave--) { 203 if (sc->sc_slaves[slave]) { 204 /* Detach the child. */ 205 error = config_detach(sc->sc_slaves[slave], flags); 206 if (error) 207 return (error); 208 sc->sc_slaves[slave] = 0; 209 } 210 } 211 212 pcmcia_function_unconfigure(sc->sc_pf); 213 214 return (0); 215 } 216 217 int 218 pcmcom_activate(self, act) 219 struct device *self; 220 enum devact act; 221 { 222 struct pcmcom_softc *sc = (void *)self; 223 int slave, error = 0, s; 224 225 s = splserial(); 226 switch (act) { 227 case DVACT_ACTIVATE: 228 error = EOPNOTSUPP; 229 break; 230 231 case DVACT_DEACTIVATE: 232 for (slave = sc->sc_nslaves - 1; slave >= 0; slave--) { 233 if (sc->sc_slaves[slave]) { 234 /* 235 * Deactivate the child. Doing so will cause 236 * our own enabled count to drop to 0, once all 237 * children are deactivated. 238 */ 239 error = config_deactivate(sc->sc_slaves[slave]); 240 if (error) 241 break; 242 } 243 } 244 break; 245 } 246 splx(s); 247 return (error); 248 } 249 250 int 251 pcmcom_print(aux, pnp) 252 void *aux; 253 const char *pnp; 254 { 255 struct pcmcom_attach_args *pca = aux; 256 257 /* only com's can attach to pcmcom's; easy... */ 258 if (pnp) 259 aprint_normal("com at %s", pnp); 260 261 aprint_normal(" slave %d", pca->pca_slave); 262 263 return (UNCONF); 264 } 265 266 int 267 pcmcom_intr(arg) 268 void *arg; 269 { 270 #if NCOM > 0 271 struct pcmcom_softc *sc = arg; 272 int i, rval = 0; 273 274 if (sc->sc_enabled_count == 0) 275 return (0); 276 277 for (i = 0; i < sc->sc_nslaves; i++) { 278 if (sc->sc_slaves[i]) 279 rval |= comintr(sc->sc_slaves[i]); 280 } 281 282 return (rval); 283 #else 284 return (0); 285 #endif /* NCOM > 0 */ 286 } 287 288 int 289 pcmcom_enable(sc) 290 struct pcmcom_softc *sc; 291 { 292 int error; 293 294 if (sc->sc_enabled_count++ != 0) 295 return (0); 296 297 /* Establish the interrupt. */ 298 sc->sc_ih = pcmcia_intr_establish(sc->sc_pf, IPL_SERIAL, 299 pcmcom_intr, sc); 300 if (!sc->sc_ih) 301 return (EIO); 302 303 error = pcmcia_function_enable(sc->sc_pf); 304 if (error) { 305 pcmcia_intr_disestablish(sc->sc_pf, sc->sc_ih); 306 sc->sc_ih = 0; 307 } 308 309 return (error); 310 } 311 312 void 313 pcmcom_disable(sc) 314 struct pcmcom_softc *sc; 315 { 316 317 if (--sc->sc_enabled_count != 0) 318 return; 319 320 pcmcia_function_disable(sc->sc_pf); 321 pcmcia_intr_disestablish(sc->sc_pf, sc->sc_ih); 322 sc->sc_ih = 0; 323 } 324 325 /****** Here begins the com attachment code. ******/ 326 327 #if NCOM_PCMCOM > 0 328 int com_pcmcom_match(device_t, cfdata_t , void *); 329 void com_pcmcom_attach(device_t, device_t, void *); 330 331 /* No pcmcom-specific goo in the softc; it's all in the parent. */ 332 CFATTACH_DECL(com_pcmcom, sizeof(struct com_softc), 333 com_pcmcom_match, com_pcmcom_attach, com_detach, com_activate); 334 335 int com_pcmcom_enable(struct com_softc *); 336 void com_pcmcom_disable(struct com_softc *); 337 338 int 339 com_pcmcom_match(device_t parent, cfdata_t cf, void *aux) 340 { 341 342 /* Device is always present. */ 343 return (1); 344 } 345 346 void 347 com_pcmcom_attach(device_t parent, device_t self, void *aux) 348 { 349 struct com_softc *sc = device_private(self); 350 struct pcmcom_attach_args *pca = aux; 351 352 sc->sc_dev = self; 353 COM_INIT_REGS(sc->sc_regs, pca->pca_iot, pca->pca_ioh, -1); 354 sc->enabled = 1; 355 356 sc->sc_frequency = COM_FREQ; 357 358 sc->enable = com_pcmcom_enable; 359 sc->disable = com_pcmcom_disable; 360 361 com_attach_subr(sc); 362 363 sc->enabled = 0; 364 } 365 366 int 367 com_pcmcom_enable(struct com_softc *sc) 368 { 369 370 return (pcmcom_enable(device_private(device_parent(sc->sc_dev)))); 371 } 372 373 void 374 com_pcmcom_disable(sc) 375 struct com_softc *sc; 376 { 377 378 pcmcom_disable(device_private(device_parent(sc->sc_dev))); 379 } 380 #endif /* NCOM_PCMCOM > 0 */ 381