The Design and Implementation of the FreeBSD Operating System, Second Edition
Now available: The Design and Implementation of the FreeBSD Operating System (Second Edition)


[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]

FreeBSD/Linux Kernel Cross Reference
sys/arm/allwinner/a10_hdmiaudio.c

Version: -  FREEBSD  -  FREEBSD-13-STABLE  -  FREEBSD-13-0  -  FREEBSD-12-STABLE  -  FREEBSD-12-0  -  FREEBSD-11-STABLE  -  FREEBSD-11-0  -  FREEBSD-10-STABLE  -  FREEBSD-10-0  -  FREEBSD-9-STABLE  -  FREEBSD-9-0  -  FREEBSD-8-STABLE  -  FREEBSD-8-0  -  FREEBSD-7-STABLE  -  FREEBSD-7-0  -  FREEBSD-6-STABLE  -  FREEBSD-6-0  -  FREEBSD-5-STABLE  -  FREEBSD-5-0  -  FREEBSD-4-STABLE  -  FREEBSD-3-STABLE  -  FREEBSD22  -  l41  -  OPENBSD  -  linux-2.6  -  MK84  -  PLAN9  -  xnu-8792 
SearchContext: -  none  -  3  -  10 

    1 /*-
    2  * Copyright (c) 2016 Jared McNeill <jmcneill@invisible.ca>
    3  *
    4  * Redistribution and use in source and binary forms, with or without
    5  * modification, are permitted provided that the following conditions
    6  * are met:
    7  * 1. Redistributions of source code must retain the above copyright
    8  *    notice, this list of conditions and the following disclaimer.
    9  * 2. Redistributions in binary form must reproduce the above copyright
   10  *    notice, this list of conditions and the following disclaimer in the
   11  *    documentation and/or other materials provided with the distribution.
   12  *
   13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
   14  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   15  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
   16  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
   17  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   18  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   19  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
   20  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   21  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   23  * SUCH DAMAGE.
   24  *
   25  * $FreeBSD$
   26  */
   27 
   28 /*
   29  * Allwinner A10/A20 HDMI Audio
   30  */
   31 
   32 #include <sys/cdefs.h>
   33 __FBSDID("$FreeBSD$");
   34 
   35 #include <sys/param.h>
   36 #include <sys/systm.h>
   37 #include <sys/bus.h>
   38 #include <sys/rman.h>
   39 #include <sys/condvar.h>
   40 #include <sys/kernel.h>
   41 #include <sys/module.h>
   42 
   43 #include <dev/sound/pcm/sound.h>
   44 #include <dev/sound/chip.h>
   45 
   46 #include <dev/ofw/ofw_bus.h>
   47 #include <dev/ofw/ofw_bus_subr.h>
   48 
   49 #include "sunxi_dma_if.h"
   50 #include "mixer_if.h"
   51 
   52 #define DRQTYPE_HDMIAUDIO       24
   53 #define DRQTYPE_SDRAM           1
   54 
   55 #define DMA_WIDTH               32
   56 #define DMA_BURST_LEN           8
   57 #define DDMA_BLKSIZE            32
   58 #define DDMA_WAIT_CYC           8
   59 
   60 #define DMABUF_MIN              4096
   61 #define DMABUF_DEFAULT          65536
   62 #define DMABUF_MAX              131072
   63 
   64 #define HDMI_SAMPLERATE         48000
   65 
   66 #define TX_FIFO                 0x01c16400
   67 
   68 static uint32_t a10hdmiaudio_fmt[] = {
   69         SND_FORMAT(AFMT_S16_LE, 2, 0),
   70         0
   71 };
   72 
   73 static struct pcmchan_caps a10hdmiaudio_pcaps = {
   74     HDMI_SAMPLERATE, HDMI_SAMPLERATE, a10hdmiaudio_fmt, 0
   75 };
   76 
   77 struct a10hdmiaudio_info;
   78 
   79 struct a10hdmiaudio_chinfo {
   80         struct snd_dbuf         *buffer;
   81         struct pcm_channel      *channel;       
   82         struct a10hdmiaudio_info        *parent;
   83         bus_dmamap_t            dmamap;
   84         void                    *dmaaddr;
   85         bus_addr_t              physaddr;
   86         device_t                dmac;
   87         void                    *dmachan;
   88 
   89         int                     run;
   90         uint32_t                pos;
   91         uint32_t                blocksize;
   92 };
   93 
   94 struct a10hdmiaudio_info {
   95         device_t                dev;
   96         struct mtx              *lock;
   97         bus_dma_tag_t           dmat;
   98         unsigned                dmasize;
   99 
  100         struct a10hdmiaudio_chinfo      play;
  101 };
  102 
  103 /*
  104  * Mixer interface
  105  */
  106 
  107 static int
  108 a10hdmiaudio_mixer_init(struct snd_mixer *m)
  109 {
  110         mix_setdevs(m, SOUND_MASK_PCM);
  111 
  112         return (0);
  113 }
  114 
  115 static int
  116 a10hdmiaudio_mixer_set(struct snd_mixer *m, unsigned dev, unsigned left,
  117     unsigned right)
  118 {
  119         return (-1);
  120 }
  121 
  122 static kobj_method_t a10hdmiaudio_mixer_methods[] = {
  123         KOBJMETHOD(mixer_init,          a10hdmiaudio_mixer_init),
  124         KOBJMETHOD(mixer_set,           a10hdmiaudio_mixer_set),
  125         KOBJMETHOD_END
  126 };
  127 MIXER_DECLARE(a10hdmiaudio_mixer);
  128 
  129 /*
  130  * Channel interface
  131  */
  132 
  133 static void
  134 a10hdmiaudio_dmamap_cb(void *arg, bus_dma_segment_t *segs, int nseg, int error)
  135 {
  136         struct a10hdmiaudio_chinfo *ch = arg;
  137 
  138         if (error != 0)
  139                 return;
  140 
  141         ch->physaddr = segs[0].ds_addr;
  142 }
  143 
  144 static void
  145 a10hdmiaudio_transfer(struct a10hdmiaudio_chinfo *ch)
  146 {
  147         int error;
  148 
  149         error = SUNXI_DMA_TRANSFER(ch->dmac, ch->dmachan,
  150             ch->physaddr + ch->pos, TX_FIFO, ch->blocksize);
  151         if (error) {
  152                 ch->run = 0;
  153                 device_printf(ch->parent->dev, "DMA transfer failed: %d\n",
  154                     error);
  155         }
  156 }
  157 
  158 static void
  159 a10hdmiaudio_dmaconfig(struct a10hdmiaudio_chinfo *ch)
  160 {
  161         struct sunxi_dma_config conf;
  162 
  163         memset(&conf, 0, sizeof(conf));
  164         conf.src_width = conf.dst_width = DMA_WIDTH;
  165         conf.src_burst_len = conf.dst_burst_len = DMA_BURST_LEN;
  166         conf.src_blksize = conf.dst_blksize = DDMA_BLKSIZE;
  167         conf.src_wait_cyc = conf.dst_wait_cyc = DDMA_WAIT_CYC;
  168         conf.src_drqtype = DRQTYPE_SDRAM;
  169         conf.dst_drqtype = DRQTYPE_HDMIAUDIO;
  170         conf.dst_noincr = true;
  171 
  172         SUNXI_DMA_SET_CONFIG(ch->dmac, ch->dmachan, &conf);
  173 }
  174 
  175 static void
  176 a10hdmiaudio_dmaintr(void *priv)
  177 {
  178         struct a10hdmiaudio_chinfo *ch = priv;
  179         unsigned bufsize;
  180 
  181         bufsize = sndbuf_getsize(ch->buffer);
  182 
  183         ch->pos += ch->blocksize;
  184         if (ch->pos >= bufsize)
  185                 ch->pos -= bufsize;
  186 
  187         if (ch->run) {
  188                 chn_intr(ch->channel);
  189                 a10hdmiaudio_transfer(ch);
  190         }
  191 }
  192 
  193 static void
  194 a10hdmiaudio_start(struct a10hdmiaudio_chinfo *ch)
  195 {
  196         ch->pos = 0;
  197 
  198         /* Configure DMA channel */
  199         a10hdmiaudio_dmaconfig(ch);
  200 
  201         /* Start DMA transfer */
  202         a10hdmiaudio_transfer(ch);
  203 }
  204 
  205 static void
  206 a10hdmiaudio_stop(struct a10hdmiaudio_chinfo *ch)
  207 {
  208         /* Disable DMA channel */
  209         SUNXI_DMA_HALT(ch->dmac, ch->dmachan);
  210 }
  211 
  212 static void *
  213 a10hdmiaudio_chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
  214     struct pcm_channel *c, int dir)
  215 {
  216         struct a10hdmiaudio_info *sc = devinfo;
  217         struct a10hdmiaudio_chinfo *ch = &sc->play;
  218         int error;
  219 
  220         ch->parent = sc;
  221         ch->channel = c;
  222         ch->buffer = b;
  223 
  224         ch->dmac = devclass_get_device(devclass_find("a10dmac"), 0);
  225         if (ch->dmac == NULL) {
  226                 device_printf(sc->dev, "cannot find DMA controller\n");
  227                 return (NULL);
  228         }
  229         ch->dmachan = SUNXI_DMA_ALLOC(ch->dmac, true, a10hdmiaudio_dmaintr, ch);
  230         if (ch->dmachan == NULL) {
  231                 device_printf(sc->dev, "cannot allocate DMA channel\n");
  232                 return (NULL);
  233         }
  234 
  235         error = bus_dmamem_alloc(sc->dmat, &ch->dmaaddr,
  236             BUS_DMA_NOWAIT | BUS_DMA_COHERENT, &ch->dmamap);
  237         if (error != 0) {
  238                 device_printf(sc->dev, "cannot allocate channel buffer\n");
  239                 return (NULL);
  240         }
  241         error = bus_dmamap_load(sc->dmat, ch->dmamap, ch->dmaaddr,
  242             sc->dmasize, a10hdmiaudio_dmamap_cb, ch, BUS_DMA_NOWAIT);
  243         if (error != 0) {
  244                 device_printf(sc->dev, "cannot load DMA map\n");
  245                 return (NULL);
  246         }
  247         memset(ch->dmaaddr, 0, sc->dmasize);
  248 
  249         if (sndbuf_setup(ch->buffer, ch->dmaaddr, sc->dmasize) != 0) {
  250                 device_printf(sc->dev, "cannot setup sndbuf\n");
  251                 return (NULL);
  252         }
  253 
  254         return (ch);
  255 }
  256 
  257 static int
  258 a10hdmiaudio_chan_free(kobj_t obj, void *data)
  259 {
  260         struct a10hdmiaudio_chinfo *ch = data;
  261         struct a10hdmiaudio_info *sc = ch->parent;
  262 
  263         SUNXI_DMA_FREE(ch->dmac, ch->dmachan);
  264         bus_dmamap_unload(sc->dmat, ch->dmamap);
  265         bus_dmamem_free(sc->dmat, ch->dmaaddr, ch->dmamap);
  266 
  267         return (0);
  268 }
  269 
  270 static int
  271 a10hdmiaudio_chan_setformat(kobj_t obj, void *data, uint32_t format)
  272 {
  273         return (0);
  274 }
  275 
  276 static uint32_t
  277 a10hdmiaudio_chan_setspeed(kobj_t obj, void *data, uint32_t speed)
  278 {
  279         return (HDMI_SAMPLERATE);
  280 }
  281 
  282 static uint32_t
  283 a10hdmiaudio_chan_setblocksize(kobj_t obj, void *data, uint32_t blocksize)
  284 {
  285         struct a10hdmiaudio_chinfo *ch = data;
  286 
  287         ch->blocksize = blocksize & ~3;
  288 
  289         return (ch->blocksize);
  290 }
  291 
  292 static int
  293 a10hdmiaudio_chan_trigger(kobj_t obj, void *data, int go)
  294 {
  295         struct a10hdmiaudio_chinfo *ch = data;
  296         struct a10hdmiaudio_info *sc = ch->parent;
  297 
  298         if (!PCMTRIG_COMMON(go))
  299                 return (0);
  300 
  301         snd_mtxlock(sc->lock);
  302         switch (go) {
  303         case PCMTRIG_START:
  304                 ch->run = 1;
  305                 a10hdmiaudio_start(ch);
  306                 break;
  307         case PCMTRIG_STOP:
  308         case PCMTRIG_ABORT:
  309                 ch->run = 0;
  310                 a10hdmiaudio_stop(ch);
  311                 break;
  312         default:
  313                 break;
  314         }
  315         snd_mtxunlock(sc->lock);
  316 
  317         return (0);
  318 }
  319 
  320 static uint32_t
  321 a10hdmiaudio_chan_getptr(kobj_t obj, void *data)
  322 {
  323         struct a10hdmiaudio_chinfo *ch = data;
  324 
  325         return (ch->pos);
  326 }
  327 
  328 static struct pcmchan_caps *
  329 a10hdmiaudio_chan_getcaps(kobj_t obj, void *data)
  330 {
  331         return (&a10hdmiaudio_pcaps);
  332 }
  333 
  334 static kobj_method_t a10hdmiaudio_chan_methods[] = {
  335         KOBJMETHOD(channel_init,                a10hdmiaudio_chan_init),
  336         KOBJMETHOD(channel_free,                a10hdmiaudio_chan_free),
  337         KOBJMETHOD(channel_setformat,           a10hdmiaudio_chan_setformat),
  338         KOBJMETHOD(channel_setspeed,            a10hdmiaudio_chan_setspeed),
  339         KOBJMETHOD(channel_setblocksize,        a10hdmiaudio_chan_setblocksize),
  340         KOBJMETHOD(channel_trigger,             a10hdmiaudio_chan_trigger),
  341         KOBJMETHOD(channel_getptr,              a10hdmiaudio_chan_getptr),
  342         KOBJMETHOD(channel_getcaps,             a10hdmiaudio_chan_getcaps),
  343         KOBJMETHOD_END
  344 };
  345 CHANNEL_DECLARE(a10hdmiaudio_chan);
  346 
  347 /*
  348  * Device interface
  349  */
  350 
  351 static int
  352 a10hdmiaudio_probe(device_t dev)
  353 {
  354         if (!ofw_bus_status_okay(dev))
  355                 return (ENXIO);
  356 
  357         if (!ofw_bus_is_compatible(dev, "allwinner,sun7i-a20-hdmiaudio"))
  358                 return (ENXIO);
  359 
  360         device_set_desc(dev, "Allwinner HDMI Audio");
  361         return (BUS_PROBE_DEFAULT);
  362 }
  363 
  364 static int
  365 a10hdmiaudio_attach(device_t dev)
  366 {
  367         struct a10hdmiaudio_info *sc;
  368         char status[SND_STATUSLEN];
  369         int error;
  370 
  371         sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO);
  372         sc->dev = dev;
  373         sc->lock = snd_mtxcreate(device_get_nameunit(dev), "a10hdmiaudio softc");
  374 
  375         sc->dmasize = pcm_getbuffersize(dev, DMABUF_MIN,
  376             DMABUF_DEFAULT, DMABUF_MAX);
  377         error = bus_dma_tag_create(
  378             bus_get_dma_tag(dev),
  379             4, sc->dmasize,             /* alignment, boundary */
  380             BUS_SPACE_MAXADDR_32BIT,    /* lowaddr */
  381             BUS_SPACE_MAXADDR,          /* highaddr */
  382             NULL, NULL,                 /* filter, filterarg */
  383             sc->dmasize, 1,             /* maxsize, nsegs */
  384             sc->dmasize, 0,             /* maxsegsize, flags */
  385             NULL, NULL,                 /* lockfunc, lockarg */
  386             &sc->dmat);
  387         if (error != 0) {
  388                 device_printf(dev, "cannot create DMA tag\n");
  389                 goto fail;
  390         }
  391 
  392         if (mixer_init(dev, &a10hdmiaudio_mixer_class, sc)) {
  393                 device_printf(dev, "mixer_init failed\n");
  394                 goto fail;
  395         }
  396 
  397         pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE);
  398         pcm_setflags(dev, pcm_getflags(dev) | SD_F_SOFTPCMVOL);
  399 
  400         if (pcm_register(dev, sc, 1, 0)) {
  401                 device_printf(dev, "pcm_register failed\n");
  402                 goto fail;
  403         }
  404 
  405         pcm_addchan(dev, PCMDIR_PLAY, &a10hdmiaudio_chan_class, sc);
  406 
  407         snprintf(status, SND_STATUSLEN, "at %s", ofw_bus_get_name(dev));
  408         pcm_setstatus(dev, status);
  409 
  410         return (0);
  411 
  412 fail:
  413         snd_mtxfree(sc->lock);
  414         free(sc, M_DEVBUF);
  415 
  416         return (error);
  417 }
  418 
  419 static device_method_t a10hdmiaudio_pcm_methods[] = {
  420         /* Device interface */
  421         DEVMETHOD(device_probe,         a10hdmiaudio_probe),
  422         DEVMETHOD(device_attach,        a10hdmiaudio_attach),
  423 
  424         DEVMETHOD_END
  425 };
  426 
  427 static driver_t a10hdmiaudio_pcm_driver = {
  428         "pcm",
  429         a10hdmiaudio_pcm_methods,
  430         PCM_SOFTC_SIZE,
  431 };
  432 
  433 DRIVER_MODULE(a10hdmiaudio, simplebus, a10hdmiaudio_pcm_driver, pcm_devclass, 0, 0);
  434 MODULE_DEPEND(a10hdmiaudio, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
  435 MODULE_VERSION(a10hdmiaudio, 1);

Cache object: c5ee7e098e33708b3ea73f0825eb05b8


[ source navigation ] [ diff markup ] [ identifier search ] [ freetext search ] [ file search ] [ list types ] [ track identifier ]


This page is part of the FreeBSD/Linux Linux Kernel Cross-Reference, and was automatically generated using a modified version of the LXR engine.