diff options
author | br <br@FreeBSD.org> | 2014-02-16 16:49:54 +0000 |
---|---|---|
committer | br <br@FreeBSD.org> | 2014-02-16 16:49:54 +0000 |
commit | 0c8f485c9421879292ffd9a528130bc8ee8d0439 (patch) | |
tree | 1e75fed692f5ba7741a1c83beb78601958bd0816 | |
parent | 6f5eb38aac102ea0f2b7a8d9a860b47a3418177b (diff) | |
download | FreeBSD-src-0c8f485c9421879292ffd9a528130bc8ee8d0439.zip FreeBSD-src-0c8f485c9421879292ffd9a528130bc8ee8d0439.tar.gz |
Add driver for Synchronous Audio Interface (SAI).
SAI supports full-duplex serial interfaces with frame
synchronization such as I2S, AC97, TDM, and codec/DSP
interfaces.
-rw-r--r-- | sys/arm/freescale/vybrid/files.vybrid | 1 | ||||
-rw-r--r-- | sys/arm/freescale/vybrid/vf_sai.c | 802 | ||||
-rw-r--r-- | sys/boot/fdt/dts/vybrid.dtsi | 3 |
3 files changed, 806 insertions, 0 deletions
diff --git a/sys/arm/freescale/vybrid/files.vybrid b/sys/arm/freescale/vybrid/files.vybrid index 756beb9..890e23e 100644 --- a/sys/arm/freescale/vybrid/files.vybrid +++ b/sys/arm/freescale/vybrid/files.vybrid @@ -29,4 +29,5 @@ arm/freescale/vybrid/vf_nfc.c optional nand arm/freescale/vybrid/vf_ehci.c optional ehci arm/freescale/vybrid/vf_gpio.c optional gpio arm/freescale/vybrid/vf_uart.c optional uart +arm/freescale/vybrid/vf_sai.c optional sound dev/ffec/if_ffec.c optional ffec diff --git a/sys/arm/freescale/vybrid/vf_sai.c b/sys/arm/freescale/vybrid/vf_sai.c new file mode 100644 index 0000000..782c9d3 --- /dev/null +++ b/sys/arm/freescale/vybrid/vf_sai.c @@ -0,0 +1,802 @@ +/*- + * Copyright (c) 2014 Ruslan Bukin <br@bsdpad.com> + * 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 AUTHOR 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 AUTHOR 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. + */ + +/* + * Vybrid Family Synchronous Audio Interface (SAI) + * Chapter 51, Vybrid Reference Manual, Rev. 5, 07/2013 + */ + +#include <sys/cdefs.h> +__FBSDID("$FreeBSD$"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/bus.h> +#include <sys/kernel.h> +#include <sys/module.h> +#include <sys/malloc.h> +#include <sys/rman.h> +#include <sys/timeet.h> +#include <sys/timetc.h> +#include <sys/watchdog.h> + +#include <dev/sound/pcm/sound.h> +#include <dev/sound/chip.h> +#include <mixer_if.h> + +#include <dev/fdt/fdt_common.h> +#include <dev/ofw/openfirm.h> +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> + +#include <machine/bus.h> +#include <machine/fdt.h> +#include <machine/cpu.h> +#include <machine/intr.h> + +#include <arm/freescale/vybrid/vf_common.h> +#include <arm/freescale/vybrid/vf_dmamux.h> +#include <arm/freescale/vybrid/vf_edma.h> + +#define I2S_TCSR 0x00 /* SAI Transmit Control */ +#define I2S_TCR1 0x04 /* SAI Transmit Configuration 1 */ +#define I2S_TCR2 0x08 /* SAI Transmit Configuration 2 */ +#define I2S_TCR3 0x0C /* SAI Transmit Configuration 3 */ +#define I2S_TCR4 0x10 /* SAI Transmit Configuration 4 */ +#define I2S_TCR5 0x14 /* SAI Transmit Configuration 5 */ +#define I2S_TDR0 0x20 /* SAI Transmit Data */ +#define I2S_TFR0 0x40 /* SAI Transmit FIFO */ +#define I2S_TMR 0x60 /* SAI Transmit Mask */ +#define I2S_RCSR 0x80 /* SAI Receive Control */ +#define I2S_RCR1 0x84 /* SAI Receive Configuration 1 */ +#define I2S_RCR2 0x88 /* SAI Receive Configuration 2 */ +#define I2S_RCR3 0x8C /* SAI Receive Configuration 3 */ +#define I2S_RCR4 0x90 /* SAI Receive Configuration 4 */ +#define I2S_RCR5 0x94 /* SAI Receive Configuration 5 */ +#define I2S_RDR0 0xA0 /* SAI Receive Data */ +#define I2S_RFR0 0xC0 /* SAI Receive FIFO */ +#define I2S_RMR 0xE0 /* SAI Receive Mask */ + +#define TCR1_TFW_M 0x1f /* Transmit FIFO Watermark Mask */ +#define TCR1_TFW_S 0 /* Transmit FIFO Watermark Shift */ +#define TCR2_MSEL_M 0x3 /* MCLK Select Mask*/ +#define TCR2_MSEL_S 26 /* MCLK Select Shift*/ +#define TCR2_BCP (1 << 25) /* Bit Clock Polarity */ +#define TCR2_BCD (1 << 24) /* Bit Clock Direction */ +#define TCR3_TCE (1 << 16) /* Transmit Channel Enable */ +#define TCR4_FRSZ_M 0x1f /* Frame size Mask */ +#define TCR4_FRSZ_S 16 /* Frame size Shift */ +#define TCR4_SYWD_M 0x1f /* Sync Width Mask */ +#define TCR4_SYWD_S 8 /* Sync Width Shift */ +#define TCR4_MF (1 << 4) /* MSB First */ +#define TCR4_FSE (1 << 3) /* Frame Sync Early */ +#define TCR4_FSP (1 << 1) /* Frame Sync Polarity Low */ +#define TCR4_FSD (1 << 0) /* Frame Sync Direction Master */ +#define TCR5_FBT_M 0x1f /* First Bit Shifted */ +#define TCR5_FBT_S 8 /* First Bit Shifted */ +#define TCR5_W0W_M 0x1f /* Word 0 Width */ +#define TCR5_W0W_S 16 /* Word 0 Width */ +#define TCR5_WNW_M 0x1f /* Word N Width */ +#define TCR5_WNW_S 24 /* Word N Width */ +#define TCSR_TE (1 << 31) /* Transmitter Enable */ +#define TCSR_BCE (1 << 28) /* Bit Clock Enable */ +#define TCSR_FRDE (1 << 0) /* FIFO Request DMA Enable */ + +#define SAI_NCHANNELS 1 + +static MALLOC_DEFINE(M_SAI, "sai", "sai audio"); + +struct sai_rate { + uint32_t speed; + uint32_t div; /* Bit Clock Divide. Division value is (div + 1) * 2. */ + uint32_t mfi; /* PLL4 Multiplication Factor Integer */ + uint32_t mfn; /* PLL4 Multiplication Factor Numerator */ + uint32_t mfd; /* PLL4 Multiplication Factor Denominator */ +}; + +/* + * Bit clock divider formula + * (div + 1) * 2 = MCLK/(nch * LRCLK * bits/1000000), + * where: + * MCLK - master clock + * nch - number of channels + * LRCLK - left right clock + * e.g. (div + 1) * 2 = 16.9344/(2 * 44100 * 24/1000000) + * + * Example for 96khz, 24bit, 18.432 Mhz mclk (192fs) + * { 96000, 1, 18, 40176000, 93000000 }, + */ + +static struct sai_rate rate_map[] = { + { 44100, 7, 33, 80798400, 93000000 }, /* 33.8688 Mhz */ + { 96000, 3, 36, 80352000, 93000000 }, /* 36.864 Mhz */ + { 192000, 1, 36, 80352000, 93000000 }, /* 36.864 Mhz */ + { 0, 0 }, +}; + +struct sc_info { + struct resource *res[2]; + bus_space_tag_t bst; + bus_space_handle_t bsh; + device_t dev; + struct mtx *lock; + uint32_t speed; + uint32_t period; + void *ih; + int pos; + int dma_size; + bus_dma_tag_t dma_tag; + bus_dmamap_t dma_map; + bus_addr_t buf_base_phys; + uint32_t *buf_base; + struct tcd_conf *tcd; + struct sai_rate *sr; + struct edma_softc *edma_sc; + int edma_chnum; +}; + +/* Channel registers */ +struct sc_chinfo { + struct snd_dbuf *buffer; + struct pcm_channel *channel; + struct sc_pcminfo *parent; + + /* Channel information */ + uint32_t dir; + uint32_t format; + + /* Flags */ + uint32_t run; +}; + +/* PCM device private data */ +struct sc_pcminfo { + device_t dev; + uint32_t (*ih) (struct sc_pcminfo *scp); + uint32_t chnum; + struct sc_chinfo chan[SAI_NCHANNELS]; + struct sc_info *sc; +}; + +static struct resource_spec sai_spec[] = { + { SYS_RES_MEMORY, 0, RF_ACTIVE }, + { SYS_RES_IRQ, 0, RF_ACTIVE }, + { -1, 0 } +}; + +static int setup_dma(struct sc_pcminfo *scp); +static void setup_sai(struct sc_info *); +static void sai_configure_clock(struct sc_info *); + +/* + * Mixer interface. + */ + +static int +saimixer_init(struct snd_mixer *m) +{ + struct sc_pcminfo *scp; + struct sc_info *sc; + int mask; + + scp = mix_getdevinfo(m); + sc = scp->sc; + + if (sc == NULL) + return -1; + + mask = SOUND_MASK_PCM; + + snd_mtxlock(sc->lock); + pcm_setflags(scp->dev, pcm_getflags(scp->dev) | SD_F_SOFTPCMVOL); + mix_setdevs(m, mask); + snd_mtxunlock(sc->lock); + + return (0); +} + +static int +saimixer_set(struct snd_mixer *m, unsigned dev, + unsigned left, unsigned right) +{ + struct sc_pcminfo *scp; + + scp = mix_getdevinfo(m); + +#if 0 + device_printf(scp->dev, "saimixer_set() %d %d\n", + left, right); +#endif + + return (0); +} + +static kobj_method_t saimixer_methods[] = { + KOBJMETHOD(mixer_init, saimixer_init), + KOBJMETHOD(mixer_set, saimixer_set), + KOBJMETHOD_END +}; +MIXER_DECLARE(saimixer); + +/* + * Channel interface. + */ + +static void * +saichan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, + struct pcm_channel *c, int dir) +{ + struct sc_pcminfo *scp; + struct sc_chinfo *ch; + struct sc_info *sc; + + scp = (struct sc_pcminfo *)devinfo; + sc = scp->sc; + + snd_mtxlock(sc->lock); + ch = &scp->chan[0]; + ch->dir = dir; + ch->run = 0; + ch->buffer = b; + ch->channel = c; + ch->parent = scp; + snd_mtxunlock(sc->lock); + + if (sndbuf_setup(ch->buffer, sc->buf_base, sc->dma_size) != 0) { + device_printf(scp->dev, "Can't setup sndbuf.\n"); + return NULL; + } + + return ch; +} + +static int +saichan_free(kobj_t obj, void *data) +{ + struct sc_chinfo *ch = data; + struct sc_pcminfo *scp = ch->parent; + struct sc_info *sc = scp->sc; + +#if 0 + device_printf(scp->dev, "saichan_free()\n"); +#endif + + snd_mtxlock(sc->lock); + /* TODO: free channel buffer */ + snd_mtxunlock(sc->lock); + + return (0); +} + +static int +saichan_setformat(kobj_t obj, void *data, uint32_t format) +{ + struct sc_chinfo *ch = data; + + ch->format = format; + + return (0); +} + +static uint32_t +saichan_setspeed(kobj_t obj, void *data, uint32_t speed) +{ + struct sc_pcminfo *scp; + struct sc_chinfo *ch; + struct sai_rate *sr; + struct sc_info *sc; + int threshold; + int i; + + ch = data; + scp = ch->parent; + sc = scp->sc; + + sr = NULL; + + /* First look for equal frequency. */ + for (i = 0; rate_map[i].speed != 0; i++) { + if (rate_map[i].speed == speed) + sr = &rate_map[i]; + } + + /* If no match, just find nearest. */ + if (sr == NULL) { + for (i = 0; rate_map[i].speed != 0; i++) { + sr = &rate_map[i]; + threshold = sr->speed + ((rate_map[i + 1].speed != 0) ? + ((rate_map[i + 1].speed - sr->speed) >> 1) : 0); + if (speed < threshold) + break; + } + } + + sc->sr = sr; + + sai_configure_clock(sc); + + return (sr->speed); +} + +static void +sai_configure_clock(struct sc_info *sc) +{ + struct sai_rate *sr; + int reg; + + sr = sc->sr; + + /* + * Manual says that TCR/RCR registers must not be + * altered when TCSR[TE] is set. + * We ignore it since we have problem sometimes + * after re-enabling transmitter (DMA goes stall). + */ + + reg = READ4(sc, I2S_TCR2); + reg &= ~(0xff << 0); + reg |= (sr->div << 0); + WRITE4(sc, I2S_TCR2, reg); + + pll4_configure_output(sr->mfi, sr->mfn, sr->mfd); +} + +static uint32_t +saichan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) +{ + struct sc_chinfo *ch = data; + struct sc_pcminfo *scp = ch->parent; + struct sc_info *sc = scp->sc; + + sndbuf_resize(ch->buffer, sc->dma_size / blocksize, blocksize); + + sc->period = sndbuf_getblksz(ch->buffer); + return (sc->period); +} + +uint32_t sai_dma_intr(void *arg, int chn); +uint32_t +sai_dma_intr(void *arg, int chn) +{ + struct sc_pcminfo *scp; + struct sc_chinfo *ch; + struct sc_info *sc; + struct tcd_conf *tcd; + + scp = arg; + ch = &scp->chan[0]; + + sc = scp->sc; + tcd = sc->tcd; + + sc->pos += (tcd->nbytes * tcd->nmajor); + if (sc->pos >= sc->dma_size) + sc->pos -= sc->dma_size; + + chn_intr(ch->channel); + + return (0); +} + +static int +find_edma_controller(struct sc_info *sc) +{ + struct edma_softc *edma_sc; + phandle_t node, edma_node; + int edma_src_transmit; + int edma_mux_group; + int edma_device_id; + device_t edma_dev; + int dts_value; + int len; + int i; + + if ((node = ofw_bus_get_node(sc->dev)) == -1) + return (ENXIO); + + if ((len = OF_getproplen(node, "edma-controller")) <= 0) + return (ENXIO); + if ((len = OF_getproplen(node, "edma-src-transmit")) <= 0) + return (ENXIO); + if ((len = OF_getproplen(node, "edma-mux-group")) <= 0) + return (ENXIO); + + OF_getprop(node, "edma-src-transmit", &dts_value, len); + edma_src_transmit = fdt32_to_cpu(dts_value); + OF_getprop(node, "edma-mux-group", &dts_value, len); + edma_mux_group = fdt32_to_cpu(dts_value); + OF_getprop(node, "edma-controller", &dts_value, len); + edma_node = OF_xref_phandle(fdt32_to_cpu(dts_value)); + + if ((len = OF_getproplen(edma_node, "device-id")) <= 0) { + return (ENXIO); + }; + + OF_getprop(edma_node, "device-id", &dts_value, len); + edma_device_id = fdt32_to_cpu(dts_value); + + edma_sc = NULL; + + for (i = 0; i < EDMA_NUM_DEVICES; i++) { + edma_dev = devclass_get_device(devclass_find("edma"), i); + if (edma_dev) { + edma_sc = device_get_softc(edma_dev); + if (edma_sc->device_id == edma_device_id) { + /* found */ + break; + }; + + edma_sc = NULL; + }; + }; + + if (edma_sc == NULL) { + device_printf(sc->dev, "no eDMA. can't operate\n"); + return (ENXIO); + }; + + sc->edma_sc = edma_sc; + + sc->edma_chnum = edma_sc->channel_configure(edma_sc, edma_mux_group, + edma_src_transmit); + if (sc->edma_chnum < 0) { + /* cant setup eDMA */ + return (ENXIO); + }; + + return (0); +}; + +static int +setup_dma(struct sc_pcminfo *scp) +{ + struct tcd_conf *tcd; + struct sc_info *sc; + + sc = scp->sc; + + tcd = malloc(sizeof(struct tcd_conf), M_DEVBUF, M_WAITOK | M_ZERO); + tcd->channel = sc->edma_chnum; + tcd->ih = sai_dma_intr; + tcd->ih_user = scp; + tcd->saddr = sc->buf_base_phys; + tcd->daddr = rman_get_start(sc->res[0]) + I2S_TDR0; + + /* + * Bytes to transfer per each minor loop. + * Hardware FIFO buffer size is 32x32bits. + */ + tcd->nbytes = 64; + + tcd->nmajor = 512; + tcd->smod = 18; /* dma_size range */ + tcd->dmod = 0; + tcd->esg = 0; + tcd->soff = 0x4; + tcd->doff = 0; + tcd->ssize = 0x2; + tcd->dsize = 0x2; + tcd->slast = 0; + tcd->dlast_sga = 0; + + sc->tcd = tcd; + + sc->edma_sc->dma_setup(sc->edma_sc, sc->tcd); + + return (0); +} + +static int +saichan_trigger(kobj_t obj, void *data, int go) +{ + struct sc_chinfo *ch = data; + struct sc_pcminfo *scp = ch->parent; + struct sc_info *sc = scp->sc; + + snd_mtxlock(sc->lock); + + switch (go) { + case PCMTRIG_START: +#if 0 + device_printf(scp->dev, "trigger start\n"); +#endif + break; + + case PCMTRIG_STOP: + case PCMTRIG_ABORT: +#if 0 + device_printf(scp->dev, "trigger stop or abort\n"); +#endif + break; + } + + snd_mtxunlock(sc->lock); + + return (0); +} + +static uint32_t +saichan_getptr(kobj_t obj, void *data) +{ + struct sc_pcminfo *scp; + struct sc_chinfo *ch; + struct sc_info *sc; + + ch = data; + scp = ch->parent; + sc = scp->sc; + + return (sc->pos); +} + +static uint32_t sai_pfmt[] = { + /* + * eDMA doesn't allow 24-bit coping, + * so we use 32. + */ + SND_FORMAT(AFMT_S32_LE, 2, 0), + 0 +}; + +static struct pcmchan_caps sai_pcaps = {44100, 192000, sai_pfmt, 0}; + +static struct pcmchan_caps * +saichan_getcaps(kobj_t obj, void *data) +{ + + return (&sai_pcaps); +} + +static kobj_method_t saichan_methods[] = { + KOBJMETHOD(channel_init, saichan_init), + KOBJMETHOD(channel_free, saichan_free), + KOBJMETHOD(channel_setformat, saichan_setformat), + KOBJMETHOD(channel_setspeed, saichan_setspeed), + KOBJMETHOD(channel_setblocksize, saichan_setblocksize), + KOBJMETHOD(channel_trigger, saichan_trigger), + KOBJMETHOD(channel_getptr, saichan_getptr), + KOBJMETHOD(channel_getcaps, saichan_getcaps), + KOBJMETHOD_END +}; +CHANNEL_DECLARE(saichan); + +static int +sai_probe(device_t dev) +{ + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (!ofw_bus_is_compatible(dev, "fsl,mvf600-sai")) + return (ENXIO); + + device_set_desc(dev, "Vybrid Family Synchronous Audio Interface"); + return (BUS_PROBE_DEFAULT); +} + +static void +sai_intr(void *arg) +{ + struct sc_pcminfo *scp; + struct sc_info *sc; + + scp = arg; + sc = scp->sc; + + device_printf(sc->dev, "Error I2S_TCSR == 0x%08x\n", + READ4(sc, I2S_TCSR)); +} + +static void +setup_sai(struct sc_info *sc) +{ + int reg; + + /* + * TCR/RCR registers must not be altered when TCSR[TE] is set. + */ + + reg = READ4(sc, I2S_TCSR); + reg &= ~(TCSR_BCE | TCSR_TE | TCSR_FRDE); + WRITE4(sc, I2S_TCSR, reg); + + reg = READ4(sc, I2S_TCR3); + reg &= ~(TCR3_TCE); + WRITE4(sc, I2S_TCR3, reg); + + reg = (64 << TCR1_TFW_S); + WRITE4(sc, I2S_TCR1, reg); + + reg = READ4(sc, I2S_TCR2); + reg &= ~(TCR2_MSEL_M << TCR2_MSEL_S); + reg |= (1 << TCR2_MSEL_S); + reg |= (TCR2_BCP | TCR2_BCD); + WRITE4(sc, I2S_TCR2, reg); + + sai_configure_clock(sc); + + reg = READ4(sc, I2S_TCR3); + reg |= (TCR3_TCE); + WRITE4(sc, I2S_TCR3, reg); + + /* Configure to 32-bit I2S mode */ + reg = READ4(sc, I2S_TCR4); + reg &= ~(TCR4_FRSZ_M << TCR4_FRSZ_S); + reg |= (1 << TCR4_FRSZ_S); /* 2 words per frame */ + reg &= ~(TCR4_SYWD_M << TCR4_SYWD_S); + reg |= (23 << TCR4_SYWD_S); + reg |= (TCR4_MF | TCR4_FSE | TCR4_FSP | TCR4_FSD); + WRITE4(sc, I2S_TCR4, reg); + + reg = READ4(sc, I2S_TCR5); + reg &= ~(TCR5_W0W_M << TCR5_W0W_S); + reg |= (23 << TCR5_W0W_S); + reg &= ~(TCR5_WNW_M << TCR5_WNW_S); + reg |= (23 << TCR5_WNW_S); + reg &= ~(TCR5_FBT_M << TCR5_FBT_S); + reg |= (31 << TCR5_FBT_S); + WRITE4(sc, I2S_TCR5, reg); + + /* Enable transmitter */ + reg = READ4(sc, I2S_TCSR); + reg |= (TCSR_BCE | TCSR_TE | TCSR_FRDE); + reg |= (1 << 10); /* FEIE */ + WRITE4(sc, I2S_TCSR, reg); +} + + +static void +sai_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int err) +{ + bus_addr_t *addr; + + if (err) + return; + + addr = (bus_addr_t*)arg; + *addr = segs[0].ds_addr; +} + +static int +sai_attach(device_t dev) +{ + char status[SND_STATUSLEN]; + struct sc_pcminfo *scp; + struct sc_info *sc; + int err; + + sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); + sc->dev = dev; + sc->sr = &rate_map[0]; + sc->pos = 0; + + sc->lock = snd_mtxcreate(device_get_nameunit(dev), "sai softc"); + if (sc->lock == NULL) { + device_printf(dev, "Cant create mtx\n"); + return (ENXIO); + } + + if (bus_alloc_resources(dev, sai_spec, sc->res)) { + device_printf(dev, "could not allocate resources\n"); + return (ENXIO); + } + + /* Memory interface */ + sc->bst = rman_get_bustag(sc->res[0]); + sc->bsh = rman_get_bushandle(sc->res[0]); + + /* eDMA */ + if (find_edma_controller(sc)) { + device_printf(dev, "could not find active eDMA\n"); + return (ENXIO); + } + + /* Setup PCM */ + scp = malloc(sizeof(struct sc_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); + scp->sc = sc; + scp->dev = dev; + + /* DMA */ + sc->dma_size = 262144; + + /* + * Must use dma_size boundary as modulo feature required. + * Modulo feature allows setup circular buffer. + */ + + err = bus_dma_tag_create( + bus_get_dma_tag(sc->dev), + 4, sc->dma_size, /* alignment, boundary */ + BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + sc->dma_size, 1, /* maxsize, nsegments */ + sc->dma_size, 0, /* maxsegsize, flags */ + NULL, NULL, /* lockfunc, lockarg */ + &sc->dma_tag); + + err = bus_dmamem_alloc(sc->dma_tag, (void **)&sc->buf_base, + BUS_DMA_NOWAIT | BUS_DMA_COHERENT, &sc->dma_map); + if (err) { + device_printf(dev, "cannot allocate framebuffer\n"); + return (ENXIO); + } + + err = bus_dmamap_load(sc->dma_tag, sc->dma_map, sc->buf_base, + sc->dma_size, sai_dmamap_cb, &sc->buf_base_phys, BUS_DMA_NOWAIT); + if (err) { + device_printf(dev, "cannot load DMA map\n"); + return (ENXIO); + } + + bzero(sc->buf_base, sc->dma_size); + + /* Setup interrupt handler */ + err = bus_setup_intr(dev, sc->res[1], INTR_MPSAFE | INTR_TYPE_AV, + NULL, sai_intr, scp, &sc->ih); + if (err) { + device_printf(dev, "Unable to alloc interrupt resource.\n"); + return (ENXIO); + } + + pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE); + + err = pcm_register(dev, scp, 1, 0); + if (err) { + device_printf(dev, "Can't register pcm.\n"); + return (ENXIO); + } + + scp->chnum = 0; + pcm_addchan(dev, PCMDIR_PLAY, &saichan_class, scp); + scp->chnum++; + + snprintf(status, SND_STATUSLEN, "at simplebus"); + pcm_setstatus(dev, status); + + mixer_init(dev, &saimixer_class, scp); + + setup_dma(scp); + setup_sai(sc); + + return (0); +} + +static device_method_t sai_pcm_methods[] = { + DEVMETHOD(device_probe, sai_probe), + DEVMETHOD(device_attach, sai_attach), + { 0, 0 } +}; + +static driver_t sai_pcm_driver = { + "pcm", + sai_pcm_methods, + PCM_SOFTC_SIZE, +}; + +DRIVER_MODULE(sai, simplebus, sai_pcm_driver, pcm_devclass, 0, 0); +MODULE_DEPEND(sai, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); +MODULE_VERSION(sai, 1); diff --git a/sys/boot/fdt/dts/vybrid.dtsi b/sys/boot/fdt/dts/vybrid.dtsi index 3a41cac..5c815de 100644 --- a/sys/boot/fdt/dts/vybrid.dtsi +++ b/sys/boot/fdt/dts/vybrid.dtsi @@ -311,6 +311,9 @@ interrupt-parent = <&GIC>; status = "disabled"; edma-controller = <&edma1>; + edma-src-receive = < 8 >; + edma-src-transmit = < 9 >; + edma-mux-group = < 1 >; clock_names = "sai3", "cko1"; iomux_config = < 16 0x2 19 0x2 |