Module Name:    src
Committed By:   martin
Date:           Sun Feb  2 14:30:00 UTC 2025

Modified Files:
        src/share/man/man4 [netbsd-10]: bwi.4
        src/sys/arch/evbppc/conf [netbsd-10]: WII
        src/sys/dev/ic [netbsd-10]: bwi.c bwireg.h bwivar.h
        src/sys/dev/sdmmc [netbsd-10]: files.sdmmc sdmmc_cis.c sdmmcvar.h
Added Files:
        src/sys/dev/sdmmc [netbsd-10]: if_bwi_sdio.c

Log Message:
Pull up following revision(s) (requested by jmcneill in ticket #1041):

        sys/dev/ic/bwi.c: revision 1.40
        share/man/man4/bwi.4: revision 1.15
        sys/dev/sdmmc/if_bwi_sdio.c: revision 1.1
        sys/dev/ic/bwi.c: revision 1.41
        sys/dev/sdmmc/sdmmcvar.h: revision 1.38
        sys/dev/ic/bwireg.h: revision 1.5
        sys/dev/ic/bwi.c: revision 1.39
        sys/dev/ic/bwivar.h: revision 1.11
        sys/dev/sdmmc/sdmmc_cis.c: revision 1.10
        sys/dev/sdmmc/files.sdmmc: revision 1.6
        sys/arch/evbppc/conf/WII: revision 1.7

bwi: Remove unnecessary pcivar.h include

sdmmc: Capture lan_nid and expose sdmmc_cisptr
LAN NID contains the MAC address for networking adapters.
Device drivers may want to processor vendor specific tuple codes, so
expose sdmmc_cisptr to help this.

bwi(4): Add support for Nintendo Wii WLAN.

Adapt the bwi(4) driver to support SDIO attachment and driving TX/RX using
PIO instead of DMA since the latter is not supported on SDIO busses.

fix uninitialized


To generate a diff of this commit:
cvs rdiff -u -r1.13 -r1.13.36.1 src/share/man/man4/bwi.4
cvs rdiff -u -r1.4.2.4 -r1.4.2.5 src/sys/arch/evbppc/conf/WII
cvs rdiff -u -r1.38 -r1.38.10.1 src/sys/dev/ic/bwi.c
cvs rdiff -u -r1.4 -r1.4.20.1 src/sys/dev/ic/bwireg.h
cvs rdiff -u -r1.10 -r1.10.46.1 src/sys/dev/ic/bwivar.h
cvs rdiff -u -r1.5 -r1.5.36.1 src/sys/dev/sdmmc/files.sdmmc
cvs rdiff -u -r0 -r1.1.2.2 src/sys/dev/sdmmc/if_bwi_sdio.c
cvs rdiff -u -r1.8.26.1 -r1.8.26.2 src/sys/dev/sdmmc/sdmmc_cis.c
cvs rdiff -u -r1.36.18.1 -r1.36.18.2 src/sys/dev/sdmmc/sdmmcvar.h

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/share/man/man4/bwi.4
diff -u src/share/man/man4/bwi.4:1.13 src/share/man/man4/bwi.4:1.13.36.1
--- src/share/man/man4/bwi.4:1.13	Tue Mar 18 18:20:39 2014
+++ src/share/man/man4/bwi.4	Sun Feb  2 14:29:59 2025
@@ -1,4 +1,4 @@
-.\" $NetBSD: bwi.4,v 1.13 2014/03/18 18:20:39 riastradh Exp $
+.\" $NetBSD: bwi.4,v 1.13.36.1 2025/02/02 14:29:59 martin Exp $
 .\"
 .\" Copyright (c) 2007 The DragonFly Project.  All rights reserved.
 .\"
@@ -31,7 +31,7 @@
 .\"
 .\" $DragonFly: src/share/man/man4/bwi.4,v 1.10 2008/07/26 16:25:40 swildner Exp $
 .\"
-.Dd April 25, 2012
+.Dd January 18, 2025
 .Dt BWI 4
 .Os
 .Sh NAME
@@ -40,6 +40,7 @@
 .Sh SYNOPSIS
 .Cd "bwi* at pci? dev ? function ?"
 .Cd "bwi* at cardbus? function ?"
+.Cd "bwi* at sdmmc?"
 .Sh DESCRIPTION
 The
 .Nm
@@ -73,6 +74,7 @@ driver:
 .Pp
 .Bl -column -offset 6n -compact "Apple AirPort Extreme" "BCM4318" "Mini PCI" "b/g"
 .It Em "Card	Chip	Bus	Standard"
+.It Apple AirPort Extreme			b/g
 .It Buffalo WLI-CB-G54	BCM4306	CardBus	b/g
 .It Buffalo WLI3-CB-G54L	BCM4318	CardBus	b/g
 .It Buffalo WLI-PCI-G54S	BCM4306	PCI	b/g
@@ -80,7 +82,7 @@ driver:
 .It Dell Wireless 1470	BCM4318	Mini PCI	b/g
 .It Dell Truemobile 1400	BCM4309	Mini PCI	b/g
 .It Dell Latitude D505	BCM4306	PCI	b/g
-.It Apple AirPort Extreme			b/g
+.It Nintendo Wii WLAN	BCM4318	SDIO	b/g
 .El
 .Sh FILES
 The firmware for the adapter is not shipped with

Index: src/sys/arch/evbppc/conf/WII
diff -u src/sys/arch/evbppc/conf/WII:1.4.2.4 src/sys/arch/evbppc/conf/WII:1.4.2.5
--- src/sys/arch/evbppc/conf/WII:1.4.2.4	Wed Oct  2 12:28:15 2024
+++ src/sys/arch/evbppc/conf/WII	Sun Feb  2 14:29:59 2025
@@ -1,4 +1,4 @@
-#	$NetBSD: WII,v 1.4.2.4 2024/10/02 12:28:15 martin Exp $
+#	$NetBSD: WII,v 1.4.2.5 2025/02/02 14:29:59 martin Exp $
 #
 #	Nintendo Wii
 #
@@ -155,6 +155,7 @@ sdhc0		at hollywood0 addr 0x0d070000 irq
 sdhc1		at hollywood0 addr 0x0d080000 irq 8	# SDIO/BT
 sdmmc*		at sdmmcbus?
 ld*		at sdmmc?
+bwi*		at sdmmc?				# WLAN
 
 include "dev/usb/usbdevices.config"
 include "dev/bluetooth/bluetoothdevices.config"

Index: src/sys/dev/ic/bwi.c
diff -u src/sys/dev/ic/bwi.c:1.38 src/sys/dev/ic/bwi.c:1.38.10.1
--- src/sys/dev/ic/bwi.c:1.38	Wed Jun 16 00:21:18 2021
+++ src/sys/dev/ic/bwi.c	Sun Feb  2 14:29:59 2025
@@ -1,4 +1,4 @@
-/*	$NetBSD: bwi.c,v 1.38 2021/06/16 00:21:18 riastradh Exp $	*/
+/*	$NetBSD: bwi.c,v 1.38.10.1 2025/02/02 14:29:59 martin Exp $	*/
 /*	$OpenBSD: bwi.c,v 1.74 2008/02/25 21:13:30 mglocker Exp $	*/
 
 /*
@@ -48,7 +48,7 @@
 
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: bwi.c,v 1.38 2021/06/16 00:21:18 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: bwi.c,v 1.38.10.1 2025/02/02 14:29:59 martin Exp $");
 
 #include <sys/param.h>
 #include <sys/callout.h>
@@ -62,6 +62,10 @@ __KERNEL_RCSID(0, "$NetBSD: bwi.c,v 1.38
 #include <sys/systm.h>
 #include <sys/bus.h>
 #include <sys/intr.h>
+#include <sys/pool.h>
+#include <sys/workqueue.h>
+#include <sys/mutex.h>
+#include <sys/kmem.h>
 
 #include <machine/endian.h>
 
@@ -88,7 +92,7 @@ int bwi_debug = 0;
 #define DPRINTF(sc, dbg, fmt, ...)					\
 do {									\
 	if ((sc)->sc_debug & (dbg))					\
-		aprint_debug_dev((sc)->sc_dev, fmt, ##__VA_ARGS__);	\
+		device_printf((sc)->sc_dev, fmt, ##__VA_ARGS__);	\
 } while (0)
 
 #else	/* !BWI_DEBUG */
@@ -99,7 +103,6 @@ do {									\
 
 /* XXX temporary porting goop */
 #include <dev/pci/pcireg.h>
-#include <dev/pci/pcivar.h>
 #include <dev/pci/pcidevs.h>
 
 /* XXX does not belong here */
@@ -310,13 +313,18 @@ static void	 bwi_watchdog(struct ifnet *
 static void	 bwi_stop(struct ifnet *, int);
 static void	 bwi_newstate_begin(struct bwi_softc *, enum ieee80211_state);
 static int	 bwi_newstate(struct ieee80211com *, enum ieee80211_state, int);
+static int	 bwi_newstate_sdio(struct ieee80211com *, enum ieee80211_state,
+		     int);
 static int	 bwi_media_change(struct ifnet *);
+static void	 bwi_task(struct work *, void *);
 /* [TRC: XXX amrr] */
 static void	 bwi_iter_func(void *, struct ieee80211_node *);
 static void	 bwi_amrr_timeout(void *);
 static void	 bwi_newassoc(struct ieee80211_node *, int);
 static struct ieee80211_node *
 		 bwi_node_alloc(struct ieee80211_node_table *);
+static int	 bwi_pio_alloc(struct bwi_softc *);
+static void	 bwi_pio_free(struct bwi_softc *);
 static int	 bwi_dma_alloc(struct bwi_softc *);
 static void	 bwi_dma_free(struct bwi_softc *);
 static void	 bwi_ring_data_free(struct bwi_ring_data *, struct bwi_softc *);
@@ -329,6 +337,13 @@ static int	 bwi_dma_mbuf_create(struct b
 static void	 bwi_dma_mbuf_destroy(struct bwi_softc *, int, int);
 static void	 bwi_enable_intrs(struct bwi_softc *, uint32_t);
 static void	 bwi_disable_intrs(struct bwi_softc *, uint32_t);
+static int	 bwi_init_tx_ring_pio(struct bwi_softc *, int);
+static int	 bwi_init_rx_ring_pio(struct bwi_softc *);
+static int	 bwi_init_txstats_pio(struct bwi_softc *);
+static void	 bwi_setup_rx_desc_pio(struct bwi_softc *, int, bus_addr_t,
+		     int);
+static void	 bwi_setup_tx_desc_pio(struct bwi_softc *, 
+		     struct bwi_ring_data *, int, bus_addr_t, int);
 static int	 bwi_init_tx_ring32(struct bwi_softc *, int);
 static void	 bwi_init_rxdesc_ring32(struct bwi_softc *, uint32_t,
 		     bus_addr_t, int, int);
@@ -349,8 +364,12 @@ static void	 bwi_set_addr_filter(struct 
 static int	 bwi_set_chan(struct bwi_softc *, struct ieee80211_channel *);
 static void	 bwi_next_scan(void *);
 static int	 bwi_rxeof(struct bwi_softc *, int);
+static int	 bwi_rxeof_pio(struct bwi_softc *);
 static int	 bwi_rxeof32(struct bwi_softc *);
 static int	 bwi_rxeof64(struct bwi_softc *);
+static void	 bwi_free_txstats_pio(struct bwi_softc *);
+static void	 bwi_free_rx_ring_pio(struct bwi_softc *);
+static void	 bwi_free_tx_ring_pio(struct bwi_softc *, int);
 static void	 bwi_reset_rx_ring32(struct bwi_softc *, uint32_t);
 static void	 bwi_free_txstats32(struct bwi_softc *);
 static void	 bwi_free_rx_ring32(struct bwi_softc *);
@@ -370,8 +389,10 @@ static void	 bwi_ds_plcp_header(struct i
 static void	 bwi_plcp_header(void *, int, uint8_t);
 static int	 bwi_encap(struct bwi_softc *, int, struct mbuf *,
 		     struct ieee80211_node **, int);
+static void	 bwi_start_tx_pio(struct bwi_softc *, uint32_t, int);
 static void	 bwi_start_tx32(struct bwi_softc *, uint32_t, int);
 static void	 bwi_start_tx64(struct bwi_softc *, uint32_t, int);
+static void	 bwi_txeof_status_pio(struct bwi_softc *);
 static void	 bwi_txeof_status32(struct bwi_softc *);
 static void	 bwi_txeof_status64(struct bwi_softc *);
 static void	 _bwi_txeof(struct bwi_softc *, uint16_t);
@@ -391,6 +412,8 @@ static void	 bwi_regwin_disable(struct b
 		     uint32_t);
 static void	 bwi_set_bssid(struct bwi_softc *, const uint8_t *);
 static void	 bwi_updateslot(struct ifnet *);
+static void	 bwi_updateslot_sdio(struct ifnet *);
+static void	 bwi_do_calibrate(struct bwi_softc *);
 static void	 bwi_calibrate(void *);
 static int	 bwi_calc_rssi(struct bwi_softc *,
 		     const struct bwi_rxbuf_hdr *);
@@ -690,30 +713,13 @@ err:
 
 /* CODE */
 
-int
-bwi_intr(void *arg)
-{
-	struct bwi_softc *sc = arg;
-	struct ifnet *ifp = &sc->sc_if;
-
-	if (!device_is_active(sc->sc_dev) ||
-	    (ifp->if_flags & IFF_RUNNING) == 0)
-		return (0);
-
-	/* Disable all interrupts */
-	bwi_disable_intrs(sc, BWI_ALL_INTRS);
-
-	softint_schedule(sc->sc_soft_ih);
-	return (1);
-}
-
 static void
 bwi_softintr(void *arg)
 {
 	struct bwi_softc *sc = arg;
 	struct bwi_mac *mac;
 	struct ifnet *ifp = &sc->sc_if;
-	uint32_t intr_status;
+	uint32_t intr_status, intr_mask;
 	uint32_t txrx_intr_status[BWI_TXRX_NRING];
 	int i, s, txrx_error, tx = 0, rx_data = -1;
 
@@ -729,11 +735,19 @@ bwi_softintr(void *arg)
 		if (intr_status == 0xffffffff)	/* Not for us */
 			goto out;
 
-		intr_status &= CSR_READ_4(sc, BWI_MAC_INTR_MASK);
-		if (intr_status == 0)		/* Nothing is interesting */
+		if (BWI_IS_SDIO(sc)) {
+			intr_mask = 0xffffffff;
+		} else {
+			/* XXX FIXME */
+			intr_mask = CSR_READ_4(sc, BWI_MAC_INTR_MASK);
+		}
+		DPRINTF(sc, BWI_DBG_INTR,
+		    "intr status 0x%08x mask 0x%08x -> 0x%08x\n",
+		    intr_status, intr_mask, intr_status & intr_mask);
+		intr_status &= intr_mask;
+		if (intr_status == 0) {		/* Nothing is interesting */
 			goto out;
-
-		DPRINTF(sc, BWI_DBG_INTR, "intr status 0x%08x\n", intr_status);
+		}
 
 		KASSERT(sc->sc_cur_regwin->rw_type == BWI_REGWIN_T_MAC);
 		mac = (struct bwi_mac *)sc->sc_cur_regwin;
@@ -802,8 +816,9 @@ bwi_softintr(void *arg)
 		if (intr_status & BWI_INTR_NOISE)
 			aprint_normal_dev(sc->sc_dev, "intr noise\n");
 
-		if (txrx_intr_status[0] & BWI_TXRX_INTR_RX)
+		if (txrx_intr_status[0] & BWI_TXRX_INTR_RX) {
 			rx_data = (sc->sc_rxeof)(sc);
+		}
 
 		if (txrx_intr_status[3] & BWI_TXRX_INTR_RX) {
 			(sc->sc_txeof_status)(sc);
@@ -815,7 +830,8 @@ bwi_softintr(void *arg)
 			tx = 1;
 		}
 
-		if (sc->sc_blink_led != NULL && sc->sc_led_blink) {
+		if (sc->sc_blink_led != NULL && sc->sc_led_blink &&
+		    !BWI_IS_SDIO(sc)) {
 			int evt = BWI_LED_EVENT_NONE;
 
 			if (tx && rx_data > 0) {
@@ -842,6 +858,28 @@ out:
 }
 
 int
+bwi_intr(void *arg)
+{
+	struct bwi_softc *sc = arg;
+	struct ifnet *ifp = &sc->sc_if;
+
+	if (!device_is_active(sc->sc_dev) ||
+	    (ifp->if_flags & IFF_RUNNING) == 0)
+		return (0);
+
+	/* Disable all interrupts */
+	bwi_disable_intrs(sc, BWI_ALL_INTRS);
+
+	if (BWI_IS_SDIO(sc)) {
+		bwi_softintr(sc);
+	} else {
+		softint_schedule(sc->sc_soft_ih);
+	}
+
+	return (1);
+}
+
+int
 bwi_attach(struct bwi_softc *sc)
 {
 	struct ieee80211com *ic = &sc->sc_ic;
@@ -853,10 +891,25 @@ bwi_attach(struct bwi_softc *sc)
 	/* [TRC: XXX Is this necessary?] */
 	s = splnet();
 
-	sc->sc_soft_ih = softint_establish(SOFTINT_NET, bwi_softintr, sc);
-	if (sc->sc_soft_ih == NULL) {
-		error = ENXIO;
-		goto fail;
+	if (BWI_IS_SDIO(sc)) {
+		error = workqueue_create(&sc->sc_taskq,
+		    device_xname(sc->sc_dev), bwi_task, sc, PRI_NONE,
+		    IPL_NET, 0);
+		if (error != 0) {
+			device_printf(sc->sc_dev,
+			    "failed to create workqueue\n");
+			goto fail;
+		}
+		sc->sc_freetask = pool_cache_init(sizeof(struct bwi_task),
+		    0, 0, 0, "bwitask", NULL, IPL_NET, NULL, NULL, NULL);
+		pool_cache_prime(sc->sc_freetask, BWI_TASK_COUNT);
+	} else {
+		sc->sc_soft_ih = softint_establish(SOFTINT_NET, bwi_softintr,
+		    sc);
+		if (sc->sc_soft_ih == NULL) {
+			error = ENXIO;
+			goto fail;
+		}
 	}
 
 	/*
@@ -945,7 +998,11 @@ bwi_attach(struct bwi_softc *sc)
 
 	bwi_bbp_power_off(sc);
 
-	error = bwi_dma_alloc(sc);
+	if (BWI_IS_PIO(sc)) {
+		error = bwi_pio_alloc(sc);
+	} else {
+		error = bwi_dma_alloc(sc);
+	}
 	if (error)
 		goto fail;
 
@@ -1019,7 +1076,8 @@ bwi_attach(struct bwi_softc *sc)
 	ic->ic_state = IEEE80211_S_INIT;
 	ic->ic_opmode = IEEE80211_M_STA;
 
-	ic->ic_updateslot = bwi_updateslot;
+	ic->ic_updateslot = BWI_IS_SDIO(sc) ?
+	    bwi_updateslot_sdio : bwi_updateslot;
 
 	if_initialize(ifp);
 	ieee80211_ifattach(ic);
@@ -1030,7 +1088,7 @@ bwi_attach(struct bwi_softc *sc)
 	/* ic->ic_flags_ext |= IEEE80211_FEXT_SWBMISS; */
 
 	sc->sc_newstate = ic->ic_newstate;
-	ic->ic_newstate = bwi_newstate;
+	ic->ic_newstate = BWI_IS_SDIO(sc) ? bwi_newstate_sdio : bwi_newstate;
 	/* [TRC: XXX amrr] */
 	ic->ic_newassoc = bwi_newassoc;
 	ic->ic_node_alloc = bwi_node_alloc;
@@ -1085,7 +1143,17 @@ bwi_detach(struct bwi_softc *sc)
 
 	splx(s);
 
-	bwi_dma_free(sc);
+	if (BWI_IS_PIO(sc)) {
+		bwi_pio_free(sc);
+		if (sc->sc_taskq != NULL) {
+			workqueue_destroy(sc->sc_taskq);
+		}
+		if (sc->sc_freetask != NULL) {
+			pool_cache_destroy(sc->sc_freetask);
+		}
+	} else {
+		bwi_dma_free(sc);
+	}
 }
 
 /* MAC */
@@ -1251,6 +1319,12 @@ bwi_mac_lateattach(struct bwi_mac *mac)
 
 	bwi_rf_off(mac);
 	CSR_WRITE_2(mac->mac_sc, BWI_BBP_ATTEN, BWI_BBP_ATTEN_MAGIC);
+
+	if (BWI_IS_PIO(mac->mac_sc)) {
+		/* Initialize RX padding data offset */
+		MOBJ_WRITE_2(mac, BWI_COMM_MOBJ, BWI_COMM_MOBJ_RXPADOFF, 0);
+	}
+
 	bwi_regwin_disable(mac->mac_sc, &mac->mac_regwin, 0);
 
 	return (0);
@@ -1527,7 +1601,7 @@ bwi_mac_test(struct bwi_mac *mac)
 	MOBJ_WRITE_4(mac, BWI_COMM_MOBJ, 0, TEST_VAL1);
 	val = MOBJ_READ_4(mac, BWI_COMM_MOBJ, 0);
 	if (val != TEST_VAL1) {
-		aprint_error_dev(sc->sc_dev, "TEST1 failed\n");
+		aprint_error_dev(sc->sc_dev, "TEST1 failed [0x%08x]\n", val);
 		return (ENXIO);
 	}
 
@@ -1535,7 +1609,7 @@ bwi_mac_test(struct bwi_mac *mac)
 	MOBJ_WRITE_4(mac, BWI_COMM_MOBJ, 0, TEST_VAL2);
 	val = MOBJ_READ_4(mac, BWI_COMM_MOBJ, 0);
 	if (val != TEST_VAL2) {
-		aprint_error_dev(sc->sc_dev, "TEST2 failed\n");
+		aprint_error_dev(sc->sc_dev, "TEST2 failed [0x%08x]\n", val);
 		return (ENXIO);
 	}
 
@@ -2552,6 +2626,12 @@ bwi_mac_get_property(struct bwi_mac *mac
 	 * Byte swap
 	 */
 	val = CSR_READ_4(sc, BWI_MAC_STATUS);
+	if (BWI_IS_PIO(sc) && (val & BWI_MAC_STATUS_BSWAP)) {
+		DPRINTF(sc, BWI_DBG_MAC | BWI_DBG_ATTACH, "disable byte swap\n");
+		val &= ~BWI_MAC_STATUS_BSWAP;
+		CSR_WRITE_4(sc, BWI_MAC_STATUS, val);
+		val = CSR_READ_4(sc, BWI_MAC_STATUS);
+	}
 	if (val & BWI_MAC_STATUS_BSWAP) {
 		DPRINTF(sc, BWI_DBG_MAC | BWI_DBG_ATTACH, "need byte swap\n");
 		mac->mac_flags |= BWI_MAC_F_BSWAP;
@@ -6419,6 +6499,10 @@ bwi_power_on(struct bwi_softc *sc, int w
 
 	DPRINTF(sc, BWI_DBG_MISC, "%s\n", __func__);
 
+	if (BWI_IS_SDIO(sc)) {
+		return;
+	}
+
 	gpio_in = (sc->sc_conf_read)(sc, BWI_PCIR_GPIO_IN);
 	if (gpio_in & BWI_PCIM_GPIO_PWR_ON)
 		goto back;
@@ -6460,6 +6544,10 @@ bwi_power_off(struct bwi_softc *sc, int 
 
 	DPRINTF(sc, BWI_DBG_MISC, "%s\n", __func__);
 
+	if (BWI_IS_SDIO(sc)) {
+		return (0);
+	}
+
 	(sc->sc_conf_read)(sc, BWI_PCIR_GPIO_IN); /* dummy read */
 	gpio_out = (sc->sc_conf_read)(sc, BWI_PCIR_GPIO_OUT);
 	gpio_en = (sc->sc_conf_read)(sc, BWI_PCIR_GPIO_ENABLE);
@@ -6901,8 +6989,13 @@ bwi_bus_init(struct bwi_softc *sc, struc
 	bus = &sc->sc_bus_regwin;
 	KASSERT(sc->sc_cur_regwin == &mac->mac_regwin);
 
+	if (BWI_IS_SDIO(sc)) {
+		sc->sc_flags |= BWI_F_BUS_INITED;
+		return (0);
+	}
+
 	/*
-	 * Tell bus to generate requested interrupts
+	 * Tell bus to generate requested interrupts (PCI and Cardbus only).
 	 */
 	if (bus->rw_rev < 6 && bus->rw_type == BWI_REGWIN_T_BUSPCI) {
 		/*
@@ -7038,7 +7131,8 @@ bwi_get_clock_freq(struct bwi_softc *sc,
 	src = -1;
 	div = 0;
 	if (com->rw_rev < 6) {
-		val = (sc->sc_conf_read)(sc, BWI_PCIR_GPIO_OUT);
+		val = BWI_IS_SDIO(sc) ?
+		    0 : (sc->sc_conf_read)(sc, BWI_PCIR_GPIO_OUT);
 		if (val & BWI_PCIM_GPIO_OUT_CLKSRC) {
 			src = BWI_CLKSRC_PCI;
 			div = 64;
@@ -7250,12 +7344,12 @@ bwi_init_statechg(struct bwi_softc *sc, 
 	if (error)
 		goto back;
 
-	/* Enable intrs */
-	bwi_enable_intrs(sc, BWI_INIT_INTRS);
-
 	ifp->if_flags |= IFF_RUNNING;
 	ifp->if_flags &= ~IFF_OACTIVE;
 
+	/* Enable intrs */
+	bwi_enable_intrs(sc, BWI_INIT_INTRS);
+
 	if (statechg) {
 		if (ic->ic_opmode != IEEE80211_M_MONITOR) {
 			/* [TRC: XXX OpenBSD omits this conditional.] */
@@ -7274,6 +7368,8 @@ back:
 	else
 		/* [TRC: XXX DragonFlyBD uses ifp->if_start(ifp).] */
 		bwi_start(ifp);
+
+	DPRINTF(sc, BWI_DBG_MISC, "%s done\n", __func__);
 }
 
 static int
@@ -7503,6 +7599,24 @@ bwi_start(struct ifnet *ifp)
 	if (trans)
 		sc->sc_tx_timer = 5;
 	ifp->if_timer = 1;
+
+	if (BWI_IS_PIO(sc)) {
+		mutex_enter(&sc->sc_pio_txlock);
+		if (!STAILQ_EMPTY(&sc->sc_pio_txpend)) {
+			struct bwi_task *t;
+
+			t = pool_cache_get(sc->sc_freetask, PR_NOWAIT);
+			if (t == NULL) {
+				device_printf(sc->sc_dev, "no free tasks\n");
+			} else {
+				t->t_ic = &sc->sc_ic;
+				t->t_cmd = BWI_TASK_TX;
+				workqueue_enqueue(sc->sc_taskq, &t->t_work,
+				    NULL);
+			}
+		}
+		mutex_exit(&sc->sc_pio_txlock);
+	}
 }
 
 static void
@@ -7549,6 +7663,8 @@ bwi_stop(struct ifnet *ifp, int state_ch
 		KASSERT(sc->sc_cur_regwin->rw_type == BWI_REGWIN_T_MAC);
 		mac = (struct bwi_mac *)sc->sc_cur_regwin;
 
+		ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE);
+
 		bwi_disable_intrs(sc, BWI_ALL_INTRS);
 		CSR_READ_4(sc, BWI_MAC_INTR_MASK);
 		bwi_mac_stop(mac);
@@ -7668,16 +7784,178 @@ back:
 }
 
 static int
+bwi_newstate_sdio(struct ieee80211com *ic, enum ieee80211_state nstate, int arg)
+{
+	struct bwi_softc *sc = ic->ic_ifp->if_softc;
+	struct bwi_task *t;
+
+	t = pool_cache_get(sc->sc_freetask, PR_NOWAIT);
+	if (t == NULL) {
+		device_printf(sc->sc_dev, "no free tasks\n");
+		return EIO;
+	}
+
+	t->t_ic = ic;
+	t->t_cmd = BWI_TASK_NEWSTATE;
+	t->t_newstate.state = nstate;
+	t->t_newstate.arg = arg;
+	workqueue_enqueue(sc->sc_taskq, &t->t_work, NULL);
+
+	return 0;
+}
+
+static inline bool
+bwi_tx_fifo_pkt_valid(struct bwi_softc *sc, u_int len)
+{
+	return len >= sizeof(uint32_t) && len <= sc->sc_pio_fifolen;
+}
+
+static inline bool
+bwi_tx_fifo_avail(struct bwi_softc *sc, u_int len)
+{
+	return len <= sc->sc_pio_fifoavail;
+}
+
+static void
+bwi_tx_frame_data_pio(struct bwi_softc *sc, u_int idx, uint32_t *ctl,
+    uint8_t *buf, u_int buflen)
+{
+	size_t count;
+
+	*ctl |= BWI_PIO_TXCTL_VALID_BYTES(4);
+	CSR_WRITE_4(sc, BWI_PIO_TXCTL(idx), *ctl);
+
+	count = buflen / sizeof(uint32_t);
+	CSR_WRITE_MULTI_4(sc, BWI_PIO_TXDATA(idx), (uint32_t *)buf, count);
+	buf += count * sizeof(uint32_t);
+	buflen -= count * sizeof(uint32_t);
+	if (buflen != 0) {
+		uint32_t data = 0;
+
+		*ctl &= ~BWI_PIO_TXCTL_VALID;
+		*ctl |= BWI_PIO_TXCTL_VALID_BYTES(buflen);
+		CSR_WRITE_4(sc, BWI_PIO_TXCTL(idx), *ctl);
+
+		memcpy(&data, buf, buflen);
+		CSR_WRITE_MULTI_4(sc, BWI_PIO_TXDATA(idx), &data, 1);
+	}
+}
+
+static void
+bwi_tx_frame_pio(struct bwi_softc *sc, struct bwi_txbuf *tb)
+{
+	struct mbuf *m = tb->tb_mbuf;
+	const u_int idx = BWI_TX_DATA_RING;
+	uint32_t ctl;
+	uint8_t *txbuf = sc->sc_pio_databuf;
+	u_int pktlen = m_length(m);
+
+	m_copydata(m, 0, pktlen, txbuf);
+
+	ctl = CSR_READ_4(sc, BWI_PIO_TXCTL(idx));
+	ctl |= BWI_PIO_TXCTL_FREADY;
+	ctl &= ~BWI_PIO_TXCTL_EOF;
+
+	bwi_tx_frame_data_pio(sc, idx, &ctl, txbuf, pktlen);
+
+	ctl |= BWI_PIO_TXCTL_EOF;
+	CSR_WRITE_4(sc, BWI_PIO_TXCTL(idx), ctl);
+}
+
+static void
+bwi_tx_pending(struct bwi_softc *sc)
+{
+	struct ifnet *ifp = &sc->sc_if;
+	struct bwi_txbuf *tb;
+
+	mutex_enter(&sc->sc_pio_txlock);
+	while ((tb = STAILQ_FIRST(&sc->sc_pio_txpend)) != NULL) {
+		const u_int pktlen = m_length(tb->tb_mbuf);
+
+		if (!bwi_tx_fifo_pkt_valid(sc, pktlen)) {
+			device_printf(sc->sc_dev,
+			    "dropping large packet (%u bytes)\n", pktlen);
+
+			STAILQ_REMOVE_HEAD(&sc->sc_pio_txpend, tb_entry);
+			if_statinc(ifp, if_oerrors);
+			m_freem(tb->tb_mbuf);
+			tb->tb_mbuf = NULL;
+			continue;
+		}
+
+		if (!bwi_tx_fifo_avail(sc, pktlen)) {
+			break;
+		}
+
+		STAILQ_REMOVE_HEAD(&sc->sc_pio_txpend, tb_entry);
+
+		sc->sc_pio_fifoavail -= roundup(pktlen, 4);
+		mutex_exit(&sc->sc_pio_txlock);
+
+		bwi_tx_frame_pio(sc, tb);
+
+		mutex_enter(&sc->sc_pio_txlock);
+	}
+	mutex_exit(&sc->sc_pio_txlock);
+}
+
+static void
+bwi_task(struct work *wk, void *arg)
+{
+	struct bwi_task *t = (struct bwi_task *)wk;
+	struct ieee80211com *ic = t->t_ic;
+	struct bwi_softc *sc = ic->ic_ifp->if_softc;
+
+	switch (t->t_cmd) {
+	case BWI_TASK_NEWSTATE:
+		bwi_newstate(ic, t->t_newstate.state, t->t_newstate.arg);
+		break;
+	case BWI_TASK_UPDATESLOT:
+		bwi_updateslot(ic->ic_ifp);
+		break;
+	case BWI_TASK_TX:
+		bwi_tx_pending(sc);
+		break;
+	case BWI_TASK_INIT:
+		bwi_init(ic->ic_ifp);
+		break;
+	case BWI_TASK_CALIBRATE:
+		bwi_do_calibrate(sc);
+		break;
+	default:
+		panic("bwi: unknown task command %d", t->t_cmd);
+	}
+
+	pool_cache_put(sc->sc_freetask, t);
+}
+
+static int
 bwi_media_change(struct ifnet *ifp)
 {
+	struct bwi_softc *sc = ifp->if_softc;
 	int error;
 
 	error = ieee80211_media_change(ifp);
 	if (error != ENETRESET)
 		return (error);
 
-	if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) == (IFF_UP | IFF_RUNNING))
-		bwi_init(ifp);
+	if ((ifp->if_flags & (IFF_UP | IFF_RUNNING)) == (IFF_UP | IFF_RUNNING)) {
+		if (BWI_IS_SDIO(sc)) {
+			struct bwi_task *t;
+
+			t = pool_cache_get(sc->sc_freetask, PR_NOWAIT);
+			if (t == NULL) {
+				device_printf(sc->sc_dev, "no free tasks\n");
+				return (ENOBUFS);
+			}
+
+			t->t_ic = &sc->sc_ic;
+			t->t_cmd = BWI_TASK_INIT;
+			workqueue_enqueue(sc->sc_taskq, &t->t_work, NULL);
+		} else {
+			bwi_init(ifp);
+		}
+	}
 
 	return (0);
 }
@@ -7742,6 +8020,64 @@ bwi_node_alloc(struct ieee80211_node_tab
 /* [TRC: XXX amrr end] */
 
 static int
+bwi_pio_alloc(struct bwi_softc *sc)
+{
+	struct bwi_mac *mac = &sc->sc_mac[0];
+	int i, j, has_txstats;
+
+	KASSERT(BWI_IS_PIO(sc));
+
+	if (mac->mac_rev < 8) {
+		aprint_error_dev(sc->sc_dev,
+		    "driver does not support MAC rev %u in PIO mode\n",
+		    mac->mac_rev);
+		return EINVAL;
+	}
+
+	has_txstats = (mac->mac_flags & BWI_MAC_F_HAS_TXSTATS) != 0;
+
+	sc->sc_init_tx_ring = bwi_init_tx_ring_pio;
+	sc->sc_free_tx_ring = bwi_free_tx_ring_pio;
+	sc->sc_init_rx_ring = bwi_init_rx_ring_pio;
+	sc->sc_free_rx_ring = bwi_free_rx_ring_pio;
+	sc->sc_setup_rxdesc = bwi_setup_rx_desc_pio;
+	sc->sc_setup_txdesc = bwi_setup_tx_desc_pio;
+	sc->sc_rxeof = bwi_rxeof_pio;
+	sc->sc_start_tx = bwi_start_tx_pio;
+	if (has_txstats) {
+		sc->sc_init_txstats = bwi_init_txstats_pio;
+		sc->sc_free_txstats = bwi_free_txstats_pio;
+		sc->sc_txeof_status = bwi_txeof_status_pio;
+	}
+
+	mutex_init(&sc->sc_pio_txlock, MUTEX_DEFAULT, IPL_NET);
+	STAILQ_INIT(&sc->sc_pio_txpend);
+
+	sc->sc_pio_fifolen = 2000 - 80;
+	sc->sc_pio_fifoavail = sc->sc_pio_fifolen;
+
+	sc->sc_pio_databuf = kmem_alloc(sc->sc_pio_fifolen, KM_SLEEP);
+
+	for (i = 0; i < BWI_TX_NRING; ++i) {
+		struct bwi_txbuf_data *tbd = &sc->sc_tx_bdata[i];
+		for (j = 0; j < BWI_TX_NDESC; ++j) {
+			tbd->tbd_buf[j].tb_data = tbd;
+		}
+	}
+
+	return 0;
+}
+
+static void
+bwi_pio_free(struct bwi_softc *sc)
+{
+	KASSERT(BWI_IS_PIO(sc));
+
+	kmem_free(sc->sc_pio_databuf, sc->sc_pio_fifolen);
+	mutex_destroy(&sc->sc_pio_txlock);
+}
+
+static int
 bwi_dma_alloc(struct bwi_softc *sc)
 {
 	int error, i, has_txstats;
@@ -7750,6 +8086,8 @@ bwi_dma_alloc(struct bwi_softc *sc)
 	bus_size_t tx_ring_sz, rx_ring_sz, desc_sz = 0;
 	uint32_t txrx_ctrl_step = 0;
 
+	KASSERT(!BWI_IS_PIO(sc));
+
 	has_txstats = 0;
 	for (i = 0; i < sc->sc_nmac; ++i) {
 		if (sc->sc_mac[i].mac_flags & BWI_MAC_F_HAS_TXSTATS) {
@@ -7865,6 +8203,8 @@ bwi_dma_free(struct bwi_softc *sc)
 {
 	int i;
 
+	KASSERT(!BWI_IS_PIO(sc));
+
 	for (i = 0; i < BWI_TX_NRING; ++i)
 		bwi_ring_data_free(&sc->sc_tx_rdata[i], sc);
 
@@ -8140,16 +8480,64 @@ bwi_dma_mbuf_destroy(struct bwi_softc *s
 static void
 bwi_enable_intrs(struct bwi_softc *sc, uint32_t enable_intrs)
 {
+	DPRINTF(sc, BWI_DBG_INTR, "enable_intrs 0x%08x\n", enable_intrs);
 	CSR_SETBITS_4(sc, BWI_MAC_INTR_MASK, enable_intrs);
 }
 
 static void
 bwi_disable_intrs(struct bwi_softc *sc, uint32_t disable_intrs)
 {
+	DPRINTF(sc, BWI_DBG_INTR, "disable_intrs 0x%08x\n", disable_intrs);
 	CSR_CLRBITS_4(sc, BWI_MAC_INTR_MASK, disable_intrs);
 }
 
 static int
+bwi_init_tx_ring_pio(struct bwi_softc *sc, int ring_idx)
+{
+	return (0);
+}
+
+static int
+bwi_init_rx_ring_pio(struct bwi_softc *sc)
+{
+	uint32_t ctrl_base = BWI_TXRX_CTRL_BASE;
+	uint32_t val;
+	int error;
+
+	val = CSR_READ_4(sc, ctrl_base + BWI_RX32_CTRL);
+	val |= BWI_TXRX32_CTRL_ENABLE |
+	       BWI_RX32_CTRL_DIRECT_FIFO;
+	CSR_WRITE_4(sc, ctrl_base + BWI_RX32_CTRL, val);
+
+	error = bwi_newbuf(sc, 0, 1);
+	if (error) {
+		aprint_error_dev(sc->sc_dev,
+		    "can't allocate RX buffer\n");
+		return (error);
+	}
+
+	return (0);
+}
+
+static int
+bwi_init_txstats_pio(struct bwi_softc *sc)
+{
+	return (0);
+}
+
+static void
+bwi_setup_rx_desc_pio(struct bwi_softc *sc, int buf_idx, bus_addr_t paddr,
+    int buf_len)
+{
+}
+
+static void
+bwi_setup_tx_desc_pio(struct bwi_softc *sc, struct bwi_ring_data *rd,
+    int buf_idx, bus_addr_t paddr, int buf_len)
+{
+}
+
+static int
 bwi_init_tx_ring32(struct bwi_softc *sc, int ring_idx)
 {
 	struct bwi_ring_data *rd;
@@ -8320,7 +8708,7 @@ bwi_newbuf(struct bwi_softc *sc, int buf
 	bus_dmamap_t map;
 	bus_addr_t paddr;
 	struct mbuf *m;
-	int error;
+	int error = 0;
 
 	KASSERT(buf_idx < BWI_RX_NDESC);
 
@@ -8343,35 +8731,39 @@ bwi_newbuf(struct bwi_softc *sc, int buf
 	}
 	m->m_len = m->m_pkthdr.len = MCLBYTES;
 
-	/*
-	 * Try to load RX buf into temporary DMA map
-	 */
-	error = bus_dmamap_load_mbuf(sc->sc_dmat, rbd->rbd_tmp_dmap, m,
-	    init ? BUS_DMA_WAITOK : BUS_DMA_NOWAIT);
-	if (error) {
-		m_freem(m);
-
+	if (!BWI_IS_PIO(sc)) {
 		/*
-		 * See the comment above
+		 * Try to load RX buf into temporary DMA map
 		 */
-		if (init)
-			return error;
-		else
-			goto back;
-	}
+		error = bus_dmamap_load_mbuf(sc->sc_dmat, rbd->rbd_tmp_dmap, m,
+		    init ? BUS_DMA_WAITOK : BUS_DMA_NOWAIT);
+		if (error) {
+			m_freem(m);
 
-	if (!init)
-		bus_dmamap_unload(sc->sc_dmat, rxbuf->rb_dmap);
+			/*
+			 * See the comment above
+			 */
+			if (init)
+				return error;
+			else
+				goto back;
+		}
+
+		if (!init)
+			bus_dmamap_unload(sc->sc_dmat, rxbuf->rb_dmap);
+	}
 	rxbuf->rb_mbuf = m;
 
-	/*
-	 * Swap RX buf's DMA map with the loaded temporary one
-	 */
-	map = rxbuf->rb_dmap;
-	rxbuf->rb_dmap = rbd->rbd_tmp_dmap;
-	rbd->rbd_tmp_dmap = map;
-	paddr = rxbuf->rb_dmap->dm_segs[0].ds_addr;
-	rxbuf->rb_paddr = paddr;
+	if (!BWI_IS_PIO(sc)) {
+		/*
+		 * Swap RX buf's DMA map with the loaded temporary one
+		 */
+		map = rxbuf->rb_dmap;
+		rxbuf->rb_dmap = rbd->rbd_tmp_dmap;
+		rbd->rbd_tmp_dmap = map;
+		paddr = rxbuf->rb_dmap->dm_segs[0].ds_addr;
+		rxbuf->rb_paddr = paddr;
+	}
 
 back:
 	/*
@@ -8379,8 +8771,10 @@ back:
 	 */
 	hdr = mtod(rxbuf->rb_mbuf, struct bwi_rxbuf_hdr *);
 	memset(hdr, 0, sizeof(*hdr));
-	bus_dmamap_sync(sc->sc_dmat, rxbuf->rb_dmap, 0,
-	    rxbuf->rb_dmap->dm_mapsize, BUS_DMASYNC_PREWRITE);
+	if (!BWI_IS_PIO(sc)) {
+		bus_dmamap_sync(sc->sc_dmat, rxbuf->rb_dmap, 0,
+		    rxbuf->rb_dmap->dm_mapsize, BUS_DMASYNC_PREWRITE);
+	}
 
 	/*
 	 * Setup RX buf descriptor
@@ -8558,6 +8952,184 @@ next:
 	return (rx_data);
 }
 
+static void
+bwi_rx_frame_data_pio(struct bwi_softc *sc, struct bwi_rxbuf_hdr *hdr, int qid)
+{
+	struct bwi_rxbuf_data *rbd = &sc->sc_rx_bdata;
+	struct bwi_rxbuf *rb = &rbd->rbd_buf[0];
+	struct ieee80211_frame_min *wh;
+	struct ieee80211_node *ni;
+	struct ifnet *ifp = &sc->sc_if;
+	struct ieee80211com *ic = &sc->sc_ic;
+	struct mbuf *m;
+	const void *plcp;
+	uint16_t flags2;
+	int buflen, hdr_extra, rssi, type, rate;
+	int s;
+
+	m = rb->rb_mbuf;
+
+	if (bwi_newbuf(sc, 0, 0)) {
+		device_printf(sc->sc_dev, "couldn't create mbuf\n");
+		if_statinc(ifp, if_ierrors);
+		return;
+	}
+
+	flags2 = le16toh(hdr->rxh_flags2);
+
+	hdr_extra = 0;
+	if (flags2 & BWI_RXH_F2_TYPE2FRAME)
+		hdr_extra = 2;
+
+	buflen = le16toh(hdr->rxh_buflen);
+
+	/* Read the packet data into the mbuf */
+	CSR_READ_MULTI_4(sc, BWI_PIO_RXDATA(qid), mtod(m, uint32_t *),
+	    buflen / sizeof(uint32_t));
+	if (buflen & 0x3) {
+		uint8_t data[4];
+		uint8_t *ppkt;
+		u_int resid;
+		u_int n;
+
+		resid = buflen & 0x3;
+		ppkt = mtod(m, uint8_t *) + (buflen & ~0x3);
+		CSR_READ_MULTI_4(sc, BWI_PIO_RXDATA(qid), (uint32_t *)&data, 1);
+		for (n = 0; n < resid; n++, ppkt++) {
+			*ppkt = data[n];
+		}
+	}
+
+	plcp = mtod(m, uint8_t *) + hdr_extra;
+	rssi = bwi_calc_rssi(sc, hdr);
+
+	m_set_rcvif(m, ifp);
+	m->m_len = m->m_pkthdr.len = buflen + hdr_extra;
+	m_adj(m, hdr_extra + 6);
+
+	if (htole16(hdr->rxh_flags1) & BWI_RXH_F1_OFDM)
+		rate = bwi_ofdm_plcp2rate(plcp);
+	else
+		rate = bwi_ds_plcp2rate(plcp);
+
+	s = splnet();
+
+	/* RX radio tap */
+	if (sc->sc_drvbpf != NULL) {
+		struct mbuf mb;
+		struct bwi_rx_radiotap_hdr *tap = &sc->sc_rxtap;
+
+		tap->wr_tsf = hdr->rxh_tsf;
+		tap->wr_flags = 0;
+		tap->wr_rate = rate;
+		tap->wr_chan_freq =
+		    htole16(ic->ic_bss->ni_chan->ic_freq);
+		tap->wr_chan_flags =
+		    htole16(ic->ic_bss->ni_chan->ic_flags);
+		tap->wr_antsignal = rssi;
+		tap->wr_antnoise = BWI_NOISE_FLOOR;
+
+		mb.m_data = (void *)tap;
+		mb.m_len = sc->sc_rxtap_len;
+		mb.m_next = m;
+		mb.m_nextpkt = NULL;
+		mb.m_owner = NULL;
+		mb.m_type = 0;
+		mb.m_flags = 0;
+		bpf_mtap3(sc->sc_drvbpf, &mb, BPF_D_IN);
+	}
+
+	m_adj(m, -IEEE80211_CRC_LEN);
+
+	wh = mtod(m, struct ieee80211_frame_min *);
+	ni = ieee80211_find_rxnode(ic, wh);
+	type = wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK;
+
+	ieee80211_input(ic, m, ni, hdr->rxh_rssi,
+	    le16toh(hdr->rxh_tsf));
+
+	ieee80211_free_node(ni);
+
+	if (type == IEEE80211_FC0_TYPE_DATA) {
+		sc->sc_rx_rate = rate;
+	}
+
+	splx(s);
+}
+
+static int
+bwi_rx_frame_pio(struct bwi_softc *sc)
+{
+	struct bwi_rxbuf_hdr rxh;
+	struct ifnet *ifp = &sc->sc_if;
+	const u_int qid = 0;
+	const size_t pio_hdrlen = 20;
+	uint32_t val;
+	uint16_t pktlen;
+	uint16_t flags2;
+	u_int n;
+
+	/* Check for frame ready bit and acknowledge it */
+	val = CSR_READ_4(sc, BWI_PIO_RXCTL(qid));
+	if ((val & BWI_PIO_RXCTL_FRAMERDY) == 0) {
+		return 0;
+	}
+	CSR_WRITE_4(sc, BWI_PIO_RXCTL(qid), BWI_PIO_RXCTL_FRAMERDY);
+
+	/* Wait for up to 100us for data ready */
+	for (n = 10; n > 0; n--) {
+		val = CSR_READ_4(sc, BWI_PIO_RXCTL(qid));
+		if ((val & BWI_PIO_RXCTL_DATARDY) != 0) {
+			break;
+		}
+		delay(10);
+	}
+	if (n == 0) {
+		device_printf(sc->sc_dev, "RX timeout\n");
+		if_statinc(ifp, if_ierrors);
+		goto ack;
+	}
+
+	/* Read 20 bytes of the packet RX header from the FIFO... */
+	CSR_READ_MULTI_4(sc, BWI_PIO_RXDATA(qid), (uint32_t *)&rxh,
+	    pio_hdrlen / sizeof(uint32_t));
+	/* ... and zero the rest of it. */
+	memset(((uint8_t *)&rxh) + pio_hdrlen, 0, sizeof(rxh) - pio_hdrlen);
+
+	/* Validate packet */
+	pktlen = le16toh(rxh.rxh_buflen);
+	if (pktlen > 0x700 || pktlen == 0) {
+		device_printf(sc->sc_dev, "RX error (length %#x)\n", pktlen);
+		if_statinc(ifp, if_ierrors);
+		goto ack;
+	}
+	flags2 = le16toh(rxh.rxh_flags2);
+	if ((flags2 & BWI_RXH_F2_INVALID) != 0) {
+		device_printf(sc->sc_dev, "RX frame invalid\n");
+		if_statinc(ifp, if_ierrors);
+		goto ack;
+	}
+
+	/* Process frame data */
+	bwi_rx_frame_data_pio(sc, &rxh, qid);
+
+ack:
+	CSR_WRITE_4(sc, BWI_PIO_RXCTL(qid), BWI_PIO_RXCTL_DATARDY);
+	return 1;
+}
+
+static int
+bwi_rxeof_pio(struct bwi_softc *sc)
+{
+	unsigned npkt = 0;
+
+	while (bwi_rx_frame_pio(sc) && npkt++ < 1000) {
+		preempt_point();
+	}
+
+	return (npkt != 0);
+}
+
 static int
 bwi_rxeof32(struct bwi_softc *sc)
 {
@@ -8611,12 +9183,28 @@ bwi_reset_rx_ring32(struct bwi_softc *sc
 }
 
 static void
+bwi_free_txstats_pio(struct bwi_softc *sc)
+{
+}
+
+static void
 bwi_free_txstats32(struct bwi_softc *sc)
 {
 	bwi_reset_rx_ring32(sc, sc->sc_txstats->stats_ctrl_base);
 }
 
 static void
+bwi_free_rx_ring_pio(struct bwi_softc *sc)
+{
+}
+
+static void
+bwi_free_tx_ring_pio(struct bwi_softc *sc, int ring_idx)
+{
+	bwi_free_tx_ring32(sc, ring_idx);
+}
+
+static void
 bwi_free_rx_ring32(struct bwi_softc *sc)
 {
 	struct bwi_ring_data *rd = &sc->sc_rx_rdata;
@@ -8685,7 +9273,9 @@ bwi_free_tx_ring32(struct bwi_softc *sc,
 		struct bwi_txbuf *tb = &tbd->tbd_buf[i];
 
 		if (tb->tb_mbuf != NULL) {
-			bus_dmamap_unload(sc->sc_dmat, tb->tb_dmap);
+			if (!BWI_IS_PIO(sc)) {
+				bus_dmamap_unload(sc->sc_dmat, tb->tb_dmap);
+			}
 			m_freem(tb->tb_mbuf);
 			tb->tb_mbuf = NULL;
 		}
@@ -9165,18 +9755,24 @@ bwi_encap(struct bwi_softc *sc, int idx,
 	hdr = NULL;
 	wh = NULL;
 
-	/* DMA load */
-	error = bus_dmamap_load_mbuf(sc->sc_dmat, tb->tb_dmap, m,
-	    BUS_DMA_NOWAIT);
-	if (error && error != EFBIG) {
-		aprint_error_dev(sc->sc_dev, "can't load TX buffer (1) %d\n",
-		    error);
-		goto back;
+	if (BWI_IS_PIO(sc)) {
+		error = 0;
+	} else {
+		/* DMA load */
+		error = bus_dmamap_load_mbuf(sc->sc_dmat, tb->tb_dmap, m,
+		    BUS_DMA_NOWAIT);
+		if (error && error != EFBIG) {
+			aprint_error_dev(sc->sc_dev,
+			    "can't load TX buffer (1) %d\n", error);
+			goto back;
+		}
 	}
 
 	if (error) {	/* error == EFBIG */
 		struct mbuf *m_new;
 
+		KASSERT(!BWI_IS_PIO(sc));
+
 		error = 0;
 
 		MGETHDR(m_new, M_DONTWAIT, MT_DATA);
@@ -9217,8 +9813,10 @@ bwi_encap(struct bwi_softc *sc, int idx,
 	}
 	error = 0;
 
-	bus_dmamap_sync(sc->sc_dmat, tb->tb_dmap, 0,
-	    tb->tb_dmap->dm_mapsize, BUS_DMASYNC_PREWRITE);
+	if (!BWI_IS_PIO(sc)) {
+		bus_dmamap_sync(sc->sc_dmat, tb->tb_dmap, 0,
+		    tb->tb_dmap->dm_mapsize, BUS_DMASYNC_PREWRITE);
+	}
 
 	if (mgt_pkt || mcast_pkt) {
 		/* Don't involve mcast/mgt packets into TX rate control */
@@ -9245,11 +9843,13 @@ bwi_encap(struct bwi_softc *sc, int idx,
 	DPRINTF(sc, BWI_DBG_TX, "idx %d, pkt_len %d, buflen %d\n",
 	    idx, pkt_len, m->m_pkthdr.len);
 
-	/* Setup TX descriptor */
-	paddr = tb->tb_dmap->dm_segs[0].ds_addr;
-	(sc->sc_setup_txdesc)(sc, rd, idx, paddr, m->m_pkthdr.len);
-	bus_dmamap_sync(sc->sc_dmat, rd->rdata_dmap, 0,
-	    rd->rdata_dmap->dm_mapsize, BUS_DMASYNC_PREWRITE);
+	if (!BWI_IS_PIO(sc)) {
+		/* Setup TX descriptor */
+		paddr = tb->tb_dmap->dm_segs[0].ds_addr;
+		(sc->sc_setup_txdesc)(sc, rd, idx, paddr, m->m_pkthdr.len);
+		bus_dmamap_sync(sc->sc_dmat, rd->rdata_dmap, 0,
+		    rd->rdata_dmap->dm_mapsize, BUS_DMASYNC_PREWRITE);
+	}
 
 	/* Kick start */
 	(sc->sc_start_tx)(sc, rd->rdata_txrx_ctrl, idx);
@@ -9261,6 +9861,17 @@ back:
 }
 
 static void
+bwi_start_tx_pio(struct bwi_softc *sc, uint32_t tx_ctrl, int idx)
+{
+	struct bwi_txbuf_data *tbd = &sc->sc_tx_bdata[BWI_TX_DATA_RING];
+	struct bwi_txbuf *tb = &tbd->tbd_buf[idx];
+
+	mutex_enter(&sc->sc_pio_txlock);
+	STAILQ_INSERT_TAIL(&sc->sc_pio_txpend, tb, tb_entry);
+	mutex_exit(&sc->sc_pio_txlock);
+}
+
+static void
 bwi_start_tx32(struct bwi_softc *sc, uint32_t tx_ctrl, int idx)
 {
 	idx = (idx + 1) % BWI_TX_NDESC;
@@ -9275,6 +9886,12 @@ bwi_start_tx64(struct bwi_softc *sc, uin
 }
 
 static void
+bwi_txeof_status_pio(struct bwi_softc *sc)
+{
+	/* TODO: PIO */
+}
+
+static void
 bwi_txeof_status32(struct bwi_softc *sc)
 {
 	struct ifnet *ifp = &sc->sc_if;
@@ -9331,7 +9948,15 @@ _bwi_txeof(struct bwi_softc *sc, uint16_
 
 	tb = &tbd->tbd_buf[buf_idx];
 
-	bus_dmamap_unload(sc->sc_dmat, tb->tb_dmap);
+	if (BWI_IS_PIO(sc)) {
+		mutex_enter(&sc->sc_pio_txlock);
+		const u_int pktlen = m_length(tb->tb_mbuf);
+		sc->sc_pio_fifoavail += roundup(pktlen, sizeof(uint32_t));
+		KASSERT(sc->sc_pio_fifoavail <= sc->sc_pio_fifolen);
+		mutex_exit(&sc->sc_pio_txlock);
+	} else {
+		bus_dmamap_unload(sc->sc_dmat, tb->tb_dmap);
+	}
 	m_freem(tb->tb_mbuf);
 	tb->tb_mbuf = NULL;
 
@@ -9352,8 +9977,10 @@ bwi_txeof_status(struct bwi_softc *sc, i
 	struct bwi_txstats_data *st = sc->sc_txstats;
 	int idx;
 
-	bus_dmamap_sync(sc->sc_dmat, st->stats_dmap, 0,
-	    st->stats_dmap->dm_mapsize, BUS_DMASYNC_POSTREAD);
+	if (!BWI_IS_PIO(sc)) {
+		bus_dmamap_sync(sc->sc_dmat, st->stats_dmap, 0,
+		    st->stats_dmap->dm_mapsize, BUS_DMASYNC_POSTREAD);
+	}
 
 	idx = st->stats_idx;
 	while (idx != end_idx) {
@@ -9693,6 +10320,41 @@ bwi_updateslot(struct ifnet *ifp)
 }
 
 static void
+bwi_updateslot_sdio(struct ifnet *ifp)
+{
+	struct bwi_softc *sc = ifp->if_softc;
+	struct bwi_task *t;
+
+	t = pool_cache_get(sc->sc_freetask, PR_NOWAIT);
+	if (t == NULL) {
+		device_printf(sc->sc_dev, "no free tasks\n");
+		return;
+	}
+
+	t->t_ic = &sc->sc_ic;
+	t->t_cmd = BWI_TASK_UPDATESLOT;
+	workqueue_enqueue(sc->sc_taskq, &t->t_work, NULL);
+}
+
+static void
+bwi_do_calibrate(struct bwi_softc *sc)
+{
+	struct bwi_mac *mac;
+	struct ieee80211com *ic = &sc->sc_ic;
+
+	KASSERT(sc->sc_cur_regwin->rw_type == BWI_REGWIN_T_MAC);
+	mac = (struct bwi_mac *)sc->sc_cur_regwin;
+
+	if (ic->ic_opmode != IEEE80211_M_MONITOR) {
+		bwi_mac_calibrate_txpower(mac, sc->sc_txpwrcb_type);
+		sc->sc_txpwrcb_type = BWI_TXPWR_CALIB;
+	}
+
+	/* XXX 15 seconds */
+	callout_schedule(&sc->sc_calib_ch, hz * 15);
+}
+
+static void
 bwi_calibrate(void *xsc)
 {
 	struct bwi_softc *sc = xsc;
@@ -9702,18 +10364,22 @@ bwi_calibrate(void *xsc)
 	s = splnet();
 
 	if (ic->ic_state == IEEE80211_S_RUN) {
-		struct bwi_mac *mac;
-
-		KASSERT(sc->sc_cur_regwin->rw_type == BWI_REGWIN_T_MAC);
-		mac = (struct bwi_mac *)sc->sc_cur_regwin;
+		if (BWI_IS_SDIO(sc)) {
+			struct bwi_task *t;
 
-		if (ic->ic_opmode != IEEE80211_M_MONITOR) {
-			bwi_mac_calibrate_txpower(mac, sc->sc_txpwrcb_type);
-			sc->sc_txpwrcb_type = BWI_TXPWR_CALIB;
+			t = pool_cache_get(sc->sc_freetask, PR_NOWAIT);
+			if (t == NULL) {
+				device_printf(sc->sc_dev, "no free tasks\n");
+				callout_schedule(&sc->sc_calib_ch, hz * 15);
+			} else {
+				t->t_ic = &sc->sc_ic;
+				t->t_cmd = BWI_TASK_CALIBRATE;
+				workqueue_enqueue(sc->sc_taskq, &t->t_work,
+				    NULL);
+			}
+		} else {
+			bwi_do_calibrate(sc);
 		}
-
-		/* XXX 15 seconds */
-		callout_schedule(&sc->sc_calib_ch, hz * 15);
 	}
 
 	splx(s);

Index: src/sys/dev/ic/bwireg.h
diff -u src/sys/dev/ic/bwireg.h:1.4 src/sys/dev/ic/bwireg.h:1.4.20.1
--- src/sys/dev/ic/bwireg.h:1.4	Mon May 18 05:47:54 2020
+++ src/sys/dev/ic/bwireg.h	Sun Feb  2 14:29:59 2025
@@ -1,4 +1,4 @@
-/*	$NetBSD: bwireg.h,v 1.4 2020/05/18 05:47:54 msaitoh Exp $	*/
+/*	$NetBSD: bwireg.h,v 1.4.20.1 2025/02/02 14:29:59 martin Exp $	*/
 /*	$OpenBSD: bwireg.h,v 1.7 2007/11/17 16:50:02 mglocker Exp $	*/
 
 /*
@@ -150,6 +150,18 @@
 #define BWI_TXRX_INTR_STATUS(i)		(BWI_TXRX_INTR_STATUS_BASE + ((i) * 8))
 #define BWI_TXRX_INTR_MASK(i)		(BWI_TXRX_INTR_MASK_BASE + ((i) * 8))
 
+#define BWI_PIO_TXCTL(qid)		(0x0000300 + (qid) * 0x10 + 0x0)
+#define BWI_PIO_TXCTL_VALID		(0xf << 0)
+#define BWI_PIO_TXCTL_VALID_BYTES(n)	((1 << (n)) - 1)
+#define BWI_PIO_TXCTL_EOF		(1 << 4)
+#define BWI_PIO_TXCTL_FREADY		(1 << 7)
+#define BWI_PIO_TXDATA(qid)		(0x0000300 + (qid) * 0x10 + 0x4)
+
+#define BWI_PIO_RXCTL(qid)		(0x0000300 + (qid) * 0x10 + 0x8)
+#define BWI_PIO_RXCTL_FRAMERDY		(1 << 0)
+#define BWI_PIO_RXCTL_DATARDY		(1 << 1)
+#define BWI_PIO_RXDATA(qid)		(0x0000300 + (qid) * 0x10 + 0xc)
+
 #define BWI_MAC_STATUS			0x00000120
 #define BWI_MAC_STATUS_ENABLE		(1 << 0)
 #define BWI_MAC_STATUS_UCODE_START	(1 << 1)
@@ -196,6 +208,7 @@
 #define BWI_COMM_MOBJ_SLOTTIME		0x10
 #define BWI_COMM_MOBJ_MACREV		0x16
 #define BWI_COMM_MOBJ_TX_ACK		0x22
+#define BWI_COMM_MOBJ_RXPADOFF		0x34
 #define BWI_COMM_MOBJ_UCODE_STATE	0x40
 #define BWI_COMM_MOBJ_SHRETRY_FB	0x44
 #define BWI_COMM_MOBJ_LGRETEY_FB	0x46
@@ -252,6 +265,7 @@
 #define BWI_TX32_STATUS_STATE_STOPPED	3
 #define BWI_RX32_CTRL			0x10
 #define BWI_RX32_CTRL_HDRSZ_MASK	0x00fe
+#define BWI_RX32_CTRL_DIRECT_FIFO	0x0100
 #define BWI_RX32_RINGINFO		0x14
 #define BWI_RX32_INDEX			0x18
 #define BWI_RX32_STATUS			0x1c

Index: src/sys/dev/ic/bwivar.h
diff -u src/sys/dev/ic/bwivar.h:1.10 src/sys/dev/ic/bwivar.h:1.10.46.1
--- src/sys/dev/ic/bwivar.h:1.10	Thu Feb  2 10:05:35 2017
+++ src/sys/dev/ic/bwivar.h	Sun Feb  2 14:29:59 2025
@@ -1,4 +1,4 @@
-/*	$NetBSD: bwivar.h,v 1.10 2017/02/02 10:05:35 nonaka Exp $	*/
+/*	$NetBSD: bwivar.h,v 1.10.46.1 2025/02/02 14:29:59 martin Exp $	*/
 /*	$OpenBSD: bwivar.h,v 1.23 2008/02/25 20:36:54 mglocker Exp $	*/
 
 /*
@@ -78,14 +78,18 @@ enum bwi_txpwrcb_type {
 	((hdr) + sizeof(struct ieee80211_frame_ack) + IEEE80211_CRC_LEN)
 
 #define CSR_READ_4(sc, reg)			\
-	bus_space_read_4((sc)->sc_mem_bt, (sc)->sc_mem_bh, (reg))
+	_bwi_read_4(sc, reg)
 #define CSR_READ_2(sc, reg)			\
-	bus_space_read_2((sc)->sc_mem_bt, (sc)->sc_mem_bh, (reg))
+	_bwi_read_2(sc, reg)
+#define CSR_READ_MULTI_4(sc, reg, datap, count)	\
+	_bwi_read_multi_4(sc, reg, datap, count)
 
 #define CSR_WRITE_4(sc, reg, val)		\
-	bus_space_write_4((sc)->sc_mem_bt, (sc)->sc_mem_bh, (reg), (val))
+	_bwi_write_4(sc, reg, val)
 #define CSR_WRITE_2(sc, reg, val)		\
-	bus_space_write_2((sc)->sc_mem_bt, (sc)->sc_mem_bh, (reg), (val))
+	_bwi_write_2(sc, reg, val)
+#define CSR_WRITE_MULTI_4(sc, reg, datap, count) \
+	_bwi_write_multi_4(sc, reg, datap, count)
 
 #define CSR_SETBITS_4(sc, reg, bits)		\
 	CSR_WRITE_4((sc), (reg), CSR_READ_4((sc), (reg)) | (bits))
@@ -102,6 +106,9 @@ enum bwi_txpwrcb_type {
 #define CSR_CLRBITS_2(sc, reg, bits)		\
 	CSR_WRITE_2((sc), (reg), CSR_READ_2((sc), (reg)) & ~(bits))
 
+struct pool_cache;
+struct workqueue;
+
 struct bwi_desc32 {
 	/* Little endian */
 	uint32_t	ctrl;
@@ -145,6 +152,7 @@ struct bwi_rxbuf_hdr {
 #define BWI_RXH_F1_OFDM		(1 << 0)
 
 #define BWI_RXH_F2_TYPE2FRAME	(1 << 2)
+#define BWI_RXH_F2_INVALID	(1 << 0)
 
 #define BWI_RXH_F3_BCM2050_RSSI	(1 << 10)
 
@@ -207,12 +215,17 @@ struct bwi_ring_data {
 	void			*rdata_desc;
 };
 
+struct bwi_txbuf_data;
+
 struct bwi_txbuf {
 	struct mbuf		*tb_mbuf;
 	bus_dmamap_t		 tb_dmap;
 
 	struct ieee80211_node	*tb_ni;
 	int			 tb_rate_idx[2];
+
+	struct bwi_txbuf_data	*tb_data;
+	STAILQ_ENTRY(bwi_txbuf)	 tb_entry;
 };
 
 struct bwi_txbuf_data {
@@ -522,6 +535,28 @@ struct bwi_node {
 	struct ieee80211_amrr_node	amn;
 };
 
+enum bwi_task_cmd {
+	BWI_TASK_NEWSTATE,
+	BWI_TASK_UPDATESLOT,
+	BWI_TASK_TX,
+	BWI_TASK_INIT,
+	BWI_TASK_CALIBRATE,
+};
+
+#define BWI_TASK_COUNT		64
+
+struct bwi_task {
+	struct work		 t_work;
+	struct ieee80211com	*t_ic;
+	enum bwi_task_cmd	 t_cmd;
+	union {
+		struct {
+			enum ieee80211_state state;
+			int	 arg;
+		} t_newstate;
+	};
+};
+
 struct bwi_softc {
 	device_t		 sc_dev;
 	struct ethercom		 sc_ec;
@@ -627,6 +662,24 @@ struct bwi_softc {
 	void			 (*sc_conf_write)(void *, uint32_t, uint32_t);
 	uint32_t		 (*sc_conf_read)(void *, uint32_t);
 
+	void			 (*sc_reg_write_2)(void *, uint32_t, uint16_t);
+	uint16_t		 (*sc_reg_read_2)(void *, uint32_t);
+	void			 (*sc_reg_write_4)(void *, uint32_t, uint32_t);
+	uint32_t		 (*sc_reg_read_4)(void *, uint32_t);
+
+	void			 (*sc_reg_write_multi_4)(void *, uint32_t,
+				     const uint32_t *, size_t);
+	void			 (*sc_reg_read_multi_4)(void *, uint32_t,
+				     uint32_t *, size_t);
+
+	struct pool_cache	*sc_freetask;
+	struct workqueue	*sc_taskq;
+	uint8_t			*sc_pio_databuf;
+	kmutex_t		 sc_pio_txlock;
+	STAILQ_HEAD(, bwi_txbuf) sc_pio_txpend;
+	size_t			 sc_pio_fifolen;
+	size_t			 sc_pio_fifoavail;
+
 	struct sysctllog	*sc_sysctllog;
 
 	/* Sysctl variables */
@@ -654,8 +707,77 @@ struct bwi_softc {
 	int			 sc_txtap_len;
 };
 
+static inline void
+_bwi_read_multi_4(struct bwi_softc *sc, bus_size_t reg, uint32_t *datap,
+    bus_size_t count)
+{
+	if (sc->sc_reg_read_multi_4 != NULL) {
+		return sc->sc_reg_read_multi_4(sc, reg, datap, count);
+	} else {
+		return bus_space_read_multi_4(sc->sc_mem_bt, sc->sc_mem_bh,
+		    reg, datap, count);
+	}
+}
+
+static inline uint16_t
+_bwi_read_2(struct bwi_softc *sc, bus_size_t reg)
+{
+	if (sc->sc_reg_read_2 != NULL) {
+		return sc->sc_reg_read_2(sc, reg);
+	} else {
+		return bus_space_read_2(sc->sc_mem_bt, sc->sc_mem_bh, reg);
+	}
+}
+
+static inline uint32_t
+_bwi_read_4(struct bwi_softc *sc, bus_size_t reg)
+{
+	if (sc->sc_reg_read_4 != NULL) {
+		return sc->sc_reg_read_4(sc, reg);
+	} else {
+		return bus_space_read_4(sc->sc_mem_bt, sc->sc_mem_bh, reg);
+	}
+}
+
+static inline void
+_bwi_write_multi_4(struct bwi_softc *sc, bus_size_t reg, const uint32_t *datap,
+    bus_size_t count)
+{
+	if (sc->sc_reg_read_multi_4 != NULL) {
+		return sc->sc_reg_write_multi_4(sc, reg, datap, count);
+	} else {
+		return bus_space_write_multi_4(sc->sc_mem_bt, sc->sc_mem_bh,
+		    reg, datap, count);
+	}
+}
+
+static inline void
+_bwi_write_2(struct bwi_softc *sc, bus_size_t reg, uint16_t val)
+{
+	if (sc->sc_reg_write_2 != NULL) {
+		sc->sc_reg_write_2(sc, reg, val);
+	} else {
+		bus_space_write_2(sc->sc_mem_bt, sc->sc_mem_bh, reg, val);
+	}
+}
+
+static inline void
+_bwi_write_4(struct bwi_softc *sc, bus_size_t reg, uint32_t val)
+{
+	if (sc->sc_reg_write_4 != NULL) {
+		sc->sc_reg_write_4(sc, reg, val);
+	} else {
+		bus_space_write_4(sc->sc_mem_bt, sc->sc_mem_bh, reg, val);
+	}
+}
+
 #define BWI_F_BUS_INITED	0x1
 #define BWI_F_PROMISC		0x2
+#define BWI_F_SDIO		0x4
+#define BWI_F_PIO		0x8
+
+#define BWI_IS_SDIO(sc)		ISSET((sc)->sc_flags, BWI_F_SDIO)
+#define BWI_IS_PIO(sc)		ISSET((sc)->sc_flags, BWI_F_PIO)
 
 #define BWI_DBG_MAC		0x00000001
 #define BWI_DBG_RF		0x00000002

Index: src/sys/dev/sdmmc/files.sdmmc
diff -u src/sys/dev/sdmmc/files.sdmmc:1.5 src/sys/dev/sdmmc/files.sdmmc:1.5.36.1
--- src/sys/dev/sdmmc/files.sdmmc:1.5	Tue Nov  7 16:30:32 2017
+++ src/sys/dev/sdmmc/files.sdmmc	Sun Feb  2 14:29:59 2025
@@ -1,4 +1,4 @@
-#      $NetBSD: files.sdmmc,v 1.5 2017/11/07 16:30:32 khorben Exp $
+#      $NetBSD: files.sdmmc,v 1.5.36.1 2025/02/02 14:29:59 martin Exp $
 #      $OpenBSD: files.sdmmc,v 1.2 2006/06/01 21:53:41 uwe Exp $
 #
 # Config file and device description for machine-independent SD/MMC code.
@@ -25,3 +25,7 @@ file	dev/sdmmc/sbt.c			sbt
 # Broadcom FullMAC SDIO wireless adapter
 attach bwfm at sdmmc with bwfm_sdio
 file	dev/sdmmc/if_bwfm_sdio.c	bwfm_sdio
+
+# Broadcom SoftMAC SDIO wireless driver
+attach bwi at sdmmc with bwi_sdio
+file	dev/sdmmc/if_bwi_sdio.c		bwi_sdio

Index: src/sys/dev/sdmmc/sdmmc_cis.c
diff -u src/sys/dev/sdmmc/sdmmc_cis.c:1.8.26.1 src/sys/dev/sdmmc/sdmmc_cis.c:1.8.26.2
--- src/sys/dev/sdmmc/sdmmc_cis.c:1.8.26.1	Mon Oct 14 17:47:00 2024
+++ src/sys/dev/sdmmc/sdmmc_cis.c	Sun Feb  2 14:29:59 2025
@@ -1,4 +1,4 @@
-/*	$NetBSD: sdmmc_cis.c,v 1.8.26.1 2024/10/14 17:47:00 martin Exp $	*/
+/*	$NetBSD: sdmmc_cis.c,v 1.8.26.2 2025/02/02 14:29:59 martin Exp $	*/
 /*	$OpenBSD: sdmmc_cis.c,v 1.1 2006/06/01 21:53:41 uwe Exp $	*/
 
 /*
@@ -20,7 +20,7 @@
 /* Routines to decode the Card Information Structure of SD I/O cards */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: sdmmc_cis.c,v 1.8.26.1 2024/10/14 17:47:00 martin Exp $");
+__KERNEL_RCSID(0, "$NetBSD: sdmmc_cis.c,v 1.8.26.2 2025/02/02 14:29:59 martin Exp $");
 
 #ifdef _KERNEL_OPT
 #include "opt_sdmmc.h"
@@ -41,7 +41,6 @@ __KERNEL_RCSID(0, "$NetBSD: sdmmc_cis.c,
 #define DPRINTF(s)	/**/
 #endif
 
-static uint32_t sdmmc_cisptr(struct sdmmc_function *);
 static void decode_funce_common(struct sdmmc_function *, struct sdmmc_cis *,
 				int, uint32_t);
 static void decode_funce_function(struct sdmmc_function *, struct sdmmc_cis *,
@@ -49,7 +48,7 @@ static void decode_funce_function(struct
 static void decode_vers_1(struct sdmmc_function *, struct sdmmc_cis *, int,
 			  uint32_t);
 
-static uint32_t
+uint32_t
 sdmmc_cisptr(struct sdmmc_function *sf)
 {
 	uint32_t cisptr = 0;
@@ -108,7 +107,6 @@ decode_funce_lan_nid(struct sdmmc_functi
 {
 	struct sdmmc_function *sf0 = sf->sc->sc_fn0;
 	device_t dev = sf->sc->sc_dev;
-	uint8_t mac[6] __unused;
 	int i;
 
 	if (tpllen != 8) {
@@ -117,13 +115,19 @@ decode_funce_lan_nid(struct sdmmc_functi
 		return;
 	}
 
+	if (sdmmc_io_read_1(sf0, reg++) != 6) {
+		aprint_error_dev(dev,
+		    "CISTPL_FUNCE(lan_nid) invalid\n");
+	}
+
 	for (i = 0; i < 6; i++) {
-		mac[i] = sdmmc_io_read_1(sf0, reg++);
+		cis->lan_nid[i] = sdmmc_io_read_1(sf0, reg++);
 	}
 
 	DPRINTF(
 	    ("CISTPL_FUNCE: LAN_NID=%02x:%02x:%02x:%02x:%02x:%02x\n",
-	    mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]));
+	    cis->lan_nid[0], cis->lan_nid[1], cis->lan_nid[2],
+	    cis->lan_nid[3], cis->lan_nid[4], cis->lan_nid[5]));
 }
 
 static void

Index: src/sys/dev/sdmmc/sdmmcvar.h
diff -u src/sys/dev/sdmmc/sdmmcvar.h:1.36.18.1 src/sys/dev/sdmmc/sdmmcvar.h:1.36.18.2
--- src/sys/dev/sdmmc/sdmmcvar.h:1.36.18.1	Sat Oct 26 15:26:43 2024
+++ src/sys/dev/sdmmc/sdmmcvar.h	Sun Feb  2 14:29:59 2025
@@ -1,4 +1,4 @@
-/*	$NetBSD: sdmmcvar.h,v 1.36.18.1 2024/10/26 15:26:43 martin Exp $	*/
+/*	$NetBSD: sdmmcvar.h,v 1.36.18.2 2025/02/02 14:29:59 martin Exp $	*/
 /*	$OpenBSD: sdmmcvar.h,v 1.13 2009/01/09 10:55:22 jsg Exp $	*/
 
 /*
@@ -187,6 +187,7 @@ struct sdmmc_cis {
 	u_char		 cis1_minor;
 	char		 cis1_info_buf[256];
 	char		*cis1_info[4];
+	uint8_t		 lan_nid[6];
 };
 
 /*
@@ -393,6 +394,7 @@ int	sdmmc_io_function_enable(struct sdmm
 void	sdmmc_io_function_disable(struct sdmmc_function *);
 int	sdmmc_io_function_abort(struct sdmmc_function *);
 
+uint32_t sdmmc_cisptr(struct sdmmc_function *);
 int	sdmmc_read_cis(struct sdmmc_function *, struct sdmmc_cis *);
 void	sdmmc_print_cis(struct sdmmc_function *);
 void	sdmmc_check_cis_quirks(struct sdmmc_function *);

Added files:

Index: src/sys/dev/sdmmc/if_bwi_sdio.c
diff -u /dev/null src/sys/dev/sdmmc/if_bwi_sdio.c:1.1.2.2
--- /dev/null	Sun Feb  2 14:30:00 2025
+++ src/sys/dev/sdmmc/if_bwi_sdio.c	Sun Feb  2 14:29:59 2025
@@ -0,0 +1,453 @@
+/* $NetBSD: if_bwi_sdio.c,v 1.1.2.2 2025/02/02 14:29:59 martin Exp $ */
+
+/*-
+ * Copyright (c) 2025 Jared McNeill <jmcne...@invisible.ca>
+ * 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.2.2 2025/02/02 14:29:59 martin 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;
+}

Reply via email to