diff options
Diffstat (limited to 'sound')
48 files changed, 2432 insertions, 176 deletions
diff --git a/sound/soc/atmel/playpaq_wm8510.c b/sound/soc/atmel/playpaq_wm8510.c index 9eb610c2ba9..9df4c68ef00 100644 --- a/sound/soc/atmel/playpaq_wm8510.c +++ b/sound/soc/atmel/playpaq_wm8510.c @@ -268,7 +268,7 @@ static int playpaq_wm8510_hw_params(struct snd_pcm_substream *substream, #endif /* CONFIG_SND_AT32_SOC_PLAYPAQ_SLAVE */ - ret = snd_soc_dai_set_pll(codec_dai, 0, + ret = snd_soc_dai_set_pll(codec_dai, 0, 0, clk_get_rate(CODEC_CLK), pll_out); if (ret < 0) { pr_warning("playpaq_wm8510: Failed to set CODEC DAI PLL (%d)\n", diff --git a/sound/soc/blackfin/bf5xx-ad1836.c b/sound/soc/blackfin/bf5xx-ad1836.c index cd361e304b0..0f45a3f56be 100644 --- a/sound/soc/blackfin/bf5xx-ad1836.c +++ b/sound/soc/blackfin/bf5xx-ad1836.c @@ -52,6 +52,7 @@ static int bf5xx_ad1836_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + unsigned int channel_map[] = {0, 4, 1, 5, 2, 6, 3, 7}; int ret = 0; /* set cpu DAI configuration */ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A | @@ -65,6 +66,12 @@ static int bf5xx_ad1836_hw_params(struct snd_pcm_substream *substream, if (ret < 0) return ret; + /* set cpu DAI channel mapping */ + ret = snd_soc_dai_set_channel_map(cpu_dai, ARRAY_SIZE(channel_map), + channel_map, ARRAY_SIZE(channel_map), channel_map); + if (ret < 0) + return ret; + return 0; } diff --git a/sound/soc/blackfin/bf5xx-ad1938.c b/sound/soc/blackfin/bf5xx-ad1938.c index 08269e91810..2ef1e5013b8 100644 --- a/sound/soc/blackfin/bf5xx-ad1938.c +++ b/sound/soc/blackfin/bf5xx-ad1938.c @@ -61,6 +61,7 @@ static int bf5xx_ad1938_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + unsigned int channel_map[] = {0, 1, 2, 3, 4, 5, 6, 7}; int ret = 0; /* set cpu DAI configuration */ ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_DSP_A | @@ -75,7 +76,13 @@ static int bf5xx_ad1938_hw_params(struct snd_pcm_substream *substream, return ret; /* set codec DAI slots, 8 channels, all channels are enabled */ - ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xFF, 8); + ret = snd_soc_dai_set_tdm_slot(codec_dai, 0xFF, 0xFF, 8, 32); + if (ret < 0) + return ret; + + /* set cpu DAI channel mapping */ + ret = snd_soc_dai_set_channel_map(cpu_dai, ARRAY_SIZE(channel_map), + channel_map, ARRAY_SIZE(channel_map), channel_map); if (ret < 0) return ret; diff --git a/sound/soc/blackfin/bf5xx-i2s.c b/sound/soc/blackfin/bf5xx-i2s.c index 084b68884ad..3e6ada0dd1c 100644 --- a/sound/soc/blackfin/bf5xx-i2s.c +++ b/sound/soc/blackfin/bf5xx-i2s.c @@ -49,7 +49,6 @@ struct bf5xx_i2s_port { u16 rcr1; u16 tcr2; u16 rcr2; - int counter; int configured; }; @@ -133,16 +132,6 @@ static int bf5xx_i2s_set_dai_fmt(struct snd_soc_dai *cpu_dai, return ret; } -static int bf5xx_i2s_startup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - pr_debug("%s enter\n", __func__); - - /*this counter is used for counting how many pcm streams are opened*/ - bf5xx_i2s.counter++; - return 0; -} - static int bf5xx_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -201,9 +190,8 @@ static void bf5xx_i2s_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { pr_debug("%s enter\n", __func__); - bf5xx_i2s.counter--; /* No active stream, SPORT is allowed to be configured again. */ - if (!bf5xx_i2s.counter) + if (!dai->active) bf5xx_i2s.configured = 0; } @@ -284,7 +272,6 @@ static int bf5xx_i2s_resume(struct snd_soc_dai *dai) SNDRV_PCM_FMTBIT_S32_LE) static struct snd_soc_dai_ops bf5xx_i2s_dai_ops = { - .startup = bf5xx_i2s_startup, .shutdown = bf5xx_i2s_shutdown, .hw_params = bf5xx_i2s_hw_params, .set_fmt = bf5xx_i2s_set_dai_fmt, diff --git a/sound/soc/blackfin/bf5xx-tdm-pcm.c b/sound/soc/blackfin/bf5xx-tdm-pcm.c index ccb5e823bd1..a8c73cbbd68 100644 --- a/sound/soc/blackfin/bf5xx-tdm-pcm.c +++ b/sound/soc/blackfin/bf5xx-tdm-pcm.c @@ -43,7 +43,7 @@ #include "bf5xx-tdm.h" #include "bf5xx-sport.h" -#define PCM_BUFFER_MAX 0x10000 +#define PCM_BUFFER_MAX 0x8000 #define FRAGMENT_SIZE_MIN (4*1024) #define FRAGMENTS_MIN 2 #define FRAGMENTS_MAX 32 @@ -177,6 +177,9 @@ out: static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel, snd_pcm_uframes_t pos, void *buf, snd_pcm_uframes_t count) { + struct snd_pcm_runtime *runtime = substream->runtime; + struct sport_device *sport = runtime->private_data; + struct bf5xx_tdm_port *tdm_port = sport->private_data; unsigned int *src; unsigned int *dst; int i; @@ -188,7 +191,7 @@ static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel, dst += pos * 8; while (count--) { for (i = 0; i < substream->runtime->channels; i++) - *(dst + i) = *src++; + *(dst + tdm_port->tx_map[i]) = *src++; dst += 8; } } else { @@ -198,7 +201,7 @@ static int bf5xx_pcm_copy(struct snd_pcm_substream *substream, int channel, src += pos * 8; while (count--) { for (i = 0; i < substream->runtime->channels; i++) - *dst++ = *(src+i); + *dst++ = *(src + tdm_port->rx_map[i]); src += 8; } } diff --git a/sound/soc/blackfin/bf5xx-tdm.c b/sound/soc/blackfin/bf5xx-tdm.c index ff546e91a22..4b360124083 100644 --- a/sound/soc/blackfin/bf5xx-tdm.c +++ b/sound/soc/blackfin/bf5xx-tdm.c @@ -46,14 +46,6 @@ #include "bf5xx-sport.h" #include "bf5xx-tdm.h" -struct bf5xx_tdm_port { - u16 tcr1; - u16 rcr1; - u16 tcr2; - u16 rcr2; - int configured; -}; - static struct bf5xx_tdm_port bf5xx_tdm; static int sport_num = CONFIG_SND_BF5XX_SPORT_NUM; @@ -181,6 +173,40 @@ static void bf5xx_tdm_shutdown(struct snd_pcm_substream *substream, bf5xx_tdm.configured = 0; } +static int bf5xx_tdm_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) +{ + int i; + unsigned int slot; + unsigned int tx_mapped = 0, rx_mapped = 0; + + if ((tx_num > BFIN_TDM_DAI_MAX_SLOTS) || + (rx_num > BFIN_TDM_DAI_MAX_SLOTS)) + return -EINVAL; + + for (i = 0; i < tx_num; i++) { + slot = tx_slot[i]; + if ((slot < BFIN_TDM_DAI_MAX_SLOTS) && + (!(tx_mapped & (1 << slot)))) { + bf5xx_tdm.tx_map[i] = slot; + tx_mapped |= 1 << slot; + } else + return -EINVAL; + } + for (i = 0; i < rx_num; i++) { + slot = rx_slot[i]; + if ((slot < BFIN_TDM_DAI_MAX_SLOTS) && + (!(rx_mapped & (1 << slot)))) { + bf5xx_tdm.rx_map[i] = slot; + rx_mapped |= 1 << slot; + } else + return -EINVAL; + } + + return 0; +} + #ifdef CONFIG_PM static int bf5xx_tdm_suspend(struct snd_soc_dai *dai) { @@ -235,6 +261,7 @@ static struct snd_soc_dai_ops bf5xx_tdm_dai_ops = { .hw_params = bf5xx_tdm_hw_params, .set_fmt = bf5xx_tdm_set_dai_fmt, .shutdown = bf5xx_tdm_shutdown, + .set_channel_map = bf5xx_tdm_set_channel_map, }; struct snd_soc_dai bf5xx_tdm_dai = { @@ -300,6 +327,8 @@ static int __devinit bfin_tdm_probe(struct platform_device *pdev) pr_err("Failed to register DAI: %d\n", ret); goto sport_config_err; } + + sport_handle->private_data = &bf5xx_tdm; return 0; sport_config_err: diff --git a/sound/soc/blackfin/bf5xx-tdm.h b/sound/soc/blackfin/bf5xx-tdm.h index 618ec3d90cd..04189a18c1b 100644 --- a/sound/soc/blackfin/bf5xx-tdm.h +++ b/sound/soc/blackfin/bf5xx-tdm.h @@ -9,6 +9,17 @@ #ifndef _BF5XX_TDM_H #define _BF5XX_TDM_H +#define BFIN_TDM_DAI_MAX_SLOTS 8 +struct bf5xx_tdm_port { + u16 tcr1; + u16 rcr1; + u16 tcr2; + u16 rcr2; + unsigned int tx_map[BFIN_TDM_DAI_MAX_SLOTS]; + unsigned int rx_map[BFIN_TDM_DAI_MAX_SLOTS]; + int configured; +}; + extern struct snd_soc_dai bf5xx_tdm_dai; #endif diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 0edca93af3b..3c46f34928e 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -19,6 +19,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_AK4104 if SPI_MASTER select SND_SOC_AK4535 if I2C select SND_SOC_AK4642 if I2C + select SND_SOC_AK4671 if I2C select SND_SOC_CS4270 if I2C select SND_SOC_MAX9877 if I2C select SND_SOC_PCM3008 @@ -36,6 +37,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8510 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8523 if I2C select SND_SOC_WM8580 if I2C + select SND_SOC_WM8711 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8728 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8731 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8750 if SND_SOC_I2C_AND_SPI @@ -96,6 +98,9 @@ config SND_SOC_AK4535 config SND_SOC_AK4642 tristate +config SND_SOC_AK4671 + tristate + # Cirrus Logic CS4270 Codec config SND_SOC_CS4270 tristate @@ -160,6 +165,9 @@ config SND_SOC_WM8523 config SND_SOC_WM8580 tristate +config SND_SOC_WM8711 + tristate + config SND_SOC_WM8728 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index fb4af28486b..fc1c458cbe2 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -6,6 +6,7 @@ snd-soc-ad73311-objs := ad73311.o snd-soc-ak4104-objs := ak4104.o snd-soc-ak4535-objs := ak4535.o snd-soc-ak4642-objs := ak4642.o +snd-soc-ak4671-objs := ak4671.o snd-soc-cs4270-objs := cs4270.o snd-soc-cx20442-objs := cx20442.o snd-soc-l3-objs := l3.o @@ -24,6 +25,7 @@ snd-soc-wm8400-objs := wm8400.o snd-soc-wm8510-objs := wm8510.o snd-soc-wm8523-objs := wm8523.o snd-soc-wm8580-objs := wm8580.o +snd-soc-wm8711-objs := wm8711.o snd-soc-wm8728-objs := wm8728.o snd-soc-wm8731-objs := wm8731.o snd-soc-wm8750-objs := wm8750.o @@ -56,6 +58,7 @@ obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o obj-$(CONFIG_SND_SOC_AK4642) += snd-soc-ak4642.o +obj-$(CONFIG_SND_SOC_AK4671) += snd-soc-ak4671.o obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o @@ -74,6 +77,7 @@ obj-$(CONFIG_SND_SOC_WM8400) += snd-soc-wm8400.o obj-$(CONFIG_SND_SOC_WM8510) += snd-soc-wm8510.o obj-$(CONFIG_SND_SOC_WM8523) += snd-soc-wm8523.o obj-$(CONFIG_SND_SOC_WM8580) += snd-soc-wm8580.o +obj-$(CONFIG_SND_SOC_WM8711) += snd-soc-wm8711.o obj-$(CONFIG_SND_SOC_WM8728) += snd-soc-wm8728.o obj-$(CONFIG_SND_SOC_WM8731) += snd-soc-wm8731.o obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o diff --git a/sound/soc/codecs/ak4671.c b/sound/soc/codecs/ak4671.c new file mode 100644 index 00000000000..b61214d1c5d --- /dev/null +++ b/sound/soc/codecs/ak4671.c @@ -0,0 +1,825 @@ +/* + * ak4671.c -- audio driver for AK4671 + * + * Copyright (C) 2009 Samsung Electronics Co.Ltd + * Author: Joonyoung Shim <jy0922.shim@samsung.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/init.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> + +#include "ak4671.h" + +static struct snd_soc_codec *ak4671_codec; + +/* codec private data */ +struct ak4671_priv { + struct snd_soc_codec codec; + u8 reg_cache[AK4671_CACHEREGNUM]; +}; + +/* ak4671 register cache & default register settings */ +static const u8 ak4671_reg[AK4671_CACHEREGNUM] = { + 0x00, /* AK4671_AD_DA_POWER_MANAGEMENT (0x00) */ + 0xf6, /* AK4671_PLL_MODE_SELECT0 (0x01) */ + 0x00, /* AK4671_PLL_MODE_SELECT1 (0x02) */ + 0x02, /* AK4671_FORMAT_SELECT (0x03) */ + 0x00, /* AK4671_MIC_SIGNAL_SELECT (0x04) */ + 0x55, /* AK4671_MIC_AMP_GAIN (0x05) */ + 0x00, /* AK4671_MIXING_POWER_MANAGEMENT0 (0x06) */ + 0x00, /* AK4671_MIXING_POWER_MANAGEMENT1 (0x07) */ + 0xb5, /* AK4671_OUTPUT_VOLUME_CONTROL (0x08) */ + 0x00, /* AK4671_LOUT1_SIGNAL_SELECT (0x09) */ + 0x00, /* AK4671_ROUT1_SIGNAL_SELECT (0x0a) */ + 0x00, /* AK4671_LOUT2_SIGNAL_SELECT (0x0b) */ + 0x00, /* AK4671_ROUT2_SIGNAL_SELECT (0x0c) */ + 0x00, /* AK4671_LOUT3_SIGNAL_SELECT (0x0d) */ + 0x00, /* AK4671_ROUT3_SIGNAL_SELECT (0x0e) */ + 0x00, /* AK4671_LOUT1_POWER_MANAGERMENT (0x0f) */ + 0x00, /* AK4671_LOUT2_POWER_MANAGERMENT (0x10) */ + 0x80, /* AK4671_LOUT3_POWER_MANAGERMENT (0x11) */ + 0x91, /* AK4671_LCH_INPUT_VOLUME_CONTROL (0x12) */ + 0x91, /* AK4671_RCH_INPUT_VOLUME_CONTROL (0x13) */ + 0xe1, /* AK4671_ALC_REFERENCE_SELECT (0x14) */ + 0x00, /* AK4671_DIGITAL_MIXING_CONTROL (0x15) */ + 0x00, /* AK4671_ALC_TIMER_SELECT (0x16) */ + 0x00, /* AK4671_ALC_MODE_CONTROL (0x17) */ + 0x02, /* AK4671_MODE_CONTROL1 (0x18) */ + 0x01, /* AK4671_MODE_CONTROL2 (0x19) */ + 0x18, /* AK4671_LCH_OUTPUT_VOLUME_CONTROL (0x1a) */ + 0x18, /* AK4671_RCH_OUTPUT_VOLUME_CONTROL (0x1b) */ + 0x00, /* AK4671_SIDETONE_A_CONTROL (0x1c) */ + 0x02, /* AK4671_DIGITAL_FILTER_SELECT (0x1d) */ + 0x00, /* AK4671_FIL3_COEFFICIENT0 (0x1e) */ + 0x00, /* AK4671_FIL3_COEFFICIENT1 (0x1f) */ + 0x00, /* AK4671_FIL3_COEFFICIENT2 (0x20) */ + 0x00, /* AK4671_FIL3_COEFFICIENT3 (0x21) */ + 0x00, /* AK4671_EQ_COEFFICIENT0 (0x22) */ + 0x00, /* AK4671_EQ_COEFFICIENT1 (0x23) */ + 0x00, /* AK4671_EQ_COEFFICIENT2 (0x24) */ + 0x00, /* AK4671_EQ_COEFFICIENT3 (0x25) */ + 0x00, /* AK4671_EQ_COEFFICIENT4 (0x26) */ + 0x00, /* AK4671_EQ_COEFFICIENT5 (0x27) */ + 0xa9, /* AK4671_FIL1_COEFFICIENT0 (0x28) */ + 0x1f, /* AK4671_FIL1_COEFFICIENT1 (0x29) */ + 0xad, /* AK4671_FIL1_COEFFICIENT2 (0x2a) */ + 0x20, /* AK4671_FIL1_COEFFICIENT3 (0x2b) */ + 0x00, /* AK4671_FIL2_COEFFICIENT0 (0x2c) */ + 0x00, /* AK4671_FIL2_COEFFICIENT1 (0x2d) */ + 0x00, /* AK4671_FIL2_COEFFICIENT2 (0x2e) */ + 0x00, /* AK4671_FIL2_COEFFICIENT3 (0x2f) */ + 0x00, /* AK4671_DIGITAL_FILTER_SELECT2 (0x30) */ + 0x00, /* this register not used */ + 0x00, /* AK4671_E1_COEFFICIENT0 (0x32) */ + 0x00, /* AK4671_E1_COEFFICIENT1 (0x33) */ + 0x00, /* AK4671_E1_COEFFICIENT2 (0x34) */ + 0x00, /* AK4671_E1_COEFFICIENT3 (0x35) */ + 0x00, /* AK4671_E1_COEFFICIENT4 (0x36) */ + 0x00, /* AK4671_E1_COEFFICIENT5 (0x37) */ + 0x00, /* AK4671_E2_COEFFICIENT0 (0x38) */ + 0x00, /* AK4671_E2_COEFFICIENT1 (0x39) */ + 0x00, /* AK4671_E2_COEFFICIENT2 (0x3a) */ + 0x00, /* AK4671_E2_COEFFICIENT3 (0x3b) */ + 0x00, /* AK4671_E2_COEFFICIENT4 (0x3c) */ + 0x00, /* AK4671_E2_COEFFICIENT5 (0x3d) */ + 0x00, /* AK4671_E3_COEFFICIENT0 (0x3e) */ + 0x00, /* AK4671_E3_COEFFICIENT1 (0x3f) */ + 0x00, /* AK4671_E3_COEFFICIENT2 (0x40) */ + 0x00, /* AK4671_E3_COEFFICIENT3 (0x41) */ + 0x00, /* AK4671_E3_COEFFICIENT4 (0x42) */ + 0x00, /* AK4671_E3_COEFFICIENT5 (0x43) */ + 0x00, /* AK4671_E4_COEFFICIENT0 (0x44) */ + 0x00, /* AK4671_E4_COEFFICIENT1 (0x45) */ + 0x00, /* AK4671_E4_COEFFICIENT2 (0x46) */ + 0x00, /* AK4671_E4_COEFFICIENT3 (0x47) */ + 0x00, /* AK4671_E4_COEFFICIENT4 (0x48) */ + 0x00, /* AK4671_E4_COEFFICIENT5 (0x49) */ + 0x00, /* AK4671_E5_COEFFICIENT0 (0x4a) */ + 0x00, /* AK4671_E5_COEFFICIENT1 (0x4b) */ + 0x00, /* AK4671_E5_COEFFICIENT2 (0x4c) */ + 0x00, /* AK4671_E5_COEFFICIENT3 (0x4d) */ + 0x00, /* AK4671_E5_COEFFICIENT4 (0x4e) */ + 0x00, /* AK4671_E5_COEFFICIENT5 (0x4f) */ + 0x88, /* AK4671_EQ_CONTROL_250HZ_100HZ (0x50) */ + 0x88, /* AK4671_EQ_CONTROL_3500HZ_1KHZ (0x51) */ + 0x08, /* AK4671_EQ_CONTRO_10KHZ (0x52) */ + 0x00, /* AK4671_PCM_IF_CONTROL0 (0x53) */ + 0x00, /* AK4671_PCM_IF_CONTROL1 (0x54) */ + 0x00, /* AK4671_PCM_IF_CONTROL2 (0x55) */ + 0x18, /* AK4671_DIGITAL_VOLUME_B_CONTROL (0x56) */ + 0x18, /* AK4671_DIGITAL_VOLUME_C_CONTROL (0x57) */ + 0x00, /* AK4671_SIDETONE_VOLUME_CONTROL (0x58) */ + 0x00, /* AK4671_DIGITAL_MIXING_CONTROL2 (0x59) */ + 0x00, /* AK4671_SAR_ADC_CONTROL (0x5a) */ +}; + +/* + * LOUT1/ROUT1 output volume control: + * from -24 to 6 dB in 6 dB steps (mute instead of -30 dB) + */ +static DECLARE_TLV_DB_SCALE(out1_tlv, -3000, 600, 1); + +/* + * LOUT2/ROUT2 output volume control: + * from -33 to 6 dB in 3 dB steps (mute instead of -33 dB) + */ +static DECLARE_TLV_DB_SCALE(out2_tlv, -3300, 300, 1); + +/* + * LOUT3/ROUT3 output volume control: + * from -6 to 3 dB in 3 dB steps + */ +static DECLARE_TLV_DB_SCALE(out3_tlv, -600, 300, 0); + +/* + * Mic amp gain control: + * from -15 to 30 dB in 3 dB steps + * REVISIT: The actual min value(0x01) is -12 dB and the reg value 0x00 is not + * available + */ +static DECLARE_TLV_DB_SCALE(mic_amp_tlv, -1500, 300, 0); + +static const struct snd_kcontrol_new ak4671_snd_controls[] = { + /* Common playback gain controls */ + SOC_SINGLE_TLV("Line Output1 Playback Volume", + AK4671_OUTPUT_VOLUME_CONTROL, 0, 0x6, 0, out1_tlv), + SOC_SINGLE_TLV("Headphone Output2 Playback Volume", + AK4671_OUTPUT_VOLUME_CONTROL, 4, 0xd, 0, out2_tlv), + SOC_SINGLE_TLV("Line Output3 Playback Volume", + AK4671_LOUT3_POWER_MANAGERMENT, 6, 0x3, 0, out3_tlv), + + /* Common capture gain controls */ + SOC_DOUBLE_TLV("Mic Amp Capture Volume", + AK4671_MIC_AMP_GAIN, 0, 4, 0xf, 0, mic_amp_tlv), +}; + +/* event handlers */ +static int ak4671_out2_event(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *kcontrol, int event) +{ + struct snd_soc_codec *codec = w->codec; + u8 reg; + + switch (event) { + case SND_SOC_DAPM_POST_PMU: + reg = snd_soc_read(codec, AK4671_LOUT2_POWER_MANAGERMENT); + reg |= AK4671_MUTEN; + snd_soc_write(codec, AK4671_LOUT2_POWER_MANAGERMENT, reg); + break; + case SND_SOC_DAPM_PRE_PMD: + reg = snd_soc_read(codec, AK4671_LOUT2_POWER_MANAGERMENT); + reg &= ~AK4671_MUTEN; + snd_soc_write(codec, AK4671_LOUT2_POWER_MANAGERMENT, reg); + break; + } + + return 0; +} + +/* Output Mixers */ +static const struct snd_kcontrol_new ak4671_lout1_mixer_controls[] = { + SOC_DAPM_SINGLE("DACL", AK4671_LOUT1_SIGNAL_SELECT, 0, 1, 0), + SOC_DAPM_SINGLE("LINL1", AK4671_LOUT1_SIGNAL_SELECT, 1, 1, 0), + SOC_DAPM_SINGLE("LINL2", AK4671_LOUT1_SIGNAL_SELECT, 2, 1, 0), + SOC_DAPM_SINGLE("LINL3", AK4671_LOUT1_SIGNAL_SELECT, 3, 1, 0), + SOC_DAPM_SINGLE("LINL4", AK4671_LOUT1_SIGNAL_SELECT, 4, 1, 0), + SOC_DAPM_SINGLE("LOOPL", AK4671_LOUT1_SIGNAL_SELECT, 5, 1, 0), +}; + +static const struct snd_kcontrol_new ak4671_rout1_mixer_controls[] = { + SOC_DAPM_SINGLE("DACR", AK4671_ROUT1_SIGNAL_SELECT, 0, 1, 0), + SOC_DAPM_SINGLE("RINR1", AK4671_ROUT1_SIGNAL_SELECT, 1, 1, 0), + SOC_DAPM_SINGLE("RINR2", AK4671_ROUT1_SIGNAL_SELECT, 2, 1, 0), + SOC_DAPM_SINGLE("RINR3", AK4671_ROUT1_SIGNAL_SELECT, 3, 1, 0), + SOC_DAPM_SINGLE("RINR4", AK4671_ROUT1_SIGNAL_SELECT, 4, 1, 0), + SOC_DAPM_SINGLE("LOOPR", AK4671_ROUT1_SIGNAL_SELECT, 5, 1, 0), +}; + +static const struct snd_kcontrol_new ak4671_lout2_mixer_controls[] = { + SOC_DAPM_SINGLE("DACHL", AK4671_LOUT2_SIGNAL_SELECT, 0, 1, 0), + SOC_DAPM_SINGLE("LINH1", AK4671_LOUT2_SIGNAL_SELECT, 1, 1, 0), + SOC_DAPM_SINGLE("LINH2", AK4671_LOUT2_SIGNAL_SELECT, 2, 1, 0), + SOC_DAPM_SINGLE("LINH3", AK4671_LOUT2_SIGNAL_SELECT, 3, 1, 0), + SOC_DAPM_SINGLE("LINH4", AK4671_LOUT2_SIGNAL_SELECT, 4, 1, 0), + SOC_DAPM_SINGLE("LOOPHL", AK4671_LOUT2_SIGNAL_SELECT, 5, 1, 0), +}; + +static const struct snd_kcontrol_new ak4671_rout2_mixer_controls[] = { + SOC_DAPM_SINGLE("DACHR", AK4671_ROUT2_SIGNAL_SELECT, 0, 1, 0), + SOC_DAPM_SINGLE("RINH1", AK4671_ROUT2_SIGNAL_SELECT, 1, 1, 0), + SOC_DAPM_SINGLE("RINH2", AK4671_ROUT2_SIGNAL_SELECT, 2, 1, 0), + SOC_DAPM_SINGLE("RINH3", AK4671_ROUT2_SIGNAL_SELECT, 3, 1, 0), + SOC_DAPM_SINGLE("RINH4", AK4671_ROUT2_SIGNAL_SELECT, 4, 1, 0), + SOC_DAPM_SINGLE("LOOPHR", AK4671_ROUT2_SIGNAL_SELECT, 5, 1, 0), +}; + +static const struct snd_kcontrol_new ak4671_lout3_mixer_controls[] = { + SOC_DAPM_SINGLE("DACSL", AK4671_LOUT3_SIGNAL_SELECT, 0, 1, 0), + SOC_DAPM_SINGLE("LINS1", AK4671_LOUT3_SIGNAL_SELECT, 1, 1, 0), + SOC_DAPM_SINGLE("LINS2", AK4671_LOUT3_SIGNAL_SELECT, 2, 1, 0), + SOC_DAPM_SINGLE("LINS3", AK4671_LOUT3_SIGNAL_SELECT, 3, 1, 0), + SOC_DAPM_SINGLE("LINS4", AK4671_LOUT3_SIGNAL_SELECT, 4, 1, 0), + SOC_DAPM_SINGLE("LOOPSL", AK4671_LOUT3_SIGNAL_SELECT, 5, 1, 0), +}; + +static const struct snd_kcontrol_new ak4671_rout3_mixer_controls[] = { + SOC_DAPM_SINGLE("DACSR", AK4671_ROUT3_SIGNAL_SELECT, 0, 1, 0), + SOC_DAPM_SINGLE("RINS1", AK4671_ROUT3_SIGNAL_SELECT, 1, 1, 0), + SOC_DAPM_SINGLE("RINS2", AK4671_ROUT3_SIGNAL_SELECT, 2, 1, 0), + SOC_DAPM_SINGLE("RINS3", AK4671_ROUT3_SIGNAL_SELECT, 3, 1, 0), + SOC_DAPM_SINGLE("RINS4", AK4671_ROUT3_SIGNAL_SELECT, 4, 1, 0), + SOC_DAPM_SINGLE("LOOPSR", AK4671_ROUT3_SIGNAL_SELECT, 5, 1, 0), +}; + +/* Input MUXs */ +static const char *ak4671_lin_mux_texts[] = + {"LIN1", "LIN2", "LIN3", "LIN4"}; +static const struct soc_enum ak4671_lin_mux_enum = + SOC_ENUM_SINGLE(AK4671_MIC_SIGNAL_SELECT, 0, + ARRAY_SIZE(ak4671_lin_mux_texts), + ak4671_lin_mux_texts); +static const struct snd_kcontrol_new ak4671_lin_mux_control = + SOC_DAPM_ENUM("Route", ak4671_lin_mux_enum); + +static const char *ak4671_rin_mux_texts[] = + {"RIN1", "RIN2", "RIN3", "RIN4"}; +static const struct soc_enum ak4671_rin_mux_enum = + SOC_ENUM_SINGLE(AK4671_MIC_SIGNAL_SELECT, 2, + ARRAY_SIZE(ak4671_rin_mux_texts), + ak4671_rin_mux_texts); +static const struct snd_kcontrol_new ak4671_rin_mux_control = + SOC_DAPM_ENUM("Route", ak4671_rin_mux_enum); + +static const struct snd_soc_dapm_widget ak4671_dapm_widgets[] = { + /* Inputs */ + SND_SOC_DAPM_INPUT("LIN1"), + SND_SOC_DAPM_INPUT("RIN1"), + SND_SOC_DAPM_INPUT("LIN2"), + SND_SOC_DAPM_INPUT("RIN2"), + SND_SOC_DAPM_INPUT("LIN3"), + SND_SOC_DAPM_INPUT("RIN3"), + SND_SOC_DAPM_INPUT("LIN4"), + SND_SOC_DAPM_INPUT("RIN4"), + + /* Outputs */ + SND_SOC_DAPM_OUTPUT("LOUT1"), + SND_SOC_DAPM_OUTPUT("ROUT1"), + SND_SOC_DAPM_OUTPUT("LOUT2"), + SND_SOC_DAPM_OUTPUT("ROUT2"), + SND_SOC_DAPM_OUTPUT("LOUT3"), + SND_SOC_DAPM_OUTPUT("ROUT3"), + + /* DAC */ + SND_SOC_DAPM_DAC("DAC Left", "Left HiFi Playback", + AK4671_AD_DA_POWER_MANAGEMENT, 6, 0), + SND_SOC_DAPM_DAC("DAC Right", "Right HiFi Playback", + AK4671_AD_DA_POWER_MANAGEMENT, 7, 0), + + /* ADC */ + SND_SOC_DAPM_ADC("ADC Left", "Left HiFi Capture", + AK4671_AD_DA_POWER_MANAGEMENT, 4, 0), + SND_SOC_DAPM_ADC("ADC Right", "Right HiFi Capture", + AK4671_AD_DA_POWER_MANAGEMENT, 5, 0), + + /* PGA */ + SND_SOC_DAPM_PGA("LOUT2 Mix Amp", + AK4671_LOUT2_POWER_MANAGERMENT, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("ROUT2 Mix Amp", + AK4671_LOUT2_POWER_MANAGERMENT, 6, 0, NULL, 0), + + SND_SOC_DAPM_PGA("LIN1 Mixing Circuit", + AK4671_MIXING_POWER_MANAGEMENT1, 0, 0, NULL, 0), + SND_SOC_DAPM_PGA("RIN1 Mixing Circuit", + AK4671_MIXING_POWER_MANAGEMENT1, 1, 0, NULL, 0), + SND_SOC_DAPM_PGA("LIN2 Mixing Circuit", + AK4671_MIXING_POWER_MANAGEMENT1, 2, 0, NULL, 0), + SND_SOC_DAPM_PGA("RIN2 Mixing Circuit", + AK4671_MIXING_POWER_MANAGEMENT1, 3, 0, NULL, 0), + SND_SOC_DAPM_PGA("LIN3 Mixing Circuit", + AK4671_MIXING_POWER_MANAGEMENT1, 4, 0, NULL, 0), + SND_SOC_DAPM_PGA("RIN3 Mixing Circuit", + AK4671_MIXING_POWER_MANAGEMENT1, 5, 0, NULL, 0), + SND_SOC_DAPM_PGA("LIN4 Mixing Circuit", + AK4671_MIXING_POWER_MANAGEMENT1, 6, 0, NULL, 0), + SND_SOC_DAPM_PGA("RIN4 Mixing Circuit", + AK4671_MIXING_POWER_MANAGEMENT1, 7, 0, NULL, 0), + + /* Output Mixers */ + SND_SOC_DAPM_MIXER("LOUT1 Mixer", AK4671_LOUT1_POWER_MANAGERMENT, 0, 0, + &ak4671_lout1_mixer_controls[0], + ARRAY_SIZE(ak4671_lout1_mixer_controls)), + SND_SOC_DAPM_MIXER("ROUT1 Mixer", AK4671_LOUT1_POWER_MANAGERMENT, 1, 0, + &ak4671_rout1_mixer_controls[0], + ARRAY_SIZE(ak4671_rout1_mixer_controls)), + SND_SOC_DAPM_MIXER_E("LOUT2 Mixer", AK4671_LOUT2_POWER_MANAGERMENT, + 0, 0, &ak4671_lout2_mixer_controls[0], + ARRAY_SIZE(ak4671_lout2_mixer_controls), + ak4671_out2_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MIXER_E("ROUT2 Mixer", AK4671_LOUT2_POWER_MANAGERMENT, + 1, 0, &ak4671_rout2_mixer_controls[0], + ARRAY_SIZE(ak4671_rout2_mixer_controls), + ak4671_out2_event, + SND_SOC_DAPM_POST_PMU|SND_SOC_DAPM_PRE_PMD), + SND_SOC_DAPM_MIXER("LOUT3 Mixer", AK4671_LOUT3_POWER_MANAGERMENT, 0, 0, + &ak4671_lout3_mixer_controls[0], + ARRAY_SIZE(ak4671_lout3_mixer_controls)), + SND_SOC_DAPM_MIXER("ROUT3 Mixer", AK4671_LOUT3_POWER_MANAGERMENT, 1, 0, + &ak4671_rout3_mixer_controls[0], + ARRAY_SIZE(ak4671_rout3_mixer_controls)), + + /* Input MUXs */ + SND_SOC_DAPM_MUX("LIN MUX", AK4671_AD_DA_POWER_MANAGEMENT, 2, 0, + &ak4671_lin_mux_control), + SND_SOC_DAPM_MUX("RIN MUX", AK4671_AD_DA_POWER_MANAGEMENT, 3, 0, + &ak4671_rin_mux_control), + + /* Mic Power */ + SND_SOC_DAPM_MICBIAS("Mic Bias", AK4671_AD_DA_POWER_MANAGEMENT, 1, 0), + + /* Supply */ + SND_SOC_DAPM_SUPPLY("PMPLL", AK4671_PLL_MODE_SELECT1, 0, 0, NULL, 0), +}; + +static const struct snd_soc_dapm_route intercon[] = { + {"DAC Left", "NULL", "PMPLL"}, + {"DAC Right", "NULL", "PMPLL"}, + {"ADC Left", "NULL", "PMPLL"}, + {"ADC Right", "NULL", "PMPLL"}, + + /* Outputs */ + {"LOUT1", "NULL", "LOUT1 Mixer"}, + {"ROUT1", "NULL", "ROUT1 Mixer"}, + {"LOUT2", "NULL", "LOUT2 Mix Amp"}, + {"ROUT2", "NULL", "ROUT2 Mix Amp"}, + {"LOUT3", "NULL", "LOUT3 Mixer"}, + {"ROUT3", "NULL", "ROUT3 Mixer"}, + + {"LOUT1 Mixer", "DACL", "DAC Left"}, + {"ROUT1 Mixer", "DACR", "DAC Right"}, + {"LOUT2 Mixer", "DACHL", "DAC Left"}, + {"ROUT2 Mixer", "DACHR", "DAC Right"}, + {"LOUT2 Mix Amp", "NULL", "LOUT2 Mixer"}, + {"ROUT2 Mix Amp", "NULL", "ROUT2 Mixer"}, + {"LOUT3 Mixer", "DACSL", "DAC Left"}, + {"ROUT3 Mixer", "DACSR", "DAC Right"}, + + /* Inputs */ + {"LIN MUX", "LIN1", "LIN1"}, + {"LIN MUX", "LIN2", "LIN2"}, + {"LIN MUX", "LIN3", "LIN3"}, + {"LIN MUX", "LIN4", "LIN4"}, + + {"RIN MUX", "RIN1", "RIN1"}, + {"RIN MUX", "RIN2", "RIN2"}, + {"RIN MUX", "RIN3", "RIN3"}, + {"RIN MUX", "RIN4", "RIN4"}, + + {"LIN1", NULL, "Mic Bias"}, + {"RIN1", NULL, "Mic Bias"}, + {"LIN2", NULL, "Mic Bias"}, + {"RIN2", NULL, "Mic Bias"}, + + {"ADC Left", "NULL", "LIN MUX"}, + {"ADC Right", "NULL", "RIN MUX"}, + + /* Analog Loops */ + {"LIN1 Mixing Circuit", "NULL", "LIN1"}, + {"RIN1 Mixing Circuit", "NULL", "RIN1"}, + {"LIN2 Mixing Circuit", "NULL", "LIN2"}, + {"RIN2 Mixing Circuit", "NULL", "RIN2"}, + {"LIN3 Mixing Circuit", "NULL", "LIN3"}, + {"RIN3 Mixing Circuit", "NULL", "RIN3"}, + {"LIN4 Mixing Circuit", "NULL", "LIN4"}, + {"RIN4 Mixing Circuit", "NULL", "RIN4"}, + + {"LOUT1 Mixer", "LINL1", "LIN1 Mixing Circuit"}, + {"ROUT1 Mixer", "RINR1", "RIN1 Mixing Circuit"}, + {"LOUT2 Mixer", "LINH1", "LIN1 Mixing Circuit"}, + {"ROUT2 Mixer", "RINH1", "RIN1 Mixing Circuit"}, + {"LOUT3 Mixer", "LINS1", "LIN1 Mixing Circuit"}, + {"ROUT3 Mixer", "RINS1", "RIN1 Mixing Circuit"}, + + {"LOUT1 Mixer", "LINL2", "LIN2 Mixing Circuit"}, + {"ROUT1 Mixer", "RINR2", "RIN2 Mixing Circuit"}, + {"LOUT2 Mixer", "LINH2", "LIN2 Mixing Circuit"}, + {"ROUT2 Mixer", "RINH2", "RIN2 Mixing Circuit"}, + {"LOUT3 Mixer", "LINS2", "LIN2 Mixing Circuit"}, + {"ROUT3 Mixer", "RINS2", "RIN2 Mixing Circuit"}, + + {"LOUT1 Mixer", "LINL3", "LIN3 Mixing Circuit"}, + {"ROUT1 Mixer", "RINR3", "RIN3 Mixing Circuit"}, + {"LOUT2 Mixer", "LINH3", "LIN3 Mixing Circuit"}, + {"ROUT2 Mixer", "RINH3", "RIN3 Mixing Circuit"}, + {"LOUT3 Mixer", "LINS3", "LIN3 Mixing Circuit"}, + {"ROUT3 Mixer", "RINS3", "RIN3 Mixing Circuit"}, + + {"LOUT1 Mixer", "LINL4", "LIN4 Mixing Circuit"}, + {"ROUT1 Mixer", "RINR4", "RIN4 Mixing Circuit"}, + {"LOUT2 Mixer", "LINH4", "LIN4 Mixing Circuit"}, + {"ROUT2 Mixer", "RINH4", "RIN4 Mixing Circuit"}, + {"LOUT3 Mixer", "LINS4", "LIN4 Mixing Circuit"}, + {"ROUT3 Mixer", "RINS4", "RIN4 Mixing Circuit"}, +}; + +static int ak4671_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, ak4671_dapm_widgets, + ARRAY_SIZE(ak4671_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +static int ak4671_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; + u8 fs; + + fs = snd_soc_read(codec, AK4671_PLL_MODE_SELECT0); + fs &= ~AK4671_FS; + + switch (params_rate(params)) { + case 8000: + fs |= AK4671_FS_8KHZ; + break; + case 12000: + fs |= AK4671_FS_12KHZ; + break; + case 16000: + fs |= AK4671_FS_16KHZ; + break; + case 24000: + fs |= AK4671_FS_24KHZ; + break; + case 11025: + fs |= AK4671_FS_11_025KHZ; + break; + case 22050: + fs |= AK4671_FS_22_05KHZ; + break; + case 32000: + fs |= AK4671_FS_32KHZ; + break; + case 44100: + fs |= AK4671_FS_44_1KHZ; + break; + case 48000: + fs |= AK4671_FS_48KHZ; + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, AK4671_PLL_MODE_SELECT0, fs); + + return 0; +} + +static int ak4671_set_dai_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = dai->codec; + u8 pll; + + pll = snd_soc_read(codec, AK4671_PLL_MODE_SELECT0); + pll &= ~AK4671_PLL; + + switch (freq) { + case 11289600: + pll |= AK4671_PLL_11_2896MHZ; + break; + case 12000000: + pll |= AK4671_PLL_12MHZ; + break; + case 12288000: + pll |= AK4671_PLL_12_288MHZ; + break; + case 13000000: + pll |= AK4671_PLL_13MHZ; + break; + case 13500000: + pll |= AK4671_PLL_13_5MHZ; + break; + case 19200000: + pll |= AK4671_PLL_19_2MHZ; + break; + case 24000000: + pll |= AK4671_PLL_24MHZ; + break; + case 26000000: + pll |= AK4671_PLL_26MHZ; + break; + case 27000000: + pll |= AK4671_PLL_27MHZ; + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, AK4671_PLL_MODE_SELECT0, pll); + + return 0; +} + +static int ak4671_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + u8 mode; + u8 format; + + /* set master/slave audio interface */ + mode = snd_soc_read(codec, AK4671_PLL_MODE_SELECT1); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + mode |= AK4671_M_S; + break; + case SND_SOC_DAIFMT_CBM_CFS: + mode &= ~(AK4671_M_S); + break; + default: + return -EINVAL; + } + + /* interface format */ + format = snd_soc_read(codec, AK4671_FORMAT_SELECT); + format &= ~AK4671_DIF; + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + format |= AK4671_DIF_I2S_MODE; + break; + case SND_SOC_DAIFMT_LEFT_J: + format |= AK4671_DIF_MSB_MODE; + break; + case SND_SOC_DAIFMT_DSP_A: + format |= AK4671_DIF_DSP_MODE; + format |= AK4671_BCKP; + format |= AK4671_MSBS; + break; + default: + return -EINVAL; + } + + /* set mode and format */ + snd_soc_write(codec, AK4671_PLL_MODE_SELECT1, mode); + snd_soc_write(codec, AK4671_FORMAT_SELECT, format); + + return 0; +} + +static int ak4671_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u8 reg; + + switch (level) { + case SND_SOC_BIAS_ON: + case SND_SOC_BIAS_PREPARE: + case SND_SOC_BIAS_STANDBY: + reg = snd_soc_read(codec, AK4671_AD_DA_POWER_MANAGEMENT); + snd_soc_write(codec, AK4671_AD_DA_POWER_MANAGEMENT, + reg | AK4671_PMVCM); + break; + case SND_SOC_BIAS_OFF: + snd_soc_write(codec, AK4671_AD_DA_POWER_MANAGEMENT, 0x00); + break; + } + codec->bias_level = level; + return 0; +} + +#define AK4671_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\ + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 |\ + SNDRV_PCM_RATE_48000) + +#define AK4671_FORMATS SNDRV_PCM_FMTBIT_S16_LE + +static struct snd_soc_dai_ops ak4671_dai_ops = { + .hw_params = ak4671_hw_params, + .set_sysclk = ak4671_set_dai_sysclk, + .set_fmt = ak4671_set_dai_fmt, +}; + +struct snd_soc_dai ak4671_dai = { + .name = "AK4671", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AK4671_RATES, + .formats = AK4671_FORMATS,}, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = AK4671_RATES, + .formats = AK4671_FORMATS,}, + .ops = &ak4671_dai_ops, +}; +EXPORT_SYMBOL_GPL(ak4671_dai); + +static int ak4671_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (ak4671_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = ak4671_codec; + codec = ak4671_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + snd_soc_add_controls(codec, ak4671_snd_controls, + ARRAY_SIZE(ak4671_snd_controls)); + ak4671_add_widgets(codec); + + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card: %d\n", ret); + goto card_err; + } + + ak4671_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + return ret; +} + +static int ak4671_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_ak4671 = { + .probe = ak4671_probe, + .remove = ak4671_remove, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_ak4671); + +static int ak4671_register(struct ak4671_priv *ak4671, + enum snd_soc_control_type control) +{ + int ret; + struct snd_soc_codec *codec = &ak4671->codec; + + if (ak4671_codec) { + dev_err(codec->dev, "Another AK4671 is registered\n"); + ret = -EINVAL; + goto err; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = ak4671; + codec->name = "AK4671"; + codec->owner = THIS_MODULE; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = ak4671_set_bias_level; + codec->dai = &ak4671_dai; + codec->num_dai = 1; + codec->reg_cache_size = AK4671_CACHEREGNUM; + codec->reg_cache = &ak4671->reg_cache; + + memcpy(codec->reg_cache, ak4671_reg, sizeof(ak4671_reg)); + + ret = snd_soc_codec_set_cache_io(codec, 8, 8, control); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + goto err; + } + + ak4671_dai.dev = codec->dev; + ak4671_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + goto err; + } + + ret = snd_soc_register_dai(&ak4671_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + goto err_codec; + } + + return 0; + +err_codec: + snd_soc_unregister_codec(codec); +err: + kfree(ak4671); + return ret; +} + +static void ak4671_unregister(struct ak4671_priv *ak4671) +{ + ak4671_set_bias_level(&ak4671->codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dai(&ak4671_dai); + snd_soc_unregister_codec(&ak4671->codec); + kfree(ak4671); + ak4671_codec = NULL; +} + +static int __devinit ak4671_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ak4671_priv *ak4671; + struct snd_soc_codec *codec; + + ak4671 = kzalloc(sizeof(struct ak4671_priv), GFP_KERNEL); + if (ak4671 == NULL) + return -ENOMEM; + + codec = &ak4671->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + + i2c_set_clientdata(client, ak4671); + codec->control_data = client; + + codec->dev = &client->dev; + + return ak4671_register(ak4671, SND_SOC_I2C); +} + +static __devexit int ak4671_i2c_remove(struct i2c_client *client) +{ + struct ak4671_priv *ak4671 = i2c_get_clientdata(client); + + ak4671_unregister(ak4671); + + return 0; +} + +static const struct i2c_device_id ak4671_i2c_id[] = { + { "ak4671", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ak4671_i2c_id); + +static struct i2c_driver ak4671_i2c_driver = { + .driver = { + .name = "ak4671", + .owner = THIS_MODULE, + }, + .probe = ak4671_i2c_probe, + .remove = __devexit_p(ak4671_i2c_remove), + .id_table = ak4671_i2c_id, +}; + +static int __init ak4671_modinit(void) +{ + return i2c_add_driver(&ak4671_i2c_driver); +} +module_init(ak4671_modinit); + +static void __exit ak4671_exit(void) +{ + i2c_del_driver(&ak4671_i2c_driver); +} +module_exit(ak4671_exit); + +MODULE_DESCRIPTION("ASoC AK4671 codec driver"); +MODULE_AUTHOR("Joonyoung Shim <jy0922.shim@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/ak4671.h b/sound/soc/codecs/ak4671.h new file mode 100644 index 00000000000..e2fad964e88 --- /dev/null +++ b/sound/soc/codecs/ak4671.h @@ -0,0 +1,156 @@ +/* + * ak4671.h -- audio driver for AK4671 + * + * Copyright (C) 2009 Samsung Electronics Co.Ltd + * Author: Joonyoung Shim <jy0922.shim@samsung.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 _AK4671_H +#define _AK4671_H + +#define AK4671_AD_DA_POWER_MANAGEMENT 0x00 +#define AK4671_PLL_MODE_SELECT0 0x01 +#define AK4671_PLL_MODE_SELECT1 0x02 +#define AK4671_FORMAT_SELECT 0x03 +#define AK4671_MIC_SIGNAL_SELECT 0x04 +#define AK4671_MIC_AMP_GAIN 0x05 +#define AK4671_MIXING_POWER_MANAGEMENT0 0x06 +#define AK4671_MIXING_POWER_MANAGEMENT1 0x07 +#define AK4671_OUTPUT_VOLUME_CONTROL 0x08 +#define AK4671_LOUT1_SIGNAL_SELECT 0x09 +#define AK4671_ROUT1_SIGNAL_SELECT 0x0a +#define AK4671_LOUT2_SIGNAL_SELECT 0x0b +#define AK4671_ROUT2_SIGNAL_SELECT 0x0c +#define AK4671_LOUT3_SIGNAL_SELECT 0x0d +#define AK4671_ROUT3_SIGNAL_SELECT 0x0e +#define AK4671_LOUT1_POWER_MANAGERMENT 0x0f +#define AK4671_LOUT2_POWER_MANAGERMENT 0x10 +#define AK4671_LOUT3_POWER_MANAGERMENT 0x11 +#define AK4671_LCH_INPUT_VOLUME_CONTROL 0x12 +#define AK4671_RCH_INPUT_VOLUME_CONTROL 0x13 +#define AK4671_ALC_REFERENCE_SELECT 0x14 +#define AK4671_DIGITAL_MIXING_CONTROL 0x15 +#define AK4671_ALC_TIMER_SELECT 0x16 +#define AK4671_ALC_MODE_CONTROL 0x17 +#define AK4671_MODE_CONTROL1 0x18 +#define AK4671_MODE_CONTROL2 0x19 +#define AK4671_LCH_OUTPUT_VOLUME_CONTROL 0x1a +#define AK4671_RCH_OUTPUT_VOLUME_CONTROL 0x1b +#define AK4671_SIDETONE_A_CONTROL 0x1c +#define AK4671_DIGITAL_FILTER_SELECT 0x1d +#define AK4671_FIL3_COEFFICIENT0 0x1e +#define AK4671_FIL3_COEFFICIENT1 0x1f +#define AK4671_FIL3_COEFFICIENT2 0x20 +#define AK4671_FIL3_COEFFICIENT3 0x21 +#define AK4671_EQ_COEFFICIENT0 0x22 +#define AK4671_EQ_COEFFICIENT1 0x23 +#define AK4671_EQ_COEFFICIENT2 0x24 +#define AK4671_EQ_COEFFICIENT3 0x25 +#define AK4671_EQ_COEFFICIENT4 0x26 +#define AK4671_EQ_COEFFICIENT5 0x27 +#define AK4671_FIL1_COEFFICIENT0 0x28 +#define AK4671_FIL1_COEFFICIENT1 0x29 +#define AK4671_FIL1_COEFFICIENT2 0x2a +#define AK4671_FIL1_COEFFICIENT3 0x2b +#define AK4671_FIL2_COEFFICIENT0 0x2c +#define AK4671_FIL2_COEFFICIENT1 0x2d +#define AK4671_FIL2_COEFFICIENT2 0x2e +#define AK4671_FIL2_COEFFICIENT3 0x2f +#define AK4671_DIGITAL_FILTER_SELECT2 0x30 +#define AK4671_E1_COEFFICIENT0 0x32 +#define AK4671_E1_COEFFICIENT1 0x33 +#define AK4671_E1_COEFFICIENT2 0x34 +#define AK4671_E1_COEFFICIENT3 0x35 +#define AK4671_E1_COEFFICIENT4 0x36 +#define AK4671_E1_COEFFICIENT5 0x37 +#define AK4671_E2_COEFFICIENT0 0x38 +#define AK4671_E2_COEFFICIENT1 0x39 +#define AK4671_E2_COEFFICIENT2 0x3a +#define AK4671_E2_COEFFICIENT3 0x3b +#define AK4671_E2_COEFFICIENT4 0x3c +#define AK4671_E2_COEFFICIENT5 0x3d +#define AK4671_E3_COEFFICIENT0 0x3e +#define AK4671_E3_COEFFICIENT1 0x3f +#define AK4671_E3_COEFFICIENT2 0x40 +#define AK4671_E3_COEFFICIENT3 0x41 +#define AK4671_E3_COEFFICIENT4 0x42 +#define AK4671_E3_COEFFICIENT5 0x43 +#define AK4671_E4_COEFFICIENT0 0x44 +#define AK4671_E4_COEFFICIENT1 0x45 +#define AK4671_E4_COEFFICIENT2 0x46 +#define AK4671_E4_COEFFICIENT3 0x47 +#define AK4671_E4_COEFFICIENT4 0x48 +#define AK4671_E4_COEFFICIENT5 0x49 +#define AK4671_E5_COEFFICIENT0 0x4a +#define AK4671_E5_COEFFICIENT1 0x4b +#define AK4671_E5_COEFFICIENT2 0x4c +#define AK4671_E5_COEFFICIENT3 0x4d +#define AK4671_E5_COEFFICIENT4 0x4e +#define AK4671_E5_COEFFICIENT5 0x4f +#define AK4671_EQ_CONTROL_250HZ_100HZ 0x50 +#define AK4671_EQ_CONTROL_3500HZ_1KHZ 0x51 +#define AK4671_EQ_CONTRO_10KHZ 0x52 +#define AK4671_PCM_IF_CONTROL0 0x53 +#define AK4671_PCM_IF_CONTROL1 0x54 +#define AK4671_PCM_IF_CONTROL2 0x55 +#define AK4671_DIGITAL_VOLUME_B_CONTROL 0x56 +#define AK4671_DIGITAL_VOLUME_C_CONTROL 0x57 +#define AK4671_SIDETONE_VOLUME_CONTROL 0x58 +#define AK4671_DIGITAL_MIXING_CONTROL2 0x59 +#define AK4671_SAR_ADC_CONTROL 0x5a + +#define AK4671_CACHEREGNUM (AK4671_SAR_ADC_CONTROL + 1) + +/* Bitfield Definitions */ + +/* AK4671_AD_DA_POWER_MANAGEMENT (0x00) Fields */ +#define AK4671_PMVCM 0x01 + +/* AK4671_PLL_MODE_SELECT0 (0x01) Fields */ +#define AK4671_PLL 0x0f +#define AK4671_PLL_11_2896MHZ (4 << 0) +#define AK4671_PLL_12_288MHZ (5 << 0) +#define AK4671_PLL_12MHZ (6 << 0) +#define AK4671_PLL_24MHZ (7 << 0) +#define AK4671_PLL_19_2MHZ (8 << 0) +#define AK4671_PLL_13_5MHZ (12 << 0) +#define AK4671_PLL_27MHZ (13 << 0) +#define AK4671_PLL_13MHZ (14 << 0) +#define AK4671_PLL_26MHZ (15 << 0) +#define AK4671_FS 0xf0 +#define AK4671_FS_8KHZ (0 << 4) +#define AK4671_FS_12KHZ (1 << 4) +#define AK4671_FS_16KHZ (2 << 4) +#define AK4671_FS_24KHZ (3 << 4) +#define AK4671_FS_11_025KHZ (5 << 4) +#define AK4671_FS_22_05KHZ (7 << 4) +#define AK4671_FS_32KHZ (10 << 4) +#define AK4671_FS_48KHZ (11 << 4) +#define AK4671_FS_44_1KHZ (15 << 4) + +/* AK4671_PLL_MODE_SELECT1 (0x02) Fields */ +#define AK4671_PMPLL 0x01 +#define AK4671_M_S 0x02 + +/* AK4671_FORMAT_SELECT (0x03) Fields */ +#define AK4671_DIF 0x03 +#define AK4671_DIF_DSP_MODE (0 << 0) +#define AK4671_DIF_MSB_MODE (2 << 0) +#define AK4671_DIF_I2S_MODE (3 << 0) +#define AK4671_BCKP 0x04 +#define AK4671_MSBS 0x08 +#define AK4671_SDOD 0x10 + +/* AK4671_LOUT2_POWER_MANAGEMENT (0x10) Fields */ +#define AK4671_MUTEN 0x04 + +extern struct snd_soc_dai ak4671_dai; +extern struct snd_soc_codec_device soc_codec_dev_ak4671; + +#endif diff --git a/sound/soc/codecs/wm8350.c b/sound/soc/codecs/wm8350.c index 593d5b9c9f0..72abc5a6d8d 100644 --- a/sound/soc/codecs/wm8350.c +++ b/sound/soc/codecs/wm8350.c @@ -1101,7 +1101,7 @@ static inline int fll_factors(struct _fll_div *fll_div, unsigned int input, } static int wm8350_set_fll(struct snd_soc_dai *codec_dai, - int pll_id, unsigned int freq_in, + int pll_id, int source, unsigned int freq_in, unsigned int freq_out) { struct snd_soc_codec *codec = codec_dai->codec; diff --git a/sound/soc/codecs/wm8400.c b/sound/soc/codecs/wm8400.c index b9ef4d91522..9cb8e50f0fb 100644 --- a/sound/soc/codecs/wm8400.c +++ b/sound/soc/codecs/wm8400.c @@ -1011,7 +1011,8 @@ static int fll_factors(struct wm8400_priv *wm8400, struct fll_factors *factors, } static int wm8400_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, - unsigned int freq_in, unsigned int freq_out) + int source, unsigned int freq_in, + unsigned int freq_out) { struct snd_soc_codec *codec = codec_dai->codec; struct wm8400_priv *wm8400 = codec->private_data; diff --git a/sound/soc/codecs/wm8510.c b/sound/soc/codecs/wm8510.c index 060d5d06ba9..5702435af81 100644 --- a/sound/soc/codecs/wm8510.c +++ b/sound/soc/codecs/wm8510.c @@ -271,8 +271,8 @@ static void pll_factors(unsigned int target, unsigned int source) pll_div.k = K; } -static int wm8510_set_dai_pll(struct snd_soc_dai *codec_dai, - int pll_id, unsigned int freq_in, unsigned int freq_out) +static int wm8510_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) { struct snd_soc_codec *codec = codec_dai->codec; u16 reg; diff --git a/sound/soc/codecs/wm8580.c b/sound/soc/codecs/wm8580.c index 6bded8c7815..3be5c0b2552 100644 --- a/sound/soc/codecs/wm8580.c +++ b/sound/soc/codecs/wm8580.c @@ -407,8 +407,8 @@ static int pll_factors(struct _pll_div *pll_div, unsigned int target, return 0; } -static int wm8580_set_dai_pll(struct snd_soc_dai *codec_dai, - int pll_id, unsigned int freq_in, unsigned int freq_out) +static int wm8580_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) { int offset; struct snd_soc_codec *codec = codec_dai->codec; diff --git a/sound/soc/codecs/wm8711.c b/sound/soc/codecs/wm8711.c new file mode 100644 index 00000000000..90ec8c58e2f --- /dev/null +++ b/sound/soc/codecs/wm8711.c @@ -0,0 +1,659 @@ +/* + * wm8711.c -- WM8711 ALSA SoC Audio driver + * + * Copyright 2006 Wolfson Microelectronics + * + * Author: Mike Arthur <linux@wolfsonmicro.com> + * + * Based on wm8731.c by Richard Purdie + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/i2c.h> +#include <linux/platform_device.h> +#include <linux/spi/spi.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/tlv.h> +#include <sound/initval.h> + +#include "wm8711.h" + +static struct snd_soc_codec *wm8711_codec; + +/* codec private data */ +struct wm8711_priv { + struct snd_soc_codec codec; + u16 reg_cache[WM8711_CACHEREGNUM]; + unsigned int sysclk; +}; + +/* + * wm8711 register cache + * We can't read the WM8711 register space when we are + * using 2 wire for device control, so we cache them instead. + * There is no point in caching the reset register + */ +static const u16 wm8711_reg[WM8711_CACHEREGNUM] = { + 0x0079, 0x0079, 0x000a, 0x0008, + 0x009f, 0x000a, 0x0000, 0x0000 +}; + +#define wm8711_reset(c) snd_soc_write(c, WM8711_RESET, 0) + +static const DECLARE_TLV_DB_SCALE(out_tlv, -12100, 100, 1); + +static const struct snd_kcontrol_new wm8711_snd_controls[] = { + +SOC_DOUBLE_R_TLV("Master Playback Volume", WM8711_LOUT1V, WM8711_ROUT1V, + 0, 127, 0, out_tlv), +SOC_DOUBLE_R("Master Playback ZC Switch", WM8711_LOUT1V, WM8711_ROUT1V, + 7, 1, 0), + +}; + +/* Output Mixer */ +static const struct snd_kcontrol_new wm8711_output_mixer_controls[] = { +SOC_DAPM_SINGLE("Line Bypass Switch", WM8711_APANA, 3, 1, 0), +SOC_DAPM_SINGLE("HiFi Playback Switch", WM8711_APANA, 4, 1, 0), +}; + +static const struct snd_soc_dapm_widget wm8711_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Output Mixer", WM8711_PWR, 4, 1, + &wm8711_output_mixer_controls[0], + ARRAY_SIZE(wm8711_output_mixer_controls)), +SND_SOC_DAPM_DAC("DAC", "HiFi Playback", WM8711_PWR, 3, 1), +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("LHPOUT"), +SND_SOC_DAPM_OUTPUT("ROUT"), +SND_SOC_DAPM_OUTPUT("RHPOUT"), +}; + +static const struct snd_soc_dapm_route intercon[] = { + /* output mixer */ + {"Output Mixer", "Line Bypass Switch", "Line Input"}, + {"Output Mixer", "HiFi Playback Switch", "DAC"}, + + /* outputs */ + {"RHPOUT", NULL, "Output Mixer"}, + {"ROUT", NULL, "Output Mixer"}, + {"LHPOUT", NULL, "Output Mixer"}, + {"LOUT", NULL, "Output Mixer"}, +}; + +static int wm8711_add_widgets(struct snd_soc_codec *codec) +{ + snd_soc_dapm_new_controls(codec, wm8711_dapm_widgets, + ARRAY_SIZE(wm8711_dapm_widgets)); + + snd_soc_dapm_add_routes(codec, intercon, ARRAY_SIZE(intercon)); + + snd_soc_dapm_new_widgets(codec); + return 0; +} + +struct _coeff_div { + u32 mclk; + u32 rate; + u16 fs; + u8 sr:4; + u8 bosr:1; + u8 usb:1; +}; + +/* codec mclk clock divider coefficients */ +static const struct _coeff_div coeff_div[] = { + /* 48k */ + {12288000, 48000, 256, 0x0, 0x0, 0x0}, + {18432000, 48000, 384, 0x0, 0x1, 0x0}, + {12000000, 48000, 250, 0x0, 0x0, 0x1}, + + /* 32k */ + {12288000, 32000, 384, 0x6, 0x0, 0x0}, + {18432000, 32000, 576, 0x6, 0x1, 0x0}, + {12000000, 32000, 375, 0x6, 0x0, 0x1}, + + /* 8k */ + {12288000, 8000, 1536, 0x3, 0x0, 0x0}, + {18432000, 8000, 2304, 0x3, 0x1, 0x0}, + {11289600, 8000, 1408, 0xb, 0x0, 0x0}, + {16934400, 8000, 2112, 0xb, 0x1, 0x0}, + {12000000, 8000, 1500, 0x3, 0x0, 0x1}, + + /* 96k */ + {12288000, 96000, 128, 0x7, 0x0, 0x0}, + {18432000, 96000, 192, 0x7, 0x1, 0x0}, + {12000000, 96000, 125, 0x7, 0x0, 0x1}, + + /* 44.1k */ + {11289600, 44100, 256, 0x8, 0x0, 0x0}, + {16934400, 44100, 384, 0x8, 0x1, 0x0}, + {12000000, 44100, 272, 0x8, 0x1, 0x1}, + + /* 88.2k */ + {11289600, 88200, 128, 0xf, 0x0, 0x0}, + {16934400, 88200, 192, 0xf, 0x1, 0x0}, + {12000000, 88200, 136, 0xf, 0x1, 0x1}, +}; + +static inline int get_coeff(int mclk, int rate) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { + if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) + return i; + } + return 0; +} + +static int wm8711_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct wm8711_priv *wm8711 = codec->private_data; + u16 iface = snd_soc_read(codec, WM8711_IFACE) & 0xfffc; + int i = get_coeff(wm8711->sysclk, params_rate(params)); + u16 srate = (coeff_div[i].sr << 2) | + (coeff_div[i].bosr << 1) | coeff_div[i].usb; + + snd_soc_write(codec, WM8711_SRATE, srate); + + /* bit size */ + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + break; + case SNDRV_PCM_FORMAT_S20_3LE: + iface |= 0x0004; + break; + case SNDRV_PCM_FORMAT_S24_LE: + iface |= 0x0008; + break; + } + + snd_soc_write(codec, WM8711_IFACE, iface); + return 0; +} + +static int wm8711_pcm_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + + /* set active */ + snd_soc_write(codec, WM8711_ACTIVE, 0x0001); + + return 0; +} + +static void wm8711_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + + /* deactivate */ + if (!codec->active) { + udelay(50); + snd_soc_write(codec, WM8711_ACTIVE, 0x0); + } +} + +static int wm8711_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + u16 mute_reg = snd_soc_read(codec, WM8711_APDIGI) & 0xfff7; + + if (mute) + snd_soc_write(codec, WM8711_APDIGI, mute_reg | 0x8); + else + snd_soc_write(codec, WM8711_APDIGI, mute_reg); + + return 0; +} + +static int wm8711_set_dai_sysclk(struct snd_soc_dai *codec_dai, + int clk_id, unsigned int freq, int dir) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct wm8711_priv *wm8711 = codec->private_data; + + switch (freq) { + case 11289600: + case 12000000: + case 12288000: + case 16934400: + case 18432000: + wm8711->sysclk = freq; + return 0; + } + return -EINVAL; +} + +static int wm8711_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + u16 iface = 0; + + /* set master/slave audio interface */ + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + iface |= 0x0040; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* interface format */ + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + iface |= 0x0002; + break; + case SND_SOC_DAIFMT_RIGHT_J: + break; + case SND_SOC_DAIFMT_LEFT_J: + iface |= 0x0001; + break; + case SND_SOC_DAIFMT_DSP_A: + iface |= 0x0003; + break; + case SND_SOC_DAIFMT_DSP_B: + iface |= 0x0013; + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + case SND_SOC_DAIFMT_IB_IF: + iface |= 0x0090; + break; + case SND_SOC_DAIFMT_IB_NF: + iface |= 0x0080; + break; + case SND_SOC_DAIFMT_NB_IF: + iface |= 0x0010; + break; + default: + return -EINVAL; + } + + /* set iface */ + snd_soc_write(codec, WM8711_IFACE, iface); + return 0; +} + + +static int wm8711_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + u16 reg = snd_soc_read(codec, WM8711_PWR) & 0xff7f; + + switch (level) { + case SND_SOC_BIAS_ON: + snd_soc_write(codec, WM8711_PWR, reg); + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + snd_soc_write(codec, WM8711_PWR, reg | 0x0040); + break; + case SND_SOC_BIAS_OFF: + snd_soc_write(codec, WM8711_ACTIVE, 0x0); + snd_soc_write(codec, WM8711_PWR, 0xffff); + break; + } + codec->bias_level = level; + return 0; +} + +#define WM8711_RATES SNDRV_PCM_RATE_8000_96000 + +#define WM8711_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_ops wm8711_ops = { + .prepare = wm8711_pcm_prepare, + .hw_params = wm8711_hw_params, + .shutdown = wm8711_shutdown, + .digital_mute = wm8711_mute, + .set_sysclk = wm8711_set_dai_sysclk, + .set_fmt = wm8711_set_dai_fmt, +}; + +struct snd_soc_dai wm8711_dai = { + .name = "WM8711", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = WM8711_RATES, + .formats = WM8711_FORMATS, + }, + .ops = &wm8711_ops, +}; +EXPORT_SYMBOL_GPL(wm8711_dai); + +static int wm8711_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + + snd_soc_write(codec, WM8711_ACTIVE, 0x0); + wm8711_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int wm8711_resume(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec = socdev->card->codec; + int i; + u8 data[2]; + u16 *cache = codec->reg_cache; + + /* Sync reg_cache with the hardware */ + for (i = 0; i < ARRAY_SIZE(wm8711_reg); i++) { + data[0] = (i << 1) | ((cache[i] >> 8) & 0x0001); + data[1] = cache[i] & 0x00ff; + codec->hw_write(codec->control_data, data, 2); + } + wm8711_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + wm8711_set_bias_level(codec, codec->suspend_bias_level); + return 0; +} + +static int wm8711_probe(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + struct snd_soc_codec *codec; + int ret = 0; + + if (wm8711_codec == NULL) { + dev_err(&pdev->dev, "Codec device not registered\n"); + return -ENODEV; + } + + socdev->card->codec = wm8711_codec; + codec = wm8711_codec; + + /* register pcms */ + ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1); + if (ret < 0) { + dev_err(codec->dev, "failed to create pcms: %d\n", ret); + goto pcm_err; + } + + snd_soc_add_controls(codec, wm8711_snd_controls, + ARRAY_SIZE(wm8711_snd_controls)); + wm8711_add_widgets(codec); + ret = snd_soc_init_card(socdev); + if (ret < 0) { + dev_err(codec->dev, "failed to register card: %d\n", ret); + goto card_err; + } + + return ret; + +card_err: + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); +pcm_err: + return ret; +} + +/* power down chip */ +static int wm8711_remove(struct platform_device *pdev) +{ + struct snd_soc_device *socdev = platform_get_drvdata(pdev); + + snd_soc_free_pcms(socdev); + snd_soc_dapm_free(socdev); + + return 0; +} + +struct snd_soc_codec_device soc_codec_dev_wm8711 = { + .probe = wm8711_probe, + .remove = wm8711_remove, + .suspend = wm8711_suspend, + .resume = wm8711_resume, +}; +EXPORT_SYMBOL_GPL(soc_codec_dev_wm8711); + +static int wm8711_register(struct wm8711_priv *wm8711, + enum snd_soc_control_type control) +{ + int ret; + struct snd_soc_codec *codec = &wm8711->codec; + u16 reg; + + if (wm8711_codec) { + dev_err(codec->dev, "Another WM8711 is registered\n"); + return -EINVAL; + } + + mutex_init(&codec->mutex); + INIT_LIST_HEAD(&codec->dapm_widgets); + INIT_LIST_HEAD(&codec->dapm_paths); + + codec->private_data = wm8711; + codec->name = "WM8711"; + codec->owner = THIS_MODULE; + codec->bias_level = SND_SOC_BIAS_OFF; + codec->set_bias_level = wm8711_set_bias_level; + codec->dai = &wm8711_dai; + codec->num_dai = 1; + codec->reg_cache_size = WM8711_CACHEREGNUM; + codec->reg_cache = &wm8711->reg_cache; + + memcpy(codec->reg_cache, wm8711_reg, sizeof(wm8711_reg)); + + ret = snd_soc_codec_set_cache_io(codec, 7, 9, control); + if (ret < 0) { + dev_err(codec->dev, "Failed to set cache I/O: %d\n", ret); + goto err; + } + + ret = wm8711_reset(codec); + if (ret < 0) { + dev_err(codec->dev, "Failed to issue reset\n"); + goto err; + } + + wm8711_dai.dev = codec->dev; + + wm8711_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + + /* Latch the update bits */ + reg = snd_soc_read(codec, WM8711_LOUT1V); + snd_soc_write(codec, WM8711_LOUT1V, reg | 0x0100); + reg = snd_soc_read(codec, WM8711_ROUT1V); + snd_soc_write(codec, WM8711_ROUT1V, reg | 0x0100); + + wm8711_codec = codec; + + ret = snd_soc_register_codec(codec); + if (ret != 0) { + dev_err(codec->dev, "Failed to register codec: %d\n", ret); + goto err; + } + + ret = snd_soc_register_dai(&wm8711_dai); + if (ret != 0) { + dev_err(codec->dev, "Failed to register DAI: %d\n", ret); + goto err_codec; + } + + return 0; + +err_codec: + snd_soc_unregister_codec(codec); +err: + kfree(wm8711); + return ret; +} + +static void wm8711_unregister(struct wm8711_priv *wm8711) +{ + wm8711_set_bias_level(&wm8711->codec, SND_SOC_BIAS_OFF); + snd_soc_unregister_dai(&wm8711_dai); + snd_soc_unregister_codec(&wm8711->codec); + kfree(wm8711); + wm8711_codec = NULL; +} + +#if defined(CONFIG_SPI_MASTER) +static int __devinit wm8711_spi_probe(struct spi_device *spi) +{ + struct snd_soc_codec *codec; + struct wm8711_priv *wm8711; + + wm8711 = kzalloc(sizeof(struct wm8711_priv), GFP_KERNEL); + if (wm8711 == NULL) + return -ENOMEM; + + codec = &wm8711->codec; + codec->control_data = spi; + codec->dev = &spi->dev; + + dev_set_drvdata(&spi->dev, wm8711); + + return wm8711_register(wm8711, SND_SOC_SPI); +} + +static int __devexit wm8711_spi_remove(struct spi_device *spi) +{ + struct wm8711_priv *wm8711 = dev_get_drvdata(&spi->dev); + + wm8711_unregister(wm8711); + + return 0; +} + +#ifdef CONFIG_PM +static int wm8711_spi_suspend(struct spi_device *spi, pm_message_t msg) +{ + return snd_soc_suspend_device(&spi->dev); +} + +static int wm8711_spi_resume(struct spi_device *spi) +{ + return snd_soc_resume_device(&spi->dev); +} +#else +#define wm8711_spi_suspend NULL +#define wm8711_spi_resume NULL +#endif + +static struct spi_driver wm8711_spi_driver = { + .driver = { + .name = "wm8711", + .bus = &spi_bus_type, + .owner = THIS_MODULE, + }, + .probe = wm8711_spi_probe, + .suspend = wm8711_spi_suspend, + .resume = wm8711_spi_resume, + .remove = __devexit_p(wm8711_spi_remove), +}; +#endif /* CONFIG_SPI_MASTER */ + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static __devinit int wm8711_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct wm8711_priv *wm8711; + struct snd_soc_codec *codec; + + wm8711 = kzalloc(sizeof(struct wm8711_priv), GFP_KERNEL); + if (wm8711 == NULL) + return -ENOMEM; + + codec = &wm8711->codec; + codec->hw_write = (hw_write_t)i2c_master_send; + + i2c_set_clientdata(i2c, wm8711); + codec->control_data = i2c; + + codec->dev = &i2c->dev; + + return wm8711_register(wm8711, SND_SOC_I2C); +} + +static __devexit int wm8711_i2c_remove(struct i2c_client *client) +{ + struct wm8711_priv *wm8711 = i2c_get_clientdata(client); + wm8711_unregister(wm8711); + return 0; +} + +static const struct i2c_device_id wm8711_i2c_id[] = { + { "wm8711", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, wm8711_i2c_id); + +static struct i2c_driver wm8711_i2c_driver = { + .driver = { + .name = "WM8711 I2C Codec", + .owner = THIS_MODULE, + }, + .probe = wm8711_i2c_probe, + .remove = __devexit_p(wm8711_i2c_remove), + .id_table = wm8711_i2c_id, +}; +#endif + +static int __init wm8711_modinit(void) +{ + int ret; +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&wm8711_i2c_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8711 I2C driver: %d\n", + ret); + } +#endif +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&wm8711_spi_driver); + if (ret != 0) { + printk(KERN_ERR "Failed to register WM8711 SPI driver: %d\n", + ret); + } +#endif + return 0; +} +module_init(wm8711_modinit); + +static void __exit wm8711_exit(void) +{ +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&wm8711_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&wm8711_spi_driver); +#endif +} +module_exit(wm8711_exit); + +MODULE_DESCRIPTION("ASoC WM8711 driver"); +MODULE_AUTHOR("Mike Arthur"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8711.h b/sound/soc/codecs/wm8711.h new file mode 100644 index 00000000000..381e84a4381 --- /dev/null +++ b/sound/soc/codecs/wm8711.h @@ -0,0 +1,42 @@ +/* + * wm8711.h -- WM8711 Soc Audio driver + * + * Copyright 2006 Wolfson Microelectronics + * + * Author: Mike Arthur <linux@wolfsonmicro.com> + * + * Based on wm8731.h + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef _WM8711_H +#define _WM8711_H + +/* WM8711 register space */ + +#define WM8711_LOUT1V 0x02 +#define WM8711_ROUT1V 0x03 +#define WM8711_APANA 0x04 +#define WM8711_APDIGI 0x05 +#define WM8711_PWR 0x06 +#define WM8711_IFACE 0x07 +#define WM8711_SRATE 0x08 +#define WM8711_ACTIVE 0x09 +#define WM8711_RESET 0x0f + +#define WM8711_CACHEREGNUM 8 + +#define WM8711_SYSCLK 0 +#define WM8711_DAI 0 + +struct wm8711_setup_data { + unsigned short i2c_address; +}; + +extern struct snd_soc_dai wm8711_dai; +extern struct snd_soc_codec_device soc_codec_dev_wm8711; + +#endif diff --git a/sound/soc/codecs/wm8753.c b/sound/soc/codecs/wm8753.c index 5ad677ce80d..9b27efb052f 100644 --- a/sound/soc/codecs/wm8753.c +++ b/sound/soc/codecs/wm8753.c @@ -724,8 +724,8 @@ static void pll_factors(struct _pll_div *pll_div, unsigned int target, pll_div->k = K; } -static int wm8753_set_dai_pll(struct snd_soc_dai *codec_dai, - int pll_id, unsigned int freq_in, unsigned int freq_out) +static int wm8753_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) { u16 reg, enable; int offset; diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c index 5e9c855c003..882604ef768 100644 --- a/sound/soc/codecs/wm8900.c +++ b/sound/soc/codecs/wm8900.c @@ -814,8 +814,8 @@ reenable: return 0; } -static int wm8900_set_dai_pll(struct snd_soc_dai *codec_dai, - int pll_id, unsigned int freq_in, unsigned int freq_out) +static int wm8900_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) { return wm8900_set_fll(codec_dai->codec, pll_id, freq_in, freq_out); } diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c index 1ef2454c520..1685cfb993c 100644 --- a/sound/soc/codecs/wm8940.c +++ b/sound/soc/codecs/wm8940.c @@ -536,8 +536,8 @@ static void pll_factors(unsigned int target, unsigned int source) } /* Untested at the moment */ -static int wm8940_set_dai_pll(struct snd_soc_dai *codec_dai, - int pll_id, unsigned int freq_in, unsigned int freq_out) +static int wm8940_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) { struct snd_soc_codec *codec = codec_dai->codec; u16 reg; diff --git a/sound/soc/codecs/wm8960.c b/sound/soc/codecs/wm8960.c index f59703be61c..416fb3c1701 100644 --- a/sound/soc/codecs/wm8960.c +++ b/sound/soc/codecs/wm8960.c @@ -540,8 +540,8 @@ static int pll_factors(unsigned int source, unsigned int target, return 0; } -static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, - int pll_id, unsigned int freq_in, unsigned int freq_out) +static int wm8960_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) { struct snd_soc_codec *codec = codec_dai->codec; u16 reg; diff --git a/sound/soc/codecs/wm8974.c b/sound/soc/codecs/wm8974.c index 98d663afc97..eff29331235 100644 --- a/sound/soc/codecs/wm8974.c +++ b/sound/soc/codecs/wm8974.c @@ -281,36 +281,38 @@ static int wm8974_add_widgets(struct snd_soc_codec *codec) } struct pll_ { - unsigned int pre_div:4; /* prescale - 1 */ + unsigned int pre_div:1; unsigned int n:4; unsigned int k; }; -static struct pll_ pll_div; - /* The size in bits of the pll divide multiplied by 10 * to allow rounding later */ #define FIXED_PLL_SIZE ((1 << 24) * 10) -static void pll_factors(unsigned int target, unsigned int source) +static void pll_factors(struct pll_ *pll_div, + unsigned int target, unsigned int source) { unsigned long long Kpart; unsigned int K, Ndiv, Nmod; + /* There is a fixed divide by 4 in the output path */ + target *= 4; + Ndiv = target / source; if (Ndiv < 6) { - source >>= 1; - pll_div.pre_div = 1; + source /= 2; + pll_div->pre_div = 1; Ndiv = target / source; } else - pll_div.pre_div = 0; + pll_div->pre_div = 0; if ((Ndiv < 6) || (Ndiv > 12)) printk(KERN_WARNING "WM8974 N value %u outwith recommended range!\n", Ndiv); - pll_div.n = Ndiv; + pll_div->n = Ndiv; Nmod = target % source; Kpart = FIXED_PLL_SIZE * (long long)Nmod; @@ -325,13 +327,14 @@ static void pll_factors(unsigned int target, unsigned int source) /* Move down to proper range now rounding is done */ K /= 10; - pll_div.k = K; + pll_div->k = K; } -static int wm8974_set_dai_pll(struct snd_soc_dai *codec_dai, - int pll_id, unsigned int freq_in, unsigned int freq_out) +static int wm8974_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) { struct snd_soc_codec *codec = codec_dai->codec; + struct pll_ pll_div; u16 reg; if (freq_in == 0 || freq_out == 0) { @@ -345,7 +348,7 @@ static int wm8974_set_dai_pll(struct snd_soc_dai *codec_dai, return 0; } - pll_factors(freq_out*4, freq_in); + pll_factors(&pll_div, freq_out, freq_in); snd_soc_write(codec, WM8974_PLLN, (pll_div.pre_div << 4) | pll_div.n); snd_soc_write(codec, WM8974_PLLK1, pll_div.k >> 18); diff --git a/sound/soc/codecs/wm8990.c b/sound/soc/codecs/wm8990.c index 2d702db4131..f657e9a5fe2 100644 --- a/sound/soc/codecs/wm8990.c +++ b/sound/soc/codecs/wm8990.c @@ -972,8 +972,8 @@ static void pll_factors(struct _pll_div *pll_div, unsigned int target, pll_div->k = K; } -static int wm8990_set_dai_pll(struct snd_soc_dai *codec_dai, - int pll_id, unsigned int freq_in, unsigned int freq_out) +static int wm8990_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) { u16 reg; struct snd_soc_codec *codec = codec_dai->codec; diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c index d9987999e92..dac39771214 100644 --- a/sound/soc/codecs/wm8993.c +++ b/sound/soc/codecs/wm8993.c @@ -422,7 +422,7 @@ static int fll_factors(struct _fll_div *fll_div, unsigned int Fref, return 0; } -static int wm8993_set_fll(struct snd_soc_dai *dai, int fll_id, +static int wm8993_set_fll(struct snd_soc_dai *dai, int fll_id, int source, unsigned int Fref, unsigned int Fout) { struct snd_soc_codec *codec = dai->codec; @@ -1572,33 +1572,15 @@ static int wm8993_i2c_probe(struct i2c_client *i2c, /* Use automatic clock configuration */ snd_soc_update_bits(codec, WM8993_CLOCKING_4, WM8993_SR_MODE, 0); - if (!wm8993->pdata.lineout1_diff) - snd_soc_update_bits(codec, WM8993_LINE_MIXER1, - WM8993_LINEOUT1_MODE, - WM8993_LINEOUT1_MODE); - if (!wm8993->pdata.lineout2_diff) - snd_soc_update_bits(codec, WM8993_LINE_MIXER2, - WM8993_LINEOUT2_MODE, - WM8993_LINEOUT2_MODE); - - if (wm8993->pdata.lineout1fb) - snd_soc_update_bits(codec, WM8993_ADDITIONAL_CONTROL, - WM8993_LINEOUT1_FB, WM8993_LINEOUT1_FB); - - if (wm8993->pdata.lineout2fb) - snd_soc_update_bits(codec, WM8993_ADDITIONAL_CONTROL, - WM8993_LINEOUT2_FB, WM8993_LINEOUT2_FB); - - /* Apply the microphone bias/detection configuration - the - * platform data is directly applicable to the register. */ - snd_soc_update_bits(codec, WM8993_MICBIAS, - WM8993_JD_SCTHR_MASK | WM8993_JD_THR_MASK | - WM8993_MICB1_LVL | WM8993_MICB2_LVL, - wm8993->pdata.jd_scthr << WM8993_JD_SCTHR_SHIFT | - wm8993->pdata.jd_thr << WM8993_JD_THR_SHIFT | - wm8993->pdata.micbias1_lvl | - wm8993->pdata.micbias1_lvl << 1); - + wm_hubs_handle_analogue_pdata(codec, wm8993->pdata.lineout1_diff, + wm8993->pdata.lineout2_diff, + wm8993->pdata.lineout1fb, + wm8993->pdata.lineout2fb, + wm8993->pdata.jd_scthr, + wm8993->pdata.jd_thr, + wm8993->pdata.micbias1_lvl, + wm8993->pdata.micbias2_lvl); + ret = wm8993_set_bias_level(codec, SND_SOC_BIAS_STANDBY); if (ret != 0) goto err; diff --git a/sound/soc/codecs/wm9713.c b/sound/soc/codecs/wm9713.c index abed37acf78..ca3d449ed89 100644 --- a/sound/soc/codecs/wm9713.c +++ b/sound/soc/codecs/wm9713.c @@ -800,8 +800,8 @@ static int wm9713_set_pll(struct snd_soc_codec *codec, return 0; } -static int wm9713_set_dai_pll(struct snd_soc_dai *codec_dai, - int pll_id, unsigned int freq_in, unsigned int freq_out) +static int wm9713_set_dai_pll(struct snd_soc_dai *codec_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) { struct snd_soc_codec *codec = codec_dai->codec; return wm9713_set_pll(codec, pll_id, freq_in, freq_out); diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index e542027eea8..810a563d0eb 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -738,6 +738,41 @@ int wm_hubs_add_analogue_routes(struct snd_soc_codec *codec, } EXPORT_SYMBOL_GPL(wm_hubs_add_analogue_routes); +int wm_hubs_handle_analogue_pdata(struct snd_soc_codec *codec, + int lineout1_diff, int lineout2_diff, + int lineout1fb, int lineout2fb, + int jd_scthr, int jd_thr, int micbias1_lvl, + int micbias2_lvl) +{ + if (!lineout1_diff) + snd_soc_update_bits(codec, WM8993_LINE_MIXER1, + WM8993_LINEOUT1_MODE, + WM8993_LINEOUT1_MODE); + if (!lineout2_diff) + snd_soc_update_bits(codec, WM8993_LINE_MIXER2, + WM8993_LINEOUT2_MODE, + WM8993_LINEOUT2_MODE); + + if (lineout1fb) + snd_soc_update_bits(codec, WM8993_ADDITIONAL_CONTROL, + WM8993_LINEOUT1_FB, WM8993_LINEOUT1_FB); + + if (lineout2fb) + snd_soc_update_bits(codec, WM8993_ADDITIONAL_CONTROL, + WM8993_LINEOUT2_FB, WM8993_LINEOUT2_FB); + + snd_soc_update_bits(codec, WM8993_MICBIAS, + WM8993_JD_SCTHR_MASK | WM8993_JD_THR_MASK | + WM8993_MICB1_LVL | WM8993_MICB2_LVL, + jd_scthr << WM8993_JD_SCTHR_SHIFT | + jd_thr << WM8993_JD_THR_SHIFT | + micbias1_lvl | + micbias2_lvl << WM8993_MICB2_LVL_SHIFT); + + return 0; +} +EXPORT_SYMBOL_GPL(wm_hubs_handle_analogue_pdata); + MODULE_DESCRIPTION("Shared support for Wolfson hubs products"); MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm_hubs.h b/sound/soc/codecs/wm_hubs.h index ec09cb6a293..36d3fba1de8 100644 --- a/sound/soc/codecs/wm_hubs.h +++ b/sound/soc/codecs/wm_hubs.h @@ -20,5 +20,10 @@ extern const unsigned int wm_hubs_spkmix_tlv[]; extern int wm_hubs_add_analogue_controls(struct snd_soc_codec *); extern int wm_hubs_add_analogue_routes(struct snd_soc_codec *, int, int); +extern int wm_hubs_handle_analogue_pdata(struct snd_soc_codec *, + int lineout1_diff, int lineout2_diff, + int lineout1fb, int lineout2fb, + int jd_scthr, int jd_thr, + int micbias1_lvl, int micbias2_lvl); #endif diff --git a/sound/soc/davinci/Kconfig b/sound/soc/davinci/Kconfig index 4dfd4ad9d90..047ee39418c 100644 --- a/sound/soc/davinci/Kconfig +++ b/sound/soc/davinci/Kconfig @@ -13,9 +13,9 @@ config SND_DAVINCI_SOC_MCASP tristate config SND_DAVINCI_SOC_EVM - tristate "SoC Audio support for DaVinci DM6446 or DM355 EVM" + tristate "SoC Audio support for DaVinci DM6446, DM355 or DM365 EVM" depends on SND_DAVINCI_SOC - depends on MACH_DAVINCI_EVM || MACH_DAVINCI_DM355_EVM + depends on MACH_DAVINCI_EVM || MACH_DAVINCI_DM355_EVM || MACH_DAVINCI_DM365_EVM select SND_DAVINCI_SOC_I2S select SND_SOC_TLV320AIC3X help diff --git a/sound/soc/davinci/davinci-evm.c b/sound/soc/davinci/davinci-evm.c index 67414f65940..7ccbe6684fc 100644 --- a/sound/soc/davinci/davinci-evm.c +++ b/sound/soc/davinci/davinci-evm.c @@ -45,7 +45,8 @@ static int evm_hw_params(struct snd_pcm_substream *substream, unsigned sysclk; /* ASP1 on DM355 EVM is clocked by an external oscillator */ - if (machine_is_davinci_dm355_evm() || machine_is_davinci_dm6467_evm()) + if (machine_is_davinci_dm355_evm() || machine_is_davinci_dm6467_evm() || + machine_is_davinci_dm365_evm()) sysclk = 27000000; /* ASP0 in DM6446 EVM is clocked by U55, as configured by @@ -176,7 +177,7 @@ static struct snd_soc_dai_link da8xx_evm_dai = { .ops = &evm_ops, }; -/* davinci-evm audio machine driver */ +/* davinci dm6446, dm355 or dm365 evm audio machine driver */ static struct snd_soc_card snd_soc_card_evm = { .name = "DaVinci EVM", .platform = &davinci_soc_platform, @@ -243,7 +244,7 @@ static int __init evm_init(void) int index; int ret; - if (machine_is_davinci_evm()) { + if (machine_is_davinci_evm() || machine_is_davinci_dm365_evm()) { evm_snd_dev_data = &evm_snd_devdata; index = 0; } else if (machine_is_davinci_dm355_evm()) { diff --git a/sound/soc/davinci/davinci-i2s.c b/sound/soc/davinci/davinci-i2s.c index 4ae70704802..2ab809359c0 100644 --- a/sound/soc/davinci/davinci-i2s.c +++ b/sound/soc/davinci/davinci-i2s.c @@ -397,6 +397,8 @@ static int davinci_i2s_hw_params(struct snd_pcm_substream *substream, } dma_params->acnt = dma_params->data_type; + dma_params->fifo_level = 0; + rcr |= DAVINCI_MCBSP_RCR_RFRLEN1(1); xcr |= DAVINCI_MCBSP_XCR_XFRLEN1(1); diff --git a/sound/soc/davinci/davinci-mcasp.c b/sound/soc/davinci/davinci-mcasp.c index 5d1f98a4c97..50ad0519a8f 100644 --- a/sound/soc/davinci/davinci-mcasp.c +++ b/sound/soc/davinci/davinci-mcasp.c @@ -714,16 +714,13 @@ static int davinci_mcasp_hw_params(struct snd_pcm_substream *substream, struct davinci_pcm_dma_params *dma_params = &dev->dma_params[substream->stream]; int word_length; - u8 numevt; + u8 fifo_level; davinci_hw_common_param(dev, substream->stream); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - numevt = dev->txnumevt; + fifo_level = dev->txnumevt; else - numevt = dev->rxnumevt; - - if (!numevt) - numevt = 1; + fifo_level = dev->rxnumevt; if (dev->op_mode == DAVINCI_MCASP_DIT_MODE) davinci_hw_dit_param(dev); @@ -751,12 +748,12 @@ static int davinci_mcasp_hw_params(struct snd_pcm_substream *substream, return -EINVAL; } - if (dev->version == MCASP_VERSION_2) { - dma_params->data_type *= numevt; - dma_params->acnt = 4 * numevt; - } else + if (dev->version == MCASP_VERSION_2 && !fifo_level) + dma_params->acnt = 4; + else dma_params->acnt = dma_params->data_type; + dma_params->fifo_level = fifo_level; davinci_config_channel_size(dev, word_length); return 0; diff --git a/sound/soc/davinci/davinci-pcm.c b/sound/soc/davinci/davinci-pcm.c index c73a915f233..fb10f1d63fd 100644 --- a/sound/soc/davinci/davinci-pcm.c +++ b/sound/soc/davinci/davinci-pcm.c @@ -66,38 +66,53 @@ static void davinci_pcm_enqueue_dma(struct snd_pcm_substream *substream) dma_addr_t dma_pos; dma_addr_t src, dst; unsigned short src_bidx, dst_bidx; + unsigned short src_cidx, dst_cidx; unsigned int data_type; unsigned short acnt; unsigned int count; + unsigned int fifo_level; period_size = snd_pcm_lib_period_bytes(substream); dma_offset = prtd->period * period_size; dma_pos = runtime->dma_addr + dma_offset; + fifo_level = prtd->params->fifo_level; pr_debug("davinci_pcm: audio_set_dma_params_play channel = %d " "dma_ptr = %x period_size=%x\n", lch, dma_pos, period_size); data_type = prtd->params->data_type; count = period_size / data_type; + if (fifo_level) + count /= fifo_level; if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { src = dma_pos; dst = prtd->params->dma_addr; src_bidx = data_type; dst_bidx = 0; + src_cidx = data_type * fifo_level; + dst_cidx = 0; } else { src = prtd->params->dma_addr; dst = dma_pos; src_bidx = 0; dst_bidx = data_type; + src_cidx = 0; + dst_cidx = data_type * fifo_level; } acnt = prtd->params->acnt; edma_set_src(lch, src, INCR, W8BIT); edma_set_dest(lch, dst, INCR, W8BIT); - edma_set_src_index(lch, src_bidx, 0); - edma_set_dest_index(lch, dst_bidx, 0); - edma_set_transfer_params(lch, acnt, count, 1, 0, ASYNC); + + edma_set_src_index(lch, src_bidx, src_cidx); + edma_set_dest_index(lch, dst_bidx, dst_cidx); + + if (!fifo_level) + edma_set_transfer_params(lch, acnt, count, 1, 0, ASYNC); + else + edma_set_transfer_params(lch, acnt, fifo_level, count, + fifo_level, ABSYNC); prtd->period++; if (unlikely(prtd->period >= runtime->periods)) diff --git a/sound/soc/davinci/davinci-pcm.h b/sound/soc/davinci/davinci-pcm.h index 8746606efc8..c8b0d2baf05 100644 --- a/sound/soc/davinci/davinci-pcm.h +++ b/sound/soc/davinci/davinci-pcm.h @@ -23,6 +23,7 @@ struct davinci_pcm_dma_params { enum dma_event_q eventq_no; /* event queue number */ unsigned char data_type; /* xfer data type */ unsigned char convert_mono_stereo; + unsigned int fifo_level; }; diff --git a/sound/soc/imx/mx27vis_wm8974.c b/sound/soc/imx/mx27vis_wm8974.c index e4dcb539108..0267d2d9168 100644 --- a/sound/soc/imx/mx27vis_wm8974.c +++ b/sound/soc/imx/mx27vis_wm8974.c @@ -157,7 +157,7 @@ static int mx27vis_hifi_hw_params(struct snd_pcm_substream *substream, /* codec PLL input is 25 MHz */ - ret = codec_dai->ops->set_pll(codec_dai, IGNORED_ARG, + ret = codec_dai->ops->set_pll(codec_dai, IGNORED_ARG, IGNORED_ARG, 25000000, pll_out); if (ret < 0) { printk(KERN_ERR "Error when setting PLL input\n"); diff --git a/sound/soc/pxa/magician.c b/sound/soc/pxa/magician.c index 9f7c61e23da..4c8d99a8d38 100644 --- a/sound/soc/pxa/magician.c +++ b/sound/soc/pxa/magician.c @@ -213,7 +213,7 @@ static int magician_playback_hw_params(struct snd_pcm_substream *substream, return ret; /* set SSP audio pll clock */ - ret = snd_soc_dai_set_pll(cpu_dai, 0, 0, acps); + ret = snd_soc_dai_set_pll(cpu_dai, 0, 0, 0, acps); if (ret < 0) return ret; diff --git a/sound/soc/pxa/pxa-ssp.c b/sound/soc/pxa/pxa-ssp.c index d11a6d7e384..3bd7712f029 100644 --- a/sound/soc/pxa/pxa-ssp.c +++ b/sound/soc/pxa/pxa-ssp.c @@ -305,8 +305,8 @@ static int pxa_ssp_set_dai_clkdiv(struct snd_soc_dai *cpu_dai, /* * Configure the PLL frequency pxa27x and (afaik - pxa320 only) */ -static int pxa_ssp_set_dai_pll(struct snd_soc_dai *cpu_dai, - int pll_id, unsigned int freq_in, unsigned int freq_out) +static int pxa_ssp_set_dai_pll(struct snd_soc_dai *cpu_dai, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) { struct ssp_priv *priv = cpu_dai->private_data; struct ssp_device *ssp = priv->dev.ssp; @@ -760,13 +760,13 @@ struct snd_soc_dai pxa_ssp_dai[] = { .resume = pxa_ssp_resume, .playback = { .channels_min = 1, - .channels_max = 2, + .channels_max = 8, .rates = PXA_SSP_RATES, .formats = PXA_SSP_FORMATS, }, .capture = { .channels_min = 1, - .channels_max = 2, + .channels_max = 8, .rates = PXA_SSP_RATES, .formats = PXA_SSP_FORMATS, }, @@ -780,13 +780,13 @@ struct snd_soc_dai pxa_ssp_dai[] = { .resume = pxa_ssp_resume, .playback = { .channels_min = 1, - .channels_max = 2, + .channels_max = 8, .rates = PXA_SSP_RATES, .formats = PXA_SSP_FORMATS, }, .capture = { .channels_min = 1, - .channels_max = 2, + .channels_max = 8, .rates = PXA_SSP_RATES, .formats = PXA_SSP_FORMATS, }, @@ -801,13 +801,13 @@ struct snd_soc_dai pxa_ssp_dai[] = { .resume = pxa_ssp_resume, .playback = { .channels_min = 1, - .channels_max = 2, + .channels_max = 8, .rates = PXA_SSP_RATES, .formats = PXA_SSP_FORMATS, }, .capture = { .channels_min = 1, - .channels_max = 2, + .channels_max = 8, .rates = PXA_SSP_RATES, .formats = PXA_SSP_FORMATS, }, @@ -822,13 +822,13 @@ struct snd_soc_dai pxa_ssp_dai[] = { .resume = pxa_ssp_resume, .playback = { .channels_min = 1, - .channels_max = 2, + .channels_max = 8, .rates = PXA_SSP_RATES, .formats = PXA_SSP_FORMATS, }, .capture = { .channels_min = 1, - .channels_max = 2, + .channels_max = 8, .rates = PXA_SSP_RATES, .formats = PXA_SSP_FORMATS, }, diff --git a/sound/soc/pxa/zylonite.c b/sound/soc/pxa/zylonite.c index 9a386b4c4ed..dd678ae2439 100644 --- a/sound/soc/pxa/zylonite.c +++ b/sound/soc/pxa/zylonite.c @@ -74,7 +74,8 @@ static const struct snd_soc_dapm_route audio_map[] = { static int zylonite_wm9713_init(struct snd_soc_codec *codec) { if (clk_pout) - snd_soc_dai_set_pll(&codec->dai[0], 0, clk_get_rate(pout), 0); + snd_soc_dai_set_pll(&codec->dai[0], 0, 0, + clk_get_rate(pout), 0); snd_soc_dapm_new_controls(codec, zylonite_dapm_widgets, ARRAY_SIZE(zylonite_dapm_widgets)); @@ -128,7 +129,7 @@ static int zylonite_voice_hw_params(struct snd_pcm_substream *substream, if (ret < 0) return ret; - ret = snd_soc_dai_set_pll(cpu_dai, 0, 0, pll_out); + ret = snd_soc_dai_set_pll(cpu_dai, 0, 0, 0, pll_out); if (ret < 0) return ret; diff --git a/sound/soc/s3c24xx/Kconfig b/sound/soc/s3c24xx/Kconfig index 923428fc1ad..d7912f1e462 100644 --- a/sound/soc/s3c24xx/Kconfig +++ b/sound/soc/s3c24xx/Kconfig @@ -56,6 +56,15 @@ config SND_S3C24XX_SOC_JIVE_WM8750 help Sat Y if you want to add support for SoC audio on the Jive. +config SND_S3C64XX_SOC_WM8580 + tristate "SoC I2S Audio support for WM8580 on SMDK64XX" + depends on SND_S3C24XX_SOC && (MACH_SMDK6400 || MACH_SMDK6410) + depends on BROKEN + select SND_SOC_WM8580 + select SND_S3C64XX_SOC_I2S + help + Sat Y if you want to add support for SoC audio on the SMDK64XX. + config SND_S3C24XX_SOC_SMDK2443_WM9710 tristate "SoC AC97 Audio support for SMDK2443 - WM9710" depends on SND_S3C24XX_SOC && MACH_SMDK2443 diff --git a/sound/soc/s3c24xx/Makefile b/sound/soc/s3c24xx/Makefile index 99f5a7dd3fc..7790406f90b 100644 --- a/sound/soc/s3c24xx/Makefile +++ b/sound/soc/s3c24xx/Makefile @@ -23,6 +23,7 @@ snd-soc-s3c24xx-uda134x-objs := s3c24xx_uda134x.o snd-soc-s3c24xx-simtec-objs := s3c24xx_simtec.o snd-soc-s3c24xx-simtec-hermes-objs := s3c24xx_simtec_hermes.o snd-soc-s3c24xx-simtec-tlv320aic23-objs := s3c24xx_simtec_tlv320aic23.o +snd-soc-smdk64xx-wm8580-objs := smdk64xx_wm8580.o obj-$(CONFIG_SND_S3C24XX_SOC_JIVE_WM8750) += snd-soc-jive-wm8750.o obj-$(CONFIG_SND_S3C24XX_SOC_NEO1973_WM8753) += snd-soc-neo1973-wm8753.o @@ -33,4 +34,5 @@ obj-$(CONFIG_SND_S3C24XX_SOC_S3C24XX_UDA134X) += snd-soc-s3c24xx-uda134x.o obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC) += snd-soc-s3c24xx-simtec.o obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_HERMES) += snd-soc-s3c24xx-simtec-hermes.o obj-$(CONFIG_SND_S3C24XX_SOC_SIMTEC_TLV320AIC23) += snd-soc-s3c24xx-simtec-tlv320aic23.o +obj-$(CONFIG_SND_S3C64XX_SOC_WM8580) += snd-soc-smdk64xx-wm8580.o diff --git a/sound/soc/s3c24xx/neo1973_gta02_wm8753.c b/sound/soc/s3c24xx/neo1973_gta02_wm8753.c index 0c52e36ddd8..26409a9cef9 100644 --- a/sound/soc/s3c24xx/neo1973_gta02_wm8753.c +++ b/sound/soc/s3c24xx/neo1973_gta02_wm8753.c @@ -119,7 +119,7 @@ static int neo1973_gta02_hifi_hw_params(struct snd_pcm_substream *substream, return ret; /* codec PLL input is PCLK/4 */ - ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, + ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, iis_clkrate / 4, pll_out); if (ret < 0) return ret; @@ -133,7 +133,7 @@ static int neo1973_gta02_hifi_hw_free(struct snd_pcm_substream *substream) struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; /* disable the PLL */ - return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0); + return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0, 0); } /* @@ -183,7 +183,7 @@ static int neo1973_gta02_voice_hw_params( return ret; /* configue and enable PLL for 12.288MHz output */ - ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, + ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, iis_clkrate / 4, 12288000); if (ret < 0) return ret; @@ -197,7 +197,7 @@ static int neo1973_gta02_voice_hw_free(struct snd_pcm_substream *substream) struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; /* disable the PLL */ - return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0); + return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0, 0); } static struct snd_soc_ops neo1973_gta02_voice_ops = { diff --git a/sound/soc/s3c24xx/neo1973_wm8753.c b/sound/soc/s3c24xx/neo1973_wm8753.c index 906709e6dd5..c9b794843a7 100644 --- a/sound/soc/s3c24xx/neo1973_wm8753.c +++ b/sound/soc/s3c24xx/neo1973_wm8753.c @@ -137,7 +137,7 @@ static int neo1973_hifi_hw_params(struct snd_pcm_substream *substream, return ret; /* codec PLL input is PCLK/4 */ - ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, + ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, iis_clkrate / 4, pll_out); if (ret < 0) return ret; @@ -153,7 +153,7 @@ static int neo1973_hifi_hw_free(struct snd_pcm_substream *substream) pr_debug("Entered %s\n", __func__); /* disable the PLL */ - return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0); + return snd_soc_dai_set_pll(codec_dai, WM8753_PLL1, 0, 0, 0); } /* @@ -203,7 +203,7 @@ static int neo1973_voice_hw_params(struct snd_pcm_substream *substream, return ret; /* configue and enable PLL for 12.288MHz output */ - ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, + ret = snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, iis_clkrate / 4, 12288000); if (ret < 0) return ret; @@ -219,7 +219,7 @@ static int neo1973_voice_hw_free(struct snd_pcm_substream *substream) pr_debug("Entered %s\n", __func__); /* disable the PLL */ - return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0); + return snd_soc_dai_set_pll(codec_dai, WM8753_PLL2, 0, 0, 0); } static struct snd_soc_ops neo1973_voice_ops = { diff --git a/sound/soc/s3c24xx/s3c-i2s-v2.c b/sound/soc/s3c24xx/s3c-i2s-v2.c index 9bc4aa35caa..11c45a37c63 100644 --- a/sound/soc/s3c24xx/s3c-i2s-v2.c +++ b/sound/soc/s3c24xx/s3c-i2s-v2.c @@ -312,12 +312,15 @@ static int s3c2412_i2s_set_fmt(struct snd_soc_dai *cpu_dai, switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { case SND_SOC_DAIFMT_RIGHT_J: + iismod |= S3C2412_IISMOD_LR_RLOW; iismod |= S3C2412_IISMOD_SDF_MSB; break; case SND_SOC_DAIFMT_LEFT_J: + iismod |= S3C2412_IISMOD_LR_RLOW; iismod |= S3C2412_IISMOD_SDF_LSB; break; case SND_SOC_DAIFMT_I2S: + iismod &= ~S3C2412_IISMOD_LR_RLOW; iismod |= S3C2412_IISMOD_SDF_IIS; break; default: @@ -467,6 +470,31 @@ static int s3c2412_i2s_set_clkdiv(struct snd_soc_dai *cpu_dai, switch (div_id) { case S3C_I2SV2_DIV_BCLK: + if (div > 3) { + /* convert value to bit field */ + + switch (div) { + case 16: + div = S3C2412_IISMOD_BCLK_16FS; + break; + + case 32: + div = S3C2412_IISMOD_BCLK_32FS; + break; + + case 24: + div = S3C2412_IISMOD_BCLK_24FS; + break; + + case 48: + div = S3C2412_IISMOD_BCLK_48FS; + break; + + default: + return -EINVAL; + } + } + reg = readl(i2s->regs + S3C2412_IISMOD); reg &= ~S3C2412_IISMOD_BCLK_MASK; writel(reg | div, i2s->regs + S3C2412_IISMOD); @@ -626,7 +654,7 @@ int s3c_i2sv2_probe(struct platform_device *pdev, } i2s->iis_pclk = clk_get(dev, "iis"); - if (i2s->iis_pclk == NULL) { + if (IS_ERR(i2s->iis_pclk)) { dev_err(dev, "failed to get iis_clock\n"); iounmap(i2s->regs); return -ENOENT; diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.c b/sound/soc/s3c24xx/s3c64xx-i2s.c index 3c06c401d0f..43fb253a342 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.c +++ b/sound/soc/s3c24xx/s3c64xx-i2s.c @@ -99,6 +99,19 @@ static int s3c64xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, iismod |= S3C64XX_IISMOD_IMS_SYSMUX; break; + case S3C64XX_CLKSRC_CDCLK: + switch (dir) { + case SND_SOC_CLOCK_IN: + iismod |= S3C64XX_IISMOD_CDCLKCON; + break; + case SND_SOC_CLOCK_OUT: + iismod &= ~S3C64XX_IISMOD_CDCLKCON; + break; + default: + return -EINVAL; + } + break; + default: return -EINVAL; } @@ -111,8 +124,12 @@ static int s3c64xx_i2s_set_sysclk(struct snd_soc_dai *cpu_dai, struct clk *s3c64xx_i2s_get_clock(struct snd_soc_dai *dai) { struct s3c_i2sv2_info *i2s = to_info(dai); + u32 iismod = readl(i2s->regs + S3C2412_IISMOD); - return i2s->iis_cclk; + if (iismod & S3C64XX_IISMOD_IMS_SYSMUX) + return i2s->iis_cclk; + else + return i2s->iis_pclk; } EXPORT_SYMBOL_GPL(s3c64xx_i2s_get_clock); diff --git a/sound/soc/s3c24xx/s3c64xx-i2s.h b/sound/soc/s3c24xx/s3c64xx-i2s.h index 02148cee261..abe7253b55f 100644 --- a/sound/soc/s3c24xx/s3c64xx-i2s.h +++ b/sound/soc/s3c24xx/s3c64xx-i2s.h @@ -25,6 +25,7 @@ struct clk; #define S3C64XX_CLKSRC_PCLK (0) #define S3C64XX_CLKSRC_MUX (1) +#define S3C64XX_CLKSRC_CDCLK (2) extern struct snd_soc_dai s3c64xx_i2s_dai[]; diff --git a/sound/soc/s3c24xx/smdk64xx_wm8580.c b/sound/soc/s3c24xx/smdk64xx_wm8580.c new file mode 100644 index 00000000000..482aaf10eff --- /dev/null +++ b/sound/soc/s3c24xx/smdk64xx_wm8580.c @@ -0,0 +1,273 @@ +/* + * smdk64xx_wm8580.c + * + * Copyright (c) 2009 Samsung Electronics Co. Ltd + * Author: Jaswinder Singh <jassi.brar@samsung.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/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/soc-dapm.h> + +#include "../codecs/wm8580.h" +#include "s3c24xx-pcm.h" +#include "s3c64xx-i2s.h" + +#define S3C64XX_I2S_V4 2 + +/* SMDK64XX has a 12MHZ crystal attached to WM8580 */ +#define SMDK64XX_WM8580_FREQ 12000000 + +static int smdk64xx_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *cpu_dai = rtd->dai->cpu_dai; + struct snd_soc_dai *codec_dai = rtd->dai->codec_dai; + unsigned int pll_out; + int bfs, rfs, ret; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_U8: + case SNDRV_PCM_FORMAT_S8: + bfs = 16; + break; + case SNDRV_PCM_FORMAT_U16_LE: + case SNDRV_PCM_FORMAT_S16_LE: + bfs = 32; + break; + default: + return -EINVAL; + } + + /* The Fvco for WM8580 PLLs must fall within [90,100]MHz. + * This criterion can't be met if we request PLL output + * as {8000x256, 64000x256, 11025x256}Hz. + * As a wayout, we rather change rfs to a minimum value that + * results in (params_rate(params) * rfs), and itself, acceptable + * to both - the CODEC and the CPU. + */ + switch (params_rate(params)) { + case 16000: + case 22050: + case 32000: + case 44100: + case 48000: + case 88200: + case 96000: + rfs = 256; + break; + case 64000: + rfs = 384; + break; + case 8000: + case 11025: + rfs = 512; + break; + default: + return -EINVAL; + } + pll_out = params_rate(params) * rfs; + + /* Set the Codec DAI configuration */ + ret = snd_soc_dai_set_fmt(codec_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + /* Set the AP DAI configuration */ + ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_I2S + | SND_SOC_DAIFMT_NB_NF + | SND_SOC_DAIFMT_CBM_CFM); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_sysclk(cpu_dai, S3C64XX_CLKSRC_CDCLK, + 0, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* We use PCLK for basic ops in SoC-Slave mode */ + ret = snd_soc_dai_set_sysclk(cpu_dai, S3C64XX_CLKSRC_PCLK, + 0, SND_SOC_CLOCK_IN); + if (ret < 0) + return ret; + + /* Set WM8580 to drive MCLK from it's PLLA */ + ret = snd_soc_dai_set_clkdiv(codec_dai, WM8580_MCLK, + WM8580_CLKSRC_PLLA); + if (ret < 0) + return ret; + + /* Explicitly set WM8580-DAC to source from MCLK */ + ret = snd_soc_dai_set_clkdiv(codec_dai, WM8580_DAC_CLKSEL, + WM8580_CLKSRC_MCLK); + if (ret < 0) + return ret; + + /* Assuming the CODEC driver evaluates it's rfs too from this call */ + ret = snd_soc_dai_set_pll(codec_dai, 0, WM8580_PLLA, + SMDK64XX_WM8580_FREQ, pll_out); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_I2SV2_DIV_BCLK, bfs); + if (ret < 0) + return ret; + + ret = snd_soc_dai_set_clkdiv(cpu_dai, S3C_I2SV2_DIV_RCLK, rfs); + if (ret < 0) + return ret; + + return 0; +} + +/* + * SMDK64XX WM8580 DAI operations. + */ +static struct snd_soc_ops smdk64xx_ops = { + .hw_params = smdk64xx_hw_params, +}; + +/* SMDK64xx Playback widgets */ +static const struct snd_soc_dapm_widget wm8580_dapm_widgets_pbk[] = { + SND_SOC_DAPM_HP("Front-L/R", NULL), + SND_SOC_DAPM_HP("Center/Sub", NULL), + SND_SOC_DAPM_HP("Rear-L/R", NULL), +}; + +/* SMDK64xx Capture widgets */ +static const struct snd_soc_dapm_widget wm8580_dapm_widgets_cpt[] = { + SND_SOC_DAPM_MIC("MicIn", NULL), + SND_SOC_DAPM_LINE("LineIn", NULL), +}; + +/* SMDK-PAIFTX connections */ +static const struct snd_soc_dapm_route audio_map_tx[] = { + /* MicIn feeds AINL */ + {"AINL", NULL, "MicIn"}, + + /* LineIn feeds AINL/R */ + {"AINL", NULL, "LineIn"}, + {"AINR", NULL, "LineIn"}, +}; + +/* SMDK-PAIFRX connections */ +static const struct snd_soc_dapm_route audio_map_rx[] = { + /* Front Left/Right are fed VOUT1L/R */ + {"Front-L/R", NULL, "VOUT1L"}, + {"Front-L/R", NULL, "VOUT1R"}, + + /* Center/Sub are fed VOUT2L/R */ + {"Center/Sub", NULL, "VOUT2L"}, + {"Center/Sub", NULL, "VOUT2R"}, + + /* Rear Left/Right are fed VOUT3L/R */ + {"Rear-L/R", NULL, "VOUT3L"}, + {"Rear-L/R", NULL, "VOUT3R"}, +}; + +static int smdk64xx_wm8580_init_paiftx(struct snd_soc_codec *codec) +{ + /* Add smdk64xx specific Capture widgets */ + snd_soc_dapm_new_controls(codec, wm8580_dapm_widgets_cpt, + ARRAY_SIZE(wm8580_dapm_widgets_cpt)); + + /* Set up PAIFTX audio path */ + snd_soc_dapm_add_routes(codec, audio_map_tx, ARRAY_SIZE(audio_map_tx)); + + /* All enabled by default */ + snd_soc_dapm_enable_pin(codec, "MicIn"); + snd_soc_dapm_enable_pin(codec, "LineIn"); + + /* signal a DAPM event */ + snd_soc_dapm_sync(codec); + + return 0; +} + +static int smdk64xx_wm8580_init_paifrx(struct snd_soc_codec *codec) +{ + /* Add smdk64xx specific Playback widgets */ + snd_soc_dapm_new_controls(codec, wm8580_dapm_widgets_pbk, + ARRAY_SIZE(wm8580_dapm_widgets_pbk)); + + /* Set up PAIFRX audio path */ + snd_soc_dapm_add_routes(codec, audio_map_rx, ARRAY_SIZE(audio_map_rx)); + + /* All enabled by default */ + snd_soc_dapm_enable_pin(codec, "Front-L/R"); + snd_soc_dapm_enable_pin(codec, "Center/Sub"); + snd_soc_dapm_enable_pin(codec, "Rear-L/R"); + + /* signal a DAPM event */ + snd_soc_dapm_sync(codec); + + return 0; +} + +static struct snd_soc_dai_link smdk64xx_dai[] = { +{ /* Primary Playback i/f */ + .name = "WM8580 PAIF RX", + .stream_name = "Playback", + .cpu_dai = &s3c64xx_i2s_dai[S3C64XX_I2S_V4], + .codec_dai = &wm8580_dai[WM8580_DAI_PAIFRX], + .init = smdk64xx_wm8580_init_paifrx, + .ops = &smdk64xx_ops, +}, +{ /* Primary Capture i/f */ + .name = "WM8580 PAIF TX", + .stream_name = "Capture", + .cpu_dai = &s3c64xx_i2s_dai[S3C64XX_I2S_V4], + .codec_dai = &wm8580_dai[WM8580_DAI_PAIFTX], + .init = smdk64xx_wm8580_init_paiftx, + .ops = &smdk64xx_ops, +}, +}; + +static struct snd_soc_card smdk64xx = { + .name = "smdk64xx", + .platform = &s3c24xx_soc_platform, + .dai_link = smdk64xx_dai, + .num_links = ARRAY_SIZE(smdk64xx_dai), +}; + +static struct snd_soc_device smdk64xx_snd_devdata = { + .card = &smdk64xx, + .codec_dev = &soc_codec_dev_wm8580, +}; + +static struct platform_device *smdk64xx_snd_device; + +static int __init smdk64xx_audio_init(void) +{ + int ret; + + smdk64xx_snd_device = platform_device_alloc("soc-audio", -1); + if (!smdk64xx_snd_device) + return -ENOMEM; + + platform_set_drvdata(smdk64xx_snd_device, &smdk64xx_snd_devdata); + smdk64xx_snd_devdata.dev = &smdk64xx_snd_device->dev; + ret = platform_device_add(smdk64xx_snd_device); + + if (ret) + platform_device_put(smdk64xx_snd_device); + + return ret; +} +module_init(smdk64xx_audio_init); + +MODULE_AUTHOR("Jaswinder Singh, jassi.brar@samsung.com"); +MODULE_DESCRIPTION("ALSA SoC SMDK64XX WM8580"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c index c8ceddc2a26..d2505e8b06c 100644 --- a/sound/soc/soc-cache.c +++ b/sound/soc/soc-cache.c @@ -77,6 +77,35 @@ static int snd_soc_7_9_spi_write(void *control_data, const char *data, #define snd_soc_7_9_spi_write NULL #endif +static int snd_soc_8_8_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + u8 *cache = codec->reg_cache; + u8 data[2]; + + BUG_ON(codec->volatile_register); + + data[0] = reg & 0xff; + data[1] = value & 0xff; + + if (reg < codec->reg_cache_size) + cache[reg] = value; + + if (codec->hw_write(codec->control_data, data, 2) == 2) + return 0; + else + return -EIO; +} + +static unsigned int snd_soc_8_8_read(struct snd_soc_codec *codec, + unsigned int reg) +{ + u8 *cache = codec->reg_cache; + if (reg >= codec->reg_cache_size) + return -1; + return cache[reg]; +} + static int snd_soc_8_16_write(struct snd_soc_codec *codec, unsigned int reg, unsigned int value) { @@ -150,9 +179,20 @@ static struct { unsigned int (*read)(struct snd_soc_codec *, unsigned int); unsigned int (*i2c_read)(struct snd_soc_codec *, unsigned int); } io_types[] = { - { 7, 9, snd_soc_7_9_write, snd_soc_7_9_spi_write, snd_soc_7_9_read }, - { 8, 16, snd_soc_8_16_write, NULL, snd_soc_8_16_read, - snd_soc_8_16_read_i2c }, + { + .addr_bits = 7, .data_bits = 9, + .write = snd_soc_7_9_write, .read = snd_soc_7_9_read, + .spi_write = snd_soc_7_9_spi_write + }, + { + .addr_bits = 8, .data_bits = 8, + .write = snd_soc_8_8_write, .read = snd_soc_8_8_read, + }, + { + .addr_bits = 8, .data_bits = 16, + .write = snd_soc_8_16_write, .read = snd_soc_8_16_read, + .i2c_read = snd_soc_8_16_read_i2c, + }, }; /** diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 7ff04ad2a97..1dec9d21c55 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1254,21 +1254,39 @@ static const struct file_operations codec_reg_fops = { static void soc_init_codec_debugfs(struct snd_soc_codec *codec) { + char codec_root[128]; + + if (codec->dev) + snprintf(codec_root, sizeof(codec_root), + "%s.%s", codec->name, dev_name(codec->dev)); + else + snprintf(codec_root, sizeof(codec_root), + "%s", codec->name); + + codec->debugfs_codec_root = debugfs_create_dir(codec_root, + debugfs_root); + if (!codec->debugfs_codec_root) { + printk(KERN_WARNING + "ASoC: Failed to create codec debugfs directory\n"); + return; + } + codec->debugfs_reg = debugfs_create_file("codec_reg", 0644, - debugfs_root, codec, - &codec_reg_fops); + codec->debugfs_codec_root, + codec, &codec_reg_fops); if (!codec->debugfs_reg) printk(KERN_WARNING "ASoC: Failed to create codec register debugfs file\n"); codec->debugfs_pop_time = debugfs_create_u32("dapm_pop_time", 0744, - debugfs_root, + codec->debugfs_codec_root, &codec->pop_time); if (!codec->debugfs_pop_time) printk(KERN_WARNING "Failed to create pop time debugfs file\n"); - codec->debugfs_dapm = debugfs_create_dir("dapm", debugfs_root); + codec->debugfs_dapm = debugfs_create_dir("dapm", + codec->debugfs_codec_root); if (!codec->debugfs_dapm) printk(KERN_WARNING "Failed to create DAPM debugfs directory\n"); @@ -1278,9 +1296,7 @@ static void soc_init_codec_debugfs(struct snd_soc_codec *codec) static void soc_cleanup_codec_debugfs(struct snd_soc_codec *codec) { - debugfs_remove_recursive(codec->debugfs_dapm); - debugfs_remove(codec->debugfs_pop_time); - debugfs_remove(codec->debugfs_reg); + debugfs_remove_recursive(codec->debugfs_codec_root); } #else @@ -2197,16 +2213,18 @@ EXPORT_SYMBOL_GPL(snd_soc_dai_set_clkdiv); * snd_soc_dai_set_pll - configure DAI PLL. * @dai: DAI * @pll_id: DAI specific PLL ID + * @source: DAI specific source for the PLL * @freq_in: PLL input clock frequency in Hz * @freq_out: requested PLL output clock frequency in Hz * * Configures and enables PLL to generate output clock based on input clock. */ -int snd_soc_dai_set_pll(struct snd_soc_dai *dai, - int pll_id, unsigned int freq_in, unsigned int freq_out) +int snd_soc_dai_set_pll(struct snd_soc_dai *dai, int pll_id, int source, + unsigned int freq_in, unsigned int freq_out) { if (dai->ops && dai->ops->set_pll) - return dai->ops->set_pll(dai, pll_id, freq_in, freq_out); + return dai->ops->set_pll(dai, pll_id, source, + freq_in, freq_out); else return -EINVAL; } @@ -2251,6 +2269,30 @@ int snd_soc_dai_set_tdm_slot(struct snd_soc_dai *dai, EXPORT_SYMBOL_GPL(snd_soc_dai_set_tdm_slot); /** + * snd_soc_dai_set_channel_map - configure DAI audio channel map + * @dai: DAI + * @tx_num: how many TX channels + * @tx_slot: pointer to an array which imply the TX slot number channel + * 0~num-1 uses + * @rx_num: how many RX channels + * @rx_slot: pointer to an array which imply the RX slot number channel + * 0~num-1 uses + * + * configure the relationship between channel number and TDM slot number. + */ +int snd_soc_dai_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) +{ + if (dai->ops && dai->ops->set_channel_map) + return dai->ops->set_channel_map(dai, tx_num, tx_slot, + rx_num, rx_slot); + else + return -EINVAL; +} +EXPORT_SYMBOL_GPL(snd_soc_dai_set_channel_map); + +/** * snd_soc_dai_set_tristate - configure DAI system or master clock. * @dai: DAI * @tristate: tristate enable diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 8de6f9dec4a..d2af872e477 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -719,6 +719,10 @@ static int dapm_supply_check_power(struct snd_soc_dapm_widget *w) /* Check if one of our outputs is connected */ list_for_each_entry(path, &w->sinks, list_source) { + if (path->connected && + !path->connected(path->source, path->sink)) + continue; + if (path->sink && path->sink->power_check && path->sink->power_check(path->sink)) { power = 1; @@ -1138,6 +1142,9 @@ static ssize_t dapm_widget_power_read_file(struct file *file, w->active ? "active" : "inactive"); list_for_each_entry(p, &w->sources, list_sink) { + if (p->connected && !p->connected(w, p->sink)) + continue; + if (p->connect) ret += snprintf(buf + ret, PAGE_SIZE - ret, " in %s %s\n", @@ -1145,6 +1152,9 @@ static ssize_t dapm_widget_power_read_file(struct file *file, p->source->name); } list_for_each_entry(p, &w->sinks, list_source) { + if (p->connected && !p->connected(w, p->sink)) + continue; + if (p->connect) ret += snprintf(buf + ret, PAGE_SIZE - ret, " out %s %s\n", @@ -1192,8 +1202,8 @@ void snd_soc_dapm_debugfs_init(struct snd_soc_codec *codec) /* test and update the power status of a mux widget */ static int dapm_mux_update_power(struct snd_soc_dapm_widget *widget, - struct snd_kcontrol *kcontrol, int mask, - int mux, int val, struct soc_enum *e) + struct snd_kcontrol *kcontrol, int change, + int mux, struct soc_enum *e) { struct snd_soc_dapm_path *path; int found = 0; @@ -1202,7 +1212,7 @@ static int dapm_mux_update_power(struct snd_soc_dapm_widget *widget, widget->id != snd_soc_dapm_value_mux) return -ENODEV; - if (!snd_soc_test_bits(widget->codec, e->reg, mask, val)) + if (!change) return 0; /* find dapm widget path assoc with kcontrol */ @@ -1387,10 +1397,13 @@ int snd_soc_dapm_sync(struct snd_soc_codec *codec) EXPORT_SYMBOL_GPL(snd_soc_dapm_sync); static int snd_soc_dapm_add_route(struct snd_soc_codec *codec, - const char *sink, const char *control, const char *source) + const struct snd_soc_dapm_route *route) { struct snd_soc_dapm_path *path; struct snd_soc_dapm_widget *wsource = NULL, *wsink = NULL, *w; + const char *sink = route->sink; + const char *control = route->control; + const char *source = route->source; int ret = 0; /* find src and dest widgets */ @@ -1414,6 +1427,7 @@ static int snd_soc_dapm_add_route(struct snd_soc_codec *codec, path->source = wsource; path->sink = wsink; + path->connected = route->connected; INIT_LIST_HEAD(&path->list); INIT_LIST_HEAD(&path->list_source); INIT_LIST_HEAD(&path->list_sink); @@ -1514,8 +1528,7 @@ int snd_soc_dapm_add_routes(struct snd_soc_codec *codec, int i, ret; for (i = 0; i < num; i++) { - ret = snd_soc_dapm_add_route(codec, route->sink, - route->control, route->source); + ret = snd_soc_dapm_add_route(codec, route); if (ret < 0) { printk(KERN_ERR "Failed to add route %s->%s\n", route->source, @@ -1752,7 +1765,7 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - unsigned int val, mux; + unsigned int val, mux, change; unsigned int mask, bitmask; int ret = 0; @@ -1772,20 +1785,21 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, mutex_lock(&widget->codec->mutex); widget->value = val; - dapm_mux_update_power(widget, kcontrol, mask, mux, val, e); - if (widget->event) { - if (widget->event_flags & SND_SOC_DAPM_PRE_REG) { - ret = widget->event(widget, - kcontrol, SND_SOC_DAPM_PRE_REG); - if (ret < 0) - goto out; - } - ret = snd_soc_update_bits(widget->codec, e->reg, mask, val); - if (widget->event_flags & SND_SOC_DAPM_POST_REG) - ret = widget->event(widget, - kcontrol, SND_SOC_DAPM_POST_REG); - } else - ret = snd_soc_update_bits(widget->codec, e->reg, mask, val); + change = snd_soc_test_bits(widget->codec, e->reg, mask, val); + dapm_mux_update_power(widget, kcontrol, change, mux, e); + + if (widget->event_flags & SND_SOC_DAPM_PRE_REG) { + ret = widget->event(widget, + kcontrol, SND_SOC_DAPM_PRE_REG); + if (ret < 0) + goto out; + } + + ret = snd_soc_update_bits(widget->codec, e->reg, mask, val); + + if (widget->event_flags & SND_SOC_DAPM_POST_REG) + ret = widget->event(widget, + kcontrol, SND_SOC_DAPM_POST_REG); out: mutex_unlock(&widget->codec->mutex); @@ -1794,6 +1808,54 @@ out: EXPORT_SYMBOL_GPL(snd_soc_dapm_put_enum_double); /** + * snd_soc_dapm_get_enum_virt - Get virtual DAPM mux + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Returns 0 for success. + */ +int snd_soc_dapm_get_enum_virt(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); + + ucontrol->value.enumerated.item[0] = widget->value; + + return 0; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_get_enum_virt); + +/** + * snd_soc_dapm_put_enum_virt - Set virtual DAPM mux + * @kcontrol: mixer control + * @ucontrol: control element information + * + * Returns 0 for success. + */ +int snd_soc_dapm_put_enum_virt(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); + struct soc_enum *e = + (struct soc_enum *)kcontrol->private_value; + int change; + int ret = 0; + + if (ucontrol->value.enumerated.item[0] >= e->max) + return -EINVAL; + + mutex_lock(&widget->codec->mutex); + + change = widget->value != ucontrol->value.enumerated.item[0]; + widget->value = ucontrol->value.enumerated.item[0]; + dapm_mux_update_power(widget, kcontrol, change, widget->value, e); + + mutex_unlock(&widget->codec->mutex); + return ret; +} +EXPORT_SYMBOL_GPL(snd_soc_dapm_put_enum_virt); + +/** * snd_soc_dapm_get_value_enum_double - dapm semi enumerated double mixer get * callback * @kcontrol: mixer control @@ -1851,7 +1913,7 @@ int snd_soc_dapm_put_value_enum_double(struct snd_kcontrol *kcontrol, { struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; - unsigned int val, mux; + unsigned int val, mux, change; unsigned int mask; int ret = 0; @@ -1869,20 +1931,21 @@ int snd_soc_dapm_put_value_enum_double(struct snd_kcontrol *kcontrol, mutex_lock(&widget->codec->mutex); widget->value = val; - dapm_mux_update_power(widget, kcontrol, mask, mux, val, e); - if (widget->event) { - if (widget->event_flags & SND_SOC_DAPM_PRE_REG) { - ret = widget->event(widget, - kcontrol, SND_SOC_DAPM_PRE_REG); - if (ret < 0) - goto out; - } - ret = snd_soc_update_bits(widget->codec, e->reg, mask, val); - if (widget->event_flags & SND_SOC_DAPM_POST_REG) - ret = widget->event(widget, - kcontrol, SND_SOC_DAPM_POST_REG); - } else - ret = snd_soc_update_bits(widget->codec, e->reg, mask, val); + change = snd_soc_test_bits(widget->codec, e->reg, mask, val); + dapm_mux_update_power(widget, kcontrol, change, mux, e); + + if (widget->event_flags & SND_SOC_DAPM_PRE_REG) { + ret = widget->event(widget, + kcontrol, SND_SOC_DAPM_PRE_REG); + if (ret < 0) + goto out; + } + + ret = snd_soc_update_bits(widget->codec, e->reg, mask, val); + + if (widget->event_flags & SND_SOC_DAPM_POST_REG) + ret = widget->event(widget, + kcontrol, SND_SOC_DAPM_POST_REG); out: mutex_unlock(&widget->codec->mutex); |