From 4b166da939012905f4c36fedada62067db31948e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gl=C3=B6ckner?= Date: Sat, 28 Mar 2009 19:47:01 +0100 Subject: ASoC: Add driver for s6000 I2S interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch adds a driver for the I2S interface found on Stretch s6000 family processors. Signed-off-by: Daniel Glöckner Signed-off-by: Mark Brown --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/s6000/Kconfig | 10 + sound/soc/s6000/Makefile | 6 + sound/soc/s6000/s6000-i2s.c | 629 ++++++++++++++++++++++++++++++++++++++++++++ sound/soc/s6000/s6000-i2s.h | 25 ++ sound/soc/s6000/s6000-pcm.c | 497 ++++++++++++++++++++++++++++++++++ sound/soc/s6000/s6000-pcm.h | 35 +++ 8 files changed, 1204 insertions(+) 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 (limited to 'sound/soc') diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 3d2bb6fc6dc..3304f9dd92f 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -32,6 +32,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 0237879fd41..8943a140c81 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 00000000000..4bfc8bcac51 --- /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 + 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 00000000000..df15f876a1a --- /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 00000000000..dcc79040bda --- /dev/null +++ b/sound/soc/s6000/s6000-i2s.c @@ -0,0 +1,629 @@ +/* + * ALSA SoC I2S Audio Layer for the Stretch S6000 family + * + * Author: Daniel Gloeckner, + * Copyright: (C) 2009 emlix GmbH + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "s6000-i2s.h" +#include "s6000-pcm.h" + +struct s6000_i2s_dev { + dma_addr_t sifbase; + u8 __iomem *scbbase; + unsigned int wide; + unsigned int channel_in; + unsigned int channel_out; + unsigned int lines_in; + unsigned 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 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 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 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 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 int s6000_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + int after) +{ + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + if ((substream->stream == SNDRV_PCM_STREAM_CAPTURE) ^ !after) + s6000_i2s_start(substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (!after) + s6000_i2s_stop(substream); + } + return 0; +} + +static 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; +} + +static unsigned int s6000_i2s_check_xrun(struct snd_soc_dai *cpu_dai) +{ + struct s6000_i2s_dev *dev = cpu_dai->private_data; + unsigned int errors; + unsigned int ret; + + errors = s6000_i2s_int_sources(dev); + if (likely(!errors)) + return 0; + + ret = 0; + if (errors & S6_I2S_INT_ALIGNMENT) + printk(KERN_ERR "s6000-i2s: WCLK misaligned\n"); + if (errors & S6_I2S_INT_UNDERRUN) + ret |= 1 << SNDRV_PCM_STREAM_PLAYBACK; + if (errors & S6_I2S_INT_OVERRUN) + ret |= 1 << SNDRV_PCM_STREAM_CAPTURE; + return ret; +} + +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 s6000_i2s_dai_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + struct s6000_i2s_dev *dev = dai->private_data; + struct s6000_snd_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) + return -EINVAL; + + 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; + + if (dev->lines_in + dev->lines_out > S6_I2S_NUM_LINES) + return -EINVAL; + + 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}; + + if (dev->lines_in > 1 || dev->lines_out > 1) + return -EINVAL; + + 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; + + if (dev->lines_in) + cfg[dev->channel_in] = S6_I2S_IN; + if (dev->lines_out) + 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]); + } + + if (dev->lines_out) { + if (dev->lines_in) { + if (!dev->dma_params.dma_out) + return -ENODEV; + } else { + dev->dma_params.dma_out = dev->dma_params.dma_in; + dev->dma_params.dma_in = 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; + return 0; +} + +#define S6000_I2S_RATES (SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_5512 | \ + SNDRV_PCM_RATE_8000_192000) +#define S6000_I2S_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops s6000_i2s_dai_ops = { + .set_fmt = s6000_i2s_set_dai_fmt, + .set_clkdiv = s6000_i2s_set_clkdiv, + .hw_params = s6000_i2s_hw_params, +}; + +struct snd_soc_dai s6000_i2s_dai = { + .name = "s6000-i2s", + .id = 0, + .probe = s6000_i2s_dai_probe, + .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 = &s6000_i2s_dai_ops, +} +EXPORT_SYMBOL_GPL(s6000_i2s_dai); + +static int __devinit s6000_i2s_probe(struct platform_device *pdev) +{ + struct s6000_i2s_dev *dev; + struct resource *scbmem, *sifmem, *region, *dma1, *dma2; + u8 __iomem *mmio; + int ret; + + 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; + } + + dma1 = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!dma1) { + dev_err(&pdev->dev, "no dma resource?\n"); + ret = -ENODEV; + goto err_release_sif; + } + + region = request_mem_region(dma1->start, dma1->end - dma1->start + 1, + pdev->name); + if (!region) { + dev_err(&pdev->dev, "I2S DMA region already claimed\n"); + ret = -EBUSY; + goto err_release_sif; + } + + dma2 = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (dma2) { + region = request_mem_region(dma2->start, + dma2->end - dma2->start + 1, + pdev->name); + if (!region) { + dev_err(&pdev->dev, + "I2S DMA region already claimed\n"); + ret = -EBUSY; + goto err_release_dma1; + } + } + + dev = kzalloc(sizeof(struct s6000_i2s_dev), GFP_KERNEL); + if (!dev) { + ret = -ENOMEM; + goto err_release_dma2; + } + + s6000_i2s_dai.dev = &pdev->dev; + s6000_i2s_dai.private_data = dev; + s6000_i2s_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->dma_params.check_xrun = s6000_i2s_check_xrun; + dev->dma_params.trigger = s6000_i2s_trigger; + dev->dma_params.dma_in = dma1->start; + dev->dma_params.dma_out = dma2 ? dma2->start : 0; + 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); + + ret = snd_soc_register_dai(&s6000_i2s_dai); + if (ret) + goto err_release_dev; + + return 0; + +err_release_dev: + kfree(dev); +err_release_dma2: + if (dma2) + release_mem_region(dma2->start, dma2->end - dma2->start + 1); +err_release_dma1: + release_mem_region(dma1->start, dma1->end - dma1->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 s6000_i2s_dev *dev = s6000_i2s_dai.private_data; + struct resource *region; + void __iomem *mmio = dev->scbbase; + + snd_soc_unregister_dai(&s6000_i2s_dai); + + s6000_i2s_stop_channel(dev, 0); + s6000_i2s_stop_channel(dev, 1); + + s6_i2s_write_reg(dev, S6_I2S_INTERRUPT_ENABLE, 0); + s6000_i2s_dai.private_data = 0; + kfree(dev); + + region = platform_get_resource(pdev, IORESOURCE_DMA, 0); + release_mem_region(region->start, region->end - region->start + 1); + + region = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (region) + release_mem_region(region->start, + region->end - region->start + 1); + + region = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_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); +} + +static struct platform_driver s6000_i2s_driver = { + .probe = s6000_i2s_probe, + .remove = __devexit_p(s6000_i2s_remove), + .driver = { + .name = "s6000-i2s", + .owner = THIS_MODULE, + }, +}; + +static int __init s6000_i2s_init(void) +{ + return platform_driver_register(&s6000_i2s_driver); +} +module_init(s6000_i2s_init); + +static void __exit s6000_i2s_exit(void) +{ + platform_driver_unregister(&s6000_i2s_driver); +} +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 00000000000..2375fdfe6db --- /dev/null +++ b/sound/soc/s6000/s6000-i2s.h @@ -0,0 +1,25 @@ +/* + * ALSA SoC I2S Audio Layer for the Stretch s6000 family + * + * Author: Daniel Gloeckner, + * Copyright: (C) 2009 emlix GmbH + * + * 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 + +extern struct snd_soc_dai s6000_i2s_dai; + +struct s6000_snd_platform_data { + int lines_in; + int lines_out; + int channel_in; + int channel_out; + int wide; + int same_rate; +}; +#endif diff --git a/sound/soc/s6000/s6000-pcm.c b/sound/soc/s6000/s6000-pcm.c new file mode 100644 index 00000000000..83b8028e209 --- /dev/null +++ b/sound/soc/s6000/s6000-pcm.c @@ -0,0 +1,497 @@ +/* + * ALSA PCM interface for the Stetch s6000 family + * + * Author: Daniel Gloeckner, + * Copyright: (C) 2009 emlix GmbH + * + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "s6000-pcm.h" + +#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_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 s6000_runtime_data *prtd; + unsigned int has_xrun; + int i, ret = IRQ_NONE; + u32 channel[2] = { + [SNDRV_PCM_STREAM_PLAYBACK] = params->dma_out, + [SNDRV_PCM_STREAM_CAPTURE] = params->dma_in + }; + + has_xrun = params->check_xrun(runtime->dai->cpu_dai); + + for (i = 0; i < ARRAY_SIZE(channel); ++i) { + struct snd_pcm_substream *substream = pcm->streams[i].substream; + unsigned int pending; + + if (!channel[i]) + continue; + + if (unlikely(has_xrun & (1 << i)) && + substream->runtime && + snd_pcm_running(substream)) { + dev_dbg(pcm->dev, "xrun\n"); + snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN); + ret = IRQ_HANDLED; + } + + 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); + dev_dbg(pcm->dev, "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); + + } + } + + return ret; +} + +static 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; + int srcinc; + u32 dma; + + spin_lock_irqsave(&prtd->lock, flags); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + srcinc = 1; + dma = par->dma_out; + } else { + srcinc = 0; + dma = par->dma_in; + } + s6dmac_enable_chan(DMA_MASK_DMAC(dma), DMA_INDEX_CHNL(dma), + 1 /* priority 1 (0 is max) */, + 0 /* peripheral requests w/o xfer length mode */, + srcinc /* source address increment */, + srcinc^1 /* destination address increment */, + 0 /* chunksize 0 (skip impossible on this dma) */, + 0 /* source skip after chunk (impossible) */, + 0 /* destination skip after chunk (impossible) */, + 4 /* 16 byte burst size */, + -1 /* don't conserve bandwidth */, + 0 /* low watermark irq descriptor theshold */, + 0 /* disable hardware timestamps */, + 1 /* enable channel */); + + s6000_pcm_enqueue_dma(substream); + s6000_pcm_enqueue_dma(substream); + + spin_unlock_irqrestore(&prtd->lock, flags); + + return 0; +} + +static 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) +{ + 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 = par->trigger(substream, cmd, 0); + if (ret < 0) + return ret; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = s6000_pcm_start(substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = s6000_pcm_stop(substream); + break; + default: + ret = -EINVAL; + } + if (ret < 0) + return ret; + + return par->trigger(substream, cmd, 1); +} + +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); +} + +static 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 00000000000..96f23f6f52b --- /dev/null +++ b/sound/soc/s6000/s6000-pcm.h @@ -0,0 +1,35 @@ +/* + * ALSA PCM interface for the Stretch s6000 family + * + * Author: Daniel Gloeckner, + * Copyright: (C) 2009 emlix GmbH + * + * 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 snd_soc_dai; +struct snd_pcm_substream; + +struct s6000_pcm_dma_params { + unsigned int (*check_xrun)(struct snd_soc_dai *cpu_dai); + int (*trigger)(struct snd_pcm_substream *substream, int cmd, int after); + 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; +}; + +extern struct snd_soc_platform s6000_soc_platform; + +#endif -- cgit v1.2.3 From 2b7dbbe0c9491e62b50978d1615193bec33a8291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gl=C3=B6ckner?= Date: Sat, 28 Mar 2009 19:47:02 +0100 Subject: ASoC: s6105 IP camera machine specific ASoC code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Signed-off-by: Mark Brown --- sound/soc/s6000/Kconfig | 9 ++ sound/soc/s6000/Makefile | 5 + sound/soc/s6000/s6105-ipcam.c | 244 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 258 insertions(+) create mode 100644 sound/soc/s6000/s6105-ipcam.c (limited to 'sound/soc') diff --git a/sound/soc/s6000/Kconfig b/sound/soc/s6000/Kconfig index 4bfc8bcac51..c74eb3d4a47 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 df15f876a1a..7a613612e01 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 00000000000..21c4f55106c --- /dev/null +++ b/sound/soc/s6000/s6105-ipcam.c @@ -0,0 +1,244 @@ +/* + * ASoC driver for Stretch s6105 IP camera platform + * + * Author: Daniel Gloeckner, + * Copyright: (C) 2009 emlix GmbH + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../codecs/tlv320aic3x.h" +#include "s6000-pcm.h" +#include "s6000-i2s.h" + +#define S6105_CAM_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_CAM_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 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); + char *differential = "Audio Out Differential"; + char *stereo = "Audio Out Stereo"; + + if (kcontrol->private_value == val) + return 0; + kcontrol->private_value = val; + snd_soc_dapm_disable_pin(codec, val ? differential : stereo); + snd_soc_dapm_sync(codec); + snd_soc_dapm_enable_pin(codec, val ? stereo : differential); + 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"); + + /* must correspond to audio_out_mux.private_value initializer */ + snd_soc_dapm_disable_pin(codec, "Audio Out Differential"); + snd_soc_dapm_sync(codec); + snd_soc_dapm_enable_pin(codec, "Audio Out Stereo"); + + 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 s6000_snd_platform_data __initdata 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; + platform_device_add_data(s6105_snd_device, &s6105_snd_data, + sizeof(s6105_snd_data)); + + 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"); -- cgit v1.2.3 From 80fbe6ac9b47cbc11e174a9bf853834dc281da35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gl=C3=B6ckner?= Date: Mon, 6 Apr 2009 11:50:22 +0200 Subject: ASoC: correct s6000 I2S clock polarity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit According to the data sheet data is clocked out on the falling edge and latched on the rising edge of the bit clock. While the left sample is transmitted the word clock line is low. Signed-off-by: Daniel Glöckner Signed-off-by: Mark Brown --- sound/soc/s6000/s6000-i2s.c | 4 ++-- sound/soc/s6000/s6105-ipcam.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/s6000/s6000-i2s.c b/sound/soc/s6000/s6000-i2s.c index dcc79040bda..c5cda187eca 100644 --- a/sound/soc/s6000/s6000-i2s.c +++ b/sound/soc/s6000/s6000-i2s.c @@ -252,10 +252,10 @@ static int s6000_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, } switch (fmt & SND_SOC_DAIFMT_INV_MASK) { - case SND_SOC_DAIFMT_IB_IF: + case SND_SOC_DAIFMT_NB_NF: w |= S6_I2S_LEFT_FIRST; break; - case SND_SOC_DAIFMT_IB_NF: + case SND_SOC_DAIFMT_NB_IF: w |= S6_I2S_RIGHT_FIRST; break; default: diff --git a/sound/soc/s6000/s6105-ipcam.c b/sound/soc/s6000/s6105-ipcam.c index 21c4f55106c..b5f95f9781c 100644 --- a/sound/soc/s6000/s6105-ipcam.c +++ b/sound/soc/s6000/s6105-ipcam.c @@ -43,7 +43,7 @@ static int s6105_hw_params(struct snd_pcm_substream *substream, /* set cpu DAI configuration */ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBM_CFM | - SND_SOC_DAIFMT_IB_IF); + SND_SOC_DAIFMT_NB_NF); if (ret < 0) return ret; -- cgit v1.2.3 From 6553e192d48af88184029066c30c9464516ea0b7 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 6 Apr 2009 16:59:32 +0100 Subject: ASoC: Display return code when failing to add a DAPM kcontrol Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 735903a7467..46485de9e6f 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -357,8 +357,9 @@ static int dapm_new_mixer(struct snd_soc_codec *codec, path->long_name); ret = snd_ctl_add(codec->card, path->kcontrol); if (ret < 0) { - printk(KERN_ERR "asoc: failed to add dapm kcontrol %s\n", - path->long_name); + printk(KERN_ERR "asoc: failed to add dapm kcontrol %s: %d\n", + path->long_name, + ret); kfree(path->long_name); path->long_name = NULL; return ret; -- cgit v1.2.3 From 06f409d76f1d382167eb1cadde2e23a73272865d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 7 Apr 2009 18:10:13 +0100 Subject: ASoC: Provide core support for symmetric sample rates Many devices require symmetric configurations of capture and playback data formats, often due to shared clocking but sometimes also due to other shared playback and record configuration in the device. Start providing core support for this by allowing the DAIs or the machine to specify that the sample rates used should be kept symmetric. A flag symmetric_rates is provided in the snd_soc_dai and snd_soc_dai_link structures. If this is set in either of the DAIs or in the machine then a constraint will be applied when a stream is already open preventing any changes in sample rate. Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 99712f652d0..dd28009f896 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -113,6 +113,35 @@ static int soc_ac97_dev_register(struct snd_soc_codec *codec) } #endif +static int soc_pcm_apply_symmetry(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_card *card = socdev->card; + struct snd_soc_dai_link *machine = rtd->dai; + struct snd_soc_dai *cpu_dai = machine->cpu_dai; + struct snd_soc_dai *codec_dai = machine->codec_dai; + int ret; + + if (codec_dai->symmetric_rates || cpu_dai->symmetric_rates || + machine->symmetric_rates) { + dev_dbg(card->dev, "Symmetry forces %dHz rate\n", + machine->rate); + + ret = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + machine->rate, + machine->rate); + if (ret < 0) { + dev_err(card->dev, + "Unable to apply rate symmetry constraint: %d\n", ret); + return ret; + } + } + + return 0; +} + /* * Called by ALSA when a PCM substream is opened, the runtime->hw record is * then initialized and any private data can be allocated. This also calls @@ -221,6 +250,13 @@ static int soc_pcm_open(struct snd_pcm_substream *substream) goto machine_err; } + /* Symmetry only applies if we've already got an active stream. */ + if (cpu_dai->active || codec_dai->active) { + ret = soc_pcm_apply_symmetry(substream); + if (ret != 0) + goto machine_err; + } + pr_debug("asoc: %s <-> %s info:\n", codec_dai->name, cpu_dai->name); pr_debug("asoc: rate mask 0x%x\n", runtime->hw.rates); pr_debug("asoc: min ch %d max ch %d\n", runtime->hw.channels_min, @@ -521,6 +557,8 @@ static int soc_pcm_hw_params(struct snd_pcm_substream *substream, } } + machine->rate = params_rate(params); + out: mutex_unlock(&pcm_mutex); return ret; -- cgit v1.2.3 From 5409fb4e327a84972483047ecf4fb41f279453e2 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 7 Apr 2009 18:45:21 +0100 Subject: ASoC: Add WM8988 CODEC driver The WM8988 is a low power, high quality stereo CODEC designed for portable digital audio applications. The device integrates complete interfaces to 2 stereo headphone or line out ports. External component requirements are drastically reduced as no separate headphone amplifiers are required. Advanced on-chip digital signal processing performs graphic equaliser, 3-D sound enhancement and automatic level control for the microphone or line input. The WM8988 can operate as a master or a slave, with various master clock frequencies including 12 or 24MHz for USB devices, or standard 256fs rates like 12.288MHz and 24.576MHz. Different audio sample rates such as 96kHz, 48kHz, 44.1kHz are generated directly from the master clock without the need for an external PLL. Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm8988.c | 1097 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm8988.h | 60 +++ 4 files changed, 1163 insertions(+) create mode 100644 sound/soc/codecs/wm8988.c create mode 100644 sound/soc/codecs/wm8988.h (limited to 'sound/soc') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index b6c7f7a01cb..ab364854675 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -36,6 +36,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8900 if I2C select SND_SOC_WM8903 if I2C select SND_SOC_WM8971 if I2C + select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8990 if I2C select SND_SOC_WM9705 if SND_SOC_AC97_BUS select SND_SOC_WM9712 if SND_SOC_AC97_BUS @@ -141,6 +142,9 @@ config SND_SOC_WM8903 config SND_SOC_WM8971 tristate +config SND_SOC_WM8988 + tristate + config SND_SOC_WM8990 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 030d2454725..a72548dc188 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -24,6 +24,7 @@ snd-soc-wm8753-objs := wm8753.o snd-soc-wm8900-objs := wm8900.o snd-soc-wm8903-objs := wm8903.o snd-soc-wm8971-objs := wm8971.o +snd-soc-wm8988-objs := wm8988.o snd-soc-wm8990-objs := wm8990.o snd-soc-wm9705-objs := wm9705.o snd-soc-wm9712-objs := wm9712.o @@ -55,6 +56,7 @@ obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o +obj-$(CONFIG_SND_SOC_WM8988) += snd-soc-wm8988.o obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o obj-$(CONFIG_SND_SOC_WM8991) += snd-soc-wm8991.o obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o diff --git a/sound/soc/codecs/wm8988.c b/sound/soc/codecs/wm8988.c new file mode 100644 index 00000000000..c05f71803aa --- /dev/null +++ b/sound/soc/codecs/wm8988.c @@ -0,0 +1,1097 @@ +/* + * wm8988.c -- WM8988 ALSA SoC audio driver + * + * Copyright 2009 Wolfson Microelectronics plc + * Copyright 2005 Openedhand Ltd. + * + * Author: Mark Brown + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8988.h" + +/* + * wm8988 register cache + * We can't read the WM8988 register space when we + * are using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8988_reg[] = { + 0x0097, 0x0097, 0x0079, 0x0079, /* 0 */ + 0x0000, 0x0008, 0x0000, 0x000a, /* 4 */ + 0x0000, 0x0000, 0x00ff, 0x00ff, /* 8 */ + 0x000f, 0x000f, 0x0000, 0x0000, /* 12 */ + 0x0000, 0x007b, 0x0000, 0x0032, /* 16 */ + 0x0000, 0x00c3, 0x00c3, 0x00c0, /* 20 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 24 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 28 */ + 0x0000, 0x0000, 0x0050, 0x0050, /* 32 */ + 0x0050, 0x0050, 0x0050, 0x0050, /* 36 */ + 0x0079, 0x0079, 0x0079, /* 40 */ +}; + +/* codec private data */ +struct wm8988_priv { + unsigned int sysclk; + struct snd_soc_codec codec; + struct snd_pcm_hw_constraint_list *sysclk_constraints; + u16 reg_cache[WM8988_NUM_REG]; +}; + + +/* + * read wm8988 register cache + */ +static inline unsigned int wm8988_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg > WM8988_NUM_REG) + return -1; + return cache[reg]; +} + +/* + * write wm8988 register cache + */ +static inline void wm8988_write_reg_cache(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg > WM8988_NUM_REG) + return; + cache[reg] = value; +} + +static int wm8988_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8753 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8988_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8988_reset(c) wm8988_write(c, WM8988_RESET, 0) + +/* + * WM8988 Controls + */ + +static const char *bass_boost_txt[] = {"Linear Control", "Adaptive Boost"}; +static const struct soc_enum bass_boost = + SOC_ENUM_SINGLE(WM8988_BASS, 7, 2, bass_boost_txt); + +static const char *bass_filter_txt[] = { "130Hz @ 48kHz", "200Hz @ 48kHz" }; +static const struct soc_enum bass_filter = + SOC_ENUM_SINGLE(WM8988_BASS, 6, 2, bass_filter_txt); + +static const char *treble_txt[] = {"8kHz", "4kHz"}; +static const struct soc_enum treble = + SOC_ENUM_SINGLE(WM8988_TREBLE, 6, 2, treble_txt); + +static const char *stereo_3d_lc_txt[] = {"200Hz", "500Hz"}; +static const struct soc_enum stereo_3d_lc = + SOC_ENUM_SINGLE(WM8988_3D, 5, 2, stereo_3d_lc_txt); + +static const char *stereo_3d_uc_txt[] = {"2.2kHz", "1.5kHz"}; +static const struct soc_enum stereo_3d_uc = + SOC_ENUM_SINGLE(WM8988_3D, 6, 2, stereo_3d_uc_txt); + +static const char *stereo_3d_func_txt[] = {"Capture", "Playback"}; +static const struct soc_enum stereo_3d_func = + SOC_ENUM_SINGLE(WM8988_3D, 7, 2, stereo_3d_func_txt); + +static const char *alc_func_txt[] = {"Off", "Right", "Left", "Stereo"}; +static const struct soc_enum alc_func = + SOC_ENUM_SINGLE(WM8988_ALC1, 7, 4, alc_func_txt); + +static const char *ng_type_txt[] = {"Constant PGA Gain", + "Mute ADC Output"}; +static const struct soc_enum ng_type = + SOC_ENUM_SINGLE(WM8988_NGATE, 1, 2, ng_type_txt); + +static const char *deemph_txt[] = {"None", "32Khz", "44.1Khz", "48Khz"}; +static const struct soc_enum deemph = + SOC_ENUM_SINGLE(WM8988_ADCDAC, 1, 4, deemph_txt); + +static const char *adcpol_txt[] = {"Normal", "L Invert", "R Invert", + "L + R Invert"}; +static const struct soc_enum adcpol = + SOC_ENUM_SINGLE(WM8988_ADCDAC, 5, 4, adcpol_txt); + +static const DECLARE_TLV_DB_SCALE(pga_tlv, -1725, 75, 0); +static const DECLARE_TLV_DB_SCALE(adc_tlv, -9750, 50, 1); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); +static const DECLARE_TLV_DB_SCALE(bypass_tlv, -1500, 300, 0); + +static const struct snd_kcontrol_new wm8988_snd_controls[] = { + +SOC_ENUM("Bass Boost", bass_boost), +SOC_ENUM("Bass Filter", bass_filter), +SOC_SINGLE("Bass Volume", WM8988_BASS, 0, 15, 1), + +SOC_SINGLE("Treble Volume", WM8988_TREBLE, 0, 15, 0), +SOC_ENUM("Treble Cut-off", treble), + +SOC_SINGLE("3D Switch", WM8988_3D, 0, 1, 0), +SOC_SINGLE("3D Volume", WM8988_3D, 1, 15, 0), +SOC_ENUM("3D Lower Cut-off", stereo_3d_lc), +SOC_ENUM("3D Upper Cut-off", stereo_3d_uc), +SOC_ENUM("3D Mode", stereo_3d_func), + +SOC_SINGLE("ALC Capture Target Volume", WM8988_ALC1, 0, 7, 0), +SOC_SINGLE("ALC Capture Max Volume", WM8988_ALC1, 4, 7, 0), +SOC_ENUM("ALC Capture Function", alc_func), +SOC_SINGLE("ALC Capture ZC Switch", WM8988_ALC2, 7, 1, 0), +SOC_SINGLE("ALC Capture Hold Time", WM8988_ALC2, 0, 15, 0), +SOC_SINGLE("ALC Capture Decay Time", WM8988_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Capture Attack Time", WM8988_ALC3, 0, 15, 0), +SOC_SINGLE("ALC Capture NG Threshold", WM8988_NGATE, 3, 31, 0), +SOC_ENUM("ALC Capture NG Type", ng_type), +SOC_SINGLE("ALC Capture NG Switch", WM8988_NGATE, 0, 1, 0), + +SOC_SINGLE("ZC Timeout Switch", WM8988_ADCTL1, 0, 1, 0), + +SOC_DOUBLE_R_TLV("Capture Digital Volume", WM8988_LADC, WM8988_RADC, + 0, 255, 0, adc_tlv), +SOC_DOUBLE_R_TLV("Capture Volume", WM8988_LINVOL, WM8988_RINVOL, + 0, 63, 0, pga_tlv), +SOC_DOUBLE_R("Capture ZC Switch", WM8988_LINVOL, WM8988_RINVOL, 6, 1, 0), +SOC_DOUBLE_R("Capture Switch", WM8988_LINVOL, WM8988_RINVOL, 7, 1, 1), + +SOC_ENUM("Playback De-emphasis", deemph), + +SOC_ENUM("Capture Polarity", adcpol), +SOC_SINGLE("Playback 6dB Attenuate", WM8988_ADCDAC, 7, 1, 0), +SOC_SINGLE("Capture 6dB Attenuate", WM8988_ADCDAC, 8, 1, 0), + +SOC_DOUBLE_R_TLV("PCM Volume", WM8988_LDAC, WM8988_RDAC, 0, 255, 0, dac_tlv), + +SOC_SINGLE_TLV("Left Mixer Left Bypass Volume", WM8988_LOUTM1, 4, 7, 1, + bypass_tlv), +SOC_SINGLE_TLV("Left Mixer Right Bypass Volume", WM8988_LOUTM2, 4, 7, 1, + bypass_tlv), +SOC_SINGLE_TLV("Right Mixer Left Bypass Volume", WM8988_ROUTM1, 4, 7, 1, + bypass_tlv), +SOC_SINGLE_TLV("Right Mixer Right Bypass Volume", WM8988_ROUTM2, 4, 7, 1, + bypass_tlv), + +SOC_DOUBLE_R("Output 1 Playback ZC Switch", WM8988_LOUT1V, + WM8988_ROUT1V, 7, 1, 0), +SOC_DOUBLE_R_TLV("Output 1 Playback Volume", WM8988_LOUT1V, WM8988_ROUT1V, + 0, 127, 0, out_tlv), + +SOC_DOUBLE_R("Output 2 Playback ZC Switch", WM8988_LOUT2V, + WM8988_ROUT2V, 7, 1, 0), +SOC_DOUBLE_R_TLV("Output 2 Playback Volume", WM8988_LOUT2V, WM8988_ROUT2V, + 0, 127, 0, out_tlv), + +}; + +/* + * DAPM Controls + */ + +static int wm8988_lrc_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 adctl2 = wm8988_read_reg_cache(codec, WM8988_ADCTL2); + + /* Use the DAC to gate LRC if active, otherwise use ADC */ + if (wm8988_read_reg_cache(codec, WM8988_PWR2) & 0x180) + adctl2 &= ~0x4; + else + adctl2 |= 0x4; + + return wm8988_write(codec, WM8988_ADCTL2, adctl2); +} + +static const char *wm8988_line_texts[] = { + "Line 1", "Line 2", "PGA", "Differential"}; + +static const unsigned int wm8988_line_values[] = { + 0, 1, 3, 4}; + +static const struct soc_enum wm8988_lline_enum = + SOC_VALUE_ENUM_SINGLE(WM8988_LOUTM1, 0, 7, + ARRAY_SIZE(wm8988_line_texts), + wm8988_line_texts, + wm8988_line_values); +static const struct snd_kcontrol_new wm8988_left_line_controls = + SOC_DAPM_VALUE_ENUM("Route", wm8988_lline_enum); + +static const struct soc_enum wm8988_rline_enum = + SOC_VALUE_ENUM_SINGLE(WM8988_ROUTM1, 0, 7, + ARRAY_SIZE(wm8988_line_texts), + wm8988_line_texts, + wm8988_line_values); +static const struct snd_kcontrol_new wm8988_right_line_controls = + SOC_DAPM_VALUE_ENUM("Route", wm8988_lline_enum); + +/* Left Mixer */ +static const struct snd_kcontrol_new wm8988_left_mixer_controls[] = { + SOC_DAPM_SINGLE("Playback Switch", WM8988_LOUTM1, 8, 1, 0), + SOC_DAPM_SINGLE("Left Bypass Switch", WM8988_LOUTM1, 7, 1, 0), + SOC_DAPM_SINGLE("Right Playback Switch", WM8988_LOUTM2, 8, 1, 0), + SOC_DAPM_SINGLE("Right Bypass Switch", WM8988_LOUTM2, 7, 1, 0), +}; + +/* Right Mixer */ +static const struct snd_kcontrol_new wm8988_right_mixer_controls[] = { + SOC_DAPM_SINGLE("Left Playback Switch", WM8988_ROUTM1, 8, 1, 0), + SOC_DAPM_SINGLE("Left Bypass Switch", WM8988_ROUTM1, 7, 1, 0), + SOC_DAPM_SINGLE("Playback Switch", WM8988_ROUTM2, 8, 1, 0), + SOC_DAPM_SINGLE("Right Bypass Switch", WM8988_ROUTM2, 7, 1, 0), +}; + +static const char *wm8988_pga_sel[] = {"Line 1", "Line 2", "Differential"}; +static const unsigned int wm8988_pga_val[] = { 0, 1, 3 }; + +/* Left PGA Mux */ +static const struct soc_enum wm8988_lpga_enum = + SOC_VALUE_ENUM_SINGLE(WM8988_LADCIN, 6, 3, + ARRAY_SIZE(wm8988_pga_sel), + wm8988_pga_sel, + wm8988_pga_val); +static const struct snd_kcontrol_new wm8988_left_pga_controls = + SOC_DAPM_VALUE_ENUM("Route", wm8988_lpga_enum); + +/* Right PGA Mux */ +static const struct soc_enum wm8988_rpga_enum = + SOC_VALUE_ENUM_SINGLE(WM8988_RADCIN, 6, 3, + ARRAY_SIZE(wm8988_pga_sel), + wm8988_pga_sel, + wm8988_pga_val); +static const struct snd_kcontrol_new wm8988_right_pga_controls = + SOC_DAPM_VALUE_ENUM("Route", wm8988_rpga_enum); + +/* Differential Mux */ +static const char *wm8988_diff_sel[] = {"Line 1", "Line 2"}; +static const struct soc_enum diffmux = + SOC_ENUM_SINGLE(WM8988_ADCIN, 8, 2, wm8988_diff_sel); +static const struct snd_kcontrol_new wm8988_diffmux_controls = + SOC_DAPM_ENUM("Route", diffmux); + +/* Mono ADC Mux */ +static const char *wm8988_mono_mux[] = {"Stereo", "Mono (Left)", + "Mono (Right)", "Digital Mono"}; +static const struct soc_enum monomux = + SOC_ENUM_SINGLE(WM8988_ADCIN, 6, 4, wm8988_mono_mux); +static const struct snd_kcontrol_new wm8988_monomux_controls = + SOC_DAPM_ENUM("Route", monomux); + +static const struct snd_soc_dapm_widget wm8988_dapm_widgets[] = { + SND_SOC_DAPM_MICBIAS("Mic Bias", WM8988_PWR1, 1, 0), + + SND_SOC_DAPM_MUX("Differential Mux", SND_SOC_NOPM, 0, 0, + &wm8988_diffmux_controls), + SND_SOC_DAPM_MUX("Left ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8988_monomux_controls), + SND_SOC_DAPM_MUX("Right ADC Mux", SND_SOC_NOPM, 0, 0, + &wm8988_monomux_controls), + + SND_SOC_DAPM_MUX("Left PGA Mux", WM8988_PWR1, 5, 0, + &wm8988_left_pga_controls), + SND_SOC_DAPM_MUX("Right PGA Mux", WM8988_PWR1, 4, 0, + &wm8988_right_pga_controls), + + SND_SOC_DAPM_MUX("Left Line Mux", SND_SOC_NOPM, 0, 0, + &wm8988_left_line_controls), + SND_SOC_DAPM_MUX("Right Line Mux", SND_SOC_NOPM, 0, 0, + &wm8988_right_line_controls), + + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8988_PWR1, 2, 0), + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8988_PWR1, 3, 0), + + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8988_PWR2, 7, 0), + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8988_PWR2, 8, 0), + + SND_SOC_DAPM_MIXER("Left Mixer", SND_SOC_NOPM, 0, 0, + &wm8988_left_mixer_controls[0], + ARRAY_SIZE(wm8988_left_mixer_controls)), + SND_SOC_DAPM_MIXER("Right Mixer", SND_SOC_NOPM, 0, 0, + &wm8988_right_mixer_controls[0], + ARRAY_SIZE(wm8988_right_mixer_controls)), + + SND_SOC_DAPM_PGA("Right Out 2", WM8988_PWR2, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 2", WM8988_PWR2, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Right Out 1", WM8988_PWR2, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Left Out 1", WM8988_PWR2, 6, 0, NULL, 0), + + SND_SOC_DAPM_POST("LRC control", wm8988_lrc_control), + + SND_SOC_DAPM_OUTPUT("LOUT1"), + SND_SOC_DAPM_OUTPUT("ROUT1"), + SND_SOC_DAPM_OUTPUT("LOUT2"), + SND_SOC_DAPM_OUTPUT("ROUT2"), + SND_SOC_DAPM_OUTPUT("VREF"), + + SND_SOC_DAPM_INPUT("LINPUT1"), + SND_SOC_DAPM_INPUT("LINPUT2"), + SND_SOC_DAPM_INPUT("RINPUT1"), + SND_SOC_DAPM_INPUT("RINPUT2"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + + { "Left Line Mux", "Line 1", "LINPUT1" }, + { "Left Line Mux", "Line 2", "LINPUT2" }, + { "Left Line Mux", "PGA", "Left PGA Mux" }, + { "Left Line Mux", "Differential", "Differential Mux" }, + + { "Right Line Mux", "Line 1", "RINPUT1" }, + { "Right Line Mux", "Line 2", "RINPUT2" }, + { "Right Line Mux", "PGA", "Right PGA Mux" }, + { "Right Line Mux", "Differential", "Differential Mux" }, + + { "Left PGA Mux", "Line 1", "LINPUT1" }, + { "Left PGA Mux", "Line 2", "LINPUT2" }, + { "Left PGA Mux", "Differential", "Differential Mux" }, + + { "Right PGA Mux", "Line 1", "RINPUT1" }, + { "Right PGA Mux", "Line 2", "RINPUT2" }, + { "Right PGA Mux", "Differential", "Differential Mux" }, + + { "Differential Mux", "Line 1", "LINPUT1" }, + { "Differential Mux", "Line 1", "RINPUT1" }, + { "Differential Mux", "Line 2", "LINPUT2" }, + { "Differential Mux", "Line 2", "RINPUT2" }, + + { "Left ADC Mux", "Stereo", "Left PGA Mux" }, + { "Left ADC Mux", "Mono (Left)", "Left PGA Mux" }, + { "Left ADC Mux", "Digital Mono", "Left PGA Mux" }, + + { "Right ADC Mux", "Stereo", "Right PGA Mux" }, + { "Right ADC Mux", "Mono (Right)", "Right PGA Mux" }, + { "Right ADC Mux", "Digital Mono", "Right PGA Mux" }, + + { "Left ADC", NULL, "Left ADC Mux" }, + { "Right ADC", NULL, "Right ADC Mux" }, + + { "Left Line Mux", "Line 1", "LINPUT1" }, + { "Left Line Mux", "Line 2", "LINPUT2" }, + { "Left Line Mux", "PGA", "Left PGA Mux" }, + { "Left Line Mux", "Differential", "Differential Mux" }, + + { "Right Line Mux", "Line 1", "RINPUT1" }, + { "Right Line Mux", "Line 2", "RINPUT2" }, + { "Right Line Mux", "PGA", "Right PGA Mux" }, + { "Right Line Mux", "Differential", "Differential Mux" }, + + { "Left Mixer", "Playback Switch", "Left DAC" }, + { "Left Mixer", "Left Bypass Switch", "Left Line Mux" }, + { "Left Mixer", "Right Playback Switch", "Right DAC" }, + { "Left Mixer", "Right Bypass Switch", "Right Line Mux" }, + + { "Right Mixer", "Left Playback Switch", "Left DAC" }, + { "Right Mixer", "Left Bypass Switch", "Left Line Mux" }, + { "Right Mixer", "Playback Switch", "Right DAC" }, + { "Right Mixer", "Right Bypass Switch", "Right Line Mux" }, + + { "Left Out 1", NULL, "Left Mixer" }, + { "LOUT1", NULL, "Left Out 1" }, + { "Right Out 1", NULL, "Right Mixer" }, + { "ROUT1", NULL, "Right Out 1" }, + + { "Left Out 2", NULL, "Left Mixer" }, + { "LOUT2", NULL, "Left Out 2" }, + { "Right Out 2", NULL, "Right Mixer" }, + { "ROUT2", NULL, "Right Out 2" }, +}; + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:5; + u8 usb:1; +}; + +/* codec hifi mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 8k */ + {12288000, 8000, 1536, 0x6, 0x0}, + {11289600, 8000, 1408, 0x16, 0x0}, + {18432000, 8000, 2304, 0x7, 0x0}, + {16934400, 8000, 2112, 0x17, 0x0}, + {12000000, 8000, 1500, 0x6, 0x1}, + + /* 11.025k */ + {11289600, 11025, 1024, 0x18, 0x0}, + {16934400, 11025, 1536, 0x19, 0x0}, + {12000000, 11025, 1088, 0x19, 0x1}, + + /* 16k */ + {12288000, 16000, 768, 0xa, 0x0}, + {18432000, 16000, 1152, 0xb, 0x0}, + {12000000, 16000, 750, 0xa, 0x1}, + + /* 22.05k */ + {11289600, 22050, 512, 0x1a, 0x0}, + {16934400, 22050, 768, 0x1b, 0x0}, + {12000000, 22050, 544, 0x1b, 0x1}, + + /* 32k */ + {12288000, 32000, 384, 0xc, 0x0}, + {18432000, 32000, 576, 0xd, 0x0}, + {12000000, 32000, 375, 0xa, 0x1}, + + /* 44.1k */ + {11289600, 44100, 256, 0x10, 0x0}, + {16934400, 44100, 384, 0x11, 0x0}, + {12000000, 44100, 272, 0x11, 0x1}, + + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0}, + {18432000, 48000, 384, 0x1, 0x0}, + {12000000, 48000, 250, 0x0, 0x1}, + + /* 88.2k */ + {11289600, 88200, 128, 0x1e, 0x0}, + {16934400, 88200, 192, 0x1f, 0x0}, + {12000000, 88200, 136, 0x1f, 0x1}, + + /* 96k */ + {12288000, 96000, 128, 0xe, 0x0}, + {18432000, 96000, 192, 0xf, 0x0}, + {12000000, 96000, 125, 0xe, 0x1}, +}; + +static inline int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + + return -EINVAL; +} + +/* The set of rates we can generate from the above for each SYSCLK */ + +static unsigned int rates_12288[] = { + 8000, 12000, 16000, 24000, 24000, 32000, 48000, 96000, +}; + +static struct snd_pcm_hw_constraint_list constraints_12288 = { + .count = ARRAY_SIZE(rates_12288), + .list = rates_12288, +}; + +static unsigned int rates_112896[] = { + 8000, 11025, 22050, 44100, +}; + +static struct snd_pcm_hw_constraint_list constraints_112896 = { + .count = ARRAY_SIZE(rates_112896), + .list = rates_112896, +}; + +static unsigned int rates_12[] = { + 8000, 11025, 12000, 16000, 22050, 2400, 32000, 41100, 48000, + 48000, 88235, 96000, +}; + +static struct snd_pcm_hw_constraint_list constraints_12 = { + .count = ARRAY_SIZE(rates_12), + .list = rates_12, +}; + +/* + * Note that this should be called from init rather than from hw_params. + */ +static int wm8988_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8988_priv *wm8988 = codec->private_data; + + switch (freq) { + case 11289600: + case 18432000: + case 22579200: + case 36864000: + wm8988->sysclk_constraints = &constraints_112896; + wm8988->sysclk = freq; + return 0; + + case 12288000: + case 16934400: + case 24576000: + case 33868800: + wm8988->sysclk_constraints = &constraints_12288; + wm8988->sysclk = freq; + return 0; + + case 12000000: + case 24000000: + wm8988->sysclk_constraints = &constraints_12; + wm8988->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int wm8988_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface = 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + wm8988_write(codec, WM8988_IFACE, iface); + return 0; +} + +static int wm8988_pcm_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8988_priv *wm8988 = codec->private_data; + + /* The set of sample rates that can be supported depends on the + * MCLK supplied to the CODEC - enforce this. + */ + if (!wm8988->sysclk) { + dev_err(codec->dev, + "No MCLK configured, call set_sysclk() on init\n"); + return -EINVAL; + } + + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + wm8988->sysclk_constraints); + + return 0; +} + +static int wm8988_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + struct wm8988_priv *wm8988 = codec->private_data; + u16 iface = wm8988_read_reg_cache(codec, WM8988_IFACE) & 0x1f3; + u16 srate = wm8988_read_reg_cache(codec, WM8988_SRATE) & 0x180; + int coeff; + + coeff = get_coeff(wm8988->sysclk, params_rate(params)); + if (coeff < 0) { + coeff = get_coeff(wm8988->sysclk / 2, params_rate(params)); + srate |= 0x40; + } + if (coeff < 0) { + dev_err(codec->dev, + "Unable to configure sample rate %dHz with %dHz MCLK\n", + params_rate(params), wm8988->sysclk); + return coeff; + } + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0008; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x000c; + break; + } + + /* set iface & srate */ + wm8988_write(codec, WM8988_IFACE, iface); + if (coeff >= 0) + wm8988_write(codec, WM8988_SRATE, srate | + (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb); + + return 0; +} + +static int wm8988_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8988_read_reg_cache(codec, WM8988_ADCDAC) & 0xfff7; + + if (mute) + wm8988_write(codec, WM8988_ADCDAC, mute_reg | 0x8); + else + wm8988_write(codec, WM8988_ADCDAC, mute_reg); + return 0; +} + +static int wm8988_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 pwr_reg = wm8988_read_reg_cache(codec, WM8988_PWR1) & ~0x1c1; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* VREF, VMID=2x50k, digital enabled */ + wm8988_write(codec, WM8988_PWR1, pwr_reg | 0x00c0); + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* VREF, VMID=2x5k */ + wm8988_write(codec, WM8988_PWR1, pwr_reg | 0x1c1); + + /* Charge caps */ + msleep(100); + } + + /* VREF, VMID=2*500k, digital stopped */ + wm8988_write(codec, WM8988_PWR1, pwr_reg | 0x0141); + break; + + case SND_SOC_BIAS_OFF: + wm8988_write(codec, WM8988_PWR1, 0x0000); + break; + } + codec->bias_level = level; + return 0; +} + +#define WM8988_RATES SNDRV_PCM_RATE_8000_96000 + +#define WM8988_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops wm8988_ops = { + .startup = wm8988_pcm_startup, + .hw_params = wm8988_pcm_hw_params, + .set_fmt = wm8988_set_dai_fmt, + .set_sysclk = wm8988_set_dai_sysclk, + .digital_mute = wm8988_mute, +}; + +struct snd_soc_dai wm8988_dai = { + .name = "WM8988", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8988_RATES, + .formats = WM8988_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8988_RATES, + .formats = WM8988_FORMATS, + }, + .ops = &wm8988_ops, + .symmetric_rates = 1, +}; +EXPORT_SYMBOL_GPL(wm8988_dai); + +static int wm8988_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + wm8988_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8988_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < WM8988_NUM_REG; i++) { + if (i == WM8988_RESET) + continue; + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + + wm8988_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} + +static struct snd_soc_codec *wm8988_codec; + +static int wm8988_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (wm8988_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = wm8988_codec; + codec = wm8988_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + snd_soc_add_controls(codec, wm8988_snd_controls, + ARRAY_SIZE(wm8988_snd_controls)); + snd_soc_dapm_new_controls(codec, wm8988_dapm_widgets, + ARRAY_SIZE(wm8988_dapm_widgets)); + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + snd_soc_dapm_new_widgets(codec); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card: %d\n", ret); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + return ret; +} + +static int wm8988_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8988 = { + .probe = wm8988_probe, + .remove = wm8988_remove, + .suspend = wm8988_suspend, + .resume = wm8988_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8988); + +static int wm8988_register(struct wm8988_priv *wm8988) +{ + struct snd_soc_codec *codec = &wm8988->codec; + int ret; + u16 reg; + + if (wm8988_codec) { + dev_err(codec->dev, "Another WM8988 is registered\n"); + ret = -EINVAL; + goto err; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = wm8988; + codec->name = "WM8988"; + codec->owner = THIS_MODULE; + codec->read = wm8988_read_reg_cache; + codec->write = wm8988_write; + codec->dai = &wm8988_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8988->reg_cache); + codec->reg_cache = &wm8988->reg_cache; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8988_set_bias_level; + + memcpy(codec->reg_cache, wm8988_reg, + sizeof(wm8988_reg)); + + ret = wm8988_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset\n"); + return ret; + } + + /* set the update bits (we always update left then right) */ + reg = wm8988_read_reg_cache(codec, WM8988_RADC); + wm8988_write(codec, WM8988_RADC, reg | 0x100); + reg = wm8988_read_reg_cache(codec, WM8988_RDAC); + wm8988_write(codec, WM8988_RDAC, reg | 0x0100); + reg = wm8988_read_reg_cache(codec, WM8988_ROUT1V); + wm8988_write(codec, WM8988_ROUT1V, reg | 0x0100); + reg = wm8988_read_reg_cache(codec, WM8988_ROUT2V); + wm8988_write(codec, WM8988_ROUT2V, reg | 0x0100); + reg = wm8988_read_reg_cache(codec, WM8988_RINVOL); + wm8988_write(codec, WM8988_RINVOL, reg | 0x0100); + + wm8988_set_bias_level(&wm8988->codec, SND_SOC_BIAS_STANDBY); + + wm8988_dai.dev = codec->dev; + + wm8988_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } + + ret = snd_soc_register_dai(&wm8988_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + return ret; + } + + return 0; + +err: + kfree(wm8988); + return ret; +} + +static void wm8988_unregister(struct wm8988_priv *wm8988) +{ + wm8988_set_bias_level(&wm8988->codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dai(&wm8988_dai); + snd_soc_unregister_codec(&wm8988->codec); + kfree(wm8988); + wm8988_codec = NULL; +} + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static int wm8988_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8988_priv *wm8988; + struct snd_soc_codec *codec; + + wm8988 = kzalloc(sizeof(struct wm8988_priv), GFP_KERNEL); + if (wm8988 == NULL) + return -ENOMEM; + + codec = &wm8988->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + + i2c_set_clientdata(i2c, wm8988); + codec->control_data = i2c; + + codec->dev = &i2c->dev; + + return wm8988_register(wm8988); +} + +static int wm8988_i2c_remove(struct i2c_client *client) +{ + struct wm8988_priv *wm8988 = i2c_get_clientdata(client); + wm8988_unregister(wm8988); + return 0; +} + +static const struct i2c_device_id wm8988_i2c_id[] = { + { "wm8988", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8988_i2c_id); + +static struct i2c_driver wm8988_i2c_driver = { + .driver = { + .name = "WM8988", + .owner = THIS_MODULE, + }, + .probe = wm8988_i2c_probe, + .remove = wm8988_i2c_remove, + .id_table = wm8988_i2c_id, +}; +#endif + +#if defined(CONFIG_SPI_MASTER) +static int wm8988_spi_write(struct spi_device *spi, const char *data, int len) +{ + struct spi_transfer t; + struct spi_message m; + u8 msg[2]; + + if (len <= 0) + return 0; + + msg[0] = data[0]; + msg[1] = data[1]; + + spi_message_init(&m); + memset(&t, 0, (sizeof t)); + + t.tx_buf = &msg[0]; + t.len = len; + + spi_message_add_tail(&t, &m); + spi_sync(spi, &m); + + return len; +} + +static int __devinit wm8988_spi_probe(struct spi_device *spi) +{ + struct wm8988_priv *wm8988; + struct snd_soc_codec *codec; + + wm8988 = kzalloc(sizeof(struct wm8988_priv), GFP_KERNEL); + if (wm8988 == NULL) + return -ENOMEM; + + codec = &wm8988->codec; + codec->hw_write = (hw_write_t)wm8988_spi_write; + codec->control_data = spi; + codec->dev = &spi->dev; + + spi->dev.driver_data = wm8988; + + return wm8988_register(wm8988); +} + +static int __devexit wm8988_spi_remove(struct spi_device *spi) +{ + struct wm8988_priv *wm8988 = spi->dev.driver_data; + + wm8988_unregister(wm8988); + + return 0; +} + +static struct spi_driver wm8988_spi_driver = { + .driver = { + .name = "wm8988", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = wm8988_spi_probe, + .remove = __devexit_p(wm8988_spi_remove), +}; +#endif + +static int __init wm8988_modinit(void) +{ + int ret; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&wm8988_i2c_driver); + if (ret != 0) + pr_err("WM8988: Unable to register I2C driver: %d\n", ret); +#endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&wm8988_spi_driver); + if (ret != 0) + pr_err("WM8988: Unable to register SPI driver: %d\n", ret); +#endif + return ret; +} +module_init(wm8988_modinit); + +static void __exit wm8988_exit(void) +{ +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&wm8988_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8988_spi_driver); +#endif +} +module_exit(wm8988_exit); + + +MODULE_DESCRIPTION("ASoC WM8988 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8988.h b/sound/soc/codecs/wm8988.h new file mode 100644 index 00000000000..4552d37fdd4 --- /dev/null +++ b/sound/soc/codecs/wm8988.h @@ -0,0 +1,60 @@ +/* + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie + * + * Based on WM8753.h + * + * 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 _WM8988_H +#define _WM8988_H + +/* WM8988 register space */ + +#define WM8988_LINVOL 0x00 +#define WM8988_RINVOL 0x01 +#define WM8988_LOUT1V 0x02 +#define WM8988_ROUT1V 0x03 +#define WM8988_ADCDAC 0x05 +#define WM8988_IFACE 0x07 +#define WM8988_SRATE 0x08 +#define WM8988_LDAC 0x0a +#define WM8988_RDAC 0x0b +#define WM8988_BASS 0x0c +#define WM8988_TREBLE 0x0d +#define WM8988_RESET 0x0f +#define WM8988_3D 0x10 +#define WM8988_ALC1 0x11 +#define WM8988_ALC2 0x12 +#define WM8988_ALC3 0x13 +#define WM8988_NGATE 0x14 +#define WM8988_LADC 0x15 +#define WM8988_RADC 0x16 +#define WM8988_ADCTL1 0x17 +#define WM8988_ADCTL2 0x18 +#define WM8988_PWR1 0x19 +#define WM8988_PWR2 0x1a +#define WM8988_ADCTL3 0x1b +#define WM8988_ADCIN 0x1f +#define WM8988_LADCIN 0x20 +#define WM8988_RADCIN 0x21 +#define WM8988_LOUTM1 0x22 +#define WM8988_LOUTM2 0x23 +#define WM8988_ROUTM1 0x24 +#define WM8988_ROUTM2 0x25 +#define WM8988_LOUT2V 0x28 +#define WM8988_ROUT2V 0x29 +#define WM8988_LPPB 0x43 +#define WM8988_NUM_REG 0x44 + +#define WM8988_SYSCLK 0 + +extern struct snd_soc_dai wm8988_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8988; + +#endif -- cgit v1.2.3 From 894bf92fdec9909fefcfe907786c6c6944a22052 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 9 Apr 2009 12:34:40 +0300 Subject: ASoC: tlv320aic23: add DSP_A format support Add DSP_A interface format support by setting the LRP bit in DSP mode. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic23.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c index c3f4afb5d01..21f69df9994 100644 --- a/sound/soc/codecs/tlv320aic23.c +++ b/sound/soc/codecs/tlv320aic23.c @@ -523,6 +523,8 @@ static int tlv320aic23_set_dai_fmt(struct snd_soc_dai *codec_dai, case SND_SOC_DAIFMT_I2S: iface_reg |= TLV320AIC23_FOR_I2S; break; + case SND_SOC_DAIFMT_DSP_A: + iface_reg |= TLV320AIC23_LRP_ON; case SND_SOC_DAIFMT_DSP_B: iface_reg |= TLV320AIC23_FOR_DSP; break; -- cgit v1.2.3 From f4c1724f3437ac70d8330968379148c954ca34c7 Mon Sep 17 00:00:00 2001 From: Alexander Beregalov Date: Sun, 12 Apr 2009 05:04:43 +0400 Subject: ASoC: n810: replace BUG() with BUG_ON() Signed-off-by: Alexander Beregalov Signed-off-by: Mark Brown --- sound/soc/omap/n810.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/omap/n810.c b/sound/soc/omap/n810.c index a6d1178ce12..e54e1c2f5e6 100644 --- a/sound/soc/omap/n810.c +++ b/sound/soc/omap/n810.c @@ -383,10 +383,9 @@ static int __init n810_soc_init(void) clk_set_parent(sys_clkout2_src, func96m_clk); clk_set_rate(sys_clkout2, 12000000); - if (gpio_request(N810_HEADSET_AMP_GPIO, "hs_amp") < 0) - BUG(); - if (gpio_request(N810_SPEAKER_AMP_GPIO, "spk_amp") < 0) - BUG(); + BUG_ON((gpio_request(N810_HEADSET_AMP_GPIO, "hs_amp") < 0) || + (gpio_request(N810_SPEAKER_AMP_GPIO, "spk_amp") < 0)); + gpio_direction_output(N810_HEADSET_AMP_GPIO, 0); gpio_direction_output(N810_SPEAKER_AMP_GPIO, 0); -- cgit v1.2.3 From f4976116a98f108bf385f217332aadb3ca98fe66 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 13 Apr 2009 10:53:02 +0100 Subject: ASoC: WM9713 requires symmetric rates on the voice DAI Signed-off-by: Mark Brown --- sound/soc/codecs/wm9713.c | 1 + 1 file changed, 1 insertion(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c index 523bad077fa..aa94cc68f0c 100644 --- a/sound/soc/codecs/wm9713.c +++ b/sound/soc/codecs/wm9713.c @@ -1069,6 +1069,7 @@ struct snd_soc_dai wm9713_dai[] = { .rates = WM9713_PCM_RATES, .formats = WM9713_PCM_FORMATS,}, .ops = &wm9713_dai_ops_voice, + .symmetric_rates = 1, }, }; EXPORT_SYMBOL_GPL(wm9713_dai); -- cgit v1.2.3 From 025756eca458b4a3d5e3d76baaffb2e8e3df79db Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 13 Apr 2009 11:09:18 +0100 Subject: ASoC: Factor out application of power for generic widgets This is simple code motion, intended to support future refactoring of the DAPM algorithms and (more immediately) the additon of events for DACs and ADCs. Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 110 ++++++++++++++++++++++++++++----------------------- 1 file changed, 60 insertions(+), 50 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 46485de9e6f..713d1258670 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -522,6 +522,65 @@ int dapm_reg_event(struct snd_soc_dapm_widget *w, } EXPORT_SYMBOL_GPL(dapm_reg_event); +/* Standard power change method, used to apply power changes to most + * widgets. + */ +static int dapm_generic_apply_power(struct snd_soc_dapm_widget *w) +{ + int ret; + + /* call any power change event handlers */ + if (w->event) + pr_debug("power %s event for %s flags %x\n", + w->power ? "on" : "off", + w->name, w->event_flags); + + /* power up pre event */ + if (w->power && w->event && + (w->event_flags & SND_SOC_DAPM_PRE_PMU)) { + ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMU); + if (ret < 0) + return ret; + } + + /* power down pre event */ + if (!w->power && w->event && + (w->event_flags & SND_SOC_DAPM_PRE_PMD)) { + ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD); + if (ret < 0) + return ret; + } + + /* Lower PGA volume to reduce pops */ + if (w->id == snd_soc_dapm_pga && !w->power) + dapm_set_pga(w, w->power); + + dapm_update_bits(w); + + /* Raise PGA volume to reduce pops */ + if (w->id == snd_soc_dapm_pga && w->power) + dapm_set_pga(w, w->power); + + /* power up post event */ + if (w->power && w->event && + (w->event_flags & SND_SOC_DAPM_POST_PMU)) { + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMU); + if (ret < 0) + return ret; + } + + /* power down post event */ + if (!w->power && w->event && + (w->event_flags & SND_SOC_DAPM_POST_PMD)) { + ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMD); + if (ret < 0) + return ret; + } + + return 0; +} + /* * Scan a single DAPM widget for a complete audio path and update the * power status appropriately. @@ -601,56 +660,7 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event, if (!power_change) return 0; - /* call any power change event handlers */ - if (w->event) - pr_debug("power %s event for %s flags %x\n", - w->power ? "on" : "off", - w->name, w->event_flags); - - /* power up pre event */ - if (power && w->event && - (w->event_flags & SND_SOC_DAPM_PRE_PMU)) { - ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMU); - if (ret < 0) - return ret; - } - - /* power down pre event */ - if (!power && w->event && - (w->event_flags & SND_SOC_DAPM_PRE_PMD)) { - ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD); - if (ret < 0) - return ret; - } - - /* Lower PGA volume to reduce pops */ - if (w->id == snd_soc_dapm_pga && !power) - dapm_set_pga(w, power); - - dapm_update_bits(w); - - /* Raise PGA volume to reduce pops */ - if (w->id == snd_soc_dapm_pga && power) - dapm_set_pga(w, power); - - /* power up post event */ - if (power && w->event && - (w->event_flags & SND_SOC_DAPM_POST_PMU)) { - ret = w->event(w, - NULL, SND_SOC_DAPM_POST_PMU); - if (ret < 0) - return ret; - } - - /* power down post event */ - if (!power && w->event && - (w->event_flags & SND_SOC_DAPM_POST_PMD)) { - ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMD); - if (ret < 0) - return ret; - } - - return 0; + return dapm_generic_apply_power(w); } /* -- cgit v1.2.3 From f6d655a6e6974e474a11b25052c29d10b80814b3 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 13 Apr 2009 11:27:03 +0100 Subject: ASoC: Support DAPM events for DACs and ADCs Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 713d1258670..a6d73379ab3 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -598,18 +598,22 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event, if (w->id == snd_soc_dapm_adc && w->active) { in = is_connected_input_ep(w); dapm_clear_walk(w->codec); - w->power = (in != 0) ? 1 : 0; - dapm_update_bits(w); - return 0; + power = (in != 0) ? 1 : 0; + if (power == w->power) + return 0; + w->power = power; + return dapm_generic_apply_power(w); } /* active DAC */ if (w->id == snd_soc_dapm_dac && w->active) { out = is_connected_output_ep(w); dapm_clear_walk(w->codec); - w->power = (out != 0) ? 1 : 0; - dapm_update_bits(w); - return 0; + power = (out != 0) ? 1 : 0; + if (power == w->power) + return 0; + w->power = power; + return dapm_generic_apply_power(w); } /* pre and post event widgets */ -- cgit v1.2.3 From 6bbcb459cd50807511491ddf96bca1ef92006bf8 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 13 Apr 2009 11:29:10 +0100 Subject: ASoC: Move the WM9713 voice DAC powerdown to a DAPM event This ensures that we sync with the DAPM powerdown sequencing properly and don't need to bounce the power on the voice DAC so often. Signed-off-by: Mark Brown --- sound/soc/codecs/wm9713.c | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c index aa94cc68f0c..a6feb784231 100644 --- a/sound/soc/codecs/wm9713.c +++ b/sound/soc/codecs/wm9713.c @@ -189,6 +189,26 @@ SOC_SINGLE("3D Lower Cut-off Switch", AC97_REC_GAIN_MIC, 4, 1, 0), SOC_SINGLE("3D Depth", AC97_REC_GAIN_MIC, 0, 15, 1), }; +static int wm9713_voice_shutdown(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 status, rate; + + BUG_ON(event != SND_SOC_DAPM_PRE_PMD); + + /* Gracefully shut down the voice interface. */ + status = ac97_read(codec, AC97_EXTENDED_MID) | 0x1000; + rate = ac97_read(codec, AC97_HANDSET_RATE) & 0xF0FF; + ac97_write(codec, AC97_HANDSET_RATE, rate | 0x0200); + schedule_timeout_interruptible(msecs_to_jiffies(1)); + ac97_write(codec, AC97_HANDSET_RATE, rate | 0x0F00); + ac97_write(codec, AC97_EXTENDED_MID, status); + + return 0; +} + + /* We have to create a fake left and right HP mixers because * the codec only has a single control that is shared by both channels. * This makes it impossible to determine the audio path using the current @@ -400,7 +420,8 @@ SND_SOC_DAPM_MIXER("AC97 Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_MIXER("HP Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_MIXER("Line Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), SND_SOC_DAPM_MIXER("Capture Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), -SND_SOC_DAPM_DAC("Voice DAC", "Voice Playback", AC97_EXTENDED_MID, 12, 1), +SND_SOC_DAPM_DAC_E("Voice DAC", "Voice Playback", AC97_EXTENDED_MID, 12, 1, + wm9713_voice_shutdown, SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_DAC("Aux DAC", "Aux Playback", AC97_EXTENDED_MID, 11, 1), SND_SOC_DAPM_PGA("Left ADC", AC97_EXTENDED_MID, 5, 1, NULL, 0), SND_SOC_DAPM_PGA("Right ADC", AC97_EXTENDED_MID, 4, 1, NULL, 0), @@ -936,21 +957,6 @@ static int wm9713_pcm_hw_params(struct snd_pcm_substream *substream, return 0; } -static void wm9713_voiceshutdown(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct snd_soc_codec *codec = dai->codec; - u16 status, rate; - - /* Gracefully shut down the voice interface. */ - status = ac97_read(codec, AC97_EXTENDED_STATUS) | 0x1000; - rate = ac97_read(codec, AC97_HANDSET_RATE) & 0xF0FF; - ac97_write(codec, AC97_HANDSET_RATE, rate | 0x0200); - schedule_timeout_interruptible(msecs_to_jiffies(1)); - ac97_write(codec, AC97_HANDSET_RATE, rate | 0x0F00); - ac97_write(codec, AC97_EXTENDED_MID, status); -} - static int ac97_hifi_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -1019,7 +1025,6 @@ static struct snd_soc_dai_ops wm9713_dai_ops_aux = { static struct snd_soc_dai_ops wm9713_dai_ops_voice = { .hw_params = wm9713_pcm_hw_params, - .shutdown = wm9713_voiceshutdown, .set_clkdiv = wm9713_set_dai_clkdiv, .set_pll = wm9713_set_dai_pll, .set_fmt = wm9713_set_dai_fmt, -- cgit v1.2.3 From a820532002e70e3a06f1ea7133e9b02443d07382 Mon Sep 17 00:00:00 2001 From: Daniel Ribeiro Date: Wed, 8 Apr 2009 10:51:24 -0300 Subject: ASoC: pxa-ssp.c fix clock/frame invert SCMODE(0): Data Driven (Falling), Data Sampled (Rising), Idle State (Low) SCMODE(1): Data Driven (Rising), Data Sampled (Falling), Idle State (Low) SCMODE(2): Data Driven (Rising), Data Sampled (Falling), Idle State (High) SCMODE(3): Data Driven (Falling), Data Sampled (Rising), Idle State (High) SCMODE(3) does not invert the clock polarity compared to the default SCMODE(0). This patch also adds all possible NF/IF, NB/IB combinations to the DSP_A and DSP_B modes. Signed-off-by: Daniel Ribeiro Signed-off-by: Mark Brown --- sound/soc/pxa/pxa-ssp.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index c7c1996a544..176af7ff234 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -568,7 +568,10 @@ static int pxa_ssp_set_dai_fmt(struct snd_soc_dai *cpu_dai, case SND_SOC_DAIFMT_NB_IF: break; case SND_SOC_DAIFMT_IB_IF: - sspsp |= SSPSP_SCMODE(3); + sspsp |= SSPSP_SCMODE(2); + break; + case SND_SOC_DAIFMT_IB_NF: + sspsp |= SSPSP_SCMODE(2) | SSPSP_SFRMP; break; default: return -EINVAL; @@ -585,7 +588,13 @@ static int pxa_ssp_set_dai_fmt(struct snd_soc_dai *cpu_dai, case SND_SOC_DAIFMT_NB_NF: sspsp |= SSPSP_SFRMP; break; + case SND_SOC_DAIFMT_NB_IF: + break; case SND_SOC_DAIFMT_IB_IF: + sspsp |= SSPSP_SCMODE(2); + break; + case SND_SOC_DAIFMT_IB_NF: + sspsp |= SSPSP_SCMODE(2) | SSPSP_SFRMP; break; default: return -EINVAL; -- cgit v1.2.3 From f2644a2c00a06236a9c5e85488b0680825bad39c Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 7 Apr 2009 19:20:14 +0100 Subject: ASoC: Add WM8960 CODEC driver The WM8960 is a low power, high quality stereo codec designed for portable digital audio applications. Stereo class D speaker drivers provide 1W per channel into 8W loads. Guaranteed low leakage, excellent PSRR and pop/click suppression mechanisms enable direct battery connection for the speaker supply. The device also integrates a complete microphone interface and a stereo headphone driver. External component requirements are drastically reduced as no separate microphone, speaker or headphone amplifiers are required. Advanced on-chip digital signal processing performs automatic level control for the microphone or line input. Stereo 24-bit sigma-delta ADCs and DACs are used with low power over-sampling digital interpolation and decimation filters and a flexible digital audio interface. The master clock can be input directly or generated internally by an onboard PLL, supporting most commonly-used clocking schemes. This driver was originally written by Liam Girdwood, with substantial subsequent additions and updates for feature completeness and changes in the ASoC framework from me. Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm8960.c | 969 ++++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm8960.h | 127 ++++++ 4 files changed, 1102 insertions(+) create mode 100644 sound/soc/codecs/wm8960.c create mode 100644 sound/soc/codecs/wm8960.h (limited to 'sound/soc') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index ab364854675..121d63f13db 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -35,6 +35,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8753 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8900 if I2C select SND_SOC_WM8903 if I2C + select SND_SOC_WM8960 if I2C select SND_SOC_WM8971 if I2C select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8990 if I2C @@ -139,6 +140,9 @@ config SND_SOC_WM8900 config SND_SOC_WM8903 tristate +config SND_SOC_WM8960 + tristate + config SND_SOC_WM8971 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index a72548dc188..d8e15a47711 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -23,6 +23,7 @@ snd-soc-wm8750-objs := wm8750.o snd-soc-wm8753-objs := wm8753.o snd-soc-wm8900-objs := wm8900.o snd-soc-wm8903-objs := wm8903.o +snd-soc-wm8960-objs := wm8960.o snd-soc-wm8971-objs := wm8971.o snd-soc-wm8988-objs := wm8988.o snd-soc-wm8990-objs := wm8990.o @@ -56,6 +57,7 @@ obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o +obj-$(CONFIG_SND_SOC_WM8960) += snd-soc-wm8960.o obj-$(CONFIG_SND_SOC_WM8988) += snd-soc-wm8988.o obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o obj-$(CONFIG_SND_SOC_WM8991) += snd-soc-wm8991.o diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c new file mode 100644 index 00000000000..e224d8add17 --- /dev/null +++ b/sound/soc/codecs/wm8960.c @@ -0,0 +1,969 @@ +/* + * wm8960.c -- WM8960 ALSA SoC Audio driver + * + * Author: Liam Girdwood + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8960.h" + +#define AUDIO_NAME "wm8960" + +struct snd_soc_codec_device soc_codec_dev_wm8960; + +/* R25 - Power 1 */ +#define WM8960_VREF 0x40 + +/* R28 - Anti-pop 1 */ +#define WM8960_POBCTRL 0x80 +#define WM8960_BUFDCOPEN 0x10 +#define WM8960_BUFIOEN 0x08 +#define WM8960_SOFT_ST 0x04 +#define WM8960_HPSTBY 0x01 + +/* R29 - Anti-pop 2 */ +#define WM8960_DISOP 0x40 + +/* + * wm8960 register cache + * We can't read the WM8960 register space when we are + * using 2 wire for device control, so we cache them instead. + */ +static const u16 wm8960_reg[WM8960_CACHEREGNUM] = { + 0x0097, 0x0097, 0x0000, 0x0000, + 0x0000, 0x0008, 0x0000, 0x000a, + 0x01c0, 0x0000, 0x00ff, 0x00ff, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x007b, 0x0100, 0x0032, + 0x0000, 0x00c3, 0x00c3, 0x01c0, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, + 0x0100, 0x0100, 0x0050, 0x0050, + 0x0050, 0x0050, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0040, 0x0000, + 0x0000, 0x0050, 0x0050, 0x0000, + 0x0002, 0x0037, 0x004d, 0x0080, + 0x0008, 0x0031, 0x0026, 0x00e9, +}; + +struct wm8960_priv { + u16 reg_cache[WM8960_CACHEREGNUM]; + struct snd_soc_codec codec; +}; + +/* + * read wm8960 register cache + */ +static inline unsigned int wm8960_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg == WM8960_RESET) + return 0; + if (reg >= WM8960_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write wm8960 register cache + */ +static inline void wm8960_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= WM8960_CACHEREGNUM) + return; + cache[reg] = value; +} + +static inline unsigned int wm8960_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + return wm8960_read_reg_cache(codec, reg); +} + +/* + * write to the WM8960 register space + */ +static int wm8960_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + /* data is + * D15..D9 WM8960 register offset + * D8...D0 register data + */ + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8960_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +#define wm8960_reset(c) wm8960_write(c, WM8960_RESET, 0) + +/* enumerated controls */ +static const char *wm8960_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; +static const char *wm8960_polarity[] = {"No Inversion", "Left Inverted", + "Right Inverted", "Stereo Inversion"}; +static const char *wm8960_3d_upper_cutoff[] = {"High", "Low"}; +static const char *wm8960_3d_lower_cutoff[] = {"Low", "High"}; +static const char *wm8960_alcfunc[] = {"Off", "Right", "Left", "Stereo"}; +static const char *wm8960_alcmode[] = {"ALC", "Limiter"}; + +static const struct soc_enum wm8960_enum[] = { + SOC_ENUM_SINGLE(WM8960_DACCTL1, 1, 4, wm8960_deemph), + SOC_ENUM_SINGLE(WM8960_DACCTL1, 5, 4, wm8960_polarity), + SOC_ENUM_SINGLE(WM8960_DACCTL2, 5, 4, wm8960_polarity), + SOC_ENUM_SINGLE(WM8960_3D, 6, 2, wm8960_3d_upper_cutoff), + SOC_ENUM_SINGLE(WM8960_3D, 5, 2, wm8960_3d_lower_cutoff), + SOC_ENUM_SINGLE(WM8960_ALC1, 7, 4, wm8960_alcfunc), + SOC_ENUM_SINGLE(WM8960_ALC3, 8, 2, wm8960_alcmode), +}; + +static const DECLARE_TLV_DB_SCALE(adc_tlv, -9700, 50, 0); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12700, 50, 1); +static const DECLARE_TLV_DB_SCALE(bypass_tlv, -2100, 300, 0); +static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); + +static const struct snd_kcontrol_new wm8960_snd_controls[] = { +SOC_DOUBLE_R_TLV("Capture Volume", WM8960_LINVOL, WM8960_RINVOL, + 0, 63, 0, adc_tlv), +SOC_DOUBLE_R("Capture Volume ZC Switch", WM8960_LINVOL, WM8960_RINVOL, + 6, 1, 0), +SOC_DOUBLE_R("Capture Switch", WM8960_LINVOL, WM8960_RINVOL, + 7, 1, 0), + +SOC_DOUBLE_R_TLV("Playback Volume", WM8960_LDAC, WM8960_RDAC, + 0, 255, 0, dac_tlv), + +SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8960_LOUT1, WM8960_ROUT1, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Headphone Playback ZC Switch", WM8960_LOUT1, WM8960_ROUT1, + 7, 1, 0), + +SOC_DOUBLE_R_TLV("Speaker Playback Volume", WM8960_LOUT2, WM8960_ROUT2, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Speaker Playback ZC Switch", WM8960_LOUT2, WM8960_ROUT2, + 7, 1, 0), +SOC_SINGLE("Speaker DC Volume", WM8960_CLASSD3, 3, 5, 0), +SOC_SINGLE("Speaker AC Volume", WM8960_CLASSD3, 0, 5, 0), + +SOC_SINGLE("PCM Playback -6dB Switch", WM8960_DACCTL1, 7, 1, 0), +SOC_ENUM("ADC Polarity", wm8960_enum[1]), +SOC_ENUM("Playback De-emphasis", wm8960_enum[0]), +SOC_SINGLE("ADC High Pass Filter Switch", WM8960_DACCTL1, 0, 1, 0), + +SOC_ENUM("DAC Polarity", wm8960_enum[2]), + +SOC_ENUM("3D Filter Upper Cut-Off", wm8960_enum[3]), +SOC_ENUM("3D Filter Lower Cut-Off", wm8960_enum[4]), +SOC_SINGLE("3D Volume", WM8960_3D, 1, 15, 0), +SOC_SINGLE("3D Switch", WM8960_3D, 0, 1, 0), + +SOC_ENUM("ALC Function", wm8960_enum[5]), +SOC_SINGLE("ALC Max Gain", WM8960_ALC1, 4, 7, 0), +SOC_SINGLE("ALC Target", WM8960_ALC1, 0, 15, 1), +SOC_SINGLE("ALC Min Gain", WM8960_ALC2, 4, 7, 0), +SOC_SINGLE("ALC Hold Time", WM8960_ALC2, 0, 15, 0), +SOC_ENUM("ALC Mode", wm8960_enum[6]), +SOC_SINGLE("ALC Decay", WM8960_ALC3, 4, 15, 0), +SOC_SINGLE("ALC Attack", WM8960_ALC3, 0, 15, 0), + +SOC_SINGLE("Noise Gate Threshold", WM8960_NOISEG, 3, 31, 0), +SOC_SINGLE("Noise Gate Switch", WM8960_NOISEG, 0, 1, 0), + +SOC_DOUBLE_R("ADC PCM Capture Volume", WM8960_LINPATH, WM8960_RINPATH, + 0, 127, 0), + +SOC_SINGLE_TLV("Left Output Mixer Boost Bypass Volume", + WM8960_BYPASS1, 4, 7, 1, bypass_tlv), +SOC_SINGLE_TLV("Left Output Mixer LINPUT3 Volume", + WM8960_LOUTMIX, 4, 7, 1, bypass_tlv), +SOC_SINGLE_TLV("Right Output Mixer Boost Bypass Volume", + WM8960_BYPASS2, 4, 7, 1, bypass_tlv), +SOC_SINGLE_TLV("Right Output Mixer RINPUT3 Volume", + WM8960_ROUTMIX, 4, 7, 1, bypass_tlv), +}; + +static const struct snd_kcontrol_new wm8960_lin_boost[] = { +SOC_DAPM_SINGLE("LINPUT2 Switch", WM8960_LINPATH, 6, 1, 0), +SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LINPATH, 7, 1, 0), +SOC_DAPM_SINGLE("LINPUT1 Switch", WM8960_LINPATH, 8, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_lin[] = { +SOC_DAPM_SINGLE("Boost Switch", WM8960_LINPATH, 3, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_rin_boost[] = { +SOC_DAPM_SINGLE("RINPUT2 Switch", WM8960_RINPATH, 6, 1, 0), +SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_RINPATH, 7, 1, 0), +SOC_DAPM_SINGLE("RINPUT1 Switch", WM8960_RINPATH, 8, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_rin[] = { +SOC_DAPM_SINGLE("Boost Switch", WM8960_RINPATH, 3, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_loutput_mixer[] = { +SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_LOUTMIX, 8, 1, 0), +SOC_DAPM_SINGLE("LINPUT3 Switch", WM8960_LOUTMIX, 7, 1, 0), +SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS1, 7, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_routput_mixer[] = { +SOC_DAPM_SINGLE("PCM Playback Switch", WM8960_ROUTMIX, 8, 1, 0), +SOC_DAPM_SINGLE("RINPUT3 Switch", WM8960_ROUTMIX, 7, 1, 0), +SOC_DAPM_SINGLE("Boost Bypass Switch", WM8960_BYPASS2, 7, 1, 0), +}; + +static const struct snd_kcontrol_new wm8960_mono_out[] = { +SOC_DAPM_SINGLE("Left Switch", WM8960_MONOMIX1, 7, 1, 0), +SOC_DAPM_SINGLE("Right Switch", WM8960_MONOMIX2, 7, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8960_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("LINPUT1"), +SND_SOC_DAPM_INPUT("RINPUT1"), +SND_SOC_DAPM_INPUT("LINPUT2"), +SND_SOC_DAPM_INPUT("RINPUT2"), +SND_SOC_DAPM_INPUT("LINPUT3"), +SND_SOC_DAPM_INPUT("RINPUT3"), + +SND_SOC_DAPM_MICBIAS("MICB", WM8960_POWER1, 1, 0), + +SND_SOC_DAPM_MIXER("Left Boost Mixer", WM8960_POWER1, 5, 0, + wm8960_lin_boost, ARRAY_SIZE(wm8960_lin_boost)), +SND_SOC_DAPM_MIXER("Right Boost Mixer", WM8960_POWER1, 4, 0, + wm8960_rin_boost, ARRAY_SIZE(wm8960_rin_boost)), + +SND_SOC_DAPM_MIXER("Left Input Mixer", WM8960_POWER3, 5, 0, + wm8960_lin, ARRAY_SIZE(wm8960_lin)), +SND_SOC_DAPM_MIXER("Right Input Mixer", WM8960_POWER3, 4, 0, + wm8960_rin, ARRAY_SIZE(wm8960_rin)), + +SND_SOC_DAPM_ADC("Left ADC", "Capture", WM8960_POWER2, 3, 0), +SND_SOC_DAPM_ADC("Right ADC", "Capture", WM8960_POWER2, 2, 0), + +SND_SOC_DAPM_DAC("Left DAC", "Playback", WM8960_POWER2, 8, 0), +SND_SOC_DAPM_DAC("Right DAC", "Playback", WM8960_POWER2, 7, 0), + +SND_SOC_DAPM_MIXER("Left Output Mixer", WM8960_POWER3, 3, 0, + &wm8960_loutput_mixer[0], + ARRAY_SIZE(wm8960_loutput_mixer)), +SND_SOC_DAPM_MIXER("Right Output Mixer", WM8960_POWER3, 2, 0, + &wm8960_routput_mixer[0], + ARRAY_SIZE(wm8960_routput_mixer)), + +SND_SOC_DAPM_MIXER("Mono Output Mixer", WM8960_POWER2, 1, 0, + &wm8960_mono_out[0], + ARRAY_SIZE(wm8960_mono_out)), + +SND_SOC_DAPM_PGA("LOUT1 PGA", WM8960_POWER2, 6, 0, NULL, 0), +SND_SOC_DAPM_PGA("ROUT1 PGA", WM8960_POWER2, 5, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Left Speaker PGA", WM8960_POWER2, 4, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Speaker PGA", WM8960_POWER2, 3, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Right Speaker Output", WM8960_CLASSD1, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("Left Speaker Output", WM8960_CLASSD1, 6, 0, NULL, 0), + +SND_SOC_DAPM_OUTPUT("SPK_LP"), +SND_SOC_DAPM_OUTPUT("SPK_LN"), +SND_SOC_DAPM_OUTPUT("HP_L"), +SND_SOC_DAPM_OUTPUT("HP_R"), +SND_SOC_DAPM_OUTPUT("SPK_RP"), +SND_SOC_DAPM_OUTPUT("SPK_RN"), +SND_SOC_DAPM_OUTPUT("OUT3"), +}; + +static const struct snd_soc_dapm_route audio_paths[] = { + { "Left Boost Mixer", "LINPUT1 Switch", "LINPUT1" }, + { "Left Boost Mixer", "LINPUT2 Switch", "LINPUT2" }, + { "Left Boost Mixer", "LINPUT3 Switch", "LINPUT3" }, + + { "Left Input Mixer", "Boost Switch", "Left Boost Mixer", }, + { "Left Input Mixer", NULL, "LINPUT1", }, /* Really Boost Switch */ + { "Left Input Mixer", NULL, "LINPUT2" }, + { "Left Input Mixer", NULL, "LINPUT3" }, + + { "Right Boost Mixer", "RINPUT1 Switch", "RINPUT1" }, + { "Right Boost Mixer", "RINPUT2 Switch", "RINPUT2" }, + { "Right Boost Mixer", "RINPUT3 Switch", "RINPUT3" }, + + { "Right Input Mixer", "Boost Switch", "Right Boost Mixer", }, + { "Right Input Mixer", NULL, "RINPUT1", }, /* Really Boost Switch */ + { "Right Input Mixer", NULL, "RINPUT2" }, + { "Right Input Mixer", NULL, "LINPUT3" }, + + { "Left ADC", NULL, "Left Input Mixer" }, + { "Right ADC", NULL, "Right Input Mixer" }, + + { "Left Output Mixer", "LINPUT3 Switch", "LINPUT3" }, + { "Left Output Mixer", "Boost Bypass Switch", "Left Boost Mixer"} , + { "Left Output Mixer", "PCM Playback Switch", "Left DAC" }, + + { "Right Output Mixer", "RINPUT3 Switch", "RINPUT3" }, + { "Right Output Mixer", "Boost Bypass Switch", "Right Boost Mixer" } , + { "Right Output Mixer", "PCM Playback Switch", "Right DAC" }, + + { "Mono Output Mixer", "Left Switch", "Left Output Mixer" }, + { "Mono Output Mixer", "Right Switch", "Right Output Mixer" }, + + { "LOUT1 PGA", NULL, "Left Output Mixer" }, + { "ROUT1 PGA", NULL, "Right Output Mixer" }, + + { "HP_L", NULL, "LOUT1 PGA" }, + { "HP_R", NULL, "ROUT1 PGA" }, + + { "Left Speaker PGA", NULL, "Left Output Mixer" }, + { "Right Speaker PGA", NULL, "Right Output Mixer" }, + + { "Left Speaker Output", NULL, "Left Speaker PGA" }, + { "Right Speaker Output", NULL, "Right Speaker PGA" }, + + { "SPK_LN", NULL, "Left Speaker Output" }, + { "SPK_LP", NULL, "Left Speaker Output" }, + { "SPK_RN", NULL, "Right Speaker Output" }, + { "SPK_RP", NULL, "Right Speaker Output" }, + + { "OUT3", NULL, "Mono Output Mixer", } +}; + +static int wm8960_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8960_dapm_widgets, + ARRAY_SIZE(wm8960_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface |= 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + /* set iface */ + wm8960_write(codec, WM8960_IFACE1, iface); + return 0; +} + +static int wm8960_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + u16 iface = wm8960_read(codec, WM8960_IFACE1) & 0xfff3; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0008; + break; + } + + /* set iface */ + wm8960_write(codec, WM8960_IFACE1, iface); + return 0; +} + +static int wm8960_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8960_read(codec, WM8960_DACCTL1) & 0xfff7; + + if (mute) + wm8960_write(codec, WM8960_DACCTL1, mute_reg | 0x8); + else + wm8960_write(codec, WM8960_DACCTL1, mute_reg); + return 0; +} + +static int wm8960_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct wm8960_data *pdata = codec->dev->platform_data; + u16 reg; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* Set VMID to 2x50k */ + reg = wm8960_read(codec, WM8960_POWER1); + reg &= ~0x180; + reg |= 0x80; + wm8960_write(codec, WM8960_POWER1, reg); + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Enable anti-pop features */ + wm8960_write(codec, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN | WM8960_BUFIOEN); + + /* Discharge HP output */ + reg = WM8960_DISOP; + if (pdata) + reg |= pdata->dres << 4; + wm8960_write(codec, WM8960_APOP2, reg); + + msleep(400); + + wm8960_write(codec, WM8960_APOP2, 0); + + /* Enable & ramp VMID at 2x50k */ + reg = wm8960_read(codec, WM8960_POWER1); + reg |= 0x80; + wm8960_write(codec, WM8960_POWER1, reg); + msleep(100); + + /* Enable VREF */ + wm8960_write(codec, WM8960_POWER1, reg | WM8960_VREF); + + /* Disable anti-pop features */ + wm8960_write(codec, WM8960_APOP1, WM8960_BUFIOEN); + } + + /* Set VMID to 2x250k */ + reg = wm8960_read(codec, WM8960_POWER1); + reg &= ~0x180; + reg |= 0x100; + wm8960_write(codec, WM8960_POWER1, reg); + break; + + case SND_SOC_BIAS_OFF: + /* Enable anti-pop features */ + wm8960_write(codec, WM8960_APOP1, + WM8960_POBCTRL | WM8960_SOFT_ST | + WM8960_BUFDCOPEN | WM8960_BUFIOEN); + + /* Disable VMID and VREF, let them discharge */ + wm8960_write(codec, WM8960_POWER1, 0); + msleep(600); + + wm8960_write(codec, WM8960_APOP1, 0); + break; + } + + codec->bias_level = level; + + return 0; +} + +/* PLL divisors */ +struct _pll_div { + u32 pre_div:1; + u32 n:4; + u32 k:24; +}; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 24) * 10) + +static int pll_factors(unsigned int source, unsigned int target, + struct _pll_div *pll_div) +{ + unsigned long long Kpart; + unsigned int K, Ndiv, Nmod; + + pr_debug("WM8960 PLL: setting %dHz->%dHz\n", source, target); + + /* Scale up target to PLL operating frequency */ + target *= 4; + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div->pre_div = 1; + Ndiv = target / source; + } else + pll_div->pre_div = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) { + pr_err("WM8960 PLL: Unsupported N=%d\n", Ndiv); + return -EINVAL; + } + + pll_div->n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div->k = K; + + pr_debug("WM8960 PLL: N=%x K=%x pre_div=%d\n", + pll_div->n, pll_div->k, pll_div->pre_div); + + return 0; +} + +static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + static struct _pll_div pll_div; + int ret; + + if (freq_in && freq_out) { + ret = pll_factors(freq_in, freq_out, &pll_div); + if (ret != 0) + return ret; + } + + /* Disable the PLL: even if we are changing the frequency the + * PLL needs to be disabled while we do so. */ + wm8960_write(codec, WM8960_CLOCK1, + wm8960_read(codec, WM8960_CLOCK1) & ~1); + wm8960_write(codec, WM8960_POWER2, + wm8960_read(codec, WM8960_POWER2) & ~1); + + if (!freq_in || !freq_out) + return 0; + + reg = wm8960_read(codec, WM8960_PLL1) & ~0x3f; + reg |= pll_div.pre_div << 4; + reg |= pll_div.n; + + if (pll_div.k) { + reg |= 0x20; + + wm8960_write(codec, WM8960_PLL2, (pll_div.k >> 18) & 0x3f); + wm8960_write(codec, WM8960_PLL3, (pll_div.k >> 9) & 0x1ff); + wm8960_write(codec, WM8960_PLL4, pll_div.k & 0x1ff); + } + wm8960_write(codec, WM8960_PLL1, reg); + + /* Turn it on */ + wm8960_write(codec, WM8960_POWER2, + wm8960_read(codec, WM8960_POWER2) | 1); + msleep(250); + wm8960_write(codec, WM8960_CLOCK1, + wm8960_read(codec, WM8960_CLOCK1) | 1); + + return 0; +} + +static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + switch (div_id) { + case WM8960_SYSCLKSEL: + reg = wm8960_read(codec, WM8960_CLOCK1) & 0x1fe; + wm8960_write(codec, WM8960_CLOCK1, reg | div); + break; + case WM8960_SYSCLKDIV: + reg = wm8960_read(codec, WM8960_CLOCK1) & 0x1f9; + wm8960_write(codec, WM8960_CLOCK1, reg | div); + break; + case WM8960_DACDIV: + reg = wm8960_read(codec, WM8960_CLOCK1) & 0x1c7; + wm8960_write(codec, WM8960_CLOCK1, reg | div); + break; + case WM8960_OPCLKDIV: + reg = wm8960_read(codec, WM8960_PLL1) & 0x03f; + wm8960_write(codec, WM8960_PLL1, reg | div); + break; + case WM8960_DCLKDIV: + reg = wm8960_read(codec, WM8960_CLOCK2) & 0x03f; + wm8960_write(codec, WM8960_CLOCK2, reg | div); + break; + case WM8960_TOCLKSEL: + reg = wm8960_read(codec, WM8960_ADDCTL1) & 0x1fd; + wm8960_write(codec, WM8960_ADDCTL1, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +#define WM8960_RATES SNDRV_PCM_RATE_8000_48000 + +#define WM8960_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops wm8960_dai_ops = { + .hw_params = wm8960_hw_params, + .digital_mute = wm8960_mute, + .set_fmt = wm8960_set_dai_fmt, + .set_clkdiv = wm8960_set_dai_clkdiv, + .set_pll = wm8960_set_dai_pll, +}; + +struct snd_soc_dai wm8960_dai = { + .name = "WM8960", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8960_RATES, + .formats = WM8960_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8960_RATES, + .formats = WM8960_FORMATS,}, + .ops = &wm8960_dai_ops, + .symmetric_rates = 1, +}; +EXPORT_SYMBOL_GPL(wm8960_dai); + +static int wm8960_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + wm8960_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8960_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8960_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + + wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + wm8960_set_bias_level(codec, codec->suspend_bias_level); + return 0; +} + +static struct snd_soc_codec *wm8960_codec; + +static int wm8960_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (wm8960_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = wm8960_codec; + codec = wm8960_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + snd_soc_add_controls(codec, wm8960_snd_controls, + ARRAY_SIZE(wm8960_snd_controls)); + wm8960_add_widgets(codec); + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card: %d\n", ret); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + return ret; +} + +/* power down chip */ +static int wm8960_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8960 = { + .probe = wm8960_probe, + .remove = wm8960_remove, + .suspend = wm8960_suspend, + .resume = wm8960_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8960); + +static int wm8960_register(struct wm8960_priv *wm8960) +{ + struct wm8960_data *pdata = wm8960->codec.dev->platform_data; + struct snd_soc_codec *codec = &wm8960->codec; + int ret; + u16 reg; + + if (wm8960_codec) { + dev_err(codec->dev, "Another WM8960 is registered\n"); + return -EINVAL; + } + + if (!pdata) { + dev_warn(codec->dev, "No platform data supplied\n"); + } else { + if (pdata->dres > WM8960_DRES_MAX) { + dev_err(codec->dev, "Invalid DRES: %d\n", pdata->dres); + pdata->dres = 0; + } + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = wm8960; + codec->name = "WM8960"; + codec->owner = THIS_MODULE; + codec->read = wm8960_read_reg_cache; + codec->write = wm8960_write; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8960_set_bias_level; + codec->dai = &wm8960_dai; + codec->num_dai = 1; + codec->reg_cache_size = WM8960_CACHEREGNUM; + codec->reg_cache = &wm8960->reg_cache; + + memcpy(codec->reg_cache, wm8960_reg, sizeof(wm8960_reg)); + + ret = wm8960_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset\n"); + return ret; + } + + wm8960_dai.dev = codec->dev; + + wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* Latch the update bits */ + reg = wm8960_read(codec, WM8960_LINVOL); + wm8960_write(codec, WM8960_LINVOL, reg | 0x100); + reg = wm8960_read(codec, WM8960_RINVOL); + wm8960_write(codec, WM8960_RINVOL, reg | 0x100); + reg = wm8960_read(codec, WM8960_LADC); + wm8960_write(codec, WM8960_LADC, reg | 0x100); + reg = wm8960_read(codec, WM8960_RADC); + wm8960_write(codec, WM8960_RADC, reg | 0x100); + reg = wm8960_read(codec, WM8960_LDAC); + wm8960_write(codec, WM8960_LDAC, reg | 0x100); + reg = wm8960_read(codec, WM8960_RDAC); + wm8960_write(codec, WM8960_RDAC, reg | 0x100); + reg = wm8960_read(codec, WM8960_LOUT1); + wm8960_write(codec, WM8960_LOUT1, reg | 0x100); + reg = wm8960_read(codec, WM8960_ROUT1); + wm8960_write(codec, WM8960_ROUT1, reg | 0x100); + reg = wm8960_read(codec, WM8960_LOUT2); + wm8960_write(codec, WM8960_LOUT2, reg | 0x100); + reg = wm8960_read(codec, WM8960_ROUT2); + wm8960_write(codec, WM8960_ROUT2, reg | 0x100); + + wm8960_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } + + ret = snd_soc_register_dai(&wm8960_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + return ret; + } + + return 0; +} + +static void wm8960_unregister(struct wm8960_priv *wm8960) +{ + wm8960_set_bias_level(&wm8960->codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dai(&wm8960_dai); + snd_soc_unregister_codec(&wm8960->codec); + kfree(wm8960); + wm8960_codec = NULL; +} + +static __devinit int wm8960_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8960_priv *wm8960; + struct snd_soc_codec *codec; + + wm8960 = kzalloc(sizeof(struct wm8960_priv), GFP_KERNEL); + if (wm8960 == NULL) + return -ENOMEM; + + codec = &wm8960->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + + i2c_set_clientdata(i2c, wm8960); + codec->control_data = i2c; + + codec->dev = &i2c->dev; + + return wm8960_register(wm8960); +} + +static __devexit int wm8960_i2c_remove(struct i2c_client *client) +{ + struct wm8960_priv *wm8960 = i2c_get_clientdata(client); + wm8960_unregister(wm8960); + return 0; +} + +static const struct i2c_device_id wm8960_i2c_id[] = { + { "wm8960", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8960_i2c_id); + +static struct i2c_driver wm8960_i2c_driver = { + .driver = { + .name = "WM8960 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = wm8960_i2c_probe, + .remove = __devexit_p(wm8960_i2c_remove), + .id_table = wm8960_i2c_id, +}; + +static int __init wm8960_modinit(void) +{ + int ret; + + ret = i2c_add_driver(&wm8960_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8960 I2C driver: %d\n", + ret); + } + + return ret; +} +module_init(wm8960_modinit); + +static void __exit wm8960_exit(void) +{ + i2c_del_driver(&wm8960_i2c_driver); +} +module_exit(wm8960_exit); + + +MODULE_DESCRIPTION("ASoC WM8960 driver"); +MODULE_AUTHOR("Liam Girdwood"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8960.h b/sound/soc/codecs/wm8960.h new file mode 100644 index 00000000000..c9af56c9d9d --- /dev/null +++ b/sound/soc/codecs/wm8960.h @@ -0,0 +1,127 @@ +/* + * wm8960.h -- WM8960 Soc Audio driver + * + * 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 _WM8960_H +#define _WM8960_H + +/* WM8960 register space */ + + +#define WM8960_CACHEREGNUM 56 + +#define WM8960_LINVOL 0x0 +#define WM8960_RINVOL 0x1 +#define WM8960_LOUT1 0x2 +#define WM8960_ROUT1 0x3 +#define WM8960_CLOCK1 0x4 +#define WM8960_DACCTL1 0x5 +#define WM8960_DACCTL2 0x6 +#define WM8960_IFACE1 0x7 +#define WM8960_CLOCK2 0x8 +#define WM8960_IFACE2 0x9 +#define WM8960_LDAC 0xa +#define WM8960_RDAC 0xb + +#define WM8960_RESET 0xf +#define WM8960_3D 0x10 +#define WM8960_ALC1 0x11 +#define WM8960_ALC2 0x12 +#define WM8960_ALC3 0x13 +#define WM8960_NOISEG 0x14 +#define WM8960_LADC 0x15 +#define WM8960_RADC 0x16 +#define WM8960_ADDCTL1 0x17 +#define WM8960_ADDCTL2 0x18 +#define WM8960_POWER1 0x19 +#define WM8960_POWER2 0x1a +#define WM8960_ADDCTL3 0x1b +#define WM8960_APOP1 0x1c +#define WM8960_APOP2 0x1d + +#define WM8960_LINPATH 0x20 +#define WM8960_RINPATH 0x21 +#define WM8960_LOUTMIX 0x22 + +#define WM8960_ROUTMIX 0x25 +#define WM8960_MONOMIX1 0x26 +#define WM8960_MONOMIX2 0x27 +#define WM8960_LOUT2 0x28 +#define WM8960_ROUT2 0x29 +#define WM8960_MONO 0x2a +#define WM8960_INBMIX1 0x2b +#define WM8960_INBMIX2 0x2c +#define WM8960_BYPASS1 0x2d +#define WM8960_BYPASS2 0x2e +#define WM8960_POWER3 0x2f +#define WM8960_ADDCTL4 0x30 +#define WM8960_CLASSD1 0x31 + +#define WM8960_CLASSD3 0x33 +#define WM8960_PLL1 0x34 +#define WM8960_PLL2 0x35 +#define WM8960_PLL3 0x36 +#define WM8960_PLL4 0x37 + + +/* + * WM8960 Clock dividers + */ +#define WM8960_SYSCLKDIV 0 +#define WM8960_DACDIV 1 +#define WM8960_OPCLKDIV 2 +#define WM8960_DCLKDIV 3 +#define WM8960_TOCLKSEL 4 +#define WM8960_SYSCLKSEL 5 + +#define WM8960_SYSCLK_DIV_1 (0 << 1) +#define WM8960_SYSCLK_DIV_2 (2 << 1) + +#define WM8960_SYSCLK_MCLK (0 << 0) +#define WM8960_SYSCLK_PLL (1 << 0) + +#define WM8960_DAC_DIV_1 (0 << 3) +#define WM8960_DAC_DIV_1_5 (1 << 3) +#define WM8960_DAC_DIV_2 (2 << 3) +#define WM8960_DAC_DIV_3 (3 << 3) +#define WM8960_DAC_DIV_4 (4 << 3) +#define WM8960_DAC_DIV_5_5 (5 << 3) +#define WM8960_DAC_DIV_6 (6 << 3) + +#define WM8960_DCLK_DIV_1_5 (0 << 6) +#define WM8960_DCLK_DIV_2 (1 << 6) +#define WM8960_DCLK_DIV_3 (2 << 6) +#define WM8960_DCLK_DIV_4 (3 << 6) +#define WM8960_DCLK_DIV_6 (4 << 6) +#define WM8960_DCLK_DIV_8 (5 << 6) +#define WM8960_DCLK_DIV_12 (6 << 6) +#define WM8960_DCLK_DIV_16 (7 << 6) + +#define WM8960_TOCLK_F19 (0 << 1) +#define WM8960_TOCLK_F21 (1 << 1) + +#define WM8960_OPCLK_DIV_1 (0 << 0) +#define WM8960_OPCLK_DIV_2 (1 << 0) +#define WM8960_OPCLK_DIV_3 (2 << 0) +#define WM8960_OPCLK_DIV_4 (3 << 0) +#define WM8960_OPCLK_DIV_5_5 (4 << 0) +#define WM8960_OPCLK_DIV_6 (5 << 0) + +extern struct snd_soc_dai wm8960_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8960; + +#define WM8960_DRES_400R 0 +#define WM8960_DRES_200R 1 +#define WM8960_DRES_600R 2 +#define WM8960_DRES_150R 3 +#define WM8960_DRES_MAX 3 + +struct wm8960_data { + int dres; +}; + +#endif -- cgit v1.2.3 From 3f1a4d826751d9759fc95da4e47d08d2745e0055 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 15 Apr 2009 21:35:26 +0100 Subject: ASoC: Check we have DAI ops when calling via accessor functions Also make sure we're checking for the right operation while we're here. Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index dd28009f896..92503927b0c 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2100,7 +2100,7 @@ EXPORT_SYMBOL_GPL(snd_soc_put_volsw_s8); int snd_soc_dai_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir) { - if (dai->ops->set_sysclk) + if (dai->ops && dai->ops->set_sysclk) return dai->ops->set_sysclk(dai, clk_id, freq, dir); else return -EINVAL; @@ -2120,7 +2120,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_set_sysclk); int snd_soc_dai_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div) { - if (dai->ops->set_clkdiv) + if (dai->ops && dai->ops->set_clkdiv) return dai->ops->set_clkdiv(dai, div_id, div); else return -EINVAL; @@ -2139,7 +2139,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_set_clkdiv); int snd_soc_dai_set_pll(struct snd_soc_dai *dai, int pll_id, unsigned int freq_in, unsigned int freq_out) { - if (dai->ops->set_pll) + if (dai->ops && dai->ops->set_pll) return dai->ops->set_pll(dai, pll_id, freq_in, freq_out); else return -EINVAL; @@ -2155,7 +2155,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_set_pll); */ int snd_soc_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) { - if (dai->ops->set_fmt) + if (dai->ops && dai->ops->set_fmt) return dai->ops->set_fmt(dai, fmt); else return -EINVAL; @@ -2174,7 +2174,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_set_fmt); int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai, unsigned int mask, int slots) { - if (dai->ops->set_sysclk) + if (dai->ops && dai->ops->set_tdm_slot) return dai->ops->set_tdm_slot(dai, mask, slots); else return -EINVAL; @@ -2190,7 +2190,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_set_tdm_slot); */ int snd_soc_dai_set_tristate(struct snd_soc_dai *dai, int tristate) { - if (dai->ops->set_sysclk) + if (dai->ops && dai->ops->set_tristate) return dai->ops->set_tristate(dai, tristate); else return -EINVAL; @@ -2206,7 +2206,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_set_tristate); */ int snd_soc_dai_digital_mute(struct snd_soc_dai *dai, int mute) { - if (dai->ops->digital_mute) + if (dai->ops && dai->ops->digital_mute) return dai->ops->digital_mute(dai, mute); else return -EINVAL; -- cgit v1.2.3 From fd5dfad9cf51bc3575b5e50193403de4a3de23bc Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 15 Apr 2009 21:37:46 +0100 Subject: ASoC: Volume controls are never of boolean type Some limited volume controls (mostly simple attenuations) have only two settings so the ASoC info functions misreport them as booleans. Since we currently have no better information check for " Volume" in the control name and always report any controls matching as being integer. Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 92503927b0c..af11791a3b8 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1779,7 +1779,7 @@ int snd_soc_info_volsw_ext(struct snd_kcontrol *kcontrol, { int max = kcontrol->private_value; - if (max == 1) + if (max == 1 && !strstr(kcontrol->id.name, " Volume")) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; else uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; @@ -1809,7 +1809,7 @@ int snd_soc_info_volsw(struct snd_kcontrol *kcontrol, unsigned int shift = mc->shift; unsigned int rshift = mc->rshift; - if (max == 1) + if (max == 1 && !strstr(kcontrol->id.name, " Volume")) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; else uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; @@ -1916,7 +1916,7 @@ int snd_soc_info_volsw_2r(struct snd_kcontrol *kcontrol, (struct soc_mixer_control *)kcontrol->private_value; int max = mc->max; - if (max == 1) + if (max == 1 && !strstr(kcontrol->id.name, " Volume")) uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; else uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; -- cgit v1.2.3 From 0d960e8891459f5af85e5781bce3f1da5f7db0fb Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 16 Apr 2009 10:08:39 +0100 Subject: ASoC: Request shared rates for WM8903 It has a shared LRCLK. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8903.c | 1 + 1 file changed, 1 insertion(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 8cf571f1a80..c5391841d41 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -1523,6 +1523,7 @@ struct snd_soc_dai wm8903_dai = { .formats = WM8903_FORMATS, }, .ops = &wm8903_dai_ops, + .symmetric_rates = 1, }; EXPORT_SYMBOL_GPL(wm8903_dai); -- cgit v1.2.3 From c29b206ffd0700acb2dc1fdb70856cc4b907216c Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 15 Apr 2009 15:38:55 +0300 Subject: ASoC: OMAP: Use single-phase for DSP mode Use single-phase mode for the DSP mode and keep the dual phase mode for the I2S mode. The mono (1 channel) mode already used single phase mode, now it is more cleaner. There is no need to configure the second phase, when the single phase is used. Signed-off-by: Peter Ujfalusi Acked-by: Jarkko Nikula Signed-off-by: Mark Brown --- sound/soc/omap/omap-mcbsp.c | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c index 9c09b94f0cf..402a1eb7bd3 100644 --- a/sound/soc/omap/omap-mcbsp.c +++ b/sound/soc/omap/omap-mcbsp.c @@ -214,8 +214,9 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data); struct omap_mcbsp_reg_cfg *regs = &mcbsp_data->regs; int dma, bus_id = mcbsp_data->bus_id, id = cpu_dai->id; - int wlen, channels; + int wlen, channels, wpf; unsigned long port; + unsigned int format; if (cpu_class_is_omap1()) { dma = omap1_dma_reqs[bus_id][substream->stream]; @@ -243,18 +244,23 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, return 0; } - channels = params_channels(params); + format = mcbsp_data->fmt & SND_SOC_DAIFMT_FORMAT_MASK; + wpf = channels = params_channels(params); switch (channels) { case 2: - /* Use dual-phase frames */ - regs->rcr2 |= RPHASE; - regs->xcr2 |= XPHASE; + if (format == SND_SOC_DAIFMT_I2S) { + /* Use dual-phase frames */ + regs->rcr2 |= RPHASE; + regs->xcr2 |= XPHASE; + /* Set 1 word per (McBSP) frame for phase1 and phase2 */ + wpf--; + regs->rcr2 |= RFRLEN2(wpf - 1); + regs->xcr2 |= XFRLEN2(wpf - 1); + } case 1: - /* Set 1 word per (McBSP) frame */ - regs->rcr2 |= RFRLEN2(1 - 1); - regs->rcr1 |= RFRLEN1(1 - 1); - regs->xcr2 |= XFRLEN2(1 - 1); - regs->xcr1 |= XFRLEN1(1 - 1); + /* Set word per (McBSP) frame for phase1 */ + regs->rcr1 |= RFRLEN1(wpf - 1); + regs->xcr1 |= XFRLEN1(wpf - 1); break; default: /* Unsupported number of channels */ @@ -276,9 +282,9 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, } /* Set FS period and length in terms of bit clock periods */ - switch (mcbsp_data->fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + switch (format) { case SND_SOC_DAIFMT_I2S: - regs->srgr2 |= FPER(wlen * 2 - 1); + regs->srgr2 |= FPER(wlen * channels - 1); regs->srgr1 |= FWID(wlen - 1); break; case SND_SOC_DAIFMT_DSP_B: -- cgit v1.2.3 From 3ba191ce051a32b20757f063120496e860ea8f9d Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Wed, 15 Apr 2009 15:38:56 +0300 Subject: ASoC: OMAP: Add DSP_A mode support for mcbsp DSP_A mode is similar to the DSP_B, but the MSB is delayed with one bclk (appears after the FS pulse and not under it). Signed-off-by: Peter Ujfalusi Acked-by: Jarkko Nikula Signed-off-by: Mark Brown --- sound/soc/omap/omap-mcbsp.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c index 402a1eb7bd3..2b4a8da0991 100644 --- a/sound/soc/omap/omap-mcbsp.c +++ b/sound/soc/omap/omap-mcbsp.c @@ -287,6 +287,7 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, regs->srgr2 |= FPER(wlen * channels - 1); regs->srgr1 |= FWID(wlen - 1); break; + case SND_SOC_DAIFMT_DSP_A: case SND_SOC_DAIFMT_DSP_B: regs->srgr2 |= FPER(wlen * channels - 1); regs->srgr1 |= FWID(wlen * channels - 2); @@ -330,6 +331,13 @@ static int omap_mcbsp_dai_set_dai_fmt(struct snd_soc_dai *cpu_dai, regs->rcr2 |= RDATDLY(1); regs->xcr2 |= XDATDLY(1); break; + case SND_SOC_DAIFMT_DSP_A: + /* 1-bit data delay */ + regs->rcr2 |= RDATDLY(1); + regs->xcr2 |= XDATDLY(1); + /* Invert FS polarity configuration */ + temp_fmt ^= SND_SOC_DAIFMT_NB_IF; + break; case SND_SOC_DAIFMT_DSP_B: /* 0-bit data delay */ regs->rcr2 |= RDATDLY(0); -- cgit v1.2.3 From 6b87a91f5417226c7fe62100b0e7217e7096b789 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 17 Apr 2009 15:55:08 +0300 Subject: ASoC: TWL4030: Fix for the constraint handling The original implementation of the constraints were good against sane applications. If the opening sequence is: stream1_open, stream1_hw_params, stream2_open, stream2_hw_params -> the constraints are set correctly for stream2. But if the sequence is: stream1_open, stream2_open, stream2_hw_params, stream1_hw_params -> than stream2 would receive constraint rate = 0, sample_bits = 0, since the stream1 has not yet called hw_params... The command to trigger this event: gst-launch-0.10 alsasrc device=hw:0 ! alsasink device=hw:0 sync=false This patch does some 'black magic' in order to always set the correct constraints and sets it only when it is needed for the other stream. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 85 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 66 insertions(+), 19 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 921b205de28..a1b76d7fd13 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -125,6 +125,11 @@ struct twl4030_priv { struct snd_pcm_substream *master_substream; struct snd_pcm_substream *slave_substream; + + unsigned int configured; + unsigned int rate; + unsigned int sample_bits; + unsigned int channels; }; /* @@ -1220,6 +1225,36 @@ static int twl4030_set_bias_level(struct snd_soc_codec *codec, return 0; } +static void twl4030_constraints(struct twl4030_priv *twl4030, + struct snd_pcm_substream *mst_substream) +{ + struct snd_pcm_substream *slv_substream; + + /* Pick the stream, which need to be constrained */ + if (mst_substream == twl4030->master_substream) + slv_substream = twl4030->slave_substream; + else if (mst_substream == twl4030->slave_substream) + slv_substream = twl4030->master_substream; + else /* This should not happen.. */ + return; + + /* Set the constraints according to the already configured stream */ + snd_pcm_hw_constraint_minmax(slv_substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + twl4030->rate, + twl4030->rate); + + snd_pcm_hw_constraint_minmax(slv_substream->runtime, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + twl4030->sample_bits, + twl4030->sample_bits); + + snd_pcm_hw_constraint_minmax(slv_substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + twl4030->channels, + twl4030->channels); +} + static int twl4030_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -1228,26 +1263,16 @@ static int twl4030_startup(struct snd_pcm_substream *substream, struct snd_soc_codec *codec = socdev->card->codec; struct twl4030_priv *twl4030 = codec->private_data; - /* If we already have a playback or capture going then constrain - * this substream to match it. - */ if (twl4030->master_substream) { - struct snd_pcm_runtime *master_runtime; - master_runtime = twl4030->master_substream->runtime; - - snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - master_runtime->rate, - master_runtime->rate); - - snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_SAMPLE_BITS, - master_runtime->sample_bits, - master_runtime->sample_bits); - twl4030->slave_substream = substream; - } else + /* The DAI has one configuration for playback and capture, so + * if the DAI has been already configured then constrain this + * substream to match it. */ + if (twl4030->configured) + twl4030_constraints(twl4030, twl4030->master_substream); + } else { twl4030->master_substream = substream; + } return 0; } @@ -1264,6 +1289,13 @@ static void twl4030_shutdown(struct snd_pcm_substream *substream, twl4030->master_substream = twl4030->slave_substream; twl4030->slave_substream = NULL; + + /* If all streams are closed, or the remaining stream has not yet + * been configured than set the DAI as not configured. */ + if (!twl4030->master_substream) + twl4030->configured = 0; + else if (!twl4030->master_substream->runtime->channels) + twl4030->configured = 0; } static int twl4030_hw_params(struct snd_pcm_substream *substream, @@ -1276,8 +1308,8 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream, struct twl4030_priv *twl4030 = codec->private_data; u8 mode, old_mode, format, old_format; - if (substream == twl4030->slave_substream) - /* Ignoring hw_params for slave substream */ + if (twl4030->configured) + /* Ignoring hw_params for already configured DAI */ return 0; /* bit rate */ @@ -1357,6 +1389,21 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream, /* set CODECPDZ afterwards */ twl4030_codec_enable(codec, 1); } + + /* Store the important parameters for the DAI configuration and set + * the DAI as configured */ + twl4030->configured = 1; + twl4030->rate = params_rate(params); + twl4030->sample_bits = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS)->min; + twl4030->channels = params_channels(params); + + /* If both playback and capture streams are open, and one of them + * is setting the hw parameters right now (since we are here), set + * constraints to the other stream to match the current one. */ + if (twl4030->slave_substream) + twl4030_constraints(twl4030, substream); + return 0; } -- cgit v1.2.3 From 7154b3e80203ee91f9ba7d0a43d3daa05c49d9e9 Mon Sep 17 00:00:00 2001 From: Joonyoung Shim Date: Mon, 20 Apr 2009 19:21:35 +0900 Subject: ASoC: TWL4030: Add support Voice DAI Add Voice DAI to support the PCM voice interface of the twl4030 codec. The PCM voice interface can be used with 8-kHz(voice narrowband) or 16-kHz(voice wideband) sampling rates, and 16bits, and mono RX and mono TX or stereo TX. The PCM voice interface has two modes - PCM mode1 : This uses the normal FS polarity and the rising edge of the clock signal. - PCM mode2 : This uses the FS polarity inverted and the falling edge of the clock signal. If the system master clock is not 26MHz or the twl4030 codec mode is not option2, the voice PCM interface is not available. Signed-off-by: Joonyoung Shim Acked-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 173 ++++++++++++++++++++++++++++++++++++++++-- sound/soc/codecs/twl4030.h | 18 ++++- sound/soc/omap/omap2evm.c | 2 +- sound/soc/omap/omap3beagle.c | 2 +- sound/soc/omap/omap3pandora.c | 4 +- sound/soc/omap/overo.c | 2 +- sound/soc/omap/sdp3430.c | 2 +- 7 files changed, 191 insertions(+), 12 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index a1b76d7fd13..cc2968cf640 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -1484,6 +1484,144 @@ static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai, return 0; } +static int twl4030_voice_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + u8 infreq; + u8 mode; + + /* If the system master clock is not 26MHz, the voice PCM interface is + * not avilable. + */ + infreq = twl4030_read_reg_cache(codec, TWL4030_REG_APLL_CTL) + & TWL4030_APLL_INFREQ; + + if (infreq != TWL4030_APLL_INFREQ_26000KHZ) { + printk(KERN_ERR "TWL4030 voice startup: " + "MCLK is not 26MHz, call set_sysclk() on init\n"); + return -EINVAL; + } + + /* If the codec mode is not option2, the voice PCM interface is not + * avilable. + */ + mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE) + & TWL4030_OPT_MODE; + + if (mode != TWL4030_OPTION_2) { + printk(KERN_ERR "TWL4030 voice startup: " + "the codec mode is not option2\n"); + return -EINVAL; + } + + return 0; +} + +static int twl4030_voice_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + u8 old_mode, mode; + + /* bit rate */ + old_mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE) + & ~(TWL4030_CODECPDZ); + mode = old_mode; + + switch (params_rate(params)) { + case 8000: + mode &= ~(TWL4030_SEL_16K); + break; + case 16000: + mode |= TWL4030_SEL_16K; + break; + default: + printk(KERN_ERR "TWL4030 voice hw params: unknown rate %d\n", + params_rate(params)); + return -EINVAL; + } + + if (mode != old_mode) { + /* change rate and set CODECPDZ */ + twl4030_codec_enable(codec, 0); + twl4030_write(codec, TWL4030_REG_CODEC_MODE, mode); + twl4030_codec_enable(codec, 1); + } + + return 0; +} + +static int twl4030_voice_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 infreq; + + switch (freq) { + case 26000000: + infreq = TWL4030_APLL_INFREQ_26000KHZ; + break; + default: + printk(KERN_ERR "TWL4030 voice set sysclk: unknown rate %d\n", + freq); + return -EINVAL; + } + + infreq |= TWL4030_APLL_EN; + twl4030_write(codec, TWL4030_REG_APLL_CTL, infreq); + + return 0; +} + +static int twl4030_voice_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u8 old_format, format; + + /* get format */ + old_format = twl4030_read_reg_cache(codec, TWL4030_REG_VOICE_IF); + format = old_format; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFM: + format &= ~(TWL4030_VIF_SLAVE_EN); + break; + case SND_SOC_DAIFMT_CBS_CFS: + format |= TWL4030_VIF_SLAVE_EN; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_NF: + format &= ~(TWL4030_VIF_FORMAT); + break; + case SND_SOC_DAIFMT_NB_IF: + format |= TWL4030_VIF_FORMAT; + break; + default: + return -EINVAL; + } + + if (format != old_format) { + /* change format and set CODECPDZ */ + twl4030_codec_enable(codec, 0); + twl4030_write(codec, TWL4030_REG_VOICE_IF, format); + twl4030_codec_enable(codec, 1); + } + + return 0; +} + #define TWL4030_RATES (SNDRV_PCM_RATE_8000_48000) #define TWL4030_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE) @@ -1495,7 +1633,15 @@ static struct snd_soc_dai_ops twl4030_dai_ops = { .set_fmt = twl4030_set_dai_fmt, }; -struct snd_soc_dai twl4030_dai = { +static struct snd_soc_dai_ops twl4030_dai_voice_ops = { + .startup = twl4030_voice_startup, + .hw_params = twl4030_voice_hw_params, + .set_sysclk = twl4030_voice_set_dai_sysclk, + .set_fmt = twl4030_voice_set_dai_fmt, +}; + +struct snd_soc_dai twl4030_dai[] = { +{ .name = "twl4030", .playback = { .stream_name = "Playback", @@ -1510,6 +1656,23 @@ struct snd_soc_dai twl4030_dai = { .rates = TWL4030_RATES, .formats = TWL4030_FORMATS,}, .ops = &twl4030_dai_ops, +}, +{ + .name = "twl4030 Voice", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &twl4030_dai_voice_ops, +}, }; EXPORT_SYMBOL_GPL(twl4030_dai); @@ -1550,8 +1713,8 @@ static int twl4030_init(struct snd_soc_device *socdev) codec->read = twl4030_read_reg_cache; codec->write = twl4030_write; codec->set_bias_level = twl4030_set_bias_level; - codec->dai = &twl4030_dai; - codec->num_dai = 1; + codec->dai = twl4030_dai; + codec->num_dai = ARRAY_SIZE(twl4030_dai), codec->reg_cache_size = sizeof(twl4030_reg); codec->reg_cache = kmemdup(twl4030_reg, sizeof(twl4030_reg), GFP_KERNEL); @@ -1645,13 +1808,13 @@ EXPORT_SYMBOL_GPL(soc_codec_dev_twl4030); static int __init twl4030_modinit(void) { - return snd_soc_register_dai(&twl4030_dai); + return snd_soc_register_dais(&twl4030_dai[0], ARRAY_SIZE(twl4030_dai)); } module_init(twl4030_modinit); static void __exit twl4030_exit(void) { - snd_soc_unregister_dai(&twl4030_dai); + snd_soc_unregister_dais(&twl4030_dai[0], ARRAY_SIZE(twl4030_dai)); } module_exit(twl4030_exit); diff --git a/sound/soc/codecs/twl4030.h b/sound/soc/codecs/twl4030.h index cb63765db1d..981ec609495 100644 --- a/sound/soc/codecs/twl4030.h +++ b/sound/soc/codecs/twl4030.h @@ -113,6 +113,8 @@ #define TWL4030_SEL_16K 0x04 #define TWL4030_CODECPDZ 0x02 #define TWL4030_OPT_MODE 0x01 +#define TWL4030_OPTION_1 (1 << 0) +#define TWL4030_OPTION_2 (0 << 0) /* TWL4030_REG_MICBIAS_CTL (0x04) Fields */ @@ -171,6 +173,17 @@ #define TWL4030_CLK256FS_EN 0x02 #define TWL4030_AIF_EN 0x01 +/* VOICE_IF (0x0F) Fields */ + +#define TWL4030_VIF_SLAVE_EN 0x80 +#define TWL4030_VIF_DIN_EN 0x40 +#define TWL4030_VIF_DOUT_EN 0x20 +#define TWL4030_VIF_SWAP 0x10 +#define TWL4030_VIF_FORMAT 0x08 +#define TWL4030_VIF_TRI_EN 0x04 +#define TWL4030_VIF_SUB_EN 0x02 +#define TWL4030_VIF_EN 0x01 + /* EAR_CTL (0x21) */ #define TWL4030_EAR_GAIN 0x30 @@ -236,7 +249,10 @@ #define TWL4030_SMOOTH_ANAVOL_EN 0x02 #define TWL4030_DIGMIC_LR_SWAP_EN 0x01 -extern struct snd_soc_dai twl4030_dai; +#define TWL4030_DAI_HIFI 0 +#define TWL4030_DAI_VOICE 1 + +extern struct snd_soc_dai twl4030_dai[2]; extern struct snd_soc_codec_device soc_codec_dev_twl4030; #endif /* End of __TWL4030_AUDIO_H__ */ diff --git a/sound/soc/omap/omap2evm.c b/sound/soc/omap/omap2evm.c index 0c2322dcf02..027e1a40f8a 100644 --- a/sound/soc/omap/omap2evm.c +++ b/sound/soc/omap/omap2evm.c @@ -86,7 +86,7 @@ static struct snd_soc_dai_link omap2evm_dai = { .name = "TWL4030", .stream_name = "TWL4030", .cpu_dai = &omap_mcbsp_dai[0], - .codec_dai = &twl4030_dai, + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], .ops = &omap2evm_ops, }; diff --git a/sound/soc/omap/omap3beagle.c b/sound/soc/omap/omap3beagle.c index fd24a4acd2f..6aa428e07d8 100644 --- a/sound/soc/omap/omap3beagle.c +++ b/sound/soc/omap/omap3beagle.c @@ -83,7 +83,7 @@ static struct snd_soc_dai_link omap3beagle_dai = { .name = "TWL4030", .stream_name = "TWL4030", .cpu_dai = &omap_mcbsp_dai[0], - .codec_dai = &twl4030_dai, + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], .ops = &omap3beagle_ops, }; diff --git a/sound/soc/omap/omap3pandora.c b/sound/soc/omap/omap3pandora.c index fe282d4ef42..ad219aaf7cb 100644 --- a/sound/soc/omap/omap3pandora.c +++ b/sound/soc/omap/omap3pandora.c @@ -228,14 +228,14 @@ static struct snd_soc_dai_link omap3pandora_dai[] = { .name = "PCM1773", .stream_name = "HiFi Out", .cpu_dai = &omap_mcbsp_dai[0], - .codec_dai = &twl4030_dai, + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], .ops = &omap3pandora_out_ops, .init = omap3pandora_out_init, }, { .name = "TWL4030", .stream_name = "Line/Mic In", .cpu_dai = &omap_mcbsp_dai[1], - .codec_dai = &twl4030_dai, + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], .ops = &omap3pandora_in_ops, .init = omap3pandora_in_init, } diff --git a/sound/soc/omap/overo.c b/sound/soc/omap/overo.c index a72dc4e159e..ec4f8fd8b3a 100644 --- a/sound/soc/omap/overo.c +++ b/sound/soc/omap/overo.c @@ -83,7 +83,7 @@ static struct snd_soc_dai_link overo_dai = { .name = "TWL4030", .stream_name = "TWL4030", .cpu_dai = &omap_mcbsp_dai[0], - .codec_dai = &twl4030_dai, + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], .ops = &overo_ops, }; diff --git a/sound/soc/omap/sdp3430.c b/sound/soc/omap/sdp3430.c index 10f1c867f11..1c7974101a0 100644 --- a/sound/soc/omap/sdp3430.c +++ b/sound/soc/omap/sdp3430.c @@ -197,7 +197,7 @@ static struct snd_soc_dai_link sdp3430_dai = { .name = "TWL4030", .stream_name = "TWL4030", .cpu_dai = &omap_mcbsp_dai[0], - .codec_dai = &twl4030_dai, + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], .init = sdp3430_twl4030_init, .ops = &sdp3430_ops, }; -- cgit v1.2.3 From cd0f2d4736ae8efabc60e54ecc8f677d0eddce02 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 20 Apr 2009 16:56:59 +0100 Subject: ASoC: Factor out generic widget power checks This will form a basis for further power check refactoring: the overall goal of these changes is to allow us to check power separately to applying it, allowing improvements in the power sequencing algorithms. Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index a6d73379ab3..28e6e324ccf 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -581,6 +581,19 @@ static int dapm_generic_apply_power(struct snd_soc_dapm_widget *w) return 0; } +/* Generic check to see if a widget should be powered. + */ +static int dapm_generic_check_power(struct snd_soc_dapm_widget *w) +{ + int in, out; + + in = is_connected_input_ep(w); + dapm_clear_walk(w->codec); + out = is_connected_output_ep(w); + dapm_clear_walk(w->codec); + return out != 0 && in != 0; +} + /* * Scan a single DAPM widget for a complete audio path and update the * power status appropriately. @@ -653,11 +666,7 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event, } /* all other widgets */ - in = is_connected_input_ep(w); - dapm_clear_walk(w->codec); - out = is_connected_output_ep(w); - dapm_clear_walk(w->codec); - power = (out != 0 && in != 0) ? 1 : 0; + power = dapm_generic_check_power(w); power_change = (w->power == power) ? 0 : 1; w->power = power; -- cgit v1.2.3 From 6ea31b9f0a0307e16656af27fcda3160e2a64a1b Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 20 Apr 2009 17:15:41 +0100 Subject: ASoC: Factor out DAPM power checks for DACs and ADCs This also switches us to using a switch statement for the widget type in dapm_power_widget(). Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 81 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 33 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 28e6e324ccf..22522e2d83a 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -594,6 +594,34 @@ static int dapm_generic_check_power(struct snd_soc_dapm_widget *w) return out != 0 && in != 0; } +/* Check to see if an ADC has power */ +static int dapm_adc_check_power(struct snd_soc_dapm_widget *w) +{ + int in; + + if (w->active) { + in = is_connected_input_ep(w); + dapm_clear_walk(w->codec); + return in != 0; + } else { + return dapm_generic_check_power(w); + } +} + +/* Check to see if a DAC has power */ +static int dapm_dac_check_power(struct snd_soc_dapm_widget *w) +{ + int out; + + if (w->active) { + out = is_connected_output_ep(w); + dapm_clear_walk(w->codec); + return out != 0; + } else { + return dapm_generic_check_power(w); + } +} + /* * Scan a single DAPM widget for a complete audio path and update the * power status appropriately. @@ -601,36 +629,23 @@ static int dapm_generic_check_power(struct snd_soc_dapm_widget *w) static int dapm_power_widget(struct snd_soc_codec *codec, int event, struct snd_soc_dapm_widget *w) { - int in, out, power_change, power, ret; + int power, ret; - /* vmid - no action */ - if (w->id == snd_soc_dapm_vmid) + /* Work out the new power state */ + switch (w->id) { + case snd_soc_dapm_vmid: + /* No action required */ return 0; - /* active ADC */ - if (w->id == snd_soc_dapm_adc && w->active) { - in = is_connected_input_ep(w); - dapm_clear_walk(w->codec); - power = (in != 0) ? 1 : 0; - if (power == w->power) - return 0; - w->power = power; - return dapm_generic_apply_power(w); - } + case snd_soc_dapm_adc: + power = dapm_adc_check_power(w); + break; - /* active DAC */ - if (w->id == snd_soc_dapm_dac && w->active) { - out = is_connected_output_ep(w); - dapm_clear_walk(w->codec); - power = (out != 0) ? 1 : 0; - if (power == w->power) - return 0; - w->power = power; - return dapm_generic_apply_power(w); - } + case snd_soc_dapm_dac: + power = dapm_dac_check_power(w); + break; - /* pre and post event widgets */ - if (w->id == snd_soc_dapm_pre) { + case snd_soc_dapm_pre: if (!w->event) return 0; @@ -646,8 +661,8 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event, return ret; } return 0; - } - if (w->id == snd_soc_dapm_post) { + + case snd_soc_dapm_post: if (!w->event) return 0; @@ -663,15 +678,15 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event, return ret; } return 0; - } - /* all other widgets */ - power = dapm_generic_check_power(w); - power_change = (w->power == power) ? 0 : 1; - w->power = power; + default: + power = dapm_generic_check_power(w); + break; + } - if (!power_change) + if (w->power == power) return 0; + w->power = power; return dapm_generic_apply_power(w); } -- cgit v1.2.3 From b75576d76d4be50196773f36709cb7a4f5ac2ab7 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 20 Apr 2009 17:56:13 +0100 Subject: ASoC: Make the DAPM power check an operation on the widget Rather than having switch statements at point of use make the DAPM power check a member of the widget structure and set it when we instantiate the widget. Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 22522e2d83a..d3d17354e76 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -631,20 +631,7 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event, { int power, ret; - /* Work out the new power state */ switch (w->id) { - case snd_soc_dapm_vmid: - /* No action required */ - return 0; - - case snd_soc_dapm_adc: - power = dapm_adc_check_power(w); - break; - - case snd_soc_dapm_dac: - power = dapm_dac_check_power(w); - break; - case snd_soc_dapm_pre: if (!w->event) return 0; @@ -680,10 +667,13 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event, return 0; default: - power = dapm_generic_check_power(w); break; } + if (!w->power_check) + return 0; + + power = w->power_check(w); if (w->power == power) return 0; w->power = power; @@ -1147,15 +1137,22 @@ int snd_soc_dapm_new_widgets(struct snd_soc_codec *codec) case snd_soc_dapm_switch: case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl: + w->power_check = dapm_generic_check_power; dapm_new_mixer(codec, w); break; case snd_soc_dapm_mux: case snd_soc_dapm_value_mux: + w->power_check = dapm_generic_check_power; dapm_new_mux(codec, w); break; case snd_soc_dapm_adc: + w->power_check = dapm_adc_check_power; + break; case snd_soc_dapm_dac: + w->power_check = dapm_dac_check_power; + break; case snd_soc_dapm_pga: + w->power_check = dapm_generic_check_power; dapm_new_pga(codec, w); break; case snd_soc_dapm_input: @@ -1165,6 +1162,8 @@ int snd_soc_dapm_new_widgets(struct snd_soc_codec *codec) case snd_soc_dapm_hp: case snd_soc_dapm_mic: case snd_soc_dapm_line: + w->power_check = dapm_generic_check_power; + break; case snd_soc_dapm_vmid: case snd_soc_dapm_pre: case snd_soc_dapm_post: -- cgit v1.2.3 From 1b4246a1fc487370665bf6c55aac8eae379642c0 Mon Sep 17 00:00:00 2001 From: Joonyoung Shim Date: Wed, 22 Apr 2009 10:56:50 +0900 Subject: ASoC: OMAP: Add checking to detect bufferless pcms Add checking in hw_params and prepare to detect bufferless pcms(i.e. BT <--> codec). Signed-off-by: Joonyoung Shim Signed-off-by: Mark Brown --- sound/soc/omap/omap-pcm.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/omap/omap-pcm.c b/sound/soc/omap/omap-pcm.c index 07cf7f46b58..6454e15f7d2 100644 --- a/sound/soc/omap/omap-pcm.c +++ b/sound/soc/omap/omap-pcm.c @@ -87,8 +87,10 @@ static int omap_pcm_hw_params(struct snd_pcm_substream *substream, struct omap_pcm_dma_data *dma_data = rtd->dai->cpu_dai->dma_data; int err = 0; + /* return if this is a bufferless transfer e.g. + * codec <--> BT codec or GSM modem -- lg FIXME */ if (!dma_data) - return -ENODEV; + return 0; snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); runtime->dma_bytes = params_buffer_bytes(params); @@ -134,6 +136,11 @@ static int omap_pcm_prepare(struct snd_pcm_substream *substream) struct omap_pcm_dma_data *dma_data = prtd->dma_data; struct omap_dma_channel_params dma_params; + /* return if this is a bufferless transfer e.g. + * codec <--> BT codec or GSM modem -- lg FIXME */ + if (!prtd->dma_data) + return 0; + memset(&dma_params, 0, sizeof(dma_params)); /* * Note: Regardless of interface data formats supported by OMAP McBSP -- cgit v1.2.3 From 246d0a17f5e09af0794960164269fc8988a8811c Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 22 Apr 2009 18:24:55 +0100 Subject: ASoC: Add power supply widget to DAPM Many modern CODECs have shared resources on chip which must be enabled for portions of the chip to work but which can be disabled at other times in order to achieve power savings. Examples of such resources include power supplies and some internal clocks. Since these widgets are dependencies for the audio path but do not carry audio signals they require slightly different handling to most widgets - they do not contribute to the audio path and so should not be counted as either inputs or outputs during path walks. Cases where one supply provides a supply for another will require additional work. There is also room for more optimisation of the graph walking to avoid repeated checks for the same thing. Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 44 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index d3d17354e76..7847f80e96d 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -52,17 +52,19 @@ /* dapm power sequences - make this per codec in the future */ static int dapm_up_seq[] = { - snd_soc_dapm_pre, snd_soc_dapm_micbias, snd_soc_dapm_mic, - snd_soc_dapm_mux, snd_soc_dapm_value_mux, snd_soc_dapm_dac, - snd_soc_dapm_mixer, snd_soc_dapm_mixer_named_ctl, snd_soc_dapm_pga, - snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk, snd_soc_dapm_post + snd_soc_dapm_pre, snd_soc_dapm_supply, snd_soc_dapm_micbias, + snd_soc_dapm_mic, snd_soc_dapm_mux, snd_soc_dapm_value_mux, + snd_soc_dapm_dac, snd_soc_dapm_mixer, snd_soc_dapm_mixer_named_ctl, + snd_soc_dapm_pga, snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk, + snd_soc_dapm_post }; static int dapm_down_seq[] = { snd_soc_dapm_pre, snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk, snd_soc_dapm_pga, snd_soc_dapm_mixer_named_ctl, snd_soc_dapm_mixer, snd_soc_dapm_dac, snd_soc_dapm_mic, snd_soc_dapm_micbias, - snd_soc_dapm_mux, snd_soc_dapm_value_mux, snd_soc_dapm_post + snd_soc_dapm_mux, snd_soc_dapm_value_mux, snd_soc_dapm_supply, + snd_soc_dapm_post }; static int dapm_status = 1; @@ -165,6 +167,7 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, case snd_soc_dapm_dac: case snd_soc_dapm_micbias: case snd_soc_dapm_vmid: + case snd_soc_dapm_supply: p->connect = 1; break; /* does effect routing - dynamically connected */ @@ -435,6 +438,9 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget) struct snd_soc_dapm_path *path; int con = 0; + if (widget->id == snd_soc_dapm_supply) + return 0; + if (widget->id == snd_soc_dapm_adc && widget->active) return 1; @@ -471,6 +477,9 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget) struct snd_soc_dapm_path *path; int con = 0; + if (widget->id == snd_soc_dapm_supply) + return 0; + /* active stream ? */ if (widget->id == snd_soc_dapm_dac && widget->active) return 1; @@ -622,6 +631,26 @@ static int dapm_dac_check_power(struct snd_soc_dapm_widget *w) } } +/* Check to see if a power supply is needed */ +static int dapm_supply_check_power(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dapm_path *path; + int power = 0; + + /* Check if one of our outputs is connected */ + list_for_each_entry(path, &w->sinks, list_source) { + if (path->sink && path->sink->power_check && + path->sink->power_check(path->sink)) { + power = 1; + break; + } + } + + dapm_clear_walk(w->codec); + + return power; +} + /* * Scan a single DAPM widget for a complete audio path and update the * power status appropriately. @@ -752,6 +781,7 @@ static void dbg_dump_dapm(struct snd_soc_codec* codec, const char *action) case snd_soc_dapm_pga: case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl: + case snd_soc_dapm_supply: if (w->name) { in = is_connected_input_ep(w); dapm_clear_walk(w->codec); @@ -880,6 +910,7 @@ static ssize_t dapm_widget_show(struct device *dev, case snd_soc_dapm_pga: case snd_soc_dapm_mixer: case snd_soc_dapm_mixer_named_ctl: + case snd_soc_dapm_supply: if (w->name) count += sprintf(buf + count, "%s: %s\n", w->name, w->power ? "On":"Off"); @@ -1044,6 +1075,7 @@ static int snd_soc_dapm_add_route(struct snd_soc_codec *codec, case snd_soc_dapm_vmid: case snd_soc_dapm_pre: case snd_soc_dapm_post: + case snd_soc_dapm_supply: list_add(&path->list, &codec->dapm_paths); list_add(&path->list_sink, &wsink->sources); list_add(&path->list_source, &wsource->sinks); @@ -1164,6 +1196,8 @@ int snd_soc_dapm_new_widgets(struct snd_soc_codec *codec) case snd_soc_dapm_line: w->power_check = dapm_generic_check_power; break; + case snd_soc_dapm_supply: + w->power_check = dapm_supply_check_power; case snd_soc_dapm_vmid: case snd_soc_dapm_pre: case snd_soc_dapm_post: -- cgit v1.2.3 From 42768a12822c3a0a6d7db69445281db975938294 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 22 Apr 2009 18:39:39 +0100 Subject: ASoC: Use DAPM supply widget for WM8903 charge pump Signed-off-by: Mark Brown --- sound/soc/codecs/wm8903.c | 53 ++++++++++++++++++----------------------------- 1 file changed, 20 insertions(+), 33 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index c5391841d41..a3a489da008 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -217,7 +217,6 @@ struct wm8903_priv { int sysclk; /* Reference counts */ - int charge_pump_users; int class_w_users; int playback_active; int capture_active; @@ -373,6 +372,15 @@ static void wm8903_reset(struct snd_soc_codec *codec) #define WM8903_OUTPUT_INT 0x2 #define WM8903_OUTPUT_IN 0x1 +static int wm8903_cp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + WARN_ON(event != SND_SOC_DAPM_POST_PMU); + mdelay(4); + + return 0; +} + /* * Event for headphone and line out amplifier power changes. Special * power up/down sequences are required in order to maximise pop/click @@ -382,12 +390,9 @@ static int wm8903_output_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; - struct wm8903_priv *wm8903 = codec->private_data; - struct i2c_client *i2c = codec->control_data; u16 val; u16 reg; int shift; - u16 cp_reg = wm8903_read(codec, WM8903_CHARGE_PUMP_0); switch (w->reg) { case WM8903_POWER_MANAGEMENT_2: @@ -419,18 +424,6 @@ static int wm8903_output_event(struct snd_soc_dapm_widget *w, /* Short the output */ val &= ~(WM8903_OUTPUT_SHORT << shift); wm8903_write(codec, reg, val); - - wm8903->charge_pump_users++; - - dev_dbg(&i2c->dev, "Charge pump use count now %d\n", - wm8903->charge_pump_users); - - if (wm8903->charge_pump_users == 1) { - dev_dbg(&i2c->dev, "Enabling charge pump\n"); - wm8903_write(codec, WM8903_CHARGE_PUMP_0, - cp_reg | WM8903_CP_ENA); - mdelay(4); - } } if (event & SND_SOC_DAPM_POST_PMU) { @@ -464,19 +457,6 @@ static int wm8903_output_event(struct snd_soc_dapm_widget *w, wm8903_write(codec, reg, val); } - if (event & SND_SOC_DAPM_POST_PMD) { - wm8903->charge_pump_users--; - - dev_dbg(&i2c->dev, "Charge pump use count now %d\n", - wm8903->charge_pump_users); - - if (wm8903->charge_pump_users == 0) { - dev_dbg(&i2c->dev, "Disabling charge pump\n"); - wm8903_write(codec, WM8903_CHARGE_PUMP_0, - cp_reg & ~WM8903_CP_ENA); - } - } - return 0; } @@ -844,26 +824,28 @@ SND_SOC_DAPM_MIXER("Right Speaker Mixer", WM8903_POWER_MANAGEMENT_4, 0, 0, SND_SOC_DAPM_PGA_E("Left Headphone Output PGA", WM8903_POWER_MANAGEMENT_2, 1, 0, NULL, 0, wm8903_output_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_PGA_E("Right Headphone Output PGA", WM8903_POWER_MANAGEMENT_2, 0, 0, NULL, 0, wm8903_output_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_PGA_E("Left Line Output PGA", WM8903_POWER_MANAGEMENT_3, 1, 0, NULL, 0, wm8903_output_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_PGA_E("Right Line Output PGA", WM8903_POWER_MANAGEMENT_3, 0, 0, NULL, 0, wm8903_output_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_PRE_PMD | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_PRE_PMD), SND_SOC_DAPM_PGA("Left Speaker PGA", WM8903_POWER_MANAGEMENT_5, 1, 0, NULL, 0), SND_SOC_DAPM_PGA("Right Speaker PGA", WM8903_POWER_MANAGEMENT_5, 0, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("Charge Pump", WM8903_CHARGE_PUMP_0, 0, 0, + wm8903_cp_event, SND_SOC_DAPM_POST_PMU), }; static const struct snd_soc_dapm_route intercon[] = { @@ -951,6 +933,11 @@ static const struct snd_soc_dapm_route intercon[] = { { "ROP", NULL, "Right Speaker PGA" }, { "RON", NULL, "Right Speaker PGA" }, + + { "Left Headphone Output PGA", NULL, "Charge Pump" }, + { "Right Headphone Output PGA", NULL, "Charge Pump" }, + { "Left Line Output PGA", NULL, "Charge Pump" }, + { "Right Line Output PGA", NULL, "Charge Pump" }, }; static int wm8903_add_widgets(struct snd_soc_codec *codec) -- cgit v1.2.3 From c2aef4ffd24dab5c8e94c66e4042ad39d38bcf39 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 22 Apr 2009 20:04:44 +0100 Subject: ASoC: Support CLK_DSP in WM8903 CLK_DSP provides a master clock for the DAC and ADC related functionality on the device. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8903.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index a3a489da008..27c8b94c055 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -846,6 +846,7 @@ SND_SOC_DAPM_PGA("Right Speaker PGA", WM8903_POWER_MANAGEMENT_5, 0, 0, SND_SOC_DAPM_SUPPLY("Charge Pump", WM8903_CHARGE_PUMP_0, 0, 0, wm8903_cp_event, SND_SOC_DAPM_POST_PMU), +SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8903_CLOCK_RATES_2, 1, 0, NULL, 0), }; static const struct snd_soc_dapm_route intercon[] = { @@ -891,7 +892,12 @@ static const struct snd_soc_dapm_route intercon[] = { { "Right Input PGA", NULL, "Right Input Mode Mux" }, { "ADCL", NULL, "Left Input PGA" }, + { "ADCL", NULL, "CLK_DSP" }, { "ADCR", NULL, "Right Input PGA" }, + { "ADCR", NULL, "CLK_DSP" }, + + { "DACL", NULL, "CLK_DSP" }, + { "DACR", NULL, "CLK_DSP" }, { "Left Output Mixer", "Left Bypass Switch", "Left Input PGA" }, { "Left Output Mixer", "Right Bypass Switch", "Right Input PGA" }, -- cgit v1.2.3 From 4dbfe8097157fde1f8054f48f991ea45833852cd Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 22 Apr 2009 20:32:40 +0100 Subject: ASoC: Optimise configuration of WM8903 DC servo Modify the default startup sequence in the chip to set the DC servo dither level for optimal performance. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8903.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 27c8b94c055..de0a5850720 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -978,6 +978,11 @@ static int wm8903_set_bias_level(struct snd_soc_codec *codec, wm8903_write(codec, WM8903_CLOCK_RATES_2, WM8903_CLK_SYS_ENA); + /* Change DC servo dither level in startup sequence */ + wm8903_write(codec, WM8903_WRITE_SEQUENCER_0, 0x11); + wm8903_write(codec, WM8903_WRITE_SEQUENCER_1, 0x1257); + wm8903_write(codec, WM8903_WRITE_SEQUENCER_2, 0x2); + wm8903_run_sequence(codec, 0); wm8903_sync_reg_cache(codec, codec->reg_cache); -- cgit v1.2.3 From d7d5c5476a12333a33b7a14ebb10eccc729c01cb Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 22 Apr 2009 21:03:50 +0100 Subject: ASoC: Actively manage the DC servo for WM8903 Save a little extra power by enabling the DC servo offset correction for the output channels only when the relevant channels are enabled. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8903.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index de0a5850720..0bab5c6bd64 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -392,14 +392,18 @@ static int wm8903_output_event(struct snd_soc_dapm_widget *w, struct snd_soc_codec *codec = w->codec; u16 val; u16 reg; + u16 dcs_reg; + u16 dcs_bit; int shift; switch (w->reg) { case WM8903_POWER_MANAGEMENT_2: reg = WM8903_ANALOGUE_HP_0; + dcs_bit = 0 + w->shift; break; case WM8903_POWER_MANAGEMENT_3: reg = WM8903_ANALOGUE_LINEOUT_0; + dcs_bit = 2 + w->shift; break; default: BUG(); @@ -439,6 +443,11 @@ static int wm8903_output_event(struct snd_soc_dapm_widget *w, val |= (WM8903_OUTPUT_OUT << shift); wm8903_write(codec, reg, val); + /* Enable the DC servo */ + dcs_reg = wm8903_read(codec, WM8903_DC_SERVO_0); + dcs_reg |= dcs_bit; + wm8903_write(codec, WM8903_DC_SERVO_0, dcs_reg); + /* Remove the short */ val |= (WM8903_OUTPUT_SHORT << shift); wm8903_write(codec, reg, val); @@ -451,6 +460,11 @@ static int wm8903_output_event(struct snd_soc_dapm_widget *w, val &= ~(WM8903_OUTPUT_SHORT << shift); wm8903_write(codec, reg, val); + /* Disable the DC servo */ + dcs_reg = wm8903_read(codec, WM8903_DC_SERVO_0); + dcs_reg &= ~dcs_bit; + wm8903_write(codec, WM8903_DC_SERVO_0, dcs_reg); + /* Then disable the intermediate and output stages */ val &= ~((WM8903_OUTPUT_OUT | WM8903_OUTPUT_INT | WM8903_OUTPUT_IN) << shift); -- cgit v1.2.3 From 727fb909e541ebd09d5b552afef02a147978c151 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 22 Apr 2009 21:06:14 +0100 Subject: ASoC: Remove redundant rate constraint for WM8903 This is now handled by symmetric_rates. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8903.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 0bab5c6bd64..bec418af97c 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -1289,14 +1289,8 @@ static int wm8903_startup(struct snd_pcm_substream *substream, if (wm8903->master_substream) { master_runtime = wm8903->master_substream->runtime; - dev_dbg(&i2c->dev, "Constraining to %d bits at %dHz\n", - master_runtime->sample_bits, - master_runtime->rate); - - snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - master_runtime->rate, - master_runtime->rate); + dev_dbg(&i2c->dev, "Constraining to %d bits\n", + master_runtime->sample_bits); snd_pcm_hw_constraint_minmax(substream->runtime, SNDRV_PCM_HW_PARAM_SAMPLE_BITS, -- cgit v1.2.3 From 291ce18ceb84aca79368369885eec2d329ae16c5 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 22 Apr 2009 21:36:14 +0100 Subject: ASoC: Implement WM8903 digital sidetone support Signed-off-by: Mark Brown --- sound/soc/codecs/wm8903.c | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index bec418af97c..d8a9222fbf7 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -533,6 +533,7 @@ static int wm8903_class_w_put(struct snd_kcontrol *kcontrol, /* ALSA can only do steps of .01dB */ static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1); +static const DECLARE_TLV_DB_SCALE(digital_sidetone_tlv, -3600, 300, 0); static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0); static const DECLARE_TLV_DB_SCALE(drc_tlv_thresh, 0, 75, 0); @@ -651,6 +652,16 @@ static const struct soc_enum rinput_inv_enum = SOC_ENUM_SINGLE(WM8903_ANALOGUE_RIGHT_INPUT_1, 4, 3, rinput_mux_text); +static const char *sidetone_text[] = { + "None", "Left", "Right" +}; + +static const struct soc_enum lsidetone_enum = + SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_0, 2, 3, sidetone_text); + +static const struct soc_enum rsidetone_enum = + SOC_ENUM_SINGLE(WM8903_DAC_DIGITAL_0, 0, 3, sidetone_text); + static const struct snd_kcontrol_new wm8903_snd_controls[] = { /* Input PGAs - No TLV since the scale depends on PGA mode */ @@ -694,6 +705,9 @@ SOC_DOUBLE_R_TLV("Digital Capture Volume", WM8903_ADC_DIGITAL_VOLUME_LEFT, SOC_ENUM("ADC Companding Mode", adc_companding), SOC_SINGLE("ADC Companding Switch", WM8903_AUDIO_INTERFACE_0, 3, 1, 0), +SOC_DOUBLE_TLV("Digital Sidetone Volume", WM8903_DAC_DIGITAL_0, 4, 8, + 12, 0, digital_sidetone_tlv), + /* DAC */ SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8903_DAC_DIGITAL_VOLUME_LEFT, WM8903_DAC_DIGITAL_VOLUME_RIGHT, 1, 120, 0, digital_tlv), @@ -756,6 +770,12 @@ static const struct snd_kcontrol_new rinput_mux = static const struct snd_kcontrol_new rinput_inv_mux = SOC_DAPM_ENUM("Right Inverting Input Mux", rinput_inv_enum); +static const struct snd_kcontrol_new lsidetone_mux = + SOC_DAPM_ENUM("DACL Sidetone Mux", lsidetone_enum); + +static const struct snd_kcontrol_new rsidetone_mux = + SOC_DAPM_ENUM("DACR Sidetone Mux", rsidetone_enum); + static const struct snd_kcontrol_new left_output_mixer[] = { SOC_DAPM_SINGLE("DACL Switch", WM8903_ANALOGUE_LEFT_MIX_0, 3, 1, 0), SOC_DAPM_SINGLE("DACR Switch", WM8903_ANALOGUE_LEFT_MIX_0, 2, 1, 0), @@ -822,6 +842,9 @@ SND_SOC_DAPM_PGA("Right Input PGA", WM8903_POWER_MANAGEMENT_0, 0, 0, NULL, 0), SND_SOC_DAPM_ADC("ADCL", "Left HiFi Capture", WM8903_POWER_MANAGEMENT_6, 1, 0), SND_SOC_DAPM_ADC("ADCR", "Right HiFi Capture", WM8903_POWER_MANAGEMENT_6, 0, 0), +SND_SOC_DAPM_MUX("DACL Sidetone", SND_SOC_NOPM, 0, 0, &lsidetone_mux), +SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &rsidetone_mux), + SND_SOC_DAPM_DAC("DACL", "Left Playback", WM8903_POWER_MANAGEMENT_6, 3, 0), SND_SOC_DAPM_DAC("DACR", "Right Playback", WM8903_POWER_MANAGEMENT_6, 2, 0), @@ -910,7 +933,14 @@ static const struct snd_soc_dapm_route intercon[] = { { "ADCR", NULL, "Right Input PGA" }, { "ADCR", NULL, "CLK_DSP" }, + { "DACL Sidetone", "Left", "ADCL" }, + { "DACL Sidetone", "Right", "ADCR" }, + { "DACR Sidetone", "Left", "ADCL" }, + { "DACR Sidetone", "Right", "ADCR" }, + + { "DACL", NULL, "DACL Sidetone" }, { "DACL", NULL, "CLK_DSP" }, + { "DACR", NULL, "DACR Sidetone" }, { "DACR", NULL, "CLK_DSP" }, { "Left Output Mixer", "Left Bypass Switch", "Left Input PGA" }, -- cgit v1.2.3 From 1a787e7ad242312af0afb2156596d42ee5e0c6bc Mon Sep 17 00:00:00 2001 From: Joonyoung Shim Date: Wed, 22 Apr 2009 13:13:34 +0900 Subject: ASoC: TWL4030: Add VDL path support Add DAPMs for VDL(Voice Down Link) path. To support VDL path, we have to change DAPMs of outputs(Earpiece, PreDrive Left/Right, Headset Left/Right, Carkit Left/Right) from mux to mixer. Signed-off-by: Joonyoung Shim Acked-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 256 ++++++++++++++++++++++----------------------- 1 file changed, 126 insertions(+), 130 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index cc2968cf640..fdf88dfbcff 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -321,104 +321,60 @@ static void twl4030_power_down(struct snd_soc_codec *codec) } /* Earpiece */ -static const char *twl4030_earpiece_texts[] = - {"Off", "DACL1", "DACL2", "DACR1"}; - -static const unsigned int twl4030_earpiece_values[] = - {0x0, 0x1, 0x2, 0x4}; - -static const struct soc_enum twl4030_earpiece_enum = - SOC_VALUE_ENUM_SINGLE(TWL4030_REG_EAR_CTL, 1, 0x7, - ARRAY_SIZE(twl4030_earpiece_texts), - twl4030_earpiece_texts, - twl4030_earpiece_values); - -static const struct snd_kcontrol_new twl4030_dapm_earpiece_control = -SOC_DAPM_VALUE_ENUM("Route", twl4030_earpiece_enum); +static const struct snd_kcontrol_new twl4030_dapm_earpiece_controls[] = { + SOC_DAPM_SINGLE("Voice", TWL4030_REG_EAR_CTL, 0, 1, 0), + SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_EAR_CTL, 1, 1, 0), + SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_EAR_CTL, 2, 1, 0), + SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_EAR_CTL, 3, 1, 0), +}; /* PreDrive Left */ -static const char *twl4030_predrivel_texts[] = - {"Off", "DACL1", "DACL2", "DACR2"}; - -static const unsigned int twl4030_predrivel_values[] = - {0x0, 0x1, 0x2, 0x4}; - -static const struct soc_enum twl4030_predrivel_enum = - SOC_VALUE_ENUM_SINGLE(TWL4030_REG_PREDL_CTL, 1, 0x7, - ARRAY_SIZE(twl4030_predrivel_texts), - twl4030_predrivel_texts, - twl4030_predrivel_values); - -static const struct snd_kcontrol_new twl4030_dapm_predrivel_control = -SOC_DAPM_VALUE_ENUM("Route", twl4030_predrivel_enum); +static const struct snd_kcontrol_new twl4030_dapm_predrivel_controls[] = { + SOC_DAPM_SINGLE("Voice", TWL4030_REG_PREDL_CTL, 0, 1, 0), + SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_PREDL_CTL, 1, 1, 0), + SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_PREDL_CTL, 2, 1, 0), + SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_PREDL_CTL, 3, 1, 0), +}; /* PreDrive Right */ -static const char *twl4030_predriver_texts[] = - {"Off", "DACR1", "DACR2", "DACL2"}; - -static const unsigned int twl4030_predriver_values[] = - {0x0, 0x1, 0x2, 0x4}; - -static const struct soc_enum twl4030_predriver_enum = - SOC_VALUE_ENUM_SINGLE(TWL4030_REG_PREDR_CTL, 1, 0x7, - ARRAY_SIZE(twl4030_predriver_texts), - twl4030_predriver_texts, - twl4030_predriver_values); - -static const struct snd_kcontrol_new twl4030_dapm_predriver_control = -SOC_DAPM_VALUE_ENUM("Route", twl4030_predriver_enum); +static const struct snd_kcontrol_new twl4030_dapm_predriver_controls[] = { + SOC_DAPM_SINGLE("Voice", TWL4030_REG_PREDR_CTL, 0, 1, 0), + SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_PREDR_CTL, 1, 1, 0), + SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_PREDR_CTL, 2, 1, 0), + SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_PREDR_CTL, 3, 1, 0), +}; /* Headset Left */ -static const char *twl4030_hsol_texts[] = - {"Off", "DACL1", "DACL2"}; - -static const struct soc_enum twl4030_hsol_enum = - SOC_ENUM_SINGLE(TWL4030_REG_HS_SEL, 1, - ARRAY_SIZE(twl4030_hsol_texts), - twl4030_hsol_texts); - -static const struct snd_kcontrol_new twl4030_dapm_hsol_control = -SOC_DAPM_ENUM("Route", twl4030_hsol_enum); +static const struct snd_kcontrol_new twl4030_dapm_hsol_controls[] = { + SOC_DAPM_SINGLE("Voice", TWL4030_REG_HS_SEL, 0, 1, 0), + SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_HS_SEL, 1, 1, 0), + SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_HS_SEL, 2, 1, 0), +}; /* Headset Right */ -static const char *twl4030_hsor_texts[] = - {"Off", "DACR1", "DACR2"}; - -static const struct soc_enum twl4030_hsor_enum = - SOC_ENUM_SINGLE(TWL4030_REG_HS_SEL, 4, - ARRAY_SIZE(twl4030_hsor_texts), - twl4030_hsor_texts); - -static const struct snd_kcontrol_new twl4030_dapm_hsor_control = -SOC_DAPM_ENUM("Route", twl4030_hsor_enum); +static const struct snd_kcontrol_new twl4030_dapm_hsor_controls[] = { + SOC_DAPM_SINGLE("Voice", TWL4030_REG_HS_SEL, 3, 1, 0), + SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_HS_SEL, 4, 1, 0), + SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_HS_SEL, 5, 1, 0), +}; /* Carkit Left */ -static const char *twl4030_carkitl_texts[] = - {"Off", "DACL1", "DACL2"}; - -static const struct soc_enum twl4030_carkitl_enum = - SOC_ENUM_SINGLE(TWL4030_REG_PRECKL_CTL, 1, - ARRAY_SIZE(twl4030_carkitl_texts), - twl4030_carkitl_texts); - -static const struct snd_kcontrol_new twl4030_dapm_carkitl_control = -SOC_DAPM_ENUM("Route", twl4030_carkitl_enum); +static const struct snd_kcontrol_new twl4030_dapm_carkitl_controls[] = { + SOC_DAPM_SINGLE("Voice", TWL4030_REG_PRECKL_CTL, 0, 1, 0), + SOC_DAPM_SINGLE("AudioL1", TWL4030_REG_PRECKL_CTL, 1, 1, 0), + SOC_DAPM_SINGLE("AudioL2", TWL4030_REG_PRECKL_CTL, 2, 1, 0), +}; /* Carkit Right */ -static const char *twl4030_carkitr_texts[] = - {"Off", "DACR1", "DACR2"}; - -static const struct soc_enum twl4030_carkitr_enum = - SOC_ENUM_SINGLE(TWL4030_REG_PRECKR_CTL, 1, - ARRAY_SIZE(twl4030_carkitr_texts), - twl4030_carkitr_texts); - -static const struct snd_kcontrol_new twl4030_dapm_carkitr_control = -SOC_DAPM_ENUM("Route", twl4030_carkitr_enum); +static const struct snd_kcontrol_new twl4030_dapm_carkitr_controls[] = { + SOC_DAPM_SINGLE("Voice", TWL4030_REG_PRECKR_CTL, 0, 1, 0), + SOC_DAPM_SINGLE("AudioR1", TWL4030_REG_PRECKR_CTL, 1, 1, 0), + SOC_DAPM_SINGLE("AudioR2", TWL4030_REG_PRECKR_CTL, 2, 1, 0), +}; /* Handsfree Left */ static const char *twl4030_handsfreel_texts[] = - {"Voice", "DACL1", "DACL2", "DACR2"}; + {"Voice", "AudioL1", "AudioL2", "AudioR2"}; static const struct soc_enum twl4030_handsfreel_enum = SOC_ENUM_SINGLE(TWL4030_REG_HFL_CTL, 0, @@ -430,7 +386,7 @@ SOC_DAPM_ENUM("Route", twl4030_handsfreel_enum); /* Handsfree Right */ static const char *twl4030_handsfreer_texts[] = - {"Voice", "DACR1", "DACR2", "DACL2"}; + {"Voice", "AudioR1", "AudioR2", "AudioL2"}; static const struct soc_enum twl4030_handsfreer_enum = SOC_ENUM_SINGLE(TWL4030_REG_HFR_CTL, 0, @@ -828,6 +784,12 @@ static DECLARE_TLV_DB_SCALE(digital_fine_tlv, -6300, 100, 1); */ static DECLARE_TLV_DB_SCALE(digital_coarse_tlv, 0, 600, 0); +/* + * Voice Downlink GAIN volume control: + * from -37 to 12 dB in 1 dB steps (mute instead of -37 dB) + */ +static DECLARE_TLV_DB_SCALE(digital_voice_downlink_tlv, -3700, 100, 1); + /* * Analog playback gain * -24 dB to 12 dB in 2 dB steps @@ -892,6 +854,16 @@ static const struct snd_kcontrol_new twl4030_snd_controls[] = { TWL4030_REG_ARXL2_APGA_CTL, TWL4030_REG_ARXR2_APGA_CTL, 1, 1, 0), + /* Common voice downlink gain controls */ + SOC_SINGLE_TLV("DAC Voice Digital Downlink Volume", + TWL4030_REG_VRXPGA, 0, 0x31, 0, digital_voice_downlink_tlv), + + SOC_SINGLE_TLV("DAC Voice Analog Downlink Volume", + TWL4030_REG_VDL_APGA_CTL, 3, 0x12, 1, analog_tlv), + + SOC_SINGLE("DAC Voice Analog Downlink Switch", + TWL4030_REG_VDL_APGA_CTL, 1, 1, 0), + /* Separate output gain controls */ SOC_DOUBLE_R_TLV_TWL4030("PreDriv Playback Volume", TWL4030_REG_PREDL_CTL, TWL4030_REG_PREDR_CTL, @@ -956,6 +928,8 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_DAC("DAC Left2", "Left Rear Playback", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_DAC("DAC Voice", "Voice Playback", + TWL4030_REG_AVDAC_CTL, 4, 0), /* Analog PGAs */ SND_SOC_DAPM_PGA("ARXR1_APGA", TWL4030_REG_ARXR1_APGA_CTL, @@ -966,6 +940,8 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { 0, 0, NULL, 0), SND_SOC_DAPM_PGA("ARXL2_APGA", TWL4030_REG_ARXL2_APGA_CTL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("VDL_APGA", TWL4030_REG_VDL_APGA_CTL, + 0, 0, NULL, 0), /* Analog bypasses */ SND_SOC_DAPM_SWITCH_E("Right1 Analog Loopback", SND_SOC_NOPM, 0, 0, @@ -998,26 +974,35 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_MIXER("Analog L2 Playback Mixer", TWL4030_REG_AVDAC_CTL, 3, 0, NULL, 0), - /* Output MUX controls */ + /* Output MIXER controls */ /* Earpiece */ - SND_SOC_DAPM_VALUE_MUX("Earpiece Mux", SND_SOC_NOPM, 0, 0, - &twl4030_dapm_earpiece_control), + SND_SOC_DAPM_MIXER("Earpiece Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_earpiece_controls[0], + ARRAY_SIZE(twl4030_dapm_earpiece_controls)), /* PreDrivL/R */ - SND_SOC_DAPM_VALUE_MUX("PredriveL Mux", SND_SOC_NOPM, 0, 0, - &twl4030_dapm_predrivel_control), - SND_SOC_DAPM_VALUE_MUX("PredriveR Mux", SND_SOC_NOPM, 0, 0, - &twl4030_dapm_predriver_control), + SND_SOC_DAPM_MIXER("PredriveL Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_predrivel_controls[0], + ARRAY_SIZE(twl4030_dapm_predrivel_controls)), + SND_SOC_DAPM_MIXER("PredriveR Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_predriver_controls[0], + ARRAY_SIZE(twl4030_dapm_predriver_controls)), /* HeadsetL/R */ - SND_SOC_DAPM_MUX_E("HeadsetL Mux", SND_SOC_NOPM, 0, 0, - &twl4030_dapm_hsol_control, headsetl_event, - SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), - SND_SOC_DAPM_MUX("HeadsetR Mux", SND_SOC_NOPM, 0, 0, - &twl4030_dapm_hsor_control), + SND_SOC_DAPM_MIXER_E("HeadsetL Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_hsol_controls[0], + ARRAY_SIZE(twl4030_dapm_hsol_controls), headsetl_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MIXER("HeadsetR Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_hsor_controls[0], + ARRAY_SIZE(twl4030_dapm_hsor_controls)), /* CarkitL/R */ - SND_SOC_DAPM_MUX("CarkitL Mux", SND_SOC_NOPM, 0, 0, - &twl4030_dapm_carkitl_control), - SND_SOC_DAPM_MUX("CarkitR Mux", SND_SOC_NOPM, 0, 0, - &twl4030_dapm_carkitr_control), + SND_SOC_DAPM_MIXER("CarkitL Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_carkitl_controls[0], + ARRAY_SIZE(twl4030_dapm_carkitl_controls)), + SND_SOC_DAPM_MIXER("CarkitR Mixer", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_carkitr_controls[0], + ARRAY_SIZE(twl4030_dapm_carkitr_controls)), + + /* Output MUX controls */ /* HandsfreeL/R */ SND_SOC_DAPM_MUX_E("HandsfreeL Mux", TWL4030_REG_HFL_CTL, 5, 0, &twl4030_dapm_handsfreel_control, handsfree_event, @@ -1082,50 +1067,61 @@ static const struct snd_soc_dapm_route intercon[] = { {"ARXL2_APGA", NULL, "Analog L2 Playback Mixer"}, {"ARXR2_APGA", NULL, "Analog R2 Playback Mixer"}, + {"VDL_APGA", NULL, "DAC Voice"}, + /* Internal playback routings */ /* Earpiece */ - {"Earpiece Mux", "DACL1", "ARXL1_APGA"}, - {"Earpiece Mux", "DACL2", "ARXL2_APGA"}, - {"Earpiece Mux", "DACR1", "ARXR1_APGA"}, + {"Earpiece Mixer", "Voice", "VDL_APGA"}, + {"Earpiece Mixer", "AudioL1", "ARXL1_APGA"}, + {"Earpiece Mixer", "AudioL2", "ARXL2_APGA"}, + {"Earpiece Mixer", "AudioR1", "ARXR1_APGA"}, /* PreDrivL */ - {"PredriveL Mux", "DACL1", "ARXL1_APGA"}, - {"PredriveL Mux", "DACL2", "ARXL2_APGA"}, - {"PredriveL Mux", "DACR2", "ARXR2_APGA"}, + {"PredriveL Mixer", "Voice", "VDL_APGA"}, + {"PredriveL Mixer", "AudioL1", "ARXL1_APGA"}, + {"PredriveL Mixer", "AudioL2", "ARXL2_APGA"}, + {"PredriveL Mixer", "AudioR2", "ARXR2_APGA"}, /* PreDrivR */ - {"PredriveR Mux", "DACR1", "ARXR1_APGA"}, - {"PredriveR Mux", "DACR2", "ARXR2_APGA"}, - {"PredriveR Mux", "DACL2", "ARXL2_APGA"}, + {"PredriveR Mixer", "Voice", "VDL_APGA"}, + {"PredriveR Mixer", "AudioR1", "ARXR1_APGA"}, + {"PredriveR Mixer", "AudioR2", "ARXR2_APGA"}, + {"PredriveR Mixer", "AudioL2", "ARXL2_APGA"}, /* HeadsetL */ - {"HeadsetL Mux", "DACL1", "ARXL1_APGA"}, - {"HeadsetL Mux", "DACL2", "ARXL2_APGA"}, + {"HeadsetL Mixer", "Voice", "VDL_APGA"}, + {"HeadsetL Mixer", "AudioL1", "ARXL1_APGA"}, + {"HeadsetL Mixer", "AudioL2", "ARXL2_APGA"}, /* HeadsetR */ - {"HeadsetR Mux", "DACR1", "ARXR1_APGA"}, - {"HeadsetR Mux", "DACR2", "ARXR2_APGA"}, + {"HeadsetR Mixer", "Voice", "VDL_APGA"}, + {"HeadsetR Mixer", "AudioR1", "ARXR1_APGA"}, + {"HeadsetR Mixer", "AudioR2", "ARXR2_APGA"}, /* CarkitL */ - {"CarkitL Mux", "DACL1", "ARXL1_APGA"}, - {"CarkitL Mux", "DACL2", "ARXL2_APGA"}, + {"CarkitL Mixer", "Voice", "VDL_APGA"}, + {"CarkitL Mixer", "AudioL1", "ARXL1_APGA"}, + {"CarkitL Mixer", "AudioL2", "ARXL2_APGA"}, /* CarkitR */ - {"CarkitR Mux", "DACR1", "ARXR1_APGA"}, - {"CarkitR Mux", "DACR2", "ARXR2_APGA"}, + {"CarkitR Mixer", "Voice", "VDL_APGA"}, + {"CarkitR Mixer", "AudioR1", "ARXR1_APGA"}, + {"CarkitR Mixer", "AudioR2", "ARXR2_APGA"}, /* HandsfreeL */ - {"HandsfreeL Mux", "DACL1", "ARXL1_APGA"}, - {"HandsfreeL Mux", "DACL2", "ARXL2_APGA"}, - {"HandsfreeL Mux", "DACR2", "ARXR2_APGA"}, + {"HandsfreeL Mux", "Voice", "VDL_APGA"}, + {"HandsfreeL Mux", "AudioL1", "ARXL1_APGA"}, + {"HandsfreeL Mux", "AudioL2", "ARXL2_APGA"}, + {"HandsfreeL Mux", "AudioR2", "ARXR2_APGA"}, /* HandsfreeR */ - {"HandsfreeR Mux", "DACR1", "ARXR1_APGA"}, - {"HandsfreeR Mux", "DACR2", "ARXR2_APGA"}, - {"HandsfreeR Mux", "DACL2", "ARXL2_APGA"}, + {"HandsfreeR Mux", "Voice", "VDL_APGA"}, + {"HandsfreeR Mux", "AudioR1", "ARXR1_APGA"}, + {"HandsfreeR Mux", "AudioR2", "ARXR2_APGA"}, + {"HandsfreeR Mux", "AudioL2", "ARXL2_APGA"}, /* outputs */ {"OUTL", NULL, "ARXL2_APGA"}, {"OUTR", NULL, "ARXR2_APGA"}, - {"EARPIECE", NULL, "Earpiece Mux"}, - {"PREDRIVEL", NULL, "PredriveL Mux"}, - {"PREDRIVER", NULL, "PredriveR Mux"}, - {"HSOL", NULL, "HeadsetL Mux"}, - {"HSOR", NULL, "HeadsetR Mux"}, - {"CARKITL", NULL, "CarkitL Mux"}, - {"CARKITR", NULL, "CarkitR Mux"}, + {"EARPIECE", NULL, "Earpiece Mixer"}, + {"PREDRIVEL", NULL, "PredriveL Mixer"}, + {"PREDRIVER", NULL, "PredriveR Mixer"}, + {"HSOL", NULL, "HeadsetL Mixer"}, + {"HSOR", NULL, "HeadsetR Mixer"}, + {"CARKITL", NULL, "CarkitL Mixer"}, + {"CARKITR", NULL, "CarkitR Mixer"}, {"HFL", NULL, "HandsfreeL Mux"}, {"HFR", NULL, "HandsfreeR Mux"}, -- cgit v1.2.3 From 2d7e71fa231035d69faffbfe506ef23638385994 Mon Sep 17 00:00:00 2001 From: Eric Miao Date: Thu, 23 Apr 2009 17:05:38 +0800 Subject: ASoC: simplify the SSP DMA parameters settings by run-time generation The SSP DMA parameters can actually be easily generated at run-time since they are almost similar except for the FIFO width and direction. Another benefit is the re-use of information from 'struct ssp_device', like SSDR physical FIFO address and DRCMR register index for both directions. Signed-off-by: Eric Miao Signed-off-by: Mark Brown Reviewed-by: pHilipp Zabel --- sound/soc/pxa/pxa-ssp.c | 203 ++++++++++-------------------------------------- 1 file changed, 43 insertions(+), 160 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index b9b61ddca6b..fb8cacca341 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -50,139 +50,6 @@ struct ssp_priv { #endif }; -#define PXA2xx_SSP1_BASE 0x41000000 -#define PXA27x_SSP2_BASE 0x41700000 -#define PXA27x_SSP3_BASE 0x41900000 -#define PXA3xx_SSP4_BASE 0x41a00000 - -static struct pxa2xx_pcm_dma_params pxa_ssp1_pcm_mono_out = { - .name = "SSP1 PCM Mono out", - .dev_addr = PXA2xx_SSP1_BASE + SSDR, - .drcmr = &DRCMR(14), - .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | - DCMD_BURST16 | DCMD_WIDTH2, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp1_pcm_mono_in = { - .name = "SSP1 PCM Mono in", - .dev_addr = PXA2xx_SSP1_BASE + SSDR, - .drcmr = &DRCMR(13), - .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | - DCMD_BURST16 | DCMD_WIDTH2, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp1_pcm_stereo_out = { - .name = "SSP1 PCM Stereo out", - .dev_addr = PXA2xx_SSP1_BASE + SSDR, - .drcmr = &DRCMR(14), - .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | - DCMD_BURST16 | DCMD_WIDTH4, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp1_pcm_stereo_in = { - .name = "SSP1 PCM Stereo in", - .dev_addr = PXA2xx_SSP1_BASE + SSDR, - .drcmr = &DRCMR(13), - .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | - DCMD_BURST16 | DCMD_WIDTH4, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp2_pcm_mono_out = { - .name = "SSP2 PCM Mono out", - .dev_addr = PXA27x_SSP2_BASE + SSDR, - .drcmr = &DRCMR(16), - .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | - DCMD_BURST16 | DCMD_WIDTH2, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp2_pcm_mono_in = { - .name = "SSP2 PCM Mono in", - .dev_addr = PXA27x_SSP2_BASE + SSDR, - .drcmr = &DRCMR(15), - .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | - DCMD_BURST16 | DCMD_WIDTH2, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp2_pcm_stereo_out = { - .name = "SSP2 PCM Stereo out", - .dev_addr = PXA27x_SSP2_BASE + SSDR, - .drcmr = &DRCMR(16), - .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | - DCMD_BURST16 | DCMD_WIDTH4, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp2_pcm_stereo_in = { - .name = "SSP2 PCM Stereo in", - .dev_addr = PXA27x_SSP2_BASE + SSDR, - .drcmr = &DRCMR(15), - .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | - DCMD_BURST16 | DCMD_WIDTH4, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp3_pcm_mono_out = { - .name = "SSP3 PCM Mono out", - .dev_addr = PXA27x_SSP3_BASE + SSDR, - .drcmr = &DRCMR(67), - .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | - DCMD_BURST16 | DCMD_WIDTH2, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp3_pcm_mono_in = { - .name = "SSP3 PCM Mono in", - .dev_addr = PXA27x_SSP3_BASE + SSDR, - .drcmr = &DRCMR(66), - .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | - DCMD_BURST16 | DCMD_WIDTH2, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp3_pcm_stereo_out = { - .name = "SSP3 PCM Stereo out", - .dev_addr = PXA27x_SSP3_BASE + SSDR, - .drcmr = &DRCMR(67), - .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | - DCMD_BURST16 | DCMD_WIDTH4, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp3_pcm_stereo_in = { - .name = "SSP3 PCM Stereo in", - .dev_addr = PXA27x_SSP3_BASE + SSDR, - .drcmr = &DRCMR(66), - .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | - DCMD_BURST16 | DCMD_WIDTH4, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp4_pcm_mono_out = { - .name = "SSP4 PCM Mono out", - .dev_addr = PXA3xx_SSP4_BASE + SSDR, - .drcmr = &DRCMR(67), - .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | - DCMD_BURST16 | DCMD_WIDTH2, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp4_pcm_mono_in = { - .name = "SSP4 PCM Mono in", - .dev_addr = PXA3xx_SSP4_BASE + SSDR, - .drcmr = &DRCMR(66), - .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | - DCMD_BURST16 | DCMD_WIDTH2, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp4_pcm_stereo_out = { - .name = "SSP4 PCM Stereo out", - .dev_addr = PXA3xx_SSP4_BASE + SSDR, - .drcmr = &DRCMR(67), - .dcmd = DCMD_INCSRCADDR | DCMD_FLOWTRG | - DCMD_BURST16 | DCMD_WIDTH4, -}; - -static struct pxa2xx_pcm_dma_params pxa_ssp4_pcm_stereo_in = { - .name = "SSP4 PCM Stereo in", - .dev_addr = PXA3xx_SSP4_BASE + SSDR, - .drcmr = &DRCMR(66), - .dcmd = DCMD_INCTRGADDR | DCMD_FLOWSRC | - DCMD_BURST16 | DCMD_WIDTH4, -}; - static void dump_registers(struct ssp_device *ssp) { dev_dbg(&ssp->pdev->dev, "SSCR0 0x%08x SSCR1 0x%08x SSTO 0x%08x\n", @@ -194,25 +61,33 @@ static void dump_registers(struct ssp_device *ssp) ssp_read_reg(ssp, SSACD)); } -static struct pxa2xx_pcm_dma_params *ssp_dma_params[4][4] = { - { - &pxa_ssp1_pcm_mono_out, &pxa_ssp1_pcm_mono_in, - &pxa_ssp1_pcm_stereo_out, &pxa_ssp1_pcm_stereo_in, - }, - { - &pxa_ssp2_pcm_mono_out, &pxa_ssp2_pcm_mono_in, - &pxa_ssp2_pcm_stereo_out, &pxa_ssp2_pcm_stereo_in, - }, - { - &pxa_ssp3_pcm_mono_out, &pxa_ssp3_pcm_mono_in, - &pxa_ssp3_pcm_stereo_out, &pxa_ssp3_pcm_stereo_in, - }, - { - &pxa_ssp4_pcm_mono_out, &pxa_ssp4_pcm_mono_in, - &pxa_ssp4_pcm_stereo_out, &pxa_ssp4_pcm_stereo_in, - }, +struct pxa2xx_pcm_dma_data { + struct pxa2xx_pcm_dma_params params; + char name[20]; }; +static struct pxa2xx_pcm_dma_params * +ssp_get_dma_params(struct ssp_device *ssp, int stereo, int out) +{ + struct pxa2xx_pcm_dma_data *dma; + + dma = kzalloc(sizeof(struct pxa2xx_pcm_dma_data), GFP_KERNEL); + if (dma == NULL) + return NULL; + + snprintf(dma->name, 20, "SSP%d PCM %s %s", ssp->port_id, + stereo ? "Stereo" : "Mono", out ? "out" : "in"); + + dma->params.name = dma->name; + dma->params.drcmr = &DRCMR(out ? ssp->drcmr_tx : ssp->drcmr_rx); + dma->params.dcmd = (out ? (DCMD_INCSRCADDR | DCMD_FLOWTRG) : + (DCMD_INCTRGADDR | DCMD_FLOWSRC)) | + (stereo ? DCMD_WIDTH4 : DCMD_WIDTH2) | DCMD_BURST16; + dma->params.dev_addr = ssp->phys_base + SSDR; + + return &dma->params; +} + static int pxa_ssp_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -227,6 +102,11 @@ static int pxa_ssp_startup(struct snd_pcm_substream *substream, clk_enable(priv->dev.ssp->clk); ssp_disable(&priv->dev); } + + if (cpu_dai->dma_data) { + kfree(cpu_dai->dma_data); + cpu_dai->dma_data = NULL; + } return ret; } @@ -241,6 +121,11 @@ static void pxa_ssp_shutdown(struct snd_pcm_substream *substream, ssp_disable(&priv->dev); clk_disable(priv->dev.ssp->clk); } + + if (cpu_dai->dma_data) { + kfree(cpu_dai->dma_data); + cpu_dai->dma_data = NULL; + } } #ifdef CONFIG_PM @@ -653,25 +538,23 @@ static int pxa_ssp_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; struct ssp_priv *priv = cpu_dai->private_data; struct ssp_device *ssp = priv->dev.ssp; - int dma = 0, chn = params_channels(params); + int chn = params_channels(params); u32 sscr0; u32 sspsp; int width = snd_pcm_format_physical_width(params_format(params)); int ttsa = ssp_read_reg(ssp, SSTSA) & 0xf; - /* select correct DMA params */ - if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) - dma = 1; /* capture DMA offset is 1,3 */ + /* generate correct DMA params */ + if (cpu_dai->dma_data) + kfree(cpu_dai->dma_data); + /* Network mode with one active slot (ttsa == 1) can be used * to force 16-bit frame width on the wire (for S16_LE), even * with two channels. Use 16-bit DMA transfers for this case. */ - if (((chn == 2) && (ttsa != 1)) || (width == 32)) - dma += 2; /* 32-bit DMA offset is 2, 16-bit is 0 */ - - cpu_dai->dma_data = ssp_dma_params[cpu_dai->id][dma]; - - dev_dbg(&ssp->pdev->dev, "pxa_ssp_hw_params: dma %d\n", dma); + cpu_dai->dma_data = ssp_get_dma_params(ssp, + ((chn == 2) && (ttsa != 1)) || (width == 32), + substream->stream == SNDRV_PCM_STREAM_PLAYBACK); /* we can only change the settings if the port is not in use */ if (ssp_read_reg(ssp, SSCR0) & SSCR0_SSE) -- cgit v1.2.3 From 8eb9feabe566d8272510d5fb33f55a72e3ab3ce4 Mon Sep 17 00:00:00 2001 From: Eric Miao Date: Thu, 23 Apr 2009 17:57:46 +0800 Subject: ASoC: change stereo/mono to 32-bit/16-bit for pxa-ssp The original idea came from pHilipp, and this makes the code looks more consistent. Signed-off-by: Eric Miao Signed-off-by: Mark Brown --- sound/soc/pxa/pxa-ssp.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index fb8cacca341..6fc787610ad 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -67,7 +67,7 @@ struct pxa2xx_pcm_dma_data { }; static struct pxa2xx_pcm_dma_params * -ssp_get_dma_params(struct ssp_device *ssp, int stereo, int out) +ssp_get_dma_params(struct ssp_device *ssp, int width4, int out) { struct pxa2xx_pcm_dma_data *dma; @@ -76,13 +76,13 @@ ssp_get_dma_params(struct ssp_device *ssp, int stereo, int out) return NULL; snprintf(dma->name, 20, "SSP%d PCM %s %s", ssp->port_id, - stereo ? "Stereo" : "Mono", out ? "out" : "in"); + width4 ? "32-bit" : "16-bit", out ? "out" : "in"); dma->params.name = dma->name; dma->params.drcmr = &DRCMR(out ? ssp->drcmr_tx : ssp->drcmr_rx); dma->params.dcmd = (out ? (DCMD_INCSRCADDR | DCMD_FLOWTRG) : (DCMD_INCTRGADDR | DCMD_FLOWSRC)) | - (stereo ? DCMD_WIDTH4 : DCMD_WIDTH2) | DCMD_BURST16; + (width4 ? DCMD_WIDTH4 : DCMD_WIDTH2) | DCMD_BURST16; dma->params.dev_addr = ssp->phys_base + SSDR; return &dma->params; -- cgit v1.2.3 From 31a00c6b7c0c4f01be49f02660de920c8b82b613 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 23 Apr 2009 14:36:48 +0300 Subject: ASoC: OMAP: Add 4 channel support to mcbsp Add 4 channel support to omap-mcbsp. This mode is going to be used by the twl4030 codec, when it is configured in Option1 mode. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/omap/omap-mcbsp.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c index 495192af8c2..a5d46a7b196 100644 --- a/sound/soc/omap/omap-mcbsp.c +++ b/sound/soc/omap/omap-mcbsp.c @@ -259,6 +259,7 @@ static int omap_mcbsp_dai_hw_params(struct snd_pcm_substream *substream, regs->xcr2 |= XFRLEN2(wpf - 1); } case 1: + case 4: /* Set word per (McBSP) frame for phase1 */ regs->rcr1 |= RFRLEN1(wpf - 1); regs->xcr1 |= XFRLEN1(wpf - 1); @@ -506,13 +507,13 @@ static struct snd_soc_dai_ops omap_mcbsp_dai_ops = { .id = (link_id), \ .playback = { \ .channels_min = 1, \ - .channels_max = 2, \ + .channels_max = 4, \ .rates = OMAP_MCBSP_RATES, \ .formats = SNDRV_PCM_FMTBIT_S16_LE, \ }, \ .capture = { \ .channels_min = 1, \ - .channels_max = 2, \ + .channels_max = 4, \ .rates = OMAP_MCBSP_RATES, \ .formats = SNDRV_PCM_FMTBIT_S16_LE, \ }, \ -- cgit v1.2.3 From 8a1f936acdfd53cb0a981f3f80483863dcd84fa9 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 23 Apr 2009 14:36:49 +0300 Subject: ASoC: TWL4030: Add 4 channel TDM support Support for 4 channel TDM (SND_SOC_DAIFMT_DSP_A) for twl4030 codec. The channel allocations are: Playback: TDM i2s TWL RX Channel 1 Left SDRL2 Channel 3 Right SDRR2 Channel 2 -- SDRL1 Channel 4 -- SDRR1 Capture: TDM i2s TWL TX Channel 1 Left TXL1 Channel 3 Right TXR1 Channel 2 -- TXL2 Channel 4 -- TXR2 Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 52 ++++++++++++++++++++++++++++++++++++++++++++-- sound/soc/codecs/twl4030.h | 11 ++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index fdf88dfbcff..e23c20c42f1 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -1251,6 +1251,28 @@ static void twl4030_constraints(struct twl4030_priv *twl4030, twl4030->channels); } +/* In case of 4 channel mode, the RX1 L/R for playback and the TX2 L/R for + * capture has to be enabled/disabled. */ +static void twl4030_tdm_enable(struct snd_soc_codec *codec, int direction, + int enable) +{ + u8 reg, mask; + + reg = twl4030_read_reg_cache(codec, TWL4030_REG_OPTION); + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + mask = TWL4030_ARXL1_VRX_EN | TWL4030_ARXR1_EN; + else + mask = TWL4030_ATXL2_VTXL_EN | TWL4030_ATXR2_VTXR_EN; + + if (enable) + reg |= mask; + else + reg &= ~mask; + + twl4030_write(codec, TWL4030_REG_OPTION, reg); +} + static int twl4030_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -1267,6 +1289,15 @@ static int twl4030_startup(struct snd_pcm_substream *substream, if (twl4030->configured) twl4030_constraints(twl4030, twl4030->master_substream); } else { + if (!(twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE) & + TWL4030_OPTION_1)) { + /* In option2 4 channel is not supported, set the + * constraint for the first stream for channels, the + * second stream will 'inherit' this cosntraint */ + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_CHANNELS, + 2, 2); + } twl4030->master_substream = substream; } @@ -1292,6 +1323,10 @@ static void twl4030_shutdown(struct snd_pcm_substream *substream, twl4030->configured = 0; else if (!twl4030->master_substream->runtime->channels) twl4030->configured = 0; + + /* If the closing substream had 4 channel, do the necessary cleanup */ + if (substream->runtime->channels == 4) + twl4030_tdm_enable(codec, substream->stream, 0); } static int twl4030_hw_params(struct snd_pcm_substream *substream, @@ -1304,6 +1339,16 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream, struct twl4030_priv *twl4030 = codec->private_data; u8 mode, old_mode, format, old_format; + /* If the substream has 4 channel, do the necessary setup */ + if (params_channels(params) == 4) { + /* Safety check: are we in the correct operating mode? */ + if ((twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE) & + TWL4030_OPTION_1)) + twl4030_tdm_enable(codec, substream->stream, 1); + else + return -EINVAL; + } + if (twl4030->configured) /* Ignoring hw_params for already configured DAI */ return 0; @@ -1461,6 +1506,9 @@ static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai, case SND_SOC_DAIFMT_I2S: format |= TWL4030_AIF_FORMAT_CODEC; break; + case SND_SOC_DAIFMT_DSP_A: + format |= TWL4030_AIF_FORMAT_TDM; + break; default: return -EINVAL; } @@ -1642,13 +1690,13 @@ struct snd_soc_dai twl4030_dai[] = { .playback = { .stream_name = "Playback", .channels_min = 2, - .channels_max = 2, + .channels_max = 4, .rates = TWL4030_RATES | SNDRV_PCM_RATE_96000, .formats = TWL4030_FORMATS,}, .capture = { .stream_name = "Capture", .channels_min = 2, - .channels_max = 2, + .channels_max = 4, .rates = TWL4030_RATES, .formats = TWL4030_FORMATS,}, .ops = &twl4030_dai_ops, diff --git a/sound/soc/codecs/twl4030.h b/sound/soc/codecs/twl4030.h index 981ec609495..3441115136f 100644 --- a/sound/soc/codecs/twl4030.h +++ b/sound/soc/codecs/twl4030.h @@ -116,6 +116,17 @@ #define TWL4030_OPTION_1 (1 << 0) #define TWL4030_OPTION_2 (0 << 0) +/* TWL4030_OPTION (0x02) Fields */ + +#define TWL4030_ATXL1_EN (1 << 0) +#define TWL4030_ATXR1_EN (1 << 1) +#define TWL4030_ATXL2_VTXL_EN (1 << 2) +#define TWL4030_ATXR2_VTXR_EN (1 << 3) +#define TWL4030_ARXL1_VRX_EN (1 << 4) +#define TWL4030_ARXR1_EN (1 << 5) +#define TWL4030_ARXL2_EN (1 << 6) +#define TWL4030_ARXR2_EN (1 << 7) + /* TWL4030_REG_MICBIAS_CTL (0x04) Fields */ #define TWL4030_MICBIAS2_CTL 0x40 -- cgit v1.2.3 From a8353a57299f965ca8747b1b062490aef2c9ca50 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 24 Apr 2009 11:03:21 +0300 Subject: ASoC: Beagle: Add support for 4 channel This patch adds support for the four channel TDM mode on Beagle board. Depending on the channel count, the interface needs to be configured differently (I2S for stereo DSP_A for four channels) Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/omap/omap3beagle.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/omap/omap3beagle.c b/sound/soc/omap/omap3beagle.c index 6aa428e07d8..b0cff9f33b7 100644 --- a/sound/soc/omap/omap3beagle.c +++ b/sound/soc/omap/omap3beagle.c @@ -41,23 +41,33 @@ static int omap3beagle_hw_params(struct snd_pcm_substream *substream, 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; + unsigned int fmt; int ret; + switch (params_channels(params)) { + case 2: /* Stereo I2S mode */ + fmt = SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM; + break; + case 4: /* Four channel TDM mode */ + fmt = SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM; + break; + default: + return -EINVAL; + } + /* Set codec DAI configuration */ - ret = snd_soc_dai_set_fmt(codec_dai, - SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM); + ret = snd_soc_dai_set_fmt(codec_dai, fmt); if (ret < 0) { printk(KERN_ERR "can't set codec DAI configuration\n"); return ret; } /* Set cpu DAI configuration */ - ret = snd_soc_dai_set_fmt(cpu_dai, - SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_NB_NF | - SND_SOC_DAIFMT_CBM_CFM); + ret = snd_soc_dai_set_fmt(cpu_dai, fmt); if (ret < 0) { printk(KERN_ERR "can't set cpu DAI configuration\n"); return ret; -- cgit v1.2.3 From 172fd9e26200668ebaf3e1d6d09b36d5d531bfa6 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 24 Apr 2009 16:33:10 +0100 Subject: ASoC: Fix S3C64xx IIS device registration and support both ports The S3C64xx IIS code had a number of problems with device registration. The hardware has two IIS ports of which the driver supported only one at once via a single exported DAI, attempting to identify the DAI to use based on the dev->id of the ASoC platform device. As well as limiting the driver to only supporting one IIS port at once this also meant that the ID of the soc-audio device (or in future the card device) had to match the IIS ID. Fix both problems by converting the driver to register the DAIs based on probing of platform devices registered by the arch/arm code, using those platform devices to interact with the clock API. Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c64xx-i2s.c | 146 ++++++++++++++++++++++++++-------------- sound/soc/s3c24xx/s3c64xx-i2s.h | 2 +- 2 files changed, 98 insertions(+), 50 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.c b/sound/soc/s3c24xx/s3c64xx-i2s.c index 33c5de7e255..a84c4bec56b 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.c +++ b/sound/soc/s3c24xx/s3c64xx-i2s.c @@ -120,36 +120,8 @@ EXPORT_SYMBOL_GPL(s3c64xx_i2s_get_clockrate); static int s3c64xx_i2s_probe(struct platform_device *pdev, struct snd_soc_dai *dai) { - struct device *dev = &pdev->dev; - struct s3c_i2sv2_info *i2s; - int ret; - - dev_dbg(dev, "%s: probing dai %d\n", __func__, pdev->id); - - if (pdev->id < 0 || pdev->id > ARRAY_SIZE(s3c64xx_i2s)) { - dev_err(dev, "id %d out of range\n", pdev->id); - return -EINVAL; - } - - i2s = &s3c64xx_i2s[pdev->id]; - - ret = s3c_i2sv2_probe(pdev, dai, i2s, - pdev->id ? S3C64XX_PA_IIS1 : S3C64XX_PA_IIS0); - if (ret) - return ret; - - i2s->dma_capture = &s3c64xx_i2s_pcm_stereo_in[pdev->id]; - i2s->dma_playback = &s3c64xx_i2s_pcm_stereo_out[pdev->id]; - - i2s->iis_cclk = clk_get(dev, "audio-bus"); - if (IS_ERR(i2s->iis_cclk)) { - dev_err(dev, "failed to get audio-bus"); - iounmap(i2s->regs); - return -ENODEV; - } - /* configure GPIO for i2s port */ - switch (pdev->id) { + switch (dai->id) { case 0: s3c_gpio_cfgpin(S3C64XX_GPD(0), S3C64XX_GPD0_I2S0_CLK); s3c_gpio_cfgpin(S3C64XX_GPD(1), S3C64XX_GPD1_I2S0_CDCLK); @@ -181,35 +153,114 @@ static struct snd_soc_dai_ops s3c64xx_i2s_dai_ops = { .set_sysclk = s3c64xx_i2s_set_sysclk, }; -struct snd_soc_dai s3c64xx_i2s_dai = { - .name = "s3c64xx-i2s", - .id = 0, - .probe = s3c64xx_i2s_probe, - .playback = { - .channels_min = 2, - .channels_max = 2, - .rates = S3C64XX_I2S_RATES, - .formats = S3C64XX_I2S_FMTS, +struct snd_soc_dai s3c64xx_i2s_dai[] = { + { + .name = "s3c64xx-i2s", + .id = 0, + .probe = s3c64xx_i2s_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C64XX_I2S_RATES, + .formats = S3C64XX_I2S_FMTS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C64XX_I2S_RATES, + .formats = S3C64XX_I2S_FMTS, + }, + .ops = &s3c64xx_i2s_dai_ops, }, - .capture = { - .channels_min = 2, - .channels_max = 2, - .rates = S3C64XX_I2S_RATES, - .formats = S3C64XX_I2S_FMTS, + { + .name = "s3c64xx-i2s", + .id = 1, + .probe = s3c64xx_i2s_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C64XX_I2S_RATES, + .formats = S3C64XX_I2S_FMTS, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = S3C64XX_I2S_RATES, + .formats = S3C64XX_I2S_FMTS, + }, + .ops = &s3c64xx_i2s_dai_ops, }, - .ops = &s3c64xx_i2s_dai_ops, }; EXPORT_SYMBOL_GPL(s3c64xx_i2s_dai); +static __devinit int s3c64xx_iis_dev_probe(struct platform_device *pdev) +{ + struct s3c_i2sv2_info *i2s; + struct snd_soc_dai *dai; + int ret; + + if (pdev->id >= ARRAY_SIZE(s3c64xx_i2s)) { + dev_err(&pdev->dev, "id %d out of range\n", pdev->id); + return -EINVAL; + } + + i2s = &s3c64xx_i2s[pdev->id]; + dai = &s3c64xx_i2s_dai[pdev->id]; + dai->dev = &pdev->dev; + + i2s->dma_capture = &s3c64xx_i2s_pcm_stereo_in[pdev->id]; + i2s->dma_playback = &s3c64xx_i2s_pcm_stereo_out[pdev->id]; + + i2s->iis_cclk = clk_get(&pdev->dev, "audio-bus"); + if (IS_ERR(i2s->iis_cclk)) { + dev_err(&pdev->dev, "failed to get audio-bus"); + ret = PTR_ERR(i2s->iis_cclk); + goto err; + } + + ret = s3c_i2sv2_probe(pdev, dai, i2s, + dai->id ? S3C64XX_PA_IIS1 : S3C64XX_PA_IIS0); + if (ret) + goto err_clk; + + ret = snd_soc_register_dai(dai); + if (ret != 0) + goto err_i2sv2; + + return 0; + +err_i2sv2: + /* Not implemented for I2Sv2 core yet */ +err_clk: + clk_put(i2s->iis_cclk); +err: + return ret; +} + +static __devexit int s3c64xx_iis_dev_remove(struct platform_device *pdev) +{ + dev_err(&pdev->dev, "Device removal not yet supported\n"); + return 0; +} + +static struct platform_driver s3c64xx_iis_driver = { + .probe = s3c64xx_iis_dev_probe, + .remove = s3c64xx_iis_dev_remove, + .driver = { + .name = "s3c64xx-iis", + .owner = THIS_MODULE, + }, +}; + static int __init s3c64xx_i2s_init(void) { - return s3c_i2sv2_register_dai(&s3c64xx_i2s_dai); + return platform_driver_register(&s3c64xx_iis_driver); } module_init(s3c64xx_i2s_init); static void __exit s3c64xx_i2s_exit(void) { - snd_soc_unregister_dai(&s3c64xx_i2s_dai); + platform_driver_unregister(&s3c64xx_iis_driver); } module_exit(s3c64xx_i2s_exit); @@ -217,6 +268,3 @@ module_exit(s3c64xx_i2s_exit); MODULE_AUTHOR("Ben Dooks, "); MODULE_DESCRIPTION("S3C64XX I2S SoC Interface"); MODULE_LICENSE("GPL"); - - - diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.h b/sound/soc/s3c24xx/s3c64xx-i2s.h index b7ffe3c38b6..597822a4658 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.h +++ b/sound/soc/s3c24xx/s3c64xx-i2s.h @@ -24,7 +24,7 @@ #define S3C64XX_CLKSRC_PCLK (0) #define S3C64XX_CLKSRC_MUX (1) -extern struct snd_soc_dai s3c64xx_i2s_dai; +extern struct snd_soc_dai s3c64xx_i2s_dai[]; extern unsigned long s3c64xx_i2s_get_clockrate(struct snd_soc_dai *cpu_dai); -- cgit v1.2.3 From 008bec397cdabd22a6f4e4c16a746a86a046f8af Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 24 Apr 2009 16:27:09 +0100 Subject: ASoC: S3C2412: Failing to get the I2S clock is an error Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c2412-i2s.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/s3c2412-i2s.c b/sound/soc/s3c24xx/s3c2412-i2s.c index b7e0b3f0bfc..168a088ba76 100644 --- a/sound/soc/s3c24xx/s3c2412-i2s.c +++ b/sound/soc/s3c24xx/s3c2412-i2s.c @@ -120,7 +120,7 @@ static int s3c2412_i2s_probe(struct platform_device *pdev, s3c2412_i2s.iis_cclk = clk_get(&pdev->dev, "i2sclk"); if (s3c2412_i2s.iis_cclk == NULL) { - pr_debug("failed to get i2sclk clock\n"); + pr_err("failed to get i2sclk clock\n"); iounmap(s3c2412_i2s.regs); return -ENODEV; } -- cgit v1.2.3 From 4bc4d8998a472cd64aa66a4abad3d833be901028 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Apr 2009 14:28:44 +0100 Subject: ASoC: Enforce symmetric rates for S3C64xx I2S interface There is only one LRCLK pin on each interface. Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c64xx-i2s.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.c b/sound/soc/s3c24xx/s3c64xx-i2s.c index a84c4bec56b..c33524803b3 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.c +++ b/sound/soc/s3c24xx/s3c64xx-i2s.c @@ -171,6 +171,7 @@ struct snd_soc_dai s3c64xx_i2s_dai[] = { .formats = S3C64XX_I2S_FMTS, }, .ops = &s3c64xx_i2s_dai_ops, + .symmetric_rates = 1, }, { .name = "s3c64xx-i2s", @@ -189,6 +190,7 @@ struct snd_soc_dai s3c64xx_i2s_dai[] = { .formats = S3C64XX_I2S_FMTS, }, .ops = &s3c64xx_i2s_dai_ops, + .symmetric_rates = 1, }, }; EXPORT_SYMBOL_GPL(s3c64xx_i2s_dai); -- cgit v1.2.3 From 008db442efa542357314593c71ab9966be909855 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Apr 2009 19:17:08 +0100 Subject: ASoC: Include WM8350 register definitions in CODEC header It's expected behaviour for the CODEC header to provide them but the WM8350 doesn't due to having all the registers together under drivers/mfd. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8350.h | 1 + 1 file changed, 1 insertion(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8350.h b/sound/soc/codecs/wm8350.h index d11bd9288cf..d088eb4b88b 100644 --- a/sound/soc/codecs/wm8350.h +++ b/sound/soc/codecs/wm8350.h @@ -13,6 +13,7 @@ #define _WM8350_H #include +#include extern struct snd_soc_dai wm8350_dai; extern struct snd_soc_codec_device soc_codec_dev_wm8350; -- cgit v1.2.3 From 5c556a6e190897a0f1ff14e13722591828412031 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Apr 2009 20:23:19 +0100 Subject: ASoC: s3c-i2s-v2 diagnostic improvements Say what invalid values we're seeing when we see an invalid value and ensure that errors are displayed by default. Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c-i2s-v2.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index ab680aac3fc..aeea49cbe74 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -105,7 +105,9 @@ void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on) break; default: - dev_err(i2s->dev, "TXEN: Invalid MODE in IISMOD\n"); + dev_err(i2s->dev, "TXEN: Invalid MODE %x in IISMOD\n", + mod & S3C2412_IISMOD_MODE_MASK); + break; } writel(con, regs + S3C2412_IISCON); @@ -132,7 +134,9 @@ void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on) break; default: - dev_err(i2s->dev, "TXDIS: Invalid MODE in IISMOD\n"); + dev_err(i2s->dev, "TXDIS: Invalid MODE %xin IISMOD\n", + mod & S3C2412_IISMOD_MODE_MASK); + break; } writel(mod, regs + S3C2412_IISMOD); @@ -175,7 +179,8 @@ void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on) break; default: - dev_err(i2s->dev, "RXEN: Invalid MODE in IISMOD\n"); + dev_err(i2s->dev, "RXEN: Invalid MODE %x in IISMOD\n", + mod & S3C2412_IISMOD_MODE_MASK); } writel(mod, regs + S3C2412_IISMOD); @@ -199,7 +204,8 @@ void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on) break; default: - dev_err(i2s->dev, "RXEN: Invalid MODE in IISMOD\n"); + dev_err(i2s->dev, "RXEN: Invalid MODE %x in IISMOD\n", + mod & S3C2412_IISMOD_MODE_MASK); } writel(con, regs + S3C2412_IISCON); @@ -281,7 +287,7 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, iismod |= IISMOD_MASTER; break; default: - pr_debug("unknwon master/slave format\n"); + pr_err("unknwon master/slave format\n"); return -EINVAL; } @@ -298,7 +304,7 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, iismod |= S3C2412_IISMOD_SDF_IIS; break; default: - pr_debug("Unknown data format\n"); + pr_err("Unknown data format\n"); return -EINVAL; } -- cgit v1.2.3 From a7be4d92d989fc53d840d24cba2ebea9e5ad8480 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Apr 2009 20:24:15 +0100 Subject: ASoC: Use our registration function for S3C64xx Make sure we get the DAI operations initialised. Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c-i2s-v2.c | 18 ++++++------------ sound/soc/s3c24xx/s3c64xx-i2s.c | 2 +- 2 files changed, 7 insertions(+), 13 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index aeea49cbe74..ab680aac3fc 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -105,9 +105,7 @@ void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on) break; default: - dev_err(i2s->dev, "TXEN: Invalid MODE %x in IISMOD\n", - mod & S3C2412_IISMOD_MODE_MASK); - break; + dev_err(i2s->dev, "TXEN: Invalid MODE in IISMOD\n"); } writel(con, regs + S3C2412_IISCON); @@ -134,9 +132,7 @@ void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on) break; default: - dev_err(i2s->dev, "TXDIS: Invalid MODE %xin IISMOD\n", - mod & S3C2412_IISMOD_MODE_MASK); - break; + dev_err(i2s->dev, "TXDIS: Invalid MODE in IISMOD\n"); } writel(mod, regs + S3C2412_IISMOD); @@ -179,8 +175,7 @@ void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on) break; default: - dev_err(i2s->dev, "RXEN: Invalid MODE %x in IISMOD\n", - mod & S3C2412_IISMOD_MODE_MASK); + dev_err(i2s->dev, "RXEN: Invalid MODE in IISMOD\n"); } writel(mod, regs + S3C2412_IISMOD); @@ -204,8 +199,7 @@ void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on) break; default: - dev_err(i2s->dev, "RXEN: Invalid MODE %x in IISMOD\n", - mod & S3C2412_IISMOD_MODE_MASK); + dev_err(i2s->dev, "RXEN: Invalid MODE in IISMOD\n"); } writel(con, regs + S3C2412_IISCON); @@ -287,7 +281,7 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, iismod |= IISMOD_MASTER; break; default: - pr_err("unknwon master/slave format\n"); + pr_debug("unknwon master/slave format\n"); return -EINVAL; } @@ -304,7 +298,7 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, iismod |= S3C2412_IISMOD_SDF_IIS; break; default: - pr_err("Unknown data format\n"); + pr_debug("Unknown data format\n"); return -EINVAL; } diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.c b/sound/soc/s3c24xx/s3c64xx-i2s.c index c33524803b3..1345fbdca70 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.c +++ b/sound/soc/s3c24xx/s3c64xx-i2s.c @@ -225,7 +225,7 @@ static __devinit int s3c64xx_iis_dev_probe(struct platform_device *pdev) if (ret) goto err_clk; - ret = snd_soc_register_dai(dai); + ret = s3c_i2sv2_register_dai(dai); if (ret != 0) goto err_i2sv2; -- cgit v1.2.3 From 0b5e92c5e020ee7437fa5304a8451d6dd08d1a26 Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Mon, 27 Apr 2009 13:49:44 +0000 Subject: ASoC WM8940 Driver Signed-off-by: Jonathan Cameron Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm8940.c | 955 ++++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm8940.h | 104 +++++ 4 files changed, 1065 insertions(+) create mode 100644 sound/soc/codecs/wm8940.c create mode 100644 sound/soc/codecs/wm8940.h (limited to 'sound/soc') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 121d63f13db..1c19ad54a9f 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -35,6 +35,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8753 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8900 if I2C select SND_SOC_WM8903 if I2C + select SND_SOC_WM8940 if I2C select SND_SOC_WM8960 if I2C select SND_SOC_WM8971 if I2C select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI @@ -140,6 +141,9 @@ config SND_SOC_WM8900 config SND_SOC_WM8903 tristate +config SND_SOC_WM8940 + tristate + config SND_SOC_WM8960 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 811696861d3..3d31b6bea83 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -23,6 +23,7 @@ snd-soc-wm8750-objs := wm8750.o snd-soc-wm8753-objs := wm8753.o snd-soc-wm8900-objs := wm8900.o snd-soc-wm8903-objs := wm8903.o +snd-soc-wm8940-objs := wm8940.o snd-soc-wm8960-objs := wm8960.o snd-soc-wm8971-objs := wm8971.o snd-soc-wm8988-objs := wm8988.o @@ -57,6 +58,7 @@ obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o +obj-$(CONFIG_SND_SOC_WM8940) += snd-soc-wm8940.o obj-$(CONFIG_SND_SOC_WM8960) += snd-soc-wm8960.o obj-$(CONFIG_SND_SOC_WM8988) += snd-soc-wm8988.o obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c new file mode 100644 index 00000000000..26987dcd8d5 --- /dev/null +++ b/sound/soc/codecs/wm8940.c @@ -0,0 +1,955 @@ +/* + * wm8940.c -- WM8940 ALSA Soc Audio driver + * + * Author: Jonathan Cameron + * + * Based on wm8510.c + * Copyright 2006 Wolfson Microelectronics PLC. + * Author: Liam Girdwood + * + * 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. + * + * Not currently handled: + * Notch filter control + * AUXMode (inverting vs mixer) + * No means to obtain current gain if alc enabled. + * No use made of gpio + * Fast VMID discharge for power down + * Soft Start + * DLR and ALR Swaps not enabled + * Digital Sidetone not supported + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8940.h" + +struct wm8940_priv { + unsigned int sysclk; + u16 reg_cache[WM8940_CACHEREGNUM]; + struct snd_soc_codec codec; +}; + +static u16 wm8940_reg_defaults[] = { + 0x8940, /* Soft Reset */ + 0x0000, /* Power 1 */ + 0x0000, /* Power 2 */ + 0x0000, /* Power 3 */ + 0x0010, /* Interface Control */ + 0x0000, /* Companding Control */ + 0x0140, /* Clock Control */ + 0x0000, /* Additional Controls */ + 0x0000, /* GPIO Control */ + 0x0002, /* Auto Increment Control */ + 0x0000, /* DAC Control */ + 0x00FF, /* DAC Volume */ + 0, + 0, + 0x0100, /* ADC Control */ + 0x00FF, /* ADC Volume */ + 0x0000, /* Notch Filter 1 Control 1 */ + 0x0000, /* Notch Filter 1 Control 2 */ + 0x0000, /* Notch Filter 2 Control 1 */ + 0x0000, /* Notch Filter 2 Control 2 */ + 0x0000, /* Notch Filter 3 Control 1 */ + 0x0000, /* Notch Filter 3 Control 2 */ + 0x0000, /* Notch Filter 4 Control 1 */ + 0x0000, /* Notch Filter 4 Control 2 */ + 0x0032, /* DAC Limit Control 1 */ + 0x0000, /* DAC Limit Control 2 */ + 0, + 0, + 0, + 0, + 0, + 0, + 0x0038, /* ALC Control 1 */ + 0x000B, /* ALC Control 2 */ + 0x0032, /* ALC Control 3 */ + 0x0000, /* Noise Gate */ + 0x0041, /* PLLN */ + 0x000C, /* PLLK1 */ + 0x0093, /* PLLK2 */ + 0x00E9, /* PLLK3 */ + 0, + 0, + 0x0030, /* ALC Control 4 */ + 0, + 0x0002, /* Input Control */ + 0x0050, /* PGA Gain */ + 0, + 0x0002, /* ADC Boost Control */ + 0, + 0x0002, /* Output Control */ + 0x0000, /* Speaker Mixer Control */ + 0, + 0, + 0, + 0x0079, /* Speaker Volume */ + 0, + 0x0000, /* Mono Mixer Control */ +}; + +static inline unsigned int wm8940_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + if (reg >= ARRAY_SIZE(wm8940_reg_defaults)) + return -1; + + return cache[reg]; +} + +static inline int wm8940_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + + if (reg >= ARRAY_SIZE(wm8940_reg_defaults)) + return -1; + + cache[reg] = value; + + return 0; +} + +static int wm8940_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + int ret; + u8 data[3] = { reg, + (value & 0xff00) >> 8, + (value & 0x00ff) + }; + + wm8940_write_reg_cache(codec, reg, value); + + ret = codec->hw_write(codec->control_data, data, 3); + + if (ret < 0) + return ret; + else if (ret != 3) + return -EIO; + return 0; +} + +static const char *wm8940_companding[] = { "Off", "NC", "u-law", "A-law" }; +static const struct soc_enum wm8940_adc_companding_enum += SOC_ENUM_SINGLE(WM8940_COMPANDINGCTL, 1, 4, wm8940_companding); +static const struct soc_enum wm8940_dac_companding_enum += SOC_ENUM_SINGLE(WM8940_COMPANDINGCTL, 3, 4, wm8940_companding); + +static const char *wm8940_alc_mode_text[] = {"ALC", "Limiter"}; +static const struct soc_enum wm8940_alc_mode_enum += SOC_ENUM_SINGLE(WM8940_ALC3, 8, 2, wm8940_alc_mode_text); + +static const char *wm8940_mic_bias_level_text[] = {"0.9", "0.65"}; +static const struct soc_enum wm8940_mic_bias_level_enum += SOC_ENUM_SINGLE(WM8940_INPUTCTL, 8, 2, wm8940_mic_bias_level_text); + +static const char *wm8940_filter_mode_text[] = {"Audio", "Application"}; +static const struct soc_enum wm8940_filter_mode_enum += SOC_ENUM_SINGLE(WM8940_ADC, 7, 2, wm8940_filter_mode_text); + +DECLARE_TLV_DB_SCALE(wm8940_spk_vol_tlv, -5700, 100, 1); +DECLARE_TLV_DB_SCALE(wm8940_att_tlv, -1000, 1000, 0); +DECLARE_TLV_DB_SCALE(wm8940_pga_vol_tlv, -1200, 75, 0); +DECLARE_TLV_DB_SCALE(wm8940_alc_min_tlv, -1200, 600, 0); +DECLARE_TLV_DB_SCALE(wm8940_alc_max_tlv, 675, 600, 0); +DECLARE_TLV_DB_SCALE(wm8940_alc_tar_tlv, -2250, 50, 0); +DECLARE_TLV_DB_SCALE(wm8940_lim_boost_tlv, 0, 100, 0); +DECLARE_TLV_DB_SCALE(wm8940_lim_thresh_tlv, -600, 100, 0); +DECLARE_TLV_DB_SCALE(wm8940_adc_tlv, -12750, 50, 1); +DECLARE_TLV_DB_SCALE(wm8940_capture_boost_vol_tlv, 0, 2000, 0); + +static const struct snd_kcontrol_new wm8940_snd_controls[] = { + SOC_SINGLE("Digital Loopback Switch", WM8940_COMPANDINGCTL, + 6, 1, 0), + SOC_ENUM("DAC Companding", wm8940_dac_companding_enum), + SOC_ENUM("ADC Companding", wm8940_adc_companding_enum), + + SOC_ENUM("ALC Mode", wm8940_alc_mode_enum), + SOC_SINGLE("ALC Switch", WM8940_ALC1, 8, 1, 0), + SOC_SINGLE_TLV("ALC Capture Max Gain", WM8940_ALC1, + 3, 7, 1, wm8940_alc_max_tlv), + SOC_SINGLE_TLV("ALC Capture Min Gain", WM8940_ALC1, + 0, 7, 0, wm8940_alc_min_tlv), + SOC_SINGLE_TLV("ALC Capture Target", WM8940_ALC2, + 0, 14, 0, wm8940_alc_tar_tlv), + SOC_SINGLE("ALC Capture Hold", WM8940_ALC2, 4, 10, 0), + SOC_SINGLE("ALC Capture Decay", WM8940_ALC3, 4, 10, 0), + SOC_SINGLE("ALC Capture Attach", WM8940_ALC3, 0, 10, 0), + SOC_SINGLE("ALC ZC Switch", WM8940_ALC4, 1, 1, 0), + SOC_SINGLE("ALC Capture Noise Gate Switch", WM8940_NOISEGATE, + 3, 1, 0), + SOC_SINGLE("ALC Capture Noise Gate Threshold", WM8940_NOISEGATE, + 0, 7, 0), + + SOC_SINGLE("DAC Playback Limiter Switch", WM8940_DACLIM1, 8, 1, 0), + SOC_SINGLE("DAC Playback Limiter Attack", WM8940_DACLIM1, 0, 9, 0), + SOC_SINGLE("DAC Playback Limiter Decay", WM8940_DACLIM1, 4, 11, 0), + SOC_SINGLE_TLV("DAC Playback Limiter Threshold", WM8940_DACLIM2, + 4, 9, 1, wm8940_lim_thresh_tlv), + SOC_SINGLE_TLV("DAC Playback Limiter Boost", WM8940_DACLIM2, + 0, 12, 0, wm8940_lim_boost_tlv), + + SOC_SINGLE("Capture PGA ZC Switch", WM8940_PGAGAIN, 7, 1, 0), + SOC_SINGLE_TLV("Capture PGA Volume", WM8940_PGAGAIN, + 0, 63, 0, wm8940_pga_vol_tlv), + SOC_SINGLE_TLV("Digital Playback Volume", WM8940_DACVOL, + 0, 255, 0, wm8940_adc_tlv), + SOC_SINGLE_TLV("Digital Capture Volume", WM8940_ADCVOL, + 0, 255, 0, wm8940_adc_tlv), + SOC_ENUM("Mic Bias Level", wm8940_mic_bias_level_enum), + SOC_SINGLE_TLV("Capture Boost Volue", WM8940_ADCBOOST, + 8, 1, 0, wm8940_capture_boost_vol_tlv), + SOC_SINGLE_TLV("Speaker Playback Volume", WM8940_SPKVOL, + 0, 63, 0, wm8940_spk_vol_tlv), + SOC_SINGLE("Speaker Playback Switch", WM8940_SPKVOL, 6, 1, 1), + + SOC_SINGLE_TLV("Speaker Mixer Line Bypass Volume", WM8940_SPKVOL, + 8, 1, 1, wm8940_att_tlv), + SOC_SINGLE("Speaker Playback ZC Switch", WM8940_SPKVOL, 7, 1, 0), + + SOC_SINGLE("Mono Out Switch", WM8940_MONOMIX, 6, 1, 1), + SOC_SINGLE_TLV("Mono Mixer Line Bypass Volume", WM8940_MONOMIX, + 7, 1, 1, wm8940_att_tlv), + + SOC_SINGLE("High Pass Filter Switch", WM8940_ADC, 8, 1, 0), + SOC_ENUM("High Pass Filter Mode", wm8940_filter_mode_enum), + SOC_SINGLE("High Pass Filter Cut Off", WM8940_ADC, 4, 7, 0), + SOC_SINGLE("ADC Inversion Switch", WM8940_ADC, 0, 1, 0), + SOC_SINGLE("DAC Inversion Switch", WM8940_DAC, 0, 1, 0), + SOC_SINGLE("DAC Auto Mute Switch", WM8940_DAC, 2, 1, 0), + SOC_SINGLE("ZC Timeout Clock Switch", WM8940_ADDCNTRL, 0, 1, 0), +}; + +static const struct snd_kcontrol_new wm8940_speaker_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Bypass Switch", WM8940_SPKMIX, 1, 1, 0), + SOC_DAPM_SINGLE("Aux Playback Switch", WM8940_SPKMIX, 5, 1, 0), + SOC_DAPM_SINGLE("PCM Playback Switch", WM8940_SPKMIX, 0, 1, 0), +}; + +static const struct snd_kcontrol_new wm8940_mono_mixer_controls[] = { + SOC_DAPM_SINGLE("Line Bypass Switch", WM8940_MONOMIX, 1, 1, 0), + SOC_DAPM_SINGLE("Aux Playback Switch", WM8940_MONOMIX, 2, 1, 0), + SOC_DAPM_SINGLE("PCM Playback Switch", WM8940_MONOMIX, 0, 1, 0), +}; + +DECLARE_TLV_DB_SCALE(wm8940_boost_vol_tlv, -1500, 300, 1); +static const struct snd_kcontrol_new wm8940_input_boost_controls[] = { + SOC_DAPM_SINGLE("Mic PGA Switch", WM8940_PGAGAIN, 6, 1, 1), + SOC_DAPM_SINGLE_TLV("Aux Volume", WM8940_ADCBOOST, + 0, 7, 0, wm8940_boost_vol_tlv), + SOC_DAPM_SINGLE_TLV("Mic Volume", WM8940_ADCBOOST, + 4, 7, 0, wm8940_boost_vol_tlv), +}; + +static const struct snd_kcontrol_new wm8940_micpga_controls[] = { + SOC_DAPM_SINGLE("AUX Switch", WM8940_INPUTCTL, 2, 1, 0), + SOC_DAPM_SINGLE("MICP Switch", WM8940_INPUTCTL, 0, 1, 0), + SOC_DAPM_SINGLE("MICN Switch", WM8940_INPUTCTL, 1, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8940_dapm_widgets[] = { + SND_SOC_DAPM_MIXER("Speaker Mixer", WM8940_POWER3, 2, 0, + &wm8940_speaker_mixer_controls[0], + ARRAY_SIZE(wm8940_speaker_mixer_controls)), + SND_SOC_DAPM_MIXER("Mono Mixer", WM8940_POWER3, 3, 0, + &wm8940_mono_mixer_controls[0], + ARRAY_SIZE(wm8940_mono_mixer_controls)), + SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8940_POWER3, 0, 0), + + SND_SOC_DAPM_PGA("SpkN Out", WM8940_POWER3, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("SpkP Out", WM8940_POWER3, 6, 0, NULL, 0), + SND_SOC_DAPM_PGA("Mono Out", WM8940_POWER3, 7, 0, NULL, 0), + SND_SOC_DAPM_OUTPUT("MONOOUT"), + SND_SOC_DAPM_OUTPUT("SPKOUTP"), + SND_SOC_DAPM_OUTPUT("SPKOUTN"), + + SND_SOC_DAPM_PGA("Aux Input", WM8940_POWER1, 6, 0, NULL, 0), + SND_SOC_DAPM_ADC("ADC", "HiFi Capture", WM8940_POWER2, 0, 0), + SND_SOC_DAPM_MIXER("Mic PGA", WM8940_POWER2, 2, 0, + &wm8940_micpga_controls[0], + ARRAY_SIZE(wm8940_micpga_controls)), + SND_SOC_DAPM_MIXER("Boost Mixer", WM8940_POWER2, 4, 0, + &wm8940_input_boost_controls[0], + ARRAY_SIZE(wm8940_input_boost_controls)), + SND_SOC_DAPM_MICBIAS("Mic Bias", WM8940_POWER1, 4, 0), + + SND_SOC_DAPM_INPUT("MICN"), + SND_SOC_DAPM_INPUT("MICP"), + SND_SOC_DAPM_INPUT("AUX"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Mono output mixer */ + {"Mono Mixer", "PCM Playback Switch", "DAC"}, + {"Mono Mixer", "Aux Playback Switch", "Aux Input"}, + {"Mono Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Speaker output mixer */ + {"Speaker Mixer", "PCM Playback Switch", "DAC"}, + {"Speaker Mixer", "Aux Playback Switch", "Aux Input"}, + {"Speaker Mixer", "Line Bypass Switch", "Boost Mixer"}, + + /* Outputs */ + {"Mono Out", NULL, "Mono Mixer"}, + {"MONOOUT", NULL, "Mono Out"}, + {"SpkN Out", NULL, "Speaker Mixer"}, + {"SpkP Out", NULL, "Speaker Mixer"}, + {"SPKOUTN", NULL, "SpkN Out"}, + {"SPKOUTP", NULL, "SpkP Out"}, + + /* Microphone PGA */ + {"Mic PGA", "MICN Switch", "MICN"}, + {"Mic PGA", "MICP Switch", "MICP"}, + {"Mic PGA", "AUX Switch", "AUX"}, + + /* Boost Mixer */ + {"Boost Mixer", "Mic PGA Switch", "Mic PGA"}, + {"Boost Mixer", "Mic Volume", "MICP"}, + {"Boost Mixer", "Aux Volume", "Aux Input"}, + + {"ADC", NULL, "Boost Mixer"}, +}; + +static int wm8940_add_widgets(struct snd_soc_codec *codec) +{ + int ret; + + ret = snd_soc_dapm_new_controls(codec, wm8940_dapm_widgets, + ARRAY_SIZE(wm8940_dapm_widgets)); + if (ret) + goto error_ret; + ret = snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + if (ret) + goto error_ret; + ret = snd_soc_dapm_new_widgets(codec); + +error_ret: + return ret; +} + +#define wm8940_reset(c) wm8940_write(c, WM8940_SOFTRESET, 0); + +static int wm8940_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = wm8940_read_reg_cache(codec, WM8940_IFACE) & 0xFE67; + u16 clk = wm8940_read_reg_cache(codec, WM8940_CLOCK) & 0x1fe; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + clk |= 1; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + wm8940_write(codec, WM8940_CLOCK, clk); + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= (2 << 3); + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= (1 << 3); + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= (3 << 3); + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= (3 << 3) | (1 << 7); + break; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= (1 << 7); + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= (1 << 8); + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= (1 << 8) | (1 << 7); + break; + } + + wm8940_write(codec, WM8940_IFACE, iface); + + return 0; +} + +static int wm8940_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + u16 iface = wm8940_read_reg_cache(codec, WM8940_IFACE) & 0xFD9F; + u16 addcntrl = wm8940_read_reg_cache(codec, WM8940_ADDCNTRL) & 0xFFF1; + u16 companding = wm8940_read_reg_cache(codec, + WM8940_COMPANDINGCTL) & 0xFFDF; + int ret; + + /* LoutR control */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE + && params_channels(params) == 2) + iface |= (1 << 9); + + switch (params_rate(params)) { + case SNDRV_PCM_RATE_8000: + addcntrl |= (0x5 << 1); + break; + case SNDRV_PCM_RATE_11025: + addcntrl |= (0x4 << 1); + break; + case SNDRV_PCM_RATE_16000: + addcntrl |= (0x3 << 1); + break; + case SNDRV_PCM_RATE_22050: + addcntrl |= (0x2 << 1); + break; + case SNDRV_PCM_RATE_32000: + addcntrl |= (0x1 << 1); + break; + case SNDRV_PCM_RATE_44100: + case SNDRV_PCM_RATE_48000: + break; + } + ret = wm8940_write(codec, WM8940_ADDCNTRL, addcntrl); + if (ret) + goto error_ret; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + companding = companding | (1 << 5); + break; + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= (1 << 5); + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= (2 << 5); + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= (3 << 5); + break; + } + ret = wm8940_write(codec, WM8940_COMPANDINGCTL, companding); + if (ret) + goto error_ret; + ret = wm8940_write(codec, WM8940_IFACE, iface); + +error_ret: + return ret; +} + +static int wm8940_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = wm8940_read_reg_cache(codec, WM8940_DAC) & 0xffbf; + + if (mute) + mute_reg |= 0x40; + + return wm8940_write(codec, WM8940_DAC, mute_reg); +} + +static int wm8940_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 val; + u16 pwr_reg = wm8940_read_reg_cache(codec, WM8940_POWER1) & 0x1F0; + int ret = 0; + + switch (level) { + case SND_SOC_BIAS_ON: + /* ensure bufioen and biasen */ + pwr_reg |= (1 << 2) | (1 << 3); + /* Enable thermal shutdown */ + val = wm8940_read_reg_cache(codec, WM8940_OUTPUTCTL); + ret = wm8940_write(codec, WM8940_OUTPUTCTL, val | 0x2); + if (ret) + break; + /* set vmid to 75k */ + ret = wm8940_write(codec, WM8940_POWER1, pwr_reg | 0x1); + break; + case SND_SOC_BIAS_PREPARE: + /* ensure bufioen and biasen */ + pwr_reg |= (1 << 2) | (1 << 3); + ret = wm8940_write(codec, WM8940_POWER1, pwr_reg | 0x1); + break; + case SND_SOC_BIAS_STANDBY: + /* ensure bufioen and biasen */ + pwr_reg |= (1 << 2) | (1 << 3); + /* set vmid to 300k for standby */ + ret = wm8940_write(codec, WM8940_POWER1, pwr_reg | 0x2); + break; + case SND_SOC_BIAS_OFF: + ret = wm8940_write(codec, WM8940_POWER1, pwr_reg); + break; + } + + return ret; +} + +struct pll_ { + unsigned int pre_scale:2; + unsigned int n:4; + unsigned int k; +}; + +static struct pll_ pll_div; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 24) * 10) +static void pll_factors(unsigned int target, unsigned int source) +{ + unsigned long long Kpart; + unsigned int K, Ndiv, Nmod; + /* The left shift ist to avoid accuracy loss when right shifting */ + Ndiv = target / source; + + if (Ndiv > 12) { + source <<= 1; + /* Multiply by 2 */ + pll_div.pre_scale = 0; + Ndiv = target / source; + } else if (Ndiv < 3) { + source >>= 2; + /* Divide by 4 */ + pll_div.pre_scale = 3; + Ndiv = target / source; + } else if (Ndiv < 6) { + source >>= 1; + /* divide by 2 */ + pll_div.pre_scale = 2; + Ndiv = target / source; + } else + pll_div.pre_scale = 1; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "WM8940 N value %d outwith recommended range!d\n", + Ndiv); + + pll_div.n = Ndiv; + Nmod = target % source; + Kpart = FIXED_PLL_SIZE * (long long)Nmod; + + do_div(Kpart, source); + + K = Kpart & 0xFFFFFFFF; + + /* Check if we need to round */ + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + K /= 10; + + pll_div.k = K; +} + +/* Untested at the moment */ +static int wm8940_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, unsigned int freq_in, unsigned int freq_out) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + + /* Turn off PLL */ + reg = wm8940_read_reg_cache(codec, WM8940_POWER1); + wm8940_write(codec, WM8940_POWER1, reg & 0x1df); + + if (freq_in == 0 || freq_out == 0) { + /* Clock CODEC directly from MCLK */ + reg = wm8940_read_reg_cache(codec, WM8940_CLOCK); + wm8940_write(codec, WM8940_CLOCK, reg & 0x0ff); + /* Pll power down */ + wm8940_write(codec, WM8940_PLLN, (1 << 7)); + return 0; + } + + /* Pll is followed by a frequency divide by 4 */ + pll_factors(freq_out*4, freq_in); + if (pll_div.k) + wm8940_write(codec, WM8940_PLLN, + (pll_div.pre_scale << 4) | pll_div.n | (1 << 6)); + else /* No factional component */ + wm8940_write(codec, WM8940_PLLN, + (pll_div.pre_scale << 4) | pll_div.n); + wm8940_write(codec, WM8940_PLLK1, pll_div.k >> 18); + wm8940_write(codec, WM8940_PLLK2, (pll_div.k >> 9) & 0x1ff); + wm8940_write(codec, WM8940_PLLK3, pll_div.k & 0x1ff); + /* Enable the PLL */ + reg = wm8940_read_reg_cache(codec, WM8940_POWER1); + wm8940_write(codec, WM8940_POWER1, reg | 0x020); + + /* Run CODEC from PLL instead of MCLK */ + reg = wm8940_read_reg_cache(codec, WM8940_CLOCK); + wm8940_write(codec, WM8940_CLOCK, reg | 0x100); + + return 0; +} + +static int wm8940_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8940_priv *wm8940 = codec->private_data; + + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + wm8940->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int wm8940_set_dai_clkdiv(struct snd_soc_dai *codec_dai, + int div_id, int div) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 reg; + int ret = 0; + + switch (div_id) { + case WM8940_BCLKDIV: + reg = wm8940_read_reg_cache(codec, WM8940_CLOCK) & 0xFFEF3; + ret = wm8940_write(codec, WM8940_CLOCK, reg | (div << 2)); + break; + case WM8940_MCLKDIV: + reg = wm8940_read_reg_cache(codec, WM8940_CLOCK) & 0xFF1F; + ret = wm8940_write(codec, WM8940_CLOCK, reg | (div << 5)); + break; + case WM8940_OPCLKDIV: + reg = wm8940_read_reg_cache(codec, WM8940_ADDCNTRL) & 0xFFCF; + ret = wm8940_write(codec, WM8940_ADDCNTRL, reg | (div << 4)); + break; + } + return ret; +} + +#define WM8940_RATES SNDRV_PCM_RATE_8000_48000 + +#define WM8940_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ + SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops wm8940_dai_ops = { + .hw_params = wm8940_i2s_hw_params, + .set_sysclk = wm8940_set_dai_sysclk, + .digital_mute = wm8940_mute, + .set_fmt = wm8940_set_dai_fmt, + .set_clkdiv = wm8940_set_dai_clkdiv, + .set_pll = wm8940_set_dai_pll, +}; + +struct snd_soc_dai wm8940_dai = { + .name = "WM8940", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8940_RATES, + .formats = WM8940_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8940_RATES, + .formats = WM8940_FORMATS, + }, + .ops = &wm8940_dai_ops, + .symmetric_rates = 1, +}; +EXPORT_SYMBOL_GPL(wm8940_dai); + +static int wm8940_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + return wm8940_set_bias_level(codec, SND_SOC_BIAS_OFF); +} + +static int wm8940_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + int i; + int ret; + u8 data[3]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware + * Could use auto incremented writes to speed this up + */ + for (i = 0; i < ARRAY_SIZE(wm8940_reg_defaults); i++) { + data[0] = i; + data[1] = (cache[i] & 0xFF00) >> 8; + data[2] = cache[i] & 0x00FF; + ret = codec->hw_write(codec->control_data, data, 3); + if (ret < 0) + goto error_ret; + else if (ret != 3) { + ret = -EIO; + goto error_ret; + } + } + ret = wm8940_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + if (ret) + goto error_ret; + ret = wm8940_set_bias_level(codec, codec->suspend_bias_level); + +error_ret: + return ret; +} + +static struct snd_soc_codec *wm8940_codec; + +static int wm8940_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + + int ret = 0; + + if (wm8940_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = wm8940_codec; + codec = wm8940_codec; + + mutex_init(&codec->mutex); + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + ret = snd_soc_add_controls(codec, wm8940_snd_controls, + ARRAY_SIZE(wm8940_snd_controls)); + if (ret) + goto error_free_pcms; + ret = wm8940_add_widgets(codec); + if (ret) + goto error_free_pcms; + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card: %d\n", ret); + goto error_free_pcms; + } + + return ret; + +error_free_pcms: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + return ret; +} + +static int wm8940_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8940 = { + .probe = wm8940_probe, + .remove = wm8940_remove, + .suspend = wm8940_suspend, + .resume = wm8940_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8940); + +static int wm8940_register(struct wm8940_priv *wm8940) +{ + struct wm8940_setup_data *pdata = wm8940->codec.dev->platform_data; + struct snd_soc_codec *codec = &wm8940->codec; + int ret; + u16 reg; + if (wm8940_codec) { + dev_err(codec->dev, "Another WM8940 is registered\n"); + return -EINVAL; + } + + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = wm8940; + codec->name = "WM8940"; + codec->owner = THIS_MODULE; + codec->read = wm8940_read_reg_cache; + codec->write = wm8940_write; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8940_set_bias_level; + codec->dai = &wm8940_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8940_reg_defaults); + codec->reg_cache = &wm8940->reg_cache; + + memcpy(codec->reg_cache, wm8940_reg_defaults, + sizeof(wm8940_reg_defaults)); + + ret = wm8940_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset\n"); + return ret; + } + + wm8940_dai.dev = codec->dev; + + wm8940_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + ret = wm8940_write(codec, WM8940_POWER1, 0x180); + if (ret < 0) + return ret; + + if (!pdata) + dev_warn(codec->dev, "No platform data supplied\n"); + else { + reg = wm8940_read_reg_cache(codec, WM8940_OUTPUTCTL); + ret = wm8940_write(codec, WM8940_OUTPUTCTL, reg | pdata->vroi); + if (ret < 0) + return ret; + } + + + wm8940_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } + + ret = snd_soc_register_dai(&wm8940_dai); + if (ret) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + return ret; + } + + return 0; +} + +static void wm8940_unregister(struct wm8940_priv *wm8940) +{ + wm8940_set_bias_level(&wm8940->codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dai(&wm8940_dai); + snd_soc_unregister_codec(&wm8940->codec); + kfree(wm8940); + wm8940_codec = NULL; +} + +static int wm8940_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8940_priv *wm8940; + struct snd_soc_codec *codec; + + wm8940 = kzalloc(sizeof *wm8940, GFP_KERNEL); + if (wm8940 == NULL) + return -ENOMEM; + + codec = &wm8940->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + i2c_set_clientdata(i2c, wm8940); + codec->control_data = i2c; + codec->dev = &i2c->dev; + + return wm8940_register(wm8940); +} + +static int wm8940_i2c_remove(struct i2c_client *client) +{ + struct wm8940_priv *wm8940 = i2c_get_clientdata(client); + + wm8940_unregister(wm8940); + + return 0; +} + +static const struct i2c_device_id wm8940_i2c_id[] = { + { "wm8940", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8940_i2c_id); + +static struct i2c_driver wm8940_i2c_driver = { + .driver = { + .name = "WM8940 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = wm8940_i2c_probe, + .remove = __devexit_p(wm8940_i2c_remove), + .id_table = wm8940_i2c_id, +}; + +static int __init wm8940_modinit(void) +{ + int ret; + + ret = i2c_add_driver(&wm8940_i2c_driver); + if (ret) + printk(KERN_ERR "Failed to register WM8940 I2C driver: %d\n", + ret); + return ret; +} +module_init(wm8940_modinit); + +static void __exit wm8940_exit(void) +{ + i2c_del_driver(&wm8940_i2c_driver); +} +module_exit(wm8940_exit); + +MODULE_DESCRIPTION("ASoC WM8940 driver"); +MODULE_AUTHOR("Jonathan Cameron"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8940.h b/sound/soc/codecs/wm8940.h new file mode 100644 index 00000000000..8410eed3ef8 --- /dev/null +++ b/sound/soc/codecs/wm8940.h @@ -0,0 +1,104 @@ +/* + * wm8940.h -- WM8940 Soc Audio driver + * + * 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 _WM8940_H +#define _WM8940_H + +struct wm8940_setup_data { + /* Vref to analogue output resistance */ +#define WM8940_VROI_1K 0 +#define WM8940_VROI_30K 1 + unsigned int vroi:1; +}; +extern struct snd_soc_dai wm8940_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8940; + +/* WM8940 register space */ +#define WM8940_SOFTRESET 0x00 +#define WM8940_POWER1 0x01 +#define WM8940_POWER2 0x02 +#define WM8940_POWER3 0x03 +#define WM8940_IFACE 0x04 +#define WM8940_COMPANDINGCTL 0x05 +#define WM8940_CLOCK 0x06 +#define WM8940_ADDCNTRL 0x07 +#define WM8940_GPIO 0x08 +#define WM8940_CTLINT 0x09 +#define WM8940_DAC 0x0A +#define WM8940_DACVOL 0x0B + +#define WM8940_ADC 0x0E +#define WM8940_ADCVOL 0x0F +#define WM8940_NOTCH1 0x10 +#define WM8940_NOTCH2 0x11 +#define WM8940_NOTCH3 0x12 +#define WM8940_NOTCH4 0x13 +#define WM8940_NOTCH5 0x14 +#define WM8940_NOTCH6 0x15 +#define WM8940_NOTCH7 0x16 +#define WM8940_NOTCH8 0x17 +#define WM8940_DACLIM1 0x18 +#define WM8940_DACLIM2 0x19 + +#define WM8940_ALC1 0x20 +#define WM8940_ALC2 0x21 +#define WM8940_ALC3 0x22 +#define WM8940_NOISEGATE 0x23 +#define WM8940_PLLN 0x24 +#define WM8940_PLLK1 0x25 +#define WM8940_PLLK2 0x26 +#define WM8940_PLLK3 0x27 + +#define WM8940_ALC4 0x2A + +#define WM8940_INPUTCTL 0x2C +#define WM8940_PGAGAIN 0x2D + +#define WM8940_ADCBOOST 0x2F + +#define WM8940_OUTPUTCTL 0x31 +#define WM8940_SPKMIX 0x32 + +#define WM8940_SPKVOL 0x36 + +#define WM8940_MONOMIX 0x38 + +#define WM8940_CACHEREGNUM 0x57 + + +/* Clock divider Id's */ +#define WM8940_BCLKDIV 0 +#define WM8940_MCLKDIV 1 +#define WM8940_OPCLKDIV 2 + +/* MCLK clock dividers */ +#define WM8940_MCLKDIV_1 0 +#define WM8940_MCLKDIV_1_5 1 +#define WM8940_MCLKDIV_2 2 +#define WM8940_MCLKDIV_3 3 +#define WM8940_MCLKDIV_4 4 +#define WM8940_MCLKDIV_6 5 +#define WM8940_MCLKDIV_8 6 +#define WM8940_MCLKDIV_12 7 + +/* BCLK clock dividers */ +#define WM8940_BCLKDIV_1 0 +#define WM8940_BCLKDIV_2 1 +#define WM8940_BCLKDIV_4 2 +#define WM8940_BCLKDIV_8 3 +#define WM8940_BCLKDIV_16 4 +#define WM8940_BCLKDIV_32 5 + +/* PLL Out Dividers */ +#define WM8940_OPCLKDIV_1 0 +#define WM8940_OPCLKDIV_2 1 +#define WM8940_OPCLKDIV_3 2 +#define WM8940_OPCLKDIV_4 3 + +#endif /* _WM8940_H */ + -- cgit v1.2.3 From 9c935386512a3faa1be1c3d81cba38b7259a43f5 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Fri, 24 Apr 2009 15:00:25 +0200 Subject: ASoC: cs4270: fix Master Capture Switch polarity The control modifies the MUTE register, hence the polarity must be inverted. Signed-off-by: Daniel Mack Acked-By: Timur Tabi Signed-off-by: Mark Brown --- sound/soc/codecs/cs4270.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index 7fa09a38762..3c34fe67c3d 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -486,7 +486,7 @@ static const struct snd_kcontrol_new cs4270_snd_controls[] = { SOC_SINGLE("Zero Cross Switch", CS4270_TRANS, 5, 1, 0), SOC_SINGLE("Popguard Switch", CS4270_MODE, 0, 1, 1), SOC_SINGLE("Auto-Mute Switch", CS4270_MUTE, 5, 1, 0), - SOC_DOUBLE("Master Capture Switch", CS4270_MUTE, 3, 4, 1, 0) + SOC_DOUBLE("Master Capture Switch", CS4270_MUTE, 3, 4, 1, 1) }; /* -- cgit v1.2.3 From 1a4ba05ec8369d62c10155a8931e81267bfbd31c Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Fri, 24 Apr 2009 16:37:45 +0200 Subject: ASoC: cs4270: add Master Playback Switch This adds a new control named 'Master Playback Switch' for cs4270 codecs. It is implemented using the new SOC_DOUBLE_EXT macro to catch the put function and store the information about manually set mute controls from userspace. When a manual mute is set, we don't want the soc core to un-mute the outputs. Renamed cs4270_mute() to cs4270_dai_mute() to avoid confusion. Signed-off-by: Daniel Mack Acked-by: Timur Tabi Signed-off-by: Mark Brown --- sound/soc/codecs/cs4270.c | 44 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index 3c34fe67c3d..ece6ed6a844 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -109,6 +109,7 @@ struct cs4270_private { unsigned int mclk; /* Input frequency of the MCLK pin */ unsigned int mode; /* The mode (I2S or left-justified) */ unsigned int slave_mode; + unsigned int manual_mute; }; /** @@ -453,7 +454,7 @@ static int cs4270_hw_params(struct snd_pcm_substream *substream, } /** - * cs4270_mute - enable/disable the CS4270 external mute + * cs4270_dai_mute - enable/disable the CS4270 external mute * @dai: the SOC DAI * @mute: 0 = disable mute, 1 = enable mute * @@ -462,21 +463,52 @@ static int cs4270_hw_params(struct snd_pcm_substream *substream, * board does not have the MUTEA or MUTEB pins connected to such circuitry, * then this function will do nothing. */ -static int cs4270_mute(struct snd_soc_dai *dai, int mute) +static int cs4270_dai_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; + struct cs4270_private *cs4270 = codec->private_data; int reg6; reg6 = snd_soc_read(codec, CS4270_MUTE); if (mute) reg6 |= CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B; - else + else { reg6 &= ~(CS4270_MUTE_DAC_A | CS4270_MUTE_DAC_B); + reg6 |= cs4270->manual_mute; + } return snd_soc_write(codec, CS4270_MUTE, reg6); } +/** + * cs4270_soc_put_mute - put callback for the 'Master Playback switch' + * alsa control. + * @kcontrol: mixer control + * @ucontrol: control element information + * + * This function basically passes the arguments on to the generic + * snd_soc_put_volsw() function and saves the mute information in + * our private data structure. This is because we want to prevent + * cs4270_dai_mute() neglecting the user's decision to manually + * mute the codec's output. + * + * Returns 0 for success. + */ +static int cs4270_soc_put_mute(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct cs4270_private *cs4270 = codec->private_data; + int left = !ucontrol->value.integer.value[0]; + int right = !ucontrol->value.integer.value[1]; + + cs4270->manual_mute = (left ? CS4270_MUTE_DAC_A : 0) | + (right ? CS4270_MUTE_DAC_B : 0); + + return snd_soc_put_volsw(kcontrol, ucontrol); +} + /* A list of non-DAPM controls that the CS4270 supports */ static const struct snd_kcontrol_new cs4270_snd_controls[] = { SOC_DOUBLE_R("Master Playback Volume", @@ -486,7 +518,9 @@ static const struct snd_kcontrol_new cs4270_snd_controls[] = { SOC_SINGLE("Zero Cross Switch", CS4270_TRANS, 5, 1, 0), SOC_SINGLE("Popguard Switch", CS4270_MODE, 0, 1, 1), SOC_SINGLE("Auto-Mute Switch", CS4270_MUTE, 5, 1, 0), - SOC_DOUBLE("Master Capture Switch", CS4270_MUTE, 3, 4, 1, 1) + SOC_DOUBLE("Master Capture Switch", CS4270_MUTE, 3, 4, 1, 1), + SOC_DOUBLE_EXT("Master Playback Switch", CS4270_MUTE, 0, 1, 1, 1, + snd_soc_get_volsw, cs4270_soc_put_mute), }; /* @@ -506,7 +540,7 @@ static struct snd_soc_dai_ops cs4270_dai_ops = { .hw_params = cs4270_hw_params, .set_sysclk = cs4270_set_dai_sysclk, .set_fmt = cs4270_set_dai_fmt, - .digital_mute = cs4270_mute, + .digital_mute = cs4270_dai_mute, }; struct snd_soc_dai cs4270_dai = { -- cgit v1.2.3 From 6be01cfb854818298753bfce65543dbc81d51d5a Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 27 Apr 2009 20:57:42 +0100 Subject: ASoC: Staticise TLV values in WM8940 Signed-off-by: Mark Brown --- sound/soc/codecs/wm8940.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c index 26987dcd8d5..a66dacc7cc8 100644 --- a/sound/soc/codecs/wm8940.c +++ b/sound/soc/codecs/wm8940.c @@ -168,16 +168,16 @@ static const char *wm8940_filter_mode_text[] = {"Audio", "Application"}; static const struct soc_enum wm8940_filter_mode_enum = SOC_ENUM_SINGLE(WM8940_ADC, 7, 2, wm8940_filter_mode_text); -DECLARE_TLV_DB_SCALE(wm8940_spk_vol_tlv, -5700, 100, 1); -DECLARE_TLV_DB_SCALE(wm8940_att_tlv, -1000, 1000, 0); -DECLARE_TLV_DB_SCALE(wm8940_pga_vol_tlv, -1200, 75, 0); -DECLARE_TLV_DB_SCALE(wm8940_alc_min_tlv, -1200, 600, 0); -DECLARE_TLV_DB_SCALE(wm8940_alc_max_tlv, 675, 600, 0); -DECLARE_TLV_DB_SCALE(wm8940_alc_tar_tlv, -2250, 50, 0); -DECLARE_TLV_DB_SCALE(wm8940_lim_boost_tlv, 0, 100, 0); -DECLARE_TLV_DB_SCALE(wm8940_lim_thresh_tlv, -600, 100, 0); -DECLARE_TLV_DB_SCALE(wm8940_adc_tlv, -12750, 50, 1); -DECLARE_TLV_DB_SCALE(wm8940_capture_boost_vol_tlv, 0, 2000, 0); +static DECLARE_TLV_DB_SCALE(wm8940_spk_vol_tlv, -5700, 100, 1); +static DECLARE_TLV_DB_SCALE(wm8940_att_tlv, -1000, 1000, 0); +static DECLARE_TLV_DB_SCALE(wm8940_pga_vol_tlv, -1200, 75, 0); +static DECLARE_TLV_DB_SCALE(wm8940_alc_min_tlv, -1200, 600, 0); +static DECLARE_TLV_DB_SCALE(wm8940_alc_max_tlv, 675, 600, 0); +static DECLARE_TLV_DB_SCALE(wm8940_alc_tar_tlv, -2250, 50, 0); +static DECLARE_TLV_DB_SCALE(wm8940_lim_boost_tlv, 0, 100, 0); +static DECLARE_TLV_DB_SCALE(wm8940_lim_thresh_tlv, -600, 100, 0); +static DECLARE_TLV_DB_SCALE(wm8940_adc_tlv, -12750, 50, 1); +static DECLARE_TLV_DB_SCALE(wm8940_capture_boost_vol_tlv, 0, 2000, 0); static const struct snd_kcontrol_new wm8940_snd_controls[] = { SOC_SINGLE("Digital Loopback Switch", WM8940_COMPANDINGCTL, @@ -253,7 +253,7 @@ static const struct snd_kcontrol_new wm8940_mono_mixer_controls[] = { SOC_DAPM_SINGLE("PCM Playback Switch", WM8940_MONOMIX, 0, 1, 0), }; -DECLARE_TLV_DB_SCALE(wm8940_boost_vol_tlv, -1500, 300, 1); +static DECLARE_TLV_DB_SCALE(wm8940_boost_vol_tlv, -1500, 300, 1); static const struct snd_kcontrol_new wm8940_input_boost_controls[] = { SOC_DAPM_SINGLE("Mic PGA Switch", WM8940_PGAGAIN, 6, 1, 1), SOC_DAPM_SINGLE_TLV("Aux Volume", WM8940_ADCBOOST, -- cgit v1.2.3 From 09aa60df92a9c5ff00e156c0dbc79f166d406a7f Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 29 Apr 2009 18:51:48 +0100 Subject: ASoC: Fix error message formatting in s3c64xx-i2s driver Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c64xx-i2s.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.c b/sound/soc/s3c24xx/s3c64xx-i2s.c index 1345fbdca70..7679f7bc79c 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.c +++ b/sound/soc/s3c24xx/s3c64xx-i2s.c @@ -215,7 +215,7 @@ static __devinit int s3c64xx_iis_dev_probe(struct platform_device *pdev) i2s->iis_cclk = clk_get(&pdev->dev, "audio-bus"); if (IS_ERR(i2s->iis_cclk)) { - dev_err(&pdev->dev, "failed to get audio-bus"); + dev_err(&pdev->dev, "failed to get audio-bus\n"); ret = PTR_ERR(i2s->iis_cclk); goto err; } -- cgit v1.2.3 From 8a0f62b842e2f189e36d9f4c575ee15da9c605ff Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 29 Apr 2009 20:28:47 +0100 Subject: ASoC: Check for supported CPUs when building s3c-i2s-v2 Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c-i2s-v2.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index ab680aac3fc..3b9201cfe43 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -37,6 +37,20 @@ #include "s3c-i2s-v2.h" +#undef S3C_IIS_V2_SUPPORTED + +#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413) +#define S3C_IIS_V2_SUPPORTED +#endif + +#ifdef CONFIG_PLAT_S3C64XX +#define S3C_IIS_V2_SUPPORTED +#endif + +#ifndef S3C_IIS_V2_SUPPORTED +#error Unsupported CPU model +#endif + #define S3C2412_I2S_DEBUG_CON 0 static inline struct s3c_i2sv2_info *to_info(struct snd_soc_dai *cpu_dai) -- cgit v1.2.3 From 51438449e717db54550b4676f38208092eb654da Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 29 Apr 2009 20:30:39 +0100 Subject: ASoC: Make S3C64xx clock export function to return struct clk This makes the interface usable with the s3c-iis-v2 rate calculator and consistent with S3C2412. Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c64xx-i2s.c | 7 +++---- sound/soc/s3c24xx/s3c64xx-i2s.h | 4 +++- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.c b/sound/soc/s3c24xx/s3c64xx-i2s.c index 7679f7bc79c..cb11f783147 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.c +++ b/sound/soc/s3c24xx/s3c64xx-i2s.c @@ -108,14 +108,13 @@ static int s3c64xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, return 0; } - -unsigned long s3c64xx_i2s_get_clockrate(struct snd_soc_dai *dai) +struct clk *s3c64xx_i2s_get_clock(struct snd_soc_dai *dai) { struct s3c_i2sv2_info *i2s = to_info(dai); - return clk_get_rate(i2s->iis_cclk); + return i2s->iis_cclk; } -EXPORT_SYMBOL_GPL(s3c64xx_i2s_get_clockrate); +EXPORT_SYMBOL_GPL(s3c64xx_i2s_get_clock); static int s3c64xx_i2s_probe(struct platform_device *pdev, struct snd_soc_dai *dai) diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.h b/sound/soc/s3c24xx/s3c64xx-i2s.h index 597822a4658..02148cee261 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.h +++ b/sound/soc/s3c24xx/s3c64xx-i2s.h @@ -15,6 +15,8 @@ #ifndef __SND_SOC_S3C24XX_S3C64XX_I2S_H #define __SND_SOC_S3C24XX_S3C64XX_I2S_H __FILE__ +struct clk; + #include "s3c-i2s-v2.h" #define S3C64XX_DIV_BCLK S3C_I2SV2_DIV_BCLK @@ -26,6 +28,6 @@ extern struct snd_soc_dai s3c64xx_i2s_dai[]; -extern unsigned long s3c64xx_i2s_get_clockrate(struct snd_soc_dai *cpu_dai); +extern struct clk *s3c64xx_i2s_get_clock(struct snd_soc_dai *dai); #endif /* __SND_SOC_S3C24XX_S3C64XX_I2S_H */ -- cgit v1.2.3 From 553b1dd58c5cf1abd6d0965041169400a3cff1ad Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 29 Apr 2009 20:29:25 +0100 Subject: ASoC: Fix data format configuration for S3C64xx IISv2 and add 24 bit The data format configuration for S3C64xx IISv2 is completely different to that for S3C24xx. Instead of a single bit configuration in bit 0 of IISMOD we have format selection in bits 13 and 14 and bit clock rate selection in bits 1 and 2. While we're here add support for 24 bit samples in S3C64xx. At some point it may be desirable to expose the bit clock rate selection to users but given the limited configuration options that may not be required. Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c-i2s-v2.c | 22 +++++++++++++++++++++- sound/soc/s3c24xx/s3c64xx-i2s.c | 3 ++- 2 files changed, 23 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index 3b9201cfe43..54f4119e609 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -280,7 +280,7 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, */ #define IISMOD_MASTER_MASK (1 << 11) #define IISMOD_SLAVE (1 << 11) -#define IISMOD_MASTER (0x0) +#define IISMOD_MASTER (0 << 11) #endif switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { @@ -341,6 +341,7 @@ static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream, iismod = readl(i2s->regs + S3C2412_IISMOD); pr_debug("%s: r: IISMOD: %x\n", __func__, iismod); +#if defined(CONFIG_CPU_S3C2412) || defined(CONFIG_CPU_S3C2413) switch (params_format(params)) { case SNDRV_PCM_FORMAT_S8: iismod |= S3C2412_IISMOD_8BIT; @@ -349,6 +350,25 @@ static int s3c2412_i2s_hw_params(struct snd_pcm_substream *substream, iismod &= ~S3C2412_IISMOD_8BIT; break; } +#endif + +#ifdef CONFIG_PLAT_S3C64XX + iismod &= ~0x606; + /* Sample size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + /* 8 bit sample, 16fs BCLK */ + iismod |= 0x2004; + break; + case SNDRV_PCM_FORMAT_S16_LE: + /* 16 bit sample, 32fs BCLK */ + break; + case SNDRV_PCM_FORMAT_S24_LE: + /* 24 bit sample, 48fs BCLK */ + iismod |= 0x4002; + break; + } +#endif writel(iismod, i2s->regs + S3C2412_IISMOD); pr_debug("%s: w: IISMOD: %x\n", __func__, iismod); diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.c b/sound/soc/s3c24xx/s3c64xx-i2s.c index cb11f783147..e0f4a1644f1 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.c +++ b/sound/soc/s3c24xx/s3c64xx-i2s.c @@ -146,7 +146,8 @@ static int s3c64xx_i2s_probe(struct platform_device *pdev, SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) #define S3C64XX_I2S_FMTS \ - (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE) + (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S24_LE) static struct snd_soc_dai_ops s3c64xx_i2s_dai_ops = { .set_sysclk = s3c64xx_i2s_set_sysclk, -- cgit v1.2.3 From 07736d48051869c37838635b41850618aa63b9a7 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 30 Apr 2009 13:13:14 +0100 Subject: ASoC: Fix boot warnings from S3C IISv2 On startup we try to make sure that the port is quiesced but if the port is already stopped then this will generate a warning about the RX/TX mode configuration. Configure the mode before doing the teardown to suppress these warnings. Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c-i2s-v2.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index 54f4119e609..34142c85922 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -573,6 +573,7 @@ int s3c_i2sv2_probe(struct platform_device *pdev, unsigned long base) { struct device *dev = &pdev->dev; + unsigned int iismod; i2s->dev = dev; @@ -594,12 +595,16 @@ int s3c_i2sv2_probe(struct platform_device *pdev, clk_enable(i2s->iis_pclk); + /* Mark ourselves as in TXRX mode so we can run through our cleanup + * process without warnings. */ + iismod = readl(i2s->regs + S3C2412_IISMOD); + iismod |= S3C2412_IISMOD_MODE_TXRX; + writel(iismod, i2s->regs + S3C2412_IISMOD); s3c2412_snd_txctrl(i2s, 0); s3c2412_snd_rxctrl(i2s, 0); return 0; } - EXPORT_SYMBOL_GPL(s3c_i2sv2_probe); #ifdef CONFIG_PM -- cgit v1.2.3 From c86bde54062a4d02c1b58203b7802797e4007a8a Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 30 Apr 2009 13:09:33 +0100 Subject: ASoC: Allow use of resource from the platform device for S3C IISv2 Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c-i2s-v2.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index 34142c85922..cb85498723d 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -580,6 +580,24 @@ int s3c_i2sv2_probe(struct platform_device *pdev, /* record our i2s structure for later use in the callbacks */ dai->private_data = i2s; + if (!base) { + struct resource *res = platform_get_resource(pdev, + IORESOURCE_MEM, + 0); + if (!res) { + dev_err(dev, "Unable to get register resource\n"); + return -ENXIO; + } + + if (!request_mem_region(res->start, resource_size(res), + "s3c64xx-i2s-v4")) { + dev_err(dev, "Unable to request register region\n"); + return -EBUSY; + } + + base = res->start; + } + i2s->regs = ioremap(base, 0x100); if (i2s->regs == NULL) { dev_err(dev, "cannot ioremap registers\n"); -- cgit v1.2.3 From af3ea7bdc77be000f69a41e7c41060f72b5a7111 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 30 Apr 2009 13:13:55 +0100 Subject: ASoC: Display the clock rate used as the basis for rate calculation Aids debugging. Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c-i2s-v2.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index cb85498723d..ad690b2b83a 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -523,6 +523,8 @@ int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info, unsigned int best_rate = 0; unsigned int best_deviation = INT_MAX; + pr_debug("Input clock rate %ldHz\n", clkrate); + if (fstab == NULL) fstab = iis_fs_tab; -- cgit v1.2.3 From 38e43c81a07de8ee8a757a9c93dd3a4937dd35e0 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 30 Apr 2009 13:14:38 +0100 Subject: ASoC: Display S3C IISv2 mode and MS errors by default Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c-i2s-v2.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index ad690b2b83a..bc4e5044e6c 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -295,7 +295,7 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, iismod |= IISMOD_MASTER; break; default: - pr_debug("unknwon master/slave format\n"); + pr_err("unknwon master/slave format\n"); return -EINVAL; } @@ -312,7 +312,7 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, iismod |= S3C2412_IISMOD_SDF_IIS; break; default: - pr_debug("Unknown data format\n"); + pr_err("Unknown data format\n"); return -EINVAL; } -- cgit v1.2.3 From abbc82466967064e4eaafa367fc225a8c803569c Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 30 Apr 2009 13:21:52 +0100 Subject: ASoC: Staticise txctrl and rxctrl for S3C IISv2 They aren't used by anything external and aren't prototyped; if any users appear they can be exported again for them. Also report what modes we have a problem with when we encounter invalid mode configurations. Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c-i2s-v2.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index bc4e5044e6c..972c2768419 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -89,7 +89,7 @@ static inline void dbg_showcon(const char *fn, u32 con) /* Turn on or off the transmission path. */ -void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on) +static void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on) { void __iomem *regs = i2s->regs; u32 fic, con, mod; @@ -119,7 +119,9 @@ void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on) break; default: - dev_err(i2s->dev, "TXEN: Invalid MODE in IISMOD\n"); + dev_err(i2s->dev, "TXEN: Invalid MODE %x in IISMOD\n", + mod & S3C2412_IISMOD_MODE_MASK); + break; } writel(con, regs + S3C2412_IISCON); @@ -146,7 +148,9 @@ void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on) break; default: - dev_err(i2s->dev, "TXDIS: Invalid MODE in IISMOD\n"); + dev_err(i2s->dev, "TXDIS: Invalid MODE %x in IISMOD\n", + mod & S3C2412_IISMOD_MODE_MASK); + break; } writel(mod, regs + S3C2412_IISMOD); @@ -157,9 +161,8 @@ void s3c2412_snd_txctrl(struct s3c_i2sv2_info *i2s, int on) dbg_showcon(__func__, con); pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); } -EXPORT_SYMBOL_GPL(s3c2412_snd_txctrl); -void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on) +static void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on) { void __iomem *regs = i2s->regs; u32 fic, con, mod; @@ -189,7 +192,8 @@ void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on) break; default: - dev_err(i2s->dev, "RXEN: Invalid MODE in IISMOD\n"); + dev_err(i2s->dev, "RXEN: Invalid MODE %x in IISMOD\n", + mod & S3C2412_IISMOD_MODE_MASK); } writel(mod, regs + S3C2412_IISMOD); @@ -213,7 +217,8 @@ void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on) break; default: - dev_err(i2s->dev, "RXEN: Invalid MODE in IISMOD\n"); + dev_err(i2s->dev, "RXDIS: Invalid MODE %x in IISMOD\n", + mod & S3C2412_IISMOD_MODE_MASK); } writel(con, regs + S3C2412_IISCON); @@ -223,7 +228,6 @@ void s3c2412_snd_rxctrl(struct s3c_i2sv2_info *i2s, int on) fic = readl(regs + S3C2412_IISFIC); pr_debug("%s: IIS: CON=%x MOD=%x FIC=%x\n", __func__, con, mod, fic); } -EXPORT_SYMBOL_GPL(s3c2412_snd_rxctrl); /* * Wait for the LR signal to allow synchronisation to the L/R clock -- cgit v1.2.3 From 71437552f2564c0d0c5cc4995045683051c5fe62 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 30 Apr 2009 13:42:04 +0100 Subject: ASoC: Use platform device resource for S3C64xx IISv2 Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c64xx-i2s.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.c b/sound/soc/s3c24xx/s3c64xx-i2s.c index e0f4a1644f1..3c06c401d0f 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.c +++ b/sound/soc/s3c24xx/s3c64xx-i2s.c @@ -220,8 +220,7 @@ static __devinit int s3c64xx_iis_dev_probe(struct platform_device *pdev) goto err; } - ret = s3c_i2sv2_probe(pdev, dai, i2s, - dai->id ? S3C64XX_PA_IIS1 : S3C64XX_PA_IIS0); + ret = s3c_i2sv2_probe(pdev, dai, i2s, 0); if (ret) goto err_clk; -- cgit v1.2.3 From 8970ef47d56fd3db28ee798b9d400caf08abd924 Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Thu, 19 Mar 2009 15:02:34 +0000 Subject: [ARM] S3C24XX: Remove hardware specific registers from DMA calls The S3C24XX DMA API channel configuration registers are being passed values comprised of register values which makes it hard to move the API to cover both the S3C24XX and S3C64XX. These values can be calculated from knowing which device the channel is connected to, so remove them from the two calls s3c2410_dma_config and s3c2410_dma_devconfig. Signed-off-by: Ben Dooks Signed-off-by: Ben Dooks --- sound/soc/s3c24xx/s3c24xx-pcm.c | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/s3c24xx-pcm.c b/sound/soc/s3c24xx/s3c24xx-pcm.c index 169ddad3157..eecfa5eba06 100644 --- a/sound/soc/s3c24xx/s3c24xx-pcm.c +++ b/sound/soc/s3c24xx/s3c24xx-pcm.c @@ -218,24 +218,17 @@ static int s3c24xx_pcm_prepare(struct snd_pcm_substream *substream) * sync to pclk, half-word transfers to the IIS-FIFO. */ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { s3c2410_dma_devconfig(prtd->params->channel, - S3C2410_DMASRC_MEM, S3C2410_DISRCC_INC | - S3C2410_DISRCC_APB, prtd->params->dma_addr); - - s3c2410_dma_config(prtd->params->channel, - prtd->params->dma_size, - S3C2410_DCON_SYNC_PCLK | - S3C2410_DCON_HANDSHAKE); + S3C2410_DMASRC_MEM, + prtd->params->dma_addr); } else { - s3c2410_dma_config(prtd->params->channel, - prtd->params->dma_size, - S3C2410_DCON_HANDSHAKE | - S3C2410_DCON_SYNC_PCLK); - s3c2410_dma_devconfig(prtd->params->channel, - S3C2410_DMASRC_HW, 0x3, - prtd->params->dma_addr); + S3C2410_DMASRC_HW, + prtd->params->dma_addr); } + s3c2410_dma_config(prtd->params->channel, + prtd->params->dma_size); + /* flush the DMA channel */ s3c2410_dma_ctrl(prtd->params->channel, S3C2410_DMAOP_FLUSH); prtd->dma_loaded = 0; -- cgit v1.2.3 From 33f503c96c976fd585dedb76514ca6cb286e60d9 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 2 May 2009 12:24:55 +0100 Subject: ASoC: Use a shared define for AC97 CODEC data formats The AC97 wire format is completely fixed so CODECs don't have any choice about the formats they accept but controllers accept a variety of data formats and render them down onto the bus. Have a shared define so all the CODEC drivers will interoperate with any of our controller drivers. Signed-off-by: Mark Brown --- sound/soc/codecs/ac97.c | 4 ++-- sound/soc/codecs/ad1980.c | 4 ++-- sound/soc/codecs/wm9705.c | 4 ++-- sound/soc/codecs/wm9712.c | 6 +++--- sound/soc/codecs/wm9713.c | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/ac97.c b/sound/soc/codecs/ac97.c index b0d4af145b8..932299bb5d1 100644 --- a/sound/soc/codecs/ac97.c +++ b/sound/soc/codecs/ac97.c @@ -53,13 +53,13 @@ struct snd_soc_dai ac97_dai = { .channels_min = 1, .channels_max = 2, .rates = STD_AC97_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .formats = SND_SOC_STD_AC97_FMTS,}, .capture = { .stream_name = "AC97 Capture", .channels_min = 1, .channels_max = 2, .rates = STD_AC97_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .formats = SND_SOC_STD_AC97_FMTS,}, .ops = &ac97_dai_ops, }; EXPORT_SYMBOL_GPL(ac97_dai); diff --git a/sound/soc/codecs/ad1980.c b/sound/soc/codecs/ad1980.c index ddb3b08ac23..d7440a982d2 100644 --- a/sound/soc/codecs/ad1980.c +++ b/sound/soc/codecs/ad1980.c @@ -137,13 +137,13 @@ struct snd_soc_dai ad1980_dai = { .channels_min = 2, .channels_max = 6, .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, }, + .formats = SND_SOC_STD_AC97_FMTS, }, .capture = { .stream_name = "Capture", .channels_min = 2, .channels_max = 2, .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S16_LE, }, + .formats = SND_SOC_STD_AC97_FMTS, }, }; EXPORT_SYMBOL_GPL(ad1980_dai); diff --git a/sound/soc/codecs/wm9705.c b/sound/soc/codecs/wm9705.c index c2d1a7a18fa..fa88b463e71 100644 --- a/sound/soc/codecs/wm9705.c +++ b/sound/soc/codecs/wm9705.c @@ -282,14 +282,14 @@ struct snd_soc_dai wm9705_dai[] = { .channels_min = 1, .channels_max = 2, .rates = WM9705_AC97_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE, + .formats = SND_SOC_STD_AC97_FMTS, }, .capture = { .stream_name = "HiFi Capture", .channels_min = 1, .channels_max = 2, .rates = WM9705_AC97_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE, + .formats = SND_SOC_STD_AC97_FMTS, }, .ops = &wm9705_dai_ops, }, diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c index 765cf1e7369..550c903f23b 100644 --- a/sound/soc/codecs/wm9712.c +++ b/sound/soc/codecs/wm9712.c @@ -534,13 +534,13 @@ struct snd_soc_dai wm9712_dai[] = { .channels_min = 1, .channels_max = 2, .rates = WM9712_AC97_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .formats = SND_SOC_STD_AC97_FMTS,}, .capture = { .stream_name = "HiFi Capture", .channels_min = 1, .channels_max = 2, .rates = WM9712_AC97_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .formats = SND_SOC_STD_AC97_FMTS,}, .ops = &wm9712_dai_ops_hifi, }, { @@ -550,7 +550,7 @@ struct snd_soc_dai wm9712_dai[] = { .channels_min = 1, .channels_max = 1, .rates = WM9712_AC97_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .formats = SND_SOC_STD_AC97_FMTS,}, .ops = &wm9712_dai_ops_aux, } }; diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c index a6feb784231..d1744e96f30 100644 --- a/sound/soc/codecs/wm9713.c +++ b/sound/soc/codecs/wm9713.c @@ -1040,13 +1040,13 @@ struct snd_soc_dai wm9713_dai[] = { .channels_min = 1, .channels_max = 2, .rates = WM9713_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .formats = SND_SOC_STD_AC97_FMTS,}, .capture = { .stream_name = "HiFi Capture", .channels_min = 1, .channels_max = 2, .rates = WM9713_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .formats = SND_SOC_STD_AC97_FMTS,}, .ops = &wm9713_dai_ops_hifi, }, { @@ -1056,7 +1056,7 @@ struct snd_soc_dai wm9713_dai[] = { .channels_min = 1, .channels_max = 1, .rates = WM9713_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .formats = SND_SOC_STD_AC97_FMTS,}, .ops = &wm9713_dai_ops_aux, }, { -- cgit v1.2.3 From fcd274a345875b05c348ba19bc6b3dd48ecbb7d0 Mon Sep 17 00:00:00 2001 From: "Lopez Cruz, Misael" Date: Thu, 30 Apr 2009 21:47:22 -0500 Subject: ASoC: TWL4030: Add VDL analog bypass This patch adds voice downlink analog bypass switch. It follows the same approach as in other analog bypass switches. DAC switch is moved from 'DAC Voice' to 'Analog Voice Playback Mixer', that will also allow voice DAC to be powered in digital voice loopback (sidetone). Signed-off-by: Misael Lopez Cruz Acked-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index efa1a80b806..efb371f6f01 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -468,6 +468,10 @@ static const struct snd_kcontrol_new twl4030_dapm_abypassr2_control = static const struct snd_kcontrol_new twl4030_dapm_abypassl2_control = SOC_DAPM_SINGLE("Switch", TWL4030_REG_ARXL2_APGA_CTL, 2, 1, 0); +/* Analog bypass for Voice */ +static const struct snd_kcontrol_new twl4030_dapm_abypassv_control = + SOC_DAPM_SINGLE("Switch", TWL4030_REG_VDL_APGA_CTL, 2, 1, 0); + /* Digital bypass gain, 0 mutes the bypass */ static const unsigned int twl4030_dapm_dbypass_tlv[] = { TLV_DB_RANGE_HEAD(2), @@ -585,7 +589,7 @@ static int bypass_event(struct snd_soc_dapm_widget *w, struct soc_mixer_control *m = (struct soc_mixer_control *)w->kcontrols->private_value; struct twl4030_priv *twl4030 = w->codec->private_data; - unsigned char reg; + unsigned char reg, misc; reg = twl4030_read_reg_cache(w->codec, m->reg); @@ -597,14 +601,28 @@ static int bypass_event(struct snd_soc_dapm_widget *w, else twl4030->bypass_state &= ~(1 << (m->reg - TWL4030_REG_ARXL1_APGA_CTL)); + } else if (m->reg == TWL4030_REG_VDL_APGA_CTL) { + /* Analog voice bypass */ + if (reg & (1 << m->shift)) + twl4030->bypass_state |= (1 << 4); + else + twl4030->bypass_state &= ~(1 << 4); } else { /* Digital bypass */ if (reg & (0x7 << m->shift)) - twl4030->bypass_state |= (1 << (m->shift ? 5 : 4)); + twl4030->bypass_state |= (1 << (m->shift ? 6 : 5)); else - twl4030->bypass_state &= ~(1 << (m->shift ? 5 : 4)); + twl4030->bypass_state &= ~(1 << (m->shift ? 6 : 5)); } + /* Enable master analog loopback mode if any analog switch is enabled*/ + misc = twl4030_read_reg_cache(w->codec, TWL4030_REG_MISC_SET_1); + if (twl4030->bypass_state & 0x1F) + misc |= TWL4030_FMLOOP_EN; + else + misc &= ~TWL4030_FMLOOP_EN; + twl4030_write(w->codec, TWL4030_REG_MISC_SET_1, misc); + if (w->codec->bias_level == SND_SOC_BIAS_STANDBY) { if (twl4030->bypass_state) twl4030_codec_mute(w->codec, 0); @@ -935,7 +953,7 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_DAC("DAC Left2", "Left Rear Playback", SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_DAC("DAC Voice", "Voice Playback", - TWL4030_REG_AVDAC_CTL, 4, 0), + SND_SOC_NOPM, 0, 0), /* Analog PGAs */ SND_SOC_DAPM_PGA("ARXR1_APGA", TWL4030_REG_ARXR1_APGA_CTL, @@ -962,6 +980,9 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_SWITCH_E("Left2 Analog Loopback", SND_SOC_NOPM, 0, 0, &twl4030_dapm_abypassl2_control, bypass_event, SND_SOC_DAPM_POST_REG), + SND_SOC_DAPM_SWITCH_E("Voice Analog Loopback", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_abypassv_control, + bypass_event, SND_SOC_DAPM_POST_REG), /* Digital bypasses */ SND_SOC_DAPM_SWITCH_E("Left Digital Loopback", SND_SOC_NOPM, 0, 0, @@ -979,6 +1000,8 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { 2, 0, NULL, 0), SND_SOC_DAPM_MIXER("Analog L2 Playback Mixer", TWL4030_REG_AVDAC_CTL, 3, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Analog Voice Playback Mixer", TWL4030_REG_AVDAC_CTL, + 4, 0, NULL, 0), /* Output MIXER controls */ /* Earpiece */ @@ -1067,13 +1090,13 @@ static const struct snd_soc_dapm_route intercon[] = { {"Analog R1 Playback Mixer", NULL, "DAC Right1"}, {"Analog L2 Playback Mixer", NULL, "DAC Left2"}, {"Analog R2 Playback Mixer", NULL, "DAC Right2"}, + {"Analog Voice Playback Mixer", NULL, "DAC Voice"}, {"ARXL1_APGA", NULL, "Analog L1 Playback Mixer"}, {"ARXR1_APGA", NULL, "Analog R1 Playback Mixer"}, {"ARXL2_APGA", NULL, "Analog L2 Playback Mixer"}, {"ARXR2_APGA", NULL, "Analog R2 Playback Mixer"}, - - {"VDL_APGA", NULL, "DAC Voice"}, + {"VDL_APGA", NULL, "Analog Voice Playback Mixer"}, /* Internal playback routings */ /* Earpiece */ @@ -1169,11 +1192,13 @@ static const struct snd_soc_dapm_route intercon[] = { {"Left1 Analog Loopback", "Switch", "Analog Left Capture Route"}, {"Right2 Analog Loopback", "Switch", "Analog Right Capture Route"}, {"Left2 Analog Loopback", "Switch", "Analog Left Capture Route"}, + {"Voice Analog Loopback", "Switch", "Analog Left Capture Route"}, {"Analog R1 Playback Mixer", NULL, "Right1 Analog Loopback"}, {"Analog L1 Playback Mixer", NULL, "Left1 Analog Loopback"}, {"Analog R2 Playback Mixer", NULL, "Right2 Analog Loopback"}, {"Analog L2 Playback Mixer", NULL, "Left2 Analog Loopback"}, + {"Analog Voice Playback Mixer", NULL, "Voice Analog Loopback"}, /* Digital bypass routes */ {"Right Digital Loopback", "Volume", "TX1 Capture Route"}, -- cgit v1.2.3 From ee8f6894f358b6a04d8190fd78990749de98a498 Mon Sep 17 00:00:00 2001 From: "Lopez Cruz, Misael" Date: Thu, 30 Apr 2009 21:48:08 -0500 Subject: ASoC: TWL4030: Add voice digital loopback: sidetone This patch add voice digital loopback (sidetone) to the twl4030 driver. It mixes voice uplink attenuated (by sidetone gain) with voice downlink when the codec is working in option2 (voice/audio mode). Signed-off-by: Misael Lopez Cruz Acked-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index efb371f6f01..23bae74ebf0 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -491,6 +491,18 @@ static const struct snd_kcontrol_new twl4030_dapm_dbypassr_control = TWL4030_REG_ATX2ARXPGA, 0, 7, 0, twl4030_dapm_dbypass_tlv); +/* + * Voice Sidetone GAIN volume control: + * from -51 to -10 dB in 1 dB steps (mute instead of -51 dB) + */ +static DECLARE_TLV_DB_SCALE(twl4030_dapm_dbypassv_tlv, -5100, 100, 1); + +/* Digital bypass voice: sidetone (VUL -> VDL)*/ +static const struct snd_kcontrol_new twl4030_dapm_dbypassv_control = + SOC_DAPM_SINGLE_TLV("Volume", + TWL4030_REG_VSTPGA, 0, 0x29, 0, + twl4030_dapm_dbypassv_tlv); + static int micpath_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { @@ -607,12 +619,18 @@ static int bypass_event(struct snd_soc_dapm_widget *w, twl4030->bypass_state |= (1 << 4); else twl4030->bypass_state &= ~(1 << 4); + } else if (m->reg == TWL4030_REG_VSTPGA) { + /* Voice digital bypass */ + if (reg) + twl4030->bypass_state |= (1 << 5); + else + twl4030->bypass_state &= ~(1 << 5); } else { /* Digital bypass */ if (reg & (0x7 << m->shift)) - twl4030->bypass_state |= (1 << (m->shift ? 6 : 5)); + twl4030->bypass_state |= (1 << (m->shift ? 7 : 6)); else - twl4030->bypass_state &= ~(1 << (m->shift ? 6 : 5)); + twl4030->bypass_state &= ~(1 << (m->shift ? 7 : 6)); } /* Enable master analog loopback mode if any analog switch is enabled*/ @@ -991,6 +1009,9 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_SWITCH_E("Right Digital Loopback", SND_SOC_NOPM, 0, 0, &twl4030_dapm_dbypassr_control, bypass_event, SND_SOC_DAPM_POST_REG), + SND_SOC_DAPM_SWITCH_E("Voice Digital Loopback", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_dbypassv_control, bypass_event, + SND_SOC_DAPM_POST_REG), SND_SOC_DAPM_MIXER("Analog R1 Playback Mixer", TWL4030_REG_AVDAC_CTL, 0, 0, NULL, 0), @@ -1203,9 +1224,11 @@ static const struct snd_soc_dapm_route intercon[] = { /* Digital bypass routes */ {"Right Digital Loopback", "Volume", "TX1 Capture Route"}, {"Left Digital Loopback", "Volume", "TX1 Capture Route"}, + {"Voice Digital Loopback", "Volume", "TX2 Capture Route"}, {"Analog R2 Playback Mixer", NULL, "Right Digital Loopback"}, {"Analog L2 Playback Mixer", NULL, "Left Digital Loopback"}, + {"Analog Voice Playback Mixer", NULL, "Voice Digital Loopback"}, }; -- cgit v1.2.3 From a195b51bc5abc745f12b7b2fe0e3422f55c1953f Mon Sep 17 00:00:00 2001 From: Jonathan Cameron Date: Mon, 4 May 2009 14:54:11 +0000 Subject: ASoC: IMote2 ASoC Support This patch adds the ASoC side of the board support for the Crossbow IMB400 daughter board. Thanks to Crossbow for considerable assistance. Signed-off-by: Jonathan Cameron Signed-off-by: Mark Brown --- sound/soc/pxa/Kconfig | 9 ++++ sound/soc/pxa/Makefile | 2 + sound/soc/pxa/imote2.c | 114 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 125 insertions(+) create mode 100644 sound/soc/pxa/imote2.c (limited to 'sound/soc') diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index ad8a10fe629..eb75a1c061a 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -134,3 +134,12 @@ config SND_PXA2XX_SOC_MIOA701 help Say Y if you want to add support for SoC audio on the MIO A701. + +config SND_PXA2XX_SOC_IMOTE2 + tristate "SoC Audio support for IMote 2" + depends on SND_PXA2XX_SOC && MACH_INTELMOTE2 + select SND_PXA2XX_SOC_I2S + select SND_SOC_WM8940 + help + Say Y if you want to add support for SoC audio on the + IMote 2. diff --git a/sound/soc/pxa/Makefile b/sound/soc/pxa/Makefile index 4b90c3ccae4..6e096b48033 100644 --- a/sound/soc/pxa/Makefile +++ b/sound/soc/pxa/Makefile @@ -22,6 +22,7 @@ snd-soc-palm27x-objs := palm27x.o snd-soc-zylonite-objs := zylonite.o snd-soc-magician-objs := magician.o snd-soc-mioa701-objs := mioa701_wm9713.o +snd-soc-imote2-objs := imote2.o obj-$(CONFIG_SND_PXA2XX_SOC_CORGI) += snd-soc-corgi.o obj-$(CONFIG_SND_PXA2XX_SOC_POODLE) += snd-soc-poodle.o @@ -35,3 +36,4 @@ obj-$(CONFIG_SND_PXA2XX_SOC_PALM27X) += snd-soc-palm27x.o obj-$(CONFIG_SND_PXA2XX_SOC_MAGICIAN) += snd-soc-magician.o obj-$(CONFIG_SND_PXA2XX_SOC_MIOA701) += snd-soc-mioa701.o obj-$(CONFIG_SND_SOC_ZYLONITE) += snd-soc-zylonite.o +obj-$(CONFIG_SND_PXA2XX_SOC_IMOTE2) += snd-soc-imote2.o diff --git a/sound/soc/pxa/imote2.c b/sound/soc/pxa/imote2.c new file mode 100644 index 00000000000..405587a0116 --- /dev/null +++ b/sound/soc/pxa/imote2.c @@ -0,0 +1,114 @@ + +#include +#include + +#include + +#include "../codecs/wm8940.h" +#include "pxa2xx-i2s.h" +#include "pxa2xx-pcm.h" + +static int imote2_asoc_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; + unsigned int clk = 0; + int ret; + + switch (params_rate(params)) { + case 8000: + case 16000: + case 48000: + case 96000: + clk = 12288000; + break; + case 11025: + case 22050: + case 44100: + clk = 11289600; + break; + } + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* CPU should be clock master */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(codec_dai, 0, clk, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set the I2S system clock as input (unused) */ + ret = snd_soc_dai_set_sysclk(cpu_dai, PXA2XX_I2S_SYSCLK, clk, + SND_SOC_CLOCK_OUT); + + return ret; +} + +static struct snd_soc_ops imote2_asoc_ops = { + .hw_params = imote2_asoc_hw_params, +}; + +static struct snd_soc_dai_link imote2_dai = { + .name = "WM8940", + .stream_name = "WM8940", + .cpu_dai = &pxa_i2s_dai, + .codec_dai = &wm8940_dai, + .ops = &imote2_asoc_ops, +}; + +static struct snd_soc_card snd_soc_imote2 = { + .name = "Imote2", + .platform = &pxa2xx_soc_platform, + .dai_link = &imote2_dai, + .num_links = 1, +}; + +static struct snd_soc_device imote2_snd_devdata = { + .card = &snd_soc_imote2, + .codec_dev = &soc_codec_dev_wm8940, +}; + +static struct platform_device *imote2_snd_device; + +static int __init imote2_asoc_init(void) +{ + int ret; + + if (!machine_is_intelmote2()) + return -ENODEV; + imote2_snd_device = platform_device_alloc("soc-audio", -1); + if (!imote2_snd_device) + return -ENOMEM; + + platform_set_drvdata(imote2_snd_device, &imote2_snd_devdata); + imote2_snd_devdata.dev = &imote2_snd_device->dev; + ret = platform_device_add(imote2_snd_device); + if (ret) + platform_device_put(imote2_snd_device); + + return ret; +} +module_init(imote2_asoc_init); + +static void __exit imote2_asoc_exit(void) +{ + platform_device_unregister(imote2_snd_device); +} +module_exit(imote2_asoc_exit); + +MODULE_AUTHOR("Jonathan Cameron"); +MODULE_DESCRIPTION("ALSA SoC Imote 2"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 376f7839b72ec526173cafb5d8eadfc61e2effdf Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Tue, 5 May 2009 08:55:47 +0300 Subject: ASoC: TWL4030: Add VIBRA output This patch adds support for the VIBRA output on TWL4030 codec. The VIBRA output can be driven with audio data or with local vibrator driver. Add the needed DAPM elements and routes for the VIBRA output and controls for the VIBRA driver configuration. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 61 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 23bae74ebf0..1a00e4b2039 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -396,6 +396,31 @@ static const struct soc_enum twl4030_handsfreer_enum = static const struct snd_kcontrol_new twl4030_dapm_handsfreer_control = SOC_DAPM_ENUM("Route", twl4030_handsfreer_enum); +/* Vibra */ +/* Vibra audio path selection */ +static const char *twl4030_vibra_texts[] = + {"AudioL1", "AudioR1", "AudioL2", "AudioR2"}; + +static const struct soc_enum twl4030_vibra_enum = + SOC_ENUM_SINGLE(TWL4030_REG_VIBRA_CTL, 2, + ARRAY_SIZE(twl4030_vibra_texts), + twl4030_vibra_texts); + +static const struct snd_kcontrol_new twl4030_dapm_vibra_control = +SOC_DAPM_ENUM("Route", twl4030_vibra_enum); + +/* Vibra path selection: local vibrator (PWM) or audio driven */ +static const char *twl4030_vibrapath_texts[] = + {"Local vibrator", "Audio"}; + +static const struct soc_enum twl4030_vibrapath_enum = + SOC_ENUM_SINGLE(TWL4030_REG_VIBRA_CTL, 4, + ARRAY_SIZE(twl4030_vibrapath_texts), + twl4030_vibrapath_texts); + +static const struct snd_kcontrol_new twl4030_dapm_vibrapath_control = +SOC_DAPM_ENUM("Route", twl4030_vibrapath_enum); + /* Left analog microphone selection */ static const char *twl4030_analoglmic_texts[] = {"Off", "Main mic", "Headset mic", "AUXL", "Carkit mic"}; @@ -867,6 +892,26 @@ static const struct soc_enum twl4030_rampdelay_enum = ARRAY_SIZE(twl4030_rampdelay_texts), twl4030_rampdelay_texts); +/* Vibra H-bridge direction mode */ +static const char *twl4030_vibradirmode_texts[] = { + "Vibra H-bridge direction", "Audio data MSB", +}; + +static const struct soc_enum twl4030_vibradirmode_enum = + SOC_ENUM_SINGLE(TWL4030_REG_VIBRA_CTL, 5, + ARRAY_SIZE(twl4030_vibradirmode_texts), + twl4030_vibradirmode_texts); + +/* Vibra H-bridge direction */ +static const char *twl4030_vibradir_texts[] = { + "Positive polarity", "Negative polarity", +}; + +static const struct soc_enum twl4030_vibradir_enum = + SOC_ENUM_SINGLE(TWL4030_REG_VIBRA_CTL, 1, + ARRAY_SIZE(twl4030_vibradir_texts), + twl4030_vibradir_texts); + static const struct snd_kcontrol_new twl4030_snd_controls[] = { /* Common playback gain controls */ SOC_DOUBLE_R_TLV("DAC1 Digital Fine Playback Volume", @@ -933,6 +978,9 @@ static const struct snd_kcontrol_new twl4030_snd_controls[] = { 0, 3, 5, 0, input_gain_tlv), SOC_ENUM("HS ramp delay", twl4030_rampdelay_enum), + + SOC_ENUM("Vibra H-bridge mode", twl4030_vibradirmode_enum), + SOC_ENUM("Vibra H-bridge direction", twl4030_vibradir_enum), }; static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { @@ -960,6 +1008,7 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_OUTPUT("CARKITR"), SND_SOC_DAPM_OUTPUT("HFL"), SND_SOC_DAPM_OUTPUT("HFR"), + SND_SOC_DAPM_OUTPUT("VIBRA"), /* DACs */ SND_SOC_DAPM_DAC("DAC Right1", "Right Front Playback", @@ -1060,6 +1109,11 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_MUX_E("HandsfreeR Mux", TWL4030_REG_HFR_CTL, 5, 0, &twl4030_dapm_handsfreer_control, handsfree_event, SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), + /* Vibra */ + SND_SOC_DAPM_MUX("Vibra Mux", TWL4030_REG_VIBRA_CTL, 0, 0, + &twl4030_dapm_vibra_control), + SND_SOC_DAPM_MUX("Vibra Route", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_vibrapath_control), /* Introducing four virtual ADC, since TWL4030 have four channel for capture */ @@ -1161,6 +1215,11 @@ static const struct snd_soc_dapm_route intercon[] = { {"HandsfreeR Mux", "AudioR1", "ARXR1_APGA"}, {"HandsfreeR Mux", "AudioR2", "ARXR2_APGA"}, {"HandsfreeR Mux", "AudioL2", "ARXL2_APGA"}, + /* Vibra */ + {"Vibra Mux", "AudioL1", "DAC Left1"}, + {"Vibra Mux", "AudioR1", "DAC Right1"}, + {"Vibra Mux", "AudioL2", "DAC Left2"}, + {"Vibra Mux", "AudioR2", "DAC Right2"}, /* outputs */ {"OUTL", NULL, "ARXL2_APGA"}, @@ -1174,6 +1233,8 @@ static const struct snd_soc_dapm_route intercon[] = { {"CARKITR", NULL, "CarkitR Mixer"}, {"HFL", NULL, "HandsfreeL Mux"}, {"HFR", NULL, "HandsfreeR Mux"}, + {"Vibra Route", "Audio", "Vibra Mux"}, + {"VIBRA", NULL, "Vibra Route"}, /* Capture path */ {"Analog Left Capture Route", "Main mic", "MAINMIC"}, -- cgit v1.2.3 From 80ab8817bf9b740df1f0778c41875e93151409bf Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Tue, 5 May 2009 11:25:00 +0200 Subject: ASoC: cs4270: introduce CS4270_I2C_INCR Replace the magic 0x80 value with a suitable macro definition. Signed-off-by: Daniel Mack Acked-by: Timur Tabi Signed-off-by: Mark Brown --- sound/soc/codecs/cs4270.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index ece6ed6a844..153124b2e3b 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -56,6 +56,7 @@ #define CS4270_FIRSTREG 0x01 #define CS4270_LASTREG 0x08 #define CS4270_NUMREGS (CS4270_LASTREG - CS4270_FIRSTREG + 1) +#define CS4270_I2C_INCR 0x80 /* Bit masks for the CS4270 registers */ #define CS4270_CHIPID_ID 0xF0 @@ -296,7 +297,7 @@ static int cs4270_fill_cache(struct snd_soc_codec *codec) s32 length; length = i2c_smbus_read_i2c_block_data(i2c_client, - CS4270_FIRSTREG | 0x80, CS4270_NUMREGS, cache); + CS4270_FIRSTREG | CS4270_I2C_INCR, CS4270_NUMREGS, cache); if (length != CS4270_NUMREGS) { dev_err(codec->dev, "i2c read failure, addr=0x%x\n", -- cgit v1.2.3 From 5e7c03442574ed0376c0621bfb0c477d79c12c71 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Wed, 6 May 2009 01:26:01 +0200 Subject: ASoC: cs4270: add power management support Signed-off-by: Daniel Mack Acked-by: Timur Tabi Signed-off-by: Mark Brown --- sound/soc/codecs/cs4270.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index 153124b2e3b..a32b8226c8a 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -18,7 +18,7 @@ * - The machine driver's 'startup' function must call * cs4270_set_dai_sysclk() with the value of MCLK. * - Only I2S and left-justified modes are supported - * - Power management is not supported + * - Power management is supported */ #include @@ -27,6 +27,7 @@ #include #include #include +#include #include "cs4270.h" @@ -65,6 +66,8 @@ #define CS4270_PWRCTL_PDN_ADC 0x20 #define CS4270_PWRCTL_PDN_DAC 0x02 #define CS4270_PWRCTL_PDN 0x01 +#define CS4270_PWRCTL_PDN_ALL \ + (CS4270_PWRCTL_PDN_ADC | CS4270_PWRCTL_PDN_DAC | CS4270_PWRCTL_PDN) #define CS4270_MODE_SPEED_MASK 0x30 #define CS4270_MODE_1X 0x00 #define CS4270_MODE_2X 0x10 @@ -788,6 +791,57 @@ static struct i2c_device_id cs4270_id[] = { }; MODULE_DEVICE_TABLE(i2c, cs4270_id); +#ifdef CONFIG_PM + +/* This suspend/resume implementation can handle both - a simple standby + * where the codec remains powered, and a full suspend, where the voltage + * domain the codec is connected to is teared down and/or any other hardware + * reset condition is asserted. + * + * The codec's own power saving features are enabled in the suspend callback, + * and all registers are written back to the hardware when resuming. + */ + +static int cs4270_i2c_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct cs4270_private *cs4270 = i2c_get_clientdata(client); + struct snd_soc_codec *codec = &cs4270->codec; + int reg = snd_soc_read(codec, CS4270_PWRCTL) | CS4270_PWRCTL_PDN_ALL; + + return snd_soc_write(codec, CS4270_PWRCTL, reg); +} + +static int cs4270_i2c_resume(struct i2c_client *client) +{ + struct cs4270_private *cs4270 = i2c_get_clientdata(client); + struct snd_soc_codec *codec = &cs4270->codec; + int reg; + + /* In case the device was put to hard reset during sleep, we need to + * wait 500ns here before any I2C communication. */ + ndelay(500); + + /* first restore the entire register cache ... */ + for (reg = CS4270_FIRSTREG; reg <= CS4270_LASTREG; reg++) { + u8 val = snd_soc_read(codec, reg); + + if (i2c_smbus_write_byte_data(client, reg, val)) { + dev_err(codec->dev, "i2c write failed\n"); + return -EIO; + } + } + + /* ... then disable the power-down bits */ + reg = snd_soc_read(codec, CS4270_PWRCTL); + reg &= ~CS4270_PWRCTL_PDN_ALL; + + return snd_soc_write(codec, CS4270_PWRCTL, reg); +} +#else +#define cs4270_i2c_suspend NULL +#define cs4270_i2c_resume NULL +#endif /* CONFIG_PM */ + /* * cs4270_i2c_driver - I2C device identification * @@ -802,6 +856,8 @@ static struct i2c_driver cs4270_i2c_driver = { .id_table = cs4270_id, .probe = cs4270_i2c_probe, .remove = cs4270_i2c_remove, + .suspend = cs4270_i2c_suspend, + .resume = cs4270_i2c_resume, }; /* -- cgit v1.2.3 From c198d811812417961582d4e25360372ca1eccdae Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 7 May 2009 14:32:00 +0300 Subject: ASoC: TWL4030: Fix typo in twl4030_codec_mute function Copy-paste error: TWL4030_PRECKL_GAIN >> TWL4030_PRECKR_GAIN It has not caused problems, since TWL4030_PRECKL_GAIN == TWL4030_PRECKR_GAIN == 0x30 Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 1a00e4b2039..fd392c65f47 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -237,7 +237,7 @@ static void twl4030_codec_mute(struct snd_soc_codec *codec, int mute) TWL4030_REG_PRECKL_CTL); reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PRECKR_CTL); twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, - reg_val & (~TWL4030_PRECKL_GAIN), + reg_val & (~TWL4030_PRECKR_GAIN), TWL4030_REG_PRECKR_CTL); /* Disable PLL */ -- cgit v1.2.3 From b4df0a6c9d88cfff77c73d33873cd60f9ab909b6 Mon Sep 17 00:00:00 2001 From: Sergey Lapin Date: Fri, 8 May 2009 19:19:41 +0400 Subject: ASoC: AFEB9260 driver ASoC driver for AT91SAM9260-based AFEB9260 board Signed-off-by: Sergey Lapin Signed-off-by: Mark Brown --- sound/soc/atmel/Kconfig | 8 ++ sound/soc/atmel/Makefile | 1 + sound/soc/atmel/snd-soc-afeb9260.c | 203 +++++++++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+) create mode 100644 sound/soc/atmel/snd-soc-afeb9260.c (limited to 'sound/soc') diff --git a/sound/soc/atmel/Kconfig b/sound/soc/atmel/Kconfig index a608d7009db..e720d5e6f04 100644 --- a/sound/soc/atmel/Kconfig +++ b/sound/soc/atmel/Kconfig @@ -41,3 +41,11 @@ config SND_AT32_SOC_PLAYPAQ_SLAVE and FRAME signals on the PlayPaq. Unless you want to play with the AT32 as the SSC master, you probably want to say N here, as this will give you better sound quality. + +config SND_AT91_SOC_AFEB9260 + tristate "SoC Audio support for AFEB9260 board" + depends on ARCH_AT91 && MACH_AFEB9260 && SND_ATMEL_SOC + select SND_ATMEL_SOC_SSC + select SND_SOC_TLV320AIC23 + help + Say Y here to support sound on AFEB9260 board. diff --git a/sound/soc/atmel/Makefile b/sound/soc/atmel/Makefile index f54a7cc68e6..e7ea56bd5f8 100644 --- a/sound/soc/atmel/Makefile +++ b/sound/soc/atmel/Makefile @@ -13,3 +13,4 @@ snd-soc-playpaq-objs := playpaq_wm8510.o obj-$(CONFIG_SND_AT91_SOC_SAM9G20_WM8731) += snd-soc-sam9g20-wm8731.o obj-$(CONFIG_SND_AT32_SOC_PLAYPAQ) += snd-soc-playpaq.o +obj-$(CONFIG_SND_AT91_SOC_AFEB9260) += snd-soc-afeb9260.o diff --git a/sound/soc/atmel/snd-soc-afeb9260.c b/sound/soc/atmel/snd-soc-afeb9260.c new file mode 100644 index 00000000000..23349de2731 --- /dev/null +++ b/sound/soc/atmel/snd-soc-afeb9260.c @@ -0,0 +1,203 @@ +/* + * afeb9260.c -- SoC audio for AFEB9260 + * + * Copyright (C) 2009 Sergey Lapin + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../codecs/tlv320aic23.h" +#include "atmel-pcm.h" +#include "atmel_ssc_dai.h" + +#define CODEC_CLOCK 12000000 + +static int afeb9260_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 err; + + /* Set codec DAI configuration */ + err = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S| + SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_CBM_CFM); + if (err < 0) { + printk(KERN_ERR "can't set codec DAI configuration\n"); + return err; + } + + /* Set cpu DAI configuration */ + err = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_IF | + SND_SOC_DAIFMT_CBM_CFM); + if (err < 0) { + printk(KERN_ERR "can't set cpu DAI configuration\n"); + return err; + } + + /* Set the codec system clock for DAC and ADC */ + err = + snd_soc_dai_set_sysclk(codec_dai, 0, CODEC_CLOCK, SND_SOC_CLOCK_IN); + + if (err < 0) { + printk(KERN_ERR "can't set codec system clock\n"); + return err; + } + + return err; +} + +static struct snd_soc_ops afeb9260_ops = { + .hw_params = afeb9260_hw_params, +}; + +static const struct snd_soc_dapm_widget tlv320aic23_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + {"Headphone Jack", NULL, "LHPOUT"}, + {"Headphone Jack", NULL, "RHPOUT"}, + + {"LLINEIN", NULL, "Line In"}, + {"RLINEIN", NULL, "Line In"}, + + {"MICIN", NULL, "Mic Jack"}, +}; + +static int afeb9260_tlv320aic23_init(struct snd_soc_codec *codec) +{ + + /* Add afeb9260 specific widgets */ + snd_soc_dapm_new_controls(codec, tlv320aic23_dapm_widgets, + ARRAY_SIZE(tlv320aic23_dapm_widgets)); + + /* Set up afeb9260 specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + snd_soc_dapm_enable_pin(codec, "Headphone Jack"); + snd_soc_dapm_enable_pin(codec, "Line In"); + snd_soc_dapm_enable_pin(codec, "Mic Jack"); + + snd_soc_dapm_sync(codec); + + return 0; +} + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link afeb9260_dai = { + .name = "TLV320AIC23", + .stream_name = "AIC23", + .cpu_dai = &atmel_ssc_dai[0], + .codec_dai = &tlv320aic23_dai, + .init = afeb9260_tlv320aic23_init, + .ops = &afeb9260_ops, +}; + +/* Audio machine driver */ +static struct snd_soc_card snd_soc_machine_afeb9260 = { + .name = "AFEB9260", + .platform = &atmel_soc_platform, + .dai_link = &afeb9260_dai, + .num_links = 1, +}; + +/* Audio subsystem */ +static struct snd_soc_device afeb9260_snd_devdata = { + .card = &snd_soc_machine_afeb9260, + .codec_dev = &soc_codec_dev_tlv320aic23, +}; + +static struct platform_device *afeb9260_snd_device; + +static int __init afeb9260_soc_init(void) +{ + int err; + struct device *dev; + struct atmel_ssc_info *ssc_p = afeb9260_dai.cpu_dai->private_data; + struct ssc_device *ssc = NULL; + + if (!(machine_is_afeb9260())) + return -ENODEV; + + ssc = ssc_request(0); + if (IS_ERR(ssc)) { + printk(KERN_ERR "ASoC: Failed to request SSC 0\n"); + err = PTR_ERR(ssc); + ssc = NULL; + goto err_ssc; + } + ssc_p->ssc = ssc; + + afeb9260_snd_device = platform_device_alloc("soc-audio", -1); + if (!afeb9260_snd_device) { + printk(KERN_ERR "ASoC: Platform device allocation failed\n"); + return -ENOMEM; + } + + platform_set_drvdata(afeb9260_snd_device, &afeb9260_snd_devdata); + afeb9260_snd_devdata.dev = &afeb9260_snd_device->dev; + err = platform_device_add(afeb9260_snd_device); + if (err) + goto err1; + + dev = &afeb9260_snd_device->dev; + + return 0; +err1: + platform_device_del(afeb9260_snd_device); + platform_device_put(afeb9260_snd_device); +err_ssc: + return err; + +} + +static void __exit afeb9260_soc_exit(void) +{ + platform_device_unregister(afeb9260_snd_device); +} + +module_init(afeb9260_soc_init); +module_exit(afeb9260_soc_exit); + +MODULE_AUTHOR("Sergey Lapin "); +MODULE_DESCRIPTION("ALSA SoC for AFEB9260"); +MODULE_LICENSE("GPL"); + -- cgit v1.2.3 From 151ab22cf71b7a1b9dd696d65a1a41e13d90cd00 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 9 May 2009 16:22:58 +0100 Subject: ASoC: Fix up CODEC DAI formats for big endian CPUs ASoC uses the standard ALSA data format definitions to specify the wire format used between the CPU and CODEC. Since the ALSA data formats all include the endianess of the data but this information is not relevant by the time the data has been encoded onto the serial link to the CODEC this means that either all the CODEC drivers need to declare both big and little endian variants or the core needs to fix up the format constraints specified by CODEC drivers. For now take the latter approach - this will need to be revisited if any CODECs are endianness dependant. Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index af11791a3b8..6ac68e47b3a 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2387,6 +2387,39 @@ void snd_soc_unregister_platform(struct snd_soc_platform *platform) } EXPORT_SYMBOL_GPL(snd_soc_unregister_platform); +static u64 codec_format_map[] = { + SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE, + SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE, + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE, + SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE, + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE, + SNDRV_PCM_FMTBIT_U32_LE | SNDRV_PCM_FMTBIT_U32_BE, + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_U24_3BE, + SNDRV_PCM_FMTBIT_U24_3LE | SNDRV_PCM_FMTBIT_U24_3BE, + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE, + SNDRV_PCM_FMTBIT_U20_3LE | SNDRV_PCM_FMTBIT_U20_3BE, + SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE, + SNDRV_PCM_FMTBIT_U18_3LE | SNDRV_PCM_FMTBIT_U18_3BE, + SNDRV_PCM_FMTBIT_FLOAT_LE | SNDRV_PCM_FMTBIT_FLOAT_BE, + SNDRV_PCM_FMTBIT_FLOAT64_LE | SNDRV_PCM_FMTBIT_FLOAT64_BE, + SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE + | SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE, +}; + +/* Fix up the DAI formats for endianness: codecs don't actually see + * the endianness of the data but we're using the CPU format + * definitions which do need to include endianness so we ensure that + * codec DAIs always have both big and little endian variants set. + */ +static void fixup_codec_formats(struct snd_soc_pcm_stream *stream) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(codec_format_map); i++) + if (stream->formats & codec_format_map[i]) + stream->formats |= codec_format_map[i]; +} + /** * snd_soc_register_codec - Register a codec with the ASoC core * @@ -2394,6 +2427,8 @@ EXPORT_SYMBOL_GPL(snd_soc_unregister_platform); */ int snd_soc_register_codec(struct snd_soc_codec *codec) { + int i; + if (!codec->name) return -EINVAL; @@ -2403,6 +2438,11 @@ int snd_soc_register_codec(struct snd_soc_codec *codec) INIT_LIST_HEAD(&codec->list); + for (i = 0; i < codec->num_dai; i++) { + fixup_codec_formats(&codec->dai[i].playback); + fixup_codec_formats(&codec->dai[i].capture); + } + mutex_lock(&client_mutex); list_add(&codec->list, &codec_list); snd_soc_instantiate_cards(); -- cgit v1.2.3 From 31cb31f76e030ae05ed45f409ce5eb68ef2987f6 Mon Sep 17 00:00:00 2001 From: Roel Kluin Date: Mon, 11 May 2009 21:57:08 +0200 Subject: ASoC: remove driver_data direct access of struct device Signed-off-by: Mark Brown --- sound/soc/codecs/wm8400.c | 4 ++-- sound/soc/codecs/wm8731.c | 4 ++-- sound/soc/codecs/wm8753.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8400.c b/sound/soc/codecs/wm8400.c index 510efa60400..e4547de8eec 100644 --- a/sound/soc/codecs/wm8400.c +++ b/sound/soc/codecs/wm8400.c @@ -1473,8 +1473,8 @@ static int wm8400_codec_probe(struct platform_device *dev) codec = &priv->codec; codec->private_data = priv; - codec->control_data = dev->dev.driver_data; - priv->wm8400 = dev->dev.driver_data; + codec->control_data = dev_get_drvdata(&dev->dev); + priv->wm8400 = dev_get_drvdata(&dev->dev); ret = regulator_bulk_get(priv->wm8400->dev, ARRAY_SIZE(power), &power[0]); diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index e043e3f6000..7a205876ef4 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -666,14 +666,14 @@ static int __devinit wm8731_spi_probe(struct spi_device *spi) codec->hw_write = (hw_write_t)wm8731_spi_write; codec->dev = &spi->dev; - spi->dev.driver_data = wm8731; + dev_set_drvdata(&spi->dev, wm8731); return wm8731_register(wm8731); } static int __devexit wm8731_spi_remove(struct spi_device *spi) { - struct wm8731_priv *wm8731 = spi->dev.driver_data; + struct wm8731_priv *wm8731 = dev_get_drvdata(&spi->dev); wm8731_unregister(wm8731); diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index a6e8f3f7f05..d121e58cae2 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -1822,14 +1822,14 @@ static int __devinit wm8753_spi_probe(struct spi_device *spi) codec->hw_write = (hw_write_t)wm8753_spi_write; codec->dev = &spi->dev; - spi->dev.driver_data = wm8753; + dev_set_drvdata(&spi->dev, wm8753); return wm8753_register(wm8753); } static int __devexit wm8753_spi_remove(struct spi_device *spi) { - struct wm8753_priv *wm8753 = spi->dev.driver_data; + struct wm8753_priv *wm8753 = dev_get_drvdata(&spi->dev); wm8753_unregister(wm8753); return 0; } -- cgit v1.2.3 From eaaa5328835d8085d24221a0e5ceaacbe14a7523 Mon Sep 17 00:00:00 2001 From: Mike Rapoport Date: Mon, 11 May 2009 15:05:29 +0300 Subject: ASoC: em-x270: make the driver support also eXeda and CM-X300 machines Signed-off-by: Mike Rapoport Signed-off-by: Mark Brown --- sound/soc/pxa/Kconfig | 4 ++-- sound/soc/pxa/em-x270.c | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index eb75a1c061a..dcd163a4ee9 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -89,13 +89,13 @@ config SND_PXA2XX_SOC_E800 Toshiba e800 PDA config SND_PXA2XX_SOC_EM_X270 - tristate "SoC Audio support for CompuLab EM-x270" + tristate "SoC Audio support for CompuLab EM-x270, eXeda and CM-X300" depends on SND_PXA2XX_SOC && MACH_EM_X270 select SND_PXA2XX_SOC_AC97 select SND_SOC_WM9712 help Say Y if you want to add support for SoC audio on - CompuLab EM-x270. + CompuLab EM-x270, eXeda and CM-X300 machines. config SND_PXA2XX_SOC_PALM27X bool "SoC Audio support for Palm T|X, T5 and LifeDrive" diff --git a/sound/soc/pxa/em-x270.c b/sound/soc/pxa/em-x270.c index 949be9c2a01..f4756e4025f 100644 --- a/sound/soc/pxa/em-x270.c +++ b/sound/soc/pxa/em-x270.c @@ -1,7 +1,7 @@ /* - * em-x270.c -- SoC audio for EM-X270 + * SoC audio driver for EM-X270, eXeda and CM-X300 * - * Copyright 2007 CompuLab, Ltd. + * Copyright 2007, 2009 CompuLab, Ltd. * * Author: Mike Rapoport * @@ -68,7 +68,8 @@ static int __init em_x270_init(void) { int ret; - if (!machine_is_em_x270()) + if (!(machine_is_em_x270() || machine_is_exeda() + || machine_is_cm_x300())) return -ENODEV; em_x270_snd_device = platform_device_alloc("soc-audio", -1); @@ -95,5 +96,5 @@ module_exit(em_x270_exit); /* Module information */ MODULE_AUTHOR("Mike Rapoport"); -MODULE_DESCRIPTION("ALSA SoC EM-X270"); +MODULE_DESCRIPTION("ALSA SoC EM-X270, eXeda and CM-X300"); MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 7de0a0aee5cf24639c14b17ab4077f5dba0d7cb9 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 11 May 2009 20:05:57 +0100 Subject: ASoC: Enforce symmetric rates for PXA2xx I2S There is a single I2S_SYNC pin on the chip. Signed-off-by: Mark Brown --- sound/soc/pxa/pxa2xx-i2s.c | 1 + 1 file changed, 1 insertion(+) (limited to 'sound/soc') diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c index 2f4b6e489b7..60145770aeb 100644 --- a/sound/soc/pxa/pxa2xx-i2s.c +++ b/sound/soc/pxa/pxa2xx-i2s.c @@ -329,6 +329,7 @@ struct snd_soc_dai pxa_i2s_dai = { .rates = PXA2XX_I2S_RATES, .formats = SNDRV_PCM_FMTBIT_S16_LE,}, .ops = &pxa_i2s_dai_ops, + .symmetric_rates = 1, }; EXPORT_SYMBOL_GPL(pxa_i2s_dai); -- cgit v1.2.3 From 97b8096dc92ae62b1d40e6bec7e7b257d2b30161 Mon Sep 17 00:00:00 2001 From: Joonyoung Shim Date: Mon, 11 May 2009 20:36:08 +0900 Subject: ASoC: TWL4030: change DAPM for analog microphone selection The inputs of the twl4030 codec can be mixed, so we will use the mixer DAPM for the analog microphone registers(0x05, 0x06), but if we enable more than one input at the same time, the input impedance of the input amplifier will be reduced. Signed-off-by: Joonyoung Shim Acked-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 52 +++++++++++++++++----------------------------- 1 file changed, 19 insertions(+), 33 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index fd392c65f47..eaf91ab465b 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -422,36 +422,18 @@ static const struct snd_kcontrol_new twl4030_dapm_vibrapath_control = SOC_DAPM_ENUM("Route", twl4030_vibrapath_enum); /* Left analog microphone selection */ -static const char *twl4030_analoglmic_texts[] = - {"Off", "Main mic", "Headset mic", "AUXL", "Carkit mic"}; - -static const unsigned int twl4030_analoglmic_values[] = - {0x0, 0x1, 0x2, 0x4, 0x8}; - -static const struct soc_enum twl4030_analoglmic_enum = - SOC_VALUE_ENUM_SINGLE(TWL4030_REG_ANAMICL, 0, 0xf, - ARRAY_SIZE(twl4030_analoglmic_texts), - twl4030_analoglmic_texts, - twl4030_analoglmic_values); - -static const struct snd_kcontrol_new twl4030_dapm_analoglmic_control = -SOC_DAPM_VALUE_ENUM("Route", twl4030_analoglmic_enum); +static const struct snd_kcontrol_new twl4030_dapm_analoglmic_controls[] = { + SOC_DAPM_SINGLE("Main mic", TWL4030_REG_ANAMICL, 0, 1, 0), + SOC_DAPM_SINGLE("Headset mic", TWL4030_REG_ANAMICL, 1, 1, 0), + SOC_DAPM_SINGLE("AUXL", TWL4030_REG_ANAMICL, 2, 1, 0), + SOC_DAPM_SINGLE("Carkit mic", TWL4030_REG_ANAMICL, 3, 1, 0), +}; /* Right analog microphone selection */ -static const char *twl4030_analogrmic_texts[] = - {"Off", "Sub mic", "AUXR"}; - -static const unsigned int twl4030_analogrmic_values[] = - {0x0, 0x1, 0x4}; - -static const struct soc_enum twl4030_analogrmic_enum = - SOC_VALUE_ENUM_SINGLE(TWL4030_REG_ANAMICR, 0, 0x5, - ARRAY_SIZE(twl4030_analogrmic_texts), - twl4030_analogrmic_texts, - twl4030_analogrmic_values); - -static const struct snd_kcontrol_new twl4030_dapm_analogrmic_control = -SOC_DAPM_VALUE_ENUM("Route", twl4030_analogrmic_enum); +static const struct snd_kcontrol_new twl4030_dapm_analogrmic_controls[] = { + SOC_DAPM_SINGLE("Sub mic", TWL4030_REG_ANAMICR, 0, 1, 0), + SOC_DAPM_SINGLE("AUXR", TWL4030_REG_ANAMICR, 1, 1, 0), +}; /* TX1 L/R Analog/Digital microphone selection */ static const char *twl4030_micpathtx1_texts[] = @@ -1138,11 +1120,15 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD| SND_SOC_DAPM_POST_REG), - /* Analog input muxes with switch for the capture amplifiers */ - SND_SOC_DAPM_VALUE_MUX("Analog Left Capture Route", - TWL4030_REG_ANAMICL, 4, 0, &twl4030_dapm_analoglmic_control), - SND_SOC_DAPM_VALUE_MUX("Analog Right Capture Route", - TWL4030_REG_ANAMICR, 4, 0, &twl4030_dapm_analogrmic_control), + /* Analog input mixers for the capture amplifiers */ + SND_SOC_DAPM_MIXER("Analog Left Capture Route", + TWL4030_REG_ANAMICL, 4, 0, + &twl4030_dapm_analoglmic_controls[0], + ARRAY_SIZE(twl4030_dapm_analoglmic_controls)), + SND_SOC_DAPM_MIXER("Analog Right Capture Route", + TWL4030_REG_ANAMICR, 4, 0, + &twl4030_dapm_analogrmic_controls[0], + ARRAY_SIZE(twl4030_dapm_analogrmic_controls)), SND_SOC_DAPM_PGA("ADC Physical Left", TWL4030_REG_AVADC_CTL, 3, 0, NULL, 0), -- cgit v1.2.3 From 8c10dc4f54d315ce801dc9ef4018aab8d0d75a7b Mon Sep 17 00:00:00 2001 From: Karl Beldan Date: Mon, 11 May 2009 23:49:41 +0200 Subject: ASoC: pxa2xx-i2s: Proper initialization Reset FIFO logic and registers, and make sure REC and RPL functions along with FIFO service are disabled at probe. Signed-off-by: Karl Beldan Signed-off-by: Mark Brown --- sound/soc/pxa/pxa2xx-i2s.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c index 60145770aeb..fce8a2894e2 100644 --- a/sound/soc/pxa/pxa2xx-i2s.c +++ b/sound/soc/pxa/pxa2xx-i2s.c @@ -347,6 +347,19 @@ static int pxa2xx_i2s_probe(struct platform_device *dev) if (ret != 0) clk_put(clk_i2s); + /* + * PXA Developer's Manual: + * If SACR0[ENB] is toggled in the middle of a normal operation, + * the SACR0[RST] bit must also be set and cleared to reset all + * I2S controller registers. + */ + SACR0 = SACR0_RST; + SACR0 = 0; + /* Make sure RPL and REC are disabled */ + SACR1 = SACR1_DRPL | SACR1_DREC; + /* Along with FIFO servicing */ + SAIMR &= ~(SAIMR_RFS | SAIMR_TFS); + return ret; } -- cgit v1.2.3 From 14610ce711a363028ffffad98947d57f21fa5372 Mon Sep 17 00:00:00 2001 From: Anuj Aggarwal Date: Thu, 14 May 2009 13:59:19 +0530 Subject: ASoC: Added OMAP3 EVM support in ASoC. Resending the patch after fixing the minor issues. Signed-off-by: Anuj Aggarwal Signed-off-by: Mark Brown --- sound/soc/omap/Kconfig | 8 +++ sound/soc/omap/Makefile | 2 + sound/soc/omap/omap3evm.c | 147 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 sound/soc/omap/omap3evm.c (limited to 'sound/soc') diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index 675732e724d..b771238662b 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -39,6 +39,14 @@ config SND_OMAP_SOC_OMAP2EVM help Say Y if you want to add support for SoC audio on the omap2evm board. +config SND_OMAP_SOC_OMAP3EVM + tristate "SoC Audio support for OMAP3EVM board" + depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP3EVM + select SND_OMAP_SOC_MCBSP + select SND_SOC_TWL4030 + help + Say Y if you want to add support for SoC audio on the omap3evm board. + config SND_OMAP_SOC_SDP3430 tristate "SoC Audio support for Texas Instruments SDP3430" depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP_3430SDP diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile index 0c9e4ac3766..a37f4986238 100644 --- a/sound/soc/omap/Makefile +++ b/sound/soc/omap/Makefile @@ -10,6 +10,7 @@ snd-soc-n810-objs := n810.o snd-soc-osk5912-objs := osk5912.o snd-soc-overo-objs := overo.o snd-soc-omap2evm-objs := omap2evm.o +snd-soc-omap3evm-objs := omap3evm.o snd-soc-sdp3430-objs := sdp3430.o snd-soc-omap3pandora-objs := omap3pandora.o snd-soc-omap3beagle-objs := omap3beagle.o @@ -18,6 +19,7 @@ obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o obj-$(CONFIG_SND_OMAP_SOC_OSK5912) += snd-soc-osk5912.o obj-$(CONFIG_SND_OMAP_SOC_OVERO) += snd-soc-overo.o obj-$(CONFIG_MACH_OMAP2EVM) += snd-soc-omap2evm.o +obj-$(CONFIG_MACH_OMAP3EVM) += snd-soc-omap3evm.o obj-$(CONFIG_SND_OMAP_SOC_SDP3430) += snd-soc-sdp3430.o obj-$(CONFIG_SND_OMAP_SOC_OMAP3_PANDORA) += snd-soc-omap3pandora.o obj-$(CONFIG_SND_OMAP_SOC_OMAP3_BEAGLE) += snd-soc-omap3beagle.o diff --git a/sound/soc/omap/omap3evm.c b/sound/soc/omap/omap3evm.c new file mode 100644 index 00000000000..9114c263077 --- /dev/null +++ b/sound/soc/omap/omap3evm.c @@ -0,0 +1,147 @@ +/* + * omap3evm.c -- ALSA SoC support for OMAP3 EVM + * + * Author: Anuj Aggarwal + * + * Based on sound/soc/omap/beagle.c by Steve Sakoman + * + * Copyright (C) 2008 Texas Instruments, Incorporated + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation version 2. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any kind, + * whether express or implied; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "omap-mcbsp.h" +#include "omap-pcm.h" +#include "../codecs/twl4030.h" + +static int omap3evm_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; + + /* Set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) { + printk(KERN_ERR "Can't set codec DAI configuration\n"); + return ret; + } + + /* Set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) { + printk(KERN_ERR "Can't set cpu DAI configuration\n"); + return ret; + } + + /* Set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000, + SND_SOC_CLOCK_IN); + if (ret < 0) { + printk(KERN_ERR "Can't set codec system clock\n"); + return ret; + } + + return 0; +} + +static struct snd_soc_ops omap3evm_ops = { + .hw_params = omap3evm_hw_params, +}; + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link omap3evm_dai = { + .name = "TWL4030", + .stream_name = "TWL4030", + .cpu_dai = &omap_mcbsp_dai[0], + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], + .ops = &omap3evm_ops, +}; + +/* Audio machine driver */ +static struct snd_soc_card snd_soc_omap3evm = { + .name = "omap3evm", + .platform = &omap_soc_platform, + .dai_link = &omap3evm_dai, + .num_links = 1, +}; + +/* Audio subsystem */ +static struct snd_soc_device omap3evm_snd_devdata = { + .card = &snd_soc_omap3evm, + .codec_dev = &soc_codec_dev_twl4030, +}; + +static struct platform_device *omap3evm_snd_device; + +static int __init omap3evm_soc_init(void) +{ + int ret; + + if (!machine_is_omap3evm()) { + pr_err("Not OMAP3 EVM!\n"); + return -ENODEV; + } + pr_info("OMAP3 EVM SoC init\n"); + + omap3evm_snd_device = platform_device_alloc("soc-audio", -1); + if (!omap3evm_snd_device) { + printk(KERN_ERR "Platform device allocation failed\n"); + return -ENOMEM; + } + + platform_set_drvdata(omap3evm_snd_device, &omap3evm_snd_devdata); + omap3evm_snd_devdata.dev = &omap3evm_snd_device->dev; + *(unsigned int *)omap3evm_dai.cpu_dai->private_data = 1; + + ret = platform_device_add(omap3evm_snd_device); + if (ret) + goto err1; + + return 0; + +err1: + printk(KERN_ERR "Unable to add platform device\n"); + platform_device_put(omap3evm_snd_device); + + return ret; +} + +static void __exit omap3evm_soc_exit(void) +{ + platform_device_unregister(omap3evm_snd_device); +} + +module_init(omap3evm_soc_init); +module_exit(omap3evm_soc_exit); + +MODULE_AUTHOR("Anuj Aggarwal "); +MODULE_DESCRIPTION("ALSA SoC OMAP3 EVM"); +MODULE_LICENSE("GPLv2"); -- cgit v1.2.3 From b243b77c708665d7af8c5e42611c27c89f918788 Mon Sep 17 00:00:00 2001 From: Karl Beldan Date: Thu, 14 May 2009 10:25:42 +0200 Subject: ASoC: pxa2xx-i2s: Proper hw initialization Make sure we are in a know good state at end of probe : Reset FIFO logic and registers, and make sure REC and RPL functions along with FIFO service are disabled (SACR0_RST enables REC and RPL). Resetting loses current settings so remove reset from stream startup. Now reset occurs only at probe. Signed-off-by: Karl Beldan Signed-off-by: Mark Brown --- sound/soc/pxa/pxa2xx-i2s.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c index 60145770aeb..bb8630b6dc8 100644 --- a/sound/soc/pxa/pxa2xx-i2s.c +++ b/sound/soc/pxa/pxa2xx-i2s.c @@ -106,10 +106,8 @@ static int pxa2xx_i2s_startup(struct snd_pcm_substream *substream, if (IS_ERR(clk_i2s)) return PTR_ERR(clk_i2s); - if (!cpu_dai->active) { - SACR0 |= SACR0_RST; + if (!cpu_dai->active) SACR0 = 0; - } return 0; } @@ -347,6 +345,19 @@ static int pxa2xx_i2s_probe(struct platform_device *dev) if (ret != 0) clk_put(clk_i2s); + /* + * PXA Developer's Manual: + * If SACR0[ENB] is toggled in the middle of a normal operation, + * the SACR0[RST] bit must also be set and cleared to reset all + * I2S controller registers. + */ + SACR0 = SACR0_RST; + SACR0 = 0; + /* Make sure RPL and REC are disabled */ + SACR1 = SACR1_DRPL | SACR1_DREC; + /* Along with FIFO servicing */ + SAIMR &= ~(SAIMR_RFS | SAIMR_TFS); + return ret; } -- cgit v1.2.3 From 34555c1077ac8f4854e0db9ad11b989a6908d210 Mon Sep 17 00:00:00 2001 From: Karl Beldan Date: Wed, 13 May 2009 22:16:46 +0200 Subject: ASoC: pxa2xx-i2s: Handle SACR1_DRPL and SACR1_DREC separately - hw_params enables both RPL and REC functions each time : Enable the appropriate function in pxa2xx_i2s_trigger. - pxa2xx_i2s_shutdown disables i2s anytime one of RPL or REC function is off : Turn it off only when both functions are off. Signed-off-by: Karl Beldan Signed-off-by: Mark Brown --- sound/soc/pxa/pxa2xx-i2s.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c index bb8630b6dc8..115b471b2de 100644 --- a/sound/soc/pxa/pxa2xx-i2s.c +++ b/sound/soc/pxa/pxa2xx-i2s.c @@ -176,9 +176,7 @@ static int pxa2xx_i2s_hw_params(struct snd_pcm_substream *substream, /* is port used by another stream */ if (!(SACR0 & SACR0_ENB)) { - SACR0 = 0; - SACR1 = 0; if (pxa_i2s.master) SACR0 |= SACR0_BCKD; @@ -224,6 +222,10 @@ static int pxa2xx_i2s_trigger(struct snd_pcm_substream *substream, int cmd, switch (cmd) { case SNDRV_PCM_TRIGGER_START: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + SACR1 &= ~SACR1_DRPL; + else + SACR1 &= ~SACR1_DREC; SACR0 |= SACR0_ENB; break; case SNDRV_PCM_TRIGGER_RESUME: @@ -250,7 +252,7 @@ static void pxa2xx_i2s_shutdown(struct snd_pcm_substream *substream, SAIMR &= ~SAIMR_RFS; } - if (SACR1 & (SACR1_DREC | SACR1_DRPL)) { + if ((SACR1 & (SACR1_DREC | SACR1_DRPL)) == (SACR1_DREC | SACR1_DRPL)) { SACR0 &= ~SACR0_ENB; pxa_i2s_wait(); clk_disable(clk_i2s); -- cgit v1.2.3 From 9bc04fd1677a956fdd7c5645a09de34ca9a8f1a6 Mon Sep 17 00:00:00 2001 From: Karl Beldan Date: Wed, 13 May 2009 22:16:52 +0200 Subject: ASoC: pxa2xx-i2s: Fix inappropriate release of i2s clock i2s_clk is 'put' for no reason in pxa2xx_i2s_shutdown. Now we 'get' i2s_clk at probe and 'put' it at driver removal or when probe fails. Signed-off-by: Karl Beldan Signed-off-by: Mark Brown --- sound/soc/pxa/pxa2xx-i2s.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c index 115b471b2de..bc12a09527f 100644 --- a/sound/soc/pxa/pxa2xx-i2s.c +++ b/sound/soc/pxa/pxa2xx-i2s.c @@ -257,8 +257,6 @@ static void pxa2xx_i2s_shutdown(struct snd_pcm_substream *substream, pxa_i2s_wait(); clk_disable(clk_i2s); } - - clk_put(clk_i2s); } #ifdef CONFIG_PM -- cgit v1.2.3 From 916465a841937a60baac6144ae3f41b0d1560f3b Mon Sep 17 00:00:00 2001 From: Karl Beldan Date: Wed, 13 May 2009 22:16:59 +0200 Subject: ASoC: pxa2xx-i2s: Fix suspend/resume pxa2xx_i2s_resume is : - unconditionnaly setting SACR0_ENB - unsetting SACR0_ENB in saved SACR0 pxa_i2s.sacr0 fix these. In pxa2xx_i2s_{resume,suspend}, save/restore registers even when !dai->active. Signed-off-by: Karl Beldan Signed-off-by: Mark Brown --- sound/soc/pxa/pxa2xx-i2s.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c index bc12a09527f..4743e262895 100644 --- a/sound/soc/pxa/pxa2xx-i2s.c +++ b/sound/soc/pxa/pxa2xx-i2s.c @@ -262,9 +262,6 @@ static void pxa2xx_i2s_shutdown(struct snd_pcm_substream *substream, #ifdef CONFIG_PM static int pxa2xx_i2s_suspend(struct snd_soc_dai *dai) { - if (!dai->active) - return 0; - /* store registers */ pxa_i2s.sacr0 = SACR0; pxa_i2s.sacr1 = SACR1; @@ -279,16 +276,14 @@ static int pxa2xx_i2s_suspend(struct snd_soc_dai *dai) static int pxa2xx_i2s_resume(struct snd_soc_dai *dai) { - if (!dai->active) - return 0; - pxa_i2s_wait(); - SACR0 = pxa_i2s.sacr0 &= ~SACR0_ENB; + SACR0 = pxa_i2s.sacr0 & ~SACR0_ENB; SACR1 = pxa_i2s.sacr1; SAIMR = pxa_i2s.saimr; SADIV = pxa_i2s.sadiv; - SACR0 |= SACR0_ENB; + + SACR0 = pxa_i2s.sacr0; return 0; } -- cgit v1.2.3 From 63c26baa2aa624b023892d66ed696525fc787560 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 14 May 2009 20:52:46 +0100 Subject: ASoC: Support AC97 link off by default on WM9712 The WM9712 can be configured by resistor strapping GPIO4 to behave like the WM9713 and default to leaving the AC97 link disabled after cold reset until a warm reset occurs. In this configuration we need to issue a warm reset after cold to bring the link up so do so. The warm reset will be harmless on systems that don't need it. [Changelog rewritten to document the reasoning. -- broonie] Signed-off-by: Marek Vasut Signed-off-by: Mark Brown --- sound/soc/codecs/wm9712.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm9712.c b/sound/soc/codecs/wm9712.c index 550c903f23b..1fd4e88f50c 100644 --- a/sound/soc/codecs/wm9712.c +++ b/sound/soc/codecs/wm9712.c @@ -585,6 +585,8 @@ static int wm9712_reset(struct snd_soc_codec *codec, int try_warm) } soc_ac97_ops.reset(codec->ac97); + if (soc_ac97_ops.warm_reset) + soc_ac97_ops.warm_reset(codec->ac97); if (ac97_read(codec, 0) != wm9712_reg[0]) goto err; return 0; -- cgit v1.2.3 From 2baaec28068d07db3d4ae6ba885fa07255b2ad79 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 15 May 2009 12:18:47 +0200 Subject: ASoC: Add missing __devexit in wm8940.c Signed-off-by: Takashi Iwai --- sound/soc/codecs/wm8940.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c index a66dacc7cc8..b8e17d6bc1f 100644 --- a/sound/soc/codecs/wm8940.c +++ b/sound/soc/codecs/wm8940.c @@ -907,7 +907,7 @@ static int wm8940_i2c_probe(struct i2c_client *i2c, return wm8940_register(wm8940); } -static int wm8940_i2c_remove(struct i2c_client *client) +static int __devexit wm8940_i2c_remove(struct i2c_client *client) { struct wm8940_priv *wm8940 = i2c_get_clientdata(client); -- cgit v1.2.3 From 2bf2778e0fb38255e55ab5e10022132b0a72420e Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 15 May 2009 12:20:52 +0200 Subject: ASoC: Optimize switch/case in magician.c Use default to optimize the switch/case in magicial_playback_hw_params(), which also fixes the compile warnings below: sound/soc/pxa/magician.c:89: warning: 'acds' may be used uninitialized in this function sound/soc/pxa/magician.c:89: warning: 'acps' may be used uninitialized in this function Signed-off-by: Takashi Iwai --- sound/soc/pxa/magician.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/pxa/magician.c b/sound/soc/pxa/magician.c index 0625c342a1c..c89a3cdf31e 100644 --- a/sound/soc/pxa/magician.c +++ b/sound/soc/pxa/magician.c @@ -106,7 +106,7 @@ static int magician_playback_hw_params(struct snd_pcm_substream *substream, /* 513156 Hz ~= _2_ * 8000 Hz * 32 (+0.23%) */ acds = PXA_SSP_CLK_AUDIO_DIV_16; break; - case 32: + default: /* 32 */ /* 1026312 Hz ~= _2_ * 8000 Hz * 64 (+0.23%) */ acds = PXA_SSP_CLK_AUDIO_DIV_8; } @@ -118,7 +118,7 @@ static int magician_playback_hw_params(struct snd_pcm_substream *substream, /* 351375 Hz ~= 11025 Hz * 32 (-0.41%) */ acds = PXA_SSP_CLK_AUDIO_DIV_4; break; - case 32: + default: /* 32 */ /* 702750 Hz ~= 11025 Hz * 64 (-0.41%) */ acds = PXA_SSP_CLK_AUDIO_DIV_2; } @@ -130,7 +130,7 @@ static int magician_playback_hw_params(struct snd_pcm_substream *substream, /* 702750 Hz ~= 22050 Hz * 32 (-0.41%) */ acds = PXA_SSP_CLK_AUDIO_DIV_2; break; - case 32: + default: /* 32 */ /* 1405500 Hz ~= 22050 Hz * 64 (-0.41%) */ acds = PXA_SSP_CLK_AUDIO_DIV_1; } @@ -142,7 +142,7 @@ static int magician_playback_hw_params(struct snd_pcm_substream *substream, /* 1405500 Hz ~= 44100 Hz * 32 (-0.41%) */ acds = PXA_SSP_CLK_AUDIO_DIV_2; break; - case 32: + default: /* 32 */ /* 2811000 Hz ~= 44100 Hz * 64 (-0.41%) */ acds = PXA_SSP_CLK_AUDIO_DIV_1; } @@ -154,19 +154,20 @@ static int magician_playback_hw_params(struct snd_pcm_substream *substream, /* 1529375 Hz ~= 48000 Hz * 32 (-0.44%) */ acds = PXA_SSP_CLK_AUDIO_DIV_2; break; - case 32: + default: /* 32 */ /* 3058750 Hz ~= 48000 Hz * 64 (-0.44%) */ acds = PXA_SSP_CLK_AUDIO_DIV_1; } break; case 96000: + default: acps = 12235000; switch (width) { case 16: /* 3058750 Hz ~= 96000 Hz * 32 (-0.44%) */ acds = PXA_SSP_CLK_AUDIO_DIV_1; break; - case 32: + default: /* 32 */ /* 6117500 Hz ~= 96000 Hz * 64 (-0.44%) */ acds = PXA_SSP_CLK_AUDIO_DIV_2; div4 = PXA_SSP_CLK_SCDB_1; -- cgit v1.2.3 From 5b740ea975c4ce3da12ac21b56f9e43354ca4327 Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sun, 17 May 2009 11:29:21 +0200 Subject: sound: use dev_set_drvdata Eliminate direct accesses to the driver_data field. cf 82ab13b26f15f49be45f15ccc96bfa0b81dfd015 The semantic patch that makes this change is as follows: (http://www.emn.fr/x-info/coccinelle/) // @@ struct device *dev; expression E; type T; @@ - dev->driver_data = (T)E + dev_set_drvdata(dev, E) @@ struct device *dev; type T; @@ - (T)dev->driver_data + dev_get_drvdata(dev) // Signed-off-by: Julia Lawall Signed-off-by: Takashi Iwai --- sound/soc/fsl/fsl_ssi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index 3711d8454d9..47afaa9747a 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -674,7 +674,7 @@ struct snd_soc_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info) ssi_private->dev = ssi_info->dev; ssi_private->asynchronous = ssi_info->asynchronous; - ssi_private->dev->driver_data = fsl_ssi_dai; + dev_set_drvdata(ssi_private->dev, fsl_ssi_dai); /* Initialize the the device_attribute structure */ dev_attr->attr.name = "ssi-stats"; -- cgit v1.2.3 From b7a755a8a145a7e34e735bda9c452317de7a538a Mon Sep 17 00:00:00 2001 From: Misael Lopez Cruz Date: Sun, 17 May 2009 20:02:31 -0500 Subject: ASoC: TWL4030: Enable/disable voice digital filters Enable TWL4030 VTXL/VTXR and VRX digital filters for uplink and downlink paths, respectively. This patch also corrects voice 8/16kHz mode selection bit (SEL_16K) of CODEC_MODE register. Signed-off-by: Misael Lopez Cruz Acked-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 37 +++++++++++++++++++++++++++++++++++++ sound/soc/codecs/twl4030.h | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index eaf91ab465b..e4d683daa45 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -1629,6 +1629,28 @@ static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai, return 0; } +/* In case of voice mode, the RX1 L(VRX) for downlink and the TX2 L/R + * (VTXL, VTXR) for uplink has to be enabled/disabled. */ +static void twl4030_voice_enable(struct snd_soc_codec *codec, int direction, + int enable) +{ + u8 reg, mask; + + reg = twl4030_read_reg_cache(codec, TWL4030_REG_OPTION); + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) + mask = TWL4030_ARXL1_VRX_EN; + else + mask = TWL4030_ATXL2_VTXL_EN | TWL4030_ATXR2_VTXR_EN; + + if (enable) + reg |= mask; + else + reg &= ~mask; + + twl4030_write(codec, TWL4030_REG_OPTION, reg); +} + static int twl4030_voice_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -1665,6 +1687,17 @@ static int twl4030_voice_startup(struct snd_pcm_substream *substream, return 0; } +static void twl4030_voice_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + + /* Enable voice digital filters */ + twl4030_voice_enable(codec, substream->stream, 0); +} + static int twl4030_voice_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { @@ -1673,6 +1706,9 @@ static int twl4030_voice_hw_params(struct snd_pcm_substream *substream, struct snd_soc_codec *codec = socdev->card->codec; u8 old_mode, mode; + /* Enable voice digital filters */ + twl4030_voice_enable(codec, substream->stream, 1); + /* bit rate */ old_mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE) & ~(TWL4030_CODECPDZ); @@ -1780,6 +1816,7 @@ static struct snd_soc_dai_ops twl4030_dai_ops = { static struct snd_soc_dai_ops twl4030_dai_voice_ops = { .startup = twl4030_voice_startup, + .shutdown = twl4030_voice_shutdown, .hw_params = twl4030_voice_hw_params, .set_sysclk = twl4030_voice_set_dai_sysclk, .set_fmt = twl4030_voice_set_dai_fmt, diff --git a/sound/soc/codecs/twl4030.h b/sound/soc/codecs/twl4030.h index 3441115136f..9668bdf430f 100644 --- a/sound/soc/codecs/twl4030.h +++ b/sound/soc/codecs/twl4030.h @@ -110,7 +110,7 @@ #define TWL4030_APLL_RATE_44100 0x90 #define TWL4030_APLL_RATE_48000 0xA0 #define TWL4030_APLL_RATE_96000 0xE0 -#define TWL4030_SEL_16K 0x04 +#define TWL4030_SEL_16K 0x08 #define TWL4030_CODECPDZ 0x02 #define TWL4030_OPT_MODE 0x01 #define TWL4030_OPTION_1 (1 << 0) -- cgit v1.2.3 From 6d3ddc81f5762d54ce7d1db70eb757c6c12fabbc Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 16 May 2009 17:47:29 +0100 Subject: ASoC: Split DAPM power checks from sequencing of power changes DAPM has always applied any changes to the power state of widgets as soon as it has determined that they are required. Instead of doing this store all the changes that are required on lists of widgets to power up and down, then iterate over those lists and apply the changes. This changes the sequence in which changes are implemented, doing all power downs before power ups and always using the up/down sequences (previously they were only used when changes were due to DAC/ADC power events). The error handling is also changed so that we continue attempting to power widgets if some changes fail. The main benefit of this is to allow future changes to do optimisations over the whole power sequence and to reduce the number of walks of the widget graph required to check the power status of widgets. Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 81 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 25 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 7847f80e96d..04ef84106d7 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -658,7 +658,7 @@ static int dapm_supply_check_power(struct snd_soc_dapm_widget *w) static int dapm_power_widget(struct snd_soc_codec *codec, int event, struct snd_soc_dapm_widget *w) { - int power, ret; + int ret; switch (w->id) { case snd_soc_dapm_pre: @@ -696,18 +696,8 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event, return 0; default: - break; + return dapm_generic_apply_power(w); } - - if (!w->power_check) - return 0; - - power = w->power_check(w); - if (w->power == power) - return 0; - w->power = power; - - return dapm_generic_apply_power(w); } /* @@ -722,27 +712,68 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event, static int dapm_power_widgets(struct snd_soc_codec *codec, int event) { struct snd_soc_dapm_widget *w; - int i, c = 1, *seq = NULL, ret = 0; - - /* do we have a sequenced stream event */ - if (event == SND_SOC_DAPM_STREAM_START) { - c = ARRAY_SIZE(dapm_up_seq); - seq = dapm_up_seq; - } else if (event == SND_SOC_DAPM_STREAM_STOP) { - c = ARRAY_SIZE(dapm_down_seq); - seq = dapm_down_seq; + int ret = 0; + int i, power; + + INIT_LIST_HEAD(&codec->up_list); + INIT_LIST_HEAD(&codec->down_list); + + /* Check which widgets we need to power and store them in + * lists indicating if they should be powered up or down. + */ + list_for_each_entry(w, &codec->dapm_widgets, list) { + switch (w->id) { + case snd_soc_dapm_pre: + list_add_tail(&codec->down_list, &w->power_list); + break; + case snd_soc_dapm_post: + list_add_tail(&codec->up_list, &w->power_list); + break; + + default: + if (!w->power_check) + continue; + + power = w->power_check(w); + if (w->power == power) + continue; + + if (power) + list_add_tail(&w->power_list, &codec->up_list); + else + list_add_tail(&w->power_list, + &codec->down_list); + + w->power = power; + break; + } } - for (i = 0; i < c; i++) { - list_for_each_entry(w, &codec->dapm_widgets, list) { + /* Power down widgets first; try to avoid amplifying pops. */ + for (i = 0; i < ARRAY_SIZE(dapm_down_seq); i++) { + list_for_each_entry(w, &codec->down_list, power_list) { + /* is widget in stream order */ + if (w->id != dapm_down_seq[i]) + continue; + + ret = dapm_power_widget(codec, event, w); + if (ret != 0) + pr_err("Failed to power down %s: %d\n", + w->name, ret); + } + } + /* Now power up. */ + for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++) { + list_for_each_entry(w, &codec->up_list, power_list) { /* is widget in stream order */ - if (seq && seq[i] && w->id != seq[i]) + if (w->id != dapm_up_seq[i]) continue; ret = dapm_power_widget(codec, event, w); if (ret != 0) - return ret; + pr_err("Failed to power up %s: %d\n", + w->name, ret); } } -- cgit v1.2.3 From aef908434cd24dd5529065bf5d781773fad21125 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 16 May 2009 17:53:16 +0100 Subject: ASoC: Make DAPM sysfs entries non-optional sysfs is so standard these days there's no point. Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 04ef84106d7..d130602b307 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -67,10 +67,6 @@ static int dapm_down_seq[] = { snd_soc_dapm_post }; -static int dapm_status = 1; -module_param(dapm_status, int, 0); -MODULE_PARM_DESC(dapm_status, "enable DPM sysfs entries"); - static void pop_wait(u32 pop_time) { if (pop_time) @@ -974,16 +970,12 @@ static DEVICE_ATTR(dapm_widget, 0444, dapm_widget_show, NULL); int snd_soc_dapm_sys_add(struct device *dev) { - if (!dapm_status) - return 0; return device_create_file(dev, &dev_attr_dapm_widget); } static void snd_soc_dapm_sys_remove(struct device *dev) { - if (dapm_status) { - device_remove_file(dev, &dev_attr_dapm_widget); - } + device_remove_file(dev, &dev_attr_dapm_widget); } /* free all dapm widgets and resources */ -- cgit v1.2.3 From 452c5eaa0d5162e02ffee742ea17540887bc2904 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 17 May 2009 21:41:23 +0100 Subject: ASoC: Integrate bias management with DAPM power management Rather than managing the bias level of the system based on if there is an active audio stream manage it based on there being an active DAPM widget. This simplifies the code a little, moving the power handling into one place, and improves audio performance for bypass paths when no playbacks or captures are active. Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 61 +++++++--------------------------------- sound/soc/soc-dapm.c | 78 ++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 64 insertions(+), 75 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index c0e706645ec..4aa8e2d3506 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -299,7 +299,6 @@ static void close_delayed_work(struct work_struct *work) { struct snd_soc_card *card = container_of(work, struct snd_soc_card, delayed_work.work); - struct snd_soc_device *socdev = card->socdev; struct snd_soc_codec *codec = card->codec; struct snd_soc_dai *codec_dai; int i; @@ -315,27 +314,10 @@ static void close_delayed_work(struct work_struct *work) /* are we waiting on this codec DAI stream */ if (codec_dai->pop_wait == 1) { - - /* Reduce power if no longer active */ - if (codec->active == 0) { - pr_debug("pop wq D1 %s %s\n", codec->name, - codec_dai->playback.stream_name); - snd_soc_dapm_set_bias_level(socdev, - SND_SOC_BIAS_PREPARE); - } - codec_dai->pop_wait = 0; snd_soc_dapm_stream_event(codec, codec_dai->playback.stream_name, SND_SOC_DAPM_STREAM_STOP); - - /* Fall into standby if no longer active */ - if (codec->active == 0) { - pr_debug("pop wq D3 %s %s\n", codec->name, - codec_dai->playback.stream_name); - snd_soc_dapm_set_bias_level(socdev, - SND_SOC_BIAS_STANDBY); - } } } mutex_unlock(&pcm_mutex); @@ -399,10 +381,6 @@ static int soc_codec_close(struct snd_pcm_substream *substream) snd_soc_dapm_stream_event(codec, codec_dai->capture.stream_name, SND_SOC_DAPM_STREAM_STOP); - - if (codec->active == 0 && codec_dai->pop_wait == 0) - snd_soc_dapm_set_bias_level(socdev, - SND_SOC_BIAS_STANDBY); } mutex_unlock(&pcm_mutex); @@ -467,36 +445,16 @@ static int soc_pcm_prepare(struct snd_pcm_substream *substream) cancel_delayed_work(&card->delayed_work); } - /* do we need to power up codec */ - if (codec->bias_level != SND_SOC_BIAS_ON) { - snd_soc_dapm_set_bias_level(socdev, - SND_SOC_BIAS_PREPARE); - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - snd_soc_dapm_stream_event(codec, - codec_dai->playback.stream_name, - SND_SOC_DAPM_STREAM_START); - else - snd_soc_dapm_stream_event(codec, - codec_dai->capture.stream_name, - SND_SOC_DAPM_STREAM_START); - - snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_ON); - snd_soc_dai_digital_mute(codec_dai, 0); - - } else { - /* codec already powered - power on widgets */ - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - snd_soc_dapm_stream_event(codec, - codec_dai->playback.stream_name, - SND_SOC_DAPM_STREAM_START); - else - snd_soc_dapm_stream_event(codec, - codec_dai->capture.stream_name, - SND_SOC_DAPM_STREAM_START); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + snd_soc_dapm_stream_event(codec, + codec_dai->playback.stream_name, + SND_SOC_DAPM_STREAM_START); + else + snd_soc_dapm_stream_event(codec, + codec_dai->capture.stream_name, + SND_SOC_DAPM_STREAM_START); - snd_soc_dai_digital_mute(codec_dai, 0); - } + snd_soc_dai_digital_mute(codec_dai, 0); out: mutex_unlock(&pcm_mutex); @@ -1372,6 +1330,7 @@ int snd_soc_new_pcms(struct snd_soc_device *socdev, int idx, const char *xid) return ret; } + codec->socdev = socdev; codec->card->dev = socdev->dev; codec->card->private_data = codec; strncpy(codec->card->driver, codec->name, sizeof(codec->card->driver)); diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index d130602b307..4ca5e56388a 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -94,6 +94,30 @@ static inline struct snd_soc_dapm_widget *dapm_cnew_widget( return kmemdup(_widget, sizeof(*_widget), GFP_KERNEL); } +/** + * snd_soc_dapm_set_bias_level - set the bias level for the system + * @socdev: audio device + * @level: level to configure + * + * Configure the bias (power) levels for the SoC audio device. + * + * Returns 0 for success else error. + */ +static int snd_soc_dapm_set_bias_level(struct snd_soc_device *socdev, + enum snd_soc_bias_level level) +{ + struct snd_soc_card *card = socdev->card; + struct snd_soc_codec *codec = socdev->card->codec; + int ret = 0; + + if (card->set_bias_level) + ret = card->set_bias_level(card, level); + if (ret == 0 && codec->set_bias_level) + ret = codec->set_bias_level(codec, level); + + return ret; +} + /* set up initial codec paths */ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, struct snd_soc_dapm_path *p, int i) @@ -707,9 +731,11 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event, */ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) { + struct snd_soc_device *socdev = codec->socdev; struct snd_soc_dapm_widget *w; int ret = 0; int i, power; + int sys_power = 0; INIT_LIST_HEAD(&codec->up_list); INIT_LIST_HEAD(&codec->down_list); @@ -731,6 +757,9 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) continue; power = w->power_check(w); + if (power) + sys_power = 1; + if (w->power == power) continue; @@ -745,6 +774,15 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) } } + /* If we're changing to all on or all off then prepare */ + if ((sys_power && codec->bias_level == SND_SOC_BIAS_STANDBY) || + (!sys_power && codec->bias_level == SND_SOC_BIAS_ON)) { + ret = snd_soc_dapm_set_bias_level(socdev, + SND_SOC_BIAS_PREPARE); + if (ret != 0) + pr_err("Failed to prepare bias: %d\n", ret); + } + /* Power down widgets first; try to avoid amplifying pops. */ for (i = 0; i < ARRAY_SIZE(dapm_down_seq); i++) { list_for_each_entry(w, &codec->down_list, power_list) { @@ -773,6 +811,22 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) } } + /* If we just powered the last thing off drop to standby bias */ + if (codec->bias_level == SND_SOC_BIAS_PREPARE && !sys_power) { + ret = snd_soc_dapm_set_bias_level(socdev, + SND_SOC_BIAS_STANDBY); + if (ret != 0) + pr_err("Failed to apply standby bias: %d\n", ret); + } + + /* If we just powered up then move to active bias */ + if (codec->bias_level == SND_SOC_BIAS_PREPARE && sys_power) { + ret = snd_soc_dapm_set_bias_level(socdev, + SND_SOC_BIAS_ON); + if (ret != 0) + pr_err("Failed to apply active bias: %d\n", ret); + } + return 0; } @@ -1720,30 +1774,6 @@ int snd_soc_dapm_stream_event(struct snd_soc_codec *codec, } EXPORT_SYMBOL_GPL(snd_soc_dapm_stream_event); -/** - * snd_soc_dapm_set_bias_level - set the bias level for the system - * @socdev: audio device - * @level: level to configure - * - * Configure the bias (power) levels for the SoC audio device. - * - * Returns 0 for success else error. - */ -int snd_soc_dapm_set_bias_level(struct snd_soc_device *socdev, - enum snd_soc_bias_level level) -{ - struct snd_soc_card *card = socdev->card; - struct snd_soc_codec *codec = socdev->card->codec; - int ret = 0; - - if (card->set_bias_level) - ret = card->set_bias_level(card, level); - if (ret == 0 && codec->set_bias_level) - ret = codec->set_bias_level(codec, level); - - return ret; -} - /** * snd_soc_dapm_enable_pin - enable pin. * @codec: SoC codec -- cgit v1.2.3 From f83fba8baab9e95fff0fe2be0e1e32a1650bdd7f Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 18 May 2009 15:44:43 +0100 Subject: ASoC: Add debug trace for bias level transitions A standard way of making sure we know when the bias level changes. Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 4ca5e56388a..39a63f958db 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -110,6 +110,24 @@ static int snd_soc_dapm_set_bias_level(struct snd_soc_device *socdev, struct snd_soc_codec *codec = socdev->card->codec; int ret = 0; + switch (level) { + case SND_SOC_BIAS_ON: + dev_dbg(socdev->dev, "Setting full bias\n"); + break; + case SND_SOC_BIAS_PREPARE: + dev_dbg(socdev->dev, "Setting bias prepare\n"); + break; + case SND_SOC_BIAS_STANDBY: + dev_dbg(socdev->dev, "Setting standby bias\n"); + break; + case SND_SOC_BIAS_OFF: + dev_dbg(socdev->dev, "Setting bias off\n"); + break; + default: + dev_err(socdev->dev, "Setting invalid bias %d\n", level); + return -EINVAL; + } + if (card->set_bias_level) ret = card->set_bias_level(card, level); if (ret == 0 && codec->set_bias_level) -- cgit v1.2.3 From ec976d6eb021dc8f2994248c310a41540f4756bd Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Wed, 13 May 2009 22:52:24 +0100 Subject: [ARM] S3C24XX: GPIO: Move gpio functions out of Move all the gpio functions out of as this file is for defining the generic IO base addresses for the kernel IO calls. Make a new header to take this and include it via the chain from which is what most of these files should be using (and will be changed as soon as possible). Note, this does make minor changes to some drivers but should not mess up any pending merges. CC: Richard Purdie Acked-by: Mark Brown CC: David Brownell Signed-off-by: Ben Dooks --- sound/soc/s3c24xx/s3c2412-i2s.c | 1 + sound/soc/s3c24xx/s3c2443-ac97.c | 1 + sound/soc/s3c24xx/s3c24xx-i2s.c | 2 ++ 3 files changed, 4 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/s3c2412-i2s.c b/sound/soc/s3c24xx/s3c2412-i2s.c index b7e0b3f0bfc..c35b74b2d1d 100644 --- a/sound/soc/s3c24xx/s3c2412-i2s.c +++ b/sound/soc/s3c24xx/s3c2412-i2s.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include diff --git a/sound/soc/s3c24xx/s3c2443-ac97.c b/sound/soc/s3c24xx/s3c2443-ac97.c index 3698f707c44..3f03d5ddfac 100644 --- a/sound/soc/s3c24xx/s3c2443-ac97.c +++ b/sound/soc/s3c24xx/s3c2443-ac97.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include diff --git a/sound/soc/s3c24xx/s3c24xx-i2s.c b/sound/soc/s3c24xx/s3c24xx-i2s.c index cc066964dad..556e35f0ab7 100644 --- a/sound/soc/s3c24xx/s3c24xx-i2s.c +++ b/sound/soc/s3c24xx/s3c24xx-i2s.c @@ -21,6 +21,8 @@ #include #include #include +#include + #include #include #include -- cgit v1.2.3 From 181da78cd048ce866b05a2e0208ea09d2f80e721 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Tue, 19 May 2009 10:51:03 +0300 Subject: ASoC: TWL4030: Fix Analog capture path for AUXR AUXR is selected by bit 2 and not by bit 1 in the ANAMICR register. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index e4d683daa45..abf691493f4 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -432,7 +432,7 @@ static const struct snd_kcontrol_new twl4030_dapm_analoglmic_controls[] = { /* Right analog microphone selection */ static const struct snd_kcontrol_new twl4030_dapm_analogrmic_controls[] = { SOC_DAPM_SINGLE("Sub mic", TWL4030_REG_ANAMICR, 0, 1, 0), - SOC_DAPM_SINGLE("AUXR", TWL4030_REG_ANAMICR, 1, 1, 0), + SOC_DAPM_SINGLE("AUXR", TWL4030_REG_ANAMICR, 2, 1, 0), }; /* TX1 L/R Analog/Digital microphone selection */ -- cgit v1.2.3 From b74bd40fa4ae018898c8a6429c2a7daf61516524 Mon Sep 17 00:00:00 2001 From: "Lopez Cruz, Misael" Date: Mon, 18 May 2009 11:52:55 -0500 Subject: ASoC: TWL4030: Add control for selecting codec operation mode Add a control for selecting the codec operation mode. TWL4030 codec has two modes: - Option 1. Audio only (4 audio DACs) - Option 2. Voice/Audio (2 audio DACs and voice ADC/DAC) Control is restricted when a stream is ongoing, since codec's operation mode cannot be changed on-the-fly. Signed-off-by: Misael Lopez Cruz Acked-by: Peter Ujflausi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index abf691493f4..731534c19b7 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -814,6 +814,48 @@ static int snd_soc_put_volsw_r2_twl4030(struct snd_kcontrol *kcontrol, return err; } +/* Codec operation modes */ +static const char *twl4030_op_modes_texts[] = { + "Option 2 (voice/audio)", "Option 1 (audio)" +}; + +static const struct soc_enum twl4030_op_modes_enum = + SOC_ENUM_SINGLE(TWL4030_REG_CODEC_MODE, 0, + ARRAY_SIZE(twl4030_op_modes_texts), + twl4030_op_modes_texts); + +int snd_soc_put_twl4030_opmode_enum_double(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct twl4030_priv *twl4030 = codec->private_data; + struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; + unsigned short val; + unsigned short mask, bitmask; + + if (twl4030->configured) { + printk(KERN_ERR "twl4030 operation mode cannot be " + "changed on-the-fly\n"); + return -EBUSY; + } + + for (bitmask = 1; bitmask < e->max; bitmask <<= 1) + ; + if (ucontrol->value.enumerated.item[0] > e->max - 1) + return -EINVAL; + + val = ucontrol->value.enumerated.item[0] << e->shift_l; + mask = (bitmask - 1) << e->shift_l; + if (e->shift_l != e->shift_r) { + if (ucontrol->value.enumerated.item[1] > e->max - 1) + return -EINVAL; + val |= ucontrol->value.enumerated.item[1] << e->shift_r; + mask |= (bitmask - 1) << e->shift_r; + } + + return snd_soc_update_bits(codec, e->reg, mask, val); +} + /* * FGAIN volume control: * from -62 to 0 dB in 1 dB steps (mute instead of -63 dB) @@ -895,6 +937,11 @@ static const struct soc_enum twl4030_vibradir_enum = twl4030_vibradir_texts); static const struct snd_kcontrol_new twl4030_snd_controls[] = { + /* Codec operation mode control */ + SOC_ENUM_EXT("Codec Operation Mode", twl4030_op_modes_enum, + snd_soc_get_enum_double, + snd_soc_put_twl4030_opmode_enum_double), + /* Common playback gain controls */ SOC_DOUBLE_R_TLV("DAC1 Digital Fine Playback Volume", TWL4030_REG_ARXL1PGA, TWL4030_REG_ARXR1PGA, -- cgit v1.2.3 From 11a728110633320d95935a1ba79c038db303596f Mon Sep 17 00:00:00 2001 From: "Lopez Cruz, Misael" Date: Mon, 18 May 2009 11:53:04 -0500 Subject: ASoC: SDP3430: Connect twl4030 voice DAI to McBSP3 Connect twl4030 voice DAI to McBSP3 in sdp3430 machine driver. Voice DAI init function enables corresponding interface by writting directly to VOICE_IF codec register. Signed-off-by: Misael Lopez Cruz Acked-by: Peter Ujflausi Signed-off-by: Mark Brown --- sound/soc/omap/sdp3430.c | 87 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 77 insertions(+), 10 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/omap/sdp3430.c b/sound/soc/omap/sdp3430.c index 1c7974101a0..19966a7c518 100644 --- a/sound/soc/omap/sdp3430.c +++ b/sound/soc/omap/sdp3430.c @@ -84,6 +84,49 @@ static struct snd_soc_ops sdp3430_ops = { .hw_params = sdp3430_hw_params, }; +static int sdp3430_hw_voice_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; + + /* Set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBS_CFM); + if (ret) { + printk(KERN_ERR "can't set codec DAI configuration\n"); + return ret; + } + + /* Set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) { + printk(KERN_ERR "can't set cpu DAI configuration\n"); + return ret; + } + + /* Set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000, + SND_SOC_CLOCK_IN); + if (ret < 0) { + printk(KERN_ERR "can't set codec system clock\n"); + return ret; + } + + return 0; +} + +static struct snd_soc_ops sdp3430_voice_ops = { + .hw_params = sdp3430_hw_voice_params, +}; + /* Headset jack */ static struct snd_soc_jack hs_jack; @@ -192,22 +235,45 @@ static int sdp3430_twl4030_init(struct snd_soc_codec *codec) return ret; } +static int sdp3430_twl4030_voice_init(struct snd_soc_codec *codec) +{ + unsigned short reg; + + /* Enable voice interface */ + reg = codec->read(codec, TWL4030_REG_VOICE_IF); + reg |= TWL4030_VIF_DIN_EN | TWL4030_VIF_DOUT_EN | TWL4030_VIF_EN; + codec->write(codec, TWL4030_REG_VOICE_IF, reg); + + return 0; +} + + /* Digital audio interface glue - connects codec <--> CPU */ -static struct snd_soc_dai_link sdp3430_dai = { - .name = "TWL4030", - .stream_name = "TWL4030", - .cpu_dai = &omap_mcbsp_dai[0], - .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], - .init = sdp3430_twl4030_init, - .ops = &sdp3430_ops, +static struct snd_soc_dai_link sdp3430_dai[] = { + { + .name = "TWL4030 I2S", + .stream_name = "TWL4030 Audio", + .cpu_dai = &omap_mcbsp_dai[0], + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], + .init = sdp3430_twl4030_init, + .ops = &sdp3430_ops, + }, + { + .name = "TWL4030 PCM", + .stream_name = "TWL4030 Voice", + .cpu_dai = &omap_mcbsp_dai[1], + .codec_dai = &twl4030_dai[TWL4030_DAI_VOICE], + .init = sdp3430_twl4030_voice_init, + .ops = &sdp3430_voice_ops, + }, }; /* Audio machine driver */ static struct snd_soc_card snd_soc_sdp3430 = { .name = "SDP3430", .platform = &omap_soc_platform, - .dai_link = &sdp3430_dai, - .num_links = 1, + .dai_link = sdp3430_dai, + .num_links = ARRAY_SIZE(sdp3430_dai), }; /* Audio subsystem */ @@ -236,7 +302,8 @@ static int __init sdp3430_soc_init(void) platform_set_drvdata(sdp3430_snd_device, &sdp3430_snd_devdata); sdp3430_snd_devdata.dev = &sdp3430_snd_device->dev; - *(unsigned int *)sdp3430_dai.cpu_dai->private_data = 1; /* McBSP2 */ + *(unsigned int *)sdp3430_dai[0].cpu_dai->private_data = 1; /* McBSP2 */ + *(unsigned int *)sdp3430_dai[1].cpu_dai->private_data = 2; /* McBSP3 */ ret = platform_device_add(sdp3430_snd_device); if (ret) -- cgit v1.2.3 From e24805dd85283ac0912b9c400768a4d171b400ff Mon Sep 17 00:00:00 2001 From: Atsushi Nemoto Date: Tue, 19 May 2009 22:12:15 +0900 Subject: ASoC: Add TXx9 AC link controller driver (v3) This patch adds support for the integrated ACLC of the TXx9 family. Signed-off-by: Atsushi Nemoto Signed-off-by: Mark Brown --- sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/txx9/Kconfig | 29 +++ sound/soc/txx9/Makefile | 11 + sound/soc/txx9/txx9aclc-ac97.c | 255 ++++++++++++++++++++++ sound/soc/txx9/txx9aclc-generic.c | 98 +++++++++ sound/soc/txx9/txx9aclc.c | 430 ++++++++++++++++++++++++++++++++++++++ sound/soc/txx9/txx9aclc.h | 83 ++++++++ 8 files changed, 908 insertions(+) create mode 100644 sound/soc/txx9/Kconfig create mode 100644 sound/soc/txx9/Makefile create mode 100644 sound/soc/txx9/txx9aclc-ac97.c create mode 100644 sound/soc/txx9/txx9aclc-generic.c create mode 100644 sound/soc/txx9/txx9aclc.c create mode 100644 sound/soc/txx9/txx9aclc.h (limited to 'sound/soc') diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index 3304f9dd92f..d3e786a9a0a 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -34,6 +34,7 @@ source "sound/soc/pxa/Kconfig" source "sound/soc/s3c24xx/Kconfig" source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig" +source "sound/soc/txx9/Kconfig" # Supported codecs source "sound/soc/codecs/Kconfig" diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 8943a140c81..6f1e28de23c 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -12,3 +12,4 @@ obj-$(CONFIG_SND_SOC) += pxa/ obj-$(CONFIG_SND_SOC) += s3c24xx/ obj-$(CONFIG_SND_SOC) += s6000/ obj-$(CONFIG_SND_SOC) += sh/ +obj-$(CONFIG_SND_SOC) += txx9/ diff --git a/sound/soc/txx9/Kconfig b/sound/soc/txx9/Kconfig new file mode 100644 index 00000000000..ebc9327eae7 --- /dev/null +++ b/sound/soc/txx9/Kconfig @@ -0,0 +1,29 @@ +## +## TXx9 ACLC +## +config SND_SOC_TXX9ACLC + tristate "SoC Audio for TXx9" + depends on HAS_TXX9_ACLC && TXX9_DMAC + help + This option enables support for the AC Link Controllers in TXx9 SoC. + +config HAS_TXX9_ACLC + bool + +config SND_SOC_TXX9ACLC_AC97 + tristate + select AC97_BUS + select SND_AC97_CODEC + select SND_SOC_AC97_BUS + + +## +## Boards +## +config SND_SOC_TXX9ACLC_GENERIC + tristate "Generic TXx9 ACLC sound machine" + depends on SND_SOC_TXX9ACLC + select SND_SOC_TXX9ACLC_AC97 + select SND_SOC_AC97_CODEC + help + This is a generic AC97 sound machine for use in TXx9 based systems. diff --git a/sound/soc/txx9/Makefile b/sound/soc/txx9/Makefile new file mode 100644 index 00000000000..551f16c0c4f --- /dev/null +++ b/sound/soc/txx9/Makefile @@ -0,0 +1,11 @@ +# Platform +snd-soc-txx9aclc-objs := txx9aclc.o +snd-soc-txx9aclc-ac97-objs := txx9aclc-ac97.o + +obj-$(CONFIG_SND_SOC_TXX9ACLC) += snd-soc-txx9aclc.o +obj-$(CONFIG_SND_SOC_TXX9ACLC_AC97) += snd-soc-txx9aclc-ac97.o + +# Machine +snd-soc-txx9aclc-generic-objs := txx9aclc-generic.o + +obj-$(CONFIG_SND_SOC_TXX9ACLC_GENERIC) += snd-soc-txx9aclc-generic.o diff --git a/sound/soc/txx9/txx9aclc-ac97.c b/sound/soc/txx9/txx9aclc-ac97.c new file mode 100644 index 00000000000..0f83bdb9b16 --- /dev/null +++ b/sound/soc/txx9/txx9aclc-ac97.c @@ -0,0 +1,255 @@ +/* + * TXx9 ACLC AC97 driver + * + * Copyright (C) 2009 Atsushi Nemoto + * + * Based on RBTX49xx patch from CELF patch archive. + * (C) Copyright TOSHIBA CORPORATION 2004-2006 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include "txx9aclc.h" + +#define AC97_DIR \ + (SND_SOC_DAIDIR_PLAYBACK | SND_SOC_DAIDIR_CAPTURE) + +#define AC97_RATES \ + SNDRV_PCM_RATE_8000_48000 + +#ifdef __BIG_ENDIAN +#define AC97_FMTS SNDRV_PCM_FMTBIT_S16_BE +#else +#define AC97_FMTS SNDRV_PCM_FMTBIT_S16_LE +#endif + +static DECLARE_WAIT_QUEUE_HEAD(ac97_waitq); + +/* REVISIT: How to find txx9aclc_soc_device from snd_ac97? */ +static struct txx9aclc_soc_device *txx9aclc_soc_dev; + +static int txx9aclc_regready(struct txx9aclc_soc_device *dev) +{ + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); + + return __raw_readl(drvdata->base + ACINTSTS) & ACINT_REGACCRDY; +} + +/* AC97 controller reads codec register */ +static unsigned short txx9aclc_ac97_read(struct snd_ac97 *ac97, + unsigned short reg) +{ + struct txx9aclc_soc_device *dev = txx9aclc_soc_dev; + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); + void __iomem *base = drvdata->base; + u32 dat; + + if (!(__raw_readl(base + ACINTSTS) & ACINT_CODECRDY(ac97->num))) + return 0xffff; + reg |= ac97->num << 7; + dat = (reg << ACREGACC_REG_SHIFT) | ACREGACC_READ; + __raw_writel(dat, base + ACREGACC); + __raw_writel(ACINT_REGACCRDY, base + ACINTEN); + if (!wait_event_timeout(ac97_waitq, txx9aclc_regready(dev), HZ)) { + __raw_writel(ACINT_REGACCRDY, base + ACINTDIS); + dev_err(dev->soc_dev.dev, "ac97 read timeout (reg %#x)\n", reg); + dat = 0xffff; + goto done; + } + dat = __raw_readl(base + ACREGACC); + if (((dat >> ACREGACC_REG_SHIFT) & 0xff) != reg) { + dev_err(dev->soc_dev.dev, "reg mismatch %x with %x\n", + dat, reg); + dat = 0xffff; + goto done; + } + dat = (dat >> ACREGACC_DAT_SHIFT) & 0xffff; +done: + __raw_writel(ACINT_REGACCRDY, base + ACINTDIS); + return dat; +} + +/* AC97 controller writes to codec register */ +static void txx9aclc_ac97_write(struct snd_ac97 *ac97, unsigned short reg, + unsigned short val) +{ + struct txx9aclc_soc_device *dev = txx9aclc_soc_dev; + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); + void __iomem *base = drvdata->base; + + __raw_writel(((reg | (ac97->num << 7)) << ACREGACC_REG_SHIFT) | + (val << ACREGACC_DAT_SHIFT), + base + ACREGACC); + __raw_writel(ACINT_REGACCRDY, base + ACINTEN); + if (!wait_event_timeout(ac97_waitq, txx9aclc_regready(dev), HZ)) { + dev_err(dev->soc_dev.dev, + "ac97 write timeout (reg %#x)\n", reg); + } + __raw_writel(ACINT_REGACCRDY, base + ACINTDIS); +} + +static void txx9aclc_ac97_cold_reset(struct snd_ac97 *ac97) +{ + struct txx9aclc_soc_device *dev = txx9aclc_soc_dev; + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); + void __iomem *base = drvdata->base; + u32 ready = ACINT_CODECRDY(ac97->num) | ACINT_REGACCRDY; + + __raw_writel(ACCTL_ENLINK, base + ACCTLDIS); + mmiowb(); + udelay(1); + __raw_writel(ACCTL_ENLINK, base + ACCTLEN); + /* wait for primary codec ready status */ + __raw_writel(ready, base + ACINTEN); + if (!wait_event_timeout(ac97_waitq, + (__raw_readl(base + ACINTSTS) & ready) == ready, + HZ)) { + dev_err(&ac97->dev, "primary codec is not ready " + "(status %#x)\n", + __raw_readl(base + ACINTSTS)); + } + __raw_writel(ACINT_REGACCRDY, base + ACINTSTS); + __raw_writel(ready, base + ACINTDIS); +} + +/* AC97 controller operations */ +struct snd_ac97_bus_ops soc_ac97_ops = { + .read = txx9aclc_ac97_read, + .write = txx9aclc_ac97_write, + .reset = txx9aclc_ac97_cold_reset, +}; +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +static irqreturn_t txx9aclc_ac97_irq(int irq, void *dev_id) +{ + struct txx9aclc_plat_drvdata *drvdata = dev_id; + void __iomem *base = drvdata->base; + + __raw_writel(__raw_readl(base + ACINTMSTS), base + ACINTDIS); + wake_up(&ac97_waitq); + return IRQ_HANDLED; +} + +static int txx9aclc_ac97_probe(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct txx9aclc_soc_device *dev = + container_of(socdev, struct txx9aclc_soc_device, soc_dev); + + dev->aclc_pdev = to_platform_device(dai->dev); + txx9aclc_soc_dev = dev; + return 0; +} + +static void txx9aclc_ac97_remove(struct platform_device *pdev, + struct snd_soc_dai *dai) +{ + struct platform_device *aclc_pdev = to_platform_device(dai->dev); + struct txx9aclc_plat_drvdata *drvdata = platform_get_drvdata(aclc_pdev); + + /* disable AC-link */ + __raw_writel(ACCTL_ENLINK, drvdata->base + ACCTLDIS); + txx9aclc_soc_dev = NULL; +} + +struct snd_soc_dai txx9aclc_ac97_dai = { + .name = "txx9aclc_ac97", + .ac97_control = 1, + .probe = txx9aclc_ac97_probe, + .remove = txx9aclc_ac97_remove, + .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, + }, +}; +EXPORT_SYMBOL_GPL(txx9aclc_ac97_dai); + +static int __devinit txx9aclc_ac97_dev_probe(struct platform_device *pdev) +{ + struct txx9aclc_plat_drvdata *drvdata; + struct resource *r; + int err; + int irq; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) + return -EBUSY; + + if (!devm_request_mem_region(&pdev->dev, r->start, resource_size(r), + dev_name(&pdev->dev))) + return -EBUSY; + + drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + platform_set_drvdata(pdev, drvdata); + drvdata->physbase = r->start; + if (sizeof(drvdata->physbase) > sizeof(r->start) && + r->start >= TXX9_DIRECTMAP_BASE && + r->start < TXX9_DIRECTMAP_BASE + 0x400000) + drvdata->physbase |= 0xf00000000ull; + drvdata->base = devm_ioremap(&pdev->dev, r->start, resource_size(r)); + if (!drvdata->base) + return -EBUSY; + err = devm_request_irq(&pdev->dev, irq, txx9aclc_ac97_irq, + IRQF_DISABLED, dev_name(&pdev->dev), drvdata); + if (err < 0) + return err; + + txx9aclc_ac97_dai.dev = &pdev->dev; + return snd_soc_register_dai(&txx9aclc_ac97_dai); +} + +static int __devexit txx9aclc_ac97_dev_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&txx9aclc_ac97_dai); + return 0; +} + +static struct platform_driver txx9aclc_ac97_driver = { + .probe = txx9aclc_ac97_dev_probe, + .remove = __devexit_p(txx9aclc_ac97_dev_remove), + .driver = { + .name = "txx9aclc-ac97", + .owner = THIS_MODULE, + }, +}; + +static int __init txx9aclc_ac97_init(void) +{ + return platform_driver_register(&txx9aclc_ac97_driver); +} + +static void __exit txx9aclc_ac97_exit(void) +{ + platform_driver_unregister(&txx9aclc_ac97_driver); +} + +module_init(txx9aclc_ac97_init); +module_exit(txx9aclc_ac97_exit); + +MODULE_AUTHOR("Atsushi Nemoto "); +MODULE_DESCRIPTION("TXx9 ACLC AC97 driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/txx9/txx9aclc-generic.c b/sound/soc/txx9/txx9aclc-generic.c new file mode 100644 index 00000000000..3175de9a92c --- /dev/null +++ b/sound/soc/txx9/txx9aclc-generic.c @@ -0,0 +1,98 @@ +/* + * Generic TXx9 ACLC machine driver + * + * Copyright (C) 2009 Atsushi Nemoto + * + * Based on RBTX49xx patch from CELF patch archive. + * (C) Copyright TOSHIBA CORPORATION 2004-2006 + * + * 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. + * + * This is a very generic AC97 sound machine driver for boards which + * have (AC97) audio at ACLC (e.g. RBTX49XX boards). + */ + +#include +#include +#include +#include +#include +#include "../codecs/ac97.h" +#include "txx9aclc.h" + +static struct snd_soc_dai_link txx9aclc_generic_dai = { + .name = "AC97", + .stream_name = "AC97 HiFi", + .cpu_dai = &txx9aclc_ac97_dai, + .codec_dai = &ac97_dai, +}; + +static struct snd_soc_card txx9aclc_generic_card = { + .name = "Generic TXx9 ACLC Audio", + .platform = &txx9aclc_soc_platform, + .dai_link = &txx9aclc_generic_dai, + .num_links = 1, +}; + +static struct txx9aclc_soc_device txx9aclc_generic_soc_device = { + .soc_dev = { + .card = &txx9aclc_generic_card, + .codec_dev = &soc_codec_dev_ac97, + }, +}; + +static int __init txx9aclc_generic_probe(struct platform_device *pdev) +{ + struct txx9aclc_soc_device *dev = &txx9aclc_generic_soc_device; + struct platform_device *soc_pdev; + int ret; + + soc_pdev = platform_device_alloc("soc-audio", -1); + if (!soc_pdev) + return -ENOMEM; + platform_set_drvdata(soc_pdev, &dev->soc_dev); + dev->soc_dev.dev = &soc_pdev->dev; + ret = platform_device_add(soc_pdev); + if (ret) { + platform_device_put(soc_pdev); + return ret; + } + platform_set_drvdata(pdev, soc_pdev); + return 0; +} + +static int __exit txx9aclc_generic_remove(struct platform_device *pdev) +{ + struct platform_device *soc_pdev = platform_get_drvdata(pdev); + + platform_device_unregister(soc_pdev); + return 0; +} + +static struct platform_driver txx9aclc_generic_driver = { + .remove = txx9aclc_generic_remove, + .driver = { + .name = "txx9aclc-generic", + .owner = THIS_MODULE, + }, +}; + +static int __init txx9aclc_generic_init(void) +{ + return platform_driver_probe(&txx9aclc_generic_driver, + txx9aclc_generic_probe); +} + +static void __exit txx9aclc_generic_exit(void) +{ + platform_driver_unregister(&txx9aclc_generic_driver); +} + +module_init(txx9aclc_generic_init); +module_exit(txx9aclc_generic_exit); + +MODULE_AUTHOR("Atsushi Nemoto "); +MODULE_DESCRIPTION("Generic TXx9 ACLC ALSA SoC audio driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/txx9/txx9aclc.c b/sound/soc/txx9/txx9aclc.c new file mode 100644 index 00000000000..fa336616152 --- /dev/null +++ b/sound/soc/txx9/txx9aclc.c @@ -0,0 +1,430 @@ +/* + * Generic TXx9 ACLC platform driver + * + * Copyright (C) 2009 Atsushi Nemoto + * + * Based on RBTX49xx patch from CELF patch archive. + * (C) Copyright TOSHIBA CORPORATION 2004-2006 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include "txx9aclc.h" + +static const struct snd_pcm_hardware txx9aclc_pcm_hardware = { + /* + * REVISIT: SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID + * needs more works for noncoherent MIPS. + */ + .info = SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_PAUSE, +#ifdef __BIG_ENDIAN + .formats = SNDRV_PCM_FMTBIT_S16_BE, +#else + .formats = SNDRV_PCM_FMTBIT_S16_LE, +#endif + .period_bytes_min = 1024, + .period_bytes_max = 8 * 1024, + .periods_min = 2, + .periods_max = 4096, + .buffer_bytes_max = 32 * 1024, +}; + +static int txx9aclc_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); + struct snd_soc_device *socdev = rtd->socdev; + struct snd_pcm_runtime *runtime = substream->runtime; + struct txx9aclc_dmadata *dmadata = runtime->private_data; + int ret; + + ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params)); + if (ret < 0) + return ret; + + dev_dbg(socdev->dev, + "runtime->dma_area = %#lx dma_addr = %#lx dma_bytes = %zd " + "runtime->min_align %ld\n", + (unsigned long)runtime->dma_area, + (unsigned long)runtime->dma_addr, runtime->dma_bytes, + runtime->min_align); + dev_dbg(socdev->dev, + "periods %d period_bytes %d stream %d\n", + params_periods(params), params_period_bytes(params), + substream->stream); + + dmadata->substream = substream; + dmadata->pos = 0; + return 0; +} + +static int txx9aclc_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + +static int txx9aclc_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct txx9aclc_dmadata *dmadata = runtime->private_data; + + dmadata->dma_addr = runtime->dma_addr; + dmadata->buffer_bytes = snd_pcm_lib_buffer_bytes(substream); + dmadata->period_bytes = snd_pcm_lib_period_bytes(substream); + + if (dmadata->buffer_bytes == dmadata->period_bytes) { + dmadata->frag_bytes = dmadata->period_bytes >> 1; + dmadata->frags = 2; + } else { + dmadata->frag_bytes = dmadata->period_bytes; + dmadata->frags = dmadata->buffer_bytes / dmadata->period_bytes; + } + dmadata->frag_count = 0; + dmadata->pos = 0; + return 0; +} + +static void txx9aclc_dma_complete(void *arg) +{ + struct txx9aclc_dmadata *dmadata = arg; + unsigned long flags; + + /* dma completion handler cannot submit new operations */ + spin_lock_irqsave(&dmadata->dma_lock, flags); + if (dmadata->frag_count >= 0) { + dmadata->dmacount--; + BUG_ON(dmadata->dmacount < 0); + tasklet_schedule(&dmadata->tasklet); + } + spin_unlock_irqrestore(&dmadata->dma_lock, flags); +} + +static struct dma_async_tx_descriptor * +txx9aclc_dma_submit(struct txx9aclc_dmadata *dmadata, dma_addr_t buf_dma_addr) +{ + struct dma_chan *chan = dmadata->dma_chan; + struct dma_async_tx_descriptor *desc; + struct scatterlist sg; + + sg_init_table(&sg, 1); + sg_set_page(&sg, pfn_to_page(PFN_DOWN(buf_dma_addr)), + dmadata->frag_bytes, buf_dma_addr & (PAGE_SIZE - 1)); + sg_dma_address(&sg) = buf_dma_addr; + desc = chan->device->device_prep_slave_sg(chan, &sg, 1, + dmadata->substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + DMA_TO_DEVICE : DMA_FROM_DEVICE, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!desc) { + dev_err(&chan->dev->device, "cannot prepare slave dma\n"); + return NULL; + } + desc->callback = txx9aclc_dma_complete; + desc->callback_param = dmadata; + desc->tx_submit(desc); + return desc; +} + +#define NR_DMA_CHAIN 2 + +static void txx9aclc_dma_tasklet(unsigned long data) +{ + struct txx9aclc_dmadata *dmadata = (struct txx9aclc_dmadata *)data; + struct dma_chan *chan = dmadata->dma_chan; + struct dma_async_tx_descriptor *desc; + struct snd_pcm_substream *substream = dmadata->substream; + u32 ctlbit = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + ACCTL_AUDODMA : ACCTL_AUDIDMA; + int i; + unsigned long flags; + + spin_lock_irqsave(&dmadata->dma_lock, flags); + if (dmadata->frag_count < 0) { + struct txx9aclc_soc_device *dev = + container_of(dmadata, struct txx9aclc_soc_device, + dmadata[substream->stream]); + struct txx9aclc_plat_drvdata *drvdata = + txx9aclc_get_plat_drvdata(dev); + void __iomem *base = drvdata->base; + + spin_unlock_irqrestore(&dmadata->dma_lock, flags); + chan->device->device_terminate_all(chan); + /* first time */ + for (i = 0; i < NR_DMA_CHAIN; i++) { + desc = txx9aclc_dma_submit(dmadata, + dmadata->dma_addr + i * dmadata->frag_bytes); + if (!desc) + return; + } + dmadata->dmacount = NR_DMA_CHAIN; + chan->device->device_issue_pending(chan); + spin_lock_irqsave(&dmadata->dma_lock, flags); + __raw_writel(ctlbit, base + ACCTLEN); + dmadata->frag_count = NR_DMA_CHAIN % dmadata->frags; + spin_unlock_irqrestore(&dmadata->dma_lock, flags); + return; + } + BUG_ON(dmadata->dmacount >= NR_DMA_CHAIN); + while (dmadata->dmacount < NR_DMA_CHAIN) { + dmadata->dmacount++; + spin_unlock_irqrestore(&dmadata->dma_lock, flags); + desc = txx9aclc_dma_submit(dmadata, + dmadata->dma_addr + + dmadata->frag_count * dmadata->frag_bytes); + if (!desc) + return; + chan->device->device_issue_pending(chan); + + spin_lock_irqsave(&dmadata->dma_lock, flags); + dmadata->frag_count++; + dmadata->frag_count %= dmadata->frags; + dmadata->pos += dmadata->frag_bytes; + dmadata->pos %= dmadata->buffer_bytes; + if ((dmadata->frag_count * dmadata->frag_bytes) % + dmadata->period_bytes == 0) + snd_pcm_period_elapsed(substream); + } + spin_unlock_irqrestore(&dmadata->dma_lock, flags); +} + +static int txx9aclc_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct txx9aclc_dmadata *dmadata = substream->runtime->private_data; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct txx9aclc_soc_device *dev = + container_of(rtd->socdev, struct txx9aclc_soc_device, soc_dev); + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); + void __iomem *base = drvdata->base; + unsigned long flags; + int ret = 0; + u32 ctlbit = substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? + ACCTL_AUDODMA : ACCTL_AUDIDMA; + + spin_lock_irqsave(&dmadata->dma_lock, flags); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + dmadata->frag_count = -1; + tasklet_schedule(&dmadata->tasklet); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + __raw_writel(ctlbit, base + ACCTLDIS); + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + __raw_writel(ctlbit, base + ACCTLEN); + break; + default: + ret = -EINVAL; + } + spin_unlock_irqrestore(&dmadata->dma_lock, flags); + return ret; +} + +static snd_pcm_uframes_t +txx9aclc_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct txx9aclc_dmadata *dmadata = substream->runtime->private_data; + + return bytes_to_frames(substream->runtime, dmadata->pos); +} + +static int txx9aclc_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct txx9aclc_soc_device *dev = + container_of(rtd->socdev, struct txx9aclc_soc_device, soc_dev); + struct txx9aclc_dmadata *dmadata = &dev->dmadata[substream->stream]; + int ret; + + ret = snd_soc_set_runtime_hwparams(substream, &txx9aclc_pcm_hardware); + if (ret) + return ret; + /* ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; + substream->runtime->private_data = dmadata; + return 0; +} + +static int txx9aclc_pcm_close(struct snd_pcm_substream *substream) +{ + struct txx9aclc_dmadata *dmadata = substream->runtime->private_data; + struct dma_chan *chan = dmadata->dma_chan; + + dmadata->frag_count = -1; + chan->device->device_terminate_all(chan); + return 0; +} + +static struct snd_pcm_ops txx9aclc_pcm_ops = { + .open = txx9aclc_pcm_open, + .close = txx9aclc_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = txx9aclc_pcm_hw_params, + .hw_free = txx9aclc_pcm_hw_free, + .prepare = txx9aclc_pcm_prepare, + .trigger = txx9aclc_pcm_trigger, + .pointer = txx9aclc_pcm_pointer, +}; + +static void txx9aclc_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +static int txx9aclc_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + return snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + card->dev, 64 * 1024, 4 * 1024 * 1024); +} + +static bool filter(struct dma_chan *chan, void *param) +{ + struct txx9aclc_dmadata *dmadata = param; + char devname[BUS_ID_SIZE + 2]; + + sprintf(devname, "%s.%d", dmadata->dma_res->name, + (int)dmadata->dma_res->start); + if (strcmp(dev_name(chan->device->dev), devname) == 0) { + chan->private = &dmadata->dma_slave; + return true; + } + return false; +} + +static int txx9aclc_dma_init(struct txx9aclc_soc_device *dev, + struct txx9aclc_dmadata *dmadata) +{ + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); + struct txx9dmac_slave *ds = &dmadata->dma_slave; + dma_cap_mask_t mask; + + spin_lock_init(&dmadata->dma_lock); + + ds->reg_width = sizeof(u32); + if (dmadata->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ds->tx_reg = drvdata->physbase + ACAUDODAT; + ds->rx_reg = 0; + } else { + ds->tx_reg = 0; + ds->rx_reg = drvdata->physbase + ACAUDIDAT; + } + + /* Try to grab a DMA channel */ + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + dmadata->dma_chan = dma_request_channel(mask, filter, dmadata); + if (!dmadata->dma_chan) { + dev_err(dev->soc_dev.dev, + "DMA channel for %s is not available\n", + dmadata->stream == SNDRV_PCM_STREAM_PLAYBACK ? + "playback" : "capture"); + return -EBUSY; + } + tasklet_init(&dmadata->tasklet, txx9aclc_dma_tasklet, + (unsigned long)dmadata); + return 0; +} + +static int txx9aclc_pcm_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct txx9aclc_soc_device *dev = + container_of(socdev, struct txx9aclc_soc_device, soc_dev); + struct resource *r; + int i; + int ret; + + dev->dmadata[0].stream = SNDRV_PCM_STREAM_PLAYBACK; + dev->dmadata[1].stream = SNDRV_PCM_STREAM_CAPTURE; + for (i = 0; i < 2; i++) { + r = platform_get_resource(dev->aclc_pdev, IORESOURCE_DMA, i); + if (!r) { + ret = -EBUSY; + goto exit; + } + dev->dmadata[i].dma_res = r; + ret = txx9aclc_dma_init(dev, &dev->dmadata[i]); + if (ret) + goto exit; + } + return 0; + +exit: + for (i = 0; i < 2; i++) { + if (dev->dmadata[i].dma_chan) + dma_release_channel(dev->dmadata[i].dma_chan); + dev->dmadata[i].dma_chan = NULL; + } + return ret; +} + +static int txx9aclc_pcm_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct txx9aclc_soc_device *dev = + container_of(socdev, struct txx9aclc_soc_device, soc_dev); + struct txx9aclc_plat_drvdata *drvdata = txx9aclc_get_plat_drvdata(dev); + void __iomem *base = drvdata->base; + int i; + + /* disable all FIFO DMAs */ + __raw_writel(ACCTL_AUDODMA | ACCTL_AUDIDMA, base + ACCTLDIS); + /* dummy R/W to clear pending DMAREQ if any */ + __raw_writel(__raw_readl(base + ACAUDIDAT), base + ACAUDODAT); + + for (i = 0; i < 2; i++) { + struct txx9aclc_dmadata *dmadata = &dev->dmadata[i]; + struct dma_chan *chan = dmadata->dma_chan; + if (chan) { + dmadata->frag_count = -1; + chan->device->device_terminate_all(chan); + dma_release_channel(chan); + } + dev->dmadata[i].dma_chan = NULL; + } + return 0; +} + +struct snd_soc_platform txx9aclc_soc_platform = { + .name = "txx9aclc-audio", + .probe = txx9aclc_pcm_probe, + .remove = txx9aclc_pcm_remove, + .pcm_ops = &txx9aclc_pcm_ops, + .pcm_new = txx9aclc_pcm_new, + .pcm_free = txx9aclc_pcm_free_dma_buffers, +}; +EXPORT_SYMBOL_GPL(txx9aclc_soc_platform); + +static int __init txx9aclc_soc_platform_init(void) +{ + return snd_soc_register_platform(&txx9aclc_soc_platform); +} + +static void __exit txx9aclc_soc_platform_exit(void) +{ + snd_soc_unregister_platform(&txx9aclc_soc_platform); +} + +module_init(txx9aclc_soc_platform_init); +module_exit(txx9aclc_soc_platform_exit); + +MODULE_AUTHOR("Atsushi Nemoto "); +MODULE_DESCRIPTION("TXx9 ACLC Audio DMA driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/txx9/txx9aclc.h b/sound/soc/txx9/txx9aclc.h new file mode 100644 index 00000000000..6769aab41b3 --- /dev/null +++ b/sound/soc/txx9/txx9aclc.h @@ -0,0 +1,83 @@ +/* + * TXx9 SoC AC Link Controller + * + * 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 __TXX9ACLC_H +#define __TXX9ACLC_H + +#include +#include + +#define ACCTLEN 0x00 /* control enable */ +#define ACCTLDIS 0x04 /* control disable */ +#define ACCTL_ENLINK 0x00000001 /* enable/disable AC-link */ +#define ACCTL_AUDODMA 0x00000100 /* AUDODMA enable/disable */ +#define ACCTL_AUDIDMA 0x00001000 /* AUDIDMA enable/disable */ +#define ACCTL_AUDOEHLT 0x00010000 /* AUDO error halt + enable/disable */ +#define ACCTL_AUDIEHLT 0x00100000 /* AUDI error halt + enable/disable */ +#define ACREGACC 0x08 /* codec register access */ +#define ACREGACC_DAT_SHIFT 0 /* data field */ +#define ACREGACC_REG_SHIFT 16 /* address field */ +#define ACREGACC_CODECID_SHIFT 24 /* CODEC ID field */ +#define ACREGACC_READ 0x80000000 /* CODEC read */ +#define ACREGACC_WRITE 0x00000000 /* CODEC write */ +#define ACINTSTS 0x10 /* interrupt status */ +#define ACINTMSTS 0x14 /* interrupt masked status */ +#define ACINTEN 0x18 /* interrupt enable */ +#define ACINTDIS 0x1c /* interrupt disable */ +#define ACINT_CODECRDY(n) (0x00000001 << (n)) /* CODECn ready */ +#define ACINT_REGACCRDY 0x00000010 /* ACREGACC ready */ +#define ACINT_AUDOERR 0x00000100 /* AUDO underrun error */ +#define ACINT_AUDIERR 0x00001000 /* AUDI overrun error */ +#define ACDMASTS 0x80 /* DMA request status */ +#define ACDMA_AUDO 0x00000001 /* AUDODMA pending */ +#define ACDMA_AUDI 0x00000010 /* AUDIDMA pending */ +#define ACAUDODAT 0xa0 /* audio out data */ +#define ACAUDIDAT 0xb0 /* audio in data */ +#define ACREVID 0xfc /* revision ID */ + +struct txx9aclc_dmadata { + struct resource *dma_res; + struct txx9dmac_slave dma_slave; + struct dma_chan *dma_chan; + struct tasklet_struct tasklet; + spinlock_t dma_lock; + int stream; /* SNDRV_PCM_STREAM_PLAYBACK or SNDRV_PCM_STREAM_CAPTURE */ + struct snd_pcm_substream *substream; + unsigned long pos; + dma_addr_t dma_addr; + unsigned long buffer_bytes; + unsigned long period_bytes; + unsigned long frag_bytes; + int frags; + int frag_count; + int dmacount; +}; + +struct txx9aclc_plat_drvdata { + void __iomem *base; + u64 physbase; +}; + +struct txx9aclc_soc_device { + struct snd_soc_device soc_dev; + struct platform_device *aclc_pdev; /* for ioresources, drvdata */ + struct txx9aclc_dmadata dmadata[2]; +}; + +static inline struct txx9aclc_plat_drvdata *txx9aclc_get_plat_drvdata( + struct txx9aclc_soc_device *sdev) +{ + return platform_get_drvdata(sdev->aclc_pdev); +} + +extern struct snd_soc_platform txx9aclc_soc_platform; +extern struct snd_soc_dai txx9aclc_ac97_dai; + +#endif /* __TXX9ACLC_H */ -- cgit v1.2.3 From 4005d39a5f5549f1f6afe88abceed78b2ab225b6 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 18 May 2009 16:02:04 +0300 Subject: ASoC: TWL4030: Change DAPM routings and controls for DACs and PGAs Restructuring the twl4030 codec's DAPM routing to be able to handle the power sequences correctly. The twl4030 codec internal implementation have this order: DAC -> Analog PGA -> Mixer/Mux While the ASoC framework expects the following order: DAC -> Mixer -> Analog PGA This patch moves the Analog PGA handling from SND_SOC_DAPM_PGA to _MIXER and adds two levels of mixer to handle the digital and analog loopback functionality. Now the analog loopback does not powers on any of the DACs. Signed-off-by: Peter Ujfalusi Tested-by: Anuj Aggarwal Tested-by: Jarkko Nikula Tested-by: Misael Lopez Cruz Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 141 +++++++++++++++++++++++---------------------- 1 file changed, 71 insertions(+), 70 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 731534c19b7..99fe44f7050 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -1051,18 +1051,6 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_DAC("DAC Voice", "Voice Playback", SND_SOC_NOPM, 0, 0), - /* Analog PGAs */ - SND_SOC_DAPM_PGA("ARXR1_APGA", TWL4030_REG_ARXR1_APGA_CTL, - 0, 0, NULL, 0), - SND_SOC_DAPM_PGA("ARXL1_APGA", TWL4030_REG_ARXL1_APGA_CTL, - 0, 0, NULL, 0), - SND_SOC_DAPM_PGA("ARXR2_APGA", TWL4030_REG_ARXR2_APGA_CTL, - 0, 0, NULL, 0), - SND_SOC_DAPM_PGA("ARXL2_APGA", TWL4030_REG_ARXL2_APGA_CTL, - 0, 0, NULL, 0), - SND_SOC_DAPM_PGA("VDL_APGA", TWL4030_REG_VDL_APGA_CTL, - 0, 0, NULL, 0), - /* Analog bypasses */ SND_SOC_DAPM_SWITCH_E("Right1 Analog Loopback", SND_SOC_NOPM, 0, 0, &twl4030_dapm_abypassr1_control, bypass_event, @@ -1091,16 +1079,29 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { &twl4030_dapm_dbypassv_control, bypass_event, SND_SOC_DAPM_POST_REG), - SND_SOC_DAPM_MIXER("Analog R1 Playback Mixer", TWL4030_REG_AVDAC_CTL, - 0, 0, NULL, 0), - SND_SOC_DAPM_MIXER("Analog L1 Playback Mixer", TWL4030_REG_AVDAC_CTL, - 1, 0, NULL, 0), - SND_SOC_DAPM_MIXER("Analog R2 Playback Mixer", TWL4030_REG_AVDAC_CTL, - 2, 0, NULL, 0), - SND_SOC_DAPM_MIXER("Analog L2 Playback Mixer", TWL4030_REG_AVDAC_CTL, - 3, 0, NULL, 0), - SND_SOC_DAPM_MIXER("Analog Voice Playback Mixer", TWL4030_REG_AVDAC_CTL, - 4, 0, NULL, 0), + /* Digital mixers, power control for the physical DACs */ + SND_SOC_DAPM_MIXER("Digital R1 Playback Mixer", + TWL4030_REG_AVDAC_CTL, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Digital L1 Playback Mixer", + TWL4030_REG_AVDAC_CTL, 1, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Digital R2 Playback Mixer", + TWL4030_REG_AVDAC_CTL, 2, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Digital L2 Playback Mixer", + TWL4030_REG_AVDAC_CTL, 3, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Digital Voice Playback Mixer", + TWL4030_REG_AVDAC_CTL, 4, 0, NULL, 0), + + /* Analog mixers, power control for the physical PGAs */ + SND_SOC_DAPM_MIXER("Analog R1 Playback Mixer", + TWL4030_REG_ARXR1_APGA_CTL, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Analog L1 Playback Mixer", + TWL4030_REG_ARXL1_APGA_CTL, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Analog R2 Playback Mixer", + TWL4030_REG_ARXR2_APGA_CTL, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Analog L2 Playback Mixer", + TWL4030_REG_ARXL2_APGA_CTL, 0, 0, NULL, 0), + SND_SOC_DAPM_MIXER("Analog Voice Playback Mixer", + TWL4030_REG_VDL_APGA_CTL, 0, 0, NULL, 0), /* Output MIXER controls */ /* Earpiece */ @@ -1194,60 +1195,60 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { }; static const struct snd_soc_dapm_route intercon[] = { - {"Analog L1 Playback Mixer", NULL, "DAC Left1"}, - {"Analog R1 Playback Mixer", NULL, "DAC Right1"}, - {"Analog L2 Playback Mixer", NULL, "DAC Left2"}, - {"Analog R2 Playback Mixer", NULL, "DAC Right2"}, - {"Analog Voice Playback Mixer", NULL, "DAC Voice"}, - - {"ARXL1_APGA", NULL, "Analog L1 Playback Mixer"}, - {"ARXR1_APGA", NULL, "Analog R1 Playback Mixer"}, - {"ARXL2_APGA", NULL, "Analog L2 Playback Mixer"}, - {"ARXR2_APGA", NULL, "Analog R2 Playback Mixer"}, - {"VDL_APGA", NULL, "Analog Voice Playback Mixer"}, + {"Digital L1 Playback Mixer", NULL, "DAC Left1"}, + {"Digital R1 Playback Mixer", NULL, "DAC Right1"}, + {"Digital L2 Playback Mixer", NULL, "DAC Left2"}, + {"Digital R2 Playback Mixer", NULL, "DAC Right2"}, + {"Digital Voice Playback Mixer", NULL, "DAC Voice"}, + + {"Analog L1 Playback Mixer", NULL, "Digital L1 Playback Mixer"}, + {"Analog R1 Playback Mixer", NULL, "Digital R1 Playback Mixer"}, + {"Analog L2 Playback Mixer", NULL, "Digital L2 Playback Mixer"}, + {"Analog R2 Playback Mixer", NULL, "Digital R2 Playback Mixer"}, + {"Analog Voice Playback Mixer", NULL, "Digital Voice Playback Mixer"}, /* Internal playback routings */ /* Earpiece */ - {"Earpiece Mixer", "Voice", "VDL_APGA"}, - {"Earpiece Mixer", "AudioL1", "ARXL1_APGA"}, - {"Earpiece Mixer", "AudioL2", "ARXL2_APGA"}, - {"Earpiece Mixer", "AudioR1", "ARXR1_APGA"}, + {"Earpiece Mixer", "Voice", "Analog Voice Playback Mixer"}, + {"Earpiece Mixer", "AudioL1", "Analog L1 Playback Mixer"}, + {"Earpiece Mixer", "AudioL2", "Analog L2 Playback Mixer"}, + {"Earpiece Mixer", "AudioR1", "Analog R1 Playback Mixer"}, /* PreDrivL */ - {"PredriveL Mixer", "Voice", "VDL_APGA"}, - {"PredriveL Mixer", "AudioL1", "ARXL1_APGA"}, - {"PredriveL Mixer", "AudioL2", "ARXL2_APGA"}, - {"PredriveL Mixer", "AudioR2", "ARXR2_APGA"}, + {"PredriveL Mixer", "Voice", "Analog Voice Playback Mixer"}, + {"PredriveL Mixer", "AudioL1", "Analog L1 Playback Mixer"}, + {"PredriveL Mixer", "AudioL2", "Analog L2 Playback Mixer"}, + {"PredriveL Mixer", "AudioR2", "Analog R2 Playback Mixer"}, /* PreDrivR */ - {"PredriveR Mixer", "Voice", "VDL_APGA"}, - {"PredriveR Mixer", "AudioR1", "ARXR1_APGA"}, - {"PredriveR Mixer", "AudioR2", "ARXR2_APGA"}, - {"PredriveR Mixer", "AudioL2", "ARXL2_APGA"}, + {"PredriveR Mixer", "Voice", "Analog Voice Playback Mixer"}, + {"PredriveR Mixer", "AudioR1", "Analog R1 Playback Mixer"}, + {"PredriveR Mixer", "AudioR2", "Analog R2 Playback Mixer"}, + {"PredriveR Mixer", "AudioL2", "Analog L2 Playback Mixer"}, /* HeadsetL */ - {"HeadsetL Mixer", "Voice", "VDL_APGA"}, - {"HeadsetL Mixer", "AudioL1", "ARXL1_APGA"}, - {"HeadsetL Mixer", "AudioL2", "ARXL2_APGA"}, + {"HeadsetL Mixer", "Voice", "Analog Voice Playback Mixer"}, + {"HeadsetL Mixer", "AudioL1", "Analog L1 Playback Mixer"}, + {"HeadsetL Mixer", "AudioL2", "Analog L2 Playback Mixer"}, /* HeadsetR */ - {"HeadsetR Mixer", "Voice", "VDL_APGA"}, - {"HeadsetR Mixer", "AudioR1", "ARXR1_APGA"}, - {"HeadsetR Mixer", "AudioR2", "ARXR2_APGA"}, + {"HeadsetR Mixer", "Voice", "Analog Voice Playback Mixer"}, + {"HeadsetR Mixer", "AudioR1", "Analog R1 Playback Mixer"}, + {"HeadsetR Mixer", "AudioR2", "Analog R2 Playback Mixer"}, /* CarkitL */ - {"CarkitL Mixer", "Voice", "VDL_APGA"}, - {"CarkitL Mixer", "AudioL1", "ARXL1_APGA"}, - {"CarkitL Mixer", "AudioL2", "ARXL2_APGA"}, + {"CarkitL Mixer", "Voice", "Analog Voice Playback Mixer"}, + {"CarkitL Mixer", "AudioL1", "Analog L1 Playback Mixer"}, + {"CarkitL Mixer", "AudioL2", "Analog L2 Playback Mixer"}, /* CarkitR */ - {"CarkitR Mixer", "Voice", "VDL_APGA"}, - {"CarkitR Mixer", "AudioR1", "ARXR1_APGA"}, - {"CarkitR Mixer", "AudioR2", "ARXR2_APGA"}, + {"CarkitR Mixer", "Voice", "Analog Voice Playback Mixer"}, + {"CarkitR Mixer", "AudioR1", "Analog R1 Playback Mixer"}, + {"CarkitR Mixer", "AudioR2", "Analog R2 Playback Mixer"}, /* HandsfreeL */ - {"HandsfreeL Mux", "Voice", "VDL_APGA"}, - {"HandsfreeL Mux", "AudioL1", "ARXL1_APGA"}, - {"HandsfreeL Mux", "AudioL2", "ARXL2_APGA"}, - {"HandsfreeL Mux", "AudioR2", "ARXR2_APGA"}, + {"HandsfreeL Mux", "Voice", "Analog Voice Playback Mixer"}, + {"HandsfreeL Mux", "AudioL1", "Analog L1 Playback Mixer"}, + {"HandsfreeL Mux", "AudioL2", "Analog L2 Playback Mixer"}, + {"HandsfreeL Mux", "AudioR2", "Analog R2 Playback Mixer"}, /* HandsfreeR */ - {"HandsfreeR Mux", "Voice", "VDL_APGA"}, - {"HandsfreeR Mux", "AudioR1", "ARXR1_APGA"}, - {"HandsfreeR Mux", "AudioR2", "ARXR2_APGA"}, - {"HandsfreeR Mux", "AudioL2", "ARXL2_APGA"}, + {"HandsfreeR Mux", "Voice", "Analog Voice Playback Mixer"}, + {"HandsfreeR Mux", "AudioR1", "Analog R1 Playback Mixer"}, + {"HandsfreeR Mux", "AudioR2", "Analog R2 Playback Mixer"}, + {"HandsfreeR Mux", "AudioL2", "Analog L2 Playback Mixer"}, /* Vibra */ {"Vibra Mux", "AudioL1", "DAC Left1"}, {"Vibra Mux", "AudioR1", "DAC Right1"}, @@ -1255,8 +1256,8 @@ static const struct snd_soc_dapm_route intercon[] = { {"Vibra Mux", "AudioR2", "DAC Right2"}, /* outputs */ - {"OUTL", NULL, "ARXL2_APGA"}, - {"OUTR", NULL, "ARXR2_APGA"}, + {"OUTL", NULL, "Analog L2 Playback Mixer"}, + {"OUTR", NULL, "Analog R2 Playback Mixer"}, {"EARPIECE", NULL, "Earpiece Mixer"}, {"PREDRIVEL", NULL, "PredriveL Mixer"}, {"PREDRIVER", NULL, "PredriveR Mixer"}, @@ -1320,9 +1321,9 @@ static const struct snd_soc_dapm_route intercon[] = { {"Left Digital Loopback", "Volume", "TX1 Capture Route"}, {"Voice Digital Loopback", "Volume", "TX2 Capture Route"}, - {"Analog R2 Playback Mixer", NULL, "Right Digital Loopback"}, - {"Analog L2 Playback Mixer", NULL, "Left Digital Loopback"}, - {"Analog Voice Playback Mixer", NULL, "Voice Digital Loopback"}, + {"Digital R2 Playback Mixer", NULL, "Right Digital Loopback"}, + {"Digital L2 Playback Mixer", NULL, "Left Digital Loopback"}, + {"Digital Voice Playback Mixer", NULL, "Voice Digital Loopback"}, }; -- cgit v1.2.3 From 6943c92e87c4aa2a6d7a1f4dbd79cf4a0b5fd67b Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 18 May 2009 16:02:05 +0300 Subject: ASoC: TWL4030: Move the Headset pop-attenuation code to PGA event This patch adds SND_SOC_DAPM_PGA_E to the headset path, which handles the headset ramp up and down sequences needed for the pop noise removal. With this patch the order of the internal components in the twl4030 codec is turned on and off in a correct order. Signed-off-by: Peter Ujfalusi Tested-by: Anuj Aggarwal Tested-by: Jarkko Nikula Tested-by: Misael Lopez Cruz Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 116 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 91 insertions(+), 25 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 99fe44f7050..f554672f67c 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -130,6 +130,12 @@ struct twl4030_priv { unsigned int rate; unsigned int sample_bits; unsigned int channels; + + unsigned int sysclk; + + /* Headset output state handling */ + unsigned int hsl_enabled; + unsigned int hsr_enabled; }; /* @@ -564,39 +570,85 @@ static int handsfree_event(struct snd_soc_dapm_widget *w, return 0; } -static int headsetl_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event) +static void headset_ramp(struct snd_soc_codec *codec, int ramp) { unsigned char hs_gain, hs_pop; + struct twl4030_priv *twl4030 = codec->private_data; + /* Base values for ramp delay calculation: 2^19 - 2^26 */ + unsigned int ramp_base[] = {524288, 1048576, 2097152, 4194304, + 8388608, 16777216, 33554432, 67108864}; - /* Save the current volume */ - hs_gain = twl4030_read_reg_cache(w->codec, TWL4030_REG_HS_GAIN_SET); - hs_pop = twl4030_read_reg_cache(w->codec, TWL4030_REG_HS_POPN_SET); + hs_gain = twl4030_read_reg_cache(codec, TWL4030_REG_HS_GAIN_SET); + hs_pop = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET); - switch (event) { - case SND_SOC_DAPM_POST_PMU: - /* Do the anti-pop/bias ramp enable according to the TRM */ + if (ramp) { + /* Headset ramp-up according to the TRM */ hs_pop |= TWL4030_VMID_EN; - twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop); - /* Is this needed? Can we just use whatever gain here? */ - twl4030_write(w->codec, TWL4030_REG_HS_GAIN_SET, - (hs_gain & (~0x0f)) | 0x0a); + twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); + twl4030_write(codec, TWL4030_REG_HS_GAIN_SET, hs_gain); hs_pop |= TWL4030_RAMP_EN; - twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop); - - /* Restore the original volume */ - twl4030_write(w->codec, TWL4030_REG_HS_GAIN_SET, hs_gain); - break; - case SND_SOC_DAPM_POST_PMD: - /* Do the anti-pop/bias ramp disable according to the TRM */ + twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); + } else { + /* Headset ramp-down _not_ according to + * the TRM, but in a way that it is working */ hs_pop &= ~TWL4030_RAMP_EN; - twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop); + twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); + /* Wait ramp delay time + 1, so the VMID can settle */ + mdelay((ramp_base[(hs_pop & TWL4030_RAMP_DELAY) >> 2] / + twl4030->sysclk) + 1); /* Bypass the reg_cache to mute the headset */ twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, hs_gain & (~0x0f), TWL4030_REG_HS_GAIN_SET); + hs_pop &= ~TWL4030_VMID_EN; - twl4030_write(w->codec, TWL4030_REG_HS_POPN_SET, hs_pop); + twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); + } +} + +static int headsetlpga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct twl4030_priv *twl4030 = w->codec->private_data; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* Do the ramp-up only once */ + if (!twl4030->hsr_enabled) + headset_ramp(w->codec, 1); + + twl4030->hsl_enabled = 1; + break; + case SND_SOC_DAPM_POST_PMD: + /* Do the ramp-down only if both headsetL/R is disabled */ + if (!twl4030->hsr_enabled) + headset_ramp(w->codec, 0); + + twl4030->hsl_enabled = 0; + break; + } + return 0; +} + +static int headsetrpga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct twl4030_priv *twl4030 = w->codec->private_data; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + /* Do the ramp-up only once */ + if (!twl4030->hsl_enabled) + headset_ramp(w->codec, 1); + + twl4030->hsr_enabled = 1; + break; + case SND_SOC_DAPM_POST_PMD: + /* Do the ramp-down only if both headsetL/R is disabled */ + if (!twl4030->hsl_enabled) + headset_ramp(w->codec, 0); + + twl4030->hsr_enabled = 0; break; } return 0; @@ -1116,13 +1168,18 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { &twl4030_dapm_predriver_controls[0], ARRAY_SIZE(twl4030_dapm_predriver_controls)), /* HeadsetL/R */ - SND_SOC_DAPM_MIXER_E("HeadsetL Mixer", SND_SOC_NOPM, 0, 0, + SND_SOC_DAPM_MIXER("HeadsetL Mixer", SND_SOC_NOPM, 0, 0, &twl4030_dapm_hsol_controls[0], - ARRAY_SIZE(twl4030_dapm_hsol_controls), headsetl_event, + ARRAY_SIZE(twl4030_dapm_hsol_controls)), + SND_SOC_DAPM_PGA_E("HeadsetL PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, headsetlpga_event, SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MIXER("HeadsetR Mixer", SND_SOC_NOPM, 0, 0, &twl4030_dapm_hsor_controls[0], ARRAY_SIZE(twl4030_dapm_hsor_controls)), + SND_SOC_DAPM_PGA_E("HeadsetR PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, headsetrpga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), /* CarkitL/R */ SND_SOC_DAPM_MIXER("CarkitL Mixer", SND_SOC_NOPM, 0, 0, &twl4030_dapm_carkitl_controls[0], @@ -1227,10 +1284,12 @@ static const struct snd_soc_dapm_route intercon[] = { {"HeadsetL Mixer", "Voice", "Analog Voice Playback Mixer"}, {"HeadsetL Mixer", "AudioL1", "Analog L1 Playback Mixer"}, {"HeadsetL Mixer", "AudioL2", "Analog L2 Playback Mixer"}, + {"HeadsetL PGA", NULL, "HeadsetL Mixer"}, /* HeadsetR */ {"HeadsetR Mixer", "Voice", "Analog Voice Playback Mixer"}, {"HeadsetR Mixer", "AudioR1", "Analog R1 Playback Mixer"}, {"HeadsetR Mixer", "AudioR2", "Analog R2 Playback Mixer"}, + {"HeadsetR PGA", NULL, "HeadsetR Mixer"}, /* CarkitL */ {"CarkitL Mixer", "Voice", "Analog Voice Playback Mixer"}, {"CarkitL Mixer", "AudioL1", "Analog L1 Playback Mixer"}, @@ -1261,8 +1320,8 @@ static const struct snd_soc_dapm_route intercon[] = { {"EARPIECE", NULL, "Earpiece Mixer"}, {"PREDRIVEL", NULL, "PredriveL Mixer"}, {"PREDRIVER", NULL, "PredriveR Mixer"}, - {"HSOL", NULL, "HeadsetL Mixer"}, - {"HSOR", NULL, "HeadsetR Mixer"}, + {"HSOL", NULL, "HeadsetL PGA"}, + {"HSOR", NULL, "HeadsetR PGA"}, {"CARKITL", NULL, "CarkitL Mixer"}, {"CARKITR", NULL, "CarkitR Mixer"}, {"HFL", NULL, "HandsfreeL Mux"}, @@ -1601,17 +1660,21 @@ static int twl4030_set_dai_sysclk(struct snd_soc_dai *codec_dai, int clk_id, unsigned int freq, int dir) { struct snd_soc_codec *codec = codec_dai->codec; + struct twl4030_priv *twl4030 = codec->private_data; u8 infreq; switch (freq) { case 19200000: infreq = TWL4030_APLL_INFREQ_19200KHZ; + twl4030->sysclk = 19200; break; case 26000000: infreq = TWL4030_APLL_INFREQ_26000KHZ; + twl4030->sysclk = 26000; break; case 38400000: infreq = TWL4030_APLL_INFREQ_38400KHZ; + twl4030->sysclk = 38400; break; default: printk(KERN_ERR "TWL4030 set sysclk: unknown rate %d\n", @@ -2000,6 +2063,9 @@ static int twl4030_probe(struct platform_device *pdev) kfree(codec); return -ENOMEM; } + /* Set default sysclk (used by the headsetl/rpga_event callback for + * pop-attenuation) */ + twl4030->sysclk = 26000; codec->private_data = twl4030; socdev->card->codec = codec; -- cgit v1.2.3 From 9da28c7b38170882b1c43d7d133ddce34e25f161 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 22 May 2009 10:13:15 +0300 Subject: ASoC: TWL4030: Add support for platform dependent configuration twl4030_setup_data structure can be passed from platform drivers to the codec via the snd_soc_device->codec_data pointer. Currently the setup data has support for the Headset pop-removal related configuration, which differs from board to board. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 22 +++++++++++++++++++--- sound/soc/codecs/twl4030.h | 5 +++++ 2 files changed, 24 insertions(+), 3 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index f554672f67c..584507f71ef 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -1997,6 +1997,8 @@ static int twl4030_resume(struct platform_device *pdev) static int twl4030_init(struct snd_soc_device *socdev) { struct snd_soc_codec *codec = socdev->card->codec; + struct twl4030_setup_data *setup = socdev->codec_data; + struct twl4030_priv *twl4030 = codec->private_data; int ret = 0; printk(KERN_INFO "TWL4030 Audio Codec init \n"); @@ -2014,6 +2016,23 @@ static int twl4030_init(struct snd_soc_device *socdev) if (codec->reg_cache == NULL) return -ENOMEM; + /* Configuration for headset ramp delay from setup data */ + if (setup) { + unsigned char hs_pop; + + if (setup->sysclk) + twl4030->sysclk = setup->sysclk; + else + twl4030->sysclk = 26000; + + hs_pop = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET); + hs_pop &= ~TWL4030_RAMP_DELAY; + hs_pop |= (setup->ramp_delay_value << 2); + twl4030_write_reg_cache(codec, TWL4030_REG_HS_POPN_SET, hs_pop); + } else { + twl4030->sysclk = 26000; + } + /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { @@ -2063,9 +2082,6 @@ static int twl4030_probe(struct platform_device *pdev) kfree(codec); return -ENOMEM; } - /* Set default sysclk (used by the headsetl/rpga_event callback for - * pop-attenuation) */ - twl4030->sysclk = 26000; codec->private_data = twl4030; socdev->card->codec = codec; diff --git a/sound/soc/codecs/twl4030.h b/sound/soc/codecs/twl4030.h index 9668bdf430f..48326e2bd9d 100644 --- a/sound/soc/codecs/twl4030.h +++ b/sound/soc/codecs/twl4030.h @@ -266,4 +266,9 @@ extern struct snd_soc_dai twl4030_dai[2]; extern struct snd_soc_codec_device soc_codec_dev_twl4030; +struct twl4030_setup_data { + unsigned int ramp_delay_value; + unsigned int sysclk; +}; + #endif /* End of __TWL4030_AUDIO_H__ */ -- cgit v1.2.3 From 7385ba44f8bcea15bf0d75ae2814f0cec63140b9 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 22 May 2009 10:13:16 +0300 Subject: ASoC: SDP4030: Use the twl4030_setup_data for headset pop-removal With this patch the initial headset pop-removal related values are configured for the twl4030 codec (ramp delay and sysclk). Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/omap/sdp3430.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/omap/sdp3430.c b/sound/soc/omap/sdp3430.c index 19966a7c518..b719e5db4f5 100644 --- a/sound/soc/omap/sdp3430.c +++ b/sound/soc/omap/sdp3430.c @@ -276,10 +276,17 @@ static struct snd_soc_card snd_soc_sdp3430 = { .num_links = ARRAY_SIZE(sdp3430_dai), }; +/* twl4030 setup */ +static struct twl4030_setup_data twl4030_setup = { + .ramp_delay_value = 3, + .sysclk = 26000, +}; + /* Audio subsystem */ static struct snd_soc_device sdp3430_snd_devdata = { .card = &snd_soc_sdp3430, .codec_dev = &soc_codec_dev_twl4030, + .codec_data = &twl4030_setup, }; static struct platform_device *sdp3430_snd_device; -- cgit v1.2.3 From b4852b793a1dd74ccde5572d8a8f73e948a5b1a1 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 22 May 2009 15:12:15 +0300 Subject: ASoC: TWL4030: Differentiate the playback streams Give unique stream names for the two playback streams so DAPM can figure out which codec_dai is in use. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 584507f71ef..9197fdd0a29 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -1092,13 +1092,13 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_OUTPUT("VIBRA"), /* DACs */ - SND_SOC_DAPM_DAC("DAC Right1", "Right Front Playback", + SND_SOC_DAPM_DAC("DAC Right1", "Right Front HiFi Playback", SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_DAC("DAC Left1", "Left Front Playback", + SND_SOC_DAPM_DAC("DAC Left1", "Left Front HiFi Playback", SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_DAC("DAC Right2", "Right Rear Playback", + SND_SOC_DAPM_DAC("DAC Right2", "Right Rear HiFi Playback", SND_SOC_NOPM, 0, 0), - SND_SOC_DAPM_DAC("DAC Left2", "Left Rear Playback", + SND_SOC_DAPM_DAC("DAC Left2", "Left Rear HiFi Playback", SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_DAC("DAC Voice", "Voice Playback", SND_SOC_NOPM, 0, 0), @@ -1937,7 +1937,7 @@ struct snd_soc_dai twl4030_dai[] = { { .name = "twl4030", .playback = { - .stream_name = "Playback", + .stream_name = "HiFi Playback", .channels_min = 2, .channels_max = 4, .rates = TWL4030_RATES | SNDRV_PCM_RATE_96000, @@ -1953,7 +1953,7 @@ struct snd_soc_dai twl4030_dai[] = { { .name = "twl4030 Voice", .playback = { - .stream_name = "Playback", + .stream_name = "Voice Playback", .channels_min = 1, .channels_max = 1, .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000, -- cgit v1.2.3 From 86ed3669f068b514ab85ffd548456a342b3fb8d3 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 22 May 2009 15:01:19 +0100 Subject: ASoC: WM9081 mono DAC with integrated 2.6W class AB/D amplifier driver The WM9081 is designed to provide high power output at low distortion levels in space-constrained portable applications. Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm9081.c | 1532 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm9081.h | 787 +++++++++++++++++++++++ 4 files changed, 2325 insertions(+) create mode 100644 sound/soc/codecs/wm9081.c create mode 100644 sound/soc/codecs/wm9081.h (limited to 'sound/soc') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 1c19ad54a9f..7f78b65fc4e 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -40,6 +40,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8971 if I2C select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8990 if I2C + select SND_SOC_WM9081 if I2C select SND_SOC_WM9705 if SND_SOC_AC97_BUS select SND_SOC_WM9712 if SND_SOC_AC97_BUS select SND_SOC_WM9713 if SND_SOC_AC97_BUS @@ -156,6 +157,9 @@ config SND_SOC_WM8988 config SND_SOC_WM8990 tristate +config SND_SOC_WM9081 + tristate + config SND_SOC_WM9705 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 3d31b6bea83..70c55fa2c43 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -28,6 +28,7 @@ snd-soc-wm8960-objs := wm8960.o snd-soc-wm8971-objs := wm8971.o snd-soc-wm8988-objs := wm8988.o snd-soc-wm8990-objs := wm8990.o +snd-soc-wm9081-objs := wm9081.o snd-soc-wm9705-objs := wm9705.o snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o @@ -62,6 +63,7 @@ obj-$(CONFIG_SND_SOC_WM8940) += snd-soc-wm8940.o obj-$(CONFIG_SND_SOC_WM8960) += snd-soc-wm8960.o obj-$(CONFIG_SND_SOC_WM8988) += snd-soc-wm8988.o obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o +obj-$(CONFIG_SND_SOC_WM9081) += snd-soc-wm9081.o obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c new file mode 100644 index 00000000000..83e3148c258 --- /dev/null +++ b/sound/soc/codecs/wm9081.c @@ -0,0 +1,1532 @@ +/* + * wm9081.c -- WM9081 ALSA SoC Audio driver + * + * Author: Mark Brown + * + * Copyright 2009 Wolfson Microelectronics plc + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "wm9081.h" + +static u16 wm9081_reg_defaults[] = { + 0x0000, /* R0 - Software Reset */ + 0x0000, /* R1 */ + 0x00B9, /* R2 - Analogue Lineout */ + 0x00B9, /* R3 - Analogue Speaker PGA */ + 0x0001, /* R4 - VMID Control */ + 0x0068, /* R5 - Bias Control 1 */ + 0x0000, /* R6 */ + 0x0000, /* R7 - Analogue Mixer */ + 0x0000, /* R8 - Anti Pop Control */ + 0x01DB, /* R9 - Analogue Speaker 1 */ + 0x0018, /* R10 - Analogue Speaker 2 */ + 0x0180, /* R11 - Power Management */ + 0x0000, /* R12 - Clock Control 1 */ + 0x0038, /* R13 - Clock Control 2 */ + 0x4000, /* R14 - Clock Control 3 */ + 0x0000, /* R15 */ + 0x0000, /* R16 - FLL Control 1 */ + 0x0200, /* R17 - FLL Control 2 */ + 0x0000, /* R18 - FLL Control 3 */ + 0x0204, /* R19 - FLL Control 4 */ + 0x0000, /* R20 - FLL Control 5 */ + 0x0000, /* R21 */ + 0x0000, /* R22 - Audio Interface 1 */ + 0x0002, /* R23 - Audio Interface 2 */ + 0x0008, /* R24 - Audio Interface 3 */ + 0x0022, /* R25 - Audio Interface 4 */ + 0x0000, /* R26 - Interrupt Status */ + 0x0006, /* R27 - Interrupt Status Mask */ + 0x0000, /* R28 - Interrupt Polarity */ + 0x0000, /* R29 - Interrupt Control */ + 0x00C0, /* R30 - DAC Digital 1 */ + 0x0008, /* R31 - DAC Digital 2 */ + 0x09AF, /* R32 - DRC 1 */ + 0x4201, /* R33 - DRC 2 */ + 0x0000, /* R34 - DRC 3 */ + 0x0000, /* R35 - DRC 4 */ + 0x0000, /* R36 */ + 0x0000, /* R37 */ + 0x0000, /* R38 - Write Sequencer 1 */ + 0x0000, /* R39 - Write Sequencer 2 */ + 0x0002, /* R40 - MW Slave 1 */ + 0x0000, /* R41 */ + 0x0000, /* R42 - EQ 1 */ + 0x0000, /* R43 - EQ 2 */ + 0x0FCA, /* R44 - EQ 3 */ + 0x0400, /* R45 - EQ 4 */ + 0x00B8, /* R46 - EQ 5 */ + 0x1EB5, /* R47 - EQ 6 */ + 0xF145, /* R48 - EQ 7 */ + 0x0B75, /* R49 - EQ 8 */ + 0x01C5, /* R50 - EQ 9 */ + 0x169E, /* R51 - EQ 10 */ + 0xF829, /* R52 - EQ 11 */ + 0x07AD, /* R53 - EQ 12 */ + 0x1103, /* R54 - EQ 13 */ + 0x1C58, /* R55 - EQ 14 */ + 0xF373, /* R56 - EQ 15 */ + 0x0A54, /* R57 - EQ 16 */ + 0x0558, /* R58 - EQ 17 */ + 0x0564, /* R59 - EQ 18 */ + 0x0559, /* R60 - EQ 19 */ + 0x4000, /* R61 - EQ 20 */ +}; + +static struct { + int ratio; + int clk_sys_rate; +} clk_sys_rates[] = { + { 64, 0 }, + { 128, 1 }, + { 192, 2 }, + { 256, 3 }, + { 384, 4 }, + { 512, 5 }, + { 768, 6 }, + { 1024, 7 }, + { 1408, 8 }, + { 1536, 9 }, +}; + +static struct { + int rate; + int sample_rate; +} sample_rates[] = { + { 8000, 0 }, + { 11025, 1 }, + { 12000, 2 }, + { 16000, 3 }, + { 22050, 4 }, + { 24000, 5 }, + { 32000, 6 }, + { 44100, 7 }, + { 48000, 8 }, + { 88200, 9 }, + { 96000, 10 }, +}; + +static struct { + int div; /* *10 due to .5s */ + int bclk_div; +} bclk_divs[] = { + { 10, 0 }, + { 15, 1 }, + { 20, 2 }, + { 30, 3 }, + { 40, 4 }, + { 50, 5 }, + { 55, 6 }, + { 60, 7 }, + { 80, 8 }, + { 100, 9 }, + { 110, 10 }, + { 120, 11 }, + { 160, 12 }, + { 200, 13 }, + { 220, 14 }, + { 240, 15 }, + { 250, 16 }, + { 300, 17 }, + { 320, 18 }, + { 440, 19 }, + { 480, 20 }, +}; + +struct wm9081_priv { + struct snd_soc_codec codec; + u16 reg_cache[WM9081_MAX_REGISTER + 1]; + int sysclk_source; + int mclk_rate; + int sysclk_rate; + int fs; + int bclk; + int master; + int fll_fref; + int fll_fout; + struct wm9081_retune_mobile_config *retune; +}; + +static int wm9081_reg_is_volatile(int reg) +{ + switch (reg) { + default: + return 0; + } +} + +static unsigned int wm9081_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + BUG_ON(reg > WM9081_MAX_REGISTER); + return cache[reg]; +} + +static unsigned int wm9081_read_hw(struct snd_soc_codec *codec, u8 reg) +{ + struct i2c_msg xfer[2]; + u16 data; + int ret; + struct i2c_client *client = codec->control_data; + + BUG_ON(reg > WM9081_MAX_REGISTER); + + /* Write register */ + xfer[0].addr = client->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = ® + + /* Read data */ + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 2; + xfer[1].buf = (u8 *)&data; + + ret = i2c_transfer(client->adapter, xfer, 2); + if (ret != 2) { + dev_err(&client->dev, "i2c_transfer() returned %d\n", ret); + return 0; + } + + return (data >> 8) | ((data & 0xff) << 8); +} + +static unsigned int wm9081_read(struct snd_soc_codec *codec, unsigned int reg) +{ + if (wm9081_reg_is_volatile(reg)) + return wm9081_read_hw(codec, reg); + else + return wm9081_read_reg_cache(codec, reg); +} + +static int wm9081_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u16 *cache = codec->reg_cache; + u8 data[3]; + + BUG_ON(reg > WM9081_MAX_REGISTER); + + if (!wm9081_reg_is_volatile(reg)) + cache[reg] = value; + + data[0] = reg; + data[1] = value >> 8; + data[2] = value & 0x00ff; + + if (codec->hw_write(codec->control_data, data, 3) == 3) + return 0; + else + return -EIO; +} + +static int wm9081_reset(struct snd_soc_codec *codec) +{ + return wm9081_write(codec, WM9081_SOFTWARE_RESET, 0); +} + +static const DECLARE_TLV_DB_SCALE(drc_in_tlv, -4500, 75, 0); +static const DECLARE_TLV_DB_SCALE(drc_out_tlv, -2250, 75, 0); +static const DECLARE_TLV_DB_SCALE(drc_min_tlv, -1800, 600, 0); +static unsigned int drc_max_tlv[] = { + TLV_DB_RANGE_HEAD(4), + 0, 0, TLV_DB_SCALE_ITEM(1200, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(1800, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(2400, 0, 0), + 3, 3, TLV_DB_SCALE_ITEM(3600, 0, 0), +}; +static const DECLARE_TLV_DB_SCALE(drc_qr_tlv, 1200, 600, 0); +static const DECLARE_TLV_DB_SCALE(drc_startup_tlv, -300, 50, 0); + +static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); + +static const DECLARE_TLV_DB_SCALE(in_tlv, -600, 600, 0); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -7200, 75, 1); +static const DECLARE_TLV_DB_SCALE(out_tlv, -5700, 100, 0); + +static const char *drc_high_text[] = { + "1", + "1/2", + "1/4", + "1/8", + "1/16", + "0", +}; + +static const struct soc_enum drc_high = + SOC_ENUM_SINGLE(WM9081_DRC_3, 3, 6, drc_high_text); + +static const char *drc_low_text[] = { + "1", + "1/2", + "1/4", + "1/8", + "0", +}; + +static const struct soc_enum drc_low = + SOC_ENUM_SINGLE(WM9081_DRC_3, 0, 5, drc_low_text); + +static const char *drc_atk_text[] = { + "181us", + "181us", + "363us", + "726us", + "1.45ms", + "2.9ms", + "5.8ms", + "11.6ms", + "23.2ms", + "46.4ms", + "92.8ms", + "185.6ms", +}; + +static const struct soc_enum drc_atk = + SOC_ENUM_SINGLE(WM9081_DRC_2, 12, 12, drc_atk_text); + +static const char *drc_dcy_text[] = { + "186ms", + "372ms", + "743ms", + "1.49s", + "2.97s", + "5.94s", + "11.89s", + "23.78s", + "47.56s", +}; + +static const struct soc_enum drc_dcy = + SOC_ENUM_SINGLE(WM9081_DRC_2, 8, 9, drc_dcy_text); + +static const char *drc_qr_dcy_text[] = { + "0.725ms", + "1.45ms", + "5.8ms", +}; + +static const struct soc_enum drc_qr_dcy = + SOC_ENUM_SINGLE(WM9081_DRC_2, 4, 3, drc_qr_dcy_text); + +static const char *dac_deemph_text[] = { + "None", + "32kHz", + "44.1kHz", + "48kHz", +}; + +static const struct soc_enum dac_deemph = + SOC_ENUM_SINGLE(WM9081_DAC_DIGITAL_2, 1, 4, dac_deemph_text); + +static const char *speaker_mode_text[] = { + "Class D", + "Class AB", +}; + +static const struct soc_enum speaker_mode = + SOC_ENUM_SINGLE(WM9081_ANALOGUE_SPEAKER_2, 6, 2, speaker_mode_text); + +static int speaker_mode_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg; + + reg = wm9081_read(codec, WM9081_ANALOGUE_SPEAKER_2); + if (reg & WM9081_SPK_MODE) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + return 0; +} + +/* + * Stop any attempts to change speaker mode while the speaker is enabled. + * + * We also have some special anti-pop controls dependant on speaker + * mode which must be changed along with the mode. + */ +static int speaker_mode_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned int reg_pwr = wm9081_read(codec, WM9081_POWER_MANAGEMENT); + unsigned int reg2 = wm9081_read(codec, WM9081_ANALOGUE_SPEAKER_2); + + /* Are we changing anything? */ + if (ucontrol->value.integer.value[0] == + ((reg2 & WM9081_SPK_MODE) != 0)) + return 0; + + /* Don't try to change modes while enabled */ + if (reg_pwr & WM9081_SPK_ENA) + return -EINVAL; + + if (ucontrol->value.integer.value[0]) { + /* Class AB */ + reg2 &= ~(WM9081_SPK_INV_MUTE | WM9081_OUT_SPK_CTRL); + reg2 |= WM9081_SPK_MODE; + } else { + /* Class D */ + reg2 |= WM9081_SPK_INV_MUTE | WM9081_OUT_SPK_CTRL; + reg2 &= ~WM9081_SPK_MODE; + } + + wm9081_write(codec, WM9081_ANALOGUE_SPEAKER_2, reg2); + + return 0; +} + +static const struct snd_kcontrol_new wm9081_snd_controls[] = { +SOC_SINGLE_TLV("IN1 Volume", WM9081_ANALOGUE_MIXER, 1, 1, 1, in_tlv), +SOC_SINGLE_TLV("IN2 Volume", WM9081_ANALOGUE_MIXER, 3, 1, 1, in_tlv), + +SOC_SINGLE_TLV("Playback Volume", WM9081_DAC_DIGITAL_1, 1, 96, 0, dac_tlv), + +SOC_SINGLE("LINEOUT Switch", WM9081_ANALOGUE_LINEOUT, 7, 1, 1), +SOC_SINGLE("LINEOUT ZC Switch", WM9081_ANALOGUE_LINEOUT, 6, 1, 0), +SOC_SINGLE_TLV("LINEOUT Volume", WM9081_ANALOGUE_LINEOUT, 0, 63, 0, out_tlv), + +SOC_SINGLE("DRC Switch", WM9081_DRC_1, 15, 1, 0), +SOC_ENUM("DRC High Slope", drc_high), +SOC_ENUM("DRC Low Slope", drc_low), +SOC_SINGLE_TLV("DRC Input Volume", WM9081_DRC_4, 5, 60, 1, drc_in_tlv), +SOC_SINGLE_TLV("DRC Output Volume", WM9081_DRC_4, 0, 30, 1, drc_out_tlv), +SOC_SINGLE_TLV("DRC Minimum Volume", WM9081_DRC_2, 2, 3, 1, drc_min_tlv), +SOC_SINGLE_TLV("DRC Maximum Volume", WM9081_DRC_2, 0, 3, 0, drc_max_tlv), +SOC_ENUM("DRC Attack", drc_atk), +SOC_ENUM("DRC Decay", drc_dcy), +SOC_SINGLE("DRC Quick Release Switch", WM9081_DRC_1, 2, 1, 0), +SOC_SINGLE_TLV("DRC Quick Release Volume", WM9081_DRC_2, 6, 3, 0, drc_qr_tlv), +SOC_ENUM("DRC Quick Release Decay", drc_qr_dcy), +SOC_SINGLE_TLV("DRC Startup Volume", WM9081_DRC_1, 6, 18, 0, drc_startup_tlv), + +SOC_SINGLE("EQ Switch", WM9081_EQ_1, 0, 1, 0), + +SOC_SINGLE("Speaker DC Volume", WM9081_ANALOGUE_SPEAKER_1, 3, 5, 0), +SOC_SINGLE("Speaker AC Volume", WM9081_ANALOGUE_SPEAKER_1, 0, 5, 0), +SOC_SINGLE("Speaker Switch", WM9081_ANALOGUE_SPEAKER_PGA, 7, 1, 1), +SOC_SINGLE("Speaker ZC Switch", WM9081_ANALOGUE_SPEAKER_PGA, 6, 1, 0), +SOC_SINGLE_TLV("Speaker Volume", WM9081_ANALOGUE_SPEAKER_PGA, 0, 63, 0, + out_tlv), +SOC_ENUM("DAC Deemphasis", dac_deemph), +SOC_ENUM_EXT("Speaker Mode", speaker_mode, speaker_mode_get, speaker_mode_put), +}; + +static const struct snd_kcontrol_new wm9081_eq_controls[] = { +SOC_SINGLE_TLV("EQ1 Volume", WM9081_EQ_1, 11, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 Volume", WM9081_EQ_1, 6, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 Volume", WM9081_EQ_1, 1, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 Volume", WM9081_EQ_2, 11, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ5 Volume", WM9081_EQ_2, 6, 24, 0, eq_tlv), +}; + +static const struct snd_kcontrol_new mixer[] = { +SOC_DAPM_SINGLE("IN1 Switch", WM9081_ANALOGUE_MIXER, 0, 1, 0), +SOC_DAPM_SINGLE("IN2 Switch", WM9081_ANALOGUE_MIXER, 2, 1, 0), +SOC_DAPM_SINGLE("Playback Switch", WM9081_ANALOGUE_MIXER, 4, 1, 0), +}; + +static int speaker_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + unsigned int reg = wm9081_read(codec, WM9081_POWER_MANAGEMENT); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + reg |= WM9081_SPK_ENA; + break; + + case SND_SOC_DAPM_PRE_PMD: + reg &= ~WM9081_SPK_ENA; + break; + } + + wm9081_write(codec, WM9081_POWER_MANAGEMENT, reg); + + return 0; +} + +struct _fll_div { + u16 fll_fratio; + u16 fll_outdiv; + u16 fll_clk_ref_div; + u16 n; + u16 k; +}; + +/* The size in bits of the FLL divide multiplied by 10 + * to allow rounding later */ +#define FIXED_FLL_SIZE ((1 << 16) * 10) + +static struct { + unsigned int min; + unsigned int max; + u16 fll_fratio; + int ratio; +} fll_fratios[] = { + { 0, 64000, 4, 16 }, + { 64000, 128000, 3, 8 }, + { 128000, 256000, 2, 4 }, + { 256000, 1000000, 1, 2 }, + { 1000000, 13500000, 0, 1 }, +}; + +static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, + unsigned int Fout) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod, target; + unsigned int div; + int i; + + /* Fref must be <=13.5MHz */ + div = 1; + while ((Fref / div) > 13500000) { + div *= 2; + + if (div > 8) { + pr_err("Can't scale %dMHz input down to <=13.5MHz\n", + Fref); + return -EINVAL; + } + } + fll_div->fll_clk_ref_div = div / 2; + + pr_debug("Fref=%u Fout=%u\n", Fref, Fout); + + /* Apply the division for our remaining calculations */ + Fref /= div; + + /* Fvco should be 90-100MHz; don't check the upper bound */ + div = 0; + target = Fout * 2; + while (target < 90000000) { + div++; + target *= 2; + if (div > 7) { + pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n", + Fout); + return -EINVAL; + } + } + fll_div->fll_outdiv = div; + + pr_debug("Fvco=%dHz\n", target); + + /* Find an appropraite FLL_FRATIO and factor it out of the target */ + for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) { + if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) { + fll_div->fll_fratio = fll_fratios[i].fll_fratio; + target /= fll_fratios[i].ratio; + break; + } + } + if (i == ARRAY_SIZE(fll_fratios)) { + pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref); + return -EINVAL; + } + + /* Now, calculate N.K */ + Ndiv = target / Fref; + + fll_div->n = Ndiv; + Nmod = target % Fref; + pr_debug("Nmod=%d\n", Nmod); + + /* Calculate fractional part - scale up so we can round. */ + Kpart = FIXED_FLL_SIZE * (long long)Nmod; + + do_div(Kpart, Fref); + + K = Kpart & 0xFFFFFFFF; + + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + fll_div->k = K / 10; + + pr_debug("N=%x K=%x FLL_FRATIO=%x FLL_OUTDIV=%x FLL_CLK_REF_DIV=%x\n", + fll_div->n, fll_div->k, + fll_div->fll_fratio, fll_div->fll_outdiv, + fll_div->fll_clk_ref_div); + + return 0; +} + +static int wm9081_set_fll(struct snd_soc_codec *codec, int fll_id, + unsigned int Fref, unsigned int Fout) +{ + struct wm9081_priv *wm9081 = codec->private_data; + u16 reg1, reg4, reg5; + struct _fll_div fll_div; + int ret; + int clk_sys_reg; + + /* Any change? */ + if (Fref == wm9081->fll_fref && Fout == wm9081->fll_fout) + return 0; + + /* Disable the FLL */ + if (Fout == 0) { + dev_dbg(codec->dev, "FLL disabled\n"); + wm9081->fll_fref = 0; + wm9081->fll_fout = 0; + + return 0; + } + + ret = fll_factors(&fll_div, Fref, Fout); + if (ret != 0) + return ret; + + reg5 = wm9081_read(codec, WM9081_FLL_CONTROL_5); + reg5 &= ~WM9081_FLL_CLK_SRC_MASK; + + switch (fll_id) { + case WM9081_SYSCLK_FLL_MCLK: + reg5 |= 0x1; + break; + + default: + dev_err(codec->dev, "Unknown FLL ID %d\n", fll_id); + return -EINVAL; + } + + /* Disable CLK_SYS while we reconfigure */ + clk_sys_reg = wm9081_read(codec, WM9081_CLOCK_CONTROL_3); + if (clk_sys_reg & WM9081_CLK_SYS_ENA) + wm9081_write(codec, WM9081_CLOCK_CONTROL_3, + clk_sys_reg & ~WM9081_CLK_SYS_ENA); + + /* Any FLL configuration change requires that the FLL be + * disabled first. */ + reg1 = wm9081_read(codec, WM9081_FLL_CONTROL_1); + reg1 &= ~WM9081_FLL_ENA; + wm9081_write(codec, WM9081_FLL_CONTROL_1, reg1); + + /* Apply the configuration */ + if (fll_div.k) + reg1 |= WM9081_FLL_FRAC_MASK; + else + reg1 &= ~WM9081_FLL_FRAC_MASK; + wm9081_write(codec, WM9081_FLL_CONTROL_1, reg1); + + wm9081_write(codec, WM9081_FLL_CONTROL_2, + (fll_div.fll_outdiv << WM9081_FLL_OUTDIV_SHIFT) | + (fll_div.fll_fratio << WM9081_FLL_FRATIO_SHIFT)); + wm9081_write(codec, WM9081_FLL_CONTROL_3, fll_div.k); + + reg4 = wm9081_read(codec, WM9081_FLL_CONTROL_4); + reg4 &= ~WM9081_FLL_N_MASK; + reg4 |= fll_div.n << WM9081_FLL_N_SHIFT; + wm9081_write(codec, WM9081_FLL_CONTROL_4, reg4); + + reg5 &= ~WM9081_FLL_CLK_REF_DIV_MASK; + reg5 |= fll_div.fll_clk_ref_div << WM9081_FLL_CLK_REF_DIV_SHIFT; + wm9081_write(codec, WM9081_FLL_CONTROL_5, reg5); + + /* Enable the FLL */ + wm9081_write(codec, WM9081_FLL_CONTROL_1, reg1 | WM9081_FLL_ENA); + + /* Then bring CLK_SYS up again if it was disabled */ + if (clk_sys_reg & WM9081_CLK_SYS_ENA) + wm9081_write(codec, WM9081_CLOCK_CONTROL_3, clk_sys_reg); + + dev_dbg(codec->dev, "FLL enabled at %dHz->%dHz\n", Fref, Fout); + + wm9081->fll_fref = Fref; + wm9081->fll_fout = Fout; + + return 0; +} + +static int configure_clock(struct snd_soc_codec *codec) +{ + struct wm9081_priv *wm9081 = codec->private_data; + int new_sysclk, i, target; + unsigned int reg; + int ret = 0; + int mclkdiv = 0; + int fll = 0; + + switch (wm9081->sysclk_source) { + case WM9081_SYSCLK_MCLK: + if (wm9081->mclk_rate > 12225000) { + mclkdiv = 1; + wm9081->sysclk_rate = wm9081->mclk_rate / 2; + } else { + wm9081->sysclk_rate = wm9081->mclk_rate; + } + wm9081_set_fll(codec, WM9081_SYSCLK_FLL_MCLK, 0, 0); + break; + + case WM9081_SYSCLK_FLL_MCLK: + /* If we have a sample rate calculate a CLK_SYS that + * gives us a suitable DAC configuration, plus BCLK. + * Ideally we would check to see if we can clock + * directly from MCLK and only use the FLL if this is + * not the case, though care must be taken with free + * running mode. + */ + if (wm9081->master && wm9081->bclk) { + /* Make sure we can generate CLK_SYS and BCLK + * and that we've got 3MHz for optimal + * performance. */ + for (i = 0; i < ARRAY_SIZE(clk_sys_rates); i++) { + target = wm9081->fs * clk_sys_rates[i].ratio; + if (target >= wm9081->bclk && + target > 3000000) + new_sysclk = target; + } + } else if (wm9081->fs) { + for (i = 0; i < ARRAY_SIZE(clk_sys_rates); i++) { + new_sysclk = clk_sys_rates[i].ratio + * wm9081->fs; + if (new_sysclk > 3000000) + break; + } + } else { + new_sysclk = 12288000; + } + + ret = wm9081_set_fll(codec, WM9081_SYSCLK_FLL_MCLK, + wm9081->mclk_rate, new_sysclk); + if (ret == 0) { + wm9081->sysclk_rate = new_sysclk; + + /* Switch SYSCLK over to FLL */ + fll = 1; + } else { + wm9081->sysclk_rate = wm9081->mclk_rate; + } + break; + + default: + return -EINVAL; + } + + reg = wm9081_read(codec, WM9081_CLOCK_CONTROL_1); + if (mclkdiv) + reg |= WM9081_MCLKDIV2; + else + reg &= ~WM9081_MCLKDIV2; + wm9081_write(codec, WM9081_CLOCK_CONTROL_1, reg); + + reg = wm9081_read(codec, WM9081_CLOCK_CONTROL_3); + if (fll) + reg |= WM9081_CLK_SRC_SEL; + else + reg &= ~WM9081_CLK_SRC_SEL; + wm9081_write(codec, WM9081_CLOCK_CONTROL_3, reg); + + dev_dbg(codec->dev, "CLK_SYS is %dHz\n", wm9081->sysclk_rate); + + return ret; +} + +static int clk_sys_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + struct wm9081_priv *wm9081 = codec->private_data; + + /* This should be done on init() for bypass paths */ + switch (wm9081->sysclk_source) { + case WM9081_SYSCLK_MCLK: + dev_dbg(codec->dev, "Using %dHz MCLK\n", wm9081->mclk_rate); + break; + case WM9081_SYSCLK_FLL_MCLK: + dev_dbg(codec->dev, "Using %dHz MCLK with FLL\n", + wm9081->mclk_rate); + break; + default: + dev_err(codec->dev, "System clock not configured\n"); + return -EINVAL; + } + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + configure_clock(codec); + break; + + case SND_SOC_DAPM_POST_PMD: + /* Disable the FLL if it's running */ + wm9081_set_fll(codec, 0, 0, 0); + break; + } + + return 0; +} + +static const struct snd_soc_dapm_widget wm9081_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("IN1"), +SND_SOC_DAPM_INPUT("IN2"), + +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM9081_POWER_MANAGEMENT, 0, 0), + +SND_SOC_DAPM_MIXER_NAMED_CTL("Mixer", SND_SOC_NOPM, 0, 0, + mixer, ARRAY_SIZE(mixer)), + +SND_SOC_DAPM_PGA("LINEOUT PGA", WM9081_POWER_MANAGEMENT, 4, 0, NULL, 0), + +SND_SOC_DAPM_PGA_E("Speaker PGA", WM9081_POWER_MANAGEMENT, 2, 0, NULL, 0, + speaker_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_OUTPUT("LINEOUT"), +SND_SOC_DAPM_OUTPUT("SPKN"), +SND_SOC_DAPM_OUTPUT("SPKP"), + +SND_SOC_DAPM_SUPPLY("CLK_SYS", WM9081_CLOCK_CONTROL_3, 0, 0, clk_sys_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_SUPPLY("CLK_DSP", WM9081_CLOCK_CONTROL_3, 1, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("TOCLK", WM9081_CLOCK_CONTROL_3, 2, 0, NULL, 0), +}; + + +static const struct snd_soc_dapm_route audio_paths[] = { + { "DAC", NULL, "CLK_SYS" }, + { "DAC", NULL, "CLK_DSP" }, + + { "Mixer", "IN1 Switch", "IN1" }, + { "Mixer", "IN2 Switch", "IN2" }, + { "Mixer", "Playback Switch", "DAC" }, + + { "LINEOUT PGA", NULL, "Mixer" }, + { "LINEOUT PGA", NULL, "TOCLK" }, + { "LINEOUT PGA", NULL, "CLK_SYS" }, + + { "LINEOUT", NULL, "LINEOUT PGA" }, + + { "Speaker PGA", NULL, "Mixer" }, + { "Speaker PGA", NULL, "TOCLK" }, + { "Speaker PGA", NULL, "CLK_SYS" }, + + { "SPKN", NULL, "Speaker PGA" }, + { "SPKP", NULL, "Speaker PGA" }, +}; + +static int wm9081_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* VMID=2*40k */ + reg = wm9081_read(codec, WM9081_VMID_CONTROL); + reg &= ~WM9081_VMID_SEL_MASK; + reg |= 0x2; + wm9081_write(codec, WM9081_VMID_CONTROL, reg); + + /* Normal bias current */ + reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1); + reg &= ~WM9081_STBY_BIAS_ENA; + wm9081_write(codec, WM9081_BIAS_CONTROL_1, reg); + break; + + case SND_SOC_BIAS_STANDBY: + /* Initial cold start */ + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Disable LINEOUT discharge */ + reg = wm9081_read(codec, WM9081_ANTI_POP_CONTROL); + reg &= ~WM9081_LINEOUT_DISCH; + wm9081_write(codec, WM9081_ANTI_POP_CONTROL, reg); + + /* Select startup bias source */ + reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1); + reg |= WM9081_BIAS_SRC | WM9081_BIAS_ENA; + wm9081_write(codec, WM9081_BIAS_CONTROL_1, reg); + + /* VMID 2*4k; Soft VMID ramp enable */ + reg = wm9081_read(codec, WM9081_VMID_CONTROL); + reg |= WM9081_VMID_RAMP | 0x6; + wm9081_write(codec, WM9081_VMID_CONTROL, reg); + + mdelay(100); + + /* Normal bias enable & soft start off */ + reg |= WM9081_BIAS_ENA; + reg &= ~WM9081_VMID_RAMP; + wm9081_write(codec, WM9081_VMID_CONTROL, reg); + + /* Standard bias source */ + reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1); + reg &= ~WM9081_BIAS_SRC; + wm9081_write(codec, WM9081_BIAS_CONTROL_1, reg); + } + + /* VMID 2*240k */ + reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1); + reg &= ~WM9081_VMID_SEL_MASK; + reg |= 0x40; + wm9081_write(codec, WM9081_VMID_CONTROL, reg); + + /* Standby bias current on */ + reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1); + reg |= WM9081_STBY_BIAS_ENA; + wm9081_write(codec, WM9081_BIAS_CONTROL_1, reg); + break; + + case SND_SOC_BIAS_OFF: + /* Startup bias source */ + reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1); + reg |= WM9081_BIAS_SRC; + wm9081_write(codec, WM9081_BIAS_CONTROL_1, reg); + + /* Disable VMID and biases with soft ramping */ + reg = wm9081_read(codec, WM9081_VMID_CONTROL); + reg &= ~(WM9081_VMID_SEL_MASK | WM9081_BIAS_ENA); + reg |= WM9081_VMID_RAMP; + wm9081_write(codec, WM9081_VMID_CONTROL, reg); + + /* Actively discharge LINEOUT */ + reg = wm9081_read(codec, WM9081_ANTI_POP_CONTROL); + reg |= WM9081_LINEOUT_DISCH; + wm9081_write(codec, WM9081_ANTI_POP_CONTROL, reg); + break; + } + + codec->bias_level = level; + + return 0; +} + +static int wm9081_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm9081_priv *wm9081 = codec->private_data; + unsigned int aif2 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_2); + + aif2 &= ~(WM9081_AIF_BCLK_INV | WM9081_AIF_LRCLK_INV | + WM9081_BCLK_DIR | WM9081_LRCLK_DIR | WM9081_AIF_FMT_MASK); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + wm9081->master = 0; + break; + case SND_SOC_DAIFMT_CBS_CFM: + aif2 |= WM9081_LRCLK_DIR; + wm9081->master = 1; + break; + case SND_SOC_DAIFMT_CBM_CFS: + aif2 |= WM9081_BCLK_DIR; + wm9081->master = 1; + break; + case SND_SOC_DAIFMT_CBM_CFM: + aif2 |= WM9081_LRCLK_DIR | WM9081_BCLK_DIR; + wm9081->master = 1; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_B: + aif2 |= WM9081_AIF_LRCLK_INV; + case SND_SOC_DAIFMT_DSP_A: + aif2 |= 0x3; + break; + case SND_SOC_DAIFMT_I2S: + aif2 |= 0x2; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + aif2 |= 0x1; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + aif2 |= WM9081_AIF_BCLK_INV; + break; + default: + return -EINVAL; + } + break; + + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + aif2 |= WM9081_AIF_BCLK_INV | WM9081_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif2 |= WM9081_AIF_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif2 |= WM9081_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + wm9081_write(codec, WM9081_AUDIO_INTERFACE_2, aif2); + + return 0; +} + +static int wm9081_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm9081_priv *wm9081 = codec->private_data; + int ret, i, best, best_val, cur_val; + unsigned int clk_ctrl2, aif1, aif2, aif3, aif4; + + clk_ctrl2 = wm9081_read(codec, WM9081_CLOCK_CONTROL_2); + clk_ctrl2 &= ~(WM9081_CLK_SYS_RATE_MASK | WM9081_SAMPLE_RATE_MASK); + + aif1 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_1); + + aif2 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_2); + aif2 &= ~WM9081_AIF_WL_MASK; + + aif3 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_3); + aif3 &= ~WM9081_BCLK_DIV_MASK; + + aif4 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_4); + aif4 &= ~WM9081_LRCLK_RATE_MASK; + + /* What BCLK do we need? */ + wm9081->fs = params_rate(params); + wm9081->bclk = 2 * wm9081->fs; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + wm9081->bclk *= 16; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + wm9081->bclk *= 20; + aif2 |= 0x4; + break; + case SNDRV_PCM_FORMAT_S24_LE: + wm9081->bclk *= 24; + aif2 |= 0x8; + break; + case SNDRV_PCM_FORMAT_S32_LE: + wm9081->bclk *= 32; + aif2 |= 0xc; + break; + default: + return -EINVAL; + } + + if (aif1 & WM9081_AIFDAC_TDM_MODE_MASK) { + int slots = ((aif1 & WM9081_AIFDAC_TDM_MODE_MASK) >> + WM9081_AIFDAC_TDM_MODE_SHIFT) + 1; + wm9081->bclk *= slots; + } + + dev_dbg(codec->dev, "Target BCLK is %dHz\n", wm9081->bclk); + + ret = configure_clock(codec); + if (ret != 0) + return ret; + + /* Select nearest CLK_SYS_RATE */ + best = 0; + best_val = abs((wm9081->sysclk_rate / clk_sys_rates[0].ratio) + - wm9081->fs); + for (i = 1; i < ARRAY_SIZE(clk_sys_rates); i++) { + cur_val = abs((wm9081->sysclk_rate / + clk_sys_rates[i].ratio) - wm9081->fs);; + if (cur_val < best_val) { + best = i; + best_val = cur_val; + } + } + dev_dbg(codec->dev, "Selected CLK_SYS_RATIO of %d\n", + clk_sys_rates[best].ratio); + clk_ctrl2 |= (clk_sys_rates[best].clk_sys_rate + << WM9081_CLK_SYS_RATE_SHIFT); + + /* SAMPLE_RATE */ + best = 0; + best_val = abs(wm9081->fs - sample_rates[0].rate); + for (i = 1; i < ARRAY_SIZE(sample_rates); i++) { + /* Closest match */ + cur_val = abs(wm9081->fs - sample_rates[i].rate); + if (cur_val < best_val) { + best = i; + best_val = cur_val; + } + } + dev_dbg(codec->dev, "Selected SAMPLE_RATE of %dHz\n", + sample_rates[best].rate); + clk_ctrl2 |= (sample_rates[i].sample_rate << WM9081_SAMPLE_RATE_SHIFT); + + /* BCLK_DIV */ + best = 0; + best_val = INT_MAX; + for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) { + cur_val = ((wm9081->sysclk_rate * 10) / bclk_divs[i].div) + - wm9081->bclk; + if (cur_val < 0) /* Table is sorted */ + break; + if (cur_val < best_val) { + best = i; + best_val = cur_val; + } + } + wm9081->bclk = (wm9081->sysclk_rate * 10) / bclk_divs[best].div; + dev_dbg(codec->dev, "Selected BCLK_DIV of %d for %dHz BCLK\n", + bclk_divs[best].div, wm9081->bclk); + aif3 |= bclk_divs[best].bclk_div; + + /* LRCLK is a simple fraction of BCLK */ + dev_dbg(codec->dev, "LRCLK_RATE is %d\n", wm9081->bclk / wm9081->fs); + aif4 |= wm9081->bclk / wm9081->fs; + + /* Apply a ReTune Mobile configuration if it's in use */ + if (wm9081->retune) { + struct wm9081_retune_mobile_config *retune = wm9081->retune; + struct wm9081_retune_mobile_setting *s; + int eq1; + + best = 0; + best_val = abs(retune->configs[0].rate - wm9081->fs); + for (i = 0; i < retune->num_configs; i++) { + cur_val = abs(retune->configs[i].rate - wm9081->fs); + if (cur_val < best_val) { + best_val = cur_val; + best = i; + } + } + s = &retune->configs[best]; + + dev_dbg(codec->dev, "ReTune Mobile %s tuned for %dHz\n", + s->name, s->rate); + + /* If the EQ is enabled then disable it while we write out */ + eq1 = wm9081_read(codec, WM9081_EQ_1) & WM9081_EQ_ENA; + if (eq1 & WM9081_EQ_ENA) + wm9081_write(codec, WM9081_EQ_1, 0); + + /* Write out the other values */ + for (i = 1; i < ARRAY_SIZE(s->config); i++) + wm9081_write(codec, WM9081_EQ_1 + i, s->config[i]); + + eq1 |= (s->config[0] & ~WM9081_EQ_ENA); + wm9081_write(codec, WM9081_EQ_1, eq1); + } + + wm9081_write(codec, WM9081_CLOCK_CONTROL_2, clk_ctrl2); + wm9081_write(codec, WM9081_AUDIO_INTERFACE_2, aif2); + wm9081_write(codec, WM9081_AUDIO_INTERFACE_3, aif3); + wm9081_write(codec, WM9081_AUDIO_INTERFACE_4, aif4); + + return 0; +} + +static int wm9081_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int reg; + + reg = wm9081_read(codec, WM9081_DAC_DIGITAL_2); + + if (mute) + reg |= WM9081_DAC_MUTE; + else + reg &= ~WM9081_DAC_MUTE; + + wm9081_write(codec, WM9081_DAC_DIGITAL_2, reg); + + return 0; +} + +static int wm9081_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm9081_priv *wm9081 = codec->private_data; + + switch (clk_id) { + case WM9081_SYSCLK_MCLK: + case WM9081_SYSCLK_FLL_MCLK: + wm9081->sysclk_source = clk_id; + wm9081->mclk_rate = freq; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int wm9081_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int mask, int slots) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int aif1 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_1); + + aif1 &= ~(WM9081_AIFDAC_TDM_SLOT_MASK | WM9081_AIFDAC_TDM_MODE_MASK); + + if (slots < 1 || slots > 4) + return -EINVAL; + + aif1 |= (slots - 1) << WM9081_AIFDAC_TDM_MODE_SHIFT; + + switch (mask) { + case 1: + break; + case 2: + aif1 |= 0x10; + break; + case 4: + aif1 |= 0x20; + break; + case 8: + aif1 |= 0x30; + break; + default: + return -EINVAL; + } + + wm9081_write(codec, WM9081_AUDIO_INTERFACE_1, aif1); + + return 0; +} + +#define WM9081_RATES SNDRV_PCM_RATE_8000_96000 + +#define WM9081_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops wm9081_dai_ops = { + .hw_params = wm9081_hw_params, + .set_sysclk = wm9081_set_sysclk, + .set_fmt = wm9081_set_dai_fmt, + .digital_mute = wm9081_digital_mute, + .set_tdm_slot = wm9081_set_tdm_slot, +}; + +/* We report two channels because the CODEC processes a stereo signal, even + * though it is only capable of handling a mono output. + */ +struct snd_soc_dai wm9081_dai = { + .name = "WM9081", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM9081_RATES, + .formats = WM9081_FORMATS, + }, + .ops = &wm9081_dai_ops, +}; +EXPORT_SYMBOL_GPL(wm9081_dai); + + +static struct snd_soc_codec *wm9081_codec; + +static int wm9081_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + struct wm9081_priv *wm9081; + int ret = 0; + + if (wm9081_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = wm9081_codec; + codec = wm9081_codec; + wm9081 = codec->private_data; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + snd_soc_add_controls(codec, wm9081_snd_controls, + ARRAY_SIZE(wm9081_snd_controls)); + if (!wm9081->retune) { + dev_dbg(codec->dev, + "No ReTune Mobile data, using normal EQ\n"); + snd_soc_add_controls(codec, wm9081_eq_controls, + ARRAY_SIZE(wm9081_eq_controls)); + } + + snd_soc_dapm_new_controls(codec, wm9081_dapm_widgets, + ARRAY_SIZE(wm9081_dapm_widgets)); + snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); + snd_soc_dapm_new_widgets(codec); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card: %d\n", ret); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + return ret; +} + +static int wm9081_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +#ifdef CONFIG_PM +static int wm9081_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + wm9081_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm9081_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + u16 *reg_cache = codec->reg_cache; + int i; + + for (i = 0; i < codec->reg_cache_size; i++) { + if (i == WM9081_SOFTWARE_RESET) + continue; + + wm9081_write(codec, i, reg_cache[i]); + } + + wm9081_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} +#else +#define wm9081_suspend NULL +#define wm9081_resume NULL +#endif + +struct snd_soc_codec_device soc_codec_dev_wm9081 = { + .probe = wm9081_probe, + .remove = wm9081_remove, + .suspend = wm9081_suspend, + .resume = wm9081_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm9081); + +static int wm9081_register(struct wm9081_priv *wm9081) +{ + struct snd_soc_codec *codec = &wm9081->codec; + int ret; + u16 reg; + + if (wm9081_codec) { + dev_err(codec->dev, "Another WM9081 is registered\n"); + ret = -EINVAL; + goto err; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = wm9081; + codec->name = "WM9081"; + codec->owner = THIS_MODULE; + codec->read = wm9081_read; + codec->write = wm9081_write; + codec->dai = &wm9081_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm9081->reg_cache); + codec->reg_cache = &wm9081->reg_cache; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm9081_set_bias_level; + + memcpy(codec->reg_cache, wm9081_reg_defaults, + sizeof(wm9081_reg_defaults)); + + reg = wm9081_read_hw(codec, WM9081_SOFTWARE_RESET); + if (reg != 0x9081) { + dev_err(codec->dev, "Device is not a WM9081: ID=0x%x\n", reg); + ret = -EINVAL; + goto err; + } + + ret = wm9081_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset\n"); + return ret; + } + + wm9081_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* Enable zero cross by default */ + reg = wm9081_read(codec, WM9081_ANALOGUE_LINEOUT); + wm9081_write(codec, WM9081_ANALOGUE_LINEOUT, reg | WM9081_LINEOUTZC); + reg = wm9081_read(codec, WM9081_ANALOGUE_SPEAKER_PGA); + wm9081_write(codec, WM9081_ANALOGUE_SPEAKER_PGA, + reg | WM9081_SPKPGAZC); + + wm9081_dai.dev = codec->dev; + + wm9081_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } + + ret = snd_soc_register_dai(&wm9081_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + return ret; + } + + return 0; + +err: + kfree(wm9081); + return ret; +} + +static void wm9081_unregister(struct wm9081_priv *wm9081) +{ + wm9081_set_bias_level(&wm9081->codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dai(&wm9081_dai); + snd_soc_unregister_codec(&wm9081->codec); + kfree(wm9081); + wm9081_codec = NULL; +} + +static __devinit int wm9081_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm9081_priv *wm9081; + struct snd_soc_codec *codec; + + wm9081 = kzalloc(sizeof(struct wm9081_priv), GFP_KERNEL); + if (wm9081 == NULL) + return -ENOMEM; + + codec = &wm9081->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + wm9081->retune = i2c->dev.platform_data; + + i2c_set_clientdata(i2c, wm9081); + codec->control_data = i2c; + + codec->dev = &i2c->dev; + + return wm9081_register(wm9081); +} + +static __devexit int wm9081_i2c_remove(struct i2c_client *client) +{ + struct wm9081_priv *wm9081 = i2c_get_clientdata(client); + wm9081_unregister(wm9081); + return 0; +} + +static const struct i2c_device_id wm9081_i2c_id[] = { + { "wm9081", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm9081_i2c_id); + +static struct i2c_driver wm9081_i2c_driver = { + .driver = { + .name = "wm9081", + .owner = THIS_MODULE, + }, + .probe = wm9081_i2c_probe, + .remove = __devexit_p(wm9081_i2c_remove), + .id_table = wm9081_i2c_id, +}; + +static int __init wm9081_modinit(void) +{ + int ret; + + ret = i2c_add_driver(&wm9081_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM9081 I2C driver: %d\n", + ret); + } + + return ret; +} +module_init(wm9081_modinit); + +static void __exit wm9081_exit(void) +{ + i2c_del_driver(&wm9081_i2c_driver); +} +module_exit(wm9081_exit); + + +MODULE_DESCRIPTION("ASoC WM9081 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm9081.h b/sound/soc/codecs/wm9081.h new file mode 100644 index 00000000000..42d3bc75702 --- /dev/null +++ b/sound/soc/codecs/wm9081.h @@ -0,0 +1,787 @@ +#ifndef WM9081_H +#define WM9081_H + +/* + * wm9081.c -- WM9081 ALSA SoC Audio driver + * + * Author: Mark Brown + * + * Copyright 2009 Wolfson Microelectronics plc + * + * 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 + +extern struct snd_soc_dai wm9081_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm9081; + +/* + * SYSCLK sources + */ +#define WM9081_SYSCLK_MCLK 1 /* Use MCLK without FLL */ +#define WM9081_SYSCLK_FLL_MCLK 2 /* Use MCLK, enabling FLL if required */ + +/* + * Register values. + */ +#define WM9081_SOFTWARE_RESET 0x00 +#define WM9081_ANALOGUE_LINEOUT 0x02 +#define WM9081_ANALOGUE_SPEAKER_PGA 0x03 +#define WM9081_VMID_CONTROL 0x04 +#define WM9081_BIAS_CONTROL_1 0x05 +#define WM9081_ANALOGUE_MIXER 0x07 +#define WM9081_ANTI_POP_CONTROL 0x08 +#define WM9081_ANALOGUE_SPEAKER_1 0x09 +#define WM9081_ANALOGUE_SPEAKER_2 0x0A +#define WM9081_POWER_MANAGEMENT 0x0B +#define WM9081_CLOCK_CONTROL_1 0x0C +#define WM9081_CLOCK_CONTROL_2 0x0D +#define WM9081_CLOCK_CONTROL_3 0x0E +#define WM9081_FLL_CONTROL_1 0x10 +#define WM9081_FLL_CONTROL_2 0x11 +#define WM9081_FLL_CONTROL_3 0x12 +#define WM9081_FLL_CONTROL_4 0x13 +#define WM9081_FLL_CONTROL_5 0x14 +#define WM9081_AUDIO_INTERFACE_1 0x16 +#define WM9081_AUDIO_INTERFACE_2 0x17 +#define WM9081_AUDIO_INTERFACE_3 0x18 +#define WM9081_AUDIO_INTERFACE_4 0x19 +#define WM9081_INTERRUPT_STATUS 0x1A +#define WM9081_INTERRUPT_STATUS_MASK 0x1B +#define WM9081_INTERRUPT_POLARITY 0x1C +#define WM9081_INTERRUPT_CONTROL 0x1D +#define WM9081_DAC_DIGITAL_1 0x1E +#define WM9081_DAC_DIGITAL_2 0x1F +#define WM9081_DRC_1 0x20 +#define WM9081_DRC_2 0x21 +#define WM9081_DRC_3 0x22 +#define WM9081_DRC_4 0x23 +#define WM9081_WRITE_SEQUENCER_1 0x26 +#define WM9081_WRITE_SEQUENCER_2 0x27 +#define WM9081_MW_SLAVE_1 0x28 +#define WM9081_EQ_1 0x2A +#define WM9081_EQ_2 0x2B +#define WM9081_EQ_3 0x2C +#define WM9081_EQ_4 0x2D +#define WM9081_EQ_5 0x2E +#define WM9081_EQ_6 0x2F +#define WM9081_EQ_7 0x30 +#define WM9081_EQ_8 0x31 +#define WM9081_EQ_9 0x32 +#define WM9081_EQ_10 0x33 +#define WM9081_EQ_11 0x34 +#define WM9081_EQ_12 0x35 +#define WM9081_EQ_13 0x36 +#define WM9081_EQ_14 0x37 +#define WM9081_EQ_15 0x38 +#define WM9081_EQ_16 0x39 +#define WM9081_EQ_17 0x3A +#define WM9081_EQ_18 0x3B +#define WM9081_EQ_19 0x3C +#define WM9081_EQ_20 0x3D + +#define WM9081_REGISTER_COUNT 55 +#define WM9081_MAX_REGISTER 0x3D + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Software Reset + */ +#define WM9081_SW_RST_DEV_ID1_MASK 0xFFFF /* SW_RST_DEV_ID1 - [15:0] */ +#define WM9081_SW_RST_DEV_ID1_SHIFT 0 /* SW_RST_DEV_ID1 - [15:0] */ +#define WM9081_SW_RST_DEV_ID1_WIDTH 16 /* SW_RST_DEV_ID1 - [15:0] */ + +/* + * R2 (0x02) - Analogue Lineout + */ +#define WM9081_LINEOUT_MUTE 0x0080 /* LINEOUT_MUTE */ +#define WM9081_LINEOUT_MUTE_MASK 0x0080 /* LINEOUT_MUTE */ +#define WM9081_LINEOUT_MUTE_SHIFT 7 /* LINEOUT_MUTE */ +#define WM9081_LINEOUT_MUTE_WIDTH 1 /* LINEOUT_MUTE */ +#define WM9081_LINEOUTZC 0x0040 /* LINEOUTZC */ +#define WM9081_LINEOUTZC_MASK 0x0040 /* LINEOUTZC */ +#define WM9081_LINEOUTZC_SHIFT 6 /* LINEOUTZC */ +#define WM9081_LINEOUTZC_WIDTH 1 /* LINEOUTZC */ +#define WM9081_LINEOUT_VOL_MASK 0x003F /* LINEOUT_VOL - [5:0] */ +#define WM9081_LINEOUT_VOL_SHIFT 0 /* LINEOUT_VOL - [5:0] */ +#define WM9081_LINEOUT_VOL_WIDTH 6 /* LINEOUT_VOL - [5:0] */ + +/* + * R3 (0x03) - Analogue Speaker PGA + */ +#define WM9081_SPKPGA_MUTE 0x0080 /* SPKPGA_MUTE */ +#define WM9081_SPKPGA_MUTE_MASK 0x0080 /* SPKPGA_MUTE */ +#define WM9081_SPKPGA_MUTE_SHIFT 7 /* SPKPGA_MUTE */ +#define WM9081_SPKPGA_MUTE_WIDTH 1 /* SPKPGA_MUTE */ +#define WM9081_SPKPGAZC 0x0040 /* SPKPGAZC */ +#define WM9081_SPKPGAZC_MASK 0x0040 /* SPKPGAZC */ +#define WM9081_SPKPGAZC_SHIFT 6 /* SPKPGAZC */ +#define WM9081_SPKPGAZC_WIDTH 1 /* SPKPGAZC */ +#define WM9081_SPKPGA_VOL_MASK 0x003F /* SPKPGA_VOL - [5:0] */ +#define WM9081_SPKPGA_VOL_SHIFT 0 /* SPKPGA_VOL - [5:0] */ +#define WM9081_SPKPGA_VOL_WIDTH 6 /* SPKPGA_VOL - [5:0] */ + +/* + * R4 (0x04) - VMID Control + */ +#define WM9081_VMID_BUF_ENA 0x0020 /* VMID_BUF_ENA */ +#define WM9081_VMID_BUF_ENA_MASK 0x0020 /* VMID_BUF_ENA */ +#define WM9081_VMID_BUF_ENA_SHIFT 5 /* VMID_BUF_ENA */ +#define WM9081_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */ +#define WM9081_VMID_RAMP 0x0008 /* VMID_RAMP */ +#define WM9081_VMID_RAMP_MASK 0x0008 /* VMID_RAMP */ +#define WM9081_VMID_RAMP_SHIFT 3 /* VMID_RAMP */ +#define WM9081_VMID_RAMP_WIDTH 1 /* VMID_RAMP */ +#define WM9081_VMID_SEL_MASK 0x0006 /* VMID_SEL - [2:1] */ +#define WM9081_VMID_SEL_SHIFT 1 /* VMID_SEL - [2:1] */ +#define WM9081_VMID_SEL_WIDTH 2 /* VMID_SEL - [2:1] */ +#define WM9081_VMID_FAST_ST 0x0001 /* VMID_FAST_ST */ +#define WM9081_VMID_FAST_ST_MASK 0x0001 /* VMID_FAST_ST */ +#define WM9081_VMID_FAST_ST_SHIFT 0 /* VMID_FAST_ST */ +#define WM9081_VMID_FAST_ST_WIDTH 1 /* VMID_FAST_ST */ + +/* + * R5 (0x05) - Bias Control 1 + */ +#define WM9081_BIAS_SRC 0x0040 /* BIAS_SRC */ +#define WM9081_BIAS_SRC_MASK 0x0040 /* BIAS_SRC */ +#define WM9081_BIAS_SRC_SHIFT 6 /* BIAS_SRC */ +#define WM9081_BIAS_SRC_WIDTH 1 /* BIAS_SRC */ +#define WM9081_STBY_BIAS_LVL 0x0020 /* STBY_BIAS_LVL */ +#define WM9081_STBY_BIAS_LVL_MASK 0x0020 /* STBY_BIAS_LVL */ +#define WM9081_STBY_BIAS_LVL_SHIFT 5 /* STBY_BIAS_LVL */ +#define WM9081_STBY_BIAS_LVL_WIDTH 1 /* STBY_BIAS_LVL */ +#define WM9081_STBY_BIAS_ENA 0x0010 /* STBY_BIAS_ENA */ +#define WM9081_STBY_BIAS_ENA_MASK 0x0010 /* STBY_BIAS_ENA */ +#define WM9081_STBY_BIAS_ENA_SHIFT 4 /* STBY_BIAS_ENA */ +#define WM9081_STBY_BIAS_ENA_WIDTH 1 /* STBY_BIAS_ENA */ +#define WM9081_BIAS_LVL_MASK 0x000C /* BIAS_LVL - [3:2] */ +#define WM9081_BIAS_LVL_SHIFT 2 /* BIAS_LVL - [3:2] */ +#define WM9081_BIAS_LVL_WIDTH 2 /* BIAS_LVL - [3:2] */ +#define WM9081_BIAS_ENA 0x0002 /* BIAS_ENA */ +#define WM9081_BIAS_ENA_MASK 0x0002 /* BIAS_ENA */ +#define WM9081_BIAS_ENA_SHIFT 1 /* BIAS_ENA */ +#define WM9081_BIAS_ENA_WIDTH 1 /* BIAS_ENA */ +#define WM9081_STARTUP_BIAS_ENA 0x0001 /* STARTUP_BIAS_ENA */ +#define WM9081_STARTUP_BIAS_ENA_MASK 0x0001 /* STARTUP_BIAS_ENA */ +#define WM9081_STARTUP_BIAS_ENA_SHIFT 0 /* STARTUP_BIAS_ENA */ +#define WM9081_STARTUP_BIAS_ENA_WIDTH 1 /* STARTUP_BIAS_ENA */ + +/* + * R7 (0x07) - Analogue Mixer + */ +#define WM9081_DAC_SEL 0x0010 /* DAC_SEL */ +#define WM9081_DAC_SEL_MASK 0x0010 /* DAC_SEL */ +#define WM9081_DAC_SEL_SHIFT 4 /* DAC_SEL */ +#define WM9081_DAC_SEL_WIDTH 1 /* DAC_SEL */ +#define WM9081_IN2_VOL 0x0008 /* IN2_VOL */ +#define WM9081_IN2_VOL_MASK 0x0008 /* IN2_VOL */ +#define WM9081_IN2_VOL_SHIFT 3 /* IN2_VOL */ +#define WM9081_IN2_VOL_WIDTH 1 /* IN2_VOL */ +#define WM9081_IN2_ENA 0x0004 /* IN2_ENA */ +#define WM9081_IN2_ENA_MASK 0x0004 /* IN2_ENA */ +#define WM9081_IN2_ENA_SHIFT 2 /* IN2_ENA */ +#define WM9081_IN2_ENA_WIDTH 1 /* IN2_ENA */ +#define WM9081_IN1_VOL 0x0002 /* IN1_VOL */ +#define WM9081_IN1_VOL_MASK 0x0002 /* IN1_VOL */ +#define WM9081_IN1_VOL_SHIFT 1 /* IN1_VOL */ +#define WM9081_IN1_VOL_WIDTH 1 /* IN1_VOL */ +#define WM9081_IN1_ENA 0x0001 /* IN1_ENA */ +#define WM9081_IN1_ENA_MASK 0x0001 /* IN1_ENA */ +#define WM9081_IN1_ENA_SHIFT 0 /* IN1_ENA */ +#define WM9081_IN1_ENA_WIDTH 1 /* IN1_ENA */ + +/* + * R8 (0x08) - Anti Pop Control + */ +#define WM9081_LINEOUT_DISCH 0x0004 /* LINEOUT_DISCH */ +#define WM9081_LINEOUT_DISCH_MASK 0x0004 /* LINEOUT_DISCH */ +#define WM9081_LINEOUT_DISCH_SHIFT 2 /* LINEOUT_DISCH */ +#define WM9081_LINEOUT_DISCH_WIDTH 1 /* LINEOUT_DISCH */ +#define WM9081_LINEOUT_VROI 0x0002 /* LINEOUT_VROI */ +#define WM9081_LINEOUT_VROI_MASK 0x0002 /* LINEOUT_VROI */ +#define WM9081_LINEOUT_VROI_SHIFT 1 /* LINEOUT_VROI */ +#define WM9081_LINEOUT_VROI_WIDTH 1 /* LINEOUT_VROI */ +#define WM9081_LINEOUT_CLAMP 0x0001 /* LINEOUT_CLAMP */ +#define WM9081_LINEOUT_CLAMP_MASK 0x0001 /* LINEOUT_CLAMP */ +#define WM9081_LINEOUT_CLAMP_SHIFT 0 /* LINEOUT_CLAMP */ +#define WM9081_LINEOUT_CLAMP_WIDTH 1 /* LINEOUT_CLAMP */ + +/* + * R9 (0x09) - Analogue Speaker 1 + */ +#define WM9081_SPK_DCGAIN_MASK 0x0038 /* SPK_DCGAIN - [5:3] */ +#define WM9081_SPK_DCGAIN_SHIFT 3 /* SPK_DCGAIN - [5:3] */ +#define WM9081_SPK_DCGAIN_WIDTH 3 /* SPK_DCGAIN - [5:3] */ +#define WM9081_SPK_ACGAIN_MASK 0x0007 /* SPK_ACGAIN - [2:0] */ +#define WM9081_SPK_ACGAIN_SHIFT 0 /* SPK_ACGAIN - [2:0] */ +#define WM9081_SPK_ACGAIN_WIDTH 3 /* SPK_ACGAIN - [2:0] */ + +/* + * R10 (0x0A) - Analogue Speaker 2 + */ +#define WM9081_SPK_MODE 0x0040 /* SPK_MODE */ +#define WM9081_SPK_MODE_MASK 0x0040 /* SPK_MODE */ +#define WM9081_SPK_MODE_SHIFT 6 /* SPK_MODE */ +#define WM9081_SPK_MODE_WIDTH 1 /* SPK_MODE */ +#define WM9081_SPK_INV_MUTE 0x0010 /* SPK_INV_MUTE */ +#define WM9081_SPK_INV_MUTE_MASK 0x0010 /* SPK_INV_MUTE */ +#define WM9081_SPK_INV_MUTE_SHIFT 4 /* SPK_INV_MUTE */ +#define WM9081_SPK_INV_MUTE_WIDTH 1 /* SPK_INV_MUTE */ +#define WM9081_OUT_SPK_CTRL 0x0008 /* OUT_SPK_CTRL */ +#define WM9081_OUT_SPK_CTRL_MASK 0x0008 /* OUT_SPK_CTRL */ +#define WM9081_OUT_SPK_CTRL_SHIFT 3 /* OUT_SPK_CTRL */ +#define WM9081_OUT_SPK_CTRL_WIDTH 1 /* OUT_SPK_CTRL */ + +/* + * R11 (0x0B) - Power Management + */ +#define WM9081_TSHUT_ENA 0x0100 /* TSHUT_ENA */ +#define WM9081_TSHUT_ENA_MASK 0x0100 /* TSHUT_ENA */ +#define WM9081_TSHUT_ENA_SHIFT 8 /* TSHUT_ENA */ +#define WM9081_TSHUT_ENA_WIDTH 1 /* TSHUT_ENA */ +#define WM9081_TSENSE_ENA 0x0080 /* TSENSE_ENA */ +#define WM9081_TSENSE_ENA_MASK 0x0080 /* TSENSE_ENA */ +#define WM9081_TSENSE_ENA_SHIFT 7 /* TSENSE_ENA */ +#define WM9081_TSENSE_ENA_WIDTH 1 /* TSENSE_ENA */ +#define WM9081_TEMP_SHUT 0x0040 /* TEMP_SHUT */ +#define WM9081_TEMP_SHUT_MASK 0x0040 /* TEMP_SHUT */ +#define WM9081_TEMP_SHUT_SHIFT 6 /* TEMP_SHUT */ +#define WM9081_TEMP_SHUT_WIDTH 1 /* TEMP_SHUT */ +#define WM9081_LINEOUT_ENA 0x0010 /* LINEOUT_ENA */ +#define WM9081_LINEOUT_ENA_MASK 0x0010 /* LINEOUT_ENA */ +#define WM9081_LINEOUT_ENA_SHIFT 4 /* LINEOUT_ENA */ +#define WM9081_LINEOUT_ENA_WIDTH 1 /* LINEOUT_ENA */ +#define WM9081_SPKPGA_ENA 0x0004 /* SPKPGA_ENA */ +#define WM9081_SPKPGA_ENA_MASK 0x0004 /* SPKPGA_ENA */ +#define WM9081_SPKPGA_ENA_SHIFT 2 /* SPKPGA_ENA */ +#define WM9081_SPKPGA_ENA_WIDTH 1 /* SPKPGA_ENA */ +#define WM9081_SPK_ENA 0x0002 /* SPK_ENA */ +#define WM9081_SPK_ENA_MASK 0x0002 /* SPK_ENA */ +#define WM9081_SPK_ENA_SHIFT 1 /* SPK_ENA */ +#define WM9081_SPK_ENA_WIDTH 1 /* SPK_ENA */ +#define WM9081_DAC_ENA 0x0001 /* DAC_ENA */ +#define WM9081_DAC_ENA_MASK 0x0001 /* DAC_ENA */ +#define WM9081_DAC_ENA_SHIFT 0 /* DAC_ENA */ +#define WM9081_DAC_ENA_WIDTH 1 /* DAC_ENA */ + +/* + * R12 (0x0C) - Clock Control 1 + */ +#define WM9081_CLK_OP_DIV_MASK 0x1C00 /* CLK_OP_DIV - [12:10] */ +#define WM9081_CLK_OP_DIV_SHIFT 10 /* CLK_OP_DIV - [12:10] */ +#define WM9081_CLK_OP_DIV_WIDTH 3 /* CLK_OP_DIV - [12:10] */ +#define WM9081_CLK_TO_DIV_MASK 0x0300 /* CLK_TO_DIV - [9:8] */ +#define WM9081_CLK_TO_DIV_SHIFT 8 /* CLK_TO_DIV - [9:8] */ +#define WM9081_CLK_TO_DIV_WIDTH 2 /* CLK_TO_DIV - [9:8] */ +#define WM9081_MCLKDIV2 0x0080 /* MCLKDIV2 */ +#define WM9081_MCLKDIV2_MASK 0x0080 /* MCLKDIV2 */ +#define WM9081_MCLKDIV2_SHIFT 7 /* MCLKDIV2 */ +#define WM9081_MCLKDIV2_WIDTH 1 /* MCLKDIV2 */ + +/* + * R13 (0x0D) - Clock Control 2 + */ +#define WM9081_CLK_SYS_RATE_MASK 0x00F0 /* CLK_SYS_RATE - [7:4] */ +#define WM9081_CLK_SYS_RATE_SHIFT 4 /* CLK_SYS_RATE - [7:4] */ +#define WM9081_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [7:4] */ +#define WM9081_SAMPLE_RATE_MASK 0x000F /* SAMPLE_RATE - [3:0] */ +#define WM9081_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [3:0] */ +#define WM9081_SAMPLE_RATE_WIDTH 4 /* SAMPLE_RATE - [3:0] */ + +/* + * R14 (0x0E) - Clock Control 3 + */ +#define WM9081_CLK_SRC_SEL 0x2000 /* CLK_SRC_SEL */ +#define WM9081_CLK_SRC_SEL_MASK 0x2000 /* CLK_SRC_SEL */ +#define WM9081_CLK_SRC_SEL_SHIFT 13 /* CLK_SRC_SEL */ +#define WM9081_CLK_SRC_SEL_WIDTH 1 /* CLK_SRC_SEL */ +#define WM9081_CLK_OP_ENA 0x0020 /* CLK_OP_ENA */ +#define WM9081_CLK_OP_ENA_MASK 0x0020 /* CLK_OP_ENA */ +#define WM9081_CLK_OP_ENA_SHIFT 5 /* CLK_OP_ENA */ +#define WM9081_CLK_OP_ENA_WIDTH 1 /* CLK_OP_ENA */ +#define WM9081_CLK_TO_ENA 0x0004 /* CLK_TO_ENA */ +#define WM9081_CLK_TO_ENA_MASK 0x0004 /* CLK_TO_ENA */ +#define WM9081_CLK_TO_ENA_SHIFT 2 /* CLK_TO_ENA */ +#define WM9081_CLK_TO_ENA_WIDTH 1 /* CLK_TO_ENA */ +#define WM9081_CLK_DSP_ENA 0x0002 /* CLK_DSP_ENA */ +#define WM9081_CLK_DSP_ENA_MASK 0x0002 /* CLK_DSP_ENA */ +#define WM9081_CLK_DSP_ENA_SHIFT 1 /* CLK_DSP_ENA */ +#define WM9081_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */ +#define WM9081_CLK_SYS_ENA 0x0001 /* CLK_SYS_ENA */ +#define WM9081_CLK_SYS_ENA_MASK 0x0001 /* CLK_SYS_ENA */ +#define WM9081_CLK_SYS_ENA_SHIFT 0 /* CLK_SYS_ENA */ +#define WM9081_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */ + +/* + * R16 (0x10) - FLL Control 1 + */ +#define WM9081_FLL_HOLD 0x0008 /* FLL_HOLD */ +#define WM9081_FLL_HOLD_MASK 0x0008 /* FLL_HOLD */ +#define WM9081_FLL_HOLD_SHIFT 3 /* FLL_HOLD */ +#define WM9081_FLL_HOLD_WIDTH 1 /* FLL_HOLD */ +#define WM9081_FLL_FRAC 0x0004 /* FLL_FRAC */ +#define WM9081_FLL_FRAC_MASK 0x0004 /* FLL_FRAC */ +#define WM9081_FLL_FRAC_SHIFT 2 /* FLL_FRAC */ +#define WM9081_FLL_FRAC_WIDTH 1 /* FLL_FRAC */ +#define WM9081_FLL_ENA 0x0001 /* FLL_ENA */ +#define WM9081_FLL_ENA_MASK 0x0001 /* FLL_ENA */ +#define WM9081_FLL_ENA_SHIFT 0 /* FLL_ENA */ +#define WM9081_FLL_ENA_WIDTH 1 /* FLL_ENA */ + +/* + * R17 (0x11) - FLL Control 2 + */ +#define WM9081_FLL_OUTDIV_MASK 0x0700 /* FLL_OUTDIV - [10:8] */ +#define WM9081_FLL_OUTDIV_SHIFT 8 /* FLL_OUTDIV - [10:8] */ +#define WM9081_FLL_OUTDIV_WIDTH 3 /* FLL_OUTDIV - [10:8] */ +#define WM9081_FLL_CTRL_RATE_MASK 0x0070 /* FLL_CTRL_RATE - [6:4] */ +#define WM9081_FLL_CTRL_RATE_SHIFT 4 /* FLL_CTRL_RATE - [6:4] */ +#define WM9081_FLL_CTRL_RATE_WIDTH 3 /* FLL_CTRL_RATE - [6:4] */ +#define WM9081_FLL_FRATIO_MASK 0x0007 /* FLL_FRATIO - [2:0] */ +#define WM9081_FLL_FRATIO_SHIFT 0 /* FLL_FRATIO - [2:0] */ +#define WM9081_FLL_FRATIO_WIDTH 3 /* FLL_FRATIO - [2:0] */ + +/* + * R18 (0x12) - FLL Control 3 + */ +#define WM9081_FLL_K_MASK 0xFFFF /* FLL_K - [15:0] */ +#define WM9081_FLL_K_SHIFT 0 /* FLL_K - [15:0] */ +#define WM9081_FLL_K_WIDTH 16 /* FLL_K - [15:0] */ + +/* + * R19 (0x13) - FLL Control 4 + */ +#define WM9081_FLL_N_MASK 0x7FE0 /* FLL_N - [14:5] */ +#define WM9081_FLL_N_SHIFT 5 /* FLL_N - [14:5] */ +#define WM9081_FLL_N_WIDTH 10 /* FLL_N - [14:5] */ +#define WM9081_FLL_GAIN_MASK 0x000F /* FLL_GAIN - [3:0] */ +#define WM9081_FLL_GAIN_SHIFT 0 /* FLL_GAIN - [3:0] */ +#define WM9081_FLL_GAIN_WIDTH 4 /* FLL_GAIN - [3:0] */ + +/* + * R20 (0x14) - FLL Control 5 + */ +#define WM9081_FLL_CLK_REF_DIV_MASK 0x0018 /* FLL_CLK_REF_DIV - [4:3] */ +#define WM9081_FLL_CLK_REF_DIV_SHIFT 3 /* FLL_CLK_REF_DIV - [4:3] */ +#define WM9081_FLL_CLK_REF_DIV_WIDTH 2 /* FLL_CLK_REF_DIV - [4:3] */ +#define WM9081_FLL_CLK_SRC_MASK 0x0003 /* FLL_CLK_SRC - [1:0] */ +#define WM9081_FLL_CLK_SRC_SHIFT 0 /* FLL_CLK_SRC - [1:0] */ +#define WM9081_FLL_CLK_SRC_WIDTH 2 /* FLL_CLK_SRC - [1:0] */ + +/* + * R22 (0x16) - Audio Interface 1 + */ +#define WM9081_AIFDAC_CHAN 0x0040 /* AIFDAC_CHAN */ +#define WM9081_AIFDAC_CHAN_MASK 0x0040 /* AIFDAC_CHAN */ +#define WM9081_AIFDAC_CHAN_SHIFT 6 /* AIFDAC_CHAN */ +#define WM9081_AIFDAC_CHAN_WIDTH 1 /* AIFDAC_CHAN */ +#define WM9081_AIFDAC_TDM_SLOT_MASK 0x0030 /* AIFDAC_TDM_SLOT - [5:4] */ +#define WM9081_AIFDAC_TDM_SLOT_SHIFT 4 /* AIFDAC_TDM_SLOT - [5:4] */ +#define WM9081_AIFDAC_TDM_SLOT_WIDTH 2 /* AIFDAC_TDM_SLOT - [5:4] */ +#define WM9081_AIFDAC_TDM_MODE_MASK 0x000C /* AIFDAC_TDM_MODE - [3:2] */ +#define WM9081_AIFDAC_TDM_MODE_SHIFT 2 /* AIFDAC_TDM_MODE - [3:2] */ +#define WM9081_AIFDAC_TDM_MODE_WIDTH 2 /* AIFDAC_TDM_MODE - [3:2] */ +#define WM9081_DAC_COMP 0x0002 /* DAC_COMP */ +#define WM9081_DAC_COMP_MASK 0x0002 /* DAC_COMP */ +#define WM9081_DAC_COMP_SHIFT 1 /* DAC_COMP */ +#define WM9081_DAC_COMP_WIDTH 1 /* DAC_COMP */ +#define WM9081_DAC_COMPMODE 0x0001 /* DAC_COMPMODE */ +#define WM9081_DAC_COMPMODE_MASK 0x0001 /* DAC_COMPMODE */ +#define WM9081_DAC_COMPMODE_SHIFT 0 /* DAC_COMPMODE */ +#define WM9081_DAC_COMPMODE_WIDTH 1 /* DAC_COMPMODE */ + +/* + * R23 (0x17) - Audio Interface 2 + */ +#define WM9081_AIF_TRIS 0x0200 /* AIF_TRIS */ +#define WM9081_AIF_TRIS_MASK 0x0200 /* AIF_TRIS */ +#define WM9081_AIF_TRIS_SHIFT 9 /* AIF_TRIS */ +#define WM9081_AIF_TRIS_WIDTH 1 /* AIF_TRIS */ +#define WM9081_DAC_DAT_INV 0x0100 /* DAC_DAT_INV */ +#define WM9081_DAC_DAT_INV_MASK 0x0100 /* DAC_DAT_INV */ +#define WM9081_DAC_DAT_INV_SHIFT 8 /* DAC_DAT_INV */ +#define WM9081_DAC_DAT_INV_WIDTH 1 /* DAC_DAT_INV */ +#define WM9081_AIF_BCLK_INV 0x0080 /* AIF_BCLK_INV */ +#define WM9081_AIF_BCLK_INV_MASK 0x0080 /* AIF_BCLK_INV */ +#define WM9081_AIF_BCLK_INV_SHIFT 7 /* AIF_BCLK_INV */ +#define WM9081_AIF_BCLK_INV_WIDTH 1 /* AIF_BCLK_INV */ +#define WM9081_BCLK_DIR 0x0040 /* BCLK_DIR */ +#define WM9081_BCLK_DIR_MASK 0x0040 /* BCLK_DIR */ +#define WM9081_BCLK_DIR_SHIFT 6 /* BCLK_DIR */ +#define WM9081_BCLK_DIR_WIDTH 1 /* BCLK_DIR */ +#define WM9081_LRCLK_DIR 0x0020 /* LRCLK_DIR */ +#define WM9081_LRCLK_DIR_MASK 0x0020 /* LRCLK_DIR */ +#define WM9081_LRCLK_DIR_SHIFT 5 /* LRCLK_DIR */ +#define WM9081_LRCLK_DIR_WIDTH 1 /* LRCLK_DIR */ +#define WM9081_AIF_LRCLK_INV 0x0010 /* AIF_LRCLK_INV */ +#define WM9081_AIF_LRCLK_INV_MASK 0x0010 /* AIF_LRCLK_INV */ +#define WM9081_AIF_LRCLK_INV_SHIFT 4 /* AIF_LRCLK_INV */ +#define WM9081_AIF_LRCLK_INV_WIDTH 1 /* AIF_LRCLK_INV */ +#define WM9081_AIF_WL_MASK 0x000C /* AIF_WL - [3:2] */ +#define WM9081_AIF_WL_SHIFT 2 /* AIF_WL - [3:2] */ +#define WM9081_AIF_WL_WIDTH 2 /* AIF_WL - [3:2] */ +#define WM9081_AIF_FMT_MASK 0x0003 /* AIF_FMT - [1:0] */ +#define WM9081_AIF_FMT_SHIFT 0 /* AIF_FMT - [1:0] */ +#define WM9081_AIF_FMT_WIDTH 2 /* AIF_FMT - [1:0] */ + +/* + * R24 (0x18) - Audio Interface 3 + */ +#define WM9081_BCLK_DIV_MASK 0x001F /* BCLK_DIV - [4:0] */ +#define WM9081_BCLK_DIV_SHIFT 0 /* BCLK_DIV - [4:0] */ +#define WM9081_BCLK_DIV_WIDTH 5 /* BCLK_DIV - [4:0] */ + +/* + * R25 (0x19) - Audio Interface 4 + */ +#define WM9081_LRCLK_RATE_MASK 0x07FF /* LRCLK_RATE - [10:0] */ +#define WM9081_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [10:0] */ +#define WM9081_LRCLK_RATE_WIDTH 11 /* LRCLK_RATE - [10:0] */ + +/* + * R26 (0x1A) - Interrupt Status + */ +#define WM9081_WSEQ_BUSY_EINT 0x0004 /* WSEQ_BUSY_EINT */ +#define WM9081_WSEQ_BUSY_EINT_MASK 0x0004 /* WSEQ_BUSY_EINT */ +#define WM9081_WSEQ_BUSY_EINT_SHIFT 2 /* WSEQ_BUSY_EINT */ +#define WM9081_WSEQ_BUSY_EINT_WIDTH 1 /* WSEQ_BUSY_EINT */ +#define WM9081_TSHUT_EINT 0x0001 /* TSHUT_EINT */ +#define WM9081_TSHUT_EINT_MASK 0x0001 /* TSHUT_EINT */ +#define WM9081_TSHUT_EINT_SHIFT 0 /* TSHUT_EINT */ +#define WM9081_TSHUT_EINT_WIDTH 1 /* TSHUT_EINT */ + +/* + * R27 (0x1B) - Interrupt Status Mask + */ +#define WM9081_IM_WSEQ_BUSY_EINT 0x0004 /* IM_WSEQ_BUSY_EINT */ +#define WM9081_IM_WSEQ_BUSY_EINT_MASK 0x0004 /* IM_WSEQ_BUSY_EINT */ +#define WM9081_IM_WSEQ_BUSY_EINT_SHIFT 2 /* IM_WSEQ_BUSY_EINT */ +#define WM9081_IM_WSEQ_BUSY_EINT_WIDTH 1 /* IM_WSEQ_BUSY_EINT */ +#define WM9081_IM_TSHUT_EINT 0x0001 /* IM_TSHUT_EINT */ +#define WM9081_IM_TSHUT_EINT_MASK 0x0001 /* IM_TSHUT_EINT */ +#define WM9081_IM_TSHUT_EINT_SHIFT 0 /* IM_TSHUT_EINT */ +#define WM9081_IM_TSHUT_EINT_WIDTH 1 /* IM_TSHUT_EINT */ + +/* + * R28 (0x1C) - Interrupt Polarity + */ +#define WM9081_TSHUT_INV 0x0001 /* TSHUT_INV */ +#define WM9081_TSHUT_INV_MASK 0x0001 /* TSHUT_INV */ +#define WM9081_TSHUT_INV_SHIFT 0 /* TSHUT_INV */ +#define WM9081_TSHUT_INV_WIDTH 1 /* TSHUT_INV */ + +/* + * R29 (0x1D) - Interrupt Control + */ +#define WM9081_IRQ_POL 0x8000 /* IRQ_POL */ +#define WM9081_IRQ_POL_MASK 0x8000 /* IRQ_POL */ +#define WM9081_IRQ_POL_SHIFT 15 /* IRQ_POL */ +#define WM9081_IRQ_POL_WIDTH 1 /* IRQ_POL */ +#define WM9081_IRQ_OP_CTRL 0x0001 /* IRQ_OP_CTRL */ +#define WM9081_IRQ_OP_CTRL_MASK 0x0001 /* IRQ_OP_CTRL */ +#define WM9081_IRQ_OP_CTRL_SHIFT 0 /* IRQ_OP_CTRL */ +#define WM9081_IRQ_OP_CTRL_WIDTH 1 /* IRQ_OP_CTRL */ + +/* + * R30 (0x1E) - DAC Digital 1 + */ +#define WM9081_DAC_VOL_MASK 0x00FF /* DAC_VOL - [7:0] */ +#define WM9081_DAC_VOL_SHIFT 0 /* DAC_VOL - [7:0] */ +#define WM9081_DAC_VOL_WIDTH 8 /* DAC_VOL - [7:0] */ + +/* + * R31 (0x1F) - DAC Digital 2 + */ +#define WM9081_DAC_MUTERATE 0x0400 /* DAC_MUTERATE */ +#define WM9081_DAC_MUTERATE_MASK 0x0400 /* DAC_MUTERATE */ +#define WM9081_DAC_MUTERATE_SHIFT 10 /* DAC_MUTERATE */ +#define WM9081_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */ +#define WM9081_DAC_MUTEMODE 0x0200 /* DAC_MUTEMODE */ +#define WM9081_DAC_MUTEMODE_MASK 0x0200 /* DAC_MUTEMODE */ +#define WM9081_DAC_MUTEMODE_SHIFT 9 /* DAC_MUTEMODE */ +#define WM9081_DAC_MUTEMODE_WIDTH 1 /* DAC_MUTEMODE */ +#define WM9081_DAC_MUTE 0x0008 /* DAC_MUTE */ +#define WM9081_DAC_MUTE_MASK 0x0008 /* DAC_MUTE */ +#define WM9081_DAC_MUTE_SHIFT 3 /* DAC_MUTE */ +#define WM9081_DAC_MUTE_WIDTH 1 /* DAC_MUTE */ +#define WM9081_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */ +#define WM9081_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */ +#define WM9081_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */ + +/* + * R32 (0x20) - DRC 1 + */ +#define WM9081_DRC_ENA 0x8000 /* DRC_ENA */ +#define WM9081_DRC_ENA_MASK 0x8000 /* DRC_ENA */ +#define WM9081_DRC_ENA_SHIFT 15 /* DRC_ENA */ +#define WM9081_DRC_ENA_WIDTH 1 /* DRC_ENA */ +#define WM9081_DRC_STARTUP_GAIN_MASK 0x07C0 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM9081_DRC_STARTUP_GAIN_SHIFT 6 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM9081_DRC_STARTUP_GAIN_WIDTH 5 /* DRC_STARTUP_GAIN - [10:6] */ +#define WM9081_DRC_FF_DLY 0x0020 /* DRC_FF_DLY */ +#define WM9081_DRC_FF_DLY_MASK 0x0020 /* DRC_FF_DLY */ +#define WM9081_DRC_FF_DLY_SHIFT 5 /* DRC_FF_DLY */ +#define WM9081_DRC_FF_DLY_WIDTH 1 /* DRC_FF_DLY */ +#define WM9081_DRC_QR 0x0004 /* DRC_QR */ +#define WM9081_DRC_QR_MASK 0x0004 /* DRC_QR */ +#define WM9081_DRC_QR_SHIFT 2 /* DRC_QR */ +#define WM9081_DRC_QR_WIDTH 1 /* DRC_QR */ +#define WM9081_DRC_ANTICLIP 0x0002 /* DRC_ANTICLIP */ +#define WM9081_DRC_ANTICLIP_MASK 0x0002 /* DRC_ANTICLIP */ +#define WM9081_DRC_ANTICLIP_SHIFT 1 /* DRC_ANTICLIP */ +#define WM9081_DRC_ANTICLIP_WIDTH 1 /* DRC_ANTICLIP */ + +/* + * R33 (0x21) - DRC 2 + */ +#define WM9081_DRC_ATK_MASK 0xF000 /* DRC_ATK - [15:12] */ +#define WM9081_DRC_ATK_SHIFT 12 /* DRC_ATK - [15:12] */ +#define WM9081_DRC_ATK_WIDTH 4 /* DRC_ATK - [15:12] */ +#define WM9081_DRC_DCY_MASK 0x0F00 /* DRC_DCY - [11:8] */ +#define WM9081_DRC_DCY_SHIFT 8 /* DRC_DCY - [11:8] */ +#define WM9081_DRC_DCY_WIDTH 4 /* DRC_DCY - [11:8] */ +#define WM9081_DRC_QR_THR_MASK 0x00C0 /* DRC_QR_THR - [7:6] */ +#define WM9081_DRC_QR_THR_SHIFT 6 /* DRC_QR_THR - [7:6] */ +#define WM9081_DRC_QR_THR_WIDTH 2 /* DRC_QR_THR - [7:6] */ +#define WM9081_DRC_QR_DCY_MASK 0x0030 /* DRC_QR_DCY - [5:4] */ +#define WM9081_DRC_QR_DCY_SHIFT 4 /* DRC_QR_DCY - [5:4] */ +#define WM9081_DRC_QR_DCY_WIDTH 2 /* DRC_QR_DCY - [5:4] */ +#define WM9081_DRC_MINGAIN_MASK 0x000C /* DRC_MINGAIN - [3:2] */ +#define WM9081_DRC_MINGAIN_SHIFT 2 /* DRC_MINGAIN - [3:2] */ +#define WM9081_DRC_MINGAIN_WIDTH 2 /* DRC_MINGAIN - [3:2] */ +#define WM9081_DRC_MAXGAIN_MASK 0x0003 /* DRC_MAXGAIN - [1:0] */ +#define WM9081_DRC_MAXGAIN_SHIFT 0 /* DRC_MAXGAIN - [1:0] */ +#define WM9081_DRC_MAXGAIN_WIDTH 2 /* DRC_MAXGAIN - [1:0] */ + +/* + * R34 (0x22) - DRC 3 + */ +#define WM9081_DRC_HI_COMP_MASK 0x0038 /* DRC_HI_COMP - [5:3] */ +#define WM9081_DRC_HI_COMP_SHIFT 3 /* DRC_HI_COMP - [5:3] */ +#define WM9081_DRC_HI_COMP_WIDTH 3 /* DRC_HI_COMP - [5:3] */ +#define WM9081_DRC_LO_COMP_MASK 0x0007 /* DRC_LO_COMP - [2:0] */ +#define WM9081_DRC_LO_COMP_SHIFT 0 /* DRC_LO_COMP - [2:0] */ +#define WM9081_DRC_LO_COMP_WIDTH 3 /* DRC_LO_COMP - [2:0] */ + +/* + * R35 (0x23) - DRC 4 + */ +#define WM9081_DRC_KNEE_IP_MASK 0x07E0 /* DRC_KNEE_IP - [10:5] */ +#define WM9081_DRC_KNEE_IP_SHIFT 5 /* DRC_KNEE_IP - [10:5] */ +#define WM9081_DRC_KNEE_IP_WIDTH 6 /* DRC_KNEE_IP - [10:5] */ +#define WM9081_DRC_KNEE_OP_MASK 0x001F /* DRC_KNEE_OP - [4:0] */ +#define WM9081_DRC_KNEE_OP_SHIFT 0 /* DRC_KNEE_OP - [4:0] */ +#define WM9081_DRC_KNEE_OP_WIDTH 5 /* DRC_KNEE_OP - [4:0] */ + +/* + * R38 (0x26) - Write Sequencer 1 + */ +#define WM9081_WSEQ_ENA 0x8000 /* WSEQ_ENA */ +#define WM9081_WSEQ_ENA_MASK 0x8000 /* WSEQ_ENA */ +#define WM9081_WSEQ_ENA_SHIFT 15 /* WSEQ_ENA */ +#define WM9081_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */ +#define WM9081_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */ +#define WM9081_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */ +#define WM9081_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */ +#define WM9081_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */ +#define WM9081_WSEQ_START 0x0100 /* WSEQ_START */ +#define WM9081_WSEQ_START_MASK 0x0100 /* WSEQ_START */ +#define WM9081_WSEQ_START_SHIFT 8 /* WSEQ_START */ +#define WM9081_WSEQ_START_WIDTH 1 /* WSEQ_START */ +#define WM9081_WSEQ_START_INDEX_MASK 0x007F /* WSEQ_START_INDEX - [6:0] */ +#define WM9081_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [6:0] */ +#define WM9081_WSEQ_START_INDEX_WIDTH 7 /* WSEQ_START_INDEX - [6:0] */ + +/* + * R39 (0x27) - Write Sequencer 2 + */ +#define WM9081_WSEQ_CURRENT_INDEX_MASK 0x07F0 /* WSEQ_CURRENT_INDEX - [10:4] */ +#define WM9081_WSEQ_CURRENT_INDEX_SHIFT 4 /* WSEQ_CURRENT_INDEX - [10:4] */ +#define WM9081_WSEQ_CURRENT_INDEX_WIDTH 7 /* WSEQ_CURRENT_INDEX - [10:4] */ +#define WM9081_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */ +#define WM9081_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */ +#define WM9081_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */ +#define WM9081_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */ + +/* + * R40 (0x28) - MW Slave 1 + */ +#define WM9081_SPI_CFG 0x0020 /* SPI_CFG */ +#define WM9081_SPI_CFG_MASK 0x0020 /* SPI_CFG */ +#define WM9081_SPI_CFG_SHIFT 5 /* SPI_CFG */ +#define WM9081_SPI_CFG_WIDTH 1 /* SPI_CFG */ +#define WM9081_SPI_4WIRE 0x0010 /* SPI_4WIRE */ +#define WM9081_SPI_4WIRE_MASK 0x0010 /* SPI_4WIRE */ +#define WM9081_SPI_4WIRE_SHIFT 4 /* SPI_4WIRE */ +#define WM9081_SPI_4WIRE_WIDTH 1 /* SPI_4WIRE */ +#define WM9081_ARA_ENA 0x0008 /* ARA_ENA */ +#define WM9081_ARA_ENA_MASK 0x0008 /* ARA_ENA */ +#define WM9081_ARA_ENA_SHIFT 3 /* ARA_ENA */ +#define WM9081_ARA_ENA_WIDTH 1 /* ARA_ENA */ +#define WM9081_AUTO_INC 0x0002 /* AUTO_INC */ +#define WM9081_AUTO_INC_MASK 0x0002 /* AUTO_INC */ +#define WM9081_AUTO_INC_SHIFT 1 /* AUTO_INC */ +#define WM9081_AUTO_INC_WIDTH 1 /* AUTO_INC */ + +/* + * R42 (0x2A) - EQ 1 + */ +#define WM9081_EQ_B1_GAIN_MASK 0xF800 /* EQ_B1_GAIN - [15:11] */ +#define WM9081_EQ_B1_GAIN_SHIFT 11 /* EQ_B1_GAIN - [15:11] */ +#define WM9081_EQ_B1_GAIN_WIDTH 5 /* EQ_B1_GAIN - [15:11] */ +#define WM9081_EQ_B2_GAIN_MASK 0x07C0 /* EQ_B2_GAIN - [10:6] */ +#define WM9081_EQ_B2_GAIN_SHIFT 6 /* EQ_B2_GAIN - [10:6] */ +#define WM9081_EQ_B2_GAIN_WIDTH 5 /* EQ_B2_GAIN - [10:6] */ +#define WM9081_EQ_B4_GAIN_MASK 0x003E /* EQ_B4_GAIN - [5:1] */ +#define WM9081_EQ_B4_GAIN_SHIFT 1 /* EQ_B4_GAIN - [5:1] */ +#define WM9081_EQ_B4_GAIN_WIDTH 5 /* EQ_B4_GAIN - [5:1] */ +#define WM9081_EQ_ENA 0x0001 /* EQ_ENA */ +#define WM9081_EQ_ENA_MASK 0x0001 /* EQ_ENA */ +#define WM9081_EQ_ENA_SHIFT 0 /* EQ_ENA */ +#define WM9081_EQ_ENA_WIDTH 1 /* EQ_ENA */ + +/* + * R43 (0x2B) - EQ 2 + */ +#define WM9081_EQ_B3_GAIN_MASK 0xF800 /* EQ_B3_GAIN - [15:11] */ +#define WM9081_EQ_B3_GAIN_SHIFT 11 /* EQ_B3_GAIN - [15:11] */ +#define WM9081_EQ_B3_GAIN_WIDTH 5 /* EQ_B3_GAIN - [15:11] */ +#define WM9081_EQ_B5_GAIN_MASK 0x07C0 /* EQ_B5_GAIN - [10:6] */ +#define WM9081_EQ_B5_GAIN_SHIFT 6 /* EQ_B5_GAIN - [10:6] */ +#define WM9081_EQ_B5_GAIN_WIDTH 5 /* EQ_B5_GAIN - [10:6] */ + +/* + * R44 (0x2C) - EQ 3 + */ +#define WM9081_EQ_B1_A_MASK 0xFFFF /* EQ_B1_A - [15:0] */ +#define WM9081_EQ_B1_A_SHIFT 0 /* EQ_B1_A - [15:0] */ +#define WM9081_EQ_B1_A_WIDTH 16 /* EQ_B1_A - [15:0] */ + +/* + * R45 (0x2D) - EQ 4 + */ +#define WM9081_EQ_B1_B_MASK 0xFFFF /* EQ_B1_B - [15:0] */ +#define WM9081_EQ_B1_B_SHIFT 0 /* EQ_B1_B - [15:0] */ +#define WM9081_EQ_B1_B_WIDTH 16 /* EQ_B1_B - [15:0] */ + +/* + * R46 (0x2E) - EQ 5 + */ +#define WM9081_EQ_B1_PG_MASK 0xFFFF /* EQ_B1_PG - [15:0] */ +#define WM9081_EQ_B1_PG_SHIFT 0 /* EQ_B1_PG - [15:0] */ +#define WM9081_EQ_B1_PG_WIDTH 16 /* EQ_B1_PG - [15:0] */ + +/* + * R47 (0x2F) - EQ 6 + */ +#define WM9081_EQ_B2_A_MASK 0xFFFF /* EQ_B2_A - [15:0] */ +#define WM9081_EQ_B2_A_SHIFT 0 /* EQ_B2_A - [15:0] */ +#define WM9081_EQ_B2_A_WIDTH 16 /* EQ_B2_A - [15:0] */ + +/* + * R48 (0x30) - EQ 7 + */ +#define WM9081_EQ_B2_B_MASK 0xFFFF /* EQ_B2_B - [15:0] */ +#define WM9081_EQ_B2_B_SHIFT 0 /* EQ_B2_B - [15:0] */ +#define WM9081_EQ_B2_B_WIDTH 16 /* EQ_B2_B - [15:0] */ + +/* + * R49 (0x31) - EQ 8 + */ +#define WM9081_EQ_B2_C_MASK 0xFFFF /* EQ_B2_C - [15:0] */ +#define WM9081_EQ_B2_C_SHIFT 0 /* EQ_B2_C - [15:0] */ +#define WM9081_EQ_B2_C_WIDTH 16 /* EQ_B2_C - [15:0] */ + +/* + * R50 (0x32) - EQ 9 + */ +#define WM9081_EQ_B2_PG_MASK 0xFFFF /* EQ_B2_PG - [15:0] */ +#define WM9081_EQ_B2_PG_SHIFT 0 /* EQ_B2_PG - [15:0] */ +#define WM9081_EQ_B2_PG_WIDTH 16 /* EQ_B2_PG - [15:0] */ + +/* + * R51 (0x33) - EQ 10 + */ +#define WM9081_EQ_B4_A_MASK 0xFFFF /* EQ_B4_A - [15:0] */ +#define WM9081_EQ_B4_A_SHIFT 0 /* EQ_B4_A - [15:0] */ +#define WM9081_EQ_B4_A_WIDTH 16 /* EQ_B4_A - [15:0] */ + +/* + * R52 (0x34) - EQ 11 + */ +#define WM9081_EQ_B4_B_MASK 0xFFFF /* EQ_B4_B - [15:0] */ +#define WM9081_EQ_B4_B_SHIFT 0 /* EQ_B4_B - [15:0] */ +#define WM9081_EQ_B4_B_WIDTH 16 /* EQ_B4_B - [15:0] */ + +/* + * R53 (0x35) - EQ 12 + */ +#define WM9081_EQ_B4_C_MASK 0xFFFF /* EQ_B4_C - [15:0] */ +#define WM9081_EQ_B4_C_SHIFT 0 /* EQ_B4_C - [15:0] */ +#define WM9081_EQ_B4_C_WIDTH 16 /* EQ_B4_C - [15:0] */ + +/* + * R54 (0x36) - EQ 13 + */ +#define WM9081_EQ_B4_PG_MASK 0xFFFF /* EQ_B4_PG - [15:0] */ +#define WM9081_EQ_B4_PG_SHIFT 0 /* EQ_B4_PG - [15:0] */ +#define WM9081_EQ_B4_PG_WIDTH 16 /* EQ_B4_PG - [15:0] */ + +/* + * R55 (0x37) - EQ 14 + */ +#define WM9081_EQ_B3_A_MASK 0xFFFF /* EQ_B3_A - [15:0] */ +#define WM9081_EQ_B3_A_SHIFT 0 /* EQ_B3_A - [15:0] */ +#define WM9081_EQ_B3_A_WIDTH 16 /* EQ_B3_A - [15:0] */ + +/* + * R56 (0x38) - EQ 15 + */ +#define WM9081_EQ_B3_B_MASK 0xFFFF /* EQ_B3_B - [15:0] */ +#define WM9081_EQ_B3_B_SHIFT 0 /* EQ_B3_B - [15:0] */ +#define WM9081_EQ_B3_B_WIDTH 16 /* EQ_B3_B - [15:0] */ + +/* + * R57 (0x39) - EQ 16 + */ +#define WM9081_EQ_B3_C_MASK 0xFFFF /* EQ_B3_C - [15:0] */ +#define WM9081_EQ_B3_C_SHIFT 0 /* EQ_B3_C - [15:0] */ +#define WM9081_EQ_B3_C_WIDTH 16 /* EQ_B3_C - [15:0] */ + +/* + * R58 (0x3A) - EQ 17 + */ +#define WM9081_EQ_B3_PG_MASK 0xFFFF /* EQ_B3_PG - [15:0] */ +#define WM9081_EQ_B3_PG_SHIFT 0 /* EQ_B3_PG - [15:0] */ +#define WM9081_EQ_B3_PG_WIDTH 16 /* EQ_B3_PG - [15:0] */ + +/* + * R59 (0x3B) - EQ 18 + */ +#define WM9081_EQ_B5_A_MASK 0xFFFF /* EQ_B5_A - [15:0] */ +#define WM9081_EQ_B5_A_SHIFT 0 /* EQ_B5_A - [15:0] */ +#define WM9081_EQ_B5_A_WIDTH 16 /* EQ_B5_A - [15:0] */ + +/* + * R60 (0x3C) - EQ 19 + */ +#define WM9081_EQ_B5_B_MASK 0xFFFF /* EQ_B5_B - [15:0] */ +#define WM9081_EQ_B5_B_SHIFT 0 /* EQ_B5_B - [15:0] */ +#define WM9081_EQ_B5_B_WIDTH 16 /* EQ_B5_B - [15:0] */ + +/* + * R61 (0x3D) - EQ 20 + */ +#define WM9081_EQ_B5_PG_MASK 0xFFFF /* EQ_B5_PG - [15:0] */ +#define WM9081_EQ_B5_PG_SHIFT 0 /* EQ_B5_PG - [15:0] */ +#define WM9081_EQ_B5_PG_WIDTH 16 /* EQ_B5_PG - [15:0] */ + + +#endif -- cgit v1.2.3 From 0154724d487586241c1ad57cfd348ed2ff2274e2 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 23 May 2009 00:01:05 +0100 Subject: ASoC: Fix WM9081 PowerPC compiler issues Ensure that we always set a new sysclk when using the FLL in master mode and pick out the correct value for the sample rate in hw_params(). Signed-off-by: Mark Brown --- sound/soc/codecs/wm9081.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c index 83e3148c258..86fc57e25f9 100644 --- a/sound/soc/codecs/wm9081.c +++ b/sound/soc/codecs/wm9081.c @@ -702,9 +702,10 @@ static int configure_clock(struct snd_soc_codec *codec) * performance. */ for (i = 0; i < ARRAY_SIZE(clk_sys_rates); i++) { target = wm9081->fs * clk_sys_rates[i].ratio; + new_sysclk = target; if (target >= wm9081->bclk && target > 3000000) - new_sysclk = target; + break; } } else if (wm9081->fs) { for (i = 0; i < ARRAY_SIZE(clk_sys_rates); i++) { @@ -1102,7 +1103,8 @@ static int wm9081_hw_params(struct snd_pcm_substream *substream, } dev_dbg(codec->dev, "Selected SAMPLE_RATE of %dHz\n", sample_rates[best].rate); - clk_ctrl2 |= (sample_rates[i].sample_rate << WM9081_SAMPLE_RATE_SHIFT); + clk_ctrl2 |= (sample_rates[best].sample_rate + << WM9081_SAMPLE_RATE_SHIFT); /* BCLK_DIV */ best = 0; -- cgit v1.2.3 From 606689e9f8c52b537c85fd4bc1ba5ce7b3ee2678 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 28 Feb 2009 17:33:52 +0000 Subject: ASoC: Add Openmoko Neo FreeRunner (GTA02) audio driver This driver supports the audio subsystem on the Openmoko Neo FreeRunner smartphone, often known by its codename GTA02. The system has a WM8753 connected to a Samsung S3C2442 with an external GPIO controlled speaker amplifier. The driver was originally written by Graeme Gregory and has recieved contributions from Openmoko, myself and members of the Openmoko community. For much of this time the primary Openmoko kernel maintainer was Andy Green. Signed-off-by: Graeme Gregory Signed-off-by: Andy Green Signed-off-by: Mark Brown --- sound/soc/s3c24xx/Kconfig | 9 + sound/soc/s3c24xx/Makefile | 2 + sound/soc/s3c24xx/neo1973_gta02_wm8753.c | 481 +++++++++++++++++++++++++++++++ 3 files changed, 492 insertions(+) create mode 100644 sound/soc/s3c24xx/neo1973_gta02_wm8753.c (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index df494d1e346..d1ed0f537c3 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig @@ -38,6 +38,15 @@ config SND_S3C24XX_SOC_NEO1973_WM8753 Say Y if you want to add support for SoC audio on smdk2440 with the WM8753. +config SND_S3C24XX_SOC_NEO1973_GTA02_WM8753 + tristate "Audio support for the Openmoko Neo FreeRunner (GTA02)" + depends on SND_S3C24XX_SOC && MACH_NEO1973_GTA02 + select SND_S3C24XX_SOC_I2S + select SND_SOC_WM8753 + help + This driver provides audio support for the Openmoko Neo FreeRunner + smartphone. + config SND_S3C24XX_SOC_JIVE_WM8750 tristate "SoC I2S Audio support for Jive" depends on SND_S3C24XX_SOC && MACH_JIVE diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile index 07a93a2ebe5..eb219b01649 100644 --- a/sound/soc/s3c24xx/Makefile +++ b/sound/soc/s3c24xx/Makefile @@ -16,12 +16,14 @@ obj-$(CONFIG_SND_S3C_I2SV2_SOC) += snd-soc-s3c-i2s-v2.o # S3C24XX Machine Support snd-soc-jive-wm8750-objs := jive_wm8750.o snd-soc-neo1973-wm8753-objs := neo1973_wm8753.o +snd-soc-neo1973-gta02-wm8753-objs := neo1973_gta02_wm8753.o snd-soc-smdk2443-wm9710-objs := smdk2443_wm9710.o snd-soc-ln2440sbc-alc650-objs := ln2440sbc_alc650.o snd-soc-s3c24xx-uda134x-objs := s3c24xx_uda134x.o obj-$(CONFIG_SND_S3C24XX_SOC_JIVE_WM8750) += snd-soc-jive-wm8750.o obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o +obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_GTA02_WM8753) += snd-soc-neo1973-gta02-wm8753.o obj-$(CONFIG_SND_S3C24XX_SOC_SMDK2443_WM9710) += snd-soc-smdk2443-wm9710.o obj-$(CONFIG_SND_S3C24XX_SOC_LN2440SBC_ALC650) += snd-soc-ln2440sbc-alc650.o obj-$(CONFIG_SND_S3C24XX_SOC_S3C24XX_UDA134X) += snd-soc-s3c24xx-uda134x.o diff --git a/sound/soc/s3c24xx/neo1973_gta02_wm8753.c b/sound/soc/s3c24xx/neo1973_gta02_wm8753.c new file mode 100644 index 00000000000..1358f6fe820 --- /dev/null +++ b/sound/soc/s3c24xx/neo1973_gta02_wm8753.c @@ -0,0 +1,481 @@ +/* + * neo1973_gta02_wm8753.c -- SoC audio for Neo1973 + * + * Copyright 2007 Openmoko Inc + * Author: Graeme Gregory + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * Copyright 2009 Wolfson Microelectronics + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include "../codecs/wm8753.h" +#include "s3c24xx-pcm.h" +#include "s3c24xx-i2s.h" + +static struct snd_soc_card neo1973_gta02; + +static int neo1973_gta02_hifi_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; + unsigned int pll_out = 0, bclk = 0; + int ret = 0; + unsigned long iis_clkrate; + + iis_clkrate = s3c24xx_i2s_get_clockrate(); + + switch (params_rate(params)) { + case 8000: + case 16000: + pll_out = 12288000; + break; + case 48000: + bclk = WM8753_BCLK_DIV_4; + pll_out = 12288000; + break; + case 96000: + bclk = WM8753_BCLK_DIV_2; + pll_out = 12288000; + break; + case 11025: + bclk = WM8753_BCLK_DIV_16; + pll_out = 11289600; + break; + case 22050: + bclk = WM8753_BCLK_DIV_8; + pll_out = 11289600; + break; + case 44100: + bclk = WM8753_BCLK_DIV_4; + pll_out = 11289600; + break; + case 88200: + bclk = WM8753_BCLK_DIV_2; + pll_out = 11289600; + break; + } + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + 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_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_MCLK, pll_out, + SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set MCLK division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_MCLK, + S3C2410_IISMOD_32FS); + if (ret < 0) + return ret; + + /* set codec BCLK division for sample rate */ + ret = snd_soc_dai_set_clkdiv(codec_dai, + WM8753_BCLKDIV, bclk); + if (ret < 0) + return ret; + + /* set prescaler division for sample rate */ + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C24XX_DIV_PRESCALER, + S3C24XX_PRESCALE(4, 4)); + if (ret < 0) + return ret; + + /* codec PLL input is PCLK/4 */ + ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, + iis_clkrate / 4, pll_out); + if (ret < 0) + return ret; + + return 0; +} + +static int neo1973_gta02_hifi_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + + /* disable the PLL */ + return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0); +} + +/* + * Neo1973 WM8753 HiFi DAI opserations. + */ +static struct snd_soc_ops neo1973_gta02_hifi_ops = { + .hw_params = neo1973_gta02_hifi_hw_params, + .hw_free = neo1973_gta02_hifi_hw_free, +}; + +static int neo1973_gta02_voice_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; + unsigned int pcmdiv = 0; + int ret = 0; + unsigned long iis_clkrate; + + iis_clkrate = s3c24xx_i2s_get_clockrate(); + + if (params_rate(params) != 8000) + return -EINVAL; + if (params_channels(params) != 1) + return -EINVAL; + + pcmdiv = WM8753_PCM_DIV_6; /* 2.048 MHz */ + + /* todo: gg check mode (DSP_B) against CSR datasheet */ + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_B | + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + if (ret < 0) + return ret; + + /* set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, WM8753_PCMCLK, + 12288000, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* set codec PCM division for sample rate */ + ret = snd_soc_dai_set_clkdiv(codec_dai, WM8753_PCMDIV, + pcmdiv); + if (ret < 0) + return ret; + + /* configue and enable PLL for 12.288MHz output */ + ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, + iis_clkrate / 4, 12288000); + if (ret < 0) + return ret; + + return 0; +} + +static int neo1973_gta02_voice_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + + /* disable the PLL */ + return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0); +} + +static struct snd_soc_ops neo1973_gta02_voice_ops = { + .hw_params = neo1973_gta02_voice_hw_params, + .hw_free = neo1973_gta02_voice_hw_free, +}; + +#define LM4853_AMP 1 +#define LM4853_SPK 2 + +static u8 lm4853_state; + +/* This has no effect, it exists only to maintain compatibility with + * existing ALSA state files. + */ +static int lm4853_set_state(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int val = ucontrol->value.integer.value[0]; + + if (val) + lm4853_state |= LM4853_AMP; + else + lm4853_state &= ~LM4853_AMP; + + return 0; +} + +static int lm4853_get_state(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = lm4853_state & LM4853_AMP; + + return 0; +} + +static int lm4853_set_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + int val = ucontrol->value.integer.value[0]; + + if (val) { + lm4853_state |= LM4853_SPK; + s3c2410_gpio_setpin(GTA02_GPIO_HP_IN, 0); + } else { + lm4853_state &= ~LM4853_SPK; + s3c2410_gpio_setpin(GTA02_GPIO_HP_IN, 1); + } + + return 0; +} + +static int lm4853_get_spk(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = (lm4853_state & LM4853_SPK) >> 1; + + return 0; +} + +static int lm4853_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, + int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) + s3c2410_gpio_setpin(GTA02_GPIO_AMP_SHUT, 0); + + if (SND_SOC_DAPM_EVENT_OFF(event)) + s3c2410_gpio_setpin(GTA02_GPIO_AMP_SHUT, 1); + + return 0; +} + +static const struct snd_soc_dapm_widget wm8753_dapm_widgets[] = { + SND_SOC_DAPM_SPK("Stereo Out", lm4853_event), + SND_SOC_DAPM_LINE("GSM Line Out", NULL), + SND_SOC_DAPM_LINE("GSM Line In", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Handset Mic", NULL), + SND_SOC_DAPM_SPK("Handset Spk", NULL), +}; + + +/* example machine audio_mapnections */ +static const struct snd_soc_dapm_route audio_map[] = { + + /* Connections to the lm4853 amp */ + {"Stereo Out", NULL, "LOUT1"}, + {"Stereo Out", NULL, "ROUT1"}, + + /* Connections to the GSM Module */ + {"GSM Line Out", NULL, "MONO1"}, + {"GSM Line Out", NULL, "MONO2"}, + {"RXP", NULL, "GSM Line In"}, + {"RXN", NULL, "GSM Line In"}, + + /* Connections to Headset */ + {"MIC1", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Headset Mic"}, + + /* Call Mic */ + {"MIC2", NULL, "Mic Bias"}, + {"MIC2N", NULL, "Mic Bias"}, + {"Mic Bias", NULL, "Handset Mic"}, + + /* Call Speaker */ + {"Handset Spk", NULL, "LOUT2"}, + {"Handset Spk", NULL, "ROUT2"}, + + /* Connect the ALC pins */ + {"ACIN", NULL, "ACOP"}, +}; + +static const struct snd_kcontrol_new wm8753_neo1973_gta02_controls[] = { + SOC_DAPM_PIN_SWITCH("Stereo Out"), + SOC_DAPM_PIN_SWITCH("GSM Line Out"), + SOC_DAPM_PIN_SWITCH("GSM Line In"), + SOC_DAPM_PIN_SWITCH("Headset Mic"), + SOC_DAPM_PIN_SWITCH("Handset Mic"), + SOC_DAPM_PIN_SWITCH("Handset Spk"), + + /* This has no effect, it exists only to maintain compatibility with + * existing ALSA state files. + */ + SOC_SINGLE_EXT("Amp State Switch", 6, 0, 1, 0, + lm4853_get_state, + lm4853_set_state), + SOC_SINGLE_EXT("Amp Spk Switch", 7, 0, 1, 0, + lm4853_get_spk, + lm4853_set_spk), +}; + +/* + * This is an example machine initialisation for a wm8753 connected to a + * neo1973 GTA02. + */ +static int neo1973_gta02_wm8753_init(struct snd_soc_codec *codec) +{ + int i, err; + + /* set up NC codec pins */ + snd_soc_dapm_nc_pin(codec, "OUT3"); + snd_soc_dapm_nc_pin(codec, "OUT4"); + snd_soc_dapm_nc_pin(codec, "LINE1"); + snd_soc_dapm_nc_pin(codec, "LINE2"); + + /* Add neo1973 gta02 specific widgets */ + snd_soc_dapm_new_controls(codec, wm8753_dapm_widgets, + ARRAY_SIZE(wm8753_dapm_widgets)); + + /* add neo1973 gta02 specific controls */ + for (i = 0; i < ARRAY_SIZE(wm8753_neo1973_gta02_controls); i++) { + err = snd_ctl_add(codec->card, + snd_soc_cnew(&wm8753_neo1973_gta02_controls[i], + codec, NULL)); + if (err < 0) + return err; + } + + /* set up neo1973 gta02 specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + /* set endpoints to default off mode */ + snd_soc_dapm_disable_pin(codec, "Stereo Out"); + snd_soc_dapm_disable_pin(codec, "GSM Line Out"); + snd_soc_dapm_disable_pin(codec, "GSM Line In"); + snd_soc_dapm_disable_pin(codec, "Headset Mic"); + snd_soc_dapm_disable_pin(codec, "Handset Mic"); + snd_soc_dapm_disable_pin(codec, "Handset Spk"); + + snd_soc_dapm_sync(codec); + + return 0; +} + +/* + * BT Codec DAI + */ +static struct snd_soc_dai bt_dai = { + .name = "Bluetooth", + .id = 0, + .playback = { + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, +}; + +static struct snd_soc_dai_link neo1973_gta02_dai[] = { +{ /* Hifi Playback - for similatious use with voice below */ + .name = "WM8753", + .stream_name = "WM8753 HiFi", + .cpu_dai = &s3c24xx_i2s_dai, + .codec_dai = &wm8753_dai[WM8753_DAI_HIFI], + .init = neo1973_gta02_wm8753_init, + .ops = &neo1973_gta02_hifi_ops, +}, +{ /* Voice via BT */ + .name = "Bluetooth", + .stream_name = "Voice", + .cpu_dai = &bt_dai, + .codec_dai = &wm8753_dai[WM8753_DAI_VOICE], + .ops = &neo1973_gta02_voice_ops, +}, +}; + +static struct snd_soc_card neo1973_gta02 = { + .name = "neo1973-gta02", + .platform = &s3c24xx_soc_platform, + .dai_link = neo1973_gta02_dai, + .num_links = ARRAY_SIZE(neo1973_gta02_dai), +}; + +static struct snd_soc_device neo1973_gta02_snd_devdata = { + .card = &neo1973_gta02, + .codec_dev = &soc_codec_dev_wm8753, +}; + +static struct platform_device *neo1973_gta02_snd_device; + +static int __init neo1973_gta02_init(void) +{ + int ret; + + if (!machine_is_neo1973_gta02()) { + printk(KERN_INFO + "Only GTA02 is supported by this ASoC driver\n"); + return -ENODEV; + } + + /* register bluetooth DAI here */ + ret = snd_soc_register_dai(&bt_dai); + if (ret) + return ret; + + neo1973_gta02_snd_device = platform_device_alloc("soc-audio", -1); + if (!neo1973_gta02_snd_device) + return -ENOMEM; + + platform_set_drvdata(neo1973_gta02_snd_device, + &neo1973_gta02_snd_devdata); + neo1973_gta02_snd_devdata.dev = &neo1973_gta02_snd_device->dev; + ret = platform_device_add(neo1973_gta02_snd_device); + + if (ret) { + platform_device_put(neo1973_gta02_snd_device); + return ret; + } + + /* Initialise GPIOs used by amp */ + s3c2410_gpio_cfgpin(GTA02_GPIO_HP_IN, S3C2410_GPIO_OUTPUT); + s3c2410_gpio_cfgpin(GTA02_GPIO_AMP_SHUT, S3C2410_GPIO_OUTPUT); + + /* Amp off by default */ + s3c2410_gpio_setpin(GTA02_GPIO_AMP_SHUT, 1); + + /* Speaker off by default */ + s3c2410_gpio_setpin(GTA02_GPIO_HP_IN, 1); + + return ret; +} +module_init(neo1973_gta02_init); + +static void __exit neo1973_gta02_exit(void) +{ + snd_soc_unregister_dai(&bt_dai); + platform_device_unregister(neo1973_gta02_snd_device); +} +module_exit(neo1973_gta02_exit); + +/* Module information */ +MODULE_AUTHOR("Graeme Gregory, graeme@openmoko.org"); +MODULE_DESCRIPTION("ALSA SoC WM8753 Neo1973 GTA02"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 3c166c7f1828f226c7f478758bf6c8ce8be1623f Mon Sep 17 00:00:00 2001 From: Jon Smirl Date: Sat, 23 May 2009 19:13:07 -0400 Subject: ASoC: Codec for STAC9766 used on the Efika Datasheet: http://www.idt.com/products/getDoc.cfm?docID=13134007 Signed-off-by: Jon Smirl Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/stac9766.c | 470 ++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/stac9766.h | 21 ++ 4 files changed, 497 insertions(+) create mode 100644 sound/soc/codecs/stac9766.c create mode 100644 sound/soc/codecs/stac9766.h (limited to 'sound/soc') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 7f78b65fc4e..cb07d9b51b6 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -19,6 +19,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_CS4270 if I2C select SND_SOC_PCM3008 select SND_SOC_SSM2602 if I2C + select SND_SOC_STAC9766 if SND_SOC_AC97_BUS select SND_SOC_TLV320AIC23 if I2C select SND_SOC_TLV320AIC26 if SPI_MASTER select SND_SOC_TLV320AIC3X if I2C @@ -93,6 +94,9 @@ config SND_SOC_PCM3008 config SND_SOC_SSM2602 tristate +config SND_SOC_STAC9766 + tristate + config SND_SOC_TLV320AIC23 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 70c55fa2c43..46c007cb562 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -7,6 +7,7 @@ snd-soc-cs4270-objs := cs4270.o snd-soc-l3-objs := l3.o snd-soc-pcm3008-objs := pcm3008.o snd-soc-ssm2602-objs := ssm2602.o +snd-soc-stac9766-objs := stac9766.o snd-soc-tlv320aic23-objs := tlv320aic23.o snd-soc-tlv320aic26-objs := tlv320aic26.o snd-soc-tlv320aic3x-objs := tlv320aic3x.o @@ -42,6 +43,7 @@ obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o +obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o obj-$(CONFIG_SND_SOC_TLV320AIC3X) += snd-soc-tlv320aic3x.o diff --git a/sound/soc/codecs/stac9766.c b/sound/soc/codecs/stac9766.c new file mode 100644 index 00000000000..7740cd5a760 --- /dev/null +++ b/sound/soc/codecs/stac9766.c @@ -0,0 +1,470 @@ +/* + * stac9766.c -- ALSA SoC STAC9766 codec support + * + * Copyright 2009 Jon Smirl, Digispeaker + * Author: Jon Smirl + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Features:- + * + * o Support for AC97 Codec, S/PDIF + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "stac9766.h" + +#define STAC9766_VERSION "0.10" + +/* + * STAC9766 register cache + */ +static const u16 stac9766_reg[] = { + 0x6A90, 0x8000, 0x8000, 0x8000, /* 6 */ + 0x0000, 0x0000, 0x8008, 0x8008, /* e */ + 0x8808, 0x8808, 0x8808, 0x8808, /* 16 */ + 0x8808, 0x0000, 0x8000, 0x0000, /* 1e */ + 0x0000, 0x0000, 0x0000, 0x000f, /* 26 */ + 0x0a05, 0x0400, 0xbb80, 0x0000, /* 2e */ + 0x0000, 0xbb80, 0x0000, 0x0000, /* 36 */ + 0x0000, 0x2000, 0x0000, 0x0100, /* 3e */ + 0x0000, 0x0000, 0x0080, 0x0000, /* 46 */ + 0x0000, 0x0000, 0x0003, 0xffff, /* 4e */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 56 */ + 0x4000, 0x0000, 0x0000, 0x0000, /* 5e */ + 0x1201, 0xFFFF, 0xFFFF, 0x0000, /* 66 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 6e */ + 0x0000, 0x0000, 0x0000, 0x0006, /* 76 */ + 0x0000, 0x0000, 0x0000, 0x0000, /* 7e */ +}; + +static const char *stac9766_record_mux[] = {"Mic", "CD", "Video", "AUX", "Line", "Stereo Mix", "Mono Mix", "Phone"}; +static const char *stac9766_mono_mux[] = {"Mix", "Mic"}; +static const char *stac9766_mic_mux[] = {"Mic1", "Mic2"}; +static const char *stac9766_SPDIF_mux[] = {"PCM", "ADC Record"}; +static const char *stac9766_popbypass_mux[] = {"Normal", "Bypass Mixer"}; +static const char *stac9766_record_all_mux[] = {"All analog", "Analog plus DAC"}; +static const char *stac9766_boost1[] = {"0dB", "10dB"}; +static const char *stac9766_boost2[] = {"0dB", "20dB"}; +static const char *stac9766_stereo_mic[] = {"Off", "On"}; + +static const struct soc_enum stac9766_record_enum = + SOC_ENUM_DOUBLE(AC97_REC_SEL, 8, 0, 8, stac9766_record_mux); +static const struct soc_enum stac9766_mono_enum = + SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 9, 2, stac9766_mono_mux); +static const struct soc_enum stac9766_mic_enum = + SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 8, 2, stac9766_mic_mux); +static const struct soc_enum stac9766_SPDIF_enum = + SOC_ENUM_SINGLE(AC97_STAC_DA_CONTROL, 1, 2, stac9766_SPDIF_mux); +static const struct soc_enum stac9766_popbypass_enum = + SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, stac9766_popbypass_mux); +static const struct soc_enum stac9766_record_all_enum = + SOC_ENUM_SINGLE(AC97_STAC_ANALOG_SPECIAL, 12, 2, stac9766_record_all_mux); +static const struct soc_enum stac9766_boost1_enum = + SOC_ENUM_SINGLE(AC97_MIC, 6, 2, stac9766_boost1); /* 0/10dB */ +static const struct soc_enum stac9766_boost2_enum = + SOC_ENUM_SINGLE(AC97_STAC_ANALOG_SPECIAL, 2, 2, stac9766_boost2); /* 0/20dB */ +static const struct soc_enum stac9766_stereo_mic_enum = + SOC_ENUM_SINGLE(AC97_STAC_STEREO_MIC, 2, 1, stac9766_stereo_mic); + +static const DECLARE_TLV_DB_LINEAR(master_tlv, -4600, 0); +static const DECLARE_TLV_DB_LINEAR(record_tlv, 0, 2250); +static const DECLARE_TLV_DB_LINEAR(beep_tlv, -4500, 0); +static const DECLARE_TLV_DB_LINEAR(mix_tlv, -3450, 1200); + +static const struct snd_kcontrol_new stac9766_snd_ac97_controls[] = { + SOC_DOUBLE_TLV("Speaker Volume", AC97_MASTER, 8, 0, 31, 1, master_tlv), + SOC_SINGLE("Speaker Switch", AC97_MASTER, 15, 1, 1), + SOC_DOUBLE_TLV("Headphone Volume", AC97_HEADPHONE, 8, 0, 31, 1, master_tlv), + SOC_SINGLE("Headphone Switch", AC97_HEADPHONE, 15, 1, 1), + SOC_SINGLE_TLV("Mono Out Volume", AC97_MASTER_MONO, 0, 31, 1, master_tlv), + SOC_SINGLE("Mono Out Switch", AC97_MASTER_MONO, 15, 1, 1), + + SOC_DOUBLE_TLV("Record Volume", AC97_REC_GAIN, 8, 0, 15, 0, record_tlv), + SOC_SINGLE("Record Switch", AC97_REC_GAIN, 15, 1, 1), + + + SOC_SINGLE_TLV("Beep Volume", AC97_PC_BEEP, 1, 15, 1, beep_tlv), + SOC_SINGLE("Beep Switch", AC97_PC_BEEP, 15, 1, 1), + SOC_SINGLE("Beep Frequency", AC97_PC_BEEP, 5, 127, 1), + SOC_SINGLE_TLV("Phone Volume", AC97_PHONE, 0, 31, 1, mix_tlv), + SOC_SINGLE("Phone Switch", AC97_PHONE, 15, 1, 1), + + SOC_ENUM("Mic Boost1", stac9766_boost1_enum), + SOC_ENUM("Mic Boost2", stac9766_boost2_enum), + SOC_SINGLE_TLV("Mic Volume", AC97_MIC, 0, 31, 1, mix_tlv), + SOC_SINGLE("Mic Switch", AC97_MIC, 15, 1, 1), + SOC_ENUM("Stereo Mic", stac9766_stereo_mic_enum), + + SOC_DOUBLE_TLV("Line Volume", AC97_LINE, 8, 0, 31, 1, mix_tlv), + SOC_SINGLE("Line Switch", AC97_LINE, 15, 1, 1), + SOC_DOUBLE_TLV("CD Volume", AC97_CD, 8, 0, 31, 1, mix_tlv), + SOC_SINGLE("CD Switch", AC97_CD, 15, 1, 1), + SOC_DOUBLE_TLV("AUX Volume", AC97_AUX, 8, 0, 31, 1, mix_tlv), + SOC_SINGLE("AUX Switch", AC97_AUX, 15, 1, 1), + SOC_DOUBLE_TLV("Video Volume", AC97_VIDEO, 8, 0, 31, 1, mix_tlv), + SOC_SINGLE("Video Switch", AC97_VIDEO, 15, 1, 1), + + SOC_DOUBLE_TLV("DAC Volume", AC97_PCM, 8, 0, 31, 1, mix_tlv), + SOC_SINGLE("DAC Switch", AC97_PCM, 15, 1, 1), + SOC_SINGLE("Loopback Test Switch", AC97_GENERAL_PURPOSE, 7, 1, 0), + SOC_SINGLE("3D Volume", AC97_3D_CONTROL, 3, 2, 1), + SOC_SINGLE("3D Switch", AC97_GENERAL_PURPOSE, 13, 1, 0), + + SOC_ENUM("SPDIF Mux", stac9766_SPDIF_enum), + SOC_ENUM("Mic1/2 Mux", stac9766_mic_enum), + SOC_ENUM("Record All Mux", stac9766_record_all_enum), + SOC_ENUM("Record Mux", stac9766_record_enum), + SOC_ENUM("Mono Mux", stac9766_mono_enum), + SOC_ENUM("Pop Bypass Mux", stac9766_popbypass_enum), +}; + +int stac9766_ac97_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) +{ + u16 *cache = codec->reg_cache; + + if (reg > AC97_STAC_PAGE0) { + stac9766_ac97_write(codec, AC97_INT_PAGING, 0); + soc_ac97_ops.write(codec->ac97, reg, val); + stac9766_ac97_write(codec, AC97_INT_PAGING, 1); + return 0; + } + if (reg / 2 > ARRAY_SIZE(stac9766_reg)) + return -EIO; + + soc_ac97_ops.write(codec->ac97, reg, val); + cache[reg / 2] = val; + return 0; +} + +unsigned int stac9766_ac97_read(struct snd_soc_codec *codec, unsigned int reg) +{ + u16 val = 0, *cache = codec->reg_cache; + + if (reg > AC97_STAC_PAGE0) { + stac9766_ac97_write(codec, AC97_INT_PAGING, 0); + val = soc_ac97_ops.read(codec->ac97, reg - AC97_STAC_PAGE0); + stac9766_ac97_write(codec, AC97_INT_PAGING, 1); + return val; + } + if (reg / 2 > ARRAY_SIZE(stac9766_reg)) + return -EIO; + + if (reg == AC97_RESET || reg == AC97_GPIO_STATUS || + reg == AC97_INT_PAGING || reg == AC97_VENDOR_ID1 || + reg == AC97_VENDOR_ID2) { + + val = soc_ac97_ops.read(codec->ac97, reg); + return val; + } + return cache[reg / 2]; +} + +static int ac97_analog_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned short reg, vra; + + vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS); + + vra |= 0x1; /* enable variable rate audio */ + + stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + reg = AC97_PCM_FRONT_DAC_RATE; + else + reg = AC97_PCM_LR_ADC_RATE; + + return stac9766_ac97_write(codec, reg, runtime->rate); +} + +static int ac97_digital_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned short reg, vra; + + stac9766_ac97_write(codec, AC97_SPDIF, 0x2002); + + vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS); + vra |= 0x5; /* Enable VRA and SPDIF out */ + + stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra); + + reg = AC97_PCM_FRONT_DAC_RATE; + + return stac9766_ac97_write(codec, reg, runtime->rate); +} + +static int ac97_digital_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned short vra; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_STOP: + vra = stac9766_ac97_read(codec, AC97_EXTENDED_STATUS); + vra &= !0x04; + stac9766_ac97_write(codec, AC97_EXTENDED_STATUS, vra); + break; + } + return 0; +} + +static int stac9766_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: /* full On */ + case SND_SOC_BIAS_PREPARE: /* partial On */ + case SND_SOC_BIAS_STANDBY: /* Off, with power */ + stac9766_ac97_write(codec, AC97_POWERDOWN, 0x0000); + break; + case SND_SOC_BIAS_OFF: /* Off, without power */ + /* disable everything including AC link */ + stac9766_ac97_write(codec, AC97_POWERDOWN, 0xffff); + break; + } + codec->bias_level = level; + return 0; +} + +int stac9766_reset(struct snd_soc_codec *codec, int try_warm) +{ + if (try_warm && soc_ac97_ops.warm_reset) { + soc_ac97_ops.warm_reset(codec->ac97); + if (stac9766_ac97_read(codec, 0) == stac9766_reg[0]) + return 1; + } + + soc_ac97_ops.reset(codec->ac97); + if (soc_ac97_ops.warm_reset) + soc_ac97_ops.warm_reset(codec->ac97); + if (stac9766_ac97_read(codec, 0) != stac9766_reg[0]) + return -EIO; + return 0; +} + +static int stac9766_codec_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + stac9766_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int stac9766_codec_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + u16 id, reset; + + reset = 0; + /* give the codec an AC97 warm reset to start the link */ +reset: + if (reset > 5) { + printk(KERN_ERR "stac9766 failed to resume"); + return -EIO; + } + codec->ac97->bus->ops->warm_reset(codec->ac97); + id = soc_ac97_ops.read(codec->ac97, AC97_VENDOR_ID2); + if (id != 0x4c13) { + stac9766_reset(codec, 0); + reset++; + goto reset; + } + stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) + stac9766_set_bias_level(codec, SND_SOC_BIAS_ON); + + return 0; +} + +static struct snd_soc_dai_ops stac9766_dai_ops_analog = +{ + .prepare = ac97_analog_prepare, +}; + +static struct snd_soc_dai_ops stac9766_dai_ops_digital = +{ + .prepare = ac97_digital_prepare, + .trigger = ac97_digital_trigger, +}; + +struct snd_soc_dai stac9766_dai[] = { +{ + .name = "stac9766 analog", + .id = 0, + .ac97_control = 1, + + /* stream cababilities */ + .playback = { + .stream_name = "stac9766 analog", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SND_SOC_STD_AC97_FMTS, + }, + .capture = { + .stream_name = "stac9766 analog", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SND_SOC_STD_AC97_FMTS, + }, + /* alsa ops */ + .ops = &stac9766_dai_ops_analog, +}, +{ + .name = "stac9766 IEC958", + .id = 1, + .ac97_control = 1, + + /* stream cababilities */ + .playback = { + .stream_name = "stac9766 IEC958", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE, + }, + /* alsa ops */ + .ops = &stac9766_dai_ops_digital, +}}; +EXPORT_SYMBOL_GPL(stac9766_dai); + +static int stac9766_codec_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + printk(KERN_INFO "STAC9766 SoC Audio Codec %s\n", STAC9766_VERSION); + + socdev->card->codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); + if (socdev->card->codec == NULL) + return -ENOMEM; + codec = socdev->card->codec; + mutex_init(&codec->mutex); + + codec->reg_cache = kmemdup(stac9766_reg, sizeof(stac9766_reg), GFP_KERNEL); + if (codec->reg_cache == NULL) { + ret = -ENOMEM; + goto cache_err; + } + codec->reg_cache_size = sizeof(stac9766_reg); + codec->reg_cache_step = 2; + + codec->name = "STAC9766"; + codec->owner = THIS_MODULE; + codec->dai = stac9766_dai; + codec->num_dai = ARRAY_SIZE(stac9766_dai); + codec->write = stac9766_ac97_write; + codec->read = stac9766_ac97_read; + codec->set_bias_level = stac9766_set_bias_level; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ret = snd_soc_new_ac97_codec(codec, &soc_ac97_ops, 0); + if (ret < 0) + goto codec_err; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) + goto pcm_err; + + /* do a cold reset for the controller and then try + * a warm reset followed by an optional cold reset for codec */ + stac9766_reset(codec, 0); + ret = stac9766_reset(codec, 1); + if (ret < 0) { + printk(KERN_ERR "Failed to reset STAC9766: AC97 link error\n"); + goto reset_err; + } + + stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + snd_soc_add_controls(codec, stac9766_snd_ac97_controls, ARRAY_SIZE( + stac9766_snd_ac97_controls)); + + ret = snd_soc_init_card(socdev); + if (ret < 0) + goto reset_err; + return 0; + +reset_err: + snd_soc_free_pcms(socdev); +pcm_err: + snd_soc_free_ac97_codec(codec); +codec_err: + kfree(codec->private_data); +cache_err: + kfree(socdev->card->codec); + socdev->card->codec = NULL; + return ret; +} + +static int stac9766_codec_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + if (codec == NULL) + return 0; + + snd_soc_free_pcms(socdev); + snd_soc_free_ac97_codec(codec); + kfree(codec->reg_cache); + kfree(codec); + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_stac9766 = +{ + .probe = stac9766_codec_probe, + .remove = stac9766_codec_remove, + .suspend = stac9766_codec_suspend, + .resume = stac9766_codec_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_stac9766); + +static int __init stac9766_modinit(void) +{ + return snd_soc_register_dais(stac9766_dai, ARRAY_SIZE(stac9766_dai)); +} +module_init(stac9766_modinit); + +static void __exit stac9766_exit(void) +{ + snd_soc_unregister_dais(stac9766_dai, ARRAY_SIZE(stac9766_dai)); +} +module_exit(stac9766_exit); + +MODULE_DESCRIPTION("ASoC stac9766 driver"); +MODULE_AUTHOR("Jon Smirl "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/stac9766.h b/sound/soc/codecs/stac9766.h new file mode 100644 index 00000000000..65642eb8393 --- /dev/null +++ b/sound/soc/codecs/stac9766.h @@ -0,0 +1,21 @@ +/* + * stac9766.h -- STAC9766 Soc Audio driver + */ + +#ifndef _STAC9766_H +#define _STAC9766_H + +#define AC97_STAC_PAGE0 0x1000 +#define AC97_STAC_DA_CONTROL (AC97_STAC_PAGE0 | 0x6A) +#define AC97_STAC_ANALOG_SPECIAL (AC97_STAC_PAGE0 | 0x6E) +#define AC97_STAC_STEREO_MIC 0x78 + +/* STAC9766 DAI ID's */ +#define STAC9766_DAI_AC97_ANALOG 0 +#define STAC9766_DAI_AC97_DIGITAL 1 + +extern struct snd_soc_dai stac9766_dai[]; +extern struct snd_soc_codec_device soc_codec_dev_stac9766; + + +#endif -- cgit v1.2.3 From 05e1efa2deb42b1bd548208e5c43f471e2cf0da1 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 24 May 2009 13:32:24 +0100 Subject: ASoC: Fix minor issues in STAC9766 driver Fairly minor issues: - Don't register the DAIs, it's not required for AC97 devices. - Make unexported functions static. - Wrap some excessively long lines. - Undo tab/space breakage. Signed-off-by: Mark Brown --- sound/soc/codecs/stac9766.c | 65 ++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 36 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/stac9766.c b/sound/soc/codecs/stac9766.c index 7740cd5a760..8ad4b7b3e3b 100644 --- a/sound/soc/codecs/stac9766.c +++ b/sound/soc/codecs/stac9766.c @@ -52,12 +52,14 @@ static const u16 stac9766_reg[] = { 0x0000, 0x0000, 0x0000, 0x0000, /* 7e */ }; -static const char *stac9766_record_mux[] = {"Mic", "CD", "Video", "AUX", "Line", "Stereo Mix", "Mono Mix", "Phone"}; +static const char *stac9766_record_mux[] = {"Mic", "CD", "Video", "AUX", + "Line", "Stereo Mix", "Mono Mix", "Phone"}; static const char *stac9766_mono_mux[] = {"Mix", "Mic"}; static const char *stac9766_mic_mux[] = {"Mic1", "Mic2"}; static const char *stac9766_SPDIF_mux[] = {"PCM", "ADC Record"}; static const char *stac9766_popbypass_mux[] = {"Normal", "Bypass Mixer"}; -static const char *stac9766_record_all_mux[] = {"All analog", "Analog plus DAC"}; +static const char *stac9766_record_all_mux[] = {"All analog", + "Analog plus DAC"}; static const char *stac9766_boost1[] = {"0dB", "10dB"}; static const char *stac9766_boost2[] = {"0dB", "20dB"}; static const char *stac9766_stereo_mic[] = {"Off", "On"}; @@ -73,7 +75,8 @@ static const struct soc_enum stac9766_SPDIF_enum = static const struct soc_enum stac9766_popbypass_enum = SOC_ENUM_SINGLE(AC97_GENERAL_PURPOSE, 15, 2, stac9766_popbypass_mux); static const struct soc_enum stac9766_record_all_enum = - SOC_ENUM_SINGLE(AC97_STAC_ANALOG_SPECIAL, 12, 2, stac9766_record_all_mux); + SOC_ENUM_SINGLE(AC97_STAC_ANALOG_SPECIAL, 12, 2, + stac9766_record_all_mux); static const struct soc_enum stac9766_boost1_enum = SOC_ENUM_SINGLE(AC97_MIC, 6, 2, stac9766_boost1); /* 0/10dB */ static const struct soc_enum stac9766_boost2_enum = @@ -89,9 +92,11 @@ static const DECLARE_TLV_DB_LINEAR(mix_tlv, -3450, 1200); static const struct snd_kcontrol_new stac9766_snd_ac97_controls[] = { SOC_DOUBLE_TLV("Speaker Volume", AC97_MASTER, 8, 0, 31, 1, master_tlv), SOC_SINGLE("Speaker Switch", AC97_MASTER, 15, 1, 1), - SOC_DOUBLE_TLV("Headphone Volume", AC97_HEADPHONE, 8, 0, 31, 1, master_tlv), + SOC_DOUBLE_TLV("Headphone Volume", AC97_HEADPHONE, 8, 0, 31, 1, + master_tlv), SOC_SINGLE("Headphone Switch", AC97_HEADPHONE, 15, 1, 1), - SOC_SINGLE_TLV("Mono Out Volume", AC97_MASTER_MONO, 0, 31, 1, master_tlv), + SOC_SINGLE_TLV("Mono Out Volume", AC97_MASTER_MONO, 0, 31, 1, + master_tlv), SOC_SINGLE("Mono Out Switch", AC97_MASTER_MONO, 15, 1, 1), SOC_DOUBLE_TLV("Record Volume", AC97_REC_GAIN, 8, 0, 15, 0, record_tlv), @@ -133,8 +138,8 @@ static const struct snd_kcontrol_new stac9766_snd_ac97_controls[] = { SOC_ENUM("Pop Bypass Mux", stac9766_popbypass_enum), }; -int stac9766_ac97_write(struct snd_soc_codec *codec, unsigned int reg, - unsigned int val) +static int stac9766_ac97_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int val) { u16 *cache = codec->reg_cache; @@ -152,7 +157,8 @@ int stac9766_ac97_write(struct snd_soc_codec *codec, unsigned int reg, return 0; } -unsigned int stac9766_ac97_read(struct snd_soc_codec *codec, unsigned int reg) +static unsigned int stac9766_ac97_read(struct snd_soc_codec *codec, + unsigned int reg) { u16 val = 0, *cache = codec->reg_cache; @@ -176,7 +182,7 @@ unsigned int stac9766_ac97_read(struct snd_soc_codec *codec, unsigned int reg) } static int ac97_analog_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) + struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; struct snd_pcm_runtime *runtime = substream->runtime; @@ -197,7 +203,7 @@ static int ac97_analog_prepare(struct snd_pcm_substream *substream, } static int ac97_digital_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) + struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; struct snd_pcm_runtime *runtime = substream->runtime; @@ -216,7 +222,7 @@ static int ac97_digital_prepare(struct snd_pcm_substream *substream, } static int ac97_digital_trigger(struct snd_pcm_substream *substream, - int cmd, struct snd_soc_dai *dai) + int cmd, struct snd_soc_dai *dai) { struct snd_soc_codec *codec = dai->codec; unsigned short vra; @@ -232,7 +238,7 @@ static int ac97_digital_trigger(struct snd_pcm_substream *substream, } static int stac9766_set_bias_level(struct snd_soc_codec *codec, - enum snd_soc_bias_level level) + enum snd_soc_bias_level level) { switch (level) { case SND_SOC_BIAS_ON: /* full On */ @@ -249,7 +255,7 @@ static int stac9766_set_bias_level(struct snd_soc_codec *codec, return 0; } -int stac9766_reset(struct snd_soc_codec *codec, int try_warm) +static int stac9766_reset(struct snd_soc_codec *codec, int try_warm) { if (try_warm && soc_ac97_ops.warm_reset) { soc_ac97_ops.warm_reset(codec->ac97); @@ -266,7 +272,7 @@ int stac9766_reset(struct snd_soc_codec *codec, int try_warm) } static int stac9766_codec_suspend(struct platform_device *pdev, - pm_message_t state) + pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; @@ -303,13 +309,11 @@ reset: return 0; } -static struct snd_soc_dai_ops stac9766_dai_ops_analog = -{ +static struct snd_soc_dai_ops stac9766_dai_ops_analog = { .prepare = ac97_analog_prepare, }; -static struct snd_soc_dai_ops stac9766_dai_ops_digital = -{ +static struct snd_soc_dai_ops stac9766_dai_ops_digital = { .prepare = ac97_digital_prepare, .trigger = ac97_digital_trigger, }; @@ -354,7 +358,8 @@ struct snd_soc_dai stac9766_dai[] = { }, /* alsa ops */ .ops = &stac9766_dai_ops_digital, -}}; +} +}; EXPORT_SYMBOL_GPL(stac9766_dai); static int stac9766_codec_probe(struct platform_device *pdev) @@ -371,7 +376,8 @@ static int stac9766_codec_probe(struct platform_device *pdev) codec = socdev->card->codec; mutex_init(&codec->mutex); - codec->reg_cache = kmemdup(stac9766_reg, sizeof(stac9766_reg), GFP_KERNEL); + codec->reg_cache = kmemdup(stac9766_reg, sizeof(stac9766_reg), + GFP_KERNEL); if (codec->reg_cache == NULL) { ret = -ENOMEM; goto cache_err; @@ -409,8 +415,8 @@ static int stac9766_codec_probe(struct platform_device *pdev) stac9766_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - snd_soc_add_controls(codec, stac9766_snd_ac97_controls, ARRAY_SIZE( - stac9766_snd_ac97_controls)); + snd_soc_add_controls(codec, stac9766_snd_ac97_controls, + ARRAY_SIZE(stac9766_snd_ac97_controls)); ret = snd_soc_init_card(socdev); if (ret < 0) @@ -444,8 +450,7 @@ static int stac9766_codec_remove(struct platform_device *pdev) return 0; } -struct snd_soc_codec_device soc_codec_dev_stac9766 = -{ +struct snd_soc_codec_device soc_codec_dev_stac9766 = { .probe = stac9766_codec_probe, .remove = stac9766_codec_remove, .suspend = stac9766_codec_suspend, @@ -453,18 +458,6 @@ struct snd_soc_codec_device soc_codec_dev_stac9766 = }; EXPORT_SYMBOL_GPL(soc_codec_dev_stac9766); -static int __init stac9766_modinit(void) -{ - return snd_soc_register_dais(stac9766_dai, ARRAY_SIZE(stac9766_dai)); -} -module_init(stac9766_modinit); - -static void __exit stac9766_exit(void) -{ - snd_soc_unregister_dais(stac9766_dai, ARRAY_SIZE(stac9766_dai)); -} -module_exit(stac9766_exit); - MODULE_DESCRIPTION("ASoC stac9766 driver"); MODULE_AUTHOR("Jon Smirl "); MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 89dd08425273773fd33fc85d48d152c5679b2fb4 Mon Sep 17 00:00:00 2001 From: Jon Smirl Date: Sat, 23 May 2009 19:12:59 -0400 Subject: ASoC: Basic split of mpc5200 DMA code out of mpc5200_psc_i2s Basic split of mpc5200 DMA code out from i2s into a standalone file. Signed-off-by: Jon Smirl Acked-by: Grant Likely Signed-off-by: Mark Brown --- sound/soc/fsl/Kconfig | 4 + sound/soc/fsl/Makefile | 2 + sound/soc/fsl/mpc5200_dma.c | 458 +++++++++++++++++++++++++++++++++++++ sound/soc/fsl/mpc5200_dma.h | 81 +++++++ sound/soc/fsl/mpc5200_psc_i2s.c | 485 +--------------------------------------- 5 files changed, 547 insertions(+), 483 deletions(-) create mode 100644 sound/soc/fsl/mpc5200_dma.c create mode 100644 sound/soc/fsl/mpc5200_dma.h (limited to 'sound/soc') diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 9fc90828337..dc79bdf3369 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -1,5 +1,8 @@ config SND_SOC_OF_SIMPLE tristate + +config SND_MPC52xx_DMA + tristate # ASoC platform support for the Freescale MPC8610 SOC. This compiles drivers # for the SSI and the Elo DMA controller. You will still need to select @@ -23,6 +26,7 @@ config SND_SOC_MPC5200_I2S tristate "Freescale MPC5200 PSC in I2S mode driver" depends on PPC_MPC52xx && PPC_BESTCOMM select SND_SOC_OF_SIMPLE + select SND_MPC52xx_DMA select PPC_BESTCOMM_GEN_BD help Say Y here to support the MPC5200 PSCs in I2S mode. diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index f85134c8638..7731ef2539b 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -10,5 +10,7 @@ snd-soc-fsl-ssi-objs := fsl_ssi.o snd-soc-fsl-dma-objs := fsl_dma.o obj-$(CONFIG_SND_SOC_MPC8610) += snd-soc-fsl-ssi.o snd-soc-fsl-dma.o +# MPC5200 Platform Support +obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o obj-$(CONFIG_SND_SOC_MPC5200_I2S) += mpc5200_psc_i2s.o diff --git a/sound/soc/fsl/mpc5200_dma.c b/sound/soc/fsl/mpc5200_dma.c new file mode 100644 index 00000000000..4bae8d6e1a6 --- /dev/null +++ b/sound/soc/fsl/mpc5200_dma.c @@ -0,0 +1,458 @@ +/* + * Freescale MPC5200 PSC DMA + * ALSA SoC Platform driver + * + * Copyright (C) 2008 Secret Lab Technologies Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "mpc5200_dma.h" + +MODULE_AUTHOR("Grant Likely "); +MODULE_DESCRIPTION("Freescale MPC5200 PSC in DMA mode ASoC Driver"); +MODULE_LICENSE("GPL"); + +/* + * Interrupt handlers + */ +static irqreturn_t psc_i2s_status_irq(int irq, void *_psc_i2s) +{ + struct psc_i2s *psc_i2s = _psc_i2s; + struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs; + u16 isr; + + isr = in_be16(®s->mpc52xx_psc_isr); + + /* Playback underrun error */ + if (psc_i2s->playback.active && (isr & MPC52xx_PSC_IMR_TXEMP)) + psc_i2s->stats.underrun_count++; + + /* Capture overrun error */ + if (psc_i2s->capture.active && (isr & MPC52xx_PSC_IMR_ORERR)) + psc_i2s->stats.overrun_count++; + + out_8(®s->command, 4 << 4); /* reset the error status */ + + return IRQ_HANDLED; +} + +/** + * psc_i2s_bcom_enqueue_next_buffer - Enqueue another audio buffer + * @s: pointer to stream private data structure + * + * Enqueues another audio period buffer into the bestcomm queue. + * + * Note: The routine must only be called when there is space available in + * the queue. Otherwise the enqueue will fail and the audio ring buffer + * will get out of sync + */ +static void psc_i2s_bcom_enqueue_next_buffer(struct psc_i2s_stream *s) +{ + struct bcom_bd *bd; + + /* Prepare and enqueue the next buffer descriptor */ + bd = bcom_prepare_next_buffer(s->bcom_task); + bd->status = s->period_bytes; + bd->data[0] = s->period_next_pt; + bcom_submit_next_buffer(s->bcom_task, NULL); + + /* Update for next period */ + s->period_next_pt += s->period_bytes; + if (s->period_next_pt >= s->period_end) + s->period_next_pt = s->period_start; +} + +/* Bestcomm DMA irq handler */ +static irqreturn_t psc_i2s_bcom_irq(int irq, void *_psc_i2s_stream) +{ + struct psc_i2s_stream *s = _psc_i2s_stream; + + /* For each finished period, dequeue the completed period buffer + * and enqueue a new one in it's place. */ + while (bcom_buffer_done(s->bcom_task)) { + bcom_retrieve_buffer(s->bcom_task, NULL, NULL); + s->period_current_pt += s->period_bytes; + if (s->period_current_pt >= s->period_end) + s->period_current_pt = s->period_start; + psc_i2s_bcom_enqueue_next_buffer(s); + bcom_enable(s->bcom_task); + } + + /* If the stream is active, then also inform the PCM middle layer + * of the period finished event. */ + if (s->active) + snd_pcm_period_elapsed(s->stream); + + return IRQ_HANDLED; +} + +/** + * psc_i2s_startup: create a new substream + * + * This is the first function called when a stream is opened. + * + * If this is the first stream open, then grab the IRQ and program most of + * the PSC registers. + */ +int psc_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + int rc; + + dev_dbg(psc_i2s->dev, "psc_i2s_startup(substream=%p)\n", substream); + + if (!psc_i2s->playback.active && + !psc_i2s->capture.active) { + /* Setup the IRQs */ + rc = request_irq(psc_i2s->irq, &psc_i2s_status_irq, IRQF_SHARED, + "psc-i2s-status", psc_i2s); + rc |= request_irq(psc_i2s->capture.irq, + &psc_i2s_bcom_irq, IRQF_SHARED, + "psc-i2s-capture", &psc_i2s->capture); + rc |= request_irq(psc_i2s->playback.irq, + &psc_i2s_bcom_irq, IRQF_SHARED, + "psc-i2s-playback", &psc_i2s->playback); + if (rc) { + free_irq(psc_i2s->irq, psc_i2s); + free_irq(psc_i2s->capture.irq, + &psc_i2s->capture); + free_irq(psc_i2s->playback.irq, + &psc_i2s->playback); + return -ENODEV; + } + } + + return 0; +} + +int psc_i2s_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + return 0; +} + +/** + * psc_i2s_trigger: start and stop the DMA transfer. + * + * This function is called by ALSA to start, stop, pause, and resume the DMA + * transfer of data. + */ +int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + struct psc_i2s_stream *s; + struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs; + u16 imr; + u8 psc_cmd; + unsigned long flags; + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_i2s->capture; + else + s = &psc_i2s->playback; + + dev_dbg(psc_i2s->dev, "psc_i2s_trigger(substream=%p, cmd=%i)" + " stream_id=%i\n", + substream, cmd, substream->pstr->stream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + s->period_bytes = frames_to_bytes(runtime, + runtime->period_size); + s->period_start = virt_to_phys(runtime->dma_area); + s->period_end = s->period_start + + (s->period_bytes * runtime->periods); + s->period_next_pt = s->period_start; + s->period_current_pt = s->period_start; + s->active = 1; + + /* First; reset everything */ + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { + out_8(®s->command, MPC52xx_PSC_RST_RX); + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + } else { + out_8(®s->command, MPC52xx_PSC_RST_TX); + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + } + + /* Next, fill up the bestcomm bd queue and enable DMA. + * This will begin filling the PSC's fifo. */ + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + bcom_gen_bd_rx_reset(s->bcom_task); + else + bcom_gen_bd_tx_reset(s->bcom_task); + while (!bcom_queue_full(s->bcom_task)) + psc_i2s_bcom_enqueue_next_buffer(s); + bcom_enable(s->bcom_task); + + /* Due to errata in the i2s mode; need to line up enabling + * the transmitter with a transition on the frame sync + * line */ + + spin_lock_irqsave(&psc_i2s->lock, flags); + /* first make sure it is low */ + while ((in_8(®s->ipcr_acr.ipcr) & 0x80) != 0) + ; + /* then wait for the transition to high */ + while ((in_8(®s->ipcr_acr.ipcr) & 0x80) == 0) + ; + /* Finally, enable the PSC. + * Receiver must always be enabled; even when we only want + * transmit. (see 15.3.2.3 of MPC5200B User's Guide) */ + psc_cmd = MPC52xx_PSC_RX_ENABLE; + if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) + psc_cmd |= MPC52xx_PSC_TX_ENABLE; + out_8(®s->command, psc_cmd); + spin_unlock_irqrestore(&psc_i2s->lock, flags); + + break; + + case SNDRV_PCM_TRIGGER_STOP: + /* Turn off the PSC */ + s->active = 0; + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (!psc_i2s->playback.active) { + out_8(®s->command, 2 << 4); /* reset rx */ + out_8(®s->command, 3 << 4); /* reset tx */ + out_8(®s->command, 4 << 4); /* reset err */ + } + } else { + out_8(®s->command, 3 << 4); /* reset tx */ + out_8(®s->command, 4 << 4); /* reset err */ + if (!psc_i2s->capture.active) + out_8(®s->command, 2 << 4); /* reset rx */ + } + + bcom_disable(s->bcom_task); + while (!bcom_queue_empty(s->bcom_task)) + bcom_retrieve_buffer(s->bcom_task, NULL, NULL); + + break; + + default: + dev_dbg(psc_i2s->dev, "invalid command\n"); + return -EINVAL; + } + + /* Update interrupt enable settings */ + imr = 0; + if (psc_i2s->playback.active) + imr |= MPC52xx_PSC_IMR_TXEMP; + if (psc_i2s->capture.active) + imr |= MPC52xx_PSC_IMR_ORERR; + out_be16(®s->isr_imr.imr, imr); + + return 0; +} + +/** + * psc_i2s_shutdown: shutdown the data transfer on a stream + * + * Shutdown the PSC if there are no other substreams open. + */ +void psc_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + + dev_dbg(psc_i2s->dev, "psc_i2s_shutdown(substream=%p)\n", substream); + + /* + * If this is the last active substream, disable the PSC and release + * the IRQ. + */ + if (!psc_i2s->playback.active && + !psc_i2s->capture.active) { + + /* Disable all interrupts and reset the PSC */ + out_be16(&psc_i2s->psc_regs->isr_imr.imr, 0); + out_8(&psc_i2s->psc_regs->command, 3 << 4); /* reset tx */ + out_8(&psc_i2s->psc_regs->command, 2 << 4); /* reset rx */ + out_8(&psc_i2s->psc_regs->command, 1 << 4); /* reset mode */ + out_8(&psc_i2s->psc_regs->command, 4 << 4); /* reset error */ + + /* Release irqs */ + free_irq(psc_i2s->irq, psc_i2s); + free_irq(psc_i2s->capture.irq, &psc_i2s->capture); + free_irq(psc_i2s->playback.irq, &psc_i2s->playback); + } +} + +/* --------------------------------------------------------------------- + * The PSC DMA 'ASoC platform' driver + * + * Can be referenced by an 'ASoC machine' driver + * This driver only deals with the audio bus; it doesn't have any + * interaction with the attached codec + */ + +static const struct snd_pcm_hardware psc_i2s_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_BATCH, + .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .period_bytes_max = 1024 * 1024, + .period_bytes_min = 32, + .periods_min = 2, + .periods_max = 256, + .buffer_bytes_max = 2 * 1024 * 1024, + .fifo_size = 0, +}; + +static int psc_i2s_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct psc_i2s_stream *s; + + dev_dbg(psc_i2s->dev, "psc_i2s_pcm_open(substream=%p)\n", substream); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_i2s->capture; + else + s = &psc_i2s->playback; + + snd_soc_set_runtime_hwparams(substream, &psc_i2s_pcm_hardware); + + s->stream = substream; + return 0; +} + +static int psc_i2s_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct psc_i2s_stream *s; + + dev_dbg(psc_i2s->dev, "psc_i2s_pcm_close(substream=%p)\n", substream); + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_i2s->capture; + else + s = &psc_i2s->playback; + + s->stream = NULL; + return 0; +} + +static snd_pcm_uframes_t +psc_i2s_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct psc_i2s_stream *s; + dma_addr_t count; + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + s = &psc_i2s->capture; + else + s = &psc_i2s->playback; + + count = s->period_current_pt - s->period_start; + + return bytes_to_frames(substream->runtime, count); +} + +static struct snd_pcm_ops psc_i2s_pcm_ops = { + .open = psc_i2s_pcm_open, + .close = psc_i2s_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .pointer = psc_i2s_pcm_pointer, +}; + +static u64 psc_i2s_pcm_dmamask = 0xffffffff; +static int psc_i2s_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + struct snd_soc_pcm_runtime *rtd = pcm->private_data; + size_t size = psc_i2s_pcm_hardware.buffer_bytes_max; + int rc = 0; + + dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_new(card=%p, dai=%p, pcm=%p)\n", + card, dai, pcm); + + if (!card->dev->dma_mask) + card->dev->dma_mask = &psc_i2s_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (pcm->streams[0].substream) { + rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size, + &pcm->streams[0].substream->dma_buffer); + if (rc) + goto playback_alloc_err; + } + + if (pcm->streams[1].substream) { + rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size, + &pcm->streams[1].substream->dma_buffer); + if (rc) + goto capture_alloc_err; + } + + return 0; + + capture_alloc_err: + if (pcm->streams[0].substream) + snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer); + playback_alloc_err: + dev_err(card->dev, "Cannot allocate buffer(s)\n"); + return -ENOMEM; +} + +static void psc_i2s_pcm_free(struct snd_pcm *pcm) +{ + struct snd_soc_pcm_runtime *rtd = pcm->private_data; + struct snd_pcm_substream *substream; + int stream; + + dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_free(pcm=%p)\n", pcm); + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (substream) { + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } + } +} + +struct snd_soc_platform psc_i2s_pcm_soc_platform = { + .name = "mpc5200-psc-audio", + .pcm_ops = &psc_i2s_pcm_ops, + .pcm_new = &psc_i2s_pcm_new, + .pcm_free = &psc_i2s_pcm_free, +}; + diff --git a/sound/soc/fsl/mpc5200_dma.h b/sound/soc/fsl/mpc5200_dma.h new file mode 100644 index 00000000000..9a19e8a70c5 --- /dev/null +++ b/sound/soc/fsl/mpc5200_dma.h @@ -0,0 +1,81 @@ +/* + * Freescale MPC5200 Audio DMA driver + */ + +#ifndef __SOUND_SOC_FSL_MPC5200_DMA_H__ +#define __SOUND_SOC_FSL_MPC5200_DMA_H__ + +/** + * psc_i2s_stream - Data specific to a single stream (playback or capture) + * @active: flag indicating if the stream is active + * @psc_i2s: pointer back to parent psc_i2s data structure + * @bcom_task: bestcomm task structure + * @irq: irq number for bestcomm task + * @period_start: physical address of start of DMA region + * @period_end: physical address of end of DMA region + * @period_next_pt: physical address of next DMA buffer to enqueue + * @period_bytes: size of DMA period in bytes + */ +struct psc_i2s_stream { + int active; + struct psc_i2s *psc_i2s; + struct bcom_task *bcom_task; + int irq; + struct snd_pcm_substream *stream; + dma_addr_t period_start; + dma_addr_t period_end; + dma_addr_t period_next_pt; + dma_addr_t period_current_pt; + int period_bytes; +}; + +/** + * psc_i2s - Private driver data + * @name: short name for this device ("PSC0", "PSC1", etc) + * @psc_regs: pointer to the PSC's registers + * @fifo_regs: pointer to the PSC's FIFO registers + * @irq: IRQ of this PSC + * @dev: struct device pointer + * @dai: the CPU DAI for this device + * @sicr: Base value used in serial interface control register; mode is ORed + * with this value. + * @playback: Playback stream context data + * @capture: Capture stream context data + */ +struct psc_i2s { + char name[32]; + struct mpc52xx_psc __iomem *psc_regs; + struct mpc52xx_psc_fifo __iomem *fifo_regs; + unsigned int irq; + struct device *dev; + struct snd_soc_dai dai; + spinlock_t lock; + u32 sicr; + + /* per-stream data */ + struct psc_i2s_stream playback; + struct psc_i2s_stream capture; + + /* Statistics */ + struct { + int overrun_count; + int underrun_count; + } stats; +}; + + +int psc_i2s_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai); + +int psc_i2s_hw_free(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai); + +void psc_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai); + +int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai); + +extern struct snd_soc_platform psc_i2s_pcm_soc_platform; + +#endif /* __SOUND_SOC_FSL_MPC5200_DMA_H__ */ diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c index 1111c710118..8974b53eec9 100644 --- a/sound/soc/fsl/mpc5200_psc_i2s.c +++ b/sound/soc/fsl/mpc5200_psc_i2s.c @@ -25,6 +25,8 @@ #include #include +#include "mpc5200_dma.h" + MODULE_AUTHOR("Grant Likely "); MODULE_DESCRIPTION("Freescale MPC5200 PSC in I2S mode ASoC Driver"); MODULE_LICENSE("GPL"); @@ -47,179 +49,6 @@ MODULE_LICENSE("GPL"); SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S24_BE | \ SNDRV_PCM_FMTBIT_S32_BE) -/** - * psc_i2s_stream - Data specific to a single stream (playback or capture) - * @active: flag indicating if the stream is active - * @psc_i2s: pointer back to parent psc_i2s data structure - * @bcom_task: bestcomm task structure - * @irq: irq number for bestcomm task - * @period_start: physical address of start of DMA region - * @period_end: physical address of end of DMA region - * @period_next_pt: physical address of next DMA buffer to enqueue - * @period_bytes: size of DMA period in bytes - */ -struct psc_i2s_stream { - int active; - struct psc_i2s *psc_i2s; - struct bcom_task *bcom_task; - int irq; - struct snd_pcm_substream *stream; - dma_addr_t period_start; - dma_addr_t period_end; - dma_addr_t period_next_pt; - dma_addr_t period_current_pt; - int period_bytes; -}; - -/** - * psc_i2s - Private driver data - * @name: short name for this device ("PSC0", "PSC1", etc) - * @psc_regs: pointer to the PSC's registers - * @fifo_regs: pointer to the PSC's FIFO registers - * @irq: IRQ of this PSC - * @dev: struct device pointer - * @dai: the CPU DAI for this device - * @sicr: Base value used in serial interface control register; mode is ORed - * with this value. - * @playback: Playback stream context data - * @capture: Capture stream context data - */ -struct psc_i2s { - char name[32]; - struct mpc52xx_psc __iomem *psc_regs; - struct mpc52xx_psc_fifo __iomem *fifo_regs; - unsigned int irq; - struct device *dev; - struct snd_soc_dai dai; - spinlock_t lock; - u32 sicr; - - /* per-stream data */ - struct psc_i2s_stream playback; - struct psc_i2s_stream capture; - - /* Statistics */ - struct { - int overrun_count; - int underrun_count; - } stats; -}; - -/* - * Interrupt handlers - */ -static irqreturn_t psc_i2s_status_irq(int irq, void *_psc_i2s) -{ - struct psc_i2s *psc_i2s = _psc_i2s; - struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs; - u16 isr; - - isr = in_be16(®s->mpc52xx_psc_isr); - - /* Playback underrun error */ - if (psc_i2s->playback.active && (isr & MPC52xx_PSC_IMR_TXEMP)) - psc_i2s->stats.underrun_count++; - - /* Capture overrun error */ - if (psc_i2s->capture.active && (isr & MPC52xx_PSC_IMR_ORERR)) - psc_i2s->stats.overrun_count++; - - out_8(®s->command, 4 << 4); /* reset the error status */ - - return IRQ_HANDLED; -} - -/** - * psc_i2s_bcom_enqueue_next_buffer - Enqueue another audio buffer - * @s: pointer to stream private data structure - * - * Enqueues another audio period buffer into the bestcomm queue. - * - * Note: The routine must only be called when there is space available in - * the queue. Otherwise the enqueue will fail and the audio ring buffer - * will get out of sync - */ -static void psc_i2s_bcom_enqueue_next_buffer(struct psc_i2s_stream *s) -{ - struct bcom_bd *bd; - - /* Prepare and enqueue the next buffer descriptor */ - bd = bcom_prepare_next_buffer(s->bcom_task); - bd->status = s->period_bytes; - bd->data[0] = s->period_next_pt; - bcom_submit_next_buffer(s->bcom_task, NULL); - - /* Update for next period */ - s->period_next_pt += s->period_bytes; - if (s->period_next_pt >= s->period_end) - s->period_next_pt = s->period_start; -} - -/* Bestcomm DMA irq handler */ -static irqreturn_t psc_i2s_bcom_irq(int irq, void *_psc_i2s_stream) -{ - struct psc_i2s_stream *s = _psc_i2s_stream; - - /* For each finished period, dequeue the completed period buffer - * and enqueue a new one in it's place. */ - while (bcom_buffer_done(s->bcom_task)) { - bcom_retrieve_buffer(s->bcom_task, NULL, NULL); - s->period_current_pt += s->period_bytes; - if (s->period_current_pt >= s->period_end) - s->period_current_pt = s->period_start; - psc_i2s_bcom_enqueue_next_buffer(s); - bcom_enable(s->bcom_task); - } - - /* If the stream is active, then also inform the PCM middle layer - * of the period finished event. */ - if (s->active) - snd_pcm_period_elapsed(s->stream); - - return IRQ_HANDLED; -} - -/** - * psc_i2s_startup: create a new substream - * - * This is the first function called when a stream is opened. - * - * If this is the first stream open, then grab the IRQ and program most of - * the PSC registers. - */ -static int psc_i2s_startup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; - int rc; - - dev_dbg(psc_i2s->dev, "psc_i2s_startup(substream=%p)\n", substream); - - if (!psc_i2s->playback.active && - !psc_i2s->capture.active) { - /* Setup the IRQs */ - rc = request_irq(psc_i2s->irq, &psc_i2s_status_irq, IRQF_SHARED, - "psc-i2s-status", psc_i2s); - rc |= request_irq(psc_i2s->capture.irq, - &psc_i2s_bcom_irq, IRQF_SHARED, - "psc-i2s-capture", &psc_i2s->capture); - rc |= request_irq(psc_i2s->playback.irq, - &psc_i2s_bcom_irq, IRQF_SHARED, - "psc-i2s-playback", &psc_i2s->playback); - if (rc) { - free_irq(psc_i2s->irq, psc_i2s); - free_irq(psc_i2s->capture.irq, - &psc_i2s->capture); - free_irq(psc_i2s->playback.irq, - &psc_i2s->playback); - return -ENODEV; - } - } - - return 0; -} - static int psc_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -258,164 +87,6 @@ static int psc_i2s_hw_params(struct snd_pcm_substream *substream, return 0; } -static int psc_i2s_hw_free(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - snd_pcm_set_runtime_buffer(substream, NULL); - return 0; -} - -/** - * psc_i2s_trigger: start and stop the DMA transfer. - * - * This function is called by ALSA to start, stop, pause, and resume the DMA - * transfer of data. - */ -static int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd, - struct snd_soc_dai *dai) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; - struct snd_pcm_runtime *runtime = substream->runtime; - struct psc_i2s_stream *s; - struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs; - u16 imr; - u8 psc_cmd; - unsigned long flags; - - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) - s = &psc_i2s->capture; - else - s = &psc_i2s->playback; - - dev_dbg(psc_i2s->dev, "psc_i2s_trigger(substream=%p, cmd=%i)" - " stream_id=%i\n", - substream, cmd, substream->pstr->stream); - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - s->period_bytes = frames_to_bytes(runtime, - runtime->period_size); - s->period_start = virt_to_phys(runtime->dma_area); - s->period_end = s->period_start + - (s->period_bytes * runtime->periods); - s->period_next_pt = s->period_start; - s->period_current_pt = s->period_start; - s->active = 1; - - /* First; reset everything */ - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { - out_8(®s->command, MPC52xx_PSC_RST_RX); - out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); - } else { - out_8(®s->command, MPC52xx_PSC_RST_TX); - out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); - } - - /* Next, fill up the bestcomm bd queue and enable DMA. - * This will begin filling the PSC's fifo. */ - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) - bcom_gen_bd_rx_reset(s->bcom_task); - else - bcom_gen_bd_tx_reset(s->bcom_task); - while (!bcom_queue_full(s->bcom_task)) - psc_i2s_bcom_enqueue_next_buffer(s); - bcom_enable(s->bcom_task); - - /* Due to errata in the i2s mode; need to line up enabling - * the transmitter with a transition on the frame sync - * line */ - - spin_lock_irqsave(&psc_i2s->lock, flags); - /* first make sure it is low */ - while ((in_8(®s->ipcr_acr.ipcr) & 0x80) != 0) - ; - /* then wait for the transition to high */ - while ((in_8(®s->ipcr_acr.ipcr) & 0x80) == 0) - ; - /* Finally, enable the PSC. - * Receiver must always be enabled; even when we only want - * transmit. (see 15.3.2.3 of MPC5200B User's Guide) */ - psc_cmd = MPC52xx_PSC_RX_ENABLE; - if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) - psc_cmd |= MPC52xx_PSC_TX_ENABLE; - out_8(®s->command, psc_cmd); - spin_unlock_irqrestore(&psc_i2s->lock, flags); - - break; - - case SNDRV_PCM_TRIGGER_STOP: - /* Turn off the PSC */ - s->active = 0; - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { - if (!psc_i2s->playback.active) { - out_8(®s->command, 2 << 4); /* reset rx */ - out_8(®s->command, 3 << 4); /* reset tx */ - out_8(®s->command, 4 << 4); /* reset err */ - } - } else { - out_8(®s->command, 3 << 4); /* reset tx */ - out_8(®s->command, 4 << 4); /* reset err */ - if (!psc_i2s->capture.active) - out_8(®s->command, 2 << 4); /* reset rx */ - } - - bcom_disable(s->bcom_task); - while (!bcom_queue_empty(s->bcom_task)) - bcom_retrieve_buffer(s->bcom_task, NULL, NULL); - - break; - - default: - dev_dbg(psc_i2s->dev, "invalid command\n"); - return -EINVAL; - } - - /* Update interrupt enable settings */ - imr = 0; - if (psc_i2s->playback.active) - imr |= MPC52xx_PSC_IMR_TXEMP; - if (psc_i2s->capture.active) - imr |= MPC52xx_PSC_IMR_ORERR; - out_be16(®s->isr_imr.imr, imr); - - return 0; -} - -/** - * psc_i2s_shutdown: shutdown the data transfer on a stream - * - * Shutdown the PSC if there are no other substreams open. - */ -static void psc_i2s_shutdown(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; - - dev_dbg(psc_i2s->dev, "psc_i2s_shutdown(substream=%p)\n", substream); - - /* - * If this is the last active substream, disable the PSC and release - * the IRQ. - */ - if (!psc_i2s->playback.active && - !psc_i2s->capture.active) { - - /* Disable all interrupts and reset the PSC */ - out_be16(&psc_i2s->psc_regs->isr_imr.imr, 0); - out_8(&psc_i2s->psc_regs->command, 3 << 4); /* reset tx */ - out_8(&psc_i2s->psc_regs->command, 2 << 4); /* reset rx */ - out_8(&psc_i2s->psc_regs->command, 1 << 4); /* reset mode */ - out_8(&psc_i2s->psc_regs->command, 4 << 4); /* reset error */ - - /* Release irqs */ - free_irq(psc_i2s->irq, psc_i2s); - free_irq(psc_i2s->capture.irq, &psc_i2s->capture); - free_irq(psc_i2s->playback.irq, &psc_i2s->playback); - } -} - /** * psc_i2s_set_sysclk: set the clock frequency and direction * @@ -494,158 +165,6 @@ static struct snd_soc_dai psc_i2s_dai_template = { .ops = &psc_i2s_dai_ops, }; -/* --------------------------------------------------------------------- - * The PSC I2S 'ASoC platform' driver - * - * Can be referenced by an 'ASoC machine' driver - * This driver only deals with the audio bus; it doesn't have any - * interaction with the attached codec - */ - -static const struct snd_pcm_hardware psc_i2s_pcm_hardware = { - .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | - SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | - SNDRV_PCM_INFO_BATCH, - .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | - SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE, - .rate_min = 8000, - .rate_max = 48000, - .channels_min = 2, - .channels_max = 2, - .period_bytes_max = 1024 * 1024, - .period_bytes_min = 32, - .periods_min = 2, - .periods_max = 256, - .buffer_bytes_max = 2 * 1024 * 1024, - .fifo_size = 0, -}; - -static int psc_i2s_pcm_open(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; - struct psc_i2s_stream *s; - - dev_dbg(psc_i2s->dev, "psc_i2s_pcm_open(substream=%p)\n", substream); - - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) - s = &psc_i2s->capture; - else - s = &psc_i2s->playback; - - snd_soc_set_runtime_hwparams(substream, &psc_i2s_pcm_hardware); - - s->stream = substream; - return 0; -} - -static int psc_i2s_pcm_close(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; - struct psc_i2s_stream *s; - - dev_dbg(psc_i2s->dev, "psc_i2s_pcm_close(substream=%p)\n", substream); - - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) - s = &psc_i2s->capture; - else - s = &psc_i2s->playback; - - s->stream = NULL; - return 0; -} - -static snd_pcm_uframes_t -psc_i2s_pcm_pointer(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; - struct psc_i2s_stream *s; - dma_addr_t count; - - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) - s = &psc_i2s->capture; - else - s = &psc_i2s->playback; - - count = s->period_current_pt - s->period_start; - - return bytes_to_frames(substream->runtime, count); -} - -static struct snd_pcm_ops psc_i2s_pcm_ops = { - .open = psc_i2s_pcm_open, - .close = psc_i2s_pcm_close, - .ioctl = snd_pcm_lib_ioctl, - .pointer = psc_i2s_pcm_pointer, -}; - -static u64 psc_i2s_pcm_dmamask = 0xffffffff; -static int psc_i2s_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, - struct snd_pcm *pcm) -{ - struct snd_soc_pcm_runtime *rtd = pcm->private_data; - size_t size = psc_i2s_pcm_hardware.buffer_bytes_max; - int rc = 0; - - dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_new(card=%p, dai=%p, pcm=%p)\n", - card, dai, pcm); - - if (!card->dev->dma_mask) - card->dev->dma_mask = &psc_i2s_pcm_dmamask; - if (!card->dev->coherent_dma_mask) - card->dev->coherent_dma_mask = 0xffffffff; - - if (pcm->streams[0].substream) { - rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size, - &pcm->streams[0].substream->dma_buffer); - if (rc) - goto playback_alloc_err; - } - - if (pcm->streams[1].substream) { - rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size, - &pcm->streams[1].substream->dma_buffer); - if (rc) - goto capture_alloc_err; - } - - return 0; - - capture_alloc_err: - if (pcm->streams[0].substream) - snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer); - playback_alloc_err: - dev_err(card->dev, "Cannot allocate buffer(s)\n"); - return -ENOMEM; -} - -static void psc_i2s_pcm_free(struct snd_pcm *pcm) -{ - struct snd_soc_pcm_runtime *rtd = pcm->private_data; - struct snd_pcm_substream *substream; - int stream; - - dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_free(pcm=%p)\n", pcm); - - for (stream = 0; stream < 2; stream++) { - substream = pcm->streams[stream].substream; - if (substream) { - snd_dma_free_pages(&substream->dma_buffer); - substream->dma_buffer.area = NULL; - substream->dma_buffer.addr = 0; - } - } -} - -struct snd_soc_platform psc_i2s_pcm_soc_platform = { - .name = "mpc5200-psc-audio", - .pcm_ops = &psc_i2s_pcm_ops, - .pcm_new = &psc_i2s_pcm_new, - .pcm_free = &psc_i2s_pcm_free, -}; - /* --------------------------------------------------------------------- * Sysfs attributes for debugging */ -- cgit v1.2.3 From cebe77674cab51a9ff1deaa077ab74aff3996764 Mon Sep 17 00:00:00 2001 From: Jon Smirl Date: Sat, 23 May 2009 19:13:01 -0400 Subject: ASoC: Rename the PSC functions to DMA Rename the functions in the mpc5200 DMA file from i2s based names to dma ones to reflect the file they are in. Signed-off-by: Jon Smirl Acked-by: Grant Likely Signed-off-by: Mark Brown --- sound/soc/fsl/mpc5200_dma.c | 194 ++++++++++++++++++++-------------------- sound/soc/fsl/mpc5200_dma.h | 26 +++--- sound/soc/fsl/mpc5200_psc_i2s.c | 160 ++++++++++++++++----------------- 3 files changed, 190 insertions(+), 190 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/fsl/mpc5200_dma.c b/sound/soc/fsl/mpc5200_dma.c index 4bae8d6e1a6..685039213f0 100644 --- a/sound/soc/fsl/mpc5200_dma.c +++ b/sound/soc/fsl/mpc5200_dma.c @@ -34,21 +34,21 @@ MODULE_LICENSE("GPL"); /* * Interrupt handlers */ -static irqreturn_t psc_i2s_status_irq(int irq, void *_psc_i2s) +static irqreturn_t psc_dma_status_irq(int irq, void *_psc_dma) { - struct psc_i2s *psc_i2s = _psc_i2s; - struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs; + struct psc_dma *psc_dma = _psc_dma; + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; u16 isr; isr = in_be16(®s->mpc52xx_psc_isr); /* Playback underrun error */ - if (psc_i2s->playback.active && (isr & MPC52xx_PSC_IMR_TXEMP)) - psc_i2s->stats.underrun_count++; + if (psc_dma->playback.active && (isr & MPC52xx_PSC_IMR_TXEMP)) + psc_dma->stats.underrun_count++; /* Capture overrun error */ - if (psc_i2s->capture.active && (isr & MPC52xx_PSC_IMR_ORERR)) - psc_i2s->stats.overrun_count++; + if (psc_dma->capture.active && (isr & MPC52xx_PSC_IMR_ORERR)) + psc_dma->stats.overrun_count++; out_8(®s->command, 4 << 4); /* reset the error status */ @@ -56,7 +56,7 @@ static irqreturn_t psc_i2s_status_irq(int irq, void *_psc_i2s) } /** - * psc_i2s_bcom_enqueue_next_buffer - Enqueue another audio buffer + * psc_dma_bcom_enqueue_next_buffer - Enqueue another audio buffer * @s: pointer to stream private data structure * * Enqueues another audio period buffer into the bestcomm queue. @@ -65,7 +65,7 @@ static irqreturn_t psc_i2s_status_irq(int irq, void *_psc_i2s) * the queue. Otherwise the enqueue will fail and the audio ring buffer * will get out of sync */ -static void psc_i2s_bcom_enqueue_next_buffer(struct psc_i2s_stream *s) +static void psc_dma_bcom_enqueue_next_buffer(struct psc_dma_stream *s) { struct bcom_bd *bd; @@ -82,9 +82,9 @@ static void psc_i2s_bcom_enqueue_next_buffer(struct psc_i2s_stream *s) } /* Bestcomm DMA irq handler */ -static irqreturn_t psc_i2s_bcom_irq(int irq, void *_psc_i2s_stream) +static irqreturn_t psc_dma_bcom_irq(int irq, void *_psc_dma_stream) { - struct psc_i2s_stream *s = _psc_i2s_stream; + struct psc_dma_stream *s = _psc_dma_stream; /* For each finished period, dequeue the completed period buffer * and enqueue a new one in it's place. */ @@ -93,7 +93,7 @@ static irqreturn_t psc_i2s_bcom_irq(int irq, void *_psc_i2s_stream) s->period_current_pt += s->period_bytes; if (s->period_current_pt >= s->period_end) s->period_current_pt = s->period_start; - psc_i2s_bcom_enqueue_next_buffer(s); + psc_dma_bcom_enqueue_next_buffer(s); bcom_enable(s->bcom_task); } @@ -106,39 +106,39 @@ static irqreturn_t psc_i2s_bcom_irq(int irq, void *_psc_i2s_stream) } /** - * psc_i2s_startup: create a new substream + * psc_dma_startup: create a new substream * * This is the first function called when a stream is opened. * * If this is the first stream open, then grab the IRQ and program most of * the PSC registers. */ -int psc_i2s_startup(struct snd_pcm_substream *substream, +int psc_dma_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; int rc; - dev_dbg(psc_i2s->dev, "psc_i2s_startup(substream=%p)\n", substream); + dev_dbg(psc_dma->dev, "psc_dma_startup(substream=%p)\n", substream); - if (!psc_i2s->playback.active && - !psc_i2s->capture.active) { + if (!psc_dma->playback.active && + !psc_dma->capture.active) { /* Setup the IRQs */ - rc = request_irq(psc_i2s->irq, &psc_i2s_status_irq, IRQF_SHARED, - "psc-i2s-status", psc_i2s); - rc |= request_irq(psc_i2s->capture.irq, - &psc_i2s_bcom_irq, IRQF_SHARED, - "psc-i2s-capture", &psc_i2s->capture); - rc |= request_irq(psc_i2s->playback.irq, - &psc_i2s_bcom_irq, IRQF_SHARED, - "psc-i2s-playback", &psc_i2s->playback); + rc = request_irq(psc_dma->irq, &psc_dma_status_irq, IRQF_SHARED, + "psc-dma-status", psc_dma); + rc |= request_irq(psc_dma->capture.irq, + &psc_dma_bcom_irq, IRQF_SHARED, + "psc-dma-capture", &psc_dma->capture); + rc |= request_irq(psc_dma->playback.irq, + &psc_dma_bcom_irq, IRQF_SHARED, + "psc-dma-playback", &psc_dma->playback); if (rc) { - free_irq(psc_i2s->irq, psc_i2s); - free_irq(psc_i2s->capture.irq, - &psc_i2s->capture); - free_irq(psc_i2s->playback.irq, - &psc_i2s->playback); + free_irq(psc_dma->irq, psc_dma); + free_irq(psc_dma->capture.irq, + &psc_dma->capture); + free_irq(psc_dma->playback.irq, + &psc_dma->playback); return -ENODEV; } } @@ -146,7 +146,7 @@ int psc_i2s_startup(struct snd_pcm_substream *substream, return 0; } -int psc_i2s_hw_free(struct snd_pcm_substream *substream, +int psc_dma_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { snd_pcm_set_runtime_buffer(substream, NULL); @@ -154,29 +154,29 @@ int psc_i2s_hw_free(struct snd_pcm_substream *substream, } /** - * psc_i2s_trigger: start and stop the DMA transfer. + * psc_dma_trigger: start and stop the DMA transfer. * * This function is called by ALSA to start, stop, pause, and resume the DMA * transfer of data. */ -int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd, +int psc_dma_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; struct snd_pcm_runtime *runtime = substream->runtime; - struct psc_i2s_stream *s; - struct mpc52xx_psc __iomem *regs = psc_i2s->psc_regs; + struct psc_dma_stream *s; + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; u16 imr; u8 psc_cmd; unsigned long flags; if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) - s = &psc_i2s->capture; + s = &psc_dma->capture; else - s = &psc_i2s->playback; + s = &psc_dma->playback; - dev_dbg(psc_i2s->dev, "psc_i2s_trigger(substream=%p, cmd=%i)" + dev_dbg(psc_dma->dev, "psc_dma_trigger(substream=%p, cmd=%i)" " stream_id=%i\n", substream, cmd, substream->pstr->stream); @@ -207,14 +207,14 @@ int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd, else bcom_gen_bd_tx_reset(s->bcom_task); while (!bcom_queue_full(s->bcom_task)) - psc_i2s_bcom_enqueue_next_buffer(s); + psc_dma_bcom_enqueue_next_buffer(s); bcom_enable(s->bcom_task); - /* Due to errata in the i2s mode; need to line up enabling + /* Due to errata in the dma mode; need to line up enabling * the transmitter with a transition on the frame sync * line */ - spin_lock_irqsave(&psc_i2s->lock, flags); + spin_lock_irqsave(&psc_dma->lock, flags); /* first make sure it is low */ while ((in_8(®s->ipcr_acr.ipcr) & 0x80) != 0) ; @@ -228,7 +228,7 @@ int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd, if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) psc_cmd |= MPC52xx_PSC_TX_ENABLE; out_8(®s->command, psc_cmd); - spin_unlock_irqrestore(&psc_i2s->lock, flags); + spin_unlock_irqrestore(&psc_dma->lock, flags); break; @@ -236,7 +236,7 @@ int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd, /* Turn off the PSC */ s->active = 0; if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { - if (!psc_i2s->playback.active) { + if (!psc_dma->playback.active) { out_8(®s->command, 2 << 4); /* reset rx */ out_8(®s->command, 3 << 4); /* reset tx */ out_8(®s->command, 4 << 4); /* reset err */ @@ -244,7 +244,7 @@ int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd, } else { out_8(®s->command, 3 << 4); /* reset tx */ out_8(®s->command, 4 << 4); /* reset err */ - if (!psc_i2s->capture.active) + if (!psc_dma->capture.active) out_8(®s->command, 2 << 4); /* reset rx */ } @@ -255,15 +255,15 @@ int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd, break; default: - dev_dbg(psc_i2s->dev, "invalid command\n"); + dev_dbg(psc_dma->dev, "invalid command\n"); return -EINVAL; } /* Update interrupt enable settings */ imr = 0; - if (psc_i2s->playback.active) + if (psc_dma->playback.active) imr |= MPC52xx_PSC_IMR_TXEMP; - if (psc_i2s->capture.active) + if (psc_dma->capture.active) imr |= MPC52xx_PSC_IMR_ORERR; out_be16(®s->isr_imr.imr, imr); @@ -271,36 +271,36 @@ int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd, } /** - * psc_i2s_shutdown: shutdown the data transfer on a stream + * psc_dma_shutdown: shutdown the data transfer on a stream * * Shutdown the PSC if there are no other substreams open. */ -void psc_i2s_shutdown(struct snd_pcm_substream *substream, +void psc_dma_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; - dev_dbg(psc_i2s->dev, "psc_i2s_shutdown(substream=%p)\n", substream); + dev_dbg(psc_dma->dev, "psc_dma_shutdown(substream=%p)\n", substream); /* * If this is the last active substream, disable the PSC and release * the IRQ. */ - if (!psc_i2s->playback.active && - !psc_i2s->capture.active) { + if (!psc_dma->playback.active && + !psc_dma->capture.active) { /* Disable all interrupts and reset the PSC */ - out_be16(&psc_i2s->psc_regs->isr_imr.imr, 0); - out_8(&psc_i2s->psc_regs->command, 3 << 4); /* reset tx */ - out_8(&psc_i2s->psc_regs->command, 2 << 4); /* reset rx */ - out_8(&psc_i2s->psc_regs->command, 1 << 4); /* reset mode */ - out_8(&psc_i2s->psc_regs->command, 4 << 4); /* reset error */ + out_be16(&psc_dma->psc_regs->isr_imr.imr, 0); + out_8(&psc_dma->psc_regs->command, 3 << 4); /* reset tx */ + out_8(&psc_dma->psc_regs->command, 2 << 4); /* reset rx */ + out_8(&psc_dma->psc_regs->command, 1 << 4); /* reset mode */ + out_8(&psc_dma->psc_regs->command, 4 << 4); /* reset error */ /* Release irqs */ - free_irq(psc_i2s->irq, psc_i2s); - free_irq(psc_i2s->capture.irq, &psc_i2s->capture); - free_irq(psc_i2s->playback.irq, &psc_i2s->playback); + free_irq(psc_dma->irq, psc_dma); + free_irq(psc_dma->capture.irq, &psc_dma->capture); + free_irq(psc_dma->playback.irq, &psc_dma->playback); } } @@ -312,7 +312,7 @@ void psc_i2s_shutdown(struct snd_pcm_substream *substream, * interaction with the attached codec */ -static const struct snd_pcm_hardware psc_i2s_pcm_hardware = { +static const struct snd_pcm_hardware psc_dma_pcm_hardware = { .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_BATCH, @@ -330,80 +330,80 @@ static const struct snd_pcm_hardware psc_i2s_pcm_hardware = { .fifo_size = 0, }; -static int psc_i2s_pcm_open(struct snd_pcm_substream *substream) +static int psc_dma_pcm_open(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; - struct psc_i2s_stream *s; + struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; + struct psc_dma_stream *s; - dev_dbg(psc_i2s->dev, "psc_i2s_pcm_open(substream=%p)\n", substream); + dev_dbg(psc_dma->dev, "psc_dma_pcm_open(substream=%p)\n", substream); if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) - s = &psc_i2s->capture; + s = &psc_dma->capture; else - s = &psc_i2s->playback; + s = &psc_dma->playback; - snd_soc_set_runtime_hwparams(substream, &psc_i2s_pcm_hardware); + snd_soc_set_runtime_hwparams(substream, &psc_dma_pcm_hardware); s->stream = substream; return 0; } -static int psc_i2s_pcm_close(struct snd_pcm_substream *substream) +static int psc_dma_pcm_close(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; - struct psc_i2s_stream *s; + struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; + struct psc_dma_stream *s; - dev_dbg(psc_i2s->dev, "psc_i2s_pcm_close(substream=%p)\n", substream); + dev_dbg(psc_dma->dev, "psc_dma_pcm_close(substream=%p)\n", substream); if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) - s = &psc_i2s->capture; + s = &psc_dma->capture; else - s = &psc_i2s->playback; + s = &psc_dma->playback; s->stream = NULL; return 0; } static snd_pcm_uframes_t -psc_i2s_pcm_pointer(struct snd_pcm_substream *substream) +psc_dma_pcm_pointer(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; - struct psc_i2s_stream *s; + struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; + struct psc_dma_stream *s; dma_addr_t count; if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) - s = &psc_i2s->capture; + s = &psc_dma->capture; else - s = &psc_i2s->playback; + s = &psc_dma->playback; count = s->period_current_pt - s->period_start; return bytes_to_frames(substream->runtime, count); } -static struct snd_pcm_ops psc_i2s_pcm_ops = { - .open = psc_i2s_pcm_open, - .close = psc_i2s_pcm_close, +static struct snd_pcm_ops psc_dma_pcm_ops = { + .open = psc_dma_pcm_open, + .close = psc_dma_pcm_close, .ioctl = snd_pcm_lib_ioctl, - .pointer = psc_i2s_pcm_pointer, + .pointer = psc_dma_pcm_pointer, }; -static u64 psc_i2s_pcm_dmamask = 0xffffffff; -static int psc_i2s_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, +static u64 psc_dma_pcm_dmamask = 0xffffffff; +static int psc_dma_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, struct snd_pcm *pcm) { struct snd_soc_pcm_runtime *rtd = pcm->private_data; - size_t size = psc_i2s_pcm_hardware.buffer_bytes_max; + size_t size = psc_dma_pcm_hardware.buffer_bytes_max; int rc = 0; - dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_new(card=%p, dai=%p, pcm=%p)\n", + dev_dbg(rtd->socdev->dev, "psc_dma_pcm_new(card=%p, dai=%p, pcm=%p)\n", card, dai, pcm); if (!card->dev->dma_mask) - card->dev->dma_mask = &psc_i2s_pcm_dmamask; + card->dev->dma_mask = &psc_dma_pcm_dmamask; if (!card->dev->coherent_dma_mask) card->dev->coherent_dma_mask = 0xffffffff; @@ -431,13 +431,13 @@ static int psc_i2s_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, return -ENOMEM; } -static void psc_i2s_pcm_free(struct snd_pcm *pcm) +static void psc_dma_pcm_free(struct snd_pcm *pcm) { struct snd_soc_pcm_runtime *rtd = pcm->private_data; struct snd_pcm_substream *substream; int stream; - dev_dbg(rtd->socdev->dev, "psc_i2s_pcm_free(pcm=%p)\n", pcm); + dev_dbg(rtd->socdev->dev, "psc_dma_pcm_free(pcm=%p)\n", pcm); for (stream = 0; stream < 2; stream++) { substream = pcm->streams[stream].substream; @@ -449,10 +449,10 @@ static void psc_i2s_pcm_free(struct snd_pcm *pcm) } } -struct snd_soc_platform psc_i2s_pcm_soc_platform = { +struct snd_soc_platform psc_dma_pcm_soc_platform = { .name = "mpc5200-psc-audio", - .pcm_ops = &psc_i2s_pcm_ops, - .pcm_new = &psc_i2s_pcm_new, - .pcm_free = &psc_i2s_pcm_free, + .pcm_ops = &psc_dma_pcm_ops, + .pcm_new = &psc_dma_pcm_new, + .pcm_free = &psc_dma_pcm_free, }; diff --git a/sound/soc/fsl/mpc5200_dma.h b/sound/soc/fsl/mpc5200_dma.h index 9a19e8a70c5..a33232c0c59 100644 --- a/sound/soc/fsl/mpc5200_dma.h +++ b/sound/soc/fsl/mpc5200_dma.h @@ -6,9 +6,9 @@ #define __SOUND_SOC_FSL_MPC5200_DMA_H__ /** - * psc_i2s_stream - Data specific to a single stream (playback or capture) + * psc_dma_stream - Data specific to a single stream (playback or capture) * @active: flag indicating if the stream is active - * @psc_i2s: pointer back to parent psc_i2s data structure + * @psc_dma: pointer back to parent psc_dma data structure * @bcom_task: bestcomm task structure * @irq: irq number for bestcomm task * @period_start: physical address of start of DMA region @@ -16,9 +16,9 @@ * @period_next_pt: physical address of next DMA buffer to enqueue * @period_bytes: size of DMA period in bytes */ -struct psc_i2s_stream { +struct psc_dma_stream { int active; - struct psc_i2s *psc_i2s; + struct psc_dma *psc_dma; struct bcom_task *bcom_task; int irq; struct snd_pcm_substream *stream; @@ -30,7 +30,7 @@ struct psc_i2s_stream { }; /** - * psc_i2s - Private driver data + * psc_dma - Private driver data * @name: short name for this device ("PSC0", "PSC1", etc) * @psc_regs: pointer to the PSC's registers * @fifo_regs: pointer to the PSC's FIFO registers @@ -42,7 +42,7 @@ struct psc_i2s_stream { * @playback: Playback stream context data * @capture: Capture stream context data */ -struct psc_i2s { +struct psc_dma { char name[32]; struct mpc52xx_psc __iomem *psc_regs; struct mpc52xx_psc_fifo __iomem *fifo_regs; @@ -53,8 +53,8 @@ struct psc_i2s { u32 sicr; /* per-stream data */ - struct psc_i2s_stream playback; - struct psc_i2s_stream capture; + struct psc_dma_stream playback; + struct psc_dma_stream capture; /* Statistics */ struct { @@ -64,18 +64,18 @@ struct psc_i2s { }; -int psc_i2s_startup(struct snd_pcm_substream *substream, +int psc_dma_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai); -int psc_i2s_hw_free(struct snd_pcm_substream *substream, +int psc_dma_hw_free(struct snd_pcm_substream *substream, struct snd_soc_dai *dai); -void psc_i2s_shutdown(struct snd_pcm_substream *substream, +void psc_dma_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai); -int psc_i2s_trigger(struct snd_pcm_substream *substream, int cmd, +int psc_dma_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai); -extern struct snd_soc_platform psc_i2s_pcm_soc_platform; +extern struct snd_soc_platform psc_dma_pcm_soc_platform; #endif /* __SOUND_SOC_FSL_MPC5200_DMA_H__ */ diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c index 8974b53eec9..12a7917b57b 100644 --- a/sound/soc/fsl/mpc5200_psc_i2s.c +++ b/sound/soc/fsl/mpc5200_psc_i2s.c @@ -54,10 +54,10 @@ static int psc_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_i2s *psc_i2s = rtd->dai->cpu_dai->private_data; + struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; u32 mode; - dev_dbg(psc_i2s->dev, "%s(substream=%p) p_size=%i p_bytes=%i" + dev_dbg(psc_dma->dev, "%s(substream=%p) p_size=%i p_bytes=%i" " periods=%i buffer_size=%i buffer_bytes=%i\n", __func__, substream, params_period_size(params), params_period_bytes(params), params_periods(params), @@ -77,10 +77,10 @@ static int psc_i2s_hw_params(struct snd_pcm_substream *substream, mode = MPC52xx_PSC_SICR_SIM_CODEC_32; break; default: - dev_dbg(psc_i2s->dev, "invalid format\n"); + dev_dbg(psc_dma->dev, "invalid format\n"); return -EINVAL; } - out_be32(&psc_i2s->psc_regs->sicr, psc_i2s->sicr | mode); + out_be32(&psc_dma->psc_regs->sicr, psc_dma->sicr | mode); snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); @@ -104,8 +104,8 @@ static int psc_i2s_hw_params(struct snd_pcm_substream *substream, static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, int clk_id, unsigned int freq, int dir) { - struct psc_i2s *psc_i2s = cpu_dai->private_data; - dev_dbg(psc_i2s->dev, "psc_i2s_set_sysclk(cpu_dai=%p, dir=%i)\n", + struct psc_dma *psc_dma = cpu_dai->private_data; + dev_dbg(psc_dma->dev, "psc_i2s_set_sysclk(cpu_dai=%p, dir=%i)\n", cpu_dai, dir); return (dir == SND_SOC_CLOCK_IN) ? 0 : -EINVAL; } @@ -123,8 +123,8 @@ static int psc_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, */ static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format) { - struct psc_i2s *psc_i2s = cpu_dai->private_data; - dev_dbg(psc_i2s->dev, "psc_i2s_set_fmt(cpu_dai=%p, format=%i)\n", + struct psc_dma *psc_dma = cpu_dai->private_data; + dev_dbg(psc_dma->dev, "psc_i2s_set_fmt(cpu_dai=%p, format=%i)\n", cpu_dai, format); return (format == SND_SOC_DAIFMT_I2S) ? 0 : -EINVAL; } @@ -140,11 +140,11 @@ static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format) * psc_i2s_dai_template: template CPU Digital Audio Interface */ static struct snd_soc_dai_ops psc_i2s_dai_ops = { - .startup = psc_i2s_startup, + .startup = psc_dma_startup, .hw_params = psc_i2s_hw_params, - .hw_free = psc_i2s_hw_free, - .shutdown = psc_i2s_shutdown, - .trigger = psc_i2s_trigger, + .hw_free = psc_dma_hw_free, + .shutdown = psc_dma_shutdown, + .trigger = psc_dma_trigger, .set_sysclk = psc_i2s_set_sysclk, .set_fmt = psc_i2s_set_fmt, }; @@ -172,24 +172,24 @@ static struct snd_soc_dai psc_i2s_dai_template = { static ssize_t psc_i2s_status_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct psc_i2s *psc_i2s = dev_get_drvdata(dev); + struct psc_dma *psc_dma = dev_get_drvdata(dev); return sprintf(buf, "status=%.4x sicr=%.8x rfnum=%i rfstat=0x%.4x " "tfnum=%i tfstat=0x%.4x\n", - in_be16(&psc_i2s->psc_regs->sr_csr.status), - in_be32(&psc_i2s->psc_regs->sicr), - in_be16(&psc_i2s->fifo_regs->rfnum) & 0x1ff, - in_be16(&psc_i2s->fifo_regs->rfstat), - in_be16(&psc_i2s->fifo_regs->tfnum) & 0x1ff, - in_be16(&psc_i2s->fifo_regs->tfstat)); + in_be16(&psc_dma->psc_regs->sr_csr.status), + in_be32(&psc_dma->psc_regs->sicr), + in_be16(&psc_dma->fifo_regs->rfnum) & 0x1ff, + in_be16(&psc_dma->fifo_regs->rfstat), + in_be16(&psc_dma->fifo_regs->tfnum) & 0x1ff, + in_be16(&psc_dma->fifo_regs->tfstat)); } -static int *psc_i2s_get_stat_attr(struct psc_i2s *psc_i2s, const char *name) +static int *psc_i2s_get_stat_attr(struct psc_dma *psc_dma, const char *name) { if (strcmp(name, "playback_underrun") == 0) - return &psc_i2s->stats.underrun_count; + return &psc_dma->stats.underrun_count; if (strcmp(name, "capture_overrun") == 0) - return &psc_i2s->stats.overrun_count; + return &psc_dma->stats.overrun_count; return NULL; } @@ -197,10 +197,10 @@ static int *psc_i2s_get_stat_attr(struct psc_i2s *psc_i2s, const char *name) static ssize_t psc_i2s_stat_show(struct device *dev, struct device_attribute *attr, char *buf) { - struct psc_i2s *psc_i2s = dev_get_drvdata(dev); + struct psc_dma *psc_dma = dev_get_drvdata(dev); int *attrib; - attrib = psc_i2s_get_stat_attr(psc_i2s, attr->attr.name); + attrib = psc_i2s_get_stat_attr(psc_dma, attr->attr.name); if (!attrib) return 0; @@ -212,10 +212,10 @@ static ssize_t psc_i2s_stat_store(struct device *dev, const char *buf, size_t count) { - struct psc_i2s *psc_i2s = dev_get_drvdata(dev); + struct psc_dma *psc_dma = dev_get_drvdata(dev); int *attrib; - attrib = psc_i2s_get_stat_attr(psc_i2s, attr->attr.name); + attrib = psc_i2s_get_stat_attr(psc_dma, attr->attr.name); if (!attrib) return 0; @@ -238,7 +238,7 @@ static int __devinit psc_i2s_of_probe(struct of_device *op, const struct of_device_id *match) { phys_addr_t fifo; - struct psc_i2s *psc_i2s; + struct psc_dma *psc_dma; struct resource res; int size, psc_id, irq, rc; const __be32 *prop; @@ -265,56 +265,56 @@ static int __devinit psc_i2s_of_probe(struct of_device *op, } /* Allocate and initialize the driver private data */ - psc_i2s = kzalloc(sizeof *psc_i2s, GFP_KERNEL); - if (!psc_i2s) { + psc_dma = kzalloc(sizeof *psc_dma, GFP_KERNEL); + if (!psc_dma) { iounmap(regs); return -ENOMEM; } - spin_lock_init(&psc_i2s->lock); - psc_i2s->irq = irq; - psc_i2s->psc_regs = regs; - psc_i2s->fifo_regs = regs + sizeof *psc_i2s->psc_regs; - psc_i2s->dev = &op->dev; - psc_i2s->playback.psc_i2s = psc_i2s; - psc_i2s->capture.psc_i2s = psc_i2s; - snprintf(psc_i2s->name, sizeof psc_i2s->name, "PSC%u", psc_id+1); + spin_lock_init(&psc_dma->lock); + psc_dma->irq = irq; + psc_dma->psc_regs = regs; + psc_dma->fifo_regs = regs + sizeof *psc_dma->psc_regs; + psc_dma->dev = &op->dev; + psc_dma->playback.psc_dma = psc_dma; + psc_dma->capture.psc_dma = psc_dma; + snprintf(psc_dma->name, sizeof psc_dma->name, "PSC%u", psc_id+1); /* Fill out the CPU DAI structure */ - memcpy(&psc_i2s->dai, &psc_i2s_dai_template, sizeof psc_i2s->dai); - psc_i2s->dai.private_data = psc_i2s; - psc_i2s->dai.name = psc_i2s->name; - psc_i2s->dai.id = psc_id; + memcpy(&psc_dma->dai, &psc_i2s_dai_template, sizeof psc_dma->dai); + psc_dma->dai.private_data = psc_dma; + psc_dma->dai.name = psc_dma->name; + psc_dma->dai.id = psc_id; /* Find the address of the fifo data registers and setup the * DMA tasks */ fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32); - psc_i2s->capture.bcom_task = + psc_dma->capture.bcom_task = bcom_psc_gen_bd_rx_init(psc_id, 10, fifo, 512); - psc_i2s->playback.bcom_task = + psc_dma->playback.bcom_task = bcom_psc_gen_bd_tx_init(psc_id, 10, fifo); - if (!psc_i2s->capture.bcom_task || - !psc_i2s->playback.bcom_task) { + if (!psc_dma->capture.bcom_task || + !psc_dma->playback.bcom_task) { dev_err(&op->dev, "Could not allocate bestcomm tasks\n"); iounmap(regs); - kfree(psc_i2s); + kfree(psc_dma); return -ENODEV; } /* Disable all interrupts and reset the PSC */ - out_be16(&psc_i2s->psc_regs->isr_imr.imr, 0); - out_8(&psc_i2s->psc_regs->command, 3 << 4); /* reset transmitter */ - out_8(&psc_i2s->psc_regs->command, 2 << 4); /* reset receiver */ - out_8(&psc_i2s->psc_regs->command, 1 << 4); /* reset mode */ - out_8(&psc_i2s->psc_regs->command, 4 << 4); /* reset error */ + out_be16(&psc_dma->psc_regs->isr_imr.imr, 0); + out_8(&psc_dma->psc_regs->command, 3 << 4); /* reset transmitter */ + out_8(&psc_dma->psc_regs->command, 2 << 4); /* reset receiver */ + out_8(&psc_dma->psc_regs->command, 1 << 4); /* reset mode */ + out_8(&psc_dma->psc_regs->command, 4 << 4); /* reset error */ /* Configure the serial interface mode; defaulting to CODEC8 mode */ - psc_i2s->sicr = MPC52xx_PSC_SICR_DTS1 | MPC52xx_PSC_SICR_I2S | + psc_dma->sicr = MPC52xx_PSC_SICR_DTS1 | MPC52xx_PSC_SICR_I2S | MPC52xx_PSC_SICR_CLKPOL; if (of_get_property(op->node, "fsl,cellslave", NULL)) - psc_i2s->sicr |= MPC52xx_PSC_SICR_CELLSLAVE | + psc_dma->sicr |= MPC52xx_PSC_SICR_CELLSLAVE | MPC52xx_PSC_SICR_GENCLK; - out_be32(&psc_i2s->psc_regs->sicr, - psc_i2s->sicr | MPC52xx_PSC_SICR_SIM_CODEC_8); + out_be32(&psc_dma->psc_regs->sicr, + psc_dma->sicr | MPC52xx_PSC_SICR_SIM_CODEC_8); /* Check for the codec handle. If it is not present then we * are done */ @@ -325,54 +325,54 @@ static int __devinit psc_i2s_of_probe(struct of_device *op, * First write: RxRdy (FIFO Alarm) generates rx FIFO irq * Second write: register Normal mode for non loopback */ - out_8(&psc_i2s->psc_regs->mode, 0); - out_8(&psc_i2s->psc_regs->mode, 0); + out_8(&psc_dma->psc_regs->mode, 0); + out_8(&psc_dma->psc_regs->mode, 0); /* Set the TX and RX fifo alarm thresholds */ - out_be16(&psc_i2s->fifo_regs->rfalarm, 0x100); - out_8(&psc_i2s->fifo_regs->rfcntl, 0x4); - out_be16(&psc_i2s->fifo_regs->tfalarm, 0x100); - out_8(&psc_i2s->fifo_regs->tfcntl, 0x7); + out_be16(&psc_dma->fifo_regs->rfalarm, 0x100); + out_8(&psc_dma->fifo_regs->rfcntl, 0x4); + out_be16(&psc_dma->fifo_regs->tfalarm, 0x100); + out_8(&psc_dma->fifo_regs->tfcntl, 0x7); /* Lookup the IRQ numbers */ - psc_i2s->playback.irq = - bcom_get_task_irq(psc_i2s->playback.bcom_task); - psc_i2s->capture.irq = - bcom_get_task_irq(psc_i2s->capture.bcom_task); + psc_dma->playback.irq = + bcom_get_task_irq(psc_dma->playback.bcom_task); + psc_dma->capture.irq = + bcom_get_task_irq(psc_dma->capture.bcom_task); /* Save what we've done so it can be found again later */ - dev_set_drvdata(&op->dev, psc_i2s); + dev_set_drvdata(&op->dev, psc_dma); /* Register the SYSFS files */ - rc = device_create_file(psc_i2s->dev, &dev_attr_status); - rc |= device_create_file(psc_i2s->dev, &dev_attr_capture_overrun); - rc |= device_create_file(psc_i2s->dev, &dev_attr_playback_underrun); + rc = device_create_file(psc_dma->dev, &dev_attr_status); + rc |= device_create_file(psc_dma->dev, &dev_attr_capture_overrun); + rc |= device_create_file(psc_dma->dev, &dev_attr_playback_underrun); if (rc) - dev_info(psc_i2s->dev, "error creating sysfs files\n"); + dev_info(psc_dma->dev, "error creating sysfs files\n"); - snd_soc_register_platform(&psc_i2s_pcm_soc_platform); + snd_soc_register_platform(&psc_dma_pcm_soc_platform); /* Tell the ASoC OF helpers about it */ - of_snd_soc_register_platform(&psc_i2s_pcm_soc_platform, op->node, - &psc_i2s->dai); + of_snd_soc_register_platform(&psc_dma_pcm_soc_platform, op->node, + &psc_dma->dai); return 0; } static int __devexit psc_i2s_of_remove(struct of_device *op) { - struct psc_i2s *psc_i2s = dev_get_drvdata(&op->dev); + struct psc_dma *psc_dma = dev_get_drvdata(&op->dev); dev_dbg(&op->dev, "psc_i2s_remove()\n"); - snd_soc_unregister_platform(&psc_i2s_pcm_soc_platform); + snd_soc_unregister_platform(&psc_dma_pcm_soc_platform); - bcom_gen_bd_rx_release(psc_i2s->capture.bcom_task); - bcom_gen_bd_tx_release(psc_i2s->playback.bcom_task); + bcom_gen_bd_rx_release(psc_dma->capture.bcom_task); + bcom_gen_bd_tx_release(psc_dma->playback.bcom_task); - iounmap(psc_i2s->psc_regs); - iounmap(psc_i2s->fifo_regs); - kfree(psc_i2s); + iounmap(psc_dma->psc_regs); + iounmap(psc_dma->fifo_regs); + kfree(psc_dma); dev_set_drvdata(&op->dev, NULL); return 0; -- cgit v1.2.3 From 5a2e9a48b1d6de35ae5efea35d117133c3eb30f2 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 25 May 2009 11:12:11 +0300 Subject: ASoC: TWL4030: Handsfree pop removal redesign Move the HandsfreeL/R (IHFL/R) pop removal code from the DAPM_MUX_E to a more appropriate DAPM_PGA_E widget. Also fix the power-up sequence to match with the TRM. The power-down sequence is not described in the TRM, so do it in a way, which seams like the correct sequence. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 78 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 19 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 9197fdd0a29..17ddcb26513 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -546,27 +546,61 @@ static int micpath_event(struct snd_soc_dapm_widget *w, return 0; } -static int handsfree_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event) +static void handsfree_ramp(struct snd_soc_codec *codec, int reg, int ramp) { - struct soc_enum *e = (struct soc_enum *)w->kcontrols->private_value; unsigned char hs_ctl; - hs_ctl = twl4030_read_reg_cache(w->codec, e->reg); + hs_ctl = twl4030_read_reg_cache(codec, reg); - if (hs_ctl & TWL4030_HF_CTL_REF_EN) { + if (ramp) { + /* HF ramp-up */ + hs_ctl |= TWL4030_HF_CTL_REF_EN; + twl4030_write(codec, reg, hs_ctl); + udelay(10); hs_ctl |= TWL4030_HF_CTL_RAMP_EN; - twl4030_write(w->codec, e->reg, hs_ctl); + twl4030_write(codec, reg, hs_ctl); + udelay(40); hs_ctl |= TWL4030_HF_CTL_LOOP_EN; - twl4030_write(w->codec, e->reg, hs_ctl); hs_ctl |= TWL4030_HF_CTL_HB_EN; - twl4030_write(w->codec, e->reg, hs_ctl); + twl4030_write(codec, reg, hs_ctl); } else { - hs_ctl &= ~(TWL4030_HF_CTL_RAMP_EN | TWL4030_HF_CTL_LOOP_EN - | TWL4030_HF_CTL_HB_EN); - twl4030_write(w->codec, e->reg, hs_ctl); + /* HF ramp-down */ + hs_ctl &= ~TWL4030_HF_CTL_LOOP_EN; + hs_ctl &= ~TWL4030_HF_CTL_HB_EN; + twl4030_write(codec, reg, hs_ctl); + hs_ctl &= ~TWL4030_HF_CTL_RAMP_EN; + twl4030_write(codec, reg, hs_ctl); + udelay(40); + hs_ctl &= ~TWL4030_HF_CTL_REF_EN; + twl4030_write(codec, reg, hs_ctl); } +} +static int handsfreelpga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + handsfree_ramp(w->codec, TWL4030_REG_HFL_CTL, 1); + break; + case SND_SOC_DAPM_POST_PMD: + handsfree_ramp(w->codec, TWL4030_REG_HFL_CTL, 0); + break; + } + return 0; +} + +static int handsfreerpga_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + switch (event) { + case SND_SOC_DAPM_POST_PMU: + handsfree_ramp(w->codec, TWL4030_REG_HFR_CTL, 1); + break; + case SND_SOC_DAPM_POST_PMD: + handsfree_ramp(w->codec, TWL4030_REG_HFR_CTL, 0); + break; + } return 0; } @@ -1190,12 +1224,16 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { /* Output MUX controls */ /* HandsfreeL/R */ - SND_SOC_DAPM_MUX_E("HandsfreeL Mux", TWL4030_REG_HFL_CTL, 5, 0, - &twl4030_dapm_handsfreel_control, handsfree_event, - SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), - SND_SOC_DAPM_MUX_E("HandsfreeR Mux", TWL4030_REG_HFR_CTL, 5, 0, - &twl4030_dapm_handsfreer_control, handsfree_event, - SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("HandsfreeL Mux", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_handsfreel_control), + SND_SOC_DAPM_PGA_E("HandsfreeL PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, handsfreelpga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("HandsfreeR Mux", SND_SOC_NOPM, 5, 0, + &twl4030_dapm_handsfreer_control), + SND_SOC_DAPM_PGA_E("HandsfreeR PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, handsfreerpga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), /* Vibra */ SND_SOC_DAPM_MUX("Vibra Mux", TWL4030_REG_VIBRA_CTL, 0, 0, &twl4030_dapm_vibra_control), @@ -1303,11 +1341,13 @@ static const struct snd_soc_dapm_route intercon[] = { {"HandsfreeL Mux", "AudioL1", "Analog L1 Playback Mixer"}, {"HandsfreeL Mux", "AudioL2", "Analog L2 Playback Mixer"}, {"HandsfreeL Mux", "AudioR2", "Analog R2 Playback Mixer"}, + {"HandsfreeL PGA", NULL, "HandsfreeL Mux"}, /* HandsfreeR */ {"HandsfreeR Mux", "Voice", "Analog Voice Playback Mixer"}, {"HandsfreeR Mux", "AudioR1", "Analog R1 Playback Mixer"}, {"HandsfreeR Mux", "AudioR2", "Analog R2 Playback Mixer"}, {"HandsfreeR Mux", "AudioL2", "Analog L2 Playback Mixer"}, + {"HandsfreeR PGA", NULL, "HandsfreeR Mux"}, /* Vibra */ {"Vibra Mux", "AudioL1", "DAC Left1"}, {"Vibra Mux", "AudioR1", "DAC Right1"}, @@ -1324,8 +1364,8 @@ static const struct snd_soc_dapm_route intercon[] = { {"HSOR", NULL, "HeadsetR PGA"}, {"CARKITL", NULL, "CarkitL Mixer"}, {"CARKITR", NULL, "CarkitR Mixer"}, - {"HFL", NULL, "HandsfreeL Mux"}, - {"HFR", NULL, "HandsfreeR Mux"}, + {"HFL", NULL, "HandsfreeL PGA"}, + {"HFR", NULL, "HandsfreeR PGA"}, {"Vibra Route", "Audio", "Vibra Mux"}, {"VIBRA", NULL, "Vibra Route"}, -- cgit v1.2.3 From f3b5d3002d5b43d277dedc1e044d02f2a40a43c5 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 25 May 2009 11:12:12 +0300 Subject: ASoC: TWL4030: Add shadow register Shadow, non HW register for dealing with the HandsfreeL/R muting. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 7 ++++++- sound/soc/codecs/twl4030.h | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 17ddcb26513..989446dabcd 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -115,6 +115,7 @@ static const u8 twl4030_reg[TWL4030_CACHEREGNUM] = { 0x00, /* REG_VIBRA_PWM_SET (0x47) */ 0x00, /* REG_ANAMIC_GAIN (0x48) */ 0x00, /* REG_MISC_SET_2 (0x49) */ + 0x00, /* REG_SW_SHADOW (0x4A) - Shadow, non HW register */ }; /* codec private data */ @@ -172,7 +173,11 @@ static int twl4030_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { twl4030_write_reg_cache(codec, reg, value); - return twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, value, reg); + if (likely(reg < TWL4030_REG_SW_SHADOW)) + return twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, value, + reg); + else + return 0; } static void twl4030_codec_enable(struct snd_soc_codec *codec, int enable) diff --git a/sound/soc/codecs/twl4030.h b/sound/soc/codecs/twl4030.h index 48326e2bd9d..fe5f395d9e4 100644 --- a/sound/soc/codecs/twl4030.h +++ b/sound/soc/codecs/twl4030.h @@ -92,8 +92,9 @@ #define TWL4030_REG_VIBRA_PWM_SET 0x47 #define TWL4030_REG_ANAMIC_GAIN 0x48 #define TWL4030_REG_MISC_SET_2 0x49 +#define TWL4030_REG_SW_SHADOW 0x4A -#define TWL4030_CACHEREGNUM (TWL4030_REG_MISC_SET_2 + 1) +#define TWL4030_CACHEREGNUM (TWL4030_REG_SW_SHADOW + 1) /* Bitfield Definitions */ @@ -260,6 +261,10 @@ #define TWL4030_SMOOTH_ANAVOL_EN 0x02 #define TWL4030_DIGMIC_LR_SWAP_EN 0x01 +/* TWL4030_REG_SW_SHADOW (0x4A) Fields */ +#define TWL4030_HFL_EN 0x01 +#define TWL4030_HFR_EN 0x02 + #define TWL4030_DAI_HIFI 0 #define TWL4030_DAI_VOICE 1 -- cgit v1.2.3 From 0f89bdcac61536c5cb2a095a514657019573afb4 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 25 May 2009 11:12:13 +0300 Subject: ASoC: TWL4030: HandsfreeL/R mute DAPM switch Add DAPM switch for HeadsetL/R mute. Since all bits are are needed for the HFL/R pop removal to work the switch is using the SW_SHADOW no HW register for the HandsfreeL/R mute. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 989446dabcd..63ebd176fbe 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -395,6 +395,10 @@ static const struct soc_enum twl4030_handsfreel_enum = static const struct snd_kcontrol_new twl4030_dapm_handsfreel_control = SOC_DAPM_ENUM("Route", twl4030_handsfreel_enum); +/* Handsfree Left virtual mute */ +static const struct snd_kcontrol_new twl4030_dapm_handsfreelmute_control = + SOC_DAPM_SINGLE("Switch", TWL4030_REG_SW_SHADOW, 0, 1, 0); + /* Handsfree Right */ static const char *twl4030_handsfreer_texts[] = {"Voice", "AudioR1", "AudioR2", "AudioL2"}; @@ -407,6 +411,10 @@ static const struct soc_enum twl4030_handsfreer_enum = static const struct snd_kcontrol_new twl4030_dapm_handsfreer_control = SOC_DAPM_ENUM("Route", twl4030_handsfreer_enum); +/* Handsfree Right virtual mute */ +static const struct snd_kcontrol_new twl4030_dapm_handsfreermute_control = + SOC_DAPM_SINGLE("Switch", TWL4030_REG_SW_SHADOW, 1, 1, 0); + /* Vibra */ /* Vibra audio path selection */ static const char *twl4030_vibra_texts[] = @@ -1231,11 +1239,15 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { /* HandsfreeL/R */ SND_SOC_DAPM_MUX("HandsfreeL Mux", SND_SOC_NOPM, 0, 0, &twl4030_dapm_handsfreel_control), + SND_SOC_DAPM_SWITCH("HandsfreeL Switch", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_handsfreelmute_control), SND_SOC_DAPM_PGA_E("HandsfreeL PGA", SND_SOC_NOPM, 0, 0, NULL, 0, handsfreelpga_event, SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MUX("HandsfreeR Mux", SND_SOC_NOPM, 5, 0, &twl4030_dapm_handsfreer_control), + SND_SOC_DAPM_SWITCH("HandsfreeR Switch", SND_SOC_NOPM, 0, 0, + &twl4030_dapm_handsfreermute_control), SND_SOC_DAPM_PGA_E("HandsfreeR PGA", SND_SOC_NOPM, 0, 0, NULL, 0, handsfreerpga_event, SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), @@ -1346,13 +1358,15 @@ static const struct snd_soc_dapm_route intercon[] = { {"HandsfreeL Mux", "AudioL1", "Analog L1 Playback Mixer"}, {"HandsfreeL Mux", "AudioL2", "Analog L2 Playback Mixer"}, {"HandsfreeL Mux", "AudioR2", "Analog R2 Playback Mixer"}, - {"HandsfreeL PGA", NULL, "HandsfreeL Mux"}, + {"HandsfreeL Switch", "Switch", "HandsfreeL Mux"}, + {"HandsfreeL PGA", NULL, "HandsfreeL Switch"}, /* HandsfreeR */ {"HandsfreeR Mux", "Voice", "Analog Voice Playback Mixer"}, {"HandsfreeR Mux", "AudioR1", "Analog R1 Playback Mixer"}, {"HandsfreeR Mux", "AudioR2", "Analog R2 Playback Mixer"}, {"HandsfreeR Mux", "AudioL2", "Analog L2 Playback Mixer"}, - {"HandsfreeR PGA", NULL, "HandsfreeR Mux"}, + {"HandsfreeR Switch", "Switch", "HandsfreeR Mux"}, + {"HandsfreeR PGA", NULL, "HandsfreeR Switch"}, /* Vibra */ {"Vibra Mux", "AudioL1", "DAC Left1"}, {"Vibra Mux", "AudioR1", "DAC Right1"}, -- cgit v1.2.3 From dbcc34756234596993a3b1304636f032e66d401f Mon Sep 17 00:00:00 2001 From: Jon Smirl Date: Tue, 26 May 2009 08:34:08 -0400 Subject: ASoC: Main rewite of the mpc5200 audio DMA code Rewrite the mpc5200 audio DMA code to support both I2S and AC97. Signed-off-by: Jon Smirl Acked-by: Grant Likely Signed-off-by: Mark Brown --- sound/soc/fsl/Kconfig | 1 - sound/soc/fsl/mpc5200_dma.c | 442 +++++++++++++++++++++++++--------------- sound/soc/fsl/mpc5200_dma.h | 33 ++- sound/soc/fsl/mpc5200_psc_i2s.c | 247 ++++------------------ sound/soc/fsl/mpc5200_psc_i2s.h | 12 ++ 5 files changed, 344 insertions(+), 391 deletions(-) create mode 100644 sound/soc/fsl/mpc5200_psc_i2s.h (limited to 'sound/soc') diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index dc79bdf3369..1918c78b858 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -25,7 +25,6 @@ config SND_SOC_MPC8610_HPCD config SND_SOC_MPC5200_I2S tristate "Freescale MPC5200 PSC in I2S mode driver" depends on PPC_MPC52xx && PPC_BESTCOMM - select SND_SOC_OF_SIMPLE select SND_MPC52xx_DMA select PPC_BESTCOMM_GEN_BD help diff --git a/sound/soc/fsl/mpc5200_dma.c b/sound/soc/fsl/mpc5200_dma.c index 685039213f0..efec33a1c5b 100644 --- a/sound/soc/fsl/mpc5200_dma.c +++ b/sound/soc/fsl/mpc5200_dma.c @@ -3,23 +3,13 @@ * ALSA SoC Platform driver * * Copyright (C) 2008 Secret Lab Technologies Ltd. + * Copyright (C) 2009 Jon Smirl, Digispeaker */ -#include #include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include #include -#include #include #include @@ -27,10 +17,6 @@ #include "mpc5200_dma.h" -MODULE_AUTHOR("Grant Likely "); -MODULE_DESCRIPTION("Freescale MPC5200 PSC in DMA mode ASoC Driver"); -MODULE_LICENSE("GPL"); - /* * Interrupt handlers */ @@ -50,7 +36,7 @@ static irqreturn_t psc_dma_status_irq(int irq, void *_psc_dma) if (psc_dma->capture.active && (isr & MPC52xx_PSC_IMR_ORERR)) psc_dma->stats.overrun_count++; - out_8(®s->command, 4 << 4); /* reset the error status */ + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); return IRQ_HANDLED; } @@ -81,21 +67,36 @@ static void psc_dma_bcom_enqueue_next_buffer(struct psc_dma_stream *s) s->period_next_pt = s->period_start; } +static void psc_dma_bcom_enqueue_tx(struct psc_dma_stream *s) +{ + while (s->appl_ptr < s->runtime->control->appl_ptr) { + + if (bcom_queue_full(s->bcom_task)) + return; + + s->appl_ptr += s->period_size; + + psc_dma_bcom_enqueue_next_buffer(s); + } +} + /* Bestcomm DMA irq handler */ -static irqreturn_t psc_dma_bcom_irq(int irq, void *_psc_dma_stream) +static irqreturn_t psc_dma_bcom_irq_tx(int irq, void *_psc_dma_stream) { struct psc_dma_stream *s = _psc_dma_stream; + spin_lock(&s->psc_dma->lock); /* For each finished period, dequeue the completed period buffer * and enqueue a new one in it's place. */ while (bcom_buffer_done(s->bcom_task)) { bcom_retrieve_buffer(s->bcom_task, NULL, NULL); + s->period_current_pt += s->period_bytes; if (s->period_current_pt >= s->period_end) s->period_current_pt = s->period_start; - psc_dma_bcom_enqueue_next_buffer(s); - bcom_enable(s->bcom_task); } + psc_dma_bcom_enqueue_tx(s); + spin_unlock(&s->psc_dma->lock); /* If the stream is active, then also inform the PCM middle layer * of the period finished event. */ @@ -105,49 +106,33 @@ static irqreturn_t psc_dma_bcom_irq(int irq, void *_psc_dma_stream) return IRQ_HANDLED; } -/** - * psc_dma_startup: create a new substream - * - * This is the first function called when a stream is opened. - * - * If this is the first stream open, then grab the IRQ and program most of - * the PSC registers. - */ -int psc_dma_startup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static irqreturn_t psc_dma_bcom_irq_rx(int irq, void *_psc_dma_stream) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; - int rc; + struct psc_dma_stream *s = _psc_dma_stream; - dev_dbg(psc_dma->dev, "psc_dma_startup(substream=%p)\n", substream); + spin_lock(&s->psc_dma->lock); + /* For each finished period, dequeue the completed period buffer + * and enqueue a new one in it's place. */ + while (bcom_buffer_done(s->bcom_task)) { + bcom_retrieve_buffer(s->bcom_task, NULL, NULL); - if (!psc_dma->playback.active && - !psc_dma->capture.active) { - /* Setup the IRQs */ - rc = request_irq(psc_dma->irq, &psc_dma_status_irq, IRQF_SHARED, - "psc-dma-status", psc_dma); - rc |= request_irq(psc_dma->capture.irq, - &psc_dma_bcom_irq, IRQF_SHARED, - "psc-dma-capture", &psc_dma->capture); - rc |= request_irq(psc_dma->playback.irq, - &psc_dma_bcom_irq, IRQF_SHARED, - "psc-dma-playback", &psc_dma->playback); - if (rc) { - free_irq(psc_dma->irq, psc_dma); - free_irq(psc_dma->capture.irq, - &psc_dma->capture); - free_irq(psc_dma->playback.irq, - &psc_dma->playback); - return -ENODEV; - } + s->period_current_pt += s->period_bytes; + if (s->period_current_pt >= s->period_end) + s->period_current_pt = s->period_start; + + psc_dma_bcom_enqueue_next_buffer(s); } + spin_unlock(&s->psc_dma->lock); - return 0; + /* If the stream is active, then also inform the PCM middle layer + * of the period finished event. */ + if (s->active) + snd_pcm_period_elapsed(s->stream); + + return IRQ_HANDLED; } -int psc_dma_hw_free(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) +static int psc_dma_hw_free(struct snd_pcm_substream *substream) { snd_pcm_set_runtime_buffer(substream, NULL); return 0; @@ -159,8 +144,7 @@ int psc_dma_hw_free(struct snd_pcm_substream *substream, * This function is called by ALSA to start, stop, pause, and resume the DMA * transfer of data. */ -int psc_dma_trigger(struct snd_pcm_substream *substream, int cmd, - struct snd_soc_dai *dai) +static int psc_dma_trigger(struct snd_pcm_substream *substream, int cmd) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; @@ -168,8 +152,8 @@ int psc_dma_trigger(struct snd_pcm_substream *substream, int cmd, struct psc_dma_stream *s; struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; u16 imr; - u8 psc_cmd; unsigned long flags; + int i; if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) s = &psc_dma->capture; @@ -189,68 +173,48 @@ int psc_dma_trigger(struct snd_pcm_substream *substream, int cmd, (s->period_bytes * runtime->periods); s->period_next_pt = s->period_start; s->period_current_pt = s->period_start; + s->period_size = runtime->period_size; s->active = 1; - /* First; reset everything */ + /* track appl_ptr so that we have a better chance of detecting + * end of stream and not over running it. + */ + s->runtime = runtime; + s->appl_ptr = s->runtime->control->appl_ptr - + (runtime->period_size * runtime->periods); + + /* Fill up the bestcomm bd queue and enable DMA. + * This will begin filling the PSC's fifo. + */ + spin_lock_irqsave(&psc_dma->lock, flags); + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { - out_8(®s->command, MPC52xx_PSC_RST_RX); - out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + bcom_gen_bd_rx_reset(s->bcom_task); + for (i = 0; i < runtime->periods; i++) + if (!bcom_queue_full(s->bcom_task)) + psc_dma_bcom_enqueue_next_buffer(s); } else { - out_8(®s->command, MPC52xx_PSC_RST_TX); - out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + bcom_gen_bd_tx_reset(s->bcom_task); + psc_dma_bcom_enqueue_tx(s); } - /* Next, fill up the bestcomm bd queue and enable DMA. - * This will begin filling the PSC's fifo. */ - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) - bcom_gen_bd_rx_reset(s->bcom_task); - else - bcom_gen_bd_tx_reset(s->bcom_task); - while (!bcom_queue_full(s->bcom_task)) - psc_dma_bcom_enqueue_next_buffer(s); bcom_enable(s->bcom_task); - - /* Due to errata in the dma mode; need to line up enabling - * the transmitter with a transition on the frame sync - * line */ - - spin_lock_irqsave(&psc_dma->lock, flags); - /* first make sure it is low */ - while ((in_8(®s->ipcr_acr.ipcr) & 0x80) != 0) - ; - /* then wait for the transition to high */ - while ((in_8(®s->ipcr_acr.ipcr) & 0x80) == 0) - ; - /* Finally, enable the PSC. - * Receiver must always be enabled; even when we only want - * transmit. (see 15.3.2.3 of MPC5200B User's Guide) */ - psc_cmd = MPC52xx_PSC_RX_ENABLE; - if (substream->pstr->stream == SNDRV_PCM_STREAM_PLAYBACK) - psc_cmd |= MPC52xx_PSC_TX_ENABLE; - out_8(®s->command, psc_cmd); spin_unlock_irqrestore(&psc_dma->lock, flags); + out_8(®s->command, MPC52xx_PSC_RST_ERR_STAT); + break; case SNDRV_PCM_TRIGGER_STOP: - /* Turn off the PSC */ s->active = 0; - if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { - if (!psc_dma->playback.active) { - out_8(®s->command, 2 << 4); /* reset rx */ - out_8(®s->command, 3 << 4); /* reset tx */ - out_8(®s->command, 4 << 4); /* reset err */ - } - } else { - out_8(®s->command, 3 << 4); /* reset tx */ - out_8(®s->command, 4 << 4); /* reset err */ - if (!psc_dma->capture.active) - out_8(®s->command, 2 << 4); /* reset rx */ - } + spin_lock_irqsave(&psc_dma->lock, flags); bcom_disable(s->bcom_task); - while (!bcom_queue_empty(s->bcom_task)) - bcom_retrieve_buffer(s->bcom_task, NULL, NULL); + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + bcom_gen_bd_rx_reset(s->bcom_task); + else + bcom_gen_bd_tx_reset(s->bcom_task); + spin_unlock_irqrestore(&psc_dma->lock, flags); break; @@ -265,44 +229,11 @@ int psc_dma_trigger(struct snd_pcm_substream *substream, int cmd, imr |= MPC52xx_PSC_IMR_TXEMP; if (psc_dma->capture.active) imr |= MPC52xx_PSC_IMR_ORERR; - out_be16(®s->isr_imr.imr, imr); + out_be16(®s->isr_imr.imr, psc_dma->imr | imr); return 0; } -/** - * psc_dma_shutdown: shutdown the data transfer on a stream - * - * Shutdown the PSC if there are no other substreams open. - */ -void psc_dma_shutdown(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; - - dev_dbg(psc_dma->dev, "psc_dma_shutdown(substream=%p)\n", substream); - - /* - * If this is the last active substream, disable the PSC and release - * the IRQ. - */ - if (!psc_dma->playback.active && - !psc_dma->capture.active) { - - /* Disable all interrupts and reset the PSC */ - out_be16(&psc_dma->psc_regs->isr_imr.imr, 0); - out_8(&psc_dma->psc_regs->command, 3 << 4); /* reset tx */ - out_8(&psc_dma->psc_regs->command, 2 << 4); /* reset rx */ - out_8(&psc_dma->psc_regs->command, 1 << 4); /* reset mode */ - out_8(&psc_dma->psc_regs->command, 4 << 4); /* reset error */ - - /* Release irqs */ - free_irq(psc_dma->irq, psc_dma); - free_irq(psc_dma->capture.irq, &psc_dma->capture); - free_irq(psc_dma->playback.irq, &psc_dma->playback); - } -} /* --------------------------------------------------------------------- * The PSC DMA 'ASoC platform' driver @@ -312,62 +243,78 @@ void psc_dma_shutdown(struct snd_pcm_substream *substream, * interaction with the attached codec */ -static const struct snd_pcm_hardware psc_dma_pcm_hardware = { +static const struct snd_pcm_hardware psc_dma_hardware = { .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_BATCH, .formats = SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | - SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE, + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE, .rate_min = 8000, .rate_max = 48000, - .channels_min = 2, + .channels_min = 1, .channels_max = 2, .period_bytes_max = 1024 * 1024, .period_bytes_min = 32, .periods_min = 2, .periods_max = 256, .buffer_bytes_max = 2 * 1024 * 1024, - .fifo_size = 0, + .fifo_size = 512, }; -static int psc_dma_pcm_open(struct snd_pcm_substream *substream) +static int psc_dma_open(struct snd_pcm_substream *substream) { + struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; struct psc_dma_stream *s; + int rc; - dev_dbg(psc_dma->dev, "psc_dma_pcm_open(substream=%p)\n", substream); + dev_dbg(psc_dma->dev, "psc_dma_open(substream=%p)\n", substream); if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) s = &psc_dma->capture; else s = &psc_dma->playback; - snd_soc_set_runtime_hwparams(substream, &psc_dma_pcm_hardware); + snd_soc_set_runtime_hwparams(substream, &psc_dma_hardware); + + rc = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (rc < 0) { + dev_err(substream->pcm->card->dev, "invalid buffer size\n"); + return rc; + } s->stream = substream; return 0; } -static int psc_dma_pcm_close(struct snd_pcm_substream *substream) +static int psc_dma_close(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; struct psc_dma_stream *s; - dev_dbg(psc_dma->dev, "psc_dma_pcm_close(substream=%p)\n", substream); + dev_dbg(psc_dma->dev, "psc_dma_close(substream=%p)\n", substream); if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) s = &psc_dma->capture; else s = &psc_dma->playback; + if (!psc_dma->playback.active && + !psc_dma->capture.active) { + + /* Disable all interrupts and reset the PSC */ + out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr); + out_8(&psc_dma->psc_regs->command, 4 << 4); /* reset error */ + } s->stream = NULL; return 0; } static snd_pcm_uframes_t -psc_dma_pcm_pointer(struct snd_pcm_substream *substream) +psc_dma_pointer(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; @@ -384,60 +331,78 @@ psc_dma_pcm_pointer(struct snd_pcm_substream *substream) return bytes_to_frames(substream->runtime, count); } -static struct snd_pcm_ops psc_dma_pcm_ops = { - .open = psc_dma_pcm_open, - .close = psc_dma_pcm_close, +static int +psc_dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static struct snd_pcm_ops psc_dma_ops = { + .open = psc_dma_open, + .close = psc_dma_close, + .hw_free = psc_dma_hw_free, .ioctl = snd_pcm_lib_ioctl, - .pointer = psc_dma_pcm_pointer, + .pointer = psc_dma_pointer, + .trigger = psc_dma_trigger, + .hw_params = psc_dma_hw_params, }; -static u64 psc_dma_pcm_dmamask = 0xffffffff; -static int psc_dma_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, +static u64 psc_dma_dmamask = 0xffffffff; +static int psc_dma_new(struct snd_card *card, struct snd_soc_dai *dai, struct snd_pcm *pcm) { struct snd_soc_pcm_runtime *rtd = pcm->private_data; - size_t size = psc_dma_pcm_hardware.buffer_bytes_max; + struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; + size_t size = psc_dma_hardware.buffer_bytes_max; int rc = 0; - dev_dbg(rtd->socdev->dev, "psc_dma_pcm_new(card=%p, dai=%p, pcm=%p)\n", + dev_dbg(rtd->socdev->dev, "psc_dma_new(card=%p, dai=%p, pcm=%p)\n", card, dai, pcm); if (!card->dev->dma_mask) - card->dev->dma_mask = &psc_dma_pcm_dmamask; + card->dev->dma_mask = &psc_dma_dmamask; if (!card->dev->coherent_dma_mask) card->dev->coherent_dma_mask = 0xffffffff; if (pcm->streams[0].substream) { - rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size, - &pcm->streams[0].substream->dma_buffer); + rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev, + size, &pcm->streams[0].substream->dma_buffer); if (rc) goto playback_alloc_err; } if (pcm->streams[1].substream) { - rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->dev, size, - &pcm->streams[1].substream->dma_buffer); + rc = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, pcm->card->dev, + size, &pcm->streams[1].substream->dma_buffer); if (rc) goto capture_alloc_err; } + if (rtd->socdev->card->codec->ac97) + rtd->socdev->card->codec->ac97->private_data = psc_dma; + return 0; capture_alloc_err: if (pcm->streams[0].substream) snd_dma_free_pages(&pcm->streams[0].substream->dma_buffer); + playback_alloc_err: dev_err(card->dev, "Cannot allocate buffer(s)\n"); + return -ENOMEM; } -static void psc_dma_pcm_free(struct snd_pcm *pcm) +static void psc_dma_free(struct snd_pcm *pcm) { struct snd_soc_pcm_runtime *rtd = pcm->private_data; struct snd_pcm_substream *substream; int stream; - dev_dbg(rtd->socdev->dev, "psc_dma_pcm_free(pcm=%p)\n", pcm); + dev_dbg(rtd->socdev->dev, "psc_dma_free(pcm=%p)\n", pcm); for (stream = 0; stream < 2; stream++) { substream = pcm->streams[stream].substream; @@ -449,10 +414,151 @@ static void psc_dma_pcm_free(struct snd_pcm *pcm) } } -struct snd_soc_platform psc_dma_pcm_soc_platform = { +struct snd_soc_platform mpc5200_audio_dma_platform = { .name = "mpc5200-psc-audio", - .pcm_ops = &psc_dma_pcm_ops, - .pcm_new = &psc_dma_pcm_new, - .pcm_free = &psc_dma_pcm_free, + .pcm_ops = &psc_dma_ops, + .pcm_new = &psc_dma_new, + .pcm_free = &psc_dma_free, }; +EXPORT_SYMBOL_GPL(mpc5200_audio_dma_platform); + +int mpc5200_audio_dma_create(struct of_device *op) +{ + phys_addr_t fifo; + struct psc_dma *psc_dma; + struct resource res; + int size, irq, rc; + const __be32 *prop; + void __iomem *regs; + + /* Fetch the registers and IRQ of the PSC */ + irq = irq_of_parse_and_map(op->node, 0); + if (of_address_to_resource(op->node, 0, &res)) { + dev_err(&op->dev, "Missing reg property\n"); + return -ENODEV; + } + regs = ioremap(res.start, 1 + res.end - res.start); + if (!regs) { + dev_err(&op->dev, "Could not map registers\n"); + return -ENODEV; + } + + /* Allocate and initialize the driver private data */ + psc_dma = kzalloc(sizeof *psc_dma, GFP_KERNEL); + if (!psc_dma) { + iounmap(regs); + return -ENOMEM; + } + + /* Get the PSC ID */ + prop = of_get_property(op->node, "cell-index", &size); + if (!prop || size < sizeof *prop) + return -ENODEV; + + spin_lock_init(&psc_dma->lock); + psc_dma->id = be32_to_cpu(*prop); + psc_dma->irq = irq; + psc_dma->psc_regs = regs; + psc_dma->fifo_regs = regs + sizeof *psc_dma->psc_regs; + psc_dma->dev = &op->dev; + psc_dma->playback.psc_dma = psc_dma; + psc_dma->capture.psc_dma = psc_dma; + snprintf(psc_dma->name, sizeof psc_dma->name, "PSC%u", psc_dma->id); + + /* Find the address of the fifo data registers and setup the + * DMA tasks */ + fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32); + psc_dma->capture.bcom_task = + bcom_psc_gen_bd_rx_init(psc_dma->id, 10, fifo, 512); + psc_dma->playback.bcom_task = + bcom_psc_gen_bd_tx_init(psc_dma->id, 10, fifo); + if (!psc_dma->capture.bcom_task || + !psc_dma->playback.bcom_task) { + dev_err(&op->dev, "Could not allocate bestcomm tasks\n"); + iounmap(regs); + kfree(psc_dma); + return -ENODEV; + } + + /* Disable all interrupts and reset the PSC */ + out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr); + /* reset receiver */ + out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_RX); + /* reset transmitter */ + out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_TX); + /* reset error */ + out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_RST_ERR_STAT); + /* reset mode */ + out_8(&psc_dma->psc_regs->command, MPC52xx_PSC_SEL_MODE_REG_1); + + /* Set up mode register; + * First write: RxRdy (FIFO Alarm) generates rx FIFO irq + * Second write: register Normal mode for non loopback + */ + out_8(&psc_dma->psc_regs->mode, 0); + out_8(&psc_dma->psc_regs->mode, 0); + + /* Set the TX and RX fifo alarm thresholds */ + out_be16(&psc_dma->fifo_regs->rfalarm, 0x100); + out_8(&psc_dma->fifo_regs->rfcntl, 0x4); + out_be16(&psc_dma->fifo_regs->tfalarm, 0x100); + out_8(&psc_dma->fifo_regs->tfcntl, 0x7); + + /* Lookup the IRQ numbers */ + psc_dma->playback.irq = + bcom_get_task_irq(psc_dma->playback.bcom_task); + psc_dma->capture.irq = + bcom_get_task_irq(psc_dma->capture.bcom_task); + + rc = request_irq(psc_dma->irq, &psc_dma_status_irq, IRQF_SHARED, + "psc-dma-status", psc_dma); + rc |= request_irq(psc_dma->capture.irq, + &psc_dma_bcom_irq_rx, IRQF_SHARED, + "psc-dma-capture", &psc_dma->capture); + rc |= request_irq(psc_dma->playback.irq, + &psc_dma_bcom_irq_tx, IRQF_SHARED, + "psc-dma-playback", &psc_dma->playback); + if (rc) { + free_irq(psc_dma->irq, psc_dma); + free_irq(psc_dma->capture.irq, + &psc_dma->capture); + free_irq(psc_dma->playback.irq, + &psc_dma->playback); + return -ENODEV; + } + /* Save what we've done so it can be found again later */ + dev_set_drvdata(&op->dev, psc_dma); + + /* Tell the ASoC OF helpers about it */ + return snd_soc_register_platform(&mpc5200_audio_dma_platform); +} +EXPORT_SYMBOL_GPL(mpc5200_audio_dma_create); + +int mpc5200_audio_dma_destroy(struct of_device *op) +{ + struct psc_dma *psc_dma = dev_get_drvdata(&op->dev); + + dev_dbg(&op->dev, "mpc5200_audio_dma_destroy()\n"); + + snd_soc_unregister_platform(&mpc5200_audio_dma_platform); + + bcom_gen_bd_rx_release(psc_dma->capture.bcom_task); + bcom_gen_bd_tx_release(psc_dma->playback.bcom_task); + + /* Release irqs */ + free_irq(psc_dma->irq, psc_dma); + free_irq(psc_dma->capture.irq, &psc_dma->capture); + free_irq(psc_dma->playback.irq, &psc_dma->playback); + + iounmap(psc_dma->psc_regs); + kfree(psc_dma); + dev_set_drvdata(&op->dev, NULL); + + return 0; +} +EXPORT_SYMBOL_GPL(mpc5200_audio_dma_destroy); + +MODULE_AUTHOR("Grant Likely "); +MODULE_DESCRIPTION("Freescale MPC5200 PSC in DMA mode ASoC Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/mpc5200_dma.h b/sound/soc/fsl/mpc5200_dma.h index a33232c0c59..2000803f06a 100644 --- a/sound/soc/fsl/mpc5200_dma.h +++ b/sound/soc/fsl/mpc5200_dma.h @@ -5,8 +5,10 @@ #ifndef __SOUND_SOC_FSL_MPC5200_DMA_H__ #define __SOUND_SOC_FSL_MPC5200_DMA_H__ +#define PSC_STREAM_NAME_LEN 32 + /** - * psc_dma_stream - Data specific to a single stream (playback or capture) + * psc_ac97_stream - Data specific to a single stream (playback or capture) * @active: flag indicating if the stream is active * @psc_dma: pointer back to parent psc_dma data structure * @bcom_task: bestcomm task structure @@ -17,6 +19,9 @@ * @period_bytes: size of DMA period in bytes */ struct psc_dma_stream { + struct snd_pcm_runtime *runtime; + snd_pcm_uframes_t appl_ptr; + int active; struct psc_dma *psc_dma; struct bcom_task *bcom_task; @@ -27,6 +32,7 @@ struct psc_dma_stream { dma_addr_t period_next_pt; dma_addr_t period_current_pt; int period_bytes; + int period_size; }; /** @@ -48,9 +54,12 @@ struct psc_dma { struct mpc52xx_psc_fifo __iomem *fifo_regs; unsigned int irq; struct device *dev; - struct snd_soc_dai dai; spinlock_t lock; u32 sicr; + uint sysclk; + int imr; + int id; + unsigned int slots; /* per-stream data */ struct psc_dma_stream playback; @@ -58,24 +67,14 @@ struct psc_dma { /* Statistics */ struct { - int overrun_count; - int underrun_count; + unsigned long overrun_count; + unsigned long underrun_count; } stats; }; +int mpc5200_audio_dma_create(struct of_device *op); +int mpc5200_audio_dma_destroy(struct of_device *op); -int psc_dma_startup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai); - -int psc_dma_hw_free(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai); - -void psc_dma_shutdown(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai); - -int psc_dma_trigger(struct snd_pcm_substream *substream, int cmd, - struct snd_soc_dai *dai); - -extern struct snd_soc_platform psc_dma_pcm_soc_platform; +extern struct snd_soc_platform mpc5200_audio_dma_platform; #endif /* __SOUND_SOC_FSL_MPC5200_DMA_H__ */ diff --git a/sound/soc/fsl/mpc5200_psc_i2s.c b/sound/soc/fsl/mpc5200_psc_i2s.c index 12a7917b57b..ce8de90fb94 100644 --- a/sound/soc/fsl/mpc5200_psc_i2s.c +++ b/sound/soc/fsl/mpc5200_psc_i2s.c @@ -3,34 +3,22 @@ * ALSA SoC Digital Audio Interface (DAI) driver * * Copyright (C) 2008 Secret Lab Technologies Ltd. + * Copyright (C) 2009 Jon Smirl, Digispeaker */ -#include #include -#include -#include -#include #include #include -#include -#include #include #include -#include #include -#include -#include -#include #include +#include "mpc5200_psc_i2s.h" #include "mpc5200_dma.h" -MODULE_AUTHOR("Grant Likely "); -MODULE_DESCRIPTION("Freescale MPC5200 PSC in I2S mode ASoC Driver"); -MODULE_LICENSE("GPL"); - /** * PSC_I2S_RATES: sample rates supported by the I2S * @@ -46,8 +34,7 @@ MODULE_LICENSE("GPL"); * PSC_I2S_FORMATS: audio formats supported by the PSC I2S mode */ #define PSC_I2S_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_BE | \ - SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S24_BE | \ - SNDRV_PCM_FMTBIT_S32_BE) + SNDRV_PCM_FMTBIT_S24_BE | SNDRV_PCM_FMTBIT_S32_BE) static int psc_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, @@ -82,8 +69,6 @@ static int psc_i2s_hw_params(struct snd_pcm_substream *substream, } out_be32(&psc_dma->psc_regs->sicr, psc_dma->sicr | mode); - snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); - return 0; } @@ -140,16 +125,13 @@ static int psc_i2s_set_fmt(struct snd_soc_dai *cpu_dai, unsigned int format) * psc_i2s_dai_template: template CPU Digital Audio Interface */ static struct snd_soc_dai_ops psc_i2s_dai_ops = { - .startup = psc_dma_startup, .hw_params = psc_i2s_hw_params, - .hw_free = psc_dma_hw_free, - .shutdown = psc_dma_shutdown, - .trigger = psc_dma_trigger, .set_sysclk = psc_i2s_set_sysclk, .set_fmt = psc_i2s_set_fmt, }; -static struct snd_soc_dai psc_i2s_dai_template = { +struct snd_soc_dai psc_i2s_dai[] = {{ + .name = "I2S", .playback = { .channels_min = 2, .channels_max = 2, @@ -163,71 +145,8 @@ static struct snd_soc_dai psc_i2s_dai_template = { .formats = PSC_I2S_FORMATS, }, .ops = &psc_i2s_dai_ops, -}; - -/* --------------------------------------------------------------------- - * Sysfs attributes for debugging - */ - -static ssize_t psc_i2s_status_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct psc_dma *psc_dma = dev_get_drvdata(dev); - - return sprintf(buf, "status=%.4x sicr=%.8x rfnum=%i rfstat=0x%.4x " - "tfnum=%i tfstat=0x%.4x\n", - in_be16(&psc_dma->psc_regs->sr_csr.status), - in_be32(&psc_dma->psc_regs->sicr), - in_be16(&psc_dma->fifo_regs->rfnum) & 0x1ff, - in_be16(&psc_dma->fifo_regs->rfstat), - in_be16(&psc_dma->fifo_regs->tfnum) & 0x1ff, - in_be16(&psc_dma->fifo_regs->tfstat)); -} - -static int *psc_i2s_get_stat_attr(struct psc_dma *psc_dma, const char *name) -{ - if (strcmp(name, "playback_underrun") == 0) - return &psc_dma->stats.underrun_count; - if (strcmp(name, "capture_overrun") == 0) - return &psc_dma->stats.overrun_count; - - return NULL; -} - -static ssize_t psc_i2s_stat_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct psc_dma *psc_dma = dev_get_drvdata(dev); - int *attrib; - - attrib = psc_i2s_get_stat_attr(psc_dma, attr->attr.name); - if (!attrib) - return 0; - - return sprintf(buf, "%i\n", *attrib); -} - -static ssize_t psc_i2s_stat_store(struct device *dev, - struct device_attribute *attr, - const char *buf, - size_t count) -{ - struct psc_dma *psc_dma = dev_get_drvdata(dev); - int *attrib; - - attrib = psc_i2s_get_stat_attr(psc_dma, attr->attr.name); - if (!attrib) - return 0; - - *attrib = simple_strtoul(buf, NULL, 0); - return count; -} - -static DEVICE_ATTR(status, 0644, psc_i2s_status_show, NULL); -static DEVICE_ATTR(playback_underrun, 0644, psc_i2s_stat_show, - psc_i2s_stat_store); -static DEVICE_ATTR(capture_overrun, 0644, psc_i2s_stat_show, - psc_i2s_stat_store); +} }; +EXPORT_SYMBOL_GPL(psc_i2s_dai); /* --------------------------------------------------------------------- * OF platform bus binding code: @@ -237,82 +156,26 @@ static DEVICE_ATTR(capture_overrun, 0644, psc_i2s_stat_show, static int __devinit psc_i2s_of_probe(struct of_device *op, const struct of_device_id *match) { - phys_addr_t fifo; + int rc; struct psc_dma *psc_dma; - struct resource res; - int size, psc_id, irq, rc; - const __be32 *prop; - void __iomem *regs; - - dev_dbg(&op->dev, "probing psc i2s device\n"); - - /* Get the PSC ID */ - prop = of_get_property(op->node, "cell-index", &size); - if (!prop || size < sizeof *prop) - return -ENODEV; - psc_id = be32_to_cpu(*prop); - - /* Fetch the registers and IRQ of the PSC */ - irq = irq_of_parse_and_map(op->node, 0); - if (of_address_to_resource(op->node, 0, &res)) { - dev_err(&op->dev, "Missing reg property\n"); - return -ENODEV; - } - regs = ioremap(res.start, 1 + res.end - res.start); - if (!regs) { - dev_err(&op->dev, "Could not map registers\n"); - return -ENODEV; - } + struct mpc52xx_psc __iomem *regs; - /* Allocate and initialize the driver private data */ - psc_dma = kzalloc(sizeof *psc_dma, GFP_KERNEL); - if (!psc_dma) { - iounmap(regs); - return -ENOMEM; - } - spin_lock_init(&psc_dma->lock); - psc_dma->irq = irq; - psc_dma->psc_regs = regs; - psc_dma->fifo_regs = regs + sizeof *psc_dma->psc_regs; - psc_dma->dev = &op->dev; - psc_dma->playback.psc_dma = psc_dma; - psc_dma->capture.psc_dma = psc_dma; - snprintf(psc_dma->name, sizeof psc_dma->name, "PSC%u", psc_id+1); - - /* Fill out the CPU DAI structure */ - memcpy(&psc_dma->dai, &psc_i2s_dai_template, sizeof psc_dma->dai); - psc_dma->dai.private_data = psc_dma; - psc_dma->dai.name = psc_dma->name; - psc_dma->dai.id = psc_id; - - /* Find the address of the fifo data registers and setup the - * DMA tasks */ - fifo = res.start + offsetof(struct mpc52xx_psc, buffer.buffer_32); - psc_dma->capture.bcom_task = - bcom_psc_gen_bd_rx_init(psc_id, 10, fifo, 512); - psc_dma->playback.bcom_task = - bcom_psc_gen_bd_tx_init(psc_id, 10, fifo); - if (!psc_dma->capture.bcom_task || - !psc_dma->playback.bcom_task) { - dev_err(&op->dev, "Could not allocate bestcomm tasks\n"); - iounmap(regs); - kfree(psc_dma); - return -ENODEV; + rc = mpc5200_audio_dma_create(op); + if (rc != 0) + return rc; + + rc = snd_soc_register_dais(psc_i2s_dai, ARRAY_SIZE(psc_i2s_dai)); + if (rc != 0) { + pr_err("Failed to register DAI\n"); + return 0; } - /* Disable all interrupts and reset the PSC */ - out_be16(&psc_dma->psc_regs->isr_imr.imr, 0); - out_8(&psc_dma->psc_regs->command, 3 << 4); /* reset transmitter */ - out_8(&psc_dma->psc_regs->command, 2 << 4); /* reset receiver */ - out_8(&psc_dma->psc_regs->command, 1 << 4); /* reset mode */ - out_8(&psc_dma->psc_regs->command, 4 << 4); /* reset error */ + psc_dma = dev_get_drvdata(&op->dev); + regs = psc_dma->psc_regs; /* Configure the serial interface mode; defaulting to CODEC8 mode */ psc_dma->sicr = MPC52xx_PSC_SICR_DTS1 | MPC52xx_PSC_SICR_I2S | MPC52xx_PSC_SICR_CLKPOL; - if (of_get_property(op->node, "fsl,cellslave", NULL)) - psc_dma->sicr |= MPC52xx_PSC_SICR_CELLSLAVE | - MPC52xx_PSC_SICR_GENCLK; out_be32(&psc_dma->psc_regs->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_SIM_CODEC_8); @@ -321,66 +184,37 @@ static int __devinit psc_i2s_of_probe(struct of_device *op, if (!of_get_property(op->node, "codec-handle", NULL)) return 0; - /* Set up mode register; - * First write: RxRdy (FIFO Alarm) generates rx FIFO irq - * Second write: register Normal mode for non loopback - */ - out_8(&psc_dma->psc_regs->mode, 0); - out_8(&psc_dma->psc_regs->mode, 0); - - /* Set the TX and RX fifo alarm thresholds */ - out_be16(&psc_dma->fifo_regs->rfalarm, 0x100); - out_8(&psc_dma->fifo_regs->rfcntl, 0x4); - out_be16(&psc_dma->fifo_regs->tfalarm, 0x100); - out_8(&psc_dma->fifo_regs->tfcntl, 0x7); - - /* Lookup the IRQ numbers */ - psc_dma->playback.irq = - bcom_get_task_irq(psc_dma->playback.bcom_task); - psc_dma->capture.irq = - bcom_get_task_irq(psc_dma->capture.bcom_task); - - /* Save what we've done so it can be found again later */ - dev_set_drvdata(&op->dev, psc_dma); - - /* Register the SYSFS files */ - rc = device_create_file(psc_dma->dev, &dev_attr_status); - rc |= device_create_file(psc_dma->dev, &dev_attr_capture_overrun); - rc |= device_create_file(psc_dma->dev, &dev_attr_playback_underrun); - if (rc) - dev_info(psc_dma->dev, "error creating sysfs files\n"); - - snd_soc_register_platform(&psc_dma_pcm_soc_platform); - - /* Tell the ASoC OF helpers about it */ - of_snd_soc_register_platform(&psc_dma_pcm_soc_platform, op->node, - &psc_dma->dai); + /* Due to errata in the dma mode; need to line up enabling + * the transmitter with a transition on the frame sync + * line */ + + /* first make sure it is low */ + while ((in_8(®s->ipcr_acr.ipcr) & 0x80) != 0) + ; + /* then wait for the transition to high */ + while ((in_8(®s->ipcr_acr.ipcr) & 0x80) == 0) + ; + /* Finally, enable the PSC. + * Receiver must always be enabled; even when we only want + * transmit. (see 15.3.2.3 of MPC5200B User's Guide) */ + + /* Go */ + out_8(&psc_dma->psc_regs->command, + MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE); return 0; + } static int __devexit psc_i2s_of_remove(struct of_device *op) { - struct psc_dma *psc_dma = dev_get_drvdata(&op->dev); - - dev_dbg(&op->dev, "psc_i2s_remove()\n"); - - snd_soc_unregister_platform(&psc_dma_pcm_soc_platform); - - bcom_gen_bd_rx_release(psc_dma->capture.bcom_task); - bcom_gen_bd_tx_release(psc_dma->playback.bcom_task); - - iounmap(psc_dma->psc_regs); - iounmap(psc_dma->fifo_regs); - kfree(psc_dma); - dev_set_drvdata(&op->dev, NULL); - - return 0; + return mpc5200_audio_dma_destroy(op); } /* Match table for of_platform binding */ static struct of_device_id psc_i2s_match[] __devinitdata = { { .compatible = "fsl,mpc5200-psc-i2s", }, + { .compatible = "fsl,mpc5200b-psc-i2s", }, {} }; MODULE_DEVICE_TABLE(of, psc_i2s_match); @@ -411,4 +245,7 @@ static void __exit psc_i2s_exit(void) } module_exit(psc_i2s_exit); +MODULE_AUTHOR("Grant Likely "); +MODULE_DESCRIPTION("Freescale MPC5200 PSC in I2S mode ASoC Driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/fsl/mpc5200_psc_i2s.h b/sound/soc/fsl/mpc5200_psc_i2s.h new file mode 100644 index 00000000000..ce55e070fdf --- /dev/null +++ b/sound/soc/fsl/mpc5200_psc_i2s.h @@ -0,0 +1,12 @@ +/* + * Freescale MPC5200 PSC in I2S mode + * ALSA SoC Digital Audio Interface (DAI) driver + * + */ + +#ifndef __SOUND_SOC_FSL_MPC52xx_PSC_I2S_H__ +#define __SOUND_SOC_FSL_MPC52xx_PSC_I2S_H__ + +extern struct snd_soc_dai psc_i2s_dai[]; + +#endif /* __SOUND_SOC_FSL_MPC52xx_PSC_I2S_H__ */ -- cgit v1.2.3 From 20d0e1520ed1ba8aad05f416245446de0f7ec4bb Mon Sep 17 00:00:00 2001 From: Jon Smirl Date: Tue, 26 May 2009 08:34:10 -0400 Subject: ASoC: AC97 driver for mpc5200 I've implemented retries for when the AC97 hardware doesn't reset on first try. About 10% of the time both the Efika and pcm030 AC97 codecs don't reset on first try and need to be poked multiple times. Failure is indicated by not having the link clock start ticking. Every once in a while even five pokes won't get the link started and I have to power cycle. Signed-off-by: Jon Smirl Signed-off-by: Mark Brown --- sound/soc/fsl/Kconfig | 11 ++ sound/soc/fsl/Makefile | 1 + sound/soc/fsl/mpc5200_psc_ac97.c | 331 +++++++++++++++++++++++++++++++++++++++ sound/soc/fsl/mpc5200_psc_ac97.h | 15 ++ 4 files changed, 358 insertions(+) create mode 100644 sound/soc/fsl/mpc5200_psc_ac97.c create mode 100644 sound/soc/fsl/mpc5200_psc_ac97.h (limited to 'sound/soc') diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 1918c78b858..3bce952d7c5 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -29,3 +29,14 @@ config SND_SOC_MPC5200_I2S select PPC_BESTCOMM_GEN_BD help Say Y here to support the MPC5200 PSCs in I2S mode. + +config SND_SOC_MPC5200_AC97 + tristate "Freescale MPC5200 PSC in AC97 mode driver" + depends on PPC_MPC52xx && PPC_BESTCOMM + select AC97_BUS + select SND_MPC52xx_DMA + select PPC_BESTCOMM_GEN_BD + help + Say Y here to support the MPC5200 PSCs in AC97 mode. + + diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index 7731ef2539b..14631a1732e 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -13,4 +13,5 @@ obj-$(CONFIG_SND_SOC_MPC8610) += snd-soc-fsl-ssi.o snd-soc-fsl-dma.o # MPC5200 Platform Support obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o obj-$(CONFIG_SND_SOC_MPC5200_I2S) += mpc5200_psc_i2s.o +obj-$(CONFIG_SND_SOC_MPC5200_AC97) += mpc5200_psc_ac97.o diff --git a/sound/soc/fsl/mpc5200_psc_ac97.c b/sound/soc/fsl/mpc5200_psc_ac97.c new file mode 100644 index 00000000000..9f2df1552f0 --- /dev/null +++ b/sound/soc/fsl/mpc5200_psc_ac97.c @@ -0,0 +1,331 @@ +/* + * linux/sound/mpc5200-ac97.c -- AC97 support for the Freescale MPC52xx chip. + * + * Copyright (C) 2009 Jon Smirl, Digispeaker + * Author: Jon Smirl + * + * 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 +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "mpc5200_dma.h" +#include "mpc5200_psc_ac97.h" + +#define DRV_NAME "mpc5200-psc-ac97" + +/* ALSA only supports a single AC97 device so static is recommend here */ +static struct psc_dma *psc_dma; + +static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg) +{ + int rc; + unsigned int val; + + /* Wait for command send status zero = ready */ + spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_CMDSEND), 100, 0, rc); + if (rc == 0) { + pr_err("timeout on ac97 bus (rdy)\n"); + return -ENODEV; + } + /* Send the read */ + out_be32(&psc_dma->psc_regs->ac97_cmd, (1<<31) | ((reg & 0x7f) << 24)); + + /* Wait for the answer */ + spin_event_timeout((in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_DATA_VAL), 100, 0, rc); + if (rc == 0) { + pr_err("timeout on ac97 read (val) %x\n", + in_be16(&psc_dma->psc_regs->sr_csr.status)); + return -ENODEV; + } + /* Get the data */ + val = in_be32(&psc_dma->psc_regs->ac97_data); + if (((val >> 24) & 0x7f) != reg) { + pr_err("reg echo error on ac97 read\n"); + return -ENODEV; + } + val = (val >> 8) & 0xffff; + + return (unsigned short) val; +} + +static void psc_ac97_write(struct snd_ac97 *ac97, + unsigned short reg, unsigned short val) +{ + int rc; + + /* Wait for command status zero = ready */ + spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_CMDSEND), 100, 0, rc); + if (rc == 0) { + pr_err("timeout on ac97 bus (write)\n"); + return; + } + /* Write data */ + out_be32(&psc_dma->psc_regs->ac97_cmd, + ((reg & 0x7f) << 24) | (val << 8)); +} + +static void psc_ac97_warm_reset(struct snd_ac97 *ac97) +{ + int rc; + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + + out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_AWR); + spin_event_timeout(0, 3, 0, rc); + out_be32(®s->sicr, psc_dma->sicr); +} + +static void psc_ac97_cold_reset(struct snd_ac97 *ac97) +{ + int rc; + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + + /* Do a cold reset */ + out_8(®s->op1, MPC52xx_PSC_OP_RES); + spin_event_timeout(0, 10, 0, rc); + out_8(®s->op0, MPC52xx_PSC_OP_RES); + spin_event_timeout(0, 50, 0, rc); + psc_ac97_warm_reset(ac97); +} + +struct snd_ac97_bus_ops soc_ac97_ops = { + .read = psc_ac97_read, + .write = psc_ac97_write, + .reset = psc_ac97_cold_reset, + .warm_reset = psc_ac97_warm_reset, +}; +EXPORT_SYMBOL_GPL(soc_ac97_ops); + +static int psc_ac97_hw_analog_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct psc_dma *psc_dma = cpu_dai->private_data; + + dev_dbg(psc_dma->dev, "%s(substream=%p) p_size=%i p_bytes=%i" + " periods=%i buffer_size=%i buffer_bytes=%i channels=%i" + " rate=%i format=%i\n", + __func__, substream, params_period_size(params), + params_period_bytes(params), params_periods(params), + params_buffer_size(params), params_buffer_bytes(params), + params_channels(params), params_rate(params), + params_format(params)); + + + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) { + if (params_channels(params) == 1) + psc_dma->slots |= 0x00000100; + else + psc_dma->slots |= 0x00000300; + } else { + if (params_channels(params) == 1) + psc_dma->slots |= 0x01000000; + else + psc_dma->slots |= 0x03000000; + } + out_be32(&psc_dma->psc_regs->ac97_slots, psc_dma->slots); + + return 0; +} + +static int psc_ac97_hw_digital_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct psc_dma *psc_dma = cpu_dai->private_data; + + if (params_channels(params) == 1) + out_be32(&psc_dma->psc_regs->ac97_slots, 0x01000000); + else + out_be32(&psc_dma->psc_regs->ac97_slots, 0x03000000); + + return 0; +} + +static int psc_ac97_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct psc_dma *psc_dma = rtd->dai->cpu_dai->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_STOP: + if (substream->pstr->stream == SNDRV_PCM_STREAM_CAPTURE) + psc_dma->slots &= 0xFFFF0000; + else + psc_dma->slots &= 0x0000FFFF; + + out_be32(&psc_dma->psc_regs->ac97_slots, psc_dma->slots); + break; + } + return 0; +} + +static int psc_ac97_probe(struct platform_device *pdev, + struct snd_soc_dai *cpu_dai) +{ + struct psc_dma *psc_dma = cpu_dai->private_data; + struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; + + /* Go */ + out_8(®s->command, MPC52xx_PSC_TX_ENABLE | MPC52xx_PSC_RX_ENABLE); + return 0; +} + +/* --------------------------------------------------------------------- + * ALSA SoC Bindings + * + * - Digital Audio Interface (DAI) template + * - create/destroy dai hooks + */ + +/** + * psc_ac97_dai_template: template CPU Digital Audio Interface + */ +static struct snd_soc_dai_ops psc_ac97_analog_ops = { + .hw_params = psc_ac97_hw_analog_params, + .trigger = psc_ac97_trigger, +}; + +static struct snd_soc_dai_ops psc_ac97_digital_ops = { + .hw_params = psc_ac97_hw_digital_params, +}; + +struct snd_soc_dai psc_ac97_dai[] = { +{ + .name = "AC97", + .ac97_control = 1, + .probe = psc_ac97_probe, + .playback = { + .channels_min = 1, + .channels_max = 6, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S32_BE, + }, + .capture = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S32_BE, + }, + .ops = &psc_ac97_analog_ops, +}, +{ + .name = "SPDIF", + .ac97_control = 1, + .playback = { + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_BE, + }, + .ops = &psc_ac97_digital_ops, +} }; +EXPORT_SYMBOL_GPL(psc_ac97_dai); + + + +/* --------------------------------------------------------------------- + * OF platform bus binding code: + * - Probe/remove operations + * - OF device match table + */ +static int __devinit psc_ac97_of_probe(struct of_device *op, + const struct of_device_id *match) +{ + int rc, i; + struct snd_ac97 ac97; + struct mpc52xx_psc __iomem *regs; + + rc = mpc5200_audio_dma_create(op); + if (rc != 0) + return rc; + + for (i = 0; i < ARRAY_SIZE(psc_ac97_dai); i++) + psc_ac97_dai[i].dev = &op->dev; + + rc = snd_soc_register_dais(psc_ac97_dai, ARRAY_SIZE(psc_ac97_dai)); + if (rc != 0) { + dev_err(&op->dev, "Failed to register DAI\n"); + return rc; + } + + psc_dma = dev_get_drvdata(&op->dev); + regs = psc_dma->psc_regs; + ac97.private_data = psc_dma; + + for (i = 0; i < ARRAY_SIZE(psc_ac97_dai); i++) + psc_ac97_dai[i].private_data = psc_dma; + + psc_dma->imr = 0; + out_be16(&psc_dma->psc_regs->isr_imr.imr, psc_dma->imr); + + /* Configure the serial interface mode to AC97 */ + psc_dma->sicr = MPC52xx_PSC_SICR_SIM_AC97 | MPC52xx_PSC_SICR_ENAC97; + out_be32(®s->sicr, psc_dma->sicr); + + /* No slots active */ + out_be32(®s->ac97_slots, 0x00000000); + + return 0; +} + +static int __devexit psc_ac97_of_remove(struct of_device *op) +{ + return mpc5200_audio_dma_destroy(op); +} + +/* Match table for of_platform binding */ +static struct of_device_id psc_ac97_match[] __devinitdata = { + { .compatible = "fsl,mpc5200-psc-ac97", }, + { .compatible = "fsl,mpc5200b-psc-ac97", }, + {} +}; +MODULE_DEVICE_TABLE(of, psc_ac97_match); + +static struct of_platform_driver psc_ac97_driver = { + .match_table = psc_ac97_match, + .probe = psc_ac97_of_probe, + .remove = __devexit_p(psc_ac97_of_remove), + .driver = { + .name = "mpc5200-psc-ac97", + .owner = THIS_MODULE, + }, +}; + +/* --------------------------------------------------------------------- + * Module setup and teardown; simply register the of_platform driver + * for the PSC in AC97 mode. + */ +static int __init psc_ac97_init(void) +{ + return of_register_platform_driver(&psc_ac97_driver); +} +module_init(psc_ac97_init); + +static void __exit psc_ac97_exit(void) +{ + of_unregister_platform_driver(&psc_ac97_driver); +} +module_exit(psc_ac97_exit); + +MODULE_AUTHOR("Jon Smirl "); +MODULE_DESCRIPTION("mpc5200 AC97 module"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/fsl/mpc5200_psc_ac97.h b/sound/soc/fsl/mpc5200_psc_ac97.h new file mode 100644 index 00000000000..4bc18c35c36 --- /dev/null +++ b/sound/soc/fsl/mpc5200_psc_ac97.h @@ -0,0 +1,15 @@ +/* + * Freescale MPC5200 PSC in AC97 mode + * ALSA SoC Digital Audio Interface (DAI) driver + * + */ + +#ifndef __SOUND_SOC_FSL_MPC52xx_PSC_AC97_H__ +#define __SOUND_SOC_FSL_MPC52xx_PSC_AC97_H__ + +extern struct snd_soc_dai psc_ac97_dai[]; + +#define MPC5200_AC97_NORMAL 0 +#define MPC5200_AC97_SPDIF 1 + +#endif /* __SOUND_SOC_FSL_MPC52xx_PSC_AC97_H__ */ -- cgit v1.2.3 From a9262c4fd404654acd3684699047fa63206518c8 Mon Sep 17 00:00:00 2001 From: Jon Smirl Date: Tue, 26 May 2009 08:34:12 -0400 Subject: ASoC: Support for AC97 on Phytec pmc030 base board. A wm9712 AC97 codec is used. Signed-off-by: Jon Smirl Signed-off-by: Mark Brown --- sound/soc/fsl/Kconfig | 7 +++ sound/soc/fsl/Makefile | 3 ++ sound/soc/fsl/pcm030-audio-fabric.c | 90 +++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 sound/soc/fsl/pcm030-audio-fabric.c (limited to 'sound/soc') diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 3bce952d7c5..79579ae5b24 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -39,4 +39,11 @@ config SND_SOC_MPC5200_AC97 help Say Y here to support the MPC5200 PSCs in AC97 mode. +config SND_MPC52xx_SOC_PCM030 + tristate "SoC AC97 Audio support for Phytec pcm030 and WM9712" + depends on PPC_MPC5200_SIMPLE + select SND_SOC_MPC5200_AC97 + select SND_SOC_WM9712 + help + Say Y if you want to add support for sound on the Phytec pcm030 baseboard. diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index 14631a1732e..66d88c86b5d 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -15,3 +15,6 @@ obj-$(CONFIG_SND_MPC52xx_DMA) += mpc5200_dma.o obj-$(CONFIG_SND_SOC_MPC5200_I2S) += mpc5200_psc_i2s.o obj-$(CONFIG_SND_SOC_MPC5200_AC97) += mpc5200_psc_ac97.o +# MPC5200 Machine Support +obj-$(CONFIG_SND_MPC52xx_SOC_PCM030) += pcm030-audio-fabric.o + diff --git a/sound/soc/fsl/pcm030-audio-fabric.c b/sound/soc/fsl/pcm030-audio-fabric.c new file mode 100644 index 00000000000..8766f7a3893 --- /dev/null +++ b/sound/soc/fsl/pcm030-audio-fabric.c @@ -0,0 +1,90 @@ +/* + * Phytec pcm030 driver for the PSC of the Freescale MPC52xx + * configured as AC97 interface + * + * Copyright 2008 Jon Smirl, Digispeaker + * Author: Jon Smirl + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "mpc5200_dma.h" +#include "mpc5200_psc_ac97.h" +#include "../codecs/wm9712.h" + +static struct snd_soc_device device; +static struct snd_soc_card card; + +static struct snd_soc_dai_link pcm030_fabric_dai[] = { +{ + .name = "AC97", + .stream_name = "AC97 Analog", + .codec_dai = &wm9712_dai[WM9712_DAI_AC97_HIFI], + .cpu_dai = &psc_ac97_dai[MPC5200_AC97_NORMAL], +}, +{ + .name = "AC97", + .stream_name = "AC97 IEC958", + .codec_dai = &wm9712_dai[WM9712_DAI_AC97_AUX], + .cpu_dai = &psc_ac97_dai[MPC5200_AC97_SPDIF], +}, +}; + +static __init int pcm030_fabric_init(void) +{ + struct platform_device *pdev; + int rc; + + if (!machine_is_compatible("phytec,pcm030")) + return -ENODEV; + + card.platform = &mpc5200_audio_dma_platform; + card.name = "pcm030"; + card.dai_link = pcm030_fabric_dai; + card.num_links = ARRAY_SIZE(pcm030_fabric_dai); + + device.card = &card; + device.codec_dev = &soc_codec_dev_wm9712; + + pdev = platform_device_alloc("soc-audio", 1); + if (!pdev) { + pr_err("pcm030_fabric_init: platform_device_alloc() failed\n"); + return -ENODEV; + } + + platform_set_drvdata(pdev, &device); + device.dev = &pdev->dev; + + rc = platform_device_add(pdev); + if (rc) { + pr_err("pcm030_fabric_init: platform_device_add() failed\n"); + return -ENODEV; + } + return 0; +} + +module_init(pcm030_fabric_init); + + +MODULE_AUTHOR("Jon Smirl "); +MODULE_DESCRIPTION(DRV_NAME ": mpc5200 pcm030 fabric driver"); +MODULE_LICENSE("GPL"); + -- cgit v1.2.3 From 6ffee43ecf8bfbe0bd74c9084c9772a59097d53b Mon Sep 17 00:00:00 2001 From: Jon Smirl Date: Tue, 26 May 2009 08:34:14 -0400 Subject: ASoC: Fabric bindings for STAC9766 on the Efika Signed-off-by: Jon Smirl Signed-off-by: Mark Brown --- sound/soc/fsl/Kconfig | 8 ++++ sound/soc/fsl/Makefile | 1 + sound/soc/fsl/efika-audio-fabric.c | 90 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 sound/soc/fsl/efika-audio-fabric.c (limited to 'sound/soc') diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 79579ae5b24..f571c6eef58 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -47,3 +47,11 @@ config SND_MPC52xx_SOC_PCM030 help Say Y if you want to add support for sound on the Phytec pcm030 baseboard. +config SND_MPC52xx_SOC_EFIKA + tristate "SoC AC97 Audio support for bbplan Efika and STAC9766" + depends on PPC_EFIKA + select SND_SOC_MPC5200_AC97 + select SND_SOC_STAC9766 + help + Say Y if you want to add support for sound on the Efika. + diff --git a/sound/soc/fsl/Makefile b/sound/soc/fsl/Makefile index 66d88c86b5d..a83a73967ec 100644 --- a/sound/soc/fsl/Makefile +++ b/sound/soc/fsl/Makefile @@ -17,4 +17,5 @@ obj-$(CONFIG_SND_SOC_MPC5200_AC97) += mpc5200_psc_ac97.o # MPC5200 Machine Support obj-$(CONFIG_SND_MPC52xx_SOC_PCM030) += pcm030-audio-fabric.o +obj-$(CONFIG_SND_MPC52xx_SOC_EFIKA) += efika-audio-fabric.o diff --git a/sound/soc/fsl/efika-audio-fabric.c b/sound/soc/fsl/efika-audio-fabric.c new file mode 100644 index 00000000000..85b0e756950 --- /dev/null +++ b/sound/soc/fsl/efika-audio-fabric.c @@ -0,0 +1,90 @@ +/* + * Efika driver for the PSC of the Freescale MPC52xx + * configured as AC97 interface + * + * Copyright 2008 Jon Smirl, Digispeaker + * Author: Jon Smirl + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "mpc5200_dma.h" +#include "mpc5200_psc_ac97.h" +#include "../codecs/stac9766.h" + +static struct snd_soc_device device; +static struct snd_soc_card card; + +static struct snd_soc_dai_link efika_fabric_dai[] = { +{ + .name = "AC97", + .stream_name = "AC97 Analog", + .codec_dai = &stac9766_dai[STAC9766_DAI_AC97_ANALOG], + .cpu_dai = &psc_ac97_dai[MPC5200_AC97_NORMAL], +}, +{ + .name = "AC97", + .stream_name = "AC97 IEC958", + .codec_dai = &stac9766_dai[STAC9766_DAI_AC97_DIGITAL], + .cpu_dai = &psc_ac97_dai[MPC5200_AC97_SPDIF], +}, +}; + +static __init int efika_fabric_init(void) +{ + struct platform_device *pdev; + int rc; + + if (!machine_is_compatible("bplan,efika")) + return -ENODEV; + + card.platform = &mpc5200_audio_dma_platform; + card.name = "Efika"; + card.dai_link = efika_fabric_dai; + card.num_links = ARRAY_SIZE(efika_fabric_dai); + + device.card = &card; + device.codec_dev = &soc_codec_dev_stac9766; + + pdev = platform_device_alloc("soc-audio", 1); + if (!pdev) { + pr_err("efika_fabric_init: platform_device_alloc() failed\n"); + return -ENODEV; + } + + platform_set_drvdata(pdev, &device); + device.dev = &pdev->dev; + + rc = platform_device_add(pdev); + if (rc) { + pr_err("efika_fabric_init: platform_device_add() failed\n"); + return -ENODEV; + } + return 0; +} + +module_init(efika_fabric_init); + + +MODULE_AUTHOR("Jon Smirl "); +MODULE_DESCRIPTION(DRV_NAME ": mpc5200 Efika fabric driver"); +MODULE_LICENSE("GPL"); + -- cgit v1.2.3 From 0c0e09e21a9e7bc6ca54e06ef3d497255ca26383 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 26 May 2009 21:14:59 +0100 Subject: ASoC: Mark MPC5200 AC97 as BROKEN until PowerPC merge issues are resolved These drivers use spin_event_timeout() which is only present in the PowerPC tree at present and which is undergoing some API revisions so temporarily mark them as BROKEN until these issues are sorted out. Signed-off-by: Mark Brown --- sound/soc/fsl/Kconfig | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index f571c6eef58..5dbebf82249 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -41,15 +41,16 @@ config SND_SOC_MPC5200_AC97 config SND_MPC52xx_SOC_PCM030 tristate "SoC AC97 Audio support for Phytec pcm030 and WM9712" - depends on PPC_MPC5200_SIMPLE + depends on PPC_MPC5200_SIMPLE && BROKEN select SND_SOC_MPC5200_AC97 select SND_SOC_WM9712 help - Say Y if you want to add support for sound on the Phytec pcm030 baseboard. + Say Y if you want to add support for sound on the Phytec pcm030 + baseboard. config SND_MPC52xx_SOC_EFIKA tristate "SoC AC97 Audio support for bbplan Efika and STAC9766" - depends on PPC_EFIKA + depends on PPC_EFIKA && BROKEN select SND_SOC_MPC5200_AC97 select SND_SOC_STAC9766 help -- cgit v1.2.3 From 08d15f034e94251606479d7ca9070994c2e2fcf0 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 23 May 2009 10:41:05 +0100 Subject: ASoC: Switch FSL SSI DAI over to symmetric_rates The effect of symmetric_constraints should provide a standard way to enforce the use of the same sample rate for both directions. Signed-off-by: Mark Brown Acked-by: Timur Tabi --- sound/soc/fsl/fsl_ssi.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/fsl/fsl_ssi.c b/sound/soc/fsl/fsl_ssi.c index 47afaa9747a..93f0f38a32c 100644 --- a/sound/soc/fsl/fsl_ssi.c +++ b/sound/soc/fsl/fsl_ssi.c @@ -375,18 +375,14 @@ static int fsl_ssi_startup(struct snd_pcm_substream *substream, struct snd_pcm_runtime *first_runtime = ssi_private->first_stream->runtime; - if (!first_runtime->rate || !first_runtime->sample_bits) { + if (!first_runtime->sample_bits) { dev_err(substream->pcm->card->dev, - "set sample rate and size in %s stream first\n", + "set sample size in %s stream first\n", substream->stream == SNDRV_PCM_STREAM_PLAYBACK ? "capture" : "playback"); return -EAGAIN; } - snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - first_runtime->rate, first_runtime->rate); - /* If we're in synchronous mode, then we need to constrain * the sample size as well. We don't support independent sample * rates in asynchronous mode. @@ -693,6 +689,7 @@ struct snd_soc_dai *fsl_ssi_create_dai(struct fsl_ssi_info *ssi_info) fsl_ssi_dai->name = ssi_private->name; fsl_ssi_dai->id = ssi_info->id; fsl_ssi_dai->dev = ssi_info->dev; + fsl_ssi_dai->symmetric_rates = 1; ret = snd_soc_register_dai(fsl_ssi_dai); if (ret != 0) { -- cgit v1.2.3 From ea8b27ad0cc2573776c6cd87617a37aaf603b8bd Mon Sep 17 00:00:00 2001 From: Jon Smirl Date: Wed, 27 May 2009 01:06:19 -0400 Subject: ASoC: Modify mpc5200 AC97 driver to use V9 of spin_event_timeout() The function signature for spin_event_timeout() has changed in version V9. Adjust the mpc5200 AC97 driver to use the new function. Signed-off-by: Jon Smirl Acked-by: Timur Tabi Signed-off-by: Mark Brown --- sound/soc/fsl/mpc5200_psc_ac97.c | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/fsl/mpc5200_psc_ac97.c b/sound/soc/fsl/mpc5200_psc_ac97.c index 9f2df1552f0..794a247b3eb 100644 --- a/sound/soc/fsl/mpc5200_psc_ac97.c +++ b/sound/soc/fsl/mpc5200_psc_ac97.c @@ -31,13 +31,13 @@ static struct psc_dma *psc_dma; static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg) { - int rc; + int status; unsigned int val; /* Wait for command send status zero = ready */ - spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & - MPC52xx_PSC_SR_CMDSEND), 100, 0, rc); - if (rc == 0) { + status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_CMDSEND), 100, 0); + if (status == 0) { pr_err("timeout on ac97 bus (rdy)\n"); return -ENODEV; } @@ -45,9 +45,9 @@ static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg) out_be32(&psc_dma->psc_regs->ac97_cmd, (1<<31) | ((reg & 0x7f) << 24)); /* Wait for the answer */ - spin_event_timeout((in_be16(&psc_dma->psc_regs->sr_csr.status) & - MPC52xx_PSC_SR_DATA_VAL), 100, 0, rc); - if (rc == 0) { + status = spin_event_timeout((in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_DATA_VAL), 100, 0); + if (status == 0) { pr_err("timeout on ac97 read (val) %x\n", in_be16(&psc_dma->psc_regs->sr_csr.status)); return -ENODEV; @@ -66,12 +66,12 @@ static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg) static void psc_ac97_write(struct snd_ac97 *ac97, unsigned short reg, unsigned short val) { - int rc; + int status; /* Wait for command status zero = ready */ - spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & - MPC52xx_PSC_SR_CMDSEND), 100, 0, rc); - if (rc == 0) { + status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & + MPC52xx_PSC_SR_CMDSEND), 100, 0); + if (status == 0) { pr_err("timeout on ac97 bus (write)\n"); return; } @@ -82,24 +82,22 @@ static void psc_ac97_write(struct snd_ac97 *ac97, static void psc_ac97_warm_reset(struct snd_ac97 *ac97) { - int rc; struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; out_be32(®s->sicr, psc_dma->sicr | MPC52xx_PSC_SICR_AWR); - spin_event_timeout(0, 3, 0, rc); + udelay(3); out_be32(®s->sicr, psc_dma->sicr); } static void psc_ac97_cold_reset(struct snd_ac97 *ac97) { - int rc; struct mpc52xx_psc __iomem *regs = psc_dma->psc_regs; /* Do a cold reset */ out_8(®s->op1, MPC52xx_PSC_OP_RES); - spin_event_timeout(0, 10, 0, rc); + udelay(10); out_8(®s->op0, MPC52xx_PSC_OP_RES); - spin_event_timeout(0, 50, 0, rc); + udelay(50); psc_ac97_warm_reset(ac97); } -- cgit v1.2.3 From 449bd54dcbd0b60070ce4129fedaf0f4ae044099 Mon Sep 17 00:00:00 2001 From: Roel Kluin Date: Wed, 27 May 2009 17:08:39 -0700 Subject: ASoC: correct print specifiers for unsigneds Unsigned variables should use `%u' rather than `%d'. Signed-off-by: Roel Kluin Signed-off-by: Andrew Morton Signed-off-by: Mark Brown --- sound/soc/atmel/playpaq_wm8510.c | 2 +- sound/soc/codecs/tlv320aic23.c | 4 ++-- sound/soc/codecs/uda134x.c | 4 ++-- sound/soc/codecs/wm8350.c | 2 +- sound/soc/codecs/wm8400.c | 4 ++-- sound/soc/codecs/wm8510.c | 2 +- sound/soc/codecs/wm8580.c | 4 ++-- sound/soc/codecs/wm8753.c | 2 +- sound/soc/codecs/wm8900.c | 6 +++--- sound/soc/codecs/wm8990.c | 2 +- sound/soc/codecs/wm9713.c | 2 +- sound/soc/pxa/pxa-ssp.c | 4 ++-- sound/soc/s3c24xx/s3c-i2s-v2.c | 4 ++-- sound/soc/sh/ssi.c | 2 +- 14 files changed, 22 insertions(+), 22 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/atmel/playpaq_wm8510.c b/sound/soc/atmel/playpaq_wm8510.c index 70657534e6b..9eb610c2ba9 100644 --- a/sound/soc/atmel/playpaq_wm8510.c +++ b/sound/soc/atmel/playpaq_wm8510.c @@ -117,7 +117,7 @@ static struct ssc_clock_data playpaq_wm8510_calc_ssc_clock( * Find actual rate, compare to requested rate */ actual_rate = (cd.ssc_rate / (cd.cmr_div * 2)) / (2 * (cd.period + 1)); - pr_debug("playpaq_wm8510: Request rate = %d, actual rate = %d\n", + pr_debug("playpaq_wm8510: Request rate = %u, actual rate = %u\n", rate, actual_rate); diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c index 21f69df9994..9fcbb9c7766 100644 --- a/sound/soc/codecs/tlv320aic23.c +++ b/sound/soc/codecs/tlv320aic23.c @@ -86,7 +86,7 @@ static int tlv320aic23_write(struct snd_soc_codec *codec, unsigned int reg, */ if ((reg < 0 || reg > 9) && (reg != 15)) { - printk(KERN_WARNING "%s Invalid register R%d\n", __func__, reg); + printk(KERN_WARNING "%s Invalid register R%u\n", __func__, reg); return -1; } @@ -98,7 +98,7 @@ static int tlv320aic23_write(struct snd_soc_codec *codec, unsigned int reg, if (codec->hw_write(codec->control_data, data, 2) == 2) return 0; - printk(KERN_ERR "%s cannot write %03x to register R%d\n", __func__, + printk(KERN_ERR "%s cannot write %03x to register R%u\n", __func__, value, reg); return -EIO; diff --git a/sound/soc/codecs/uda134x.c b/sound/soc/codecs/uda134x.c index ddefb8f8014..269b108e1de 100644 --- a/sound/soc/codecs/uda134x.c +++ b/sound/soc/codecs/uda134x.c @@ -101,7 +101,7 @@ static int uda134x_write(struct snd_soc_codec *codec, unsigned int reg, pr_debug("%s reg: %02X, value:%02X\n", __func__, reg, value); if (reg >= UDA134X_REGS_NUM) { - printk(KERN_ERR "%s unkown register: reg: %d", + printk(KERN_ERR "%s unkown register: reg: %u", __func__, reg); return -EINVAL; } @@ -296,7 +296,7 @@ static int uda134x_set_dai_sysclk(struct snd_soc_dai *codec_dai, struct snd_soc_codec *codec = codec_dai->codec; struct uda134x_priv *uda134x = codec->private_data; - pr_debug("%s clk_id: %d, freq: %d, dir: %d\n", __func__, + pr_debug("%s clk_id: %d, freq: %u, dir: %d\n", __func__, clk_id, freq, dir); /* Anything between 256fs*8Khz and 512fs*48Khz should be acceptable diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index 0275321ff8a..e7348d341b7 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -1108,7 +1108,7 @@ static int wm8350_set_fll(struct snd_soc_dai *codec_dai, if (ret < 0) return ret; dev_dbg(wm8350->dev, - "FLL in %d FLL out %d N 0x%x K 0x%x div %d ratio %d", + "FLL in %u FLL out %u N 0x%x K 0x%x div %d ratio %d", freq_in, freq_out, fll_div.n, fll_div.k, fll_div.div, fll_div.ratio); diff --git a/sound/soc/codecs/wm8400.c b/sound/soc/codecs/wm8400.c index e4547de8eec..502eefac1ec 100644 --- a/sound/soc/codecs/wm8400.c +++ b/sound/soc/codecs/wm8400.c @@ -954,7 +954,7 @@ static int fll_factors(struct wm8400_priv *wm8400, struct fll_factors *factors, factors->outdiv *= 2; if (factors->outdiv > 32) { dev_err(wm8400->wm8400->dev, - "Unsupported FLL output frequency %dHz\n", + "Unsupported FLL output frequency %uHz\n", Fout); return -EINVAL; } @@ -1003,7 +1003,7 @@ static int fll_factors(struct wm8400_priv *wm8400, struct fll_factors *factors, factors->k = K / 10; dev_dbg(wm8400->wm8400->dev, - "FLL: Fref=%d Fout=%d N=%x K=%x, FRATIO=%x OUTDIV=%x\n", + "FLL: Fref=%u Fout=%u N=%x K=%x, FRATIO=%x OUTDIV=%x\n", Fref, Fout, factors->n, factors->k, factors->fratio, factors->outdiv); diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c index 6a4cea09c45..c8b8dba8589 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -298,7 +298,7 @@ static void pll_factors(unsigned int target, unsigned int source) if ((Ndiv < 6) || (Ndiv > 12)) printk(KERN_WARNING - "WM8510 N value %d outwith recommended range!d\n", + "WM8510 N value %u outwith recommended range!d\n", Ndiv); pll_div.n = Ndiv; diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index 9f6be3d31ac..86c4b24db81 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -415,7 +415,7 @@ static int pll_factors(struct _pll_div *pll_div, unsigned int target, unsigned int K, Ndiv, Nmod; int i; - pr_debug("wm8580: PLL %dHz->%dHz\n", source, target); + pr_debug("wm8580: PLL %uHz->%uHz\n", source, target); /* Scale the output frequency up; the PLL should run in the * region of 90-100MHz. @@ -447,7 +447,7 @@ static int pll_factors(struct _pll_div *pll_div, unsigned int target, if ((Ndiv < 5) || (Ndiv > 13)) { printk(KERN_ERR - "WM8580 N=%d outside supported range\n", Ndiv); + "WM8580 N=%u outside supported range\n", Ndiv); return -EINVAL; } diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index d121e58cae2..d28eeaceb85 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -703,7 +703,7 @@ static void pll_factors(struct _pll_div *pll_div, unsigned int target, if ((Ndiv < 6) || (Ndiv > 12)) printk(KERN_WARNING - "wm8753: unsupported N = %d\n", Ndiv); + "wm8753: unsupported N = %u\n", Ndiv); pll_div->n = Ndiv; Nmod = target % source; diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c index 46c5ea1ff92..3c78945244b 100644 --- a/sound/soc/codecs/wm8900.c +++ b/sound/soc/codecs/wm8900.c @@ -778,11 +778,11 @@ static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, } if (target > 100000000) - printk(KERN_WARNING "wm8900: FLL rate %d out of range, Fref=%d" - " Fout=%d\n", target, Fref, Fout); + printk(KERN_WARNING "wm8900: FLL rate %u out of range, Fref=%u" + " Fout=%u\n", target, Fref, Fout); if (div > 32) { printk(KERN_ERR "wm8900: Invalid FLL division rate %u, " - "Fref=%d, Fout=%d, target=%d\n", + "Fref=%u, Fout=%u, target=%u\n", div, Fref, Fout, target); return -EINVAL; } diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c index 40cd274eb1e..d029818350e 100644 --- a/sound/soc/codecs/wm8990.c +++ b/sound/soc/codecs/wm8990.c @@ -998,7 +998,7 @@ static void pll_factors(struct _pll_div *pll_div, unsigned int target, if ((Ndiv < 6) || (Ndiv > 12)) printk(KERN_WARNING - "WM8990 N value outwith recommended range! N = %d\n", Ndiv); + "WM8990 N value outwith recommended range! N = %u\n", Ndiv); pll_div->n = Ndiv; Nmod = target % source; diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c index d1744e96f30..abed37acf78 100644 --- a/sound/soc/codecs/wm9713.c +++ b/sound/soc/codecs/wm9713.c @@ -710,7 +710,7 @@ static void pll_factors(struct _pll_div *pll_div, unsigned int source) Ndiv = target / source; if ((Ndiv < 5) || (Ndiv > 12)) printk(KERN_WARNING - "WM9713 PLL N value %d out of recommended range!\n", + "WM9713 PLL N value %u out of recommended range!\n", Ndiv); pll_div->n = Ndiv; diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index 6fc787610ad..19c45409d94 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -208,7 +208,7 @@ static int pxa_ssp_set_dai_sysclk(struct snd_soc_dai *cpu_dai, ~(SSCR0_ECS | SSCR0_NCS | SSCR0_MOD | SSCR0_ACS); dev_dbg(&ssp->pdev->dev, - "pxa_ssp_set_dai_sysclk id: %d, clk_id %d, freq %d\n", + "pxa_ssp_set_dai_sysclk id: %d, clk_id %d, freq %u\n", cpu_dai->id, clk_id, freq); switch (clk_id) { @@ -357,7 +357,7 @@ static int pxa_ssp_set_dai_pll(struct snd_soc_dai *cpu_dai, ssacd |= (0x6 << 4); dev_dbg(&ssp->pdev->dev, - "Using SSACDD %x to supply %dHz\n", + "Using SSACDD %x to supply %uHz\n", val, freq_out); break; } diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index 972c2768419..1a283170ca9 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -547,7 +547,7 @@ int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info, actual = clkrate / (fsdiv * div); deviation = actual - rate; - printk(KERN_DEBUG "%dfs: div %d => result %d, deviation %d\n", + printk(KERN_DEBUG "%ufs: div %u => result %u, deviation %d\n", fsdiv, div, actual, deviation); deviation = abs(deviation); @@ -563,7 +563,7 @@ int s3c_i2sv2_iis_calc_rate(struct s3c_i2sv2_rate_calc *info, break; } - printk(KERN_DEBUG "best: fs=%d, div=%d, rate=%d\n", + printk(KERN_DEBUG "best: fs=%u, div=%u, rate=%u\n", best_fs, best_div, best_rate); info->fs_div = best_fs; diff --git a/sound/soc/sh/ssi.c b/sound/soc/sh/ssi.c index 56fa0872abb..b378096cadb 100644 --- a/sound/soc/sh/ssi.c +++ b/sound/soc/sh/ssi.c @@ -145,7 +145,7 @@ static int ssi_hw_params(struct snd_pcm_substream *substream, recv = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) ? 0 : 1; pr_debug("ssi_hw_params() enter\nssicr was %08lx\n", ssicr); - pr_debug("bits: %d channels: %d\n", bits, channels); + pr_debug("bits: %u channels: %u\n", bits, channels); ssicr &= ~(CR_TRMD | CR_CHNL_MASK | CR_DWL_MASK | CR_PDTA | CR_SWL_MASK); -- cgit v1.2.3 From be461ba836770263826457624bc4a5173a1f5040 Mon Sep 17 00:00:00 2001 From: Chaithrika U S Date: Thu, 28 May 2009 05:10:50 -0400 Subject: ASoC: Add dummy S/PDIF codec support McASP on DM646x can operate in DIT (S/PDIF) where no codec is needed. This patch provides stub codec that can be used in these configurations. On DM646x EVM the McASP1 is connected to the S/PDIF out. Signed-off-by: Steve Chen Signed-off-by: Pavel Kiryukhin Signed-off-by: Naresh Medisetty Signed-off-by: Chaithrika U S Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 +++ sound/soc/codecs/Makefile | 2 ++ sound/soc/codecs/spdif_transciever.c | 69 ++++++++++++++++++++++++++++++++++++ sound/soc/codecs/spdif_transciever.h | 17 +++++++++ 4 files changed, 92 insertions(+) create mode 100644 sound/soc/codecs/spdif_transciever.c create mode 100644 sound/soc/codecs/spdif_transciever.h (limited to 'sound/soc') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index cb07d9b51b6..bbc97fd7664 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -18,6 +18,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_AK4535 if I2C select SND_SOC_CS4270 if I2C select SND_SOC_PCM3008 + select SND_SOC_SPDIF select SND_SOC_SSM2602 if I2C select SND_SOC_STAC9766 if SND_SOC_AC97_BUS select SND_SOC_TLV320AIC23 if I2C @@ -91,6 +92,9 @@ config SND_SOC_L3 config SND_SOC_PCM3008 tristate +config SND_SOC_SPDIF + tristate + config SND_SOC_SSM2602 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 46c007cb562..8b7530546f4 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -6,6 +6,7 @@ snd-soc-ak4535-objs := ak4535.o snd-soc-cs4270-objs := cs4270.o snd-soc-l3-objs := l3.o snd-soc-pcm3008-objs := pcm3008.o +snd-soc-spdif-objs := spdif_transciever.o snd-soc-ssm2602-objs := ssm2602.o snd-soc-stac9766-objs := stac9766.o snd-soc-tlv320aic23-objs := tlv320aic23.o @@ -42,6 +43,7 @@ obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o +obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o diff --git a/sound/soc/codecs/spdif_transciever.c b/sound/soc/codecs/spdif_transciever.c new file mode 100644 index 00000000000..118e976b28a --- /dev/null +++ b/sound/soc/codecs/spdif_transciever.c @@ -0,0 +1,69 @@ +/* + * ALSA SoC SPDIF DIT driver + * + * This driver is used by controllers which can operate in DIT (SPDI/F) where + * no codec is needed. This file provides stub codec that can be used + * in these configurations. TI DaVinci Audio controller uses this driver. + * + * Author: Steve Chen, + * Copyright: (C) 2009 MontaVista Software, Inc., + * Copyright: (C) 2009 Texas Instruments, India + * + * 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 +#include +#include +#include + +#define STUB_RATES SNDRV_PCM_RATE_8000_96000 +#define STUB_FORMATS SNDRV_PCM_FMTBIT_S16_LE + + +struct snd_soc_dai dit_stub_dai = { + .name = "DIT", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 384, + .rates = STUB_RATES, + .formats = STUB_FORMATS, + }, +}; + +static int spdif_dit_probe(struct platform_device *pdev) +{ + return snd_soc_register_dai(&dit_stub_dai); +} + +static int spdif_dit_remove(struct platform_device *pdev) +{ + snd_soc_unregister_dai(&dit_stub_dai); + return 0; +} + +static struct platform_driver spdif_dit_driver = { + .probe = spdif_dit_probe, + .remove = spdif_dit_remove, + .driver = { + .name = "spdif-dit", + .owner = THIS_MODULE, + }, +}; + +static int __init dit_modinit(void) +{ + return platform_driver_register(&spdif_dit_driver); +} + +static void __exit dit_exit(void) +{ + platform_driver_unregister(&spdif_dit_driver); +} + +module_init(dit_modinit); +module_exit(dit_exit); + diff --git a/sound/soc/codecs/spdif_transciever.h b/sound/soc/codecs/spdif_transciever.h new file mode 100644 index 00000000000..296f2eb6c4e --- /dev/null +++ b/sound/soc/codecs/spdif_transciever.h @@ -0,0 +1,17 @@ +/* + * ALSA SoC DIT/DIR driver header + * + * Author: Steve Chen, + * Copyright: (C) 2008 MontaVista Software, Inc., + * + * 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 CODEC_STUBS_H +#define CODEC_STUBS_H + +extern struct snd_soc_dai dit_stub_dai; + +#endif /* CODEC_STUBS_H */ -- cgit v1.2.3 From 203350c1a8e23adf17fd9a96d8bfc7adf63c1ff6 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 28 May 2009 14:51:00 +0100 Subject: ASoC: Initialise dev for the dummy S/PDIF DAI Also include the header to make sure the DAI is prototyped. Signed-off-by: Mark Brown --- sound/soc/codecs/spdif_transciever.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/spdif_transciever.c b/sound/soc/codecs/spdif_transciever.c index 118e976b28a..218b33adad9 100644 --- a/sound/soc/codecs/spdif_transciever.c +++ b/sound/soc/codecs/spdif_transciever.c @@ -19,10 +19,11 @@ #include #include +#include "spdif_transciever.h" + #define STUB_RATES SNDRV_PCM_RATE_8000_96000 #define STUB_FORMATS SNDRV_PCM_FMTBIT_S16_LE - struct snd_soc_dai dit_stub_dai = { .name = "DIT", .playback = { @@ -36,6 +37,7 @@ struct snd_soc_dai dit_stub_dai = { static int spdif_dit_probe(struct platform_device *pdev) { + dit_stub_dai.dev = &pdev->dev; return snd_soc_register_dai(&dit_stub_dai); } -- cgit v1.2.3 From 16a30fbb0d3aa4ee829a2dd3d0e314e2b5ae96a9 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 29 May 2009 09:22:37 +0300 Subject: ASoC: TWL4030: Use reg_cache in twl4030_init_chip Use the codec->reg_cache instead of the array directly in twl4030_init_chip for setting the default values. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 63ebd176fbe..df474a5dd35 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -145,7 +145,6 @@ struct twl4030_priv { static inline unsigned int twl4030_read_reg_cache(struct snd_soc_codec *codec, unsigned int reg) { - u8 *cache = codec->reg_cache; if (reg >= TWL4030_CACHEREGNUM) return -EIO; @@ -204,6 +203,7 @@ static void twl4030_codec_enable(struct snd_soc_codec *codec, int enable) static void twl4030_init_chip(struct snd_soc_codec *codec) { + u8 *cache = codec->reg_cache; int i; /* clear CODECPDZ prior to setting register defaults */ @@ -211,7 +211,7 @@ static void twl4030_init_chip(struct snd_soc_codec *codec) /* set all audio section registers to reasonable defaults */ for (i = TWL4030_REG_OPTION; i <= TWL4030_REG_MISC_SET_2; i++) - twl4030_write(codec, i, twl4030_reg[i]); + twl4030_write(codec, i, cache[i]); } -- cgit v1.2.3 From eaf1ac8bb58888e0773c0b81dfedb9d7c0123a1d Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Mon, 1 Jun 2009 14:06:40 +0300 Subject: ASoC: TWL4030: Check the interface format for 4 channel mode In addition to the operating mode check, also check the codec's interface format in case of four channel mode. If the codec is not in TDM (DSP_A) mode, return with error. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index df474a5dd35..c53c7cabbd2 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -1608,9 +1608,15 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream, /* If the substream has 4 channel, do the necessary setup */ if (params_channels(params) == 4) { - /* Safety check: are we in the correct operating mode? */ - if ((twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE) & - TWL4030_OPTION_1)) + u8 format, mode; + + format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF); + mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE); + + /* Safety check: are we in the correct operating mode and + * the interface is in TDM mode? */ + if ((mode & TWL4030_OPTION_1) && + ((format & TWL4030_AIF_FORMAT) == TWL4030_AIF_FORMAT_TDM)) twl4030_tdm_enable(codec, substream->stream, 1); else return -EINVAL; -- cgit v1.2.3 From 2552a710f4b991136c650bf2a6d1b81f27f6273e Mon Sep 17 00:00:00 2001 From: Cliff Cai Date: Tue, 2 Jun 2009 00:18:53 -0400 Subject: ASoC: SSM2602: remove unsupported sample rates Signed-off-by: Cliff Cai Signed-off-by: Mike Frysinger Signed-off-by: Mark Brown --- sound/soc/codecs/ssm2602.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index 87f606c7682..d6af069b7ed 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -497,11 +497,9 @@ static int ssm2602_set_bias_level(struct snd_soc_codec *codec, return 0; } -#define SSM2602_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ - SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ - SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ - SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\ - SNDRV_PCM_RATE_96000) +#define SSM2602_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_32000 |\ + SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000) #define SSM2602_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) -- cgit v1.2.3 From 80d5bd93143439aff77fd246f5d06570b7a4641e Mon Sep 17 00:00:00 2001 From: Cliff Cai Date: Tue, 2 Jun 2009 00:18:56 -0400 Subject: ASoC: Blackfin: set the transfer size according the ac97_frame size Signed-off-by: Cliff Cai Signed-off-by: Mike Frysinger Signed-off-by: Bryan Wu Signed-off-by: Mark Brown --- sound/soc/blackfin/bf5xx-sport.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/blackfin/bf5xx-sport.c b/sound/soc/blackfin/bf5xx-sport.c index b7953c8cf83..469ce7fab20 100644 --- a/sound/soc/blackfin/bf5xx-sport.c +++ b/sound/soc/blackfin/bf5xx-sport.c @@ -190,7 +190,7 @@ static inline int sport_hook_rx_dummy(struct sport_device *sport) desc = get_dma_next_desc_ptr(sport->dma_rx_chan); /* Copy the descriptor which will be damaged to backup */ temp_desc = *desc; - desc->x_count = 0xa; + desc->x_count = sport->dummy_count / 2; desc->y_count = 0; desc->next_desc_addr = sport->dummy_rx_desc; local_irq_restore(flags); @@ -309,7 +309,7 @@ static inline int sport_hook_tx_dummy(struct sport_device *sport) desc = get_dma_next_desc_ptr(sport->dma_tx_chan); /* Store the descriptor which will be damaged */ temp_desc = *desc; - desc->x_count = 0xa; + desc->x_count = sport->dummy_count / 2; desc->y_count = 0; desc->next_desc_addr = sport->dummy_tx_desc; local_irq_restore(flags); -- cgit v1.2.3 From cf485da15a3b507c7dab42337639e4f4025d3373 Mon Sep 17 00:00:00 2001 From: Sonic Zhang Date: Tue, 2 Jun 2009 00:18:57 -0400 Subject: ASoC: Blackfin: document how anomaly 05000250 is handled Signed-off-by: Sonic Zhang Signed-off-by: Mike Frysinger Signed-off-by: Mark Brown --- sound/soc/blackfin/bf5xx-ac97.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/blackfin/bf5xx-ac97.c b/sound/soc/blackfin/bf5xx-ac97.c index 8a935f2d176..b1ed423fabd 100644 --- a/sound/soc/blackfin/bf5xx-ac97.c +++ b/sound/soc/blackfin/bf5xx-ac97.c @@ -31,6 +31,15 @@ #include "bf5xx-sport.h" #include "bf5xx-ac97.h" +/* Anomaly notes: + * 05000250 - AD1980 is running in TDM mode and RFS/TFS are generated by SPORT + * contrtoller. But, RFSDIV and TFSDIV are always set to 16*16-1, + * while the max AC97 data size is 13*16. The DIV is always larger + * than data size. AD73311 and ad2602 are not running in TDM mode. + * AD1836 and AD73322 depend on external RFS/TFS only. So, this + * anomaly does not affect blackfin sound drivers. +*/ + static int *cmd_count; static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM; -- cgit v1.2.3 From f692fce0cf8625b6cc8678e802fb0e2e657b1ca6 Mon Sep 17 00:00:00 2001 From: Cliff Cai Date: Tue, 2 Jun 2009 00:18:54 -0400 Subject: ASoC: SSM2602: assign last substream to the master when shutting down Fixes crash when shutting down. Signed-off-by: Cliff Cai Signed-off-by: Mike Frysinger Signed-off-by: Mark Brown --- sound/soc/codecs/ssm2602.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index d6af069b7ed..1fc4c8e0899 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -336,15 +336,17 @@ static int ssm2602_startup(struct snd_pcm_substream *substream, master_runtime->sample_bits, master_runtime->rate); - snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_RATE, - master_runtime->rate, - master_runtime->rate); - - snd_pcm_hw_constraint_minmax(substream->runtime, - SNDRV_PCM_HW_PARAM_SAMPLE_BITS, - master_runtime->sample_bits, - master_runtime->sample_bits); + if (master_runtime->rate != 0) + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, + master_runtime->rate, + master_runtime->rate); + + if (master_runtime->sample_bits != 0) + snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_SAMPLE_BITS, + master_runtime->sample_bits, + master_runtime->sample_bits); ssm2602->slave_substream = substream; } else @@ -372,6 +374,11 @@ static void ssm2602_shutdown(struct snd_pcm_substream *substream, struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; struct ssm2602_priv *ssm2602 = codec->private_data; + + if (ssm2602->master_substream == substream) + ssm2602->master_substream = ssm2602->slave_substream; + + ssm2602->slave_substream = NULL; /* deactivate */ if (!codec->active) ssm2602_write(codec, SSM2602_ACTIVE, 0); -- cgit v1.2.3 From d08664fdb50795b29cf70b0269ea02f7248e76c3 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 4 Jun 2009 09:58:18 +0200 Subject: ASoC: Fix build error in twl4030.c Fix the (likely cut-n-paste) error by commit 16a30fbb0d3aa4ee829a2dd3d0e314e2b5ae96a9, which causes the error below: sound/soc/codecs/twl4030.c: In function 'twl4030_read_reg_cache': sound/soc/codecs/twl4030.c:152: error: 'cache' undeclared (first use in this function) Signed-off-by: Takashi Iwai --- sound/soc/codecs/twl4030.c | 1 + 1 file changed, 1 insertion(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index c53c7cabbd2..4dbb853eef5 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -145,6 +145,7 @@ struct twl4030_priv { static inline unsigned int twl4030_read_reg_cache(struct snd_soc_codec *codec, unsigned int reg) { + u8 *cache = codec->reg_cache; if (reg >= TWL4030_CACHEREGNUM) return -EIO; -- cgit v1.2.3 From e3509ff0fb9df53e45cd68488e3b463a80455db7 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Wed, 3 Jun 2009 17:44:49 +0200 Subject: ASoC: fix NULL pointer dereference in soc_suspend() In case the initalization of an soc_device failed, there is no codec associated with it. soc_suspend() will still dereference the pointer and cause an Ooops when entering the sleep mode. This happens on our board with a multi-target kernel image when booted on a machine without audio circuits. This patch makes the code bail out very early in this special case. Signed-off-by: Daniel Mack Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 4aa8e2d3506..3f44150d8e3 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -628,6 +628,12 @@ static int soc_suspend(struct platform_device *pdev, pm_message_t state) struct snd_soc_codec *codec = card->codec; int i; + /* If the initialization of this soc device failed, there is no codec + * associated with it. Just bail out in this case. + */ + if (!codec) + return 0; + /* Due to the resume being scheduled into a workqueue we could * suspend before that's finished - wait for it to complete. */ -- cgit v1.2.3 From 37330efd4abb474b3fdfacea68beb37cf67564ed Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 23 Apr 2009 11:27:11 +0200 Subject: [ARM] pxa/palm: Add Palm27x aSoC driver to PalmTE2 Signed-off-by: Marek Vasut Signed-off-by: Eric Miao --- sound/soc/pxa/Kconfig | 7 ++++--- sound/soc/pxa/palm27x.c | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/pxa/Kconfig b/sound/soc/pxa/Kconfig index ad8a10fe629..96b2699abf6 100644 --- a/sound/soc/pxa/Kconfig +++ b/sound/soc/pxa/Kconfig @@ -98,13 +98,14 @@ config SND_PXA2XX_SOC_EM_X270 CompuLab EM-x270. config SND_PXA2XX_SOC_PALM27X - bool "SoC Audio support for Palm T|X, T5 and LifeDrive" - depends on SND_PXA2XX_SOC && (MACH_PALMLD || MACH_PALMTX || MACH_PALMT5) + bool "SoC Audio support for Palm T|X, T5, E2 and LifeDrive" + depends on SND_PXA2XX_SOC && (MACH_PALMLD || MACH_PALMTX || \ + MACH_PALMT5 || MACH_PALMTE2) select SND_PXA2XX_SOC_AC97 select SND_SOC_WM9712 help Say Y if you want to add support for SoC audio on - Palm T|X, T5 or LifeDrive handheld computer. + Palm T|X, T5, E2 or LifeDrive handheld computer. config SND_SOC_ZYLONITE tristate "SoC Audio support for Marvell Zylonite" diff --git a/sound/soc/pxa/palm27x.c b/sound/soc/pxa/palm27x.c index 44fcc4e01e0..e6102fda0a7 100644 --- a/sound/soc/pxa/palm27x.c +++ b/sound/soc/pxa/palm27x.c @@ -205,7 +205,7 @@ static int palm27x_asoc_probe(struct platform_device *pdev) int ret; if (!(machine_is_palmtx() || machine_is_palmt5() || - machine_is_palmld())) + machine_is_palmld() || machine_is_palmte2())) return -ENODEV; if (pdev->dev.platform_data) -- cgit v1.2.3 From ccff4b15e0847223de0a481f5b7fa5ef902cf3bd Mon Sep 17 00:00:00 2001 From: Troy Kisky Date: Fri, 5 Jun 2009 19:15:58 -0700 Subject: ASoC: codec tlv320aic23 fix bogus divide by 0 message Some code analyzer software mistakenly gives divide by 0 error messages for these lines. This patch will end its confusion. Signed-off-by: Troy Kisky Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic23.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/tlv320aic23.c b/sound/soc/codecs/tlv320aic23.c index 9fcbb9c7766..0b8dcb5cd72 100644 --- a/sound/soc/codecs/tlv320aic23.c +++ b/sound/soc/codecs/tlv320aic23.c @@ -273,14 +273,14 @@ static const unsigned short sr_valid_mask[] = { * Every divisor is a factor of 11*12 */ #define SR_MULT (11*12) -#define A(x) (x) ? (SR_MULT/x) : 0 +#define A(x) (SR_MULT/x) static const unsigned char sr_adc_mult_table[] = { - A(2), A(2), A(12), A(12), A(0), A(0), A(3), A(1), - A(2), A(2), A(11), A(11), A(0), A(0), A(0), A(1) + A(2), A(2), A(12), A(12), 0, 0, A(3), A(1), + A(2), A(2), A(11), A(11), 0, 0, 0, A(1) }; static const unsigned char sr_dac_mult_table[] = { - A(2), A(12), A(2), A(12), A(0), A(0), A(3), A(1), - A(2), A(11), A(2), A(11), A(0), A(0), A(0), A(1) + A(2), A(12), A(2), A(12), 0, 0, A(3), A(1), + A(2), A(11), A(2), A(11), 0, 0, 0, A(1) }; static unsigned get_score(int adc, int adc_l, int adc_h, int need_adc, -- cgit v1.2.3 From 74b8f955a73d20b1e22403fd1ef85834fbf38d98 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 6 Jun 2009 11:26:15 +0100 Subject: ASoC: Apostrophe patrol Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 39a63f958db..21c69074aa1 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -12,7 +12,7 @@ * Features: * o Changes power status of internal codec blocks depending on the * dynamic configuration of codec internal audio paths and active - * DAC's/ADC's. + * DACs/ADCs. * o Platform power domain - can support external components i.e. amps and * mic/meadphone insertion events. * o Automatic Mic Bias support @@ -220,7 +220,7 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, } } -/* connect mux widget to it's interconnecting audio paths */ +/* connect mux widget to its interconnecting audio paths */ static int dapm_connect_mux(struct snd_soc_codec *codec, struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest, struct snd_soc_dapm_path *path, const char *control_name, @@ -243,7 +243,7 @@ static int dapm_connect_mux(struct snd_soc_codec *codec, return -ENODEV; } -/* connect mixer widget to it's interconnecting audio paths */ +/* connect mixer widget to its interconnecting audio paths */ static int dapm_connect_mixer(struct snd_soc_codec *codec, struct snd_soc_dapm_widget *src, struct snd_soc_dapm_widget *dest, struct snd_soc_dapm_path *path, const char *control_name) @@ -1797,7 +1797,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_stream_event); * @codec: SoC codec * @pin: pin name * - * Enables input/output pin and it's parents or children widgets iff there is + * Enables input/output pin and its parents or children widgets iff there is * a valid audio route and active audio stream. * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to * do any widget power switching. @@ -1813,7 +1813,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_enable_pin); * @codec: SoC codec * @pin: pin name * - * Disables input/output pin and it's parents or children widgets. + * Disables input/output pin and its parents or children widgets. * NOTE: snd_soc_dapm_sync() needs to be called after this for DAPM to * do any widget power switching. */ -- cgit v1.2.3 From 38357ab2c83631728afa37a783c9b1bd474a0739 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 6 Jun 2009 19:03:23 +0100 Subject: ASoC: Sort DAPM power sequences while building lists In the past the DAPM power sequencing was done by iterating over the list of widgets once for each widget type and powering widgets of that type. Instead of doing that do the sorting at the time we insert the widgets into the lists of widgets to apply power changes to. This reduces the amount of computation required for seqencing still further, though the costs are generally dwarfed by the costs of the register writes implementing them. Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 109 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 71 insertions(+), 38 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 21c69074aa1..1b38e219559 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -52,19 +52,37 @@ /* dapm power sequences - make this per codec in the future */ static int dapm_up_seq[] = { - snd_soc_dapm_pre, snd_soc_dapm_supply, snd_soc_dapm_micbias, - snd_soc_dapm_mic, snd_soc_dapm_mux, snd_soc_dapm_value_mux, - snd_soc_dapm_dac, snd_soc_dapm_mixer, snd_soc_dapm_mixer_named_ctl, - snd_soc_dapm_pga, snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk, - snd_soc_dapm_post + [snd_soc_dapm_pre] = 0, + [snd_soc_dapm_supply] = 1, + [snd_soc_dapm_micbias] = 2, + [snd_soc_dapm_mic] = 3, + [snd_soc_dapm_mux] = 4, + [snd_soc_dapm_value_mux] = 5, + [snd_soc_dapm_dac] = 6, + [snd_soc_dapm_mixer] = 7, + [snd_soc_dapm_mixer_named_ctl] = 8, + [snd_soc_dapm_pga] = 9, + [snd_soc_dapm_adc] = 10, + [snd_soc_dapm_hp] = 11, + [snd_soc_dapm_spk] = 12, + [snd_soc_dapm_post] = 13, }; static int dapm_down_seq[] = { - snd_soc_dapm_pre, snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk, - snd_soc_dapm_pga, snd_soc_dapm_mixer_named_ctl, snd_soc_dapm_mixer, - snd_soc_dapm_dac, snd_soc_dapm_mic, snd_soc_dapm_micbias, - snd_soc_dapm_mux, snd_soc_dapm_value_mux, snd_soc_dapm_supply, - snd_soc_dapm_post + [snd_soc_dapm_pre] = 0, + [snd_soc_dapm_adc] = 1, + [snd_soc_dapm_hp] = 2, + [snd_soc_dapm_spk] = 3, + [snd_soc_dapm_pga] = 4, + [snd_soc_dapm_mixer_named_ctl] = 5, + [snd_soc_dapm_mixer] = 6, + [snd_soc_dapm_dac] = 7, + [snd_soc_dapm_mic] = 8, + [snd_soc_dapm_micbias] = 9, + [snd_soc_dapm_mux] = 10, + [snd_soc_dapm_value_mux] = 11, + [snd_soc_dapm_supply] = 12, + [snd_soc_dapm_post] = 13, }; static void pop_wait(u32 pop_time) @@ -738,6 +756,32 @@ static int dapm_power_widget(struct snd_soc_codec *codec, int event, } } +static int dapm_seq_compare(struct snd_soc_dapm_widget *a, + struct snd_soc_dapm_widget *b, + int sort[]) +{ + if (sort[a->id] != sort[b->id]) + return sort[a->id] - sort[b->id]; + + return 0; +} + +/* Insert a widget in order into a DAPM power sequence. */ +static void dapm_seq_insert(struct snd_soc_dapm_widget *new_widget, + struct list_head *list, + int sort[]) +{ + struct snd_soc_dapm_widget *w; + + list_for_each_entry(w, list, power_list) + if (dapm_seq_compare(new_widget, w, sort) < 0) { + list_add_tail(&new_widget->power_list, &w->power_list); + return; + } + + list_add_tail(&new_widget->power_list, list); +} + /* * Scan each dapm widget for complete audio path. * A complete path is a route that has valid endpoints i.e.:- @@ -752,7 +796,7 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) struct snd_soc_device *socdev = codec->socdev; struct snd_soc_dapm_widget *w; int ret = 0; - int i, power; + int power; int sys_power = 0; INIT_LIST_HEAD(&codec->up_list); @@ -764,10 +808,10 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) list_for_each_entry(w, &codec->dapm_widgets, list) { switch (w->id) { case snd_soc_dapm_pre: - list_add_tail(&codec->down_list, &w->power_list); + dapm_seq_insert(w, &codec->down_list, dapm_down_seq); break; case snd_soc_dapm_post: - list_add_tail(&codec->up_list, &w->power_list); + dapm_seq_insert(w, &codec->up_list, dapm_up_seq); break; default: @@ -782,10 +826,11 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) continue; if (power) - list_add_tail(&w->power_list, &codec->up_list); + dapm_seq_insert(w, &codec->up_list, + dapm_up_seq); else - list_add_tail(&w->power_list, - &codec->down_list); + dapm_seq_insert(w, &codec->down_list, + dapm_down_seq); w->power = power; break; @@ -802,31 +847,19 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) } /* Power down widgets first; try to avoid amplifying pops. */ - for (i = 0; i < ARRAY_SIZE(dapm_down_seq); i++) { - list_for_each_entry(w, &codec->down_list, power_list) { - /* is widget in stream order */ - if (w->id != dapm_down_seq[i]) - continue; - - ret = dapm_power_widget(codec, event, w); - if (ret != 0) - pr_err("Failed to power down %s: %d\n", - w->name, ret); - } + list_for_each_entry(w, &codec->down_list, power_list) { + ret = dapm_power_widget(codec, event, w); + if (ret != 0) + pr_err("Failed to power down %s: %d\n", + w->name, ret); } /* Now power up. */ - for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++) { - list_for_each_entry(w, &codec->up_list, power_list) { - /* is widget in stream order */ - if (w->id != dapm_up_seq[i]) - continue; - - ret = dapm_power_widget(codec, event, w); - if (ret != 0) - pr_err("Failed to power up %s: %d\n", - w->name, ret); - } + list_for_each_entry(w, &codec->up_list, power_list) { + ret = dapm_power_widget(codec, event, w); + if (ret != 0) + pr_err("Failed to power up %s: %d\n", + w->name, ret); } /* If we just powered the last thing off drop to standby bias */ -- cgit v1.2.3 From 163cac061c97394d4ef9c89efe5921dac937ddb8 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 7 Jun 2009 10:12:52 +0100 Subject: ASoC: Factor out DAPM sequence execution Lump the list walk into a single function, and pull in the power application too so we can do some further refactoring. Pure code motion. Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 122 +++++++++++++++++++++++++-------------------------- 1 file changed, 61 insertions(+), 61 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 1b38e219559..257d4f15e00 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -707,55 +707,6 @@ static int dapm_supply_check_power(struct snd_soc_dapm_widget *w) return power; } -/* - * Scan a single DAPM widget for a complete audio path and update the - * power status appropriately. - */ -static int dapm_power_widget(struct snd_soc_codec *codec, int event, - struct snd_soc_dapm_widget *w) -{ - int ret; - - switch (w->id) { - case snd_soc_dapm_pre: - if (!w->event) - return 0; - - if (event == SND_SOC_DAPM_STREAM_START) { - ret = w->event(w, - NULL, SND_SOC_DAPM_PRE_PMU); - if (ret < 0) - return ret; - } else if (event == SND_SOC_DAPM_STREAM_STOP) { - ret = w->event(w, - NULL, SND_SOC_DAPM_PRE_PMD); - if (ret < 0) - return ret; - } - return 0; - - case snd_soc_dapm_post: - if (!w->event) - return 0; - - if (event == SND_SOC_DAPM_STREAM_START) { - ret = w->event(w, - NULL, SND_SOC_DAPM_POST_PMU); - if (ret < 0) - return ret; - } else if (event == SND_SOC_DAPM_STREAM_STOP) { - ret = w->event(w, - NULL, SND_SOC_DAPM_POST_PMD); - if (ret < 0) - return ret; - } - return 0; - - default: - return dapm_generic_apply_power(w); - } -} - static int dapm_seq_compare(struct snd_soc_dapm_widget *a, struct snd_soc_dapm_widget *b, int sort[]) @@ -782,6 +733,65 @@ static void dapm_seq_insert(struct snd_soc_dapm_widget *new_widget, list_add_tail(&new_widget->power_list, list); } +/* Apply a DAPM power sequence */ +static void dapm_seq_run(struct snd_soc_codec *codec, struct list_head *list, + int event) +{ + struct snd_soc_dapm_widget *w; + int ret; + + list_for_each_entry(w, list, power_list) { + switch (w->id) { + case snd_soc_dapm_pre: + if (!w->event) + list_for_each_entry_continue(w, list, + power_list); + + if (event == SND_SOC_DAPM_STREAM_START) { + ret = w->event(w, + NULL, SND_SOC_DAPM_PRE_PMU); + if (ret < 0) + pr_err("PRE widget failed: %d\n", + ret); + } else if (event == SND_SOC_DAPM_STREAM_STOP) { + ret = w->event(w, + NULL, SND_SOC_DAPM_PRE_PMD); + if (ret < 0) + pr_err("PRE widget failed: %d\n", + ret); + } + break; + + case snd_soc_dapm_post: + if (!w->event) + list_for_each_entry_continue(w, list, + power_list); + + if (event == SND_SOC_DAPM_STREAM_START) { + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMU); + if (ret < 0) + pr_err("POST widget failed: %d\n", + ret); + } else if (event == SND_SOC_DAPM_STREAM_STOP) { + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMD); + if (ret < 0) + pr_err("POST widget failed: %d\n", + ret); + } + break; + + default: + ret = dapm_generic_apply_power(w); + if (ret < 0) + pr_err("Failed to apply widget power: %d\n", + ret); + break; + } + } +} + /* * Scan each dapm widget for complete audio path. * A complete path is a route that has valid endpoints i.e.:- @@ -847,20 +857,10 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) } /* Power down widgets first; try to avoid amplifying pops. */ - list_for_each_entry(w, &codec->down_list, power_list) { - ret = dapm_power_widget(codec, event, w); - if (ret != 0) - pr_err("Failed to power down %s: %d\n", - w->name, ret); - } + dapm_seq_run(codec, &codec->down_list, event); /* Now power up. */ - list_for_each_entry(w, &codec->up_list, power_list) { - ret = dapm_power_widget(codec, event, w); - if (ret != 0) - pr_err("Failed to power up %s: %d\n", - w->name, ret); - } + dapm_seq_run(codec, &codec->up_list, event); /* If we just powered the last thing off drop to standby bias */ if (codec->bias_level == SND_SOC_BIAS_PREPARE && !sys_power) { -- cgit v1.2.3 From 5204d49676dae3ae1f9dff5b60bf567d24680872 Mon Sep 17 00:00:00 2001 From: Chaithrika U S Date: Fri, 5 Jun 2009 06:28:23 -0400 Subject: ASoC: Introduce platform driver model for dm644x, dm355 Introduce the platform driver model to get platform data for dm355 and dm644x. Register platform driver and acquire the resources in the probe function Since the platform specific code had been moved from machine driver to dm.c Signed-off-by: Naresh Medisetty Signed-off-by: Chaithrika U S Signed-off-by: Mark Brown --- sound/soc/davinci/davinci-i2s.c | 117 +++++++++++++++++++++++----------------- 1 file changed, 67 insertions(+), 50 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c index b1ea52fc83c..500d2f57418 100644 --- a/sound/soc/davinci/davinci-i2s.c +++ b/sound/soc/davinci/davinci-i2s.c @@ -436,16 +436,40 @@ static int davinci_i2s_trigger(struct snd_pcm_substream *substream, int cmd, return ret; } -static int davinci_i2s_probe(struct platform_device *pdev, - struct snd_soc_dai *dai) +#define DAVINCI_I2S_RATES SNDRV_PCM_RATE_8000_96000 + +static struct snd_soc_dai_ops davinci_i2s_dai_ops = { + .startup = davinci_i2s_startup, + .trigger = davinci_i2s_trigger, + .hw_params = davinci_i2s_hw_params, + .set_fmt = davinci_i2s_set_dai_fmt, + +}; + +struct snd_soc_dai davinci_i2s_dai = { + .name = "davinci-i2s", + .id = 0, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = DAVINCI_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = DAVINCI_I2S_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE,}, + .ops = &davinci_i2s_dai_ops, + +}; +EXPORT_SYMBOL_GPL(davinci_i2s_dai); + +static int davinci_i2s_probe(struct platform_device *pdev) { - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_card *card = socdev->card; - struct snd_soc_dai *cpu_dai = card->dai_link->cpu_dai; + struct snd_platform_data *pdata = pdev->dev.platform_data; struct davinci_mcbsp_dev *dev; - struct resource *mem, *ioarea; - struct evm_snd_platform_data *pdata; - int ret; + struct resource *mem, *ioarea, *res; + int ret = 0; mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!mem) { @@ -466,9 +490,7 @@ static int davinci_i2s_probe(struct platform_device *pdev, goto err_release_region; } - cpu_dai->private_data = dev; - - dev->clk = clk_get(&pdev->dev, NULL); + dev->clk = clk_get(&pdev->dev, pdata->clk_name); if (IS_ERR(dev->clk)) { ret = -ENODEV; goto err_free_mem; @@ -476,18 +498,35 @@ static int davinci_i2s_probe(struct platform_device *pdev, clk_enable(dev->clk); dev->base = (void __iomem *)IO_ADDRESS(mem->start); - pdata = pdev->dev.platform_data; dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK] = &davinci_i2s_pcm_out; - dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK]->channel = pdata->tx_dma_ch; dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK]->dma_addr = (dma_addr_t)(io_v2p(dev->base) + DAVINCI_MCBSP_DXR_REG); dev->dma_params[SNDRV_PCM_STREAM_CAPTURE] = &davinci_i2s_pcm_in; - dev->dma_params[SNDRV_PCM_STREAM_CAPTURE]->channel = pdata->rx_dma_ch; dev->dma_params[SNDRV_PCM_STREAM_CAPTURE]->dma_addr = (dma_addr_t)(io_v2p(dev->base) + DAVINCI_MCBSP_DRR_REG); + /* first TX, then RX */ + res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!res) { + dev_err(&pdev->dev, "no DMA resource\n"); + goto err_free_mem; + } + dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK]->channel = res->start; + + res = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (!res) { + dev_err(&pdev->dev, "no DMA resource\n"); + goto err_free_mem; + } + dev->dma_params[SNDRV_PCM_STREAM_CAPTURE]->channel = res->start; + + davinci_i2s_dai.private_data = dev; + ret = snd_soc_register_dai(&davinci_i2s_dai); + if (ret != 0) + goto err_free_mem; + return 0; err_free_mem: @@ -498,62 +537,40 @@ err_release_region: return ret; } -static void davinci_i2s_remove(struct platform_device *pdev, - struct snd_soc_dai *dai) +static int davinci_i2s_remove(struct platform_device *pdev) { - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_card *card = socdev->card; - struct snd_soc_dai *cpu_dai = card->dai_link->cpu_dai; - struct davinci_mcbsp_dev *dev = cpu_dai->private_data; + struct davinci_mcbsp_dev *dev = davinci_i2s_dai.private_data; struct resource *mem; + snd_soc_unregister_dai(&davinci_i2s_dai); clk_disable(dev->clk); clk_put(dev->clk); dev->clk = NULL; - kfree(dev); - mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); release_mem_region(mem->start, (mem->end - mem->start) + 1); -} -#define DAVINCI_I2S_RATES SNDRV_PCM_RATE_8000_96000 - -static struct snd_soc_dai_ops davinci_i2s_dai_ops = { - .startup = davinci_i2s_startup, - .trigger = davinci_i2s_trigger, - .hw_params = davinci_i2s_hw_params, - .set_fmt = davinci_i2s_set_dai_fmt, -}; + return 0; +} -struct snd_soc_dai davinci_i2s_dai = { - .name = "davinci-i2s", - .id = 0, - .probe = davinci_i2s_probe, - .remove = davinci_i2s_remove, - .playback = { - .channels_min = 2, - .channels_max = 2, - .rates = DAVINCI_I2S_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, - .capture = { - .channels_min = 2, - .channels_max = 2, - .rates = DAVINCI_I2S_RATES, - .formats = SNDRV_PCM_FMTBIT_S16_LE,}, - .ops = &davinci_i2s_dai_ops, +static struct platform_driver davinci_mcbsp_driver = { + .probe = davinci_i2s_probe, + .remove = davinci_i2s_remove, + .driver = { + .name = "davinci-asp", + .owner = THIS_MODULE, + }, }; -EXPORT_SYMBOL_GPL(davinci_i2s_dai); static int __init davinci_i2s_init(void) { - return snd_soc_register_dai(&davinci_i2s_dai); + return platform_driver_register(&davinci_mcbsp_driver); } module_init(davinci_i2s_init); static void __exit davinci_i2s_exit(void) { - snd_soc_unregister_dai(&davinci_i2s_dai); + platform_driver_unregister(&davinci_mcbsp_driver); } module_exit(davinci_i2s_exit); -- cgit v1.2.3 From b67f4487295560599f6cca55fb7e8773ff27f00a Mon Sep 17 00:00:00 2001 From: Chaithrika U S Date: Fri, 5 Jun 2009 06:28:40 -0400 Subject: ASoC: Add mcasp support for DM646x Adds driver support for the two instances of McASP on TI's DM646x. The multichannel audio serial port (McASP) functions as a general-purpose audio serial port optimized for the needs of multichannel audio application. (http://www.ti.com/litv/pdf/spruer1b). There are two instances of McASP on DM646x. The McASP0 module includes up to 4 serializers that can be individually enabled to either transmit or receive in different modes. The McASP1 module is limited with only 1 pinned-out serializer that can be enabled to only transmit in DIT mode (neither receiving in any mode nor transmitting in either Burst or TDM mode is supported). McASP0 consists of transmit and receive sections that may operate synchronized, or completely independently with separate master clocks, bit clocks, and frame syncs, and using different transmit modes with different bit-stream formats. Signed-off-by: Steve Chen Signed-off-by: Pavel Kiryukhin Signed-off-by: Naresh Medisetty Signed-off-by: Chaithrika U S Signed-off-by: Mark Brown --- sound/soc/davinci/Kconfig | 13 + sound/soc/davinci/Makefile | 3 + sound/soc/davinci/davinci-mcasp.c | 874 ++++++++++++++++++++++++++++++++++++++ sound/soc/davinci/davinci-mcasp.h | 55 +++ sound/soc/davinci/davinci-pcm.h | 18 +- 5 files changed, 955 insertions(+), 8 deletions(-) create mode 100644 sound/soc/davinci/davinci-mcasp.c create mode 100644 sound/soc/davinci/davinci-mcasp.h (limited to 'sound/soc') diff --git a/sound/soc/davinci/Kconfig b/sound/soc/davinci/Kconfig index 411a710be66..6802dd5e473 100644 --- a/sound/soc/davinci/Kconfig +++ b/sound/soc/davinci/Kconfig @@ -9,6 +9,9 @@ config SND_DAVINCI_SOC config SND_DAVINCI_SOC_I2S tristate +config SND_DAVINCI_SOC_MCASP + tristate + config SND_DAVINCI_SOC_EVM tristate "SoC Audio support for DaVinci DM6446 or DM355 EVM" depends on SND_DAVINCI_SOC @@ -19,6 +22,16 @@ config SND_DAVINCI_SOC_EVM Say Y if you want to add support for SoC audio on TI DaVinci DM6446 or DM355 EVM platforms. +config SND_DM6467_SOC_EVM + tristate "SoC Audio support for DaVinci DM6467 EVM" + depends on SND_DAVINCI_SOC && MACH_DAVINCI_DM6467_EVM + select SND_DAVINCI_SOC_MCASP + select SND_SOC_TLV320AIC3X + select SND_SOC_SPDIF + + help + Say Y if you want to add support for SoC audio on TI + config SND_DAVINCI_SOC_SFFSDR tristate "SoC Audio support for SFFSDR" depends on SND_DAVINCI_SOC && MACH_SFFSDR diff --git a/sound/soc/davinci/Makefile b/sound/soc/davinci/Makefile index ca8bae1fc3f..67be54f3a3a 100644 --- a/sound/soc/davinci/Makefile +++ b/sound/soc/davinci/Makefile @@ -1,13 +1,16 @@ # DAVINCI Platform Support snd-soc-davinci-objs := davinci-pcm.o snd-soc-davinci-i2s-objs := davinci-i2s.o +snd-soc-davinci-mcasp-objs:= davinci-mcasp.o obj-$(CONFIG_SND_DAVINCI_SOC) += snd-soc-davinci.o obj-$(CONFIG_SND_DAVINCI_SOC_I2S) += snd-soc-davinci-i2s.o +obj-$(CONFIG_SND_DAVINCI_SOC_MCASP) += snd-soc-davinci-mcasp.o # DAVINCI Machine Support snd-soc-evm-objs := davinci-evm.o snd-soc-sffsdr-objs := davinci-sffsdr.o obj-$(CONFIG_SND_DAVINCI_SOC_EVM) += snd-soc-evm.o +obj-$(CONFIG_SND_DM6467_SOC_EVM) += snd-soc-evm.o obj-$(CONFIG_SND_DAVINCI_SOC_SFFSDR) += snd-soc-sffsdr.o diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c new file mode 100644 index 00000000000..b27aab60ece --- /dev/null +++ b/sound/soc/davinci/davinci-mcasp.c @@ -0,0 +1,874 @@ +/* + * ALSA SoC McASP Audio Layer for TI DAVINCI processor + * + * Multi-channel Audio Serial Port Driver + * + * Author: Nirmal Pandey , + * Suresh Rajashekara + * Steve Chen + * + * Copyright: (C) 2009 MontaVista Software, Inc., + * Copyright: (C) 2009 Texas Instruments, India + * + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "davinci-pcm.h" +#include "davinci-mcasp.h" + +/* + * McASP register definitions + */ +#define DAVINCI_MCASP_PID_REG 0x00 +#define DAVINCI_MCASP_PWREMUMGT_REG 0x04 + +#define DAVINCI_MCASP_PFUNC_REG 0x10 +#define DAVINCI_MCASP_PDIR_REG 0x14 +#define DAVINCI_MCASP_PDOUT_REG 0x18 +#define DAVINCI_MCASP_PDSET_REG 0x1c + +#define DAVINCI_MCASP_PDCLR_REG 0x20 + +#define DAVINCI_MCASP_TLGC_REG 0x30 +#define DAVINCI_MCASP_TLMR_REG 0x34 + +#define DAVINCI_MCASP_GBLCTL_REG 0x44 +#define DAVINCI_MCASP_AMUTE_REG 0x48 +#define DAVINCI_MCASP_LBCTL_REG 0x4c + +#define DAVINCI_MCASP_TXDITCTL_REG 0x50 + +#define DAVINCI_MCASP_GBLCTLR_REG 0x60 +#define DAVINCI_MCASP_RXMASK_REG 0x64 +#define DAVINCI_MCASP_RXFMT_REG 0x68 +#define DAVINCI_MCASP_RXFMCTL_REG 0x6c + +#define DAVINCI_MCASP_ACLKRCTL_REG 0x70 +#define DAVINCI_MCASP_AHCLKRCTL_REG 0x74 +#define DAVINCI_MCASP_RXTDM_REG 0x78 +#define DAVINCI_MCASP_EVTCTLR_REG 0x7c + +#define DAVINCI_MCASP_RXSTAT_REG 0x80 +#define DAVINCI_MCASP_RXTDMSLOT_REG 0x84 +#define DAVINCI_MCASP_RXCLKCHK_REG 0x88 +#define DAVINCI_MCASP_REVTCTL_REG 0x8c + +#define DAVINCI_MCASP_GBLCTLX_REG 0xa0 +#define DAVINCI_MCASP_TXMASK_REG 0xa4 +#define DAVINCI_MCASP_TXFMT_REG 0xa8 +#define DAVINCI_MCASP_TXFMCTL_REG 0xac + +#define DAVINCI_MCASP_ACLKXCTL_REG 0xb0 +#define DAVINCI_MCASP_AHCLKXCTL_REG 0xb4 +#define DAVINCI_MCASP_TXTDM_REG 0xb8 +#define DAVINCI_MCASP_EVTCTLX_REG 0xbc + +#define DAVINCI_MCASP_TXSTAT_REG 0xc0 +#define DAVINCI_MCASP_TXTDMSLOT_REG 0xc4 +#define DAVINCI_MCASP_TXCLKCHK_REG 0xc8 +#define DAVINCI_MCASP_XEVTCTL_REG 0xcc + +/* Left(even TDM Slot) Channel Status Register File */ +#define DAVINCI_MCASP_DITCSRA_REG 0x100 +/* Right(odd TDM slot) Channel Status Register File */ +#define DAVINCI_MCASP_DITCSRB_REG 0x118 +/* Left(even TDM slot) User Data Register File */ +#define DAVINCI_MCASP_DITUDRA_REG 0x130 +/* Right(odd TDM Slot) User Data Register File */ +#define DAVINCI_MCASP_DITUDRB_REG 0x148 + +/* Serializer n Control Register */ +#define DAVINCI_MCASP_XRSRCTL_BASE_REG 0x180 +#define DAVINCI_MCASP_XRSRCTL_REG(n) (DAVINCI_MCASP_XRSRCTL_BASE_REG + \ + (n << 2)) + +/* Transmit Buffer for Serializer n */ +#define DAVINCI_MCASP_TXBUF_REG 0x200 +/* Receive Buffer for Serializer n */ +#define DAVINCI_MCASP_RXBUF_REG 0x280 + + +/* + * DAVINCI_MCASP_PWREMUMGT_REG - Power Down and Emulation Management + * Register Bits + */ +#define MCASP_FREE BIT(0) +#define MCASP_SOFT BIT(1) + +/* + * DAVINCI_MCASP_PFUNC_REG - Pin Function / GPIO Enable Register Bits + */ +#define AXR(n) (1<private_data; + cpu_dai->dma_data = dev->dma_params[substream->stream]; + return 0; +} + +static void mcasp_start_rx(struct davinci_audio_dev *dev) +{ + mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXHCLKRST); + mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXCLKRST); + mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXSERCLR); + mcasp_set_reg(dev->base + DAVINCI_MCASP_RXBUF_REG, 0); + + mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXSMRST); + mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXFSRST); + mcasp_set_reg(dev->base + DAVINCI_MCASP_RXBUF_REG, 0); + + mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXSMRST); + mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, RXFSRST); +} + +static void mcasp_start_tx(struct davinci_audio_dev *dev) +{ + mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXHCLKRST); + mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXCLKRST); + mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXSERCLR); + mcasp_set_reg(dev->base + DAVINCI_MCASP_TXBUF_REG, 0); + + mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXSMRST); + mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXFSRST); + mcasp_set_reg(dev->base + DAVINCI_MCASP_TXBUF_REG, 0); + mcasp_set_reg(dev->base + DAVINCI_MCASP_TXBUF_REG, 0); +} + +static void davinci_mcasp_start(struct davinci_audio_dev *dev, int stream) +{ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + mcasp_start_tx(dev); + else + mcasp_start_rx(dev); +} + +static void mcasp_stop_rx(struct davinci_audio_dev *dev) +{ + mcasp_set_reg(dev->base + DAVINCI_MCASP_GBLCTLR_REG, 0); + mcasp_set_reg(dev->base + DAVINCI_MCASP_RXSTAT_REG, 0xFFFFFFFF); +} + +static void mcasp_stop_tx(struct davinci_audio_dev *dev) +{ + mcasp_set_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, 0); + mcasp_set_reg(dev->base + DAVINCI_MCASP_TXSTAT_REG, 0xFFFFFFFF); +} + +static void davinci_mcasp_stop(struct davinci_audio_dev *dev, int stream) +{ + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + mcasp_stop_tx(dev); + else + mcasp_stop_rx(dev); +} + +static int davinci_mcasp_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + struct davinci_audio_dev *dev = cpu_dai->private_data; + void __iomem *base = dev->base; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + /* codec is clock and frame slave */ + mcasp_set_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE); + mcasp_set_bits(base + DAVINCI_MCASP_TXFMCTL_REG, AFSXE); + + mcasp_set_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRE); + mcasp_set_bits(base + DAVINCI_MCASP_RXFMCTL_REG, AFSRE); + + mcasp_set_bits(base + DAVINCI_MCASP_PDIR_REG, (0x7 << 26)); + break; + + case SND_SOC_DAIFMT_CBM_CFM: + /* codec is clock and frame master */ + mcasp_clr_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE); + mcasp_clr_bits(base + DAVINCI_MCASP_TXFMCTL_REG, AFSXE); + + mcasp_clr_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRE); + mcasp_clr_bits(base + DAVINCI_MCASP_RXFMCTL_REG, AFSRE); + + mcasp_clr_bits(base + DAVINCI_MCASP_PDIR_REG, (0x3f << 26)); + break; + + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_NF: + mcasp_clr_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL); + mcasp_clr_bits(base + DAVINCI_MCASP_TXFMCTL_REG, FSXPOL); + + mcasp_set_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL); + mcasp_clr_bits(base + DAVINCI_MCASP_RXFMCTL_REG, FSRPOL); + break; + + case SND_SOC_DAIFMT_NB_IF: + mcasp_set_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL); + mcasp_set_bits(base + DAVINCI_MCASP_TXFMCTL_REG, FSXPOL); + + mcasp_clr_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL); + mcasp_set_bits(base + DAVINCI_MCASP_RXFMCTL_REG, FSRPOL); + break; + + case SND_SOC_DAIFMT_IB_IF: + mcasp_clr_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL); + mcasp_set_bits(base + DAVINCI_MCASP_TXFMCTL_REG, FSXPOL); + + mcasp_set_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL); + mcasp_set_bits(base + DAVINCI_MCASP_RXFMCTL_REG, FSRPOL); + break; + + case SND_SOC_DAIFMT_NB_NF: + mcasp_set_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXPOL); + mcasp_clr_bits(base + DAVINCI_MCASP_TXFMCTL_REG, FSXPOL); + + mcasp_clr_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRPOL); + mcasp_clr_bits(base + DAVINCI_MCASP_RXFMCTL_REG, FSRPOL); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int davinci_config_channel_size(struct davinci_audio_dev *dev, + int channel_size) +{ + u32 fmt = 0; + + switch (channel_size) { + case DAVINCI_AUDIO_WORD_8: + fmt = 0x03; + break; + + case DAVINCI_AUDIO_WORD_12: + fmt = 0x05; + break; + + case DAVINCI_AUDIO_WORD_16: + fmt = 0x07; + break; + + case DAVINCI_AUDIO_WORD_20: + fmt = 0x09; + break; + + case DAVINCI_AUDIO_WORD_24: + fmt = 0x0B; + break; + + case DAVINCI_AUDIO_WORD_28: + fmt = 0x0D; + break; + + case DAVINCI_AUDIO_WORD_32: + fmt = 0x0F; + break; + + default: + return -EINVAL; + } + + mcasp_mod_bits(dev->base + DAVINCI_MCASP_RXFMT_REG, + RXSSZ(fmt), RXSSZ(0x0F)); + mcasp_mod_bits(dev->base + DAVINCI_MCASP_TXFMT_REG, + TXSSZ(fmt), TXSSZ(0x0F)); + return 0; +} + +static void davinci_hw_common_param(struct davinci_audio_dev *dev, int stream) +{ + int i; + + /* Default configuration */ + mcasp_set_bits(dev->base + DAVINCI_MCASP_PWREMUMGT_REG, MCASP_SOFT); + + /* All PINS as McASP */ + mcasp_set_reg(dev->base + DAVINCI_MCASP_PFUNC_REG, 0x00000000); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + mcasp_set_reg(dev->base + DAVINCI_MCASP_TXSTAT_REG, 0xFFFFFFFF); + mcasp_clr_bits(dev->base + DAVINCI_MCASP_XEVTCTL_REG, + TXDATADMADIS); + } else { + mcasp_set_reg(dev->base + DAVINCI_MCASP_RXSTAT_REG, 0xFFFFFFFF); + mcasp_clr_bits(dev->base + DAVINCI_MCASP_REVTCTL_REG, + RXDATADMADIS); + } + + for (i = 0; i < dev->num_serializer; i++) { + mcasp_set_bits(dev->base + DAVINCI_MCASP_XRSRCTL_REG(i), + dev->serial_dir[i]); + if (dev->serial_dir[i] == TX_MODE) + mcasp_set_bits(dev->base + DAVINCI_MCASP_PDIR_REG, + AXR(i)); + else if (dev->serial_dir[i] == RX_MODE) + mcasp_clr_bits(dev->base + DAVINCI_MCASP_PDIR_REG, + AXR(i)); + } +} + +static void davinci_hw_param(struct davinci_audio_dev *dev, int stream) +{ + int i, active_slots; + u32 mask = 0; + + active_slots = (dev->tdm_slots > 31) ? 32 : dev->tdm_slots; + for (i = 0; i < active_slots; i++) + mask |= (1 << i); + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + /* bit stream is MSB first with no delay */ + /* DSP_B mode */ + mcasp_set_bits(dev->base + DAVINCI_MCASP_AHCLKXCTL_REG, + AHCLKXE); + mcasp_set_reg(dev->base + DAVINCI_MCASP_TXTDM_REG, mask); + mcasp_set_bits(dev->base + DAVINCI_MCASP_TXFMT_REG, TXORD); + + if ((dev->tdm_slots >= 2) || (dev->tdm_slots <= 32)) + mcasp_mod_bits(dev->base + DAVINCI_MCASP_TXFMCTL_REG, + FSXMOD(dev->tdm_slots), FSXMOD(0x1FF)); + else + printk(KERN_ERR "playback tdm slot %d not supported\n", + dev->tdm_slots); + + mcasp_set_reg(dev->base + DAVINCI_MCASP_TXMASK_REG, 0xFFFFFFFF); + mcasp_clr_bits(dev->base + DAVINCI_MCASP_TXFMCTL_REG, FSXDUR); + } else { + /* bit stream is MSB first with no delay */ + /* DSP_B mode */ + mcasp_set_bits(dev->base + DAVINCI_MCASP_RXFMT_REG, RXORD); + mcasp_set_bits(dev->base + DAVINCI_MCASP_AHCLKRCTL_REG, + AHCLKRE); + mcasp_set_reg(dev->base + DAVINCI_MCASP_RXTDM_REG, mask); + + if ((dev->tdm_slots >= 2) || (dev->tdm_slots <= 32)) + mcasp_mod_bits(dev->base + DAVINCI_MCASP_RXFMCTL_REG, + FSRMOD(dev->tdm_slots), FSRMOD(0x1FF)); + else + printk(KERN_ERR "capture tdm slot %d not supported\n", + dev->tdm_slots); + + mcasp_set_reg(dev->base + DAVINCI_MCASP_RXMASK_REG, 0xFFFFFFFF); + mcasp_clr_bits(dev->base + DAVINCI_MCASP_RXFMCTL_REG, FSRDUR); + } +} + +/* S/PDIF */ +static void davinci_hw_dit_param(struct davinci_audio_dev *dev) +{ + /* Set the PDIR for Serialiser as output */ + mcasp_set_bits(dev->base + DAVINCI_MCASP_PDIR_REG, AFSX); + + /* TXMASK for 24 bits */ + mcasp_set_reg(dev->base + DAVINCI_MCASP_TXMASK_REG, 0x00FFFFFF); + + /* Set the TX format : 24 bit right rotation, 32 bit slot, Pad 0 + and LSB first */ + mcasp_set_bits(dev->base + DAVINCI_MCASP_TXFMT_REG, + TXROT(6) | TXSSZ(15)); + + /* Set TX frame synch : DIT Mode, 1 bit width, internal, rising edge */ + mcasp_set_reg(dev->base + DAVINCI_MCASP_TXFMCTL_REG, + AFSXE | FSXMOD(0x180)); + + /* Set the TX tdm : for all the slots */ + mcasp_set_reg(dev->base + DAVINCI_MCASP_TXTDM_REG, 0xFFFFFFFF); + + /* Set the TX clock controls : div = 1 and internal */ + mcasp_set_bits(dev->base + DAVINCI_MCASP_ACLKXCTL_REG, + ACLKXE | TX_ASYNC); + + mcasp_clr_bits(dev->base + DAVINCI_MCASP_XEVTCTL_REG, TXDATADMADIS); + + /* Only 44100 and 48000 are valid, both have the same setting */ + mcasp_set_bits(dev->base + DAVINCI_MCASP_AHCLKXCTL_REG, AHCLKXDIV(3)); + + /* Enable the DIT */ + mcasp_set_bits(dev->base + DAVINCI_MCASP_TXDITCTL_REG, DITEN); +} + +static int davinci_mcasp_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *cpu_dai) +{ + struct davinci_audio_dev *dev = cpu_dai->private_data; + struct davinci_pcm_dma_params *dma_params = + dev->dma_params[substream->stream]; + int word_length; + + davinci_hw_common_param(dev, substream->stream); + + if (dev->op_mode == DAVINCI_MCASP_DIT_MODE) + davinci_hw_dit_param(dev); + else + davinci_hw_param(dev, substream->stream); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S8: + dma_params->data_type = 1; + word_length = DAVINCI_AUDIO_WORD_8; + break; + + case SNDRV_PCM_FORMAT_S16_LE: + dma_params->data_type = 2; + word_length = DAVINCI_AUDIO_WORD_16; + break; + + case SNDRV_PCM_FORMAT_S32_LE: + dma_params->data_type = 4; + word_length = DAVINCI_AUDIO_WORD_32; + break; + + default: + printk(KERN_WARNING "davinci-mcasp: unsupported PCM format"); + return -EINVAL; + } + davinci_config_channel_size(dev, word_length); + + return 0; +} + +static int davinci_mcasp_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *cpu_dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct davinci_audio_dev *dev = rtd->dai->cpu_dai->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + davinci_mcasp_start(dev, substream->stream); + break; + + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + davinci_mcasp_stop(dev, substream->stream); + break; + + default: + ret = -EINVAL; + } + + return ret; +} + +static struct snd_soc_dai_ops davinci_mcasp_dai_ops = { + .startup = davinci_mcasp_startup, + .trigger = davinci_mcasp_trigger, + .hw_params = davinci_mcasp_hw_params, + .set_fmt = davinci_mcasp_set_dai_fmt, + +}; + +struct snd_soc_dai davinci_mcasp_dai[] = { + { + .name = "davinci-i2s", + .id = 0, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = DAVINCI_MCASP_RATES, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = DAVINCI_MCASP_RATES, + .formats = SNDRV_PCM_FMTBIT_S8 | + SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S32_LE, + }, + .ops = &davinci_mcasp_dai_ops, + + }, + { + .name = "davinci-dit", + .id = 1, + .playback = { + .channels_min = 1, + .channels_max = 384, + .rates = DAVINCI_MCASP_RATES, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &davinci_mcasp_dai_ops, + }, + +}; +EXPORT_SYMBOL_GPL(davinci_mcasp_dai); + +static int davinci_mcasp_probe(struct platform_device *pdev) +{ + struct davinci_pcm_dma_params *dma_data; + struct resource *mem, *ioarea, *res; + struct snd_platform_data *pdata; + struct davinci_audio_dev *dev; + int count = 0; + int ret = 0; + + dev = kzalloc(sizeof(struct davinci_audio_dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dma_data = kzalloc(sizeof(struct davinci_pcm_dma_params) * 2, + GFP_KERNEL); + if (!dma_data) { + ret = -ENOMEM; + goto err_release_dev; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "no mem resource?\n"); + ret = -ENODEV; + goto err_release_data; + } + + ioarea = request_mem_region(mem->start, + (mem->end - mem->start) + 1, pdev->name); + if (!ioarea) { + dev_err(&pdev->dev, "Audio region already claimed\n"); + ret = -EBUSY; + goto err_release_data; + } + + pdata = pdev->dev.platform_data; + dev->clk = clk_get(&pdev->dev, pdata->clk_name); + if (IS_ERR(dev->clk)) { + ret = -ENODEV; + goto err_release_region; + } + + clk_enable(dev->clk); + + dev->base = (void __iomem *)IO_ADDRESS(mem->start); + dev->op_mode = pdata->op_mode; + dev->tdm_slots = pdata->tdm_slots; + dev->num_serializer = pdata->num_serializer; + dev->serial_dir = pdata->serial_dir; + dev->codec_fmt = pdata->codec_fmt; + + dma_data[count].name = "I2S PCM Stereo out"; + dma_data[count].eventq_no = pdata->eventq_no; + dma_data[count].dma_addr = (dma_addr_t) (pdata->tx_dma_offset + + io_v2p(dev->base)); + dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK] = &dma_data[count]; + + /* first TX, then RX */ + res = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!res) { + dev_err(&pdev->dev, "no DMA resource\n"); + goto err_release_region; + } + + dma_data[count].channel = res->start; + count++; + dma_data[count].name = "I2S PCM Stereo in"; + dma_data[count].eventq_no = pdata->eventq_no; + dma_data[count].dma_addr = (dma_addr_t)(pdata->rx_dma_offset + + io_v2p(dev->base)); + dev->dma_params[SNDRV_PCM_STREAM_CAPTURE] = &dma_data[count]; + + res = platform_get_resource(pdev, IORESOURCE_DMA, 1); + if (!res) { + dev_err(&pdev->dev, "no DMA resource\n"); + goto err_release_region; + } + + dma_data[count].channel = res->start; + davinci_mcasp_dai[pdev->id].private_data = dev; + davinci_mcasp_dai[pdev->id].dev = &pdev->dev; + ret = snd_soc_register_dai(&davinci_mcasp_dai[pdev->id]); + + if (ret != 0) + goto err_release_region; + return 0; + +err_release_region: + release_mem_region(mem->start, (mem->end - mem->start) + 1); +err_release_data: + kfree(dma_data); +err_release_dev: + kfree(dev); + + return ret; +} + +static int davinci_mcasp_remove(struct platform_device *pdev) +{ + struct davinci_pcm_dma_params *dma_data; + struct davinci_audio_dev *dev; + struct resource *mem; + + snd_soc_unregister_dai(&davinci_mcasp_dai[pdev->id]); + dev = davinci_mcasp_dai[pdev->id].private_data; + clk_disable(dev->clk); + clk_put(dev->clk); + dev->clk = NULL; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(mem->start, (mem->end - mem->start) + 1); + + dma_data = dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK]; + kfree(dma_data); + kfree(dev); + + return 0; +} + +static struct platform_driver davinci_mcasp_driver = { + .probe = davinci_mcasp_probe, + .remove = davinci_mcasp_remove, + .driver = { + .name = "davinci-mcasp", + .owner = THIS_MODULE, + }, +}; + +static int __init davinci_mcasp_init(void) +{ + return platform_driver_register(&davinci_mcasp_driver); +} +module_init(davinci_mcasp_init); + +static void __exit davinci_mcasp_exit(void) +{ + platform_driver_unregister(&davinci_mcasp_driver); +} +module_exit(davinci_mcasp_exit); + +MODULE_AUTHOR("Steve Chen"); +MODULE_DESCRIPTION("TI DAVINCI McASP SoC Interface"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/davinci/davinci-mcasp.h b/sound/soc/davinci/davinci-mcasp.h new file mode 100644 index 00000000000..36b71047a06 --- /dev/null +++ b/sound/soc/davinci/davinci-mcasp.h @@ -0,0 +1,55 @@ +/* + * ALSA SoC McASP Audio Layer for TI DAVINCI processor + * + * MCASP related definitions + * + * Author: Nirmal Pandey , + * Suresh Rajashekara + * Steve Chen + * + * Copyright: (C) 2009 MontaVista Software, Inc., + * Copyright: (C) 2009 Texas Instruments, India + * + * 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 DAVINCI_MCASP_H +#define DAVINCI_MCASP_H + +#include +#include +#include "davinci-pcm.h" + +extern struct snd_soc_dai davinci_mcasp_dai[]; + +#define DAVINCI_MCASP_RATES SNDRV_PCM_RATE_8000_96000 +#define DAVINCI_MCASP_I2S_DAI 0 +#define DAVINCI_MCASP_DIT_DAI 1 + +enum { + DAVINCI_AUDIO_WORD_8 = 0, + DAVINCI_AUDIO_WORD_12, + DAVINCI_AUDIO_WORD_16, + DAVINCI_AUDIO_WORD_20, + DAVINCI_AUDIO_WORD_24, + DAVINCI_AUDIO_WORD_32, + DAVINCI_AUDIO_WORD_28, /* This is only valid for McASP */ +}; + +struct davinci_audio_dev { + void __iomem *base; + int sample_rate; + struct clk *clk; + struct davinci_pcm_dma_params *dma_params[2]; + unsigned int codec_fmt; + + /* McASP specific data */ + int tdm_slots; + u8 op_mode; + u8 num_serializer; + u8 *serial_dir; +}; + +#endif /* DAVINCI_MCASP_H */ diff --git a/sound/soc/davinci/davinci-pcm.h b/sound/soc/davinci/davinci-pcm.h index 62cb4eb07e3..eb4287faa3d 100644 --- a/sound/soc/davinci/davinci-pcm.h +++ b/sound/soc/davinci/davinci-pcm.h @@ -12,17 +12,19 @@ #ifndef _DAVINCI_PCM_H #define _DAVINCI_PCM_H +#include +#include + + struct davinci_pcm_dma_params { - char *name; /* stream identifier */ - int channel; /* sync dma channel ID */ - dma_addr_t dma_addr; /* device physical address for DMA */ - unsigned int data_type; /* xfer data type */ + char *name; /* stream identifier */ + int channel; /* sync dma channel ID */ + dma_addr_t dma_addr; /* device physical address for DMA */ + enum dma_event_q eventq_no; /* event queue number */ + unsigned char data_type; /* xfer data type */ + unsigned char convert_mono_stereo; }; -struct evm_snd_platform_data { - int tx_dma_ch; - int rx_dma_ch; -}; extern struct snd_soc_platform davinci_soc_platform; -- cgit v1.2.3 From 04f80f5c486b39446af44e218dba90ec210d61ca Mon Sep 17 00:00:00 2001 From: Chaithrika U S Date: Fri, 5 Jun 2009 06:28:49 -0400 Subject: ASoC: Add machine driver support for DM646x This patch does the following: (1) Add support for the DM646x machine (2) Modifications required to introduce the platform driver model to get platform data for all the machines including dm355 and dm644x. Signed-off-by: Steve Chen Signed-off-by: Pavel Kiryukhin Signed-off-by: Naresh Medisetty Signed-off-by: Chaithrika U S Signed-off-by: Mark Brown --- sound/soc/davinci/davinci-evm.c | 102 +++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 54 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/davinci/davinci-evm.c b/sound/soc/davinci/davinci-evm.c index 58fd1cbedd8..f3bb6f60f20 100644 --- a/sound/soc/davinci/davinci-evm.c +++ b/sound/soc/davinci/davinci-evm.c @@ -27,9 +27,10 @@ #include #include "../codecs/tlv320aic3x.h" +#include "../codecs/spdif_transciever.h" #include "davinci-pcm.h" #include "davinci-i2s.h" - +#include "davinci-mcasp.h" #define AUDIO_FORMAT (SND_SOC_DAIFMT_DSP_B | \ SND_SOC_DAIFMT_CBM_CFM | SND_SOC_DAIFMT_IB_NF) @@ -43,7 +44,7 @@ static int evm_hw_params(struct snd_pcm_substream *substream, unsigned sysclk; /* ASP1 on DM355 EVM is clocked by an external oscillator */ - if (machine_is_davinci_dm355_evm()) + if (machine_is_davinci_dm355_evm() || machine_is_davinci_dm6467_evm()) sysclk = 27000000; /* ASP0 in DM6446 EVM is clocked by U55, as configured by @@ -144,6 +145,24 @@ static struct snd_soc_dai_link evm_dai = { .ops = &evm_ops, }; +static struct snd_soc_dai_link dm6467_evm_dai[] = { + { + .name = "TLV320AIC3X", + .stream_name = "AIC3X", + .cpu_dai = &davinci_mcasp_dai[DAVINCI_MCASP_I2S_DAI], + .codec_dai = &aic3x_dai, + .init = evm_aic3x_init, + .ops = &evm_ops, + }, + { + .name = "McASP", + .stream_name = "spdif", + .cpu_dai = &davinci_mcasp_dai[DAVINCI_MCASP_DIT_DAI], + .codec_dai = &dit_stub_dai, + .ops = &evm_ops, + }, +}; + /* davinci-evm audio machine driver */ static struct snd_soc_card snd_soc_card_evm = { .name = "DaVinci EVM", @@ -152,12 +171,26 @@ static struct snd_soc_card snd_soc_card_evm = { .num_links = 1, }; +/* davinci dm6467 evm audio machine driver */ +static struct snd_soc_card dm6467_snd_soc_card_evm = { + .name = "DaVinci DM6467 EVM", + .platform = &davinci_soc_platform, + .dai_link = dm6467_evm_dai, + .num_links = ARRAY_SIZE(dm6467_evm_dai), +}; + /* evm audio private data */ static struct aic3x_setup_data evm_aic3x_setup = { .i2c_bus = 1, .i2c_address = 0x1b, }; +/* dm6467 evm audio private data */ +static struct aic3x_setup_data dm6467_evm_aic3x_setup = { + .i2c_bus = 1, + .i2c_address = 0x18, +}; + /* evm audio subsystem */ static struct snd_soc_device evm_snd_devdata = { .card = &snd_soc_card_evm, @@ -165,60 +198,30 @@ static struct snd_soc_device evm_snd_devdata = { .codec_data = &evm_aic3x_setup, }; -/* DM6446 EVM uses ASP0; line-out is a pair of RCA jacks */ -static struct resource evm_snd_resources[] = { - { - .start = DAVINCI_ASP0_BASE, - .end = DAVINCI_ASP0_BASE + SZ_8K - 1, - .flags = IORESOURCE_MEM, - }, -}; - -static struct evm_snd_platform_data evm_snd_data = { - .tx_dma_ch = DAVINCI_DMA_ASP0_TX, - .rx_dma_ch = DAVINCI_DMA_ASP0_RX, -}; - -/* DM335 EVM uses ASP1; line-out is a stereo mini-jack */ -static struct resource dm335evm_snd_resources[] = { - { - .start = DAVINCI_ASP1_BASE, - .end = DAVINCI_ASP1_BASE + SZ_8K - 1, - .flags = IORESOURCE_MEM, - }, -}; - -static struct evm_snd_platform_data dm335evm_snd_data = { - .tx_dma_ch = DAVINCI_DMA_ASP1_TX, - .rx_dma_ch = DAVINCI_DMA_ASP1_RX, +/* evm audio subsystem */ +static struct snd_soc_device dm6467_evm_snd_devdata = { + .card = &dm6467_snd_soc_card_evm, + .codec_dev = &soc_codec_dev_aic3x, + .codec_data = &dm6467_evm_aic3x_setup, }; static struct platform_device *evm_snd_device; static int __init evm_init(void) { - struct resource *resources; - unsigned num_resources; - struct evm_snd_platform_data *data; + struct snd_soc_device *evm_snd_dev_data; int index; int ret; if (machine_is_davinci_evm()) { - davinci_cfg_reg(DM644X_MCBSP); - - resources = evm_snd_resources; - num_resources = ARRAY_SIZE(evm_snd_resources); - data = &evm_snd_data; + evm_snd_dev_data = &evm_snd_devdata; index = 0; } else if (machine_is_davinci_dm355_evm()) { - /* we don't use ASP1 IRQs, or we'd need to mux them ... */ - davinci_cfg_reg(DM355_EVT8_ASP1_TX); - davinci_cfg_reg(DM355_EVT9_ASP1_RX); - - resources = dm335evm_snd_resources; - num_resources = ARRAY_SIZE(dm335evm_snd_resources); - data = &dm335evm_snd_data; + evm_snd_dev_data = &evm_snd_devdata; index = 1; + } else if (machine_is_davinci_dm6467_evm()) { + evm_snd_dev_data = &dm6467_evm_snd_devdata; + index = 0; } else return -EINVAL; @@ -226,17 +229,8 @@ static int __init evm_init(void) if (!evm_snd_device) return -ENOMEM; - platform_set_drvdata(evm_snd_device, &evm_snd_devdata); - evm_snd_devdata.dev = &evm_snd_device->dev; - platform_device_add_data(evm_snd_device, data, sizeof(*data)); - - ret = platform_device_add_resources(evm_snd_device, resources, - num_resources); - if (ret) { - platform_device_put(evm_snd_device); - return ret; - } - + platform_set_drvdata(evm_snd_device, evm_snd_dev_data); + evm_snd_dev_data->dev = &evm_snd_device->dev; ret = platform_device_add(evm_snd_device); if (ret) platform_device_put(evm_snd_device); -- cgit v1.2.3 From 46f5822f7841697d4aedaf4672661d7a765172cd Mon Sep 17 00:00:00 2001 From: Daniel Ribeiro Date: Sun, 7 Jun 2009 02:49:11 -0300 Subject: ASoC: Allow 32 bit registers for DAPM Replace the remaining unsigned shorts with unsigned ints. Tested with pcap2 codec (25 bits registers). Signed-off-by: Daniel Ribeiro Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 28 ++++++++++++++-------------- sound/soc/soc-dapm.c | 16 ++++++++-------- 2 files changed, 22 insertions(+), 22 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 3f44150d8e3..e1a920cd895 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1264,10 +1264,10 @@ EXPORT_SYMBOL_GPL(snd_soc_free_ac97_codec); * Returns 1 for change else 0. */ int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg, - unsigned short mask, unsigned short value) + unsigned int mask, unsigned int value) { int change; - unsigned short old, new; + unsigned int old, new; mutex_lock(&io_mutex); old = snd_soc_read(codec, reg); @@ -1294,10 +1294,10 @@ EXPORT_SYMBOL_GPL(snd_soc_update_bits); * Returns 1 for change else 0. */ int snd_soc_test_bits(struct snd_soc_codec *codec, unsigned short reg, - unsigned short mask, unsigned short value) + unsigned int mask, unsigned int value) { int change; - unsigned short old, new; + unsigned int old, new; mutex_lock(&io_mutex); old = snd_soc_read(codec, reg); @@ -1583,7 +1583,7 @@ int snd_soc_get_enum_double(struct snd_kcontrol *kcontrol, { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - unsigned short val, bitmask; + unsigned int val, bitmask; for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; @@ -1612,8 +1612,8 @@ int snd_soc_put_enum_double(struct snd_kcontrol *kcontrol, { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - unsigned short val; - unsigned short mask, bitmask; + unsigned int val; + unsigned int mask, bitmask; for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; @@ -1649,7 +1649,7 @@ int snd_soc_get_value_enum_double(struct snd_kcontrol *kcontrol, { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - unsigned short reg_val, val, mux; + unsigned int reg_val, val, mux; reg_val = snd_soc_read(codec, e->reg); val = (reg_val >> e->shift_l) & e->mask; @@ -1688,8 +1688,8 @@ int snd_soc_put_value_enum_double(struct snd_kcontrol *kcontrol, { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - unsigned short val; - unsigned short mask; + unsigned int val; + unsigned int mask; if (ucontrol->value.enumerated.item[0] > e->max - 1) return -EINVAL; @@ -1849,7 +1849,7 @@ int snd_soc_put_volsw(struct snd_kcontrol *kcontrol, int max = mc->max; unsigned int mask = (1 << fls(max)) - 1; unsigned int invert = mc->invert; - unsigned short val, val2, val_mask; + unsigned int val, val2, val_mask; val = (ucontrol->value.integer.value[0] & mask); if (invert) @@ -1915,7 +1915,7 @@ int snd_soc_get_volsw_2r(struct snd_kcontrol *kcontrol, unsigned int reg2 = mc->rreg; unsigned int shift = mc->shift; int max = mc->max; - unsigned int mask = (1<invert; ucontrol->value.integer.value[0] = @@ -1955,7 +1955,7 @@ int snd_soc_put_volsw_2r(struct snd_kcontrol *kcontrol, unsigned int mask = (1 << fls(max)) - 1; unsigned int invert = mc->invert; int err; - unsigned short val, val2, val_mask; + unsigned int val, val2, val_mask; val_mask = mask << shift; val = (ucontrol->value.integer.value[0] & mask); @@ -2047,7 +2047,7 @@ int snd_soc_put_volsw_s8(struct snd_kcontrol *kcontrol, struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); unsigned int reg = mc->reg; int min = mc->min; - unsigned short val; + unsigned int val; val = (ucontrol->value.integer.value[0]+min) & 0xff; val |= ((ucontrol->value.integer.value[1]+min) & 0xff) << 8; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 21c69074aa1..7ad8afa8553 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -268,7 +268,7 @@ static int dapm_connect_mixer(struct snd_soc_codec *codec, static int dapm_update_bits(struct snd_soc_dapm_widget *widget) { int change, power; - unsigned short old, new; + unsigned int old, new; struct snd_soc_codec *codec = widget->codec; /* check for valid widgets */ @@ -1372,7 +1372,7 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, int max = mc->max; unsigned int mask = (1 << fls(max)) - 1; unsigned int invert = mc->invert; - unsigned short val, val2, val_mask; + unsigned int val, val2, val_mask; int ret; val = (ucontrol->value.integer.value[0] & mask); @@ -1436,7 +1436,7 @@ int snd_soc_dapm_get_enum_double(struct snd_kcontrol *kcontrol, { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - unsigned short val, bitmask; + unsigned int val, bitmask; for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; @@ -1464,8 +1464,8 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - unsigned short val, mux; - unsigned short mask, bitmask; + unsigned int val, mux; + unsigned int mask, bitmask; int ret = 0; for (bitmask = 1; bitmask < e->max; bitmask <<= 1) @@ -1523,7 +1523,7 @@ int snd_soc_dapm_get_value_enum_double(struct snd_kcontrol *kcontrol, { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - unsigned short reg_val, val, mux; + unsigned int reg_val, val, mux; reg_val = snd_soc_read(widget->codec, e->reg); val = (reg_val >> e->shift_l) & e->mask; @@ -1563,8 +1563,8 @@ int snd_soc_dapm_put_value_enum_double(struct snd_kcontrol *kcontrol, { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - unsigned short val, mux; - unsigned short mask; + unsigned int val, mux; + unsigned int mask; int ret = 0; if (ucontrol->value.enumerated.item[0] > e->max - 1) -- cgit v1.2.3 From b22ead2a510fdb30440753f90237e86fdac70fae Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 7 Jun 2009 12:51:26 +0100 Subject: ASoC: Coalesce register writes for DAPM sequences Reduce the number of register writes we need to set the power state for a CODEC by coalescing updates to widgets with the same sequence order and same register into a single write. This can be a noticable performance improvement with slow or heavily contended control buses, such as I2C controllers with a low clock frequency, and is particularly noticable when resuming. It can also reduce the noticability of and pops and clicks by ensuring that left and right channels are powered simultaneously if they are in the same register. Currently widgets that have events are not coalesced, including PGAs which may use the volume ramping control. Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 136 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 104 insertions(+), 32 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 257d4f15e00..66f07cdfb2f 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -713,6 +713,8 @@ static int dapm_seq_compare(struct snd_soc_dapm_widget *a, { if (sort[a->id] != sort[b->id]) return sort[a->id] - sort[b->id]; + if (a->reg != b->reg) + return a->reg - b->reg; return 0; } @@ -733,63 +735,133 @@ static void dapm_seq_insert(struct snd_soc_dapm_widget *new_widget, list_add_tail(&new_widget->power_list, list); } -/* Apply a DAPM power sequence */ -static void dapm_seq_run(struct snd_soc_codec *codec, struct list_head *list, - int event) +/* Apply the coalesced changes from a DAPM sequence */ +static void dapm_seq_run_coalesced(struct snd_soc_codec *codec, + struct list_head *pending) { struct snd_soc_dapm_widget *w; + int reg, power; + unsigned int value = 0; + unsigned int mask = 0; + unsigned int cur_mask; + + reg = list_first_entry(pending, struct snd_soc_dapm_widget, + power_list)->reg; + + list_for_each_entry(w, pending, power_list) { + cur_mask = 1 << w->shift; + BUG_ON(reg != w->reg); + + if (w->invert) + power = !w->power; + else + power = w->power; + + mask |= cur_mask; + if (power) + value |= cur_mask; + + pop_dbg(codec->pop_time, + "pop test : Queue %s: reg=0x%x, 0x%x/0x%x\n", + w->name, reg, value, mask); + } + + pop_dbg(codec->pop_time, + "pop test : Applying 0x%x/0x%x to %x in %dms\n", + value, mask, reg, codec->pop_time); + pop_wait(codec->pop_time); + snd_soc_update_bits(codec, reg, mask, value); +} + +/* Apply a DAPM power sequence. + * + * We walk over a pre-sorted list of widgets to apply power to. In + * order to minimise the number of writes to the device required + * multiple widgets will be updated in a single write where possible. + * Currently anything that requires more than a single write is not + * handled. + */ +static void dapm_seq_run(struct snd_soc_codec *codec, struct list_head *list, + int event, int sort[]) +{ + struct snd_soc_dapm_widget *w, *n; + LIST_HEAD(pending); + int cur_sort = -1; + int cur_reg = SND_SOC_NOPM; int ret; - list_for_each_entry(w, list, power_list) { + list_for_each_entry_safe(w, n, list, power_list) { + ret = 0; + + /* Do we need to apply any queued changes? */ + if (sort[w->id] != cur_sort || w->reg != cur_reg) { + if (!list_empty(&pending)) + dapm_seq_run_coalesced(codec, &pending); + + INIT_LIST_HEAD(&pending); + cur_sort = -1; + cur_reg = SND_SOC_NOPM; + } + switch (w->id) { case snd_soc_dapm_pre: if (!w->event) - list_for_each_entry_continue(w, list, - power_list); + list_for_each_entry_safe_continue(w, n, list, + power_list); - if (event == SND_SOC_DAPM_STREAM_START) { + if (event == SND_SOC_DAPM_STREAM_START) ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMU); - if (ret < 0) - pr_err("PRE widget failed: %d\n", - ret); - } else if (event == SND_SOC_DAPM_STREAM_STOP) { + else if (event == SND_SOC_DAPM_STREAM_STOP) ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD); - if (ret < 0) - pr_err("PRE widget failed: %d\n", - ret); - } break; case snd_soc_dapm_post: if (!w->event) - list_for_each_entry_continue(w, list, - power_list); + list_for_each_entry_safe_continue(w, n, list, + power_list); - if (event == SND_SOC_DAPM_STREAM_START) { + if (event == SND_SOC_DAPM_STREAM_START) ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMU); - if (ret < 0) - pr_err("POST widget failed: %d\n", - ret); - } else if (event == SND_SOC_DAPM_STREAM_STOP) { + else if (event == SND_SOC_DAPM_STREAM_STOP) ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMD); - if (ret < 0) - pr_err("POST widget failed: %d\n", - ret); - } break; - default: + case snd_soc_dapm_input: + case snd_soc_dapm_output: + case snd_soc_dapm_hp: + case snd_soc_dapm_mic: + case snd_soc_dapm_line: + case snd_soc_dapm_spk: + /* No register support currently */ + case snd_soc_dapm_pga: + /* Don't coalsece these yet due to gain ramping */ ret = dapm_generic_apply_power(w); - if (ret < 0) - pr_err("Failed to apply widget power: %d\n", - ret); break; + + default: + /* If there's an event or an invalid register + * then run immediately, otherwise store the + * updates so that we can coalesce. */ + if (w->reg >= 0 && !w->event) { + cur_sort = sort[w->id]; + cur_reg = w->reg; + list_move(&w->power_list, &pending); + } else { + ret = dapm_generic_apply_power(w); + } } + + if (ret < 0) + pr_err("Failed to apply widget power: %d\n", + ret); } + + if (!list_empty(&pending)) + dapm_seq_run_coalesced(codec, &pending); } /* @@ -857,10 +929,10 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) } /* Power down widgets first; try to avoid amplifying pops. */ - dapm_seq_run(codec, &codec->down_list, event); + dapm_seq_run(codec, &codec->down_list, event, dapm_down_seq); /* Now power up. */ - dapm_seq_run(codec, &codec->up_list, event); + dapm_seq_run(codec, &codec->up_list, event, dapm_up_seq); /* If we just powered the last thing off drop to standby bias */ if (codec->bias_level == SND_SOC_BIAS_PREPARE && !sys_power) { -- cgit v1.2.3 From e3d4dabd2d9b74778f6f15a830eb3a0027bb3799 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 7 Jun 2009 13:08:45 +0100 Subject: ASoC: Sort specialised mixers and muxes together The more flexible value muxes and named mixers don't need to be sorted differently from a power management point of view, they are different only in terms of the control interface and not in terms of seqencing behaviour. Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 66f07cdfb2f..9187db18a04 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -57,15 +57,15 @@ static int dapm_up_seq[] = { [snd_soc_dapm_micbias] = 2, [snd_soc_dapm_mic] = 3, [snd_soc_dapm_mux] = 4, - [snd_soc_dapm_value_mux] = 5, - [snd_soc_dapm_dac] = 6, - [snd_soc_dapm_mixer] = 7, - [snd_soc_dapm_mixer_named_ctl] = 8, - [snd_soc_dapm_pga] = 9, - [snd_soc_dapm_adc] = 10, - [snd_soc_dapm_hp] = 11, - [snd_soc_dapm_spk] = 12, - [snd_soc_dapm_post] = 13, + [snd_soc_dapm_value_mux] = 4, + [snd_soc_dapm_dac] = 5, + [snd_soc_dapm_mixer] = 6, + [snd_soc_dapm_mixer_named_ctl] = 6, + [snd_soc_dapm_pga] = 7, + [snd_soc_dapm_adc] = 8, + [snd_soc_dapm_hp] = 9, + [snd_soc_dapm_spk] = 10, + [snd_soc_dapm_post] = 11, }; static int dapm_down_seq[] = { @@ -75,14 +75,14 @@ static int dapm_down_seq[] = { [snd_soc_dapm_spk] = 3, [snd_soc_dapm_pga] = 4, [snd_soc_dapm_mixer_named_ctl] = 5, - [snd_soc_dapm_mixer] = 6, - [snd_soc_dapm_dac] = 7, - [snd_soc_dapm_mic] = 8, - [snd_soc_dapm_micbias] = 9, - [snd_soc_dapm_mux] = 10, - [snd_soc_dapm_value_mux] = 11, - [snd_soc_dapm_supply] = 12, - [snd_soc_dapm_post] = 13, + [snd_soc_dapm_mixer] = 5, + [snd_soc_dapm_dac] = 6, + [snd_soc_dapm_mic] = 7, + [snd_soc_dapm_micbias] = 8, + [snd_soc_dapm_mux] = 9, + [snd_soc_dapm_value_mux] = 9, + [snd_soc_dapm_supply] = 10, + [snd_soc_dapm_post] = 11, }; static void pop_wait(u32 pop_time) -- cgit v1.2.3 From 81628103dd8527d99ea39b054a3f002d5859d7c3 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 7 Jun 2009 13:21:24 +0100 Subject: ASoC: Coalesce power updates for DAPM widgets with events Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 76 +++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 16 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 9187db18a04..3fc791c28aa 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -740,7 +740,7 @@ static void dapm_seq_run_coalesced(struct snd_soc_codec *codec, struct list_head *pending) { struct snd_soc_dapm_widget *w; - int reg, power; + int reg, power, ret; unsigned int value = 0; unsigned int mask = 0; unsigned int cur_mask; @@ -764,13 +764,62 @@ static void dapm_seq_run_coalesced(struct snd_soc_codec *codec, pop_dbg(codec->pop_time, "pop test : Queue %s: reg=0x%x, 0x%x/0x%x\n", w->name, reg, value, mask); + + /* power up pre event */ + if (w->power && w->event && + (w->event_flags & SND_SOC_DAPM_PRE_PMU)) { + pop_dbg(codec->pop_time, "pop test : %s PRE_PMU\n", + w->name); + ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMU); + if (ret < 0) + pr_err("%s: pre event failed: %d\n", + w->name, ret); + } + + /* power down pre event */ + if (!w->power && w->event && + (w->event_flags & SND_SOC_DAPM_PRE_PMD)) { + pop_dbg(codec->pop_time, "pop test : %s PRE_PMD\n", + w->name); + ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD); + if (ret < 0) + pr_err("%s: pre event failed: %d\n", + w->name, ret); + } + } + + if (reg >= 0) { + pop_dbg(codec->pop_time, + "pop test : Applying 0x%x/0x%x to %x in %dms\n", + value, mask, reg, codec->pop_time); + pop_wait(codec->pop_time); + snd_soc_update_bits(codec, reg, mask, value); } - pop_dbg(codec->pop_time, - "pop test : Applying 0x%x/0x%x to %x in %dms\n", - value, mask, reg, codec->pop_time); - pop_wait(codec->pop_time); - snd_soc_update_bits(codec, reg, mask, value); + list_for_each_entry(w, pending, power_list) { + /* power up post event */ + if (w->power && w->event && + (w->event_flags & SND_SOC_DAPM_POST_PMU)) { + pop_dbg(codec->pop_time, "pop test : %s POST_PMU\n", + w->name); + ret = w->event(w, + NULL, SND_SOC_DAPM_POST_PMU); + if (ret < 0) + pr_err("%s: post event failed: %d\n", + w->name, ret); + } + + /* power down post event */ + if (!w->power && w->event && + (w->event_flags & SND_SOC_DAPM_POST_PMD)) { + pop_dbg(codec->pop_time, "pop test : %s POST_PMD\n", + w->name); + ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMD); + if (ret < 0) + pr_err("%s: post event failed: %d\n", + w->name, ret); + } + } } /* Apply a DAPM power sequence. @@ -843,16 +892,11 @@ static void dapm_seq_run(struct snd_soc_codec *codec, struct list_head *list, break; default: - /* If there's an event or an invalid register - * then run immediately, otherwise store the - * updates so that we can coalesce. */ - if (w->reg >= 0 && !w->event) { - cur_sort = sort[w->id]; - cur_reg = w->reg; - list_move(&w->power_list, &pending); - } else { - ret = dapm_generic_apply_power(w); - } + /* Queue it up for application */ + cur_sort = sort[w->id]; + cur_reg = w->reg; + list_move(&w->power_list, &pending); + break; } if (ret < 0) -- cgit v1.2.3 From 4f1c1923851f9734c972812121e80a3b04ab3af4 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 7 Jun 2009 13:37:44 +0100 Subject: ASoC: Coalesce power updates for PGAs Handle gain ramping for PGAs so we can coalesce their power updates too. This is not ideal since we can't cope properly with gain ramping for stereo paths but that was the case without coalescing and gain ramping is relatively infrequently used so the effects are limited. Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 3fc791c28aa..7299ce405b2 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -786,6 +786,10 @@ static void dapm_seq_run_coalesced(struct snd_soc_codec *codec, pr_err("%s: pre event failed: %d\n", w->name, ret); } + + /* Lower PGA volume to reduce pops */ + if (w->id == snd_soc_dapm_pga && !w->power) + dapm_set_pga(w, w->power); } if (reg >= 0) { @@ -797,6 +801,10 @@ static void dapm_seq_run_coalesced(struct snd_soc_codec *codec, } list_for_each_entry(w, pending, power_list) { + /* Raise PGA volume to reduce pops */ + if (w->id == snd_soc_dapm_pga && w->power) + dapm_set_pga(w, w->power); + /* power up post event */ if (w->power && w->event && (w->event_flags & SND_SOC_DAPM_POST_PMU)) { @@ -886,8 +894,6 @@ static void dapm_seq_run(struct snd_soc_codec *codec, struct list_head *list, case snd_soc_dapm_line: case snd_soc_dapm_spk: /* No register support currently */ - case snd_soc_dapm_pga: - /* Don't coalsece these yet due to gain ramping */ ret = dapm_generic_apply_power(w); break; -- cgit v1.2.3 From 291f3bbcacf278726911c713e14cedb71c486b16 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 7 Jun 2009 13:57:17 +0100 Subject: ASoC: Make DAPM power sequence lists local variables They are now only accessed within dapm_power_widgets() so can be local to that function. Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 7299ce405b2..1c30da1535b 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -927,23 +927,22 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) { struct snd_soc_device *socdev = codec->socdev; struct snd_soc_dapm_widget *w; + LIST_HEAD(up_list); + LIST_HEAD(down_list); int ret = 0; int power; int sys_power = 0; - INIT_LIST_HEAD(&codec->up_list); - INIT_LIST_HEAD(&codec->down_list); - /* Check which widgets we need to power and store them in * lists indicating if they should be powered up or down. */ list_for_each_entry(w, &codec->dapm_widgets, list) { switch (w->id) { case snd_soc_dapm_pre: - dapm_seq_insert(w, &codec->down_list, dapm_down_seq); + dapm_seq_insert(w, &down_list, dapm_down_seq); break; case snd_soc_dapm_post: - dapm_seq_insert(w, &codec->up_list, dapm_up_seq); + dapm_seq_insert(w, &up_list, dapm_up_seq); break; default: @@ -958,11 +957,9 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) continue; if (power) - dapm_seq_insert(w, &codec->up_list, - dapm_up_seq); + dapm_seq_insert(w, &up_list, dapm_up_seq); else - dapm_seq_insert(w, &codec->down_list, - dapm_down_seq); + dapm_seq_insert(w, &down_list, dapm_down_seq); w->power = power; break; @@ -979,10 +976,10 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) } /* Power down widgets first; try to avoid amplifying pops. */ - dapm_seq_run(codec, &codec->down_list, event, dapm_down_seq); + dapm_seq_run(codec, &down_list, event, dapm_down_seq); /* Now power up. */ - dapm_seq_run(codec, &codec->up_list, event, dapm_up_seq); + dapm_seq_run(codec, &up_list, event, dapm_up_seq); /* If we just powered the last thing off drop to standby bias */ if (codec->bias_level == SND_SOC_BIAS_PREPARE && !sys_power) { -- cgit v1.2.3 From efd13be09e2db4ac4efa2c6101c4f50ee1ead4b0 Mon Sep 17 00:00:00 2001 From: Chaithrika U S Date: Mon, 8 Jun 2009 06:49:41 -0400 Subject: ASoC: Minor fixes to DaVinci I2S probe function Assign proper errors when platform resource claims fail. Signed-off-by: Chaithrika U S Signed-off-by: Mark Brown --- sound/soc/davinci/davinci-i2s.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c index 500d2f57418..2f11970e640 100644 --- a/sound/soc/davinci/davinci-i2s.c +++ b/sound/soc/davinci/davinci-i2s.c @@ -469,7 +469,7 @@ static int davinci_i2s_probe(struct platform_device *pdev) struct snd_platform_data *pdata = pdev->dev.platform_data; struct davinci_mcbsp_dev *dev; struct resource *mem, *ioarea, *res; - int ret = 0; + int ret; mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!mem) { @@ -511,6 +511,7 @@ static int davinci_i2s_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_DMA, 0); if (!res) { dev_err(&pdev->dev, "no DMA resource\n"); + ret = -ENXIO; goto err_free_mem; } dev->dma_params[SNDRV_PCM_STREAM_PLAYBACK]->channel = res->start; @@ -518,6 +519,7 @@ static int davinci_i2s_probe(struct platform_device *pdev) res = platform_get_resource(pdev, IORESOURCE_DMA, 1); if (!res) { dev_err(&pdev->dev, "no DMA resource\n"); + ret = -ENXIO; goto err_free_mem; } dev->dma_params[SNDRV_PCM_STREAM_CAPTURE]->channel = res->start; -- cgit v1.2.3 From 236e6723bedb483b2ebf73ada01e2c853c5cac01 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 10 Jun 2009 13:55:34 +0100 Subject: ASoC: Fix lm4857 control Commit 4eaa9819dc08d7bfd1065ce530e31b18a119dcaf changed semantics of private_value member of kcontrol. This resulted in inability to control amplifier and subsequently in very low output volume. Tested-by: Johannes Schauer Signed-off-by: Paul Fertser Signed-off-by: Mark Brown --- sound/soc/s3c24xx/neo1973_wm8753.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/neo1973_wm8753.c b/sound/soc/s3c24xx/neo1973_wm8753.c index 289fadf60b1..906709e6dd5 100644 --- a/sound/soc/s3c24xx/neo1973_wm8753.c +++ b/sound/soc/s3c24xx/neo1973_wm8753.c @@ -345,9 +345,11 @@ static void lm4857_write_regs(void) static int lm4857_get_reg(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - int reg = kcontrol->private_value & 0xFF; - int shift = (kcontrol->private_value >> 8) & 0x0F; - int mask = (kcontrol->private_value >> 16) & 0xFF; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int reg = mc->reg; + int shift = mc->shift; + int mask = mc->max; pr_debug("Entered %s\n", __func__); @@ -358,9 +360,11 @@ static int lm4857_get_reg(struct snd_kcontrol *kcontrol, static int lm4857_set_reg(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - int reg = kcontrol->private_value & 0xFF; - int shift = (kcontrol->private_value >> 8) & 0x0F; - int mask = (kcontrol->private_value >> 16) & 0xFF; + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int reg = mc->reg; + int shift = mc->shift; + int mask = mc->max; if (((lm4857_regs[reg] >> shift) & mask) == ucontrol->value.integer.value[0]) -- cgit v1.2.3 From fa44c077eb2a17aa6913feceb493d13f45f3fa25 Mon Sep 17 00:00:00 2001 From: Daniel Ribeiro Date: Wed, 10 Jun 2009 15:23:24 -0300 Subject: ASoC: remove duplicated code on pxa-ssp.c * We don't need to write the registers twice, remove the first write. * DAIFMT_INV switch is duplicated inside DAIFMT_FORMAT switch, move it out. (This patch is for Mark's for-2.6.32 branch, I have not checked if the code is duplicated on current 2.6.30) Signed-off-by: Daniel Ribeiro Signed-off-by: Mark Brown --- sound/soc/pxa/pxa-ssp.c | 50 +++++++++++++++---------------------------------- 1 file changed, 15 insertions(+), 35 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index 19c45409d94..e22c5cef8fe 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -457,31 +457,27 @@ static int pxa_ssp_set_dai_fmt(struct snd_soc_dai *cpu_dai, return -EINVAL; } - ssp_write_reg(ssp, SSCR0, sscr0); - ssp_write_reg(ssp, SSCR1, sscr1); - ssp_write_reg(ssp, SSPSP, sspsp); + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + sspsp |= SSPSP_SFRMP; + break; + case SND_SOC_DAIFMT_NB_IF: + break; + case SND_SOC_DAIFMT_IB_IF: + sspsp |= SSPSP_SCMODE(2); + break; + case SND_SOC_DAIFMT_IB_NF: + sspsp |= SSPSP_SCMODE(2) | SSPSP_SFRMP; + break; + default: + return -EINVAL; + } switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: sscr0 |= SSCR0_PSP; sscr1 |= SSCR1_RWOT | SSCR1_TRAIL; - /* See hw_params() */ - switch (fmt & SND_SOC_DAIFMT_INV_MASK) { - case SND_SOC_DAIFMT_NB_NF: - sspsp |= SSPSP_SFRMP; - break; - case SND_SOC_DAIFMT_NB_IF: - break; - case SND_SOC_DAIFMT_IB_IF: - sspsp |= SSPSP_SCMODE(2); - break; - case SND_SOC_DAIFMT_IB_NF: - sspsp |= SSPSP_SCMODE(2) | SSPSP_SFRMP; - break; - default: - return -EINVAL; - } break; case SND_SOC_DAIFMT_DSP_A: @@ -489,22 +485,6 @@ static int pxa_ssp_set_dai_fmt(struct snd_soc_dai *cpu_dai, case SND_SOC_DAIFMT_DSP_B: sscr0 |= SSCR0_MOD | SSCR0_PSP; sscr1 |= SSCR1_TRAIL | SSCR1_RWOT; - - switch (fmt & SND_SOC_DAIFMT_INV_MASK) { - case SND_SOC_DAIFMT_NB_NF: - sspsp |= SSPSP_SFRMP; - break; - case SND_SOC_DAIFMT_NB_IF: - break; - case SND_SOC_DAIFMT_IB_IF: - sspsp |= SSPSP_SCMODE(2); - break; - case SND_SOC_DAIFMT_IB_NF: - sspsp |= SSPSP_SCMODE(2) | SSPSP_SFRMP; - break; - default: - return -EINVAL; - } break; default: -- cgit v1.2.3 From 74dc55ed5b709e4a2a538252f98ea62358df82ce Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 9 Jun 2009 09:55:51 +0100 Subject: ASoC: Add WM8961 driver The WM8961 is a low power, high quality stereo CODEC designed for portable digital applications with headphone and stereo class D speaker drivers. Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm8961.c | 1309 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm8961.h | 866 ++++++++++++++++++++++++++++++ 4 files changed, 2181 insertions(+) create mode 100644 sound/soc/codecs/wm8961.c create mode 100644 sound/soc/codecs/wm8961.h (limited to 'sound/soc') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index bbc97fd7664..021dbdfa5b9 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -39,6 +39,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8903 if I2C select SND_SOC_WM8940 if I2C select SND_SOC_WM8960 if I2C + select SND_SOC_WM8961 if I2C select SND_SOC_WM8971 if I2C select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8990 if I2C @@ -156,6 +157,9 @@ config SND_SOC_WM8940 config SND_SOC_WM8960 tristate +config SND_SOC_WM8961 + tristate + config SND_SOC_WM8971 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 8b7530546f4..e520c2b7f0e 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -27,6 +27,7 @@ snd-soc-wm8900-objs := wm8900.o snd-soc-wm8903-objs := wm8903.o snd-soc-wm8940-objs := wm8940.o snd-soc-wm8960-objs := wm8960.o +snd-soc-wm8961-objs := wm8961.o snd-soc-wm8971-objs := wm8971.o snd-soc-wm8988-objs := wm8988.o snd-soc-wm8990-objs := wm8990.o @@ -65,6 +66,7 @@ obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o obj-$(CONFIG_SND_SOC_WM8940) += snd-soc-wm8940.o obj-$(CONFIG_SND_SOC_WM8960) += snd-soc-wm8960.o +obj-$(CONFIG_SND_SOC_WM8961) += snd-soc-wm8961.o obj-$(CONFIG_SND_SOC_WM8988) += snd-soc-wm8988.o obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o obj-$(CONFIG_SND_SOC_WM9081) += snd-soc-wm9081.o diff --git a/sound/soc/codecs/wm8961.c b/sound/soc/codecs/wm8961.c new file mode 100644 index 00000000000..8e78959ca40 --- /dev/null +++ b/sound/soc/codecs/wm8961.c @@ -0,0 +1,1309 @@ +/* + * wm8961.c -- WM8961 ALSA SoC Audio driver + * + * Author: Mark Brown + * + * 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. + * + * Currently unimplemented features: + * - ALC + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8961.h" + +#define WM8961_MAX_REGISTER 0xFC + +static u16 wm8961_reg_defaults[] = { + 0x009F, /* R0 - Left Input volume */ + 0x009F, /* R1 - Right Input volume */ + 0x0000, /* R2 - LOUT1 volume */ + 0x0000, /* R3 - ROUT1 volume */ + 0x0020, /* R4 - Clocking1 */ + 0x0008, /* R5 - ADC & DAC Control 1 */ + 0x0000, /* R6 - ADC & DAC Control 2 */ + 0x000A, /* R7 - Audio Interface 0 */ + 0x01F4, /* R8 - Clocking2 */ + 0x0000, /* R9 - Audio Interface 1 */ + 0x00FF, /* R10 - Left DAC volume */ + 0x00FF, /* R11 - Right DAC volume */ + 0x0000, /* R12 */ + 0x0000, /* R13 */ + 0x0040, /* R14 - Audio Interface 2 */ + 0x0000, /* R15 - Software Reset */ + 0x0000, /* R16 */ + 0x007B, /* R17 - ALC1 */ + 0x0000, /* R18 - ALC2 */ + 0x0032, /* R19 - ALC3 */ + 0x0000, /* R20 - Noise Gate */ + 0x00C0, /* R21 - Left ADC volume */ + 0x00C0, /* R22 - Right ADC volume */ + 0x0120, /* R23 - Additional control(1) */ + 0x0000, /* R24 - Additional control(2) */ + 0x0000, /* R25 - Pwr Mgmt (1) */ + 0x0000, /* R26 - Pwr Mgmt (2) */ + 0x0000, /* R27 - Additional Control (3) */ + 0x0000, /* R28 - Anti-pop */ + 0x0000, /* R29 */ + 0x005F, /* R30 - Clocking 3 */ + 0x0000, /* R31 */ + 0x0000, /* R32 - ADCL signal path */ + 0x0000, /* R33 - ADCR signal path */ + 0x0000, /* R34 */ + 0x0000, /* R35 */ + 0x0000, /* R36 */ + 0x0000, /* R37 */ + 0x0000, /* R38 */ + 0x0000, /* R39 */ + 0x0000, /* R40 - LOUT2 volume */ + 0x0000, /* R41 - ROUT2 volume */ + 0x0000, /* R42 */ + 0x0000, /* R43 */ + 0x0000, /* R44 */ + 0x0000, /* R45 */ + 0x0000, /* R46 */ + 0x0000, /* R47 - Pwr Mgmt (3) */ + 0x0023, /* R48 - Additional Control (4) */ + 0x0000, /* R49 - Class D Control 1 */ + 0x0000, /* R50 */ + 0x0003, /* R51 - Class D Control 2 */ + 0x0000, /* R52 */ + 0x0000, /* R53 */ + 0x0000, /* R54 */ + 0x0000, /* R55 */ + 0x0106, /* R56 - Clocking 4 */ + 0x0000, /* R57 - DSP Sidetone 0 */ + 0x0000, /* R58 - DSP Sidetone 1 */ + 0x0000, /* R59 */ + 0x0000, /* R60 - DC Servo 0 */ + 0x0000, /* R61 - DC Servo 1 */ + 0x0000, /* R62 */ + 0x015E, /* R63 - DC Servo 3 */ + 0x0010, /* R64 */ + 0x0010, /* R65 - DC Servo 5 */ + 0x0000, /* R66 */ + 0x0001, /* R67 */ + 0x0003, /* R68 - Analogue PGA Bias */ + 0x0000, /* R69 - Analogue HP 0 */ + 0x0060, /* R70 */ + 0x01FB, /* R71 - Analogue HP 2 */ + 0x0000, /* R72 - Charge Pump 1 */ + 0x0065, /* R73 */ + 0x005F, /* R74 */ + 0x0059, /* R75 */ + 0x006B, /* R76 */ + 0x0038, /* R77 */ + 0x000C, /* R78 */ + 0x000A, /* R79 */ + 0x006B, /* R80 */ + 0x0000, /* R81 */ + 0x0000, /* R82 - Charge Pump B */ + 0x0087, /* R83 */ + 0x0000, /* R84 */ + 0x005C, /* R85 */ + 0x0000, /* R86 */ + 0x0000, /* R87 - Write Sequencer 1 */ + 0x0000, /* R88 - Write Sequencer 2 */ + 0x0000, /* R89 - Write Sequencer 3 */ + 0x0000, /* R90 - Write Sequencer 4 */ + 0x0000, /* R91 - Write Sequencer 5 */ + 0x0000, /* R92 - Write Sequencer 6 */ + 0x0000, /* R93 - Write Sequencer 7 */ + 0x0000, /* R94 */ + 0x0000, /* R95 */ + 0x0000, /* R96 */ + 0x0000, /* R97 */ + 0x0000, /* R98 */ + 0x0000, /* R99 */ + 0x0000, /* R100 */ + 0x0000, /* R101 */ + 0x0000, /* R102 */ + 0x0000, /* R103 */ + 0x0000, /* R104 */ + 0x0000, /* R105 */ + 0x0000, /* R106 */ + 0x0000, /* R107 */ + 0x0000, /* R108 */ + 0x0000, /* R109 */ + 0x0000, /* R110 */ + 0x0000, /* R111 */ + 0x0000, /* R112 */ + 0x0000, /* R113 */ + 0x0000, /* R114 */ + 0x0000, /* R115 */ + 0x0000, /* R116 */ + 0x0000, /* R117 */ + 0x0000, /* R118 */ + 0x0000, /* R119 */ + 0x0000, /* R120 */ + 0x0000, /* R121 */ + 0x0000, /* R122 */ + 0x0000, /* R123 */ + 0x0000, /* R124 */ + 0x0000, /* R125 */ + 0x0000, /* R126 */ + 0x0000, /* R127 */ + 0x0000, /* R128 */ + 0x0000, /* R129 */ + 0x0000, /* R130 */ + 0x0000, /* R131 */ + 0x0000, /* R132 */ + 0x0000, /* R133 */ + 0x0000, /* R134 */ + 0x0000, /* R135 */ + 0x0000, /* R136 */ + 0x0000, /* R137 */ + 0x0000, /* R138 */ + 0x0000, /* R139 */ + 0x0000, /* R140 */ + 0x0000, /* R141 */ + 0x0000, /* R142 */ + 0x0000, /* R143 */ + 0x0000, /* R144 */ + 0x0000, /* R145 */ + 0x0000, /* R146 */ + 0x0000, /* R147 */ + 0x0000, /* R148 */ + 0x0000, /* R149 */ + 0x0000, /* R150 */ + 0x0000, /* R151 */ + 0x0000, /* R152 */ + 0x0000, /* R153 */ + 0x0000, /* R154 */ + 0x0000, /* R155 */ + 0x0000, /* R156 */ + 0x0000, /* R157 */ + 0x0000, /* R158 */ + 0x0000, /* R159 */ + 0x0000, /* R160 */ + 0x0000, /* R161 */ + 0x0000, /* R162 */ + 0x0000, /* R163 */ + 0x0000, /* R164 */ + 0x0000, /* R165 */ + 0x0000, /* R166 */ + 0x0000, /* R167 */ + 0x0000, /* R168 */ + 0x0000, /* R169 */ + 0x0000, /* R170 */ + 0x0000, /* R171 */ + 0x0000, /* R172 */ + 0x0000, /* R173 */ + 0x0000, /* R174 */ + 0x0000, /* R175 */ + 0x0000, /* R176 */ + 0x0000, /* R177 */ + 0x0000, /* R178 */ + 0x0000, /* R179 */ + 0x0000, /* R180 */ + 0x0000, /* R181 */ + 0x0000, /* R182 */ + 0x0000, /* R183 */ + 0x0000, /* R184 */ + 0x0000, /* R185 */ + 0x0000, /* R186 */ + 0x0000, /* R187 */ + 0x0000, /* R188 */ + 0x0000, /* R189 */ + 0x0000, /* R190 */ + 0x0000, /* R191 */ + 0x0000, /* R192 */ + 0x0000, /* R193 */ + 0x0000, /* R194 */ + 0x0000, /* R195 */ + 0x0030, /* R196 */ + 0x0006, /* R197 */ + 0x0000, /* R198 */ + 0x0060, /* R199 */ + 0x0000, /* R200 */ + 0x003F, /* R201 */ + 0x0000, /* R202 */ + 0x0000, /* R203 */ + 0x0000, /* R204 */ + 0x0001, /* R205 */ + 0x0000, /* R206 */ + 0x0181, /* R207 */ + 0x0005, /* R208 */ + 0x0008, /* R209 */ + 0x0008, /* R210 */ + 0x0000, /* R211 */ + 0x013B, /* R212 */ + 0x0000, /* R213 */ + 0x0000, /* R214 */ + 0x0000, /* R215 */ + 0x0000, /* R216 */ + 0x0070, /* R217 */ + 0x0000, /* R218 */ + 0x0000, /* R219 */ + 0x0000, /* R220 */ + 0x0000, /* R221 */ + 0x0000, /* R222 */ + 0x0003, /* R223 */ + 0x0000, /* R224 */ + 0x0000, /* R225 */ + 0x0001, /* R226 */ + 0x0008, /* R227 */ + 0x0000, /* R228 */ + 0x0000, /* R229 */ + 0x0000, /* R230 */ + 0x0000, /* R231 */ + 0x0004, /* R232 */ + 0x0000, /* R233 */ + 0x0000, /* R234 */ + 0x0000, /* R235 */ + 0x0000, /* R236 */ + 0x0000, /* R237 */ + 0x0080, /* R238 */ + 0x0000, /* R239 */ + 0x0000, /* R240 */ + 0x0000, /* R241 */ + 0x0000, /* R242 */ + 0x0000, /* R243 */ + 0x0000, /* R244 */ + 0x0052, /* R245 */ + 0x0110, /* R246 */ + 0x0040, /* R247 */ + 0x0000, /* R248 */ + 0x0030, /* R249 */ + 0x0000, /* R250 */ + 0x0000, /* R251 */ + 0x0001, /* R252 - General test 1 */ +}; + +struct wm8961_priv { + struct snd_soc_codec codec; + int sysclk; + u16 reg_cache[WM8961_MAX_REGISTER]; +}; + +static int wm8961_reg_is_volatile(int reg) +{ + switch (reg) { + case WM8961_WRITE_SEQUENCER_7: + case WM8961_DC_SERVO_1: + return 1; + + default: + return 0; + } +} + +static unsigned int wm8961_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + BUG_ON(reg > WM8961_MAX_REGISTER); + return cache[reg]; +} + +static unsigned int wm8961_read_hw(struct snd_soc_codec *codec, u8 reg) +{ + struct i2c_msg xfer[2]; + u16 data; + int ret; + struct i2c_client *client = codec->control_data; + + BUG_ON(reg > WM8961_MAX_REGISTER); + + /* Write register */ + xfer[0].addr = client->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = ® + + /* Read data */ + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 2; + xfer[1].buf = (u8 *)&data; + + ret = i2c_transfer(client->adapter, xfer, 2); + if (ret != 2) { + dev_err(&client->dev, "i2c_transfer() returned %d\n", ret); + return 0; + } + + return (data >> 8) | ((data & 0xff) << 8); +} + +static unsigned int wm8961_read(struct snd_soc_codec *codec, unsigned int reg) +{ + if (wm8961_reg_is_volatile(reg)) + return wm8961_read_hw(codec, reg); + else + return wm8961_read_reg_cache(codec, reg); +} + +static int wm8961_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u16 *cache = codec->reg_cache; + u8 data[3]; + + BUG_ON(reg > WM8961_MAX_REGISTER); + + if (!wm8961_reg_is_volatile(reg)) + cache[reg] = value; + + data[0] = reg; + data[1] = value >> 8; + data[2] = value & 0x00ff; + + if (codec->hw_write(codec->control_data, data, 3) == 3) + return 0; + else + return -EIO; +} + +static int wm8961_reset(struct snd_soc_codec *codec) +{ + return wm8961_write(codec, WM8961_SOFTWARE_RESET, 0); +} + +/* + * The headphone output supports special anti-pop sequences giving + * silent power up and power down. + */ +static int wm8961_hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 hp_reg = wm8961_read(codec, WM8961_ANALOGUE_HP_0); + u16 cp_reg = wm8961_read(codec, WM8961_CHARGE_PUMP_1); + u16 pwr_reg = wm8961_read(codec, WM8961_PWR_MGMT_2); + u16 dcs_reg = wm8961_read(codec, WM8961_DC_SERVO_1); + int timeout = 500; + + if (event & SND_SOC_DAPM_POST_PMU) { + /* Make sure the output is shorted */ + hp_reg &= ~(WM8961_HPR_RMV_SHORT | WM8961_HPL_RMV_SHORT); + wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + + /* Enable the charge pump */ + cp_reg |= WM8961_CP_ENA; + wm8961_write(codec, WM8961_CHARGE_PUMP_1, cp_reg); + mdelay(5); + + /* Enable the PGA */ + pwr_reg |= WM8961_LOUT1_PGA | WM8961_ROUT1_PGA; + wm8961_write(codec, WM8961_PWR_MGMT_2, pwr_reg); + + /* Enable the amplifier */ + hp_reg |= WM8961_HPR_ENA | WM8961_HPL_ENA; + wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + + /* Second stage enable */ + hp_reg |= WM8961_HPR_ENA_DLY | WM8961_HPL_ENA_DLY; + wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + + /* Enable the DC servo & trigger startup */ + dcs_reg |= + WM8961_DCS_ENA_CHAN_HPR | WM8961_DCS_TRIG_STARTUP_HPR | + WM8961_DCS_ENA_CHAN_HPL | WM8961_DCS_TRIG_STARTUP_HPL; + dev_dbg(codec->dev, "Enabling DC servo\n"); + + wm8961_write(codec, WM8961_DC_SERVO_1, dcs_reg); + do { + msleep(1); + dcs_reg = wm8961_read(codec, WM8961_DC_SERVO_1); + } while (--timeout && + dcs_reg & (WM8961_DCS_TRIG_STARTUP_HPR | + WM8961_DCS_TRIG_STARTUP_HPL)); + if (dcs_reg & (WM8961_DCS_TRIG_STARTUP_HPR | + WM8961_DCS_TRIG_STARTUP_HPL)) + dev_err(codec->dev, "DC servo timed out\n"); + else + dev_dbg(codec->dev, "DC servo startup complete\n"); + + /* Enable the output stage */ + hp_reg |= WM8961_HPR_ENA_OUTP | WM8961_HPL_ENA_OUTP; + wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + + /* Remove the short on the output stage */ + hp_reg |= WM8961_HPR_RMV_SHORT | WM8961_HPL_RMV_SHORT; + wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + } + + if (event & SND_SOC_DAPM_PRE_PMD) { + /* Short the output */ + hp_reg &= ~(WM8961_HPR_RMV_SHORT | WM8961_HPL_RMV_SHORT); + wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + + /* Disable the output stage */ + hp_reg &= ~(WM8961_HPR_ENA_OUTP | WM8961_HPL_ENA_OUTP); + wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + + /* Disable DC offset cancellation */ + dcs_reg &= ~(WM8961_DCS_ENA_CHAN_HPR | + WM8961_DCS_ENA_CHAN_HPL); + wm8961_write(codec, WM8961_DC_SERVO_1, dcs_reg); + + /* Finish up */ + hp_reg &= ~(WM8961_HPR_ENA_DLY | WM8961_HPR_ENA | + WM8961_HPL_ENA_DLY | WM8961_HPL_ENA); + wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + + /* Disable the PGA */ + pwr_reg &= ~(WM8961_LOUT1_PGA | WM8961_ROUT1_PGA); + wm8961_write(codec, WM8961_PWR_MGMT_2, pwr_reg); + + /* Disable the charge pump */ + dev_dbg(codec->dev, "Disabling charge pump\n"); + wm8961_write(codec, WM8961_CHARGE_PUMP_1, + cp_reg & ~WM8961_CP_ENA); + } + + return 0; +} + +static int wm8961_spk_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 pwr_reg = wm8961_read(codec, WM8961_PWR_MGMT_2); + u16 spk_reg = wm8961_read(codec, WM8961_CLASS_D_CONTROL_1); + + if (event & SND_SOC_DAPM_POST_PMU) { + /* Enable the PGA */ + pwr_reg |= WM8961_SPKL_PGA | WM8961_SPKR_PGA; + wm8961_write(codec, WM8961_PWR_MGMT_2, pwr_reg); + + /* Enable the amplifier */ + spk_reg |= WM8961_SPKL_ENA | WM8961_SPKR_ENA; + wm8961_write(codec, WM8961_CLASS_D_CONTROL_1, spk_reg); + } + + if (event & SND_SOC_DAPM_PRE_PMD) { + /* Enable the amplifier */ + spk_reg &= ~(WM8961_SPKL_ENA | WM8961_SPKR_ENA); + wm8961_write(codec, WM8961_CLASS_D_CONTROL_1, spk_reg); + + /* Enable the PGA */ + pwr_reg &= ~(WM8961_SPKL_PGA | WM8961_SPKR_PGA); + wm8961_write(codec, WM8961_PWR_MGMT_2, pwr_reg); + } + + return 0; +} + +static const char *adc_hpf_text[] = { + "Hi-fi", "Voice 1", "Voice 2", "Voice 3", +}; + +static const struct soc_enum adc_hpf = + SOC_ENUM_SINGLE(WM8961_ADC_DAC_CONTROL_2, 7, 4, adc_hpf_text); + +static const char *dac_deemph_text[] = { + "None", "32kHz", "44.1kHz", "48kHz", +}; + +static const struct soc_enum dac_deemph = + SOC_ENUM_SINGLE(WM8961_ADC_DAC_CONTROL_1, 1, 4, dac_deemph_text); + +static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); +static const DECLARE_TLV_DB_SCALE(hp_sec_tlv, -700, 100, 0); +static const DECLARE_TLV_DB_SCALE(adc_tlv, -7200, 75, 1); +static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -3600, 300, 0); +static unsigned int boost_tlv[] = { + TLV_DB_RANGE_HEAD(4), + 0, 0, TLV_DB_SCALE_ITEM(0, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(13, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(20, 0, 0), + 3, 3, TLV_DB_SCALE_ITEM(29, 0, 0), +}; +static const DECLARE_TLV_DB_SCALE(pga_tlv, -2325, 75, 0); + +static const struct snd_kcontrol_new wm8961_snd_controls[] = { +SOC_DOUBLE_R_TLV("Headphone Volume", WM8961_LOUT1_VOLUME, WM8961_ROUT1_VOLUME, + 0, 127, 0, out_tlv), +SOC_DOUBLE_TLV("Headphone Secondary Volume", WM8961_ANALOGUE_HP_2, + 6, 3, 7, 0, hp_sec_tlv), +SOC_DOUBLE_R("Headphone ZC Switch", WM8961_LOUT1_VOLUME, WM8961_ROUT1_VOLUME, + 7, 1, 0), + +SOC_DOUBLE_R_TLV("Speaker Volume", WM8961_LOUT2_VOLUME, WM8961_ROUT2_VOLUME, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Speaker ZC Switch", WM8961_LOUT2_VOLUME, WM8961_ROUT2_VOLUME, + 7, 1, 0), +SOC_SINGLE("Speaker AC Gain", WM8961_CLASS_D_CONTROL_2, 0, 7, 0), + +SOC_SINGLE("DAC x128 OSR Switch", WM8961_ADC_DAC_CONTROL_2, 0, 1, 0), +SOC_ENUM("DAC Deemphasis", dac_deemph), +SOC_SINGLE("DAC Soft Mute Switch", WM8961_ADC_DAC_CONTROL_2, 3, 1, 0), + +SOC_DOUBLE_R_TLV("Sidetone Volume", WM8961_DSP_SIDETONE_0, + WM8961_DSP_SIDETONE_1, 4, 12, 0, sidetone_tlv), + +SOC_SINGLE("ADC High Pass Filter Switch", WM8961_ADC_DAC_CONTROL_1, 0, 1, 0), +SOC_ENUM("ADC High Pass Filter Mode", adc_hpf), + +SOC_DOUBLE_R_TLV("Capture Volume", + WM8961_LEFT_ADC_VOLUME, WM8961_RIGHT_ADC_VOLUME, + 1, 119, 0, adc_tlv), +SOC_DOUBLE_R_TLV("Capture Boost Volume", + WM8961_ADCL_SIGNAL_PATH, WM8961_ADCR_SIGNAL_PATH, + 4, 3, 0, boost_tlv), +SOC_DOUBLE_R_TLV("Capture PGA Volume", + WM8961_LEFT_INPUT_VOLUME, WM8961_RIGHT_INPUT_VOLUME, + 0, 62, 0, pga_tlv), +SOC_DOUBLE_R("Capture PGA ZC Switch", + WM8961_LEFT_INPUT_VOLUME, WM8961_RIGHT_INPUT_VOLUME, + 6, 1, 1), +SOC_DOUBLE_R("Capture PGA Switch", + WM8961_LEFT_INPUT_VOLUME, WM8961_RIGHT_INPUT_VOLUME, + 7, 1, 1), +}; + +static const char *sidetone_text[] = { + "None", "Left", "Right" +}; + +static const struct soc_enum dacl_sidetone = + SOC_ENUM_SINGLE(WM8961_DSP_SIDETONE_0, 2, 3, sidetone_text); + +static const struct soc_enum dacr_sidetone = + SOC_ENUM_SINGLE(WM8961_DSP_SIDETONE_1, 2, 3, sidetone_text); + +static const struct snd_kcontrol_new dacl_mux = + SOC_DAPM_ENUM("DACL Sidetone", dacl_sidetone); + +static const struct snd_kcontrol_new dacr_mux = + SOC_DAPM_ENUM("DACR Sidetone", dacr_sidetone); + +static const struct snd_soc_dapm_widget wm8961_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("LINPUT"), +SND_SOC_DAPM_INPUT("RINPUT"), + +SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8961_CLOCKING2, 4, 0, NULL, 0), + +SND_SOC_DAPM_PGA("Left Input", WM8961_PWR_MGMT_1, 5, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Input", WM8961_PWR_MGMT_1, 4, 0, NULL, 0), + +SND_SOC_DAPM_ADC("ADCL", "HiFi Capture", WM8961_PWR_MGMT_1, 3, 0), +SND_SOC_DAPM_ADC("ADCR", "HiFi Capture", WM8961_PWR_MGMT_1, 2, 0), + +SND_SOC_DAPM_MICBIAS("MICBIAS", WM8961_PWR_MGMT_1, 1, 0), + +SND_SOC_DAPM_MUX("DACL Sidetone", SND_SOC_NOPM, 0, 0, &dacl_mux), +SND_SOC_DAPM_MUX("DACR Sidetone", SND_SOC_NOPM, 0, 0, &dacr_mux), + +SND_SOC_DAPM_DAC("DACL", "HiFi Playback", WM8961_PWR_MGMT_2, 8, 0), +SND_SOC_DAPM_DAC("DACR", "HiFi Playback", WM8961_PWR_MGMT_2, 7, 0), + +/* Handle as a mono path for DCS */ +SND_SOC_DAPM_PGA_E("Headphone Output", SND_SOC_NOPM, + 4, 0, NULL, 0, wm8961_hp_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), +SND_SOC_DAPM_PGA_E("Speaker Output", SND_SOC_NOPM, + 4, 0, NULL, 0, wm8961_spk_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_OUTPUT("HP_L"), +SND_SOC_DAPM_OUTPUT("HP_R"), +SND_SOC_DAPM_OUTPUT("SPK_LN"), +SND_SOC_DAPM_OUTPUT("SPK_LP"), +SND_SOC_DAPM_OUTPUT("SPK_RN"), +SND_SOC_DAPM_OUTPUT("SPK_RP"), +}; + + +static const struct snd_soc_dapm_route audio_paths[] = { + { "DACL", NULL, "CLK_DSP" }, + { "DACL", NULL, "DACL Sidetone" }, + { "DACR", NULL, "CLK_DSP" }, + { "DACR", NULL, "DACR Sidetone" }, + + { "DACL Sidetone", "Left", "ADCL" }, + { "DACL Sidetone", "Right", "ADCR" }, + + { "DACR Sidetone", "Left", "ADCL" }, + { "DACR Sidetone", "Right", "ADCR" }, + + { "HP_L", NULL, "Headphone Output" }, + { "HP_R", NULL, "Headphone Output" }, + { "Headphone Output", NULL, "DACL" }, + { "Headphone Output", NULL, "DACR" }, + + { "SPK_LN", NULL, "Speaker Output" }, + { "SPK_LP", NULL, "Speaker Output" }, + { "SPK_RN", NULL, "Speaker Output" }, + { "SPK_RP", NULL, "Speaker Output" }, + + { "Speaker Output", NULL, "DACL" }, + { "Speaker Output", NULL, "DACR" }, + + { "ADCL", NULL, "Left Input" }, + { "ADCL", NULL, "CLK_DSP" }, + { "ADCR", NULL, "Right Input" }, + { "ADCR", NULL, "CLK_DSP" }, + + { "Left Input", NULL, "LINPUT" }, + { "Right Input", NULL, "RINPUT" }, + +}; + +/* Values for CLK_SYS_RATE */ +static struct { + int ratio; + u16 val; +} wm8961_clk_sys_ratio[] = { + { 64, 0 }, + { 128, 1 }, + { 192, 2 }, + { 256, 3 }, + { 384, 4 }, + { 512, 5 }, + { 768, 6 }, + { 1024, 7 }, + { 1408, 8 }, + { 1536, 9 }, +}; + +/* Values for SAMPLE_RATE */ +static struct { + int rate; + u16 val; +} wm8961_srate[] = { + { 48000, 0 }, + { 44100, 0 }, + { 32000, 1 }, + { 22050, 2 }, + { 24000, 2 }, + { 16000, 3 }, + { 11250, 4 }, + { 12000, 4 }, + { 8000, 5 }, +}; + +static int wm8961_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8961_priv *wm8961 = codec->private_data; + int i, best, target, fs; + u16 reg; + + fs = params_rate(params); + + if (!wm8961->sysclk) { + dev_err(codec->dev, "MCLK has not been specified\n"); + return -EINVAL; + } + + /* Find the closest sample rate for the filters */ + best = 0; + for (i = 0; i < ARRAY_SIZE(wm8961_srate); i++) { + if (abs(wm8961_srate[i].rate - fs) < + abs(wm8961_srate[best].rate - fs)) + best = i; + } + reg = wm8961_read(codec, WM8961_ADDITIONAL_CONTROL_3); + reg &= ~WM8961_SAMPLE_RATE_MASK; + reg |= wm8961_srate[best].val; + wm8961_write(codec, WM8961_ADDITIONAL_CONTROL_3, reg); + dev_dbg(codec->dev, "Selected SRATE %dHz for %dHz\n", + wm8961_srate[best].rate, fs); + + /* Select a CLK_SYS/fs ratio equal to or higher than required */ + target = wm8961->sysclk / fs; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && target < 64) { + dev_err(codec->dev, + "SYSCLK must be at least 64*fs for DAC\n"); + return -EINVAL; + } + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE && target < 256) { + dev_err(codec->dev, + "SYSCLK must be at least 256*fs for ADC\n"); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(wm8961_clk_sys_ratio); i++) { + if (wm8961_clk_sys_ratio[i].ratio >= target) + break; + } + if (i == ARRAY_SIZE(wm8961_clk_sys_ratio)) { + dev_err(codec->dev, "Unable to generate CLK_SYS_RATE\n"); + return -EINVAL; + } + dev_dbg(codec->dev, "Selected CLK_SYS_RATE of %d for %d/%d=%d\n", + wm8961_clk_sys_ratio[i].ratio, wm8961->sysclk, fs, + wm8961->sysclk / fs); + + reg = wm8961_read(codec, WM8961_CLOCKING_4); + reg &= ~WM8961_CLK_SYS_RATE_MASK; + reg |= wm8961_clk_sys_ratio[i].val << WM8961_CLK_SYS_RATE_SHIFT; + wm8961_write(codec, WM8961_CLOCKING_4, reg); + + reg = wm8961_read(codec, WM8961_AUDIO_INTERFACE_0); + reg &= ~WM8961_WL_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + reg |= 1 << WM8961_WL_SHIFT; + break; + case SNDRV_PCM_FORMAT_S24_LE: + reg |= 2 << WM8961_WL_SHIFT; + break; + case SNDRV_PCM_FORMAT_S32_LE: + reg |= 3 << WM8961_WL_SHIFT; + break; + default: + return -EINVAL; + } + wm8961_write(codec, WM8961_AUDIO_INTERFACE_0, reg); + + /* Sloping stop-band filter is recommended for <= 24kHz */ + reg = wm8961_read(codec, WM8961_ADC_DAC_CONTROL_2); + if (fs <= 24000) + reg |= WM8961_DACSLOPE; + else + reg &= WM8961_DACSLOPE; + wm8961_write(codec, WM8961_ADC_DAC_CONTROL_2, reg); + + return 0; +} + +static int wm8961_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, + int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8961_priv *wm8961 = codec->private_data; + u16 reg = wm8961_read(codec, WM8961_CLOCKING1); + + if (freq > 33000000) { + dev_err(codec->dev, "MCLK must be <33MHz\n"); + return -EINVAL; + } + + if (freq > 16500000) { + dev_dbg(codec->dev, "Using MCLK/2 for %dHz MCLK\n", freq); + reg |= WM8961_MCLKDIV; + freq /= 2; + } else { + dev_dbg(codec->dev, "Using MCLK/1 for %dHz MCLK\n", freq); + reg &= WM8961_MCLKDIV; + } + + wm8961_write(codec, WM8961_CLOCKING1, reg); + + wm8961->sysclk = freq; + + return 0; +} + +static int wm8961_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + u16 aif = wm8961_read(codec, WM8961_AUDIO_INTERFACE_0); + + aif &= ~(WM8961_BCLKINV | WM8961_LRP | + WM8961_MS | WM8961_FORMAT_MASK); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + aif |= WM8961_MS; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_RIGHT_J: + break; + + case SND_SOC_DAIFMT_LEFT_J: + aif |= 1; + break; + + case SND_SOC_DAIFMT_I2S: + aif |= 2; + break; + + case SND_SOC_DAIFMT_DSP_B: + aif |= WM8961_LRP; + case SND_SOC_DAIFMT_DSP_A: + aif |= 3; + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + case SND_SOC_DAIFMT_IB_NF: + break; + default: + return -EINVAL; + } + break; + + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_NB_IF: + aif |= WM8961_LRP; + break; + case SND_SOC_DAIFMT_IB_NF: + aif |= WM8961_BCLKINV; + break; + case SND_SOC_DAIFMT_IB_IF: + aif |= WM8961_BCLKINV | WM8961_LRP; + break; + default: + return -EINVAL; + } + + return wm8961_write(codec, WM8961_AUDIO_INTERFACE_0, aif); +} + +static int wm8961_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + struct snd_soc_codec *codec = dai->codec; + u16 reg = wm8961_read(codec, WM8961_ADDITIONAL_CONTROL_2); + + if (tristate) + reg |= WM8961_TRIS; + else + reg &= ~WM8961_TRIS; + + return wm8961_write(codec, WM8961_ADDITIONAL_CONTROL_2, reg); +} + +static int wm8961_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 reg = wm8961_read(codec, WM8961_ADC_DAC_CONTROL_1); + + if (mute) + reg |= WM8961_DACMU; + else + reg &= ~WM8961_DACMU; + + msleep(17); + + return wm8961_write(codec, WM8961_ADC_DAC_CONTROL_1, reg); +} + +static int wm8961_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div) +{ + struct snd_soc_codec *codec = dai->codec; + u16 reg; + + switch (div_id) { + case WM8961_BCLK: + reg = wm8961_read(codec, WM8961_CLOCKING2); + reg &= ~WM8961_BCLKDIV_MASK; + reg |= div; + wm8961_write(codec, WM8961_CLOCKING2, reg); + break; + + case WM8961_LRCLK: + reg = wm8961_read(codec, WM8961_AUDIO_INTERFACE_2); + reg &= ~WM8961_LRCLK_RATE_MASK; + reg |= div; + wm8961_write(codec, WM8961_AUDIO_INTERFACE_2, reg); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int wm8961_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg; + + /* This is all slightly unusual since we have no bypass paths + * and the output amplifier structure means we can just slam + * the biases straight up rather than having to ramp them + * slowly. + */ + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + if (codec->bias_level == SND_SOC_BIAS_STANDBY) { + /* Enable bias generation */ + reg = wm8961_read(codec, WM8961_ANTI_POP); + reg |= WM8961_BUFIOEN | WM8961_BUFDCOPEN; + wm8961_write(codec, WM8961_ANTI_POP, reg); + + /* VMID=2*50k, VREF */ + reg = wm8961_read(codec, WM8961_PWR_MGMT_1); + reg &= ~WM8961_VMIDSEL_MASK; + reg |= (1 << WM8961_VMIDSEL_SHIFT) | WM8961_VREF; + wm8961_write(codec, WM8961_PWR_MGMT_1, reg); + } + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_PREPARE) { + /* VREF off */ + reg = wm8961_read(codec, WM8961_PWR_MGMT_1); + reg &= ~WM8961_VREF; + wm8961_write(codec, WM8961_PWR_MGMT_1, reg); + + /* Bias generation off */ + reg = wm8961_read(codec, WM8961_ANTI_POP); + reg &= ~(WM8961_BUFIOEN | WM8961_BUFDCOPEN); + wm8961_write(codec, WM8961_ANTI_POP, reg); + + /* VMID off */ + reg = wm8961_read(codec, WM8961_PWR_MGMT_1); + reg &= ~WM8961_VMIDSEL_MASK; + wm8961_write(codec, WM8961_PWR_MGMT_1, reg); + } + break; + + case SND_SOC_BIAS_OFF: + break; + } + + codec->bias_level = level; + + return 0; +} + + +#define WM8961_RATES SNDRV_PCM_RATE_8000_48000 + +#define WM8961_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE | \ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops wm8961_dai_ops = { + .hw_params = wm8961_hw_params, + .set_sysclk = wm8961_set_sysclk, + .set_fmt = wm8961_set_fmt, + .digital_mute = wm8961_digital_mute, + .set_tristate = wm8961_set_tristate, + .set_clkdiv = wm8961_set_clkdiv, +}; + +struct snd_soc_dai wm8961_dai = { + .name = "WM8961", + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8961_RATES, + .formats = WM8961_FORMATS,}, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8961_RATES, + .formats = WM8961_FORMATS,}, + .ops = &wm8961_dai_ops, +}; +EXPORT_SYMBOL_GPL(wm8961_dai); + + +static struct snd_soc_codec *wm8961_codec; + +static int wm8961_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (wm8961_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = wm8961_codec; + codec = wm8961_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + snd_soc_add_controls(codec, wm8961_snd_controls, + ARRAY_SIZE(wm8961_snd_controls)); + snd_soc_dapm_new_controls(codec, wm8961_dapm_widgets, + ARRAY_SIZE(wm8961_dapm_widgets)); + snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); + snd_soc_dapm_new_widgets(codec); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card: %d\n", ret); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + return ret; +} + +static int wm8961_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +#ifdef CONFIG_PM +static int wm8961_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + wm8961_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm8961_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + u16 *reg_cache = codec->reg_cache; + int i; + + for (i = 0; i < codec->reg_cache_size; i++) { + if (i == WM8961_SOFTWARE_RESET) + continue; + + wm8961_write(codec, i, reg_cache[i]); + } + + wm8961_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} +#else +#define wm8961_suspend NULL +#define wm8961_resume NULL +#endif + +struct snd_soc_codec_device soc_codec_dev_wm8961 = { + .probe = wm8961_probe, + .remove = wm8961_remove, + .suspend = wm8961_suspend, + .resume = wm8961_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8961); + +static int wm8961_register(struct wm8961_priv *wm8961) +{ + struct snd_soc_codec *codec = &wm8961->codec; + int ret; + u16 reg; + + if (wm8961_codec) { + dev_err(codec->dev, "Another WM8961 is registered\n"); + ret = -EINVAL; + goto err; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = wm8961; + codec->name = "WM8961"; + codec->owner = THIS_MODULE; + codec->read = wm8961_read; + codec->write = wm8961_write; + codec->dai = &wm8961_dai; + codec->num_dai = 1; + codec->reg_cache_size = ARRAY_SIZE(wm8961->reg_cache); + codec->reg_cache = &wm8961->reg_cache; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8961_set_bias_level; + + memcpy(codec->reg_cache, wm8961_reg_defaults, + sizeof(wm8961_reg_defaults)); + + reg = wm8961_read_hw(codec, WM8961_SOFTWARE_RESET); + if (reg != 0x1801) { + dev_err(codec->dev, "Device is not a WM8961: ID=0x%x\n", reg); + ret = -EINVAL; + goto err; + } + + reg = wm8961_read_hw(codec, WM8961_RIGHT_INPUT_VOLUME); + dev_info(codec->dev, "WM8961 family %d revision %c\n", + (reg & WM8961_DEVICE_ID_MASK) >> WM8961_DEVICE_ID_SHIFT, + ((reg & WM8961_CHIP_REV_MASK) >> WM8961_CHIP_REV_SHIFT) + + 'A'); + + ret = wm8961_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset\n"); + return ret; + } + + /* Enable class W */ + reg = wm8961_read(codec, WM8961_CHARGE_PUMP_B); + reg |= WM8961_CP_DYN_PWR_MASK; + wm8961_write(codec, WM8961_CHARGE_PUMP_B, reg); + + /* Latch volume update bits (right channel only, we always + * write both out) and default ZC on. */ + reg = wm8961_read(codec, WM8961_ROUT1_VOLUME); + wm8961_write(codec, WM8961_ROUT1_VOLUME, + reg | WM8961_LO1ZC | WM8961_OUT1VU); + wm8961_write(codec, WM8961_LOUT1_VOLUME, reg | WM8961_LO1ZC); + reg = wm8961_read(codec, WM8961_ROUT2_VOLUME); + wm8961_write(codec, WM8961_ROUT2_VOLUME, + reg | WM8961_SPKRZC | WM8961_SPKVU); + wm8961_write(codec, WM8961_LOUT2_VOLUME, reg | WM8961_SPKLZC); + + reg = wm8961_read(codec, WM8961_RIGHT_ADC_VOLUME); + wm8961_write(codec, WM8961_RIGHT_ADC_VOLUME, reg | WM8961_ADCVU); + reg = wm8961_read(codec, WM8961_RIGHT_INPUT_VOLUME); + wm8961_write(codec, WM8961_RIGHT_INPUT_VOLUME, reg | WM8961_IPVU); + + /* Use soft mute by default */ + reg = wm8961_read(codec, WM8961_ADC_DAC_CONTROL_2); + reg |= WM8961_DACSMM; + wm8961_write(codec, WM8961_ADC_DAC_CONTROL_2, reg); + + /* Use automatic clocking mode by default; for now this is all + * we support. + */ + reg = wm8961_read(codec, WM8961_CLOCKING_3); + reg &= ~WM8961_MANUAL_MODE; + wm8961_write(codec, WM8961_CLOCKING_3, reg); + + wm8961_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + wm8961_dai.dev = codec->dev; + + wm8961_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } + + ret = snd_soc_register_dai(&wm8961_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + return ret; + } + + return 0; + +err: + kfree(wm8961); + return ret; +} + +static void wm8961_unregister(struct wm8961_priv *wm8961) +{ + wm8961_set_bias_level(&wm8961->codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dai(&wm8961_dai); + snd_soc_unregister_codec(&wm8961->codec); + kfree(wm8961); + wm8961_codec = NULL; +} + +static __devinit int wm8961_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8961_priv *wm8961; + struct snd_soc_codec *codec; + + wm8961 = kzalloc(sizeof(struct wm8961_priv), GFP_KERNEL); + if (wm8961 == NULL) + return -ENOMEM; + + codec = &wm8961->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + + i2c_set_clientdata(i2c, wm8961); + codec->control_data = i2c; + + codec->dev = &i2c->dev; + + return wm8961_register(wm8961); +} + +static __devexit int wm8961_i2c_remove(struct i2c_client *client) +{ + struct wm8961_priv *wm8961 = i2c_get_clientdata(client); + wm8961_unregister(wm8961); + return 0; +} + +static const struct i2c_device_id wm8961_i2c_id[] = { + { "wm8961", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8961_i2c_id); + +static struct i2c_driver wm8961_i2c_driver = { + .driver = { + .name = "wm8961", + .owner = THIS_MODULE, + }, + .probe = wm8961_i2c_probe, + .remove = __devexit_p(wm8961_i2c_remove), + .id_table = wm8961_i2c_id, +}; + +static int __init wm8961_modinit(void) +{ + int ret; + + ret = i2c_add_driver(&wm8961_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8961 I2C driver: %d\n", + ret); + } + + return ret; +} +module_init(wm8961_modinit); + +static void __exit wm8961_exit(void) +{ + i2c_del_driver(&wm8961_i2c_driver); +} +module_exit(wm8961_exit); + + +MODULE_DESCRIPTION("ASoC WM8961 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8961.h b/sound/soc/codecs/wm8961.h new file mode 100644 index 00000000000..5513bfd720d --- /dev/null +++ b/sound/soc/codecs/wm8961.h @@ -0,0 +1,866 @@ +/* + * wm8961.h -- WM8961 Soc Audio driver + * + * 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 _WM8961_H +#define _WM8961_H + +#include + +extern struct snd_soc_codec_device soc_codec_dev_wm8961; +extern struct snd_soc_dai wm8961_dai; + +#define WM8961_BCLK 1 +#define WM8961_LRCLK 2 + +#define WM8961_BCLK_DIV_1 0 +#define WM8961_BCLK_DIV_1_5 1 +#define WM8961_BCLK_DIV_2 2 +#define WM8961_BCLK_DIV_3 3 +#define WM8961_BCLK_DIV_4 4 +#define WM8961_BCLK_DIV_5_5 5 +#define WM8961_BCLK_DIV_6 6 +#define WM8961_BCLK_DIV_8 7 +#define WM8961_BCLK_DIV_11 8 +#define WM8961_BCLK_DIV_12 9 +#define WM8961_BCLK_DIV_16 10 +#define WM8961_BCLK_DIV_24 11 +#define WM8961_BCLK_DIV_32 13 + + +/* + * Register values. + */ +#define WM8961_LEFT_INPUT_VOLUME 0x00 +#define WM8961_RIGHT_INPUT_VOLUME 0x01 +#define WM8961_LOUT1_VOLUME 0x02 +#define WM8961_ROUT1_VOLUME 0x03 +#define WM8961_CLOCKING1 0x04 +#define WM8961_ADC_DAC_CONTROL_1 0x05 +#define WM8961_ADC_DAC_CONTROL_2 0x06 +#define WM8961_AUDIO_INTERFACE_0 0x07 +#define WM8961_CLOCKING2 0x08 +#define WM8961_AUDIO_INTERFACE_1 0x09 +#define WM8961_LEFT_DAC_VOLUME 0x0A +#define WM8961_RIGHT_DAC_VOLUME 0x0B +#define WM8961_AUDIO_INTERFACE_2 0x0E +#define WM8961_SOFTWARE_RESET 0x0F +#define WM8961_ALC1 0x11 +#define WM8961_ALC2 0x12 +#define WM8961_ALC3 0x13 +#define WM8961_NOISE_GATE 0x14 +#define WM8961_LEFT_ADC_VOLUME 0x15 +#define WM8961_RIGHT_ADC_VOLUME 0x16 +#define WM8961_ADDITIONAL_CONTROL_1 0x17 +#define WM8961_ADDITIONAL_CONTROL_2 0x18 +#define WM8961_PWR_MGMT_1 0x19 +#define WM8961_PWR_MGMT_2 0x1A +#define WM8961_ADDITIONAL_CONTROL_3 0x1B +#define WM8961_ANTI_POP 0x1C +#define WM8961_CLOCKING_3 0x1E +#define WM8961_ADCL_SIGNAL_PATH 0x20 +#define WM8961_ADCR_SIGNAL_PATH 0x21 +#define WM8961_LOUT2_VOLUME 0x28 +#define WM8961_ROUT2_VOLUME 0x29 +#define WM8961_PWR_MGMT_3 0x2F +#define WM8961_ADDITIONAL_CONTROL_4 0x30 +#define WM8961_CLASS_D_CONTROL_1 0x31 +#define WM8961_CLASS_D_CONTROL_2 0x33 +#define WM8961_CLOCKING_4 0x38 +#define WM8961_DSP_SIDETONE_0 0x39 +#define WM8961_DSP_SIDETONE_1 0x3A +#define WM8961_DC_SERVO_0 0x3C +#define WM8961_DC_SERVO_1 0x3D +#define WM8961_DC_SERVO_3 0x3F +#define WM8961_DC_SERVO_5 0x41 +#define WM8961_ANALOGUE_PGA_BIAS 0x44 +#define WM8961_ANALOGUE_HP_0 0x45 +#define WM8961_ANALOGUE_HP_2 0x47 +#define WM8961_CHARGE_PUMP_1 0x48 +#define WM8961_CHARGE_PUMP_B 0x52 +#define WM8961_WRITE_SEQUENCER_1 0x57 +#define WM8961_WRITE_SEQUENCER_2 0x58 +#define WM8961_WRITE_SEQUENCER_3 0x59 +#define WM8961_WRITE_SEQUENCER_4 0x5A +#define WM8961_WRITE_SEQUENCER_5 0x5B +#define WM8961_WRITE_SEQUENCER_6 0x5C +#define WM8961_WRITE_SEQUENCER_7 0x5D +#define WM8961_GENERAL_TEST_1 0xFC + + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Left Input volume + */ +#define WM8961_IPVU 0x0100 /* IPVU */ +#define WM8961_IPVU_MASK 0x0100 /* IPVU */ +#define WM8961_IPVU_SHIFT 8 /* IPVU */ +#define WM8961_IPVU_WIDTH 1 /* IPVU */ +#define WM8961_LINMUTE 0x0080 /* LINMUTE */ +#define WM8961_LINMUTE_MASK 0x0080 /* LINMUTE */ +#define WM8961_LINMUTE_SHIFT 7 /* LINMUTE */ +#define WM8961_LINMUTE_WIDTH 1 /* LINMUTE */ +#define WM8961_LIZC 0x0040 /* LIZC */ +#define WM8961_LIZC_MASK 0x0040 /* LIZC */ +#define WM8961_LIZC_SHIFT 6 /* LIZC */ +#define WM8961_LIZC_WIDTH 1 /* LIZC */ +#define WM8961_LINVOL_MASK 0x003F /* LINVOL - [5:0] */ +#define WM8961_LINVOL_SHIFT 0 /* LINVOL - [5:0] */ +#define WM8961_LINVOL_WIDTH 6 /* LINVOL - [5:0] */ + +/* + * R1 (0x01) - Right Input volume + */ +#define WM8961_DEVICE_ID_MASK 0xF000 /* DEVICE_ID - [15:12] */ +#define WM8961_DEVICE_ID_SHIFT 12 /* DEVICE_ID - [15:12] */ +#define WM8961_DEVICE_ID_WIDTH 4 /* DEVICE_ID - [15:12] */ +#define WM8961_CHIP_REV_MASK 0x0E00 /* CHIP_REV - [11:9] */ +#define WM8961_CHIP_REV_SHIFT 9 /* CHIP_REV - [11:9] */ +#define WM8961_CHIP_REV_WIDTH 3 /* CHIP_REV - [11:9] */ +#define WM8961_IPVU 0x0100 /* IPVU */ +#define WM8961_IPVU_MASK 0x0100 /* IPVU */ +#define WM8961_IPVU_SHIFT 8 /* IPVU */ +#define WM8961_IPVU_WIDTH 1 /* IPVU */ +#define WM8961_RINMUTE 0x0080 /* RINMUTE */ +#define WM8961_RINMUTE_MASK 0x0080 /* RINMUTE */ +#define WM8961_RINMUTE_SHIFT 7 /* RINMUTE */ +#define WM8961_RINMUTE_WIDTH 1 /* RINMUTE */ +#define WM8961_RIZC 0x0040 /* RIZC */ +#define WM8961_RIZC_MASK 0x0040 /* RIZC */ +#define WM8961_RIZC_SHIFT 6 /* RIZC */ +#define WM8961_RIZC_WIDTH 1 /* RIZC */ +#define WM8961_RINVOL_MASK 0x003F /* RINVOL - [5:0] */ +#define WM8961_RINVOL_SHIFT 0 /* RINVOL - [5:0] */ +#define WM8961_RINVOL_WIDTH 6 /* RINVOL - [5:0] */ + +/* + * R2 (0x02) - LOUT1 volume + */ +#define WM8961_OUT1VU 0x0100 /* OUT1VU */ +#define WM8961_OUT1VU_MASK 0x0100 /* OUT1VU */ +#define WM8961_OUT1VU_SHIFT 8 /* OUT1VU */ +#define WM8961_OUT1VU_WIDTH 1 /* OUT1VU */ +#define WM8961_LO1ZC 0x0080 /* LO1ZC */ +#define WM8961_LO1ZC_MASK 0x0080 /* LO1ZC */ +#define WM8961_LO1ZC_SHIFT 7 /* LO1ZC */ +#define WM8961_LO1ZC_WIDTH 1 /* LO1ZC */ +#define WM8961_LOUT1VOL_MASK 0x007F /* LOUT1VOL - [6:0] */ +#define WM8961_LOUT1VOL_SHIFT 0 /* LOUT1VOL - [6:0] */ +#define WM8961_LOUT1VOL_WIDTH 7 /* LOUT1VOL - [6:0] */ + +/* + * R3 (0x03) - ROUT1 volume + */ +#define WM8961_OUT1VU 0x0100 /* OUT1VU */ +#define WM8961_OUT1VU_MASK 0x0100 /* OUT1VU */ +#define WM8961_OUT1VU_SHIFT 8 /* OUT1VU */ +#define WM8961_OUT1VU_WIDTH 1 /* OUT1VU */ +#define WM8961_RO1ZC 0x0080 /* RO1ZC */ +#define WM8961_RO1ZC_MASK 0x0080 /* RO1ZC */ +#define WM8961_RO1ZC_SHIFT 7 /* RO1ZC */ +#define WM8961_RO1ZC_WIDTH 1 /* RO1ZC */ +#define WM8961_ROUT1VOL_MASK 0x007F /* ROUT1VOL - [6:0] */ +#define WM8961_ROUT1VOL_SHIFT 0 /* ROUT1VOL - [6:0] */ +#define WM8961_ROUT1VOL_WIDTH 7 /* ROUT1VOL - [6:0] */ + +/* + * R4 (0x04) - Clocking1 + */ +#define WM8961_ADCDIV_MASK 0x01C0 /* ADCDIV - [8:6] */ +#define WM8961_ADCDIV_SHIFT 6 /* ADCDIV - [8:6] */ +#define WM8961_ADCDIV_WIDTH 3 /* ADCDIV - [8:6] */ +#define WM8961_DACDIV_MASK 0x0038 /* DACDIV - [5:3] */ +#define WM8961_DACDIV_SHIFT 3 /* DACDIV - [5:3] */ +#define WM8961_DACDIV_WIDTH 3 /* DACDIV - [5:3] */ +#define WM8961_MCLKDIV 0x0004 /* MCLKDIV */ +#define WM8961_MCLKDIV_MASK 0x0004 /* MCLKDIV */ +#define WM8961_MCLKDIV_SHIFT 2 /* MCLKDIV */ +#define WM8961_MCLKDIV_WIDTH 1 /* MCLKDIV */ + +/* + * R5 (0x05) - ADC & DAC Control 1 + */ +#define WM8961_ADCPOL_MASK 0x0060 /* ADCPOL - [6:5] */ +#define WM8961_ADCPOL_SHIFT 5 /* ADCPOL - [6:5] */ +#define WM8961_ADCPOL_WIDTH 2 /* ADCPOL - [6:5] */ +#define WM8961_DACMU 0x0008 /* DACMU */ +#define WM8961_DACMU_MASK 0x0008 /* DACMU */ +#define WM8961_DACMU_SHIFT 3 /* DACMU */ +#define WM8961_DACMU_WIDTH 1 /* DACMU */ +#define WM8961_DEEMPH_MASK 0x0006 /* DEEMPH - [2:1] */ +#define WM8961_DEEMPH_SHIFT 1 /* DEEMPH - [2:1] */ +#define WM8961_DEEMPH_WIDTH 2 /* DEEMPH - [2:1] */ +#define WM8961_ADCHPD 0x0001 /* ADCHPD */ +#define WM8961_ADCHPD_MASK 0x0001 /* ADCHPD */ +#define WM8961_ADCHPD_SHIFT 0 /* ADCHPD */ +#define WM8961_ADCHPD_WIDTH 1 /* ADCHPD */ + +/* + * R6 (0x06) - ADC & DAC Control 2 + */ +#define WM8961_ADC_HPF_CUT_MASK 0x0180 /* ADC_HPF_CUT - [8:7] */ +#define WM8961_ADC_HPF_CUT_SHIFT 7 /* ADC_HPF_CUT - [8:7] */ +#define WM8961_ADC_HPF_CUT_WIDTH 2 /* ADC_HPF_CUT - [8:7] */ +#define WM8961_DACPOL_MASK 0x0060 /* DACPOL - [6:5] */ +#define WM8961_DACPOL_SHIFT 5 /* DACPOL - [6:5] */ +#define WM8961_DACPOL_WIDTH 2 /* DACPOL - [6:5] */ +#define WM8961_DACSMM 0x0008 /* DACSMM */ +#define WM8961_DACSMM_MASK 0x0008 /* DACSMM */ +#define WM8961_DACSMM_SHIFT 3 /* DACSMM */ +#define WM8961_DACSMM_WIDTH 1 /* DACSMM */ +#define WM8961_DACMR 0x0004 /* DACMR */ +#define WM8961_DACMR_MASK 0x0004 /* DACMR */ +#define WM8961_DACMR_SHIFT 2 /* DACMR */ +#define WM8961_DACMR_WIDTH 1 /* DACMR */ +#define WM8961_DACSLOPE 0x0002 /* DACSLOPE */ +#define WM8961_DACSLOPE_MASK 0x0002 /* DACSLOPE */ +#define WM8961_DACSLOPE_SHIFT 1 /* DACSLOPE */ +#define WM8961_DACSLOPE_WIDTH 1 /* DACSLOPE */ +#define WM8961_DAC_OSR128 0x0001 /* DAC_OSR128 */ +#define WM8961_DAC_OSR128_MASK 0x0001 /* DAC_OSR128 */ +#define WM8961_DAC_OSR128_SHIFT 0 /* DAC_OSR128 */ +#define WM8961_DAC_OSR128_WIDTH 1 /* DAC_OSR128 */ + +/* + * R7 (0x07) - Audio Interface 0 + */ +#define WM8961_ALRSWAP 0x0100 /* ALRSWAP */ +#define WM8961_ALRSWAP_MASK 0x0100 /* ALRSWAP */ +#define WM8961_ALRSWAP_SHIFT 8 /* ALRSWAP */ +#define WM8961_ALRSWAP_WIDTH 1 /* ALRSWAP */ +#define WM8961_BCLKINV 0x0080 /* BCLKINV */ +#define WM8961_BCLKINV_MASK 0x0080 /* BCLKINV */ +#define WM8961_BCLKINV_SHIFT 7 /* BCLKINV */ +#define WM8961_BCLKINV_WIDTH 1 /* BCLKINV */ +#define WM8961_MS 0x0040 /* MS */ +#define WM8961_MS_MASK 0x0040 /* MS */ +#define WM8961_MS_SHIFT 6 /* MS */ +#define WM8961_MS_WIDTH 1 /* MS */ +#define WM8961_DLRSWAP 0x0020 /* DLRSWAP */ +#define WM8961_DLRSWAP_MASK 0x0020 /* DLRSWAP */ +#define WM8961_DLRSWAP_SHIFT 5 /* DLRSWAP */ +#define WM8961_DLRSWAP_WIDTH 1 /* DLRSWAP */ +#define WM8961_LRP 0x0010 /* LRP */ +#define WM8961_LRP_MASK 0x0010 /* LRP */ +#define WM8961_LRP_SHIFT 4 /* LRP */ +#define WM8961_LRP_WIDTH 1 /* LRP */ +#define WM8961_WL_MASK 0x000C /* WL - [3:2] */ +#define WM8961_WL_SHIFT 2 /* WL - [3:2] */ +#define WM8961_WL_WIDTH 2 /* WL - [3:2] */ +#define WM8961_FORMAT_MASK 0x0003 /* FORMAT - [1:0] */ +#define WM8961_FORMAT_SHIFT 0 /* FORMAT - [1:0] */ +#define WM8961_FORMAT_WIDTH 2 /* FORMAT - [1:0] */ + +/* + * R8 (0x08) - Clocking2 + */ +#define WM8961_DCLKDIV_MASK 0x01C0 /* DCLKDIV - [8:6] */ +#define WM8961_DCLKDIV_SHIFT 6 /* DCLKDIV - [8:6] */ +#define WM8961_DCLKDIV_WIDTH 3 /* DCLKDIV - [8:6] */ +#define WM8961_CLK_SYS_ENA 0x0020 /* CLK_SYS_ENA */ +#define WM8961_CLK_SYS_ENA_MASK 0x0020 /* CLK_SYS_ENA */ +#define WM8961_CLK_SYS_ENA_SHIFT 5 /* CLK_SYS_ENA */ +#define WM8961_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */ +#define WM8961_CLK_DSP_ENA 0x0010 /* CLK_DSP_ENA */ +#define WM8961_CLK_DSP_ENA_MASK 0x0010 /* CLK_DSP_ENA */ +#define WM8961_CLK_DSP_ENA_SHIFT 4 /* CLK_DSP_ENA */ +#define WM8961_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */ +#define WM8961_BCLKDIV_MASK 0x000F /* BCLKDIV - [3:0] */ +#define WM8961_BCLKDIV_SHIFT 0 /* BCLKDIV - [3:0] */ +#define WM8961_BCLKDIV_WIDTH 4 /* BCLKDIV - [3:0] */ + +/* + * R9 (0x09) - Audio Interface 1 + */ +#define WM8961_DACCOMP_MASK 0x0018 /* DACCOMP - [4:3] */ +#define WM8961_DACCOMP_SHIFT 3 /* DACCOMP - [4:3] */ +#define WM8961_DACCOMP_WIDTH 2 /* DACCOMP - [4:3] */ +#define WM8961_ADCCOMP_MASK 0x0006 /* ADCCOMP - [2:1] */ +#define WM8961_ADCCOMP_SHIFT 1 /* ADCCOMP - [2:1] */ +#define WM8961_ADCCOMP_WIDTH 2 /* ADCCOMP - [2:1] */ +#define WM8961_LOOPBACK 0x0001 /* LOOPBACK */ +#define WM8961_LOOPBACK_MASK 0x0001 /* LOOPBACK */ +#define WM8961_LOOPBACK_SHIFT 0 /* LOOPBACK */ +#define WM8961_LOOPBACK_WIDTH 1 /* LOOPBACK */ + +/* + * R10 (0x0A) - Left DAC volume + */ +#define WM8961_DACVU 0x0100 /* DACVU */ +#define WM8961_DACVU_MASK 0x0100 /* DACVU */ +#define WM8961_DACVU_SHIFT 8 /* DACVU */ +#define WM8961_DACVU_WIDTH 1 /* DACVU */ +#define WM8961_LDACVOL_MASK 0x00FF /* LDACVOL - [7:0] */ +#define WM8961_LDACVOL_SHIFT 0 /* LDACVOL - [7:0] */ +#define WM8961_LDACVOL_WIDTH 8 /* LDACVOL - [7:0] */ + +/* + * R11 (0x0B) - Right DAC volume + */ +#define WM8961_DACVU 0x0100 /* DACVU */ +#define WM8961_DACVU_MASK 0x0100 /* DACVU */ +#define WM8961_DACVU_SHIFT 8 /* DACVU */ +#define WM8961_DACVU_WIDTH 1 /* DACVU */ +#define WM8961_RDACVOL_MASK 0x00FF /* RDACVOL - [7:0] */ +#define WM8961_RDACVOL_SHIFT 0 /* RDACVOL - [7:0] */ +#define WM8961_RDACVOL_WIDTH 8 /* RDACVOL - [7:0] */ + +/* + * R14 (0x0E) - Audio Interface 2 + */ +#define WM8961_LRCLK_RATE_MASK 0x01FF /* LRCLK_RATE - [8:0] */ +#define WM8961_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [8:0] */ +#define WM8961_LRCLK_RATE_WIDTH 9 /* LRCLK_RATE - [8:0] */ + +/* + * R15 (0x0F) - Software Reset + */ +#define WM8961_SW_RST_DEV_ID1_MASK 0xFFFF /* SW_RST_DEV_ID1 - [15:0] */ +#define WM8961_SW_RST_DEV_ID1_SHIFT 0 /* SW_RST_DEV_ID1 - [15:0] */ +#define WM8961_SW_RST_DEV_ID1_WIDTH 16 /* SW_RST_DEV_ID1 - [15:0] */ + +/* + * R17 (0x11) - ALC1 + */ +#define WM8961_ALCSEL_MASK 0x0180 /* ALCSEL - [8:7] */ +#define WM8961_ALCSEL_SHIFT 7 /* ALCSEL - [8:7] */ +#define WM8961_ALCSEL_WIDTH 2 /* ALCSEL - [8:7] */ +#define WM8961_MAXGAIN_MASK 0x0070 /* MAXGAIN - [6:4] */ +#define WM8961_MAXGAIN_SHIFT 4 /* MAXGAIN - [6:4] */ +#define WM8961_MAXGAIN_WIDTH 3 /* MAXGAIN - [6:4] */ +#define WM8961_ALCL_MASK 0x000F /* ALCL - [3:0] */ +#define WM8961_ALCL_SHIFT 0 /* ALCL - [3:0] */ +#define WM8961_ALCL_WIDTH 4 /* ALCL - [3:0] */ + +/* + * R18 (0x12) - ALC2 + */ +#define WM8961_ALCZC 0x0080 /* ALCZC */ +#define WM8961_ALCZC_MASK 0x0080 /* ALCZC */ +#define WM8961_ALCZC_SHIFT 7 /* ALCZC */ +#define WM8961_ALCZC_WIDTH 1 /* ALCZC */ +#define WM8961_MINGAIN_MASK 0x0070 /* MINGAIN - [6:4] */ +#define WM8961_MINGAIN_SHIFT 4 /* MINGAIN - [6:4] */ +#define WM8961_MINGAIN_WIDTH 3 /* MINGAIN - [6:4] */ +#define WM8961_HLD_MASK 0x000F /* HLD - [3:0] */ +#define WM8961_HLD_SHIFT 0 /* HLD - [3:0] */ +#define WM8961_HLD_WIDTH 4 /* HLD - [3:0] */ + +/* + * R19 (0x13) - ALC3 + */ +#define WM8961_ALCMODE 0x0100 /* ALCMODE */ +#define WM8961_ALCMODE_MASK 0x0100 /* ALCMODE */ +#define WM8961_ALCMODE_SHIFT 8 /* ALCMODE */ +#define WM8961_ALCMODE_WIDTH 1 /* ALCMODE */ +#define WM8961_DCY_MASK 0x00F0 /* DCY - [7:4] */ +#define WM8961_DCY_SHIFT 4 /* DCY - [7:4] */ +#define WM8961_DCY_WIDTH 4 /* DCY - [7:4] */ +#define WM8961_ATK_MASK 0x000F /* ATK - [3:0] */ +#define WM8961_ATK_SHIFT 0 /* ATK - [3:0] */ +#define WM8961_ATK_WIDTH 4 /* ATK - [3:0] */ + +/* + * R20 (0x14) - Noise Gate + */ +#define WM8961_NGTH_MASK 0x00F8 /* NGTH - [7:3] */ +#define WM8961_NGTH_SHIFT 3 /* NGTH - [7:3] */ +#define WM8961_NGTH_WIDTH 5 /* NGTH - [7:3] */ +#define WM8961_NGG 0x0002 /* NGG */ +#define WM8961_NGG_MASK 0x0002 /* NGG */ +#define WM8961_NGG_SHIFT 1 /* NGG */ +#define WM8961_NGG_WIDTH 1 /* NGG */ +#define WM8961_NGAT 0x0001 /* NGAT */ +#define WM8961_NGAT_MASK 0x0001 /* NGAT */ +#define WM8961_NGAT_SHIFT 0 /* NGAT */ +#define WM8961_NGAT_WIDTH 1 /* NGAT */ + +/* + * R21 (0x15) - Left ADC volume + */ +#define WM8961_ADCVU 0x0100 /* ADCVU */ +#define WM8961_ADCVU_MASK 0x0100 /* ADCVU */ +#define WM8961_ADCVU_SHIFT 8 /* ADCVU */ +#define WM8961_ADCVU_WIDTH 1 /* ADCVU */ +#define WM8961_LADCVOL_MASK 0x00FF /* LADCVOL - [7:0] */ +#define WM8961_LADCVOL_SHIFT 0 /* LADCVOL - [7:0] */ +#define WM8961_LADCVOL_WIDTH 8 /* LADCVOL - [7:0] */ + +/* + * R22 (0x16) - Right ADC volume + */ +#define WM8961_ADCVU 0x0100 /* ADCVU */ +#define WM8961_ADCVU_MASK 0x0100 /* ADCVU */ +#define WM8961_ADCVU_SHIFT 8 /* ADCVU */ +#define WM8961_ADCVU_WIDTH 1 /* ADCVU */ +#define WM8961_RADCVOL_MASK 0x00FF /* RADCVOL - [7:0] */ +#define WM8961_RADCVOL_SHIFT 0 /* RADCVOL - [7:0] */ +#define WM8961_RADCVOL_WIDTH 8 /* RADCVOL - [7:0] */ + +/* + * R23 (0x17) - Additional control(1) + */ +#define WM8961_TSDEN 0x0100 /* TSDEN */ +#define WM8961_TSDEN_MASK 0x0100 /* TSDEN */ +#define WM8961_TSDEN_SHIFT 8 /* TSDEN */ +#define WM8961_TSDEN_WIDTH 1 /* TSDEN */ +#define WM8961_DMONOMIX 0x0010 /* DMONOMIX */ +#define WM8961_DMONOMIX_MASK 0x0010 /* DMONOMIX */ +#define WM8961_DMONOMIX_SHIFT 4 /* DMONOMIX */ +#define WM8961_DMONOMIX_WIDTH 1 /* DMONOMIX */ +#define WM8961_TOEN 0x0001 /* TOEN */ +#define WM8961_TOEN_MASK 0x0001 /* TOEN */ +#define WM8961_TOEN_SHIFT 0 /* TOEN */ +#define WM8961_TOEN_WIDTH 1 /* TOEN */ + +/* + * R24 (0x18) - Additional control(2) + */ +#define WM8961_TRIS 0x0008 /* TRIS */ +#define WM8961_TRIS_MASK 0x0008 /* TRIS */ +#define WM8961_TRIS_SHIFT 3 /* TRIS */ +#define WM8961_TRIS_WIDTH 1 /* TRIS */ + +/* + * R25 (0x19) - Pwr Mgmt (1) + */ +#define WM8961_VMIDSEL_MASK 0x0180 /* VMIDSEL - [8:7] */ +#define WM8961_VMIDSEL_SHIFT 7 /* VMIDSEL - [8:7] */ +#define WM8961_VMIDSEL_WIDTH 2 /* VMIDSEL - [8:7] */ +#define WM8961_VREF 0x0040 /* VREF */ +#define WM8961_VREF_MASK 0x0040 /* VREF */ +#define WM8961_VREF_SHIFT 6 /* VREF */ +#define WM8961_VREF_WIDTH 1 /* VREF */ +#define WM8961_AINL 0x0020 /* AINL */ +#define WM8961_AINL_MASK 0x0020 /* AINL */ +#define WM8961_AINL_SHIFT 5 /* AINL */ +#define WM8961_AINL_WIDTH 1 /* AINL */ +#define WM8961_AINR 0x0010 /* AINR */ +#define WM8961_AINR_MASK 0x0010 /* AINR */ +#define WM8961_AINR_SHIFT 4 /* AINR */ +#define WM8961_AINR_WIDTH 1 /* AINR */ +#define WM8961_ADCL 0x0008 /* ADCL */ +#define WM8961_ADCL_MASK 0x0008 /* ADCL */ +#define WM8961_ADCL_SHIFT 3 /* ADCL */ +#define WM8961_ADCL_WIDTH 1 /* ADCL */ +#define WM8961_ADCR 0x0004 /* ADCR */ +#define WM8961_ADCR_MASK 0x0004 /* ADCR */ +#define WM8961_ADCR_SHIFT 2 /* ADCR */ +#define WM8961_ADCR_WIDTH 1 /* ADCR */ +#define WM8961_MICB 0x0002 /* MICB */ +#define WM8961_MICB_MASK 0x0002 /* MICB */ +#define WM8961_MICB_SHIFT 1 /* MICB */ +#define WM8961_MICB_WIDTH 1 /* MICB */ + +/* + * R26 (0x1A) - Pwr Mgmt (2) + */ +#define WM8961_DACL 0x0100 /* DACL */ +#define WM8961_DACL_MASK 0x0100 /* DACL */ +#define WM8961_DACL_SHIFT 8 /* DACL */ +#define WM8961_DACL_WIDTH 1 /* DACL */ +#define WM8961_DACR 0x0080 /* DACR */ +#define WM8961_DACR_MASK 0x0080 /* DACR */ +#define WM8961_DACR_SHIFT 7 /* DACR */ +#define WM8961_DACR_WIDTH 1 /* DACR */ +#define WM8961_LOUT1_PGA 0x0040 /* LOUT1_PGA */ +#define WM8961_LOUT1_PGA_MASK 0x0040 /* LOUT1_PGA */ +#define WM8961_LOUT1_PGA_SHIFT 6 /* LOUT1_PGA */ +#define WM8961_LOUT1_PGA_WIDTH 1 /* LOUT1_PGA */ +#define WM8961_ROUT1_PGA 0x0020 /* ROUT1_PGA */ +#define WM8961_ROUT1_PGA_MASK 0x0020 /* ROUT1_PGA */ +#define WM8961_ROUT1_PGA_SHIFT 5 /* ROUT1_PGA */ +#define WM8961_ROUT1_PGA_WIDTH 1 /* ROUT1_PGA */ +#define WM8961_SPKL_PGA 0x0010 /* SPKL_PGA */ +#define WM8961_SPKL_PGA_MASK 0x0010 /* SPKL_PGA */ +#define WM8961_SPKL_PGA_SHIFT 4 /* SPKL_PGA */ +#define WM8961_SPKL_PGA_WIDTH 1 /* SPKL_PGA */ +#define WM8961_SPKR_PGA 0x0008 /* SPKR_PGA */ +#define WM8961_SPKR_PGA_MASK 0x0008 /* SPKR_PGA */ +#define WM8961_SPKR_PGA_SHIFT 3 /* SPKR_PGA */ +#define WM8961_SPKR_PGA_WIDTH 1 /* SPKR_PGA */ + +/* + * R27 (0x1B) - Additional Control (3) + */ +#define WM8961_SAMPLE_RATE_MASK 0x0007 /* SAMPLE_RATE - [2:0] */ +#define WM8961_SAMPLE_RATE_SHIFT 0 /* SAMPLE_RATE - [2:0] */ +#define WM8961_SAMPLE_RATE_WIDTH 3 /* SAMPLE_RATE - [2:0] */ + +/* + * R28 (0x1C) - Anti-pop + */ +#define WM8961_BUFDCOPEN 0x0010 /* BUFDCOPEN */ +#define WM8961_BUFDCOPEN_MASK 0x0010 /* BUFDCOPEN */ +#define WM8961_BUFDCOPEN_SHIFT 4 /* BUFDCOPEN */ +#define WM8961_BUFDCOPEN_WIDTH 1 /* BUFDCOPEN */ +#define WM8961_BUFIOEN 0x0008 /* BUFIOEN */ +#define WM8961_BUFIOEN_MASK 0x0008 /* BUFIOEN */ +#define WM8961_BUFIOEN_SHIFT 3 /* BUFIOEN */ +#define WM8961_BUFIOEN_WIDTH 1 /* BUFIOEN */ +#define WM8961_SOFT_ST 0x0004 /* SOFT_ST */ +#define WM8961_SOFT_ST_MASK 0x0004 /* SOFT_ST */ +#define WM8961_SOFT_ST_SHIFT 2 /* SOFT_ST */ +#define WM8961_SOFT_ST_WIDTH 1 /* SOFT_ST */ + +/* + * R30 (0x1E) - Clocking 3 + */ +#define WM8961_CLK_TO_DIV_MASK 0x0180 /* CLK_TO_DIV - [8:7] */ +#define WM8961_CLK_TO_DIV_SHIFT 7 /* CLK_TO_DIV - [8:7] */ +#define WM8961_CLK_TO_DIV_WIDTH 2 /* CLK_TO_DIV - [8:7] */ +#define WM8961_CLK_256K_DIV_MASK 0x007E /* CLK_256K_DIV - [6:1] */ +#define WM8961_CLK_256K_DIV_SHIFT 1 /* CLK_256K_DIV - [6:1] */ +#define WM8961_CLK_256K_DIV_WIDTH 6 /* CLK_256K_DIV - [6:1] */ +#define WM8961_MANUAL_MODE 0x0001 /* MANUAL_MODE */ +#define WM8961_MANUAL_MODE_MASK 0x0001 /* MANUAL_MODE */ +#define WM8961_MANUAL_MODE_SHIFT 0 /* MANUAL_MODE */ +#define WM8961_MANUAL_MODE_WIDTH 1 /* MANUAL_MODE */ + +/* + * R32 (0x20) - ADCL signal path + */ +#define WM8961_LMICBOOST_MASK 0x0030 /* LMICBOOST - [5:4] */ +#define WM8961_LMICBOOST_SHIFT 4 /* LMICBOOST - [5:4] */ +#define WM8961_LMICBOOST_WIDTH 2 /* LMICBOOST - [5:4] */ + +/* + * R33 (0x21) - ADCR signal path + */ +#define WM8961_RMICBOOST_MASK 0x0030 /* RMICBOOST - [5:4] */ +#define WM8961_RMICBOOST_SHIFT 4 /* RMICBOOST - [5:4] */ +#define WM8961_RMICBOOST_WIDTH 2 /* RMICBOOST - [5:4] */ + +/* + * R40 (0x28) - LOUT2 volume + */ +#define WM8961_SPKVU 0x0100 /* SPKVU */ +#define WM8961_SPKVU_MASK 0x0100 /* SPKVU */ +#define WM8961_SPKVU_SHIFT 8 /* SPKVU */ +#define WM8961_SPKVU_WIDTH 1 /* SPKVU */ +#define WM8961_SPKLZC 0x0080 /* SPKLZC */ +#define WM8961_SPKLZC_MASK 0x0080 /* SPKLZC */ +#define WM8961_SPKLZC_SHIFT 7 /* SPKLZC */ +#define WM8961_SPKLZC_WIDTH 1 /* SPKLZC */ +#define WM8961_SPKLVOL_MASK 0x007F /* SPKLVOL - [6:0] */ +#define WM8961_SPKLVOL_SHIFT 0 /* SPKLVOL - [6:0] */ +#define WM8961_SPKLVOL_WIDTH 7 /* SPKLVOL - [6:0] */ + +/* + * R41 (0x29) - ROUT2 volume + */ +#define WM8961_SPKVU 0x0100 /* SPKVU */ +#define WM8961_SPKVU_MASK 0x0100 /* SPKVU */ +#define WM8961_SPKVU_SHIFT 8 /* SPKVU */ +#define WM8961_SPKVU_WIDTH 1 /* SPKVU */ +#define WM8961_SPKRZC 0x0080 /* SPKRZC */ +#define WM8961_SPKRZC_MASK 0x0080 /* SPKRZC */ +#define WM8961_SPKRZC_SHIFT 7 /* SPKRZC */ +#define WM8961_SPKRZC_WIDTH 1 /* SPKRZC */ +#define WM8961_SPKRVOL_MASK 0x007F /* SPKRVOL - [6:0] */ +#define WM8961_SPKRVOL_SHIFT 0 /* SPKRVOL - [6:0] */ +#define WM8961_SPKRVOL_WIDTH 7 /* SPKRVOL - [6:0] */ + +/* + * R47 (0x2F) - Pwr Mgmt (3) + */ +#define WM8961_TEMP_SHUT 0x0002 /* TEMP_SHUT */ +#define WM8961_TEMP_SHUT_MASK 0x0002 /* TEMP_SHUT */ +#define WM8961_TEMP_SHUT_SHIFT 1 /* TEMP_SHUT */ +#define WM8961_TEMP_SHUT_WIDTH 1 /* TEMP_SHUT */ +#define WM8961_TEMP_WARN 0x0001 /* TEMP_WARN */ +#define WM8961_TEMP_WARN_MASK 0x0001 /* TEMP_WARN */ +#define WM8961_TEMP_WARN_SHIFT 0 /* TEMP_WARN */ +#define WM8961_TEMP_WARN_WIDTH 1 /* TEMP_WARN */ + +/* + * R48 (0x30) - Additional Control (4) + */ +#define WM8961_TSENSEN 0x0002 /* TSENSEN */ +#define WM8961_TSENSEN_MASK 0x0002 /* TSENSEN */ +#define WM8961_TSENSEN_SHIFT 1 /* TSENSEN */ +#define WM8961_TSENSEN_WIDTH 1 /* TSENSEN */ +#define WM8961_MBSEL 0x0001 /* MBSEL */ +#define WM8961_MBSEL_MASK 0x0001 /* MBSEL */ +#define WM8961_MBSEL_SHIFT 0 /* MBSEL */ +#define WM8961_MBSEL_WIDTH 1 /* MBSEL */ + +/* + * R49 (0x31) - Class D Control 1 + */ +#define WM8961_SPKR_ENA 0x0080 /* SPKR_ENA */ +#define WM8961_SPKR_ENA_MASK 0x0080 /* SPKR_ENA */ +#define WM8961_SPKR_ENA_SHIFT 7 /* SPKR_ENA */ +#define WM8961_SPKR_ENA_WIDTH 1 /* SPKR_ENA */ +#define WM8961_SPKL_ENA 0x0040 /* SPKL_ENA */ +#define WM8961_SPKL_ENA_MASK 0x0040 /* SPKL_ENA */ +#define WM8961_SPKL_ENA_SHIFT 6 /* SPKL_ENA */ +#define WM8961_SPKL_ENA_WIDTH 1 /* SPKL_ENA */ + +/* + * R51 (0x33) - Class D Control 2 + */ +#define WM8961_CLASSD_ACGAIN_MASK 0x0007 /* CLASSD_ACGAIN - [2:0] */ +#define WM8961_CLASSD_ACGAIN_SHIFT 0 /* CLASSD_ACGAIN - [2:0] */ +#define WM8961_CLASSD_ACGAIN_WIDTH 3 /* CLASSD_ACGAIN - [2:0] */ + +/* + * R56 (0x38) - Clocking 4 + */ +#define WM8961_CLK_DCS_DIV_MASK 0x01E0 /* CLK_DCS_DIV - [8:5] */ +#define WM8961_CLK_DCS_DIV_SHIFT 5 /* CLK_DCS_DIV - [8:5] */ +#define WM8961_CLK_DCS_DIV_WIDTH 4 /* CLK_DCS_DIV - [8:5] */ +#define WM8961_CLK_SYS_RATE_MASK 0x001E /* CLK_SYS_RATE - [4:1] */ +#define WM8961_CLK_SYS_RATE_SHIFT 1 /* CLK_SYS_RATE - [4:1] */ +#define WM8961_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [4:1] */ + +/* + * R57 (0x39) - DSP Sidetone 0 + */ +#define WM8961_ADCR_DAC_SVOL_MASK 0x00F0 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8961_ADCR_DAC_SVOL_SHIFT 4 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8961_ADCR_DAC_SVOL_WIDTH 4 /* ADCR_DAC_SVOL - [7:4] */ +#define WM8961_ADC_TO_DACR_MASK 0x000C /* ADC_TO_DACR - [3:2] */ +#define WM8961_ADC_TO_DACR_SHIFT 2 /* ADC_TO_DACR - [3:2] */ +#define WM8961_ADC_TO_DACR_WIDTH 2 /* ADC_TO_DACR - [3:2] */ + +/* + * R58 (0x3A) - DSP Sidetone 1 + */ +#define WM8961_ADCL_DAC_SVOL_MASK 0x00F0 /* ADCL_DAC_SVOL - [7:4] */ +#define WM8961_ADCL_DAC_SVOL_SHIFT 4 /* ADCL_DAC_SVOL - [7:4] */ +#define WM8961_ADCL_DAC_SVOL_WIDTH 4 /* ADCL_DAC_SVOL - [7:4] */ +#define WM8961_ADC_TO_DACL_MASK 0x000C /* ADC_TO_DACL - [3:2] */ +#define WM8961_ADC_TO_DACL_SHIFT 2 /* ADC_TO_DACL - [3:2] */ +#define WM8961_ADC_TO_DACL_WIDTH 2 /* ADC_TO_DACL - [3:2] */ + +/* + * R60 (0x3C) - DC Servo 0 + */ +#define WM8961_DCS_ENA_CHAN_INL 0x0080 /* DCS_ENA_CHAN_INL */ +#define WM8961_DCS_ENA_CHAN_INL_MASK 0x0080 /* DCS_ENA_CHAN_INL */ +#define WM8961_DCS_ENA_CHAN_INL_SHIFT 7 /* DCS_ENA_CHAN_INL */ +#define WM8961_DCS_ENA_CHAN_INL_WIDTH 1 /* DCS_ENA_CHAN_INL */ +#define WM8961_DCS_TRIG_STARTUP_INL 0x0040 /* DCS_TRIG_STARTUP_INL */ +#define WM8961_DCS_TRIG_STARTUP_INL_MASK 0x0040 /* DCS_TRIG_STARTUP_INL */ +#define WM8961_DCS_TRIG_STARTUP_INL_SHIFT 6 /* DCS_TRIG_STARTUP_INL */ +#define WM8961_DCS_TRIG_STARTUP_INL_WIDTH 1 /* DCS_TRIG_STARTUP_INL */ +#define WM8961_DCS_TRIG_SERIES_INL 0x0010 /* DCS_TRIG_SERIES_INL */ +#define WM8961_DCS_TRIG_SERIES_INL_MASK 0x0010 /* DCS_TRIG_SERIES_INL */ +#define WM8961_DCS_TRIG_SERIES_INL_SHIFT 4 /* DCS_TRIG_SERIES_INL */ +#define WM8961_DCS_TRIG_SERIES_INL_WIDTH 1 /* DCS_TRIG_SERIES_INL */ +#define WM8961_DCS_ENA_CHAN_INR 0x0008 /* DCS_ENA_CHAN_INR */ +#define WM8961_DCS_ENA_CHAN_INR_MASK 0x0008 /* DCS_ENA_CHAN_INR */ +#define WM8961_DCS_ENA_CHAN_INR_SHIFT 3 /* DCS_ENA_CHAN_INR */ +#define WM8961_DCS_ENA_CHAN_INR_WIDTH 1 /* DCS_ENA_CHAN_INR */ +#define WM8961_DCS_TRIG_STARTUP_INR 0x0004 /* DCS_TRIG_STARTUP_INR */ +#define WM8961_DCS_TRIG_STARTUP_INR_MASK 0x0004 /* DCS_TRIG_STARTUP_INR */ +#define WM8961_DCS_TRIG_STARTUP_INR_SHIFT 2 /* DCS_TRIG_STARTUP_INR */ +#define WM8961_DCS_TRIG_STARTUP_INR_WIDTH 1 /* DCS_TRIG_STARTUP_INR */ +#define WM8961_DCS_TRIG_SERIES_INR 0x0001 /* DCS_TRIG_SERIES_INR */ +#define WM8961_DCS_TRIG_SERIES_INR_MASK 0x0001 /* DCS_TRIG_SERIES_INR */ +#define WM8961_DCS_TRIG_SERIES_INR_SHIFT 0 /* DCS_TRIG_SERIES_INR */ +#define WM8961_DCS_TRIG_SERIES_INR_WIDTH 1 /* DCS_TRIG_SERIES_INR */ + +/* + * R61 (0x3D) - DC Servo 1 + */ +#define WM8961_DCS_ENA_CHAN_HPL 0x0080 /* DCS_ENA_CHAN_HPL */ +#define WM8961_DCS_ENA_CHAN_HPL_MASK 0x0080 /* DCS_ENA_CHAN_HPL */ +#define WM8961_DCS_ENA_CHAN_HPL_SHIFT 7 /* DCS_ENA_CHAN_HPL */ +#define WM8961_DCS_ENA_CHAN_HPL_WIDTH 1 /* DCS_ENA_CHAN_HPL */ +#define WM8961_DCS_TRIG_STARTUP_HPL 0x0040 /* DCS_TRIG_STARTUP_HPL */ +#define WM8961_DCS_TRIG_STARTUP_HPL_MASK 0x0040 /* DCS_TRIG_STARTUP_HPL */ +#define WM8961_DCS_TRIG_STARTUP_HPL_SHIFT 6 /* DCS_TRIG_STARTUP_HPL */ +#define WM8961_DCS_TRIG_STARTUP_HPL_WIDTH 1 /* DCS_TRIG_STARTUP_HPL */ +#define WM8961_DCS_TRIG_SERIES_HPL 0x0010 /* DCS_TRIG_SERIES_HPL */ +#define WM8961_DCS_TRIG_SERIES_HPL_MASK 0x0010 /* DCS_TRIG_SERIES_HPL */ +#define WM8961_DCS_TRIG_SERIES_HPL_SHIFT 4 /* DCS_TRIG_SERIES_HPL */ +#define WM8961_DCS_TRIG_SERIES_HPL_WIDTH 1 /* DCS_TRIG_SERIES_HPL */ +#define WM8961_DCS_ENA_CHAN_HPR 0x0008 /* DCS_ENA_CHAN_HPR */ +#define WM8961_DCS_ENA_CHAN_HPR_MASK 0x0008 /* DCS_ENA_CHAN_HPR */ +#define WM8961_DCS_ENA_CHAN_HPR_SHIFT 3 /* DCS_ENA_CHAN_HPR */ +#define WM8961_DCS_ENA_CHAN_HPR_WIDTH 1 /* DCS_ENA_CHAN_HPR */ +#define WM8961_DCS_TRIG_STARTUP_HPR 0x0004 /* DCS_TRIG_STARTUP_HPR */ +#define WM8961_DCS_TRIG_STARTUP_HPR_MASK 0x0004 /* DCS_TRIG_STARTUP_HPR */ +#define WM8961_DCS_TRIG_STARTUP_HPR_SHIFT 2 /* DCS_TRIG_STARTUP_HPR */ +#define WM8961_DCS_TRIG_STARTUP_HPR_WIDTH 1 /* DCS_TRIG_STARTUP_HPR */ +#define WM8961_DCS_TRIG_SERIES_HPR 0x0001 /* DCS_TRIG_SERIES_HPR */ +#define WM8961_DCS_TRIG_SERIES_HPR_MASK 0x0001 /* DCS_TRIG_SERIES_HPR */ +#define WM8961_DCS_TRIG_SERIES_HPR_SHIFT 0 /* DCS_TRIG_SERIES_HPR */ +#define WM8961_DCS_TRIG_SERIES_HPR_WIDTH 1 /* DCS_TRIG_SERIES_HPR */ + +/* + * R63 (0x3F) - DC Servo 3 + */ +#define WM8961_DCS_FILT_BW_SERIES_MASK 0x0030 /* DCS_FILT_BW_SERIES - [5:4] */ +#define WM8961_DCS_FILT_BW_SERIES_SHIFT 4 /* DCS_FILT_BW_SERIES - [5:4] */ +#define WM8961_DCS_FILT_BW_SERIES_WIDTH 2 /* DCS_FILT_BW_SERIES - [5:4] */ + +/* + * R65 (0x41) - DC Servo 5 + */ +#define WM8961_DCS_SERIES_NO_HP_MASK 0x007F /* DCS_SERIES_NO_HP - [6:0] */ +#define WM8961_DCS_SERIES_NO_HP_SHIFT 0 /* DCS_SERIES_NO_HP - [6:0] */ +#define WM8961_DCS_SERIES_NO_HP_WIDTH 7 /* DCS_SERIES_NO_HP - [6:0] */ + +/* + * R68 (0x44) - Analogue PGA Bias + */ +#define WM8961_HP_PGAS_BIAS_MASK 0x0007 /* HP_PGAS_BIAS - [2:0] */ +#define WM8961_HP_PGAS_BIAS_SHIFT 0 /* HP_PGAS_BIAS - [2:0] */ +#define WM8961_HP_PGAS_BIAS_WIDTH 3 /* HP_PGAS_BIAS - [2:0] */ + +/* + * R69 (0x45) - Analogue HP 0 + */ +#define WM8961_HPL_RMV_SHORT 0x0080 /* HPL_RMV_SHORT */ +#define WM8961_HPL_RMV_SHORT_MASK 0x0080 /* HPL_RMV_SHORT */ +#define WM8961_HPL_RMV_SHORT_SHIFT 7 /* HPL_RMV_SHORT */ +#define WM8961_HPL_RMV_SHORT_WIDTH 1 /* HPL_RMV_SHORT */ +#define WM8961_HPL_ENA_OUTP 0x0040 /* HPL_ENA_OUTP */ +#define WM8961_HPL_ENA_OUTP_MASK 0x0040 /* HPL_ENA_OUTP */ +#define WM8961_HPL_ENA_OUTP_SHIFT 6 /* HPL_ENA_OUTP */ +#define WM8961_HPL_ENA_OUTP_WIDTH 1 /* HPL_ENA_OUTP */ +#define WM8961_HPL_ENA_DLY 0x0020 /* HPL_ENA_DLY */ +#define WM8961_HPL_ENA_DLY_MASK 0x0020 /* HPL_ENA_DLY */ +#define WM8961_HPL_ENA_DLY_SHIFT 5 /* HPL_ENA_DLY */ +#define WM8961_HPL_ENA_DLY_WIDTH 1 /* HPL_ENA_DLY */ +#define WM8961_HPL_ENA 0x0010 /* HPL_ENA */ +#define WM8961_HPL_ENA_MASK 0x0010 /* HPL_ENA */ +#define WM8961_HPL_ENA_SHIFT 4 /* HPL_ENA */ +#define WM8961_HPL_ENA_WIDTH 1 /* HPL_ENA */ +#define WM8961_HPR_RMV_SHORT 0x0008 /* HPR_RMV_SHORT */ +#define WM8961_HPR_RMV_SHORT_MASK 0x0008 /* HPR_RMV_SHORT */ +#define WM8961_HPR_RMV_SHORT_SHIFT 3 /* HPR_RMV_SHORT */ +#define WM8961_HPR_RMV_SHORT_WIDTH 1 /* HPR_RMV_SHORT */ +#define WM8961_HPR_ENA_OUTP 0x0004 /* HPR_ENA_OUTP */ +#define WM8961_HPR_ENA_OUTP_MASK 0x0004 /* HPR_ENA_OUTP */ +#define WM8961_HPR_ENA_OUTP_SHIFT 2 /* HPR_ENA_OUTP */ +#define WM8961_HPR_ENA_OUTP_WIDTH 1 /* HPR_ENA_OUTP */ +#define WM8961_HPR_ENA_DLY 0x0002 /* HPR_ENA_DLY */ +#define WM8961_HPR_ENA_DLY_MASK 0x0002 /* HPR_ENA_DLY */ +#define WM8961_HPR_ENA_DLY_SHIFT 1 /* HPR_ENA_DLY */ +#define WM8961_HPR_ENA_DLY_WIDTH 1 /* HPR_ENA_DLY */ +#define WM8961_HPR_ENA 0x0001 /* HPR_ENA */ +#define WM8961_HPR_ENA_MASK 0x0001 /* HPR_ENA */ +#define WM8961_HPR_ENA_SHIFT 0 /* HPR_ENA */ +#define WM8961_HPR_ENA_WIDTH 1 /* HPR_ENA */ + +/* + * R71 (0x47) - Analogue HP 2 + */ +#define WM8961_HPL_VOL_MASK 0x01C0 /* HPL_VOL - [8:6] */ +#define WM8961_HPL_VOL_SHIFT 6 /* HPL_VOL - [8:6] */ +#define WM8961_HPL_VOL_WIDTH 3 /* HPL_VOL - [8:6] */ +#define WM8961_HPR_VOL_MASK 0x0038 /* HPR_VOL - [5:3] */ +#define WM8961_HPR_VOL_SHIFT 3 /* HPR_VOL - [5:3] */ +#define WM8961_HPR_VOL_WIDTH 3 /* HPR_VOL - [5:3] */ +#define WM8961_HP_BIAS_BOOST_MASK 0x0007 /* HP_BIAS_BOOST - [2:0] */ +#define WM8961_HP_BIAS_BOOST_SHIFT 0 /* HP_BIAS_BOOST - [2:0] */ +#define WM8961_HP_BIAS_BOOST_WIDTH 3 /* HP_BIAS_BOOST - [2:0] */ + +/* + * R72 (0x48) - Charge Pump 1 + */ +#define WM8961_CP_ENA 0x0001 /* CP_ENA */ +#define WM8961_CP_ENA_MASK 0x0001 /* CP_ENA */ +#define WM8961_CP_ENA_SHIFT 0 /* CP_ENA */ +#define WM8961_CP_ENA_WIDTH 1 /* CP_ENA */ + +/* + * R82 (0x52) - Charge Pump B + */ +#define WM8961_CP_DYN_PWR_MASK 0x0003 /* CP_DYN_PWR - [1:0] */ +#define WM8961_CP_DYN_PWR_SHIFT 0 /* CP_DYN_PWR - [1:0] */ +#define WM8961_CP_DYN_PWR_WIDTH 2 /* CP_DYN_PWR - [1:0] */ + +/* + * R87 (0x57) - Write Sequencer 1 + */ +#define WM8961_WSEQ_ENA 0x0020 /* WSEQ_ENA */ +#define WM8961_WSEQ_ENA_MASK 0x0020 /* WSEQ_ENA */ +#define WM8961_WSEQ_ENA_SHIFT 5 /* WSEQ_ENA */ +#define WM8961_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */ +#define WM8961_WSEQ_WRITE_INDEX_MASK 0x001F /* WSEQ_WRITE_INDEX - [4:0] */ +#define WM8961_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [4:0] */ +#define WM8961_WSEQ_WRITE_INDEX_WIDTH 5 /* WSEQ_WRITE_INDEX - [4:0] */ + +/* + * R88 (0x58) - Write Sequencer 2 + */ +#define WM8961_WSEQ_EOS 0x0100 /* WSEQ_EOS */ +#define WM8961_WSEQ_EOS_MASK 0x0100 /* WSEQ_EOS */ +#define WM8961_WSEQ_EOS_SHIFT 8 /* WSEQ_EOS */ +#define WM8961_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */ +#define WM8961_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */ +#define WM8961_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */ +#define WM8961_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */ + +/* + * R89 (0x59) - Write Sequencer 3 + */ +#define WM8961_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */ +#define WM8961_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */ +#define WM8961_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */ + +/* + * R90 (0x5A) - Write Sequencer 4 + */ +#define WM8961_WSEQ_ABORT 0x0100 /* WSEQ_ABORT */ +#define WM8961_WSEQ_ABORT_MASK 0x0100 /* WSEQ_ABORT */ +#define WM8961_WSEQ_ABORT_SHIFT 8 /* WSEQ_ABORT */ +#define WM8961_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */ +#define WM8961_WSEQ_START 0x0080 /* WSEQ_START */ +#define WM8961_WSEQ_START_MASK 0x0080 /* WSEQ_START */ +#define WM8961_WSEQ_START_SHIFT 7 /* WSEQ_START */ +#define WM8961_WSEQ_START_WIDTH 1 /* WSEQ_START */ +#define WM8961_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */ +#define WM8961_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */ +#define WM8961_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */ + +/* + * R91 (0x5B) - Write Sequencer 5 + */ +#define WM8961_WSEQ_DATA_WIDTH_MASK 0x0070 /* WSEQ_DATA_WIDTH - [6:4] */ +#define WM8961_WSEQ_DATA_WIDTH_SHIFT 4 /* WSEQ_DATA_WIDTH - [6:4] */ +#define WM8961_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [6:4] */ +#define WM8961_WSEQ_DATA_START_MASK 0x000F /* WSEQ_DATA_START - [3:0] */ +#define WM8961_WSEQ_DATA_START_SHIFT 0 /* WSEQ_DATA_START - [3:0] */ +#define WM8961_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [3:0] */ + +/* + * R92 (0x5C) - Write Sequencer 6 + */ +#define WM8961_WSEQ_DELAY_MASK 0x000F /* WSEQ_DELAY - [3:0] */ +#define WM8961_WSEQ_DELAY_SHIFT 0 /* WSEQ_DELAY - [3:0] */ +#define WM8961_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [3:0] */ + +/* + * R93 (0x5D) - Write Sequencer 7 + */ +#define WM8961_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */ +#define WM8961_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */ +#define WM8961_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */ +#define WM8961_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */ + +/* + * R252 (0xFC) - General test 1 + */ +#define WM8961_ARA_ENA 0x0002 /* ARA_ENA */ +#define WM8961_ARA_ENA_MASK 0x0002 /* ARA_ENA */ +#define WM8961_ARA_ENA_SHIFT 1 /* ARA_ENA */ +#define WM8961_ARA_ENA_WIDTH 1 /* ARA_ENA */ +#define WM8961_AUTO_INC 0x0001 /* AUTO_INC */ +#define WM8961_AUTO_INC_MASK 0x0001 /* AUTO_INC */ +#define WM8961_AUTO_INC_SHIFT 0 /* AUTO_INC */ +#define WM8961_AUTO_INC_WIDTH 1 /* AUTO_INC */ + +#endif -- cgit v1.2.3 From ba2533a47865ec0dbc72834287a8a048e9337a95 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 12 Jun 2009 16:55:56 +0100 Subject: ASoC: Remove odd bit clock ratios for WM8903 These are not supported since performance can not be guaranteed when they are in use. Signed-off-by: Mark Brown Cc: stable@kernel.org --- sound/soc/codecs/wm8903.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index d8a9222fbf7..e8d2e3e14c4 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -1257,22 +1257,18 @@ static struct { int div; } bclk_divs[] = { { 10, 0 }, - { 15, 1 }, { 20, 2 }, { 30, 3 }, { 40, 4 }, { 50, 5 }, - { 55, 6 }, { 60, 7 }, { 80, 8 }, { 100, 9 }, - { 110, 10 }, { 120, 11 }, { 160, 12 }, { 200, 13 }, { 220, 14 }, { 240, 15 }, - { 250, 16 }, { 300, 17 }, { 320, 18 }, { 440, 19 }, -- cgit v1.2.3 From df205936d5d1dfec9a52c90af77bb54a2c9c9728 Mon Sep 17 00:00:00 2001 From: "Lopez Cruz, Misael" Date: Thu, 11 Jun 2009 17:35:27 -0500 Subject: ASoC: Zoom2: Add machine driver for Zoom2 board Add support for Zoom2 board. Zoom2 machine driver connects both codec DAIs (audio and voice) to omap3 McBSP ports in the following way: HiFi <-> McBSP2 Voice <-> McBSP3 The zoom2 driver has the following DAPM widgets: * Ext Mic: MAINMIC, SUBMIC (with bias) * Ext Spk: HFL, HFR * Headset Stereophone: HSOL, HSOR * Headset Mic: HSMIC (with bias) * Aux In: AUXL, AUXR Signed-off-by: Misael Lopez Cruz Signed-off-by: Mark Brown --- sound/soc/omap/Kconfig | 7 ++ sound/soc/omap/Makefile | 2 + sound/soc/omap/zoom2.c | 301 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 310 insertions(+) create mode 100644 sound/soc/omap/zoom2.c (limited to 'sound/soc') diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index b771238662b..a5a90e59453 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -72,4 +72,11 @@ config SND_OMAP_SOC_OMAP3_BEAGLE help Say Y if you want to add support for SoC audio on the Beagleboard. +config SND_OMAP_SOC_ZOOM2 + tristate "SoC Audio support for Zoom2" + depends on TWL4030_CORE && SND_OMAP_SOC && MACH_OMAP_ZOOM2 + select SND_OMAP_SOC_MCBSP + select SND_SOC_TWL4030 + help + Say Y if you want to add support for Soc audio on Zoom2 board. diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile index a37f4986238..fefc48f02bd 100644 --- a/sound/soc/omap/Makefile +++ b/sound/soc/omap/Makefile @@ -14,6 +14,7 @@ snd-soc-omap3evm-objs := omap3evm.o snd-soc-sdp3430-objs := sdp3430.o snd-soc-omap3pandora-objs := omap3pandora.o snd-soc-omap3beagle-objs := omap3beagle.o +snd-soc-zoom2-objs := zoom2.o obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o obj-$(CONFIG_SND_OMAP_SOC_OSK5912) += snd-soc-osk5912.o @@ -23,3 +24,4 @@ obj-$(CONFIG_MACH_OMAP3EVM) += snd-soc-omap3evm.o obj-$(CONFIG_SND_OMAP_SOC_SDP3430) += snd-soc-sdp3430.o obj-$(CONFIG_SND_OMAP_SOC_OMAP3_PANDORA) += snd-soc-omap3pandora.o obj-$(CONFIG_SND_OMAP_SOC_OMAP3_BEAGLE) += snd-soc-omap3beagle.o +obj-$(CONFIG_SND_OMAP_SOC_ZOOM2) += snd-soc-zoom2.o diff --git a/sound/soc/omap/zoom2.c b/sound/soc/omap/zoom2.c new file mode 100644 index 00000000000..be2e307fdf7 --- /dev/null +++ b/sound/soc/omap/zoom2.c @@ -0,0 +1,301 @@ +/* + * zoom2.c -- SoC audio for Zoom2 + * + * Author: Misael Lopez Cruz + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "omap-mcbsp.h" +#include "omap-pcm.h" +#include "../codecs/twl4030.h" + +#define ZOOM2_HEADSET_MUX_GPIO (OMAP_MAX_GPIO_LINES + 15) + +static int zoom2_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; + + /* Set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) { + printk(KERN_ERR "can't set codec DAI configuration\n"); + return ret; + } + + /* Set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) { + printk(KERN_ERR "can't set cpu DAI configuration\n"); + return ret; + } + + /* Set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000, + SND_SOC_CLOCK_IN); + if (ret < 0) { + printk(KERN_ERR "can't set codec system clock\n"); + return ret; + } + + return 0; +} + +static struct snd_soc_ops zoom2_ops = { + .hw_params = zoom2_hw_params, +}; + +static int zoom2_hw_voice_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; + + /* Set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBS_CFM); + if (ret) { + printk(KERN_ERR "can't set codec DAI configuration\n"); + return ret; + } + + /* Set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_NF | + SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) { + printk(KERN_ERR "can't set cpu DAI configuration\n"); + return ret; + } + + /* Set the codec system clock for DAC and ADC */ + ret = snd_soc_dai_set_sysclk(codec_dai, 0, 26000000, + SND_SOC_CLOCK_IN); + if (ret < 0) { + printk(KERN_ERR "can't set codec system clock\n"); + return ret; + } + + return 0; +} + +static struct snd_soc_ops zoom2_voice_ops = { + .hw_params = zoom2_hw_voice_params, +}; + +/* Zoom2 machine DAPM */ +static const struct snd_soc_dapm_widget zoom2_twl4030_dapm_widgets[] = { + SND_SOC_DAPM_MIC("Ext Mic", NULL), + SND_SOC_DAPM_SPK("Ext Spk", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_HP("Headset Stereophone", NULL), + SND_SOC_DAPM_LINE("Aux In", NULL), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* External Mics: MAINMIC, SUBMIC with bias*/ + {"MAINMIC", NULL, "Mic Bias 1"}, + {"SUBMIC", NULL, "Mic Bias 2"}, + {"Mic Bias 1", NULL, "Ext Mic"}, + {"Mic Bias 2", NULL, "Ext Mic"}, + + /* External Speakers: HFL, HFR */ + {"Ext Spk", NULL, "HFL"}, + {"Ext Spk", NULL, "HFR"}, + + /* Headset Stereophone: HSOL, HSOR */ + {"Headset Stereophone", NULL, "HSOL"}, + {"Headset Stereophone", NULL, "HSOR"}, + + /* Headset Mic: HSMIC with bias */ + {"HSMIC", NULL, "Headset Mic Bias"}, + {"Headset Mic Bias", NULL, "Headset Mic"}, + + /* Aux In: AUXL, AUXR */ + {"Aux In", NULL, "AUXL"}, + {"Aux In", NULL, "AUXR"}, +}; + +static int zoom2_twl4030_init(struct snd_soc_codec *codec) +{ + int ret; + + /* Add Zoom2 specific widgets */ + ret = snd_soc_dapm_new_controls(codec, zoom2_twl4030_dapm_widgets, + ARRAY_SIZE(zoom2_twl4030_dapm_widgets)); + if (ret) + return ret; + + /* Set up Zoom2 specific audio path audio_map */ + snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + + /* Zoom2 connected pins */ + snd_soc_dapm_enable_pin(codec, "Ext Mic"); + snd_soc_dapm_enable_pin(codec, "Ext Spk"); + snd_soc_dapm_enable_pin(codec, "Headset Mic"); + snd_soc_dapm_enable_pin(codec, "Headset Stereophone"); + snd_soc_dapm_enable_pin(codec, "Aux In"); + + /* TWL4030 not connected pins */ + snd_soc_dapm_nc_pin(codec, "CARKITMIC"); + snd_soc_dapm_nc_pin(codec, "DIGIMIC0"); + snd_soc_dapm_nc_pin(codec, "DIGIMIC1"); + + snd_soc_dapm_nc_pin(codec, "OUTL"); + snd_soc_dapm_nc_pin(codec, "OUTR"); + snd_soc_dapm_nc_pin(codec, "EARPIECE"); + snd_soc_dapm_nc_pin(codec, "PREDRIVEL"); + snd_soc_dapm_nc_pin(codec, "PREDRIVER"); + snd_soc_dapm_nc_pin(codec, "CARKITL"); + snd_soc_dapm_nc_pin(codec, "CARKITR"); + + ret = snd_soc_dapm_sync(codec); + + return ret; +} + +static int zoom2_twl4030_voice_init(struct snd_soc_codec *codec) +{ + unsigned short reg; + + /* Enable voice interface */ + reg = codec->read(codec, TWL4030_REG_VOICE_IF); + reg |= TWL4030_VIF_DIN_EN | TWL4030_VIF_DOUT_EN | TWL4030_VIF_EN; + codec->write(codec, TWL4030_REG_VOICE_IF, reg); + + return 0; +} + +/* Digital audio interface glue - connects codec <--> CPU */ +static struct snd_soc_dai_link zoom2_dai[] = { + { + .name = "TWL4030 I2S", + .stream_name = "TWL4030 Audio", + .cpu_dai = &omap_mcbsp_dai[0], + .codec_dai = &twl4030_dai[TWL4030_DAI_HIFI], + .init = zoom2_twl4030_init, + .ops = &zoom2_ops, + }, + { + .name = "TWL4030 PCM", + .stream_name = "TWL4030 Voice", + .cpu_dai = &omap_mcbsp_dai[1], + .codec_dai = &twl4030_dai[TWL4030_DAI_VOICE], + .init = zoom2_twl4030_voice_init, + .ops = &zoom2_voice_ops, + }, +}; + +/* Audio machine driver */ +static struct snd_soc_card snd_soc_zoom2 = { + .name = "Zoom2", + .platform = &omap_soc_platform, + .dai_link = zoom2_dai, + .num_links = ARRAY_SIZE(zoom2_dai), +}; + +/* twl4030 setup */ +static struct twl4030_setup_data twl4030_setup = { + .ramp_delay_value = 2, /* 81 ms */ + .sysclk = 26000, +}; + +/* Audio subsystem */ +static struct snd_soc_device zoom2_snd_devdata = { + .card = &snd_soc_zoom2, + .codec_dev = &soc_codec_dev_twl4030, + .codec_data = &twl4030_setup, +}; + +static struct platform_device *zoom2_snd_device; + +static int __init zoom2_soc_init(void) +{ + int ret; + + if (!machine_is_omap_zoom2()) { + pr_debug("Not Zoom2!\n"); + return -ENODEV; + } + printk(KERN_INFO "Zoom2 SoC init\n"); + + zoom2_snd_device = platform_device_alloc("soc-audio", -1); + if (!zoom2_snd_device) { + printk(KERN_ERR "Platform device allocation failed\n"); + return -ENOMEM; + } + + platform_set_drvdata(zoom2_snd_device, &zoom2_snd_devdata); + zoom2_snd_devdata.dev = &zoom2_snd_device->dev; + *(unsigned int *)zoom2_dai[0].cpu_dai->private_data = 1; /* McBSP2 */ + *(unsigned int *)zoom2_dai[1].cpu_dai->private_data = 2; /* McBSP3 */ + + ret = platform_device_add(zoom2_snd_device); + if (ret) + goto err1; + + BUG_ON(gpio_request(ZOOM2_HEADSET_MUX_GPIO, "hs_mux") < 0); + gpio_direction_output(ZOOM2_HEADSET_MUX_GPIO, 0); + + return 0; + +err1: + printk(KERN_ERR "Unable to add platform device\n"); + platform_device_put(zoom2_snd_device); + + return ret; +} +module_init(zoom2_soc_init); + +static void __exit zoom2_soc_exit(void) +{ + gpio_free(ZOOM2_HEADSET_MUX_GPIO); + + platform_device_unregister(zoom2_snd_device); +} +module_exit(zoom2_soc_exit); + +MODULE_AUTHOR("Misael Lopez Cruz "); +MODULE_DESCRIPTION("ALSA SoC Zoom2"); +MODULE_LICENSE("GPL"); + -- cgit v1.2.3 From 5ba73683ba5223b218b26f878663e3907e85eedc Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 13 Jun 2009 11:14:55 +0100 Subject: ASoC: Revert duplicated code in SSM2602 driver The Blackfin submission was done as a patch against a different tree and contained a duplicate hunk which will cause us to loose track of the substream pointers when shutting down. Remove one of the duplicated hunks. Signed-off-by: Mark Brown --- sound/soc/codecs/ssm2602.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index 1fc4c8e0899..c550750c79c 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -375,10 +375,6 @@ static void ssm2602_shutdown(struct snd_pcm_substream *substream, struct snd_soc_codec *codec = socdev->card->codec; struct ssm2602_priv *ssm2602 = codec->private_data; - if (ssm2602->master_substream == substream) - ssm2602->master_substream = ssm2602->slave_substream; - - ssm2602->slave_substream = NULL; /* deactivate */ if (!codec->active) ssm2602_write(codec, SSM2602_ACTIVE, 0); -- cgit v1.2.3 From 9e79261f302083cbc6aa95e0f778e3583b1ab36e Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 12 Jun 2009 17:27:07 +0100 Subject: ASoC: Automatically control WM8903 sloping stopband filter For best performance the DAC sloping stopband filter should be enabled below 24kHz and not enabled above that so remove the user visible control for this and do it autonomously in the driver. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8903.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index d8a9222fbf7..3ebd770463e 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -715,8 +715,6 @@ SOC_ENUM("DAC Soft Mute Rate", soft_mute), SOC_ENUM("DAC Mute Mode", mute_mode), SOC_SINGLE("DAC Mono Switch", WM8903_DAC_DIGITAL_1, 12, 1, 0), SOC_ENUM("DAC De-emphasis", dac_deemphasis), -SOC_SINGLE("DAC Sloping Stopband Filter Switch", - WM8903_DAC_DIGITAL_1, 11, 1, 0), SOC_ENUM("DAC Companding Mode", dac_companding), SOC_SINGLE("DAC Companding Switch", WM8903_AUDIO_INTERFACE_0, 1, 1, 0), @@ -1377,12 +1375,19 @@ static int wm8903_hw_params(struct snd_pcm_substream *substream, u16 aif3 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_3); u16 clock0 = wm8903_read(codec, WM8903_CLOCK_RATES_0); u16 clock1 = wm8903_read(codec, WM8903_CLOCK_RATES_1); + u16 dac_digital1 = wm8903_read(codec, WM8903_DAC_DIGITAL_1); if (substream == wm8903->slave_substream) { dev_dbg(&i2c->dev, "Ignoring hw_params for slave substream\n"); return 0; } + /* Enable sloping stopband filter for low sample rates */ + if (fs <= 24000) + dac_digital1 |= WM8903_DAC_SB_FILT; + else + dac_digital1 &= ~WM8903_DAC_SB_FILT; + /* Configure sample rate logic for DSP - choose nearest rate */ dsp_config = 0; best_val = abs(sample_rates[dsp_config].rate - fs); @@ -1507,6 +1512,7 @@ static int wm8903_hw_params(struct snd_pcm_substream *substream, wm8903_write(codec, WM8903_AUDIO_INTERFACE_1, aif1); wm8903_write(codec, WM8903_AUDIO_INTERFACE_2, aif2); wm8903_write(codec, WM8903_AUDIO_INTERFACE_3, aif3); + wm8903_write(codec, WM8903_DAC_DIGITAL_1, dac_digital1); return 0; } -- cgit v1.2.3 From 21002e20767292d85701154cdf12a591b45f0979 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 12 Jun 2009 17:27:52 +0100 Subject: ASoC: Automatically manage WM8900 sloping stopband filter For best performance the DAC sloping stopband filter should be enabled below 24kHz and not enabled above that so remove the user visible control for this and do it autonomously in the driver. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8900.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c index 3c78945244b..de99206a010 100644 --- a/sound/soc/codecs/wm8900.c +++ b/sound/soc/codecs/wm8900.c @@ -116,6 +116,7 @@ #define WM8900_REG_CLOCKING2_DAC_CLKDIV 0x1c #define WM8900_REG_DACCTRL_MUTE 0x004 +#define WM8900_REG_DACCTRL_DAC_SB_FILT 0x100 #define WM8900_REG_DACCTRL_AIF_LRCLKRATE 0x400 #define WM8900_REG_AUDIO3_ADCLRC_DIR 0x0800 @@ -439,7 +440,6 @@ SOC_SINGLE("DAC Soft Mute Switch", WM8900_REG_DACCTRL, 6, 1, 1), SOC_ENUM("DAC Mute Rate", dac_mute_rate), SOC_SINGLE("DAC Mono Switch", WM8900_REG_DACCTRL, 9, 1, 0), SOC_ENUM("DAC Deemphasis", dac_deemphasis), -SOC_SINGLE("DAC Sloping Stopband Filter Switch", WM8900_REG_DACCTRL, 8, 1, 0), SOC_SINGLE("DAC Sigma-Delta Modulator Clock Switch", WM8900_REG_DACCTRL, 12, 1, 0), @@ -743,6 +743,17 @@ static int wm8900_hw_params(struct snd_pcm_substream *substream, wm8900_write(codec, WM8900_REG_AUDIO1, reg); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + reg = wm8900_read(codec, WM8900_REG_DACCTRL); + + if (params_rate(params) <= 24000) + reg |= WM8900_REG_DACCTRL_DAC_SB_FILT; + else + reg &= ~WM8900_REG_DACCTRL_DAC_SB_FILT; + + wm8900_write(codec, WM8900_REG_DACCTRL, reg); + } + return 0; } -- cgit v1.2.3 From 619439998ac32953d737fbe2dc82eb67024547d0 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 12 Jun 2009 22:56:59 +0100 Subject: ASoC: Automatically manage WM8350 sloping stopband filter For best performance the DAC sloping stopband filter should be enabled below 24kHz and not enabled above that so remove the user visible control for this and do it autonomously in the driver. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8350.c | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index e7348d341b7..f6bb59951f9 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -406,7 +406,6 @@ static const char *wm8350_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" }; static const char *wm8350_pol[] = { "Normal", "Inv R", "Inv L", "Inv L & R" }; static const char *wm8350_dacmutem[] = { "Normal", "Soft" }; static const char *wm8350_dacmutes[] = { "Fast", "Slow" }; -static const char *wm8350_dacfilter[] = { "Normal", "Sloping" }; static const char *wm8350_adcfilter[] = { "None", "High Pass" }; static const char *wm8350_adchp[] = { "44.1kHz", "8kHz", "16kHz", "32kHz" }; static const char *wm8350_lr[] = { "Left", "Right" }; @@ -416,7 +415,6 @@ static const struct soc_enum wm8350_enum[] = { SOC_ENUM_SINGLE(WM8350_DAC_CONTROL, 0, 4, wm8350_pol), SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 14, 2, wm8350_dacmutem), SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 13, 2, wm8350_dacmutes), - SOC_ENUM_SINGLE(WM8350_DAC_MUTE_VOLUME, 12, 2, wm8350_dacfilter), SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 15, 2, wm8350_adcfilter), SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 8, 4, wm8350_adchp), SOC_ENUM_SINGLE(WM8350_ADC_CONTROL, 0, 4, wm8350_pol), @@ -444,10 +442,9 @@ static const struct snd_kcontrol_new wm8350_snd_controls[] = { 0, 255, 0, dac_pcm_tlv), SOC_ENUM("Playback PCM Mute Function", wm8350_enum[2]), SOC_ENUM("Playback PCM Mute Speed", wm8350_enum[3]), - SOC_ENUM("Playback PCM Filter", wm8350_enum[4]), - SOC_ENUM("Capture PCM Filter", wm8350_enum[5]), - SOC_ENUM("Capture PCM HP Filter", wm8350_enum[6]), - SOC_ENUM("Capture ADC Inversion", wm8350_enum[7]), + SOC_ENUM("Capture PCM Filter", wm8350_enum[4]), + SOC_ENUM("Capture PCM HP Filter", wm8350_enum[5]), + SOC_ENUM("Capture ADC Inversion", wm8350_enum[6]), SOC_WM8350_DOUBLE_R_TLV("Capture PCM Volume", WM8350_ADC_DIGITAL_VOLUME_L, WM8350_ADC_DIGITAL_VOLUME_R, @@ -993,6 +990,7 @@ static int wm8350_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_dai *codec_dai) { struct snd_soc_codec *codec = codec_dai->codec; + struct wm8350 *wm8350 = codec->control_data; u16 iface = wm8350_codec_read(codec, WM8350_AI_FORMATING) & ~WM8350_AIF_WL_MASK; @@ -1012,6 +1010,19 @@ static int wm8350_pcm_hw_params(struct snd_pcm_substream *substream, } wm8350_codec_write(codec, WM8350_AI_FORMATING, iface); + + /* The sloping stopband filter is recommended for use with + * lower sample rates to improve performance. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (params_rate(params) < 24000) + wm8350_set_bits(wm8350, WM8350_DAC_MUTE_VOLUME, + WM8350_DAC_SB_FILT); + else + wm8350_clear_bits(wm8350, WM8350_DAC_MUTE_VOLUME, + WM8350_DAC_SB_FILT); + } + return 0; } -- cgit v1.2.3 From 831dc0f10f7b2a4856094ff160c018bf19f77527 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 13 Jun 2009 19:55:02 +0100 Subject: ASoC: Add stub suspend and resume calls for ASoC subdevices Now that ASoC subdevices can be regular devices they can have normal suspend and resume calls from their buses. However, suspending them individually is not desirable since this can lead to problems such as pops and clicks from devices being suspended with their signals being amplified or clocks being stopped suddenly. This will be resolved by having the normal device model suspend and resume calls call into ASoC which will suspend the entire card while any of its components are suspended. At present this is not yet implemented but in order to aid the transition of drivers to the standard device model this patch adds API calls for the notifications. Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index e1a920cd895..44141178ff4 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -788,6 +788,45 @@ static int soc_resume(struct platform_device *pdev) return 0; } +/** + * snd_soc_suspend_device: Notify core of device suspend + * + * @dev: Device being suspended. + * + * In order to ensure that the entire audio subsystem is suspended in a + * coordinated fashion ASoC devices should suspend themselves when + * called by ASoC. When the standard kernel suspend process asks the + * device to suspend it should call this function to initiate a suspend + * of the entire ASoC card. + * + * \note Currently this function is stubbed out. + */ +int snd_soc_suspend_device(struct device *dev) +{ + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_suspend_device); + +/** + * snd_soc_resume_device: Notify core of device resume + * + * @dev: Device being resumed. + * + * In order to ensure that the entire audio subsystem is resumed in a + * coordinated fashion ASoC devices should resume themselves when called + * by ASoC. When the standard kernel resume process asks the device + * to resume it should call this function. Once all the components of + * the card have notified that they are ready to be resumed the card + * will be resumed. + * + * \note Currently this function is stubbed out. + */ +int snd_soc_resume_device(struct device *dev) +{ + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_resume_device); + #else #define soc_suspend NULL #define soc_resume NULL -- cgit v1.2.3 From b3b50b3f31775be5d2e441618bbc1c5cbee4d9f1 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 13 Jun 2009 22:30:18 +0100 Subject: ASoC: Add suspend and resume callbacks to Wolfson CODEC drivers Signed-off-by: Mark Brown --- sound/soc/codecs/wm8350.c | 17 +++++++++++++++++ sound/soc/codecs/wm8400.c | 17 +++++++++++++++++ sound/soc/codecs/wm8580.c | 17 +++++++++++++++++ sound/soc/codecs/wm8731.c | 39 +++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm8753.c | 35 +++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm8900.c | 17 +++++++++++++++++ sound/soc/codecs/wm8903.c | 17 +++++++++++++++++ sound/soc/codecs/wm8940.c | 17 +++++++++++++++++ sound/soc/codecs/wm8960.c | 17 +++++++++++++++++ sound/soc/codecs/wm8988.c | 34 ++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm9081.c | 17 +++++++++++++++++ 11 files changed, 244 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index f6bb59951f9..4ded0e3a35e 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -1671,6 +1671,21 @@ static int __devexit wm8350_codec_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_PM +static int wm8350_codec_suspend(struct platform_device *pdev, pm_message_t m) +{ + return snd_soc_suspend_device(&pdev->dev); +} + +static int wm8350_codec_resume(struct platform_device *pdev) +{ + return snd_soc_resume_device(&pdev->dev); +} +#else +#define wm8350_codec_suspend NULL +#define wm8350_codec_resume NULL +#endif + static struct platform_driver wm8350_codec_driver = { .driver = { .name = "wm8350-codec", @@ -1678,6 +1693,8 @@ static struct platform_driver wm8350_codec_driver = { }, .probe = wm8350_codec_probe, .remove = __devexit_p(wm8350_codec_remove), + .suspend = wm8350_codec_suspend, + .resume = wm8350_codec_resume, }; static __init int wm8350_init(void) diff --git a/sound/soc/codecs/wm8400.c b/sound/soc/codecs/wm8400.c index 502eefac1ec..0bf903f2756 100644 --- a/sound/soc/codecs/wm8400.c +++ b/sound/soc/codecs/wm8400.c @@ -1553,6 +1553,21 @@ static int __exit wm8400_codec_remove(struct platform_device *dev) return 0; } +#ifdef CONFIG_PM +static int wm8400_pdev_suspend(struct platform_device *pdev, pm_message_t msg) +{ + return snd_soc_suspend_device(&pdev->dev); +} + +static int wm8400_pdev_resume(struct platform_device *pdev) +{ + return snd_soc_resume_device(&pdev->dev); +} +#else +#define wm8400_pdev_suspend NULL +#define wm8400_pdev_resume NULL +#endif + static struct platform_driver wm8400_codec_driver = { .driver = { .name = "wm8400-codec", @@ -1560,6 +1575,8 @@ static struct platform_driver wm8400_codec_driver = { }, .probe = wm8400_codec_probe, .remove = __exit_p(wm8400_codec_remove), + .suspend = wm8400_pdev_suspend, + .resume = wm8400_pdev_resume, }; static int __init wm8400_codec_init(void) diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index 86c4b24db81..261ef101d4f 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -995,6 +995,21 @@ static int wm8580_i2c_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int wm8580_i2c_suspend(struct i2c_client *client, pm_message_t msg) +{ + return snd_soc_suspend_device(&client->dev); +} + +static int wm8580_i2c_resume(struct i2c_client *client) +{ + return snd_soc_resume_device(&client->dev); +} +#else +#define wm8580_i2c_suspend NULL +#define wm8580_i2c_resume NULL +#endif + static const struct i2c_device_id wm8580_i2c_id[] = { { "wm8580", 0 }, { } @@ -1008,6 +1023,8 @@ static struct i2c_driver wm8580_i2c_driver = { }, .probe = wm8580_i2c_probe, .remove = wm8580_i2c_remove, + .suspend = wm8580_i2c_suspend, + .resume = wm8580_i2c_resume, .id_table = wm8580_i2c_id, }; #endif diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index 7a205876ef4..d7f4788f7ac 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -460,6 +460,7 @@ struct snd_soc_dai wm8731_dai = { }; EXPORT_SYMBOL_GPL(wm8731_dai); +#ifdef CONFIG_PM static int wm8731_suspend(struct platform_device *pdev, pm_message_t state) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); @@ -488,6 +489,10 @@ static int wm8731_resume(struct platform_device *pdev) wm8731_set_bias_level(codec, codec->suspend_bias_level); return 0; } +#else +#define wm8731_suspend NULL +#define wm8731_resume NULL +#endif static int wm8731_probe(struct platform_device *pdev) { @@ -680,6 +685,21 @@ static int __devexit wm8731_spi_remove(struct spi_device *spi) return 0; } +#ifdef CONFIG_PM +static int wm8731_spi_suspend(struct spi_device *spi, pm_message_t msg) +{ + return snd_soc_suspend_device(&spi->dev); +} + +static int wm8731_spi_resume(struct spi_device *spi) +{ + return snd_soc_resume_device(&spi->dev); +} +#else +#define wm8731_spi_suspend NULL +#define wm8731_spi_resume NULL +#endif + static struct spi_driver wm8731_spi_driver = { .driver = { .name = "wm8731", @@ -687,6 +707,8 @@ static struct spi_driver wm8731_spi_driver = { .owner = THIS_MODULE, }, .probe = wm8731_spi_probe, + .suspend = wm8731_spi_suspend, + .resume = wm8731_spi_resume, .remove = __devexit_p(wm8731_spi_remove), }; #endif /* CONFIG_SPI_MASTER */ @@ -720,6 +742,21 @@ static __devexit int wm8731_i2c_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int wm8731_i2c_suspend(struct i2c_client *i2c, pm_message_t msg) +{ + return snd_soc_suspend_device(&i2c->dev); +} + +static int wm8731_i2c_resume(struct i2c_client *i2c) +{ + return snd_soc_resume_device(&i2c->dev); +} +#else +#define wm8731_i2c_suspend NULL +#define wm8731_i2c_resume NULL +#endif + static const struct i2c_device_id wm8731_i2c_id[] = { { "wm8731", 0 }, { } @@ -733,6 +770,8 @@ static struct i2c_driver wm8731_i2c_driver = { }, .probe = wm8731_i2c_probe, .remove = __devexit_p(wm8731_i2c_remove), + .suspend = wm8731_i2c_suspend, + .resume = wm8731_i2c_resume, .id_table = wm8731_i2c_id, }; #endif diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index d28eeaceb85..370f7df0362 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -1766,6 +1766,21 @@ static int wm8753_i2c_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int wm8753_i2c_suspend(struct i2c_client *client, pm_message_t msg) +{ + return snd_soc_suspend_device(&client->dev); +} + +static int wm8753_i2c_resume(struct i2c_client *client) +{ + return snd_soc_resume_device(&client->dev); +} +#else +#define wm8753_i2c_suspend NULL +#define wm8753_i2c_resume NULL +#endif + static const struct i2c_device_id wm8753_i2c_id[] = { { "wm8753", 0 }, { } @@ -1779,6 +1794,8 @@ static struct i2c_driver wm8753_i2c_driver = { }, .probe = wm8753_i2c_probe, .remove = wm8753_i2c_remove, + .suspend = wm8753_i2c_suspend, + .resume = wm8753_i2c_resume, .id_table = wm8753_i2c_id, }; #endif @@ -1834,6 +1851,22 @@ static int __devexit wm8753_spi_remove(struct spi_device *spi) return 0; } +#ifdef CONFIG_PM +static int wm8753_spi_suspend(struct spi_device *spi, pm_message_t msg) +{ + return snd_soc_suspend_device(&spi->dev); +} + +static int wm8753_spi_resume(struct spi_device *spi) +{ + return snd_soc_resume_device(&spi->dev); +} + +#else +#define wm8753_spi_suspend NULL +#define wm8753_spi_resume NULL +#endif + static struct spi_driver wm8753_spi_driver = { .driver = { .name = "wm8753", @@ -1842,6 +1875,8 @@ static struct spi_driver wm8753_spi_driver = { }, .probe = wm8753_spi_probe, .remove = __devexit_p(wm8753_spi_remove), + .suspend = wm8753_spi_suspend, + .resume = wm8753_spi_resume, }; #endif diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c index de99206a010..ac308993ac5 100644 --- a/sound/soc/codecs/wm8900.c +++ b/sound/soc/codecs/wm8900.c @@ -1399,6 +1399,21 @@ static __devexit int wm8900_i2c_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int wm8900_i2c_suspend(struct i2c_client *client, pm_message_t msg) +{ + return snd_soc_suspend_device(&client->dev); +} + +static int wm8900_i2c_resume(struct i2c_client *client) +{ + return snd_soc_resume_device(&client->dev); +} +#else +#define wm8900_i2c_suspend NULL +#define wm8900_i2c_resume NULL +#endif + static const struct i2c_device_id wm8900_i2c_id[] = { { "wm8900", 0 }, { } @@ -1412,6 +1427,8 @@ static struct i2c_driver wm8900_i2c_driver = { }, .probe = wm8900_i2c_probe, .remove = __devexit_p(wm8900_i2c_remove), + .suspend = wm8900_i2c_suspend, + .resume = wm8900_i2c_resume, .id_table = wm8900_i2c_id, }; diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 3ebd770463e..6239af8fdf7 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -1731,6 +1731,21 @@ static __devexit int wm8903_i2c_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int wm8903_i2c_suspend(struct i2c_client *client, pm_message_t msg) +{ + return snd_soc_suspend_device(&client->dev); +} + +static int wm8903_i2c_resume(struct i2c_client *client) +{ + return snd_soc_resume_device(&client->dev); +} +#else +#define wm8903_i2c_suspend NULL +#define wm8903_i2c_resume NULL +#endif + /* i2c codec control layer */ static const struct i2c_device_id wm8903_i2c_id[] = { { "wm8903", 0 }, @@ -1745,6 +1760,8 @@ static struct i2c_driver wm8903_i2c_driver = { }, .probe = wm8903_i2c_probe, .remove = __devexit_p(wm8903_i2c_remove), + .suspend = wm8903_i2c_suspend, + .resume = wm8903_i2c_resume, .id_table = wm8903_i2c_id, }; diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c index b8e17d6bc1f..b69210a7742 100644 --- a/sound/soc/codecs/wm8940.c +++ b/sound/soc/codecs/wm8940.c @@ -916,6 +916,21 @@ static int __devexit wm8940_i2c_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int wm8940_i2c_suspend(struct i2c_client *client, pm_message_t msg) +{ + return snd_soc_suspend_device(&client->dev); +} + +static int wm8940_i2c_resume(struct i2c_client *client) +{ + return snd_soc_resume_device(&client->dev); +} +#else +#define wm8940_i2c_suspend NULL +#define wm8940_i2c_resume NULL +#endif + static const struct i2c_device_id wm8940_i2c_id[] = { { "wm8940", 0 }, { } @@ -929,6 +944,8 @@ static struct i2c_driver wm8940_i2c_driver = { }, .probe = wm8940_i2c_probe, .remove = __devexit_p(wm8940_i2c_remove), + .suspend = wm8940_i2c_suspend, + .resume = wm8940_i2c_resume, .id_table = wm8940_i2c_id, }; diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index e224d8add17..b7894d6dffc 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -927,6 +927,21 @@ static __devexit int wm8960_i2c_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int wm8960_i2c_suspend(struct i2c_client *client, pm_message_t msg) +{ + return snd_soc_suspend_device(&client->dev); +} + +static int wm8960_i2c_resume(struct i2c_client *client) +{ + return snd_soc_resume_device(&client->dev); +} +#else +#define wm8960_i2c_suspend NULL +#define wm8960_i2c_resume NULL +#endif + static const struct i2c_device_id wm8960_i2c_id[] = { { "wm8960", 0 }, { } @@ -940,6 +955,8 @@ static struct i2c_driver wm8960_i2c_driver = { }, .probe = wm8960_i2c_probe, .remove = __devexit_p(wm8960_i2c_remove), + .suspend = wm8960_i2c_suspend, + .resume = wm8960_i2c_resume, .id_table = wm8960_i2c_id, }; diff --git a/sound/soc/codecs/wm8988.c b/sound/soc/codecs/wm8988.c index c05f71803aa..03fac6a0f80 100644 --- a/sound/soc/codecs/wm8988.c +++ b/sound/soc/codecs/wm8988.c @@ -981,6 +981,21 @@ static int wm8988_i2c_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int wm8988_i2c_suspend(struct i2c_client *client, pm_message_t msg) +{ + return snd_soc_suspend_device(&client->dev); +} + +static int wm8988_i2c_resume(struct i2c_client *client) +{ + return snd_soc_resume_device(&client->dev); +} +#else +#define wm8988_i2c_suspend NULL +#define wm8988_i2c_resume NULL +#endif + static const struct i2c_device_id wm8988_i2c_id[] = { { "wm8988", 0 }, { } @@ -994,6 +1009,8 @@ static struct i2c_driver wm8988_i2c_driver = { }, .probe = wm8988_i2c_probe, .remove = wm8988_i2c_remove, + .suspend = wm8988_i2c_suspend, + .resume = wm8988_i2c_resume, .id_table = wm8988_i2c_id, }; #endif @@ -1051,6 +1068,21 @@ static int __devexit wm8988_spi_remove(struct spi_device *spi) return 0; } +#ifdef CONFIG_PM +static int wm8988_spi_suspend(struct spi_device *spi, pm_message_t msg) +{ + return snd_soc_suspend_device(&spi->dev); +} + +static int wm8988_spi_resume(struct spi_device *spi) +{ + return snd_soc_resume_device(&spi->dev); +} +#else +#define wm8988_spi_suspend NULL +#define wm8988_spi_resume NULL +#endif + static struct spi_driver wm8988_spi_driver = { .driver = { .name = "wm8988", @@ -1059,6 +1091,8 @@ static struct spi_driver wm8988_spi_driver = { }, .probe = wm8988_spi_probe, .remove = __devexit_p(wm8988_spi_remove), + .suspend = wm8988_spi_suspend, + .resume = wm8988_spi_resume, }; #endif diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c index 86fc57e25f9..dbe20597d87 100644 --- a/sound/soc/codecs/wm9081.c +++ b/sound/soc/codecs/wm9081.c @@ -1492,6 +1492,21 @@ static __devexit int wm9081_i2c_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int wm9081_i2c_suspend(struct i2c_client *client, pm_message_t msg) +{ + return snd_soc_suspend_device(&client->dev); +} + +static int wm9081_i2c_resume(struct i2c_client *client) +{ + return snd_soc_resume_device(&client->dev); +} +#else +#define wm9081_i2c_suspend NULL +#define wm9081_i2c_resume NULL +#endif + static const struct i2c_device_id wm9081_i2c_id[] = { { "wm9081", 0 }, { } @@ -1505,6 +1520,8 @@ static struct i2c_driver wm9081_i2c_driver = { }, .probe = wm9081_i2c_probe, .remove = __devexit_p(wm9081_i2c_remove), + .suspend = wm9081_i2c_suspend, + .resume = wm9081_i2c_resume, .id_table = wm9081_i2c_id, }; -- cgit v1.2.3 From 6d5701b29e392f77d222254aa8e375315d59f0aa Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 14 Jun 2009 11:33:37 +0100 Subject: ASoC: Instantiate any forgotten DAPM widgets With the recent changes to the DAPM power checks it has become important to explicitly instantiate all widgets but some drivers were forgetting to do that. Since everything needs to do it add a call to instantiate them immediately before the card registration - it does no harm when it is called repeatedly and saves work in drivers. Tested-by: pHilipp Zabel Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 3f44150d8e3..1d70829464e 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1389,6 +1389,9 @@ int snd_soc_init_card(struct snd_soc_device *socdev) snprintf(codec->card->longname, sizeof(codec->card->longname), "%s (%s)", card->name, codec->name); + /* Make sure all DAPM widgets are instantiated */ + snd_soc_dapm_new_widgets(codec); + ret = snd_card_register(codec->card); if (ret < 0) { printk(KERN_ERR "asoc: failed to register soundcard for %s\n", -- cgit v1.2.3 From 33745fb3448f90d2b9e414ccc6689e9ee70cfae4 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Sun, 14 Jun 2009 12:51:30 +0200 Subject: ASoC: magician: fix PXA SSP clock polarity Follow-up fix needed since "ASoC: pxa-ssp.c fix clock/frame invert". Signed-off-by: Philipp Zabel Signed-off-by: Mark Brown --- sound/soc/pxa/magician.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/pxa/magician.c b/sound/soc/pxa/magician.c index c89a3cdf31e..326955dea36 100644 --- a/sound/soc/pxa/magician.c +++ b/sound/soc/pxa/magician.c @@ -184,7 +184,7 @@ static int magician_playback_hw_params(struct snd_pcm_substream *substream, /* set cpu DAI configuration */ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A | - SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBS_CFS); + SND_SOC_DAIFMT_NB_IF | SND_SOC_DAIFMT_CBS_CFS); if (ret < 0) return ret; -- cgit v1.2.3 From 1abd91849990ed61d6468ffa8b7fc1ae61db4b1a Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Mon, 15 Jun 2009 22:18:23 +0200 Subject: ASoC: UDA1380: refactor device registration This patch mostly follows commit 5998102b9095fdb7c67755812038612afea315c5 "ASoC: Refactor WM8731 device registration" to make UDA1380 use standard device instantiation. Similarly, the I2C device registration temporarily moves into the magician machine driver before it will find its final resting place in the board file. At the same time, platform specific configuration is moved to platform data and common power/reset GPIO handling moves into the codec driver. Signed-off-by: Philipp Zabel Signed-off-by: Mark Brown --- sound/soc/codecs/uda1380.c | 313 +++++++++++++++++++++++++-------------------- sound/soc/codecs/uda1380.h | 8 -- sound/soc/pxa/magician.c | 54 ++++---- 3 files changed, 199 insertions(+), 176 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/uda1380.c b/sound/soc/codecs/uda1380.c index 5b21594e0e5..92ec0344215 100644 --- a/sound/soc/codecs/uda1380.c +++ b/sound/soc/codecs/uda1380.c @@ -5,9 +5,7 @@ * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * - * Copyright (c) 2007 Philipp Zabel - * Improved support for DAPM and audio routing/mixing capabilities, - * added TLV support. + * Copyright (c) 2007-2009 Philipp Zabel * * Modified by Richard Purdie to fit into SoC * codec model. @@ -19,26 +17,32 @@ #include #include #include -#include #include #include -#include +#include #include #include #include #include #include #include -#include #include #include #include +#include #include "uda1380.h" -static struct work_struct uda1380_work; static struct snd_soc_codec *uda1380_codec; +/* codec private data */ +struct uda1380_priv { + struct snd_soc_codec codec; + u16 reg_cache[UDA1380_CACHEREGNUM]; + unsigned int dac_clk; + struct work_struct work; +}; + /* * uda1380 register cache */ @@ -473,6 +477,7 @@ static int uda1380_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; + struct uda1380_priv *uda1380 = codec->private_data; int mixer = uda1380_read_reg_cache(codec, UDA1380_MIXER); switch (cmd) { @@ -480,13 +485,13 @@ static int uda1380_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: uda1380_write_reg_cache(codec, UDA1380_MIXER, mixer & ~R14_SILENCE); - schedule_work(&uda1380_work); + schedule_work(&uda1380->work); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: uda1380_write_reg_cache(codec, UDA1380_MIXER, mixer | R14_SILENCE); - schedule_work(&uda1380_work); + schedule_work(&uda1380->work); break; } return 0; @@ -670,44 +675,33 @@ static int uda1380_resume(struct platform_device *pdev) return 0; } -/* - * initialise the UDA1380 driver - * register mixer and dsp interfaces with the kernel - */ -static int uda1380_init(struct snd_soc_device *socdev, int dac_clk) +static int uda1380_probe(struct platform_device *pdev) { - struct snd_soc_codec *codec = socdev->card->codec; + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + struct uda1380_platform_data *pdata; int ret = 0; - codec->name = "UDA1380"; - codec->owner = THIS_MODULE; - codec->read = uda1380_read_reg_cache; - codec->write = uda1380_write; - codec->set_bias_level = uda1380_set_bias_level; - codec->dai = uda1380_dai; - codec->num_dai = ARRAY_SIZE(uda1380_dai); - codec->reg_cache = kmemdup(uda1380_reg, sizeof(uda1380_reg), - GFP_KERNEL); - if (codec->reg_cache == NULL) - return -ENOMEM; - codec->reg_cache_size = ARRAY_SIZE(uda1380_reg); - codec->reg_cache_step = 1; - uda1380_reset(codec); + if (uda1380_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } - uda1380_codec = codec; - INIT_WORK(&uda1380_work, uda1380_flush_work); + socdev->card->codec = uda1380_codec; + codec = uda1380_codec; + pdata = codec->dev->platform_data; /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { - pr_err("uda1380: failed to create pcms\n"); + dev_err(codec->dev, "failed to create pcms: %d\n", ret); goto pcm_err; } /* power on device */ uda1380_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* set clock input */ - switch (dac_clk) { + switch (pdata->dac_clk) { case UDA1380_DAC_CLK_SYSCLK: uda1380_write(codec, UDA1380_CLK, 0); break; @@ -716,13 +710,12 @@ static int uda1380_init(struct snd_soc_device *socdev, int dac_clk) break; } - /* uda1380 init */ snd_soc_add_controls(codec, uda1380_snd_controls, ARRAY_SIZE(uda1380_snd_controls)); uda1380_add_widgets(codec); ret = snd_soc_init_card(socdev); if (ret < 0) { - pr_err("uda1380: failed to register card\n"); + dev_err(codec->dev, "failed to register card: %d\n", ret); goto card_err; } @@ -732,165 +725,201 @@ card_err: snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); pcm_err: - kfree(codec->reg_cache); return ret; } -static struct snd_soc_device *uda1380_socdev; - -#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) - -static int uda1380_i2c_probe(struct i2c_client *i2c, - const struct i2c_device_id *id) +/* power down chip */ +static int uda1380_remove(struct platform_device *pdev) { - struct snd_soc_device *socdev = uda1380_socdev; - struct uda1380_setup_data *setup = socdev->codec_data; + struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; - int ret; - - i2c_set_clientdata(i2c, codec); - codec->control_data = i2c; - ret = uda1380_init(socdev, setup->dac_clk); - if (ret < 0) - pr_err("uda1380: failed to initialise UDA1380\n"); + if (codec->control_data) + uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF); - return ret; -} + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); -static int uda1380_i2c_remove(struct i2c_client *client) -{ - struct snd_soc_codec *codec = i2c_get_clientdata(client); - kfree(codec->reg_cache); return 0; } -static const struct i2c_device_id uda1380_i2c_id[] = { - { "uda1380", 0 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, uda1380_i2c_id); - -static struct i2c_driver uda1380_i2c_driver = { - .driver = { - .name = "UDA1380 I2C Codec", - .owner = THIS_MODULE, - }, - .probe = uda1380_i2c_probe, - .remove = uda1380_i2c_remove, - .id_table = uda1380_i2c_id, +struct snd_soc_codec_device soc_codec_dev_uda1380 = { + .probe = uda1380_probe, + .remove = uda1380_remove, + .suspend = uda1380_suspend, + .resume = uda1380_resume, }; +EXPORT_SYMBOL_GPL(soc_codec_dev_uda1380); -static int uda1380_add_i2c_device(struct platform_device *pdev, - const struct uda1380_setup_data *setup) +static int uda1380_register(struct uda1380_priv *uda1380) { - struct i2c_board_info info; - struct i2c_adapter *adapter; - struct i2c_client *client; - int ret; + int ret, i; + struct snd_soc_codec *codec = &uda1380->codec; + struct uda1380_platform_data *pdata = codec->dev->platform_data; - ret = i2c_add_driver(&uda1380_i2c_driver); - if (ret != 0) { - dev_err(&pdev->dev, "can't add i2c driver\n"); - return ret; + if (uda1380_codec) { + dev_err(codec->dev, "Another UDA1380 is registered\n"); + return -EINVAL; + } + + if (!pdata || !pdata->gpio_power || !pdata->gpio_reset) + return -EINVAL; + + ret = gpio_request(pdata->gpio_power, "uda1380 power"); + if (ret) + goto err_out; + ret = gpio_request(pdata->gpio_reset, "uda1380 reset"); + if (ret) + goto err_gpio; + + gpio_direction_output(pdata->gpio_power, 1); + + /* we may need to have the clock running here - pH5 */ + gpio_direction_output(pdata->gpio_reset, 1); + udelay(5); + gpio_set_value(pdata->gpio_reset, 0); + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = uda1380; + codec->name = "UDA1380"; + codec->owner = THIS_MODULE; + codec->read = uda1380_read_reg_cache; + codec->write = uda1380_write; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = uda1380_set_bias_level; + codec->dai = uda1380_dai; + codec->num_dai = ARRAY_SIZE(uda1380_dai); + codec->reg_cache_size = ARRAY_SIZE(uda1380_reg); + codec->reg_cache = &uda1380->reg_cache; + codec->reg_cache_step = 1; + + memcpy(codec->reg_cache, uda1380_reg, sizeof(uda1380_reg)); + + ret = uda1380_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset\n"); + goto err_reset; } - memset(&info, 0, sizeof(struct i2c_board_info)); - info.addr = setup->i2c_address; - strlcpy(info.type, "uda1380", I2C_NAME_SIZE); + INIT_WORK(&uda1380->work, uda1380_flush_work); + + for (i = 0; i < ARRAY_SIZE(uda1380_dai); i++) + uda1380_dai[i].dev = codec->dev; - adapter = i2c_get_adapter(setup->i2c_bus); - if (!adapter) { - dev_err(&pdev->dev, "can't get i2c adapter %d\n", - setup->i2c_bus); - goto err_driver; + uda1380_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + goto err_reset; } - client = i2c_new_device(adapter, &info); - i2c_put_adapter(adapter); - if (!client) { - dev_err(&pdev->dev, "can't add i2c device at 0x%x\n", - (unsigned int)info.addr); - goto err_driver; + ret = snd_soc_register_dais(uda1380_dai, ARRAY_SIZE(uda1380_dai)); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAIs: %d\n", ret); + goto err_dai; } return 0; -err_driver: - i2c_del_driver(&uda1380_i2c_driver); - return -ENODEV; +err_dai: + snd_soc_unregister_codec(codec); +err_reset: + gpio_set_value(pdata->gpio_power, 0); + gpio_free(pdata->gpio_reset); +err_gpio: + gpio_free(pdata->gpio_power); +err_out: + return ret; } -#endif -static int uda1380_probe(struct platform_device *pdev) +static void uda1380_unregister(struct uda1380_priv *uda1380) { - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct uda1380_setup_data *setup; + struct snd_soc_codec *codec = &uda1380->codec; + struct uda1380_platform_data *pdata = codec->dev->platform_data; + + snd_soc_unregister_dais(uda1380_dai, ARRAY_SIZE(uda1380_dai)); + snd_soc_unregister_codec(&uda1380->codec); + + gpio_set_value(pdata->gpio_power, 0); + gpio_free(pdata->gpio_reset); + gpio_free(pdata->gpio_power); + + kfree(uda1380); + uda1380_codec = NULL; +} + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static __devinit int uda1380_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct uda1380_priv *uda1380; struct snd_soc_codec *codec; int ret; - setup = socdev->codec_data; - codec = kzalloc(sizeof(struct snd_soc_codec), GFP_KERNEL); - if (codec == NULL) + uda1380 = kzalloc(sizeof(struct uda1380_priv), GFP_KERNEL); + if (uda1380 == NULL) return -ENOMEM; - socdev->card->codec = codec; - mutex_init(&codec->mutex); - INIT_LIST_HEAD(&codec->dapm_widgets); - INIT_LIST_HEAD(&codec->dapm_paths); + codec = &uda1380->codec; + codec->hw_write = (hw_write_t)i2c_master_send; - uda1380_socdev = socdev; - ret = -ENODEV; + i2c_set_clientdata(i2c, uda1380); + codec->control_data = i2c; -#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) - if (setup->i2c_address) { - codec->hw_write = (hw_write_t)i2c_master_send; - ret = uda1380_add_i2c_device(pdev, setup); - } -#endif + codec->dev = &i2c->dev; + ret = uda1380_register(uda1380); if (ret != 0) - kfree(codec); + kfree(uda1380); + return ret; } -/* power down chip */ -static int uda1380_remove(struct platform_device *pdev) +static int __devexit uda1380_i2c_remove(struct i2c_client *i2c) { - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec = socdev->card->codec; - - if (codec->control_data) - uda1380_set_bias_level(codec, SND_SOC_BIAS_OFF); - - snd_soc_free_pcms(socdev); - snd_soc_dapm_free(socdev); -#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) - i2c_unregister_device(codec->control_data); - i2c_del_driver(&uda1380_i2c_driver); -#endif - kfree(codec); - + struct uda1380_priv *uda1380 = i2c_get_clientdata(i2c); + uda1380_unregister(uda1380); return 0; } -struct snd_soc_codec_device soc_codec_dev_uda1380 = { - .probe = uda1380_probe, - .remove = uda1380_remove, - .suspend = uda1380_suspend, - .resume = uda1380_resume, +static const struct i2c_device_id uda1380_i2c_id[] = { + { "uda1380", 0 }, + { } }; -EXPORT_SYMBOL_GPL(soc_codec_dev_uda1380); +MODULE_DEVICE_TABLE(i2c, uda1380_i2c_id); + +static struct i2c_driver uda1380_i2c_driver = { + .driver = { + .name = "UDA1380 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = uda1380_i2c_probe, + .remove = __devexit_p(uda1380_i2c_remove), + .id_table = uda1380_i2c_id, +}; +#endif static int __init uda1380_modinit(void) { - return snd_soc_register_dais(uda1380_dai, ARRAY_SIZE(uda1380_dai)); + int ret; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&uda1380_i2c_driver); + if (ret != 0) + pr_err("Failed to register UDA1380 I2C driver: %d\n", ret); +#endif + return 0; } module_init(uda1380_modinit); static void __exit uda1380_exit(void) { - snd_soc_unregister_dais(uda1380_dai, ARRAY_SIZE(uda1380_dai)); +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&uda1380_i2c_driver); +#endif } module_exit(uda1380_exit); diff --git a/sound/soc/codecs/uda1380.h b/sound/soc/codecs/uda1380.h index c55c17a52a1..9cefa8a5477 100644 --- a/sound/soc/codecs/uda1380.h +++ b/sound/soc/codecs/uda1380.h @@ -72,14 +72,6 @@ #define R22_SKIP_DCFIL 0x0002 #define R23_AGC_EN 0x0001 -struct uda1380_setup_data { - int i2c_bus; - unsigned short i2c_address; - int dac_clk; -#define UDA1380_DAC_CLK_SYSCLK 0 -#define UDA1380_DAC_CLK_WSPLL 1 -}; - #define UDA1380_DAI_DUPLEX 0 /* playback and capture on single DAI */ #define UDA1380_DAI_PLAYBACK 1 /* playback DAI */ #define UDA1380_DAI_CAPTURE 2 /* capture DAI */ diff --git a/sound/soc/pxa/magician.c b/sound/soc/pxa/magician.c index c89a3cdf31e..9fe4ad20615 100644 --- a/sound/soc/pxa/magician.c +++ b/sound/soc/pxa/magician.c @@ -20,12 +20,14 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include @@ -447,34 +449,47 @@ static struct snd_soc_card snd_soc_card_magician = { .platform = &pxa2xx_soc_platform, }; -/* magician audio private data */ -static struct uda1380_setup_data magician_uda1380_setup = { - .i2c_address = 0x18, - .dac_clk = UDA1380_DAC_CLK_WSPLL, -}; - /* magician audio subsystem */ static struct snd_soc_device magician_snd_devdata = { .card = &snd_soc_card_magician, .codec_dev = &soc_codec_dev_uda1380, - .codec_data = &magician_uda1380_setup, }; static struct platform_device *magician_snd_device; +/* + * FIXME: move into magician board file once merged into the pxa tree + */ +static struct uda1380_platform_data uda1380_info = { + .gpio_power = EGPIO_MAGICIAN_CODEC_POWER, + .gpio_reset = EGPIO_MAGICIAN_CODEC_RESET, + .dac_clk = UDA1380_DAC_CLK_WSPLL, +}; + +static struct i2c_board_info i2c_board_info[] = { + { + I2C_BOARD_INFO("uda1380", 0x18), + .platform_data = &uda1380_info, + }, +}; + static int __init magician_init(void) { int ret; + struct i2c_adapter *adapter; + struct i2c_client *client; if (!machine_is_magician()) return -ENODEV; - ret = gpio_request(EGPIO_MAGICIAN_CODEC_POWER, "CODEC_POWER"); - if (ret) - goto err_request_power; - ret = gpio_request(EGPIO_MAGICIAN_CODEC_RESET, "CODEC_RESET"); - if (ret) - goto err_request_reset; + adapter = i2c_get_adapter(0); + if (!adapter) + return -ENODEV; + client = i2c_new_device(adapter, i2c_board_info); + i2c_put_adapter(adapter); + if (!client) + return -ENODEV; + ret = gpio_request(EGPIO_MAGICIAN_SPK_POWER, "SPK_POWER"); if (ret) goto err_request_spk; @@ -491,14 +506,8 @@ static int __init magician_init(void) if (ret) goto err_request_in_sel1; - gpio_set_value(EGPIO_MAGICIAN_CODEC_POWER, 1); gpio_set_value(EGPIO_MAGICIAN_IN_SEL0, 0); - /* we may need to have the clock running here - pH5 */ - gpio_set_value(EGPIO_MAGICIAN_CODEC_RESET, 1); - udelay(5); - gpio_set_value(EGPIO_MAGICIAN_CODEC_RESET, 0); - magician_snd_device = platform_device_alloc("soc-audio", -1); if (!magician_snd_device) { ret = -ENOMEM; @@ -526,10 +535,6 @@ err_request_mic: err_request_ep: gpio_free(EGPIO_MAGICIAN_SPK_POWER); err_request_spk: - gpio_free(EGPIO_MAGICIAN_CODEC_RESET); -err_request_reset: - gpio_free(EGPIO_MAGICIAN_CODEC_POWER); -err_request_power: return ret; } @@ -540,15 +545,12 @@ static void __exit magician_exit(void) gpio_set_value(EGPIO_MAGICIAN_SPK_POWER, 0); gpio_set_value(EGPIO_MAGICIAN_EP_POWER, 0); gpio_set_value(EGPIO_MAGICIAN_MIC_POWER, 0); - gpio_set_value(EGPIO_MAGICIAN_CODEC_POWER, 0); gpio_free(EGPIO_MAGICIAN_IN_SEL1); gpio_free(EGPIO_MAGICIAN_IN_SEL0); gpio_free(EGPIO_MAGICIAN_MIC_POWER); gpio_free(EGPIO_MAGICIAN_EP_POWER); gpio_free(EGPIO_MAGICIAN_SPK_POWER); - gpio_free(EGPIO_MAGICIAN_CODEC_RESET); - gpio_free(EGPIO_MAGICIAN_CODEC_POWER); } module_init(magician_init); -- cgit v1.2.3 From 8776b268c6b0b671c2c744b06fee021b6da3e45f Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 26 Apr 2009 12:53:58 +0100 Subject: [ARM] pxa: register wm8731 explicitly for corgi and poodle The wm8731 driver has been converted to register using the standard I2C device registration mechanism so we can now do the registration in the arch/arm code as normal. Signed-off-by: Mark Brown Signed-off-by: Eric Miao --- sound/soc/pxa/corgi.c | 36 ------------------------------------ sound/soc/pxa/poodle.c | 36 ------------------------------------ 2 files changed, 72 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/pxa/corgi.c b/sound/soc/pxa/corgi.c index d5be2b30cda..fefe1a57f31 100644 --- a/sound/soc/pxa/corgi.c +++ b/sound/soc/pxa/corgi.c @@ -320,38 +320,6 @@ static struct snd_soc_device corgi_snd_devdata = { .codec_dev = &soc_codec_dev_wm8731, }; -/* - * FIXME: This is a temporary bodge to avoid cross-tree merge issues. - * New drivers should register the wm8731 I2C device in the machine - * setup code (under arch/arm for ARM systems). - */ -static int wm8731_i2c_register(void) -{ - struct i2c_board_info info; - struct i2c_adapter *adapter; - struct i2c_client *client; - - memset(&info, 0, sizeof(struct i2c_board_info)); - info.addr = 0x1b; - strlcpy(info.type, "wm8731", I2C_NAME_SIZE); - - adapter = i2c_get_adapter(0); - if (!adapter) { - printk(KERN_ERR "can't get i2c adapter 0\n"); - return -ENODEV; - } - - client = i2c_new_device(adapter, &info); - i2c_put_adapter(adapter); - if (!client) { - printk(KERN_ERR "can't add i2c device at 0x%x\n", - (unsigned int)info.addr); - return -ENODEV; - } - - return 0; -} - static struct platform_device *corgi_snd_device; static int __init corgi_init(void) @@ -362,10 +330,6 @@ static int __init corgi_init(void) machine_is_husky())) return -ENODEV; - ret = wm8731_i2c_register(); - if (ret != 0) - return ret; - corgi_snd_device = platform_device_alloc("soc-audio", -1); if (!corgi_snd_device) return -ENOMEM; diff --git a/sound/soc/pxa/poodle.c b/sound/soc/pxa/poodle.c index a51058f6674..c5f36e0eab5 100644 --- a/sound/soc/pxa/poodle.c +++ b/sound/soc/pxa/poodle.c @@ -280,38 +280,6 @@ static struct snd_soc_card snd_soc_poodle = { .num_links = 1, }; -/* - * FIXME: This is a temporary bodge to avoid cross-tree merge issues. - * New drivers should register the wm8731 I2C device in the machine - * setup code (under arch/arm for ARM systems). - */ -static int wm8731_i2c_register(void) -{ - struct i2c_board_info info; - struct i2c_adapter *adapter; - struct i2c_client *client; - - memset(&info, 0, sizeof(struct i2c_board_info)); - info.addr = 0x1b; - strlcpy(info.type, "wm8731", I2C_NAME_SIZE); - - adapter = i2c_get_adapter(0); - if (!adapter) { - printk(KERN_ERR "can't get i2c adapter 0\n"); - return -ENODEV; - } - - client = i2c_new_device(adapter, &info); - i2c_put_adapter(adapter); - if (!client) { - printk(KERN_ERR "can't add i2c device at 0x%x\n", - (unsigned int)info.addr); - return -ENODEV; - } - - return 0; -} - /* poodle audio subsystem */ static struct snd_soc_device poodle_snd_devdata = { .card = &snd_soc_poodle, @@ -327,10 +295,6 @@ static int __init poodle_init(void) if (!machine_is_poodle()) return -ENODEV; - ret = wm8731_i2c_register(); - if (ret != 0) - return ret; - locomo_gpio_set_dir(&poodle_locomo_device.dev, POODLE_LOCOMO_GPIO_AMP_ON, 0); /* should we mute HP at startup - burning power ?*/ -- cgit v1.2.3 From ef39412622b6e8f09c383de9565b07e93553fc27 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Wed, 17 Jun 2009 11:42:45 +0200 Subject: ASoC: Kill BUS_ID_SIZE Remove the use of BUS_ID_SIZE from txx9aclc.c, as BUS_ID_SIZE will be removed soon later. Also, use snprintf() instead of sprintf() as a safer operation. Signed-off-by: Takashi Iwai --- sound/soc/txx9/txx9aclc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/txx9/txx9aclc.c b/sound/soc/txx9/txx9aclc.c index fa336616152..938a58a5a24 100644 --- a/sound/soc/txx9/txx9aclc.c +++ b/sound/soc/txx9/txx9aclc.c @@ -297,9 +297,9 @@ static int txx9aclc_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, static bool filter(struct dma_chan *chan, void *param) { struct txx9aclc_dmadata *dmadata = param; - char devname[BUS_ID_SIZE + 2]; + char devname[20 + 2]; /* FIXME: old BUS_ID_SIZE + 2 */ - sprintf(devname, "%s.%d", dmadata->dma_res->name, + snprintf(devname, sizeof(devname), "%s.%d", dmadata->dma_res->name, (int)dmadata->dma_res->start); if (strcmp(dev_name(chan->device->dev), devname) == 0) { chan->private = &dmadata->dma_slave; -- cgit v1.2.3 From a583cd53478f0c55b92f084bdbe3b66d2b4496df Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 17 Jun 2009 17:30:14 +0100 Subject: ASoC: Regulator support for WM8580 Add basic support for integration with the regulator API to WM8580. Since the core cannot yet disable biases when the CODEC is idle we simply request and enable the regulators for the entire time the driver is active. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8580.c | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index 261ef101d4f..97b9ed95d28 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -24,6 +24,8 @@ #include #include #include +#include + #include #include #include @@ -187,15 +189,22 @@ struct pll_state { unsigned int out; }; +#define WM8580_NUM_SUPPLIES 3 +static const char *wm8580_supply_names[WM8580_NUM_SUPPLIES] = { + "AVDD", + "DVDD", + "PVDD", +}; + /* codec private data */ struct wm8580_priv { struct snd_soc_codec codec; + struct regulator_bulk_data supplies[WM8580_NUM_SUPPLIES]; u16 reg_cache[WM8580_MAX_REGISTER + 1]; struct pll_state a; struct pll_state b; }; - /* * read wm8580 register cache */ @@ -922,11 +931,28 @@ static int wm8580_register(struct wm8580_priv *wm8580) memcpy(codec->reg_cache, wm8580_reg, sizeof(wm8580_reg)); + for (i = 0; i < ARRAY_SIZE(wm8580->supplies); i++) + wm8580->supplies[i].supply = wm8580_supply_names[i]; + + ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8580->supplies), + wm8580->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to request supplies: %d\n", ret); + goto err; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8580->supplies), + wm8580->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); + goto err_regulator_get; + } + /* Get the codec into a known state */ ret = wm8580_write(codec, WM8580_RESET, 0); if (ret != 0) { dev_err(codec->dev, "Failed to reset codec: %d\n", ret); - goto err; + goto err_regulator_enable; } for (i = 0; i < ARRAY_SIZE(wm8580_dai); i++) @@ -939,7 +965,7 @@ static int wm8580_register(struct wm8580_priv *wm8580) ret = snd_soc_register_codec(codec); if (ret != 0) { dev_err(codec->dev, "Failed to register codec: %d\n", ret); - goto err; + goto err_regulator_enable; } ret = snd_soc_register_dais(wm8580_dai, ARRAY_SIZE(wm8580_dai)); @@ -952,6 +978,10 @@ static int wm8580_register(struct wm8580_priv *wm8580) err_codec: snd_soc_unregister_codec(codec); +err_regulator_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8580->supplies), wm8580->supplies); +err_regulator_get: + regulator_bulk_free(ARRAY_SIZE(wm8580->supplies), wm8580->supplies); err: kfree(wm8580); return ret; @@ -962,6 +992,8 @@ static void wm8580_unregister(struct wm8580_priv *wm8580) wm8580_set_bias_level(&wm8580->codec, SND_SOC_BIAS_OFF); snd_soc_unregister_dais(wm8580_dai, ARRAY_SIZE(wm8580_dai)); snd_soc_unregister_codec(&wm8580->codec); + regulator_bulk_disable(ARRAY_SIZE(wm8580->supplies), wm8580->supplies); + regulator_bulk_free(ARRAY_SIZE(wm8580->supplies), wm8580->supplies); kfree(wm8580); wm8580_codec = NULL; } -- cgit v1.2.3 From b53109db5e016425b767e8e33669a5f41257e6e5 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 20 Jun 2009 13:53:20 +0100 Subject: ASoC: Fix shadowed variables in twl4030 No need to define second copies of mode and format, they're declared with exactly the same type at the head of the function and there's no conflict in their use. Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 4dbb853eef5..a5062ccd28e 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -1609,8 +1609,6 @@ static int twl4030_hw_params(struct snd_pcm_substream *substream, /* If the substream has 4 channel, do the necessary setup */ if (params_channels(params) == 4) { - u8 format, mode; - format = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF); mode = twl4030_read_reg_cache(codec, TWL4030_REG_CODEC_MODE); -- cgit v1.2.3 From 423c238d7185a0a8f1b28ddb0be44e0286927909 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 20 Jun 2009 13:54:02 +0100 Subject: ASoC: Staticise put_twl4030_opmode_enum_double() It's an operation for a control and doesn't need to be exported. Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index a5062ccd28e..4275eea3145 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -924,7 +924,7 @@ static const struct soc_enum twl4030_op_modes_enum = ARRAY_SIZE(twl4030_op_modes_texts), twl4030_op_modes_texts); -int snd_soc_put_twl4030_opmode_enum_double(struct snd_kcontrol *kcontrol, +static int snd_soc_put_twl4030_opmode_enum_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); -- cgit v1.2.3 From 895c9c056591f05ba3b669cda6c12afb212c6253 Mon Sep 17 00:00:00 2001 From: Cliff Cai Date: Sat, 20 Jun 2009 11:29:05 -0400 Subject: ASoC: Blackfin: keep better track of SPORT configuration state Do not let the SPORT be reconfigured until there are no more active streams. Then we can let the system reprogram the SPORT state. Signed-off-by: Cliff Cai Signed-off-by: Mike Frysinger Signed-off-by: Mark Brown --- sound/soc/blackfin/bf5xx-i2s.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/blackfin/bf5xx-i2s.c b/sound/soc/blackfin/bf5xx-i2s.c index 96482441967..0e8af48650b 100644 --- a/sound/soc/blackfin/bf5xx-i2s.c +++ b/sound/soc/blackfin/bf5xx-i2s.c @@ -50,6 +50,7 @@ struct bf5xx_i2s_port { u16 tcr2; u16 rcr2; int counter; + int configured; }; static struct bf5xx_i2s_port bf5xx_i2s; @@ -168,7 +169,7 @@ static int bf5xx_i2s_hw_params(struct snd_pcm_substream *substream, break; } - if (bf5xx_i2s.counter == 1) { + if (!bf5xx_i2s.configured) { /* * TX and RX are not independent,they are enabled at the * same time, even if only one side is running. So, we @@ -177,6 +178,7 @@ static int bf5xx_i2s_hw_params(struct snd_pcm_substream *substream, * * CPU DAI:slave mode. */ + bf5xx_i2s.configured = 1; ret = sport_config_rx(sport_handle, bf5xx_i2s.rcr1, bf5xx_i2s.rcr2, 0, 0); if (ret) { @@ -200,6 +202,9 @@ static void bf5xx_i2s_shutdown(struct snd_pcm_substream *substream, { pr_debug("%s enter\n", __func__); bf5xx_i2s.counter--; + /* No active stream, SPORT is allowed to be configured again. */ + if (!bf5xx_i2s.counter) + bf5xx_i2s.configured = 0; } static int bf5xx_i2s_probe(struct platform_device *pdev, -- cgit v1.2.3 From 92a6ad34282f8403c4eb83025a2790af86cdb6bd Mon Sep 17 00:00:00 2001 From: Barry Song Date: Sat, 20 Jun 2009 11:29:57 -0400 Subject: ASoC: Blackfin: update the bf5xx_i2s_resume parameters Latest ASoC only passes snd_soc_dai to the resume function. Signed-off-by: Barry Song Signed-off-by: Mike Frysinger Signed-off-by: Mark Brown --- sound/soc/blackfin/bf5xx-i2s.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/blackfin/bf5xx-i2s.c b/sound/soc/blackfin/bf5xx-i2s.c index 0e8af48650b..af06904bab0 100644 --- a/sound/soc/blackfin/bf5xx-i2s.c +++ b/sound/soc/blackfin/bf5xx-i2s.c @@ -249,8 +249,7 @@ static int bf5xx_i2s_suspend(struct snd_soc_dai *dai) return 0; } -static int bf5xx_i2s_resume(struct platform_device *pdev, - struct snd_soc_dai *dai) +static int bf5xx_i2s_resume(struct snd_soc_dai *dai) { int ret; struct sport_device *sport = -- cgit v1.2.3 From f274143f127dd9420e6def7dc47340b55f29c885 Mon Sep 17 00:00:00 2001 From: Barry Song Date: Sat, 20 Jun 2009 11:29:56 -0400 Subject: ASoC: Blackfin: convert internal names from bf52x to bf5xx These drivers aren't BF52x specific, so don't use bf52x in the names. Signed-off-by: Barry Song Signed-off-by: Mike Frysinger Signed-off-by: Mark Brown --- sound/soc/blackfin/bf5xx-ad73311.c | 16 ++++++++-------- sound/soc/blackfin/bf5xx-ssm2602.c | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/blackfin/bf5xx-ad73311.c b/sound/soc/blackfin/bf5xx-ad73311.c index edfbdc024e6..9825b71d0e2 100644 --- a/sound/soc/blackfin/bf5xx-ad73311.c +++ b/sound/soc/blackfin/bf5xx-ad73311.c @@ -203,23 +203,23 @@ static struct snd_soc_device bf5xx_ad73311_snd_devdata = { .codec_dev = &soc_codec_dev_ad73311, }; -static struct platform_device *bf52x_ad73311_snd_device; +static struct platform_device *bf5xx_ad73311_snd_device; static int __init bf5xx_ad73311_init(void) { int ret; pr_debug("%s enter\n", __func__); - bf52x_ad73311_snd_device = platform_device_alloc("soc-audio", -1); - if (!bf52x_ad73311_snd_device) + bf5xx_ad73311_snd_device = platform_device_alloc("soc-audio", -1); + if (!bf5xx_ad73311_snd_device) return -ENOMEM; - platform_set_drvdata(bf52x_ad73311_snd_device, &bf5xx_ad73311_snd_devdata); - bf5xx_ad73311_snd_devdata.dev = &bf52x_ad73311_snd_device->dev; - ret = platform_device_add(bf52x_ad73311_snd_device); + platform_set_drvdata(bf5xx_ad73311_snd_device, &bf5xx_ad73311_snd_devdata); + bf5xx_ad73311_snd_devdata.dev = &bf5xx_ad73311_snd_device->dev; + ret = platform_device_add(bf5xx_ad73311_snd_device); if (ret) - platform_device_put(bf52x_ad73311_snd_device); + platform_device_put(bf5xx_ad73311_snd_device); return ret; } @@ -227,7 +227,7 @@ static int __init bf5xx_ad73311_init(void) static void __exit bf5xx_ad73311_exit(void) { pr_debug("%s enter\n", __func__); - platform_device_unregister(bf52x_ad73311_snd_device); + platform_device_unregister(bf5xx_ad73311_snd_device); } module_init(bf5xx_ad73311_init); diff --git a/sound/soc/blackfin/bf5xx-ssm2602.c b/sound/soc/blackfin/bf5xx-ssm2602.c index bc0cdded711..3a00fa4dbe6 100644 --- a/sound/soc/blackfin/bf5xx-ssm2602.c +++ b/sound/soc/blackfin/bf5xx-ssm2602.c @@ -148,24 +148,24 @@ static struct snd_soc_device bf5xx_ssm2602_snd_devdata = { .codec_data = &bf5xx_ssm2602_setup, }; -static struct platform_device *bf52x_ssm2602_snd_device; +static struct platform_device *bf5xx_ssm2602_snd_device; static int __init bf5xx_ssm2602_init(void) { int ret; pr_debug("%s enter\n", __func__); - bf52x_ssm2602_snd_device = platform_device_alloc("soc-audio", -1); - if (!bf52x_ssm2602_snd_device) + bf5xx_ssm2602_snd_device = platform_device_alloc("soc-audio", -1); + if (!bf5xx_ssm2602_snd_device) return -ENOMEM; - platform_set_drvdata(bf52x_ssm2602_snd_device, + platform_set_drvdata(bf5xx_ssm2602_snd_device, &bf5xx_ssm2602_snd_devdata); - bf5xx_ssm2602_snd_devdata.dev = &bf52x_ssm2602_snd_device->dev; - ret = platform_device_add(bf52x_ssm2602_snd_device); + bf5xx_ssm2602_snd_devdata.dev = &bf5xx_ssm2602_snd_device->dev; + ret = platform_device_add(bf5xx_ssm2602_snd_device); if (ret) - platform_device_put(bf52x_ssm2602_snd_device); + platform_device_put(bf5xx_ssm2602_snd_device); return ret; } @@ -173,7 +173,7 @@ static int __init bf5xx_ssm2602_init(void) static void __exit bf5xx_ssm2602_exit(void) { pr_debug("%s enter\n", __func__); - platform_device_unregister(bf52x_ssm2602_snd_device); + platform_device_unregister(bf5xx_ssm2602_snd_device); } module_init(bf5xx_ssm2602_init); -- cgit v1.2.3 From c264301c777840b2df130e042b7f5a0c1041646f Mon Sep 17 00:00:00 2001 From: "Lopez Cruz, Misael" Date: Fri, 19 Jun 2009 03:23:42 -0500 Subject: ASoC: TWL4030: Fix voice interface clock masters Voice interface of twl4030 codec supports: CBM_CFM and CBS_CFS. It doesn't support CBS_CFM. Signed-off-by: Misael Lopez Cruz Acked-By: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 2 +- sound/soc/omap/sdp3430.c | 2 +- sound/soc/omap/zoom2.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 4275eea3145..091125c888e 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -1946,7 +1946,7 @@ static int twl4030_voice_set_dai_fmt(struct snd_soc_dai *codec_dai, /* set master/slave audio interface */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { - case SND_SOC_DAIFMT_CBS_CFM: + case SND_SOC_DAIFMT_CBM_CFM: format &= ~(TWL4030_VIF_SLAVE_EN); break; case SND_SOC_DAIFMT_CBS_CFS: diff --git a/sound/soc/omap/sdp3430.c b/sound/soc/omap/sdp3430.c index b719e5db4f5..c51594d8fd1 100644 --- a/sound/soc/omap/sdp3430.c +++ b/sound/soc/omap/sdp3430.c @@ -96,7 +96,7 @@ static int sdp3430_hw_voice_params(struct snd_pcm_substream *substream, ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF | - SND_SOC_DAIFMT_CBS_CFM); + SND_SOC_DAIFMT_CBM_CFM); if (ret) { printk(KERN_ERR "can't set codec DAI configuration\n"); return ret; diff --git a/sound/soc/omap/zoom2.c b/sound/soc/omap/zoom2.c index be2e307fdf7..3de6d2bd390 100644 --- a/sound/soc/omap/zoom2.c +++ b/sound/soc/omap/zoom2.c @@ -92,7 +92,7 @@ static int zoom2_hw_voice_params(struct snd_pcm_substream *substream, ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_IB_NF | - SND_SOC_DAIFMT_CBS_CFM); + SND_SOC_DAIFMT_CBM_CFM); if (ret) { printk(KERN_ERR "can't set codec DAI configuration\n"); return ret; -- cgit v1.2.3 From 328d0a138e3d7761f4db53fabf82279b90ea66dd Mon Sep 17 00:00:00 2001 From: "Lopez Cruz, Misael" Date: Mon, 22 Jun 2009 10:51:52 -0500 Subject: ASoC: TWL4030: Add AVADC Clock Priority AVDAC clk priority allows to determine the path ADC must be connected when the codec is in option2 and both HiFi and Voice paths are enabled. Signed-off-by: Misael Lopez Cruz Acked-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 091125c888e..f916a9a4671 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -1005,6 +1005,16 @@ static DECLARE_TLV_DB_SCALE(digital_capture_tlv, 0, 100, 0); */ static DECLARE_TLV_DB_SCALE(input_gain_tlv, 0, 600, 0); +/* AVADC clock priority */ +static const char *twl4030_avadc_clk_priority_texts[] = { + "Voice high priority", "HiFi high priority" +}; + +static const struct soc_enum twl4030_avadc_clk_priority_enum = + SOC_ENUM_SINGLE(TWL4030_REG_AVADC_CTL, 2, + ARRAY_SIZE(twl4030_avadc_clk_priority_texts), + twl4030_avadc_clk_priority_texts); + static const char *twl4030_rampdelay_texts[] = { "27/20/14 ms", "55/40/27 ms", "109/81/55 ms", "218/161/109 ms", "437/323/218 ms", "874/645/437 ms", "1748/1291/874 ms", @@ -1106,6 +1116,8 @@ static const struct snd_kcontrol_new twl4030_snd_controls[] = { SOC_DOUBLE_TLV("Analog Capture Volume", TWL4030_REG_ANAMIC_GAIN, 0, 3, 5, 0, input_gain_tlv), + SOC_ENUM("AVADC Clock Priority", twl4030_avadc_clk_priority_enum), + SOC_ENUM("HS ramp delay", twl4030_rampdelay_enum), SOC_ENUM("Vibra H-bridge mode", twl4030_vibradirmode_enum), -- cgit v1.2.3 From 30808ca751c3b8d81e948efb8fed7451a8321010 Mon Sep 17 00:00:00 2001 From: "Lopez Cruz, Misael" Date: Mon, 22 Jun 2009 19:34:07 -0500 Subject: ASoC: TWL4030: Correct bypass event for voice sidetone Event for voice sidetone was being interpreted as an analog HiFi bypass event because VSTPGA register offset is less than ARXR2_APGA_CTL offset. Reordering the register checks allows to handle voice digital bypass event properly. Signed-off-by: Misael Lopez Cruz Acked-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index f916a9a4671..df42fa2abd9 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -712,7 +712,19 @@ static int bypass_event(struct snd_soc_dapm_widget *w, reg = twl4030_read_reg_cache(w->codec, m->reg); - if (m->reg <= TWL4030_REG_ARXR2_APGA_CTL) { + /* + * bypass_state[0:3] - analog HiFi bypass + * bypass_state[4] - analog voice bypass + * bypass_state[5] - digital voice bypass + * bypass_state[6:7] - digital HiFi bypass + */ + if (m->reg == TWL4030_REG_VSTPGA) { + /* Voice digital bypass */ + if (reg) + twl4030->bypass_state |= (1 << 5); + else + twl4030->bypass_state &= ~(1 << 5); + } else if (m->reg <= TWL4030_REG_ARXR2_APGA_CTL) { /* Analog bypass */ if (reg & (1 << m->shift)) twl4030->bypass_state |= @@ -726,12 +738,6 @@ static int bypass_event(struct snd_soc_dapm_widget *w, twl4030->bypass_state |= (1 << 4); else twl4030->bypass_state &= ~(1 << 4); - } else if (m->reg == TWL4030_REG_VSTPGA) { - /* Voice digital bypass */ - if (reg) - twl4030->bypass_state |= (1 << 5); - else - twl4030->bypass_state &= ~(1 << 5); } else { /* Digital bypass */ if (reg & (0x7 << m->shift)) -- cgit v1.2.3 From 517374704da44c1ba77c1600714fe214524af286 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 22 Jun 2009 13:16:51 +0100 Subject: ASoC: Add a shutdown callback Ensure that the audio subsystem is powered down cleanly when the system shuts down by providing a shutdown operation. This ensures that all the components have been returned to an off state cleanly which should avoid audio issues from partially charged capacitors or noise on digital inputs if the system is restarted quickly. Signed-off-by: Mark Brown Tested-by: Ben Dooks --- sound/soc/soc-core.c | 16 ++++++++++++++++ sound/soc/soc-dapm.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 44141178ff4..55d45c43ba1 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1020,6 +1020,21 @@ static int soc_remove(struct platform_device *pdev) return 0; } +static void soc_shutdown(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_card *card = socdev->card; + + if (!card->instantiated) + return; + + /* Flush out pmdown_time work - we actually do want to run it + * now, we're shutting down so no imminent restart. */ + run_delayed_work(&card->delayed_work); + + snd_soc_dapm_shutdown(socdev); +} + /* ASoC platform driver */ static struct platform_driver soc_driver = { .driver = { @@ -1030,6 +1045,7 @@ static struct platform_driver soc_driver = { .remove = soc_remove, .suspend = soc_suspend, .resume = soc_resume, + .shutdown = soc_shutdown, }; /* create a new pcm */ diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 653435930ad..b9129efeedf 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -2032,6 +2032,35 @@ void snd_soc_dapm_free(struct snd_soc_device *socdev) } EXPORT_SYMBOL_GPL(snd_soc_dapm_free); +/* + * snd_soc_dapm_shutdown - callback for system shutdown + */ +void snd_soc_dapm_shutdown(struct snd_soc_device *socdev) +{ + struct snd_soc_codec *codec = socdev->card->codec; + struct snd_soc_dapm_widget *w; + LIST_HEAD(down_list); + int powerdown = 0; + + list_for_each_entry(w, &codec->dapm_widgets, list) { + if (w->power) { + dapm_seq_insert(w, &down_list, dapm_down_seq); + powerdown = 1; + } + } + + /* If there were no widgets to power down we're already in + * standby. + */ + if (powerdown) { + snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_PREPARE); + dapm_seq_run(codec, &down_list, 0, dapm_down_seq); + snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_STANDBY); + } + + snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_OFF); +} + /* Module information */ MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk"); MODULE_DESCRIPTION("Dynamic Audio Power Management core for ALSA SoC"); -- cgit v1.2.3 From e2f551dacbdff8e40365a989ab66104b03316f4d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 25 Jun 2009 13:57:59 +0100 Subject: ASoC: Add core suspend and resume callbacks to WM8961 Signed-off-by: Mark Brown --- sound/soc/codecs/wm8961.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8961.c b/sound/soc/codecs/wm8961.c index 8e78959ca40..1af2d10702f 100644 --- a/sound/soc/codecs/wm8961.c +++ b/sound/soc/codecs/wm8961.c @@ -1267,6 +1267,21 @@ static __devexit int wm8961_i2c_remove(struct i2c_client *client) return 0; } +#ifdef CONFIG_PM +static int wm8961_i2c_suspend(struct i2c_client *client) +{ + return snd_soc_suspend_device(&client->dev); +} + +static int wm8961_i2c_resume(struct i2c_client *client) +{ + return snd_soc_resume_device(&client->dev); +} +#else +#define wm8961_i2c_suspend NULL +#define wm8961_i2c_resume NULL +#endif + static const struct i2c_device_id wm8961_i2c_id[] = { { "wm8961", 0 }, { } @@ -1280,6 +1295,8 @@ static struct i2c_driver wm8961_i2c_driver = { }, .probe = wm8961_i2c_probe, .remove = __devexit_p(wm8961_i2c_remove), + .suspend = wm8961_i2c_suspend, + .resume = wm8961_i2c_resume, .id_table = wm8961_i2c_id, }; -- cgit v1.2.3 From 647613e97fa46f6c25cf38b0f2fa81eba5f278d4 Mon Sep 17 00:00:00 2001 From: Atsushi Nemoto Date: Thu, 25 Jun 2009 22:36:58 +0900 Subject: ASoC: txx9aclc: dynamically allocate dmaengine devname Use kasprintf to allocate temporary devname string instead of a fixed size string. This fixes "FIXME" introduced on removal of BUS_ID_SIZE. Signed-off-by: Atsushi Nemoto Signed-off-by: Mark Brown --- sound/soc/txx9/txx9aclc.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/txx9/txx9aclc.c b/sound/soc/txx9/txx9aclc.c index 938a58a5a24..efed64b8b02 100644 --- a/sound/soc/txx9/txx9aclc.c +++ b/sound/soc/txx9/txx9aclc.c @@ -297,15 +297,17 @@ static int txx9aclc_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, static bool filter(struct dma_chan *chan, void *param) { struct txx9aclc_dmadata *dmadata = param; - char devname[20 + 2]; /* FIXME: old BUS_ID_SIZE + 2 */ + char *devname; + bool found = false; - snprintf(devname, sizeof(devname), "%s.%d", dmadata->dma_res->name, + devname = kasprintf(GFP_KERNEL, "%s.%d", dmadata->dma_res->name, (int)dmadata->dma_res->start); if (strcmp(dev_name(chan->device->dev), devname) == 0) { chan->private = &dmadata->dma_slave; - return true; + found = true; } - return false; + kfree(devname); + return found; } static int txx9aclc_dma_init(struct txx9aclc_soc_device *dev, -- cgit v1.2.3 From e3c7dbb07cde886ba97f183c4cc98a2a99b46eaa Mon Sep 17 00:00:00 2001 From: "Lopez Cruz, Misael" Date: Thu, 25 Jun 2009 12:36:14 -0500 Subject: ASoC: Remove word "Switch" from Handsfree switch name SoC dapm adds the suffix "Switch" to SND_SOC_DAPM_SWITCH controls, removing word "Switch" from HandsfreeL/HandsfreeR widget name for avoiding to duplicate it. Signed-off-by: Misael Lopez Cruz Acked-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index df42fa2abd9..49ceb620678 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -1258,14 +1258,14 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { /* HandsfreeL/R */ SND_SOC_DAPM_MUX("HandsfreeL Mux", SND_SOC_NOPM, 0, 0, &twl4030_dapm_handsfreel_control), - SND_SOC_DAPM_SWITCH("HandsfreeL Switch", SND_SOC_NOPM, 0, 0, + SND_SOC_DAPM_SWITCH("HandsfreeL", SND_SOC_NOPM, 0, 0, &twl4030_dapm_handsfreelmute_control), SND_SOC_DAPM_PGA_E("HandsfreeL PGA", SND_SOC_NOPM, 0, 0, NULL, 0, handsfreelpga_event, SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MUX("HandsfreeR Mux", SND_SOC_NOPM, 5, 0, &twl4030_dapm_handsfreer_control), - SND_SOC_DAPM_SWITCH("HandsfreeR Switch", SND_SOC_NOPM, 0, 0, + SND_SOC_DAPM_SWITCH("HandsfreeR", SND_SOC_NOPM, 0, 0, &twl4030_dapm_handsfreermute_control), SND_SOC_DAPM_PGA_E("HandsfreeR PGA", SND_SOC_NOPM, 0, 0, NULL, 0, handsfreerpga_event, @@ -1377,15 +1377,15 @@ static const struct snd_soc_dapm_route intercon[] = { {"HandsfreeL Mux", "AudioL1", "Analog L1 Playback Mixer"}, {"HandsfreeL Mux", "AudioL2", "Analog L2 Playback Mixer"}, {"HandsfreeL Mux", "AudioR2", "Analog R2 Playback Mixer"}, - {"HandsfreeL Switch", "Switch", "HandsfreeL Mux"}, - {"HandsfreeL PGA", NULL, "HandsfreeL Switch"}, + {"HandsfreeL", "Switch", "HandsfreeL Mux"}, + {"HandsfreeL PGA", NULL, "HandsfreeL"}, /* HandsfreeR */ {"HandsfreeR Mux", "Voice", "Analog Voice Playback Mixer"}, {"HandsfreeR Mux", "AudioR1", "Analog R1 Playback Mixer"}, {"HandsfreeR Mux", "AudioR2", "Analog R2 Playback Mixer"}, {"HandsfreeR Mux", "AudioL2", "Analog L2 Playback Mixer"}, - {"HandsfreeR Switch", "Switch", "HandsfreeR Mux"}, - {"HandsfreeR PGA", NULL, "HandsfreeR Switch"}, + {"HandsfreeR", "Switch", "HandsfreeR Mux"}, + {"HandsfreeR PGA", NULL, "HandsfreeR"}, /* Vibra */ {"Vibra Mux", "AudioL1", "DAC Left1"}, {"Vibra Mux", "AudioR1", "DAC Right1"}, -- cgit v1.2.3 From c2caa4da46a41899b29f90ec30ef3ba86665b334 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 26 Jun 2009 15:36:56 +0100 Subject: ASoC: Fix widget powerdown on shutdown We need to set the widget power state we want to implement. Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 1 + 1 file changed, 1 insertion(+) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index b9129efeedf..5157ec110cf 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -2045,6 +2045,7 @@ void snd_soc_dapm_shutdown(struct snd_soc_device *socdev) list_for_each_entry(w, &codec->dapm_widgets, list) { if (w->power) { dapm_seq_insert(w, &down_list, dapm_down_seq); + w->power = 0; powerdown = 1; } } -- cgit v1.2.3 From 9ea21ebca1a1c4caf3bdaecb0879034107cac1e9 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 29 Jun 2009 16:29:06 +0100 Subject: ASoC: Fix WM8961 suspend function type Signed-off-by: Mark Brown --- sound/soc/codecs/wm8961.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8961.c b/sound/soc/codecs/wm8961.c index 1af2d10702f..bd1af92a122 100644 --- a/sound/soc/codecs/wm8961.c +++ b/sound/soc/codecs/wm8961.c @@ -1268,7 +1268,7 @@ static __devexit int wm8961_i2c_remove(struct i2c_client *client) } #ifdef CONFIG_PM -static int wm8961_i2c_suspend(struct i2c_client *client) +static int wm8961_i2c_suspend(struct i2c_client *client, pm_message_t state) { return snd_soc_suspend_device(&client->dev); } -- cgit v1.2.3 From 6a84c234da06a4ac0c1b4c819b83cf264674c2d8 Mon Sep 17 00:00:00 2001 From: Grant Likely Date: Sun, 28 Jun 2009 01:41:52 -0600 Subject: ASoC: Fix typo in MPC5200 PSC AC97 driver Kconfig ALSA SoC drivers should be specify SND_SOC_AC97_BUS instead, not AC97_BUS. Without SND_SOC_AC97_BUS defined, an AC97 device will not get correctly registered on the AC97 bus, which prevents thinks like the WM9712 touchscreen driver from getting probed. Tested against 2.6.31-rc1. Signed-off-by: Grant Likely Acked-by: Jon Smirl Signed-off-by: Mark Brown --- sound/soc/fsl/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 5dbebf82249..5661876ee83 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -33,7 +33,7 @@ config SND_SOC_MPC5200_I2S config SND_SOC_MPC5200_AC97 tristate "Freescale MPC5200 PSC in AC97 mode driver" depends on PPC_MPC52xx && PPC_BESTCOMM - select AC97_BUS + select SND_SOC_AC97_BUS select SND_MPC52xx_DMA select PPC_BESTCOMM_GEN_BD help -- cgit v1.2.3 From 40d9ec14e7e1f62d2379ecc1b5ee00ddfc2a5d0c Mon Sep 17 00:00:00 2001 From: Grant Likely Date: Sun, 28 Jun 2009 01:42:06 -0600 Subject: ASoC: remove BROKEN from Efika and pcm030 fabric drivers The needed spin_event_timeout() macro is now merged in from the powerpc tree, so these drivers are no longer broken. This reverts commit 0c0e09e21a9e7bc6ca54e06ef3d497255ca26383 (ASoC: Mark MPC5200 AC97 as BROKEN until PowerPC merge issues are resolved) Tested against 2.6.31-rc1. Signed-off-by: Grant Likely Acked-by: Jon Smirl Signed-off-by: Mark Brown --- sound/soc/fsl/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig index 5661876ee83..8cb65ccad35 100644 --- a/sound/soc/fsl/Kconfig +++ b/sound/soc/fsl/Kconfig @@ -41,7 +41,7 @@ config SND_SOC_MPC5200_AC97 config SND_MPC52xx_SOC_PCM030 tristate "SoC AC97 Audio support for Phytec pcm030 and WM9712" - depends on PPC_MPC5200_SIMPLE && BROKEN + depends on PPC_MPC5200_SIMPLE select SND_SOC_MPC5200_AC97 select SND_SOC_WM9712 help @@ -50,7 +50,7 @@ config SND_MPC52xx_SOC_PCM030 config SND_MPC52xx_SOC_EFIKA tristate "SoC AC97 Audio support for bbplan Efika and STAC9766" - depends on PPC_EFIKA && BROKEN + depends on PPC_EFIKA select SND_SOC_MPC5200_AC97 select SND_SOC_STAC9766 help -- cgit v1.2.3 From 1bdd7419910c1506151e7b9e2d60c6980e015f76 Mon Sep 17 00:00:00 2001 From: Janusz Krzysztofik Date: Sun, 28 Jun 2009 00:21:05 +0200 Subject: ASoC: OMAP: fix OMAP1510 broken PCM pointer callback This patch tries to work around the problem of broken OMAP1510 PCM playback pointer calculation by replacing DMA function call that incorrectly tries to read the value form DMA hardware with a value computed locally from an already maintained variable omap_runtime_data.period_index. Tested on OMAP5910 based Amstrad Delta (E3) using work in progress ASoC driver. Based on linux-2.6-asoc.git v2.6.31-rc1. Signed-off-by: Janusz Krzysztofik Acked-by: Jarkko Nikula Acked-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/omap/omap-pcm.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/omap/omap-pcm.c b/sound/soc/omap/omap-pcm.c index 6454e15f7d2..84a1950880e 100644 --- a/sound/soc/omap/omap-pcm.c +++ b/sound/soc/omap/omap-pcm.c @@ -216,12 +216,15 @@ static snd_pcm_uframes_t omap_pcm_pointer(struct snd_pcm_substream *substream) dma_addr_t ptr; snd_pcm_uframes_t offset; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - ptr = omap_get_dma_src_pos(prtd->dma_ch); - else + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { ptr = omap_get_dma_dst_pos(prtd->dma_ch); + offset = bytes_to_frames(runtime, ptr - runtime->dma_addr); + } else if (!(cpu_is_omap1510())) { + ptr = omap_get_dma_src_pos(prtd->dma_ch); + offset = bytes_to_frames(runtime, ptr - runtime->dma_addr); + } else + offset = prtd->period_index * runtime->period_size; - offset = bytes_to_frames(runtime, ptr - runtime->dma_addr); if (offset >= runtime->buffer_size) offset = 0; -- cgit v1.2.3 From 416356fcfad46bdebcf8e2afdb94919401ff99d3 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 30 Jun 2009 19:05:15 +0100 Subject: ASoC: Convert to dev_pm_ops Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 235503230fe..dfc03c0bacb 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -619,8 +619,9 @@ static struct snd_pcm_ops soc_pcm_ops = { #ifdef CONFIG_PM /* powers down audio subsystem for suspend */ -static int soc_suspend(struct platform_device *pdev, pm_message_t state) +static int soc_suspend(struct device *dev) { + struct platform_device *pdev = to_platform_device(dev); struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_card *card = socdev->card; struct snd_soc_platform *platform = card->platform; @@ -656,7 +657,7 @@ static int soc_suspend(struct platform_device *pdev, pm_message_t state) snd_pcm_suspend_all(card->dai_link[i].pcm); if (card->suspend_pre) - card->suspend_pre(pdev, state); + card->suspend_pre(pdev, PMSG_SUSPEND); for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai; @@ -682,7 +683,7 @@ static int soc_suspend(struct platform_device *pdev, pm_message_t state) } if (codec_dev->suspend) - codec_dev->suspend(pdev, state); + codec_dev->suspend(pdev, PMSG_SUSPEND); for (i = 0; i < card->num_links; i++) { struct snd_soc_dai *cpu_dai = card->dai_link[i].cpu_dai; @@ -691,7 +692,7 @@ static int soc_suspend(struct platform_device *pdev, pm_message_t state) } if (card->suspend_post) - card->suspend_post(pdev, state); + card->suspend_post(pdev, PMSG_SUSPEND); return 0; } @@ -765,8 +766,9 @@ static void soc_resume_deferred(struct work_struct *work) } /* powers up audio subsystem after a suspend */ -static int soc_resume(struct platform_device *pdev) +static int soc_resume(struct device *dev) { + struct platform_device *pdev = to_platform_device(dev); struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_card *card = socdev->card; struct snd_soc_dai *cpu_dai = card->dai_link[0].cpu_dai; @@ -826,7 +828,6 @@ int snd_soc_resume_device(struct device *dev) return 0; } EXPORT_SYMBOL_GPL(snd_soc_resume_device); - #else #define soc_suspend NULL #define soc_resume NULL @@ -1020,32 +1021,39 @@ static int soc_remove(struct platform_device *pdev) return 0; } -static void soc_shutdown(struct platform_device *pdev) +static int soc_poweroff(struct device *dev) { + struct platform_device *pdev = to_platform_device(dev); struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_card *card = socdev->card; if (!card->instantiated) - return; + return 0; /* Flush out pmdown_time work - we actually do want to run it * now, we're shutting down so no imminent restart. */ run_delayed_work(&card->delayed_work); snd_soc_dapm_shutdown(socdev); + + return 0; } +static struct dev_pm_ops soc_pm_ops = { + .suspend = soc_suspend, + .resume = soc_resume, + .poweroff = soc_poweroff, +}; + /* ASoC platform driver */ static struct platform_driver soc_driver = { .driver = { .name = "soc-audio", .owner = THIS_MODULE, + .pm = &soc_pm_ops, }, .probe = soc_probe, .remove = soc_remove, - .suspend = soc_suspend, - .resume = soc_resume, - .shutdown = soc_shutdown, }; /* create a new pcm */ -- cgit v1.2.3 From da9ff1f796e81976935407251815838bef9868d4 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 1 Jul 2009 18:23:26 +0100 Subject: ASoC: Only disable pxa2xx-i2s clocks if we enabled them The clock API can't cope with unbalanced enables and disables and we only enable in hw_params() but try to disable in shutdown. Signed-off-by: Mark Brown --- sound/soc/pxa/pxa2xx-i2s.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/pxa/pxa2xx-i2s.c b/sound/soc/pxa/pxa2xx-i2s.c index 4743e262895..6b8f655d1ad 100644 --- a/sound/soc/pxa/pxa2xx-i2s.c +++ b/sound/soc/pxa/pxa2xx-i2s.c @@ -167,6 +167,7 @@ static int pxa2xx_i2s_hw_params(struct snd_pcm_substream *substream, BUG_ON(IS_ERR(clk_i2s)); clk_enable(clk_i2s); + dai->private_data = dai; pxa_i2s_wait(); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) @@ -255,7 +256,10 @@ static void pxa2xx_i2s_shutdown(struct snd_pcm_substream *substream, if ((SACR1 & (SACR1_DREC | SACR1_DRPL)) == (SACR1_DREC | SACR1_DRPL)) { SACR0 &= ~SACR0_ENB; pxa_i2s_wait(); - clk_disable(clk_i2s); + if (dai->private_data != NULL) { + clk_disable(clk_i2s); + dai->private_data = NULL; + } } } @@ -336,6 +340,7 @@ static int pxa2xx_i2s_probe(struct platform_device *dev) return PTR_ERR(clk_i2s); pxa_i2s_dai.dev = &dev->dev; + pxa_i2s_dai.private_data = NULL; ret = snd_soc_register_dai(&pxa_i2s_dai); if (ret != 0) clk_put(clk_i2s); -- cgit v1.2.3 From 1dcf98ff8e2a4571a2accb852686023b47ca629a Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 1 Jul 2009 18:28:54 +0100 Subject: ASoC: Add WM8523 CODEC driver The WM8523 is a high performance stereo DAC with integral charge pump providing 2Vrms line driver outputs using a single 3.3V power supply rail. Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm8523.c | 755 ++++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm8523.h | 160 ++++++++++ 4 files changed, 921 insertions(+) create mode 100644 sound/soc/codecs/wm8523.c create mode 100644 sound/soc/codecs/wm8523.h (limited to 'sound/soc') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 021dbdfa5b9..68ea5b6648d 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -30,6 +30,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8350 if MFD_WM8350 select SND_SOC_WM8400 if MFD_WM8400 select SND_SOC_WM8510 if SND_SOC_I2C_AND_SPI + select SND_SOC_WM8523 if I2C select SND_SOC_WM8580 if I2C select SND_SOC_WM8728 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8731 if SND_SOC_I2C_AND_SPI @@ -130,6 +131,9 @@ config SND_SOC_WM8400 config SND_SOC_WM8510 tristate +config SND_SOC_WM8523 + tristate + config SND_SOC_WM8580 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index e520c2b7f0e..8ce28a34e8e 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -18,6 +18,7 @@ snd-soc-uda1380-objs := uda1380.o snd-soc-wm8350-objs := wm8350.o snd-soc-wm8400-objs := wm8400.o snd-soc-wm8510-objs := wm8510.o +snd-soc-wm8523-objs := wm8523.o snd-soc-wm8580-objs := wm8580.o snd-soc-wm8728-objs := wm8728.o snd-soc-wm8731-objs := wm8731.o @@ -56,6 +57,7 @@ obj-$(CONFIG_SND_SOC_UDA1380) += snd-soc-uda1380.o obj-$(CONFIG_SND_SOC_WM8350) += snd-soc-wm8350.o obj-$(CONFIG_SND_SOC_WM8400) += snd-soc-wm8400.o obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o +obj-$(CONFIG_SND_SOC_WM8523) += snd-soc-wm8523.o obj-$(CONFIG_SND_SOC_WM8580) += snd-soc-wm8580.o obj-$(CONFIG_SND_SOC_WM8728) += snd-soc-wm8728.o obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o diff --git a/sound/soc/codecs/wm8523.c b/sound/soc/codecs/wm8523.c new file mode 100644 index 00000000000..3b499ae7ce6 --- /dev/null +++ b/sound/soc/codecs/wm8523.c @@ -0,0 +1,755 @@ +/* + * wm8523.c -- WM8523 ALSA SoC Audio driver + * + * Copyright 2009 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8523.h" + +static struct snd_soc_codec *wm8523_codec; +struct snd_soc_codec_device soc_codec_dev_wm8523; + +#define WM8523_NUM_SUPPLIES 2 +static const char *wm8523_supply_names[WM8523_NUM_SUPPLIES] = { + "AVDD", + "LINEVDD", +}; + +#define WM8523_NUM_RATES 7 + +/* codec private data */ +struct wm8523_priv { + struct snd_soc_codec codec; + u16 reg_cache[WM8523_REGISTER_COUNT]; + struct regulator_bulk_data supplies[WM8523_NUM_SUPPLIES]; + unsigned int sysclk; + unsigned int rate_constraint_list[WM8523_NUM_RATES]; + struct snd_pcm_hw_constraint_list rate_constraint; +}; + +static const u16 wm8523_reg[WM8523_REGISTER_COUNT] = { + 0x8523, /* R0 - DEVICE_ID */ + 0x0001, /* R1 - REVISION */ + 0x0000, /* R2 - PSCTRL1 */ + 0x1812, /* R3 - AIF_CTRL1 */ + 0x0000, /* R4 - AIF_CTRL2 */ + 0x0001, /* R5 - DAC_CTRL3 */ + 0x0190, /* R6 - DAC_GAINL */ + 0x0190, /* R7 - DAC_GAINR */ + 0x0000, /* R8 - ZERO_DETECT */ +}; + +static int wm8523_volatile(unsigned int reg) +{ + switch (reg) { + case WM8523_DEVICE_ID: + case WM8523_REVISION: + return 1; + default: + return 0; + } +} + +static int wm8523_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + struct wm8523_priv *wm8523 = codec->private_data; + u8 data[3]; + + BUG_ON(reg > WM8523_MAX_REGISTER); + + data[0] = reg; + data[1] = (value >> 8) & 0x00ff; + data[2] = value & 0x00ff; + + if (!wm8523_volatile(reg)) + wm8523->reg_cache[reg] = value; + if (codec->hw_write(codec->control_data, data, 3) == 3) + return 0; + else + return -EIO; +} + +static int wm8523_reset(struct snd_soc_codec *codec) +{ + return wm8523_write(codec, WM8523_DEVICE_ID, 0); +} + +static unsigned int wm8523_read_hw(struct snd_soc_codec *codec, u8 reg) +{ + struct i2c_msg xfer[2]; + u16 data; + int ret; + struct i2c_client *i2c = codec->control_data; + + /* Write register */ + xfer[0].addr = i2c->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = ® + + /* Read data */ + xfer[1].addr = i2c->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 2; + xfer[1].buf = (u8 *)&data; + + ret = i2c_transfer(i2c->adapter, xfer, 2); + if (ret != 2) { + dev_err(codec->dev, "Failed to read 0x%x: %d\n", reg, ret); + return 0; + } + + return (data >> 8) | ((data & 0xff) << 8); +} + + +static unsigned int wm8523_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *reg_cache = codec->reg_cache; + + BUG_ON(reg > WM8523_MAX_REGISTER); + + if (wm8523_volatile(reg)) + return wm8523_read_hw(codec, reg); + else + return reg_cache[reg]; +} + +static const DECLARE_TLV_DB_SCALE(dac_tlv, -10000, 25, 0); + +static const char *wm8523_zd_count_text[] = { + "1024", + "2048", +}; + +static const struct soc_enum wm8523_zc_count = + SOC_ENUM_SINGLE(WM8523_ZERO_DETECT, 0, 2, wm8523_zd_count_text); + +static const struct snd_kcontrol_new wm8523_snd_controls[] = { +SOC_DOUBLE_R_TLV("Playback Volume", WM8523_DAC_GAINL, WM8523_DAC_GAINR, + 0, 448, 0, dac_tlv), +SOC_SINGLE("ZC Switch", WM8523_DAC_CTRL3, 4, 1, 0), +SOC_SINGLE("Playback Deemphasis Switch", WM8523_AIF_CTRL1, 8, 1, 0), +SOC_DOUBLE("Playback Switch", WM8523_DAC_CTRL3, 2, 3, 1, 1), +SOC_SINGLE("Volume Ramp Up Switch", WM8523_DAC_CTRL3, 1, 1, 0), +SOC_SINGLE("Volume Ramp Down Switch", WM8523_DAC_CTRL3, 0, 1, 0), +SOC_ENUM("Zero Detect Count", wm8523_zc_count), +}; + +static const struct snd_soc_dapm_widget wm8523_dapm_widgets[] = { +SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_OUTPUT("LINEVOUTL"), +SND_SOC_DAPM_OUTPUT("LINEVOUTR"), +}; + +static const struct snd_soc_dapm_route intercon[] = { + { "LINEVOUTL", NULL, "DAC" }, + { "LINEVOUTR", NULL, "DAC" }, +}; + +static int wm8523_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8523_dapm_widgets, + ARRAY_SIZE(wm8523_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static struct { + int value; + int ratio; +} lrclk_ratios[WM8523_NUM_RATES] = { + { 1, 128 }, + { 2, 192 }, + { 3, 256 }, + { 4, 384 }, + { 5, 512 }, + { 6, 768 }, + { 7, 1152 }, +}; + +static int wm8523_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8523_priv *wm8523 = codec->private_data; + + /* The set of sample rates that can be supported depends on the + * MCLK supplied to the CODEC - enforce this. + */ + if (!wm8523->sysclk) { + dev_err(codec->dev, + "No MCLK configured, call set_sysclk() on init\n"); + return -EINVAL; + } + + return 0; + snd_pcm_hw_constraint_list(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &wm8523->rate_constraint); + + return 0; +} + +static int wm8523_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + struct wm8523_priv *wm8523 = codec->private_data; + int i; + u16 aifctrl1 = wm8523_read(codec, WM8523_AIF_CTRL1); + u16 aifctrl2 = wm8523_read(codec, WM8523_AIF_CTRL2); + + /* Find a supported LRCLK ratio */ + for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) { + if (wm8523->sysclk / params_rate(params) == + lrclk_ratios[i].ratio) + break; + } + + /* Should never happen, should be handled by constraints */ + if (i == ARRAY_SIZE(lrclk_ratios)) { + dev_err(codec->dev, "MCLK/fs ratio %d unsupported\n", + wm8523->sysclk / params_rate(params)); + return -EINVAL; + } + + aifctrl2 &= ~WM8523_SR_MASK; + aifctrl2 |= lrclk_ratios[i].value; + + aifctrl1 &= ~WM8523_WL_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + aifctrl1 |= 0x8; + break; + case SNDRV_PCM_FORMAT_S24_LE: + aifctrl1 |= 0x10; + break; + case SNDRV_PCM_FORMAT_S32_LE: + aifctrl1 |= 0x18; + break; + } + + wm8523_write(codec, WM8523_AIF_CTRL1, aifctrl1); + wm8523_write(codec, WM8523_AIF_CTRL2, aifctrl2); + + return 0; +} + +static int wm8523_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8523_priv *wm8523 = codec->private_data; + unsigned int val; + int i; + + wm8523->sysclk = freq; + + wm8523->rate_constraint.count = 0; + for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) { + val = freq / lrclk_ratios[i].ratio; + /* Check that it's a standard rate since core can't + * cope with others and having the odd rates confuses + * constraint matching. + */ + switch (val) { + case 8000: + case 11025: + case 16000: + case 22050: + case 32000: + case 44100: + case 48000: + case 64000: + case 88200: + case 96000: + case 176400: + case 192000: + dev_dbg(codec->dev, "Supported sample rate: %dHz\n", + val); + wm8523->rate_constraint_list[i] = val; + wm8523->rate_constraint.count++; + break; + default: + dev_dbg(codec->dev, "Skipping sample rate: %dHz\n", + val); + } + } + + /* Need at least one supported rate... */ + if (wm8523->rate_constraint.count == 0) + return -EINVAL; + + return 0; +} + + +static int wm8523_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 aifctrl1 = wm8523_read(codec, WM8523_AIF_CTRL1); + + aifctrl1 &= ~(WM8523_BCLK_INV_MASK | WM8523_LRCLK_INV_MASK | + WM8523_FMT_MASK | WM8523_AIF_MSTR_MASK); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + aifctrl1 |= WM8523_AIF_MSTR; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + aifctrl1 |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + aifctrl1 |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + aifctrl1 |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + aifctrl1 |= 0x0023; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + aifctrl1 |= WM8523_BCLK_INV | WM8523_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aifctrl1 |= WM8523_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aifctrl1 |= WM8523_LRCLK_INV; + break; + default: + return -EINVAL; + } + + wm8523_write(codec, WM8523_AIF_CTRL1, aifctrl1); + + return 0; +} + +static int wm8523_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct wm8523_priv *wm8523 = codec->private_data; + int ret, i; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* Full power on */ + snd_soc_update_bits(codec, WM8523_PSCTRL1, + WM8523_SYS_ENA_MASK, 3); + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(wm8523->supplies), + wm8523->supplies); + if (ret != 0) { + dev_err(codec->dev, + "Failed to enable supplies: %d\n", + ret); + return ret; + } + + /* Initial power up */ + snd_soc_update_bits(codec, WM8523_PSCTRL1, + WM8523_SYS_ENA_MASK, 1); + + /* Sync back default/cached values */ + for (i = WM8523_AIF_CTRL1; + i < WM8523_MAX_REGISTER; i++) + wm8523_write(codec, i, wm8523->reg_cache[i]); + + + msleep(100); + } + + /* Power up to mute */ + snd_soc_update_bits(codec, WM8523_PSCTRL1, + WM8523_SYS_ENA_MASK, 2); + + break; + + case SND_SOC_BIAS_OFF: + /* The chip runs through the power down sequence for us. */ + snd_soc_update_bits(codec, WM8523_PSCTRL1, + WM8523_SYS_ENA_MASK, 0); + msleep(100); + + regulator_bulk_disable(ARRAY_SIZE(wm8523->supplies), + wm8523->supplies); + break; + } + codec->bias_level = level; + return 0; +} + +#define WM8523_RATES SNDRV_PCM_RATE_8000_192000 + +#define WM8523_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops wm8523_dai_ops = { + .startup = wm8523_startup, + .hw_params = wm8523_hw_params, + .set_sysclk = wm8523_set_dai_sysclk, + .set_fmt = wm8523_set_dai_fmt, +}; + +struct snd_soc_dai wm8523_dai = { + .name = "WM8523", + .playback = { + .stream_name = "Playback", + .channels_min = 2, /* Mono modes not yet supported */ + .channels_max = 2, + .rates = WM8523_RATES, + .formats = WM8523_FORMATS, + }, + .ops = &wm8523_dai_ops, +}; +EXPORT_SYMBOL_GPL(wm8523_dai); + +#ifdef CONFIG_PM +static int wm8523_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + wm8523_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8523_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + wm8523_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} +#else +#define wm8523_suspend NULL +#define wm8523_resume NULL +#endif + +static int wm8523_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (wm8523_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = wm8523_codec; + codec = wm8523_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + snd_soc_add_controls(codec, wm8523_snd_controls, + ARRAY_SIZE(wm8523_snd_controls)); + wm8523_add_widgets(codec); + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card: %d\n", ret); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + return ret; +} + +static int wm8523_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8523 = { + .probe = wm8523_probe, + .remove = wm8523_remove, + .suspend = wm8523_suspend, + .resume = wm8523_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8523); + +static int wm8523_register(struct wm8523_priv *wm8523) +{ + int ret; + struct snd_soc_codec *codec = &wm8523->codec; + int i; + + if (wm8523_codec) { + dev_err(codec->dev, "Another WM8523 is registered\n"); + return -EINVAL; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = wm8523; + codec->name = "WM8523"; + codec->owner = THIS_MODULE; + codec->read = wm8523_read; + codec->write = wm8523_write; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8523_set_bias_level; + codec->dai = &wm8523_dai; + codec->num_dai = 1; + codec->reg_cache_size = WM8523_REGISTER_COUNT; + codec->reg_cache = &wm8523->reg_cache; + + wm8523->rate_constraint.list = &wm8523->rate_constraint_list[0]; + wm8523->rate_constraint.count = + ARRAY_SIZE(wm8523->rate_constraint_list); + + memcpy(codec->reg_cache, wm8523_reg, sizeof(wm8523_reg)); + + for (i = 0; i < ARRAY_SIZE(wm8523->supplies); i++) + wm8523->supplies[i].supply = wm8523_supply_names[i]; + + ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8523->supplies), + wm8523->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to request supplies: %d\n", ret); + goto err; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8523->supplies), + wm8523->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); + goto err_get; + } + + ret = wm8523_read(codec, WM8523_DEVICE_ID); + if (ret < 0) { + dev_err(codec->dev, "Failed to read ID register\n"); + goto err_enable; + } + if (ret != wm8523_reg[WM8523_DEVICE_ID]) { + dev_err(codec->dev, "Device is not a WM8523, ID is %x\n", ret); + ret = -EINVAL; + goto err_enable; + } + + ret = wm8523_read(codec, WM8523_REVISION); + if (ret < 0) { + dev_err(codec->dev, "Failed to read revision register\n"); + goto err_enable; + } + dev_info(codec->dev, "revision %c\n", + (ret & WM8523_CHIP_REV_MASK) + 'A'); + + ret = wm8523_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset\n"); + goto err_enable; + } + + wm8523_dai.dev = codec->dev; + + /* Change some default settings - latch VU and enable ZC */ + wm8523->reg_cache[WM8523_DAC_GAINR] |= WM8523_DACR_VU; + wm8523->reg_cache[WM8523_DAC_CTRL3] |= WM8523_ZC; + + wm8523_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* Bias level configuration will have done an extra enable */ + regulator_bulk_disable(ARRAY_SIZE(wm8523->supplies), wm8523->supplies); + + wm8523_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } + + ret = snd_soc_register_dai(&wm8523_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + return ret; + } + + return 0; + +err_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8523->supplies), wm8523->supplies); +err_get: + regulator_bulk_free(ARRAY_SIZE(wm8523->supplies), wm8523->supplies); +err: + kfree(wm8523); + return ret; +} + +static void wm8523_unregister(struct wm8523_priv *wm8523) +{ + wm8523_set_bias_level(&wm8523->codec, SND_SOC_BIAS_OFF); + regulator_bulk_free(ARRAY_SIZE(wm8523->supplies), wm8523->supplies); + snd_soc_unregister_dai(&wm8523_dai); + snd_soc_unregister_codec(&wm8523->codec); + kfree(wm8523); + wm8523_codec = NULL; +} + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static __devinit int wm8523_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8523_priv *wm8523; + struct snd_soc_codec *codec; + + wm8523 = kzalloc(sizeof(struct wm8523_priv), GFP_KERNEL); + if (wm8523 == NULL) + return -ENOMEM; + + codec = &wm8523->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + + i2c_set_clientdata(i2c, wm8523); + codec->control_data = i2c; + + codec->dev = &i2c->dev; + + return wm8523_register(wm8523); +} + +static __devexit int wm8523_i2c_remove(struct i2c_client *client) +{ + struct wm8523_priv *wm8523 = i2c_get_clientdata(client); + wm8523_unregister(wm8523); + return 0; +} + +#ifdef CONFIG_PM +static int wm8523_i2c_suspend(struct i2c_client *i2c, pm_message_t msg) +{ + return snd_soc_suspend_device(&i2c->dev); +} + +static int wm8523_i2c_resume(struct i2c_client *i2c) +{ + return snd_soc_resume_device(&i2c->dev); +} +#else +#define wm8523_i2c_suspend NULL +#define wm8523_i2c_resume NULL +#endif + +static const struct i2c_device_id wm8523_i2c_id[] = { + { "wm8523", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8523_i2c_id); + +static struct i2c_driver wm8523_i2c_driver = { + .driver = { + .name = "WM8523", + .owner = THIS_MODULE, + }, + .probe = wm8523_i2c_probe, + .remove = __devexit_p(wm8523_i2c_remove), + .suspend = wm8523_i2c_suspend, + .resume = wm8523_i2c_resume, + .id_table = wm8523_i2c_id, +}; +#endif + +static int __init wm8523_modinit(void) +{ + int ret; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&wm8523_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8523 I2C driver: %d\n", + ret); + } +#endif + return 0; +} +module_init(wm8523_modinit); + +static void __exit wm8523_exit(void) +{ +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&wm8523_i2c_driver); +#endif +} +module_exit(wm8523_exit); + +MODULE_DESCRIPTION("ASoC WM8523 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8523.h b/sound/soc/codecs/wm8523.h new file mode 100644 index 00000000000..1aa9ce3e135 --- /dev/null +++ b/sound/soc/codecs/wm8523.h @@ -0,0 +1,160 @@ +/* + * wm8523.h -- WM8423 ASoC driver + * + * Copyright 2009 Wolfson Microelectronics, plc + * + * Author: Mark Brown + * + * Based on wm8753.h + * + * 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 _WM8523_H +#define _WM8523_H + +/* + * Register values. + */ +#define WM8523_DEVICE_ID 0x00 +#define WM8523_REVISION 0x01 +#define WM8523_PSCTRL1 0x02 +#define WM8523_AIF_CTRL1 0x03 +#define WM8523_AIF_CTRL2 0x04 +#define WM8523_DAC_CTRL3 0x05 +#define WM8523_DAC_GAINL 0x06 +#define WM8523_DAC_GAINR 0x07 +#define WM8523_ZERO_DETECT 0x08 + +#define WM8523_REGISTER_COUNT 9 +#define WM8523_MAX_REGISTER 0x08 + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - DEVICE_ID + */ +#define WM8523_CHIP_ID_MASK 0xFFFF /* CHIP_ID - [15:0] */ +#define WM8523_CHIP_ID_SHIFT 0 /* CHIP_ID - [15:0] */ +#define WM8523_CHIP_ID_WIDTH 16 /* CHIP_ID - [15:0] */ + +/* + * R1 (0x01) - REVISION + */ +#define WM8523_CHIP_REV_MASK 0x0007 /* CHIP_REV - [2:0] */ +#define WM8523_CHIP_REV_SHIFT 0 /* CHIP_REV - [2:0] */ +#define WM8523_CHIP_REV_WIDTH 3 /* CHIP_REV - [2:0] */ + +/* + * R2 (0x02) - PSCTRL1 + */ +#define WM8523_SYS_ENA_MASK 0x0003 /* SYS_ENA - [1:0] */ +#define WM8523_SYS_ENA_SHIFT 0 /* SYS_ENA - [1:0] */ +#define WM8523_SYS_ENA_WIDTH 2 /* SYS_ENA - [1:0] */ + +/* + * R3 (0x03) - AIF_CTRL1 + */ +#define WM8523_TDM_MODE_MASK 0x1800 /* TDM_MODE - [12:11] */ +#define WM8523_TDM_MODE_SHIFT 11 /* TDM_MODE - [12:11] */ +#define WM8523_TDM_MODE_WIDTH 2 /* TDM_MODE - [12:11] */ +#define WM8523_TDM_SLOT_MASK 0x0600 /* TDM_SLOT - [10:9] */ +#define WM8523_TDM_SLOT_SHIFT 9 /* TDM_SLOT - [10:9] */ +#define WM8523_TDM_SLOT_WIDTH 2 /* TDM_SLOT - [10:9] */ +#define WM8523_DEEMPH 0x0100 /* DEEMPH */ +#define WM8523_DEEMPH_MASK 0x0100 /* DEEMPH */ +#define WM8523_DEEMPH_SHIFT 8 /* DEEMPH */ +#define WM8523_DEEMPH_WIDTH 1 /* DEEMPH */ +#define WM8523_AIF_MSTR 0x0080 /* AIF_MSTR */ +#define WM8523_AIF_MSTR_MASK 0x0080 /* AIF_MSTR */ +#define WM8523_AIF_MSTR_SHIFT 7 /* AIF_MSTR */ +#define WM8523_AIF_MSTR_WIDTH 1 /* AIF_MSTR */ +#define WM8523_LRCLK_INV 0x0040 /* LRCLK_INV */ +#define WM8523_LRCLK_INV_MASK 0x0040 /* LRCLK_INV */ +#define WM8523_LRCLK_INV_SHIFT 6 /* LRCLK_INV */ +#define WM8523_LRCLK_INV_WIDTH 1 /* LRCLK_INV */ +#define WM8523_BCLK_INV 0x0020 /* BCLK_INV */ +#define WM8523_BCLK_INV_MASK 0x0020 /* BCLK_INV */ +#define WM8523_BCLK_INV_SHIFT 5 /* BCLK_INV */ +#define WM8523_BCLK_INV_WIDTH 1 /* BCLK_INV */ +#define WM8523_WL_MASK 0x0018 /* WL - [4:3] */ +#define WM8523_WL_SHIFT 3 /* WL - [4:3] */ +#define WM8523_WL_WIDTH 2 /* WL - [4:3] */ +#define WM8523_FMT_MASK 0x0007 /* FMT - [2:0] */ +#define WM8523_FMT_SHIFT 0 /* FMT - [2:0] */ +#define WM8523_FMT_WIDTH 3 /* FMT - [2:0] */ + +/* + * R4 (0x04) - AIF_CTRL2 + */ +#define WM8523_DAC_OP_MUX_MASK 0x00C0 /* DAC_OP_MUX - [7:6] */ +#define WM8523_DAC_OP_MUX_SHIFT 6 /* DAC_OP_MUX - [7:6] */ +#define WM8523_DAC_OP_MUX_WIDTH 2 /* DAC_OP_MUX - [7:6] */ +#define WM8523_BCLKDIV_MASK 0x0038 /* BCLKDIV - [5:3] */ +#define WM8523_BCLKDIV_SHIFT 3 /* BCLKDIV - [5:3] */ +#define WM8523_BCLKDIV_WIDTH 3 /* BCLKDIV - [5:3] */ +#define WM8523_SR_MASK 0x0007 /* SR - [2:0] */ +#define WM8523_SR_SHIFT 0 /* SR - [2:0] */ +#define WM8523_SR_WIDTH 3 /* SR - [2:0] */ + +/* + * R5 (0x05) - DAC_CTRL3 + */ +#define WM8523_ZC 0x0010 /* ZC */ +#define WM8523_ZC_MASK 0x0010 /* ZC */ +#define WM8523_ZC_SHIFT 4 /* ZC */ +#define WM8523_ZC_WIDTH 1 /* ZC */ +#define WM8523_DACR 0x0008 /* DACR */ +#define WM8523_DACR_MASK 0x0008 /* DACR */ +#define WM8523_DACR_SHIFT 3 /* DACR */ +#define WM8523_DACR_WIDTH 1 /* DACR */ +#define WM8523_DACL 0x0004 /* DACL */ +#define WM8523_DACL_MASK 0x0004 /* DACL */ +#define WM8523_DACL_SHIFT 2 /* DACL */ +#define WM8523_DACL_WIDTH 1 /* DACL */ +#define WM8523_VOL_UP_RAMP 0x0002 /* VOL_UP_RAMP */ +#define WM8523_VOL_UP_RAMP_MASK 0x0002 /* VOL_UP_RAMP */ +#define WM8523_VOL_UP_RAMP_SHIFT 1 /* VOL_UP_RAMP */ +#define WM8523_VOL_UP_RAMP_WIDTH 1 /* VOL_UP_RAMP */ +#define WM8523_VOL_DOWN_RAMP 0x0001 /* VOL_DOWN_RAMP */ +#define WM8523_VOL_DOWN_RAMP_MASK 0x0001 /* VOL_DOWN_RAMP */ +#define WM8523_VOL_DOWN_RAMP_SHIFT 0 /* VOL_DOWN_RAMP */ +#define WM8523_VOL_DOWN_RAMP_WIDTH 1 /* VOL_DOWN_RAMP */ + +/* + * R6 (0x06) - DAC_GAINL + */ +#define WM8523_DACL_VU 0x0200 /* DACL_VU */ +#define WM8523_DACL_VU_MASK 0x0200 /* DACL_VU */ +#define WM8523_DACL_VU_SHIFT 9 /* DACL_VU */ +#define WM8523_DACL_VU_WIDTH 1 /* DACL_VU */ +#define WM8523_DACL_VOL_MASK 0x01FF /* DACL_VOL - [8:0] */ +#define WM8523_DACL_VOL_SHIFT 0 /* DACL_VOL - [8:0] */ +#define WM8523_DACL_VOL_WIDTH 9 /* DACL_VOL - [8:0] */ + +/* + * R7 (0x07) - DAC_GAINR + */ +#define WM8523_DACR_VU 0x0200 /* DACR_VU */ +#define WM8523_DACR_VU_MASK 0x0200 /* DACR_VU */ +#define WM8523_DACR_VU_SHIFT 9 /* DACR_VU */ +#define WM8523_DACR_VU_WIDTH 1 /* DACR_VU */ +#define WM8523_DACR_VOL_MASK 0x01FF /* DACR_VOL - [8:0] */ +#define WM8523_DACR_VOL_SHIFT 0 /* DACR_VOL - [8:0] */ +#define WM8523_DACR_VOL_WIDTH 9 /* DACR_VOL - [8:0] */ + +/* + * R8 (0x08) - ZERO_DETECT + */ +#define WM8523_ZD_COUNT_MASK 0x0003 /* ZD_COUNT - [1:0] */ +#define WM8523_ZD_COUNT_SHIFT 0 /* ZD_COUNT - [1:0] */ +#define WM8523_ZD_COUNT_WIDTH 2 /* ZD_COUNT - [1:0] */ + +extern struct snd_soc_dai wm8523_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8523; + +#endif -- cgit v1.2.3 From 4e49ffd10f01950bd81e8ef368a1ffb484a7d759 Mon Sep 17 00:00:00 2001 From: "Candelaria Villareal, Jorge" Date: Wed, 1 Jul 2009 19:17:43 -0500 Subject: ASoC: TWL4030: Add EXTMUTE to reduce pop-noise effect According to TRM, an external FET controlled by a 1.8V output signal can be used to reduce the pop-noise heard when the audio amplifier is switched on. It is suggested that GPIO6 of TWL4030 be used, but any other gpio can be used instead. This is indicated in machine driver with the following twl4030_setup_data members: -hs_extmute. Set to 1 if board has support for EXTMUTE. -set_hs_extmute. Set to a callback funcion to control an external gpio line. Set to NULL if MUTE[GPIO6] pin is used. Codec driver takes care of enabling and disabling this output during the headset pop attenuation sequence. Also add a delay to let VMID settle in ramp up sequence. Signed-off-by: Jorge Eduardo Candelaria Acked-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 27 +++++++++++++++++++++++++++ sound/soc/codecs/twl4030.h | 2 ++ 2 files changed, 29 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 49ceb620678..6da94cadd15 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -620,6 +620,9 @@ static int handsfreerpga_event(struct snd_soc_dapm_widget *w, static void headset_ramp(struct snd_soc_codec *codec, int ramp) { + struct snd_soc_device *socdev = codec->socdev; + struct twl4030_setup_data *setup = socdev->codec_data; + unsigned char hs_gain, hs_pop; struct twl4030_priv *twl4030 = codec->private_data; /* Base values for ramp delay calculation: 2^19 - 2^26 */ @@ -629,6 +632,17 @@ static void headset_ramp(struct snd_soc_codec *codec, int ramp) hs_gain = twl4030_read_reg_cache(codec, TWL4030_REG_HS_GAIN_SET); hs_pop = twl4030_read_reg_cache(codec, TWL4030_REG_HS_POPN_SET); + /* Enable external mute control, this dramatically reduces + * the pop-noise */ + if (setup && setup->hs_extmute) { + if (setup->set_hs_extmute) { + setup->set_hs_extmute(1); + } else { + hs_pop |= TWL4030_EXTMUTE; + twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); + } + } + if (ramp) { /* Headset ramp-up according to the TRM */ hs_pop |= TWL4030_VMID_EN; @@ -636,6 +650,9 @@ static void headset_ramp(struct snd_soc_codec *codec, int ramp) twl4030_write(codec, TWL4030_REG_HS_GAIN_SET, hs_gain); hs_pop |= TWL4030_RAMP_EN; twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); + /* Wait ramp delay time + 1, so the VMID can settle */ + mdelay((ramp_base[(hs_pop & TWL4030_RAMP_DELAY) >> 2] / + twl4030->sysclk) + 1); } else { /* Headset ramp-down _not_ according to * the TRM, but in a way that it is working */ @@ -652,6 +669,16 @@ static void headset_ramp(struct snd_soc_codec *codec, int ramp) hs_pop &= ~TWL4030_VMID_EN; twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); } + + /* Disable external mute */ + if (setup && setup->hs_extmute) { + if (setup->set_hs_extmute) { + setup->set_hs_extmute(0); + } else { + hs_pop &= ~TWL4030_EXTMUTE; + twl4030_write(codec, TWL4030_REG_HS_POPN_SET, hs_pop); + } + } } static int headsetlpga_event(struct snd_soc_dapm_widget *w, diff --git a/sound/soc/codecs/twl4030.h b/sound/soc/codecs/twl4030.h index fe5f395d9e4..2b4bfa23f98 100644 --- a/sound/soc/codecs/twl4030.h +++ b/sound/soc/codecs/twl4030.h @@ -274,6 +274,8 @@ extern struct snd_soc_codec_device soc_codec_dev_twl4030; struct twl4030_setup_data { unsigned int ramp_delay_value; unsigned int sysclk; + unsigned int hs_extmute:1; + void (*set_hs_extmute)(int mute); }; #endif /* End of __TWL4030_AUDIO_H__ */ -- cgit v1.2.3 From 1df892cba45f9856d369a6a317ad2d1e44bca423 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 3 Jul 2009 10:33:39 +0100 Subject: ASoC: Fix register cache initialisation for WM8753 The wrong register cache variable was being used to provide the size for the memcpy(), resulting in a copy of only a void * of data. Reported-by: Lars-Peter Clausen Signed-off-by: Mark Brown Cc: stable@kernel.org --- sound/soc/codecs/wm8753.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index d28eeaceb85..e06b0cfe4f2 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -1664,7 +1664,7 @@ static int wm8753_register(struct wm8753_priv *wm8753) codec->reg_cache = &wm8753->reg_cache; codec->private_data = wm8753; - memcpy(codec->reg_cache, wm8753_reg, sizeof(codec->reg_cache)); + memcpy(codec->reg_cache, wm8753_reg, sizeof(wm8753->reg_cache)); INIT_DELAYED_WORK(&codec->delayed_work, wm8753_work); ret = wm8753_reset(codec); -- cgit v1.2.3 From 07573534b0b030226ee5ab560e53aac7e6c0dd84 Mon Sep 17 00:00:00 2001 From: Grant Likely Date: Thu, 2 Jul 2009 11:57:19 -0600 Subject: ASoC: Fix mpc5200-psc-ac97 to ensure the data ready bit is cleared When doing register reads, it is possible for there to be a stale data ready bit set which will cause subsequent reads to return prematurely with incorrect data. This patch fixes the issues by ensuring stale data is cleared before starting another transaction. Signed-off-by: Grant Likely Acked-by: Jon Smirl Signed-off-by: Mark Brown --- sound/soc/fsl/mpc5200_psc_ac97.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/fsl/mpc5200_psc_ac97.c b/sound/soc/fsl/mpc5200_psc_ac97.c index 794a247b3eb..9b8503f2d68 100644 --- a/sound/soc/fsl/mpc5200_psc_ac97.c +++ b/sound/soc/fsl/mpc5200_psc_ac97.c @@ -41,6 +41,10 @@ static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg) pr_err("timeout on ac97 bus (rdy)\n"); return -ENODEV; } + + /* Force clear the data valid bit */ + in_be32(&psc_dma->psc_regs->ac97_data); + /* Send the read */ out_be32(&psc_dma->psc_regs->ac97_cmd, (1<<31) | ((reg & 0x7f) << 24)); -- cgit v1.2.3 From 0827d6ba0b76be398a3c4298afd41f4965d2cdcb Mon Sep 17 00:00:00 2001 From: Grant Likely Date: Thu, 2 Jul 2009 11:57:25 -0600 Subject: ASoC: add locking to mpc5200-psc-ac97 driver AC97 bus register read/write hooks need to provide locking, but the mpc5200-psc-ac97 driver does not. This patch adds a mutex around the register access routines. Signed-off-by: Grant Likely Acked-by: Jon Smirl Signed-off-by: Mark Brown --- sound/soc/fsl/mpc5200_dma.c | 1 + sound/soc/fsl/mpc5200_dma.h | 1 + sound/soc/fsl/mpc5200_psc_ac97.c | 13 ++++++++++++- 3 files changed, 14 insertions(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/fsl/mpc5200_dma.c b/sound/soc/fsl/mpc5200_dma.c index efec33a1c5b..f0a2d407199 100644 --- a/sound/soc/fsl/mpc5200_dma.c +++ b/sound/soc/fsl/mpc5200_dma.c @@ -456,6 +456,7 @@ int mpc5200_audio_dma_create(struct of_device *op) return -ENODEV; spin_lock_init(&psc_dma->lock); + mutex_init(&psc_dma->mutex); psc_dma->id = be32_to_cpu(*prop); psc_dma->irq = irq; psc_dma->psc_regs = regs; diff --git a/sound/soc/fsl/mpc5200_dma.h b/sound/soc/fsl/mpc5200_dma.h index 2000803f06a..8d396bb9d9f 100644 --- a/sound/soc/fsl/mpc5200_dma.h +++ b/sound/soc/fsl/mpc5200_dma.h @@ -55,6 +55,7 @@ struct psc_dma { unsigned int irq; struct device *dev; spinlock_t lock; + struct mutex mutex; u32 sicr; uint sysclk; int imr; diff --git a/sound/soc/fsl/mpc5200_psc_ac97.c b/sound/soc/fsl/mpc5200_psc_ac97.c index 9b8503f2d68..7eb549985d4 100644 --- a/sound/soc/fsl/mpc5200_psc_ac97.c +++ b/sound/soc/fsl/mpc5200_psc_ac97.c @@ -34,11 +34,14 @@ static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg) int status; unsigned int val; + mutex_lock(&psc_dma->mutex); + /* Wait for command send status zero = ready */ status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & MPC52xx_PSC_SR_CMDSEND), 100, 0); if (status == 0) { pr_err("timeout on ac97 bus (rdy)\n"); + mutex_unlock(&psc_dma->mutex); return -ENODEV; } @@ -54,16 +57,19 @@ static unsigned short psc_ac97_read(struct snd_ac97 *ac97, unsigned short reg) if (status == 0) { pr_err("timeout on ac97 read (val) %x\n", in_be16(&psc_dma->psc_regs->sr_csr.status)); + mutex_unlock(&psc_dma->mutex); return -ENODEV; } /* Get the data */ val = in_be32(&psc_dma->psc_regs->ac97_data); if (((val >> 24) & 0x7f) != reg) { pr_err("reg echo error on ac97 read\n"); + mutex_unlock(&psc_dma->mutex); return -ENODEV; } val = (val >> 8) & 0xffff; + mutex_unlock(&psc_dma->mutex); return (unsigned short) val; } @@ -72,16 +78,21 @@ static void psc_ac97_write(struct snd_ac97 *ac97, { int status; + mutex_lock(&psc_dma->mutex); + /* Wait for command status zero = ready */ status = spin_event_timeout(!(in_be16(&psc_dma->psc_regs->sr_csr.status) & MPC52xx_PSC_SR_CMDSEND), 100, 0); if (status == 0) { pr_err("timeout on ac97 bus (write)\n"); - return; + goto out; } /* Write data */ out_be32(&psc_dma->psc_regs->ac97_cmd, ((reg & 0x7f) << 24) | (val << 8)); + + out: + mutex_unlock(&psc_dma->mutex); } static void psc_ac97_warm_reset(struct snd_ac97 *ac97) -- cgit v1.2.3 From 1b6ebdd7bbdb3fdcab82c8056dbae0c23dbaaf4d Mon Sep 17 00:00:00 2001 From: "Candelaria Villareal, Jorge" Date: Thu, 2 Jul 2009 19:04:07 -0500 Subject: ASoC: Zoom2: Update twl4030_setup_data parameters Add support for EXTMUTE in Zoom2 machine driver. This is necessary to further reduce pop noise problem. Signal EXTMUTE is connected to signal GPIO 153 in Zoom2 board. In addition, change ramp delay value to 3 (218/161/109 ms). With previous ramp delay value, pop noise was louder. With a longer value the beep tone can be observed. Signed-off-by: Jorge Eduardo Candelaria Acked-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/omap/zoom2.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/omap/zoom2.c b/sound/soc/omap/zoom2.c index 3de6d2bd390..f90b45f5622 100644 --- a/sound/soc/omap/zoom2.c +++ b/sound/soc/omap/zoom2.c @@ -35,7 +35,8 @@ #include "omap-pcm.h" #include "../codecs/twl4030.h" -#define ZOOM2_HEADSET_MUX_GPIO (OMAP_MAX_GPIO_LINES + 15) +#define ZOOM2_HEADSET_MUX_GPIO (OMAP_MAX_GPIO_LINES + 15) +#define ZOOM2_HEADSET_EXTMUTE_GPIO 153 static int zoom2_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) @@ -234,10 +235,18 @@ static struct snd_soc_card snd_soc_zoom2 = { .num_links = ARRAY_SIZE(zoom2_dai), }; +/* EXTMUTE callback function */ +void zoom2_set_hs_extmute(int mute) +{ + gpio_set_value(ZOOM2_HEADSET_EXTMUTE_GPIO, mute); +} + /* twl4030 setup */ static struct twl4030_setup_data twl4030_setup = { - .ramp_delay_value = 2, /* 81 ms */ + .ramp_delay_value = 3, /* 161 ms */ .sysclk = 26000, + .hs_extmute = 1, + .set_hs_extmute = zoom2_set_hs_extmute, }; /* Audio subsystem */ @@ -277,6 +286,9 @@ static int __init zoom2_soc_init(void) BUG_ON(gpio_request(ZOOM2_HEADSET_MUX_GPIO, "hs_mux") < 0); gpio_direction_output(ZOOM2_HEADSET_MUX_GPIO, 0); + BUG_ON(gpio_request(ZOOM2_HEADSET_EXTMUTE_GPIO, "ext_mute") < 0); + gpio_direction_output(ZOOM2_HEADSET_EXTMUTE_GPIO, 0); + return 0; err1: @@ -290,6 +302,7 @@ module_init(zoom2_soc_init); static void __exit zoom2_soc_exit(void) { gpio_free(ZOOM2_HEADSET_MUX_GPIO); + gpio_free(ZOOM2_HEADSET_EXTMUTE_GPIO); platform_device_unregister(zoom2_snd_device); } -- cgit v1.2.3 From 6814044324e745e7c55aaf4bd47bdfd9a51d842a Mon Sep 17 00:00:00 2001 From: "Lopez Cruz, Misael" Date: Fri, 3 Jul 2009 02:21:39 -0500 Subject: ASoC: TWL4030: Add tristate callbacks for HiFi and Voice Add "set_tristate" callbacks for HiFi and Voice DAIs. Machine drivers can enable and disable tristate for each DAI with "snd_soc_dai_set_tristate" function. Signed-off-by: Misael Lopez Cruz Acked-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 6da94cadd15..818fb37bd7f 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -1849,6 +1849,19 @@ static int twl4030_set_dai_fmt(struct snd_soc_dai *codec_dai, return 0; } +static int twl4030_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + struct snd_soc_codec *codec = dai->codec; + u8 reg = twl4030_read_reg_cache(codec, TWL4030_REG_AUDIO_IF); + + if (tristate) + reg |= TWL4030_AIF_TRI_EN; + else + reg &= ~TWL4030_AIF_TRI_EN; + + return twl4030_write(codec, TWL4030_REG_AUDIO_IF, reg); +} + /* In case of voice mode, the RX1 L(VRX) for downlink and the TX2 L/R * (VTXL, VTXR) for uplink has to be enabled/disabled. */ static void twl4030_voice_enable(struct snd_soc_codec *codec, int direction, @@ -2023,6 +2036,19 @@ static int twl4030_voice_set_dai_fmt(struct snd_soc_dai *codec_dai, return 0; } +static int twl4030_voice_set_tristate(struct snd_soc_dai *dai, int tristate) +{ + struct snd_soc_codec *codec = dai->codec; + u8 reg = twl4030_read_reg_cache(codec, TWL4030_REG_VOICE_IF); + + if (tristate) + reg |= TWL4030_VIF_TRI_EN; + else + reg &= ~TWL4030_VIF_TRI_EN; + + return twl4030_write(codec, TWL4030_REG_VOICE_IF, reg); +} + #define TWL4030_RATES (SNDRV_PCM_RATE_8000_48000) #define TWL4030_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FORMAT_S24_LE) @@ -2032,6 +2058,7 @@ static struct snd_soc_dai_ops twl4030_dai_ops = { .hw_params = twl4030_hw_params, .set_sysclk = twl4030_set_dai_sysclk, .set_fmt = twl4030_set_dai_fmt, + .set_tristate = twl4030_set_tristate, }; static struct snd_soc_dai_ops twl4030_dai_voice_ops = { @@ -2040,6 +2067,7 @@ static struct snd_soc_dai_ops twl4030_dai_voice_ops = { .hw_params = twl4030_voice_hw_params, .set_sysclk = twl4030_voice_set_dai_sysclk, .set_fmt = twl4030_voice_set_dai_fmt, + .set_tristate = twl4030_voice_set_tristate, }; struct snd_soc_dai twl4030_dai[] = { -- cgit v1.2.3 From 637a935aaba2f05e2178c9d1b714d7a2c36c8b44 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Fri, 3 Jul 2009 01:04:16 +0200 Subject: ASoC: Fix wm8753 register cache size and initialization Register cache space was not being allocated for the final register, causing bugs when it was used. Allocate space for it. Also ensure that the final register is displayed in sysfs. [Commit message rewritten to document actual issue. -- broonie] Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown --- sound/soc/codecs/wm8753.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index e06b0cfe4f2..49c4b2898af 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -79,7 +79,7 @@ static const u16 wm8753_reg[] = { 0x0097, 0x0097, 0x0000, 0x0004, 0x0000, 0x0083, 0x0024, 0x01ba, 0x0000, 0x0083, 0x0024, 0x01ba, - 0x0000, 0x0000 + 0x0000, 0x0000, 0x0000 }; /* codec private data */ @@ -1660,7 +1660,7 @@ static int wm8753_register(struct wm8753_priv *wm8753) codec->set_bias_level = wm8753_set_bias_level; codec->dai = wm8753_dai; codec->num_dai = 2; - codec->reg_cache_size = ARRAY_SIZE(wm8753->reg_cache); + codec->reg_cache_size = ARRAY_SIZE(wm8753->reg_cache) + 1; codec->reg_cache = &wm8753->reg_cache; codec->private_data = wm8753; -- cgit v1.2.3 From 35cf63583d8d81d8ac261e944db9eeb44a60692d Mon Sep 17 00:00:00 2001 From: Troy Kisky Date: Sat, 4 Jul 2009 19:29:51 -0700 Subject: ASoC: DaVinci: i2s, remove MOD_REG_BIT macro No functional changes. Rename variable w to something more meaningful. Remove code obfuscating macro MOD_REG_BIT. Signed-off-by: Troy Kisky Signed-off-by: Mark Brown --- sound/soc/davinci/davinci-i2s.c | 96 +++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 52 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c index b1ea52fc83c..bf5ec4b2f7f 100644 --- a/sound/soc/davinci/davinci-i2s.c +++ b/sound/soc/davinci/davinci-i2s.c @@ -85,14 +85,6 @@ #define DAVINCI_MCBSP_PCR_FSRM (1 << 10) #define DAVINCI_MCBSP_PCR_FSXM (1 << 11) -#define MOD_REG_BIT(val, mask, set) do { \ - if (set) { \ - val |= mask; \ - } else { \ - val &= ~mask; \ - } \ -} while (0) - enum { DAVINCI_MCBSP_WORD_8 = 0, DAVINCI_MCBSP_WORD_12, @@ -133,13 +125,13 @@ static void davinci_mcbsp_start(struct snd_pcm_substream *substream) struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_platform *platform = socdev->card->platform; - u32 w; + u32 spcr; int ret; /* Start the sample generator and enable transmitter/receiver */ - w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); - MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_GRST, 1); - davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, w); + spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); + spcr |= DAVINCI_MCBSP_SPCR_GRST; + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { /* Stop the DMA to avoid data loss */ @@ -152,17 +144,17 @@ static void davinci_mcbsp_start(struct snd_pcm_substream *substream) } /* Enable the transmitter */ - w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); - MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_XRST, 1); - davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, w); + spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); + spcr |= DAVINCI_MCBSP_SPCR_XRST; + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); /* wait for any unexpected frame sync error to occur */ udelay(100); /* Disable the transmitter to clear any outstanding XSYNCERR */ - w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); - MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_XRST, 0); - davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, w); + spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); + spcr &= ~DAVINCI_MCBSP_SPCR_XRST; + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); /* Restart the DMA */ if (platform->pcm_ops->trigger) { @@ -172,40 +164,39 @@ static void davinci_mcbsp_start(struct snd_pcm_substream *substream) printk(KERN_DEBUG "Playback DMA start failed\n"); } /* Enable the transmitter */ - w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); - MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_XRST, 1); - davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, w); + spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); + spcr |= DAVINCI_MCBSP_SPCR_XRST; + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); } else { /* Enable the reciever */ - w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); - MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_RRST, 1); - davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, w); + spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); + spcr |= DAVINCI_MCBSP_SPCR_RRST; + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); } /* Start frame sync */ - w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); - MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_FRST, 1); - davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, w); + spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); + spcr |= DAVINCI_MCBSP_SPCR_FRST; + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); } static void davinci_mcbsp_stop(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data; - u32 w; + u32 spcr; /* Reset transmitter/receiver and sample rate/frame sync generators */ - w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); - MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_GRST | - DAVINCI_MCBSP_SPCR_FRST, 0); + spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); + spcr &= ~(DAVINCI_MCBSP_SPCR_GRST | DAVINCI_MCBSP_SPCR_FRST); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_XRST, 0); + spcr &= ~DAVINCI_MCBSP_SPCR_XRST; else - MOD_REG_BIT(w, DAVINCI_MCBSP_SPCR_RRST, 0); - davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, w); + spcr &= ~DAVINCI_MCBSP_SPCR_RRST; + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); } static int davinci_i2s_startup(struct snd_pcm_substream *substream, @@ -358,25 +349,26 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream, struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data; struct snd_interval *i = NULL; int mcbsp_word_length; - u32 w; + unsigned int rcr, xcr, srgr; + u32 spcr; /* general line settings */ - w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); + spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { - w |= DAVINCI_MCBSP_SPCR_RINTM(3) | DAVINCI_MCBSP_SPCR_FREE; - davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, w); + spcr |= DAVINCI_MCBSP_SPCR_RINTM(3) | DAVINCI_MCBSP_SPCR_FREE; + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); } else { - w |= DAVINCI_MCBSP_SPCR_XINTM(3) | DAVINCI_MCBSP_SPCR_FREE; - davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, w); + spcr |= DAVINCI_MCBSP_SPCR_XINTM(3) | DAVINCI_MCBSP_SPCR_FREE; + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); } i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_SAMPLE_BITS); - w = DAVINCI_MCBSP_SRGR_FSGM; - MOD_REG_BIT(w, DAVINCI_MCBSP_SRGR_FWID(snd_interval_value(i) - 1), 1); + srgr = DAVINCI_MCBSP_SRGR_FSGM; + srgr |= DAVINCI_MCBSP_SRGR_FWID(snd_interval_value(i) - 1); i = hw_param_interval(params, SNDRV_PCM_HW_PARAM_FRAME_BITS); - MOD_REG_BIT(w, DAVINCI_MCBSP_SRGR_FPER(snd_interval_value(i) - 1), 1); - davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, w); + srgr |= DAVINCI_MCBSP_SRGR_FPER(snd_interval_value(i) - 1); + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, srgr); /* Determine xfer data type */ switch (params_format(params)) { @@ -398,16 +390,16 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream, } if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { - w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_RCR_REG); - MOD_REG_BIT(w, DAVINCI_MCBSP_RCR_RWDLEN1(mcbsp_word_length) | - DAVINCI_MCBSP_RCR_RWDLEN2(mcbsp_word_length), 1); - davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_RCR_REG, w); + rcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_RCR_REG); + rcr |= DAVINCI_MCBSP_RCR_RWDLEN1(mcbsp_word_length) | + DAVINCI_MCBSP_RCR_RWDLEN2(mcbsp_word_length); + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_RCR_REG, rcr); } else { - w = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_XCR_REG); - MOD_REG_BIT(w, DAVINCI_MCBSP_XCR_XWDLEN1(mcbsp_word_length) | - DAVINCI_MCBSP_XCR_XWDLEN2(mcbsp_word_length), 1); - davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_XCR_REG, w); + xcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_XCR_REG); + xcr |= DAVINCI_MCBSP_XCR_XWDLEN1(mcbsp_word_length) | + DAVINCI_MCBSP_XCR_XWDLEN2(mcbsp_word_length); + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_XCR_REG, xcr); } return 0; -- cgit v1.2.3 From c392bec716431d77e503345117a3e7ad3face677 Mon Sep 17 00:00:00 2001 From: Troy Kisky Date: Sat, 4 Jul 2009 19:29:52 -0700 Subject: ASoC: DaVinci: i2s toggle clock to complete reset Add toggle_clock function to complete i2s reset earlier. Signed-off-by: Troy Kisky Signed-off-by: Mark Brown --- sound/soc/davinci/davinci-i2s.c | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c index bf5ec4b2f7f..dd44167a90f 100644 --- a/sound/soc/davinci/davinci-i2s.c +++ b/sound/soc/davinci/davinci-i2s.c @@ -104,6 +104,7 @@ static struct davinci_pcm_dma_params davinci_i2s_pcm_in = { struct davinci_mcbsp_dev { void __iomem *base; + u32 pcr; struct clk *clk; struct davinci_pcm_dma_params *dma_params[2]; }; @@ -119,17 +120,34 @@ static inline u32 davinci_mcbsp_read_reg(struct davinci_mcbsp_dev *dev, int reg) return __raw_readl(dev->base + reg); } +static void toggle_clock(struct davinci_mcbsp_dev *dev, int playback) +{ + u32 m = playback ? DAVINCI_MCBSP_PCR_CLKXP : DAVINCI_MCBSP_PCR_CLKRP; + /* The clock needs to toggle to complete reset. + * So, fake it by toggling the clk polarity. + */ + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG, dev->pcr ^ m); + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG, dev->pcr); +} + static void davinci_mcbsp_start(struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_platform *platform = socdev->card->platform; + int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); u32 spcr; int ret; - - /* Start the sample generator and enable transmitter/receiver */ + u32 mask = playback ? DAVINCI_MCBSP_SPCR_XRST : DAVINCI_MCBSP_SPCR_RRST; spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); + if (spcr & mask) { + /* start off disabled */ + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, + spcr & ~mask); + toggle_clock(dev, playback); + } + /* Start the sample generator and enable transmitter/receiver */ spcr |= DAVINCI_MCBSP_SPCR_GRST; davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); @@ -155,6 +173,7 @@ static void davinci_mcbsp_start(struct snd_pcm_substream *substream) spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); spcr &= ~DAVINCI_MCBSP_SPCR_XRST; davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); + toggle_clock(dev, playback); /* Restart the DMA */ if (platform->pcm_ops->trigger) { @@ -188,15 +207,14 @@ static void davinci_mcbsp_stop(struct snd_pcm_substream *substream) struct snd_soc_pcm_runtime *rtd = substream->private_data; struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data; u32 spcr; + int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); /* Reset transmitter/receiver and sample rate/frame sync generators */ spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); spcr &= ~(DAVINCI_MCBSP_SPCR_GRST | DAVINCI_MCBSP_SPCR_FRST); - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - spcr &= ~DAVINCI_MCBSP_SPCR_XRST; - else - spcr &= ~DAVINCI_MCBSP_SPCR_RRST; + spcr &= playback ? ~DAVINCI_MCBSP_SPCR_XRST : ~DAVINCI_MCBSP_SPCR_RRST; davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); + toggle_clock(dev, playback); } static int davinci_i2s_startup(struct snd_pcm_substream *substream, @@ -334,6 +352,7 @@ static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, return -EINVAL; } davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, srgr); + dev->pcr = pcr; davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG, pcr); davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_RCR_REG, rcr); davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_XCR_REG, xcr); -- cgit v1.2.3 From f9af37cc632b59c39768329797443308ad6b4137 Mon Sep 17 00:00:00 2001 From: Troy Kisky Date: Sat, 4 Jul 2009 19:29:53 -0700 Subject: ASoc: DaVinci: i2s, minor cleanup Add davinci_mcbsp_dev as argument to davinci_mcbsp_start and davinci_mcbsp_stop. Signed-off-by: Troy Kisky Signed-off-by: Mark Brown --- sound/soc/davinci/davinci-i2s.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c index dd44167a90f..f7d7e4c3076 100644 --- a/sound/soc/davinci/davinci-i2s.c +++ b/sound/soc/davinci/davinci-i2s.c @@ -130,10 +130,10 @@ static void toggle_clock(struct davinci_mcbsp_dev *dev, int playback) davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG, dev->pcr); } -static void davinci_mcbsp_start(struct snd_pcm_substream *substream) +static void davinci_mcbsp_start(struct davinci_mcbsp_dev *dev, + struct snd_pcm_substream *substream) { struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_platform *platform = socdev->card->platform; int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); @@ -202,12 +202,9 @@ static void davinci_mcbsp_start(struct snd_pcm_substream *substream) davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); } -static void davinci_mcbsp_stop(struct snd_pcm_substream *substream) +static void davinci_mcbsp_stop(struct davinci_mcbsp_dev *dev, int playback) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data; u32 spcr; - int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); /* Reset transmitter/receiver and sample rate/frame sync generators */ spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); @@ -427,18 +424,21 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream, static int davinci_i2s_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data; int ret = 0; + int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - davinci_mcbsp_start(substream); + davinci_mcbsp_start(dev, substream); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - davinci_mcbsp_stop(substream); + davinci_mcbsp_stop(dev, playback); break; default: ret = -EINVAL; -- cgit v1.2.3 From eba575c30b93755dc0ee5e9adbb5b06d3289b62a Mon Sep 17 00:00:00 2001 From: Troy Kisky Date: Sat, 4 Jul 2009 19:29:54 -0700 Subject: ASoC: DaVinci: i2s cleanup Move variable declaration closer to use. Signed-off-by: Troy Kisky Signed-off-by: Mark Brown --- sound/soc/davinci/davinci-i2s.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c index f7d7e4c3076..73a0fd8e182 100644 --- a/sound/soc/davinci/davinci-i2s.c +++ b/sound/soc/davinci/davinci-i2s.c @@ -138,7 +138,6 @@ static void davinci_mcbsp_start(struct davinci_mcbsp_dev *dev, struct snd_soc_platform *platform = socdev->card->platform; int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); u32 spcr; - int ret; u32 mask = playback ? DAVINCI_MCBSP_SPCR_XRST : DAVINCI_MCBSP_SPCR_RRST; spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); if (spcr & mask) { @@ -155,7 +154,7 @@ static void davinci_mcbsp_start(struct davinci_mcbsp_dev *dev, /* Stop the DMA to avoid data loss */ /* while the transmitter is out of reset to handle XSYNCERR */ if (platform->pcm_ops->trigger) { - ret = platform->pcm_ops->trigger(substream, + int ret = platform->pcm_ops->trigger(substream, SNDRV_PCM_TRIGGER_STOP); if (ret < 0) printk(KERN_DEBUG "Playback DMA stop failed\n"); @@ -177,7 +176,7 @@ static void davinci_mcbsp_start(struct davinci_mcbsp_dev *dev, /* Restart the DMA */ if (platform->pcm_ops->trigger) { - ret = platform->pcm_ops->trigger(substream, + int ret = platform->pcm_ops->trigger(substream, SNDRV_PCM_TRIGGER_START); if (ret < 0) printk(KERN_DEBUG "Playback DMA start failed\n"); -- cgit v1.2.3 From 1bef449989d0866a7db366a8153135cfa3d5cad9 Mon Sep 17 00:00:00 2001 From: Troy Kisky Date: Sat, 4 Jul 2009 19:29:55 -0700 Subject: ASoC: DaVinci: i2s, only start sample generator if needed Only start sample generator if needed, and more cleanup on davinci_mcbsp_start. Signed-off-by: Troy Kisky Signed-off-by: Mark Brown --- sound/soc/davinci/davinci-i2s.c | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c index 73a0fd8e182..42609654b5e 100644 --- a/sound/soc/davinci/davinci-i2s.c +++ b/sound/soc/davinci/davinci-i2s.c @@ -146,11 +146,14 @@ static void davinci_mcbsp_start(struct davinci_mcbsp_dev *dev, spcr & ~mask); toggle_clock(dev, playback); } - /* Start the sample generator and enable transmitter/receiver */ - spcr |= DAVINCI_MCBSP_SPCR_GRST; - davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); + if (dev->pcr & (DAVINCI_MCBSP_PCR_FSXM | DAVINCI_MCBSP_PCR_FSRM | + DAVINCI_MCBSP_PCR_CLKXM | DAVINCI_MCBSP_PCR_CLKRM)) { + /* Start the sample generator */ + spcr |= DAVINCI_MCBSP_SPCR_GRST; + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); + } - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (playback) { /* Stop the DMA to avoid data loss */ /* while the transmitter is out of reset to handle XSYNCERR */ if (platform->pcm_ops->trigger) { @@ -181,23 +184,16 @@ static void davinci_mcbsp_start(struct davinci_mcbsp_dev *dev, if (ret < 0) printk(KERN_DEBUG "Playback DMA start failed\n"); } - /* Enable the transmitter */ - spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); - spcr |= DAVINCI_MCBSP_SPCR_XRST; - davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); - - } else { - - /* Enable the reciever */ - spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); - spcr |= DAVINCI_MCBSP_SPCR_RRST; - davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); } - - /* Start frame sync */ + /* Enable transmitter or receiver */ spcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_SPCR_REG); - spcr |= DAVINCI_MCBSP_SPCR_FRST; + spcr |= mask; + + if (dev->pcr & (DAVINCI_MCBSP_PCR_FSXM | DAVINCI_MCBSP_PCR_FSRM)) { + /* Start frame sync */ + spcr |= DAVINCI_MCBSP_SPCR_FRST; + } davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SPCR_REG, spcr); } -- cgit v1.2.3 From 9333b594a08f2b2d8b7d96260164817acdbc5cab Mon Sep 17 00:00:00 2001 From: Troy Kisky Date: Sat, 4 Jul 2009 19:29:56 -0700 Subject: ASoC: DaVinci: i2s, minor cleanup of davinci_i2s_startup Save a few lines of code. Signed-off-by: Troy Kisky Signed-off-by: Mark Brown --- sound/soc/davinci/davinci-i2s.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c index 42609654b5e..5b31b56e2e9 100644 --- a/sound/soc/davinci/davinci-i2s.c +++ b/sound/soc/davinci/davinci-i2s.c @@ -210,14 +210,10 @@ static void davinci_mcbsp_stop(struct davinci_mcbsp_dev *dev, int playback) } static int davinci_i2s_startup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) + struct snd_soc_dai *cpu_dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; - struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data; - + struct davinci_mcbsp_dev *dev = cpu_dai->private_data; cpu_dai->dma_data = dev->dma_params[substream->stream]; - return 0; } -- cgit v1.2.3 From f5cfa954e6812c09e3a4b89e8009b22285b860a3 Mon Sep 17 00:00:00 2001 From: Troy Kisky Date: Sat, 4 Jul 2009 19:29:57 -0700 Subject: ASoC: DaVinci: i2s, fix mcbsp_word_length update Code previously just "ors" in this field without clearing first. Fix, by never reading this register. Signed-off-by: Troy Kisky Signed-off-by: Mark Brown --- sound/soc/davinci/davinci-i2s.c | 50 ++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 21 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c index 5b31b56e2e9..6fa1b6aebc3 100644 --- a/sound/soc/davinci/davinci-i2s.c +++ b/sound/soc/davinci/davinci-i2s.c @@ -63,6 +63,7 @@ #define DAVINCI_MCBSP_RCR_RWDLEN1(v) ((v) << 5) #define DAVINCI_MCBSP_RCR_RFRLEN1(v) ((v) << 8) #define DAVINCI_MCBSP_RCR_RDATDLY(v) ((v) << 16) +#define DAVINCI_MCBSP_RCR_RFIG (1 << 18) #define DAVINCI_MCBSP_RCR_RWDLEN2(v) ((v) << 21) #define DAVINCI_MCBSP_XCR_XWDLEN1(v) ((v) << 5) @@ -104,6 +105,9 @@ static struct davinci_pcm_dma_params davinci_i2s_pcm_in = { struct davinci_mcbsp_dev { void __iomem *base; +#define MOD_DSP_A 0 +#define MOD_DSP_B 1 + int mode; u32 pcr; struct clk *clk; struct davinci_pcm_dma_params *dma_params[2]; @@ -225,12 +229,11 @@ static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, struct davinci_mcbsp_dev *dev = cpu_dai->private_data; unsigned int pcr; unsigned int srgr; - unsigned int rcr; - unsigned int xcr; srgr = DAVINCI_MCBSP_SRGR_FSGM | DAVINCI_MCBSP_SRGR_FPER(DEFAULT_BITPERSAMPLE * 2 - 1) | DAVINCI_MCBSP_SRGR_FWID(DEFAULT_BITPERSAMPLE - 1); + /* set master/slave audio interface */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBS_CFS: /* cpu is master */ @@ -255,11 +258,8 @@ static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, return -EINVAL; } - rcr = DAVINCI_MCBSP_RCR_RFRLEN1(1); - xcr = DAVINCI_MCBSP_XCR_XFIG | DAVINCI_MCBSP_XCR_XFRLEN1(1); + /* interface format */ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { - case SND_SOC_DAIFMT_DSP_B: - break; case SND_SOC_DAIFMT_I2S: /* Davinci doesn't support TRUE I2S, but some codecs will have * the left and right channels contiguous. This allows @@ -279,8 +279,10 @@ static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, */ fmt ^= SND_SOC_DAIFMT_NB_IF; case SND_SOC_DAIFMT_DSP_A: - rcr |= DAVINCI_MCBSP_RCR_RDATDLY(1); - xcr |= DAVINCI_MCBSP_XCR_XDATDLY(1); + dev->mode = MOD_DSP_A; + break; + case SND_SOC_DAIFMT_DSP_B: + dev->mode = MOD_DSP_B; break; default: printk(KERN_ERR "%s:bad format\n", __func__); @@ -342,8 +344,6 @@ static int davinci_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, srgr); dev->pcr = pcr; davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_PCR_REG, pcr); - davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_RCR_REG, rcr); - davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_XCR_REG, xcr); return 0; } @@ -377,6 +377,15 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream, srgr |= DAVINCI_MCBSP_SRGR_FPER(snd_interval_value(i) - 1); davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_SRGR_REG, srgr); + rcr = DAVINCI_MCBSP_RCR_RFIG; + xcr = DAVINCI_MCBSP_XCR_XFIG; + if (dev->mode == MOD_DSP_B) { + rcr |= DAVINCI_MCBSP_RCR_RDATDLY(0); + xcr |= DAVINCI_MCBSP_XCR_XDATDLY(0); + } else { + rcr |= DAVINCI_MCBSP_RCR_RDATDLY(1); + xcr |= DAVINCI_MCBSP_XCR_XDATDLY(1); + } /* Determine xfer data type */ switch (params_format(params)) { case SNDRV_PCM_FORMAT_S8: @@ -396,19 +405,18 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { - rcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_RCR_REG); - rcr |= DAVINCI_MCBSP_RCR_RWDLEN1(mcbsp_word_length) | - DAVINCI_MCBSP_RCR_RWDLEN2(mcbsp_word_length); - davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_RCR_REG, rcr); + rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(1); + xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(1); - } else { - xcr = davinci_mcbsp_read_reg(dev, DAVINCI_MCBSP_XCR_REG); - xcr |= DAVINCI_MCBSP_XCR_XWDLEN1(mcbsp_word_length) | - DAVINCI_MCBSP_XCR_XWDLEN2(mcbsp_word_length); - davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_XCR_REG, xcr); + rcr |= DAVINCI_MCBSP_RCR_RWDLEN1(mcbsp_word_length) | + DAVINCI_MCBSP_RCR_RWDLEN2(mcbsp_word_length); + xcr |= DAVINCI_MCBSP_XCR_XWDLEN1(mcbsp_word_length) | + DAVINCI_MCBSP_XCR_XWDLEN2(mcbsp_word_length); - } + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_XCR_REG, xcr); + else + davinci_mcbsp_write_reg(dev, DAVINCI_MCBSP_RCR_REG, rcr); return 0; } -- cgit v1.2.3 From af0adf3e81dbcf782388b8a7d0d7094ef5e70f50 Mon Sep 17 00:00:00 2001 From: Troy Kisky Date: Sat, 4 Jul 2009 19:29:59 -0700 Subject: ASoC: DaVinci: i2s, add davinci_i2s_prepare and shutdown If the codec is master then prepare should call mcbsp_start, not trigger. Signed-off-by: Troy Kisky Signed-off-by: Mark Brown --- sound/soc/davinci/davinci-i2s.c | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c index 6fa1b6aebc3..88ccef79a5e 100644 --- a/sound/soc/davinci/davinci-i2s.c +++ b/sound/soc/davinci/davinci-i2s.c @@ -420,6 +420,20 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream, return 0; } +static int davinci_i2s_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data; + int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + davinci_mcbsp_stop(dev, playback); + if ((dev->pcr & DAVINCI_MCBSP_PCR_FSXM) == 0) { + /* codec is master */ + davinci_mcbsp_start(dev, substream); + } + return 0; +} + static int davinci_i2s_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { @@ -427,6 +441,8 @@ static int davinci_i2s_trigger(struct snd_pcm_substream *substream, int cmd, struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data; int ret = 0; int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + if ((dev->pcr & DAVINCI_MCBSP_PCR_FSXM) == 0) + return 0; /* return if codec is master */ switch (cmd) { case SNDRV_PCM_TRIGGER_START: @@ -442,10 +458,18 @@ static int davinci_i2s_trigger(struct snd_pcm_substream *substream, int cmd, default: ret = -EINVAL; } - return ret; } +static void davinci_i2s_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data; + int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); + davinci_mcbsp_stop(dev, playback); +} + static int davinci_i2s_probe(struct platform_device *pdev, struct snd_soc_dai *dai) { @@ -531,6 +555,8 @@ static void davinci_i2s_remove(struct platform_device *pdev, static struct snd_soc_dai_ops davinci_i2s_dai_ops = { .startup = davinci_i2s_startup, + .shutdown = davinci_i2s_shutdown, + .prepare = davinci_i2s_prepare, .trigger = davinci_i2s_trigger, .hw_params = davinci_i2s_hw_params, .set_fmt = davinci_i2s_set_dai_fmt, -- cgit v1.2.3 From 096e49d5e6f7bd93395e7ddf7e0239e1644d0505 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 5 Jul 2009 15:12:22 +0100 Subject: ASoC: Add CODEC volatile register operation Add a volatile_register() operation to the CODEC structure providing a standard operation to query if a register is volatile. This will be used to factor out the register cache I/O operations for the CODECs. Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index dfc03c0bacb..8bf49a4e664 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1125,6 +1125,23 @@ static int soc_new_pcm(struct snd_soc_device *socdev, return ret; } +/** + * snd_soc_codec_volatile_register: Report if a register is volatile. + * + * @codec: CODEC to query. + * @reg: Register to query. + * + * Boolean function indiciating if a CODEC register is volatile. + */ +int snd_soc_codec_volatile_register(struct snd_soc_codec *codec, int reg) +{ + if (codec->volatile_register) + return codec->volatile_register(reg); + else + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_codec_volatile_register); + /* codec register dump */ static ssize_t soc_codec_reg_show(struct snd_soc_codec *codec, char *buf) { -- cgit v1.2.3 From fe5422fc4ac221910083e603eb2a873280a9bd37 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 5 Jul 2009 15:18:01 +0100 Subject: ASoC: Fix leaks in WM8731 probe error handling Signed-off-by: Mark Brown --- sound/soc/codecs/wm8731.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index d7f4788f7ac..dfbc1bb375f 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -560,7 +560,8 @@ static int wm8731_register(struct wm8731_priv *wm8731) if (wm8731_codec) { dev_err(codec->dev, "Another WM8731 is registered\n"); - return -EINVAL; + ret = -EINVAL; + goto err; } mutex_init(&codec->mutex); @@ -583,8 +584,8 @@ static int wm8731_register(struct wm8731_priv *wm8731) ret = wm8731_reset(codec); if (ret < 0) { - dev_err(codec->dev, "Failed to issue reset\n"); - return ret; + dev_err(codec->dev, "Failed to issue reset: %d\n", ret); + goto err; } wm8731_dai.dev = codec->dev; @@ -610,17 +611,23 @@ static int wm8731_register(struct wm8731_priv *wm8731) ret = snd_soc_register_codec(codec); if (ret != 0) { dev_err(codec->dev, "Failed to register codec: %d\n", ret); - return ret; + goto err; } ret = snd_soc_register_dai(&wm8731_dai); if (ret != 0) { dev_err(codec->dev, "Failed to register DAI: %d\n", ret); snd_soc_unregister_codec(codec); - return ret; + goto err_codec; } return 0; + +err_codec: + snd_soc_unregister_codec(codec); +err: + kfree(wm8731); + return ret; } static void wm8731_unregister(struct wm8731_priv *wm8731) -- cgit v1.2.3 From 1a01417e85846e42e939a345a294e8ae2287cc1f Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 5 Jul 2009 15:47:03 +0100 Subject: ASoC: Fix WM8960 leaks on probe failure Signed-off-by: Mark Brown --- sound/soc/codecs/wm8960.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index b7894d6dffc..d1769e6c0c4 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -810,7 +810,8 @@ static int wm8960_register(struct wm8960_priv *wm8960) if (wm8960_codec) { dev_err(codec->dev, "Another WM8960 is registered\n"); - return -EINVAL; + ret = -EINVAL; + goto err; } if (!pdata) { @@ -843,7 +844,7 @@ static int wm8960_register(struct wm8960_priv *wm8960) ret = wm8960_reset(codec); if (ret < 0) { dev_err(codec->dev, "Failed to issue reset\n"); - return ret; + goto err; } wm8960_dai.dev = codec->dev; @@ -877,17 +878,22 @@ static int wm8960_register(struct wm8960_priv *wm8960) ret = snd_soc_register_codec(codec); if (ret != 0) { dev_err(codec->dev, "Failed to register codec: %d\n", ret); - return ret; + goto err; } ret = snd_soc_register_dai(&wm8960_dai); if (ret != 0) { dev_err(codec->dev, "Failed to register DAI: %d\n", ret); - snd_soc_unregister_codec(codec); - return ret; + goto err_codec; } return 0; + +err_codec: + snd_soc_unregister_codec(codec); +err: + kfree(wm8960); + return ret; } static void wm8960_unregister(struct wm8960_priv *wm8960) -- cgit v1.2.3 From 5420f30723122012c7bb868a55ff21c7d383b68e Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 5 Jul 2009 16:29:39 +0100 Subject: ASoC: Fix leaks in WM8988 registration error handling Signed-off-by: Mark Brown --- sound/soc/codecs/wm8988.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8988.c b/sound/soc/codecs/wm8988.c index 03fac6a0f80..6f15acd1048 100644 --- a/sound/soc/codecs/wm8988.c +++ b/sound/soc/codecs/wm8988.c @@ -902,7 +902,7 @@ static int wm8988_register(struct wm8988_priv *wm8988) ret = wm8988_reset(codec); if (ret < 0) { dev_err(codec->dev, "Failed to issue reset\n"); - return ret; + goto err; } /* set the update bits (we always update left then right) */ @@ -926,18 +926,20 @@ static int wm8988_register(struct wm8988_priv *wm8988) ret = snd_soc_register_codec(codec); if (ret != 0) { dev_err(codec->dev, "Failed to register codec: %d\n", ret); - return ret; + goto err; } ret = snd_soc_register_dai(&wm8988_dai); if (ret != 0) { dev_err(codec->dev, "Failed to register DAI: %d\n", ret); snd_soc_unregister_codec(codec); - return ret; + goto err_codec; } return 0; +err_codec: + snd_soc_unregister_codec(codec); err: kfree(wm8988); return ret; -- cgit v1.2.3 From 17a52fd60a0a0e617ed94aadb1b19751a8fa219e Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 5 Jul 2009 17:24:50 +0100 Subject: ASoC: Begin to factor out register cache I/O functions A lot of CODECs share the same register data formats and therefore replicate the code to manage access to and caching of the register map. In order to reduce code duplication centralised versions of this code will be introduced with drivers able to configure the use of the common code by calling the new snd_soc_codec_set_cache_io() API call during startup. As an initial user the 7 bit address/9 bit data format used by many Wolfson devices is supported for write only CODECs and the drivers with straightforward register cache implementations are converted to use it. Signed-off-by: Mark Brown --- sound/soc/Makefile | 2 +- sound/soc/codecs/wm8510.c | 141 ++++++++++++--------------------- sound/soc/codecs/wm8728.c | 78 ++++++------------- sound/soc/codecs/wm8731.c | 108 +++++++------------------- sound/soc/codecs/wm8750.c | 121 ++++++++++------------------- sound/soc/codecs/wm8960.c | 194 +++++++++++++++++----------------------------- sound/soc/codecs/wm8971.c | 121 +++++++++++------------------ sound/soc/codecs/wm8988.c | 105 ++++++++----------------- sound/soc/soc-cache.c | 105 +++++++++++++++++++++++++ 9 files changed, 398 insertions(+), 577 deletions(-) create mode 100644 sound/soc/soc-cache.c (limited to 'sound/soc') diff --git a/sound/soc/Makefile b/sound/soc/Makefile index 6f1e28de23c..4eaf48aab0f 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -1,4 +1,4 @@ -snd-soc-core-objs := soc-core.o soc-dapm.o soc-jack.o +snd-soc-core-objs := soc-core.o soc-dapm.o soc-jack.o soc-cache.o obj-$(CONFIG_SND_SOC) += snd-soc-core.o obj-$(CONFIG_SND_SOC) += codecs/ diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c index c8b8dba8589..7a169bff86f 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -58,55 +58,7 @@ static const u16 wm8510_reg[WM8510_CACHEREGNUM] = { #define WM8510_POWER1_BIASEN 0x08 #define WM8510_POWER1_BUFIOEN 0x10 -/* - * read wm8510 register cache - */ -static inline unsigned int wm8510_read_reg_cache(struct snd_soc_codec *codec, - unsigned int reg) -{ - u16 *cache = codec->reg_cache; - if (reg == WM8510_RESET) - return 0; - if (reg >= WM8510_CACHEREGNUM) - return -1; - return cache[reg]; -} - -/* - * write wm8510 register cache - */ -static inline void wm8510_write_reg_cache(struct snd_soc_codec *codec, - u16 reg, unsigned int value) -{ - u16 *cache = codec->reg_cache; - if (reg >= WM8510_CACHEREGNUM) - return; - cache[reg] = value; -} - -/* - * write to the WM8510 register space - */ -static int wm8510_write(struct snd_soc_codec *codec, unsigned int reg, - unsigned int value) -{ - u8 data[2]; - - /* data is - * D15..D9 WM8510 register offset - * D8...D0 register data - */ - data[0] = (reg << 1) | ((value >> 8) & 0x0001); - data[1] = value & 0x00ff; - - wm8510_write_reg_cache(codec, reg, value); - if (codec->hw_write(codec->control_data, data, 2) == 2) - return 0; - else - return -EIO; -} - -#define wm8510_reset(c) wm8510_write(c, WM8510_RESET, 0) +#define wm8510_reset(c) snd_soc_write(c, WM8510_RESET, 0) static const char *wm8510_companding[] = { "Off", "NC", "u-law", "A-law" }; static const char *wm8510_deemp[] = { "None", "32kHz", "44.1kHz", "48kHz" }; @@ -327,27 +279,27 @@ static int wm8510_set_dai_pll(struct snd_soc_dai *codec_dai, if (freq_in == 0 || freq_out == 0) { /* Clock CODEC directly from MCLK */ - reg = wm8510_read_reg_cache(codec, WM8510_CLOCK); - wm8510_write(codec, WM8510_CLOCK, reg & 0x0ff); + reg = snd_soc_read(codec, WM8510_CLOCK); + snd_soc_write(codec, WM8510_CLOCK, reg & 0x0ff); /* Turn off PLL */ - reg = wm8510_read_reg_cache(codec, WM8510_POWER1); - wm8510_write(codec, WM8510_POWER1, reg & 0x1df); + reg = snd_soc_read(codec, WM8510_POWER1); + snd_soc_write(codec, WM8510_POWER1, reg & 0x1df); return 0; } pll_factors(freq_out*4, freq_in); - wm8510_write(codec, WM8510_PLLN, (pll_div.pre_div << 4) | pll_div.n); - wm8510_write(codec, WM8510_PLLK1, pll_div.k >> 18); - wm8510_write(codec, WM8510_PLLK2, (pll_div.k >> 9) & 0x1ff); - wm8510_write(codec, WM8510_PLLK3, pll_div.k & 0x1ff); - reg = wm8510_read_reg_cache(codec, WM8510_POWER1); - wm8510_write(codec, WM8510_POWER1, reg | 0x020); + snd_soc_write(codec, WM8510_PLLN, (pll_div.pre_div << 4) | pll_div.n); + snd_soc_write(codec, WM8510_PLLK1, pll_div.k >> 18); + snd_soc_write(codec, WM8510_PLLK2, (pll_div.k >> 9) & 0x1ff); + snd_soc_write(codec, WM8510_PLLK3, pll_div.k & 0x1ff); + reg = snd_soc_read(codec, WM8510_POWER1); + snd_soc_write(codec, WM8510_POWER1, reg | 0x020); /* Run CODEC from PLL instead of MCLK */ - reg = wm8510_read_reg_cache(codec, WM8510_CLOCK); - wm8510_write(codec, WM8510_CLOCK, reg | 0x100); + reg = snd_soc_read(codec, WM8510_CLOCK); + snd_soc_write(codec, WM8510_CLOCK, reg | 0x100); return 0; } @@ -363,24 +315,24 @@ static int wm8510_set_dai_clkdiv(struct snd_soc_dai *codec_dai, switch (div_id) { case WM8510_OPCLKDIV: - reg = wm8510_read_reg_cache(codec, WM8510_GPIO) & 0x1cf; - wm8510_write(codec, WM8510_GPIO, reg | div); + reg = snd_soc_read(codec, WM8510_GPIO) & 0x1cf; + snd_soc_write(codec, WM8510_GPIO, reg | div); break; case WM8510_MCLKDIV: - reg = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x11f; - wm8510_write(codec, WM8510_CLOCK, reg | div); + reg = snd_soc_read(codec, WM8510_CLOCK) & 0x11f; + snd_soc_write(codec, WM8510_CLOCK, reg | div); break; case WM8510_ADCCLK: - reg = wm8510_read_reg_cache(codec, WM8510_ADC) & 0x1f7; - wm8510_write(codec, WM8510_ADC, reg | div); + reg = snd_soc_read(codec, WM8510_ADC) & 0x1f7; + snd_soc_write(codec, WM8510_ADC, reg | div); break; case WM8510_DACCLK: - reg = wm8510_read_reg_cache(codec, WM8510_DAC) & 0x1f7; - wm8510_write(codec, WM8510_DAC, reg | div); + reg = snd_soc_read(codec, WM8510_DAC) & 0x1f7; + snd_soc_write(codec, WM8510_DAC, reg | div); break; case WM8510_BCLKDIV: - reg = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1e3; - wm8510_write(codec, WM8510_CLOCK, reg | div); + reg = snd_soc_read(codec, WM8510_CLOCK) & 0x1e3; + snd_soc_write(codec, WM8510_CLOCK, reg | div); break; default: return -EINVAL; @@ -394,7 +346,7 @@ static int wm8510_set_dai_fmt(struct snd_soc_dai *codec_dai, { struct snd_soc_codec *codec = codec_dai->codec; u16 iface = 0; - u16 clk = wm8510_read_reg_cache(codec, WM8510_CLOCK) & 0x1fe; + u16 clk = snd_soc_read(codec, WM8510_CLOCK) & 0x1fe; /* set master/slave audio interface */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { @@ -441,8 +393,8 @@ static int wm8510_set_dai_fmt(struct snd_soc_dai *codec_dai, return -EINVAL; } - wm8510_write(codec, WM8510_IFACE, iface); - wm8510_write(codec, WM8510_CLOCK, clk); + snd_soc_write(codec, WM8510_IFACE, iface); + snd_soc_write(codec, WM8510_CLOCK, clk); return 0; } @@ -453,8 +405,8 @@ static int wm8510_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; - u16 iface = wm8510_read_reg_cache(codec, WM8510_IFACE) & 0x19f; - u16 adn = wm8510_read_reg_cache(codec, WM8510_ADD) & 0x1f1; + u16 iface = snd_soc_read(codec, WM8510_IFACE) & 0x19f; + u16 adn = snd_soc_read(codec, WM8510_ADD) & 0x1f1; /* bit size */ switch (params_format(params)) { @@ -493,20 +445,20 @@ static int wm8510_pcm_hw_params(struct snd_pcm_substream *substream, break; } - wm8510_write(codec, WM8510_IFACE, iface); - wm8510_write(codec, WM8510_ADD, adn); + snd_soc_write(codec, WM8510_IFACE, iface); + snd_soc_write(codec, WM8510_ADD, adn); return 0; } static int wm8510_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; - u16 mute_reg = wm8510_read_reg_cache(codec, WM8510_DAC) & 0xffbf; + u16 mute_reg = snd_soc_read(codec, WM8510_DAC) & 0xffbf; if (mute) - wm8510_write(codec, WM8510_DAC, mute_reg | 0x40); + snd_soc_write(codec, WM8510_DAC, mute_reg | 0x40); else - wm8510_write(codec, WM8510_DAC, mute_reg); + snd_soc_write(codec, WM8510_DAC, mute_reg); return 0; } @@ -514,13 +466,13 @@ static int wm8510_mute(struct snd_soc_dai *dai, int mute) static int wm8510_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - u16 power1 = wm8510_read_reg_cache(codec, WM8510_POWER1) & ~0x3; + u16 power1 = snd_soc_read(codec, WM8510_POWER1) & ~0x3; switch (level) { case SND_SOC_BIAS_ON: case SND_SOC_BIAS_PREPARE: power1 |= 0x1; /* VMID 50k */ - wm8510_write(codec, WM8510_POWER1, power1); + snd_soc_write(codec, WM8510_POWER1, power1); break; case SND_SOC_BIAS_STANDBY: @@ -528,18 +480,18 @@ static int wm8510_set_bias_level(struct snd_soc_codec *codec, if (codec->bias_level == SND_SOC_BIAS_OFF) { /* Initial cap charge at VMID 5k */ - wm8510_write(codec, WM8510_POWER1, power1 | 0x3); + snd_soc_write(codec, WM8510_POWER1, power1 | 0x3); mdelay(100); } power1 |= 0x2; /* VMID 500k */ - wm8510_write(codec, WM8510_POWER1, power1); + snd_soc_write(codec, WM8510_POWER1, power1); break; case SND_SOC_BIAS_OFF: - wm8510_write(codec, WM8510_POWER1, 0); - wm8510_write(codec, WM8510_POWER2, 0); - wm8510_write(codec, WM8510_POWER3, 0); + snd_soc_write(codec, WM8510_POWER1, 0); + snd_soc_write(codec, WM8510_POWER2, 0); + snd_soc_write(codec, WM8510_POWER3, 0); break; } @@ -619,8 +571,6 @@ static int wm8510_init(struct snd_soc_device *socdev) codec->name = "WM8510"; codec->owner = THIS_MODULE; - codec->read = wm8510_read_reg_cache; - codec->write = wm8510_write; codec->set_bias_level = wm8510_set_bias_level; codec->dai = &wm8510_dai; codec->num_dai = 1; @@ -630,13 +580,20 @@ static int wm8510_init(struct snd_soc_device *socdev) if (codec->reg_cache == NULL) return -ENOMEM; + ret = snd_soc_codec_set_cache_io(codec, 7, 9); + if (ret < 0) { + printk(KERN_ERR "wm8510: failed to set cache I/O: %d\n", + ret); + goto err; + } + wm8510_reset(codec); /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { printk(KERN_ERR "wm8510: failed to create pcms\n"); - goto pcm_err; + goto err; } /* power on device */ @@ -655,7 +612,7 @@ static int wm8510_init(struct snd_soc_device *socdev) card_err: snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); -pcm_err: +err: kfree(codec->reg_cache); return ret; } diff --git a/sound/soc/codecs/wm8728.c b/sound/soc/codecs/wm8728.c index e7ff2121ede..66da44b08d3 100644 --- a/sound/soc/codecs/wm8728.c +++ b/sound/soc/codecs/wm8728.c @@ -43,45 +43,6 @@ static const u16 wm8728_reg_defaults[] = { 0x100, }; -static inline unsigned int wm8728_read_reg_cache(struct snd_soc_codec *codec, - unsigned int reg) -{ - u16 *cache = codec->reg_cache; - BUG_ON(reg >= ARRAY_SIZE(wm8728_reg_defaults)); - return cache[reg]; -} - -static inline void wm8728_write_reg_cache(struct snd_soc_codec *codec, - u16 reg, unsigned int value) -{ - u16 *cache = codec->reg_cache; - BUG_ON(reg >= ARRAY_SIZE(wm8728_reg_defaults)); - cache[reg] = value; -} - -/* - * write to the WM8728 register space - */ -static int wm8728_write(struct snd_soc_codec *codec, unsigned int reg, - unsigned int value) -{ - u8 data[2]; - - /* data is - * D15..D9 WM8728 register offset - * D8...D0 register data - */ - data[0] = (reg << 1) | ((value >> 8) & 0x0001); - data[1] = value & 0x00ff; - - wm8728_write_reg_cache(codec, reg, value); - - if (codec->hw_write(codec->control_data, data, 2) == 2) - return 0; - else - return -EIO; -} - static const DECLARE_TLV_DB_SCALE(wm8728_tlv, -12750, 50, 1); static const struct snd_kcontrol_new wm8728_snd_controls[] = { @@ -121,12 +82,12 @@ static int wm8728_add_widgets(struct snd_soc_codec *codec) static int wm8728_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; - u16 mute_reg = wm8728_read_reg_cache(codec, WM8728_DACCTL); + u16 mute_reg = snd_soc_read(codec, WM8728_DACCTL); if (mute) - wm8728_write(codec, WM8728_DACCTL, mute_reg | 1); + snd_soc_write(codec, WM8728_DACCTL, mute_reg | 1); else - wm8728_write(codec, WM8728_DACCTL, mute_reg & ~1); + snd_soc_write(codec, WM8728_DACCTL, mute_reg & ~1); return 0; } @@ -138,7 +99,7 @@ static int wm8728_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; - u16 dac = wm8728_read_reg_cache(codec, WM8728_DACCTL); + u16 dac = snd_soc_read(codec, WM8728_DACCTL); dac &= ~0x18; @@ -155,7 +116,7 @@ static int wm8728_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - wm8728_write(codec, WM8728_DACCTL, dac); + snd_soc_write(codec, WM8728_DACCTL, dac); return 0; } @@ -164,7 +125,7 @@ static int wm8728_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; - u16 iface = wm8728_read_reg_cache(codec, WM8728_IFCTL); + u16 iface = snd_soc_read(codec, WM8728_IFCTL); /* Currently only I2S is supported by the driver, though the * hardware is more flexible. @@ -204,7 +165,7 @@ static int wm8728_set_dai_fmt(struct snd_soc_dai *codec_dai, return -EINVAL; } - wm8728_write(codec, WM8728_IFCTL, iface); + snd_soc_write(codec, WM8728_IFCTL, iface); return 0; } @@ -220,19 +181,19 @@ static int wm8728_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_STANDBY: if (codec->bias_level == SND_SOC_BIAS_OFF) { /* Power everything up... */ - reg = wm8728_read_reg_cache(codec, WM8728_DACCTL); - wm8728_write(codec, WM8728_DACCTL, reg & ~0x4); + reg = snd_soc_read(codec, WM8728_DACCTL); + snd_soc_write(codec, WM8728_DACCTL, reg & ~0x4); /* ..then sync in the register cache. */ for (i = 0; i < ARRAY_SIZE(wm8728_reg_defaults); i++) - wm8728_write(codec, i, - wm8728_read_reg_cache(codec, i)); + snd_soc_write(codec, i, + snd_soc_read(codec, i)); } break; case SND_SOC_BIAS_OFF: - reg = wm8728_read_reg_cache(codec, WM8728_DACCTL); - wm8728_write(codec, WM8728_DACCTL, reg | 0x4); + reg = snd_soc_read(codec, WM8728_DACCTL); + snd_soc_write(codec, WM8728_DACCTL, reg | 0x4); break; } codec->bias_level = level; @@ -294,8 +255,6 @@ static int wm8728_init(struct snd_soc_device *socdev) codec->name = "WM8728"; codec->owner = THIS_MODULE; - codec->read = wm8728_read_reg_cache; - codec->write = wm8728_write; codec->set_bias_level = wm8728_set_bias_level; codec->dai = &wm8728_dai; codec->num_dai = 1; @@ -307,11 +266,18 @@ static int wm8728_init(struct snd_soc_device *socdev) if (codec->reg_cache == NULL) return -ENOMEM; + ret = snd_soc_codec_set_cache_io(codec, 7, 9); + if (ret < 0) { + printk(KERN_ERR "wm8728: failed to configure cache I/O: %d\n", + ret); + goto err; + } + /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { printk(KERN_ERR "wm8728: failed to create pcms\n"); - goto pcm_err; + goto err; } /* power on device */ @@ -331,7 +297,7 @@ static int wm8728_init(struct snd_soc_device *socdev) card_err: snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); -pcm_err: +err: kfree(codec->reg_cache); return ret; } diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index dfbc1bb375f..4eb84ff691b 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -50,60 +50,12 @@ static int wm8731_spi_write(struct spi_device *spi, const char *data, int len); * There is no point in caching the reset register */ static const u16 wm8731_reg[WM8731_CACHEREGNUM] = { - 0x0097, 0x0097, 0x0079, 0x0079, - 0x000a, 0x0008, 0x009f, 0x000a, - 0x0000, 0x0000 + 0x0097, 0x0097, 0x0079, 0x0079, + 0x000a, 0x0008, 0x009f, 0x000a, + 0x0000, 0x0000 }; -/* - * read wm8731 register cache - */ -static inline unsigned int wm8731_read_reg_cache(struct snd_soc_codec *codec, - unsigned int reg) -{ - u16 *cache = codec->reg_cache; - if (reg == WM8731_RESET) - return 0; - if (reg >= WM8731_CACHEREGNUM) - return -1; - return cache[reg]; -} - -/* - * write wm8731 register cache - */ -static inline void wm8731_write_reg_cache(struct snd_soc_codec *codec, - u16 reg, unsigned int value) -{ - u16 *cache = codec->reg_cache; - if (reg >= WM8731_CACHEREGNUM) - return; - cache[reg] = value; -} - -/* - * write to the WM8731 register space - */ -static int wm8731_write(struct snd_soc_codec *codec, unsigned int reg, - unsigned int value) -{ - u8 data[2]; - - /* data is - * D15..D9 WM8731 register offset - * D8...D0 register data - */ - data[0] = (reg << 1) | ((value >> 8) & 0x0001); - data[1] = value & 0x00ff; - - wm8731_write_reg_cache(codec, reg, value); - if (codec->hw_write(codec->control_data, data, 2) == 2) - return 0; - else - return -EIO; -} - -#define wm8731_reset(c) wm8731_write(c, WM8731_RESET, 0) +#define wm8731_reset(c) snd_soc_write(c, WM8731_RESET, 0) static const char *wm8731_input_select[] = {"Line In", "Mic"}; static const char *wm8731_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; @@ -260,12 +212,12 @@ static int wm8731_hw_params(struct snd_pcm_substream *substream, struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; struct wm8731_priv *wm8731 = codec->private_data; - u16 iface = wm8731_read_reg_cache(codec, WM8731_IFACE) & 0xfff3; + u16 iface = snd_soc_read(codec, WM8731_IFACE) & 0xfff3; int i = get_coeff(wm8731->sysclk, params_rate(params)); u16 srate = (coeff_div[i].sr << 2) | (coeff_div[i].bosr << 1) | coeff_div[i].usb; - wm8731_write(codec, WM8731_SRATE, srate); + snd_soc_write(codec, WM8731_SRATE, srate); /* bit size */ switch (params_format(params)) { @@ -279,7 +231,7 @@ static int wm8731_hw_params(struct snd_pcm_substream *substream, break; } - wm8731_write(codec, WM8731_IFACE, iface); + snd_soc_write(codec, WM8731_IFACE, iface); return 0; } @@ -291,7 +243,7 @@ static int wm8731_pcm_prepare(struct snd_pcm_substream *substream, struct snd_soc_codec *codec = socdev->card->codec; /* set active */ - wm8731_write(codec, WM8731_ACTIVE, 0x0001); + snd_soc_write(codec, WM8731_ACTIVE, 0x0001); return 0; } @@ -306,19 +258,19 @@ static void wm8731_shutdown(struct snd_pcm_substream *substream, /* deactivate */ if (!codec->active) { udelay(50); - wm8731_write(codec, WM8731_ACTIVE, 0x0); + snd_soc_write(codec, WM8731_ACTIVE, 0x0); } } static int wm8731_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; - u16 mute_reg = wm8731_read_reg_cache(codec, WM8731_APDIGI) & 0xfff7; + u16 mute_reg = snd_soc_read(codec, WM8731_APDIGI) & 0xfff7; if (mute) - wm8731_write(codec, WM8731_APDIGI, mute_reg | 0x8); + snd_soc_write(codec, WM8731_APDIGI, mute_reg | 0x8); else - wm8731_write(codec, WM8731_APDIGI, mute_reg); + snd_soc_write(codec, WM8731_APDIGI, mute_reg); return 0; } @@ -396,7 +348,7 @@ static int wm8731_set_dai_fmt(struct snd_soc_dai *codec_dai, } /* set iface */ - wm8731_write(codec, WM8731_IFACE, iface); + snd_soc_write(codec, WM8731_IFACE, iface); return 0; } @@ -412,12 +364,12 @@ static int wm8731_set_bias_level(struct snd_soc_codec *codec, break; case SND_SOC_BIAS_STANDBY: /* Clear PWROFF, gate CLKOUT, everything else as-is */ - reg = wm8731_read_reg_cache(codec, WM8731_PWR) & 0xff7f; - wm8731_write(codec, WM8731_PWR, reg | 0x0040); + reg = snd_soc_read(codec, WM8731_PWR) & 0xff7f; + snd_soc_write(codec, WM8731_PWR, reg | 0x0040); break; case SND_SOC_BIAS_OFF: - wm8731_write(codec, WM8731_ACTIVE, 0x0); - wm8731_write(codec, WM8731_PWR, 0xffff); + snd_soc_write(codec, WM8731_ACTIVE, 0x0); + snd_soc_write(codec, WM8731_PWR, 0xffff); break; } codec->bias_level = level; @@ -466,7 +418,7 @@ static int wm8731_suspend(struct platform_device *pdev, pm_message_t state) struct snd_soc_device *socdev = platform_get_drvdata(pdev); struct snd_soc_codec *codec = socdev->card->codec; - wm8731_write(codec, WM8731_ACTIVE, 0x0); + snd_soc_write(codec, WM8731_ACTIVE, 0x0); wm8731_set_bias_level(codec, SND_SOC_BIAS_OFF); return 0; } @@ -556,7 +508,6 @@ static int wm8731_register(struct wm8731_priv *wm8731) { int ret; struct snd_soc_codec *codec = &wm8731->codec; - u16 reg; if (wm8731_codec) { dev_err(codec->dev, "Another WM8731 is registered\n"); @@ -571,8 +522,6 @@ static int wm8731_register(struct wm8731_priv *wm8731) codec->private_data = wm8731; codec->name = "WM8731"; codec->owner = THIS_MODULE; - codec->read = wm8731_read_reg_cache; - codec->write = wm8731_write; codec->bias_level = SND_SOC_BIAS_OFF; codec->set_bias_level = wm8731_set_bias_level; codec->dai = &wm8731_dai; @@ -582,6 +531,12 @@ static int wm8731_register(struct wm8731_priv *wm8731) memcpy(codec->reg_cache, wm8731_reg, sizeof(wm8731_reg)); + ret = snd_soc_codec_set_cache_io(codec, 7, 9); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + goto err; + } + ret = wm8731_reset(codec); if (ret < 0) { dev_err(codec->dev, "Failed to issue reset: %d\n", ret); @@ -593,18 +548,13 @@ static int wm8731_register(struct wm8731_priv *wm8731) wm8731_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* Latch the update bits */ - reg = wm8731_read_reg_cache(codec, WM8731_LOUT1V); - wm8731_write(codec, WM8731_LOUT1V, reg & ~0x0100); - reg = wm8731_read_reg_cache(codec, WM8731_ROUT1V); - wm8731_write(codec, WM8731_ROUT1V, reg & ~0x0100); - reg = wm8731_read_reg_cache(codec, WM8731_LINVOL); - wm8731_write(codec, WM8731_LINVOL, reg & ~0x0100); - reg = wm8731_read_reg_cache(codec, WM8731_RINVOL); - wm8731_write(codec, WM8731_RINVOL, reg & ~0x0100); + snd_soc_update_bits(codec, WM8731_LOUT1V, 0x100, 0); + snd_soc_update_bits(codec, WM8731_ROUT1V, 0x100, 0); + snd_soc_update_bits(codec, WM8731_LINVOL, 0x100, 0); + snd_soc_update_bits(codec, WM8731_RINVOL, 0x100, 0); /* Disable bypass path by default */ - reg = wm8731_read_reg_cache(codec, WM8731_APANA); - wm8731_write(codec, WM8731_APANA, reg & ~0x4); + snd_soc_update_bits(codec, WM8731_APANA, 0x4, 0); wm8731_codec = codec; diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index b64509b01a4..ed09043181e 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -55,50 +55,7 @@ static const u16 wm8750_reg[] = { 0x0079, 0x0079, 0x0079, /* 40 */ }; -/* - * read wm8750 register cache - */ -static inline unsigned int wm8750_read_reg_cache(struct snd_soc_codec *codec, - unsigned int reg) -{ - u16 *cache = codec->reg_cache; - if (reg > WM8750_CACHE_REGNUM) - return -1; - return cache[reg]; -} - -/* - * write wm8750 register cache - */ -static inline void wm8750_write_reg_cache(struct snd_soc_codec *codec, - unsigned int reg, unsigned int value) -{ - u16 *cache = codec->reg_cache; - if (reg > WM8750_CACHE_REGNUM) - return; - cache[reg] = value; -} - -static int wm8750_write(struct snd_soc_codec *codec, unsigned int reg, - unsigned int value) -{ - u8 data[2]; - - /* data is - * D15..D9 WM8753 register offset - * D8...D0 register data - */ - data[0] = (reg << 1) | ((value >> 8) & 0x0001); - data[1] = value & 0x00ff; - - wm8750_write_reg_cache(codec, reg, value); - if (codec->hw_write(codec->control_data, data, 2) == 2) - return 0; - else - return -EIO; -} - -#define wm8750_reset(c) wm8750_write(c, WM8750_RESET, 0) +#define wm8750_reset(c) snd_soc_write(c, WM8750_RESET, 0) /* * WM8750 Controls @@ -594,7 +551,7 @@ static int wm8750_set_dai_fmt(struct snd_soc_dai *codec_dai, return -EINVAL; } - wm8750_write(codec, WM8750_IFACE, iface); + snd_soc_write(codec, WM8750_IFACE, iface); return 0; } @@ -606,8 +563,8 @@ static int wm8750_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; struct wm8750_priv *wm8750 = codec->private_data; - u16 iface = wm8750_read_reg_cache(codec, WM8750_IFACE) & 0x1f3; - u16 srate = wm8750_read_reg_cache(codec, WM8750_SRATE) & 0x1c0; + u16 iface = snd_soc_read(codec, WM8750_IFACE) & 0x1f3; + u16 srate = snd_soc_read(codec, WM8750_SRATE) & 0x1c0; int coeff = get_coeff(wm8750->sysclk, params_rate(params)); /* bit size */ @@ -626,9 +583,9 @@ static int wm8750_pcm_hw_params(struct snd_pcm_substream *substream, } /* set iface & srate */ - wm8750_write(codec, WM8750_IFACE, iface); + snd_soc_write(codec, WM8750_IFACE, iface); if (coeff >= 0) - wm8750_write(codec, WM8750_SRATE, srate | + snd_soc_write(codec, WM8750_SRATE, srate | (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb); return 0; @@ -637,35 +594,35 @@ static int wm8750_pcm_hw_params(struct snd_pcm_substream *substream, static int wm8750_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; - u16 mute_reg = wm8750_read_reg_cache(codec, WM8750_ADCDAC) & 0xfff7; + u16 mute_reg = snd_soc_read(codec, WM8750_ADCDAC) & 0xfff7; if (mute) - wm8750_write(codec, WM8750_ADCDAC, mute_reg | 0x8); + snd_soc_write(codec, WM8750_ADCDAC, mute_reg | 0x8); else - wm8750_write(codec, WM8750_ADCDAC, mute_reg); + snd_soc_write(codec, WM8750_ADCDAC, mute_reg); return 0; } static int wm8750_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - u16 pwr_reg = wm8750_read_reg_cache(codec, WM8750_PWR1) & 0xfe3e; + u16 pwr_reg = snd_soc_read(codec, WM8750_PWR1) & 0xfe3e; switch (level) { case SND_SOC_BIAS_ON: /* set vmid to 50k and unmute dac */ - wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x00c0); + snd_soc_write(codec, WM8750_PWR1, pwr_reg | 0x00c0); break; case SND_SOC_BIAS_PREPARE: /* set vmid to 5k for quick power up */ - wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x01c1); + snd_soc_write(codec, WM8750_PWR1, pwr_reg | 0x01c1); break; case SND_SOC_BIAS_STANDBY: /* mute dac and set vmid to 500k, enable VREF */ - wm8750_write(codec, WM8750_PWR1, pwr_reg | 0x0141); + snd_soc_write(codec, WM8750_PWR1, pwr_reg | 0x0141); break; case SND_SOC_BIAS_OFF: - wm8750_write(codec, WM8750_PWR1, 0x0001); + snd_soc_write(codec, WM8750_PWR1, 0x0001); break; } codec->bias_level = level; @@ -761,8 +718,6 @@ static int wm8750_init(struct snd_soc_device *socdev) codec->name = "WM8750"; codec->owner = THIS_MODULE; - codec->read = wm8750_read_reg_cache; - codec->write = wm8750_write; codec->set_bias_level = wm8750_set_bias_level; codec->dai = &wm8750_dai; codec->num_dai = 1; @@ -771,13 +726,23 @@ static int wm8750_init(struct snd_soc_device *socdev) if (codec->reg_cache == NULL) return -ENOMEM; - wm8750_reset(codec); + ret = snd_soc_codec_set_cache_io(codec, 7, 9); + if (ret < 0) { + printk(KERN_ERR "wm8750: failed to set cache I/O: %d\n", ret); + goto err; + } + + ret = wm8750_reset(codec); + if (ret < 0) { + printk(KERN_ERR "wm8750: failed to reset: %d\n", ret); + goto err; + } /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { printk(KERN_ERR "wm8750: failed to create pcms\n"); - goto pcm_err; + goto err; } /* charge output caps */ @@ -786,22 +751,22 @@ static int wm8750_init(struct snd_soc_device *socdev) schedule_delayed_work(&codec->delayed_work, msecs_to_jiffies(1000)); /* set the update bits */ - reg = wm8750_read_reg_cache(codec, WM8750_LDAC); - wm8750_write(codec, WM8750_LDAC, reg | 0x0100); - reg = wm8750_read_reg_cache(codec, WM8750_RDAC); - wm8750_write(codec, WM8750_RDAC, reg | 0x0100); - reg = wm8750_read_reg_cache(codec, WM8750_LOUT1V); - wm8750_write(codec, WM8750_LOUT1V, reg | 0x0100); - reg = wm8750_read_reg_cache(codec, WM8750_ROUT1V); - wm8750_write(codec, WM8750_ROUT1V, reg | 0x0100); - reg = wm8750_read_reg_cache(codec, WM8750_LOUT2V); - wm8750_write(codec, WM8750_LOUT2V, reg | 0x0100); - reg = wm8750_read_reg_cache(codec, WM8750_ROUT2V); - wm8750_write(codec, WM8750_ROUT2V, reg | 0x0100); - reg = wm8750_read_reg_cache(codec, WM8750_LINVOL); - wm8750_write(codec, WM8750_LINVOL, reg | 0x0100); - reg = wm8750_read_reg_cache(codec, WM8750_RINVOL); - wm8750_write(codec, WM8750_RINVOL, reg | 0x0100); + reg = snd_soc_read(codec, WM8750_LDAC); + snd_soc_write(codec, WM8750_LDAC, reg | 0x0100); + reg = snd_soc_read(codec, WM8750_RDAC); + snd_soc_write(codec, WM8750_RDAC, reg | 0x0100); + reg = snd_soc_read(codec, WM8750_LOUT1V); + snd_soc_write(codec, WM8750_LOUT1V, reg | 0x0100); + reg = snd_soc_read(codec, WM8750_ROUT1V); + snd_soc_write(codec, WM8750_ROUT1V, reg | 0x0100); + reg = snd_soc_read(codec, WM8750_LOUT2V); + snd_soc_write(codec, WM8750_LOUT2V, reg | 0x0100); + reg = snd_soc_read(codec, WM8750_ROUT2V); + snd_soc_write(codec, WM8750_ROUT2V, reg | 0x0100); + reg = snd_soc_read(codec, WM8750_LINVOL); + snd_soc_write(codec, WM8750_LINVOL, reg | 0x0100); + reg = snd_soc_read(codec, WM8750_RINVOL); + snd_soc_write(codec, WM8750_RINVOL, reg | 0x0100); snd_soc_add_controls(codec, wm8750_snd_controls, ARRAY_SIZE(wm8750_snd_controls)); @@ -816,7 +781,7 @@ static int wm8750_init(struct snd_soc_device *socdev) card_err: snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); -pcm_err: +err: kfree(codec->reg_cache); return ret; } diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index d1769e6c0c4..c529ffcfe5d 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -69,61 +69,7 @@ struct wm8960_priv { struct snd_soc_codec codec; }; -/* - * read wm8960 register cache - */ -static inline unsigned int wm8960_read_reg_cache(struct snd_soc_codec *codec, - unsigned int reg) -{ - u16 *cache = codec->reg_cache; - if (reg == WM8960_RESET) - return 0; - if (reg >= WM8960_CACHEREGNUM) - return -1; - return cache[reg]; -} - -/* - * write wm8960 register cache - */ -static inline void wm8960_write_reg_cache(struct snd_soc_codec *codec, - u16 reg, unsigned int value) -{ - u16 *cache = codec->reg_cache; - if (reg >= WM8960_CACHEREGNUM) - return; - cache[reg] = value; -} - -static inline unsigned int wm8960_read(struct snd_soc_codec *codec, - unsigned int reg) -{ - return wm8960_read_reg_cache(codec, reg); -} - -/* - * write to the WM8960 register space - */ -static int wm8960_write(struct snd_soc_codec *codec, unsigned int reg, - unsigned int value) -{ - u8 data[2]; - - /* data is - * D15..D9 WM8960 register offset - * D8...D0 register data - */ - data[0] = (reg << 1) | ((value >> 8) & 0x0001); - data[1] = value & 0x00ff; - - wm8960_write_reg_cache(codec, reg, value); - if (codec->hw_write(codec->control_data, data, 2) == 2) - return 0; - else - return -EIO; -} - -#define wm8960_reset(c) wm8960_write(c, WM8960_RESET, 0) +#define wm8960_reset(c) snd_soc_write(c, WM8960_RESET, 0) /* enumerated controls */ static const char *wm8960_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"}; @@ -420,7 +366,7 @@ static int wm8960_set_dai_fmt(struct snd_soc_dai *codec_dai, } /* set iface */ - wm8960_write(codec, WM8960_IFACE1, iface); + snd_soc_write(codec, WM8960_IFACE1, iface); return 0; } @@ -431,7 +377,7 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; - u16 iface = wm8960_read(codec, WM8960_IFACE1) & 0xfff3; + u16 iface = snd_soc_read(codec, WM8960_IFACE1) & 0xfff3; /* bit size */ switch (params_format(params)) { @@ -446,19 +392,19 @@ static int wm8960_hw_params(struct snd_pcm_substream *substream, } /* set iface */ - wm8960_write(codec, WM8960_IFACE1, iface); + snd_soc_write(codec, WM8960_IFACE1, iface); return 0; } static int wm8960_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; - u16 mute_reg = wm8960_read(codec, WM8960_DACCTL1) & 0xfff7; + u16 mute_reg = snd_soc_read(codec, WM8960_DACCTL1) & 0xfff7; if (mute) - wm8960_write(codec, WM8960_DACCTL1, mute_reg | 0x8); + snd_soc_write(codec, WM8960_DACCTL1, mute_reg | 0x8); else - wm8960_write(codec, WM8960_DACCTL1, mute_reg); + snd_soc_write(codec, WM8960_DACCTL1, mute_reg); return 0; } @@ -474,16 +420,16 @@ static int wm8960_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_PREPARE: /* Set VMID to 2x50k */ - reg = wm8960_read(codec, WM8960_POWER1); + reg = snd_soc_read(codec, WM8960_POWER1); reg &= ~0x180; reg |= 0x80; - wm8960_write(codec, WM8960_POWER1, reg); + snd_soc_write(codec, WM8960_POWER1, reg); break; case SND_SOC_BIAS_STANDBY: if (codec->bias_level == SND_SOC_BIAS_OFF) { /* Enable anti-pop features */ - wm8960_write(codec, WM8960_APOP1, + snd_soc_write(codec, WM8960_APOP1, WM8960_POBCTRL | WM8960_SOFT_ST | WM8960_BUFDCOPEN | WM8960_BUFIOEN); @@ -491,43 +437,43 @@ static int wm8960_set_bias_level(struct snd_soc_codec *codec, reg = WM8960_DISOP; if (pdata) reg |= pdata->dres << 4; - wm8960_write(codec, WM8960_APOP2, reg); + snd_soc_write(codec, WM8960_APOP2, reg); msleep(400); - wm8960_write(codec, WM8960_APOP2, 0); + snd_soc_write(codec, WM8960_APOP2, 0); /* Enable & ramp VMID at 2x50k */ - reg = wm8960_read(codec, WM8960_POWER1); + reg = snd_soc_read(codec, WM8960_POWER1); reg |= 0x80; - wm8960_write(codec, WM8960_POWER1, reg); + snd_soc_write(codec, WM8960_POWER1, reg); msleep(100); /* Enable VREF */ - wm8960_write(codec, WM8960_POWER1, reg | WM8960_VREF); + snd_soc_write(codec, WM8960_POWER1, reg | WM8960_VREF); /* Disable anti-pop features */ - wm8960_write(codec, WM8960_APOP1, WM8960_BUFIOEN); + snd_soc_write(codec, WM8960_APOP1, WM8960_BUFIOEN); } /* Set VMID to 2x250k */ - reg = wm8960_read(codec, WM8960_POWER1); + reg = snd_soc_read(codec, WM8960_POWER1); reg &= ~0x180; reg |= 0x100; - wm8960_write(codec, WM8960_POWER1, reg); + snd_soc_write(codec, WM8960_POWER1, reg); break; case SND_SOC_BIAS_OFF: /* Enable anti-pop features */ - wm8960_write(codec, WM8960_APOP1, + snd_soc_write(codec, WM8960_APOP1, WM8960_POBCTRL | WM8960_SOFT_ST | WM8960_BUFDCOPEN | WM8960_BUFIOEN); /* Disable VMID and VREF, let them discharge */ - wm8960_write(codec, WM8960_POWER1, 0); + snd_soc_write(codec, WM8960_POWER1, 0); msleep(600); - wm8960_write(codec, WM8960_APOP1, 0); + snd_soc_write(codec, WM8960_APOP1, 0); break; } @@ -610,33 +556,33 @@ static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, /* Disable the PLL: even if we are changing the frequency the * PLL needs to be disabled while we do so. */ - wm8960_write(codec, WM8960_CLOCK1, - wm8960_read(codec, WM8960_CLOCK1) & ~1); - wm8960_write(codec, WM8960_POWER2, - wm8960_read(codec, WM8960_POWER2) & ~1); + snd_soc_write(codec, WM8960_CLOCK1, + snd_soc_read(codec, WM8960_CLOCK1) & ~1); + snd_soc_write(codec, WM8960_POWER2, + snd_soc_read(codec, WM8960_POWER2) & ~1); if (!freq_in || !freq_out) return 0; - reg = wm8960_read(codec, WM8960_PLL1) & ~0x3f; + reg = snd_soc_read(codec, WM8960_PLL1) & ~0x3f; reg |= pll_div.pre_div << 4; reg |= pll_div.n; if (pll_div.k) { reg |= 0x20; - wm8960_write(codec, WM8960_PLL2, (pll_div.k >> 18) & 0x3f); - wm8960_write(codec, WM8960_PLL3, (pll_div.k >> 9) & 0x1ff); - wm8960_write(codec, WM8960_PLL4, pll_div.k & 0x1ff); + snd_soc_write(codec, WM8960_PLL2, (pll_div.k >> 18) & 0x3f); + snd_soc_write(codec, WM8960_PLL3, (pll_div.k >> 9) & 0x1ff); + snd_soc_write(codec, WM8960_PLL4, pll_div.k & 0x1ff); } - wm8960_write(codec, WM8960_PLL1, reg); + snd_soc_write(codec, WM8960_PLL1, reg); /* Turn it on */ - wm8960_write(codec, WM8960_POWER2, - wm8960_read(codec, WM8960_POWER2) | 1); + snd_soc_write(codec, WM8960_POWER2, + snd_soc_read(codec, WM8960_POWER2) | 1); msleep(250); - wm8960_write(codec, WM8960_CLOCK1, - wm8960_read(codec, WM8960_CLOCK1) | 1); + snd_soc_write(codec, WM8960_CLOCK1, + snd_soc_read(codec, WM8960_CLOCK1) | 1); return 0; } @@ -649,28 +595,28 @@ static int wm8960_set_dai_clkdiv(struct snd_soc_dai *codec_dai, switch (div_id) { case WM8960_SYSCLKSEL: - reg = wm8960_read(codec, WM8960_CLOCK1) & 0x1fe; - wm8960_write(codec, WM8960_CLOCK1, reg | div); + reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1fe; + snd_soc_write(codec, WM8960_CLOCK1, reg | div); break; case WM8960_SYSCLKDIV: - reg = wm8960_read(codec, WM8960_CLOCK1) & 0x1f9; - wm8960_write(codec, WM8960_CLOCK1, reg | div); + reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1f9; + snd_soc_write(codec, WM8960_CLOCK1, reg | div); break; case WM8960_DACDIV: - reg = wm8960_read(codec, WM8960_CLOCK1) & 0x1c7; - wm8960_write(codec, WM8960_CLOCK1, reg | div); + reg = snd_soc_read(codec, WM8960_CLOCK1) & 0x1c7; + snd_soc_write(codec, WM8960_CLOCK1, reg | div); break; case WM8960_OPCLKDIV: - reg = wm8960_read(codec, WM8960_PLL1) & 0x03f; - wm8960_write(codec, WM8960_PLL1, reg | div); + reg = snd_soc_read(codec, WM8960_PLL1) & 0x03f; + snd_soc_write(codec, WM8960_PLL1, reg | div); break; case WM8960_DCLKDIV: - reg = wm8960_read(codec, WM8960_CLOCK2) & 0x03f; - wm8960_write(codec, WM8960_CLOCK2, reg | div); + reg = snd_soc_read(codec, WM8960_CLOCK2) & 0x03f; + snd_soc_write(codec, WM8960_CLOCK2, reg | div); break; case WM8960_TOCLKSEL: - reg = wm8960_read(codec, WM8960_ADDCTL1) & 0x1fd; - wm8960_write(codec, WM8960_ADDCTL1, reg | div); + reg = snd_soc_read(codec, WM8960_ADDCTL1) & 0x1fd; + snd_soc_write(codec, WM8960_ADDCTL1, reg | div); break; default: return -EINVAL; @@ -830,8 +776,6 @@ static int wm8960_register(struct wm8960_priv *wm8960) codec->private_data = wm8960; codec->name = "WM8960"; codec->owner = THIS_MODULE; - codec->read = wm8960_read_reg_cache; - codec->write = wm8960_write; codec->bias_level = SND_SOC_BIAS_OFF; codec->set_bias_level = wm8960_set_bias_level; codec->dai = &wm8960_dai; @@ -841,6 +785,12 @@ static int wm8960_register(struct wm8960_priv *wm8960) memcpy(codec->reg_cache, wm8960_reg, sizeof(wm8960_reg)); + ret = snd_soc_codec_set_cache_io(codec, 7, 9); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + goto err; + } + ret = wm8960_reset(codec); if (ret < 0) { dev_err(codec->dev, "Failed to issue reset\n"); @@ -852,26 +802,26 @@ static int wm8960_register(struct wm8960_priv *wm8960) wm8960_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* Latch the update bits */ - reg = wm8960_read(codec, WM8960_LINVOL); - wm8960_write(codec, WM8960_LINVOL, reg | 0x100); - reg = wm8960_read(codec, WM8960_RINVOL); - wm8960_write(codec, WM8960_RINVOL, reg | 0x100); - reg = wm8960_read(codec, WM8960_LADC); - wm8960_write(codec, WM8960_LADC, reg | 0x100); - reg = wm8960_read(codec, WM8960_RADC); - wm8960_write(codec, WM8960_RADC, reg | 0x100); - reg = wm8960_read(codec, WM8960_LDAC); - wm8960_write(codec, WM8960_LDAC, reg | 0x100); - reg = wm8960_read(codec, WM8960_RDAC); - wm8960_write(codec, WM8960_RDAC, reg | 0x100); - reg = wm8960_read(codec, WM8960_LOUT1); - wm8960_write(codec, WM8960_LOUT1, reg | 0x100); - reg = wm8960_read(codec, WM8960_ROUT1); - wm8960_write(codec, WM8960_ROUT1, reg | 0x100); - reg = wm8960_read(codec, WM8960_LOUT2); - wm8960_write(codec, WM8960_LOUT2, reg | 0x100); - reg = wm8960_read(codec, WM8960_ROUT2); - wm8960_write(codec, WM8960_ROUT2, reg | 0x100); + reg = snd_soc_read(codec, WM8960_LINVOL); + snd_soc_write(codec, WM8960_LINVOL, reg | 0x100); + reg = snd_soc_read(codec, WM8960_RINVOL); + snd_soc_write(codec, WM8960_RINVOL, reg | 0x100); + reg = snd_soc_read(codec, WM8960_LADC); + snd_soc_write(codec, WM8960_LADC, reg | 0x100); + reg = snd_soc_read(codec, WM8960_RADC); + snd_soc_write(codec, WM8960_RADC, reg | 0x100); + reg = snd_soc_read(codec, WM8960_LDAC); + snd_soc_write(codec, WM8960_LDAC, reg | 0x100); + reg = snd_soc_read(codec, WM8960_RDAC); + snd_soc_write(codec, WM8960_RDAC, reg | 0x100); + reg = snd_soc_read(codec, WM8960_LOUT1); + snd_soc_write(codec, WM8960_LOUT1, reg | 0x100); + reg = snd_soc_read(codec, WM8960_ROUT1); + snd_soc_write(codec, WM8960_ROUT1, reg | 0x100); + reg = snd_soc_read(codec, WM8960_LOUT2); + snd_soc_write(codec, WM8960_LOUT2, reg | 0x100); + reg = snd_soc_read(codec, WM8960_ROUT2); + snd_soc_write(codec, WM8960_ROUT2, reg | 0x100); wm8960_codec = codec; diff --git a/sound/soc/codecs/wm8971.c b/sound/soc/codecs/wm8971.c index 032dca22dbd..53f3bc90ea0 100644 --- a/sound/soc/codecs/wm8971.c +++ b/sound/soc/codecs/wm8971.c @@ -59,44 +59,7 @@ static const u16 wm8971_reg[] = { 0x0079, 0x0079, 0x0079, /* 40 */ }; -static inline unsigned int wm8971_read_reg_cache(struct snd_soc_codec *codec, - unsigned int reg) -{ - u16 *cache = codec->reg_cache; - if (reg < WM8971_REG_COUNT) - return cache[reg]; - - return -1; -} - -static inline void wm8971_write_reg_cache(struct snd_soc_codec *codec, - unsigned int reg, unsigned int value) -{ - u16 *cache = codec->reg_cache; - if (reg < WM8971_REG_COUNT) - cache[reg] = value; -} - -static int wm8971_write(struct snd_soc_codec *codec, unsigned int reg, - unsigned int value) -{ - u8 data[2]; - - /* data is - * D15..D9 WM8753 register offset - * D8...D0 register data - */ - data[0] = (reg << 1) | ((value >> 8) & 0x0001); - data[1] = value & 0x00ff; - - wm8971_write_reg_cache (codec, reg, value); - if (codec->hw_write(codec->control_data, data, 2) == 2) - return 0; - else - return -EIO; -} - -#define wm8971_reset(c) wm8971_write(c, WM8971_RESET, 0) +#define wm8971_reset(c) snd_soc_write(c, WM8971_RESET, 0) /* WM8971 Controls */ static const char *wm8971_bass[] = { "Linear Control", "Adaptive Boost" }; @@ -521,7 +484,7 @@ static int wm8971_set_dai_fmt(struct snd_soc_dai *codec_dai, return -EINVAL; } - wm8971_write(codec, WM8971_IFACE, iface); + snd_soc_write(codec, WM8971_IFACE, iface); return 0; } @@ -533,8 +496,8 @@ static int wm8971_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; struct wm8971_priv *wm8971 = codec->private_data; - u16 iface = wm8971_read_reg_cache(codec, WM8971_IFACE) & 0x1f3; - u16 srate = wm8971_read_reg_cache(codec, WM8971_SRATE) & 0x1c0; + u16 iface = snd_soc_read(codec, WM8971_IFACE) & 0x1f3; + u16 srate = snd_soc_read(codec, WM8971_SRATE) & 0x1c0; int coeff = get_coeff(wm8971->sysclk, params_rate(params)); /* bit size */ @@ -553,9 +516,9 @@ static int wm8971_pcm_hw_params(struct snd_pcm_substream *substream, } /* set iface & srate */ - wm8971_write(codec, WM8971_IFACE, iface); + snd_soc_write(codec, WM8971_IFACE, iface); if (coeff >= 0) - wm8971_write(codec, WM8971_SRATE, srate | + snd_soc_write(codec, WM8971_SRATE, srate | (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb); return 0; @@ -564,33 +527,33 @@ static int wm8971_pcm_hw_params(struct snd_pcm_substream *substream, static int wm8971_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; - u16 mute_reg = wm8971_read_reg_cache(codec, WM8971_ADCDAC) & 0xfff7; + u16 mute_reg = snd_soc_read(codec, WM8971_ADCDAC) & 0xfff7; if (mute) - wm8971_write(codec, WM8971_ADCDAC, mute_reg | 0x8); + snd_soc_write(codec, WM8971_ADCDAC, mute_reg | 0x8); else - wm8971_write(codec, WM8971_ADCDAC, mute_reg); + snd_soc_write(codec, WM8971_ADCDAC, mute_reg); return 0; } static int wm8971_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - u16 pwr_reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e; + u16 pwr_reg = snd_soc_read(codec, WM8971_PWR1) & 0xfe3e; switch (level) { case SND_SOC_BIAS_ON: /* set vmid to 50k and unmute dac */ - wm8971_write(codec, WM8971_PWR1, pwr_reg | 0x00c1); + snd_soc_write(codec, WM8971_PWR1, pwr_reg | 0x00c1); break; case SND_SOC_BIAS_PREPARE: break; case SND_SOC_BIAS_STANDBY: /* mute dac and set vmid to 500k, enable VREF */ - wm8971_write(codec, WM8971_PWR1, pwr_reg | 0x0140); + snd_soc_write(codec, WM8971_PWR1, pwr_reg | 0x0140); break; case SND_SOC_BIAS_OFF: - wm8971_write(codec, WM8971_PWR1, 0x0001); + snd_soc_write(codec, WM8971_PWR1, 0x0001); break; } codec->bias_level = level; @@ -667,8 +630,8 @@ static int wm8971_resume(struct platform_device *pdev) /* charge wm8971 caps */ if (codec->suspend_bias_level == SND_SOC_BIAS_ON) { - reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e; - wm8971_write(codec, WM8971_PWR1, reg | 0x01c0); + reg = snd_soc_read(codec, WM8971_PWR1) & 0xfe3e; + snd_soc_write(codec, WM8971_PWR1, reg | 0x01c0); codec->bias_level = SND_SOC_BIAS_ON; queue_delayed_work(wm8971_workq, &codec->delayed_work, msecs_to_jiffies(1000)); @@ -684,8 +647,6 @@ static int wm8971_init(struct snd_soc_device *socdev) codec->name = "WM8971"; codec->owner = THIS_MODULE; - codec->read = wm8971_read_reg_cache; - codec->write = wm8971_write; codec->set_bias_level = wm8971_set_bias_level; codec->dai = &wm8971_dai; codec->reg_cache_size = ARRAY_SIZE(wm8971_reg); @@ -695,42 +656,48 @@ static int wm8971_init(struct snd_soc_device *socdev) if (codec->reg_cache == NULL) return -ENOMEM; + ret = snd_soc_codec_set_cache_io(codec, 7, 9); + if (ret < 0) { + printk(KERN_ERR "wm8971: failed to set cache I/O: %d\n", ret); + goto err; + } + wm8971_reset(codec); /* register pcms */ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); if (ret < 0) { printk(KERN_ERR "wm8971: failed to create pcms\n"); - goto pcm_err; + goto err; } /* charge output caps - set vmid to 5k for quick power up */ - reg = wm8971_read_reg_cache(codec, WM8971_PWR1) & 0xfe3e; - wm8971_write(codec, WM8971_PWR1, reg | 0x01c0); + reg = snd_soc_read(codec, WM8971_PWR1) & 0xfe3e; + snd_soc_write(codec, WM8971_PWR1, reg | 0x01c0); codec->bias_level = SND_SOC_BIAS_STANDBY; queue_delayed_work(wm8971_workq, &codec->delayed_work, msecs_to_jiffies(1000)); /* set the update bits */ - reg = wm8971_read_reg_cache(codec, WM8971_LDAC); - wm8971_write(codec, WM8971_LDAC, reg | 0x0100); - reg = wm8971_read_reg_cache(codec, WM8971_RDAC); - wm8971_write(codec, WM8971_RDAC, reg | 0x0100); - - reg = wm8971_read_reg_cache(codec, WM8971_LOUT1V); - wm8971_write(codec, WM8971_LOUT1V, reg | 0x0100); - reg = wm8971_read_reg_cache(codec, WM8971_ROUT1V); - wm8971_write(codec, WM8971_ROUT1V, reg | 0x0100); - - reg = wm8971_read_reg_cache(codec, WM8971_LOUT2V); - wm8971_write(codec, WM8971_LOUT2V, reg | 0x0100); - reg = wm8971_read_reg_cache(codec, WM8971_ROUT2V); - wm8971_write(codec, WM8971_ROUT2V, reg | 0x0100); - - reg = wm8971_read_reg_cache(codec, WM8971_LINVOL); - wm8971_write(codec, WM8971_LINVOL, reg | 0x0100); - reg = wm8971_read_reg_cache(codec, WM8971_RINVOL); - wm8971_write(codec, WM8971_RINVOL, reg | 0x0100); + reg = snd_soc_read(codec, WM8971_LDAC); + snd_soc_write(codec, WM8971_LDAC, reg | 0x0100); + reg = snd_soc_read(codec, WM8971_RDAC); + snd_soc_write(codec, WM8971_RDAC, reg | 0x0100); + + reg = snd_soc_read(codec, WM8971_LOUT1V); + snd_soc_write(codec, WM8971_LOUT1V, reg | 0x0100); + reg = snd_soc_read(codec, WM8971_ROUT1V); + snd_soc_write(codec, WM8971_ROUT1V, reg | 0x0100); + + reg = snd_soc_read(codec, WM8971_LOUT2V); + snd_soc_write(codec, WM8971_LOUT2V, reg | 0x0100); + reg = snd_soc_read(codec, WM8971_ROUT2V); + snd_soc_write(codec, WM8971_ROUT2V, reg | 0x0100); + + reg = snd_soc_read(codec, WM8971_LINVOL); + snd_soc_write(codec, WM8971_LINVOL, reg | 0x0100); + reg = snd_soc_read(codec, WM8971_RINVOL); + snd_soc_write(codec, WM8971_RINVOL, reg | 0x0100); snd_soc_add_controls(codec, wm8971_snd_controls, ARRAY_SIZE(wm8971_snd_controls)); @@ -745,7 +712,7 @@ static int wm8971_init(struct snd_soc_device *socdev) card_err: snd_soc_free_pcms(socdev); snd_soc_dapm_free(socdev); -pcm_err: +err: kfree(codec->reg_cache); return ret; } diff --git a/sound/soc/codecs/wm8988.c b/sound/soc/codecs/wm8988.c index 6f15acd1048..7d5b807bdb4 100644 --- a/sound/soc/codecs/wm8988.c +++ b/sound/soc/codecs/wm8988.c @@ -57,50 +57,7 @@ struct wm8988_priv { }; -/* - * read wm8988 register cache - */ -static inline unsigned int wm8988_read_reg_cache(struct snd_soc_codec *codec, - unsigned int reg) -{ - u16 *cache = codec->reg_cache; - if (reg > WM8988_NUM_REG) - return -1; - return cache[reg]; -} - -/* - * write wm8988 register cache - */ -static inline void wm8988_write_reg_cache(struct snd_soc_codec *codec, - unsigned int reg, unsigned int value) -{ - u16 *cache = codec->reg_cache; - if (reg > WM8988_NUM_REG) - return; - cache[reg] = value; -} - -static int wm8988_write(struct snd_soc_codec *codec, unsigned int reg, - unsigned int value) -{ - u8 data[2]; - - /* data is - * D15..D9 WM8753 register offset - * D8...D0 register data - */ - data[0] = (reg << 1) | ((value >> 8) & 0x0001); - data[1] = value & 0x00ff; - - wm8988_write_reg_cache(codec, reg, value); - if (codec->hw_write(codec->control_data, data, 2) == 2) - return 0; - else - return -EIO; -} - -#define wm8988_reset(c) wm8988_write(c, WM8988_RESET, 0) +#define wm8988_reset(c) snd_soc_write(c, WM8988_RESET, 0) /* * WM8988 Controls @@ -226,15 +183,15 @@ static int wm8988_lrc_control(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; - u16 adctl2 = wm8988_read_reg_cache(codec, WM8988_ADCTL2); + u16 adctl2 = snd_soc_read(codec, WM8988_ADCTL2); /* Use the DAC to gate LRC if active, otherwise use ADC */ - if (wm8988_read_reg_cache(codec, WM8988_PWR2) & 0x180) + if (snd_soc_read(codec, WM8988_PWR2) & 0x180) adctl2 &= ~0x4; else adctl2 |= 0x4; - return wm8988_write(codec, WM8988_ADCTL2, adctl2); + return snd_soc_write(codec, WM8988_ADCTL2, adctl2); } static const char *wm8988_line_texts[] = { @@ -619,7 +576,7 @@ static int wm8988_set_dai_fmt(struct snd_soc_dai *codec_dai, return -EINVAL; } - wm8988_write(codec, WM8988_IFACE, iface); + snd_soc_write(codec, WM8988_IFACE, iface); return 0; } @@ -653,8 +610,8 @@ static int wm8988_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; struct wm8988_priv *wm8988 = codec->private_data; - u16 iface = wm8988_read_reg_cache(codec, WM8988_IFACE) & 0x1f3; - u16 srate = wm8988_read_reg_cache(codec, WM8988_SRATE) & 0x180; + u16 iface = snd_soc_read(codec, WM8988_IFACE) & 0x1f3; + u16 srate = snd_soc_read(codec, WM8988_SRATE) & 0x180; int coeff; coeff = get_coeff(wm8988->sysclk, params_rate(params)); @@ -685,9 +642,9 @@ static int wm8988_pcm_hw_params(struct snd_pcm_substream *substream, } /* set iface & srate */ - wm8988_write(codec, WM8988_IFACE, iface); + snd_soc_write(codec, WM8988_IFACE, iface); if (coeff >= 0) - wm8988_write(codec, WM8988_SRATE, srate | + snd_soc_write(codec, WM8988_SRATE, srate | (coeff_div[coeff].sr << 1) | coeff_div[coeff].usb); return 0; @@ -696,19 +653,19 @@ static int wm8988_pcm_hw_params(struct snd_pcm_substream *substream, static int wm8988_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; - u16 mute_reg = wm8988_read_reg_cache(codec, WM8988_ADCDAC) & 0xfff7; + u16 mute_reg = snd_soc_read(codec, WM8988_ADCDAC) & 0xfff7; if (mute) - wm8988_write(codec, WM8988_ADCDAC, mute_reg | 0x8); + snd_soc_write(codec, WM8988_ADCDAC, mute_reg | 0x8); else - wm8988_write(codec, WM8988_ADCDAC, mute_reg); + snd_soc_write(codec, WM8988_ADCDAC, mute_reg); return 0; } static int wm8988_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { - u16 pwr_reg = wm8988_read_reg_cache(codec, WM8988_PWR1) & ~0x1c1; + u16 pwr_reg = snd_soc_read(codec, WM8988_PWR1) & ~0x1c1; switch (level) { case SND_SOC_BIAS_ON: @@ -716,24 +673,24 @@ static int wm8988_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_PREPARE: /* VREF, VMID=2x50k, digital enabled */ - wm8988_write(codec, WM8988_PWR1, pwr_reg | 0x00c0); + snd_soc_write(codec, WM8988_PWR1, pwr_reg | 0x00c0); break; case SND_SOC_BIAS_STANDBY: if (codec->bias_level == SND_SOC_BIAS_OFF) { /* VREF, VMID=2x5k */ - wm8988_write(codec, WM8988_PWR1, pwr_reg | 0x1c1); + snd_soc_write(codec, WM8988_PWR1, pwr_reg | 0x1c1); /* Charge caps */ msleep(100); } /* VREF, VMID=2*500k, digital stopped */ - wm8988_write(codec, WM8988_PWR1, pwr_reg | 0x0141); + snd_soc_write(codec, WM8988_PWR1, pwr_reg | 0x0141); break; case SND_SOC_BIAS_OFF: - wm8988_write(codec, WM8988_PWR1, 0x0000); + snd_soc_write(codec, WM8988_PWR1, 0x0000); break; } codec->bias_level = level; @@ -887,8 +844,6 @@ static int wm8988_register(struct wm8988_priv *wm8988) codec->private_data = wm8988; codec->name = "WM8988"; codec->owner = THIS_MODULE; - codec->read = wm8988_read_reg_cache; - codec->write = wm8988_write; codec->dai = &wm8988_dai; codec->num_dai = 1; codec->reg_cache_size = ARRAY_SIZE(wm8988->reg_cache); @@ -899,6 +854,12 @@ static int wm8988_register(struct wm8988_priv *wm8988) memcpy(codec->reg_cache, wm8988_reg, sizeof(wm8988_reg)); + ret = snd_soc_codec_set_cache_io(codec, 7, 9); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + goto err; + } + ret = wm8988_reset(codec); if (ret < 0) { dev_err(codec->dev, "Failed to issue reset\n"); @@ -906,16 +867,16 @@ static int wm8988_register(struct wm8988_priv *wm8988) } /* set the update bits (we always update left then right) */ - reg = wm8988_read_reg_cache(codec, WM8988_RADC); - wm8988_write(codec, WM8988_RADC, reg | 0x100); - reg = wm8988_read_reg_cache(codec, WM8988_RDAC); - wm8988_write(codec, WM8988_RDAC, reg | 0x0100); - reg = wm8988_read_reg_cache(codec, WM8988_ROUT1V); - wm8988_write(codec, WM8988_ROUT1V, reg | 0x0100); - reg = wm8988_read_reg_cache(codec, WM8988_ROUT2V); - wm8988_write(codec, WM8988_ROUT2V, reg | 0x0100); - reg = wm8988_read_reg_cache(codec, WM8988_RINVOL); - wm8988_write(codec, WM8988_RINVOL, reg | 0x0100); + reg = snd_soc_read(codec, WM8988_RADC); + snd_soc_write(codec, WM8988_RADC, reg | 0x100); + reg = snd_soc_read(codec, WM8988_RDAC); + snd_soc_write(codec, WM8988_RDAC, reg | 0x0100); + reg = snd_soc_read(codec, WM8988_ROUT1V); + snd_soc_write(codec, WM8988_ROUT1V, reg | 0x0100); + reg = snd_soc_read(codec, WM8988_ROUT2V); + snd_soc_write(codec, WM8988_ROUT2V, reg | 0x0100); + reg = snd_soc_read(codec, WM8988_RINVOL); + snd_soc_write(codec, WM8988_RINVOL, reg | 0x0100); wm8988_set_bias_level(&wm8988->codec, SND_SOC_BIAS_STANDBY); diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c new file mode 100644 index 00000000000..4eb4333a0ef --- /dev/null +++ b/sound/soc/soc-cache.c @@ -0,0 +1,105 @@ +/* + * soc-cache.c -- ASoC register cache helpers + * + * Copyright 2009 Wolfson Microelectronics PLC. + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include + +static unsigned int snd_soc_7_9_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg >= codec->reg_cache_size) + return -1; + return cache[reg]; +} + +static int snd_soc_7_9_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u16 *cache = codec->reg_cache; + u8 data[2]; + int ret; + + BUG_ON(codec->volatile_register); + + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + if (reg < codec->reg_cache_size) + cache[reg] = value; + ret = codec->hw_write(codec->control_data, data, 2); + if (ret == 2) + return 0; + if (ret < 0) + return ret; + else + return -EIO; +} + + +static struct { + int addr_bits; + int data_bits; + int (*write)(struct snd_soc_codec *, unsigned int, unsigned int); + unsigned int (*read)(struct snd_soc_codec *, unsigned int); +} io_types[] = { + { 7, 9, snd_soc_7_9_write, snd_soc_7_9_read }, +}; + +/** + * snd_soc_codec_set_cache_io: Set up standard I/O functions. + * + * @codec: CODEC to configure. + * @type: Type of cache. + * @addr_bits: Number of bits of register address data. + * @data_bits: Number of bits of data per register. + * + * Register formats are frequently shared between many I2C and SPI + * devices. In order to promote code reuse the ASoC core provides + * some standard implementations of CODEC read and write operations + * which can be set up using this function. + * + * The caller is responsible for allocating and initialising the + * actual cache. + * + * Note that at present this code cannot be used by CODECs with + * volatile registers. + */ +int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec, + int addr_bits, int data_bits) +{ + int i; + + /* We don't support volatile registers yet - refactoring of + * the hw_read operation will be required to do so. */ + if (codec->volatile_register) { + printk(KERN_ERR "Volatile registers not yet supported\n"); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(io_types); i++) + if (io_types[i].addr_bits == addr_bits && + io_types[i].data_bits == data_bits) + break; + if (i == ARRAY_SIZE(io_types)) { + printk(KERN_ERR + "No I/O functions for %d bit address %d bit data\n", + addr_bits, data_bits); + return -EINVAL; + } + + codec->write = io_types[i].write; + codec->read = io_types[i].read; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_codec_set_cache_io); -- cgit v1.2.3 From 1e30a5828e4e9e49fcc6e471bf0d99d4fd273ba4 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 5 Jul 2009 17:28:41 +0100 Subject: ASoC: Remove unused AK4535 hardware read functionality Nothing uses it and the existing hw_read operation needs to be refectored so it's easier to remove it rather than work with it. Support can be re-added if the code requires volatile registers. Signed-off-by: Mark Brown --- sound/soc/codecs/ak4535.c | 16 ---------------- 1 file changed, 16 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/ak4535.c b/sound/soc/codecs/ak4535.c index dd338020276..0abec0d29a9 100644 --- a/sound/soc/codecs/ak4535.c +++ b/sound/soc/codecs/ak4535.c @@ -59,21 +59,6 @@ static inline unsigned int ak4535_read_reg_cache(struct snd_soc_codec *codec, return cache[reg]; } -static inline unsigned int ak4535_read(struct snd_soc_codec *codec, - unsigned int reg) -{ - u8 data; - data = reg; - - if (codec->hw_write(codec->control_data, &data, 1) != 1) - return -EIO; - - if (codec->hw_read(codec->control_data, &data, 1) != 1) - return -EIO; - - return data; -}; - /* * write ak4535 register cache */ @@ -635,7 +620,6 @@ static int ak4535_probe(struct platform_device *pdev) #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) if (setup->i2c_address) { codec->hw_write = (hw_write_t)i2c_master_send; - codec->hw_read = (hw_read_t)i2c_master_recv; ret = ak4535_add_i2c_device(pdev, setup); } #endif -- cgit v1.2.3 From 5f345346dd715d53fb2ed168a4fd432e1bdcb14d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 5 Jul 2009 17:35:28 +0100 Subject: ASoC: Remove use of hw_read from TLV320AIC3x driver The TLV320AIC3x driver is currently the only user of the CODEC hw_read operation and is jumping through some hoops in order to do so. In order to support future refactoring to make the hw_read operation more usable unwrap the usage in this driver to avoid its use. Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic3x.c | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index ab099f48248..0cf401fec80 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -145,8 +145,8 @@ static int aic3x_read(struct snd_soc_codec *codec, unsigned int reg, u8 *value) { *value = reg & 0xff; - if (codec->hw_read(codec->control_data, value, 1) != 1) - return -EIO; + + value[0] = i2c_smbus_read_byte_data(codec->control_data, value[0]); aic3x_write_reg_cache(codec, reg, *value); return 0; @@ -1316,12 +1316,6 @@ static struct i2c_driver aic3x_i2c_driver = { .id_table = aic3x_i2c_id, }; -static int aic3x_i2c_read(struct i2c_client *client, u8 *value, int len) -{ - value[0] = i2c_smbus_read_byte_data(client, value[0]); - return (len == 1); -} - static int aic3x_add_i2c_device(struct platform_device *pdev, const struct aic3x_setup_data *setup) { @@ -1394,7 +1388,6 @@ static int aic3x_probe(struct platform_device *pdev) #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) if (setup->i2c_address) { codec->hw_write = (hw_write_t) i2c_master_send; - codec->hw_read = (hw_read_t) aic3x_i2c_read; ret = aic3x_add_i2c_device(pdev, setup); } #else -- cgit v1.2.3 From f6f1eb1033959f3633eafbe06dcac92b9c503c41 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sun, 5 Jul 2009 17:57:57 +0100 Subject: ASoC: Factor out WM8580 register cache code Note the slightly tricky cache usage in the volume update function due to the requirement for a separate write for the VU bit. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8580.c | 148 +++++++++++++--------------------------------- 1 file changed, 41 insertions(+), 107 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index 97b9ed95d28..2250adea749 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -205,73 +205,6 @@ struct wm8580_priv { struct pll_state b; }; -/* - * read wm8580 register cache - */ -static inline unsigned int wm8580_read_reg_cache(struct snd_soc_codec *codec, - unsigned int reg) -{ - u16 *cache = codec->reg_cache; - BUG_ON(reg >= ARRAY_SIZE(wm8580_reg)); - return cache[reg]; -} - -/* - * write wm8580 register cache - */ -static inline void wm8580_write_reg_cache(struct snd_soc_codec *codec, - unsigned int reg, unsigned int value) -{ - u16 *cache = codec->reg_cache; - - cache[reg] = value; -} - -/* - * write to the WM8580 register space - */ -static int wm8580_write(struct snd_soc_codec *codec, unsigned int reg, - unsigned int value) -{ - u8 data[2]; - - BUG_ON(reg >= ARRAY_SIZE(wm8580_reg)); - - /* Registers are 9 bits wide */ - value &= 0x1ff; - - switch (reg) { - case WM8580_RESET: - /* Uncached */ - break; - default: - if (value == wm8580_read_reg_cache(codec, reg)) - return 0; - } - - /* data is - * D15..D9 WM8580 register offset - * D8...D0 register data - */ - data[0] = (reg << 1) | ((value >> 8) & 0x0001); - data[1] = value & 0x00ff; - - wm8580_write_reg_cache(codec, reg, value); - if (codec->hw_write(codec->control_data, data, 2) == 2) - return 0; - else - return -EIO; -} - -static inline unsigned int wm8580_read(struct snd_soc_codec *codec, - unsigned int reg) -{ - switch (reg) { - default: - return wm8580_read_reg_cache(codec, reg); - } -} - static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); static int wm8580_out_vu(struct snd_kcontrol *kcontrol, @@ -280,25 +213,22 @@ static int wm8580_out_vu(struct snd_kcontrol *kcontrol, struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + u16 *reg_cache = codec->reg_cache; unsigned int reg = mc->reg; unsigned int reg2 = mc->rreg; int ret; - u16 val; /* Clear the register cache so we write without VU set */ - wm8580_write_reg_cache(codec, reg, 0); - wm8580_write_reg_cache(codec, reg2, 0); + reg_cache[reg] = 0; + reg_cache[reg2] = 0; ret = snd_soc_put_volsw_2r(kcontrol, ucontrol); if (ret < 0) return ret; /* Now write again with the volume update bit set */ - val = wm8580_read_reg_cache(codec, reg); - wm8580_write(codec, reg, val | 0x0100); - - val = wm8580_read_reg_cache(codec, reg2); - wm8580_write(codec, reg2, val | 0x0100); + snd_soc_update_bits(codec, reg, 0x100, 0x100); + snd_soc_update_bits(codec, reg2, 0x100, 0x100); return 0; } @@ -521,27 +451,27 @@ static int wm8580_set_dai_pll(struct snd_soc_dai *codec_dai, /* Always disable the PLL - it is not safe to leave it running * while reprogramming it. */ - reg = wm8580_read(codec, WM8580_PWRDN2); - wm8580_write(codec, WM8580_PWRDN2, reg | pwr_mask); + reg = snd_soc_read(codec, WM8580_PWRDN2); + snd_soc_write(codec, WM8580_PWRDN2, reg | pwr_mask); if (!freq_in || !freq_out) return 0; - wm8580_write(codec, WM8580_PLLA1 + offset, pll_div.k & 0x1ff); - wm8580_write(codec, WM8580_PLLA2 + offset, (pll_div.k >> 9) & 0xff); - wm8580_write(codec, WM8580_PLLA3 + offset, + snd_soc_write(codec, WM8580_PLLA1 + offset, pll_div.k & 0x1ff); + snd_soc_write(codec, WM8580_PLLA2 + offset, (pll_div.k >> 9) & 0xff); + snd_soc_write(codec, WM8580_PLLA3 + offset, (pll_div.k >> 18 & 0xf) | (pll_div.n << 4)); - reg = wm8580_read(codec, WM8580_PLLA4 + offset); + reg = snd_soc_read(codec, WM8580_PLLA4 + offset); reg &= ~0x3f; reg |= pll_div.prescale | pll_div.postscale << 1 | pll_div.freqmode << 3; - wm8580_write(codec, WM8580_PLLA4 + offset, reg); + snd_soc_write(codec, WM8580_PLLA4 + offset, reg); /* All done, turn it on */ - reg = wm8580_read(codec, WM8580_PWRDN2); - wm8580_write(codec, WM8580_PWRDN2, reg & ~pwr_mask); + reg = snd_soc_read(codec, WM8580_PWRDN2); + snd_soc_write(codec, WM8580_PWRDN2, reg & ~pwr_mask); return 0; } @@ -556,7 +486,7 @@ static int wm8580_paif_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; - u16 paifb = wm8580_read(codec, WM8580_PAIF3 + dai->id); + u16 paifb = snd_soc_read(codec, WM8580_PAIF3 + dai->id); paifb &= ~WM8580_AIF_LENGTH_MASK; /* bit size */ @@ -576,7 +506,7 @@ static int wm8580_paif_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - wm8580_write(codec, WM8580_PAIF3 + dai->id, paifb); + snd_soc_write(codec, WM8580_PAIF3 + dai->id, paifb); return 0; } @@ -588,8 +518,8 @@ static int wm8580_set_paif_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int aifb; int can_invert_lrclk; - aifa = wm8580_read(codec, WM8580_PAIF1 + codec_dai->id); - aifb = wm8580_read(codec, WM8580_PAIF3 + codec_dai->id); + aifa = snd_soc_read(codec, WM8580_PAIF1 + codec_dai->id); + aifb = snd_soc_read(codec, WM8580_PAIF3 + codec_dai->id); aifb &= ~(WM8580_AIF_FMT_MASK | WM8580_AIF_LRP | WM8580_AIF_BCP); @@ -655,8 +585,8 @@ static int wm8580_set_paif_dai_fmt(struct snd_soc_dai *codec_dai, return -EINVAL; } - wm8580_write(codec, WM8580_PAIF1 + codec_dai->id, aifa); - wm8580_write(codec, WM8580_PAIF3 + codec_dai->id, aifb); + snd_soc_write(codec, WM8580_PAIF1 + codec_dai->id, aifa); + snd_soc_write(codec, WM8580_PAIF3 + codec_dai->id, aifb); return 0; } @@ -669,7 +599,7 @@ static int wm8580_set_dai_clkdiv(struct snd_soc_dai *codec_dai, switch (div_id) { case WM8580_MCLK: - reg = wm8580_read(codec, WM8580_PLLB4); + reg = snd_soc_read(codec, WM8580_PLLB4); reg &= ~WM8580_PLLB4_MCLKOUTSRC_MASK; switch (div) { @@ -691,11 +621,11 @@ static int wm8580_set_dai_clkdiv(struct snd_soc_dai *codec_dai, default: return -EINVAL; } - wm8580_write(codec, WM8580_PLLB4, reg); + snd_soc_write(codec, WM8580_PLLB4, reg); break; case WM8580_DAC_CLKSEL: - reg = wm8580_read(codec, WM8580_CLKSEL); + reg = snd_soc_read(codec, WM8580_CLKSEL); reg &= ~WM8580_CLKSEL_DAC_CLKSEL_MASK; switch (div) { @@ -713,11 +643,11 @@ static int wm8580_set_dai_clkdiv(struct snd_soc_dai *codec_dai, default: return -EINVAL; } - wm8580_write(codec, WM8580_CLKSEL, reg); + snd_soc_write(codec, WM8580_CLKSEL, reg); break; case WM8580_CLKOUTSRC: - reg = wm8580_read(codec, WM8580_PLLB4); + reg = snd_soc_read(codec, WM8580_PLLB4); reg &= ~WM8580_PLLB4_CLKOUTSRC_MASK; switch (div) { @@ -739,7 +669,7 @@ static int wm8580_set_dai_clkdiv(struct snd_soc_dai *codec_dai, default: return -EINVAL; } - wm8580_write(codec, WM8580_PLLB4, reg); + snd_soc_write(codec, WM8580_PLLB4, reg); break; default: @@ -754,14 +684,14 @@ static int wm8580_digital_mute(struct snd_soc_dai *codec_dai, int mute) struct snd_soc_codec *codec = codec_dai->codec; unsigned int reg; - reg = wm8580_read(codec, WM8580_DAC_CONTROL5); + reg = snd_soc_read(codec, WM8580_DAC_CONTROL5); if (mute) reg |= WM8580_DAC_CONTROL5_MUTEALL; else reg &= ~WM8580_DAC_CONTROL5_MUTEALL; - wm8580_write(codec, WM8580_DAC_CONTROL5, reg); + snd_soc_write(codec, WM8580_DAC_CONTROL5, reg); return 0; } @@ -778,20 +708,20 @@ static int wm8580_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_STANDBY: if (codec->bias_level == SND_SOC_BIAS_OFF) { /* Power up and get individual control of the DACs */ - reg = wm8580_read(codec, WM8580_PWRDN1); + reg = snd_soc_read(codec, WM8580_PWRDN1); reg &= ~(WM8580_PWRDN1_PWDN | WM8580_PWRDN1_ALLDACPD); - wm8580_write(codec, WM8580_PWRDN1, reg); + snd_soc_write(codec, WM8580_PWRDN1, reg); /* Make VMID high impedence */ - reg = wm8580_read(codec, WM8580_ADC_CONTROL1); + reg = snd_soc_read(codec, WM8580_ADC_CONTROL1); reg &= ~0x100; - wm8580_write(codec, WM8580_ADC_CONTROL1, reg); + snd_soc_write(codec, WM8580_ADC_CONTROL1, reg); } break; case SND_SOC_BIAS_OFF: - reg = wm8580_read(codec, WM8580_PWRDN1); - wm8580_write(codec, WM8580_PWRDN1, reg | WM8580_PWRDN1_PWDN); + reg = snd_soc_read(codec, WM8580_PWRDN1); + snd_soc_write(codec, WM8580_PWRDN1, reg | WM8580_PWRDN1_PWDN); break; } codec->bias_level = level; @@ -920,8 +850,6 @@ static int wm8580_register(struct wm8580_priv *wm8580) codec->private_data = wm8580; codec->name = "WM8580"; codec->owner = THIS_MODULE; - codec->read = wm8580_read_reg_cache; - codec->write = wm8580_write; codec->bias_level = SND_SOC_BIAS_OFF; codec->set_bias_level = wm8580_set_bias_level; codec->dai = wm8580_dai; @@ -931,6 +859,12 @@ static int wm8580_register(struct wm8580_priv *wm8580) memcpy(codec->reg_cache, wm8580_reg, sizeof(wm8580_reg)); + ret = snd_soc_codec_set_cache_io(codec, 7, 9); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + goto err; + } + for (i = 0; i < ARRAY_SIZE(wm8580->supplies); i++) wm8580->supplies[i].supply = wm8580_supply_names[i]; @@ -949,7 +883,7 @@ static int wm8580_register(struct wm8580_priv *wm8580) } /* Get the codec into a known state */ - ret = wm8580_write(codec, WM8580_RESET, 0); + ret = snd_soc_write(codec, WM8580_RESET, 0); if (ret != 0) { dev_err(codec->dev, "Failed to reset codec: %d\n", ret); goto err_regulator_enable; -- cgit v1.2.3 From 6e5414750af8a521c3ba7805d6ad460ba21df1a1 Mon Sep 17 00:00:00 2001 From: Troy Kisky Date: Tue, 7 Jul 2009 17:36:06 -0700 Subject: ASoC: DaVinci: pcm, don't play 1st sound period twice Update the dma link with correct data as soon as the master channel has copied it. Otherwise, the 1st period will play twice. Signed-off-by: Troy Kisky Signed-off-by: Mark Brown --- sound/soc/davinci/davinci-pcm.c | 1 + 1 file changed, 1 insertion(+) (limited to 'sound/soc') diff --git a/sound/soc/davinci/davinci-pcm.c b/sound/soc/davinci/davinci-pcm.c index a0599658848..8fd0c3cdc71 100644 --- a/sound/soc/davinci/davinci-pcm.c +++ b/sound/soc/davinci/davinci-pcm.c @@ -206,6 +206,7 @@ static int davinci_pcm_prepare(struct snd_pcm_substream *substream) /* Copy self-linked parameter RAM entry into master channel */ edma_read_slot(prtd->slave_lch, &temp); edma_write_slot(prtd->master_lch, &temp); + davinci_pcm_enqueue_dma(substream); return 0; } -- cgit v1.2.3 From d00efa648d3c78ba212f08facf0defdff385dc28 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 8 Jul 2009 16:53:12 +0100 Subject: ASoC: Add TLV information for WM8731 Signed-off-by: Mark Brown --- sound/soc/codecs/wm8731.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index dfbc1bb375f..33e89a1d1e8 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -26,6 +26,7 @@ #include #include #include +#include #include "wm8731.h" @@ -113,20 +114,26 @@ static const struct soc_enum wm8731_enum[] = { SOC_ENUM_SINGLE(WM8731_APDIGI, 1, 4, wm8731_deemph), }; +static const DECLARE_TLV_DB_SCALE(in_tlv, -3450, 150, 0); +static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -1500, 300, 0); +static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); + static const struct snd_kcontrol_new wm8731_snd_controls[] = { -SOC_DOUBLE_R("Master Playback Volume", WM8731_LOUT1V, WM8731_ROUT1V, - 0, 127, 0), +SOC_DOUBLE_R_TLV("Master Playback Volume", WM8731_LOUT1V, WM8731_ROUT1V, + 0, 127, 0, out_tlv), SOC_DOUBLE_R("Master Playback ZC Switch", WM8731_LOUT1V, WM8731_ROUT1V, 7, 1, 0), -SOC_DOUBLE_R("Capture Volume", WM8731_LINVOL, WM8731_RINVOL, 0, 31, 0), +SOC_DOUBLE_R_TLV("Capture Volume", WM8731_LINVOL, WM8731_RINVOL, 0, 31, 0, + in_tlv), SOC_DOUBLE_R("Line Capture Switch", WM8731_LINVOL, WM8731_RINVOL, 7, 1, 1), SOC_SINGLE("Mic Boost (+20dB)", WM8731_APANA, 0, 1, 0), SOC_SINGLE("Capture Mic Switch", WM8731_APANA, 1, 1, 1), -SOC_SINGLE("Sidetone Playback Volume", WM8731_APANA, 6, 3, 1), +SOC_SINGLE_TLV("Sidetone Playback Volume", WM8731_APANA, 6, 3, 1, + sidetone_tlv), SOC_SINGLE("ADC High Pass Filter Switch", WM8731_APDIGI, 0, 1, 1), SOC_SINGLE("Store DC Offset Switch", WM8731_APDIGI, 4, 1, 0), -- cgit v1.2.3 From ef38ed888eee33af93a006db6e337d71320dfa2b Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 8 Jul 2009 17:05:43 +0100 Subject: ASoC: Correct WM8731 Mic Capture Switch control name Signed-off-by: Mark Brown --- sound/soc/codecs/wm8731.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index 33e89a1d1e8..de43263a6fa 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -130,7 +130,7 @@ SOC_DOUBLE_R_TLV("Capture Volume", WM8731_LINVOL, WM8731_RINVOL, 0, 31, 0, SOC_DOUBLE_R("Line Capture Switch", WM8731_LINVOL, WM8731_RINVOL, 7, 1, 1), SOC_SINGLE("Mic Boost (+20dB)", WM8731_APANA, 0, 1, 0), -SOC_SINGLE("Capture Mic Switch", WM8731_APANA, 1, 1, 1), +SOC_SINGLE("Mic Capture Switch", WM8731_APANA, 1, 1, 1), SOC_SINGLE_TLV("Sidetone Playback Volume", WM8731_APANA, 6, 3, 1, sidetone_tlv), -- cgit v1.2.3 From ff7d04b130d76ab3a7b79120070346ccf3a72b3f Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 8 Jul 2009 16:54:51 +0100 Subject: ASoC: DaVinci I2S needs mach/asp.h Reported-by: Troy Kisky Signed-off-by: Mark Brown --- sound/soc/davinci/davinci-i2s.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c index 7c4839994e2..f7330e112be 100644 --- a/sound/soc/davinci/davinci-i2s.c +++ b/sound/soc/davinci/davinci-i2s.c @@ -22,6 +22,8 @@ #include #include +#include + #include "davinci-pcm.h" -- cgit v1.2.3 From 942c435ba79fd263a922bb114d56eccab6250662 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 5 Jun 2009 16:32:59 +0100 Subject: ASoC: Add WM8993 CODEC driver The WM8993 is a highly integrated ultra-low power hi-fi CODEC designed for portable devices such as multimedia phones. Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm8993.c | 2203 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm8993.h | 2132 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 4341 insertions(+) create mode 100644 sound/soc/codecs/wm8993.c create mode 100644 sound/soc/codecs/wm8993.h (limited to 'sound/soc') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 68ea5b6648d..b674d3a9f78 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -44,6 +44,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8971 if I2C select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8990 if I2C + select SND_SOC_WM8993 if I2C select SND_SOC_WM9081 if I2C select SND_SOC_WM9705 if SND_SOC_AC97_BUS select SND_SOC_WM9712 if SND_SOC_AC97_BUS @@ -173,6 +174,9 @@ config SND_SOC_WM8988 config SND_SOC_WM8990 tristate +config SND_SOC_WM8993 + tristate + config SND_SOC_WM9081 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 8ce28a34e8e..301eb08ea5c 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -32,6 +32,7 @@ snd-soc-wm8961-objs := wm8961.o snd-soc-wm8971-objs := wm8971.o snd-soc-wm8988-objs := wm8988.o snd-soc-wm8990-objs := wm8990.o +snd-soc-wm8993-objs := wm8993.o snd-soc-wm9081-objs := wm9081.o snd-soc-wm9705-objs := wm9705.o snd-soc-wm9712-objs := wm9712.o @@ -71,6 +72,7 @@ obj-$(CONFIG_SND_SOC_WM8960) += snd-soc-wm8960.o obj-$(CONFIG_SND_SOC_WM8961) += snd-soc-wm8961.o obj-$(CONFIG_SND_SOC_WM8988) += snd-soc-wm8988.o obj-$(CONFIG_SND_SOC_WM8990) += snd-soc-wm8990.o +obj-$(CONFIG_SND_SOC_WM8993) += snd-soc-wm8993.o obj-$(CONFIG_SND_SOC_WM9081) += snd-soc-wm9081.o obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c new file mode 100644 index 00000000000..3e5c65a1ea8 --- /dev/null +++ b/sound/soc/codecs/wm8993.c @@ -0,0 +1,2203 @@ +/* + * wm8993.c -- WM8993 ALSA SoC audio driver + * + * Copyright 2009 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8993.h" + +static u16 wm8993_reg_defaults[WM8993_REGISTER_COUNT] = { + 0x8993, /* R0 - Software Reset */ + 0x0000, /* R1 - Power Management (1) */ + 0x6000, /* R2 - Power Management (2) */ + 0x0000, /* R3 - Power Management (3) */ + 0x4050, /* R4 - Audio Interface (1) */ + 0x4000, /* R5 - Audio Interface (2) */ + 0x01C8, /* R6 - Clocking 1 */ + 0x0000, /* R7 - Clocking 2 */ + 0x0000, /* R8 - Audio Interface (3) */ + 0x0040, /* R9 - Audio Interface (4) */ + 0x0004, /* R10 - DAC CTRL */ + 0x00C0, /* R11 - Left DAC Digital Volume */ + 0x00C0, /* R12 - Right DAC Digital Volume */ + 0x0000, /* R13 - Digital Side Tone */ + 0x0300, /* R14 - ADC CTRL */ + 0x00C0, /* R15 - Left ADC Digital Volume */ + 0x00C0, /* R16 - Right ADC Digital Volume */ + 0x0000, /* R17 */ + 0x0000, /* R18 - GPIO CTRL 1 */ + 0x0010, /* R19 - GPIO1 */ + 0x0000, /* R20 - IRQ_DEBOUNCE */ + 0x0000, /* R21 */ + 0x8000, /* R22 - GPIOCTRL 2 */ + 0x0800, /* R23 - GPIO_POL */ + 0x008B, /* R24 - Left Line Input 1&2 Volume */ + 0x008B, /* R25 - Left Line Input 3&4 Volume */ + 0x008B, /* R26 - Right Line Input 1&2 Volume */ + 0x008B, /* R27 - Right Line Input 3&4 Volume */ + 0x006D, /* R28 - Left Output Volume */ + 0x006D, /* R29 - Right Output Volume */ + 0x0066, /* R30 - Line Outputs Volume */ + 0x0020, /* R31 - HPOUT2 Volume */ + 0x0079, /* R32 - Left OPGA Volume */ + 0x0079, /* R33 - Right OPGA Volume */ + 0x0003, /* R34 - SPKMIXL Attenuation */ + 0x0003, /* R35 - SPKMIXR Attenuation */ + 0x0011, /* R36 - SPKOUT Mixers */ + 0x0100, /* R37 - SPKOUT Boost */ + 0x0079, /* R38 - Speaker Volume Left */ + 0x0079, /* R39 - Speaker Volume Right */ + 0x0000, /* R40 - Input Mixer2 */ + 0x0000, /* R41 - Input Mixer3 */ + 0x0000, /* R42 - Input Mixer4 */ + 0x0000, /* R43 - Input Mixer5 */ + 0x0000, /* R44 - Input Mixer6 */ + 0x0000, /* R45 - Output Mixer1 */ + 0x0000, /* R46 - Output Mixer2 */ + 0x0000, /* R47 - Output Mixer3 */ + 0x0000, /* R48 - Output Mixer4 */ + 0x0000, /* R49 - Output Mixer5 */ + 0x0000, /* R50 - Output Mixer6 */ + 0x0000, /* R51 - HPOUT2 Mixer */ + 0x0000, /* R52 - Line Mixer1 */ + 0x0000, /* R53 - Line Mixer2 */ + 0x0000, /* R54 - Speaker Mixer */ + 0x0000, /* R55 - Additional Control */ + 0x0000, /* R56 - AntiPOP1 */ + 0x0000, /* R57 - AntiPOP2 */ + 0x0000, /* R58 - MICBIAS */ + 0x0000, /* R59 */ + 0x0000, /* R60 - FLL Control 1 */ + 0x0000, /* R61 - FLL Control 2 */ + 0x0000, /* R62 - FLL Control 3 */ + 0x2EE0, /* R63 - FLL Control 4 */ + 0x0002, /* R64 - FLL Control 5 */ + 0x2287, /* R65 - Clocking 3 */ + 0x025F, /* R66 - Clocking 4 */ + 0x0000, /* R67 - MW Slave Control */ + 0x0000, /* R68 */ + 0x0002, /* R69 - Bus Control 1 */ + 0x0000, /* R70 - Write Sequencer 0 */ + 0x0000, /* R71 - Write Sequencer 1 */ + 0x0000, /* R72 - Write Sequencer 2 */ + 0x0000, /* R73 - Write Sequencer 3 */ + 0x0000, /* R74 - Write Sequencer 4 */ + 0x0000, /* R75 - Write Sequencer 5 */ + 0x1F25, /* R76 - Charge Pump 1 */ + 0x0000, /* R77 */ + 0x0000, /* R78 */ + 0x0000, /* R79 */ + 0x0000, /* R80 */ + 0x0000, /* R81 - Class W 0 */ + 0x0000, /* R82 */ + 0x0000, /* R83 */ + 0x0000, /* R84 - DC Servo 0 */ + 0x054A, /* R85 - DC Servo 1 */ + 0x0000, /* R86 */ + 0x0000, /* R87 - DC Servo 3 */ + 0x0000, /* R88 - DC Servo Readback 0 */ + 0x0000, /* R89 - DC Servo Readback 1 */ + 0x0000, /* R90 - DC Servo Readback 2 */ + 0x0000, /* R91 */ + 0x0000, /* R92 */ + 0x0000, /* R93 */ + 0x0000, /* R94 */ + 0x0000, /* R95 */ + 0x0100, /* R96 - Analogue HP 0 */ + 0x0000, /* R97 */ + 0x0000, /* R98 - EQ1 */ + 0x000C, /* R99 - EQ2 */ + 0x000C, /* R100 - EQ3 */ + 0x000C, /* R101 - EQ4 */ + 0x000C, /* R102 - EQ5 */ + 0x000C, /* R103 - EQ6 */ + 0x0FCA, /* R104 - EQ7 */ + 0x0400, /* R105 - EQ8 */ + 0x00D8, /* R106 - EQ9 */ + 0x1EB5, /* R107 - EQ10 */ + 0xF145, /* R108 - EQ11 */ + 0x0B75, /* R109 - EQ12 */ + 0x01C5, /* R110 - EQ13 */ + 0x1C58, /* R111 - EQ14 */ + 0xF373, /* R112 - EQ15 */ + 0x0A54, /* R113 - EQ16 */ + 0x0558, /* R114 - EQ17 */ + 0x168E, /* R115 - EQ18 */ + 0xF829, /* R116 - EQ19 */ + 0x07AD, /* R117 - EQ20 */ + 0x1103, /* R118 - EQ21 */ + 0x0564, /* R119 - EQ22 */ + 0x0559, /* R120 - EQ23 */ + 0x4000, /* R121 - EQ24 */ + 0x0000, /* R122 - Digital Pulls */ + 0x0F08, /* R123 - DRC Control 1 */ + 0x0000, /* R124 - DRC Control 2 */ + 0x0080, /* R125 - DRC Control 3 */ + 0x0000, /* R126 - DRC Control 4 */ +}; + +static struct { + int ratio; + int clk_sys_rate; +} clk_sys_rates[] = { + { 64, 0 }, + { 128, 1 }, + { 192, 2 }, + { 256, 3 }, + { 384, 4 }, + { 512, 5 }, + { 768, 6 }, + { 1024, 7 }, + { 1408, 8 }, + { 1536, 9 }, +}; + +static struct { + int rate; + int sample_rate; +} sample_rates[] = { + { 8000, 0 }, + { 11025, 1 }, + { 12000, 1 }, + { 16000, 2 }, + { 22050, 3 }, + { 24000, 3 }, + { 32000, 4 }, + { 44100, 5 }, + { 48000, 5 }, +}; + +static struct { + int div; /* *10 due to .5s */ + int bclk_div; +} bclk_divs[] = { + { 10, 0 }, + { 15, 1 }, + { 20, 2 }, + { 30, 3 }, + { 40, 4 }, + { 55, 5 }, + { 60, 6 }, + { 80, 7 }, + { 110, 8 }, + { 120, 9 }, + { 160, 10 }, + { 220, 11 }, + { 240, 12 }, + { 320, 13 }, + { 440, 14 }, + { 480, 15 }, +}; + +struct wm8993_priv { + u16 reg_cache[WM8993_REGISTER_COUNT]; + struct wm8993_platform_data pdata; + struct snd_soc_codec codec; + int master; + int sysclk_source; + unsigned int mclk_rate; + unsigned int sysclk_rate; + unsigned int fs; + unsigned int bclk; + int class_w_users; + unsigned int fll_fref; + unsigned int fll_fout; +}; + +static unsigned int wm8993_read_hw(struct snd_soc_codec *codec, u8 reg) +{ + struct i2c_msg xfer[2]; + u16 data; + int ret; + struct i2c_client *i2c = codec->control_data; + + /* Write register */ + xfer[0].addr = i2c->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = ® + + /* Read data */ + xfer[1].addr = i2c->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 2; + xfer[1].buf = (u8 *)&data; + + ret = i2c_transfer(i2c->adapter, xfer, 2); + if (ret != 2) { + dev_err(codec->dev, "Failed to read 0x%x: %d\n", reg, ret); + return 0; + } + + return (data >> 8) | ((data & 0xff) << 8); +} + +static int wm8993_volatile(unsigned int reg) +{ + switch (reg) { + case WM8993_SOFTWARE_RESET: + case WM8993_DC_SERVO_0: + case WM8993_DC_SERVO_READBACK_0: + case WM8993_DC_SERVO_READBACK_1: + case WM8993_DC_SERVO_READBACK_2: + return 1; + default: + return 0; + } +} + +static unsigned int wm8993_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *reg_cache = codec->reg_cache; + + BUG_ON(reg > WM8993_MAX_REGISTER); + + if (wm8993_volatile(reg)) + return wm8993_read_hw(codec, reg); + else + return reg_cache[reg]; +} + +static int wm8993_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u16 *reg_cache = codec->reg_cache; + u8 data[3]; + int ret; + + BUG_ON(reg > WM8993_MAX_REGISTER); + + /* data is + * D15..D9 WM8993 register offset + * D8...D0 register data + */ + data[0] = reg; + data[1] = value >> 8; + data[2] = value & 0x00ff; + + if (!wm8993_volatile(reg)) + reg_cache[reg] = value; + + ret = codec->hw_write(codec->control_data, data, 3); + + if (ret == 3) + return 0; + if (ret < 0) + return ret; + return -EIO; +} + +struct _fll_div { + u16 fll_fratio; + u16 fll_outdiv; + u16 fll_clk_ref_div; + u16 n; + u16 k; +}; + +/* The size in bits of the FLL divide multiplied by 10 + * to allow rounding later */ +#define FIXED_FLL_SIZE ((1 << 16) * 10) + +static struct { + unsigned int min; + unsigned int max; + u16 fll_fratio; + int ratio; +} fll_fratios[] = { + { 0, 64000, 4, 16 }, + { 64000, 128000, 3, 8 }, + { 128000, 256000, 2, 4 }, + { 256000, 1000000, 1, 2 }, + { 1000000, 13500000, 0, 1 }, +}; + +static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, + unsigned int Fout) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod, target; + unsigned int div; + int i; + + /* Fref must be <=13.5MHz */ + div = 1; + while ((Fref / div) > 13500000) { + div *= 2; + + if (div > 8) { + pr_err("Can't scale %dMHz input down to <=13.5MHz\n", + Fref); + return -EINVAL; + } + } + + pr_debug("Fref=%u Fout=%u\n", Fref, Fout); + + /* Apply the division for our remaining calculations */ + Fref /= div; + + /* Fvco should be 90-100MHz; don't check the upper bound */ + div = 0; + target = Fout * 2; + while (target < 90000000) { + div++; + target *= 2; + if (div > 7) { + pr_err("Unable to find FLL_OUTDIV for Fout=%uHz\n", + Fout); + return -EINVAL; + } + } + fll_div->fll_outdiv = div; + + pr_debug("Fvco=%dHz\n", target); + + /* Find an appropraite FLL_FRATIO and factor it out of the target */ + for (i = 0; i < ARRAY_SIZE(fll_fratios); i++) { + if (fll_fratios[i].min <= Fref && Fref <= fll_fratios[i].max) { + fll_div->fll_fratio = fll_fratios[i].fll_fratio; + target /= fll_fratios[i].ratio; + break; + } + } + if (i == ARRAY_SIZE(fll_fratios)) { + pr_err("Unable to find FLL_FRATIO for Fref=%uHz\n", Fref); + return -EINVAL; + } + + /* Now, calculate N.K */ + Ndiv = target / Fref; + + fll_div->n = Ndiv; + Nmod = target % Fref; + pr_debug("Nmod=%d\n", Nmod); + + /* Calculate fractional part - scale up so we can round. */ + Kpart = FIXED_FLL_SIZE * (long long)Nmod; + + do_div(Kpart, Fref); + + K = Kpart & 0xFFFFFFFF; + + if ((K % 10) >= 5) + K += 5; + + /* Move down to proper range now rounding is done */ + fll_div->k = K / 10; + + pr_debug("N=%x K=%x FLL_FRATIO=%x FLL_OUTDIV=%x FLL_CLK_REF_DIV=%x\n", + fll_div->n, fll_div->k, + fll_div->fll_fratio, fll_div->fll_outdiv, + fll_div->fll_clk_ref_div); + + return 0; +} + +static int wm8993_set_fll(struct snd_soc_dai *dai, int fll_id, + unsigned int Fref, unsigned int Fout) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8993_priv *wm8993 = codec->private_data; + u16 reg1, reg4, reg5; + struct _fll_div fll_div; + int ret; + + /* Any change? */ + if (Fref == wm8993->fll_fref && Fout == wm8993->fll_fout) + return 0; + + /* Disable the FLL */ + if (Fout == 0) { + dev_dbg(codec->dev, "FLL disabled\n"); + wm8993->fll_fref = 0; + wm8993->fll_fout = 0; + + reg1 = wm8993_read(codec, WM8993_FLL_CONTROL_1); + reg1 &= ~WM8993_FLL_ENA; + wm8993_write(codec, WM8993_FLL_CONTROL_1, reg1); + + return 0; + } + + ret = fll_factors(&fll_div, Fref, Fout); + if (ret != 0) + return ret; + + reg5 = wm8993_read(codec, WM8993_FLL_CONTROL_5); + reg5 &= ~WM8993_FLL_CLK_SRC_MASK; + + switch (fll_id) { + case WM8993_FLL_MCLK: + break; + + case WM8993_FLL_LRCLK: + reg5 |= 1; + break; + + case WM8993_FLL_BCLK: + reg5 |= 2; + break; + + default: + dev_err(codec->dev, "Unknown FLL ID %d\n", fll_id); + return -EINVAL; + } + + /* Any FLL configuration change requires that the FLL be + * disabled first. */ + reg1 = wm8993_read(codec, WM8993_FLL_CONTROL_1); + reg1 &= ~WM8993_FLL_ENA; + wm8993_write(codec, WM8993_FLL_CONTROL_1, reg1); + + /* Apply the configuration */ + if (fll_div.k) + reg1 |= WM8993_FLL_FRAC_MASK; + else + reg1 &= ~WM8993_FLL_FRAC_MASK; + wm8993_write(codec, WM8993_FLL_CONTROL_1, reg1); + + wm8993_write(codec, WM8993_FLL_CONTROL_2, + (fll_div.fll_outdiv << WM8993_FLL_OUTDIV_SHIFT) | + (fll_div.fll_fratio << WM8993_FLL_FRATIO_SHIFT)); + wm8993_write(codec, WM8993_FLL_CONTROL_3, fll_div.k); + + reg4 = wm8993_read(codec, WM8993_FLL_CONTROL_4); + reg4 &= ~WM8993_FLL_N_MASK; + reg4 |= fll_div.n << WM8993_FLL_N_SHIFT; + wm8993_write(codec, WM8993_FLL_CONTROL_4, reg4); + + reg5 &= ~WM8993_FLL_CLK_REF_DIV_MASK; + reg5 |= fll_div.fll_clk_ref_div << WM8993_FLL_CLK_REF_DIV_SHIFT; + wm8993_write(codec, WM8993_FLL_CONTROL_5, reg5); + + /* Enable the FLL */ + wm8993_write(codec, WM8993_FLL_CONTROL_1, reg1 | WM8993_FLL_ENA); + + dev_dbg(codec->dev, "FLL enabled at %dHz->%dHz\n", Fref, Fout); + + wm8993->fll_fref = Fref; + wm8993->fll_fout = Fout; + + return 0; +} + +static int configure_clock(struct snd_soc_codec *codec) +{ + struct wm8993_priv *wm8993 = codec->private_data; + unsigned int reg; + + /* This should be done on init() for bypass paths */ + switch (wm8993->sysclk_source) { + case WM8993_SYSCLK_MCLK: + dev_dbg(codec->dev, "Using %dHz MCLK\n", wm8993->mclk_rate); + + reg = wm8993_read(codec, WM8993_CLOCKING_2); + reg &= ~WM8993_SYSCLK_SRC; + if (wm8993->mclk_rate > 13500000) { + reg |= WM8993_MCLK_DIV; + wm8993->sysclk_rate = wm8993->mclk_rate / 2; + } else { + reg &= ~WM8993_MCLK_DIV; + wm8993->sysclk_rate = wm8993->mclk_rate; + } + reg &= ~WM8993_MCLK_DIV; + reg &= ~(WM8993_MCLK_DIV | WM8993_SYSCLK_SRC); + wm8993_write(codec, WM8993_CLOCKING_2, reg); + break; + + case WM8993_SYSCLK_FLL: + dev_dbg(codec->dev, "Using %dHz FLL clock\n", + wm8993->fll_fout); + + reg = wm8993_read(codec, WM8993_CLOCKING_2); + reg |= WM8993_SYSCLK_SRC; + if (wm8993->fll_fout > 13500000) { + reg |= WM8993_MCLK_DIV; + wm8993->sysclk_rate = wm8993->fll_fout / 2; + } else { + reg &= ~WM8993_MCLK_DIV; + wm8993->sysclk_rate = wm8993->fll_fout; + } + wm8993_write(codec, WM8993_CLOCKING_2, reg); + break; + + default: + dev_err(codec->dev, "System clock not configured\n"); + return -EINVAL; + } + + dev_dbg(codec->dev, "CLK_SYS is %dHz\n", wm8993->sysclk_rate); + + return 0; +} + +static void wait_for_dc_servo(struct snd_soc_codec *codec, int mask) +{ + unsigned int reg; + int count = 0; + + dev_dbg(codec->dev, "Waiting for DC servo...\n"); + do { + count++; + msleep(1); + reg = wm8993_read(codec, WM8993_DC_SERVO_READBACK_0); + dev_dbg(codec->dev, "DC servo status: %x\n", reg); + } while ((reg & WM8993_DCS_CAL_COMPLETE_MASK) + != WM8993_DCS_CAL_COMPLETE_MASK && count < 1000); + + if ((reg & WM8993_DCS_CAL_COMPLETE_MASK) + != WM8993_DCS_CAL_COMPLETE_MASK) + dev_err(codec->dev, "Timed out waiting for DC Servo\n"); +} + +static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1650, 150, 0); +static const DECLARE_TLV_DB_SCALE(inmix_sw_tlv, 0, 3000, 0); +static const DECLARE_TLV_DB_SCALE(inmix_tlv, -1500, 300, 1); +static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -3600, 300, 0); +static const DECLARE_TLV_DB_SCALE(drc_comp_threash, -4500, 75, 0); +static const DECLARE_TLV_DB_SCALE(drc_comp_amp, -2250, 75, 0); +static const DECLARE_TLV_DB_SCALE(drc_min_tlv, -1800, 600, 0); +static const unsigned int drc_max_tlv[] = { + TLV_DB_RANGE_HEAD(4), + 0, 2, TLV_DB_SCALE_ITEM(1200, 600, 0), + 3, 3, TLV_DB_SCALE_ITEM(3600, 0, 0), +}; +static const DECLARE_TLV_DB_SCALE(drc_qr_tlv, 1200, 600, 0); +static const DECLARE_TLV_DB_SCALE(drc_startup_tlv, -1800, 300, 0); +static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); +static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1); +static const DECLARE_TLV_DB_SCALE(dac_boost_tlv, 0, 600, 0); +static const DECLARE_TLV_DB_SCALE(earpiece_tlv, -600, 600, 0); +static const DECLARE_TLV_DB_SCALE(outmix_tlv, -2100, 300, 0); +static const DECLARE_TLV_DB_SCALE(spkmix_tlv, -300, 300, 0); +static const DECLARE_TLV_DB_SCALE(spkmixout_tlv, -1800, 600, 1); +static const DECLARE_TLV_DB_SCALE(outpga_tlv, -5700, 100, 0); +static const unsigned int spkboost_tlv[] = { + TLV_DB_RANGE_HEAD(7), + 0, 6, TLV_DB_SCALE_ITEM(0, 150, 0), + 7, 7, TLV_DB_SCALE_ITEM(1200, 0, 0), +}; +static const DECLARE_TLV_DB_SCALE(line_tlv, -600, 600, 0); + +static const char *speaker_ref_text[] = { + "SPKVDD/2", + "VMID", +}; + +static const struct soc_enum speaker_ref = + SOC_ENUM_SINGLE(WM8993_SPEAKER_MIXER, 8, 2, speaker_ref_text); + +static const char *speaker_mode_text[] = { + "Class D", + "Class AB", +}; + +static const struct soc_enum speaker_mode = + SOC_ENUM_SINGLE(WM8993_SPKMIXR_ATTENUATION, 8, 2, speaker_mode_text); + +static const char *dac_deemph_text[] = { + "None", + "32kHz", + "44.1kHz", + "48kHz", +}; + +static const struct soc_enum dac_deemph = + SOC_ENUM_SINGLE(WM8993_DAC_CTRL, 4, 4, dac_deemph_text); + +static const char *adc_hpf_text[] = { + "Hi-Fi", + "Voice 1", + "Voice 2", + "Voice 3", +}; + +static const struct soc_enum adc_hpf = + SOC_ENUM_SINGLE(WM8993_ADC_CTRL, 5, 4, adc_hpf_text); + +static const char *drc_path_text[] = { + "ADC", + "DAC" +}; + +static const struct soc_enum drc_path = + SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_1, 14, 2, drc_path_text); + +static const char *drc_r0_text[] = { + "1", + "1/2", + "1/4", + "1/8", + "1/16", + "0", +}; + +static const struct soc_enum drc_r0 = + SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_3, 8, 6, drc_r0_text); + +static const char *drc_r1_text[] = { + "1", + "1/2", + "1/4", + "1/8", + "0", +}; + +static const struct soc_enum drc_r1 = + SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_4, 13, 5, drc_r1_text); + +static const char *drc_attack_text[] = { + "Reserved", + "181us", + "363us", + "726us", + "1.45ms", + "2.9ms", + "5.8ms", + "11.6ms", + "23.2ms", + "46.4ms", + "92.8ms", + "185.6ms", +}; + +static const struct soc_enum drc_attack = + SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_2, 12, 12, drc_attack_text); + +static const char *drc_decay_text[] = { + "186ms", + "372ms", + "743ms", + "1.49s", + "2.97ms", + "5.94ms", + "11.89ms", + "23.78ms", + "47.56ms", +}; + +static const struct soc_enum drc_decay = + SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_2, 8, 9, drc_decay_text); + +static const char *drc_ff_text[] = { + "5 samples", + "9 samples", +}; + +static const struct soc_enum drc_ff = + SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_3, 7, 2, drc_ff_text); + +static const char *drc_qr_rate_text[] = { + "0.725ms", + "1.45ms", + "5.8ms", +}; + +static const struct soc_enum drc_qr_rate = + SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_3, 0, 3, drc_qr_rate_text); + +static const char *drc_smooth_text[] = { + "Low", + "Medium", + "High", +}; + +static const struct soc_enum drc_smooth = + SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_1, 4, 3, drc_smooth_text); + + +/* + * Update the DC servo calibration on gain changes + */ +static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int ret; + + ret = snd_soc_put_volsw_2r(kcontrol, ucontrol); + + /* Only need to do this if the outputs are active */ + if (wm8993_read(codec, WM8993_POWER_MANAGEMENT_1) + & (WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA)) + snd_soc_update_bits(codec, + WM8993_DC_SERVO_0, + WM8993_DCS_TRIG_SINGLE_0 | + WM8993_DCS_TRIG_SINGLE_1, + WM8993_DCS_TRIG_SINGLE_0 | + WM8993_DCS_TRIG_SINGLE_1); + + return ret; +} + +static const struct snd_kcontrol_new wm8993_snd_controls[] = { +SOC_SINGLE_TLV("IN1L Volume", WM8993_LEFT_LINE_INPUT_1_2_VOLUME, 0, 31, 0, + inpga_tlv), +SOC_SINGLE("IN1L Switch", WM8993_LEFT_LINE_INPUT_1_2_VOLUME, 7, 1, 1), +SOC_SINGLE("IN1L ZC Switch", WM8993_LEFT_LINE_INPUT_1_2_VOLUME, 7, 1, 0), + +SOC_SINGLE_TLV("IN1R Volume", WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, 0, 31, 0, + inpga_tlv), +SOC_SINGLE("IN1R Switch", WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, 7, 1, 1), +SOC_SINGLE("IN1R ZC Switch", WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, 7, 1, 0), + + +SOC_SINGLE_TLV("IN2L Volume", WM8993_LEFT_LINE_INPUT_3_4_VOLUME, 0, 31, 0, + inpga_tlv), +SOC_SINGLE("IN2L Switch", WM8993_LEFT_LINE_INPUT_3_4_VOLUME, 7, 1, 1), +SOC_SINGLE("IN2L ZC Switch", WM8993_LEFT_LINE_INPUT_3_4_VOLUME, 7, 1, 0), + +SOC_SINGLE_TLV("IN2R Volume", WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, 0, 31, 0, + inpga_tlv), +SOC_SINGLE("IN2R Switch", WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, 7, 1, 1), +SOC_SINGLE("IN2R ZC Switch", WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, 7, 1, 0), + +SOC_SINGLE_TLV("MIXINL IN2L Volume", WM8993_INPUT_MIXER3, 7, 1, 0, + inmix_sw_tlv), +SOC_SINGLE_TLV("MIXINL IN1L Volume", WM8993_INPUT_MIXER3, 4, 1, 0, + inmix_sw_tlv), +SOC_SINGLE_TLV("MIXINL Output Record Volume", WM8993_INPUT_MIXER3, 0, 7, 0, + inmix_tlv), +SOC_SINGLE_TLV("MIXINL IN1LP Volume", WM8993_INPUT_MIXER5, 6, 7, 0, inmix_tlv), +SOC_SINGLE_TLV("MIXINL Direct Voice Volume", WM8993_INPUT_MIXER5, 0, 6, 0, + inmix_tlv), + +SOC_SINGLE_TLV("MIXINR IN2R Volume", WM8993_INPUT_MIXER4, 7, 1, 0, + inmix_sw_tlv), +SOC_SINGLE_TLV("MIXINR IN1R Volume", WM8993_INPUT_MIXER4, 4, 1, 0, + inmix_sw_tlv), +SOC_SINGLE_TLV("MIXINR Output Record Volume", WM8993_INPUT_MIXER4, 0, 7, 0, + inmix_tlv), +SOC_SINGLE_TLV("MIXINR IN1RP Volume", WM8993_INPUT_MIXER6, 6, 7, 0, inmix_tlv), +SOC_SINGLE_TLV("MIXINR Direct Voice Volume", WM8993_INPUT_MIXER6, 0, 6, 0, + inmix_tlv), + +SOC_DOUBLE_TLV("Digital Sidetone Volume", WM8993_DIGITAL_SIDE_TONE, + 5, 9, 12, 0, sidetone_tlv), + +SOC_SINGLE("DRC Switch", WM8993_DRC_CONTROL_1, 15, 1, 0), +SOC_ENUM("DRC Path", drc_path), +SOC_SINGLE_TLV("DRC Compressor Threashold Volume", WM8993_DRC_CONTROL_2, + 2, 60, 1, drc_comp_threash), +SOC_SINGLE_TLV("DRC Compressor Amplitude Volume", WM8993_DRC_CONTROL_3, + 11, 30, 1, drc_comp_amp), +SOC_ENUM("DRC R0", drc_r0), +SOC_ENUM("DRC R1", drc_r1), +SOC_SINGLE_TLV("DRC Minimum Volume", WM8993_DRC_CONTROL_1, 2, 3, 1, + drc_min_tlv), +SOC_SINGLE_TLV("DRC Maximum Volume", WM8993_DRC_CONTROL_1, 0, 3, 0, + drc_max_tlv), +SOC_ENUM("DRC Attack Rate", drc_attack), +SOC_ENUM("DRC Decay Rate", drc_decay), +SOC_ENUM("DRC FF Delay", drc_ff), +SOC_SINGLE("DRC Anti-clip Switch", WM8993_DRC_CONTROL_1, 9, 1, 0), +SOC_SINGLE("DRC Quick Release Switch", WM8993_DRC_CONTROL_1, 10, 1, 0), +SOC_SINGLE_TLV("DRC Quick Release Volume", WM8993_DRC_CONTROL_3, 2, 3, 0, + drc_qr_tlv), +SOC_ENUM("DRC Quick Release Rate", drc_qr_rate), +SOC_SINGLE("DRC Smoothing Switch", WM8993_DRC_CONTROL_1, 11, 1, 0), +SOC_SINGLE("DRC Smoothing Hysteresis Switch", WM8993_DRC_CONTROL_1, 8, 1, 0), +SOC_ENUM("DRC Smoothing Hysteresis Threashold", drc_smooth), +SOC_SINGLE_TLV("DRC Startup Volume", WM8993_DRC_CONTROL_4, 8, 18, 0, + drc_startup_tlv), + +SOC_SINGLE("EQ Switch", WM8993_EQ1, 0, 1, 0), + +SOC_DOUBLE_R_TLV("Capture Volume", WM8993_LEFT_ADC_DIGITAL_VOLUME, + WM8993_RIGHT_ADC_DIGITAL_VOLUME, 1, 96, 0, digital_tlv), +SOC_SINGLE("ADC High Pass Filter Switch", WM8993_ADC_CTRL, 8, 1, 0), +SOC_ENUM("ADC High Pass Filter Mode", adc_hpf), + +SOC_DOUBLE_R_TLV("Playback Volume", WM8993_LEFT_DAC_DIGITAL_VOLUME, + WM8993_RIGHT_DAC_DIGITAL_VOLUME, 1, 96, 0, digital_tlv), +SOC_SINGLE_TLV("Playback Boost Volume", WM8993_AUDIO_INTERFACE_2, 10, 3, 0, + dac_boost_tlv), +SOC_ENUM("DAC Deemphasis", dac_deemph), + +SOC_SINGLE_TLV("Left Output Mixer IN2RN Volume", WM8993_OUTPUT_MIXER5, 6, 7, 1, + outmix_tlv), +SOC_SINGLE_TLV("Left Output Mixer IN2LN Volume", WM8993_OUTPUT_MIXER3, 6, 7, 1, + outmix_tlv), +SOC_SINGLE_TLV("Left Output Mixer IN2LP Volume", WM8993_OUTPUT_MIXER3, 9, 7, 1, + outmix_tlv), +SOC_SINGLE_TLV("Left Output Mixer IN1L Volume", WM8993_OUTPUT_MIXER3, 0, 7, 1, + outmix_tlv), +SOC_SINGLE_TLV("Left Output Mixer IN1R Volume", WM8993_OUTPUT_MIXER3, 3, 7, 1, + outmix_tlv), +SOC_SINGLE_TLV("Left Output Mixer Right Input Volume", + WM8993_OUTPUT_MIXER5, 3, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Left Output Mixer Left Input Volume", + WM8993_OUTPUT_MIXER5, 0, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Left Output Mixer DAC Volume", WM8993_OUTPUT_MIXER5, 9, 7, 1, + outmix_tlv), + +SOC_SINGLE_TLV("Right Output Mixer IN2LN Volume", + WM8993_OUTPUT_MIXER6, 6, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Right Output Mixer IN2RN Volume", + WM8993_OUTPUT_MIXER4, 6, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Right Output Mixer IN1L Volume", + WM8993_OUTPUT_MIXER4, 3, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Right Output Mixer IN1R Volume", + WM8993_OUTPUT_MIXER4, 0, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Right Output Mixer IN2RP Volume", + WM8993_OUTPUT_MIXER4, 9, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Right Output Mixer Left Input Volume", + WM8993_OUTPUT_MIXER6, 3, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Right Output Mixer Right Input Volume", + WM8993_OUTPUT_MIXER6, 6, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Right Output Mixer DAC Volume", + WM8993_OUTPUT_MIXER6, 9, 7, 1, outmix_tlv), + +SOC_DOUBLE_R_TLV("Output Volume", WM8993_LEFT_OPGA_VOLUME, + WM8993_RIGHT_OPGA_VOLUME, 0, 63, 0, outpga_tlv), +SOC_DOUBLE_R("Output Switch", WM8993_LEFT_OPGA_VOLUME, + WM8993_RIGHT_OPGA_VOLUME, 6, 1, 0), +SOC_DOUBLE_R("Output ZC Switch", WM8993_LEFT_OPGA_VOLUME, + WM8993_RIGHT_OPGA_VOLUME, 7, 1, 0), + +SOC_SINGLE("Earpiece Switch", WM8993_HPOUT2_VOLUME, 5, 1, 1), +SOC_SINGLE_TLV("Earpiece Volume", WM8993_HPOUT2_VOLUME, 4, 1, 1, earpiece_tlv), + +SOC_SINGLE_TLV("SPKL Input Volume", WM8993_SPKMIXL_ATTENUATION, + 5, 1, 1, spkmix_tlv), +SOC_SINGLE_TLV("SPKL IN1LP Volume", WM8993_SPKMIXL_ATTENUATION, + 4, 1, 1, spkmix_tlv), +SOC_SINGLE_TLV("SPKL Output Volume", WM8993_SPKMIXL_ATTENUATION, + 3, 1, 1, spkmix_tlv), +SOC_SINGLE_TLV("SPKL DAC Volume", WM8993_SPKMIXL_ATTENUATION, + 2, 1, 1, spkmix_tlv), + +SOC_SINGLE_TLV("SPKR Input Volume", WM8993_SPKMIXR_ATTENUATION, + 5, 1, 1, spkmix_tlv), +SOC_SINGLE_TLV("SPKR IN1RP Volume", WM8993_SPKMIXR_ATTENUATION, + 4, 1, 1, spkmix_tlv), +SOC_SINGLE_TLV("SPKR Output Volume", WM8993_SPKMIXR_ATTENUATION, + 3, 1, 1, spkmix_tlv), +SOC_SINGLE_TLV("SPKR DAC Volume", WM8993_SPKMIXR_ATTENUATION, + 2, 1, 1, spkmix_tlv), + +SOC_DOUBLE_R_TLV("Speaker Mixer Volume", + WM8993_SPKMIXL_ATTENUATION, WM8993_SPKMIXR_ATTENUATION, + 0, 3, 1, spkmixout_tlv), +SOC_DOUBLE_R_TLV("Speaker Volume", + WM8993_SPEAKER_VOLUME_LEFT, WM8993_SPEAKER_VOLUME_RIGHT, + 0, 63, 0, outpga_tlv), +SOC_DOUBLE_R("Speaker Switch", + WM8993_SPEAKER_VOLUME_LEFT, WM8993_SPEAKER_VOLUME_RIGHT, + 6, 1, 0), +SOC_DOUBLE_R("Speaker ZC Switch", + WM8993_SPEAKER_VOLUME_LEFT, WM8993_SPEAKER_VOLUME_RIGHT, + 7, 1, 0), +SOC_DOUBLE_TLV("Speaker Boost Volume", WM8993_SPKOUT_BOOST, 0, 3, 7, 0, + spkboost_tlv), +SOC_ENUM("Speaker Reference", speaker_ref), +SOC_ENUM("Speaker Mode", speaker_mode), + +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Headphone Volume", + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_READWRITE, + .tlv.p = outpga_tlv, + .info = snd_soc_info_volsw_2r, + .get = snd_soc_get_volsw_2r, .put = wm8993_put_dc_servo, + .private_value = (unsigned long)&(struct soc_mixer_control) { + .reg = WM8993_LEFT_OUTPUT_VOLUME, + .rreg = WM8993_RIGHT_OUTPUT_VOLUME, + .shift = 0, .max = 63 + }, +}, +SOC_DOUBLE_R("Headphone Switch", WM8993_LEFT_OUTPUT_VOLUME, + WM8993_RIGHT_OUTPUT_VOLUME, 6, 1, 0), +SOC_DOUBLE_R("Headphone ZC Switch", WM8993_LEFT_OUTPUT_VOLUME, + WM8993_RIGHT_OUTPUT_VOLUME, 7, 1, 0), + +SOC_SINGLE("LINEOUT1N Switch", WM8993_LINE_OUTPUTS_VOLUME, 6, 1, 1), +SOC_SINGLE("LINEOUT1P Switch", WM8993_LINE_OUTPUTS_VOLUME, 5, 1, 1), +SOC_SINGLE_TLV("LINEOUT1 Volume", WM8993_LINE_OUTPUTS_VOLUME, 4, 1, 1, + line_tlv), + +SOC_SINGLE("LINEOUT2N Switch", WM8993_LINE_OUTPUTS_VOLUME, 2, 1, 1), +SOC_SINGLE("LINEOUT2P Switch", WM8993_LINE_OUTPUTS_VOLUME, 1, 1, 1), +SOC_SINGLE_TLV("LINEOUT2 Volume", WM8993_LINE_OUTPUTS_VOLUME, 0, 1, 1, + line_tlv), +}; + +static const struct snd_kcontrol_new wm8993_eq_controls[] = { +SOC_SINGLE_TLV("EQ1 Volume", WM8993_EQ2, 0, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ2 Volume", WM8993_EQ3, 0, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ3 Volume", WM8993_EQ4, 0, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ4 Volume", WM8993_EQ5, 0, 24, 0, eq_tlv), +SOC_SINGLE_TLV("EQ5 Volume", WM8993_EQ6, 0, 24, 0, eq_tlv), +}; + +static int wm8993_earpiece_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *control, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 reg = wm8993_read(codec, WM8993_ANTIPOP1) & ~WM8993_HPOUT2_IN_ENA; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + reg |= WM8993_HPOUT2_IN_ENA; + wm8993_write(codec, WM8993_ANTIPOP1, reg); + udelay(50); + break; + + case SND_SOC_DAPM_POST_PMD: + wm8993_write(codec, WM8993_ANTIPOP1, reg); + break; + + default: + BUG(); + break; + } + + return 0; +} + +static int clk_sys_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + return configure_clock(codec); + + case SND_SOC_DAPM_POST_PMD: + break; + } + + return 0; +} + +/* + * When used with DAC outputs only the WM8993 charge pump supports + * operation in class W mode, providing very low power consumption + * when used with digital sources. Enable and disable this mode + * automatically depending on the mixer configuration. + * + * Currently the only supported paths are the direct DAC->headphone + * paths (which provide minimum power consumption anyway). + */ +static int wm8993_class_w_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); + struct snd_soc_codec *codec = widget->codec; + struct wm8993_priv *wm8993 = codec->private_data; + int ret; + + /* Turn it off if we're using the main output mixer */ + if (ucontrol->value.integer.value[0] == 0) { + if (wm8993->class_w_users == 0) { + dev_dbg(codec->dev, "Disabling Class W\n"); + snd_soc_update_bits(codec, WM8993_CLASS_W_0, + WM8993_CP_DYN_FREQ | + WM8993_CP_DYN_V, + 0); + } + wm8993->class_w_users++; + } + + /* Implement the change */ + ret = snd_soc_dapm_put_enum_double(kcontrol, ucontrol); + + /* Enable it if we're using the direct DAC path */ + if (ucontrol->value.integer.value[0] == 1) { + if (wm8993->class_w_users == 1) { + dev_dbg(codec->dev, "Enabling Class W\n"); + snd_soc_update_bits(codec, WM8993_CLASS_W_0, + WM8993_CP_DYN_FREQ | + WM8993_CP_DYN_V, + WM8993_CP_DYN_FREQ | + WM8993_CP_DYN_V); + } + wm8993->class_w_users--; + } + + dev_dbg(codec->dev, "Indirect DAC use count now %d\n", + wm8993->class_w_users); + + return ret; +} + +#define SOC_DAPM_ENUM_W(xname, xenum) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = snd_soc_info_enum_double, \ + .get = snd_soc_dapm_get_enum_double, \ + .put = wm8993_class_w_put, \ + .private_value = (unsigned long)&xenum } + +static int hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + unsigned int reg = wm8993_read(codec, WM8993_ANALOGUE_HP_0); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_update_bits(codec, WM8993_CHARGE_PUMP_1, + WM8993_CP_ENA, WM8993_CP_ENA); + + msleep(5); + + snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1, + WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA, + WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA); + + reg |= WM8993_HPOUT1L_DLY | WM8993_HPOUT1R_DLY; + wm8993_write(codec, WM8993_ANALOGUE_HP_0, reg); + + /* Start the DC servo */ + snd_soc_update_bits(codec, WM8993_DC_SERVO_0, + WM8993_DCS_ENA_CHAN_0 | + WM8993_DCS_ENA_CHAN_1 | + WM8993_DCS_TRIG_STARTUP_1 | + WM8993_DCS_TRIG_STARTUP_0, + WM8993_DCS_ENA_CHAN_0 | + WM8993_DCS_ENA_CHAN_1 | + WM8993_DCS_TRIG_STARTUP_1 | + WM8993_DCS_TRIG_STARTUP_0); + wait_for_dc_servo(codec, WM8993_DCS_TRIG_STARTUP_0 | + WM8993_DCS_TRIG_STARTUP_1); + snd_soc_update_bits(codec, WM8993_DC_SERVO_1, + WM8993_DCS_TIMER_PERIOD_01_MASK, 0xa); + + reg |= WM8993_HPOUT1R_OUTP | WM8993_HPOUT1R_RMV_SHORT | + WM8993_HPOUT1L_OUTP | WM8993_HPOUT1L_RMV_SHORT; + wm8993_write(codec, WM8993_ANALOGUE_HP_0, reg); + break; + + case SND_SOC_DAPM_PRE_PMD: + reg &= ~(WM8993_HPOUT1L_RMV_SHORT | + WM8993_HPOUT1L_DLY | + WM8993_HPOUT1L_OUTP | + WM8993_HPOUT1R_RMV_SHORT | + WM8993_HPOUT1R_DLY | + WM8993_HPOUT1R_OUTP); + + snd_soc_update_bits(codec, WM8993_DC_SERVO_1, + WM8993_DCS_TIMER_PERIOD_01_MASK, 0); + snd_soc_update_bits(codec, WM8993_DC_SERVO_0, + WM8993_DCS_ENA_CHAN_0 | + WM8993_DCS_ENA_CHAN_1, 0); + + wm8993_write(codec, WM8993_ANALOGUE_HP_0, reg); + snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1, + WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA, + 0); + + snd_soc_update_bits(codec, WM8993_CHARGE_PUMP_1, + WM8993_CP_ENA, 0); + break; + } + + return 0; +} + +static const struct snd_kcontrol_new in1l_pga[] = { +SOC_DAPM_SINGLE("IN1LP Switch", WM8993_INPUT_MIXER2, 5, 1, 0), +SOC_DAPM_SINGLE("IN1LN Switch", WM8993_INPUT_MIXER2, 4, 1, 0), +}; + +static const struct snd_kcontrol_new in1r_pga[] = { +SOC_DAPM_SINGLE("IN1RP Switch", WM8993_INPUT_MIXER2, 1, 1, 0), +SOC_DAPM_SINGLE("IN1RN Switch", WM8993_INPUT_MIXER2, 0, 1, 0), +}; + +static const struct snd_kcontrol_new in2l_pga[] = { +SOC_DAPM_SINGLE("IN2LP Switch", WM8993_INPUT_MIXER2, 7, 1, 0), +SOC_DAPM_SINGLE("IN2LN Switch", WM8993_INPUT_MIXER2, 6, 1, 0), +}; + +static const struct snd_kcontrol_new in2r_pga[] = { +SOC_DAPM_SINGLE("IN2RP Switch", WM8993_INPUT_MIXER2, 3, 1, 0), +SOC_DAPM_SINGLE("IN2RN Switch", WM8993_INPUT_MIXER2, 2, 1, 0), +}; + +static const struct snd_kcontrol_new mixinl[] = { +SOC_DAPM_SINGLE("IN2L Switch", WM8993_INPUT_MIXER3, 8, 1, 0), +SOC_DAPM_SINGLE("IN1L Switch", WM8993_INPUT_MIXER3, 5, 1, 0), +}; + +static const struct snd_kcontrol_new mixinr[] = { +SOC_DAPM_SINGLE("IN2R Switch", WM8993_INPUT_MIXER4, 8, 1, 0), +SOC_DAPM_SINGLE("IN1R Switch", WM8993_INPUT_MIXER4, 5, 1, 0), +}; + +static const struct snd_kcontrol_new left_output_mixer[] = { +SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER1, 7, 1, 0), +SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER1, 6, 1, 0), +SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER1, 5, 1, 0), +SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER1, 4, 1, 0), +SOC_DAPM_SINGLE("IN2LP Switch", WM8993_OUTPUT_MIXER1, 1, 1, 0), +SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER1, 3, 1, 0), +SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER1, 2, 1, 0), +SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER1, 0, 1, 0), +}; + +static const struct snd_kcontrol_new right_output_mixer[] = { +SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER2, 7, 1, 0), +SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER2, 6, 1, 0), +SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER2, 5, 1, 0), +SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER2, 4, 1, 0), +SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER2, 3, 1, 0), +SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER2, 2, 1, 0), +SOC_DAPM_SINGLE("IN2RP Switch", WM8993_OUTPUT_MIXER2, 1, 1, 0), +SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER2, 0, 1, 0), +}; + +static const struct snd_kcontrol_new earpiece_mixer[] = { +SOC_DAPM_SINGLE("Direct Voice Switch", WM8993_HPOUT2_MIXER, 5, 1, 0), +SOC_DAPM_SINGLE("Left Output Switch", WM8993_HPOUT2_MIXER, 4, 1, 0), +SOC_DAPM_SINGLE("Right Output Switch", WM8993_HPOUT2_MIXER, 3, 1, 0), +}; + +static const struct snd_kcontrol_new left_speaker_mixer[] = { +SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 7, 1, 0), +SOC_DAPM_SINGLE("IN1LP Switch", WM8993_SPEAKER_MIXER, 5, 1, 0), +SOC_DAPM_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 3, 1, 0), +SOC_DAPM_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 6, 1, 0), +}; + +static const struct snd_kcontrol_new right_speaker_mixer[] = { +SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 6, 1, 0), +SOC_DAPM_SINGLE("IN1RP Switch", WM8993_SPEAKER_MIXER, 4, 1, 0), +SOC_DAPM_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 2, 1, 0), +SOC_DAPM_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 0, 1, 0), +}; + +static const struct snd_kcontrol_new left_speaker_boost[] = { +SOC_DAPM_SINGLE("Direct Voice Switch", WM8993_SPKOUT_MIXERS, 5, 1, 0), +SOC_DAPM_SINGLE("SPKL Switch", WM8993_SPKOUT_MIXERS, 4, 1, 0), +SOC_DAPM_SINGLE("SPKR Switch", WM8993_SPKOUT_MIXERS, 3, 1, 0), +}; + +static const struct snd_kcontrol_new right_speaker_boost[] = { +SOC_DAPM_SINGLE("Direct Voice Switch", WM8993_SPKOUT_MIXERS, 2, 1, 0), +SOC_DAPM_SINGLE("SPKL Switch", WM8993_SPKOUT_MIXERS, 1, 1, 0), +SOC_DAPM_SINGLE("SPKR Switch", WM8993_SPKOUT_MIXERS, 0, 1, 0), +}; + +static const char *hp_mux_text[] = { + "Mixer", + "DAC", +}; + +static const struct soc_enum hpl_enum = + SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER1, 8, 2, hp_mux_text); + +static const struct snd_kcontrol_new hpl_mux = + SOC_DAPM_ENUM_W("Left Headphone Mux", hpl_enum); + +static const struct soc_enum hpr_enum = + SOC_ENUM_SINGLE(WM8993_OUTPUT_MIXER2, 8, 2, hp_mux_text); + +static const struct snd_kcontrol_new hpr_mux = + SOC_DAPM_ENUM_W("Right Headphone Mux", hpr_enum); + +static const struct snd_kcontrol_new line1_mix[] = { +SOC_DAPM_SINGLE("IN1R Switch", WM8993_LINE_MIXER1, 2, 1, 0), +SOC_DAPM_SINGLE("IN1L Switch", WM8993_LINE_MIXER1, 1, 1, 0), +SOC_DAPM_SINGLE("Output Switch", WM8993_LINE_MIXER1, 0, 1, 0), +}; + +static const struct snd_kcontrol_new line1n_mix[] = { +SOC_DAPM_SINGLE("Left Output Switch", WM8993_LINE_MIXER1, 6, 1, 0), +SOC_DAPM_SINGLE("Right Output Switch", WM8993_LINE_MIXER1, 5, 1, 0), +}; + +static const struct snd_kcontrol_new line1p_mix[] = { +SOC_DAPM_SINGLE("Left Output Switch", WM8993_LINE_MIXER1, 0, 1, 0), +}; + +static const struct snd_kcontrol_new line2_mix[] = { +SOC_DAPM_SINGLE("IN2R Switch", WM8993_LINE_MIXER2, 2, 1, 0), +SOC_DAPM_SINGLE("IN2L Switch", WM8993_LINE_MIXER2, 1, 1, 0), +SOC_DAPM_SINGLE("Output Switch", WM8993_LINE_MIXER2, 0, 1, 0), +}; + +static const struct snd_kcontrol_new line2n_mix[] = { +SOC_DAPM_SINGLE("Left Output Switch", WM8993_LINE_MIXER2, 6, 1, 0), +SOC_DAPM_SINGLE("Right Output Switch", WM8993_LINE_MIXER2, 5, 1, 0), +}; + +static const struct snd_kcontrol_new line2p_mix[] = { +SOC_DAPM_SINGLE("Right Output Switch", WM8993_LINE_MIXER2, 0, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8993_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("IN1LN"), +SND_SOC_DAPM_INPUT("IN1LP"), +SND_SOC_DAPM_INPUT("IN2LN"), +SND_SOC_DAPM_INPUT("IN2LP/VXRN"), +SND_SOC_DAPM_INPUT("IN1RN"), +SND_SOC_DAPM_INPUT("IN1RP"), +SND_SOC_DAPM_INPUT("IN2RN"), +SND_SOC_DAPM_INPUT("IN2RP/VXRP"), + +SND_SOC_DAPM_SUPPLY("CLK_SYS", WM8993_BUS_CONTROL_1, 1, 0, clk_sys_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_SUPPLY("TOCLK", WM8993_CLOCKING_1, 14, 0, NULL, 0), +SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8993_CLOCKING_3, 0, 0, NULL, 0), + +SND_SOC_DAPM_MICBIAS("MICBIAS2", WM8993_POWER_MANAGEMENT_1, 5, 0), +SND_SOC_DAPM_MICBIAS("MICBIAS1", WM8993_POWER_MANAGEMENT_1, 4, 0), + +SND_SOC_DAPM_MIXER("IN1L PGA", WM8993_POWER_MANAGEMENT_2, 6, 0, + in1l_pga, ARRAY_SIZE(in1l_pga)), +SND_SOC_DAPM_MIXER("IN1R PGA", WM8993_POWER_MANAGEMENT_2, 4, 0, + in1r_pga, ARRAY_SIZE(in1r_pga)), + +SND_SOC_DAPM_MIXER("IN2L PGA", WM8993_POWER_MANAGEMENT_2, 7, 0, + in2l_pga, ARRAY_SIZE(in2l_pga)), +SND_SOC_DAPM_MIXER("IN2R PGA", WM8993_POWER_MANAGEMENT_2, 5, 0, + in2r_pga, ARRAY_SIZE(in2r_pga)), + +/* Dummy widgets to represent differential paths */ +SND_SOC_DAPM_PGA("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0), + +SND_SOC_DAPM_MIXER("MIXINL", WM8993_POWER_MANAGEMENT_2, 9, 0, + mixinl, ARRAY_SIZE(mixinl)), +SND_SOC_DAPM_MIXER("MIXINR", WM8993_POWER_MANAGEMENT_2, 8, 0, + mixinr, ARRAY_SIZE(mixinr)), + +SND_SOC_DAPM_ADC("ADCL", "Capture", WM8993_POWER_MANAGEMENT_2, 1, 0), +SND_SOC_DAPM_ADC("ADCR", "Capture", WM8993_POWER_MANAGEMENT_2, 0, 0), + +SND_SOC_DAPM_DAC("DACL", "Playback", WM8993_POWER_MANAGEMENT_3, 1, 0), +SND_SOC_DAPM_DAC("DACR", "Playback", WM8993_POWER_MANAGEMENT_3, 0, 0), + +SND_SOC_DAPM_MIXER("Left Output Mixer", WM8993_POWER_MANAGEMENT_3, 5, 0, + left_output_mixer, ARRAY_SIZE(left_output_mixer)), +SND_SOC_DAPM_MIXER("Right Output Mixer", WM8993_POWER_MANAGEMENT_3, 4, 0, + right_output_mixer, ARRAY_SIZE(right_output_mixer)), + +SND_SOC_DAPM_PGA("Left Output PGA", WM8993_POWER_MANAGEMENT_3, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Output PGA", WM8993_POWER_MANAGEMENT_3, 6, 0, NULL, 0), + +SND_SOC_DAPM_MIXER("Earpiece Mixer", SND_SOC_NOPM, 0, 0, + earpiece_mixer, ARRAY_SIZE(earpiece_mixer)), +SND_SOC_DAPM_PGA_E("Earpiece Driver", WM8993_POWER_MANAGEMENT_1, 11, 0, + NULL, 0, wm8993_earpiece_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + +SND_SOC_DAPM_MIXER("SPKL", WM8993_POWER_MANAGEMENT_3, 8, 0, + left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)), +SND_SOC_DAPM_MIXER("SPKR", WM8993_POWER_MANAGEMENT_3, 9, 0, + right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)), + +SND_SOC_DAPM_MIXER("SPKL Boost", SND_SOC_NOPM, 0, 0, + left_speaker_boost, ARRAY_SIZE(left_speaker_boost)), +SND_SOC_DAPM_MIXER("SPKR Boost", SND_SOC_NOPM, 0, 0, + right_speaker_boost, ARRAY_SIZE(right_speaker_boost)), + +SND_SOC_DAPM_PGA("SPKL Driver", WM8993_POWER_MANAGEMENT_1, 12, 0, + NULL, 0), +SND_SOC_DAPM_PGA("SPKR Driver", WM8993_POWER_MANAGEMENT_1, 13, 0, + NULL, 0), + +SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux), +SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux), +SND_SOC_DAPM_PGA_E("Headphone PGA", SND_SOC_NOPM, 0, 0, + NULL, 0, + hp_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_MIXER("LINEOUT1 Mixer", SND_SOC_NOPM, 0, 0, + line1_mix, ARRAY_SIZE(line1_mix)), +SND_SOC_DAPM_MIXER("LINEOUT2 Mixer", SND_SOC_NOPM, 0, 0, + line2_mix, ARRAY_SIZE(line2_mix)), + +SND_SOC_DAPM_MIXER("LINEOUT1N Mixer", SND_SOC_NOPM, 0, 0, + line1n_mix, ARRAY_SIZE(line1n_mix)), +SND_SOC_DAPM_MIXER("LINEOUT1P Mixer", SND_SOC_NOPM, 0, 0, + line1p_mix, ARRAY_SIZE(line1p_mix)), +SND_SOC_DAPM_MIXER("LINEOUT2N Mixer", SND_SOC_NOPM, 0, 0, + line2n_mix, ARRAY_SIZE(line2n_mix)), +SND_SOC_DAPM_MIXER("LINEOUT2P Mixer", SND_SOC_NOPM, 0, 0, + line2p_mix, ARRAY_SIZE(line2p_mix)), + +SND_SOC_DAPM_PGA("LINEOUT1N Driver", WM8993_POWER_MANAGEMENT_3, 13, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LINEOUT1P Driver", WM8993_POWER_MANAGEMENT_3, 12, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LINEOUT2N Driver", WM8993_POWER_MANAGEMENT_3, 11, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LINEOUT2P Driver", WM8993_POWER_MANAGEMENT_3, 10, 0, + NULL, 0), + +SND_SOC_DAPM_OUTPUT("SPKOUTLP"), +SND_SOC_DAPM_OUTPUT("SPKOUTLN"), +SND_SOC_DAPM_OUTPUT("SPKOUTRP"), +SND_SOC_DAPM_OUTPUT("SPKOUTRN"), +SND_SOC_DAPM_OUTPUT("HPOUT1L"), +SND_SOC_DAPM_OUTPUT("HPOUT1R"), +SND_SOC_DAPM_OUTPUT("HPOUT2P"), +SND_SOC_DAPM_OUTPUT("HPOUT2N"), +SND_SOC_DAPM_OUTPUT("LINEOUT1P"), +SND_SOC_DAPM_OUTPUT("LINEOUT1N"), +SND_SOC_DAPM_OUTPUT("LINEOUT2P"), +SND_SOC_DAPM_OUTPUT("LINEOUT2N"), +}; + +static const struct snd_soc_dapm_route routes[] = { + { "IN1L PGA", "IN1LP Switch", "IN1LP" }, + { "IN1L PGA", "IN1LN Switch", "IN1LN" }, + + { "IN1R PGA", "IN1RP Switch", "IN1RP" }, + { "IN1R PGA", "IN1RN Switch", "IN1RN" }, + + { "IN2L PGA", "IN2LP Switch", "IN2LP/VXRN" }, + { "IN2L PGA", "IN2LN Switch", "IN2LN" }, + + { "IN2R PGA", "IN2RP Switch", "IN2RP/VXRP" }, + { "IN2R PGA", "IN2RN Switch", "IN2RN" }, + + { "Direct Voice", NULL, "IN2LP/VXRN" }, + { "Direct Voice", NULL, "IN2RP/VXRP" }, + + { "MIXINL", "IN1L Switch", "IN1L PGA" }, + { "MIXINL", "IN2L Switch", "IN2L PGA" }, + { "MIXINL", NULL, "Direct Voice" }, + { "MIXINL", NULL, "IN1LP" }, + { "MIXINL", NULL, "Left Output Mixer" }, + + { "MIXINR", "IN1R Switch", "IN1R PGA" }, + { "MIXINR", "IN2R Switch", "IN2R PGA" }, + { "MIXINR", NULL, "Direct Voice" }, + { "MIXINR", NULL, "IN1RP" }, + { "MIXINR", NULL, "Right Output Mixer" }, + + { "ADCL", NULL, "MIXINL" }, + { "ADCL", NULL, "CLK_SYS" }, + { "ADCL", NULL, "CLK_DSP" }, + { "ADCR", NULL, "MIXINR" }, + { "ADCR", NULL, "CLK_SYS" }, + { "ADCR", NULL, "CLK_DSP" }, + + { "DACL", NULL, "CLK_SYS" }, + { "DACL", NULL, "CLK_DSP" }, + { "DACR", NULL, "CLK_SYS" }, + { "DACR", NULL, "CLK_DSP" }, + + { "Left Output Mixer", "Left Input Switch", "MIXINL" }, + { "Left Output Mixer", "Right Input Switch", "MIXINR" }, + { "Left Output Mixer", "IN2RN Switch", "IN2RN" }, + { "Left Output Mixer", "IN2LN Switch", "IN2LN" }, + { "Left Output Mixer", "IN2LP Switch", "IN2LP/VXRN" }, + { "Left Output Mixer", "IN1L Switch", "IN1L PGA" }, + { "Left Output Mixer", "IN1R Switch", "IN1R PGA" }, + { "Left Output Mixer", "DAC Switch", "DACL" }, + + { "Right Output Mixer", "Left Input Switch", "MIXINL" }, + { "Right Output Mixer", "Right Input Switch", "MIXINR" }, + { "Right Output Mixer", "IN2LN Switch", "IN2LN" }, + { "Right Output Mixer", "IN2RN Switch", "IN2RN" }, + { "Right Output Mixer", "IN2RP Switch", "IN2RP/VXRP" }, + { "Right Output Mixer", "IN1L Switch", "IN1L PGA" }, + { "Right Output Mixer", "IN1R Switch", "IN1R PGA" }, + { "Right Output Mixer", "DAC Switch", "DACR" }, + + { "Left Output PGA", NULL, "Left Output Mixer" }, + { "Left Output PGA", NULL, "CLK_SYS" }, + { "Left Output PGA", NULL, "TOCLK" }, + + { "Right Output PGA", NULL, "Right Output Mixer" }, + { "Right Output PGA", NULL, "CLK_SYS" }, + { "Right Output PGA", NULL, "TOCLK" }, + + { "Earpiece Mixer", "Direct Voice Switch", "Direct Voice" }, + { "Earpiece Mixer", "Left Output Switch", "Left Output PGA" }, + { "Earpiece Mixer", "Right Output Switch", "Right Output PGA" }, + + { "Earpiece Driver", NULL, "Earpiece Mixer" }, + { "HPOUT2N", NULL, "Earpiece Driver" }, + { "HPOUT2P", NULL, "Earpiece Driver" }, + + { "SPKL", "Input Switch", "MIXINL" }, + { "SPKL", "IN1LP Switch", "IN1LP" }, + { "SPKL", "Output Switch", "Left Output Mixer" }, + { "SPKL", "DAC Switch", "DACL" }, + { "SPKL", NULL, "CLK_SYS" }, + { "SPKL", NULL, "TOCLK" }, + + { "SPKR", "Input Switch", "MIXINR" }, + { "SPKR", "IN1RP Switch", "IN1RP" }, + { "SPKR", "Output Switch", "Right Output Mixer" }, + { "SPKR", "DAC Switch", "DACR" }, + { "SPKR", NULL, "CLK_SYS" }, + { "SPKR", NULL, "TOCLK" }, + + { "SPKL Boost", "Direct Voice Switch", "Direct Voice" }, + { "SPKL Boost", "SPKL Switch", "SPKL" }, + { "SPKL Boost", "SPKR Switch", "SPKR" }, + + { "SPKR Boost", "Direct Voice Switch", "Direct Voice" }, + { "SPKR Boost", "SPKR Switch", "SPKR" }, + { "SPKR Boost", "SPKL Switch", "SPKL" }, + + { "SPKL Driver", NULL, "SPKL Boost" }, + { "SPKL Driver", NULL, "CLK_SYS" }, + + { "SPKR Driver", NULL, "SPKR Boost" }, + { "SPKR Driver", NULL, "CLK_SYS" }, + + { "SPKOUTLP", NULL, "SPKL Driver" }, + { "SPKOUTLN", NULL, "SPKL Driver" }, + { "SPKOUTRP", NULL, "SPKR Driver" }, + { "SPKOUTRN", NULL, "SPKR Driver" }, + + { "Left Headphone Mux", "DAC", "DACL" }, + { "Left Headphone Mux", "Mixer", "Left Output Mixer" }, + { "Right Headphone Mux", "DAC", "DACR" }, + { "Right Headphone Mux", "Mixer", "Right Output Mixer" }, + + { "Headphone PGA", NULL, "Left Headphone Mux" }, + { "Headphone PGA", NULL, "Right Headphone Mux" }, + { "Headphone PGA", NULL, "CLK_SYS" }, + { "Headphone PGA", NULL, "TOCLK" }, + + { "HPOUT1L", NULL, "Headphone PGA" }, + { "HPOUT1R", NULL, "Headphone PGA" }, + + { "LINEOUT1N", NULL, "LINEOUT1N Driver" }, + { "LINEOUT1P", NULL, "LINEOUT1P Driver" }, + { "LINEOUT2N", NULL, "LINEOUT2N Driver" }, + { "LINEOUT2P", NULL, "LINEOUT2P Driver" }, +}; + +static const struct snd_soc_dapm_route lineout1_diff_routes[] = { + { "LINEOUT1 Mixer", "IN1L Switch", "IN1L PGA" }, + { "LINEOUT1 Mixer", "IN1R Switch", "IN1R PGA" }, + { "LINEOUT1 Mixer", "Output Switch", "Left Output Mixer" }, + + { "LINEOUT1N Driver", NULL, "LINEOUT1 Mixer" }, + { "LINEOUT1P Driver", NULL, "LINEOUT1 Mixer" }, +}; + +static const struct snd_soc_dapm_route lineout1_se_routes[] = { + { "LINEOUT1N Mixer", "Left Output Switch", "Left Output Mixer" }, + { "LINEOUT1N Mixer", "Right Output Switch", "Left Output Mixer" }, + + { "LINEOUT1P Mixer", "Left Output Switch", "Left Output Mixer" }, + + { "LINEOUT1N Driver", NULL, "LINEOUT1N Mixer" }, + { "LINEOUT1P Driver", NULL, "LINEOUT1P Mixer" }, +}; + +static const struct snd_soc_dapm_route lineout2_diff_routes[] = { + { "LINEOUT2 Mixer", "IN2L Switch", "IN2L PGA" }, + { "LINEOUT2 Mixer", "IN2R Switch", "IN2R PGA" }, + { "LINEOUT2 Mixer", "Output Switch", "Right Output Mixer" }, + + { "LINEOUT2N Driver", NULL, "LINEOUT2 Mixer" }, + { "LINEOUT2P Driver", NULL, "LINEOUT2 Mixer" }, +}; + +static const struct snd_soc_dapm_route lineout2_se_routes[] = { + { "LINEOUT2N Mixer", "Left Output Switch", "Left Output Mixer" }, + { "LINEOUT2N Mixer", "Right Output Switch", "Left Output Mixer" }, + + { "LINEOUT2P Mixer", "Right Output Switch", "Right Output Mixer" }, + + { "LINEOUT2N Driver", NULL, "LINEOUT2N Mixer" }, + { "LINEOUT2P Driver", NULL, "LINEOUT2P Mixer" }, +}; + +static int wm8993_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + struct wm8993_priv *wm8993 = codec->private_data; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + /* VMID=2*40k */ + snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1, + WM8993_VMID_SEL_MASK, 0x2); + snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_2, + WM8993_TSHUT_ENA, WM8993_TSHUT_ENA); + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Bring up VMID with fast soft start */ + snd_soc_update_bits(codec, WM8993_ANTIPOP2, + WM8993_STARTUP_BIAS_ENA | + WM8993_VMID_BUF_ENA | + WM8993_VMID_RAMP_MASK | + WM8993_BIAS_SRC, + WM8993_STARTUP_BIAS_ENA | + WM8993_VMID_BUF_ENA | + WM8993_VMID_RAMP_MASK | + WM8993_BIAS_SRC); + + /* If either line output is single ended we + * need the VMID buffer */ + if (!wm8993->pdata.lineout1_diff || + !wm8993->pdata.lineout2_diff) + snd_soc_update_bits(codec, WM8993_ANTIPOP1, + WM8993_LINEOUT_VMID_BUF_ENA, + WM8993_LINEOUT_VMID_BUF_ENA); + + /* VMID=2*40k */ + snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1, + WM8993_VMID_SEL_MASK | + WM8993_BIAS_ENA, + WM8993_BIAS_ENA | 0x2); + msleep(32); + + /* Switch to normal bias */ + snd_soc_update_bits(codec, WM8993_ANTIPOP2, + WM8993_BIAS_SRC | + WM8993_STARTUP_BIAS_ENA, 0); + } + + /* VMID=2*240k */ + snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1, + WM8993_VMID_SEL_MASK, 0x4); + + snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_2, + WM8993_TSHUT_ENA, 0); + break; + + case SND_SOC_BIAS_OFF: + snd_soc_update_bits(codec, WM8993_ANTIPOP1, + WM8993_LINEOUT_VMID_BUF_ENA, 0); + + snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1, + WM8993_VMID_SEL_MASK | WM8993_BIAS_ENA, + 0); + break; + } + + codec->bias_level = level; + + return 0; +} + +static int wm8993_set_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8993_priv *wm8993 = codec->private_data; + + switch (clk_id) { + case WM8993_SYSCLK_MCLK: + wm8993->mclk_rate = freq; + case WM8993_SYSCLK_FLL: + wm8993->sysclk_source = clk_id; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int wm8993_set_dai_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8993_priv *wm8993 = codec->private_data; + unsigned int aif1 = wm8993_read(codec, WM8993_AUDIO_INTERFACE_1); + unsigned int aif4 = wm8993_read(codec, WM8993_AUDIO_INTERFACE_4); + + aif1 &= ~(WM8993_BCLK_DIR | WM8993_AIF_BCLK_INV | + WM8993_AIF_LRCLK_INV | WM8993_AIF_FMT_MASK); + aif4 &= ~WM8993_LRCLK_DIR; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + wm8993->master = 0; + break; + case SND_SOC_DAIFMT_CBS_CFM: + aif4 |= WM8993_LRCLK_DIR; + wm8993->master = 1; + break; + case SND_SOC_DAIFMT_CBM_CFS: + aif1 |= WM8993_BCLK_DIR; + wm8993->master = 1; + break; + case SND_SOC_DAIFMT_CBM_CFM: + aif1 |= WM8993_BCLK_DIR; + aif4 |= WM8993_LRCLK_DIR; + wm8993->master = 1; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_B: + aif1 |= WM8993_AIF_LRCLK_INV; + case SND_SOC_DAIFMT_DSP_A: + aif1 |= 0x18; + break; + case SND_SOC_DAIFMT_I2S: + aif1 |= 0x10; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + aif1 |= 0x8; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + case SND_SOC_DAIFMT_DSP_B: + /* frame inversion not valid for DSP modes */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8993_AIF_BCLK_INV; + break; + default: + return -EINVAL; + } + break; + + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + aif1 |= WM8993_AIF_BCLK_INV | WM8993_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: + aif1 |= WM8993_AIF_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: + aif1 |= WM8993_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + wm8993_write(codec, WM8993_AUDIO_INTERFACE_1, aif1); + wm8993_write(codec, WM8993_AUDIO_INTERFACE_4, aif4); + + return 0; +} + +static int wm8993_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8993_priv *wm8993 = codec->private_data; + int ret, i, best, best_val, cur_val; + unsigned int clocking1, clocking3, aif1, aif4; + + clocking1 = wm8993_read(codec, WM8993_CLOCKING_1); + clocking1 &= ~WM8993_BCLK_DIV_MASK; + + clocking3 = wm8993_read(codec, WM8993_CLOCKING_3); + clocking3 &= ~(WM8993_CLK_SYS_RATE_MASK | WM8993_SAMPLE_RATE_MASK); + + aif1 = wm8993_read(codec, WM8993_AUDIO_INTERFACE_1); + aif1 &= ~WM8993_AIF_WL_MASK; + + aif4 = wm8993_read(codec, WM8993_AUDIO_INTERFACE_4); + aif4 &= ~WM8993_LRCLK_RATE_MASK; + + /* What BCLK do we need? */ + wm8993->fs = params_rate(params); + wm8993->bclk = 2 * wm8993->fs; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + wm8993->bclk *= 16; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + wm8993->bclk *= 20; + aif1 |= 0x8; + break; + case SNDRV_PCM_FORMAT_S24_LE: + wm8993->bclk *= 24; + aif1 |= 0x10; + break; + case SNDRV_PCM_FORMAT_S32_LE: + wm8993->bclk *= 32; + aif1 |= 0x18; + break; + default: + return -EINVAL; + } + + dev_dbg(codec->dev, "Target BCLK is %dHz\n", wm8993->bclk); + + ret = configure_clock(codec); + if (ret != 0) + return ret; + + /* Select nearest CLK_SYS_RATE */ + best = 0; + best_val = abs((wm8993->sysclk_rate / clk_sys_rates[0].ratio) + - wm8993->fs); + for (i = 1; i < ARRAY_SIZE(clk_sys_rates); i++) { + cur_val = abs((wm8993->sysclk_rate / + clk_sys_rates[i].ratio) - wm8993->fs);; + if (cur_val < best_val) { + best = i; + best_val = cur_val; + } + } + dev_dbg(codec->dev, "Selected CLK_SYS_RATIO of %d\n", + clk_sys_rates[best].ratio); + clocking3 |= (clk_sys_rates[best].clk_sys_rate + << WM8993_CLK_SYS_RATE_SHIFT); + + /* SAMPLE_RATE */ + best = 0; + best_val = abs(wm8993->fs - sample_rates[0].rate); + for (i = 1; i < ARRAY_SIZE(sample_rates); i++) { + /* Closest match */ + cur_val = abs(wm8993->fs - sample_rates[i].rate); + if (cur_val < best_val) { + best = i; + best_val = cur_val; + } + } + dev_dbg(codec->dev, "Selected SAMPLE_RATE of %dHz\n", + sample_rates[best].rate); + clocking3 |= (sample_rates[i].sample_rate << WM8993_SAMPLE_RATE_SHIFT); + + /* BCLK_DIV */ + best = 0; + best_val = INT_MAX; + for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) { + cur_val = ((wm8993->sysclk_rate * 10) / bclk_divs[i].div) + - wm8993->bclk; + if (cur_val < 0) /* Table is sorted */ + break; + if (cur_val < best_val) { + best = i; + best_val = cur_val; + } + } + wm8993->bclk = (wm8993->sysclk_rate * 10) / bclk_divs[best].div; + dev_dbg(codec->dev, "Selected BCLK_DIV of %d for %dHz BCLK\n", + bclk_divs[best].div, wm8993->bclk); + clocking1 |= bclk_divs[best].bclk_div << WM8993_BCLK_DIV_SHIFT; + + /* LRCLK is a simple fraction of BCLK */ + dev_dbg(codec->dev, "LRCLK_RATE is %d\n", wm8993->bclk / wm8993->fs); + aif4 |= wm8993->bclk / wm8993->fs; + + wm8993_write(codec, WM8993_CLOCKING_1, clocking1); + wm8993_write(codec, WM8993_CLOCKING_3, clocking3); + wm8993_write(codec, WM8993_AUDIO_INTERFACE_1, aif1); + wm8993_write(codec, WM8993_AUDIO_INTERFACE_4, aif4); + + /* ReTune Mobile? */ + if (wm8993->pdata.num_retune_configs) { + u16 eq1 = wm8993_read(codec, WM8993_EQ1); + struct wm8993_retune_mobile_setting *s; + + best = 0; + best_val = abs(wm8993->pdata.retune_configs[0].rate + - wm8993->fs); + for (i = 0; i < wm8993->pdata.num_retune_configs; i++) { + cur_val = abs(wm8993->pdata.retune_configs[i].rate + - wm8993->fs); + if (cur_val < best_val) { + best_val = cur_val; + best = i; + } + } + s = &wm8993->pdata.retune_configs[best]; + + dev_dbg(codec->dev, "ReTune Mobile %s tuned for %dHz\n", + s->name, s->rate); + + /* Disable EQ while we reconfigure */ + snd_soc_update_bits(codec, WM8993_EQ1, WM8993_EQ_ENA, 0); + + for (i = 1; i < ARRAY_SIZE(s->config); i++) + wm8993_write(codec, WM8993_EQ1 + i, s->config[i]); + + snd_soc_update_bits(codec, WM8993_EQ1, WM8993_EQ_ENA, eq1); + } + + return 0; +} + +static int wm8993_digital_mute(struct snd_soc_dai *codec_dai, int mute) +{ + struct snd_soc_codec *codec = codec_dai->codec; + unsigned int reg; + + reg = wm8993_read(codec, WM8993_DAC_CTRL); + + if (mute) + reg |= WM8993_DAC_MUTE; + else + reg &= ~WM8993_DAC_MUTE; + + wm8993_write(codec, WM8993_DAC_CTRL, reg); + + return 0; +} + +static struct snd_soc_dai_ops wm8993_ops = { + .set_sysclk = wm8993_set_sysclk, + .set_fmt = wm8993_set_dai_fmt, + .hw_params = wm8993_hw_params, + .digital_mute = wm8993_digital_mute, + .set_pll = wm8993_set_fll, +}; + +#define WM8993_RATES SNDRV_PCM_RATE_8000_48000 + +#define WM8993_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\ + SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE |\ + SNDRV_PCM_FMTBIT_S32_LE) + +struct snd_soc_dai wm8993_dai = { + .name = "WM8993", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8993_RATES, + .formats = WM8993_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = WM8993_RATES, + .formats = WM8993_FORMATS, + }, + .ops = &wm8993_ops, + .symmetric_rates = 1, +}; +EXPORT_SYMBOL_GPL(wm8993_dai); + +static struct snd_soc_codec *wm8993_codec; + +static int wm8993_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + struct wm8993_priv *wm8993; + int ret = 0; + + if (!wm8993_codec) { + dev_err(&pdev->dev, "I2C device not yet probed\n"); + goto err; + } + + socdev->card->codec = wm8993_codec; + codec = wm8993_codec; + wm8993 = codec->private_data; + + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms\n"); + goto err; + } + + snd_soc_add_controls(codec, wm8993_snd_controls, + ARRAY_SIZE(wm8993_snd_controls)); + if (wm8993->pdata.num_retune_configs != 0) { + dev_dbg(codec->dev, "Using ReTune Mobile\n"); + } else { + dev_dbg(codec->dev, "No ReTune Mobile, using normal EQ\n"); + snd_soc_add_controls(codec, wm8993_eq_controls, + ARRAY_SIZE(wm8993_eq_controls)); + } + + snd_soc_dapm_new_controls(codec, wm8993_dapm_widgets, + ARRAY_SIZE(wm8993_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, routes, ARRAY_SIZE(routes)); + + if (wm8993->pdata.lineout1_diff) + snd_soc_dapm_add_routes(codec, + lineout1_diff_routes, + ARRAY_SIZE(lineout1_diff_routes)); + else + snd_soc_dapm_add_routes(codec, + lineout1_se_routes, + ARRAY_SIZE(lineout1_se_routes)); + + if (wm8993->pdata.lineout2_diff) + snd_soc_dapm_add_routes(codec, + lineout2_diff_routes, + ARRAY_SIZE(lineout2_diff_routes)); + else + snd_soc_dapm_add_routes(codec, + lineout2_se_routes, + ARRAY_SIZE(lineout2_se_routes)); + + snd_soc_dapm_new_widgets(codec); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +err: + return ret; +} + +static int wm8993_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8993 = { + .probe = wm8993_probe, + .remove = wm8993_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8993); + +static int wm8993_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8993_priv *wm8993; + struct snd_soc_codec *codec; + unsigned int val; + int ret; + + if (wm8993_codec) { + dev_err(&i2c->dev, "A WM8993 is already registered\n"); + return -EINVAL; + } + + wm8993 = kzalloc(sizeof(struct wm8993_priv), GFP_KERNEL); + if (wm8993 == NULL) + return -ENOMEM; + + codec = &wm8993->codec; + if (i2c->dev.platform_data) + memcpy(&wm8993->pdata, i2c->dev.platform_data, + sizeof(wm8993->pdata)); + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->name = "WM8993"; + codec->read = wm8993_read; + codec->write = wm8993_write; + codec->hw_write = (hw_write_t)i2c_master_send; + codec->reg_cache = wm8993->reg_cache; + codec->reg_cache_size = ARRAY_SIZE(wm8993->reg_cache); + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8993_set_bias_level; + codec->dai = &wm8993_dai; + codec->num_dai = 1; + codec->private_data = wm8993; + + memcpy(wm8993->reg_cache, wm8993_reg_defaults, + sizeof(wm8993->reg_cache)); + + i2c_set_clientdata(i2c, wm8993); + codec->control_data = i2c; + wm8993_codec = codec; + + codec->dev = &i2c->dev; + + val = wm8993_read_hw(codec, WM8993_SOFTWARE_RESET); + if (val != wm8993_reg_defaults[WM8993_SOFTWARE_RESET]) { + dev_err(codec->dev, "Invalid ID register value %x\n", val); + ret = -EINVAL; + goto err; + } + + ret = wm8993_write(codec, WM8993_SOFTWARE_RESET, 0xffff); + if (ret != 0) + goto err; + + /* By default we're using the output mixers */ + wm8993->class_w_users = 2; + + /* Latch volume update bits and default ZC on */ + snd_soc_update_bits(codec, WM8993_LEFT_LINE_INPUT_1_2_VOLUME, + WM8993_IN1_VU, WM8993_IN1_VU); + snd_soc_update_bits(codec, WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8993_IN1_VU, WM8993_IN1_VU); + snd_soc_update_bits(codec, WM8993_LEFT_LINE_INPUT_3_4_VOLUME, + WM8993_IN2_VU, WM8993_IN2_VU); + snd_soc_update_bits(codec, WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8993_IN2_VU, WM8993_IN2_VU); + + snd_soc_update_bits(codec, WM8993_SPEAKER_VOLUME_RIGHT, + WM8993_SPKOUT_VU, WM8993_SPKOUT_VU); + + snd_soc_update_bits(codec, WM8993_LEFT_OUTPUT_VOLUME, + WM8993_HPOUT1L_ZC, WM8993_HPOUT1L_ZC); + snd_soc_update_bits(codec, WM8993_RIGHT_OUTPUT_VOLUME, + WM8993_HPOUT1_VU | WM8993_HPOUT1R_ZC, + WM8993_HPOUT1_VU | WM8993_HPOUT1R_ZC); + + snd_soc_update_bits(codec, WM8993_LEFT_OPGA_VOLUME, + WM8993_MIXOUTL_ZC, WM8993_MIXOUTL_ZC); + snd_soc_update_bits(codec, WM8993_RIGHT_OPGA_VOLUME, + WM8993_MIXOUTR_ZC | WM8993_MIXOUT_VU, + WM8993_MIXOUTR_ZC | WM8993_MIXOUT_VU); + + snd_soc_update_bits(codec, WM8993_RIGHT_DAC_DIGITAL_VOLUME, + WM8993_DAC_VU, WM8993_DAC_VU); + snd_soc_update_bits(codec, WM8993_RIGHT_ADC_DIGITAL_VOLUME, + WM8993_ADC_VU, WM8993_ADC_VU); + + /* Manualy manage the HPOUT sequencing for independent stereo + * control. */ + snd_soc_update_bits(codec, WM8993_ANALOGUE_HP_0, + WM8993_HPOUT1_AUTO_PU, 0); + + /* Use automatic clock configuration */ + snd_soc_update_bits(codec, WM8993_CLOCKING_4, WM8993_SR_MODE, 0); + + if (!wm8993->pdata.lineout1_diff) + snd_soc_update_bits(codec, WM8993_LINE_MIXER1, + WM8993_LINEOUT1_MODE, + WM8993_LINEOUT1_MODE); + if (!wm8993->pdata.lineout2_diff) + snd_soc_update_bits(codec, WM8993_LINE_MIXER2, + WM8993_LINEOUT2_MODE, + WM8993_LINEOUT2_MODE); + + if (wm8993->pdata.lineout1fb) + snd_soc_update_bits(codec, WM8993_ADDITIONAL_CONTROL, + WM8993_LINEOUT1_FB, WM8993_LINEOUT1_FB); + + if (wm8993->pdata.lineout2fb) + snd_soc_update_bits(codec, WM8993_ADDITIONAL_CONTROL, + WM8993_LINEOUT2_FB, WM8993_LINEOUT2_FB); + + /* Apply the microphone bias/detection configuration - the + * platform data is directly applicable to the register. */ + snd_soc_update_bits(codec, WM8993_MICBIAS, + WM8993_JD_SCTHR_MASK | WM8993_JD_THR_MASK | + WM8993_MICB1_LVL | WM8993_MICB2_LVL, + wm8993->pdata.jd_scthr << WM8993_JD_SCTHR_SHIFT | + wm8993->pdata.jd_thr << WM8993_JD_THR_SHIFT | + wm8993->pdata.micbias1_lvl | + wm8993->pdata.micbias1_lvl << 1); + + ret = wm8993_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + if (ret != 0) + goto err; + + wm8993_dai.dev = codec->dev; + + ret = snd_soc_register_dai(&wm8993_dai); + if (ret != 0) + goto err_bias; + + ret = snd_soc_register_codec(codec); + + return 0; + +err_bias: + wm8993_set_bias_level(codec, SND_SOC_BIAS_OFF); +err: + wm8993_codec = NULL; + kfree(wm8993); + return ret; +} + +static int wm8993_i2c_remove(struct i2c_client *client) +{ + struct wm8993_priv *wm8993 = i2c_get_clientdata(client); + + snd_soc_unregister_codec(&wm8993->codec); + snd_soc_unregister_dai(&wm8993_dai); + + wm8993_set_bias_level(&wm8993->codec, SND_SOC_BIAS_OFF); + kfree(wm8993); + + return 0; +} + +static const struct i2c_device_id wm8993_i2c_id[] = { + { "wm8993", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8993_i2c_id); + +static struct i2c_driver wm8993_i2c_driver = { + .driver = { + .name = "WM8993", + .owner = THIS_MODULE, + }, + .probe = wm8993_i2c_probe, + .remove = wm8993_i2c_remove, + .id_table = wm8993_i2c_id, +}; + + +static int __init wm8993_modinit(void) +{ + int ret; + + ret = i2c_add_driver(&wm8993_i2c_driver); + if (ret != 0) + pr_err("WM8993: Unable to register I2C driver: %d\n", ret); + + return ret; +} +module_init(wm8993_modinit); + +static void __exit wm8993_exit(void) +{ + i2c_del_driver(&wm8993_i2c_driver); +} +module_exit(wm8993_exit); + + +MODULE_DESCRIPTION("ASoC WM8993 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8993.h b/sound/soc/codecs/wm8993.h new file mode 100644 index 00000000000..30e71ca88da --- /dev/null +++ b/sound/soc/codecs/wm8993.h @@ -0,0 +1,2132 @@ +#ifndef WM8993_H +#define WM8993_H + +extern struct snd_soc_dai wm8993_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8993; + +#define WM8993_SYSCLK_MCLK 1 +#define WM8993_SYSCLK_FLL 2 + +#define WM8993_FLL_MCLK 1 +#define WM8993_FLL_BCLK 2 +#define WM8993_FLL_LRCLK 3 + +/* + * Register values. + */ +#define WM8993_SOFTWARE_RESET 0x00 +#define WM8993_POWER_MANAGEMENT_1 0x01 +#define WM8993_POWER_MANAGEMENT_2 0x02 +#define WM8993_POWER_MANAGEMENT_3 0x03 +#define WM8993_AUDIO_INTERFACE_1 0x04 +#define WM8993_AUDIO_INTERFACE_2 0x05 +#define WM8993_CLOCKING_1 0x06 +#define WM8993_CLOCKING_2 0x07 +#define WM8993_AUDIO_INTERFACE_3 0x08 +#define WM8993_AUDIO_INTERFACE_4 0x09 +#define WM8993_DAC_CTRL 0x0A +#define WM8993_LEFT_DAC_DIGITAL_VOLUME 0x0B +#define WM8993_RIGHT_DAC_DIGITAL_VOLUME 0x0C +#define WM8993_DIGITAL_SIDE_TONE 0x0D +#define WM8993_ADC_CTRL 0x0E +#define WM8993_LEFT_ADC_DIGITAL_VOLUME 0x0F +#define WM8993_RIGHT_ADC_DIGITAL_VOLUME 0x10 +#define WM8993_GPIO_CTRL_1 0x12 +#define WM8993_GPIO1 0x13 +#define WM8993_IRQ_DEBOUNCE 0x14 +#define WM8993_GPIOCTRL_2 0x16 +#define WM8993_GPIO_POL 0x17 +#define WM8993_LEFT_LINE_INPUT_1_2_VOLUME 0x18 +#define WM8993_LEFT_LINE_INPUT_3_4_VOLUME 0x19 +#define WM8993_RIGHT_LINE_INPUT_1_2_VOLUME 0x1A +#define WM8993_RIGHT_LINE_INPUT_3_4_VOLUME 0x1B +#define WM8993_LEFT_OUTPUT_VOLUME 0x1C +#define WM8993_RIGHT_OUTPUT_VOLUME 0x1D +#define WM8993_LINE_OUTPUTS_VOLUME 0x1E +#define WM8993_HPOUT2_VOLUME 0x1F +#define WM8993_LEFT_OPGA_VOLUME 0x20 +#define WM8993_RIGHT_OPGA_VOLUME 0x21 +#define WM8993_SPKMIXL_ATTENUATION 0x22 +#define WM8993_SPKMIXR_ATTENUATION 0x23 +#define WM8993_SPKOUT_MIXERS 0x24 +#define WM8993_SPKOUT_BOOST 0x25 +#define WM8993_SPEAKER_VOLUME_LEFT 0x26 +#define WM8993_SPEAKER_VOLUME_RIGHT 0x27 +#define WM8993_INPUT_MIXER2 0x28 +#define WM8993_INPUT_MIXER3 0x29 +#define WM8993_INPUT_MIXER4 0x2A +#define WM8993_INPUT_MIXER5 0x2B +#define WM8993_INPUT_MIXER6 0x2C +#define WM8993_OUTPUT_MIXER1 0x2D +#define WM8993_OUTPUT_MIXER2 0x2E +#define WM8993_OUTPUT_MIXER3 0x2F +#define WM8993_OUTPUT_MIXER4 0x30 +#define WM8993_OUTPUT_MIXER5 0x31 +#define WM8993_OUTPUT_MIXER6 0x32 +#define WM8993_HPOUT2_MIXER 0x33 +#define WM8993_LINE_MIXER1 0x34 +#define WM8993_LINE_MIXER2 0x35 +#define WM8993_SPEAKER_MIXER 0x36 +#define WM8993_ADDITIONAL_CONTROL 0x37 +#define WM8993_ANTIPOP1 0x38 +#define WM8993_ANTIPOP2 0x39 +#define WM8993_MICBIAS 0x3A +#define WM8993_FLL_CONTROL_1 0x3C +#define WM8993_FLL_CONTROL_2 0x3D +#define WM8993_FLL_CONTROL_3 0x3E +#define WM8993_FLL_CONTROL_4 0x3F +#define WM8993_FLL_CONTROL_5 0x40 +#define WM8993_CLOCKING_3 0x41 +#define WM8993_CLOCKING_4 0x42 +#define WM8993_MW_SLAVE_CONTROL 0x43 +#define WM8993_BUS_CONTROL_1 0x45 +#define WM8993_WRITE_SEQUENCER_0 0x46 +#define WM8993_WRITE_SEQUENCER_1 0x47 +#define WM8993_WRITE_SEQUENCER_2 0x48 +#define WM8993_WRITE_SEQUENCER_3 0x49 +#define WM8993_WRITE_SEQUENCER_4 0x4A +#define WM8993_WRITE_SEQUENCER_5 0x4B +#define WM8993_CHARGE_PUMP_1 0x4C +#define WM8993_CLASS_W_0 0x51 +#define WM8993_DC_SERVO_0 0x54 +#define WM8993_DC_SERVO_1 0x55 +#define WM8993_DC_SERVO_3 0x57 +#define WM8993_DC_SERVO_READBACK_0 0x58 +#define WM8993_DC_SERVO_READBACK_1 0x59 +#define WM8993_DC_SERVO_READBACK_2 0x5A +#define WM8993_ANALOGUE_HP_0 0x60 +#define WM8993_EQ1 0x62 +#define WM8993_EQ2 0x63 +#define WM8993_EQ3 0x64 +#define WM8993_EQ4 0x65 +#define WM8993_EQ5 0x66 +#define WM8993_EQ6 0x67 +#define WM8993_EQ7 0x68 +#define WM8993_EQ8 0x69 +#define WM8993_EQ9 0x6A +#define WM8993_EQ10 0x6B +#define WM8993_EQ11 0x6C +#define WM8993_EQ12 0x6D +#define WM8993_EQ13 0x6E +#define WM8993_EQ14 0x6F +#define WM8993_EQ15 0x70 +#define WM8993_EQ16 0x71 +#define WM8993_EQ17 0x72 +#define WM8993_EQ18 0x73 +#define WM8993_EQ19 0x74 +#define WM8993_EQ20 0x75 +#define WM8993_EQ21 0x76 +#define WM8993_EQ22 0x77 +#define WM8993_EQ23 0x78 +#define WM8993_EQ24 0x79 +#define WM8993_DIGITAL_PULLS 0x7A +#define WM8993_DRC_CONTROL_1 0x7B +#define WM8993_DRC_CONTROL_2 0x7C +#define WM8993_DRC_CONTROL_3 0x7D +#define WM8993_DRC_CONTROL_4 0x7E + +#define WM8993_REGISTER_COUNT 0x7F +#define WM8993_MAX_REGISTER 0x7E + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Software Reset + */ +#define WM8993_SW_RESET_MASK 0xFFFF /* SW_RESET - [15:0] */ +#define WM8993_SW_RESET_SHIFT 0 /* SW_RESET - [15:0] */ +#define WM8993_SW_RESET_WIDTH 16 /* SW_RESET - [15:0] */ + +/* + * R1 (0x01) - Power Management (1) + */ +#define WM8993_SPKOUTR_ENA 0x2000 /* SPKOUTR_ENA */ +#define WM8993_SPKOUTR_ENA_MASK 0x2000 /* SPKOUTR_ENA */ +#define WM8993_SPKOUTR_ENA_SHIFT 13 /* SPKOUTR_ENA */ +#define WM8993_SPKOUTR_ENA_WIDTH 1 /* SPKOUTR_ENA */ +#define WM8993_SPKOUTL_ENA 0x1000 /* SPKOUTL_ENA */ +#define WM8993_SPKOUTL_ENA_MASK 0x1000 /* SPKOUTL_ENA */ +#define WM8993_SPKOUTL_ENA_SHIFT 12 /* SPKOUTL_ENA */ +#define WM8993_SPKOUTL_ENA_WIDTH 1 /* SPKOUTL_ENA */ +#define WM8993_HPOUT2_ENA 0x0800 /* HPOUT2_ENA */ +#define WM8993_HPOUT2_ENA_MASK 0x0800 /* HPOUT2_ENA */ +#define WM8993_HPOUT2_ENA_SHIFT 11 /* HPOUT2_ENA */ +#define WM8993_HPOUT2_ENA_WIDTH 1 /* HPOUT2_ENA */ +#define WM8993_HPOUT1L_ENA 0x0200 /* HPOUT1L_ENA */ +#define WM8993_HPOUT1L_ENA_MASK 0x0200 /* HPOUT1L_ENA */ +#define WM8993_HPOUT1L_ENA_SHIFT 9 /* HPOUT1L_ENA */ +#define WM8993_HPOUT1L_ENA_WIDTH 1 /* HPOUT1L_ENA */ +#define WM8993_HPOUT1R_ENA 0x0100 /* HPOUT1R_ENA */ +#define WM8993_HPOUT1R_ENA_MASK 0x0100 /* HPOUT1R_ENA */ +#define WM8993_HPOUT1R_ENA_SHIFT 8 /* HPOUT1R_ENA */ +#define WM8993_HPOUT1R_ENA_WIDTH 1 /* HPOUT1R_ENA */ +#define WM8993_MICB2_ENA 0x0020 /* MICB2_ENA */ +#define WM8993_MICB2_ENA_MASK 0x0020 /* MICB2_ENA */ +#define WM8993_MICB2_ENA_SHIFT 5 /* MICB2_ENA */ +#define WM8993_MICB2_ENA_WIDTH 1 /* MICB2_ENA */ +#define WM8993_MICB1_ENA 0x0010 /* MICB1_ENA */ +#define WM8993_MICB1_ENA_MASK 0x0010 /* MICB1_ENA */ +#define WM8993_MICB1_ENA_SHIFT 4 /* MICB1_ENA */ +#define WM8993_MICB1_ENA_WIDTH 1 /* MICB1_ENA */ +#define WM8993_VMID_SEL_MASK 0x0006 /* VMID_SEL - [2:1] */ +#define WM8993_VMID_SEL_SHIFT 1 /* VMID_SEL - [2:1] */ +#define WM8993_VMID_SEL_WIDTH 2 /* VMID_SEL - [2:1] */ +#define WM8993_BIAS_ENA 0x0001 /* BIAS_ENA */ +#define WM8993_BIAS_ENA_MASK 0x0001 /* BIAS_ENA */ +#define WM8993_BIAS_ENA_SHIFT 0 /* BIAS_ENA */ +#define WM8993_BIAS_ENA_WIDTH 1 /* BIAS_ENA */ + +/* + * R2 (0x02) - Power Management (2) + */ +#define WM8993_TSHUT_ENA 0x4000 /* TSHUT_ENA */ +#define WM8993_TSHUT_ENA_MASK 0x4000 /* TSHUT_ENA */ +#define WM8993_TSHUT_ENA_SHIFT 14 /* TSHUT_ENA */ +#define WM8993_TSHUT_ENA_WIDTH 1 /* TSHUT_ENA */ +#define WM8993_TSHUT_OPDIS 0x2000 /* TSHUT_OPDIS */ +#define WM8993_TSHUT_OPDIS_MASK 0x2000 /* TSHUT_OPDIS */ +#define WM8993_TSHUT_OPDIS_SHIFT 13 /* TSHUT_OPDIS */ +#define WM8993_TSHUT_OPDIS_WIDTH 1 /* TSHUT_OPDIS */ +#define WM8993_OPCLK_ENA 0x0800 /* OPCLK_ENA */ +#define WM8993_OPCLK_ENA_MASK 0x0800 /* OPCLK_ENA */ +#define WM8993_OPCLK_ENA_SHIFT 11 /* OPCLK_ENA */ +#define WM8993_OPCLK_ENA_WIDTH 1 /* OPCLK_ENA */ +#define WM8993_MIXINL_ENA 0x0200 /* MIXINL_ENA */ +#define WM8993_MIXINL_ENA_MASK 0x0200 /* MIXINL_ENA */ +#define WM8993_MIXINL_ENA_SHIFT 9 /* MIXINL_ENA */ +#define WM8993_MIXINL_ENA_WIDTH 1 /* MIXINL_ENA */ +#define WM8993_MIXINR_ENA 0x0100 /* MIXINR_ENA */ +#define WM8993_MIXINR_ENA_MASK 0x0100 /* MIXINR_ENA */ +#define WM8993_MIXINR_ENA_SHIFT 8 /* MIXINR_ENA */ +#define WM8993_MIXINR_ENA_WIDTH 1 /* MIXINR_ENA */ +#define WM8993_IN2L_ENA 0x0080 /* IN2L_ENA */ +#define WM8993_IN2L_ENA_MASK 0x0080 /* IN2L_ENA */ +#define WM8993_IN2L_ENA_SHIFT 7 /* IN2L_ENA */ +#define WM8993_IN2L_ENA_WIDTH 1 /* IN2L_ENA */ +#define WM8993_IN1L_ENA 0x0040 /* IN1L_ENA */ +#define WM8993_IN1L_ENA_MASK 0x0040 /* IN1L_ENA */ +#define WM8993_IN1L_ENA_SHIFT 6 /* IN1L_ENA */ +#define WM8993_IN1L_ENA_WIDTH 1 /* IN1L_ENA */ +#define WM8993_IN2R_ENA 0x0020 /* IN2R_ENA */ +#define WM8993_IN2R_ENA_MASK 0x0020 /* IN2R_ENA */ +#define WM8993_IN2R_ENA_SHIFT 5 /* IN2R_ENA */ +#define WM8993_IN2R_ENA_WIDTH 1 /* IN2R_ENA */ +#define WM8993_IN1R_ENA 0x0010 /* IN1R_ENA */ +#define WM8993_IN1R_ENA_MASK 0x0010 /* IN1R_ENA */ +#define WM8993_IN1R_ENA_SHIFT 4 /* IN1R_ENA */ +#define WM8993_IN1R_ENA_WIDTH 1 /* IN1R_ENA */ +#define WM8993_ADCL_ENA 0x0002 /* ADCL_ENA */ +#define WM8993_ADCL_ENA_MASK 0x0002 /* ADCL_ENA */ +#define WM8993_ADCL_ENA_SHIFT 1 /* ADCL_ENA */ +#define WM8993_ADCL_ENA_WIDTH 1 /* ADCL_ENA */ +#define WM8993_ADCR_ENA 0x0001 /* ADCR_ENA */ +#define WM8993_ADCR_ENA_MASK 0x0001 /* ADCR_ENA */ +#define WM8993_ADCR_ENA_SHIFT 0 /* ADCR_ENA */ +#define WM8993_ADCR_ENA_WIDTH 1 /* ADCR_ENA */ + +/* + * R3 (0x03) - Power Management (3) + */ +#define WM8993_LINEOUT1N_ENA 0x2000 /* LINEOUT1N_ENA */ +#define WM8993_LINEOUT1N_ENA_MASK 0x2000 /* LINEOUT1N_ENA */ +#define WM8993_LINEOUT1N_ENA_SHIFT 13 /* LINEOUT1N_ENA */ +#define WM8993_LINEOUT1N_ENA_WIDTH 1 /* LINEOUT1N_ENA */ +#define WM8993_LINEOUT1P_ENA 0x1000 /* LINEOUT1P_ENA */ +#define WM8993_LINEOUT1P_ENA_MASK 0x1000 /* LINEOUT1P_ENA */ +#define WM8993_LINEOUT1P_ENA_SHIFT 12 /* LINEOUT1P_ENA */ +#define WM8993_LINEOUT1P_ENA_WIDTH 1 /* LINEOUT1P_ENA */ +#define WM8993_LINEOUT2N_ENA 0x0800 /* LINEOUT2N_ENA */ +#define WM8993_LINEOUT2N_ENA_MASK 0x0800 /* LINEOUT2N_ENA */ +#define WM8993_LINEOUT2N_ENA_SHIFT 11 /* LINEOUT2N_ENA */ +#define WM8993_LINEOUT2N_ENA_WIDTH 1 /* LINEOUT2N_ENA */ +#define WM8993_LINEOUT2P_ENA 0x0400 /* LINEOUT2P_ENA */ +#define WM8993_LINEOUT2P_ENA_MASK 0x0400 /* LINEOUT2P_ENA */ +#define WM8993_LINEOUT2P_ENA_SHIFT 10 /* LINEOUT2P_ENA */ +#define WM8993_LINEOUT2P_ENA_WIDTH 1 /* LINEOUT2P_ENA */ +#define WM8993_SPKRVOL_ENA 0x0200 /* SPKRVOL_ENA */ +#define WM8993_SPKRVOL_ENA_MASK 0x0200 /* SPKRVOL_ENA */ +#define WM8993_SPKRVOL_ENA_SHIFT 9 /* SPKRVOL_ENA */ +#define WM8993_SPKRVOL_ENA_WIDTH 1 /* SPKRVOL_ENA */ +#define WM8993_SPKLVOL_ENA 0x0100 /* SPKLVOL_ENA */ +#define WM8993_SPKLVOL_ENA_MASK 0x0100 /* SPKLVOL_ENA */ +#define WM8993_SPKLVOL_ENA_SHIFT 8 /* SPKLVOL_ENA */ +#define WM8993_SPKLVOL_ENA_WIDTH 1 /* SPKLVOL_ENA */ +#define WM8993_MIXOUTLVOL_ENA 0x0080 /* MIXOUTLVOL_ENA */ +#define WM8993_MIXOUTLVOL_ENA_MASK 0x0080 /* MIXOUTLVOL_ENA */ +#define WM8993_MIXOUTLVOL_ENA_SHIFT 7 /* MIXOUTLVOL_ENA */ +#define WM8993_MIXOUTLVOL_ENA_WIDTH 1 /* MIXOUTLVOL_ENA */ +#define WM8993_MIXOUTRVOL_ENA 0x0040 /* MIXOUTRVOL_ENA */ +#define WM8993_MIXOUTRVOL_ENA_MASK 0x0040 /* MIXOUTRVOL_ENA */ +#define WM8993_MIXOUTRVOL_ENA_SHIFT 6 /* MIXOUTRVOL_ENA */ +#define WM8993_MIXOUTRVOL_ENA_WIDTH 1 /* MIXOUTRVOL_ENA */ +#define WM8993_MIXOUTL_ENA 0x0020 /* MIXOUTL_ENA */ +#define WM8993_MIXOUTL_ENA_MASK 0x0020 /* MIXOUTL_ENA */ +#define WM8993_MIXOUTL_ENA_SHIFT 5 /* MIXOUTL_ENA */ +#define WM8993_MIXOUTL_ENA_WIDTH 1 /* MIXOUTL_ENA */ +#define WM8993_MIXOUTR_ENA 0x0010 /* MIXOUTR_ENA */ +#define WM8993_MIXOUTR_ENA_MASK 0x0010 /* MIXOUTR_ENA */ +#define WM8993_MIXOUTR_ENA_SHIFT 4 /* MIXOUTR_ENA */ +#define WM8993_MIXOUTR_ENA_WIDTH 1 /* MIXOUTR_ENA */ +#define WM8993_DACL_ENA 0x0002 /* DACL_ENA */ +#define WM8993_DACL_ENA_MASK 0x0002 /* DACL_ENA */ +#define WM8993_DACL_ENA_SHIFT 1 /* DACL_ENA */ +#define WM8993_DACL_ENA_WIDTH 1 /* DACL_ENA */ +#define WM8993_DACR_ENA 0x0001 /* DACR_ENA */ +#define WM8993_DACR_ENA_MASK 0x0001 /* DACR_ENA */ +#define WM8993_DACR_ENA_SHIFT 0 /* DACR_ENA */ +#define WM8993_DACR_ENA_WIDTH 1 /* DACR_ENA */ + +/* + * R4 (0x04) - Audio Interface (1) + */ +#define WM8993_AIFADCL_SRC 0x8000 /* AIFADCL_SRC */ +#define WM8993_AIFADCL_SRC_MASK 0x8000 /* AIFADCL_SRC */ +#define WM8993_AIFADCL_SRC_SHIFT 15 /* AIFADCL_SRC */ +#define WM8993_AIFADCL_SRC_WIDTH 1 /* AIFADCL_SRC */ +#define WM8993_AIFADCR_SRC 0x4000 /* AIFADCR_SRC */ +#define WM8993_AIFADCR_SRC_MASK 0x4000 /* AIFADCR_SRC */ +#define WM8993_AIFADCR_SRC_SHIFT 14 /* AIFADCR_SRC */ +#define WM8993_AIFADCR_SRC_WIDTH 1 /* AIFADCR_SRC */ +#define WM8993_AIFADC_TDM 0x2000 /* AIFADC_TDM */ +#define WM8993_AIFADC_TDM_MASK 0x2000 /* AIFADC_TDM */ +#define WM8993_AIFADC_TDM_SHIFT 13 /* AIFADC_TDM */ +#define WM8993_AIFADC_TDM_WIDTH 1 /* AIFADC_TDM */ +#define WM8993_AIFADC_TDM_CHAN 0x1000 /* AIFADC_TDM_CHAN */ +#define WM8993_AIFADC_TDM_CHAN_MASK 0x1000 /* AIFADC_TDM_CHAN */ +#define WM8993_AIFADC_TDM_CHAN_SHIFT 12 /* AIFADC_TDM_CHAN */ +#define WM8993_AIFADC_TDM_CHAN_WIDTH 1 /* AIFADC_TDM_CHAN */ +#define WM8993_BCLK_DIR 0x0200 /* BCLK_DIR */ +#define WM8993_BCLK_DIR_MASK 0x0200 /* BCLK_DIR */ +#define WM8993_BCLK_DIR_SHIFT 9 /* BCLK_DIR */ +#define WM8993_BCLK_DIR_WIDTH 1 /* BCLK_DIR */ +#define WM8993_AIF_BCLK_INV 0x0100 /* AIF_BCLK_INV */ +#define WM8993_AIF_BCLK_INV_MASK 0x0100 /* AIF_BCLK_INV */ +#define WM8993_AIF_BCLK_INV_SHIFT 8 /* AIF_BCLK_INV */ +#define WM8993_AIF_BCLK_INV_WIDTH 1 /* AIF_BCLK_INV */ +#define WM8993_AIF_LRCLK_INV 0x0080 /* AIF_LRCLK_INV */ +#define WM8993_AIF_LRCLK_INV_MASK 0x0080 /* AIF_LRCLK_INV */ +#define WM8993_AIF_LRCLK_INV_SHIFT 7 /* AIF_LRCLK_INV */ +#define WM8993_AIF_LRCLK_INV_WIDTH 1 /* AIF_LRCLK_INV */ +#define WM8993_AIF_WL_MASK 0x0060 /* AIF_WL - [6:5] */ +#define WM8993_AIF_WL_SHIFT 5 /* AIF_WL - [6:5] */ +#define WM8993_AIF_WL_WIDTH 2 /* AIF_WL - [6:5] */ +#define WM8993_AIF_FMT_MASK 0x0018 /* AIF_FMT - [4:3] */ +#define WM8993_AIF_FMT_SHIFT 3 /* AIF_FMT - [4:3] */ +#define WM8993_AIF_FMT_WIDTH 2 /* AIF_FMT - [4:3] */ + +/* + * R5 (0x05) - Audio Interface (2) + */ +#define WM8993_AIFDACL_SRC 0x8000 /* AIFDACL_SRC */ +#define WM8993_AIFDACL_SRC_MASK 0x8000 /* AIFDACL_SRC */ +#define WM8993_AIFDACL_SRC_SHIFT 15 /* AIFDACL_SRC */ +#define WM8993_AIFDACL_SRC_WIDTH 1 /* AIFDACL_SRC */ +#define WM8993_AIFDACR_SRC 0x4000 /* AIFDACR_SRC */ +#define WM8993_AIFDACR_SRC_MASK 0x4000 /* AIFDACR_SRC */ +#define WM8993_AIFDACR_SRC_SHIFT 14 /* AIFDACR_SRC */ +#define WM8993_AIFDACR_SRC_WIDTH 1 /* AIFDACR_SRC */ +#define WM8993_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */ +#define WM8993_AIFDAC_TDM_MASK 0x2000 /* AIFDAC_TDM */ +#define WM8993_AIFDAC_TDM_SHIFT 13 /* AIFDAC_TDM */ +#define WM8993_AIFDAC_TDM_WIDTH 1 /* AIFDAC_TDM */ +#define WM8993_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */ +#define WM8993_AIFDAC_TDM_CHAN_MASK 0x1000 /* AIFDAC_TDM_CHAN */ +#define WM8993_AIFDAC_TDM_CHAN_SHIFT 12 /* AIFDAC_TDM_CHAN */ +#define WM8993_AIFDAC_TDM_CHAN_WIDTH 1 /* AIFDAC_TDM_CHAN */ +#define WM8993_DAC_BOOST_MASK 0x0C00 /* DAC_BOOST - [11:10] */ +#define WM8993_DAC_BOOST_SHIFT 10 /* DAC_BOOST - [11:10] */ +#define WM8993_DAC_BOOST_WIDTH 2 /* DAC_BOOST - [11:10] */ +#define WM8993_DAC_COMP 0x0010 /* DAC_COMP */ +#define WM8993_DAC_COMP_MASK 0x0010 /* DAC_COMP */ +#define WM8993_DAC_COMP_SHIFT 4 /* DAC_COMP */ +#define WM8993_DAC_COMP_WIDTH 1 /* DAC_COMP */ +#define WM8993_DAC_COMPMODE 0x0008 /* DAC_COMPMODE */ +#define WM8993_DAC_COMPMODE_MASK 0x0008 /* DAC_COMPMODE */ +#define WM8993_DAC_COMPMODE_SHIFT 3 /* DAC_COMPMODE */ +#define WM8993_DAC_COMPMODE_WIDTH 1 /* DAC_COMPMODE */ +#define WM8993_ADC_COMP 0x0004 /* ADC_COMP */ +#define WM8993_ADC_COMP_MASK 0x0004 /* ADC_COMP */ +#define WM8993_ADC_COMP_SHIFT 2 /* ADC_COMP */ +#define WM8993_ADC_COMP_WIDTH 1 /* ADC_COMP */ +#define WM8993_ADC_COMPMODE 0x0002 /* ADC_COMPMODE */ +#define WM8993_ADC_COMPMODE_MASK 0x0002 /* ADC_COMPMODE */ +#define WM8993_ADC_COMPMODE_SHIFT 1 /* ADC_COMPMODE */ +#define WM8993_ADC_COMPMODE_WIDTH 1 /* ADC_COMPMODE */ +#define WM8993_LOOPBACK 0x0001 /* LOOPBACK */ +#define WM8993_LOOPBACK_MASK 0x0001 /* LOOPBACK */ +#define WM8993_LOOPBACK_SHIFT 0 /* LOOPBACK */ +#define WM8993_LOOPBACK_WIDTH 1 /* LOOPBACK */ + +/* + * R6 (0x06) - Clocking 1 + */ +#define WM8993_TOCLK_RATE 0x8000 /* TOCLK_RATE */ +#define WM8993_TOCLK_RATE_MASK 0x8000 /* TOCLK_RATE */ +#define WM8993_TOCLK_RATE_SHIFT 15 /* TOCLK_RATE */ +#define WM8993_TOCLK_RATE_WIDTH 1 /* TOCLK_RATE */ +#define WM8993_TOCLK_ENA 0x4000 /* TOCLK_ENA */ +#define WM8993_TOCLK_ENA_MASK 0x4000 /* TOCLK_ENA */ +#define WM8993_TOCLK_ENA_SHIFT 14 /* TOCLK_ENA */ +#define WM8993_TOCLK_ENA_WIDTH 1 /* TOCLK_ENA */ +#define WM8993_OPCLK_DIV_MASK 0x1E00 /* OPCLK_DIV - [12:9] */ +#define WM8993_OPCLK_DIV_SHIFT 9 /* OPCLK_DIV - [12:9] */ +#define WM8993_OPCLK_DIV_WIDTH 4 /* OPCLK_DIV - [12:9] */ +#define WM8993_DCLK_DIV_MASK 0x01C0 /* DCLK_DIV - [8:6] */ +#define WM8993_DCLK_DIV_SHIFT 6 /* DCLK_DIV - [8:6] */ +#define WM8993_DCLK_DIV_WIDTH 3 /* DCLK_DIV - [8:6] */ +#define WM8993_BCLK_DIV_MASK 0x001E /* BCLK_DIV - [4:1] */ +#define WM8993_BCLK_DIV_SHIFT 1 /* BCLK_DIV - [4:1] */ +#define WM8993_BCLK_DIV_WIDTH 4 /* BCLK_DIV - [4:1] */ + +/* + * R7 (0x07) - Clocking 2 + */ +#define WM8993_MCLK_SRC 0x8000 /* MCLK_SRC */ +#define WM8993_MCLK_SRC_MASK 0x8000 /* MCLK_SRC */ +#define WM8993_MCLK_SRC_SHIFT 15 /* MCLK_SRC */ +#define WM8993_MCLK_SRC_WIDTH 1 /* MCLK_SRC */ +#define WM8993_SYSCLK_SRC 0x4000 /* SYSCLK_SRC */ +#define WM8993_SYSCLK_SRC_MASK 0x4000 /* SYSCLK_SRC */ +#define WM8993_SYSCLK_SRC_SHIFT 14 /* SYSCLK_SRC */ +#define WM8993_SYSCLK_SRC_WIDTH 1 /* SYSCLK_SRC */ +#define WM8993_MCLK_DIV 0x1000 /* MCLK_DIV */ +#define WM8993_MCLK_DIV_MASK 0x1000 /* MCLK_DIV */ +#define WM8993_MCLK_DIV_SHIFT 12 /* MCLK_DIV */ +#define WM8993_MCLK_DIV_WIDTH 1 /* MCLK_DIV */ +#define WM8993_MCLK_INV 0x0400 /* MCLK_INV */ +#define WM8993_MCLK_INV_MASK 0x0400 /* MCLK_INV */ +#define WM8993_MCLK_INV_SHIFT 10 /* MCLK_INV */ +#define WM8993_MCLK_INV_WIDTH 1 /* MCLK_INV */ +#define WM8993_ADC_DIV_MASK 0x00E0 /* ADC_DIV - [7:5] */ +#define WM8993_ADC_DIV_SHIFT 5 /* ADC_DIV - [7:5] */ +#define WM8993_ADC_DIV_WIDTH 3 /* ADC_DIV - [7:5] */ +#define WM8993_DAC_DIV_MASK 0x001C /* DAC_DIV - [4:2] */ +#define WM8993_DAC_DIV_SHIFT 2 /* DAC_DIV - [4:2] */ +#define WM8993_DAC_DIV_WIDTH 3 /* DAC_DIV - [4:2] */ + +/* + * R8 (0x08) - Audio Interface (3) + */ +#define WM8993_AIF_MSTR1 0x8000 /* AIF_MSTR1 */ +#define WM8993_AIF_MSTR1_MASK 0x8000 /* AIF_MSTR1 */ +#define WM8993_AIF_MSTR1_SHIFT 15 /* AIF_MSTR1 */ +#define WM8993_AIF_MSTR1_WIDTH 1 /* AIF_MSTR1 */ + +/* + * R9 (0x09) - Audio Interface (4) + */ +#define WM8993_AIF_TRIS 0x2000 /* AIF_TRIS */ +#define WM8993_AIF_TRIS_MASK 0x2000 /* AIF_TRIS */ +#define WM8993_AIF_TRIS_SHIFT 13 /* AIF_TRIS */ +#define WM8993_AIF_TRIS_WIDTH 1 /* AIF_TRIS */ +#define WM8993_LRCLK_DIR 0x0800 /* LRCLK_DIR */ +#define WM8993_LRCLK_DIR_MASK 0x0800 /* LRCLK_DIR */ +#define WM8993_LRCLK_DIR_SHIFT 11 /* LRCLK_DIR */ +#define WM8993_LRCLK_DIR_WIDTH 1 /* LRCLK_DIR */ +#define WM8993_LRCLK_RATE_MASK 0x07FF /* LRCLK_RATE - [10:0] */ +#define WM8993_LRCLK_RATE_SHIFT 0 /* LRCLK_RATE - [10:0] */ +#define WM8993_LRCLK_RATE_WIDTH 11 /* LRCLK_RATE - [10:0] */ + +/* + * R10 (0x0A) - DAC CTRL + */ +#define WM8993_DAC_OSR128 0x2000 /* DAC_OSR128 */ +#define WM8993_DAC_OSR128_MASK 0x2000 /* DAC_OSR128 */ +#define WM8993_DAC_OSR128_SHIFT 13 /* DAC_OSR128 */ +#define WM8993_DAC_OSR128_WIDTH 1 /* DAC_OSR128 */ +#define WM8993_DAC_MONO 0x0200 /* DAC_MONO */ +#define WM8993_DAC_MONO_MASK 0x0200 /* DAC_MONO */ +#define WM8993_DAC_MONO_SHIFT 9 /* DAC_MONO */ +#define WM8993_DAC_MONO_WIDTH 1 /* DAC_MONO */ +#define WM8993_DAC_SB_FILT 0x0100 /* DAC_SB_FILT */ +#define WM8993_DAC_SB_FILT_MASK 0x0100 /* DAC_SB_FILT */ +#define WM8993_DAC_SB_FILT_SHIFT 8 /* DAC_SB_FILT */ +#define WM8993_DAC_SB_FILT_WIDTH 1 /* DAC_SB_FILT */ +#define WM8993_DAC_MUTERATE 0x0080 /* DAC_MUTERATE */ +#define WM8993_DAC_MUTERATE_MASK 0x0080 /* DAC_MUTERATE */ +#define WM8993_DAC_MUTERATE_SHIFT 7 /* DAC_MUTERATE */ +#define WM8993_DAC_MUTERATE_WIDTH 1 /* DAC_MUTERATE */ +#define WM8993_DAC_UNMUTE_RAMP 0x0040 /* DAC_UNMUTE_RAMP */ +#define WM8993_DAC_UNMUTE_RAMP_MASK 0x0040 /* DAC_UNMUTE_RAMP */ +#define WM8993_DAC_UNMUTE_RAMP_SHIFT 6 /* DAC_UNMUTE_RAMP */ +#define WM8993_DAC_UNMUTE_RAMP_WIDTH 1 /* DAC_UNMUTE_RAMP */ +#define WM8993_DEEMPH_MASK 0x0030 /* DEEMPH - [5:4] */ +#define WM8993_DEEMPH_SHIFT 4 /* DEEMPH - [5:4] */ +#define WM8993_DEEMPH_WIDTH 2 /* DEEMPH - [5:4] */ +#define WM8993_DAC_MUTE 0x0004 /* DAC_MUTE */ +#define WM8993_DAC_MUTE_MASK 0x0004 /* DAC_MUTE */ +#define WM8993_DAC_MUTE_SHIFT 2 /* DAC_MUTE */ +#define WM8993_DAC_MUTE_WIDTH 1 /* DAC_MUTE */ +#define WM8993_DACL_DATINV 0x0002 /* DACL_DATINV */ +#define WM8993_DACL_DATINV_MASK 0x0002 /* DACL_DATINV */ +#define WM8993_DACL_DATINV_SHIFT 1 /* DACL_DATINV */ +#define WM8993_DACL_DATINV_WIDTH 1 /* DACL_DATINV */ +#define WM8993_DACR_DATINV 0x0001 /* DACR_DATINV */ +#define WM8993_DACR_DATINV_MASK 0x0001 /* DACR_DATINV */ +#define WM8993_DACR_DATINV_SHIFT 0 /* DACR_DATINV */ +#define WM8993_DACR_DATINV_WIDTH 1 /* DACR_DATINV */ + +/* + * R11 (0x0B) - Left DAC Digital Volume + */ +#define WM8993_DAC_VU 0x0100 /* DAC_VU */ +#define WM8993_DAC_VU_MASK 0x0100 /* DAC_VU */ +#define WM8993_DAC_VU_SHIFT 8 /* DAC_VU */ +#define WM8993_DAC_VU_WIDTH 1 /* DAC_VU */ +#define WM8993_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */ +#define WM8993_DACL_VOL_SHIFT 0 /* DACL_VOL - [7:0] */ +#define WM8993_DACL_VOL_WIDTH 8 /* DACL_VOL - [7:0] */ + +/* + * R12 (0x0C) - Right DAC Digital Volume + */ +#define WM8993_DAC_VU 0x0100 /* DAC_VU */ +#define WM8993_DAC_VU_MASK 0x0100 /* DAC_VU */ +#define WM8993_DAC_VU_SHIFT 8 /* DAC_VU */ +#define WM8993_DAC_VU_WIDTH 1 /* DAC_VU */ +#define WM8993_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */ +#define WM8993_DACR_VOL_SHIFT 0 /* DACR_VOL - [7:0] */ +#define WM8993_DACR_VOL_WIDTH 8 /* DACR_VOL - [7:0] */ + +/* + * R13 (0x0D) - Digital Side Tone + */ +#define WM8993_ADCL_DAC_SVOL_MASK 0x1E00 /* ADCL_DAC_SVOL - [12:9] */ +#define WM8993_ADCL_DAC_SVOL_SHIFT 9 /* ADCL_DAC_SVOL - [12:9] */ +#define WM8993_ADCL_DAC_SVOL_WIDTH 4 /* ADCL_DAC_SVOL - [12:9] */ +#define WM8993_ADCR_DAC_SVOL_MASK 0x01E0 /* ADCR_DAC_SVOL - [8:5] */ +#define WM8993_ADCR_DAC_SVOL_SHIFT 5 /* ADCR_DAC_SVOL - [8:5] */ +#define WM8993_ADCR_DAC_SVOL_WIDTH 4 /* ADCR_DAC_SVOL - [8:5] */ +#define WM8993_ADC_TO_DACL_MASK 0x000C /* ADC_TO_DACL - [3:2] */ +#define WM8993_ADC_TO_DACL_SHIFT 2 /* ADC_TO_DACL - [3:2] */ +#define WM8993_ADC_TO_DACL_WIDTH 2 /* ADC_TO_DACL - [3:2] */ +#define WM8993_ADC_TO_DACR_MASK 0x0003 /* ADC_TO_DACR - [1:0] */ +#define WM8993_ADC_TO_DACR_SHIFT 0 /* ADC_TO_DACR - [1:0] */ +#define WM8993_ADC_TO_DACR_WIDTH 2 /* ADC_TO_DACR - [1:0] */ + +/* + * R14 (0x0E) - ADC CTRL + */ +#define WM8993_ADC_OSR128 0x0200 /* ADC_OSR128 */ +#define WM8993_ADC_OSR128_MASK 0x0200 /* ADC_OSR128 */ +#define WM8993_ADC_OSR128_SHIFT 9 /* ADC_OSR128 */ +#define WM8993_ADC_OSR128_WIDTH 1 /* ADC_OSR128 */ +#define WM8993_ADC_HPF 0x0100 /* ADC_HPF */ +#define WM8993_ADC_HPF_MASK 0x0100 /* ADC_HPF */ +#define WM8993_ADC_HPF_SHIFT 8 /* ADC_HPF */ +#define WM8993_ADC_HPF_WIDTH 1 /* ADC_HPF */ +#define WM8993_ADC_HPF_CUT_MASK 0x0060 /* ADC_HPF_CUT - [6:5] */ +#define WM8993_ADC_HPF_CUT_SHIFT 5 /* ADC_HPF_CUT - [6:5] */ +#define WM8993_ADC_HPF_CUT_WIDTH 2 /* ADC_HPF_CUT - [6:5] */ +#define WM8993_ADCL_DATINV 0x0002 /* ADCL_DATINV */ +#define WM8993_ADCL_DATINV_MASK 0x0002 /* ADCL_DATINV */ +#define WM8993_ADCL_DATINV_SHIFT 1 /* ADCL_DATINV */ +#define WM8993_ADCL_DATINV_WIDTH 1 /* ADCL_DATINV */ +#define WM8993_ADCR_DATINV 0x0001 /* ADCR_DATINV */ +#define WM8993_ADCR_DATINV_MASK 0x0001 /* ADCR_DATINV */ +#define WM8993_ADCR_DATINV_SHIFT 0 /* ADCR_DATINV */ +#define WM8993_ADCR_DATINV_WIDTH 1 /* ADCR_DATINV */ + +/* + * R15 (0x0F) - Left ADC Digital Volume + */ +#define WM8993_ADC_VU 0x0100 /* ADC_VU */ +#define WM8993_ADC_VU_MASK 0x0100 /* ADC_VU */ +#define WM8993_ADC_VU_SHIFT 8 /* ADC_VU */ +#define WM8993_ADC_VU_WIDTH 1 /* ADC_VU */ +#define WM8993_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */ +#define WM8993_ADCL_VOL_SHIFT 0 /* ADCL_VOL - [7:0] */ +#define WM8993_ADCL_VOL_WIDTH 8 /* ADCL_VOL - [7:0] */ + +/* + * R16 (0x10) - Right ADC Digital Volume + */ +#define WM8993_ADC_VU 0x0100 /* ADC_VU */ +#define WM8993_ADC_VU_MASK 0x0100 /* ADC_VU */ +#define WM8993_ADC_VU_SHIFT 8 /* ADC_VU */ +#define WM8993_ADC_VU_WIDTH 1 /* ADC_VU */ +#define WM8993_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */ +#define WM8993_ADCR_VOL_SHIFT 0 /* ADCR_VOL - [7:0] */ +#define WM8993_ADCR_VOL_WIDTH 8 /* ADCR_VOL - [7:0] */ + +/* + * R18 (0x12) - GPIO CTRL 1 + */ +#define WM8993_JD2_SC_EINT 0x8000 /* JD2_SC_EINT */ +#define WM8993_JD2_SC_EINT_MASK 0x8000 /* JD2_SC_EINT */ +#define WM8993_JD2_SC_EINT_SHIFT 15 /* JD2_SC_EINT */ +#define WM8993_JD2_SC_EINT_WIDTH 1 /* JD2_SC_EINT */ +#define WM8993_JD2_EINT 0x4000 /* JD2_EINT */ +#define WM8993_JD2_EINT_MASK 0x4000 /* JD2_EINT */ +#define WM8993_JD2_EINT_SHIFT 14 /* JD2_EINT */ +#define WM8993_JD2_EINT_WIDTH 1 /* JD2_EINT */ +#define WM8993_WSEQ_EINT 0x2000 /* WSEQ_EINT */ +#define WM8993_WSEQ_EINT_MASK 0x2000 /* WSEQ_EINT */ +#define WM8993_WSEQ_EINT_SHIFT 13 /* WSEQ_EINT */ +#define WM8993_WSEQ_EINT_WIDTH 1 /* WSEQ_EINT */ +#define WM8993_IRQ 0x1000 /* IRQ */ +#define WM8993_IRQ_MASK 0x1000 /* IRQ */ +#define WM8993_IRQ_SHIFT 12 /* IRQ */ +#define WM8993_IRQ_WIDTH 1 /* IRQ */ +#define WM8993_TEMPOK_EINT 0x0800 /* TEMPOK_EINT */ +#define WM8993_TEMPOK_EINT_MASK 0x0800 /* TEMPOK_EINT */ +#define WM8993_TEMPOK_EINT_SHIFT 11 /* TEMPOK_EINT */ +#define WM8993_TEMPOK_EINT_WIDTH 1 /* TEMPOK_EINT */ +#define WM8993_JD1_SC_EINT 0x0400 /* JD1_SC_EINT */ +#define WM8993_JD1_SC_EINT_MASK 0x0400 /* JD1_SC_EINT */ +#define WM8993_JD1_SC_EINT_SHIFT 10 /* JD1_SC_EINT */ +#define WM8993_JD1_SC_EINT_WIDTH 1 /* JD1_SC_EINT */ +#define WM8993_JD1_EINT 0x0200 /* JD1_EINT */ +#define WM8993_JD1_EINT_MASK 0x0200 /* JD1_EINT */ +#define WM8993_JD1_EINT_SHIFT 9 /* JD1_EINT */ +#define WM8993_JD1_EINT_WIDTH 1 /* JD1_EINT */ +#define WM8993_FLL_LOCK_EINT 0x0100 /* FLL_LOCK_EINT */ +#define WM8993_FLL_LOCK_EINT_MASK 0x0100 /* FLL_LOCK_EINT */ +#define WM8993_FLL_LOCK_EINT_SHIFT 8 /* FLL_LOCK_EINT */ +#define WM8993_FLL_LOCK_EINT_WIDTH 1 /* FLL_LOCK_EINT */ +#define WM8993_GPI8_EINT 0x0080 /* GPI8_EINT */ +#define WM8993_GPI8_EINT_MASK 0x0080 /* GPI8_EINT */ +#define WM8993_GPI8_EINT_SHIFT 7 /* GPI8_EINT */ +#define WM8993_GPI8_EINT_WIDTH 1 /* GPI8_EINT */ +#define WM8993_GPI7_EINT 0x0040 /* GPI7_EINT */ +#define WM8993_GPI7_EINT_MASK 0x0040 /* GPI7_EINT */ +#define WM8993_GPI7_EINT_SHIFT 6 /* GPI7_EINT */ +#define WM8993_GPI7_EINT_WIDTH 1 /* GPI7_EINT */ +#define WM8993_GPIO1_EINT 0x0001 /* GPIO1_EINT */ +#define WM8993_GPIO1_EINT_MASK 0x0001 /* GPIO1_EINT */ +#define WM8993_GPIO1_EINT_SHIFT 0 /* GPIO1_EINT */ +#define WM8993_GPIO1_EINT_WIDTH 1 /* GPIO1_EINT */ + +/* + * R19 (0x13) - GPIO1 + */ +#define WM8993_GPIO1_PU 0x0020 /* GPIO1_PU */ +#define WM8993_GPIO1_PU_MASK 0x0020 /* GPIO1_PU */ +#define WM8993_GPIO1_PU_SHIFT 5 /* GPIO1_PU */ +#define WM8993_GPIO1_PU_WIDTH 1 /* GPIO1_PU */ +#define WM8993_GPIO1_PD 0x0010 /* GPIO1_PD */ +#define WM8993_GPIO1_PD_MASK 0x0010 /* GPIO1_PD */ +#define WM8993_GPIO1_PD_SHIFT 4 /* GPIO1_PD */ +#define WM8993_GPIO1_PD_WIDTH 1 /* GPIO1_PD */ +#define WM8993_GPIO1_SEL_MASK 0x000F /* GPIO1_SEL - [3:0] */ +#define WM8993_GPIO1_SEL_SHIFT 0 /* GPIO1_SEL - [3:0] */ +#define WM8993_GPIO1_SEL_WIDTH 4 /* GPIO1_SEL - [3:0] */ + +/* + * R20 (0x14) - IRQ_DEBOUNCE + */ +#define WM8993_JD2_SC_DB 0x8000 /* JD2_SC_DB */ +#define WM8993_JD2_SC_DB_MASK 0x8000 /* JD2_SC_DB */ +#define WM8993_JD2_SC_DB_SHIFT 15 /* JD2_SC_DB */ +#define WM8993_JD2_SC_DB_WIDTH 1 /* JD2_SC_DB */ +#define WM8993_JD2_DB 0x4000 /* JD2_DB */ +#define WM8993_JD2_DB_MASK 0x4000 /* JD2_DB */ +#define WM8993_JD2_DB_SHIFT 14 /* JD2_DB */ +#define WM8993_JD2_DB_WIDTH 1 /* JD2_DB */ +#define WM8993_WSEQ_DB 0x2000 /* WSEQ_DB */ +#define WM8993_WSEQ_DB_MASK 0x2000 /* WSEQ_DB */ +#define WM8993_WSEQ_DB_SHIFT 13 /* WSEQ_DB */ +#define WM8993_WSEQ_DB_WIDTH 1 /* WSEQ_DB */ +#define WM8993_TEMPOK_DB 0x0800 /* TEMPOK_DB */ +#define WM8993_TEMPOK_DB_MASK 0x0800 /* TEMPOK_DB */ +#define WM8993_TEMPOK_DB_SHIFT 11 /* TEMPOK_DB */ +#define WM8993_TEMPOK_DB_WIDTH 1 /* TEMPOK_DB */ +#define WM8993_JD1_SC_DB 0x0400 /* JD1_SC_DB */ +#define WM8993_JD1_SC_DB_MASK 0x0400 /* JD1_SC_DB */ +#define WM8993_JD1_SC_DB_SHIFT 10 /* JD1_SC_DB */ +#define WM8993_JD1_SC_DB_WIDTH 1 /* JD1_SC_DB */ +#define WM8993_JD1_DB 0x0200 /* JD1_DB */ +#define WM8993_JD1_DB_MASK 0x0200 /* JD1_DB */ +#define WM8993_JD1_DB_SHIFT 9 /* JD1_DB */ +#define WM8993_JD1_DB_WIDTH 1 /* JD1_DB */ +#define WM8993_FLL_LOCK_DB 0x0100 /* FLL_LOCK_DB */ +#define WM8993_FLL_LOCK_DB_MASK 0x0100 /* FLL_LOCK_DB */ +#define WM8993_FLL_LOCK_DB_SHIFT 8 /* FLL_LOCK_DB */ +#define WM8993_FLL_LOCK_DB_WIDTH 1 /* FLL_LOCK_DB */ +#define WM8993_GPI8_DB 0x0080 /* GPI8_DB */ +#define WM8993_GPI8_DB_MASK 0x0080 /* GPI8_DB */ +#define WM8993_GPI8_DB_SHIFT 7 /* GPI8_DB */ +#define WM8993_GPI8_DB_WIDTH 1 /* GPI8_DB */ +#define WM8993_GPI7_DB 0x0008 /* GPI7_DB */ +#define WM8993_GPI7_DB_MASK 0x0008 /* GPI7_DB */ +#define WM8993_GPI7_DB_SHIFT 3 /* GPI7_DB */ +#define WM8993_GPI7_DB_WIDTH 1 /* GPI7_DB */ +#define WM8993_GPIO1_DB 0x0001 /* GPIO1_DB */ +#define WM8993_GPIO1_DB_MASK 0x0001 /* GPIO1_DB */ +#define WM8993_GPIO1_DB_SHIFT 0 /* GPIO1_DB */ +#define WM8993_GPIO1_DB_WIDTH 1 /* GPIO1_DB */ + +/* + * R22 (0x16) - GPIOCTRL 2 + */ +#define WM8993_IM_JD2_EINT 0x2000 /* IM_JD2_EINT */ +#define WM8993_IM_JD2_EINT_MASK 0x2000 /* IM_JD2_EINT */ +#define WM8993_IM_JD2_EINT_SHIFT 13 /* IM_JD2_EINT */ +#define WM8993_IM_JD2_EINT_WIDTH 1 /* IM_JD2_EINT */ +#define WM8993_IM_JD2_SC_EINT 0x1000 /* IM_JD2_SC_EINT */ +#define WM8993_IM_JD2_SC_EINT_MASK 0x1000 /* IM_JD2_SC_EINT */ +#define WM8993_IM_JD2_SC_EINT_SHIFT 12 /* IM_JD2_SC_EINT */ +#define WM8993_IM_JD2_SC_EINT_WIDTH 1 /* IM_JD2_SC_EINT */ +#define WM8993_IM_TEMPOK_EINT 0x0800 /* IM_TEMPOK_EINT */ +#define WM8993_IM_TEMPOK_EINT_MASK 0x0800 /* IM_TEMPOK_EINT */ +#define WM8993_IM_TEMPOK_EINT_SHIFT 11 /* IM_TEMPOK_EINT */ +#define WM8993_IM_TEMPOK_EINT_WIDTH 1 /* IM_TEMPOK_EINT */ +#define WM8993_IM_JD1_SC_EINT 0x0400 /* IM_JD1_SC_EINT */ +#define WM8993_IM_JD1_SC_EINT_MASK 0x0400 /* IM_JD1_SC_EINT */ +#define WM8993_IM_JD1_SC_EINT_SHIFT 10 /* IM_JD1_SC_EINT */ +#define WM8993_IM_JD1_SC_EINT_WIDTH 1 /* IM_JD1_SC_EINT */ +#define WM8993_IM_JD1_EINT 0x0200 /* IM_JD1_EINT */ +#define WM8993_IM_JD1_EINT_MASK 0x0200 /* IM_JD1_EINT */ +#define WM8993_IM_JD1_EINT_SHIFT 9 /* IM_JD1_EINT */ +#define WM8993_IM_JD1_EINT_WIDTH 1 /* IM_JD1_EINT */ +#define WM8993_IM_FLL_LOCK_EINT 0x0100 /* IM_FLL_LOCK_EINT */ +#define WM8993_IM_FLL_LOCK_EINT_MASK 0x0100 /* IM_FLL_LOCK_EINT */ +#define WM8993_IM_FLL_LOCK_EINT_SHIFT 8 /* IM_FLL_LOCK_EINT */ +#define WM8993_IM_FLL_LOCK_EINT_WIDTH 1 /* IM_FLL_LOCK_EINT */ +#define WM8993_IM_GPI8_EINT 0x0040 /* IM_GPI8_EINT */ +#define WM8993_IM_GPI8_EINT_MASK 0x0040 /* IM_GPI8_EINT */ +#define WM8993_IM_GPI8_EINT_SHIFT 6 /* IM_GPI8_EINT */ +#define WM8993_IM_GPI8_EINT_WIDTH 1 /* IM_GPI8_EINT */ +#define WM8993_IM_GPIO1_EINT 0x0020 /* IM_GPIO1_EINT */ +#define WM8993_IM_GPIO1_EINT_MASK 0x0020 /* IM_GPIO1_EINT */ +#define WM8993_IM_GPIO1_EINT_SHIFT 5 /* IM_GPIO1_EINT */ +#define WM8993_IM_GPIO1_EINT_WIDTH 1 /* IM_GPIO1_EINT */ +#define WM8993_GPI8_ENA 0x0010 /* GPI8_ENA */ +#define WM8993_GPI8_ENA_MASK 0x0010 /* GPI8_ENA */ +#define WM8993_GPI8_ENA_SHIFT 4 /* GPI8_ENA */ +#define WM8993_GPI8_ENA_WIDTH 1 /* GPI8_ENA */ +#define WM8993_IM_GPI7_EINT 0x0004 /* IM_GPI7_EINT */ +#define WM8993_IM_GPI7_EINT_MASK 0x0004 /* IM_GPI7_EINT */ +#define WM8993_IM_GPI7_EINT_SHIFT 2 /* IM_GPI7_EINT */ +#define WM8993_IM_GPI7_EINT_WIDTH 1 /* IM_GPI7_EINT */ +#define WM8993_IM_WSEQ_EINT 0x0002 /* IM_WSEQ_EINT */ +#define WM8993_IM_WSEQ_EINT_MASK 0x0002 /* IM_WSEQ_EINT */ +#define WM8993_IM_WSEQ_EINT_SHIFT 1 /* IM_WSEQ_EINT */ +#define WM8993_IM_WSEQ_EINT_WIDTH 1 /* IM_WSEQ_EINT */ +#define WM8993_GPI7_ENA 0x0001 /* GPI7_ENA */ +#define WM8993_GPI7_ENA_MASK 0x0001 /* GPI7_ENA */ +#define WM8993_GPI7_ENA_SHIFT 0 /* GPI7_ENA */ +#define WM8993_GPI7_ENA_WIDTH 1 /* GPI7_ENA */ + +/* + * R23 (0x17) - GPIO_POL + */ +#define WM8993_JD2_SC_POL 0x8000 /* JD2_SC_POL */ +#define WM8993_JD2_SC_POL_MASK 0x8000 /* JD2_SC_POL */ +#define WM8993_JD2_SC_POL_SHIFT 15 /* JD2_SC_POL */ +#define WM8993_JD2_SC_POL_WIDTH 1 /* JD2_SC_POL */ +#define WM8993_JD2_POL 0x4000 /* JD2_POL */ +#define WM8993_JD2_POL_MASK 0x4000 /* JD2_POL */ +#define WM8993_JD2_POL_SHIFT 14 /* JD2_POL */ +#define WM8993_JD2_POL_WIDTH 1 /* JD2_POL */ +#define WM8993_WSEQ_POL 0x2000 /* WSEQ_POL */ +#define WM8993_WSEQ_POL_MASK 0x2000 /* WSEQ_POL */ +#define WM8993_WSEQ_POL_SHIFT 13 /* WSEQ_POL */ +#define WM8993_WSEQ_POL_WIDTH 1 /* WSEQ_POL */ +#define WM8993_IRQ_POL 0x1000 /* IRQ_POL */ +#define WM8993_IRQ_POL_MASK 0x1000 /* IRQ_POL */ +#define WM8993_IRQ_POL_SHIFT 12 /* IRQ_POL */ +#define WM8993_IRQ_POL_WIDTH 1 /* IRQ_POL */ +#define WM8993_TEMPOK_POL 0x0800 /* TEMPOK_POL */ +#define WM8993_TEMPOK_POL_MASK 0x0800 /* TEMPOK_POL */ +#define WM8993_TEMPOK_POL_SHIFT 11 /* TEMPOK_POL */ +#define WM8993_TEMPOK_POL_WIDTH 1 /* TEMPOK_POL */ +#define WM8993_JD1_SC_POL 0x0400 /* JD1_SC_POL */ +#define WM8993_JD1_SC_POL_MASK 0x0400 /* JD1_SC_POL */ +#define WM8993_JD1_SC_POL_SHIFT 10 /* JD1_SC_POL */ +#define WM8993_JD1_SC_POL_WIDTH 1 /* JD1_SC_POL */ +#define WM8993_JD1_POL 0x0200 /* JD1_POL */ +#define WM8993_JD1_POL_MASK 0x0200 /* JD1_POL */ +#define WM8993_JD1_POL_SHIFT 9 /* JD1_POL */ +#define WM8993_JD1_POL_WIDTH 1 /* JD1_POL */ +#define WM8993_FLL_LOCK_POL 0x0100 /* FLL_LOCK_POL */ +#define WM8993_FLL_LOCK_POL_MASK 0x0100 /* FLL_LOCK_POL */ +#define WM8993_FLL_LOCK_POL_SHIFT 8 /* FLL_LOCK_POL */ +#define WM8993_FLL_LOCK_POL_WIDTH 1 /* FLL_LOCK_POL */ +#define WM8993_GPI8_POL 0x0080 /* GPI8_POL */ +#define WM8993_GPI8_POL_MASK 0x0080 /* GPI8_POL */ +#define WM8993_GPI8_POL_SHIFT 7 /* GPI8_POL */ +#define WM8993_GPI8_POL_WIDTH 1 /* GPI8_POL */ +#define WM8993_GPI7_POL 0x0040 /* GPI7_POL */ +#define WM8993_GPI7_POL_MASK 0x0040 /* GPI7_POL */ +#define WM8993_GPI7_POL_SHIFT 6 /* GPI7_POL */ +#define WM8993_GPI7_POL_WIDTH 1 /* GPI7_POL */ +#define WM8993_GPIO1_POL 0x0001 /* GPIO1_POL */ +#define WM8993_GPIO1_POL_MASK 0x0001 /* GPIO1_POL */ +#define WM8993_GPIO1_POL_SHIFT 0 /* GPIO1_POL */ +#define WM8993_GPIO1_POL_WIDTH 1 /* GPIO1_POL */ + +/* + * R24 (0x18) - Left Line Input 1&2 Volume + */ +#define WM8993_IN1_VU 0x0100 /* IN1_VU */ +#define WM8993_IN1_VU_MASK 0x0100 /* IN1_VU */ +#define WM8993_IN1_VU_SHIFT 8 /* IN1_VU */ +#define WM8993_IN1_VU_WIDTH 1 /* IN1_VU */ +#define WM8993_IN1L_MUTE 0x0080 /* IN1L_MUTE */ +#define WM8993_IN1L_MUTE_MASK 0x0080 /* IN1L_MUTE */ +#define WM8993_IN1L_MUTE_SHIFT 7 /* IN1L_MUTE */ +#define WM8993_IN1L_MUTE_WIDTH 1 /* IN1L_MUTE */ +#define WM8993_IN1L_ZC 0x0040 /* IN1L_ZC */ +#define WM8993_IN1L_ZC_MASK 0x0040 /* IN1L_ZC */ +#define WM8993_IN1L_ZC_SHIFT 6 /* IN1L_ZC */ +#define WM8993_IN1L_ZC_WIDTH 1 /* IN1L_ZC */ +#define WM8993_IN1L_VOL_MASK 0x001F /* IN1L_VOL - [4:0] */ +#define WM8993_IN1L_VOL_SHIFT 0 /* IN1L_VOL - [4:0] */ +#define WM8993_IN1L_VOL_WIDTH 5 /* IN1L_VOL - [4:0] */ + +/* + * R25 (0x19) - Left Line Input 3&4 Volume + */ +#define WM8993_IN2_VU 0x0100 /* IN2_VU */ +#define WM8993_IN2_VU_MASK 0x0100 /* IN2_VU */ +#define WM8993_IN2_VU_SHIFT 8 /* IN2_VU */ +#define WM8993_IN2_VU_WIDTH 1 /* IN2_VU */ +#define WM8993_IN2L_MUTE 0x0080 /* IN2L_MUTE */ +#define WM8993_IN2L_MUTE_MASK 0x0080 /* IN2L_MUTE */ +#define WM8993_IN2L_MUTE_SHIFT 7 /* IN2L_MUTE */ +#define WM8993_IN2L_MUTE_WIDTH 1 /* IN2L_MUTE */ +#define WM8993_IN2L_ZC 0x0040 /* IN2L_ZC */ +#define WM8993_IN2L_ZC_MASK 0x0040 /* IN2L_ZC */ +#define WM8993_IN2L_ZC_SHIFT 6 /* IN2L_ZC */ +#define WM8993_IN2L_ZC_WIDTH 1 /* IN2L_ZC */ +#define WM8993_IN2L_VOL_MASK 0x001F /* IN2L_VOL - [4:0] */ +#define WM8993_IN2L_VOL_SHIFT 0 /* IN2L_VOL - [4:0] */ +#define WM8993_IN2L_VOL_WIDTH 5 /* IN2L_VOL - [4:0] */ + +/* + * R26 (0x1A) - Right Line Input 1&2 Volume + */ +#define WM8993_IN1_VU 0x0100 /* IN1_VU */ +#define WM8993_IN1_VU_MASK 0x0100 /* IN1_VU */ +#define WM8993_IN1_VU_SHIFT 8 /* IN1_VU */ +#define WM8993_IN1_VU_WIDTH 1 /* IN1_VU */ +#define WM8993_IN1R_MUTE 0x0080 /* IN1R_MUTE */ +#define WM8993_IN1R_MUTE_MASK 0x0080 /* IN1R_MUTE */ +#define WM8993_IN1R_MUTE_SHIFT 7 /* IN1R_MUTE */ +#define WM8993_IN1R_MUTE_WIDTH 1 /* IN1R_MUTE */ +#define WM8993_IN1R_ZC 0x0040 /* IN1R_ZC */ +#define WM8993_IN1R_ZC_MASK 0x0040 /* IN1R_ZC */ +#define WM8993_IN1R_ZC_SHIFT 6 /* IN1R_ZC */ +#define WM8993_IN1R_ZC_WIDTH 1 /* IN1R_ZC */ +#define WM8993_IN1R_VOL_MASK 0x001F /* IN1R_VOL - [4:0] */ +#define WM8993_IN1R_VOL_SHIFT 0 /* IN1R_VOL - [4:0] */ +#define WM8993_IN1R_VOL_WIDTH 5 /* IN1R_VOL - [4:0] */ + +/* + * R27 (0x1B) - Right Line Input 3&4 Volume + */ +#define WM8993_IN2_VU 0x0100 /* IN2_VU */ +#define WM8993_IN2_VU_MASK 0x0100 /* IN2_VU */ +#define WM8993_IN2_VU_SHIFT 8 /* IN2_VU */ +#define WM8993_IN2_VU_WIDTH 1 /* IN2_VU */ +#define WM8993_IN2R_MUTE 0x0080 /* IN2R_MUTE */ +#define WM8993_IN2R_MUTE_MASK 0x0080 /* IN2R_MUTE */ +#define WM8993_IN2R_MUTE_SHIFT 7 /* IN2R_MUTE */ +#define WM8993_IN2R_MUTE_WIDTH 1 /* IN2R_MUTE */ +#define WM8993_IN2R_ZC 0x0040 /* IN2R_ZC */ +#define WM8993_IN2R_ZC_MASK 0x0040 /* IN2R_ZC */ +#define WM8993_IN2R_ZC_SHIFT 6 /* IN2R_ZC */ +#define WM8993_IN2R_ZC_WIDTH 1 /* IN2R_ZC */ +#define WM8993_IN2R_VOL_MASK 0x001F /* IN2R_VOL - [4:0] */ +#define WM8993_IN2R_VOL_SHIFT 0 /* IN2R_VOL - [4:0] */ +#define WM8993_IN2R_VOL_WIDTH 5 /* IN2R_VOL - [4:0] */ + +/* + * R28 (0x1C) - Left Output Volume + */ +#define WM8993_HPOUT1_VU 0x0100 /* HPOUT1_VU */ +#define WM8993_HPOUT1_VU_MASK 0x0100 /* HPOUT1_VU */ +#define WM8993_HPOUT1_VU_SHIFT 8 /* HPOUT1_VU */ +#define WM8993_HPOUT1_VU_WIDTH 1 /* HPOUT1_VU */ +#define WM8993_HPOUT1L_ZC 0x0080 /* HPOUT1L_ZC */ +#define WM8993_HPOUT1L_ZC_MASK 0x0080 /* HPOUT1L_ZC */ +#define WM8993_HPOUT1L_ZC_SHIFT 7 /* HPOUT1L_ZC */ +#define WM8993_HPOUT1L_ZC_WIDTH 1 /* HPOUT1L_ZC */ +#define WM8993_HPOUT1L_MUTE_N 0x0040 /* HPOUT1L_MUTE_N */ +#define WM8993_HPOUT1L_MUTE_N_MASK 0x0040 /* HPOUT1L_MUTE_N */ +#define WM8993_HPOUT1L_MUTE_N_SHIFT 6 /* HPOUT1L_MUTE_N */ +#define WM8993_HPOUT1L_MUTE_N_WIDTH 1 /* HPOUT1L_MUTE_N */ +#define WM8993_HPOUT1L_VOL_MASK 0x003F /* HPOUT1L_VOL - [5:0] */ +#define WM8993_HPOUT1L_VOL_SHIFT 0 /* HPOUT1L_VOL - [5:0] */ +#define WM8993_HPOUT1L_VOL_WIDTH 6 /* HPOUT1L_VOL - [5:0] */ + +/* + * R29 (0x1D) - Right Output Volume + */ +#define WM8993_HPOUT1_VU 0x0100 /* HPOUT1_VU */ +#define WM8993_HPOUT1_VU_MASK 0x0100 /* HPOUT1_VU */ +#define WM8993_HPOUT1_VU_SHIFT 8 /* HPOUT1_VU */ +#define WM8993_HPOUT1_VU_WIDTH 1 /* HPOUT1_VU */ +#define WM8993_HPOUT1R_ZC 0x0080 /* HPOUT1R_ZC */ +#define WM8993_HPOUT1R_ZC_MASK 0x0080 /* HPOUT1R_ZC */ +#define WM8993_HPOUT1R_ZC_SHIFT 7 /* HPOUT1R_ZC */ +#define WM8993_HPOUT1R_ZC_WIDTH 1 /* HPOUT1R_ZC */ +#define WM8993_HPOUT1R_MUTE_N 0x0040 /* HPOUT1R_MUTE_N */ +#define WM8993_HPOUT1R_MUTE_N_MASK 0x0040 /* HPOUT1R_MUTE_N */ +#define WM8993_HPOUT1R_MUTE_N_SHIFT 6 /* HPOUT1R_MUTE_N */ +#define WM8993_HPOUT1R_MUTE_N_WIDTH 1 /* HPOUT1R_MUTE_N */ +#define WM8993_HPOUT1R_VOL_MASK 0x003F /* HPOUT1R_VOL - [5:0] */ +#define WM8993_HPOUT1R_VOL_SHIFT 0 /* HPOUT1R_VOL - [5:0] */ +#define WM8993_HPOUT1R_VOL_WIDTH 6 /* HPOUT1R_VOL - [5:0] */ + +/* + * R30 (0x1E) - Line Outputs Volume + */ +#define WM8993_LINEOUT1N_MUTE 0x0040 /* LINEOUT1N_MUTE */ +#define WM8993_LINEOUT1N_MUTE_MASK 0x0040 /* LINEOUT1N_MUTE */ +#define WM8993_LINEOUT1N_MUTE_SHIFT 6 /* LINEOUT1N_MUTE */ +#define WM8993_LINEOUT1N_MUTE_WIDTH 1 /* LINEOUT1N_MUTE */ +#define WM8993_LINEOUT1P_MUTE 0x0020 /* LINEOUT1P_MUTE */ +#define WM8993_LINEOUT1P_MUTE_MASK 0x0020 /* LINEOUT1P_MUTE */ +#define WM8993_LINEOUT1P_MUTE_SHIFT 5 /* LINEOUT1P_MUTE */ +#define WM8993_LINEOUT1P_MUTE_WIDTH 1 /* LINEOUT1P_MUTE */ +#define WM8993_LINEOUT1_VOL 0x0010 /* LINEOUT1_VOL */ +#define WM8993_LINEOUT1_VOL_MASK 0x0010 /* LINEOUT1_VOL */ +#define WM8993_LINEOUT1_VOL_SHIFT 4 /* LINEOUT1_VOL */ +#define WM8993_LINEOUT1_VOL_WIDTH 1 /* LINEOUT1_VOL */ +#define WM8993_LINEOUT2N_MUTE 0x0004 /* LINEOUT2N_MUTE */ +#define WM8993_LINEOUT2N_MUTE_MASK 0x0004 /* LINEOUT2N_MUTE */ +#define WM8993_LINEOUT2N_MUTE_SHIFT 2 /* LINEOUT2N_MUTE */ +#define WM8993_LINEOUT2N_MUTE_WIDTH 1 /* LINEOUT2N_MUTE */ +#define WM8993_LINEOUT2P_MUTE 0x0002 /* LINEOUT2P_MUTE */ +#define WM8993_LINEOUT2P_MUTE_MASK 0x0002 /* LINEOUT2P_MUTE */ +#define WM8993_LINEOUT2P_MUTE_SHIFT 1 /* LINEOUT2P_MUTE */ +#define WM8993_LINEOUT2P_MUTE_WIDTH 1 /* LINEOUT2P_MUTE */ +#define WM8993_LINEOUT2_VOL 0x0001 /* LINEOUT2_VOL */ +#define WM8993_LINEOUT2_VOL_MASK 0x0001 /* LINEOUT2_VOL */ +#define WM8993_LINEOUT2_VOL_SHIFT 0 /* LINEOUT2_VOL */ +#define WM8993_LINEOUT2_VOL_WIDTH 1 /* LINEOUT2_VOL */ + +/* + * R31 (0x1F) - HPOUT2 Volume + */ +#define WM8993_HPOUT2_MUTE 0x0020 /* HPOUT2_MUTE */ +#define WM8993_HPOUT2_MUTE_MASK 0x0020 /* HPOUT2_MUTE */ +#define WM8993_HPOUT2_MUTE_SHIFT 5 /* HPOUT2_MUTE */ +#define WM8993_HPOUT2_MUTE_WIDTH 1 /* HPOUT2_MUTE */ +#define WM8993_HPOUT2_VOL 0x0010 /* HPOUT2_VOL */ +#define WM8993_HPOUT2_VOL_MASK 0x0010 /* HPOUT2_VOL */ +#define WM8993_HPOUT2_VOL_SHIFT 4 /* HPOUT2_VOL */ +#define WM8993_HPOUT2_VOL_WIDTH 1 /* HPOUT2_VOL */ + +/* + * R32 (0x20) - Left OPGA Volume + */ +#define WM8993_MIXOUT_VU 0x0100 /* MIXOUT_VU */ +#define WM8993_MIXOUT_VU_MASK 0x0100 /* MIXOUT_VU */ +#define WM8993_MIXOUT_VU_SHIFT 8 /* MIXOUT_VU */ +#define WM8993_MIXOUT_VU_WIDTH 1 /* MIXOUT_VU */ +#define WM8993_MIXOUTL_ZC 0x0080 /* MIXOUTL_ZC */ +#define WM8993_MIXOUTL_ZC_MASK 0x0080 /* MIXOUTL_ZC */ +#define WM8993_MIXOUTL_ZC_SHIFT 7 /* MIXOUTL_ZC */ +#define WM8993_MIXOUTL_ZC_WIDTH 1 /* MIXOUTL_ZC */ +#define WM8993_MIXOUTL_MUTE_N 0x0040 /* MIXOUTL_MUTE_N */ +#define WM8993_MIXOUTL_MUTE_N_MASK 0x0040 /* MIXOUTL_MUTE_N */ +#define WM8993_MIXOUTL_MUTE_N_SHIFT 6 /* MIXOUTL_MUTE_N */ +#define WM8993_MIXOUTL_MUTE_N_WIDTH 1 /* MIXOUTL_MUTE_N */ +#define WM8993_MIXOUTL_VOL_MASK 0x003F /* MIXOUTL_VOL - [5:0] */ +#define WM8993_MIXOUTL_VOL_SHIFT 0 /* MIXOUTL_VOL - [5:0] */ +#define WM8993_MIXOUTL_VOL_WIDTH 6 /* MIXOUTL_VOL - [5:0] */ + +/* + * R33 (0x21) - Right OPGA Volume + */ +#define WM8993_MIXOUT_VU 0x0100 /* MIXOUT_VU */ +#define WM8993_MIXOUT_VU_MASK 0x0100 /* MIXOUT_VU */ +#define WM8993_MIXOUT_VU_SHIFT 8 /* MIXOUT_VU */ +#define WM8993_MIXOUT_VU_WIDTH 1 /* MIXOUT_VU */ +#define WM8993_MIXOUTR_ZC 0x0080 /* MIXOUTR_ZC */ +#define WM8993_MIXOUTR_ZC_MASK 0x0080 /* MIXOUTR_ZC */ +#define WM8993_MIXOUTR_ZC_SHIFT 7 /* MIXOUTR_ZC */ +#define WM8993_MIXOUTR_ZC_WIDTH 1 /* MIXOUTR_ZC */ +#define WM8993_MIXOUTR_MUTE_N 0x0040 /* MIXOUTR_MUTE_N */ +#define WM8993_MIXOUTR_MUTE_N_MASK 0x0040 /* MIXOUTR_MUTE_N */ +#define WM8993_MIXOUTR_MUTE_N_SHIFT 6 /* MIXOUTR_MUTE_N */ +#define WM8993_MIXOUTR_MUTE_N_WIDTH 1 /* MIXOUTR_MUTE_N */ +#define WM8993_MIXOUTR_VOL_MASK 0x003F /* MIXOUTR_VOL - [5:0] */ +#define WM8993_MIXOUTR_VOL_SHIFT 0 /* MIXOUTR_VOL - [5:0] */ +#define WM8993_MIXOUTR_VOL_WIDTH 6 /* MIXOUTR_VOL - [5:0] */ + +/* + * R34 (0x22) - SPKMIXL Attenuation + */ +#define WM8993_MIXINL_SPKMIXL_VOL 0x0020 /* MIXINL_SPKMIXL_VOL */ +#define WM8993_MIXINL_SPKMIXL_VOL_MASK 0x0020 /* MIXINL_SPKMIXL_VOL */ +#define WM8993_MIXINL_SPKMIXL_VOL_SHIFT 5 /* MIXINL_SPKMIXL_VOL */ +#define WM8993_MIXINL_SPKMIXL_VOL_WIDTH 1 /* MIXINL_SPKMIXL_VOL */ +#define WM8993_IN1LP_SPKMIXL_VOL 0x0010 /* IN1LP_SPKMIXL_VOL */ +#define WM8993_IN1LP_SPKMIXL_VOL_MASK 0x0010 /* IN1LP_SPKMIXL_VOL */ +#define WM8993_IN1LP_SPKMIXL_VOL_SHIFT 4 /* IN1LP_SPKMIXL_VOL */ +#define WM8993_IN1LP_SPKMIXL_VOL_WIDTH 1 /* IN1LP_SPKMIXL_VOL */ +#define WM8993_MIXOUTL_SPKMIXL_VOL 0x0008 /* MIXOUTL_SPKMIXL_VOL */ +#define WM8993_MIXOUTL_SPKMIXL_VOL_MASK 0x0008 /* MIXOUTL_SPKMIXL_VOL */ +#define WM8993_MIXOUTL_SPKMIXL_VOL_SHIFT 3 /* MIXOUTL_SPKMIXL_VOL */ +#define WM8993_MIXOUTL_SPKMIXL_VOL_WIDTH 1 /* MIXOUTL_SPKMIXL_VOL */ +#define WM8993_DACL_SPKMIXL_VOL 0x0004 /* DACL_SPKMIXL_VOL */ +#define WM8993_DACL_SPKMIXL_VOL_MASK 0x0004 /* DACL_SPKMIXL_VOL */ +#define WM8993_DACL_SPKMIXL_VOL_SHIFT 2 /* DACL_SPKMIXL_VOL */ +#define WM8993_DACL_SPKMIXL_VOL_WIDTH 1 /* DACL_SPKMIXL_VOL */ +#define WM8993_SPKMIXL_VOL_MASK 0x0003 /* SPKMIXL_VOL - [1:0] */ +#define WM8993_SPKMIXL_VOL_SHIFT 0 /* SPKMIXL_VOL - [1:0] */ +#define WM8993_SPKMIXL_VOL_WIDTH 2 /* SPKMIXL_VOL - [1:0] */ + +/* + * R35 (0x23) - SPKMIXR Attenuation + */ +#define WM8993_SPKOUT_CLASSAB_MODE 0x0100 /* SPKOUT_CLASSAB_MODE */ +#define WM8993_SPKOUT_CLASSAB_MODE_MASK 0x0100 /* SPKOUT_CLASSAB_MODE */ +#define WM8993_SPKOUT_CLASSAB_MODE_SHIFT 8 /* SPKOUT_CLASSAB_MODE */ +#define WM8993_SPKOUT_CLASSAB_MODE_WIDTH 1 /* SPKOUT_CLASSAB_MODE */ +#define WM8993_MIXINR_SPKMIXR_VOL 0x0020 /* MIXINR_SPKMIXR_VOL */ +#define WM8993_MIXINR_SPKMIXR_VOL_MASK 0x0020 /* MIXINR_SPKMIXR_VOL */ +#define WM8993_MIXINR_SPKMIXR_VOL_SHIFT 5 /* MIXINR_SPKMIXR_VOL */ +#define WM8993_MIXINR_SPKMIXR_VOL_WIDTH 1 /* MIXINR_SPKMIXR_VOL */ +#define WM8993_IN1RP_SPKMIXR_VOL 0x0010 /* IN1RP_SPKMIXR_VOL */ +#define WM8993_IN1RP_SPKMIXR_VOL_MASK 0x0010 /* IN1RP_SPKMIXR_VOL */ +#define WM8993_IN1RP_SPKMIXR_VOL_SHIFT 4 /* IN1RP_SPKMIXR_VOL */ +#define WM8993_IN1RP_SPKMIXR_VOL_WIDTH 1 /* IN1RP_SPKMIXR_VOL */ +#define WM8993_MIXOUTR_SPKMIXR_VOL 0x0008 /* MIXOUTR_SPKMIXR_VOL */ +#define WM8993_MIXOUTR_SPKMIXR_VOL_MASK 0x0008 /* MIXOUTR_SPKMIXR_VOL */ +#define WM8993_MIXOUTR_SPKMIXR_VOL_SHIFT 3 /* MIXOUTR_SPKMIXR_VOL */ +#define WM8993_MIXOUTR_SPKMIXR_VOL_WIDTH 1 /* MIXOUTR_SPKMIXR_VOL */ +#define WM8993_DACR_SPKMIXR_VOL 0x0004 /* DACR_SPKMIXR_VOL */ +#define WM8993_DACR_SPKMIXR_VOL_MASK 0x0004 /* DACR_SPKMIXR_VOL */ +#define WM8993_DACR_SPKMIXR_VOL_SHIFT 2 /* DACR_SPKMIXR_VOL */ +#define WM8993_DACR_SPKMIXR_VOL_WIDTH 1 /* DACR_SPKMIXR_VOL */ +#define WM8993_SPKMIXR_VOL_MASK 0x0003 /* SPKMIXR_VOL - [1:0] */ +#define WM8993_SPKMIXR_VOL_SHIFT 0 /* SPKMIXR_VOL - [1:0] */ +#define WM8993_SPKMIXR_VOL_WIDTH 2 /* SPKMIXR_VOL - [1:0] */ + +/* + * R36 (0x24) - SPKOUT Mixers + */ +#define WM8993_VRX_TO_SPKOUTL 0x0020 /* VRX_TO_SPKOUTL */ +#define WM8993_VRX_TO_SPKOUTL_MASK 0x0020 /* VRX_TO_SPKOUTL */ +#define WM8993_VRX_TO_SPKOUTL_SHIFT 5 /* VRX_TO_SPKOUTL */ +#define WM8993_VRX_TO_SPKOUTL_WIDTH 1 /* VRX_TO_SPKOUTL */ +#define WM8993_SPKMIXL_TO_SPKOUTL 0x0010 /* SPKMIXL_TO_SPKOUTL */ +#define WM8993_SPKMIXL_TO_SPKOUTL_MASK 0x0010 /* SPKMIXL_TO_SPKOUTL */ +#define WM8993_SPKMIXL_TO_SPKOUTL_SHIFT 4 /* SPKMIXL_TO_SPKOUTL */ +#define WM8993_SPKMIXL_TO_SPKOUTL_WIDTH 1 /* SPKMIXL_TO_SPKOUTL */ +#define WM8993_SPKMIXR_TO_SPKOUTL 0x0008 /* SPKMIXR_TO_SPKOUTL */ +#define WM8993_SPKMIXR_TO_SPKOUTL_MASK 0x0008 /* SPKMIXR_TO_SPKOUTL */ +#define WM8993_SPKMIXR_TO_SPKOUTL_SHIFT 3 /* SPKMIXR_TO_SPKOUTL */ +#define WM8993_SPKMIXR_TO_SPKOUTL_WIDTH 1 /* SPKMIXR_TO_SPKOUTL */ +#define WM8993_VRX_TO_SPKOUTR 0x0004 /* VRX_TO_SPKOUTR */ +#define WM8993_VRX_TO_SPKOUTR_MASK 0x0004 /* VRX_TO_SPKOUTR */ +#define WM8993_VRX_TO_SPKOUTR_SHIFT 2 /* VRX_TO_SPKOUTR */ +#define WM8993_VRX_TO_SPKOUTR_WIDTH 1 /* VRX_TO_SPKOUTR */ +#define WM8993_SPKMIXL_TO_SPKOUTR 0x0002 /* SPKMIXL_TO_SPKOUTR */ +#define WM8993_SPKMIXL_TO_SPKOUTR_MASK 0x0002 /* SPKMIXL_TO_SPKOUTR */ +#define WM8993_SPKMIXL_TO_SPKOUTR_SHIFT 1 /* SPKMIXL_TO_SPKOUTR */ +#define WM8993_SPKMIXL_TO_SPKOUTR_WIDTH 1 /* SPKMIXL_TO_SPKOUTR */ +#define WM8993_SPKMIXR_TO_SPKOUTR 0x0001 /* SPKMIXR_TO_SPKOUTR */ +#define WM8993_SPKMIXR_TO_SPKOUTR_MASK 0x0001 /* SPKMIXR_TO_SPKOUTR */ +#define WM8993_SPKMIXR_TO_SPKOUTR_SHIFT 0 /* SPKMIXR_TO_SPKOUTR */ +#define WM8993_SPKMIXR_TO_SPKOUTR_WIDTH 1 /* SPKMIXR_TO_SPKOUTR */ + +/* + * R37 (0x25) - SPKOUT Boost + */ +#define WM8993_SPKOUTL_BOOST_MASK 0x0038 /* SPKOUTL_BOOST - [5:3] */ +#define WM8993_SPKOUTL_BOOST_SHIFT 3 /* SPKOUTL_BOOST - [5:3] */ +#define WM8993_SPKOUTL_BOOST_WIDTH 3 /* SPKOUTL_BOOST - [5:3] */ +#define WM8993_SPKOUTR_BOOST_MASK 0x0007 /* SPKOUTR_BOOST - [2:0] */ +#define WM8993_SPKOUTR_BOOST_SHIFT 0 /* SPKOUTR_BOOST - [2:0] */ +#define WM8993_SPKOUTR_BOOST_WIDTH 3 /* SPKOUTR_BOOST - [2:0] */ + +/* + * R38 (0x26) - Speaker Volume Left + */ +#define WM8993_SPKOUT_VU 0x0100 /* SPKOUT_VU */ +#define WM8993_SPKOUT_VU_MASK 0x0100 /* SPKOUT_VU */ +#define WM8993_SPKOUT_VU_SHIFT 8 /* SPKOUT_VU */ +#define WM8993_SPKOUT_VU_WIDTH 1 /* SPKOUT_VU */ +#define WM8993_SPKOUTL_ZC 0x0080 /* SPKOUTL_ZC */ +#define WM8993_SPKOUTL_ZC_MASK 0x0080 /* SPKOUTL_ZC */ +#define WM8993_SPKOUTL_ZC_SHIFT 7 /* SPKOUTL_ZC */ +#define WM8993_SPKOUTL_ZC_WIDTH 1 /* SPKOUTL_ZC */ +#define WM8993_SPKOUTL_MUTE_N 0x0040 /* SPKOUTL_MUTE_N */ +#define WM8993_SPKOUTL_MUTE_N_MASK 0x0040 /* SPKOUTL_MUTE_N */ +#define WM8993_SPKOUTL_MUTE_N_SHIFT 6 /* SPKOUTL_MUTE_N */ +#define WM8993_SPKOUTL_MUTE_N_WIDTH 1 /* SPKOUTL_MUTE_N */ +#define WM8993_SPKOUTL_VOL_MASK 0x003F /* SPKOUTL_VOL - [5:0] */ +#define WM8993_SPKOUTL_VOL_SHIFT 0 /* SPKOUTL_VOL - [5:0] */ +#define WM8993_SPKOUTL_VOL_WIDTH 6 /* SPKOUTL_VOL - [5:0] */ + +/* + * R39 (0x27) - Speaker Volume Right + */ +#define WM8993_SPKOUT_VU 0x0100 /* SPKOUT_VU */ +#define WM8993_SPKOUT_VU_MASK 0x0100 /* SPKOUT_VU */ +#define WM8993_SPKOUT_VU_SHIFT 8 /* SPKOUT_VU */ +#define WM8993_SPKOUT_VU_WIDTH 1 /* SPKOUT_VU */ +#define WM8993_SPKOUTR_ZC 0x0080 /* SPKOUTR_ZC */ +#define WM8993_SPKOUTR_ZC_MASK 0x0080 /* SPKOUTR_ZC */ +#define WM8993_SPKOUTR_ZC_SHIFT 7 /* SPKOUTR_ZC */ +#define WM8993_SPKOUTR_ZC_WIDTH 1 /* SPKOUTR_ZC */ +#define WM8993_SPKOUTR_MUTE_N 0x0040 /* SPKOUTR_MUTE_N */ +#define WM8993_SPKOUTR_MUTE_N_MASK 0x0040 /* SPKOUTR_MUTE_N */ +#define WM8993_SPKOUTR_MUTE_N_SHIFT 6 /* SPKOUTR_MUTE_N */ +#define WM8993_SPKOUTR_MUTE_N_WIDTH 1 /* SPKOUTR_MUTE_N */ +#define WM8993_SPKOUTR_VOL_MASK 0x003F /* SPKOUTR_VOL - [5:0] */ +#define WM8993_SPKOUTR_VOL_SHIFT 0 /* SPKOUTR_VOL - [5:0] */ +#define WM8993_SPKOUTR_VOL_WIDTH 6 /* SPKOUTR_VOL - [5:0] */ + +/* + * R40 (0x28) - Input Mixer2 + */ +#define WM8993_IN2LP_TO_IN2L 0x0080 /* IN2LP_TO_IN2L */ +#define WM8993_IN2LP_TO_IN2L_MASK 0x0080 /* IN2LP_TO_IN2L */ +#define WM8993_IN2LP_TO_IN2L_SHIFT 7 /* IN2LP_TO_IN2L */ +#define WM8993_IN2LP_TO_IN2L_WIDTH 1 /* IN2LP_TO_IN2L */ +#define WM8993_IN2LN_TO_IN2L 0x0040 /* IN2LN_TO_IN2L */ +#define WM8993_IN2LN_TO_IN2L_MASK 0x0040 /* IN2LN_TO_IN2L */ +#define WM8993_IN2LN_TO_IN2L_SHIFT 6 /* IN2LN_TO_IN2L */ +#define WM8993_IN2LN_TO_IN2L_WIDTH 1 /* IN2LN_TO_IN2L */ +#define WM8993_IN1LP_TO_IN1L 0x0020 /* IN1LP_TO_IN1L */ +#define WM8993_IN1LP_TO_IN1L_MASK 0x0020 /* IN1LP_TO_IN1L */ +#define WM8993_IN1LP_TO_IN1L_SHIFT 5 /* IN1LP_TO_IN1L */ +#define WM8993_IN1LP_TO_IN1L_WIDTH 1 /* IN1LP_TO_IN1L */ +#define WM8993_IN1LN_TO_IN1L 0x0010 /* IN1LN_TO_IN1L */ +#define WM8993_IN1LN_TO_IN1L_MASK 0x0010 /* IN1LN_TO_IN1L */ +#define WM8993_IN1LN_TO_IN1L_SHIFT 4 /* IN1LN_TO_IN1L */ +#define WM8993_IN1LN_TO_IN1L_WIDTH 1 /* IN1LN_TO_IN1L */ +#define WM8993_IN2RP_TO_IN2R 0x0008 /* IN2RP_TO_IN2R */ +#define WM8993_IN2RP_TO_IN2R_MASK 0x0008 /* IN2RP_TO_IN2R */ +#define WM8993_IN2RP_TO_IN2R_SHIFT 3 /* IN2RP_TO_IN2R */ +#define WM8993_IN2RP_TO_IN2R_WIDTH 1 /* IN2RP_TO_IN2R */ +#define WM8993_IN2RN_TO_IN2R 0x0004 /* IN2RN_TO_IN2R */ +#define WM8993_IN2RN_TO_IN2R_MASK 0x0004 /* IN2RN_TO_IN2R */ +#define WM8993_IN2RN_TO_IN2R_SHIFT 2 /* IN2RN_TO_IN2R */ +#define WM8993_IN2RN_TO_IN2R_WIDTH 1 /* IN2RN_TO_IN2R */ +#define WM8993_IN1RP_TO_IN1R 0x0002 /* IN1RP_TO_IN1R */ +#define WM8993_IN1RP_TO_IN1R_MASK 0x0002 /* IN1RP_TO_IN1R */ +#define WM8993_IN1RP_TO_IN1R_SHIFT 1 /* IN1RP_TO_IN1R */ +#define WM8993_IN1RP_TO_IN1R_WIDTH 1 /* IN1RP_TO_IN1R */ +#define WM8993_IN1RN_TO_IN1R 0x0001 /* IN1RN_TO_IN1R */ +#define WM8993_IN1RN_TO_IN1R_MASK 0x0001 /* IN1RN_TO_IN1R */ +#define WM8993_IN1RN_TO_IN1R_SHIFT 0 /* IN1RN_TO_IN1R */ +#define WM8993_IN1RN_TO_IN1R_WIDTH 1 /* IN1RN_TO_IN1R */ + +/* + * R41 (0x29) - Input Mixer3 + */ +#define WM8993_IN2L_TO_MIXINL 0x0100 /* IN2L_TO_MIXINL */ +#define WM8993_IN2L_TO_MIXINL_MASK 0x0100 /* IN2L_TO_MIXINL */ +#define WM8993_IN2L_TO_MIXINL_SHIFT 8 /* IN2L_TO_MIXINL */ +#define WM8993_IN2L_TO_MIXINL_WIDTH 1 /* IN2L_TO_MIXINL */ +#define WM8993_IN2L_MIXINL_VOL 0x0080 /* IN2L_MIXINL_VOL */ +#define WM8993_IN2L_MIXINL_VOL_MASK 0x0080 /* IN2L_MIXINL_VOL */ +#define WM8993_IN2L_MIXINL_VOL_SHIFT 7 /* IN2L_MIXINL_VOL */ +#define WM8993_IN2L_MIXINL_VOL_WIDTH 1 /* IN2L_MIXINL_VOL */ +#define WM8993_IN1L_TO_MIXINL 0x0020 /* IN1L_TO_MIXINL */ +#define WM8993_IN1L_TO_MIXINL_MASK 0x0020 /* IN1L_TO_MIXINL */ +#define WM8993_IN1L_TO_MIXINL_SHIFT 5 /* IN1L_TO_MIXINL */ +#define WM8993_IN1L_TO_MIXINL_WIDTH 1 /* IN1L_TO_MIXINL */ +#define WM8993_IN1L_MIXINL_VOL 0x0010 /* IN1L_MIXINL_VOL */ +#define WM8993_IN1L_MIXINL_VOL_MASK 0x0010 /* IN1L_MIXINL_VOL */ +#define WM8993_IN1L_MIXINL_VOL_SHIFT 4 /* IN1L_MIXINL_VOL */ +#define WM8993_IN1L_MIXINL_VOL_WIDTH 1 /* IN1L_MIXINL_VOL */ +#define WM8993_MIXOUTL_MIXINL_VOL_MASK 0x0007 /* MIXOUTL_MIXINL_VOL - [2:0] */ +#define WM8993_MIXOUTL_MIXINL_VOL_SHIFT 0 /* MIXOUTL_MIXINL_VOL - [2:0] */ +#define WM8993_MIXOUTL_MIXINL_VOL_WIDTH 3 /* MIXOUTL_MIXINL_VOL - [2:0] */ + +/* + * R42 (0x2A) - Input Mixer4 + */ +#define WM8993_IN2R_TO_MIXINR 0x0100 /* IN2R_TO_MIXINR */ +#define WM8993_IN2R_TO_MIXINR_MASK 0x0100 /* IN2R_TO_MIXINR */ +#define WM8993_IN2R_TO_MIXINR_SHIFT 8 /* IN2R_TO_MIXINR */ +#define WM8993_IN2R_TO_MIXINR_WIDTH 1 /* IN2R_TO_MIXINR */ +#define WM8993_IN2R_MIXINR_VOL 0x0080 /* IN2R_MIXINR_VOL */ +#define WM8993_IN2R_MIXINR_VOL_MASK 0x0080 /* IN2R_MIXINR_VOL */ +#define WM8993_IN2R_MIXINR_VOL_SHIFT 7 /* IN2R_MIXINR_VOL */ +#define WM8993_IN2R_MIXINR_VOL_WIDTH 1 /* IN2R_MIXINR_VOL */ +#define WM8993_IN1R_TO_MIXINR 0x0020 /* IN1R_TO_MIXINR */ +#define WM8993_IN1R_TO_MIXINR_MASK 0x0020 /* IN1R_TO_MIXINR */ +#define WM8993_IN1R_TO_MIXINR_SHIFT 5 /* IN1R_TO_MIXINR */ +#define WM8993_IN1R_TO_MIXINR_WIDTH 1 /* IN1R_TO_MIXINR */ +#define WM8993_IN1R_MIXINR_VOL 0x0010 /* IN1R_MIXINR_VOL */ +#define WM8993_IN1R_MIXINR_VOL_MASK 0x0010 /* IN1R_MIXINR_VOL */ +#define WM8993_IN1R_MIXINR_VOL_SHIFT 4 /* IN1R_MIXINR_VOL */ +#define WM8993_IN1R_MIXINR_VOL_WIDTH 1 /* IN1R_MIXINR_VOL */ +#define WM8993_MIXOUTR_MIXINR_VOL_MASK 0x0007 /* MIXOUTR_MIXINR_VOL - [2:0] */ +#define WM8993_MIXOUTR_MIXINR_VOL_SHIFT 0 /* MIXOUTR_MIXINR_VOL - [2:0] */ +#define WM8993_MIXOUTR_MIXINR_VOL_WIDTH 3 /* MIXOUTR_MIXINR_VOL - [2:0] */ + +/* + * R43 (0x2B) - Input Mixer5 + */ +#define WM8993_IN1LP_MIXINL_VOL_MASK 0x01C0 /* IN1LP_MIXINL_VOL - [8:6] */ +#define WM8993_IN1LP_MIXINL_VOL_SHIFT 6 /* IN1LP_MIXINL_VOL - [8:6] */ +#define WM8993_IN1LP_MIXINL_VOL_WIDTH 3 /* IN1LP_MIXINL_VOL - [8:6] */ +#define WM8993_VRX_MIXINL_VOL_MASK 0x0007 /* VRX_MIXINL_VOL - [2:0] */ +#define WM8993_VRX_MIXINL_VOL_SHIFT 0 /* VRX_MIXINL_VOL - [2:0] */ +#define WM8993_VRX_MIXINL_VOL_WIDTH 3 /* VRX_MIXINL_VOL - [2:0] */ + +/* + * R44 (0x2C) - Input Mixer6 + */ +#define WM8993_IN1RP_MIXINR_VOL_MASK 0x01C0 /* IN1RP_MIXINR_VOL - [8:6] */ +#define WM8993_IN1RP_MIXINR_VOL_SHIFT 6 /* IN1RP_MIXINR_VOL - [8:6] */ +#define WM8993_IN1RP_MIXINR_VOL_WIDTH 3 /* IN1RP_MIXINR_VOL - [8:6] */ +#define WM8993_VRX_MIXINR_VOL_MASK 0x0007 /* VRX_MIXINR_VOL - [2:0] */ +#define WM8993_VRX_MIXINR_VOL_SHIFT 0 /* VRX_MIXINR_VOL - [2:0] */ +#define WM8993_VRX_MIXINR_VOL_WIDTH 3 /* VRX_MIXINR_VOL - [2:0] */ + +/* + * R45 (0x2D) - Output Mixer1 + */ +#define WM8993_DACL_TO_HPOUT1L 0x0100 /* DACL_TO_HPOUT1L */ +#define WM8993_DACL_TO_HPOUT1L_MASK 0x0100 /* DACL_TO_HPOUT1L */ +#define WM8993_DACL_TO_HPOUT1L_SHIFT 8 /* DACL_TO_HPOUT1L */ +#define WM8993_DACL_TO_HPOUT1L_WIDTH 1 /* DACL_TO_HPOUT1L */ +#define WM8993_MIXINR_TO_MIXOUTL 0x0080 /* MIXINR_TO_MIXOUTL */ +#define WM8993_MIXINR_TO_MIXOUTL_MASK 0x0080 /* MIXINR_TO_MIXOUTL */ +#define WM8993_MIXINR_TO_MIXOUTL_SHIFT 7 /* MIXINR_TO_MIXOUTL */ +#define WM8993_MIXINR_TO_MIXOUTL_WIDTH 1 /* MIXINR_TO_MIXOUTL */ +#define WM8993_MIXINL_TO_MIXOUTL 0x0040 /* MIXINL_TO_MIXOUTL */ +#define WM8993_MIXINL_TO_MIXOUTL_MASK 0x0040 /* MIXINL_TO_MIXOUTL */ +#define WM8993_MIXINL_TO_MIXOUTL_SHIFT 6 /* MIXINL_TO_MIXOUTL */ +#define WM8993_MIXINL_TO_MIXOUTL_WIDTH 1 /* MIXINL_TO_MIXOUTL */ +#define WM8993_IN2RN_TO_MIXOUTL 0x0020 /* IN2RN_TO_MIXOUTL */ +#define WM8993_IN2RN_TO_MIXOUTL_MASK 0x0020 /* IN2RN_TO_MIXOUTL */ +#define WM8993_IN2RN_TO_MIXOUTL_SHIFT 5 /* IN2RN_TO_MIXOUTL */ +#define WM8993_IN2RN_TO_MIXOUTL_WIDTH 1 /* IN2RN_TO_MIXOUTL */ +#define WM8993_IN2LN_TO_MIXOUTL 0x0010 /* IN2LN_TO_MIXOUTL */ +#define WM8993_IN2LN_TO_MIXOUTL_MASK 0x0010 /* IN2LN_TO_MIXOUTL */ +#define WM8993_IN2LN_TO_MIXOUTL_SHIFT 4 /* IN2LN_TO_MIXOUTL */ +#define WM8993_IN2LN_TO_MIXOUTL_WIDTH 1 /* IN2LN_TO_MIXOUTL */ +#define WM8993_IN1R_TO_MIXOUTL 0x0008 /* IN1R_TO_MIXOUTL */ +#define WM8993_IN1R_TO_MIXOUTL_MASK 0x0008 /* IN1R_TO_MIXOUTL */ +#define WM8993_IN1R_TO_MIXOUTL_SHIFT 3 /* IN1R_TO_MIXOUTL */ +#define WM8993_IN1R_TO_MIXOUTL_WIDTH 1 /* IN1R_TO_MIXOUTL */ +#define WM8993_IN1L_TO_MIXOUTL 0x0004 /* IN1L_TO_MIXOUTL */ +#define WM8993_IN1L_TO_MIXOUTL_MASK 0x0004 /* IN1L_TO_MIXOUTL */ +#define WM8993_IN1L_TO_MIXOUTL_SHIFT 2 /* IN1L_TO_MIXOUTL */ +#define WM8993_IN1L_TO_MIXOUTL_WIDTH 1 /* IN1L_TO_MIXOUTL */ +#define WM8993_IN2LP_TO_MIXOUTL 0x0002 /* IN2LP_TO_MIXOUTL */ +#define WM8993_IN2LP_TO_MIXOUTL_MASK 0x0002 /* IN2LP_TO_MIXOUTL */ +#define WM8993_IN2LP_TO_MIXOUTL_SHIFT 1 /* IN2LP_TO_MIXOUTL */ +#define WM8993_IN2LP_TO_MIXOUTL_WIDTH 1 /* IN2LP_TO_MIXOUTL */ +#define WM8993_DACL_TO_MIXOUTL 0x0001 /* DACL_TO_MIXOUTL */ +#define WM8993_DACL_TO_MIXOUTL_MASK 0x0001 /* DACL_TO_MIXOUTL */ +#define WM8993_DACL_TO_MIXOUTL_SHIFT 0 /* DACL_TO_MIXOUTL */ +#define WM8993_DACL_TO_MIXOUTL_WIDTH 1 /* DACL_TO_MIXOUTL */ + +/* + * R46 (0x2E) - Output Mixer2 + */ +#define WM8993_DACR_TO_HPOUT1R 0x0100 /* DACR_TO_HPOUT1R */ +#define WM8993_DACR_TO_HPOUT1R_MASK 0x0100 /* DACR_TO_HPOUT1R */ +#define WM8993_DACR_TO_HPOUT1R_SHIFT 8 /* DACR_TO_HPOUT1R */ +#define WM8993_DACR_TO_HPOUT1R_WIDTH 1 /* DACR_TO_HPOUT1R */ +#define WM8993_MIXINL_TO_MIXOUTR 0x0080 /* MIXINL_TO_MIXOUTR */ +#define WM8993_MIXINL_TO_MIXOUTR_MASK 0x0080 /* MIXINL_TO_MIXOUTR */ +#define WM8993_MIXINL_TO_MIXOUTR_SHIFT 7 /* MIXINL_TO_MIXOUTR */ +#define WM8993_MIXINL_TO_MIXOUTR_WIDTH 1 /* MIXINL_TO_MIXOUTR */ +#define WM8993_MIXINR_TO_MIXOUTR 0x0040 /* MIXINR_TO_MIXOUTR */ +#define WM8993_MIXINR_TO_MIXOUTR_MASK 0x0040 /* MIXINR_TO_MIXOUTR */ +#define WM8993_MIXINR_TO_MIXOUTR_SHIFT 6 /* MIXINR_TO_MIXOUTR */ +#define WM8993_MIXINR_TO_MIXOUTR_WIDTH 1 /* MIXINR_TO_MIXOUTR */ +#define WM8993_IN2LN_TO_MIXOUTR 0x0020 /* IN2LN_TO_MIXOUTR */ +#define WM8993_IN2LN_TO_MIXOUTR_MASK 0x0020 /* IN2LN_TO_MIXOUTR */ +#define WM8993_IN2LN_TO_MIXOUTR_SHIFT 5 /* IN2LN_TO_MIXOUTR */ +#define WM8993_IN2LN_TO_MIXOUTR_WIDTH 1 /* IN2LN_TO_MIXOUTR */ +#define WM8993_IN2RN_TO_MIXOUTR 0x0010 /* IN2RN_TO_MIXOUTR */ +#define WM8993_IN2RN_TO_MIXOUTR_MASK 0x0010 /* IN2RN_TO_MIXOUTR */ +#define WM8993_IN2RN_TO_MIXOUTR_SHIFT 4 /* IN2RN_TO_MIXOUTR */ +#define WM8993_IN2RN_TO_MIXOUTR_WIDTH 1 /* IN2RN_TO_MIXOUTR */ +#define WM8993_IN1L_TO_MIXOUTR 0x0008 /* IN1L_TO_MIXOUTR */ +#define WM8993_IN1L_TO_MIXOUTR_MASK 0x0008 /* IN1L_TO_MIXOUTR */ +#define WM8993_IN1L_TO_MIXOUTR_SHIFT 3 /* IN1L_TO_MIXOUTR */ +#define WM8993_IN1L_TO_MIXOUTR_WIDTH 1 /* IN1L_TO_MIXOUTR */ +#define WM8993_IN1R_TO_MIXOUTR 0x0004 /* IN1R_TO_MIXOUTR */ +#define WM8993_IN1R_TO_MIXOUTR_MASK 0x0004 /* IN1R_TO_MIXOUTR */ +#define WM8993_IN1R_TO_MIXOUTR_SHIFT 2 /* IN1R_TO_MIXOUTR */ +#define WM8993_IN1R_TO_MIXOUTR_WIDTH 1 /* IN1R_TO_MIXOUTR */ +#define WM8993_IN2RP_TO_MIXOUTR 0x0002 /* IN2RP_TO_MIXOUTR */ +#define WM8993_IN2RP_TO_MIXOUTR_MASK 0x0002 /* IN2RP_TO_MIXOUTR */ +#define WM8993_IN2RP_TO_MIXOUTR_SHIFT 1 /* IN2RP_TO_MIXOUTR */ +#define WM8993_IN2RP_TO_MIXOUTR_WIDTH 1 /* IN2RP_TO_MIXOUTR */ +#define WM8993_DACR_TO_MIXOUTR 0x0001 /* DACR_TO_MIXOUTR */ +#define WM8993_DACR_TO_MIXOUTR_MASK 0x0001 /* DACR_TO_MIXOUTR */ +#define WM8993_DACR_TO_MIXOUTR_SHIFT 0 /* DACR_TO_MIXOUTR */ +#define WM8993_DACR_TO_MIXOUTR_WIDTH 1 /* DACR_TO_MIXOUTR */ + +/* + * R47 (0x2F) - Output Mixer3 + */ +#define WM8993_IN2LP_MIXOUTL_VOL_MASK 0x0E00 /* IN2LP_MIXOUTL_VOL - [11:9] */ +#define WM8993_IN2LP_MIXOUTL_VOL_SHIFT 9 /* IN2LP_MIXOUTL_VOL - [11:9] */ +#define WM8993_IN2LP_MIXOUTL_VOL_WIDTH 3 /* IN2LP_MIXOUTL_VOL - [11:9] */ +#define WM8993_IN2LN_MIXOUTL_VOL_MASK 0x01C0 /* IN2LN_MIXOUTL_VOL - [8:6] */ +#define WM8993_IN2LN_MIXOUTL_VOL_SHIFT 6 /* IN2LN_MIXOUTL_VOL - [8:6] */ +#define WM8993_IN2LN_MIXOUTL_VOL_WIDTH 3 /* IN2LN_MIXOUTL_VOL - [8:6] */ +#define WM8993_IN1R_MIXOUTL_VOL_MASK 0x0038 /* IN1R_MIXOUTL_VOL - [5:3] */ +#define WM8993_IN1R_MIXOUTL_VOL_SHIFT 3 /* IN1R_MIXOUTL_VOL - [5:3] */ +#define WM8993_IN1R_MIXOUTL_VOL_WIDTH 3 /* IN1R_MIXOUTL_VOL - [5:3] */ +#define WM8993_IN1L_MIXOUTL_VOL_MASK 0x0007 /* IN1L_MIXOUTL_VOL - [2:0] */ +#define WM8993_IN1L_MIXOUTL_VOL_SHIFT 0 /* IN1L_MIXOUTL_VOL - [2:0] */ +#define WM8993_IN1L_MIXOUTL_VOL_WIDTH 3 /* IN1L_MIXOUTL_VOL - [2:0] */ + +/* + * R48 (0x30) - Output Mixer4 + */ +#define WM8993_IN2RP_MIXOUTR_VOL_MASK 0x0E00 /* IN2RP_MIXOUTR_VOL - [11:9] */ +#define WM8993_IN2RP_MIXOUTR_VOL_SHIFT 9 /* IN2RP_MIXOUTR_VOL - [11:9] */ +#define WM8993_IN2RP_MIXOUTR_VOL_WIDTH 3 /* IN2RP_MIXOUTR_VOL - [11:9] */ +#define WM8993_IN2RN_MIXOUTR_VOL_MASK 0x01C0 /* IN2RN_MIXOUTR_VOL - [8:6] */ +#define WM8993_IN2RN_MIXOUTR_VOL_SHIFT 6 /* IN2RN_MIXOUTR_VOL - [8:6] */ +#define WM8993_IN2RN_MIXOUTR_VOL_WIDTH 3 /* IN2RN_MIXOUTR_VOL - [8:6] */ +#define WM8993_IN1L_MIXOUTR_VOL_MASK 0x0038 /* IN1L_MIXOUTR_VOL - [5:3] */ +#define WM8993_IN1L_MIXOUTR_VOL_SHIFT 3 /* IN1L_MIXOUTR_VOL - [5:3] */ +#define WM8993_IN1L_MIXOUTR_VOL_WIDTH 3 /* IN1L_MIXOUTR_VOL - [5:3] */ +#define WM8993_IN1R_MIXOUTR_VOL_MASK 0x0007 /* IN1R_MIXOUTR_VOL - [2:0] */ +#define WM8993_IN1R_MIXOUTR_VOL_SHIFT 0 /* IN1R_MIXOUTR_VOL - [2:0] */ +#define WM8993_IN1R_MIXOUTR_VOL_WIDTH 3 /* IN1R_MIXOUTR_VOL - [2:0] */ + +/* + * R49 (0x31) - Output Mixer5 + */ +#define WM8993_DACL_MIXOUTL_VOL_MASK 0x0E00 /* DACL_MIXOUTL_VOL - [11:9] */ +#define WM8993_DACL_MIXOUTL_VOL_SHIFT 9 /* DACL_MIXOUTL_VOL - [11:9] */ +#define WM8993_DACL_MIXOUTL_VOL_WIDTH 3 /* DACL_MIXOUTL_VOL - [11:9] */ +#define WM8993_IN2RN_MIXOUTL_VOL_MASK 0x01C0 /* IN2RN_MIXOUTL_VOL - [8:6] */ +#define WM8993_IN2RN_MIXOUTL_VOL_SHIFT 6 /* IN2RN_MIXOUTL_VOL - [8:6] */ +#define WM8993_IN2RN_MIXOUTL_VOL_WIDTH 3 /* IN2RN_MIXOUTL_VOL - [8:6] */ +#define WM8993_MIXINR_MIXOUTL_VOL_MASK 0x0038 /* MIXINR_MIXOUTL_VOL - [5:3] */ +#define WM8993_MIXINR_MIXOUTL_VOL_SHIFT 3 /* MIXINR_MIXOUTL_VOL - [5:3] */ +#define WM8993_MIXINR_MIXOUTL_VOL_WIDTH 3 /* MIXINR_MIXOUTL_VOL - [5:3] */ +#define WM8993_MIXINL_MIXOUTL_VOL_MASK 0x0007 /* MIXINL_MIXOUTL_VOL - [2:0] */ +#define WM8993_MIXINL_MIXOUTL_VOL_SHIFT 0 /* MIXINL_MIXOUTL_VOL - [2:0] */ +#define WM8993_MIXINL_MIXOUTL_VOL_WIDTH 3 /* MIXINL_MIXOUTL_VOL - [2:0] */ + +/* + * R50 (0x32) - Output Mixer6 + */ +#define WM8993_DACR_MIXOUTR_VOL_MASK 0x0E00 /* DACR_MIXOUTR_VOL - [11:9] */ +#define WM8993_DACR_MIXOUTR_VOL_SHIFT 9 /* DACR_MIXOUTR_VOL - [11:9] */ +#define WM8993_DACR_MIXOUTR_VOL_WIDTH 3 /* DACR_MIXOUTR_VOL - [11:9] */ +#define WM8993_IN2LN_MIXOUTR_VOL_MASK 0x01C0 /* IN2LN_MIXOUTR_VOL - [8:6] */ +#define WM8993_IN2LN_MIXOUTR_VOL_SHIFT 6 /* IN2LN_MIXOUTR_VOL - [8:6] */ +#define WM8993_IN2LN_MIXOUTR_VOL_WIDTH 3 /* IN2LN_MIXOUTR_VOL - [8:6] */ +#define WM8993_MIXINL_MIXOUTR_VOL_MASK 0x0038 /* MIXINL_MIXOUTR_VOL - [5:3] */ +#define WM8993_MIXINL_MIXOUTR_VOL_SHIFT 3 /* MIXINL_MIXOUTR_VOL - [5:3] */ +#define WM8993_MIXINL_MIXOUTR_VOL_WIDTH 3 /* MIXINL_MIXOUTR_VOL - [5:3] */ +#define WM8993_MIXINR_MIXOUTR_VOL_MASK 0x0007 /* MIXINR_MIXOUTR_VOL - [2:0] */ +#define WM8993_MIXINR_MIXOUTR_VOL_SHIFT 0 /* MIXINR_MIXOUTR_VOL - [2:0] */ +#define WM8993_MIXINR_MIXOUTR_VOL_WIDTH 3 /* MIXINR_MIXOUTR_VOL - [2:0] */ + +/* + * R51 (0x33) - HPOUT2 Mixer + */ +#define WM8993_VRX_TO_HPOUT2 0x0020 /* VRX_TO_HPOUT2 */ +#define WM8993_VRX_TO_HPOUT2_MASK 0x0020 /* VRX_TO_HPOUT2 */ +#define WM8993_VRX_TO_HPOUT2_SHIFT 5 /* VRX_TO_HPOUT2 */ +#define WM8993_VRX_TO_HPOUT2_WIDTH 1 /* VRX_TO_HPOUT2 */ +#define WM8993_MIXOUTLVOL_TO_HPOUT2 0x0010 /* MIXOUTLVOL_TO_HPOUT2 */ +#define WM8993_MIXOUTLVOL_TO_HPOUT2_MASK 0x0010 /* MIXOUTLVOL_TO_HPOUT2 */ +#define WM8993_MIXOUTLVOL_TO_HPOUT2_SHIFT 4 /* MIXOUTLVOL_TO_HPOUT2 */ +#define WM8993_MIXOUTLVOL_TO_HPOUT2_WIDTH 1 /* MIXOUTLVOL_TO_HPOUT2 */ +#define WM8993_MIXOUTRVOL_TO_HPOUT2 0x0008 /* MIXOUTRVOL_TO_HPOUT2 */ +#define WM8993_MIXOUTRVOL_TO_HPOUT2_MASK 0x0008 /* MIXOUTRVOL_TO_HPOUT2 */ +#define WM8993_MIXOUTRVOL_TO_HPOUT2_SHIFT 3 /* MIXOUTRVOL_TO_HPOUT2 */ +#define WM8993_MIXOUTRVOL_TO_HPOUT2_WIDTH 1 /* MIXOUTRVOL_TO_HPOUT2 */ + +/* + * R52 (0x34) - Line Mixer1 + */ +#define WM8993_MIXOUTL_TO_LINEOUT1N 0x0040 /* MIXOUTL_TO_LINEOUT1N */ +#define WM8993_MIXOUTL_TO_LINEOUT1N_MASK 0x0040 /* MIXOUTL_TO_LINEOUT1N */ +#define WM8993_MIXOUTL_TO_LINEOUT1N_SHIFT 6 /* MIXOUTL_TO_LINEOUT1N */ +#define WM8993_MIXOUTL_TO_LINEOUT1N_WIDTH 1 /* MIXOUTL_TO_LINEOUT1N */ +#define WM8993_MIXOUTR_TO_LINEOUT1N 0x0020 /* MIXOUTR_TO_LINEOUT1N */ +#define WM8993_MIXOUTR_TO_LINEOUT1N_MASK 0x0020 /* MIXOUTR_TO_LINEOUT1N */ +#define WM8993_MIXOUTR_TO_LINEOUT1N_SHIFT 5 /* MIXOUTR_TO_LINEOUT1N */ +#define WM8993_MIXOUTR_TO_LINEOUT1N_WIDTH 1 /* MIXOUTR_TO_LINEOUT1N */ +#define WM8993_LINEOUT1_MODE 0x0010 /* LINEOUT1_MODE */ +#define WM8993_LINEOUT1_MODE_MASK 0x0010 /* LINEOUT1_MODE */ +#define WM8993_LINEOUT1_MODE_SHIFT 4 /* LINEOUT1_MODE */ +#define WM8993_LINEOUT1_MODE_WIDTH 1 /* LINEOUT1_MODE */ +#define WM8993_IN1R_TO_LINEOUT1P 0x0004 /* IN1R_TO_LINEOUT1P */ +#define WM8993_IN1R_TO_LINEOUT1P_MASK 0x0004 /* IN1R_TO_LINEOUT1P */ +#define WM8993_IN1R_TO_LINEOUT1P_SHIFT 2 /* IN1R_TO_LINEOUT1P */ +#define WM8993_IN1R_TO_LINEOUT1P_WIDTH 1 /* IN1R_TO_LINEOUT1P */ +#define WM8993_IN1L_TO_LINEOUT1P 0x0002 /* IN1L_TO_LINEOUT1P */ +#define WM8993_IN1L_TO_LINEOUT1P_MASK 0x0002 /* IN1L_TO_LINEOUT1P */ +#define WM8993_IN1L_TO_LINEOUT1P_SHIFT 1 /* IN1L_TO_LINEOUT1P */ +#define WM8993_IN1L_TO_LINEOUT1P_WIDTH 1 /* IN1L_TO_LINEOUT1P */ +#define WM8993_MIXOUTL_TO_LINEOUT1P 0x0001 /* MIXOUTL_TO_LINEOUT1P */ +#define WM8993_MIXOUTL_TO_LINEOUT1P_MASK 0x0001 /* MIXOUTL_TO_LINEOUT1P */ +#define WM8993_MIXOUTL_TO_LINEOUT1P_SHIFT 0 /* MIXOUTL_TO_LINEOUT1P */ +#define WM8993_MIXOUTL_TO_LINEOUT1P_WIDTH 1 /* MIXOUTL_TO_LINEOUT1P */ + +/* + * R53 (0x35) - Line Mixer2 + */ +#define WM8993_MIXOUTR_TO_LINEOUT2N 0x0040 /* MIXOUTR_TO_LINEOUT2N */ +#define WM8993_MIXOUTR_TO_LINEOUT2N_MASK 0x0040 /* MIXOUTR_TO_LINEOUT2N */ +#define WM8993_MIXOUTR_TO_LINEOUT2N_SHIFT 6 /* MIXOUTR_TO_LINEOUT2N */ +#define WM8993_MIXOUTR_TO_LINEOUT2N_WIDTH 1 /* MIXOUTR_TO_LINEOUT2N */ +#define WM8993_MIXOUTL_TO_LINEOUT2N 0x0020 /* MIXOUTL_TO_LINEOUT2N */ +#define WM8993_MIXOUTL_TO_LINEOUT2N_MASK 0x0020 /* MIXOUTL_TO_LINEOUT2N */ +#define WM8993_MIXOUTL_TO_LINEOUT2N_SHIFT 5 /* MIXOUTL_TO_LINEOUT2N */ +#define WM8993_MIXOUTL_TO_LINEOUT2N_WIDTH 1 /* MIXOUTL_TO_LINEOUT2N */ +#define WM8993_LINEOUT2_MODE 0x0010 /* LINEOUT2_MODE */ +#define WM8993_LINEOUT2_MODE_MASK 0x0010 /* LINEOUT2_MODE */ +#define WM8993_LINEOUT2_MODE_SHIFT 4 /* LINEOUT2_MODE */ +#define WM8993_LINEOUT2_MODE_WIDTH 1 /* LINEOUT2_MODE */ +#define WM8993_IN1L_TO_LINEOUT2P 0x0004 /* IN1L_TO_LINEOUT2P */ +#define WM8993_IN1L_TO_LINEOUT2P_MASK 0x0004 /* IN1L_TO_LINEOUT2P */ +#define WM8993_IN1L_TO_LINEOUT2P_SHIFT 2 /* IN1L_TO_LINEOUT2P */ +#define WM8993_IN1L_TO_LINEOUT2P_WIDTH 1 /* IN1L_TO_LINEOUT2P */ +#define WM8993_IN1R_TO_LINEOUT2P 0x0002 /* IN1R_TO_LINEOUT2P */ +#define WM8993_IN1R_TO_LINEOUT2P_MASK 0x0002 /* IN1R_TO_LINEOUT2P */ +#define WM8993_IN1R_TO_LINEOUT2P_SHIFT 1 /* IN1R_TO_LINEOUT2P */ +#define WM8993_IN1R_TO_LINEOUT2P_WIDTH 1 /* IN1R_TO_LINEOUT2P */ +#define WM8993_MIXOUTR_TO_LINEOUT2P 0x0001 /* MIXOUTR_TO_LINEOUT2P */ +#define WM8993_MIXOUTR_TO_LINEOUT2P_MASK 0x0001 /* MIXOUTR_TO_LINEOUT2P */ +#define WM8993_MIXOUTR_TO_LINEOUT2P_SHIFT 0 /* MIXOUTR_TO_LINEOUT2P */ +#define WM8993_MIXOUTR_TO_LINEOUT2P_WIDTH 1 /* MIXOUTR_TO_LINEOUT2P */ + +/* + * R54 (0x36) - Speaker Mixer + */ +#define WM8993_SPKAB_REF_SEL 0x0100 /* SPKAB_REF_SEL */ +#define WM8993_SPKAB_REF_SEL_MASK 0x0100 /* SPKAB_REF_SEL */ +#define WM8993_SPKAB_REF_SEL_SHIFT 8 /* SPKAB_REF_SEL */ +#define WM8993_SPKAB_REF_SEL_WIDTH 1 /* SPKAB_REF_SEL */ +#define WM8993_MIXINL_TO_SPKMIXL 0x0080 /* MIXINL_TO_SPKMIXL */ +#define WM8993_MIXINL_TO_SPKMIXL_MASK 0x0080 /* MIXINL_TO_SPKMIXL */ +#define WM8993_MIXINL_TO_SPKMIXL_SHIFT 7 /* MIXINL_TO_SPKMIXL */ +#define WM8993_MIXINL_TO_SPKMIXL_WIDTH 1 /* MIXINL_TO_SPKMIXL */ +#define WM8993_MIXINR_TO_SPKMIXR 0x0040 /* MIXINR_TO_SPKMIXR */ +#define WM8993_MIXINR_TO_SPKMIXR_MASK 0x0040 /* MIXINR_TO_SPKMIXR */ +#define WM8993_MIXINR_TO_SPKMIXR_SHIFT 6 /* MIXINR_TO_SPKMIXR */ +#define WM8993_MIXINR_TO_SPKMIXR_WIDTH 1 /* MIXINR_TO_SPKMIXR */ +#define WM8993_IN1LP_TO_SPKMIXL 0x0020 /* IN1LP_TO_SPKMIXL */ +#define WM8993_IN1LP_TO_SPKMIXL_MASK 0x0020 /* IN1LP_TO_SPKMIXL */ +#define WM8993_IN1LP_TO_SPKMIXL_SHIFT 5 /* IN1LP_TO_SPKMIXL */ +#define WM8993_IN1LP_TO_SPKMIXL_WIDTH 1 /* IN1LP_TO_SPKMIXL */ +#define WM8993_IN1RP_TO_SPKMIXR 0x0010 /* IN1RP_TO_SPKMIXR */ +#define WM8993_IN1RP_TO_SPKMIXR_MASK 0x0010 /* IN1RP_TO_SPKMIXR */ +#define WM8993_IN1RP_TO_SPKMIXR_SHIFT 4 /* IN1RP_TO_SPKMIXR */ +#define WM8993_IN1RP_TO_SPKMIXR_WIDTH 1 /* IN1RP_TO_SPKMIXR */ +#define WM8993_MIXOUTL_TO_SPKMIXL 0x0008 /* MIXOUTL_TO_SPKMIXL */ +#define WM8993_MIXOUTL_TO_SPKMIXL_MASK 0x0008 /* MIXOUTL_TO_SPKMIXL */ +#define WM8993_MIXOUTL_TO_SPKMIXL_SHIFT 3 /* MIXOUTL_TO_SPKMIXL */ +#define WM8993_MIXOUTL_TO_SPKMIXL_WIDTH 1 /* MIXOUTL_TO_SPKMIXL */ +#define WM8993_MIXOUTR_TO_SPKMIXR 0x0004 /* MIXOUTR_TO_SPKMIXR */ +#define WM8993_MIXOUTR_TO_SPKMIXR_MASK 0x0004 /* MIXOUTR_TO_SPKMIXR */ +#define WM8993_MIXOUTR_TO_SPKMIXR_SHIFT 2 /* MIXOUTR_TO_SPKMIXR */ +#define WM8993_MIXOUTR_TO_SPKMIXR_WIDTH 1 /* MIXOUTR_TO_SPKMIXR */ +#define WM8993_DACL_TO_SPKMIXL 0x0002 /* DACL_TO_SPKMIXL */ +#define WM8993_DACL_TO_SPKMIXL_MASK 0x0002 /* DACL_TO_SPKMIXL */ +#define WM8993_DACL_TO_SPKMIXL_SHIFT 1 /* DACL_TO_SPKMIXL */ +#define WM8993_DACL_TO_SPKMIXL_WIDTH 1 /* DACL_TO_SPKMIXL */ +#define WM8993_DACR_TO_SPKMIXR 0x0001 /* DACR_TO_SPKMIXR */ +#define WM8993_DACR_TO_SPKMIXR_MASK 0x0001 /* DACR_TO_SPKMIXR */ +#define WM8993_DACR_TO_SPKMIXR_SHIFT 0 /* DACR_TO_SPKMIXR */ +#define WM8993_DACR_TO_SPKMIXR_WIDTH 1 /* DACR_TO_SPKMIXR */ + +/* + * R55 (0x37) - Additional Control + */ +#define WM8993_LINEOUT1_FB 0x0080 /* LINEOUT1_FB */ +#define WM8993_LINEOUT1_FB_MASK 0x0080 /* LINEOUT1_FB */ +#define WM8993_LINEOUT1_FB_SHIFT 7 /* LINEOUT1_FB */ +#define WM8993_LINEOUT1_FB_WIDTH 1 /* LINEOUT1_FB */ +#define WM8993_LINEOUT2_FB 0x0040 /* LINEOUT2_FB */ +#define WM8993_LINEOUT2_FB_MASK 0x0040 /* LINEOUT2_FB */ +#define WM8993_LINEOUT2_FB_SHIFT 6 /* LINEOUT2_FB */ +#define WM8993_LINEOUT2_FB_WIDTH 1 /* LINEOUT2_FB */ +#define WM8993_VROI 0x0001 /* VROI */ +#define WM8993_VROI_MASK 0x0001 /* VROI */ +#define WM8993_VROI_SHIFT 0 /* VROI */ +#define WM8993_VROI_WIDTH 1 /* VROI */ + +/* + * R56 (0x38) - AntiPOP1 + */ +#define WM8993_LINEOUT_VMID_BUF_ENA 0x0080 /* LINEOUT_VMID_BUF_ENA */ +#define WM8993_LINEOUT_VMID_BUF_ENA_MASK 0x0080 /* LINEOUT_VMID_BUF_ENA */ +#define WM8993_LINEOUT_VMID_BUF_ENA_SHIFT 7 /* LINEOUT_VMID_BUF_ENA */ +#define WM8993_LINEOUT_VMID_BUF_ENA_WIDTH 1 /* LINEOUT_VMID_BUF_ENA */ +#define WM8993_HPOUT2_IN_ENA 0x0040 /* HPOUT2_IN_ENA */ +#define WM8993_HPOUT2_IN_ENA_MASK 0x0040 /* HPOUT2_IN_ENA */ +#define WM8993_HPOUT2_IN_ENA_SHIFT 6 /* HPOUT2_IN_ENA */ +#define WM8993_HPOUT2_IN_ENA_WIDTH 1 /* HPOUT2_IN_ENA */ +#define WM8993_LINEOUT1_DISCH 0x0020 /* LINEOUT1_DISCH */ +#define WM8993_LINEOUT1_DISCH_MASK 0x0020 /* LINEOUT1_DISCH */ +#define WM8993_LINEOUT1_DISCH_SHIFT 5 /* LINEOUT1_DISCH */ +#define WM8993_LINEOUT1_DISCH_WIDTH 1 /* LINEOUT1_DISCH */ +#define WM8993_LINEOUT2_DISCH 0x0010 /* LINEOUT2_DISCH */ +#define WM8993_LINEOUT2_DISCH_MASK 0x0010 /* LINEOUT2_DISCH */ +#define WM8993_LINEOUT2_DISCH_SHIFT 4 /* LINEOUT2_DISCH */ +#define WM8993_LINEOUT2_DISCH_WIDTH 1 /* LINEOUT2_DISCH */ + +/* + * R57 (0x39) - AntiPOP2 + */ +#define WM8993_VMID_RAMP_MASK 0x0060 /* VMID_RAMP - [6:5] */ +#define WM8993_VMID_RAMP_SHIFT 5 /* VMID_RAMP - [6:5] */ +#define WM8993_VMID_RAMP_WIDTH 2 /* VMID_RAMP - [6:5] */ +#define WM8993_VMID_BUF_ENA 0x0008 /* VMID_BUF_ENA */ +#define WM8993_VMID_BUF_ENA_MASK 0x0008 /* VMID_BUF_ENA */ +#define WM8993_VMID_BUF_ENA_SHIFT 3 /* VMID_BUF_ENA */ +#define WM8993_VMID_BUF_ENA_WIDTH 1 /* VMID_BUF_ENA */ +#define WM8993_STARTUP_BIAS_ENA 0x0004 /* STARTUP_BIAS_ENA */ +#define WM8993_STARTUP_BIAS_ENA_MASK 0x0004 /* STARTUP_BIAS_ENA */ +#define WM8993_STARTUP_BIAS_ENA_SHIFT 2 /* STARTUP_BIAS_ENA */ +#define WM8993_STARTUP_BIAS_ENA_WIDTH 1 /* STARTUP_BIAS_ENA */ +#define WM8993_BIAS_SRC 0x0002 /* BIAS_SRC */ +#define WM8993_BIAS_SRC_MASK 0x0002 /* BIAS_SRC */ +#define WM8993_BIAS_SRC_SHIFT 1 /* BIAS_SRC */ +#define WM8993_BIAS_SRC_WIDTH 1 /* BIAS_SRC */ +#define WM8993_VMID_DISCH 0x0001 /* VMID_DISCH */ +#define WM8993_VMID_DISCH_MASK 0x0001 /* VMID_DISCH */ +#define WM8993_VMID_DISCH_SHIFT 0 /* VMID_DISCH */ +#define WM8993_VMID_DISCH_WIDTH 1 /* VMID_DISCH */ + +/* + * R58 (0x3A) - MICBIAS + */ +#define WM8993_JD_SCTHR_MASK 0x00C0 /* JD_SCTHR - [7:6] */ +#define WM8993_JD_SCTHR_SHIFT 6 /* JD_SCTHR - [7:6] */ +#define WM8993_JD_SCTHR_WIDTH 2 /* JD_SCTHR - [7:6] */ +#define WM8993_JD_THR_MASK 0x0030 /* JD_THR - [5:4] */ +#define WM8993_JD_THR_SHIFT 4 /* JD_THR - [5:4] */ +#define WM8993_JD_THR_WIDTH 2 /* JD_THR - [5:4] */ +#define WM8993_JD_ENA 0x0004 /* JD_ENA */ +#define WM8993_JD_ENA_MASK 0x0004 /* JD_ENA */ +#define WM8993_JD_ENA_SHIFT 2 /* JD_ENA */ +#define WM8993_JD_ENA_WIDTH 1 /* JD_ENA */ +#define WM8993_MICB2_LVL 0x0002 /* MICB2_LVL */ +#define WM8993_MICB2_LVL_MASK 0x0002 /* MICB2_LVL */ +#define WM8993_MICB2_LVL_SHIFT 1 /* MICB2_LVL */ +#define WM8993_MICB2_LVL_WIDTH 1 /* MICB2_LVL */ +#define WM8993_MICB1_LVL 0x0001 /* MICB1_LVL */ +#define WM8993_MICB1_LVL_MASK 0x0001 /* MICB1_LVL */ +#define WM8993_MICB1_LVL_SHIFT 0 /* MICB1_LVL */ +#define WM8993_MICB1_LVL_WIDTH 1 /* MICB1_LVL */ + +/* + * R60 (0x3C) - FLL Control 1 + */ +#define WM8993_FLL_FRAC 0x0004 /* FLL_FRAC */ +#define WM8993_FLL_FRAC_MASK 0x0004 /* FLL_FRAC */ +#define WM8993_FLL_FRAC_SHIFT 2 /* FLL_FRAC */ +#define WM8993_FLL_FRAC_WIDTH 1 /* FLL_FRAC */ +#define WM8993_FLL_OSC_ENA 0x0002 /* FLL_OSC_ENA */ +#define WM8993_FLL_OSC_ENA_MASK 0x0002 /* FLL_OSC_ENA */ +#define WM8993_FLL_OSC_ENA_SHIFT 1 /* FLL_OSC_ENA */ +#define WM8993_FLL_OSC_ENA_WIDTH 1 /* FLL_OSC_ENA */ +#define WM8993_FLL_ENA 0x0001 /* FLL_ENA */ +#define WM8993_FLL_ENA_MASK 0x0001 /* FLL_ENA */ +#define WM8993_FLL_ENA_SHIFT 0 /* FLL_ENA */ +#define WM8993_FLL_ENA_WIDTH 1 /* FLL_ENA */ + +/* + * R61 (0x3D) - FLL Control 2 + */ +#define WM8993_FLL_OUTDIV_MASK 0x0700 /* FLL_OUTDIV - [10:8] */ +#define WM8993_FLL_OUTDIV_SHIFT 8 /* FLL_OUTDIV - [10:8] */ +#define WM8993_FLL_OUTDIV_WIDTH 3 /* FLL_OUTDIV - [10:8] */ +#define WM8993_FLL_CTRL_RATE_MASK 0x0070 /* FLL_CTRL_RATE - [6:4] */ +#define WM8993_FLL_CTRL_RATE_SHIFT 4 /* FLL_CTRL_RATE - [6:4] */ +#define WM8993_FLL_CTRL_RATE_WIDTH 3 /* FLL_CTRL_RATE - [6:4] */ +#define WM8993_FLL_FRATIO_MASK 0x0007 /* FLL_FRATIO - [2:0] */ +#define WM8993_FLL_FRATIO_SHIFT 0 /* FLL_FRATIO - [2:0] */ +#define WM8993_FLL_FRATIO_WIDTH 3 /* FLL_FRATIO - [2:0] */ + +/* + * R62 (0x3E) - FLL Control 3 + */ +#define WM8993_FLL_K_MASK 0xFFFF /* FLL_K - [15:0] */ +#define WM8993_FLL_K_SHIFT 0 /* FLL_K - [15:0] */ +#define WM8993_FLL_K_WIDTH 16 /* FLL_K - [15:0] */ + +/* + * R63 (0x3F) - FLL Control 4 + */ +#define WM8993_FLL_N_MASK 0x7FE0 /* FLL_N - [14:5] */ +#define WM8993_FLL_N_SHIFT 5 /* FLL_N - [14:5] */ +#define WM8993_FLL_N_WIDTH 10 /* FLL_N - [14:5] */ +#define WM8993_FLL_GAIN_MASK 0x000F /* FLL_GAIN - [3:0] */ +#define WM8993_FLL_GAIN_SHIFT 0 /* FLL_GAIN - [3:0] */ +#define WM8993_FLL_GAIN_WIDTH 4 /* FLL_GAIN - [3:0] */ + +/* + * R64 (0x40) - FLL Control 5 + */ +#define WM8993_FLL_FRC_NCO_VAL_MASK 0x1F80 /* FLL_FRC_NCO_VAL - [12:7] */ +#define WM8993_FLL_FRC_NCO_VAL_SHIFT 7 /* FLL_FRC_NCO_VAL - [12:7] */ +#define WM8993_FLL_FRC_NCO_VAL_WIDTH 6 /* FLL_FRC_NCO_VAL - [12:7] */ +#define WM8993_FLL_FRC_NCO 0x0040 /* FLL_FRC_NCO */ +#define WM8993_FLL_FRC_NCO_MASK 0x0040 /* FLL_FRC_NCO */ +#define WM8993_FLL_FRC_NCO_SHIFT 6 /* FLL_FRC_NCO */ +#define WM8993_FLL_FRC_NCO_WIDTH 1 /* FLL_FRC_NCO */ +#define WM8993_FLL_CLK_REF_DIV_MASK 0x0018 /* FLL_CLK_REF_DIV - [4:3] */ +#define WM8993_FLL_CLK_REF_DIV_SHIFT 3 /* FLL_CLK_REF_DIV - [4:3] */ +#define WM8993_FLL_CLK_REF_DIV_WIDTH 2 /* FLL_CLK_REF_DIV - [4:3] */ +#define WM8993_FLL_CLK_SRC_MASK 0x0003 /* FLL_CLK_SRC - [1:0] */ +#define WM8993_FLL_CLK_SRC_SHIFT 0 /* FLL_CLK_SRC - [1:0] */ +#define WM8993_FLL_CLK_SRC_WIDTH 2 /* FLL_CLK_SRC - [1:0] */ + +/* + * R65 (0x41) - Clocking 3 + */ +#define WM8993_CLK_DCS_DIV_MASK 0x3C00 /* CLK_DCS_DIV - [13:10] */ +#define WM8993_CLK_DCS_DIV_SHIFT 10 /* CLK_DCS_DIV - [13:10] */ +#define WM8993_CLK_DCS_DIV_WIDTH 4 /* CLK_DCS_DIV - [13:10] */ +#define WM8993_SAMPLE_RATE_MASK 0x0380 /* SAMPLE_RATE - [9:7] */ +#define WM8993_SAMPLE_RATE_SHIFT 7 /* SAMPLE_RATE - [9:7] */ +#define WM8993_SAMPLE_RATE_WIDTH 3 /* SAMPLE_RATE - [9:7] */ +#define WM8993_CLK_SYS_RATE_MASK 0x001E /* CLK_SYS_RATE - [4:1] */ +#define WM8993_CLK_SYS_RATE_SHIFT 1 /* CLK_SYS_RATE - [4:1] */ +#define WM8993_CLK_SYS_RATE_WIDTH 4 /* CLK_SYS_RATE - [4:1] */ +#define WM8993_CLK_DSP_ENA 0x0001 /* CLK_DSP_ENA */ +#define WM8993_CLK_DSP_ENA_MASK 0x0001 /* CLK_DSP_ENA */ +#define WM8993_CLK_DSP_ENA_SHIFT 0 /* CLK_DSP_ENA */ +#define WM8993_CLK_DSP_ENA_WIDTH 1 /* CLK_DSP_ENA */ + +/* + * R66 (0x42) - Clocking 4 + */ +#define WM8993_DAC_DIV4 0x0200 /* DAC_DIV4 */ +#define WM8993_DAC_DIV4_MASK 0x0200 /* DAC_DIV4 */ +#define WM8993_DAC_DIV4_SHIFT 9 /* DAC_DIV4 */ +#define WM8993_DAC_DIV4_WIDTH 1 /* DAC_DIV4 */ +#define WM8993_CLK_256K_DIV_MASK 0x007E /* CLK_256K_DIV - [6:1] */ +#define WM8993_CLK_256K_DIV_SHIFT 1 /* CLK_256K_DIV - [6:1] */ +#define WM8993_CLK_256K_DIV_WIDTH 6 /* CLK_256K_DIV - [6:1] */ +#define WM8993_SR_MODE 0x0001 /* SR_MODE */ +#define WM8993_SR_MODE_MASK 0x0001 /* SR_MODE */ +#define WM8993_SR_MODE_SHIFT 0 /* SR_MODE */ +#define WM8993_SR_MODE_WIDTH 1 /* SR_MODE */ + +/* + * R67 (0x43) - MW Slave Control + */ +#define WM8993_MASK_WRITE_ENA 0x0001 /* MASK_WRITE_ENA */ +#define WM8993_MASK_WRITE_ENA_MASK 0x0001 /* MASK_WRITE_ENA */ +#define WM8993_MASK_WRITE_ENA_SHIFT 0 /* MASK_WRITE_ENA */ +#define WM8993_MASK_WRITE_ENA_WIDTH 1 /* MASK_WRITE_ENA */ + +/* + * R69 (0x45) - Bus Control 1 + */ +#define WM8993_CLK_SYS_ENA 0x0002 /* CLK_SYS_ENA */ +#define WM8993_CLK_SYS_ENA_MASK 0x0002 /* CLK_SYS_ENA */ +#define WM8993_CLK_SYS_ENA_SHIFT 1 /* CLK_SYS_ENA */ +#define WM8993_CLK_SYS_ENA_WIDTH 1 /* CLK_SYS_ENA */ + +/* + * R70 (0x46) - Write Sequencer 0 + */ +#define WM8993_WSEQ_ENA 0x0100 /* WSEQ_ENA */ +#define WM8993_WSEQ_ENA_MASK 0x0100 /* WSEQ_ENA */ +#define WM8993_WSEQ_ENA_SHIFT 8 /* WSEQ_ENA */ +#define WM8993_WSEQ_ENA_WIDTH 1 /* WSEQ_ENA */ +#define WM8993_WSEQ_WRITE_INDEX_MASK 0x001F /* WSEQ_WRITE_INDEX - [4:0] */ +#define WM8993_WSEQ_WRITE_INDEX_SHIFT 0 /* WSEQ_WRITE_INDEX - [4:0] */ +#define WM8993_WSEQ_WRITE_INDEX_WIDTH 5 /* WSEQ_WRITE_INDEX - [4:0] */ + +/* + * R71 (0x47) - Write Sequencer 1 + */ +#define WM8993_WSEQ_DATA_WIDTH_MASK 0x7000 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM8993_WSEQ_DATA_WIDTH_SHIFT 12 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM8993_WSEQ_DATA_WIDTH_WIDTH 3 /* WSEQ_DATA_WIDTH - [14:12] */ +#define WM8993_WSEQ_DATA_START_MASK 0x0F00 /* WSEQ_DATA_START - [11:8] */ +#define WM8993_WSEQ_DATA_START_SHIFT 8 /* WSEQ_DATA_START - [11:8] */ +#define WM8993_WSEQ_DATA_START_WIDTH 4 /* WSEQ_DATA_START - [11:8] */ +#define WM8993_WSEQ_ADDR_MASK 0x00FF /* WSEQ_ADDR - [7:0] */ +#define WM8993_WSEQ_ADDR_SHIFT 0 /* WSEQ_ADDR - [7:0] */ +#define WM8993_WSEQ_ADDR_WIDTH 8 /* WSEQ_ADDR - [7:0] */ + +/* + * R72 (0x48) - Write Sequencer 2 + */ +#define WM8993_WSEQ_EOS 0x4000 /* WSEQ_EOS */ +#define WM8993_WSEQ_EOS_MASK 0x4000 /* WSEQ_EOS */ +#define WM8993_WSEQ_EOS_SHIFT 14 /* WSEQ_EOS */ +#define WM8993_WSEQ_EOS_WIDTH 1 /* WSEQ_EOS */ +#define WM8993_WSEQ_DELAY_MASK 0x0F00 /* WSEQ_DELAY - [11:8] */ +#define WM8993_WSEQ_DELAY_SHIFT 8 /* WSEQ_DELAY - [11:8] */ +#define WM8993_WSEQ_DELAY_WIDTH 4 /* WSEQ_DELAY - [11:8] */ +#define WM8993_WSEQ_DATA_MASK 0x00FF /* WSEQ_DATA - [7:0] */ +#define WM8993_WSEQ_DATA_SHIFT 0 /* WSEQ_DATA - [7:0] */ +#define WM8993_WSEQ_DATA_WIDTH 8 /* WSEQ_DATA - [7:0] */ + +/* + * R73 (0x49) - Write Sequencer 3 + */ +#define WM8993_WSEQ_ABORT 0x0200 /* WSEQ_ABORT */ +#define WM8993_WSEQ_ABORT_MASK 0x0200 /* WSEQ_ABORT */ +#define WM8993_WSEQ_ABORT_SHIFT 9 /* WSEQ_ABORT */ +#define WM8993_WSEQ_ABORT_WIDTH 1 /* WSEQ_ABORT */ +#define WM8993_WSEQ_START 0x0100 /* WSEQ_START */ +#define WM8993_WSEQ_START_MASK 0x0100 /* WSEQ_START */ +#define WM8993_WSEQ_START_SHIFT 8 /* WSEQ_START */ +#define WM8993_WSEQ_START_WIDTH 1 /* WSEQ_START */ +#define WM8993_WSEQ_START_INDEX_MASK 0x003F /* WSEQ_START_INDEX - [5:0] */ +#define WM8993_WSEQ_START_INDEX_SHIFT 0 /* WSEQ_START_INDEX - [5:0] */ +#define WM8993_WSEQ_START_INDEX_WIDTH 6 /* WSEQ_START_INDEX - [5:0] */ + +/* + * R74 (0x4A) - Write Sequencer 4 + */ +#define WM8993_WSEQ_BUSY 0x0001 /* WSEQ_BUSY */ +#define WM8993_WSEQ_BUSY_MASK 0x0001 /* WSEQ_BUSY */ +#define WM8993_WSEQ_BUSY_SHIFT 0 /* WSEQ_BUSY */ +#define WM8993_WSEQ_BUSY_WIDTH 1 /* WSEQ_BUSY */ + +/* + * R75 (0x4B) - Write Sequencer 5 + */ +#define WM8993_WSEQ_CURRENT_INDEX_MASK 0x003F /* WSEQ_CURRENT_INDEX - [5:0] */ +#define WM8993_WSEQ_CURRENT_INDEX_SHIFT 0 /* WSEQ_CURRENT_INDEX - [5:0] */ +#define WM8993_WSEQ_CURRENT_INDEX_WIDTH 6 /* WSEQ_CURRENT_INDEX - [5:0] */ + +/* + * R76 (0x4C) - Charge Pump 1 + */ +#define WM8993_CP_ENA 0x8000 /* CP_ENA */ +#define WM8993_CP_ENA_MASK 0x8000 /* CP_ENA */ +#define WM8993_CP_ENA_SHIFT 15 /* CP_ENA */ +#define WM8993_CP_ENA_WIDTH 1 /* CP_ENA */ + +/* + * R81 (0x51) - Class W 0 + */ +#define WM8993_CP_DYN_FREQ 0x0002 /* CP_DYN_FREQ */ +#define WM8993_CP_DYN_FREQ_MASK 0x0002 /* CP_DYN_FREQ */ +#define WM8993_CP_DYN_FREQ_SHIFT 1 /* CP_DYN_FREQ */ +#define WM8993_CP_DYN_FREQ_WIDTH 1 /* CP_DYN_FREQ */ +#define WM8993_CP_DYN_V 0x0001 /* CP_DYN_V */ +#define WM8993_CP_DYN_V_MASK 0x0001 /* CP_DYN_V */ +#define WM8993_CP_DYN_V_SHIFT 0 /* CP_DYN_V */ +#define WM8993_CP_DYN_V_WIDTH 1 /* CP_DYN_V */ + +/* + * R84 (0x54) - DC Servo 0 + */ +#define WM8993_DCS_TRIG_SINGLE_1 0x2000 /* DCS_TRIG_SINGLE_1 */ +#define WM8993_DCS_TRIG_SINGLE_1_MASK 0x2000 /* DCS_TRIG_SINGLE_1 */ +#define WM8993_DCS_TRIG_SINGLE_1_SHIFT 13 /* DCS_TRIG_SINGLE_1 */ +#define WM8993_DCS_TRIG_SINGLE_1_WIDTH 1 /* DCS_TRIG_SINGLE_1 */ +#define WM8993_DCS_TRIG_SINGLE_0 0x1000 /* DCS_TRIG_SINGLE_0 */ +#define WM8993_DCS_TRIG_SINGLE_0_MASK 0x1000 /* DCS_TRIG_SINGLE_0 */ +#define WM8993_DCS_TRIG_SINGLE_0_SHIFT 12 /* DCS_TRIG_SINGLE_0 */ +#define WM8993_DCS_TRIG_SINGLE_0_WIDTH 1 /* DCS_TRIG_SINGLE_0 */ +#define WM8993_DCS_TRIG_SERIES_1 0x0200 /* DCS_TRIG_SERIES_1 */ +#define WM8993_DCS_TRIG_SERIES_1_MASK 0x0200 /* DCS_TRIG_SERIES_1 */ +#define WM8993_DCS_TRIG_SERIES_1_SHIFT 9 /* DCS_TRIG_SERIES_1 */ +#define WM8993_DCS_TRIG_SERIES_1_WIDTH 1 /* DCS_TRIG_SERIES_1 */ +#define WM8993_DCS_TRIG_SERIES_0 0x0100 /* DCS_TRIG_SERIES_0 */ +#define WM8993_DCS_TRIG_SERIES_0_MASK 0x0100 /* DCS_TRIG_SERIES_0 */ +#define WM8993_DCS_TRIG_SERIES_0_SHIFT 8 /* DCS_TRIG_SERIES_0 */ +#define WM8993_DCS_TRIG_SERIES_0_WIDTH 1 /* DCS_TRIG_SERIES_0 */ +#define WM8993_DCS_TRIG_STARTUP_1 0x0020 /* DCS_TRIG_STARTUP_1 */ +#define WM8993_DCS_TRIG_STARTUP_1_MASK 0x0020 /* DCS_TRIG_STARTUP_1 */ +#define WM8993_DCS_TRIG_STARTUP_1_SHIFT 5 /* DCS_TRIG_STARTUP_1 */ +#define WM8993_DCS_TRIG_STARTUP_1_WIDTH 1 /* DCS_TRIG_STARTUP_1 */ +#define WM8993_DCS_TRIG_STARTUP_0 0x0010 /* DCS_TRIG_STARTUP_0 */ +#define WM8993_DCS_TRIG_STARTUP_0_MASK 0x0010 /* DCS_TRIG_STARTUP_0 */ +#define WM8993_DCS_TRIG_STARTUP_0_SHIFT 4 /* DCS_TRIG_STARTUP_0 */ +#define WM8993_DCS_TRIG_STARTUP_0_WIDTH 1 /* DCS_TRIG_STARTUP_0 */ +#define WM8993_DCS_TRIG_DAC_WR_1 0x0008 /* DCS_TRIG_DAC_WR_1 */ +#define WM8993_DCS_TRIG_DAC_WR_1_MASK 0x0008 /* DCS_TRIG_DAC_WR_1 */ +#define WM8993_DCS_TRIG_DAC_WR_1_SHIFT 3 /* DCS_TRIG_DAC_WR_1 */ +#define WM8993_DCS_TRIG_DAC_WR_1_WIDTH 1 /* DCS_TRIG_DAC_WR_1 */ +#define WM8993_DCS_TRIG_DAC_WR_0 0x0004 /* DCS_TRIG_DAC_WR_0 */ +#define WM8993_DCS_TRIG_DAC_WR_0_MASK 0x0004 /* DCS_TRIG_DAC_WR_0 */ +#define WM8993_DCS_TRIG_DAC_WR_0_SHIFT 2 /* DCS_TRIG_DAC_WR_0 */ +#define WM8993_DCS_TRIG_DAC_WR_0_WIDTH 1 /* DCS_TRIG_DAC_WR_0 */ +#define WM8993_DCS_ENA_CHAN_1 0x0002 /* DCS_ENA_CHAN_1 */ +#define WM8993_DCS_ENA_CHAN_1_MASK 0x0002 /* DCS_ENA_CHAN_1 */ +#define WM8993_DCS_ENA_CHAN_1_SHIFT 1 /* DCS_ENA_CHAN_1 */ +#define WM8993_DCS_ENA_CHAN_1_WIDTH 1 /* DCS_ENA_CHAN_1 */ +#define WM8993_DCS_ENA_CHAN_0 0x0001 /* DCS_ENA_CHAN_0 */ +#define WM8993_DCS_ENA_CHAN_0_MASK 0x0001 /* DCS_ENA_CHAN_0 */ +#define WM8993_DCS_ENA_CHAN_0_SHIFT 0 /* DCS_ENA_CHAN_0 */ +#define WM8993_DCS_ENA_CHAN_0_WIDTH 1 /* DCS_ENA_CHAN_0 */ + +/* + * R85 (0x55) - DC Servo 1 + */ +#define WM8993_DCS_SERIES_NO_01_MASK 0x0FE0 /* DCS_SERIES_NO_01 - [11:5] */ +#define WM8993_DCS_SERIES_NO_01_SHIFT 5 /* DCS_SERIES_NO_01 - [11:5] */ +#define WM8993_DCS_SERIES_NO_01_WIDTH 7 /* DCS_SERIES_NO_01 - [11:5] */ +#define WM8993_DCS_TIMER_PERIOD_01_MASK 0x000F /* DCS_TIMER_PERIOD_01 - [3:0] */ +#define WM8993_DCS_TIMER_PERIOD_01_SHIFT 0 /* DCS_TIMER_PERIOD_01 - [3:0] */ +#define WM8993_DCS_TIMER_PERIOD_01_WIDTH 4 /* DCS_TIMER_PERIOD_01 - [3:0] */ + +/* + * R87 (0x57) - DC Servo 3 + */ +#define WM8993_DCS_DAC_WR_VAL_1_MASK 0xFF00 /* DCS_DAC_WR_VAL_1 - [15:8] */ +#define WM8993_DCS_DAC_WR_VAL_1_SHIFT 8 /* DCS_DAC_WR_VAL_1 - [15:8] */ +#define WM8993_DCS_DAC_WR_VAL_1_WIDTH 8 /* DCS_DAC_WR_VAL_1 - [15:8] */ +#define WM8993_DCS_DAC_WR_VAL_0_MASK 0x00FF /* DCS_DAC_WR_VAL_0 - [7:0] */ +#define WM8993_DCS_DAC_WR_VAL_0_SHIFT 0 /* DCS_DAC_WR_VAL_0 - [7:0] */ +#define WM8993_DCS_DAC_WR_VAL_0_WIDTH 8 /* DCS_DAC_WR_VAL_0 - [7:0] */ + +/* + * R88 (0x58) - DC Servo Readback 0 + */ +#define WM8993_DCS_DATAPATH_BUSY 0x4000 /* DCS_DATAPATH_BUSY */ +#define WM8993_DCS_DATAPATH_BUSY_MASK 0x4000 /* DCS_DATAPATH_BUSY */ +#define WM8993_DCS_DATAPATH_BUSY_SHIFT 14 /* DCS_DATAPATH_BUSY */ +#define WM8993_DCS_DATAPATH_BUSY_WIDTH 1 /* DCS_DATAPATH_BUSY */ +#define WM8993_DCS_CHANNEL_MASK 0x3000 /* DCS_CHANNEL - [13:12] */ +#define WM8993_DCS_CHANNEL_SHIFT 12 /* DCS_CHANNEL - [13:12] */ +#define WM8993_DCS_CHANNEL_WIDTH 2 /* DCS_CHANNEL - [13:12] */ +#define WM8993_DCS_CAL_COMPLETE_MASK 0x0300 /* DCS_CAL_COMPLETE - [9:8] */ +#define WM8993_DCS_CAL_COMPLETE_SHIFT 8 /* DCS_CAL_COMPLETE - [9:8] */ +#define WM8993_DCS_CAL_COMPLETE_WIDTH 2 /* DCS_CAL_COMPLETE - [9:8] */ +#define WM8993_DCS_DAC_WR_COMPLETE_MASK 0x0030 /* DCS_DAC_WR_COMPLETE - [5:4] */ +#define WM8993_DCS_DAC_WR_COMPLETE_SHIFT 4 /* DCS_DAC_WR_COMPLETE - [5:4] */ +#define WM8993_DCS_DAC_WR_COMPLETE_WIDTH 2 /* DCS_DAC_WR_COMPLETE - [5:4] */ +#define WM8993_DCS_STARTUP_COMPLETE_MASK 0x0003 /* DCS_STARTUP_COMPLETE - [1:0] */ +#define WM8993_DCS_STARTUP_COMPLETE_SHIFT 0 /* DCS_STARTUP_COMPLETE - [1:0] */ +#define WM8993_DCS_STARTUP_COMPLETE_WIDTH 2 /* DCS_STARTUP_COMPLETE - [1:0] */ + +/* + * R89 (0x59) - DC Servo Readback 1 + */ +#define WM8993_DCS_INTEG_CHAN_1_MASK 0x00FF /* DCS_INTEG_CHAN_1 - [7:0] */ +#define WM8993_DCS_INTEG_CHAN_1_SHIFT 0 /* DCS_INTEG_CHAN_1 - [7:0] */ +#define WM8993_DCS_INTEG_CHAN_1_WIDTH 8 /* DCS_INTEG_CHAN_1 - [7:0] */ + +/* + * R90 (0x5A) - DC Servo Readback 2 + */ +#define WM8993_DCS_INTEG_CHAN_0_MASK 0x00FF /* DCS_INTEG_CHAN_0 - [7:0] */ +#define WM8993_DCS_INTEG_CHAN_0_SHIFT 0 /* DCS_INTEG_CHAN_0 - [7:0] */ +#define WM8993_DCS_INTEG_CHAN_0_WIDTH 8 /* DCS_INTEG_CHAN_0 - [7:0] */ + +/* + * R96 (0x60) - Analogue HP 0 + */ +#define WM8993_HPOUT1_AUTO_PU 0x0100 /* HPOUT1_AUTO_PU */ +#define WM8993_HPOUT1_AUTO_PU_MASK 0x0100 /* HPOUT1_AUTO_PU */ +#define WM8993_HPOUT1_AUTO_PU_SHIFT 8 /* HPOUT1_AUTO_PU */ +#define WM8993_HPOUT1_AUTO_PU_WIDTH 1 /* HPOUT1_AUTO_PU */ +#define WM8993_HPOUT1L_RMV_SHORT 0x0080 /* HPOUT1L_RMV_SHORT */ +#define WM8993_HPOUT1L_RMV_SHORT_MASK 0x0080 /* HPOUT1L_RMV_SHORT */ +#define WM8993_HPOUT1L_RMV_SHORT_SHIFT 7 /* HPOUT1L_RMV_SHORT */ +#define WM8993_HPOUT1L_RMV_SHORT_WIDTH 1 /* HPOUT1L_RMV_SHORT */ +#define WM8993_HPOUT1L_OUTP 0x0040 /* HPOUT1L_OUTP */ +#define WM8993_HPOUT1L_OUTP_MASK 0x0040 /* HPOUT1L_OUTP */ +#define WM8993_HPOUT1L_OUTP_SHIFT 6 /* HPOUT1L_OUTP */ +#define WM8993_HPOUT1L_OUTP_WIDTH 1 /* HPOUT1L_OUTP */ +#define WM8993_HPOUT1L_DLY 0x0020 /* HPOUT1L_DLY */ +#define WM8993_HPOUT1L_DLY_MASK 0x0020 /* HPOUT1L_DLY */ +#define WM8993_HPOUT1L_DLY_SHIFT 5 /* HPOUT1L_DLY */ +#define WM8993_HPOUT1L_DLY_WIDTH 1 /* HPOUT1L_DLY */ +#define WM8993_HPOUT1R_RMV_SHORT 0x0008 /* HPOUT1R_RMV_SHORT */ +#define WM8993_HPOUT1R_RMV_SHORT_MASK 0x0008 /* HPOUT1R_RMV_SHORT */ +#define WM8993_HPOUT1R_RMV_SHORT_SHIFT 3 /* HPOUT1R_RMV_SHORT */ +#define WM8993_HPOUT1R_RMV_SHORT_WIDTH 1 /* HPOUT1R_RMV_SHORT */ +#define WM8993_HPOUT1R_OUTP 0x0004 /* HPOUT1R_OUTP */ +#define WM8993_HPOUT1R_OUTP_MASK 0x0004 /* HPOUT1R_OUTP */ +#define WM8993_HPOUT1R_OUTP_SHIFT 2 /* HPOUT1R_OUTP */ +#define WM8993_HPOUT1R_OUTP_WIDTH 1 /* HPOUT1R_OUTP */ +#define WM8993_HPOUT1R_DLY 0x0002 /* HPOUT1R_DLY */ +#define WM8993_HPOUT1R_DLY_MASK 0x0002 /* HPOUT1R_DLY */ +#define WM8993_HPOUT1R_DLY_SHIFT 1 /* HPOUT1R_DLY */ +#define WM8993_HPOUT1R_DLY_WIDTH 1 /* HPOUT1R_DLY */ + +/* + * R98 (0x62) - EQ1 + */ +#define WM8993_EQ_ENA 0x0001 /* EQ_ENA */ +#define WM8993_EQ_ENA_MASK 0x0001 /* EQ_ENA */ +#define WM8993_EQ_ENA_SHIFT 0 /* EQ_ENA */ +#define WM8993_EQ_ENA_WIDTH 1 /* EQ_ENA */ + +/* + * R99 (0x63) - EQ2 + */ +#define WM8993_EQ_B1_GAIN_MASK 0x001F /* EQ_B1_GAIN - [4:0] */ +#define WM8993_EQ_B1_GAIN_SHIFT 0 /* EQ_B1_GAIN - [4:0] */ +#define WM8993_EQ_B1_GAIN_WIDTH 5 /* EQ_B1_GAIN - [4:0] */ + +/* + * R100 (0x64) - EQ3 + */ +#define WM8993_EQ_B2_GAIN_MASK 0x001F /* EQ_B2_GAIN - [4:0] */ +#define WM8993_EQ_B2_GAIN_SHIFT 0 /* EQ_B2_GAIN - [4:0] */ +#define WM8993_EQ_B2_GAIN_WIDTH 5 /* EQ_B2_GAIN - [4:0] */ + +/* + * R101 (0x65) - EQ4 + */ +#define WM8993_EQ_B3_GAIN_MASK 0x001F /* EQ_B3_GAIN - [4:0] */ +#define WM8993_EQ_B3_GAIN_SHIFT 0 /* EQ_B3_GAIN - [4:0] */ +#define WM8993_EQ_B3_GAIN_WIDTH 5 /* EQ_B3_GAIN - [4:0] */ + +/* + * R102 (0x66) - EQ5 + */ +#define WM8993_EQ_B4_GAIN_MASK 0x001F /* EQ_B4_GAIN - [4:0] */ +#define WM8993_EQ_B4_GAIN_SHIFT 0 /* EQ_B4_GAIN - [4:0] */ +#define WM8993_EQ_B4_GAIN_WIDTH 5 /* EQ_B4_GAIN - [4:0] */ + +/* + * R103 (0x67) - EQ6 + */ +#define WM8993_EQ_B5_GAIN_MASK 0x001F /* EQ_B5_GAIN - [4:0] */ +#define WM8993_EQ_B5_GAIN_SHIFT 0 /* EQ_B5_GAIN - [4:0] */ +#define WM8993_EQ_B5_GAIN_WIDTH 5 /* EQ_B5_GAIN - [4:0] */ + +/* + * R104 (0x68) - EQ7 + */ +#define WM8993_EQ_B1_A_MASK 0xFFFF /* EQ_B1_A - [15:0] */ +#define WM8993_EQ_B1_A_SHIFT 0 /* EQ_B1_A - [15:0] */ +#define WM8993_EQ_B1_A_WIDTH 16 /* EQ_B1_A - [15:0] */ + +/* + * R105 (0x69) - EQ8 + */ +#define WM8993_EQ_B1_B_MASK 0xFFFF /* EQ_B1_B - [15:0] */ +#define WM8993_EQ_B1_B_SHIFT 0 /* EQ_B1_B - [15:0] */ +#define WM8993_EQ_B1_B_WIDTH 16 /* EQ_B1_B - [15:0] */ + +/* + * R106 (0x6A) - EQ9 + */ +#define WM8993_EQ_B1_PG_MASK 0xFFFF /* EQ_B1_PG - [15:0] */ +#define WM8993_EQ_B1_PG_SHIFT 0 /* EQ_B1_PG - [15:0] */ +#define WM8993_EQ_B1_PG_WIDTH 16 /* EQ_B1_PG - [15:0] */ + +/* + * R107 (0x6B) - EQ10 + */ +#define WM8993_EQ_B2_A_MASK 0xFFFF /* EQ_B2_A - [15:0] */ +#define WM8993_EQ_B2_A_SHIFT 0 /* EQ_B2_A - [15:0] */ +#define WM8993_EQ_B2_A_WIDTH 16 /* EQ_B2_A - [15:0] */ + +/* + * R108 (0x6C) - EQ11 + */ +#define WM8993_EQ_B2_B_MASK 0xFFFF /* EQ_B2_B - [15:0] */ +#define WM8993_EQ_B2_B_SHIFT 0 /* EQ_B2_B - [15:0] */ +#define WM8993_EQ_B2_B_WIDTH 16 /* EQ_B2_B - [15:0] */ + +/* + * R109 (0x6D) - EQ12 + */ +#define WM8993_EQ_B2_C_MASK 0xFFFF /* EQ_B2_C - [15:0] */ +#define WM8993_EQ_B2_C_SHIFT 0 /* EQ_B2_C - [15:0] */ +#define WM8993_EQ_B2_C_WIDTH 16 /* EQ_B2_C - [15:0] */ + +/* + * R110 (0x6E) - EQ13 + */ +#define WM8993_EQ_B2_PG_MASK 0xFFFF /* EQ_B2_PG - [15:0] */ +#define WM8993_EQ_B2_PG_SHIFT 0 /* EQ_B2_PG - [15:0] */ +#define WM8993_EQ_B2_PG_WIDTH 16 /* EQ_B2_PG - [15:0] */ + +/* + * R111 (0x6F) - EQ14 + */ +#define WM8993_EQ_B3_A_MASK 0xFFFF /* EQ_B3_A - [15:0] */ +#define WM8993_EQ_B3_A_SHIFT 0 /* EQ_B3_A - [15:0] */ +#define WM8993_EQ_B3_A_WIDTH 16 /* EQ_B3_A - [15:0] */ + +/* + * R112 (0x70) - EQ15 + */ +#define WM8993_EQ_B3_B_MASK 0xFFFF /* EQ_B3_B - [15:0] */ +#define WM8993_EQ_B3_B_SHIFT 0 /* EQ_B3_B - [15:0] */ +#define WM8993_EQ_B3_B_WIDTH 16 /* EQ_B3_B - [15:0] */ + +/* + * R113 (0x71) - EQ16 + */ +#define WM8993_EQ_B3_C_MASK 0xFFFF /* EQ_B3_C - [15:0] */ +#define WM8993_EQ_B3_C_SHIFT 0 /* EQ_B3_C - [15:0] */ +#define WM8993_EQ_B3_C_WIDTH 16 /* EQ_B3_C - [15:0] */ + +/* + * R114 (0x72) - EQ17 + */ +#define WM8993_EQ_B3_PG_MASK 0xFFFF /* EQ_B3_PG - [15:0] */ +#define WM8993_EQ_B3_PG_SHIFT 0 /* EQ_B3_PG - [15:0] */ +#define WM8993_EQ_B3_PG_WIDTH 16 /* EQ_B3_PG - [15:0] */ + +/* + * R115 (0x73) - EQ18 + */ +#define WM8993_EQ_B4_A_MASK 0xFFFF /* EQ_B4_A - [15:0] */ +#define WM8993_EQ_B4_A_SHIFT 0 /* EQ_B4_A - [15:0] */ +#define WM8993_EQ_B4_A_WIDTH 16 /* EQ_B4_A - [15:0] */ + +/* + * R116 (0x74) - EQ19 + */ +#define WM8993_EQ_B4_B_MASK 0xFFFF /* EQ_B4_B - [15:0] */ +#define WM8993_EQ_B4_B_SHIFT 0 /* EQ_B4_B - [15:0] */ +#define WM8993_EQ_B4_B_WIDTH 16 /* EQ_B4_B - [15:0] */ + +/* + * R117 (0x75) - EQ20 + */ +#define WM8993_EQ_B4_C_MASK 0xFFFF /* EQ_B4_C - [15:0] */ +#define WM8993_EQ_B4_C_SHIFT 0 /* EQ_B4_C - [15:0] */ +#define WM8993_EQ_B4_C_WIDTH 16 /* EQ_B4_C - [15:0] */ + +/* + * R118 (0x76) - EQ21 + */ +#define WM8993_EQ_B4_PG_MASK 0xFFFF /* EQ_B4_PG - [15:0] */ +#define WM8993_EQ_B4_PG_SHIFT 0 /* EQ_B4_PG - [15:0] */ +#define WM8993_EQ_B4_PG_WIDTH 16 /* EQ_B4_PG - [15:0] */ + +/* + * R119 (0x77) - EQ22 + */ +#define WM8993_EQ_B5_A_MASK 0xFFFF /* EQ_B5_A - [15:0] */ +#define WM8993_EQ_B5_A_SHIFT 0 /* EQ_B5_A - [15:0] */ +#define WM8993_EQ_B5_A_WIDTH 16 /* EQ_B5_A - [15:0] */ + +/* + * R120 (0x78) - EQ23 + */ +#define WM8993_EQ_B5_B_MASK 0xFFFF /* EQ_B5_B - [15:0] */ +#define WM8993_EQ_B5_B_SHIFT 0 /* EQ_B5_B - [15:0] */ +#define WM8993_EQ_B5_B_WIDTH 16 /* EQ_B5_B - [15:0] */ + +/* + * R121 (0x79) - EQ24 + */ +#define WM8993_EQ_B5_PG_MASK 0xFFFF /* EQ_B5_PG - [15:0] */ +#define WM8993_EQ_B5_PG_SHIFT 0 /* EQ_B5_PG - [15:0] */ +#define WM8993_EQ_B5_PG_WIDTH 16 /* EQ_B5_PG - [15:0] */ + +/* + * R122 (0x7A) - Digital Pulls + */ +#define WM8993_MCLK_PU 0x0080 /* MCLK_PU */ +#define WM8993_MCLK_PU_MASK 0x0080 /* MCLK_PU */ +#define WM8993_MCLK_PU_SHIFT 7 /* MCLK_PU */ +#define WM8993_MCLK_PU_WIDTH 1 /* MCLK_PU */ +#define WM8993_MCLK_PD 0x0040 /* MCLK_PD */ +#define WM8993_MCLK_PD_MASK 0x0040 /* MCLK_PD */ +#define WM8993_MCLK_PD_SHIFT 6 /* MCLK_PD */ +#define WM8993_MCLK_PD_WIDTH 1 /* MCLK_PD */ +#define WM8993_DACDAT_PU 0x0020 /* DACDAT_PU */ +#define WM8993_DACDAT_PU_MASK 0x0020 /* DACDAT_PU */ +#define WM8993_DACDAT_PU_SHIFT 5 /* DACDAT_PU */ +#define WM8993_DACDAT_PU_WIDTH 1 /* DACDAT_PU */ +#define WM8993_DACDAT_PD 0x0010 /* DACDAT_PD */ +#define WM8993_DACDAT_PD_MASK 0x0010 /* DACDAT_PD */ +#define WM8993_DACDAT_PD_SHIFT 4 /* DACDAT_PD */ +#define WM8993_DACDAT_PD_WIDTH 1 /* DACDAT_PD */ +#define WM8993_LRCLK_PU 0x0008 /* LRCLK_PU */ +#define WM8993_LRCLK_PU_MASK 0x0008 /* LRCLK_PU */ +#define WM8993_LRCLK_PU_SHIFT 3 /* LRCLK_PU */ +#define WM8993_LRCLK_PU_WIDTH 1 /* LRCLK_PU */ +#define WM8993_LRCLK_PD 0x0004 /* LRCLK_PD */ +#define WM8993_LRCLK_PD_MASK 0x0004 /* LRCLK_PD */ +#define WM8993_LRCLK_PD_SHIFT 2 /* LRCLK_PD */ +#define WM8993_LRCLK_PD_WIDTH 1 /* LRCLK_PD */ +#define WM8993_BCLK_PU 0x0002 /* BCLK_PU */ +#define WM8993_BCLK_PU_MASK 0x0002 /* BCLK_PU */ +#define WM8993_BCLK_PU_SHIFT 1 /* BCLK_PU */ +#define WM8993_BCLK_PU_WIDTH 1 /* BCLK_PU */ +#define WM8993_BCLK_PD 0x0001 /* BCLK_PD */ +#define WM8993_BCLK_PD_MASK 0x0001 /* BCLK_PD */ +#define WM8993_BCLK_PD_SHIFT 0 /* BCLK_PD */ +#define WM8993_BCLK_PD_WIDTH 1 /* BCLK_PD */ + +/* + * R123 (0x7B) - DRC Control 1 + */ +#define WM8993_DRC_ENA 0x8000 /* DRC_ENA */ +#define WM8993_DRC_ENA_MASK 0x8000 /* DRC_ENA */ +#define WM8993_DRC_ENA_SHIFT 15 /* DRC_ENA */ +#define WM8993_DRC_ENA_WIDTH 1 /* DRC_ENA */ +#define WM8993_DRC_DAC_PATH 0x4000 /* DRC_DAC_PATH */ +#define WM8993_DRC_DAC_PATH_MASK 0x4000 /* DRC_DAC_PATH */ +#define WM8993_DRC_DAC_PATH_SHIFT 14 /* DRC_DAC_PATH */ +#define WM8993_DRC_DAC_PATH_WIDTH 1 /* DRC_DAC_PATH */ +#define WM8993_DRC_SMOOTH_ENA 0x0800 /* DRC_SMOOTH_ENA */ +#define WM8993_DRC_SMOOTH_ENA_MASK 0x0800 /* DRC_SMOOTH_ENA */ +#define WM8993_DRC_SMOOTH_ENA_SHIFT 11 /* DRC_SMOOTH_ENA */ +#define WM8993_DRC_SMOOTH_ENA_WIDTH 1 /* DRC_SMOOTH_ENA */ +#define WM8993_DRC_QR_ENA 0x0400 /* DRC_QR_ENA */ +#define WM8993_DRC_QR_ENA_MASK 0x0400 /* DRC_QR_ENA */ +#define WM8993_DRC_QR_ENA_SHIFT 10 /* DRC_QR_ENA */ +#define WM8993_DRC_QR_ENA_WIDTH 1 /* DRC_QR_ENA */ +#define WM8993_DRC_ANTICLIP_ENA 0x0200 /* DRC_ANTICLIP_ENA */ +#define WM8993_DRC_ANTICLIP_ENA_MASK 0x0200 /* DRC_ANTICLIP_ENA */ +#define WM8993_DRC_ANTICLIP_ENA_SHIFT 9 /* DRC_ANTICLIP_ENA */ +#define WM8993_DRC_ANTICLIP_ENA_WIDTH 1 /* DRC_ANTICLIP_ENA */ +#define WM8993_DRC_HYST_ENA 0x0100 /* DRC_HYST_ENA */ +#define WM8993_DRC_HYST_ENA_MASK 0x0100 /* DRC_HYST_ENA */ +#define WM8993_DRC_HYST_ENA_SHIFT 8 /* DRC_HYST_ENA */ +#define WM8993_DRC_HYST_ENA_WIDTH 1 /* DRC_HYST_ENA */ +#define WM8993_DRC_THRESH_HYST_MASK 0x0030 /* DRC_THRESH_HYST - [5:4] */ +#define WM8993_DRC_THRESH_HYST_SHIFT 4 /* DRC_THRESH_HYST - [5:4] */ +#define WM8993_DRC_THRESH_HYST_WIDTH 2 /* DRC_THRESH_HYST - [5:4] */ +#define WM8993_DRC_MINGAIN_MASK 0x000C /* DRC_MINGAIN - [3:2] */ +#define WM8993_DRC_MINGAIN_SHIFT 2 /* DRC_MINGAIN - [3:2] */ +#define WM8993_DRC_MINGAIN_WIDTH 2 /* DRC_MINGAIN - [3:2] */ +#define WM8993_DRC_MAXGAIN_MASK 0x0003 /* DRC_MAXGAIN - [1:0] */ +#define WM8993_DRC_MAXGAIN_SHIFT 0 /* DRC_MAXGAIN - [1:0] */ +#define WM8993_DRC_MAXGAIN_WIDTH 2 /* DRC_MAXGAIN - [1:0] */ + +/* + * R124 (0x7C) - DRC Control 2 + */ +#define WM8993_DRC_ATTACK_RATE_MASK 0xF000 /* DRC_ATTACK_RATE - [15:12] */ +#define WM8993_DRC_ATTACK_RATE_SHIFT 12 /* DRC_ATTACK_RATE - [15:12] */ +#define WM8993_DRC_ATTACK_RATE_WIDTH 4 /* DRC_ATTACK_RATE - [15:12] */ +#define WM8993_DRC_DECAY_RATE_MASK 0x0F00 /* DRC_DECAY_RATE - [11:8] */ +#define WM8993_DRC_DECAY_RATE_SHIFT 8 /* DRC_DECAY_RATE - [11:8] */ +#define WM8993_DRC_DECAY_RATE_WIDTH 4 /* DRC_DECAY_RATE - [11:8] */ +#define WM8993_DRC_THRESH_COMP_MASK 0x00FC /* DRC_THRESH_COMP - [7:2] */ +#define WM8993_DRC_THRESH_COMP_SHIFT 2 /* DRC_THRESH_COMP - [7:2] */ +#define WM8993_DRC_THRESH_COMP_WIDTH 6 /* DRC_THRESH_COMP - [7:2] */ + +/* + * R125 (0x7D) - DRC Control 3 + */ +#define WM8993_DRC_AMP_COMP_MASK 0xF800 /* DRC_AMP_COMP - [15:11] */ +#define WM8993_DRC_AMP_COMP_SHIFT 11 /* DRC_AMP_COMP - [15:11] */ +#define WM8993_DRC_AMP_COMP_WIDTH 5 /* DRC_AMP_COMP - [15:11] */ +#define WM8993_DRC_R0_SLOPE_COMP_MASK 0x0700 /* DRC_R0_SLOPE_COMP - [10:8] */ +#define WM8993_DRC_R0_SLOPE_COMP_SHIFT 8 /* DRC_R0_SLOPE_COMP - [10:8] */ +#define WM8993_DRC_R0_SLOPE_COMP_WIDTH 3 /* DRC_R0_SLOPE_COMP - [10:8] */ +#define WM8993_DRC_FF_DELAY 0x0080 /* DRC_FF_DELAY */ +#define WM8993_DRC_FF_DELAY_MASK 0x0080 /* DRC_FF_DELAY */ +#define WM8993_DRC_FF_DELAY_SHIFT 7 /* DRC_FF_DELAY */ +#define WM8993_DRC_FF_DELAY_WIDTH 1 /* DRC_FF_DELAY */ +#define WM8993_DRC_THRESH_QR_MASK 0x000C /* DRC_THRESH_QR - [3:2] */ +#define WM8993_DRC_THRESH_QR_SHIFT 2 /* DRC_THRESH_QR - [3:2] */ +#define WM8993_DRC_THRESH_QR_WIDTH 2 /* DRC_THRESH_QR - [3:2] */ +#define WM8993_DRC_RATE_QR_MASK 0x0003 /* DRC_RATE_QR - [1:0] */ +#define WM8993_DRC_RATE_QR_SHIFT 0 /* DRC_RATE_QR - [1:0] */ +#define WM8993_DRC_RATE_QR_WIDTH 2 /* DRC_RATE_QR - [1:0] */ + +/* + * R126 (0x7E) - DRC Control 4 + */ +#define WM8993_DRC_R1_SLOPE_COMP_MASK 0xE000 /* DRC_R1_SLOPE_COMP - [15:13] */ +#define WM8993_DRC_R1_SLOPE_COMP_SHIFT 13 /* DRC_R1_SLOPE_COMP - [15:13] */ +#define WM8993_DRC_R1_SLOPE_COMP_WIDTH 3 /* DRC_R1_SLOPE_COMP - [15:13] */ +#define WM8993_DRC_STARTUP_GAIN_MASK 0x1F00 /* DRC_STARTUP_GAIN - [12:8] */ +#define WM8993_DRC_STARTUP_GAIN_SHIFT 8 /* DRC_STARTUP_GAIN - [12:8] */ +#define WM8993_DRC_STARTUP_GAIN_WIDTH 5 /* DRC_STARTUP_GAIN - [12:8] */ + +#endif -- cgit v1.2.3 From 4934482d93ca42d57180e08bf3ee6b77124f5f8e Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 8 Jul 2009 17:48:12 +0100 Subject: ASoC: Limit WM8731 to symmetric rates While the hardware is capable of some limited asynmmetric modes the driver does not currently support those modes so tell applications that only symmetric rates are available. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8731.c | 1 + 1 file changed, 1 insertion(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index de43263a6fa..15600285207 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -464,6 +464,7 @@ struct snd_soc_dai wm8731_dai = { .rates = WM8731_RATES, .formats = WM8731_FORMATS,}, .ops = &wm8731_dai_ops, + .symmetric_rates = 1, }; EXPORT_SYMBOL_GPL(wm8731_dai); -- cgit v1.2.3 From 2a01e5f3587be459c934d711bffddc14d66386f3 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 8 Jul 2009 18:05:51 +0100 Subject: ASoC: Use CODEC as clock master on AT91SAM9G20-EK This simplifies the driver by removing the need to manually configure dividers within the CPU and improve audio performance by ensuring that the optimal phase relationships between the clocks in the system are maintained. Note that currently this means that for playback to work the Output Mixer HiFi switch must be enabled since otherwise CODEC will not generate the DAC clock. Signed-off-by: Mark Brown --- sound/soc/atmel/sam9g20_wm8731.c | 89 +--------------------------------------- 1 file changed, 2 insertions(+), 87 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/atmel/sam9g20_wm8731.c b/sound/soc/atmel/sam9g20_wm8731.c index 173a239a541..9abab94fe2c 100644 --- a/sound/soc/atmel/sam9g20_wm8731.c +++ b/sound/soc/atmel/sam9g20_wm8731.c @@ -87,102 +87,17 @@ static int at91sam9g20ek_hw_params(struct snd_pcm_substream *substream, 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; - struct atmel_ssc_info *ssc_p = cpu_dai->private_data; - struct ssc_device *ssc = ssc_p->ssc; int ret; - unsigned int rate; - int cmr_div, period; - - if (ssc == NULL) { - printk(KERN_INFO "at91sam9g20ek_hw_params: ssc is NULL!\n"); - return -EINVAL; - } - /* set codec DAI configuration */ ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S | - SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); + SND_SOC_DAIFMT_NB_NF | 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_I2S | - SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBS_CFS); - if (ret < 0) - return ret; - - /* - * The SSC clock dividers depend on the sample rate. The CMR.DIV - * field divides the system master clock MCK to drive the SSC TK - * signal which provides the codec BCLK. The TCMR.PERIOD and - * RCMR.PERIOD fields further divide the BCLK signal to drive - * the SSC TF and RF signals which provide the codec DACLRC and - * ADCLRC clocks. - * - * The dividers were determined through trial and error, where a - * CMR.DIV value is chosen such that the resulting BCLK value is - * divisible, or almost divisible, by (2 * sample rate), and then - * the TCMR.PERIOD or RCMR.PERIOD is BCLK / (2 * sample rate) - 1. - */ - rate = params_rate(params); - - switch (rate) { - case 8000: - cmr_div = 55; /* BCLK = 133MHz/(2*55) = 1.209MHz */ - period = 74; /* LRC = BCLK/(2*(74+1)) ~= 8060,6Hz */ - break; - case 11025: - cmr_div = 67; /* BCLK = 133MHz/(2*60) = 1.108MHz */ - period = 45; /* LRC = BCLK/(2*(49+1)) = 11083,3Hz */ - break; - case 16000: - cmr_div = 63; /* BCLK = 133MHz/(2*63) = 1.055MHz */ - period = 32; /* LRC = BCLK/(2*(32+1)) = 15993,2Hz */ - break; - case 22050: - cmr_div = 52; /* BCLK = 133MHz/(2*52) = 1.278MHz */ - period = 28; /* LRC = BCLK/(2*(28+1)) = 22049Hz */ - break; - case 32000: - cmr_div = 66; /* BCLK = 133MHz/(2*66) = 1.007MHz */ - period = 15; /* LRC = BCLK/(2*(15+1)) = 31486,742Hz */ - break; - case 44100: - cmr_div = 29; /* BCLK = 133MHz/(2*29) = 2.293MHz */ - period = 25; /* LRC = BCLK/(2*(25+1)) = 44098Hz */ - break; - case 48000: - cmr_div = 33; /* BCLK = 133MHz/(2*33) = 2.015MHz */ - period = 20; /* LRC = BCLK/(2*(20+1)) = 47979,79Hz */ - break; - case 88200: - cmr_div = 29; /* BCLK = 133MHz/(2*29) = 2.293MHz */ - period = 12; /* LRC = BCLK/(2*(12+1)) = 88196Hz */ - break; - case 96000: - cmr_div = 23; /* BCLK = 133MHz/(2*23) = 2.891MHz */ - period = 14; /* LRC = BCLK/(2*(14+1)) = 96376Hz */ - break; - default: - printk(KERN_WARNING "unsupported rate %d" - " on at91sam9g20ek board\n", rate); - return -EINVAL; - } - - /* set the MCK divider for BCLK */ - ret = snd_soc_dai_set_clkdiv(cpu_dai, ATMEL_SSC_CMR_DIV, cmr_div); - if (ret < 0) - return ret; - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - /* set the BCLK divider for DACLRC */ - ret = snd_soc_dai_set_clkdiv(cpu_dai, - ATMEL_SSC_TCMR_PERIOD, period); - } else { - /* set the BCLK divider for ADCLRC */ - ret = snd_soc_dai_set_clkdiv(cpu_dai, - ATMEL_SSC_RCMR_PERIOD, period); - } + SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM); if (ret < 0) return ret; -- cgit v1.2.3 From 22df8eb4fe293d67c98732e02a2dbef0d9c6cd96 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 8 Jul 2009 18:18:19 +0100 Subject: ASoC: Disable microphone input for AT91SAM9G20-EK by default As shipped the board does not have inputs but it is relatively straightforward to modify the board to hook them up so support is provided in the driver. When these modifications have not been made enabling the microphone stage can cause problems. Add an ifdef to disable this by default. Don't put it into Kconfig since users will have to get their soldering irons out to change things. Signed-off-by: Mark Brown --- sound/soc/atmel/sam9g20_wm8731.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/atmel/sam9g20_wm8731.c b/sound/soc/atmel/sam9g20_wm8731.c index 9abab94fe2c..ba93df79f26 100644 --- a/sound/soc/atmel/sam9g20_wm8731.c +++ b/sound/soc/atmel/sam9g20_wm8731.c @@ -56,6 +56,13 @@ #define MCLK_RATE 12000000 +/* + * As shipped the board does not have inputs. However, it is relatively + * straightforward to modify the board to hook them up so support is left + * in the driver. + */ +#undef ENABLE_MIC_INPUT + static struct clk *mclk; static int at91sam9g20ek_startup(struct snd_pcm_substream *substream) @@ -170,8 +177,13 @@ static int at91sam9g20ek_wm8731_init(struct snd_soc_codec *codec) snd_soc_dapm_nc_pin(codec, "RLINEIN"); snd_soc_dapm_nc_pin(codec, "LLINEIN"); - /* always connected */ +#ifdef ENABLE_MIC_INPUT snd_soc_dapm_enable_pin(codec, "Int Mic"); +#else + snd_soc_dapm_nc_pin(codec, "Int Mic"); +#endif + + /* always connected */ snd_soc_dapm_enable_pin(codec, "Ext Spk"); snd_soc_dapm_sync(codec); -- cgit v1.2.3 From 96fd6d471b2c953df5379a356be04f8c8f8b4bdf Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 8 Jul 2009 18:26:16 +0100 Subject: ASoC: Configure WM8731 SYSCLK at startup on AT91SAM9G20-EK The system clock is currently fixed by the driver and this avoids the need for us to handle errors with enabling and disabling MCLK (which was incorrect previously so this fixes bugs in error handling). Signed-off-by: Mark Brown --- sound/soc/atmel/sam9g20_wm8731.c | 35 ++++++++++------------------------- 1 file changed, 10 insertions(+), 25 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/atmel/sam9g20_wm8731.c b/sound/soc/atmel/sam9g20_wm8731.c index ba93df79f26..130b12118d4 100644 --- a/sound/soc/atmel/sam9g20_wm8731.c +++ b/sound/soc/atmel/sam9g20_wm8731.c @@ -65,29 +65,6 @@ static struct clk *mclk; -static int at91sam9g20ek_startup(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); - struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; - int ret; - - ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK, - MCLK_RATE, SND_SOC_CLOCK_IN); - if (ret < 0) { - clk_disable(mclk); - return ret; - } - - return 0; -} - -static void at91sam9g20ek_shutdown(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = snd_pcm_substream_chip(substream); - - dev_dbg(rtd->socdev->dev, "shutdown"); -} - static int at91sam9g20ek_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params) { @@ -112,9 +89,7 @@ static int at91sam9g20ek_hw_params(struct snd_pcm_substream *substream, } static struct snd_soc_ops at91sam9g20ek_ops = { - .startup = at91sam9g20ek_startup, .hw_params = at91sam9g20ek_hw_params, - .shutdown = at91sam9g20ek_shutdown, }; static int at91sam9g20ek_set_bias_level(struct snd_soc_card *card, @@ -163,10 +138,20 @@ static const struct snd_soc_dapm_route intercon[] = { */ static int at91sam9g20ek_wm8731_init(struct snd_soc_codec *codec) { + struct snd_soc_dai *codec_dai = &codec->dai[0]; + int ret; + printk(KERN_DEBUG "at91sam9g20ek_wm8731 " ": at91sam9g20ek_wm8731_init() called\n"); + ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK, + MCLK_RATE, SND_SOC_CLOCK_IN); + if (ret < 0) { + printk(KERN_ERR "Failed to set WM8731 SYSCLK: %d\n", ret); + return ret; + } + /* Add specific widgets */ snd_soc_dapm_new_controls(codec, at91sam9g20ek_dapm_widgets, ARRAY_SIZE(at91sam9g20ek_dapm_widgets)); -- cgit v1.2.3 From cb507e7e79f3c0e45ecaefad9220c34fcfbe74e8 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 8 Jul 2009 18:54:57 +0100 Subject: ASoC: Add pop delay debug at end of DAPM sequencing Provide an interval after the end of DAPM sequencing so that we can distinguish between a pop in the final step of the sequence and a pop generated from some other source outside DAPM. Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 5157ec110cf..a40594f5ad6 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -997,6 +997,9 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) pr_err("Failed to apply active bias: %d\n", ret); } + pop_dbg(codec->pop_time, "DAPM sequencing finished, waiting %dms\n", + codec->pop_time); + return 0; } -- cgit v1.2.3 From cc369cf504c51d36765e18b0c28a09a92d28ed79 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 9 Jul 2009 11:28:07 +0100 Subject: ASoC: WM8510 has a single frame clock so needs symmetric rates Signed-off-by: Mark Brown --- sound/soc/codecs/wm8510.c | 1 + 1 file changed, 1 insertion(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c index c8b8dba8589..261d4cb7596 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -577,6 +577,7 @@ struct snd_soc_dai wm8510_dai = { .rates = WM8510_RATES, .formats = WM8510_FORMATS,}, .ops = &wm8510_dai_ops, + .symmetric_rates = 1, }; EXPORT_SYMBOL_GPL(wm8510_dai); -- cgit v1.2.3 From 33e319fba727708ee45fec70091c498da4e8f606 Mon Sep 17 00:00:00 2001 From: Roel Kluin Date: Mon, 13 Jul 2009 02:20:10 +0200 Subject: ASoC: Keep index within stac9766_reg[] Keep index within stac9766_reg[] Signed-off-by: Roel Kluin Signed-off-by: Mark Brown --- sound/soc/codecs/stac9766.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/stac9766.c b/sound/soc/codecs/stac9766.c index 8ad4b7b3e3b..befc6488c39 100644 --- a/sound/soc/codecs/stac9766.c +++ b/sound/soc/codecs/stac9766.c @@ -149,7 +149,7 @@ static int stac9766_ac97_write(struct snd_soc_codec *codec, unsigned int reg, stac9766_ac97_write(codec, AC97_INT_PAGING, 1); return 0; } - if (reg / 2 > ARRAY_SIZE(stac9766_reg)) + if (reg / 2 >= ARRAY_SIZE(stac9766_reg)) return -EIO; soc_ac97_ops.write(codec->ac97, reg, val); @@ -168,7 +168,7 @@ static unsigned int stac9766_ac97_read(struct snd_soc_codec *codec, stac9766_ac97_write(codec, AC97_INT_PAGING, 1); return val; } - if (reg / 2 > ARRAY_SIZE(stac9766_reg)) + if (reg / 2 >= ARRAY_SIZE(stac9766_reg)) return -EIO; if (reg == AC97_RESET || reg == AC97_GPIO_STATUS || -- cgit v1.2.3 From 087d53ab11d538ce57b8a8e0ef899d7ee6a38548 Mon Sep 17 00:00:00 2001 From: Rongrong Cao Date: Fri, 10 Jul 2009 20:13:30 +0100 Subject: ASoC: fix checking for external widgets bug In SOC DAPM layer of SOUND subsystem, when add signal route (in the function snd_soc_dapm_add_route() ), the original code has wrong logic when dapm layer check each widget whether an external one. Signed-off-by: Rongrong Cao Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index a40594f5ad6..c68c204a48a 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -1293,8 +1293,8 @@ static int snd_soc_dapm_add_route(struct snd_soc_codec *codec, if (wsink->id == snd_soc_dapm_input) { if (wsource->id == snd_soc_dapm_micbias || wsource->id == snd_soc_dapm_mic || - wsink->id == snd_soc_dapm_line || - wsink->id == snd_soc_dapm_output) + wsource->id == snd_soc_dapm_line || + wsource->id == snd_soc_dapm_output) wsink->ext = 1; } if (wsource->id == snd_soc_dapm_output) { -- cgit v1.2.3 From a27e304b5c0bd7a3d6f337fd107a0a93d8687821 Mon Sep 17 00:00:00 2001 From: Kevin Hilman Date: Mon, 13 Jul 2009 14:50:39 -0700 Subject: ASoC: spdif codec: enable use by modules Signed-off-by: Mark Brown --- sound/soc/codecs/spdif_transciever.c | 1 + 1 file changed, 1 insertion(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/spdif_transciever.c b/sound/soc/codecs/spdif_transciever.c index 218b33adad9..ca9e9979a60 100644 --- a/sound/soc/codecs/spdif_transciever.c +++ b/sound/soc/codecs/spdif_transciever.c @@ -34,6 +34,7 @@ struct snd_soc_dai dit_stub_dai = { .formats = STUB_FORMATS, }, }; +EXPORT_SYMBOL_GPL(dit_stub_dai); static int spdif_dit_probe(struct platform_device *pdev) { -- cgit v1.2.3 From 0a0cf58d93b49bdd3ba6049a5536e76c32ef7f88 Mon Sep 17 00:00:00 2001 From: Kevin Hilman Date: Mon, 13 Jul 2009 14:50:40 -0700 Subject: ASoC: spdif: set module licence to GPL Without MODULE_LICENCE("GPL"), when built as a module it will fail to load because it uses other GPL symbols from kernel. Signed-off-by: Kevin Hilman Signed-off-by: Mark Brown --- sound/soc/codecs/spdif_transciever.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/spdif_transciever.c b/sound/soc/codecs/spdif_transciever.c index ca9e9979a60..a6319114105 100644 --- a/sound/soc/codecs/spdif_transciever.c +++ b/sound/soc/codecs/spdif_transciever.c @@ -21,6 +21,8 @@ #include "spdif_transciever.h" +MODULE_LICENSE("GPL"); + #define STUB_RATES SNDRV_PCM_RATE_8000_96000 #define STUB_FORMATS SNDRV_PCM_FMTBIT_S16_LE -- cgit v1.2.3 From 18d02bc32c0b35726b2a6a3699a0c44e5e8f6c94 Mon Sep 17 00:00:00 2001 From: Cliff Cai Date: Tue, 14 Jul 2009 10:01:39 -0400 Subject: ASoC: Blackfin AC97: fix resume handling There is no need to manually start playback/capture ourselves as the PCM driver will handle things for us. Signed-off-by: Cliff Cai Signed-off-by: Mike Frysinger Signed-off-by: Mark Brown --- sound/soc/blackfin/bf5xx-ac97.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/blackfin/bf5xx-ac97.c b/sound/soc/blackfin/bf5xx-ac97.c index b1ed423fabd..2758b9017a7 100644 --- a/sound/soc/blackfin/bf5xx-ac97.c +++ b/sound/soc/blackfin/bf5xx-ac97.c @@ -277,28 +277,24 @@ static int bf5xx_ac97_resume(struct snd_soc_dai *dai) if (!dai->active) return 0; - ret = sport_set_multichannel(sport_handle, 16, 0x1F, 1); + ret = sport_set_multichannel(sport, 16, 0x1F, 1); if (ret) { pr_err("SPORT is busy!\n"); return -EBUSY; } - ret = sport_config_rx(sport_handle, IRFS, 0xF, 0, (16*16-1)); + ret = sport_config_rx(sport, IRFS, 0xF, 0, (16*16-1)); if (ret) { pr_err("SPORT is busy!\n"); return -EBUSY; } - ret = sport_config_tx(sport_handle, ITFS, 0xF, 0, (16*16-1)); + ret = sport_config_tx(sport, ITFS, 0xF, 0, (16*16-1)); if (ret) { pr_err("SPORT is busy!\n"); return -EBUSY; } - if (dai->capture.active) - sport_rx_start(sport); - if (dai->playback.active) - sport_tx_start(sport); return 0; } -- cgit v1.2.3 From 82d76f4d9fbf7d951d63e7056fd9911ada507bd1 Mon Sep 17 00:00:00 2001 From: Cliff Cai Date: Tue, 14 Jul 2009 10:01:40 -0400 Subject: ASoC: Blackfin I2S: fix resume handling There is no need to manually start playback/capture ourselves as the PCM driver will handle things for us. Signed-off-by: Cliff Cai Signed-off-by: Mike Frysinger Signed-off-by: Mark Brown --- sound/soc/blackfin/bf5xx-i2s.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/blackfin/bf5xx-i2s.c b/sound/soc/blackfin/bf5xx-i2s.c index af06904bab0..876abade27e 100644 --- a/sound/soc/blackfin/bf5xx-i2s.c +++ b/sound/soc/blackfin/bf5xx-i2s.c @@ -259,22 +259,18 @@ static int bf5xx_i2s_resume(struct snd_soc_dai *dai) if (!dai->active) return 0; - ret = sport_config_rx(sport_handle, RFSR | RCKFE, RSFSE|0x1f, 0, 0); + ret = sport_config_rx(sport, RFSR | RCKFE, RSFSE|0x1f, 0, 0); if (ret) { pr_err("SPORT is busy!\n"); return -EBUSY; } - ret = sport_config_tx(sport_handle, TFSR | TCKFE, TSFSE|0x1f, 0, 0); + ret = sport_config_tx(sport, TFSR | TCKFE, TSFSE|0x1f, 0, 0); if (ret) { pr_err("SPORT is busy!\n"); return -EBUSY; } - if (dai->capture.active) - sport_rx_start(sport); - if (dai->playback.active) - sport_tx_start(sport); return 0; } -- cgit v1.2.3 From e465d544fab81aa122f86a50418f4be7f1fcf066 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 15 Jul 2009 10:01:30 +0100 Subject: ASoC: Fix sample rate lookup in WM8993 We need to use the best value we picked, not the last value we looked at. Reported-by: Stephen Rothwell Signed-off-by: Mark Brown --- sound/soc/codecs/wm8993.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c index 3e5c65a1ea8..f9c49b3e559 100644 --- a/sound/soc/codecs/wm8993.c +++ b/sound/soc/codecs/wm8993.c @@ -1796,7 +1796,8 @@ static int wm8993_hw_params(struct snd_pcm_substream *substream, } dev_dbg(codec->dev, "Selected SAMPLE_RATE of %dHz\n", sample_rates[best].rate); - clocking3 |= (sample_rates[i].sample_rate << WM8993_SAMPLE_RATE_SHIFT); + clocking3 |= (sample_rates[best].sample_rate + << WM8993_SAMPLE_RATE_SHIFT); /* BCLK_DIV */ best = 0; -- cgit v1.2.3 From 4b75e9476737dffef44a66818a4a481edd2512a1 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 15 Jul 2009 10:36:51 +0100 Subject: ASoC: Error out if we can't determine a suitable WM9081 sysclk Due to the flexibility of the WM9081 FLL this should never happen in a real system. Reported-by: Jaswinder Singh Rajput Signed-off-by: Mark Brown --- sound/soc/codecs/wm9081.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c index dbe20597d87..156f2a4a593 100644 --- a/sound/soc/codecs/wm9081.c +++ b/sound/soc/codecs/wm9081.c @@ -707,6 +707,10 @@ static int configure_clock(struct snd_soc_codec *codec) target > 3000000) break; } + + if (i == ARRAY_SIZE(clk_sys_rates)) + return -EINVAL; + } else if (wm9081->fs) { for (i = 0; i < ARRAY_SIZE(clk_sys_rates); i++) { new_sysclk = clk_sys_rates[i].ratio @@ -714,6 +718,10 @@ static int configure_clock(struct snd_soc_codec *codec) if (new_sysclk > 3000000) break; } + + if (i == ARRAY_SIZE(clk_sys_rates)) + return -EINVAL; + } else { new_sysclk = 12288000; } -- cgit v1.2.3 From 9db9ed977d4f1a317f5f4d467d43025fa27223d8 Mon Sep 17 00:00:00 2001 From: Joonyoung Shim Date: Wed, 15 Jul 2009 20:34:00 +0900 Subject: ASoC: MAX9877: add MAX9877 amp driver The MAX9877 combines a high-efficiency Class D audio power amplifier with a stereo Class AB capacitor-less DirectDrive headphone amplifier. The max9877_add_controls() is called to register the MAX9877 specific controls on machine specific init() of the machine driver. The datasheet for the MAX9877 can find at the following url: http://datasheets.maxim-ic.com/en/ds/MAX9877.pdf [Slight edit to sort the ALL_CODECS entries -- broonie.] Signed-off-by: Joonyoung Shim Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 5 + sound/soc/codecs/Makefile | 6 + sound/soc/codecs/max9877.c | 270 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/max9877.h | 37 +++++++ 4 files changed, 318 insertions(+) create mode 100644 sound/soc/codecs/max9877.c create mode 100644 sound/soc/codecs/max9877.h (limited to 'sound/soc') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index b674d3a9f78..4219a41d386 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -17,6 +17,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_AK4104 if SPI_MASTER select SND_SOC_AK4535 if I2C select SND_SOC_CS4270 if I2C + select SND_SOC_MAX9877 if I2C select SND_SOC_PCM3008 select SND_SOC_SPDIF select SND_SOC_SSM2602 if I2C @@ -188,3 +189,7 @@ config SND_SOC_WM9712 config SND_SOC_WM9713 tristate + +# Amp +config SND_SOC_MAX9877 + tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 301eb08ea5c..cf111c97822 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -38,6 +38,9 @@ snd-soc-wm9705-objs := wm9705.o snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o +# Amp +snd-soc-max9877-objs := max9877.o + obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o @@ -77,3 +80,6 @@ obj-$(CONFIG_SND_SOC_WM9081) += snd-soc-wm9081.o obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o + +# Amp +obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o diff --git a/sound/soc/codecs/max9877.c b/sound/soc/codecs/max9877.c new file mode 100644 index 00000000000..7df9a7c0208 --- /dev/null +++ b/sound/soc/codecs/max9877.c @@ -0,0 +1,270 @@ +/* + * max9877.c -- amp driver for max9877 + * + * Copyright (C) 2009 Samsung Electronics Co.Ltd + * Author: Joonyoung Shim + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include +#include +#include +#include +#include + +#include "max9877.h" + +static struct i2c_client *i2c; + +static u8 max9877_regs[5] = { 0x40, 0x00, 0x00, 0x00, 0x49 }; + +static void max9877_write_regs(void) +{ + if (i2c_master_send(i2c, max9877_regs, 5) != 5) + dev_err(&i2c->dev, "i2c write failed\n"); +} + +static int max9877_get_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int reg = mc->reg; + int reg2 = mc->rreg; + int shift = mc->shift; + int mask = mc->max; + + ucontrol->value.integer.value[0] = (max9877_regs[reg] >> shift) & mask; + + if (reg2) + ucontrol->value.integer.value[1] = + (max9877_regs[reg2] >> shift) & mask; + + return 0; +} + +static int max9877_set_reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int reg = mc->reg; + int reg2 = mc->rreg; + int shift = mc->shift; + int mask = mc->max; + int change = 1; + int change2 = 1; + int ret = 0; + + if (((max9877_regs[reg] >> shift) & mask) == + ucontrol->value.integer.value[0]) + change = 0; + + if (reg2) + if (((max9877_regs[reg2] >> shift) & mask) == + ucontrol->value.integer.value[1]) + change2 = 0; + + if (change) { + max9877_regs[reg] &= ~(mask << shift); + max9877_regs[reg] |= ucontrol->value.integer.value[0] << shift; + ret = change; + } + + if (reg2 && change2) { + max9877_regs[reg2] &= ~(mask << shift); + max9877_regs[reg2] |= ucontrol->value.integer.value[1] << shift; + ret = change2; + } + + if (ret) + max9877_write_regs(); + + return ret; +} + +static int max9877_get_out_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 value = max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OUTMODE_MASK; + + if (value) + value -= 1; + + ucontrol->value.integer.value[0] = value; + return 0; +} + +static int max9877_set_out_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 value = ucontrol->value.integer.value[0]; + + if (value) + value += 1; + + if ((max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OUTMODE_MASK) == value) + return 0; + + max9877_regs[MAX9877_OUTPUT_MODE] |= value; + max9877_write_regs(); + return 1; +} + +static int max9877_get_osc_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 value = (max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OSC_MASK); + + value = value >> MAX9877_OSC_OFFSET; + + ucontrol->value.integer.value[0] = value; + return 0; +} + +static int max9877_set_osc_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + u8 value = ucontrol->value.integer.value[0]; + + value = value << MAX9877_OSC_OFFSET; + if ((max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OSC_MASK) == value) + return 0; + + max9877_regs[MAX9877_OUTPUT_MODE] |= value; + max9877_write_regs(); + return 1; +} + +static const unsigned int max9877_pgain_tlv[] = { + TLV_DB_RANGE_HEAD(2), + 0, 1, TLV_DB_SCALE_ITEM(0, 900, 0), + 2, 2, TLV_DB_SCALE_ITEM(2000, 0, 0), +}; + +static const unsigned int max9877_output_tlv[] = { + TLV_DB_RANGE_HEAD(4), + 0, 7, TLV_DB_SCALE_ITEM(-7900, 400, 1), + 8, 15, TLV_DB_SCALE_ITEM(-4700, 300, 0), + 16, 23, TLV_DB_SCALE_ITEM(-2300, 200, 0), + 24, 31, TLV_DB_SCALE_ITEM(-700, 100, 0), +}; + +static const char *max9877_out_mode[] = { + "INA -> SPK", + "INA -> HP", + "INA -> SPK and HP", + "INB -> SPK", + "INB -> HP", + "INB -> SPK and HP", + "INA + INB -> SPK", + "INA + INB -> HP", + "INA + INB -> SPK and HP", +}; + +static const char *max9877_osc_mode[] = { + "1176KHz", + "1100KHz", + "700KHz", +}; + +static const struct soc_enum max9877_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max9877_out_mode), max9877_out_mode), + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(max9877_osc_mode), max9877_osc_mode), +}; + +static const struct snd_kcontrol_new max9877_controls[] = { + SOC_SINGLE_EXT_TLV("MAX9877 PGAINA Playback Volume", + MAX9877_INPUT_MODE, 0, 2, 0, + max9877_get_reg, max9877_set_reg, max9877_pgain_tlv), + SOC_SINGLE_EXT_TLV("MAX9877 PGAINB Playback Volume", + MAX9877_INPUT_MODE, 2, 2, 0, + max9877_get_reg, max9877_set_reg, max9877_pgain_tlv), + SOC_SINGLE_EXT_TLV("MAX9877 Amp Speaker Playback Volume", + MAX9877_SPK_VOLUME, 0, 31, 0, + max9877_get_reg, max9877_set_reg, max9877_output_tlv), + SOC_DOUBLE_R_EXT_TLV("MAX9877 Amp HP Playback Volume", + MAX9877_HPL_VOLUME, MAX9877_HPR_VOLUME, 0, 31, 0, + max9877_get_reg, max9877_set_reg, max9877_output_tlv), + SOC_SINGLE_EXT("MAX9877 INB Stereo Switch", + MAX9877_INPUT_MODE, 4, 1, 1, + max9877_get_reg, max9877_set_reg), + SOC_SINGLE_EXT("MAX9877 INA Stereo Switch", + MAX9877_INPUT_MODE, 5, 1, 1, + max9877_get_reg, max9877_set_reg), + SOC_SINGLE_EXT("MAX9877 Zero-crossing detection Switch", + MAX9877_INPUT_MODE, 6, 1, 0, + max9877_get_reg, max9877_set_reg), + SOC_SINGLE_EXT("MAX9877 Bypass Mode Switch", + MAX9877_OUTPUT_MODE, 6, 1, 0, + max9877_get_reg, max9877_set_reg), + SOC_SINGLE_EXT("MAX9877 Shutdown Mode Switch", + MAX9877_OUTPUT_MODE, 7, 1, 1, + max9877_get_reg, max9877_set_reg), + SOC_ENUM_EXT("MAX9877 Output Mode", max9877_enum[0], + max9877_get_out_mode, max9877_set_out_mode), + SOC_ENUM_EXT("MAX9877 Oscillator Mode", max9877_enum[1], + max9877_get_osc_mode, max9877_set_osc_mode), +}; + +/* This function is called from ASoC machine driver */ +int max9877_add_controls(struct snd_soc_codec *codec) +{ + return snd_soc_add_controls(codec, max9877_controls, + ARRAY_SIZE(max9877_controls)); +} +EXPORT_SYMBOL_GPL(max9877_add_controls); + +static int __devinit max9877_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + i2c = client; + + max9877_write_regs(); + + return 0; +} + +static __devexit int max9877_i2c_remove(struct i2c_client *client) +{ + i2c = NULL; + + return 0; +} + +static const struct i2c_device_id max9877_i2c_id[] = { + { "max9877", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, max9877_i2c_id); + +static struct i2c_driver max9877_i2c_driver = { + .driver = { + .name = "max9877", + .owner = THIS_MODULE, + }, + .probe = max9877_i2c_probe, + .remove = __devexit_p(max9877_i2c_remove), + .id_table = max9877_i2c_id, +}; + +static int __init max9877_init(void) +{ + return i2c_add_driver(&max9877_i2c_driver); +} +module_init(max9877_init); + +static void __exit max9877_exit(void) +{ + i2c_del_driver(&max9877_i2c_driver); +} +module_exit(max9877_exit); + +MODULE_DESCRIPTION("ASoC MAX9877 amp driver"); +MODULE_AUTHOR("Joonyoung Shim "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/max9877.h b/sound/soc/codecs/max9877.h new file mode 100644 index 00000000000..6da72290ac5 --- /dev/null +++ b/sound/soc/codecs/max9877.h @@ -0,0 +1,37 @@ +/* + * max9877.h -- amp driver for max9877 + * + * Copyright (C) 2009 Samsung Electronics Co.Ltd + * Author: Joonyoung Shim + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _MAX9877_H +#define _MAX9877_H + +#define MAX9877_INPUT_MODE 0x00 +#define MAX9877_SPK_VOLUME 0x01 +#define MAX9877_HPL_VOLUME 0x02 +#define MAX9877_HPR_VOLUME 0x03 +#define MAX9877_OUTPUT_MODE 0x04 + +/* MAX9877_INPUT_MODE */ +#define MAX9877_INB (1 << 4) +#define MAX9877_INA (1 << 5) +#define MAX9877_ZCD (1 << 6) + +/* MAX9877_OUTPUT_MODE */ +#define MAX9877_OUTMODE_MASK (15 << 0) +#define MAX9877_OSC_MASK (3 << 4) +#define MAX9877_OSC_OFFSET 4 +#define MAX9877_BYPASS (1 << 6) +#define MAX9877_SHDN (1 << 7) + +extern int max9877_add_controls(struct snd_soc_codec *codec); + +#endif -- cgit v1.2.3 From 3e46a447396df99e2367fe1564651abaacc19c13 Mon Sep 17 00:00:00 2001 From: Kevin Hilman Date: Wed, 15 Jul 2009 10:42:09 -0700 Subject: ASoC: davinci: don't use clock names clock name strings are no longer passed on platform_data. Instead, we rely entirely on struct device and clkdev to find the right clock. Signed-off-by: Kevin Hilman Signed-off-by: Mark Brown --- sound/soc/davinci/davinci-i2s.c | 2 +- sound/soc/davinci/davinci-mcasp.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c index f7330e112be..e5cd97b74c5 100644 --- a/sound/soc/davinci/davinci-i2s.c +++ b/sound/soc/davinci/davinci-i2s.c @@ -528,7 +528,7 @@ static int davinci_i2s_probe(struct platform_device *pdev) goto err_release_region; } - dev->clk = clk_get(&pdev->dev, pdata->clk_name); + dev->clk = clk_get(&pdev->dev, NULL); if (IS_ERR(dev->clk)) { ret = -ENODEV; goto err_free_mem; diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c index b27aab60ece..f0c03477106 100644 --- a/sound/soc/davinci/davinci-mcasp.c +++ b/sound/soc/davinci/davinci-mcasp.c @@ -764,7 +764,7 @@ static int davinci_mcasp_probe(struct platform_device *pdev) } pdata = pdev->dev.platform_data; - dev->clk = clk_get(&pdev->dev, pdata->clk_name); + dev->clk = clk_get(&pdev->dev, NULL); if (IS_ERR(dev->clk)) { ret = -ENODEV; goto err_release_region; -- cgit v1.2.3 From 1274738d85d0e25c4f82d83f50a6bcbe2397e9ea Mon Sep 17 00:00:00 2001 From: Barry Song <21cnbao@gmail.com> Date: Thu, 16 Jul 2009 16:00:05 +0800 Subject: ASoC: new ad1938 codec driver based on asoc Signed-off-by: Barry Song <21cnbao@gmail.com> Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/ad1938.c | 652 ++++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/ad1938.h | 100 +++++++ 4 files changed, 758 insertions(+) create mode 100644 sound/soc/codecs/ad1938.c create mode 100644 sound/soc/codecs/ad1938.h (limited to 'sound/soc') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 4219a41d386..57f5b73f1bd 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -12,6 +12,7 @@ config SND_SOC_ALL_CODECS tristate "Build all ASoC CODEC drivers" select SND_SOC_L3 select SND_SOC_AC97_CODEC if SND_SOC_AC97_BUS + select SND_SOC_AD1938 if SPI_MASTER select SND_SOC_AD1980 if SND_SOC_AC97_BUS select SND_SOC_AD73311 if I2C select SND_SOC_AK4104 if SPI_MASTER @@ -66,6 +67,9 @@ config SND_SOC_AC97_CODEC tristate select SND_AC97_CODEC +config SND_SOC_AD1938 + tristate + config SND_SOC_AD1980 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index cf111c97822..9a92f553916 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -1,4 +1,5 @@ snd-soc-ac97-objs := ac97.o +snd-soc-ad1938-objs := ad1938.o snd-soc-ad1980-objs := ad1980.o snd-soc-ad73311-objs := ad73311.o snd-soc-ak4104-objs := ak4104.o @@ -42,6 +43,7 @@ snd-soc-wm9713-objs := wm9713.o snd-soc-max9877-objs := max9877.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o +obj-$(CONFIG_SND_SOC_AD1938) += snd-soc-ad1938.o obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o diff --git a/sound/soc/codecs/ad1938.c b/sound/soc/codecs/ad1938.c new file mode 100644 index 00000000000..3dc80910318 --- /dev/null +++ b/sound/soc/codecs/ad1938.c @@ -0,0 +1,652 @@ +/* + * File: sound/soc/codecs/ad1938.c + * Author: Barry Song + * + * Created: June 04 2009 + * Description: Driver for AD1938 sound chip + * + * Modified: + * Copyright 2009 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ad1938.h" + +/* codec private data */ +struct ad1938_priv { + struct snd_soc_codec codec; + u8 reg_cache[AD1938_NUM_REGS]; + +}; + +static struct snd_soc_codec *ad1938_codec; +struct snd_soc_codec_device soc_codec_dev_ad1938; +static int ad1938_register(struct ad1938_priv *ad1938); +static void ad1938_unregister(struct ad1938_priv *ad1938); + +/* + * AD1938 volume/mute/de-emphasis etc. controls + */ +static const char *ad1938_deemp[] = {"None", "48kHz", "44.1kHz", "32kHz"}; + +static const struct soc_enum ad1938_deemp_enum = + SOC_ENUM_SINGLE(AD1938_DAC_CTRL2, 1, 4, ad1938_deemp); + +static const struct snd_kcontrol_new ad1938_snd_controls[] = { + /* DAC volume control */ + SOC_DOUBLE_R("DAC1 Volume", AD1938_DAC_L1_VOL, + AD1938_DAC_R1_VOL, 0, 0xFF, 1), + SOC_DOUBLE_R("DAC2 Volume", AD1938_DAC_L2_VOL, + AD1938_DAC_R2_VOL, 0, 0xFF, 1), + SOC_DOUBLE_R("DAC3 Volume", AD1938_DAC_L3_VOL, + AD1938_DAC_R3_VOL, 0, 0xFF, 1), + SOC_DOUBLE_R("DAC4 Volume", AD1938_DAC_L4_VOL, + AD1938_DAC_R4_VOL, 0, 0xFF, 1), + + /* ADC switch control */ + SOC_DOUBLE("ADC1 Switch", AD1938_ADC_CTRL0, AD1938_ADCL1_MUTE, AD1938_ADCR1_MUTE, 1, 1), + SOC_DOUBLE("ADC2 Switch", AD1938_ADC_CTRL0, AD1938_ADCL2_MUTE, AD1938_ADCR2_MUTE, 1, 1), + + /* DAC switch control */ + SOC_DOUBLE("DAC1 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL1_MUTE, AD1938_DACR1_MUTE, 1, 1), + SOC_DOUBLE("DAC2 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL2_MUTE, AD1938_DACR2_MUTE, 1, 1), + SOC_DOUBLE("DAC3 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL3_MUTE, AD1938_DACR3_MUTE, 1, 1), + SOC_DOUBLE("DAC4 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL4_MUTE, AD1938_DACR4_MUTE, 1, 1), + + /* ADC high-pass filter */ + SOC_SINGLE("ADC High Pass Filter Switch", AD1938_ADC_CTRL0, + AD1938_ADC_HIGHPASS_FILTER, 1, 0), + + /* DAC de-emphasis */ + SOC_ENUM("Playback Deemphasis", ad1938_deemp_enum), +}; + +static const struct snd_soc_dapm_widget ad1938_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", "Playback", AD1938_DAC_CTRL0, 0, 1), + SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_SUPPLY("ADC_PWR", AD1938_ADC_CTRL0, 0, 1, NULL, 0), +}; + +static const struct snd_soc_dapm_route audio_paths[] = { + { "DAC", NULL, "ADC_PWR" }, + { "ADC", NULL, "ADC_PWR" }, +}; + +/* + * DAI ops entries + */ + +static int ad1938_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + int reg; + + reg = codec->read(codec, AD1938_DAC_CTRL2); + reg = (mute > 0) ? reg | AD1938_DAC_MASTER_MUTE : reg & (~AD1938_DAC_MASTER_MUTE); + codec->write(codec, AD1938_DAC_CTRL2, reg); + + return 0; +} + +static inline int ad1938_pll_powerctrl(struct snd_soc_codec *codec, int cmd) +{ + int reg = codec->read(codec, AD1938_PLL_CLK_CTRL0); + reg = (cmd > 0) ? reg & (~AD1938_PLL_POWERDOWN) : reg | AD1938_PLL_POWERDOWN; + codec->write(codec, AD1938_PLL_CLK_CTRL0, reg); + + return 0; +} + +static int ad1938_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int mask, int slots) +{ + struct snd_soc_codec *codec = dai->codec; + int dac_reg = codec->read(codec, AD1938_DAC_CTRL1); + int adc_reg = codec->read(codec, AD1938_ADC_CTRL2); + + dac_reg &= ~AD1938_DAC_CHAN_MASK; + adc_reg &= ~AD1938_ADC_CHAN_MASK; + + switch(slots) { + case 2: + dac_reg |= AD1938_DAC_2_CHANNELS << AD1938_DAC_CHAN_SHFT; + adc_reg |= AD1938_ADC_2_CHANNELS << AD1938_ADC_CHAN_SHFT; + break; + case 4: + dac_reg |= AD1938_DAC_4_CHANNELS << AD1938_DAC_CHAN_SHFT; + adc_reg |= AD1938_ADC_4_CHANNELS << AD1938_ADC_CHAN_SHFT; + break; + case 8: + dac_reg |= AD1938_DAC_8_CHANNELS << AD1938_DAC_CHAN_SHFT; + adc_reg |= AD1938_ADC_8_CHANNELS << AD1938_ADC_CHAN_SHFT; + break; + case 16: + dac_reg |= AD1938_DAC_16_CHANNELS << AD1938_DAC_CHAN_SHFT; + adc_reg |= AD1938_ADC_16_CHANNELS << AD1938_ADC_CHAN_SHFT; + break; + default: + return -EINVAL; + } + + codec->write(codec, AD1938_DAC_CTRL1, dac_reg); + codec->write(codec, AD1938_ADC_CTRL2, adc_reg); + + return 0; +} + +static int ad1938_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + int adc_reg, dac_reg; + + adc_reg = codec->read(codec, AD1938_ADC_CTRL2); + dac_reg = codec->read(codec, AD1938_DAC_CTRL1); + + /* At present, the driver only support AUX ADC mode(SND_SOC_DAIFMT_I2S with TDM) + * and ADC&DAC TDM mode(SND_SOC_DAIFMT_DSP_A) + */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + adc_reg &= ~AD1938_ADC_SERFMT_MASK; + adc_reg |= AD1938_ADC_SERFMT_TDM; + break; + case SND_SOC_DAIFMT_DSP_A: + adc_reg &= ~AD1938_ADC_SERFMT_MASK; + adc_reg |= AD1938_ADC_SERFMT_AUX; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: /* normal bit clock + frame */ + adc_reg &= ~AD1938_ADC_LEFT_HIGH; + adc_reg &= ~AD1938_ADC_BCLK_INV; + dac_reg &= ~AD1938_DAC_LEFT_HIGH; + dac_reg &= ~AD1938_DAC_BCLK_INV; + break; + case SND_SOC_DAIFMT_NB_IF: /* normal bclk + invert frm */ + adc_reg |= AD1938_ADC_LEFT_HIGH; + adc_reg &= ~AD1938_ADC_BCLK_INV; + dac_reg |= AD1938_DAC_LEFT_HIGH; + dac_reg &= ~AD1938_DAC_BCLK_INV; + break; + case SND_SOC_DAIFMT_IB_NF: /* invert bclk + normal frm */ + adc_reg &= ~AD1938_ADC_LEFT_HIGH; + adc_reg |= AD1938_ADC_BCLK_INV; + dac_reg &= ~AD1938_DAC_LEFT_HIGH; + dac_reg |= AD1938_DAC_BCLK_INV; + break; + + case SND_SOC_DAIFMT_IB_IF: /* invert bclk + frm */ + adc_reg |= AD1938_ADC_LEFT_HIGH; + adc_reg |= AD1938_ADC_BCLK_INV; + dac_reg |= AD1938_DAC_LEFT_HIGH; + dac_reg |= AD1938_DAC_BCLK_INV; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: /* codec clk & frm master */ + adc_reg |= AD1938_ADC_LCR_MASTER; + adc_reg |= AD1938_ADC_BCLK_MASTER; + dac_reg |= AD1938_DAC_LCR_MASTER; + dac_reg |= AD1938_DAC_BCLK_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFM: /* codec clk slave & frm master */ + adc_reg |= AD1938_ADC_LCR_MASTER; + adc_reg &= ~AD1938_ADC_BCLK_MASTER; + dac_reg |= AD1938_DAC_LCR_MASTER; + dac_reg &= ~AD1938_DAC_BCLK_MASTER; + break; + case SND_SOC_DAIFMT_CBM_CFS: /* codec clk master & frame slave */ + adc_reg &= ~AD1938_ADC_LCR_MASTER; + adc_reg |= AD1938_ADC_BCLK_MASTER; + dac_reg &= ~AD1938_DAC_LCR_MASTER; + dac_reg |= AD1938_DAC_BCLK_MASTER; + break; + case SND_SOC_DAIFMT_CBS_CFS: /* codec clk & frm slave */ + adc_reg &= ~AD1938_ADC_LCR_MASTER; + adc_reg &= ~AD1938_ADC_BCLK_MASTER; + dac_reg &= ~AD1938_DAC_LCR_MASTER; + dac_reg &= ~AD1938_DAC_BCLK_MASTER; + break; + default: + return -EINVAL; + } + + codec->write(codec, AD1938_ADC_CTRL2, adc_reg); + codec->write(codec, AD1938_DAC_CTRL1, dac_reg); + + return 0; +} + +static int ad1938_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int word_len = 0, reg = 0; + + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + word_len = 3; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + word_len = 1; + break; + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S32_LE: + word_len = 0; + break; + } + + reg = codec->read(codec, AD1938_DAC_CTRL2); + reg = (reg & (~AD1938_DAC_WORD_LEN_MASK)) | word_len; + codec->write(codec, AD1938_DAC_CTRL2, reg); + + reg = codec->read(codec, AD1938_ADC_CTRL1); + reg = (reg & (~AD1938_ADC_WORD_LEN_MASK)) | word_len; + codec->write(codec, AD1938_ADC_CTRL1, reg); + + return 0; +} + +static int ad1938_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + ad1938_pll_powerctrl(codec, 1); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + case SND_SOC_BIAS_OFF: + ad1938_pll_powerctrl(codec, 0); + break; + } + codec->bias_level = level; + return 0; +} + +/* + * interface to read/write ad1938 register + */ + +#define AD1938_SPI_ADDR 0x4 +#define AD1938_SPI_READ 0x1 +#define AD1938_SPI_BUFLEN 3 + +/* + * write to the ad1938 register space + */ + +static int ad1938_write_reg(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 *reg_cache = codec->reg_cache; + int ret = 0; + + if(value != reg_cache[reg]) { + uint8_t buf[AD1938_SPI_BUFLEN]; + struct spi_transfer t = { + .tx_buf = buf, + .len = AD1938_SPI_BUFLEN, + }; + struct spi_message m; + + buf[0] = AD1938_SPI_ADDR << 1; + buf[1] = reg; + buf[2] = value; + spi_message_init(&m); + spi_message_add_tail(&t, &m); + if((ret = spi_sync(codec->control_data, &m)) == 0) + reg_cache[reg] = value; + } + + return ret; +} + +/* + * read from the ad1938 register space cache + */ + +static unsigned int ad1938_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 *reg_cache = codec->reg_cache; + + if (reg >= codec->reg_cache_size) + return -EINVAL; + + return reg_cache[reg]; +} + +/* + * read from the ad1938 register space + */ + +static unsigned int ad1938_read_reg(struct snd_soc_codec *codec, unsigned int reg) +{ + char w_buf[AD1938_SPI_BUFLEN]; + char r_buf[AD1938_SPI_BUFLEN]; + int ret; + + struct spi_transfer t = { + .tx_buf = w_buf, + .rx_buf = r_buf, + .len = AD1938_SPI_BUFLEN, + }; + struct spi_message m; + + w_buf[0] = (AD1938_SPI_ADDR << 1) | AD1938_SPI_READ; + w_buf[1] = reg; + w_buf[2] = 0; + + spi_message_init(&m); + spi_message_add_tail(&t, &m); + ret = spi_sync(codec->control_data, &m); + if (ret == 0) + return r_buf[2]; + else + return -EIO; +} + +static int ad1938_fill_cache(struct snd_soc_codec *codec) +{ + int i; + u8 *reg_cache = codec->reg_cache; + struct spi_device *spi = codec->control_data; + + for (i = 0; i < codec->reg_cache_size; i++) { + int ret = ad1938_read_reg(codec, i); + if (ret == -EIO) { + dev_err(&spi->dev, "AD1938 SPI read failure\n"); + return ret; + } + reg_cache[i] = ret; + } + + return 0; +} + +static int __devinit ad1938_spi_probe(struct spi_device *spi) +{ + struct snd_soc_codec *codec; + struct ad1938_priv *ad1938; + + ad1938 = kzalloc(sizeof(struct ad1938_priv), GFP_KERNEL); + if (ad1938 == NULL) + return -ENOMEM; + + codec = &ad1938->codec; + codec->control_data = spi; + codec->dev = &spi->dev; + + spi->dev.driver_data = ad1938; + + return ad1938_register(ad1938); +} + +static int __devexit ad1938_spi_remove(struct spi_device *spi) +{ + struct ad1938_priv *ad1938 = spi->dev.driver_data; + + ad1938_unregister(ad1938); + return 0; +} + +static struct spi_driver ad1938_spi_driver = { + .driver = { + .name = "ad1938-spi", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = ad1938_spi_probe, + .remove = __devexit_p(ad1938_spi_remove), +}; + +static struct snd_soc_dai_ops ad1938_dai_ops = { + .hw_params = ad1938_hw_params, + .digital_mute = ad1938_mute, + .set_tdm_slot = ad1938_set_tdm_slot, + .set_fmt = ad1938_set_dai_fmt, +}; + +/* codec DAI instance */ +struct snd_soc_dai ad1938_dai = { + .name = "AD1938", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, + }, + .ops = &ad1938_dai_ops, +}; +EXPORT_SYMBOL_GPL(ad1938_dai); + +static int ad1938_register(struct ad1938_priv *ad1938) +{ + int ret; + struct snd_soc_codec *codec = &ad1938->codec; + + if (ad1938_codec) { + dev_err(codec->dev, "Another ad1938 is registered\n"); + return -EINVAL; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + codec->private_data = ad1938; + codec->reg_cache = ad1938->reg_cache; + codec->reg_cache_size = AD1938_NUM_REGS; + codec->name = "AD1938"; + codec->owner = THIS_MODULE; + codec->dai = &ad1938_dai; + codec->num_dai = 1; + codec->write = ad1938_write_reg; + codec->read = ad1938_read_reg_cache; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ad1938_dai.dev = codec->dev; + ad1938_codec = codec; + + /* default setting for ad1938 */ + codec->write(codec, AD1938_DAC_CHNL_MUTE, 0x0); /* unmute dac channels */ + codec->write(codec, AD1938_DAC_CTRL2, 0x1A); /* de-emphasis: 48kHz, powedown dac */ + codec->write(codec, AD1938_DAC_CTRL0, 0x21); /* powerdown dac, dac tdm mode */ + codec->write(codec, AD1938_ADC_CTRL0, 0x3); /* high-pass filter enable */ + codec->write(codec, AD1938_ADC_CTRL1, 0x43); /* sata delay=1, adc aux mode */ + codec->write(codec, AD1938_PLL_CLK_CTRL0, 0x9D); /* pll input:mclki/xi */ + codec->write(codec, AD1938_PLL_CLK_CTRL1, 0x04); + + ad1938_fill_cache(codec); + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } + + ret = snd_soc_register_dai(&ad1938_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + return ret; + } + + return 0; +} + +static void ad1938_unregister(struct ad1938_priv *ad1938) +{ + ad1938_set_bias_level(&ad1938->codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dai(&ad1938_dai); + snd_soc_unregister_codec(&ad1938->codec); + kfree(ad1938); + ad1938_codec = NULL; +} + +static int ad1938_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (ad1938_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = ad1938_codec; + codec = ad1938_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + snd_soc_add_controls(codec, ad1938_snd_controls, + ARRAY_SIZE(ad1938_snd_controls)); + snd_soc_dapm_new_controls(codec, ad1938_dapm_widgets, + ARRAY_SIZE(ad1938_dapm_widgets)); + snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); + snd_soc_dapm_new_widgets(codec); + + ad1938_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card: %d\n", ret); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + return ret; +} + +/* power down chip */ +static int ad1938_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +#ifdef CONFIG_PM +static int ad1938_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + ad1938_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int ad1938_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + if (codec->suspend_bias_level == SND_SOC_BIAS_ON) + ad1938_set_bias_level(codec, SND_SOC_BIAS_ON); + + return 0; +} +#else +#define ad1938_suspend NULL +#define ad1938_resume NULL +#endif + +struct snd_soc_codec_device soc_codec_dev_ad1938 = { + .probe = ad1938_probe, + .remove = ad1938_remove, + .suspend = ad1938_suspend, + .resume = ad1938_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ad1938); + +static int __init ad1938_init(void) +{ + int ret; + + ret = spi_register_driver(&ad1938_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register ad1938 SPI driver: %d\n", + ret); + } + + return ret; +} +module_init(ad1938_init); + +static void __exit ad1938_exit(void) +{ + spi_unregister_driver(&ad1938_spi_driver); +} +module_exit(ad1938_exit); + +MODULE_DESCRIPTION("ASoC ad1938 driver"); +MODULE_AUTHOR("Barry Song "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ad1938.h b/sound/soc/codecs/ad1938.h new file mode 100644 index 00000000000..fe3c48cd2d5 --- /dev/null +++ b/sound/soc/codecs/ad1938.h @@ -0,0 +1,100 @@ +/* + * File: sound/soc/codecs/ad1836.h + * Based on: + * Author: Barry Song + * + * Created: May 25, 2009 + * Description: definitions for AD1938 registers + * + * Modified: + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __AD1938_H__ +#define __AD1938_H__ + +#define AD1938_PLL_CLK_CTRL0 0 +#define AD1938_PLL_POWERDOWN 0x01 +#define AD1938_PLL_CLK_CTRL1 1 +#define AD1938_DAC_CTRL0 2 +#define AD1938_DAC_POWERDOWN 0x01 +#define AD1938_DAC_SERFMT_MASK 0xC0 +#define AD1938_DAC_SERFMT_STEREO (0 << 6) +#define AD1938_DAC_SERFMT_TDM (1 << 6) +#define AD1938_DAC_CTRL1 3 +#define AD1938_DAC_2_CHANNELS 0 +#define AD1938_DAC_4_CHANNELS 1 +#define AD1938_DAC_8_CHANNELS 2 +#define AD1938_DAC_16_CHANNELS 3 +#define AD1938_DAC_CHAN_SHFT 1 +#define AD1938_DAC_CHAN_MASK (3 << AD1938_DAC_CHAN_SHFT) +#define AD1938_DAC_LCR_MASTER (1 << 4) +#define AD1938_DAC_BCLK_MASTER (1 << 5) +#define AD1938_DAC_LEFT_HIGH (1 << 3) +#define AD1938_DAC_BCLK_INV (1 << 7) +#define AD1938_DAC_CTRL2 4 +#define AD1938_DAC_WORD_LEN_MASK 0xC +#define AD1938_DAC_MASTER_MUTE 1 +#define AD1938_DAC_CHNL_MUTE 5 +#define AD1938_DACL1_MUTE 0 +#define AD1938_DACR1_MUTE 1 +#define AD1938_DACL2_MUTE 2 +#define AD1938_DACR2_MUTE 3 +#define AD1938_DACL3_MUTE 4 +#define AD1938_DACR3_MUTE 5 +#define AD1938_DACL4_MUTE 6 +#define AD1938_DACR4_MUTE 7 +#define AD1938_DAC_L1_VOL 6 +#define AD1938_DAC_R1_VOL 7 +#define AD1938_DAC_L2_VOL 8 +#define AD1938_DAC_R2_VOL 9 +#define AD1938_DAC_L3_VOL 10 +#define AD1938_DAC_R3_VOL 11 +#define AD1938_DAC_L4_VOL 12 +#define AD1938_DAC_R4_VOL 13 +#define AD1938_ADC_CTRL0 14 +#define AD1938_ADC_POWERDOWN 0x01 +#define AD1938_ADC_HIGHPASS_FILTER 1 +#define AD1938_ADCL1_MUTE 2 +#define AD1938_ADCR1_MUTE 3 +#define AD1938_ADCL2_MUTE 4 +#define AD1938_ADCR2_MUTE 5 +#define AD1938_ADC_CTRL1 15 +#define AD1938_ADC_SERFMT_MASK 0x60 +#define AD1938_ADC_SERFMT_STEREO (0 << 5) +#define AD1938_ADC_SERFMT_TDM (1 << 2) +#define AD1938_ADC_SERFMT_AUX (2 << 5) +#define AD1938_ADC_WORD_LEN_MASK 0x3 +#define AD1938_ADC_CTRL2 16 +#define AD1938_ADC_2_CHANNELS 0 +#define AD1938_ADC_4_CHANNELS 1 +#define AD1938_ADC_8_CHANNELS 2 +#define AD1938_ADC_16_CHANNELS 3 +#define AD1938_ADC_CHAN_SHFT 4 +#define AD1938_ADC_CHAN_MASK (3 << AD1938_ADC_CHAN_SHFT) +#define AD1938_ADC_LCR_MASTER (1 << 3) +#define AD1938_ADC_BCLK_MASTER (1 << 6) +#define AD1938_ADC_LEFT_HIGH (1 << 2) +#define AD1938_ADC_BCLK_INV (1 << 1) + +#define AD1938_NUM_REGS 17 + +extern struct snd_soc_dai ad1938_dai; +extern struct snd_soc_codec_device soc_codec_dev_ad1938; +#endif -- cgit v1.2.3 From 416c8fe3cdcd2d46a6ca12d8ec6143c62d8569d9 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 17 Jul 2009 07:48:03 +0200 Subject: ASoC: Kill direct accesses to driver_data Replaced with dev_{get|set}_drvdata(). Signed-off-by: Takashi Iwai --- sound/soc/codecs/ad1938.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/ad1938.c b/sound/soc/codecs/ad1938.c index 3dc80910318..5a7d00cad69 100644 --- a/sound/soc/codecs/ad1938.c +++ b/sound/soc/codecs/ad1938.c @@ -420,14 +420,14 @@ static int __devinit ad1938_spi_probe(struct spi_device *spi) codec->control_data = spi; codec->dev = &spi->dev; - spi->dev.driver_data = ad1938; + dev_set_drvdata(&spi->dev, ad1938); return ad1938_register(ad1938); } static int __devexit ad1938_spi_remove(struct spi_device *spi) { - struct ad1938_priv *ad1938 = spi->dev.driver_data; + struct ad1938_priv *ad1938 = dev_get_drvdata(&spi->dev); ad1938_unregister(ad1938); return 0; -- cgit v1.2.3 From c5910a703889cf44ac1aa9405642a7d3b5bc6f24 Mon Sep 17 00:00:00 2001 From: "Candelaria Villareal, Jorge" Date: Tue, 14 Jul 2009 20:04:08 -0500 Subject: ASoC: SDP3430: Add support for EXTMUTE using TWL GPIO6 Board sdp3430 has hardware support for EXTMUTE using TWL4030 GPIO6 line, controlled by register INTBR_PMBR1. Machine driver takes care of enabling gpio line through i2c and codec driver manipulates the line during headset ramp up/down sequence. Signed-off-by: Jorge Eduardo Candelaria Signed-off-by: Mark Brown --- sound/soc/omap/sdp3430.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/omap/sdp3430.c b/sound/soc/omap/sdp3430.c index c51594d8fd1..f7e5b7488c3 100644 --- a/sound/soc/omap/sdp3430.c +++ b/sound/soc/omap/sdp3430.c @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -39,6 +40,9 @@ #include "omap-pcm.h" #include "../codecs/twl4030.h" +#define TWL4030_INTBR_PMBR1 0x0D +#define EXTMUTE(value) (value << 2) + static struct snd_soc_card snd_soc_sdp3430; static int sdp3430_hw_params(struct snd_pcm_substream *substream, @@ -280,6 +284,7 @@ static struct snd_soc_card snd_soc_sdp3430 = { static struct twl4030_setup_data twl4030_setup = { .ramp_delay_value = 3, .sysclk = 26000, + .hs_extmute = 1, }; /* Audio subsystem */ @@ -312,6 +317,10 @@ static int __init sdp3430_soc_init(void) *(unsigned int *)sdp3430_dai[0].cpu_dai->private_data = 1; /* McBSP2 */ *(unsigned int *)sdp3430_dai[1].cpu_dai->private_data = 2; /* McBSP3 */ + /* Set TWL4030 GPIO6 as EXTMUTE signal */ + twl4030_i2c_write_u8(TWL4030_MODULE_INTBR, EXTMUTE(0x02), + TWL4030_MODULE_INTBR); + ret = platform_device_add(sdp3430_snd_device); if (ret) goto err1; -- cgit v1.2.3 From 8aa2df53088f024b13cb005956bd6da94cc1fd2d Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 17 Jul 2009 21:53:49 +0100 Subject: ASoC: Bodge around GCC 4.4.0 flow analysis bug in GCC 4.4.0 GCC 4.4.0 doesn't appear to be able to spot that we don't apply any FLL configuration if the output frequency is zero. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8400.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8400.c b/sound/soc/codecs/wm8400.c index 0bf903f2756..b9ef4d91522 100644 --- a/sound/soc/codecs/wm8400.c +++ b/sound/soc/codecs/wm8400.c @@ -1022,10 +1022,15 @@ static int wm8400_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, if (freq_in == wm8400->fll_in && freq_out == wm8400->fll_out) return 0; - if (freq_out != 0) { + if (freq_out) { ret = fll_factors(wm8400, &factors, freq_in, freq_out); if (ret != 0) return ret; + } else { + /* Bodge GCC 4.4.0 uninitialised variable warning - it + * doesn't seem capable of working out that we exit if + * freq_out is 0 before any of the uses. */ + memset(&factors, 0, sizeof(factors)); } wm8400->fll_out = freq_out; @@ -1040,7 +1045,7 @@ static int wm8400_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, reg &= ~WM8400_FLL_OSC_ENA; wm8400_write(codec, WM8400_FLL_CONTROL_1, reg); - if (freq_out == 0) + if (!freq_out) return 0; reg &= ~(WM8400_FLL_REF_FREQ | WM8400_FLL_FRATIO_MASK); -- cgit v1.2.3 From 0c11f655553c84bb695fdd5ea8b19d42b41b7133 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 17 Jul 2009 22:13:01 +0100 Subject: ASoC: Fix FLL reference clock division setup in WM8993 Signed-off-by: Mark Brown --- sound/soc/codecs/wm8993.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c index f9c49b3e559..e246ca07989 100644 --- a/sound/soc/codecs/wm8993.c +++ b/sound/soc/codecs/wm8993.c @@ -345,8 +345,10 @@ static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, /* Fref must be <=13.5MHz */ div = 1; + fll_div->fll_clk_ref_div = 0; while ((Fref / div) > 13500000) { div *= 2; + fll_div->fll_clk_ref_div++; if (div > 8) { pr_err("Can't scale %dMHz input down to <=13.5MHz\n", -- cgit v1.2.3 From bca146578c863a1f37cb191995317e855a0364e1 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 18 Jul 2009 11:09:42 +0100 Subject: ASoC: Fix checkpatch issues in AD1938 Signed-off-by: Mark Brown --- sound/soc/codecs/ad1938.c | 156 +++++++++++++++++++++++----------------------- 1 file changed, 78 insertions(+), 78 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/ad1938.c b/sound/soc/codecs/ad1938.c index 5a7d00cad69..ffc97c9b797 100644 --- a/sound/soc/codecs/ad1938.c +++ b/sound/soc/codecs/ad1938.c @@ -43,9 +43,8 @@ /* codec private data */ struct ad1938_priv { - struct snd_soc_codec codec; + struct snd_soc_codec codec; u8 reg_cache[AD1938_NUM_REGS]; - }; static struct snd_soc_codec *ad1938_codec; @@ -136,7 +135,7 @@ static int ad1938_set_tdm_slot(struct snd_soc_dai *dai, dac_reg &= ~AD1938_DAC_CHAN_MASK; adc_reg &= ~AD1938_ADC_CHAN_MASK; - switch(slots) { + switch (slots) { case 2: dac_reg |= AD1938_DAC_2_CHANNELS << AD1938_DAC_CHAN_SHFT; adc_reg |= AD1938_ADC_2_CHANNELS << AD1938_ADC_CHAN_SHFT; @@ -324,7 +323,7 @@ static int ad1938_write_reg(struct snd_soc_codec *codec, unsigned int reg, u8 *reg_cache = codec->reg_cache; int ret = 0; - if(value != reg_cache[reg]) { + if (value != reg_cache[reg]) { uint8_t buf[AD1938_SPI_BUFLEN]; struct spi_transfer t = { .tx_buf = buf, @@ -337,7 +336,8 @@ static int ad1938_write_reg(struct snd_soc_codec *codec, unsigned int reg, buf[2] = value; spi_message_init(&m); spi_message_add_tail(&t, &m); - if((ret = spi_sync(codec->control_data, &m)) == 0) + ret = spi_sync(codec->control_data, &m); + if (ret == 0) reg_cache[reg] = value; } @@ -349,14 +349,14 @@ static int ad1938_write_reg(struct snd_soc_codec *codec, unsigned int reg, */ static unsigned int ad1938_read_reg_cache(struct snd_soc_codec *codec, - unsigned int reg) + unsigned int reg) { - u8 *reg_cache = codec->reg_cache; + u8 *reg_cache = codec->reg_cache; - if (reg >= codec->reg_cache_size) - return -EINVAL; + if (reg >= codec->reg_cache_size) + return -EINVAL; - return reg_cache[reg]; + return reg_cache[reg]; } /* @@ -391,20 +391,20 @@ static unsigned int ad1938_read_reg(struct snd_soc_codec *codec, unsigned int re static int ad1938_fill_cache(struct snd_soc_codec *codec) { - int i; - u8 *reg_cache = codec->reg_cache; - struct spi_device *spi = codec->control_data; - - for (i = 0; i < codec->reg_cache_size; i++) { - int ret = ad1938_read_reg(codec, i); - if (ret == -EIO) { - dev_err(&spi->dev, "AD1938 SPI read failure\n"); - return ret; - } - reg_cache[i] = ret; - } - - return 0; + int i; + u8 *reg_cache = codec->reg_cache; + struct spi_device *spi = codec->control_data; + + for (i = 0; i < codec->reg_cache_size; i++) { + int ret = ad1938_read_reg(codec, i); + if (ret == -EIO) { + dev_err(&spi->dev, "AD1938 SPI read failure\n"); + return ret; + } + reg_cache[i] = ret; + } + + return 0; } static int __devinit ad1938_spi_probe(struct spi_device *spi) @@ -498,8 +498,8 @@ static int ad1938_register(struct ad1938_priv *ad1938) INIT_LIST_HEAD(&codec->dapm_widgets); INIT_LIST_HEAD(&codec->dapm_paths); - ad1938_dai.dev = codec->dev; - ad1938_codec = codec; + ad1938_dai.dev = codec->dev; + ad1938_codec = codec; /* default setting for ad1938 */ codec->write(codec, AD1938_DAC_CHNL_MUTE, 0x0); /* unmute dac channels */ @@ -512,85 +512,85 @@ static int ad1938_register(struct ad1938_priv *ad1938) ad1938_fill_cache(codec); - ret = snd_soc_register_codec(codec); - if (ret != 0) { - dev_err(codec->dev, "Failed to register codec: %d\n", ret); - return ret; - } + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + return ret; + } - ret = snd_soc_register_dai(&ad1938_dai); - if (ret != 0) { - dev_err(codec->dev, "Failed to register DAI: %d\n", ret); - snd_soc_unregister_codec(codec); - return ret; - } + ret = snd_soc_register_dai(&ad1938_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + return ret; + } - return 0; + return 0; } static void ad1938_unregister(struct ad1938_priv *ad1938) { - ad1938_set_bias_level(&ad1938->codec, SND_SOC_BIAS_OFF); - snd_soc_unregister_dai(&ad1938_dai); - snd_soc_unregister_codec(&ad1938->codec); - kfree(ad1938); - ad1938_codec = NULL; + ad1938_set_bias_level(&ad1938->codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dai(&ad1938_dai); + snd_soc_unregister_codec(&ad1938->codec); + kfree(ad1938); + ad1938_codec = NULL; } static int ad1938_probe(struct platform_device *pdev) { - struct snd_soc_device *socdev = platform_get_drvdata(pdev); - struct snd_soc_codec *codec; - int ret = 0; - - if (ad1938_codec == NULL) { - dev_err(&pdev->dev, "Codec device not registered\n"); - return -ENODEV; - } - - socdev->card->codec = ad1938_codec; - codec = ad1938_codec; - - /* register pcms */ - ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); - if (ret < 0) { - dev_err(codec->dev, "failed to create pcms: %d\n", ret); - goto pcm_err; - } - - snd_soc_add_controls(codec, ad1938_snd_controls, - ARRAY_SIZE(ad1938_snd_controls)); - snd_soc_dapm_new_controls(codec, ad1938_dapm_widgets, - ARRAY_SIZE(ad1938_dapm_widgets)); + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (ad1938_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = ad1938_codec; + codec = ad1938_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + snd_soc_add_controls(codec, ad1938_snd_controls, + ARRAY_SIZE(ad1938_snd_controls)); + snd_soc_dapm_new_controls(codec, ad1938_dapm_widgets, + ARRAY_SIZE(ad1938_dapm_widgets)); snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); snd_soc_dapm_new_widgets(codec); ad1938_set_bias_level(codec, SND_SOC_BIAS_STANDBY); ret = snd_soc_init_card(socdev); - if (ret < 0) { - dev_err(codec->dev, "failed to register card: %d\n", ret); - goto card_err; - } + if (ret < 0) { + dev_err(codec->dev, "failed to register card: %d\n", ret); + goto card_err; + } - return ret; + return ret; card_err: - snd_soc_free_pcms(socdev); - snd_soc_dapm_free(socdev); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); pcm_err: - return ret; + return ret; } /* power down chip */ static int ad1938_remove(struct platform_device *pdev) { - struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_device *socdev = platform_get_drvdata(pdev); - snd_soc_free_pcms(socdev); - snd_soc_dapm_free(socdev); + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); - return 0; + return 0; } #ifdef CONFIG_PM -- cgit v1.2.3 From ed0f19b23754dc24f9a2d3c266a36277410f4686 Mon Sep 17 00:00:00 2001 From: John Bonesio Date: Tue, 21 Jul 2009 13:15:40 -0700 Subject: ASoC: MPC5200: Increase the delay time between resets Reset was failing with the original udelay(50) between the code in psc_ac97_cold_reset() and the call to psc_ac97_warm_reset(). Through testing it was found that a delay of 1ms was necessary for the cold_reset code to consistently complete successfully. Signed-off-by: John Bonesio Signed-off-by: Mark Brown --- sound/soc/fsl/mpc5200_psc_ac97.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/fsl/mpc5200_psc_ac97.c b/sound/soc/fsl/mpc5200_psc_ac97.c index 7eb549985d4..c4ae3e096bb 100644 --- a/sound/soc/fsl/mpc5200_psc_ac97.c +++ b/sound/soc/fsl/mpc5200_psc_ac97.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -112,7 +113,7 @@ static void psc_ac97_cold_reset(struct snd_ac97 *ac97) out_8(®s->op1, MPC52xx_PSC_OP_RES); udelay(10); out_8(®s->op0, MPC52xx_PSC_OP_RES); - udelay(50); + msleep(1); psc_ac97_warm_reset(ac97); } -- cgit v1.2.3 From e458a48f8776d624d05246471501c6086adb1d7a Mon Sep 17 00:00:00 2001 From: Joonyoung Shim Date: Wed, 22 Jul 2009 14:09:08 +0900 Subject: ASoC: MAX9877: separate callback functions The callback function to control register was used by whole controls in MAX9877 driver, but this causes using many if statement for double register control or invert. So, the callback function for double register control is separate differently, and the code for invert is added in the callback function. Signed-off-by: Joonyoung Shim Signed-off-by: Mark Brown --- sound/soc/codecs/max9877.c | 96 ++++++++++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 33 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/max9877.c b/sound/soc/codecs/max9877.c index 7df9a7c0208..a1f1119e1b7 100644 --- a/sound/soc/codecs/max9877.c +++ b/sound/soc/codecs/max9877.c @@ -34,16 +34,16 @@ static int max9877_get_reg(struct snd_kcontrol *kcontrol, { struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; - int reg = mc->reg; - int reg2 = mc->rreg; - int shift = mc->shift; - int mask = mc->max; + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int mask = mc->max; + unsigned int invert = mc->invert; ucontrol->value.integer.value[0] = (max9877_regs[reg] >> shift) & mask; - if (reg2) - ucontrol->value.integer.value[1] = - (max9877_regs[reg2] >> shift) & mask; + if (invert) + ucontrol->value.integer.value[0] = + mask - ucontrol->value.integer.value[0]; return 0; } @@ -53,39 +53,69 @@ static int max9877_set_reg(struct snd_kcontrol *kcontrol, { struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; - int reg = mc->reg; - int reg2 = mc->rreg; - int shift = mc->shift; - int mask = mc->max; - int change = 1; - int change2 = 1; - int ret = 0; - - if (((max9877_regs[reg] >> shift) & mask) == - ucontrol->value.integer.value[0]) + unsigned int reg = mc->reg; + unsigned int shift = mc->shift; + unsigned int mask = mc->max; + unsigned int invert = mc->invert; + unsigned int val = (ucontrol->value.integer.value[0] & mask); + + if (invert) + val = mask - val; + + if (((max9877_regs[reg] >> shift) & mask) == val) + return 0; + + max9877_regs[reg] &= ~(mask << shift); + max9877_regs[reg] |= val << shift; + max9877_write_regs(); + + return 1; +} + +static int max9877_get_2reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + unsigned int mask = mc->max; + + ucontrol->value.integer.value[0] = (max9877_regs[reg] >> shift) & mask; + ucontrol->value.integer.value[1] = (max9877_regs[reg2] >> shift) & mask; + + return 0; +} + +static int max9877_set_2reg(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + unsigned int reg = mc->reg; + unsigned int reg2 = mc->rreg; + unsigned int shift = mc->shift; + unsigned int mask = mc->max; + unsigned int val = (ucontrol->value.integer.value[0] & mask); + unsigned int val2 = (ucontrol->value.integer.value[1] & mask); + unsigned int change = 1; + + if (((max9877_regs[reg] >> shift) & mask) == val) change = 0; - if (reg2) - if (((max9877_regs[reg2] >> shift) & mask) == - ucontrol->value.integer.value[1]) - change2 = 0; + if (((max9877_regs[reg2] >> shift) & mask) == val2) + change = 0; if (change) { max9877_regs[reg] &= ~(mask << shift); - max9877_regs[reg] |= ucontrol->value.integer.value[0] << shift; - ret = change; - } - - if (reg2 && change2) { + max9877_regs[reg] |= val << shift; max9877_regs[reg2] &= ~(mask << shift); - max9877_regs[reg2] |= ucontrol->value.integer.value[1] << shift; - ret = change2; - } - - if (ret) + max9877_regs[reg2] |= val2 << shift; max9877_write_regs(); + } - return ret; + return change; } static int max9877_get_out_mode(struct snd_kcontrol *kcontrol, @@ -190,7 +220,7 @@ static const struct snd_kcontrol_new max9877_controls[] = { max9877_get_reg, max9877_set_reg, max9877_output_tlv), SOC_DOUBLE_R_EXT_TLV("MAX9877 Amp HP Playback Volume", MAX9877_HPL_VOLUME, MAX9877_HPR_VOLUME, 0, 31, 0, - max9877_get_reg, max9877_set_reg, max9877_output_tlv), + max9877_get_2reg, max9877_set_2reg, max9877_output_tlv), SOC_SINGLE_EXT("MAX9877 INB Stereo Switch", MAX9877_INPUT_MODE, 4, 1, 1, max9877_get_reg, max9877_set_reg), -- cgit v1.2.3 From 06c71282a90470184a78f7f0ab0f7ce0fc1f69c8 Mon Sep 17 00:00:00 2001 From: Chaithrika U S Date: Wed, 22 Jul 2009 07:45:04 -0400 Subject: ASoC: tlv320aic3x: Enable PLL when not bypassed PLL was not being enabled when it was not bypassed. This patch enables the PLL when it is used. Additionally, it disables the PLL when it is bypassed. Without this patch, the audio on TI DM646x EVM and DM355 EVM does not work properly. The bit clocks and the frame sync signals from the codec are not correct and hence the playback/record are faster than usual for most sample rates. The reason for this was that the PLL was not enabled when it was not bypassed. Tested on DM6467 EVM, playback tested on DM355 EVM. Signed-off-by: Chaithrika U S Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic3x.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index ab099f48248..cb0d1bf34b5 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -767,6 +767,7 @@ static int aic3x_hw_params(struct snd_pcm_substream *substream, int codec_clk = 0, bypass_pll = 0, fsref, last_clk = 0; u8 data, r, p, pll_q, pll_p = 1, pll_r = 1, pll_j = 1; u16 pll_d = 1; + u8 reg; /* select data word length */ data = @@ -801,8 +802,16 @@ static int aic3x_hw_params(struct snd_pcm_substream *substream, pll_q &= 0xf; aic3x_write(codec, AIC3X_PLL_PROGA_REG, pll_q << PLLQ_SHIFT); aic3x_write(codec, AIC3X_GPIOB_REG, CODEC_CLKIN_CLKDIV); - } else + /* disable PLL if it is bypassed */ + reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG); + aic3x_write(codec, AIC3X_PLL_PROGA_REG, reg & ~PLL_ENABLE); + + } else { aic3x_write(codec, AIC3X_GPIOB_REG, CODEC_CLKIN_PLLDIV); + /* enable PLL when it is used */ + reg = aic3x_read_reg_cache(codec, AIC3X_PLL_PROGA_REG); + aic3x_write(codec, AIC3X_PLL_PROGA_REG, reg | PLL_ENABLE); + } /* Route Left DAC to left channel input and * right DAC to right channel input */ -- cgit v1.2.3 From d756b2774829c746e0cf2967802cbd41967eda65 Mon Sep 17 00:00:00 2001 From: "Lopez Cruz, Misael" Date: Wed, 22 Jul 2009 20:45:03 -0500 Subject: ASoC: OMAP: Staticise pcm creation function of omap-pcm Signed-off-by: Misael Lopez Cruz Signed-off-by: Mark Brown --- sound/soc/omap/omap-pcm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/omap/omap-pcm.c b/sound/soc/omap/omap-pcm.c index 84a1950880e..c3c931d4537 100644 --- a/sound/soc/omap/omap-pcm.c +++ b/sound/soc/omap/omap-pcm.c @@ -330,7 +330,7 @@ static void omap_pcm_free_dma_buffers(struct snd_pcm *pcm) } } -int omap_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, +static int omap_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, struct snd_pcm *pcm) { int ret = 0; -- cgit v1.2.3 From 459dc35233c88d9eb7c5d0e6c086122751e64750 Mon Sep 17 00:00:00 2001 From: Janusz Krzysztofik Date: Wed, 22 Jul 2009 05:22:28 +0200 Subject: ASoC: Add support for Conexant CX20442-11 voice modem codec This patch adds support for Conexant CX20442-11 voice modem codec, suitable for use by the ASoC board driver for Amstrad E3 (Delta) videophone. Related sound card driver will follow. This codec is an optional part of the Conexant SmartV three chip modem design. As such, documentation for its proprietary digital audio interface is not available. However, on Amstrad Delta board, thanks to Mark Underwood who created an initial, omap-alsa based sound driver a few years ago[1], the codec has been discovered to be accessible not only from the modem side, but also over the OMAP McBSP based CPU DAI. Thus, the driver can be used by any sound card that can access the codec DAI directly. The DAI configuration parameters (sample rate and format, number of channels) has been selected out empirically for best user experience. The codec analogue interface consists of two pairs of analogue I/O pins: speakerphone interface or telephone handset/headset interface. Furthermore, it seams to provide two operation modes for speakerphone I/O: standard and advanced, with automatic gain control and echo cancelation. Even if the codec control interface is unknown and not available, all those interfaces and modes can be selected over the modem chip using V.253 commands. The driver is able to issue necessary commands over a suitable hw_write function if provided by a sound card driver. Otherwise, the codec can be controlled over the modem from userspace while inactive. Even if nothig is known about the codec internal power management capabilities, DAPM widgets has been used to model the codec audio map. Automatically performed powering up/down of those virtual widgets results in corresponding V.253 commands being issued. Some driver features/oddities may be board specific, but I have no way to verify that with any board other than Amstrad Delta. [1] http://www.earth.li/pipermail/e3-hacking/2006-April/000481.html Created and tested against linux-2.6.31-rc3. Applies and works with linux-omap-2.6 commit 7c5cb7862d32cb344be7831d466535d5255e35ac as well. Signed-off-by: Janusz Krzysztofik Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 3 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/cx20442.c | 395 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/cx20442.h | 19 +++ 4 files changed, 419 insertions(+) create mode 100644 sound/soc/codecs/cx20442.c create mode 100644 sound/soc/codecs/cx20442.h (limited to 'sound/soc') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 57f5b73f1bd..fdfc5024e40 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -94,6 +94,9 @@ config SND_SOC_CS4270_VD33_ERRATA bool depends on SND_SOC_CS4270 +config SND_SOC_CX20442 + tristate + config SND_SOC_L3 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 9a92f553916..1131d6dc761 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -5,6 +5,7 @@ snd-soc-ad73311-objs := ad73311.o snd-soc-ak4104-objs := ak4104.o snd-soc-ak4535-objs := ak4535.o snd-soc-cs4270-objs := cs4270.o +snd-soc-cx20442-objs := cx20442.o snd-soc-l3-objs := l3.o snd-soc-pcm3008-objs := pcm3008.o snd-soc-spdif-objs := spdif_transciever.o @@ -49,6 +50,7 @@ obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o +obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o diff --git a/sound/soc/codecs/cx20442.c b/sound/soc/codecs/cx20442.c new file mode 100644 index 00000000000..f64483c75b5 --- /dev/null +++ b/sound/soc/codecs/cx20442.c @@ -0,0 +1,395 @@ +/* + * cx20442.c -- CX20442 ALSA Soc Audio driver + * + * Copyright 2009 Janusz Krzysztofik + * + * Initially based on sound/soc/codecs/wm8400.c + * Copyright 2008, 2009 Wolfson Microelectronics PLC. + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include + +#include "cx20442.h" + + +struct cx20442_priv { + struct snd_soc_codec codec; + u8 reg_cache[1]; +}; + +#define CX20442_PM 0x0 + +#define CX20442_TELIN 0 +#define CX20442_TELOUT 1 +#define CX20442_MIC 2 +#define CX20442_SPKOUT 3 +#define CX20442_AGC 4 + +static const struct snd_soc_dapm_widget cx20442_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("TELOUT"), + SND_SOC_DAPM_OUTPUT("SPKOUT"), + SND_SOC_DAPM_OUTPUT("AGCOUT"), + + SND_SOC_DAPM_MIXER("SPKOUT Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_PGA("TELOUT Amp", CX20442_PM, CX20442_TELOUT, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPKOUT Amp", CX20442_PM, CX20442_SPKOUT, 0, NULL, 0), + SND_SOC_DAPM_PGA("SPKOUT AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0), + + SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_MIXER("Input Mixer", SND_SOC_NOPM, 0, 0, NULL, 0), + + SND_SOC_DAPM_MICBIAS("TELIN Bias", CX20442_PM, CX20442_TELIN, 0), + SND_SOC_DAPM_MICBIAS("MIC Bias", CX20442_PM, CX20442_MIC, 0), + + SND_SOC_DAPM_PGA("MIC AGC", CX20442_PM, CX20442_AGC, 0, NULL, 0), + + SND_SOC_DAPM_INPUT("TELIN"), + SND_SOC_DAPM_INPUT("MIC"), + SND_SOC_DAPM_INPUT("AGCIN"), +}; + +static const struct snd_soc_dapm_route cx20442_audio_map[] = { + {"TELOUT", NULL, "TELOUT Amp"}, + + {"SPKOUT", NULL, "SPKOUT Mixer"}, + {"SPKOUT Mixer", NULL, "SPKOUT Amp"}, + + {"TELOUT Amp", NULL, "DAC"}, + {"SPKOUT Amp", NULL, "DAC"}, + + {"SPKOUT Mixer", NULL, "SPKOUT AGC"}, + {"SPKOUT AGC", NULL, "AGCIN"}, + + {"AGCOUT", NULL, "MIC AGC"}, + {"MIC AGC", NULL, "MIC"}, + + {"MIC Bias", NULL, "MIC"}, + {"Input Mixer", NULL, "MIC Bias"}, + + {"TELIN Bias", NULL, "TELIN"}, + {"Input Mixer", NULL, "TELIN Bias"}, + + {"ADC", NULL, "Input Mixer"}, +}; + +static int cx20442_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, cx20442_dapm_widgets, + ARRAY_SIZE(cx20442_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, cx20442_audio_map, + ARRAY_SIZE(cx20442_audio_map)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static unsigned int cx20442_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 *reg_cache = codec->reg_cache; + + if (reg >= codec->reg_cache_size) + return -EINVAL; + + return reg_cache[reg]; +} + +enum v253_vls { + V253_VLS_NONE = 0, + V253_VLS_T, + V253_VLS_L, + V253_VLS_LT, + V253_VLS_S, + V253_VLS_ST, + V253_VLS_M, + V253_VLS_MST, + V253_VLS_S1, + V253_VLS_S1T, + V253_VLS_MS1T, + V253_VLS_M1, + V253_VLS_M1ST, + V253_VLS_M1S1T, + V253_VLS_H, + V253_VLS_HT, + V253_VLS_MS, + V253_VLS_MS1, + V253_VLS_M1S, + V253_VLS_M1S1, + V253_VLS_TEST, +}; + +static int cx20442_pm_to_v253_vls(u8 value) +{ + switch(value & ~(1 << CX20442_AGC)) { + case 0: + return V253_VLS_T; + case (1 << CX20442_SPKOUT): + return V253_VLS_S1; + case (1 << CX20442_MIC): + return V253_VLS_M1; + case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC): + return V253_VLS_M1S1; + case (1 << CX20442_TELOUT): + case (1 << CX20442_TELIN): + case (1 << CX20442_TELOUT) | (1 << CX20442_TELIN): + return V253_VLS_L; + case (1 << CX20442_TELOUT) | (1 << CX20442_MIC): + return V253_VLS_NONE; + } + return -EINVAL; +} +static int cx20442_pm_to_v253_vsp(u8 value) +{ + switch(value & ~(1 << CX20442_AGC)) { + case (1 << CX20442_SPKOUT): + case (1 << CX20442_MIC): + case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC): + return (bool)(value & (1 << CX20442_AGC)); + } + return (value & (1 << CX20442_AGC)) ? -EINVAL : 0; +} + +static int cx20442_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 *reg_cache = codec->reg_cache; + int vls, vsp, old, len; + char buf[18]; + + if (reg >= codec->reg_cache_size) + return -EINVAL; + + if (!codec->hw_write || !codec->control_data) + return -EIO; + + old = reg_cache[reg]; + reg_cache[reg] = value; + + vls = cx20442_pm_to_v253_vls(value); + if (vls < 0) + return vls; + + vsp = cx20442_pm_to_v253_vsp(value); + if (vsp < 0 ) + return vsp; + + if ((vls == V253_VLS_T) || + (vls == cx20442_pm_to_v253_vls(old))) { + if (vsp == cx20442_pm_to_v253_vsp(old)) + return 0; + len = snprintf(buf, ARRAY_SIZE(buf), "at+vsp=%d\r", vsp); + } else if (vsp == cx20442_pm_to_v253_vsp(old)) + len = snprintf(buf, ARRAY_SIZE(buf), "at+vls=%d\r", vls); + else + len = snprintf(buf, ARRAY_SIZE(buf), + "at+vls=%d;+vsp=%d\r", vls, vsp); + + if (unlikely(len > (ARRAY_SIZE(buf) - 1))) + return -ENOMEM; + + if (codec->hw_write(codec->control_data, buf, len) != len) + return -EIO; + + return 0; +} + +struct snd_soc_dai cx20442_dai = { + .name = "CX20442", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 1, + .rates = SNDRV_PCM_RATE_8000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, +}; +EXPORT_SYMBOL_GPL(cx20442_dai); + +static struct snd_soc_codec *cx20442_codec; + +static int cx20442_codec_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret; + + if(!cx20442_codec) { + dev_err(&pdev->dev, "cx20442 not yet discovered\n"); + return -ENODEV; + } + codec = cx20442_codec; + + socdev->card->codec = codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(&pdev->dev, "failed to create pcms\n"); + goto pcm_err; + } + + cx20442_add_widgets(codec); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register card\n"); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + return ret; +} + +/* power down chip */ +static int cx20442_codec_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device cx20442_codec_dev = { + .probe = cx20442_codec_probe, + .remove = cx20442_codec_remove, +}; +EXPORT_SYMBOL_GPL(cx20442_codec_dev); + +static int cx20442_register(struct cx20442_priv *cx20442) +{ + struct snd_soc_codec *codec = &cx20442->codec; + int ret; + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->name = "CX20442"; + codec->owner = THIS_MODULE; + codec->private_data = cx20442; + + codec->dai = &cx20442_dai; + codec->num_dai = 1; + + codec->reg_cache = &cx20442->reg_cache; + codec->reg_cache_size = ARRAY_SIZE(cx20442->reg_cache); + codec->read = cx20442_read_reg_cache; + codec->write = cx20442_write; + + codec->bias_level = SND_SOC_BIAS_OFF; + + cx20442_dai.dev = codec->dev; + + cx20442_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + //dev_err(&dev->dev, "Failed to register codec: %d\n", ret); + goto err; + } + + ret = snd_soc_register_dai(&cx20442_dai); + if (ret != 0) { + //dev_err(&dev->dev, "Failed to register DAI: %d\n", ret); + goto err_codec; + } + + return 0; + +err_codec: + snd_soc_unregister_codec(codec); +err: + cx20442_codec = NULL; + kfree(cx20442); + return ret; +} + +static void cx20442_unregister(struct cx20442_priv *cx20442) +{ + snd_soc_unregister_dai(&cx20442_dai); + snd_soc_unregister_codec(&cx20442->codec); + + cx20442_codec = NULL; + kfree(cx20442); +} + +static int cx20442_platform_probe(struct platform_device *pdev) +{ + struct cx20442_priv *cx20442; + struct snd_soc_codec *codec; + + cx20442 = kzalloc(sizeof(struct cx20442_priv), GFP_KERNEL); + if (cx20442 == NULL) + return -ENOMEM; + + codec = &cx20442->codec; + + codec->control_data = NULL; + codec->hw_write = NULL; + codec->pop_time = 0; + + codec->dev = &pdev->dev; + platform_set_drvdata(pdev, cx20442); + + return cx20442_register(cx20442); +} + +static int __exit cx20442_platform_remove(struct platform_device *pdev) +{ + struct cx20442_priv *cx20442 = platform_get_drvdata(pdev); + + cx20442_unregister(cx20442); + return 0; +} + +static struct platform_driver cx20442_platform_driver = { + .driver = { + .name = "cx20442", + .owner = THIS_MODULE, + }, + .probe = cx20442_platform_probe, + .remove = __exit_p(cx20442_platform_remove), +}; + +static int __init cx20442_init(void) +{ + return platform_driver_register(&cx20442_platform_driver); +} +module_init(cx20442_init); + +static void __exit cx20442_exit(void) +{ + platform_driver_unregister(&cx20442_platform_driver); +} +module_exit(cx20442_exit); + +MODULE_DESCRIPTION("ASoC CX20442-11 voice modem codec driver"); +MODULE_AUTHOR("Janusz Krzysztofik"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:cx20442-codec"); diff --git a/sound/soc/codecs/cx20442.h b/sound/soc/codecs/cx20442.h new file mode 100644 index 00000000000..d0a4f297aef --- /dev/null +++ b/sound/soc/codecs/cx20442.h @@ -0,0 +1,19 @@ +/* + * cx20442.h -- audio driver for CX20442 + * + * Copyright 2009 Janusz Krzysztofik + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#ifndef _CX20442_CODEC_H +#define _CX20442_CODEC_H + +extern struct snd_soc_dai cx20442_dai; +extern struct snd_soc_codec_device cx20442_codec_dev; + +#endif -- cgit v1.2.3 From a7569afa8b79ca9272b0d7544335bc05b5b721d6 Mon Sep 17 00:00:00 2001 From: Joonyoung Shim Date: Thu, 23 Jul 2009 17:33:17 +0900 Subject: ASoC: MAX9877: fix write operation for register The MAX9877 needs an address of start register when we write values to registers through i2c_master_send(), but the code for this was missed in max9877_write_regs(). If the value of control is 0 in the max9877_set_out_mode(), the value is not increased to 1, but actually the value to write to the register should be 1. And the register bits for out_mode and osc_mode should be cleared before writing. Signed-off-by: Joonyoung Shim Signed-off-by: Mark Brown --- sound/soc/codecs/max9877.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/max9877.c b/sound/soc/codecs/max9877.c index a1f1119e1b7..9e7e964a5fa 100644 --- a/sound/soc/codecs/max9877.c +++ b/sound/soc/codecs/max9877.c @@ -25,7 +25,14 @@ static u8 max9877_regs[5] = { 0x40, 0x00, 0x00, 0x00, 0x49 }; static void max9877_write_regs(void) { - if (i2c_master_send(i2c, max9877_regs, 5) != 5) + unsigned int i; + u8 data[6]; + + data[0] = MAX9877_INPUT_MODE; + for (i = 0; i < ARRAY_SIZE(max9877_regs); i++) + data[i + 1] = max9877_regs[i]; + + if (i2c_master_send(i2c, data, 6) != 6) dev_err(&i2c->dev, "i2c write failed\n"); } @@ -135,12 +142,12 @@ static int max9877_set_out_mode(struct snd_kcontrol *kcontrol, { u8 value = ucontrol->value.integer.value[0]; - if (value) - value += 1; + value += 1; if ((max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OUTMODE_MASK) == value) return 0; + max9877_regs[MAX9877_OUTPUT_MODE] &= ~MAX9877_OUTMODE_MASK; max9877_regs[MAX9877_OUTPUT_MODE] |= value; max9877_write_regs(); return 1; @@ -166,6 +173,7 @@ static int max9877_set_osc_mode(struct snd_kcontrol *kcontrol, if ((max9877_regs[MAX9877_OUTPUT_MODE] & MAX9877_OSC_MASK) == value) return 0; + max9877_regs[MAX9877_OUTPUT_MODE] &= ~MAX9877_OSC_MASK; max9877_regs[MAX9877_OUTPUT_MODE] |= value; max9877_write_regs(); return 1; -- cgit v1.2.3 From 474828a40f6ddab6e2a3475a19c5c84aa3ec7d60 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Wed, 22 Jul 2009 13:01:03 +0200 Subject: ALSA: Allow passing platform_data to devices attached to AC97 bus This patch allows passing platform_data to devices attached to AC97 bus (like touchscreens, battery measurement chips ...). Signed-off-by: Marek Vasut Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 8bf49a4e664..5eabb711760 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -1461,8 +1462,11 @@ int snd_soc_init_card(struct snd_soc_device *socdev) continue; } } - if (card->dai_link[i].codec_dai->ac97_control) + if (card->dai_link[i].codec_dai->ac97_control) { ac97 = 1; + snd_ac97_dev_add_pdata(codec->ac97, + card->dai_link[i].cpu_dai->ac97_pdata); + } } snprintf(codec->card->shortname, sizeof(codec->card->shortname), "%s", card->name); -- cgit v1.2.3 From 178b699c25c39f042c3c2446e6bd5dbed18c0442 Mon Sep 17 00:00:00 2001 From: Janusz Krzysztofik Date: Fri, 24 Jul 2009 02:48:57 +0200 Subject: ASoC: Jack handling enhancements as suggested by subsystem maintainer The patch adds a few small enhancements to the ASoC jack handling, as suggested by Mark in his comments to my Amstrad Delta driver, and a few fixes for related bugs found while learning Mark's code and testing results. Enhancements: 1. Update status of an ASoC jack while associating it with new gpios. 2. Really update DAPM pins while associating them with an ASoC jack. 3. Export ASoC jack gpios over gpiolib sysfs for diagnostic purposes. Fixes: 1. Apply mask on jack status report before using it, just for case. 2. While updating jack associated DAPM pins, use full resulting jack status, not the status report passed as an argument. Created and tested on linux-2.6.31-rc3 Signed-off-by: Janusz Krzysztofik Signed-off-by: Mark Brown --- sound/soc/soc-jack.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-jack.c b/sound/soc/soc-jack.c index 28346fb2e70..4aa7d8f8ce7 100644 --- a/sound/soc/soc-jack.c +++ b/sound/soc/soc-jack.c @@ -73,14 +73,15 @@ void snd_soc_jack_report(struct snd_soc_jack *jack, int status, int mask) oldstatus = jack->status; jack->status &= ~mask; - jack->status |= status; + jack->status |= status & mask; - /* The DAPM sync is expensive enough to be worth skipping */ - if (jack->status == oldstatus) + /* The DAPM sync is expensive enough to be worth skipping. + * However, empty mask means pin synchronization is desired. */ + if (mask && (jack->status == oldstatus)) goto out; list_for_each_entry(pin, &jack->pins, list) { - enable = pin->mask & status; + enable = pin->mask & jack->status; if (pin->invert) enable = !enable; @@ -228,8 +229,16 @@ int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count, if (ret) goto err; +#ifdef CONFIG_GPIO_SYSFS + /* Expose GPIO value over sysfs for diagnostic purposes */ + gpio_export(gpios[i].gpio, false); +#endif + INIT_WORK(&gpios[i].work, gpio_work); gpios[i].jack = jack; + + /* Update initial jack status */ + snd_soc_jack_gpio_detect(&gpios[i]); } return 0; @@ -258,6 +267,9 @@ void snd_soc_jack_free_gpios(struct snd_soc_jack *jack, int count, int i; for (i = 0; i < count; i++) { +#ifdef CONFIG_GPIO_SYSFS + gpio_unexport(gpios[i].gpio); +#endif free_irq(gpio_to_irq(gpios[i].gpio), &gpios[i]); gpio_free(gpios[i].gpio); gpios[i].jack = NULL; -- cgit v1.2.3 From 4ce2f2fe61002ab35c9333d52c95b5ff9e949f50 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 23 Jul 2009 22:16:56 +0200 Subject: ASoC: Switch palm27x-asoc to jack detection api This patch removes the old method of jack detection from palm27x-asoc driver and adds jack detection api. It also removes some other (now) useless stuff from the driver and corrects pin configuration for the codec. Signed-off-by: Marek Vasut Signed-off-by: Mark Brown --- sound/soc/pxa/palm27x.c | 204 +++++++++++++++++------------------------------- 1 file changed, 73 insertions(+), 131 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/pxa/palm27x.c b/sound/soc/pxa/palm27x.c index e6102fda0a7..1f96e3227be 100644 --- a/sound/soc/pxa/palm27x.c +++ b/sound/soc/pxa/palm27x.c @@ -17,13 +17,12 @@ #include #include #include -#include -#include #include #include #include #include +#include #include #include @@ -33,90 +32,31 @@ #include "pxa2xx-pcm.h" #include "pxa2xx-ac97.h" -static int palm27x_jack_func = 1; -static int palm27x_spk_func = 1; -static int palm27x_ep_gpio = -1; +static struct snd_soc_jack hs_jack; -static void palm27x_ext_control(struct snd_soc_codec *codec) -{ - if (!palm27x_spk_func) - snd_soc_dapm_enable_pin(codec, "Speaker"); - else - snd_soc_dapm_disable_pin(codec, "Speaker"); - - if (!palm27x_jack_func) - snd_soc_dapm_enable_pin(codec, "Headphone Jack"); - else - snd_soc_dapm_disable_pin(codec, "Headphone Jack"); - - snd_soc_dapm_sync(codec); -} - -static int palm27x_startup(struct snd_pcm_substream *substream) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->socdev->card->codec; - - /* check the jack status at stream startup */ - palm27x_ext_control(codec); - return 0; -} - -static struct snd_soc_ops palm27x_ops = { - .startup = palm27x_startup, +/* Headphones jack detection DAPM pins */ +static struct snd_soc_jack_pin hs_jack_pins[] = { + { + .pin = "Headphone Jack", + .mask = SND_JACK_HEADPHONE, + }, }; -static irqreturn_t palm27x_interrupt(int irq, void *v) -{ - palm27x_spk_func = gpio_get_value(palm27x_ep_gpio); - palm27x_jack_func = !palm27x_spk_func; - return IRQ_HANDLED; -} - -static int palm27x_get_jack(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - ucontrol->value.integer.value[0] = palm27x_jack_func; - return 0; -} - -static int palm27x_set_jack(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - - if (palm27x_jack_func == ucontrol->value.integer.value[0]) - return 0; - - palm27x_jack_func = ucontrol->value.integer.value[0]; - palm27x_ext_control(codec); - return 1; -} - -static int palm27x_get_spk(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - ucontrol->value.integer.value[0] = palm27x_spk_func; - return 0; -} - -static int palm27x_set_spk(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - - if (palm27x_spk_func == ucontrol->value.integer.value[0]) - return 0; - - palm27x_spk_func = ucontrol->value.integer.value[0]; - palm27x_ext_control(codec); - return 1; -} +/* Headphones jack detection gpios */ +static struct snd_soc_jack_gpio hs_jack_gpios[] = { + [0] = { + /* gpio is set on per-platform basis */ + .name = "hp-gpio", + .report = SND_JACK_HEADPHONE, + .debounce_time = 200, + }, +}; -/* PalmTX machine dapm widgets */ +/* Palm27x machine dapm widgets */ static const struct snd_soc_dapm_widget palm27x_dapm_widgets[] = { SND_SOC_DAPM_HP("Headphone Jack", NULL), - SND_SOC_DAPM_SPK("Speaker", NULL), + SND_SOC_DAPM_SPK("Ext. Speaker", NULL), + SND_SOC_DAPM_MIC("Ext. Microphone", NULL), }; /* PalmTX audio map */ @@ -126,46 +66,66 @@ static const struct snd_soc_dapm_route audio_map[] = { {"Headphone Jack", NULL, "HPOUTR"}, /* ext speaker connected to ROUT2, LOUT2 */ - {"Speaker", NULL, "LOUT2"}, - {"Speaker", NULL, "ROUT2"}, -}; + {"Ext. Speaker", NULL, "LOUT2"}, + {"Ext. Speaker", NULL, "ROUT2"}, -static const char *jack_function[] = {"Headphone", "Off"}; -static const char *spk_function[] = {"On", "Off"}; -static const struct soc_enum palm27x_enum[] = { - SOC_ENUM_SINGLE_EXT(2, jack_function), - SOC_ENUM_SINGLE_EXT(2, spk_function), + /* mic connected to MIC1 */ + {"Ext. Microphone", NULL, "MIC1"}, }; -static const struct snd_kcontrol_new palm27x_controls[] = { - SOC_ENUM_EXT("Jack Function", palm27x_enum[0], palm27x_get_jack, - palm27x_set_jack), - SOC_ENUM_EXT("Speaker Function", palm27x_enum[1], palm27x_get_spk, - palm27x_set_spk), -}; +static struct snd_soc_card palm27x_asoc; static int palm27x_ac97_init(struct snd_soc_codec *codec) { int err; + /* add palm27x specific widgets */ + err = snd_soc_dapm_new_controls(codec, palm27x_dapm_widgets, + ARRAY_SIZE(palm27x_dapm_widgets)); + if (err) + return err; + + /* set up palm27x specific audio path audio_map */ + err = snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + if (err) + return err; + + /* connected pins */ + if (machine_is_palmld()) + snd_soc_dapm_enable_pin(codec, "MIC1"); + snd_soc_dapm_enable_pin(codec, "HPOUTL"); + snd_soc_dapm_enable_pin(codec, "HPOUTR"); + snd_soc_dapm_enable_pin(codec, "LOUT2"); + snd_soc_dapm_enable_pin(codec, "ROUT2"); + + /* not connected pins */ snd_soc_dapm_nc_pin(codec, "OUT3"); snd_soc_dapm_nc_pin(codec, "MONOOUT"); + snd_soc_dapm_nc_pin(codec, "LINEINL"); + snd_soc_dapm_nc_pin(codec, "LINEINR"); + snd_soc_dapm_nc_pin(codec, "PCBEEP"); + snd_soc_dapm_nc_pin(codec, "PHONE"); + snd_soc_dapm_nc_pin(codec, "MIC2"); + + err = snd_soc_dapm_sync(codec); + if (err) + return err; - /* add palm27x specific controls */ - err = snd_soc_add_controls(codec, palm27x_controls, - ARRAY_SIZE(palm27x_controls)); - if (err < 0) + /* Jack detection API stuff */ + err = snd_soc_jack_new(&palm27x_asoc, "Headphone Jack", + SND_JACK_HEADPHONE, &hs_jack); + if (err) return err; - /* add palm27x specific widgets */ - snd_soc_dapm_new_controls(codec, palm27x_dapm_widgets, - ARRAY_SIZE(palm27x_dapm_widgets)); + err = snd_soc_jack_add_pins(&hs_jack, ARRAY_SIZE(hs_jack_pins), + hs_jack_pins); + if (err) + return err; - /* set up palm27x specific audio path audio_map */ - snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); + err = snd_soc_jack_add_gpios(&hs_jack, ARRAY_SIZE(hs_jack_gpios), + hs_jack_gpios); - snd_soc_dapm_sync(codec); - return 0; + return err; } static struct snd_soc_dai_link palm27x_dai[] = { @@ -175,14 +135,12 @@ static struct snd_soc_dai_link palm27x_dai[] = { .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_HIFI], .codec_dai = &wm9712_dai[WM9712_DAI_AC97_HIFI], .init = palm27x_ac97_init, - .ops = &palm27x_ops, }, { .name = "AC97 Aux", .stream_name = "AC97 Aux", .cpu_dai = &pxa_ac97_dai[PXA2XX_DAI_AC97_AUX], .codec_dai = &wm9712_dai[WM9712_DAI_AC97_AUX], - .ops = &palm27x_ops, }, }; @@ -208,27 +166,17 @@ static int palm27x_asoc_probe(struct platform_device *pdev) machine_is_palmld() || machine_is_palmte2())) return -ENODEV; - if (pdev->dev.platform_data) - palm27x_ep_gpio = ((struct palm27x_asoc_info *) - (pdev->dev.platform_data))->jack_gpio; - - ret = gpio_request(palm27x_ep_gpio, "Headphone Jack"); - if (ret) - return ret; - ret = gpio_direction_input(palm27x_ep_gpio); - if (ret) - goto err_alloc; + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "please supply platform_data\n"); + return -ENODEV; + } - if (request_irq(gpio_to_irq(palm27x_ep_gpio), palm27x_interrupt, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - "Headphone jack", NULL)) - goto err_alloc; + hs_jack_gpios[0].gpio = ((struct palm27x_asoc_info *) + (pdev->dev.platform_data))->jack_gpio; palm27x_snd_device = platform_device_alloc("soc-audio", -1); - if (!palm27x_snd_device) { - ret = -ENOMEM; - goto err_dev; - } + if (!palm27x_snd_device) + return -ENOMEM; platform_set_drvdata(palm27x_snd_device, &palm27x_snd_devdata); palm27x_snd_devdata.dev = &palm27x_snd_device->dev; @@ -241,18 +189,12 @@ static int palm27x_asoc_probe(struct platform_device *pdev) put_device: platform_device_put(palm27x_snd_device); -err_dev: - free_irq(gpio_to_irq(palm27x_ep_gpio), NULL); -err_alloc: - gpio_free(palm27x_ep_gpio); return ret; } static int __devexit palm27x_asoc_remove(struct platform_device *pdev) { - free_irq(gpio_to_irq(palm27x_ep_gpio), NULL); - gpio_free(palm27x_ep_gpio); platform_device_unregister(palm27x_snd_device); return 0; } -- cgit v1.2.3 From b84eab08a67913581515a1184f1deedf1d54dc5d Mon Sep 17 00:00:00 2001 From: Janusz Krzysztofik Date: Tue, 28 Jul 2009 20:24:12 +0200 Subject: ASoC: CX20442: fix issues pointed out by subsystem maintainer The patch fixes some checkpatch identified issues and adds a comment about line discipline interaction to my driver code, as requested by Mark on my inital submission (thank you Mark for applying my imperfect patch anyway). It also fixes MODULE_ALIAS mismatch as used in my machine driver. Signed-off-by: Janusz Krzysztofik Signed-off-by: Mark Brown --- sound/soc/codecs/cx20442.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/cx20442.c b/sound/soc/codecs/cx20442.c index f64483c75b5..7bbb77baa23 100644 --- a/sound/soc/codecs/cx20442.c +++ b/sound/soc/codecs/cx20442.c @@ -132,7 +132,7 @@ enum v253_vls { static int cx20442_pm_to_v253_vls(u8 value) { - switch(value & ~(1 << CX20442_AGC)) { + switch (value & ~(1 << CX20442_AGC)) { case 0: return V253_VLS_T; case (1 << CX20442_SPKOUT): @@ -152,7 +152,7 @@ static int cx20442_pm_to_v253_vls(u8 value) } static int cx20442_pm_to_v253_vsp(u8 value) { - switch(value & ~(1 << CX20442_AGC)) { + switch (value & ~(1 << CX20442_AGC)) { case (1 << CX20442_SPKOUT): case (1 << CX20442_MIC): case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC): @@ -171,6 +171,9 @@ static int cx20442_write(struct snd_soc_codec *codec, unsigned int reg, if (reg >= codec->reg_cache_size) return -EINVAL; + /* hw_write and control_data pointers required for talking to the modem + * are expected to be set by the machine driver's line discipline + * initialization code */ if (!codec->hw_write || !codec->control_data) return -EIO; @@ -182,7 +185,7 @@ static int cx20442_write(struct snd_soc_codec *codec, unsigned int reg, return vls; vsp = cx20442_pm_to_v253_vsp(value); - if (vsp < 0 ) + if (vsp < 0) return vsp; if ((vls == V253_VLS_T) || @@ -232,7 +235,7 @@ static int cx20442_codec_probe(struct platform_device *pdev) struct snd_soc_codec *codec; int ret; - if(!cx20442_codec) { + if (!cx20442_codec) { dev_err(&pdev->dev, "cx20442 not yet discovered\n"); return -ENODEV; } @@ -310,13 +313,13 @@ static int cx20442_register(struct cx20442_priv *cx20442) ret = snd_soc_register_codec(codec); if (ret != 0) { - //dev_err(&dev->dev, "Failed to register codec: %d\n", ret); + dev_err(&codec->dev, "Failed to register codec: %d\n", ret); goto err; } ret = snd_soc_register_dai(&cx20442_dai); if (ret != 0) { - //dev_err(&dev->dev, "Failed to register DAI: %d\n", ret); + dev_err(&codec->dev, "Failed to register DAI: %d\n", ret); goto err_codec; } @@ -392,4 +395,4 @@ module_exit(cx20442_exit); MODULE_DESCRIPTION("ASoC CX20442-11 voice modem codec driver"); MODULE_AUTHOR("Janusz Krzysztofik"); MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:cx20442-codec"); +MODULE_ALIAS("platform:cx20442"); -- cgit v1.2.3 From 01e2ab207ca5a8edac622ab112b581d41b5eb36e Mon Sep 17 00:00:00 2001 From: Barry Song <21cnbao@gmail.com> Date: Mon, 27 Jul 2009 18:06:39 +0800 Subject: ASoC: blackfin I2S(TDM mode) CPU DAI driver The I2S DAI driver for blackfin SPORT, but works in TDM mode. I2S is not a special case of TDM with only left and right two slots for SPORT interface. I2S coordinates with TDM in SPORT, but not a part of TDM. TDM require different hardware configuration with I2S, not only different slot number. One is "Stereo Serial Operation" mode of SPORT, the other one is "Multichannel Operation" mode. They are incompatible at the same time. Hardware and DMA description and data transfer flow are much different for I2S and TDM. Merging them as a whole will be very ugly and difficult to maintain. So we don't define a new DAI type, but give two DAI instances for standard I2S and TDM, both in I2S-family DAI type. The TDM instance still uses the I2S-family DAI type. Signed-off-by: Barry Song <21cnbao@gmail.com> Signed-off-by: Mark Brown --- sound/soc/blackfin/Kconfig | 15 +- sound/soc/blackfin/Makefile | 4 + sound/soc/blackfin/bf5xx-tdm-pcm.c | 330 +++++++++++++++++++++++++++++++++++ sound/soc/blackfin/bf5xx-tdm-pcm.h | 21 +++ sound/soc/blackfin/bf5xx-tdm.c | 343 +++++++++++++++++++++++++++++++++++++ sound/soc/blackfin/bf5xx-tdm.h | 14 ++ 6 files changed, 726 insertions(+), 1 deletion(-) create mode 100644 sound/soc/blackfin/bf5xx-tdm-pcm.c create mode 100644 sound/soc/blackfin/bf5xx-tdm-pcm.h create mode 100644 sound/soc/blackfin/bf5xx-tdm.c create mode 100644 sound/soc/blackfin/bf5xx-tdm.h (limited to 'sound/soc') diff --git a/sound/soc/blackfin/Kconfig b/sound/soc/blackfin/Kconfig index 811596f4c09..2ab6d2fa87a 100644 --- a/sound/soc/blackfin/Kconfig +++ b/sound/soc/blackfin/Kconfig @@ -7,6 +7,15 @@ config SND_BF5XX_I2S mode (supports single stereo In/Out). You will also need to select the audio interfaces to support below. +config SND_BF5XX_TDM + tristate "SoC I2S(TDM mode) Audio for the ADI BF5xx chip" + depends on (BLACKFIN && SND_SOC) + help + Say Y or M if you want to add support for codecs attached to + the Blackfin SPORT (synchronous serial ports) interface in TDM + mode. + You will also need to select the audio interfaces to support below. + config SND_BF5XX_SOC_SSM2602 tristate "SoC SSM2602 Audio support for BF52x ezkit" depends on SND_BF5XX_I2S @@ -69,6 +78,10 @@ config SND_BF5XX_SOC_I2S tristate select SND_BF5XX_SOC_SPORT +config SND_BF5XX_SOC_TDM + tristate + select SND_BF5XX_SOC_SPORT + config SND_BF5XX_SOC_AC97 tristate select AC97_BUS @@ -85,7 +98,7 @@ config SND_BF5XX_SOC_AD1980 config SND_BF5XX_SPORT_NUM int "Set a SPORT for Sound chip" - depends on (SND_BF5XX_I2S || SND_BF5XX_AC97) + depends on (SND_BF5XX_I2S || SND_BF5XX_AC97 || SND_BF5XX_TDM) range 0 3 if BF54x range 0 1 if !BF54x default 0 diff --git a/sound/soc/blackfin/Makefile b/sound/soc/blackfin/Makefile index 97bb37a6359..f86192623dd 100644 --- a/sound/soc/blackfin/Makefile +++ b/sound/soc/blackfin/Makefile @@ -1,15 +1,19 @@ # Blackfin Platform Support snd-bf5xx-ac97-objs := bf5xx-ac97-pcm.o snd-bf5xx-i2s-objs := bf5xx-i2s-pcm.o +snd-bf5xx-tdm-objs := bf5xx-tdm-pcm.o snd-soc-bf5xx-sport-objs := bf5xx-sport.o snd-soc-bf5xx-ac97-objs := bf5xx-ac97.o snd-soc-bf5xx-i2s-objs := bf5xx-i2s.o +snd-soc-bf5xx-tdm-objs := bf5xx-tdm.o obj-$(CONFIG_SND_BF5XX_AC97) += snd-bf5xx-ac97.o obj-$(CONFIG_SND_BF5XX_I2S) += snd-bf5xx-i2s.o +obj-$(CONFIG_SND_BF5XX_TDM) += snd-bf5xx-tdm.o obj-$(CONFIG_SND_BF5XX_SOC_SPORT) += snd-soc-bf5xx-sport.o obj-$(CONFIG_SND_BF5XX_SOC_AC97) += snd-soc-bf5xx-ac97.o obj-$(CONFIG_SND_BF5XX_SOC_I2S) += snd-soc-bf5xx-i2s.o +obj-$(CONFIG_SND_BF5XX_SOC_TDM) += snd-soc-bf5xx-tdm.o # Blackfin Machine Support snd-ad1980-objs := bf5xx-ad1980.o diff --git a/sound/soc/blackfin/bf5xx-tdm-pcm.c b/sound/soc/blackfin/bf5xx-tdm-pcm.c new file mode 100644 index 00000000000..af7fa8d7ff0 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-tdm-pcm.c @@ -0,0 +1,330 @@ +/* + * File: sound/soc/blackfin/bf5xx-tdm-pcm.c + * Author: Barry Song + * + * Created: Tue June 06 2009 + * Description: DMA driver for tdm codec + * + * Modified: + * Copyright 2009 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "bf5xx-tdm-pcm.h" +#include "bf5xx-tdm.h" +#include "bf5xx-sport.h" + +#define PCM_BUFFER_MAX 0x10000 +#define FRAGMENT_SIZE_MIN (4*1024) +#define FRAGMENTS_MIN 2 +#define FRAGMENTS_MAX 32 + +static void bf5xx_dma_irq(void *data) +{ + struct snd_pcm_substream *pcm = data; + snd_pcm_period_elapsed(pcm); +} + +static const struct snd_pcm_hardware bf5xx_pcm_hardware = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_RESUME), + .formats = SNDRV_PCM_FMTBIT_S32_LE, + .rates = SNDRV_PCM_RATE_48000, + .channels_min = 2, + .channels_max = 8, + .buffer_bytes_max = PCM_BUFFER_MAX, + .period_bytes_min = FRAGMENT_SIZE_MIN, + .period_bytes_max = PCM_BUFFER_MAX/2, + .periods_min = FRAGMENTS_MIN, + .periods_max = FRAGMENTS_MAX, +}; + +static int bf5xx_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + size_t size = bf5xx_pcm_hardware.buffer_bytes_max; + snd_pcm_lib_malloc_pages(substream, size * 4); + + return 0; +} + +static int bf5xx_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_lib_free_pages(substream); + + return 0; +} + +static int bf5xx_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + int fragsize_bytes = frames_to_bytes(runtime, runtime->period_size); + + fragsize_bytes /= runtime->channels; + /* inflate the fragsize to match the dma width of SPORT */ + fragsize_bytes *= 8; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + sport_set_tx_callback(sport, bf5xx_dma_irq, substream); + sport_config_tx_dma(sport, runtime->dma_area, + runtime->periods, fragsize_bytes); + } else { + sport_set_rx_callback(sport, bf5xx_dma_irq, substream); + sport_config_rx_dma(sport, runtime->dma_area, + runtime->periods, fragsize_bytes); + } + + return 0; +} + +static int bf5xx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sport_tx_start(sport); + else + sport_rx_start(sport); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + sport_tx_stop(sport); + else + sport_rx_stop(sport); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static snd_pcm_uframes_t bf5xx_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + unsigned int diff; + snd_pcm_uframes_t frames; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + diff = sport_curr_offset_tx(sport); + frames = diff / (8*4); /* 32 bytes per frame */ + } else { + diff = sport_curr_offset_rx(sport); + frames = diff / (8*4); + } + return frames; +} + +static int bf5xx_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + int ret = 0; + + snd_soc_set_runtime_hwparams(substream, &bf5xx_pcm_hardware); + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + goto out; + + if (sport_handle != NULL) + runtime->private_data = sport_handle; + else { + pr_err("sport_handle is NULL\n"); + ret = -ENODEV; + } +out: + return ret; +} + +static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel, + snd_pcm_uframes_t pos, void *buf, snd_pcm_uframes_t count) +{ + unsigned int *src; + unsigned int *dst; + int i; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + src = buf; + dst = (unsigned int *)substream->runtime->dma_area; + + dst += pos * 8; + while (count--) { + for (i = 0; i < substream->runtime->channels; i++) + *(dst + i) = *src++; + dst += 8; + } + } else { + src = (unsigned int *)substream->runtime->dma_area; + dst = buf; + + src += pos * 8; + while (count--) { + for (i = 0; i < substream->runtime->channels; i++) + *dst++ = *(src+i); + src += 8; + } + } + + return 0; +} + +static int bf5xx_pcm_silence(struct snd_pcm_substream *substream, + int channel, snd_pcm_uframes_t pos, snd_pcm_uframes_t count) +{ + unsigned char *buf = substream->runtime->dma_area; + buf += pos * 8 * 4; + memset(buf, '\0', count * 8 * 4); + + return 0; +} + + +struct snd_pcm_ops bf5xx_pcm_tdm_ops = { + .open = bf5xx_pcm_open, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = bf5xx_pcm_hw_params, + .hw_free = bf5xx_pcm_hw_free, + .prepare = bf5xx_pcm_prepare, + .trigger = bf5xx_pcm_trigger, + .pointer = bf5xx_pcm_pointer, + .copy = bf5xx_pcm_copy, + .silence = bf5xx_pcm_silence, +}; + +static int bf5xx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) +{ + struct snd_pcm_substream *substream = pcm->streams[stream].substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + size_t size = bf5xx_pcm_hardware.buffer_bytes_max; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->area = dma_alloc_coherent(pcm->card->dev, size * 4, + &buf->addr, GFP_KERNEL); + if (!buf->area) { + pr_err("Failed to allocate dma memory \ + Please increase uncached DMA memory region\n"); + return -ENOMEM; + } + buf->bytes = size; + + if (stream == SNDRV_PCM_STREAM_PLAYBACK) + sport_handle->tx_buf = buf->area; + else + sport_handle->rx_buf = buf->area; + + return 0; +} + +static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + struct snd_dma_buffer *buf; + int stream; + + for (stream = 0; stream < 2; stream++) { + substream = pcm->streams[stream].substream; + if (!substream) + continue; + + buf = &substream->dma_buffer; + if (!buf->area) + continue; + dma_free_coherent(NULL, buf->bytes, buf->area, 0); + buf->area = NULL; + } + if (sport_handle) + sport_done(sport_handle); +} + +static u64 bf5xx_pcm_dmamask = DMA_32BIT_MASK; + +static int bf5xx_pcm_tdm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + int ret = 0; + + if (!card->dev->dma_mask) + card->dev->dma_mask = &bf5xx_pcm_dmamask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = DMA_32BIT_MASK; + + if (dai->playback.channels_min) { + ret = bf5xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto out; + } + + if (dai->capture.channels_min) { + ret = bf5xx_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto out; + } +out: + return ret; +} + +struct snd_soc_platform bf5xx_tdm_soc_platform = { + .name = "bf5xx-audio", + .pcm_ops = &bf5xx_pcm_tdm_ops, + .pcm_new = bf5xx_pcm_tdm_new, + .pcm_free = bf5xx_pcm_free_dma_buffers, +}; +EXPORT_SYMBOL_GPL(bf5xx_tdm_soc_platform); + +static int __init bfin_pcm_tdm_init(void) +{ + return snd_soc_register_platform(&bf5xx_tdm_soc_platform); +} +module_init(bfin_pcm_tdm_init); + +static void __exit bfin_pcm_tdm_exit(void) +{ + snd_soc_unregister_platform(&bf5xx_tdm_soc_platform); +} +module_exit(bfin_pcm_tdm_exit); + +MODULE_AUTHOR("Barry Song"); +MODULE_DESCRIPTION("ADI Blackfin TDM PCM DMA module"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/blackfin/bf5xx-tdm-pcm.h b/sound/soc/blackfin/bf5xx-tdm-pcm.h new file mode 100644 index 00000000000..9e8473d1c38 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-tdm-pcm.h @@ -0,0 +1,21 @@ +/* + * linux/sound/arm/bf5xx-tdm-pcm.h -- ALSA PCM interface for the Blackfin + * + * Copyright 2009 Analog Device Inc. + * + * 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 _BF5XX_TDM_PCM_H +#define _BF5XX_TDM_PCM_H + +struct bf5xx_pcm_dma_params { + char *name; /* stream identifier */ +}; + +/* platform data */ +extern struct snd_soc_platform bf5xx_tdm_soc_platform; + +#endif diff --git a/sound/soc/blackfin/bf5xx-tdm.c b/sound/soc/blackfin/bf5xx-tdm.c new file mode 100644 index 00000000000..3096badf09a --- /dev/null +++ b/sound/soc/blackfin/bf5xx-tdm.c @@ -0,0 +1,343 @@ +/* + * File: sound/soc/blackfin/bf5xx-tdm.c + * Author: Barry Song + * + * Created: Thurs June 04 2009 + * Description: Blackfin I2S(TDM) CPU DAI driver + * Even though TDM mode can be as part of I2S DAI, but there + * are so much difference in configuration and data flow, + * it's very ugly to integrate I2S and TDM into a module + * + * Modified: + * Copyright 2009 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "bf5xx-sport.h" +#include "bf5xx-tdm.h" + +struct bf5xx_tdm_port { + u16 tcr1; + u16 rcr1; + u16 tcr2; + u16 rcr2; + int configured; +}; + +static struct bf5xx_tdm_port bf5xx_tdm; +static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM; + +static struct sport_param sport_params[2] = { + { + .dma_rx_chan = CH_SPORT0_RX, + .dma_tx_chan = CH_SPORT0_TX, + .err_irq = IRQ_SPORT0_ERROR, + .regs = (struct sport_register *)SPORT0_TCR1, + }, + { + .dma_rx_chan = CH_SPORT1_RX, + .dma_tx_chan = CH_SPORT1_TX, + .err_irq = IRQ_SPORT1_ERROR, + .regs = (struct sport_register *)SPORT1_TCR1, + } +}; + +/* + * Setting the TFS pin selector for SPORT 0 based on whether the selected + * port id F or G. If the port is F then no conflict should exist for the + * TFS. When Port G is selected and EMAC then there is a conflict between + * the PHY interrupt line and TFS. Current settings prevent the conflict + * by ignoring the TFS pin when Port G is selected. This allows both + * ssm2602 using Port G and EMAC concurrently. + */ +#ifdef CONFIG_BF527_SPORT0_PORTF +#define LOCAL_SPORT0_TFS (P_SPORT0_TFS) +#else +#define LOCAL_SPORT0_TFS (0) +#endif + +static u16 sport_req[][7] = { {P_SPORT0_DTPRI, P_SPORT0_TSCLK, P_SPORT0_RFS, + P_SPORT0_DRPRI, P_SPORT0_RSCLK, LOCAL_SPORT0_TFS, 0}, + {P_SPORT1_DTPRI, P_SPORT1_TSCLK, P_SPORT1_RFS, P_SPORT1_DRPRI, + P_SPORT1_RSCLK, P_SPORT1_TFS, 0} }; + +static int bf5xx_tdm_set_dai_fmt(struct snd_soc_dai *cpu_dai, + unsigned int fmt) +{ + int ret = 0; + + /* interface format:support TDM,slave mode */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + break; + default: + printk(KERN_ERR "%s: Unknown DAI format type\n", __func__); + ret = -EINVAL; + break; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + break; + case SND_SOC_DAIFMT_CBS_CFS: + case SND_SOC_DAIFMT_CBM_CFS: + case SND_SOC_DAIFMT_CBS_CFM: + ret = -EINVAL; + break; + default: + printk(KERN_ERR "%s: Unknown DAI master type\n", __func__); + ret = -EINVAL; + break; + } + + return ret; +} + +static int bf5xx_tdm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int ret = 0; + + bf5xx_tdm.tcr2 &= ~0x1f; + bf5xx_tdm.rcr2 &= ~0x1f; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S32_LE: + bf5xx_tdm.tcr2 |= 31; + bf5xx_tdm.rcr2 |= 31; + sport_handle->wdsize = 4; + break; + /* at present, we only support 32bit transfer */ + default: + pr_err("not supported PCM format yet\n"); + return -EINVAL; + break; + } + + if (!bf5xx_tdm.configured) { + /* + * TX and RX are not independent,they are enabled at the + * same time, even if only one side is running. So, we + * need to configure both of them at the time when the first + * stream is opened. + * + * CPU DAI:slave mode. + */ + ret = sport_config_rx(sport_handle, bf5xx_tdm.rcr1, + bf5xx_tdm.rcr2, 0, 0); + if (ret) { + pr_err("SPORT is busy!\n"); + return -EBUSY; + } + + ret = sport_config_tx(sport_handle, bf5xx_tdm.tcr1, + bf5xx_tdm.tcr2, 0, 0); + if (ret) { + pr_err("SPORT is busy!\n"); + return -EBUSY; + } + + bf5xx_tdm.configured = 1; + } + + return 0; +} + +static void bf5xx_tdm_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + /* No active stream, SPORT is allowed to be configured again. */ + if (!dai->active) + bf5xx_tdm.configured = 0; +} + +#ifdef CONFIG_PM +static int bf5xx_tdm_suspend(struct snd_soc_dai *dai) +{ + struct sport_device *sport = + (struct sport_device *)dai->private_data; + + if (!dai->active) + return 0; + if (dai->capture.active) + sport_rx_stop(sport); + if (dai->playback.active) + sport_tx_stop(sport); + return 0; +} + +static int bf5xx_tdm_resume(struct snd_soc_dai *dai) +{ + int ret; + struct sport_device *sport = + (struct sport_device *)dai->private_data; + + if (!dai->active) + return 0; + + ret = sport_set_multichannel(sport, 8, 0xFF, 1); + if (ret) { + pr_err("SPORT is busy!\n"); + ret = -EBUSY; + } + + ret = sport_config_rx(sport, IRFS, 0x1F, 0, 0); + if (ret) { + pr_err("SPORT is busy!\n"); + ret = -EBUSY; + } + + ret = sport_config_tx(sport, ITFS, 0x1F, 0, 0); + if (ret) { + pr_err("SPORT is busy!\n"); + ret = -EBUSY; + } + + return 0; +} + +#else +#define bf5xx_tdm_suspend NULL +#define bf5xx_tdm_resume NULL +#endif + +static struct snd_soc_dai_ops bf5xx_tdm_dai_ops = { + .hw_params = bf5xx_tdm_hw_params, + .set_fmt = bf5xx_tdm_set_dai_fmt, + .shutdown = bf5xx_tdm_shutdown, +}; + +struct snd_soc_dai bf5xx_tdm_dai = { + .name = "bf5xx-tdm", + .id = 0, + .suspend = bf5xx_tdm_suspend, + .resume = bf5xx_tdm_resume, + .playback = { + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE,}, + .capture = { + .channels_min = 2, + .channels_max = 8, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE,}, + .ops = &bf5xx_tdm_dai_ops, +}; +EXPORT_SYMBOL_GPL(bf5xx_tdm_dai); + +static int __devinit bfin_tdm_probe(struct platform_device *pdev) +{ + int ret = 0; + + if (peripheral_request_list(&sport_req[sport_num][0], "soc-audio")) { + pr_err("Requesting Peripherals failed\n"); + return -EFAULT; + } + + /* request DMA for SPORT */ + sport_handle = sport_init(&sport_params[sport_num], 4, \ + 8 * sizeof(u32), NULL); + if (!sport_handle) { + peripheral_free_list(&sport_req[sport_num][0]); + return -ENODEV; + } + + /* SPORT works in TDM mode */ + ret = sport_set_multichannel(sport_handle, 8, 0xFF, 1); + if (ret) { + pr_err("SPORT is busy!\n"); + ret = -EBUSY; + goto sport_config_err; + } + + ret = sport_config_rx(sport_handle, IRFS, 0x1F, 0, 0); + if (ret) { + pr_err("SPORT is busy!\n"); + ret = -EBUSY; + goto sport_config_err; + } + + ret = sport_config_tx(sport_handle, ITFS, 0x1F, 0, 0); + if (ret) { + pr_err("SPORT is busy!\n"); + ret = -EBUSY; + goto sport_config_err; + } + + ret = snd_soc_register_dai(&bf5xx_tdm_dai); + if (ret) { + pr_err("Failed to register DAI: %d\n", ret); + goto sport_config_err; + } + return 0; + +sport_config_err: + peripheral_free_list(&sport_req[sport_num][0]); + return ret; +} + +static int __devexit bfin_tdm_remove(struct platform_device *pdev) +{ + peripheral_free_list(&sport_req[sport_num][0]); + snd_soc_unregister_dai(&bf5xx_tdm_dai); + + return 0; +} + +static struct platform_driver bfin_tdm_driver = { + .probe = bfin_tdm_probe, + .remove = __devexit_p(bfin_tdm_remove), + .driver = { + .name = "bfin-tdm", + .owner = THIS_MODULE, + }, +}; + +static int __init bfin_tdm_init(void) +{ + return platform_driver_register(&bfin_tdm_driver); +} +module_init(bfin_tdm_init); + +static void __exit bfin_tdm_exit(void) +{ + platform_driver_unregister(&bfin_tdm_driver); +} +module_exit(bfin_tdm_exit); + +/* Module information */ +MODULE_AUTHOR("Barry Song"); +MODULE_DESCRIPTION("TDM driver for ADI Blackfin"); +MODULE_LICENSE("GPL"); + diff --git a/sound/soc/blackfin/bf5xx-tdm.h b/sound/soc/blackfin/bf5xx-tdm.h new file mode 100644 index 00000000000..e5157cd81c5 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-tdm.h @@ -0,0 +1,14 @@ +/* + * linux/sound/arm/bf5xx-i2s.h + * + * 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 _BF5XX_TDM_H +#define _BF5XX_TDM_H + +extern struct snd_soc_dai bf5xx_tdm_dai; + +#endif -- cgit v1.2.3 From c8489c3ed319677f23e5387fec09c844365566e8 Mon Sep 17 00:00:00 2001 From: Barry Song <21cnbao@gmail.com> Date: Thu, 23 Jul 2009 02:10:34 +0800 Subject: ASoC: board driver to connect bf5xx with ad1938 Signed-off-by: Barry Song <21cnbao@gmail.com> Signed-off-by: Mark Brown --- sound/soc/blackfin/Kconfig | 8 +++ sound/soc/blackfin/Makefile | 2 + sound/soc/blackfin/bf5xx-ad1938.c | 142 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 sound/soc/blackfin/bf5xx-ad1938.c (limited to 'sound/soc') diff --git a/sound/soc/blackfin/Kconfig b/sound/soc/blackfin/Kconfig index 2ab6d2fa87a..8a4de4de30f 100644 --- a/sound/soc/blackfin/Kconfig +++ b/sound/soc/blackfin/Kconfig @@ -96,6 +96,14 @@ config SND_BF5XX_SOC_AD1980 help Say Y if you want to add support for SoC audio on BF5xx STAMP/EZKIT. +config SND_BF5XX_SOC_AD1938 + tristate "SoC AD1938 Audio support for Blackfin" + depends on SND_BF5XX_TDM + select SND_BF5XX_SOC_TDM + select SND_SOC_AD1938 + help + Say Y if you want to add support for AD1938 codec on Blackfin. + config SND_BF5XX_SPORT_NUM int "Set a SPORT for Sound chip" depends on (SND_BF5XX_I2S || SND_BF5XX_AC97 || SND_BF5XX_TDM) diff --git a/sound/soc/blackfin/Makefile b/sound/soc/blackfin/Makefile index f86192623dd..f4d760741fa 100644 --- a/sound/soc/blackfin/Makefile +++ b/sound/soc/blackfin/Makefile @@ -19,7 +19,9 @@ obj-$(CONFIG_SND_BF5XX_SOC_TDM) += snd-soc-bf5xx-tdm.o snd-ad1980-objs := bf5xx-ad1980.o snd-ssm2602-objs := bf5xx-ssm2602.o snd-ad73311-objs := bf5xx-ad73311.o +snd-ad1938-objs := bf5xx-ad1938.o obj-$(CONFIG_SND_BF5XX_SOC_AD1980) += snd-ad1980.o obj-$(CONFIG_SND_BF5XX_SOC_SSM2602) += snd-ssm2602.o obj-$(CONFIG_SND_BF5XX_SOC_AD73311) += snd-ad73311.o +obj-$(CONFIG_SND_BF5XX_SOC_AD1938) += snd-ad1938.o diff --git a/sound/soc/blackfin/bf5xx-ad1938.c b/sound/soc/blackfin/bf5xx-ad1938.c new file mode 100644 index 00000000000..08269e91810 --- /dev/null +++ b/sound/soc/blackfin/bf5xx-ad1938.c @@ -0,0 +1,142 @@ +/* + * File: sound/soc/blackfin/bf5xx-ad1938.c + * Author: Barry Song + * + * Created: Thur June 4 2009 + * Description: Board driver for ad1938 sound chip + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../codecs/ad1938.h" +#include "bf5xx-sport.h" + +#include "bf5xx-tdm-pcm.h" +#include "bf5xx-tdm.h" + +static struct snd_soc_card bf5xx_ad1938; + +static int bf5xx_ad1938_startup(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + + cpu_dai->private_data = sport_handle; + return 0; +} + +static int bf5xx_ad1938_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 *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + int ret = 0; + /* set cpu DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_IB_IF | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* set codec DAI slots, 8 channels, all channels are enabled */ + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xFF, 8); + if (ret < 0) + return ret; + + return 0; +} + +static struct snd_soc_ops bf5xx_ad1938_ops = { + .startup = bf5xx_ad1938_startup, + .hw_params = bf5xx_ad1938_hw_params, +}; + +static struct snd_soc_dai_link bf5xx_ad1938_dai = { + .name = "ad1938", + .stream_name = "AD1938", + .cpu_dai = &bf5xx_tdm_dai, + .codec_dai = &ad1938_dai, + .ops = &bf5xx_ad1938_ops, +}; + +static struct snd_soc_card bf5xx_ad1938 = { + .name = "bf5xx_ad1938", + .platform = &bf5xx_tdm_soc_platform, + .dai_link = &bf5xx_ad1938_dai, + .num_links = 1, +}; + +static struct snd_soc_device bf5xx_ad1938_snd_devdata = { + .card = &bf5xx_ad1938, + .codec_dev = &soc_codec_dev_ad1938, +}; + +static struct platform_device *bfxx_ad1938_snd_device; + +static int __init bf5xx_ad1938_init(void) +{ + int ret; + + bfxx_ad1938_snd_device = platform_device_alloc("soc-audio", -1); + if (!bfxx_ad1938_snd_device) + return -ENOMEM; + + platform_set_drvdata(bfxx_ad1938_snd_device, &bf5xx_ad1938_snd_devdata); + bf5xx_ad1938_snd_devdata.dev = &bfxx_ad1938_snd_device->dev; + ret = platform_device_add(bfxx_ad1938_snd_device); + + if (ret) + platform_device_put(bfxx_ad1938_snd_device); + + return ret; +} + +static void __exit bf5xx_ad1938_exit(void) +{ + platform_device_unregister(bfxx_ad1938_snd_device); +} + +module_init(bf5xx_ad1938_init); +module_exit(bf5xx_ad1938_exit); + +/* Module information */ +MODULE_AUTHOR("Barry Song"); +MODULE_DESCRIPTION("ALSA SoC AD1938 board driver"); +MODULE_LICENSE("GPL"); + -- cgit v1.2.3 From 69331fbdeede932b64a0eaea6d196beaa9c40e0f Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Wed, 29 Jul 2009 16:40:18 +0200 Subject: ASoC: neo1973_gta02_wm8753: Replace snd_soc_cnew with snd_soc_add_controls. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown --- sound/soc/s3c24xx/neo1973_gta02_wm8753.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/neo1973_gta02_wm8753.c b/sound/soc/s3c24xx/neo1973_gta02_wm8753.c index 1358f6fe820..75ee63db791 100644 --- a/sound/soc/s3c24xx/neo1973_gta02_wm8753.c +++ b/sound/soc/s3c24xx/neo1973_gta02_wm8753.c @@ -338,7 +338,7 @@ static const struct snd_kcontrol_new wm8753_neo1973_gta02_controls[] = { */ static int neo1973_gta02_wm8753_init(struct snd_soc_codec *codec) { - int i, err; + int err; /* set up NC codec pins */ snd_soc_dapm_nc_pin(codec, "OUT3"); @@ -351,13 +351,11 @@ static int neo1973_gta02_wm8753_init(struct snd_soc_codec *codec) ARRAY_SIZE(wm8753_dapm_widgets)); /* add neo1973 gta02 specific controls */ - for (i = 0; i < ARRAY_SIZE(wm8753_neo1973_gta02_controls); i++) { - err = snd_ctl_add(codec->card, - snd_soc_cnew(&wm8753_neo1973_gta02_controls[i], - codec, NULL)); - if (err < 0) - return err; - } + err = snd_soc_add_controls(codec, wm8753_neo1973_gta02_controls, + ARRAY_SIZE(wm8753_neo1973_gta02_controls)); + + if (err < 0) + return err; /* set up neo1973 gta02 specific audio path audio_map */ snd_soc_dapm_add_routes(codec, audio_map, ARRAY_SIZE(audio_map)); -- cgit v1.2.3 From 82c4362ee3d8a0b8797ba77222b25c7671696e2b Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Wed, 29 Jul 2009 16:40:19 +0200 Subject: ASoC: neo1973_gta02_wm8753: Replace deprecated s3c_gpio calls with gpiolib With the s3c platform has implementing gpiolib support the s3c_gpio api has been deprecated. This patch gets rid of all s3c_gpio calls and replaces them by using gpiolib. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown --- sound/soc/s3c24xx/neo1973_gta02_wm8753.c | 53 ++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 17 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/neo1973_gta02_wm8753.c b/sound/soc/s3c24xx/neo1973_gta02_wm8753.c index 75ee63db791..0c52e36ddd8 100644 --- a/sound/soc/s3c24xx/neo1973_gta02_wm8753.c +++ b/sound/soc/s3c24xx/neo1973_gta02_wm8753.c @@ -1,5 +1,5 @@ /* - * neo1973_gta02_wm8753.c -- SoC audio for Neo1973 + * neo1973_gta02_wm8753.c -- SoC audio for Openmoko Freerunner(GTA02) * * Copyright 2007 Openmoko Inc * Author: Graeme Gregory @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -28,10 +29,7 @@ #include #include -#include -#include #include -#include #include #include "../codecs/wm8753.h" #include "s3c24xx-pcm.h" @@ -243,10 +241,10 @@ static int lm4853_set_spk(struct snd_kcontrol *kcontrol, if (val) { lm4853_state |= LM4853_SPK; - s3c2410_gpio_setpin(GTA02_GPIO_HP_IN, 0); + gpio_set_value(GTA02_GPIO_HP_IN, 0); } else { lm4853_state &= ~LM4853_SPK; - s3c2410_gpio_setpin(GTA02_GPIO_HP_IN, 1); + gpio_set_value(GTA02_GPIO_HP_IN, 1); } return 0; @@ -264,11 +262,7 @@ static int lm4853_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k, int event) { - if (SND_SOC_DAPM_EVENT_ON(event)) - s3c2410_gpio_setpin(GTA02_GPIO_AMP_SHUT, 0); - - if (SND_SOC_DAPM_EVENT_OFF(event)) - s3c2410_gpio_setpin(GTA02_GPIO_AMP_SHUT, 1); + gpio_set_value(GTA02_GPIO_AMP_SHUT, SND_SOC_DAPM_EVENT_OFF(value)); return 0; } @@ -453,15 +447,38 @@ static int __init neo1973_gta02_init(void) } /* Initialise GPIOs used by amp */ - s3c2410_gpio_cfgpin(GTA02_GPIO_HP_IN, S3C2410_GPIO_OUTPUT); - s3c2410_gpio_cfgpin(GTA02_GPIO_AMP_SHUT, S3C2410_GPIO_OUTPUT); + ret = gpio_request(GTA02_GPIO_HP_IN, "GTA02_HP_IN"); + if (ret) { + pr_err("gta02_wm8753: Failed to register GPIO %d\n", GTA02_GPIO_HP_IN); + goto err_unregister_device; + } - /* Amp off by default */ - s3c2410_gpio_setpin(GTA02_GPIO_AMP_SHUT, 1); + ret = gpio_direction_output(GTA02_GPIO_AMP_HP_IN, 1); + if (ret) { + pr_err("gta02_wm8753: Failed to configure GPIO %d\n", GTA02_GPIO_HP_IN); + goto err_free_gpio_hp_in; + } - /* Speaker off by default */ - s3c2410_gpio_setpin(GTA02_GPIO_HP_IN, 1); + ret = gpio_request(GTA02_GPIO_AMP_SHUT, "GTA02_AMP_SHUT"); + if (ret) { + pr_err("gta02_wm8753: Failed to register GPIO %d\n", GTA02_GPIO_AMP_SHUT); + goto err_free_gpio_hp_in; + } + ret = gpio_direction_output(GTA02_GPIO_AMP_SHUT, 1); + if (ret) { + pr_err("gta02_wm8753: Failed to configure GPIO %d\n", GTA02_GPIO_AMP_SHUT); + goto err_free_gpio_amp_shut; + } + + return 0; + +err_free_gpio_amp_shut: + gpio_free(GTA02_GPIO_AMP_SHUT); +err_free_gpio_hp_in: + gpio_free(GTA02_GPIO_HP_IN); +err_unregister_device: + platform_device_unregister(neo1973_gta02_snd_device); return ret; } module_init(neo1973_gta02_init); @@ -470,6 +487,8 @@ static void __exit neo1973_gta02_exit(void) { snd_soc_unregister_dai(&bt_dai); platform_device_unregister(neo1973_gta02_snd_device); + gpio_free(GTA02_GPIO_HP_IN); + gpio_free(GTA02_GPIO_AMP_SHUT); } module_exit(neo1973_gta02_exit); -- cgit v1.2.3 From 3a39f832a5699d50a6785de97c551033cc09d0ec Mon Sep 17 00:00:00 2001 From: Barry Song <21cnbao@gmail.com> Date: Wed, 29 Jul 2009 15:03:16 +0800 Subject: ASoC: Fix checkpatch issues and typos of ad1938 codec and bf5xx-tdm dai MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. fix "line over 80 characters" checkpatch warnings 2. ‘DMA_nnBIT_MASK’ is deprecated, use DMA_BIT_MASK instead 3. fix typos Signed-off-by: Barry Song <21cnbao@gmail.com> Signed-off-by: Mark Brown --- sound/soc/blackfin/bf5xx-tdm-pcm.c | 4 +-- sound/soc/blackfin/bf5xx-tdm-pcm.h | 2 +- sound/soc/blackfin/bf5xx-tdm.h | 2 +- sound/soc/codecs/ad1938.c | 50 +++++++++++++++++++++++++------------- 4 files changed, 37 insertions(+), 21 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/blackfin/bf5xx-tdm-pcm.c b/sound/soc/blackfin/bf5xx-tdm-pcm.c index af7fa8d7ff0..ccb5e823bd1 100644 --- a/sound/soc/blackfin/bf5xx-tdm-pcm.c +++ b/sound/soc/blackfin/bf5xx-tdm-pcm.c @@ -276,7 +276,7 @@ static void bf5xx_pcm_free_dma_buffers(struct snd_pcm *pcm) sport_done(sport_handle); } -static u64 bf5xx_pcm_dmamask = DMA_32BIT_MASK; +static u64 bf5xx_pcm_dmamask = DMA_BIT_MASK(32); static int bf5xx_pcm_tdm_new(struct snd_card *card, struct snd_soc_dai *dai, struct snd_pcm *pcm) @@ -286,7 +286,7 @@ static int bf5xx_pcm_tdm_new(struct snd_card *card, struct snd_soc_dai *dai, if (!card->dev->dma_mask) card->dev->dma_mask = &bf5xx_pcm_dmamask; if (!card->dev->coherent_dma_mask) - card->dev->coherent_dma_mask = DMA_32BIT_MASK; + card->dev->coherent_dma_mask = DMA_BIT_MASK(32); if (dai->playback.channels_min) { ret = bf5xx_pcm_preallocate_dma_buffer(pcm, diff --git a/sound/soc/blackfin/bf5xx-tdm-pcm.h b/sound/soc/blackfin/bf5xx-tdm-pcm.h index 9e8473d1c38..ddc5047df88 100644 --- a/sound/soc/blackfin/bf5xx-tdm-pcm.h +++ b/sound/soc/blackfin/bf5xx-tdm-pcm.h @@ -1,5 +1,5 @@ /* - * linux/sound/arm/bf5xx-tdm-pcm.h -- ALSA PCM interface for the Blackfin + * sound/soc/blackfin/bf5xx-tdm-pcm.h -- ALSA PCM interface for the Blackfin * * Copyright 2009 Analog Device Inc. * diff --git a/sound/soc/blackfin/bf5xx-tdm.h b/sound/soc/blackfin/bf5xx-tdm.h index e5157cd81c5..618ec3d90cd 100644 --- a/sound/soc/blackfin/bf5xx-tdm.h +++ b/sound/soc/blackfin/bf5xx-tdm.h @@ -1,5 +1,5 @@ /* - * linux/sound/arm/bf5xx-i2s.h + * sound/soc/blackfin/bf5xx-tdm.h * * 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 diff --git a/sound/soc/codecs/ad1938.c b/sound/soc/codecs/ad1938.c index ffc97c9b797..aafda743fc8 100644 --- a/sound/soc/codecs/ad1938.c +++ b/sound/soc/codecs/ad1938.c @@ -72,14 +72,20 @@ static const struct snd_kcontrol_new ad1938_snd_controls[] = { AD1938_DAC_R4_VOL, 0, 0xFF, 1), /* ADC switch control */ - SOC_DOUBLE("ADC1 Switch", AD1938_ADC_CTRL0, AD1938_ADCL1_MUTE, AD1938_ADCR1_MUTE, 1, 1), - SOC_DOUBLE("ADC2 Switch", AD1938_ADC_CTRL0, AD1938_ADCL2_MUTE, AD1938_ADCR2_MUTE, 1, 1), + SOC_DOUBLE("ADC1 Switch", AD1938_ADC_CTRL0, AD1938_ADCL1_MUTE, + AD1938_ADCR1_MUTE, 1, 1), + SOC_DOUBLE("ADC2 Switch", AD1938_ADC_CTRL0, AD1938_ADCL2_MUTE, + AD1938_ADCR2_MUTE, 1, 1), /* DAC switch control */ - SOC_DOUBLE("DAC1 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL1_MUTE, AD1938_DACR1_MUTE, 1, 1), - SOC_DOUBLE("DAC2 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL2_MUTE, AD1938_DACR2_MUTE, 1, 1), - SOC_DOUBLE("DAC3 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL3_MUTE, AD1938_DACR3_MUTE, 1, 1), - SOC_DOUBLE("DAC4 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL4_MUTE, AD1938_DACR4_MUTE, 1, 1), + SOC_DOUBLE("DAC1 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL1_MUTE, + AD1938_DACR1_MUTE, 1, 1), + SOC_DOUBLE("DAC2 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL2_MUTE, + AD1938_DACR2_MUTE, 1, 1), + SOC_DOUBLE("DAC3 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL3_MUTE, + AD1938_DACR3_MUTE, 1, 1), + SOC_DOUBLE("DAC4 Switch", AD1938_DAC_CHNL_MUTE, AD1938_DACL4_MUTE, + AD1938_DACR4_MUTE, 1, 1), /* ADC high-pass filter */ SOC_SINGLE("ADC High Pass Filter Switch", AD1938_ADC_CTRL0, @@ -110,7 +116,8 @@ static int ad1938_mute(struct snd_soc_dai *dai, int mute) int reg; reg = codec->read(codec, AD1938_DAC_CTRL2); - reg = (mute > 0) ? reg | AD1938_DAC_MASTER_MUTE : reg & (~AD1938_DAC_MASTER_MUTE); + reg = (mute > 0) ? reg | AD1938_DAC_MASTER_MUTE : reg & + (~AD1938_DAC_MASTER_MUTE); codec->write(codec, AD1938_DAC_CTRL2, reg); return 0; @@ -119,7 +126,8 @@ static int ad1938_mute(struct snd_soc_dai *dai, int mute) static inline int ad1938_pll_powerctrl(struct snd_soc_codec *codec, int cmd) { int reg = codec->read(codec, AD1938_PLL_CLK_CTRL0); - reg = (cmd > 0) ? reg & (~AD1938_PLL_POWERDOWN) : reg | AD1938_PLL_POWERDOWN; + reg = (cmd > 0) ? reg & (~AD1938_PLL_POWERDOWN) : reg | + AD1938_PLL_POWERDOWN; codec->write(codec, AD1938_PLL_CLK_CTRL0, reg); return 0; @@ -171,8 +179,8 @@ static int ad1938_set_dai_fmt(struct snd_soc_dai *codec_dai, adc_reg = codec->read(codec, AD1938_ADC_CTRL2); dac_reg = codec->read(codec, AD1938_DAC_CTRL1); - /* At present, the driver only support AUX ADC mode(SND_SOC_DAIFMT_I2S with TDM) - * and ADC&DAC TDM mode(SND_SOC_DAIFMT_DSP_A) + /* At present, the driver only support AUX ADC mode(SND_SOC_DAIFMT_I2S + * with TDM) and ADC&DAC TDM mode(SND_SOC_DAIFMT_DSP_A) */ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: @@ -363,7 +371,8 @@ static unsigned int ad1938_read_reg_cache(struct snd_soc_codec *codec, * read from the ad1938 register space */ -static unsigned int ad1938_read_reg(struct snd_soc_codec *codec, unsigned int reg) +static unsigned int ad1938_read_reg(struct snd_soc_codec *codec, + unsigned int reg) { char w_buf[AD1938_SPI_BUFLEN]; char r_buf[AD1938_SPI_BUFLEN]; @@ -502,12 +511,19 @@ static int ad1938_register(struct ad1938_priv *ad1938) ad1938_codec = codec; /* default setting for ad1938 */ - codec->write(codec, AD1938_DAC_CHNL_MUTE, 0x0); /* unmute dac channels */ - codec->write(codec, AD1938_DAC_CTRL2, 0x1A); /* de-emphasis: 48kHz, powedown dac */ - codec->write(codec, AD1938_DAC_CTRL0, 0x21); /* powerdown dac, dac tdm mode */ - codec->write(codec, AD1938_ADC_CTRL0, 0x3); /* high-pass filter enable */ - codec->write(codec, AD1938_ADC_CTRL1, 0x43); /* sata delay=1, adc aux mode */ - codec->write(codec, AD1938_PLL_CLK_CTRL0, 0x9D); /* pll input:mclki/xi */ + + /* unmute dac channels */ + codec->write(codec, AD1938_DAC_CHNL_MUTE, 0x0); + /* de-emphasis: 48kHz, powedown dac */ + codec->write(codec, AD1938_DAC_CTRL2, 0x1A); + /* powerdown dac, dac in tdm mode */ + codec->write(codec, AD1938_DAC_CTRL0, 0x41); + /* high-pass filter enable */ + codec->write(codec, AD1938_ADC_CTRL0, 0x3); + /* sata delay=1, adc aux mode */ + codec->write(codec, AD1938_ADC_CTRL1, 0x43); + /* pll input: mclki/xi */ + codec->write(codec, AD1938_PLL_CLK_CTRL0, 0x9D); codec->write(codec, AD1938_PLL_CLK_CTRL1, 0x04); ad1938_fill_cache(codec); -- cgit v1.2.3 From 77ee09c67e051a5ebd19a53ba3945dbdc8d21b3c Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 31 Jul 2009 18:26:51 +0100 Subject: ASoC: Allow CODECs to flag invalid registers This helps CODECs with sparse register maps work better with the register cache display interface. Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 5eabb711760..fb8d7a76615 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1156,6 +1156,9 @@ static ssize_t soc_codec_reg_show(struct snd_soc_codec *codec, char *buf) count += sprintf(buf, "%s registers\n", codec->name); for (i = 0; i < codec->reg_cache_size; i += step) { + if (codec->readable_register && !codec->readable_register(i)) + continue; + count += sprintf(buf + count, "%2x: ", i); if (count >= PAGE_SIZE - 1) break; -- cgit v1.2.3 From b8e22c1fe375a8c3b3e4fd7c569cb8932607bef3 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Fri, 31 Jul 2009 21:15:25 +0200 Subject: ASoC: jack: Fix race in snd_soc_jack_add_gpios The irq can fire as soon as it has been requested, thus all fields accessed from within the irq handler must be initialized prior to requesting the irq. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown --- sound/soc/soc-jack.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-jack.c b/sound/soc/soc-jack.c index 4aa7d8f8ce7..1d455ab7949 100644 --- a/sound/soc/soc-jack.c +++ b/sound/soc/soc-jack.c @@ -221,6 +221,9 @@ int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count, if (ret) goto err; + INIT_WORK(&gpios[i].work, gpio_work); + gpios[i].jack = jack; + ret = request_irq(gpio_to_irq(gpios[i].gpio), gpio_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, @@ -234,9 +237,6 @@ int snd_soc_jack_add_gpios(struct snd_soc_jack *jack, int count, gpio_export(gpios[i].gpio, false); #endif - INIT_WORK(&gpios[i].work, gpio_work); - gpios[i].jack = jack; - /* Update initial jack status */ snd_soc_jack_gpio_detect(&gpios[i]); } -- cgit v1.2.3 From ad120dae127f5cb14b4c4ed0b98313fb13b9c846 Mon Sep 17 00:00:00 2001 From: Janusz Krzysztofik Date: Wed, 29 Jul 2009 12:24:46 +0200 Subject: ASoC: CX20442: push down machine independent line discipline bits This corrected patch adds machine independent line discipline code, prevoiusly exsiting inside my Amstrad Delta ASoC machine dirver, to the Conexant CX20442 codec driver. The code can be used as a standalone line discipline, or as a set of codec specific functions called from machine's line discipline callbacks. Anyway, the line discipline itself must be registered by a machine driver. Applies on top of the followup to my initial driver version: http://mailman.alsa-project.org/pipermail/alsa-devel/2009-July/019757.html Suggested by ASoC manintainer Mark Brown Signed-off-by: Janusz Krzysztofik Signed-off-by: Mark Brown --- sound/soc/codecs/cx20442.c | 116 ++++++++++++++++++++++++++++++++++++++++++--- sound/soc/codecs/cx20442.h | 1 + 2 files changed, 111 insertions(+), 6 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/cx20442.c b/sound/soc/codecs/cx20442.c index 7bbb77baa23..f33245510c3 100644 --- a/sound/soc/codecs/cx20442.c +++ b/sound/soc/codecs/cx20442.c @@ -13,6 +13,8 @@ * option) any later version. */ +#include + #include #include #include @@ -172,8 +174,7 @@ static int cx20442_write(struct snd_soc_codec *codec, unsigned int reg, return -EINVAL; /* hw_write and control_data pointers required for talking to the modem - * are expected to be set by the machine driver's line discipline - * initialization code */ + * are expected to be set by the line discipline initialization code */ if (!codec->hw_write || !codec->control_data) return -EIO; @@ -208,6 +209,111 @@ static int cx20442_write(struct snd_soc_codec *codec, unsigned int reg, return 0; } + +/* Moved up here as line discipline referres it during initialization */ +static struct snd_soc_codec *cx20442_codec; + + +/* + * Line discpline related code + * + * Any of the callback functions below can be used in two ways: + * 1) registerd by a machine driver as one of line discipline operations, + * 2) called from a machine's provided line discipline callback function + * in case when extra machine specific code must be run as well. + */ + +/* Modem init: echo off, digital speaker off, quiet off, voice mode */ +static const char *v253_init = "ate0m0q0+fclass=8\r"; + +/* Line discipline .open() */ +static int v253_open(struct tty_struct *tty) +{ + struct snd_soc_codec *codec = cx20442_codec; + int ret, len = strlen(v253_init); + + /* Doesn't make sense without write callback */ + if (!tty->ops->write) + return -EINVAL; + + /* Pass the codec structure address for use by other ldisc callbacks */ + tty->disc_data = codec; + + if (tty->ops->write(tty, v253_init, len) != len) { + ret = -EIO; + goto err; + } + /* Actual setup will be performed after the modem responds. */ + return 0; +err: + tty->disc_data = NULL; + return ret; +} + +/* Line discipline .close() */ +static void v253_close(struct tty_struct *tty) +{ + struct snd_soc_codec *codec = tty->disc_data; + + tty->disc_data = NULL; + + if (!codec) + return; + + /* Prevent the codec driver from further accessing the modem */ + codec->hw_write = NULL; + codec->control_data = NULL; + codec->pop_time = 0; +} + +/* Line discipline .hangup() */ +static int v253_hangup(struct tty_struct *tty) +{ + v253_close(tty); + return 0; +} + +/* Line discipline .receive_buf() */ +static void v253_receive(struct tty_struct *tty, + const unsigned char *cp, char *fp, int count) +{ + struct snd_soc_codec *codec = tty->disc_data; + + if (!codec) + return; + + if (!codec->control_data) { + /* First modem response, complete setup procedure */ + + /* Set up codec driver access to modem controls */ + codec->control_data = tty; + codec->hw_write = (hw_write_t)tty->ops->write; + codec->pop_time = 1; + } +} + +/* Line discipline .write_wakeup() */ +static void v253_wakeup(struct tty_struct *tty) +{ +} + +struct tty_ldisc_ops v253_ops = { + .magic = TTY_LDISC_MAGIC, + .name = "cx20442", + .owner = THIS_MODULE, + .open = v253_open, + .close = v253_close, + .hangup = v253_hangup, + .receive_buf = v253_receive, + .write_wakeup = v253_wakeup, +}; +EXPORT_SYMBOL_GPL(v253_ops); + + +/* + * Codec DAI + */ + struct snd_soc_dai cx20442_dai = { .name = "CX20442", .playback = { @@ -227,8 +333,6 @@ struct snd_soc_dai cx20442_dai = { }; EXPORT_SYMBOL_GPL(cx20442_dai); -static struct snd_soc_codec *cx20442_codec; - static int cx20442_codec_probe(struct platform_device *pdev) { struct snd_soc_device *socdev = platform_get_drvdata(pdev); @@ -313,13 +417,13 @@ static int cx20442_register(struct cx20442_priv *cx20442) ret = snd_soc_register_codec(codec); if (ret != 0) { - dev_err(&codec->dev, "Failed to register codec: %d\n", ret); + dev_err(codec->dev, "Failed to register codec: %d\n", ret); goto err; } ret = snd_soc_register_dai(&cx20442_dai); if (ret != 0) { - dev_err(&codec->dev, "Failed to register DAI: %d\n", ret); + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); goto err_codec; } diff --git a/sound/soc/codecs/cx20442.h b/sound/soc/codecs/cx20442.h index d0a4f297aef..688a5eb62e1 100644 --- a/sound/soc/codecs/cx20442.h +++ b/sound/soc/codecs/cx20442.h @@ -15,5 +15,6 @@ extern struct snd_soc_dai cx20442_dai; extern struct snd_soc_codec_device cx20442_codec_dev; +extern struct tty_ldisc_ops v253_ops; #endif -- cgit v1.2.3 From 6d7f68a1eab3d5b3c0a6a5cf9434c77cd3a8c6ac Mon Sep 17 00:00:00 2001 From: Janusz Krzysztofik Date: Wed, 29 Jul 2009 13:18:53 +0200 Subject: ASoC: add support for Amstrad E3 (Delta) machine This patch adds machine support for Amstrad E3 (Delta) videophone to ASoC. Created and tested against linux-2.6.31-rc3. Applies and works with linux-omap-2.6 commit 7c5cb7862d32cb344be7831d466535d5255e35ac as well. Depends on: 1) latest version of the CX20442 codec driver that exposes v253_ops structure[1], 2) patch 2/3 form this series: TTY: Add definition of a new line discipline required by Amstrad E3 (Delta) ASoC driver[2]. CPU DAI parameters best matching the codec DAI has been selected out empirically for best user experience. Board specific audio function control (with related DAPM widgets) has been modeled after empirically discovered codec capabilities. Unlike other ASoC machine drivers, this one makes use of a codec provided line discipline that is required for talking to a modem chip that can control the codec behavoiur. As the line discipline operations must call board specific bits as well, the machine driver registers its own line discipline ops, not the codec provided, and then calls those codec provided from inside its own callbacks. If some kind of a glue, like a bus over a tty, exsited that could help in runtime detection of a modem (bus adapter) over a more generic line discipline (bus driver)[3], the line discipline code could be probably designed in a more generic way. In order to work at all, this driver requires a working McBSP1. On OMAP1510 based machines (not sure if other OMAP1 variants as well), where McBSP1 is a DSP public peripheral, that means the kernel must provide basic DSP support, ie. omap_dsp_init(), in order to power up the DSP. This used to be included in linux-omap-2.6 tree up to commit 2512fd29db4eb09e82d182596304c7aaf76d2c5c. Without that, the driver would not work, ie. not shift in/out any bits over the CPU DAI[4]. This limitation is not board, but CPU specific, and may apply to other code that makes use of McBSP1/McBSP3 on affected machines. I provide an extra patch (4/3) as a temporary solution. To work correctly in playback mode, this driver requires my prevoiusly submitted patch that corrects pcm pointer calculation for OMAP1510 based machines[5] (already included in linux-2.6.31-rc3). To support codec controls, this driver requires my previously submitted patch that adds support for modem found on Amstrad Delta[6]. [1] http://mailman.alsa-project.org/pipermail/alsa-devel/2009-July/019780.html [2] http://www.spinics.net/lists/linux-serial/msg01862.html [3] http://www.spinics.net/lists/linux-serial/msg01856.html [4] http://www.spinics.net/lists/linux-omap/msg15114.html [5] http://mailman.alsa-project.org/pipermail/alsa-devel/2009-June/018950.html [6] http://www.spinics.net/lists/linux-omap/msg15432.html Credits to: Mark Underwood - for his initial, omap-alsa based sound driver for this machine, Mark Brown - for his help, patience and excellent subsytem maintainer support. Signed-off-by: Janusz Krzysztofik Signed-off-by: Mark Brown --- sound/soc/omap/Kconfig | 8 + sound/soc/omap/Makefile | 2 + sound/soc/omap/ams-delta.c | 646 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 656 insertions(+) create mode 100644 sound/soc/omap/ams-delta.c (limited to 'sound/soc') diff --git a/sound/soc/omap/Kconfig b/sound/soc/omap/Kconfig index a5a90e59453..2dee9839be8 100644 --- a/sound/soc/omap/Kconfig +++ b/sound/soc/omap/Kconfig @@ -15,6 +15,14 @@ config SND_OMAP_SOC_N810 help Say Y if you want to add support for SoC audio on Nokia N810. +config SND_OMAP_SOC_AMS_DELTA + tristate "SoC Audio support for Amstrad E3 (Delta) videophone" + depends on SND_OMAP_SOC && MACH_AMS_DELTA + select SND_OMAP_SOC_MCBSP + select SND_SOC_CX20442 + help + Say Y if you want to add support for SoC audio on Amstrad Delta. + config SND_OMAP_SOC_OSK5912 tristate "SoC Audio support for omap osk5912" depends on SND_OMAP_SOC && MACH_OMAP_OSK && I2C diff --git a/sound/soc/omap/Makefile b/sound/soc/omap/Makefile index fefc48f02bd..02d69471dcb 100644 --- a/sound/soc/omap/Makefile +++ b/sound/soc/omap/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_SND_OMAP_SOC_MCBSP) += snd-soc-omap-mcbsp.o # OMAP Machine Support snd-soc-n810-objs := n810.o +snd-soc-ams-delta-objs := ams-delta.o snd-soc-osk5912-objs := osk5912.o snd-soc-overo-objs := overo.o snd-soc-omap2evm-objs := omap2evm.o @@ -17,6 +18,7 @@ snd-soc-omap3beagle-objs := omap3beagle.o snd-soc-zoom2-objs := zoom2.o obj-$(CONFIG_SND_OMAP_SOC_N810) += snd-soc-n810.o +obj-$(CONFIG_SND_OMAP_SOC_AMS_DELTA) += snd-soc-ams-delta.o obj-$(CONFIG_SND_OMAP_SOC_OSK5912) += snd-soc-osk5912.o obj-$(CONFIG_SND_OMAP_SOC_OVERO) += snd-soc-overo.o obj-$(CONFIG_MACH_OMAP2EVM) += snd-soc-omap2evm.o diff --git a/sound/soc/omap/ams-delta.c b/sound/soc/omap/ams-delta.c new file mode 100644 index 00000000000..4f35b1f18cb --- /dev/null +++ b/sound/soc/omap/ams-delta.c @@ -0,0 +1,646 @@ +/* + * ams-delta.c -- SoC audio for Amstrad E3 (Delta) videophone + * + * Copyright (C) 2009 Janusz Krzysztofik + * + * Initially based on sound/soc/omap/osk5912.x + * Copyright (C) 2008 Mistral Solutions + * + * 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. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include "omap-mcbsp.h" +#include "omap-pcm.h" +#include "../codecs/cx20442.h" + + +/* Board specific DAPM widgets */ + const struct snd_soc_dapm_widget ams_delta_dapm_widgets[] = { + /* Handset */ + SND_SOC_DAPM_MIC("Mouthpiece", NULL), + SND_SOC_DAPM_HP("Earpiece", NULL), + /* Handsfree/Speakerphone */ + SND_SOC_DAPM_MIC("Microphone", NULL), + SND_SOC_DAPM_SPK("Speaker", NULL), +}; + +/* How they are connected to codec pins */ +static const struct snd_soc_dapm_route ams_delta_audio_map[] = { + {"TELIN", NULL, "Mouthpiece"}, + {"Earpiece", NULL, "TELOUT"}, + + {"MIC", NULL, "Microphone"}, + {"Speaker", NULL, "SPKOUT"}, +}; + +/* + * Controls, functional after the modem line discipline is activated. + */ + +/* Virtual switch: audio input/output constellations */ +static const char *ams_delta_audio_mode[] = + {"Mixed", "Handset", "Handsfree", "Speakerphone"}; + +/* Selection <-> pin translation */ +#define AMS_DELTA_MOUTHPIECE 0 +#define AMS_DELTA_EARPIECE 1 +#define AMS_DELTA_MICROPHONE 2 +#define AMS_DELTA_SPEAKER 3 +#define AMS_DELTA_AGC 4 + +#define AMS_DELTA_MIXED ((1 << AMS_DELTA_EARPIECE) | \ + (1 << AMS_DELTA_MICROPHONE)) +#define AMS_DELTA_HANDSET ((1 << AMS_DELTA_MOUTHPIECE) | \ + (1 << AMS_DELTA_EARPIECE)) +#define AMS_DELTA_HANDSFREE ((1 << AMS_DELTA_MICROPHONE) | \ + (1 << AMS_DELTA_SPEAKER)) +#define AMS_DELTA_SPEAKERPHONE (AMS_DELTA_HANDSFREE | (1 << AMS_DELTA_AGC)) + +unsigned short ams_delta_audio_mode_pins[] = { + AMS_DELTA_MIXED, + AMS_DELTA_HANDSET, + AMS_DELTA_HANDSFREE, + AMS_DELTA_SPEAKERPHONE, +}; + +static unsigned short ams_delta_audio_agc; + +static int ams_delta_set_audio_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct soc_enum *control = (struct soc_enum *)kcontrol->private_value; + unsigned short pins; + int pin, changed = 0; + + /* Refuse any mode changes if we are not able to control the codec. */ + if (!codec->control_data) + return -EUNATCH; + + if (ucontrol->value.enumerated.item[0] >= control->max) + return -EINVAL; + + mutex_lock(&codec->mutex); + + /* Translate selection to bitmap */ + pins = ams_delta_audio_mode_pins[ucontrol->value.enumerated.item[0]]; + + /* Setup pins after corresponding bits if changed */ + pin = !!(pins & (1 << AMS_DELTA_MOUTHPIECE)); + if (pin != snd_soc_dapm_get_pin_status(codec, "Mouthpiece")) { + changed = 1; + if (pin) + snd_soc_dapm_enable_pin(codec, "Mouthpiece"); + else + snd_soc_dapm_disable_pin(codec, "Mouthpiece"); + } + pin = !!(pins & (1 << AMS_DELTA_EARPIECE)); + if (pin != snd_soc_dapm_get_pin_status(codec, "Earpiece")) { + changed = 1; + if (pin) + snd_soc_dapm_enable_pin(codec, "Earpiece"); + else + snd_soc_dapm_disable_pin(codec, "Earpiece"); + } + pin = !!(pins & (1 << AMS_DELTA_MICROPHONE)); + if (pin != snd_soc_dapm_get_pin_status(codec, "Microphone")) { + changed = 1; + if (pin) + snd_soc_dapm_enable_pin(codec, "Microphone"); + else + snd_soc_dapm_disable_pin(codec, "Microphone"); + } + pin = !!(pins & (1 << AMS_DELTA_SPEAKER)); + if (pin != snd_soc_dapm_get_pin_status(codec, "Speaker")) { + changed = 1; + if (pin) + snd_soc_dapm_enable_pin(codec, "Speaker"); + else + snd_soc_dapm_disable_pin(codec, "Speaker"); + } + pin = !!(pins & (1 << AMS_DELTA_AGC)); + if (pin != ams_delta_audio_agc) { + ams_delta_audio_agc = pin; + changed = 1; + if (pin) + snd_soc_dapm_enable_pin(codec, "AGCIN"); + else + snd_soc_dapm_disable_pin(codec, "AGCIN"); + } + if (changed) + snd_soc_dapm_sync(codec); + + mutex_unlock(&codec->mutex); + + return changed; +} + +static int ams_delta_get_audio_mode(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + unsigned short pins, mode; + + pins = ((snd_soc_dapm_get_pin_status(codec, "Mouthpiece") << + AMS_DELTA_MOUTHPIECE) | + (snd_soc_dapm_get_pin_status(codec, "Earpiece") << + AMS_DELTA_EARPIECE)); + if (pins) + pins |= (snd_soc_dapm_get_pin_status(codec, "Microphone") << + AMS_DELTA_MICROPHONE); + else + pins = ((snd_soc_dapm_get_pin_status(codec, "Microphone") << + AMS_DELTA_MICROPHONE) | + (snd_soc_dapm_get_pin_status(codec, "Speaker") << + AMS_DELTA_SPEAKER) | + (ams_delta_audio_agc << AMS_DELTA_AGC)); + + for (mode = 0; mode < ARRAY_SIZE(ams_delta_audio_mode); mode++) + if (pins == ams_delta_audio_mode_pins[mode]) + break; + + if (mode >= ARRAY_SIZE(ams_delta_audio_mode)) + return -EINVAL; + + ucontrol->value.enumerated.item[0] = mode; + + return 0; +} + +static const struct soc_enum ams_delta_audio_enum[] = { + SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(ams_delta_audio_mode), + ams_delta_audio_mode), +}; + +static const struct snd_kcontrol_new ams_delta_audio_controls[] = { + SOC_ENUM_EXT("Audio Mode", ams_delta_audio_enum[0], + ams_delta_get_audio_mode, ams_delta_set_audio_mode), +}; + +/* Hook switch */ +static struct snd_soc_jack ams_delta_hook_switch; +static struct snd_soc_jack_gpio ams_delta_hook_switch_gpios[] = { + { + .gpio = 4, + .name = "hook_switch", + .report = SND_JACK_HEADSET, + .invert = 1, + .debounce_time = 150, + } +}; + +/* After we are able to control the codec over the modem, + * the hook switch can be used for dynamic DAPM reconfiguration. */ +static struct snd_soc_jack_pin ams_delta_hook_switch_pins[] = { + /* Handset */ + { + .pin = "Mouthpiece", + .mask = SND_JACK_MICROPHONE, + }, + { + .pin = "Earpiece", + .mask = SND_JACK_HEADPHONE, + }, + /* Handsfree */ + { + .pin = "Microphone", + .mask = SND_JACK_MICROPHONE, + .invert = 1, + }, + { + .pin = "Speaker", + .mask = SND_JACK_HEADPHONE, + .invert = 1, + }, +}; + + +/* + * Modem line discipline, required for making above controls functional. + * Activated from userspace with ldattach, possibly invoked from udev rule. + */ + +/* To actually apply any modem controlled configuration changes to the codec, + * we must connect codec DAI pins to the modem for a moment. Be carefull not + * to interfere with our digital mute function that shares the same hardware. */ +static struct timer_list cx81801_timer; +static bool cx81801_cmd_pending; +static bool ams_delta_muted; +static DEFINE_SPINLOCK(ams_delta_lock); + +static void cx81801_timeout(unsigned long data) +{ + int muted; + + spin_lock(&ams_delta_lock); + cx81801_cmd_pending = 0; + muted = ams_delta_muted; + spin_unlock(&ams_delta_lock); + + /* Reconnect the codec DAI back from the modem to the CPU DAI + * only if digital mute still off */ + if (!muted) + ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_CODEC, 0); +} + +/* Line discipline .open() */ +static int cx81801_open(struct tty_struct *tty) +{ + return v253_ops.open(tty); +} + +/* Line discipline .close() */ +static void cx81801_close(struct tty_struct *tty) +{ + struct snd_soc_codec *codec = tty->disc_data; + + del_timer_sync(&cx81801_timer); + + v253_ops.close(tty); + + /* Prevent the hook switch from further changing the DAPM pins */ + INIT_LIST_HEAD(&ams_delta_hook_switch.pins); + + /* Revert back to default audio input/output constellation */ + snd_soc_dapm_disable_pin(codec, "Mouthpiece"); + snd_soc_dapm_enable_pin(codec, "Earpiece"); + snd_soc_dapm_enable_pin(codec, "Microphone"); + snd_soc_dapm_disable_pin(codec, "Speaker"); + snd_soc_dapm_disable_pin(codec, "AGCIN"); + snd_soc_dapm_sync(codec); +} + +/* Line discipline .hangup() */ +static int cx81801_hangup(struct tty_struct *tty) +{ + cx81801_close(tty); + return 0; +} + +/* Line discipline .recieve_buf() */ +static void cx81801_receive(struct tty_struct *tty, + const unsigned char *cp, char *fp, int count) +{ + struct snd_soc_codec *codec = tty->disc_data; + const unsigned char *c; + int apply, ret; + + if (!codec->control_data) { + /* First modem response, complete setup procedure */ + + /* Initialize timer used for config pulse generation */ + setup_timer(&cx81801_timer, cx81801_timeout, 0); + + v253_ops.receive_buf(tty, cp, fp, count); + + /* Link hook switch to DAPM pins */ + ret = snd_soc_jack_add_pins(&ams_delta_hook_switch, + ARRAY_SIZE(ams_delta_hook_switch_pins), + ams_delta_hook_switch_pins); + if (ret) + dev_warn(codec->socdev->card->dev, + "Failed to link hook switch to DAPM pins, " + "will continue with hook switch unlinked.\n"); + + return; + } + + v253_ops.receive_buf(tty, cp, fp, count); + + for (c = &cp[count - 1]; c >= cp; c--) { + if (*c != '\r') + continue; + /* Complete modem response received, apply config to codec */ + + spin_lock_bh(&ams_delta_lock); + mod_timer(&cx81801_timer, jiffies + msecs_to_jiffies(150)); + apply = !ams_delta_muted && !cx81801_cmd_pending; + cx81801_cmd_pending = 1; + spin_unlock_bh(&ams_delta_lock); + + /* Apply config pulse by connecting the codec to the modem + * if not already done */ + if (apply) + ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_CODEC, + AMS_DELTA_LATCH2_MODEM_CODEC); + break; + } +} + +/* Line discipline .write_wakeup() */ +static void cx81801_wakeup(struct tty_struct *tty) +{ + v253_ops.write_wakeup(tty); +} + +static struct tty_ldisc_ops cx81801_ops = { + .magic = TTY_LDISC_MAGIC, + .name = "cx81801", + .owner = THIS_MODULE, + .open = cx81801_open, + .close = cx81801_close, + .hangup = cx81801_hangup, + .receive_buf = cx81801_receive, + .write_wakeup = cx81801_wakeup, +}; + + +/* + * Even if not very usefull, the sound card can still work without any of the + * above functonality activated. You can still control its audio input/output + * constellation and speakerphone gain from userspace by issueing AT commands + * over the modem port. + */ + +static int ams_delta_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + + /* Set cpu DAI configuration */ + return snd_soc_dai_set_fmt(rtd->dai->cpu_dai, + SND_SOC_DAIFMT_DSP_A | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBM_CFM); +} + +static struct snd_soc_ops ams_delta_ops = { + .hw_params = ams_delta_hw_params, +}; + + +/* Board specific codec bias level control */ +static int ams_delta_set_bias_level(struct snd_soc_card *card, + enum snd_soc_bias_level level) +{ + struct snd_soc_codec *codec = card->codec; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) + ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_NRESET, + AMS_DELTA_LATCH2_MODEM_NRESET); + break; + case SND_SOC_BIAS_OFF: + if (codec->bias_level != SND_SOC_BIAS_OFF) + ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_NRESET, + 0); + } + codec->bias_level = level; + + return 0; +} + +/* Digital mute implemented using modem/CPU multiplexer. + * Shares hardware with codec config pulse generation */ +static bool ams_delta_muted = 1; + +static int ams_delta_digital_mute(struct snd_soc_dai *dai, int mute) +{ + int apply; + + if (ams_delta_muted == mute) + return 0; + + spin_lock_bh(&ams_delta_lock); + ams_delta_muted = mute; + apply = !cx81801_cmd_pending; + spin_unlock_bh(&ams_delta_lock); + + if (apply) + ams_delta_latch2_write(AMS_DELTA_LATCH2_MODEM_CODEC, + mute ? AMS_DELTA_LATCH2_MODEM_CODEC : 0); + return 0; +} + +/* Our codec DAI probably doesn't have its own .ops structure */ +static struct snd_soc_dai_ops ams_delta_dai_ops = { + .digital_mute = ams_delta_digital_mute, +}; + +/* Will be used if the codec ever has its own digital_mute function */ +static int ams_delta_startup(struct snd_pcm_substream *substream) +{ + return ams_delta_digital_mute(NULL, 0); +} + +static void ams_delta_shutdown(struct snd_pcm_substream *substream) +{ + ams_delta_digital_mute(NULL, 1); +} + + +/* + * Card initialization + */ + +static int ams_delta_cx20442_init(struct snd_soc_codec *codec) +{ + struct snd_soc_dai *codec_dai = codec->dai; + struct snd_soc_card *card = codec->socdev->card; + int ret; + /* Codec is ready, now add/activate board specific controls */ + + /* Set up digital mute if not provided by the codec */ + if (!codec_dai->ops) { + codec_dai->ops = &ams_delta_dai_ops; + } else if (!codec_dai->ops->digital_mute) { + codec_dai->ops->digital_mute = ams_delta_digital_mute; + } else { + ams_delta_ops.startup = ams_delta_startup; + ams_delta_ops.shutdown = ams_delta_shutdown; + } + + /* Set codec bias level */ + ams_delta_set_bias_level(card, SND_SOC_BIAS_STANDBY); + + /* Add hook switch - can be used to control the codec from userspace + * even if line discipline fails */ + ret = snd_soc_jack_new(card, "hook_switch", + SND_JACK_HEADSET, &ams_delta_hook_switch); + if (ret) + dev_warn(card->dev, + "Failed to allocate resources for hook switch, " + "will continue without one.\n"); + else { + ret = snd_soc_jack_add_gpios(&ams_delta_hook_switch, + ARRAY_SIZE(ams_delta_hook_switch_gpios), + ams_delta_hook_switch_gpios); + if (ret) + dev_warn(card->dev, + "Failed to set up hook switch GPIO line, " + "will continue with hook switch inactive.\n"); + } + + /* Register optional line discipline for over the modem control */ + ret = tty_register_ldisc(N_AMSDELTA, &cx81801_ops); + if (ret) { + dev_warn(card->dev, + "Failed to register line discipline, " + "will continue without any controls.\n"); + return 0; + } + + /* Add board specific DAPM widgets and routes */ + ret = snd_soc_dapm_new_controls(codec, ams_delta_dapm_widgets, + ARRAY_SIZE(ams_delta_dapm_widgets)); + if (ret) { + dev_warn(card->dev, + "Failed to register DAPM controls, " + "will continue without any.\n"); + return 0; + } + + ret = snd_soc_dapm_add_routes(codec, ams_delta_audio_map, + ARRAY_SIZE(ams_delta_audio_map)); + if (ret) { + dev_warn(card->dev, + "Failed to set up DAPM routes, " + "will continue with codec default map.\n"); + return 0; + } + + /* Set up initial pin constellation */ + snd_soc_dapm_disable_pin(codec, "Mouthpiece"); + snd_soc_dapm_enable_pin(codec, "Earpiece"); + snd_soc_dapm_enable_pin(codec, "Microphone"); + snd_soc_dapm_disable_pin(codec, "Speaker"); + snd_soc_dapm_disable_pin(codec, "AGCIN"); + snd_soc_dapm_disable_pin(codec, "AGCOUT"); + snd_soc_dapm_sync(codec); + + /* Add virtual switch */ + ret = snd_soc_add_controls(codec, ams_delta_audio_controls, + ARRAY_SIZE(ams_delta_audio_controls)); + if (ret) + dev_warn(card->dev, + "Failed to register audio mode control, " + "will continue without it.\n"); + + return 0; +} + +/* DAI glue - connects codec <--> CPU */ +static struct snd_soc_dai_link ams_delta_dai_link = { + .name = "CX20442", + .stream_name = "CX20442", + .cpu_dai = &omap_mcbsp_dai[0], + .codec_dai = &cx20442_dai, + .init = ams_delta_cx20442_init, + .ops = &ams_delta_ops, +}; + +/* Audio card driver */ +static struct snd_soc_card ams_delta_audio_card = { + .name = "AMS_DELTA", + .platform = &omap_soc_platform, + .dai_link = &ams_delta_dai_link, + .num_links = 1, + .set_bias_level = ams_delta_set_bias_level, +}; + +/* Audio subsystem */ +static struct snd_soc_device ams_delta_snd_soc_device = { + .card = &ams_delta_audio_card, + .codec_dev = &cx20442_codec_dev, +}; + +/* Module init/exit */ +static struct platform_device *ams_delta_audio_platform_device; +static struct platform_device *cx20442_platform_device; + +static int __init ams_delta_module_init(void) +{ + int ret; + + if (!(machine_is_ams_delta())) + return -ENODEV; + + ams_delta_audio_platform_device = + platform_device_alloc("soc-audio", -1); + if (!ams_delta_audio_platform_device) + return -ENOMEM; + + platform_set_drvdata(ams_delta_audio_platform_device, + &ams_delta_snd_soc_device); + ams_delta_snd_soc_device.dev = &ams_delta_audio_platform_device->dev; + *(unsigned int *)ams_delta_dai_link.cpu_dai->private_data = OMAP_MCBSP1; + + ret = platform_device_add(ams_delta_audio_platform_device); + if (ret) + goto err; + + /* + * Codec platform device could be registered from elsewhere (board?), + * but I do it here as it makes sense only if used with the card. + */ + cx20442_platform_device = platform_device_register_simple("cx20442", + -1, NULL, 0); + return 0; +err: + platform_device_put(ams_delta_audio_platform_device); + return ret; +} +module_init(ams_delta_module_init); + +static void __exit ams_delta_module_exit(void) +{ + struct snd_soc_codec *codec; + struct tty_struct *tty; + + if (ams_delta_audio_card.codec) { + codec = ams_delta_audio_card.codec; + + if (codec->control_data) { + tty = codec->control_data; + + tty_hangup(tty); + } + } + + if (tty_unregister_ldisc(N_AMSDELTA) != 0) + dev_warn(&ams_delta_audio_platform_device->dev, + "failed to unregister AMSDELTA line discipline\n"); + + snd_soc_jack_free_gpios(&ams_delta_hook_switch, + ARRAY_SIZE(ams_delta_hook_switch_gpios), + ams_delta_hook_switch_gpios); + + /* Keep modem power on */ + ams_delta_set_bias_level(&ams_delta_audio_card, SND_SOC_BIAS_STANDBY); + + platform_device_unregister(cx20442_platform_device); + platform_device_unregister(ams_delta_audio_platform_device); +} +module_exit(ams_delta_module_exit); + +MODULE_AUTHOR("Janusz Krzysztofik "); +MODULE_DESCRIPTION("ALSA SoC driver for Amstrad E3 (Delta) videophone"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 7084a42b965d972079201414d19a399e65b26099 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 10 Jul 2009 22:24:27 +0100 Subject: ASoC: Add I/O control bus information to factored out cache setup While writes tend to be able to use a fairly bus independant format to do the writes reads are all bus specific. To allow us to factor out this code include the bus type as a parameter when setting up the cache. Initially just use this to factor out hw_write_t for I2C. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8510.c | 10 +++++----- sound/soc/codecs/wm8580.c | 8 ++++---- sound/soc/codecs/wm8728.c | 10 +++++----- sound/soc/codecs/wm8731.c | 10 +++++----- sound/soc/codecs/wm8750.c | 10 +++++----- sound/soc/codecs/wm8960.c | 8 ++++---- sound/soc/codecs/wm8971.c | 8 ++++---- sound/soc/codecs/wm8988.c | 10 +++++----- sound/soc/soc-cache.c | 19 ++++++++++++++++++- 9 files changed, 55 insertions(+), 38 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c index 7a169bff86f..4ca724ff4c6 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -564,7 +564,8 @@ static int wm8510_resume(struct platform_device *pdev) * initialise the WM8510 driver * register the mixer and dsp interfaces with the kernel */ -static int wm8510_init(struct snd_soc_device *socdev) +static int wm8510_init(struct snd_soc_device *socdev, + enum snd_soc_control_type control) { struct snd_soc_codec *codec = socdev->card->codec; int ret = 0; @@ -580,7 +581,7 @@ static int wm8510_init(struct snd_soc_device *socdev) if (codec->reg_cache == NULL) return -ENOMEM; - ret = snd_soc_codec_set_cache_io(codec, 7, 9); + ret = snd_soc_codec_set_cache_io(codec, 7, 9, control); if (ret < 0) { printk(KERN_ERR "wm8510: failed to set cache I/O: %d\n", ret); @@ -635,7 +636,7 @@ static int wm8510_i2c_probe(struct i2c_client *i2c, i2c_set_clientdata(i2c, codec); codec->control_data = i2c; - ret = wm8510_init(socdev); + ret = wm8510_init(socdev, SND_SOC_I2C); if (ret < 0) pr_err("failed to initialise WM8510\n"); @@ -715,7 +716,7 @@ static int __devinit wm8510_spi_probe(struct spi_device *spi) codec->control_data = spi; - ret = wm8510_init(socdev); + ret = wm8510_init(socdev, SND_SOC_SPI); if (ret < 0) dev_err(&spi->dev, "failed to initialise WM8510\n"); @@ -784,7 +785,6 @@ static int wm8510_probe(struct platform_device *pdev) wm8510_socdev = socdev; #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) if (setup->i2c_address) { - codec->hw_write = (hw_write_t)i2c_master_send; ret = wm8510_add_i2c_device(pdev, setup); } #endif diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index 2250adea749..d5473473a1e 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -832,7 +832,8 @@ struct snd_soc_codec_device soc_codec_dev_wm8580 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_wm8580); -static int wm8580_register(struct wm8580_priv *wm8580) +static int wm8580_register(struct wm8580_priv *wm8580, + enum snd_soc_control_type control) { int ret, i; struct snd_soc_codec *codec = &wm8580->codec; @@ -859,7 +860,7 @@ static int wm8580_register(struct wm8580_priv *wm8580) memcpy(codec->reg_cache, wm8580_reg, sizeof(wm8580_reg)); - ret = snd_soc_codec_set_cache_io(codec, 7, 9); + ret = snd_soc_codec_set_cache_io(codec, 7, 9, control); if (ret < 0) { dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); goto err; @@ -944,14 +945,13 @@ static int wm8580_i2c_probe(struct i2c_client *i2c, return -ENOMEM; codec = &wm8580->codec; - codec->hw_write = (hw_write_t)i2c_master_send; i2c_set_clientdata(i2c, wm8580); codec->control_data = i2c; codec->dev = &i2c->dev; - return wm8580_register(wm8580); + return wm8580_register(wm8580, SND_SOC_I2C); } static int wm8580_i2c_remove(struct i2c_client *client) diff --git a/sound/soc/codecs/wm8728.c b/sound/soc/codecs/wm8728.c index 66da44b08d3..a28630de9c9 100644 --- a/sound/soc/codecs/wm8728.c +++ b/sound/soc/codecs/wm8728.c @@ -248,7 +248,8 @@ static int wm8728_resume(struct platform_device *pdev) * initialise the WM8728 driver * register the mixer and dsp interfaces with the kernel */ -static int wm8728_init(struct snd_soc_device *socdev) +static int wm8728_init(struct snd_soc_device *socdev, + enum snd_soc_control_type control) { struct snd_soc_codec *codec = socdev->card->codec; int ret = 0; @@ -266,7 +267,7 @@ static int wm8728_init(struct snd_soc_device *socdev) if (codec->reg_cache == NULL) return -ENOMEM; - ret = snd_soc_codec_set_cache_io(codec, 7, 9); + ret = snd_soc_codec_set_cache_io(codec, 7, 9, control); if (ret < 0) { printk(KERN_ERR "wm8728: failed to configure cache I/O: %d\n", ret); @@ -323,7 +324,7 @@ static int wm8728_i2c_probe(struct i2c_client *i2c, i2c_set_clientdata(i2c, codec); codec->control_data = i2c; - ret = wm8728_init(socdev); + ret = wm8728_init(socdev, SND_SOC_I2C); if (ret < 0) pr_err("failed to initialise WM8728\n"); @@ -403,7 +404,7 @@ static int __devinit wm8728_spi_probe(struct spi_device *spi) codec->control_data = spi; - ret = wm8728_init(socdev); + ret = wm8728_init(socdev, SND_SOC_SPI); if (ret < 0) dev_err(&spi->dev, "failed to initialise WM8728\n"); @@ -472,7 +473,6 @@ static int wm8728_probe(struct platform_device *pdev) #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) if (setup->i2c_address) { - codec->hw_write = (hw_write_t)i2c_master_send; ret = wm8728_add_i2c_device(pdev, setup); } #endif diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index 4eb84ff691b..27fc942a5ce 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -504,7 +504,8 @@ struct snd_soc_codec_device soc_codec_dev_wm8731 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_wm8731); -static int wm8731_register(struct wm8731_priv *wm8731) +static int wm8731_register(struct wm8731_priv *wm8731, + enum snd_soc_control_type control) { int ret; struct snd_soc_codec *codec = &wm8731->codec; @@ -531,7 +532,7 @@ static int wm8731_register(struct wm8731_priv *wm8731) memcpy(codec->reg_cache, wm8731_reg, sizeof(wm8731_reg)); - ret = snd_soc_codec_set_cache_io(codec, 7, 9); + ret = snd_soc_codec_set_cache_io(codec, 7, 9, control); if (ret < 0) { dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); goto err; @@ -630,7 +631,7 @@ static int __devinit wm8731_spi_probe(struct spi_device *spi) dev_set_drvdata(&spi->dev, wm8731); - return wm8731_register(wm8731); + return wm8731_register(wm8731, SND_SOC_SPI); } static int __devexit wm8731_spi_remove(struct spi_device *spi) @@ -682,14 +683,13 @@ static __devinit int wm8731_i2c_probe(struct i2c_client *i2c, return -ENOMEM; codec = &wm8731->codec; - codec->hw_write = (hw_write_t)i2c_master_send; i2c_set_clientdata(i2c, wm8731); codec->control_data = i2c; codec->dev = &i2c->dev; - return wm8731_register(wm8731); + return wm8731_register(wm8731, SND_SOC_I2C); } static __devexit int wm8731_i2c_remove(struct i2c_client *client) diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index ed09043181e..e6422b16167 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -711,7 +711,8 @@ static int wm8750_resume(struct platform_device *pdev) * initialise the WM8750 driver * register the mixer and dsp interfaces with the kernel */ -static int wm8750_init(struct snd_soc_device *socdev) +static int wm8750_init(struct snd_soc_device *socdev, + enum snd_soc_control_type control) { struct snd_soc_codec *codec = socdev->card->codec; int reg, ret = 0; @@ -726,7 +727,7 @@ static int wm8750_init(struct snd_soc_device *socdev) if (codec->reg_cache == NULL) return -ENOMEM; - ret = snd_soc_codec_set_cache_io(codec, 7, 9); + ret = snd_soc_codec_set_cache_io(codec, 7, 9, control); if (ret < 0) { printk(KERN_ERR "wm8750: failed to set cache I/O: %d\n", ret); goto err; @@ -809,7 +810,7 @@ static int wm8750_i2c_probe(struct i2c_client *i2c, i2c_set_clientdata(i2c, codec); codec->control_data = i2c; - ret = wm8750_init(socdev); + ret = wm8750_init(socdev, SND_SOC_I2C); if (ret < 0) pr_err("failed to initialise WM8750\n"); @@ -889,7 +890,7 @@ static int __devinit wm8750_spi_probe(struct spi_device *spi) codec->control_data = spi; - ret = wm8750_init(socdev); + ret = wm8750_init(socdev, SND_SOC_SPI); if (ret < 0) dev_err(&spi->dev, "failed to initialise WM8750\n"); @@ -967,7 +968,6 @@ static int wm8750_probe(struct platform_device *pdev) #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) if (setup->i2c_address) { - codec->hw_write = (hw_write_t)i2c_master_send; ret = wm8750_add_i2c_device(pdev, setup); } #endif diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index c529ffcfe5d..f59703be61c 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -747,7 +747,8 @@ struct snd_soc_codec_device soc_codec_dev_wm8960 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_wm8960); -static int wm8960_register(struct wm8960_priv *wm8960) +static int wm8960_register(struct wm8960_priv *wm8960, + enum snd_soc_control_type control) { struct wm8960_data *pdata = wm8960->codec.dev->platform_data; struct snd_soc_codec *codec = &wm8960->codec; @@ -785,7 +786,7 @@ static int wm8960_register(struct wm8960_priv *wm8960) memcpy(codec->reg_cache, wm8960_reg, sizeof(wm8960_reg)); - ret = snd_soc_codec_set_cache_io(codec, 7, 9); + ret = snd_soc_codec_set_cache_io(codec, 7, 9, control); if (ret < 0) { dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); goto err; @@ -866,14 +867,13 @@ static __devinit int wm8960_i2c_probe(struct i2c_client *i2c, return -ENOMEM; codec = &wm8960->codec; - codec->hw_write = (hw_write_t)i2c_master_send; i2c_set_clientdata(i2c, wm8960); codec->control_data = i2c; codec->dev = &i2c->dev; - return wm8960_register(wm8960); + return wm8960_register(wm8960, SND_SOC_I2C); } static __devexit int wm8960_i2c_remove(struct i2c_client *client) diff --git a/sound/soc/codecs/wm8971.c b/sound/soc/codecs/wm8971.c index 53f3bc90ea0..d66efb0546e 100644 --- a/sound/soc/codecs/wm8971.c +++ b/sound/soc/codecs/wm8971.c @@ -640,7 +640,8 @@ static int wm8971_resume(struct platform_device *pdev) return 0; } -static int wm8971_init(struct snd_soc_device *socdev) +static int wm8971_init(struct snd_soc_device *socdev, + enum snd_soc_control_type control) { struct snd_soc_codec *codec = socdev->card->codec; int reg, ret = 0; @@ -656,7 +657,7 @@ static int wm8971_init(struct snd_soc_device *socdev) if (codec->reg_cache == NULL) return -ENOMEM; - ret = snd_soc_codec_set_cache_io(codec, 7, 9); + ret = snd_soc_codec_set_cache_io(codec, 7, 9, control); if (ret < 0) { printk(KERN_ERR "wm8971: failed to set cache I/O: %d\n", ret); goto err; @@ -734,7 +735,7 @@ static int wm8971_i2c_probe(struct i2c_client *i2c, codec->control_data = i2c; - ret = wm8971_init(socdev); + ret = wm8971_init(socdev, SND_SOC_I2C); if (ret < 0) pr_err("failed to initialise WM8971\n"); @@ -844,7 +845,6 @@ static int wm8971_probe(struct platform_device *pdev) #if defined (CONFIG_I2C) || defined (CONFIG_I2C_MODULE) if (setup->i2c_address) { - codec->hw_write = (hw_write_t)i2c_master_send; ret = wm8971_add_i2c_device(pdev, setup); } #endif diff --git a/sound/soc/codecs/wm8988.c b/sound/soc/codecs/wm8988.c index 7d5b807bdb4..7486d3ec787 100644 --- a/sound/soc/codecs/wm8988.c +++ b/sound/soc/codecs/wm8988.c @@ -825,7 +825,8 @@ struct snd_soc_codec_device soc_codec_dev_wm8988 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_wm8988); -static int wm8988_register(struct wm8988_priv *wm8988) +static int wm8988_register(struct wm8988_priv *wm8988, + enum snd_soc_control_type control) { struct snd_soc_codec *codec = &wm8988->codec; int ret; @@ -854,7 +855,7 @@ static int wm8988_register(struct wm8988_priv *wm8988) memcpy(codec->reg_cache, wm8988_reg, sizeof(wm8988_reg)); - ret = snd_soc_codec_set_cache_io(codec, 7, 9); + ret = snd_soc_codec_set_cache_io(codec, 7, 9, control); if (ret < 0) { dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); goto err; @@ -927,14 +928,13 @@ static int wm8988_i2c_probe(struct i2c_client *i2c, return -ENOMEM; codec = &wm8988->codec; - codec->hw_write = (hw_write_t)i2c_master_send; i2c_set_clientdata(i2c, wm8988); codec->control_data = i2c; codec->dev = &i2c->dev; - return wm8988_register(wm8988); + return wm8988_register(wm8988, SND_SOC_I2C); } static int wm8988_i2c_remove(struct i2c_client *client) @@ -1019,7 +1019,7 @@ static int __devinit wm8988_spi_probe(struct spi_device *spi) spi->dev.driver_data = wm8988; - return wm8988_register(wm8988); + return wm8988_register(wm8988, SND_SOC_SPI); } static int __devexit wm8988_spi_remove(struct spi_device *spi) diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c index 4eb4333a0ef..8b126682c84 100644 --- a/sound/soc/soc-cache.c +++ b/sound/soc/soc-cache.c @@ -11,6 +11,7 @@ * option) any later version. */ +#include #include static unsigned int snd_soc_7_9_read(struct snd_soc_codec *codec, @@ -62,6 +63,7 @@ static struct { * @type: Type of cache. * @addr_bits: Number of bits of register address data. * @data_bits: Number of bits of data per register. + * @control: Control bus used. * * Register formats are frequently shared between many I2C and SPI * devices. In order to promote code reuse the ASoC core provides @@ -75,7 +77,8 @@ static struct { * volatile registers. */ int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec, - int addr_bits, int data_bits) + int addr_bits, int data_bits, + enum snd_soc_control_type control) { int i; @@ -100,6 +103,20 @@ int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec, codec->write = io_types[i].write; codec->read = io_types[i].read; + switch (control) { + case SND_SOC_CUSTOM: + break; + + case SND_SOC_I2C: +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + codec->hw_write = (hw_write_t)i2c_master_send; +#endif + break; + + case SND_SOC_SPI: + break; + } + return 0; } EXPORT_SYMBOL_GPL(snd_soc_codec_set_cache_io); -- cgit v1.2.3 From afa2f1066e7288a9e4f8e3fda277da245219dffc Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 10 Jul 2009 23:11:24 +0100 Subject: ASoC: Factor out I2C 8 bit address 16 bit data I/O As part of this refactoring the type of the CODEC hw_read operation is changed to match the regular read operation. Signed-off-by: Mark Brown --- sound/soc/soc-cache.c | 79 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 8 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c index 8b126682c84..ab058b81163 100644 --- a/sound/soc/soc-cache.c +++ b/sound/soc/soc-cache.c @@ -46,14 +46,82 @@ static int snd_soc_7_9_write(struct snd_soc_codec *codec, unsigned int reg, return -EIO; } +static int snd_soc_8_16_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u16 *reg_cache = codec->reg_cache; + u8 data[3]; + + data[0] = reg; + data[1] = (value >> 8) & 0xff; + data[2] = value & 0xff; + + if (!snd_soc_codec_volatile_register(codec, reg)) + reg_cache[reg] = value; + + if (codec->hw_write(codec->control_data, data, 3) == 3) + return 0; + else + return -EIO; +} + +static unsigned int snd_soc_8_16_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + + if (reg >= codec->reg_cache_size || + snd_soc_codec_volatile_register(codec, reg)) + return codec->hw_read(codec, reg); + else + return cache[reg]; +} + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static unsigned int snd_soc_8_16_read_i2c(struct snd_soc_codec *codec, + unsigned int r) +{ + struct i2c_msg xfer[2]; + u8 reg = r; + u16 data; + int ret; + struct i2c_client *client = codec->control_data; + + /* Write register */ + xfer[0].addr = client->addr; + xfer[0].flags = 0; + xfer[0].len = 1; + xfer[0].buf = ® + + /* Read data */ + xfer[1].addr = client->addr; + xfer[1].flags = I2C_M_RD; + xfer[1].len = 2; + xfer[1].buf = (u8 *)&data; + + ret = i2c_transfer(client->adapter, xfer, 2); + if (ret != 2) { + dev_err(&client->dev, "i2c_transfer() returned %d\n", ret); + return 0; + } + + return (data >> 8) | ((data & 0xff) << 8); +} +#else +#define snd_soc_8_16_read_i2c NULL +#endif static struct { int addr_bits; int data_bits; - int (*write)(struct snd_soc_codec *, unsigned int, unsigned int); + int (*write)(struct snd_soc_codec *codec, unsigned int, unsigned int); unsigned int (*read)(struct snd_soc_codec *, unsigned int); + unsigned int (*i2c_read)(struct snd_soc_codec *, unsigned int); } io_types[] = { { 7, 9, snd_soc_7_9_write, snd_soc_7_9_read }, + { 8, 16, + snd_soc_8_16_write, snd_soc_8_16_read, + snd_soc_8_16_read_i2c }, }; /** @@ -82,13 +150,6 @@ int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec, { int i; - /* We don't support volatile registers yet - refactoring of - * the hw_read operation will be required to do so. */ - if (codec->volatile_register) { - printk(KERN_ERR "Volatile registers not yet supported\n"); - return -EINVAL; - } - for (i = 0; i < ARRAY_SIZE(io_types); i++) if (io_types[i].addr_bits == addr_bits && io_types[i].data_bits == data_bits) @@ -111,6 +172,8 @@ int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec, #if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) codec->hw_write = (hw_write_t)i2c_master_send; #endif + if (io_types[i].i2c_read) + codec->hw_read = io_types[i].i2c_read; break; case SND_SOC_SPI: -- cgit v1.2.3 From 8d50e447d19fec64adebeef55f2b60d695435412 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 10 Jul 2009 23:12:01 +0100 Subject: ASoC: Factor out I/O for Wolfson 8 bit data 16 bit register CODECs Signed-off-by: Mark Brown --- sound/soc/codecs/wm8523.c | 98 +++----------- sound/soc/codecs/wm8900.c | 319 +++++++++++++++++----------------------------- sound/soc/codecs/wm8903.c | 244 +++++++++++++---------------------- sound/soc/codecs/wm8940.c | 143 ++++++++------------- sound/soc/codecs/wm8961.c | 231 ++++++++++++--------------------- sound/soc/codecs/wm8990.c | 194 +++++++++++----------------- sound/soc/codecs/wm9081.c | 229 ++++++++++++--------------------- 7 files changed, 521 insertions(+), 937 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8523.c b/sound/soc/codecs/wm8523.c index 3b499ae7ce6..25870a4652f 100644 --- a/sound/soc/codecs/wm8523.c +++ b/sound/soc/codecs/wm8523.c @@ -62,7 +62,7 @@ static const u16 wm8523_reg[WM8523_REGISTER_COUNT] = { 0x0000, /* R8 - ZERO_DETECT */ }; -static int wm8523_volatile(unsigned int reg) +static int wm8523_volatile_register(unsigned int reg) { switch (reg) { case WM8523_DEVICE_ID: @@ -73,71 +73,9 @@ static int wm8523_volatile(unsigned int reg) } } -static int wm8523_write(struct snd_soc_codec *codec, unsigned int reg, - unsigned int value) -{ - struct wm8523_priv *wm8523 = codec->private_data; - u8 data[3]; - - BUG_ON(reg > WM8523_MAX_REGISTER); - - data[0] = reg; - data[1] = (value >> 8) & 0x00ff; - data[2] = value & 0x00ff; - - if (!wm8523_volatile(reg)) - wm8523->reg_cache[reg] = value; - if (codec->hw_write(codec->control_data, data, 3) == 3) - return 0; - else - return -EIO; -} - static int wm8523_reset(struct snd_soc_codec *codec) { - return wm8523_write(codec, WM8523_DEVICE_ID, 0); -} - -static unsigned int wm8523_read_hw(struct snd_soc_codec *codec, u8 reg) -{ - struct i2c_msg xfer[2]; - u16 data; - int ret; - struct i2c_client *i2c = codec->control_data; - - /* Write register */ - xfer[0].addr = i2c->addr; - xfer[0].flags = 0; - xfer[0].len = 1; - xfer[0].buf = ® - - /* Read data */ - xfer[1].addr = i2c->addr; - xfer[1].flags = I2C_M_RD; - xfer[1].len = 2; - xfer[1].buf = (u8 *)&data; - - ret = i2c_transfer(i2c->adapter, xfer, 2); - if (ret != 2) { - dev_err(codec->dev, "Failed to read 0x%x: %d\n", reg, ret); - return 0; - } - - return (data >> 8) | ((data & 0xff) << 8); -} - - -static unsigned int wm8523_read(struct snd_soc_codec *codec, - unsigned int reg) -{ - u16 *reg_cache = codec->reg_cache; - - BUG_ON(reg > WM8523_MAX_REGISTER); - - if (wm8523_volatile(reg)) - return wm8523_read_hw(codec, reg); - else - return reg_cache[reg]; + return snd_soc_write(codec, WM8523_DEVICE_ID, 0); } static const DECLARE_TLV_DB_SCALE(dac_tlv, -10000, 25, 0); @@ -228,8 +166,8 @@ static int wm8523_hw_params(struct snd_pcm_substream *substream, struct snd_soc_codec *codec = socdev->card->codec; struct wm8523_priv *wm8523 = codec->private_data; int i; - u16 aifctrl1 = wm8523_read(codec, WM8523_AIF_CTRL1); - u16 aifctrl2 = wm8523_read(codec, WM8523_AIF_CTRL2); + u16 aifctrl1 = snd_soc_read(codec, WM8523_AIF_CTRL1); + u16 aifctrl2 = snd_soc_read(codec, WM8523_AIF_CTRL2); /* Find a supported LRCLK ratio */ for (i = 0; i < ARRAY_SIZE(lrclk_ratios); i++) { @@ -263,8 +201,8 @@ static int wm8523_hw_params(struct snd_pcm_substream *substream, break; } - wm8523_write(codec, WM8523_AIF_CTRL1, aifctrl1); - wm8523_write(codec, WM8523_AIF_CTRL2, aifctrl2); + snd_soc_write(codec, WM8523_AIF_CTRL1, aifctrl1); + snd_soc_write(codec, WM8523_AIF_CTRL2, aifctrl2); return 0; } @@ -322,7 +260,7 @@ static int wm8523_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; - u16 aifctrl1 = wm8523_read(codec, WM8523_AIF_CTRL1); + u16 aifctrl1 = snd_soc_read(codec, WM8523_AIF_CTRL1); aifctrl1 &= ~(WM8523_BCLK_INV_MASK | WM8523_LRCLK_INV_MASK | WM8523_FMT_MASK | WM8523_AIF_MSTR_MASK); @@ -372,7 +310,7 @@ static int wm8523_set_dai_fmt(struct snd_soc_dai *codec_dai, return -EINVAL; } - wm8523_write(codec, WM8523_AIF_CTRL1, aifctrl1); + snd_soc_write(codec, WM8523_AIF_CTRL1, aifctrl1); return 0; } @@ -411,7 +349,7 @@ static int wm8523_set_bias_level(struct snd_soc_codec *codec, /* Sync back default/cached values */ for (i = WM8523_AIF_CTRL1; i < WM8523_MAX_REGISTER; i++) - wm8523_write(codec, i, wm8523->reg_cache[i]); + snd_soc_write(codec, i, wm8523->reg_cache[i]); msleep(100); @@ -543,7 +481,8 @@ struct snd_soc_codec_device soc_codec_dev_wm8523 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_wm8523); -static int wm8523_register(struct wm8523_priv *wm8523) +static int wm8523_register(struct wm8523_priv *wm8523, + enum snd_soc_control_type control) { int ret; struct snd_soc_codec *codec = &wm8523->codec; @@ -561,14 +500,13 @@ static int wm8523_register(struct wm8523_priv *wm8523) codec->private_data = wm8523; codec->name = "WM8523"; codec->owner = THIS_MODULE; - codec->read = wm8523_read; - codec->write = wm8523_write; codec->bias_level = SND_SOC_BIAS_OFF; codec->set_bias_level = wm8523_set_bias_level; codec->dai = &wm8523_dai; codec->num_dai = 1; codec->reg_cache_size = WM8523_REGISTER_COUNT; codec->reg_cache = &wm8523->reg_cache; + codec->volatile_register = wm8523_volatile_register; wm8523->rate_constraint.list = &wm8523->rate_constraint_list[0]; wm8523->rate_constraint.count = @@ -576,6 +514,12 @@ static int wm8523_register(struct wm8523_priv *wm8523) memcpy(codec->reg_cache, wm8523_reg, sizeof(wm8523_reg)); + ret = snd_soc_codec_set_cache_io(codec, 8, 16, control); + if (ret != 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + goto err; + } + for (i = 0; i < ARRAY_SIZE(wm8523->supplies); i++) wm8523->supplies[i].supply = wm8523_supply_names[i]; @@ -593,7 +537,7 @@ static int wm8523_register(struct wm8523_priv *wm8523) goto err_get; } - ret = wm8523_read(codec, WM8523_DEVICE_ID); + ret = snd_soc_read(codec, WM8523_DEVICE_ID); if (ret < 0) { dev_err(codec->dev, "Failed to read ID register\n"); goto err_enable; @@ -604,7 +548,7 @@ static int wm8523_register(struct wm8523_priv *wm8523) goto err_enable; } - ret = wm8523_read(codec, WM8523_REVISION); + ret = snd_soc_read(codec, WM8523_REVISION); if (ret < 0) { dev_err(codec->dev, "Failed to read revision register\n"); goto err_enable; @@ -684,7 +628,7 @@ static __devinit int wm8523_i2c_probe(struct i2c_client *i2c, codec->dev = &i2c->dev; - return wm8523_register(wm8523); + return wm8523_register(wm8523, SND_SOC_I2C); } static __devexit int wm8523_i2c_remove(struct i2c_client *client) diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c index ac308993ac5..5e9c855c003 100644 --- a/sound/soc/codecs/wm8900.c +++ b/sound/soc/codecs/wm8900.c @@ -183,111 +183,20 @@ static const u16 wm8900_reg_defaults[WM8900_MAXREG] = { /* Remaining registers all zero */ }; -/* - * read wm8900 register cache - */ -static inline unsigned int wm8900_read_reg_cache(struct snd_soc_codec *codec, - unsigned int reg) -{ - u16 *cache = codec->reg_cache; - - BUG_ON(reg >= WM8900_MAXREG); - - if (reg == WM8900_REG_ID) - return 0; - - return cache[reg]; -} - -/* - * write wm8900 register cache - */ -static inline void wm8900_write_reg_cache(struct snd_soc_codec *codec, - u16 reg, unsigned int value) -{ - u16 *cache = codec->reg_cache; - - BUG_ON(reg >= WM8900_MAXREG); - - cache[reg] = value; -} - -/* - * write to the WM8900 register space - */ -static int wm8900_write(struct snd_soc_codec *codec, unsigned int reg, - unsigned int value) -{ - u8 data[3]; - - if (value == wm8900_read_reg_cache(codec, reg)) - return 0; - - /* data is - * D15..D9 WM8900 register offset - * D8...D0 register data - */ - data[0] = reg; - data[1] = value >> 8; - data[2] = value & 0x00ff; - - wm8900_write_reg_cache(codec, reg, value); - if (codec->hw_write(codec->control_data, data, 3) == 3) - return 0; - else - return -EIO; -} - -/* - * Read from the wm8900. - */ -static unsigned int wm8900_chip_read(struct snd_soc_codec *codec, u8 reg) -{ - struct i2c_msg xfer[2]; - u16 data; - int ret; - struct i2c_client *client = codec->control_data; - - BUG_ON(reg != WM8900_REG_ID && reg != WM8900_REG_POWER1); - - /* Write register */ - xfer[0].addr = client->addr; - xfer[0].flags = 0; - xfer[0].len = 1; - xfer[0].buf = ® - - /* Read data */ - xfer[1].addr = client->addr; - xfer[1].flags = I2C_M_RD; - xfer[1].len = 2; - xfer[1].buf = (u8 *)&data; - - ret = i2c_transfer(client->adapter, xfer, 2); - if (ret != 2) { - printk(KERN_CRIT "i2c_transfer returned %d\n", ret); - return 0; - } - - return (data >> 8) | ((data & 0xff) << 8); -} - -/* - * Read from the WM8900 register space. Most registers can't be read - * and are therefore supplied from cache. - */ -static unsigned int wm8900_read(struct snd_soc_codec *codec, unsigned int reg) +static int wm8900_volatile_register(unsigned int reg) { switch (reg) { case WM8900_REG_ID: - return wm8900_chip_read(codec, reg); + case WM8900_REG_POWER1: + return 1; default: - return wm8900_read_reg_cache(codec, reg); + return 0; } } static void wm8900_reset(struct snd_soc_codec *codec) { - wm8900_write(codec, WM8900_REG_RESET, 0); + snd_soc_write(codec, WM8900_REG_RESET, 0); memcpy(codec->reg_cache, wm8900_reg_defaults, sizeof(codec->reg_cache)); @@ -297,14 +206,14 @@ static int wm8900_hp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; - u16 hpctl1 = wm8900_read(codec, WM8900_REG_HPCTL1); + u16 hpctl1 = snd_soc_read(codec, WM8900_REG_HPCTL1); switch (event) { case SND_SOC_DAPM_PRE_PMU: /* Clamp headphone outputs */ hpctl1 = WM8900_REG_HPCTL1_HP_CLAMP_IP | WM8900_REG_HPCTL1_HP_CLAMP_OP; - wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1); break; case SND_SOC_DAPM_POST_PMU: @@ -313,41 +222,41 @@ static int wm8900_hp_event(struct snd_soc_dapm_widget *w, hpctl1 |= WM8900_REG_HPCTL1_HP_SHORT | WM8900_REG_HPCTL1_HP_SHORT2 | WM8900_REG_HPCTL1_HP_IPSTAGE_ENA; - wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1); msleep(400); /* Enable the output stage */ hpctl1 &= ~WM8900_REG_HPCTL1_HP_CLAMP_OP; hpctl1 |= WM8900_REG_HPCTL1_HP_OPSTAGE_ENA; - wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1); /* Remove the shorts */ hpctl1 &= ~WM8900_REG_HPCTL1_HP_SHORT2; - wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1); hpctl1 &= ~WM8900_REG_HPCTL1_HP_SHORT; - wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1); break; case SND_SOC_DAPM_PRE_PMD: /* Short the output */ hpctl1 |= WM8900_REG_HPCTL1_HP_SHORT; - wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1); /* Disable the output stage */ hpctl1 &= ~WM8900_REG_HPCTL1_HP_OPSTAGE_ENA; - wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1); /* Clamp the outputs and power down input */ hpctl1 |= WM8900_REG_HPCTL1_HP_CLAMP_IP | WM8900_REG_HPCTL1_HP_CLAMP_OP; hpctl1 &= ~WM8900_REG_HPCTL1_HP_IPSTAGE_ENA; - wm8900_write(codec, WM8900_REG_HPCTL1, hpctl1); + snd_soc_write(codec, WM8900_REG_HPCTL1, hpctl1); break; case SND_SOC_DAPM_POST_PMD: /* Disable everything */ - wm8900_write(codec, WM8900_REG_HPCTL1, 0); + snd_soc_write(codec, WM8900_REG_HPCTL1, 0); break; default: @@ -723,7 +632,7 @@ static int wm8900_hw_params(struct snd_pcm_substream *substream, struct snd_soc_codec *codec = socdev->card->codec; u16 reg; - reg = wm8900_read(codec, WM8900_REG_AUDIO1) & ~0x60; + reg = snd_soc_read(codec, WM8900_REG_AUDIO1) & ~0x60; switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: @@ -741,17 +650,17 @@ static int wm8900_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - wm8900_write(codec, WM8900_REG_AUDIO1, reg); + snd_soc_write(codec, WM8900_REG_AUDIO1, reg); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - reg = wm8900_read(codec, WM8900_REG_DACCTRL); + reg = snd_soc_read(codec, WM8900_REG_DACCTRL); if (params_rate(params) <= 24000) reg |= WM8900_REG_DACCTRL_DAC_SB_FILT; else reg &= ~WM8900_REG_DACCTRL_DAC_SB_FILT; - wm8900_write(codec, WM8900_REG_DACCTRL, reg); + snd_soc_write(codec, WM8900_REG_DACCTRL, reg); } return 0; @@ -845,18 +754,18 @@ static int wm8900_set_fll(struct snd_soc_codec *codec, return 0; /* The digital side should be disabled during any change. */ - reg = wm8900_read(codec, WM8900_REG_POWER1); - wm8900_write(codec, WM8900_REG_POWER1, + reg = snd_soc_read(codec, WM8900_REG_POWER1); + snd_soc_write(codec, WM8900_REG_POWER1, reg & (~WM8900_REG_POWER1_FLL_ENA)); /* Disable the FLL? */ if (!freq_in || !freq_out) { - reg = wm8900_read(codec, WM8900_REG_CLOCKING1); - wm8900_write(codec, WM8900_REG_CLOCKING1, + reg = snd_soc_read(codec, WM8900_REG_CLOCKING1); + snd_soc_write(codec, WM8900_REG_CLOCKING1, reg & (~WM8900_REG_CLOCKING1_MCLK_SRC)); - reg = wm8900_read(codec, WM8900_REG_FLLCTL1); - wm8900_write(codec, WM8900_REG_FLLCTL1, + reg = snd_soc_read(codec, WM8900_REG_FLLCTL1); + snd_soc_write(codec, WM8900_REG_FLLCTL1, reg & (~WM8900_REG_FLLCTL1_OSC_ENA)); wm8900->fll_in = freq_in; @@ -873,33 +782,33 @@ static int wm8900_set_fll(struct snd_soc_codec *codec, /* The osclilator *MUST* be enabled before we enable the * digital circuit. */ - wm8900_write(codec, WM8900_REG_FLLCTL1, + snd_soc_write(codec, WM8900_REG_FLLCTL1, fll_div.fll_ratio | WM8900_REG_FLLCTL1_OSC_ENA); - wm8900_write(codec, WM8900_REG_FLLCTL4, fll_div.n >> 5); - wm8900_write(codec, WM8900_REG_FLLCTL5, + snd_soc_write(codec, WM8900_REG_FLLCTL4, fll_div.n >> 5); + snd_soc_write(codec, WM8900_REG_FLLCTL5, (fll_div.fllclk_div << 6) | (fll_div.n & 0x1f)); if (fll_div.k) { - wm8900_write(codec, WM8900_REG_FLLCTL2, + snd_soc_write(codec, WM8900_REG_FLLCTL2, (fll_div.k >> 8) | 0x100); - wm8900_write(codec, WM8900_REG_FLLCTL3, fll_div.k & 0xff); + snd_soc_write(codec, WM8900_REG_FLLCTL3, fll_div.k & 0xff); } else - wm8900_write(codec, WM8900_REG_FLLCTL2, 0); + snd_soc_write(codec, WM8900_REG_FLLCTL2, 0); if (fll_div.fll_slow_lock_ref) - wm8900_write(codec, WM8900_REG_FLLCTL6, + snd_soc_write(codec, WM8900_REG_FLLCTL6, WM8900_REG_FLLCTL6_FLL_SLOW_LOCK_REF); else - wm8900_write(codec, WM8900_REG_FLLCTL6, 0); + snd_soc_write(codec, WM8900_REG_FLLCTL6, 0); - reg = wm8900_read(codec, WM8900_REG_POWER1); - wm8900_write(codec, WM8900_REG_POWER1, + reg = snd_soc_read(codec, WM8900_REG_POWER1); + snd_soc_write(codec, WM8900_REG_POWER1, reg | WM8900_REG_POWER1_FLL_ENA); reenable: - reg = wm8900_read(codec, WM8900_REG_CLOCKING1); - wm8900_write(codec, WM8900_REG_CLOCKING1, + reg = snd_soc_read(codec, WM8900_REG_CLOCKING1); + snd_soc_write(codec, WM8900_REG_CLOCKING1, reg | WM8900_REG_CLOCKING1_MCLK_SRC); return 0; @@ -919,38 +828,38 @@ static int wm8900_set_dai_clkdiv(struct snd_soc_dai *codec_dai, switch (div_id) { case WM8900_BCLK_DIV: - reg = wm8900_read(codec, WM8900_REG_CLOCKING1); - wm8900_write(codec, WM8900_REG_CLOCKING1, + reg = snd_soc_read(codec, WM8900_REG_CLOCKING1); + snd_soc_write(codec, WM8900_REG_CLOCKING1, div | (reg & WM8900_REG_CLOCKING1_BCLK_MASK)); break; case WM8900_OPCLK_DIV: - reg = wm8900_read(codec, WM8900_REG_CLOCKING1); - wm8900_write(codec, WM8900_REG_CLOCKING1, + reg = snd_soc_read(codec, WM8900_REG_CLOCKING1); + snd_soc_write(codec, WM8900_REG_CLOCKING1, div | (reg & WM8900_REG_CLOCKING1_OPCLK_MASK)); break; case WM8900_DAC_LRCLK: - reg = wm8900_read(codec, WM8900_REG_AUDIO4); - wm8900_write(codec, WM8900_REG_AUDIO4, + reg = snd_soc_read(codec, WM8900_REG_AUDIO4); + snd_soc_write(codec, WM8900_REG_AUDIO4, div | (reg & WM8900_LRC_MASK)); break; case WM8900_ADC_LRCLK: - reg = wm8900_read(codec, WM8900_REG_AUDIO3); - wm8900_write(codec, WM8900_REG_AUDIO3, + reg = snd_soc_read(codec, WM8900_REG_AUDIO3); + snd_soc_write(codec, WM8900_REG_AUDIO3, div | (reg & WM8900_LRC_MASK)); break; case WM8900_DAC_CLKDIV: - reg = wm8900_read(codec, WM8900_REG_CLOCKING2); - wm8900_write(codec, WM8900_REG_CLOCKING2, + reg = snd_soc_read(codec, WM8900_REG_CLOCKING2); + snd_soc_write(codec, WM8900_REG_CLOCKING2, div | (reg & WM8900_REG_CLOCKING2_DAC_CLKDIV)); break; case WM8900_ADC_CLKDIV: - reg = wm8900_read(codec, WM8900_REG_CLOCKING2); - wm8900_write(codec, WM8900_REG_CLOCKING2, + reg = snd_soc_read(codec, WM8900_REG_CLOCKING2); + snd_soc_write(codec, WM8900_REG_CLOCKING2, div | (reg & WM8900_REG_CLOCKING2_ADC_CLKDIV)); break; case WM8900_LRCLK_MODE: - reg = wm8900_read(codec, WM8900_REG_DACCTRL); - wm8900_write(codec, WM8900_REG_DACCTRL, + reg = snd_soc_read(codec, WM8900_REG_DACCTRL); + snd_soc_write(codec, WM8900_REG_DACCTRL, div | (reg & WM8900_REG_DACCTRL_AIF_LRCLKRATE)); break; default: @@ -967,10 +876,10 @@ static int wm8900_set_dai_fmt(struct snd_soc_dai *codec_dai, struct snd_soc_codec *codec = codec_dai->codec; unsigned int clocking1, aif1, aif3, aif4; - clocking1 = wm8900_read(codec, WM8900_REG_CLOCKING1); - aif1 = wm8900_read(codec, WM8900_REG_AUDIO1); - aif3 = wm8900_read(codec, WM8900_REG_AUDIO3); - aif4 = wm8900_read(codec, WM8900_REG_AUDIO4); + clocking1 = snd_soc_read(codec, WM8900_REG_CLOCKING1); + aif1 = snd_soc_read(codec, WM8900_REG_AUDIO1); + aif3 = snd_soc_read(codec, WM8900_REG_AUDIO3); + aif4 = snd_soc_read(codec, WM8900_REG_AUDIO4); /* set master/slave audio interface */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { @@ -1066,10 +975,10 @@ static int wm8900_set_dai_fmt(struct snd_soc_dai *codec_dai, return -EINVAL; } - wm8900_write(codec, WM8900_REG_CLOCKING1, clocking1); - wm8900_write(codec, WM8900_REG_AUDIO1, aif1); - wm8900_write(codec, WM8900_REG_AUDIO3, aif3); - wm8900_write(codec, WM8900_REG_AUDIO4, aif4); + snd_soc_write(codec, WM8900_REG_CLOCKING1, clocking1); + snd_soc_write(codec, WM8900_REG_AUDIO1, aif1); + snd_soc_write(codec, WM8900_REG_AUDIO3, aif3); + snd_soc_write(codec, WM8900_REG_AUDIO4, aif4); return 0; } @@ -1079,14 +988,14 @@ static int wm8900_digital_mute(struct snd_soc_dai *codec_dai, int mute) struct snd_soc_codec *codec = codec_dai->codec; u16 reg; - reg = wm8900_read(codec, WM8900_REG_DACCTRL); + reg = snd_soc_read(codec, WM8900_REG_DACCTRL); if (mute) reg |= WM8900_REG_DACCTRL_MUTE; else reg &= ~WM8900_REG_DACCTRL_MUTE; - wm8900_write(codec, WM8900_REG_DACCTRL, reg); + snd_soc_write(codec, WM8900_REG_DACCTRL, reg); return 0; } @@ -1135,11 +1044,11 @@ static int wm8900_set_bias_level(struct snd_soc_codec *codec, switch (level) { case SND_SOC_BIAS_ON: /* Enable thermal shutdown */ - reg = wm8900_read(codec, WM8900_REG_GPIO); - wm8900_write(codec, WM8900_REG_GPIO, + reg = snd_soc_read(codec, WM8900_REG_GPIO); + snd_soc_write(codec, WM8900_REG_GPIO, reg | WM8900_REG_GPIO_TEMP_ENA); - reg = wm8900_read(codec, WM8900_REG_ADDCTL); - wm8900_write(codec, WM8900_REG_ADDCTL, + reg = snd_soc_read(codec, WM8900_REG_ADDCTL); + snd_soc_write(codec, WM8900_REG_ADDCTL, reg | WM8900_REG_ADDCTL_TEMP_SD); break; @@ -1150,69 +1059,69 @@ static int wm8900_set_bias_level(struct snd_soc_codec *codec, /* Charge capacitors if initial power up */ if (codec->bias_level == SND_SOC_BIAS_OFF) { /* STARTUP_BIAS_ENA on */ - wm8900_write(codec, WM8900_REG_POWER1, + snd_soc_write(codec, WM8900_REG_POWER1, WM8900_REG_POWER1_STARTUP_BIAS_ENA); /* Startup bias mode */ - wm8900_write(codec, WM8900_REG_ADDCTL, + snd_soc_write(codec, WM8900_REG_ADDCTL, WM8900_REG_ADDCTL_BIAS_SRC | WM8900_REG_ADDCTL_VMID_SOFTST); /* VMID 2x50k */ - wm8900_write(codec, WM8900_REG_POWER1, + snd_soc_write(codec, WM8900_REG_POWER1, WM8900_REG_POWER1_STARTUP_BIAS_ENA | 0x1); /* Allow capacitors to charge */ schedule_timeout_interruptible(msecs_to_jiffies(400)); /* Enable bias */ - wm8900_write(codec, WM8900_REG_POWER1, + snd_soc_write(codec, WM8900_REG_POWER1, WM8900_REG_POWER1_STARTUP_BIAS_ENA | WM8900_REG_POWER1_BIAS_ENA | 0x1); - wm8900_write(codec, WM8900_REG_ADDCTL, 0); + snd_soc_write(codec, WM8900_REG_ADDCTL, 0); - wm8900_write(codec, WM8900_REG_POWER1, + snd_soc_write(codec, WM8900_REG_POWER1, WM8900_REG_POWER1_BIAS_ENA | 0x1); } - reg = wm8900_read(codec, WM8900_REG_POWER1); - wm8900_write(codec, WM8900_REG_POWER1, + reg = snd_soc_read(codec, WM8900_REG_POWER1); + snd_soc_write(codec, WM8900_REG_POWER1, (reg & WM8900_REG_POWER1_FLL_ENA) | WM8900_REG_POWER1_BIAS_ENA | 0x1); - wm8900_write(codec, WM8900_REG_POWER2, + snd_soc_write(codec, WM8900_REG_POWER2, WM8900_REG_POWER2_SYSCLK_ENA); - wm8900_write(codec, WM8900_REG_POWER3, 0); + snd_soc_write(codec, WM8900_REG_POWER3, 0); break; case SND_SOC_BIAS_OFF: /* Startup bias enable */ - reg = wm8900_read(codec, WM8900_REG_POWER1); - wm8900_write(codec, WM8900_REG_POWER1, + reg = snd_soc_read(codec, WM8900_REG_POWER1); + snd_soc_write(codec, WM8900_REG_POWER1, reg & WM8900_REG_POWER1_STARTUP_BIAS_ENA); - wm8900_write(codec, WM8900_REG_ADDCTL, + snd_soc_write(codec, WM8900_REG_ADDCTL, WM8900_REG_ADDCTL_BIAS_SRC | WM8900_REG_ADDCTL_VMID_SOFTST); /* Discharge caps */ - wm8900_write(codec, WM8900_REG_POWER1, + snd_soc_write(codec, WM8900_REG_POWER1, WM8900_REG_POWER1_STARTUP_BIAS_ENA); schedule_timeout_interruptible(msecs_to_jiffies(500)); /* Remove clamp */ - wm8900_write(codec, WM8900_REG_HPCTL1, 0); + snd_soc_write(codec, WM8900_REG_HPCTL1, 0); /* Power down */ - wm8900_write(codec, WM8900_REG_ADDCTL, 0); - wm8900_write(codec, WM8900_REG_POWER1, 0); - wm8900_write(codec, WM8900_REG_POWER2, 0); - wm8900_write(codec, WM8900_REG_POWER3, 0); + snd_soc_write(codec, WM8900_REG_ADDCTL, 0); + snd_soc_write(codec, WM8900_REG_POWER1, 0); + snd_soc_write(codec, WM8900_REG_POWER2, 0); + snd_soc_write(codec, WM8900_REG_POWER3, 0); /* Need to let things settle before stopping the clock * to ensure that restart works, see "Stopping the * master clock" in the datasheet. */ schedule_timeout_interruptible(msecs_to_jiffies(1)); - wm8900_write(codec, WM8900_REG_POWER2, + snd_soc_write(codec, WM8900_REG_POWER2, WM8900_REG_POWER2_SYSCLK_ENA); break; } @@ -1275,7 +1184,7 @@ static int wm8900_resume(struct platform_device *pdev) if (cache) { for (i = 0; i < WM8900_MAXREG; i++) - wm8900_write(codec, i, cache[i]); + snd_soc_write(codec, i, cache[i]); kfree(cache); } else dev_err(&pdev->dev, "Unable to allocate register cache\n"); @@ -1308,16 +1217,20 @@ static __devinit int wm8900_i2c_probe(struct i2c_client *i2c, codec->name = "WM8900"; codec->owner = THIS_MODULE; - codec->read = wm8900_read; - codec->write = wm8900_write; codec->dai = &wm8900_dai; codec->num_dai = 1; - codec->hw_write = (hw_write_t)i2c_master_send; codec->control_data = i2c; codec->set_bias_level = wm8900_set_bias_level; + codec->volatile_register = wm8900_volatile_register; codec->dev = &i2c->dev; - reg = wm8900_read(codec, WM8900_REG_ID); + ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to set cache I/O: %d\n", ret); + goto err; + } + + reg = snd_soc_read(codec, WM8900_REG_ID); if (reg != 0x8900) { dev_err(&i2c->dev, "Device is not a WM8900 - ID %x\n", reg); ret = -ENODEV; @@ -1325,7 +1238,7 @@ static __devinit int wm8900_i2c_probe(struct i2c_client *i2c, } /* Read back from the chip */ - reg = wm8900_chip_read(codec, WM8900_REG_POWER1); + reg = snd_soc_read(codec, WM8900_REG_POWER1); reg = (reg >> 12) & 0xf; dev_info(&i2c->dev, "WM8900 revision %d\n", reg); @@ -1335,29 +1248,29 @@ static __devinit int wm8900_i2c_probe(struct i2c_client *i2c, wm8900_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* Latch the volume update bits */ - wm8900_write(codec, WM8900_REG_LINVOL, - wm8900_read(codec, WM8900_REG_LINVOL) | 0x100); - wm8900_write(codec, WM8900_REG_RINVOL, - wm8900_read(codec, WM8900_REG_RINVOL) | 0x100); - wm8900_write(codec, WM8900_REG_LOUT1CTL, - wm8900_read(codec, WM8900_REG_LOUT1CTL) | 0x100); - wm8900_write(codec, WM8900_REG_ROUT1CTL, - wm8900_read(codec, WM8900_REG_ROUT1CTL) | 0x100); - wm8900_write(codec, WM8900_REG_LOUT2CTL, - wm8900_read(codec, WM8900_REG_LOUT2CTL) | 0x100); - wm8900_write(codec, WM8900_REG_ROUT2CTL, - wm8900_read(codec, WM8900_REG_ROUT2CTL) | 0x100); - wm8900_write(codec, WM8900_REG_LDAC_DV, - wm8900_read(codec, WM8900_REG_LDAC_DV) | 0x100); - wm8900_write(codec, WM8900_REG_RDAC_DV, - wm8900_read(codec, WM8900_REG_RDAC_DV) | 0x100); - wm8900_write(codec, WM8900_REG_LADC_DV, - wm8900_read(codec, WM8900_REG_LADC_DV) | 0x100); - wm8900_write(codec, WM8900_REG_RADC_DV, - wm8900_read(codec, WM8900_REG_RADC_DV) | 0x100); + snd_soc_write(codec, WM8900_REG_LINVOL, + snd_soc_read(codec, WM8900_REG_LINVOL) | 0x100); + snd_soc_write(codec, WM8900_REG_RINVOL, + snd_soc_read(codec, WM8900_REG_RINVOL) | 0x100); + snd_soc_write(codec, WM8900_REG_LOUT1CTL, + snd_soc_read(codec, WM8900_REG_LOUT1CTL) | 0x100); + snd_soc_write(codec, WM8900_REG_ROUT1CTL, + snd_soc_read(codec, WM8900_REG_ROUT1CTL) | 0x100); + snd_soc_write(codec, WM8900_REG_LOUT2CTL, + snd_soc_read(codec, WM8900_REG_LOUT2CTL) | 0x100); + snd_soc_write(codec, WM8900_REG_ROUT2CTL, + snd_soc_read(codec, WM8900_REG_ROUT2CTL) | 0x100); + snd_soc_write(codec, WM8900_REG_LDAC_DV, + snd_soc_read(codec, WM8900_REG_LDAC_DV) | 0x100); + snd_soc_write(codec, WM8900_REG_RDAC_DV, + snd_soc_read(codec, WM8900_REG_RDAC_DV) | 0x100); + snd_soc_write(codec, WM8900_REG_LADC_DV, + snd_soc_read(codec, WM8900_REG_LADC_DV) | 0x100); + snd_soc_write(codec, WM8900_REG_RADC_DV, + snd_soc_read(codec, WM8900_REG_RADC_DV) | 0x100); /* Set the DAC and mixer output bias */ - wm8900_write(codec, WM8900_REG_OUTBIASCTL, 0x81); + snd_soc_write(codec, WM8900_REG_OUTBIASCTL, 0x81); wm8900_dai.dev = &i2c->dev; diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index c9baeae3e27..fe1307b500c 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -225,94 +225,18 @@ struct wm8903_priv { struct snd_pcm_substream *slave_substream; }; - -static unsigned int wm8903_read_reg_cache(struct snd_soc_codec *codec, - unsigned int reg) -{ - u16 *cache = codec->reg_cache; - - BUG_ON(reg >= ARRAY_SIZE(wm8903_reg_defaults)); - - return cache[reg]; -} - -static unsigned int wm8903_hw_read(struct snd_soc_codec *codec, u8 reg) -{ - struct i2c_msg xfer[2]; - u16 data; - int ret; - struct i2c_client *client = codec->control_data; - - /* Write register */ - xfer[0].addr = client->addr; - xfer[0].flags = 0; - xfer[0].len = 1; - xfer[0].buf = ® - - /* Read data */ - xfer[1].addr = client->addr; - xfer[1].flags = I2C_M_RD; - xfer[1].len = 2; - xfer[1].buf = (u8 *)&data; - - ret = i2c_transfer(client->adapter, xfer, 2); - if (ret != 2) { - pr_err("i2c_transfer returned %d\n", ret); - return 0; - } - - return (data >> 8) | ((data & 0xff) << 8); -} - -static unsigned int wm8903_read(struct snd_soc_codec *codec, - unsigned int reg) +static int wm8903_volatile_register(unsigned int reg) { switch (reg) { case WM8903_SW_RESET_AND_ID: case WM8903_REVISION_NUMBER: case WM8903_INTERRUPT_STATUS_1: case WM8903_WRITE_SEQUENCER_4: - return wm8903_hw_read(codec, reg); + return 1; default: - return wm8903_read_reg_cache(codec, reg); - } -} - -static void wm8903_write_reg_cache(struct snd_soc_codec *codec, - u16 reg, unsigned int value) -{ - u16 *cache = codec->reg_cache; - - BUG_ON(reg >= ARRAY_SIZE(wm8903_reg_defaults)); - - switch (reg) { - case WM8903_SW_RESET_AND_ID: - case WM8903_REVISION_NUMBER: - break; - - default: - cache[reg] = value; - break; - } -} - -static int wm8903_write(struct snd_soc_codec *codec, unsigned int reg, - unsigned int value) -{ - u8 data[3]; - - wm8903_write_reg_cache(codec, reg, value); - - /* Data format is 1 byte of address followed by 2 bytes of data */ - data[0] = reg; - data[1] = (value >> 8) & 0xff; - data[2] = value & 0xff; - - if (codec->hw_write(codec->control_data, data, 3) == 2) return 0; - else - return -EIO; + } } static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start) @@ -323,13 +247,13 @@ static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start) BUG_ON(start > 48); /* Enable the sequencer */ - reg[0] = wm8903_read(codec, WM8903_WRITE_SEQUENCER_0); + reg[0] = snd_soc_read(codec, WM8903_WRITE_SEQUENCER_0); reg[0] |= WM8903_WSEQ_ENA; - wm8903_write(codec, WM8903_WRITE_SEQUENCER_0, reg[0]); + snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, reg[0]); dev_dbg(&i2c->dev, "Starting sequence at %d\n", start); - wm8903_write(codec, WM8903_WRITE_SEQUENCER_3, + snd_soc_write(codec, WM8903_WRITE_SEQUENCER_3, start | WM8903_WSEQ_START); /* Wait for it to complete. If we have the interrupt wired up then @@ -339,13 +263,13 @@ static int wm8903_run_sequence(struct snd_soc_codec *codec, unsigned int start) do { msleep(10); - reg[4] = wm8903_read(codec, WM8903_WRITE_SEQUENCER_4); + reg[4] = snd_soc_read(codec, WM8903_WRITE_SEQUENCER_4); } while (reg[4] & WM8903_WSEQ_BUSY); dev_dbg(&i2c->dev, "Sequence complete\n"); /* Disable the sequencer again */ - wm8903_write(codec, WM8903_WRITE_SEQUENCER_0, + snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, reg[0] & ~WM8903_WSEQ_ENA); return 0; @@ -357,12 +281,12 @@ static void wm8903_sync_reg_cache(struct snd_soc_codec *codec, u16 *cache) /* There really ought to be something better we can do here :/ */ for (i = 0; i < ARRAY_SIZE(wm8903_reg_defaults); i++) - cache[i] = wm8903_hw_read(codec, i); + cache[i] = codec->hw_read(codec, i); } static void wm8903_reset(struct snd_soc_codec *codec) { - wm8903_write(codec, WM8903_SW_RESET_AND_ID, 0); + snd_soc_write(codec, WM8903_SW_RESET_AND_ID, 0); memcpy(codec->reg_cache, wm8903_reg_defaults, sizeof(wm8903_reg_defaults)); } @@ -423,52 +347,52 @@ static int wm8903_output_event(struct snd_soc_dapm_widget *w, } if (event & SND_SOC_DAPM_PRE_PMU) { - val = wm8903_read(codec, reg); + val = snd_soc_read(codec, reg); /* Short the output */ val &= ~(WM8903_OUTPUT_SHORT << shift); - wm8903_write(codec, reg, val); + snd_soc_write(codec, reg, val); } if (event & SND_SOC_DAPM_POST_PMU) { - val = wm8903_read(codec, reg); + val = snd_soc_read(codec, reg); val |= (WM8903_OUTPUT_IN << shift); - wm8903_write(codec, reg, val); + snd_soc_write(codec, reg, val); val |= (WM8903_OUTPUT_INT << shift); - wm8903_write(codec, reg, val); + snd_soc_write(codec, reg, val); /* Turn on the output ENA_OUTP */ val |= (WM8903_OUTPUT_OUT << shift); - wm8903_write(codec, reg, val); + snd_soc_write(codec, reg, val); /* Enable the DC servo */ - dcs_reg = wm8903_read(codec, WM8903_DC_SERVO_0); + dcs_reg = snd_soc_read(codec, WM8903_DC_SERVO_0); dcs_reg |= dcs_bit; - wm8903_write(codec, WM8903_DC_SERVO_0, dcs_reg); + snd_soc_write(codec, WM8903_DC_SERVO_0, dcs_reg); /* Remove the short */ val |= (WM8903_OUTPUT_SHORT << shift); - wm8903_write(codec, reg, val); + snd_soc_write(codec, reg, val); } if (event & SND_SOC_DAPM_PRE_PMD) { - val = wm8903_read(codec, reg); + val = snd_soc_read(codec, reg); /* Short the output */ val &= ~(WM8903_OUTPUT_SHORT << shift); - wm8903_write(codec, reg, val); + snd_soc_write(codec, reg, val); /* Disable the DC servo */ - dcs_reg = wm8903_read(codec, WM8903_DC_SERVO_0); + dcs_reg = snd_soc_read(codec, WM8903_DC_SERVO_0); dcs_reg &= ~dcs_bit; - wm8903_write(codec, WM8903_DC_SERVO_0, dcs_reg); + snd_soc_write(codec, WM8903_DC_SERVO_0, dcs_reg); /* Then disable the intermediate and output stages */ val &= ~((WM8903_OUTPUT_OUT | WM8903_OUTPUT_INT | WM8903_OUTPUT_IN) << shift); - wm8903_write(codec, reg, val); + snd_soc_write(codec, reg, val); } return 0; @@ -492,13 +416,13 @@ static int wm8903_class_w_put(struct snd_kcontrol *kcontrol, u16 reg; int ret; - reg = wm8903_read(codec, WM8903_CLASS_W_0); + reg = snd_soc_read(codec, WM8903_CLASS_W_0); /* Turn it off if we're about to enable bypass */ if (ucontrol->value.integer.value[0]) { if (wm8903->class_w_users == 0) { dev_dbg(&i2c->dev, "Disabling Class W\n"); - wm8903_write(codec, WM8903_CLASS_W_0, reg & + snd_soc_write(codec, WM8903_CLASS_W_0, reg & ~(WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V)); } wm8903->class_w_users++; @@ -511,7 +435,7 @@ static int wm8903_class_w_put(struct snd_kcontrol *kcontrol, if (!ucontrol->value.integer.value[0]) { if (wm8903->class_w_users == 1) { dev_dbg(&i2c->dev, "Enabling Class W\n"); - wm8903_write(codec, WM8903_CLASS_W_0, reg | + snd_soc_write(codec, WM8903_CLASS_W_0, reg | WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V); } wm8903->class_w_users--; @@ -1009,55 +933,55 @@ static int wm8903_set_bias_level(struct snd_soc_codec *codec, switch (level) { case SND_SOC_BIAS_ON: case SND_SOC_BIAS_PREPARE: - reg = wm8903_read(codec, WM8903_VMID_CONTROL_0); + reg = snd_soc_read(codec, WM8903_VMID_CONTROL_0); reg &= ~(WM8903_VMID_RES_MASK); reg |= WM8903_VMID_RES_50K; - wm8903_write(codec, WM8903_VMID_CONTROL_0, reg); + snd_soc_write(codec, WM8903_VMID_CONTROL_0, reg); break; case SND_SOC_BIAS_STANDBY: if (codec->bias_level == SND_SOC_BIAS_OFF) { - wm8903_write(codec, WM8903_CLOCK_RATES_2, + snd_soc_write(codec, WM8903_CLOCK_RATES_2, WM8903_CLK_SYS_ENA); /* Change DC servo dither level in startup sequence */ - wm8903_write(codec, WM8903_WRITE_SEQUENCER_0, 0x11); - wm8903_write(codec, WM8903_WRITE_SEQUENCER_1, 0x1257); - wm8903_write(codec, WM8903_WRITE_SEQUENCER_2, 0x2); + snd_soc_write(codec, WM8903_WRITE_SEQUENCER_0, 0x11); + snd_soc_write(codec, WM8903_WRITE_SEQUENCER_1, 0x1257); + snd_soc_write(codec, WM8903_WRITE_SEQUENCER_2, 0x2); wm8903_run_sequence(codec, 0); wm8903_sync_reg_cache(codec, codec->reg_cache); /* Enable low impedence charge pump output */ - reg = wm8903_read(codec, + reg = snd_soc_read(codec, WM8903_CONTROL_INTERFACE_TEST_1); - wm8903_write(codec, WM8903_CONTROL_INTERFACE_TEST_1, + snd_soc_write(codec, WM8903_CONTROL_INTERFACE_TEST_1, reg | WM8903_TEST_KEY); - reg2 = wm8903_read(codec, WM8903_CHARGE_PUMP_TEST_1); - wm8903_write(codec, WM8903_CHARGE_PUMP_TEST_1, + reg2 = snd_soc_read(codec, WM8903_CHARGE_PUMP_TEST_1); + snd_soc_write(codec, WM8903_CHARGE_PUMP_TEST_1, reg2 | WM8903_CP_SW_KELVIN_MODE_MASK); - wm8903_write(codec, WM8903_CONTROL_INTERFACE_TEST_1, + snd_soc_write(codec, WM8903_CONTROL_INTERFACE_TEST_1, reg); /* By default no bypass paths are enabled so * enable Class W support. */ dev_dbg(&i2c->dev, "Enabling Class W\n"); - wm8903_write(codec, WM8903_CLASS_W_0, reg | + snd_soc_write(codec, WM8903_CLASS_W_0, reg | WM8903_CP_DYN_FREQ | WM8903_CP_DYN_V); } - reg = wm8903_read(codec, WM8903_VMID_CONTROL_0); + reg = snd_soc_read(codec, WM8903_VMID_CONTROL_0); reg &= ~(WM8903_VMID_RES_MASK); reg |= WM8903_VMID_RES_250K; - wm8903_write(codec, WM8903_VMID_CONTROL_0, reg); + snd_soc_write(codec, WM8903_VMID_CONTROL_0, reg); break; case SND_SOC_BIAS_OFF: wm8903_run_sequence(codec, 32); - reg = wm8903_read(codec, WM8903_CLOCK_RATES_2); + reg = snd_soc_read(codec, WM8903_CLOCK_RATES_2); reg &= ~WM8903_CLK_SYS_ENA; - wm8903_write(codec, WM8903_CLOCK_RATES_2, reg); + snd_soc_write(codec, WM8903_CLOCK_RATES_2, reg); break; } @@ -1081,7 +1005,7 @@ static int wm8903_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; - u16 aif1 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_1); + u16 aif1 = snd_soc_read(codec, WM8903_AUDIO_INTERFACE_1); aif1 &= ~(WM8903_LRCLK_DIR | WM8903_BCLK_DIR | WM8903_AIF_FMT_MASK | WM8903_AIF_LRCLK_INV | WM8903_AIF_BCLK_INV); @@ -1159,7 +1083,7 @@ static int wm8903_set_dai_fmt(struct snd_soc_dai *codec_dai, return -EINVAL; } - wm8903_write(codec, WM8903_AUDIO_INTERFACE_1, aif1); + snd_soc_write(codec, WM8903_AUDIO_INTERFACE_1, aif1); return 0; } @@ -1169,14 +1093,14 @@ static int wm8903_digital_mute(struct snd_soc_dai *codec_dai, int mute) struct snd_soc_codec *codec = codec_dai->codec; u16 reg; - reg = wm8903_read(codec, WM8903_DAC_DIGITAL_1); + reg = snd_soc_read(codec, WM8903_DAC_DIGITAL_1); if (mute) reg |= WM8903_DAC_MUTE; else reg &= ~WM8903_DAC_MUTE; - wm8903_write(codec, WM8903_DAC_DIGITAL_1, reg); + snd_soc_write(codec, WM8903_DAC_DIGITAL_1, reg); return 0; } @@ -1366,12 +1290,12 @@ static int wm8903_hw_params(struct snd_pcm_substream *substream, int cur_val; int clk_sys; - u16 aif1 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_1); - u16 aif2 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_2); - u16 aif3 = wm8903_read(codec, WM8903_AUDIO_INTERFACE_3); - u16 clock0 = wm8903_read(codec, WM8903_CLOCK_RATES_0); - u16 clock1 = wm8903_read(codec, WM8903_CLOCK_RATES_1); - u16 dac_digital1 = wm8903_read(codec, WM8903_DAC_DIGITAL_1); + u16 aif1 = snd_soc_read(codec, WM8903_AUDIO_INTERFACE_1); + u16 aif2 = snd_soc_read(codec, WM8903_AUDIO_INTERFACE_2); + u16 aif3 = snd_soc_read(codec, WM8903_AUDIO_INTERFACE_3); + u16 clock0 = snd_soc_read(codec, WM8903_CLOCK_RATES_0); + u16 clock1 = snd_soc_read(codec, WM8903_CLOCK_RATES_1); + u16 dac_digital1 = snd_soc_read(codec, WM8903_DAC_DIGITAL_1); if (substream == wm8903->slave_substream) { dev_dbg(&i2c->dev, "Ignoring hw_params for slave substream\n"); @@ -1503,12 +1427,12 @@ static int wm8903_hw_params(struct snd_pcm_substream *substream, aif2 |= bclk_divs[bclk_div].div; aif3 |= bclk / fs; - wm8903_write(codec, WM8903_CLOCK_RATES_0, clock0); - wm8903_write(codec, WM8903_CLOCK_RATES_1, clock1); - wm8903_write(codec, WM8903_AUDIO_INTERFACE_1, aif1); - wm8903_write(codec, WM8903_AUDIO_INTERFACE_2, aif2); - wm8903_write(codec, WM8903_AUDIO_INTERFACE_3, aif3); - wm8903_write(codec, WM8903_DAC_DIGITAL_1, dac_digital1); + snd_soc_write(codec, WM8903_CLOCK_RATES_0, clock0); + snd_soc_write(codec, WM8903_CLOCK_RATES_1, clock1); + snd_soc_write(codec, WM8903_AUDIO_INTERFACE_1, aif1); + snd_soc_write(codec, WM8903_AUDIO_INTERFACE_2, aif2); + snd_soc_write(codec, WM8903_AUDIO_INTERFACE_3, aif3); + snd_soc_write(codec, WM8903_DAC_DIGITAL_1, dac_digital1); return 0; } @@ -1593,7 +1517,7 @@ static int wm8903_resume(struct platform_device *pdev) if (tmp_cache) { for (i = 2; i < ARRAY_SIZE(wm8903_reg_defaults); i++) if (tmp_cache[i] != reg_cache[i]) - wm8903_write(codec, i, tmp_cache[i]); + snd_soc_write(codec, i, tmp_cache[i]); } else { dev_err(&i2c->dev, "Failed to allocate temporary cache\n"); } @@ -1624,9 +1548,6 @@ static __devinit int wm8903_i2c_probe(struct i2c_client *i2c, codec->dev = &i2c->dev; codec->name = "WM8903"; codec->owner = THIS_MODULE; - codec->read = wm8903_read; - codec->write = wm8903_write; - codec->hw_write = (hw_write_t)i2c_master_send; codec->bias_level = SND_SOC_BIAS_OFF; codec->set_bias_level = wm8903_set_bias_level; codec->dai = &wm8903_dai; @@ -1634,18 +1555,25 @@ static __devinit int wm8903_i2c_probe(struct i2c_client *i2c, codec->reg_cache_size = ARRAY_SIZE(wm8903->reg_cache); codec->reg_cache = &wm8903->reg_cache[0]; codec->private_data = wm8903; + codec->volatile_register = wm8903_volatile_register; i2c_set_clientdata(i2c, codec); codec->control_data = i2c; - val = wm8903_hw_read(codec, WM8903_SW_RESET_AND_ID); + ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to set cache I/O: %d\n", ret); + goto err; + } + + val = snd_soc_read(codec, WM8903_SW_RESET_AND_ID); if (val != wm8903_reg_defaults[WM8903_SW_RESET_AND_ID]) { dev_err(&i2c->dev, "Device with ID register %x is not a WM8903\n", val); return -ENODEV; } - val = wm8903_read(codec, WM8903_REVISION_NUMBER); + val = snd_soc_read(codec, WM8903_REVISION_NUMBER); dev_info(&i2c->dev, "WM8903 revision %d\n", val & WM8903_CHIP_REV_MASK); @@ -1655,35 +1583,35 @@ static __devinit int wm8903_i2c_probe(struct i2c_client *i2c, wm8903_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* Latch volume update bits */ - val = wm8903_read(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT); + val = snd_soc_read(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT); val |= WM8903_ADCVU; - wm8903_write(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT, val); - wm8903_write(codec, WM8903_ADC_DIGITAL_VOLUME_RIGHT, val); + snd_soc_write(codec, WM8903_ADC_DIGITAL_VOLUME_LEFT, val); + snd_soc_write(codec, WM8903_ADC_DIGITAL_VOLUME_RIGHT, val); - val = wm8903_read(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT); + val = snd_soc_read(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT); val |= WM8903_DACVU; - wm8903_write(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT, val); - wm8903_write(codec, WM8903_DAC_DIGITAL_VOLUME_RIGHT, val); + snd_soc_write(codec, WM8903_DAC_DIGITAL_VOLUME_LEFT, val); + snd_soc_write(codec, WM8903_DAC_DIGITAL_VOLUME_RIGHT, val); - val = wm8903_read(codec, WM8903_ANALOGUE_OUT1_LEFT); + val = snd_soc_read(codec, WM8903_ANALOGUE_OUT1_LEFT); val |= WM8903_HPOUTVU; - wm8903_write(codec, WM8903_ANALOGUE_OUT1_LEFT, val); - wm8903_write(codec, WM8903_ANALOGUE_OUT1_RIGHT, val); + snd_soc_write(codec, WM8903_ANALOGUE_OUT1_LEFT, val); + snd_soc_write(codec, WM8903_ANALOGUE_OUT1_RIGHT, val); - val = wm8903_read(codec, WM8903_ANALOGUE_OUT2_LEFT); + val = snd_soc_read(codec, WM8903_ANALOGUE_OUT2_LEFT); val |= WM8903_LINEOUTVU; - wm8903_write(codec, WM8903_ANALOGUE_OUT2_LEFT, val); - wm8903_write(codec, WM8903_ANALOGUE_OUT2_RIGHT, val); + snd_soc_write(codec, WM8903_ANALOGUE_OUT2_LEFT, val); + snd_soc_write(codec, WM8903_ANALOGUE_OUT2_RIGHT, val); - val = wm8903_read(codec, WM8903_ANALOGUE_OUT3_LEFT); + val = snd_soc_read(codec, WM8903_ANALOGUE_OUT3_LEFT); val |= WM8903_SPKVU; - wm8903_write(codec, WM8903_ANALOGUE_OUT3_LEFT, val); - wm8903_write(codec, WM8903_ANALOGUE_OUT3_RIGHT, val); + snd_soc_write(codec, WM8903_ANALOGUE_OUT3_LEFT, val); + snd_soc_write(codec, WM8903_ANALOGUE_OUT3_RIGHT, val); /* Enable DAC soft mute by default */ - val = wm8903_read(codec, WM8903_DAC_DIGITAL_1); + val = snd_soc_read(codec, WM8903_DAC_DIGITAL_1); val |= WM8903_DAC_MUTEMODE; - wm8903_write(codec, WM8903_DAC_DIGITAL_1, val); + snd_soc_write(codec, WM8903_DAC_DIGITAL_1, val); wm8903_dai.dev = &i2c->dev; wm8903_codec = codec; diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c index b69210a7742..da97aae475a 100644 --- a/sound/soc/codecs/wm8940.c +++ b/sound/soc/codecs/wm8940.c @@ -106,50 +106,6 @@ static u16 wm8940_reg_defaults[] = { 0x0000, /* Mono Mixer Control */ }; -static inline unsigned int wm8940_read_reg_cache(struct snd_soc_codec *codec, - unsigned int reg) -{ - u16 *cache = codec->reg_cache; - - if (reg >= ARRAY_SIZE(wm8940_reg_defaults)) - return -1; - - return cache[reg]; -} - -static inline int wm8940_write_reg_cache(struct snd_soc_codec *codec, - u16 reg, unsigned int value) -{ - u16 *cache = codec->reg_cache; - - if (reg >= ARRAY_SIZE(wm8940_reg_defaults)) - return -1; - - cache[reg] = value; - - return 0; -} - -static int wm8940_write(struct snd_soc_codec *codec, unsigned int reg, - unsigned int value) -{ - int ret; - u8 data[3] = { reg, - (value & 0xff00) >> 8, - (value & 0x00ff) - }; - - wm8940_write_reg_cache(codec, reg, value); - - ret = codec->hw_write(codec->control_data, data, 3); - - if (ret < 0) - return ret; - else if (ret != 3) - return -EIO; - return 0; -} - static const char *wm8940_companding[] = { "Off", "NC", "u-law", "A-law" }; static const struct soc_enum wm8940_adc_companding_enum = SOC_ENUM_SINGLE(WM8940_COMPANDINGCTL, 1, 4, wm8940_companding); @@ -348,14 +304,14 @@ error_ret: return ret; } -#define wm8940_reset(c) wm8940_write(c, WM8940_SOFTRESET, 0); +#define wm8940_reset(c) snd_soc_write(c, WM8940_SOFTRESET, 0); static int wm8940_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt) { struct snd_soc_codec *codec = codec_dai->codec; - u16 iface = wm8940_read_reg_cache(codec, WM8940_IFACE) & 0xFE67; - u16 clk = wm8940_read_reg_cache(codec, WM8940_CLOCK) & 0x1fe; + u16 iface = snd_soc_read(codec, WM8940_IFACE) & 0xFE67; + u16 clk = snd_soc_read(codec, WM8940_CLOCK) & 0x1fe; switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { case SND_SOC_DAIFMT_CBM_CFM: @@ -366,7 +322,7 @@ static int wm8940_set_dai_fmt(struct snd_soc_dai *codec_dai, default: return -EINVAL; } - wm8940_write(codec, WM8940_CLOCK, clk); + snd_soc_write(codec, WM8940_CLOCK, clk); switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_I2S: @@ -399,7 +355,7 @@ static int wm8940_set_dai_fmt(struct snd_soc_dai *codec_dai, break; } - wm8940_write(codec, WM8940_IFACE, iface); + snd_soc_write(codec, WM8940_IFACE, iface); return 0; } @@ -411,9 +367,9 @@ static int wm8940_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; - u16 iface = wm8940_read_reg_cache(codec, WM8940_IFACE) & 0xFD9F; - u16 addcntrl = wm8940_read_reg_cache(codec, WM8940_ADDCNTRL) & 0xFFF1; - u16 companding = wm8940_read_reg_cache(codec, + u16 iface = snd_soc_read(codec, WM8940_IFACE) & 0xFD9F; + u16 addcntrl = snd_soc_read(codec, WM8940_ADDCNTRL) & 0xFFF1; + u16 companding = snd_soc_read(codec, WM8940_COMPANDINGCTL) & 0xFFDF; int ret; @@ -442,7 +398,7 @@ static int wm8940_i2s_hw_params(struct snd_pcm_substream *substream, case SNDRV_PCM_RATE_48000: break; } - ret = wm8940_write(codec, WM8940_ADDCNTRL, addcntrl); + ret = snd_soc_write(codec, WM8940_ADDCNTRL, addcntrl); if (ret) goto error_ret; @@ -462,10 +418,10 @@ static int wm8940_i2s_hw_params(struct snd_pcm_substream *substream, iface |= (3 << 5); break; } - ret = wm8940_write(codec, WM8940_COMPANDINGCTL, companding); + ret = snd_soc_write(codec, WM8940_COMPANDINGCTL, companding); if (ret) goto error_ret; - ret = wm8940_write(codec, WM8940_IFACE, iface); + ret = snd_soc_write(codec, WM8940_IFACE, iface); error_ret: return ret; @@ -474,19 +430,19 @@ error_ret: static int wm8940_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; - u16 mute_reg = wm8940_read_reg_cache(codec, WM8940_DAC) & 0xffbf; + u16 mute_reg = snd_soc_read(codec, WM8940_DAC) & 0xffbf; if (mute) mute_reg |= 0x40; - return wm8940_write(codec, WM8940_DAC, mute_reg); + return snd_soc_write(codec, WM8940_DAC, mute_reg); } static int wm8940_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { u16 val; - u16 pwr_reg = wm8940_read_reg_cache(codec, WM8940_POWER1) & 0x1F0; + u16 pwr_reg = snd_soc_read(codec, WM8940_POWER1) & 0x1F0; int ret = 0; switch (level) { @@ -494,26 +450,26 @@ static int wm8940_set_bias_level(struct snd_soc_codec *codec, /* ensure bufioen and biasen */ pwr_reg |= (1 << 2) | (1 << 3); /* Enable thermal shutdown */ - val = wm8940_read_reg_cache(codec, WM8940_OUTPUTCTL); - ret = wm8940_write(codec, WM8940_OUTPUTCTL, val | 0x2); + val = snd_soc_read(codec, WM8940_OUTPUTCTL); + ret = snd_soc_write(codec, WM8940_OUTPUTCTL, val | 0x2); if (ret) break; /* set vmid to 75k */ - ret = wm8940_write(codec, WM8940_POWER1, pwr_reg | 0x1); + ret = snd_soc_write(codec, WM8940_POWER1, pwr_reg | 0x1); break; case SND_SOC_BIAS_PREPARE: /* ensure bufioen and biasen */ pwr_reg |= (1 << 2) | (1 << 3); - ret = wm8940_write(codec, WM8940_POWER1, pwr_reg | 0x1); + ret = snd_soc_write(codec, WM8940_POWER1, pwr_reg | 0x1); break; case SND_SOC_BIAS_STANDBY: /* ensure bufioen and biasen */ pwr_reg |= (1 << 2) | (1 << 3); /* set vmid to 300k for standby */ - ret = wm8940_write(codec, WM8940_POWER1, pwr_reg | 0x2); + ret = snd_soc_write(codec, WM8940_POWER1, pwr_reg | 0x2); break; case SND_SOC_BIAS_OFF: - ret = wm8940_write(codec, WM8940_POWER1, pwr_reg); + ret = snd_soc_write(codec, WM8940_POWER1, pwr_reg); break; } @@ -587,36 +543,36 @@ static int wm8940_set_dai_pll(struct snd_soc_dai *codec_dai, u16 reg; /* Turn off PLL */ - reg = wm8940_read_reg_cache(codec, WM8940_POWER1); - wm8940_write(codec, WM8940_POWER1, reg & 0x1df); + reg = snd_soc_read(codec, WM8940_POWER1); + snd_soc_write(codec, WM8940_POWER1, reg & 0x1df); if (freq_in == 0 || freq_out == 0) { /* Clock CODEC directly from MCLK */ - reg = wm8940_read_reg_cache(codec, WM8940_CLOCK); - wm8940_write(codec, WM8940_CLOCK, reg & 0x0ff); + reg = snd_soc_read(codec, WM8940_CLOCK); + snd_soc_write(codec, WM8940_CLOCK, reg & 0x0ff); /* Pll power down */ - wm8940_write(codec, WM8940_PLLN, (1 << 7)); + snd_soc_write(codec, WM8940_PLLN, (1 << 7)); return 0; } /* Pll is followed by a frequency divide by 4 */ pll_factors(freq_out*4, freq_in); if (pll_div.k) - wm8940_write(codec, WM8940_PLLN, + snd_soc_write(codec, WM8940_PLLN, (pll_div.pre_scale << 4) | pll_div.n | (1 << 6)); else /* No factional component */ - wm8940_write(codec, WM8940_PLLN, + snd_soc_write(codec, WM8940_PLLN, (pll_div.pre_scale << 4) | pll_div.n); - wm8940_write(codec, WM8940_PLLK1, pll_div.k >> 18); - wm8940_write(codec, WM8940_PLLK2, (pll_div.k >> 9) & 0x1ff); - wm8940_write(codec, WM8940_PLLK3, pll_div.k & 0x1ff); + snd_soc_write(codec, WM8940_PLLK1, pll_div.k >> 18); + snd_soc_write(codec, WM8940_PLLK2, (pll_div.k >> 9) & 0x1ff); + snd_soc_write(codec, WM8940_PLLK3, pll_div.k & 0x1ff); /* Enable the PLL */ - reg = wm8940_read_reg_cache(codec, WM8940_POWER1); - wm8940_write(codec, WM8940_POWER1, reg | 0x020); + reg = snd_soc_read(codec, WM8940_POWER1); + snd_soc_write(codec, WM8940_POWER1, reg | 0x020); /* Run CODEC from PLL instead of MCLK */ - reg = wm8940_read_reg_cache(codec, WM8940_CLOCK); - wm8940_write(codec, WM8940_CLOCK, reg | 0x100); + reg = snd_soc_read(codec, WM8940_CLOCK); + snd_soc_write(codec, WM8940_CLOCK, reg | 0x100); return 0; } @@ -648,16 +604,16 @@ static int wm8940_set_dai_clkdiv(struct snd_soc_dai *codec_dai, switch (div_id) { case WM8940_BCLKDIV: - reg = wm8940_read_reg_cache(codec, WM8940_CLOCK) & 0xFFEF3; - ret = wm8940_write(codec, WM8940_CLOCK, reg | (div << 2)); + reg = snd_soc_read(codec, WM8940_CLOCK) & 0xFFEF3; + ret = snd_soc_write(codec, WM8940_CLOCK, reg | (div << 2)); break; case WM8940_MCLKDIV: - reg = wm8940_read_reg_cache(codec, WM8940_CLOCK) & 0xFF1F; - ret = wm8940_write(codec, WM8940_CLOCK, reg | (div << 5)); + reg = snd_soc_read(codec, WM8940_CLOCK) & 0xFF1F; + ret = snd_soc_write(codec, WM8940_CLOCK, reg | (div << 5)); break; case WM8940_OPCLKDIV: - reg = wm8940_read_reg_cache(codec, WM8940_ADDCNTRL) & 0xFFCF; - ret = wm8940_write(codec, WM8940_ADDCNTRL, reg | (div << 4)); + reg = snd_soc_read(codec, WM8940_ADDCNTRL) & 0xFFCF; + ret = snd_soc_write(codec, WM8940_ADDCNTRL, reg | (div << 4)); break; } return ret; @@ -808,7 +764,8 @@ struct snd_soc_codec_device soc_codec_dev_wm8940 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_wm8940); -static int wm8940_register(struct wm8940_priv *wm8940) +static int wm8940_register(struct wm8940_priv *wm8940, + enum snd_soc_control_type control) { struct wm8940_setup_data *pdata = wm8940->codec.dev->platform_data; struct snd_soc_codec *codec = &wm8940->codec; @@ -825,8 +782,6 @@ static int wm8940_register(struct wm8940_priv *wm8940) codec->private_data = wm8940; codec->name = "WM8940"; codec->owner = THIS_MODULE; - codec->read = wm8940_read_reg_cache; - codec->write = wm8940_write; codec->bias_level = SND_SOC_BIAS_OFF; codec->set_bias_level = wm8940_set_bias_level; codec->dai = &wm8940_dai; @@ -834,6 +789,12 @@ static int wm8940_register(struct wm8940_priv *wm8940) codec->reg_cache_size = ARRAY_SIZE(wm8940_reg_defaults); codec->reg_cache = &wm8940->reg_cache; + ret = snd_soc_codec_set_cache_io(codec, 8, 16, control); + if (ret == 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + memcpy(codec->reg_cache, wm8940_reg_defaults, sizeof(wm8940_reg_defaults)); @@ -847,15 +808,15 @@ static int wm8940_register(struct wm8940_priv *wm8940) wm8940_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - ret = wm8940_write(codec, WM8940_POWER1, 0x180); + ret = snd_soc_write(codec, WM8940_POWER1, 0x180); if (ret < 0) return ret; if (!pdata) dev_warn(codec->dev, "No platform data supplied\n"); else { - reg = wm8940_read_reg_cache(codec, WM8940_OUTPUTCTL); - ret = wm8940_write(codec, WM8940_OUTPUTCTL, reg | pdata->vroi); + reg = snd_soc_read(codec, WM8940_OUTPUTCTL); + ret = snd_soc_write(codec, WM8940_OUTPUTCTL, reg | pdata->vroi); if (ret < 0) return ret; } @@ -904,7 +865,7 @@ static int wm8940_i2c_probe(struct i2c_client *i2c, codec->control_data = i2c; codec->dev = &i2c->dev; - return wm8940_register(wm8940); + return wm8940_register(wm8940, SND_SOC_I2C); } static int __devexit wm8940_i2c_remove(struct i2c_client *client) diff --git a/sound/soc/codecs/wm8961.c b/sound/soc/codecs/wm8961.c index bd1af92a122..50303208589 100644 --- a/sound/soc/codecs/wm8961.c +++ b/sound/soc/codecs/wm8961.c @@ -292,9 +292,10 @@ struct wm8961_priv { u16 reg_cache[WM8961_MAX_REGISTER]; }; -static int wm8961_reg_is_volatile(int reg) +static int wm8961_volatile_register(unsigned int reg) { switch (reg) { + case WM8961_SOFTWARE_RESET: case WM8961_WRITE_SEQUENCER_7: case WM8961_DC_SERVO_1: return 1; @@ -304,76 +305,9 @@ static int wm8961_reg_is_volatile(int reg) } } -static unsigned int wm8961_read_reg_cache(struct snd_soc_codec *codec, - unsigned int reg) -{ - u16 *cache = codec->reg_cache; - BUG_ON(reg > WM8961_MAX_REGISTER); - return cache[reg]; -} - -static unsigned int wm8961_read_hw(struct snd_soc_codec *codec, u8 reg) -{ - struct i2c_msg xfer[2]; - u16 data; - int ret; - struct i2c_client *client = codec->control_data; - - BUG_ON(reg > WM8961_MAX_REGISTER); - - /* Write register */ - xfer[0].addr = client->addr; - xfer[0].flags = 0; - xfer[0].len = 1; - xfer[0].buf = ® - - /* Read data */ - xfer[1].addr = client->addr; - xfer[1].flags = I2C_M_RD; - xfer[1].len = 2; - xfer[1].buf = (u8 *)&data; - - ret = i2c_transfer(client->adapter, xfer, 2); - if (ret != 2) { - dev_err(&client->dev, "i2c_transfer() returned %d\n", ret); - return 0; - } - - return (data >> 8) | ((data & 0xff) << 8); -} - -static unsigned int wm8961_read(struct snd_soc_codec *codec, unsigned int reg) -{ - if (wm8961_reg_is_volatile(reg)) - return wm8961_read_hw(codec, reg); - else - return wm8961_read_reg_cache(codec, reg); -} - -static int wm8961_write(struct snd_soc_codec *codec, unsigned int reg, - unsigned int value) -{ - u16 *cache = codec->reg_cache; - u8 data[3]; - - BUG_ON(reg > WM8961_MAX_REGISTER); - - if (!wm8961_reg_is_volatile(reg)) - cache[reg] = value; - - data[0] = reg; - data[1] = value >> 8; - data[2] = value & 0x00ff; - - if (codec->hw_write(codec->control_data, data, 3) == 3) - return 0; - else - return -EIO; -} - static int wm8961_reset(struct snd_soc_codec *codec) { - return wm8961_write(codec, WM8961_SOFTWARE_RESET, 0); + return snd_soc_write(codec, WM8961_SOFTWARE_RESET, 0); } /* @@ -384,33 +318,33 @@ static int wm8961_hp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; - u16 hp_reg = wm8961_read(codec, WM8961_ANALOGUE_HP_0); - u16 cp_reg = wm8961_read(codec, WM8961_CHARGE_PUMP_1); - u16 pwr_reg = wm8961_read(codec, WM8961_PWR_MGMT_2); - u16 dcs_reg = wm8961_read(codec, WM8961_DC_SERVO_1); + u16 hp_reg = snd_soc_read(codec, WM8961_ANALOGUE_HP_0); + u16 cp_reg = snd_soc_read(codec, WM8961_CHARGE_PUMP_1); + u16 pwr_reg = snd_soc_read(codec, WM8961_PWR_MGMT_2); + u16 dcs_reg = snd_soc_read(codec, WM8961_DC_SERVO_1); int timeout = 500; if (event & SND_SOC_DAPM_POST_PMU) { /* Make sure the output is shorted */ hp_reg &= ~(WM8961_HPR_RMV_SHORT | WM8961_HPL_RMV_SHORT); - wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); /* Enable the charge pump */ cp_reg |= WM8961_CP_ENA; - wm8961_write(codec, WM8961_CHARGE_PUMP_1, cp_reg); + snd_soc_write(codec, WM8961_CHARGE_PUMP_1, cp_reg); mdelay(5); /* Enable the PGA */ pwr_reg |= WM8961_LOUT1_PGA | WM8961_ROUT1_PGA; - wm8961_write(codec, WM8961_PWR_MGMT_2, pwr_reg); + snd_soc_write(codec, WM8961_PWR_MGMT_2, pwr_reg); /* Enable the amplifier */ hp_reg |= WM8961_HPR_ENA | WM8961_HPL_ENA; - wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); /* Second stage enable */ hp_reg |= WM8961_HPR_ENA_DLY | WM8961_HPL_ENA_DLY; - wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); /* Enable the DC servo & trigger startup */ dcs_reg |= @@ -418,10 +352,10 @@ static int wm8961_hp_event(struct snd_soc_dapm_widget *w, WM8961_DCS_ENA_CHAN_HPL | WM8961_DCS_TRIG_STARTUP_HPL; dev_dbg(codec->dev, "Enabling DC servo\n"); - wm8961_write(codec, WM8961_DC_SERVO_1, dcs_reg); + snd_soc_write(codec, WM8961_DC_SERVO_1, dcs_reg); do { msleep(1); - dcs_reg = wm8961_read(codec, WM8961_DC_SERVO_1); + dcs_reg = snd_soc_read(codec, WM8961_DC_SERVO_1); } while (--timeout && dcs_reg & (WM8961_DCS_TRIG_STARTUP_HPR | WM8961_DCS_TRIG_STARTUP_HPL)); @@ -433,39 +367,39 @@ static int wm8961_hp_event(struct snd_soc_dapm_widget *w, /* Enable the output stage */ hp_reg |= WM8961_HPR_ENA_OUTP | WM8961_HPL_ENA_OUTP; - wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); /* Remove the short on the output stage */ hp_reg |= WM8961_HPR_RMV_SHORT | WM8961_HPL_RMV_SHORT; - wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); } if (event & SND_SOC_DAPM_PRE_PMD) { /* Short the output */ hp_reg &= ~(WM8961_HPR_RMV_SHORT | WM8961_HPL_RMV_SHORT); - wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); /* Disable the output stage */ hp_reg &= ~(WM8961_HPR_ENA_OUTP | WM8961_HPL_ENA_OUTP); - wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); /* Disable DC offset cancellation */ dcs_reg &= ~(WM8961_DCS_ENA_CHAN_HPR | WM8961_DCS_ENA_CHAN_HPL); - wm8961_write(codec, WM8961_DC_SERVO_1, dcs_reg); + snd_soc_write(codec, WM8961_DC_SERVO_1, dcs_reg); /* Finish up */ hp_reg &= ~(WM8961_HPR_ENA_DLY | WM8961_HPR_ENA | WM8961_HPL_ENA_DLY | WM8961_HPL_ENA); - wm8961_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); + snd_soc_write(codec, WM8961_ANALOGUE_HP_0, hp_reg); /* Disable the PGA */ pwr_reg &= ~(WM8961_LOUT1_PGA | WM8961_ROUT1_PGA); - wm8961_write(codec, WM8961_PWR_MGMT_2, pwr_reg); + snd_soc_write(codec, WM8961_PWR_MGMT_2, pwr_reg); /* Disable the charge pump */ dev_dbg(codec->dev, "Disabling charge pump\n"); - wm8961_write(codec, WM8961_CHARGE_PUMP_1, + snd_soc_write(codec, WM8961_CHARGE_PUMP_1, cp_reg & ~WM8961_CP_ENA); } @@ -476,27 +410,27 @@ static int wm8961_spk_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; - u16 pwr_reg = wm8961_read(codec, WM8961_PWR_MGMT_2); - u16 spk_reg = wm8961_read(codec, WM8961_CLASS_D_CONTROL_1); + u16 pwr_reg = snd_soc_read(codec, WM8961_PWR_MGMT_2); + u16 spk_reg = snd_soc_read(codec, WM8961_CLASS_D_CONTROL_1); if (event & SND_SOC_DAPM_POST_PMU) { /* Enable the PGA */ pwr_reg |= WM8961_SPKL_PGA | WM8961_SPKR_PGA; - wm8961_write(codec, WM8961_PWR_MGMT_2, pwr_reg); + snd_soc_write(codec, WM8961_PWR_MGMT_2, pwr_reg); /* Enable the amplifier */ spk_reg |= WM8961_SPKL_ENA | WM8961_SPKR_ENA; - wm8961_write(codec, WM8961_CLASS_D_CONTROL_1, spk_reg); + snd_soc_write(codec, WM8961_CLASS_D_CONTROL_1, spk_reg); } if (event & SND_SOC_DAPM_PRE_PMD) { /* Enable the amplifier */ spk_reg &= ~(WM8961_SPKL_ENA | WM8961_SPKR_ENA); - wm8961_write(codec, WM8961_CLASS_D_CONTROL_1, spk_reg); + snd_soc_write(codec, WM8961_CLASS_D_CONTROL_1, spk_reg); /* Enable the PGA */ pwr_reg &= ~(WM8961_SPKL_PGA | WM8961_SPKR_PGA); - wm8961_write(codec, WM8961_PWR_MGMT_2, pwr_reg); + snd_soc_write(codec, WM8961_PWR_MGMT_2, pwr_reg); } return 0; @@ -714,10 +648,10 @@ static int wm8961_hw_params(struct snd_pcm_substream *substream, abs(wm8961_srate[best].rate - fs)) best = i; } - reg = wm8961_read(codec, WM8961_ADDITIONAL_CONTROL_3); + reg = snd_soc_read(codec, WM8961_ADDITIONAL_CONTROL_3); reg &= ~WM8961_SAMPLE_RATE_MASK; reg |= wm8961_srate[best].val; - wm8961_write(codec, WM8961_ADDITIONAL_CONTROL_3, reg); + snd_soc_write(codec, WM8961_ADDITIONAL_CONTROL_3, reg); dev_dbg(codec->dev, "Selected SRATE %dHz for %dHz\n", wm8961_srate[best].rate, fs); @@ -747,12 +681,12 @@ static int wm8961_hw_params(struct snd_pcm_substream *substream, wm8961_clk_sys_ratio[i].ratio, wm8961->sysclk, fs, wm8961->sysclk / fs); - reg = wm8961_read(codec, WM8961_CLOCKING_4); + reg = snd_soc_read(codec, WM8961_CLOCKING_4); reg &= ~WM8961_CLK_SYS_RATE_MASK; reg |= wm8961_clk_sys_ratio[i].val << WM8961_CLK_SYS_RATE_SHIFT; - wm8961_write(codec, WM8961_CLOCKING_4, reg); + snd_soc_write(codec, WM8961_CLOCKING_4, reg); - reg = wm8961_read(codec, WM8961_AUDIO_INTERFACE_0); + reg = snd_soc_read(codec, WM8961_AUDIO_INTERFACE_0); reg &= ~WM8961_WL_MASK; switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: @@ -769,15 +703,15 @@ static int wm8961_hw_params(struct snd_pcm_substream *substream, default: return -EINVAL; } - wm8961_write(codec, WM8961_AUDIO_INTERFACE_0, reg); + snd_soc_write(codec, WM8961_AUDIO_INTERFACE_0, reg); /* Sloping stop-band filter is recommended for <= 24kHz */ - reg = wm8961_read(codec, WM8961_ADC_DAC_CONTROL_2); + reg = snd_soc_read(codec, WM8961_ADC_DAC_CONTROL_2); if (fs <= 24000) reg |= WM8961_DACSLOPE; else reg &= WM8961_DACSLOPE; - wm8961_write(codec, WM8961_ADC_DAC_CONTROL_2, reg); + snd_soc_write(codec, WM8961_ADC_DAC_CONTROL_2, reg); return 0; } @@ -788,7 +722,7 @@ static int wm8961_set_sysclk(struct snd_soc_dai *dai, int clk_id, { struct snd_soc_codec *codec = dai->codec; struct wm8961_priv *wm8961 = codec->private_data; - u16 reg = wm8961_read(codec, WM8961_CLOCKING1); + u16 reg = snd_soc_read(codec, WM8961_CLOCKING1); if (freq > 33000000) { dev_err(codec->dev, "MCLK must be <33MHz\n"); @@ -804,7 +738,7 @@ static int wm8961_set_sysclk(struct snd_soc_dai *dai, int clk_id, reg &= WM8961_MCLKDIV; } - wm8961_write(codec, WM8961_CLOCKING1, reg); + snd_soc_write(codec, WM8961_CLOCKING1, reg); wm8961->sysclk = freq; @@ -814,7 +748,7 @@ static int wm8961_set_sysclk(struct snd_soc_dai *dai, int clk_id, static int wm8961_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) { struct snd_soc_codec *codec = dai->codec; - u16 aif = wm8961_read(codec, WM8961_AUDIO_INTERFACE_0); + u16 aif = snd_soc_read(codec, WM8961_AUDIO_INTERFACE_0); aif &= ~(WM8961_BCLKINV | WM8961_LRP | WM8961_MS | WM8961_FORMAT_MASK); @@ -874,26 +808,26 @@ static int wm8961_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) return -EINVAL; } - return wm8961_write(codec, WM8961_AUDIO_INTERFACE_0, aif); + return snd_soc_write(codec, WM8961_AUDIO_INTERFACE_0, aif); } static int wm8961_set_tristate(struct snd_soc_dai *dai, int tristate) { struct snd_soc_codec *codec = dai->codec; - u16 reg = wm8961_read(codec, WM8961_ADDITIONAL_CONTROL_2); + u16 reg = snd_soc_read(codec, WM8961_ADDITIONAL_CONTROL_2); if (tristate) reg |= WM8961_TRIS; else reg &= ~WM8961_TRIS; - return wm8961_write(codec, WM8961_ADDITIONAL_CONTROL_2, reg); + return snd_soc_write(codec, WM8961_ADDITIONAL_CONTROL_2, reg); } static int wm8961_digital_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; - u16 reg = wm8961_read(codec, WM8961_ADC_DAC_CONTROL_1); + u16 reg = snd_soc_read(codec, WM8961_ADC_DAC_CONTROL_1); if (mute) reg |= WM8961_DACMU; @@ -902,7 +836,7 @@ static int wm8961_digital_mute(struct snd_soc_dai *dai, int mute) msleep(17); - return wm8961_write(codec, WM8961_ADC_DAC_CONTROL_1, reg); + return snd_soc_write(codec, WM8961_ADC_DAC_CONTROL_1, reg); } static int wm8961_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div) @@ -912,17 +846,17 @@ static int wm8961_set_clkdiv(struct snd_soc_dai *dai, int div_id, int div) switch (div_id) { case WM8961_BCLK: - reg = wm8961_read(codec, WM8961_CLOCKING2); + reg = snd_soc_read(codec, WM8961_CLOCKING2); reg &= ~WM8961_BCLKDIV_MASK; reg |= div; - wm8961_write(codec, WM8961_CLOCKING2, reg); + snd_soc_write(codec, WM8961_CLOCKING2, reg); break; case WM8961_LRCLK: - reg = wm8961_read(codec, WM8961_AUDIO_INTERFACE_2); + reg = snd_soc_read(codec, WM8961_AUDIO_INTERFACE_2); reg &= ~WM8961_LRCLK_RATE_MASK; reg |= div; - wm8961_write(codec, WM8961_AUDIO_INTERFACE_2, reg); + snd_soc_write(codec, WM8961_AUDIO_INTERFACE_2, reg); break; default: @@ -949,34 +883,34 @@ static int wm8961_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_PREPARE: if (codec->bias_level == SND_SOC_BIAS_STANDBY) { /* Enable bias generation */ - reg = wm8961_read(codec, WM8961_ANTI_POP); + reg = snd_soc_read(codec, WM8961_ANTI_POP); reg |= WM8961_BUFIOEN | WM8961_BUFDCOPEN; - wm8961_write(codec, WM8961_ANTI_POP, reg); + snd_soc_write(codec, WM8961_ANTI_POP, reg); /* VMID=2*50k, VREF */ - reg = wm8961_read(codec, WM8961_PWR_MGMT_1); + reg = snd_soc_read(codec, WM8961_PWR_MGMT_1); reg &= ~WM8961_VMIDSEL_MASK; reg |= (1 << WM8961_VMIDSEL_SHIFT) | WM8961_VREF; - wm8961_write(codec, WM8961_PWR_MGMT_1, reg); + snd_soc_write(codec, WM8961_PWR_MGMT_1, reg); } break; case SND_SOC_BIAS_STANDBY: if (codec->bias_level == SND_SOC_BIAS_PREPARE) { /* VREF off */ - reg = wm8961_read(codec, WM8961_PWR_MGMT_1); + reg = snd_soc_read(codec, WM8961_PWR_MGMT_1); reg &= ~WM8961_VREF; - wm8961_write(codec, WM8961_PWR_MGMT_1, reg); + snd_soc_write(codec, WM8961_PWR_MGMT_1, reg); /* Bias generation off */ - reg = wm8961_read(codec, WM8961_ANTI_POP); + reg = snd_soc_read(codec, WM8961_ANTI_POP); reg &= ~(WM8961_BUFIOEN | WM8961_BUFDCOPEN); - wm8961_write(codec, WM8961_ANTI_POP, reg); + snd_soc_write(codec, WM8961_ANTI_POP, reg); /* VMID off */ - reg = wm8961_read(codec, WM8961_PWR_MGMT_1); + reg = snd_soc_read(codec, WM8961_PWR_MGMT_1); reg &= ~WM8961_VMIDSEL_MASK; - wm8961_write(codec, WM8961_PWR_MGMT_1, reg); + snd_soc_write(codec, WM8961_PWR_MGMT_1, reg); } break; @@ -1101,7 +1035,7 @@ static int wm8961_resume(struct platform_device *pdev) if (i == WM8961_SOFTWARE_RESET) continue; - wm8961_write(codec, i, reg_cache[i]); + snd_soc_write(codec, i, reg_cache[i]); } wm8961_set_bias_level(codec, SND_SOC_BIAS_STANDBY); @@ -1140,26 +1074,32 @@ static int wm8961_register(struct wm8961_priv *wm8961) codec->private_data = wm8961; codec->name = "WM8961"; codec->owner = THIS_MODULE; - codec->read = wm8961_read; - codec->write = wm8961_write; codec->dai = &wm8961_dai; codec->num_dai = 1; codec->reg_cache_size = ARRAY_SIZE(wm8961->reg_cache); codec->reg_cache = &wm8961->reg_cache; codec->bias_level = SND_SOC_BIAS_OFF; codec->set_bias_level = wm8961_set_bias_level; + codec->volatile_register = wm8961_volatile_register; memcpy(codec->reg_cache, wm8961_reg_defaults, sizeof(wm8961_reg_defaults)); - reg = wm8961_read_hw(codec, WM8961_SOFTWARE_RESET); + ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C); + if (ret != 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + goto err; + } + + reg = snd_soc_read(codec, WM8961_SOFTWARE_RESET); if (reg != 0x1801) { dev_err(codec->dev, "Device is not a WM8961: ID=0x%x\n", reg); ret = -EINVAL; goto err; } - reg = wm8961_read_hw(codec, WM8961_RIGHT_INPUT_VOLUME); + /* This isn't volatile - readback doesn't correspond to write */ + reg = codec->hw_read(codec, WM8961_RIGHT_INPUT_VOLUME); dev_info(codec->dev, "WM8961 family %d revision %c\n", (reg & WM8961_DEVICE_ID_MASK) >> WM8961_DEVICE_ID_SHIFT, ((reg & WM8961_CHIP_REV_MASK) >> WM8961_CHIP_REV_SHIFT) @@ -1172,37 +1112,37 @@ static int wm8961_register(struct wm8961_priv *wm8961) } /* Enable class W */ - reg = wm8961_read(codec, WM8961_CHARGE_PUMP_B); + reg = snd_soc_read(codec, WM8961_CHARGE_PUMP_B); reg |= WM8961_CP_DYN_PWR_MASK; - wm8961_write(codec, WM8961_CHARGE_PUMP_B, reg); + snd_soc_write(codec, WM8961_CHARGE_PUMP_B, reg); /* Latch volume update bits (right channel only, we always * write both out) and default ZC on. */ - reg = wm8961_read(codec, WM8961_ROUT1_VOLUME); - wm8961_write(codec, WM8961_ROUT1_VOLUME, + reg = snd_soc_read(codec, WM8961_ROUT1_VOLUME); + snd_soc_write(codec, WM8961_ROUT1_VOLUME, reg | WM8961_LO1ZC | WM8961_OUT1VU); - wm8961_write(codec, WM8961_LOUT1_VOLUME, reg | WM8961_LO1ZC); - reg = wm8961_read(codec, WM8961_ROUT2_VOLUME); - wm8961_write(codec, WM8961_ROUT2_VOLUME, + snd_soc_write(codec, WM8961_LOUT1_VOLUME, reg | WM8961_LO1ZC); + reg = snd_soc_read(codec, WM8961_ROUT2_VOLUME); + snd_soc_write(codec, WM8961_ROUT2_VOLUME, reg | WM8961_SPKRZC | WM8961_SPKVU); - wm8961_write(codec, WM8961_LOUT2_VOLUME, reg | WM8961_SPKLZC); + snd_soc_write(codec, WM8961_LOUT2_VOLUME, reg | WM8961_SPKLZC); - reg = wm8961_read(codec, WM8961_RIGHT_ADC_VOLUME); - wm8961_write(codec, WM8961_RIGHT_ADC_VOLUME, reg | WM8961_ADCVU); - reg = wm8961_read(codec, WM8961_RIGHT_INPUT_VOLUME); - wm8961_write(codec, WM8961_RIGHT_INPUT_VOLUME, reg | WM8961_IPVU); + reg = snd_soc_read(codec, WM8961_RIGHT_ADC_VOLUME); + snd_soc_write(codec, WM8961_RIGHT_ADC_VOLUME, reg | WM8961_ADCVU); + reg = snd_soc_read(codec, WM8961_RIGHT_INPUT_VOLUME); + snd_soc_write(codec, WM8961_RIGHT_INPUT_VOLUME, reg | WM8961_IPVU); /* Use soft mute by default */ - reg = wm8961_read(codec, WM8961_ADC_DAC_CONTROL_2); + reg = snd_soc_read(codec, WM8961_ADC_DAC_CONTROL_2); reg |= WM8961_DACSMM; - wm8961_write(codec, WM8961_ADC_DAC_CONTROL_2, reg); + snd_soc_write(codec, WM8961_ADC_DAC_CONTROL_2, reg); /* Use automatic clocking mode by default; for now this is all * we support. */ - reg = wm8961_read(codec, WM8961_CLOCKING_3); + reg = snd_soc_read(codec, WM8961_CLOCKING_3); reg &= ~WM8961_MANUAL_MODE; - wm8961_write(codec, WM8961_CLOCKING_3, reg); + snd_soc_write(codec, WM8961_CLOCKING_3, reg); wm8961_set_bias_level(codec, SND_SOC_BIAS_STANDBY); @@ -1250,7 +1190,6 @@ static __devinit int wm8961_i2c_probe(struct i2c_client *i2c, return -ENOMEM; codec = &wm8961->codec; - codec->hw_write = (hw_write_t)i2c_master_send; i2c_set_clientdata(i2c, wm8961); codec->control_data = i2c; diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c index d029818350e..2d702db4131 100644 --- a/sound/soc/codecs/wm8990.c +++ b/sound/soc/codecs/wm8990.c @@ -108,53 +108,7 @@ static const u16 wm8990_reg[] = { 0x0000, /* R63 - Driver internal */ }; -/* - * read wm8990 register cache - */ -static inline unsigned int wm8990_read_reg_cache(struct snd_soc_codec *codec, - unsigned int reg) -{ - u16 *cache = codec->reg_cache; - BUG_ON(reg >= ARRAY_SIZE(wm8990_reg)); - return cache[reg]; -} - -/* - * write wm8990 register cache - */ -static inline void wm8990_write_reg_cache(struct snd_soc_codec *codec, - unsigned int reg, unsigned int value) -{ - u16 *cache = codec->reg_cache; - - /* Reset register and reserved registers are uncached */ - if (reg == 0 || reg >= ARRAY_SIZE(wm8990_reg)) - return; - - cache[reg] = value; -} - -/* - * write to the wm8990 register space - */ -static int wm8990_write(struct snd_soc_codec *codec, unsigned int reg, - unsigned int value) -{ - u8 data[3]; - - data[0] = reg & 0xFF; - data[1] = (value >> 8) & 0xFF; - data[2] = value & 0xFF; - - wm8990_write_reg_cache(codec, reg, value); - - if (codec->hw_write(codec->control_data, data, 3) == 2) - return 0; - else - return -EIO; -} - -#define wm8990_reset(c) wm8990_write(c, WM8990_RESET, 0) +#define wm8990_reset(c) snd_soc_write(c, WM8990_RESET, 0) static const DECLARE_TLV_DB_LINEAR(rec_mix_tlv, -1500, 600); @@ -187,8 +141,8 @@ static int wm899x_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol, return ret; /* now hit the volume update bits (always bit 8) */ - val = wm8990_read_reg_cache(codec, reg); - return wm8990_write(codec, reg, val | 0x0100); + val = snd_soc_read(codec, reg); + return snd_soc_write(codec, reg, val | 0x0100); } #define SOC_WM899X_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert,\ @@ -427,8 +381,8 @@ static int inmixer_event(struct snd_soc_dapm_widget *w, { u16 reg, fakepower; - reg = wm8990_read_reg_cache(w->codec, WM8990_POWER_MANAGEMENT_2); - fakepower = wm8990_read_reg_cache(w->codec, WM8990_INTDRIVBITS); + reg = snd_soc_read(w->codec, WM8990_POWER_MANAGEMENT_2); + fakepower = snd_soc_read(w->codec, WM8990_INTDRIVBITS); if (fakepower & ((1 << WM8990_INMIXL_PWR_BIT) | (1 << WM8990_AINLMUX_PWR_BIT))) { @@ -443,7 +397,7 @@ static int inmixer_event(struct snd_soc_dapm_widget *w, } else { reg &= ~WM8990_AINL_ENA; } - wm8990_write(w->codec, WM8990_POWER_MANAGEMENT_2, reg); + snd_soc_write(w->codec, WM8990_POWER_MANAGEMENT_2, reg); return 0; } @@ -457,7 +411,7 @@ static int outmixer_event(struct snd_soc_dapm_widget *w, switch (reg_shift) { case WM8990_SPEAKER_MIXER | (WM8990_LDSPK_BIT << 8) : - reg = wm8990_read_reg_cache(w->codec, WM8990_OUTPUT_MIXER1); + reg = snd_soc_read(w->codec, WM8990_OUTPUT_MIXER1); if (reg & WM8990_LDLO) { printk(KERN_WARNING "Cannot set as Output Mixer 1 LDLO Set\n"); @@ -465,7 +419,7 @@ static int outmixer_event(struct snd_soc_dapm_widget *w, } break; case WM8990_SPEAKER_MIXER | (WM8990_RDSPK_BIT << 8): - reg = wm8990_read_reg_cache(w->codec, WM8990_OUTPUT_MIXER2); + reg = snd_soc_read(w->codec, WM8990_OUTPUT_MIXER2); if (reg & WM8990_RDRO) { printk(KERN_WARNING "Cannot set as Output Mixer 2 RDRO Set\n"); @@ -473,7 +427,7 @@ static int outmixer_event(struct snd_soc_dapm_widget *w, } break; case WM8990_OUTPUT_MIXER1 | (WM8990_LDLO_BIT << 8): - reg = wm8990_read_reg_cache(w->codec, WM8990_SPEAKER_MIXER); + reg = snd_soc_read(w->codec, WM8990_SPEAKER_MIXER); if (reg & WM8990_LDSPK) { printk(KERN_WARNING "Cannot set as Speaker Mixer LDSPK Set\n"); @@ -481,7 +435,7 @@ static int outmixer_event(struct snd_soc_dapm_widget *w, } break; case WM8990_OUTPUT_MIXER2 | (WM8990_RDRO_BIT << 8): - reg = wm8990_read_reg_cache(w->codec, WM8990_SPEAKER_MIXER); + reg = snd_soc_read(w->codec, WM8990_SPEAKER_MIXER); if (reg & WM8990_RDSPK) { printk(KERN_WARNING "Cannot set as Speaker Mixer RDSPK Set\n"); @@ -1029,24 +983,24 @@ static int wm8990_set_dai_pll(struct snd_soc_dai *codec_dai, pll_factors(&pll_div, freq_out * 4, freq_in); /* Turn on PLL */ - reg = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_2); + reg = snd_soc_read(codec, WM8990_POWER_MANAGEMENT_2); reg |= WM8990_PLL_ENA; - wm8990_write(codec, WM8990_POWER_MANAGEMENT_2, reg); + snd_soc_write(codec, WM8990_POWER_MANAGEMENT_2, reg); /* sysclk comes from PLL */ - reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2); - wm8990_write(codec, WM8990_CLOCKING_2, reg | WM8990_SYSCLK_SRC); + reg = snd_soc_read(codec, WM8990_CLOCKING_2); + snd_soc_write(codec, WM8990_CLOCKING_2, reg | WM8990_SYSCLK_SRC); /* set up N , fractional mode and pre-divisor if neccessary */ - wm8990_write(codec, WM8990_PLL1, pll_div.n | WM8990_SDM | + snd_soc_write(codec, WM8990_PLL1, pll_div.n | WM8990_SDM | (pll_div.div2?WM8990_PRESCALE:0)); - wm8990_write(codec, WM8990_PLL2, (u8)(pll_div.k>>8)); - wm8990_write(codec, WM8990_PLL3, (u8)(pll_div.k & 0xFF)); + snd_soc_write(codec, WM8990_PLL2, (u8)(pll_div.k>>8)); + snd_soc_write(codec, WM8990_PLL3, (u8)(pll_div.k & 0xFF)); } else { /* Turn on PLL */ - reg = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_2); + reg = snd_soc_read(codec, WM8990_POWER_MANAGEMENT_2); reg &= ~WM8990_PLL_ENA; - wm8990_write(codec, WM8990_POWER_MANAGEMENT_2, reg); + snd_soc_write(codec, WM8990_POWER_MANAGEMENT_2, reg); } return 0; } @@ -1073,8 +1027,8 @@ static int wm8990_set_dai_fmt(struct snd_soc_dai *codec_dai, struct snd_soc_codec *codec = codec_dai->codec; u16 audio1, audio3; - audio1 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_1); - audio3 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_3); + audio1 = snd_soc_read(codec, WM8990_AUDIO_INTERFACE_1); + audio3 = snd_soc_read(codec, WM8990_AUDIO_INTERFACE_3); /* set master/slave audio interface */ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { @@ -1115,8 +1069,8 @@ static int wm8990_set_dai_fmt(struct snd_soc_dai *codec_dai, return -EINVAL; } - wm8990_write(codec, WM8990_AUDIO_INTERFACE_1, audio1); - wm8990_write(codec, WM8990_AUDIO_INTERFACE_3, audio3); + snd_soc_write(codec, WM8990_AUDIO_INTERFACE_1, audio1); + snd_soc_write(codec, WM8990_AUDIO_INTERFACE_3, audio3); return 0; } @@ -1128,24 +1082,24 @@ static int wm8990_set_dai_clkdiv(struct snd_soc_dai *codec_dai, switch (div_id) { case WM8990_MCLK_DIV: - reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2) & + reg = snd_soc_read(codec, WM8990_CLOCKING_2) & ~WM8990_MCLK_DIV_MASK; - wm8990_write(codec, WM8990_CLOCKING_2, reg | div); + snd_soc_write(codec, WM8990_CLOCKING_2, reg | div); break; case WM8990_DACCLK_DIV: - reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2) & + reg = snd_soc_read(codec, WM8990_CLOCKING_2) & ~WM8990_DAC_CLKDIV_MASK; - wm8990_write(codec, WM8990_CLOCKING_2, reg | div); + snd_soc_write(codec, WM8990_CLOCKING_2, reg | div); break; case WM8990_ADCCLK_DIV: - reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_2) & + reg = snd_soc_read(codec, WM8990_CLOCKING_2) & ~WM8990_ADC_CLKDIV_MASK; - wm8990_write(codec, WM8990_CLOCKING_2, reg | div); + snd_soc_write(codec, WM8990_CLOCKING_2, reg | div); break; case WM8990_BCLK_DIV: - reg = wm8990_read_reg_cache(codec, WM8990_CLOCKING_1) & + reg = snd_soc_read(codec, WM8990_CLOCKING_1) & ~WM8990_BCLK_DIV_MASK; - wm8990_write(codec, WM8990_CLOCKING_1, reg | div); + snd_soc_write(codec, WM8990_CLOCKING_1, reg | div); break; default: return -EINVAL; @@ -1164,7 +1118,7 @@ static int wm8990_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_device *socdev = rtd->socdev; struct snd_soc_codec *codec = socdev->card->codec; - u16 audio1 = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_1); + u16 audio1 = snd_soc_read(codec, WM8990_AUDIO_INTERFACE_1); audio1 &= ~WM8990_AIF_WL_MASK; /* bit size */ @@ -1182,7 +1136,7 @@ static int wm8990_hw_params(struct snd_pcm_substream *substream, break; } - wm8990_write(codec, WM8990_AUDIO_INTERFACE_1, audio1); + snd_soc_write(codec, WM8990_AUDIO_INTERFACE_1, audio1); return 0; } @@ -1191,12 +1145,12 @@ static int wm8990_mute(struct snd_soc_dai *dai, int mute) struct snd_soc_codec *codec = dai->codec; u16 val; - val = wm8990_read_reg_cache(codec, WM8990_DAC_CTRL) & ~WM8990_DAC_MUTE; + val = snd_soc_read(codec, WM8990_DAC_CTRL) & ~WM8990_DAC_MUTE; if (mute) - wm8990_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE); + snd_soc_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE); else - wm8990_write(codec, WM8990_DAC_CTRL, val); + snd_soc_write(codec, WM8990_DAC_CTRL, val); return 0; } @@ -1212,21 +1166,21 @@ static int wm8990_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_PREPARE: /* VMID=2*50k */ - val = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_1) & + val = snd_soc_read(codec, WM8990_POWER_MANAGEMENT_1) & ~WM8990_VMID_MODE_MASK; - wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, val | 0x2); + snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, val | 0x2); break; case SND_SOC_BIAS_STANDBY: if (codec->bias_level == SND_SOC_BIAS_OFF) { /* Enable all output discharge bits */ - wm8990_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE | + snd_soc_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE | WM8990_DIS_RLINE | WM8990_DIS_OUT3 | WM8990_DIS_OUT4 | WM8990_DIS_LOUT | WM8990_DIS_ROUT); /* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */ - wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | + snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | WM8990_BUFDCOPEN | WM8990_POBCTRL | WM8990_VMIDTOG); @@ -1234,83 +1188,83 @@ static int wm8990_set_bias_level(struct snd_soc_codec *codec, msleep(msecs_to_jiffies(300)); /* Disable VMIDTOG */ - wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | + snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | WM8990_BUFDCOPEN | WM8990_POBCTRL); /* disable all output discharge bits */ - wm8990_write(codec, WM8990_ANTIPOP1, 0); + snd_soc_write(codec, WM8990_ANTIPOP1, 0); /* Enable outputs */ - wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1b00); + snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1b00); msleep(msecs_to_jiffies(50)); /* Enable VMID at 2x50k */ - wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f02); + snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f02); msleep(msecs_to_jiffies(100)); /* Enable VREF */ - wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03); + snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03); msleep(msecs_to_jiffies(600)); /* Enable BUFIOEN */ - wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | + snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | WM8990_BUFDCOPEN | WM8990_POBCTRL | WM8990_BUFIOEN); /* Disable outputs */ - wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x3); + snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x3); /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ - wm8990_write(codec, WM8990_ANTIPOP2, WM8990_BUFIOEN); + snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_BUFIOEN); /* Enable workaround for ADC clocking issue. */ - wm8990_write(codec, WM8990_EXT_ACCESS_ENA, 0x2); - wm8990_write(codec, WM8990_EXT_CTL1, 0xa003); - wm8990_write(codec, WM8990_EXT_ACCESS_ENA, 0); + snd_soc_write(codec, WM8990_EXT_ACCESS_ENA, 0x2); + snd_soc_write(codec, WM8990_EXT_CTL1, 0xa003); + snd_soc_write(codec, WM8990_EXT_ACCESS_ENA, 0); } /* VMID=2*250k */ - val = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_1) & + val = snd_soc_read(codec, WM8990_POWER_MANAGEMENT_1) & ~WM8990_VMID_MODE_MASK; - wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, val | 0x4); + snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, val | 0x4); break; case SND_SOC_BIAS_OFF: /* Enable POBCTRL and SOFT_ST */ - wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | + snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | WM8990_POBCTRL | WM8990_BUFIOEN); /* Enable POBCTRL, SOFT_ST and BUFDCOPEN */ - wm8990_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | + snd_soc_write(codec, WM8990_ANTIPOP2, WM8990_SOFTST | WM8990_BUFDCOPEN | WM8990_POBCTRL | WM8990_BUFIOEN); /* mute DAC */ - val = wm8990_read_reg_cache(codec, WM8990_DAC_CTRL); - wm8990_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE); + val = snd_soc_read(codec, WM8990_DAC_CTRL); + snd_soc_write(codec, WM8990_DAC_CTRL, val | WM8990_DAC_MUTE); /* Enable any disabled outputs */ - wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03); + snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f03); /* Disable VMID */ - wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f01); + snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x1f01); msleep(msecs_to_jiffies(300)); /* Enable all output discharge bits */ - wm8990_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE | + snd_soc_write(codec, WM8990_ANTIPOP1, WM8990_DIS_LLINE | WM8990_DIS_RLINE | WM8990_DIS_OUT3 | WM8990_DIS_OUT4 | WM8990_DIS_LOUT | WM8990_DIS_ROUT); /* Disable VREF */ - wm8990_write(codec, WM8990_POWER_MANAGEMENT_1, 0x0); + snd_soc_write(codec, WM8990_POWER_MANAGEMENT_1, 0x0); /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ - wm8990_write(codec, WM8990_ANTIPOP2, 0x0); + snd_soc_write(codec, WM8990_ANTIPOP2, 0x0); break; } @@ -1411,8 +1365,6 @@ static int wm8990_init(struct snd_soc_device *socdev) codec->name = "WM8990"; codec->owner = THIS_MODULE; - codec->read = wm8990_read_reg_cache; - codec->write = wm8990_write; codec->set_bias_level = wm8990_set_bias_level; codec->dai = &wm8990_dai; codec->num_dai = 2; @@ -1422,6 +1374,12 @@ static int wm8990_init(struct snd_soc_device *socdev) if (codec->reg_cache == NULL) return -ENOMEM; + ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C); + if (ret < 0) { + printk(KERN_ERR "wm8990: failed to set cache I/O: %d\n", ret); + goto pcm_err; + } + wm8990_reset(codec); /* register pcms */ @@ -1435,18 +1393,18 @@ static int wm8990_init(struct snd_soc_device *socdev) codec->bias_level = SND_SOC_BIAS_OFF; wm8990_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - reg = wm8990_read_reg_cache(codec, WM8990_AUDIO_INTERFACE_4); - wm8990_write(codec, WM8990_AUDIO_INTERFACE_4, reg | WM8990_ALRCGPIO1); + reg = snd_soc_read(codec, WM8990_AUDIO_INTERFACE_4); + snd_soc_write(codec, WM8990_AUDIO_INTERFACE_4, reg | WM8990_ALRCGPIO1); - reg = wm8990_read_reg_cache(codec, WM8990_GPIO1_GPIO2) & + reg = snd_soc_read(codec, WM8990_GPIO1_GPIO2) & ~WM8990_GPIO1_SEL_MASK; - wm8990_write(codec, WM8990_GPIO1_GPIO2, reg | 1); + snd_soc_write(codec, WM8990_GPIO1_GPIO2, reg | 1); - reg = wm8990_read_reg_cache(codec, WM8990_POWER_MANAGEMENT_2); - wm8990_write(codec, WM8990_POWER_MANAGEMENT_2, reg | WM8990_OPCLK_ENA); + reg = snd_soc_read(codec, WM8990_POWER_MANAGEMENT_2); + snd_soc_write(codec, WM8990_POWER_MANAGEMENT_2, reg | WM8990_OPCLK_ENA); - wm8990_write(codec, WM8990_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8)); - wm8990_write(codec, WM8990_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8)); + snd_soc_write(codec, WM8990_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8)); + snd_soc_write(codec, WM8990_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8)); snd_soc_add_controls(codec, wm8990_snd_controls, ARRAY_SIZE(wm8990_snd_controls)); diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c index dbe20597d87..dc383c29cce 100644 --- a/sound/soc/codecs/wm9081.c +++ b/sound/soc/codecs/wm9081.c @@ -168,84 +168,19 @@ struct wm9081_priv { struct wm9081_retune_mobile_config *retune; }; -static int wm9081_reg_is_volatile(int reg) +static int wm9081_volatile_register(unsigned int reg) { switch (reg) { + case WM9081_SOFTWARE_RESET: + return 1; default: return 0; } } -static unsigned int wm9081_read_reg_cache(struct snd_soc_codec *codec, - unsigned int reg) -{ - u16 *cache = codec->reg_cache; - BUG_ON(reg > WM9081_MAX_REGISTER); - return cache[reg]; -} - -static unsigned int wm9081_read_hw(struct snd_soc_codec *codec, u8 reg) -{ - struct i2c_msg xfer[2]; - u16 data; - int ret; - struct i2c_client *client = codec->control_data; - - BUG_ON(reg > WM9081_MAX_REGISTER); - - /* Write register */ - xfer[0].addr = client->addr; - xfer[0].flags = 0; - xfer[0].len = 1; - xfer[0].buf = ® - - /* Read data */ - xfer[1].addr = client->addr; - xfer[1].flags = I2C_M_RD; - xfer[1].len = 2; - xfer[1].buf = (u8 *)&data; - - ret = i2c_transfer(client->adapter, xfer, 2); - if (ret != 2) { - dev_err(&client->dev, "i2c_transfer() returned %d\n", ret); - return 0; - } - - return (data >> 8) | ((data & 0xff) << 8); -} - -static unsigned int wm9081_read(struct snd_soc_codec *codec, unsigned int reg) -{ - if (wm9081_reg_is_volatile(reg)) - return wm9081_read_hw(codec, reg); - else - return wm9081_read_reg_cache(codec, reg); -} - -static int wm9081_write(struct snd_soc_codec *codec, unsigned int reg, - unsigned int value) -{ - u16 *cache = codec->reg_cache; - u8 data[3]; - - BUG_ON(reg > WM9081_MAX_REGISTER); - - if (!wm9081_reg_is_volatile(reg)) - cache[reg] = value; - - data[0] = reg; - data[1] = value >> 8; - data[2] = value & 0x00ff; - - if (codec->hw_write(codec->control_data, data, 3) == 3) - return 0; - else - return -EIO; -} - static int wm9081_reset(struct snd_soc_codec *codec) { - return wm9081_write(codec, WM9081_SOFTWARE_RESET, 0); + return snd_soc_write(codec, WM9081_SOFTWARE_RESET, 0); } static const DECLARE_TLV_DB_SCALE(drc_in_tlv, -4500, 75, 0); @@ -356,7 +291,7 @@ static int speaker_mode_get(struct snd_kcontrol *kcontrol, struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); unsigned int reg; - reg = wm9081_read(codec, WM9081_ANALOGUE_SPEAKER_2); + reg = snd_soc_read(codec, WM9081_ANALOGUE_SPEAKER_2); if (reg & WM9081_SPK_MODE) ucontrol->value.integer.value[0] = 1; else @@ -375,8 +310,8 @@ static int speaker_mode_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - unsigned int reg_pwr = wm9081_read(codec, WM9081_POWER_MANAGEMENT); - unsigned int reg2 = wm9081_read(codec, WM9081_ANALOGUE_SPEAKER_2); + unsigned int reg_pwr = snd_soc_read(codec, WM9081_POWER_MANAGEMENT); + unsigned int reg2 = snd_soc_read(codec, WM9081_ANALOGUE_SPEAKER_2); /* Are we changing anything? */ if (ucontrol->value.integer.value[0] == @@ -397,7 +332,7 @@ static int speaker_mode_put(struct snd_kcontrol *kcontrol, reg2 &= ~WM9081_SPK_MODE; } - wm9081_write(codec, WM9081_ANALOGUE_SPEAKER_2, reg2); + snd_soc_write(codec, WM9081_ANALOGUE_SPEAKER_2, reg2); return 0; } @@ -456,7 +391,7 @@ static int speaker_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; - unsigned int reg = wm9081_read(codec, WM9081_POWER_MANAGEMENT); + unsigned int reg = snd_soc_read(codec, WM9081_POWER_MANAGEMENT); switch (event) { case SND_SOC_DAPM_POST_PMU: @@ -468,7 +403,7 @@ static int speaker_event(struct snd_soc_dapm_widget *w, break; } - wm9081_write(codec, WM9081_POWER_MANAGEMENT, reg); + snd_soc_write(codec, WM9081_POWER_MANAGEMENT, reg); return 0; } @@ -607,7 +542,7 @@ static int wm9081_set_fll(struct snd_soc_codec *codec, int fll_id, if (ret != 0) return ret; - reg5 = wm9081_read(codec, WM9081_FLL_CONTROL_5); + reg5 = snd_soc_read(codec, WM9081_FLL_CONTROL_5); reg5 &= ~WM9081_FLL_CLK_SRC_MASK; switch (fll_id) { @@ -621,44 +556,44 @@ static int wm9081_set_fll(struct snd_soc_codec *codec, int fll_id, } /* Disable CLK_SYS while we reconfigure */ - clk_sys_reg = wm9081_read(codec, WM9081_CLOCK_CONTROL_3); + clk_sys_reg = snd_soc_read(codec, WM9081_CLOCK_CONTROL_3); if (clk_sys_reg & WM9081_CLK_SYS_ENA) - wm9081_write(codec, WM9081_CLOCK_CONTROL_3, + snd_soc_write(codec, WM9081_CLOCK_CONTROL_3, clk_sys_reg & ~WM9081_CLK_SYS_ENA); /* Any FLL configuration change requires that the FLL be * disabled first. */ - reg1 = wm9081_read(codec, WM9081_FLL_CONTROL_1); + reg1 = snd_soc_read(codec, WM9081_FLL_CONTROL_1); reg1 &= ~WM9081_FLL_ENA; - wm9081_write(codec, WM9081_FLL_CONTROL_1, reg1); + snd_soc_write(codec, WM9081_FLL_CONTROL_1, reg1); /* Apply the configuration */ if (fll_div.k) reg1 |= WM9081_FLL_FRAC_MASK; else reg1 &= ~WM9081_FLL_FRAC_MASK; - wm9081_write(codec, WM9081_FLL_CONTROL_1, reg1); + snd_soc_write(codec, WM9081_FLL_CONTROL_1, reg1); - wm9081_write(codec, WM9081_FLL_CONTROL_2, + snd_soc_write(codec, WM9081_FLL_CONTROL_2, (fll_div.fll_outdiv << WM9081_FLL_OUTDIV_SHIFT) | (fll_div.fll_fratio << WM9081_FLL_FRATIO_SHIFT)); - wm9081_write(codec, WM9081_FLL_CONTROL_3, fll_div.k); + snd_soc_write(codec, WM9081_FLL_CONTROL_3, fll_div.k); - reg4 = wm9081_read(codec, WM9081_FLL_CONTROL_4); + reg4 = snd_soc_read(codec, WM9081_FLL_CONTROL_4); reg4 &= ~WM9081_FLL_N_MASK; reg4 |= fll_div.n << WM9081_FLL_N_SHIFT; - wm9081_write(codec, WM9081_FLL_CONTROL_4, reg4); + snd_soc_write(codec, WM9081_FLL_CONTROL_4, reg4); reg5 &= ~WM9081_FLL_CLK_REF_DIV_MASK; reg5 |= fll_div.fll_clk_ref_div << WM9081_FLL_CLK_REF_DIV_SHIFT; - wm9081_write(codec, WM9081_FLL_CONTROL_5, reg5); + snd_soc_write(codec, WM9081_FLL_CONTROL_5, reg5); /* Enable the FLL */ - wm9081_write(codec, WM9081_FLL_CONTROL_1, reg1 | WM9081_FLL_ENA); + snd_soc_write(codec, WM9081_FLL_CONTROL_1, reg1 | WM9081_FLL_ENA); /* Then bring CLK_SYS up again if it was disabled */ if (clk_sys_reg & WM9081_CLK_SYS_ENA) - wm9081_write(codec, WM9081_CLOCK_CONTROL_3, clk_sys_reg); + snd_soc_write(codec, WM9081_CLOCK_CONTROL_3, clk_sys_reg); dev_dbg(codec->dev, "FLL enabled at %dHz->%dHz\n", Fref, Fout); @@ -734,19 +669,19 @@ static int configure_clock(struct snd_soc_codec *codec) return -EINVAL; } - reg = wm9081_read(codec, WM9081_CLOCK_CONTROL_1); + reg = snd_soc_read(codec, WM9081_CLOCK_CONTROL_1); if (mclkdiv) reg |= WM9081_MCLKDIV2; else reg &= ~WM9081_MCLKDIV2; - wm9081_write(codec, WM9081_CLOCK_CONTROL_1, reg); + snd_soc_write(codec, WM9081_CLOCK_CONTROL_1, reg); - reg = wm9081_read(codec, WM9081_CLOCK_CONTROL_3); + reg = snd_soc_read(codec, WM9081_CLOCK_CONTROL_3); if (fll) reg |= WM9081_CLK_SRC_SEL; else reg &= ~WM9081_CLK_SRC_SEL; - wm9081_write(codec, WM9081_CLOCK_CONTROL_3, reg); + snd_soc_write(codec, WM9081_CLOCK_CONTROL_3, reg); dev_dbg(codec->dev, "CLK_SYS is %dHz\n", wm9081->sysclk_rate); @@ -846,76 +781,76 @@ static int wm9081_set_bias_level(struct snd_soc_codec *codec, case SND_SOC_BIAS_PREPARE: /* VMID=2*40k */ - reg = wm9081_read(codec, WM9081_VMID_CONTROL); + reg = snd_soc_read(codec, WM9081_VMID_CONTROL); reg &= ~WM9081_VMID_SEL_MASK; reg |= 0x2; - wm9081_write(codec, WM9081_VMID_CONTROL, reg); + snd_soc_write(codec, WM9081_VMID_CONTROL, reg); /* Normal bias current */ - reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1); + reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1); reg &= ~WM9081_STBY_BIAS_ENA; - wm9081_write(codec, WM9081_BIAS_CONTROL_1, reg); + snd_soc_write(codec, WM9081_BIAS_CONTROL_1, reg); break; case SND_SOC_BIAS_STANDBY: /* Initial cold start */ if (codec->bias_level == SND_SOC_BIAS_OFF) { /* Disable LINEOUT discharge */ - reg = wm9081_read(codec, WM9081_ANTI_POP_CONTROL); + reg = snd_soc_read(codec, WM9081_ANTI_POP_CONTROL); reg &= ~WM9081_LINEOUT_DISCH; - wm9081_write(codec, WM9081_ANTI_POP_CONTROL, reg); + snd_soc_write(codec, WM9081_ANTI_POP_CONTROL, reg); /* Select startup bias source */ - reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1); + reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1); reg |= WM9081_BIAS_SRC | WM9081_BIAS_ENA; - wm9081_write(codec, WM9081_BIAS_CONTROL_1, reg); + snd_soc_write(codec, WM9081_BIAS_CONTROL_1, reg); /* VMID 2*4k; Soft VMID ramp enable */ - reg = wm9081_read(codec, WM9081_VMID_CONTROL); + reg = snd_soc_read(codec, WM9081_VMID_CONTROL); reg |= WM9081_VMID_RAMP | 0x6; - wm9081_write(codec, WM9081_VMID_CONTROL, reg); + snd_soc_write(codec, WM9081_VMID_CONTROL, reg); mdelay(100); /* Normal bias enable & soft start off */ reg |= WM9081_BIAS_ENA; reg &= ~WM9081_VMID_RAMP; - wm9081_write(codec, WM9081_VMID_CONTROL, reg); + snd_soc_write(codec, WM9081_VMID_CONTROL, reg); /* Standard bias source */ - reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1); + reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1); reg &= ~WM9081_BIAS_SRC; - wm9081_write(codec, WM9081_BIAS_CONTROL_1, reg); + snd_soc_write(codec, WM9081_BIAS_CONTROL_1, reg); } /* VMID 2*240k */ - reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1); + reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1); reg &= ~WM9081_VMID_SEL_MASK; reg |= 0x40; - wm9081_write(codec, WM9081_VMID_CONTROL, reg); + snd_soc_write(codec, WM9081_VMID_CONTROL, reg); /* Standby bias current on */ - reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1); + reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1); reg |= WM9081_STBY_BIAS_ENA; - wm9081_write(codec, WM9081_BIAS_CONTROL_1, reg); + snd_soc_write(codec, WM9081_BIAS_CONTROL_1, reg); break; case SND_SOC_BIAS_OFF: /* Startup bias source */ - reg = wm9081_read(codec, WM9081_BIAS_CONTROL_1); + reg = snd_soc_read(codec, WM9081_BIAS_CONTROL_1); reg |= WM9081_BIAS_SRC; - wm9081_write(codec, WM9081_BIAS_CONTROL_1, reg); + snd_soc_write(codec, WM9081_BIAS_CONTROL_1, reg); /* Disable VMID and biases with soft ramping */ - reg = wm9081_read(codec, WM9081_VMID_CONTROL); + reg = snd_soc_read(codec, WM9081_VMID_CONTROL); reg &= ~(WM9081_VMID_SEL_MASK | WM9081_BIAS_ENA); reg |= WM9081_VMID_RAMP; - wm9081_write(codec, WM9081_VMID_CONTROL, reg); + snd_soc_write(codec, WM9081_VMID_CONTROL, reg); /* Actively discharge LINEOUT */ - reg = wm9081_read(codec, WM9081_ANTI_POP_CONTROL); + reg = snd_soc_read(codec, WM9081_ANTI_POP_CONTROL); reg |= WM9081_LINEOUT_DISCH; - wm9081_write(codec, WM9081_ANTI_POP_CONTROL, reg); + snd_soc_write(codec, WM9081_ANTI_POP_CONTROL, reg); break; } @@ -929,7 +864,7 @@ static int wm9081_set_dai_fmt(struct snd_soc_dai *dai, { struct snd_soc_codec *codec = dai->codec; struct wm9081_priv *wm9081 = codec->private_data; - unsigned int aif2 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_2); + unsigned int aif2 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_2); aif2 &= ~(WM9081_AIF_BCLK_INV | WM9081_AIF_LRCLK_INV | WM9081_BCLK_DIR | WM9081_LRCLK_DIR | WM9081_AIF_FMT_MASK); @@ -1010,7 +945,7 @@ static int wm9081_set_dai_fmt(struct snd_soc_dai *dai, return -EINVAL; } - wm9081_write(codec, WM9081_AUDIO_INTERFACE_2, aif2); + snd_soc_write(codec, WM9081_AUDIO_INTERFACE_2, aif2); return 0; } @@ -1024,18 +959,18 @@ static int wm9081_hw_params(struct snd_pcm_substream *substream, int ret, i, best, best_val, cur_val; unsigned int clk_ctrl2, aif1, aif2, aif3, aif4; - clk_ctrl2 = wm9081_read(codec, WM9081_CLOCK_CONTROL_2); + clk_ctrl2 = snd_soc_read(codec, WM9081_CLOCK_CONTROL_2); clk_ctrl2 &= ~(WM9081_CLK_SYS_RATE_MASK | WM9081_SAMPLE_RATE_MASK); - aif1 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_1); + aif1 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_1); - aif2 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_2); + aif2 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_2); aif2 &= ~WM9081_AIF_WL_MASK; - aif3 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_3); + aif3 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_3); aif3 &= ~WM9081_BCLK_DIV_MASK; - aif4 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_4); + aif4 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_4); aif4 &= ~WM9081_LRCLK_RATE_MASK; /* What BCLK do we need? */ @@ -1149,22 +1084,22 @@ static int wm9081_hw_params(struct snd_pcm_substream *substream, s->name, s->rate); /* If the EQ is enabled then disable it while we write out */ - eq1 = wm9081_read(codec, WM9081_EQ_1) & WM9081_EQ_ENA; + eq1 = snd_soc_read(codec, WM9081_EQ_1) & WM9081_EQ_ENA; if (eq1 & WM9081_EQ_ENA) - wm9081_write(codec, WM9081_EQ_1, 0); + snd_soc_write(codec, WM9081_EQ_1, 0); /* Write out the other values */ for (i = 1; i < ARRAY_SIZE(s->config); i++) - wm9081_write(codec, WM9081_EQ_1 + i, s->config[i]); + snd_soc_write(codec, WM9081_EQ_1 + i, s->config[i]); eq1 |= (s->config[0] & ~WM9081_EQ_ENA); - wm9081_write(codec, WM9081_EQ_1, eq1); + snd_soc_write(codec, WM9081_EQ_1, eq1); } - wm9081_write(codec, WM9081_CLOCK_CONTROL_2, clk_ctrl2); - wm9081_write(codec, WM9081_AUDIO_INTERFACE_2, aif2); - wm9081_write(codec, WM9081_AUDIO_INTERFACE_3, aif3); - wm9081_write(codec, WM9081_AUDIO_INTERFACE_4, aif4); + snd_soc_write(codec, WM9081_CLOCK_CONTROL_2, clk_ctrl2); + snd_soc_write(codec, WM9081_AUDIO_INTERFACE_2, aif2); + snd_soc_write(codec, WM9081_AUDIO_INTERFACE_3, aif3); + snd_soc_write(codec, WM9081_AUDIO_INTERFACE_4, aif4); return 0; } @@ -1174,14 +1109,14 @@ static int wm9081_digital_mute(struct snd_soc_dai *codec_dai, int mute) struct snd_soc_codec *codec = codec_dai->codec; unsigned int reg; - reg = wm9081_read(codec, WM9081_DAC_DIGITAL_2); + reg = snd_soc_read(codec, WM9081_DAC_DIGITAL_2); if (mute) reg |= WM9081_DAC_MUTE; else reg &= ~WM9081_DAC_MUTE; - wm9081_write(codec, WM9081_DAC_DIGITAL_2, reg); + snd_soc_write(codec, WM9081_DAC_DIGITAL_2, reg); return 0; } @@ -1210,7 +1145,7 @@ static int wm9081_set_tdm_slot(struct snd_soc_dai *dai, unsigned int mask, int slots) { struct snd_soc_codec *codec = dai->codec; - unsigned int aif1 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_1); + unsigned int aif1 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_1); aif1 &= ~(WM9081_AIFDAC_TDM_SLOT_MASK | WM9081_AIFDAC_TDM_MODE_MASK); @@ -1235,7 +1170,7 @@ static int wm9081_set_tdm_slot(struct snd_soc_dai *dai, return -EINVAL; } - wm9081_write(codec, WM9081_AUDIO_INTERFACE_1, aif1); + snd_soc_write(codec, WM9081_AUDIO_INTERFACE_1, aif1); return 0; } @@ -1357,7 +1292,7 @@ static int wm9081_resume(struct platform_device *pdev) if (i == WM9081_SOFTWARE_RESET) continue; - wm9081_write(codec, i, reg_cache[i]); + snd_soc_write(codec, i, reg_cache[i]); } wm9081_set_bias_level(codec, SND_SOC_BIAS_STANDBY); @@ -1377,7 +1312,8 @@ struct snd_soc_codec_device soc_codec_dev_wm9081 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_wm9081); -static int wm9081_register(struct wm9081_priv *wm9081) +static int wm9081_register(struct wm9081_priv *wm9081, + enum snd_soc_control_type control) { struct snd_soc_codec *codec = &wm9081->codec; int ret; @@ -1396,19 +1332,24 @@ static int wm9081_register(struct wm9081_priv *wm9081) codec->private_data = wm9081; codec->name = "WM9081"; codec->owner = THIS_MODULE; - codec->read = wm9081_read; - codec->write = wm9081_write; codec->dai = &wm9081_dai; codec->num_dai = 1; codec->reg_cache_size = ARRAY_SIZE(wm9081->reg_cache); codec->reg_cache = &wm9081->reg_cache; codec->bias_level = SND_SOC_BIAS_OFF; codec->set_bias_level = wm9081_set_bias_level; + codec->volatile_register = wm9081_volatile_register; memcpy(codec->reg_cache, wm9081_reg_defaults, sizeof(wm9081_reg_defaults)); - reg = wm9081_read_hw(codec, WM9081_SOFTWARE_RESET); + ret = snd_soc_codec_set_cache_io(codec, 8, 16, control); + if (ret != 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + return ret; + } + + reg = snd_soc_read(codec, WM9081_SOFTWARE_RESET); if (reg != 0x9081) { dev_err(codec->dev, "Device is not a WM9081: ID=0x%x\n", reg); ret = -EINVAL; @@ -1424,10 +1365,10 @@ static int wm9081_register(struct wm9081_priv *wm9081) wm9081_set_bias_level(codec, SND_SOC_BIAS_STANDBY); /* Enable zero cross by default */ - reg = wm9081_read(codec, WM9081_ANALOGUE_LINEOUT); - wm9081_write(codec, WM9081_ANALOGUE_LINEOUT, reg | WM9081_LINEOUTZC); - reg = wm9081_read(codec, WM9081_ANALOGUE_SPEAKER_PGA); - wm9081_write(codec, WM9081_ANALOGUE_SPEAKER_PGA, + reg = snd_soc_read(codec, WM9081_ANALOGUE_LINEOUT); + snd_soc_write(codec, WM9081_ANALOGUE_LINEOUT, reg | WM9081_LINEOUTZC); + reg = snd_soc_read(codec, WM9081_ANALOGUE_SPEAKER_PGA); + snd_soc_write(codec, WM9081_ANALOGUE_SPEAKER_PGA, reg | WM9081_SPKPGAZC); wm9081_dai.dev = codec->dev; @@ -1482,7 +1423,7 @@ static __devinit int wm9081_i2c_probe(struct i2c_client *i2c, codec->dev = &i2c->dev; - return wm9081_register(wm9081); + return wm9081_register(wm9081, SND_SOC_I2C); } static __devexit int wm9081_i2c_remove(struct i2c_client *client) -- cgit v1.2.3 From 27ded041f03026e8c6be9efc626e11ddfb4620c1 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Fri, 10 Jul 2009 23:28:16 +0100 Subject: ASoC: Factor out 7 bit register 9 bit data SPI write This converts all the Wolfson drivers using this format (the only devices that do) except WM8753 to use it. Signed-off-by: Mark Brown --- sound/soc/codecs/wm8510.c | 25 ------------------------- sound/soc/codecs/wm8728.c | 25 ------------------------- sound/soc/codecs/wm8731.c | 25 ------------------------- sound/soc/codecs/wm8750.c | 25 ------------------------- sound/soc/codecs/wm8988.c | 25 ------------------------- sound/soc/soc-cache.c | 39 ++++++++++++++++++++++++++++++++++++--- 6 files changed, 36 insertions(+), 128 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c index 4ca724ff4c6..bcd6a40171d 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -737,30 +737,6 @@ static struct spi_driver wm8510_spi_driver = { .probe = wm8510_spi_probe, .remove = __devexit_p(wm8510_spi_remove), }; - -static int wm8510_spi_write(struct spi_device *spi, const char *data, int len) -{ - struct spi_transfer t; - struct spi_message m; - u8 msg[2]; - - if (len <= 0) - return 0; - - msg[0] = data[0]; - msg[1] = data[1]; - - spi_message_init(&m); - memset(&t, 0, (sizeof t)); - - t.tx_buf = &msg[0]; - t.len = len; - - spi_message_add_tail(&t, &m); - spi_sync(spi, &m); - - return len; -} #endif /* CONFIG_SPI_MASTER */ static int wm8510_probe(struct platform_device *pdev) @@ -790,7 +766,6 @@ static int wm8510_probe(struct platform_device *pdev) #endif #if defined(CONFIG_SPI_MASTER) if (setup->spi) { - codec->hw_write = (hw_write_t)wm8510_spi_write; ret = spi_register_driver(&wm8510_spi_driver); if (ret != 0) printk(KERN_ERR "can't add spi driver"); diff --git a/sound/soc/codecs/wm8728.c b/sound/soc/codecs/wm8728.c index a28630de9c9..16e969a762c 100644 --- a/sound/soc/codecs/wm8728.c +++ b/sound/soc/codecs/wm8728.c @@ -425,30 +425,6 @@ static struct spi_driver wm8728_spi_driver = { .probe = wm8728_spi_probe, .remove = __devexit_p(wm8728_spi_remove), }; - -static int wm8728_spi_write(struct spi_device *spi, const char *data, int len) -{ - struct spi_transfer t; - struct spi_message m; - u8 msg[2]; - - if (len <= 0) - return 0; - - msg[0] = data[0]; - msg[1] = data[1]; - - spi_message_init(&m); - memset(&t, 0, (sizeof t)); - - t.tx_buf = &msg[0]; - t.len = len; - - spi_message_add_tail(&t, &m); - spi_sync(spi, &m); - - return len; -} #endif /* CONFIG_SPI_MASTER */ static int wm8728_probe(struct platform_device *pdev) @@ -478,7 +454,6 @@ static int wm8728_probe(struct platform_device *pdev) #endif #if defined(CONFIG_SPI_MASTER) if (setup->spi) { - codec->hw_write = (hw_write_t)wm8728_spi_write; ret = spi_register_driver(&wm8728_spi_driver); if (ret != 0) printk(KERN_ERR "can't add spi driver"); diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index 27fc942a5ce..6232afd907e 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -591,30 +591,6 @@ static void wm8731_unregister(struct wm8731_priv *wm8731) } #if defined(CONFIG_SPI_MASTER) -static int wm8731_spi_write(struct spi_device *spi, const char *data, int len) -{ - struct spi_transfer t; - struct spi_message m; - u8 msg[2]; - - if (len <= 0) - return 0; - - msg[0] = data[0]; - msg[1] = data[1]; - - spi_message_init(&m); - memset(&t, 0, (sizeof t)); - - t.tx_buf = &msg[0]; - t.len = len; - - spi_message_add_tail(&t, &m); - spi_sync(spi, &m); - - return len; -} - static int __devinit wm8731_spi_probe(struct spi_device *spi) { struct snd_soc_codec *codec; @@ -626,7 +602,6 @@ static int __devinit wm8731_spi_probe(struct spi_device *spi) codec = &wm8731->codec; codec->control_data = spi; - codec->hw_write = (hw_write_t)wm8731_spi_write; codec->dev = &spi->dev; dev_set_drvdata(&spi->dev, wm8731); diff --git a/sound/soc/codecs/wm8750.c b/sound/soc/codecs/wm8750.c index e6422b16167..4ba1e7e93fb 100644 --- a/sound/soc/codecs/wm8750.c +++ b/sound/soc/codecs/wm8750.c @@ -911,30 +911,6 @@ static struct spi_driver wm8750_spi_driver = { .probe = wm8750_spi_probe, .remove = __devexit_p(wm8750_spi_remove), }; - -static int wm8750_spi_write(struct spi_device *spi, const char *data, int len) -{ - struct spi_transfer t; - struct spi_message m; - u8 msg[2]; - - if (len <= 0) - return 0; - - msg[0] = data[0]; - msg[1] = data[1]; - - spi_message_init(&m); - memset(&t, 0, (sizeof t)); - - t.tx_buf = &msg[0]; - t.len = len; - - spi_message_add_tail(&t, &m); - spi_sync(spi, &m); - - return len; -} #endif static int wm8750_probe(struct platform_device *pdev) @@ -973,7 +949,6 @@ static int wm8750_probe(struct platform_device *pdev) #endif #if defined(CONFIG_SPI_MASTER) if (setup->spi) { - codec->hw_write = (hw_write_t)wm8750_spi_write; ret = spi_register_driver(&wm8750_spi_driver); if (ret != 0) printk(KERN_ERR "can't add spi driver"); diff --git a/sound/soc/codecs/wm8988.c b/sound/soc/codecs/wm8988.c index 7486d3ec787..1c8653523c8 100644 --- a/sound/soc/codecs/wm8988.c +++ b/sound/soc/codecs/wm8988.c @@ -979,30 +979,6 @@ static struct i2c_driver wm8988_i2c_driver = { #endif #if defined(CONFIG_SPI_MASTER) -static int wm8988_spi_write(struct spi_device *spi, const char *data, int len) -{ - struct spi_transfer t; - struct spi_message m; - u8 msg[2]; - - if (len <= 0) - return 0; - - msg[0] = data[0]; - msg[1] = data[1]; - - spi_message_init(&m); - memset(&t, 0, (sizeof t)); - - t.tx_buf = &msg[0]; - t.len = len; - - spi_message_add_tail(&t, &m); - spi_sync(spi, &m); - - return len; -} - static int __devinit wm8988_spi_probe(struct spi_device *spi) { struct wm8988_priv *wm8988; @@ -1013,7 +989,6 @@ static int __devinit wm8988_spi_probe(struct spi_device *spi) return -ENOMEM; codec = &wm8988->codec; - codec->hw_write = (hw_write_t)wm8988_spi_write; codec->control_data = spi; codec->dev = &spi->dev; diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c index ab058b81163..56ec3f22bf1 100644 --- a/sound/soc/soc-cache.c +++ b/sound/soc/soc-cache.c @@ -12,6 +12,7 @@ */ #include +#include #include static unsigned int snd_soc_7_9_read(struct snd_soc_codec *codec, @@ -46,6 +47,36 @@ static int snd_soc_7_9_write(struct snd_soc_codec *codec, unsigned int reg, return -EIO; } +#if defined(CONFIG_SPI_MASTER) +static int snd_soc_7_9_spi_write(void *control_data, const char *data, + int len) +{ + struct spi_device *spi = control_data; + struct spi_transfer t; + struct spi_message m; + u8 msg[2]; + + if (len <= 0) + return 0; + + msg[0] = data[0]; + msg[1] = data[1]; + + spi_message_init(&m); + memset(&t, 0, (sizeof t)); + + t.tx_buf = &msg[0]; + t.len = len; + + spi_message_add_tail(&t, &m); + spi_sync(spi, &m); + + return len; +} +#else +#define snd_soc_7_9_spi_write NULL +#endif + static int snd_soc_8_16_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { @@ -115,12 +146,12 @@ static struct { int addr_bits; int data_bits; int (*write)(struct snd_soc_codec *codec, unsigned int, unsigned int); + int (*spi_write)(void *, const char *, int); unsigned int (*read)(struct snd_soc_codec *, unsigned int); unsigned int (*i2c_read)(struct snd_soc_codec *, unsigned int); } io_types[] = { - { 7, 9, snd_soc_7_9_write, snd_soc_7_9_read }, - { 8, 16, - snd_soc_8_16_write, snd_soc_8_16_read, + { 7, 9, snd_soc_7_9_write, snd_soc_7_9_spi_write, snd_soc_7_9_read }, + { 8, 16, snd_soc_8_16_write, NULL, snd_soc_8_16_read, snd_soc_8_16_read_i2c }, }; @@ -177,6 +208,8 @@ int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec, break; case SND_SOC_SPI: + if (io_types[i].spi_write) + codec->hw_write = io_types[i].spi_write; break; } -- cgit v1.2.3 From 4bc4c9a5f5206d795e9ee1af8b0b074d126354af Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 4 Aug 2009 11:38:42 +0100 Subject: ASoC: Existing S3C24xx AC97 drivers should depend on S3C24xx Signed-off-by: Mark Brown --- sound/soc/s3c24xx/Kconfig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index d1ed0f537c3..808de5c5caa 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig @@ -66,7 +66,7 @@ config SND_S3C24XX_SOC_SMDK2443_WM9710 config SND_S3C24XX_SOC_LN2440SBC_ALC650 tristate "SoC AC97 Audio support for LN2440SBC - ALC650" - depends on SND_S3C24XX_SOC + depends on SND_S3C24XX_SOC && ARCH_S3C2410 select SND_S3C2443_SOC_AC97 select SND_SOC_AC97_CODEC help @@ -75,7 +75,7 @@ config SND_S3C24XX_SOC_LN2440SBC_ALC650 config SND_S3C24XX_SOC_S3C24XX_UDA134X tristate "SoC I2S Audio support UDA134X wired to a S3C24XX" - depends on SND_S3C24XX_SOC + depends on SND_S3C24XX_SOC && ARCH_S3C2410 select SND_S3C24XX_SOC_I2S select SND_SOC_L3 select SND_SOC_UDA134X -- cgit v1.2.3 From b0a2712ffdde139f95d3e78c359877b23709509a Mon Sep 17 00:00:00 2001 From: John Bonesio Date: Wed, 29 Jul 2009 08:38:55 -0700 Subject: ASoC: MPC5200: Support for buffer wrap around The code in psc_dma_bcom_enqueue_tx() didn't account for the fact that s->runtime->control->appl_ptr can wrap around to the beginning of the buffer. This change fixes this problem. Signed-off-by: John Bonesio Acked-by: Grant Likely Signed-off-by: Mark Brown --- sound/soc/fsl/mpc5200_dma.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/fsl/mpc5200_dma.c b/sound/soc/fsl/mpc5200_dma.c index f0a2d407199..9ff62e3a9b1 100644 --- a/sound/soc/fsl/mpc5200_dma.c +++ b/sound/soc/fsl/mpc5200_dma.c @@ -69,6 +69,23 @@ static void psc_dma_bcom_enqueue_next_buffer(struct psc_dma_stream *s) static void psc_dma_bcom_enqueue_tx(struct psc_dma_stream *s) { + if (s->appl_ptr > s->runtime->control->appl_ptr) { + /* + * In this case s->runtime->control->appl_ptr has wrapped around. + * Play the data to the end of the boundary, then wrap our own + * appl_ptr back around. + */ + while (s->appl_ptr < s->runtime->boundary) { + if (bcom_queue_full(s->bcom_task)) + return; + + s->appl_ptr += s->period_size; + + psc_dma_bcom_enqueue_next_buffer(s); + } + s->appl_ptr -= s->runtime->boundary; + } + while (s->appl_ptr < s->runtime->control->appl_ptr) { if (bcom_queue_full(s->bcom_task)) -- cgit v1.2.3 From 15b5bdaeeb15e0c18cca3ce7449188ca46516721 Mon Sep 17 00:00:00 2001 From: Daniel Mack Date: Wed, 5 Aug 2009 20:50:43 +0200 Subject: ALSA: ASoC: cs4270: move power management hooks to snd_soc_codec_device Power management for the cs4270 codec is currently implemented as part of the i2c_driver struct. The disadvantage of doing it this way is that the callbacks registered in the snd_soc_card struct are called _before_ the codec's callbacks. That doesn't work, because the snd_soc_card callbacks will most likely switch down the codec's power domains or pull the reset GPIOs, and hence make the i2c communication bail out. Fix this by binding the suspend and resume code to the snd_soc_codec_device driver model and let the I2C functions only call the SoC core function for resume and suspend, which do nothing currently but will do later. Signed-off-by: Daniel Mack Cc: Timur Tabi Signed-off-by: Mark Brown --- sound/soc/codecs/cs4270.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index a32b8226c8a..ca1e24a8f12 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -806,15 +806,30 @@ static int cs4270_i2c_suspend(struct i2c_client *client, pm_message_t mesg) { struct cs4270_private *cs4270 = i2c_get_clientdata(client); struct snd_soc_codec *codec = &cs4270->codec; - int reg = snd_soc_read(codec, CS4270_PWRCTL) | CS4270_PWRCTL_PDN_ALL; - return snd_soc_write(codec, CS4270_PWRCTL, reg); + return snd_soc_suspend_device(codec->dev); } static int cs4270_i2c_resume(struct i2c_client *client) { struct cs4270_private *cs4270 = i2c_get_clientdata(client); struct snd_soc_codec *codec = &cs4270->codec; + + return snd_soc_resume_device(codec->dev); +} + +static int cs4270_soc_suspend(struct platform_device *pdev, pm_message_t mesg) +{ + struct snd_soc_codec *codec = cs4270_codec; + int reg = snd_soc_read(codec, CS4270_PWRCTL) | CS4270_PWRCTL_PDN_ALL; + + return snd_soc_write(codec, CS4270_PWRCTL, reg); +} + +static int cs4270_soc_resume(struct platform_device *pdev) +{ + struct snd_soc_codec *codec = cs4270_codec; + struct i2c_client *i2c_client = codec->control_data; int reg; /* In case the device was put to hard reset during sleep, we need to @@ -825,7 +840,7 @@ static int cs4270_i2c_resume(struct i2c_client *client) for (reg = CS4270_FIRSTREG; reg <= CS4270_LASTREG; reg++) { u8 val = snd_soc_read(codec, reg); - if (i2c_smbus_write_byte_data(client, reg, val)) { + if (i2c_smbus_write_byte_data(i2c_client, reg, val)) { dev_err(codec->dev, "i2c write failed\n"); return -EIO; } @@ -840,6 +855,8 @@ static int cs4270_i2c_resume(struct i2c_client *client) #else #define cs4270_i2c_suspend NULL #define cs4270_i2c_resume NULL +#define cs4270_soc_suspend NULL +#define cs4270_soc_resume NULL #endif /* CONFIG_PM */ /* @@ -868,7 +885,9 @@ static struct i2c_driver cs4270_i2c_driver = { */ struct snd_soc_codec_device soc_codec_device_cs4270 = { .probe = cs4270_probe, - .remove = cs4270_remove + .remove = cs4270_remove, + .suspend = cs4270_soc_suspend, + .resume = cs4270_soc_resume, }; EXPORT_SYMBOL_GPL(soc_codec_device_cs4270); -- cgit v1.2.3 From 924914ee953f2ccf3a2d49bda08c9ce1046a0c58 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 4 Aug 2009 23:50:16 +0100 Subject: ASoC: Add WM8776 CODEC driver The WM8776 is a high performance, stereo audio CODEC with five channel input selector. The WM8776 is ideal for surround sound processing applications for home hi-fi, DVD-RW and other audio visual equipment. This driver implements support for most WM8776 features - currently the ADC automatic level control/limiter functionality is omitted. Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm8776.c | 781 ++++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm8776.h | 51 +++ 4 files changed, 838 insertions(+) create mode 100644 sound/soc/codecs/wm8776.c create mode 100644 sound/soc/codecs/wm8776.h (limited to 'sound/soc') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index fdfc5024e40..31a6d2111ed 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -38,6 +38,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8731 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8750 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8753 if SND_SOC_I2C_AND_SPI + select SND_SOC_WM8776 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8900 if I2C select SND_SOC_WM8903 if I2C select SND_SOC_WM8940 if I2C @@ -158,6 +159,9 @@ config SND_SOC_WM8750 config SND_SOC_WM8753 tristate +config SND_SOC_WM8776 + tristate + config SND_SOC_WM8900 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 1131d6dc761..78dce5d3fa2 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -26,6 +26,7 @@ snd-soc-wm8728-objs := wm8728.o snd-soc-wm8731-objs := wm8731.o snd-soc-wm8750-objs := wm8750.o snd-soc-wm8753-objs := wm8753.o +snd-soc-wm8776-objs := wm8776.o snd-soc-wm8900-objs := wm8900.o snd-soc-wm8903-objs := wm8903.o snd-soc-wm8940-objs := wm8940.o @@ -71,6 +72,7 @@ obj-$(CONFIG_SND_SOC_WM8728) += snd-soc-wm8728.o obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o +obj-$(CONFIG_SND_SOC_WM8776) += snd-soc-wm8776.o obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o obj-$(CONFIG_SND_SOC_WM8971) += snd-soc-wm8971.o diff --git a/sound/soc/codecs/wm8776.c b/sound/soc/codecs/wm8776.c new file mode 100644 index 00000000000..ee12bf824a4 --- /dev/null +++ b/sound/soc/codecs/wm8776.c @@ -0,0 +1,781 @@ +/* + * wm8776.c -- WM8776 ALSA SoC Audio driver + * + * Copyright 2009 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * 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. + * + * TODO: Input ALC/limiter support + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8776.h" + +static struct snd_soc_codec *wm8776_codec; +struct snd_soc_codec_device soc_codec_dev_wm8776; + +/* codec private data */ +struct wm8776_priv { + struct snd_soc_codec codec; + u16 reg_cache[WM8776_CACHEREGNUM]; + int sysclk[2]; +}; + +#ifdef CONFIG_SPI_MASTER +static int wm8776_spi_write(struct spi_device *spi, const char *data, int len); +#endif + +static const u16 wm8776_reg[WM8776_CACHEREGNUM] = { + 0x79, 0x79, 0x79, 0xff, 0xff, /* 4 */ + 0xff, 0x00, 0x90, 0x00, 0x00, /* 9 */ + 0x22, 0x22, 0x22, 0x08, 0xcf, /* 14 */ + 0xcf, 0x7b, 0x00, 0x32, 0x00, /* 19 */ + 0xa6, 0x01, 0x01 +}; + +/* + * read wm8776 register cache + */ +static inline unsigned int wm8776_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *cache = codec->reg_cache; + if (reg >= WM8776_CACHEREGNUM) + return -1; + return cache[reg]; +} + +/* + * write wm8776 register cache + */ +static inline void wm8776_write_reg_cache(struct snd_soc_codec *codec, + u16 reg, unsigned int value) +{ + u16 *cache = codec->reg_cache; + if (reg >= WM8776_CACHEREGNUM) + return; + cache[reg] = value; +} + +/* + * write to the WM8776 register space + */ +static int wm8776_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 data[2]; + + data[0] = (reg << 1) | ((value >> 8) & 0x0001); + data[1] = value & 0x00ff; + + wm8776_write_reg_cache(codec, reg, value); + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +static int wm8776_reset(struct snd_soc_codec *codec) +{ + return wm8776_write(codec, WM8776_RESET, 0); +} + +static const DECLARE_TLV_DB_SCALE(hp_tlv, -12100, 100, 1); +static const DECLARE_TLV_DB_SCALE(dac_tlv, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(adc_tlv, -10350, 50, 1); + +static const struct snd_kcontrol_new wm8776_snd_controls[] = { +SOC_DOUBLE_R_TLV("Headphone Playback Volume", WM8776_HPLVOL, WM8776_HPRVOL, + 0, 127, 0, hp_tlv), +SOC_DOUBLE_R_TLV("Digital Playback Volume", WM8776_DACLVOL, WM8776_DACRVOL, + 0, 255, 0, dac_tlv), +SOC_SINGLE("Digital Playback ZC Switch", WM8776_DACCTRL1, 0, 1, 0), + +SOC_SINGLE("Deemphasis Switch", WM8776_DACCTRL2, 0, 1, 0), + +SOC_DOUBLE_R_TLV("Capture Volume", WM8776_ADCLVOL, WM8776_ADCRVOL, + 0, 255, 0, adc_tlv), +SOC_DOUBLE("Capture Switch", WM8776_ADCMUX, 7, 6, 1, 1), +SOC_DOUBLE_R("Capture ZC Switch", WM8776_ADCLVOL, WM8776_ADCRVOL, 8, 1, 0), +SOC_SINGLE("Capture HPF Switch", WM8776_ADCIFCTRL, 8, 1, 1), +}; + +static const struct snd_kcontrol_new inmix_controls[] = { +SOC_DAPM_SINGLE("AIN1 Switch", WM8776_ADCMUX, 0, 1, 0), +SOC_DAPM_SINGLE("AIN2 Switch", WM8776_ADCMUX, 1, 1, 0), +SOC_DAPM_SINGLE("AIN3 Switch", WM8776_ADCMUX, 2, 1, 0), +SOC_DAPM_SINGLE("AIN4 Switch", WM8776_ADCMUX, 3, 1, 0), +SOC_DAPM_SINGLE("AIN5 Switch", WM8776_ADCMUX, 4, 1, 0), +}; + +static const struct snd_kcontrol_new outmix_controls[] = { +SOC_DAPM_SINGLE("DAC Switch", WM8776_OUTMUX, 0, 1, 0), +SOC_DAPM_SINGLE("AUX Switch", WM8776_OUTMUX, 1, 1, 0), +SOC_DAPM_SINGLE("Bypass Switch", WM8776_OUTMUX, 2, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8776_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("AUX"), +SND_SOC_DAPM_INPUT("AUX"), + +SND_SOC_DAPM_INPUT("AIN1"), +SND_SOC_DAPM_INPUT("AIN2"), +SND_SOC_DAPM_INPUT("AIN3"), +SND_SOC_DAPM_INPUT("AIN4"), +SND_SOC_DAPM_INPUT("AIN5"), + +SND_SOC_DAPM_MIXER("Input Mixer", WM8776_PWRDOWN, 6, 1, + inmix_controls, ARRAY_SIZE(inmix_controls)), + +SND_SOC_DAPM_ADC("ADC", "Capture", WM8776_PWRDOWN, 1, 1), +SND_SOC_DAPM_DAC("DAC", "Playback", WM8776_PWRDOWN, 2, 1), + +SND_SOC_DAPM_MIXER("Output Mixer", SND_SOC_NOPM, 0, 0, + outmix_controls, ARRAY_SIZE(outmix_controls)), + +SND_SOC_DAPM_PGA("Headphone PGA", WM8776_PWRDOWN, 3, 1, NULL, 0), + +SND_SOC_DAPM_OUTPUT("VOUT"), + +SND_SOC_DAPM_OUTPUT("HPOUTL"), +SND_SOC_DAPM_OUTPUT("HPOUTR"), +}; + +static const struct snd_soc_dapm_route routes[] = { + { "Input Mixer", "AIN1 Switch", "AIN1" }, + { "Input Mixer", "AIN2 Switch", "AIN2" }, + { "Input Mixer", "AIN3 Switch", "AIN3" }, + { "Input Mixer", "AIN4 Switch", "AIN4" }, + { "Input Mixer", "AIN5 Switch", "AIN5" }, + + { "ADC", NULL, "Input Mixer" }, + + { "Output Mixer", "DAC Switch", "DAC" }, + { "Output Mixer", "AUX Switch", "AUX" }, + { "Output Mixer", "Bypass Switch", "Input Mixer" }, + + { "VOUT", NULL, "Output Mixer" }, + + { "Headphone PGA", NULL, "Output Mixer" }, + + { "HPOUTL", NULL, "Headphone PGA" }, + { "HPOUTR", NULL, "Headphone PGA" }, +}; + +static int wm8776_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + int reg, iface, master; + + switch (dai->id) { + case WM8776_DAI_DAC: + reg = WM8776_DACIFCTRL; + master = 0x80; + break; + case WM8776_DAI_ADC: + reg = WM8776_ADCIFCTRL; + master = 0x100; + break; + default: + return -EINVAL; + } + + iface = 0; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + break; + case SND_SOC_DAIFMT_CBS_CFS: + master = 0; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + /* FIXME: CHECK A/B */ + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0007; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x00c; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x008; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x004; + break; + default: + return -EINVAL; + } + + /* Finally, write out the values */ + snd_soc_update_bits(codec, reg, 0xf, iface); + snd_soc_update_bits(codec, WM8776_MSTRCTRL, 0x180, master); + + return 0; +} + +static int mclk_ratios[] = { + 128, + 192, + 256, + 384, + 512, + 768, +}; + +static int wm8776_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8776_priv *wm8776 = codec->private_data; + int iface_reg, iface; + int ratio_shift, master; + int i; + + iface = 0; + + switch (dai->id) { + case WM8776_DAI_DAC: + iface_reg = WM8776_DACIFCTRL; + master = 0x80; + ratio_shift = 4; + break; + case WM8776_DAI_ADC: + iface_reg = WM8776_ADCIFCTRL; + master = 0x100; + ratio_shift = 0; + break; + default: + return -EINVAL; + } + + + /* Set word length */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x10; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x20; + break; + case SNDRV_PCM_FORMAT_S32_LE: + iface |= 0x30; + break; + } + + /* Only need to set MCLK/LRCLK ratio if we're master */ + if (snd_soc_read(codec, WM8776_MSTRCTRL) & master) { + for (i = 0; i < ARRAY_SIZE(mclk_ratios); i++) { + if (wm8776->sysclk[dai->id] / params_rate(params) + == mclk_ratios[i]) + break; + } + + if (i == ARRAY_SIZE(mclk_ratios)) { + dev_err(codec->dev, + "Unable to configure MCLK ratio %d/%d\n", + wm8776->sysclk[dai->id], params_rate(params)); + return -EINVAL; + } + + dev_dbg(codec->dev, "MCLK is %dfs\n", mclk_ratios[i]); + + snd_soc_update_bits(codec, WM8776_MSTRCTRL, + 0x7 << ratio_shift, i << ratio_shift); + } else { + dev_dbg(codec->dev, "DAI in slave mode\n"); + } + + snd_soc_update_bits(codec, iface_reg, 0x30, iface); + + return 0; +} + +static int wm8776_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + + return snd_soc_write(codec, WM8776_DACMUTE, !!mute); +} + +static int wm8776_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8776_priv *wm8776 = codec->private_data; + + BUG_ON(dai->id >= ARRAY_SIZE(wm8776->sysclk)); + + wm8776->sysclk[dai->id] = freq; + + return 0; +} + +static int wm8776_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + if (codec->bias_level == SND_SOC_BIAS_OFF) { + /* Disable the global powerdown; DAPM does the rest */ + snd_soc_update_bits(codec, WM8776_PWRDOWN, 1, 0); + } + + break; + case SND_SOC_BIAS_OFF: + snd_soc_update_bits(codec, WM8776_PWRDOWN, 1, 1); + break; + } + + codec->bias_level = level; + return 0; +} + +#define WM8776_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_88200 |\ + SNDRV_PCM_RATE_96000) + + +#define WM8776_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE) + +static struct snd_soc_dai_ops wm8776_dac_ops = { + .digital_mute = wm8776_mute, + .hw_params = wm8776_hw_params, + .set_fmt = wm8776_set_fmt, + .set_sysclk = wm8776_set_sysclk, +}; + +static struct snd_soc_dai_ops wm8776_adc_ops = { + .hw_params = wm8776_hw_params, + .set_fmt = wm8776_set_fmt, + .set_sysclk = wm8776_set_sysclk, +}; + +struct snd_soc_dai wm8776_dai[] = { + { + .name = "WM8776 Playback", + .id = WM8776_DAI_DAC, + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = WM8776_RATES, + .formats = WM8776_FORMATS, + }, + .ops = &wm8776_dac_ops, + }, + { + .name = "WM8776 Capture", + .id = WM8776_DAI_ADC, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + .rates = WM8776_RATES, + .formats = WM8776_FORMATS, + }, + .ops = &wm8776_adc_ops, + }, +}; +EXPORT_SYMBOL_GPL(wm8776_dai); + +#ifdef CONFIG_PM +static int wm8776_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + wm8776_set_bias_level(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +static int wm8776_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8776_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + + wm8776_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return 0; +} +#else +#define wm8776_suspend NULL +#define wm8776_resume NULL +#endif + +static int wm8776_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (wm8776_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = wm8776_codec; + codec = wm8776_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + snd_soc_add_controls(codec, wm8776_snd_controls, + ARRAY_SIZE(wm8776_snd_controls)); + snd_soc_dapm_new_controls(codec, wm8776_dapm_widgets, + ARRAY_SIZE(wm8776_dapm_widgets)); + snd_soc_dapm_add_routes(codec, routes, ARRAY_SIZE(routes)); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card: %d\n", ret); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + return ret; +} + +/* power down chip */ +static int wm8776_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8776 = { + .probe = wm8776_probe, + .remove = wm8776_remove, + .suspend = wm8776_suspend, + .resume = wm8776_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8776); + +static int wm8776_register(struct wm8776_priv *wm8776) +{ + int ret, i; + struct snd_soc_codec *codec = &wm8776->codec; + + if (wm8776_codec) { + dev_err(codec->dev, "Another WM8776 is registered\n"); + ret = -EINVAL; + goto err; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = wm8776; + codec->name = "WM8776"; + codec->owner = THIS_MODULE; + codec->read = wm8776_read_reg_cache; + codec->write = wm8776_write; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8776_set_bias_level; + codec->dai = wm8776_dai; + codec->num_dai = ARRAY_SIZE(wm8776_dai); + codec->reg_cache_size = WM8776_CACHEREGNUM; + codec->reg_cache = &wm8776->reg_cache; + + memcpy(codec->reg_cache, wm8776_reg, sizeof(wm8776_reg)); + + for (i = 0; i < ARRAY_SIZE(wm8776_dai); i++) + wm8776_dai[i].dev = codec->dev; + + ret = wm8776_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset: %d\n", ret); + goto err; + } + + wm8776_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* Latch the update bits; right channel only since we always + * update both. */ + snd_soc_update_bits(codec, WM8776_HPRVOL, 0x100, 0x100); + snd_soc_update_bits(codec, WM8776_DACRVOL, 0x100, 0x100); + + wm8776_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + goto err; + } + + ret = snd_soc_register_dais(wm8776_dai, ARRAY_SIZE(wm8776_dai)); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAIs: %d\n", ret); + goto err_codec; + } + + return 0; + +err_codec: + snd_soc_unregister_codec(codec); +err: + kfree(wm8776); + return ret; +} + +static void wm8776_unregister(struct wm8776_priv *wm8776) +{ + wm8776_set_bias_level(&wm8776->codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dais(wm8776_dai, ARRAY_SIZE(wm8776_dai)); + snd_soc_unregister_codec(&wm8776->codec); + kfree(wm8776); + wm8776_codec = NULL; +} + +#if defined(CONFIG_SPI_MASTER) +static int wm8776_spi_write(struct spi_device *spi, const char *data, int len) +{ + struct spi_transfer t; + struct spi_message m; + u8 msg[2]; + + if (len <= 0) + return 0; + + msg[0] = data[0]; + msg[1] = data[1]; + + spi_message_init(&m); + memset(&t, 0, (sizeof t)); + + t.tx_buf = &msg[0]; + t.len = len; + + spi_message_add_tail(&t, &m); + spi_sync(spi, &m); + + return len; +} + +static int __devinit wm8776_spi_probe(struct spi_device *spi) +{ + struct snd_soc_codec *codec; + struct wm8776_priv *wm8776; + + wm8776 = kzalloc(sizeof(struct wm8776_priv), GFP_KERNEL); + if (wm8776 == NULL) + return -ENOMEM; + + codec = &wm8776->codec; + codec->control_data = spi; + codec->hw_write = (hw_write_t)wm8776_spi_write; + codec->dev = &spi->dev; + + dev_set_drvdata(&spi->dev, wm8776); + + return wm8776_register(wm8776); +} + +static int __devexit wm8776_spi_remove(struct spi_device *spi) +{ + struct wm8776_priv *wm8776 = dev_get_drvdata(&spi->dev); + + wm8776_unregister(wm8776); + + return 0; +} + +#ifdef CONFIG_PM +static int wm8776_spi_suspend(struct spi_device *spi, pm_message_t msg) +{ + return snd_soc_suspend_device(&spi->dev); +} + +static int wm8776_spi_resume(struct spi_device *spi) +{ + return snd_soc_resume_device(&spi->dev); +} +#else +#define wm8776_spi_suspend NULL +#define wm8776_spi_resume NULL +#endif + +static struct spi_driver wm8776_spi_driver = { + .driver = { + .name = "wm8776", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = wm8776_spi_probe, + .suspend = wm8776_spi_suspend, + .resume = wm8776_spi_resume, + .remove = __devexit_p(wm8776_spi_remove), +}; +#endif /* CONFIG_SPI_MASTER */ + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static __devinit int wm8776_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8776_priv *wm8776; + struct snd_soc_codec *codec; + + wm8776 = kzalloc(sizeof(struct wm8776_priv), GFP_KERNEL); + if (wm8776 == NULL) + return -ENOMEM; + + codec = &wm8776->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + + i2c_set_clientdata(i2c, wm8776); + codec->control_data = i2c; + + codec->dev = &i2c->dev; + + return wm8776_register(wm8776); +} + +static __devexit int wm8776_i2c_remove(struct i2c_client *client) +{ + struct wm8776_priv *wm8776 = i2c_get_clientdata(client); + wm8776_unregister(wm8776); + return 0; +} + +#ifdef CONFIG_PM +static int wm8776_i2c_suspend(struct i2c_client *i2c, pm_message_t msg) +{ + return snd_soc_suspend_device(&i2c->dev); +} + +static int wm8776_i2c_resume(struct i2c_client *i2c) +{ + return snd_soc_resume_device(&i2c->dev); +} +#else +#define wm8776_i2c_suspend NULL +#define wm8776_i2c_resume NULL +#endif + +static const struct i2c_device_id wm8776_i2c_id[] = { + { "wm8776", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8776_i2c_id); + +static struct i2c_driver wm8776_i2c_driver = { + .driver = { + .name = "wm8776", + .owner = THIS_MODULE, + }, + .probe = wm8776_i2c_probe, + .remove = __devexit_p(wm8776_i2c_remove), + .suspend = wm8776_i2c_suspend, + .resume = wm8776_i2c_resume, + .id_table = wm8776_i2c_id, +}; +#endif + +static int __init wm8776_modinit(void) +{ + int ret; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&wm8776_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8776 I2C driver: %d\n", + ret); + } +#endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&wm8776_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8776 SPI driver: %d\n", + ret); + } +#endif + return 0; +} +module_init(wm8776_modinit); + +static void __exit wm8776_exit(void) +{ +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&wm8776_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8776_spi_driver); +#endif +} +module_exit(wm8776_exit); + +MODULE_DESCRIPTION("ASoC WM8776 driver"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8776.h b/sound/soc/codecs/wm8776.h new file mode 100644 index 00000000000..6606d25d2d8 --- /dev/null +++ b/sound/soc/codecs/wm8776.h @@ -0,0 +1,51 @@ +/* + * wm8776.h -- WM8776 ASoC driver + * + * Copyright 2009 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * 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 _WM8776_H +#define _WM8776_H + +/* Registers */ + +#define WM8776_HPLVOL 0x00 +#define WM8776_HPRVOL 0x01 +#define WM8776_HPMASTER 0x02 +#define WM8776_DACLVOL 0x03 +#define WM8776_DACRVOL 0x04 +#define WM8776_DACMASTER 0x05 +#define WM8776_PHASESWAP 0x06 +#define WM8776_DACCTRL1 0x07 +#define WM8776_DACMUTE 0x08 +#define WM8776_DACCTRL2 0x09 +#define WM8776_DACIFCTRL 0x0a +#define WM8776_ADCIFCTRL 0x0b +#define WM8776_MSTRCTRL 0x0c +#define WM8776_PWRDOWN 0x0d +#define WM8776_ADCLVOL 0x0e +#define WM8776_ADCRVOL 0x0f +#define WM8776_ALCCTRL1 0x10 +#define WM8776_ALCCTRL2 0x11 +#define WM8776_ALCCTRL3 0x12 +#define WM8776_NOISEGATE 0x13 +#define WM8776_LIMITER 0x14 +#define WM8776_ADCMUX 0x15 +#define WM8776_OUTMUX 0x16 +#define WM8776_RESET 0x17 + +#define WM8776_CACHEREGNUM 0x17 + +#define WM8776_DAI_DAC 0 +#define WM8776_DAI_ADC 1 + +extern struct snd_soc_dai wm8776_dai[]; +extern struct snd_soc_codec_device soc_codec_dev_wm8776; + +#endif -- cgit v1.2.3 From 4977b03e3dc3b76182903cda2df9dc2ef8f566a0 Mon Sep 17 00:00:00 2001 From: Janusz Krzysztofik Date: Thu, 6 Aug 2009 12:24:54 +0200 Subject: ASoC: CX20442: add some debugging This patch adds debugging statement that can help in tracing how the driver is trying to control the codec device. Signed-off-by: Janusz Krzysztofik Signed-off-by: Mark Brown --- sound/soc/codecs/cx20442.c | 1 + 1 file changed, 1 insertion(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/cx20442.c b/sound/soc/codecs/cx20442.c index f33245510c3..0e54adcc979 100644 --- a/sound/soc/codecs/cx20442.c +++ b/sound/soc/codecs/cx20442.c @@ -203,6 +203,7 @@ static int cx20442_write(struct snd_soc_codec *codec, unsigned int reg, if (unlikely(len > (ARRAY_SIZE(buf) - 1))) return -ENOMEM; + dev_dbg(codec->dev, "%s: %s\n", __func__, buf); if (codec->hw_write(codec->control_data, buf, len) != len) return -EIO; -- cgit v1.2.3 From 9029bb316bb99ccc7f0518c4d3e0d6adc0729c4f Mon Sep 17 00:00:00 2001 From: Janusz Krzysztofik Date: Thu, 6 Aug 2009 12:25:00 +0200 Subject: ASoC: CX20442: simplify codec controller usage This patch is a workaround for the problem of several subsequent control statements not being applied correctly to the codec controller (modem). In order to follow the hook switch state change from handset to handsfree while in full duplex mode, two consecutive +VLS control commands were sent to the modem. The first one was M1 (microphone only), the seconds one was M1S1 (both microphone and speaker). As there was no real modem handshaking procedure implemented, neither in the codec nor in the machine driver part of the line discipline, the modem was having the second command missed. Since a possibility to switch to microphone only mode (and speaker only mode as well) seams of no value, I have modified the code to issue single M1S1 command only for any of those cases. Tested on my Amstrad Delta. Signed-off-by: Janusz Krzysztofik Signed-off-by: Mark Brown --- sound/soc/codecs/cx20442.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/cx20442.c b/sound/soc/codecs/cx20442.c index 0e54adcc979..38eac9c866e 100644 --- a/sound/soc/codecs/cx20442.c +++ b/sound/soc/codecs/cx20442.c @@ -138,9 +138,7 @@ static int cx20442_pm_to_v253_vls(u8 value) case 0: return V253_VLS_T; case (1 << CX20442_SPKOUT): - return V253_VLS_S1; case (1 << CX20442_MIC): - return V253_VLS_M1; case (1 << CX20442_SPKOUT) | (1 << CX20442_MIC): return V253_VLS_M1S1; case (1 << CX20442_TELOUT): -- cgit v1.2.3 From a5479e389e989acfeca9c32eeb0083d086202280 Mon Sep 17 00:00:00 2001 From: Daniel Ribeiro Date: Mon, 15 Jun 2009 21:44:31 -0300 Subject: ASoC: change set_tdm_slot api to allow slot_width override. Extend set_tdm_slot to allow the user to arbitrarily set the frame width and active TX/RX slots. Updates magician.c and wm9081.c for the new set_tdm_slot(). wm9081.c still doesn't handle the slot_width override. While being there, correct an incorrect use of SlotsPerFrm(7) use in bitmask on pxa-ssp.c (SSCR0_SlotsPerFrm(x) is (((x) - 1) << 24)) ). (this series is meant for Mark's for-2.6.32 branch) Signed-off-by: Daniel Ribeiro Signed-off-by: Mark Brown --- sound/soc/codecs/wm9081.c | 5 +++-- sound/soc/pxa/magician.c | 2 +- sound/soc/pxa/pxa-ssp.c | 27 ++++++++++++++++++++------- sound/soc/soc-core.c | 9 ++++++--- 4 files changed, 30 insertions(+), 13 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c index 156f2a4a593..c9653235119 100644 --- a/sound/soc/codecs/wm9081.c +++ b/sound/soc/codecs/wm9081.c @@ -1214,8 +1214,9 @@ static int wm9081_set_sysclk(struct snd_soc_dai *codec_dai, return 0; } +/* FIXME: Needs to handle slot_width */ static int wm9081_set_tdm_slot(struct snd_soc_dai *dai, - unsigned int mask, int slots) + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) { struct snd_soc_codec *codec = dai->codec; unsigned int aif1 = wm9081_read(codec, WM9081_AUDIO_INTERFACE_1); @@ -1227,7 +1228,7 @@ static int wm9081_set_tdm_slot(struct snd_soc_dai *dai, aif1 |= (slots - 1) << WM9081_AIFDAC_TDM_MODE_SHIFT; - switch (mask) { + switch (rx_mask) { case 1: break; case 2: diff --git a/sound/soc/pxa/magician.c b/sound/soc/pxa/magician.c index 8889cd37160..9f7c61e23da 100644 --- a/sound/soc/pxa/magician.c +++ b/sound/soc/pxa/magician.c @@ -190,7 +190,7 @@ static int magician_playback_hw_params(struct snd_pcm_substream *substream, if (ret < 0) return ret; - ret = snd_soc_dai_set_tdm_slot(cpu_dai, 1, 1); + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 1, 0, 1, width); if (ret < 0) return ret; diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index e22c5cef8fe..5b9ed646478 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -375,21 +375,34 @@ static int pxa_ssp_set_dai_pll(struct snd_soc_dai *cpu_dai, * Set the active slots in TDM/Network mode */ static int pxa_ssp_set_dai_tdm_slot(struct snd_soc_dai *cpu_dai, - unsigned int mask, int slots) + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) { struct ssp_priv *priv = cpu_dai->private_data; struct ssp_device *ssp = priv->dev.ssp; u32 sscr0; - sscr0 = ssp_read_reg(ssp, SSCR0) & ~SSCR0_SlotsPerFrm(7); + sscr0 = ssp_read_reg(ssp, SSCR0); + sscr0 &= ~(SSCR0_MOD | SSCR0_SlotsPerFrm(8) | SSCR0_EDSS | SSCR0_DSS); + + /* set slot width */ + if (slot_width > 16) + sscr0 |= SSCR0_EDSS | SSCR0_DataSize(slot_width - 16); + else + sscr0 |= SSCR0_DataSize(slot_width); - /* set number of active slots */ - sscr0 |= SSCR0_SlotsPerFrm(slots); + if (slots > 1) { + /* enable network mode */ + sscr0 |= SSCR0_MOD; + + /* set number of active slots */ + sscr0 |= SSCR0_SlotsPerFrm(slots); + + /* set active slot mask */ + ssp_write_reg(ssp, SSTSA, tx_mask); + ssp_write_reg(ssp, SSRSA, rx_mask); + } ssp_write_reg(ssp, SSCR0, sscr0); - /* set active slot mask */ - ssp_write_reg(ssp, SSTSA, mask); - ssp_write_reg(ssp, SSRSA, mask); return 0; } diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index fb8d7a76615..e984a17cd65 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2223,17 +2223,20 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_set_fmt); /** * snd_soc_dai_set_tdm_slot - configure DAI TDM. * @dai: DAI - * @mask: DAI specific mask representing used slots. + * @tx_mask: bitmask representing active TX slots. + * @rx_mask: bitmask representing active RX slots. * @slots: Number of slots in use. + * @slot_width: Width in bits for each slot. * * Configures a DAI for TDM operation. Both mask and slots are codec and DAI * specific. */ int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai, - unsigned int mask, int slots) + unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) { if (dai->ops && dai->ops->set_tdm_slot) - return dai->ops->set_tdm_slot(dai, mask, slots); + return dai->ops->set_tdm_slot(dai, tx_mask, rx_mask, + slots, slot_width); else return -EINVAL; } -- cgit v1.2.3 From c12abc012e18b362204345c323536f228d65c4db Mon Sep 17 00:00:00 2001 From: Jarkko Nikula Date: Fri, 7 Aug 2009 09:59:47 +0300 Subject: ARM: OMAP: McBSP: Fix ASoC on OMAP1510 by fixing API of omap_mcbsp_start/stop Simultaneous audio playback and capture on OMAP1510 can cause that second stream is stalled if there is enough delay between startup of the audio streams. Current implementation of the omap_mcbsp_start is starting both transmitter and receiver at the same time and it is called only for firstly started audio stream from the OMAP McBSP based ASoC DAI driver. Since DMA request lines on OMAP1510 are edge sensitive, the DMA request is missed if there is no DMA transfer set up at that time when the first word after McBSP startup is transmitted. The problem hasn't noted before since later OMAPs are using level sensitive DMA request lines. Fix the problem by changing API of omap_mcbsp_start and omap_mcbsp_stop by allowing to start and stop individually McBSP transmitter and receiver logics. Then call those functions individually for both audio playback and capture streams. This ensures that DMA transfer is setup before transmitter or receiver is started. Thanks to Janusz Krzysztofik for detailed problem analysis and Peter Ujfalusi for info about DMA request line behavior differences between the OMAP generations. Reported-and-tested-by: Janusz Krzysztofik Signed-off-by: Jarkko Nikula Acked-by: Tony Lindgren Acked-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/omap/omap-mcbsp.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/omap/omap-mcbsp.c b/sound/soc/omap/omap-mcbsp.c index a5d46a7b196..6a837ffd5d0 100644 --- a/sound/soc/omap/omap-mcbsp.c +++ b/sound/soc/omap/omap-mcbsp.c @@ -183,21 +183,21 @@ static int omap_mcbsp_dai_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; struct omap_mcbsp_data *mcbsp_data = to_mcbsp(cpu_dai->private_data); - int err = 0; + int err = 0, play = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - if (!mcbsp_data->active++) - omap_mcbsp_start(mcbsp_data->bus_id); + mcbsp_data->active++; + omap_mcbsp_start(mcbsp_data->bus_id, play, !play); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - if (!--mcbsp_data->active) - omap_mcbsp_stop(mcbsp_data->bus_id); + omap_mcbsp_stop(mcbsp_data->bus_id, play, !play); + mcbsp_data->active--; break; default: err = -EINVAL; -- cgit v1.2.3 From 9bb74150561bb337b49192a8f8e8f812057b88ea Mon Sep 17 00:00:00 2001 From: Troy Kisky Date: Thu, 6 Aug 2009 16:55:31 -0700 Subject: ASoC: DaVinci: i2s: don't bounce through rtd to get dai dai is a parameter to the functions, so use it instead of looking it up. Signed-off-by: Troy Kisky Signed-off-by: Mark Brown --- sound/soc/davinci/davinci-i2s.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c index e5cd97b74c5..2a56fb78f67 100644 --- a/sound/soc/davinci/davinci-i2s.c +++ b/sound/soc/davinci/davinci-i2s.c @@ -353,9 +353,8 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct davinci_pcm_dma_params *dma_params = rtd->dai->cpu_dai->dma_data; - struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data; + struct davinci_pcm_dma_params *dma_params = dai->dma_data; + struct davinci_mcbsp_dev *dev = dai->private_data; struct snd_interval *i = NULL; int mcbsp_word_length; unsigned int rcr, xcr, srgr; @@ -425,8 +424,7 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream, static int davinci_i2s_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data; + struct davinci_mcbsp_dev *dev = dai->private_data; int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); davinci_mcbsp_stop(dev, playback); if ((dev->pcr & DAVINCI_MCBSP_PCR_FSXM) == 0) { @@ -439,8 +437,7 @@ static int davinci_i2s_prepare(struct snd_pcm_substream *substream, static int davinci_i2s_trigger(struct snd_pcm_substream *substream, int cmd, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data; + struct davinci_mcbsp_dev *dev = dai->private_data; int ret = 0; int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); if ((dev->pcr & DAVINCI_MCBSP_PCR_FSXM) == 0) @@ -466,8 +463,7 @@ static int davinci_i2s_trigger(struct snd_pcm_substream *substream, int cmd, static void davinci_i2s_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct davinci_mcbsp_dev *dev = rtd->dai->cpu_dai->private_data; + struct davinci_mcbsp_dev *dev = dai->private_data; int playback = (substream->stream == SNDRV_PCM_STREAM_PLAYBACK); davinci_mcbsp_stop(dev, playback); } -- cgit v1.2.3 From 6a90d536feeccb2c84a2831252b1ed4642684da3 Mon Sep 17 00:00:00 2001 From: Troy Kisky Date: Thu, 6 Aug 2009 16:55:32 -0700 Subject: ASoC: DaVinci: pcm, constrain buffer size to multiple of period The dma setup code assumes that the buffer size is a multiple of the period size. Signed-off-by: Troy Kisky Signed-off-by: Mark Brown --- sound/soc/davinci/davinci-pcm.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/davinci/davinci-pcm.c b/sound/soc/davinci/davinci-pcm.c index 8fd0c3cdc71..ab43a539c11 100644 --- a/sound/soc/davinci/davinci-pcm.c +++ b/sound/soc/davinci/davinci-pcm.c @@ -244,6 +244,11 @@ static int davinci_pcm_open(struct snd_pcm_substream *substream) int ret = 0; snd_soc_set_runtime_hwparams(substream, &davinci_pcm_hardware); + /* ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + return ret; prtd = kzalloc(sizeof(struct davinci_runtime_data), GFP_KERNEL); if (prtd == NULL) -- cgit v1.2.3 From b7b8f9bf0cd73b90561e8123fd5ec28f4539c419 Mon Sep 17 00:00:00 2001 From: Janusz Krzysztofik Date: Thu, 6 Aug 2009 13:07:32 +0200 Subject: TTY/ASoC: Rename N_AMSDELTA line discipline to N_V253 The patch changes the line discipline name registered in include/linux/tty.h and updates the ams-delta machine driver to use it. Signed-off-by: Janusz Krzysztofik Signed-off-by: Mark Brown --- sound/soc/omap/ams-delta.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/omap/ams-delta.c b/sound/soc/omap/ams-delta.c index 4f35b1f18cb..5a5166ac727 100644 --- a/sound/soc/omap/ams-delta.c +++ b/sound/soc/omap/ams-delta.c @@ -500,7 +500,7 @@ static int ams_delta_cx20442_init(struct snd_soc_codec *codec) } /* Register optional line discipline for over the modem control */ - ret = tty_register_ldisc(N_AMSDELTA, &cx81801_ops); + ret = tty_register_ldisc(N_V253, &cx81801_ops); if (ret) { dev_warn(card->dev, "Failed to register line discipline, " @@ -625,9 +625,9 @@ static void __exit ams_delta_module_exit(void) } } - if (tty_unregister_ldisc(N_AMSDELTA) != 0) + if (tty_unregister_ldisc(N_V253) != 0) dev_warn(&ams_delta_audio_platform_device->dev, - "failed to unregister AMSDELTA line discipline\n"); + "failed to unregister V253 line discipline\n"); snd_soc_jack_free_gpios(&ams_delta_hook_switch, ARRAY_SIZE(ams_delta_hook_switch_gpios), -- cgit v1.2.3 From dbe9ea6e79d38cd5f2069405864ff06606cf9ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig?= Date: Fri, 7 Aug 2009 22:42:37 +0200 Subject: ASoC: s3c2443-ac97: convert semaphore to mutex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes a build failure for 2.6.31-rc4-rt1 (ARCH=arm, s3c2410_defconfig): CC [M] sound/soc/s3c24xx/s3c2443-ac97.o sound/soc/s3c24xx/s3c2443-ac97.c:50: warning: type defaults to 'int' in declaration of 'DECLARE_MUTEX' sound/soc/s3c24xx/s3c2443-ac97.c:50: warning: parameter names (without types) in function declaration sound/soc/s3c24xx/s3c2443-ac97.c: In function 's3c2443_ac97_read': sound/soc/s3c24xx/s3c2443-ac97.c:59: error: 'ac97_mutex' undeclared (first use in this function) sound/soc/s3c24xx/s3c2443-ac97.c:59: error: (Each undeclared identifier is reported only once sound/soc/s3c24xx/s3c2443-ac97.c:59: error: for each function it appears in.) sound/soc/s3c24xx/s3c2443-ac97.c: In function 's3c2443_ac97_write': sound/soc/s3c24xx/s3c2443-ac97.c:93: error: 'ac97_mutex' undeclared (first use in this function) Signed-off-by: Uwe Kleine-König Signed-off-by: Mark Brown --- sound/soc/s3c24xx/s3c2443-ac97.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/s3c24xx/s3c2443-ac97.c b/sound/soc/s3c24xx/s3c2443-ac97.c index 3f03d5ddfac..bf16f20fcbb 100644 --- a/sound/soc/s3c24xx/s3c2443-ac97.c +++ b/sound/soc/s3c24xx/s3c2443-ac97.c @@ -47,7 +47,7 @@ static struct s3c24xx_ac97_info s3c24xx_ac97; static DECLARE_COMPLETION(ac97_completion); static u32 codec_ready; -static DECLARE_MUTEX(ac97_mutex); +static DEFINE_MUTEX(ac97_mutex); static unsigned short s3c2443_ac97_read(struct snd_ac97 *ac97, unsigned short reg) @@ -56,7 +56,7 @@ static unsigned short s3c2443_ac97_read(struct snd_ac97 *ac97, u32 ac_codec_cmd; u32 stat, addr, data; - down(&ac97_mutex); + mutex_lock(&ac97_mutex); codec_ready = S3C_AC97_GLBSTAT_CODECREADY; ac_codec_cmd = readl(s3c24xx_ac97.regs + S3C_AC97_CODEC_CMD); @@ -79,7 +79,7 @@ static unsigned short s3c2443_ac97_read(struct snd_ac97 *ac97, printk(KERN_ERR "s3c24xx-ac97: req addr = %02x," " rep addr = %02x\n", reg, addr); - up(&ac97_mutex); + mutex_unlock(&ac97_mutex); return (unsigned short)data; } @@ -90,7 +90,7 @@ static void s3c2443_ac97_write(struct snd_ac97 *ac97, unsigned short reg, u32 ac_glbctrl; u32 ac_codec_cmd; - down(&ac97_mutex); + mutex_lock(&ac97_mutex); codec_ready = S3C_AC97_GLBSTAT_CODECREADY; ac_codec_cmd = readl(s3c24xx_ac97.regs + S3C_AC97_CODEC_CMD); @@ -109,7 +109,7 @@ static void s3c2443_ac97_write(struct snd_ac97 *ac97, unsigned short reg, ac_codec_cmd |= S3C_AC97_CODEC_CMD_READ; writel(ac_codec_cmd, s3c24xx_ac97.regs + S3C_AC97_CODEC_CMD); - up(&ac97_mutex); + mutex_unlock(&ac97_mutex); } -- cgit v1.2.3 From 7ae5945f0cacdfa30ed3257892dbea75bc959a28 Mon Sep 17 00:00:00 2001 From: Chaithrika U S Date: Fri, 7 Aug 2009 10:07:51 -0400 Subject: ASoC: DaVinci: Support Audio on DA830 EVM Add support for audio on DA830 EVM- here McASP1 is interfaced to TLV320AIC3106 codec. Signed-off-by: Chaithrika U S Signed-off-by: Mark Brown --- sound/soc/davinci/Kconfig | 11 +++++++++++ sound/soc/davinci/Makefile | 1 + sound/soc/davinci/davinci-evm.c | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/davinci/Kconfig b/sound/soc/davinci/Kconfig index 6802dd5e473..677a5381487 100644 --- a/sound/soc/davinci/Kconfig +++ b/sound/soc/davinci/Kconfig @@ -41,3 +41,14 @@ config SND_DAVINCI_SOC_SFFSDR help Say Y if you want to add support for SoC audio on Lyrtech SFFSDR board. + +config SND_DA830_SOC_EVM + tristate "SoC Audio support for DA830/OMAPL137 EVM" + depends on SND_DAVINCI_SOC && MACH_DAVINCI_DA830_EVM + select SND_DAVINCI_SOC_MCASP + select SND_SOC_TLV320AIC3X + + help + Say Y if you want to add support for SoC audio on TI + DA830/OMAPL137 EVM + diff --git a/sound/soc/davinci/Makefile b/sound/soc/davinci/Makefile index 67be54f3a3a..5e2195f80cf 100644 --- a/sound/soc/davinci/Makefile +++ b/sound/soc/davinci/Makefile @@ -13,4 +13,5 @@ snd-soc-sffsdr-objs := davinci-sffsdr.o obj-$(CONFIG_SND_DAVINCI_SOC_EVM) += snd-soc-evm.o obj-$(CONFIG_SND_DM6467_SOC_EVM) += snd-soc-evm.o +obj-$(CONFIG_SND_DA830_SOC_EVM) += snd-soc-evm.o obj-$(CONFIG_SND_DAVINCI_SOC_SFFSDR) += snd-soc-sffsdr.o diff --git a/sound/soc/davinci/davinci-evm.c b/sound/soc/davinci/davinci-evm.c index f3bb6f60f20..a5a92124b6d 100644 --- a/sound/soc/davinci/davinci-evm.c +++ b/sound/soc/davinci/davinci-evm.c @@ -54,6 +54,9 @@ static int evm_hw_params(struct snd_pcm_substream *substream, else if (machine_is_davinci_evm()) sysclk = 12288000; + else if (machine_is_davinci_da830_evm()) + sysclk = 24576000; + else return -EINVAL; @@ -162,6 +165,14 @@ static struct snd_soc_dai_link dm6467_evm_dai[] = { .ops = &evm_ops, }, }; +static struct snd_soc_dai_link da830_evm_dai = { + .name = "TLV320AIC3X", + .stream_name = "AIC3X", + .cpu_dai = &davinci_mcasp_dai[DAVINCI_MCASP_I2S_DAI], + .codec_dai = &aic3x_dai, + .init = evm_aic3x_init, + .ops = &evm_ops, +}; /* davinci-evm audio machine driver */ static struct snd_soc_card snd_soc_card_evm = { @@ -179,6 +190,13 @@ static struct snd_soc_card dm6467_snd_soc_card_evm = { .num_links = ARRAY_SIZE(dm6467_evm_dai), }; +static struct snd_soc_card da830_snd_soc_card = { + .name = "DA830 EVM", + .dai_link = &da830_evm_dai, + .platform = &davinci_soc_platform, + .num_links = 1, +}; + /* evm audio private data */ static struct aic3x_setup_data evm_aic3x_setup = { .i2c_bus = 1, @@ -191,6 +209,11 @@ static struct aic3x_setup_data dm6467_evm_aic3x_setup = { .i2c_address = 0x18, }; +static struct aic3x_setup_data da830_evm_aic3x_setup = { + .i2c_bus = 1, + .i2c_address = 0x18, +}; + /* evm audio subsystem */ static struct snd_soc_device evm_snd_devdata = { .card = &snd_soc_card_evm, @@ -205,6 +228,13 @@ static struct snd_soc_device dm6467_evm_snd_devdata = { .codec_data = &dm6467_evm_aic3x_setup, }; +/* evm audio subsystem */ +static struct snd_soc_device da830_evm_snd_devdata = { + .card = &da830_snd_soc_card, + .codec_dev = &soc_codec_dev_aic3x, + .codec_data = &da830_evm_aic3x_setup, +}; + static struct platform_device *evm_snd_device; static int __init evm_init(void) @@ -222,6 +252,9 @@ static int __init evm_init(void) } else if (machine_is_davinci_dm6467_evm()) { evm_snd_dev_data = &dm6467_evm_snd_devdata; index = 0; + } else if (machine_is_davinci_da830_evm()) { + evm_snd_dev_data = &da830_evm_snd_devdata; + index = 1; } else return -EINVAL; -- cgit v1.2.3 From 35b1207b34975d7ff2ea014171229c02bdb066f1 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Sat, 8 Aug 2009 10:37:33 +0100 Subject: ASoC: Convert WM8776 to use factored out register cache code Signed-off-by: Mark Brown --- sound/soc/codecs/wm8776.c | 59 +++++++++-------------------------------------- 1 file changed, 11 insertions(+), 48 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8776.c b/sound/soc/codecs/wm8776.c index ee12bf824a4..a9829aa26e5 100644 --- a/sound/soc/codecs/wm8776.c +++ b/sound/soc/codecs/wm8776.c @@ -52,51 +52,9 @@ static const u16 wm8776_reg[WM8776_CACHEREGNUM] = { 0xa6, 0x01, 0x01 }; -/* - * read wm8776 register cache - */ -static inline unsigned int wm8776_read_reg_cache(struct snd_soc_codec *codec, - unsigned int reg) -{ - u16 *cache = codec->reg_cache; - if (reg >= WM8776_CACHEREGNUM) - return -1; - return cache[reg]; -} - -/* - * write wm8776 register cache - */ -static inline void wm8776_write_reg_cache(struct snd_soc_codec *codec, - u16 reg, unsigned int value) -{ - u16 *cache = codec->reg_cache; - if (reg >= WM8776_CACHEREGNUM) - return; - cache[reg] = value; -} - -/* - * write to the WM8776 register space - */ -static int wm8776_write(struct snd_soc_codec *codec, unsigned int reg, - unsigned int value) -{ - u8 data[2]; - - data[0] = (reg << 1) | ((value >> 8) & 0x0001); - data[1] = value & 0x00ff; - - wm8776_write_reg_cache(codec, reg, value); - if (codec->hw_write(codec->control_data, data, 2) == 2) - return 0; - else - return -EIO; -} - static int wm8776_reset(struct snd_soc_codec *codec) { - return wm8776_write(codec, WM8776_RESET, 0); + return snd_soc_write(codec, WM8776_RESET, 0); } static const DECLARE_TLV_DB_SCALE(hp_tlv, -12100, 100, 1); @@ -523,7 +481,8 @@ struct snd_soc_codec_device soc_codec_dev_wm8776 = { }; EXPORT_SYMBOL_GPL(soc_codec_dev_wm8776); -static int wm8776_register(struct wm8776_priv *wm8776) +static int wm8776_register(struct wm8776_priv *wm8776, + enum snd_soc_control_type control) { int ret, i; struct snd_soc_codec *codec = &wm8776->codec; @@ -541,8 +500,6 @@ static int wm8776_register(struct wm8776_priv *wm8776) codec->private_data = wm8776; codec->name = "WM8776"; codec->owner = THIS_MODULE; - codec->read = wm8776_read_reg_cache; - codec->write = wm8776_write; codec->bias_level = SND_SOC_BIAS_OFF; codec->set_bias_level = wm8776_set_bias_level; codec->dai = wm8776_dai; @@ -552,6 +509,12 @@ static int wm8776_register(struct wm8776_priv *wm8776) memcpy(codec->reg_cache, wm8776_reg, sizeof(wm8776_reg)); + ret = snd_soc_codec_set_cache_io(codec, 7, 9, control); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + goto err; + } + for (i = 0; i < ARRAY_SIZE(wm8776_dai); i++) wm8776_dai[i].dev = codec->dev; @@ -641,7 +604,7 @@ static int __devinit wm8776_spi_probe(struct spi_device *spi) dev_set_drvdata(&spi->dev, wm8776); - return wm8776_register(wm8776); + return wm8776_register(wm8776, SND_SOC_SPI); } static int __devexit wm8776_spi_remove(struct spi_device *spi) @@ -700,7 +663,7 @@ static __devinit int wm8776_i2c_probe(struct i2c_client *i2c, codec->dev = &i2c->dev; - return wm8776_register(wm8776); + return wm8776_register(wm8776, SND_SOC_I2C); } static __devexit int wm8776_i2c_remove(struct i2c_client *client) -- cgit v1.2.3 From e0c48a18f76415d3ec1ac28e5f7a0df10e2f3d8c Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Mon, 10 Aug 2009 12:59:29 +0100 Subject: ASoC: Drop unneeded declaration of removed wm8731 SPI write function Signed-off-by: Mark Brown --- sound/soc/codecs/wm8731.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm8731.c b/sound/soc/codecs/wm8731.c index 2b980ccb603..d3fd4f28d96 100644 --- a/sound/soc/codecs/wm8731.c +++ b/sound/soc/codecs/wm8731.c @@ -40,9 +40,6 @@ struct wm8731_priv { unsigned int sysclk; }; -#ifdef CONFIG_SPI_MASTER -static int wm8731_spi_write(struct spi_device *spi, const char *data, int len); -#endif /* * wm8731 register cache -- cgit v1.2.3 From 17244c24f95ba61a12b6afeab5780a1e3f1a4c57 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Mon, 10 Aug 2009 16:04:39 -0700 Subject: ASoC: fix I2C build errors Fix soc build errors when I2C is built as a loadable module: (.text+0x5d26b): undefined reference to `i2c_master_send' soc-cache.c:(.text+0x5d32d): undefined reference to `i2c_transfer' Signed-off-by: Randy Dunlap Signed-off-by: Mark Brown --- sound/soc/soc-cache.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c index 56ec3f22bf1..c8ceddc2a26 100644 --- a/sound/soc/soc-cache.c +++ b/sound/soc/soc-cache.c @@ -108,7 +108,7 @@ static unsigned int snd_soc_8_16_read(struct snd_soc_codec *codec, return cache[reg]; } -#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) static unsigned int snd_soc_8_16_read_i2c(struct snd_soc_codec *codec, unsigned int r) { @@ -200,7 +200,7 @@ int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec, break; case SND_SOC_I2C: -#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +#if defined(CONFIG_I2C) || (defined(CONFIG_I2C_MODULE) && defined(MODULE)) codec->hw_write = (hw_write_t)i2c_master_send; #endif if (io_types[i].i2c_read) -- cgit v1.2.3 From e0026beac0f1a6253488478b3f022ff243b012b9 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 11 Aug 2009 16:28:39 +0100 Subject: ASoC: Update WM9081 for tdm_slot() API change Store the TDM slot width then if it's set use that rather than the sample size to calculate BCLK. Leave imposing constraints to the core (which should do this but doesn't yet) or machine driver. Also allow 0 TDM slots to be configure (for use when disabling TDM). Signed-off-by: Mark Brown --- sound/soc/codecs/wm9081.c | 60 +++++++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 25 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c index c147f2b8723..c64e55aa63b 100644 --- a/sound/soc/codecs/wm9081.c +++ b/sound/soc/codecs/wm9081.c @@ -165,6 +165,7 @@ struct wm9081_priv { int master; int fll_fref; int fll_fout; + int tdm_width; struct wm9081_retune_mobile_config *retune; }; @@ -981,33 +982,37 @@ static int wm9081_hw_params(struct snd_pcm_substream *substream, aif4 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_4); aif4 &= ~WM9081_LRCLK_RATE_MASK; - /* What BCLK do we need? */ wm9081->fs = params_rate(params); - wm9081->bclk = 2 * wm9081->fs; - switch (params_format(params)) { - case SNDRV_PCM_FORMAT_S16_LE: - wm9081->bclk *= 16; - break; - case SNDRV_PCM_FORMAT_S20_3LE: - wm9081->bclk *= 20; - aif2 |= 0x4; - break; - case SNDRV_PCM_FORMAT_S24_LE: - wm9081->bclk *= 24; - aif2 |= 0x8; - break; - case SNDRV_PCM_FORMAT_S32_LE: - wm9081->bclk *= 32; - aif2 |= 0xc; - break; - default: - return -EINVAL; - } - if (aif1 & WM9081_AIFDAC_TDM_MODE_MASK) { + if (wm9081->tdm_width) { + /* If TDM is set up then that fixes our BCLK. */ int slots = ((aif1 & WM9081_AIFDAC_TDM_MODE_MASK) >> WM9081_AIFDAC_TDM_MODE_SHIFT) + 1; - wm9081->bclk *= slots; + + wm9081->bclk = wm9081->fs * wm9081->tdm_width * slots; + } else { + /* Otherwise work out a BCLK from the sample size */ + wm9081->bclk = 2 * wm9081->fs; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + wm9081->bclk *= 16; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + wm9081->bclk *= 20; + aif2 |= 0x4; + break; + case SNDRV_PCM_FORMAT_S24_LE: + wm9081->bclk *= 24; + aif2 |= 0x8; + break; + case SNDRV_PCM_FORMAT_S32_LE: + wm9081->bclk *= 32; + aif2 |= 0xc; + break; + default: + return -EINVAL; + } } dev_dbg(codec->dev, "Target BCLK is %dHz\n", wm9081->bclk); @@ -1149,18 +1154,23 @@ static int wm9081_set_sysclk(struct snd_soc_dai *codec_dai, return 0; } -/* FIXME: Needs to handle slot_width */ static int wm9081_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, unsigned int rx_mask, int slots, int slot_width) { struct snd_soc_codec *codec = dai->codec; + struct wm9081_priv *wm9081 = codec->private_data; unsigned int aif1 = snd_soc_read(codec, WM9081_AUDIO_INTERFACE_1); aif1 &= ~(WM9081_AIFDAC_TDM_SLOT_MASK | WM9081_AIFDAC_TDM_MODE_MASK); - if (slots < 1 || slots > 4) + if (slots < 0 || slots > 4) return -EINVAL; + wm9081->tdm_width = slot_width; + + if (slots == 0) + slots = 1; + aif1 |= (slots - 1) << WM9081_AIFDAC_TDM_MODE_SHIFT; switch (rx_mask) { -- cgit v1.2.3 From d2a382143b35a486b8089861790d6b3ec4e46932 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 12 Aug 2009 14:30:33 +0100 Subject: ASoC: Update AD1938 for new TDM slot API It's only actually paying attention to the slot count anyway. Signed-off-by: Mark Brown --- sound/soc/codecs/ad1938.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/ad1938.c b/sound/soc/codecs/ad1938.c index aafda743fc8..2ffb1af152b 100644 --- a/sound/soc/codecs/ad1938.c +++ b/sound/soc/codecs/ad1938.c @@ -133,8 +133,8 @@ static inline int ad1938_pll_powerctrl(struct snd_soc_codec *codec, int cmd) return 0; } -static int ad1938_set_tdm_slot(struct snd_soc_dai *dai, - unsigned int mask, int slots) +static int ad1938_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask, + unsigned int mask, int slots, int width) { struct snd_soc_codec *codec = dai->codec; int dac_reg = codec->read(codec, AD1938_DAC_CTRL1); -- cgit v1.2.3 From c4ff357ada4fc7a73d899a496b636c698519b958 Mon Sep 17 00:00:00 2001 From: Barry Song <21cnbao@gmail.com> Date: Wed, 12 Aug 2009 11:34:25 +0800 Subject: ASoC: add output/input widgets in ad1938 to make dac/adc dynamic PM work According to the function dapm_dac_check_power() in sound/soc/soc-dapm.c, dac power can't be on/off stand-alone without any output widget as sink. And according to dapm_adc_check_power(), adc power can't be on/off stand-alone without any input widget as source. So we can't only define some stand-alone SND_SOC_DAPM_DAC/SND_SOC_DAPM_ADC to hope their power can be managed dynamically. Signed-off-by: Barry Song <21cnbao@gmail.com> Signed-off-by: Mark Brown --- sound/soc/codecs/ad1938.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/codecs/ad1938.c b/sound/soc/codecs/ad1938.c index 2ffb1af152b..051f68cc997 100644 --- a/sound/soc/codecs/ad1938.c +++ b/sound/soc/codecs/ad1938.c @@ -99,11 +99,23 @@ static const struct snd_soc_dapm_widget ad1938_dapm_widgets[] = { SND_SOC_DAPM_DAC("DAC", "Playback", AD1938_DAC_CTRL0, 0, 1), SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_SUPPLY("ADC_PWR", AD1938_ADC_CTRL0, 0, 1, NULL, 0), + SND_SOC_DAPM_OUTPUT("DAC1OUT"), + SND_SOC_DAPM_OUTPUT("DAC2OUT"), + SND_SOC_DAPM_OUTPUT("DAC3OUT"), + SND_SOC_DAPM_OUTPUT("DAC4OUT"), + SND_SOC_DAPM_INPUT("ADC1IN"), + SND_SOC_DAPM_INPUT("ADC2IN"), }; static const struct snd_soc_dapm_route audio_paths[] = { { "DAC", NULL, "ADC_PWR" }, { "ADC", NULL, "ADC_PWR" }, + { "DAC1OUT", "DAC1 Switch", "DAC" }, + { "DAC2OUT", "DAC2 Switch", "DAC" }, + { "DAC3OUT", "DAC3 Switch", "DAC" }, + { "DAC4OUT", "DAC4 Switch", "DAC" }, + { "ADC", "ADC1 Switch", "ADC1IN" }, + { "ADC", "ADC2 Switch", "ADC2IN" }, }; /* -- cgit v1.2.3 From 9008adf9a9c3a43ef237f6cd416857569beb0029 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Thu, 13 Aug 2009 15:59:34 +0300 Subject: ASoC: TWL4030: Introduce PGAs for outputs Dynamically control and control only the needed output amplifier muting/un-muting. The original code was muting and un-muting the following output amplifiers: Earpiece PreDrivL/R, CarkitL/R at the same time regardless which pin is actually in use at the given moment. Move these as separate PGA so only the needed amplifier will be touched. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 109 +++++++++++++++++++++++++-------------------- 1 file changed, 60 insertions(+), 49 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 818fb37bd7f..1a65004fa37 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -225,55 +225,11 @@ static void twl4030_codec_mute(struct snd_soc_codec *codec, int mute) return; if (mute) { - /* Bypass the reg_cache and mute the volumes - * Headset mute is done in it's own event handler - * Things to mute: Earpiece, PreDrivL/R, CarkitL/R - */ - reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_EAR_CTL); - twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, - reg_val & (~TWL4030_EAR_GAIN), - TWL4030_REG_EAR_CTL); - - reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PREDL_CTL); - twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, - reg_val & (~TWL4030_PREDL_GAIN), - TWL4030_REG_PREDL_CTL); - reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PREDR_CTL); - twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, - reg_val & (~TWL4030_PREDR_GAIN), - TWL4030_REG_PREDL_CTL); - - reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PRECKL_CTL); - twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, - reg_val & (~TWL4030_PRECKL_GAIN), - TWL4030_REG_PRECKL_CTL); - reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_PRECKR_CTL); - twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, - reg_val & (~TWL4030_PRECKR_GAIN), - TWL4030_REG_PRECKR_CTL); - /* Disable PLL */ reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_APLL_CTL); reg_val &= ~TWL4030_APLL_EN; twl4030_write(codec, TWL4030_REG_APLL_CTL, reg_val); } else { - /* Restore the volumes - * Headset mute is done in it's own event handler - * Things to restore: Earpiece, PreDrivL/R, CarkitL/R - */ - twl4030_write(codec, TWL4030_REG_EAR_CTL, - twl4030_read_reg_cache(codec, TWL4030_REG_EAR_CTL)); - - twl4030_write(codec, TWL4030_REG_PREDL_CTL, - twl4030_read_reg_cache(codec, TWL4030_REG_PREDL_CTL)); - twl4030_write(codec, TWL4030_REG_PREDR_CTL, - twl4030_read_reg_cache(codec, TWL4030_REG_PREDR_CTL)); - - twl4030_write(codec, TWL4030_REG_PRECKL_CTL, - twl4030_read_reg_cache(codec, TWL4030_REG_PRECKL_CTL)); - twl4030_write(codec, TWL4030_REG_PRECKR_CTL, - twl4030_read_reg_cache(codec, TWL4030_REG_PRECKR_CTL)); - /* Enable PLL */ reg_val = twl4030_read_reg_cache(codec, TWL4030_REG_APLL_CTL); reg_val |= TWL4030_APLL_EN; @@ -560,6 +516,41 @@ static int micpath_event(struct snd_soc_dapm_widget *w, return 0; } +/* + * Output PGA builder: + * Handle the muting and unmuting of the given output (turning off the + * amplifier associated with the output pin) + * On mute bypass the reg_cache and mute the volume + * On unmute: restore the register content + * Outputs handled in this way: Earpiece, PreDrivL/R, CarkitL/R + */ +#define TWL4030_OUTPUT_PGA(pin_name, reg, mask) \ +static int pin_name##pga_event(struct snd_soc_dapm_widget *w, \ + struct snd_kcontrol *kcontrol, int event) \ +{ \ + u8 reg_val; \ + \ + switch (event) { \ + case SND_SOC_DAPM_POST_PMU: \ + twl4030_write(w->codec, reg, \ + twl4030_read_reg_cache(w->codec, reg)); \ + break; \ + case SND_SOC_DAPM_POST_PMD: \ + reg_val = twl4030_read_reg_cache(w->codec, reg); \ + twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, \ + reg_val & (~mask), \ + reg); \ + break; \ + } \ + return 0; \ +} + +TWL4030_OUTPUT_PGA(earpiece, TWL4030_REG_EAR_CTL, TWL4030_EAR_GAIN); +TWL4030_OUTPUT_PGA(predrivel, TWL4030_REG_PREDL_CTL, TWL4030_PREDL_GAIN); +TWL4030_OUTPUT_PGA(predriver, TWL4030_REG_PREDR_CTL, TWL4030_PREDR_GAIN); +TWL4030_OUTPUT_PGA(carkitl, TWL4030_REG_PRECKL_CTL, TWL4030_PRECKL_GAIN); +TWL4030_OUTPUT_PGA(carkitr, TWL4030_REG_PRECKR_CTL, TWL4030_PRECKR_GAIN); + static void handsfree_ramp(struct snd_soc_codec *codec, int reg, int ramp) { unsigned char hs_ctl; @@ -1253,13 +1244,22 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_MIXER("Earpiece Mixer", SND_SOC_NOPM, 0, 0, &twl4030_dapm_earpiece_controls[0], ARRAY_SIZE(twl4030_dapm_earpiece_controls)), + SND_SOC_DAPM_PGA_E("Earpiece PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, earpiecepga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), /* PreDrivL/R */ SND_SOC_DAPM_MIXER("PredriveL Mixer", SND_SOC_NOPM, 0, 0, &twl4030_dapm_predrivel_controls[0], ARRAY_SIZE(twl4030_dapm_predrivel_controls)), + SND_SOC_DAPM_PGA_E("PredriveL PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, predrivelpga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MIXER("PredriveR Mixer", SND_SOC_NOPM, 0, 0, &twl4030_dapm_predriver_controls[0], ARRAY_SIZE(twl4030_dapm_predriver_controls)), + SND_SOC_DAPM_PGA_E("PredriveR PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, predriverpga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), /* HeadsetL/R */ SND_SOC_DAPM_MIXER("HeadsetL Mixer", SND_SOC_NOPM, 0, 0, &twl4030_dapm_hsol_controls[0], @@ -1277,9 +1277,15 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_MIXER("CarkitL Mixer", SND_SOC_NOPM, 0, 0, &twl4030_dapm_carkitl_controls[0], ARRAY_SIZE(twl4030_dapm_carkitl_controls)), + SND_SOC_DAPM_PGA_E("CarkitL PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, carkitlpga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_MIXER("CarkitR Mixer", SND_SOC_NOPM, 0, 0, &twl4030_dapm_carkitr_controls[0], ARRAY_SIZE(twl4030_dapm_carkitr_controls)), + SND_SOC_DAPM_PGA_E("CarkitR PGA", SND_SOC_NOPM, + 0, 0, NULL, 0, carkitrpga_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_POST_PMD), /* Output MUX controls */ /* HandsfreeL/R */ @@ -1371,16 +1377,19 @@ static const struct snd_soc_dapm_route intercon[] = { {"Earpiece Mixer", "AudioL1", "Analog L1 Playback Mixer"}, {"Earpiece Mixer", "AudioL2", "Analog L2 Playback Mixer"}, {"Earpiece Mixer", "AudioR1", "Analog R1 Playback Mixer"}, + {"Earpiece PGA", NULL, "Earpiece Mixer"}, /* PreDrivL */ {"PredriveL Mixer", "Voice", "Analog Voice Playback Mixer"}, {"PredriveL Mixer", "AudioL1", "Analog L1 Playback Mixer"}, {"PredriveL Mixer", "AudioL2", "Analog L2 Playback Mixer"}, {"PredriveL Mixer", "AudioR2", "Analog R2 Playback Mixer"}, + {"PredriveL PGA", NULL, "PredriveL Mixer"}, /* PreDrivR */ {"PredriveR Mixer", "Voice", "Analog Voice Playback Mixer"}, {"PredriveR Mixer", "AudioR1", "Analog R1 Playback Mixer"}, {"PredriveR Mixer", "AudioR2", "Analog R2 Playback Mixer"}, {"PredriveR Mixer", "AudioL2", "Analog L2 Playback Mixer"}, + {"PredriveR PGA", NULL, "PredriveR Mixer"}, /* HeadsetL */ {"HeadsetL Mixer", "Voice", "Analog Voice Playback Mixer"}, {"HeadsetL Mixer", "AudioL1", "Analog L1 Playback Mixer"}, @@ -1395,10 +1404,12 @@ static const struct snd_soc_dapm_route intercon[] = { {"CarkitL Mixer", "Voice", "Analog Voice Playback Mixer"}, {"CarkitL Mixer", "AudioL1", "Analog L1 Playback Mixer"}, {"CarkitL Mixer", "AudioL2", "Analog L2 Playback Mixer"}, + {"CarkitL PGA", NULL, "CarkitL Mixer"}, /* CarkitR */ {"CarkitR Mixer", "Voice", "Analog Voice Playback Mixer"}, {"CarkitR Mixer", "AudioR1", "Analog R1 Playback Mixer"}, {"CarkitR Mixer", "AudioR2", "Analog R2 Playback Mixer"}, + {"CarkitR PGA", NULL, "CarkitR Mixer"}, /* HandsfreeL */ {"HandsfreeL Mux", "Voice", "Analog Voice Playback Mixer"}, {"HandsfreeL Mux", "AudioL1", "Analog L1 Playback Mixer"}, @@ -1422,13 +1433,13 @@ static const struct snd_soc_dapm_route intercon[] = { /* outputs */ {"OUTL", NULL, "Analog L2 Playback Mixer"}, {"OUTR", NULL, "Analog R2 Playback Mixer"}, - {"EARPIECE", NULL, "Earpiece Mixer"}, - {"PREDRIVEL", NULL, "PredriveL Mixer"}, - {"PREDRIVER", NULL, "PredriveR Mixer"}, + {"EARPIECE", NULL, "Earpiece PGA"}, + {"PREDRIVEL", NULL, "PredriveL PGA"}, + {"PREDRIVER", NULL, "PredriveR PGA"}, {"HSOL", NULL, "HeadsetL PGA"}, {"HSOR", NULL, "HeadsetR PGA"}, - {"CARKITL", NULL, "CarkitL Mixer"}, - {"CARKITR", NULL, "CarkitR Mixer"}, + {"CARKITL", NULL, "CarkitL PGA"}, + {"CARKITR", NULL, "CarkitR PGA"}, {"HFL", NULL, "HandsfreeL PGA"}, {"HFR", NULL, "HandsfreeR PGA"}, {"Vibra Route", "Audio", "Vibra Mux"}, -- cgit v1.2.3 From 7eaae41ea54af5516138aef5fd33bc55598cbf48 Mon Sep 17 00:00:00 2001 From: Barry Song <21cnbao@gmail.com> Date: Thu, 13 Aug 2009 11:59:32 +0800 Subject: new ad1836 codec driver based on asoc There has been an ad1836 driver in sound/blackfin based on traditional alsa. The new driver is based on asoc. The architecture of ad1836 codec driver is very much like ad1938. Signed-off-by: Barry Song <21cnbao@gmail.com> Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 3 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/ad1836.c | 451 ++++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/ad1836.h | 64 +++++++ 4 files changed, 520 insertions(+) create mode 100644 sound/soc/codecs/ad1836.c create mode 100644 sound/soc/codecs/ad1836.h (limited to 'sound/soc') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 31a6d2111ed..05e227953cf 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -68,6 +68,9 @@ config SND_SOC_AC97_CODEC tristate select SND_AC97_CODEC +config SND_SOC_AD1836 + tristate + config SND_SOC_AD1938 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 78dce5d3fa2..9cbcf8f5b88 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -1,4 +1,5 @@ snd-soc-ac97-objs := ac97.o +snd-soc-ad1836-objs := ad1836.o snd-soc-ad1938-objs := ad1938.o snd-soc-ad1980-objs := ad1980.o snd-soc-ad73311-objs := ad73311.o @@ -45,6 +46,7 @@ snd-soc-wm9713-objs := wm9713.o snd-soc-max9877-objs := max9877.o obj-$(CONFIG_SND_SOC_AC97_CODEC) += snd-soc-ac97.o +obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o obj-$(CONFIG_SND_SOC_AD1938) += snd-soc-ad1938.o obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o diff --git a/sound/soc/codecs/ad1836.c b/sound/soc/codecs/ad1836.c new file mode 100644 index 00000000000..79b9d198a61 --- /dev/null +++ b/sound/soc/codecs/ad1836.c @@ -0,0 +1,451 @@ +/* + * File: sound/soc/codecs/ad1836.c + * Author: Barry Song + * + * Created: Aug 04 2009 + * Description: Driver for AD1836 sound chip + * + * Modified: + * Copyright 2009 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ad1836.h" + +/* codec private data */ +struct ad1836_priv { + struct snd_soc_codec codec; + u16 reg_cache[AD1836_NUM_REGS]; +}; + +static struct snd_soc_codec *ad1836_codec; +struct snd_soc_codec_device soc_codec_dev_ad1836; +static int ad1836_register(struct ad1836_priv *ad1836); +static void ad1836_unregister(struct ad1836_priv *ad1836); + +/* + * AD1836 volume/mute/de-emphasis etc. controls + */ +static const char *ad1836_deemp[] = {"None", "44.1kHz", "32kHz", "48kHz"}; + +static const struct soc_enum ad1836_deemp_enum = + SOC_ENUM_SINGLE(AD1836_DAC_CTRL1, 8, 4, ad1836_deemp); + +static const struct snd_kcontrol_new ad1836_snd_controls[] = { + /* DAC volume control */ + SOC_DOUBLE_R("DAC1 Volume", AD1836_DAC_L1_VOL, + AD1836_DAC_R1_VOL, 0, 0x3FF, 0), + SOC_DOUBLE_R("DAC2 Volume", AD1836_DAC_L2_VOL, + AD1836_DAC_R2_VOL, 0, 0x3FF, 0), + SOC_DOUBLE_R("DAC3 Volume", AD1836_DAC_L3_VOL, + AD1836_DAC_R3_VOL, 0, 0x3FF, 0), + + /* ADC switch control */ + SOC_DOUBLE("ADC1 Switch", AD1836_ADC_CTRL2, AD1836_ADCL1_MUTE, + AD1836_ADCR1_MUTE, 1, 1), + SOC_DOUBLE("ADC2 Switch", AD1836_ADC_CTRL2, AD1836_ADCL2_MUTE, + AD1836_ADCR2_MUTE, 1, 1), + + /* DAC switch control */ + SOC_DOUBLE("DAC1 Switch", AD1836_DAC_CTRL2, AD1836_DACL1_MUTE, + AD1836_DACR1_MUTE, 1, 1), + SOC_DOUBLE("DAC2 Switch", AD1836_DAC_CTRL2, AD1836_DACL2_MUTE, + AD1836_DACR2_MUTE, 1, 1), + SOC_DOUBLE("DAC3 Switch", AD1836_DAC_CTRL2, AD1836_DACL3_MUTE, + AD1836_DACR3_MUTE, 1, 1), + + /* ADC high-pass filter */ + SOC_SINGLE("ADC High Pass Filter Switch", AD1836_ADC_CTRL1, + AD1836_ADC_HIGHPASS_FILTER, 1, 0), + + /* DAC de-emphasis */ + SOC_ENUM("Playback Deemphasis", ad1836_deemp_enum), +}; + +static const struct snd_soc_dapm_widget ad1836_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", "Playback", AD1836_DAC_CTRL1, + AD1836_DAC_POWERDOWN, 1), + SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_SUPPLY("ADC_PWR", AD1836_ADC_CTRL1, + AD1836_ADC_POWERDOWN, 1, NULL, 0), + SND_SOC_DAPM_OUTPUT("DAC1OUT"), + SND_SOC_DAPM_OUTPUT("DAC2OUT"), + SND_SOC_DAPM_OUTPUT("DAC3OUT"), + SND_SOC_DAPM_INPUT("ADC1IN"), + SND_SOC_DAPM_INPUT("ADC2IN"), +}; + +static const struct snd_soc_dapm_route audio_paths[] = { + { "DAC", NULL, "ADC_PWR" }, + { "ADC", NULL, "ADC_PWR" }, + { "DAC1OUT", "DAC1 Switch", "DAC" }, + { "DAC2OUT", "DAC2 Switch", "DAC" }, + { "DAC3OUT", "DAC3 Switch", "DAC" }, + { "ADC", "ADC1 Switch", "ADC1IN" }, + { "ADC", "ADC2 Switch", "ADC2IN" }, +}; + +/* + * DAI ops entries + */ + +static int ad1836_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + /* at present, we support adc aux mode to interface with + * blackfin sport tdm mode + */ + case SND_SOC_DAIFMT_DSP_A: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_IB_IF: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + /* ALCLK,ABCLK are both output, AD1836 can only be master */ + case SND_SOC_DAIFMT_CBM_CFM: + break; + default: + return -EINVAL; + } + + return 0; +} + +static int ad1836_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + int word_len = 0; + + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_device *socdev = rtd->socdev; + struct snd_soc_codec *codec = socdev->card->codec; + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + word_len = 3; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + word_len = 1; + break; + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S32_LE: + word_len = 0; + break; + } + + snd_soc_update_bits(codec, AD1836_DAC_CTRL1, + AD1836_DAC_WORD_LEN_MASK, word_len); + + snd_soc_update_bits(codec, AD1836_ADC_CTRL2, + AD1836_ADC_WORD_LEN_MASK, word_len); + + return 0; +} + + +/* + * interface to read/write ad1836 register + */ +#define AD1836_SPI_REG_SHFT 12 +#define AD1836_SPI_READ (1 << 11) +#define AD1836_SPI_VAL_MSK 0x3FF + +/* + * write to the ad1836 register space + */ + +static int ad1836_write_reg(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u16 *reg_cache = codec->reg_cache; + int ret = 0; + + if (value != reg_cache[reg]) { + unsigned short buf; + struct spi_transfer t = { + .tx_buf = &buf, + .len = 2, + }; + struct spi_message m; + + buf = (reg << AD1836_SPI_REG_SHFT) | + (value & AD1836_SPI_VAL_MSK); + spi_message_init(&m); + spi_message_add_tail(&t, &m); + ret = spi_sync(codec->control_data, &m); + if (ret == 0) + reg_cache[reg] = value; + } + + return ret; +} + +/* + * read from the ad1836 register space cache + */ +static unsigned int ad1836_read_reg_cache(struct snd_soc_codec *codec, + unsigned int reg) +{ + u16 *reg_cache = codec->reg_cache; + + if (reg >= codec->reg_cache_size) + return -EINVAL; + + return reg_cache[reg]; +} + +static int __devinit ad1836_spi_probe(struct spi_device *spi) +{ + struct snd_soc_codec *codec; + struct ad1836_priv *ad1836; + + ad1836 = kzalloc(sizeof(struct ad1836_priv), GFP_KERNEL); + if (ad1836 == NULL) + return -ENOMEM; + + codec = &ad1836->codec; + codec->control_data = spi; + codec->dev = &spi->dev; + + dev_set_drvdata(&spi->dev, ad1836); + + return ad1836_register(ad1836); +} + +static int __devexit ad1836_spi_remove(struct spi_device *spi) +{ + struct ad1836_priv *ad1836 = dev_get_drvdata(&spi->dev); + + ad1836_unregister(ad1836); + return 0; +} + +static struct spi_driver ad1836_spi_driver = { + .driver = { + .name = "ad1836-spi", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = ad1836_spi_probe, + .remove = __devexit_p(ad1836_spi_remove), +}; + +static struct snd_soc_dai_ops ad1836_dai_ops = { + .hw_params = ad1836_hw_params, + .set_fmt = ad1836_set_dai_fmt, +}; + +/* codec DAI instance */ +struct snd_soc_dai ad1836_dai = { + .name = "AD1836", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 6, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 4, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, + }, + .ops = &ad1836_dai_ops, +}; +EXPORT_SYMBOL_GPL(ad1836_dai); + +static int ad1836_register(struct ad1836_priv *ad1836) +{ + int ret; + struct snd_soc_codec *codec = &ad1836->codec; + + if (ad1836_codec) { + dev_err(codec->dev, "Another ad1836 is registered\n"); + return -EINVAL; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + codec->private_data = ad1836; + codec->reg_cache = ad1836->reg_cache; + codec->reg_cache_size = AD1836_NUM_REGS; + codec->name = "AD1836"; + codec->owner = THIS_MODULE; + codec->dai = &ad1836_dai; + codec->num_dai = 1; + codec->write = ad1836_write_reg; + codec->read = ad1836_read_reg_cache; + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + ad1836_dai.dev = codec->dev; + ad1836_codec = codec; + + /* default setting for ad1836 */ + /* de-emphasis: 48kHz, power-on dac */ + codec->write(codec, AD1836_DAC_CTRL1, 0x300); + /* unmute dac channels */ + codec->write(codec, AD1836_DAC_CTRL2, 0x0); + /* high-pass filter enable, power-on adc */ + codec->write(codec, AD1836_ADC_CTRL1, 0x100); + /* unmute adc channles, adc aux mode */ + codec->write(codec, AD1836_ADC_CTRL2, 0x180); + /* left/right diff:PGA/MUX */ + codec->write(codec, AD1836_ADC_CTRL3, 0x3A); + /* volume */ + codec->write(codec, AD1836_DAC_L1_VOL, 0x3FF); + codec->write(codec, AD1836_DAC_R1_VOL, 0x3FF); + codec->write(codec, AD1836_DAC_L2_VOL, 0x3FF); + codec->write(codec, AD1836_DAC_R2_VOL, 0x3FF); + codec->write(codec, AD1836_DAC_L3_VOL, 0x3FF); + codec->write(codec, AD1836_DAC_R3_VOL, 0x3FF); + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + kfree(ad1836); + return ret; + } + + ret = snd_soc_register_dai(&ad1836_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + snd_soc_unregister_codec(codec); + kfree(ad1836); + return ret; + } + + return 0; +} + +static void ad1836_unregister(struct ad1836_priv *ad1836) +{ + snd_soc_unregister_dai(&ad1836_dai); + snd_soc_unregister_codec(&ad1836->codec); + kfree(ad1836); + ad1836_codec = NULL; +} + +static int ad1836_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (ad1836_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = ad1836_codec; + codec = ad1836_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + snd_soc_add_controls(codec, ad1836_snd_controls, + ARRAY_SIZE(ad1836_snd_controls)); + snd_soc_dapm_new_controls(codec, ad1836_dapm_widgets, + ARRAY_SIZE(ad1836_dapm_widgets)); + snd_soc_dapm_add_routes(codec, audio_paths, ARRAY_SIZE(audio_paths)); + snd_soc_dapm_new_widgets(codec); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card: %d\n", ret); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + return ret; +} + +/* power down chip */ +static int ad1836_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ad1836 = { + .probe = ad1836_probe, + .remove = ad1836_remove, + /* The power management of ad1836 is very simple. There are + * only adc&dac 2 components to control. Dapm handles them. + */ + .suspend = NULL, + .resume = NULL, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ad1836); + +static int __init ad1836_init(void) +{ + int ret; + + ret = spi_register_driver(&ad1836_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register ad1836 SPI driver: %d\n", + ret); + } + + return ret; +} +module_init(ad1836_init); + +static void __exit ad1836_exit(void) +{ + spi_unregister_driver(&ad1836_spi_driver); +} +module_exit(ad1836_exit); + +MODULE_DESCRIPTION("ASoC ad1836 driver"); +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ad1836.h b/sound/soc/codecs/ad1836.h new file mode 100644 index 00000000000..7660ee6973c --- /dev/null +++ b/sound/soc/codecs/ad1836.h @@ -0,0 +1,64 @@ +/* + * File: sound/soc/codecs/ad1836.h + * Based on: + * Author: Barry Song + * + * Created: Aug 04, 2009 + * Description: definitions for AD1836 registers + * + * Modified: + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#ifndef __AD1836_H__ +#define __AD1836_H__ + +#define AD1836_DAC_CTRL1 0 +#define AD1836_DAC_POWERDOWN 2 +#define AD1836_DAC_SERFMT_MASK 0xE0 +#define AD1836_DAC_SERFMT_PCK256 (0x4 << 5) +#define AD1836_DAC_SERFMT_PCK128 (0x5 << 5) +#define AD1836_DAC_WORD_LEN_MASK 0x18 + +#define AD1836_DAC_CTRL2 1 +#define AD1836_DACL1_MUTE 0 +#define AD1836_DACR1_MUTE 1 +#define AD1836_DACL2_MUTE 2 +#define AD1836_DACR2_MUTE 3 +#define AD1836_DACL3_MUTE 4 +#define AD1836_DACR3_MUTE 5 + +#define AD1836_DAC_L1_VOL 2 +#define AD1836_DAC_R1_VOL 3 +#define AD1836_DAC_L2_VOL 4 +#define AD1836_DAC_R2_VOL 5 +#define AD1836_DAC_L3_VOL 6 +#define AD1836_DAC_R3_VOL 7 + +#define AD1836_ADC_CTRL1 12 +#define AD1836_ADC_POWERDOWN 7 +#define AD1836_ADC_HIGHPASS_FILTER 8 + +#define AD1836_ADC_CTRL2 13 +#define AD1836_ADCL1_MUTE 0 +#define AD1836_ADCR1_MUTE 1 +#define AD1836_ADCL2_MUTE 2 +#define AD1836_ADCR2_MUTE 3 +#define AD1836_ADC_WORD_LEN_MASK 0x30 +#define AD1836_ADC_SERFMT_MASK (7 << 6) +#define AD1836_ADC_SERFMT_PCK256 (0x4 << 6) +#define AD1836_ADC_SERFMT_PCK128 (0x5 << 6) + +#define AD1836_ADC_CTRL3 14 + +#define AD1836_NUM_REGS 16 + +extern struct snd_soc_dai ad1836_dai; +extern struct snd_soc_codec_device soc_codec_dev_ad1836; +#endif -- cgit v1.2.3 From e9ade7f933d629698cbe5f09f1e5511269180fea Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Thu, 13 Aug 2009 15:19:42 +0100 Subject: ASoC: Minor cleanups to AD1938 driver - Build in SND_SOC_ALL_CODECS. - Remove null suspend/resume stuff. Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 1 + sound/soc/codecs/ad1836.c | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 05e227953cf..dd9b2956ebc 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -12,6 +12,7 @@ config SND_SOC_ALL_CODECS tristate "Build all ASoC CODEC drivers" select SND_SOC_L3 select SND_SOC_AC97_CODEC if SND_SOC_AC97_BUS + select SND_SOC_AD1836 if SPI_MASTER select SND_SOC_AD1938 if SPI_MASTER select SND_SOC_AD1980 if SND_SOC_AC97_BUS select SND_SOC_AD73311 if I2C diff --git a/sound/soc/codecs/ad1836.c b/sound/soc/codecs/ad1836.c index 79b9d198a61..3612bb92df9 100644 --- a/sound/soc/codecs/ad1836.c +++ b/sound/soc/codecs/ad1836.c @@ -418,11 +418,6 @@ static int ad1836_remove(struct platform_device *pdev) struct snd_soc_codec_device soc_codec_dev_ad1836 = { .probe = ad1836_probe, .remove = ad1836_remove, - /* The power management of ad1836 is very simple. There are - * only adc&dac 2 components to control. Dapm handles them. - */ - .suspend = NULL, - .resume = NULL, }; EXPORT_SYMBOL_GPL(soc_codec_dev_ad1836); -- cgit v1.2.3 From a2342ae325bb1f65d90a9f38baab4c9762018a5e Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Wed, 29 Jul 2009 21:21:49 +0100 Subject: ASoC: Factor out shared code from WM8993 The WM8993 analogue control is shared with other devices in the same product line. Since this is a very substantial proportion of the driver move the definitions of these controls into a new wm_hubs module which allows them to be shared between the two. Signed-off-by: Mark Brown --- sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/wm8993.c | 716 ++---------------------------------------- sound/soc/codecs/wm_hubs.c | 758 +++++++++++++++++++++++++++++++++++++++++++++ sound/soc/codecs/wm_hubs.h | 24 ++ 5 files changed, 810 insertions(+), 694 deletions(-) create mode 100644 sound/soc/codecs/wm_hubs.c create mode 100644 sound/soc/codecs/wm_hubs.h (limited to 'sound/soc') diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index dd9b2956ebc..910b9162cbb 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -64,6 +64,10 @@ config SND_SOC_ALL_CODECS If unsure select "N". +config SND_SOC_WM_HUBS + tristate + default y if SND_SOC_WM8993=y + default m if SND_SOC_WM8993=m config SND_SOC_AC97_CODEC tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 9cbcf8f5b88..c7fd2297a06 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -41,6 +41,7 @@ snd-soc-wm9081-objs := wm9081.o snd-soc-wm9705-objs := wm9705.o snd-soc-wm9712-objs := wm9712.o snd-soc-wm9713-objs := wm9713.o +snd-soc-wm-hubs-objs := wm_hubs.o # Amp snd-soc-max9877-objs := max9877.o @@ -88,6 +89,7 @@ obj-$(CONFIG_SND_SOC_WM9081) += snd-soc-wm9081.o obj-$(CONFIG_SND_SOC_WM9705) += snd-soc-wm9705.o obj-$(CONFIG_SND_SOC_WM9712) += snd-soc-wm9712.o obj-$(CONFIG_SND_SOC_WM9713) += snd-soc-wm9713.o +obj-$(CONFIG_SND_SOC_WM_HUBS) += snd-soc-wm-hubs.o # Amp obj-$(CONFIG_SND_SOC_MAX9877) += snd-soc-max9877.o diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c index e246ca07989..cd156693113 100644 --- a/sound/soc/codecs/wm8993.c +++ b/sound/soc/codecs/wm8993.c @@ -27,6 +27,7 @@ #include #include "wm8993.h" +#include "wm_hubs.h" static u16 wm8993_reg_defaults[WM8993_REGISTER_COUNT] = { 0x8993, /* R0 - Software Reset */ @@ -557,28 +558,6 @@ static int configure_clock(struct snd_soc_codec *codec) return 0; } -static void wait_for_dc_servo(struct snd_soc_codec *codec, int mask) -{ - unsigned int reg; - int count = 0; - - dev_dbg(codec->dev, "Waiting for DC servo...\n"); - do { - count++; - msleep(1); - reg = wm8993_read(codec, WM8993_DC_SERVO_READBACK_0); - dev_dbg(codec->dev, "DC servo status: %x\n", reg); - } while ((reg & WM8993_DCS_CAL_COMPLETE_MASK) - != WM8993_DCS_CAL_COMPLETE_MASK && count < 1000); - - if ((reg & WM8993_DCS_CAL_COMPLETE_MASK) - != WM8993_DCS_CAL_COMPLETE_MASK) - dev_err(codec->dev, "Timed out waiting for DC Servo\n"); -} - -static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1650, 150, 0); -static const DECLARE_TLV_DB_SCALE(inmix_sw_tlv, 0, 3000, 0); -static const DECLARE_TLV_DB_SCALE(inmix_tlv, -1500, 300, 1); static const DECLARE_TLV_DB_SCALE(sidetone_tlv, -3600, 300, 0); static const DECLARE_TLV_DB_SCALE(drc_comp_threash, -4500, 75, 0); static const DECLARE_TLV_DB_SCALE(drc_comp_amp, -2250, 75, 0); @@ -593,33 +572,6 @@ static const DECLARE_TLV_DB_SCALE(drc_startup_tlv, -1800, 300, 0); static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1); static const DECLARE_TLV_DB_SCALE(dac_boost_tlv, 0, 600, 0); -static const DECLARE_TLV_DB_SCALE(earpiece_tlv, -600, 600, 0); -static const DECLARE_TLV_DB_SCALE(outmix_tlv, -2100, 300, 0); -static const DECLARE_TLV_DB_SCALE(spkmix_tlv, -300, 300, 0); -static const DECLARE_TLV_DB_SCALE(spkmixout_tlv, -1800, 600, 1); -static const DECLARE_TLV_DB_SCALE(outpga_tlv, -5700, 100, 0); -static const unsigned int spkboost_tlv[] = { - TLV_DB_RANGE_HEAD(7), - 0, 6, TLV_DB_SCALE_ITEM(0, 150, 0), - 7, 7, TLV_DB_SCALE_ITEM(1200, 0, 0), -}; -static const DECLARE_TLV_DB_SCALE(line_tlv, -600, 600, 0); - -static const char *speaker_ref_text[] = { - "SPKVDD/2", - "VMID", -}; - -static const struct soc_enum speaker_ref = - SOC_ENUM_SINGLE(WM8993_SPEAKER_MIXER, 8, 2, speaker_ref_text); - -static const char *speaker_mode_text[] = { - "Class D", - "Class AB", -}; - -static const struct soc_enum speaker_mode = - SOC_ENUM_SINGLE(WM8993_SPKMIXR_ATTENUATION, 8, 2, speaker_mode_text); static const char *dac_deemph_text[] = { "None", @@ -731,73 +683,7 @@ static const char *drc_smooth_text[] = { static const struct soc_enum drc_smooth = SOC_ENUM_SINGLE(WM8993_DRC_CONTROL_1, 4, 3, drc_smooth_text); - -/* - * Update the DC servo calibration on gain changes - */ -static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) -{ - struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); - int ret; - - ret = snd_soc_put_volsw_2r(kcontrol, ucontrol); - - /* Only need to do this if the outputs are active */ - if (wm8993_read(codec, WM8993_POWER_MANAGEMENT_1) - & (WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA)) - snd_soc_update_bits(codec, - WM8993_DC_SERVO_0, - WM8993_DCS_TRIG_SINGLE_0 | - WM8993_DCS_TRIG_SINGLE_1, - WM8993_DCS_TRIG_SINGLE_0 | - WM8993_DCS_TRIG_SINGLE_1); - - return ret; -} - static const struct snd_kcontrol_new wm8993_snd_controls[] = { -SOC_SINGLE_TLV("IN1L Volume", WM8993_LEFT_LINE_INPUT_1_2_VOLUME, 0, 31, 0, - inpga_tlv), -SOC_SINGLE("IN1L Switch", WM8993_LEFT_LINE_INPUT_1_2_VOLUME, 7, 1, 1), -SOC_SINGLE("IN1L ZC Switch", WM8993_LEFT_LINE_INPUT_1_2_VOLUME, 7, 1, 0), - -SOC_SINGLE_TLV("IN1R Volume", WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, 0, 31, 0, - inpga_tlv), -SOC_SINGLE("IN1R Switch", WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, 7, 1, 1), -SOC_SINGLE("IN1R ZC Switch", WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, 7, 1, 0), - - -SOC_SINGLE_TLV("IN2L Volume", WM8993_LEFT_LINE_INPUT_3_4_VOLUME, 0, 31, 0, - inpga_tlv), -SOC_SINGLE("IN2L Switch", WM8993_LEFT_LINE_INPUT_3_4_VOLUME, 7, 1, 1), -SOC_SINGLE("IN2L ZC Switch", WM8993_LEFT_LINE_INPUT_3_4_VOLUME, 7, 1, 0), - -SOC_SINGLE_TLV("IN2R Volume", WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, 0, 31, 0, - inpga_tlv), -SOC_SINGLE("IN2R Switch", WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, 7, 1, 1), -SOC_SINGLE("IN2R ZC Switch", WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, 7, 1, 0), - -SOC_SINGLE_TLV("MIXINL IN2L Volume", WM8993_INPUT_MIXER3, 7, 1, 0, - inmix_sw_tlv), -SOC_SINGLE_TLV("MIXINL IN1L Volume", WM8993_INPUT_MIXER3, 4, 1, 0, - inmix_sw_tlv), -SOC_SINGLE_TLV("MIXINL Output Record Volume", WM8993_INPUT_MIXER3, 0, 7, 0, - inmix_tlv), -SOC_SINGLE_TLV("MIXINL IN1LP Volume", WM8993_INPUT_MIXER5, 6, 7, 0, inmix_tlv), -SOC_SINGLE_TLV("MIXINL Direct Voice Volume", WM8993_INPUT_MIXER5, 0, 6, 0, - inmix_tlv), - -SOC_SINGLE_TLV("MIXINR IN2R Volume", WM8993_INPUT_MIXER4, 7, 1, 0, - inmix_sw_tlv), -SOC_SINGLE_TLV("MIXINR IN1R Volume", WM8993_INPUT_MIXER4, 4, 1, 0, - inmix_sw_tlv), -SOC_SINGLE_TLV("MIXINR Output Record Volume", WM8993_INPUT_MIXER4, 0, 7, 0, - inmix_tlv), -SOC_SINGLE_TLV("MIXINR IN1RP Volume", WM8993_INPUT_MIXER6, 6, 7, 0, inmix_tlv), -SOC_SINGLE_TLV("MIXINR Direct Voice Volume", WM8993_INPUT_MIXER6, 0, 6, 0, - inmix_tlv), - SOC_DOUBLE_TLV("Digital Sidetone Volume", WM8993_DIGITAL_SIDE_TONE, 5, 9, 12, 0, sidetone_tlv), @@ -840,112 +726,11 @@ SOC_SINGLE_TLV("Playback Boost Volume", WM8993_AUDIO_INTERFACE_2, 10, 3, 0, dac_boost_tlv), SOC_ENUM("DAC Deemphasis", dac_deemph), -SOC_SINGLE_TLV("Left Output Mixer IN2RN Volume", WM8993_OUTPUT_MIXER5, 6, 7, 1, - outmix_tlv), -SOC_SINGLE_TLV("Left Output Mixer IN2LN Volume", WM8993_OUTPUT_MIXER3, 6, 7, 1, - outmix_tlv), -SOC_SINGLE_TLV("Left Output Mixer IN2LP Volume", WM8993_OUTPUT_MIXER3, 9, 7, 1, - outmix_tlv), -SOC_SINGLE_TLV("Left Output Mixer IN1L Volume", WM8993_OUTPUT_MIXER3, 0, 7, 1, - outmix_tlv), -SOC_SINGLE_TLV("Left Output Mixer IN1R Volume", WM8993_OUTPUT_MIXER3, 3, 7, 1, - outmix_tlv), -SOC_SINGLE_TLV("Left Output Mixer Right Input Volume", - WM8993_OUTPUT_MIXER5, 3, 7, 1, outmix_tlv), -SOC_SINGLE_TLV("Left Output Mixer Left Input Volume", - WM8993_OUTPUT_MIXER5, 0, 7, 1, outmix_tlv), -SOC_SINGLE_TLV("Left Output Mixer DAC Volume", WM8993_OUTPUT_MIXER5, 9, 7, 1, - outmix_tlv), - -SOC_SINGLE_TLV("Right Output Mixer IN2LN Volume", - WM8993_OUTPUT_MIXER6, 6, 7, 1, outmix_tlv), -SOC_SINGLE_TLV("Right Output Mixer IN2RN Volume", - WM8993_OUTPUT_MIXER4, 6, 7, 1, outmix_tlv), -SOC_SINGLE_TLV("Right Output Mixer IN1L Volume", - WM8993_OUTPUT_MIXER4, 3, 7, 1, outmix_tlv), -SOC_SINGLE_TLV("Right Output Mixer IN1R Volume", - WM8993_OUTPUT_MIXER4, 0, 7, 1, outmix_tlv), -SOC_SINGLE_TLV("Right Output Mixer IN2RP Volume", - WM8993_OUTPUT_MIXER4, 9, 7, 1, outmix_tlv), -SOC_SINGLE_TLV("Right Output Mixer Left Input Volume", - WM8993_OUTPUT_MIXER6, 3, 7, 1, outmix_tlv), -SOC_SINGLE_TLV("Right Output Mixer Right Input Volume", - WM8993_OUTPUT_MIXER6, 6, 7, 1, outmix_tlv), -SOC_SINGLE_TLV("Right Output Mixer DAC Volume", - WM8993_OUTPUT_MIXER6, 9, 7, 1, outmix_tlv), - -SOC_DOUBLE_R_TLV("Output Volume", WM8993_LEFT_OPGA_VOLUME, - WM8993_RIGHT_OPGA_VOLUME, 0, 63, 0, outpga_tlv), -SOC_DOUBLE_R("Output Switch", WM8993_LEFT_OPGA_VOLUME, - WM8993_RIGHT_OPGA_VOLUME, 6, 1, 0), -SOC_DOUBLE_R("Output ZC Switch", WM8993_LEFT_OPGA_VOLUME, - WM8993_RIGHT_OPGA_VOLUME, 7, 1, 0), - -SOC_SINGLE("Earpiece Switch", WM8993_HPOUT2_VOLUME, 5, 1, 1), -SOC_SINGLE_TLV("Earpiece Volume", WM8993_HPOUT2_VOLUME, 4, 1, 1, earpiece_tlv), - -SOC_SINGLE_TLV("SPKL Input Volume", WM8993_SPKMIXL_ATTENUATION, - 5, 1, 1, spkmix_tlv), -SOC_SINGLE_TLV("SPKL IN1LP Volume", WM8993_SPKMIXL_ATTENUATION, - 4, 1, 1, spkmix_tlv), -SOC_SINGLE_TLV("SPKL Output Volume", WM8993_SPKMIXL_ATTENUATION, - 3, 1, 1, spkmix_tlv), SOC_SINGLE_TLV("SPKL DAC Volume", WM8993_SPKMIXL_ATTENUATION, - 2, 1, 1, spkmix_tlv), - -SOC_SINGLE_TLV("SPKR Input Volume", WM8993_SPKMIXR_ATTENUATION, - 5, 1, 1, spkmix_tlv), -SOC_SINGLE_TLV("SPKR IN1RP Volume", WM8993_SPKMIXR_ATTENUATION, - 4, 1, 1, spkmix_tlv), -SOC_SINGLE_TLV("SPKR Output Volume", WM8993_SPKMIXR_ATTENUATION, - 3, 1, 1, spkmix_tlv), -SOC_SINGLE_TLV("SPKR DAC Volume", WM8993_SPKMIXR_ATTENUATION, - 2, 1, 1, spkmix_tlv), - -SOC_DOUBLE_R_TLV("Speaker Mixer Volume", - WM8993_SPKMIXL_ATTENUATION, WM8993_SPKMIXR_ATTENUATION, - 0, 3, 1, spkmixout_tlv), -SOC_DOUBLE_R_TLV("Speaker Volume", - WM8993_SPEAKER_VOLUME_LEFT, WM8993_SPEAKER_VOLUME_RIGHT, - 0, 63, 0, outpga_tlv), -SOC_DOUBLE_R("Speaker Switch", - WM8993_SPEAKER_VOLUME_LEFT, WM8993_SPEAKER_VOLUME_RIGHT, - 6, 1, 0), -SOC_DOUBLE_R("Speaker ZC Switch", - WM8993_SPEAKER_VOLUME_LEFT, WM8993_SPEAKER_VOLUME_RIGHT, - 7, 1, 0), -SOC_DOUBLE_TLV("Speaker Boost Volume", WM8993_SPKOUT_BOOST, 0, 3, 7, 0, - spkboost_tlv), -SOC_ENUM("Speaker Reference", speaker_ref), -SOC_ENUM("Speaker Mode", speaker_mode), + 2, 1, 1, wm_hubs_spkmix_tlv), -{ - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Headphone Volume", - .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | - SNDRV_CTL_ELEM_ACCESS_READWRITE, - .tlv.p = outpga_tlv, - .info = snd_soc_info_volsw_2r, - .get = snd_soc_get_volsw_2r, .put = wm8993_put_dc_servo, - .private_value = (unsigned long)&(struct soc_mixer_control) { - .reg = WM8993_LEFT_OUTPUT_VOLUME, - .rreg = WM8993_RIGHT_OUTPUT_VOLUME, - .shift = 0, .max = 63 - }, -}, -SOC_DOUBLE_R("Headphone Switch", WM8993_LEFT_OUTPUT_VOLUME, - WM8993_RIGHT_OUTPUT_VOLUME, 6, 1, 0), -SOC_DOUBLE_R("Headphone ZC Switch", WM8993_LEFT_OUTPUT_VOLUME, - WM8993_RIGHT_OUTPUT_VOLUME, 7, 1, 0), - -SOC_SINGLE("LINEOUT1N Switch", WM8993_LINE_OUTPUTS_VOLUME, 6, 1, 1), -SOC_SINGLE("LINEOUT1P Switch", WM8993_LINE_OUTPUTS_VOLUME, 5, 1, 1), -SOC_SINGLE_TLV("LINEOUT1 Volume", WM8993_LINE_OUTPUTS_VOLUME, 4, 1, 1, - line_tlv), - -SOC_SINGLE("LINEOUT2N Switch", WM8993_LINE_OUTPUTS_VOLUME, 2, 1, 1), -SOC_SINGLE("LINEOUT2P Switch", WM8993_LINE_OUTPUTS_VOLUME, 1, 1, 1), -SOC_SINGLE_TLV("LINEOUT2 Volume", WM8993_LINE_OUTPUTS_VOLUME, 0, 1, 1, - line_tlv), +SOC_SINGLE_TLV("SPKR DAC Volume", WM8993_SPKMIXR_ATTENUATION, + 2, 1, 1, wm_hubs_spkmix_tlv), }; static const struct snd_kcontrol_new wm8993_eq_controls[] = { @@ -956,31 +741,6 @@ SOC_SINGLE_TLV("EQ4 Volume", WM8993_EQ5, 0, 24, 0, eq_tlv), SOC_SINGLE_TLV("EQ5 Volume", WM8993_EQ6, 0, 24, 0, eq_tlv), }; -static int wm8993_earpiece_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *control, int event) -{ - struct snd_soc_codec *codec = w->codec; - u16 reg = wm8993_read(codec, WM8993_ANTIPOP1) & ~WM8993_HPOUT2_IN_ENA; - - switch (event) { - case SND_SOC_DAPM_PRE_PMU: - reg |= WM8993_HPOUT2_IN_ENA; - wm8993_write(codec, WM8993_ANTIPOP1, reg); - udelay(50); - break; - - case SND_SOC_DAPM_POST_PMD: - wm8993_write(codec, WM8993_ANTIPOP1, reg); - break; - - default: - BUG(); - break; - } - - return 0; -} - static int clk_sys_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { @@ -1006,8 +766,8 @@ static int clk_sys_event(struct snd_soc_dapm_widget *w, * Currently the only supported paths are the direct DAC->headphone * paths (which provide minimum power consumption anyway). */ -static int wm8993_class_w_put(struct snd_kcontrol *kcontrol, - struct snd_ctl_elem_value *ucontrol) +static int class_w_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); struct snd_soc_codec *codec = widget->codec; @@ -1052,160 +812,9 @@ static int wm8993_class_w_put(struct snd_kcontrol *kcontrol, { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ .info = snd_soc_info_enum_double, \ .get = snd_soc_dapm_get_enum_double, \ - .put = wm8993_class_w_put, \ + .put = class_w_put, \ .private_value = (unsigned long)&xenum } -static int hp_event(struct snd_soc_dapm_widget *w, - struct snd_kcontrol *kcontrol, int event) -{ - struct snd_soc_codec *codec = w->codec; - unsigned int reg = wm8993_read(codec, WM8993_ANALOGUE_HP_0); - - switch (event) { - case SND_SOC_DAPM_POST_PMU: - snd_soc_update_bits(codec, WM8993_CHARGE_PUMP_1, - WM8993_CP_ENA, WM8993_CP_ENA); - - msleep(5); - - snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1, - WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA, - WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA); - - reg |= WM8993_HPOUT1L_DLY | WM8993_HPOUT1R_DLY; - wm8993_write(codec, WM8993_ANALOGUE_HP_0, reg); - - /* Start the DC servo */ - snd_soc_update_bits(codec, WM8993_DC_SERVO_0, - WM8993_DCS_ENA_CHAN_0 | - WM8993_DCS_ENA_CHAN_1 | - WM8993_DCS_TRIG_STARTUP_1 | - WM8993_DCS_TRIG_STARTUP_0, - WM8993_DCS_ENA_CHAN_0 | - WM8993_DCS_ENA_CHAN_1 | - WM8993_DCS_TRIG_STARTUP_1 | - WM8993_DCS_TRIG_STARTUP_0); - wait_for_dc_servo(codec, WM8993_DCS_TRIG_STARTUP_0 | - WM8993_DCS_TRIG_STARTUP_1); - snd_soc_update_bits(codec, WM8993_DC_SERVO_1, - WM8993_DCS_TIMER_PERIOD_01_MASK, 0xa); - - reg |= WM8993_HPOUT1R_OUTP | WM8993_HPOUT1R_RMV_SHORT | - WM8993_HPOUT1L_OUTP | WM8993_HPOUT1L_RMV_SHORT; - wm8993_write(codec, WM8993_ANALOGUE_HP_0, reg); - break; - - case SND_SOC_DAPM_PRE_PMD: - reg &= ~(WM8993_HPOUT1L_RMV_SHORT | - WM8993_HPOUT1L_DLY | - WM8993_HPOUT1L_OUTP | - WM8993_HPOUT1R_RMV_SHORT | - WM8993_HPOUT1R_DLY | - WM8993_HPOUT1R_OUTP); - - snd_soc_update_bits(codec, WM8993_DC_SERVO_1, - WM8993_DCS_TIMER_PERIOD_01_MASK, 0); - snd_soc_update_bits(codec, WM8993_DC_SERVO_0, - WM8993_DCS_ENA_CHAN_0 | - WM8993_DCS_ENA_CHAN_1, 0); - - wm8993_write(codec, WM8993_ANALOGUE_HP_0, reg); - snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1, - WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA, - 0); - - snd_soc_update_bits(codec, WM8993_CHARGE_PUMP_1, - WM8993_CP_ENA, 0); - break; - } - - return 0; -} - -static const struct snd_kcontrol_new in1l_pga[] = { -SOC_DAPM_SINGLE("IN1LP Switch", WM8993_INPUT_MIXER2, 5, 1, 0), -SOC_DAPM_SINGLE("IN1LN Switch", WM8993_INPUT_MIXER2, 4, 1, 0), -}; - -static const struct snd_kcontrol_new in1r_pga[] = { -SOC_DAPM_SINGLE("IN1RP Switch", WM8993_INPUT_MIXER2, 1, 1, 0), -SOC_DAPM_SINGLE("IN1RN Switch", WM8993_INPUT_MIXER2, 0, 1, 0), -}; - -static const struct snd_kcontrol_new in2l_pga[] = { -SOC_DAPM_SINGLE("IN2LP Switch", WM8993_INPUT_MIXER2, 7, 1, 0), -SOC_DAPM_SINGLE("IN2LN Switch", WM8993_INPUT_MIXER2, 6, 1, 0), -}; - -static const struct snd_kcontrol_new in2r_pga[] = { -SOC_DAPM_SINGLE("IN2RP Switch", WM8993_INPUT_MIXER2, 3, 1, 0), -SOC_DAPM_SINGLE("IN2RN Switch", WM8993_INPUT_MIXER2, 2, 1, 0), -}; - -static const struct snd_kcontrol_new mixinl[] = { -SOC_DAPM_SINGLE("IN2L Switch", WM8993_INPUT_MIXER3, 8, 1, 0), -SOC_DAPM_SINGLE("IN1L Switch", WM8993_INPUT_MIXER3, 5, 1, 0), -}; - -static const struct snd_kcontrol_new mixinr[] = { -SOC_DAPM_SINGLE("IN2R Switch", WM8993_INPUT_MIXER4, 8, 1, 0), -SOC_DAPM_SINGLE("IN1R Switch", WM8993_INPUT_MIXER4, 5, 1, 0), -}; - -static const struct snd_kcontrol_new left_output_mixer[] = { -SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER1, 7, 1, 0), -SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER1, 6, 1, 0), -SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER1, 5, 1, 0), -SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER1, 4, 1, 0), -SOC_DAPM_SINGLE("IN2LP Switch", WM8993_OUTPUT_MIXER1, 1, 1, 0), -SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER1, 3, 1, 0), -SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER1, 2, 1, 0), -SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER1, 0, 1, 0), -}; - -static const struct snd_kcontrol_new right_output_mixer[] = { -SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER2, 7, 1, 0), -SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER2, 6, 1, 0), -SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER2, 5, 1, 0), -SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER2, 4, 1, 0), -SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER2, 3, 1, 0), -SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER2, 2, 1, 0), -SOC_DAPM_SINGLE("IN2RP Switch", WM8993_OUTPUT_MIXER2, 1, 1, 0), -SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER2, 0, 1, 0), -}; - -static const struct snd_kcontrol_new earpiece_mixer[] = { -SOC_DAPM_SINGLE("Direct Voice Switch", WM8993_HPOUT2_MIXER, 5, 1, 0), -SOC_DAPM_SINGLE("Left Output Switch", WM8993_HPOUT2_MIXER, 4, 1, 0), -SOC_DAPM_SINGLE("Right Output Switch", WM8993_HPOUT2_MIXER, 3, 1, 0), -}; - -static const struct snd_kcontrol_new left_speaker_mixer[] = { -SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 7, 1, 0), -SOC_DAPM_SINGLE("IN1LP Switch", WM8993_SPEAKER_MIXER, 5, 1, 0), -SOC_DAPM_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 3, 1, 0), -SOC_DAPM_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 6, 1, 0), -}; - -static const struct snd_kcontrol_new right_speaker_mixer[] = { -SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 6, 1, 0), -SOC_DAPM_SINGLE("IN1RP Switch", WM8993_SPEAKER_MIXER, 4, 1, 0), -SOC_DAPM_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 2, 1, 0), -SOC_DAPM_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 0, 1, 0), -}; - -static const struct snd_kcontrol_new left_speaker_boost[] = { -SOC_DAPM_SINGLE("Direct Voice Switch", WM8993_SPKOUT_MIXERS, 5, 1, 0), -SOC_DAPM_SINGLE("SPKL Switch", WM8993_SPKOUT_MIXERS, 4, 1, 0), -SOC_DAPM_SINGLE("SPKR Switch", WM8993_SPKOUT_MIXERS, 3, 1, 0), -}; - -static const struct snd_kcontrol_new right_speaker_boost[] = { -SOC_DAPM_SINGLE("Direct Voice Switch", WM8993_SPKOUT_MIXERS, 2, 1, 0), -SOC_DAPM_SINGLE("SPKL Switch", WM8993_SPKOUT_MIXERS, 1, 1, 0), -SOC_DAPM_SINGLE("SPKR Switch", WM8993_SPKOUT_MIXERS, 0, 1, 0), -}; - static const char *hp_mux_text[] = { "Mixer", "DAC", @@ -1223,71 +832,26 @@ static const struct soc_enum hpr_enum = static const struct snd_kcontrol_new hpr_mux = SOC_DAPM_ENUM_W("Right Headphone Mux", hpr_enum); -static const struct snd_kcontrol_new line1_mix[] = { -SOC_DAPM_SINGLE("IN1R Switch", WM8993_LINE_MIXER1, 2, 1, 0), -SOC_DAPM_SINGLE("IN1L Switch", WM8993_LINE_MIXER1, 1, 1, 0), -SOC_DAPM_SINGLE("Output Switch", WM8993_LINE_MIXER1, 0, 1, 0), -}; - -static const struct snd_kcontrol_new line1n_mix[] = { -SOC_DAPM_SINGLE("Left Output Switch", WM8993_LINE_MIXER1, 6, 1, 0), -SOC_DAPM_SINGLE("Right Output Switch", WM8993_LINE_MIXER1, 5, 1, 0), -}; - -static const struct snd_kcontrol_new line1p_mix[] = { -SOC_DAPM_SINGLE("Left Output Switch", WM8993_LINE_MIXER1, 0, 1, 0), -}; - -static const struct snd_kcontrol_new line2_mix[] = { -SOC_DAPM_SINGLE("IN2R Switch", WM8993_LINE_MIXER2, 2, 1, 0), -SOC_DAPM_SINGLE("IN2L Switch", WM8993_LINE_MIXER2, 1, 1, 0), -SOC_DAPM_SINGLE("Output Switch", WM8993_LINE_MIXER2, 0, 1, 0), -}; - -static const struct snd_kcontrol_new line2n_mix[] = { -SOC_DAPM_SINGLE("Left Output Switch", WM8993_LINE_MIXER2, 6, 1, 0), -SOC_DAPM_SINGLE("Right Output Switch", WM8993_LINE_MIXER2, 5, 1, 0), +static const struct snd_kcontrol_new left_speaker_mixer[] = { +SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 7, 1, 0), +SOC_DAPM_SINGLE("IN1LP Switch", WM8993_SPEAKER_MIXER, 5, 1, 0), +SOC_DAPM_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 3, 1, 0), +SOC_DAPM_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 6, 1, 0), }; -static const struct snd_kcontrol_new line2p_mix[] = { -SOC_DAPM_SINGLE("Right Output Switch", WM8993_LINE_MIXER2, 0, 1, 0), +static const struct snd_kcontrol_new right_speaker_mixer[] = { +SOC_DAPM_SINGLE("Input Switch", WM8993_SPEAKER_MIXER, 6, 1, 0), +SOC_DAPM_SINGLE("IN1RP Switch", WM8993_SPEAKER_MIXER, 4, 1, 0), +SOC_DAPM_SINGLE("Output Switch", WM8993_SPEAKER_MIXER, 2, 1, 0), +SOC_DAPM_SINGLE("DAC Switch", WM8993_SPEAKER_MIXER, 0, 1, 0), }; static const struct snd_soc_dapm_widget wm8993_dapm_widgets[] = { -SND_SOC_DAPM_INPUT("IN1LN"), -SND_SOC_DAPM_INPUT("IN1LP"), -SND_SOC_DAPM_INPUT("IN2LN"), -SND_SOC_DAPM_INPUT("IN2LP/VXRN"), -SND_SOC_DAPM_INPUT("IN1RN"), -SND_SOC_DAPM_INPUT("IN1RP"), -SND_SOC_DAPM_INPUT("IN2RN"), -SND_SOC_DAPM_INPUT("IN2RP/VXRP"), - SND_SOC_DAPM_SUPPLY("CLK_SYS", WM8993_BUS_CONTROL_1, 1, 0, clk_sys_event, SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), SND_SOC_DAPM_SUPPLY("TOCLK", WM8993_CLOCKING_1, 14, 0, NULL, 0), SND_SOC_DAPM_SUPPLY("CLK_DSP", WM8993_CLOCKING_3, 0, 0, NULL, 0), -SND_SOC_DAPM_MICBIAS("MICBIAS2", WM8993_POWER_MANAGEMENT_1, 5, 0), -SND_SOC_DAPM_MICBIAS("MICBIAS1", WM8993_POWER_MANAGEMENT_1, 4, 0), - -SND_SOC_DAPM_MIXER("IN1L PGA", WM8993_POWER_MANAGEMENT_2, 6, 0, - in1l_pga, ARRAY_SIZE(in1l_pga)), -SND_SOC_DAPM_MIXER("IN1R PGA", WM8993_POWER_MANAGEMENT_2, 4, 0, - in1r_pga, ARRAY_SIZE(in1r_pga)), - -SND_SOC_DAPM_MIXER("IN2L PGA", WM8993_POWER_MANAGEMENT_2, 7, 0, - in2l_pga, ARRAY_SIZE(in2l_pga)), -SND_SOC_DAPM_MIXER("IN2R PGA", WM8993_POWER_MANAGEMENT_2, 5, 0, - in2r_pga, ARRAY_SIZE(in2r_pga)), - -/* Dummy widgets to represent differential paths */ -SND_SOC_DAPM_PGA("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0), - -SND_SOC_DAPM_MIXER("MIXINL", WM8993_POWER_MANAGEMENT_2, 9, 0, - mixinl, ARRAY_SIZE(mixinl)), -SND_SOC_DAPM_MIXER("MIXINR", WM8993_POWER_MANAGEMENT_2, 8, 0, - mixinr, ARRAY_SIZE(mixinr)), SND_SOC_DAPM_ADC("ADCL", "Capture", WM8993_POWER_MANAGEMENT_2, 1, 0), SND_SOC_DAPM_ADC("ADCR", "Capture", WM8993_POWER_MANAGEMENT_2, 0, 0), @@ -1295,110 +859,19 @@ SND_SOC_DAPM_ADC("ADCR", "Capture", WM8993_POWER_MANAGEMENT_2, 0, 0), SND_SOC_DAPM_DAC("DACL", "Playback", WM8993_POWER_MANAGEMENT_3, 1, 0), SND_SOC_DAPM_DAC("DACR", "Playback", WM8993_POWER_MANAGEMENT_3, 0, 0), -SND_SOC_DAPM_MIXER("Left Output Mixer", WM8993_POWER_MANAGEMENT_3, 5, 0, - left_output_mixer, ARRAY_SIZE(left_output_mixer)), -SND_SOC_DAPM_MIXER("Right Output Mixer", WM8993_POWER_MANAGEMENT_3, 4, 0, - right_output_mixer, ARRAY_SIZE(right_output_mixer)), - -SND_SOC_DAPM_PGA("Left Output PGA", WM8993_POWER_MANAGEMENT_3, 7, 0, NULL, 0), -SND_SOC_DAPM_PGA("Right Output PGA", WM8993_POWER_MANAGEMENT_3, 6, 0, NULL, 0), - -SND_SOC_DAPM_MIXER("Earpiece Mixer", SND_SOC_NOPM, 0, 0, - earpiece_mixer, ARRAY_SIZE(earpiece_mixer)), -SND_SOC_DAPM_PGA_E("Earpiece Driver", WM8993_POWER_MANAGEMENT_1, 11, 0, - NULL, 0, wm8993_earpiece_event, - SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), +SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux), +SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux), SND_SOC_DAPM_MIXER("SPKL", WM8993_POWER_MANAGEMENT_3, 8, 0, left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)), SND_SOC_DAPM_MIXER("SPKR", WM8993_POWER_MANAGEMENT_3, 9, 0, right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)), -SND_SOC_DAPM_MIXER("SPKL Boost", SND_SOC_NOPM, 0, 0, - left_speaker_boost, ARRAY_SIZE(left_speaker_boost)), -SND_SOC_DAPM_MIXER("SPKR Boost", SND_SOC_NOPM, 0, 0, - right_speaker_boost, ARRAY_SIZE(right_speaker_boost)), - -SND_SOC_DAPM_PGA("SPKL Driver", WM8993_POWER_MANAGEMENT_1, 12, 0, - NULL, 0), -SND_SOC_DAPM_PGA("SPKR Driver", WM8993_POWER_MANAGEMENT_1, 13, 0, - NULL, 0), - -SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux), -SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux), -SND_SOC_DAPM_PGA_E("Headphone PGA", SND_SOC_NOPM, 0, 0, - NULL, 0, - hp_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), - -SND_SOC_DAPM_MIXER("LINEOUT1 Mixer", SND_SOC_NOPM, 0, 0, - line1_mix, ARRAY_SIZE(line1_mix)), -SND_SOC_DAPM_MIXER("LINEOUT2 Mixer", SND_SOC_NOPM, 0, 0, - line2_mix, ARRAY_SIZE(line2_mix)), - -SND_SOC_DAPM_MIXER("LINEOUT1N Mixer", SND_SOC_NOPM, 0, 0, - line1n_mix, ARRAY_SIZE(line1n_mix)), -SND_SOC_DAPM_MIXER("LINEOUT1P Mixer", SND_SOC_NOPM, 0, 0, - line1p_mix, ARRAY_SIZE(line1p_mix)), -SND_SOC_DAPM_MIXER("LINEOUT2N Mixer", SND_SOC_NOPM, 0, 0, - line2n_mix, ARRAY_SIZE(line2n_mix)), -SND_SOC_DAPM_MIXER("LINEOUT2P Mixer", SND_SOC_NOPM, 0, 0, - line2p_mix, ARRAY_SIZE(line2p_mix)), - -SND_SOC_DAPM_PGA("LINEOUT1N Driver", WM8993_POWER_MANAGEMENT_3, 13, 0, - NULL, 0), -SND_SOC_DAPM_PGA("LINEOUT1P Driver", WM8993_POWER_MANAGEMENT_3, 12, 0, - NULL, 0), -SND_SOC_DAPM_PGA("LINEOUT2N Driver", WM8993_POWER_MANAGEMENT_3, 11, 0, - NULL, 0), -SND_SOC_DAPM_PGA("LINEOUT2P Driver", WM8993_POWER_MANAGEMENT_3, 10, 0, - NULL, 0), - -SND_SOC_DAPM_OUTPUT("SPKOUTLP"), -SND_SOC_DAPM_OUTPUT("SPKOUTLN"), -SND_SOC_DAPM_OUTPUT("SPKOUTRP"), -SND_SOC_DAPM_OUTPUT("SPKOUTRN"), -SND_SOC_DAPM_OUTPUT("HPOUT1L"), -SND_SOC_DAPM_OUTPUT("HPOUT1R"), -SND_SOC_DAPM_OUTPUT("HPOUT2P"), -SND_SOC_DAPM_OUTPUT("HPOUT2N"), -SND_SOC_DAPM_OUTPUT("LINEOUT1P"), -SND_SOC_DAPM_OUTPUT("LINEOUT1N"), -SND_SOC_DAPM_OUTPUT("LINEOUT2P"), -SND_SOC_DAPM_OUTPUT("LINEOUT2N"), }; static const struct snd_soc_dapm_route routes[] = { - { "IN1L PGA", "IN1LP Switch", "IN1LP" }, - { "IN1L PGA", "IN1LN Switch", "IN1LN" }, - - { "IN1R PGA", "IN1RP Switch", "IN1RP" }, - { "IN1R PGA", "IN1RN Switch", "IN1RN" }, - - { "IN2L PGA", "IN2LP Switch", "IN2LP/VXRN" }, - { "IN2L PGA", "IN2LN Switch", "IN2LN" }, - - { "IN2R PGA", "IN2RP Switch", "IN2RP/VXRP" }, - { "IN2R PGA", "IN2RN Switch", "IN2RN" }, - - { "Direct Voice", NULL, "IN2LP/VXRN" }, - { "Direct Voice", NULL, "IN2RP/VXRP" }, - - { "MIXINL", "IN1L Switch", "IN1L PGA" }, - { "MIXINL", "IN2L Switch", "IN2L PGA" }, - { "MIXINL", NULL, "Direct Voice" }, - { "MIXINL", NULL, "IN1LP" }, - { "MIXINL", NULL, "Left Output Mixer" }, - - { "MIXINR", "IN1R Switch", "IN1R PGA" }, - { "MIXINR", "IN2R Switch", "IN2R PGA" }, - { "MIXINR", NULL, "Direct Voice" }, - { "MIXINR", NULL, "IN1RP" }, - { "MIXINR", NULL, "Right Output Mixer" }, - - { "ADCL", NULL, "MIXINL" }, { "ADCL", NULL, "CLK_SYS" }, { "ADCL", NULL, "CLK_DSP" }, - { "ADCR", NULL, "MIXINR" }, { "ADCR", NULL, "CLK_SYS" }, { "ADCR", NULL, "CLK_DSP" }, @@ -1407,128 +880,22 @@ static const struct snd_soc_dapm_route routes[] = { { "DACR", NULL, "CLK_SYS" }, { "DACR", NULL, "CLK_DSP" }, - { "Left Output Mixer", "Left Input Switch", "MIXINL" }, - { "Left Output Mixer", "Right Input Switch", "MIXINR" }, - { "Left Output Mixer", "IN2RN Switch", "IN2RN" }, - { "Left Output Mixer", "IN2LN Switch", "IN2LN" }, - { "Left Output Mixer", "IN2LP Switch", "IN2LP/VXRN" }, - { "Left Output Mixer", "IN1L Switch", "IN1L PGA" }, - { "Left Output Mixer", "IN1R Switch", "IN1R PGA" }, { "Left Output Mixer", "DAC Switch", "DACL" }, - { "Right Output Mixer", "Left Input Switch", "MIXINL" }, - { "Right Output Mixer", "Right Input Switch", "MIXINR" }, - { "Right Output Mixer", "IN2LN Switch", "IN2LN" }, - { "Right Output Mixer", "IN2RN Switch", "IN2RN" }, - { "Right Output Mixer", "IN2RP Switch", "IN2RP/VXRP" }, - { "Right Output Mixer", "IN1L Switch", "IN1L PGA" }, - { "Right Output Mixer", "IN1R Switch", "IN1R PGA" }, { "Right Output Mixer", "DAC Switch", "DACR" }, - { "Left Output PGA", NULL, "Left Output Mixer" }, { "Left Output PGA", NULL, "CLK_SYS" }, - { "Left Output PGA", NULL, "TOCLK" }, - { "Right Output PGA", NULL, "Right Output Mixer" }, { "Right Output PGA", NULL, "CLK_SYS" }, - { "Right Output PGA", NULL, "TOCLK" }, - - { "Earpiece Mixer", "Direct Voice Switch", "Direct Voice" }, - { "Earpiece Mixer", "Left Output Switch", "Left Output PGA" }, - { "Earpiece Mixer", "Right Output Switch", "Right Output PGA" }, - { "Earpiece Driver", NULL, "Earpiece Mixer" }, - { "HPOUT2N", NULL, "Earpiece Driver" }, - { "HPOUT2P", NULL, "Earpiece Driver" }, - - { "SPKL", "Input Switch", "MIXINL" }, - { "SPKL", "IN1LP Switch", "IN1LP" }, - { "SPKL", "Output Switch", "Left Output Mixer" }, { "SPKL", "DAC Switch", "DACL" }, { "SPKL", NULL, "CLK_SYS" }, - { "SPKL", NULL, "TOCLK" }, - { "SPKR", "Input Switch", "MIXINR" }, - { "SPKR", "IN1RP Switch", "IN1RP" }, - { "SPKR", "Output Switch", "Right Output Mixer" }, { "SPKR", "DAC Switch", "DACR" }, { "SPKR", NULL, "CLK_SYS" }, - { "SPKR", NULL, "TOCLK" }, - - { "SPKL Boost", "Direct Voice Switch", "Direct Voice" }, - { "SPKL Boost", "SPKL Switch", "SPKL" }, - { "SPKL Boost", "SPKR Switch", "SPKR" }, - - { "SPKR Boost", "Direct Voice Switch", "Direct Voice" }, - { "SPKR Boost", "SPKR Switch", "SPKR" }, - { "SPKR Boost", "SPKL Switch", "SPKL" }, - - { "SPKL Driver", NULL, "SPKL Boost" }, - { "SPKL Driver", NULL, "CLK_SYS" }, - - { "SPKR Driver", NULL, "SPKR Boost" }, - { "SPKR Driver", NULL, "CLK_SYS" }, - - { "SPKOUTLP", NULL, "SPKL Driver" }, - { "SPKOUTLN", NULL, "SPKL Driver" }, - { "SPKOUTRP", NULL, "SPKR Driver" }, - { "SPKOUTRN", NULL, "SPKR Driver" }, { "Left Headphone Mux", "DAC", "DACL" }, - { "Left Headphone Mux", "Mixer", "Left Output Mixer" }, { "Right Headphone Mux", "DAC", "DACR" }, - { "Right Headphone Mux", "Mixer", "Right Output Mixer" }, - - { "Headphone PGA", NULL, "Left Headphone Mux" }, - { "Headphone PGA", NULL, "Right Headphone Mux" }, - { "Headphone PGA", NULL, "CLK_SYS" }, - { "Headphone PGA", NULL, "TOCLK" }, - - { "HPOUT1L", NULL, "Headphone PGA" }, - { "HPOUT1R", NULL, "Headphone PGA" }, - - { "LINEOUT1N", NULL, "LINEOUT1N Driver" }, - { "LINEOUT1P", NULL, "LINEOUT1P Driver" }, - { "LINEOUT2N", NULL, "LINEOUT2N Driver" }, - { "LINEOUT2P", NULL, "LINEOUT2P Driver" }, -}; - -static const struct snd_soc_dapm_route lineout1_diff_routes[] = { - { "LINEOUT1 Mixer", "IN1L Switch", "IN1L PGA" }, - { "LINEOUT1 Mixer", "IN1R Switch", "IN1R PGA" }, - { "LINEOUT1 Mixer", "Output Switch", "Left Output Mixer" }, - - { "LINEOUT1N Driver", NULL, "LINEOUT1 Mixer" }, - { "LINEOUT1P Driver", NULL, "LINEOUT1 Mixer" }, -}; - -static const struct snd_soc_dapm_route lineout1_se_routes[] = { - { "LINEOUT1N Mixer", "Left Output Switch", "Left Output Mixer" }, - { "LINEOUT1N Mixer", "Right Output Switch", "Left Output Mixer" }, - - { "LINEOUT1P Mixer", "Left Output Switch", "Left Output Mixer" }, - - { "LINEOUT1N Driver", NULL, "LINEOUT1N Mixer" }, - { "LINEOUT1P Driver", NULL, "LINEOUT1P Mixer" }, -}; - -static const struct snd_soc_dapm_route lineout2_diff_routes[] = { - { "LINEOUT2 Mixer", "IN2L Switch", "IN2L PGA" }, - { "LINEOUT2 Mixer", "IN2R Switch", "IN2R PGA" }, - { "LINEOUT2 Mixer", "Output Switch", "Right Output Mixer" }, - - { "LINEOUT2N Driver", NULL, "LINEOUT2 Mixer" }, - { "LINEOUT2P Driver", NULL, "LINEOUT2 Mixer" }, -}; - -static const struct snd_soc_dapm_route lineout2_se_routes[] = { - { "LINEOUT2N Mixer", "Left Output Switch", "Left Output Mixer" }, - { "LINEOUT2N Mixer", "Right Output Switch", "Left Output Mixer" }, - - { "LINEOUT2P Mixer", "Right Output Switch", "Right Output Mixer" }, - - { "LINEOUT2N Driver", NULL, "LINEOUT2N Mixer" }, - { "LINEOUT2P Driver", NULL, "LINEOUT2P Mixer" }, }; static int wm8993_set_bias_level(struct snd_soc_codec *codec, @@ -1950,26 +1317,11 @@ static int wm8993_probe(struct platform_device *pdev) snd_soc_dapm_new_controls(codec, wm8993_dapm_widgets, ARRAY_SIZE(wm8993_dapm_widgets)); + wm_hubs_add_analogue_controls(codec); snd_soc_dapm_add_routes(codec, routes, ARRAY_SIZE(routes)); - - if (wm8993->pdata.lineout1_diff) - snd_soc_dapm_add_routes(codec, - lineout1_diff_routes, - ARRAY_SIZE(lineout1_diff_routes)); - else - snd_soc_dapm_add_routes(codec, - lineout1_se_routes, - ARRAY_SIZE(lineout1_se_routes)); - - if (wm8993->pdata.lineout2_diff) - snd_soc_dapm_add_routes(codec, - lineout2_diff_routes, - ARRAY_SIZE(lineout2_diff_routes)); - else - snd_soc_dapm_add_routes(codec, - lineout2_se_routes, - ARRAY_SIZE(lineout2_se_routes)); + wm_hubs_add_analogue_routes(codec, wm8993->pdata.lineout1_diff, + wm8993->pdata.lineout2_diff); snd_soc_dapm_new_widgets(codec); @@ -2066,30 +1418,6 @@ static int wm8993_i2c_probe(struct i2c_client *i2c, wm8993->class_w_users = 2; /* Latch volume update bits and default ZC on */ - snd_soc_update_bits(codec, WM8993_LEFT_LINE_INPUT_1_2_VOLUME, - WM8993_IN1_VU, WM8993_IN1_VU); - snd_soc_update_bits(codec, WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, - WM8993_IN1_VU, WM8993_IN1_VU); - snd_soc_update_bits(codec, WM8993_LEFT_LINE_INPUT_3_4_VOLUME, - WM8993_IN2_VU, WM8993_IN2_VU); - snd_soc_update_bits(codec, WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, - WM8993_IN2_VU, WM8993_IN2_VU); - - snd_soc_update_bits(codec, WM8993_SPEAKER_VOLUME_RIGHT, - WM8993_SPKOUT_VU, WM8993_SPKOUT_VU); - - snd_soc_update_bits(codec, WM8993_LEFT_OUTPUT_VOLUME, - WM8993_HPOUT1L_ZC, WM8993_HPOUT1L_ZC); - snd_soc_update_bits(codec, WM8993_RIGHT_OUTPUT_VOLUME, - WM8993_HPOUT1_VU | WM8993_HPOUT1R_ZC, - WM8993_HPOUT1_VU | WM8993_HPOUT1R_ZC); - - snd_soc_update_bits(codec, WM8993_LEFT_OPGA_VOLUME, - WM8993_MIXOUTL_ZC, WM8993_MIXOUTL_ZC); - snd_soc_update_bits(codec, WM8993_RIGHT_OPGA_VOLUME, - WM8993_MIXOUTR_ZC | WM8993_MIXOUT_VU, - WM8993_MIXOUTR_ZC | WM8993_MIXOUT_VU); - snd_soc_update_bits(codec, WM8993_RIGHT_DAC_DIGITAL_VOLUME, WM8993_DAC_VU, WM8993_DAC_VU); snd_soc_update_bits(codec, WM8993_RIGHT_ADC_DIGITAL_VOLUME, diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c new file mode 100644 index 00000000000..e8fc474ba5c --- /dev/null +++ b/sound/soc/codecs/wm_hubs.c @@ -0,0 +1,758 @@ +/* + * wm_hubs.c -- WM8993/4 common code + * + * Copyright 2009 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "wm8993.h" +#include "wm_hubs.h" + +const DECLARE_TLV_DB_SCALE(wm_hubs_spkmix_tlv, -300, 300, 0); +EXPORT_SYMBOL_GPL(wm_hubs_spkmix_tlv); + +static const DECLARE_TLV_DB_SCALE(inpga_tlv, -1650, 150, 0); +static const DECLARE_TLV_DB_SCALE(inmix_sw_tlv, 0, 3000, 0); +static const DECLARE_TLV_DB_SCALE(inmix_tlv, -1500, 300, 1); +static const DECLARE_TLV_DB_SCALE(earpiece_tlv, -600, 600, 0); +static const DECLARE_TLV_DB_SCALE(outmix_tlv, -2100, 300, 0); +static const DECLARE_TLV_DB_SCALE(spkmixout_tlv, -1800, 600, 1); +static const DECLARE_TLV_DB_SCALE(outpga_tlv, -5700, 100, 0); +static const unsigned int spkboost_tlv[] = { + TLV_DB_RANGE_HEAD(7), + 0, 6, TLV_DB_SCALE_ITEM(0, 150, 0), + 7, 7, TLV_DB_SCALE_ITEM(1200, 0, 0), +}; +static const DECLARE_TLV_DB_SCALE(line_tlv, -600, 600, 0); + +static const char *speaker_ref_text[] = { + "SPKVDD/2", + "VMID", +}; + +static const struct soc_enum speaker_ref = + SOC_ENUM_SINGLE(WM8993_SPEAKER_MIXER, 8, 2, speaker_ref_text); + +static const char *speaker_mode_text[] = { + "Class D", + "Class AB", +}; + +static const struct soc_enum speaker_mode = + SOC_ENUM_SINGLE(WM8993_SPKMIXR_ATTENUATION, 8, 2, speaker_mode_text); + +static void wait_for_dc_servo(struct snd_soc_codec *codec) +{ + unsigned int reg; + int count = 0; + + dev_dbg(codec->dev, "Waiting for DC servo...\n"); + do { + count++; + msleep(1); + reg = snd_soc_read(codec, WM8993_DC_SERVO_READBACK_0); + dev_dbg(codec->dev, "DC servo status: %x\n", reg); + } while ((reg & WM8993_DCS_CAL_COMPLETE_MASK) + != WM8993_DCS_CAL_COMPLETE_MASK && count < 1000); + + if ((reg & WM8993_DCS_CAL_COMPLETE_MASK) + != WM8993_DCS_CAL_COMPLETE_MASK) + dev_err(codec->dev, "Timed out waiting for DC Servo\n"); +} + +/* + * Update the DC servo calibration on gain changes + */ +static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int ret; + + ret = snd_soc_put_volsw_2r(kcontrol, ucontrol); + + /* Only need to do this if the outputs are active */ + if (snd_soc_read(codec, WM8993_POWER_MANAGEMENT_1) + & (WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA)) + snd_soc_update_bits(codec, + WM8993_DC_SERVO_0, + WM8993_DCS_TRIG_SINGLE_0 | + WM8993_DCS_TRIG_SINGLE_1, + WM8993_DCS_TRIG_SINGLE_0 | + WM8993_DCS_TRIG_SINGLE_1); + + return ret; +} + +static const struct snd_kcontrol_new analogue_snd_controls[] = { +SOC_SINGLE_TLV("IN1L Volume", WM8993_LEFT_LINE_INPUT_1_2_VOLUME, 0, 31, 0, + inpga_tlv), +SOC_SINGLE("IN1L Switch", WM8993_LEFT_LINE_INPUT_1_2_VOLUME, 7, 1, 1), +SOC_SINGLE("IN1L ZC Switch", WM8993_LEFT_LINE_INPUT_1_2_VOLUME, 7, 1, 0), + +SOC_SINGLE_TLV("IN1R Volume", WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, 0, 31, 0, + inpga_tlv), +SOC_SINGLE("IN1R Switch", WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, 7, 1, 1), +SOC_SINGLE("IN1R ZC Switch", WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, 7, 1, 0), + + +SOC_SINGLE_TLV("IN2L Volume", WM8993_LEFT_LINE_INPUT_3_4_VOLUME, 0, 31, 0, + inpga_tlv), +SOC_SINGLE("IN2L Switch", WM8993_LEFT_LINE_INPUT_3_4_VOLUME, 7, 1, 1), +SOC_SINGLE("IN2L ZC Switch", WM8993_LEFT_LINE_INPUT_3_4_VOLUME, 7, 1, 0), + +SOC_SINGLE_TLV("IN2R Volume", WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, 0, 31, 0, + inpga_tlv), +SOC_SINGLE("IN2R Switch", WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, 7, 1, 1), +SOC_SINGLE("IN2R ZC Switch", WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, 7, 1, 0), + +SOC_SINGLE_TLV("MIXINL IN2L Volume", WM8993_INPUT_MIXER3, 7, 1, 0, + inmix_sw_tlv), +SOC_SINGLE_TLV("MIXINL IN1L Volume", WM8993_INPUT_MIXER3, 4, 1, 0, + inmix_sw_tlv), +SOC_SINGLE_TLV("MIXINL Output Record Volume", WM8993_INPUT_MIXER3, 0, 7, 0, + inmix_tlv), +SOC_SINGLE_TLV("MIXINL IN1LP Volume", WM8993_INPUT_MIXER5, 6, 7, 0, inmix_tlv), +SOC_SINGLE_TLV("MIXINL Direct Voice Volume", WM8993_INPUT_MIXER5, 0, 6, 0, + inmix_tlv), + +SOC_SINGLE_TLV("MIXINR IN2R Volume", WM8993_INPUT_MIXER4, 7, 1, 0, + inmix_sw_tlv), +SOC_SINGLE_TLV("MIXINR IN1R Volume", WM8993_INPUT_MIXER4, 4, 1, 0, + inmix_sw_tlv), +SOC_SINGLE_TLV("MIXINR Output Record Volume", WM8993_INPUT_MIXER4, 0, 7, 0, + inmix_tlv), +SOC_SINGLE_TLV("MIXINR IN1RP Volume", WM8993_INPUT_MIXER6, 6, 7, 0, inmix_tlv), +SOC_SINGLE_TLV("MIXINR Direct Voice Volume", WM8993_INPUT_MIXER6, 0, 6, 0, + inmix_tlv), + +SOC_SINGLE_TLV("Left Output Mixer IN2RN Volume", WM8993_OUTPUT_MIXER5, 6, 7, 1, + outmix_tlv), +SOC_SINGLE_TLV("Left Output Mixer IN2LN Volume", WM8993_OUTPUT_MIXER3, 6, 7, 1, + outmix_tlv), +SOC_SINGLE_TLV("Left Output Mixer IN2LP Volume", WM8993_OUTPUT_MIXER3, 9, 7, 1, + outmix_tlv), +SOC_SINGLE_TLV("Left Output Mixer IN1L Volume", WM8993_OUTPUT_MIXER3, 0, 7, 1, + outmix_tlv), +SOC_SINGLE_TLV("Left Output Mixer IN1R Volume", WM8993_OUTPUT_MIXER3, 3, 7, 1, + outmix_tlv), +SOC_SINGLE_TLV("Left Output Mixer Right Input Volume", + WM8993_OUTPUT_MIXER5, 3, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Left Output Mixer Left Input Volume", + WM8993_OUTPUT_MIXER5, 0, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Left Output Mixer DAC Volume", WM8993_OUTPUT_MIXER5, 9, 7, 1, + outmix_tlv), + +SOC_SINGLE_TLV("Right Output Mixer IN2LN Volume", + WM8993_OUTPUT_MIXER6, 6, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Right Output Mixer IN2RN Volume", + WM8993_OUTPUT_MIXER4, 6, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Right Output Mixer IN1L Volume", + WM8993_OUTPUT_MIXER4, 3, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Right Output Mixer IN1R Volume", + WM8993_OUTPUT_MIXER4, 0, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Right Output Mixer IN2RP Volume", + WM8993_OUTPUT_MIXER4, 9, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Right Output Mixer Left Input Volume", + WM8993_OUTPUT_MIXER6, 3, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Right Output Mixer Right Input Volume", + WM8993_OUTPUT_MIXER6, 6, 7, 1, outmix_tlv), +SOC_SINGLE_TLV("Right Output Mixer DAC Volume", + WM8993_OUTPUT_MIXER6, 9, 7, 1, outmix_tlv), + +SOC_DOUBLE_R_TLV("Output Volume", WM8993_LEFT_OPGA_VOLUME, + WM8993_RIGHT_OPGA_VOLUME, 0, 63, 0, outpga_tlv), +SOC_DOUBLE_R("Output Switch", WM8993_LEFT_OPGA_VOLUME, + WM8993_RIGHT_OPGA_VOLUME, 6, 1, 0), +SOC_DOUBLE_R("Output ZC Switch", WM8993_LEFT_OPGA_VOLUME, + WM8993_RIGHT_OPGA_VOLUME, 7, 1, 0), + +SOC_SINGLE("Earpiece Switch", WM8993_HPOUT2_VOLUME, 5, 1, 1), +SOC_SINGLE_TLV("Earpiece Volume", WM8993_HPOUT2_VOLUME, 4, 1, 1, earpiece_tlv), + +SOC_SINGLE_TLV("SPKL Input Volume", WM8993_SPKMIXL_ATTENUATION, + 5, 1, 1, wm_hubs_spkmix_tlv), +SOC_SINGLE_TLV("SPKL IN1LP Volume", WM8993_SPKMIXL_ATTENUATION, + 4, 1, 1, wm_hubs_spkmix_tlv), +SOC_SINGLE_TLV("SPKL Output Volume", WM8993_SPKMIXL_ATTENUATION, + 3, 1, 1, wm_hubs_spkmix_tlv), + +SOC_SINGLE_TLV("SPKR Input Volume", WM8993_SPKMIXR_ATTENUATION, + 5, 1, 1, wm_hubs_spkmix_tlv), +SOC_SINGLE_TLV("SPKR IN1RP Volume", WM8993_SPKMIXR_ATTENUATION, + 4, 1, 1, wm_hubs_spkmix_tlv), +SOC_SINGLE_TLV("SPKR Output Volume", WM8993_SPKMIXR_ATTENUATION, + 3, 1, 1, wm_hubs_spkmix_tlv), + +SOC_DOUBLE_R_TLV("Speaker Mixer Volume", + WM8993_SPKMIXL_ATTENUATION, WM8993_SPKMIXR_ATTENUATION, + 0, 3, 1, spkmixout_tlv), +SOC_DOUBLE_R_TLV("Speaker Volume", + WM8993_SPEAKER_VOLUME_LEFT, WM8993_SPEAKER_VOLUME_RIGHT, + 0, 63, 0, outpga_tlv), +SOC_DOUBLE_R("Speaker Switch", + WM8993_SPEAKER_VOLUME_LEFT, WM8993_SPEAKER_VOLUME_RIGHT, + 6, 1, 0), +SOC_DOUBLE_R("Speaker ZC Switch", + WM8993_SPEAKER_VOLUME_LEFT, WM8993_SPEAKER_VOLUME_RIGHT, + 7, 1, 0), +SOC_DOUBLE_TLV("Speaker Boost Volume", WM8993_SPKOUT_BOOST, 0, 3, 7, 0, + spkboost_tlv), +SOC_ENUM("Speaker Reference", speaker_ref), +SOC_ENUM("Speaker Mode", speaker_mode), + +{ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Headphone Volume", + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ | + SNDRV_CTL_ELEM_ACCESS_READWRITE, + .tlv.p = outpga_tlv, + .info = snd_soc_info_volsw_2r, + .get = snd_soc_get_volsw_2r, .put = wm8993_put_dc_servo, + .private_value = (unsigned long)&(struct soc_mixer_control) { + .reg = WM8993_LEFT_OUTPUT_VOLUME, + .rreg = WM8993_RIGHT_OUTPUT_VOLUME, + .shift = 0, .max = 63 + }, +}, +SOC_DOUBLE_R("Headphone Switch", WM8993_LEFT_OUTPUT_VOLUME, + WM8993_RIGHT_OUTPUT_VOLUME, 6, 1, 0), +SOC_DOUBLE_R("Headphone ZC Switch", WM8993_LEFT_OUTPUT_VOLUME, + WM8993_RIGHT_OUTPUT_VOLUME, 7, 1, 0), + +SOC_SINGLE("LINEOUT1N Switch", WM8993_LINE_OUTPUTS_VOLUME, 6, 1, 1), +SOC_SINGLE("LINEOUT1P Switch", WM8993_LINE_OUTPUTS_VOLUME, 5, 1, 1), +SOC_SINGLE_TLV("LINEOUT1 Volume", WM8993_LINE_OUTPUTS_VOLUME, 4, 1, 1, + line_tlv), + +SOC_SINGLE("LINEOUT2N Switch", WM8993_LINE_OUTPUTS_VOLUME, 2, 1, 1), +SOC_SINGLE("LINEOUT2P Switch", WM8993_LINE_OUTPUTS_VOLUME, 1, 1, 1), +SOC_SINGLE_TLV("LINEOUT2 Volume", WM8993_LINE_OUTPUTS_VOLUME, 0, 1, 1, + line_tlv), +}; + +static int hp_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + unsigned int reg = snd_soc_read(codec, WM8993_ANALOGUE_HP_0); + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + snd_soc_update_bits(codec, WM8993_CHARGE_PUMP_1, + WM8993_CP_ENA, WM8993_CP_ENA); + + msleep(5); + + snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1, + WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA, + WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA); + + reg |= WM8993_HPOUT1L_DLY | WM8993_HPOUT1R_DLY; + snd_soc_write(codec, WM8993_ANALOGUE_HP_0, reg); + + /* Start the DC servo */ + snd_soc_update_bits(codec, WM8993_DC_SERVO_0, + WM8993_DCS_ENA_CHAN_0 | + WM8993_DCS_ENA_CHAN_1 | + WM8993_DCS_TRIG_STARTUP_1 | + WM8993_DCS_TRIG_STARTUP_0, + WM8993_DCS_ENA_CHAN_0 | + WM8993_DCS_ENA_CHAN_1 | + WM8993_DCS_TRIG_STARTUP_1 | + WM8993_DCS_TRIG_STARTUP_0); + wait_for_dc_servo(codec); + snd_soc_update_bits(codec, WM8993_DC_SERVO_1, + WM8993_DCS_TIMER_PERIOD_01_MASK, 0xa); + + reg |= WM8993_HPOUT1R_OUTP | WM8993_HPOUT1R_RMV_SHORT | + WM8993_HPOUT1L_OUTP | WM8993_HPOUT1L_RMV_SHORT; + snd_soc_write(codec, WM8993_ANALOGUE_HP_0, reg); + break; + + case SND_SOC_DAPM_PRE_PMD: + reg &= ~(WM8993_HPOUT1L_RMV_SHORT | + WM8993_HPOUT1L_DLY | + WM8993_HPOUT1L_OUTP | + WM8993_HPOUT1R_RMV_SHORT | + WM8993_HPOUT1R_DLY | + WM8993_HPOUT1R_OUTP); + + snd_soc_update_bits(codec, WM8993_DC_SERVO_1, + WM8993_DCS_TIMER_PERIOD_01_MASK, 0); + snd_soc_update_bits(codec, WM8993_DC_SERVO_0, + WM8993_DCS_ENA_CHAN_0 | + WM8993_DCS_ENA_CHAN_1, 0); + + snd_soc_write(codec, WM8993_ANALOGUE_HP_0, reg); + snd_soc_update_bits(codec, WM8993_POWER_MANAGEMENT_1, + WM8993_HPOUT1L_ENA | WM8993_HPOUT1R_ENA, + 0); + + snd_soc_update_bits(codec, WM8993_CHARGE_PUMP_1, + WM8993_CP_ENA, 0); + break; + } + + return 0; +} + +static int earpiece_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *control, int event) +{ + struct snd_soc_codec *codec = w->codec; + u16 reg = snd_soc_read(codec, WM8993_ANTIPOP1) & ~WM8993_HPOUT2_IN_ENA; + + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + reg |= WM8993_HPOUT2_IN_ENA; + snd_soc_write(codec, WM8993_ANTIPOP1, reg); + udelay(50); + break; + + case SND_SOC_DAPM_POST_PMD: + snd_soc_write(codec, WM8993_ANTIPOP1, reg); + break; + + default: + BUG(); + break; + } + + return 0; +} + +static const struct snd_kcontrol_new in1l_pga[] = { +SOC_DAPM_SINGLE("IN1LP Switch", WM8993_INPUT_MIXER2, 5, 1, 0), +SOC_DAPM_SINGLE("IN1LN Switch", WM8993_INPUT_MIXER2, 4, 1, 0), +}; + +static const struct snd_kcontrol_new in1r_pga[] = { +SOC_DAPM_SINGLE("IN1RP Switch", WM8993_INPUT_MIXER2, 1, 1, 0), +SOC_DAPM_SINGLE("IN1RN Switch", WM8993_INPUT_MIXER2, 0, 1, 0), +}; + +static const struct snd_kcontrol_new in2l_pga[] = { +SOC_DAPM_SINGLE("IN2LP Switch", WM8993_INPUT_MIXER2, 7, 1, 0), +SOC_DAPM_SINGLE("IN2LN Switch", WM8993_INPUT_MIXER2, 6, 1, 0), +}; + +static const struct snd_kcontrol_new in2r_pga[] = { +SOC_DAPM_SINGLE("IN2RP Switch", WM8993_INPUT_MIXER2, 3, 1, 0), +SOC_DAPM_SINGLE("IN2RN Switch", WM8993_INPUT_MIXER2, 2, 1, 0), +}; + +static const struct snd_kcontrol_new mixinl[] = { +SOC_DAPM_SINGLE("IN2L Switch", WM8993_INPUT_MIXER3, 8, 1, 0), +SOC_DAPM_SINGLE("IN1L Switch", WM8993_INPUT_MIXER3, 5, 1, 0), +}; + +static const struct snd_kcontrol_new mixinr[] = { +SOC_DAPM_SINGLE("IN2R Switch", WM8993_INPUT_MIXER4, 8, 1, 0), +SOC_DAPM_SINGLE("IN1R Switch", WM8993_INPUT_MIXER4, 5, 1, 0), +}; + +static const struct snd_kcontrol_new left_output_mixer[] = { +SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER1, 7, 1, 0), +SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER1, 6, 1, 0), +SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER1, 5, 1, 0), +SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER1, 4, 1, 0), +SOC_DAPM_SINGLE("IN2LP Switch", WM8993_OUTPUT_MIXER1, 1, 1, 0), +SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER1, 3, 1, 0), +SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER1, 2, 1, 0), +SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER1, 0, 1, 0), +}; + +static const struct snd_kcontrol_new right_output_mixer[] = { +SOC_DAPM_SINGLE("Left Input Switch", WM8993_OUTPUT_MIXER2, 7, 1, 0), +SOC_DAPM_SINGLE("Right Input Switch", WM8993_OUTPUT_MIXER2, 6, 1, 0), +SOC_DAPM_SINGLE("IN2LN Switch", WM8993_OUTPUT_MIXER2, 5, 1, 0), +SOC_DAPM_SINGLE("IN2RN Switch", WM8993_OUTPUT_MIXER2, 4, 1, 0), +SOC_DAPM_SINGLE("IN1L Switch", WM8993_OUTPUT_MIXER2, 3, 1, 0), +SOC_DAPM_SINGLE("IN1R Switch", WM8993_OUTPUT_MIXER2, 2, 1, 0), +SOC_DAPM_SINGLE("IN2RP Switch", WM8993_OUTPUT_MIXER2, 1, 1, 0), +SOC_DAPM_SINGLE("DAC Switch", WM8993_OUTPUT_MIXER2, 0, 1, 0), +}; + +static const struct snd_kcontrol_new earpiece_mixer[] = { +SOC_DAPM_SINGLE("Direct Voice Switch", WM8993_HPOUT2_MIXER, 5, 1, 0), +SOC_DAPM_SINGLE("Left Output Switch", WM8993_HPOUT2_MIXER, 4, 1, 0), +SOC_DAPM_SINGLE("Right Output Switch", WM8993_HPOUT2_MIXER, 3, 1, 0), +}; + +static const struct snd_kcontrol_new left_speaker_boost[] = { +SOC_DAPM_SINGLE("Direct Voice Switch", WM8993_SPKOUT_MIXERS, 5, 1, 0), +SOC_DAPM_SINGLE("SPKL Switch", WM8993_SPKOUT_MIXERS, 4, 1, 0), +SOC_DAPM_SINGLE("SPKR Switch", WM8993_SPKOUT_MIXERS, 3, 1, 0), +}; + +static const struct snd_kcontrol_new right_speaker_boost[] = { +SOC_DAPM_SINGLE("Direct Voice Switch", WM8993_SPKOUT_MIXERS, 2, 1, 0), +SOC_DAPM_SINGLE("SPKL Switch", WM8993_SPKOUT_MIXERS, 1, 1, 0), +SOC_DAPM_SINGLE("SPKR Switch", WM8993_SPKOUT_MIXERS, 0, 1, 0), +}; + +static const struct snd_kcontrol_new line1_mix[] = { +SOC_DAPM_SINGLE("IN1R Switch", WM8993_LINE_MIXER1, 2, 1, 0), +SOC_DAPM_SINGLE("IN1L Switch", WM8993_LINE_MIXER1, 1, 1, 0), +SOC_DAPM_SINGLE("Output Switch", WM8993_LINE_MIXER1, 0, 1, 0), +}; + +static const struct snd_kcontrol_new line1n_mix[] = { +SOC_DAPM_SINGLE("Left Output Switch", WM8993_LINE_MIXER1, 6, 1, 0), +SOC_DAPM_SINGLE("Right Output Switch", WM8993_LINE_MIXER1, 5, 1, 0), +}; + +static const struct snd_kcontrol_new line1p_mix[] = { +SOC_DAPM_SINGLE("Left Output Switch", WM8993_LINE_MIXER1, 0, 1, 0), +}; + +static const struct snd_kcontrol_new line2_mix[] = { +SOC_DAPM_SINGLE("IN2R Switch", WM8993_LINE_MIXER2, 2, 1, 0), +SOC_DAPM_SINGLE("IN2L Switch", WM8993_LINE_MIXER2, 1, 1, 0), +SOC_DAPM_SINGLE("Output Switch", WM8993_LINE_MIXER2, 0, 1, 0), +}; + +static const struct snd_kcontrol_new line2n_mix[] = { +SOC_DAPM_SINGLE("Left Output Switch", WM8993_LINE_MIXER2, 6, 1, 0), +SOC_DAPM_SINGLE("Right Output Switch", WM8993_LINE_MIXER2, 5, 1, 0), +}; + +static const struct snd_kcontrol_new line2p_mix[] = { +SOC_DAPM_SINGLE("Right Output Switch", WM8993_LINE_MIXER2, 0, 1, 0), +}; + +static const struct snd_soc_dapm_widget analogue_dapm_widgets[] = { +SND_SOC_DAPM_INPUT("IN1LN"), +SND_SOC_DAPM_INPUT("IN1LP"), +SND_SOC_DAPM_INPUT("IN2LN"), +SND_SOC_DAPM_INPUT("IN2LP/VXRN"), +SND_SOC_DAPM_INPUT("IN1RN"), +SND_SOC_DAPM_INPUT("IN1RP"), +SND_SOC_DAPM_INPUT("IN2RN"), +SND_SOC_DAPM_INPUT("IN2RP/VXRP"), + +SND_SOC_DAPM_MICBIAS("MICBIAS2", WM8993_POWER_MANAGEMENT_1, 5, 0), +SND_SOC_DAPM_MICBIAS("MICBIAS1", WM8993_POWER_MANAGEMENT_1, 4, 0), + +SND_SOC_DAPM_MIXER("IN1L PGA", WM8993_POWER_MANAGEMENT_2, 6, 0, + in1l_pga, ARRAY_SIZE(in1l_pga)), +SND_SOC_DAPM_MIXER("IN1R PGA", WM8993_POWER_MANAGEMENT_2, 4, 0, + in1r_pga, ARRAY_SIZE(in1r_pga)), + +SND_SOC_DAPM_MIXER("IN2L PGA", WM8993_POWER_MANAGEMENT_2, 7, 0, + in2l_pga, ARRAY_SIZE(in2l_pga)), +SND_SOC_DAPM_MIXER("IN2R PGA", WM8993_POWER_MANAGEMENT_2, 5, 0, + in2r_pga, ARRAY_SIZE(in2r_pga)), + +/* Dummy widgets to represent differential paths */ +SND_SOC_DAPM_PGA("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0), + +SND_SOC_DAPM_MIXER("MIXINL", WM8993_POWER_MANAGEMENT_2, 9, 0, + mixinl, ARRAY_SIZE(mixinl)), +SND_SOC_DAPM_MIXER("MIXINR", WM8993_POWER_MANAGEMENT_2, 8, 0, + mixinr, ARRAY_SIZE(mixinr)), + +SND_SOC_DAPM_ADC("ADCL", "Capture", WM8993_POWER_MANAGEMENT_2, 1, 0), +SND_SOC_DAPM_ADC("ADCR", "Capture", WM8993_POWER_MANAGEMENT_2, 0, 0), + +SND_SOC_DAPM_DAC("DACL", "Playback", WM8993_POWER_MANAGEMENT_3, 1, 0), +SND_SOC_DAPM_DAC("DACR", "Playback", WM8993_POWER_MANAGEMENT_3, 0, 0), + +SND_SOC_DAPM_MIXER("Left Output Mixer", WM8993_POWER_MANAGEMENT_3, 5, 0, + left_output_mixer, ARRAY_SIZE(left_output_mixer)), +SND_SOC_DAPM_MIXER("Right Output Mixer", WM8993_POWER_MANAGEMENT_3, 4, 0, + right_output_mixer, ARRAY_SIZE(right_output_mixer)), + +SND_SOC_DAPM_PGA("Left Output PGA", WM8993_POWER_MANAGEMENT_3, 7, 0, NULL, 0), +SND_SOC_DAPM_PGA("Right Output PGA", WM8993_POWER_MANAGEMENT_3, 6, 0, NULL, 0), + +SND_SOC_DAPM_PGA_E("Headphone PGA", SND_SOC_NOPM, 0, 0, + NULL, 0, + hp_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + +SND_SOC_DAPM_MIXER("Earpiece Mixer", SND_SOC_NOPM, 0, 0, + earpiece_mixer, ARRAY_SIZE(earpiece_mixer)), +SND_SOC_DAPM_PGA_E("Earpiece Driver", WM8993_POWER_MANAGEMENT_1, 11, 0, + NULL, 0, earpiece_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + +SND_SOC_DAPM_MIXER("SPKL Boost", SND_SOC_NOPM, 0, 0, + left_speaker_boost, ARRAY_SIZE(left_speaker_boost)), +SND_SOC_DAPM_MIXER("SPKR Boost", SND_SOC_NOPM, 0, 0, + right_speaker_boost, ARRAY_SIZE(right_speaker_boost)), + +SND_SOC_DAPM_PGA("SPKL Driver", WM8993_POWER_MANAGEMENT_1, 12, 0, + NULL, 0), +SND_SOC_DAPM_PGA("SPKR Driver", WM8993_POWER_MANAGEMENT_1, 13, 0, + NULL, 0), + +SND_SOC_DAPM_MIXER("LINEOUT1 Mixer", SND_SOC_NOPM, 0, 0, + line1_mix, ARRAY_SIZE(line1_mix)), +SND_SOC_DAPM_MIXER("LINEOUT2 Mixer", SND_SOC_NOPM, 0, 0, + line2_mix, ARRAY_SIZE(line2_mix)), + +SND_SOC_DAPM_MIXER("LINEOUT1N Mixer", SND_SOC_NOPM, 0, 0, + line1n_mix, ARRAY_SIZE(line1n_mix)), +SND_SOC_DAPM_MIXER("LINEOUT1P Mixer", SND_SOC_NOPM, 0, 0, + line1p_mix, ARRAY_SIZE(line1p_mix)), +SND_SOC_DAPM_MIXER("LINEOUT2N Mixer", SND_SOC_NOPM, 0, 0, + line2n_mix, ARRAY_SIZE(line2n_mix)), +SND_SOC_DAPM_MIXER("LINEOUT2P Mixer", SND_SOC_NOPM, 0, 0, + line2p_mix, ARRAY_SIZE(line2p_mix)), + +SND_SOC_DAPM_PGA("LINEOUT1N Driver", WM8993_POWER_MANAGEMENT_3, 13, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LINEOUT1P Driver", WM8993_POWER_MANAGEMENT_3, 12, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LINEOUT2N Driver", WM8993_POWER_MANAGEMENT_3, 11, 0, + NULL, 0), +SND_SOC_DAPM_PGA("LINEOUT2P Driver", WM8993_POWER_MANAGEMENT_3, 10, 0, + NULL, 0), + +SND_SOC_DAPM_OUTPUT("SPKOUTLP"), +SND_SOC_DAPM_OUTPUT("SPKOUTLN"), +SND_SOC_DAPM_OUTPUT("SPKOUTRP"), +SND_SOC_DAPM_OUTPUT("SPKOUTRN"), +SND_SOC_DAPM_OUTPUT("HPOUT1L"), +SND_SOC_DAPM_OUTPUT("HPOUT1R"), +SND_SOC_DAPM_OUTPUT("HPOUT2P"), +SND_SOC_DAPM_OUTPUT("HPOUT2N"), +SND_SOC_DAPM_OUTPUT("LINEOUT1P"), +SND_SOC_DAPM_OUTPUT("LINEOUT1N"), +SND_SOC_DAPM_OUTPUT("LINEOUT2P"), +SND_SOC_DAPM_OUTPUT("LINEOUT2N"), +}; + +static const struct snd_soc_dapm_route analogue_routes[] = { + { "IN1L PGA", "IN1LP Switch", "IN1LP" }, + { "IN1L PGA", "IN1LN Switch", "IN1LN" }, + + { "IN1R PGA", "IN1RP Switch", "IN1RP" }, + { "IN1R PGA", "IN1RN Switch", "IN1RN" }, + + { "IN2L PGA", "IN2LP Switch", "IN2LP/VXRN" }, + { "IN2L PGA", "IN2LN Switch", "IN2LN" }, + + { "IN2R PGA", "IN2RP Switch", "IN2RP/VXRP" }, + { "IN2R PGA", "IN2RN Switch", "IN2RN" }, + + { "Direct Voice", NULL, "IN2LP/VXRN" }, + { "Direct Voice", NULL, "IN2RP/VXRP" }, + + { "MIXINL", "IN1L Switch", "IN1L PGA" }, + { "MIXINL", "IN2L Switch", "IN2L PGA" }, + { "MIXINL", NULL, "Direct Voice" }, + { "MIXINL", NULL, "IN1LP" }, + { "MIXINL", NULL, "Left Output Mixer" }, + + { "MIXINR", "IN1R Switch", "IN1R PGA" }, + { "MIXINR", "IN2R Switch", "IN2R PGA" }, + { "MIXINR", NULL, "Direct Voice" }, + { "MIXINR", NULL, "IN1RP" }, + { "MIXINR", NULL, "Right Output Mixer" }, + + { "ADCL", NULL, "MIXINL" }, + { "ADCR", NULL, "MIXINR" }, + + { "Left Output Mixer", "Left Input Switch", "MIXINL" }, + { "Left Output Mixer", "Right Input Switch", "MIXINR" }, + { "Left Output Mixer", "IN2RN Switch", "IN2RN" }, + { "Left Output Mixer", "IN2LN Switch", "IN2LN" }, + { "Left Output Mixer", "IN2LP Switch", "IN2LP/VXRN" }, + { "Left Output Mixer", "IN1L Switch", "IN1L PGA" }, + { "Left Output Mixer", "IN1R Switch", "IN1R PGA" }, + + { "Right Output Mixer", "Left Input Switch", "MIXINL" }, + { "Right Output Mixer", "Right Input Switch", "MIXINR" }, + { "Right Output Mixer", "IN2LN Switch", "IN2LN" }, + { "Right Output Mixer", "IN2RN Switch", "IN2RN" }, + { "Right Output Mixer", "IN2RP Switch", "IN2RP/VXRP" }, + { "Right Output Mixer", "IN1L Switch", "IN1L PGA" }, + { "Right Output Mixer", "IN1R Switch", "IN1R PGA" }, + + { "Left Output PGA", NULL, "Left Output Mixer" }, + { "Left Output PGA", NULL, "TOCLK" }, + + { "Right Output PGA", NULL, "Right Output Mixer" }, + { "Right Output PGA", NULL, "TOCLK" }, + + { "Earpiece Mixer", "Direct Voice Switch", "Direct Voice" }, + { "Earpiece Mixer", "Left Output Switch", "Left Output PGA" }, + { "Earpiece Mixer", "Right Output Switch", "Right Output PGA" }, + + { "Earpiece Driver", NULL, "Earpiece Mixer" }, + { "HPOUT2N", NULL, "Earpiece Driver" }, + { "HPOUT2P", NULL, "Earpiece Driver" }, + + { "SPKL", "Input Switch", "MIXINL" }, + { "SPKL", "IN1LP Switch", "IN1LP" }, + { "SPKL", "Output Switch", "Left Output Mixer" }, + { "SPKL", NULL, "TOCLK" }, + + { "SPKR", "Input Switch", "MIXINR" }, + { "SPKR", "IN1RP Switch", "IN1RP" }, + { "SPKR", "Output Switch", "Right Output Mixer" }, + { "SPKR", NULL, "TOCLK" }, + + { "SPKL Boost", "Direct Voice Switch", "Direct Voice" }, + { "SPKL Boost", "SPKL Switch", "SPKL" }, + { "SPKL Boost", "SPKR Switch", "SPKR" }, + + { "SPKR Boost", "Direct Voice Switch", "Direct Voice" }, + { "SPKR Boost", "SPKR Switch", "SPKR" }, + { "SPKR Boost", "SPKL Switch", "SPKL" }, + + { "SPKL Driver", NULL, "SPKL Boost" }, + { "SPKL Driver", NULL, "CLK_SYS" }, + + { "SPKR Driver", NULL, "SPKR Boost" }, + { "SPKR Driver", NULL, "CLK_SYS" }, + + { "SPKOUTLP", NULL, "SPKL Driver" }, + { "SPKOUTLN", NULL, "SPKL Driver" }, + { "SPKOUTRP", NULL, "SPKR Driver" }, + { "SPKOUTRN", NULL, "SPKR Driver" }, + + { "Left Headphone Mux", "Mixer", "Left Output Mixer" }, + { "Right Headphone Mux", "Mixer", "Right Output Mixer" }, + + { "Headphone PGA", NULL, "Left Headphone Mux" }, + { "Headphone PGA", NULL, "Right Headphone Mux" }, + { "Headphone PGA", NULL, "CLK_SYS" }, + + { "HPOUT1L", NULL, "Headphone PGA" }, + { "HPOUT1R", NULL, "Headphone PGA" }, + + { "LINEOUT1N", NULL, "LINEOUT1N Driver" }, + { "LINEOUT1P", NULL, "LINEOUT1P Driver" }, + { "LINEOUT2N", NULL, "LINEOUT2N Driver" }, + { "LINEOUT2P", NULL, "LINEOUT2P Driver" }, +}; + +static const struct snd_soc_dapm_route lineout1_diff_routes[] = { + { "LINEOUT1 Mixer", "IN1L Switch", "IN1L PGA" }, + { "LINEOUT1 Mixer", "IN1R Switch", "IN1R PGA" }, + { "LINEOUT1 Mixer", "Output Switch", "Left Output Mixer" }, + + { "LINEOUT1N Driver", NULL, "LINEOUT1 Mixer" }, + { "LINEOUT1P Driver", NULL, "LINEOUT1 Mixer" }, +}; + +static const struct snd_soc_dapm_route lineout1_se_routes[] = { + { "LINEOUT1N Mixer", "Left Output Switch", "Left Output Mixer" }, + { "LINEOUT1N Mixer", "Right Output Switch", "Left Output Mixer" }, + + { "LINEOUT1P Mixer", "Left Output Switch", "Left Output Mixer" }, + + { "LINEOUT1N Driver", NULL, "LINEOUT1N Mixer" }, + { "LINEOUT1P Driver", NULL, "LINEOUT1P Mixer" }, +}; + +static const struct snd_soc_dapm_route lineout2_diff_routes[] = { + { "LINEOUT2 Mixer", "IN2L Switch", "IN2L PGA" }, + { "LINEOUT2 Mixer", "IN2R Switch", "IN2R PGA" }, + { "LINEOUT2 Mixer", "Output Switch", "Right Output Mixer" }, + + { "LINEOUT2N Driver", NULL, "LINEOUT2 Mixer" }, + { "LINEOUT2P Driver", NULL, "LINEOUT2 Mixer" }, +}; + +static const struct snd_soc_dapm_route lineout2_se_routes[] = { + { "LINEOUT2N Mixer", "Left Output Switch", "Left Output Mixer" }, + { "LINEOUT2N Mixer", "Right Output Switch", "Left Output Mixer" }, + + { "LINEOUT2P Mixer", "Right Output Switch", "Right Output Mixer" }, + + { "LINEOUT2N Driver", NULL, "LINEOUT2N Mixer" }, + { "LINEOUT2P Driver", NULL, "LINEOUT2P Mixer" }, +}; + +int wm_hubs_add_analogue_controls(struct snd_soc_codec *codec) +{ + /* Latch volume update bits & default ZC on */ + snd_soc_update_bits(codec, WM8993_LEFT_LINE_INPUT_1_2_VOLUME, + WM8993_IN1_VU, WM8993_IN1_VU); + snd_soc_update_bits(codec, WM8993_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8993_IN1_VU, WM8993_IN1_VU); + snd_soc_update_bits(codec, WM8993_LEFT_LINE_INPUT_3_4_VOLUME, + WM8993_IN2_VU, WM8993_IN2_VU); + snd_soc_update_bits(codec, WM8993_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8993_IN2_VU, WM8993_IN2_VU); + + snd_soc_update_bits(codec, WM8993_SPEAKER_VOLUME_RIGHT, + WM8993_SPKOUT_VU, WM8993_SPKOUT_VU); + + snd_soc_update_bits(codec, WM8993_LEFT_OUTPUT_VOLUME, + WM8993_HPOUT1L_ZC, WM8993_HPOUT1L_ZC); + snd_soc_update_bits(codec, WM8993_RIGHT_OUTPUT_VOLUME, + WM8993_HPOUT1_VU | WM8993_HPOUT1R_ZC, + WM8993_HPOUT1_VU | WM8993_HPOUT1R_ZC); + + snd_soc_update_bits(codec, WM8993_LEFT_OPGA_VOLUME, + WM8993_MIXOUTL_ZC, WM8993_MIXOUTL_ZC); + snd_soc_update_bits(codec, WM8993_RIGHT_OPGA_VOLUME, + WM8993_MIXOUTR_ZC | WM8993_MIXOUT_VU, + WM8993_MIXOUTR_ZC | WM8993_MIXOUT_VU); + + snd_soc_add_controls(codec, analogue_snd_controls, + ARRAY_SIZE(analogue_snd_controls)); + + snd_soc_dapm_new_controls(codec, analogue_dapm_widgets, + ARRAY_SIZE(analogue_dapm_widgets)); + return 0; +} +EXPORT_SYMBOL_GPL(wm_hubs_add_analogue_controls); + +int wm_hubs_add_analogue_routes(struct snd_soc_codec *codec, + int lineout1_diff, int lineout2_diff) +{ + snd_soc_dapm_add_routes(codec, analogue_routes, + ARRAY_SIZE(analogue_routes)); + + if (lineout1_diff) + snd_soc_dapm_add_routes(codec, + lineout1_diff_routes, + ARRAY_SIZE(lineout1_diff_routes)); + else + snd_soc_dapm_add_routes(codec, + lineout1_se_routes, + ARRAY_SIZE(lineout1_se_routes)); + + if (lineout2_diff) + snd_soc_dapm_add_routes(codec, + lineout2_diff_routes, + ARRAY_SIZE(lineout2_diff_routes)); + else + snd_soc_dapm_add_routes(codec, + lineout2_se_routes, + ARRAY_SIZE(lineout2_se_routes)); + + return 0; +} +EXPORT_SYMBOL_GPL(wm_hubs_add_analogue_routes); + +MODULE_DESCRIPTION("Shared support for Wolfson hubs products"); +MODULE_AUTHOR("Mark Brown "); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm_hubs.h b/sound/soc/codecs/wm_hubs.h new file mode 100644 index 00000000000..ec09cb6a293 --- /dev/null +++ b/sound/soc/codecs/wm_hubs.h @@ -0,0 +1,24 @@ +/* + * wm_hubs.h -- WM899x common code + * + * Copyright 2009 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * + * 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 _WM_HUBS_H +#define _WM_HUBS_H + +struct snd_soc_codec; + +extern const unsigned int wm_hubs_spkmix_tlv[]; + +extern int wm_hubs_add_analogue_controls(struct snd_soc_codec *); +extern int wm_hubs_add_analogue_routes(struct snd_soc_codec *, int, int); + +#endif -- cgit v1.2.3 From 6a99fb5fb85f267d075310fd30c4bf6f0ae63062 Mon Sep 17 00:00:00 2001 From: Chaithrika U S Date: Tue, 11 Aug 2009 16:58:52 -0400 Subject: ASoC: DaVinci: McASP driver enhacements On DA830/OMAP-L137 and DA850/OMAP-L138 SoCs, the McASP peripheral has FIFO support. This FIFO provides additional data buffering. It also provides tolerance to variation in host/DMA controller response times. The read and write FIFO sizes are 256 bytes each. If FIFO is enabled, the DMA events from McASP are sent to the FIFO which in turn sends DMA requests to the host CPU according to the thresholds programmed. More details of the FIFO operation can be found at http://focus.ti.com/general/docs/lit/getliterature.tsp?literatureNumber= sprufm1&fileType=pdf This patch adds support for FIFO configuration. The platform data has a version field which differentiates the McASP on different SoCs. Signed-off-by: Chaithrika U S Signed-off-by: Mark Brown --- sound/soc/davinci/davinci-i2s.c | 1 + sound/soc/davinci/davinci-mcasp.c | 105 +++++++++++++++++++++++++++++++++++--- sound/soc/davinci/davinci-mcasp.h | 5 ++ sound/soc/davinci/davinci-pcm.c | 4 +- sound/soc/davinci/davinci-pcm.h | 1 + 5 files changed, 107 insertions(+), 9 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c index 2a56fb78f67..12a6c549ee6 100644 --- a/sound/soc/davinci/davinci-i2s.c +++ b/sound/soc/davinci/davinci-i2s.c @@ -406,6 +406,7 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } + dma_params->acnt = dma_params->data_type; rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(1); xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(1); diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c index f0c03477106..e672f431323 100644 --- a/sound/soc/davinci/davinci-mcasp.c +++ b/sound/soc/davinci/davinci-mcasp.c @@ -102,6 +102,11 @@ /* Receive Buffer for Serializer n */ #define DAVINCI_MCASP_RXBUF_REG 0x280 +/* McASP FIFO Registers */ +#define DAVINCI_MCASP_WFIFOCTL (0x1010) +#define DAVINCI_MCASP_WFIFOSTS (0x1014) +#define DAVINCI_MCASP_RFIFOCTL (0x1018) +#define DAVINCI_MCASP_RFIFOSTS (0x101C) /* * DAVINCI_MCASP_PWREMUMGT_REG - Power Down and Emulation Management @@ -276,6 +281,13 @@ */ #define TXDATADMADIS BIT(0) +/* + * DAVINCI_MCASP_W[R]FIFOCTL - Write/Read FIFO Control Register bits + */ +#define FIFO_ENABLE BIT(16) +#define NUMEVT_MASK (0xFF << 8) +#define NUMDMA_MASK (0xFF) + #define DAVINCI_MCASP_NUM_SERIALIZER 16 static inline void mcasp_set_bits(void __iomem *reg, u32 val) @@ -345,6 +357,9 @@ static void mcasp_start_rx(struct davinci_audio_dev *dev) static void mcasp_start_tx(struct davinci_audio_dev *dev) { + u8 offset = 0, i; + u32 cnt; + mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXHCLKRST); mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXCLKRST); mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXSERCLR); @@ -353,6 +368,19 @@ static void mcasp_start_tx(struct davinci_audio_dev *dev) mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXSMRST); mcasp_set_ctl_reg(dev->base + DAVINCI_MCASP_GBLCTLX_REG, TXFSRST); mcasp_set_reg(dev->base + DAVINCI_MCASP_TXBUF_REG, 0); + for (i = 0; i < dev->num_serializer; i++) { + if (dev->serial_dir[i] == TX_MODE) { + offset = i; + break; + } + } + + /* wait for TX ready */ + cnt = 0; + while (!(mcasp_get_reg(dev->base + DAVINCI_MCASP_XRSRCTL_REG(offset)) & + TXSTATE) && (cnt < 100000)) + cnt++; + mcasp_set_reg(dev->base + DAVINCI_MCASP_TXBUF_REG, 0); } @@ -362,6 +390,13 @@ static void davinci_mcasp_start(struct davinci_audio_dev *dev, int stream) mcasp_start_tx(dev); else mcasp_start_rx(dev); + + /* enable FIFO */ + if (dev->txnumevt) + mcasp_set_bits(dev->base + DAVINCI_MCASP_WFIFOCTL, FIFO_ENABLE); + + if (dev->rxnumevt) + mcasp_set_bits(dev->base + DAVINCI_MCASP_RFIFOCTL, FIFO_ENABLE); } static void mcasp_stop_rx(struct davinci_audio_dev *dev) @@ -382,6 +417,13 @@ static void davinci_mcasp_stop(struct davinci_audio_dev *dev, int stream) mcasp_stop_tx(dev); else mcasp_stop_rx(dev); + + /* disable FIFO */ + if (dev->txnumevt) + mcasp_clr_bits(dev->base + DAVINCI_MCASP_WFIFOCTL, FIFO_ENABLE); + + if (dev->rxnumevt) + mcasp_clr_bits(dev->base + DAVINCI_MCASP_RFIFOCTL, FIFO_ENABLE); } static int davinci_mcasp_set_dai_fmt(struct snd_soc_dai *cpu_dai, @@ -401,7 +443,6 @@ static int davinci_mcasp_set_dai_fmt(struct snd_soc_dai *cpu_dai, mcasp_set_bits(base + DAVINCI_MCASP_PDIR_REG, (0x7 << 26)); break; - case SND_SOC_DAIFMT_CBM_CFM: /* codec is clock and frame master */ mcasp_clr_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE); @@ -505,6 +546,8 @@ static int davinci_config_channel_size(struct davinci_audio_dev *dev, static void davinci_hw_common_param(struct davinci_audio_dev *dev, int stream) { int i; + u8 tx_ser = 0; + u8 rx_ser = 0; /* Default configuration */ mcasp_set_bits(dev->base + DAVINCI_MCASP_PWREMUMGT_REG, MCASP_SOFT); @@ -525,12 +568,37 @@ static void davinci_hw_common_param(struct davinci_audio_dev *dev, int stream) for (i = 0; i < dev->num_serializer; i++) { mcasp_set_bits(dev->base + DAVINCI_MCASP_XRSRCTL_REG(i), dev->serial_dir[i]); - if (dev->serial_dir[i] == TX_MODE) + if (dev->serial_dir[i] == TX_MODE) { mcasp_set_bits(dev->base + DAVINCI_MCASP_PDIR_REG, AXR(i)); - else if (dev->serial_dir[i] == RX_MODE) + tx_ser++; + } else if (dev->serial_dir[i] == RX_MODE) { mcasp_clr_bits(dev->base + DAVINCI_MCASP_PDIR_REG, AXR(i)); + rx_ser++; + } + } + + if (dev->txnumevt && stream == SNDRV_PCM_STREAM_PLAYBACK) { + if (dev->txnumevt * tx_ser > 64) + dev->txnumevt = 1; + + mcasp_mod_bits(dev->base + DAVINCI_MCASP_WFIFOCTL, tx_ser, + NUMDMA_MASK); + mcasp_mod_bits(dev->base + DAVINCI_MCASP_WFIFOCTL, + ((dev->txnumevt * tx_ser) << 8), NUMEVT_MASK); + mcasp_set_bits(dev->base + DAVINCI_MCASP_WFIFOCTL, FIFO_ENABLE); + } + + if (dev->rxnumevt && stream == SNDRV_PCM_STREAM_CAPTURE) { + if (dev->rxnumevt * rx_ser > 64) + dev->rxnumevt = 1; + + mcasp_mod_bits(dev->base + DAVINCI_MCASP_RFIFOCTL, rx_ser, + NUMDMA_MASK); + mcasp_mod_bits(dev->base + DAVINCI_MCASP_RFIFOCTL, + ((dev->rxnumevt * rx_ser) << 8), NUMEVT_MASK); + mcasp_set_bits(dev->base + DAVINCI_MCASP_RFIFOCTL, FIFO_ENABLE); } } @@ -543,6 +611,8 @@ static void davinci_hw_param(struct davinci_audio_dev *dev, int stream) for (i = 0; i < active_slots; i++) mask |= (1 << i); + mcasp_clr_bits(dev->base + DAVINCI_MCASP_ACLKXCTL_REG, TX_ASYNC); + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { /* bit stream is MSB first with no delay */ /* DSP_B mode */ @@ -622,8 +692,16 @@ static int davinci_mcasp_hw_params(struct snd_pcm_substream *substream, struct davinci_pcm_dma_params *dma_params = dev->dma_params[substream->stream]; int word_length; + u8 numevt; davinci_hw_common_param(dev, substream->stream); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + numevt = dev->txnumevt; + else + numevt = dev->rxnumevt; + + if (!numevt) + numevt = 1; if (dev->op_mode == DAVINCI_MCASP_DIT_MODE) davinci_hw_dit_param(dev); @@ -650,6 +728,13 @@ static int davinci_mcasp_hw_params(struct snd_pcm_substream *substream, printk(KERN_WARNING "davinci-mcasp: unsupported PCM format"); return -EINVAL; } + + if (dev->version == MCASP_VERSION_2) { + dma_params->data_type *= numevt; + dma_params->acnt = 4 * numevt; + } else + dma_params->acnt = dma_params->data_type; + davinci_config_channel_size(dev, word_length); return 0; @@ -778,6 +863,9 @@ static int davinci_mcasp_probe(struct platform_device *pdev) dev->num_serializer = pdata->num_serializer; dev->serial_dir = pdata->serial_dir; dev->codec_fmt = pdata->codec_fmt; + dev->version = pdata->version; + dev->txnumevt = pdata->txnumevt; + dev->rxnumevt = pdata->rxnumevt; dma_data[count].name = "I2S PCM Stereo out"; dma_data[count].eventq_no = pdata->eventq_no; @@ -807,9 +895,9 @@ static int davinci_mcasp_probe(struct platform_device *pdev) } dma_data[count].channel = res->start; - davinci_mcasp_dai[pdev->id].private_data = dev; - davinci_mcasp_dai[pdev->id].dev = &pdev->dev; - ret = snd_soc_register_dai(&davinci_mcasp_dai[pdev->id]); + davinci_mcasp_dai[pdata->op_mode].private_data = dev; + davinci_mcasp_dai[pdata->op_mode].dev = &pdev->dev; + ret = snd_soc_register_dai(&davinci_mcasp_dai[pdata->op_mode]); if (ret != 0) goto err_release_region; @@ -827,12 +915,13 @@ err_release_dev: static int davinci_mcasp_remove(struct platform_device *pdev) { + struct snd_platform_data *pdata = pdev->dev.platform_data; struct davinci_pcm_dma_params *dma_data; struct davinci_audio_dev *dev; struct resource *mem; - snd_soc_unregister_dai(&davinci_mcasp_dai[pdev->id]); - dev = davinci_mcasp_dai[pdev->id].private_data; + snd_soc_unregister_dai(&davinci_mcasp_dai[pdata->op_mode]); + dev = davinci_mcasp_dai[pdata->op_mode].private_data; clk_disable(dev->clk); clk_put(dev->clk); dev->clk = NULL; diff --git a/sound/soc/davinci/davinci-mcasp.h b/sound/soc/davinci/davinci-mcasp.h index 36b71047a06..554354c1cc2 100644 --- a/sound/soc/davinci/davinci-mcasp.h +++ b/sound/soc/davinci/davinci-mcasp.h @@ -50,6 +50,11 @@ struct davinci_audio_dev { u8 op_mode; u8 num_serializer; u8 *serial_dir; + u8 version; + + /* McASP FIFO related */ + u8 txnumevt; + u8 rxnumevt; }; #endif /* DAVINCI_MCASP_H */ diff --git a/sound/soc/davinci/davinci-pcm.c b/sound/soc/davinci/davinci-pcm.c index ab43a539c11..091dacb78b4 100644 --- a/sound/soc/davinci/davinci-pcm.c +++ b/sound/soc/davinci/davinci-pcm.c @@ -67,6 +67,7 @@ static void davinci_pcm_enqueue_dma(struct snd_pcm_substream *substream) dma_addr_t src, dst; unsigned short src_bidx, dst_bidx; unsigned int data_type; + unsigned short acnt; unsigned int count; period_size = snd_pcm_lib_period_bytes(substream); @@ -91,11 +92,12 @@ static void davinci_pcm_enqueue_dma(struct snd_pcm_substream *substream) dst_bidx = data_type; } + acnt = prtd->params->acnt; edma_set_src(lch, src, INCR, W8BIT); edma_set_dest(lch, dst, INCR, W8BIT); edma_set_src_index(lch, src_bidx, 0); edma_set_dest_index(lch, dst_bidx, 0); - edma_set_transfer_params(lch, data_type, count, 1, 0, ASYNC); + edma_set_transfer_params(lch, acnt, count, 1, 0, ASYNC); prtd->period++; if (unlikely(prtd->period >= runtime->periods)) diff --git a/sound/soc/davinci/davinci-pcm.h b/sound/soc/davinci/davinci-pcm.h index eb4287faa3d..63d96253c73 100644 --- a/sound/soc/davinci/davinci-pcm.h +++ b/sound/soc/davinci/davinci-pcm.h @@ -19,6 +19,7 @@ struct davinci_pcm_dma_params { char *name; /* stream identifier */ int channel; /* sync dma channel ID */ + unsigned short acnt; dma_addr_t dma_addr; /* device physical address for DMA */ enum dma_event_q eventq_no; /* event queue number */ unsigned char data_type; /* xfer data type */ -- cgit v1.2.3 From 517ee6cf699967b421bcf8967e7bbeb3c45ded1a Mon Sep 17 00:00:00 2001 From: Chaithrika U S Date: Tue, 11 Aug 2009 16:59:12 -0400 Subject: ASoC: DaVinci: Add a DAI format to McASP driver The patch adds a DAI format: Codec bit clock master and frame sync slave, to the driver. Signed-off-by: Chaithrika U S Signed-off-by: Mark Brown --- sound/soc/davinci/davinci-mcasp.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'sound/soc') diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c index e672f431323..eca22d7829d 100644 --- a/sound/soc/davinci/davinci-mcasp.c +++ b/sound/soc/davinci/davinci-mcasp.c @@ -443,6 +443,16 @@ static int davinci_mcasp_set_dai_fmt(struct snd_soc_dai *cpu_dai, mcasp_set_bits(base + DAVINCI_MCASP_PDIR_REG, (0x7 << 26)); break; + case SND_SOC_DAIFMT_CBM_CFS: + /* codec is clock master and frame slave */ + mcasp_set_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE); + mcasp_set_bits(base + DAVINCI_MCASP_TXFMCTL_REG, AFSXE); + + mcasp_set_bits(base + DAVINCI_MCASP_ACLKRCTL_REG, ACLKRE); + mcasp_set_bits(base + DAVINCI_MCASP_RXFMCTL_REG, AFSRE); + + mcasp_set_bits(base + DAVINCI_MCASP_PDIR_REG, (0x2d << 26)); + break; case SND_SOC_DAIFMT_CBM_CFM: /* codec is clock and frame master */ mcasp_clr_bits(base + DAVINCI_MCASP_ACLKXCTL_REG, ACLKXE); -- cgit v1.2.3 From 30230f4cd7c002a40c13758d12c1c6d96100461d Mon Sep 17 00:00:00 2001 From: Chaithrika U S Date: Tue, 11 Aug 2009 16:59:21 -0400 Subject: ASoC: DaVinci: Add audio support fot DA850/OMAP-L138 EVM There is one instance of McASP on DA850/OMAP-L138 SoC. This is connected to TLV320AIC3106 codec for audio playback and capture. This patch adds audio support on this platform. Some of the structure prefix names which are common for DA830/OMAP-L137 EVM and DA850/OMAP-L138 EVM have been renamed to da8xx from da830. Signed-off-by: Chaithrika U S Signed-off-by: Mark Brown --- sound/soc/davinci/Kconfig | 13 +++++++++++-- sound/soc/davinci/Makefile | 1 + sound/soc/davinci/davinci-evm.c | 29 +++++++++++++++++++++++------ 3 files changed, 35 insertions(+), 8 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/davinci/Kconfig b/sound/soc/davinci/Kconfig index 677a5381487..4dfd4ad9d90 100644 --- a/sound/soc/davinci/Kconfig +++ b/sound/soc/davinci/Kconfig @@ -43,12 +43,21 @@ config SND_DAVINCI_SOC_SFFSDR Lyrtech SFFSDR board. config SND_DA830_SOC_EVM - tristate "SoC Audio support for DA830/OMAPL137 EVM" + tristate "SoC Audio support for DA830/OMAP-L137 EVM" depends on SND_DAVINCI_SOC && MACH_DAVINCI_DA830_EVM select SND_DAVINCI_SOC_MCASP select SND_SOC_TLV320AIC3X help Say Y if you want to add support for SoC audio on TI - DA830/OMAPL137 EVM + DA830/OMAP-L137 EVM + +config SND_DA850_SOC_EVM + tristate "SoC Audio support for DA850/OMAP-L138 EVM" + depends on SND_DAVINCI_SOC && MACH_DAVINCI_DA850_EVM + select SND_DAVINCI_SOC_MCASP + select SND_SOC_TLV320AIC3X + help + Say Y if you want to add support for SoC audio on TI + DA850/OMAP-L138 EVM diff --git a/sound/soc/davinci/Makefile b/sound/soc/davinci/Makefile index 5e2195f80cf..a6939d71b98 100644 --- a/sound/soc/davinci/Makefile +++ b/sound/soc/davinci/Makefile @@ -14,4 +14,5 @@ snd-soc-sffsdr-objs := davinci-sffsdr.o obj-$(CONFIG_SND_DAVINCI_SOC_EVM) += snd-soc-evm.o obj-$(CONFIG_SND_DM6467_SOC_EVM) += snd-soc-evm.o obj-$(CONFIG_SND_DA830_SOC_EVM) += snd-soc-evm.o +obj-$(CONFIG_SND_DA850_SOC_EVM) += snd-soc-evm.o obj-$(CONFIG_SND_DAVINCI_SOC_SFFSDR) += snd-soc-sffsdr.o diff --git a/sound/soc/davinci/davinci-evm.c b/sound/soc/davinci/davinci-evm.c index a5a92124b6d..46c1b0cb1d1 100644 --- a/sound/soc/davinci/davinci-evm.c +++ b/sound/soc/davinci/davinci-evm.c @@ -54,7 +54,8 @@ static int evm_hw_params(struct snd_pcm_substream *substream, else if (machine_is_davinci_evm()) sysclk = 12288000; - else if (machine_is_davinci_da830_evm()) + else if (machine_is_davinci_da830_evm() || + machine_is_davinci_da850_evm()) sysclk = 24576000; else @@ -165,7 +166,7 @@ static struct snd_soc_dai_link dm6467_evm_dai[] = { .ops = &evm_ops, }, }; -static struct snd_soc_dai_link da830_evm_dai = { +static struct snd_soc_dai_link da8xx_evm_dai = { .name = "TLV320AIC3X", .stream_name = "AIC3X", .cpu_dai = &davinci_mcasp_dai[DAVINCI_MCASP_I2S_DAI], @@ -191,8 +192,15 @@ static struct snd_soc_card dm6467_snd_soc_card_evm = { }; static struct snd_soc_card da830_snd_soc_card = { - .name = "DA830 EVM", - .dai_link = &da830_evm_dai, + .name = "DA830/OMAP-L137 EVM", + .dai_link = &da8xx_evm_dai, + .platform = &davinci_soc_platform, + .num_links = 1, +}; + +static struct snd_soc_card da850_snd_soc_card = { + .name = "DA850/OMAP-L138 EVM", + .dai_link = &da8xx_evm_dai, .platform = &davinci_soc_platform, .num_links = 1, }; @@ -209,7 +217,7 @@ static struct aic3x_setup_data dm6467_evm_aic3x_setup = { .i2c_address = 0x18, }; -static struct aic3x_setup_data da830_evm_aic3x_setup = { +static struct aic3x_setup_data da8xx_evm_aic3x_setup = { .i2c_bus = 1, .i2c_address = 0x18, }; @@ -232,7 +240,13 @@ static struct snd_soc_device dm6467_evm_snd_devdata = { static struct snd_soc_device da830_evm_snd_devdata = { .card = &da830_snd_soc_card, .codec_dev = &soc_codec_dev_aic3x, - .codec_data = &da830_evm_aic3x_setup, + .codec_data = &da8xx_evm_aic3x_setup, +}; + +static struct snd_soc_device da850_evm_snd_devdata = { + .card = &da850_snd_soc_card, + .codec_dev = &soc_codec_dev_aic3x, + .codec_data = &da8xx_evm_aic3x_setup, }; static struct platform_device *evm_snd_device; @@ -255,6 +269,9 @@ static int __init evm_init(void) } else if (machine_is_davinci_da830_evm()) { evm_snd_dev_data = &da830_evm_snd_devdata; index = 1; + } else if (machine_is_davinci_da850_evm()) { + evm_snd_dev_data = &da850_evm_snd_devdata; + index = 0; } else return -EINVAL; -- cgit v1.2.3 From 4ac0478f2afaf8e778b4190d6218459a9dbf2a8f Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 30 Jul 2009 02:55:01 +0200 Subject: ALSA: Allow passing platform_data for pxa2xx-ac97 This patch adds support for passing platform data to ac97 bus devices from PXA2xx-AC97 driver.. Signed-off-by: Marek Vasut Signed-off-by: Mark Brown --- sound/soc/pxa/pxa2xx-ac97.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/pxa/pxa2xx-ac97.c b/sound/soc/pxa/pxa2xx-ac97.c index d9c94d71fa6..7330e5c5b9d 100644 --- a/sound/soc/pxa/pxa2xx-ac97.c +++ b/sound/soc/pxa/pxa2xx-ac97.c @@ -22,6 +22,7 @@ #include #include #include +#include #include "pxa2xx-pcm.h" #include "pxa2xx-ac97.h" @@ -241,9 +242,18 @@ EXPORT_SYMBOL_GPL(soc_ac97_ops); static int __devinit pxa2xx_ac97_dev_probe(struct platform_device *pdev) { int i; + pxa2xx_audio_ops_t *pdata = pdev->dev.platform_data; - for (i = 0; i < ARRAY_SIZE(pxa_ac97_dai); i++) + if (pdev->id >= 0) { + dev_err(&pdev->dev, "PXA2xx has only one AC97 port.\n"); + return -ENXIO; + } + + for (i = 0; i < ARRAY_SIZE(pxa_ac97_dai); i++) { pxa_ac97_dai[i].dev = &pdev->dev; + if (pdata && pdata->codec_pdata) + pxa_ac97_dai[i].ac97_pdata = pdata->codec_pdata; + } /* Punt most of the init to the SoC probe; we may need the machine * driver to do interesting things with the clocking to get us up -- cgit v1.2.3 From 9028935d752ede57b086ab1bfe2420d4cba80127 Mon Sep 17 00:00:00 2001 From: Peter Ujfalusi Date: Fri, 14 Aug 2009 08:44:00 +0300 Subject: ASoC: TWL4030: Fix for capture mixer strings Change the strings related to capture in order to be interpreted correctly by alsamixer and possible other UI based mixer applications. Signed-off-by: Peter Ujfalusi Signed-off-by: Mark Brown --- sound/soc/codecs/twl4030.c | 46 +++++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 21 deletions(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 1a65004fa37..4df7c6c61c7 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -399,16 +399,20 @@ SOC_DAPM_ENUM("Route", twl4030_vibrapath_enum); /* Left analog microphone selection */ static const struct snd_kcontrol_new twl4030_dapm_analoglmic_controls[] = { - SOC_DAPM_SINGLE("Main mic", TWL4030_REG_ANAMICL, 0, 1, 0), - SOC_DAPM_SINGLE("Headset mic", TWL4030_REG_ANAMICL, 1, 1, 0), - SOC_DAPM_SINGLE("AUXL", TWL4030_REG_ANAMICL, 2, 1, 0), - SOC_DAPM_SINGLE("Carkit mic", TWL4030_REG_ANAMICL, 3, 1, 0), + SOC_DAPM_SINGLE("Main Mic Capture Switch", + TWL4030_REG_ANAMICL, 0, 1, 0), + SOC_DAPM_SINGLE("Headset Mic Capture Switch", + TWL4030_REG_ANAMICL, 1, 1, 0), + SOC_DAPM_SINGLE("AUXL Capture Switch", + TWL4030_REG_ANAMICL, 2, 1, 0), + SOC_DAPM_SINGLE("Carkit Mic Capture Switch", + TWL4030_REG_ANAMICL, 3, 1, 0), }; /* Right analog microphone selection */ static const struct snd_kcontrol_new twl4030_dapm_analogrmic_controls[] = { - SOC_DAPM_SINGLE("Sub mic", TWL4030_REG_ANAMICR, 0, 1, 0), - SOC_DAPM_SINGLE("AUXR", TWL4030_REG_ANAMICR, 2, 1, 0), + SOC_DAPM_SINGLE("Sub Mic Capture Switch", TWL4030_REG_ANAMICR, 0, 1, 0), + SOC_DAPM_SINGLE("AUXR Capture Switch", TWL4030_REG_ANAMICR, 2, 1, 0), }; /* TX1 L/R Analog/Digital microphone selection */ @@ -1333,11 +1337,11 @@ static const struct snd_soc_dapm_widget twl4030_dapm_widgets[] = { SND_SOC_DAPM_POST_REG), /* Analog input mixers for the capture amplifiers */ - SND_SOC_DAPM_MIXER("Analog Left Capture Route", + SND_SOC_DAPM_MIXER("Analog Left", TWL4030_REG_ANAMICL, 4, 0, &twl4030_dapm_analoglmic_controls[0], ARRAY_SIZE(twl4030_dapm_analoglmic_controls)), - SND_SOC_DAPM_MIXER("Analog Right Capture Route", + SND_SOC_DAPM_MIXER("Analog Right", TWL4030_REG_ANAMICR, 4, 0, &twl4030_dapm_analogrmic_controls[0], ARRAY_SIZE(twl4030_dapm_analogrmic_controls)), @@ -1446,16 +1450,16 @@ static const struct snd_soc_dapm_route intercon[] = { {"VIBRA", NULL, "Vibra Route"}, /* Capture path */ - {"Analog Left Capture Route", "Main mic", "MAINMIC"}, - {"Analog Left Capture Route", "Headset mic", "HSMIC"}, - {"Analog Left Capture Route", "AUXL", "AUXL"}, - {"Analog Left Capture Route", "Carkit mic", "CARKITMIC"}, + {"Analog Left", "Main Mic Capture Switch", "MAINMIC"}, + {"Analog Left", "Headset Mic Capture Switch", "HSMIC"}, + {"Analog Left", "AUXL Capture Switch", "AUXL"}, + {"Analog Left", "Carkit Mic Capture Switch", "CARKITMIC"}, - {"Analog Right Capture Route", "Sub mic", "SUBMIC"}, - {"Analog Right Capture Route", "AUXR", "AUXR"}, + {"Analog Right", "Sub Mic Capture Switch", "SUBMIC"}, + {"Analog Right", "AUXR Capture Switch", "AUXR"}, - {"ADC Physical Left", NULL, "Analog Left Capture Route"}, - {"ADC Physical Right", NULL, "Analog Right Capture Route"}, + {"ADC Physical Left", NULL, "Analog Left"}, + {"ADC Physical Right", NULL, "Analog Right"}, {"Digimic0 Enable", NULL, "DIGIMIC0"}, {"Digimic1 Enable", NULL, "DIGIMIC1"}, @@ -1479,11 +1483,11 @@ static const struct snd_soc_dapm_route intercon[] = { {"ADC Virtual Right2", NULL, "TX2 Capture Route"}, /* Analog bypass routes */ - {"Right1 Analog Loopback", "Switch", "Analog Right Capture Route"}, - {"Left1 Analog Loopback", "Switch", "Analog Left Capture Route"}, - {"Right2 Analog Loopback", "Switch", "Analog Right Capture Route"}, - {"Left2 Analog Loopback", "Switch", "Analog Left Capture Route"}, - {"Voice Analog Loopback", "Switch", "Analog Left Capture Route"}, + {"Right1 Analog Loopback", "Switch", "Analog Right"}, + {"Left1 Analog Loopback", "Switch", "Analog Left"}, + {"Right2 Analog Loopback", "Switch", "Analog Right"}, + {"Left2 Analog Loopback", "Switch", "Analog Left"}, + {"Voice Analog Loopback", "Switch", "Analog Left"}, {"Analog R1 Playback Mixer", NULL, "Right1 Analog Loopback"}, {"Analog L1 Playback Mixer", NULL, "Left1 Analog Loopback"}, -- cgit v1.2.3 From 2a708137fd7d3e0928f27157aaed8aba404b11c9 Mon Sep 17 00:00:00 2001 From: Barry Song <21cnbao@gmail.com> Date: Fri, 14 Aug 2009 12:06:38 +0800 Subject: ASoC: delete -spi suffix in ad1938 and free private data while registers fail Signed-off-by: Barry Song <21cnbao@gmail.com> Signed-off-by: Mark Brown --- sound/soc/codecs/ad1938.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'sound/soc') diff --git a/sound/soc/codecs/ad1938.c b/sound/soc/codecs/ad1938.c index 051f68cc997..e62b27701a4 100644 --- a/sound/soc/codecs/ad1938.c +++ b/sound/soc/codecs/ad1938.c @@ -456,7 +456,7 @@ static int __devexit ad1938_spi_remove(struct spi_device *spi) static struct spi_driver ad1938_spi_driver = { .driver = { - .name = "ad1938-spi", + .name = "ad1938", .bus = &spi_bus_type, .owner = THIS_MODULE, }, @@ -543,6 +543,7 @@ static int ad1938_register(struct ad1938_priv *ad1938) ret = snd_soc_register_codec(codec); if (ret != 0) { dev_err(codec->dev, "Failed to register codec: %d\n", ret); + kfree(ad1938); return ret; } @@ -550,6 +551,7 @@ static int ad1938_register(struct ad1938_priv *ad1938) if (ret != 0) { dev_err(codec->dev, "Failed to register DAI: %d\n", ret); snd_soc_unregister_codec(codec); + kfree(ad1938); return ret; } -- cgit v1.2.3