All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH V4 0/3] ASoC for Alchemy Au1000/1500/1100
@ 2011-07-24 10:11 ` Manuel Lauss
  0 siblings, 0 replies; 16+ messages in thread
From: Manuel Lauss @ 2011-07-24 10:11 UTC (permalink / raw)
  To: alsa-devel, Mark Brown
  Cc: Liam Girdwood, Linux-MIPS, Ralf Baechle, Manuel Lauss

Hello,

Here's V4 of the AC97/I2S ASoC patchset for early Alchemy
chips and their respective evaluation boards. 
The patches are largely based on the old mips/au1x00.c driver which
they replace.

AC97 Tested on a Db1500 development board; I2S untested since none
of the testboards I have actually have an I2S codec (just testpoints).

Changes since V3:
- dropped the hunk which removed the I2S constants from the au1000.h header
  to avoid merge conflicts with other patches.
- use the context structure declared in psc.h.  Follow-up patches for psc* code
  depend on this.

Changes since V2:
- implemented changes after feedback from Lars-Peter Clausen:
* split patch 1 in two, one for the ASoC drivers, and a separate for
  DB1000 machine code.
* get rid of automatic dma device registration
* tidied the I2S/AC97 sources
- mark sound/mips/au1x00.c as DEPRECATED instead of removing it outright.

Changes since V1:
- added untested I2S controller driver for completeness, removed the audio
  defines from the au1000 header.


Manuel Lauss (3):
  ASoC: Alchemy AC97C/I2SC audio support
  ASoC: Add a DB1x00 AC97 machine driver
  ALSA: deprecate MIPS AU1X00 AC97 driver

 arch/mips/alchemy/devboards/db1x00/platform.c |   48 ++++
 sound/mips/Kconfig                            |    5 +-
 sound/soc/au1x/Kconfig                        |   28 ++
 sound/soc/au1x/Makefile                       |   10 +
 sound/soc/au1x/ac97c.c                        |  365 ++++++++++++++++++++++++
 sound/soc/au1x/db1000.c                       |   75 +++++
 sound/soc/au1x/dma.c                          |  374 +++++++++++++++++++++++++
 sound/soc/au1x/i2sc.c                         |  342 ++++++++++++++++++++++
 sound/soc/au1x/psc.h                          |   19 +-
 9 files changed, 1256 insertions(+), 10 deletions(-)
 create mode 100644 sound/soc/au1x/ac97c.c
 create mode 100644 sound/soc/au1x/db1000.c
 create mode 100644 sound/soc/au1x/dma.c
 create mode 100644 sound/soc/au1x/i2sc.c

-- 
1.7.6

^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH V4 0/3] ASoC for Alchemy Au1000/1500/1100
@ 2011-07-24 10:11 ` Manuel Lauss
  0 siblings, 0 replies; 16+ messages in thread
From: Manuel Lauss @ 2011-07-24 10:11 UTC (permalink / raw)
  To: alsa-devel; +Cc: Linux-MIPS, Manuel Lauss, Liam Girdwood, Ralf Baechle

Hello,

Here's V4 of the AC97/I2S ASoC patchset for early Alchemy
chips and their respective evaluation boards. 
The patches are largely based on the old mips/au1x00.c driver which
they replace.

AC97 Tested on a Db1500 development board; I2S untested since none
of the testboards I have actually have an I2S codec (just testpoints).

Changes since V3:
- dropped the hunk which removed the I2S constants from the au1000.h header
  to avoid merge conflicts with other patches.
- use the context structure declared in psc.h.  Follow-up patches for psc* code
  depend on this.

Changes since V2:
- implemented changes after feedback from Lars-Peter Clausen:
* split patch 1 in two, one for the ASoC drivers, and a separate for
  DB1000 machine code.
* get rid of automatic dma device registration
* tidied the I2S/AC97 sources
- mark sound/mips/au1x00.c as DEPRECATED instead of removing it outright.

Changes since V1:
- added untested I2S controller driver for completeness, removed the audio
  defines from the au1000 header.


Manuel Lauss (3):
  ASoC: Alchemy AC97C/I2SC audio support
  ASoC: Add a DB1x00 AC97 machine driver
  ALSA: deprecate MIPS AU1X00 AC97 driver

 arch/mips/alchemy/devboards/db1x00/platform.c |   48 ++++
 sound/mips/Kconfig                            |    5 +-
 sound/soc/au1x/Kconfig                        |   28 ++
 sound/soc/au1x/Makefile                       |   10 +
 sound/soc/au1x/ac97c.c                        |  365 ++++++++++++++++++++++++
 sound/soc/au1x/db1000.c                       |   75 +++++
 sound/soc/au1x/dma.c                          |  374 +++++++++++++++++++++++++
 sound/soc/au1x/i2sc.c                         |  342 ++++++++++++++++++++++
 sound/soc/au1x/psc.h                          |   19 +-
 9 files changed, 1256 insertions(+), 10 deletions(-)
 create mode 100644 sound/soc/au1x/ac97c.c
 create mode 100644 sound/soc/au1x/db1000.c
 create mode 100644 sound/soc/au1x/dma.c
 create mode 100644 sound/soc/au1x/i2sc.c

-- 
1.7.6


^ permalink raw reply	[flat|nested] 16+ messages in thread

* [PATCH V4 1/3] ASoC: Alchemy AC97C/I2SC audio support
  2011-07-24 10:11 ` Manuel Lauss
@ 2011-07-24 10:11   ` Manuel Lauss
  -1 siblings, 0 replies; 16+ messages in thread
From: Manuel Lauss @ 2011-07-24 10:11 UTC (permalink / raw)
  To: alsa-devel, Mark Brown
  Cc: Liam Girdwood, Linux-MIPS, Ralf Baechle, Manuel Lauss

This patch adds ASoC support for the AC97 and I2S controllers
on the old Au1000/Au1500/Au1100 chips,

AC97 Tested on a Db1500.  I2S untested since none of the boards
actually have an I2S codec wired up (just test pins).

Signed-off-by: Manuel Lauss <manuel.lauss@googlemail.com>
---
V4: dropped hunk which removed I2S constants in au1000.h header to avoid merge
    conflicts with other patches, use the context structure in psc.h since it
    fits really well.
V3: implemented feedback from Lars-Peter Clausen: src tidying, no more
    automatic dma device registration, split off db1000 board code.
V2: added untested I2S controller driver for completeness, removed the audio
    defines from the au1000 header as well.

 sound/soc/au1x/Kconfig  |   19 +++
 sound/soc/au1x/Makefile |    8 +
 sound/soc/au1x/ac97c.c  |  365 +++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/au1x/dma.c    |  374 +++++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/au1x/i2sc.c   |  342 +++++++++++++++++++++++++++++++++++++++++++
 sound/soc/au1x/psc.h    |   19 ++-
 6 files changed, 1118 insertions(+), 9 deletions(-)
 create mode 100644 sound/soc/au1x/ac97c.c
 create mode 100644 sound/soc/au1x/dma.c
 create mode 100644 sound/soc/au1x/i2sc.c

diff --git a/sound/soc/au1x/Kconfig b/sound/soc/au1x/Kconfig
index 4b67140..0460b42 100644
--- a/sound/soc/au1x/Kconfig
+++ b/sound/soc/au1x/Kconfig
@@ -18,6 +18,25 @@ config SND_SOC_AU1XPSC_AC97
 	select SND_AC97_CODEC
 	select SND_SOC_AC97_BUS
 
+##
+## Au1000/1500/1100 DMA + AC97C/I2SC
+##
+config SND_SOC_AU1XAUDIO
+	tristate "SoC Audio for Au1000/Au1500/Au1100"
+	depends on MIPS_ALCHEMY
+	help
+	  This is a driver set for the AC97 unit and the
+	  old DMA controller as found on the Au1000/Au1500/Au1100 chips.
+
+config SND_SOC_AU1XAC97C
+	tristate
+	select AC97_BUS
+	select SND_AC97_CODEC
+	select SND_SOC_AC97_BUS
+
+config SND_SOC_AU1XI2SC
+	tristate
+
 
 ##
 ## Boards
diff --git a/sound/soc/au1x/Makefile b/sound/soc/au1x/Makefile
index 1687307..ff5531e 100644
--- a/sound/soc/au1x/Makefile
+++ b/sound/soc/au1x/Makefile
@@ -3,9 +3,17 @@ snd-soc-au1xpsc-dbdma-objs := dbdma2.o
 snd-soc-au1xpsc-i2s-objs := psc-i2s.o
 snd-soc-au1xpsc-ac97-objs := psc-ac97.o
 
+# Au1000/1500/1100 Audio units
+snd-soc-au1x-dma-objs := dma.o
+snd-soc-au1x-ac97c-objs := ac97c.o
+snd-soc-au1x-i2sc-objs := i2sc.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
+obj-$(CONFIG_SND_SOC_AU1XAUDIO) += snd-soc-au1x-dma.o
+obj-$(CONFIG_SND_SOC_AU1XAC97C) += snd-soc-au1x-ac97c.o
+obj-$(CONFIG_SND_SOC_AU1XI2SC) += snd-soc-au1x-i2sc.o
 
 # Boards
 snd-soc-db1200-objs := db1200.o
