[alsa-devel] ASoC: Au12x0/Au1550 PSC Audio support.
Takashi Iwai
tiwai at suse.de
Fri Jul 4 17:13:27 CEST 2008
At Thu, 3 Jul 2008 12:09:25 +0200,
Manuel Lauss wrote:
>
> On Thu, Jul 03, 2008 at 10:55:47AM +0100, Liam Girdwood wrote:
> > On Thu, 2008-07-03 at 11:53 +0200, Manuel Lauss wrote:
> > > On Thu, Jul 03, 2008 at 10:29:19AM +0100, Liam Girdwood wrote:
> > > >
> > > > Btw, any plans to fix or remove some of the 'Fixmes' in your other ASoC
> > > > code. We could upstream the Au1x000 stuff after a little cleanup and
> > > > resolution of the fixmes.
> > >
> > > I've updated the code considerably since the last year. It's still ASoC v1,
> > > and lots of the FIXMEs in the ac97 part won't disappear until it's moved to
> > > ASoC v2 (I hope, didn't investigate).
> > >
> > > If you're interested I can send you a patch (against current linus' HEAD)
> > > with the newest, most awesome au1xxx psc asoc code ;-)
> > >
> >
> > Yes please.
>
> Here you go, apply on top of linus' head + the ac97 PM patch.
>
> ---
>
> From: Manuel Lauss <mano at roarinelk.homelinux.net>
>
> Audio for Au12x0/Au1550 PSCs in AC97 and I2S mode, for ASoC v1 framework.
>
> - DBDMA, AC97 and I2S drivers
> - sample AC97 machine code (Db1200)
>
> Signed-off-by: Manuel Lauss <mano at roarinelk.homelinux.net>
Shall I apply this one? At a quick glance, the patch looks OK.
thanks,
Takashi
> ---
> include/asm-mips/mach-au1x00/au1xxx_psc.h | 51 ++---
> sound/soc/Kconfig | 1 +
> sound/soc/Makefile | 2 +-
> sound/soc/au1x/Kconfig | 36 +++
> sound/soc/au1x/Makefile | 13 +
> sound/soc/au1x/dbdma2.c | 435 +++++++++++++++++++++++++++++
> sound/soc/au1x/psc-ac97.c | 378 +++++++++++++++++++++++++
> sound/soc/au1x/psc-i2s.c | 426 ++++++++++++++++++++++++++++
> sound/soc/au1x/psc.h | 48 ++++
> sound/soc/au1x/sample-ac97.c | 144 ++++++++++
> 10 files changed, 1499 insertions(+), 35 deletions(-)
> create mode 100644 sound/soc/au1x/Kconfig
> create mode 100644 sound/soc/au1x/Makefile
> create mode 100644 sound/soc/au1x/dbdma2.c
> create mode 100644 sound/soc/au1x/psc-ac97.c
> create mode 100644 sound/soc/au1x/psc-i2s.c
> create mode 100644 sound/soc/au1x/psc.h
> create mode 100644 sound/soc/au1x/sample-ac97.c
>
> diff --git a/include/asm-mips/mach-au1x00/au1xxx_psc.h b/include/asm-mips/mach-au1x00/au1xxx_psc.h
> index dae4eca..912768d 100644
> --- a/include/asm-mips/mach-au1x00/au1xxx_psc.h
> +++ b/include/asm-mips/mach-au1x00/au1xxx_psc.h
> @@ -69,29 +69,16 @@
> #define PSC_CTRL_ENABLE 3
>
> /* AC97 Registers. */
> -#define PSC_AC97CFG_OFFSET 0x00000008
> -#define PSC_AC97MSK_OFFSET 0x0000000c
> -#define PSC_AC97PCR_OFFSET 0x00000010
> -#define PSC_AC97STAT_OFFSET 0x00000014
> -#define PSC_AC97EVNT_OFFSET 0x00000018
> -#define PSC_AC97TXRX_OFFSET 0x0000001c
> -#define PSC_AC97CDC_OFFSET 0x00000020
> -#define PSC_AC97RST_OFFSET 0x00000024
> -#define PSC_AC97GPO_OFFSET 0x00000028
> -#define PSC_AC97GPI_OFFSET 0x0000002c
> -
> -#define AC97_PSC_SEL (AC97_PSC_BASE + PSC_SEL_OFFSET)
> -#define AC97_PSC_CTRL (AC97_PSC_BASE + PSC_CTRL_OFFSET)
> -#define PSC_AC97CFG (AC97_PSC_BASE + PSC_AC97CFG_OFFSET)
> -#define PSC_AC97MSK (AC97_PSC_BASE + PSC_AC97MSK_OFFSET)
> -#define PSC_AC97PCR (AC97_PSC_BASE + PSC_AC97PCR_OFFSET)
> -#define PSC_AC97STAT (AC97_PSC_BASE + PSC_AC97STAT_OFFSET)
> -#define PSC_AC97EVNT (AC97_PSC_BASE + PSC_AC97EVNT_OFFSET)
> -#define PSC_AC97TXRX (AC97_PSC_BASE + PSC_AC97TXRX_OFFSET)
> -#define PSC_AC97CDC (AC97_PSC_BASE + PSC_AC97CDC_OFFSET)
> -#define PSC_AC97RST (AC97_PSC_BASE + PSC_AC97RST_OFFSET)
> -#define PSC_AC97GPO (AC97_PSC_BASE + PSC_AC97GPO_OFFSET)
> -#define PSC_AC97GPI (AC97_PSC_BASE + PSC_AC97GPI_OFFSET)
> +#define PSC_AC97CFG 0x00000008
> +#define PSC_AC97MSK 0x0000000c
> +#define PSC_AC97PCR 0x00000010
> +#define PSC_AC97STAT 0x00000014
> +#define PSC_AC97EVNT 0x00000018
> +#define PSC_AC97TXRX 0x0000001c
> +#define PSC_AC97CDC 0x00000020
> +#define PSC_AC97RST 0x00000024
> +#define PSC_AC97GPO 0x00000028
> +#define PSC_AC97GPI 0x0000002c
>
> /* AC97 Config Register. */
> #define PSC_AC97CFG_RT_MASK (3 << 30)
> @@ -192,17 +179,13 @@
> #define PSC_AC97RST_SNC (1 << 0)
>
> /* PSC in I2S Mode. */
> -typedef struct psc_i2s {
> - u32 psc_sel;
> - u32 psc_ctrl;
> - u32 psc_i2scfg;
> - u32 psc_i2smsk;
> - u32 psc_i2spcr;
> - u32 psc_i2sstat;
> - u32 psc_i2sevent;
> - u32 psc_i2stxrx;
> - u32 psc_i2sudf;
> -} psc_i2s_t;
> +#define PSC_I2SCFG 0x08
> +#define PSC_I2SMASK 0x0C
> +#define PSC_I2SPCR 0x10
> +#define PSC_I2SSTAT 0x14
> +#define PSC_I2SEVENT 0x18
> +#define PSC_I2SRXTX 0x1C
> +#define PSC_I2SUDF 0x20
>
> /* I2S Config Register. */
> #define PSC_I2SCFG_RT_MASK (3 << 30)
> diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
> index 18f28ac..caacf95 100644
> --- a/sound/soc/Kconfig
> +++ b/sound/soc/Kconfig
> @@ -25,6 +25,7 @@ config SND_SOC
>
> # All the supported Soc's
> source "sound/soc/at91/Kconfig"
> +source "sound/soc/au1x/Kconfig"
> source "sound/soc/pxa/Kconfig"
> source "sound/soc/s3c24xx/Kconfig"
> source "sound/soc/sh/Kconfig"
> diff --git a/sound/soc/Makefile b/sound/soc/Makefile
> index 782db21..5dafddd 100644
> --- a/sound/soc/Makefile
> +++ b/sound/soc/Makefile
> @@ -1,4 +1,4 @@
> snd-soc-core-objs := soc-core.o soc-dapm.o
>
> obj-$(CONFIG_SND_SOC) += snd-soc-core.o
> -obj-$(CONFIG_SND_SOC) += codecs/ at91/ pxa/ s3c24xx/ sh/ fsl/ davinci/ omap/
> +obj-$(CONFIG_SND_SOC) += codecs/ at91/ au1x/ pxa/ s3c24xx/ sh/ fsl/ davinci/ omap/
> diff --git a/sound/soc/au1x/Kconfig b/sound/soc/au1x/Kconfig
> new file mode 100644
> index 0000000..8ef9015
> --- /dev/null
> +++ b/sound/soc/au1x/Kconfig
> @@ -0,0 +1,36 @@
> +menu "SoC Audio for the Alchemy/AMD/RMI Au1xxx"
> + depends on SOC_AU1200 || SOC_AU1550
> +
> +##
> +## Au1200/Au1550 PSC + DBDMA
> +##
> +config SND_SOC_AU1XPSC
> + tristate "SoC Audio for Au1200/Au1250/Au1550"
> + depends on SND_SOC && (SOC_AU1200 || SOC_AU1550)
> + help
> + This option enables support for the Programmable Serial
> + Controllers in AC97 and I2S mode, and the Descriptor-Based DMA
> + Controller (DBDMA) as found on the Au1200/Au1250/Au1550 SoC.
> +
> +config SND_SOC_AU1XPSC_I2S
> + tristate
> +
> +config SND_SOC_AU1XPSC_AC97
> + tristate
> + select AC97_BUS
> + select SND_AC97_CODEC
> + select SND_SOC_AC97_BUS
> +
> +
> +##
> +## Boards
> +##
> +config SND_SOC_SAMPLE_PSC_AC97
> + tristate "Sample Au12x0/Au1550 PSC AC97 sound machine"
> + select SND_SOC_AU1XPSC_AC97
> + select SND_SOC_AC97_CODEC
> + help
> + This is a sample AC97 sound machine for use in Au12x0/Au1550
> + based systems which have audio on PSC1 (e.g. Db1200 demoboard).
> +
> +endmenu
> diff --git a/sound/soc/au1x/Makefile b/sound/soc/au1x/Makefile
> new file mode 100644
> index 0000000..6c6950b
> --- /dev/null
> +++ b/sound/soc/au1x/Makefile
> @@ -0,0 +1,13 @@
> +# Au1200/Au1550 PSC audio
> +snd-soc-au1xpsc-dbdma-objs := dbdma2.o
> +snd-soc-au1xpsc-i2s-objs := psc-i2s.o
> +snd-soc-au1xpsc-ac97-objs := psc-ac97.o
> +
> +obj-$(CONFIG_SND_SOC_AU1XPSC) += snd-soc-au1xpsc-dbdma.o
> +obj-$(CONFIG_SND_SOC_AU1XPSC_I2S) += snd-soc-au1xpsc-i2s.o
> +obj-$(CONFIG_SND_SOC_AU1XPSC_AC97) += snd-soc-au1xpsc-ac97.o
> +
> +# Boards
> +snd-soc-sample-ac97-objs := sample-ac97.o
> +
> +obj-$(CONFIG_SND_SOC_SAMPLE_PSC_AC97) += snd-soc-sample-ac97.o
> diff --git a/sound/soc/au1x/dbdma2.c b/sound/soc/au1x/dbdma2.c
> new file mode 100644
> index 0000000..f924c54
> --- /dev/null
> +++ b/sound/soc/au1x/dbdma2.c
> @@ -0,0 +1,435 @@
> +/*
> + * Au12x0/Au1550 PSC ALSA ASoC audio support.
> + *
> + * (c) 2007-2008 MSC Vertriebsges.m.b.H.,
> + * Manuel Lauss <mano at roarinelk.homelinux.net>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * DMA glue for Au1x-PSC audio.
> + *
> + * NOTE: all of these drivers can only work with a SINGLE instance
> + * of a PSC. Multiple independent audio devices are impossible
> + * with ASoC v1.
> + */
> +
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/dma-mapping.h>
> +
> +#include <sound/core.h>
> +#include <sound/pcm.h>
> +#include <sound/pcm_params.h>
> +#include <sound/soc.h>
> +
> +#include <asm/mach-au1x00/au1000.h>
> +#include <asm/mach-au1x00/au1xxx_dbdma.h>
> +#include <asm/mach-au1x00/au1xxx_psc.h>
> +
> +#include "psc.h"
> +
> +/*#define PCM_DEBUG*/
> +
> +#define MSG(x...) printk(KERN_INFO "au1x-dbdma: " x)
> +#ifdef PCM_DEBUG
> +#define DBG MSG
> +#else
> +#define DBG(x...) do {} while (0)
> +#endif
> +
> +struct au1xpsc_audio_dmadata {
> + /* DDMA control data */
> + unsigned int ddma_id; /* DDMA direction ID for this PSC */
> + u32 ddma_chan; /* DDMA context */
> +
> + /* PCM context (for irq handlers) */
> + struct snd_pcm_substream *substream;
> + unsigned long curr_period; /* current segment DDMA is working on */
> + unsigned long q_period; /* queue period(s) */
> + unsigned long dma_area; /* address of DMA area (phyical area) */
> + unsigned long dma_area_s; /* start address of DMA area (phyical area) */
> + unsigned long pos; /* current byte position being played */
> + unsigned long periods; /* number of SG segments in total */
> + unsigned long period_bytes; /* size in bytes of one SG segment */
> +
> + /* runtime data */
> + int msbits;
> +};
> +
> +static struct au1xpsc_audio_dmadata *au1xpsc_audio_pcmdma[2];
> +
> +/*
> + * These settings are somewhat okay, at least on my machine audio plays
> + * almost skip-free. Especially the 64kB buffer seems to help a LOT.
> + */
> +#define AU1XPSC_PERIOD_MIN_BYTES 1024
> +#define AU1XPSC_BUFFER_MIN_BYTES 65536
> +
> +#define AU1XPSC_PCM_FMTS \
> + SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 | \
> + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \
> + SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE | \
> + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE | \
> + SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_U32_BE | \
> + 0
> +
> +/* PCM hardware DMA capabilities - platform specific */
> +static const struct snd_pcm_hardware au1xpsc_pcm_hardware = {
> + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
> + SNDRV_PCM_INFO_INTERLEAVED,
> + .formats = AU1XPSC_PCM_FMTS,
> + .period_bytes_min = AU1XPSC_PERIOD_MIN_BYTES,
> + .period_bytes_max = 4096 * 1024 - 1,
> + .periods_min = 2,
> + .periods_max = 4096, /* 2 to as-much-as-you-like */
> + .buffer_bytes_max = 4096 * 1024 - 1,
> + .fifo_size = 16, /* fifo entries of AC97/I2S PSC */
> +};
> +
> +static void au1x_pcm_queue_tx(struct au1xpsc_audio_dmadata *cd)
> +{
> + au1xxx_dbdma_put_source_flags(cd->ddma_chan,
> + (void *)phys_to_virt(cd->dma_area),
> + cd->period_bytes, DDMA_FLAGS_IE);
> +
> + /* update next-to-queue period */
> + ++cd->q_period;
> + cd->dma_area += cd->period_bytes;
> + if (cd->q_period >= cd->periods) {
> + cd->q_period = 0;
> + cd->dma_area = cd->dma_area_s;
> + }
> +}
> +
> +static void au1x_pcm_queue_rx(struct au1xpsc_audio_dmadata *cd)
> +{
> + au1xxx_dbdma_put_dest_flags(cd->ddma_chan,
> + (void *)phys_to_virt(cd->dma_area),
> + cd->period_bytes, DDMA_FLAGS_IE);
> +
> + /* update next-to-queue period */
> + ++cd->q_period;
> + cd->dma_area += cd->period_bytes;
> + if (cd->q_period >= cd->periods) {
> + cd->q_period = 0;
> + cd->dma_area = cd->dma_area_s;
> + }
> +}
> +
> +static void au1x_pcm_dmatx_cb(int irq, void *dev_id)
> +{
> + struct au1xpsc_audio_dmadata *cd = dev_id;
> +
> + cd->pos += cd->period_bytes;
> + if (++cd->curr_period >= cd->periods) {
> + cd->pos = 0;
> + cd->curr_period = 0;
> + }
> + snd_pcm_period_elapsed(cd->substream);
> + au1x_pcm_queue_tx(cd);
> +}
> +
> +static void au1x_pcm_dmarx_cb(int irq, void *dev_id)
> +{
> + struct au1xpsc_audio_dmadata *cd = dev_id;
> +
> + cd->pos += cd->period_bytes;
> + if (++cd->curr_period >= cd->periods) {
> + cd->pos = 0;
> + cd->curr_period = 0;
> + }
> + snd_pcm_period_elapsed(cd->substream);
> + au1x_pcm_queue_rx(cd);
> +}
> +
> +static void au1x_pcm_dbdma_free(struct au1xpsc_audio_dmadata *pcd)
> +{
> + if (pcd->ddma_chan) {
> + au1xxx_dbdma_stop(pcd->ddma_chan);
> + au1xxx_dbdma_reset(pcd->ddma_chan);
> + au1xxx_dbdma_chan_free(pcd->ddma_chan);
> + pcd->ddma_chan = 0;
> + pcd->msbits = 0;
> + }
> +}
> +
> +/* in case of missing DMA ring or changed TX-source / RX-dest bit widths,
> + * allocate (or reallocate) a 2-descriptor DMA ring with bit depth according
> + * to ALSA-supplied sample depth. This is due to limitations in the dbdma api
> + * (cannot adjust source/dest widths of already allocated descriptor ring).
> + */
> +static int au1x_pcm_dbdma_realloc(struct au1xpsc_audio_dmadata *pcd, int is_rx,
> + int msbits)
> +{
> + /* DMA only in 8/16/32 bit widths */
> + if (msbits == 24)
> + msbits = 32;
> +
> + /* check current config: correct bits and descriptors allocated? */
> + if ((pcd->ddma_chan) && (msbits == pcd->msbits))
> + goto out; /* all ok! */
> +
> + au1x_pcm_dbdma_free(pcd);
> +
> + if (is_rx)
> + pcd->ddma_chan = au1xxx_dbdma_chan_alloc(pcd->ddma_id,
> + DSCR_CMD0_ALWAYS,
> + au1x_pcm_dmarx_cb, (void *)pcd);
> + else
> + pcd->ddma_chan = au1xxx_dbdma_chan_alloc(DSCR_CMD0_ALWAYS,
> + pcd->ddma_id,
> + au1x_pcm_dmatx_cb, (void *)pcd);
> +
> + if (!pcd->ddma_chan)
> + return -ENOMEM;;
> +
> + au1xxx_dbdma_set_devwidth(pcd->ddma_chan, msbits);
> + au1xxx_dbdma_ring_alloc(pcd->ddma_chan, 2);
> +
> + pcd->msbits = msbits;
> +
> + au1xxx_dbdma_stop(pcd->ddma_chan);
> + au1xxx_dbdma_reset(pcd->ddma_chan);
> +
> +out:
> + return 0;
> +}
> +
> +/*
> + * Called by ALSA when the hardware params are set by application. This
> + * function can also be called multiple times and can allocate buffers
> + * (using snd_pcm_lib_* ). It's non-atomic.
> + */
> +static int au1xpsc_pcm_hw_params(struct snd_pcm_substream *substream,
> + struct snd_pcm_hw_params *params)
> +{
> + struct snd_pcm_runtime *runtime = substream->runtime;
> + struct au1xpsc_audio_dmadata *pcd;
> + int is_rx, ret;
> +
> + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
> + if (ret < 0)
> + goto out;
> +
> + is_rx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
> + pcd = au1xpsc_audio_pcmdma[is_rx];
> +
> + DBG("runtime->dma_area = 0x%08lx dma_addr_t = 0x%08lx dma_size = %d "
> + "runtime->min_align %d\n",
> + (unsigned long)runtime->dma_area,
> + (unsigned long)runtime->dma_addr, runtime->dma_bytes,
> + runtime->min_align);
> +
> + DBG("bits %d frags %d frag_bytes %d is_rx %d\n", params->msbits,
> + params_periods(params), params_period_bytes(params), is_rx);
> +
> + ret = au1x_pcm_dbdma_realloc(pcd, is_rx, params->msbits);
> + if (ret) {
> + MSG("DDMA channel (re)alloc failed!\n");
> + goto out;
> + }
> +
> + pcd->substream = substream;
> + pcd->period_bytes = params_period_bytes(params);
> + pcd->periods = params_periods(params);
> + pcd->dma_area_s = pcd->dma_area = (unsigned long)runtime->dma_addr;
> + pcd->q_period = 0;
> + pcd->curr_period = 0;
> + pcd->pos = 0;
> +
> + ret = 0;
> +out:
> + return ret;
> +}
> +
> +static int au1xpsc_pcm_hw_free(struct snd_pcm_substream *substream)
> +{
> + snd_pcm_lib_free_pages(substream);
> + return 0;
> +}
> +
> +static int au1xpsc_pcm_prepare(struct snd_pcm_substream *substream)
> +{
> + struct au1xpsc_audio_dmadata *pcd;
> + int is_rx;
> +
> + is_rx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
> + pcd = au1xpsc_audio_pcmdma[is_rx];
> +
> + au1xxx_dbdma_reset(pcd->ddma_chan);
> +
> + if (is_rx) {
> + au1x_pcm_queue_rx(pcd);
> + au1x_pcm_queue_rx(pcd);
> + } else {
> + au1x_pcm_queue_tx(pcd);
> + au1x_pcm_queue_tx(pcd);
> + }
> +
> + return 0;
> +}
> +
> +static int au1xpsc_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
> +{
> + int is_rx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
> + u32 chan = au1xpsc_audio_pcmdma[is_rx]->ddma_chan;
> +
> + switch (cmd) {
> + case SNDRV_PCM_TRIGGER_START:
> + case SNDRV_PCM_TRIGGER_RESUME:
> + au1xxx_dbdma_start(chan);
> + break;
> + case SNDRV_PCM_TRIGGER_STOP:
> + case SNDRV_PCM_TRIGGER_SUSPEND:
> + au1xxx_dbdma_stop(chan);
> + break;
> + default:
> + return -EINVAL;
> + }
> + return 0;
> +}
> +
> +static snd_pcm_uframes_t
> +au1xpsc_pcm_pointer(struct snd_pcm_substream *substream)
> +{
> + int is_rx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
> +
> + return bytes_to_frames(substream->runtime,
> + au1xpsc_audio_pcmdma[is_rx]->pos);
> +}
> +
> +static int au1xpsc_pcm_open(struct snd_pcm_substream *substream)
> +{
> + snd_soc_set_runtime_hwparams(substream, &au1xpsc_pcm_hardware);
> +
> + return 0;
> +}
> +
> +static int au1xpsc_pcm_close(struct snd_pcm_substream *substream)
> +{
> + int is_rx = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
> +
> + au1x_pcm_dbdma_free(au1xpsc_audio_pcmdma[is_rx]);
> +
> + return 0;
> +}
> +
> +struct snd_pcm_ops au1xpsc_pcm_ops = {
> + .open = au1xpsc_pcm_open,
> + .close = au1xpsc_pcm_close,
> + .ioctl = snd_pcm_lib_ioctl,
> + .hw_params = au1xpsc_pcm_hw_params,
> + .hw_free = au1xpsc_pcm_hw_free,
> + .prepare = au1xpsc_pcm_prepare,
> + .trigger = au1xpsc_pcm_trigger,
> + .pointer = au1xpsc_pcm_pointer,
> +};
> +
> +static void au1xpsc_pcm_free_dma_buffers(struct snd_pcm *pcm)
> +{
> + snd_pcm_lib_preallocate_free_for_all(pcm);
> +}
> +
> +static int au1xpsc_pcm_new(struct snd_card *card,
> + struct snd_soc_codec_dai *dai,
> + struct snd_pcm *pcm)
> +{
> + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
> + card->dev, AU1XPSC_BUFFER_MIN_BYTES, (4096 * 1024) - 1);
> +
> + return 0;
> +}
> +
> +static int au1xpsc_pcm_probe(struct platform_device *pdev)
> +{
> + struct resource *r;
> + int ret;
> +
> + if (au1xpsc_audio_pcmdma[0])
> + return -EBUSY;
> +
> + /* TX DMA */
> + au1xpsc_audio_pcmdma[0]
> + = kzalloc(sizeof(struct au1xpsc_audio_dmadata), GFP_KERNEL);
> + if (!au1xpsc_audio_pcmdma[0])
> + return -ENOMEM;
> +
> + r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
> + if (!r) {
> + ret = -ENODEV;
> + goto out1;
> + }
> + (au1xpsc_audio_pcmdma[0])->ddma_id = r->start;
> +
> + /* RX DMA */
> + au1xpsc_audio_pcmdma[1]
> + = kzalloc(sizeof(struct au1xpsc_audio_dmadata), GFP_KERNEL);
> + if (!au1xpsc_audio_pcmdma[1])
> + return -ENOMEM;
> +
> + r = platform_get_resource(pdev, IORESOURCE_DMA, 1);
> + if (!r) {
> + ret = -ENODEV;
> + goto out2;
> + }
> + (au1xpsc_audio_pcmdma[1])->ddma_id = r->start;
> +
> + return 0;
> +
> +out2:
> + kfree(au1xpsc_audio_pcmdma[1]);
> + au1xpsc_audio_pcmdma[1] = NULL;
> +out1:
> + kfree(au1xpsc_audio_pcmdma[0]);
> + au1xpsc_audio_pcmdma[0] = NULL;
> + return ret;
> +}
> +
> +static int au1xpsc_pcm_remove(struct platform_device *pdev)
> +{
> + int i;
> +
> + for (i = 0; i < 2; i++) {
> + if (au1xpsc_audio_pcmdma[i]) {
> + au1x_pcm_dbdma_free(au1xpsc_audio_pcmdma[i]);
> + kfree(au1xpsc_audio_pcmdma[i]);
> + au1xpsc_audio_pcmdma[i] = NULL;
> + }
> + }
> +
> + return 0;
> +}
> +
> +/* au1xpsc audio platform */
> +struct snd_soc_platform au1xpsc_soc_platform = {
> + .name = "au1xpsc-pcm-dbdma",
> + .probe = au1xpsc_pcm_probe,
> + .remove = au1xpsc_pcm_remove,
> + .pcm_ops = &au1xpsc_pcm_ops,
> + .pcm_new = au1xpsc_pcm_new,
> + .pcm_free = au1xpsc_pcm_free_dma_buffers,
> +};
> +EXPORT_SYMBOL_GPL(au1xpsc_soc_platform);
> +
> +static int __init au1xpsc_audio_dbdma_init(void)
> +{
> + au1xpsc_audio_pcmdma[0] = NULL;
> + au1xpsc_audio_pcmdma[1] = NULL;
> + return 0;
> +}
> +
> +static void __exit au1xpsc_audio_dbdma_exit(void)
> +{
> +}
> +
> +module_init(au1xpsc_audio_dbdma_init);
> +module_exit(au1xpsc_audio_dbdma_exit);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Au12x0/Au1550 PSC Audio DMA driver");
> +MODULE_AUTHOR("Manuel Lauss <mano at roarinelk.homelinux.net>");
> diff --git a/sound/soc/au1x/psc-ac97.c b/sound/soc/au1x/psc-ac97.c
> new file mode 100644
> index 0000000..c9cf72b
> --- /dev/null
> +++ b/sound/soc/au1x/psc-ac97.c
> @@ -0,0 +1,378 @@
> +/*
> + * Au12x0/Au1550 PSC ALSA ASoC audio support.
> + *
> + * (c) 2007-2008 MSC Vertriebsges.m.b.H.,
> + * Manuel Lauss <mano at roarinelk.homelinux.net>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * Au1xxx-PSC AC97 glue.
> + *
> + * NOTE: all of these drivers can only work with a SINGLE instance
> + * of a PSC. Multiple independent audio devices are impossible
> + * with ASoC v1.
> + */
> +
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/device.h>
> +#include <linux/delay.h>
> +#include <linux/suspend.h>
> +#include <sound/core.h>
> +#include <sound/pcm.h>
> +#include <sound/initval.h>
> +#include <sound/soc.h>
> +#include <asm/mach-au1x00/au1000.h>
> +#include <asm/mach-au1x00/au1xxx_psc.h>
> +
> +#include "psc.h"
> +
> +#define AC97_RD (1<<25)
> +
> +#define AC97_DIR \
> + (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE)
> +
> +#define AC97_RATES \
> + SNDRV_PCM_RATE_8000_48000
> +
> +#define AC97_FMTS \
> + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3BE
> +
> +/* instance data. There can be only one, MacLeod!!!! */
> +static struct au1xpsc_audio_data *au1xpsc_ac97_workdata;
> +
> +/* AC97 controller reads codec register */
> +static unsigned short au1xpsc_ac97_read(struct snd_ac97 *ac97,
> + unsigned short reg)
> +{
> + /* FIXME */
> + struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata;
> + unsigned short data, tmo;
> +
> + au_writel(AC97_RD | ((reg & 127) << 16), AC97_CDC(pscdata));
> + au_sync();
> +
> + tmo = 1000;
> + while ((!(au_readl(AC97_EVNT(pscdata)) & (1<<24))) && --tmo)
> + udelay(2);
> +
> + if (!tmo)
> + data = 0xffff;
> + else
> + data = au_readl(AC97_CDC(pscdata)) & 0xffff;
> +
> + au_writel(1<<24, AC97_EVNT(pscdata));
> + au_sync();
> +
> + return data;
> +}
> +
> +/* AC97 controller writes to codec register */
> +static void au1xpsc_ac97_write(struct snd_ac97 *ac97, unsigned short reg,
> + unsigned short val)
> +{
> + /* FIXME */
> + struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata;
> + unsigned int tmo;
> +
> + au_writel(((reg & 127) << 16) | (val & 0xffff), AC97_CDC(pscdata));
> + au_sync();
> + tmo = 1000;
> + while ((!(au_readl(AC97_EVNT(pscdata)) & (1 << 24))) && --tmo)
> + au_sync();
> +
> + au_writel(1 << 24, AC97_EVNT(pscdata));
> + au_sync();
> +}
> +
> +/* AC97 controller asserts a warm reset */
> +static void au1xpsc_ac97_warm_reset(struct snd_ac97 *ac97)
> +{
> + /* FIXME */
> + struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata;
> +
> + au_writel(1, AC97_RST(pscdata));
> + au_sync();
> + msleep(10);
> + au_writel(0, AC97_RST(pscdata));
> + au_sync();
> +}
> +
> +static void au1xpsc_ac97_cold_reset(struct snd_ac97 *ac97)
> +{
> + /* FIXME */
> + struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata;
> + int i;
> +
> + /* disable PSC during cold reset */
> + au_writel(0, PSC_CTRL(pscdata));
> +
> + /* issue cold reset */
> + au_writel(2, AC97_RST(pscdata));
> + au_sync();
> + msleep(500);
> + au_writel(0, AC97_RST(pscdata));
> + au_sync();
> +
> + /* enable PSC */
> + au_writel(3, PSC_CTRL(pscdata));
> + au_sync();
> +
> + /* wait for PSC to indicate it's ready */
> + i = 100000;
> + while (((au_readl(AC97_STAT(pscdata)) & 1) == 0) && (--i))
> + au_sync();
> +
> + if (i == 0) {
> + printk(KERN_ALERT "psc-ac97: PSC not ready!\n");
> + return;
> + }
> +
> + /* enable the ac97 function */
> + au_writel(pscdata->cfg | 0x04000000, AC97_CFG(pscdata));
> + au_sync();
> +
> + /* wait for AC97 core to become ready */
> + i = 100000;
> + while (((au_readl(AC97_STAT(pscdata)) & 2) == 0) && (--i))
> + au_sync();
> + if (i == 0)
> + printk(KERN_ALERT "psc-ac97: AC97 ctrl not ready\n");
> +}
> +
> +/* AC97 controller operations */
> +struct snd_ac97_bus_ops soc_ac97_ops = {
> + .read = au1xpsc_ac97_read,
> + .write = au1xpsc_ac97_write,
> + .reset = au1xpsc_ac97_cold_reset,
> + .warm_reset = au1xpsc_ac97_warm_reset,
> +};
> +EXPORT_SYMBOL_GPL(soc_ac97_ops);
> +
> +static int au1xpsc_ac97_hw_params(struct snd_pcm_substream *substream,
> + struct snd_pcm_hw_params *params)
> +{
> + struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata;
> + unsigned long r;
> + int chans, recv;
> +
> + chans = params_channels(params);
> + recv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
> +
> + /* need to disable the controller before changing any other
> + * AC97CFG reg contents
> + */
> + r = au_readl(AC97_CFG(pscdata));
> + au_writel(r & ~(1<<26), AC97_CFG(pscdata));
> + au_sync();
> +
> + /* set sample bitdepth: REG[24:21]=(BITS-2)/2 */
> + r &= ~(0xf << 21);
> + r |= (((params->msbits-2)>>1) & 0xf) << 21;
> +
> + /* channels */
> + r |= (3 << (1 + (recv ? 0 : 10))); /* stereo pair */
> +
> + /* set FIFO params: max fifo threshold, 8 slots TX/RX */
> + r |= (3<<30) | (3<<28);
> +
> + /* finally enable the AC97 controller again */
> + au_writel(r | (1<<26), AC97_CFG(pscdata));
> + au_sync();
> +
> + return 0;
> +}
> +
> +static int au1xpsc_ac97_trigger(struct snd_pcm_substream *substream,
> + int cmd)
> +{
> + struct au1xpsc_audio_data *pscdata = au1xpsc_ac97_workdata;
> + int ret, rcv;
> +
> + rcv = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 0 : 1;
> + ret = 0;
> +
> + switch (cmd) {
> + case SNDRV_PCM_TRIGGER_START:
> + case SNDRV_PCM_TRIGGER_RESUME:
> + au_writel(1 << (rcv ? 4 : 0), AC97_PCR(pscdata));
> + au_sync();
> + break;
> + case SNDRV_PCM_TRIGGER_STOP:
> + case SNDRV_PCM_TRIGGER_SUSPEND:
> + au_writel(1 << (rcv ? 5 : 1), AC97_PCR(pscdata));
> + au_sync();
> + break;
> + default:
> + ret = -EINVAL;
> + }
> + return ret;
> +}
> +
> +static int au1xpsc_ac97_probe(struct platform_device *pdev)
> +{
> + int ret;
> + struct resource *r;
> + unsigned long sel;
> +
> + if (au1xpsc_ac97_workdata)
> + return -EBUSY;
> +
> + au1xpsc_ac97_workdata =
> + kzalloc(sizeof(struct au1xpsc_audio_data), GFP_KERNEL);
> + if (!au1xpsc_ac97_workdata)
> + return -ENOMEM;
> +
> + r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!r) {
> + ret = -ENODEV;
> + goto out0;
> + }
> +
> + ret = -EBUSY;
> + au1xpsc_ac97_workdata->ioarea =
> + request_mem_region(r->start, r->end - r->start + 1,
> + "au1xpsc_ac97");
> + if (!au1xpsc_ac97_workdata->ioarea)
> + goto out0;
> +
> + au1xpsc_ac97_workdata->mmio = ioremap(r->start, 0xffff);
> + if (!au1xpsc_ac97_workdata->mmio)
> + goto out1;
> +
> + /* preserve PSC clock source set up by platform (dev.platform_data
> + * is already occupied by soc layer)
> + */
> + sel = au_readl(PSC_SEL(au1xpsc_ac97_workdata)) & PSC_SEL_CLK_MASK;
> + au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_ac97_workdata));
> + au_sync();
> + au_writel(0, PSC_SEL(au1xpsc_ac97_workdata));
> + au_sync();
> + au_writel(PSC_SEL_PS_AC97MODE | sel, PSC_SEL(au1xpsc_ac97_workdata));
> + au_sync();
> + /* enable PSC */
> + au_writel(PSC_CTRL_ENABLE, PSC_CTRL(au1xpsc_ac97_workdata));
> + au_sync();
> +
> + return 0;
> +
> +out1:
> + release_resource(au1xpsc_ac97_workdata->ioarea);
> + kfree(au1xpsc_ac97_workdata->ioarea);
> +out0:
> + kfree(au1xpsc_ac97_workdata);
> + au1xpsc_ac97_workdata = NULL;
> + return ret;
> +}
> +
> +static void au1xpsc_ac97_remove(struct platform_device *pdev)
> +{
> + /* disable PSC completely */
> + au_writel(0, AC97_CFG(au1xpsc_ac97_workdata));
> + au_sync();
> + au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_ac97_workdata));
> + au_sync();
> +
> + iounmap(au1xpsc_ac97_workdata->mmio);
> + release_resource(au1xpsc_ac97_workdata->ioarea);
> + kfree(au1xpsc_ac97_workdata->ioarea);
> + kfree(au1xpsc_ac97_workdata);
> + au1xpsc_ac97_workdata = NULL;
> +}
> +
> +static int au1xpsc_ac97_suspend(struct platform_device *pdev,
> + struct snd_soc_cpu_dai *cpu_dai)
> +{
> + /* save interesting registers and disable PSC */
> + au1xpsc_ac97_workdata->pm[0] =
> + au_readl(PSC_SEL(au1xpsc_ac97_workdata));
> + au1xpsc_ac97_workdata->pm[1] =
> + au_readl(AC97_CFG(au1xpsc_ac97_workdata));
> +
> + au_writel(0, AC97_CFG(au1xpsc_ac97_workdata));
> + au_sync();
> + au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_ac97_workdata));
> + au_sync();
> +
> + return 0;
> +}
> +
> +static int au1xpsc_ac97_resume(struct platform_device *pdev,
> + struct snd_soc_cpu_dai *cpu_dai)
> +{
> + int i;
> +
> + au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_ac97_workdata));
> + au_sync();
> + au_writel(0, PSC_SEL(au1xpsc_ac97_workdata));
> + au_sync();
> +
> + au_writel(au1xpsc_ac97_workdata->pm[0],
> + PSC_SEL(au1xpsc_ac97_workdata));
> + au_sync();
> + au_writel(0, AC97_CFG(au1xpsc_ac97_workdata));
> + au_sync();
> +
> + /* enable PSC */
> + au_writel(PSC_CTRL_ENABLE, PSC_CTRL(au1xpsc_ac97_workdata));
> + au_sync();
> +
> + /* wait for PSC to indicate it's ready */
> + i = 100000;
> + while ((!(au_readl(AC97_STAT(au1xpsc_ac97_workdata)) & 1)) && (--i))
> + au_sync();
> +
> + /* after this point the ac97 core will cold-reset the codec.
> + * During cold-reset the code will write pre-defined data to
> + * the config register.
> + */
> + au1xpsc_ac97_workdata->cfg = au1xpsc_ac97_workdata->pm[1];
> +
> + return 0;
> +}
> +
> +struct snd_soc_cpu_dai au1xpsc_ac97_dai = {
> + .name = "au1xpsc_ac97",
> + .type = SND_SOC_DAI_AC97,
> + .probe = au1xpsc_ac97_probe,
> + .remove = au1xpsc_ac97_remove,
> + .suspend = au1xpsc_ac97_suspend,
> + .resume = au1xpsc_ac97_resume,
> + .playback = {
> + .rates = AC97_RATES,
> + .formats = AC97_FMTS,
> + .channels_min = 2,
> + .channels_max = 2,
> + },
> + .capture = {
> + .rates = AC97_RATES,
> + .formats = AC97_FMTS,
> + .channels_min = 2,
> + .channels_max = 2,
> + },
> + .ops = {
> + .trigger = au1xpsc_ac97_trigger,
> + .hw_params = au1xpsc_ac97_hw_params,
> + },
> +};
> +
> +EXPORT_SYMBOL_GPL(au1xpsc_ac97_dai);
> +
> +static int __init au1xpsc_ac97_init(void)
> +{
> + au1xpsc_ac97_workdata = NULL;
> + return 0;
> +}
> +
> +static void __exit au1xpsc_ac97_exit(void)
> +{
> +}
> +
> +module_init(au1xpsc_ac97_init);
> +module_exit(au1xpsc_ac97_exit);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Au12x0/Au1550 PSC AC97 ALSA ASoC audio driver");
> +MODULE_AUTHOR("Manuel Lauss <mano at roarinelk.homelinux.net>");
> diff --git a/sound/soc/au1x/psc-i2s.c b/sound/soc/au1x/psc-i2s.c
> new file mode 100644
> index 0000000..42d4488
> --- /dev/null
> +++ b/sound/soc/au1x/psc-i2s.c
> @@ -0,0 +1,426 @@
> +/*
> + * Au12x0/Au1550 PSC ALSA ASoC audio support.
> + *
> + * (c) 2007-2008 MSC Vertriebsges.m.b.H.,
> + * Manuel Lauss <mano at roarinelk.homelinux.net>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * Au1xxx-PSC I2S glue.
> + *
> + * NOTE: all of these drivers can only work with a SINGLE instance
> + * of a PSC. Multiple independent audio devices are impossible
> + * with ASoC v1.
> + */
> +
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/suspend.h>
> +#include <sound/core.h>
> +#include <sound/pcm.h>
> +#include <sound/initval.h>
> +#include <sound/soc.h>
> +#include <asm/mach-au1x00/au1000.h>
> +#include <asm/mach-au1x00/au1xxx_psc.h>
> +
> +#include "psc.h"
> +
> +/* supported I2S DAI hardware formats */
> +#define AU1XPSC_I2S_DAIFMT \
> + (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_LEFT_J | \
> + SND_SOC_DAIFMT_NB_NF)
> +
> +/* supported I2S direction */
> +#define AU1XPSC_I2S_DIR \
> + (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE)
> +
> +#define AU1XPSC_I2S_RATES \
> + SNDRV_PCM_RATE_8000_192000
> +
> +#define AU1XPSC_I2S_FMTS \
> + (SNDRV_PCM_FMTBIT_S16_LE/* | SNDRV_PCM_FMTBIT_S24_LE*/)
> +
> +
> +/* instance data. There can be only one, MacLeod!!!! */
> +static struct au1xpsc_audio_data *au1xpsc_i2s_workdata;
> +
> +static int au1xpsc_i2s_set_fmt(struct snd_soc_cpu_dai *cpu_dai,
> + unsigned int fmt)
> +{
> + struct au1xpsc_audio_data *pscdata = au1xpsc_i2s_workdata;
> + unsigned long ct;
> + int ret;
> +
> + ret = -EINVAL;
> +
> + ct = pscdata->cfg;
> +
> + ct &= ~((1<<9)|(1<<10)); /* MSB (left-) justified*/
> + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
> + case SND_SOC_DAIFMT_I2S:
> + ct |= (1<<9); /* enable I2S mode */
> + break;
> + case SND_SOC_DAIFMT_MSB:
> + break;
> + case SND_SOC_DAIFMT_LSB:
> + ct |= (1<<10); /* LSB (right-) justified */
> + break;
> + default:
> + goto out;
> + }
> +
> + ct &= ~((1 << 12) | (1 << 15)); /* IB-IF */
> + switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
> + case SND_SOC_DAIFMT_NB_NF:
> + ct |= (1<<12) | (1<<15); /* NF: left = low */
> + break;
> + case SND_SOC_DAIFMT_NB_IF:
> + ct |= (1<<12);
> + break;
> + case SND_SOC_DAIFMT_IB_NF:
> + ct |= (1<<15); /* IB-NF */
> + break;
> + case SND_SOC_DAIFMT_IB_IF:
> + break;
> + default:
> + goto out;
> + }
> +
> + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
> + case SND_SOC_DAIFMT_CBM_CFM: /* CODEC master */
> + ct |= (1<<0); /* PSC I2S slave mode */
> + break;
> + case SND_SOC_DAIFMT_CBS_CFS: /* CODEC slave */
> + ct &= ~(1<<0); /* PSC I2S Master mode */
> + break;
> + default:
> + goto out;
> + }
> +
> + pscdata->cfg = ct;
> + ret = 0;
> +out:
> + return ret;
> +}
> +
> +static int au1xpsc_i2s_hw_params(struct snd_pcm_substream *substream,
> + struct snd_pcm_hw_params *params)
> +{
> + struct au1xpsc_audio_data *pscdata = au1xpsc_i2s_workdata;
> +
> + int cfgbits;
> + unsigned long stat;
> +
> + /* check if the PSC is already streaming data */
> + /* FIXME: should probably ONLY check if pscdata->rate is != 0 */
> + stat = au_readl(I2S_STAT(pscdata));
> + if (stat & (3<<4)) {
> + /* already active, check settings (don't trust pscdata->cfg) */
> + cfgbits = au_readl(I2S_CFG(pscdata));
> + cfgbits = ((cfgbits >> 4) & 0x1f) + 1;
> + if (cfgbits != params->msbits)
> + return -EINVAL;
> +
> + /* FIXME: does ALSA/ASoC already check? */
> + if (params_rate(params) != pscdata->rate)
> + return -EINVAL;
> +
> + } else {
> + /* set sample bitdepth */
> + pscdata->cfg &= ~(0x1f << 4);
> + pscdata->cfg |= (((params->msbits - 1) & 0x1f) << 4);
> + /* remember current rate for other stream */
> + pscdata->rate = params_rate(params);
> + }
> + return 0;
> +}
> +
> +/* Configure PSC late: on my devel systems the codec is I2S master and
> + * supplies the i2sbitclock __AND__ i2sMclk (!) to the PSC unit. ASoC
> + * uses aggressive PM and switches the codec off when it is not in use
> + * which also means the PSC unit doesn't get any clocks and is therefore
> + * dead. That's why this chunk here gets called from the trigger callback
> + * because I can be reasonably certain the codec is driving the clocks.
> + */
> +static int au1xpsc_i2s_configure(struct au1xpsc_audio_data *pscdata)
> +{
> + unsigned long tmo;
> +
> + au_writel(PSC_CTRL_ENABLE, PSC_CTRL(pscdata));
> + au_sync();
> +
> + /* wait for PSC unit to become ready */
> + tmo = 1000000;
> + while (!(au_readl(I2S_STAT(pscdata)) & 1) && tmo)
> + tmo--;
> +
> + if (!tmo)
> + return -ETIMEDOUT;
> +
> + /* configure the I2S controller; need to disable it first. */
> + au_writel(0, I2S_CFG(pscdata));
> + au_sync();
> +
> + /* start I2S controller: config | max_tx_thresh | max_rx_thresh | enable */
> + au_writel(pscdata->cfg | (1<<26), I2S_CFG(pscdata));
> + au_sync();
> +
> + /* wait for I2S controller to become ready */
> + tmo = 1000000;
> + while (!(au_readl(I2S_STAT(pscdata)) & 2) && tmo)
> + tmo--;
> +
> + return (tmo == 0) ? -ETIMEDOUT : 0;
> +}
> +
> +static int au1xpsc_i2s_start(struct au1xpsc_audio_data *pscdata, int play)
> +{
> + unsigned long tmo;
> + int ret;
> +
> + ret = 0;
> +
> + /* if both TX and RX are idle, configure the PSC */
> + if ((au_readl(I2S_STAT(pscdata)) & ((1<<4)|(1<<5))) == 0) {
> + ret = au1xpsc_i2s_configure(pscdata);
> + if (ret)
> + goto out;
> + }
> +
> + /* clear fifo */
> + au_writel(play ? (1<<2) : (1<<6), I2S_PCR(pscdata));
> + au_sync();
> +
> + /* and start */
> + au_writel(play ? (1<<0) : (1<<4), I2S_PCR(pscdata));
> + au_sync();
> +
> + /* wait for start confirmation */
> + tmo = 1000000;
> + while ((0 == (au_readl(I2S_STAT(pscdata)) & (play ? (1<<4) : (1<<5)))) && tmo)
> + tmo--;
> +
> + if (!tmo) {
> + au_writel(play ? (1<<1) : (1<<5), I2S_PCR(pscdata));
> + au_sync();
> + ret = -ETIMEDOUT;
> + }
> +out:
> + return ret;
> +}
> +
> +static int au1xpsc_i2s_stop(struct au1xpsc_audio_data *pscdata, int play)
> +{
> + unsigned long tmo, stat;
> +
> + au_writel(play ? (1<<1) : (1<<5), I2S_PCR(pscdata));
> + au_sync();
> + /* wait for stop confirmation */
> + tmo = 1000000;
> + do {
> + stat = au_readl(I2S_STAT(pscdata));
> + tmo--;
> + } while ((stat & (play ? (1<<4) : (1<<5))) && tmo);
> +
> + /* if both TX and RX are idle, disable the I2S and PSC */
> + stat = au_readl(I2S_STAT(pscdata)) & (3<<4);
> + if (!stat) {
> + /* disable I2S controller */
> + au_writel(0, I2S_CFG(pscdata));
> + au_sync();
> +
> + /* suspend PSC */
> + au_writel(PSC_CTRL_SUSPEND, PSC_CTRL(pscdata));
> + au_sync();
> +
> + pscdata->rate = 0;
> + /* don't change pscdata->cfg! PM depends on it! */
> + }
> + return 0;
> +}
> +
> +static int au1xpsc_i2s_trigger(struct snd_pcm_substream *substream, int cmd)
> +{
> + struct au1xpsc_audio_data *pscdata = au1xpsc_i2s_workdata;
> + int ret, play;
> +
> + play = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? 1 : 0;
> +
> + switch (cmd) {
> + case SNDRV_PCM_TRIGGER_START:
> + case SNDRV_PCM_TRIGGER_RESUME:
> + ret = au1xpsc_i2s_start(pscdata, play);
> + break;
> + case SNDRV_PCM_TRIGGER_STOP:
> + case SNDRV_PCM_TRIGGER_SUSPEND:
> + ret = au1xpsc_i2s_stop(pscdata, play);
> + break;
> + default:
> + ret = -EINVAL;
> + }
> + return ret;
> +}
> +
> +static int au1xpsc_i2s_probe(struct platform_device *pdev)
> +{
> + int ret;
> + struct resource *r;
> + unsigned long sel;
> +
> + if (au1xpsc_i2s_workdata)
> + return -EBUSY;
> +
> + au1xpsc_i2s_workdata =
> + kzalloc(sizeof(struct au1xpsc_audio_data), GFP_KERNEL);
> + if (!au1xpsc_i2s_workdata)
> + return -ENOMEM;
> +
> + r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!r) {
> + ret = -ENODEV;
> + goto out0;
> + }
> +
> + ret = -EBUSY;
> + au1xpsc_i2s_workdata->ioarea =
> + request_mem_region(r->start, r->end - r->start + 1,
> + "au1xpsc_i2s");
> + if (!au1xpsc_i2s_workdata->ioarea)
> + goto out0;
> +
> + au1xpsc_i2s_workdata->mmio = ioremap(r->start, 0xffff);
> + if (!au1xpsc_i2s_workdata->mmio)
> + goto out1;
> +
> + /* preserve PSC clock source set up by platform (dev.platform_data
> + * is already occupied by soc layer)
> + */
> + sel = au_readl(PSC_SEL(au1xpsc_i2s_workdata)) & PSC_SEL_CLK_MASK;
> + au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_i2s_workdata));
> + au_sync();
> + au_writel(PSC_SEL_PS_I2SMODE | sel, PSC_SEL(au1xpsc_i2s_workdata));
> + au_writel(0, I2S_CFG(au1xpsc_i2s_workdata));
> + au_sync();
> + au_writel(PSC_CTRL_ENABLE, PSC_CTRL(au1xpsc_i2s_workdata));
> + au_sync();
> +
> + /* preconfigure: set max rx/tx fifo depths */
> + au1xpsc_i2s_workdata->cfg |= (3<<30) | (3<<28);
> +
> + /* controller might not become ready if it is clocked by the codec;
> + * codec is initialized later on and parameters are set even later
> + */
> +
> + return 0;
> +
> +out1:
> + release_resource(au1xpsc_i2s_workdata->ioarea);
> + kfree(au1xpsc_i2s_workdata->ioarea);
> +out0:
> + kfree(au1xpsc_i2s_workdata);
> + au1xpsc_i2s_workdata = NULL;
> + return ret;
> +}
> +
> +static void au1xpsc_i2s_remove(struct platform_device *pdev)
> +{
> + au_writel(0, I2S_CFG(au1xpsc_i2s_workdata));
> + au_sync();
> + au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_i2s_workdata));
> + au_sync();
> +
> + iounmap(au1xpsc_i2s_workdata->mmio);
> + release_resource(au1xpsc_i2s_workdata->ioarea);
> + kfree(au1xpsc_i2s_workdata->ioarea);
> + kfree(au1xpsc_i2s_workdata);
> + au1xpsc_i2s_workdata = NULL;
> +}
> +
> +
> +static int au1xpsc_i2s_suspend(struct platform_device *pdev,
> + struct snd_soc_cpu_dai *cpu_dai)
> +{
> + /* save interesting registers and disable PSC */
> + au1xpsc_i2s_workdata->pm[0] =
> + au_readl(PSC_SEL(au1xpsc_i2s_workdata));
> + au1xpsc_i2s_workdata->pm[1] =
> + au_readl(I2S_CFG(au1xpsc_i2s_workdata));
> +
> + au_writel(0, I2S_CFG(au1xpsc_i2s_workdata));
> + au_sync();
> + au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_i2s_workdata));
> + au_sync();
> +
> + return 0;
> +}
> +
> +static int au1xpsc_i2s_resume(struct platform_device *pdev,
> + struct snd_soc_cpu_dai *cpu_dai)
> +{
> + au_writel(PSC_CTRL_DISABLE, PSC_CTRL(au1xpsc_i2s_workdata));
> + au_sync();
> + au_writel(0, PSC_SEL(au1xpsc_i2s_workdata));
> + au_sync();
> + au_writel(au1xpsc_i2s_workdata->pm[0],
> + PSC_SEL(au1xpsc_i2s_workdata));
> + au_sync();
> +
> + /* enable PSC */
> + au_writel(PSC_CTRL_ENABLE, PSC_CTRL(au1xpsc_i2s_workdata));
> + au_sync();
> +
> + /* same comment as in probe() callback also applies here */
> +
> + /* write back saved config */
> + au_writel(au1xpsc_i2s_workdata->pm[1],
> + I2S_CFG(au1xpsc_i2s_workdata));
> + au_sync();
> +
> + return 0;
> +}
> +
> +struct snd_soc_cpu_dai au1xpsc_i2s_dai = {
> + .name = "au1xpsc_i2s",
> + .type = SND_SOC_DAI_I2S,
> + .probe = au1xpsc_i2s_probe,
> + .remove = au1xpsc_i2s_remove,
> + .suspend = au1xpsc_i2s_suspend,
> + .resume = au1xpsc_i2s_resume,
> + .playback = {
> + .rates = AU1XPSC_I2S_RATES,
> + .formats = AU1XPSC_I2S_FMTS,
> + .channels_min = 2,
> + .channels_max = 8,},
> + .capture = {
> + .rates = AU1XPSC_I2S_RATES,
> + .formats = AU1XPSC_I2S_FMTS,
> + .channels_min = 2,
> + .channels_max = 8,},
> + .ops = {
> + .trigger = au1xpsc_i2s_trigger,
> + .hw_params = au1xpsc_i2s_hw_params,
> + },
> + .dai_ops = {
> + .set_fmt = au1xpsc_i2s_set_fmt,
> + },
> +};
> +EXPORT_SYMBOL(au1xpsc_i2s_dai);
> +
> +static int __init au1xpsc_i2s_init(void)
> +{
> + au1xpsc_i2s_workdata = NULL;
> + return 0;
> +}
> +
> +static void __exit au1xpsc_i2s_exit(void)
> +{
> +}
> +
> +module_init(au1xpsc_i2s_init);
> +module_exit(au1xpsc_i2s_exit);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Au12x0/Au1550 PSC I2S ALSA ASoC audio driver");
> +MODULE_AUTHOR("Manuel Lauss <mano at roarinelk.homelinux.net>");
> diff --git a/sound/soc/au1x/psc.h b/sound/soc/au1x/psc.h
> new file mode 100644
> index 0000000..98e29eb
> --- /dev/null
> +++ b/sound/soc/au1x/psc.h
> @@ -0,0 +1,48 @@
> +/*
> + * Au12x0/Au1550 PSC ALSA ASoC audio support.
> + *
> + * (c) 2007-2008 MSC Vertriebsges.m.b.H.,
> + * Manuel Lauss <mano at roarinelk.homelinux.net>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * NOTE: all of these drivers can only work with a SINGLE instance
> + * of a PSC. Multiple independent audio devices are impossible
> + * with ASoC v1.
> + */
> +
> +#ifndef _AU1X_PCM_H
> +#define _AU1X_PCM_H
> +
> +extern struct snd_soc_cpu_dai au1xpsc_ac97_dai;
> +extern struct snd_soc_cpu_dai au1xpsc_i2s_dai;
> +extern struct snd_soc_platform au1xpsc_soc_platform;
> +extern struct snd_ac97_bus_ops soc_ac97_ops;
> +
> +struct au1xpsc_audio_data {
> + void __iomem *mmio;
> + int irq;
> + struct resource *ioarea;
> +
> + unsigned long cfg;
> + unsigned long rate;
> +
> + unsigned long pm[2];
> +};
> +
> +/* easy access macros */
> +#define PSC_CTRL(x) ((unsigned long)((x)->mmio) + PSC_CTRL_OFFSET)
> +#define PSC_SEL(x) ((unsigned long)((x)->mmio) + PSC_SEL_OFFSET)
> +#define I2S_STAT(x) ((unsigned long)((x)->mmio) + PSC_I2SSTAT)
> +#define I2S_CFG(x) ((unsigned long)((x)->mmio) + PSC_I2SCFG)
> +#define I2S_PCR(x) ((unsigned long)((x)->mmio) + PSC_I2SPCR)
> +#define AC97_CFG(x) ((unsigned long)((x)->mmio) + PSC_AC97CFG)
> +#define AC97_CDC(x) ((unsigned long)((x)->mmio) + PSC_AC97CDC)
> +#define AC97_EVNT(x) ((unsigned long)((x)->mmio) + PSC_AC97EVNT)
> +#define AC97_PCR(x) ((unsigned long)((x)->mmio) + PSC_AC97PCR)
> +#define AC97_RST(x) ((unsigned long)((x)->mmio) + PSC_AC97RST)
> +#define AC97_STAT(x) ((unsigned long)((x)->mmio) + PSC_AC97STAT)
> +
> +#endif
> diff --git a/sound/soc/au1x/sample-ac97.c b/sound/soc/au1x/sample-ac97.c
> new file mode 100644
> index 0000000..fce81da
> --- /dev/null
> +++ b/sound/soc/au1x/sample-ac97.c
> @@ -0,0 +1,144 @@
> +/*
> + * Sample Au12x0/Au1550 PSC AC97 sound machine.
> + *
> + * Copyright (c) 2007-2008 Manuel Lauss <mano at roarinelk.homelinux.net>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms outlined in the file COPYING at the root of this
> + * source archive.
> + *
> + * This is a very generic AC97 sound machine driver for boards which
> + * have (AC97) audio at PSC1 (e.g. DB1200 demoboards).
> + */
> +
> +#include <linux/module.h>
> +#include <linux/moduleparam.h>
> +#include <linux/timer.h>
> +#include <linux/interrupt.h>
> +#include <linux/platform_device.h>
> +#include <sound/core.h>
> +#include <sound/pcm.h>
> +#include <sound/soc.h>
> +#include <sound/soc-dapm.h>
> +#include <asm/mach-au1x00/au1000.h>
> +#include <asm/mach-au1x00/au1xxx_psc.h>
> +#include <asm/mach-au1x00/au1xxx_dbdma.h>
> +
> +#include "../codecs/ac97.h"
> +#include "psc.h"
> +
> +static int au1xpsc_sample_ac97_init(struct snd_soc_codec *codec)
> +{
> + snd_soc_dapm_sync_endpoints(codec);
> + return 0;
> +}
> +
> +static struct snd_soc_dai_link au1xpsc_sample_ac97_dai = {
> + .name = "AC97",
> + .stream_name = "AC97 HiFi",
> + .cpu_dai = &au1xpsc_ac97_dai, /* see psc-ac97.c */
> + .codec_dai = &ac97_dai, /* see codecs/ac97.c */
> + .init = au1xpsc_sample_ac97_init,
> + .ops = NULL,
> +};
> +
> +static struct snd_soc_machine au1xpsc_sample_ac97_machine = {
> + .name = "Au1xxx PSC AC97 Audio",
> + .dai_link = &au1xpsc_sample_ac97_dai,
> + .num_links = 1,
> +};
> +
> +static struct snd_soc_device au1xpsc_sample_ac97_devdata = {
> + .machine = &au1xpsc_sample_ac97_machine,
> + .platform = &au1xpsc_soc_platform, /* see dbdma2.c */
> + .codec_dev = &soc_codec_dev_ac97,
> +};
> +
> +static struct resource au1xpsc_psc1_res[] = {
> + [0] = {
> + .start = CPHYSADDR(PSC1_BASE_ADDR),
> + .end = CPHYSADDR(PSC1_BASE_ADDR) + 0x000fffff,
> + .flags = IORESOURCE_MEM,
> + },
> + [1] = {
> +#ifdef CONFIG_SOC_AU1200
> + .start = AU1200_PSC1_INT,
> + .end = AU1200_PSC1_INT,
> +#elif defined(CONFIG_SOC_AU1550)
> + .start = AU1550_PSC1_INT,
> + .end = AU1550_PSC1_INT,
> +#endif
> + .flags = IORESOURCE_IRQ,
> + },
> + [2] = {
> + .start = DSCR_CMD0_PSC1_TX,
> + .end = DSCR_CMD0_PSC1_TX,
> + .flags = IORESOURCE_DMA,
> + },
> + [3] = {
> + .start = DSCR_CMD0_PSC1_RX,
> + .end = DSCR_CMD0_PSC1_RX,
> + .flags = IORESOURCE_DMA,
> + },
> +};
> +
> +static struct platform_device *au1xpsc_sample_ac97_dev = NULL;
> +
> +static int __init au1xpsc_sample_ac97_load(void)
> +{
> + int ret;
> +
> +#ifdef CONFIG_SOC_AU1200
> + unsigned long io;
> +
> + /* modify sys_pinfunc for AC97 on PSC1 */
> + io = au_readl(SYS_PINFUNC);
> + io |= SYS_PINFUNC_P1C;
> + io &= ~(SYS_PINFUNC_P1A | SYS_PINFUNC_P1B);
> + au_writel(io, SYS_PINFUNC);
> + au_sync();
> +#endif
> +
> + ret = -ENOMEM;
> +
> + /* setup PSC clock source for AC97 part: external clock provided
> + * by codec. The psc-ac97.c driver depends on this setting!
> + */
> + au_writel(PSC_SEL_CLK_SERCLK, PSC1_BASE_ADDR + PSC_SEL_OFFSET);
> + au_sync();
> +
> + au1xpsc_sample_ac97_dev = platform_device_alloc("soc-audio", -1);
> + if (!au1xpsc_sample_ac97_dev)
> + goto out;
> +
> + au1xpsc_sample_ac97_dev->resource =
> + kmemdup(au1xpsc_psc1_res, sizeof(struct resource) *
> + ARRAY_SIZE(au1xpsc_psc1_res), GFP_KERNEL);
> + au1xpsc_sample_ac97_dev->num_resources = ARRAY_SIZE(au1xpsc_psc1_res);
> + au1xpsc_sample_ac97_dev->id = 1;
> +
> + platform_set_drvdata(au1xpsc_sample_ac97_dev,
> + &au1xpsc_sample_ac97_devdata);
> + au1xpsc_sample_ac97_devdata.dev = &au1xpsc_sample_ac97_dev->dev;
> + ret = platform_device_add(au1xpsc_sample_ac97_dev);
> +
> + if (ret) {
> + platform_device_put(au1xpsc_sample_ac97_dev);
> + au1xpsc_sample_ac97_dev = NULL;
> + }
> +
> +out:
> + return ret;
> +}
> +
> +static void __exit au1xpsc_sample_ac97_exit(void)
> +{
> + platform_device_unregister(au1xpsc_sample_ac97_dev);
> +}
> +
> +module_init(au1xpsc_sample_ac97_load);
> +module_exit(au1xpsc_sample_ac97_exit);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Au1xxx PSC sample AC97 machine");
> +MODULE_AUTHOR("Manuel Lauss <mano at roarinelk.homelinux.net>");
> --
> 1.5.6.1
>
> _______________________________________________
> Alsa-devel mailing list
> Alsa-devel at alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
>
More information about the Alsa-devel
mailing list