Add driver for Broadcom 802.11a/b/g/n/ac SDIO wireless devices, based on

the OpenBSD bwfm(4) driver.

I could not test this on any hardware yet, as it does not attach as-is on
my Raspberry PI 3.
This commit is contained in:
khorben 2017-11-07 16:30:32 +00:00
parent cd1c6201df
commit d6735baf16
2 changed files with 449 additions and 1 deletions

View File

@ -1,4 +1,4 @@
# $NetBSD: files.sdmmc,v 1.4 2014/10/02 21:49:22 jmcneill Exp $ # $NetBSD: files.sdmmc,v 1.5 2017/11/07 16:30:32 khorben Exp $
# $OpenBSD: files.sdmmc,v 1.2 2006/06/01 21:53:41 uwe 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. # Config file and device description for machine-independent SD/MMC code.
@ -21,3 +21,7 @@ file dev/sdmmc/ld_sdmmc.c ld_sdmmc
device sbt: btbus, bluetooth device sbt: btbus, bluetooth
attach sbt at sdmmc attach sbt at sdmmc
file dev/sdmmc/sbt.c sbt 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

View File

@ -0,0 +1,444 @@
/* $NetBSD: if_bwfm_sdio.c,v 1.1 2017/11/07 16:30:32 khorben Exp $ */
/* $OpenBSD: if_bwfm_sdio.c,v 1.1 2017/10/11 17:19:50 patrick Exp $ */
/*
* Copyright (c) 2010-2016 Broadcom Corporation
* Copyright (c) 2016,2017 Patrick Wildt <patrick@blueri.se>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/buf.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/device.h>
#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/mutex.h>
#include <sys/workqueue.h>
#include <sys/pcq.h>
#include <net/bpf.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_media.h>
#include <net/if_ether.h>
#include <netinet/in.h>
#include <net80211/ieee80211_var.h>
#include <dev/sdmmc/sdmmcvar.h>
#include <dev/ic/bwfmvar.h>
#include <dev/ic/bwfmreg.h>
#define BWFM_SDIO_CCCR_BRCM_CARDCAP 0xf0
#define BWFM_SDIO_CCCR_BRCM_CARDCAP_CMD14_SUPPORT 0x02
#define BWFM_SDIO_CCCR_BRCM_CARDCAP_CMD14_EXT 0x04
#define BWFM_SDIO_CCCR_BRCM_CARDCAP_CMD_NODEC 0x08
#define BWFM_SDIO_CCCR_BRCM_CARDCTRL 0xf1
#define BWFM_SDIO_CCCR_BRCM_CARDCTRL_WLANRESET 0x02
#define BWFM_SDIO_CCCR_BRCM_SEPINT 0xf2
#ifdef BWFM_DEBUG
#define DPRINTF(x) do { if (bwfm_debug > 0) printf x; } while (0)
#define DPRINTFN(n, x) do { if (bwfm_debug >= (n)) printf x; } while (0)
static int bwfm_debug = 2;
#else
#define DPRINTF(x) do { ; } while (0)
#define DPRINTFN(n, x) do { ; } while (0)
#endif
#define DEVNAME(sc) device_xname((sc)->sc_sc.sc_dev)
struct bwfm_sdio_softc {
struct bwfm_softc sc_sc;
struct sdmmc_function **sc_sf;
uint32_t sc_bar0;
};
int bwfm_sdio_match(device_t, cfdata_t, void *);
void bwfm_sdio_attach(device_t, struct device *, void *);
int bwfm_sdio_detach(device_t, int);
uint8_t bwfm_sdio_read_1(struct bwfm_sdio_softc *, uint32_t);
uint32_t bwfm_sdio_read_4(struct bwfm_sdio_softc *, uint32_t);
void bwfm_sdio_write_1(struct bwfm_sdio_softc *, uint32_t,
uint8_t);
void bwfm_sdio_write_4(struct bwfm_sdio_softc *, uint32_t,
uint32_t);
uint32_t bwfm_sdio_buscore_read(struct bwfm_softc *, uint32_t);
void bwfm_sdio_buscore_write(struct bwfm_softc *, uint32_t,
uint32_t);
int bwfm_sdio_buscore_prepare(struct bwfm_softc *);
void bwfm_sdio_buscore_activate(struct bwfm_softc *, uint32_t);
int bwfm_sdio_txdata(struct bwfm_softc *, struct mbuf *);
int bwfm_sdio_txctl(struct bwfm_softc *, char *, size_t);
int bwfm_sdio_rxctl(struct bwfm_softc *, char *, size_t *);
struct bwfm_bus_ops bwfm_sdio_bus_ops = {
.bs_init = NULL,
.bs_stop = NULL,
.bs_txdata = bwfm_sdio_txdata,
.bs_txctl = bwfm_sdio_txctl,
.bs_rxctl = bwfm_sdio_rxctl,
};
struct bwfm_buscore_ops bwfm_sdio_buscore_ops = {
.bc_read = bwfm_sdio_buscore_read,
.bc_write = bwfm_sdio_buscore_write,
.bc_prepare = bwfm_sdio_buscore_prepare,
.bc_reset = NULL,
.bc_setup = NULL,
.bc_activate = bwfm_sdio_buscore_activate,
};
CFATTACH_DECL_NEW(bwfm_sdio, sizeof(struct bwfm_sdio_softc),
bwfm_sdio_match, bwfm_sdio_attach, bwfm_sdio_detach, NULL);
int
bwfm_sdio_match(device_t parent, cfdata_t match, void *aux)
{
struct sdmmc_attach_args *saa = aux;
struct sdmmc_function *sf = saa->sf;
struct sdmmc_cis *cis;
/* Not SDIO. */
if (sf == NULL)
return 0;
/* Look for Broadcom 433[04]. */
cis = &sf->sc->sc_fn0->cis;
if (cis->manufacturer != 0x02d0 || (cis->product != 0x4330 &&
cis->product != 0x4334))
return 0;
/* We need both functions, but ... */
if (sf->sc->sc_function_count <= 1)
return 0;
/* ... only attach for one. */
if (sf->number != 1)
return 0;
return 1;
}
void
bwfm_sdio_attach(device_t parent, device_t self, void *aux)
{
struct bwfm_sdio_softc *sc = device_private(self);
struct sdmmc_attach_args *saa = aux;
struct sdmmc_function *sf = saa->sf;
struct bwfm_core *core;
aprint_naive("\n");
sc->sc_sf = malloc((sf->sc->sc_function_count + 1) *
sizeof(struct sdmmc_function *), M_DEVBUF, M_WAITOK);
/* Copy all function pointers. */
SIMPLEQ_FOREACH(sf, &saa->sf->sc->sf_head, sf_list) {
sc->sc_sf[sf->number] = sf;
}
sf = saa->sf;
/*
* TODO: set block size to 64 for func 1, 512 for func 2.
* We might need to work on the SDMMC stack to be able to set
* a block size per function. Currently the IO code uses the
* SDHC controller's maximum block length.
*/
/* Enable Function 1. */
if (sdmmc_io_function_enable(sc->sc_sf[1]) != 0) {
aprint_error_dev(self, "cannot enable function 1\n");
goto err;
}
DPRINTFN(2, ("%s: F1 signature read @0x18000000=%x\n", DEVNAME(sc),
bwfm_sdio_read_4(sc, 0x18000000)));
/* Force PLL off */
bwfm_sdio_write_1(sc, BWFM_SDIO_FUNC1_CHIPCLKCSR,
BWFM_SDIO_FUNC1_CHIPCLKCSR_FORCE_HW_CLKREQ_OFF |
BWFM_SDIO_FUNC1_CHIPCLKCSR_ALP_AVAIL_REQ);
sc->sc_sc.sc_buscore_ops = &bwfm_sdio_buscore_ops;
if (bwfm_chip_attach(&sc->sc_sc) != 0) {
aprint_error_dev(self, "cannot attach chip\n");
goto err;
}
/* TODO: drive strength */
bwfm_sdio_write_1(sc, BWFM_SDIO_CCCR_BRCM_CARDCTRL,
bwfm_sdio_read_1(sc, BWFM_SDIO_CCCR_BRCM_CARDCTRL) |
BWFM_SDIO_CCCR_BRCM_CARDCTRL_WLANRESET);
core = bwfm_chip_get_pmu(&sc->sc_sc);
bwfm_sdio_write_4(sc, core->co_base + BWFM_CHIP_REG_PMUCONTROL,
bwfm_sdio_read_4(sc, core->co_base + BWFM_CHIP_REG_PMUCONTROL) |
(BWFM_CHIP_REG_PMUCONTROL_RES_RELOAD <<
BWFM_CHIP_REG_PMUCONTROL_RES_SHIFT));
sc->sc_sc.sc_bus_ops = &bwfm_sdio_bus_ops;
sc->sc_sc.sc_proto_ops = &bwfm_proto_bcdc_ops;
bwfm_attach(&sc->sc_sc);
return;
err:
free(sc->sc_sf, M_DEVBUF);
}
int
bwfm_sdio_detach(struct device *self, int flags)
{
struct bwfm_sdio_softc *sc = (struct bwfm_sdio_softc *)self;
bwfm_detach(&sc->sc_sc, flags);
free(sc->sc_sf, M_DEVBUF);
return 0;
}
uint8_t
bwfm_sdio_read_1(struct bwfm_sdio_softc *sc, uint32_t addr)
{
struct sdmmc_function *sf;
uint8_t rv;
/*
* figure out how to read the register based on address range
* 0x00 ~ 0x7FF: function 0 CCCR and FBR
* 0x10000 ~ 0x1FFFF: function 1 miscellaneous registers
* The rest: function 1 silicon backplane core registers
*/
if ((addr & ~0x7ff) == 0)
sf = sc->sc_sf[0];
else
sf = sc->sc_sf[1];
rv = sdmmc_io_read_1(sf, addr);
return rv;
}
uint32_t
bwfm_sdio_read_4(struct bwfm_sdio_softc *sc, uint32_t addr)
{
struct sdmmc_function *sf;
uint32_t bar0 = addr & ~BWFM_SDIO_SB_OFT_ADDR_MASK;
uint32_t rv;
if (sc->sc_bar0 != bar0) {
bwfm_sdio_write_1(sc, BWFM_SDIO_FUNC1_SBADDRLOW,
(bar0 >> 8) & 0x80);
bwfm_sdio_write_1(sc, BWFM_SDIO_FUNC1_SBADDRMID,
(bar0 >> 16) & 0xff);
bwfm_sdio_write_1(sc, BWFM_SDIO_FUNC1_SBADDRHIGH,
(bar0 >> 24) & 0xff);
sc->sc_bar0 = bar0;
}
addr &= BWFM_SDIO_SB_OFT_ADDR_MASK;
addr |= BWFM_SDIO_SB_ACCESS_2_4B_FLAG;
/*
* figure out how to read the register based on address range
* 0x00 ~ 0x7FF: function 0 CCCR and FBR
* 0x10000 ~ 0x1FFFF: function 1 miscellaneous registers
* The rest: function 1 silicon backplane core registers
*/
if ((addr & ~0x7ff) == 0)
sf = sc->sc_sf[0];
else
sf = sc->sc_sf[1];
rv = sdmmc_io_read_4(sf, addr);
return rv;
}
void
bwfm_sdio_write_1(struct bwfm_sdio_softc *sc, uint32_t addr, uint8_t data)
{
struct sdmmc_function *sf;
/*
* figure out how to read the register based on address range
* 0x00 ~ 0x7FF: function 0 CCCR and FBR
* 0x10000 ~ 0x1FFFF: function 1 miscellaneous registers
* The rest: function 1 silicon backplane core registers
*/
if ((addr & ~0x7ff) == 0)
sf = sc->sc_sf[0];
else
sf = sc->sc_sf[1];
sdmmc_io_write_1(sf, addr, data);
}
void
bwfm_sdio_write_4(struct bwfm_sdio_softc *sc, uint32_t addr, uint32_t data)
{
struct sdmmc_function *sf;
uint32_t bar0 = addr & ~BWFM_SDIO_SB_OFT_ADDR_MASK;
if (sc->sc_bar0 != bar0) {
bwfm_sdio_write_1(sc, BWFM_SDIO_FUNC1_SBADDRLOW,
(bar0 >> 8) & 0x80);
bwfm_sdio_write_1(sc, BWFM_SDIO_FUNC1_SBADDRMID,
(bar0 >> 16) & 0xff);
bwfm_sdio_write_1(sc, BWFM_SDIO_FUNC1_SBADDRHIGH,
(bar0 >> 24) & 0xff);
sc->sc_bar0 = bar0;
}
addr &= BWFM_SDIO_SB_OFT_ADDR_MASK;
addr |= BWFM_SDIO_SB_ACCESS_2_4B_FLAG;
/*
* figure out how to read the register based on address range
* 0x00 ~ 0x7FF: function 0 CCCR and FBR
* 0x10000 ~ 0x1FFFF: function 1 miscellaneous registers
* The rest: function 1 silicon backplane core registers
*/
if ((addr & ~0x7ff) == 0)
sf = sc->sc_sf[0];
else
sf = sc->sc_sf[1];
sdmmc_io_write_4(sf, addr, data);
}
uint32_t
bwfm_sdio_buscore_read(struct bwfm_softc *bwfm, uint32_t reg)
{
struct bwfm_sdio_softc *sc = (void *)bwfm;
uint32_t val;
val = bwfm_sdio_read_4(sc, reg);
/* TODO: Workaround for 4335/4339 */
return val;
}
void
bwfm_sdio_buscore_write(struct bwfm_softc *bwfm, uint32_t reg, uint32_t val)
{
struct bwfm_sdio_softc *sc = (void *)bwfm;
bwfm_sdio_write_4(sc, reg, val);
}
int
bwfm_sdio_buscore_prepare(struct bwfm_softc *bwfm)
{
struct bwfm_sdio_softc *sc = (void *)bwfm;
uint8_t clkval, clkset, clkmask;
int i;
clkset = BWFM_SDIO_FUNC1_CHIPCLKCSR_ALP_AVAIL_REQ |
BWFM_SDIO_FUNC1_CHIPCLKCSR_FORCE_HW_CLKREQ_OFF;
bwfm_sdio_write_1(sc, BWFM_SDIO_FUNC1_CHIPCLKCSR, clkset);
clkmask = BWFM_SDIO_FUNC1_CHIPCLKCSR_ALP_AVAIL |
BWFM_SDIO_FUNC1_CHIPCLKCSR_HT_AVAIL;
clkval = bwfm_sdio_read_1(sc, BWFM_SDIO_FUNC1_CHIPCLKCSR);
if ((clkval & ~clkmask) != clkset) {
printf("%s: wrote 0x%02x read 0x%02x\n", DEVNAME(sc),
clkset, clkval);
return 1;
}
for (i = 1000; i > 0; i--) {
clkval = bwfm_sdio_read_1(sc,
BWFM_SDIO_FUNC1_CHIPCLKCSR);
if (clkval & clkmask)
break;
}
if (i == 0) {
printf("%s: timeout on ALPAV wait, clkval 0x%02x\n",
DEVNAME(sc), clkval);
return 1;
}
clkset = BWFM_SDIO_FUNC1_CHIPCLKCSR_FORCE_HW_CLKREQ_OFF |
BWFM_SDIO_FUNC1_CHIPCLKCSR_FORCE_ALP;
bwfm_sdio_write_1(sc, BWFM_SDIO_FUNC1_CHIPCLKCSR, clkset);
delay(65);
bwfm_sdio_write_1(sc, BWFM_SDIO_FUNC1_SDIOPULLUP, 0);
return 0;
}
void
bwfm_sdio_buscore_activate(struct bwfm_softc *bwfm, uint32_t rstvec)
{
struct bwfm_sdio_softc *sc = (void *)bwfm;
struct bwfm_core *core;
core = bwfm_chip_get_core(&sc->sc_sc, BWFM_AGENT_CORE_SDIO_DEV);
bwfm_sdio_buscore_write(&sc->sc_sc,
core->co_base + BWFM_SDPCMD_INTSTATUS, 0xFFFFFFFF);
#if notyet
if (rstvec)
bwfm_sdio_ram_write(&sc->sc_sc, 0, &rstvec, sizeof(rstvec));
#endif
}
int
bwfm_sdio_txdata(struct bwfm_softc *bwfm, struct mbuf *m)
{
#ifdef BWFM_DEBUG
struct bwfm_sdio_softc *sc = (void *)bwfm;
#endif
int ret = 1;
DPRINTFN(2, ("%s: %s\n", DEVNAME(sc), __func__));
return ret;
}
int
bwfm_sdio_txctl(struct bwfm_softc *bwfm, char *buf, size_t len)
{
#ifdef BWFM_DEBUG
struct bwfm_sdio_softc *sc = (void *)bwfm;
#endif
int ret = 1;
DPRINTFN(2, ("%s: %s\n", DEVNAME(sc), __func__));
return ret;
}
int
bwfm_sdio_rxctl(struct bwfm_softc *bwfm, char *buf, size_t *len)
{
#ifdef BWFM_DEBUG
struct bwfm_sdio_softc *sc = (void *)bwfm;
#endif
int ret = 1;
DPRINTFN(2, ("%s: %s\n", DEVNAME(sc), __func__));
return ret;
}