diff --git a/sound/soc/au1x/ac97c.c b/sound/soc/au1x/ac97c.c
new file mode 100644
index 0000000..35884ae
--- /dev/null
+++ b/sound/soc/au1x/ac97c.c
@@ -0,0 +1,365 @@
+/*
+ * Au1000/Au1500/Au1100 AC97C controller driver for ASoC
+ *
+ * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
+ *
+ * based on the old ALSA driver originally written by
+ *			Charles Eidsness <charles@cooper-street.com>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.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 "psc.h"
+
+/* register offsets and bits */
+#define AC97_CONFIG	0x00
+#define AC97_STATUS	0x04
+#define AC97_DATA	0x08
+#define AC97_CMDRESP	0x0c
+#define AC97_ENABLE	0x10
+
+#define CFG_RC(x)	(((x) & 0x3ff) << 13)	/* valid rx slots mask */
+#define CFG_XS(x)	(((x) & 0x3ff) << 3)	/* valid tx slots mask */
+#define CFG_SG		(1 << 2)	/* sync gate */
+#define CFG_SN		(1 << 1)	/* sync control */
+#define CFG_RS		(1 << 0)	/* acrst# control */
+#define STAT_XU		(1 << 11)	/* tx underflow */
+#define STAT_XO		(1 << 10)	/* tx overflow */
+#define STAT_RU		(1 << 9)	/* rx underflow */
+#define STAT_RO		(1 << 8)	/* rx overflow */
+#define STAT_RD		(1 << 7)	/* codec ready */
+#define STAT_CP		(1 << 6)	/* command pending */
+#define STAT_TE		(1 << 4)	/* tx fifo empty */
+#define STAT_TF		(1 << 3)	/* tx fifo full */
+#define STAT_RE		(1 << 1)	/* rx fifo empty */
+#define STAT_RF		(1 << 0)	/* rx fifo full */
+#define CMD_SET_DATA(x)	(((x) & 0xffff) << 16)
+#define CMD_GET_DATA(x)	((x) & 0xffff)
+#define CMD_READ	(1 << 7)
+#define CMD_WRITE	(0 << 7)
+#define CMD_IDX(x)	((x) & 0x7f)
+#define EN_D		(1 << 1)	/* DISable bit */
+#define EN_CE		(1 << 0)	/* clock enable bit */
+
+/* how often to retry failed codec register reads/writes */
+#define AC97_RW_RETRIES	5
+
+#define AC97_RATES	\
+	SNDRV_PCM_RATE_8000_44100
+
+#define AC97_FMTS	\
+	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE)
+
+/* instance data. There can be only one, MacLeod!!!!, fortunately there IS only
+ * once AC97C on early Alchemy chips. The newer ones aren't so lucky.
+ */
+static struct au1xpsc_audio_data *ac97c_workdata;
+#define ac97_to_ctx(x)		ac97c_workdata
+
+static inline unsigned long RD(struct au1xpsc_audio_data *ctx, int reg)
+{
+	return __raw_readl(ctx->mmio + reg);
+}
+
+static inline void WR(struct au1xpsc_audio_data *ctx, int reg, unsigned long v)
+{
+	__raw_writel(v, ctx->mmio + reg);
+	wmb();
+}
+
+static unsigned short au1xac97c_ac97_read(struct snd_ac97 *ac97,
+					  unsigned short r)
+{
+	struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
+	unsigned int tmo, retry;
+	unsigned long data;
+
+	data = ~0;
+	retry = AC97_RW_RETRIES;
+	do {
+		mutex_lock(&ctx->lock);
+
+		tmo = 5;
+		while ((RD(ctx, AC97_STATUS) & STAT_CP) && tmo--)
+			udelay(21);	/* wait an ac97 frame time */
+		if (!tmo) {
+			pr_debug("ac97rd timeout #1\n");
+			goto next;
+		}
+
+		WR(ctx, AC97_CMDRESP, CMD_IDX(r) | CMD_READ);
+
+		/* stupid errata: data is only valid for 21us, so
+		 * poll, Forrest, poll...
+		 */
+		tmo = 0x10000;
+		while ((RD(ctx, AC97_STATUS) & STAT_CP) && tmo--)
+			asm volatile ("nop");
+		data = RD(ctx, AC97_CMDRESP);
+
+		if (!tmo)
+			pr_debug("ac97rd timeout #2\n");
+
+next:
+		mutex_unlock(&ctx->lock);
+	} while (--retry && !tmo);
+
+	pr_debug("AC97RD %04x %04lx %d\n", r, data, retry);
+
+	return retry ? data & 0xffff : 0xffff;
+}
+
+static void au1xac97c_ac97_write(struct snd_ac97 *ac97, unsigned short r,
+				 unsigned short v)
+{
+	struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
+	unsigned int tmo, retry;
+
+	retry = AC97_RW_RETRIES;
+	do {
+		mutex_lock(&ctx->lock);
+
+		for (tmo = 5; (RD(ctx, AC97_STATUS) & STAT_CP) && tmo; tmo--)
+			udelay(21);
+		if (!tmo) {
+			pr_debug("ac97wr timeout #1\n");
+			goto next;
+		}
+
+		WR(ctx, AC97_CMDRESP, CMD_WRITE | CMD_IDX(r) | CMD_SET_DATA(v));
+
+		for (tmo = 10; (RD(ctx, AC97_STATUS) & STAT_CP) && tmo; tmo--)
+			udelay(21);
+		if (!tmo)
+			pr_debug("ac97wr timeout #2\n");
+next:
+		mutex_unlock(&ctx->lock);
+	} while (--retry && !tmo);
+
+	pr_debug("AC97WR %04x %04x %d\n", r, v, retry);
+}
+
+static void au1xac97c_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+	struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
+
+	WR(ctx, AC97_CONFIG, ctx->cfg | CFG_SG | CFG_SN);
+	msleep(20);
+	WR(ctx, AC97_CONFIG, ctx->cfg | CFG_SG);
+	WR(ctx, AC97_CONFIG, ctx->cfg);
+}
+
+static void au1xac97c_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+	struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
+	int i;
+
+	WR(ctx, AC97_CONFIG, ctx->cfg | CFG_RS);
+	msleep(500);
+	WR(ctx, AC97_CONFIG, ctx->cfg);
+
+	/* wait for codec ready */
+	i = 50;
+	while (((RD(ctx, AC97_STATUS) & STAT_RD) == 0) && --i)
+		msleep(20);
+	if (!i)
+		printk(KERN_ERR "ac97c: codec not ready after cold reset\n");
+}
+
+/* AC97 controller operations */
+struct snd_ac97_bus_ops soc_ac97_ops = {
+	.read		= au1xac97c_ac97_read,
+	.write		= au1xac97c_ac97_write,
+	.reset		= au1xac97c_ac97_cold_reset,
+	.warm_reset	= au1xac97c_ac97_warm_reset,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);	/* globals be gone! */
+
+static int alchemy_ac97c_startup(struct snd_pcm_substream *substream,
+				 struct snd_soc_dai *dai)
+{
+	struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
+	snd_soc_dai_set_dma_data(dai, substream, &ctx->dmaids[0]);
+	return 0;
+}
+
+static struct snd_soc_dai_ops alchemy_ac97c_ops = {
+	.startup		= alchemy_ac97c_startup,
+};
+
+static int au1xac97c_dai_probe(struct snd_soc_dai *dai)
+{
+	return ac97c_workdata ? 0 : -ENODEV;
+}
+
+static struct snd_soc_dai_driver au1xac97c_dai_driver = {
+	.name			= "alchemy-ac97c",
+	.ac97_control		= 1,
+	.probe			= au1xac97c_dai_probe,
+	.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			= &alchemy_ac97c_ops,
+};
+
+static int __devinit au1xac97c_drvprobe(struct platform_device *pdev)
+{
+	int ret;
+	struct resource *r;
+	struct au1xpsc_audio_data *ctx;
+
+	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	mutex_init(&ctx->lock);
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!r) {
+		ret = -ENODEV;
+		goto out0;
+	}
+
+	ret = -EBUSY;
+	if (!request_mem_region(r->start, resource_size(r), pdev->name))
+		goto out0;
+
+	ctx->mmio = ioremap_nocache(r->start, resource_size(r));
+	if (!ctx->mmio)
+		goto out1;
+
+	r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+	if (!r)
+		goto out1;
+	ctx->dmaids[PCM_TX] = r->start;
+
+	r = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+	if (!r)
+		goto out1;
+	ctx->dmaids[PCM_RX] = r->start;
+
+	/* switch it on */
+	WR(ctx, AC97_ENABLE, EN_D | EN_CE);
+	WR(ctx, AC97_ENABLE, EN_CE);
+
+	ctx->cfg = CFG_RC(3) | CFG_XS(3);
+	WR(ctx, AC97_CONFIG, ctx->cfg);
+
+	platform_set_drvdata(pdev, ctx);
+
+	ret = snd_soc_register_dai(&pdev->dev, &au1xac97c_dai_driver);
+	if (ret)
+		goto out1;
+
+	ac97c_workdata = ctx;
+	return 0;
+
+
+	snd_soc_unregister_dai(&pdev->dev);
+out1:
+	release_mem_region(r->start, resource_size(r));
+out0:
+	kfree(ctx);
+	return ret;
+}
+
+static int __devexit au1xac97c_drvremove(struct platform_device *pdev)
+{
+	struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev);
+	struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	snd_soc_unregister_dai(&pdev->dev);
+
+	WR(ctx, AC97_ENABLE, EN_D);	/* clock off, disable */
+
+	iounmap(ctx->mmio);
+	release_mem_region(r->start, resource_size(r));
+	kfree(ctx);
+
+	ac97c_workdata = NULL;	/* MDEV */
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int au1xac97c_drvsuspend(struct device *dev)
+{
+	struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev);
+
+	WR(ctx, AC97_ENABLE, EN_D);	/* clock off, disable */
+
+	return 0;
+}
+
+static int au1xac97c_drvresume(struct device *dev)
+{
+	struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev);
+
+	WR(ctx, AC97_ENABLE, EN_D | EN_CE);
+	WR(ctx, AC97_ENABLE, EN_CE);
+	WR(ctx, AC97_CONFIG, ctx->cfg);
+
+	return 0;
+}
+
+static const struct dev_pm_ops au1xpscac97_pmops = {
+	.suspend	= au1xac97c_drvsuspend,
+	.resume		= au1xac97c_drvresume,
+};
+
+#define AU1XPSCAC97_PMOPS (&au1xpscac97_pmops)
+
+#else
+
+#define AU1XPSCAC97_PMOPS NULL
+
+#endif
+
+static struct platform_driver au1xac97c_driver = {
+	.driver	= {
+		.name	= "alchemy-ac97c",
+		.owner	= THIS_MODULE,
+		.pm	= AU1XPSCAC97_PMOPS,
+	},
+	.probe		= au1xac97c_drvprobe,
+	.remove		= __devexit_p(au1xac97c_drvremove),
+};
+
+static int __init au1xac97c_load(void)
+{
+	ac97c_workdata = NULL;
+	return platform_driver_register(&au1xac97c_driver);
+}
+
+static void __exit au1xac97c_unload(void)
+{
+	platform_driver_unregister(&au1xac97c_driver);
+}
+
+module_init(au1xac97c_load);
+module_exit(au1xac97c_unload);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Au1000/1500/1100 AC97C ASoC driver");
+MODULE_AUTHOR("Manuel Lauss");
diff --git a/sound/soc/au1x/dma.c b/sound/soc/au1x/dma.c
new file mode 100644
index 0000000..20fedbd
--- /dev/null
+++ b/sound/soc/au1x/dma.c
@@ -0,0 +1,374 @@
+/*
+ * Au1000/Au1500/Au1100 Audio DMA support.
+ *
+ * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
+ *
+ * copied almost verbatim from the old ALSA driver, written by
+ *			Charles Eidsness <charles@cooper-street.com>
+ */
+
+#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/au1000_dma.h>
+
+#include "psc.h"
+
+#define ALCHEMY_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)
+
+struct pcm_period {
+	u32 start;
+	u32 relative_end;	/* relative to start of buffer */
+	struct pcm_period *next;
+};
+
+struct audio_stream {
+	struct snd_pcm_substream *substream;
+	int dma;
+	struct pcm_period *buffer;
+	unsigned int period_size;
+	unsigned int periods;
+};
+
+struct alchemy_pcm_ctx {
+	struct audio_stream stream[2];	/* playback & capture */
+};
+
+static void au1000_release_dma_link(struct audio_stream *stream)
+{
+	struct pcm_period *pointer;
+	struct pcm_period *pointer_next;
+
+	stream->period_size = 0;
+	stream->periods = 0;
+	pointer = stream->buffer;
+	if (!pointer)
+		return;
+	do {
+		pointer_next = pointer->next;
+		kfree(pointer);
+		pointer = pointer_next;
+	} while (pointer != stream->buffer);
+	stream->buffer = NULL;
+}
+
+static int au1000_setup_dma_link(struct audio_stream *stream,
+				 unsigned int period_bytes,
+				 unsigned int periods)
+{
+	struct snd_pcm_substream *substream = stream->substream;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct pcm_period *pointer;
+	unsigned long dma_start;
+	int i;
+
+	dma_start = virt_to_phys(runtime->dma_area);
+
+	if (stream->period_size == period_bytes &&
+	    stream->periods == periods)
+		return 0; /* not changed */
+
+	au1000_release_dma_link(stream);
+
+	stream->period_size = period_bytes;
+	stream->periods = periods;
+
+	stream->buffer = kmalloc(sizeof(struct pcm_period), GFP_KERNEL);
+	if (!stream->buffer)
+		return -ENOMEM;
+	pointer = stream->buffer;
+	for (i = 0; i < periods; i++) {
+		pointer->start = (u32)(dma_start + (i * period_bytes));
+		pointer->relative_end = (u32) (((i+1) * period_bytes) - 0x1);
+		if (i < periods - 1) {
+			pointer->next = kmalloc(sizeof(struct pcm_period),
+						GFP_KERNEL);
+			if (!pointer->next) {
+				au1000_release_dma_link(stream);
+				return -ENOMEM;
+			}
+			pointer = pointer->next;
+		}
+	}
+	pointer->next = stream->buffer;
+	return 0;
+}
+
+static void au1000_dma_stop(struct audio_stream *stream)
+{
+	if (stream->buffer)
+		disable_dma(stream->dma);
+}
+
+static void au1000_dma_start(struct audio_stream *stream)
+{
+	if (!stream->buffer)
+		return;
+
+	init_dma(stream->dma);
+	if (get_dma_active_buffer(stream->dma) == 0) {
+		clear_dma_done0(stream->dma);
+		set_dma_addr0(stream->dma, stream->buffer->start);
+		set_dma_count0(stream->dma, stream->period_size >> 1);
+		set_dma_addr1(stream->dma, stream->buffer->next->start);
+		set_dma_count1(stream->dma, stream->period_size >> 1);
+	} else {
+		clear_dma_done1(stream->dma);
+		set_dma_addr1(stream->dma, stream->buffer->start);
+		set_dma_count1(stream->dma, stream->period_size >> 1);
+		set_dma_addr0(stream->dma, stream->buffer->next->start);
+		set_dma_count0(stream->dma, stream->period_size >> 1);
+	}
+	enable_dma_buffers(stream->dma);
+	start_dma(stream->dma);
+}
+
+static irqreturn_t au1000_dma_interrupt(int irq, void *ptr)
+{
+	struct audio_stream *stream = (struct audio_stream *)ptr;
+	struct snd_pcm_substream *substream = stream->substream;
+
+	switch (get_dma_buffer_done(stream->dma)) {
+	case DMA_D0:
+		stream->buffer = stream->buffer->next;
+		clear_dma_done0(stream->dma);
+		set_dma_addr0(stream->dma, stream->buffer->next->start);
+		set_dma_count0(stream->dma, stream->period_size >> 1);
+		enable_dma_buffer0(stream->dma);
+		break;
+	case DMA_D1:
+		stream->buffer = stream->buffer->next;
+		clear_dma_done1(stream->dma);
+		set_dma_addr1(stream->dma, stream->buffer->next->start);
+		set_dma_count1(stream->dma, stream->period_size >> 1);
+		enable_dma_buffer1(stream->dma);
+		break;
+	case (DMA_D0 | DMA_D1):
+		pr_debug("DMA %d missed interrupt.\n", stream->dma);
+		au1000_dma_stop(stream);
+		au1000_dma_start(stream);
+		break;
+	case (~DMA_D0 & ~DMA_D1):
+		pr_debug("DMA %d empty irq.\n", stream->dma);
+	}
+	snd_pcm_period_elapsed(substream);
+	return IRQ_HANDLED;
+}
+
+static const struct snd_pcm_hardware alchemy_pcm_hardware = {
+	.info		  = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+			    SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BATCH,
+	.formats	  = ALCHEMY_PCM_FMTS,
+	.rates		  = SNDRV_PCM_RATE_8000_192000,
+	.rate_min	  = SNDRV_PCM_RATE_8000,
+	.rate_max	  = SNDRV_PCM_RATE_192000,
+	.channels_min	  = 2,
+	.channels_max	  = 2,
+	.period_bytes_min = 1024,
+	.period_bytes_max = 16 * 1024 - 1,
+	.periods_min	  = 4,
+	.periods_max	  = 255,
+	.buffer_bytes_max = 128 * 1024,
+	.fifo_size	  = 16,
+};
+
+static inline struct alchemy_pcm_ctx *ss_to_ctx(struct snd_pcm_substream *ss)
+{
+	struct snd_soc_pcm_runtime *rtd = ss->private_data;
+	return snd_soc_platform_get_drvdata(rtd->platform);
+}
+
+static inline struct audio_stream *ss_to_as(struct snd_pcm_substream *ss)
+{
+	struct alchemy_pcm_ctx *ctx = ss_to_ctx(ss);
+	return &(ctx->stream[SUBSTREAM_TYPE(ss)]);
+}
+
+static int alchemy_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct alchemy_pcm_ctx *ctx = ss_to_ctx(substream);
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	int stype = SUBSTREAM_TYPE(substream);
+	int *dmaids;
+	char *name;
+
+	dmaids = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+	if (!dmaids)
+		return -ENODEV;	/* whoa, has ordering changed? */
+
+	/* DMA setup */
+	name = (stype == PCM_TX) ? "audio-tx" : "audio-rx";
+	ctx->stream[stype].dma = request_au1000_dma(dmaids[stype], name,
+					au1000_dma_interrupt, IRQF_DISABLED,
+					&ctx->stream[stype]);
+	set_dma_mode(ctx->stream[stype].dma,
+		     get_dma_mode(ctx->stream[stype].dma) & ~DMA_NC);
+
+	ctx->stream[stype].substream = substream;
+	ctx->stream[stype].buffer = NULL;
+	snd_soc_set_runtime_hwparams(substream, &alchemy_pcm_hardware);
+
+	return 0;
+}
+
+static int alchemy_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct alchemy_pcm_ctx *ctx = ss_to_ctx(substream);
+	int stype = SUBSTREAM_TYPE(substream);
+
+	ctx->stream[SUBSTREAM_TYPE(substream)].substream = NULL;
+	free_au1000_dma(ctx->stream[stype].dma);
+
+	return 0;
+}
+
+static int alchemy_pcm_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	struct audio_stream *stream = ss_to_as(substream);
+	int err;
+
+	err = snd_pcm_lib_malloc_pages(substream,
+				       params_buffer_bytes(hw_params));
+	if (err < 0)
+		return err;
+	return au1000_setup_dma_link(stream,
+				     params_period_bytes(hw_params),
+				     params_periods(hw_params));
+}
+
+static int alchemy_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct audio_stream *stream = ss_to_as(substream);
+	au1000_release_dma_link(stream);
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int alchemy_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct audio_stream *stream = ss_to_as(substream);
+	int err = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		au1000_dma_start(stream);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		au1000_dma_stop(stream);
+		break;
+	default:
+		err = -EINVAL;
+		break;
+	}
+	return err;
+}
+
+static snd_pcm_uframes_t alchemy_pcm_pointer(struct snd_pcm_substream *ss)
+{
+	struct audio_stream *stream = ss_to_as(ss);
+	long location;
+
+	location = get_dma_residue(stream->dma);
+	location = stream->buffer->relative_end - location;
+	if (location == -1)
+		location = 0;
+	return bytes_to_frames(ss->runtime, location);
+}
+
+static struct snd_pcm_ops alchemy_pcm_ops = {
+	.open			= alchemy_pcm_open,
+	.close			= alchemy_pcm_close,
+	.ioctl			= snd_pcm_lib_ioctl,
+	.hw_params	        = alchemy_pcm_hw_params,
+	.hw_free	        = alchemy_pcm_hw_free,
+	.trigger		= alchemy_pcm_trigger,
+	.pointer		= alchemy_pcm_pointer,
+};
+
+static void alchemy_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static int alchemy_pcm_new(struct snd_card *card,
+			   struct snd_soc_dai *dai,
+			   struct snd_pcm *pcm)
+{
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+		snd_dma_continuous_data(GFP_KERNEL), 65536, (4096 * 1024) - 1);
+
+	return 0;
+}
+
+struct snd_soc_platform_driver alchemy_pcm_soc_platform = {
+	.ops		= &alchemy_pcm_ops,
+	.pcm_new	= alchemy_pcm_new,
+	.pcm_free	= alchemy_pcm_free_dma_buffers,
+};
+
+static int __devinit alchemy_pcm_drvprobe(struct platform_device *pdev)
+{
+	struct alchemy_pcm_ctx *ctx;
+	int ret;
+
+	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, ctx);
+
+	ret = snd_soc_register_platform(&pdev->dev, &alchemy_pcm_soc_platform);
+	if (ret)
+		kfree(ctx);
+
+	return ret;
+}
+
+static int __devexit alchemy_pcm_drvremove(struct platform_device *pdev)
+{
+	struct alchemy_pcm_ctx *ctx = platform_get_drvdata(pdev);
+
+	snd_soc_unregister_platform(&pdev->dev);
+	kfree(ctx);
+
+	return 0;
+}
+
+static struct platform_driver alchemy_pcmdma_driver = {
+	.driver	= {
+		.name	= "alchemy-pcm-dma",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= alchemy_pcm_drvprobe,
+	.remove		= __devexit_p(alchemy_pcm_drvremove),
+};
+
+static int __init alchemy_pcmdma_load(void)
+{
+	return platform_driver_register(&alchemy_pcmdma_driver);
+}
+
+static void __exit alchemy_pcmdma_unload(void)
+{
+	platform_driver_unregister(&alchemy_pcmdma_driver);
+}
+
+module_init(alchemy_pcmdma_load);
+module_exit(alchemy_pcmdma_unload);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Au1000/Au1500/Au1100 Audio DMA driver");
+MODULE_AUTHOR("Manuel Lauss");
diff --git a/sound/soc/au1x/i2sc.c b/sound/soc/au1x/i2sc.c
new file mode 100644
index 0000000..e3964a2
--- /dev/null
+++ b/sound/soc/au1x/i2sc.c
@@ -0,0 +1,342 @@
+/*
+ * Au1000/Au1500/Au1100 I2S controller driver for ASoC
+ *
+ * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
+ *
+ * Note: clock supplied to the I2S controller must be 256x samplerate.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.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 "psc.h"
+
+#define I2S_RXTX	0x00
+#define I2S_CFG		0x04
+#define I2S_ENABLE	0x08
+
+#define CFG_XU		(1 << 25)	/* tx underflow */
+#define CFG_XO		(1 << 24)
+#define CFG_RU		(1 << 23)
+#define CFG_RO		(1 << 22)
+#define CFG_TR		(1 << 21)
+#define CFG_TE		(1 << 20)
+#define CFG_TF		(1 << 19)
+#define CFG_RR		(1 << 18)
+#define CFG_RF		(1 << 17)
+#define CFG_ICK		(1 << 12)	/* clock invert */
+#define CFG_PD		(1 << 11)	/* set to make I2SDIO INPUT */
+#define CFG_LB		(1 << 10)	/* loopback */
+#define CFG_IC		(1 << 9)	/* word select invert */
+#define CFG_FM_I2S	(0 << 7)	/* I2S format */
+#define CFG_FM_LJ	(1 << 7)	/* left-justified */
+#define CFG_FM_RJ	(2 << 7)	/* right-justified */
+#define CFG_FM_MASK	(3 << 7)
+#define CFG_TN		(1 << 6)	/* tx fifo en */
+#define CFG_RN		(1 << 5)	/* rx fifo en */
+#define CFG_SZ_8	(0x08)
+#define CFG_SZ_16	(0x10)
+#define CFG_SZ_18	(0x12)
+#define CFG_SZ_20	(0x14)
+#define CFG_SZ_24	(0x18)
+#define CFG_SZ_MASK	(0x1f)
+#define EN_D		(1 << 1)	/* DISable */
+#define EN_CE		(1 << 0)	/* clock enable */
+
+/* only limited by clock generator and board design */
+#define AU1XI2SC_RATES \
+	SNDRV_PCM_RATE_CONTINUOUS
+
+#define AU1XI2SC_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_S18_3LE | SNDRV_PCM_FMTBIT_U18_3LE |	\
+	SNDRV_PCM_FMTBIT_S18_3BE | SNDRV_PCM_FMTBIT_U18_3BE |	\
+	SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE |	\
+	SNDRV_PCM_FMTBIT_S20_3BE | SNDRV_PCM_FMTBIT_U20_3BE |	\
+	SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE |	\
+	SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE |	\
+	0)
+
+static inline unsigned long RD(struct au1xpsc_audio_data *ctx, int reg)
+{
+	return __raw_readl(ctx->mmio + reg);
+}
+
+static inline void WR(struct au1xpsc_audio_data *ctx, int reg, unsigned long v)
+{
+	__raw_writel(v, ctx->mmio + reg);
+	wmb();
+}
+
+static int au1xi2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+	struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(cpu_dai);
+	unsigned long c;
+	int ret;
+
+	ret = -EINVAL;
+	c = ctx->cfg;
+
+	c &= ~CFG_FM_MASK;
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		c |= CFG_FM_I2S;
+		break;
+	case SND_SOC_DAIFMT_MSB:
+		c |= CFG_FM_RJ;
+		break;
+	case SND_SOC_DAIFMT_LSB:
+		c |= CFG_FM_LJ;
+		break;
+	default:
+		goto out;
+	}
+
+	c &= ~(CFG_IC | CFG_ICK);		/* IB-IF */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		c |= CFG_IC | CFG_ICK;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		c |= CFG_IC;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		c |= CFG_ICK;
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		break;
+	default:
+		goto out;
+	}
+
+	/* I2S controller only supports master */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:	/* CODEC slave */
+		break;
+	default:
+		goto out;
+	}
+
+	ret = 0;
+	ctx->cfg = c;
+out:
+	return ret;
+}
+
+static int au1xi2s_trigger(struct snd_pcm_substream *substream,
+			   int cmd, struct snd_soc_dai *dai)
+{
+	struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
+	int stype = SUBSTREAM_TYPE(substream);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		/* power up */
+		WR(ctx, I2S_ENABLE, EN_D | EN_CE);
+		WR(ctx, I2S_ENABLE, EN_CE);
+		ctx->cfg |= (stype == PCM_TX) ? CFG_TN : CFG_RN;
+		WR(ctx, I2S_CFG, ctx->cfg);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		ctx->cfg &= ~((stype == PCM_TX) ? CFG_TN : CFG_RN);
+		WR(ctx, I2S_CFG, ctx->cfg);
+		WR(ctx, I2S_ENABLE, EN_D);		/* power off */
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static unsigned long msbits_to_reg(int msbits)
+{
+	switch (msbits) {
+	case 8:  return CFG_SZ_8;
+	case 16: return CFG_SZ_16;
+	case 18: return CFG_SZ_18;
+	case 20: return CFG_SZ_20;
+	case 24: return CFG_SZ_24;
+	}
+	return 0;
+}
+
+static int au1xi2s_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *params,
+			     struct snd_soc_dai *dai)
+{
+	struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
+	unsigned long v;
+
+	v = msbits_to_reg(params->msbits);
+	if (!v)
+		return -EINVAL;
+
+	ctx->cfg &= ~CFG_SZ_MASK;
+	ctx->cfg |= v;
+	return 0;
+}
+
+static int au1xi2s_startup(struct snd_pcm_substream *substream,
+			   struct snd_soc_dai *dai)
+{
+	struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
+	snd_soc_dai_set_dma_data(dai, substream, &ctx->dmaids[0]);
+	return 0;
+}
+
+static const struct snd_soc_dai_ops au1xi2s_dai_ops = {
+	.startup	= au1xi2s_startup,
+	.trigger	= au1xi2s_trigger,
+	.hw_params	= au1xi2s_hw_params,
+	.set_fmt	= au1xi2s_set_fmt,
+};
+
+static struct snd_soc_dai_driver au1xi2s_dai_driver = {
+	.symmetric_rates	= 1,
+	.playback = {
+		.rates		= AU1XI2SC_RATES,
+		.formats	= AU1XI2SC_FMTS,
+		.channels_min	= 2,
+		.channels_max	= 2,
+	},
+	.capture = {
+		.rates		= AU1XI2SC_RATES,
+		.formats	= AU1XI2SC_FMTS,
+		.channels_min	= 2,
+		.channels_max	= 2,
+	},
+	.ops = &au1xi2s_dai_ops,
+};
+
+static int __devinit au1xi2s_drvprobe(struct platform_device *pdev)
+{
+	int ret;
+	struct resource *r;
+	struct au1xpsc_audio_data *ctx;
+
+	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!r) {
+		ret = -ENODEV;
+		goto out0;
+	}
+
+	ret = -EBUSY;
+	if (!request_mem_region(r->start, resource_size(r), pdev->name))
+		goto out0;
+
+	ctx->mmio = ioremap_nocache(r->start, resource_size(r));
+	if (!ctx->mmio)
+		goto out1;
+
+	r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+	if (!r)
+		goto out1;
+	ctx->dmaids[PCM_TX] = r->start;
+
+	r = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+	if (!r)
+		goto out1;
+	ctx->dmaids[PCM_RX] = r->start;
+
+	platform_set_drvdata(pdev, ctx);
+
+	ret = snd_soc_register_dai(&pdev->dev, &au1xi2s_dai_driver);
+	if (ret)
+		goto out1;
+
+	return 0;
+
+	snd_soc_unregister_dai(&pdev->dev);
+out1:
+	release_mem_region(r->start, resource_size(r));
+out0:
+	kfree(ctx);
+	return ret;
+}
+
+static int __devexit au1xi2s_drvremove(struct platform_device *pdev)
+{
+	struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev);
+	struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	snd_soc_unregister_dai(&pdev->dev);
+
+	WR(ctx, I2S_ENABLE, EN_D);	/* clock off, disable */
+
+	iounmap(ctx->mmio);
+	release_mem_region(r->start, resource_size(r));
+	kfree(ctx);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int au1xi2s_drvsuspend(struct device *dev)
+{
+	struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev);
+
+	WR(ctx, I2S_ENABLE, EN_D);	/* clock off, disable */
+
+	return 0;
+}
+
+static int au1xi2s_drvresume(struct device *dev)
+{
+	return 0;
+}
+
+static const struct dev_pm_ops au1xi2sc_pmops = {
+	.suspend	= au1xi2s_drvsuspend,
+	.resume		= au1xi2s_drvresume,
+};
+
+#define AU1XI2SC_PMOPS (&au1xi2sc_pmops)
+
+#else
+
+#define AU1XI2SC_PMOPS NULL
+
+#endif
+
+static struct platform_driver au1xi2s_driver = {
+	.driver	= {
+		.name	= "alchemy-i2sc",
+		.owner	= THIS_MODULE,
+		.pm	= AU1XI2SC_PMOPS,
+	},
+	.probe		= au1xi2s_drvprobe,
+	.remove		= __devexit_p(au1xi2s_drvremove),
+};
+
+static int __init au1xi2s_load(void)
+{
+	return platform_driver_register(&au1xi2s_driver);
+}
+
+static void __exit au1xi2s_unload(void)
+{
+	platform_driver_unregister(&au1xi2s_driver);
+}
+
+module_init(au1xi2s_load);
+module_exit(au1xi2s_unload);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Au1000/1500/1100 I2S ASoC driver");
+MODULE_AUTHOR("Manuel Lauss");
diff --git a/sound/soc/au1x/psc.h b/sound/soc/au1x/psc.h
index b30eadd..c59b9e5 100644
--- a/sound/soc/au1x/psc.h
+++ b/sound/soc/au1x/psc.h
@@ -1,7 +1,7 @@
 /*
- * Au12x0/Au1550 PSC ALSA ASoC audio support.
+ * Alchemy ALSA ASoC audio support.
  *
- * (c) 2007-2008 MSC Vertriebsges.m.b.H.,
+ * (c) 2007-2011 MSC Vertriebsges.m.b.H.,
  *	Manuel Lauss <manuel.lauss@gmail.com>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -13,7 +13,13 @@
 #ifndef _AU1X_PCM_H
 #define _AU1X_PCM_H
 
-/* DBDMA helpers */
+#define PCM_TX	0
+#define PCM_RX	1
+
+#define SUBSTREAM_TYPE(substream) \
+	((substream)->stream == SNDRV_PCM_STREAM_PLAYBACK ? PCM_TX : PCM_RX)
+
+/* PSC/DBDMA helpers */
 extern struct platform_device *au1xpsc_pcm_add(struct platform_device *pdev);
 extern void au1xpsc_pcm_destroy(struct platform_device *dmapd);
 
@@ -27,15 +33,10 @@ struct au1xpsc_audio_data {
 
 	unsigned long pm[2];
 	struct mutex lock;
+	int dmaids[2];
 	struct platform_device *dmapd;
 };
 
