#include <sys/types.h>
#include <sys/systm.h>
#include <sys/mutex.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <machine/intr.h>
#include <dev/ofw/fdt.h>
#include <dev/ofw/openfirm.h>
#include <dev/ic/bcm2835_mbox.h>
#include <dev/ic/bcm2835_vcprop.h>
#define DEVNAME(sc) ((sc)->sc_dev.dv_xname)
struct cfdriver bcmmbox_cd = { NULL, "bcmmbox", DV_DULL };
struct bcmmbox_softc {
struct device sc_dev;
bus_space_tag_t sc_iot;
bus_space_handle_t sc_ioh;
bus_dma_tag_t sc_dmat;
bus_dma_segment_t sc_dmaseg;
bus_dmamap_t sc_dmamap;
caddr_t sc_dmabuf;
void *sc_ih;
struct mutex sc_intr_lock;
int sc_chan[BCMMBOX_NUM_CHANNELS];
uint32_t sc_mbox[BCMMBOX_NUM_CHANNELS];
};
static struct bcmmbox_softc *bcmmbox_sc;
int bcmmbox_match(struct device *, void *, void *);
void bcmmbox_attach(struct device *, struct device *, void *);
const struct cfattach bcmmbox_ca = {
sizeof(struct bcmmbox_softc),
bcmmbox_match,
bcmmbox_attach,
};
uint32_t bcmmbox_reg_read(struct bcmmbox_softc *, int);
void bcmmbox_reg_write(struct bcmmbox_softc *, int, uint32_t);
void bcmmbox_reg_flush(struct bcmmbox_softc *, int);
int bcmmbox_intr(void *);
int bcmmbox_intr_helper(struct bcmmbox_softc *, int);
int
bcmmbox_match(struct device *parent, void *match, void *aux)
{
struct fdt_attach_args *faa = aux;
return OF_is_compatible(faa->fa_node, "brcm,bcm2835-mbox");
}
void
bcmmbox_attach(struct device *parent, struct device *self, void *aux)
{
struct bcmmbox_softc *sc = (struct bcmmbox_softc *)self;
struct fdt_attach_args *faa = aux;
bus_addr_t addr;
bus_size_t size;
int nsegs;
if (bcmmbox_sc) {
printf(": a similar device as already attached\n");
return;
}
bcmmbox_sc = sc;
mtx_init(&sc->sc_intr_lock, IPL_VM);
if (faa->fa_nreg < 1) {
printf(": no registers\n");
return;
}
addr = faa->fa_reg[0].addr;
size = faa->fa_reg[0].size;
sc->sc_dmat = faa->fa_dmat;
sc->sc_iot = faa->fa_iot;
if (bus_space_map(sc->sc_iot, addr, size, 0, &sc->sc_ioh)) {
printf(": can't map registers\n");
return;
}
if (bus_dmamem_alloc(sc->sc_dmat, PAGE_SIZE, PAGE_SIZE, 0,
&sc->sc_dmaseg, 1, &nsegs, BUS_DMA_WAITOK | BUS_DMA_COHERENT)) {
printf(": can't allocate DMA memory\n");
goto clean_bus_space_map;
}
if (bus_dmamem_map(sc->sc_dmat, &sc->sc_dmaseg, 1, PAGE_SIZE,
&sc->sc_dmabuf, BUS_DMA_WAITOK)) {
printf(": can't map DMA memory\n");
goto clean_dmamem_free;
}
if (bus_dmamap_create(sc->sc_dmat, PAGE_SIZE, 1, PAGE_SIZE, 0,
BUS_DMA_WAITOK | BUS_DMA_ALLOCNOW, &sc->sc_dmamap)) {
printf(": can't create DMA map\n");
goto clean_dmamem_unmap;
}
if (bus_dmamap_load_raw(sc->sc_dmat, sc->sc_dmamap,
&sc->sc_dmaseg, 1, PAGE_SIZE, BUS_DMA_WAITOK)) {
printf(": can't load DMA map\n");
goto clean_dmamap_destroy;
}
sc->sc_ih = fdt_intr_establish(faa->fa_node, IPL_VM, bcmmbox_intr, sc,
DEVNAME(sc));
if (sc->sc_ih == NULL) {
printf(": failed to establish interrupt\n");
goto clean_dmamap;
}
bcmmbox_reg_write(sc, BCMMBOX_CFG, BCMMBOX_CFG_DATA_IRQ_EN);
printf("\n");
return;
clean_dmamap:
bus_dmamap_unload(sc->sc_dmat, sc->sc_dmamap);
clean_dmamap_destroy:
bus_dmamap_destroy(sc->sc_dmat, sc->sc_dmamap);
clean_dmamem_unmap:
bus_dmamem_unmap(sc->sc_dmat, sc->sc_dmabuf, PAGE_SIZE);
clean_dmamem_free:
bus_dmamem_free(sc->sc_dmat, &sc->sc_dmaseg, 1);
clean_bus_space_map:
bus_space_unmap(sc->sc_iot, sc->sc_ioh, size);
}
uint32_t
bcmmbox_reg_read(struct bcmmbox_softc *sc, int addr)
{
return bus_space_read_4(sc->sc_iot, sc->sc_ioh, addr);
}
void
bcmmbox_reg_write(struct bcmmbox_softc *sc, int addr, uint32_t val)
{
bus_space_write_4(sc->sc_iot, sc->sc_ioh, addr, val);
}
void
bcmmbox_reg_flush(struct bcmmbox_softc *sc, int flags)
{
bus_space_barrier(sc->sc_iot, sc->sc_ioh, 0, BCMMBOX_SIZE, flags);
}
int
bcmmbox_intr(void *cookie)
{
struct bcmmbox_softc *sc = cookie;
int ret;
mtx_enter(&sc->sc_intr_lock);
ret = bcmmbox_intr_helper(sc, 1);
mtx_leave(&sc->sc_intr_lock);
return ret;
}
int
bcmmbox_intr_helper(struct bcmmbox_softc *sc, int broadcast)
{
uint32_t mbox, chan, data;
int ret = 0;
bcmmbox_reg_flush(sc, BUS_SPACE_BARRIER_READ);
while (!ISSET(bcmmbox_reg_read(sc, BCMMBOX0_STATUS), BCMMBOX_STATUS_EMPTY)) {
mbox = bcmmbox_reg_read(sc, BCMMBOX0_READ);
chan = mbox & BCMMBOX_CHANNEL_MASK;
data = mbox & ~BCMMBOX_CHANNEL_MASK;
ret = 1;
if ((sc->sc_mbox[chan] & BCMMBOX_CHANNEL_MASK) != 0) {
printf("%s: chan %d overflow\n", DEVNAME(sc), chan);
continue;
}
sc->sc_mbox[chan] = data | BCMMBOX_CHANNEL_MASK;
if (broadcast)
wakeup(&sc->sc_chan[chan]);
}
return ret;
}
void
bcmmbox_read(uint8_t chan, uint32_t *data)
{
struct bcmmbox_softc *sc = bcmmbox_sc;
int error = 0, timo;
KASSERT(sc != NULL);
KASSERT(chan == (chan & BCMMBOX_CHANNEL_MASK));
mtx_enter(&sc->sc_intr_lock);
timo = 1000;
while ((sc->sc_mbox[chan] & BCMMBOX_CHANNEL_MASK) == 0) {
if (cold) {
bcmmbox_intr_helper(sc, 0);
if (--timo == 0) {
error = EWOULDBLOCK;
break;
}
delay(1000);
} else {
error = msleep_nsec(&sc->sc_chan[chan],
&sc->sc_intr_lock, PWAIT, "bcmmbox",
SEC_TO_NSEC(1));
if (error)
break;
}
}
*data = sc->sc_mbox[chan] & ~BCMMBOX_CHANNEL_MASK;
sc->sc_mbox[chan] = 0;
mtx_leave(&sc->sc_intr_lock);
if (error)
printf("%s: timeout\n", sc->sc_dev.dv_xname);
}
void
bcmmbox_write(uint8_t chan, uint32_t data)
{
struct bcmmbox_softc *sc = bcmmbox_sc;
uint32_t rdata;
KASSERT(sc != NULL);
KASSERT(chan == (chan & BCMMBOX_CHANNEL_MASK));
KASSERT(data == (data & ~BCMMBOX_CHANNEL_MASK));
while (1) {
bcmmbox_reg_flush(sc, BUS_SPACE_BARRIER_READ);
rdata = bcmmbox_reg_read(sc, BCMMBOX0_STATUS);
if (!ISSET(rdata, BCMMBOX_STATUS_FULL))
break;
}
bcmmbox_reg_write(sc, BCMMBOX1_WRITE, chan | data);
bcmmbox_reg_flush(sc, BUS_SPACE_BARRIER_WRITE);
}
int
bcmmbox_post(uint8_t chan, void *buf, size_t len, uint32_t *res)
{
struct bcmmbox_softc *sc = bcmmbox_sc;
KASSERT(sc != NULL);
KASSERT(len < PAGE_SIZE);
if (sc == NULL)
return ENXIO;
memcpy(sc->sc_dmabuf, buf, len);
bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap, 0, len,
BUS_DMASYNC_PREREAD | BUS_DMASYNC_PREWRITE);
bcmmbox_write(chan, sc->sc_dmamap->dm_segs[0].ds_addr);
bcmmbox_read(chan, res);
bus_dmamap_sync(sc->sc_dmat, sc->sc_dmamap, 0, len,
BUS_DMASYNC_POSTREAD | BUS_DMASYNC_POSTWRITE);
memcpy(buf, sc->sc_dmabuf, len);
return 0;
}