| 1 | /* $NetBSD: if_athn_pci.c,v 1.12 2015/11/24 18:17:37 jakllsch Exp $ */ |
| 2 | /* $OpenBSD: if_athn_pci.c,v 1.11 2011/01/08 10:02:32 damien Exp $ */ |
| 3 | |
| 4 | /*- |
| 5 | * Copyright (c) 2009 Damien Bergamini <damien.bergamini@free.fr> |
| 6 | * |
| 7 | * Permission to use, copy, modify, and distribute this software for any |
| 8 | * purpose with or without fee is hereby granted, provided that the above |
| 9 | * copyright notice and this permission notice appear in all copies. |
| 10 | * |
| 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| 12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| 13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| 14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| 15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| 16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| 17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| 18 | */ |
| 19 | |
| 20 | /* |
| 21 | * PCI front-end for Atheros 802.11a/g/n chipsets. |
| 22 | */ |
| 23 | |
| 24 | #include <sys/cdefs.h> |
| 25 | __KERNEL_RCSID(0, "$NetBSD: if_athn_pci.c,v 1.12 2015/11/24 18:17:37 jakllsch Exp $" ); |
| 26 | |
| 27 | #include "opt_inet.h" |
| 28 | |
| 29 | #include <sys/param.h> |
| 30 | #include <sys/sockio.h> |
| 31 | #include <sys/mbuf.h> |
| 32 | #include <sys/kernel.h> |
| 33 | #include <sys/socket.h> |
| 34 | #include <sys/systm.h> |
| 35 | #include <sys/malloc.h> |
| 36 | #include <sys/callout.h> |
| 37 | #include <sys/device.h> |
| 38 | |
| 39 | #include <sys/bus.h> |
| 40 | #include <sys/intr.h> |
| 41 | |
| 42 | #include <net/if.h> |
| 43 | #include <net/if_ether.h> |
| 44 | #include <net/if_media.h> |
| 45 | |
| 46 | #include <net80211/ieee80211_var.h> |
| 47 | #include <net80211/ieee80211_amrr.h> |
| 48 | #include <net80211/ieee80211_radiotap.h> |
| 49 | |
| 50 | #include <dev/ic/athnreg.h> |
| 51 | #include <dev/ic/athnvar.h> |
| 52 | |
| 53 | #include <dev/pci/pcireg.h> |
| 54 | #include <dev/pci/pcivar.h> |
| 55 | #include <dev/pci/pcidevs.h> |
| 56 | |
| 57 | #define PCI_SUBSYSID_ATHEROS_COEX2WIRE 0x309b |
| 58 | #define PCI_SUBSYSID_ATHEROS_COEX3WIRE_SA 0x30aa |
| 59 | #define PCI_SUBSYSID_ATHEROS_COEX3WIRE_DA 0x30ab |
| 60 | |
| 61 | #define ATHN_PCI_MMBA PCI_BAR(0) /* memory mapped base */ |
| 62 | |
| 63 | struct athn_pci_softc { |
| 64 | struct athn_softc psc_sc; |
| 65 | |
| 66 | /* PCI specific goo. */ |
| 67 | pci_chipset_tag_t psc_pc; |
| 68 | pcitag_t psc_tag; |
| 69 | pci_intr_handle_t psc_pih; |
| 70 | void *psc_ih; |
| 71 | bus_space_tag_t psc_iot; |
| 72 | bus_space_handle_t psc_ioh; |
| 73 | bus_size_t psc_mapsz; |
| 74 | int psc_cap_off; |
| 75 | }; |
| 76 | |
| 77 | #define Static static |
| 78 | |
| 79 | Static int athn_pci_match(device_t, cfdata_t, void *); |
| 80 | Static void athn_pci_attach(device_t, device_t, void *); |
| 81 | Static int athn_pci_detach(device_t, int); |
| 82 | Static int athn_pci_activate(device_t, enum devact); |
| 83 | |
| 84 | CFATTACH_DECL_NEW(athn_pci, sizeof(struct athn_pci_softc), athn_pci_match, |
| 85 | athn_pci_attach, athn_pci_detach, athn_pci_activate); |
| 86 | |
| 87 | Static bool athn_pci_resume(device_t, const pmf_qual_t *); |
| 88 | Static bool athn_pci_suspend(device_t, const pmf_qual_t *); |
| 89 | Static uint32_t athn_pci_read(struct athn_softc *, uint32_t); |
| 90 | Static void athn_pci_write(struct athn_softc *, uint32_t, uint32_t); |
| 91 | Static void athn_pci_write_barrier(struct athn_softc *); |
| 92 | Static void athn_pci_disable_aspm(struct athn_softc *); |
| 93 | |
| 94 | Static int |
| 95 | athn_pci_match(device_t parent, cfdata_t match, void *aux) |
| 96 | { |
| 97 | static const struct { |
| 98 | pci_vendor_id_t apd_vendor; |
| 99 | pci_product_id_t apd_product; |
| 100 | } athn_pci_devices[] = { |
| 101 | { PCI_VENDOR_ATHEROS, PCI_PRODUCT_ATHEROS_AR5416 }, |
| 102 | { PCI_VENDOR_ATHEROS, PCI_PRODUCT_ATHEROS_AR5418 }, |
| 103 | { PCI_VENDOR_ATHEROS, PCI_PRODUCT_ATHEROS_AR9160 }, |
| 104 | { PCI_VENDOR_ATHEROS, PCI_PRODUCT_ATHEROS_AR9280 }, |
| 105 | { PCI_VENDOR_ATHEROS, PCI_PRODUCT_ATHEROS_AR9281 }, |
| 106 | { PCI_VENDOR_ATHEROS, PCI_PRODUCT_ATHEROS_AR9285 }, |
| 107 | { PCI_VENDOR_ATHEROS, PCI_PRODUCT_ATHEROS_AR2427 }, |
| 108 | { PCI_VENDOR_ATHEROS, PCI_PRODUCT_ATHEROS_AR9227 }, |
| 109 | { PCI_VENDOR_ATHEROS, PCI_PRODUCT_ATHEROS_AR9287 }, |
| 110 | { PCI_VENDOR_ATHEROS, PCI_PRODUCT_ATHEROS_AR9300 } |
| 111 | }; |
| 112 | struct pci_attach_args *pa = aux; |
| 113 | size_t i; |
| 114 | |
| 115 | for (i = 0; i < __arraycount(athn_pci_devices); i++) { |
| 116 | if (PCI_VENDOR(pa->pa_id) == athn_pci_devices[i].apd_vendor && |
| 117 | PCI_PRODUCT(pa->pa_id) == athn_pci_devices[i].apd_product) |
| 118 | /* |
| 119 | * Match better than 1, we prefer this driver |
| 120 | * over ath(4) |
| 121 | */ |
| 122 | return 10; |
| 123 | } |
| 124 | return 0; |
| 125 | } |
| 126 | |
| 127 | Static void |
| 128 | athn_pci_attach(device_t parent, device_t self, void *aux) |
| 129 | { |
| 130 | struct athn_pci_softc *psc = device_private(self); |
| 131 | struct athn_softc *sc = &psc->psc_sc; |
| 132 | struct ieee80211com *ic = &sc->sc_ic; |
| 133 | struct pci_attach_args *pa = aux; |
| 134 | const char *intrstr; |
| 135 | pcireg_t memtype, reg; |
| 136 | pci_product_id_t subsysid; |
| 137 | int error; |
| 138 | char intrbuf[PCI_INTRSTR_LEN]; |
| 139 | |
| 140 | sc->sc_dev = self; |
| 141 | sc->sc_dmat = pa->pa_dmat; |
| 142 | psc->psc_pc = pa->pa_pc; |
| 143 | psc->psc_tag = pa->pa_tag; |
| 144 | |
| 145 | sc->sc_ops.read = athn_pci_read; |
| 146 | sc->sc_ops.write = athn_pci_write; |
| 147 | sc->sc_ops.write_barrier = athn_pci_write_barrier; |
| 148 | |
| 149 | /* |
| 150 | * Get the offset of the PCI Express Capability Structure in PCI |
| 151 | * Configuration Space (Linux hardcodes it as 0x60.) |
| 152 | */ |
| 153 | error = pci_get_capability(pa->pa_pc, pa->pa_tag, PCI_CAP_PCIEXPRESS, |
| 154 | &psc->psc_cap_off, NULL); |
| 155 | if (error != 0) { /* Found. */ |
| 156 | sc->sc_disable_aspm = athn_pci_disable_aspm; |
| 157 | sc->sc_flags |= ATHN_FLAG_PCIE; |
| 158 | } |
| 159 | /* |
| 160 | * Noone knows why this shit is necessary but there are claims that |
| 161 | * not doing this may cause very frequent PCI FATAL interrupts from |
| 162 | * the card: http://bugzilla.kernel.org/show_bug.cgi?id=13483 |
| 163 | */ |
| 164 | reg = pci_conf_read(pa->pa_pc, pa->pa_tag, 0x40); |
| 165 | if (reg & 0xff00) |
| 166 | pci_conf_write(pa->pa_pc, pa->pa_tag, 0x40, reg & ~0xff00); |
| 167 | |
| 168 | /* Change latency timer; default value yields poor results. */ |
| 169 | reg = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_BHLC_REG); |
| 170 | reg &= ~(PCI_LATTIMER_MASK << PCI_LATTIMER_SHIFT); |
| 171 | reg |= 168 << PCI_LATTIMER_SHIFT; |
| 172 | pci_conf_write(pa->pa_pc, pa->pa_tag, PCI_BHLC_REG, reg); |
| 173 | |
| 174 | /* Determine if bluetooth is also supported (combo chip.) */ |
| 175 | reg = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_SUBSYS_ID_REG); |
| 176 | subsysid = PCI_PRODUCT(reg); |
| 177 | if (subsysid == PCI_SUBSYSID_ATHEROS_COEX3WIRE_SA || |
| 178 | subsysid == PCI_SUBSYSID_ATHEROS_COEX3WIRE_DA) |
| 179 | sc->sc_flags |= ATHN_FLAG_BTCOEX3WIRE; |
| 180 | else if (subsysid == PCI_SUBSYSID_ATHEROS_COEX2WIRE) |
| 181 | sc->sc_flags |= ATHN_FLAG_BTCOEX2WIRE; |
| 182 | |
| 183 | /* |
| 184 | * Setup memory-mapping of PCI registers. |
| 185 | */ |
| 186 | memtype = pci_mapreg_type(pa->pa_pc, pa->pa_tag, ATHN_PCI_MMBA); |
| 187 | if (memtype != PCI_MAPREG_TYPE_MEM && |
| 188 | memtype != PCI_MAPREG_MEM_TYPE_64BIT) { |
| 189 | aprint_error_dev(self, "bad pci register type %d\n" , |
| 190 | (int)memtype); |
| 191 | goto fail; |
| 192 | } |
| 193 | error = pci_mapreg_map(pa, ATHN_PCI_MMBA, memtype, 0, &psc->psc_iot, |
| 194 | &psc->psc_ioh, NULL, &psc->psc_mapsz); |
| 195 | if (error != 0) { |
| 196 | aprint_error_dev(self, "cannot map register space\n" ); |
| 197 | goto fail; |
| 198 | } |
| 199 | |
| 200 | /* |
| 201 | * Arrange interrupt line. |
| 202 | */ |
| 203 | if (pci_intr_map(pa, &psc->psc_pih) != 0) { |
| 204 | aprint_error_dev(self, "couldn't map interrupt\n" ); |
| 205 | goto fail1; |
| 206 | } |
| 207 | |
| 208 | intrstr = pci_intr_string(psc->psc_pc, psc->psc_pih, intrbuf, sizeof(intrbuf)); |
| 209 | psc->psc_ih = pci_intr_establish(psc->psc_pc, psc->psc_pih, IPL_NET, |
| 210 | athn_intr, sc); |
| 211 | if (psc->psc_ih == NULL) { |
| 212 | aprint_error_dev(self, "couldn't map interrupt\n" ); |
| 213 | goto fail1; |
| 214 | } |
| 215 | |
| 216 | ic->ic_ifp = &sc->sc_if; |
| 217 | if (athn_attach(sc) != 0) |
| 218 | goto fail2; |
| 219 | |
| 220 | aprint_verbose_dev(self, "interrupting at %s\n" , intrstr); |
| 221 | |
| 222 | if (pmf_device_register(self, athn_pci_suspend, athn_pci_resume)) { |
| 223 | pmf_class_network_register(self, &sc->sc_if); |
| 224 | pmf_device_suspend(self, &sc->sc_qual); |
| 225 | } |
| 226 | else |
| 227 | aprint_error_dev(self, "couldn't establish power handler\n" ); |
| 228 | |
| 229 | ieee80211_announce(ic); |
| 230 | return; |
| 231 | |
| 232 | fail2: |
| 233 | pci_intr_disestablish(psc->psc_pc, psc->psc_ih); |
| 234 | psc->psc_ih = NULL; |
| 235 | fail1: |
| 236 | bus_space_unmap(psc->psc_iot, psc->psc_ioh, psc->psc_mapsz); |
| 237 | psc->psc_mapsz = 0; |
| 238 | fail: |
| 239 | return; |
| 240 | } |
| 241 | |
| 242 | Static int |
| 243 | athn_pci_detach(device_t self, int flags) |
| 244 | { |
| 245 | struct athn_pci_softc *psc = device_private(self); |
| 246 | struct athn_softc *sc = &psc->psc_sc; |
| 247 | |
| 248 | if (psc->psc_ih != NULL) { |
| 249 | athn_detach(sc); |
| 250 | pci_intr_disestablish(psc->psc_pc, psc->psc_ih); |
| 251 | psc->psc_ih = NULL; |
| 252 | } |
| 253 | if (psc->psc_mapsz > 0) { |
| 254 | bus_space_unmap(psc->psc_iot, psc->psc_ioh, psc->psc_mapsz); |
| 255 | psc->psc_mapsz = 0; |
| 256 | } |
| 257 | return 0; |
| 258 | } |
| 259 | |
| 260 | Static int |
| 261 | athn_pci_activate(device_t self, enum devact act) |
| 262 | { |
| 263 | struct athn_pci_softc *psc = device_private(self); |
| 264 | struct athn_softc *sc = &psc->psc_sc; |
| 265 | |
| 266 | switch (act) { |
| 267 | case DVACT_DEACTIVATE: |
| 268 | if_deactivate(sc->sc_ic.ic_ifp); |
| 269 | break; |
| 270 | } |
| 271 | return 0; |
| 272 | } |
| 273 | |
| 274 | Static bool |
| 275 | athn_pci_suspend(device_t self, const pmf_qual_t *qual) |
| 276 | { |
| 277 | struct athn_pci_softc *psc = device_private(self); |
| 278 | struct athn_softc *sc = &psc->psc_sc; |
| 279 | |
| 280 | athn_suspend(sc); |
| 281 | if (psc->psc_ih != NULL) { |
| 282 | pci_intr_disestablish(psc->psc_pc, psc->psc_ih); |
| 283 | psc->psc_ih = NULL; |
| 284 | } |
| 285 | return true; |
| 286 | } |
| 287 | |
| 288 | Static bool |
| 289 | athn_pci_resume(device_t self, const pmf_qual_t *qual) |
| 290 | { |
| 291 | struct athn_pci_softc *psc = device_private(self); |
| 292 | struct athn_softc *sc = &psc->psc_sc; |
| 293 | pcireg_t reg; |
| 294 | |
| 295 | /* |
| 296 | * XXX: see comment in athn_attach(). |
| 297 | */ |
| 298 | reg = pci_conf_read(psc->psc_pc, psc->psc_tag, 0x40); |
| 299 | if (reg & 0xff00) |
| 300 | pci_conf_write(psc->psc_pc, psc->psc_tag, 0x40, reg & ~0xff00); |
| 301 | |
| 302 | psc->psc_ih = pci_intr_establish(psc->psc_pc, psc->psc_pih, IPL_NET, |
| 303 | athn_intr, sc); |
| 304 | if (psc->psc_ih == NULL) { |
| 305 | aprint_error_dev(self, "couldn't map interrupt\n" ); |
| 306 | return false; |
| 307 | } |
| 308 | return athn_resume(sc); |
| 309 | } |
| 310 | |
| 311 | Static uint32_t |
| 312 | athn_pci_read(struct athn_softc *sc, uint32_t addr) |
| 313 | { |
| 314 | struct athn_pci_softc *psc = (struct athn_pci_softc *)sc; |
| 315 | |
| 316 | return bus_space_read_4(psc->psc_iot, psc->psc_ioh, addr); |
| 317 | } |
| 318 | |
| 319 | Static void |
| 320 | athn_pci_write(struct athn_softc *sc, uint32_t addr, uint32_t val) |
| 321 | { |
| 322 | struct athn_pci_softc *psc = (struct athn_pci_softc *)sc; |
| 323 | |
| 324 | bus_space_write_4(psc->psc_iot, psc->psc_ioh, addr, val); |
| 325 | } |
| 326 | |
| 327 | Static void |
| 328 | athn_pci_write_barrier(struct athn_softc *sc) |
| 329 | { |
| 330 | struct athn_pci_softc *psc = (struct athn_pci_softc *)sc; |
| 331 | |
| 332 | bus_space_barrier(psc->psc_iot, psc->psc_ioh, 0, psc->psc_mapsz, |
| 333 | BUS_SPACE_BARRIER_WRITE); |
| 334 | } |
| 335 | |
| 336 | Static void |
| 337 | athn_pci_disable_aspm(struct athn_softc *sc) |
| 338 | { |
| 339 | struct athn_pci_softc *psc = (struct athn_pci_softc *)sc; |
| 340 | pcireg_t reg; |
| 341 | |
| 342 | /* Disable PCIe Active State Power Management (ASPM). */ |
| 343 | reg = pci_conf_read(psc->psc_pc, psc->psc_tag, |
| 344 | psc->psc_cap_off + PCIE_LCSR); |
| 345 | reg &= ~(PCIE_LCSR_ASPM_L0S | PCIE_LCSR_ASPM_L1); |
| 346 | pci_conf_write(psc->psc_pc, psc->psc_tag, |
| 347 | psc->psc_cap_off + PCIE_LCSR, reg); |
| 348 | } |
| 349 | |