-#define PCM_TX	0
-#define PCM_RX	1
-
-#define SUBSTREAM_TYPE(substream) \
-	((substream)->stream == SNDRV_PCM_STREAM_PLAYBACK ? PCM_TX : PCM_RX)
-
 /* 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)
-- 
1.7.6

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH V4 1/3] ASoC: Alchemy AC97C/I2SC audio support
@ 2011-07-24 10:11   ` Manuel Lauss
  0 siblings, 0 replies; 16+ messages in thread
From: Manuel Lauss @ 2011-07-24 10:11 UTC (permalink / raw)
  To: alsa-devel; +Cc: Linux-MIPS, Manuel Lauss, Liam Girdwood, Ralf Baechle

This patch adds ASoC support for the AC97 and I2S controllers
on the old Au1000/Au1500/Au1100 chips,

AC97 Tested on a Db1500.  I2S untested since none of the boards
actually have an I2S codec wired up (just test pins).

Signed-off-by: Manuel Lauss <manuel.lauss@googlemail.com>
---
V4: dropped hunk which removed I2S constants in au1000.h header to avoid merge
    conflicts with other patches, use the context structure in psc.h since it
    fits really well.
V3: implemented feedback from Lars-Peter Clausen: src tidying, no more
    automatic dma device registration, split off db1000 board code.
V2: added untested I2S controller driver for completeness, removed the audio
    defines from the au1000 header as well.

 sound/soc/au1x/Kconfig  |   19 +++
 sound/soc/au1x/Makefile |    8 +
 sound/soc/au1x/ac97c.c  |  365 +++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/au1x/dma.c    |  374 +++++++++++++++++++++++++++++++++++++++++++++++
 sound/soc/au1x/i2sc.c   |  342 +++++++++++++++++++++++++++++++++++++++++++
 sound/soc/au1x/psc.h    |   19 ++-
 6 files changed, 1118 insertions(+), 9 deletions(-)
 create mode 100644 sound/soc/au1x/ac97c.c
 create mode 100644 sound/soc/au1x/dma.c
 create mode 100644 sound/soc/au1x/i2sc.c

diff --git a/sound/soc/au1x/Kconfig b/sound/soc/au1x/Kconfig
index 4b67140..0460b42 100644
--- a/sound/soc/au1x/Kconfig
+++ b/sound/soc/au1x/Kconfig
@@ -18,6 +18,25 @@ config SND_SOC_AU1XPSC_AC97
 	select SND_AC97_CODEC
 	select SND_SOC_AC97_BUS
 
+##
+## Au1000/1500/1100 DMA + AC97C/I2SC
+##
+config SND_SOC_AU1XAUDIO
+	tristate "SoC Audio for Au1000/Au1500/Au1100"
+	depends on MIPS_ALCHEMY
+	help
+	  This is a driver set for the AC97 unit and the
+	  old DMA controller as found on the Au1000/Au1500/Au1100 chips.
+
+config SND_SOC_AU1XAC97C
+	tristate
+	select AC97_BUS
+	select SND_AC97_CODEC
+	select SND_SOC_AC97_BUS
+
+config SND_SOC_AU1XI2SC
+	tristate
+
 
 ##
 ## Boards
diff --git a/sound/soc/au1x/Makefile b/sound/soc/au1x/Makefile
index 1687307..ff5531e 100644
--- a/sound/soc/au1x/Makefile
+++ b/sound/soc/au1x/Makefile
@@ -3,9 +3,17 @@ snd-soc-au1xpsc-dbdma-objs := dbdma2.o
 snd-soc-au1xpsc-i2s-objs := psc-i2s.o
 snd-soc-au1xpsc-ac97-objs := psc-ac97.o
 
+# Au1000/1500/1100 Audio units
+snd-soc-au1x-dma-objs := dma.o
+snd-soc-au1x-ac97c-objs := ac97c.o
+snd-soc-au1x-i2sc-objs := i2sc.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
+obj-$(CONFIG_SND_SOC_AU1XAUDIO) += snd-soc-au1x-dma.o
+obj-$(CONFIG_SND_SOC_AU1XAC97C) += snd-soc-au1x-ac97c.o
+obj-$(CONFIG_SND_SOC_AU1XI2SC) += snd-soc-au1x-i2sc.o
 
 # Boards
 snd-soc-db1200-objs := db1200.o
diff --git a/sound/soc/au1x/ac97c.c b/sound/soc/au1x/ac97c.c
new file mode 100644
index 0000000..35884ae
--- /dev/null
+++ b/sound/soc/au1x/ac97c.c
@@ -0,0 +1,365 @@
+/*
+ * Au1000/Au1500/Au1100 AC97C controller driver for ASoC
+ *
+ * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
+ *
+ * based on the old ALSA driver originally written by
+ *			Charles Eidsness <charles@cooper-street.com>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.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 "psc.h"
+
+/* register offsets and bits */
+#define AC97_CONFIG	0x00
+#define AC97_STATUS	0x04
+#define AC97_DATA	0x08
+#define AC97_CMDRESP	0x0c
+#define AC97_ENABLE	0x10
+
+#define CFG_RC(x)	(((x) & 0x3ff) << 13)	/* valid rx slots mask */
+#define CFG_XS(x)	(((x) & 0x3ff) << 3)	/* valid tx slots mask */
+#define CFG_SG		(1 << 2)	/* sync gate */
+#define CFG_SN		(1 << 1)	/* sync control */
+#define CFG_RS		(1 << 0)	/* acrst# control */
+#define STAT_XU		(1 << 11)	/* tx underflow */
+#define STAT_XO		(1 << 10)	/* tx overflow */
+#define STAT_RU		(1 << 9)	/* rx underflow */
+#define STAT_RO		(1 << 8)	/* rx overflow */
+#define STAT_RD		(1 << 7)	/* codec ready */
+#define STAT_CP		(1 << 6)	/* command pending */
+#define STAT_TE		(1 << 4)	/* tx fifo empty */
+#define STAT_TF		(1 << 3)	/* tx fifo full */
+#define STAT_RE		(1 << 1)	/* rx fifo empty */
+#define STAT_RF		(1 << 0)	/* rx fifo full */
+#define CMD_SET_DATA(x)	(((x) & 0xffff) << 16)
+#define CMD_GET_DATA(x)	((x) & 0xffff)
+#define CMD_READ	(1 << 7)
+#define CMD_WRITE	(0 << 7)
+#define CMD_IDX(x)	((x) & 0x7f)
+#define EN_D		(1 << 1)	/* DISable bit */
+#define EN_CE		(1 << 0)	/* clock enable bit */
+
+/* how often to retry failed codec register reads/writes */
+#define AC97_RW_RETRIES	5
+
+#define AC97_RATES	\
+	SNDRV_PCM_RATE_8000_44100
+
+#define AC97_FMTS	\
+	(SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE)
+
+/* instance data. There can be only one, MacLeod!!!!, fortunately there IS only
+ * once AC97C on early Alchemy chips. The newer ones aren't so lucky.
+ */
+static struct au1xpsc_audio_data *ac97c_workdata;
+#define ac97_to_ctx(x)		ac97c_workdata
+
+static inline unsigned long RD(struct au1xpsc_audio_data *ctx, int reg)
+{
+	return __raw_readl(ctx->mmio + reg);
+}
+
+static inline void WR(struct au1xpsc_audio_data *ctx, int reg, unsigned long v)
+{
+	__raw_writel(v, ctx->mmio + reg);
+	wmb();
+}
+
+static unsigned short au1xac97c_ac97_read(struct snd_ac97 *ac97,
+					  unsigned short r)
+{
+	struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
+	unsigned int tmo, retry;
+	unsigned long data;
+
+	data = ~0;
+	retry = AC97_RW_RETRIES;
+	do {
+		mutex_lock(&ctx->lock);
+
+		tmo = 5;
+		while ((RD(ctx, AC97_STATUS) & STAT_CP) && tmo--)
+			udelay(21);	/* wait an ac97 frame time */
+		if (!tmo) {
+			pr_debug("ac97rd timeout #1\n");
+			goto next;
+		}
+
+		WR(ctx, AC97_CMDRESP, CMD_IDX(r) | CMD_READ);
+
+		/* stupid errata: data is only valid for 21us, so
+		 * poll, Forrest, poll...
+		 */
+		tmo = 0x10000;
+		while ((RD(ctx, AC97_STATUS) & STAT_CP) && tmo--)
+			asm volatile ("nop");
+		data = RD(ctx, AC97_CMDRESP);
+
+		if (!tmo)
+			pr_debug("ac97rd timeout #2\n");
+
+next:
+		mutex_unlock(&ctx->lock);
+	} while (--retry && !tmo);
+
+	pr_debug("AC97RD %04x %04lx %d\n", r, data, retry);
+
+	return retry ? data & 0xffff : 0xffff;
+}
+
+static void au1xac97c_ac97_write(struct snd_ac97 *ac97, unsigned short r,
+				 unsigned short v)
+{
+	struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
+	unsigned int tmo, retry;
+
+	retry = AC97_RW_RETRIES;
+	do {
+		mutex_lock(&ctx->lock);
+
+		for (tmo = 5; (RD(ctx, AC97_STATUS) & STAT_CP) && tmo; tmo--)
+			udelay(21);
+		if (!tmo) {
+			pr_debug("ac97wr timeout #1\n");
+			goto next;
+		}
+
+		WR(ctx, AC97_CMDRESP, CMD_WRITE | CMD_IDX(r) | CMD_SET_DATA(v));
+
+		for (tmo = 10; (RD(ctx, AC97_STATUS) & STAT_CP) && tmo; tmo--)
+			udelay(21);
+		if (!tmo)
+			pr_debug("ac97wr timeout #2\n");
+next:
+		mutex_unlock(&ctx->lock);
+	} while (--retry && !tmo);
+
+	pr_debug("AC97WR %04x %04x %d\n", r, v, retry);
+}
+
+static void au1xac97c_ac97_warm_reset(struct snd_ac97 *ac97)
+{
+	struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
+
+	WR(ctx, AC97_CONFIG, ctx->cfg | CFG_SG | CFG_SN);
+	msleep(20);
+	WR(ctx, AC97_CONFIG, ctx->cfg | CFG_SG);
+	WR(ctx, AC97_CONFIG, ctx->cfg);
+}
+
+static void au1xac97c_ac97_cold_reset(struct snd_ac97 *ac97)
+{
+	struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
+	int i;
+
+	WR(ctx, AC97_CONFIG, ctx->cfg | CFG_RS);
+	msleep(500);
+	WR(ctx, AC97_CONFIG, ctx->cfg);
+
+	/* wait for codec ready */
+	i = 50;
+	while (((RD(ctx, AC97_STATUS) & STAT_RD) == 0) && --i)
+		msleep(20);
+	if (!i)
+		printk(KERN_ERR "ac97c: codec not ready after cold reset\n");
+}
+
+/* AC97 controller operations */
+struct snd_ac97_bus_ops soc_ac97_ops = {
+	.read		= au1xac97c_ac97_read,
+	.write		= au1xac97c_ac97_write,
+	.reset		= au1xac97c_ac97_cold_reset,
+	.warm_reset	= au1xac97c_ac97_warm_reset,
+};
+EXPORT_SYMBOL_GPL(soc_ac97_ops);	/* globals be gone! */
+
+static int alchemy_ac97c_startup(struct snd_pcm_substream *substream,
+				 struct snd_soc_dai *dai)
+{
+	struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
+	snd_soc_dai_set_dma_data(dai, substream, &ctx->dmaids[0]);
+	return 0;
+}
+
+static struct snd_soc_dai_ops alchemy_ac97c_ops = {
+	.startup		= alchemy_ac97c_startup,
+};
+
+static int au1xac97c_dai_probe(struct snd_soc_dai *dai)
+{
+	return ac97c_workdata ? 0 : -ENODEV;
+}
+
+static struct snd_soc_dai_driver au1xac97c_dai_driver = {
+	.name			= "alchemy-ac97c",
+	.ac97_control		= 1,
+	.probe			= au1xac97c_dai_probe,
+	.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			= &alchemy_ac97c_ops,
+};
+
+static int __devinit au1xac97c_drvprobe(struct platform_device *pdev)
+{
+	int ret;
+	struct resource *r;
+	struct au1xpsc_audio_data *ctx;
+
+	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	mutex_init(&ctx->lock);
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!r) {
+		ret = -ENODEV;
+		goto out0;
+	}
+
+	ret = -EBUSY;
+	if (!request_mem_region(r->start, resource_size(r), pdev->name))
+		goto out0;
+
+	ctx->mmio = ioremap_nocache(r->start, resource_size(r));
+	if (!ctx->mmio)
+		goto out1;
+
+	r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+	if (!r)
+		goto out1;
+	ctx->dmaids[PCM_TX] = r->start;
+
+	r = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+	if (!r)
+		goto out1;
+	ctx->dmaids[PCM_RX] = r->start;
+
+	/* switch it on */
+	WR(ctx, AC97_ENABLE, EN_D | EN_CE);
+	WR(ctx, AC97_ENABLE, EN_CE);
+
+	ctx->cfg = CFG_RC(3) | CFG_XS(3);
+	WR(ctx, AC97_CONFIG, ctx->cfg);
+
+	platform_set_drvdata(pdev, ctx);
+
+	ret = snd_soc_register_dai(&pdev->dev, &au1xac97c_dai_driver);
+	if (ret)
+		goto out1;
+
+	ac97c_workdata = ctx;
+	return 0;
+
+
+	snd_soc_unregister_dai(&pdev->dev);
+out1:
+	release_mem_region(r->start, resource_size(r));
+out0:
+	kfree(ctx);
+	return ret;
+}
+
+static int __devexit au1xac97c_drvremove(struct platform_device *pdev)
+{
+	struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev);
+	struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	snd_soc_unregister_dai(&pdev->dev);
+
+	WR(ctx, AC97_ENABLE, EN_D);	/* clock off, disable */
+
+	iounmap(ctx->mmio);
+	release_mem_region(r->start, resource_size(r));
+	kfree(ctx);
+
+	ac97c_workdata = NULL;	/* MDEV */
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int au1xac97c_drvsuspend(struct device *dev)
+{
+	struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev);
+
+	WR(ctx, AC97_ENABLE, EN_D);	/* clock off, disable */
+
+	return 0;
+}
+
+static int au1xac97c_drvresume(struct device *dev)
+{
+	struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev);
+
+	WR(ctx, AC97_ENABLE, EN_D | EN_CE);
+	WR(ctx, AC97_ENABLE, EN_CE);
+	WR(ctx, AC97_CONFIG, ctx->cfg);
+
+	return 0;
+}
+
+static const struct dev_pm_ops au1xpscac97_pmops = {
+	.suspend	= au1xac97c_drvsuspend,
+	.resume		= au1xac97c_drvresume,
+};
+
+#define AU1XPSCAC97_PMOPS (&au1xpscac97_pmops)
+
+#else
+
+#define AU1XPSCAC97_PMOPS NULL
+
+#endif
+
+static struct platform_driver au1xac97c_driver = {
+	.driver	= {
+		.name	= "alchemy-ac97c",
+		.owner	= THIS_MODULE,
+		.pm	= AU1XPSCAC97_PMOPS,
+	},
+	.probe		= au1xac97c_drvprobe,
+	.remove		= __devexit_p(au1xac97c_drvremove),
+};
+
+static int __init au1xac97c_load(void)
+{
+	ac97c_workdata = NULL;
+	return platform_driver_register(&au1xac97c_driver);
+}
+
+static void __exit au1xac97c_unload(void)
+{
+	platform_driver_unregister(&au1xac97c_driver);
+}
+
+module_init(au1xac97c_load);
+module_exit(au1xac97c_unload);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Au1000/1500/1100 AC97C ASoC driver");
+MODULE_AUTHOR("Manuel Lauss");
diff --git a/sound/soc/au1x/dma.c b/sound/soc/au1x/dma.c
new file mode 100644
index 0000000..20fedbd
--- /dev/null
+++ b/sound/soc/au1x/dma.c
@@ -0,0 +1,374 @@
+/*
+ * Au1000/Au1500/Au1100 Audio DMA support.
+ *
+ * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
+ *
+ * copied almost verbatim from the old ALSA driver, written by
+ *			Charles Eidsness <charles@cooper-street.com>
+ */
+
+#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/au1000_dma.h>
+
+#include "psc.h"
+
+#define ALCHEMY_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)
+
+struct pcm_period {
+	u32 start;
+	u32 relative_end;	/* relative to start of buffer */
+	struct pcm_period *next;
+};
+
+struct audio_stream {
+	struct snd_pcm_substream *substream;
+	int dma;
+	struct pcm_period *buffer;
+	unsigned int period_size;
+	unsigned int periods;
+};
+
+struct alchemy_pcm_ctx {
+	struct audio_stream stream[2];	/* playback & capture */
+};
+
+static void au1000_release_dma_link(struct audio_stream *stream)
+{
+	struct pcm_period *pointer;
+	struct pcm_period *pointer_next;
+
+	stream->period_size = 0;
+	stream->periods = 0;
+	pointer = stream->buffer;
+	if (!pointer)
+		return;
+	do {
+		pointer_next = pointer->next;
+		kfree(pointer);
+		pointer = pointer_next;
+	} while (pointer != stream->buffer);
+	stream->buffer = NULL;
+}
+
+static int au1000_setup_dma_link(struct audio_stream *stream,
+				 unsigned int period_bytes,
+				 unsigned int periods)
+{
+	struct snd_pcm_substream *substream = stream->substream;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct pcm_period *pointer;
+	unsigned long dma_start;
+	int i;
+
+	dma_start = virt_to_phys(runtime->dma_area);
+
+	if (stream->period_size == period_bytes &&
+	    stream->periods == periods)
+		return 0; /* not changed */
+
+	au1000_release_dma_link(stream);
+
+	stream->period_size = period_bytes;
+	stream->periods = periods;
+
+	stream->buffer = kmalloc(sizeof(struct pcm_period), GFP_KERNEL);
+	if (!stream->buffer)
+		return -ENOMEM;
+	pointer = stream->buffer;
+	for (i = 0; i < periods; i++) {
+		pointer->start = (u32)(dma_start + (i * period_bytes));
+		pointer->relative_end = (u32) (((i+1) * period_bytes) - 0x1);
+		if (i < periods - 1) {
+			pointer->next = kmalloc(sizeof(struct pcm_period),
+						GFP_KERNEL);
+			if (!pointer->next) {
+				au1000_release_dma_link(stream);
+				return -ENOMEM;
+			}
+			pointer = pointer->next;
+		}
+	}
+	pointer->next = stream->buffer;
+	return 0;
+}
+
+static void au1000_dma_stop(struct audio_stream *stream)
+{
+	if (stream->buffer)
+		disable_dma(stream->dma);
+}
+
+static void au1000_dma_start(struct audio_stream *stream)
+{
+	if (!stream->buffer)
+		return;
+
+	init_dma(stream->dma);
+	if (get_dma_active_buffer(stream->dma) == 0) {
+		clear_dma_done0(stream->dma);
+		set_dma_addr0(stream->dma, stream->buffer->start);
+		set_dma_count0(stream->dma, stream->period_size >> 1);
+		set_dma_addr1(stream->dma, stream->buffer->next->start);
+		set_dma_count1(stream->dma, stream->period_size >> 1);
+	} else {
+		clear_dma_done1(stream->dma);
+		set_dma_addr1(stream->dma, stream->buffer->start);
+		set_dma_count1(stream->dma, stream->period_size >> 1);
+		set_dma_addr0(stream->dma, stream->buffer->next->start);
+		set_dma_count0(stream->dma, stream->period_size >> 1);
+	}
+	enable_dma_buffers(stream->dma);
+	start_dma(stream->dma);
+}
+
+static irqreturn_t au1000_dma_interrupt(int irq, void *ptr)
+{
+	struct audio_stream *stream = (struct audio_stream *)ptr;
+	struct snd_pcm_substream *substream = stream->substream;
+
+	switch (get_dma_buffer_done(stream->dma)) {
+	case DMA_D0:
+		stream->buffer = stream->buffer->next;
+		clear_dma_done0(stream->dma);
+		set_dma_addr0(stream->dma, stream->buffer->next->start);
+		set_dma_count0(stream->dma, stream->period_size >> 1);
+		enable_dma_buffer0(stream->dma);
+		break;
+	case DMA_D1:
+		stream->buffer = stream->buffer->next;
+		clear_dma_done1(stream->dma);
+		set_dma_addr1(stream->dma, stream->buffer->next->start);
+		set_dma_count1(stream->dma, stream->period_size >> 1);
+		enable_dma_buffer1(stream->dma);
+		break;
+	case (DMA_D0 | DMA_D1):
+		pr_debug("DMA %d missed interrupt.\n", stream->dma);
+		au1000_dma_stop(stream);
+		au1000_dma_start(stream);
+		break;
+	case (~DMA_D0 & ~DMA_D1):
+		pr_debug("DMA %d empty irq.\n", stream->dma);
+	}
+	snd_pcm_period_elapsed(substream);
+	return IRQ_HANDLED;
+}
+
+static const struct snd_pcm_hardware alchemy_pcm_hardware = {
+	.info		  = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+			    SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BATCH,
+	.formats	  = ALCHEMY_PCM_FMTS,
+	.rates		  = SNDRV_PCM_RATE_8000_192000,
+	.rate_min	  = SNDRV_PCM_RATE_8000,
+	.rate_max	  = SNDRV_PCM_RATE_192000,
+	.channels_min	  = 2,
+	.channels_max	  = 2,
+	.period_bytes_min = 1024,
+	.period_bytes_max = 16 * 1024 - 1,
+	.periods_min	  = 4,
+	.periods_max	  = 255,
+	.buffer_bytes_max = 128 * 1024,
+	.fifo_size	  = 16,
+};
+
+static inline struct alchemy_pcm_ctx *ss_to_ctx(struct snd_pcm_substream *ss)
+{
+	struct snd_soc_pcm_runtime *rtd = ss->private_data;
+	return snd_soc_platform_get_drvdata(rtd->platform);
+}
+
+static inline struct audio_stream *ss_to_as(struct snd_pcm_substream *ss)
+{
+	struct alchemy_pcm_ctx *ctx = ss_to_ctx(ss);
+	return &(ctx->stream[SUBSTREAM_TYPE(ss)]);
+}
+
+static int alchemy_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct alchemy_pcm_ctx *ctx = ss_to_ctx(substream);
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	int stype = SUBSTREAM_TYPE(substream);
+	int *dmaids;
+	char *name;
+
+	dmaids = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
+	if (!dmaids)
+		return -ENODEV;	/* whoa, has ordering changed? */
+
+	/* DMA setup */
+	name = (stype == PCM_TX) ? "audio-tx" : "audio-rx";
+	ctx->stream[stype].dma = request_au1000_dma(dmaids[stype], name,
+					au1000_dma_interrupt, IRQF_DISABLED,
+					&ctx->stream[stype]);
+	set_dma_mode(ctx->stream[stype].dma,
+		     get_dma_mode(ctx->stream[stype].dma) & ~DMA_NC);
+
+	ctx->stream[stype].substream = substream;
+	ctx->stream[stype].buffer = NULL;
+	snd_soc_set_runtime_hwparams(substream, &alchemy_pcm_hardware);
+
+	return 0;
+}
+
+static int alchemy_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct alchemy_pcm_ctx *ctx = ss_to_ctx(substream);
+	int stype = SUBSTREAM_TYPE(substream);
+
+	ctx->stream[SUBSTREAM_TYPE(substream)].substream = NULL;
+	free_au1000_dma(ctx->stream[stype].dma);
+
+	return 0;
+}
+
+static int alchemy_pcm_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	struct audio_stream *stream = ss_to_as(substream);
+	int err;
+
+	err = snd_pcm_lib_malloc_pages(substream,
+				       params_buffer_bytes(hw_params));
+	if (err < 0)
+		return err;
+	return au1000_setup_dma_link(stream,
+				     params_period_bytes(hw_params),
+				     params_periods(hw_params));
+}
+
+static int alchemy_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct audio_stream *stream = ss_to_as(substream);
+	au1000_release_dma_link(stream);
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static int alchemy_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct audio_stream *stream = ss_to_as(substream);
+	int err = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		au1000_dma_start(stream);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		au1000_dma_stop(stream);
+		break;
+	default:
+		err = -EINVAL;
+		break;
+	}
+	return err;
+}
+
+static snd_pcm_uframes_t alchemy_pcm_pointer(struct snd_pcm_substream *ss)
+{
+	struct audio_stream *stream = ss_to_as(ss);
+	long location;
+
+	location = get_dma_residue(stream->dma);
+	location = stream->buffer->relative_end - location;
+	if (location == -1)
+		location = 0;
+	return bytes_to_frames(ss->runtime, location);
+}
+
+static struct snd_pcm_ops alchemy_pcm_ops = {
+	.open			= alchemy_pcm_open,
+	.close			= alchemy_pcm_close,
+	.ioctl			= snd_pcm_lib_ioctl,
+	.hw_params	        = alchemy_pcm_hw_params,
+	.hw_free	        = alchemy_pcm_hw_free,
+	.trigger		= alchemy_pcm_trigger,
+	.pointer		= alchemy_pcm_pointer,
+};
+
+static void alchemy_pcm_free_dma_buffers(struct snd_pcm *pcm)
+{
+	snd_pcm_lib_preallocate_free_for_all(pcm);
+}
+
+static int alchemy_pcm_new(struct snd_card *card,
+			   struct snd_soc_dai *dai,
+			   struct snd_pcm *pcm)
+{
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+		snd_dma_continuous_data(GFP_KERNEL), 65536, (4096 * 1024) - 1);
+
+	return 0;
+}
+
+struct snd_soc_platform_driver alchemy_pcm_soc_platform = {
+	.ops		= &alchemy_pcm_ops,
+	.pcm_new	= alchemy_pcm_new,
+	.pcm_free	= alchemy_pcm_free_dma_buffers,
+};
+
+static int __devinit alchemy_pcm_drvprobe(struct platform_device *pdev)
+{
+	struct alchemy_pcm_ctx *ctx;
+	int ret;
+
+	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, ctx);
+
+	ret = snd_soc_register_platform(&pdev->dev, &alchemy_pcm_soc_platform);
+	if (ret)
+		kfree(ctx);
+
+	return ret;
+}
+
+static int __devexit alchemy_pcm_drvremove(struct platform_device *pdev)
+{
+	struct alchemy_pcm_ctx *ctx = platform_get_drvdata(pdev);
+
+	snd_soc_unregister_platform(&pdev->dev);
+	kfree(ctx);
+
+	return 0;
+}
+
+static struct platform_driver alchemy_pcmdma_driver = {
+	.driver	= {
+		.name	= "alchemy-pcm-dma",
+		.owner	= THIS_MODULE,
+	},
+	.probe		= alchemy_pcm_drvprobe,
+	.remove		= __devexit_p(alchemy_pcm_drvremove),
+};
+
+static int __init alchemy_pcmdma_load(void)
+{
+	return platform_driver_register(&alchemy_pcmdma_driver);
+}
+
+static void __exit alchemy_pcmdma_unload(void)
+{
+	platform_driver_unregister(&alchemy_pcmdma_driver);
+}
+
+module_init(alchemy_pcmdma_load);
+module_exit(alchemy_pcmdma_unload);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Au1000/Au1500/Au1100 Audio DMA driver");
+MODULE_AUTHOR("Manuel Lauss");
diff --git a/sound/soc/au1x/i2sc.c b/sound/soc/au1x/i2sc.c
new file mode 100644
index 0000000..e3964a2
--- /dev/null
+++ b/sound/soc/au1x/i2sc.c
@@ -0,0 +1,342 @@
+/*
+ * Au1000/Au1500/Au1100 I2S controller driver for ASoC
+ *
+ * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
+ *
+ * Note: clock supplied to the I2S controller must be 256x samplerate.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.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 "psc.h"
+
+#define I2S_RXTX	0x00
+#define I2S_CFG		0x04
+#define I2S_ENABLE	0x08
+
+#define CFG_XU		(1 << 25)	/* tx underflow */
+#define CFG_XO		(1 << 24)
+#define CFG_RU		(1 << 23)
+#define CFG_RO		(1 << 22)
+#define CFG_TR		(1 << 21)
+#define CFG_TE		(1 << 20)
+#define CFG_TF		(1 << 19)
+#define CFG_RR		(1 << 18)
+#define CFG_RF		(1 << 17)
+#define CFG_ICK		(1 << 12)	/* clock invert */
+#define CFG_PD		(1 << 11)	/* set to make I2SDIO INPUT */
+#define CFG_LB		(1 << 10)	/* loopback */
+#define CFG_IC		(1 << 9)	/* word select invert */
+#define CFG_FM_I2S	(0 << 7)	/* I2S format */
+#define CFG_FM_LJ	(1 << 7)	/* left-justified */
+#define CFG_FM_RJ	(2 << 7)	/* right-justified */
+#define CFG_FM_MASK	(3 << 7)
+#define CFG_TN		(1 << 6)	/* tx fifo en */
+#define CFG_RN		(1 << 5)	/* rx fifo en */
+#define CFG_SZ_8	(0x08)
+#define CFG_SZ_16	(0x10)
+#define CFG_SZ_18	(0x12)
+#define CFG_SZ_20	(0x14)
+#define CFG_SZ_24	(0x18)
+#define CFG_SZ_MASK	(0x1f)
+#define EN_D		(1 << 1)	/* DISable */
+#define EN_CE		(1 << 0)	/* clock enable */
+
+/* only limited by clock generator and board design */
+#define AU1XI2SC_RATES \
+	SNDRV_PCM_RATE_CONTINUOUS
+
+#define AU1XI2SC_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_S18_3LE | SNDRV_PCM_FMTBIT_U18_3LE |	\
+	SNDRV_PCM_FMTBIT_S18_3BE | SNDRV_PCM_FMTBIT_U18_3BE |	\
+	SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE |	\
+	SNDRV_PCM_FMTBIT_S20_3BE | SNDRV_PCM_FMTBIT_U20_3BE |	\
+	SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE |	\
+	SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE |	\
+	0)
+
+static inline unsigned long RD(struct au1xpsc_audio_data *ctx, int reg)
+{
+	return __raw_readl(ctx->mmio + reg);
+}
+
+static inline void WR(struct au1xpsc_audio_data *ctx, int reg, unsigned long v)
+{
+	__raw_writel(v, ctx->mmio + reg);
+	wmb();
+}
+
+static int au1xi2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
+{
+	struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(cpu_dai);
+	unsigned long c;
+	int ret;
+
+	ret = -EINVAL;
+	c = ctx->cfg;
+
+	c &= ~CFG_FM_MASK;
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		c |= CFG_FM_I2S;
+		break;
+	case SND_SOC_DAIFMT_MSB:
+		c |= CFG_FM_RJ;
+		break;
+	case SND_SOC_DAIFMT_LSB:
+		c |= CFG_FM_LJ;
+		break;
+	default:
+		goto out;
+	}
+
+	c &= ~(CFG_IC | CFG_ICK);		/* IB-IF */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		c |= CFG_IC | CFG_ICK;
+		break;
+	case SND_SOC_DAIFMT_NB_IF:
+		c |= CFG_IC;
+		break;
+	case SND_SOC_DAIFMT_IB_NF:
+		c |= CFG_ICK;
+		break;
+	case SND_SOC_DAIFMT_IB_IF:
+		break;
+	default:
+		goto out;
+	}
+
+	/* I2S controller only supports master */
+	switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+	case SND_SOC_DAIFMT_CBS_CFS:	/* CODEC slave */
+		break;
+	default:
+		goto out;
+	}
+
+	ret = 0;
+	ctx->cfg = c;
+out:
+	return ret;
+}
+
+static int au1xi2s_trigger(struct snd_pcm_substream *substream,
+			   int cmd, struct snd_soc_dai *dai)
+{
+	struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
+	int stype = SUBSTREAM_TYPE(substream);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		/* power up */
+		WR(ctx, I2S_ENABLE, EN_D | EN_CE);
+		WR(ctx, I2S_ENABLE, EN_CE);
+		ctx->cfg |= (stype == PCM_TX) ? CFG_TN : CFG_RN;
+		WR(ctx, I2S_CFG, ctx->cfg);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		ctx->cfg &= ~((stype == PCM_TX) ? CFG_TN : CFG_RN);
+		WR(ctx, I2S_CFG, ctx->cfg);
+		WR(ctx, I2S_ENABLE, EN_D);		/* power off */
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static unsigned long msbits_to_reg(int msbits)
+{
+	switch (msbits) {
+	case 8:  return CFG_SZ_8;
+	case 16: return CFG_SZ_16;
+	case 18: return CFG_SZ_18;
+	case 20: return CFG_SZ_20;
+	case 24: return CFG_SZ_24;
+	}
+	return 0;
+}
+
+static int au1xi2s_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *params,
+			     struct snd_soc_dai *dai)
+{
+	struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
+	unsigned long v;
+
+	v = msbits_to_reg(params->msbits);
+	if (!v)
+		return -EINVAL;
+
+	ctx->cfg &= ~CFG_SZ_MASK;
+	ctx->cfg |= v;
+	return 0;
+}
+
+static int au1xi2s_startup(struct snd_pcm_substream *substream,
+			   struct snd_soc_dai *dai)
+{
+	struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
+	snd_soc_dai_set_dma_data(dai, substream, &ctx->dmaids[0]);
+	return 0;
+}
+
+static const struct snd_soc_dai_ops au1xi2s_dai_ops = {
+	.startup	= au1xi2s_startup,
+	.trigger	= au1xi2s_trigger,
+	.hw_params	= au1xi2s_hw_params,
+	.set_fmt	= au1xi2s_set_fmt,
+};
+
+static struct snd_soc_dai_driver au1xi2s_dai_driver = {
+	.symmetric_rates	= 1,
+	.playback = {
+		.rates		= AU1XI2SC_RATES,
+		.formats	= AU1XI2SC_FMTS,
+		.channels_min	= 2,
+		.channels_max	= 2,
+	},
+	.capture = {
+		.rates		= AU1XI2SC_RATES,
+		.formats	= AU1XI2SC_FMTS,
+		.channels_min	= 2,
+		.channels_max	= 2,
+	},
+	.ops = &au1xi2s_dai_ops,
+};
+
+static int __devinit au1xi2s_drvprobe(struct platform_device *pdev)
+{
+	int ret;
+	struct resource *r;
+	struct au1xpsc_audio_data *ctx;
+
+	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+	if (!ctx)
+		return -ENOMEM;
+
+	r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!r) {
+		ret = -ENODEV;
+		goto out0;
+	}
+
+	ret = -EBUSY;
+	if (!request_mem_region(r->start, resource_size(r), pdev->name))
+		goto out0;
+
+	ctx->mmio = ioremap_nocache(r->start, resource_size(r));
+	if (!ctx->mmio)
+		goto out1;
+
+	r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
+	if (!r)
+		goto out1;
+	ctx->dmaids[PCM_TX] = r->start;
+
+	r = platform_get_resource(pdev, IORESOURCE_DMA, 1);
+	if (!r)
+		goto out1;
+	ctx->dmaids[PCM_RX] = r->start;
+
+	platform_set_drvdata(pdev, ctx);
+
+	ret = snd_soc_register_dai(&pdev->dev, &au1xi2s_dai_driver);
+	if (ret)
+		goto out1;
+
+	return 0;
+
+	snd_soc_unregister_dai(&pdev->dev);
+out1:
+	release_mem_region(r->start, resource_size(r));
+out0:
+	kfree(ctx);
+	return ret;
+}
+
+static int __devexit au1xi2s_drvremove(struct platform_device *pdev)
+{
+	struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev);
+	struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+	snd_soc_unregister_dai(&pdev->dev);
+
+	WR(ctx, I2S_ENABLE, EN_D);	/* clock off, disable */
+
+	iounmap(ctx->mmio);
+	release_mem_region(r->start, resource_size(r));
+	kfree(ctx);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int au1xi2s_drvsuspend(struct device *dev)
+{
+	struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev);
+
+	WR(ctx, I2S_ENABLE, EN_D);	/* clock off, disable */
+
+	return 0;
+}
+
+static int au1xi2s_drvresume(struct device *dev)
+{
+	return 0;
+}
+
+static const struct dev_pm_ops au1xi2sc_pmops = {
+	.suspend	= au1xi2s_drvsuspend,
+	.resume		= au1xi2s_drvresume,
+};
+
+#define AU1XI2SC_PMOPS (&au1xi2sc_pmops)
+
+#else
+
+#define AU1XI2SC_PMOPS NULL
+
+#endif
+
+static struct platform_driver au1xi2s_driver = {
+	.driver	= {
+		.name	= "alchemy-i2sc",
+		.owner	= THIS_MODULE,
+		.pm	= AU1XI2SC_PMOPS,
+	},
+	.probe		= au1xi2s_drvprobe,
+	.remove		= __devexit_p(au1xi2s_drvremove),
+};
+
+static int __init au1xi2s_load(void)
+{
+	return platform_driver_register(&au1xi2s_driver);
+}
+
+static void __exit au1xi2s_unload(void)
+{
+	platform_driver_unregister(&au1xi2s_driver);
+}
+
+module_init(au1xi2s_load);
+module_exit(au1xi2s_unload);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Au1000/1500/1100 I2S ASoC driver");
+MODULE_AUTHOR("Manuel Lauss");
diff --git a/sound/soc/au1x/psc.h b/sound/soc/au1x/psc.h
index b30eadd..c59b9e5 100644
--- a/sound/soc/au1x/psc.h
+++ b/sound/soc/au1x/psc.h
@@ -1,7 +1,7 @@
 /*
- * Au12x0/Au1550 PSC ALSA ASoC audio support.
+ * Alchemy ALSA ASoC audio support.
  *
- * (c) 2007-2008 MSC Vertriebsges.m.b.H.,
+ * (c) 2007-2011 MSC Vertriebsges.m.b.H.,
  *	Manuel Lauss <manuel.lauss@gmail.com>
  *
  * This program is free software; you can redistribute it and/or modify
@@ -13,7 +13,13 @@
 #ifndef _AU1X_PCM_H
 #define _AU1X_PCM_H
 
-/* DBDMA helpers */
+#define PCM_TX	0
+#define PCM_RX	1
+
+#define SUBSTREAM_TYPE(substream) \
+	((substream)->stream == SNDRV_PCM_STREAM_PLAYBACK ? PCM_TX : PCM_RX)
+
+/* PSC/DBDMA helpers */
 extern struct platform_device *au1xpsc_pcm_add(struct platform_device *pdev);
 extern void au1xpsc_pcm_destroy(struct platform_device *dmapd);
 
