diff --git a/sys/arm/allwinner/a10_codec.c b/sys/arm/allwinner/a10_codec.c index 421d1c015691..9294c1e5290c 100644 --- a/sys/arm/allwinner/a10_codec.c +++ b/sys/arm/allwinner/a10_codec.c @@ -1,1206 +1,1205 @@ /*- * Copyright (c) 2014-2016 Jared D. McNeill * 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 ``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 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. */ /* * Allwinner A10/A20 and H3 Audio Codec */ #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include "sunxi_dma_if.h" #include "mixer_if.h" struct a10codec_info; struct a10codec_config { /* mixer class */ struct kobj_class *mixer_class; /* toggle DAC/ADC mute */ void (*mute)(struct a10codec_info *, int, int); /* DRQ types */ u_int drqtype_codec; u_int drqtype_sdram; /* register map */ bus_size_t DPC, DAC_FIFOC, DAC_FIFOS, DAC_TXDATA, ADC_FIFOC, ADC_FIFOS, ADC_RXDATA, DAC_CNT, ADC_CNT; }; #define TX_TRIG_LEVEL 0xf #define RX_TRIG_LEVEL 0x7 #define DRQ_CLR_CNT 0x3 #define AC_DAC_DPC(_sc) ((_sc)->cfg->DPC) #define DAC_DPC_EN_DA 0x80000000 #define AC_DAC_FIFOC(_sc) ((_sc)->cfg->DAC_FIFOC) #define DAC_FIFOC_FS_SHIFT 29 #define DAC_FIFOC_FS_MASK (7U << DAC_FIFOC_FS_SHIFT) #define DAC_FS_48KHZ 0 #define DAC_FS_32KHZ 1 #define DAC_FS_24KHZ 2 #define DAC_FS_16KHZ 3 #define DAC_FS_12KHZ 4 #define DAC_FS_8KHZ 5 #define DAC_FS_192KHZ 6 #define DAC_FS_96KHZ 7 #define DAC_FIFOC_FIFO_MODE_SHIFT 24 #define DAC_FIFOC_FIFO_MODE_MASK (3U << DAC_FIFOC_FIFO_MODE_SHIFT) #define FIFO_MODE_24_31_8 0 #define FIFO_MODE_16_31_16 0 #define FIFO_MODE_16_15_0 1 #define DAC_FIFOC_DRQ_CLR_CNT_SHIFT 21 #define DAC_FIFOC_DRQ_CLR_CNT_MASK (3U << DAC_FIFOC_DRQ_CLR_CNT_SHIFT) #define DAC_FIFOC_TX_TRIG_LEVEL_SHIFT 8 #define DAC_FIFOC_TX_TRIG_LEVEL_MASK (0x7f << DAC_FIFOC_TX_TRIG_LEVEL_SHIFT) #define DAC_FIFOC_MONO_EN (1U << 6) #define DAC_FIFOC_TX_BITS (1U << 5) #define DAC_FIFOC_DRQ_EN (1U << 4) #define DAC_FIFOC_FIFO_FLUSH (1U << 0) #define AC_DAC_FIFOS(_sc) ((_sc)->cfg->DAC_FIFOS) #define AC_DAC_TXDATA(_sc) ((_sc)->cfg->DAC_TXDATA) #define AC_ADC_FIFOC(_sc) ((_sc)->cfg->ADC_FIFOC) #define ADC_FIFOC_FS_SHIFT 29 #define ADC_FIFOC_FS_MASK (7U << ADC_FIFOC_FS_SHIFT) #define ADC_FS_48KHZ 0 #define ADC_FIFOC_EN_AD (1U << 28) #define ADC_FIFOC_RX_FIFO_MODE (1U << 24) #define ADC_FIFOC_RX_TRIG_LEVEL_SHIFT 8 #define ADC_FIFOC_RX_TRIG_LEVEL_MASK (0x1f << ADC_FIFOC_RX_TRIG_LEVEL_SHIFT) #define ADC_FIFOC_MONO_EN (1U << 7) #define ADC_FIFOC_RX_BITS (1U << 6) #define ADC_FIFOC_DRQ_EN (1U << 4) #define ADC_FIFOC_FIFO_FLUSH (1U << 1) #define AC_ADC_FIFOS(_sc) ((_sc)->cfg->ADC_FIFOS) #define AC_ADC_RXDATA(_sc) ((_sc)->cfg->ADC_RXDATA) #define AC_DAC_CNT(_sc) ((_sc)->cfg->DAC_CNT) #define AC_ADC_CNT(_sc) ((_sc)->cfg->ADC_CNT) static uint32_t a10codec_fmt[] = { SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), 0 }; static struct pcmchan_caps a10codec_pcaps = { 8000, 192000, a10codec_fmt, 0 }; static struct pcmchan_caps a10codec_rcaps = { 8000, 48000, a10codec_fmt, 0 }; struct a10codec_info; struct a10codec_chinfo { struct snd_dbuf *buffer; struct pcm_channel *channel; struct a10codec_info *parent; bus_dmamap_t dmamap; void *dmaaddr; bus_addr_t physaddr; bus_size_t fifo; device_t dmac; void *dmachan; int dir; int run; uint32_t pos; uint32_t format; uint32_t blocksize; uint32_t speed; }; struct a10codec_info { device_t dev; struct resource *res[2]; struct mtx *lock; bus_dma_tag_t dmat; unsigned dmasize; void *ih; struct a10codec_config *cfg; struct a10codec_chinfo play; struct a10codec_chinfo rec; }; static struct resource_spec a10codec_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { -1, 0 } }; #define CODEC_ANALOG_READ(sc, reg) bus_read_4((sc)->res[1], (reg)) #define CODEC_ANALOG_WRITE(sc, reg, val) bus_write_4((sc)->res[1], (reg), (val)) #define CODEC_READ(sc, reg) bus_read_4((sc)->res[0], (reg)) #define CODEC_WRITE(sc, reg, val) bus_write_4((sc)->res[0], (reg), (val)) /* * A10/A20 mixer interface */ #define A10_DAC_ACTL 0x10 #define A10_DACAREN (1U << 31) #define A10_DACALEN (1U << 30) #define A10_MIXEN (1U << 29) #define A10_DACPAS (1U << 8) #define A10_PAMUTE (1U << 6) #define A10_PAVOL_SHIFT 0 #define A10_PAVOL_MASK (0x3f << A10_PAVOL_SHIFT) #define A10_ADC_ACTL 0x28 #define A10_ADCREN (1U << 31) #define A10_ADCLEN (1U << 30) #define A10_PREG1EN (1U << 29) #define A10_PREG2EN (1U << 28) #define A10_VMICEN (1U << 27) #define A10_ADCG_SHIFT 20 #define A10_ADCG_MASK (7U << A10_ADCG_SHIFT) #define A10_ADCIS_SHIFT 17 #define A10_ADCIS_MASK (7U << A10_ADCIS_SHIFT) #define A10_ADC_IS_LINEIN 0 #define A10_ADC_IS_FMIN 1 #define A10_ADC_IS_MIC1 2 #define A10_ADC_IS_MIC2 3 #define A10_ADC_IS_MIC1_L_MIC2_R 4 #define A10_ADC_IS_MIC1_LR_MIC2_LR 5 #define A10_ADC_IS_OMIX 6 #define A10_ADC_IS_LINEIN_L_MIC1_R 7 #define A10_LNRDF (1U << 16) #define A10_LNPREG_SHIFT 13 #define A10_LNPREG_MASK (7U << A10_LNPREG_SHIFT) #define A10_PA_EN (1U << 4) #define A10_DDE (1U << 3) static int a10_mixer_init(struct snd_mixer *m) { struct a10codec_info *sc = mix_getdevinfo(m); uint32_t val; mix_setdevs(m, SOUND_MASK_VOLUME | SOUND_MASK_LINE | SOUND_MASK_RECLEV); mix_setrecdevs(m, SOUND_MASK_LINE | SOUND_MASK_LINE1 | SOUND_MASK_MIC); /* Unmute input source to PA */ val = CODEC_READ(sc, A10_DAC_ACTL); val |= A10_PAMUTE; CODEC_WRITE(sc, A10_DAC_ACTL, val); /* Enable PA */ val = CODEC_READ(sc, A10_ADC_ACTL); val |= A10_PA_EN; CODEC_WRITE(sc, A10_ADC_ACTL, val); return (0); } static const struct a10_mixer { unsigned reg; unsigned mask; unsigned shift; } a10_mixers[SOUND_MIXER_NRDEVICES] = { [SOUND_MIXER_VOLUME] = { A10_DAC_ACTL, A10_PAVOL_MASK, A10_PAVOL_SHIFT }, [SOUND_MIXER_LINE] = { A10_ADC_ACTL, A10_LNPREG_MASK, A10_LNPREG_SHIFT }, [SOUND_MIXER_RECLEV] = { A10_ADC_ACTL, A10_ADCG_MASK, A10_ADCG_SHIFT }, }; static int a10_mixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct a10codec_info *sc = mix_getdevinfo(m); uint32_t val; unsigned nvol, max; max = a10_mixers[dev].mask >> a10_mixers[dev].shift; nvol = (left * max) / 100; val = CODEC_READ(sc, a10_mixers[dev].reg); val &= ~a10_mixers[dev].mask; val |= (nvol << a10_mixers[dev].shift); CODEC_WRITE(sc, a10_mixers[dev].reg, val); left = right = (left * 100) / max; return (left | (right << 8)); } static uint32_t a10_mixer_setrecsrc(struct snd_mixer *m, uint32_t src) { struct a10codec_info *sc = mix_getdevinfo(m); uint32_t val; val = CODEC_READ(sc, A10_ADC_ACTL); switch (src) { case SOUND_MASK_LINE: /* line-in */ val &= ~A10_ADCIS_MASK; val |= (A10_ADC_IS_LINEIN << A10_ADCIS_SHIFT); break; case SOUND_MASK_MIC: /* MIC1 */ val &= ~A10_ADCIS_MASK; val |= (A10_ADC_IS_MIC1 << A10_ADCIS_SHIFT); break; case SOUND_MASK_LINE1: /* MIC2 */ val &= ~A10_ADCIS_MASK; val |= (A10_ADC_IS_MIC2 << A10_ADCIS_SHIFT); break; default: break; } CODEC_WRITE(sc, A10_ADC_ACTL, val); switch ((val & A10_ADCIS_MASK) >> A10_ADCIS_SHIFT) { case A10_ADC_IS_LINEIN: return (SOUND_MASK_LINE); case A10_ADC_IS_MIC1: return (SOUND_MASK_MIC); case A10_ADC_IS_MIC2: return (SOUND_MASK_LINE1); default: return (0); } } static void a10_mute(struct a10codec_info *sc, int mute, int dir) { uint32_t val; if (dir == PCMDIR_PLAY) { val = CODEC_READ(sc, A10_DAC_ACTL); if (mute) { /* Disable DAC analog l/r channels and output mixer */ val &= ~A10_DACAREN; val &= ~A10_DACALEN; val &= ~A10_DACPAS; } else { /* Enable DAC analog l/r channels and output mixer */ val |= A10_DACAREN; val |= A10_DACALEN; val |= A10_DACPAS; } CODEC_WRITE(sc, A10_DAC_ACTL, val); } else { val = CODEC_READ(sc, A10_ADC_ACTL); if (mute) { /* Disable ADC analog l/r channels, MIC1 preamp, * and VMIC pin voltage */ val &= ~A10_ADCREN; val &= ~A10_ADCLEN; val &= ~A10_PREG1EN; val &= ~A10_VMICEN; } else { /* Enable ADC analog l/r channels, MIC1 preamp, * and VMIC pin voltage */ val |= A10_ADCREN; val |= A10_ADCLEN; val |= A10_PREG1EN; val |= A10_VMICEN; } CODEC_WRITE(sc, A10_ADC_ACTL, val); } } static kobj_method_t a10_mixer_methods[] = { KOBJMETHOD(mixer_init, a10_mixer_init), KOBJMETHOD(mixer_set, a10_mixer_set), KOBJMETHOD(mixer_setrecsrc, a10_mixer_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(a10_mixer); /* * H3 mixer interface */ #define H3_PR_CFG 0x00 #define H3_AC_PR_RST (1 << 28) #define H3_AC_PR_RW (1 << 24) #define H3_AC_PR_ADDR_SHIFT 16 #define H3_AC_PR_ADDR_MASK (0x1f << H3_AC_PR_ADDR_SHIFT) #define H3_ACDA_PR_WDAT_SHIFT 8 #define H3_ACDA_PR_WDAT_MASK (0xff << H3_ACDA_PR_WDAT_SHIFT) #define H3_ACDA_PR_RDAT_SHIFT 0 #define H3_ACDA_PR_RDAT_MASK (0xff << H3_ACDA_PR_RDAT_SHIFT) #define H3_LOMIXSC 0x01 #define H3_LOMIXSC_LDAC (1 << 1) #define H3_ROMIXSC 0x02 #define H3_ROMIXSC_RDAC (1 << 1) #define H3_DAC_PA_SRC 0x03 #define H3_DACAREN (1 << 7) #define H3_DACALEN (1 << 6) #define H3_RMIXEN (1 << 5) #define H3_LMIXEN (1 << 4) #define H3_LINEIN_GCTR 0x05 #define H3_LINEING_SHIFT 4 #define H3_LINEING_MASK (0x7 << H3_LINEING_SHIFT) #define H3_MIC_GCTR 0x06 #define H3_MIC1_GAIN_SHIFT 4 #define H3_MIC1_GAIN_MASK (0x7 << H3_MIC1_GAIN_SHIFT) #define H3_MIC2_GAIN_SHIFT 0 #define H3_MIC2_GAIN_MASK (0x7 << H3_MIC2_GAIN_SHIFT) #define H3_PAEN_CTR 0x07 #define H3_LINEOUTEN (1 << 7) #define H3_LINEOUT_VOLC 0x09 #define H3_LINEOUTVOL_SHIFT 3 #define H3_LINEOUTVOL_MASK (0x1f << H3_LINEOUTVOL_SHIFT) #define H3_MIC2G_LINEOUT_CTR 0x0a #define H3_LINEOUT_LSEL (1 << 3) #define H3_LINEOUT_RSEL (1 << 2) #define H3_LADCMIXSC 0x0c #define H3_RADCMIXSC 0x0d #define H3_ADCMIXSC_MIC1 (1 << 6) #define H3_ADCMIXSC_MIC2 (1 << 5) #define H3_ADCMIXSC_LINEIN (1 << 2) #define H3_ADCMIXSC_OMIXER (3 << 0) #define H3_ADC_AP_EN 0x0f #define H3_ADCREN (1 << 7) #define H3_ADCLEN (1 << 6) #define H3_ADCG_SHIFT 0 #define H3_ADCG_MASK (0x7 << H3_ADCG_SHIFT) static u_int h3_pr_read(struct a10codec_info *sc, u_int addr) { uint32_t val; /* Read current value */ val = CODEC_ANALOG_READ(sc, H3_PR_CFG); /* De-assert reset */ val |= H3_AC_PR_RST; CODEC_ANALOG_WRITE(sc, H3_PR_CFG, val); /* Read mode */ val &= ~H3_AC_PR_RW; CODEC_ANALOG_WRITE(sc, H3_PR_CFG, val); /* Set address */ val &= ~H3_AC_PR_ADDR_MASK; val |= (addr << H3_AC_PR_ADDR_SHIFT); CODEC_ANALOG_WRITE(sc, H3_PR_CFG, val); /* Read data */ return (CODEC_ANALOG_READ(sc , H3_PR_CFG) & H3_ACDA_PR_RDAT_MASK); } static void h3_pr_write(struct a10codec_info *sc, u_int addr, u_int data) { uint32_t val; /* Read current value */ val = CODEC_ANALOG_READ(sc, H3_PR_CFG); /* De-assert reset */ val |= H3_AC_PR_RST; CODEC_ANALOG_WRITE(sc, H3_PR_CFG, val); /* Set address */ val &= ~H3_AC_PR_ADDR_MASK; val |= (addr << H3_AC_PR_ADDR_SHIFT); CODEC_ANALOG_WRITE(sc, H3_PR_CFG, val); /* Write data */ val &= ~H3_ACDA_PR_WDAT_MASK; val |= (data << H3_ACDA_PR_WDAT_SHIFT); CODEC_ANALOG_WRITE(sc, H3_PR_CFG, val); /* Write mode */ val |= H3_AC_PR_RW; CODEC_ANALOG_WRITE(sc, H3_PR_CFG, val); } static void h3_pr_set_clear(struct a10codec_info *sc, u_int addr, u_int set, u_int clr) { u_int old, new; old = h3_pr_read(sc, addr); new = set | (old & ~clr); h3_pr_write(sc, addr, new); } static int h3_mixer_init(struct snd_mixer *m) { int rid=1; pcell_t reg[2]; phandle_t analogref; struct a10codec_info *sc = mix_getdevinfo(m); if (OF_getencprop(ofw_bus_get_node(sc->dev), "allwinner,codec-analog-controls", &analogref, sizeof(analogref)) <= 0) { return (ENXIO); } if (OF_getencprop(OF_node_from_xref(analogref), "reg", reg, sizeof(reg)) <= 0) { return (ENXIO); } sc->res[1] = bus_alloc_resource(sc->dev, SYS_RES_MEMORY, &rid, reg[0], reg[0]+reg[1], reg[1], RF_ACTIVE ); if (sc->res[1] == NULL) { return (ENXIO); } mix_setdevs(m, SOUND_MASK_PCM | SOUND_MASK_VOLUME | SOUND_MASK_RECLEV | SOUND_MASK_MIC | SOUND_MASK_LINE | SOUND_MASK_LINE1); mix_setrecdevs(m, SOUND_MASK_MIC | SOUND_MASK_LINE | SOUND_MASK_LINE1 | SOUND_MASK_IMIX); pcm_setflags(sc->dev, pcm_getflags(sc->dev) | SD_F_SOFTPCMVOL); /* Right & Left LINEOUT enable */ h3_pr_set_clear(sc, H3_PAEN_CTR, H3_LINEOUTEN, 0); h3_pr_set_clear(sc, H3_MIC2G_LINEOUT_CTR, H3_LINEOUT_LSEL | H3_LINEOUT_RSEL, 0); return (0); } static const struct h3_mixer { unsigned reg; unsigned mask; unsigned shift; } h3_mixers[SOUND_MIXER_NRDEVICES] = { [SOUND_MIXER_VOLUME] = { H3_LINEOUT_VOLC, H3_LINEOUTVOL_MASK, H3_LINEOUTVOL_SHIFT }, [SOUND_MIXER_RECLEV] = { H3_ADC_AP_EN, H3_ADCG_MASK, H3_ADCG_SHIFT }, [SOUND_MIXER_LINE] = { H3_LINEIN_GCTR, H3_LINEING_MASK, H3_LINEING_SHIFT }, [SOUND_MIXER_MIC] = { H3_MIC_GCTR, H3_MIC1_GAIN_MASK, H3_MIC1_GAIN_SHIFT }, [SOUND_MIXER_LINE1] = { H3_MIC_GCTR, H3_MIC2_GAIN_MASK, H3_MIC2_GAIN_SHIFT }, }; static int h3_mixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct a10codec_info *sc = mix_getdevinfo(m); unsigned nvol, max; max = h3_mixers[dev].mask >> h3_mixers[dev].shift; nvol = (left * max) / 100; h3_pr_set_clear(sc, h3_mixers[dev].reg, nvol << h3_mixers[dev].shift, h3_mixers[dev].mask); left = right = (left * 100) / max; return (left | (right << 8)); } static uint32_t h3_mixer_setrecsrc(struct snd_mixer *m, uint32_t src) { struct a10codec_info *sc = mix_getdevinfo(m); uint32_t val; val = 0; src &= (SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_LINE1 | SOUND_MASK_IMIX); if ((src & SOUND_MASK_LINE) != 0) /* line-in */ val |= H3_ADCMIXSC_LINEIN; if ((src & SOUND_MASK_MIC) != 0) /* MIC1 */ val |= H3_ADCMIXSC_MIC1; if ((src & SOUND_MASK_LINE1) != 0) /* MIC2 */ val |= H3_ADCMIXSC_MIC2; if ((src & SOUND_MASK_IMIX) != 0) /* l/r output mixer */ val |= H3_ADCMIXSC_OMIXER; h3_pr_write(sc, H3_LADCMIXSC, val); h3_pr_write(sc, H3_RADCMIXSC, val); return (src); } static void h3_mute(struct a10codec_info *sc, int mute, int dir) { if (dir == PCMDIR_PLAY) { if (mute) { /* Mute DAC l/r channels to output mixer */ h3_pr_set_clear(sc, H3_LOMIXSC, 0, H3_LOMIXSC_LDAC); h3_pr_set_clear(sc, H3_ROMIXSC, 0, H3_ROMIXSC_RDAC); /* Disable DAC analog l/r channels and output mixer */ h3_pr_set_clear(sc, H3_DAC_PA_SRC, 0, H3_DACAREN | H3_DACALEN | H3_RMIXEN | H3_LMIXEN); } else { /* Enable DAC analog l/r channels and output mixer */ h3_pr_set_clear(sc, H3_DAC_PA_SRC, H3_DACAREN | H3_DACALEN | H3_RMIXEN | H3_LMIXEN, 0); /* Unmute DAC l/r channels to output mixer */ h3_pr_set_clear(sc, H3_LOMIXSC, H3_LOMIXSC_LDAC, 0); h3_pr_set_clear(sc, H3_ROMIXSC, H3_ROMIXSC_RDAC, 0); } } else { if (mute) { /* Disable ADC analog l/r channels */ h3_pr_set_clear(sc, H3_ADC_AP_EN, 0, H3_ADCREN | H3_ADCLEN); } else { /* Enable ADC analog l/r channels */ h3_pr_set_clear(sc, H3_ADC_AP_EN, H3_ADCREN | H3_ADCLEN, 0); } } } static kobj_method_t h3_mixer_methods[] = { KOBJMETHOD(mixer_init, h3_mixer_init), KOBJMETHOD(mixer_set, h3_mixer_set), KOBJMETHOD(mixer_setrecsrc, h3_mixer_setrecsrc), KOBJMETHOD_END }; MIXER_DECLARE(h3_mixer); /* * Channel interface */ static void a10codec_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct a10codec_chinfo *ch = arg; if (error != 0) return; ch->physaddr = segs[0].ds_addr; } static void a10codec_transfer(struct a10codec_chinfo *ch) { bus_addr_t src, dst; int error; if (ch->dir == PCMDIR_PLAY) { src = ch->physaddr + ch->pos; dst = ch->fifo; } else { src = ch->fifo; dst = ch->physaddr + ch->pos; } error = SUNXI_DMA_TRANSFER(ch->dmac, ch->dmachan, src, dst, ch->blocksize); if (error) { ch->run = 0; device_printf(ch->parent->dev, "DMA transfer failed: %d\n", error); } } static void a10codec_dmaconfig(struct a10codec_chinfo *ch) { struct a10codec_info *sc = ch->parent; struct sunxi_dma_config conf; memset(&conf, 0, sizeof(conf)); conf.src_width = conf.dst_width = 16; conf.src_burst_len = conf.dst_burst_len = 4; if (ch->dir == PCMDIR_PLAY) { conf.dst_noincr = true; conf.src_drqtype = sc->cfg->drqtype_sdram; conf.dst_drqtype = sc->cfg->drqtype_codec; } else { conf.src_noincr = true; conf.src_drqtype = sc->cfg->drqtype_codec; conf.dst_drqtype = sc->cfg->drqtype_sdram; } SUNXI_DMA_SET_CONFIG(ch->dmac, ch->dmachan, &conf); } static void a10codec_dmaintr(void *priv) { struct a10codec_chinfo *ch = priv; unsigned bufsize; bufsize = sndbuf_getsize(ch->buffer); ch->pos += ch->blocksize; if (ch->pos >= bufsize) ch->pos -= bufsize; if (ch->run) { chn_intr(ch->channel); a10codec_transfer(ch); } } static unsigned a10codec_fs(struct a10codec_chinfo *ch) { switch (ch->speed) { case 48000: return (DAC_FS_48KHZ); case 24000: return (DAC_FS_24KHZ); case 12000: return (DAC_FS_12KHZ); case 192000: return (DAC_FS_192KHZ); case 32000: return (DAC_FS_32KHZ); case 16000: return (DAC_FS_16KHZ); case 8000: return (DAC_FS_8KHZ); case 96000: return (DAC_FS_96KHZ); default: return (DAC_FS_48KHZ); } } static void a10codec_start(struct a10codec_chinfo *ch) { struct a10codec_info *sc = ch->parent; uint32_t val; ch->pos = 0; if (ch->dir == PCMDIR_PLAY) { /* Flush DAC FIFO */ CODEC_WRITE(sc, AC_DAC_FIFOC(sc), DAC_FIFOC_FIFO_FLUSH); /* Clear DAC FIFO status */ CODEC_WRITE(sc, AC_DAC_FIFOS(sc), CODEC_READ(sc, AC_DAC_FIFOS(sc))); /* Unmute output */ sc->cfg->mute(sc, 0, ch->dir); /* Configure DAC DMA channel */ a10codec_dmaconfig(ch); /* Configure DAC FIFO */ CODEC_WRITE(sc, AC_DAC_FIFOC(sc), (AFMT_CHANNEL(ch->format) == 1 ? DAC_FIFOC_MONO_EN : 0) | (a10codec_fs(ch) << DAC_FIFOC_FS_SHIFT) | (FIFO_MODE_16_15_0 << DAC_FIFOC_FIFO_MODE_SHIFT) | (DRQ_CLR_CNT << DAC_FIFOC_DRQ_CLR_CNT_SHIFT) | (TX_TRIG_LEVEL << DAC_FIFOC_TX_TRIG_LEVEL_SHIFT)); /* Enable DAC DRQ */ val = CODEC_READ(sc, AC_DAC_FIFOC(sc)); val |= DAC_FIFOC_DRQ_EN; CODEC_WRITE(sc, AC_DAC_FIFOC(sc), val); } else { /* Flush ADC FIFO */ CODEC_WRITE(sc, AC_ADC_FIFOC(sc), ADC_FIFOC_FIFO_FLUSH); /* Clear ADC FIFO status */ CODEC_WRITE(sc, AC_ADC_FIFOS(sc), CODEC_READ(sc, AC_ADC_FIFOS(sc))); /* Unmute input */ sc->cfg->mute(sc, 0, ch->dir); /* Configure ADC DMA channel */ a10codec_dmaconfig(ch); /* Configure ADC FIFO */ CODEC_WRITE(sc, AC_ADC_FIFOC(sc), ADC_FIFOC_EN_AD | ADC_FIFOC_RX_FIFO_MODE | (AFMT_CHANNEL(ch->format) == 1 ? ADC_FIFOC_MONO_EN : 0) | (a10codec_fs(ch) << ADC_FIFOC_FS_SHIFT) | (RX_TRIG_LEVEL << ADC_FIFOC_RX_TRIG_LEVEL_SHIFT)); /* Enable ADC DRQ */ val = CODEC_READ(sc, AC_ADC_FIFOC(sc)); val |= ADC_FIFOC_DRQ_EN; CODEC_WRITE(sc, AC_ADC_FIFOC(sc), val); } /* Start DMA transfer */ a10codec_transfer(ch); } static void a10codec_stop(struct a10codec_chinfo *ch) { struct a10codec_info *sc = ch->parent; /* Disable DMA channel */ SUNXI_DMA_HALT(ch->dmac, ch->dmachan); sc->cfg->mute(sc, 1, ch->dir); if (ch->dir == PCMDIR_PLAY) { /* Disable DAC DRQ */ CODEC_WRITE(sc, AC_DAC_FIFOC(sc), 0); } else { /* Disable ADC DRQ */ CODEC_WRITE(sc, AC_ADC_FIFOC(sc), 0); } } static void * a10codec_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct a10codec_info *sc = devinfo; struct a10codec_chinfo *ch = dir == PCMDIR_PLAY ? &sc->play : &sc->rec; phandle_t xref; pcell_t *cells; int ncells, error; error = ofw_bus_parse_xref_list_alloc(ofw_bus_get_node(sc->dev), "dmas", "#dma-cells", dir == PCMDIR_PLAY ? 1 : 0, &xref, &ncells, &cells); if (error != 0) { device_printf(sc->dev, "cannot parse 'dmas' property\n"); return (NULL); } OF_prop_free(cells); ch->parent = sc; ch->channel = c; ch->buffer = b; ch->dir = dir; ch->fifo = rman_get_start(sc->res[0]) + (dir == PCMDIR_REC ? AC_ADC_RXDATA(sc) : AC_DAC_TXDATA(sc)); ch->dmac = OF_device_from_xref(xref); if (ch->dmac == NULL) { device_printf(sc->dev, "cannot find DMA controller\n"); device_printf(sc->dev, "xref = 0x%x\n", (u_int)xref); return (NULL); } ch->dmachan = SUNXI_DMA_ALLOC(ch->dmac, false, a10codec_dmaintr, ch); if (ch->dmachan == NULL) { device_printf(sc->dev, "cannot allocate DMA channel\n"); return (NULL); } error = bus_dmamem_alloc(sc->dmat, &ch->dmaaddr, BUS_DMA_NOWAIT | BUS_DMA_COHERENT, &ch->dmamap); if (error != 0) { device_printf(sc->dev, "cannot allocate channel buffer\n"); return (NULL); } error = bus_dmamap_load(sc->dmat, ch->dmamap, ch->dmaaddr, sc->dmasize, a10codec_dmamap_cb, ch, BUS_DMA_NOWAIT); if (error != 0) { device_printf(sc->dev, "cannot load DMA map\n"); return (NULL); } memset(ch->dmaaddr, 0, sc->dmasize); if (sndbuf_setup(ch->buffer, ch->dmaaddr, sc->dmasize) != 0) { device_printf(sc->dev, "cannot setup sndbuf\n"); return (NULL); } return (ch); } static int a10codec_chan_free(kobj_t obj, void *data) { struct a10codec_chinfo *ch = data; struct a10codec_info *sc = ch->parent; SUNXI_DMA_FREE(ch->dmac, ch->dmachan); bus_dmamap_unload(sc->dmat, ch->dmamap); bus_dmamem_free(sc->dmat, ch->dmaaddr, ch->dmamap); return (0); } static int a10codec_chan_setformat(kobj_t obj, void *data, uint32_t format) { struct a10codec_chinfo *ch = data; ch->format = format; return (0); } static uint32_t a10codec_chan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct a10codec_chinfo *ch = data; /* * The codec supports full duplex operation but both DAC and ADC * use the same source clock (PLL2). Limit the available speeds to * those supported by a 24576000 Hz input. */ switch (speed) { case 8000: case 12000: case 16000: case 24000: case 32000: case 48000: ch->speed = speed; break; case 96000: case 192000: /* 96 KHz / 192 KHz mode only supported for playback */ if (ch->dir == PCMDIR_PLAY) { ch->speed = speed; } else { ch->speed = 48000; } break; case 44100: ch->speed = 48000; break; case 22050: ch->speed = 24000; break; case 11025: ch->speed = 12000; break; default: ch->speed = 48000; break; } return (ch->speed); } static uint32_t a10codec_chan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { struct a10codec_chinfo *ch = data; ch->blocksize = blocksize & ~3; return (ch->blocksize); } static int a10codec_chan_trigger(kobj_t obj, void *data, int go) { struct a10codec_chinfo *ch = data; struct a10codec_info *sc = ch->parent; if (!PCMTRIG_COMMON(go)) return (0); snd_mtxlock(sc->lock); switch (go) { case PCMTRIG_START: ch->run = 1; a10codec_stop(ch); a10codec_start(ch); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: ch->run = 0; a10codec_stop(ch); break; default: break; } snd_mtxunlock(sc->lock); return (0); } static uint32_t a10codec_chan_getptr(kobj_t obj, void *data) { struct a10codec_chinfo *ch = data; return (ch->pos); } static struct pcmchan_caps * a10codec_chan_getcaps(kobj_t obj, void *data) { struct a10codec_chinfo *ch = data; if (ch->dir == PCMDIR_PLAY) { return (&a10codec_pcaps); } else { return (&a10codec_rcaps); } } static kobj_method_t a10codec_chan_methods[] = { KOBJMETHOD(channel_init, a10codec_chan_init), KOBJMETHOD(channel_free, a10codec_chan_free), KOBJMETHOD(channel_setformat, a10codec_chan_setformat), KOBJMETHOD(channel_setspeed, a10codec_chan_setspeed), KOBJMETHOD(channel_setblocksize, a10codec_chan_setblocksize), KOBJMETHOD(channel_trigger, a10codec_chan_trigger), KOBJMETHOD(channel_getptr, a10codec_chan_getptr), KOBJMETHOD(channel_getcaps, a10codec_chan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(a10codec_chan); /* * Device interface */ static const struct a10codec_config a10_config = { .mixer_class = &a10_mixer_class, .mute = a10_mute, .drqtype_codec = 19, .drqtype_sdram = 22, .DPC = 0x00, .DAC_FIFOC = 0x04, .DAC_FIFOS = 0x08, .DAC_TXDATA = 0x0c, .ADC_FIFOC = 0x1c, .ADC_FIFOS = 0x20, .ADC_RXDATA = 0x24, .DAC_CNT = 0x30, .ADC_CNT = 0x34, }; static const struct a10codec_config h3_config = { .mixer_class = &h3_mixer_class, .mute = h3_mute, .drqtype_codec = 15, .drqtype_sdram = 1, .DPC = 0x00, .DAC_FIFOC = 0x04, .DAC_FIFOS = 0x08, .DAC_TXDATA = 0x20, .ADC_FIFOC = 0x10, .ADC_FIFOS = 0x14, .ADC_RXDATA = 0x18, .DAC_CNT = 0x40, .ADC_CNT = 0x44, }; static struct ofw_compat_data compat_data[] = { { "allwinner,sun4i-a10-codec", (uintptr_t)&a10_config }, { "allwinner,sun7i-a20-codec", (uintptr_t)&a10_config }, { "allwinner,sun8i-h3-codec", (uintptr_t)&h3_config }, { NULL, 0 } }; static int a10codec_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) return (ENXIO); device_set_desc(dev, "Allwinner Audio Codec"); return (BUS_PROBE_DEFAULT); } static int a10codec_attach(device_t dev) { struct a10codec_info *sc; char status[SND_STATUSLEN]; struct gpiobus_pin *pa_pin; phandle_t node; clk_t clk_bus, clk_codec; hwreset_t rst; uint32_t val; int error; node = ofw_bus_get_node(dev); sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->cfg = (void *)ofw_bus_search_compatible(dev, compat_data)->ocd_data; sc->dev = dev; sc->lock = snd_mtxcreate(device_get_nameunit(dev), "a10codec softc"); if (bus_alloc_resources(dev, a10codec_spec, sc->res)) { device_printf(dev, "cannot allocate resources for device\n"); error = ENXIO; goto fail; } sc->dmasize = 131072; error = bus_dma_tag_create( bus_get_dma_tag(dev), 4, sc->dmasize, /* alignment, boundary */ BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ sc->dmasize, 1, /* maxsize, nsegs */ sc->dmasize, 0, /* maxsegsize, flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->dmat); if (error != 0) { device_printf(dev, "cannot create DMA tag\n"); goto fail; } /* Get clocks */ if (clk_get_by_ofw_name(dev, 0, "apb", &clk_bus) != 0 && clk_get_by_ofw_name(dev, 0, "ahb", &clk_bus) != 0) { device_printf(dev, "cannot find bus clock\n"); goto fail; } if (clk_get_by_ofw_name(dev, 0, "codec", &clk_codec) != 0) { device_printf(dev, "cannot find codec clock\n"); goto fail; } /* Gating bus clock for codec */ if (clk_enable(clk_bus) != 0) { device_printf(dev, "cannot enable bus clock\n"); goto fail; } /* Activate audio codec clock. According to the A10 and A20 user * manuals, Audio_pll can be either 24.576MHz or 22.5792MHz. Most * audio sampling rates require an 24.576MHz input clock with the * exception of 44.1kHz, 22.05kHz, and 11.025kHz. Unfortunately, * both capture and playback use the same clock source so to * safely support independent full duplex operation, we use a fixed * 24.576MHz clock source and don't advertise native support for * the three sampling rates that require a 22.5792MHz input. */ error = clk_set_freq(clk_codec, 24576000, CLK_SET_ROUND_DOWN); if (error != 0) { device_printf(dev, "cannot set codec clock frequency\n"); goto fail; } /* Enable audio codec clock */ error = clk_enable(clk_codec); if (error != 0) { device_printf(dev, "cannot enable codec clock\n"); goto fail; } /* De-assert hwreset */ if (hwreset_get_by_ofw_idx(dev, 0, 0, &rst) == 0) { error = hwreset_deassert(rst); if (error != 0) { device_printf(dev, "cannot de-assert reset\n"); goto fail; } } /* Enable DAC */ val = CODEC_READ(sc, AC_DAC_DPC(sc)); val |= DAC_DPC_EN_DA; CODEC_WRITE(sc, AC_DAC_DPC(sc), val); if (mixer_init(dev, sc->cfg->mixer_class, sc)) { device_printf(dev, "mixer_init failed\n"); goto fail; } /* Unmute PA */ if (gpio_pin_get_by_ofw_property(dev, node, "allwinner,pa-gpios", &pa_pin) == 0) { error = gpio_pin_set_active(pa_pin, 1); if (error != 0) device_printf(dev, "failed to unmute PA\n"); } pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE); if (pcm_register(dev, sc, 1, 1)) { device_printf(dev, "pcm_register failed\n"); goto fail; } pcm_addchan(dev, PCMDIR_PLAY, &a10codec_chan_class, sc); pcm_addchan(dev, PCMDIR_REC, &a10codec_chan_class, sc); snprintf(status, SND_STATUSLEN, "at %s", ofw_bus_get_name(dev)); pcm_setstatus(dev, status); return (0); fail: bus_release_resources(dev, a10codec_spec, sc->res); snd_mtxfree(sc->lock); free(sc, M_DEVBUF); return (ENXIO); } static device_method_t a10codec_pcm_methods[] = { /* Device interface */ DEVMETHOD(device_probe, a10codec_probe), DEVMETHOD(device_attach, a10codec_attach), DEVMETHOD_END }; static driver_t a10codec_pcm_driver = { "pcm", a10codec_pcm_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(a10codec, simplebus, a10codec_pcm_driver, 0, 0); MODULE_DEPEND(a10codec, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(a10codec, 1); diff --git a/sys/arm/broadcom/bcm2835/bcm2835_audio.c b/sys/arm/broadcom/bcm2835/bcm2835_audio.c index e0f525fa9520..75a0c81f29a1 100644 --- a/sys/arm/broadcom/bcm2835/bcm2835_audio.c +++ b/sys/arm/broadcom/bcm2835/bcm2835_audio.c @@ -1,968 +1,967 @@ /*- * Copyright (c) 2015 Oleksandr Tymoshenko * * 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. */ #ifdef HAVE_KERNEL_OPTION_HEADERS #include "opt_snd.h" #endif #include -#include #include "mixer_if.h" #include "interface/compat/vchi_bsd.h" #include "interface/vchi/vchi.h" #include "interface/vchiq_arm/vchiq.h" #include "vc_vchi_audioserv_defs.h" /* Audio destination */ #define DEST_AUTO 0 #define DEST_HEADPHONES 1 #define DEST_HDMI 2 /* Playback state */ #define PLAYBACK_IDLE 0 #define PLAYBACK_PLAYING 1 #define PLAYBACK_STOPPING 2 /* Worker thread state */ #define WORKER_RUNNING 0 #define WORKER_STOPPING 1 #define WORKER_STOPPED 2 /* * Worker thread flags, set to 1 in flags_pending * when driver requests one or another operation * from worker. Cleared to 0 once worker performs * the operations. */ #define AUDIO_PARAMS (1 << 0) #define AUDIO_PLAY (1 << 1) #define AUDIO_STOP (1 << 2) #define VCHIQ_AUDIO_PACKET_SIZE 4000 #define VCHIQ_AUDIO_BUFFER_SIZE 10*VCHIQ_AUDIO_PACKET_SIZE #define VCHIQ_AUDIO_MAX_VOLUME /* volume in terms of 0.01dB */ #define VCHIQ_AUDIO_VOLUME_MIN -10239 #define VCHIQ_AUDIO_VOLUME(db100) (uint32_t)(-((db100) << 8)/100) /* dB levels with 5% volume step */ static int db_levels[] = { VCHIQ_AUDIO_VOLUME_MIN, -4605, -3794, -3218, -2772, -2407, -2099, -1832, -1597, -1386, -1195, -1021, -861, -713, -575, -446, -325, -210, -102, 0, }; static uint32_t bcm2835_audio_playfmt[] = { SND_FORMAT(AFMT_U8, 1, 0), SND_FORMAT(AFMT_U8, 2, 0), SND_FORMAT(AFMT_S8, 1, 0), SND_FORMAT(AFMT_S8, 2, 0), SND_FORMAT(AFMT_S16_LE, 1, 0), SND_FORMAT(AFMT_S16_LE, 2, 0), SND_FORMAT(AFMT_U16_LE, 1, 0), SND_FORMAT(AFMT_U16_LE, 2, 0), 0 }; static struct pcmchan_caps bcm2835_audio_playcaps = {8000, 48000, bcm2835_audio_playfmt, 0}; struct bcm2835_audio_info; struct bcm2835_audio_chinfo { struct bcm2835_audio_info *parent; struct pcm_channel *channel; struct snd_dbuf *buffer; uint32_t fmt, spd, blksz; /* Pointer to first unsubmitted sample */ uint32_t unsubmittedptr; /* * Number of bytes in "submitted but not played" * pseudo-buffer */ int available_space; int playback_state; uint64_t callbacks; uint64_t submitted_samples; uint64_t retrieved_samples; uint64_t underruns; int starved; }; struct bcm2835_audio_info { device_t dev; unsigned int bufsz; struct bcm2835_audio_chinfo pch; uint32_t dest, volume; struct intr_config_hook intr_hook; /* VCHI data */ VCHI_INSTANCE_T vchi_instance; VCHI_CONNECTION_T *vchi_connection; VCHI_SERVICE_HANDLE_T vchi_handle; struct mtx lock; struct cv worker_cv; uint32_t flags_pending; /* Worker thread state */ int worker_state; }; #define BCM2835_AUDIO_LOCK(sc) mtx_lock(&(sc)->lock) #define BCM2835_AUDIO_LOCKED(sc) mtx_assert(&(sc)->lock, MA_OWNED) #define BCM2835_AUDIO_UNLOCK(sc) mtx_unlock(&(sc)->lock) static const char * dest_description(uint32_t dest) { switch (dest) { case DEST_AUTO: return "AUTO"; break; case DEST_HEADPHONES: return "HEADPHONES"; break; case DEST_HDMI: return "HDMI"; break; default: return "UNKNOWN"; break; } } static void bcm2835_worker_update_params(struct bcm2835_audio_info *sc) { BCM2835_AUDIO_LOCKED(sc); sc->flags_pending |= AUDIO_PARAMS; cv_signal(&sc->worker_cv); } static void bcm2835_worker_play_start(struct bcm2835_audio_info *sc) { BCM2835_AUDIO_LOCK(sc); sc->flags_pending &= ~(AUDIO_STOP); sc->flags_pending |= AUDIO_PLAY; cv_signal(&sc->worker_cv); BCM2835_AUDIO_UNLOCK(sc); } static void bcm2835_worker_play_stop(struct bcm2835_audio_info *sc) { BCM2835_AUDIO_LOCK(sc); sc->flags_pending &= ~(AUDIO_PLAY); sc->flags_pending |= AUDIO_STOP; cv_signal(&sc->worker_cv); BCM2835_AUDIO_UNLOCK(sc); } static void bcm2835_audio_callback(void *param, const VCHI_CALLBACK_REASON_T reason, void *msg_handle) { struct bcm2835_audio_info *sc = (struct bcm2835_audio_info *)param; int32_t status; uint32_t msg_len; VC_AUDIO_MSG_T m; if (reason != VCHI_CALLBACK_MSG_AVAILABLE) return; status = vchi_msg_dequeue(sc->vchi_handle, &m, sizeof m, &msg_len, VCHI_FLAGS_NONE); if (status != 0) return; if (m.type == VC_AUDIO_MSG_TYPE_RESULT) { if (m.u.result.success) { device_printf(sc->dev, "msg type %08x failed\n", m.type); } } else if (m.type == VC_AUDIO_MSG_TYPE_COMPLETE) { struct bcm2835_audio_chinfo *ch = m.u.complete.cookie; int count = m.u.complete.count & 0xffff; int perr = (m.u.complete.count & (1U << 30)) != 0; ch->callbacks++; if (perr) ch->underruns++; BCM2835_AUDIO_LOCK(sc); if (ch->playback_state != PLAYBACK_IDLE) { /* Prevent LOR */ BCM2835_AUDIO_UNLOCK(sc); chn_intr(sc->pch.channel); BCM2835_AUDIO_LOCK(sc); } /* We should check again, state might have changed */ if (ch->playback_state != PLAYBACK_IDLE) { if (!perr) { if ((ch->available_space + count)> VCHIQ_AUDIO_BUFFER_SIZE) { device_printf(sc->dev, "inconsistent data in callback:\n"); device_printf(sc->dev, "available_space == %d, count = %d, perr=%d\n", ch->available_space, count, perr); device_printf(sc->dev, "retrieved_samples = %lld, submitted_samples = %lld\n", ch->retrieved_samples, ch->submitted_samples); } ch->available_space += count; ch->retrieved_samples += count; } if (perr || (ch->available_space >= VCHIQ_AUDIO_PACKET_SIZE)) cv_signal(&sc->worker_cv); } BCM2835_AUDIO_UNLOCK(sc); } else printf("%s: unknown m.type: %d\n", __func__, m.type); } /* VCHIQ stuff */ static void bcm2835_audio_init(struct bcm2835_audio_info *sc) { int status; /* Initialize and create a VCHI connection */ status = vchi_initialise(&sc->vchi_instance); if (status != 0) { printf("vchi_initialise failed: %d\n", status); return; } status = vchi_connect(NULL, 0, sc->vchi_instance); if (status != 0) { printf("vchi_connect failed: %d\n", status); return; } SERVICE_CREATION_T params = { VCHI_VERSION_EX(VC_AUDIOSERV_VER, VC_AUDIOSERV_MIN_VER), VC_AUDIO_SERVER_NAME, /* 4cc service code */ sc->vchi_connection, /* passed in fn pointers */ 0, /* rx fifo size */ 0, /* tx fifo size */ bcm2835_audio_callback, /* service callback */ sc, /* service callback parameter */ 1, 1, 0 /* want crc check on bulk transfers */ }; status = vchi_service_open(sc->vchi_instance, ¶ms, &sc->vchi_handle); if (status != 0) sc->vchi_handle = VCHIQ_SERVICE_HANDLE_INVALID; } static void bcm2835_audio_release(struct bcm2835_audio_info *sc) { int success; if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) { success = vchi_service_close(sc->vchi_handle); if (success != 0) printf("vchi_service_close failed: %d\n", success); vchi_service_release(sc->vchi_handle); sc->vchi_handle = VCHIQ_SERVICE_HANDLE_INVALID; } vchi_disconnect(sc->vchi_instance); } static void bcm2835_audio_reset_channel(struct bcm2835_audio_chinfo *ch) { ch->available_space = VCHIQ_AUDIO_BUFFER_SIZE; ch->unsubmittedptr = 0; sndbuf_reset(ch->buffer); } static void bcm2835_audio_start(struct bcm2835_audio_chinfo *ch) { VC_AUDIO_MSG_T m; int ret; struct bcm2835_audio_info *sc = ch->parent; if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) { m.type = VC_AUDIO_MSG_TYPE_START; ret = vchi_msg_queue(sc->vchi_handle, &m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL); if (ret != 0) printf("%s: vchi_msg_queue failed (err %d)\n", __func__, ret); } } static void bcm2835_audio_stop(struct bcm2835_audio_chinfo *ch) { VC_AUDIO_MSG_T m; int ret; struct bcm2835_audio_info *sc = ch->parent; if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) { m.type = VC_AUDIO_MSG_TYPE_STOP; m.u.stop.draining = 0; ret = vchi_msg_queue(sc->vchi_handle, &m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL); if (ret != 0) printf("%s: vchi_msg_queue failed (err %d)\n", __func__, ret); } } static void bcm2835_audio_open(struct bcm2835_audio_info *sc) { VC_AUDIO_MSG_T m; int ret; if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) { m.type = VC_AUDIO_MSG_TYPE_OPEN; ret = vchi_msg_queue(sc->vchi_handle, &m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL); if (ret != 0) printf("%s: vchi_msg_queue failed (err %d)\n", __func__, ret); } } static void bcm2835_audio_update_controls(struct bcm2835_audio_info *sc, uint32_t volume, uint32_t dest) { VC_AUDIO_MSG_T m; int ret, db; if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) { m.type = VC_AUDIO_MSG_TYPE_CONTROL; m.u.control.dest = dest; if (volume > 99) volume = 99; db = db_levels[volume/5]; m.u.control.volume = VCHIQ_AUDIO_VOLUME(db); ret = vchi_msg_queue(sc->vchi_handle, &m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL); if (ret != 0) printf("%s: vchi_msg_queue failed (err %d)\n", __func__, ret); } } static void bcm2835_audio_update_params(struct bcm2835_audio_info *sc, uint32_t fmt, uint32_t speed) { VC_AUDIO_MSG_T m; int ret; if (sc->vchi_handle != VCHIQ_SERVICE_HANDLE_INVALID) { m.type = VC_AUDIO_MSG_TYPE_CONFIG; m.u.config.channels = AFMT_CHANNEL(fmt); m.u.config.samplerate = speed; m.u.config.bps = AFMT_BIT(fmt); ret = vchi_msg_queue(sc->vchi_handle, &m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL); if (ret != 0) printf("%s: vchi_msg_queue failed (err %d)\n", __func__, ret); } } static bool bcm2835_audio_buffer_should_sleep(struct bcm2835_audio_chinfo *ch) { if (ch->playback_state != PLAYBACK_PLAYING) return (true); /* Not enough data */ if (sndbuf_getready(ch->buffer) < VCHIQ_AUDIO_PACKET_SIZE) { printf("starve\n"); ch->starved++; return (true); } /* Not enough free space */ if (ch->available_space < VCHIQ_AUDIO_PACKET_SIZE) { return (true); } return (false); } static void bcm2835_audio_write_samples(struct bcm2835_audio_chinfo *ch, void *buf, uint32_t count) { struct bcm2835_audio_info *sc = ch->parent; VC_AUDIO_MSG_T m; int ret; if (sc->vchi_handle == VCHIQ_SERVICE_HANDLE_INVALID) { return; } m.type = VC_AUDIO_MSG_TYPE_WRITE; m.u.write.count = count; m.u.write.max_packet = VCHIQ_AUDIO_PACKET_SIZE; m.u.write.callback = NULL; m.u.write.cookie = ch; m.u.write.silence = 0; ret = vchi_msg_queue(sc->vchi_handle, &m, sizeof m, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL); if (ret != 0) printf("%s: vchi_msg_queue failed (err %d)\n", __func__, ret); while (count > 0) { int bytes = MIN((int)m.u.write.max_packet, (int)count); ret = vchi_msg_queue(sc->vchi_handle, buf, bytes, VCHI_FLAGS_BLOCK_UNTIL_QUEUED, NULL); if (ret != 0) printf("%s: vchi_msg_queue failed: %d\n", __func__, ret); buf = (char *)buf + bytes; count -= bytes; } } static void bcm2835_audio_worker(void *data) { struct bcm2835_audio_info *sc = (struct bcm2835_audio_info *)data; struct bcm2835_audio_chinfo *ch = &sc->pch; uint32_t speed, format; uint32_t volume, dest; uint32_t flags; uint32_t count, size, readyptr; uint8_t *buf; ch->playback_state = PLAYBACK_IDLE; while (1) { if (sc->worker_state != WORKER_RUNNING) break; BCM2835_AUDIO_LOCK(sc); /* * wait until there are flags set or buffer is ready * to consume more samples */ while ((sc->flags_pending == 0) && bcm2835_audio_buffer_should_sleep(ch)) { cv_wait_sig(&sc->worker_cv, &sc->lock); } flags = sc->flags_pending; /* Clear pending flags */ sc->flags_pending = 0; BCM2835_AUDIO_UNLOCK(sc); /* Requested to change parameters */ if (flags & AUDIO_PARAMS) { BCM2835_AUDIO_LOCK(sc); speed = ch->spd; format = ch->fmt; volume = sc->volume; dest = sc->dest; BCM2835_AUDIO_UNLOCK(sc); if (ch->playback_state == PLAYBACK_IDLE) bcm2835_audio_update_params(sc, format, speed); bcm2835_audio_update_controls(sc, volume, dest); } /* Requested to stop playback */ if ((flags & AUDIO_STOP) && (ch->playback_state == PLAYBACK_PLAYING)) { bcm2835_audio_stop(ch); BCM2835_AUDIO_LOCK(sc); bcm2835_audio_reset_channel(&sc->pch); ch->playback_state = PLAYBACK_IDLE; BCM2835_AUDIO_UNLOCK(sc); continue; } /* Requested to start playback */ if ((flags & AUDIO_PLAY) && (ch->playback_state == PLAYBACK_IDLE)) { BCM2835_AUDIO_LOCK(sc); ch->playback_state = PLAYBACK_PLAYING; BCM2835_AUDIO_UNLOCK(sc); bcm2835_audio_start(ch); } if (ch->playback_state == PLAYBACK_IDLE) continue; if (sndbuf_getready(ch->buffer) == 0) continue; count = sndbuf_getready(ch->buffer); size = sndbuf_getsize(ch->buffer); readyptr = sndbuf_getreadyptr(ch->buffer); BCM2835_AUDIO_LOCK(sc); if (readyptr + count > size) count = size - readyptr; count = min(count, ch->available_space); count -= (count % VCHIQ_AUDIO_PACKET_SIZE); BCM2835_AUDIO_UNLOCK(sc); if (count < VCHIQ_AUDIO_PACKET_SIZE) continue; buf = (uint8_t*)sndbuf_getbuf(ch->buffer) + readyptr; bcm2835_audio_write_samples(ch, buf, count); BCM2835_AUDIO_LOCK(sc); ch->unsubmittedptr = (ch->unsubmittedptr + count) % sndbuf_getsize(ch->buffer); ch->available_space -= count; ch->submitted_samples += count; KASSERT(ch->available_space >= 0, ("ch->available_space == %d\n", ch->available_space)); BCM2835_AUDIO_UNLOCK(sc); } BCM2835_AUDIO_LOCK(sc); sc->worker_state = WORKER_STOPPED; cv_signal(&sc->worker_cv); BCM2835_AUDIO_UNLOCK(sc); kproc_exit(0); } static void bcm2835_audio_create_worker(struct bcm2835_audio_info *sc) { struct proc *newp; sc->worker_state = WORKER_RUNNING; if (kproc_create(bcm2835_audio_worker, (void*)sc, &newp, 0, 0, "bcm2835_audio_worker") != 0) { printf("failed to create bcm2835_audio_worker\n"); } } /* -------------------------------------------------------------------- */ /* channel interface for VCHI audio */ static void * bcmchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct bcm2835_audio_info *sc = devinfo; struct bcm2835_audio_chinfo *ch = &sc->pch; void *buffer; if (dir == PCMDIR_REC) return NULL; ch->parent = sc; ch->channel = c; ch->buffer = b; /* default values */ ch->spd = 44100; ch->fmt = SND_FORMAT(AFMT_S16_LE, 2, 0); ch->blksz = VCHIQ_AUDIO_PACKET_SIZE; buffer = malloc(sc->bufsz, M_DEVBUF, M_WAITOK | M_ZERO); if (sndbuf_setup(ch->buffer, buffer, sc->bufsz) != 0) { device_printf(sc->dev, "sndbuf_setup failed\n"); free(buffer, M_DEVBUF); return NULL; } BCM2835_AUDIO_LOCK(sc); bcm2835_worker_update_params(sc); BCM2835_AUDIO_UNLOCK(sc); return ch; } static int bcmchan_free(kobj_t obj, void *data) { struct bcm2835_audio_chinfo *ch = data; void *buffer; buffer = sndbuf_getbuf(ch->buffer); if (buffer) free(buffer, M_DEVBUF); return (0); } static int bcmchan_setformat(kobj_t obj, void *data, uint32_t format) { struct bcm2835_audio_chinfo *ch = data; struct bcm2835_audio_info *sc = ch->parent; BCM2835_AUDIO_LOCK(sc); ch->fmt = format; bcm2835_worker_update_params(sc); BCM2835_AUDIO_UNLOCK(sc); return 0; } static uint32_t bcmchan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct bcm2835_audio_chinfo *ch = data; struct bcm2835_audio_info *sc = ch->parent; BCM2835_AUDIO_LOCK(sc); ch->spd = speed; bcm2835_worker_update_params(sc); BCM2835_AUDIO_UNLOCK(sc); return ch->spd; } static uint32_t bcmchan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { struct bcm2835_audio_chinfo *ch = data; return ch->blksz; } static int bcmchan_trigger(kobj_t obj, void *data, int go) { struct bcm2835_audio_chinfo *ch = data; struct bcm2835_audio_info *sc = ch->parent; if (!PCMTRIG_COMMON(go)) return (0); switch (go) { case PCMTRIG_START: /* kickstart data flow */ chn_intr(sc->pch.channel); ch->submitted_samples = 0; ch->retrieved_samples = 0; bcm2835_worker_play_start(sc); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: bcm2835_worker_play_stop(sc); break; default: break; } return 0; } static uint32_t bcmchan_getptr(kobj_t obj, void *data) { struct bcm2835_audio_chinfo *ch = data; struct bcm2835_audio_info *sc = ch->parent; uint32_t ret; BCM2835_AUDIO_LOCK(sc); ret = ch->unsubmittedptr; BCM2835_AUDIO_UNLOCK(sc); return ret; } static struct pcmchan_caps * bcmchan_getcaps(kobj_t obj, void *data) { return &bcm2835_audio_playcaps; } static kobj_method_t bcmchan_methods[] = { KOBJMETHOD(channel_init, bcmchan_init), KOBJMETHOD(channel_free, bcmchan_free), KOBJMETHOD(channel_setformat, bcmchan_setformat), KOBJMETHOD(channel_setspeed, bcmchan_setspeed), KOBJMETHOD(channel_setblocksize, bcmchan_setblocksize), KOBJMETHOD(channel_trigger, bcmchan_trigger), KOBJMETHOD(channel_getptr, bcmchan_getptr), KOBJMETHOD(channel_getcaps, bcmchan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(bcmchan); /************************************************************/ static int bcmmix_init(struct snd_mixer *m) { mix_setdevs(m, SOUND_MASK_VOLUME); return (0); } static int bcmmix_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct bcm2835_audio_info *sc = mix_getdevinfo(m); switch (dev) { case SOUND_MIXER_VOLUME: BCM2835_AUDIO_LOCK(sc); sc->volume = left; bcm2835_worker_update_params(sc); BCM2835_AUDIO_UNLOCK(sc); break; default: break; } return left | (left << 8); } static kobj_method_t bcmmixer_methods[] = { KOBJMETHOD(mixer_init, bcmmix_init), KOBJMETHOD(mixer_set, bcmmix_set), KOBJMETHOD_END }; MIXER_DECLARE(bcmmixer); static int sysctl_bcm2835_audio_dest(SYSCTL_HANDLER_ARGS) { struct bcm2835_audio_info *sc = arg1; int val; int err; val = sc->dest; err = sysctl_handle_int(oidp, &val, 0, req); if (err || !req->newptr) /* error || read request */ return (err); if ((val < 0) || (val > 2)) return (EINVAL); BCM2835_AUDIO_LOCK(sc); sc->dest = val; bcm2835_worker_update_params(sc); BCM2835_AUDIO_UNLOCK(sc); if (bootverbose) device_printf(sc->dev, "destination set to %s\n", dest_description(val)); return (0); } static void vchi_audio_sysctl_init(struct bcm2835_audio_info *sc) { struct sysctl_ctx_list *ctx; struct sysctl_oid *tree_node; struct sysctl_oid_list *tree; /* * Add system sysctl tree/handlers. */ ctx = device_get_sysctl_ctx(sc->dev); tree_node = device_get_sysctl_tree(sc->dev); tree = SYSCTL_CHILDREN(tree_node); SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "dest", CTLFLAG_RW | CTLTYPE_UINT | CTLFLAG_NEEDGIANT, sc, sizeof(*sc), sysctl_bcm2835_audio_dest, "IU", "audio destination, " "0 - auto, 1 - headphones, 2 - HDMI"); SYSCTL_ADD_UQUAD(ctx, tree, OID_AUTO, "callbacks", CTLFLAG_RD, &sc->pch.callbacks, "callbacks total"); SYSCTL_ADD_UQUAD(ctx, tree, OID_AUTO, "submitted", CTLFLAG_RD, &sc->pch.submitted_samples, "last play submitted samples"); SYSCTL_ADD_UQUAD(ctx, tree, OID_AUTO, "retrieved", CTLFLAG_RD, &sc->pch.retrieved_samples, "last play retrieved samples"); SYSCTL_ADD_UQUAD(ctx, tree, OID_AUTO, "underruns", CTLFLAG_RD, &sc->pch.underruns, "callback underruns"); SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "freebuffer", CTLFLAG_RD, &sc->pch.available_space, sc->pch.available_space, "callbacks total"); SYSCTL_ADD_INT(ctx, tree, OID_AUTO, "starved", CTLFLAG_RD, &sc->pch.starved, sc->pch.starved, "number of starved conditions"); } static void bcm2835_audio_identify(driver_t *driver, device_t parent) { BUS_ADD_CHILD(parent, 0, "pcm", 0); } static int bcm2835_audio_probe(device_t dev) { device_set_desc(dev, "VCHIQ audio"); return (BUS_PROBE_DEFAULT); } static void bcm2835_audio_delayed_init(void *xsc) { struct bcm2835_audio_info *sc; char status[SND_STATUSLEN]; sc = xsc; config_intrhook_disestablish(&sc->intr_hook); bcm2835_audio_init(sc); bcm2835_audio_open(sc); sc->volume = 75; sc->dest = DEST_AUTO; if (mixer_init(sc->dev, &bcmmixer_class, sc)) { device_printf(sc->dev, "mixer_init failed\n"); goto no; } if (pcm_register(sc->dev, sc, 1, 0)) { device_printf(sc->dev, "pcm_register failed\n"); goto no; } pcm_addchan(sc->dev, PCMDIR_PLAY, &bcmchan_class, sc); snprintf(status, SND_STATUSLEN, "at VCHIQ"); pcm_setstatus(sc->dev, status); bcm2835_audio_reset_channel(&sc->pch); bcm2835_audio_create_worker(sc); vchi_audio_sysctl_init(sc); no: ; } static int bcm2835_audio_attach(device_t dev) { struct bcm2835_audio_info *sc; sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO); sc->dev = dev; sc->bufsz = VCHIQ_AUDIO_BUFFER_SIZE; mtx_init(&sc->lock, device_get_nameunit(dev), "bcm_audio_lock", MTX_DEF); cv_init(&sc->worker_cv, "worker_cv"); sc->vchi_handle = VCHIQ_SERVICE_HANDLE_INVALID; /* * We need interrupts enabled for VCHI to work properly, * so delay initialization until it happens. */ sc->intr_hook.ich_func = bcm2835_audio_delayed_init; sc->intr_hook.ich_arg = sc; if (config_intrhook_establish(&sc->intr_hook) != 0) goto no; return 0; no: return ENXIO; } static int bcm2835_audio_detach(device_t dev) { int r; struct bcm2835_audio_info *sc; sc = pcm_getdevinfo(dev); /* Stop worker thread */ BCM2835_AUDIO_LOCK(sc); sc->worker_state = WORKER_STOPPING; cv_signal(&sc->worker_cv); /* Wait for thread to exit */ while (sc->worker_state != WORKER_STOPPED) cv_wait_sig(&sc->worker_cv, &sc->lock); BCM2835_AUDIO_UNLOCK(sc); r = pcm_unregister(dev); if (r) return r; mtx_destroy(&sc->lock); cv_destroy(&sc->worker_cv); bcm2835_audio_release(sc); free(sc, M_DEVBUF); return 0; } static device_method_t bcm2835_audio_methods[] = { /* Device interface */ DEVMETHOD(device_identify, bcm2835_audio_identify), DEVMETHOD(device_probe, bcm2835_audio_probe), DEVMETHOD(device_attach, bcm2835_audio_attach), DEVMETHOD(device_detach, bcm2835_audio_detach), { 0, 0 } }; static driver_t bcm2835_audio_driver = { "pcm", bcm2835_audio_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(bcm2835_audio, vchiq, bcm2835_audio_driver, 0, 0); MODULE_DEPEND(bcm2835_audio, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_DEPEND(bcm2835_audio, vchiq, 1, 1, 1); MODULE_VERSION(bcm2835_audio, 1); diff --git a/sys/arm/freescale/imx/imx6_ssi.c b/sys/arm/freescale/imx/imx6_ssi.c index 2fb55ca161d3..273957f64b21 100644 --- a/sys/arm/freescale/imx/imx6_ssi.c +++ b/sys/arm/freescale/imx/imx6_ssi.c @@ -1,856 +1,855 @@ /*- * Copyright (c) 2015 Ruslan Bukin * 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. */ /* * i.MX6 Synchronous Serial Interface (SSI) * * Chapter 61, i.MX 6Dual/6Quad Applications Processor Reference Manual, * Rev. 1, 04/2013 */ #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #define READ4(_sc, _reg) \ bus_space_read_4(_sc->bst, _sc->bsh, _reg) #define WRITE4(_sc, _reg, _val) \ bus_space_write_4(_sc->bst, _sc->bsh, _reg, _val) #define SSI_NCHANNELS 1 #define DMAS_TOTAL 8 /* i.MX6 SSI registers */ #define SSI_STX0 0x00 /* Transmit Data Register n */ #define SSI_STX1 0x04 /* Transmit Data Register n */ #define SSI_SRX0 0x08 /* Receive Data Register n */ #define SSI_SRX1 0x0C /* Receive Data Register n */ #define SSI_SCR 0x10 /* Control Register */ #define SCR_I2S_MODE_S 5 /* I2S Mode Select. */ #define SCR_I2S_MODE_M 0x3 #define SCR_SYN (1 << 4) #define SCR_NET (1 << 3) /* Network mode */ #define SCR_RE (1 << 2) /* Receive Enable. */ #define SCR_TE (1 << 1) /* Transmit Enable. */ #define SCR_SSIEN (1 << 0) /* SSI Enable */ #define SSI_SISR 0x14 /* Interrupt Status Register */ #define SSI_SIER 0x18 /* Interrupt Enable Register */ #define SIER_RDMAE (1 << 22) /* Receive DMA Enable. */ #define SIER_RIE (1 << 21) /* Receive Interrupt Enable. */ #define SIER_TDMAE (1 << 20) /* Transmit DMA Enable. */ #define SIER_TIE (1 << 19) /* Transmit Interrupt Enable. */ #define SIER_TDE0IE (1 << 12) /* Transmit Data Register Empty 0. */ #define SIER_TUE0IE (1 << 8) /* Transmitter Underrun Error 0. */ #define SIER_TFE0IE (1 << 0) /* Transmit FIFO Empty 0 IE. */ #define SSI_STCR 0x1C /* Transmit Configuration Register */ #define STCR_TXBIT0 (1 << 9) /* Transmit Bit 0 shift MSB/LSB */ #define STCR_TFEN1 (1 << 8) /* Transmit FIFO Enable 1. */ #define STCR_TFEN0 (1 << 7) /* Transmit FIFO Enable 0. */ #define STCR_TFDIR (1 << 6) /* Transmit Frame Direction. */ #define STCR_TXDIR (1 << 5) /* Transmit Clock Direction. */ #define STCR_TSHFD (1 << 4) /* Transmit Shift Direction. */ #define STCR_TSCKP (1 << 3) /* Transmit Clock Polarity. */ #define STCR_TFSI (1 << 2) /* Transmit Frame Sync Invert. */ #define STCR_TFSL (1 << 1) /* Transmit Frame Sync Length. */ #define STCR_TEFS (1 << 0) /* Transmit Early Frame Sync. */ #define SSI_SRCR 0x20 /* Receive Configuration Register */ #define SSI_STCCR 0x24 /* Transmit Clock Control Register */ #define STCCR_DIV2 (1 << 18) /* Divide By 2. */ #define STCCR_PSR (1 << 17) /* Divide clock by 8. */ #define WL3_WL0_S 13 #define WL3_WL0_M 0xf #define DC4_DC0_S 8 #define DC4_DC0_M 0x1f #define PM7_PM0_S 0 #define PM7_PM0_M 0xff #define SSI_SRCCR 0x28 /* Receive Clock Control Register */ #define SSI_SFCSR 0x2C /* FIFO Control/Status Register */ #define SFCSR_RFWM1_S 20 /* Receive FIFO Empty WaterMark 1 */ #define SFCSR_RFWM1_M 0xf #define SFCSR_TFWM1_S 16 /* Transmit FIFO Empty WaterMark 1 */ #define SFCSR_TFWM1_M 0xf #define SFCSR_RFWM0_S 4 /* Receive FIFO Empty WaterMark 0 */ #define SFCSR_RFWM0_M 0xf #define SFCSR_TFWM0_S 0 /* Transmit FIFO Empty WaterMark 0 */ #define SFCSR_TFWM0_M 0xf #define SSI_SACNT 0x38 /* AC97 Control Register */ #define SSI_SACADD 0x3C /* AC97 Command Address Register */ #define SSI_SACDAT 0x40 /* AC97 Command Data Register */ #define SSI_SATAG 0x44 /* AC97 Tag Register */ #define SSI_STMSK 0x48 /* Transmit Time Slot Mask Register */ #define SSI_SRMSK 0x4C /* Receive Time Slot Mask Register */ #define SSI_SACCST 0x50 /* AC97 Channel Status Register */ #define SSI_SACCEN 0x54 /* AC97 Channel Enable Register */ #define SSI_SACCDIS 0x58 /* AC97 Channel Disable Register */ static MALLOC_DEFINE(M_SSI, "ssi", "ssi audio"); uint32_t ssi_dma_intr(void *arg, int chn); struct ssi_rate { uint32_t speed; uint32_t mfi; /* PLL4 Multiplication Factor Integer */ uint32_t mfn; /* PLL4 Multiplication Factor Numerator */ uint32_t mfd; /* PLL4 Multiplication Factor Denominator */ /* More dividers to configure can be added here */ }; static struct ssi_rate rate_map[] = { { 192000, 49, 152, 1000 }, /* PLL4 49.152 Mhz */ /* TODO: add more frequences */ { 0, 0 }, }; /* * i.MX6 example bit clock formula * * BCLK = 2 channels * 192000 hz * 24 bit = 9216000 hz = * (24000000 * (49 + 152/1000.0) / 4 / 4 / 2 / 2 / 2 / 1 / 1) * ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ * | | | | | | | | | | | * Fref ------/ | | | | | | | | | | * PLL4 div select -/ | | | | | | | | | * PLL4 num --------------/ | | | | | | | | * PLL4 denom -------------------/ | | | | | | | * PLL4 post div ---------------------/ | | | | | | * CCM ssi pre div (CCM_CS1CDR) ----------/ | | | | | * CCM ssi post div (CCM_CS1CDR) -------------/ | | | | * SSI PM7_PM0_S ---------------------------------/ | | | * SSI Fixed divider ---------------------------------/ | | * SSI DIV2 ----------------------------------------------/ | * SSI PSR (prescaler /1 or /8) ------------------------------/ * * MCLK (Master clock) depends on DAC, usually BCLK * 4 */ struct sc_info { struct resource *res[2]; bus_space_tag_t bst; bus_space_handle_t bsh; device_t dev; struct mtx *lock; 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 sdma_conf *conf; struct ssi_rate *sr; struct sdma_softc *sdma_sc; uint32_t sdma_ev_rx; uint32_t sdma_ev_tx; int sdma_channel; }; /* 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[SSI_NCHANNELS]; struct sc_info *sc; }; static struct resource_spec ssi_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_ssi(struct sc_info *); static void ssi_configure_clock(struct sc_info *); /* * Mixer interface. */ static int ssimixer_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; mask |= SOUND_MASK_VOLUME; 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 ssimixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sc_pcminfo *scp; scp = mix_getdevinfo(m); /* Here we can configure hardware volume on our DAC */ #if 1 device_printf(scp->dev, "ssimixer_set() %d %d\n", left, right); #endif return (0); } static kobj_method_t ssimixer_methods[] = { KOBJMETHOD(mixer_init, ssimixer_init), KOBJMETHOD(mixer_set, ssimixer_set), KOBJMETHOD_END }; MIXER_DECLARE(ssimixer); /* * Channel interface. */ static void * ssichan_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 ssichan_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, "ssichan_free()\n"); #endif snd_mtxlock(sc->lock); /* TODO: free channel buffer */ snd_mtxunlock(sc->lock); return (0); } static int ssichan_setformat(kobj_t obj, void *data, uint32_t format) { struct sc_chinfo *ch = data; ch->format = format; return (0); } static uint32_t ssichan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct ssi_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; ssi_configure_clock(sc); return (sr->speed); } static void ssi_configure_clock(struct sc_info *sc) { struct ssi_rate *sr; sr = sc->sr; pll4_configure_output(sr->mfi, sr->mfn, sr->mfd); /* Configure other dividers here, if any */ } static uint32_t ssichan_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); setup_dma(scp); return (sndbuf_getblksz(ch->buffer)); } uint32_t ssi_dma_intr(void *arg, int chn) { struct sc_pcminfo *scp; struct sdma_conf *conf; struct sc_chinfo *ch; struct sc_info *sc; int bufsize; scp = arg; ch = &scp->chan[0]; sc = scp->sc; conf = sc->conf; bufsize = sndbuf_getsize(ch->buffer); sc->pos += conf->period; if (sc->pos >= bufsize) sc->pos -= bufsize; if (ch->run) chn_intr(ch->channel); return (0); } static int find_sdma_controller(struct sc_info *sc) { struct sdma_softc *sdma_sc; phandle_t node; device_t sdma_dev; pcell_t dts_value[DMAS_TOTAL]; int len; if ((node = ofw_bus_get_node(sc->dev)) == -1) return (ENXIO); if ((len = OF_getproplen(node, "dmas")) <= 0) return (ENXIO); if (len != sizeof(dts_value)) { device_printf(sc->dev, "\"dmas\" property length is invalid: %d (expected %d)", len, sizeof(dts_value)); return (ENXIO); } OF_getencprop(node, "dmas", dts_value, sizeof(dts_value)); sc->sdma_ev_rx = dts_value[1]; sc->sdma_ev_tx = dts_value[5]; sdma_sc = NULL; sdma_dev = devclass_get_device(devclass_find("sdma"), 0); if (sdma_dev) sdma_sc = device_get_softc(sdma_dev); if (sdma_sc == NULL) { device_printf(sc->dev, "No sDMA found. Can't operate\n"); return (ENXIO); } sc->sdma_sc = sdma_sc; return (0); }; static int setup_dma(struct sc_pcminfo *scp) { struct sdma_conf *conf; struct sc_chinfo *ch; struct sc_info *sc; int fmt; ch = &scp->chan[0]; sc = scp->sc; conf = sc->conf; conf->ih = ssi_dma_intr; conf->ih_user = scp; conf->saddr = sc->buf_base_phys; conf->daddr = rman_get_start(sc->res[0]) + SSI_STX0; conf->event = sc->sdma_ev_tx; /* SDMA TX event */ conf->period = sndbuf_getblksz(ch->buffer); conf->num_bd = sndbuf_getblkcnt(ch->buffer); /* * Word Length * Can be 32, 24, 16 or 8 for sDMA. * * SSI supports 24 at max. */ fmt = sndbuf_getfmt(ch->buffer); if (fmt & AFMT_16BIT) { conf->word_length = 16; conf->command = CMD_2BYTES; } else if (fmt & AFMT_24BIT) { conf->word_length = 24; conf->command = CMD_3BYTES; } else { device_printf(sc->dev, "Unknown format\n"); return (-1); } return (0); } static int ssi_start(struct sc_pcminfo *scp) { struct sc_info *sc; int reg; sc = scp->sc; if (sdma_configure(sc->sdma_channel, sc->conf) != 0) { device_printf(sc->dev, "Can't configure sDMA\n"); return (-1); } /* Enable DMA interrupt */ reg = (SIER_TDMAE); WRITE4(sc, SSI_SIER, reg); sdma_start(sc->sdma_channel); return (0); } static int ssi_stop(struct sc_pcminfo *scp) { struct sc_info *sc; int reg; sc = scp->sc; reg = READ4(sc, SSI_SIER); reg &= ~(SIER_TDMAE); WRITE4(sc, SSI_SIER, reg); sdma_stop(sc->sdma_channel); bzero(sc->buf_base, sc->dma_size); return (0); } static int ssichan_trigger(kobj_t obj, void *data, int go) { struct sc_pcminfo *scp; struct sc_chinfo *ch; struct sc_info *sc; ch = data; scp = ch->parent; sc = scp->sc; snd_mtxlock(sc->lock); switch (go) { case PCMTRIG_START: #if 0 device_printf(scp->dev, "trigger start\n"); #endif ch->run = 1; ssi_start(scp); break; case PCMTRIG_STOP: case PCMTRIG_ABORT: #if 0 device_printf(scp->dev, "trigger stop or abort\n"); #endif ch->run = 0; ssi_stop(scp); break; } snd_mtxunlock(sc->lock); return (0); } static uint32_t ssichan_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 ssi_pfmt[] = { SND_FORMAT(AFMT_S24_LE, 2, 0), 0 }; static struct pcmchan_caps ssi_pcaps = {44100, 192000, ssi_pfmt, 0}; static struct pcmchan_caps * ssichan_getcaps(kobj_t obj, void *data) { return (&ssi_pcaps); } static kobj_method_t ssichan_methods[] = { KOBJMETHOD(channel_init, ssichan_init), KOBJMETHOD(channel_free, ssichan_free), KOBJMETHOD(channel_setformat, ssichan_setformat), KOBJMETHOD(channel_setspeed, ssichan_setspeed), KOBJMETHOD(channel_setblocksize, ssichan_setblocksize), KOBJMETHOD(channel_trigger, ssichan_trigger), KOBJMETHOD(channel_getptr, ssichan_getptr), KOBJMETHOD(channel_getcaps, ssichan_getcaps), KOBJMETHOD_END }; CHANNEL_DECLARE(ssichan); static int ssi_probe(device_t dev) { if (!ofw_bus_status_okay(dev)) return (ENXIO); if (!ofw_bus_is_compatible(dev, "fsl,imx6q-ssi")) return (ENXIO); device_set_desc(dev, "i.MX6 Synchronous Serial Interface (SSI)"); return (BUS_PROBE_DEFAULT); } static void ssi_intr(void *arg) { #if 0 struct sc_pcminfo *scp; struct sc_info *sc; scp = arg; sc = scp->sc; #endif /* We don't use SSI interrupt */ #if 0 device_printf(scp->sc->dev, "SSI Intr 0x%08x\n", READ4(sc, SSI_SISR)); #endif } static void setup_ssi(struct sc_info *sc) { int reg; reg = READ4(sc, SSI_STCCR); reg &= ~(WL3_WL0_M << WL3_WL0_S); reg |= (0xb << WL3_WL0_S); /* 24 bit */ reg &= ~(DC4_DC0_M << DC4_DC0_S); reg |= (1 << DC4_DC0_S); /* 2 words per frame */ reg &= ~(STCCR_DIV2); /* Divide by 1 */ reg &= ~(STCCR_PSR); /* Divide by 1 */ reg &= ~(PM7_PM0_M << PM7_PM0_S); reg |= (1 << PM7_PM0_S); /* Divide by 2 */ WRITE4(sc, SSI_STCCR, reg); reg = READ4(sc, SSI_SFCSR); reg &= ~(SFCSR_TFWM0_M << SFCSR_TFWM0_S); reg |= (8 << SFCSR_TFWM0_S); /* empty slots */ WRITE4(sc, SSI_SFCSR, reg); reg = READ4(sc, SSI_STCR); reg |= (STCR_TFEN0); reg &= ~(STCR_TFEN1); reg &= ~(STCR_TSHFD); /* MSB */ reg |= (STCR_TXBIT0); reg |= (STCR_TXDIR | STCR_TFDIR); reg |= (STCR_TSCKP); /* falling edge */ reg |= (STCR_TFSI); reg &= ~(STCR_TFSI); /* active high frame sync */ reg &= ~(STCR_TFSL); reg |= STCR_TEFS; WRITE4(sc, SSI_STCR, reg); reg = READ4(sc, SSI_SCR); reg &= ~(SCR_I2S_MODE_M << SCR_I2S_MODE_S); /* Not master */ reg |= (SCR_SSIEN | SCR_TE); reg |= (SCR_NET); reg |= (SCR_SYN); WRITE4(sc, SSI_SCR, reg); } static void ssi_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 ssi_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->conf = malloc(sizeof(struct sdma_conf), M_DEVBUF, M_WAITOK | M_ZERO); sc->lock = snd_mtxcreate(device_get_nameunit(dev), "ssi softc"); if (sc->lock == NULL) { device_printf(dev, "Can't create mtx\n"); return (ENXIO); } if (bus_alloc_resources(dev, ssi_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]); /* SDMA */ if (find_sdma_controller(sc)) { device_printf(dev, "could not find active SDMA\n"); return (ENXIO); } /* Setup PCM */ scp = malloc(sizeof(struct sc_pcminfo), M_DEVBUF, M_NOWAIT | M_ZERO); scp->sc = sc; scp->dev = dev; /* * Maximum possible DMA buffer. * Will be used partially to match 24 bit word. */ sc->dma_size = 131072; /* * 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, ssi_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, ssi_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, &ssichan_class, scp); scp->chnum++; snprintf(status, SND_STATUSLEN, "at simplebus"); pcm_setstatus(dev, status); mixer_init(dev, &ssimixer_class, scp); setup_ssi(sc); imx_ccm_ssi_configure(dev); sc->sdma_channel = sdma_alloc(); if (sc->sdma_channel < 0) { device_printf(sc->dev, "Can't get sDMA channel\n"); return (1); } return (0); } static device_method_t ssi_pcm_methods[] = { DEVMETHOD(device_probe, ssi_probe), DEVMETHOD(device_attach, ssi_attach), { 0, 0 } }; static driver_t ssi_pcm_driver = { "pcm", ssi_pcm_methods, PCM_SOFTC_SIZE, }; DRIVER_MODULE(ssi, simplebus, ssi_pcm_driver, 0, 0); MODULE_DEPEND(ssi, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_DEPEND(ssi, sdma, 0, 0, 0); MODULE_VERSION(ssi, 1); diff --git a/sys/arm/freescale/vybrid/vf_sai.c b/sys/arm/freescale/vybrid/vf_sai.c index 6a36ddc3c057..696ecfb980f4 100644 --- a/sys/arm/freescale/vybrid/vf_sai.c +++ b/sys/arm/freescale/vybrid/vf_sai.c @@ -1,801 +1,800 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2014 Ruslan Bukin * 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 #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #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) { #if 0 struct sc_pcminfo *scp; scp = mix_getdevinfo(m); 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; if (ch->run) 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_getencprop(node, "edma-src-transmit", &dts_value, len); edma_src_transmit = dts_value; OF_getencprop(node, "edma-mux-group", &dts_value, len); edma_mux_group = dts_value; OF_getencprop(node, "edma-controller", &dts_value, len); edma_node = OF_node_from_xref(dts_value); if ((len = OF_getproplen(edma_node, "device-id")) <= 0) { return (ENXIO); } OF_getencprop(edma_node, "device-id", &dts_value, len); edma_device_id = 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) { /* can't 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 = 17; /* 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 ch->run = 1; break; case PCMTRIG_STOP: case PCMTRIG_ABORT: #if 0 device_printf(scp->dev, "trigger stop or abort\n"); #endif ch->run = 0; 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 = 131072; /* * 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, 0, 0); MODULE_DEPEND(sai, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(sai, 1);