diff options
Diffstat (limited to 'sound/soc')
71 files changed, 6300 insertions, 661 deletions
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig index a3efc52a34da..8224db5f0434 100644 --- a/sound/soc/Kconfig +++ b/sound/soc/Kconfig @@ -50,10 +50,12 @@ source "sound/soc/jz4740/Kconfig" source "sound/soc/nuc900/Kconfig" source "sound/soc/omap/Kconfig" source "sound/soc/kirkwood/Kconfig" +source "sound/soc/mid-x86/Kconfig" source "sound/soc/pxa/Kconfig" source "sound/soc/samsung/Kconfig" source "sound/soc/s6000/Kconfig" source "sound/soc/sh/Kconfig" +source "sound/soc/tegra/Kconfig" source "sound/soc/txx9/Kconfig" # Supported codecs diff --git a/sound/soc/Makefile b/sound/soc/Makefile index ce913bf5213c..1ed61c5df2c5 100644 --- a/sound/soc/Makefile +++ b/sound/soc/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_SND_SOC) += ep93xx/ obj-$(CONFIG_SND_SOC) += fsl/ obj-$(CONFIG_SND_SOC) += imx/ obj-$(CONFIG_SND_SOC) += jz4740/ +obj-$(CONFIG_SND_SOC) += mid-x86/ obj-$(CONFIG_SND_SOC) += nuc900/ obj-$(CONFIG_SND_SOC) += omap/ obj-$(CONFIG_SND_SOC) += kirkwood/ @@ -17,4 +18,5 @@ obj-$(CONFIG_SND_SOC) += pxa/ obj-$(CONFIG_SND_SOC) += samsung/ obj-$(CONFIG_SND_SOC) += s6000/ obj-$(CONFIG_SND_SOC) += sh/ +obj-$(CONFIG_SND_SOC) += tegra/ obj-$(CONFIG_SND_SOC) += txx9/ diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index c48b23c1d4fc..a9cb2a04ad56 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -32,6 +32,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_MAX98088 if I2C select SND_SOC_MAX9877 if I2C select SND_SOC_PCM3008 + select SND_SOC_SN95031 if INTEL_SCU_IPC select SND_SOC_SPDIF select SND_SOC_SSM2602 if I2C select SND_SOC_STAC9766 if SND_SOC_AC97_BUS @@ -76,6 +77,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8985 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8988 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8990 if I2C + select SND_SOC_WM8991 if I2C select SND_SOC_WM8993 if I2C select SND_SOC_WM8994 if MFD_WM8994 select SND_SOC_WM8995 if SND_SOC_I2C_AND_SPI @@ -176,6 +178,9 @@ config SND_SOC_MAX98088 config SND_SOC_PCM3008 tristate +config SND_SOC_SN95031 + tristate + config SND_SOC_SPDIF tristate @@ -304,6 +309,9 @@ config SND_SOC_WM8988 config SND_SOC_WM8990 tristate +config SND_SOC_WM8991 + tristate + config SND_SOC_WM8993 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index 579af9c4f128..68e76af894b9 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -19,6 +19,7 @@ snd-soc-l3-objs := l3.o snd-soc-max98088-objs := max98088.o snd-soc-pcm3008-objs := pcm3008.o snd-soc-alc5623-objs := alc5623.o +snd-soc-sn95031-objs := sn95031.o snd-soc-spdif-objs := spdif_transciever.o snd-soc-ssm2602-objs := ssm2602.o snd-soc-stac9766-objs := stac9766.o @@ -61,6 +62,7 @@ snd-soc-wm8978-objs := wm8978.o snd-soc-wm8985-objs := wm8985.o snd-soc-wm8988-objs := wm8988.o snd-soc-wm8990-objs := wm8990.o +snd-soc-wm8991-objs := wm8991.o snd-soc-wm8993-objs := wm8993.o snd-soc-wm8994-objs := wm8994.o wm8994-tables.o snd-soc-wm8995-objs := wm8995.o @@ -99,6 +101,7 @@ obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o obj-$(CONFIG_SND_SOC_MAX98088) += snd-soc-max98088.o obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o obj-$(CONFIG_SND_SOC_ALC5623) += snd-soc-alc5623.o +obj-$(CONFIG_SND_SOC_SN95031) +=snd-soc-sn95031.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 @@ -141,6 +144,7 @@ obj-$(CONFIG_SND_SOC_WM8978) += snd-soc-wm8978.o obj-$(CONFIG_SND_SOC_WM8985) += snd-soc-wm8985.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_WM8993) += snd-soc-wm8993.o obj-$(CONFIG_SND_SOC_WM8994) += snd-soc-wm8994.o obj-$(CONFIG_SND_SOC_WM8995) += snd-soc-wm8995.o diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index 8b51245f2318..c0fccadaea9a 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -193,12 +193,12 @@ static struct cs4270_mode_ratios cs4270_mode_ratios[] = { /* The number of MCLK/LRCK ratios supported by the CS4270 */ #define NUM_MCLK_RATIOS ARRAY_SIZE(cs4270_mode_ratios) -static int cs4270_reg_is_readable(unsigned int reg) +static int cs4270_reg_is_readable(struct snd_soc_codec *codec, unsigned int reg) { return (reg >= CS4270_FIRSTREG) && (reg <= CS4270_LASTREG); } -static int cs4270_reg_is_volatile(unsigned int reg) +static int cs4270_reg_is_volatile(struct snd_soc_codec *codec, unsigned int reg) { /* Unreadable registers are considered volatile */ if ((reg < CS4270_FIRSTREG) || (reg > CS4270_LASTREG)) diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c index 89498f9ad2e5..bd0517cb7980 100644 --- a/sound/soc/codecs/max98088.c +++ b/sound/soc/codecs/max98088.c @@ -608,7 +608,7 @@ static struct { { 0xFF, 0x00, 1 }, /* FF */ }; -static int max98088_volatile_register(unsigned int reg) +static int max98088_volatile_register(struct snd_soc_codec *codec, unsigned int reg) { return max98088_access[reg].vol; } diff --git a/sound/soc/codecs/sn95031.c b/sound/soc/codecs/sn95031.c new file mode 100644 index 000000000000..593632cf791d --- /dev/null +++ b/sound/soc/codecs/sn95031.c @@ -0,0 +1,495 @@ +/* + * sn95031.c - TI sn95031 Codec driver + * + * Copyright (C) 2010 Intel Corp + * Author: Vinod Koul <vinod.koul@intel.com> + * Author: Harsha Priya <priya.harsha@intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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 of the License. + * + * 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., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <asm/intel_scu_ipc.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include "sn95031.h" + +#define SN95031_RATES (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_44100) +#define SN95031_FORMATS (SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S16_LE) + +/* + * todo: + * capture paths + * jack detection + * PM functions + */ + +static inline unsigned int sn95031_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 value = 0; + int ret; + + ret = intel_scu_ipc_ioread8(reg, &value); + if (ret) + pr_err("read of %x failed, err %d\n", reg, ret); + return value; + +} + +static inline int sn95031_write(struct snd_soc_codec *codec, + unsigned int reg, unsigned int value) +{ + int ret; + + ret = intel_scu_ipc_iowrite8(reg, value); + if (ret) + pr_err("write of %x failed, err %d\n", reg, ret); + return ret; +} + +static int sn95031_set_vaud_bias(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + if (codec->dapm.bias_level == SND_SOC_BIAS_STANDBY) { + pr_debug("vaud_bias powering up pll\n"); + /* power up the pll */ + snd_soc_write(codec, SN95031_AUDPLLCTRL, BIT(5)); + /* enable pcm 2 */ + snd_soc_update_bits(codec, SN95031_PCM2C2, + BIT(0), BIT(0)); + } + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { + pr_debug("vaud_bias power up rail\n"); + /* power up the rail */ + snd_soc_write(codec, SN95031_VAUD, + BIT(2)|BIT(1)|BIT(0)); + msleep(1); + } else if (codec->dapm.bias_level == SND_SOC_BIAS_PREPARE) { + /* turn off pcm */ + pr_debug("vaud_bias power dn pcm\n"); + snd_soc_update_bits(codec, SN95031_PCM2C2, BIT(0), 0); + snd_soc_write(codec, SN95031_AUDPLLCTRL, 0); + } + break; + + + case SND_SOC_BIAS_OFF: + pr_debug("vaud_bias _OFF doing rail shutdown\n"); + snd_soc_write(codec, SN95031_VAUD, BIT(3)); + break; + } + + codec->dapm.bias_level = level; + return 0; +} + +static int sn95031_vhs_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) { + pr_debug("VHS SND_SOC_DAPM_EVENT_ON doing rail startup now\n"); + /* power up the rail */ + snd_soc_write(w->codec, SN95031_VHSP, 0x3D); + snd_soc_write(w->codec, SN95031_VHSN, 0x3F); + msleep(1); + } else if (SND_SOC_DAPM_EVENT_OFF(event)) { + pr_debug("VHS SND_SOC_DAPM_EVENT_OFF doing rail shutdown\n"); + snd_soc_write(w->codec, SN95031_VHSP, 0xC4); + snd_soc_write(w->codec, SN95031_VHSN, 0x04); + } + return 0; +} + +static int sn95031_vihf_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + if (SND_SOC_DAPM_EVENT_ON(event)) { + pr_debug("VIHF SND_SOC_DAPM_EVENT_ON doing rail startup now\n"); + /* power up the rail */ + snd_soc_write(w->codec, SN95031_VIHF, 0x27); + msleep(1); + } else if (SND_SOC_DAPM_EVENT_OFF(event)) { + pr_debug("VIHF SND_SOC_DAPM_EVENT_OFF doing rail shutdown\n"); + snd_soc_write(w->codec, SN95031_VIHF, 0x24); + } + return 0; +} + +/* DAPM widgets */ +static const struct snd_soc_dapm_widget sn95031_dapm_widgets[] = { + + /* all end points mic, hs etc */ + SND_SOC_DAPM_OUTPUT("HPOUTL"), + SND_SOC_DAPM_OUTPUT("HPOUTR"), + SND_SOC_DAPM_OUTPUT("EPOUT"), + SND_SOC_DAPM_OUTPUT("IHFOUTL"), + SND_SOC_DAPM_OUTPUT("IHFOUTR"), + SND_SOC_DAPM_OUTPUT("LINEOUTL"), + SND_SOC_DAPM_OUTPUT("LINEOUTR"), + SND_SOC_DAPM_OUTPUT("VIB1OUT"), + SND_SOC_DAPM_OUTPUT("VIB2OUT"), + + SND_SOC_DAPM_SUPPLY("Headset Rail", SND_SOC_NOPM, 0, 0, + sn95031_vhs_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_SUPPLY("Speaker Rail", SND_SOC_NOPM, 0, 0, + sn95031_vihf_event, + SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD), + + /* playback path driver enables */ + SND_SOC_DAPM_PGA("Headset Left Playback", + SN95031_DRIVEREN, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Headset Right Playback", + SN95031_DRIVEREN, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("Speaker Left Playback", + SN95031_DRIVEREN, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("Speaker Right Playback", + SN95031_DRIVEREN, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("Vibra1 Playback", + SN95031_DRIVEREN, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Vibra2 Playback", + SN95031_DRIVEREN, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Earpiece Playback", + SN95031_DRIVEREN, 6, 0, NULL, 0), + SND_SOC_DAPM_PGA("Lineout Left Playback", + SN95031_LOCTL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Lineout Right Playback", + SN95031_LOCTL, 4, 0, NULL, 0), + + /* playback path filter enable */ + SND_SOC_DAPM_PGA("Headset Left Filter", + SN95031_HSEPRXCTRL, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("Headset Right Filter", + SN95031_HSEPRXCTRL, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("Speaker Left Filter", + SN95031_IHFRXCTRL, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("Speaker Right Filter", + SN95031_IHFRXCTRL, 1, 0, NULL, 0), + + /* DACs */ + SND_SOC_DAPM_DAC("HSDAC Left", "Headset", + SN95031_DACCONFIG, 0, 0), + SND_SOC_DAPM_DAC("HSDAC Right", "Headset", + SN95031_DACCONFIG, 1, 0), + SND_SOC_DAPM_DAC("IHFDAC Left", "Speaker", + SN95031_DACCONFIG, 2, 0), + SND_SOC_DAPM_DAC("IHFDAC Right", "Speaker", + SN95031_DACCONFIG, 3, 0), + SND_SOC_DAPM_DAC("Vibra1 DAC", "Vibra1", + SN95031_VIB1C5, 1, 0), + SND_SOC_DAPM_DAC("Vibra2 DAC", "Vibra2", + SN95031_VIB2C5, 1, 0), +}; + +static const struct snd_soc_dapm_route sn95031_audio_map[] = { + /* headset and earpiece map */ + { "HPOUTL", NULL, "Headset Left Playback" }, + { "HPOUTR", NULL, "Headset Right Playback" }, + { "EPOUT", NULL, "Earpiece Playback" }, + { "Headset Left Playback", NULL, "Headset Left Filter"}, + { "Headset Right Playback", NULL, "Headset Right Filter"}, + { "Earpiece Playback", NULL, "Headset Left Filter"}, + { "Headset Left Filter", NULL, "HSDAC Left"}, + { "Headset Right Filter", NULL, "HSDAC Right"}, + { "HSDAC Left", NULL, "Headset Rail"}, + { "HSDAC Right", NULL, "Headset Rail"}, + + /* speaker map */ + { "IHFOUTL", "NULL", "Speaker Left Playback"}, + { "IHFOUTR", "NULL", "Speaker Right Playback"}, + { "Speaker Left Playback", NULL, "Speaker Left Filter"}, + { "Speaker Right Playback", NULL, "Speaker Right Filter"}, + { "Speaker Left Filter", NULL, "IHFDAC Left"}, + { "Speaker Right Filter", NULL, "IHFDAC Right"}, + { "IHFDAC Left", NULL, "Speaker Rail"}, + { "IHFDAC Right", NULL, "Speaker Rail"}, + + /* vibra map */ + { "VIB1OUT", NULL, "Vibra1 Playback"}, + { "Vibra1 Playback", NULL, "Vibra1 DAC"}, + + { "VIB2OUT", NULL, "Vibra2 Playback"}, + { "Vibra2 Playback", NULL, "Vibra2 DAC"}, + + /* lineout */ + { "LINEOUTL", NULL, "Lineout Left Playback"}, + { "LINEOUTR", NULL, "Lineout Right Playback"}, + { "Lineout Left Playback", NULL, "Headset Left Filter"}, + { "Lineout Left Playback", NULL, "Speaker Left Filter"}, + { "Lineout Left Playback", NULL, "Vibra1 DAC"}, + { "Lineout Right Playback", NULL, "Headset Right Filter"}, + { "Lineout Right Playback", NULL, "Speaker Right Filter"}, + { "Lineout Right Playback", NULL, "Vibra2 DAC"}, +}; + +/* speaker and headset mutes, for audio pops and clicks */ +static int sn95031_pcm_hs_mute(struct snd_soc_dai *dai, int mute) +{ + snd_soc_update_bits(dai->codec, + SN95031_HSLVOLCTRL, BIT(7), (!mute << 7)); + snd_soc_update_bits(dai->codec, + SN95031_HSRVOLCTRL, BIT(7), (!mute << 7)); + return 0; +} + +static int sn95031_pcm_spkr_mute(struct snd_soc_dai *dai, int mute) +{ + snd_soc_update_bits(dai->codec, + SN95031_IHFLVOLCTRL, BIT(7), (!mute << 7)); + snd_soc_update_bits(dai->codec, + SN95031_IHFRVOLCTRL, BIT(7), (!mute << 7)); + return 0; +} + +int sn95031_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + unsigned int format, rate; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + format = BIT(4)|BIT(5); + break; + + case SNDRV_PCM_FORMAT_S24_LE: + format = 0; + break; + default: + return -EINVAL; + } + snd_soc_update_bits(dai->codec, SN95031_PCM2C2, + BIT(4)|BIT(5), format); + + switch (params_rate(params)) { + case 48000: + pr_debug("RATE_48000\n"); + rate = 0; + break; + + case 44100: + pr_debug("RATE_44100\n"); + rate = BIT(7); + break; + + default: + pr_err("ERR rate %d\n", params_rate(params)); + return -EINVAL; + } + snd_soc_update_bits(dai->codec, SN95031_PCM1C1, BIT(7), rate); + + return 0; +} + +/* Codec DAI section */ +static struct snd_soc_dai_ops sn95031_headset_dai_ops = { + .digital_mute = sn95031_pcm_hs_mute, + .hw_params = sn95031_pcm_hw_params, +}; + +static struct snd_soc_dai_ops sn95031_speaker_dai_ops = { + .digital_mute = sn95031_pcm_spkr_mute, + .hw_params = sn95031_pcm_hw_params, +}; + +static struct snd_soc_dai_ops sn95031_vib1_dai_ops = { + .hw_params = sn95031_pcm_hw_params, +}; + +static struct snd_soc_dai_ops sn95031_vib2_dai_ops = { + .hw_params = sn95031_pcm_hw_params, +}; + +struct snd_soc_dai_driver sn95031_dais[] = { +{ + .name = "SN95031 Headset", + .playback = { + .stream_name = "Headset", + .channels_min = 2, + .channels_max = 2, + .rates = SN95031_RATES, + .formats = SN95031_FORMATS, + }, + .ops = &sn95031_headset_dai_ops, +}, +{ .name = "SN95031 Speaker", + .playback = { + .stream_name = "Speaker", + .channels_min = 2, + .channels_max = 2, + .rates = SN95031_RATES, + .formats = SN95031_FORMATS, + }, + .ops = &sn95031_speaker_dai_ops, +}, +{ .name = "SN95031 Vibra1", + .playback = { + .stream_name = "Vibra1", + .channels_min = 1, + .channels_max = 1, + .rates = SN95031_RATES, + .formats = SN95031_FORMATS, + }, + .ops = &sn95031_vib1_dai_ops, +}, +{ .name = "SN95031 Vibra2", + .playback = { + .stream_name = "Vibra2", + .channels_min = 1, + .channels_max = 1, + .rates = SN95031_RATES, + .formats = SN95031_FORMATS, + }, + .ops = &sn95031_vib2_dai_ops, +}, +}; + +/* codec registration */ +static int sn95031_codec_probe(struct snd_soc_codec *codec) +{ + int ret; + + pr_debug("codec_probe called\n"); + + codec->dapm.bias_level = SND_SOC_BIAS_OFF; + codec->dapm.idle_bias_off = 1; + + /* PCM interface config + * This sets the pcm rx slot conguration to max 6 slots + * for max 4 dais (2 stereo and 2 mono) + */ + snd_soc_write(codec, SN95031_PCM2RXSLOT01, 0x10); + snd_soc_write(codec, SN95031_PCM2RXSLOT23, 0x32); + snd_soc_write(codec, SN95031_PCM2RXSLOT45, 0x54); + /* pcm port setting + * This sets the pcm port to slave and clock at 19.2Mhz which + * can support 6slots, sampling rate set per stream in hw-params + */ + snd_soc_write(codec, SN95031_PCM1C1, 0x00); + snd_soc_write(codec, SN95031_PCM2C1, 0x01); + snd_soc_write(codec, SN95031_PCM2C2, 0x0A); + snd_soc_write(codec, SN95031_HSMIXER, BIT(0)|BIT(4)); + /* vendor vibra workround, the vibras are muted by + * custom register so unmute them + */ + snd_soc_write(codec, SN95031_SSR5, 0x80); + snd_soc_write(codec, SN95031_SSR6, 0x80); + snd_soc_write(codec, SN95031_VIB1C5, 0x00); + snd_soc_write(codec, SN95031_VIB2C5, 0x00); + /* configure vibras for pcm port */ + snd_soc_write(codec, SN95031_VIB1C3, 0x00); + snd_soc_write(codec, SN95031_VIB2C3, 0x00); + + /* soft mute ramp time */ + snd_soc_write(codec, SN95031_SOFTMUTE, 0x3); + /* fix the initial volume at 1dB, + * default in +9dB, + * 1dB give optimal swing on DAC, amps + */ + snd_soc_write(codec, SN95031_HSLVOLCTRL, 0x08); + snd_soc_write(codec, SN95031_HSRVOLCTRL, 0x08); + snd_soc_write(codec, SN95031_IHFLVOLCTRL, 0x08); + snd_soc_write(codec, SN95031_IHFRVOLCTRL, 0x08); + /* dac mode and lineout workaround */ + snd_soc_write(codec, SN95031_SSR2, 0x10); + snd_soc_write(codec, SN95031_SSR3, 0x40); + + ret = snd_soc_dapm_new_controls(&codec->dapm, sn95031_dapm_widgets, + ARRAY_SIZE(sn95031_dapm_widgets)); + if (ret) + pr_err("soc_dapm_new_control failed %d", ret); + ret = snd_soc_dapm_add_routes(&codec->dapm, sn95031_audio_map, + ARRAY_SIZE(sn95031_audio_map)); + if (ret) + pr_err("soc_dapm_add_routes failed %d", ret); + + return ret; +} + +static int sn95031_codec_remove(struct snd_soc_codec *codec) +{ + pr_debug("codec_remove called\n"); + sn95031_set_vaud_bias(codec, SND_SOC_BIAS_OFF); + + return 0; +} + +struct snd_soc_codec_driver sn95031_codec = { + .probe = sn95031_codec_probe, + .remove = sn95031_codec_remove, + .read = sn95031_read, + .write = sn95031_write, + .set_bias_level = sn95031_set_vaud_bias, +}; + +static int __devinit sn95031_device_probe(struct platform_device *pdev) +{ + pr_debug("codec device probe called for %s\n", dev_name(&pdev->dev)); + return snd_soc_register_codec(&pdev->dev, &sn95031_codec, + sn95031_dais, ARRAY_SIZE(sn95031_dais)); +} + +static int __devexit sn95031_device_remove(struct platform_device *pdev) +{ + pr_debug("codec device remove called\n"); + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +static struct platform_driver sn95031_codec_driver = { + .driver = { + .name = "sn95031", + .owner = THIS_MODULE, + }, + .probe = sn95031_device_probe, + .remove = sn95031_device_remove, +}; + +static int __init sn95031_init(void) +{ + pr_debug("driver init called\n"); + return platform_driver_register(&sn95031_codec_driver); +} +module_init(sn95031_init); + +static void __exit sn95031_exit(void) +{ + pr_debug("driver exit called\n"); + platform_driver_unregister(&sn95031_codec_driver); +} +module_exit(sn95031_exit); + +MODULE_DESCRIPTION("ASoC TI SN95031 codec driver"); +MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>"); +MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:sn95031"); diff --git a/sound/soc/codecs/sn95031.h b/sound/soc/codecs/sn95031.h new file mode 100644 index 000000000000..e2b17d908aeb --- /dev/null +++ b/sound/soc/codecs/sn95031.h @@ -0,0 +1,99 @@ +/* + * sn95031.h - TI sn95031 Codec driver + * + * Copyright (C) 2010 Intel Corp + * Author: Vinod Koul <vinod.koul@intel.com> + * Author: Harsha Priya <priya.harsha@intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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 of the License. + * + * 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., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * + */ +#ifndef _SN95031_H +#define _SN95031_H + +/*register map*/ +#define SN95031_VAUD 0xDB +#define SN95031_VHSP 0xDC +#define SN95031_VHSN 0xDD +#define SN95031_VIHF 0xC9 + +#define SN95031_AUDPLLCTRL 0x240 +#define SN95031_DMICBUF0123 0x241 +#define SN95031_DMICBUF45 0x242 +#define SN95031_DMICGPO 0x244 +#define SN95031_DMICMUX 0x245 +#define SN95031_DMICLK 0x246 +#define SN95031_MICBIAS 0x247 +#define SN95031_ADCCONFIG 0x248 +#define SN95031_MICAMP1 0x249 +#define SN95031_MICAMP2 0x24A +#define SN95031_NOISEMUX 0x24B +#define SN95031_AUDIOMUX12 0x24C +#define SN95031_AUDIOMUX34 0x24D +#define SN95031_AUDIOSINC 0x24E +#define SN95031_AUDIOTXEN 0x24F +#define SN95031_HSEPRXCTRL 0x250 +#define SN95031_IHFRXCTRL 0x251 +#define SN95031_HSMIXER 0x256 +#define SN95031_DACCONFIG 0x257 +#define SN95031_SOFTMUTE 0x258 +#define SN95031_HSLVOLCTRL 0x259 +#define SN95031_HSRVOLCTRL 0x25A +#define SN95031_IHFLVOLCTRL 0x25B +#define SN95031_IHFRVOLCTRL 0x25C +#define SN95031_DRIVEREN 0x25D +#define SN95031_LOCTL 0x25E +#define SN95031_VIB1C1 0x25F +#define SN95031_VIB1C2 0x260 +#define SN95031_VIB1C3 0x261 +#define SN95031_VIB1SPIPCM1 0x262 +#define SN95031_VIB1SPIPCM2 0x263 +#define SN95031_VIB1C5 0x264 +#define SN95031_VIB2C1 0x265 +#define SN95031_VIB2C2 0x266 +#define SN95031_VIB2C3 0x267 +#define SN95031_VIB2SPIPCM1 0x268 +#define SN95031_VIB2SPIPCM2 0x269 +#define SN95031_VIB2C5 0x26A +#define SN95031_BTNCTRL1 0x26B +#define SN95031_BTNCTRL2 0x26C +#define SN95031_PCM1TXSLOT01 0x26D +#define SN95031_PCM1TXSLOT23 0x26E +#define SN95031_PCM1TXSLOT45 0x26F +#define SN95031_PCM1RXSLOT0_3 0x270 +#define SN95031_PCM1RXSLOT45 0x271 +#define SN95031_PCM2TXSLOT01 0x272 +#define SN95031_PCM2TXSLOT23 0x273 +#define SN95031_PCM2TXSLOT45 0x274 +#define SN95031_PCM2RXSLOT01 0x275 +#define SN95031_PCM2RXSLOT23 0x276 +#define SN95031_PCM2RXSLOT45 0x277 +#define SN95031_PCM1C1 0x278 +#define SN95031_PCM1C2 0x279 +#define SN95031_PCM1C3 0x27A +#define SN95031_PCM2C1 0x27B +#define SN95031_PCM2C2 0x27C +/*end codec register defn*/ + +/*vendor defn these are not part of avp*/ +#define SN95031_SSR2 0x381 +#define SN95031_SSR3 0x382 +#define SN95031_SSR5 0x384 +#define SN95031_SSR6 0x385 + +#endif diff --git a/sound/soc/codecs/wm8523.c b/sound/soc/codecs/wm8523.c index 5eb2f501ce32..4fd4d8dca0fc 100644 --- a/sound/soc/codecs/wm8523.c +++ b/sound/soc/codecs/wm8523.c @@ -58,7 +58,7 @@ static const u16 wm8523_reg[WM8523_REGISTER_COUNT] = { 0x0000, /* R8 - ZERO_DETECT */ }; -static int wm8523_volatile_register(unsigned int reg) +static int wm8523_volatile_register(struct snd_soc_codec *codec, unsigned int reg) { switch (reg) { case WM8523_DEVICE_ID: @@ -414,7 +414,6 @@ static int wm8523_resume(struct snd_soc_codec *codec) static int wm8523_probe(struct snd_soc_codec *codec) { struct wm8523_priv *wm8523 = snd_soc_codec_get_drvdata(codec); - u16 *reg_cache = codec->reg_cache; int ret, i; codec->hw_write = (hw_write_t)i2c_master_send; @@ -471,8 +470,9 @@ static int wm8523_probe(struct snd_soc_codec *codec) } /* Change some default settings - latch VU and enable ZC */ - reg_cache[WM8523_DAC_GAINR] |= WM8523_DACR_VU; - reg_cache[WM8523_DAC_CTRL3] |= WM8523_ZC; + snd_soc_update_bits(codec, WM8523_DAC_GAINR, + WM8523_DACR_VU, WM8523_DACR_VU); + snd_soc_update_bits(codec, WM8523_DAC_CTRL3, WM8523_ZC, WM8523_ZC); wm8523_set_bias_level(codec, SND_SOC_BIAS_STANDBY); diff --git a/sound/soc/codecs/wm8741.c b/sound/soc/codecs/wm8741.c index 494f2d31d75b..25af901fe813 100644 --- a/sound/soc/codecs/wm8741.c +++ b/sound/soc/codecs/wm8741.c @@ -421,7 +421,6 @@ static int wm8741_resume(struct snd_soc_codec *codec) static int wm8741_probe(struct snd_soc_codec *codec) { struct wm8741_priv *wm8741 = snd_soc_codec_get_drvdata(codec); - u16 *reg_cache = codec->reg_cache; int ret = 0; ret = snd_soc_codec_set_cache_io(codec, 7, 9, wm8741->control_type); @@ -437,10 +436,14 @@ static int wm8741_probe(struct snd_soc_codec *codec) } /* Change some default settings - latch VU */ - reg_cache[WM8741_DACLLSB_ATTENUATION] |= WM8741_UPDATELL; - reg_cache[WM8741_DACLMSB_ATTENUATION] |= WM8741_UPDATELM; - reg_cache[WM8741_DACRLSB_ATTENUATION] |= WM8741_UPDATERL; - reg_cache[WM8741_DACRLSB_ATTENUATION] |= WM8741_UPDATERM; + snd_soc_update_bits(codec, WM8741_DACLLSB_ATTENUATION, + WM8741_UPDATELL, WM8741_UPDATELL); + snd_soc_update_bits(codec, WM8741_DACLMSB_ATTENUATION, + WM8741_UPDATELM, WM8741_UPDATELM); + snd_soc_update_bits(codec, WM8741_DACRLSB_ATTENUATION, + WM8741_UPDATERL, WM8741_UPDATERL); + snd_soc_update_bits(codec, WM8741_DACRLSB_ATTENUATION, + WM8741_UPDATERM, WM8741_UPDATERM); snd_soc_add_controls(codec, wm8741_snd_controls, ARRAY_SIZE(wm8741_snd_controls)); diff --git a/sound/soc/codecs/wm8804.c b/sound/soc/codecs/wm8804.c index 6dae1b40c9f7..6785688f8806 100644 --- a/sound/soc/codecs/wm8804.c +++ b/sound/soc/codecs/wm8804.c @@ -175,7 +175,7 @@ static int txsrc_put(struct snd_kcontrol *kcontrol, return 0; } -static int wm8804_volatile(unsigned int reg) +static int wm8804_volatile(struct snd_soc_codec *codec, unsigned int reg) { switch (reg) { case WM8804_RST_DEVID1: diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c index cd0959926d12..449ea09a193d 100644 --- a/sound/soc/codecs/wm8900.c +++ b/sound/soc/codecs/wm8900.c @@ -180,7 +180,7 @@ static const u16 wm8900_reg_defaults[WM8900_MAXREG] = { /* Remaining registers all zero */ }; -static int wm8900_volatile_register(unsigned int reg) +static int wm8900_volatile_register(struct snd_soc_codec *codec, unsigned int reg) { switch (reg) { case WM8900_REG_ID: diff --git a/sound/soc/codecs/wm8903.c b/sound/soc/codecs/wm8903.c index 987476a5895f..a2a446cb1807 100644 --- a/sound/soc/codecs/wm8903.c +++ b/sound/soc/codecs/wm8903.c @@ -232,7 +232,7 @@ struct wm8903_priv { int mic_delay; }; -static int wm8903_volatile_register(unsigned int reg) +static int wm8903_volatile_register(struct snd_soc_codec *codec, unsigned int reg) { switch (reg) { case WM8903_SW_RESET_AND_ID: diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c index 9de44a4c05c0..443ae580445c 100644 --- a/sound/soc/codecs/wm8904.c +++ b/sound/soc/codecs/wm8904.c @@ -596,7 +596,7 @@ static struct { { 0x003F, 0x003F, 0 }, /* R248 - FLL NCO Test 1 */ }; -static int wm8904_volatile_register(unsigned int reg) +static int wm8904_volatile_register(struct snd_soc_codec *codec, unsigned int reg) { return wm8904_access[reg].vol; } @@ -2436,19 +2436,28 @@ static int wm8904_probe(struct snd_soc_codec *codec) } /* Change some default settings - latch VU and enable ZC */ - reg_cache[WM8904_ADC_DIGITAL_VOLUME_LEFT] |= WM8904_ADC_VU; - reg_cache[WM8904_ADC_DIGITAL_VOLUME_RIGHT] |= WM8904_ADC_VU; - reg_cache[WM8904_DAC_DIGITAL_VOLUME_LEFT] |= WM8904_DAC_VU; - reg_cache[WM8904_DAC_DIGITAL_VOLUME_RIGHT] |= WM8904_DAC_VU; - reg_cache[WM8904_ANALOGUE_OUT1_LEFT] |= WM8904_HPOUT_VU | - WM8904_HPOUTLZC; - reg_cache[WM8904_ANALOGUE_OUT1_RIGHT] |= WM8904_HPOUT_VU | - WM8904_HPOUTRZC; - reg_cache[WM8904_ANALOGUE_OUT2_LEFT] |= WM8904_LINEOUT_VU | - WM8904_LINEOUTLZC; - reg_cache[WM8904_ANALOGUE_OUT2_RIGHT] |= WM8904_LINEOUT_VU | - WM8904_LINEOUTRZC; - reg_cache[WM8904_CLOCK_RATES_0] &= ~WM8904_SR_MODE; + snd_soc_update_bits(codec, WM8904_ADC_DIGITAL_VOLUME_LEFT, + WM8904_ADC_VU, WM8904_ADC_VU); + snd_soc_update_bits(codec, WM8904_ADC_DIGITAL_VOLUME_RIGHT, + WM8904_ADC_VU, WM8904_ADC_VU); + snd_soc_update_bits(codec, WM8904_DAC_DIGITAL_VOLUME_LEFT, + WM8904_DAC_VU, WM8904_DAC_VU); + snd_soc_update_bits(codec, WM8904_DAC_DIGITAL_VOLUME_RIGHT, + WM8904_DAC_VU, WM8904_DAC_VU); + snd_soc_update_bits(codec, WM8904_ANALOGUE_OUT1_LEFT, + WM8904_HPOUT_VU | WM8904_HPOUTLZC, + WM8904_HPOUT_VU | WM8904_HPOUTLZC); + snd_soc_update_bits(codec, WM8904_ANALOGUE_OUT1_RIGHT, + WM8904_HPOUT_VU | WM8904_HPOUTRZC, + WM8904_HPOUT_VU | WM8904_HPOUTRZC); + snd_soc_update_bits(codec, WM8904_ANALOGUE_OUT2_LEFT, + WM8904_LINEOUT_VU | WM8904_LINEOUTLZC, + WM8904_LINEOUT_VU | WM8904_LINEOUTLZC); + snd_soc_update_bits(codec, WM8904_ANALOGUE_OUT2_RIGHT, + WM8904_LINEOUT_VU | WM8904_LINEOUTRZC, + WM8904_LINEOUT_VU | WM8904_LINEOUTRZC); + snd_soc_update_bits(codec, WM8904_CLOCK_RATES_0, + WM8904_SR_MODE, 0); /* Apply configuration from the platform data. */ if (wm8904->pdata) { @@ -2469,10 +2478,12 @@ static int wm8904_probe(struct snd_soc_codec *codec) /* Set Class W by default - this will be managed by the Class * G widget at runtime where bypass paths are available. */ - reg_cache[WM8904_CLASS_W_0] |= WM8904_CP_DYN_PWR; + snd_soc_update_bits(codec, WM8904_CLASS_W_0, + WM8904_CP_DYN_PWR, WM8904_CP_DYN_PWR); /* Use normal bias source */ - reg_cache[WM8904_BIAS_CONTROL_0] &= ~WM8904_POBCTRL; + snd_soc_update_bits(codec, WM8904_BIAS_CONTROL_0, + WM8904_POBCTRL, 0); wm8904_set_bias_level(codec, SND_SOC_BIAS_STANDBY); diff --git a/sound/soc/codecs/wm8955.c b/sound/soc/codecs/wm8955.c index 7167dfc96aa7..5e0214d6293e 100644 --- a/sound/soc/codecs/wm8955.c +++ b/sound/soc/codecs/wm8955.c @@ -934,16 +934,27 @@ static int wm8955_probe(struct snd_soc_codec *codec) } /* Change some default settings - latch VU and enable ZC */ - reg_cache[WM8955_LEFT_DAC_VOLUME] |= WM8955_LDVU; - reg_cache[WM8955_RIGHT_DAC_VOLUME] |= WM8955_RDVU; - reg_cache[WM8955_LOUT1_VOLUME] |= WM8955_LO1VU | WM8955_LO1ZC; - reg_cache[WM8955_ROUT1_VOLUME] |= WM8955_RO1VU | WM8955_RO1ZC; - reg_cache[WM8955_LOUT2_VOLUME] |= WM8955_LO2VU | WM8955_LO2ZC; - reg_cache[WM8955_ROUT2_VOLUME] |= WM8955_RO2VU | WM8955_RO2ZC; - reg_cache[WM8955_MONOOUT_VOLUME] |= WM8955_MOZC; + snd_soc_update_bits(codec, WM8955_LEFT_DAC_VOLUME, + WM8955_LDVU, WM8955_LDVU); + snd_soc_update_bits(codec, WM8955_RIGHT_DAC_VOLUME, + WM8955_RDVU, WM8955_RDVU); + snd_soc_update_bits(codec, WM8955_LOUT1_VOLUME, + WM8955_LO1VU | WM8955_LO1ZC, + WM8955_LO1VU | WM8955_LO1ZC); + snd_soc_update_bits(codec, WM8955_ROUT1_VOLUME, + WM8955_RO1VU | WM8955_RO1ZC, + WM8955_RO1VU | WM8955_RO1ZC); + snd_soc_update_bits(codec, WM8955_LOUT2_VOLUME, + WM8955_LO2VU | WM8955_LO2ZC, + WM8955_LO2VU | WM8955_LO2ZC); + snd_soc_update_bits(codec, WM8955_ROUT2_VOLUME, + WM8955_RO2VU | WM8955_RO2ZC, + WM8955_RO2VU | WM8955_RO2ZC); + snd_soc_update_bits(codec, WM8955_MONOOUT_VOLUME, + WM8955_MOZC, WM8955_MOZC); /* Also enable adaptive bass boost by default */ - reg_cache[WM8955_BASS_CONTROL] |= WM8955_BB; + snd_soc_update_bits(codec, WM8955_BASS_CONTROL, WM8955_BB, WM8955_BB); /* Set platform data values */ if (pdata) { diff --git a/sound/soc/codecs/wm8961.c b/sound/soc/codecs/wm8961.c index 55252e7d02c9..cdee8103d09b 100644 --- a/sound/soc/codecs/wm8961.c +++ b/sound/soc/codecs/wm8961.c @@ -291,7 +291,7 @@ struct wm8961_priv { int sysclk; }; -static int wm8961_volatile_register(unsigned int reg) +static int wm8961_volatile_register(struct snd_soc_codec *codec, unsigned int reg) { switch (reg) { case WM8961_SOFTWARE_RESET: diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c index b9cb1fcf8c92..5c7b730a864f 100644 --- a/sound/soc/codecs/wm8962.c +++ b/sound/soc/codecs/wm8962.c @@ -1938,7 +1938,7 @@ static const struct wm8962_reg_access { [21139] = { 0xFFFF, 0xFFFF, 0x0000 }, /* R21139 - VSS_XTS32_0 */ }; -static int wm8962_volatile_register(unsigned int reg) +static int wm8962_volatile_register(struct snd_soc_codec *codec, unsigned int reg) { if (wm8962_reg_access[reg].vol) return 1; @@ -1946,7 +1946,7 @@ static int wm8962_volatile_register(unsigned int reg) return 0; } -static int wm8962_readable_register(unsigned int reg) +static int wm8962_readable_register(struct snd_soc_codec *codec, unsigned int reg) { if (wm8962_reg_access[reg].read) return 1; @@ -3822,16 +3822,26 @@ static int wm8962_probe(struct snd_soc_codec *codec) } /* Latch volume update bits */ - reg_cache[WM8962_LEFT_INPUT_VOLUME] |= WM8962_IN_VU; - reg_cache[WM8962_RIGHT_INPUT_VOLUME] |= WM8962_IN_VU; - reg_cache[WM8962_LEFT_ADC_VOLUME] |= WM8962_ADC_VU; - reg_cache[WM8962_RIGHT_ADC_VOLUME] |= WM8962_ADC_VU; - reg_cache[WM8962_LEFT_DAC_VOLUME] |= WM8962_DAC_VU; - reg_cache[WM8962_RIGHT_DAC_VOLUME] |= WM8962_DAC_VU; - reg_cache[WM8962_SPKOUTL_VOLUME] |= WM8962_SPKOUT_VU; - reg_cache[WM8962_SPKOUTR_VOLUME] |= WM8962_SPKOUT_VU; - reg_cache[WM8962_HPOUTL_VOLUME] |= WM8962_HPOUT_VU; - reg_cache[WM8962_HPOUTR_VOLUME] |= WM8962_HPOUT_VU; + snd_soc_update_bits(codec, WM8962_LEFT_INPUT_VOLUME, + WM8962_IN_VU, WM8962_IN_VU); + snd_soc_update_bits(codec, WM8962_RIGHT_INPUT_VOLUME, + WM8962_IN_VU, WM8962_IN_VU); + snd_soc_update_bits(codec, WM8962_LEFT_ADC_VOLUME, + WM8962_ADC_VU, WM8962_ADC_VU); + snd_soc_update_bits(codec, WM8962_RIGHT_ADC_VOLUME, + WM8962_ADC_VU, WM8962_ADC_VU); + snd_soc_update_bits(codec, WM8962_LEFT_DAC_VOLUME, + WM8962_DAC_VU, WM8962_DAC_VU); + snd_soc_update_bits(codec, WM8962_RIGHT_DAC_VOLUME, + WM8962_DAC_VU, WM8962_DAC_VU); + snd_soc_update_bits(codec, WM8962_SPKOUTL_VOLUME, + WM8962_SPKOUT_VU, WM8962_SPKOUT_VU); + snd_soc_update_bits(codec, WM8962_SPKOUTR_VOLUME, + WM8962_SPKOUT_VU, WM8962_SPKOUT_VU); + snd_soc_update_bits(codec, WM8962_HPOUTL_VOLUME, + WM8962_HPOUT_VU, WM8962_HPOUT_VU); + snd_soc_update_bits(codec, WM8962_HPOUTR_VOLUME, + WM8962_HPOUT_VU, WM8962_HPOUT_VU); wm8962_add_widgets(codec); diff --git a/sound/soc/codecs/wm8978.c b/sound/soc/codecs/wm8978.c index 4bbc3442703f..30fb48ec2799 100644 --- a/sound/soc/codecs/wm8978.c +++ b/sound/soc/codecs/wm8978.c @@ -965,7 +965,7 @@ static int wm8978_probe(struct snd_soc_codec *codec) * written. */ for (i = 0; i < ARRAY_SIZE(update_reg); i++) - ((u16 *)codec->reg_cache)[update_reg[i]] |= 0x100; + snd_soc_update_bits(codec, update_reg[i], 0x100, 0x100); /* Reset the codec */ ret = snd_soc_write(codec, WM8978_RESET, 0); diff --git a/sound/soc/codecs/wm8991.c b/sound/soc/codecs/wm8991.c new file mode 100644 index 000000000000..28fdfd66661d --- /dev/null +++ b/sound/soc/codecs/wm8991.c @@ -0,0 +1,1427 @@ +/* + * wm8991.c -- WM8991 ALSA Soc Audio driver + * + * Copyright 2007-2010 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * linux@wolfsonmicro.com + * + * 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 <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/version.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> +#include <asm/div64.h> + +#include "wm8991.h" + +struct wm8991_priv { + enum snd_soc_control_type control_type; + unsigned int pcmclk; +}; + +static const u16 wm8991_reg_defs[] = { + 0x8991, /* R0 - 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) */ + 0x0040, /* 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 */ + 0x0100, /* R14 - ADC CTRL */ + 0x00C0, /* R15 - Left ADC Digital Volume */ + 0x00C0, /* R16 - Right ADC Digital Volume */ + 0x0000, /* R17 */ + 0x0000, /* R18 - GPIO CTRL 1 */ + 0x1000, /* R19 - GPIO1 & GPIO2 */ + 0x1010, /* R20 - GPIO3 & GPIO4 */ + 0x1010, /* R21 - GPIO5 & GPIO6 */ + 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 */ + 0x0000, /* R28 - Left Output Volume */ + 0x0000, /* R29 - Right Output Volume */ + 0x0066, /* R30 - Line Outputs Volume */ + 0x0022, /* R31 - Out3/4 Volume */ + 0x0079, /* R32 - Left OPGA Volume */ + 0x0079, /* R33 - Right OPGA Volume */ + 0x0003, /* R34 - Speaker Volume */ + 0x0003, /* R35 - ClassD1 */ + 0x0000, /* R36 */ + 0x0100, /* R37 - ClassD3 */ + 0x0000, /* R38 */ + 0x0000, /* R39 - Input Mixer1 */ + 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 */ + 0x0180, /* R51 - Out3/4 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 */ + 0x0008, /* R60 - PLL1 */ + 0x0031, /* R61 - PLL2 */ + 0x0026, /* R62 - PLL3 */ +}; + +#define wm8991_reset(c) snd_soc_write(c, WM8991_RESET, 0) + +static const unsigned int rec_mix_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 7, TLV_DB_LINEAR_ITEM(-1500, 600), +}; + +static const unsigned int in_pga_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 0x1F, TLV_DB_LINEAR_ITEM(-1650, 3000), +}; + +static const unsigned int out_mix_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 7, TLV_DB_LINEAR_ITEM(0, -2100), +}; + +static const unsigned int out_pga_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 127, TLV_DB_LINEAR_ITEM(-7300, 600), +}; + +static const unsigned int out_omix_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 7, TLV_DB_LINEAR_ITEM(-600, 0), +}; + +static const unsigned int out_dac_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 255, TLV_DB_LINEAR_ITEM(-7163, 0), +}; + +static const unsigned int in_adc_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 255, TLV_DB_LINEAR_ITEM(-7163, 1763), +}; + +static const unsigned int out_sidetone_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 31, TLV_DB_LINEAR_ITEM(-3600, 0), +}; + +static int wm899x_outpga_put_volsw_vu(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int reg = kcontrol->private_value & 0xff; + int ret; + u16 val; + + ret = snd_soc_put_volsw(kcontrol, ucontrol); + if (ret < 0) + return ret; + + /* now hit the volume update bits (always bit 8) */ + val = snd_soc_read(codec, reg); + return snd_soc_write(codec, reg, val | 0x0100); +} + +static const char *wm8991_digital_sidetone[] = +{"None", "Left ADC", "Right ADC", "Reserved"}; + +static const struct soc_enum wm8991_left_digital_sidetone_enum = + SOC_ENUM_SINGLE(WM8991_DIGITAL_SIDE_TONE, + WM8991_ADC_TO_DACL_SHIFT, + WM8991_ADC_TO_DACL_MASK, + wm8991_digital_sidetone); + +static const struct soc_enum wm8991_right_digital_sidetone_enum = + SOC_ENUM_SINGLE(WM8991_DIGITAL_SIDE_TONE, + WM8991_ADC_TO_DACR_SHIFT, + WM8991_ADC_TO_DACR_MASK, + wm8991_digital_sidetone); + +static const char *wm8991_adcmode[] = +{"Hi-fi mode", "Voice mode 1", "Voice mode 2", "Voice mode 3"}; + +static const struct soc_enum wm8991_right_adcmode_enum = + SOC_ENUM_SINGLE(WM8991_ADC_CTRL, + WM8991_ADC_HPF_CUT_SHIFT, + WM8991_ADC_HPF_CUT_MASK, + wm8991_adcmode); + +static const struct snd_kcontrol_new wm8991_snd_controls[] = { + /* INMIXL */ + SOC_SINGLE("LIN12 PGA Boost", WM8991_INPUT_MIXER3, WM8991_L12MNBST_BIT, 1, 0), + SOC_SINGLE("LIN34 PGA Boost", WM8991_INPUT_MIXER3, WM8991_L34MNBST_BIT, 1, 0), + /* INMIXR */ + SOC_SINGLE("RIN12 PGA Boost", WM8991_INPUT_MIXER3, WM8991_R12MNBST_BIT, 1, 0), + SOC_SINGLE("RIN34 PGA Boost", WM8991_INPUT_MIXER3, WM8991_R34MNBST_BIT, 1, 0), + + /* LOMIX */ + SOC_SINGLE_TLV("LOMIX LIN3 Bypass Volume", WM8991_OUTPUT_MIXER3, + WM8991_LLI3LOVOL_SHIFT, WM8991_LLI3LOVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("LOMIX RIN12 PGA Bypass Volume", WM8991_OUTPUT_MIXER3, + WM8991_LR12LOVOL_SHIFT, WM8991_LR12LOVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("LOMIX LIN12 PGA Bypass Volume", WM8991_OUTPUT_MIXER3, + WM8991_LL12LOVOL_SHIFT, WM8991_LL12LOVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("LOMIX RIN3 Bypass Volume", WM8991_OUTPUT_MIXER5, + WM8991_LRI3LOVOL_SHIFT, WM8991_LRI3LOVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("LOMIX AINRMUX Bypass Volume", WM8991_OUTPUT_MIXER5, + WM8991_LRBLOVOL_SHIFT, WM8991_LRBLOVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("LOMIX AINLMUX Bypass Volume", WM8991_OUTPUT_MIXER5, + WM8991_LRBLOVOL_SHIFT, WM8991_LRBLOVOL_MASK, 1, out_mix_tlv), + + /* ROMIX */ + SOC_SINGLE_TLV("ROMIX RIN3 Bypass Volume", WM8991_OUTPUT_MIXER4, + WM8991_RRI3ROVOL_SHIFT, WM8991_RRI3ROVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("ROMIX LIN12 PGA Bypass Volume", WM8991_OUTPUT_MIXER4, + WM8991_RL12ROVOL_SHIFT, WM8991_RL12ROVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("ROMIX RIN12 PGA Bypass Volume", WM8991_OUTPUT_MIXER4, + WM8991_RR12ROVOL_SHIFT, WM8991_RR12ROVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("ROMIX LIN3 Bypass Volume", WM8991_OUTPUT_MIXER6, + WM8991_RLI3ROVOL_SHIFT, WM8991_RLI3ROVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("ROMIX AINLMUX Bypass Volume", WM8991_OUTPUT_MIXER6, + WM8991_RLBROVOL_SHIFT, WM8991_RLBROVOL_MASK, 1, out_mix_tlv), + SOC_SINGLE_TLV("ROMIX AINRMUX Bypass Volume", WM8991_OUTPUT_MIXER6, + WM8991_RRBROVOL_SHIFT, WM8991_RRBROVOL_MASK, 1, out_mix_tlv), + + /* LOUT */ + SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOUT Volume", WM8991_LEFT_OUTPUT_VOLUME, + WM8991_LOUTVOL_SHIFT, WM8991_LOUTVOL_MASK, 0, out_pga_tlv), + SOC_SINGLE("LOUT ZC", WM8991_LEFT_OUTPUT_VOLUME, WM8991_LOZC_BIT, 1, 0), + + /* ROUT */ + SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROUT Volume", WM8991_RIGHT_OUTPUT_VOLUME, + WM8991_ROUTVOL_SHIFT, WM8991_ROUTVOL_MASK, 0, out_pga_tlv), + SOC_SINGLE("ROUT ZC", WM8991_RIGHT_OUTPUT_VOLUME, WM8991_ROZC_BIT, 1, 0), + + /* LOPGA */ + SOC_WM899X_OUTPGA_SINGLE_R_TLV("LOPGA Volume", WM8991_LEFT_OPGA_VOLUME, + WM8991_LOPGAVOL_SHIFT, WM8991_LOPGAVOL_MASK, 0, out_pga_tlv), + SOC_SINGLE("LOPGA ZC Switch", WM8991_LEFT_OPGA_VOLUME, + WM8991_LOPGAZC_BIT, 1, 0), + + /* ROPGA */ + SOC_WM899X_OUTPGA_SINGLE_R_TLV("ROPGA Volume", WM8991_RIGHT_OPGA_VOLUME, + WM8991_ROPGAVOL_SHIFT, WM8991_ROPGAVOL_MASK, 0, out_pga_tlv), + SOC_SINGLE("ROPGA ZC Switch", WM8991_RIGHT_OPGA_VOLUME, + WM8991_ROPGAZC_BIT, 1, 0), + + SOC_SINGLE("LON Mute Switch", WM8991_LINE_OUTPUTS_VOLUME, + WM8991_LONMUTE_BIT, 1, 0), + SOC_SINGLE("LOP Mute Switch", WM8991_LINE_OUTPUTS_VOLUME, + WM8991_LOPMUTE_BIT, 1, 0), + SOC_SINGLE("LOP Attenuation Switch", WM8991_LINE_OUTPUTS_VOLUME, + WM8991_LOATTN_BIT, 1, 0), + SOC_SINGLE("RON Mute Switch", WM8991_LINE_OUTPUTS_VOLUME, + WM8991_RONMUTE_BIT, 1, 0), + SOC_SINGLE("ROP Mute Switch", WM8991_LINE_OUTPUTS_VOLUME, + WM8991_ROPMUTE_BIT, 1, 0), + SOC_SINGLE("ROP Attenuation Switch", WM8991_LINE_OUTPUTS_VOLUME, + WM8991_ROATTN_BIT, 1, 0), + + SOC_SINGLE("OUT3 Mute Switch", WM8991_OUT3_4_VOLUME, + WM8991_OUT3MUTE_BIT, 1, 0), + SOC_SINGLE("OUT3 Attenuation Switch", WM8991_OUT3_4_VOLUME, + WM8991_OUT3ATTN_BIT, 1, 0), + + SOC_SINGLE("OUT4 Mute Switch", WM8991_OUT3_4_VOLUME, + WM8991_OUT4MUTE_BIT, 1, 0), + SOC_SINGLE("OUT4 Attenuation Switch", WM8991_OUT3_4_VOLUME, + WM8991_OUT4ATTN_BIT, 1, 0), + + SOC_SINGLE("Speaker Mode Switch", WM8991_CLASSD1, + WM8991_CDMODE_BIT, 1, 0), + + SOC_SINGLE("Speaker Output Attenuation Volume", WM8991_SPEAKER_VOLUME, + WM8991_SPKVOL_SHIFT, WM8991_SPKVOL_MASK, 0), + SOC_SINGLE("Speaker DC Boost Volume", WM8991_CLASSD3, + WM8991_DCGAIN_SHIFT, WM8991_DCGAIN_MASK, 0), + SOC_SINGLE("Speaker AC Boost Volume", WM8991_CLASSD3, + WM8991_ACGAIN_SHIFT, WM8991_ACGAIN_MASK, 0), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left DAC Digital Volume", + WM8991_LEFT_DAC_DIGITAL_VOLUME, + WM8991_DACL_VOL_SHIFT, + WM8991_DACL_VOL_MASK, + 0, + out_dac_tlv), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right DAC Digital Volume", + WM8991_RIGHT_DAC_DIGITAL_VOLUME, + WM8991_DACR_VOL_SHIFT, + WM8991_DACR_VOL_MASK, + 0, + out_dac_tlv), + + SOC_ENUM("Left Digital Sidetone", wm8991_left_digital_sidetone_enum), + SOC_ENUM("Right Digital Sidetone", wm8991_right_digital_sidetone_enum), + + SOC_SINGLE_TLV("Left Digital Sidetone Volume", WM8991_DIGITAL_SIDE_TONE, + WM8991_ADCL_DAC_SVOL_SHIFT, WM8991_ADCL_DAC_SVOL_MASK, 0, + out_sidetone_tlv), + SOC_SINGLE_TLV("Right Digital Sidetone Volume", WM8991_DIGITAL_SIDE_TONE, + WM8991_ADCR_DAC_SVOL_SHIFT, WM8991_ADCR_DAC_SVOL_MASK, 0, + out_sidetone_tlv), + + SOC_SINGLE("ADC Digital High Pass Filter Switch", WM8991_ADC_CTRL, + WM8991_ADC_HPF_ENA_BIT, 1, 0), + + SOC_ENUM("ADC HPF Mode", wm8991_right_adcmode_enum), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("Left ADC Digital Volume", + WM8991_LEFT_ADC_DIGITAL_VOLUME, + WM8991_ADCL_VOL_SHIFT, + WM8991_ADCL_VOL_MASK, + 0, + in_adc_tlv), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("Right ADC Digital Volume", + WM8991_RIGHT_ADC_DIGITAL_VOLUME, + WM8991_ADCR_VOL_SHIFT, + WM8991_ADCR_VOL_MASK, + 0, + in_adc_tlv), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN12 Volume", + WM8991_LEFT_LINE_INPUT_1_2_VOLUME, + WM8991_LIN12VOL_SHIFT, + WM8991_LIN12VOL_MASK, + 0, + in_pga_tlv), + + SOC_SINGLE("LIN12 ZC Switch", WM8991_LEFT_LINE_INPUT_1_2_VOLUME, + WM8991_LI12ZC_BIT, 1, 0), + + SOC_SINGLE("LIN12 Mute Switch", WM8991_LEFT_LINE_INPUT_1_2_VOLUME, + WM8991_LI12MUTE_BIT, 1, 0), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("LIN34 Volume", + WM8991_LEFT_LINE_INPUT_3_4_VOLUME, + WM8991_LIN34VOL_SHIFT, + WM8991_LIN34VOL_MASK, + 0, + in_pga_tlv), + + SOC_SINGLE("LIN34 ZC Switch", WM8991_LEFT_LINE_INPUT_3_4_VOLUME, + WM8991_LI34ZC_BIT, 1, 0), + + SOC_SINGLE("LIN34 Mute Switch", WM8991_LEFT_LINE_INPUT_3_4_VOLUME, + WM8991_LI34MUTE_BIT, 1, 0), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN12 Volume", + WM8991_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8991_RIN12VOL_SHIFT, + WM8991_RIN12VOL_MASK, + 0, + in_pga_tlv), + + SOC_SINGLE("RIN12 ZC Switch", WM8991_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8991_RI12ZC_BIT, 1, 0), + + SOC_SINGLE("RIN12 Mute Switch", WM8991_RIGHT_LINE_INPUT_1_2_VOLUME, + WM8991_RI12MUTE_BIT, 1, 0), + + SOC_WM899X_OUTPGA_SINGLE_R_TLV("RIN34 Volume", + WM8991_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8991_RIN34VOL_SHIFT, + WM8991_RIN34VOL_MASK, + 0, + in_pga_tlv), + + SOC_SINGLE("RIN34 ZC Switch", WM8991_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8991_RI34ZC_BIT, 1, 0), + + SOC_SINGLE("RIN34 Mute Switch", WM8991_RIGHT_LINE_INPUT_3_4_VOLUME, + WM8991_RI34MUTE_BIT, 1, 0), +}; + +/* + * _DAPM_ Controls + */ +static int inmixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + u16 reg, fakepower; + + reg = snd_soc_read(w->codec, WM8991_POWER_MANAGEMENT_2); + fakepower = snd_soc_read(w->codec, WM8991_INTDRIVBITS); + + if (fakepower & ((1 << WM8991_INMIXL_PWR_BIT) | + (1 << WM8991_AINLMUX_PWR_BIT))) + reg |= WM8991_AINL_ENA; + else + reg &= ~WM8991_AINL_ENA; + + if (fakepower & ((1 << WM8991_INMIXR_PWR_BIT) | + (1 << WM8991_AINRMUX_PWR_BIT))) + reg |= WM8991_AINR_ENA; + else + reg &= ~WM8991_AINL_ENA; + + snd_soc_write(w->codec, WM8991_POWER_MANAGEMENT_2, reg); + return 0; +} + +static int outmixer_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + u32 reg_shift = kcontrol->private_value & 0xfff; + int ret = 0; + u16 reg; + + switch (reg_shift) { + case WM8991_SPEAKER_MIXER | (WM8991_LDSPK_BIT << 8): + reg = snd_soc_read(w->codec, WM8991_OUTPUT_MIXER1); + if (reg & WM8991_LDLO) { + printk(KERN_WARNING + "Cannot set as Output Mixer 1 LDLO Set\n"); + ret = -1; + } + break; + + case WM8991_SPEAKER_MIXER | (WM8991_RDSPK_BIT << 8): + reg = snd_soc_read(w->codec, WM8991_OUTPUT_MIXER2); + if (reg & WM8991_RDRO) { + printk(KERN_WARNING + "Cannot set as Output Mixer 2 RDRO Set\n"); + ret = -1; + } + break; + + case WM8991_OUTPUT_MIXER1 | (WM8991_LDLO_BIT << 8): + reg = snd_soc_read(w->codec, WM8991_SPEAKER_MIXER); + if (reg & WM8991_LDSPK) { + printk(KERN_WARNING + "Cannot set as Speaker Mixer LDSPK Set\n"); + ret = -1; + } + break; + + case WM8991_OUTPUT_MIXER2 | (WM8991_RDRO_BIT << 8): + reg = snd_soc_read(w->codec, WM8991_SPEAKER_MIXER); + if (reg & WM8991_RDSPK) { + printk(KERN_WARNING + "Cannot set as Speaker Mixer RDSPK Set\n"); + ret = -1; + } + break; + } + + return ret; +} + +/* INMIX dB values */ +static const unsigned int in_mix_tlv[] = { + TLV_DB_RANGE_HEAD(1), + 0, 7, TLV_DB_LINEAR_ITEM(-1200, 600), +}; + +/* Left In PGA Connections */ +static const struct snd_kcontrol_new wm8991_dapm_lin12_pga_controls[] = { + SOC_DAPM_SINGLE("LIN1 Switch", WM8991_INPUT_MIXER2, WM8991_LMN1_BIT, 1, 0), + SOC_DAPM_SINGLE("LIN2 Switch", WM8991_INPUT_MIXER2, WM8991_LMP2_BIT, 1, 0), +}; + +static const struct snd_kcontrol_new wm8991_dapm_lin34_pga_controls[] = { + SOC_DAPM_SINGLE("LIN3 Switch", WM8991_INPUT_MIXER2, WM8991_LMN3_BIT, 1, 0), + SOC_DAPM_SINGLE("LIN4 Switch", WM8991_INPUT_MIXER2, WM8991_LMP4_BIT, 1, 0), +}; + +/* Right In PGA Connections */ +static const struct snd_kcontrol_new wm8991_dapm_rin12_pga_controls[] = { + SOC_DAPM_SINGLE("RIN1 Switch", WM8991_INPUT_MIXER2, WM8991_RMN1_BIT, 1, 0), + SOC_DAPM_SINGLE("RIN2 Switch", WM8991_INPUT_MIXER2, WM8991_RMP2_BIT, 1, 0), +}; + +static const struct snd_kcontrol_new wm8991_dapm_rin34_pga_controls[] = { + SOC_DAPM_SINGLE("RIN3 Switch", WM8991_INPUT_MIXER2, WM8991_RMN3_BIT, 1, 0), + SOC_DAPM_SINGLE("RIN4 Switch", WM8991_INPUT_MIXER2, WM8991_RMP4_BIT, 1, 0), +}; + +/* INMIXL */ +static const struct snd_kcontrol_new wm8991_dapm_inmixl_controls[] = { + SOC_DAPM_SINGLE_TLV("Record Left Volume", WM8991_INPUT_MIXER3, + WM8991_LDBVOL_SHIFT, WM8991_LDBVOL_MASK, 0, in_mix_tlv), + SOC_DAPM_SINGLE_TLV("LIN2 Volume", WM8991_INPUT_MIXER5, WM8991_LI2BVOL_SHIFT, + 7, 0, in_mix_tlv), + SOC_DAPM_SINGLE("LINPGA12 Switch", WM8991_INPUT_MIXER3, WM8991_L12MNB_BIT, + 1, 0), + SOC_DAPM_SINGLE("LINPGA34 Switch", WM8991_INPUT_MIXER3, WM8991_L34MNB_BIT, + 1, 0), +}; + +/* INMIXR */ +static const struct snd_kcontrol_new wm8991_dapm_inmixr_controls[] = { + SOC_DAPM_SINGLE_TLV("Record Right Volume", WM8991_INPUT_MIXER4, + WM8991_RDBVOL_SHIFT, WM8991_RDBVOL_MASK, 0, in_mix_tlv), + SOC_DAPM_SINGLE_TLV("RIN2 Volume", WM8991_INPUT_MIXER6, WM8991_RI2BVOL_SHIFT, + 7, 0, in_mix_tlv), + SOC_DAPM_SINGLE("RINPGA12 Switch", WM8991_INPUT_MIXER3, WM8991_L12MNB_BIT, + 1, 0), + SOC_DAPM_SINGLE("RINPGA34 Switch", WM8991_INPUT_MIXER3, WM8991_L34MNB_BIT, + 1, 0), +}; + +/* AINLMUX */ +static const char *wm8991_ainlmux[] = +{"INMIXL Mix", "RXVOICE Mix", "DIFFINL Mix"}; + +static const struct soc_enum wm8991_ainlmux_enum = + SOC_ENUM_SINGLE(WM8991_INPUT_MIXER1, WM8991_AINLMODE_SHIFT, + ARRAY_SIZE(wm8991_ainlmux), wm8991_ainlmux); + +static const struct snd_kcontrol_new wm8991_dapm_ainlmux_controls = + SOC_DAPM_ENUM("Route", wm8991_ainlmux_enum); + +/* DIFFINL */ + +/* AINRMUX */ +static const char *wm8991_ainrmux[] = +{"INMIXR Mix", "RXVOICE Mix", "DIFFINR Mix"}; + +static const struct soc_enum wm8991_ainrmux_enum = + SOC_ENUM_SINGLE(WM8991_INPUT_MIXER1, WM8991_AINRMODE_SHIFT, + ARRAY_SIZE(wm8991_ainrmux), wm8991_ainrmux); + +static const struct snd_kcontrol_new wm8991_dapm_ainrmux_controls = + SOC_DAPM_ENUM("Route", wm8991_ainrmux_enum); + +/* RXVOICE */ +static const struct snd_kcontrol_new wm8991_dapm_rxvoice_controls[] = { + SOC_DAPM_SINGLE_TLV("LIN4RXN", WM8991_INPUT_MIXER5, WM8991_LR4BVOL_SHIFT, + WM8991_LR4BVOL_MASK, 0, in_mix_tlv), + SOC_DAPM_SINGLE_TLV("RIN4RXP", WM8991_INPUT_MIXER6, WM8991_RL4BVOL_SHIFT, + WM8991_RL4BVOL_MASK, 0, in_mix_tlv), +}; + +/* LOMIX */ +static const struct snd_kcontrol_new wm8991_dapm_lomix_controls[] = { + SOC_DAPM_SINGLE("LOMIX Right ADC Bypass Switch", WM8991_OUTPUT_MIXER1, + WM8991_LRBLO_BIT, 1, 0), + SOC_DAPM_SINGLE("LOMIX Left ADC Bypass Switch", WM8991_OUTPUT_MIXER1, + WM8991_LLBLO_BIT, 1, 0), + SOC_DAPM_SINGLE("LOMIX RIN3 Bypass Switch", WM8991_OUTPUT_MIXER1, + WM8991_LRI3LO_BIT, 1, 0), + SOC_DAPM_SINGLE("LOMIX LIN3 Bypass Switch", WM8991_OUTPUT_MIXER1, + WM8991_LLI3LO_BIT, 1, 0), + SOC_DAPM_SINGLE("LOMIX RIN12 PGA Bypass Switch", WM8991_OUTPUT_MIXER1, + WM8991_LR12LO_BIT, 1, 0), + SOC_DAPM_SINGLE("LOMIX LIN12 PGA Bypass Switch", WM8991_OUTPUT_MIXER1, + WM8991_LL12LO_BIT, 1, 0), + SOC_DAPM_SINGLE("LOMIX Left DAC Switch", WM8991_OUTPUT_MIXER1, + WM8991_LDLO_BIT, 1, 0), +}; + +/* ROMIX */ +static const struct snd_kcontrol_new wm8991_dapm_romix_controls[] = { + SOC_DAPM_SINGLE("ROMIX Left ADC Bypass Switch", WM8991_OUTPUT_MIXER2, + WM8991_RLBRO_BIT, 1, 0), + SOC_DAPM_SINGLE("ROMIX Right ADC Bypass Switch", WM8991_OUTPUT_MIXER2, + WM8991_RRBRO_BIT, 1, 0), + SOC_DAPM_SINGLE("ROMIX LIN3 Bypass Switch", WM8991_OUTPUT_MIXER2, + WM8991_RLI3RO_BIT, 1, 0), + SOC_DAPM_SINGLE("ROMIX RIN3 Bypass Switch", WM8991_OUTPUT_MIXER2, + WM8991_RRI3RO_BIT, 1, 0), + SOC_DAPM_SINGLE("ROMIX LIN12 PGA Bypass Switch", WM8991_OUTPUT_MIXER2, + WM8991_RL12RO_BIT, 1, 0), + SOC_DAPM_SINGLE("ROMIX RIN12 PGA Bypass Switch", WM8991_OUTPUT_MIXER2, + WM8991_RR12RO_BIT, 1, 0), + SOC_DAPM_SINGLE("ROMIX Right DAC Switch", WM8991_OUTPUT_MIXER2, + WM8991_RDRO_BIT, 1, 0), +}; + +/* LONMIX */ +static const struct snd_kcontrol_new wm8991_dapm_lonmix_controls[] = { + SOC_DAPM_SINGLE("LONMIX Left Mixer PGA Switch", WM8991_LINE_MIXER1, + WM8991_LLOPGALON_BIT, 1, 0), + SOC_DAPM_SINGLE("LONMIX Right Mixer PGA Switch", WM8991_LINE_MIXER1, + WM8991_LROPGALON_BIT, 1, 0), + SOC_DAPM_SINGLE("LONMIX Inverted LOP Switch", WM8991_LINE_MIXER1, + WM8991_LOPLON_BIT, 1, 0), +}; + +/* LOPMIX */ +static const struct snd_kcontrol_new wm8991_dapm_lopmix_controls[] = { + SOC_DAPM_SINGLE("LOPMIX Right Mic Bypass Switch", WM8991_LINE_MIXER1, + WM8991_LR12LOP_BIT, 1, 0), + SOC_DAPM_SINGLE("LOPMIX Left Mic Bypass Switch", WM8991_LINE_MIXER1, + WM8991_LL12LOP_BIT, 1, 0), + SOC_DAPM_SINGLE("LOPMIX Left Mixer PGA Switch", WM8991_LINE_MIXER1, + WM8991_LLOPGALOP_BIT, 1, 0), +}; + +/* RONMIX */ +static const struct snd_kcontrol_new wm8991_dapm_ronmix_controls[] = { + SOC_DAPM_SINGLE("RONMIX Right Mixer PGA Switch", WM8991_LINE_MIXER2, + WM8991_RROPGARON_BIT, 1, 0), + SOC_DAPM_SINGLE("RONMIX Left Mixer PGA Switch", WM8991_LINE_MIXER2, + WM8991_RLOPGARON_BIT, 1, 0), + SOC_DAPM_SINGLE("RONMIX Inverted ROP Switch", WM8991_LINE_MIXER2, + WM8991_ROPRON_BIT, 1, 0), +}; + +/* ROPMIX */ +static const struct snd_kcontrol_new wm8991_dapm_ropmix_controls[] = { + SOC_DAPM_SINGLE("ROPMIX Left Mic Bypass Switch", WM8991_LINE_MIXER2, + WM8991_RL12ROP_BIT, 1, 0), + SOC_DAPM_SINGLE("ROPMIX Right Mic Bypass Switch", WM8991_LINE_MIXER2, + WM8991_RR12ROP_BIT, 1, 0), + SOC_DAPM_SINGLE("ROPMIX Right Mixer PGA Switch", WM8991_LINE_MIXER2, + WM8991_RROPGAROP_BIT, 1, 0), +}; + +/* OUT3MIX */ +static const struct snd_kcontrol_new wm8991_dapm_out3mix_controls[] = { + SOC_DAPM_SINGLE("OUT3MIX LIN4RXN Bypass Switch", WM8991_OUT3_4_MIXER, + WM8991_LI4O3_BIT, 1, 0), + SOC_DAPM_SINGLE("OUT3MIX Left Out PGA Switch", WM8991_OUT3_4_MIXER, + WM8991_LPGAO3_BIT, 1, 0), +}; + +/* OUT4MIX */ +static const struct snd_kcontrol_new wm8991_dapm_out4mix_controls[] = { + SOC_DAPM_SINGLE("OUT4MIX Right Out PGA Switch", WM8991_OUT3_4_MIXER, + WM8991_RPGAO4_BIT, 1, 0), + SOC_DAPM_SINGLE("OUT4MIX RIN4RXP Bypass Switch", WM8991_OUT3_4_MIXER, + WM8991_RI4O4_BIT, 1, 0), +}; + +/* SPKMIX */ +static const struct snd_kcontrol_new wm8991_dapm_spkmix_controls[] = { + SOC_DAPM_SINGLE("SPKMIX LIN2 Bypass Switch", WM8991_SPEAKER_MIXER, + WM8991_LI2SPK_BIT, 1, 0), + SOC_DAPM_SINGLE("SPKMIX LADC Bypass Switch", WM8991_SPEAKER_MIXER, + WM8991_LB2SPK_BIT, 1, 0), + SOC_DAPM_SINGLE("SPKMIX Left Mixer PGA Switch", WM8991_SPEAKER_MIXER, + WM8991_LOPGASPK_BIT, 1, 0), + SOC_DAPM_SINGLE("SPKMIX Left DAC Switch", WM8991_SPEAKER_MIXER, + WM8991_LDSPK_BIT, 1, 0), + SOC_DAPM_SINGLE("SPKMIX Right DAC Switch", WM8991_SPEAKER_MIXER, + WM8991_RDSPK_BIT, 1, 0), + SOC_DAPM_SINGLE("SPKMIX Right Mixer PGA Switch", WM8991_SPEAKER_MIXER, + WM8991_ROPGASPK_BIT, 1, 0), + SOC_DAPM_SINGLE("SPKMIX RADC Bypass Switch", WM8991_SPEAKER_MIXER, + WM8991_RL12ROP_BIT, 1, 0), + SOC_DAPM_SINGLE("SPKMIX RIN2 Bypass Switch", WM8991_SPEAKER_MIXER, + WM8991_RI2SPK_BIT, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8991_dapm_widgets[] = { + /* Input Side */ + /* Input Lines */ + SND_SOC_DAPM_INPUT("LIN1"), + SND_SOC_DAPM_INPUT("LIN2"), + SND_SOC_DAPM_INPUT("LIN3"), + SND_SOC_DAPM_INPUT("LIN4RXN"), + SND_SOC_DAPM_INPUT("RIN3"), + SND_SOC_DAPM_INPUT("RIN4RXP"), + SND_SOC_DAPM_INPUT("RIN1"), + SND_SOC_DAPM_INPUT("RIN2"), + SND_SOC_DAPM_INPUT("Internal ADC Source"), + + /* DACs */ + SND_SOC_DAPM_ADC("Left ADC", "Left Capture", WM8991_POWER_MANAGEMENT_2, + WM8991_ADCL_ENA_BIT, 0), + SND_SOC_DAPM_ADC("Right ADC", "Right Capture", WM8991_POWER_MANAGEMENT_2, + WM8991_ADCR_ENA_BIT, 0), + + /* Input PGAs */ + SND_SOC_DAPM_MIXER("LIN12 PGA", WM8991_POWER_MANAGEMENT_2, WM8991_LIN12_ENA_BIT, + 0, &wm8991_dapm_lin12_pga_controls[0], + ARRAY_SIZE(wm8991_dapm_lin12_pga_controls)), + SND_SOC_DAPM_MIXER("LIN34 PGA", WM8991_POWER_MANAGEMENT_2, WM8991_LIN34_ENA_BIT, + 0, &wm8991_dapm_lin34_pga_controls[0], + ARRAY_SIZE(wm8991_dapm_lin34_pga_controls)), + SND_SOC_DAPM_MIXER("RIN12 PGA", WM8991_POWER_MANAGEMENT_2, WM8991_RIN12_ENA_BIT, + 0, &wm8991_dapm_rin12_pga_controls[0], + ARRAY_SIZE(wm8991_dapm_rin12_pga_controls)), + SND_SOC_DAPM_MIXER("RIN34 PGA", WM8991_POWER_MANAGEMENT_2, WM8991_RIN34_ENA_BIT, + 0, &wm8991_dapm_rin34_pga_controls[0], + ARRAY_SIZE(wm8991_dapm_rin34_pga_controls)), + + /* INMIXL */ + SND_SOC_DAPM_MIXER_E("INMIXL", WM8991_INTDRIVBITS, WM8991_INMIXL_PWR_BIT, 0, + &wm8991_dapm_inmixl_controls[0], + ARRAY_SIZE(wm8991_dapm_inmixl_controls), + inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* AINLMUX */ + SND_SOC_DAPM_MUX_E("AINLMUX", WM8991_INTDRIVBITS, WM8991_AINLMUX_PWR_BIT, 0, + &wm8991_dapm_ainlmux_controls, inmixer_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* INMIXR */ + SND_SOC_DAPM_MIXER_E("INMIXR", WM8991_INTDRIVBITS, WM8991_INMIXR_PWR_BIT, 0, + &wm8991_dapm_inmixr_controls[0], + ARRAY_SIZE(wm8991_dapm_inmixr_controls), + inmixer_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* AINRMUX */ + SND_SOC_DAPM_MUX_E("AINRMUX", WM8991_INTDRIVBITS, WM8991_AINRMUX_PWR_BIT, 0, + &wm8991_dapm_ainrmux_controls, inmixer_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), + + /* Output Side */ + /* DACs */ + SND_SOC_DAPM_DAC("Left DAC", "Left Playback", WM8991_POWER_MANAGEMENT_3, + WM8991_DACL_ENA_BIT, 0), + SND_SOC_DAPM_DAC("Right DAC", "Right Playback", WM8991_POWER_MANAGEMENT_3, + WM8991_DACR_ENA_BIT, 0), + + /* LOMIX */ + SND_SOC_DAPM_MIXER_E("LOMIX", WM8991_POWER_MANAGEMENT_3, WM8991_LOMIX_ENA_BIT, + 0, &wm8991_dapm_lomix_controls[0], + ARRAY_SIZE(wm8991_dapm_lomix_controls), + outmixer_event, SND_SOC_DAPM_PRE_REG), + + /* LONMIX */ + SND_SOC_DAPM_MIXER("LONMIX", WM8991_POWER_MANAGEMENT_3, WM8991_LON_ENA_BIT, 0, + &wm8991_dapm_lonmix_controls[0], + ARRAY_SIZE(wm8991_dapm_lonmix_controls)), + + /* LOPMIX */ + SND_SOC_DAPM_MIXER("LOPMIX", WM8991_POWER_MANAGEMENT_3, WM8991_LOP_ENA_BIT, 0, + &wm8991_dapm_lopmix_controls[0], + ARRAY_SIZE(wm8991_dapm_lopmix_controls)), + + /* OUT3MIX */ + SND_SOC_DAPM_MIXER("OUT3MIX", WM8991_POWER_MANAGEMENT_1, WM8991_OUT3_ENA_BIT, 0, + &wm8991_dapm_out3mix_controls[0], + ARRAY_SIZE(wm8991_dapm_out3mix_controls)), + + /* SPKMIX */ + SND_SOC_DAPM_MIXER_E("SPKMIX", WM8991_POWER_MANAGEMENT_1, WM8991_SPK_ENA_BIT, 0, + &wm8991_dapm_spkmix_controls[0], + ARRAY_SIZE(wm8991_dapm_spkmix_controls), outmixer_event, + SND_SOC_DAPM_PRE_REG), + + /* OUT4MIX */ + SND_SOC_DAPM_MIXER("OUT4MIX", WM8991_POWER_MANAGEMENT_1, WM8991_OUT4_ENA_BIT, 0, + &wm8991_dapm_out4mix_controls[0], + ARRAY_SIZE(wm8991_dapm_out4mix_controls)), + + /* ROPMIX */ + SND_SOC_DAPM_MIXER("ROPMIX", WM8991_POWER_MANAGEMENT_3, WM8991_ROP_ENA_BIT, 0, + &wm8991_dapm_ropmix_controls[0], + ARRAY_SIZE(wm8991_dapm_ropmix_controls)), + + /* RONMIX */ + SND_SOC_DAPM_MIXER("RONMIX", WM8991_POWER_MANAGEMENT_3, WM8991_RON_ENA_BIT, 0, + &wm8991_dapm_ronmix_controls[0], + ARRAY_SIZE(wm8991_dapm_ronmix_controls)), + + /* ROMIX */ + SND_SOC_DAPM_MIXER_E("ROMIX", WM8991_POWER_MANAGEMENT_3, WM8991_ROMIX_ENA_BIT, + 0, &wm8991_dapm_romix_controls[0], + ARRAY_SIZE(wm8991_dapm_romix_controls), + outmixer_event, SND_SOC_DAPM_PRE_REG), + + /* LOUT PGA */ + SND_SOC_DAPM_PGA("LOUT PGA", WM8991_POWER_MANAGEMENT_1, WM8991_LOUT_ENA_BIT, 0, + NULL, 0), + + /* ROUT PGA */ + SND_SOC_DAPM_PGA("ROUT PGA", WM8991_POWER_MANAGEMENT_1, WM8991_ROUT_ENA_BIT, 0, + NULL, 0), + + /* LOPGA */ + SND_SOC_DAPM_PGA("LOPGA", WM8991_POWER_MANAGEMENT_3, WM8991_LOPGA_ENA_BIT, 0, + NULL, 0), + + /* ROPGA */ + SND_SOC_DAPM_PGA("ROPGA", WM8991_POWER_MANAGEMENT_3, WM8991_ROPGA_ENA_BIT, 0, + NULL, 0), + + /* MICBIAS */ + SND_SOC_DAPM_MICBIAS("MICBIAS", WM8991_POWER_MANAGEMENT_1, + WM8991_MICBIAS_ENA_BIT, 0), + + SND_SOC_DAPM_OUTPUT("LON"), + SND_SOC_DAPM_OUTPUT("LOP"), + SND_SOC_DAPM_OUTPUT("OUT3"), + SND_SOC_DAPM_OUTPUT("LOUT"), + SND_SOC_DAPM_OUTPUT("SPKN"), + SND_SOC_DAPM_OUTPUT("SPKP"), + SND_SOC_DAPM_OUTPUT("ROUT"), + SND_SOC_DAPM_OUTPUT("OUT4"), + SND_SOC_DAPM_OUTPUT("ROP"), + SND_SOC_DAPM_OUTPUT("RON"), + SND_SOC_DAPM_OUTPUT("OUT"), + + SND_SOC_DAPM_OUTPUT("Internal DAC Sink"), +}; + +static const struct snd_soc_dapm_route audio_map[] = { + /* Make DACs turn on when playing even if not mixed into any outputs */ + {"Internal DAC Sink", NULL, "Left DAC"}, + {"Internal DAC Sink", NULL, "Right DAC"}, + + /* Make ADCs turn on when recording even if not mixed from any inputs */ + {"Left ADC", NULL, "Internal ADC Source"}, + {"Right ADC", NULL, "Internal ADC Source"}, + + /* Input Side */ + /* LIN12 PGA */ + {"LIN12 PGA", "LIN1 Switch", "LIN1"}, + {"LIN12 PGA", "LIN2 Switch", "LIN2"}, + /* LIN34 PGA */ + {"LIN34 PGA", "LIN3 Switch", "LIN3"}, + {"LIN34 PGA", "LIN4 Switch", "LIN4RXN"}, + /* INMIXL */ + {"INMIXL", "Record Left Volume", "LOMIX"}, + {"INMIXL", "LIN2 Volume", "LIN2"}, + {"INMIXL", "LINPGA12 Switch", "LIN12 PGA"}, + {"INMIXL", "LINPGA34 Switch", "LIN34 PGA"}, + /* AINLMUX */ + {"AINLMUX", "INMIXL Mix", "INMIXL"}, + {"AINLMUX", "DIFFINL Mix", "LIN12 PGA"}, + {"AINLMUX", "DIFFINL Mix", "LIN34 PGA"}, + {"AINLMUX", "RXVOICE Mix", "LIN4RXN"}, + {"AINLMUX", "RXVOICE Mix", "RIN4RXP"}, + /* ADC */ + {"Left ADC", NULL, "AINLMUX"}, + + /* RIN12 PGA */ + {"RIN12 PGA", "RIN1 Switch", "RIN1"}, + {"RIN12 PGA", "RIN2 Switch", "RIN2"}, + /* RIN34 PGA */ + {"RIN34 PGA", "RIN3 Switch", "RIN3"}, + {"RIN34 PGA", "RIN4 Switch", "RIN4RXP"}, + /* INMIXL */ + {"INMIXR", "Record Right Volume", "ROMIX"}, + {"INMIXR", "RIN2 Volume", "RIN2"}, + {"INMIXR", "RINPGA12 Switch", "RIN12 PGA"}, + {"INMIXR", "RINPGA34 Switch", "RIN34 PGA"}, + /* AINRMUX */ + {"AINRMUX", "INMIXR Mix", "INMIXR"}, + {"AINRMUX", "DIFFINR Mix", "RIN12 PGA"}, + {"AINRMUX", "DIFFINR Mix", "RIN34 PGA"}, + {"AINRMUX", "RXVOICE Mix", "LIN4RXN"}, + {"AINRMUX", "RXVOICE Mix", "RIN4RXP"}, + /* ADC */ + {"Right ADC", NULL, "AINRMUX"}, + + /* LOMIX */ + {"LOMIX", "LOMIX RIN3 Bypass Switch", "RIN3"}, + {"LOMIX", "LOMIX LIN3 Bypass Switch", "LIN3"}, + {"LOMIX", "LOMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, + {"LOMIX", "LOMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, + {"LOMIX", "LOMIX Right ADC Bypass Switch", "AINRMUX"}, + {"LOMIX", "LOMIX Left ADC Bypass Switch", "AINLMUX"}, + {"LOMIX", "LOMIX Left DAC Switch", "Left DAC"}, + + /* ROMIX */ + {"ROMIX", "ROMIX RIN3 Bypass Switch", "RIN3"}, + {"ROMIX", "ROMIX LIN3 Bypass Switch", "LIN3"}, + {"ROMIX", "ROMIX LIN12 PGA Bypass Switch", "LIN12 PGA"}, + {"ROMIX", "ROMIX RIN12 PGA Bypass Switch", "RIN12 PGA"}, + {"ROMIX", "ROMIX Right ADC Bypass Switch", "AINRMUX"}, + {"ROMIX", "ROMIX Left ADC Bypass Switch", "AINLMUX"}, + {"ROMIX", "ROMIX Right DAC Switch", "Right DAC"}, + + /* SPKMIX */ + {"SPKMIX", "SPKMIX LIN2 Bypass Switch", "LIN2"}, + {"SPKMIX", "SPKMIX RIN2 Bypass Switch", "RIN2"}, + {"SPKMIX", "SPKMIX LADC Bypass Switch", "AINLMUX"}, + {"SPKMIX", "SPKMIX RADC Bypass Switch", "AINRMUX"}, + {"SPKMIX", "SPKMIX Left Mixer PGA Switch", "LOPGA"}, + {"SPKMIX", "SPKMIX Right Mixer PGA Switch", "ROPGA"}, + {"SPKMIX", "SPKMIX Right DAC Switch", "Right DAC"}, + {"SPKMIX", "SPKMIX Left DAC Switch", "Right DAC"}, + + /* LONMIX */ + {"LONMIX", "LONMIX Left Mixer PGA Switch", "LOPGA"}, + {"LONMIX", "LONMIX Right Mixer PGA Switch", "ROPGA"}, + {"LONMIX", "LONMIX Inverted LOP Switch", "LOPMIX"}, + + /* LOPMIX */ + {"LOPMIX", "LOPMIX Right Mic Bypass Switch", "RIN12 PGA"}, + {"LOPMIX", "LOPMIX Left Mic Bypass Switch", "LIN12 PGA"}, + {"LOPMIX", "LOPMIX Left Mixer PGA Switch", "LOPGA"}, + + /* OUT3MIX */ + {"OUT3MIX", "OUT3MIX LIN4RXN Bypass Switch", "LIN4RXN"}, + {"OUT3MIX", "OUT3MIX Left Out PGA Switch", "LOPGA"}, + + /* OUT4MIX */ + {"OUT4MIX", "OUT4MIX Right Out PGA Switch", "ROPGA"}, + {"OUT4MIX", "OUT4MIX RIN4RXP Bypass Switch", "RIN4RXP"}, + + /* RONMIX */ + {"RONMIX", "RONMIX Right Mixer PGA Switch", "ROPGA"}, + {"RONMIX", "RONMIX Left Mixer PGA Switch", "LOPGA"}, + {"RONMIX", "RONMIX Inverted ROP Switch", "ROPMIX"}, + + /* ROPMIX */ + {"ROPMIX", "ROPMIX Left Mic Bypass Switch", "LIN12 PGA"}, + {"ROPMIX", "ROPMIX Right Mic Bypass Switch", "RIN12 PGA"}, + {"ROPMIX", "ROPMIX Right Mixer PGA Switch", "ROPGA"}, + + /* Out Mixer PGAs */ + {"LOPGA", NULL, "LOMIX"}, + {"ROPGA", NULL, "ROMIX"}, + + {"LOUT PGA", NULL, "LOMIX"}, + {"ROUT PGA", NULL, "ROMIX"}, + + /* Output Pins */ + {"LON", NULL, "LONMIX"}, + {"LOP", NULL, "LOPMIX"}, + {"OUT", NULL, "OUT3MIX"}, + {"LOUT", NULL, "LOUT PGA"}, + {"SPKN", NULL, "SPKMIX"}, + {"ROUT", NULL, "ROUT PGA"}, + {"OUT4", NULL, "OUT4MIX"}, + {"ROP", NULL, "ROPMIX"}, + {"RON", NULL, "RONMIX"}, +}; + +/* PLL divisors */ +struct _pll_div { + u32 div2; + u32 n; + u32 k; +}; + +/* The size in bits of the pll divide multiplied by 10 + * to allow rounding later */ +#define FIXED_PLL_SIZE ((1 << 16) * 10) + +static void pll_factors(struct _pll_div *pll_div, unsigned int target, + unsigned int source) +{ + u64 Kpart; + unsigned int K, Ndiv, Nmod; + + + Ndiv = target / source; + if (Ndiv < 6) { + source >>= 1; + pll_div->div2 = 1; + Ndiv = target / source; + } else + pll_div->div2 = 0; + + if ((Ndiv < 6) || (Ndiv > 12)) + printk(KERN_WARNING + "WM8991 N value outwith recommended range! N = %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; +} + +static int wm8991_set_dai_pll(struct snd_soc_dai *codec_dai, + int pll_id, int src, unsigned int freq_in, unsigned int freq_out) +{ + u16 reg; + struct snd_soc_codec *codec = codec_dai->codec; + struct _pll_div pll_div; + + if (freq_in && freq_out) { + pll_factors(&pll_div, freq_out * 4, freq_in); + + /* Turn on PLL */ + reg = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_2); + reg |= WM8991_PLL_ENA; + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_2, reg); + + /* sysclk comes from PLL */ + reg = snd_soc_read(codec, WM8991_CLOCKING_2); + snd_soc_write(codec, WM8991_CLOCKING_2, reg | WM8991_SYSCLK_SRC); + + /* set up N , fractional mode and pre-divisor if neccessary */ + snd_soc_write(codec, WM8991_PLL1, pll_div.n | WM8991_SDM | + (pll_div.div2 ? WM8991_PRESCALE : 0)); + snd_soc_write(codec, WM8991_PLL2, (u8)(pll_div.k>>8)); + snd_soc_write(codec, WM8991_PLL3, (u8)(pll_div.k & 0xFF)); + } else { + /* Turn on PLL */ + reg = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_2); + reg &= ~WM8991_PLL_ENA; + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_2, reg); + } + return 0; +} + +/* + * Set's ADC and Voice DAC format. + */ +static int wm8991_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 audio1, audio3; + + audio1 = snd_soc_read(codec, WM8991_AUDIO_INTERFACE_1); + audio3 = snd_soc_read(codec, WM8991_AUDIO_INTERFACE_3); + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + audio3 &= ~WM8991_AIF_MSTR1; + break; + case SND_SOC_DAIFMT_CBM_CFM: + audio3 |= WM8991_AIF_MSTR1; + break; + default: + return -EINVAL; + } + + audio1 &= ~WM8991_AIF_FMT_MASK; + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + audio1 |= WM8991_AIF_TMF_I2S; + audio1 &= ~WM8991_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_RIGHT_J: + audio1 |= WM8991_AIF_TMF_RIGHTJ; + audio1 &= ~WM8991_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_LEFT_J: + audio1 |= WM8991_AIF_TMF_LEFTJ; + audio1 &= ~WM8991_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_A: + audio1 |= WM8991_AIF_TMF_DSP; + audio1 &= ~WM8991_AIF_LRCLK_INV; + break; + case SND_SOC_DAIFMT_DSP_B: + audio1 |= WM8991_AIF_TMF_DSP | WM8991_AIF_LRCLK_INV; + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, WM8991_AUDIO_INTERFACE_1, audio1); + snd_soc_write(codec, WM8991_AUDIO_INTERFACE_3, audio3); + return 0; +} + +static int wm8991_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 WM8991_MCLK_DIV: + reg = snd_soc_read(codec, WM8991_CLOCKING_2) & + ~WM8991_MCLK_DIV_MASK; + snd_soc_write(codec, WM8991_CLOCKING_2, reg | div); + break; + case WM8991_DACCLK_DIV: + reg = snd_soc_read(codec, WM8991_CLOCKING_2) & + ~WM8991_DAC_CLKDIV_MASK; + snd_soc_write(codec, WM8991_CLOCKING_2, reg | div); + break; + case WM8991_ADCCLK_DIV: + reg = snd_soc_read(codec, WM8991_CLOCKING_2) & + ~WM8991_ADC_CLKDIV_MASK; + snd_soc_write(codec, WM8991_CLOCKING_2, reg | div); + break; + case WM8991_BCLK_DIV: + reg = snd_soc_read(codec, WM8991_CLOCKING_1) & + ~WM8991_BCLK_DIV_MASK; + snd_soc_write(codec, WM8991_CLOCKING_1, reg | div); + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * Set PCM DAI bit size and sample rate. + */ +static int wm8991_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; + u16 audio1 = snd_soc_read(codec, WM8991_AUDIO_INTERFACE_1); + + audio1 &= ~WM8991_AIF_WL_MASK; + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + audio1 |= WM8991_AIF_WL_20BITS; + break; + case SNDRV_PCM_FORMAT_S24_LE: + audio1 |= WM8991_AIF_WL_24BITS; + break; + case SNDRV_PCM_FORMAT_S32_LE: + audio1 |= WM8991_AIF_WL_32BITS; + break; + } + + snd_soc_write(codec, WM8991_AUDIO_INTERFACE_1, audio1); + return 0; +} + +static int wm8991_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 val; + + val = snd_soc_read(codec, WM8991_DAC_CTRL) & ~WM8991_DAC_MUTE; + if (mute) + snd_soc_write(codec, WM8991_DAC_CTRL, val | WM8991_DAC_MUTE); + else + snd_soc_write(codec, WM8991_DAC_CTRL, val); + return 0; +} + +static int wm8991_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 val; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* VMID=2*50k */ + val = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_1) & + ~WM8991_VMID_MODE_MASK; + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, val | 0x2); + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { + snd_soc_cache_sync(codec); + /* Enable all output discharge bits */ + snd_soc_write(codec, WM8991_ANTIPOP1, WM8991_DIS_LLINE | + WM8991_DIS_RLINE | WM8991_DIS_OUT3 | + WM8991_DIS_OUT4 | WM8991_DIS_LOUT | + WM8991_DIS_ROUT); + + /* Enable POBCTRL, SOFT_ST, VMIDTOG and BUFDCOPEN */ + snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_SOFTST | + WM8991_BUFDCOPEN | WM8991_POBCTRL | + WM8991_VMIDTOG); + + /* Delay to allow output caps to discharge */ + msleep(300); + + /* Disable VMIDTOG */ + snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_SOFTST | + WM8991_BUFDCOPEN | WM8991_POBCTRL); + + /* disable all output discharge bits */ + snd_soc_write(codec, WM8991_ANTIPOP1, 0); + + /* Enable outputs */ + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x1b00); + + msleep(50); + + /* Enable VMID at 2x50k */ + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x1f02); + + msleep(100); + + /* Enable VREF */ + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x1f03); + + msleep(600); + + /* Enable BUFIOEN */ + snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_SOFTST | + WM8991_BUFDCOPEN | WM8991_POBCTRL | + WM8991_BUFIOEN); + + /* Disable outputs */ + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x3); + + /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ + snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_BUFIOEN); + } + + /* VMID=2*250k */ + val = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_1) & + ~WM8991_VMID_MODE_MASK; + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, val | 0x4); + break; + + case SND_SOC_BIAS_OFF: + /* Enable POBCTRL and SOFT_ST */ + snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_SOFTST | + WM8991_POBCTRL | WM8991_BUFIOEN); + + /* Enable POBCTRL, SOFT_ST and BUFDCOPEN */ + snd_soc_write(codec, WM8991_ANTIPOP2, WM8991_SOFTST | + WM8991_BUFDCOPEN | WM8991_POBCTRL | + WM8991_BUFIOEN); + + /* mute DAC */ + val = snd_soc_read(codec, WM8991_DAC_CTRL); + snd_soc_write(codec, WM8991_DAC_CTRL, val | WM8991_DAC_MUTE); + + /* Enable any disabled outputs */ + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x1f03); + + /* Disable VMID */ + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x1f01); + + msleep(300); + + /* Enable all output discharge bits */ + snd_soc_write(codec, WM8991_ANTIPOP1, WM8991_DIS_LLINE | + WM8991_DIS_RLINE | WM8991_DIS_OUT3 | + WM8991_DIS_OUT4 | WM8991_DIS_LOUT | + WM8991_DIS_ROUT); + + /* Disable VREF */ + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, 0x0); + + /* disable POBCTRL, SOFT_ST and BUFDCOPEN */ + snd_soc_write(codec, WM8991_ANTIPOP2, 0x0); + codec->cache_sync = 1; + break; + } + + codec->dapm.bias_level = level; + return 0; +} + +static int wm8991_suspend(struct snd_soc_codec *codec, pm_message_t state) +{ + wm8991_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8991_resume(struct snd_soc_codec *codec) +{ + wm8991_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + return 0; +} + +/* power down chip */ +static int wm8991_remove(struct snd_soc_codec *codec) +{ + wm8991_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8991_probe(struct snd_soc_codec *codec) +{ + struct wm8991_priv *wm8991; + int ret; + unsigned int reg; + + wm8991 = snd_soc_codec_get_drvdata(codec); + + ret = snd_soc_codec_set_cache_io(codec, 8, 16, wm8991->control_type); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache i/o: %d\n", ret); + return ret; + } + + ret = wm8991_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset\n"); + return ret; + } + + wm8991_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + reg = snd_soc_read(codec, WM8991_AUDIO_INTERFACE_4); + snd_soc_write(codec, WM8991_AUDIO_INTERFACE_4, reg | WM8991_ALRCGPIO1); + + reg = snd_soc_read(codec, WM8991_GPIO1_GPIO2) & + ~WM8991_GPIO1_SEL_MASK; + snd_soc_write(codec, WM8991_GPIO1_GPIO2, reg | 1); + + reg = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_1); + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_1, reg | WM8991_VREF_ENA| + WM8991_VMID_MODE_MASK); + + reg = snd_soc_read(codec, WM8991_POWER_MANAGEMENT_2); + snd_soc_write(codec, WM8991_POWER_MANAGEMENT_2, reg | WM8991_OPCLK_ENA); + + snd_soc_write(codec, WM8991_DAC_CTRL, 0); + snd_soc_write(codec, WM8991_LEFT_OUTPUT_VOLUME, 0x50 | (1<<8)); + snd_soc_write(codec, WM8991_RIGHT_OUTPUT_VOLUME, 0x50 | (1<<8)); + + snd_soc_add_controls(codec, wm8991_snd_controls, + ARRAY_SIZE(wm8991_snd_controls)); + + snd_soc_dapm_new_controls(&codec->dapm, wm8991_dapm_widgets, + ARRAY_SIZE(wm8991_dapm_widgets)); + snd_soc_dapm_add_routes(&codec->dapm, audio_map, + ARRAY_SIZE(audio_map)); + return 0; +} + +#define WM8991_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops wm8991_ops = { + .hw_params = wm8991_hw_params, + .digital_mute = wm8991_mute, + .set_fmt = wm8991_set_dai_fmt, + .set_clkdiv = wm8991_set_dai_clkdiv, + .set_pll = wm8991_set_dai_pll +}; + +/* + * The WM8991 supports 2 different and mutually exclusive DAI + * configurations. + * + * 1. ADC/DAC on Primary Interface + * 2. ADC on Primary Interface/DAC on secondary + */ +static struct snd_soc_dai_driver wm8991_dai = { + /* ADC/DAC on primary */ + .name = "wm8991", + .id = 1, + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = WM8991_FORMATS + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = WM8991_FORMATS + }, + .ops = &wm8991_ops +}; + +static struct snd_soc_codec_driver soc_codec_dev_wm8991 = { + .probe = wm8991_probe, + .remove = wm8991_remove, + .suspend = wm8991_suspend, + .resume = wm8991_resume, + .set_bias_level = wm8991_set_bias_level, + .reg_cache_size = WM8991_MAX_REGISTER + 1, + .reg_word_size = sizeof(u16), + .reg_cache_default = wm8991_reg_defs +}; + +static __devinit int wm8991_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8991_priv *wm8991; + int ret; + + wm8991 = kzalloc(sizeof *wm8991, GFP_KERNEL); + if (!wm8991) + return -ENOMEM; + + wm8991->control_type = SND_SOC_I2C; + i2c_set_clientdata(i2c, wm8991); + + ret = snd_soc_register_codec(&i2c->dev, + &soc_codec_dev_wm8991, &wm8991_dai, 1); + if (ret < 0) + kfree(wm8991); + return ret; +} + +static __devexit int wm8991_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + kfree(i2c_get_clientdata(client)); + return 0; +} + +static const struct i2c_device_id wm8991_i2c_id[] = { + { "wm8991", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8991_i2c_id); + +static struct i2c_driver wm8991_i2c_driver = { + .driver = { + .name = "wm8991", + .owner = THIS_MODULE, + }, + .probe = wm8991_i2c_probe, + .remove = __devexit_p(wm8991_i2c_remove), + .id_table = wm8991_i2c_id, +}; + +static int __init wm8991_modinit(void) +{ + int ret; + ret = i2c_add_driver(&wm8991_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8991 I2C driver: %d\n", + ret); + } + return 0; +} +module_init(wm8991_modinit); + +static void __exit wm8991_exit(void) +{ + i2c_del_driver(&wm8991_i2c_driver); +} +module_exit(wm8991_exit); + +MODULE_DESCRIPTION("ASoC WM8991 driver"); +MODULE_AUTHOR("Graeme Gregory"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8991.h b/sound/soc/codecs/wm8991.h new file mode 100644 index 000000000000..8a942efd18a5 --- /dev/null +++ b/sound/soc/codecs/wm8991.h @@ -0,0 +1,833 @@ +/* + * wm8991.h -- audio driver for WM8991 + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * 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 _WM8991_H +#define _WM8991_H + +/* + * Register values. + */ +#define WM8991_RESET 0x00 +#define WM8991_POWER_MANAGEMENT_1 0x01 +#define WM8991_POWER_MANAGEMENT_2 0x02 +#define WM8991_POWER_MANAGEMENT_3 0x03 +#define WM8991_AUDIO_INTERFACE_1 0x04 +#define WM8991_AUDIO_INTERFACE_2 0x05 +#define WM8991_CLOCKING_1 0x06 +#define WM8991_CLOCKING_2 0x07 +#define WM8991_AUDIO_INTERFACE_3 0x08 +#define WM8991_AUDIO_INTERFACE_4 0x09 +#define WM8991_DAC_CTRL 0x0A +#define WM8991_LEFT_DAC_DIGITAL_VOLUME 0x0B +#define WM8991_RIGHT_DAC_DIGITAL_VOLUME 0x0C +#define WM8991_DIGITAL_SIDE_TONE 0x0D +#define WM8991_ADC_CTRL 0x0E +#define WM8991_LEFT_ADC_DIGITAL_VOLUME 0x0F +#define WM8991_RIGHT_ADC_DIGITAL_VOLUME 0x10 +#define WM8991_GPIO_CTRL_1 0x12 +#define WM8991_GPIO1_GPIO2 0x13 +#define WM8991_GPIO3_GPIO4 0x14 +#define WM8991_GPIO5_GPIO6 0x15 +#define WM8991_GPIOCTRL_2 0x16 +#define WM8991_GPIO_POL 0x17 +#define WM8991_LEFT_LINE_INPUT_1_2_VOLUME 0x18 +#define WM8991_LEFT_LINE_INPUT_3_4_VOLUME 0x19 +#define WM8991_RIGHT_LINE_INPUT_1_2_VOLUME 0x1A +#define WM8991_RIGHT_LINE_INPUT_3_4_VOLUME 0x1B +#define WM8991_LEFT_OUTPUT_VOLUME 0x1C +#define WM8991_RIGHT_OUTPUT_VOLUME 0x1D +#define WM8991_LINE_OUTPUTS_VOLUME 0x1E +#define WM8991_OUT3_4_VOLUME 0x1F +#define WM8991_LEFT_OPGA_VOLUME 0x20 +#define WM8991_RIGHT_OPGA_VOLUME 0x21 +#define WM8991_SPEAKER_VOLUME 0x22 +#define WM8991_CLASSD1 0x23 +#define WM8991_CLASSD3 0x25 +#define WM8991_INPUT_MIXER1 0x27 +#define WM8991_INPUT_MIXER2 0x28 +#define WM8991_INPUT_MIXER3 0x29 +#define WM8991_INPUT_MIXER4 0x2A +#define WM8991_INPUT_MIXER5 0x2B +#define WM8991_INPUT_MIXER6 0x2C +#define WM8991_OUTPUT_MIXER1 0x2D +#define WM8991_OUTPUT_MIXER2 0x2E +#define WM8991_OUTPUT_MIXER3 0x2F +#define WM8991_OUTPUT_MIXER4 0x30 +#define WM8991_OUTPUT_MIXER5 0x31 +#define WM8991_OUTPUT_MIXER6 0x32 +#define WM8991_OUT3_4_MIXER 0x33 +#define WM8991_LINE_MIXER1 0x34 +#define WM8991_LINE_MIXER2 0x35 +#define WM8991_SPEAKER_MIXER 0x36 +#define WM8991_ADDITIONAL_CONTROL 0x37 +#define WM8991_ANTIPOP1 0x38 +#define WM8991_ANTIPOP2 0x39 +#define WM8991_MICBIAS 0x3A +#define WM8991_PLL1 0x3C +#define WM8991_PLL2 0x3D +#define WM8991_PLL3 0x3E +#define WM8991_INTDRIVBITS 0x3F + +#define WM8991_REGISTER_COUNT 60 +#define WM8991_MAX_REGISTER 0x3F + +/* + * Field Definitions. + */ + +/* + * R0 (0x00) - Reset + */ +#define WM8991_SW_RESET_CHIP_ID_MASK 0xFFFF /* SW_RESET_CHIP_ID - [15:0] */ + +/* + * R1 (0x01) - Power Management (1) + */ +#define WM8991_SPK_ENA 0x1000 /* SPK_ENA */ +#define WM8991_SPK_ENA_BIT 12 +#define WM8991_OUT3_ENA 0x0800 /* OUT3_ENA */ +#define WM8991_OUT3_ENA_BIT 11 +#define WM8991_OUT4_ENA 0x0400 /* OUT4_ENA */ +#define WM8991_OUT4_ENA_BIT 10 +#define WM8991_LOUT_ENA 0x0200 /* LOUT_ENA */ +#define WM8991_LOUT_ENA_BIT 9 +#define WM8991_ROUT_ENA 0x0100 /* ROUT_ENA */ +#define WM8991_ROUT_ENA_BIT 8 +#define WM8991_MICBIAS_ENA 0x0010 /* MICBIAS_ENA */ +#define WM8991_MICBIAS_ENA_BIT 4 +#define WM8991_VMID_MODE_MASK 0x0006 /* VMID_MODE - [2:1] */ +#define WM8991_VREF_ENA 0x0001 /* VREF_ENA */ +#define WM8991_VREF_ENA_BIT 0 + +/* + * R2 (0x02) - Power Management (2) + */ +#define WM8991_PLL_ENA 0x8000 /* PLL_ENA */ +#define WM8991_PLL_ENA_BIT 15 +#define WM8991_TSHUT_ENA 0x4000 /* TSHUT_ENA */ +#define WM8991_TSHUT_ENA_BIT 14 +#define WM8991_TSHUT_OPDIS 0x2000 /* TSHUT_OPDIS */ +#define WM8991_TSHUT_OPDIS_BIT 13 +#define WM8991_OPCLK_ENA 0x0800 /* OPCLK_ENA */ +#define WM8991_OPCLK_ENA_BIT 11 +#define WM8991_AINL_ENA 0x0200 /* AINL_ENA */ +#define WM8991_AINL_ENA_BIT 9 +#define WM8991_AINR_ENA 0x0100 /* AINR_ENA */ +#define WM8991_AINR_ENA_BIT 8 +#define WM8991_LIN34_ENA 0x0080 /* LIN34_ENA */ +#define WM8991_LIN34_ENA_BIT 7 +#define WM8991_LIN12_ENA 0x0040 /* LIN12_ENA */ +#define WM8991_LIN12_ENA_BIT 6 +#define WM8991_RIN34_ENA 0x0020 /* RIN34_ENA */ +#define WM8991_RIN34_ENA_BIT 5 +#define WM8991_RIN12_ENA 0x0010 /* RIN12_ENA */ +#define WM8991_RIN12_ENA_BIT 4 +#define WM8991_ADCL_ENA 0x0002 /* ADCL_ENA */ +#define WM8991_ADCL_ENA_BIT 1 +#define WM8991_ADCR_ENA 0x0001 /* ADCR_ENA */ +#define WM8991_ADCR_ENA_BIT 0 + +/* + * R3 (0x03) - Power Management (3) + */ +#define WM8991_LON_ENA 0x2000 /* LON_ENA */ +#define WM8991_LON_ENA_BIT 13 +#define WM8991_LOP_ENA 0x1000 /* LOP_ENA */ +#define WM8991_LOP_ENA_BIT 12 +#define WM8991_RON_ENA 0x0800 /* RON_ENA */ +#define WM8991_RON_ENA_BIT 11 +#define WM8991_ROP_ENA 0x0400 /* ROP_ENA */ +#define WM8991_ROP_ENA_BIT 10 +#define WM8991_LOPGA_ENA 0x0080 /* LOPGA_ENA */ +#define WM8991_LOPGA_ENA_BIT 7 +#define WM8991_ROPGA_ENA 0x0040 /* ROPGA_ENA */ +#define WM8991_ROPGA_ENA_BIT 6 +#define WM8991_LOMIX_ENA 0x0020 /* LOMIX_ENA */ +#define WM8991_LOMIX_ENA_BIT 5 +#define WM8991_ROMIX_ENA 0x0010 /* ROMIX_ENA */ +#define WM8991_ROMIX_ENA_BIT 4 +#define WM8991_DACL_ENA 0x0002 /* DACL_ENA */ +#define WM8991_DACL_ENA_BIT 1 +#define WM8991_DACR_ENA 0x0001 /* DACR_ENA */ +#define WM8991_DACR_ENA_BIT 0 + +/* + * R4 (0x04) - Audio Interface (1) + */ +#define WM8991_AIFADCL_SRC 0x8000 /* AIFADCL_SRC */ +#define WM8991_AIFADCR_SRC 0x4000 /* AIFADCR_SRC */ +#define WM8991_AIFADC_TDM 0x2000 /* AIFADC_TDM */ +#define WM8991_AIFADC_TDM_CHAN 0x1000 /* AIFADC_TDM_CHAN */ +#define WM8991_AIF_BCLK_INV 0x0100 /* AIF_BCLK_INV */ +#define WM8991_AIF_LRCLK_INV 0x0080 /* AIF_LRCLK_INV */ +#define WM8991_AIF_WL_MASK 0x0060 /* AIF_WL - [6:5] */ +#define WM8991_AIF_WL_16BITS (0 << 5) +#define WM8991_AIF_WL_20BITS (1 << 5) +#define WM8991_AIF_WL_24BITS (2 << 5) +#define WM8991_AIF_WL_32BITS (3 << 5) +#define WM8991_AIF_FMT_MASK 0x0018 /* AIF_FMT - [4:3] */ +#define WM8991_AIF_TMF_RIGHTJ (0 << 3) +#define WM8991_AIF_TMF_LEFTJ (1 << 3) +#define WM8991_AIF_TMF_I2S (2 << 3) +#define WM8991_AIF_TMF_DSP (3 << 3) + +/* + * R5 (0x05) - Audio Interface (2) + */ +#define WM8991_DACL_SRC 0x8000 /* DACL_SRC */ +#define WM8991_DACR_SRC 0x4000 /* DACR_SRC */ +#define WM8991_AIFDAC_TDM 0x2000 /* AIFDAC_TDM */ +#define WM8991_AIFDAC_TDM_CHAN 0x1000 /* AIFDAC_TDM_CHAN */ +#define WM8991_DAC_BOOST_MASK 0x0C00 /* DAC_BOOST - [11:10] */ +#define WM8991_DAC_COMP 0x0010 /* DAC_COMP */ +#define WM8991_DAC_COMPMODE 0x0008 /* DAC_COMPMODE */ +#define WM8991_ADC_COMP 0x0004 /* ADC_COMP */ +#define WM8991_ADC_COMPMODE 0x0002 /* ADC_COMPMODE */ +#define WM8991_LOOPBACK 0x0001 /* LOOPBACK */ + +/* + * R6 (0x06) - Clocking (1) + */ +#define WM8991_TOCLK_RATE 0x8000 /* TOCLK_RATE */ +#define WM8991_TOCLK_ENA 0x4000 /* TOCLK_ENA */ +#define WM8991_OPCLKDIV_MASK 0x1E00 /* OPCLKDIV - [12:9] */ +#define WM8991_DCLKDIV_MASK 0x01C0 /* DCLKDIV - [8:6] */ +#define WM8991_BCLK_DIV_MASK 0x001E /* BCLK_DIV - [4:1] */ +#define WM8991_BCLK_DIV_1 (0x0 << 1) +#define WM8991_BCLK_DIV_1_5 (0x1 << 1) +#define WM8991_BCLK_DIV_2 (0x2 << 1) +#define WM8991_BCLK_DIV_3 (0x3 << 1) +#define WM8991_BCLK_DIV_4 (0x4 << 1) +#define WM8991_BCLK_DIV_5_5 (0x5 << 1) +#define WM8991_BCLK_DIV_6 (0x6 << 1) +#define WM8991_BCLK_DIV_8 (0x7 << 1) +#define WM8991_BCLK_DIV_11 (0x8 << 1) +#define WM8991_BCLK_DIV_12 (0x9 << 1) +#define WM8991_BCLK_DIV_16 (0xA << 1) +#define WM8991_BCLK_DIV_22 (0xB << 1) +#define WM8991_BCLK_DIV_24 (0xC << 1) +#define WM8991_BCLK_DIV_32 (0xD << 1) +#define WM8991_BCLK_DIV_44 (0xE << 1) +#define WM8991_BCLK_DIV_48 (0xF << 1) + +/* + * R7 (0x07) - Clocking (2) + */ +#define WM8991_MCLK_SRC 0x8000 /* MCLK_SRC */ +#define WM8991_SYSCLK_SRC 0x4000 /* SYSCLK_SRC */ +#define WM8991_CLK_FORCE 0x2000 /* CLK_FORCE */ +#define WM8991_MCLK_DIV_MASK 0x1800 /* MCLK_DIV - [12:11] */ +#define WM8991_MCLK_DIV_1 (0 << 11) +#define WM8991_MCLK_DIV_2 ( 2 << 11) +#define WM8991_MCLK_INV 0x0400 /* MCLK_INV */ +#define WM8991_ADC_CLKDIV_MASK 0x00E0 /* ADC_CLKDIV - [7:5] */ +#define WM8991_ADC_CLKDIV_1 (0 << 5) +#define WM8991_ADC_CLKDIV_1_5 (1 << 5) +#define WM8991_ADC_CLKDIV_2 (2 << 5) +#define WM8991_ADC_CLKDIV_3 (3 << 5) +#define WM8991_ADC_CLKDIV_4 (4 << 5) +#define WM8991_ADC_CLKDIV_5_5 (5 << 5) +#define WM8991_ADC_CLKDIV_6 (6 << 5) +#define WM8991_DAC_CLKDIV_MASK 0x001C /* DAC_CLKDIV - [4:2] */ +#define WM8991_DAC_CLKDIV_1 (0 << 2) +#define WM8991_DAC_CLKDIV_1_5 (1 << 2) +#define WM8991_DAC_CLKDIV_2 (2 << 2) +#define WM8991_DAC_CLKDIV_3 (3 << 2) +#define WM8991_DAC_CLKDIV_4 (4 << 2) +#define WM8991_DAC_CLKDIV_5_5 (5 << 2) +#define WM8991_DAC_CLKDIV_6 (6 << 2) + +/* + * R8 (0x08) - Audio Interface (3) + */ +#define WM8991_AIF_MSTR1 0x8000 /* AIF_MSTR1 */ +#define WM8991_AIF_MSTR2 0x4000 /* AIF_MSTR2 */ +#define WM8991_AIF_SEL 0x2000 /* AIF_SEL */ +#define WM8991_ADCLRC_DIR 0x0800 /* ADCLRC_DIR */ +#define WM8991_ADCLRC_RATE_MASK 0x07FF /* ADCLRC_RATE - [10:0] */ + +/* + * R9 (0x09) - Audio Interface (4) + */ +#define WM8991_ALRCGPIO1 0x8000 /* ALRCGPIO1 */ +#define WM8991_ALRCBGPIO6 0x4000 /* ALRCBGPIO6 */ +#define WM8991_AIF_TRIS 0x2000 /* AIF_TRIS */ +#define WM8991_DACLRC_DIR 0x0800 /* DACLRC_DIR */ +#define WM8991_DACLRC_RATE_MASK 0x07FF /* DACLRC_RATE - [10:0] */ + +/* + * R10 (0x0A) - DAC CTRL + */ +#define WM8991_AIF_LRCLKRATE 0x0400 /* AIF_LRCLKRATE */ +#define WM8991_DAC_MONO 0x0200 /* DAC_MONO */ +#define WM8991_DAC_SB_FILT 0x0100 /* DAC_SB_FILT */ +#define WM8991_DAC_MUTERATE 0x0080 /* DAC_MUTERATE */ +#define WM8991_DAC_MUTEMODE 0x0040 /* DAC_MUTEMODE */ +#define WM8991_DEEMP_MASK 0x0030 /* DEEMP - [5:4] */ +#define WM8991_DAC_MUTE 0x0004 /* DAC_MUTE */ +#define WM8991_DACL_DATINV 0x0002 /* DACL_DATINV */ +#define WM8991_DACR_DATINV 0x0001 /* DACR_DATINV */ + +/* + * R11 (0x0B) - Left DAC Digital Volume + */ +#define WM8991_DAC_VU 0x0100 /* DAC_VU */ +#define WM8991_DACL_VOL_MASK 0x00FF /* DACL_VOL - [7:0] */ +#define WM8991_DACL_VOL_SHIFT 0 +/* + * R12 (0x0C) - Right DAC Digital Volume + */ +#define WM8991_DAC_VU 0x0100 /* DAC_VU */ +#define WM8991_DACR_VOL_MASK 0x00FF /* DACR_VOL - [7:0] */ +#define WM8991_DACR_VOL_SHIFT 0 +/* + * R13 (0x0D) - Digital Side Tone + */ +#define WM8991_ADCL_DAC_SVOL_MASK 0x0F /* ADCL_DAC_SVOL - [12:9] */ +#define WM8991_ADCL_DAC_SVOL_SHIFT 9 +#define WM8991_ADCR_DAC_SVOL_MASK 0x0F /* ADCR_DAC_SVOL - [8:5] */ +#define WM8991_ADCR_DAC_SVOL_SHIFT 5 +#define WM8991_ADC_TO_DACL_MASK 0x03 /* ADC_TO_DACL - [3:2] */ +#define WM8991_ADC_TO_DACL_SHIFT 2 +#define WM8991_ADC_TO_DACR_MASK 0x03 /* ADC_TO_DACR - [1:0] */ +#define WM8991_ADC_TO_DACR_SHIFT 0 + +/* + * R14 (0x0E) - ADC CTRL + */ +#define WM8991_ADC_HPF_ENA 0x0100 /* ADC_HPF_ENA */ +#define WM8991_ADC_HPF_ENA_BIT 8 +#define WM8991_ADC_HPF_CUT_MASK 0x03 /* ADC_HPF_CUT - [6:5] */ +#define WM8991_ADC_HPF_CUT_SHIFT 5 +#define WM8991_ADCL_DATINV 0x0002 /* ADCL_DATINV */ +#define WM8991_ADCL_DATINV_BIT 1 +#define WM8991_ADCR_DATINV 0x0001 /* ADCR_DATINV */ +#define WM8991_ADCR_DATINV_BIT 0 + +/* + * R15 (0x0F) - Left ADC Digital Volume + */ +#define WM8991_ADC_VU 0x0100 /* ADC_VU */ +#define WM8991_ADCL_VOL_MASK 0x00FF /* ADCL_VOL - [7:0] */ +#define WM8991_ADCL_VOL_SHIFT 0 + +/* + * R16 (0x10) - Right ADC Digital Volume + */ +#define WM8991_ADC_VU 0x0100 /* ADC_VU */ +#define WM8991_ADCR_VOL_MASK 0x00FF /* ADCR_VOL - [7:0] */ +#define WM8991_ADCR_VOL_SHIFT 0 + +/* + * R18 (0x12) - GPIO CTRL 1 + */ +#define WM8991_IRQ 0x1000 /* IRQ */ +#define WM8991_TEMPOK 0x0800 /* TEMPOK */ +#define WM8991_MICSHRT 0x0400 /* MICSHRT */ +#define WM8991_MICDET 0x0200 /* MICDET */ +#define WM8991_PLL_LCK 0x0100 /* PLL_LCK */ +#define WM8991_GPI8_STATUS 0x0080 /* GPI8_STATUS */ +#define WM8991_GPI7_STATUS 0x0040 /* GPI7_STATUS */ +#define WM8991_GPIO6_STATUS 0x0020 /* GPIO6_STATUS */ +#define WM8991_GPIO5_STATUS 0x0010 /* GPIO5_STATUS */ +#define WM8991_GPIO4_STATUS 0x0008 /* GPIO4_STATUS */ +#define WM8991_GPIO3_STATUS 0x0004 /* GPIO3_STATUS */ +#define WM8991_GPIO2_STATUS 0x0002 /* GPIO2_STATUS */ +#define WM8991_GPIO1_STATUS 0x0001 /* GPIO1_STATUS */ + +/* + * R19 (0x13) - GPIO1 & GPIO2 + */ +#define WM8991_GPIO2_DEB_ENA 0x8000 /* GPIO2_DEB_ENA */ +#define WM8991_GPIO2_IRQ_ENA 0x4000 /* GPIO2_IRQ_ENA */ +#define WM8991_GPIO2_PU 0x2000 /* GPIO2_PU */ +#define WM8991_GPIO2_PD 0x1000 /* GPIO2_PD */ +#define WM8991_GPIO2_SEL_MASK 0x0F00 /* GPIO2_SEL - [11:8] */ +#define WM8991_GPIO1_DEB_ENA 0x0080 /* GPIO1_DEB_ENA */ +#define WM8991_GPIO1_IRQ_ENA 0x0040 /* GPIO1_IRQ_ENA */ +#define WM8991_GPIO1_PU 0x0020 /* GPIO1_PU */ +#define WM8991_GPIO1_PD 0x0010 /* GPIO1_PD */ +#define WM8991_GPIO1_SEL_MASK 0x000F /* GPIO1_SEL - [3:0] */ + +/* + * R20 (0x14) - GPIO3 & GPIO4 + */ +#define WM8991_GPIO4_DEB_ENA 0x8000 /* GPIO4_DEB_ENA */ +#define WM8991_GPIO4_IRQ_ENA 0x4000 /* GPIO4_IRQ_ENA */ +#define WM8991_GPIO4_PU 0x2000 /* GPIO4_PU */ +#define WM8991_GPIO4_PD 0x1000 /* GPIO4_PD */ +#define WM8991_GPIO4_SEL_MASK 0x0F00 /* GPIO4_SEL - [11:8] */ +#define WM8991_GPIO3_DEB_ENA 0x0080 /* GPIO3_DEB_ENA */ +#define WM8991_GPIO3_IRQ_ENA 0x0040 /* GPIO3_IRQ_ENA */ +#define WM8991_GPIO3_PU 0x0020 /* GPIO3_PU */ +#define WM8991_GPIO3_PD 0x0010 /* GPIO3_PD */ +#define WM8991_GPIO3_SEL_MASK 0x000F /* GPIO3_SEL - [3:0] */ + +/* + * R21 (0x15) - GPIO5 & GPIO6 + */ +#define WM8991_GPIO6_DEB_ENA 0x8000 /* GPIO6_DEB_ENA */ +#define WM8991_GPIO6_IRQ_ENA 0x4000 /* GPIO6_IRQ_ENA */ +#define WM8991_GPIO6_PU 0x2000 /* GPIO6_PU */ +#define WM8991_GPIO6_PD 0x1000 /* GPIO6_PD */ +#define WM8991_GPIO6_SEL_MASK 0x0F00 /* GPIO6_SEL - [11:8] */ +#define WM8991_GPIO5_DEB_ENA 0x0080 /* GPIO5_DEB_ENA */ +#define WM8991_GPIO5_IRQ_ENA 0x0040 /* GPIO5_IRQ_ENA */ +#define WM8991_GPIO5_PU 0x0020 /* GPIO5_PU */ +#define WM8991_GPIO5_PD 0x0010 /* GPIO5_PD */ +#define WM8991_GPIO5_SEL_MASK 0x000F /* GPIO5_SEL - [3:0] */ + +/* + * R22 (0x16) - GPIOCTRL 2 + */ +#define WM8991_RD_3W_ENA 0x8000 /* RD_3W_ENA */ +#define WM8991_MODE_3W4W 0x4000 /* MODE_3W4W */ +#define WM8991_TEMPOK_IRQ_ENA 0x0800 /* TEMPOK_IRQ_ENA */ +#define WM8991_MICSHRT_IRQ_ENA 0x0400 /* MICSHRT_IRQ_ENA */ +#define WM8991_MICDET_IRQ_ENA 0x0200 /* MICDET_IRQ_ENA */ +#define WM8991_PLL_LCK_IRQ_ENA 0x0100 /* PLL_LCK_IRQ_ENA */ +#define WM8991_GPI8_DEB_ENA 0x0080 /* GPI8_DEB_ENA */ +#define WM8991_GPI8_IRQ_ENA 0x0040 /* GPI8_IRQ_ENA */ +#define WM8991_GPI8_ENA 0x0010 /* GPI8_ENA */ +#define WM8991_GPI7_DEB_ENA 0x0008 /* GPI7_DEB_ENA */ +#define WM8991_GPI7_IRQ_ENA 0x0004 /* GPI7_IRQ_ENA */ +#define WM8991_GPI7_ENA 0x0001 /* GPI7_ENA */ + +/* + * R23 (0x17) - GPIO_POL + */ +#define WM8991_IRQ_INV 0x1000 /* IRQ_INV */ +#define WM8991_TEMPOK_POL 0x0800 /* TEMPOK_POL */ +#define WM8991_MICSHRT_POL 0x0400 /* MICSHRT_POL */ +#define WM8991_MICDET_POL 0x0200 /* MICDET_POL */ +#define WM8991_PLL_LCK_POL 0x0100 /* PLL_LCK_POL */ +#define WM8991_GPI8_POL 0x0080 /* GPI8_POL */ +#define WM8991_GPI7_POL 0x0040 /* GPI7_POL */ +#define WM8991_GPIO6_POL 0x0020 /* GPIO6_POL */ +#define WM8991_GPIO5_POL 0x0010 /* GPIO5_POL */ +#define WM8991_GPIO4_POL 0x0008 /* GPIO4_POL */ +#define WM8991_GPIO3_POL 0x0004 /* GPIO3_POL */ +#define WM8991_GPIO2_POL 0x0002 /* GPIO2_POL */ +#define WM8991_GPIO1_POL 0x0001 /* GPIO1_POL */ + +/* + * R24 (0x18) - Left Line Input 1&2 Volume + */ +#define WM8991_IPVU 0x0100 /* IPVU */ +#define WM8991_LI12MUTE 0x0080 /* LI12MUTE */ +#define WM8991_LI12MUTE_BIT 7 +#define WM8991_LI12ZC 0x0040 /* LI12ZC */ +#define WM8991_LI12ZC_BIT 6 +#define WM8991_LIN12VOL_MASK 0x001F /* LIN12VOL - [4:0] */ +#define WM8991_LIN12VOL_SHIFT 0 +/* + * R25 (0x19) - Left Line Input 3&4 Volume + */ +#define WM8991_IPVU 0x0100 /* IPVU */ +#define WM8991_LI34MUTE 0x0080 /* LI34MUTE */ +#define WM8991_LI34MUTE_BIT 7 +#define WM8991_LI34ZC 0x0040 /* LI34ZC */ +#define WM8991_LI34ZC_BIT 6 +#define WM8991_LIN34VOL_MASK 0x001F /* LIN34VOL - [4:0] */ +#define WM8991_LIN34VOL_SHIFT 0 + +/* + * R26 (0x1A) - Right Line Input 1&2 Volume + */ +#define WM8991_IPVU 0x0100 /* IPVU */ +#define WM8991_RI12MUTE 0x0080 /* RI12MUTE */ +#define WM8991_RI12MUTE_BIT 7 +#define WM8991_RI12ZC 0x0040 /* RI12ZC */ +#define WM8991_RI12ZC_BIT 6 +#define WM8991_RIN12VOL_MASK 0x001F /* RIN12VOL - [4:0] */ +#define WM8991_RIN12VOL_SHIFT 0 + +/* + * R27 (0x1B) - Right Line Input 3&4 Volume + */ +#define WM8991_IPVU 0x0100 /* IPVU */ +#define WM8991_RI34MUTE 0x0080 /* RI34MUTE */ +#define WM8991_RI34MUTE_BIT 7 +#define WM8991_RI34ZC 0x0040 /* RI34ZC */ +#define WM8991_RI34ZC_BIT 6 +#define WM8991_RIN34VOL_MASK 0x001F /* RIN34VOL - [4:0] */ +#define WM8991_RIN34VOL_SHIFT 0 + +/* + * R28 (0x1C) - Left Output Volume + */ +#define WM8991_OPVU 0x0100 /* OPVU */ +#define WM8991_LOZC 0x0080 /* LOZC */ +#define WM8991_LOZC_BIT 7 +#define WM8991_LOUTVOL_MASK 0x007F /* LOUTVOL - [6:0] */ +#define WM8991_LOUTVOL_SHIFT 0 +/* + * R29 (0x1D) - Right Output Volume + */ +#define WM8991_OPVU 0x0100 /* OPVU */ +#define WM8991_ROZC 0x0080 /* ROZC */ +#define WM8991_ROZC_BIT 7 +#define WM8991_ROUTVOL_MASK 0x007F /* ROUTVOL - [6:0] */ +#define WM8991_ROUTVOL_SHIFT 0 +/* + * R30 (0x1E) - Line Outputs Volume + */ +#define WM8991_LONMUTE 0x0040 /* LONMUTE */ +#define WM8991_LONMUTE_BIT 6 +#define WM8991_LOPMUTE 0x0020 /* LOPMUTE */ +#define WM8991_LOPMUTE_BIT 5 +#define WM8991_LOATTN 0x0010 /* LOATTN */ +#define WM8991_LOATTN_BIT 4 +#define WM8991_RONMUTE 0x0004 /* RONMUTE */ +#define WM8991_RONMUTE_BIT 2 +#define WM8991_ROPMUTE 0x0002 /* ROPMUTE */ +#define WM8991_ROPMUTE_BIT 1 +#define WM8991_ROATTN 0x0001 /* ROATTN */ +#define WM8991_ROATTN_BIT 0 + +/* + * R31 (0x1F) - Out3/4 Volume + */ +#define WM8991_OUT3MUTE 0x0020 /* OUT3MUTE */ +#define WM8991_OUT3MUTE_BIT 5 +#define WM8991_OUT3ATTN 0x0010 /* OUT3ATTN */ +#define WM8991_OUT3ATTN_BIT 4 +#define WM8991_OUT4MUTE 0x0002 /* OUT4MUTE */ +#define WM8991_OUT4MUTE_BIT 1 +#define WM8991_OUT4ATTN 0x0001 /* OUT4ATTN */ +#define WM8991_OUT4ATTN_BIT 0 + +/* + * R32 (0x20) - Left OPGA Volume + */ +#define WM8991_OPVU 0x0100 /* OPVU */ +#define WM8991_LOPGAZC 0x0080 /* LOPGAZC */ +#define WM8991_LOPGAZC_BIT 7 +#define WM8991_LOPGAVOL_MASK 0x007F /* LOPGAVOL - [6:0] */ +#define WM8991_LOPGAVOL_SHIFT 0 + +/* + * R33 (0x21) - Right OPGA Volume + */ +#define WM8991_OPVU 0x0100 /* OPVU */ +#define WM8991_ROPGAZC 0x0080 /* ROPGAZC */ +#define WM8991_ROPGAZC_BIT 7 +#define WM8991_ROPGAVOL_MASK 0x007F /* ROPGAVOL - [6:0] */ +#define WM8991_ROPGAVOL_SHIFT 0 +/* + * R34 (0x22) - Speaker Volume + */ +#define WM8991_SPKVOL_MASK 0x0003 /* SPKVOL - [1:0] */ +#define WM8991_SPKVOL_SHIFT 0 + +/* + * R35 (0x23) - ClassD1 + */ +#define WM8991_CDMODE 0x0100 /* CDMODE */ +#define WM8991_CDMODE_BIT 8 + +/* + * R37 (0x25) - ClassD3 + */ +#define WM8991_DCGAIN_MASK 0x0007 /* DCGAIN - [5:3] */ +#define WM8991_DCGAIN_SHIFT 3 +#define WM8991_ACGAIN_MASK 0x0007 /* ACGAIN - [2:0] */ +#define WM8991_ACGAIN_SHIFT 0 +/* + * R39 (0x27) - Input Mixer1 + */ +#define WM8991_AINLMODE_MASK 0x000C /* AINLMODE - [3:2] */ +#define WM8991_AINLMODE_SHIFT 2 +#define WM8991_AINRMODE_MASK 0x0003 /* AINRMODE - [1:0] */ +#define WM8991_AINRMODE_SHIFT 0 + +/* + * R40 (0x28) - Input Mixer2 + */ +#define WM8991_LMP4 0x0080 /* LMP4 */ +#define WM8991_LMP4_BIT 7 /* LMP4 */ +#define WM8991_LMN3 0x0040 /* LMN3 */ +#define WM8991_LMN3_BIT 6 /* LMN3 */ +#define WM8991_LMP2 0x0020 /* LMP2 */ +#define WM8991_LMP2_BIT 5 /* LMP2 */ +#define WM8991_LMN1 0x0010 /* LMN1 */ +#define WM8991_LMN1_BIT 4 /* LMN1 */ +#define WM8991_RMP4 0x0008 /* RMP4 */ +#define WM8991_RMP4_BIT 3 /* RMP4 */ +#define WM8991_RMN3 0x0004 /* RMN3 */ +#define WM8991_RMN3_BIT 2 /* RMN3 */ +#define WM8991_RMP2 0x0002 /* RMP2 */ +#define WM8991_RMP2_BIT 1 /* RMP2 */ +#define WM8991_RMN1 0x0001 /* RMN1 */ +#define WM8991_RMN1_BIT 0 /* RMN1 */ + +/* + * R41 (0x29) - Input Mixer3 + */ +#define WM8991_L34MNB 0x0100 /* L34MNB */ +#define WM8991_L34MNB_BIT 8 +#define WM8991_L34MNBST 0x0080 /* L34MNBST */ +#define WM8991_L34MNBST_BIT 7 +#define WM8991_L12MNB 0x0020 /* L12MNB */ +#define WM8991_L12MNB_BIT 5 +#define WM8991_L12MNBST 0x0010 /* L12MNBST */ +#define WM8991_L12MNBST_BIT 4 +#define WM8991_LDBVOL_MASK 0x0007 /* LDBVOL - [2:0] */ +#define WM8991_LDBVOL_SHIFT 0 + +/* + * R42 (0x2A) - Input Mixer4 + */ +#define WM8991_R34MNB 0x0100 /* R34MNB */ +#define WM8991_R34MNB_BIT 8 +#define WM8991_R34MNBST 0x0080 /* R34MNBST */ +#define WM8991_R34MNBST_BIT 7 +#define WM8991_R12MNB 0x0020 /* R12MNB */ +#define WM8991_R12MNB_BIT 5 +#define WM8991_R12MNBST 0x0010 /* R12MNBST */ +#define WM8991_R12MNBST_BIT 4 +#define WM8991_RDBVOL_MASK 0x0007 /* RDBVOL - [2:0] */ +#define WM8991_RDBVOL_SHIFT 0 + +/* + * R43 (0x2B) - Input Mixer5 + */ +#define WM8991_LI2BVOL_MASK 0x07 /* LI2BVOL - [8:6] */ +#define WM8991_LI2BVOL_SHIFT 6 +#define WM8991_LR4BVOL_MASK 0x07 /* LR4BVOL - [5:3] */ +#define WM8991_LR4BVOL_SHIFT 3 +#define WM8991_LL4BVOL_MASK 0x07 /* LL4BVOL - [2:0] */ +#define WM8991_LL4BVOL_SHIFT 0 + +/* + * R44 (0x2C) - Input Mixer6 + */ +#define WM8991_RI2BVOL_MASK 0x07 /* RI2BVOL - [8:6] */ +#define WM8991_RI2BVOL_SHIFT 6 +#define WM8991_RL4BVOL_MASK 0x07 /* RL4BVOL - [5:3] */ +#define WM8991_RL4BVOL_SHIFT 3 +#define WM8991_RR4BVOL_MASK 0x07 /* RR4BVOL - [2:0] */ +#define WM8991_RR4BVOL_SHIFT 0 + +/* + * R45 (0x2D) - Output Mixer1 + */ +#define WM8991_LRBLO 0x0080 /* LRBLO */ +#define WM8991_LRBLO_BIT 7 +#define WM8991_LLBLO 0x0040 /* LLBLO */ +#define WM8991_LLBLO_BIT 6 +#define WM8991_LRI3LO 0x0020 /* LRI3LO */ +#define WM8991_LRI3LO_BIT 5 +#define WM8991_LLI3LO 0x0010 /* LLI3LO */ +#define WM8991_LLI3LO_BIT 4 +#define WM8991_LR12LO 0x0008 /* LR12LO */ +#define WM8991_LR12LO_BIT 3 +#define WM8991_LL12LO 0x0004 /* LL12LO */ +#define WM8991_LL12LO_BIT 2 +#define WM8991_LDLO 0x0001 /* LDLO */ +#define WM8991_LDLO_BIT 0 + +/* + * R46 (0x2E) - Output Mixer2 + */ +#define WM8991_RLBRO 0x0080 /* RLBRO */ +#define WM8991_RLBRO_BIT 7 +#define WM8991_RRBRO 0x0040 /* RRBRO */ +#define WM8991_RRBRO_BIT 6 +#define WM8991_RLI3RO 0x0020 /* RLI3RO */ +#define WM8991_RLI3RO_BIT 5 +#define WM8991_RRI3RO 0x0010 /* RRI3RO */ +#define WM8991_RRI3RO_BIT 4 +#define WM8991_RL12RO 0x0008 /* RL12RO */ +#define WM8991_RL12RO_BIT 3 +#define WM8991_RR12RO 0x0004 /* RR12RO */ +#define WM8991_RR12RO_BIT 2 +#define WM8991_RDRO 0x0001 /* RDRO */ +#define WM8991_RDRO_BIT 0 + +/* + * R47 (0x2F) - Output Mixer3 + */ +#define WM8991_LLI3LOVOL_MASK 0x07 /* LLI3LOVOL - [8:6] */ +#define WM8991_LLI3LOVOL_SHIFT 6 +#define WM8991_LR12LOVOL_MASK 0x07 /* LR12LOVOL - [5:3] */ +#define WM8991_LR12LOVOL_SHIFT 3 +#define WM8991_LL12LOVOL_MASK 0x07 /* LL12LOVOL - [2:0] */ +#define WM8991_LL12LOVOL_SHIFT 0 + +/* + * R48 (0x30) - Output Mixer4 + */ +#define WM8991_RRI3ROVOL_MASK 0x07 /* RRI3ROVOL - [8:6] */ +#define WM8991_RRI3ROVOL_SHIFT 6 +#define WM8991_RL12ROVOL_MASK 0x07 /* RL12ROVOL - [5:3] */ +#define WM8991_RL12ROVOL_SHIFT 3 +#define WM8991_RR12ROVOL_MASK 0x07 /* RR12ROVOL - [2:0] */ +#define WM8991_RR12ROVOL_SHIFT 0 + +/* + * R49 (0x31) - Output Mixer5 + */ +#define WM8991_LRI3LOVOL_MASK 0x07 /* LRI3LOVOL - [8:6] */ +#define WM8991_LRI3LOVOL_SHIFT 6 +#define WM8991_LRBLOVOL_MASK 0x07 /* LRBLOVOL - [5:3] */ +#define WM8991_LRBLOVOL_SHIFT 3 +#define WM8991_LLBLOVOL_MASK 0x07 /* LLBLOVOL - [2:0] */ +#define WM8991_LLBLOVOL_SHIFT 0 + +/* + * R50 (0x32) - Output Mixer6 + */ +#define WM8991_RLI3ROVOL_MASK 0x07 /* RLI3ROVOL - [8:6] */ +#define WM8991_RLI3ROVOL_SHIFT 6 +#define WM8991_RLBROVOL_MASK 0x07 /* RLBROVOL - [5:3] */ +#define WM8991_RLBROVOL_SHIFT 3 +#define WM8991_RRBROVOL_MASK 0x07 /* RRBROVOL - [2:0] */ +#define WM8991_RRBROVOL_SHIFT 0 + +/* + * R51 (0x33) - Out3/4 Mixer + */ +#define WM8991_VSEL_MASK 0x0180 /* VSEL - [8:7] */ +#define WM8991_LI4O3 0x0020 /* LI4O3 */ +#define WM8991_LI4O3_BIT 5 +#define WM8991_LPGAO3 0x0010 /* LPGAO3 */ +#define WM8991_LPGAO3_BIT 4 +#define WM8991_RI4O4 0x0002 /* RI4O4 */ +#define WM8991_RI4O4_BIT 1 +#define WM8991_RPGAO4 0x0001 /* RPGAO4 */ +#define WM8991_RPGAO4_BIT 0 +/* + * R52 (0x34) - Line Mixer1 + */ +#define WM8991_LLOPGALON 0x0040 /* LLOPGALON */ +#define WM8991_LLOPGALON_BIT 6 +#define WM8991_LROPGALON 0x0020 /* LROPGALON */ +#define WM8991_LROPGALON_BIT 5 +#define WM8991_LOPLON 0x0010 /* LOPLON */ +#define WM8991_LOPLON_BIT 4 +#define WM8991_LR12LOP 0x0004 /* LR12LOP */ +#define WM8991_LR12LOP_BIT 2 +#define WM8991_LL12LOP 0x0002 /* LL12LOP */ +#define WM8991_LL12LOP_BIT 1 +#define WM8991_LLOPGALOP 0x0001 /* LLOPGALOP */ +#define WM8991_LLOPGALOP_BIT 0 +/* + * R53 (0x35) - Line Mixer2 + */ +#define WM8991_RROPGARON 0x0040 /* RROPGARON */ +#define WM8991_RROPGARON_BIT 6 +#define WM8991_RLOPGARON 0x0020 /* RLOPGARON */ +#define WM8991_RLOPGARON_BIT 5 +#define WM8991_ROPRON 0x0010 /* ROPRON */ +#define WM8991_ROPRON_BIT 4 +#define WM8991_RL12ROP 0x0004 /* RL12ROP */ +#define WM8991_RL12ROP_BIT 2 +#define WM8991_RR12ROP 0x0002 /* RR12ROP */ +#define WM8991_RR12ROP_BIT 1 +#define WM8991_RROPGAROP 0x0001 /* RROPGAROP */ +#define WM8991_RROPGAROP_BIT 0 + +/* + * R54 (0x36) - Speaker Mixer + */ +#define WM8991_LB2SPK 0x0080 /* LB2SPK */ +#define WM8991_LB2SPK_BIT 7 +#define WM8991_RB2SPK 0x0040 /* RB2SPK */ +#define WM8991_RB2SPK_BIT 6 +#define WM8991_LI2SPK 0x0020 /* LI2SPK */ +#define WM8991_LI2SPK_BIT 5 +#define WM8991_RI2SPK 0x0010 /* RI2SPK */ +#define WM8991_RI2SPK_BIT 4 +#define WM8991_LOPGASPK 0x0008 /* LOPGASPK */ +#define WM8991_LOPGASPK_BIT 3 +#define WM8991_ROPGASPK 0x0004 /* ROPGASPK */ +#define WM8991_ROPGASPK_BIT 2 +#define WM8991_LDSPK 0x0002 /* LDSPK */ +#define WM8991_LDSPK_BIT 1 +#define WM8991_RDSPK 0x0001 /* RDSPK */ +#define WM8991_RDSPK_BIT 0 + +/* + * R55 (0x37) - Additional Control + */ +#define WM8991_VROI 0x0001 /* VROI */ + +/* + * R56 (0x38) - AntiPOP1 + */ +#define WM8991_DIS_LLINE 0x0020 /* DIS_LLINE */ +#define WM8991_DIS_RLINE 0x0010 /* DIS_RLINE */ +#define WM8991_DIS_OUT3 0x0008 /* DIS_OUT3 */ +#define WM8991_DIS_OUT4 0x0004 /* DIS_OUT4 */ +#define WM8991_DIS_LOUT 0x0002 /* DIS_LOUT */ +#define WM8991_DIS_ROUT 0x0001 /* DIS_ROUT */ + +/* + * R57 (0x39) - AntiPOP2 + */ +#define WM8991_SOFTST 0x0040 /* SOFTST */ +#define WM8991_BUFIOEN 0x0008 /* BUFIOEN */ +#define WM8991_BUFDCOPEN 0x0004 /* BUFDCOPEN */ +#define WM8991_POBCTRL 0x0002 /* POBCTRL */ +#define WM8991_VMIDTOG 0x0001 /* VMIDTOG */ + +/* + * R58 (0x3A) - MICBIAS + */ +#define WM8991_MCDSCTH_MASK 0x00C0 /* MCDSCTH - [7:6] */ +#define WM8991_MCDTHR_MASK 0x0038 /* MCDTHR - [5:3] */ +#define WM8991_MCD 0x0004 /* MCD */ +#define WM8991_MBSEL 0x0001 /* MBSEL */ + +/* + * R60 (0x3C) - PLL1 + */ +#define WM8991_SDM 0x0080 /* SDM */ +#define WM8991_PRESCALE 0x0040 /* PRESCALE */ +#define WM8991_PLLN_MASK 0x000F /* PLLN - [3:0] */ + +/* + * R61 (0x3D) - PLL2 + */ +#define WM8991_PLLK1_MASK 0x00FF /* PLLK1 - [7:0] */ + +/* + * R62 (0x3E) - PLL3 + */ +#define WM8991_PLLK2_MASK 0x00FF /* PLLK2 - [7:0] */ + +/* + * R63 (0x3F) - Internal Driver Bits + */ +#define WM8991_INMIXL_PWR_BIT 0 +#define WM8991_AINLMUX_PWR_BIT 1 +#define WM8991_INMIXR_PWR_BIT 2 +#define WM8991_AINRMUX_PWR_BIT 3 + +#define WM8991_MCLK_DIV 0 +#define WM8991_DACCLK_DIV 1 +#define WM8991_ADCCLK_DIV 2 +#define WM8991_BCLK_DIV 3 + +#define SOC_WM899X_OUTPGA_SINGLE_R_TLV(xname, reg, shift, max, invert,\ + tlv_array) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = (xname), \ + .access = SNDRV_CTL_ELEM_ACCESS_TLV_READ |\ + SNDRV_CTL_ELEM_ACCESS_READWRITE,\ + .tlv.p = (tlv_array), \ + .info = snd_soc_info_volsw, \ + .get = snd_soc_get_volsw, .put = wm899x_outpga_put_volsw_vu, \ + .private_value = SOC_SINGLE_VALUE(reg, shift, max, invert) } + +#endif /* _WM8991_H */ diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c index 18c0d9ce7c32..379fa22c5b6c 100644 --- a/sound/soc/codecs/wm8993.c +++ b/sound/soc/codecs/wm8993.c @@ -242,7 +242,7 @@ struct wm8993_priv { int fll_src; }; -static int wm8993_volatile(unsigned int reg) +static int wm8993_volatile(struct snd_soc_codec *codec, unsigned int reg) { switch (reg) { case WM8993_SOFTWARE_RESET: diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index 3351f77607b3..0ca81d3c64e8 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -109,7 +109,7 @@ struct wm8994_priv { struct wm8994_pdata *pdata; }; -static int wm8994_readable(unsigned int reg) +static int wm8994_readable(struct snd_soc_codec *codec, unsigned int reg) { switch (reg) { case WM8994_GPIO_1: @@ -136,7 +136,7 @@ static int wm8994_readable(unsigned int reg) return wm8994_access_masks[reg].readable != 0; } -static int wm8994_volatile(unsigned int reg) +static int wm8994_volatile(struct snd_soc_codec *codec, unsigned int reg) { if (reg >= WM8994_CACHE_SIZE) return 1; @@ -164,7 +164,7 @@ static int wm8994_write(struct snd_soc_codec *codec, unsigned int reg, BUG_ON(reg > WM8994_MAX_REGISTER); - if (!wm8994_volatile(reg)) { + if (!wm8994_volatile(codec, reg)) { ret = snd_soc_cache_write(codec, reg, value); if (ret != 0) dev_err(codec->dev, "Cache write to %x failed: %d\n", @@ -182,7 +182,7 @@ static unsigned int wm8994_read(struct snd_soc_codec *codec, BUG_ON(reg > WM8994_MAX_REGISTER); - if (!wm8994_volatile(reg) && wm8994_readable(reg) && + if (!wm8994_volatile(codec, reg) && wm8994_readable(codec, reg) && reg < codec->driver->reg_cache_size) { ret = snd_soc_cache_read(codec, reg, &val); if (ret >= 0) @@ -2943,7 +2943,7 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) /* Read our current status back from the chip - we don't want to * reset as this may interfere with the GPIO or LDO operation. */ for (i = 0; i < WM8994_CACHE_SIZE; i++) { - if (!wm8994_readable(i) || wm8994_volatile(i)) + if (!wm8994_readable(codec, i) || wm8994_volatile(codec, i)) continue; ret = wm8994_reg_read(codec->control_data, i); diff --git a/sound/soc/codecs/wm8995.c b/sound/soc/codecs/wm8995.c index 608c84c5aa8e..67eaaecbb42e 100644 --- a/sound/soc/codecs/wm8995.c +++ b/sound/soc/codecs/wm8995.c @@ -19,6 +19,7 @@ #include <linux/pm.h> #include <linux/i2c.h> #include <linux/spi/spi.h> +#include <linux/regulator/consumer.h> #include <linux/slab.h> #include <sound/core.h> #include <sound/pcm.h> @@ -30,6 +31,18 @@ #include "wm8995.h" +#define WM8995_NUM_SUPPLIES 8 +static const char *wm8995_supply_names[WM8995_NUM_SUPPLIES] = { + "DCVDD", + "DBVDD1", + "DBVDD2", + "DBVDD3", + "AVDD1", + "AVDD2", + "CPVDD", + "MICVDD" +}; + static const u16 wm8995_reg_defs[WM8995_MAX_REGISTER + 1] = { [0] = 0x8995, [5] = 0x0100, [16] = 0x000b, [17] = 0x000b, [24] = 0x02c0, [25] = 0x02c0, [26] = 0x02c0, [27] = 0x02c0, @@ -126,8 +139,37 @@ struct wm8995_priv { int mclk[2]; int aifclk[2]; struct fll_config fll[2], fll_suspend[2]; + struct regulator_bulk_data supplies[WM8995_NUM_SUPPLIES]; + struct notifier_block disable_nb[WM8995_NUM_SUPPLIES]; + struct snd_soc_codec *codec; }; +/* + * We can't use the same notifier block for more than one supply and + * there's no way I can see to get from a callback to the caller + * except container_of(). + */ +#define WM8995_REGULATOR_EVENT(n) \ +static int wm8995_regulator_event_##n(struct notifier_block *nb, \ + unsigned long event, void *data) \ +{ \ + struct wm8995_priv *wm8995 = container_of(nb, struct wm8995_priv, \ + disable_nb[n]); \ + if (event & REGULATOR_EVENT_DISABLE) { \ + wm8995->codec->cache_sync = 1; \ + } \ + return 0; \ +} + +WM8995_REGULATOR_EVENT(0) +WM8995_REGULATOR_EVENT(1) +WM8995_REGULATOR_EVENT(2) +WM8995_REGULATOR_EVENT(3) +WM8995_REGULATOR_EVENT(4) +WM8995_REGULATOR_EVENT(5) +WM8995_REGULATOR_EVENT(6) +WM8995_REGULATOR_EVENT(7) + static const DECLARE_TLV_DB_SCALE(digital_tlv, -7200, 75, 1); static const DECLARE_TLV_DB_SCALE(in1lr_pga_tlv, -1650, 150, 0); static const DECLARE_TLV_DB_SCALE(in1l_boost_tlv, 0, 600, 0); @@ -909,7 +951,7 @@ static const struct snd_soc_dapm_route wm8995_intercon[] = { { "SPK2R", NULL, "SPK2R Driver" } }; -static int wm8995_volatile(unsigned int reg) +static int wm8995_volatile(struct snd_soc_codec *codec, unsigned int reg) { /* out of bounds registers are generally considered * volatile to support register banks that are partially @@ -1483,6 +1525,11 @@ static int wm8995_set_bias_level(struct snd_soc_codec *codec, break; case SND_SOC_BIAS_STANDBY: if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(wm8995->supplies), + wm8995->supplies); + if (ret) + return ret; + ret = snd_soc_cache_sync(codec); if (ret) { dev_err(codec->dev, @@ -1492,12 +1539,13 @@ static int wm8995_set_bias_level(struct snd_soc_codec *codec, snd_soc_update_bits(codec, WM8995_POWER_MANAGEMENT_1, WM8995_BG_ENA_MASK, WM8995_BG_ENA); - } break; case SND_SOC_BIAS_OFF: snd_soc_update_bits(codec, WM8995_POWER_MANAGEMENT_1, WM8995_BG_ENA_MASK, 0); + regulator_bulk_disable(ARRAY_SIZE(wm8995->supplies), + wm8995->supplies); break; } @@ -1536,10 +1584,12 @@ static int wm8995_remove(struct snd_soc_codec *codec) static int wm8995_probe(struct snd_soc_codec *codec) { struct wm8995_priv *wm8995; + int i; int ret; codec->dapm.idle_bias_off = 1; wm8995 = snd_soc_codec_get_drvdata(codec); + wm8995->codec = codec; ret = snd_soc_codec_set_cache_io(codec, 16, 16, wm8995->control_type); if (ret < 0) { @@ -1547,21 +1597,58 @@ static int wm8995_probe(struct snd_soc_codec *codec) return ret; } + for (i = 0; i < ARRAY_SIZE(wm8995->supplies); i++) + wm8995->supplies[i].supply = wm8995_supply_names[i]; + + ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(wm8995->supplies), + wm8995->supplies); + if (ret) { + dev_err(codec->dev, "Failed to request supplies: %d\n", ret); + return ret; + } + + wm8995->disable_nb[0].notifier_call = wm8995_regulator_event_0; + wm8995->disable_nb[1].notifier_call = wm8995_regulator_event_1; + wm8995->disable_nb[2].notifier_call = wm8995_regulator_event_2; + wm8995->disable_nb[3].notifier_call = wm8995_regulator_event_3; + wm8995->disable_nb[4].notifier_call = wm8995_regulator_event_4; + wm8995->disable_nb[5].notifier_call = wm8995_regulator_event_5; + wm8995->disable_nb[6].notifier_call = wm8995_regulator_event_6; + wm8995->disable_nb[7].notifier_call = wm8995_regulator_event_7; + + /* This should really be moved into the regulator core */ + for (i = 0; i < ARRAY_SIZE(wm8995->supplies); i++) { + ret = regulator_register_notifier(wm8995->supplies[i].consumer, + &wm8995->disable_nb[i]); + if (ret) { + dev_err(codec->dev, + "Failed to register regulator notifier: %d\n", + ret); + } + } + + ret = regulator_bulk_enable(ARRAY_SIZE(wm8995->supplies), + wm8995->supplies); + if (ret) { + dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); + goto err_reg_get; + } + ret = snd_soc_read(codec, WM8995_SOFTWARE_RESET); if (ret < 0) { dev_err(codec->dev, "Failed to read device ID: %d\n", ret); - return ret; + goto err_reg_enable; } if (ret != 0x8995) { dev_err(codec->dev, "Invalid device ID: %#x\n", ret); - return -EINVAL; + goto err_reg_enable; } ret = snd_soc_write(codec, WM8995_SOFTWARE_RESET, 0); if (ret < 0) { dev_err(codec->dev, "Failed to issue reset: %d\n", ret); - return ret; + goto err_reg_enable; } wm8995_set_bias_level(codec, SND_SOC_BIAS_STANDBY); @@ -1596,6 +1683,12 @@ static int wm8995_probe(struct snd_soc_codec *codec) ARRAY_SIZE(wm8995_intercon)); return 0; + +err_reg_enable: + regulator_bulk_disable(ARRAY_SIZE(wm8995->supplies), wm8995->supplies); +err_reg_get: + regulator_bulk_free(ARRAY_SIZE(wm8995->supplies), wm8995->supplies); + return ret; } #define WM8995_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c index 43825b2102a5..5c224dd917d7 100644 --- a/sound/soc/codecs/wm9081.c +++ b/sound/soc/codecs/wm9081.c @@ -169,7 +169,7 @@ struct wm9081_priv { struct wm9081_retune_mobile_config *retune; }; -static int wm9081_volatile_register(unsigned int reg) +static int wm9081_volatile_register(struct snd_soc_codec *codec, unsigned int reg) { switch (reg) { case WM9081_SOFTWARE_RESET: diff --git a/sound/soc/codecs/wm9090.c b/sound/soc/codecs/wm9090.c index a788c4297046..4de12203e611 100644 --- a/sound/soc/codecs/wm9090.c +++ b/sound/soc/codecs/wm9090.c @@ -144,7 +144,7 @@ struct wm9090_priv { void *control_data; }; -static int wm9090_volatile(unsigned int reg) +static int wm9090_volatile(struct snd_soc_codec *codec, unsigned int reg) { switch (reg) { case WM9090_SOFTWARE_RESET: @@ -518,7 +518,7 @@ static int wm9090_set_bias_level(struct snd_soc_codec *codec, for (i = 1; i < codec->driver->reg_cache_size; i++) { if (reg_cache[i] == wm9090_reg_defaults[i]) continue; - if (wm9090_volatile(i)) + if (wm9090_volatile(codec, i)) continue; ret = snd_soc_write(codec, i, reg_cache[i]); @@ -551,7 +551,6 @@ static int wm9090_set_bias_level(struct snd_soc_codec *codec, static int wm9090_probe(struct snd_soc_codec *codec) { struct wm9090_priv *wm9090 = snd_soc_codec_get_drvdata(codec); - u16 *reg_cache = codec->reg_cache; int ret; codec->control_data = wm9090->control_data; @@ -576,22 +575,30 @@ static int wm9090_probe(struct snd_soc_codec *codec) /* Configure some defaults; they will be written out when we * bring the bias up. */ - reg_cache[WM9090_IN1_LINE_INPUT_A_VOLUME] |= WM9090_IN1_VU - | WM9090_IN1A_ZC; - reg_cache[WM9090_IN1_LINE_INPUT_B_VOLUME] |= WM9090_IN1_VU - | WM9090_IN1B_ZC; - reg_cache[WM9090_IN2_LINE_INPUT_A_VOLUME] |= WM9090_IN2_VU - | WM9090_IN2A_ZC; - reg_cache[WM9090_IN2_LINE_INPUT_B_VOLUME] |= WM9090_IN2_VU - | WM9090_IN2B_ZC; - reg_cache[WM9090_SPEAKER_VOLUME_LEFT] |= - WM9090_SPKOUT_VU | WM9090_SPKOUTL_ZC; - reg_cache[WM9090_LEFT_OUTPUT_VOLUME] |= - WM9090_HPOUT1_VU | WM9090_HPOUT1L_ZC; - reg_cache[WM9090_RIGHT_OUTPUT_VOLUME] |= - WM9090_HPOUT1_VU | WM9090_HPOUT1R_ZC; - - reg_cache[WM9090_CLOCKING_1] |= WM9090_TOCLK_ENA; + snd_soc_update_bits(codec, WM9090_IN1_LINE_INPUT_A_VOLUME, + WM9090_IN1_VU | WM9090_IN1A_ZC, + WM9090_IN1_VU | WM9090_IN1A_ZC); + snd_soc_update_bits(codec, WM9090_IN1_LINE_INPUT_B_VOLUME, + WM9090_IN1_VU | WM9090_IN1B_ZC, + WM9090_IN1_VU | WM9090_IN1B_ZC); + snd_soc_update_bits(codec, WM9090_IN2_LINE_INPUT_A_VOLUME, + WM9090_IN2_VU | WM9090_IN2A_ZC, + WM9090_IN2_VU | WM9090_IN2A_ZC); + snd_soc_update_bits(codec, WM9090_IN2_LINE_INPUT_B_VOLUME, + WM9090_IN2_VU | WM9090_IN2B_ZC, + WM9090_IN2_VU | WM9090_IN2B_ZC); + snd_soc_update_bits(codec, WM9090_SPEAKER_VOLUME_LEFT, + WM9090_SPKOUT_VU | WM9090_SPKOUTL_ZC, + WM9090_SPKOUT_VU | WM9090_SPKOUTL_ZC); + snd_soc_update_bits(codec, WM9090_LEFT_OUTPUT_VOLUME, + WM9090_HPOUT1_VU | WM9090_HPOUT1L_ZC, + WM9090_HPOUT1_VU | WM9090_HPOUT1L_ZC); + snd_soc_update_bits(codec, WM9090_RIGHT_OUTPUT_VOLUME, + WM9090_HPOUT1_VU | WM9090_HPOUT1R_ZC, + WM9090_HPOUT1_VU | WM9090_HPOUT1R_ZC); + + snd_soc_update_bits(codec, WM9090_CLOCKING_1, + WM9090_TOCLK_ENA, WM9090_TOCLK_ENA); wm9090_set_bias_level(codec, SND_SOC_BIAS_STANDBY); diff --git a/sound/soc/mid-x86/Kconfig b/sound/soc/mid-x86/Kconfig new file mode 100644 index 000000000000..1ad753836356 --- /dev/null +++ b/sound/soc/mid-x86/Kconfig @@ -0,0 +1,14 @@ +config SND_MFLD_MACHINE + tristate "SOC Machine Audio driver for Intel Medfield MID platform" + depends on INTEL_SCU_IPC + select SND_SOC_SN95031 + select SND_SST_PLATFORM + help + This adds support for ASoC machine driver for Intel(R) MID Medfield platform + used as alsa device in audio substem in Intel(R) MID devices + Say Y if you have such a device + If unsure select "N". + +config SND_SST_PLATFORM + tristate + depends on SND_INTEL_SST diff --git a/sound/soc/mid-x86/Makefile b/sound/soc/mid-x86/Makefile new file mode 100644 index 000000000000..639883339465 --- /dev/null +++ b/sound/soc/mid-x86/Makefile @@ -0,0 +1,5 @@ +snd-soc-sst-platform-objs := sst_platform.o +snd-soc-mfld-machine-objs := mfld_machine.o + +obj-$(CONFIG_SND_SST_PLATFORM) += snd-soc-sst-platform.o +obj-$(CONFIG_SND_MFLD_MACHINE) += snd-soc-mfld-machine.o diff --git a/sound/soc/mid-x86/mfld_machine.c b/sound/soc/mid-x86/mfld_machine.c new file mode 100644 index 000000000000..1a330be1a01e --- /dev/null +++ b/sound/soc/mid-x86/mfld_machine.c @@ -0,0 +1,296 @@ +/* + * mfld_machine.c - ASoc Machine driver for Intel Medfield MID platform + * + * Copyright (C) 2010 Intel Corp + * Author: Vinod Koul <vinod.koul@intel.com> + * Author: Harsha Priya <priya.harsha@intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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 of the License. + * + * 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., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/init.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "../codecs/sn95031.h" + +#define MID_MONO 1 +#define MID_STEREO 2 +#define MID_MAX_CAP 5 + +static unsigned int hs_switch; +static unsigned int lo_dac; + +/* sound card controls */ +static const char *headset_switch_text[] = {"Earpiece", "Headset"}; + +static const char *lo_text[] = {"Vibra", "Headset", "IHF", "None"}; + +static const struct soc_enum headset_enum = + SOC_ENUM_SINGLE_EXT(2, headset_switch_text); + +static const struct soc_enum lo_enum = + SOC_ENUM_SINGLE_EXT(4, lo_text); + +static int headset_get_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = hs_switch; + return 0; +} + +static int headset_set_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (ucontrol->value.integer.value[0] == hs_switch) + return 0; + + if (ucontrol->value.integer.value[0]) { + pr_debug("hs_set HS path\n"); + snd_soc_dapm_enable_pin(&codec->dapm, "HPOUTL"); + snd_soc_dapm_enable_pin(&codec->dapm, "HPOUTR"); + snd_soc_dapm_disable_pin(&codec->dapm, "EPOUT"); + } else { + pr_debug("hs_set EP path\n"); + snd_soc_dapm_disable_pin(&codec->dapm, "HPOUTL"); + snd_soc_dapm_disable_pin(&codec->dapm, "HPOUTR"); + snd_soc_dapm_enable_pin(&codec->dapm, "EPOUT"); + } + snd_soc_dapm_sync(&codec->dapm); + hs_switch = ucontrol->value.integer.value[0]; + + return 0; +} + +static void lo_enable_out_pins(struct snd_soc_codec *codec) +{ + snd_soc_dapm_enable_pin(&codec->dapm, "IHFOUTL"); + snd_soc_dapm_enable_pin(&codec->dapm, "IHFOUTR"); + snd_soc_dapm_enable_pin(&codec->dapm, "LINEOUTL"); + snd_soc_dapm_enable_pin(&codec->dapm, "LINEOUTR"); + snd_soc_dapm_enable_pin(&codec->dapm, "VIB1OUT"); + snd_soc_dapm_enable_pin(&codec->dapm, "VIB2OUT"); + if (hs_switch) { + snd_soc_dapm_enable_pin(&codec->dapm, "HPOUTL"); + snd_soc_dapm_enable_pin(&codec->dapm, "HPOUTR"); + snd_soc_dapm_disable_pin(&codec->dapm, "EPOUT"); + } else { + snd_soc_dapm_disable_pin(&codec->dapm, "HPOUTL"); + snd_soc_dapm_disable_pin(&codec->dapm, "HPOUTR"); + snd_soc_dapm_enable_pin(&codec->dapm, "EPOUT"); + } +} + +static int lo_get_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + ucontrol->value.integer.value[0] = lo_dac; + return 0; +} + +static int lo_set_switch(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + + if (ucontrol->value.integer.value[0] == lo_dac) + return 0; + + /* we dont want to work with last state of lineout so just enable all + * pins and then disable pins not required + */ + lo_enable_out_pins(codec); + switch (ucontrol->value.integer.value[0]) { + case 0: + pr_debug("set vibra path\n"); + snd_soc_dapm_disable_pin(&codec->dapm, "VIB1OUT"); + snd_soc_dapm_disable_pin(&codec->dapm, "VIB2OUT"); + snd_soc_update_bits(codec, SN95031_LOCTL, 0x66, 0); + break; + + case 1: + pr_debug("set hs path\n"); + snd_soc_dapm_disable_pin(&codec->dapm, "HPOUTL"); + snd_soc_dapm_disable_pin(&codec->dapm, "HPOUTR"); + snd_soc_dapm_disable_pin(&codec->dapm, "EPOUT"); + snd_soc_update_bits(codec, SN95031_LOCTL, 0x66, 0x22); + break; + + case 2: + pr_debug("set spkr path\n"); + snd_soc_dapm_disable_pin(&codec->dapm, "IHFOUTL"); + snd_soc_dapm_disable_pin(&codec->dapm, "IHFOUTR"); + snd_soc_update_bits(codec, SN95031_LOCTL, 0x66, 0x44); + break; + + case 3: + pr_debug("set null path\n"); + snd_soc_dapm_disable_pin(&codec->dapm, "LINEOUTL"); + snd_soc_dapm_disable_pin(&codec->dapm, "LINEOUTR"); + snd_soc_update_bits(codec, SN95031_LOCTL, 0x66, 0x66); + break; + } + snd_soc_dapm_sync(&codec->dapm); + lo_dac = ucontrol->value.integer.value[0]; + return 0; +} + +static const struct snd_kcontrol_new mfld_snd_controls[] = { + SOC_ENUM_EXT("Playback Switch", headset_enum, + headset_get_switch, headset_set_switch), + SOC_ENUM_EXT("Lineout Mux", lo_enum, + lo_get_switch, lo_set_switch), +}; + +static int mfld_init(struct snd_soc_pcm_runtime *runtime) +{ + struct snd_soc_codec *codec = runtime->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + int ret_val; + + ret_val = snd_soc_add_controls(codec, mfld_snd_controls, + ARRAY_SIZE(mfld_snd_controls)); + if (ret_val) { + pr_err("soc_add_controls failed %d", ret_val); + return ret_val; + } + /* default is earpiece pin, userspace sets it explcitly */ + snd_soc_dapm_disable_pin(dapm, "HPOUTL"); + snd_soc_dapm_disable_pin(dapm, "HPOUTR"); + /* default is lineout NC, userspace sets it explcitly */ + snd_soc_dapm_disable_pin(dapm, "LINEOUTL"); + snd_soc_dapm_disable_pin(dapm, "LINEOUTR"); + lo_dac = 3; + hs_switch = 0; + return snd_soc_dapm_sync(dapm); +} + +struct snd_soc_dai_link mfld_msic_dailink[] = { + { + .name = "Medfield Headset", + .stream_name = "Headset", + .cpu_dai_name = "Headset-cpu-dai", + .codec_dai_name = "SN95031 Headset", + .codec_name = "sn95031", + .platform_name = "sst-platform", + .init = mfld_init, + }, + { + .name = "Medfield Speaker", + .stream_name = "Speaker", + .cpu_dai_name = "Speaker-cpu-dai", + .codec_dai_name = "SN95031 Speaker", + .codec_name = "sn95031", + .platform_name = "sst-platform", + .init = NULL, + }, + { + .name = "Medfield Vibra", + .stream_name = "Vibra1", + .cpu_dai_name = "Vibra1-cpu-dai", + .codec_dai_name = "SN95031 Vibra1", + .codec_name = "sn95031", + .platform_name = "sst-platform", + .init = NULL, + }, + { + .name = "Medfield Haptics", + .stream_name = "Vibra2", + .cpu_dai_name = "Vibra2-cpu-dai", + .codec_dai_name = "SN95031 Vibra2", + .codec_name = "sn95031", + .platform_name = "sst-platform", + .init = NULL, + }, +}; + +/* SoC card */ +static struct snd_soc_card snd_soc_card_mfld = { + .name = "medfield_audio", + .dai_link = mfld_msic_dailink, + .num_links = ARRAY_SIZE(mfld_msic_dailink), +}; + +static int __devinit snd_mfld_mc_probe(struct platform_device *pdev) +{ + struct platform_device *socdev; + int ret_val = 0; + + pr_debug("snd_mfld_mc_probe called\n"); + + socdev = platform_device_alloc("soc-audio", -1); + if (!socdev) { + pr_err("soc-audio device allocation failed\n"); + return -ENOMEM; + } + platform_set_drvdata(socdev, &snd_soc_card_mfld); + ret_val = platform_device_add(socdev); + if (ret_val) { + pr_err("Unable to add soc-audio device, err %d\n", ret_val); + platform_device_put(socdev); + } + + platform_set_drvdata(pdev, socdev); + + pr_debug("successfully exited probe\n"); + return ret_val; +} + +static int __devexit snd_mfld_mc_remove(struct platform_device *pdev) +{ + struct platform_device *socdev = platform_get_drvdata(pdev); + pr_debug("snd_mfld_mc_remove called\n"); + + platform_device_unregister(socdev); + platform_set_drvdata(pdev, NULL); + return 0; +} + +static struct platform_driver snd_mfld_mc_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "msic_audio", + }, + .probe = snd_mfld_mc_probe, + .remove = __devexit_p(snd_mfld_mc_remove), +}; + +static int __init snd_mfld_driver_init(void) +{ + pr_debug("snd_mfld_driver_init called\n"); + return platform_driver_register(&snd_mfld_mc_driver); +} +module_init(snd_mfld_driver_init); + +static void __exit snd_mfld_driver_exit(void) +{ + pr_debug("snd_mfld_driver_exit called\n"); + platform_driver_unregister(&snd_mfld_mc_driver); +} +module_exit(snd_mfld_driver_exit); + +MODULE_DESCRIPTION("ASoC Intel(R) MID Machine driver"); +MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>"); +MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:msic-audio"); diff --git a/sound/soc/mid-x86/sst_platform.c b/sound/soc/mid-x86/sst_platform.c new file mode 100644 index 000000000000..1d1f5447b338 --- /dev/null +++ b/sound/soc/mid-x86/sst_platform.c @@ -0,0 +1,459 @@ +/* + * sst_platform.c - Intel MID Platform driver + * + * Copyright (C) 2010 Intel Corp + * Author: Vinod Koul <vinod.koul@intel.com> + * Author: Harsha Priya <priya.harsha@intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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 of the License. + * + * 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., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * + */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/slab.h> +#include <linux/io.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include "../../../drivers/staging/intel_sst/intel_sst_ioctl.h" +#include "../../../drivers/staging/intel_sst/intel_sst.h" +#include "sst_platform.h" + +static struct snd_pcm_hardware sst_platform_pcm_hw = { + .info = (SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_DOUBLE | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_MMAP| + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_SYNC_START), + .formats = (SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_U16 | + SNDRV_PCM_FMTBIT_S24 | SNDRV_PCM_FMTBIT_U24 | + SNDRV_PCM_FMTBIT_S32 | SNDRV_PCM_FMTBIT_U32), + .rates = (SNDRV_PCM_RATE_8000| + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000), + .rate_min = SST_MIN_RATE, + .rate_max = SST_MAX_RATE, + .channels_min = SST_MIN_CHANNEL, + .channels_max = SST_MAX_CHANNEL, + .buffer_bytes_max = SST_MAX_BUFFER, + .period_bytes_min = SST_MIN_PERIOD_BYTES, + .period_bytes_max = SST_MAX_PERIOD_BYTES, + .periods_min = SST_MIN_PERIODS, + .periods_max = SST_MAX_PERIODS, + .fifo_size = SST_FIFO_SIZE, +}; + +/* MFLD - MSIC */ +struct snd_soc_dai_driver sst_platform_dai[] = { +{ + .name = "Headset-cpu-dai", + .id = 0, + .playback = { + .channels_min = SST_STEREO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +{ + .name = "Speaker-cpu-dai", + .id = 1, + .playback = { + .channels_min = SST_MONO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +{ + .name = "Vibra1-cpu-dai", + .id = 2, + .playback = { + .channels_min = SST_MONO, + .channels_max = SST_MONO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +{ + .name = "Vibra2-cpu-dai", + .id = 3, + .playback = { + .channels_min = SST_MONO, + .channels_max = SST_STEREO, + .rates = SNDRV_PCM_RATE_48000, + .formats = SNDRV_PCM_FMTBIT_S24_LE, + }, +}, +}; + +/* helper functions */ +static inline void sst_set_stream_status(struct sst_runtime_stream *stream, + int state) +{ + spin_lock(&stream->status_lock); + stream->stream_status = state; + spin_unlock(&stream->status_lock); +} + +static inline int sst_get_stream_status(struct sst_runtime_stream *stream) +{ + int state; + + spin_lock(&stream->status_lock); + state = stream->stream_status; + spin_unlock(&stream->status_lock); + return state; +} + +static void sst_fill_pcm_params(struct snd_pcm_substream *substream, + struct snd_sst_stream_params *param) +{ + + param->uc.pcm_params.codec = SST_CODEC_TYPE_PCM; + param->uc.pcm_params.num_chan = (u8) substream->runtime->channels; + param->uc.pcm_params.pcm_wd_sz = substream->runtime->sample_bits; + param->uc.pcm_params.reserved = 0; + param->uc.pcm_params.sfreq = substream->runtime->rate; + param->uc.pcm_params.ring_buffer_size = + snd_pcm_lib_buffer_bytes(substream); + param->uc.pcm_params.period_count = substream->runtime->period_size; + param->uc.pcm_params.ring_buffer_addr = + virt_to_phys(substream->dma_buffer.area); + pr_debug("period_cnt = %d\n", param->uc.pcm_params.period_count); + pr_debug("sfreq= %d, wd_sz = %d\n", + param->uc.pcm_params.sfreq, param->uc.pcm_params.pcm_wd_sz); +} + +static int sst_platform_alloc_stream(struct snd_pcm_substream *substream) +{ + struct sst_runtime_stream *stream = + substream->runtime->private_data; + struct snd_sst_stream_params param = {{{0,},},}; + struct snd_sst_params str_params = {0}; + int ret_val; + + /* set codec params and inform SST driver the same */ + sst_fill_pcm_params(substream, ¶m); + substream->runtime->dma_area = substream->dma_buffer.area; + str_params.sparams = param; + str_params.codec = param.uc.pcm_params.codec; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + str_params.ops = STREAM_OPS_PLAYBACK; + str_params.device_type = substream->pcm->device + 1; + pr_debug("Playbck stream,Device %d\n", + substream->pcm->device); + } else { + str_params.ops = STREAM_OPS_CAPTURE; + str_params.device_type = SND_SST_DEVICE_CAPTURE; + pr_debug("Capture stream,Device %d\n", + substream->pcm->device); + } + ret_val = stream->sstdrv_ops->pcm_control->open(&str_params); + pr_debug("SST_SND_PLAY/CAPTURE ret_val = %x\n", ret_val); + if (ret_val < 0) + return ret_val; + + stream->stream_info.str_id = ret_val; + pr_debug("str id : %d\n", stream->stream_info.str_id); + return ret_val; +} + +static void sst_period_elapsed(void *mad_substream) +{ + struct snd_pcm_substream *substream = mad_substream; + struct sst_runtime_stream *stream; + int status; + + if (!substream || !substream->runtime) + return; + stream = substream->runtime->private_data; + if (!stream) + return; + status = sst_get_stream_status(stream); + if (status != SST_PLATFORM_RUNNING) + return; + snd_pcm_period_elapsed(substream); +} + +static int sst_platform_init_stream(struct snd_pcm_substream *substream) +{ + struct sst_runtime_stream *stream = + substream->runtime->private_data; + int ret_val; + + pr_debug("setting buffer ptr param\n"); + sst_set_stream_status(stream, SST_PLATFORM_INIT); + stream->stream_info.period_elapsed = sst_period_elapsed; + stream->stream_info.mad_substream = substream; + stream->stream_info.buffer_ptr = 0; + stream->stream_info.sfreq = substream->runtime->rate; + ret_val = stream->sstdrv_ops->pcm_control->device_control( + SST_SND_STREAM_INIT, &stream->stream_info); + if (ret_val) + pr_err("control_set ret error %d\n", ret_val); + return ret_val; + +} +/* end -- helper functions */ + +static int sst_platform_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime; + struct sst_runtime_stream *stream; + int ret_val = 0; + + pr_debug("sst_platform_open called\n"); + runtime = substream->runtime; + runtime->hw = sst_platform_pcm_hw; + stream = kzalloc(sizeof(*stream), GFP_KERNEL); + if (!stream) + return -ENOMEM; + spin_lock_init(&stream->status_lock); + stream->stream_info.str_id = 0; + sst_set_stream_status(stream, SST_PLATFORM_INIT); + stream->stream_info.mad_substream = substream; + /* allocate memory for SST API set */ + stream->sstdrv_ops = kzalloc(sizeof(*stream->sstdrv_ops), + GFP_KERNEL); + if (!stream->sstdrv_ops) { + pr_err("sst: mem allocation for ops fail\n"); + kfree(stream); + return -ENOMEM; + } + stream->sstdrv_ops->vendor_id = MSIC_VENDOR_ID; + /* registering with SST driver to get access to SST APIs to use */ + ret_val = register_sst_card(stream->sstdrv_ops); + if (ret_val) { + pr_err("sst: sst card registration failed\n"); + return ret_val; + } + runtime->private_data = stream; + return snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); +} + +static int sst_platform_close(struct snd_pcm_substream *substream) +{ + struct sst_runtime_stream *stream; + int ret_val = 0, str_id; + + pr_debug("sst_platform_close called\n"); + stream = substream->runtime->private_data; + str_id = stream->stream_info.str_id; + if (str_id) + ret_val = stream->sstdrv_ops->pcm_control->close(str_id); + kfree(stream->sstdrv_ops); + kfree(stream); + return ret_val; +} + +static int sst_platform_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct sst_runtime_stream *stream; + int ret_val = 0, str_id; + + pr_debug("sst_platform_pcm_prepare called\n"); + stream = substream->runtime->private_data; + str_id = stream->stream_info.str_id; + if (stream->stream_info.str_id) { + ret_val = stream->sstdrv_ops->pcm_control->device_control( + SST_SND_DROP, &str_id); + return ret_val; + } + + ret_val = sst_platform_alloc_stream(substream); + if (ret_val < 0) + return ret_val; + snprintf(substream->pcm->id, sizeof(substream->pcm->id), + "%d", stream->stream_info.str_id); + + ret_val = sst_platform_init_stream(substream); + if (ret_val) + return ret_val; + substream->runtime->hw.info = SNDRV_PCM_INFO_BLOCK_TRANSFER; + return ret_val; +} + +static int sst_platform_pcm_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + int ret_val = 0, str_id; + struct sst_runtime_stream *stream; + int str_cmd, status; + + pr_debug("sst_platform_pcm_trigger called\n"); + stream = substream->runtime->private_data; + str_id = stream->stream_info.str_id; + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + pr_debug("sst: Trigger Start\n"); + str_cmd = SST_SND_START; + status = SST_PLATFORM_RUNNING; + stream->stream_info.mad_substream = substream; + break; + case SNDRV_PCM_TRIGGER_STOP: + pr_debug("sst: in stop\n"); + str_cmd = SST_SND_DROP; + status = SST_PLATFORM_DROPPED; + break; + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + pr_debug("sst: in pause\n"); + str_cmd = SST_SND_PAUSE; + status = SST_PLATFORM_PAUSED; + break; + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + pr_debug("sst: in pause release\n"); + str_cmd = SST_SND_RESUME; + status = SST_PLATFORM_RUNNING; + break; + default: + return -EINVAL; + } + ret_val = stream->sstdrv_ops->pcm_control->device_control(str_cmd, + &str_id); + if (!ret_val) + sst_set_stream_status(stream, status); + + return ret_val; +} + + +static snd_pcm_uframes_t sst_platform_pcm_pointer + (struct snd_pcm_substream *substream) +{ + struct sst_runtime_stream *stream; + int ret_val, status; + struct pcm_stream_info *str_info; + + stream = substream->runtime->private_data; + status = sst_get_stream_status(stream); + if (status == SST_PLATFORM_INIT) + return 0; + str_info = &stream->stream_info; + ret_val = stream->sstdrv_ops->pcm_control->device_control( + SST_SND_BUFFER_POINTER, str_info); + if (ret_val) { + pr_err("sst: error code = %d\n", ret_val); + return ret_val; + } + return stream->stream_info.buffer_ptr; +} + + +static struct snd_pcm_ops sst_platform_ops = { + .open = sst_platform_open, + .close = sst_platform_close, + .ioctl = snd_pcm_lib_ioctl, + .prepare = sst_platform_pcm_prepare, + .trigger = sst_platform_pcm_trigger, + .pointer = sst_platform_pcm_pointer, +}; + +static void sst_pcm_free(struct snd_pcm *pcm) +{ + pr_debug("sst_pcm_free called\n"); + snd_pcm_lib_preallocate_free_for_all(pcm); +} + +int sst_pcm_new(struct snd_card *card, struct snd_soc_dai *dai, + struct snd_pcm *pcm) +{ + int retval = 0; + + pr_debug("sst_pcm_new called\n"); + if (dai->driver->playback.channels_min || + dai->driver->capture.channels_min) { + retval = snd_pcm_lib_preallocate_pages_for_all(pcm, + SNDRV_DMA_TYPE_CONTINUOUS, + snd_dma_continuous_data(GFP_KERNEL), + SST_MIN_BUFFER, SST_MAX_BUFFER); + if (retval) { + pr_err("dma buffer allocationf fail\n"); + return retval; + } + } + return retval; +} +struct snd_soc_platform_driver sst_soc_platform_drv = { + .ops = &sst_platform_ops, + .pcm_new = sst_pcm_new, + .pcm_free = sst_pcm_free, +}; + +static int sst_platform_probe(struct platform_device *pdev) +{ + int ret; + + pr_debug("sst_platform_probe called\n"); + ret = snd_soc_register_platform(&pdev->dev, &sst_soc_platform_drv); + if (ret) { + pr_err("registering soc platform failed\n"); + return ret; + } + + ret = snd_soc_register_dais(&pdev->dev, + sst_platform_dai, ARRAY_SIZE(sst_platform_dai)); + if (ret) { + pr_err("registering cpu dais failed\n"); + snd_soc_unregister_platform(&pdev->dev); + } + return ret; +} + +static int sst_platform_remove(struct platform_device *pdev) +{ + + snd_soc_unregister_dais(&pdev->dev, ARRAY_SIZE(sst_platform_dai)); + snd_soc_unregister_platform(&pdev->dev); + pr_debug("sst_platform_remove sucess\n"); + return 0; +} + +static struct platform_driver sst_platform_driver = { + .driver = { + .name = "sst-platform", + .owner = THIS_MODULE, + }, + .probe = sst_platform_probe, + .remove = sst_platform_remove, +}; + +static int __init sst_soc_platform_init(void) +{ + pr_debug("sst_soc_platform_init called\n"); + return platform_driver_register(&sst_platform_driver); +} +module_init(sst_soc_platform_init); + +static void __exit sst_soc_platform_exit(void) +{ + platform_driver_unregister(&sst_platform_driver); + pr_debug("sst_soc_platform_exit sucess\n"); +} +module_exit(sst_soc_platform_exit); + +MODULE_DESCRIPTION("ASoC Intel(R) MID Platform driver"); +MODULE_AUTHOR("Vinod Koul <vinod.koul@intel.com>"); +MODULE_AUTHOR("Harsha Priya <priya.harsha@intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:sst-platform"); diff --git a/sound/soc/mid-x86/sst_platform.h b/sound/soc/mid-x86/sst_platform.h new file mode 100644 index 000000000000..df370286694f --- /dev/null +++ b/sound/soc/mid-x86/sst_platform.h @@ -0,0 +1,63 @@ +/* + * sst_platform.h - Intel MID Platform driver header file + * + * Copyright (C) 2010 Intel Corp + * Author: Vinod Koul <vinod.koul@intel.com> + * Author: Harsha Priya <priya.harsha@intel.com> + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * 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 of the License. + * + * 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., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * + */ + +#ifndef __SST_PLATFORMDRV_H__ +#define __SST_PLATFORMDRV_H__ + +#define SST_MONO 1 +#define SST_STEREO 2 +#define SST_MAX_CAP 5 + +#define SST_MIN_RATE 8000 +#define SST_MAX_RATE 48000 +#define SST_MIN_CHANNEL 1 +#define SST_MAX_CHANNEL 5 +#define SST_MAX_BUFFER (800*1024) +#define SST_MIN_BUFFER (800*1024) +#define SST_MIN_PERIOD_BYTES 32 +#define SST_MAX_PERIOD_BYTES SST_MAX_BUFFER +#define SST_MIN_PERIODS 2 +#define SST_MAX_PERIODS (1024*2) +#define SST_FIFO_SIZE 0 +#define SST_CARD_NAMES "intel_mid_card" +#define MSIC_VENDOR_ID 3 + +struct sst_runtime_stream { + int stream_status; + struct pcm_stream_info stream_info; + struct intel_sst_card_ops *sstdrv_ops; + spinlock_t status_lock; +}; + +enum sst_drv_status { + SST_PLATFORM_INIT = 1, + SST_PLATFORM_STARTED, + SST_PLATFORM_RUNNING, + SST_PLATFORM_PAUSED, + SST_PLATFORM_DROPPED, +}; + +#endif diff --git a/sound/soc/samsung/ac97.c b/sound/soc/samsung/ac97.c index 4770a9550341..f97110e72e85 100644 --- a/sound/soc/samsung/ac97.c +++ b/sound/soc/samsung/ac97.c @@ -12,24 +12,24 @@ * published by the Free Software Foundation. */ -#include <linux/init.h> -#include <linux/module.h> #include <linux/io.h> #include <linux/delay.h> #include <linux/clk.h> #include <sound/soc.h> -#include <plat/regs-ac97.h> #include <mach/dma.h> +#include <plat/regs-ac97.h> #include <plat/audio.h> #include "dma.h" -#include "ac97.h" #define AC_CMD_ADDR(x) (x << 16) #define AC_CMD_DATA(x) (x & 0xffff) +#define S3C_AC97_DAI_PCM 0 +#define S3C_AC97_DAI_MIC 1 + struct s3c_ac97_info { struct clk *ac97_clk; void __iomem *regs; diff --git a/sound/soc/samsung/ac97.h b/sound/soc/samsung/ac97.h deleted file mode 100644 index 0d0e1b511457..000000000000 --- a/sound/soc/samsung/ac97.h +++ /dev/null @@ -1,21 +0,0 @@ -/* sound/soc/samsung/ac97.h - * - * ALSA SoC Audio Layer - S3C AC97 Controller driver - * Evolved from s3c2443-ac97.h - * - * Copyright (c) 2010 Samsung Electronics Co. Ltd - * Author: Jaswinder Singh <jassi.brar@samsung.com> - * Credits: Graeme Gregory, Sean Choi - * - * 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 __S3C_AC97_H_ -#define __S3C_AC97_H_ - -#define S3C_AC97_DAI_PCM 0 -#define S3C_AC97_DAI_MIC 1 - -#endif /* __S3C_AC97_H_ */ diff --git a/sound/soc/samsung/dma.c b/sound/soc/samsung/dma.c index 21240198c5d6..9bce1df1f0d1 100644 --- a/sound/soc/samsung/dma.c +++ b/sound/soc/samsung/dma.c @@ -14,17 +14,11 @@ * option) any later version. */ -#include <linux/module.h> -#include <linux/init.h> -#include <linux/io.h> -#include <linux/platform_device.h> #include <linux/slab.h> #include <linux/dma-mapping.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> #include <sound/soc.h> +#include <sound/pcm_params.h> #include <asm/dma.h> #include <mach/hardware.h> @@ -32,6 +26,9 @@ #include "dma.h" +#define ST_RUNNING (1<<0) +#define ST_OPENED (1<<1) + static const struct snd_pcm_hardware dma_hardware = { .info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | diff --git a/sound/soc/samsung/dma.h b/sound/soc/samsung/dma.h index f8cd2b4223af..c50659269a40 100644 --- a/sound/soc/samsung/dma.h +++ b/sound/soc/samsung/dma.h @@ -12,9 +12,6 @@ #ifndef _S3C_AUDIO_H #define _S3C_AUDIO_H -#define ST_RUNNING (1<<0) -#define ST_OPENED (1<<1) - struct s3c_dma_params { struct s3c2410_dma_client *client; /* stream identifier */ int channel; /* Channel ID */ @@ -22,9 +19,4 @@ struct s3c_dma_params { int dma_size; /* Size of the DMA transfer */ }; -#define S3C24XX_DAI_I2S 0 - -/* platform data */ -extern struct snd_ac97_bus_ops s3c24xx_ac97_ops; - #endif diff --git a/sound/soc/samsung/goni_wm8994.c b/sound/soc/samsung/goni_wm8994.c index 34dd9ef1b9c0..f6b3a3ce5919 100644 --- a/sound/soc/samsung/goni_wm8994.c +++ b/sound/soc/samsung/goni_wm8994.c @@ -11,21 +11,13 @@ * */ -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/io.h> -#include <linux/platform_device.h> #include <sound/soc.h> #include <sound/jack.h> + #include <asm/mach-types.h> #include <mach/gpio.h> -#include <mach/regs-clock.h> -#include <linux/mfd/wm8994/core.h> -#include <linux/mfd/wm8994/registers.h> #include "../codecs/wm8994.h" -#include "dma.h" -#include "i2s.h" #define MACHINE_NAME 0 #define CPU_VOICE_DAI 1 diff --git a/sound/soc/samsung/h1940_uda1380.c b/sound/soc/samsung/h1940_uda1380.c index c45f7ce14d61..241f55d00660 100644 --- a/sound/soc/samsung/h1940_uda1380.c +++ b/sound/soc/samsung/h1940_uda1380.c @@ -13,25 +13,16 @@ * */ -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/platform_device.h> -#include <linux/i2c.h> #include <linux/gpio.h> #include <sound/soc.h> -#include <sound/uda1380.h> #include <sound/jack.h> #include <plat/regs-iis.h> - #include <mach/h1940-latch.h> - #include <asm/mach-types.h> -#include "dma.h" #include "s3c24xx-i2s.h" -#include "../codecs/uda1380.h" static unsigned int rates[] = { 11025, diff --git a/sound/soc/samsung/i2s.c b/sound/soc/samsung/i2s.c index d00ac3a7102c..ffa09b3b2caa 100644 --- a/sound/soc/samsung/i2s.c +++ b/sound/soc/samsung/i2s.c @@ -15,9 +15,8 @@ #include <linux/clk.h> #include <linux/io.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> #include <sound/soc.h> +#include <sound/pcm_params.h> #include <plat/audio.h> diff --git a/sound/soc/samsung/jive_wm8750.c b/sound/soc/samsung/jive_wm8750.c index 08802520e014..3b53ad54bc33 100644 --- a/sound/soc/samsung/jive_wm8750.c +++ b/sound/soc/samsung/jive_wm8750.c @@ -11,22 +11,11 @@ * published by the Free Software Foundation. */ -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/timer.h> -#include <linux/interrupt.h> -#include <linux/platform_device.h> -#include <linux/clk.h> - -#include <sound/core.h> -#include <sound/pcm.h> #include <sound/soc.h> #include <asm/mach-types.h> -#include "dma.h" #include "s3c2412-i2s.h" - #include "../codecs/wm8750.h" static const struct snd_soc_dapm_route audio_map[] = { diff --git a/sound/soc/samsung/ln2440sbc_alc650.c b/sound/soc/samsung/ln2440sbc_alc650.c index a2bb34def740..bd91c19a6c08 100644 --- a/sound/soc/samsung/ln2440sbc_alc650.c +++ b/sound/soc/samsung/ln2440sbc_alc650.c @@ -16,15 +16,8 @@ * */ -#include <linux/module.h> -#include <linux/device.h> -#include <sound/core.h> -#include <sound/pcm.h> #include <sound/soc.h> -#include "dma.h" -#include "ac97.h" - static struct snd_soc_card ln2440sbc; static struct snd_soc_dai_link ln2440sbc_dai[] = { diff --git a/sound/soc/samsung/neo1973_gta02_wm8753.c b/sound/soc/samsung/neo1973_gta02_wm8753.c index 3eec610c10f9..69e08fd1ff00 100644 --- a/sound/soc/samsung/neo1973_gta02_wm8753.c +++ b/sound/soc/samsung/neo1973_gta02_wm8753.c @@ -13,25 +13,15 @@ * option) any later version. */ -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/timer.h> -#include <linux/interrupt.h> -#include <linux/platform_device.h> #include <linux/gpio.h> -#include <sound/core.h> -#include <sound/pcm.h> + #include <sound/soc.h> #include <asm/mach-types.h> - #include <plat/regs-iis.h> - -#include <mach/regs-clock.h> -#include <asm/io.h> #include <mach/gta02.h> + #include "../codecs/wm8753.h" -#include "dma.h" #include "s3c24xx-i2s.h" static struct snd_soc_card neo1973_gta02; diff --git a/sound/soc/samsung/pcm.c b/sound/soc/samsung/pcm.c index 48d0b750406b..38aac7d57a59 100644 --- a/sound/soc/samsung/pcm.c +++ b/sound/soc/samsung/pcm.c @@ -11,20 +11,11 @@ * published by the Free Software Foundation. */ -#include <linux/init.h> -#include <linux/module.h> -#include <linux/device.h> -#include <linux/delay.h> #include <linux/clk.h> -#include <linux/kernel.h> -#include <linux/gpio.h> #include <linux/io.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/initval.h> #include <sound/soc.h> +#include <sound/pcm_params.h> #include <plat/audio.h> #include <plat/dma.h> @@ -32,6 +23,113 @@ #include "dma.h" #include "pcm.h" +/*Register Offsets */ +#define S3C_PCM_CTL 0x00 +#define S3C_PCM_CLKCTL 0x04 +#define S3C_PCM_TXFIFO 0x08 +#define S3C_PCM_RXFIFO 0x0C +#define S3C_PCM_IRQCTL 0x10 +#define S3C_PCM_IRQSTAT 0x14 +#define S3C_PCM_FIFOSTAT 0x18 +#define S3C_PCM_CLRINT 0x20 + +/* PCM_CTL Bit-Fields */ +#define S3C_PCM_CTL_TXDIPSTICK_MASK 0x3f +#define S3C_PCM_CTL_TXDIPSTICK_SHIFT 13 +#define S3C_PCM_CTL_RXDIPSTICK_MASK 0x3f +#define S3C_PCM_CTL_RXDIPSTICK_SHIFT 7 +#define S3C_PCM_CTL_TXDMA_EN (0x1 << 6) +#define S3C_PCM_CTL_RXDMA_EN (0x1 << 5) +#define S3C_PCM_CTL_TXMSB_AFTER_FSYNC (0x1 << 4) +#define S3C_PCM_CTL_RXMSB_AFTER_FSYNC (0x1 << 3) +#define S3C_PCM_CTL_TXFIFO_EN (0x1 << 2) +#define S3C_PCM_CTL_RXFIFO_EN (0x1 << 1) +#define S3C_PCM_CTL_ENABLE (0x1 << 0) + +/* PCM_CLKCTL Bit-Fields */ +#define S3C_PCM_CLKCTL_SERCLK_EN (0x1 << 19) +#define S3C_PCM_CLKCTL_SERCLKSEL_PCLK (0x1 << 18) +#define S3C_PCM_CLKCTL_SCLKDIV_MASK 0x1ff +#define S3C_PCM_CLKCTL_SYNCDIV_MASK 0x1ff +#define S3C_PCM_CLKCTL_SCLKDIV_SHIFT 9 +#define S3C_PCM_CLKCTL_SYNCDIV_SHIFT 0 + +/* PCM_TXFIFO Bit-Fields */ +#define S3C_PCM_TXFIFO_DVALID (0x1 << 16) +#define S3C_PCM_TXFIFO_DATA_MSK (0xffff << 0) + +/* PCM_RXFIFO Bit-Fields */ +#define S3C_PCM_RXFIFO_DVALID (0x1 << 16) +#define S3C_PCM_RXFIFO_DATA_MSK (0xffff << 0) + +/* PCM_IRQCTL Bit-Fields */ +#define S3C_PCM_IRQCTL_IRQEN (0x1 << 14) +#define S3C_PCM_IRQCTL_WRDEN (0x1 << 12) +#define S3C_PCM_IRQCTL_TXEMPTYEN (0x1 << 11) +#define S3C_PCM_IRQCTL_TXALMSTEMPTYEN (0x1 << 10) +#define S3C_PCM_IRQCTL_TXFULLEN (0x1 << 9) +#define S3C_PCM_IRQCTL_TXALMSTFULLEN (0x1 << 8) +#define S3C_PCM_IRQCTL_TXSTARVEN (0x1 << 7) +#define S3C_PCM_IRQCTL_TXERROVRFLEN (0x1 << 6) +#define S3C_PCM_IRQCTL_RXEMPTEN (0x1 << 5) +#define S3C_PCM_IRQCTL_RXALMSTEMPTEN (0x1 << 4) +#define S3C_PCM_IRQCTL_RXFULLEN (0x1 << 3) +#define S3C_PCM_IRQCTL_RXALMSTFULLEN (0x1 << 2) +#define S3C_PCM_IRQCTL_RXSTARVEN (0x1 << 1) +#define S3C_PCM_IRQCTL_RXERROVRFLEN (0x1 << 0) + +/* PCM_IRQSTAT Bit-Fields */ +#define S3C_PCM_IRQSTAT_IRQPND (0x1 << 13) +#define S3C_PCM_IRQSTAT_WRD_XFER (0x1 << 12) +#define S3C_PCM_IRQSTAT_TXEMPTY (0x1 << 11) +#define S3C_PCM_IRQSTAT_TXALMSTEMPTY (0x1 << 10) +#define S3C_PCM_IRQSTAT_TXFULL (0x1 << 9) +#define S3C_PCM_IRQSTAT_TXALMSTFULL (0x1 << 8) +#define S3C_PCM_IRQSTAT_TXSTARV (0x1 << 7) +#define S3C_PCM_IRQSTAT_TXERROVRFL (0x1 << 6) +#define S3C_PCM_IRQSTAT_RXEMPT (0x1 << 5) +#define S3C_PCM_IRQSTAT_RXALMSTEMPT (0x1 << 4) +#define S3C_PCM_IRQSTAT_RXFULL (0x1 << 3) +#define S3C_PCM_IRQSTAT_RXALMSTFULL (0x1 << 2) +#define S3C_PCM_IRQSTAT_RXSTARV (0x1 << 1) +#define S3C_PCM_IRQSTAT_RXERROVRFL (0x1 << 0) + +/* PCM_FIFOSTAT Bit-Fields */ +#define S3C_PCM_FIFOSTAT_TXCNT_MSK (0x3f << 14) +#define S3C_PCM_FIFOSTAT_TXFIFOEMPTY (0x1 << 13) +#define S3C_PCM_FIFOSTAT_TXFIFOALMSTEMPTY (0x1 << 12) +#define S3C_PCM_FIFOSTAT_TXFIFOFULL (0x1 << 11) +#define S3C_PCM_FIFOSTAT_TXFIFOALMSTFULL (0x1 << 10) +#define S3C_PCM_FIFOSTAT_RXCNT_MSK (0x3f << 4) +#define S3C_PCM_FIFOSTAT_RXFIFOEMPTY (0x1 << 3) +#define S3C_PCM_FIFOSTAT_RXFIFOALMSTEMPTY (0x1 << 2) +#define S3C_PCM_FIFOSTAT_RXFIFOFULL (0x1 << 1) +#define S3C_PCM_FIFOSTAT_RXFIFOALMSTFULL (0x1 << 0) + +/** + * struct s3c_pcm_info - S3C PCM Controller information + * @dev: The parent device passed to use from the probe. + * @regs: The pointer to the device register block. + * @dma_playback: DMA information for playback channel. + * @dma_capture: DMA information for capture channel. + */ +struct s3c_pcm_info { + spinlock_t lock; + struct device *dev; + void __iomem *regs; + + unsigned int sclk_per_fs; + + /* Whether to keep PCMSCLK enabled even when idle(no active xfer) */ + unsigned int idleclk; + + struct clk *pclk; + struct clk *cclk; + + struct s3c_dma_params *dma_playback; + struct s3c_dma_params *dma_capture; +}; + static struct s3c2410_dma_client s3c_pcm_dma_client_out = { .name = "PCM Stereo out" }; diff --git a/sound/soc/samsung/pcm.h b/sound/soc/samsung/pcm.h index 03393dcf852d..726baf814613 100644 --- a/sound/soc/samsung/pcm.h +++ b/sound/soc/samsung/pcm.h @@ -9,116 +9,9 @@ #ifndef __S3C_PCM_H #define __S3C_PCM_H __FILE__ -/*Register Offsets */ -#define S3C_PCM_CTL (0x00) -#define S3C_PCM_CLKCTL (0x04) -#define S3C_PCM_TXFIFO (0x08) -#define S3C_PCM_RXFIFO (0x0C) -#define S3C_PCM_IRQCTL (0x10) -#define S3C_PCM_IRQSTAT (0x14) -#define S3C_PCM_FIFOSTAT (0x18) -#define S3C_PCM_CLRINT (0x20) - -/* PCM_CTL Bit-Fields */ -#define S3C_PCM_CTL_TXDIPSTICK_MASK (0x3f) -#define S3C_PCM_CTL_TXDIPSTICK_SHIFT (13) -#define S3C_PCM_CTL_RXDIPSTICK_MASK (0x3f) -#define S3C_PCM_CTL_RXDIPSTICK_SHIFT (7) -#define S3C_PCM_CTL_TXDMA_EN (0x1<<6) -#define S3C_PCM_CTL_RXDMA_EN (0x1<<5) -#define S3C_PCM_CTL_TXMSB_AFTER_FSYNC (0x1<<4) -#define S3C_PCM_CTL_RXMSB_AFTER_FSYNC (0x1<<3) -#define S3C_PCM_CTL_TXFIFO_EN (0x1<<2) -#define S3C_PCM_CTL_RXFIFO_EN (0x1<<1) -#define S3C_PCM_CTL_ENABLE (0x1<<0) - -/* PCM_CLKCTL Bit-Fields */ -#define S3C_PCM_CLKCTL_SERCLK_EN (0x1<<19) -#define S3C_PCM_CLKCTL_SERCLKSEL_PCLK (0x1<<18) -#define S3C_PCM_CLKCTL_SCLKDIV_MASK (0x1ff) -#define S3C_PCM_CLKCTL_SYNCDIV_MASK (0x1ff) -#define S3C_PCM_CLKCTL_SCLKDIV_SHIFT (9) -#define S3C_PCM_CLKCTL_SYNCDIV_SHIFT (0) - -/* PCM_TXFIFO Bit-Fields */ -#define S3C_PCM_TXFIFO_DVALID (0x1<<16) -#define S3C_PCM_TXFIFO_DATA_MSK (0xffff<<0) - -/* PCM_RXFIFO Bit-Fields */ -#define S3C_PCM_RXFIFO_DVALID (0x1<<16) -#define S3C_PCM_RXFIFO_DATA_MSK (0xffff<<0) - -/* PCM_IRQCTL Bit-Fields */ -#define S3C_PCM_IRQCTL_IRQEN (0x1<<14) -#define S3C_PCM_IRQCTL_WRDEN (0x1<<12) -#define S3C_PCM_IRQCTL_TXEMPTYEN (0x1<<11) -#define S3C_PCM_IRQCTL_TXALMSTEMPTYEN (0x1<<10) -#define S3C_PCM_IRQCTL_TXFULLEN (0x1<<9) -#define S3C_PCM_IRQCTL_TXALMSTFULLEN (0x1<<8) -#define S3C_PCM_IRQCTL_TXSTARVEN (0x1<<7) -#define S3C_PCM_IRQCTL_TXERROVRFLEN (0x1<<6) -#define S3C_PCM_IRQCTL_RXEMPTEN (0x1<<5) -#define S3C_PCM_IRQCTL_RXALMSTEMPTEN (0x1<<4) -#define S3C_PCM_IRQCTL_RXFULLEN (0x1<<3) -#define S3C_PCM_IRQCTL_RXALMSTFULLEN (0x1<<2) -#define S3C_PCM_IRQCTL_RXSTARVEN (0x1<<1) -#define S3C_PCM_IRQCTL_RXERROVRFLEN (0x1<<0) - -/* PCM_IRQSTAT Bit-Fields */ -#define S3C_PCM_IRQSTAT_IRQPND (0x1<<13) -#define S3C_PCM_IRQSTAT_WRD_XFER (0x1<<12) -#define S3C_PCM_IRQSTAT_TXEMPTY (0x1<<11) -#define S3C_PCM_IRQSTAT_TXALMSTEMPTY (0x1<<10) -#define S3C_PCM_IRQSTAT_TXFULL (0x1<<9) -#define S3C_PCM_IRQSTAT_TXALMSTFULL (0x1<<8) -#define S3C_PCM_IRQSTAT_TXSTARV (0x1<<7) -#define S3C_PCM_IRQSTAT_TXERROVRFL (0x1<<6) -#define S3C_PCM_IRQSTAT_RXEMPT (0x1<<5) -#define S3C_PCM_IRQSTAT_RXALMSTEMPT (0x1<<4) -#define S3C_PCM_IRQSTAT_RXFULL (0x1<<3) -#define S3C_PCM_IRQSTAT_RXALMSTFULL (0x1<<2) -#define S3C_PCM_IRQSTAT_RXSTARV (0x1<<1) -#define S3C_PCM_IRQSTAT_RXERROVRFL (0x1<<0) - -/* PCM_FIFOSTAT Bit-Fields */ -#define S3C_PCM_FIFOSTAT_TXCNT_MSK (0x3f<<14) -#define S3C_PCM_FIFOSTAT_TXFIFOEMPTY (0x1<<13) -#define S3C_PCM_FIFOSTAT_TXFIFOALMSTEMPTY (0x1<<12) -#define S3C_PCM_FIFOSTAT_TXFIFOFULL (0x1<<11) -#define S3C_PCM_FIFOSTAT_TXFIFOALMSTFULL (0x1<<10) -#define S3C_PCM_FIFOSTAT_RXCNT_MSK (0x3f<<4) -#define S3C_PCM_FIFOSTAT_RXFIFOEMPTY (0x1<<3) -#define S3C_PCM_FIFOSTAT_RXFIFOALMSTEMPTY (0x1<<2) -#define S3C_PCM_FIFOSTAT_RXFIFOFULL (0x1<<1) -#define S3C_PCM_FIFOSTAT_RXFIFOALMSTFULL (0x1<<0) - #define S3C_PCM_CLKSRC_PCLK 0 #define S3C_PCM_CLKSRC_MUX 1 #define S3C_PCM_SCLK_PER_FS 0 -/** - * struct s3c_pcm_info - S3C PCM Controller information - * @dev: The parent device passed to use from the probe. - * @regs: The pointer to the device register block. - * @dma_playback: DMA information for playback channel. - * @dma_capture: DMA information for capture channel. - */ -struct s3c_pcm_info { - spinlock_t lock; - struct device *dev; - void __iomem *regs; - - unsigned int sclk_per_fs; - - /* Whether to keep PCMSCLK enabled even when idle(no active xfer) */ - unsigned int idleclk; - - struct clk *pclk; - struct clk *cclk; - - struct s3c_dma_params *dma_playback; - struct s3c_dma_params *dma_capture; -}; - #endif /* __S3C_PCM_H */ diff --git a/sound/soc/samsung/rx1950_uda1380.c b/sound/soc/samsung/rx1950_uda1380.c index f40027445dda..1e574a5d440d 100644 --- a/sound/soc/samsung/rx1950_uda1380.c +++ b/sound/soc/samsung/rx1950_uda1380.c @@ -17,26 +17,15 @@ * */ -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/platform_device.h> -#include <linux/i2c.h> #include <linux/gpio.h> -#include <linux/clk.h> #include <sound/soc.h> -#include <sound/uda1380.h> #include <sound/jack.h> #include <plat/regs-iis.h> - -#include <mach/regs-clock.h> - #include <asm/mach-types.h> -#include "dma.h" #include "s3c24xx-i2s.h" -#include "../codecs/uda1380.h" static int rx1950_uda1380_init(struct snd_soc_pcm_runtime *rtd); static int rx1950_startup(struct snd_pcm_substream *substream); diff --git a/sound/soc/samsung/s3c-i2s-v2.c b/sound/soc/samsung/s3c-i2s-v2.c index 094f36e41e83..52074a2b0696 100644 --- a/sound/soc/samsung/s3c-i2s-v2.c +++ b/sound/soc/samsung/s3c-i2s-v2.c @@ -20,9 +20,8 @@ #include <linux/clk.h> #include <linux/io.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> #include <sound/soc.h> +#include <sound/pcm_params.h> #include <mach/dma.h> diff --git a/sound/soc/samsung/s3c2412-i2s.c b/sound/soc/samsung/s3c2412-i2s.c index 7ea837867124..841ab14c1100 100644 --- a/sound/soc/samsung/s3c2412-i2s.c +++ b/sound/soc/samsung/s3c2412-i2s.c @@ -16,21 +16,13 @@ * option) any later version. */ -#include <linux/init.h> -#include <linux/module.h> -#include <linux/device.h> #include <linux/delay.h> #include <linux/gpio.h> #include <linux/clk.h> -#include <linux/kernel.h> #include <linux/io.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/initval.h> #include <sound/soc.h> -#include <mach/hardware.h> +#include <sound/pcm_params.h> #include <mach/regs-gpio.h> #include <mach/dma.h> @@ -39,8 +31,6 @@ #include "regs-i2s-v2.h" #include "s3c2412-i2s.h" -#define S3C2412_I2S_DEBUG 0 - static struct s3c2410_dma_client s3c2412_dma_client_out = { .name = "I2S PCM Stereo out" }; diff --git a/sound/soc/samsung/s3c24xx-i2s.c b/sound/soc/samsung/s3c24xx-i2s.c index 13e41ed8e22b..63d8849d80bd 100644 --- a/sound/soc/samsung/s3c24xx-i2s.c +++ b/sound/soc/samsung/s3c24xx-i2s.c @@ -14,28 +14,16 @@ * option) any later version. */ -#include <linux/init.h> -#include <linux/module.h> -#include <linux/device.h> #include <linux/delay.h> #include <linux/clk.h> -#include <linux/jiffies.h> #include <linux/io.h> #include <linux/gpio.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> -#include <sound/initval.h> #include <sound/soc.h> +#include <sound/pcm_params.h> -#include <mach/hardware.h> #include <mach/regs-gpio.h> -#include <mach/regs-clock.h> - -#include <asm/dma.h> #include <mach/dma.h> - #include <plat/regs-iis.h> #include "dma.h" diff --git a/sound/soc/samsung/s3c24xx_simtec.c b/sound/soc/samsung/s3c24xx_simtec.c index a434032d1832..349566f0686b 100644 --- a/sound/soc/samsung/s3c24xx_simtec.c +++ b/sound/soc/samsung/s3c24xx_simtec.c @@ -7,20 +7,13 @@ * published by the Free Software Foundation. */ -#include <linux/module.h> -#include <linux/moduleparam.h> -#include <linux/platform_device.h> #include <linux/gpio.h> #include <linux/clk.h> -#include <linux/i2c.h> -#include <sound/core.h> -#include <sound/pcm.h> #include <sound/soc.h> #include <plat/audio-simtec.h> -#include "dma.h" #include "s3c24xx-i2s.h" #include "s3c24xx_simtec.h" diff --git a/sound/soc/samsung/s3c24xx_simtec_hermes.c b/sound/soc/samsung/s3c24xx_simtec_hermes.c index bb4292e3596c..d7b3e6e9d783 100644 --- a/sound/soc/samsung/s3c24xx_simtec_hermes.c +++ b/sound/soc/samsung/s3c24xx_simtec_hermes.c @@ -7,18 +7,8 @@ * published by the Free Software Foundation. */ -#include <linux/module.h> -#include <linux/clk.h> -#include <linux/platform_device.h> - -#include <sound/core.h> -#include <sound/pcm.h> #include <sound/soc.h> -#include <plat/audio-simtec.h> - -#include "dma.h" -#include "s3c24xx-i2s.h" #include "s3c24xx_simtec.h" static const struct snd_soc_dapm_widget dapm_widgets[] = { diff --git a/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c b/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c index fbba4e367729..ff6168f5f2e0 100644 --- a/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c +++ b/sound/soc/samsung/s3c24xx_simtec_tlv320aic23.c @@ -7,22 +7,10 @@ * published by the Free Software Foundation. */ -#include <linux/module.h> -#include <linux/clk.h> -#include <linux/platform_device.h> - -#include <sound/core.h> -#include <sound/pcm.h> #include <sound/soc.h> -#include <plat/audio-simtec.h> - -#include "dma.h" -#include "s3c24xx-i2s.h" #include "s3c24xx_simtec.h" -#include "../codecs/tlv320aic23.h" - /* supported machines: * * Machine Connections AMP diff --git a/sound/soc/samsung/s3c24xx_uda134x.c b/sound/soc/samsung/s3c24xx_uda134x.c index cdc8ecbcb8ef..ce749a10ec07 100644 --- a/sound/soc/samsung/s3c24xx_uda134x.c +++ b/sound/soc/samsung/s3c24xx_uda134x.c @@ -11,22 +11,15 @@ * published by the Free Software Foundation. */ -#include <linux/module.h> #include <linux/clk.h> -#include <linux/mutex.h> #include <linux/gpio.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> + #include <sound/soc.h> #include <sound/s3c24xx_uda134x.h> -#include <sound/uda134x.h> #include <plat/regs-iis.h> -#include "dma.h" #include "s3c24xx-i2s.h" -#include "../codecs/uda134x.h" - /* #define ENFORCE_RATES 1 */ /* diff --git a/sound/soc/samsung/smartq_wm8987.c b/sound/soc/samsung/smartq_wm8987.c index 61e2b5219d42..0a2c4f223038 100644 --- a/sound/soc/samsung/smartq_wm8987.c +++ b/sound/soc/samsung/smartq_wm8987.c @@ -13,20 +13,14 @@ * */ -#include <linux/module.h> -#include <linux/platform_device.h> #include <linux/gpio.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> #include <sound/soc.h> #include <sound/jack.h> #include <asm/mach-types.h> -#include "dma.h" #include "i2s.h" - #include "../codecs/wm8750.h" /* diff --git a/sound/soc/samsung/smdk2443_wm9710.c b/sound/soc/samsung/smdk2443_wm9710.c index 3be7e7e92d6e..3a0dbfc793f0 100644 --- a/sound/soc/samsung/smdk2443_wm9710.c +++ b/sound/soc/samsung/smdk2443_wm9710.c @@ -12,15 +12,8 @@ * */ -#include <linux/module.h> -#include <linux/device.h> -#include <sound/core.h> -#include <sound/pcm.h> #include <sound/soc.h> -#include "dma.h" -#include "ac97.h" - static struct snd_soc_card smdk2443; static struct snd_soc_dai_link smdk2443_dai[] = { diff --git a/sound/soc/samsung/smdk_spdif.c b/sound/soc/samsung/smdk_spdif.c index b5c3fad01bb8..e8ac961c6ba1 100644 --- a/sound/soc/samsung/smdk_spdif.c +++ b/sound/soc/samsung/smdk_spdif.c @@ -10,15 +10,10 @@ * */ -#include <linux/module.h> -#include <linux/device.h> #include <linux/clk.h> -#include <plat/devs.h> - #include <sound/soc.h> -#include "dma.h" #include "spdif.h" /* Audio clock settings are belonged to board specific part. Every diff --git a/sound/soc/samsung/smdk_wm8580.c b/sound/soc/samsung/smdk_wm8580.c index b2cff1a44aed..8aacf23d6f3a 100644 --- a/sound/soc/samsung/smdk_wm8580.c +++ b/sound/soc/samsung/smdk_wm8580.c @@ -10,17 +10,12 @@ * option) any later version. */ -#include <linux/platform_device.h> -#include <linux/clk.h> -#include <sound/core.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> #include <sound/soc.h> +#include <sound/pcm_params.h> #include <asm/mach-types.h> #include "../codecs/wm8580.h" -#include "dma.h" #include "i2s.h" /* diff --git a/sound/soc/samsung/smdk_wm9713.c b/sound/soc/samsung/smdk_wm9713.c index ae5fed6f772f..fffe3c1dd1bd 100644 --- a/sound/soc/samsung/smdk_wm9713.c +++ b/sound/soc/samsung/smdk_wm9713.c @@ -11,13 +11,8 @@ * */ -#include <linux/module.h> -#include <linux/device.h> #include <sound/soc.h> -#include "dma.h" -#include "ac97.h" - static struct snd_soc_card smdk; /* diff --git a/sound/soc/samsung/spdif.c b/sound/soc/samsung/spdif.c index f0816404ea3e..28c491dacf7a 100644 --- a/sound/soc/samsung/spdif.c +++ b/sound/soc/samsung/spdif.c @@ -13,9 +13,8 @@ #include <linux/clk.h> #include <linux/io.h> -#include <sound/pcm.h> -#include <sound/pcm_params.h> #include <sound/soc.h> +#include <sound/pcm_params.h> #include <plat/audio.h> #include <mach/dma.h> diff --git a/sound/soc/sh/fsi-ak4642.c b/sound/soc/sh/fsi-ak4642.c index a14820ac9665..56cd34223100 100644 --- a/sound/soc/sh/fsi-ak4642.c +++ b/sound/soc/sh/fsi-ak4642.c @@ -18,6 +18,7 @@ struct fsi_ak4642_data { const char *cpu_dai; const char *codec; const char *platform; + int id; }; static int fsi_ak4642_dai_init(struct snd_soc_pcm_runtime *rtd) @@ -60,7 +61,7 @@ static int fsi_ak4642_probe(struct platform_device *pdev) pdata = (struct fsi_ak4642_data *)id_entry->driver_data; - fsi_snd_device = platform_device_alloc("soc-audio", FSI_PORT_A); + fsi_snd_device = platform_device_alloc("soc-audio", pdata->id); if (!fsi_snd_device) goto out; @@ -93,6 +94,7 @@ static struct fsi_ak4642_data fsi_a_ak4642 = { .cpu_dai = "fsia-dai", .codec = "ak4642-codec.0-0012", .platform = "sh_fsi.0", + .id = FSI_PORT_A, }; static struct fsi_ak4642_data fsi_b_ak4642 = { @@ -101,6 +103,7 @@ static struct fsi_ak4642_data fsi_b_ak4642 = { .cpu_dai = "fsib-dai", .codec = "ak4642-codec.0-0012", .platform = "sh_fsi.0", + .id = FSI_PORT_B, }; static struct fsi_ak4642_data fsi_a_ak4643 = { @@ -109,6 +112,7 @@ static struct fsi_ak4642_data fsi_a_ak4643 = { .cpu_dai = "fsia-dai", .codec = "ak4642-codec.0-0013", .platform = "sh_fsi.0", + .id = FSI_PORT_A, }; static struct fsi_ak4642_data fsi_b_ak4643 = { @@ -117,6 +121,7 @@ static struct fsi_ak4642_data fsi_b_ak4643 = { .cpu_dai = "fsib-dai", .codec = "ak4642-codec.0-0013", .platform = "sh_fsi.0", + .id = FSI_PORT_B, }; static struct fsi_ak4642_data fsi2_a_ak4642 = { @@ -125,6 +130,7 @@ static struct fsi_ak4642_data fsi2_a_ak4642 = { .cpu_dai = "fsia-dai", .codec = "ak4642-codec.0-0012", .platform = "sh_fsi2", + .id = FSI_PORT_A, }; static struct fsi_ak4642_data fsi2_b_ak4642 = { @@ -133,6 +139,7 @@ static struct fsi_ak4642_data fsi2_b_ak4642 = { .cpu_dai = "fsib-dai", .codec = "ak4642-codec.0-0012", .platform = "sh_fsi2", + .id = FSI_PORT_B, }; static struct fsi_ak4642_data fsi2_a_ak4643 = { @@ -141,6 +148,7 @@ static struct fsi_ak4642_data fsi2_a_ak4643 = { .cpu_dai = "fsia-dai", .codec = "ak4642-codec.0-0013", .platform = "sh_fsi2", + .id = FSI_PORT_A, }; static struct fsi_ak4642_data fsi2_b_ak4643 = { @@ -149,6 +157,7 @@ static struct fsi_ak4642_data fsi2_b_ak4643 = { .cpu_dai = "fsib-dai", .codec = "ak4642-codec.0-0013", .platform = "sh_fsi2", + .id = FSI_PORT_B, }; static struct platform_device_id fsi_id_table[] = { diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c index 8c2a21a978ac..d97a59f6a249 100644 --- a/sound/soc/soc-cache.c +++ b/sound/soc/soc-cache.c @@ -761,6 +761,49 @@ int snd_soc_codec_set_cache_io(struct snd_soc_codec *codec, } EXPORT_SYMBOL_GPL(snd_soc_codec_set_cache_io); +static bool snd_soc_set_cache_val(void *base, unsigned int idx, + unsigned int val, unsigned int word_size) +{ + switch (word_size) { + case 1: { + u8 *cache = base; + if (cache[idx] == val) + return true; + cache[idx] = val; + break; + } + case 2: { + u16 *cache = base; + if (cache[idx] == val) + return true; + cache[idx] = val; + break; + } + default: + BUG(); + } + return false; +} + +static unsigned int snd_soc_get_cache_val(const void *base, unsigned int idx, + unsigned int word_size) +{ + switch (word_size) { + case 1: { + const u8 *cache = base; + return cache[idx]; + } + case 2: { + const u16 *cache = base; + return cache[idx]; + } + default: + BUG(); + } + /* unreachable */ + return -1; +} + struct snd_soc_rbtree_node { struct rb_node node; unsigned int reg; @@ -924,7 +967,12 @@ static int snd_soc_rbtree_cache_exit(struct snd_soc_codec *codec) static int snd_soc_rbtree_cache_init(struct snd_soc_codec *codec) { + struct snd_soc_rbtree_node *rbtree_node; struct snd_soc_rbtree_ctx *rbtree_ctx; + unsigned int val; + unsigned int word_size; + int i; + int ret; codec->reg_cache = kmalloc(sizeof *rbtree_ctx, GFP_KERNEL); if (!codec->reg_cache) @@ -936,53 +984,25 @@ static int snd_soc_rbtree_cache_init(struct snd_soc_codec *codec) if (!codec->reg_def_copy) return 0; -/* - * populate the rbtree with the initialized registers. All other - * registers will be inserted into the tree when they are first written. - * - * The reasoning behind this, is that we need to step through and - * dereference the cache in u8/u16 increments without sacrificing - * portability. This could also be done using memcpy() but that would - * be slightly more cryptic. - */ -#define snd_soc_rbtree_populate(cache) \ -({ \ - int ret, i; \ - struct snd_soc_rbtree_node *rbtree_node; \ - \ - ret = 0; \ - cache = codec->reg_def_copy; \ - for (i = 0; i < codec->driver->reg_cache_size; ++i) { \ - if (!cache[i]) \ - continue; \ - rbtree_node = kzalloc(sizeof *rbtree_node, GFP_KERNEL); \ - if (!rbtree_node) { \ - ret = -ENOMEM; \ - snd_soc_cache_exit(codec); \ - break; \ - } \ - rbtree_node->reg = i; \ - rbtree_node->value = cache[i]; \ - rbtree_node->defval = cache[i]; \ - snd_soc_rbtree_insert(&rbtree_ctx->root, \ - rbtree_node); \ - } \ - ret; \ -}) - - switch (codec->driver->reg_word_size) { - case 1: { - const u8 *cache; - - return snd_soc_rbtree_populate(cache); - } - case 2: { - const u16 *cache; - - return snd_soc_rbtree_populate(cache); - } - default: - BUG(); + /* + * populate the rbtree with the initialized registers. All other + * registers will be inserted when they are first modified. + */ + word_size = codec->driver->reg_word_size; + for (i = 0; i < codec->driver->reg_cache_size; ++i) { + val = snd_soc_get_cache_val(codec->reg_def_copy, i, word_size); + if (!val) + continue; + rbtree_node = kzalloc(sizeof *rbtree_node, GFP_KERNEL); + if (!rbtree_node) { + ret = -ENOMEM; + snd_soc_cache_exit(codec); + break; + } + rbtree_node->reg = i; + rbtree_node->value = val; + rbtree_node->defval = val; + snd_soc_rbtree_insert(&rbtree_ctx->root, rbtree_node); } return 0; @@ -1080,34 +1100,28 @@ static inline int snd_soc_lzo_get_blkindex(struct snd_soc_codec *codec, unsigned int reg) { const struct snd_soc_codec_driver *codec_drv; - size_t reg_size; codec_drv = codec->driver; - reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size; return (reg * codec_drv->reg_word_size) / - DIV_ROUND_UP(reg_size, snd_soc_lzo_block_count()); + DIV_ROUND_UP(codec->reg_size, snd_soc_lzo_block_count()); } static inline int snd_soc_lzo_get_blkpos(struct snd_soc_codec *codec, unsigned int reg) { const struct snd_soc_codec_driver *codec_drv; - size_t reg_size; codec_drv = codec->driver; - reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size; - return reg % (DIV_ROUND_UP(reg_size, snd_soc_lzo_block_count()) / + return reg % (DIV_ROUND_UP(codec->reg_size, snd_soc_lzo_block_count()) / codec_drv->reg_word_size); } static inline int snd_soc_lzo_get_blksize(struct snd_soc_codec *codec) { const struct snd_soc_codec_driver *codec_drv; - size_t reg_size; codec_drv = codec->driver; - reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size; - return DIV_ROUND_UP(reg_size, snd_soc_lzo_block_count()); + return DIV_ROUND_UP(codec->reg_size, snd_soc_lzo_block_count()); } static int snd_soc_lzo_cache_sync(struct snd_soc_codec *codec) @@ -1165,29 +1179,10 @@ static int snd_soc_lzo_cache_write(struct snd_soc_codec *codec, } /* write the new value to the cache */ - switch (codec->driver->reg_word_size) { - case 1: { - u8 *cache; - cache = lzo_block->dst; - if (cache[blkpos] == value) { - kfree(lzo_block->dst); - goto out; - } - cache[blkpos] = value; - } - break; - case 2: { - u16 *cache; - cache = lzo_block->dst; - if (cache[blkpos] == value) { - kfree(lzo_block->dst); - goto out; - } - cache[blkpos] = value; - } - break; - default: - BUG(); + if (snd_soc_set_cache_val(lzo_block->dst, blkpos, value, + codec->driver->reg_word_size)) { + kfree(lzo_block->dst); + goto out; } /* prepare the source to be the decompressed block */ @@ -1241,25 +1236,10 @@ static int snd_soc_lzo_cache_read(struct snd_soc_codec *codec, /* decompress the block */ ret = snd_soc_lzo_decompress_cache_block(codec, lzo_block); - if (ret >= 0) { + if (ret >= 0) /* fetch the value from the cache */ - switch (codec->driver->reg_word_size) { - case 1: { - u8 *cache; - cache = lzo_block->dst; - *value = cache[blkpos]; - } - break; - case 2: { - u16 *cache; - cache = lzo_block->dst; - *value = cache[blkpos]; - } - break; - default: - BUG(); - } - } + *value = snd_soc_get_cache_val(lzo_block->dst, blkpos, + codec->driver->reg_word_size); kfree(lzo_block->dst); /* restore the pointer and length of the compressed block */ @@ -1301,7 +1281,7 @@ static int snd_soc_lzo_cache_exit(struct snd_soc_codec *codec) static int snd_soc_lzo_cache_init(struct snd_soc_codec *codec) { struct snd_soc_lzo_ctx **lzo_blocks; - size_t reg_size, bmp_size; + size_t bmp_size; const struct snd_soc_codec_driver *codec_drv; int ret, tofree, i, blksize, blkcount; const char *p, *end; @@ -1309,7 +1289,6 @@ static int snd_soc_lzo_cache_init(struct snd_soc_codec *codec) ret = 0; codec_drv = codec->driver; - reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size; /* * If we have not been given a default register cache @@ -1321,8 +1300,7 @@ static int snd_soc_lzo_cache_init(struct snd_soc_codec *codec) tofree = 1; if (!codec->reg_def_copy) { - codec->reg_def_copy = kzalloc(reg_size, - GFP_KERNEL); + codec->reg_def_copy = kzalloc(codec->reg_size, GFP_KERNEL); if (!codec->reg_def_copy) return -ENOMEM; } @@ -1370,7 +1348,7 @@ static int snd_soc_lzo_cache_init(struct snd_soc_codec *codec) blksize = snd_soc_lzo_get_blksize(codec); p = codec->reg_def_copy; - end = codec->reg_def_copy + reg_size; + end = codec->reg_def_copy + codec->reg_size; /* compress the register map and fill the lzo blocks */ for (i = 0; i < blkcount; ++i, p += blksize) { lzo_blocks[i]->src = p; @@ -1414,28 +1392,10 @@ static int snd_soc_flat_cache_sync(struct snd_soc_codec *codec) ret = snd_soc_cache_read(codec, i, &val); if (ret) return ret; - if (codec_drv->reg_cache_default) { - switch (codec_drv->reg_word_size) { - case 1: { - const u8 *cache; - - cache = codec_drv->reg_cache_default; - if (cache[i] == val) - continue; - } - break; - case 2: { - const u16 *cache; - - cache = codec_drv->reg_cache_default; - if (cache[i] == val) - continue; - } - break; - default: - BUG(); - } - } + if (codec->reg_def_copy) + if (snd_soc_get_cache_val(codec->reg_def_copy, + i, codec_drv->reg_word_size) == val) + continue; ret = snd_soc_write(codec, i, val); if (ret) return ret; @@ -1448,50 +1408,16 @@ static int snd_soc_flat_cache_sync(struct snd_soc_codec *codec) static int snd_soc_flat_cache_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { - switch (codec->driver->reg_word_size) { - case 1: { - u8 *cache; - - cache = codec->reg_cache; - cache[reg] = value; - } - break; - case 2: { - u16 *cache; - - cache = codec->reg_cache; - cache[reg] = value; - } - break; - default: - BUG(); - } - + snd_soc_set_cache_val(codec->reg_cache, reg, value, + codec->driver->reg_word_size); return 0; } static int snd_soc_flat_cache_read(struct snd_soc_codec *codec, unsigned int reg, unsigned int *value) { - switch (codec->driver->reg_word_size) { - case 1: { - u8 *cache; - - cache = codec->reg_cache; - *value = cache[reg]; - } - break; - case 2: { - u16 *cache; - - cache = codec->reg_cache; - *value = cache[reg]; - } - break; - default: - BUG(); - } - + *value = snd_soc_get_cache_val(codec->reg_cache, reg, + codec->driver->reg_word_size); return 0; } @@ -1507,24 +1433,14 @@ static int snd_soc_flat_cache_exit(struct snd_soc_codec *codec) static int snd_soc_flat_cache_init(struct snd_soc_codec *codec) { const struct snd_soc_codec_driver *codec_drv; - size_t reg_size; codec_drv = codec->driver; - reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size; - - /* - * for flat compression, we don't need to keep a copy of the - * original defaults register cache as it will definitely not - * be marked as __devinitconst - */ - kfree(codec->reg_def_copy); - codec->reg_def_copy = NULL; - if (codec_drv->reg_cache_default) - codec->reg_cache = kmemdup(codec_drv->reg_cache_default, - reg_size, GFP_KERNEL); + if (codec->reg_def_copy) + codec->reg_cache = kmemdup(codec->reg_def_copy, + codec->reg_size, GFP_KERNEL); else - codec->reg_cache = kzalloc(reg_size, GFP_KERNEL); + codec->reg_cache = kzalloc(codec->reg_size, GFP_KERNEL); if (!codec->reg_cache) return -ENOMEM; @@ -1687,3 +1603,52 @@ int snd_soc_cache_sync(struct snd_soc_codec *codec) return -EINVAL; } EXPORT_SYMBOL_GPL(snd_soc_cache_sync); + +static int snd_soc_get_reg_access_index(struct snd_soc_codec *codec, + unsigned int reg) +{ + const struct snd_soc_codec_driver *codec_drv; + unsigned int min, max, index; + + codec_drv = codec->driver; + min = 0; + max = codec_drv->reg_access_size - 1; + do { + index = (min + max) / 2; + if (codec_drv->reg_access_default[index].reg == reg) + return index; + if (codec_drv->reg_access_default[index].reg < reg) + min = index + 1; + else + max = index; + } while (min <= max); + return -1; +} + +int snd_soc_default_volatile_register(struct snd_soc_codec *codec, + unsigned int reg) +{ + int index; + + if (reg >= codec->driver->reg_cache_size) + return 1; + index = snd_soc_get_reg_access_index(codec, reg); + if (index < 0) + return 0; + return codec->driver->reg_access_default[index].vol; +} +EXPORT_SYMBOL_GPL(snd_soc_default_volatile_register); + +int snd_soc_default_readable_register(struct snd_soc_codec *codec, + unsigned int reg) +{ + int index; + + if (reg >= codec->driver->reg_cache_size) + return 1; + index = snd_soc_get_reg_access_index(codec, reg); + if (index < 0) + return 0; + return codec->driver->reg_access_default[index].read; +} +EXPORT_SYMBOL_GPL(snd_soc_default_readable_register); diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index bac7291b6ff6..b0e7689159c1 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -48,7 +48,8 @@ static DEFINE_MUTEX(pcm_mutex); static DECLARE_WAIT_QUEUE_HEAD(soc_pm_waitq); #ifdef CONFIG_DEBUG_FS -static struct dentry *debugfs_root; +struct dentry *snd_soc_debugfs_root; +EXPORT_SYMBOL_GPL(snd_soc_debugfs_root); #endif static DEFINE_MUTEX(client_mutex); @@ -57,8 +58,6 @@ static LIST_HEAD(dai_list); static LIST_HEAD(platform_list); static LIST_HEAD(codec_list); -static int snd_soc_register_card(struct snd_soc_card *card); -static int snd_soc_unregister_card(struct snd_soc_card *card); static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num); /* @@ -83,7 +82,7 @@ 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->driver->reg_cache_size; i += step) { - if (codec->driver->readable_register && !codec->driver->readable_register(i)) + if (codec->readable_register && !codec->readable_register(codec, i)) continue; count += sprintf(buf + count, "%2x: ", i); @@ -209,6 +208,10 @@ static ssize_t codec_reg_write_file(struct file *file, start++; if (strict_strtoul(start, 16, &value)) return -EINVAL; + + /* Userspace has been fiddling around behind the kernel's back */ + add_taint(TAINT_USER); + snd_soc_write(codec, reg, value); return buf_size; } @@ -356,7 +359,7 @@ static const struct file_operations platform_list_fops = { static void soc_init_card_debugfs(struct snd_soc_card *card) { card->debugfs_card_root = debugfs_create_dir(card->name, - debugfs_root); + snd_soc_debugfs_root); if (!card->debugfs_card_root) { dev_warn(card->dev, "ASoC: Failed to create codec debugfs directory\n"); @@ -1467,7 +1470,6 @@ static int soc_post_component_init(struct snd_soc_card *card, /* Make sure all DAPM widgets are instantiated */ snd_soc_dapm_new_widgets(&codec->dapm); - snd_soc_dapm_sync(&codec->dapm); /* register the rtd device */ rtd->codec = codec; @@ -1743,6 +1745,8 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) list_for_each_entry(codec, &codec_list, list) { if (codec->cache_init) continue; + /* by default we don't override the compress_type */ + compress_type = 0; /* check to see if we need to override the compress_type */ for (i = 0; i < card->num_configs; ++i) { codec_conf = &card->codec_conf[i]; @@ -1753,18 +1757,6 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) break; } } - if (i == card->num_configs) { - /* no need to override the compress_type so - * go ahead and do the standard thing */ - ret = snd_soc_init_codec_cache(codec, 0); - if (ret < 0) { - mutex_unlock(&card->mutex); - return; - } - continue; - } - /* override the compress_type with the one supplied in - * the machine driver */ ret = snd_soc_init_codec_cache(codec, compress_type); if (ret < 0) { mutex_unlock(&card->mutex); @@ -1875,16 +1867,16 @@ static int soc_probe(struct platform_device *pdev) struct snd_soc_card *card = platform_get_drvdata(pdev); int ret = 0; + /* + * no card, so machine driver should be registering card + * we should not be here in that case so ret error + */ + if (!card) + return -EINVAL; + /* Bodge while we unpick instantiation */ card->dev = &pdev->dev; - INIT_LIST_HEAD(&card->dai_dev_list); - INIT_LIST_HEAD(&card->codec_dev_list); - INIT_LIST_HEAD(&card->platform_dev_list); - INIT_LIST_HEAD(&card->widgets); - INIT_LIST_HEAD(&card->paths); - INIT_LIST_HEAD(&card->dapm_list); - - soc_init_card_debugfs(card); + snd_soc_initialize_card_lists(card); ret = snd_soc_register_card(card); if (ret != 0) { @@ -1895,37 +1887,42 @@ static int soc_probe(struct platform_device *pdev) return 0; } -/* removes a socdev */ -static int soc_remove(struct platform_device *pdev) +static int soc_cleanup_card_resources(struct snd_soc_card *card) { - struct snd_soc_card *card = platform_get_drvdata(pdev); + struct platform_device *pdev = to_platform_device(card->dev); int i; - if (card->instantiated) { + /* make sure any delayed work runs */ + for (i = 0; i < card->num_rtd; i++) { + struct snd_soc_pcm_runtime *rtd = &card->rtd[i]; + flush_delayed_work_sync(&rtd->delayed_work); + } - /* make sure any delayed work runs */ - for (i = 0; i < card->num_rtd; i++) { - struct snd_soc_pcm_runtime *rtd = &card->rtd[i]; - flush_delayed_work_sync(&rtd->delayed_work); - } + /* remove auxiliary devices */ + for (i = 0; i < card->num_aux_devs; i++) + soc_remove_aux_dev(card, i); + + /* remove and free each DAI */ + for (i = 0; i < card->num_rtd; i++) + soc_remove_dai_link(card, i); + + soc_cleanup_card_debugfs(card); - /* remove auxiliary devices */ - for (i = 0; i < card->num_aux_devs; i++) - soc_remove_aux_dev(card, i); + /* remove the card */ + if (card->remove) + card->remove(pdev); - /* remove and free each DAI */ - for (i = 0; i < card->num_rtd; i++) - soc_remove_dai_link(card, i); + kfree(card->rtd); + snd_card_free(card->snd_card); + return 0; - soc_cleanup_card_debugfs(card); +} - /* remove the card */ - if (card->remove) - card->remove(pdev); +/* removes a socdev */ +static int soc_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); - kfree(card->rtd); - snd_card_free(card->snd_card); - } snd_soc_unregister_card(card); return 0; } @@ -2034,8 +2031,8 @@ static int soc_new_pcm(struct snd_soc_pcm_runtime *rtd, int num) */ int snd_soc_codec_volatile_register(struct snd_soc_codec *codec, int reg) { - if (codec->driver->volatile_register) - return codec->driver->volatile_register(reg); + if (codec->volatile_register) + return codec->volatile_register(codec, reg); else return 0; } @@ -2132,19 +2129,27 @@ EXPORT_SYMBOL_GPL(snd_soc_write); * * Writes new register value. * - * Returns 1 for change else 0. + * Returns 1 for change, 0 for no change, or negative error code. */ int snd_soc_update_bits(struct snd_soc_codec *codec, unsigned short reg, unsigned int mask, unsigned int value) { int change; unsigned int old, new; + int ret; - old = snd_soc_read(codec, reg); + ret = snd_soc_read(codec, reg); + if (ret < 0) + return ret; + + old = ret; new = (old & ~mask) | value; change = old != new; - if (change) - snd_soc_write(codec, reg, new); + if (change) { + ret = snd_soc_write(codec, reg, new); + if (ret < 0) + return ret; + } return change; } @@ -3104,17 +3109,16 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_digital_mute); * * @card: Card to register * - * Note that currently this is an internal only function: it will be - * exposed to machine drivers after further backporting of ASoC v2 - * registration APIs. */ -static int snd_soc_register_card(struct snd_soc_card *card) +int snd_soc_register_card(struct snd_soc_card *card) { int i; if (!card->name || !card->dev) return -EINVAL; + soc_init_card_debugfs(card); + card->rtd = kzalloc(sizeof(struct snd_soc_pcm_runtime) * (card->num_links + card->num_aux_devs), GFP_KERNEL); @@ -3138,18 +3142,18 @@ static int snd_soc_register_card(struct snd_soc_card *card) return 0; } +EXPORT_SYMBOL_GPL(snd_soc_register_card); /** * snd_soc_unregister_card - Unregister a card with the ASoC core * * @card: Card to unregister * - * Note that currently this is an internal only function: it will be - * exposed to machine drivers after further backporting of ASoC v2 - * registration APIs. */ -static int snd_soc_unregister_card(struct snd_soc_card *card) +int snd_soc_unregister_card(struct snd_soc_card *card) { + if (card->instantiated) + soc_cleanup_card_resources(card); mutex_lock(&client_mutex); list_del(&card->list); mutex_unlock(&client_mutex); @@ -3157,6 +3161,7 @@ static int snd_soc_unregister_card(struct snd_soc_card *card) return 0; } +EXPORT_SYMBOL_GPL(snd_soc_unregister_card); /* * Simplify DAI link configuration by removing ".-1" from device names @@ -3486,9 +3491,12 @@ int snd_soc_register_codec(struct device *dev, codec->write = codec_drv->write; codec->read = codec_drv->read; + codec->volatile_register = codec_drv->volatile_register; + codec->readable_register = codec_drv->readable_register; codec->dapm.bias_level = SND_SOC_BIAS_OFF; codec->dapm.dev = dev; codec->dapm.codec = codec; + codec->dapm.seq_notifier = codec_drv->seq_notifier; codec->dev = dev; codec->driver = codec_drv; codec->num_dai = num_dai; @@ -3497,20 +3505,30 @@ int snd_soc_register_codec(struct device *dev, /* allocate CODEC register cache */ if (codec_drv->reg_cache_size && codec_drv->reg_word_size) { reg_size = codec_drv->reg_cache_size * codec_drv->reg_word_size; + codec->reg_size = reg_size; /* it is necessary to make a copy of the default register cache * because in the case of using a compression type that requires * the default register cache to be marked as __devinitconst the * kernel might have freed the array by the time we initialize * the cache. */ - codec->reg_def_copy = kmemdup(codec_drv->reg_cache_default, - reg_size, GFP_KERNEL); - if (!codec->reg_def_copy) { - ret = -ENOMEM; - goto fail; + if (codec_drv->reg_cache_default) { + codec->reg_def_copy = kmemdup(codec_drv->reg_cache_default, + reg_size, GFP_KERNEL); + if (!codec->reg_def_copy) { + ret = -ENOMEM; + goto fail; + } } } + if (codec_drv->reg_access_size && codec_drv->reg_access_default) { + if (!codec->volatile_register) + codec->volatile_register = snd_soc_default_volatile_register; + if (!codec->readable_register) + codec->readable_register = snd_soc_default_readable_register; + } + for (i = 0; i < num_dai; i++) { fixup_codec_formats(&dai_drv[i].playback); fixup_codec_formats(&dai_drv[i].capture); @@ -3577,22 +3595,22 @@ EXPORT_SYMBOL_GPL(snd_soc_unregister_codec); static int __init snd_soc_init(void) { #ifdef CONFIG_DEBUG_FS - debugfs_root = debugfs_create_dir("asoc", NULL); - if (IS_ERR(debugfs_root) || !debugfs_root) { + snd_soc_debugfs_root = debugfs_create_dir("asoc", NULL); + if (IS_ERR(snd_soc_debugfs_root) || !snd_soc_debugfs_root) { printk(KERN_WARNING "ASoC: Failed to create debugfs directory\n"); - debugfs_root = NULL; + snd_soc_debugfs_root = NULL; } - if (!debugfs_create_file("codecs", 0444, debugfs_root, NULL, + if (!debugfs_create_file("codecs", 0444, snd_soc_debugfs_root, NULL, &codec_list_fops)) pr_warn("ASoC: Failed to create CODEC list debugfs file\n"); - if (!debugfs_create_file("dais", 0444, debugfs_root, NULL, + if (!debugfs_create_file("dais", 0444, snd_soc_debugfs_root, NULL, &dai_list_fops)) pr_warn("ASoC: Failed to create DAI list debugfs file\n"); - if (!debugfs_create_file("platforms", 0444, debugfs_root, NULL, + if (!debugfs_create_file("platforms", 0444, snd_soc_debugfs_root, NULL, &platform_list_fops)) pr_warn("ASoC: Failed to create platform list debugfs file\n"); #endif @@ -3604,7 +3622,7 @@ module_init(snd_soc_init); static void __exit snd_soc_exit(void) { #ifdef CONFIG_DEBUG_FS - debugfs_remove_recursive(debugfs_root); + debugfs_remove_recursive(snd_soc_debugfs_root); #endif platform_driver_unregister(&soc_driver); } diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 499730ab5638..37b376f4c75d 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -726,10 +726,23 @@ static int dapm_supply_check_power(struct snd_soc_dapm_widget *w) static int dapm_seq_compare(struct snd_soc_dapm_widget *a, struct snd_soc_dapm_widget *b, - int sort[]) + bool power_up) { + int *sort; + + if (power_up) + sort = dapm_up_seq; + else + sort = dapm_down_seq; + if (sort[a->id] != sort[b->id]) return sort[a->id] - sort[b->id]; + if (a->subseq != b->subseq) { + if (power_up) + return a->subseq - b->subseq; + else + return b->subseq - a->subseq; + } if (a->reg != b->reg) return a->reg - b->reg; if (a->dapm != b->dapm) @@ -741,12 +754,12 @@ static int dapm_seq_compare(struct snd_soc_dapm_widget *a, /* 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[]) + bool power_up) { struct snd_soc_dapm_widget *w; list_for_each_entry(w, list, power_list) - if (dapm_seq_compare(new_widget, w, sort) < 0) { + if (dapm_seq_compare(new_widget, w, power_up) < 0) { list_add_tail(&new_widget->power_list, &w->power_list); return; } @@ -857,26 +870,41 @@ static void dapm_seq_run_coalesced(struct snd_soc_dapm_context *dapm, * handled. */ static void dapm_seq_run(struct snd_soc_dapm_context *dapm, - struct list_head *list, int event, int sort[]) + struct list_head *list, int event, bool power_up) { struct snd_soc_dapm_widget *w, *n; LIST_HEAD(pending); int cur_sort = -1; + int cur_subseq = -1; int cur_reg = SND_SOC_NOPM; struct snd_soc_dapm_context *cur_dapm = NULL; - int ret; + int ret, i; + int *sort; + + if (power_up) + sort = dapm_up_seq; + else + sort = dapm_down_seq; 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 || - w->dapm != cur_dapm) { + w->dapm != cur_dapm || w->subseq != cur_subseq) { if (!list_empty(&pending)) dapm_seq_run_coalesced(cur_dapm, &pending); + if (cur_dapm && cur_dapm->seq_notifier) { + for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++) + if (sort[i] == cur_sort) + cur_dapm->seq_notifier(cur_dapm, + i); + } + INIT_LIST_HEAD(&pending); cur_sort = -1; + cur_subseq = -1; cur_reg = SND_SOC_NOPM; cur_dapm = NULL; } @@ -921,6 +949,7 @@ static void dapm_seq_run(struct snd_soc_dapm_context *dapm, default: /* Queue it up for application */ cur_sort = sort[w->id]; + cur_subseq = w->subseq; cur_reg = w->reg; cur_dapm = w->dapm; list_move(&w->power_list, &pending); @@ -934,6 +963,13 @@ static void dapm_seq_run(struct snd_soc_dapm_context *dapm, if (!list_empty(&pending)) dapm_seq_run_coalesced(dapm, &pending); + + if (cur_dapm && cur_dapm->seq_notifier) { + for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++) + if (sort[i] == cur_sort) + cur_dapm->seq_notifier(cur_dapm, + i); + } } static void dapm_widget_update(struct snd_soc_dapm_context *dapm) @@ -1002,10 +1038,10 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event) list_for_each_entry(w, &card->widgets, list) { switch (w->id) { case snd_soc_dapm_pre: - dapm_seq_insert(w, &down_list, dapm_down_seq); + dapm_seq_insert(w, &down_list, false); break; case snd_soc_dapm_post: - dapm_seq_insert(w, &up_list, dapm_up_seq); + dapm_seq_insert(w, &up_list, true); break; default: @@ -1025,9 +1061,9 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event) trace_snd_soc_dapm_widget_power(w, power); if (power) - dapm_seq_insert(w, &up_list, dapm_up_seq); + dapm_seq_insert(w, &up_list, true); else - dapm_seq_insert(w, &down_list, dapm_down_seq); + dapm_seq_insert(w, &down_list, false); w->power = power; break; @@ -1086,12 +1122,12 @@ static int dapm_power_widgets(struct snd_soc_dapm_context *dapm, int event) } /* Power down widgets first; try to avoid amplifying pops. */ - dapm_seq_run(dapm, &down_list, event, dapm_down_seq); + dapm_seq_run(dapm, &down_list, event, false); dapm_widget_update(dapm); /* Now power up. */ - dapm_seq_run(dapm, &up_list, event, dapm_up_seq); + dapm_seq_run(dapm, &up_list, event, true); list_for_each_entry(d, &dapm->card->dapm_list, list) { /* If we just powered the last thing off drop to standby bias */ @@ -2372,7 +2408,7 @@ static void soc_dapm_shutdown_codec(struct snd_soc_dapm_context *dapm) if (w->dapm != dapm) continue; if (w->power) { - dapm_seq_insert(w, &down_list, dapm_down_seq); + dapm_seq_insert(w, &down_list, false); w->power = 0; powerdown = 1; } @@ -2383,7 +2419,7 @@ static void soc_dapm_shutdown_codec(struct snd_soc_dapm_context *dapm) */ if (powerdown) { snd_soc_dapm_set_bias_level(NULL, dapm, SND_SOC_BIAS_PREPARE); - dapm_seq_run(dapm, &down_list, 0, dapm_down_seq); + dapm_seq_run(dapm, &down_list, 0, false); snd_soc_dapm_set_bias_level(NULL, dapm, SND_SOC_BIAS_STANDBY); } } diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig new file mode 100644 index 000000000000..66b504f06c23 --- /dev/null +++ b/sound/soc/tegra/Kconfig @@ -0,0 +1,26 @@ +config SND_TEGRA_SOC + tristate "SoC Audio for the Tegra System-on-Chip" + depends on ARCH_TEGRA && TEGRA_SYSTEM_DMA + default m + help + Say Y or M here if you want support for SoC audio on Tegra. + +config SND_TEGRA_SOC_I2S + tristate + depends on SND_TEGRA_SOC + default m + help + Say Y or M if you want to add support for codecs attached to the + Tegra I2S interface. You will also need to select the individual + machine drivers to support below. + +config SND_TEGRA_SOC_HARMONY + tristate "SoC Audio support for Tegra Harmony reference board" + depends on SND_TEGRA_SOC && MACH_HARMONY && I2C + default m + select SND_TEGRA_SOC_I2S + select SND_SOC_WM8903 + help + Say Y or M here if you want to add support for SoC audio on the + Tegra Harmony reference board. + diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile new file mode 100644 index 000000000000..dfd2ab9d975c --- /dev/null +++ b/sound/soc/tegra/Makefile @@ -0,0 +1,14 @@ +# Tegra platform Support +snd-soc-tegra-das-objs := tegra_das.o +snd-soc-tegra-pcm-objs := tegra_pcm.o +snd-soc-tegra-i2s-objs := tegra_i2s.o + +obj-$(CONFIG_SND_TEGRA_SOC) += snd-soc-tegra-das.o +obj-$(CONFIG_SND_TEGRA_SOC) += snd-soc-tegra-pcm.o +obj-$(CONFIG_SND_TEGRA_SOC_I2S) += snd-soc-tegra-i2s.o + +# Tegra machine Support +snd-soc-tegra-harmony-objs := harmony.o +snd-soc-tegra-harmony-objs += tegra_asoc_utils.o + +obj-$(CONFIG_SND_TEGRA_SOC_HARMONY) += snd-soc-tegra-harmony.o diff --git a/sound/soc/tegra/harmony.c b/sound/soc/tegra/harmony.c new file mode 100644 index 000000000000..b160b7113f45 --- /dev/null +++ b/sound/soc/tegra/harmony.c @@ -0,0 +1,210 @@ +/* + * harmony.c - Harmony machine ASoC driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. + * + * Copyright 2007 Wolfson Microelectronics PLC. + * Author: Graeme Gregory + * graeme.gregory@wolfsonmicro.com or linux@wolfsonmicro.com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 <asm/mach-types.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "tegra_das.h" +#include "tegra_i2s.h" +#include "tegra_pcm.h" +#include "tegra_asoc_utils.h" + +#define PREFIX "ASoC Harmony: " + +static struct platform_device *harmony_snd_device; + +static int harmony_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->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + int srate, mclk, mclk_change; + int err; + + srate = params_rate(params); + switch (srate) { + case 64000: + case 88200: + case 96000: + mclk = 128 * srate; + break; + default: + mclk = 256 * srate; + break; + } + /* FIXME: Codec only requires >= 3MHz if OSR==0 */ + while (mclk < 6000000) + mclk *= 2; + + err = tegra_asoc_utils_set_rate(srate, mclk, &mclk_change); + if (err < 0) { + pr_err(PREFIX "Can't configure clocks\n"); + return err; + } + + err = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (err < 0) { + pr_err(PREFIX "codec_dai fmt not set\n"); + return err; + } + + err = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (err < 0) { + pr_err(PREFIX "cpu_dai fmt not set\n"); + return err; + } + + if (mclk_change) { + err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, SND_SOC_CLOCK_IN); + if (err < 0) { + pr_err(PREFIX "codec_dai clock not set\n"); + return err; + } + } + + return 0; +} + +static struct snd_soc_ops harmony_asoc_ops = { + .hw_params = harmony_asoc_hw_params, +}; + +static const struct snd_soc_dapm_widget harmony_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static const struct snd_soc_dapm_route harmony_audio_map[] = { + {"Headphone Jack", NULL, "HPOUTR"}, + {"Headphone Jack", NULL, "HPOUTL"}, + {"Mic Bias", NULL, "Mic Jack"}, + {"IN1L", NULL, "Mic Bias"}, +}; + +static int harmony_asoc_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + + snd_soc_dapm_new_controls(dapm, harmony_dapm_widgets, + ARRAY_SIZE(harmony_dapm_widgets)); + + snd_soc_dapm_add_routes(dapm, harmony_audio_map, + ARRAY_SIZE(harmony_audio_map)); + + snd_soc_dapm_enable_pin(dapm, "Headphone Jack"); + snd_soc_dapm_enable_pin(dapm, "Mic Jack"); + snd_soc_dapm_sync(dapm); + + return 0; +} + +static struct snd_soc_dai_link harmony_wm8903_dai = { + .name = "WM8903", + .stream_name = "WM8903 PCM", + .codec_name = "wm8903-codec.0-001a", + .platform_name = "tegra-pcm-audio", + .cpu_dai_name = "tegra-i2s.0", + .codec_dai_name = "wm8903-hifi", + .init = harmony_asoc_init, + .ops = &harmony_asoc_ops, +}; + +static struct snd_soc_card snd_soc_harmony = { + .name = "tegra-harmony", + .dai_link = &harmony_wm8903_dai, + .num_links = 1, +}; + +static int __init harmony_soc_modinit(void) +{ + int ret; + + if (!machine_is_harmony()) { + pr_err(PREFIX "Not running on Tegra Harmony!\n"); + return -ENODEV; + } + + ret = tegra_asoc_utils_init(); + if (ret) { + return ret; + } + + /* + * Create and register platform device + */ + harmony_snd_device = platform_device_alloc("soc-audio", -1); + if (harmony_snd_device == NULL) { + pr_err(PREFIX "platform_device_alloc failed\n"); + ret = -ENOMEM; + goto err_clock_utils; + } + + platform_set_drvdata(harmony_snd_device, &snd_soc_harmony); + + ret = platform_device_add(harmony_snd_device); + if (ret) { + pr_err(PREFIX "platform_device_add failed (%d)\n", + ret); + goto err_device_put; + } + + return 0; + +err_device_put: + platform_device_put(harmony_snd_device); +err_clock_utils: + tegra_asoc_utils_fini(); + return ret; +} +module_init(harmony_soc_modinit); + +static void __exit harmony_soc_modexit(void) +{ + platform_device_unregister(harmony_snd_device); + + tegra_asoc_utils_fini(); +} +module_exit(harmony_soc_modexit); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Harmony machine ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/tegra/tegra_asoc_utils.c b/sound/soc/tegra/tegra_asoc_utils.c new file mode 100644 index 000000000000..cfe2ea890dc0 --- /dev/null +++ b/sound/soc/tegra/tegra_asoc_utils.c @@ -0,0 +1,152 @@ +/* + * tegra_asoc_utils.c - Harmony machine ASoC driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010 - NVIDIA, 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. + * + * 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 <linux/clk.h> +#include <linux/err.h> +#include <linux/kernel.h> + +#include "tegra_asoc_utils.h" + +#define PREFIX "ASoC Tegra: " + +static struct clk *clk_pll_a; +static struct clk *clk_pll_a_out0; +static struct clk *clk_cdev1; + +static int set_baseclock, set_mclk; + +int tegra_asoc_utils_set_rate(int srate, int mclk, int *mclk_change) +{ + int new_baseclock; + int err; + + switch (srate) { + case 11025: + case 22050: + case 44100: + case 88200: + new_baseclock = 56448000; + break; + case 8000: + case 16000: + case 32000: + case 48000: + case 64000: + case 96000: + new_baseclock = 73728000; + break; + default: + return -EINVAL; + } + + *mclk_change = ((new_baseclock != set_baseclock) || + (mclk != set_mclk)); + if (!*mclk_change) + return 0; + + set_baseclock = 0; + set_mclk = 0; + + clk_disable(clk_cdev1); + clk_disable(clk_pll_a_out0); + clk_disable(clk_pll_a); + + err = clk_set_rate(clk_pll_a, new_baseclock); + if (err) { + pr_err(PREFIX "Can't set pll_a rate: %d\n", err); + return err; + } + + err = clk_set_rate(clk_pll_a_out0, mclk); + if (err) { + pr_err(PREFIX "Can't set pll_a_out0 rate: %d\n", err); + return err; + } + + /* Don't set cdev1 rate; its locked to pll_a_out0 */ + + err = clk_enable(clk_pll_a); + if (err) { + pr_err(PREFIX "Can't enable pll_a: %d\n", err); + return err; + } + + err = clk_enable(clk_pll_a_out0); + if (err) { + pr_err(PREFIX "Can't enable pll_a_out0: %d\n", err); + return err; + } + + err = clk_enable(clk_cdev1); + if (err) { + pr_err(PREFIX "Can't enable cdev1: %d\n", err); + return err; + } + + set_baseclock = new_baseclock; + set_mclk = mclk; + + return 0; +} + +int tegra_asoc_utils_init(void) +{ + int ret; + + clk_pll_a = clk_get_sys(NULL, "pll_a"); + if (IS_ERR(clk_pll_a)) { + pr_err(PREFIX "Can't retrieve clk pll_a\n"); + ret = PTR_ERR(clk_pll_a); + goto err; + } + + clk_pll_a_out0 = clk_get_sys(NULL, "pll_a_out0"); + if (IS_ERR(clk_pll_a_out0)) { + pr_err(PREFIX "Can't retrieve clk pll_a_out0\n"); + ret = PTR_ERR(clk_pll_a_out0); + goto err_put_pll_a; + } + + clk_cdev1 = clk_get_sys(NULL, "cdev1"); + if (IS_ERR(clk_cdev1)) { + pr_err(PREFIX "Can't retrieve clk cdev1\n"); + ret = PTR_ERR(clk_cdev1); + goto err_put_pll_a_out0; + } + + return 0; + +err_put_pll_a_out0: + clk_put(clk_pll_a_out0); +err_put_pll_a: + clk_put(clk_pll_a); +err: + return ret; +} + +void tegra_asoc_utils_fini(void) +{ + clk_put(clk_cdev1); + clk_put(clk_pll_a_out0); + clk_put(clk_pll_a); +} + diff --git a/sound/soc/tegra/tegra_asoc_utils.h b/sound/soc/tegra/tegra_asoc_utils.h new file mode 100644 index 000000000000..855f8f6e44ca --- /dev/null +++ b/sound/soc/tegra/tegra_asoc_utils.h @@ -0,0 +1,31 @@ +/* + * tegra_asoc_utils.h - Definitions for Tegra DAS driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010 - NVIDIA, 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. + * + * 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 + * + */ + +#ifndef __TEGRA_ASOC_UTILS_H__ +#define __TEGRA_ASOC_UTILS_H_ + +int tegra_asoc_utils_set_rate(int srate, int mclk_rate, int *mclk_change); +int tegra_asoc_utils_init(void); +void tegra_asoc_utils_fini(void); + +#endif + diff --git a/sound/soc/tegra/tegra_das.c b/sound/soc/tegra/tegra_das.c new file mode 100644 index 000000000000..01eb9c9301de --- /dev/null +++ b/sound/soc/tegra/tegra_das.c @@ -0,0 +1,264 @@ +/* + * tegra_das.c - Tegra DAS driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010 - NVIDIA, 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. + * + * 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 <linux/module.h> +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <mach/iomap.h> +#include <sound/soc.h> +#include "tegra_das.h" + +#define DRV_NAME "tegra-das" + +static struct tegra_das *das; + +static inline void tegra_das_write(u32 reg, u32 val) +{ + __raw_writel(val, das->regs + reg); +} + +static inline u32 tegra_das_read(u32 reg) +{ + return __raw_readl(das->regs + reg); +} + +int tegra_das_connect_dap_to_dac(int dap, int dac) +{ + u32 addr; + u32 reg; + + if (!das) + return -ENODEV; + + addr = TEGRA_DAS_DAP_CTRL_SEL + + (dap * TEGRA_DAS_DAP_CTRL_SEL_STRIDE); + reg = dac << TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P; + + tegra_das_write(addr, reg); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra_das_connect_dap_to_dac); + +int tegra_das_connect_dap_to_dap(int dap, int otherdap, int master, + int sdata1rx, int sdata2rx) +{ + u32 addr; + u32 reg; + + if (!das) + return -ENODEV; + + addr = TEGRA_DAS_DAP_CTRL_SEL + + (dap * TEGRA_DAS_DAP_CTRL_SEL_STRIDE); + reg = otherdap << TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P | + !!sdata2rx << TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P | + !!sdata1rx << TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P | + !!master << TEGRA_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P; + + tegra_das_write(addr, reg); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra_das_connect_dap_to_dap); + +int tegra_das_connect_dac_to_dap(int dac, int dap) +{ + u32 addr; + u32 reg; + + if (!das) + return -ENODEV; + + addr = TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL + + (dac * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE); + reg = dap << TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P | + dap << TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P | + dap << TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P; + + tegra_das_write(addr, reg); + + return 0; +} +EXPORT_SYMBOL_GPL(tegra_das_connect_dac_to_dap); + +#ifdef CONFIG_DEBUG_FS +static int tegra_das_show(struct seq_file *s, void *unused) +{ + int i; + u32 addr; + u32 reg; + + for (i = 0; i < TEGRA_DAS_DAP_CTRL_SEL_COUNT; i++) { + addr = TEGRA_DAS_DAP_CTRL_SEL + + (i * TEGRA_DAS_DAP_CTRL_SEL_STRIDE); + reg = tegra_das_read(addr); + seq_printf(s, "TEGRA_DAS_DAP_CTRL_SEL[%d] = %08x\n", i, reg); + } + + for (i = 0; i < TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_COUNT; i++) { + addr = TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL + + (i * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE); + reg = tegra_das_read(addr); + seq_printf(s, "TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL[%d] = %08x\n", + i, reg); + } + + return 0; +} + +static int tegra_das_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, tegra_das_show, inode->i_private); +} + +static const struct file_operations tegra_das_debug_fops = { + .open = tegra_das_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void tegra_das_debug_add(struct tegra_das *das) +{ + das->debug = debugfs_create_file(DRV_NAME, S_IRUGO, + snd_soc_debugfs_root, das, + &tegra_das_debug_fops); +} + +static void tegra_das_debug_remove(struct tegra_das *das) +{ + if (das->debug) + debugfs_remove(das->debug); +} +#else +static inline void tegra_das_debug_add(struct tegra_das *das) +{ +} + +static inline void tegra_das_debug_remove(struct tegra_das *das) +{ +} +#endif + +static int __devinit tegra_das_probe(struct platform_device *pdev) +{ + struct resource *res, *region; + int ret = 0; + + if (das) + return -ENODEV; + + das = kzalloc(sizeof(struct tegra_das), GFP_KERNEL); + if (!das) { + dev_err(&pdev->dev, "Can't allocate tegra_das\n"); + ret = -ENOMEM; + goto exit; + } + das->dev = &pdev->dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "No memory resource\n"); + ret = -ENODEV; + goto err_free; + } + + region = request_mem_region(res->start, resource_size(res), + pdev->name); + if (!region) { + dev_err(&pdev->dev, "Memory region already claimed\n"); + ret = -EBUSY; + goto err_free; + } + + das->regs = ioremap(res->start, resource_size(res)); + if (!das->regs) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto err_release; + } + + tegra_das_debug_add(das); + + platform_set_drvdata(pdev, das); + + return 0; + +err_release: + release_mem_region(res->start, resource_size(res)); +err_free: + kfree(das); + das = 0; +exit: + return ret; +} + +static int __devexit tegra_das_remove(struct platform_device *pdev) +{ + struct resource *res; + + if (!das) + return -ENODEV; + + platform_set_drvdata(pdev, NULL); + + tegra_das_debug_remove(das); + + iounmap(das->regs); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, resource_size(res)); + + kfree(das); + das = 0; + + return 0; +} + +static struct platform_driver tegra_das_driver = { + .probe = tegra_das_probe, + .remove = __devexit_p(tegra_das_remove), + .driver = { + .name = DRV_NAME, + }, +}; + +static int __init tegra_das_modinit(void) +{ + return platform_driver_register(&tegra_das_driver); +} +module_init(tegra_das_modinit); + +static void __exit tegra_das_modexit(void) +{ + platform_driver_unregister(&tegra_das_driver); +} +module_exit(tegra_das_modexit); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra DAS driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/tegra/tegra_das.h b/sound/soc/tegra/tegra_das.h new file mode 100644 index 000000000000..2c96c7b3c459 --- /dev/null +++ b/sound/soc/tegra/tegra_das.h @@ -0,0 +1,135 @@ +/* + * tegra_das.h - Definitions for Tegra DAS driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010 - NVIDIA, 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. + * + * 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 + * + */ + +#ifndef __TEGRA_DAS_H__ +#define __TEGRA_DAS_H__ + +/* Register TEGRA_DAS_DAP_CTRL_SEL */ +#define TEGRA_DAS_DAP_CTRL_SEL 0x00 +#define TEGRA_DAS_DAP_CTRL_SEL_COUNT 5 +#define TEGRA_DAS_DAP_CTRL_SEL_STRIDE 4 +#define TEGRA_DAS_DAP_CTRL_SEL_DAP_MS_SEL_P 31 +#define TEGRA_DAS_DAP_CTRL_SEL_DAP_MS_SEL_S 1 +#define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_P 30 +#define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA1_TX_RX_S 1 +#define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_P 29 +#define TEGRA_DAS_DAP_CTRL_SEL_DAP_SDATA2_TX_RX_S 1 +#define TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_P 0 +#define TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL_S 5 + +/* Values for field TEGRA_DAS_DAP_CTRL_SEL_DAP_CTRL_SEL */ +#define TEGRA_DAS_DAP_SEL_DAC1 0 +#define TEGRA_DAS_DAP_SEL_DAC2 1 +#define TEGRA_DAS_DAP_SEL_DAC3 2 +#define TEGRA_DAS_DAP_SEL_DAP1 16 +#define TEGRA_DAS_DAP_SEL_DAP2 17 +#define TEGRA_DAS_DAP_SEL_DAP3 18 +#define TEGRA_DAS_DAP_SEL_DAP4 19 +#define TEGRA_DAS_DAP_SEL_DAP5 20 + +/* Register TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL */ +#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL 0x40 +#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_COUNT 3 +#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_STRIDE 4 +#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_P 28 +#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL_S 4 +#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_P 24 +#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL_S 4 +#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_P 0 +#define TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL_S 4 + +/* + * Values for: + * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA2_SEL + * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_SDATA1_SEL + * TEGRA_DAS_DAC_INPUT_DATA_CLK_SEL_DAC_CLK_SEL + */ +#define TEGRA_DAS_DAC_SEL_DAP1 0 +#define TEGRA_DAS_DAC_SEL_DAP2 1 +#define TEGRA_DAS_DAC_SEL_DAP3 2 +#define TEGRA_DAS_DAC_SEL_DAP4 3 +#define TEGRA_DAS_DAC_SEL_DAP5 4 + +/* + * Names/IDs of the DACs/DAPs. + */ + +#define TEGRA_DAS_DAP_ID_1 0 +#define TEGRA_DAS_DAP_ID_2 1 +#define TEGRA_DAS_DAP_ID_3 2 +#define TEGRA_DAS_DAP_ID_4 3 +#define TEGRA_DAS_DAP_ID_5 4 + +#define TEGRA_DAS_DAC_ID_1 0 +#define TEGRA_DAS_DAC_ID_2 1 +#define TEGRA_DAS_DAC_ID_3 2 + +struct tegra_das { + struct device *dev; + void __iomem *regs; + struct dentry *debug; +}; + +/* + * Terminology: + * DAS: Digital audio switch (HW module controlled by this driver) + * DAP: Digital audio port (port/pins on Tegra device) + * DAC: Digital audio controller (e.g. I2S or AC97 controller elsewhere) + * + * The Tegra DAS is a mux/cross-bar which can connect each DAP to a specific + * DAC, or another DAP. When DAPs are connected, one must be the master and + * one the slave. Each DAC allows selection of a specific DAP for input, to + * cater for the case where N DAPs are connected to 1 DAC for broadcast + * output. + * + * This driver is dumb; no attempt is made to ensure that a valid routing + * configuration is programmed. + */ + +/* + * Connect a DAP to to a DAC + * dap_id: DAP to connect: TEGRA_DAS_DAP_ID_* + * dac_sel: DAC to connect to: TEGRA_DAS_DAP_SEL_DAC* + */ +extern int tegra_das_connect_dap_to_dac(int dap_id, int dac_sel); + +/* + * Connect a DAP to to another DAP + * dap_id: DAP to connect: TEGRA_DAS_DAP_ID_* + * other_dap_sel: DAP to connect to: TEGRA_DAS_DAP_SEL_DAP* + * master: Is this DAP the master (1) or slave (0) + * sdata1rx: Is this DAP's SDATA1 pin RX (1) or TX (0) + * sdata2rx: Is this DAP's SDATA2 pin RX (1) or TX (0) + */ +extern int tegra_das_connect_dap_to_dap(int dap_id, int other_dap_sel, + int master, int sdata1rx, + int sdata2rx); + +/* + * Connect a DAC's input to a DAP + * (DAC outputs are selected by the DAP) + * dac_id: DAC ID to connect: TEGRA_DAS_DAC_ID_* + * dap_sel: DAP to receive input from: TEGRA_DAS_DAC_SEL_DAP* + */ +extern int tegra_das_connect_dac_to_dap(int dac_id, int dap_sel); + +#endif diff --git a/sound/soc/tegra/tegra_i2s.c b/sound/soc/tegra/tegra_i2s.c new file mode 100644 index 000000000000..6d668785e9af --- /dev/null +++ b/sound/soc/tegra/tegra_i2s.c @@ -0,0 +1,502 @@ +/* + * tegra_i2s.c - Tegra I2S driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson <speterson@nvidia.com> + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev <malchev@google.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 <linux/clk.h> +#include <linux/module.h> +#include <linux/debugfs.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/seq_file.h> +#include <linux/slab.h> +#include <linux/io.h> +#include <mach/iomap.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "tegra_das.h" +#include "tegra_i2s.h" + +#define DRV_NAME "tegra-i2s" + +static inline void tegra_i2s_write(struct tegra_i2s *i2s, u32 reg, u32 val) +{ + __raw_writel(val, i2s->regs + reg); +} + +static inline u32 tegra_i2s_read(struct tegra_i2s *i2s, u32 reg) +{ + return __raw_readl(i2s->regs + reg); +} + +#ifdef CONFIG_DEBUG_FS +static int tegra_i2s_show(struct seq_file *s, void *unused) +{ +#define REG(r) { r, #r } + static const struct { + int offset; + const char *name; + } regs[] = { + REG(TEGRA_I2S_CTRL), + REG(TEGRA_I2S_STATUS), + REG(TEGRA_I2S_TIMING), + REG(TEGRA_I2S_FIFO_SCR), + REG(TEGRA_I2S_PCM_CTRL), + REG(TEGRA_I2S_NW_CTRL), + REG(TEGRA_I2S_TDM_CTRL), + REG(TEGRA_I2S_TDM_TX_RX_CTRL), + }; +#undef REG + + struct tegra_i2s *i2s = s->private; + int i; + + for (i = 0; i < ARRAY_SIZE(regs); i++) { + u32 val = tegra_i2s_read(i2s, regs[i].offset); + seq_printf(s, "%s = %08x\n", regs[i].name, val); + } + + return 0; +} + +static int tegra_i2s_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, tegra_i2s_show, inode->i_private); +} + +static const struct file_operations tegra_i2s_debug_fops = { + .open = tegra_i2s_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static void tegra_i2s_debug_add(struct tegra_i2s *i2s, int id) +{ + char name[] = DRV_NAME ".0"; + + snprintf(name, sizeof(name), DRV_NAME".%1d", id); + i2s->debug = debugfs_create_file(name, S_IRUGO, snd_soc_debugfs_root, + i2s, &tegra_i2s_debug_fops); +} + +static void tegra_i2s_debug_remove(struct tegra_i2s *i2s) +{ + if (i2s->debug) + debugfs_remove(i2s->debug); +} +#else +static inline void tegra_i2s_debug_add(struct tegra_i2s *i2s) +{ +} + +static inline void tegra_i2s_debug_remove(struct tegra_i2s *i2s) +{ +} +#endif + +static int tegra_i2s_set_fmt(struct snd_soc_dai *dai, + unsigned int fmt) +{ + struct tegra_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_MASTER_ENABLE; + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + i2s->reg_ctrl |= TEGRA_I2S_CTRL_MASTER_ENABLE; + break; + case SND_SOC_DAIFMT_CBM_CFM: + break; + default: + return -EINVAL; + } + + i2s->reg_ctrl &= ~(TEGRA_I2S_CTRL_BIT_FORMAT_MASK | + TEGRA_I2S_CTRL_LRCK_MASK); + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_DSP_A: + i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_DSP; + i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_DSP_B: + i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_DSP; + i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_R_LOW; + break; + case SND_SOC_DAIFMT_I2S: + i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_I2S; + i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_RIGHT_J: + i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_RJM; + i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW; + break; + case SND_SOC_DAIFMT_LEFT_J: + i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_FORMAT_LJM; + i2s->reg_ctrl |= TEGRA_I2S_CTRL_LRCK_L_LOW; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tegra_i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct device *dev = substream->pcm->card->dev; + struct tegra_i2s *i2s = snd_soc_dai_get_drvdata(dai); + u32 reg; + int ret, sample_size, srate, i2sclock, bitcnt; + + i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_BIT_SIZE_MASK; + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_SIZE_16; + sample_size = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_SIZE_24; + sample_size = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + i2s->reg_ctrl |= TEGRA_I2S_CTRL_BIT_SIZE_32; + sample_size = 32; + break; + default: + return -EINVAL; + } + + srate = params_rate(params); + + /* Final "* 2" required by Tegra hardware */ + i2sclock = srate * params_channels(params) * sample_size * 2; + + ret = clk_set_rate(i2s->clk_i2s, i2sclock); + if (ret) { + dev_err(dev, "Can't set I2S clock rate: %d\n", ret); + return ret; + } + + bitcnt = (i2sclock / (2 * srate)) - 1; + if (bitcnt < 0 || bitcnt > TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US) + return -EINVAL; + reg = bitcnt << TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT; + + if (i2sclock % (2 * srate)) + reg |= TEGRA_I2S_TIMING_NON_SYM_ENABLE; + + tegra_i2s_write(i2s, TEGRA_I2S_TIMING, reg); + + tegra_i2s_write(i2s, TEGRA_I2S_FIFO_SCR, + TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS | + TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS); + + return 0; +} + +static void tegra_i2s_start_playback(struct tegra_i2s *i2s) +{ + i2s->reg_ctrl |= TEGRA_I2S_CTRL_FIFO1_ENABLE; + tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl); +} + +static void tegra_i2s_stop_playback(struct tegra_i2s *i2s) +{ + i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_FIFO1_ENABLE; + tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl); +} + +static void tegra_i2s_start_capture(struct tegra_i2s *i2s) +{ + i2s->reg_ctrl |= TEGRA_I2S_CTRL_FIFO2_ENABLE; + tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl); +} + +static void tegra_i2s_stop_capture(struct tegra_i2s *i2s) +{ + i2s->reg_ctrl &= ~TEGRA_I2S_CTRL_FIFO2_ENABLE; + tegra_i2s_write(i2s, TEGRA_I2S_CTRL, i2s->reg_ctrl); +} + +static int tegra_i2s_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct tegra_i2s *i2s = snd_soc_dai_get_drvdata(dai); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + case SNDRV_PCM_TRIGGER_RESUME: + if (!i2s->clk_refs) + clk_enable(i2s->clk_i2s); + i2s->clk_refs++; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra_i2s_start_playback(i2s); + else + tegra_i2s_start_capture(i2s); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + case SNDRV_PCM_TRIGGER_SUSPEND: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + tegra_i2s_stop_playback(i2s); + else + tegra_i2s_stop_capture(i2s); + i2s->clk_refs--; + if (!i2s->clk_refs) + clk_disable(i2s->clk_i2s); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int tegra_i2s_probe(struct snd_soc_dai *dai) +{ + struct tegra_i2s * i2s = snd_soc_dai_get_drvdata(dai); + + dai->capture_dma_data = &i2s->capture_dma_data; + dai->playback_dma_data = &i2s->playback_dma_data; + + return 0; +} + +static struct snd_soc_dai_ops tegra_i2s_dai_ops = { + .set_fmt = tegra_i2s_set_fmt, + .hw_params = tegra_i2s_hw_params, + .trigger = tegra_i2s_trigger, +}; + +struct snd_soc_dai_driver tegra_i2s_dai[] = { + { + .name = DRV_NAME ".0", + .probe = tegra_i2s_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra_i2s_dai_ops, + .symmetric_rates = 1, + }, + { + .name = DRV_NAME ".1", + .probe = tegra_i2s_probe, + .playback = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .capture = { + .channels_min = 2, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_96000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + }, + .ops = &tegra_i2s_dai_ops, + .symmetric_rates = 1, + }, +}; + +static __devinit int tegra_i2s_platform_probe(struct platform_device *pdev) +{ + struct tegra_i2s * i2s; + char clk_name[12]; /* tegra-i2s.0 */ + struct resource *mem, *memregion, *dmareq; + int ret; + + if ((pdev->id < 0) || + (pdev->id >= ARRAY_SIZE(tegra_i2s_dai))) { + dev_err(&pdev->dev, "ID %d out of range\n", pdev->id); + return -EINVAL; + } + + /* + * FIXME: Until a codec driver exists for the tegra DAS, hard-code a + * 1:1 mapping between audio controllers and audio ports. + */ + ret = tegra_das_connect_dap_to_dac(TEGRA_DAS_DAP_ID_1 + pdev->id, + TEGRA_DAS_DAP_SEL_DAC1 + pdev->id); + if (ret) { + dev_err(&pdev->dev, "Can't set up DAP connection\n"); + return ret; + } + ret = tegra_das_connect_dac_to_dap(TEGRA_DAS_DAC_ID_1 + pdev->id, + TEGRA_DAS_DAC_SEL_DAP1 + pdev->id); + if (ret) { + dev_err(&pdev->dev, "Can't set up DAC connection\n"); + return ret; + } + + i2s = kzalloc(sizeof(struct tegra_i2s), GFP_KERNEL); + if (!i2s) { + dev_err(&pdev->dev, "Can't allocate tegra_i2s\n"); + ret = -ENOMEM; + goto exit; + } + dev_set_drvdata(&pdev->dev, i2s); + + snprintf(clk_name, sizeof(clk_name), DRV_NAME ".%d", pdev->id); + i2s->clk_i2s = clk_get_sys(clk_name, NULL); + if (IS_ERR(i2s->clk_i2s)) { + pr_err("Can't retrieve i2s clock\n"); + ret = PTR_ERR(i2s->clk_i2s); + goto err_free; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "No memory resource\n"); + ret = -ENODEV; + goto err_clk_put; + } + + dmareq = platform_get_resource(pdev, IORESOURCE_DMA, 0); + if (!dmareq) { + dev_err(&pdev->dev, "No DMA resource\n"); + ret = -ENODEV; + goto err_clk_put; + } + + memregion = request_mem_region(mem->start, resource_size(mem), + DRV_NAME); + if (!memregion) { + dev_err(&pdev->dev, "Memory region already claimed\n"); + ret = -EBUSY; + goto err_clk_put; + } + + i2s->regs = ioremap(mem->start, resource_size(mem)); + if (!i2s->regs) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENOMEM; + goto err_release; + } + + i2s->capture_dma_data.addr = mem->start + TEGRA_I2S_FIFO2; + i2s->capture_dma_data.wrap = 4; + i2s->capture_dma_data.width = 32; + i2s->capture_dma_data.req_sel = dmareq->start; + + i2s->playback_dma_data.addr = mem->start + TEGRA_I2S_FIFO1; + i2s->playback_dma_data.wrap = 4; + i2s->playback_dma_data.width = 32; + i2s->playback_dma_data.req_sel = dmareq->start; + + i2s->reg_ctrl = TEGRA_I2S_CTRL_FIFO_FORMAT_PACKED; + + ret = snd_soc_register_dai(&pdev->dev, &tegra_i2s_dai[pdev->id]); + if (ret) { + dev_err(&pdev->dev, "Could not register DAI: %d\n", ret); + ret = -ENOMEM; + goto err_unmap; + } + + tegra_i2s_debug_add(i2s, pdev->id); + + return 0; + +err_unmap: + iounmap(i2s->regs); +err_release: + release_mem_region(mem->start, resource_size(mem)); +err_clk_put: + clk_put(i2s->clk_i2s); +err_free: + kfree(i2s); +exit: + return ret; +} + +static int __devexit tegra_i2s_platform_remove(struct platform_device *pdev) +{ + struct tegra_i2s *i2s = dev_get_drvdata(&pdev->dev); + struct resource *res; + + snd_soc_unregister_dai(&pdev->dev); + + tegra_i2s_debug_remove(i2s); + + iounmap(i2s->regs); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, resource_size(res)); + + clk_put(i2s->clk_i2s); + + kfree(i2s); + + return 0; +} + +static struct platform_driver tegra_i2s_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = tegra_i2s_platform_probe, + .remove = __devexit_p(tegra_i2s_platform_remove), +}; + +static int __init snd_tegra_i2s_init(void) +{ + return platform_driver_register(&tegra_i2s_driver); +} +module_init(snd_tegra_i2s_init); + +static void __exit snd_tegra_i2s_exit(void) +{ + platform_driver_unregister(&tegra_i2s_driver); +} +module_exit(snd_tegra_i2s_exit); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra I2S ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/tegra/tegra_i2s.h b/sound/soc/tegra/tegra_i2s.h new file mode 100644 index 000000000000..2b38a096f46c --- /dev/null +++ b/sound/soc/tegra/tegra_i2s.h @@ -0,0 +1,165 @@ +/* + * tegra_i2s.h - Definitions for Tegra I2S driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson <speterson@nvidia.com> + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev <malchev@google.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 + * + */ + +#ifndef __TEGRA_I2S_H__ +#define __TEGRA_I2S_H__ + +#include "tegra_pcm.h" + +/* Register offsets from TEGRA_I2S1_BASE and TEGRA_I2S2_BASE */ + +#define TEGRA_I2S_CTRL 0x00 +#define TEGRA_I2S_STATUS 0x04 +#define TEGRA_I2S_TIMING 0x08 +#define TEGRA_I2S_FIFO_SCR 0x0c +#define TEGRA_I2S_PCM_CTRL 0x10 +#define TEGRA_I2S_NW_CTRL 0x14 +#define TEGRA_I2S_TDM_CTRL 0x20 +#define TEGRA_I2S_TDM_TX_RX_CTRL 0x24 +#define TEGRA_I2S_FIFO1 0x40 +#define TEGRA_I2S_FIFO2 0x80 + +/* Fields in TEGRA_I2S_CTRL */ + +#define TEGRA_I2S_CTRL_FIFO2_TX_ENABLE (1 << 30) +#define TEGRA_I2S_CTRL_FIFO1_ENABLE (1 << 29) +#define TEGRA_I2S_CTRL_FIFO2_ENABLE (1 << 28) +#define TEGRA_I2S_CTRL_FIFO1_RX_ENABLE (1 << 27) +#define TEGRA_I2S_CTRL_FIFO_LPBK_ENABLE (1 << 26) +#define TEGRA_I2S_CTRL_MASTER_ENABLE (1 << 25) + +#define TEGRA_I2S_LRCK_LEFT_LOW 0 +#define TEGRA_I2S_LRCK_RIGHT_LOW 1 + +#define TEGRA_I2S_CTRL_LRCK_SHIFT 24 +#define TEGRA_I2S_CTRL_LRCK_MASK (1 << TEGRA_I2S_CTRL_LRCK_SHIFT) +#define TEGRA_I2S_CTRL_LRCK_L_LOW (TEGRA_I2S_LRCK_LEFT_LOW << TEGRA_I2S_CTRL_LRCK_SHIFT) +#define TEGRA_I2S_CTRL_LRCK_R_LOW (TEGRA_I2S_LRCK_RIGHT_LOW << TEGRA_I2S_CTRL_LRCK_SHIFT) + +#define TEGRA_I2S_BIT_FORMAT_I2S 0 +#define TEGRA_I2S_BIT_FORMAT_RJM 1 +#define TEGRA_I2S_BIT_FORMAT_LJM 2 +#define TEGRA_I2S_BIT_FORMAT_DSP 3 + +#define TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT 10 +#define TEGRA_I2S_CTRL_BIT_FORMAT_MASK (3 << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT) +#define TEGRA_I2S_CTRL_BIT_FORMAT_I2S (TEGRA_I2S_BIT_FORMAT_I2S << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT) +#define TEGRA_I2S_CTRL_BIT_FORMAT_RJM (TEGRA_I2S_BIT_FORMAT_RJM << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT) +#define TEGRA_I2S_CTRL_BIT_FORMAT_LJM (TEGRA_I2S_BIT_FORMAT_LJM << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT) +#define TEGRA_I2S_CTRL_BIT_FORMAT_DSP (TEGRA_I2S_BIT_FORMAT_DSP << TEGRA_I2S_CTRL_BIT_FORMAT_SHIFT) + +#define TEGRA_I2S_BIT_SIZE_16 0 +#define TEGRA_I2S_BIT_SIZE_20 1 +#define TEGRA_I2S_BIT_SIZE_24 2 +#define TEGRA_I2S_BIT_SIZE_32 3 + +#define TEGRA_I2S_CTRL_BIT_SIZE_SHIFT 8 +#define TEGRA_I2S_CTRL_BIT_SIZE_MASK (3 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA_I2S_CTRL_BIT_SIZE_16 (TEGRA_I2S_BIT_SIZE_16 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA_I2S_CTRL_BIT_SIZE_20 (TEGRA_I2S_BIT_SIZE_20 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA_I2S_CTRL_BIT_SIZE_24 (TEGRA_I2S_BIT_SIZE_24 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT) +#define TEGRA_I2S_CTRL_BIT_SIZE_32 (TEGRA_I2S_BIT_SIZE_32 << TEGRA_I2S_CTRL_BIT_SIZE_SHIFT) + +#define TEGRA_I2S_FIFO_16_LSB 0 +#define TEGRA_I2S_FIFO_20_LSB 1 +#define TEGRA_I2S_FIFO_24_LSB 2 +#define TEGRA_I2S_FIFO_32 3 +#define TEGRA_I2S_FIFO_PACKED 7 + +#define TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT 4 +#define TEGRA_I2S_CTRL_FIFO_FORMAT_MASK (7 << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA_I2S_CTRL_FIFO_FORMAT_16_LSB (TEGRA_I2S_FIFO_16_LSB << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA_I2S_CTRL_FIFO_FORMAT_20_LSB (TEGRA_I2S_FIFO_20_LSB << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA_I2S_CTRL_FIFO_FORMAT_24_LSB (TEGRA_I2S_FIFO_24_LSB << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA_I2S_CTRL_FIFO_FORMAT_32 (TEGRA_I2S_FIFO_32 << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) +#define TEGRA_I2S_CTRL_FIFO_FORMAT_PACKED (TEGRA_I2S_FIFO_PACKED << TEGRA_I2S_CTRL_FIFO_FORMAT_SHIFT) + +#define TEGRA_I2S_CTRL_IE_FIFO1_ERR (1 << 3) +#define TEGRA_I2S_CTRL_IE_FIFO2_ERR (1 << 2) +#define TEGRA_I2S_CTRL_QE_FIFO1 (1 << 1) +#define TEGRA_I2S_CTRL_QE_FIFO2 (1 << 0) + +/* Fields in TEGRA_I2S_STATUS */ + +#define TEGRA_I2S_STATUS_FIFO1_RDY (1 << 31) +#define TEGRA_I2S_STATUS_FIFO2_RDY (1 << 30) +#define TEGRA_I2S_STATUS_FIFO1_BSY (1 << 29) +#define TEGRA_I2S_STATUS_FIFO2_BSY (1 << 28) +#define TEGRA_I2S_STATUS_FIFO1_ERR (1 << 3) +#define TEGRA_I2S_STATUS_FIFO2_ERR (1 << 2) +#define TEGRA_I2S_STATUS_QS_FIFO1 (1 << 1) +#define TEGRA_I2S_STATUS_QS_FIFO2 (1 << 0) + +/* Fields in TEGRA_I2S_TIMING */ + +#define TEGRA_I2S_TIMING_NON_SYM_ENABLE (1 << 12) +#define TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT 0 +#define TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US 0x7fff +#define TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK (TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_MASK_US << TEGRA_I2S_TIMING_CHANNEL_BIT_COUNT_SHIFT) + +/* Fields in TEGRA_I2S_FIFO_SCR */ + +#define TEGRA_I2S_FIFO_SCR_FIFO2_FULL_EMPTY_COUNT_SHIFT 24 +#define TEGRA_I2S_FIFO_SCR_FIFO1_FULL_EMPTY_COUNT_SHIFT 16 +#define TEGRA_I2S_FIFO_SCR_FIFO_FULL_EMPTY_COUNT_MASK 0x3f + +#define TEGRA_I2S_FIFO_SCR_FIFO2_CLR (1 << 12) +#define TEGRA_I2S_FIFO_SCR_FIFO1_CLR (1 << 8) + +#define TEGRA_I2S_FIFO_ATN_LVL_ONE_SLOT 0 +#define TEGRA_I2S_FIFO_ATN_LVL_FOUR_SLOTS 1 +#define TEGRA_I2S_FIFO_ATN_LVL_EIGHT_SLOTS 2 +#define TEGRA_I2S_FIFO_ATN_LVL_TWELVE_SLOTS 3 + +#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT 4 +#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_MASK (3 << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) +#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_ONE_SLOT (TEGRA_I2S_FIFO_ATN_LVL_ONE_SLOT << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) +#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_FOUR_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_FOUR_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) +#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_EIGHT_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_EIGHT_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) +#define TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_TWELVE_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO2_ATN_LVL_SHIFT) + +#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT 0 +#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_MASK (3 << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) +#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_ONE_SLOT (TEGRA_I2S_FIFO_ATN_LVL_ONE_SLOT << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) +#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_FOUR_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_FOUR_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) +#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_EIGHT_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_EIGHT_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) +#define TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_TWELVE_SLOTS (TEGRA_I2S_FIFO_ATN_LVL_TWELVE_SLOTS << TEGRA_I2S_FIFO_SCR_FIFO1_ATN_LVL_SHIFT) + +struct tegra_i2s { + struct clk *clk_i2s; + int clk_refs; + struct tegra_pcm_dma_params capture_dma_data; + struct tegra_pcm_dma_params playback_dma_data; + void __iomem *regs; + struct dentry *debug; + u32 reg_ctrl; +}; + +#endif diff --git a/sound/soc/tegra/tegra_pcm.c b/sound/soc/tegra/tegra_pcm.c new file mode 100644 index 000000000000..663ea9fa0ca3 --- /dev/null +++ b/sound/soc/tegra/tegra_pcm.c @@ -0,0 +1,401 @@ +/* + * tegra_pcm.c - Tegra PCM driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson <speterson@nvidia.com> + * Vijay Mali <vmali@nvidia.com> + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev <malchev@google.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "tegra_pcm.h" + +static const struct snd_pcm_hardware tegra_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME | + SNDRV_PCM_INFO_INTERLEAVED, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 2, + .channels_max = 2, + .period_bytes_min = 1024, + .period_bytes_max = PAGE_SIZE, + .periods_min = 2, + .periods_max = 8, + .buffer_bytes_max = PAGE_SIZE * 8, + .fifo_size = 4, +}; + +static void tegra_pcm_queue_dma(struct tegra_runtime_data *prtd) +{ + struct snd_pcm_substream *substream = prtd->substream; + struct snd_dma_buffer *buf = &substream->dma_buffer; + struct tegra_dma_req *dma_req; + unsigned long addr; + + dma_req = &prtd->dma_req[prtd->dma_req_idx]; + prtd->dma_req_idx = 1 - prtd->dma_req_idx; + + addr = buf->addr + prtd->dma_pos; + prtd->dma_pos += dma_req->size; + if (prtd->dma_pos >= prtd->dma_pos_end) + prtd->dma_pos = 0; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + dma_req->source_addr = addr; + else + dma_req->dest_addr = addr; + + tegra_dma_enqueue_req(prtd->dma_chan, dma_req); +} + +static void dma_complete_callback(struct tegra_dma_req *req) +{ + struct tegra_runtime_data *prtd = (struct tegra_runtime_data *)req->dev; + struct snd_pcm_substream *substream = prtd->substream; + struct snd_pcm_runtime *runtime = substream->runtime; + + spin_lock(&prtd->lock); + + if (!prtd->running) { + spin_unlock(&prtd->lock); + return; + } + + if (++prtd->period_index >= runtime->periods) + prtd->period_index = 0; + + tegra_pcm_queue_dma(prtd); + + spin_unlock(&prtd->lock); + + snd_pcm_period_elapsed(substream); +} + +static void setup_dma_tx_request(struct tegra_dma_req *req, + struct tegra_pcm_dma_params * dmap) +{ + req->complete = dma_complete_callback; + req->to_memory = false; + req->dest_addr = dmap->addr; + req->dest_wrap = dmap->wrap; + req->source_bus_width = 32; + req->source_wrap = 0; + req->dest_bus_width = dmap->width; + req->req_sel = dmap->req_sel; +} + +static void setup_dma_rx_request(struct tegra_dma_req *req, + struct tegra_pcm_dma_params * dmap) +{ + req->complete = dma_complete_callback; + req->to_memory = true; + req->source_addr = dmap->addr; + req->dest_wrap = 0; + req->source_bus_width = dmap->width; + req->source_wrap = dmap->wrap; + req->dest_bus_width = 32; + req->req_sel = dmap->req_sel; +} + +static int tegra_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct tegra_runtime_data *prtd; + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct tegra_pcm_dma_params * dmap; + int ret = 0; + + prtd = kzalloc(sizeof(struct tegra_runtime_data), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + runtime->private_data = prtd; + prtd->substream = substream; + + spin_lock_init(&prtd->lock); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + dmap = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + setup_dma_tx_request(&prtd->dma_req[0], dmap); + setup_dma_tx_request(&prtd->dma_req[1], dmap); + } else { + dmap = snd_soc_dai_get_dma_data(rtd->cpu_dai, substream); + setup_dma_rx_request(&prtd->dma_req[0], dmap); + setup_dma_rx_request(&prtd->dma_req[1], dmap); + } + + prtd->dma_req[0].dev = prtd; + prtd->dma_req[1].dev = prtd; + + prtd->dma_chan = tegra_dma_allocate_channel(TEGRA_DMA_MODE_ONESHOT); + if (IS_ERR(prtd->dma_chan)) { + ret = PTR_ERR(prtd->dma_chan); + goto err; + } + + /* Set HW params now that initialization is complete */ + snd_soc_set_runtime_hwparams(substream, &tegra_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) + goto err; + + return 0; + +err: + if (prtd->dma_chan) { + tegra_dma_free_channel(prtd->dma_chan); + } + + kfree(prtd); + + return ret; +} + +static int tegra_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct tegra_runtime_data *prtd = runtime->private_data; + + tegra_dma_free_channel(prtd->dma_chan); + + kfree(prtd); + + return 0; +} + +static int tegra_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct tegra_runtime_data *prtd = runtime->private_data; + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + prtd->dma_req[0].size = params_period_bytes(params); + prtd->dma_req[1].size = prtd->dma_req[0].size; + + return 0; +} + +static int tegra_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_set_runtime_buffer(substream, NULL); + + return 0; +} + +static int tegra_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct tegra_runtime_data *prtd = runtime->private_data; + unsigned long flags; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + prtd->dma_pos = 0; + prtd->dma_pos_end = frames_to_bytes(runtime, runtime->periods * runtime->period_size); + prtd->period_index = 0; + prtd->dma_req_idx = 0; + /* Fall-through */ + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + spin_lock_irqsave(&prtd->lock, flags); + prtd->running = 1; + spin_unlock_irqrestore(&prtd->lock, flags); + tegra_pcm_queue_dma(prtd); + tegra_pcm_queue_dma(prtd); + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + spin_lock_irqsave(&prtd->lock, flags); + prtd->running = 0; + spin_unlock_irqrestore(&prtd->lock, flags); + tegra_dma_dequeue_req(prtd->dma_chan, &prtd->dma_req[0]); + tegra_dma_dequeue_req(prtd->dma_chan, &prtd->dma_req[1]); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t tegra_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct tegra_runtime_data *prtd = runtime->private_data; + + return prtd->period_index * runtime->period_size; +} + + +static int tegra_pcm_mmap(struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_writecombine(substream->pcm->card->dev, vma, + runtime->dma_area, + runtime->dma_addr, + runtime->dma_bytes); +} + +static struct snd_pcm_ops tegra_pcm_ops = { + .open = tegra_pcm_open, + .close = tegra_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = tegra_pcm_hw_params, + .hw_free = tegra_pcm_hw_free, + .trigger = tegra_pcm_trigger, + .pointer = tegra_pcm_pointer, + .mmap = tegra_pcm_mmap, +}; + +static int tegra_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 = tegra_pcm_hardware.buffer_bytes_max; + + buf->area = dma_alloc_writecombine(pcm->card->dev, size, + &buf->addr, GFP_KERNEL); + if (!buf->area) + return -ENOMEM; + + buf->dev.type = SNDRV_DMA_TYPE_DEV; + buf->dev.dev = pcm->card->dev; + buf->private_data = NULL; + buf->bytes = size; + + return 0; +} + +static void tegra_pcm_deallocate_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; + + if (!buf->area) + return; + + dma_free_writecombine(pcm->card->dev, buf->bytes, + buf->area, buf->addr); + buf->area = NULL; +} + +static u64 tegra_dma_mask = DMA_BIT_MASK(32); + +static int tegra_pcm_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 = &tegra_dma_mask; + if (!card->dev->coherent_dma_mask) + card->dev->coherent_dma_mask = 0xffffffff; + + if (dai->driver->playback.channels_min) { + ret = tegra_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_PLAYBACK); + if (ret) + goto err; + } + + if (dai->driver->capture.channels_min) { + ret = tegra_pcm_preallocate_dma_buffer(pcm, + SNDRV_PCM_STREAM_CAPTURE); + if (ret) + goto err_free_play; + } + + return 0; + +err_free_play: + tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); +err: + return ret; +} + +static void tegra_pcm_free(struct snd_pcm *pcm) +{ + tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE); + tegra_pcm_deallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); +} + +struct snd_soc_platform_driver tegra_pcm_platform = { + .ops = &tegra_pcm_ops, + .pcm_new = tegra_pcm_new, + .pcm_free = tegra_pcm_free, +}; + +static int __devinit tegra_pcm_platform_probe(struct platform_device *pdev) +{ + return snd_soc_register_platform(&pdev->dev, &tegra_pcm_platform); +} + +static int __devexit tegra_pcm_platform_remove(struct platform_device *pdev) +{ + snd_soc_unregister_platform(&pdev->dev); + return 0; +} + +static struct platform_driver tegra_pcm_driver = { + .driver = { + .name = "tegra-pcm-audio", + .owner = THIS_MODULE, + }, + .probe = tegra_pcm_platform_probe, + .remove = __devexit_p(tegra_pcm_platform_remove), +}; + +static int __init snd_tegra_pcm_init(void) +{ + return platform_driver_register(&tegra_pcm_driver); +} +module_init(snd_tegra_pcm_init); + +static void __exit snd_tegra_pcm_exit(void) +{ + platform_driver_unregister(&tegra_pcm_driver); +} +module_exit(snd_tegra_pcm_exit); + +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_DESCRIPTION("Tegra PCM ASoC driver"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/tegra/tegra_pcm.h b/sound/soc/tegra/tegra_pcm.h new file mode 100644 index 000000000000..dbb90339fe0d --- /dev/null +++ b/sound/soc/tegra/tegra_pcm.h @@ -0,0 +1,55 @@ +/* + * tegra_pcm.h - Definitions for Tegra PCM driver + * + * Author: Stephen Warren <swarren@nvidia.com> + * Copyright (C) 2010 - NVIDIA, Inc. + * + * Based on code copyright/by: + * + * Copyright (c) 2009-2010, NVIDIA Corporation. + * Scott Peterson <speterson@nvidia.com> + * + * Copyright (C) 2010 Google, Inc. + * Iliyan Malchev <malchev@google.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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 + * + */ + +#ifndef __TEGRA_PCM_H__ +#define __TEGRA_PCM_H__ + +#include <mach/dma.h> + +struct tegra_pcm_dma_params { + unsigned long addr; + unsigned long wrap; + unsigned long width; + unsigned long req_sel; +}; + +struct tegra_runtime_data { + struct snd_pcm_substream *substream; + spinlock_t lock; + int running; + int dma_pos; + int dma_pos_end; + int period_index; + int dma_req_idx; + struct tegra_dma_req dma_req[2]; + struct tegra_dma_channel *dma_chan; +}; + +#endif |