Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 20 Dec 2016 18:27:53 +0000 (UTC)
From:      Ruslan Bukin <br@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r310333 - in head/sys/mips: conf ingenic
Message-ID:  <201612201827.uBKIRrBA099876@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: br
Date: Tue Dec 20 18:27:53 2016
New Revision: 310333
URL: https://svnweb.freebsd.org/changeset/base/310333

Log:
  Add jz4780 AIC (Audio Interface Controller) driver.
  
  Sponsored by:	DARPA, AFRL
  Differential Revision:	https://reviews.freebsd.org/D8808

Added:
  head/sys/mips/ingenic/jz4780_aic.c   (contents, props changed)
Modified:
  head/sys/mips/conf/CANNA
  head/sys/mips/conf/CI20
  head/sys/mips/ingenic/files.jz4780
  head/sys/mips/ingenic/files.x1000

Modified: head/sys/mips/conf/CANNA
==============================================================================
--- head/sys/mips/conf/CANNA	Tue Dec 20 18:25:41 2016	(r310332)
+++ head/sys/mips/conf/CANNA	Tue Dec 20 18:27:53 2016	(r310333)
@@ -26,5 +26,8 @@ options 	ROOTDEVNAME=\"ufs:mmcsd0s3\"
 
 makeoptions	TRAMPLOADADDR=0x88000000
 
+device		sound
+device		xdma
+
 #options 	VERBOSE_SYSINIT
 options 	PRINTF_BUFR_SIZE=256

Modified: head/sys/mips/conf/CI20
==============================================================================
--- head/sys/mips/conf/CI20	Tue Dec 20 18:25:41 2016	(r310332)
+++ head/sys/mips/conf/CI20	Tue Dec 20 18:27:53 2016	(r310333)
@@ -25,6 +25,9 @@ makeoptions	FDT_DTS_FILE=ingenic/ci20.dt
 
 options 	ROOTDEVNAME=\"ufs:mmcsd0\"
 
+device		sound
+device		xdma
+
 makeoptions	TRAMPLOADADDR=0x88000000
 
 #options 	VERBOSE_SYSINIT

Modified: head/sys/mips/ingenic/files.jz4780
==============================================================================
--- head/sys/mips/ingenic/files.jz4780	Tue Dec 20 18:25:41 2016	(r310332)
+++ head/sys/mips/ingenic/files.jz4780	Tue Dec 20 18:27:53 2016	(r310333)
@@ -23,6 +23,10 @@ mips/ingenic/jz4780_pdma.c	standard
 mips/ingenic/jz4780_pinctrl.c	standard
 mips/ingenic/jz4780_timer.c	standard
 
+# Sound
+mips/ingenic/jz4780_aic.c	optional sound xdma
+mips/ingenic/jz4780_codec.c	optional sound
+
 # SMP
 mips/ingenic/jz4780_mp.c	optional smp
 mips/ingenic/jz4780_mpboot.S	optional smp

Modified: head/sys/mips/ingenic/files.x1000
==============================================================================
--- head/sys/mips/ingenic/files.x1000	Tue Dec 20 18:25:41 2016	(r310332)
+++ head/sys/mips/ingenic/files.x1000	Tue Dec 20 18:27:53 2016	(r310333)
@@ -14,5 +14,9 @@ mips/ingenic/jz4780_pdma.c	standard
 mips/ingenic/jz4780_pinctrl.c	standard
 mips/ingenic/jz4780_timer.c	standard
 
+# Sound
+mips/ingenic/jz4780_aic.c	optional sound xdma
+mips/ingenic/jz4780_codec.c	optional sound
+
 # Custom interface between pinctrl and gpio
 mips/ingenic/jz4780_gpio_if.m	standard