@@ -27,15 +33,10 @@ struct au1xpsc_audio_data {
 
 	unsigned long pm[2];
 	struct mutex lock;
+	int dmaids[2];
 	struct platform_device *dmapd;
 };
 
-#define PCM_TX	0
-#define PCM_RX	1
-
-#define SUBSTREAM_TYPE(substream) \
-	((substream)->stream == SNDRV_PCM_STREAM_PLAYBACK ? PCM_TX : PCM_RX)
-
 /* 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)
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH V4 2/3] ASoC: Add a DB1x00 AC97 machine driver
  2011-07-24 10:11 ` Manuel Lauss
@ 2011-07-24 10:11   ` Manuel Lauss
  -1 siblings, 0 replies; 16+ messages in thread
From: Manuel Lauss @ 2011-07-24 10:11 UTC (permalink / raw)
  To: alsa-devel, Mark Brown
  Cc: Liam Girdwood, Linux-MIPS, Ralf Baechle, Manuel Lauss

Add a machine driver suitable for the AC97 part on the DB1000/DB1500/DB1100
boards.

Run-tested on DB1500.

Signed-off-by: Manuel Lauss <manuel.lauss@googlemail.com>
---
V4: no changes.
V3: split off from patch 1, implemented new machine code.

 arch/mips/alchemy/devboards/db1x00/platform.c |   48 ++++++++++++++++
 sound/soc/au1x/Kconfig                        |    9 +++
 sound/soc/au1x/Makefile                       |    2 +
 sound/soc/au1x/db1000.c                       |   75 +++++++++++++++++++++++++
 4 files changed, 134 insertions(+), 0 deletions(-)
 create mode 100644 sound/soc/au1x/db1000.c

diff --git a/arch/mips/alchemy/devboards/db1x00/platform.c b/arch/mips/alchemy/devboards/db1x00/platform.c
index 978d5ab..7057d28 100644
--- a/arch/mips/alchemy/devboards/db1x00/platform.c
+++ b/arch/mips/alchemy/devboards/db1x00/platform.c
@@ -19,8 +19,11 @@
  */
 
 #include <linux/init.h>
+#include <linux/interrupt.h>
 #include <linux/platform_device.h>
 
+#include <asm/mach-au1x00/au1000.h>
+#include <asm/mach-au1x00/au1000_dma.h>
 #include <asm/mach-au1x00/au1xxx.h>
 #include <asm/mach-db1x00/bcsr.h>
 #include "../platform.h"
@@ -85,6 +88,45 @@
 #endif
 #endif
 
+static struct resource alchemy_ac97c_res[] = {
+	[0] = {
+		.start	= AU1000_AC97_PHYS_ADDR,
+		.end	= AU1000_AC97_PHYS_ADDR + 0xfff,
+		.flags	= IORESOURCE_MEM,
+	},
+	[1] = {
+		.start	= DMA_ID_AC97C_TX,
+		.end	= DMA_ID_AC97C_TX,
+		.flags	= IORESOURCE_DMA,
+	},
+	[2] = {
+		.start	= DMA_ID_AC97C_RX,
+		.end	= DMA_ID_AC97C_RX,
+		.flags	= IORESOURCE_DMA,
+	},
+};
+
+static struct platform_device alchemy_ac97c_dev = {
+	.name		= "alchemy-ac97c",
+	.id		= -1,
+	.resource	= alchemy_ac97c_res,
+	.num_resources	= ARRAY_SIZE(alchemy_ac97c_res),
+};
+
+static struct platform_device alchemy_ac97c_dma_dev = {
+	.name		= "alchemy-pcm-dma",
+	.id		= 0,
+};
+
+static struct platform_device db1x00_codec_dev = {
+	.name		= "ac97-codec",
+	.id		= -1,
+};
+
+static struct platform_device db1x00_audio_dev = {
+	.name		= "db1000-audio",
+};
+
 static int __init db1xxx_dev_init(void)
 {
 #ifdef DB1XXX_HAS_PCMCIA
@@ -113,6 +155,12 @@ static int __init db1xxx_dev_init(void)
 				    1);
 #endif
 	db1x_register_norflash(BOARD_FLASH_SIZE, BOARD_FLASH_WIDTH, F_SWAPPED);
+
+	platform_device_register(&db1x00_codec_dev);
+	platform_device_register(&alchemy_ac97c_dma_dev);
+	platform_device_register(&alchemy_ac97c_dev);
+	platform_device_register(&db1x00_audio_dev);
+
 	return 0;
 }
 device_initcall(db1xxx_dev_init);
diff --git a/sound/soc/au1x/Kconfig b/sound/soc/au1x/Kconfig
index 0460b42..6d59254 100644
--- a/sound/soc/au1x/Kconfig
+++ b/sound/soc/au1x/Kconfig
@@ -41,6 +41,15 @@ config SND_SOC_AU1XI2SC
 ##
 ## Boards
 ##
+config SND_SOC_DB1000
+	tristate "DB1000 Audio support"
+	depends on SND_SOC_AU1XAUDIO
+	select SND_SOC_AU1XAC97C
+	select SND_SOC_AC97_CODEC
+	help
+	  Select this option to enable AC97 audio on the early DB1x00 series
+	  of boards (DB1000/DB1500/DB1100).
+
 config SND_SOC_DB1200
 	tristate "DB1200 AC97+I2S audio support"
 	depends on SND_SOC_AU1XPSC
diff --git a/sound/soc/au1x/Makefile b/sound/soc/au1x/Makefile
index ff5531e..9207105 100644
--- a/sound/soc/au1x/Makefile
+++ b/sound/soc/au1x/Makefile
@@ -16,6 +16,8 @@ obj-$(CONFIG_SND_SOC_AU1XAC97C) += snd-soc-au1x-ac97c.o
 obj-$(CONFIG_SND_SOC_AU1XI2SC) += snd-soc-au1x-i2sc.o
 
 # Boards
+snd-soc-db1000-objs := db1000.o
 snd-soc-db1200-objs := db1200.o
 
+obj-$(CONFIG_SND_SOC_DB1000) += snd-soc-db1000.o
 obj-$(CONFIG_SND_SOC_DB1200) += snd-soc-db1200.o
diff --git a/sound/soc/au1x/db1000.c b/sound/soc/au1x/db1000.c
new file mode 100644
index 0000000..127477a
--- /dev/null
+++ b/sound/soc/au1x/db1000.c
@@ -0,0 +1,75 @@
+/*
+ * DB1000/DB1500/DB1100 ASoC audio fabric support code.
+ *
+ * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
+ *
+ */
+
+#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 <asm/mach-au1x00/au1000.h>
+#include <asm/mach-db1x00/bcsr.h>
+
+#include "psc.h"
+
+static struct snd_soc_dai_link db1000_ac97_dai = {
+	.name		= "AC97",
+	.stream_name	= "AC97 HiFi",
+	.codec_dai_name	= "ac97-hifi",
+	.cpu_dai_name	= "alchemy-ac97c",
+	.platform_name	= "alchemy-pcm-dma.0",
+	.codec_name	= "ac97-codec",
+};
+
+static struct snd_soc_card db1000_ac97 = {
+	.name		= "DB1000_AC97",
+	.dai_link	= &db1000_ac97_dai,
+	.num_links	= 1,
+};
+
+static int __devinit db1000_audio_probe(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = &db1000_ac97;
+	card->dev = &pdev->dev;
+	return snd_soc_register_card(card);
+}
+
+static int __devexit db1000_audio_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+	snd_soc_unregister_card(card);
+	return 0;
+}
+
+static struct platform_driver db1000_audio_driver = {
+	.driver	= {
+		.name	= "db1000-audio",
+		.owner	= THIS_MODULE,
+		.pm	= &snd_soc_pm_ops,
+	},
+	.probe		= db1000_audio_probe,
+	.remove		= __devexit_p(db1000_audio_remove),
+};
+
+static int __init db1000_audio_load(void)
+{
+	return platform_driver_register(&db1000_audio_driver);
+}
+
+static void __exit db1000_audio_unload(void)
+{
+	platform_driver_unregister(&db1000_audio_driver);
+}
+
+module_init(db1000_audio_load);
+module_exit(db1000_audio_unload);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("DB1000/DB1500/DB1100 ASoC audio");
+MODULE_AUTHOR("Manuel Lauss");
-- 
1.7.6

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH V4 2/3] ASoC: Add a DB1x00 AC97 machine driver
@ 2011-07-24 10:11   ` Manuel Lauss
  0 siblings, 0 replies; 16+ messages in thread
From: Manuel Lauss @ 2011-07-24 10:11 UTC (permalink / raw)
  To: alsa-devel; +Cc: Linux-MIPS, Manuel Lauss, Liam Girdwood, Ralf Baechle

Add a machine driver suitable for the AC97 part on the DB1000/DB1500/DB1100
boards.

Run-tested on DB1500.

Signed-off-by: Manuel Lauss <manuel.lauss@googlemail.com>
---
V4: no changes.
V3: split off from patch 1, implemented new machine code.

 arch/mips/alchemy/devboards/db1x00/platform.c |   48 ++++++++++++++++
 sound/soc/au1x/Kconfig                        |    9 +++
 sound/soc/au1x/Makefile                       |    2 +
 sound/soc/au1x/db1000.c                       |   75 +++++++++++++++++++++++++
 4 files changed, 134 insertions(+), 0 deletions(-)
 create mode 100644 sound/soc/au1x/db1000.c

diff --git a/arch/mips/alchemy/devboards/db1x00/platform.c b/arch/mips/alchemy/devboards/db1x00/platform.c
index 978d5ab..7057d28 100644
--- a/arch/mips/alchemy/devboards/db1x00/platform.c
+++ b/arch/mips/alchemy/devboards/db1x00/platform.c
@@ -19,8 +19,11 @@
  */
 
 #include <linux/init.h>
+#include <linux/interrupt.h>
 #include <linux/platform_device.h>
 
+#include <asm/mach-au1x00/au1000.h>
+#include <asm/mach-au1x00/au1000_dma.h>
 #include <asm/mach-au1x00/au1xxx.h>
 #include <asm/mach-db1x00/bcsr.h>
 #include "../platform.h"
@@ -85,6 +88,45 @@
 #endif
 #endif
 
+static struct resource alchemy_ac97c_res[] = {
+	[0] = {
+		.start	= AU1000_AC97_PHYS_ADDR,
+		.end	= AU1000_AC97_PHYS_ADDR + 0xfff,
+		.flags	= IORESOURCE_MEM,
+	},
+	[1] = {
+		.start	= DMA_ID_AC97C_TX,
+		.end	= DMA_ID_AC97C_TX,
+		.flags	= IORESOURCE_DMA,
+	},
+	[2] = {
+		.start	= DMA_ID_AC97C_RX,
+		.end	= DMA_ID_AC97C_RX,
+		.flags	= IORESOURCE_DMA,
+	},
+};
+
+static struct platform_device alchemy_ac97c_dev = {
+	.name		= "alchemy-ac97c",
+	.id		= -1,
+	.resource	= alchemy_ac97c_res,
+	.num_resources	= ARRAY_SIZE(alchemy_ac97c_res),
+};
+
+static struct platform_device alchemy_ac97c_dma_dev = {
+	.name		= "alchemy-pcm-dma",
+	.id		= 0,
+};
+
+static struct platform_device db1x00_codec_dev = {
+	.name		= "ac97-codec",
+	.id		= -1,
+};
+
+static struct platform_device db1x00_audio_dev = {
+	.name		= "db1000-audio",
+};
+
 static int __init db1xxx_dev_init(void)
 {
 #ifdef DB1XXX_HAS_PCMCIA
@@ -113,6 +155,12 @@ static int __init db1xxx_dev_init(void)
 				    1);
 #endif
 	db1x_register_norflash(BOARD_FLASH_SIZE, BOARD_FLASH_WIDTH, F_SWAPPED);
+
+	platform_device_register(&db1x00_codec_dev);
+	platform_device_register(&alchemy_ac97c_dma_dev);
+	platform_device_register(&alchemy_ac97c_dev);
+	platform_device_register(&db1x00_audio_dev);
+
 	return 0;
 }
 device_initcall(db1xxx_dev_init);
diff --git a/sound/soc/au1x/Kconfig b/sound/soc/au1x/Kconfig
index 0460b42..6d59254 100644
--- a/sound/soc/au1x/Kconfig
+++ b/sound/soc/au1x/Kconfig
@@ -41,6 +41,15 @@ config SND_SOC_AU1XI2SC
 ##
 ## Boards
 ##
+config SND_SOC_DB1000
+	tristate "DB1000 Audio support"
+	depends on SND_SOC_AU1XAUDIO
+	select SND_SOC_AU1XAC97C
+	select SND_SOC_AC97_CODEC
+	help
+	  Select this option to enable AC97 audio on the early DB1x00 series
+	  of boards (DB1000/DB1500/DB1100).
+
 config SND_SOC_DB1200
 	tristate "DB1200 AC97+I2S audio support"
 	depends on SND_SOC_AU1XPSC
diff --git a/sound/soc/au1x/Makefile b/sound/soc/au1x/Makefile
index ff5531e..9207105 100644
--- a/sound/soc/au1x/Makefile
+++ b/sound/soc/au1x/Makefile
@@ -16,6 +16,8 @@ obj-$(CONFIG_SND_SOC_AU1XAC97C) += snd-soc-au1x-ac97c.o
 obj-$(CONFIG_SND_SOC_AU1XI2SC) += snd-soc-au1x-i2sc.o
 
 # Boards
+snd-soc-db1000-objs := db1000.o
 snd-soc-db1200-objs := db1200.o
 
+obj-$(CONFIG_SND_SOC_DB1000) += snd-soc-db1000.o
 obj-$(CONFIG_SND_SOC_DB1200) += snd-soc-db1200.o
diff --git a/sound/soc/au1x/db1000.c b/sound/soc/au1x/db1000.c
new file mode 100644
index 0000000..127477a
--- /dev/null
+++ b/sound/soc/au1x/db1000.c
@@ -0,0 +1,75 @@
+/*
+ * DB1000/DB1500/DB1100 ASoC audio fabric support code.
+ *
+ * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
+ *
+ */
+
+#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 <asm/mach-au1x00/au1000.h>
+#include <asm/mach-db1x00/bcsr.h>
+
+#include "psc.h"
+
+static struct snd_soc_dai_link db1000_ac97_dai = {
+	.name		= "AC97",
+	.stream_name	= "AC97 HiFi",
+	.codec_dai_name	= "ac97-hifi",
+	.cpu_dai_name	= "alchemy-ac97c",
+	.platform_name	= "alchemy-pcm-dma.0",
+	.codec_name	= "ac97-codec",
+};
+
+static struct snd_soc_card db1000_ac97 = {
+	.name		= "DB1000_AC97",
+	.dai_link	= &db1000_ac97_dai,
+	.num_links	= 1,
+};
+
+static int __devinit db1000_audio_probe(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = &db1000_ac97;
+	card->dev = &pdev->dev;
+	return snd_soc_register_card(card);
+}
+
+static int __devexit db1000_audio_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+	snd_soc_unregister_card(card);
+	return 0;
+}
+
+static struct platform_driver db1000_audio_driver = {
+	.driver	= {
+		.name	= "db1000-audio",
+		.owner	= THIS_MODULE,
+		.pm	= &snd_soc_pm_ops,
+	},
+	.probe		= db1000_audio_probe,
+	.remove		= __devexit_p(db1000_audio_remove),
+};
+
+static int __init db1000_audio_load(void)
+{
+	return platform_driver_register(&db1000_audio_driver);
+}
+
+static void __exit db1000_audio_unload(void)
+{
+	platform_driver_unregister(&db1000_audio_driver);
+}
+
+module_init(db1000_audio_load);
+module_exit(db1000_audio_unload);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("DB1000/DB1500/DB1100 ASoC audio");
+MODULE_AUTHOR("Manuel Lauss");
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH V4 3/3] ALSA: deprecate MIPS AU1X00 AC97 driver
  2011-07-24 10:11 ` Manuel Lauss
@ 2011-07-24 10:11   ` Manuel Lauss
  -1 siblings, 0 replies; 16+ messages in thread
From: Manuel Lauss @ 2011-07-24 10:11 UTC (permalink / raw)
  To: alsa-devel, Mark Brown
  Cc: Liam Girdwood, Linux-MIPS, Ralf Baechle, Manuel Lauss

Now that an ASoC variant is available, tell users that this
driver is now living on borrowed time...

Signed-off-by: Manuel Lauss <manuel.lauss@googlemail.com>
---
V4: no changes.
V3: mark driver DEPRECATED instead of removing it outright.

 sound/mips/Kconfig |    5 ++++-
 1 files changed, 4 insertions(+), 1 deletions(-)

diff --git a/sound/mips/Kconfig b/sound/mips/Kconfig
index a9823fa..77dd0a1 100644
--- a/sound/mips/Kconfig
+++ b/sound/mips/Kconfig
@@ -23,12 +23,15 @@ config SND_SGI_HAL2
 
 
 config SND_AU1X00
-	tristate "Au1x00 AC97 Port Driver"
+	tristate "Au1x00 AC97 Port Driver (DEPRECATED)"
 	depends on SOC_AU1000 || SOC_AU1100 || SOC_AU1500
 	select SND_PCM
 	select SND_AC97_CODEC
 	help
 	  ALSA Sound driver for the Au1x00's AC97 port.
 
+	  Newer drivers for ASoC are available, please do not use
+	  this driver as it will be removed in the future.
+
 endif	# SND_MIPS
 
-- 
1.7.6

^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH V4 3/3] ALSA: deprecate MIPS AU1X00 AC97 driver
@ 2011-07-24 10:11   ` Manuel Lauss
  0 siblings, 0 replies; 16+ messages in thread
From: Manuel Lauss @ 2011-07-24 10:11 UTC (permalink / raw)
  To: alsa-devel; +Cc: Linux-MIPS, Manuel Lauss, Liam Girdwood, Ralf Baechle

Now that an ASoC variant is available, tell users that this
driver is now living on borrowed time...

Signed-off-by: Manuel Lauss <manuel.lauss@googlemail.com>
---
V4: no changes.
V3: mark driver DEPRECATED instead of removing it outright.

 sound/mips/Kconfig |    5 ++++-
 1 files changed, 4 insertions(+), 1 deletions(-)

diff --git a/sound/mips/Kconfig b/sound/mips/Kconfig
index a9823fa..77dd0a1 100644
--- a/sound/mips/Kconfig
+++ b/sound/mips/Kconfig
@@ -23,12 +23,15 @@ config SND_SGI_HAL2
 
 
 config SND_AU1X00
-	tristate "Au1x00 AC97 Port Driver"
+	tristate "Au1x00 AC97 Port Driver (DEPRECATED)"
 	depends on SOC_AU1000 || SOC_AU1100 || SOC_AU1500
 	select SND_PCM
 	select SND_AC97_CODEC
 	help
 	  ALSA Sound driver for the Au1x00's AC97 port.
 
+	  Newer drivers for ASoC are available, please do not use
+	  this driver as it will be removed in the future.
+
 endif	# SND_MIPS
 
-- 
1.7.6


^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH V4 1/3] ASoC: Alchemy AC97C/I2SC audio support
  2011-07-24 10:11   ` Manuel Lauss
@ 2011-07-25  9:32     ` Liam Girdwood
  -1 siblings, 0 replies; 16+ messages in thread
From: Liam Girdwood @ 2011-07-25  9:32 UTC (permalink / raw)
  To: Manuel Lauss; +Cc: alsa-devel, Mark Brown, Linux-MIPS, Ralf Baechle

On 24/07/11 11:11, Manuel Lauss wrote:
> This patch adds ASoC support for the AC97 and I2S controllers
> on the old Au1000/Au1500/Au1100 chips,
> 
> AC97 Tested on a Db1500.  I2S untested since none of the boards
> actually have an I2S codec wired up (just test pins).
> 
> Signed-off-by: Manuel Lauss <manuel.lauss@googlemail.com>
> ---
> V4: dropped hunk which removed I2S constants in au1000.h header to avoid merge
>     conflicts with other patches, use the context structure in psc.h since it
>     fits really well.
> V3: implemented feedback from Lars-Peter Clausen: src tidying, no more
>     automatic dma device registration, split off db1000 board code.
> V2: added untested I2S controller driver for completeness, removed the audio
>     defines from the au1000 header as well.
> 

Looks mostly OK, I just have some questions below:-

