/* $NetBSD: if_bwi_sdio.c,v 1.1 2025/01/19 00:29:29 jmcneill Exp $ */
/*-
* Copyright (c) 2025 Jared McNeill <
[email protected]>
* 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 <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: if_bwi_sdio.c,v 1.1 2025/01/19 00:29:29 jmcneill Exp $");
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/mutex.h>
#include <sys/systm.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_ether.h>
#include <net/if_media.h>
#include <netinet/in.h>
#include <net80211/ieee80211_node.h>
#include <net80211/ieee80211_amrr.h>
#include <net80211/ieee80211_radiotap.h>
#include <net80211/ieee80211_var.h>
#include <dev/ic/bwireg.h>
#include <dev/ic/bwivar.h>
#include <dev/pcmcia/pcmciareg.h>
#include <dev/sdmmc/sdmmcdevs.h>
#include <dev/sdmmc/sdmmcvar.h>
#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;
}