[BACK]Return to if_bwfm_sdio.c CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / src / sys / dev / sdmmc

File: [cvs.NetBSD.org] / src / sys / dev / sdmmc / if_bwfm_sdio.c (download)

Revision 1.1.2.2, Sun Dec 3 11:37:32 2017 UTC (6 years, 4 months ago) by jdolecek
Branch: tls-maxphys
Changes since 1.1.2.1: +444 -0 lines

update from HEAD

/* $NetBSD: if_bwfm_sdio.c,v 1.1.2.2 2017/12/03 11:37:32 jdolecek 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;
}