/* $NetBSD: if_bwi_sdio.c,v 1.1 2025/01/19 00:29:29 jmcneill Exp $ */

/*-
 * Copyright (c) 2025 Jared McNeill <jmcneill@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 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;
}