[alsa-devel] [PATCH V4 0/3] ASoC for Alchemy Au1000/1500/1100
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
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)
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
-- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
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 -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
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");
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
-- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
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
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 -- To unsubscribe from this list: send the line "unsubscribe alsa-devel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
participants (2)
-
Liam Girdwood
-
Manuel Lauss