[alsa-devel] [patch 1/2] alsa: ASoC driver for s6000 I2S interface
This patch adds a driver for the I2S interface found on Stretch s6000 family processors.
Signed-off-by: Daniel Glöckner dg@emlix.com --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/s6000/Kconfig | 10 + sound/soc/s6000/Makefile | 6 + sound/soc/s6000/s6000-i2s.c | 421 +++++++++++++++++++++++++++++++++++ sound/soc/s6000/s6000-i2s.h | 167 ++++++++++++++ sound/soc/s6000/s6000-pcm.c | 518 +++++++++++++++++++++++++++++++++++++++++++ sound/soc/s6000/s6000-pcm.h | 41 ++++ 8 files changed, 1165 insertions(+), 0 deletions(-) create mode 100644 sound/soc/s6000/Kconfig create mode 100644 sound/soc/s6000/Makefile create mode 100644 sound/soc/s6000/s6000-i2s.c create mode 100644 sound/soc/s6000/s6000-i2s.h create mode 100644 sound/soc/s6000/s6000-pcm.c create mode 100644 sound/soc/s6000/s6000-pcm.h
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index ef025c6..56e5198 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -31,6 +31,7 @@ source "sound/soc/fsl/Kconfig" source "sound/soc/omap/Kconfig" source "sound/soc/pxa/Kconfig" source "sound/soc/s3c24xx/Kconfig" +source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig"
# Supported codecs diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 86a9b1f..6b33d80 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -10,4 +10,5 @@ obj-$(CONFIG_SND_SOC) += fsl/ obj-$(CONFIG_SND_SOC) += omap/ obj-$(CONFIG_SND_SOC) += pxa/ obj-$(CONFIG_SND_SOC) += s3c24xx/ +obj-$(CONFIG_SND_SOC) += s6000/ obj-$(CONFIG_SND_SOC) += sh/ diff --git a/sound/soc/s6000/Kconfig b/sound/soc/s6000/Kconfig new file mode 100644 index 0000000..5612ed2 --- /dev/null +++ b/sound/soc/s6000/Kconfig @@ -0,0 +1,10 @@ +config SND_S6000_SOC + tristate "SoC Audio for the Stretch s6000 family" + depends on XTENSA_VARIANT_S6000 && SND_SOC + help + Say Y or M if you want to add support for codecs attached to + s6000 family chips. You will also need to select the platform + to support below. + +config SND_S6000_SOC_I2S + tristate diff --git a/sound/soc/s6000/Makefile b/sound/soc/s6000/Makefile new file mode 100644 index 0000000..df15f87 --- /dev/null +++ b/sound/soc/s6000/Makefile @@ -0,0 +1,6 @@ +# s6000 Platform Support +snd-soc-s6000-objs := s6000-pcm.o +snd-soc-s6000-i2s-objs := s6000-i2s.o + +obj-$(CONFIG_SND_S6000_SOC) += snd-soc-s6000.o +obj-$(CONFIG_SND_S6000_SOC_I2S) += snd-soc-s6000-i2s.o diff --git a/sound/soc/s6000/s6000-i2s.c b/sound/soc/s6000/s6000-i2s.c new file mode 100644 index 0000000..719a3c4 --- /dev/null +++ b/sound/soc/s6000/s6000-i2s.c @@ -0,0 +1,421 @@ +/* + * ALSA SoC I2S Audio Layer for the Stretch S6000 family + * + * Author: Daniel Gloeckner, dg@emlix.com + * Copyright: (C) 2009 emlix GmbH info@emlix.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/clk.h> +#include <linux/interrupt.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/initval.h> +#include <sound/soc.h> + +#include "s6000-i2s.h" +#include "s6000-pcm.h" + +static void s6000_i2s_wait_disabled(struct s6000_i2s_dev *dev) +{ + int channel; + int n = 50; + for (channel = 0; channel < 2; channel++) { + while (--n >= 0) { + int v = s6_i2s_read_reg(dev, S6_I2S_ENABLE(channel)); + if ((v & S6_I2S_IS_ENABLED) + || !(v & (S6_I2S_DMA_ACTIVE | S6_I2S_IS_BUSY))) + break; + udelay(20); + } + } + if (n < 0) + printk(KERN_WARNING "s6000-i2s: timeout disabling interfaces"); +} + +static int s6000_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct s6000_i2s_dev *dev = cpu_dai->private_data; + u32 w; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + w = S6_I2S_SCK_IN | S6_I2S_WS_IN; + break; + case SND_SOC_DAIFMT_CBS_CFM: + w = S6_I2S_SCK_OUT | S6_I2S_WS_IN; + break; + case SND_SOC_DAIFMT_CBM_CFS: + w = S6_I2S_SCK_IN | S6_I2S_WS_OUT; + break; + case SND_SOC_DAIFMT_CBS_CFS: + w = S6_I2S_SCK_OUT | S6_I2S_WS_OUT; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + w |= S6_I2S_LEFT_FIRST; + break; + case SND_SOC_DAIFMT_IB_NF: + w |= S6_I2S_RIGHT_FIRST; + break; + default: + return -EINVAL; + } + + s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(0), + S6_I2S_FIRST | S6_I2S_WS_DIR | S6_I2S_SCK_DIR, w); + s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(1), + S6_I2S_FIRST | S6_I2S_WS_DIR | S6_I2S_SCK_DIR, w); + + return 0; +} + +static int s6000_i2s_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div) +{ + struct s6000_i2s_dev *dev = dai->private_data; + + if (!div || (div & 1) || div > (S6_I2S_DIV_MASK + 1) * 2) + return -EINVAL; + + s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(div_id), + S6_I2S_DIV_MASK, div / 2 - 1); + return 0; +} + +static int s6000_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct s6000_i2s_dev *dev = dai->private_data; + int interf; + u32 w = 0; + + if (dev->wide) + interf = 0; + else { + w |= (((params_channels(params) - 2) / 2) + << S6_I2S_CHANNELS_SHIFT) & S6_I2S_CHANNELS_MASK; + interf = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ? dev->channel_out : dev->channel_in; + } + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + w |= S6_I2S_16BIT | S6_I2S_MEM_16BIT; + break; + case SNDRV_PCM_FORMAT_S32_LE: + w |= S6_I2S_32BIT | S6_I2S_MEM_32BIT; + break; + default: + printk(KERN_WARNING "s6000-i2s: unsupported PCM format %x\n", + params_format(params)); + return -EINVAL; + } + + if (s6_i2s_read_reg(dev, S6_I2S_INTERFACE_CFG(interf)) + & S6_I2S_IS_ENABLED) { + printk(KERN_ERR "s6000-i2s: interface already enabled\n"); + return -EBUSY; + } + + s6_i2s_mod_reg(dev, S6_I2S_INTERFACE_CFG(interf), + S6_I2S_CHANNELS_MASK|S6_I2S_MEM_MASK|S6_I2S_BITS_MASK, + w); + + return 0; +} + +static int __devinit s6000_i2s_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + struct s6000_i2s_dev *dev; + struct resource *scbmem, *sifmem, *region, *dma_in, *dma_out; + struct s6000_snd_platform_data *pdata; + u8 __iomem *mmio; + int ret; + + pdata = pdev->dev.platform_data; + + scbmem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!scbmem) { + dev_err(&pdev->dev, "no mem resource?\n"); + ret = -ENODEV; + goto err_release_none; + } + + region = request_mem_region(scbmem->start, + scbmem->end - scbmem->start + 1, + pdev->name); + if (!region) { + dev_err(&pdev->dev, "I2S SCB region already claimed\n"); + ret = -EBUSY; + goto err_release_none; + } + + mmio = ioremap(scbmem->start, scbmem->end - scbmem->start + 1); + if (!mmio) { + dev_err(&pdev->dev, "can't ioremap SCB region\n"); + ret = -ENOMEM; + goto err_release_scb; + } + + sifmem = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!sifmem) { + dev_err(&pdev->dev, "no second mem resource?\n"); + ret = -ENODEV; + goto err_release_map; + } + + region = request_mem_region(sifmem->start, + sifmem->end - sifmem->start + 1, + pdev->name); + if (!region) { + dev_err(&pdev->dev, "I2S SIF region already claimed\n"); + ret = -EBUSY; + goto err_release_map; + } + + dma_in = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!dma_in) { + dev_err(&pdev->dev, "no dma resource?\n"); + ret = -ENODEV; + goto err_release_sif; + } + + region = request_mem_region(dma_in->start, + dma_in->end - dma_in->start + 1, + pdev->name); + if (!region) { + dev_err(&pdev->dev, "I2S DMA region already claimed\n"); + ret = -EBUSY; + goto err_release_sif; + } + + dma_out = 0; + if (pdata->lines_out) { + if (pdata->lines_in) { + dma_out = platform_get_resource(pdev, IORESOURCE_DMA, + 1); + if (!dma_out) { + dev_err(&pdev->dev, + "no second dma resource?\n"); + ret = -ENODEV; + goto err_release_dma1; + } + + region = request_mem_region(dma_out->start, + dma_out->end + - dma_out->start + 1, + pdev->name); + if (!region) { + dev_err(&pdev->dev, + "I2S DMA region already claimed\n"); + ret = -EBUSY; + goto err_release_dma1; + } + } else { + dma_out = dma_in; + dma_in = 0; + } + } + + dev = kzalloc(sizeof(struct s6000_i2s_dev), GFP_KERNEL); + if (!dev) { + ret = -ENOMEM; + goto err_release_dma2; + } + + dai->private_data = dev; + dai->dma_data = &dev->dma_params; + + dev->sifbase = sifmem->start; + dev->scbbase = mmio; + + s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, 0); + s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_CLEAR, + S6_I2S_INT_ALIGNMENT | + S6_I2S_INT_UNDERRUN | + S6_I2S_INT_OVERRUN); + + s6000_i2s_stop_channel(dev, 0); + s6000_i2s_stop_channel(dev, 1); + s6000_i2s_wait_disabled(dev); + + dev->wide = pdata->wide; + dev->channel_in = pdata->channel_in; + dev->channel_out = pdata->channel_out; + dev->lines_in = pdata->lines_in; + dev->lines_out = pdata->lines_out; + + s6_i2s_write_reg(dev, S6_I2S_MODE, + dev->wide ? S6_I2S_WIDE : S6_I2S_DUAL); + + if (dev->wide) { + int i; + dev->channel_in = 0; + dev->channel_out = 1; + dai->capture.channels_min = 2 * dev->lines_in; + dai->capture.channels_max = dai->capture.channels_min; + dai->playback.channels_min = 2 * dev->lines_out; + dai->playback.channels_max = dai->playback.channels_min; + + for (i = 0; i < dev->lines_out; i++) + s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i), S6_I2S_OUT); + + for (; i < S6_I2S_NUM_LINES - dev->lines_in; i++) + s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i), + S6_I2S_UNUSED); + + for (; i < S6_I2S_NUM_LINES; i++) + s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(i), S6_I2S_IN); + } else { + unsigned int cfg[2] = {S6_I2S_UNUSED, S6_I2S_UNUSED}; + + dev->lines_in = (dev->lines_in != 0); + dev->lines_out = (dev->lines_out != 0); + dai->capture.channels_min = 2 * dev->lines_in; + dai->capture.channels_max = 8 * dev->lines_in; + dai->playback.channels_min = 2 * dev->lines_out; + dai->playback.channels_max = 8 * dev->lines_out; + + cfg[dev->channel_in] = S6_I2S_IN; + cfg[dev->channel_out] = S6_I2S_OUT; + + s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(0), cfg[0]); + s6_i2s_write_reg(dev, S6_I2S_DATA_CFG(1), cfg[1]); + } + + dev->dma_params.dma_in = dma_in ? dma_in->start : 0; + dev->dma_params.dma_out = dma_out ? dma_out->start : 0; + dev->dma_params.sif_in = dev->sifbase + (dev->channel_in ? + S6_I2S_SIF_PORT1 : S6_I2S_SIF_PORT0); + dev->dma_params.sif_out = dev->sifbase + (dev->channel_out ? + S6_I2S_SIF_PORT1 : S6_I2S_SIF_PORT0); + dev->dma_params.same_rate = pdata->same_rate | pdata->wide; + + dev->dma_params.irq = platform_get_irq(pdev, 0); + if (dev->dma_params.irq < 0) { + dev_err(&pdev->dev, "no irq resource?\n"); + ret = -ENODEV; + goto err_release_dev; + } + + s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, + S6_I2S_INT_ALIGNMENT | + S6_I2S_INT_UNDERRUN | + S6_I2S_INT_OVERRUN); + + return 0; + +err_release_dev: + kfree(dev); +err_release_dma2: + if (dma_out) + release_mem_region(dma_out->start, + dma_out->end - dma_out->start + 1); +err_release_dma1: + if (dma_in) + release_mem_region(dma_in->start, + dma_in->end - dma_in->start + 1); +err_release_sif: + release_mem_region(sifmem->start, (sifmem->end - sifmem->start) + 1); +err_release_map: + iounmap(mmio); +err_release_scb: + release_mem_region(scbmem->start, (scbmem->end - scbmem->start) + 1); +err_release_none: + return ret; +} + +static void __devexit s6000_i2s_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + struct s6000_i2s_dev *dev = dai->private_data; + struct resource *region; + void __iomem *mmio = dev->scbbase; + + s6000_i2s_stop_channel(dev, 0); + s6000_i2s_stop_channel(dev, 1); + kfree(dev); + dai->private_data = 0; + + s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, 0); + + region = platform_get_resource(pdev, IORESOURCE_DMA, 0); + release_region(region->start, region->end - region->start + 1); + + if (dev->lines_in && dev->lines_out) { + region = platform_get_resource(pdev, IORESOURCE_DMA, 1); + release_region(region->start, region->end - region->start + 1); + } + + region = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_region(region->start, (region->end - region->start) + 1); + + iounmap(mmio); + region = platform_get_resource(pdev, IORESOURCE_IO, 0); + release_mem_region(region->start, (region->end - region->start) + 1); +} + +#define S6000_I2S_RATES (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT | \ + SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000) +#define S6000_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai s6000_i2s_dai = { + .name = "s6000-i2s", + .id = 0, + .probe = s6000_i2s_probe, + .remove = __devexit_p(s6000_i2s_remove), + .playback = { + .channels_min = 2, + .channels_max = 8, + .formats = S6000_I2S_FORMATS, + .rates = S6000_I2S_RATES, + .rate_min = 0, + .rate_max = 1562500, + }, + .capture = { + .channels_min = 2, + .channels_max = 8, + .formats = S6000_I2S_FORMATS, + .rates = S6000_I2S_RATES, + .rate_min = 0, + .rate_max = 1562500, + }, + .ops = { + .set_fmt = s6000_i2s_set_dai_fmt, + .set_clkdiv = s6000_i2s_set_clkdiv, + .hw_params = s6000_i2s_hw_params, + } +} +EXPORT_SYMBOL_GPL(s6000_i2s_dai); + +static int __init s6000_i2s_init(void) +{ + return snd_soc_register_dai(&s6000_i2s_dai); +} +module_init(s6000_i2s_init); + +static void __exit s6000_i2s_exit(void) +{ + snd_soc_unregister_dai(&s6000_i2s_dai); +} +module_exit(s6000_i2s_exit); + +MODULE_AUTHOR("Daniel Gloeckner"); +MODULE_DESCRIPTION("Stretch s6000 family I2S SoC Interface"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s6000/s6000-i2s.h b/sound/soc/s6000/s6000-i2s.h new file mode 100644 index 0000000..4918f95 --- /dev/null +++ b/sound/soc/s6000/s6000-i2s.h @@ -0,0 +1,167 @@ +/* + * ALSA SoC I2S Audio Layer for the Stretch s6000 family + * + * Author: Daniel Gloeckner, dg@emlix.com + * Copyright: (C) 2009 emlix GmbH info@emlix.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _S6000_I2S_H +#define _S6000_I2S_H + +#include <linux/io.h> +#include "s6000-pcm.h" + +extern struct snd_soc_dai s6000_i2s_dai; + +struct s6000_i2s_dev { + dma_addr_t sifbase; + u8 __iomem *scbbase; + int wide; + int channel_in; + int channel_out; + int lines_in; + int lines_out; + struct s6000_pcm_dma_params dma_params; +}; + +#define S6_I2S_INTERRUPT_STATUS 0x00 +#define S6_I2S_INT_OVERRUN 1 +#define S6_I2S_INT_UNDERRUN 2 +#define S6_I2S_INT_ALIGNMENT 4 +#define S6_I2S_INTERRUPT_ENABLE 0x04 +#define S6_I2S_INTERRUPT_RAW 0x08 +#define S6_I2S_INTERRUPT_CLEAR 0x0C +#define S6_I2S_INTERRUPT_SET 0x10 +#define S6_I2S_MODE 0x20 +#define S6_I2S_DUAL 0 +#define S6_I2S_WIDE 1 +#define S6_I2S_TX_DEFAULT 0x24 +#define S6_I2S_DATA_CFG(c) (0x40 + 0x10 * (c)) +#define S6_I2S_IN 0 +#define S6_I2S_OUT 1 +#define S6_I2S_UNUSED 2 +#define S6_I2S_INTERFACE_CFG(c) (0x44 + 0x10 * (c)) +#define S6_I2S_DIV_MASK 0x001fff +#define S6_I2S_16BIT 0x000000 +#define S6_I2S_20BIT 0x002000 +#define S6_I2S_24BIT 0x004000 +#define S6_I2S_32BIT 0x006000 +#define S6_I2S_BITS_MASK 0x006000 +#define S6_I2S_MEM_16BIT 0x000000 +#define S6_I2S_MEM_32BIT 0x008000 +#define S6_I2S_MEM_MASK 0x008000 +#define S6_I2S_CHANNELS_SHIFT 16 +#define S6_I2S_CHANNELS_MASK 0x030000 +#define S6_I2S_SCK_IN 0x000000 +#define S6_I2S_SCK_OUT 0x040000 +#define S6_I2S_SCK_DIR 0x040000 +#define S6_I2S_WS_IN 0x000000 +#define S6_I2S_WS_OUT 0x080000 +#define S6_I2S_WS_DIR 0x080000 +#define S6_I2S_LEFT_FIRST 0x000000 +#define S6_I2S_RIGHT_FIRST 0x100000 +#define S6_I2S_FIRST 0x100000 +#define S6_I2S_CUR_SCK 0x200000 +#define S6_I2S_CUR_WS 0x400000 +#define S6_I2S_ENABLE(c) (0x48 + 0x10 * (c)) +#define S6_I2S_DISABLE_IF 0x02 +#define S6_I2S_ENABLE_IF 0x03 +#define S6_I2S_IS_BUSY 0x04 +#define S6_I2S_DMA_ACTIVE 0x08 +#define S6_I2S_IS_ENABLED 0x10 + +#define S6_I2S_NUM_LINES 4 + +#define S6_I2S_SIF_PORT0 0x0000000 +#define S6_I2S_SIF_PORT1 0x0000080 /* docs say 0x0000010 */ + +static inline void s6_i2s_write_reg(struct s6000_i2s_dev *dev, int reg, u32 val) +{ + writel(val, dev->scbbase + reg); +} + +static inline u32 s6_i2s_read_reg(struct s6000_i2s_dev *dev, int reg) +{ + return readl(dev->scbbase + reg); +} + +static inline void s6_i2s_mod_reg(struct s6000_i2s_dev *dev, int reg, + u32 mask, u32 val) +{ + val ^= s6_i2s_read_reg(dev, reg) & ~mask; + s6_i2s_write_reg(dev, reg, val); +} + +static inline void s6000_i2s_start_channel(struct s6000_i2s_dev *dev, + int channel) +{ + int i, j, cur, prev; + + /* + * Wait for WCLK to toggle 5 times before enabling the channel + * s6000 Family Datasheet 3.6.4: + * "At least two cycles of WS must occur between commands + * to disable or enable the interface" + */ + j = 0; + prev = ~S6_I2S_CUR_WS; + for (i = 1000000; --i && j < 6; ) { + cur = s6_i2s_read_reg(dev, S6_I2S_INTERFACE_CFG(channel)) + & S6_I2S_CUR_WS; + if (prev != cur) { + prev = cur; + j++; + } + } + if (j < 6) + printk(KERN_WARNING "s6000-i2s: timeout waiting for WCLK\n"); + + s6_i2s_write_reg(dev, S6_I2S_ENABLE(channel), S6_I2S_ENABLE_IF); +} + +static inline void s6000_i2s_stop_channel(struct s6000_i2s_dev *dev, + int channel) +{ + s6_i2s_write_reg(dev, S6_I2S_ENABLE(channel), S6_I2S_DISABLE_IF); +} + +static inline void s6000_i2s_start(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct s6000_i2s_dev *dev = rtd->dai->cpu_dai->private_data; + int channel; + + channel = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + dev->channel_out : dev->channel_in; + + s6000_i2s_start_channel(dev, channel); +} + +static inline void s6000_i2s_stop(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct s6000_i2s_dev *dev = rtd->dai->cpu_dai->private_data; + int channel; + + channel = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? + dev->channel_out : dev->channel_in; + + s6000_i2s_stop_channel(dev, channel); +} + +static inline unsigned int s6000_i2s_int_sources(struct s6000_i2s_dev *dev) +{ + unsigned int pending; + pending = s6_i2s_read_reg(dev, S6_I2S_INTERRUPT_RAW); + pending &= S6_I2S_INT_ALIGNMENT | + S6_I2S_INT_UNDERRUN | + S6_I2S_INT_OVERRUN; + s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_CLEAR, pending); + + return pending; +} +#endif diff --git a/sound/soc/s6000/s6000-pcm.c b/sound/soc/s6000/s6000-pcm.c new file mode 100644 index 0000000..90b546c --- /dev/null +++ b/sound/soc/s6000/s6000-pcm.c @@ -0,0 +1,518 @@ +/* + * ALSA PCM interface for the Stetch s6000 family + * + * Author: Daniel Gloeckner, dg@emlix.com + * Copyright: (C) 2009 emlix GmbH info@emlix.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> + +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include <asm/dma.h> +#include <variant/dmac.h> + +#include "s6000-i2s.h" +#include "s6000-pcm.h" + +#define S6000_PCM_DEBUG 0 +#if S6000_PCM_DEBUG +#define DPRINTK(x...) printk(KERN_DEBUG x) +#else +#define DPRINTK(x...) +#endif + +#define S6_PCM_PREALLOCATE_SIZE (96 * 1024) +#define S6_PCM_PREALLOCATE_MAX (2048 * 1024) + +static struct snd_pcm_hardware s6000_pcm_hardware = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_JOINT_DUPLEX), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE), + .rates = (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT | + SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000), + .rate_min = 0, + .rate_max = 1562500, + .channels_min = 2, + .channels_max = 8, + .buffer_bytes_max = 0x7ffffff0, + .period_bytes_min = 16, + .period_bytes_max = 0xfffff0, + .periods_min = 2, + .periods_max = 1024, /* no limit */ + .fifo_size = 0, +}; + +struct s6000_runtime_data { + spinlock_t lock; + int period; /* current DMA period */ +}; + +static void s6000_pcm_enqueue_dma(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct s6000_runtime_data *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + int channel; + unsigned int period_size; + unsigned int dma_offset; + dma_addr_t dma_pos; + dma_addr_t src, dst; + + period_size = snd_pcm_lib_period_bytes(substream); + dma_offset = prtd->period * period_size; + dma_pos = runtime->dma_addr + dma_offset; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + src = dma_pos; + dst = par->sif_out; + channel = par->dma_out; + } else { + src = par->sif_in; + dst = dma_pos; + channel = par->dma_in; + } + + if (!s6dmac_channel_enabled(DMA_MASK_DMAC(channel), + DMA_INDEX_CHNL(channel))) + return; + + if (s6dmac_fifo_full(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel))) { + printk(KERN_ERR "s6000-pcm: fifo full\n"); + return; + } + + BUG_ON(period_size & 15); + s6dmac_put_fifo(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel), + src, dst, period_size); + + prtd->period++; + if (unlikely(prtd->period >= runtime->periods)) + prtd->period = 0; +} + +static irqreturn_t s6000_pcm_irq(int irq, void *data) +{ + struct snd_pcm *pcm = data; + struct snd_soc_pcm_runtime *runtime = pcm->private_data; + struct s6000_pcm_dma_params *params = runtime->dai->cpu_dai->dma_data; + struct snd_pcm_substream *playback, *capture; + struct s6000_runtime_data *prtd; + unsigned int i2s_errors; + int i, ret = IRQ_NONE; + u32 channel[2] = { + [SNDRV_PCM_STREAM_PLAYBACK] = params->dma_out, + [SNDRV_PCM_STREAM_CAPTURE] = params->dma_in + }; + + i2s_errors = s6000_i2s_int_sources(runtime->dai->cpu_dai->private_data); + playback = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + capture = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + + if (unlikely(i2s_errors & S6_I2S_INT_ALIGNMENT)) { + printk(KERN_ERR "s6000-i2s: Alignment Error\n"); + ret = IRQ_HANDLED; + } + + if (unlikely(i2s_errors & S6_I2S_INT_UNDERRUN) && + playback->runtime && + snd_pcm_running(playback)) { + printk(KERN_WARNING "s6000-i2s: Tx Underrun\n"); + s6000_i2s_start(playback); + + prtd = playback->runtime->private_data; + spin_lock(&prtd->lock); + s6000_pcm_enqueue_dma(playback); + /* a second descriptor will be queued below */ + spin_unlock(&prtd->lock); + + ret = IRQ_HANDLED; + } + + for (i = 0; i < 2; ++i) { + struct snd_pcm_substream *substream = pcm->streams[i].substream; + unsigned int pending; + + if (!channel[i]) + continue; + + pending = s6dmac_int_sources(DMA_MASK_DMAC(channel[i]), + DMA_INDEX_CHNL(channel[i])); + + if (pending & 1) { + ret = IRQ_HANDLED; + if (likely(substream->runtime && + snd_pcm_running(substream))) { + snd_pcm_period_elapsed(substream); + DPRINTK("period elapsed %x %x\n", + s6dmac_cur_src(DMA_MASK_DMAC(channel[i]), + DMA_INDEX_CHNL(channel[i])), + s6dmac_cur_dst(DMA_MASK_DMAC(channel[i]), + DMA_INDEX_CHNL(channel[i]))); + prtd = substream->runtime->private_data; + spin_lock(&prtd->lock); + s6000_pcm_enqueue_dma(substream); + spin_unlock(&prtd->lock); + } + } + + if (unlikely(pending & ~7)) { + if (pending & (1 << 3)) + printk(KERN_WARNING + "s6000-pcm: DMA %x Underflow\n", + channel[i]); + if (pending & (1 << 4)) + printk(KERN_WARNING + "s6000-pcm: DMA %x Overflow\n", + channel[i]); + if (pending & 0x1e0) + printk(KERN_WARNING + "s6000-pcm: DMA %x Master Error " + "(mask %x)\n", + channel[i], pending >> 5); + + } + } + + if (unlikely(i2s_errors & S6_I2S_INT_OVERRUN) && + capture->runtime && + snd_pcm_running(capture)) { + printk(KERN_WARNING "s6000-i2s: Rx Overrun\n"); + + prtd = capture->runtime->private_data; + spin_lock(&prtd->lock); + s6000_pcm_enqueue_dma(capture); + spin_unlock(&prtd->lock); + + s6000_i2s_start(capture); + ret = IRQ_HANDLED; + } + + return ret; +} + +int s6000_pcm_start(struct snd_pcm_substream *substream) +{ + struct s6000_runtime_data *prtd = substream->runtime->private_data; + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + unsigned long flags; + + spin_lock_irqsave(&prtd->lock, flags); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + s6dmac_enable_chan(DMA_MASK_DMAC(par->dma_out), + DMA_INDEX_CHNL(par->dma_out), + 1, 0, 1, 0, 0, 0, 0, 4, -1, 0, 0, 1); + else + s6dmac_enable_chan(DMA_MASK_DMAC(par->dma_in), + DMA_INDEX_CHNL(par->dma_in), + 1, 0, 0, 1, 0, 0, 0, 4, -1, 0, 0, 1); + + s6000_pcm_enqueue_dma(substream); + s6000_pcm_enqueue_dma(substream); + + spin_unlock_irqrestore(&prtd->lock, flags); + + return 0; +} + +int s6000_pcm_stop(struct snd_pcm_substream *substream) +{ + struct s6000_runtime_data *prtd = substream->runtime->private_data; + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + unsigned long flags; + u32 channel; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + channel = par->dma_out; + else + channel = par->dma_in; + + s6dmac_set_terminal_count(DMA_MASK_DMAC(channel), + DMA_INDEX_CHNL(channel), 0); + + spin_lock_irqsave(&prtd->lock, flags); + + s6dmac_disable_chan(DMA_MASK_DMAC(channel), DMA_INDEX_CHNL(channel)); + + spin_unlock_irqrestore(&prtd->lock, flags); + + return 0; +} + +static int s6000_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + s6000_i2s_start(substream); + ret = s6000_pcm_start(substream); + } else { + ret = s6000_pcm_start(substream); + s6000_i2s_start(substream); + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + s6000_i2s_stop(substream); + ret = s6000_pcm_stop(substream); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int s6000_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct s6000_runtime_data *prtd = substream->runtime->private_data; + + prtd->period = 0; + + return 0; +} + +static snd_pcm_uframes_t s6000_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct s6000_runtime_data *prtd = runtime->private_data; + unsigned long flags; + unsigned int offset; + dma_addr_t count; + + spin_lock_irqsave(&prtd->lock, flags); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + count = s6dmac_cur_src(DMA_MASK_DMAC(par->dma_out), + DMA_INDEX_CHNL(par->dma_out)); + else + count = s6dmac_cur_dst(DMA_MASK_DMAC(par->dma_in), + DMA_INDEX_CHNL(par->dma_in)); + + count -= runtime->dma_addr; + + spin_unlock_irqrestore(&prtd->lock, flags); + + offset = bytes_to_frames(runtime, count); + if (unlikely(offset >= runtime->buffer_size)) + offset = 0; + + return offset; +} + +static int s6000_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct s6000_runtime_data *prtd; + int ret; + + snd_soc_set_runtime_hwparams(substream, &s6000_pcm_hardware); + + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 16); + if (ret < 0) + return ret; + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 16); + if (ret < 0) + return ret; + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + + if (par->same_rate) { + int rate; + spin_lock(&par->lock); /* needed? */ + rate = par->rate; + spin_unlock(&par->lock); + if (rate != -1) { + ret = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_RATE, + rate, rate); + if (ret < 0) + return ret; + } + } + + prtd = kzalloc(sizeof(struct s6000_runtime_data), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + spin_lock_init(&prtd->lock); + + runtime->private_data = prtd; + + return 0; +} + +static int s6000_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct s6000_runtime_data *prtd = runtime->private_data; + + kfree(prtd); + + return 0; +} + +static int s6000_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + int ret; + ret = snd_pcm_lib_malloc_pages(substream, + params_buffer_bytes(hw_params)); + if (ret < 0) { + printk(KERN_WARNING "s6000-pcm: allocation of memory failed\n"); + return ret; + } + + if (par->same_rate) { + spin_lock(&par->lock); + if (par->rate == -1 || + !(par->in_use & ~(1 << substream->stream))) { + par->rate = params_rate(hw_params); + par->in_use |= 1 << substream->stream; + } else if (params_rate(hw_params) != par->rate) { + snd_pcm_lib_free_pages(substream); + par->in_use &= ~(1 << substream->stream); + ret = -EBUSY; + } + spin_unlock(&par->lock); + } + return ret; +} + +static int s6000_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = substream->private_data; + struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data; + + spin_lock(&par->lock); + par->in_use &= ~(1 << substream->stream); + if (!par->in_use) + par->rate = -1; + spin_unlock(&par->lock); + + return snd_pcm_lib_free_pages(substream); +} + +struct snd_pcm_ops s6000_pcm_ops = { + .open = s6000_pcm_open, + .close = s6000_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = s6000_pcm_hw_params, + .hw_free = s6000_pcm_hw_free, + .trigger = s6000_pcm_trigger, + .prepare = s6000_pcm_prepare, + .pointer = s6000_pcm_pointer, +}; + +static void s6000_pcm_free(struct snd_pcm *pcm) +{ + struct snd_soc_pcm_runtime *runtime = pcm->private_data; + struct s6000_pcm_dma_params *params = runtime->dai->cpu_dai->dma_data; + + free_irq(params->irq, pcm); + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static u64 s6000_pcm_dmamask = DMA_32BIT_MASK; + +static int s6000_pcm_new(struct snd_card *card, + struct snd_soc_dai *dai, struct snd_pcm *pcm) +{ + struct snd_soc_pcm_runtime *runtime = pcm->private_data; + struct s6000_pcm_dma_params *params = runtime->dai->cpu_dai->dma_data; + int res; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &s6000_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_32BIT_MASK; + + if (params->dma_in) { + s6dmac_disable_chan(DMA_MASK_DMAC(params->dma_in), + DMA_INDEX_CHNL(params->dma_in)); + s6dmac_int_sources(DMA_MASK_DMAC(params->dma_in), + DMA_INDEX_CHNL(params->dma_in)); + } + + if (params->dma_out) { + s6dmac_disable_chan(DMA_MASK_DMAC(params->dma_out), + DMA_INDEX_CHNL(params->dma_out)); + s6dmac_int_sources(DMA_MASK_DMAC(params->dma_out), + DMA_INDEX_CHNL(params->dma_out)); + } + + res = request_irq(params->irq, s6000_pcm_irq, IRQF_SHARED, + s6000_soc_platform.name, pcm); + if (res) { + printk(KERN_ERR "s6000-pcm couldn't get IRQ\n"); + return res; + } + + res = snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_DEV, + card->dev, + S6_PCM_PREALLOCATE_SIZE, + S6_PCM_PREALLOCATE_MAX); + if (res) + printk(KERN_WARNING "s6000-pcm: preallocation failed\n"); + + spin_lock_init(¶ms->lock); + params->in_use = 0; + params->rate = -1; + return 0; +} + +struct snd_soc_platform s6000_soc_platform = { + .name = "s6000-audio", + .pcm_ops = &s6000_pcm_ops, + .pcm_new = s6000_pcm_new, + .pcm_free = s6000_pcm_free, +}; +EXPORT_SYMBOL_GPL(s6000_soc_platform); + +static int __init s6000_pcm_init(void) +{ + return snd_soc_register_platform(&s6000_soc_platform); +} +module_init(s6000_pcm_init); + +static void __exit s6000_pcm_exit(void) +{ + snd_soc_unregister_platform(&s6000_soc_platform); +} +module_exit(s6000_pcm_exit); + +MODULE_AUTHOR("Daniel Gloeckner"); +MODULE_DESCRIPTION("Stretch s6000 family PCM DMA module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/s6000/s6000-pcm.h b/sound/soc/s6000/s6000-pcm.h new file mode 100644 index 0000000..8d7305f --- /dev/null +++ b/sound/soc/s6000/s6000-pcm.h @@ -0,0 +1,41 @@ +/* + * ALSA PCM interface for the Stretch s6000 family + * + * Author: Daniel Gloeckner, dg@emlix.com + * Copyright: (C) 2009 emlix GmbH info@emlix.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _S6000_PCM_H +#define _S6000_PCM_H + +struct s6000_pcm_dma_params { + dma_addr_t sif_in; + dma_addr_t sif_out; + u32 dma_in; + u32 dma_out; + int irq; + int same_rate; + + spinlock_t lock; + int in_use; + int rate; +}; + +struct s6000_snd_platform_data { + int lines_in; + int lines_out; + int channel_in; + int channel_out; + int wide; + int same_rate; +}; + +int s6000_pcm_start(struct snd_pcm_substream *substream); +int s6000_pcm_stop(struct snd_pcm_substream *substream); +extern struct snd_soc_platform s6000_soc_platform; + +#endif
This patch adds machine specific code for the audio part of the Stretch s6105 IP camera reference design.
The device uses the tlv320aic31(01) codec to generate the clock for both I2S ports of the soc. While the master clock is generated by a configurable PLL chip, the code assumes the factory default settings.
An additional kcontrol has been added to handle the special routing of the board, connecting both HPLCOM and HPROUT to the same pin of the audio jack. One of these should always be switched off.
Signed-off-by: Daniel Glöckner dg@emlix.com --- sound/soc/s6000/Kconfig | 9 ++ sound/soc/s6000/Makefile | 5 + sound/soc/s6000/s6105-ipcam.c | 286 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 300 insertions(+), 0 deletions(-) create mode 100644 sound/soc/s6000/s6105-ipcam.c
diff --git a/sound/soc/s6000/Kconfig b/sound/soc/s6000/Kconfig index 5612ed2..6cf6081 100644 --- a/sound/soc/s6000/Kconfig +++ b/sound/soc/s6000/Kconfig @@ -8,3 +8,12 @@ config SND_S6000_SOC
config SND_S6000_SOC_I2S tristate + +config SND_S6000_SOC_S6IPCAM + tristate "SoC Audio support for Stretch 6105 IP Camera" + depends on SND_S6000_SOC && XTENSA_PLATFORM_S6105 + select SND_S6000_SOC_I2S + select SND_SOC_TLV320AIC3X + help + Say Y if you want to add support for SoC audio on the + Stretch s6105 IP Camera Reference Design. diff --git a/sound/soc/s6000/Makefile b/sound/soc/s6000/Makefile index df15f87..7a61361 100644 --- a/sound/soc/s6000/Makefile +++ b/sound/soc/s6000/Makefile @@ -4,3 +4,8 @@ snd-soc-s6000-i2s-objs := s6000-i2s.o
obj-$(CONFIG_SND_S6000_SOC) += snd-soc-s6000.o obj-$(CONFIG_SND_S6000_SOC_I2S) += snd-soc-s6000-i2s.o + +# s6105 Machine Support +snd-soc-s6ipcam-objs := s6105-ipcam.o + +obj-$(CONFIG_SND_S6000_SOC_S6IPCAM) += snd-soc-s6ipcam.o diff --git a/sound/soc/s6000/s6105-ipcam.c b/sound/soc/s6000/s6105-ipcam.c new file mode 100644 index 0000000..c771e2f --- /dev/null +++ b/sound/soc/s6000/s6105-ipcam.c @@ -0,0 +1,286 @@ +/* + * ASoC driver for Stretch s6105 IP camera platform + * + * Author: Daniel Gloeckner, dg@emlix.com + * Copyright: (C) 2009 emlix GmbH info@emlix.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/timer.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <platform/irq.h> +#include <variant/dmac.h> + +#include "../codecs/tlv320aic3x.h" +#include "s6000-pcm.h" +#include "s6000-i2s.h" + +#define S6105_CODEC_CLOCK 12288000 + +static int s6105_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + int ret = 0; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM | + SND_SOC_DAIFMT_IB_IF); + if (ret < 0) + return ret; + + /* set the codec system clock */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, S6105_CODEC_CLOCK, + SND_SOC_CLOCK_OUT); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops s6105_ops = { + .hw_params = s6105_hw_params, +}; + +/* s6105 machine dapm widgets */ +static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { + SND_SOC_DAPM_LINE("Audio Out Differential", NULL), + SND_SOC_DAPM_LINE("Audio Out Stereo", NULL), + SND_SOC_DAPM_LINE("Audio In", NULL), +}; + +/* s6105 machine audio_mapnections to the codec pins */ +static const struct snd_soc_dapm_route audio_map[] = { + /* Audio Out connected to HPLOUT, HPLCOM, HPROUT */ + {"Audio Out Differential", NULL, "HPLOUT"}, + {"Audio Out Differential", NULL, "HPLCOM"}, + {"Audio Out Stereo", NULL, "HPLOUT"}, + {"Audio Out Stereo", NULL, "HPROUT"}, + + /* Audio In connected to LINE1L, LINE1R */ + {"LINE1L", NULL, "Audio In"}, + {"LINE1R", NULL, "Audio In"}, +}; + +static int output_type_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + uinfo->count = 1; + uinfo->value.enumerated.items = 2; + if (uinfo->value.enumerated.item) { + uinfo->value.enumerated.item = 1; + strcpy(uinfo->value.enumerated.name, "HPLOUT/HPROUT"); + } else { + strcpy(uinfo->value.enumerated.name, "HPLOUT/HPLCOM"); + } + return 0; +} + +static int output_type_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.enumerated.item[0] = kcontrol->private_value; + return 0; +} + +static int snd_soc_dapm_set_endpoint(struct snd_soc_codec *codec, char *name, + int status) +{ + if (status) + return snd_soc_dapm_enable_pin(codec, name); + else + return snd_soc_dapm_disable_pin(codec, name); +} + +static int output_type_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = kcontrol->private_data; + unsigned int val = (ucontrol->value.enumerated.item[0] != 0); + + if (kcontrol->private_value == val) + return 0; + kcontrol->private_value = val; + + snd_soc_dapm_set_endpoint(codec, "Audio Out Differential", !val); + snd_soc_dapm_set_endpoint(codec, "Audio Out Stereo", val); + snd_soc_dapm_sync(codec); + + return 1; +} + +static const struct snd_kcontrol_new audio_out_mux = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Output Mux", + .index = 0, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = output_type_info, + .get = output_type_get, + .put = output_type_put, + .private_value = 1 /* default to stereo */ +}; + +/* Logic for a aic3x as connected on the s6105 ip camera ref design */ +static int s6105_aic3x_init(struct snd_soc_codec *codec) +{ + /* Add s6105 specific widgets */ + snd_soc_dapm_new_controls(codec, aic3x_dapm_widgets, + ARRAY_SIZE(aic3x_dapm_widgets)); + + /* Set up s6105 specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + /* not present */ + snd_soc_dapm_nc_pin(codec, "MONO_LOUT"); + snd_soc_dapm_nc_pin(codec, "LINE2L"); + snd_soc_dapm_nc_pin(codec, "LINE2R"); + + /* not connected */ + snd_soc_dapm_nc_pin(codec, "MIC3L"); /* LINE2L on this chip */ + snd_soc_dapm_nc_pin(codec, "MIC3R"); /* LINE2R on this chip */ + snd_soc_dapm_nc_pin(codec, "LLOUT"); + snd_soc_dapm_nc_pin(codec, "RLOUT"); + snd_soc_dapm_nc_pin(codec, "HPRCOM"); + + /* always connected */ + snd_soc_dapm_enable_pin(codec, "Audio In"); + + snd_soc_dapm_set_endpoint(codec, "Audio Out Differential", + !audio_out_mux.private_value); + snd_soc_dapm_set_endpoint(codec, "Audio Out Stereo", + audio_out_mux.private_value); + + snd_soc_dapm_sync(codec); + + snd_ctl_add(codec->card, snd_ctl_new1(&audio_out_mux, codec)); + + return 0; +} + +/* s6105 digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link s6105_dai = { + .name = "TLV320AIC31", + .stream_name = "AIC31", + .cpu_dai = &s6000_i2s_dai, + .codec_dai = &aic3x_dai, + .init = s6105_aic3x_init, + .ops = &s6105_ops, +}; + +/* s6105 audio machine driver */ +static struct snd_soc_card snd_soc_card_s6105 = { + .name = "Stretch IP Camera", + .platform = &s6000_soc_platform, + .dai_link = &s6105_dai, + .num_links = 1, +}; + +/* s6105 audio private data */ +static struct aic3x_setup_data s6105_aic3x_setup = { + .i2c_bus = 0, + .i2c_address = 0x18, +}; + +/* s6105 audio subsystem */ +static struct snd_soc_device s6105_snd_devdata = { + .card = &snd_soc_card_s6105, + .codec_dev = &soc_codec_dev_aic3x, + .codec_data = &s6105_aic3x_setup, +}; + +static struct resource s6105_snd_resources[] = { + { + .start = S6_REG_I2S, + .end = S6_REG_I2S + 0x10000 - 1, + .flags = IORESOURCE_MEM + }, + { + .start = S6_MEM_I2S, + .end = S6_MEM_I2S + 0x2000000 - 1, + .flags = IORESOURCE_MEM + }, + { + .start = DMA_CHNL(S6_REG_HIFDMA, S6_HIFDMA_I2S0), + .end = DMA_CHNL(S6_REG_HIFDMA, S6_HIFDMA_I2S0) + 0x100 - 1, + .flags = IORESOURCE_DMA + }, + { + .start = DMA_CHNL(S6_REG_HIFDMA, S6_HIFDMA_I2S1), + .end = DMA_CHNL(S6_REG_HIFDMA, S6_HIFDMA_I2S1) + 0x100 - 1, + .flags = IORESOURCE_DMA + }, + { + .start = I2S_INTNUM, + .flags = IORESOURCE_IRQ, + } +}; + +static struct s6000_snd_platform_data s6105_snd_data = { + .wide = 0, + .channel_in = 0, + .channel_out = 1, + .lines_in = 1, + .lines_out = 1, + .same_rate = 1, +}; + +static struct platform_device *s6105_snd_device; + +static int __init s6105_init(void) +{ + int ret; + + s6105_snd_device = platform_device_alloc("soc-audio", -1); + if (!s6105_snd_device) + return -ENOMEM; + + platform_set_drvdata(s6105_snd_device, &s6105_snd_devdata); + s6105_snd_devdata.dev = &s6105_snd_device->dev; + s6105_snd_device->dev.platform_data = &s6105_snd_data; + + ret = platform_device_add_resources(s6105_snd_device, + s6105_snd_resources, + ARRAY_SIZE(s6105_snd_resources)); + if (ret) { + platform_device_put(s6105_snd_device); + return ret; + } + + ret = platform_device_add(s6105_snd_device); + if (ret) + platform_device_put(s6105_snd_device); + + return ret; +} + +static void __exit s6105_exit(void) +{ + platform_device_unregister(s6105_snd_device); +} + +module_init(s6105_init); +module_exit(s6105_exit); + +MODULE_AUTHOR("Daniel Gloeckner"); +MODULE_DESCRIPTION("Stretch s6105 IP camera ASoC driver"); +MODULE_LICENSE("GPL");
On Thu, Mar 26, 2009 at 02:36:14PM +0100, Daniel Gl??ckner wrote:
This patch adds machine specific code for the audio part of the Stretch s6105 IP camera reference design.
This looks mostly good, though some of the comments on the platform support affect this driver too. You should also check for current ASoC APIs.
- /* set the codec system clock */
- ret = snd_soc_dai_set_sysclk(codec_dai, 0, S6105_CODEC_CLOCK,
SND_SOC_CLOCK_OUT);
Are you sure you're applying the correct configuration to the correct DAI here - S6105_CODEC_CLOCK is a CPU define, not a CODEC define?
+static int snd_soc_dapm_set_endpoint(struct snd_soc_codec *codec, char *name,
int status)
+{
- if (status)
return snd_soc_dapm_enable_pin(codec, name);
- else
return snd_soc_dapm_disable_pin(codec, name);
+}
This shouldn't be in a driver!
- snd_soc_dapm_set_endpoint(codec, "Audio Out Differential", !val);
- snd_soc_dapm_set_endpoint(codec, "Audio Out Stereo", val);
- snd_soc_dapm_sync(codec);
Hrm. Given the fact that you can't have the stereo and differential outputs enabled simultaneously is it not safer to first disable the active output, then sync, then enable the new output?
On 03/26/2009 03:26 PM, Mark Brown wrote:
Are you sure you're applying the correct configuration to the correct DAI here - S6105_CODEC_CLOCK is a CPU define, not a CODEC define?
In this case S6105 was referring to the S6105 reference design. It's a machine define at the top of the file.
+static int snd_soc_dapm_set_endpoint(struct snd_soc_codec *codec, char *name,
int status)
+{
- if (status)
return snd_soc_dapm_enable_pin(codec, name);
- else
return snd_soc_dapm_disable_pin(codec, name);
+}
This shouldn't be in a driver!
I know, but it was made static in soc-dapm.c when it was renamed to snd_soc_dapm_set_pin.
Hrm. Given the fact that you can't have the stereo and differential outputs enabled simultaneously is it not safer to first disable the active output, then sync, then enable the new output?
The pins are connected via 0.47uF.. I'll insert the sync. Then I won't have to bug you about exporting snd_soc_dapm_set_pin as well (as it will no longer result in shorter code).
Daniel
On Fri, Mar 27, 2009 at 05:33:21PM +0100, Daniel Glöckner wrote:
On 03/26/2009 03:26 PM, Mark Brown wrote:
Are you sure you're applying the correct configuration to the correct DAI here - S6105_CODEC_CLOCK is a CPU define, not a CODEC define?
In this case S6105 was referring to the S6105 reference design. It's a machine define at the top of the file.
Hrm, OK.
+static int snd_soc_dapm_set_endpoint(struct snd_soc_codec *codec, char *name,
This shouldn't be in a driver!
I know, but it was made static in soc-dapm.c when it was renamed to snd_soc_dapm_set_pin.
In cases like this you should submit a patch adding the API back to the core rather than adding a generic API as local code in your machine driver.
On Thu, Mar 26, 2009 at 02:36:13PM +0100, Daniel Gl??ckner wrote:
This patch adds a driver for the I2S interface found on Stretch s6000 family processors.
Thanks for this. It looks basically good - there's some issues below, several of which appear to be due to this having been done against a slightly old kernel version. Please see the topic/asoc branch of
git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound-2.6.git
for the most current code.
+config SND_S6000_SOC
- tristate "SoC Audio for the Stretch s6000 family"
- depends on XTENSA_VARIANT_S6000 && SND_SOC
- help
I'm not seeing any hits on XTENSA_VARIANT_S6000 in next? While that shouldn't hurt immediately since it'll just never appear it'd be good to make sure that the APIs this depends on are going into mainline in the form they're in. Do you know what the status is there?
The SND_SOC isn't required due to the use of menuconfig in the sound/soc directory.
+static int __devinit s6000_i2s_probe(struct platform_device *pdev,
struct snd_soc_dai *dai)
+{
- struct s6000_i2s_dev *dev;
- struct resource *scbmem, *sifmem, *region, *dma_in, *dma_out;
- struct s6000_snd_platform_data *pdata;
- u8 __iomem *mmio;
- int ret;
- pdata = pdev->dev.platform_data;
- scbmem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!scbmem) {
With current ASoC it is now possible for the DAIs to be probed using the regular device model and register with the core when that happens. The core will delay probe of the card until all components have registered with it. For CPU side stuff this means that you can provide a standard set of resources for the device in your architecture code without having to have all the machines replicate them in their soc-audio device.
Look at the PXA AC97 driver for an example of this on the platform side.
+#define S6000_I2S_RATES (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT | \
SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000)
SND_PCM_RATE_KNOT doesn't work with ASoC (though it shouldn't do any harm since it'll just get removed).
- .ops = {
.set_fmt = s6000_i2s_set_dai_fmt,
.set_clkdiv = s6000_i2s_set_clkdiv,
.hw_params = s6000_i2s_hw_params,
- }
This won't build with current ASoC, the DAI ops have been moved out of line.
+static inline void s6_i2s_write_reg(struct s6000_i2s_dev *dev, int reg, u32 val) +{
- writel(val, dev->scbbase + reg);
+}
Any reason to expose these to users?
+static inline void s6000_i2s_start_channel(struct s6000_i2s_dev *dev,
int channel)
+{
This and the rest of the functions in the file look especially like it should be in the body of the driver. It also looks a little large to be forcing inline?
+#define S6000_PCM_DEBUG 0 +#if S6000_PCM_DEBUG +#define DPRINTK(x...) printk(KERN_DEBUG x) +#else +#define DPRINTK(x...) +#endif
Use the standard pr_dbg() (or dev_dbg()), no need to reproduce it.
- if (unlikely(i2s_errors & S6_I2S_INT_ALIGNMENT)) {
printk(KERN_ERR "s6000-i2s: Alignment Error\n");
- if (unlikely(i2s_errors & S6_I2S_INT_UNDERRUN) &&
playback->runtime &&
snd_pcm_running(playback)) {
printk(KERN_WARNING "s6000-i2s: Tx Underrun\n");
s6000_i2s_start(playback);
prtd = playback->runtime->private_data;
spin_lock(&prtd->lock);
s6000_pcm_enqueue_dma(playback);
/* a second descriptor will be queued below */
spin_unlock(&prtd->lock);
Hrm. ALSA has underrun handling mechanisms as standard?
- for (i = 0; i < 2; ++i) {
Magic number here - ARRAY_SIZE()?
+int s6000_pcm_start(struct snd_pcm_substream *substream) +{
- struct s6000_runtime_data *prtd = substream->runtime->private_data;
- struct snd_soc_pcm_runtime *soc_runtime = substream->private_data;
- struct s6000_pcm_dma_params *par = soc_runtime->dai->cpu_dai->dma_data;
- unsigned long flags;
- spin_lock_irqsave(&prtd->lock, flags);
- if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
s6dmac_enable_chan(DMA_MASK_DMAC(par->dma_out),
DMA_INDEX_CHNL(par->dma_out),
1, 0, 1, 0, 0, 0, 0, 4, -1, 0, 0, 1);
Hrm. Lots of magic numbers here but it seems to come from the API you're using so not much doing I guess.
On 03/26/2009 03:15 PM, Mark Brown wrote:
I'm not seeing any hits on XTENSA_VARIANT_S6000 in next? While that shouldn't hurt immediately since it'll just never appear it'd be good to make sure that the APIs this depends on are going into mainline in the form they're in. Do you know what the status is there?
The patch introducing XTENSA_VARIANT_S6000 has already been reviewed by the architecture maintainer and there is hope that it will make it into 2.6.30. The dma api, which we posted four days ago to LKML, has not received any comments so far.
+#define S6000_I2S_RATES (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT | \
SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000)
SND_PCM_RATE_KNOT doesn't work with ASoC (though it shouldn't do any harm since it'll just get removed).
Except for the maximum bit clock (50 MHz) the interface doesn't care about the sampling rate, so I put in all SNDRV_PCM_RATE_* constants. Shall I remove it?
+static inline void s6_i2s_write_reg(struct s6000_i2s_dev *dev, int reg, u32 val) +{
- writel(val, dev->scbbase + reg);
+}
Any reason to expose these to users?
+static inline void s6000_i2s_start_channel(struct s6000_i2s_dev *dev,
int channel)
+{
This and the rest of the functions in the file look especially like it should be in the body of the driver. It also looks a little large to be forcing inline?
This was an attempt to avoid symbol exports.. I'll move everything to s6000-i2s.c and export the three necessary functions.
- if (unlikely(i2s_errors & S6_I2S_INT_UNDERRUN) &&
playback->runtime &&
snd_pcm_running(playback)) {
printk(KERN_WARNING "s6000-i2s: Tx Underrun\n");
s6000_i2s_start(playback);
prtd = playback->runtime->private_data;
spin_lock(&prtd->lock);
s6000_pcm_enqueue_dma(playback);
/* a second descriptor will be queued below */
spin_unlock(&prtd->lock);
Hrm. ALSA has underrun handling mechanisms as standard?
I guess snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN) is the way to go then?
The remaining issues will be addressed in the v2 patch as well.
Daniel
On Fri, Mar 27, 2009 at 05:09:08PM +0100, Daniel Glöckner wrote:
On 03/26/2009 03:15 PM, Mark Brown wrote:
I'm not seeing any hits on XTENSA_VARIANT_S6000 in next? While that shouldn't hurt immediately since it'll just never appear it'd be good to make sure that the APIs this depends on are going into mainline in the form they're in. Do you know what the status is there?
The patch introducing XTENSA_VARIANT_S6000 has already been reviewed by the architecture maintainer and there is hope that it will make it into 2.6.30. The dma api, which we posted four days ago to LKML, has not received any comments so far.
OK, might be worth nagging the architecture maintainer to get their tree included in -next if it isn't already.
+#define S6000_I2S_RATES (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT | \
SNDRV_PCM_RATE_5512 | SNDRV_PCM_RATE_8000_192000)
SND_PCM_RATE_KNOT doesn't work with ASoC (though it shouldn't do any harm since it'll just get removed).
Except for the maximum bit clock (50 MHz) the interface doesn't care about the sampling rate, so I put in all SNDRV_PCM_RATE_* constants. Shall I remove it?
Yes, please - this applies to almost all CPU side drivers, it's kind of unfortunate but in practice it generally makes very little practical difference since applications tend to use one of the standard audio rates.
Any reason to expose these to users?
This was an attempt to avoid symbol exports.. I'll move everything to s6000-i2s.c and export the three necessary functions.
Which functions are you exporting? None of this looked like anything that I'd expect to see used outside of the driver.
On 03/27/2009 05:20 PM, Mark Brown wrote:
Any reason to expose these to users?
This was an attempt to avoid symbol exports.. I'll move everything to s6000-i2s.c and export the three necessary functions.
Which functions are you exporting? None of this looked like anything that I'd expect to see used outside of the driver.
s6000_i2s_start and s6000_i2s_stop are called in s6000_pcm_trigger and s6000_i2s_int_sources is called in the interrupt handler.
s6000_i2s_start can't be called from the dai's trigger callback on playback as the data sheet is very explicit about the order of enabling dma and i2s.
s6000_i2s_stop is called there for consistency. The order on disabling is uncritical.
Daniel
On Fri, Mar 27, 2009 at 05:49:33PM +0100, Daniel Glöckner wrote:
s6000_i2s_start and s6000_i2s_stop are called in s6000_pcm_trigger and s6000_i2s_int_sources is called in the interrupt handler.
s6000_i2s_start can't be called from the dai's trigger callback on playback as the data sheet is very explicit about the order of enabling dma and i2s.
s6000_i2s_stop is called there for consistency. The order on disabling is uncritical.
Are these two drivers really separate? The reason for splitting out the DMA driver in the normal case is that it is common for CPUs to be able to use the same DMA driver for multiple interfaces - if that's not the case then don't feel obligated to split them up too much.
Also note that you can pass data from the I2S driver to the PCM driver via the dma_data pointer - if you need to pass a little vtable through there then that's another option.
participants (2)
-
Daniel Glöckner
-
Mark Brown