>  sound/soc/au1x/Kconfig  |   19 +++
>  sound/soc/au1x/Makefile |    8 +
>  sound/soc/au1x/ac97c.c  |  365 +++++++++++++++++++++++++++++++++++++++++++++
>  sound/soc/au1x/dma.c    |  374 +++++++++++++++++++++++++++++++++++++++++++++++
>  sound/soc/au1x/i2sc.c   |  342 +++++++++++++++++++++++++++++++++++++++++++
>  sound/soc/au1x/psc.h    |   19 ++-
>  6 files changed, 1118 insertions(+), 9 deletions(-)
>  create mode 100644 sound/soc/au1x/ac97c.c
>  create mode 100644 sound/soc/au1x/dma.c
>  create mode 100644 sound/soc/au1x/i2sc.c
> 
> diff --git a/sound/soc/au1x/Kconfig b/sound/soc/au1x/Kconfig
> index 4b67140..0460b42 100644
> --- a/sound/soc/au1x/Kconfig
> +++ b/sound/soc/au1x/Kconfig
> @@ -18,6 +18,25 @@ config SND_SOC_AU1XPSC_AC97
>         select SND_AC97_CODEC
>         select SND_SOC_AC97_BUS
> 
> +##
> +## Au1000/1500/1100 DMA + AC97C/I2SC
> +##
> +config SND_SOC_AU1XAUDIO
> +       tristate "SoC Audio for Au1000/Au1500/Au1100"
> +       depends on MIPS_ALCHEMY
> +       help
> +         This is a driver set for the AC97 unit and the
> +         old DMA controller as found on the Au1000/Au1500/Au1100 chips.
> +
> +config SND_SOC_AU1XAC97C
> +       tristate
> +       select AC97_BUS
> +       select SND_AC97_CODEC
> +       select SND_SOC_AC97_BUS
> +
> +config SND_SOC_AU1XI2SC
> +       tristate
> +
> 
>  ##
>  ## Boards
> diff --git a/sound/soc/au1x/Makefile b/sound/soc/au1x/Makefile
> index 1687307..ff5531e 100644
> --- a/sound/soc/au1x/Makefile
> +++ b/sound/soc/au1x/Makefile
> @@ -3,9 +3,17 @@ snd-soc-au1xpsc-dbdma-objs := dbdma2.o
>  snd-soc-au1xpsc-i2s-objs := psc-i2s.o
>  snd-soc-au1xpsc-ac97-objs := psc-ac97.o
> 
> +# Au1000/1500/1100 Audio units
> +snd-soc-au1x-dma-objs := dma.o
> +snd-soc-au1x-ac97c-objs := ac97c.o
> +snd-soc-au1x-i2sc-objs := i2sc.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
> +obj-$(CONFIG_SND_SOC_AU1XAUDIO) += snd-soc-au1x-dma.o
> +obj-$(CONFIG_SND_SOC_AU1XAC97C) += snd-soc-au1x-ac97c.o
> +obj-$(CONFIG_SND_SOC_AU1XI2SC) += snd-soc-au1x-i2sc.o
> 
>  # Boards
>  snd-soc-db1200-objs := db1200.o
> diff --git a/sound/soc/au1x/ac97c.c b/sound/soc/au1x/ac97c.c
> new file mode 100644
> index 0000000..35884ae
> --- /dev/null
> +++ b/sound/soc/au1x/ac97c.c
> @@ -0,0 +1,365 @@
> +/*
> + * Au1000/Au1500/Au1100 AC97C controller driver for ASoC
> + *
> + * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
> + *
> + * based on the old ALSA driver originally written by
> + *                     Charles Eidsness <charles@cooper-street.com>
> + */
> +
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/device.h>
> +#include <linux/delay.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.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 "psc.h"
> +
> +/* register offsets and bits */
> +#define AC97_CONFIG    0x00
> +#define AC97_STATUS    0x04
> +#define AC97_DATA      0x08
> +#define AC97_CMDRESP   0x0c
> +#define AC97_ENABLE    0x10
> +
> +#define CFG_RC(x)      (((x) & 0x3ff) << 13)   /* valid rx slots mask */
> +#define CFG_XS(x)      (((x) & 0x3ff) << 3)    /* valid tx slots mask */
> +#define CFG_SG         (1 << 2)        /* sync gate */
> +#define CFG_SN         (1 << 1)        /* sync control */
> +#define CFG_RS         (1 << 0)        /* acrst# control */
> +#define STAT_XU                (1 << 11)       /* tx underflow */
> +#define STAT_XO                (1 << 10)       /* tx overflow */
> +#define STAT_RU                (1 << 9)        /* rx underflow */
> +#define STAT_RO                (1 << 8)        /* rx overflow */
> +#define STAT_RD                (1 << 7)        /* codec ready */
> +#define STAT_CP                (1 << 6)        /* command pending */
> +#define STAT_TE                (1 << 4)        /* tx fifo empty */
> +#define STAT_TF                (1 << 3)        /* tx fifo full */
> +#define STAT_RE                (1 << 1)        /* rx fifo empty */
> +#define STAT_RF                (1 << 0)        /* rx fifo full */
> +#define CMD_SET_DATA(x)        (((x) & 0xffff) << 16)
> +#define CMD_GET_DATA(x)        ((x) & 0xffff)
> +#define CMD_READ       (1 << 7)
> +#define CMD_WRITE      (0 << 7)
> +#define CMD_IDX(x)     ((x) & 0x7f)
> +#define EN_D           (1 << 1)        /* DISable bit */
> +#define EN_CE          (1 << 0)        /* clock enable bit */
> +
> +/* how often to retry failed codec register reads/writes */
> +#define AC97_RW_RETRIES        5
> +
> +#define AC97_RATES     \
> +       SNDRV_PCM_RATE_8000_44100

Just curious, is there any reason this doesn't support 48kHz ?

> +
> +#define AC97_FMTS      \
> +       (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE)
> +
> +/* instance data. There can be only one, MacLeod!!!!, fortunately there IS only
> + * once AC97C on early Alchemy chips. The newer ones aren't so lucky.
> + */
> +static struct au1xpsc_audio_data *ac97c_workdata;
> +#define ac97_to_ctx(x)         ac97c_workdata
> +
> +static inline unsigned long RD(struct au1xpsc_audio_data *ctx, int reg)
> +{
> +       return __raw_readl(ctx->mmio + reg);
> +}
> +
> +static inline void WR(struct au1xpsc_audio_data *ctx, int reg, unsigned long v)
> +{
> +       __raw_writel(v, ctx->mmio + reg);
> +       wmb();
> +}
> +
> +static unsigned short au1xac97c_ac97_read(struct snd_ac97 *ac97,
> +                                         unsigned short r)
> +{
> +       struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
> +       unsigned int tmo, retry;
> +       unsigned long data;
> +
> +       data = ~0;
> +       retry = AC97_RW_RETRIES;
> +       do {
> +               mutex_lock(&ctx->lock);
> +
> +               tmo = 5;
> +               while ((RD(ctx, AC97_STATUS) & STAT_CP) && tmo--)
> +                       udelay(21);     /* wait an ac97 frame time */
> +               if (!tmo) {
> +                       pr_debug("ac97rd timeout #1\n");
> +                       goto next;
> +               }
> +
> +               WR(ctx, AC97_CMDRESP, CMD_IDX(r) | CMD_READ);
> +
> +               /* stupid errata: data is only valid for 21us, so
> +                * poll, Forrest, poll...
> +                */
> +               tmo = 0x10000;
> +               while ((RD(ctx, AC97_STATUS) & STAT_CP) && tmo--)
> +                       asm volatile ("nop");
> +               data = RD(ctx, AC97_CMDRESP);
> +
> +               if (!tmo)
> +                       pr_debug("ac97rd timeout #2\n");
> +
> +next:
> +               mutex_unlock(&ctx->lock);
> +       } while (--retry && !tmo);
> +
> +       pr_debug("AC97RD %04x %04lx %d\n", r, data, retry);
> +
> +       return retry ? data & 0xffff : 0xffff;
> +}
> +
> +static void au1xac97c_ac97_write(struct snd_ac97 *ac97, unsigned short r,
> +                                unsigned short v)
> +{
> +       struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
> +       unsigned int tmo, retry;
> +
> +       retry = AC97_RW_RETRIES;
> +       do {
> +               mutex_lock(&ctx->lock);
> +
> +               for (tmo = 5; (RD(ctx, AC97_STATUS) & STAT_CP) && tmo; tmo--)
> +                       udelay(21);
> +               if (!tmo) {
> +                       pr_debug("ac97wr timeout #1\n");
> +                       goto next;
> +               }
> +
> +               WR(ctx, AC97_CMDRESP, CMD_WRITE | CMD_IDX(r) | CMD_SET_DATA(v));
> +
> +               for (tmo = 10; (RD(ctx, AC97_STATUS) & STAT_CP) && tmo; tmo--)
> +                       udelay(21);
> +               if (!tmo)
> +                       pr_debug("ac97wr timeout #2\n");
> +next:
> +               mutex_unlock(&ctx->lock);
> +       } while (--retry && !tmo);
> +
> +       pr_debug("AC97WR %04x %04x %d\n", r, v, retry);
> +}
> +
> +static void au1xac97c_ac97_warm_reset(struct snd_ac97 *ac97)
> +{
> +       struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
> +
> +       WR(ctx, AC97_CONFIG, ctx->cfg | CFG_SG | CFG_SN);
> +       msleep(20);
> +       WR(ctx, AC97_CONFIG, ctx->cfg | CFG_SG);
> +       WR(ctx, AC97_CONFIG, ctx->cfg);
> +}
> +
> +static void au1xac97c_ac97_cold_reset(struct snd_ac97 *ac97)
> +{
> +       struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
> +       int i;
> +
> +       WR(ctx, AC97_CONFIG, ctx->cfg | CFG_RS);
> +       msleep(500);
> +       WR(ctx, AC97_CONFIG, ctx->cfg);
> +
> +       /* wait for codec ready */
> +       i = 50;
> +       while (((RD(ctx, AC97_STATUS) & STAT_RD) == 0) && --i)
> +               msleep(20);
> +       if (!i)
> +               printk(KERN_ERR "ac97c: codec not ready after cold reset\n");
> +}
> +
> +/* AC97 controller operations */
> +struct snd_ac97_bus_ops soc_ac97_ops = {
> +       .read           = au1xac97c_ac97_read,
> +       .write          = au1xac97c_ac97_write,
> +       .reset          = au1xac97c_ac97_cold_reset,
> +       .warm_reset     = au1xac97c_ac97_warm_reset,
> +};
> +EXPORT_SYMBOL_GPL(soc_ac97_ops);       /* globals be gone! */
> +
> +static int alchemy_ac97c_startup(struct snd_pcm_substream *substream,
> +                                struct snd_soc_dai *dai)
> +{
> +       struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
> +       snd_soc_dai_set_dma_data(dai, substream, &ctx->dmaids[0]);
> +       return 0;
> +}
> +
> +static struct snd_soc_dai_ops alchemy_ac97c_ops = {
> +       .startup                = alchemy_ac97c_startup,
> +};
> +
> +static int au1xac97c_dai_probe(struct snd_soc_dai *dai)
> +{
> +       return ac97c_workdata ? 0 : -ENODEV;
> +}
> +
> +static struct snd_soc_dai_driver au1xac97c_dai_driver = {
> +       .name                   = "alchemy-ac97c",
> +       .ac97_control           = 1,
> +       .probe                  = au1xac97c_dai_probe,
> +       .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                    = &alchemy_ac97c_ops,
> +};
> +
> +static int __devinit au1xac97c_drvprobe(struct platform_device *pdev)
> +{
> +       int ret;
> +       struct resource *r;
> +       struct au1xpsc_audio_data *ctx;
> +
> +       ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
> +       if (!ctx)
> +               return -ENOMEM;
> +
> +       mutex_init(&ctx->lock);
> +
> +       r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       if (!r) {
> +               ret = -ENODEV;
> +               goto out0;
> +       }
> +
> +       ret = -EBUSY;
> +       if (!request_mem_region(r->start, resource_size(r), pdev->name))
> +               goto out0;
> +
> +       ctx->mmio = ioremap_nocache(r->start, resource_size(r));
> +       if (!ctx->mmio)
> +               goto out1;
> +
> +       r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
> +       if (!r)
> +               goto out1;
> +       ctx->dmaids[PCM_TX] = r->start;
> +
> +       r = platform_get_resource(pdev, IORESOURCE_DMA, 1);
> +       if (!r)
> +               goto out1;
> +       ctx->dmaids[PCM_RX] = r->start;
> +
> +       /* switch it on */
> +       WR(ctx, AC97_ENABLE, EN_D | EN_CE);
> +       WR(ctx, AC97_ENABLE, EN_CE);
> +
> +       ctx->cfg = CFG_RC(3) | CFG_XS(3);
> +       WR(ctx, AC97_CONFIG, ctx->cfg);
> +
> +       platform_set_drvdata(pdev, ctx);
> +
> +       ret = snd_soc_register_dai(&pdev->dev, &au1xac97c_dai_driver);
> +       if (ret)
> +               goto out1;
> +
> +       ac97c_workdata = ctx;
> +       return 0;
> +
> +
> +       snd_soc_unregister_dai(&pdev->dev);
> +out1:
> +       release_mem_region(r->start, resource_size(r));
> +out0:
> +       kfree(ctx);
> +       return ret;
> +}
> +
> +static int __devexit au1xac97c_drvremove(struct platform_device *pdev)
> +{
> +       struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev);
> +       struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +
> +       snd_soc_unregister_dai(&pdev->dev);
> +
> +       WR(ctx, AC97_ENABLE, EN_D);     /* clock off, disable */
> +
> +       iounmap(ctx->mmio);
> +       release_mem_region(r->start, resource_size(r));
> +       kfree(ctx);
> +
> +       ac97c_workdata = NULL;  /* MDEV */
> +
> +       return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int au1xac97c_drvsuspend(struct device *dev)
> +{
> +       struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev);
> +
> +       WR(ctx, AC97_ENABLE, EN_D);     /* clock off, disable */
> +
> +       return 0;
> +}
> +
> +static int au1xac97c_drvresume(struct device *dev)
> +{
> +       struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev);
> +
> +       WR(ctx, AC97_ENABLE, EN_D | EN_CE);
> +       WR(ctx, AC97_ENABLE, EN_CE);
> +       WR(ctx, AC97_CONFIG, ctx->cfg);
> +
> +       return 0;
> +}
> +
> +static const struct dev_pm_ops au1xpscac97_pmops = {
> +       .suspend        = au1xac97c_drvsuspend,
> +       .resume         = au1xac97c_drvresume,
> +};
> +
> +#define AU1XPSCAC97_PMOPS (&au1xpscac97_pmops)
> +
> +#else
> +
> +#define AU1XPSCAC97_PMOPS NULL
> +
> +#endif
> +
> +static struct platform_driver au1xac97c_driver = {
> +       .driver = {
> +               .name   = "alchemy-ac97c",
> +               .owner  = THIS_MODULE,
> +               .pm     = AU1XPSCAC97_PMOPS,
> +       },
> +       .probe          = au1xac97c_drvprobe,
> +       .remove         = __devexit_p(au1xac97c_drvremove),
> +};
> +
> +static int __init au1xac97c_load(void)
> +{
> +       ac97c_workdata = NULL;
> +       return platform_driver_register(&au1xac97c_driver);
> +}
> +
> +static void __exit au1xac97c_unload(void)
> +{
> +       platform_driver_unregister(&au1xac97c_driver);
> +}
> +
> +module_init(au1xac97c_load);
> +module_exit(au1xac97c_unload);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Au1000/1500/1100 AC97C ASoC driver");
> +MODULE_AUTHOR("Manuel Lauss");
> diff --git a/sound/soc/au1x/dma.c b/sound/soc/au1x/dma.c
> new file mode 100644
> index 0000000..20fedbd
> --- /dev/null
> +++ b/sound/soc/au1x/dma.c
> @@ -0,0 +1,374 @@
> +/*
> + * Au1000/Au1500/Au1100 Audio DMA support.
> + *
> + * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
> + *
> + * copied almost verbatim from the old ALSA driver, written by
> + *                     Charles Eidsness <charles@cooper-street.com>
> + */
> +
> +#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/au1000_dma.h>
> +
> +#include "psc.h"
> +
> +#define ALCHEMY_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)
> +
> +struct pcm_period {
> +       u32 start;
> +       u32 relative_end;       /* relative to start of buffer */
> +       struct pcm_period *next;
> +};
> +
> +struct audio_stream {
> +       struct snd_pcm_substream *substream;
> +       int dma;
> +       struct pcm_period *buffer;
> +       unsigned int period_size;
> +       unsigned int periods;
> +};
> +
> +struct alchemy_pcm_ctx {
> +       struct audio_stream stream[2];  /* playback & capture */
> +};
> +
> +static void au1000_release_dma_link(struct audio_stream *stream)
> +{
> +       struct pcm_period *pointer;
> +       struct pcm_period *pointer_next;
> +
> +       stream->period_size = 0;
> +       stream->periods = 0;
> +       pointer = stream->buffer;
> +       if (!pointer)
> +               return;
> +       do {
> +               pointer_next = pointer->next;
> +               kfree(pointer);
> +               pointer = pointer_next;
> +       } while (pointer != stream->buffer);
> +       stream->buffer = NULL;
> +}
> +
> +static int au1000_setup_dma_link(struct audio_stream *stream,
> +                                unsigned int period_bytes,
> +                                unsigned int periods)
> +{
> +       struct snd_pcm_substream *substream = stream->substream;
> +       struct snd_pcm_runtime *runtime = substream->runtime;
> +       struct pcm_period *pointer;
> +       unsigned long dma_start;
> +       int i;
> +
> +       dma_start = virt_to_phys(runtime->dma_area);
> +
> +       if (stream->period_size == period_bytes &&
> +           stream->periods == periods)
> +               return 0; /* not changed */
> +
> +       au1000_release_dma_link(stream);
> +
> +       stream->period_size = period_bytes;
> +       stream->periods = periods;
> +
> +       stream->buffer = kmalloc(sizeof(struct pcm_period), GFP_KERNEL);
> +       if (!stream->buffer)
> +               return -ENOMEM;
> +       pointer = stream->buffer;
> +       for (i = 0; i < periods; i++) {
> +               pointer->start = (u32)(dma_start + (i * period_bytes));
> +               pointer->relative_end = (u32) (((i+1) * period_bytes) - 0x1);
> +               if (i < periods - 1) {
> +                       pointer->next = kmalloc(sizeof(struct pcm_period),
> +                                               GFP_KERNEL);
> +                       if (!pointer->next) {
> +                               au1000_release_dma_link(stream);
> +                               return -ENOMEM;
> +                       }
> +                       pointer = pointer->next;
> +               }
> +       }
> +       pointer->next = stream->buffer;
> +       return 0;
> +}
> +
> +static void au1000_dma_stop(struct audio_stream *stream)
> +{
> +       if (stream->buffer)
> +               disable_dma(stream->dma);
> +}
> +
> +static void au1000_dma_start(struct audio_stream *stream)
> +{
> +       if (!stream->buffer)
> +               return;
> +
> +       init_dma(stream->dma);
> +       if (get_dma_active_buffer(stream->dma) == 0) {
> +               clear_dma_done0(stream->dma);
> +               set_dma_addr0(stream->dma, stream->buffer->start);
> +               set_dma_count0(stream->dma, stream->period_size >> 1);
> +               set_dma_addr1(stream->dma, stream->buffer->next->start);
> +               set_dma_count1(stream->dma, stream->period_size >> 1);
> +       } else {
> +               clear_dma_done1(stream->dma);
> +               set_dma_addr1(stream->dma, stream->buffer->start);
> +               set_dma_count1(stream->dma, stream->period_size >> 1);
> +               set_dma_addr0(stream->dma, stream->buffer->next->start);
> +               set_dma_count0(stream->dma, stream->period_size >> 1);
> +       }
> +       enable_dma_buffers(stream->dma);
> +       start_dma(stream->dma);
> +}
> +
> +static irqreturn_t au1000_dma_interrupt(int irq, void *ptr)
> +{
> +       struct audio_stream *stream = (struct audio_stream *)ptr;
> +       struct snd_pcm_substream *substream = stream->substream;
> +
> +       switch (get_dma_buffer_done(stream->dma)) {
> +       case DMA_D0:
> +               stream->buffer = stream->buffer->next;
> +               clear_dma_done0(stream->dma);
> +               set_dma_addr0(stream->dma, stream->buffer->next->start);
> +               set_dma_count0(stream->dma, stream->period_size >> 1);
> +               enable_dma_buffer0(stream->dma);
> +               break;
> +       case DMA_D1:
> +               stream->buffer = stream->buffer->next;
> +               clear_dma_done1(stream->dma);
> +               set_dma_addr1(stream->dma, stream->buffer->next->start);
> +               set_dma_count1(stream->dma, stream->period_size >> 1);
> +               enable_dma_buffer1(stream->dma);
> +               break;
> +       case (DMA_D0 | DMA_D1):
> +               pr_debug("DMA %d missed interrupt.\n", stream->dma);
> +               au1000_dma_stop(stream);
> +               au1000_dma_start(stream);
> +               break;
> +       case (~DMA_D0 & ~DMA_D1):
> +               pr_debug("DMA %d empty irq.\n", stream->dma);
> +       }
> +       snd_pcm_period_elapsed(substream);
> +       return IRQ_HANDLED;
> +}
> +
> +static const struct snd_pcm_hardware alchemy_pcm_hardware = {
> +       .info             = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
> +                           SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BATCH,
> +       .formats          = ALCHEMY_PCM_FMTS,
> +       .rates            = SNDRV_PCM_RATE_8000_192000,
> +       .rate_min         = SNDRV_PCM_RATE_8000,
> +       .rate_max         = SNDRV_PCM_RATE_192000,
> +       .channels_min     = 2,
> +       .channels_max     = 2,
> +       .period_bytes_min = 1024,
> +       .period_bytes_max = 16 * 1024 - 1,
> +       .periods_min      = 4,
> +       .periods_max      = 255,
> +       .buffer_bytes_max = 128 * 1024,
> +       .fifo_size        = 16,
> +};
> +
> +static inline struct alchemy_pcm_ctx *ss_to_ctx(struct snd_pcm_substream *ss)
> +{
> +       struct snd_soc_pcm_runtime *rtd = ss->private_data;
> +       return snd_soc_platform_get_drvdata(rtd->platform);
> +}
> +
> +static inline struct audio_stream *ss_to_as(struct snd_pcm_substream *ss)
> +{
> +       struct alchemy_pcm_ctx *ctx = ss_to_ctx(ss);
> +       return &(ctx->stream[SUBSTREAM_TYPE(ss)]);
> +}
> +
> +static int alchemy_pcm_open(struct snd_pcm_substream *substream)
> +{
> +       struct alchemy_pcm_ctx *ctx = ss_to_ctx(substream);
> +       struct snd_soc_pcm_runtime *rtd = substream->private_data;
> +       int stype = SUBSTREAM_TYPE(substream);
> +       int *dmaids;
> +       char *name;
> +
> +       dmaids = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
> +       if (!dmaids)
> +               return -ENODEV; /* whoa, has ordering changed? */
> +
> +       /* DMA setup */
> +       name = (stype == PCM_TX) ? "audio-tx" : "audio-rx";
> +       ctx->stream[stype].dma = request_au1000_dma(dmaids[stype], name,
> +                                       au1000_dma_interrupt, IRQF_DISABLED,
> +                                       &ctx->stream[stype]);
> +       set_dma_mode(ctx->stream[stype].dma,
> +                    get_dma_mode(ctx->stream[stype].dma) & ~DMA_NC);
> +
> +       ctx->stream[stype].substream = substream;
> +       ctx->stream[stype].buffer = NULL;
> +       snd_soc_set_runtime_hwparams(substream, &alchemy_pcm_hardware);
> +
> +       return 0;
> +}
> +
> +static int alchemy_pcm_close(struct snd_pcm_substream *substream)
> +{
> +       struct alchemy_pcm_ctx *ctx = ss_to_ctx(substream);
> +       int stype = SUBSTREAM_TYPE(substream);
> +
> +       ctx->stream[SUBSTREAM_TYPE(substream)].substream = NULL;
> +       free_au1000_dma(ctx->stream[stype].dma);
> +
> +       return 0;
> +}
> +
> +static int alchemy_pcm_hw_params(struct snd_pcm_substream *substream,
> +                                struct snd_pcm_hw_params *hw_params)
> +{
> +       struct audio_stream *stream = ss_to_as(substream);
> +       int err;
> +
> +       err = snd_pcm_lib_malloc_pages(substream,
> +                                      params_buffer_bytes(hw_params));
> +       if (err < 0)
> +               return err;
> +       return au1000_setup_dma_link(stream,
> +                                    params_period_bytes(hw_params),
> +                                    params_periods(hw_params));


What happens if this fails ? You already have malloc'ed some pages.

> +}
> +
> +static int alchemy_pcm_hw_free(struct snd_pcm_substream *substream)
> +{
> +       struct audio_stream *stream = ss_to_as(substream);
> +       au1000_release_dma_link(stream);
> +       return snd_pcm_lib_free_pages(substream);
> +}
> +
> +static int alchemy_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
> +{
> +       struct audio_stream *stream = ss_to_as(substream);
> +       int err = 0;
> +
> +       switch (cmd) {
> +       case SNDRV_PCM_TRIGGER_START:
> +               au1000_dma_start(stream);
> +               break;
> +       case SNDRV_PCM_TRIGGER_STOP:
> +               au1000_dma_stop(stream);
> +               break;
> +       default:
> +               err = -EINVAL;
> +               break;
> +       }
> +       return err;
> +}
> +
> +static snd_pcm_uframes_t alchemy_pcm_pointer(struct snd_pcm_substream *ss)
> +{
> +       struct audio_stream *stream = ss_to_as(ss);
> +       long location;
> +
> +       location = get_dma_residue(stream->dma);
> +       location = stream->buffer->relative_end - location;
> +       if (location == -1)
> +               location = 0;
> +       return bytes_to_frames(ss->runtime, location);
> +}
> +
> +static struct snd_pcm_ops alchemy_pcm_ops = {
> +       .open                   = alchemy_pcm_open,
> +       .close                  = alchemy_pcm_close,
> +       .ioctl                  = snd_pcm_lib_ioctl,
> +       .hw_params              = alchemy_pcm_hw_params,
> +       .hw_free                = alchemy_pcm_hw_free,
> +       .trigger                = alchemy_pcm_trigger,
> +       .pointer                = alchemy_pcm_pointer,
> +};
> +
> +static void alchemy_pcm_free_dma_buffers(struct snd_pcm *pcm)
> +{
> +       snd_pcm_lib_preallocate_free_for_all(pcm);
> +}
> +
> +static int alchemy_pcm_new(struct snd_card *card,
> +                          struct snd_soc_dai *dai,
> +                          struct snd_pcm *pcm)

