/* $NetBSD: if_bwi_sdio.c,v 1.1 2025/01/19 00:29:29 jmcneill Exp $ */ /*- * Copyright (c) 2025 Jared McNeill * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include __KERNEL_RCSID(0, "$NetBSD: if_bwi_sdio.c,v 1.1 2025/01/19 00:29:29 jmcneill Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BWI_SDIO_FUNC1_SBADDRLOW 0x1000a #define BWI_SDIO_FUNC1_SBADDRMID 0x1000b #define BWI_SDIO_FUNC1_SBADDRHI 0x1000c #define BWI_CISTPL_VENDOR 0x80 #define BWI_VENDOR_SROMREV 0 #define BWI_VENDOR_ID 1 #define BWI_VENDOR_BOARDREV 2 #define BWI_VENDOR_PA 3 #define BWI_VENDOR_OEMNAME 4 #define BWI_VENDOR_CCODE 5 #define BWI_VENDOR_ANTENNA 6 #define BWI_VENDOR_ANTGAIN 7 #define BWI_VENDOR_BFLAGS 8 #define BWI_VENDOR_LEDS 9 #define BWI_SDIO_REG_OFFSET(ssc, reg) \ ((reg) | ((ssc)->sc_sel_regwin & 0x7000)) #define BWI_SDIO_REG_32BIT_ACCESS 0x8000 static const struct bwi_sdio_product { uint16_t vendor; uint16_t product; } bwi_sdio_products[] = { { SDMMC_VENDOR_BROADCOM, SDMMC_PRODUCT_BROADCOM_NINTENDO_WII }, }; struct bwi_sdio_sprom { uint16_t pa_params[3]; uint16_t board_vendor; uint16_t card_flags; uint8_t srom_rev; uint8_t board_rev; uint8_t idle_tssi; uint8_t max_txpwr; uint8_t country_code; uint8_t ant_avail; uint8_t ant_gain; uint8_t gpio[4]; }; struct bwi_sdio_softc { struct bwi_softc sc_base; struct sdmmc_function *sc_sf; struct bwi_sdio_sprom sc_sprom; uint32_t sc_sel_regwin; kmutex_t sc_lock; }; static int bwi_sdio_match(device_t, cfdata_t, void *); static void bwi_sdio_attach(device_t, device_t, void *); static void bwi_sdio_parse_cis(struct bwi_sdio_softc *); static int bwi_sdio_intr(void *); static void bwi_sdio_conf_write(void *, uint32_t, uint32_t); static uint32_t bwi_sdio_conf_read(void *, uint32_t); static void bwi_sdio_reg_write_2(void *, uint32_t, uint16_t); static uint16_t bwi_sdio_reg_read_2(void *, uint32_t); static void bwi_sdio_reg_write_4(void *, uint32_t, uint32_t); static uint32_t bwi_sdio_reg_read_4(void *, uint32_t); static void bwi_sdio_reg_write_multi_4(void *, uint32_t, const uint32_t *, size_t); static void bwi_sdio_reg_read_multi_4(void *, uint32_t, uint32_t *, size_t); CFATTACH_DECL_NEW(bwi_sdio, sizeof(struct bwi_sdio_softc), bwi_sdio_match, bwi_sdio_attach, NULL, NULL); static int bwi_sdio_match(device_t parent, cfdata_t cf, void *aux) { struct sdmmc_attach_args * const saa = aux; struct sdmmc_function *sf = saa->sf; struct sdmmc_cis *cis; u_int n; if (sf == NULL) { return 0; } cis = &sf->sc->sc_fn0->cis; for (n = 0; n < __arraycount(bwi_sdio_products); n++) { const struct bwi_sdio_product *bsp = &bwi_sdio_products[n]; if (bsp->vendor == cis->manufacturer && bsp->product == cis->product) { return 1; } } return 0; } static void bwi_sdio_attach(device_t parent, device_t self, void *aux) { struct bwi_sdio_softc * const ssc = device_private(self); struct bwi_softc * const sc = &ssc->sc_base; struct sdmmc_attach_args * const saa = aux; struct sdmmc_function *sf = saa->sf; struct sdmmc_cis *cis = &sf->sc->sc_fn0->cis; int error; void *ih; aprint_naive("\n"); aprint_normal(": Broadcom Wireless\n"); sc->sc_dev = self; sc->sc_flags = BWI_F_SDIO | BWI_F_PIO; sc->sc_conf_write = bwi_sdio_conf_write; sc->sc_conf_read = bwi_sdio_conf_read; sc->sc_reg_write_multi_4 = bwi_sdio_reg_write_multi_4; sc->sc_reg_read_multi_4 = bwi_sdio_reg_read_multi_4; sc->sc_reg_write_2 = bwi_sdio_reg_write_2; sc->sc_reg_read_2 = bwi_sdio_reg_read_2; sc->sc_reg_write_4 = bwi_sdio_reg_write_4; sc->sc_reg_read_4 = bwi_sdio_reg_read_4; sc->sc_pci_revid = 0; /* XXX can this come from CIS? */ sc->sc_pci_did = cis->product; sc->sc_pci_subvid = cis->manufacturer; sc->sc_pci_subdid = cis->product; ssc->sc_sf = sf; mutex_init(&ssc->sc_lock, MUTEX_DEFAULT, IPL_NONE); sdmmc_io_set_blocklen(ssc->sc_sf, 64); if (sdmmc_io_function_enable(ssc->sc_sf) != 0) { aprint_error_dev(self, "couldn't enable function\n"); return; } bwi_sdio_parse_cis(ssc); ih = sdmmc_intr_establish(parent, bwi_sdio_intr, ssc, device_xname(self)); if (ih == NULL) { aprint_error_dev(self, "couldn't establish interrupt\n"); return; } error = bwi_attach(sc); if (error != 0) { sdmmc_intr_disestablish(ih); return; } sdmmc_intr_enable(ssc->sc_sf); } static void bwi_sdio_parse_cis(struct bwi_sdio_softc *ssc) { struct sdmmc_function *sf0 = ssc->sc_sf->sc->sc_fn0; struct bwi_sdio_sprom *sprom = &ssc->sc_sprom; uint32_t reg; uint8_t tplcode, tpllen; reg = sdmmc_cisptr(ssc->sc_sf); for (;;) { tplcode = sdmmc_io_read_1(sf0, reg++); if (tplcode == PCMCIA_CISTPL_NULL) { continue; } tpllen = sdmmc_io_read_1(sf0, reg++); if (tplcode == PCMCIA_CISTPL_END || tpllen == 0) { break; } if (tplcode != BWI_CISTPL_VENDOR) { reg += tpllen; continue; } switch (sdmmc_io_read_1(sf0, reg)) { case BWI_VENDOR_SROMREV: sprom->srom_rev = sdmmc_io_read_1(sf0, reg + 1); break; case BWI_VENDOR_ID: sprom->board_vendor = sdmmc_io_read_1(sf0, reg + 1) | ((uint16_t)sdmmc_io_read_1(sf0, reg + 2) << 8); break; case BWI_VENDOR_BOARDREV: sprom->board_rev = sdmmc_io_read_1(sf0, reg + 1); break; case BWI_VENDOR_PA: sprom->pa_params[0] = sdmmc_io_read_1(sf0, reg + 1) | ((uint16_t)sdmmc_io_read_1(sf0, reg + 2) << 8); sprom->pa_params[1] = sdmmc_io_read_1(sf0, reg + 3) | ((uint16_t)sdmmc_io_read_1(sf0, reg + 4) << 8); sprom->pa_params[2] = sdmmc_io_read_1(sf0, reg + 5) | ((uint16_t)sdmmc_io_read_1(sf0, reg + 6) << 8); sprom->idle_tssi = sdmmc_io_read_1(sf0, reg + 7); sprom->max_txpwr = sdmmc_io_read_1(sf0, reg + 8); break; case BWI_VENDOR_CCODE: sprom->country_code = sdmmc_io_read_1(sf0, reg + 1); break; case BWI_VENDOR_ANTGAIN: sprom->ant_gain = sdmmc_io_read_1(sf0, reg + 1); break; case BWI_VENDOR_BFLAGS: sprom->card_flags = sdmmc_io_read_1(sf0, reg + 1) | ((uint16_t)sdmmc_io_read_1(sf0, reg + 2) << 8); break; case BWI_VENDOR_LEDS: sprom->gpio[0] = sdmmc_io_read_1(sf0, reg + 1); sprom->gpio[1] = sdmmc_io_read_1(sf0, reg + 2); sprom->gpio[2] = sdmmc_io_read_1(sf0, reg + 3); sprom->gpio[3] = sdmmc_io_read_1(sf0, reg + 4); break; } reg += tpllen; } } static int bwi_sdio_intr(void *priv) { struct bwi_sdio_softc * const ssc = priv; bwi_intr(&ssc->sc_base); return 1; } static void bwi_sdio_conf_write(void *priv, uint32_t reg, uint32_t val) { struct bwi_sdio_softc * const ssc = priv; KASSERT(reg == BWI_PCIR_SEL_REGWIN); mutex_enter(&ssc->sc_lock); if (reg == BWI_PCIR_SEL_REGWIN && ssc->sc_sel_regwin != val) { sdmmc_io_write_1(ssc->sc_sf, BWI_SDIO_FUNC1_SBADDRLOW, (val >> 8) & 0x80); sdmmc_io_write_1(ssc->sc_sf, BWI_SDIO_FUNC1_SBADDRMID, (val >> 16) & 0xff); sdmmc_io_write_1(ssc->sc_sf, BWI_SDIO_FUNC1_SBADDRHI, (val >> 24) & 0xff); ssc->sc_sel_regwin = val; } mutex_exit(&ssc->sc_lock); } static uint32_t bwi_sdio_conf_read(void *priv, uint32_t reg) { struct bwi_sdio_softc * const ssc = priv; KASSERT(reg == BWI_PCIR_SEL_REGWIN); if (reg == BWI_PCIR_SEL_REGWIN) { return ssc->sc_sel_regwin; } else { return 0; } } static void bwi_sdio_reg_write_multi_4(void *priv, uint32_t reg, const uint32_t *datap, size_t count) { struct bwi_sdio_softc * const ssc = priv; mutex_enter(&ssc->sc_lock); sdmmc_io_write_multi_1(ssc->sc_sf, BWI_SDIO_REG_OFFSET(ssc, reg) | BWI_SDIO_REG_32BIT_ACCESS, (uint8_t *)__UNCONST(datap), count * sizeof(uint32_t)); mutex_exit(&ssc->sc_lock); } static void bwi_sdio_reg_read_multi_4(void *priv, uint32_t reg, uint32_t *datap, size_t count) { struct bwi_sdio_softc * const ssc = priv; mutex_enter(&ssc->sc_lock); sdmmc_io_read_multi_1(ssc->sc_sf, BWI_SDIO_REG_OFFSET(ssc, reg) | BWI_SDIO_REG_32BIT_ACCESS, (uint8_t *)datap, count * sizeof(uint32_t)); mutex_exit(&ssc->sc_lock); } static void bwi_sdio_reg_write_2(void *priv, uint32_t reg, uint16_t val) { struct bwi_sdio_softc * const ssc = priv; val = htole16(val); mutex_enter(&ssc->sc_lock); sdmmc_io_write_2(ssc->sc_sf, BWI_SDIO_REG_OFFSET(ssc, reg), val); mutex_exit(&ssc->sc_lock); } static uint16_t bwi_sdio_reg_read_sprom(struct bwi_sdio_softc *ssc, uint32_t reg) { struct bwi_sdio_sprom *sprom = &ssc->sc_sprom; struct sdmmc_cis *cis = &ssc->sc_sf->cis; switch (reg) { case BWI_SPROM_11BG_EADDR ... BWI_SPROM_11BG_EADDR + 4: return *(uint16_t *)&cis->lan_nid[reg - BWI_SPROM_11BG_EADDR]; case BWI_SPROM_11A_EADDR ... BWI_SPROM_11A_EADDR + 4: return *(uint16_t *)&cis->lan_nid[reg - BWI_SPROM_11A_EADDR]; case BWI_SPROM_CARD_INFO: return (uint16_t)sprom->country_code << 8; case BWI_SPROM_PA_PARAM_11BG ... BWI_SPROM_PA_PARAM_11BG + 4: return sprom->pa_params[(reg - BWI_SPROM_PA_PARAM_11BG) / 2]; case BWI_SPROM_PA_PARAM_11A ... BWI_SPROM_PA_PARAM_11A + 4: return sprom->pa_params[(reg - BWI_SPROM_PA_PARAM_11A) / 2]; case BWI_SPROM_GPIO01: return sprom->gpio[0] | ((uint16_t)sprom->gpio[1] << 8); case BWI_SPROM_GPIO23: return sprom->gpio[2] | ((uint16_t)sprom->gpio[3] << 8); case BWI_SPROM_MAX_TXPWR: return sprom->max_txpwr | ((uint16_t)sprom->max_txpwr << 8); case BWI_SPROM_IDLE_TSSI: return sprom->idle_tssi | ((uint16_t)sprom->idle_tssi << 8); case BWI_SPROM_CARD_FLAGS: return sprom->card_flags; case BWI_SPROM_ANT_GAIN: return sprom->ant_gain | ((uint16_t)sprom->ant_gain << 8); default: return 0xffff; } } static uint16_t bwi_sdio_reg_read_2(void *priv, uint32_t reg) { struct bwi_sdio_softc * const ssc = priv; uint16_t val; /* Emulate SPROM reads */ if (reg >= BWI_SPROM_START && reg <= BWI_SPROM_START + BWI_SPROM_ANT_GAIN) { return bwi_sdio_reg_read_sprom(ssc, reg - BWI_SPROM_START); } mutex_enter(&ssc->sc_lock); val = sdmmc_io_read_2(ssc->sc_sf, BWI_SDIO_REG_OFFSET(ssc, reg)); mutex_exit(&ssc->sc_lock); val = le16toh(val); return val; } static void bwi_sdio_reg_write_4(void *priv, uint32_t reg, uint32_t val) { struct bwi_sdio_softc * const ssc = priv; val = htole32(val); mutex_enter(&ssc->sc_lock); sdmmc_io_write_4(ssc->sc_sf, BWI_SDIO_REG_OFFSET(ssc, reg) | BWI_SDIO_REG_32BIT_ACCESS, val); /* SDIO cards require a read after a 32-bit write */ sdmmc_io_read_4(ssc->sc_sf, 0); mutex_exit(&ssc->sc_lock); } static uint32_t bwi_sdio_reg_read_4(void *priv, uint32_t reg) { struct bwi_sdio_softc * const ssc = priv; uint32_t val; mutex_enter(&ssc->sc_lock); val = sdmmc_io_read_4(ssc->sc_sf, BWI_SDIO_REG_OFFSET(ssc, reg) | BWI_SDIO_REG_32BIT_ACCESS); mutex_exit(&ssc->sc_lock); val = le32toh(val); return val; }