1/* $NetBSD: pcmcom.c,v 1.38 2009/11/12 22:46:47 dyoung 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.38 2009/11/12 22:46:47 dyoung 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 70struct pcmcom_softc { 71 device_t 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 device_t sc_slaves[NSLAVES]; /* slave info */ 79 int sc_nslaves; /* slave count */ 80 81 int sc_state; 82#define PCMCOM_ATTACHED 3 83}; 84 85struct 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 91int pcmcom_match(device_t, cfdata_t, void *); 92int pcmcom_validate_config(struct pcmcia_config_entry *); 93void pcmcom_attach(device_t, device_t, void *); 94int pcmcom_detach(device_t, int); 95void pcmcom_childdet(device_t, device_t); 96 97CFATTACH_DECL(pcmcom, sizeof(struct pcmcom_softc), 98 pcmcom_match, pcmcom_attach, pcmcom_detach, NULL); 99 100const 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}; 108const size_t pcmcom_nproducts = __arraycount(pcmcom_products); 109 110int pcmcom_print(void *, const char *); 111 112int pcmcom_enable(struct pcmcom_softc *); 113void pcmcom_disable(struct pcmcom_softc *); 114 115int pcmcom_intr(void *); 116 117int 118pcmcom_match(device_t parent, cfdata_t cf, void *aux) 119{ 120 struct pcmcia_attach_args *pa = aux; 121 122 if (pcmcia_product_lookup(pa, pcmcom_products, pcmcom_nproducts, 123 sizeof(pcmcom_products[0]), NULL)) 124 return (2); /* beat com_pcmcia */ 125 return (0); 126} 127 128int 129pcmcom_validate_config(struct pcmcia_config_entry *cfe) 130{ 131 if (cfe->iftype != PCMCIA_IFTYPE_IO || 132 cfe->num_iospace < 1 || cfe->num_iospace > NSLAVES) 133 return (EINVAL); 134 return (0); 135} 136 137void 138pcmcom_attach(device_t parent, device_t self, void *aux) 139{ 140 struct pcmcom_softc *sc = device_private(self); 141 struct pcmcia_attach_args *pa = aux; 142 struct pcmcia_config_entry *cfe; 143 int slave; 144 int error; 145 int locs[PCMCOMCF_NLOCS]; 146 147 sc->sc_dev = self; 148 149 sc->sc_pf = pa->pf; 150 151 error = pcmcia_function_configure(pa->pf, pcmcom_validate_config); 152 if (error) { 153 aprint_error_dev(self, "configure failed, error=%d\n", error); 154 return; 155 } 156 157 cfe = pa->pf->cfe; 158 sc->sc_nslaves = cfe->num_iospace; 159 160 error = pcmcom_enable(sc); 161 if (error) 162 goto fail; 163 164 /* Attach the children. */ 165 for (slave = 0; slave < sc->sc_nslaves; slave++) { 166 struct pcmcom_attach_args pca; 167 168 printf("%s: slave %d\n", device_xname(self), slave); 169 170 pca.pca_iot = cfe->iospace[slave].handle.iot; 171 pca.pca_ioh = cfe->iospace[slave].handle.ioh; 172 pca.pca_slave = slave; 173 174 locs[PCMCOMCF_SLAVE] = slave; 175 176 sc->sc_slaves[slave] = config_found_sm_loc(sc->sc_dev, 177 "pcmcom", locs, 178 &pca, pcmcom_print, config_stdsubmatch); 179 } 180 181 pcmcom_disable(sc); 182 sc->sc_state = PCMCOM_ATTACHED; 183 return; 184 185fail: 186 pcmcia_function_unconfigure(pa->pf); 187} 188 189void 190pcmcom_childdet(device_t self, device_t child) 191{ 192 struct pcmcom_softc *sc = device_private(self); 193 int slave; 194 195 for (slave = sc->sc_nslaves - 1; slave >= 0; slave--) { 196 if (sc->sc_slaves[slave] == child) 197 sc->sc_slaves[slave] = NULL; 198 } 199} 200 201int 202pcmcom_detach(device_t self, int flags) 203{ 204 struct pcmcom_softc *sc = device_private(self); 205 int slave, error; 206 207 if (sc->sc_state != PCMCOM_ATTACHED) 208 return (0); 209 210 for (slave = sc->sc_nslaves - 1; slave >= 0; slave--) { 211 if (sc->sc_slaves[slave]) { 212 /* Detach the child. */ 213 error = config_detach(sc->sc_slaves[slave], flags); 214 if (error) 215 return (error); 216 } 217 } 218 219 pcmcia_function_unconfigure(sc->sc_pf); 220 221 return (0); 222} 223 224int 225pcmcom_print(void *aux, const char *pnp) 226{ 227 struct pcmcom_attach_args *pca = aux; 228 229 /* only com's can attach to pcmcom's; easy... */ 230 if (pnp) 231 aprint_normal("com at %s", pnp); 232 233 aprint_normal(" slave %d", pca->pca_slave); 234 235 return (UNCONF); 236} 237 238int 239pcmcom_intr(void *arg) 240{ 241#if NCOM > 0 242 struct pcmcom_softc *sc = arg; 243 int i, rval = 0; 244 245 if (sc->sc_enabled_count == 0) 246 return (0); 247 248 for (i = 0; i < sc->sc_nslaves; i++) { 249 if (sc->sc_slaves[i]) 250 rval |= comintr(sc->sc_slaves[i]); 251 } 252 253 return (rval); 254#else 255 return (0); 256#endif /* NCOM > 0 */ 257} 258 259int 260pcmcom_enable(struct pcmcom_softc *sc) 261{ 262 int error; 263 264 if (sc->sc_enabled_count++ != 0) 265 return (0); 266 267 /* Establish the interrupt. */ 268 sc->sc_ih = pcmcia_intr_establish(sc->sc_pf, IPL_SERIAL, 269 pcmcom_intr, sc); 270 if (!sc->sc_ih) 271 return (EIO); 272 273 error = pcmcia_function_enable(sc->sc_pf); 274 if (error) { 275 pcmcia_intr_disestablish(sc->sc_pf, sc->sc_ih); 276 sc->sc_ih = 0; 277 } 278 279 return (error); 280} 281 282void 283pcmcom_disable(struct pcmcom_softc *sc) 284{ 285 286 if (--sc->sc_enabled_count != 0) 287 return; 288 289 pcmcia_function_disable(sc->sc_pf); 290 pcmcia_intr_disestablish(sc->sc_pf, sc->sc_ih); 291 sc->sc_ih = 0; 292} 293 294/****** Here begins the com attachment code. ******/ 295 296#if NCOM_PCMCOM > 0 297int com_pcmcom_match(device_t, cfdata_t , void *); 298void com_pcmcom_attach(device_t, device_t, void *); 299 300/* No pcmcom-specific goo in the softc; it's all in the parent. */ 301CFATTACH_DECL(com_pcmcom, sizeof(struct com_softc), 302 com_pcmcom_match, com_pcmcom_attach, com_detach, NULL); 303 304int com_pcmcom_enable(struct com_softc *); 305void com_pcmcom_disable(struct com_softc *); 306 307int 308com_pcmcom_match(device_t parent, cfdata_t cf, void *aux) 309{ 310 311 /* Device is always present. */ 312 return (1); 313} 314 315void 316com_pcmcom_attach(device_t parent, device_t self, void *aux) 317{ 318 struct com_softc *sc = device_private(self); 319 struct pcmcom_attach_args *pca = aux; 320 321 sc->sc_dev = self; 322 COM_INIT_REGS(sc->sc_regs, pca->pca_iot, pca->pca_ioh, -1); 323 sc->enabled = 1; 324 325 sc->sc_frequency = COM_FREQ; 326 327 sc->enable = com_pcmcom_enable; 328 sc->disable = com_pcmcom_disable; 329 330 com_attach_subr(sc); 331 332 sc->enabled = 0; 333} 334 335int 336com_pcmcom_enable(struct com_softc *sc) 337{ 338 return pcmcom_enable(device_private(device_parent(sc->sc_dev))); 339} 340 341void 342com_pcmcom_disable(struct com_softc *sc) 343{ 344 345 pcmcom_disable(device_private(device_parent(sc->sc_dev))); 346} 347#endif /* NCOM_PCMCOM > 0 */ 348