This API call has been updated to only pass the rtd *

> +{
> +       snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
> +               snd_dma_continuous_data(GFP_KERNEL), 65536, (4096 * 1024) - 1);
> +
> +       return 0;
> +}
> +
> +struct snd_soc_platform_driver alchemy_pcm_soc_platform = {
> +       .ops            = &alchemy_pcm_ops,
> +       .pcm_new        = alchemy_pcm_new,
> +       .pcm_free       = alchemy_pcm_free_dma_buffers,
> +};
> +
> +static int __devinit alchemy_pcm_drvprobe(struct platform_device *pdev)
> +{
> +       struct alchemy_pcm_ctx *ctx;
> +       int ret;
> +
> +       ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
> +       if (!ctx)
> +               return -ENOMEM;
> +
> +       platform_set_drvdata(pdev, ctx);
> +
> +       ret = snd_soc_register_platform(&pdev->dev, &alchemy_pcm_soc_platform);
> +       if (ret)
> +               kfree(ctx);
> +
> +       return ret;
> +}
> +
> +static int __devexit alchemy_pcm_drvremove(struct platform_device *pdev)
> +{
> +       struct alchemy_pcm_ctx *ctx = platform_get_drvdata(pdev);
> +
> +       snd_soc_unregister_platform(&pdev->dev);
> +       kfree(ctx);
> +
> +       return 0;
> +}
> +
> +static struct platform_driver alchemy_pcmdma_driver = {
> +       .driver = {
> +               .name   = "alchemy-pcm-dma",
> +               .owner  = THIS_MODULE,
> +       },
> +       .probe          = alchemy_pcm_drvprobe,
> +       .remove         = __devexit_p(alchemy_pcm_drvremove),
> +};
> +
> +static int __init alchemy_pcmdma_load(void)
> +{
> +       return platform_driver_register(&alchemy_pcmdma_driver);
> +}
> +
> +static void __exit alchemy_pcmdma_unload(void)
> +{
> +       platform_driver_unregister(&alchemy_pcmdma_driver);
> +}
> +
> +module_init(alchemy_pcmdma_load);
> +module_exit(alchemy_pcmdma_unload);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Au1000/Au1500/Au1100 Audio DMA driver");
> +MODULE_AUTHOR("Manuel Lauss");
> diff --git a/sound/soc/au1x/i2sc.c b/sound/soc/au1x/i2sc.c
> new file mode 100644
> index 0000000..e3964a2
> --- /dev/null
> +++ b/sound/soc/au1x/i2sc.c
> @@ -0,0 +1,342 @@
> +/*
> + * Au1000/Au1500/Au1100 I2S controller driver for ASoC
> + *
> + * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
> + *
> + * Note: clock supplied to the I2S controller must be 256x samplerate.
> + */
> +
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/slab.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 "psc.h"
> +
> +#define I2S_RXTX       0x00
> +#define I2S_CFG                0x04
> +#define I2S_ENABLE     0x08
> +
> +#define CFG_XU         (1 << 25)       /* tx underflow */
> +#define CFG_XO         (1 << 24)
> +#define CFG_RU         (1 << 23)
> +#define CFG_RO         (1 << 22)
> +#define CFG_TR         (1 << 21)
> +#define CFG_TE         (1 << 20)
> +#define CFG_TF         (1 << 19)
> +#define CFG_RR         (1 << 18)
> +#define CFG_RF         (1 << 17)
> +#define CFG_ICK                (1 << 12)       /* clock invert */
> +#define CFG_PD         (1 << 11)       /* set to make I2SDIO INPUT */
> +#define CFG_LB         (1 << 10)       /* loopback */
> +#define CFG_IC         (1 << 9)        /* word select invert */
> +#define CFG_FM_I2S     (0 << 7)        /* I2S format */
> +#define CFG_FM_LJ      (1 << 7)        /* left-justified */
> +#define CFG_FM_RJ      (2 << 7)        /* right-justified */
> +#define CFG_FM_MASK    (3 << 7)
> +#define CFG_TN         (1 << 6)        /* tx fifo en */
> +#define CFG_RN         (1 << 5)        /* rx fifo en */
> +#define CFG_SZ_8       (0x08)
> +#define CFG_SZ_16      (0x10)
> +#define CFG_SZ_18      (0x12)
> +#define CFG_SZ_20      (0x14)
> +#define CFG_SZ_24      (0x18)
> +#define CFG_SZ_MASK    (0x1f)
> +#define EN_D           (1 << 1)        /* DISable */
> +#define EN_CE          (1 << 0)        /* clock enable */
> +
> +/* only limited by clock generator and board design */
> +#define AU1XI2SC_RATES \
> +       SNDRV_PCM_RATE_CONTINUOUS
> +
> +#define AU1XI2SC_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_S18_3LE | SNDRV_PCM_FMTBIT_U18_3LE |   \
> +       SNDRV_PCM_FMTBIT_S18_3BE | SNDRV_PCM_FMTBIT_U18_3BE |   \
> +       SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE |   \
> +       SNDRV_PCM_FMTBIT_S20_3BE | SNDRV_PCM_FMTBIT_U20_3BE |   \
> +       SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE |     \
> +       SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE |     \
> +       0)
> +
> +static inline unsigned long RD(struct au1xpsc_audio_data *ctx, int reg)
> +{
> +       return __raw_readl(ctx->mmio + reg);
> +}
> +
> +static inline void WR(struct au1xpsc_audio_data *ctx, int reg, unsigned long v)
> +{
> +       __raw_writel(v, ctx->mmio + reg);
> +       wmb();
> +}

Btw, just wondering if arch/mips already supplies a suitable RD()/WR() for you ?

> +
> +static int au1xi2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
> +{
> +       struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(cpu_dai);
> +       unsigned long c;
> +       int ret;
> +
> +       ret = -EINVAL;
> +       c = ctx->cfg;
> +
> +       c &= ~CFG_FM_MASK;
> +       switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
> +       case SND_SOC_DAIFMT_I2S:
> +               c |= CFG_FM_I2S;
> +               break;
> +       case SND_SOC_DAIFMT_MSB:
> +               c |= CFG_FM_RJ;
> +               break;
> +       case SND_SOC_DAIFMT_LSB:
> +               c |= CFG_FM_LJ;
> +               break;
> +       default:
> +               goto out;
> +       }
> +
> +       c &= ~(CFG_IC | CFG_ICK);               /* IB-IF */
> +       switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
> +       case SND_SOC_DAIFMT_NB_NF:
> +               c |= CFG_IC | CFG_ICK;
> +               break;
> +       case SND_SOC_DAIFMT_NB_IF:
> +               c |= CFG_IC;
> +               break;
> +       case SND_SOC_DAIFMT_IB_NF:
> +               c |= CFG_ICK;
> +               break;
> +       case SND_SOC_DAIFMT_IB_IF:
> +               break;
> +       default:
> +               goto out;
> +       }
> +
> +       /* I2S controller only supports master */
> +       switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
> +       case SND_SOC_DAIFMT_CBS_CFS:    /* CODEC slave */
> +               break;
> +       default:
> +               goto out;
> +       }
> +
> +       ret = 0;
> +       ctx->cfg = c;
> +out:
> +       return ret;
> +}
> +
> +static int au1xi2s_trigger(struct snd_pcm_substream *substream,
> +                          int cmd, struct snd_soc_dai *dai)
> +{
> +       struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
> +       int stype = SUBSTREAM_TYPE(substream);
> +
> +       switch (cmd) {
> +       case SNDRV_PCM_TRIGGER_START:
> +       case SNDRV_PCM_TRIGGER_RESUME:
> +               /* power up */
> +               WR(ctx, I2S_ENABLE, EN_D | EN_CE);
> +               WR(ctx, I2S_ENABLE, EN_CE);
> +               ctx->cfg |= (stype == PCM_TX) ? CFG_TN : CFG_RN;
> +               WR(ctx, I2S_CFG, ctx->cfg);
> +               break;
> +       case SNDRV_PCM_TRIGGER_STOP:
> +       case SNDRV_PCM_TRIGGER_SUSPEND:
> +               ctx->cfg &= ~((stype == PCM_TX) ? CFG_TN : CFG_RN);
> +               WR(ctx, I2S_CFG, ctx->cfg);
> +               WR(ctx, I2S_ENABLE, EN_D);              /* power off */
> +               break;
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       return 0;
> +}
> +
> +static unsigned long msbits_to_reg(int msbits)
> +{
> +       switch (msbits) {
> +       case 8:  return CFG_SZ_8;
> +       case 16: return CFG_SZ_16;
> +       case 18: return CFG_SZ_18;
> +       case 20: return CFG_SZ_20;
> +       case 24: return CFG_SZ_24;

It's best to format all the switch statements consistently throughout your code.

> +       }
> +       return 0;
> +}
> +
> +static int au1xi2s_hw_params(struct snd_pcm_substream *substream,
> +                            struct snd_pcm_hw_params *params,
> +                            struct snd_soc_dai *dai)
> +{
> +       struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
> +       unsigned long v;
> +
> +       v = msbits_to_reg(params->msbits);
> +       if (!v)
> +               return -EINVAL;
> +
> +       ctx->cfg &= ~CFG_SZ_MASK;
> +       ctx->cfg |= v;
> +       return 0;
> +}
> +
> +static int au1xi2s_startup(struct snd_pcm_substream *substream,
> +                          struct snd_soc_dai *dai)
> +{
> +       struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
> +       snd_soc_dai_set_dma_data(dai, substream, &ctx->dmaids[0]);
> +       return 0;
> +}
> +
> +static const struct snd_soc_dai_ops au1xi2s_dai_ops = {
> +       .startup        = au1xi2s_startup,
> +       .trigger        = au1xi2s_trigger,
> +       .hw_params      = au1xi2s_hw_params,
> +       .set_fmt        = au1xi2s_set_fmt,
> +};
> +
> +static struct snd_soc_dai_driver au1xi2s_dai_driver = {
> +       .symmetric_rates        = 1,
> +       .playback = {
> +               .rates          = AU1XI2SC_RATES,
> +               .formats        = AU1XI2SC_FMTS,
> +               .channels_min   = 2,
> +               .channels_max   = 2,
> +       },
> +       .capture = {
> +               .rates          = AU1XI2SC_RATES,
> +               .formats        = AU1XI2SC_FMTS,
> +               .channels_min   = 2,
> +               .channels_max   = 2,
> +       },
> +       .ops = &au1xi2s_dai_ops,
> +};
> +
> +static int __devinit au1xi2s_drvprobe(struct platform_device *pdev)
> +{
> +       int ret;
> +       struct resource *r;
> +       struct au1xpsc_audio_data *ctx;
> +
> +       ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
> +       if (!ctx)
> +               return -ENOMEM;
> +
> +       r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       if (!r) {
> +               ret = -ENODEV;
> +               goto out0;
> +       }
> +
> +       ret = -EBUSY;
> +       if (!request_mem_region(r->start, resource_size(r), pdev->name))
> +               goto out0;
> +
> +       ctx->mmio = ioremap_nocache(r->start, resource_size(r));
> +       if (!ctx->mmio)
> +               goto out1;
> +
> +       r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
> +       if (!r)
> +               goto out1;
> +       ctx->dmaids[PCM_TX] = r->start;
> +
> +       r = platform_get_resource(pdev, IORESOURCE_DMA, 1);
> +       if (!r)
> +               goto out1;
> +       ctx->dmaids[PCM_RX] = r->start;
> +
> +       platform_set_drvdata(pdev, ctx);
> +
> +       ret = snd_soc_register_dai(&pdev->dev, &au1xi2s_dai_driver);
> +       if (ret)
> +               goto out1;
> +
> +       return 0;
> +
> +       snd_soc_unregister_dai(&pdev->dev);
> +out1:
> +       release_mem_region(r->start, resource_size(r));
> +out0:
> +       kfree(ctx);
> +       return ret;
> +}
> +
> +static int __devexit au1xi2s_drvremove(struct platform_device *pdev)
> +{
> +       struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev);
> +       struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +
> +       snd_soc_unregister_dai(&pdev->dev);
> +
> +       WR(ctx, I2S_ENABLE, EN_D);      /* clock off, disable */
> +
> +       iounmap(ctx->mmio);
> +       release_mem_region(r->start, resource_size(r));
> +       kfree(ctx);
> +
> +       return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int au1xi2s_drvsuspend(struct device *dev)
> +{
> +       struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev);
> +
> +       WR(ctx, I2S_ENABLE, EN_D);      /* clock off, disable */
> +
> +       return 0;
> +}
> +
> +static int au1xi2s_drvresume(struct device *dev)
> +{

Should we not enalbe the clock here (i.e. in order to balance the clock off in suspend) ?

> +       return 0;
> +}
> +
> +static const struct dev_pm_ops au1xi2sc_pmops = {
> +       .suspend        = au1xi2s_drvsuspend,
> +       .resume         = au1xi2s_drvresume,
> +};
> +
> +#define AU1XI2SC_PMOPS (&au1xi2sc_pmops)
> +
> +#else
> +
> +#define AU1XI2SC_PMOPS NULL
> +
> +#endif
> +
> +static struct platform_driver au1xi2s_driver = {
> +       .driver = {
> +               .name   = "alchemy-i2sc",
> +               .owner  = THIS_MODULE,
> +               .pm     = AU1XI2SC_PMOPS,
> +       },
> +       .probe          = au1xi2s_drvprobe,
> +       .remove         = __devexit_p(au1xi2s_drvremove),
> +};
> +
> +static int __init au1xi2s_load(void)
> +{
> +       return platform_driver_register(&au1xi2s_driver);
> +}
> +
> +static void __exit au1xi2s_unload(void)
> +{
> +       platform_driver_unregister(&au1xi2s_driver);
> +}
> +
> +module_init(au1xi2s_load);
> +module_exit(au1xi2s_unload);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Au1000/1500/1100 I2S ASoC driver");
> +MODULE_AUTHOR("Manuel Lauss");
> diff --git a/sound/soc/au1x/psc.h b/sound/soc/au1x/psc.h
> index b30eadd..c59b9e5 100644
> --- a/sound/soc/au1x/psc.h
> +++ b/sound/soc/au1x/psc.h
> @@ -1,7 +1,7 @@
>  /*
> - * Au12x0/Au1550 PSC ALSA ASoC audio support.
> + * Alchemy ALSA ASoC audio support.
>   *
> - * (c) 2007-2008 MSC Vertriebsges.m.b.H.,
> + * (c) 2007-2011 MSC Vertriebsges.m.b.H.,
>   *     Manuel Lauss <manuel.lauss@gmail.com>
>   *
>   * This program is free software; you can redistribute it and/or modify
> @@ -13,7 +13,13 @@
>  #ifndef _AU1X_PCM_H
>  #define _AU1X_PCM_H
> 
> -/* DBDMA helpers */
> +#define PCM_TX 0
> +#define PCM_RX 1

Is there any need for these macros, SNDRV_PCM_STREAM_PLAYBACK and SNDRV_PCMP_STREAM_CAPTURE should be used for this type of logic.

> +
> +#define SUBSTREAM_TYPE(substream) \
> +       ((substream)->stream == SNDRV_PCM_STREAM_PLAYBACK ? PCM_TX : PCM_RX)
> +
> +/* PSC/DBDMA helpers */
>  extern struct platform_device *au1xpsc_pcm_add(struct platform_device *pdev);
>  extern void au1xpsc_pcm_destroy(struct platform_device *dmapd);
> 
> @@ -27,15 +33,10 @@ struct au1xpsc_audio_data {
> 
>         unsigned long pm[2];
>         struct mutex lock;
> +       int dmaids[2];
>         struct platform_device *dmapd;
>  };
> 
> -#define PCM_TX 0
> -#define PCM_RX 1
> -
> -#define SUBSTREAM_TYPE(substream) \
> -       ((substream)->stream == SNDRV_PCM_STREAM_PLAYBACK ? PCM_TX : PCM_RX)
> -
>  /* 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)
> --
> 1.7.6
> 

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH V4 1/3] ASoC: Alchemy AC97C/I2SC audio support
@ 2011-07-25  9:32     ` Liam Girdwood
  0 siblings, 0 replies; 16+ messages in thread
From: Liam Girdwood @ 2011-07-25  9:32 UTC (permalink / raw)
  To: alsa-devel; +Cc: Linux-MIPS, Mark Brown, alsa-devel, Ralf Baechle

On 24/07/11 11:11, Manuel Lauss wrote:
> This patch adds ASoC support for the AC97 and I2S controllers
> on the old Au1000/Au1500/Au1100 chips,
> 
> AC97 Tested on a Db1500.  I2S untested since none of the boards
> actually have an I2S codec wired up (just test pins).
> 
> Signed-off-by: Manuel Lauss <manuel.lauss@googlemail.com>
> ---
> V4: dropped hunk which removed I2S constants in au1000.h header to avoid merge
>     conflicts with other patches, use the context structure in psc.h since it
>     fits really well.
> V3: implemented feedback from Lars-Peter Clausen: src tidying, no more
>     automatic dma device registration, split off db1000 board code.
> V2: added untested I2S controller driver for completeness, removed the audio
>     defines from the au1000 header as well.
> 

Looks mostly OK, I just have some questions below:-

>  sound/soc/au1x/Kconfig  |   19 +++
>  sound/soc/au1x/Makefile |    8 +
>  sound/soc/au1x/ac97c.c  |  365 +++++++++++++++++++++++++++++++++++++++++++++
>  sound/soc/au1x/dma.c    |  374 +++++++++++++++++++++++++++++++++++++++++++++++
>  sound/soc/au1x/i2sc.c   |  342 +++++++++++++++++++++++++++++++++++++++++++
>  sound/soc/au1x/psc.h    |   19 ++-
>  6 files changed, 1118 insertions(+), 9 deletions(-)
>  create mode 100644 sound/soc/au1x/ac97c.c
>  create mode 100644 sound/soc/au1x/dma.c
>  create mode 100644 sound/soc/au1x/i2sc.c
> 
> diff --git a/sound/soc/au1x/Kconfig b/sound/soc/au1x/Kconfig
> index 4b67140..0460b42 100644
> --- a/sound/soc/au1x/Kconfig
> +++ b/sound/soc/au1x/Kconfig
> @@ -18,6 +18,25 @@ config SND_SOC_AU1XPSC_AC97
>         select SND_AC97_CODEC
>         select SND_SOC_AC97_BUS
> 
> +##
> +## Au1000/1500/1100 DMA + AC97C/I2SC
> +##
> +config SND_SOC_AU1XAUDIO
> +       tristate "SoC Audio for Au1000/Au1500/Au1100"
> +       depends on MIPS_ALCHEMY
> +       help
> +         This is a driver set for the AC97 unit and the
> +         old DMA controller as found on the Au1000/Au1500/Au1100 chips.
> +
> +config SND_SOC_AU1XAC97C
> +       tristate
> +       select AC97_BUS
> +       select SND_AC97_CODEC
> +       select SND_SOC_AC97_BUS
> +
> +config SND_SOC_AU1XI2SC
> +       tristate
> +
> 
>  ##
>  ## Boards
> diff --git a/sound/soc/au1x/Makefile b/sound/soc/au1x/Makefile
> index 1687307..ff5531e 100644
> --- a/sound/soc/au1x/Makefile
> +++ b/sound/soc/au1x/Makefile
> @@ -3,9 +3,17 @@ snd-soc-au1xpsc-dbdma-objs := dbdma2.o
>  snd-soc-au1xpsc-i2s-objs := psc-i2s.o
>  snd-soc-au1xpsc-ac97-objs := psc-ac97.o
> 
> +# Au1000/1500/1100 Audio units
> +snd-soc-au1x-dma-objs := dma.o
> +snd-soc-au1x-ac97c-objs := ac97c.o
> +snd-soc-au1x-i2sc-objs := i2sc.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
> +obj-$(CONFIG_SND_SOC_AU1XAUDIO) += snd-soc-au1x-dma.o
> +obj-$(CONFIG_SND_SOC_AU1XAC97C) += snd-soc-au1x-ac97c.o
> +obj-$(CONFIG_SND_SOC_AU1XI2SC) += snd-soc-au1x-i2sc.o
> 
>  # Boards
>  snd-soc-db1200-objs := db1200.o
> diff --git a/sound/soc/au1x/ac97c.c b/sound/soc/au1x/ac97c.c
> new file mode 100644
> index 0000000..35884ae
> --- /dev/null
> +++ b/sound/soc/au1x/ac97c.c
> @@ -0,0 +1,365 @@
> +/*
> + * Au1000/Au1500/Au1100 AC97C controller driver for ASoC
> + *
> + * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
> + *
> + * based on the old ALSA driver originally written by
> + *                     Charles Eidsness <charles@cooper-street.com>
> + */
> +
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <linux/device.h>
> +#include <linux/delay.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.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 "psc.h"
> +
> +/* register offsets and bits */
> +#define AC97_CONFIG    0x00
> +#define AC97_STATUS    0x04
> +#define AC97_DATA      0x08
> +#define AC97_CMDRESP   0x0c
> +#define AC97_ENABLE    0x10
> +
> +#define CFG_RC(x)      (((x) & 0x3ff) << 13)   /* valid rx slots mask */
> +#define CFG_XS(x)      (((x) & 0x3ff) << 3)    /* valid tx slots mask */
> +#define CFG_SG         (1 << 2)        /* sync gate */
> +#define CFG_SN         (1 << 1)        /* sync control */
> +#define CFG_RS         (1 << 0)        /* acrst# control */
> +#define STAT_XU                (1 << 11)       /* tx underflow */
> +#define STAT_XO                (1 << 10)       /* tx overflow */
> +#define STAT_RU                (1 << 9)        /* rx underflow */
> +#define STAT_RO                (1 << 8)        /* rx overflow */
> +#define STAT_RD                (1 << 7)        /* codec ready */
> +#define STAT_CP                (1 << 6)        /* command pending */
> +#define STAT_TE                (1 << 4)        /* tx fifo empty */
> +#define STAT_TF                (1 << 3)        /* tx fifo full */
> +#define STAT_RE                (1 << 1)        /* rx fifo empty */
> +#define STAT_RF                (1 << 0)        /* rx fifo full */
> +#define CMD_SET_DATA(x)        (((x) & 0xffff) << 16)
> +#define CMD_GET_DATA(x)        ((x) & 0xffff)
> +#define CMD_READ       (1 << 7)
> +#define CMD_WRITE      (0 << 7)
> +#define CMD_IDX(x)     ((x) & 0x7f)
> +#define EN_D           (1 << 1)        /* DISable bit */
> +#define EN_CE          (1 << 0)        /* clock enable bit */
> +
> +/* how often to retry failed codec register reads/writes */
> +#define AC97_RW_RETRIES        5
> +
> +#define AC97_RATES     \
> +       SNDRV_PCM_RATE_8000_44100

Just curious, is there any reason this doesn't support 48kHz ?