Added: head/sys/mips/ingenic/jz4780_aic.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/sys/mips/ingenic/jz4780_aic.c	Tue Dec 20 18:27:53 2016	(r310333)
@@ -0,0 +1,754 @@
+/*-
+ * Copyright (c) 2016 Ruslan Bukin <br@bsdpad.com>
+ * All rights reserved.
+ *
+ * This software was developed by SRI International and the University of
+ * Cambridge Computer Laboratory under DARPA/AFRL contract FA8750-10-C-0237
+ * ("CTSRD"), as part of the DARPA CRASH research programme.
+ *
+ * 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.
+ */
+
+/* Ingenic JZ4780 Audio Interface Controller (AIC). */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/conf.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/resource.h>
+#include <sys/rman.h>
+
+#include <machine/bus.h>
+
+#include <dev/fdt/fdt_common.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/chip.h>
+#include <mixer_if.h>
+
+#include <dev/extres/clk/clk.h>
+#include <dev/xdma/xdma.h>
+
+#include <mips/ingenic/jz4780_common.h>
+#include <mips/ingenic/jz4780_aic.h>
+
+#define	AIC_NCHANNELS		1
+
+struct aic_softc {
+	device_t		dev;
+	struct resource		*res[1];
+	bus_space_tag_t		bst;
+	bus_space_handle_t	bsh;
+	struct mtx		*lock;
+	int			pos;
+	bus_dma_tag_t		dma_tag;
+	bus_dmamap_t		dma_map;
+	bus_addr_t		buf_base_phys;
+	uint32_t		*buf_base;
+	uintptr_t		aic_fifo_paddr;
+	int			dma_size;
+	clk_t			clk_aic;
+	clk_t			clk_i2s;
+	struct aic_rate		*sr;
+	void			*ih;
+	struct xdma_channel	*xchan;
+	xdma_controller_t	*xdma_tx;
+};
+
+/* 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		chnum;
+	struct sc_chinfo	chan[AIC_NCHANNELS];
+	struct aic_softc	*sc;
+};
+
+static struct resource_spec aic_spec[] = {
+	{ SYS_RES_MEMORY,	0,	RF_ACTIVE },
+	{ -1, 0 }
+};
+
+static int aic_probe(device_t dev);
+static int aic_attach(device_t dev);
+static int aic_detach(device_t dev);
+static int setup_xdma(struct sc_pcminfo *scp);
+
+struct aic_rate {
+        uint32_t speed;
+};
+
+static struct aic_rate rate_map[] = {
+	{ 96000 },
+	/* TODO: add more frequences */
+	{ 0 },
+};
+
+/*
+ * Mixer interface.
+ */
+static int
+aicmixer_init(struct snd_mixer *m)
+{
+	struct sc_pcminfo *scp;
+	struct aic_softc *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
+aicmixer_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 */
+
+	return (0);
+}
+
+static kobj_method_t aicmixer_methods[] = {
+	KOBJMETHOD(mixer_init,      aicmixer_init),
+	KOBJMETHOD(mixer_set,       aicmixer_set),
+	KOBJMETHOD_END
+};
+MIXER_DECLARE(aicmixer);
+
+/*
+ * Channel interface.
+ */
+static void *
+aicchan_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 aic_softc *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
+aicchan_free(kobj_t obj, void *data)
+{
+	struct sc_chinfo *ch = data;
+	struct sc_pcminfo *scp = ch->parent;
+	struct aic_softc *sc = scp->sc;
+
+	snd_mtxlock(sc->lock);
+	/* TODO: free channel buffer */
+	snd_mtxunlock(sc->lock);
+
+	return (0);
+}
+
+static int
+aicchan_setformat(kobj_t obj, void *data, uint32_t format)
+{
+	struct sc_pcminfo *scp;
+	struct sc_chinfo *ch;
+
+	ch = data;
+	scp = ch->parent;
+
+	ch->format = format;
+
+	return (0);
+}
+
+static uint32_t
+aicchan_setspeed(kobj_t obj, void *data, uint32_t speed)
+{
+	struct sc_pcminfo *scp;
+	struct sc_chinfo *ch;
+	struct aic_rate *sr;
+	struct aic_softc *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;
+
+	/* Clocks can be reconfigured here. */
+
+	return (sr->speed);
+}
+
+static uint32_t
+aicchan_setblocksize(kobj_t obj, void *data, uint32_t blocksize)
+{
+	struct sc_pcminfo *scp;
+	struct sc_chinfo *ch;
+	struct aic_softc *sc;
+
+	ch = data;
+	scp = ch->parent;
+	sc = scp->sc;
+
+	sndbuf_resize(ch->buffer, sc->dma_size / blocksize, blocksize);
+
+	return (sndbuf_getblksz(ch->buffer));
+}
+
+static int
+aic_intr(void *arg)
+{
+	struct sc_pcminfo *scp;
+	xdma_channel_t *xchan;
+	struct sc_chinfo *ch;
+	struct aic_softc *sc;
+	xdma_config_t *conf;
+	int bufsize;
+
+	scp = arg;
+	sc = scp->sc;
+	ch = &scp->chan[0];
+
+	xchan = sc->xchan;
+	conf = &xchan->conf;
+
+	bufsize = sndbuf_getsize(ch->buffer);
+
+	sc->pos += conf->block_len;
+	if (sc->pos >= bufsize)
+		sc->pos -= bufsize;
+
+	if (ch->run)
+		chn_intr(ch->channel);
+
+	return (0);
+}
+
+static int
+setup_xdma(struct sc_pcminfo *scp)
+{
+	struct aic_softc *sc;
+	struct sc_chinfo *ch;
+	int fmt;
+	int err;
+
+	ch = &scp->chan[0];
+	sc = scp->sc;
+
+	fmt = sndbuf_getfmt(ch->buffer);
+
+	KASSERT(fmt & AFMT_16BIT, ("16-bit audio supported only."));
+
+	err = xdma_prep_cyclic(sc->xchan,
+	    XDMA_MEM_TO_DEV,			/* direction */
+	    sc->buf_base_phys,			/* src addr */
+	    sc->aic_fifo_paddr,			/* dst addr */
+	    sndbuf_getblksz(ch->buffer),	/* block len */
+	    sndbuf_getblkcnt(ch->buffer),	/* block num */
+	    2,					/* src port width */
+	    2);					/* dst port width */
+	if (err != 0) {
+		device_printf(sc->dev, "Can't configure virtual channel\n");
+		return (-1);
+	}
+
+	xdma_begin(sc->xchan);
+
+	return (0);
+}
+
+static int
+aic_start(struct sc_pcminfo *scp)
+{
+	struct aic_softc *sc;
+	int reg;
+
+	sc = scp->sc;
+	sc->pos = 0;
+
+	/* Ensure clock enabled. */
+	reg = READ4(sc, I2SCR);
+	reg |= (I2SCR_ESCLK);
+	WRITE4(sc, I2SCR, reg);
+
+	setup_xdma(scp);
+
+	reg = (AICCR_OSS_16 | AICCR_ISS_16);
+	reg |= (AICCR_CHANNEL_2);
+	reg |= (AICCR_TDMS);
+	reg |= (AICCR_ERPL);
+	WRITE4(sc, AICCR, reg);
+
+	return (0);
+}
+
+static int
+aic_stop(struct sc_pcminfo *scp)
+{
+	struct aic_softc *sc;
+	int reg;
+
+	sc = scp->sc;
+
+	reg = READ4(sc, AICCR);
+	reg &= ~(AICCR_TDMS | AICCR_ERPL);
+	WRITE4(sc, AICCR, reg);
+
+	xdma_terminate(sc->xchan);
+
+	sc->pos = 0;
+
+	bzero(sc->buf_base, sc->dma_size);
+
+	return (0);
+}
+
+static int
+aicchan_trigger(kobj_t obj, void *data, int go)
+{
+	struct sc_pcminfo *scp;
+	struct sc_chinfo *ch;
+	struct aic_softc *sc;
+
+	ch = data;
+	scp = ch->parent;
+	sc = scp->sc;
+
+	snd_mtxlock(sc->lock);
+
+	switch (go) {
+	case PCMTRIG_START:
+		ch->run = 1;
+
+		aic_start(scp);
+
+		break;
+
+	case PCMTRIG_STOP:
+	case PCMTRIG_ABORT:
+		ch->run = 0;
+
+		aic_stop(scp);
+
+		break;
+	}
+
+	snd_mtxunlock(sc->lock);
+
+	return (0);
+}
+
+static uint32_t
+aicchan_getptr(kobj_t obj, void *data)
+{
+	struct sc_pcminfo *scp;
+	struct sc_chinfo *ch;
+	struct aic_softc *sc;
+
+	ch = data;
+	scp = ch->parent;
+	sc = scp->sc;
+
+	return (sc->pos);
+}
+
+static uint32_t aic_pfmt[] = {
+	SND_FORMAT(AFMT_S16_LE, 2, 0),
+	0
+};
+
+static struct pcmchan_caps aic_pcaps = {96000, 96000, aic_pfmt, 0};
+
+static struct pcmchan_caps *
+aicchan_getcaps(kobj_t obj, void *data)
+{
+
+	return (&aic_pcaps);
+}
+
+static kobj_method_t aicchan_methods[] = {
+	KOBJMETHOD(channel_init,         aicchan_init),
+	KOBJMETHOD(channel_free,         aicchan_free),
+	KOBJMETHOD(channel_setformat,    aicchan_setformat),
+	KOBJMETHOD(channel_setspeed,     aicchan_setspeed),
+	KOBJMETHOD(channel_setblocksize, aicchan_setblocksize),
+	KOBJMETHOD(channel_trigger,      aicchan_trigger),
+	KOBJMETHOD(channel_getptr,       aicchan_getptr),
+	KOBJMETHOD(channel_getcaps,      aicchan_getcaps),
+	KOBJMETHOD_END
+};
+CHANNEL_DECLARE(aicchan);
+
+static void
+aic_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
+aic_dma_setup(struct aic_softc *sc)
+{
+	device_t dev;
+	int err;
+
+	dev = sc->dev;
+
+	/* DMA buffer size. */
+	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);
+	if (err) {
+		device_printf(dev, "cannot create bus dma tag\n");
+		return (-1);
+	}
+
+	err = bus_dmamem_alloc(sc->dma_tag, (void **)&sc->buf_base,
+	    BUS_DMA_WAITOK | BUS_DMA_COHERENT, &sc->dma_map);
+	if (err) {
+		device_printf(dev, "cannot allocate memory\n");
+		return (-1);
+	}
+
+	err = bus_dmamap_load(sc->dma_tag, sc->dma_map, sc->buf_base,
+	    sc->dma_size, aic_dmamap_cb, &sc->buf_base_phys, BUS_DMA_WAITOK);
+	if (err) {
+		device_printf(dev, "cannot load DMA map\n");
+		return (-1);
+	}
+
+	bzero(sc->buf_base, sc->dma_size);
+
+	return (0);
+}
+
+static int
+aic_configure_clocks(struct aic_softc *sc)
+{
+	uint64_t aic_freq;
+	uint64_t i2s_freq;
+	device_t dev;
+	int err;
+
+	dev = sc->dev;
+
+	err = clk_get_by_ofw_name(sc->dev, 0, "aic", &sc->clk_aic);
+	if (err != 0) {
+		device_printf(dev, "Can't find aic clock.\n");
+		return (-1);
+	}
+
+	err = clk_enable(sc->clk_aic);
+	if (err != 0) {
+		device_printf(dev, "Can't enable aic clock.\n");
+		return (-1);
+	}
+
+	err = clk_get_by_ofw_name(sc->dev, 0, "i2s", &sc->clk_i2s);
+	if (err != 0) {
+		device_printf(dev, "Can't find i2s clock.\n");
+		return (-1);
+	}
+
+	err = clk_enable(sc->clk_i2s);
+	if (err != 0) {
+		device_printf(dev, "Can't enable i2s clock.\n");
+		return (-1);
+	}
+
+	err = clk_set_freq(sc->clk_i2s, 12000000, 0);
+	if (err != 0) {
+		device_printf(dev, "Can't set i2s frequency.\n");
+		return (-1);
+	}
+
+	clk_get_freq(sc->clk_aic, &aic_freq);
+	clk_get_freq(sc->clk_i2s, &i2s_freq);
+
+	device_printf(dev, "Frequency aic %d i2s %d\n",
+	    (uint32_t)aic_freq, (uint32_t)i2s_freq);
+
+	return (0);
+}
+
+static int
+aic_configure(struct aic_softc *sc)
+{
+	int internal_codec;
+	int reg;
+
+	internal_codec = 1;
+
+	WRITE4(sc, AICFR, AICFR_RST);
+
+	/* Configure AIC */
+	reg = 0;
+	if (internal_codec) {
+		reg |= (AICFR_ICDC);
+	} else {
+		reg |= (AICFR_SYNCD | AICFR_BCKD);
+	}
+	reg |= (AICFR_AUSEL);	/* I2S/MSB-justified format. */
+	reg |= (AICFR_TFTH(8));	/* Transmit FIFO threshold */
+	reg |= (AICFR_RFTH(7));	/* Receive FIFO threshold */
+	WRITE4(sc, AICFR, reg);
+
+	reg = READ4(sc, AICFR);
+	reg |= (AICFR_ENB);	/* Enable the controller. */
+	WRITE4(sc, AICFR, reg);
+
+	return (0);
+}
+
+static int
+aic_probe(device_t dev)
+{
+
+	if (!ofw_bus_status_okay(dev))
+		return (ENXIO);
+
+	if (!ofw_bus_is_compatible(dev, "ingenic,jz4780-i2s"))
+		return (ENXIO);
+
+	device_set_desc(dev, "Audio Interface Controller");
+
+	return (BUS_PROBE_DEFAULT);
+}
+
+static int
+aic_attach(device_t dev)
+{
+	char status[SND_STATUSLEN];
+	struct sc_pcminfo *scp;
+	struct aic_softc *sc;
+	int err;
+
+	sc = malloc(sizeof(*sc), M_DEVBUF, M_WAITOK | M_ZERO);
+	sc->dev = dev;
+	sc->pos = 0;
+
+	/* Get xDMA controller */
+	sc->xdma_tx = xdma_ofw_get(sc->dev, "tx");
+	if (sc->xdma_tx == NULL) {
+		device_printf(dev, "Can't find DMA controller.\n");
+		return (ENXIO);
+	}
+
+	/* Alloc xDMA virtual channel. */
+	sc->xchan = xdma_channel_alloc(sc->xdma_tx);
+	if (sc->xchan == NULL) {
+		device_printf(dev, "Can't alloc virtual DMA channel.\n");
+		return (ENXIO);
+	}
+
+	/* Setup sound subsystem */
+	sc->lock = snd_mtxcreate(device_get_nameunit(dev), "aic softc");
+	if (sc->lock == NULL) {
+		device_printf(dev, "Can't create mtx.\n");
+		return (ENXIO);
+	}
+
+	if (bus_alloc_resources(dev, aic_spec, sc->res)) {
+		device_printf(dev,
+		    "could not allocate resources for device\n");
+		return (ENXIO);
+	}
+
+	/* Memory interface */
+	sc->bst = rman_get_bustag(sc->res[0]);
+	sc->bsh = rman_get_bushandle(sc->res[0]);
+	sc->aic_fifo_paddr = rman_get_start(sc->res[0]) + AICDR;
+
+	/* Setup PCM. */
+	scp = malloc(sizeof(struct sc_pcminfo), M_DEVBUF, M_WAITOK | M_ZERO);
+	scp->sc = sc;
+	scp->dev = dev;
+
+	/* Setup audio buffer. */
+	err = aic_dma_setup(sc);
+	if (err != 0) {
+		device_printf(dev, "Can't setup sound buffer.\n");
+		return (ENXIO);
+	}
+
+	/* Setup clocks. */
+	err = aic_configure_clocks(sc);
+	if (err != 0) {
+		device_printf(dev, "Can't configure clocks.\n");
+		return (ENXIO);
+	}
+
+	err = aic_configure(sc);
+	if (err != 0) {
+		device_printf(dev, "Can't configure AIC.\n");
+		return (ENXIO);
+	}
+
+	pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE);
+
+	/* Setup interrupt handler. */
+	err = xdma_setup_intr(sc->xchan, aic_intr, scp, &sc->ih);
+	if (err) {
+		device_printf(sc->dev,
+		    "Can't setup xDMA interrupt handler.\n");
+		return (ENXIO);
+	}
+
+	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, &aicchan_class, scp);
+	scp->chnum++;
+
+	snprintf(status, SND_STATUSLEN, "at %s", ofw_bus_get_name(dev));
+	pcm_setstatus(dev, status);
+
+	mixer_init(dev, &aicmixer_class, scp);
+
+	return (0);
+}
+
+static int
+aic_detach(device_t dev)
+{
+	struct aic_softc *sc;
+
+	sc = device_get_softc(dev);
+
+	xdma_channel_free(sc->xchan);
+
+	bus_release_resources(dev, aic_spec, sc->res);
+
+	return (0);
+}
+
+static device_method_t aic_pcm_methods[] = {
+	/* Device interface */
+	DEVMETHOD(device_probe,		aic_probe),
+	DEVMETHOD(device_attach,	aic_attach),
+	DEVMETHOD(device_detach,	aic_detach),
+	DEVMETHOD_END
+};
+
+static driver_t aic_pcm_driver = {
+	"pcm",
+	aic_pcm_methods,
+	PCM_SOFTC_SIZE,
+};
+
+DRIVER_MODULE(aic, simplebus, aic_pcm_driver, pcm_devclass, 0, 0);
+MODULE_DEPEND(aic, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
+MODULE_VERSION(aic, 1);



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201612201827.uBKIRrBA099876>