> +
> +#define AC97_FMTS      \
> +       (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE)
> +
> +/* instance data. There can be only one, MacLeod!!!!, fortunately there IS only
> + * once AC97C on early Alchemy chips. The newer ones aren't so lucky.
> + */
> +static struct au1xpsc_audio_data *ac97c_workdata;
> +#define ac97_to_ctx(x)         ac97c_workdata
> +
> +static inline unsigned long RD(struct au1xpsc_audio_data *ctx, int reg)
> +{
> +       return __raw_readl(ctx->mmio + reg);
> +}
> +
> +static inline void WR(struct au1xpsc_audio_data *ctx, int reg, unsigned long v)
> +{
> +       __raw_writel(v, ctx->mmio + reg);
> +       wmb();
> +}
> +
> +static unsigned short au1xac97c_ac97_read(struct snd_ac97 *ac97,
> +                                         unsigned short r)
> +{
> +       struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
> +       unsigned int tmo, retry;
> +       unsigned long data;
> +
> +       data = ~0;
> +       retry = AC97_RW_RETRIES;
> +       do {
> +               mutex_lock(&ctx->lock);
> +
> +               tmo = 5;
> +               while ((RD(ctx, AC97_STATUS) & STAT_CP) && tmo--)
> +                       udelay(21);     /* wait an ac97 frame time */
> +               if (!tmo) {
> +                       pr_debug("ac97rd timeout #1\n");
> +                       goto next;
> +               }
> +
> +               WR(ctx, AC97_CMDRESP, CMD_IDX(r) | CMD_READ);
> +
> +               /* stupid errata: data is only valid for 21us, so
> +                * poll, Forrest, poll...
> +                */
> +               tmo = 0x10000;
> +               while ((RD(ctx, AC97_STATUS) & STAT_CP) && tmo--)
> +                       asm volatile ("nop");
> +               data = RD(ctx, AC97_CMDRESP);
> +
> +               if (!tmo)
> +                       pr_debug("ac97rd timeout #2\n");
> +
> +next:
> +               mutex_unlock(&ctx->lock);
> +       } while (--retry && !tmo);
> +
> +       pr_debug("AC97RD %04x %04lx %d\n", r, data, retry);
> +
> +       return retry ? data & 0xffff : 0xffff;
> +}
> +
> +static void au1xac97c_ac97_write(struct snd_ac97 *ac97, unsigned short r,
> +                                unsigned short v)
> +{
> +       struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
> +       unsigned int tmo, retry;
> +
> +       retry = AC97_RW_RETRIES;
> +       do {
> +               mutex_lock(&ctx->lock);
> +
> +               for (tmo = 5; (RD(ctx, AC97_STATUS) & STAT_CP) && tmo; tmo--)
> +                       udelay(21);
> +               if (!tmo) {
> +                       pr_debug("ac97wr timeout #1\n");
> +                       goto next;
> +               }
> +
> +               WR(ctx, AC97_CMDRESP, CMD_WRITE | CMD_IDX(r) | CMD_SET_DATA(v));
> +
> +               for (tmo = 10; (RD(ctx, AC97_STATUS) & STAT_CP) && tmo; tmo--)
> +                       udelay(21);
> +               if (!tmo)
> +                       pr_debug("ac97wr timeout #2\n");
> +next:
> +               mutex_unlock(&ctx->lock);
> +       } while (--retry && !tmo);
> +
> +       pr_debug("AC97WR %04x %04x %d\n", r, v, retry);
> +}
> +
> +static void au1xac97c_ac97_warm_reset(struct snd_ac97 *ac97)
> +{
> +       struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
> +
> +       WR(ctx, AC97_CONFIG, ctx->cfg | CFG_SG | CFG_SN);
> +       msleep(20);
> +       WR(ctx, AC97_CONFIG, ctx->cfg | CFG_SG);
> +       WR(ctx, AC97_CONFIG, ctx->cfg);
> +}
> +
> +static void au1xac97c_ac97_cold_reset(struct snd_ac97 *ac97)
> +{
> +       struct au1xpsc_audio_data *ctx = ac97_to_ctx(ac97);
> +       int i;
> +
> +       WR(ctx, AC97_CONFIG, ctx->cfg | CFG_RS);
> +       msleep(500);
> +       WR(ctx, AC97_CONFIG, ctx->cfg);
> +
> +       /* wait for codec ready */
> +       i = 50;
> +       while (((RD(ctx, AC97_STATUS) & STAT_RD) == 0) && --i)
> +               msleep(20);
> +       if (!i)
> +               printk(KERN_ERR "ac97c: codec not ready after cold reset\n");
> +}
> +
> +/* AC97 controller operations */
> +struct snd_ac97_bus_ops soc_ac97_ops = {
> +       .read           = au1xac97c_ac97_read,
> +       .write          = au1xac97c_ac97_write,
> +       .reset          = au1xac97c_ac97_cold_reset,
> +       .warm_reset     = au1xac97c_ac97_warm_reset,
> +};
> +EXPORT_SYMBOL_GPL(soc_ac97_ops);       /* globals be gone! */
> +
> +static int alchemy_ac97c_startup(struct snd_pcm_substream *substream,
> +                                struct snd_soc_dai *dai)
> +{
> +       struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
> +       snd_soc_dai_set_dma_data(dai, substream, &ctx->dmaids[0]);
> +       return 0;
> +}
> +
> +static struct snd_soc_dai_ops alchemy_ac97c_ops = {
> +       .startup                = alchemy_ac97c_startup,
> +};
> +
> +static int au1xac97c_dai_probe(struct snd_soc_dai *dai)
> +{
> +       return ac97c_workdata ? 0 : -ENODEV;
> +}
> +
> +static struct snd_soc_dai_driver au1xac97c_dai_driver = {
> +       .name                   = "alchemy-ac97c",
> +       .ac97_control           = 1,
> +       .probe                  = au1xac97c_dai_probe,
> +       .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                    = &alchemy_ac97c_ops,
> +};
> +
> +static int __devinit au1xac97c_drvprobe(struct platform_device *pdev)
> +{
> +       int ret;
> +       struct resource *r;
> +       struct au1xpsc_audio_data *ctx;
> +
> +       ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
> +       if (!ctx)
> +               return -ENOMEM;
> +
> +       mutex_init(&ctx->lock);
> +
> +       r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       if (!r) {
> +               ret = -ENODEV;
> +               goto out0;
> +       }
> +
> +       ret = -EBUSY;
> +       if (!request_mem_region(r->start, resource_size(r), pdev->name))
> +               goto out0;
> +
> +       ctx->mmio = ioremap_nocache(r->start, resource_size(r));
> +       if (!ctx->mmio)
> +               goto out1;
> +
> +       r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
> +       if (!r)
> +               goto out1;
> +       ctx->dmaids[PCM_TX] = r->start;
> +
> +       r = platform_get_resource(pdev, IORESOURCE_DMA, 1);
> +       if (!r)
> +               goto out1;
> +       ctx->dmaids[PCM_RX] = r->start;
> +
> +       /* switch it on */
> +       WR(ctx, AC97_ENABLE, EN_D | EN_CE);
> +       WR(ctx, AC97_ENABLE, EN_CE);
> +
> +       ctx->cfg = CFG_RC(3) | CFG_XS(3);
> +       WR(ctx, AC97_CONFIG, ctx->cfg);
> +
> +       platform_set_drvdata(pdev, ctx);
> +
> +       ret = snd_soc_register_dai(&pdev->dev, &au1xac97c_dai_driver);
> +       if (ret)
> +               goto out1;
> +
> +       ac97c_workdata = ctx;
> +       return 0;
> +
> +
> +       snd_soc_unregister_dai(&pdev->dev);
> +out1:
> +       release_mem_region(r->start, resource_size(r));
> +out0:
> +       kfree(ctx);
> +       return ret;
> +}
> +
> +static int __devexit au1xac97c_drvremove(struct platform_device *pdev)
> +{
> +       struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev);
> +       struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +
> +       snd_soc_unregister_dai(&pdev->dev);
> +
> +       WR(ctx, AC97_ENABLE, EN_D);     /* clock off, disable */
> +
> +       iounmap(ctx->mmio);
> +       release_mem_region(r->start, resource_size(r));
> +       kfree(ctx);
> +
> +       ac97c_workdata = NULL;  /* MDEV */
> +
> +       return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int au1xac97c_drvsuspend(struct device *dev)
> +{
> +       struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev);
> +
> +       WR(ctx, AC97_ENABLE, EN_D);     /* clock off, disable */
> +
> +       return 0;
> +}
> +
> +static int au1xac97c_drvresume(struct device *dev)
> +{
> +       struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev);
> +
> +       WR(ctx, AC97_ENABLE, EN_D | EN_CE);
> +       WR(ctx, AC97_ENABLE, EN_CE);
> +       WR(ctx, AC97_CONFIG, ctx->cfg);
> +
> +       return 0;
> +}
> +
> +static const struct dev_pm_ops au1xpscac97_pmops = {
> +       .suspend        = au1xac97c_drvsuspend,
> +       .resume         = au1xac97c_drvresume,
> +};
> +
> +#define AU1XPSCAC97_PMOPS (&au1xpscac97_pmops)
> +
> +#else
> +
> +#define AU1XPSCAC97_PMOPS NULL
> +
> +#endif
> +
> +static struct platform_driver au1xac97c_driver = {
> +       .driver = {
> +               .name   = "alchemy-ac97c",
> +               .owner  = THIS_MODULE,
> +               .pm     = AU1XPSCAC97_PMOPS,
> +       },
> +       .probe          = au1xac97c_drvprobe,
> +       .remove         = __devexit_p(au1xac97c_drvremove),
> +};
> +
> +static int __init au1xac97c_load(void)
> +{
> +       ac97c_workdata = NULL;
> +       return platform_driver_register(&au1xac97c_driver);
> +}
> +
> +static void __exit au1xac97c_unload(void)
> +{
> +       platform_driver_unregister(&au1xac97c_driver);
> +}
> +
> +module_init(au1xac97c_load);
> +module_exit(au1xac97c_unload);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Au1000/1500/1100 AC97C ASoC driver");
> +MODULE_AUTHOR("Manuel Lauss");
> diff --git a/sound/soc/au1x/dma.c b/sound/soc/au1x/dma.c
> new file mode 100644
> index 0000000..20fedbd
> --- /dev/null
> +++ b/sound/soc/au1x/dma.c
> @@ -0,0 +1,374 @@
> +/*
> + * Au1000/Au1500/Au1100 Audio DMA support.
> + *
> + * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
> + *
> + * copied almost verbatim from the old ALSA driver, written by
> + *                     Charles Eidsness <charles@cooper-street.com>
> + */
> +
> +#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/au1000_dma.h>
> +
> +#include "psc.h"
> +
> +#define ALCHEMY_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)
> +
> +struct pcm_period {
> +       u32 start;
> +       u32 relative_end;       /* relative to start of buffer */
> +       struct pcm_period *next;
> +};
> +
> +struct audio_stream {
> +       struct snd_pcm_substream *substream;
> +       int dma;
> +       struct pcm_period *buffer;
> +       unsigned int period_size;
> +       unsigned int periods;
> +};
> +
> +struct alchemy_pcm_ctx {
> +       struct audio_stream stream[2];  /* playback & capture */
> +};
> +
> +static void au1000_release_dma_link(struct audio_stream *stream)
> +{
> +       struct pcm_period *pointer;
> +       struct pcm_period *pointer_next;
> +
> +       stream->period_size = 0;
> +       stream->periods = 0;
> +       pointer = stream->buffer;
> +       if (!pointer)
> +               return;
> +       do {
> +               pointer_next = pointer->next;
> +               kfree(pointer);
> +               pointer = pointer_next;
> +       } while (pointer != stream->buffer);
> +       stream->buffer = NULL;
> +}
> +
> +static int au1000_setup_dma_link(struct audio_stream *stream,
> +                                unsigned int period_bytes,
> +                                unsigned int periods)
> +{
> +       struct snd_pcm_substream *substream = stream->substream;
> +       struct snd_pcm_runtime *runtime = substream->runtime;
> +       struct pcm_period *pointer;
> +       unsigned long dma_start;
> +       int i;
> +
> +       dma_start = virt_to_phys(runtime->dma_area);
> +
> +       if (stream->period_size == period_bytes &&
> +           stream->periods == periods)
> +               return 0; /* not changed */
> +
> +       au1000_release_dma_link(stream);
> +
> +       stream->period_size = period_bytes;
> +       stream->periods = periods;
> +
> +       stream->buffer = kmalloc(sizeof(struct pcm_period), GFP_KERNEL);
> +       if (!stream->buffer)
> +               return -ENOMEM;
> +       pointer = stream->buffer;
> +       for (i = 0; i < periods; i++) {
> +               pointer->start = (u32)(dma_start + (i * period_bytes));
> +               pointer->relative_end = (u32) (((i+1) * period_bytes) - 0x1);
> +               if (i < periods - 1) {
> +                       pointer->next = kmalloc(sizeof(struct pcm_period),
> +                                               GFP_KERNEL);
> +                       if (!pointer->next) {
> +                               au1000_release_dma_link(stream);
> +                               return -ENOMEM;
> +                       }
> +                       pointer = pointer->next;
> +               }
> +       }
> +       pointer->next = stream->buffer;
> +       return 0;
> +}
> +
> +static void au1000_dma_stop(struct audio_stream *stream)
> +{
> +       if (stream->buffer)
> +               disable_dma(stream->dma);
> +}
> +
> +static void au1000_dma_start(struct audio_stream *stream)
> +{
> +       if (!stream->buffer)
> +               return;
> +
> +       init_dma(stream->dma);
> +       if (get_dma_active_buffer(stream->dma) == 0) {
> +               clear_dma_done0(stream->dma);
> +               set_dma_addr0(stream->dma, stream->buffer->start);
> +               set_dma_count0(stream->dma, stream->period_size >> 1);
> +               set_dma_addr1(stream->dma, stream->buffer->next->start);
> +               set_dma_count1(stream->dma, stream->period_size >> 1);
> +       } else {
> +               clear_dma_done1(stream->dma);
> +               set_dma_addr1(stream->dma, stream->buffer->start);
> +               set_dma_count1(stream->dma, stream->period_size >> 1);
> +               set_dma_addr0(stream->dma, stream->buffer->next->start);
> +               set_dma_count0(stream->dma, stream->period_size >> 1);
> +       }
> +       enable_dma_buffers(stream->dma);
> +       start_dma(stream->dma);
> +}
> +
> +static irqreturn_t au1000_dma_interrupt(int irq, void *ptr)
> +{
> +       struct audio_stream *stream = (struct audio_stream *)ptr;
> +       struct snd_pcm_substream *substream = stream->substream;
> +
> +       switch (get_dma_buffer_done(stream->dma)) {
> +       case DMA_D0:
> +               stream->buffer = stream->buffer->next;
> +               clear_dma_done0(stream->dma);
> +               set_dma_addr0(stream->dma, stream->buffer->next->start);
> +               set_dma_count0(stream->dma, stream->period_size >> 1);
> +               enable_dma_buffer0(stream->dma);
> +               break;
> +       case DMA_D1:
> +               stream->buffer = stream->buffer->next;
> +               clear_dma_done1(stream->dma);
> +               set_dma_addr1(stream->dma, stream->buffer->next->start);
> +               set_dma_count1(stream->dma, stream->period_size >> 1);
> +               enable_dma_buffer1(stream->dma);
> +               break;
> +       case (DMA_D0 | DMA_D1):
> +               pr_debug("DMA %d missed interrupt.\n", stream->dma);
> +               au1000_dma_stop(stream);
> +               au1000_dma_start(stream);
> +               break;
> +       case (~DMA_D0 & ~DMA_D1):
> +               pr_debug("DMA %d empty irq.\n", stream->dma);
> +       }
> +       snd_pcm_period_elapsed(substream);
> +       return IRQ_HANDLED;
> +}
> +
> +static const struct snd_pcm_hardware alchemy_pcm_hardware = {
> +       .info             = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
> +                           SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BATCH,
> +       .formats          = ALCHEMY_PCM_FMTS,
> +       .rates            = SNDRV_PCM_RATE_8000_192000,
> +       .rate_min         = SNDRV_PCM_RATE_8000,
> +       .rate_max         = SNDRV_PCM_RATE_192000,
> +       .channels_min     = 2,
> +       .channels_max     = 2,
> +       .period_bytes_min = 1024,
> +       .period_bytes_max = 16 * 1024 - 1,
> +       .periods_min      = 4,
> +       .periods_max      = 255,
> +       .buffer_bytes_max = 128 * 1024,
> +       .fifo_size        = 16,
> +};
> +
> +static inline struct alchemy_pcm_ctx *ss_to_ctx(struct snd_pcm_substream *ss)
> +{
> +       struct snd_soc_pcm_runtime *rtd = ss->private_data;
> +       return snd_soc_platform_get_drvdata(rtd->platform);
> +}
> +
> +static inline struct audio_stream *ss_to_as(struct snd_pcm_substream *ss)
> +{
> +       struct alchemy_pcm_ctx *ctx = ss_to_ctx(ss);
> +       return &(ctx->stream[SUBSTREAM_TYPE(ss)]);
> +}
> +
> +static int alchemy_pcm_open(struct snd_pcm_substream *substream)
> +{
> +       struct alchemy_pcm_ctx *ctx = ss_to_ctx(substream);
> +       struct snd_soc_pcm_runtime *rtd = substream->private_data;
> +       int stype = SUBSTREAM_TYPE(substream);
> +       int *dmaids;
> +       char *name;
> +
> +       dmaids = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream);
> +       if (!dmaids)
> +               return -ENODEV; /* whoa, has ordering changed? */
> +
> +       /* DMA setup */
> +       name = (stype == PCM_TX) ? "audio-tx" : "audio-rx";
> +       ctx->stream[stype].dma = request_au1000_dma(dmaids[stype], name,
> +                                       au1000_dma_interrupt, IRQF_DISABLED,
> +                                       &ctx->stream[stype]);
> +       set_dma_mode(ctx->stream[stype].dma,
> +                    get_dma_mode(ctx->stream[stype].dma) & ~DMA_NC);
> +
> +       ctx->stream[stype].substream = substream;
> +       ctx->stream[stype].buffer = NULL;
> +       snd_soc_set_runtime_hwparams(substream, &alchemy_pcm_hardware);
> +
> +       return 0;
> +}
> +
> +static int alchemy_pcm_close(struct snd_pcm_substream *substream)
> +{
> +       struct alchemy_pcm_ctx *ctx = ss_to_ctx(substream);
> +       int stype = SUBSTREAM_TYPE(substream);
> +
> +       ctx->stream[SUBSTREAM_TYPE(substream)].substream = NULL;
> +       free_au1000_dma(ctx->stream[stype].dma);
> +
> +       return 0;
> +}
> +
> +static int alchemy_pcm_hw_params(struct snd_pcm_substream *substream,
> +                                struct snd_pcm_hw_params *hw_params)
> +{
> +       struct audio_stream *stream = ss_to_as(substream);
> +       int err;
> +
> +       err = snd_pcm_lib_malloc_pages(substream,
> +                                      params_buffer_bytes(hw_params));
> +       if (err < 0)
> +               return err;
> +       return au1000_setup_dma_link(stream,
> +                                    params_period_bytes(hw_params),
> +                                    params_periods(hw_params));


What happens if this fails ? You already have malloc'ed some pages.

> +}
> +
> +static int alchemy_pcm_hw_free(struct snd_pcm_substream *substream)
> +{
> +       struct audio_stream *stream = ss_to_as(substream);
> +       au1000_release_dma_link(stream);
> +       return snd_pcm_lib_free_pages(substream);
> +}
> +
> +static int alchemy_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
> +{
> +       struct audio_stream *stream = ss_to_as(substream);
> +       int err = 0;
> +
> +       switch (cmd) {
> +       case SNDRV_PCM_TRIGGER_START:
> +               au1000_dma_start(stream);
> +               break;
> +       case SNDRV_PCM_TRIGGER_STOP:
> +               au1000_dma_stop(stream);
> +               break;
> +       default:
> +               err = -EINVAL;
> +               break;
> +       }
> +       return err;
> +}
> +
> +static snd_pcm_uframes_t alchemy_pcm_pointer(struct snd_pcm_substream *ss)
> +{
> +       struct audio_stream *stream = ss_to_as(ss);
> +       long location;
> +
> +       location = get_dma_residue(stream->dma);
> +       location = stream->buffer->relative_end - location;
> +       if (location == -1)
> +               location = 0;
> +       return bytes_to_frames(ss->runtime, location);
> +}
> +
> +static struct snd_pcm_ops alchemy_pcm_ops = {
> +       .open                   = alchemy_pcm_open,
> +       .close                  = alchemy_pcm_close,
> +       .ioctl                  = snd_pcm_lib_ioctl,
> +       .hw_params              = alchemy_pcm_hw_params,
> +       .hw_free                = alchemy_pcm_hw_free,
> +       .trigger                = alchemy_pcm_trigger,
> +       .pointer                = alchemy_pcm_pointer,
> +};
> +
> +static void alchemy_pcm_free_dma_buffers(struct snd_pcm *pcm)
> +{
> +       snd_pcm_lib_preallocate_free_for_all(pcm);
> +}
> +
> +static int alchemy_pcm_new(struct snd_card *card,
> +                          struct snd_soc_dai *dai,
> +                          struct snd_pcm *pcm)

This API call has been updated to only pass the rtd *

> +{
> +       snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
> +               snd_dma_continuous_data(GFP_KERNEL), 65536, (4096 * 1024) - 1);
> +
> +       return 0;
> +}
> +
> +struct snd_soc_platform_driver alchemy_pcm_soc_platform = {
> +       .ops            = &alchemy_pcm_ops,
> +       .pcm_new        = alchemy_pcm_new,
> +       .pcm_free       = alchemy_pcm_free_dma_buffers,
> +};
> +
> +static int __devinit alchemy_pcm_drvprobe(struct platform_device *pdev)
> +{
> +       struct alchemy_pcm_ctx *ctx;
> +       int ret;
> +
> +       ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
> +       if (!ctx)
> +               return -ENOMEM;
> +
> +       platform_set_drvdata(pdev, ctx);
> +
> +       ret = snd_soc_register_platform(&pdev->dev, &alchemy_pcm_soc_platform);
> +       if (ret)
> +               kfree(ctx);
> +
> +       return ret;
> +}
> +
> +static int __devexit alchemy_pcm_drvremove(struct platform_device *pdev)
> +{
> +       struct alchemy_pcm_ctx *ctx = platform_get_drvdata(pdev);
> +
> +       snd_soc_unregister_platform(&pdev->dev);
> +       kfree(ctx);
> +
> +       return 0;
> +}
> +
> +static struct platform_driver alchemy_pcmdma_driver = {
> +       .driver = {
> +               .name   = "alchemy-pcm-dma",
> +               .owner  = THIS_MODULE,
> +       },
> +       .probe          = alchemy_pcm_drvprobe,
> +       .remove         = __devexit_p(alchemy_pcm_drvremove),
> +};
> +
> +static int __init alchemy_pcmdma_load(void)
> +{
> +       return platform_driver_register(&alchemy_pcmdma_driver);
> +}
> +
> +static void __exit alchemy_pcmdma_unload(void)
> +{
> +       platform_driver_unregister(&alchemy_pcmdma_driver);
> +}
> +
> +module_init(alchemy_pcmdma_load);
> +module_exit(alchemy_pcmdma_unload);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Au1000/Au1500/Au1100 Audio DMA driver");
> +MODULE_AUTHOR("Manuel Lauss");
> diff --git a/sound/soc/au1x/i2sc.c b/sound/soc/au1x/i2sc.c
> new file mode 100644
> index 0000000..e3964a2
> --- /dev/null
> +++ b/sound/soc/au1x/i2sc.c
> @@ -0,0 +1,342 @@
> +/*
> + * Au1000/Au1500/Au1100 I2S controller driver for ASoC
> + *
> + * (c) 2011 Manuel Lauss <manuel.lauss@googlemail.com>
> + *
> + * Note: clock supplied to the I2S controller must be 256x samplerate.
> + */
> +
> +#include <linux/init.h>
> +#include <linux/module.h>
> +#include <linux/slab.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 "psc.h"
> +
> +#define I2S_RXTX       0x00
> +#define I2S_CFG                0x04
> +#define I2S_ENABLE     0x08
> +
> +#define CFG_XU         (1 << 25)       /* tx underflow */
> +#define CFG_XO         (1 << 24)
> +#define CFG_RU         (1 << 23)
> +#define CFG_RO         (1 << 22)
> +#define CFG_TR         (1 << 21)
> +#define CFG_TE         (1 << 20)
> +#define CFG_TF         (1 << 19)
> +#define CFG_RR         (1 << 18)
> +#define CFG_RF         (1 << 17)
> +#define CFG_ICK                (1 << 12)       /* clock invert */
> +#define CFG_PD         (1 << 11)       /* set to make I2SDIO INPUT */
> +#define CFG_LB         (1 << 10)       /* loopback */
> +#define CFG_IC         (1 << 9)        /* word select invert */
> +#define CFG_FM_I2S     (0 << 7)        /* I2S format */
> +#define CFG_FM_LJ      (1 << 7)        /* left-justified */
> +#define CFG_FM_RJ      (2 << 7)        /* right-justified */
> +#define CFG_FM_MASK    (3 << 7)
> +#define CFG_TN         (1 << 6)        /* tx fifo en */
> +#define CFG_RN         (1 << 5)        /* rx fifo en */
> +#define CFG_SZ_8       (0x08)
> +#define CFG_SZ_16      (0x10)
> +#define CFG_SZ_18      (0x12)
> +#define CFG_SZ_20      (0x14)
> +#define CFG_SZ_24      (0x18)
> +#define CFG_SZ_MASK    (0x1f)
> +#define EN_D           (1 << 1)        /* DISable */
> +#define EN_CE          (1 << 0)        /* clock enable */
> +
> +/* only limited by clock generator and board design */
> +#define AU1XI2SC_RATES \
> +       SNDRV_PCM_RATE_CONTINUOUS
> +
> +#define AU1XI2SC_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_S18_3LE | SNDRV_PCM_FMTBIT_U18_3LE |   \
> +       SNDRV_PCM_FMTBIT_S18_3BE | SNDRV_PCM_FMTBIT_U18_3BE |   \
> +       SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_U20_3LE |   \
> +       SNDRV_PCM_FMTBIT_S20_3BE | SNDRV_PCM_FMTBIT_U20_3BE |   \
> +       SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE |     \
> +       SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE |     \
> +       0)
> +
> +static inline unsigned long RD(struct au1xpsc_audio_data *ctx, int reg)
> +{
> +       return __raw_readl(ctx->mmio + reg);
> +}
> +
> +static inline void WR(struct au1xpsc_audio_data *ctx, int reg, unsigned long v)
> +{
> +       __raw_writel(v, ctx->mmio + reg);
> +       wmb();
> +}

Btw, just wondering if arch/mips already supplies a suitable RD()/WR() for you ?

> +
> +static int au1xi2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int fmt)
> +{
> +       struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(cpu_dai);
> +       unsigned long c;
> +       int ret;
> +
> +       ret = -EINVAL;
> +       c = ctx->cfg;
> +
> +       c &= ~CFG_FM_MASK;
> +       switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
> +       case SND_SOC_DAIFMT_I2S:
> +               c |= CFG_FM_I2S;
> +               break;
> +       case SND_SOC_DAIFMT_MSB:
> +               c |= CFG_FM_RJ;
> +               break;
> +       case SND_SOC_DAIFMT_LSB:
> +               c |= CFG_FM_LJ;
> +               break;
> +       default:
> +               goto out;
> +       }
> +
> +       c &= ~(CFG_IC | CFG_ICK);               /* IB-IF */
> +       switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
> +       case SND_SOC_DAIFMT_NB_NF:
> +               c |= CFG_IC | CFG_ICK;
> +               break;
> +       case SND_SOC_DAIFMT_NB_IF:
> +               c |= CFG_IC;
> +               break;
> +       case SND_SOC_DAIFMT_IB_NF:
> +               c |= CFG_ICK;
> +               break;
> +       case SND_SOC_DAIFMT_IB_IF:
> +               break;
> +       default:
> +               goto out;
> +       }
> +
> +       /* I2S controller only supports master */
> +       switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
> +       case SND_SOC_DAIFMT_CBS_CFS:    /* CODEC slave */
> +               break;
> +       default:
> +               goto out;
> +       }
> +
> +       ret = 0;
> +       ctx->cfg = c;
> +out:
> +       return ret;
> +}
> +
> +static int au1xi2s_trigger(struct snd_pcm_substream *substream,
> +                          int cmd, struct snd_soc_dai *dai)
> +{
> +       struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
> +       int stype = SUBSTREAM_TYPE(substream);
> +
> +       switch (cmd) {
> +       case SNDRV_PCM_TRIGGER_START:
> +       case SNDRV_PCM_TRIGGER_RESUME:
> +               /* power up */
> +               WR(ctx, I2S_ENABLE, EN_D | EN_CE);
> +               WR(ctx, I2S_ENABLE, EN_CE);
> +               ctx->cfg |= (stype == PCM_TX) ? CFG_TN : CFG_RN;
> +               WR(ctx, I2S_CFG, ctx->cfg);
> +               break;
> +       case SNDRV_PCM_TRIGGER_STOP:
> +       case SNDRV_PCM_TRIGGER_SUSPEND:
> +               ctx->cfg &= ~((stype == PCM_TX) ? CFG_TN : CFG_RN);
> +               WR(ctx, I2S_CFG, ctx->cfg);
> +               WR(ctx, I2S_ENABLE, EN_D);              /* power off */
> +               break;
> +       default:
> +               return -EINVAL;
> +       }
> +
> +       return 0;
> +}
> +
> +static unsigned long msbits_to_reg(int msbits)
> +{
> +       switch (msbits) {
> +       case 8:  return CFG_SZ_8;
> +       case 16: return CFG_SZ_16;
> +       case 18: return CFG_SZ_18;
> +       case 20: return CFG_SZ_20;
> +       case 24: return CFG_SZ_24;

It's best to format all the switch statements consistently throughout your code.

> +       }
> +       return 0;
> +}
> +
> +static int au1xi2s_hw_params(struct snd_pcm_substream *substream,
> +                            struct snd_pcm_hw_params *params,
> +                            struct snd_soc_dai *dai)
> +{
> +       struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
> +       unsigned long v;
> +
> +       v = msbits_to_reg(params->msbits);
> +       if (!v)
> +               return -EINVAL;
> +
> +       ctx->cfg &= ~CFG_SZ_MASK;
> +       ctx->cfg |= v;
> +       return 0;
> +}
> +
> +static int au1xi2s_startup(struct snd_pcm_substream *substream,
> +                          struct snd_soc_dai *dai)
> +{
> +       struct au1xpsc_audio_data *ctx = snd_soc_dai_get_drvdata(dai);
> +       snd_soc_dai_set_dma_data(dai, substream, &ctx->dmaids[0]);
> +       return 0;
> +}
> +
> +static const struct snd_soc_dai_ops au1xi2s_dai_ops = {
> +       .startup        = au1xi2s_startup,
> +       .trigger        = au1xi2s_trigger,
> +       .hw_params      = au1xi2s_hw_params,
> +       .set_fmt        = au1xi2s_set_fmt,
> +};
> +
> +static struct snd_soc_dai_driver au1xi2s_dai_driver = {
> +       .symmetric_rates        = 1,
> +       .playback = {
> +               .rates          = AU1XI2SC_RATES,
> +               .formats        = AU1XI2SC_FMTS,
> +               .channels_min   = 2,
> +               .channels_max   = 2,
> +       },
> +       .capture = {
> +               .rates          = AU1XI2SC_RATES,
> +               .formats        = AU1XI2SC_FMTS,
> +               .channels_min   = 2,
> +               .channels_max   = 2,
> +       },
> +       .ops = &au1xi2s_dai_ops,
> +};
> +
> +static int __devinit au1xi2s_drvprobe(struct platform_device *pdev)
> +{
> +       int ret;
> +       struct resource *r;
> +       struct au1xpsc_audio_data *ctx;
> +
> +       ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
> +       if (!ctx)
> +               return -ENOMEM;
> +
> +       r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       if (!r) {
> +               ret = -ENODEV;
> +               goto out0;
> +       }
> +
> +       ret = -EBUSY;
> +       if (!request_mem_region(r->start, resource_size(r), pdev->name))
> +               goto out0;
> +
> +       ctx->mmio = ioremap_nocache(r->start, resource_size(r));
> +       if (!ctx->mmio)
> +               goto out1;
> +
> +       r = platform_get_resource(pdev, IORESOURCE_DMA, 0);
> +       if (!r)
> +               goto out1;
> +       ctx->dmaids[PCM_TX] = r->start;
> +
> +       r = platform_get_resource(pdev, IORESOURCE_DMA, 1);
> +       if (!r)
> +               goto out1;
> +       ctx->dmaids[PCM_RX] = r->start;
> +
> +       platform_set_drvdata(pdev, ctx);
> +
> +       ret = snd_soc_register_dai(&pdev->dev, &au1xi2s_dai_driver);
> +       if (ret)
> +               goto out1;
> +
> +       return 0;
> +
> +       snd_soc_unregister_dai(&pdev->dev);
> +out1:
> +       release_mem_region(r->start, resource_size(r));
> +out0:
> +       kfree(ctx);
> +       return ret;
> +}
> +
> +static int __devexit au1xi2s_drvremove(struct platform_device *pdev)
> +{
> +       struct au1xpsc_audio_data *ctx = platform_get_drvdata(pdev);
> +       struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +
> +       snd_soc_unregister_dai(&pdev->dev);
> +
> +       WR(ctx, I2S_ENABLE, EN_D);      /* clock off, disable */
> +
> +       iounmap(ctx->mmio);
> +       release_mem_region(r->start, resource_size(r));
> +       kfree(ctx);
> +
> +       return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int au1xi2s_drvsuspend(struct device *dev)
> +{
> +       struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev);
> +
> +       WR(ctx, I2S_ENABLE, EN_D);      /* clock off, disable */
> +
> +       return 0;
> +}
> +
> +static int au1xi2s_drvresume(struct device *dev)
> +{

Should we not enalbe the clock here (i.e. in order to balance the clock off in suspend) ?

> +       return 0;
> +}
> +
> +static const struct dev_pm_ops au1xi2sc_pmops = {
> +       .suspend        = au1xi2s_drvsuspend,
> +       .resume         = au1xi2s_drvresume,
> +};
> +
> +#define AU1XI2SC_PMOPS (&au1xi2sc_pmops)
> +
> +#else
> +
> +#define AU1XI2SC_PMOPS NULL
> +
> +#endif
> +
> +static struct platform_driver au1xi2s_driver = {
> +       .driver = {
> +               .name   = "alchemy-i2sc",
> +               .owner  = THIS_MODULE,
> +               .pm     = AU1XI2SC_PMOPS,
> +       },
> +       .probe          = au1xi2s_drvprobe,
> +       .remove         = __devexit_p(au1xi2s_drvremove),
> +};
> +
> +static int __init au1xi2s_load(void)
> +{
> +       return platform_driver_register(&au1xi2s_driver);
> +}
> +
> +static void __exit au1xi2s_unload(void)
> +{
> +       platform_driver_unregister(&au1xi2s_driver);
> +}
> +
> +module_init(au1xi2s_load);
> +module_exit(au1xi2s_unload);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Au1000/1500/1100 I2S ASoC driver");
> +MODULE_AUTHOR("Manuel Lauss");
> diff --git a/sound/soc/au1x/psc.h b/sound/soc/au1x/psc.h
> index b30eadd..c59b9e5 100644
> --- a/sound/soc/au1x/psc.h
> +++ b/sound/soc/au1x/psc.h
> @@ -1,7 +1,7 @@
>  /*
> - * Au12x0/Au1550 PSC ALSA ASoC audio support.
> + * Alchemy ALSA ASoC audio support.
>   *
> - * (c) 2007-2008 MSC Vertriebsges.m.b.H.,
> + * (c) 2007-2011 MSC Vertriebsges.m.b.H.,
>   *     Manuel Lauss <manuel.lauss@gmail.com>
>   *
>   * This program is free software; you can redistribute it and/or modify
> @@ -13,7 +13,13 @@
>  #ifndef _AU1X_PCM_H
>  #define _AU1X_PCM_H
> 
> -/* DBDMA helpers */
> +#define PCM_TX 0
> +#define PCM_RX 1

Is there any need for these macros, SNDRV_PCM_STREAM_PLAYBACK and SNDRV_PCMP_STREAM_CAPTURE should be used for this type of logic.

> +
> +#define SUBSTREAM_TYPE(substream) \
> +       ((substream)->stream == SNDRV_PCM_STREAM_PLAYBACK ? PCM_TX : PCM_RX)
> +
> +/* PSC/DBDMA helpers */
>  extern struct platform_device *au1xpsc_pcm_add(struct platform_device *pdev);
>  extern void au1xpsc_pcm_destroy(struct platform_device *dmapd);
> 
> @@ -27,15 +33,10 @@ struct au1xpsc_audio_data {
> 
>         unsigned long pm[2];
>         struct mutex lock;
> +       int dmaids[2];
>         struct platform_device *dmapd;
>  };
> 
> -#define PCM_TX 0
> -#define PCM_RX 1
> -
> -#define SUBSTREAM_TYPE(substream) \
> -       ((substream)->stream == SNDRV_PCM_STREAM_PLAYBACK ? PCM_TX : PCM_RX)
> -
>  /* 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)
> --
> 1.7.6
> 


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH V4 2/3] ASoC: Add a DB1x00 AC97 machine driver
  2011-07-24 10:11   ` Manuel Lauss
@ 2011-07-25  9:34     ` Liam Girdwood
  -1 siblings, 0 replies; 16+ messages in thread
From: Liam Girdwood @ 2011-07-25  9:34 UTC (permalink / raw)
  To: Manuel Lauss; +Cc: alsa-devel, Mark Brown, Linux-MIPS, Ralf Baechle

On 24/07/11 11:11, Manuel Lauss wrote:
> Add a machine driver suitable for the AC97 part on the DB1000/DB1500/DB1100
> boards.
> 
> Run-tested on DB1500.
> 
> Signed-off-by: Manuel Lauss <manuel.lauss@googlemail.com>

Acked-by: Liam Girdwood <lrg@ti.com>

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH V4 2/3] ASoC: Add a DB1x00 AC97 machine driver
@ 2011-07-25  9:34     ` Liam Girdwood
  0 siblings, 0 replies; 16+ messages in thread
From: Liam Girdwood @ 2011-07-25  9:34 UTC (permalink / raw)
  To: alsa-devel; +Cc: Linux-MIPS, Mark Brown, alsa-devel, Ralf Baechle

On 24/07/11 11:11, Manuel Lauss wrote:
> Add a machine driver suitable for the AC97 part on the DB1000/DB1500/DB1100
> boards.
> 
> Run-tested on DB1500.
> 
> Signed-off-by: Manuel Lauss <manuel.lauss@googlemail.com>

Acked-by: Liam Girdwood <lrg@ti.com>


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH V4 3/3] ALSA: deprecate MIPS AU1X00 AC97 driver
  2011-07-24 10:11   ` Manuel Lauss
@ 2011-07-25  9:34     ` Liam Girdwood
  -1 siblings, 0 replies; 16+ messages in thread
From: Liam Girdwood @ 2011-07-25  9:34 UTC (permalink / raw)
  To: Manuel Lauss; +Cc: alsa-devel, Mark Brown, Linux-MIPS, Ralf Baechle

On 24/07/11 11:11, Manuel Lauss wrote:
> Now that an ASoC variant is available, tell users that this
> driver is now living on borrowed time...
> 
> Signed-off-by: Manuel Lauss <manuel.lauss@googlemail.com>
> ---
> V4: no changes.
> V3: mark driver DEPRECATED instead of removing it outright.
> 

Acked-by: Liam Girdwood <lrg@ti.com>

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH V4 3/3] ALSA: deprecate MIPS AU1X00 AC97 driver
@ 2011-07-25  9:34     ` Liam Girdwood
  0 siblings, 0 replies; 16+ messages in thread
From: Liam Girdwood @ 2011-07-25  9:34 UTC (permalink / raw)
  To: alsa-devel; +Cc: Linux-MIPS, Mark Brown, alsa-devel, Ralf Baechle

On 24/07/11 11:11, Manuel Lauss wrote:
> Now that an ASoC variant is available, tell users that this
> driver is now living on borrowed time...
> 
> Signed-off-by: Manuel Lauss <manuel.lauss@googlemail.com>
> ---
> V4: no changes.
> V3: mark driver DEPRECATED instead of removing it outright.
> 

Acked-by: Liam Girdwood <lrg@ti.com>

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH V4 1/3] ASoC: Alchemy AC97C/I2SC audio support
  2011-07-25  9:32     ` Liam Girdwood
@ 2011-07-25 11:02       ` Manuel Lauss
  -1 siblings, 0 replies; 16+ messages in thread
From: Manuel Lauss @ 2011-07-25 11:02 UTC (permalink / raw)
  To: Liam Girdwood; +Cc: alsa-devel, Mark Brown, Linux-MIPS, Ralf Baechle

On Mon, Jul 25, 2011 at 11:32 AM, Liam Girdwood <lrg@ti.com> wrote:
> On 24/07/11 11:11, Manuel Lauss wrote:
>> This patch adds ASoC support for the AC97 and I2S controllers
>> on the old Au1000/Au1500/Au1100 chips,
>>
>> AC97 Tested on a Db1500.  I2S untested since none of the boards
>> actually have an I2S codec wired up (just test pins).
>>
>> Signed-off-by: Manuel Lauss <manuel.lauss@googlemail.com>
>> ---
>> V4: dropped hunk which removed I2S constants in au1000.h header to avoid merge
>>     conflicts with other patches, use the context structure in psc.h since it
>>     fits really well.
>> V3: implemented feedback from Lars-Peter Clausen: src tidying, no more
>>     automatic dma device registration, split off db1000 board code.
>> V2: added untested I2S controller driver for completeness, removed the audio
>>     defines from the au1000 header as well.
>>
>
> Looks mostly OK, I just have some questions below:-
>> +#define AC97_RATES     \
>> +       SNDRV_PCM_RATE_8000_44100
>
> Just curious, is there any reason this doesn't support 48kHz ?

No reason really; fixed.


>> diff --git a/sound/soc/au1x/dma.c b/sound/soc/au1x/dma.c
>> new file mode 100644
>> index 0000000..20fedbd
>> +static int alchemy_pcm_hw_params(struct snd_pcm_substream *substream,
>> +                                struct snd_pcm_hw_params *hw_params)
>> +{
>> +       struct audio_stream *stream = ss_to_as(substream);
>> +       int err;
>> +
>> +       err = snd_pcm_lib_malloc_pages(substream,
>> +                                      params_buffer_bytes(hw_params));
>> +       if (err < 0)
>> +               return err;
>> +       return au1000_setup_dma_link(stream,
>> +                                    params_period_bytes(hw_params),
>> +                                    params_periods(hw_params));
>
>
> What happens if this fails ? You already have malloc'ed some pages.

also fixed,


>> +static int alchemy_pcm_new(struct snd_card *card,
>> +                          struct snd_soc_dai *dai,
>> +                          struct snd_pcm *pcm)
>
> This API call has been updated to only pass the rtd *

fixed,


>> diff --git a/sound/soc/au1x/i2sc.c b/sound/soc/au1x/i2sc.c
>> +static inline unsigned long RD(struct au1xpsc_audio_data *ctx, int reg)
>> +{
>> +       return __raw_readl(ctx->mmio + reg);
>> +}
>> +
>> +static inline void WR(struct au1xpsc_audio_data *ctx, int reg, unsigned long v)
>> +{
>> +       __raw_writel(v, ctx->mmio + reg);
>> +       wmb();
>> +}
>
> Btw, just wondering if arch/mips already supplies a suitable RD()/WR() for you ?

with the proper context, no.  I've used these kind of macros in other
code, though :)


>> +static unsigned long msbits_to_reg(int msbits)
>> +{
>> +       switch (msbits) {
>> +       case 8:  return CFG_SZ_8;
>> +       case 16: return CFG_SZ_16;
>> +       case 18: return CFG_SZ_18;
>> +       case 20: return CFG_SZ_20;
>> +       case 24: return CFG_SZ_24;
>
> It's best to format all the switch statements consistently throughout your code.

also fixed,


>> +static int au1xi2s_drvsuspend(struct device *dev)
>> +{
>> +       struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev);
>> +
>> +       WR(ctx, I2S_ENABLE, EN_D);      /* clock off, disable */
>> +
>> +       return 0;
>> +}
>> +
>> +static int au1xi2s_drvresume(struct device *dev)
>> +{
>
> Should we not enalbe the clock here (i.e. in order to balance the clock off in suspend) ?

According to the datasheet, these bits only control internal clock
supply to the I2S module
itself.  It's reenabled in the trigger callback.  Clock for the codec
is controlled through
another register in the central clock block.   As I wrote, this is an
untested driver since none
of the boards I have actually have an I2S codec wired up.  So this is
all guesswork based
on vague datasheets ;)


>> diff --git a/sound/soc/au1x/psc.h b/sound/soc/au1x/psc.h
>> index b30eadd..c59b9e5 100644
>> --- a/sound/soc/au1x/psc.h
>> +++ b/sound/soc/au1x/psc.h
>> @@ -1,7 +1,7 @@
>>  /*
>> - * Au12x0/Au1550 PSC ALSA ASoC audio support.
>> + * Alchemy ALSA ASoC audio support.
>>   *
>> - * (c) 2007-2008 MSC Vertriebsges.m.b.H.,
>> + * (c) 2007-2011 MSC Vertriebsges.m.b.H.,
>>   *     Manuel Lauss <manuel.lauss@gmail.com>
>>   *
>>   * This program is free software; you can redistribute it and/or modify
>> @@ -13,7 +13,13 @@
>>  #ifndef _AU1X_PCM_H
>>  #define _AU1X_PCM_H
>>
>> -/* DBDMA helpers */
>> +#define PCM_TX 0
>> +#define PCM_RX 1
>
> Is there any need for these macros, SNDRV_PCM_STREAM_PLAYBACK and SNDRV_PCMP_STREAM_CAPTURE should be used for this type of logic.

As long as they both stay 0 and 1, might as well use them directly.  Fixed.
I'll remove them in a follow-up patch for the PSC drivers.

Thanks for the review!
        Manuel Lauss

^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH V4 1/3] ASoC: Alchemy AC97C/I2SC audio support
@ 2011-07-25 11:02       ` Manuel Lauss
  0 siblings, 0 replies; 16+ messages in thread
From: Manuel Lauss @ 2011-07-25 11:02 UTC (permalink / raw)
  To: alsa-devel; +Cc: Linux-MIPS, Mark Brown, alsa-devel, Ralf Baechle

On Mon, Jul 25, 2011 at 11:32 AM, Liam Girdwood <lrg@ti.com> wrote:
> On 24/07/11 11:11, Manuel Lauss wrote:
>> This patch adds ASoC support for the AC97 and I2S controllers
>> on the old Au1000/Au1500/Au1100 chips,
>>
>> AC97 Tested on a Db1500.  I2S untested since none of the boards
>> actually have an I2S codec wired up (just test pins).
>>
>> Signed-off-by: Manuel Lauss <manuel.lauss@googlemail.com>
>> ---
>> V4: dropped hunk which removed I2S constants in au1000.h header to avoid merge
>>     conflicts with other patches, use the context structure in psc.h since it
>>     fits really well.
>> V3: implemented feedback from Lars-Peter Clausen: src tidying, no more
>>     automatic dma device registration, split off db1000 board code.
>> V2: added untested I2S controller driver for completeness, removed the audio
>>     defines from the au1000 header as well.
>>
>
> Looks mostly OK, I just have some questions below:-
>> +#define AC97_RATES     \
>> +       SNDRV_PCM_RATE_8000_44100
>
> Just curious, is there any reason this doesn't support 48kHz ?

No reason really; fixed.


>> diff --git a/sound/soc/au1x/dma.c b/sound/soc/au1x/dma.c
>> new file mode 100644
>> index 0000000..20fedbd
>> +static int alchemy_pcm_hw_params(struct snd_pcm_substream *substream,
>> +                                struct snd_pcm_hw_params *hw_params)
>> +{
>> +       struct audio_stream *stream = ss_to_as(substream);
>> +       int err;
>> +
>> +       err = snd_pcm_lib_malloc_pages(substream,
>> +                                      params_buffer_bytes(hw_params));
>> +       if (err < 0)
>> +               return err;
>> +       return au1000_setup_dma_link(stream,
>> +                                    params_period_bytes(hw_params),
>> +                                    params_periods(hw_params));
>
>
> What happens if this fails ? You already have malloc'ed some pages.

also fixed,


>> +static int alchemy_pcm_new(struct snd_card *card,
>> +                          struct snd_soc_dai *dai,
>> +                          struct snd_pcm *pcm)
>
> This API call has been updated to only pass the rtd *

fixed,


>> diff --git a/sound/soc/au1x/i2sc.c b/sound/soc/au1x/i2sc.c
>> +static inline unsigned long RD(struct au1xpsc_audio_data *ctx, int reg)
>> +{
>> +       return __raw_readl(ctx->mmio + reg);
>> +}
>> +
>> +static inline void WR(struct au1xpsc_audio_data *ctx, int reg, unsigned long v)
>> +{
>> +       __raw_writel(v, ctx->mmio + reg);
>> +       wmb();
>> +}
>
> Btw, just wondering if arch/mips already supplies a suitable RD()/WR() for you ?

with the proper context, no.  I've used these kind of macros in other
code, though :)


>> +static unsigned long msbits_to_reg(int msbits)
>> +{
>> +       switch (msbits) {
>> +       case 8:  return CFG_SZ_8;
>> +       case 16: return CFG_SZ_16;
>> +       case 18: return CFG_SZ_18;
>> +       case 20: return CFG_SZ_20;
>> +       case 24: return CFG_SZ_24;
>
> It's best to format all the switch statements consistently throughout your code.

also fixed,


>> +static int au1xi2s_drvsuspend(struct device *dev)
>> +{
>> +       struct au1xpsc_audio_data *ctx = dev_get_drvdata(dev);
>> +
>> +       WR(ctx, I2S_ENABLE, EN_D);      /* clock off, disable */
>> +
>> +       return 0;
>> +}
>> +
>> +static int au1xi2s_drvresume(struct device *dev)
>> +{
>
> Should we not enalbe the clock here (i.e. in order to balance the clock off in suspend) ?

According to the datasheet, these bits only control internal clock
supply to the I2S module
itself.  It's reenabled in the trigger callback.  Clock for the codec
is controlled through
another register in the central clock block.   As I wrote, this is an
untested driver since none
of the boards I have actually have an I2S codec wired up.  So this is
all guesswork based
on vague datasheets ;)


>> diff --git a/sound/soc/au1x/psc.h b/sound/soc/au1x/psc.h
>> index b30eadd..c59b9e5 100644
>> --- a/sound/soc/au1x/psc.h
>> +++ b/sound/soc/au1x/psc.h
>> @@ -1,7 +1,7 @@
>>  /*
>> - * Au12x0/Au1550 PSC ALSA ASoC audio support.
>> + * Alchemy ALSA ASoC audio support.
>>   *
>> - * (c) 2007-2008 MSC Vertriebsges.m.b.H.,
>> + * (c) 2007-2011 MSC Vertriebsges.m.b.H.,
>>   *     Manuel Lauss <manuel.lauss@gmail.com>
>>   *
>>   * This program is free software; you can redistribute it and/or modify
>> @@ -13,7 +13,13 @@
>>  #ifndef _AU1X_PCM_H
>>  #define _AU1X_PCM_H
>>
>> -/* DBDMA helpers */
>> +#define PCM_TX 0
>> +#define PCM_RX 1
>
> Is there any need for these macros, SNDRV_PCM_STREAM_PLAYBACK and SNDRV_PCMP_STREAM_CAPTURE should be used for this type of logic.

As long as they both stay 0 and 1, might as well use them directly.  Fixed.
I'll remove them in a follow-up patch for the PSC drivers.

Thanks for the review!
        Manuel Lauss

^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2011-07-25 11:03 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-07-24 10:11 [PATCH V4 0/3] ASoC for Alchemy Au1000/1500/1100 Manuel Lauss
2011-07-24 10:11 ` Manuel Lauss
2011-07-24 10:11 ` [PATCH V4 1/3] ASoC: Alchemy AC97C/I2SC audio support Manuel Lauss
2011-07-24 10:11   ` Manuel Lauss
2011-07-25  9:32   ` Liam Girdwood
2011-07-25  9:32     ` Liam Girdwood
2011-07-25 11:02     ` Manuel Lauss
2011-07-25 11:02       ` Manuel Lauss
2011-07-24 10:11 ` [PATCH V4 2/3] ASoC: Add a DB1x00 AC97 machine driver Manuel Lauss
2011-07-24 10:11   ` Manuel Lauss
2011-07-25  9:34   ` Liam Girdwood
2011-07-25  9:34     ` Liam Girdwood
2011-07-24 10:11 ` [PATCH V4 3/3] ALSA: deprecate MIPS AU1X00 AC97 driver Manuel Lauss
2011-07-24 10:11   ` Manuel Lauss
2011-07-25  9:34   ` Liam Girdwood
2011-07-25  9:34     ` Liam Girdwood

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.