diff options
Diffstat (limited to 'sound/soc/codecs')
28 files changed, 3369 insertions, 319 deletions
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig index 98175a096df..ff43405752a 100644 --- a/sound/soc/codecs/Kconfig +++ b/sound/soc/codecs/Kconfig @@ -17,6 +17,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_AD193X if SND_SOC_I2C_AND_SPI select SND_SOC_AD1980 if SND_SOC_AC97_BUS select SND_SOC_AD73311 + select SND_SOC_ADAV80X select SND_SOC_ADS117X select SND_SOC_AK4104 if SPI_MASTER select SND_SOC_AK4535 if I2C @@ -42,6 +43,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_SN95031 if INTEL_SCU_IPC select SND_SOC_SPDIF select SND_SOC_SSM2602 if SND_SOC_I2C_AND_SPI + select SND_SOC_STA32X if I2C select SND_SOC_STAC9766 if SND_SOC_AC97_BUS select SND_SOC_TLV320AIC23 if I2C select SND_SOC_TLV320AIC26 if SPI_MASTER @@ -71,6 +73,7 @@ config SND_SOC_ALL_CODECS select SND_SOC_WM8753 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8770 if SPI_MASTER select SND_SOC_WM8776 if SND_SOC_I2C_AND_SPI + select SND_SOC_WM8782 select SND_SOC_WM8804 if SND_SOC_I2C_AND_SPI select SND_SOC_WM8900 if I2C select SND_SOC_WM8903 if I2C @@ -130,7 +133,14 @@ config SND_SOC_AD1980 config SND_SOC_AD73311 tristate - + +config SND_SOC_ADAU1701 + select SIGMA + tristate + +config SND_SOC_ADAV80X + tristate + config SND_SOC_ADS117X tristate @@ -216,6 +226,9 @@ config SND_SOC_SPDIF config SND_SOC_SSM2602 tristate +config SND_SOC_STA32X + tristate + config SND_SOC_STAC9766 tristate @@ -299,6 +312,9 @@ config SND_SOC_WM8770 config SND_SOC_WM8776 tristate +config SND_SOC_WM8782 + tristate + config SND_SOC_WM8804 tristate diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile index fd8558406ef..4957431e23f 100644 --- a/sound/soc/codecs/Makefile +++ b/sound/soc/codecs/Makefile @@ -4,6 +4,8 @@ snd-soc-ad1836-objs := ad1836.o snd-soc-ad193x-objs := ad193x.o snd-soc-ad1980-objs := ad1980.o snd-soc-ad73311-objs := ad73311.o +snd-soc-adau1701-objs := adau1701.o +snd-soc-adav80x-objs := adav80x.o snd-soc-ads117x-objs := ads117x.o snd-soc-ak4104-objs := ak4104.o snd-soc-ak4535-objs := ak4535.o @@ -28,6 +30,7 @@ snd-soc-alc5623-objs := alc5623.o snd-soc-sn95031-objs := sn95031.o snd-soc-spdif-objs := spdif_transciever.o snd-soc-ssm2602-objs := ssm2602.o +snd-soc-sta32x-objs := sta32x.o snd-soc-stac9766-objs := stac9766.o snd-soc-tlv320aic23-objs := tlv320aic23.o snd-soc-tlv320aic26-objs := tlv320aic26.o @@ -55,6 +58,7 @@ snd-soc-wm8750-objs := wm8750.o snd-soc-wm8753-objs := wm8753.o snd-soc-wm8770-objs := wm8770.o snd-soc-wm8776-objs := wm8776.o +snd-soc-wm8782-objs := wm8782.o snd-soc-wm8804-objs := wm8804.o snd-soc-wm8900-objs := wm8900.o snd-soc-wm8903-objs := wm8903.o @@ -95,6 +99,8 @@ obj-$(CONFIG_SND_SOC_AD1836) += snd-soc-ad1836.o obj-$(CONFIG_SND_SOC_AD193X) += snd-soc-ad193x.o obj-$(CONFIG_SND_SOC_AD1980) += snd-soc-ad1980.o obj-$(CONFIG_SND_SOC_AD73311) += snd-soc-ad73311.o +obj-$(CONFIG_SND_SOC_ADAU1701) += snd-soc-adau1701.o +obj-$(CONFIG_SND_SOC_ADAV80X) += snd-soc-adav80x.o obj-$(CONFIG_SND_SOC_ADS117X) += snd-soc-ads117x.o obj-$(CONFIG_SND_SOC_AK4104) += snd-soc-ak4104.o obj-$(CONFIG_SND_SOC_AK4535) += snd-soc-ak4535.o @@ -120,6 +126,7 @@ obj-$(CONFIG_SND_SOC_SGTL5000) += snd-soc-sgtl5000.o obj-$(CONFIG_SND_SOC_SN95031) +=snd-soc-sn95031.o obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o +obj-$(CONFIG_SND_SOC_STA32X) += snd-soc-sta32x.o obj-$(CONFIG_SND_SOC_STAC9766) += snd-soc-stac9766.o obj-$(CONFIG_SND_SOC_TLV320AIC23) += snd-soc-tlv320aic23.o obj-$(CONFIG_SND_SOC_TLV320AIC26) += snd-soc-tlv320aic26.o @@ -147,6 +154,7 @@ obj-$(CONFIG_SND_SOC_WM8750) += snd-soc-wm8750.o obj-$(CONFIG_SND_SOC_WM8753) += snd-soc-wm8753.o obj-$(CONFIG_SND_SOC_WM8770) += snd-soc-wm8770.o obj-$(CONFIG_SND_SOC_WM8776) += snd-soc-wm8776.o +obj-$(CONFIG_SND_SOC_WM8782) += snd-soc-wm8782.o obj-$(CONFIG_SND_SOC_WM8804) += snd-soc-wm8804.o obj-$(CONFIG_SND_SOC_WM8900) += snd-soc-wm8900.o obj-$(CONFIG_SND_SOC_WM8903) += snd-soc-wm8903.o diff --git a/sound/soc/codecs/ad1836.c b/sound/soc/codecs/ad1836.c index 754c496412b..4e5c5726366 100644 --- a/sound/soc/codecs/ad1836.c +++ b/sound/soc/codecs/ad1836.c @@ -1,19 +1,10 @@ -/* - * File: sound/soc/codecs/ad1836.c - * Author: Barry Song <Barry.Song@analog.com> - * - * Created: Aug 04 2009 - * Description: Driver for AD1836 sound chip - * - * Modified: - * Copyright 2009 Analog Devices Inc. + /* + * Audio Codec driver supporting: + * AD1835A, AD1836, AD1837A, AD1838A, AD1839A * - * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * Copyright 2009-2011 Analog Devices Inc. * - * 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. + * Licensed under the GPL-2 or later. */ #include <linux/init.h> @@ -30,10 +21,15 @@ #include <linux/spi/spi.h> #include "ad1836.h" +enum ad1836_type { + AD1835, + AD1836, + AD1838, +}; + /* codec private data */ struct ad1836_priv { - enum snd_soc_control_type control_type; - void *control_data; + enum ad1836_type type; }; /* @@ -44,29 +40,60 @@ static const char *ad1836_deemp[] = {"None", "44.1kHz", "32kHz", "48kHz"}; static const struct soc_enum ad1836_deemp_enum = SOC_ENUM_SINGLE(AD1836_DAC_CTRL1, 8, 4, ad1836_deemp); -static const struct snd_kcontrol_new ad1836_snd_controls[] = { - /* DAC volume control */ - SOC_DOUBLE_R("DAC1 Volume", AD1836_DAC_L1_VOL, - AD1836_DAC_R1_VOL, 0, 0x3FF, 0), - SOC_DOUBLE_R("DAC2 Volume", AD1836_DAC_L2_VOL, - AD1836_DAC_R2_VOL, 0, 0x3FF, 0), - SOC_DOUBLE_R("DAC3 Volume", AD1836_DAC_L3_VOL, - AD1836_DAC_R3_VOL, 0, 0x3FF, 0), - - /* ADC switch control */ - SOC_DOUBLE("ADC1 Switch", AD1836_ADC_CTRL2, AD1836_ADCL1_MUTE, - AD1836_ADCR1_MUTE, 1, 1), - SOC_DOUBLE("ADC2 Switch", AD1836_ADC_CTRL2, AD1836_ADCL2_MUTE, - AD1836_ADCR2_MUTE, 1, 1), - - /* DAC switch control */ - SOC_DOUBLE("DAC1 Switch", AD1836_DAC_CTRL2, AD1836_DACL1_MUTE, - AD1836_DACR1_MUTE, 1, 1), - SOC_DOUBLE("DAC2 Switch", AD1836_DAC_CTRL2, AD1836_DACL2_MUTE, - AD1836_DACR2_MUTE, 1, 1), - SOC_DOUBLE("DAC3 Switch", AD1836_DAC_CTRL2, AD1836_DACL3_MUTE, - AD1836_DACR3_MUTE, 1, 1), +#define AD1836_DAC_VOLUME(x) \ + SOC_DOUBLE_R("DAC" #x " Playback Volume", AD1836_DAC_L_VOL(x), \ + AD1836_DAC_R_VOL(x), 0, 0x3FF, 0) + +#define AD1836_DAC_SWITCH(x) \ + SOC_DOUBLE("DAC" #x " Playback Switch", AD1836_DAC_CTRL2, \ + AD1836_MUTE_LEFT(x), AD1836_MUTE_RIGHT(x), 1, 1) + +#define AD1836_ADC_SWITCH(x) \ + SOC_DOUBLE("ADC" #x " Capture Switch", AD1836_ADC_CTRL2, \ + AD1836_MUTE_LEFT(x), AD1836_MUTE_RIGHT(x), 1, 1) + +static const struct snd_kcontrol_new ad183x_dac_controls[] = { + AD1836_DAC_VOLUME(1), + AD1836_DAC_SWITCH(1), + AD1836_DAC_VOLUME(2), + AD1836_DAC_SWITCH(2), + AD1836_DAC_VOLUME(3), + AD1836_DAC_SWITCH(3), + AD1836_DAC_VOLUME(4), + AD1836_DAC_SWITCH(4), +}; + +static const struct snd_soc_dapm_widget ad183x_dac_dapm_widgets[] = { + SND_SOC_DAPM_OUTPUT("DAC1OUT"), + SND_SOC_DAPM_OUTPUT("DAC2OUT"), + SND_SOC_DAPM_OUTPUT("DAC3OUT"), + SND_SOC_DAPM_OUTPUT("DAC4OUT"), +}; + +static const struct snd_soc_dapm_route ad183x_dac_routes[] = { + { "DAC1OUT", NULL, "DAC" }, + { "DAC2OUT", NULL, "DAC" }, + { "DAC3OUT", NULL, "DAC" }, + { "DAC4OUT", NULL, "DAC" }, +}; + +static const struct snd_kcontrol_new ad183x_adc_controls[] = { + AD1836_ADC_SWITCH(1), + AD1836_ADC_SWITCH(2), + AD1836_ADC_SWITCH(3), +}; + +static const struct snd_soc_dapm_widget ad183x_adc_dapm_widgets[] = { + SND_SOC_DAPM_INPUT("ADC1IN"), + SND_SOC_DAPM_INPUT("ADC2IN"), +}; + +static const struct snd_soc_dapm_route ad183x_adc_routes[] = { + { "ADC", NULL, "ADC1IN" }, + { "ADC", NULL, "ADC2IN" }, +}; +static const struct snd_kcontrol_new ad183x_controls[] = { /* ADC high-pass filter */ SOC_SINGLE("ADC High Pass Filter Switch", AD1836_ADC_CTRL1, AD1836_ADC_HIGHPASS_FILTER, 1, 0), @@ -75,27 +102,24 @@ static const struct snd_kcontrol_new ad1836_snd_controls[] = { SOC_ENUM("Playback Deemphasis", ad1836_deemp_enum), }; -static const struct snd_soc_dapm_widget ad1836_dapm_widgets[] = { +static const struct snd_soc_dapm_widget ad183x_dapm_widgets[] = { SND_SOC_DAPM_DAC("DAC", "Playback", AD1836_DAC_CTRL1, AD1836_DAC_POWERDOWN, 1), SND_SOC_DAPM_ADC("ADC", "Capture", SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_SUPPLY("ADC_PWR", AD1836_ADC_CTRL1, AD1836_ADC_POWERDOWN, 1, NULL, 0), - SND_SOC_DAPM_OUTPUT("DAC1OUT"), - SND_SOC_DAPM_OUTPUT("DAC2OUT"), - SND_SOC_DAPM_OUTPUT("DAC3OUT"), - SND_SOC_DAPM_INPUT("ADC1IN"), - SND_SOC_DAPM_INPUT("ADC2IN"), }; -static const struct snd_soc_dapm_route audio_paths[] = { +static const struct snd_soc_dapm_route ad183x_dapm_routes[] = { { "DAC", NULL, "ADC_PWR" }, { "ADC", NULL, "ADC_PWR" }, - { "DAC1OUT", "DAC1 Switch", "DAC" }, - { "DAC2OUT", "DAC2 Switch", "DAC" }, - { "DAC3OUT", "DAC3 Switch", "DAC" }, - { "ADC", "ADC1 Switch", "ADC1IN" }, - { "ADC", "ADC2 Switch", "ADC2IN" }, +}; + +static const DECLARE_TLV_DB_SCALE(ad1836_in_tlv, 0, 300, 0); + +static const struct snd_kcontrol_new ad1836_controls[] = { + SOC_DOUBLE_TLV("ADC2 Capture Volume", AD1836_ADC_CTRL1, 3, 0, 4, 0, + ad1836_in_tlv), }; /* @@ -165,64 +189,69 @@ static int ad1836_hw_params(struct snd_pcm_substream *substream, return 0; } +static struct snd_soc_dai_ops ad1836_dai_ops = { + .hw_params = ad1836_hw_params, + .set_fmt = ad1836_set_dai_fmt, +}; + +#define AD183X_DAI(_name, num_dacs, num_adcs) \ +{ \ + .name = _name "-hifi", \ + .playback = { \ + .stream_name = "Playback", \ + .channels_min = 2, \ + .channels_max = (num_dacs) * 2, \ + .rates = SNDRV_PCM_RATE_48000, \ + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, \ + }, \ + .capture = { \ + .stream_name = "Capture", \ + .channels_min = 2, \ + .channels_max = (num_adcs) * 2, \ + .rates = SNDRV_PCM_RATE_48000, \ + .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, \ + }, \ + .ops = &ad1836_dai_ops, \ +} + +static struct snd_soc_dai_driver ad183x_dais[] = { + [AD1835] = AD183X_DAI("ad1835", 4, 1), + [AD1836] = AD183X_DAI("ad1836", 3, 2), + [AD1838] = AD183X_DAI("ad1838", 3, 1), +}; + #ifdef CONFIG_PM -static int ad1836_soc_suspend(struct snd_soc_codec *codec, - pm_message_t state) +static int ad1836_suspend(struct snd_soc_codec *codec, pm_message_t state) { /* reset clock control mode */ - u16 adc_ctrl2 = snd_soc_read(codec, AD1836_ADC_CTRL2); - adc_ctrl2 &= ~AD1836_ADC_SERFMT_MASK; - - return snd_soc_write(codec, AD1836_ADC_CTRL2, adc_ctrl2); + return snd_soc_update_bits(codec, AD1836_ADC_CTRL2, + AD1836_ADC_SERFMT_MASK, 0); } -static int ad1836_soc_resume(struct snd_soc_codec *codec) +static int ad1836_resume(struct snd_soc_codec *codec) { /* restore clock control mode */ - u16 adc_ctrl2 = snd_soc_read(codec, AD1836_ADC_CTRL2); - adc_ctrl2 |= AD1836_ADC_AUX; - - return snd_soc_write(codec, AD1836_ADC_CTRL2, adc_ctrl2); + return snd_soc_update_bits(codec, AD1836_ADC_CTRL2, + AD1836_ADC_SERFMT_MASK, AD1836_ADC_AUX); } #else -#define ad1836_soc_suspend NULL -#define ad1836_soc_resume NULL +#define ad1836_suspend NULL +#define ad1836_resume NULL #endif -static struct snd_soc_dai_ops ad1836_dai_ops = { - .hw_params = ad1836_hw_params, - .set_fmt = ad1836_set_dai_fmt, -}; - -/* codec DAI instance */ -static struct snd_soc_dai_driver ad1836_dai = { - .name = "ad1836-hifi", - .playback = { - .stream_name = "Playback", - .channels_min = 2, - .channels_max = 6, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, - }, - .capture = { - .stream_name = "Capture", - .channels_min = 2, - .channels_max = 4, - .rates = SNDRV_PCM_RATE_48000, - .formats = SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S16_LE | - SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE, - }, - .ops = &ad1836_dai_ops, -}; - static int ad1836_probe(struct snd_soc_codec *codec) { struct ad1836_priv *ad1836 = snd_soc_codec_get_drvdata(codec); struct snd_soc_dapm_context *dapm = &codec->dapm; + int num_dacs, num_adcs; int ret = 0; + int i; + + num_dacs = ad183x_dais[ad1836->type].playback.channels_max / 2; + num_adcs = ad183x_dais[ad1836->type].capture.channels_max / 2; - codec->control_data = ad1836->control_data; ret = snd_soc_codec_set_cache_io(codec, 4, 12, SND_SOC_SPI); if (ret < 0) { dev_err(codec->dev, "failed to set cache I/O: %d\n", @@ -239,21 +268,46 @@ static int ad1836_probe(struct snd_soc_codec *codec) snd_soc_write(codec, AD1836_ADC_CTRL1, 0x100); /* unmute adc channles, adc aux mode */ snd_soc_write(codec, AD1836_ADC_CTRL2, 0x180); - /* left/right diff:PGA/MUX */ - snd_soc_write(codec, AD1836_ADC_CTRL3, 0x3A); /* volume */ - snd_soc_write(codec, AD1836_DAC_L1_VOL, 0x3FF); - snd_soc_write(codec, AD1836_DAC_R1_VOL, 0x3FF); - snd_soc_write(codec, AD1836_DAC_L2_VOL, 0x3FF); - snd_soc_write(codec, AD1836_DAC_R2_VOL, 0x3FF); - snd_soc_write(codec, AD1836_DAC_L3_VOL, 0x3FF); - snd_soc_write(codec, AD1836_DAC_R3_VOL, 0x3FF); - - snd_soc_add_controls(codec, ad1836_snd_controls, - ARRAY_SIZE(ad1836_snd_controls)); - snd_soc_dapm_new_controls(dapm, ad1836_dapm_widgets, - ARRAY_SIZE(ad1836_dapm_widgets)); - snd_soc_dapm_add_routes(dapm, audio_paths, ARRAY_SIZE(audio_paths)); + for (i = 1; i <= num_dacs; ++i) { + snd_soc_write(codec, AD1836_DAC_L_VOL(i), 0x3FF); + snd_soc_write(codec, AD1836_DAC_R_VOL(i), 0x3FF); + } + + if (ad1836->type == AD1836) { + /* left/right diff:PGA/MUX */ + snd_soc_write(codec, AD1836_ADC_CTRL3, 0x3A); + ret = snd_soc_add_controls(codec, ad1836_controls, + ARRAY_SIZE(ad1836_controls)); + if (ret) + return ret; + } else { + snd_soc_write(codec, AD1836_ADC_CTRL3, 0x00); + } + + ret = snd_soc_add_controls(codec, ad183x_dac_controls, num_dacs * 2); + if (ret) + return ret; + + ret = snd_soc_add_controls(codec, ad183x_adc_controls, num_adcs); + if (ret) + return ret; + + ret = snd_soc_dapm_new_controls(dapm, ad183x_dac_dapm_widgets, num_dacs); + if (ret) + return ret; + + ret = snd_soc_dapm_new_controls(dapm, ad183x_adc_dapm_widgets, num_adcs); + if (ret) + return ret; + + ret = snd_soc_dapm_add_routes(dapm, ad183x_dac_routes, num_dacs); + if (ret) + return ret; + + ret = snd_soc_dapm_add_routes(dapm, ad183x_adc_routes, num_adcs); + if (ret) + return ret; return ret; } @@ -262,19 +316,24 @@ static int ad1836_probe(struct snd_soc_codec *codec) static int ad1836_remove(struct snd_soc_codec *codec) { /* reset clock control mode */ - u16 adc_ctrl2 = snd_soc_read(codec, AD1836_ADC_CTRL2); - adc_ctrl2 &= ~AD1836_ADC_SERFMT_MASK; - - return snd_soc_write(codec, AD1836_ADC_CTRL2, adc_ctrl2); + return snd_soc_update_bits(codec, AD1836_ADC_CTRL2, + AD1836_ADC_SERFMT_MASK, 0); } static struct snd_soc_codec_driver soc_codec_dev_ad1836 = { - .probe = ad1836_probe, - .remove = ad1836_remove, - .suspend = ad1836_soc_suspend, - .resume = ad1836_soc_resume, + .probe = ad1836_probe, + .remove = ad1836_remove, + .suspend = ad1836_suspend, + .resume = ad1836_resume, .reg_cache_size = AD1836_NUM_REGS, .reg_word_size = sizeof(u16), + + .controls = ad183x_controls, + .num_controls = ARRAY_SIZE(ad183x_controls), + .dapm_widgets = ad183x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ad183x_dapm_widgets), + .dapm_routes = ad183x_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(ad183x_dapm_routes), }; static int __devinit ad1836_spi_probe(struct spi_device *spi) @@ -286,12 +345,12 @@ static int __devinit ad1836_spi_probe(struct spi_device *spi) if (ad1836 == NULL) return -ENOMEM; + ad1836->type = spi_get_device_id(spi)->driver_data; + spi_set_drvdata(spi, ad1836); - ad1836->control_data = spi; - ad1836->control_type = SND_SOC_SPI; ret = snd_soc_register_codec(&spi->dev, - &soc_codec_dev_ad1836, &ad1836_dai, 1); + &soc_codec_dev_ad1836, &ad183x_dais[ad1836->type], 1); if (ret < 0) kfree(ad1836); return ret; @@ -303,27 +362,29 @@ static int __devexit ad1836_spi_remove(struct spi_device *spi) kfree(spi_get_drvdata(spi)); return 0; } +static const struct spi_device_id ad1836_ids[] = { + { "ad1835", AD1835 }, + { "ad1836", AD1836 }, + { "ad1837", AD1835 }, + { "ad1838", AD1838 }, + { "ad1839", AD1838 }, + { }, +}; +MODULE_DEVICE_TABLE(spi, ad1836_ids); static struct spi_driver ad1836_spi_driver = { .driver = { - .name = "ad1836-codec", + .name = "ad1836", .owner = THIS_MODULE, }, .probe = ad1836_spi_probe, .remove = __devexit_p(ad1836_spi_remove), + .id_table = ad1836_ids, }; static int __init ad1836_init(void) { - int ret; - - ret = spi_register_driver(&ad1836_spi_driver); - if (ret != 0) { - printk(KERN_ERR "Failed to register ad1836 SPI driver: %d\n", - ret); - } - - return ret; + return spi_register_driver(&ad1836_spi_driver); } module_init(ad1836_init); diff --git a/sound/soc/codecs/ad1836.h b/sound/soc/codecs/ad1836.h index 9d6a3f8f8aa..444747f0db2 100644 --- a/sound/soc/codecs/ad1836.h +++ b/sound/soc/codecs/ad1836.h @@ -1,19 +1,10 @@ /* - * File: sound/soc/codecs/ad1836.h - * Based on: - * Author: Barry Song <Barry.Song@analog.com> + * Audio Codec driver supporting: + * AD1835A, AD1836, AD1837A, AD1838A, AD1839A * - * Created: Aug 04, 2009 - * Description: definitions for AD1836 registers + * Copyright 2009-2011 Analog Devices Inc. * - * Modified: - * - * Bugs: Enter bugs at http://blackfin.uclinux.org/ - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. + * Licensed under the GPL-2 or later. */ #ifndef __AD1836_H__ @@ -21,39 +12,30 @@ #define AD1836_DAC_CTRL1 0 #define AD1836_DAC_POWERDOWN 2 -#define AD1836_DAC_SERFMT_MASK 0xE0 +#define AD1836_DAC_SERFMT_MASK 0xE0 #define AD1836_DAC_SERFMT_PCK256 (0x4 << 5) #define AD1836_DAC_SERFMT_PCK128 (0x5 << 5) #define AD1836_DAC_WORD_LEN_MASK 0x18 #define AD1836_DAC_WORD_LEN_OFFSET 3 #define AD1836_DAC_CTRL2 1 -#define AD1836_DACL1_MUTE 0 -#define AD1836_DACR1_MUTE 1 -#define AD1836_DACL2_MUTE 2 -#define AD1836_DACR2_MUTE 3 -#define AD1836_DACL3_MUTE 4 -#define AD1836_DACR3_MUTE 5 -#define AD1836_DAC_L1_VOL 2 -#define AD1836_DAC_R1_VOL 3 -#define AD1836_DAC_L2_VOL 4 -#define AD1836_DAC_R2_VOL 5 -#define AD1836_DAC_L3_VOL 6 -#define AD1836_DAC_R3_VOL 7 +/* These macros are one-based. So AD183X_MUTE_LEFT(1) will return the mute bit + * for the first ADC/DAC */ +#define AD1836_MUTE_LEFT(x) (((x) * 2) - 2) +#define AD1836_MUTE_RIGHT(x) (((x) * 2) - 1) + +#define AD1836_DAC_L_VOL(x) ((x) * 2) +#define AD1836_DAC_R_VOL(x) (1 + ((x) * 2)) #define AD1836_ADC_CTRL1 12 #define AD1836_ADC_POWERDOWN 7 #define AD1836_ADC_HIGHPASS_FILTER 8 #define AD1836_ADC_CTRL2 13 -#define AD1836_ADCL1_MUTE 0 -#define AD1836_ADCR1_MUTE 1 -#define AD1836_ADCL2_MUTE 2 -#define AD1836_ADCR2_MUTE 3 #define AD1836_ADC_WORD_LEN_MASK 0x30 #define AD1836_ADC_WORD_OFFSET 5 -#define AD1836_ADC_SERFMT_MASK (7 << 6) +#define AD1836_ADC_SERFMT_MASK (7 << 6) #define AD1836_ADC_SERFMT_PCK256 (0x4 << 6) #define AD1836_ADC_SERFMT_PCK128 (0x5 << 6) #define AD1836_ADC_AUX (0x6 << 6) diff --git a/sound/soc/codecs/adau1701.c b/sound/soc/codecs/adau1701.c new file mode 100644 index 00000000000..2758d5fc60d --- /dev/null +++ b/sound/soc/codecs/adau1701.c @@ -0,0 +1,549 @@ +/* + * Driver for ADAU1701 SigmaDSP processor + * + * Copyright 2011 Analog Devices Inc. + * Author: Lars-Peter Clausen <lars@metafoo.de> + * based on an inital version by Cliff Cai <cliff.cai@analog.com> + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/sigma.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#include "adau1701.h" + +#define ADAU1701_DSPCTRL 0x1c +#define ADAU1701_SEROCTL 0x1e +#define ADAU1701_SERICTL 0x1f + +#define ADAU1701_AUXNPOW 0x22 + +#define ADAU1701_OSCIPOW 0x26 +#define ADAU1701_DACSET 0x27 + +#define ADAU1701_NUM_REGS 0x28 + +#define ADAU1701_DSPCTRL_CR (1 << 2) +#define ADAU1701_DSPCTRL_DAM (1 << 3) +#define ADAU1701_DSPCTRL_ADM (1 << 4) +#define ADAU1701_DSPCTRL_SR_48 0x00 +#define ADAU1701_DSPCTRL_SR_96 0x01 +#define ADAU1701_DSPCTRL_SR_192 0x02 +#define ADAU1701_DSPCTRL_SR_MASK 0x03 + +#define ADAU1701_SEROCTL_INV_LRCLK 0x2000 +#define ADAU1701_SEROCTL_INV_BCLK 0x1000 +#define ADAU1701_SEROCTL_MASTER 0x0800 + +#define ADAU1701_SEROCTL_OBF16 0x0000 +#define ADAU1701_SEROCTL_OBF8 0x0200 +#define ADAU1701_SEROCTL_OBF4 0x0400 +#define ADAU1701_SEROCTL_OBF2 0x0600 +#define ADAU1701_SEROCTL_OBF_MASK 0x0600 + +#define ADAU1701_SEROCTL_OLF1024 0x0000 +#define ADAU1701_SEROCTL_OLF512 0x0080 +#define ADAU1701_SEROCTL_OLF256 0x0100 +#define ADAU1701_SEROCTL_OLF_MASK 0x0180 + +#define ADAU1701_SEROCTL_MSB_DEALY1 0x0000 +#define ADAU1701_SEROCTL_MSB_DEALY0 0x0004 +#define ADAU1701_SEROCTL_MSB_DEALY8 0x0008 +#define ADAU1701_SEROCTL_MSB_DEALY12 0x000c +#define ADAU1701_SEROCTL_MSB_DEALY16 0x0010 +#define ADAU1701_SEROCTL_MSB_DEALY_MASK 0x001c + +#define ADAU1701_SEROCTL_WORD_LEN_24 0x0000 +#define ADAU1701_SEROCTL_WORD_LEN_20 0x0001 +#define ADAU1701_SEROCTL_WORD_LEN_16 0x0010 +#define ADAU1701_SEROCTL_WORD_LEN_MASK 0x0003 + +#define ADAU1701_AUXNPOW_VBPD 0x40 +#define ADAU1701_AUXNPOW_VRPD 0x20 + +#define ADAU1701_SERICTL_I2S 0 +#define ADAU1701_SERICTL_LEFTJ 1 +#define ADAU1701_SERICTL_TDM 2 +#define ADAU1701_SERICTL_RIGHTJ_24 3 +#define ADAU1701_SERICTL_RIGHTJ_20 4 +#define ADAU1701_SERICTL_RIGHTJ_18 5 +#define ADAU1701_SERICTL_RIGHTJ_16 6 +#define ADAU1701_SERICTL_MODE_MASK 7 +#define ADAU1701_SERICTL_INV_BCLK BIT(3) +#define ADAU1701_SERICTL_INV_LRCLK BIT(4) + +#define ADAU1701_OSCIPOW_OPD 0x04 +#define ADAU1701_DACSET_DACINIT 1 + +#define ADAU1701_FIRMWARE "adau1701.bin" + +struct adau1701 { + unsigned int dai_fmt; +}; + +static const struct snd_kcontrol_new adau1701_controls[] = { + SOC_SINGLE("Master Capture Switch", ADAU1701_DSPCTRL, 4, 1, 0), +}; + +static const struct snd_soc_dapm_widget adau1701_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC0", "Playback", ADAU1701_AUXNPOW, 3, 1), + SND_SOC_DAPM_DAC("DAC1", "Playback", ADAU1701_AUXNPOW, 2, 1), + SND_SOC_DAPM_DAC("DAC2", "Playback", ADAU1701_AUXNPOW, 1, 1), + SND_SOC_DAPM_DAC("DAC3", "Playback", ADAU1701_AUXNPOW, 0, 1), + SND_SOC_DAPM_ADC("ADC", "Capture", ADAU1701_AUXNPOW, 7, 1), + + SND_SOC_DAPM_OUTPUT("OUT0"), + SND_SOC_DAPM_OUTPUT("OUT1"), + SND_SOC_DAPM_OUTPUT("OUT2"), + SND_SOC_DAPM_OUTPUT("OUT3"), + SND_SOC_DAPM_INPUT("IN0"), + SND_SOC_DAPM_INPUT("IN1"), +}; + +static const struct snd_soc_dapm_route adau1701_dapm_routes[] = { + { "OUT0", NULL, "DAC0" }, + { "OUT1", NULL, "DAC1" }, + { "OUT2", NULL, "DAC2" }, + { "OUT3", NULL, "DAC3" }, + + { "ADC", NULL, "IN0" }, + { "ADC", NULL, "IN1" }, +}; + +static unsigned int adau1701_register_size(struct snd_soc_codec *codec, + unsigned int reg) +{ + switch (reg) { + case ADAU1701_DSPCTRL: + case ADAU1701_SEROCTL: + case ADAU1701_AUXNPOW: + case ADAU1701_OSCIPOW: + case ADAU1701_DACSET: + return 2; + case ADAU1701_SERICTL: + return 1; + } + + dev_err(codec->dev, "Unsupported register address: %d\n", reg); + return 0; +} + +static int adau1701_write(struct snd_soc_codec *codec, unsigned int reg, + unsigned int value) +{ + unsigned int i; + unsigned int size; + uint8_t buf[4]; + int ret; + + size = adau1701_register_size(codec, reg); + if (size == 0) + return -EINVAL; + + snd_soc_cache_write(codec, reg, value); + + buf[0] = 0x08; + buf[1] = reg; + + for (i = size + 1; i >= 2; --i) { + buf[i] = value; + value >>= 8; + } + + ret = i2c_master_send(to_i2c_client(codec->dev), buf, size + 2); + if (ret == size + 2) + return 0; + else if (ret < 0) + return ret; + else + return -EIO; +} + +static unsigned int adau1701_read(struct snd_soc_codec *codec, unsigned int reg) +{ + unsigned int value; + unsigned int ret; + + ret = snd_soc_cache_read(codec, reg, &value); + if (ret) + return ret; + + return value; +} + +static int adau1701_load_firmware(struct snd_soc_codec *codec) +{ + return process_sigma_firmware(codec->control_data, ADAU1701_FIRMWARE); +} + +static int adau1701_set_capture_pcm_format(struct snd_soc_codec *codec, + snd_pcm_format_t format) +{ + struct adau1701 *adau1701 = snd_soc_codec_get_drvdata(codec); + unsigned int mask = ADAU1701_SEROCTL_WORD_LEN_MASK; + unsigned int val; + + switch (format) { + case SNDRV_PCM_FORMAT_S16_LE: + val = ADAU1701_SEROCTL_WORD_LEN_16; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + val = ADAU1701_SEROCTL_WORD_LEN_20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + val = ADAU1701_SEROCTL_WORD_LEN_24; + break; + default: + return -EINVAL; + } + + if (adau1701->dai_fmt == SND_SOC_DAIFMT_RIGHT_J) { + switch (format) { + case SNDRV_PCM_FORMAT_S16_LE: + val |= ADAU1701_SEROCTL_MSB_DEALY16; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + val |= ADAU1701_SEROCTL_MSB_DEALY12; + break; + case SNDRV_PCM_FORMAT_S24_LE: + val |= ADAU1701_SEROCTL_MSB_DEALY8; + break; + } + mask |= ADAU1701_SEROCTL_MSB_DEALY_MASK; + } + + snd_soc_update_bits(codec, ADAU1701_SEROCTL, mask, val); + + return 0; +} + +static int adau1701_set_playback_pcm_format(struct snd_soc_codec *codec, + snd_pcm_format_t format) +{ + struct adau1701 *adau1701 = snd_soc_codec_get_drvdata(codec); + unsigned int val; + + if (adau1701->dai_fmt != SND_SOC_DAIFMT_RIGHT_J) + return 0; + + switch (format) { + case SNDRV_PCM_FORMAT_S16_LE: + val = ADAU1701_SERICTL_RIGHTJ_16; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + val = ADAU1701_SERICTL_RIGHTJ_20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + val = ADAU1701_SERICTL_RIGHTJ_24; + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, ADAU1701_SERICTL, + ADAU1701_SERICTL_MODE_MASK, val); + + return 0; +} + +static int adau1701_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + snd_pcm_format_t format; + unsigned int val; + + switch (params_rate(params)) { + case 192000: + val = ADAU1701_DSPCTRL_SR_192; + break; + case 96000: + val = ADAU1701_DSPCTRL_SR_96; + break; + case 48000: + val = ADAU1701_DSPCTRL_SR_48; + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, ADAU1701_DSPCTRL, + ADAU1701_DSPCTRL_SR_MASK, val); + + format = params_format(params); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + return adau1701_set_playback_pcm_format(codec, format); + else + return adau1701_set_capture_pcm_format(codec, format); +} + +static int adau1701_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct adau1701 *adau1701 = snd_soc_codec_get_drvdata(codec); + unsigned int serictl = 0x00, seroctl = 0x00; + bool invert_lrclk; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + /* master, 64-bits per sample, 1 frame per sample */ + seroctl |= ADAU1701_SEROCTL_MASTER | ADAU1701_SEROCTL_OBF16 + | ADAU1701_SEROCTL_OLF1024; + break; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + /* clock inversion */ + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + invert_lrclk = false; + break; + case SND_SOC_DAIFMT_NB_IF: + invert_lrclk = true; + break; + case SND_SOC_DAIFMT_IB_NF: + invert_lrclk = false; + serictl |= ADAU1701_SERICTL_INV_BCLK; + seroctl |= ADAU1701_SEROCTL_INV_BCLK; + break; + case SND_SOC_DAIFMT_IB_IF: + invert_lrclk = true; + serictl |= ADAU1701_SERICTL_INV_BCLK; + seroctl |= ADAU1701_SEROCTL_INV_BCLK; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + break; + case SND_SOC_DAIFMT_LEFT_J: + serictl |= ADAU1701_SERICTL_LEFTJ; + seroctl |= ADAU1701_SEROCTL_MSB_DEALY0; + invert_lrclk = !invert_lrclk; + break; + case SND_SOC_DAIFMT_RIGHT_J: + serictl |= ADAU1701_SERICTL_RIGHTJ_24; + seroctl |= ADAU1701_SEROCTL_MSB_DEALY8; + invert_lrclk = !invert_lrclk; + break; + default: + return -EINVAL; + } + + if (invert_lrclk) { + seroctl |= ADAU1701_SEROCTL_INV_LRCLK; + serictl |= ADAU1701_SERICTL_INV_LRCLK; + } + + adau1701->dai_fmt = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + + snd_soc_write(codec, ADAU1701_SERICTL, serictl); + snd_soc_update_bits(codec, ADAU1701_SEROCTL, + ~ADAU1701_SEROCTL_WORD_LEN_MASK, seroctl); + + return 0; +} + +static int adau1701_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + unsigned int mask = ADAU1701_AUXNPOW_VBPD | ADAU1701_AUXNPOW_VRPD; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + /* Enable VREF and VREF buffer */ + snd_soc_update_bits(codec, ADAU1701_AUXNPOW, mask, 0x00); + break; + case SND_SOC_BIAS_OFF: + /* Disable VREF and VREF buffer */ + snd_soc_update_bits(codec, ADAU1701_AUXNPOW, mask, mask); + break; + } + + codec->dapm.bias_level = level; + return 0; +} + +static int adau1701_digital_mute(struct snd_soc_dai *dai, int mute) +{ + struct snd_soc_codec *codec = dai->codec; + unsigned int mask = ADAU1701_DSPCTRL_DAM; + unsigned int val; + + if (mute) + val = 0; + else + val = mask; + + snd_soc_update_bits(codec, ADAU1701_DSPCTRL, mask, val); + + return 0; +} + +static int adau1701_set_sysclk(struct snd_soc_codec *codec, int clk_id, + unsigned int freq, int dir) +{ + unsigned int val; + + switch (clk_id) { + case ADAU1701_CLK_SRC_OSC: + val = 0x0; + break; + case ADAU1701_CLK_SRC_MCLK: + val = ADAU1701_OSCIPOW_OPD; + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, ADAU1701_OSCIPOW, ADAU1701_OSCIPOW_OPD, val); + + return 0; +} + +#define ADAU1701_RATES (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_192000) + +#define ADAU1701_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_3LE |\ + SNDRV_PCM_FMTBIT_S24_LE) + +static const struct snd_soc_dai_ops adau1701_dai_ops = { + .set_fmt = adau1701_set_dai_fmt, + .hw_params = adau1701_hw_params, + .digital_mute = adau1701_digital_mute, +}; + +static struct snd_soc_dai_driver adau1701_dai = { + .name = "adau1701", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 8, + .rates = ADAU1701_RATES, + .formats = ADAU1701_FORMATS, + }, + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 8, + .rates = ADAU1701_RATES, + .formats = ADAU1701_FORMATS, + }, + .ops = &adau1701_dai_ops, + .symmetric_rates = 1, +}; + +static int adau1701_probe(struct snd_soc_codec *codec) +{ + int ret; + + codec->dapm.idle_bias_off = 1; + + ret = adau1701_load_firmware(codec); + if (ret) + dev_warn(codec->dev, "Failed to load firmware\n"); + + snd_soc_write(codec, ADAU1701_DACSET, ADAU1701_DACSET_DACINIT); + snd_soc_write(codec, ADAU1701_DSPCTRL, ADAU1701_DSPCTRL_CR); + + return 0; +} + +static struct snd_soc_codec_driver adau1701_codec_drv = { + .probe = adau1701_probe, + .set_bias_level = adau1701_set_bias_level, + + .reg_cache_size = ADAU1701_NUM_REGS, + .reg_word_size = sizeof(u16), + + .controls = adau1701_controls, + .num_controls = ARRAY_SIZE(adau1701_controls), + .dapm_widgets = adau1701_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(adau1701_dapm_widgets), + .dapm_routes = adau1701_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(adau1701_dapm_routes), + + .write = adau1701_write, + .read = adau1701_read, + + .set_sysclk = adau1701_set_sysclk, +}; + +static __devinit int adau1701_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adau1701 *adau1701; + int ret; + + adau1701 = kzalloc(sizeof(*adau1701), GFP_KERNEL); + if (!adau1701) + return -ENOMEM; + + i2c_set_clientdata(client, adau1701); + ret = snd_soc_register_codec(&client->dev, &adau1701_codec_drv, + &adau1701_dai, 1); + if (ret < 0) + kfree(adau1701); + + return ret; +} + +static __devexit int adau1701_i2c_remove(struct i2c_client *client) +{ + snd_soc_unregister_codec(&client->dev); + kfree(i2c_get_clientdata(client)); + return 0; +} + +static const struct i2c_device_id adau1701_i2c_id[] = { + { "adau1701", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adau1701_i2c_id); + +static struct i2c_driver adau1701_i2c_driver = { + .driver = { + .name = "adau1701", + .owner = THIS_MODULE, + }, + .probe = adau1701_i2c_probe, + .remove = __devexit_p(adau1701_i2c_remove), + .id_table = adau1701_i2c_id, +}; + +static int __init adau1701_init(void) +{ + return i2c_add_driver(&adau1701_i2c_driver); +} +module_init(adau1701_init); + +static void __exit adau1701_exit(void) +{ + i2c_del_driver(&adau1701_i2c_driver); +} +module_exit(adau1701_exit); + +MODULE_DESCRIPTION("ASoC ADAU1701 SigmaDSP driver"); +MODULE_AUTHOR("Cliff Cai <cliff.cai@analog.com>"); +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adau1701.h b/sound/soc/codecs/adau1701.h new file mode 100644 index 00000000000..8d0949a2aec --- /dev/null +++ b/sound/soc/codecs/adau1701.h @@ -0,0 +1,17 @@ +/* + * header file for ADAU1701 SigmaDSP processor + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#ifndef _ADAU1701_H +#define _ADAU1701_H + +enum adau1701_clk_src { + ADAU1701_CLK_SRC_OSC, + ADAU1701_CLK_SRC_MCLK, +}; + +#endif diff --git a/sound/soc/codecs/adav80x.c b/sound/soc/codecs/adav80x.c new file mode 100644 index 00000000000..e30fba62392 --- /dev/null +++ b/sound/soc/codecs/adav80x.c @@ -0,0 +1,951 @@ +/* + * ADAV80X Audio Codec driver supporting ADAV801, ADAV803 + * + * Copyright 2011 Analog Devices Inc. + * Author: Yi Li <yi.li@analog.com> + * Author: Lars-Peter Clausen <lars@metafoo.de> + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/spi/spi.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/tlv.h> +#include <sound/soc.h> + +#include "adav80x.h" + +#define ADAV80X_PLAYBACK_CTRL 0x04 +#define ADAV80X_AUX_IN_CTRL 0x05 +#define ADAV80X_REC_CTRL 0x06 +#define ADAV80X_AUX_OUT_CTRL 0x07 +#define ADAV80X_DPATH_CTRL1 0x62 +#define ADAV80X_DPATH_CTRL2 0x63 +#define ADAV80X_DAC_CTRL1 0x64 +#define ADAV80X_DAC_CTRL2 0x65 +#define ADAV80X_DAC_CTRL3 0x66 +#define ADAV80X_DAC_L_VOL 0x68 +#define ADAV80X_DAC_R_VOL 0x69 +#define ADAV80X_PGA_L_VOL 0x6c +#define ADAV80X_PGA_R_VOL 0x6d +#define ADAV80X_ADC_CTRL1 0x6e +#define ADAV80X_ADC_CTRL2 0x6f +#define ADAV80X_ADC_L_VOL 0x70 +#define ADAV80X_ADC_R_VOL 0x71 +#define ADAV80X_PLL_CTRL1 0x74 +#define ADAV80X_PLL_CTRL2 0x75 +#define ADAV80X_ICLK_CTRL1 0x76 +#define ADAV80X_ICLK_CTRL2 0x77 +#define ADAV80X_PLL_CLK_SRC 0x78 +#define ADAV80X_PLL_OUTE 0x7a + +#define ADAV80X_PLL_CLK_SRC_PLL_XIN(pll) 0x00 +#define ADAV80X_PLL_CLK_SRC_PLL_MCLKI(pll) (0x40 << (pll)) +#define ADAV80X_PLL_CLK_SRC_PLL_MASK(pll) (0x40 << (pll)) + +#define ADAV80X_ICLK_CTRL1_DAC_SRC(src) ((src) << 5) +#define ADAV80X_ICLK_CTRL1_ADC_SRC(src) ((src) << 2) +#define ADAV80X_ICLK_CTRL1_ICLK2_SRC(src) (src) +#define ADAV80X_ICLK_CTRL2_ICLK1_SRC(src) ((src) << 3) + +#define ADAV80X_PLL_CTRL1_PLLDIV 0x10 +#define ADAV80X_PLL_CTRL1_PLLPD(pll) (0x04 << (pll)) +#define ADAV80X_PLL_CTRL1_XTLPD 0x02 + +#define ADAV80X_PLL_CTRL2_FIELD(pll, x) ((x) << ((pll) * 4)) + +#define ADAV80X_PLL_CTRL2_FS_48(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x00) +#define ADAV80X_PLL_CTRL2_FS_32(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x08) +#define ADAV80X_PLL_CTRL2_FS_44(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x0c) + +#define ADAV80X_PLL_CTRL2_SEL(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x02) +#define ADAV80X_PLL_CTRL2_DOUB(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x01) +#define ADAV80X_PLL_CTRL2_PLL_MASK(pll) ADAV80X_PLL_CTRL2_FIELD((pll), 0x0f) + +#define ADAV80X_ADC_CTRL1_MODULATOR_MASK 0x80 +#define ADAV80X_ADC_CTRL1_MODULATOR_128FS 0x00 +#define ADAV80X_ADC_CTRL1_MODULATOR_64FS 0x80 + +#define ADAV80X_DAC_CTRL1_PD 0x80 + +#define ADAV80X_DAC_CTRL2_DIV1 0x00 +#define ADAV80X_DAC_CTRL2_DIV1_5 0x10 +#define ADAV80X_DAC_CTRL2_DIV2 0x20 +#define ADAV80X_DAC_CTRL2_DIV3 0x30 +#define ADAV80X_DAC_CTRL2_DIV_MASK 0x30 + +#define ADAV80X_DAC_CTRL2_INTERPOL_256FS 0x00 +#define ADAV80X_DAC_CTRL2_INTERPOL_128FS 0x40 +#define ADAV80X_DAC_CTRL2_INTERPOL_64FS 0x80 +#define ADAV80X_DAC_CTRL2_INTERPOL_MASK 0xc0 + +#define ADAV80X_DAC_CTRL2_DEEMPH_NONE 0x00 +#define ADAV80X_DAC_CTRL2_DEEMPH_44 0x01 +#define ADAV80X_DAC_CTRL2_DEEMPH_32 0x02 +#define ADAV80X_DAC_CTRL2_DEEMPH_48 0x03 +#define ADAV80X_DAC_CTRL2_DEEMPH_MASK 0x01 + +#define ADAV80X_CAPTURE_MODE_MASTER 0x20 +#define ADAV80X_CAPTURE_WORD_LEN24 0x00 +#define ADAV80X_CAPTURE_WORD_LEN20 0x04 +#define ADAV80X_CAPTRUE_WORD_LEN18 0x08 +#define ADAV80X_CAPTURE_WORD_LEN16 0x0c +#define ADAV80X_CAPTURE_WORD_LEN_MASK 0x0c + +#define ADAV80X_CAPTURE_MODE_LEFT_J 0x00 +#define ADAV80X_CAPTURE_MODE_I2S 0x01 +#define ADAV80X_CAPTURE_MODE_RIGHT_J 0x03 +#define ADAV80X_CAPTURE_MODE_MASK 0x03 + +#define ADAV80X_PLAYBACK_MODE_MASTER 0x10 +#define ADAV80X_PLAYBACK_MODE_LEFT_J 0x00 +#define ADAV80X_PLAYBACK_MODE_I2S 0x01 +#define ADAV80X_PLAYBACK_MODE_RIGHT_J_24 0x04 +#define ADAV80X_PLAYBACK_MODE_RIGHT_J_20 0x05 +#define ADAV80X_PLAYBACK_MODE_RIGHT_J_18 0x06 +#define ADAV80X_PLAYBACK_MODE_RIGHT_J_16 0x07 +#define ADAV80X_PLAYBACK_MODE_MASK 0x07 + +#define ADAV80X_PLL_OUTE_SYSCLKPD(x) BIT(2 - (x)) + +static u8 adav80x_default_regs[] = { + 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x01, 0x80, 0x26, 0x00, 0x00, + 0x02, 0x40, 0x20, 0x00, 0x09, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd1, 0x92, 0xb1, 0x37, + 0x48, 0xd2, 0xfb, 0xca, 0xd2, 0x15, 0xe8, 0x29, 0xb9, 0x6a, 0xda, 0x2b, + 0xb7, 0xc0, 0x11, 0x65, 0x5c, 0xf6, 0xff, 0x8d, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa5, 0x00, 0x00, + 0x00, 0xe8, 0x46, 0xe1, 0x5b, 0xd3, 0x43, 0x77, 0x93, 0xa7, 0x44, 0xee, + 0x32, 0x12, 0xc0, 0x11, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x3f, 0x3f, + 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x52, 0x00, +}; + +struct adav80x { + enum snd_soc_control_type control_type; + + enum adav80x_clk_src clk_src; + unsigned int sysclk; + enum adav80x_pll_src pll_src; + + unsigned int dai_fmt[2]; + unsigned int rate; + bool deemph; + bool sysclk_pd[3]; +}; + +static const char *adav80x_mux_text[] = { + "ADC", + "Playback", + "Aux Playback", +}; + +static const unsigned int adav80x_mux_values[] = { + 0, 2, 3, +}; + +#define ADAV80X_MUX_ENUM_DECL(name, reg, shift) \ + SOC_VALUE_ENUM_DOUBLE_DECL(name, reg, shift, 7, \ + ARRAY_SIZE(adav80x_mux_text), adav80x_mux_text, \ + adav80x_mux_values) + +static ADAV80X_MUX_ENUM_DECL(adav80x_aux_capture_enum, ADAV80X_DPATH_CTRL1, 0); +static ADAV80X_MUX_ENUM_DECL(adav80x_capture_enum, ADAV80X_DPATH_CTRL1, 3); +static ADAV80X_MUX_ENUM_DECL(adav80x_dac_enum, ADAV80X_DPATH_CTRL2, 3); + +static const struct snd_kcontrol_new adav80x_aux_capture_mux_ctrl = + SOC_DAPM_VALUE_ENUM("Route", adav80x_aux_capture_enum); +static const struct snd_kcontrol_new adav80x_capture_mux_ctrl = + SOC_DAPM_VALUE_ENUM("Route", adav80x_capture_enum); +static const struct snd_kcontrol_new adav80x_dac_mux_ctrl = + SOC_DAPM_VALUE_ENUM("Route", adav80x_dac_enum); + +#define ADAV80X_MUX(name, ctrl) \ + SND_SOC_DAPM_VALUE_MUX(name, SND_SOC_NOPM, 0, 0, ctrl) + +static const struct snd_soc_dapm_widget adav80x_dapm_widgets[] = { + SND_SOC_DAPM_DAC("DAC", NULL, ADAV80X_DAC_CTRL1, 7, 1), + SND_SOC_DAPM_ADC("ADC", NULL, ADAV80X_ADC_CTRL1, 5, 1), + + SND_SOC_DAPM_PGA("Right PGA", ADAV80X_ADC_CTRL1, 0, 1, NULL, 0), + SND_SOC_DAPM_PGA("Left PGA", ADAV80X_ADC_CTRL1, 1, 1, NULL, 0), + + SND_SOC_DAPM_AIF_OUT("AIFOUT", "HiFi Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIFIN", "HiFi Playback", 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_OUT("AIFAUXOUT", "Aux Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("AIFAUXIN", "Aux Playback", 0, SND_SOC_NOPM, 0, 0), + + ADAV80X_MUX("Aux Capture Select", &adav80x_aux_capture_mux_ctrl), + ADAV80X_MUX("Capture Select", &adav80x_capture_mux_ctrl), + ADAV80X_MUX("DAC Select", &adav80x_dac_mux_ctrl), + + SND_SOC_DAPM_INPUT("VINR"), + SND_SOC_DAPM_INPUT("VINL"), + SND_SOC_DAPM_OUTPUT("VOUTR"), + SND_SOC_DAPM_OUTPUT("VOUTL"), + + SND_SOC_DAPM_SUPPLY("SYSCLK", SND_SOC_NOPM, 0, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL1", ADAV80X_PLL_CTRL1, 2, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("PLL2", ADAV80X_PLL_CTRL1, 3, 1, NULL, 0), + SND_SOC_DAPM_SUPPLY("OSC", ADAV80X_PLL_CTRL1, 1, 1, NULL, 0), +}; + +static int adav80x_dapm_sysclk_check(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_codec *codec = source->codec; + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + const char *clk; + + switch (adav80x->clk_src) { + case ADAV80X_CLK_PLL1: + clk = "PLL1"; + break; + case ADAV80X_CLK_PLL2: + clk = "PLL2"; + break; + case ADAV80X_CLK_XTAL: + clk = "OSC"; + break; + default: + return 0; + } + + return strcmp(source->name, clk) == 0; +} + +static int adav80x_dapm_pll_check(struct snd_soc_dapm_widget *source, + struct snd_soc_dapm_widget *sink) +{ + struct snd_soc_codec *codec = source->codec; + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + + return adav80x->pll_src == ADAV80X_PLL_SRC_XTAL; +} + + +static const struct snd_soc_dapm_route adav80x_dapm_routes[] = { + { "DAC Select", "ADC", "ADC" }, + { "DAC Select", "Playback", "AIFIN" }, + { "DAC Select", "Aux Playback", "AIFAUXIN" }, + { "DAC", NULL, "DAC Select" }, + + { "Capture Select", "ADC", "ADC" }, + { "Capture Select", "Playback", "AIFIN" }, + { "Capture Select", "Aux Playback", "AIFAUXIN" }, + { "AIFOUT", NULL, "Capture Select" }, + + { "Aux Capture Select", "ADC", "ADC" }, + { "Aux Capture Select", "Playback", "AIFIN" }, + { "Aux Capture Select", "Aux Playback", "AIFAUXIN" }, + { "AIFAUXOUT", NULL, "Aux Capture Select" }, + + { "VOUTR", NULL, "DAC" }, + { "VOUTL", NULL, "DAC" }, + + { "Left PGA", NULL, "VINL" }, + { "Right PGA", NULL, "VINR" }, + { "ADC", NULL, "Left PGA" }, + { "ADC", NULL, "Right PGA" }, + + { "SYSCLK", NULL, "PLL1", adav80x_dapm_sysclk_check }, + { "SYSCLK", NULL, "PLL2", adav80x_dapm_sysclk_check }, + { "SYSCLK", NULL, "OSC", adav80x_dapm_sysclk_check }, + { "PLL1", NULL, "OSC", adav80x_dapm_pll_check }, + { "PLL2", NULL, "OSC", adav80x_dapm_pll_check }, + + { "ADC", NULL, "SYSCLK" }, + { "DAC", NULL, "SYSCLK" }, + { "AIFOUT", NULL, "SYSCLK" }, + { "AIFAUXOUT", NULL, "SYSCLK" }, + { "AIFIN", NULL, "SYSCLK" }, + { "AIFAUXIN", NULL, "SYSCLK" }, +}; + +static int adav80x_set_deemph(struct snd_soc_codec *codec) +{ + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + unsigned int val; + + if (adav80x->deemph) { + switch (adav80x->rate) { + case 32000: + val = ADAV80X_DAC_CTRL2_DEEMPH_32; + break; + case 44100: + val = ADAV80X_DAC_CTRL2_DEEMPH_44; + break; + case 48000: + case 64000: + case 88200: + case 96000: + val = ADAV80X_DAC_CTRL2_DEEMPH_48; + break; + default: + val = ADAV80X_DAC_CTRL2_DEEMPH_NONE; + break; + } + } else { + val = ADAV80X_DAC_CTRL2_DEEMPH_NONE; + } + + return snd_soc_update_bits(codec, ADAV80X_DAC_CTRL2, + ADAV80X_DAC_CTRL2_DEEMPH_MASK, val); +} + +static int adav80x_put_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + unsigned int deemph = ucontrol->value.enumerated.item[0]; + + if (deemph > 1) + return -EINVAL; + + adav80x->deemph = deemph; + + return adav80x_set_deemph(codec); +} + +static int adav80x_get_deemph(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + + ucontrol->value.enumerated.item[0] = adav80x->deemph; + return 0; +}; + +static const DECLARE_TLV_DB_SCALE(adav80x_inpga_tlv, 0, 50, 0); +static const DECLARE_TLV_DB_MINMAX(adav80x_digital_tlv, -9563, 0); + +static const struct snd_kcontrol_new adav80x_controls[] = { + SOC_DOUBLE_R_TLV("Master Playback Volume", ADAV80X_DAC_L_VOL, + ADAV80X_DAC_R_VOL, 0, 0xff, 0, adav80x_digital_tlv), + SOC_DOUBLE_R_TLV("Master Capture Volume", ADAV80X_ADC_L_VOL, + ADAV80X_ADC_R_VOL, 0, 0xff, 0, adav80x_digital_tlv), + + SOC_DOUBLE_R_TLV("PGA Capture Volume", ADAV80X_PGA_L_VOL, + ADAV80X_PGA_R_VOL, 0, 0x30, 0, adav80x_inpga_tlv), + + SOC_DOUBLE("Master Playback Switch", ADAV80X_DAC_CTRL1, 0, 1, 1, 0), + SOC_DOUBLE("Master Capture Switch", ADAV80X_ADC_CTRL1, 2, 3, 1, 1), + + SOC_SINGLE("ADC High Pass Filter Switch", ADAV80X_ADC_CTRL1, 6, 1, 0), + + SOC_SINGLE_BOOL_EXT("Playback De-emphasis Switch", 0, + adav80x_get_deemph, adav80x_put_deemph), +}; + +static unsigned int adav80x_port_ctrl_regs[2][2] = { + { ADAV80X_REC_CTRL, ADAV80X_PLAYBACK_CTRL, }, + { ADAV80X_AUX_OUT_CTRL, ADAV80X_AUX_IN_CTRL }, +}; + +static int adav80x_set_dai_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct snd_soc_codec *codec = dai->codec; + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + unsigned int capture = 0x00; + unsigned int playback = 0x00; + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBM_CFM: + capture |= ADAV80X_CAPTURE_MODE_MASTER; + playback |= ADAV80X_PLAYBACK_MODE_MASTER; + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + capture |= ADAV80X_CAPTURE_MODE_I2S; + playback |= ADAV80X_PLAYBACK_MODE_I2S; + break; + case SND_SOC_DAIFMT_LEFT_J: + capture |= ADAV80X_CAPTURE_MODE_LEFT_J; + playback |= ADAV80X_PLAYBACK_MODE_LEFT_J; + break; + case SND_SOC_DAIFMT_RIGHT_J: + capture |= ADAV80X_CAPTURE_MODE_RIGHT_J; + playback |= ADAV80X_PLAYBACK_MODE_RIGHT_J_24; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, adav80x_port_ctrl_regs[dai->id][0], + ADAV80X_CAPTURE_MODE_MASK | ADAV80X_CAPTURE_MODE_MASTER, + capture); + snd_soc_write(codec, adav80x_port_ctrl_regs[dai->id][1], playback); + + adav80x->dai_fmt[dai->id] = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + + return 0; +} + +static int adav80x_set_adc_clock(struct snd_soc_codec *codec, + unsigned int sample_rate) +{ + unsigned int val; + + if (sample_rate <= 48000) + val = ADAV80X_ADC_CTRL1_MODULATOR_128FS; + else + val = ADAV80X_ADC_CTRL1_MODULATOR_64FS; + + snd_soc_update_bits(codec, ADAV80X_ADC_CTRL1, + ADAV80X_ADC_CTRL1_MODULATOR_MASK, val); + + return 0; +} + +static int adav80x_set_dac_clock(struct snd_soc_codec *codec, + unsigned int sample_rate) +{ + unsigned int val; + + if (sample_rate <= 48000) + val = ADAV80X_DAC_CTRL2_DIV1 | ADAV80X_DAC_CTRL2_INTERPOL_256FS; + else + val = ADAV80X_DAC_CTRL2_DIV2 | ADAV80X_DAC_CTRL2_INTERPOL_128FS; + + snd_soc_update_bits(codec, ADAV80X_DAC_CTRL2, + ADAV80X_DAC_CTRL2_DIV_MASK | ADAV80X_DAC_CTRL2_INTERPOL_MASK, + val); + + return 0; +} + +static int adav80x_set_capture_pcm_format(struct snd_soc_codec *codec, + struct snd_soc_dai *dai, snd_pcm_format_t format) +{ + unsigned int val; + + switch (format) { + case SNDRV_PCM_FORMAT_S16_LE: + val = ADAV80X_CAPTURE_WORD_LEN16; + break; + case SNDRV_PCM_FORMAT_S18_3LE: + val = ADAV80X_CAPTRUE_WORD_LEN18; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + val = ADAV80X_CAPTURE_WORD_LEN20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + val = ADAV80X_CAPTURE_WORD_LEN24; + break; + default: + break; + } + + snd_soc_update_bits(codec, adav80x_port_ctrl_regs[dai->id][0], + ADAV80X_CAPTURE_WORD_LEN_MASK, val); + + return 0; +} + +static int adav80x_set_playback_pcm_format(struct snd_soc_codec *codec, + struct snd_soc_dai *dai, snd_pcm_format_t format) +{ + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + unsigned int val; + + if (adav80x->dai_fmt[dai->id] != SND_SOC_DAIFMT_RIGHT_J) + return 0; + + switch (format) { + case SNDRV_PCM_FORMAT_S16_LE: + val = ADAV80X_PLAYBACK_MODE_RIGHT_J_16; + break; + case SNDRV_PCM_FORMAT_S18_3LE: + val = ADAV80X_PLAYBACK_MODE_RIGHT_J_18; + break; + case SNDRV_PCM_FORMAT_S20_3LE: + val = ADAV80X_PLAYBACK_MODE_RIGHT_J_20; + break; + case SNDRV_PCM_FORMAT_S24_LE: + val = ADAV80X_PLAYBACK_MODE_RIGHT_J_24; + break; + default: + break; + } + + snd_soc_update_bits(codec, adav80x_port_ctrl_regs[dai->id][1], + ADAV80X_PLAYBACK_MODE_MASK, val); + + return 0; +} + +static int adav80x_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 adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + unsigned int rate = params_rate(params); + + if (rate * 256 != adav80x->sysclk) + return -EINVAL; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + adav80x_set_playback_pcm_format(codec, dai, + params_format(params)); + adav80x_set_dac_clock(codec, rate); + } else { + adav80x_set_capture_pcm_format(codec, dai, + params_format(params)); + adav80x_set_adc_clock(codec, rate); + } + adav80x->rate = rate; + adav80x_set_deemph(codec); + + return 0; +} + +static int adav80x_set_sysclk(struct snd_soc_codec *codec, + int clk_id, unsigned int freq, int dir) +{ + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + + if (dir == SND_SOC_CLOCK_IN) { + switch (clk_id) { + case ADAV80X_CLK_XIN: + case ADAV80X_CLK_XTAL: + case ADAV80X_CLK_MCLKI: + case ADAV80X_CLK_PLL1: + case ADAV80X_CLK_PLL2: + break; + default: + return -EINVAL; + } + + adav80x->sysclk = freq; + + if (adav80x->clk_src != clk_id) { + unsigned int iclk_ctrl1, iclk_ctrl2; + + adav80x->clk_src = clk_id; + if (clk_id == ADAV80X_CLK_XTAL) + clk_id = ADAV80X_CLK_XIN; + + iclk_ctrl1 = ADAV80X_ICLK_CTRL1_DAC_SRC(clk_id) | + ADAV80X_ICLK_CTRL1_ADC_SRC(clk_id) | + ADAV80X_ICLK_CTRL1_ICLK2_SRC(clk_id); + iclk_ctrl2 = ADAV80X_ICLK_CTRL2_ICLK1_SRC(clk_id); + + snd_soc_write(codec, ADAV80X_ICLK_CTRL1, iclk_ctrl1); + snd_soc_write(codec, ADAV80X_ICLK_CTRL2, iclk_ctrl2); + + snd_soc_dapm_sync(&codec->dapm); + } + } else { + unsigned int mask; + + switch (clk_id) { + case ADAV80X_CLK_SYSCLK1: + case ADAV80X_CLK_SYSCLK2: + case ADAV80X_CLK_SYSCLK3: + break; + default: + return -EINVAL; + } + + clk_id -= ADAV80X_CLK_SYSCLK1; + mask = ADAV80X_PLL_OUTE_SYSCLKPD(clk_id); + + if (freq == 0) { + snd_soc_update_bits(codec, ADAV80X_PLL_OUTE, mask, mask); + adav80x->sysclk_pd[clk_id] = true; + } else { + snd_soc_update_bits(codec, ADAV80X_PLL_OUTE, mask, 0); + adav80x->sysclk_pd[clk_id] = false; + } + + if (adav80x->sysclk_pd[0]) + snd_soc_dapm_disable_pin(&codec->dapm, "PLL1"); + else + snd_soc_dapm_force_enable_pin(&codec->dapm, "PLL1"); + + if (adav80x->sysclk_pd[1] || adav80x->sysclk_pd[2]) + snd_soc_dapm_disable_pin(&codec->dapm, "PLL2"); + else + snd_soc_dapm_force_enable_pin(&codec->dapm, "PLL2"); + + snd_soc_dapm_sync(&codec->dapm); + } + + return 0; +} + +static int adav80x_set_pll(struct snd_soc_codec *codec, int pll_id, + int source, unsigned int freq_in, unsigned int freq_out) +{ + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + unsigned int pll_ctrl1 = 0; + unsigned int pll_ctrl2 = 0; + unsigned int pll_src; + + switch (source) { + case ADAV80X_PLL_SRC_XTAL: + case ADAV80X_PLL_SRC_XIN: + case ADAV80X_PLL_SRC_MCLKI: + break; + default: + return -EINVAL; + } + + if (!freq_out) + return 0; + + switch (freq_in) { + case 27000000: + break; + case 54000000: + if (source == ADAV80X_PLL_SRC_XIN) { + pll_ctrl1 |= ADAV80X_PLL_CTRL1_PLLDIV; + break; + } + default: + return -EINVAL; + } + + if (freq_out > 12288000) { + pll_ctrl2 |= ADAV80X_PLL_CTRL2_DOUB(pll_id); + freq_out /= 2; + } + + /* freq_out = sample_rate * 256 */ + switch (freq_out) { + case 8192000: + pll_ctrl2 |= ADAV80X_PLL_CTRL2_FS_32(pll_id); + break; + case 11289600: + pll_ctrl2 |= ADAV80X_PLL_CTRL2_FS_44(pll_id); + break; + case 12288000: + pll_ctrl2 |= ADAV80X_PLL_CTRL2_FS_48(pll_id); + break; + default: + return -EINVAL; + } + + snd_soc_update_bits(codec, ADAV80X_PLL_CTRL1, ADAV80X_PLL_CTRL1_PLLDIV, + pll_ctrl1); + snd_soc_update_bits(codec, ADAV80X_PLL_CTRL2, + ADAV80X_PLL_CTRL2_PLL_MASK(pll_id), pll_ctrl2); + + if (source != adav80x->pll_src) { + if (source == ADAV80X_PLL_SRC_MCLKI) + pll_src = ADAV80X_PLL_CLK_SRC_PLL_MCLKI(pll_id); + else + pll_src = ADAV80X_PLL_CLK_SRC_PLL_XIN(pll_id); + + snd_soc_update_bits(codec, ADAV80X_PLL_CLK_SRC, + ADAV80X_PLL_CLK_SRC_PLL_MASK(pll_id), pll_src); + + adav80x->pll_src = source; + + snd_soc_dapm_sync(&codec->dapm); + } + + return 0; +} + +static int adav80x_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + unsigned int mask = ADAV80X_DAC_CTRL1_PD; + + switch (level) { + case SND_SOC_BIAS_ON: + break; + case SND_SOC_BIAS_PREPARE: + break; + case SND_SOC_BIAS_STANDBY: + snd_soc_update_bits(codec, ADAV80X_DAC_CTRL1, mask, 0x00); + break; + case SND_SOC_BIAS_OFF: + snd_soc_update_bits(codec, ADAV80X_DAC_CTRL1, mask, mask); + break; + } + + codec->dapm.bias_level = level; + return 0; +} + +/* Enforce the same sample rate on all audio interfaces */ +static int adav80x_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + + if (!codec->active || !adav80x->rate) + return 0; + + return snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_RATE, adav80x->rate, adav80x->rate); +} + +static void adav80x_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + + if (!codec->active) + adav80x->rate = 0; +} + +static const struct snd_soc_dai_ops adav80x_dai_ops = { + .set_fmt = adav80x_set_dai_fmt, + .hw_params = adav80x_hw_params, + .startup = adav80x_dai_startup, + .shutdown = adav80x_dai_shutdown, +}; + +#define ADAV80X_PLAYBACK_RATES (SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_64000 | SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000) + +#define ADAV80X_CAPTURE_RATES (SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000) + +#define ADAV80X_FORMATS (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S18_3LE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S24_LE) + +static struct snd_soc_dai_driver adav80x_dais[] = { + { + .name = "adav80x-hifi", + .id = 0, + .playback = { + .stream_name = "HiFi Playback", + .channels_min = 2, + .channels_max = 2, + .rates = ADAV80X_PLAYBACK_RATES, + .formats = ADAV80X_FORMATS, + }, + .capture = { + .stream_name = "HiFi Capture", + .channels_min = 2, + .channels_max = 2, + .rates = ADAV80X_CAPTURE_RATES, + .formats = ADAV80X_FORMATS, + }, + .ops = &adav80x_dai_ops, + }, + { + .name = "adav80x-aux", + .id = 1, + .playback = { + .stream_name = "Aux Playback", + .channels_min = 2, + .channels_max = 2, + .rates = ADAV80X_PLAYBACK_RATES, + .formats = ADAV80X_FORMATS, + }, + .capture = { + .stream_name = "Aux Capture", + .channels_min = 2, + .channels_max = 2, + .rates = ADAV80X_CAPTURE_RATES, + .formats = ADAV80X_FORMATS, + }, + .ops = &adav80x_dai_ops, + }, +}; + +static int adav80x_probe(struct snd_soc_codec *codec) +{ + int ret; + struct adav80x *adav80x = snd_soc_codec_get_drvdata(codec); + + ret = snd_soc_codec_set_cache_io(codec, 7, 9, adav80x->control_type); + if (ret) { + dev_err(codec->dev, "failed to set cache I/O: %d\n", ret); + return ret; + } + + /* Force PLLs on for SYSCLK output */ + snd_soc_dapm_force_enable_pin(&codec->dapm, "PLL1"); + snd_soc_dapm_force_enable_pin(&codec->dapm, "PLL2"); + + /* Power down S/PDIF receiver, since it is currently not supported */ + snd_soc_write(codec, ADAV80X_PLL_OUTE, 0x20); + /* Disable DAC zero flag */ + snd_soc_write(codec, ADAV80X_DAC_CTRL3, 0x6); + + return adav80x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); +} + +static int adav80x_suspend(struct snd_soc_codec *codec, pm_message_t state) +{ + return adav80x_set_bias_level(codec, SND_SOC_BIAS_OFF); +} + +static int adav80x_resume(struct snd_soc_codec *codec) +{ + adav80x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + codec->cache_sync = 1; + snd_soc_cache_sync(codec); + + return 0; +} + +static int adav80x_remove(struct snd_soc_codec *codec) +{ + return adav80x_set_bias_level(codec, SND_SOC_BIAS_OFF); +} + +static struct snd_soc_codec_driver adav80x_codec_driver = { + .probe = adav80x_probe, + .remove = adav80x_remove, + .suspend = adav80x_suspend, + .resume = adav80x_resume, + .set_bias_level = adav80x_set_bias_level, + + .set_pll = adav80x_set_pll, + .set_sysclk = adav80x_set_sysclk, + + .reg_word_size = sizeof(u8), + .reg_cache_size = ARRAY_SIZE(adav80x_default_regs), + .reg_cache_default = adav80x_default_regs, + + .controls = adav80x_controls, + .num_controls = ARRAY_SIZE(adav80x_controls), + .dapm_widgets = adav80x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(adav80x_dapm_widgets), + .dapm_routes = adav80x_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(adav80x_dapm_routes), +}; + +static int __devinit adav80x_bus_probe(struct device *dev, + enum snd_soc_control_type control_type) +{ + struct adav80x *adav80x; + int ret; + + adav80x = kzalloc(sizeof(*adav80x), GFP_KERNEL); + if (!adav80x) + return -ENOMEM; + + dev_set_drvdata(dev, adav80x); + adav80x->control_type = control_type; + + ret = snd_soc_register_codec(dev, &adav80x_codec_driver, + adav80x_dais, ARRAY_SIZE(adav80x_dais)); + if (ret) + kfree(adav80x); + + return ret; +} + +static int __devexit adav80x_bus_remove(struct device *dev) +{ + snd_soc_unregister_codec(dev); + kfree(dev_get_drvdata(dev)); + return 0; +} + +#if defined(CONFIG_SPI_MASTER) +static int __devinit adav80x_spi_probe(struct spi_device *spi) +{ + return adav80x_bus_probe(&spi->dev, SND_SOC_SPI); +} + +static int __devexit adav80x_spi_remove(struct spi_device *spi) +{ + return adav80x_bus_remove(&spi->dev); +} + +static struct spi_driver adav80x_spi_driver = { + .driver = { + .name = "adav801", + .owner = THIS_MODULE, + }, + .probe = adav80x_spi_probe, + .remove = __devexit_p(adav80x_spi_remove), +}; +#endif + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) +static const struct i2c_device_id adav80x_id[] = { + { "adav803", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, adav80x_id); + +static int __devinit adav80x_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return adav80x_bus_probe(&client->dev, SND_SOC_I2C); +} + +static int __devexit adav80x_i2c_remove(struct i2c_client *client) +{ + return adav80x_bus_remove(&client->dev); +} + +static struct i2c_driver adav80x_i2c_driver = { + .driver = { + .name = "adav803", + .owner = THIS_MODULE, + }, + .probe = adav80x_i2c_probe, + .remove = __devexit_p(adav80x_i2c_remove), + .id_table = adav80x_id, +}; +#endif + +static int __init adav80x_init(void) +{ + int ret = 0; + +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + ret = i2c_add_driver(&adav80x_i2c_driver); + if (ret) + return ret; +#endif + +#if defined(CONFIG_SPI_MASTER) + ret = spi_register_driver(&adav80x_spi_driver); +#endif + + return ret; +} +module_init(adav80x_init); + +static void __exit adav80x_exit(void) +{ +#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE) + i2c_del_driver(&adav80x_i2c_driver); +#endif +#if defined(CONFIG_SPI_MASTER) + spi_unregister_driver(&adav80x_spi_driver); +#endif +} +module_exit(adav80x_exit); + +MODULE_DESCRIPTION("ASoC ADAV80x driver"); +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_AUTHOR("Yi Li <yi.li@analog.com>>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/adav80x.h b/sound/soc/codecs/adav80x.h new file mode 100644 index 00000000000..adb0fc76d4e --- /dev/null +++ b/sound/soc/codecs/adav80x.h @@ -0,0 +1,35 @@ +/* + * header file for ADAV80X parts + * + * Copyright 2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#ifndef _ADAV80X_H +#define _ADAV80X_H + +enum adav80x_pll_src { + ADAV80X_PLL_SRC_XIN, + ADAV80X_PLL_SRC_XTAL, + ADAV80X_PLL_SRC_MCLKI, +}; + +enum adav80x_pll { + ADAV80X_PLL1 = 0, + ADAV80X_PLL2 = 1, +}; + +enum adav80x_clk_src { + ADAV80X_CLK_XIN = 0, + ADAV80X_CLK_MCLKI = 1, + ADAV80X_CLK_PLL1 = 2, + ADAV80X_CLK_PLL2 = 3, + ADAV80X_CLK_XTAL = 6, + + ADAV80X_CLK_SYSCLK1 = 6, + ADAV80X_CLK_SYSCLK2 = 7, + ADAV80X_CLK_SYSCLK3 = 8, +}; + +#endif diff --git a/sound/soc/codecs/ak4641.c b/sound/soc/codecs/ak4641.c index ed96f247c2d..7a64e58cddc 100644 --- a/sound/soc/codecs/ak4641.c +++ b/sound/soc/codecs/ak4641.c @@ -457,7 +457,7 @@ static struct snd_soc_dai_ops ak4641_pcm_dai_ops = { .set_sysclk = ak4641_set_dai_sysclk, }; -struct snd_soc_dai_driver ak4641_dai[] = { +static struct snd_soc_dai_driver ak4641_dai[] = { { .name = "ak4641-hifi", .id = 1, diff --git a/sound/soc/codecs/cs4270.c b/sound/soc/codecs/cs4270.c index 0206a17d728..6cc8678f49f 100644 --- a/sound/soc/codecs/cs4270.c +++ b/sound/soc/codecs/cs4270.c @@ -636,10 +636,7 @@ static int cs4270_soc_resume(struct snd_soc_codec *codec) #endif /* CONFIG_PM */ /* - * ASoC codec device structure - * - * Assign this variable to the codec_dev field of the machine driver's - * snd_soc_device structure. + * ASoC codec driver structure */ static const struct snd_soc_codec_driver soc_codec_device_cs4270 = { .probe = cs4270_probe, diff --git a/sound/soc/codecs/max98088.c b/sound/soc/codecs/max98088.c index 4173b67c94d..ac65a2d3640 100644 --- a/sound/soc/codecs/max98088.c +++ b/sound/soc/codecs/max98088.c @@ -1397,8 +1397,6 @@ static int max98088_dai_set_sysclk(struct snd_soc_dai *dai, if (freq == max98088->sysclk) return 0; - max98088->sysclk = freq; /* remember current sysclk */ - /* Setup clocks for slave mode, and using the PLL * PSCLK = 0x01 (when master clk is 10MHz to 20MHz) * 0x02 (when master clk is 20MHz to 30MHz).. diff --git a/sound/soc/codecs/max98095.c b/sound/soc/codecs/max98095.c index e1d282d477d..668434d4430 100644 --- a/sound/soc/codecs/max98095.c +++ b/sound/soc/codecs/max98095.c @@ -1517,8 +1517,6 @@ static int max98095_dai_set_sysclk(struct snd_soc_dai *dai, if (freq == max98095->sysclk) return 0; - max98095->sysclk = freq; /* remember current sysclk */ - /* Setup clocks for slave mode, and using the PLL * PSCLK = 0x01 (when master clk is 10MHz to 20MHz) * 0x02 (when master clk is 20MHz to 40MHz).. @@ -2261,11 +2259,11 @@ static int max98095_probe(struct snd_soc_codec *codec) ret = snd_soc_read(codec, M98095_0FF_REV_ID); if (ret < 0) { - dev_err(codec->dev, "Failed to read device revision: %d\n", + dev_err(codec->dev, "Failure reading hardware revision: %d\n", ret); goto err_access; } - dev_info(codec->dev, "revision %c\n", ret + 'A'); + dev_info(codec->dev, "Hardware revision: %c\n", ret - 0x40 + 'A'); snd_soc_write(codec, M98095_097_PWR_SYS, M98095_PWRSV); @@ -2342,8 +2340,8 @@ static int max98095_i2c_probe(struct i2c_client *i2c, max98095->control_data = i2c; max98095->pdata = i2c->dev.platform_data; - ret = snd_soc_register_codec(&i2c->dev, - &soc_codec_dev_max98095, &max98095_dai[0], 3); + ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_max98095, + max98095_dai, ARRAY_SIZE(max98095_dai)); if (ret < 0) kfree(max98095); return ret; diff --git a/sound/soc/codecs/sta32x.c b/sound/soc/codecs/sta32x.c new file mode 100644 index 00000000000..409d89d1f34 --- /dev/null +++ b/sound/soc/codecs/sta32x.c @@ -0,0 +1,917 @@ +/* + * Codec driver for ST STA32x 2.1-channel high-efficiency digital audio system + * + * Copyright: 2011 Raumfeld GmbH + * Author: Johannes Stezenbach <js@sig21.net> + * + * based on code from: + * Wolfson Microelectronics PLC. + * Mark Brown <broonie@opensource.wolfsonmicro.com> + * Freescale Semiconductor, Inc. + * Timur Tabi <timur@freescale.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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ":%s:%d: " fmt, __func__, __LINE__ + +#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/regulator/consumer.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/initval.h> +#include <sound/tlv.h> + +#include "sta32x.h" + +#define STA32X_RATES (SNDRV_PCM_RATE_32000 | \ + SNDRV_PCM_RATE_44100 | \ + SNDRV_PCM_RATE_48000 | \ + SNDRV_PCM_RATE_88200 | \ + SNDRV_PCM_RATE_96000 | \ + SNDRV_PCM_RATE_176400 | \ + SNDRV_PCM_RATE_192000) + +#define STA32X_FORMATS \ + (SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | \ + SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S18_3BE | \ + SNDRV_PCM_FMTBIT_S20_3LE | SNDRV_PCM_FMTBIT_S20_3BE | \ + SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_3BE | \ + SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE | \ + SNDRV_PCM_FMTBIT_S32_LE | SNDRV_PCM_FMTBIT_S32_BE) + +/* Power-up register defaults */ +static const u8 sta32x_regs[STA32X_REGISTER_COUNT] = { + 0x63, 0x80, 0xc2, 0x40, 0xc2, 0x5c, 0x10, 0xff, 0x60, 0x60, + 0x60, 0x80, 0x00, 0x00, 0x00, 0x40, 0x80, 0x77, 0x6a, 0x69, + 0x6a, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, + 0xc0, 0xf3, 0x33, 0x00, 0x0c, +}; + +/* regulator power supply names */ +static const char *sta32x_supply_names[] = { + "Vdda", /* analog supply, 3.3VV */ + "Vdd3", /* digital supply, 3.3V */ + "Vcc" /* power amp spply, 10V - 36V */ +}; + +/* codec private data */ +struct sta32x_priv { + struct regulator_bulk_data supplies[ARRAY_SIZE(sta32x_supply_names)]; + struct snd_soc_codec *codec; + + unsigned int mclk; + unsigned int format; +}; + +static const DECLARE_TLV_DB_SCALE(mvol_tlv, -12700, 50, 1); +static const DECLARE_TLV_DB_SCALE(chvol_tlv, -7950, 50, 1); +static const DECLARE_TLV_DB_SCALE(tone_tlv, -120, 200, 0); + +static const char *sta32x_drc_ac[] = { + "Anti-Clipping", "Dynamic Range Compression" }; +static const char *sta32x_auto_eq_mode[] = { + "User", "Preset", "Loudness" }; +static const char *sta32x_auto_gc_mode[] = { + "User", "AC no clipping", "AC limited clipping (10%)", + "DRC nighttime listening mode" }; +static const char *sta32x_auto_xo_mode[] = { + "User", "80Hz", "100Hz", "120Hz", "140Hz", "160Hz", "180Hz", "200Hz", + "220Hz", "240Hz", "260Hz", "280Hz", "300Hz", "320Hz", "340Hz", "360Hz" }; +static const char *sta32x_preset_eq_mode[] = { + "Flat", "Rock", "Soft Rock", "Jazz", "Classical", "Dance", "Pop", "Soft", + "Hard", "Party", "Vocal", "Hip-Hop", "Dialog", "Bass-boost #1", + "Bass-boost #2", "Bass-boost #3", "Loudness 1", "Loudness 2", + "Loudness 3", "Loudness 4", "Loudness 5", "Loudness 6", "Loudness 7", + "Loudness 8", "Loudness 9", "Loudness 10", "Loudness 11", "Loudness 12", + "Loudness 13", "Loudness 14", "Loudness 15", "Loudness 16" }; +static const char *sta32x_limiter_select[] = { + "Limiter Disabled", "Limiter #1", "Limiter #2" }; +static const char *sta32x_limiter_attack_rate[] = { + "3.1584", "2.7072", "2.2560", "1.8048", "1.3536", "0.9024", + "0.4512", "0.2256", "0.1504", "0.1123", "0.0902", "0.0752", + "0.0645", "0.0564", "0.0501", "0.0451" }; +static const char *sta32x_limiter_release_rate[] = { + "0.5116", "0.1370", "0.0744", "0.0499", "0.0360", "0.0299", + "0.0264", "0.0208", "0.0198", "0.0172", "0.0147", "0.0137", + "0.0134", "0.0117", "0.0110", "0.0104" }; + +static const unsigned int sta32x_limiter_ac_attack_tlv[] = { + TLV_DB_RANGE_HEAD(2), + 0, 7, TLV_DB_SCALE_ITEM(-1200, 200, 0), + 8, 16, TLV_DB_SCALE_ITEM(300, 100, 0), +}; + +static const unsigned int sta32x_limiter_ac_release_tlv[] = { + TLV_DB_RANGE_HEAD(5), + 0, 0, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 0), + 1, 1, TLV_DB_SCALE_ITEM(-2900, 0, 0), + 2, 2, TLV_DB_SCALE_ITEM(-2000, 0, 0), + 3, 8, TLV_DB_SCALE_ITEM(-1400, 200, 0), + 8, 16, TLV_DB_SCALE_ITEM(-700, 100, 0), +}; + +static const unsigned int sta32x_limiter_drc_attack_tlv[] = { + TLV_DB_RANGE_HEAD(3), + 0, 7, TLV_DB_SCALE_ITEM(-3100, 200, 0), + 8, 13, TLV_DB_SCALE_ITEM(-1600, 100, 0), + 14, 16, TLV_DB_SCALE_ITEM(-1000, 300, 0), +}; + +static const unsigned int sta32x_limiter_drc_release_tlv[] = { + TLV_DB_RANGE_HEAD(5), + 0, 0, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 0), + 1, 2, TLV_DB_SCALE_ITEM(-3800, 200, 0), + 3, 4, TLV_DB_SCALE_ITEM(-3300, 200, 0), + 5, 12, TLV_DB_SCALE_ITEM(-3000, 200, 0), + 13, 16, TLV_DB_SCALE_ITEM(-1500, 300, 0), +}; + +static const struct soc_enum sta32x_drc_ac_enum = + SOC_ENUM_SINGLE(STA32X_CONFD, STA32X_CONFD_DRC_SHIFT, + 2, sta32x_drc_ac); +static const struct soc_enum sta32x_auto_eq_enum = + SOC_ENUM_SINGLE(STA32X_AUTO1, STA32X_AUTO1_AMEQ_SHIFT, + 3, sta32x_auto_eq_mode); +static const struct soc_enum sta32x_auto_gc_enum = + SOC_ENUM_SINGLE(STA32X_AUTO1, STA32X_AUTO1_AMGC_SHIFT, + 4, sta32x_auto_gc_mode); +static const struct soc_enum sta32x_auto_xo_enum = + SOC_ENUM_SINGLE(STA32X_AUTO2, STA32X_AUTO2_XO_SHIFT, + 16, sta32x_auto_xo_mode); +static const struct soc_enum sta32x_preset_eq_enum = + SOC_ENUM_SINGLE(STA32X_AUTO3, STA32X_AUTO3_PEQ_SHIFT, + 32, sta32x_preset_eq_mode); +static const struct soc_enum sta32x_limiter_ch1_enum = + SOC_ENUM_SINGLE(STA32X_C1CFG, STA32X_CxCFG_LS_SHIFT, + 3, sta32x_limiter_select); +static const struct soc_enum sta32x_limiter_ch2_enum = + SOC_ENUM_SINGLE(STA32X_C2CFG, STA32X_CxCFG_LS_SHIFT, + 3, sta32x_limiter_select); +static const struct soc_enum sta32x_limiter_ch3_enum = + SOC_ENUM_SINGLE(STA32X_C3CFG, STA32X_CxCFG_LS_SHIFT, + 3, sta32x_limiter_select); +static const struct soc_enum sta32x_limiter1_attack_rate_enum = + SOC_ENUM_SINGLE(STA32X_L1AR, STA32X_LxA_SHIFT, + 16, sta32x_limiter_attack_rate); +static const struct soc_enum sta32x_limiter2_attack_rate_enum = + SOC_ENUM_SINGLE(STA32X_L2AR, STA32X_LxA_SHIFT, + 16, sta32x_limiter_attack_rate); +static const struct soc_enum sta32x_limiter1_release_rate_enum = + SOC_ENUM_SINGLE(STA32X_L1AR, STA32X_LxR_SHIFT, + 16, sta32x_limiter_release_rate); +static const struct soc_enum sta32x_limiter2_release_rate_enum = + SOC_ENUM_SINGLE(STA32X_L2AR, STA32X_LxR_SHIFT, + 16, sta32x_limiter_release_rate); + +/* byte array controls for setting biquad, mixer, scaling coefficients; + * for biquads all five coefficients need to be set in one go, + * mixer and pre/postscale coefs can be set individually; + * each coef is 24bit, the bytes are ordered in the same way + * as given in the STA32x data sheet (big endian; b1, b2, a1, a2, b0) + */ + +static int sta32x_coefficient_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + int numcoef = kcontrol->private_value >> 16; + uinfo->type = SNDRV_CTL_ELEM_TYPE_BYTES; + uinfo->count = 3 * numcoef; + return 0; +} + +static int sta32x_coefficient_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + unsigned int cfud; + int i; + + /* preserve reserved bits in STA32X_CFUD */ + cfud = snd_soc_read(codec, STA32X_CFUD) & 0xf0; + /* chip documentation does not say if the bits are self clearing, + * so do it explicitly */ + snd_soc_write(codec, STA32X_CFUD, cfud); + + snd_soc_write(codec, STA32X_CFADDR2, index); + if (numcoef == 1) + snd_soc_write(codec, STA32X_CFUD, cfud | 0x04); + else if (numcoef == 5) + snd_soc_write(codec, STA32X_CFUD, cfud | 0x08); + else + return -EINVAL; + for (i = 0; i < 3 * numcoef; i++) + ucontrol->value.bytes.data[i] = + snd_soc_read(codec, STA32X_B1CF1 + i); + + return 0; +} + +static int sta32x_coefficient_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_codec *codec = snd_kcontrol_chip(kcontrol); + int numcoef = kcontrol->private_value >> 16; + int index = kcontrol->private_value & 0xffff; + unsigned int cfud; + int i; + + /* preserve reserved bits in STA32X_CFUD */ + cfud = snd_soc_read(codec, STA32X_CFUD) & 0xf0; + /* chip documentation does not say if the bits are self clearing, + * so do it explicitly */ + snd_soc_write(codec, STA32X_CFUD, cfud); + + snd_soc_write(codec, STA32X_CFADDR2, index); + for (i = 0; i < 3 * numcoef; i++) + snd_soc_write(codec, STA32X_B1CF1 + i, + ucontrol->value.bytes.data[i]); + if (numcoef == 1) + snd_soc_write(codec, STA32X_CFUD, cfud | 0x01); + else if (numcoef == 5) + snd_soc_write(codec, STA32X_CFUD, cfud | 0x02); + else + return -EINVAL; + + return 0; +} + +#define SINGLE_COEF(xname, index) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = sta32x_coefficient_info, \ + .get = sta32x_coefficient_get,\ + .put = sta32x_coefficient_put, \ + .private_value = index | (1 << 16) } + +#define BIQUAD_COEFS(xname, index) \ +{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ + .info = sta32x_coefficient_info, \ + .get = sta32x_coefficient_get,\ + .put = sta32x_coefficient_put, \ + .private_value = index | (5 << 16) } + +static const struct snd_kcontrol_new sta32x_snd_controls[] = { +SOC_SINGLE_TLV("Master Volume", STA32X_MVOL, 0, 0xff, 1, mvol_tlv), +SOC_SINGLE("Master Switch", STA32X_MMUTE, 0, 1, 1), +SOC_SINGLE("Ch1 Switch", STA32X_MMUTE, 1, 1, 1), +SOC_SINGLE("Ch2 Switch", STA32X_MMUTE, 2, 1, 1), +SOC_SINGLE("Ch3 Switch", STA32X_MMUTE, 3, 1, 1), +SOC_SINGLE_TLV("Ch1 Volume", STA32X_C1VOL, 0, 0xff, 1, chvol_tlv), +SOC_SINGLE_TLV("Ch2 Volume", STA32X_C2VOL, 0, 0xff, 1, chvol_tlv), +SOC_SINGLE_TLV("Ch3 Volume", STA32X_C3VOL, 0, 0xff, 1, chvol_tlv), +SOC_SINGLE("De-emphasis Filter Switch", STA32X_CONFD, STA32X_CONFD_DEMP_SHIFT, 1, 0), +SOC_ENUM("Compressor/Limiter Switch", sta32x_drc_ac_enum), +SOC_SINGLE("Miami Mode Switch", STA32X_CONFD, STA32X_CONFD_MME_SHIFT, 1, 0), +SOC_SINGLE("Zero Cross Switch", STA32X_CONFE, STA32X_CONFE_ZCE_SHIFT, 1, 0), +SOC_SINGLE("Soft Ramp Switch", STA32X_CONFE, STA32X_CONFE_SVE_SHIFT, 1, 0), +SOC_SINGLE("Auto-Mute Switch", STA32X_CONFF, STA32X_CONFF_IDE_SHIFT, 1, 0), +SOC_ENUM("Automode EQ", sta32x_auto_eq_enum), +SOC_ENUM("Automode GC", sta32x_auto_gc_enum), +SOC_ENUM("Automode XO", sta32x_auto_xo_enum), +SOC_ENUM("Preset EQ", sta32x_preset_eq_enum), +SOC_SINGLE("Ch1 Tone Control Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_TCB_SHIFT, 1, 0), +SOC_SINGLE("Ch2 Tone Control Bypass Switch", STA32X_C2CFG, STA32X_CxCFG_TCB_SHIFT, 1, 0), +SOC_SINGLE("Ch1 EQ Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_EQBP_SHIFT, 1, 0), +SOC_SINGLE("Ch2 EQ Bypass Switch", STA32X_C2CFG, STA32X_CxCFG_EQBP_SHIFT, 1, 0), +SOC_SINGLE("Ch1 Master Volume Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0), +SOC_SINGLE("Ch2 Master Volume Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0), +SOC_SINGLE("Ch3 Master Volume Bypass Switch", STA32X_C1CFG, STA32X_CxCFG_VBP_SHIFT, 1, 0), +SOC_ENUM("Ch1 Limiter Select", sta32x_limiter_ch1_enum), +SOC_ENUM("Ch2 Limiter Select", sta32x_limiter_ch2_enum), +SOC_ENUM("Ch3 Limiter Select", sta32x_limiter_ch3_enum), +SOC_SINGLE_TLV("Bass Tone Control", STA32X_TONE, STA32X_TONE_BTC_SHIFT, 15, 0, tone_tlv), +SOC_SINGLE_TLV("Treble Tone Control", STA32X_TONE, STA32X_TONE_TTC_SHIFT, 15, 0, tone_tlv), +SOC_ENUM("Limiter1 Attack Rate (dB/ms)", sta32x_limiter1_attack_rate_enum), +SOC_ENUM("Limiter2 Attack Rate (dB/ms)", sta32x_limiter2_attack_rate_enum), +SOC_ENUM("Limiter1 Release Rate (dB/ms)", sta32x_limiter1_release_rate_enum), +SOC_ENUM("Limiter2 Release Rate (dB/ms)", sta32x_limiter1_release_rate_enum), + +/* depending on mode, the attack/release thresholds have + * two different enum definitions; provide both + */ +SOC_SINGLE_TLV("Limiter1 Attack Threshold (AC Mode)", STA32X_L1ATRT, STA32X_LxA_SHIFT, + 16, 0, sta32x_limiter_ac_attack_tlv), +SOC_SINGLE_TLV("Limiter2 Attack Threshold (AC Mode)", STA32X_L2ATRT, STA32X_LxA_SHIFT, + 16, 0, sta32x_limiter_ac_attack_tlv), +SOC_SINGLE_TLV("Limiter1 Release Threshold (AC Mode)", STA32X_L1ATRT, STA32X_LxR_SHIFT, + 16, 0, sta32x_limiter_ac_release_tlv), +SOC_SINGLE_TLV("Limiter2 Release Threshold (AC Mode)", STA32X_L2ATRT, STA32X_LxR_SHIFT, + 16, 0, sta32x_limiter_ac_release_tlv), +SOC_SINGLE_TLV("Limiter1 Attack Threshold (DRC Mode)", STA32X_L1ATRT, STA32X_LxA_SHIFT, + 16, 0, sta32x_limiter_drc_attack_tlv), +SOC_SINGLE_TLV("Limiter2 Attack Threshold (DRC Mode)", STA32X_L2ATRT, STA32X_LxA_SHIFT, + 16, 0, sta32x_limiter_drc_attack_tlv), +SOC_SINGLE_TLV("Limiter1 Release Threshold (DRC Mode)", STA32X_L1ATRT, STA32X_LxR_SHIFT, + 16, 0, sta32x_limiter_drc_release_tlv), +SOC_SINGLE_TLV("Limiter2 Release Threshold (DRC Mode)", STA32X_L2ATRT, STA32X_LxR_SHIFT, + 16, 0, sta32x_limiter_drc_release_tlv), + +BIQUAD_COEFS("Ch1 - Biquad 1", 0), +BIQUAD_COEFS("Ch1 - Biquad 2", 5), +BIQUAD_COEFS("Ch1 - Biquad 3", 10), +BIQUAD_COEFS("Ch1 - Biquad 4", 15), +BIQUAD_COEFS("Ch2 - Biquad 1", 20), +BIQUAD_COEFS("Ch2 - Biquad 2", 25), +BIQUAD_COEFS("Ch2 - Biquad 3", 30), +BIQUAD_COEFS("Ch2 - Biquad 4", 35), +BIQUAD_COEFS("High-pass", 40), +BIQUAD_COEFS("Low-pass", 45), +SINGLE_COEF("Ch1 - Prescale", 50), +SINGLE_COEF("Ch2 - Prescale", 51), +SINGLE_COEF("Ch1 - Postscale", 52), +SINGLE_COEF("Ch2 - Postscale", 53), +SINGLE_COEF("Ch3 - Postscale", 54), +SINGLE_COEF("Thermal warning - Postscale", 55), +SINGLE_COEF("Ch1 - Mix 1", 56), +SINGLE_COEF("Ch1 - Mix 2", 57), +SINGLE_COEF("Ch2 - Mix 1", 58), +SINGLE_COEF("Ch2 - Mix 2", 59), +SINGLE_COEF("Ch3 - Mix 1", 60), +SINGLE_COEF("Ch3 - Mix 2", 61), +}; + +static const struct snd_soc_dapm_widget sta32x_dapm_widgets[] = { +SND_SOC_DAPM_DAC("DAC", "Playback", SND_SOC_NOPM, 0, 0), +SND_SOC_DAPM_OUTPUT("LEFT"), +SND_SOC_DAPM_OUTPUT("RIGHT"), +SND_SOC_DAPM_OUTPUT("SUB"), +}; + +static const struct snd_soc_dapm_route sta32x_dapm_routes[] = { + { "LEFT", NULL, "DAC" }, + { "RIGHT", NULL, "DAC" }, + { "SUB", NULL, "DAC" }, +}; + +/* MCLK interpolation ratio per fs */ +static struct { + int fs; + int ir; +} interpolation_ratios[] = { + { 32000, 0 }, + { 44100, 0 }, + { 48000, 0 }, + { 88200, 1 }, + { 96000, 1 }, + { 176400, 2 }, + { 192000, 2 }, +}; + +/* MCLK to fs clock ratios */ +static struct { + int ratio; + int mcs; +} mclk_ratios[3][7] = { + { { 768, 0 }, { 512, 1 }, { 384, 2 }, { 256, 3 }, + { 128, 4 }, { 576, 5 }, { 0, 0 } }, + { { 384, 2 }, { 256, 3 }, { 192, 4 }, { 128, 5 }, {64, 0 }, { 0, 0 } }, + { { 384, 2 }, { 256, 3 }, { 192, 4 }, { 128, 5 }, {64, 0 }, { 0, 0 } }, +}; + + +/** + * sta32x_set_dai_sysclk - configure MCLK + * @codec_dai: the codec DAI + * @clk_id: the clock ID (ignored) + * @freq: the MCLK input frequency + * @dir: the clock direction (ignored) + * + * The value of MCLK is used to determine which sample rates are supported + * by the STA32X, based on the mclk_ratios table. + * + * This function must be called by the machine driver's 'startup' function, + * otherwise the list of supported sample rates will not be available in + * time for ALSA. + * + * For setups with variable MCLKs, pass 0 as 'freq' argument. This will cause + * theoretically possible sample rates to be enabled. Call it again with a + * proper value set one the external clock is set (most probably you would do + * that from a machine's driver 'hw_param' hook. + */ +static int sta32x_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 sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec); + int i, j, ir, fs; + unsigned int rates = 0; + unsigned int rate_min = -1; + unsigned int rate_max = 0; + + pr_debug("mclk=%u\n", freq); + sta32x->mclk = freq; + + if (sta32x->mclk) { + for (i = 0; i < ARRAY_SIZE(interpolation_ratios); i++) { + ir = interpolation_ratios[i].ir; + fs = interpolation_ratios[i].fs; + for (j = 0; mclk_ratios[ir][j].ratio; j++) { + if (mclk_ratios[ir][j].ratio * fs == freq) { + rates |= snd_pcm_rate_to_rate_bit(fs); + if (fs < rate_min) + rate_min = fs; + if (fs > rate_max) + rate_max = fs; + } + } + } + /* FIXME: soc should support a rate list */ + rates &= ~SNDRV_PCM_RATE_KNOT; + + if (!rates) { + dev_err(codec->dev, "could not find a valid sample rate\n"); + return -EINVAL; + } + } else { + /* enable all possible rates */ + rates = STA32X_RATES; + rate_min = 32000; + rate_max = 192000; + } + + codec_dai->driver->playback.rates = rates; + codec_dai->driver->playback.rate_min = rate_min; + codec_dai->driver->playback.rate_max = rate_max; + return 0; +} + +/** + * sta32x_set_dai_fmt - configure the codec for the selected audio format + * @codec_dai: the codec DAI + * @fmt: a SND_SOC_DAIFMT_x value indicating the data format + * + * This function takes a bitmask of SND_SOC_DAIFMT_x bits and programs the + * codec accordingly. + */ +static int sta32x_set_dai_fmt(struct snd_soc_dai *codec_dai, + unsigned int fmt) +{ + struct snd_soc_codec *codec = codec_dai->codec; + struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec); + u8 confb = snd_soc_read(codec, STA32X_CONFB); + + pr_debug("\n"); + confb &= ~(STA32X_CONFB_C1IM | STA32X_CONFB_C2IM); + + switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) { + case SND_SOC_DAIFMT_I2S: + case SND_SOC_DAIFMT_RIGHT_J: + case SND_SOC_DAIFMT_LEFT_J: + sta32x->format = fmt & SND_SOC_DAIFMT_FORMAT_MASK; + break; + default: + return -EINVAL; + } + + switch (fmt & SND_SOC_DAIFMT_INV_MASK) { + case SND_SOC_DAIFMT_NB_NF: + confb |= STA32X_CONFB_C2IM; + break; + case SND_SOC_DAIFMT_NB_IF: + confb |= STA32X_CONFB_C1IM; + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, STA32X_CONFB, confb); + return 0; +} + +/** + * sta32x_hw_params - program the STA32X with the given hardware parameters. + * @substream: the audio stream + * @params: the hardware parameters to set + * @dai: the SOC DAI (ignored) + * + * This function programs the hardware with the values provided. + * Specifically, the sample rate and the data format. + */ +static int sta32x_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_codec *codec = rtd->codec; + struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec); + unsigned int rate; + int i, mcs = -1, ir = -1; + u8 confa, confb; + + rate = params_rate(params); + pr_debug("rate: %u\n", rate); + for (i = 0; i < ARRAY_SIZE(interpolation_ratios); i++) + if (interpolation_ratios[i].fs == rate) + ir = interpolation_ratios[i].ir; + if (ir < 0) + return -EINVAL; + for (i = 0; mclk_ratios[ir][i].ratio; i++) + if (mclk_ratios[ir][i].ratio * rate == sta32x->mclk) + mcs = mclk_ratios[ir][i].mcs; + if (mcs < 0) + return -EINVAL; + + confa = snd_soc_read(codec, STA32X_CONFA); + confa &= ~(STA32X_CONFA_MCS_MASK | STA32X_CONFA_IR_MASK); + confa |= (ir << STA32X_CONFA_IR_SHIFT) | (mcs << STA32X_CONFA_MCS_SHIFT); + + confb = snd_soc_read(codec, STA32X_CONFB); + confb &= ~(STA32X_CONFB_SAI_MASK | STA32X_CONFB_SAIFB); + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S24_LE: + case SNDRV_PCM_FORMAT_S24_BE: + case SNDRV_PCM_FORMAT_S24_3LE: + case SNDRV_PCM_FORMAT_S24_3BE: + pr_debug("24bit\n"); + /* fall through */ + case SNDRV_PCM_FORMAT_S32_LE: + case SNDRV_PCM_FORMAT_S32_BE: + pr_debug("24bit or 32bit\n"); + switch (sta32x->format) { + case SND_SOC_DAIFMT_I2S: + confb |= 0x0; + break; + case SND_SOC_DAIFMT_LEFT_J: + confb |= 0x1; + break; + case SND_SOC_DAIFMT_RIGHT_J: + confb |= 0x2; + break; + } + + break; + case SNDRV_PCM_FORMAT_S20_3LE: + case SNDRV_PCM_FORMAT_S20_3BE: + pr_debug("20bit\n"); + switch (sta32x->format) { + case SND_SOC_DAIFMT_I2S: + confb |= 0x4; + break; + case SND_SOC_DAIFMT_LEFT_J: + confb |= 0x5; + break; + case SND_SOC_DAIFMT_RIGHT_J: + confb |= 0x6; + break; + } + + break; + case SNDRV_PCM_FORMAT_S18_3LE: + case SNDRV_PCM_FORMAT_S18_3BE: + pr_debug("18bit\n"); + switch (sta32x->format) { + case SND_SOC_DAIFMT_I2S: + confb |= 0x8; + break; + case SND_SOC_DAIFMT_LEFT_J: + confb |= 0x9; + break; + case SND_SOC_DAIFMT_RIGHT_J: + confb |= 0xa; + break; + } + + break; + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_S16_BE: + pr_debug("16bit\n"); + switch (sta32x->format) { + case SND_SOC_DAIFMT_I2S: + confb |= 0x0; + break; + case SND_SOC_DAIFMT_LEFT_J: + confb |= 0xd; + break; + case SND_SOC_DAIFMT_RIGHT_J: + confb |= 0xe; + break; + } + + break; + default: + return -EINVAL; + } + + snd_soc_write(codec, STA32X_CONFA, confa); + snd_soc_write(codec, STA32X_CONFB, confb); + return 0; +} + +/** + * sta32x_set_bias_level - DAPM callback + * @codec: the codec device + * @level: DAPM power level + * + * This is called by ALSA to put the codec into low power mode + * or to wake it up. If the codec is powered off completely + * all registers must be restored after power on. + */ +static int sta32x_set_bias_level(struct snd_soc_codec *codec, + enum snd_soc_bias_level level) +{ + int ret; + struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec); + + pr_debug("level = %d\n", level); + switch (level) { + case SND_SOC_BIAS_ON: + break; + + case SND_SOC_BIAS_PREPARE: + /* Full power on */ + snd_soc_update_bits(codec, STA32X_CONFF, + STA32X_CONFF_PWDN | STA32X_CONFF_EAPD, + STA32X_CONFF_PWDN | STA32X_CONFF_EAPD); + break; + + case SND_SOC_BIAS_STANDBY: + if (codec->dapm.bias_level == SND_SOC_BIAS_OFF) { + ret = regulator_bulk_enable(ARRAY_SIZE(sta32x->supplies), + sta32x->supplies); + if (ret != 0) { + dev_err(codec->dev, + "Failed to enable supplies: %d\n", ret); + return ret; + } + + snd_soc_cache_sync(codec); + } + + /* Power up to mute */ + /* FIXME */ + snd_soc_update_bits(codec, STA32X_CONFF, + STA32X_CONFF_PWDN | STA32X_CONFF_EAPD, + STA32X_CONFF_PWDN | STA32X_CONFF_EAPD); + + break; + + case SND_SOC_BIAS_OFF: + /* The chip runs through the power down sequence for us. */ + snd_soc_update_bits(codec, STA32X_CONFF, + STA32X_CONFF_PWDN | STA32X_CONFF_EAPD, + STA32X_CONFF_PWDN); + msleep(300); + + regulator_bulk_disable(ARRAY_SIZE(sta32x->supplies), + sta32x->supplies); + break; + } + codec->dapm.bias_level = level; + return 0; +} + +static struct snd_soc_dai_ops sta32x_dai_ops = { + .hw_params = sta32x_hw_params, + .set_sysclk = sta32x_set_dai_sysclk, + .set_fmt = sta32x_set_dai_fmt, +}; + +static struct snd_soc_dai_driver sta32x_dai = { + .name = "STA32X", + .playback = { + .stream_name = "Playback", + .channels_min = 2, + .channels_max = 2, + .rates = STA32X_RATES, + .formats = STA32X_FORMATS, + }, + .ops = &sta32x_dai_ops, +}; + +#ifdef CONFIG_PM +static int sta32x_suspend(struct snd_soc_codec *codec, pm_message_t state) +{ + sta32x_set_bias_level(codec, SND_SOC_BIAS_OFF); + return 0; +} + +static int sta32x_resume(struct snd_soc_codec *codec) +{ + sta32x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + return 0; +} +#else +#define sta32x_suspend NULL +#define sta32x_resume NULL +#endif + +static int sta32x_probe(struct snd_soc_codec *codec) +{ + struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec); + int i, ret = 0; + + sta32x->codec = codec; + + /* regulators */ + for (i = 0; i < ARRAY_SIZE(sta32x->supplies); i++) + sta32x->supplies[i].supply = sta32x_supply_names[i]; + + ret = regulator_bulk_get(codec->dev, ARRAY_SIZE(sta32x->supplies), + sta32x->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to request supplies: %d\n", ret); + goto err; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(sta32x->supplies), + sta32x->supplies); + if (ret != 0) { + dev_err(codec->dev, "Failed to enable supplies: %d\n", ret); + goto err_get; + } + + /* Tell ASoC what kind of I/O to use to read the registers. ASoC will + * then do the I2C transactions itself. + */ + ret = snd_soc_codec_set_cache_io(codec, 8, 8, SND_SOC_I2C); + if (ret < 0) { + dev_err(codec->dev, "failed to set cache I/O (ret=%i)\n", ret); + return ret; + } + + /* read reg reset values into cache */ + for (i = 0; i < STA32X_REGISTER_COUNT; i++) + snd_soc_cache_write(codec, i, sta32x_regs[i]); + + /* preserve reset values of reserved register bits */ + snd_soc_cache_write(codec, STA32X_CONFC, + codec->hw_read(codec, STA32X_CONFC)); + snd_soc_cache_write(codec, STA32X_CONFE, + codec->hw_read(codec, STA32X_CONFE)); + snd_soc_cache_write(codec, STA32X_CONFF, + codec->hw_read(codec, STA32X_CONFF)); + snd_soc_cache_write(codec, STA32X_MMUTE, + codec->hw_read(codec, STA32X_MMUTE)); + snd_soc_cache_write(codec, STA32X_AUTO1, + codec->hw_read(codec, STA32X_AUTO1)); + snd_soc_cache_write(codec, STA32X_AUTO3, + codec->hw_read(codec, STA32X_AUTO3)); + snd_soc_cache_write(codec, STA32X_C3CFG, + codec->hw_read(codec, STA32X_C3CFG)); + + /* FIXME enable thermal warning adjustment and recovery */ + snd_soc_update_bits(codec, STA32X_CONFA, + STA32X_CONFA_TWAB | STA32X_CONFA_TWRB, 0); + + /* FIXME select 2.1 mode */ + snd_soc_update_bits(codec, STA32X_CONFF, + STA32X_CONFF_OCFG_MASK, + 1 << STA32X_CONFF_OCFG_SHIFT); + + /* FIXME channel to output mapping */ + snd_soc_update_bits(codec, STA32X_C1CFG, + STA32X_CxCFG_OM_MASK, + 0 << STA32X_CxCFG_OM_SHIFT); + snd_soc_update_bits(codec, STA32X_C2CFG, + STA32X_CxCFG_OM_MASK, + 1 << STA32X_CxCFG_OM_SHIFT); + snd_soc_update_bits(codec, STA32X_C3CFG, + STA32X_CxCFG_OM_MASK, + 2 << STA32X_CxCFG_OM_SHIFT); + + sta32x_set_bias_level(codec, SND_SOC_BIAS_STANDBY); + /* Bias level configuration will have done an extra enable */ + regulator_bulk_disable(ARRAY_SIZE(sta32x->supplies), sta32x->supplies); + + return 0; + +err_get: + regulator_bulk_free(ARRAY_SIZE(sta32x->supplies), sta32x->supplies); +err: + return ret; +} + +static int sta32x_remove(struct snd_soc_codec *codec) +{ + struct sta32x_priv *sta32x = snd_soc_codec_get_drvdata(codec); + + regulator_bulk_disable(ARRAY_SIZE(sta32x->supplies), sta32x->supplies); + regulator_bulk_free(ARRAY_SIZE(sta32x->supplies), sta32x->supplies); + + return 0; +} + +static int sta32x_reg_is_volatile(struct snd_soc_codec *codec, + unsigned int reg) +{ + switch (reg) { + case STA32X_CONFA ... STA32X_L2ATRT: + case STA32X_MPCC1 ... STA32X_FDRC2: + return 0; + } + return 1; +} + +static const struct snd_soc_codec_driver sta32x_codec = { + .probe = sta32x_probe, + .remove = sta32x_remove, + .suspend = sta32x_suspend, + .resume = sta32x_resume, + .reg_cache_size = STA32X_REGISTER_COUNT, + .reg_word_size = sizeof(u8), + .volatile_register = sta32x_reg_is_volatile, + .set_bias_level = sta32x_set_bias_level, + .controls = sta32x_snd_controls, + .num_controls = ARRAY_SIZE(sta32x_snd_controls), + .dapm_widgets = sta32x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(sta32x_dapm_widgets), + .dapm_routes = sta32x_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(sta32x_dapm_routes), +}; + +static __devinit int sta32x_i2c_probe(struct i2c_client *i2c, + const struct i2c_device_id *id) +{ + struct sta32x_priv *sta32x; + int ret; + + sta32x = kzalloc(sizeof(struct sta32x_priv), GFP_KERNEL); + if (!sta32x) + return -ENOMEM; + + i2c_set_clientdata(i2c, sta32x); + + ret = snd_soc_register_codec(&i2c->dev, &sta32x_codec, &sta32x_dai, 1); + if (ret != 0) { + dev_err(&i2c->dev, "Failed to register codec (%d)\n", ret); + return ret; + } + + return 0; +} + +static __devexit int sta32x_i2c_remove(struct i2c_client *client) +{ + struct sta32x_priv *sta32x = i2c_get_clientdata(client); + struct snd_soc_codec *codec = sta32x->codec; + + if (codec) + sta32x_set_bias_level(codec, SND_SOC_BIAS_OFF); + + regulator_bulk_free(ARRAY_SIZE(sta32x->supplies), sta32x->supplies); + + if (codec) { + snd_soc_unregister_codec(&client->dev); + snd_soc_codec_set_drvdata(codec, NULL); + } + + kfree(sta32x); + return 0; +} + +static const struct i2c_device_id sta32x_i2c_id[] = { + { "sta326", 0 }, + { "sta328", 0 }, + { "sta329", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sta32x_i2c_id); + +static struct i2c_driver sta32x_i2c_driver = { + .driver = { + .name = "sta32x", + .owner = THIS_MODULE, + }, + .probe = sta32x_i2c_probe, + .remove = __devexit_p(sta32x_i2c_remove), + .id_table = sta32x_i2c_id, +}; + +static int __init sta32x_init(void) +{ + return i2c_add_driver(&sta32x_i2c_driver); +} +module_init(sta32x_init); + +static void __exit sta32x_exit(void) +{ + i2c_del_driver(&sta32x_i2c_driver); +} +module_exit(sta32x_exit); + +MODULE_DESCRIPTION("ASoC STA32X driver"); +MODULE_AUTHOR("Johannes Stezenbach <js@sig21.net>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/sta32x.h b/sound/soc/codecs/sta32x.h new file mode 100644 index 00000000000..b97ee5a7566 --- /dev/null +++ b/sound/soc/codecs/sta32x.h @@ -0,0 +1,210 @@ +/* + * Codec driver for ST STA32x 2.1-channel high-efficiency digital audio system + * + * Copyright: 2011 Raumfeld GmbH + * Author: Johannes Stezenbach <js@sig21.net> + * + * based on code from: + * Wolfson Microelectronics PLC. + * Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +#ifndef _ASOC_STA_32X_H +#define _ASOC_STA_32X_H + +/* STA326 register addresses */ + +#define STA32X_REGISTER_COUNT 0x2d + +#define STA32X_CONFA 0x00 +#define STA32X_CONFB 0x01 +#define STA32X_CONFC 0x02 +#define STA32X_CONFD 0x03 +#define STA32X_CONFE 0x04 +#define STA32X_CONFF 0x05 +#define STA32X_MMUTE 0x06 +#define STA32X_MVOL 0x07 +#define STA32X_C1VOL 0x08 +#define STA32X_C2VOL 0x09 +#define STA32X_C3VOL 0x0a +#define STA32X_AUTO1 0x0b +#define STA32X_AUTO2 0x0c +#define STA32X_AUTO3 0x0d +#define STA32X_C1CFG 0x0e +#define STA32X_C2CFG 0x0f +#define STA32X_C3CFG 0x10 +#define STA32X_TONE 0x11 +#define STA32X_L1AR 0x12 +#define STA32X_L1ATRT 0x13 +#define STA32X_L2AR 0x14 +#define STA32X_L2ATRT 0x15 +#define STA32X_CFADDR2 0x16 +#define STA32X_B1CF1 0x17 +#define STA32X_B1CF2 0x18 +#define STA32X_B1CF3 0x19 +#define STA32X_B2CF1 0x1a +#define STA32X_B2CF2 0x1b +#define STA32X_B2CF3 0x1c +#define STA32X_A1CF1 0x1d +#define STA32X_A1CF2 0x1e +#define STA32X_A1CF3 0x1f +#define STA32X_A2CF1 0x20 +#define STA32X_A2CF2 0x21 +#define STA32X_A2CF3 0x22 +#define STA32X_B0CF1 0x23 +#define STA32X_B0CF2 0x24 +#define STA32X_B0CF3 0x25 +#define STA32X_CFUD 0x26 +#define STA32X_MPCC1 0x27 +#define STA32X_MPCC2 0x28 +/* Reserved 0x29 */ +/* Reserved 0x2a */ +#define STA32X_Reserved 0x2a +#define STA32X_FDRC1 0x2b +#define STA32X_FDRC2 0x2c +/* Reserved 0x2d */ + + +/* STA326 register field definitions */ + +/* 0x00 CONFA */ +#define STA32X_CONFA_MCS_MASK 0x03 +#define STA32X_CONFA_MCS_SHIFT 0 +#define STA32X_CONFA_IR_MASK 0x18 +#define STA32X_CONFA_IR_SHIFT 3 +#define STA32X_CONFA_TWRB 0x20 +#define STA32X_CONFA_TWAB 0x40 +#define STA32X_CONFA_FDRB 0x80 + +/* 0x01 CONFB */ +#define STA32X_CONFB_SAI_MASK 0x0f +#define STA32X_CONFB_SAI_SHIFT 0 +#define STA32X_CONFB_SAIFB 0x10 +#define STA32X_CONFB_DSCKE 0x20 +#define STA32X_CONFB_C1IM 0x40 +#define STA32X_CONFB_C2IM 0x80 + +/* 0x02 CONFC */ +#define STA32X_CONFC_OM_MASK 0x03 +#define STA32X_CONFC_OM_SHIFT 0 +#define STA32X_CONFC_CSZ_MASK 0x7c +#define STA32X_CONFC_CSZ_SHIFT 2 + +/* 0x03 CONFD */ +#define STA32X_CONFD_HPB 0x01 +#define STA32X_CONFD_HPB_SHIFT 0 +#define STA32X_CONFD_DEMP 0x02 +#define STA32X_CONFD_DEMP_SHIFT 1 +#define STA32X_CONFD_DSPB 0x04 +#define STA32X_CONFD_DSPB_SHIFT 2 +#define STA32X_CONFD_PSL 0x08 +#define STA32X_CONFD_PSL_SHIFT 3 +#define STA32X_CONFD_BQL 0x10 +#define STA32X_CONFD_BQL_SHIFT 4 +#define STA32X_CONFD_DRC 0x20 +#define STA32X_CONFD_DRC_SHIFT 5 +#define STA32X_CONFD_ZDE 0x40 +#define STA32X_CONFD_ZDE_SHIFT 6 +#define STA32X_CONFD_MME 0x80 +#define STA32X_CONFD_MME_SHIFT 7 + +/* 0x04 CONFE */ +#define STA32X_CONFE_MPCV 0x01 +#define STA32X_CONFE_MPCV_SHIFT 0 +#define STA32X_CONFE_MPC 0x02 +#define STA32X_CONFE_MPC_SHIFT 1 +#define STA32X_CONFE_AME 0x08 +#define STA32X_CONFE_AME_SHIFT 3 +#define STA32X_CONFE_PWMS 0x10 +#define STA32X_CONFE_PWMS_SHIFT 4 +#define STA32X_CONFE_ZCE 0x40 +#define STA32X_CONFE_ZCE_SHIFT 6 +#define STA32X_CONFE_SVE 0x80 +#define STA32X_CONFE_SVE_SHIFT 7 + +/* 0x05 CONFF */ +#define STA32X_CONFF_OCFG_MASK 0x03 +#define STA32X_CONFF_OCFG_SHIFT 0 +#define STA32X_CONFF_IDE 0x04 +#define STA32X_CONFF_IDE_SHIFT 3 +#define STA32X_CONFF_BCLE 0x08 +#define STA32X_CONFF_ECLE 0x20 +#define STA32X_CONFF_PWDN 0x40 +#define STA32X_CONFF_EAPD 0x80 + +/* 0x06 MMUTE */ +#define STA32X_MMUTE_MMUTE 0x01 + +/* 0x0b AUTO1 */ +#define STA32X_AUTO1_AMEQ_MASK 0x03 +#define STA32X_AUTO1_AMEQ_SHIFT 0 +#define STA32X_AUTO1_AMV_MASK 0xc0 +#define STA32X_AUTO1_AMV_SHIFT 2 +#define STA32X_AUTO1_AMGC_MASK 0x30 +#define STA32X_AUTO1_AMGC_SHIFT 4 +#define STA32X_AUTO1_AMPS 0x80 + +/* 0x0c AUTO2 */ +#define STA32X_AUTO2_AMAME 0x01 +#define STA32X_AUTO2_AMAM_MASK 0x0e +#define STA32X_AUTO2_AMAM_SHIFT 1 +#define STA32X_AUTO2_XO_MASK 0xf0 +#define STA32X_AUTO2_XO_SHIFT 4 + +/* 0x0d AUTO3 */ +#define STA32X_AUTO3_PEQ_MASK 0x1f +#define STA32X_AUTO3_PEQ_SHIFT 0 + +/* 0x0e 0x0f 0x10 CxCFG */ +#define STA32X_CxCFG_TCB 0x01 /* only C1 and C2 */ +#define STA32X_CxCFG_TCB_SHIFT 0 +#define STA32X_CxCFG_EQBP 0x02 /* only C1 and C2 */ +#define STA32X_CxCFG_EQBP_SHIFT 1 +#define STA32X_CxCFG_VBP 0x03 +#define STA32X_CxCFG_VBP_SHIFT 2 +#define STA32X_CxCFG_BO 0x04 +#define STA32X_CxCFG_LS_MASK 0x30 +#define STA32X_CxCFG_LS_SHIFT 4 +#define STA32X_CxCFG_OM_MASK 0xc0 +#define STA32X_CxCFG_OM_SHIFT 6 + +/* 0x11 TONE */ +#define STA32X_TONE_BTC_SHIFT 0 +#define STA32X_TONE_TTC_SHIFT 4 + +/* 0x12 0x13 0x14 0x15 limiter attack/release */ +#define STA32X_LxA_SHIFT 0 +#define STA32X_LxR_SHIFT 4 + +/* 0x26 CFUD */ +#define STA32X_CFUD_W1 0x01 +#define STA32X_CFUD_WA 0x02 +#define STA32X_CFUD_R1 0x04 +#define STA32X_CFUD_RA 0x08 + + +/* biquad filter coefficient table offsets */ +#define STA32X_C1_BQ_BASE 0 +#define STA32X_C2_BQ_BASE 20 +#define STA32X_CH_BQ_NUM 4 +#define STA32X_BQ_NUM_COEF 5 +#define STA32X_XO_HP_BQ_BASE 40 +#define STA32X_XO_LP_BQ_BASE 45 +#define STA32X_C1_PRESCALE 50 +#define STA32X_C2_PRESCALE 51 +#define STA32X_C1_POSTSCALE 52 +#define STA32X_C2_POSTSCALE 53 +#define STA32X_C3_POSTSCALE 54 +#define STA32X_TW_POSTSCALE 55 +#define STA32X_C1_MIX1 56 +#define STA32X_C1_MIX2 57 +#define STA32X_C2_MIX1 58 +#define STA32X_C2_MIX2 59 +#define STA32X_C3_MIX1 60 +#define STA32X_C3_MIX2 61 + +#endif /* _ASOC_STA_32X_H */ diff --git a/sound/soc/codecs/tlv320aic3x.c b/sound/soc/codecs/tlv320aic3x.c index 789453d44ec..0963c4c7a83 100644 --- a/sound/soc/codecs/tlv320aic3x.c +++ b/sound/soc/codecs/tlv320aic3x.c @@ -226,11 +226,13 @@ static const char *aic3x_adc_hpf[] = #define RDAC_ENUM 1 #define LHPCOM_ENUM 2 #define RHPCOM_ENUM 3 -#define LINE1L_ENUM 4 -#define LINE1R_ENUM 5 -#define LINE2L_ENUM 6 -#define LINE2R_ENUM 7 -#define ADC_HPF_ENUM 8 +#define LINE1L_2_L_ENUM 4 +#define LINE1L_2_R_ENUM 5 +#define LINE1R_2_L_ENUM 6 +#define LINE1R_2_R_ENUM 7 +#define LINE2L_ENUM 8 +#define LINE2R_ENUM 9 +#define ADC_HPF_ENUM 10 static const struct soc_enum aic3x_enum[] = { SOC_ENUM_SINGLE(DAC_LINE_MUX, 6, 3, aic3x_left_dac_mux), @@ -238,6 +240,8 @@ static const struct soc_enum aic3x_enum[] = { SOC_ENUM_SINGLE(HPLCOM_CFG, 4, 3, aic3x_left_hpcom_mux), SOC_ENUM_SINGLE(HPRCOM_CFG, 3, 5, aic3x_right_hpcom_mux), SOC_ENUM_SINGLE(LINE1L_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux), + SOC_ENUM_SINGLE(LINE1L_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux), + SOC_ENUM_SINGLE(LINE1R_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux), SOC_ENUM_SINGLE(LINE1R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux), SOC_ENUM_SINGLE(LINE2L_2_LADC_CTRL, 7, 2, aic3x_linein_mode_mux), SOC_ENUM_SINGLE(LINE2R_2_RADC_CTRL, 7, 2, aic3x_linein_mode_mux), @@ -490,12 +494,16 @@ static const struct snd_kcontrol_new aic3x_right_pga_mixer_controls[] = { }; /* Left Line1 Mux */ -static const struct snd_kcontrol_new aic3x_left_line1_mux_controls = -SOC_DAPM_ENUM("Route", aic3x_enum[LINE1L_ENUM]); +static const struct snd_kcontrol_new aic3x_left_line1l_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_enum[LINE1L_2_L_ENUM]); +static const struct snd_kcontrol_new aic3x_right_line1l_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_enum[LINE1L_2_R_ENUM]); /* Right Line1 Mux */ -static const struct snd_kcontrol_new aic3x_right_line1_mux_controls = -SOC_DAPM_ENUM("Route", aic3x_enum[LINE1R_ENUM]); +static const struct snd_kcontrol_new aic3x_right_line1r_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_enum[LINE1R_2_R_ENUM]); +static const struct snd_kcontrol_new aic3x_left_line1r_mux_controls = +SOC_DAPM_ENUM("Route", aic3x_enum[LINE1R_2_L_ENUM]); /* Left Line2 Mux */ static const struct snd_kcontrol_new aic3x_left_line2_mux_controls = @@ -535,9 +543,9 @@ static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { &aic3x_left_pga_mixer_controls[0], ARRAY_SIZE(aic3x_left_pga_mixer_controls)), SND_SOC_DAPM_MUX("Left Line1L Mux", SND_SOC_NOPM, 0, 0, - &aic3x_left_line1_mux_controls), + &aic3x_left_line1l_mux_controls), SND_SOC_DAPM_MUX("Left Line1R Mux", SND_SOC_NOPM, 0, 0, - &aic3x_left_line1_mux_controls), + &aic3x_left_line1r_mux_controls), SND_SOC_DAPM_MUX("Left Line2L Mux", SND_SOC_NOPM, 0, 0, &aic3x_left_line2_mux_controls), @@ -548,9 +556,9 @@ static const struct snd_soc_dapm_widget aic3x_dapm_widgets[] = { &aic3x_right_pga_mixer_controls[0], ARRAY_SIZE(aic3x_right_pga_mixer_controls)), SND_SOC_DAPM_MUX("Right Line1L Mux", SND_SOC_NOPM, 0, 0, - &aic3x_right_line1_mux_controls), + &aic3x_right_line1l_mux_controls), SND_SOC_DAPM_MUX("Right Line1R Mux", SND_SOC_NOPM, 0, 0, - &aic3x_right_line1_mux_controls), + &aic3x_right_line1r_mux_controls), SND_SOC_DAPM_MUX("Right Line2R Mux", SND_SOC_NOPM, 0, 0, &aic3x_right_line2_mux_controls), diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index 4c336636d4f..cd63bba623d 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -954,9 +954,9 @@ static DECLARE_TLV_DB_SCALE(mic_preamp_tlv, -600, 600, 0); /* * MICGAIN volume control: - * from -6 to 30 dB in 6 dB steps + * from 6 to 30 dB in 6 dB steps */ -static DECLARE_TLV_DB_SCALE(mic_amp_tlv, -600, 600, 0); +static DECLARE_TLV_DB_SCALE(mic_amp_tlv, 600, 600, 0); /* * AFMGAIN volume control: diff --git a/sound/soc/codecs/wm8782.c b/sound/soc/codecs/wm8782.c new file mode 100644 index 00000000000..a2a09f85ea9 --- /dev/null +++ b/sound/soc/codecs/wm8782.c @@ -0,0 +1,80 @@ +/* + * sound/soc/codecs/wm8782.c + * simple, strap-pin configured 24bit 2ch ADC + * + * Copyright: 2011 Raumfeld GmbH + * Author: Johannes Stezenbach <js@sig21.net> + * + * based on ad73311.c + * Copyright: Analog Device Inc. + * Author: Cliff Cai <cliff.cai@analog.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/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/ac97_codec.h> +#include <sound/initval.h> +#include <sound/soc.h> + +static struct snd_soc_dai_driver wm8782_dai = { + .name = "wm8782", + .capture = { + .stream_name = "Capture", + .channels_min = 2, + .channels_max = 2, + /* For configurations with FSAMPEN=0 */ + .rates = SNDRV_PCM_RATE_8000_48000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S20_3LE | + SNDRV_PCM_FMTBIT_S24_LE, + }, +}; + +static struct snd_soc_codec_driver soc_codec_dev_wm8782; + +static __devinit int wm8782_probe(struct platform_device *pdev) +{ + return snd_soc_register_codec(&pdev->dev, + &soc_codec_dev_wm8782, &wm8782_dai, 1); +} + +static int __devexit wm8782_remove(struct platform_device *pdev) +{ + snd_soc_unregister_codec(&pdev->dev); + return 0; +} + +static struct platform_driver wm8782_codec_driver = { + .driver = { + .name = "wm8782", + .owner = THIS_MODULE, + }, + .probe = wm8782_probe, + .remove = wm8782_remove, +}; + +static int __init wm8782_init(void) +{ + return platform_driver_register(&wm8782_codec_driver); +} +module_init(wm8782_init); + +static void __exit wm8782_exit(void) +{ + platform_driver_unregister(&wm8782_codec_driver); +} +module_exit(wm8782_exit); + +MODULE_DESCRIPTION("ASoC WM8782 driver"); +MODULE_AUTHOR("Johannes Stezenbach <js@sig21.net>"); +MODULE_LICENSE("GPL"); diff --git a/sound/soc/codecs/wm8900.c b/sound/soc/codecs/wm8900.c index 449ea09a193..082040eda8a 100644 --- a/sound/soc/codecs/wm8900.c +++ b/sound/soc/codecs/wm8900.c @@ -1167,6 +1167,7 @@ static int wm8900_resume(struct snd_soc_codec *codec) ret = wm8900_set_fll(codec, 0, fll_in, fll_out); if (ret != 0) { dev_err(codec->dev, "Failed to restart FLL\n"); + kfree(cache); return ret; } } diff --git a/sound/soc/codecs/wm8904.c b/sound/soc/codecs/wm8904.c index 9b3bba4df5b..b085575d4aa 100644 --- a/sound/soc/codecs/wm8904.c +++ b/sound/soc/codecs/wm8904.c @@ -2560,6 +2560,7 @@ static __devexit int wm8904_i2c_remove(struct i2c_client *client) static const struct i2c_device_id wm8904_i2c_id[] = { { "wm8904", WM8904 }, { "wm8912", WM8912 }, + { "wm8918", WM8904 }, /* Actually a subset, updates to follow */ { } }; MODULE_DEVICE_TABLE(i2c, wm8904_i2c_id); diff --git a/sound/soc/codecs/wm8915.c b/sound/soc/codecs/wm8915.c index e2ab4fac281..423baa9be24 100644 --- a/sound/soc/codecs/wm8915.c +++ b/sound/soc/codecs/wm8915.c @@ -41,14 +41,12 @@ #define HPOUT2L 4 #define HPOUT2R 8 -#define WM8915_NUM_SUPPLIES 6 +#define WM8915_NUM_SUPPLIES 4 static const char *wm8915_supply_names[WM8915_NUM_SUPPLIES] = { - "DCVDD", "DBVDD", "AVDD1", "AVDD2", "CPVDD", - "MICVDD", }; struct wm8915_priv { @@ -57,6 +55,7 @@ struct wm8915_priv { int ldo1ena; int sysclk; + int sysclk_src; int fll_src; int fll_fref; @@ -76,6 +75,7 @@ struct wm8915_priv { struct wm8915_pdata pdata; int rx_rate[WM8915_AIFS]; + int bclk_rate[WM8915_AIFS]; /* Platform dependant ReTune mobile configuration */ int num_retune_mobile_texts; @@ -113,8 +113,6 @@ WM8915_REGULATOR_EVENT(0) WM8915_REGULATOR_EVENT(1) WM8915_REGULATOR_EVENT(2) WM8915_REGULATOR_EVENT(3) -WM8915_REGULATOR_EVENT(4) -WM8915_REGULATOR_EVENT(5) static const u16 wm8915_reg[WM8915_MAX_REGISTER] = { [WM8915_SOFTWARE_RESET] = 0x8915, @@ -1565,6 +1563,50 @@ static int wm8915_reset(struct snd_soc_codec *codec) return snd_soc_write(codec, WM8915_SOFTWARE_RESET, 0x8915); } +static const int bclk_divs[] = { + 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96 +}; + +static void wm8915_update_bclk(struct snd_soc_codec *codec) +{ + struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec); + int aif, best, cur_val, bclk_rate, bclk_reg, i; + + /* Don't bother if we're in a low frequency idle mode that + * can't support audio. + */ + if (wm8915->sysclk < 64000) + return; + + for (aif = 0; aif < WM8915_AIFS; aif++) { + switch (aif) { + case 0: + bclk_reg = WM8915_AIF1_BCLK; + break; + case 1: + bclk_reg = WM8915_AIF2_BCLK; + break; + } + + bclk_rate = wm8915->bclk_rate[aif]; + + /* Pick a divisor for BCLK as close as we can get to ideal */ + best = 0; + for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) { + cur_val = (wm8915->sysclk / bclk_divs[i]) - bclk_rate; + if (cur_val < 0) /* BCLK table is sorted */ + break; + best = i; + } + bclk_rate = wm8915->sysclk / bclk_divs[best]; + dev_dbg(codec->dev, "Using BCLK_DIV %d for actual BCLK %dHz\n", + bclk_divs[best], bclk_rate); + + snd_soc_update_bits(codec, bclk_reg, + WM8915_AIF1_BCLK_DIV_MASK, best); + } +} + static int wm8915_set_bias_level(struct snd_soc_codec *codec, enum snd_soc_bias_level level) { @@ -1717,10 +1759,6 @@ static int wm8915_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) return 0; } -static const int bclk_divs[] = { - 1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96 -}; - static const int dsp_divs[] = { 48000, 32000, 16000, 8000 }; @@ -1731,17 +1769,11 @@ static int wm8915_hw_params(struct snd_pcm_substream *substream, { struct snd_soc_codec *codec = dai->codec; struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec); - int bits, i, bclk_rate, best, cur_val; + int bits, i, bclk_rate; int aifdata = 0; - int bclk = 0; int lrclk = 0; int dsp = 0; - int aifdata_reg, bclk_reg, lrclk_reg, dsp_shift; - - if (!wm8915->sysclk) { - dev_err(codec->dev, "SYSCLK not configured\n"); - return -EINVAL; - } + int aifdata_reg, lrclk_reg, dsp_shift; switch (dai->id) { case 0: @@ -1753,7 +1785,6 @@ static int wm8915_hw_params(struct snd_pcm_substream *substream, aifdata_reg = WM8915_AIF1TX_DATA_CONFIGURATION_1; lrclk_reg = WM8915_AIF1_TX_LRCLK_1; } - bclk_reg = WM8915_AIF1_BCLK; dsp_shift = 0; break; case 1: @@ -1765,7 +1796,6 @@ static int wm8915_hw_params(struct snd_pcm_substream *substream, aifdata_reg = WM8915_AIF2TX_DATA_CONFIGURATION_1; lrclk_reg = WM8915_AIF2_TX_LRCLK_1; } - bclk_reg = WM8915_AIF2_BCLK; dsp_shift = WM8915_DSP2_DIV_SHIFT; break; default: @@ -1779,6 +1809,9 @@ static int wm8915_hw_params(struct snd_pcm_substream *substream, return bclk_rate; } + wm8915->bclk_rate[dai->id] = bclk_rate; + wm8915->rx_rate[dai->id] = params_rate(params); + /* Needs looking at for TDM */ bits = snd_pcm_format_width(params_format(params)); if (bits < 0) @@ -1796,18 +1829,7 @@ static int wm8915_hw_params(struct snd_pcm_substream *substream, } dsp |= i << dsp_shift; - /* Pick a divisor for BCLK as close as we can get to ideal */ - best = 0; - for (i = 0; i < ARRAY_SIZE(bclk_divs); i++) { - cur_val = (wm8915->sysclk / bclk_divs[i]) - bclk_rate; - if (cur_val < 0) /* BCLK table is sorted */ - break; - best = i; - } - bclk_rate = wm8915->sysclk / bclk_divs[best]; - dev_dbg(dai->dev, "Using BCLK_DIV %d for actual BCLK %dHz\n", - bclk_divs[best], bclk_rate); - bclk |= best; + wm8915_update_bclk(codec); lrclk = bclk_rate / params_rate(params); dev_dbg(dai->dev, "Using LRCLK rate %d for actual LRCLK %dHz\n", @@ -1817,14 +1839,11 @@ static int wm8915_hw_params(struct snd_pcm_substream *substream, WM8915_AIF1TX_WL_MASK | WM8915_AIF1TX_SLOT_LEN_MASK, aifdata); - snd_soc_update_bits(codec, bclk_reg, WM8915_AIF1_BCLK_DIV_MASK, bclk); snd_soc_update_bits(codec, lrclk_reg, WM8915_AIF1RX_RATE_MASK, lrclk); snd_soc_update_bits(codec, WM8915_AIF_CLOCKING_2, WM8915_DSP1_DIV_SHIFT << dsp_shift, dsp); - wm8915->rx_rate[dai->id] = params_rate(params); - return 0; } @@ -1838,6 +1857,9 @@ static int wm8915_set_sysclk(struct snd_soc_dai *dai, int src; int old; + if (freq == wm8915->sysclk && clk_id == wm8915->sysclk_src) + return 0; + /* Disable SYSCLK while we reconfigure */ old = snd_soc_read(codec, WM8915_AIF_CLOCKING_1) & WM8915_SYSCLK_ENA; snd_soc_update_bits(codec, WM8915_AIF_CLOCKING_1, @@ -1882,6 +1904,8 @@ static int wm8915_set_sysclk(struct snd_soc_dai *dai, return -EINVAL; } + wm8915_update_bclk(codec); + snd_soc_update_bits(codec, WM8915_AIF_CLOCKING_1, WM8915_SYSCLK_SRC_MASK | WM8915_SYSCLK_DIV_MASK, src << WM8915_SYSCLK_SRC_SHIFT | ratediv); @@ -1889,6 +1913,8 @@ static int wm8915_set_sysclk(struct snd_soc_dai *dai, snd_soc_update_bits(codec, WM8915_AIF_CLOCKING_1, WM8915_SYSCLK_ENA, old); + wm8915->sysclk_src = clk_id; + return 0; } @@ -2007,6 +2033,7 @@ static int wm8915_set_fll(struct snd_soc_codec *codec, int fll_id, int source, unsigned int Fref, unsigned int Fout) { struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec); + struct i2c_client *i2c = to_i2c_client(codec->dev); struct _fll_div fll_div; unsigned long timeout; int ret, reg; @@ -2093,7 +2120,18 @@ static int wm8915_set_fll(struct snd_soc_codec *codec, int fll_id, int source, else timeout = msecs_to_jiffies(2); - wait_for_completion_timeout(&wm8915->fll_lock, timeout); + /* Allow substantially longer if we've actually got the IRQ */ + if (i2c->irq) + timeout *= 1000; + + ret = wait_for_completion_timeout(&wm8915->fll_lock, timeout); + + if (ret == 0 && i2c->irq) { + dev_err(codec->dev, "Timed out waiting for FLL\n"); + ret = -ETIMEDOUT; + } else { + ret = 0; + } dev_dbg(codec->dev, "FLL configured for %dHz->%dHz\n", Fref, Fout); @@ -2101,7 +2139,7 @@ static int wm8915_set_fll(struct snd_soc_codec *codec, int fll_id, int source, wm8915->fll_fout = Fout; wm8915->fll_src = source; - return 0; + return ret; } #ifdef CONFIG_GPIOLIB @@ -2293,6 +2331,12 @@ static void wm8915_micd(struct snd_soc_codec *codec) SND_JACK_HEADSET | SND_JACK_BTN_0); wm8915->jack_mic = true; wm8915->detecting = false; + + /* Increase poll rate to give better responsiveness + * for buttons */ + snd_soc_update_bits(codec, WM8915_MIC_DETECT_1, + WM8915_MICD_RATE_MASK, + 5 << WM8915_MICD_RATE_SHIFT); } /* If we detected a lower impedence during initial startup @@ -2333,15 +2377,17 @@ static void wm8915_micd(struct snd_soc_codec *codec) SND_JACK_HEADPHONE, SND_JACK_HEADSET | SND_JACK_BTN_0); + + /* Increase the detection rate a bit for + * responsiveness. + */ + snd_soc_update_bits(codec, WM8915_MIC_DETECT_1, + WM8915_MICD_RATE_MASK, + 7 << WM8915_MICD_RATE_SHIFT); + wm8915->detecting = false; } } - - /* Increase poll rate to give better responsiveness for buttons */ - if (!wm8915->detecting) - snd_soc_update_bits(codec, WM8915_MIC_DETECT_1, - WM8915_MICD_RATE_MASK, - 5 << WM8915_MICD_RATE_SHIFT); } static irqreturn_t wm8915_irq(int irq, void *data) @@ -2383,6 +2429,20 @@ static irqreturn_t wm8915_irq(int irq, void *data) } } +static irqreturn_t wm8915_edge_irq(int irq, void *data) +{ + irqreturn_t ret = IRQ_NONE; + irqreturn_t val; + + do { + val = wm8915_irq(irq, data); + if (val != IRQ_NONE) + ret = val; + } while (val != IRQ_NONE); + + return ret; +} + static void wm8915_retune_mobile_pdata(struct snd_soc_codec *codec) { struct wm8915_priv *wm8915 = snd_soc_codec_get_drvdata(codec); @@ -2482,8 +2542,6 @@ static int wm8915_probe(struct snd_soc_codec *codec) wm8915->disable_nb[1].notifier_call = wm8915_regulator_event_1; wm8915->disable_nb[2].notifier_call = wm8915_regulator_event_2; wm8915->disable_nb[3].notifier_call = wm8915_regulator_event_3; - wm8915->disable_nb[4].notifier_call = wm8915_regulator_event_4; - wm8915->disable_nb[5].notifier_call = wm8915_regulator_event_5; /* This should really be moved into the regulator core */ for (i = 0; i < ARRAY_SIZE(wm8915->supplies); i++) { @@ -2709,8 +2767,14 @@ static int wm8915_probe(struct snd_soc_codec *codec) irq_flags |= IRQF_ONESHOT; - ret = request_threaded_irq(i2c->irq, NULL, wm8915_irq, - irq_flags, "wm8915", codec); + if (irq_flags & (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)) + ret = request_threaded_irq(i2c->irq, NULL, + wm8915_edge_irq, + irq_flags, "wm8915", codec); + else + ret = request_threaded_irq(i2c->irq, NULL, wm8915_irq, + irq_flags, "wm8915", codec); + if (ret == 0) { /* Unmask the interrupt */ snd_soc_update_bits(codec, WM8915_INTERRUPT_CONTROL, diff --git a/sound/soc/codecs/wm8940.c b/sound/soc/codecs/wm8940.c index 25580e3ee7c..056daa0010f 100644 --- a/sound/soc/codecs/wm8940.c +++ b/sound/soc/codecs/wm8940.c @@ -297,8 +297,6 @@ static int wm8940_add_widgets(struct snd_soc_codec *codec) if (ret) goto error_ret; ret = snd_soc_dapm_add_routes(dapm, audio_map, ARRAY_SIZE(audio_map)); - if (ret) - goto error_ret; error_ret: return ret; @@ -683,8 +681,6 @@ static int wm8940_resume(struct snd_soc_codec *codec) } } ret = wm8940_set_bias_level(codec, SND_SOC_BIAS_STANDBY); - if (ret) - goto error_ret; error_ret: return ret; @@ -730,9 +726,6 @@ static int wm8940_probe(struct snd_soc_codec *codec) if (ret) return ret; ret = wm8940_add_widgets(codec); - if (ret) - return ret; - return ret; } diff --git a/sound/soc/codecs/wm8962.c b/sound/soc/codecs/wm8962.c index 5e05eed96c3..8499c563a9b 100644 --- a/sound/soc/codecs/wm8962.c +++ b/sound/soc/codecs/wm8962.c @@ -78,6 +78,8 @@ struct wm8962_priv { #ifdef CONFIG_GPIOLIB struct gpio_chip gpio_chip; #endif + + int irq; }; /* We can't use the same notifier block for more than one supply and @@ -1982,6 +1984,7 @@ static const unsigned int classd_tlv[] = { 0, 6, TLV_DB_SCALE_ITEM(0, 150, 0), 7, 7, TLV_DB_SCALE_ITEM(1200, 0, 0), }; +static const DECLARE_TLV_DB_SCALE(eq_tlv, -1200, 100, 0); /* The VU bits for the headphones are in a different register to the mute * bits and only take effect on the PGA if it is actually powered. @@ -2119,6 +2122,18 @@ SOC_SINGLE_TLV("HPMIXR MIXINR Volume", WM8962_HEADPHONE_MIXER_4, SOC_SINGLE_TLV("Speaker Boost Volume", WM8962_CLASS_D_CONTROL_2, 0, 7, 0, classd_tlv), + +SOC_SINGLE("EQ Switch", WM8962_EQ1, WM8962_EQ_ENA_SHIFT, 1, 0), +SOC_DOUBLE_R_TLV("EQ1 Volume", WM8962_EQ2, WM8962_EQ22, + WM8962_EQL_B1_GAIN_SHIFT, 31, 0, eq_tlv), +SOC_DOUBLE_R_TLV("EQ2 Volume", WM8962_EQ2, WM8962_EQ22, + WM8962_EQL_B2_GAIN_SHIFT, 31, 0, eq_tlv), +SOC_DOUBLE_R_TLV("EQ3 Volume", WM8962_EQ2, WM8962_EQ22, + WM8962_EQL_B3_GAIN_SHIFT, 31, 0, eq_tlv), +SOC_DOUBLE_R_TLV("EQ4 Volume", WM8962_EQ3, WM8962_EQ23, + WM8962_EQL_B4_GAIN_SHIFT, 31, 0, eq_tlv), +SOC_DOUBLE_R_TLV("EQ5 Volume", WM8962_EQ3, WM8962_EQ23, + WM8962_EQL_B5_GAIN_SHIFT, 31, 0, eq_tlv), }; static const struct snd_kcontrol_new wm8962_spk_mono_controls[] = { @@ -2184,6 +2199,8 @@ static int sysclk_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *kcontrol, int event) { struct snd_soc_codec *codec = w->codec; + struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec); + unsigned long timeout; int src; int fll; @@ -2203,9 +2220,19 @@ static int sysclk_event(struct snd_soc_dapm_widget *w, switch (event) { case SND_SOC_DAPM_PRE_PMU: - if (fll) + if (fll) { snd_soc_update_bits(codec, WM8962_FLL_CONTROL_1, WM8962_FLL_ENA, WM8962_FLL_ENA); + if (wm8962->irq) { + timeout = msecs_to_jiffies(5); + timeout = wait_for_completion_timeout(&wm8962->fll_lock, + timeout); + + if (timeout == 0) + dev_err(codec->dev, + "Timed out starting FLL\n"); + } + } break; case SND_SOC_DAPM_POST_PMD: @@ -2763,18 +2790,44 @@ static const int bclk_divs[] = { 1, -1, 2, 3, 4, -1, 6, 8, -1, 12, 16, 24, -1, 32, 32, 32 }; +static const int sysclk_rates[] = { + 64, 128, 192, 256, 384, 512, 768, 1024, 1408, 1536, +}; + static void wm8962_configure_bclk(struct snd_soc_codec *codec) { struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec); int dspclk, i; int clocking2 = 0; + int clocking4 = 0; int aif2 = 0; - if (!wm8962->bclk) { - dev_dbg(codec->dev, "No BCLK rate configured\n"); + if (!wm8962->sysclk_rate) { + dev_dbg(codec->dev, "No SYSCLK configured\n"); + return; + } + + if (!wm8962->bclk || !wm8962->lrclk) { + dev_dbg(codec->dev, "No audio clocks configured\n"); return; } + for (i = 0; i < ARRAY_SIZE(sysclk_rates); i++) { + if (sysclk_rates[i] == wm8962->sysclk_rate / wm8962->lrclk) { + clocking4 |= i << WM8962_SYSCLK_RATE_SHIFT; + break; + } + } + + if (i == ARRAY_SIZE(sysclk_rates)) { + dev_err(codec->dev, "Unsupported sysclk ratio %d\n", + wm8962->sysclk_rate / wm8962->lrclk); + return; + } + + snd_soc_update_bits(codec, WM8962_CLOCKING_4, + WM8962_SYSCLK_RATE_MASK, clocking4); + dspclk = snd_soc_read(codec, WM8962_CLOCKING1); if (dspclk < 0) { dev_err(codec->dev, "Failed to read DSPCLK: %d\n", dspclk); @@ -2844,6 +2897,8 @@ static int wm8962_set_bias_level(struct snd_soc_codec *codec, /* VMID 2*50k */ snd_soc_update_bits(codec, WM8962_PWR_MGMT_1, WM8962_VMID_SEL_MASK, 0x80); + + wm8962_configure_bclk(codec); break; case SND_SOC_BIAS_STANDBY: @@ -2876,8 +2931,6 @@ static int wm8962_set_bias_level(struct snd_soc_codec *codec, snd_soc_update_bits(codec, WM8962_CLOCKING2, WM8962_CLKREG_OVD, WM8962_CLKREG_OVD); - - wm8962_configure_bclk(codec); } /* VMID 2*250k */ @@ -2918,10 +2971,6 @@ static const struct { { 96000, 6 }, }; -static const int sysclk_rates[] = { - 64, 128, 192, 256, 384, 512, 768, 1024, 1408, 1536, -}; - static int wm8962_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) @@ -2929,41 +2978,27 @@ static int wm8962_hw_params(struct snd_pcm_substream *substream, struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_codec *codec = rtd->codec; struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec); - int rate = params_rate(params); int i; int aif0 = 0; int adctl3 = 0; - int clocking4 = 0; wm8962->bclk = snd_soc_params_to_bclk(params); wm8962->lrclk = params_rate(params); for (i = 0; i < ARRAY_SIZE(sr_vals); i++) { - if (sr_vals[i].rate == rate) { + if (sr_vals[i].rate == wm8962->lrclk) { adctl3 |= sr_vals[i].reg; break; } } if (i == ARRAY_SIZE(sr_vals)) { - dev_err(codec->dev, "Unsupported rate %dHz\n", rate); + dev_err(codec->dev, "Unsupported rate %dHz\n", wm8962->lrclk); return -EINVAL; } - if (rate % 8000 == 0) + if (wm8962->lrclk % 8000 == 0) adctl3 |= WM8962_SAMPLE_RATE_INT_MODE; - for (i = 0; i < ARRAY_SIZE(sysclk_rates); i++) { - if (sysclk_rates[i] == wm8962->sysclk_rate / rate) { - clocking4 |= i << WM8962_SYSCLK_RATE_SHIFT; - break; - } - } - if (i == ARRAY_SIZE(sysclk_rates)) { - dev_err(codec->dev, "Unsupported sysclk ratio %d\n", - wm8962->sysclk_rate / rate); - return -EINVAL; - } - switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: break; @@ -2985,8 +3020,6 @@ static int wm8962_hw_params(struct snd_pcm_substream *substream, snd_soc_update_bits(codec, WM8962_ADDITIONAL_CONTROL_3, WM8962_SAMPLE_RATE_INT_MODE | WM8962_SAMPLE_RATE_MASK, adctl3); - snd_soc_update_bits(codec, WM8962_CLOCKING_4, - WM8962_SYSCLK_RATE_MASK, clocking4); wm8962_configure_bclk(codec); @@ -3261,16 +3294,31 @@ static int wm8962_set_fll(struct snd_soc_codec *codec, int fll_id, int source, dev_dbg(codec->dev, "FLL configured for %dHz->%dHz\n", Fref, Fout); - /* This should be a massive overestimate */ - timeout = msecs_to_jiffies(1); + ret = 0; + + if (fll1 & WM8962_FLL_ENA) { + /* This should be a massive overestimate but go even + * higher if we'll error out + */ + if (wm8962->irq) + timeout = msecs_to_jiffies(5); + else + timeout = msecs_to_jiffies(1); + + timeout = wait_for_completion_timeout(&wm8962->fll_lock, + timeout); - wait_for_completion_timeout(&wm8962->fll_lock, timeout); + if (timeout == 0 && wm8962->irq) { + dev_err(codec->dev, "FLL lock timed out"); + ret = -ETIMEDOUT; + } + } wm8962->fll_fref = Fref; wm8962->fll_fout = Fout; wm8962->fll_src = source; - return 0; + return ret; } static int wm8962_mute(struct snd_soc_dai *dai, int mute) @@ -3731,8 +3779,6 @@ static int wm8962_probe(struct snd_soc_codec *codec) int ret; struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec); struct wm8962_pdata *pdata = dev_get_platdata(codec->dev); - struct i2c_client *i2c = container_of(codec->dev, struct i2c_client, - dev); u16 *reg_cache = codec->reg_cache; int i, trigger, irq_pol; bool dmicclk, dmicdat; @@ -3871,6 +3917,9 @@ static int wm8962_probe(struct snd_soc_codec *codec) snd_soc_update_bits(codec, WM8962_HPOUTR_VOLUME, WM8962_HPOUT_VU, WM8962_HPOUT_VU); + /* Stereo control for EQ */ + snd_soc_update_bits(codec, WM8962_EQ1, WM8962_EQ_SHARED_COEFF, 0); + wm8962_add_widgets(codec); /* Save boards having to disable DMIC when not in use */ @@ -3899,7 +3948,7 @@ static int wm8962_probe(struct snd_soc_codec *codec) wm8962_init_beep(codec); wm8962_init_gpio(codec); - if (i2c->irq) { + if (wm8962->irq) { if (pdata && pdata->irq_active_low) { trigger = IRQF_TRIGGER_LOW; irq_pol = WM8962_IRQ_POL; @@ -3911,12 +3960,13 @@ static int wm8962_probe(struct snd_soc_codec *codec) snd_soc_update_bits(codec, WM8962_INTERRUPT_CONTROL, WM8962_IRQ_POL, irq_pol); - ret = request_threaded_irq(i2c->irq, NULL, wm8962_irq, + ret = request_threaded_irq(wm8962->irq, NULL, wm8962_irq, trigger | IRQF_ONESHOT, "wm8962", codec); if (ret != 0) { dev_err(codec->dev, "Failed to request IRQ %d: %d\n", - i2c->irq, ret); + wm8962->irq, ret); + wm8962->irq = 0; /* Non-fatal */ } else { /* Enable some IRQs by default */ @@ -3941,12 +3991,10 @@ err: static int wm8962_remove(struct snd_soc_codec *codec) { struct wm8962_priv *wm8962 = snd_soc_codec_get_drvdata(codec); - struct i2c_client *i2c = container_of(codec->dev, struct i2c_client, - dev); int i; - if (i2c->irq) - free_irq(i2c->irq, codec); + if (wm8962->irq) + free_irq(wm8962->irq, codec); cancel_delayed_work_sync(&wm8962->mic_work); @@ -3986,6 +4034,8 @@ static __devinit int wm8962_i2c_probe(struct i2c_client *i2c, i2c_set_clientdata(i2c, wm8962); + wm8962->irq = i2c->irq; + ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_wm8962, &wm8962_dai, 1); if (ret < 0) diff --git a/sound/soc/codecs/wm8993.c b/sound/soc/codecs/wm8993.c index 9e5ff789b80..6e85b8869af 100644 --- a/sound/soc/codecs/wm8993.c +++ b/sound/soc/codecs/wm8993.c @@ -876,7 +876,7 @@ SND_SOC_DAPM_MIXER("SPKL", WM8993_POWER_MANAGEMENT_3, 8, 0, left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)), SND_SOC_DAPM_MIXER("SPKR", WM8993_POWER_MANAGEMENT_3, 9, 0, right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)), - +SND_SOC_DAPM_PGA("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0), }; static const struct snd_soc_dapm_route routes[] = { @@ -1434,6 +1434,7 @@ static int wm8993_probe(struct snd_soc_codec *codec) wm8993->hubs_data.hp_startup_mode = 1; wm8993->hubs_data.dcs_codes = -2; + wm8993->hubs_data.series_startup = 1; ret = snd_soc_codec_set_cache_io(codec, 8, 16, SND_SOC_I2C); if (ret != 0) { diff --git a/sound/soc/codecs/wm8994.c b/sound/soc/codecs/wm8994.c index 5f0c238e178..ee64be2d994 100644 --- a/sound/soc/codecs/wm8994.c +++ b/sound/soc/codecs/wm8994.c @@ -195,10 +195,6 @@ static int configure_aif_clock(struct snd_soc_codec *codec, int aif) aif + 1, rate); } - if (rate && rate < 3000000) - dev_warn(codec->dev, "AIF%dCLK is %dHz, should be >=3MHz for optimal performance\n", - aif + 1, rate); - wm8994->aifclk[aif] = rate; snd_soc_update_bits(codec, WM8994_AIF1_CLOCKING_1 + offset, @@ -1146,13 +1142,33 @@ SND_SOC_DAPM_PGA_E("Late DAC2L Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0, late_enable_ev, SND_SOC_DAPM_PRE_PMU), SND_SOC_DAPM_PGA_E("Late DAC2R Enable PGA", SND_SOC_NOPM, 0, 0, NULL, 0, late_enable_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_PGA_E("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0, + late_enable_ev, SND_SOC_DAPM_PRE_PMU), + +SND_SOC_DAPM_MIXER_E("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0, + left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer), + late_enable_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_MIXER_E("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0, + right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer), + late_enable_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_MUX_E("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux, + late_enable_ev, SND_SOC_DAPM_PRE_PMU), +SND_SOC_DAPM_MUX_E("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux, + late_enable_ev, SND_SOC_DAPM_PRE_PMU), SND_SOC_DAPM_POST("Late Disable PGA", late_disable_ev) }; static const struct snd_soc_dapm_widget wm8994_lateclk_widgets[] = { SND_SOC_DAPM_SUPPLY("AIF1CLK", WM8994_AIF1_CLOCKING_1, 0, 0, NULL, 0), -SND_SOC_DAPM_SUPPLY("AIF2CLK", WM8994_AIF2_CLOCKING_1, 0, 0, NULL, 0) +SND_SOC_DAPM_SUPPLY("AIF2CLK", WM8994_AIF2_CLOCKING_1, 0, 0, NULL, 0), +SND_SOC_DAPM_PGA("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0), +SND_SOC_DAPM_MIXER("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0, + left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)), +SND_SOC_DAPM_MIXER("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0, + right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)), +SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux), +SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux), }; static const struct snd_soc_dapm_widget wm8994_dac_revd_widgets[] = { @@ -1283,14 +1299,6 @@ SND_SOC_DAPM_ADC("DMIC1R", NULL, WM8994_POWER_MANAGEMENT_4, 2, 0), SND_SOC_DAPM_ADC("ADCL", NULL, SND_SOC_NOPM, 1, 0), SND_SOC_DAPM_ADC("ADCR", NULL, SND_SOC_NOPM, 0, 0), -SND_SOC_DAPM_MUX("Left Headphone Mux", SND_SOC_NOPM, 0, 0, &hpl_mux), -SND_SOC_DAPM_MUX("Right Headphone Mux", SND_SOC_NOPM, 0, 0, &hpr_mux), - -SND_SOC_DAPM_MIXER("SPKL", WM8994_POWER_MANAGEMENT_3, 8, 0, - left_speaker_mixer, ARRAY_SIZE(left_speaker_mixer)), -SND_SOC_DAPM_MIXER("SPKR", WM8994_POWER_MANAGEMENT_3, 9, 0, - right_speaker_mixer, ARRAY_SIZE(right_speaker_mixer)), - SND_SOC_DAPM_POST("Debug log", post_ev), }; @@ -1623,6 +1631,7 @@ static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src, int reg_offset, ret; struct fll_div fll; u16 reg, aif1, aif2; + unsigned long timeout; aif1 = snd_soc_read(codec, WM8994_AIF1_CLOCKING_1) & WM8994_AIF1CLK_ENA; @@ -1714,7 +1723,15 @@ static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src, WM8994_FLL1_ENA | WM8994_FLL1_FRAC, reg); - msleep(5); + if (wm8994->fll_locked_irq) { + timeout = wait_for_completion_timeout(&wm8994->fll_locked[id], + msecs_to_jiffies(10)); + if (timeout == 0) + dev_warn(codec->dev, + "Timed out waiting for FLL lock\n"); + } else { + msleep(5); + } } wm8994->fll[id].in = freq_in; @@ -1732,6 +1749,14 @@ static int _wm8994_set_fll(struct snd_soc_codec *codec, int id, int src, return 0; } +static irqreturn_t wm8994_fll_locked_irq(int irq, void *data) +{ + struct completion *completion = data; + + complete(completion); + + return IRQ_HANDLED; +} static int opclk_divs[] = { 10, 20, 30, 40, 55, 60, 80, 120, 160 }; @@ -2849,6 +2874,15 @@ out: return IRQ_HANDLED; } +static irqreturn_t wm8994_fifo_error(int irq, void *data) +{ + struct snd_soc_codec *codec = data; + + dev_err(codec->dev, "FIFO error\n"); + + return IRQ_HANDLED; +} + static int wm8994_codec_probe(struct snd_soc_codec *codec) { struct wm8994 *control; @@ -2867,6 +2901,9 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) wm8994->pdata = dev_get_platdata(codec->dev->parent); wm8994->codec = codec; + for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++) + init_completion(&wm8994->fll_locked[i]); + if (wm8994->pdata && wm8994->pdata->micdet_irq) wm8994->micdet_irq = wm8994->pdata->micdet_irq; else if (wm8994->pdata && wm8994->pdata->irq_base) @@ -2905,6 +2942,7 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) wm8994->hubs.dcs_codes = -5; wm8994->hubs.hp_startup_mode = 1; wm8994->hubs.dcs_readback_mode = 1; + wm8994->hubs.series_startup = 1; break; default: wm8994->hubs.dcs_readback_mode = 1; @@ -2919,6 +2957,15 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) break; } + wm8994_request_irq(codec->control_data, WM8994_IRQ_FIFOS_ERR, + wm8994_fifo_error, "FIFO error", codec); + + ret = wm8994_request_irq(codec->control_data, WM8994_IRQ_DCS_DONE, + wm_hubs_dcs_done, "DC servo done", + &wm8994->hubs); + if (ret == 0) + wm8994->hubs.dcs_done_irq = true; + switch (control->type) { case WM8994: if (wm8994->micdet_irq) { @@ -2975,6 +3022,16 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) } } + wm8994->fll_locked_irq = true; + for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++) { + ret = wm8994_request_irq(codec->control_data, + WM8994_IRQ_FLL1_LOCK + i, + wm8994_fll_locked_irq, "FLL lock", + &wm8994->fll_locked[i]); + if (ret != 0) + wm8994->fll_locked_irq = false; + } + /* Remember if AIFnLRCLK is configured as a GPIO. This should be * configured on init - if a system wants to do this dynamically * at runtime we can deal with that then. @@ -3050,10 +3107,18 @@ static int wm8994_codec_probe(struct snd_soc_codec *codec) 1 << WM8994_AIF2DAC_3D_GAIN_SHIFT, 1 << WM8994_AIF2DAC_3D_GAIN_SHIFT); - /* Unconditionally enable AIF1 ADC TDM mode; it only affects - * behaviour on idle TDM clock cycles. */ - snd_soc_update_bits(codec, WM8994_AIF1_CONTROL_1, - WM8994_AIF1ADC_TDM, WM8994_AIF1ADC_TDM); + /* Unconditionally enable AIF1 ADC TDM mode on chips which can + * use this; it only affects behaviour on idle TDM clock + * cycles. */ + switch (control->type) { + case WM8994: + case WM8958: + snd_soc_update_bits(codec, WM8994_AIF1_CONTROL_1, + WM8994_AIF1ADC_TDM, WM8994_AIF1ADC_TDM); + break; + default: + break; + } wm8994_update_class_w(codec); @@ -3152,6 +3217,12 @@ err_irq: wm8994_free_irq(codec->control_data, WM8994_IRQ_MIC1_SHRT, wm8994); if (wm8994->micdet_irq) free_irq(wm8994->micdet_irq, wm8994); + for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++) + wm8994_free_irq(codec->control_data, WM8994_IRQ_FLL1_LOCK + i, + &wm8994->fll_locked[i]); + wm8994_free_irq(codec->control_data, WM8994_IRQ_DCS_DONE, + &wm8994->hubs); + wm8994_free_irq(codec->control_data, WM8994_IRQ_FIFOS_ERR, codec); err: kfree(wm8994); return ret; @@ -3161,11 +3232,20 @@ static int wm8994_codec_remove(struct snd_soc_codec *codec) { struct wm8994_priv *wm8994 = snd_soc_codec_get_drvdata(codec); struct wm8994 *control = codec->control_data; + int i; wm8994_set_bias_level(codec, SND_SOC_BIAS_OFF); pm_runtime_disable(codec->dev); + for (i = 0; i < ARRAY_SIZE(wm8994->fll_locked); i++) + wm8994_free_irq(codec->control_data, WM8994_IRQ_FLL1_LOCK + i, + &wm8994->fll_locked[i]); + + wm8994_free_irq(codec->control_data, WM8994_IRQ_DCS_DONE, + &wm8994->hubs); + wm8994_free_irq(codec->control_data, WM8994_IRQ_FIFOS_ERR, codec); + switch (control->type) { case WM8994: if (wm8994->micdet_irq) diff --git a/sound/soc/codecs/wm8994.h b/sound/soc/codecs/wm8994.h index 0a1db04b73b..1ab2266039f 100644 --- a/sound/soc/codecs/wm8994.h +++ b/sound/soc/codecs/wm8994.h @@ -11,6 +11,7 @@ #include <sound/soc.h> #include <linux/firmware.h> +#include <linux/completion.h> #include "wm_hubs.h" @@ -79,6 +80,8 @@ struct wm8994_priv { int mclk[2]; int aifclk[2]; struct wm8994_fll_config fll[2], fll_suspend[2]; + struct completion fll_locked[2]; + bool fll_locked_irq; int dac_rates[2]; int lrclk_shared[2]; diff --git a/sound/soc/codecs/wm9081.c b/sound/soc/codecs/wm9081.c index 91c6b39de50..a4691321f9b 100644 --- a/sound/soc/codecs/wm9081.c +++ b/sound/soc/codecs/wm9081.c @@ -727,7 +727,7 @@ SND_SOC_DAPM_MIXER_NAMED_CTL("Mixer", SND_SOC_NOPM, 0, 0, SND_SOC_DAPM_PGA("LINEOUT PGA", WM9081_POWER_MANAGEMENT, 4, 0, NULL, 0), SND_SOC_DAPM_PGA("Speaker PGA", WM9081_POWER_MANAGEMENT, 2, 0, NULL, 0), -SND_SOC_DAPM_PGA("Speaker", WM9081_POWER_MANAGEMENT, 1, 0, NULL, 0), +SND_SOC_DAPM_OUT_DRV("Speaker", WM9081_POWER_MANAGEMENT, 1, 0, NULL, 0), SND_SOC_DAPM_OUTPUT("LINEOUT"), SND_SOC_DAPM_OUTPUT("SPKN"), diff --git a/sound/soc/codecs/wm_hubs.c b/sound/soc/codecs/wm_hubs.c index 9e370d14ad8..5c2d5657b47 100644 --- a/sound/soc/codecs/wm_hubs.c +++ b/sound/soc/codecs/wm_hubs.c @@ -63,9 +63,11 @@ static const struct soc_enum speaker_mode = static void wait_for_dc_servo(struct snd_soc_codec *codec, unsigned int op) { + struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); unsigned int reg; int count = 0; unsigned int val; + unsigned long timeout; val = op | WM8993_DCS_ENA_CHAN_0 | WM8993_DCS_ENA_CHAN_1; @@ -74,18 +76,37 @@ static void wait_for_dc_servo(struct snd_soc_codec *codec, unsigned int op) dev_dbg(codec->dev, "Waiting for DC servo...\n"); - do { - count++; - msleep(1); + if (hubs->dcs_done_irq) { + timeout = wait_for_completion_timeout(&hubs->dcs_done, + msecs_to_jiffies(500)); + if (timeout == 0) + dev_warn(codec->dev, "No DC servo interrupt\n"); + reg = snd_soc_read(codec, WM8993_DC_SERVO_0); - dev_dbg(codec->dev, "DC servo: %x\n", reg); - } while (reg & op && count < 400); + } else { + do { + count++; + msleep(1); + reg = snd_soc_read(codec, WM8993_DC_SERVO_0); + dev_dbg(codec->dev, "DC servo: %x\n", reg); + } while (reg & op && count < 400); + } if (reg & op) dev_err(codec->dev, "Timed out waiting for DC Servo %x\n", op); } +irqreturn_t wm_hubs_dcs_done(int irq, void *data) +{ + struct wm_hubs_data *hubs = data; + + complete(&hubs->dcs_done); + + return IRQ_HANDLED; +} +EXPORT_SYMBOL_GPL(wm_hubs_dcs_done); + /* * Startup calibration of the DC servo */ @@ -107,8 +128,7 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec) return; } - /* Devices not using a DCS code correction have startup mode */ - if (hubs->dcs_codes) { + if (hubs->series_startup) { /* Set for 32 series updates */ snd_soc_update_bits(codec, WM8993_DC_SERVO_1, WM8993_DCS_SERIES_NO_01_MASK, @@ -134,9 +154,9 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec) break; case 1: reg = snd_soc_read(codec, WM8993_DC_SERVO_3); - reg_l = (reg & WM8993_DCS_DAC_WR_VAL_1_MASK) + reg_r = (reg & WM8993_DCS_DAC_WR_VAL_1_MASK) >> WM8993_DCS_DAC_WR_VAL_1_SHIFT; - reg_r = reg & WM8993_DCS_DAC_WR_VAL_0_MASK; + reg_l = reg & WM8993_DCS_DAC_WR_VAL_0_MASK; break; default: WARN(1, "Unknown DCS readback method\n"); @@ -150,13 +170,13 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec) dev_dbg(codec->dev, "Applying %d code DC servo correction\n", hubs->dcs_codes); - /* HPOUT1L */ - offset = reg_l; + /* HPOUT1R */ + offset = reg_r; offset += hubs->dcs_codes; dcs_cfg = (u8)offset << WM8993_DCS_DAC_WR_VAL_1_SHIFT; - /* HPOUT1R */ - offset = reg_r; + /* HPOUT1L */ + offset = reg_l; offset += hubs->dcs_codes; dcs_cfg |= (u8)offset; @@ -168,8 +188,8 @@ static void calibrate_dc_servo(struct snd_soc_codec *codec) WM8993_DCS_TRIG_DAC_WR_0 | WM8993_DCS_TRIG_DAC_WR_1); } else { - dcs_cfg = reg_l << WM8993_DCS_DAC_WR_VAL_1_SHIFT; - dcs_cfg |= reg_r; + dcs_cfg = reg_r << WM8993_DCS_DAC_WR_VAL_1_SHIFT; + dcs_cfg |= reg_l; } /* Save the callibrated offset if we're in class W mode and @@ -195,7 +215,7 @@ static int wm8993_put_dc_servo(struct snd_kcontrol *kcontrol, /* If we're applying an offset correction then updating the * callibration would be likely to introduce further offsets. */ - if (hubs->dcs_codes) + if (hubs->dcs_codes || hubs->no_series_update) return ret; /* Only need to do this if the outputs are active */ @@ -599,9 +619,6 @@ SND_SOC_DAPM_MIXER("IN2L PGA", WM8993_POWER_MANAGEMENT_2, 7, 0, SND_SOC_DAPM_MIXER("IN2R PGA", WM8993_POWER_MANAGEMENT_2, 5, 0, in2r_pga, ARRAY_SIZE(in2r_pga)), -/* Dummy widgets to represent differential paths */ -SND_SOC_DAPM_PGA("Direct Voice", SND_SOC_NOPM, 0, 0, NULL, 0), - SND_SOC_DAPM_MIXER("MIXINL", WM8993_POWER_MANAGEMENT_2, 9, 0, mixinl, ARRAY_SIZE(mixinl)), SND_SOC_DAPM_MIXER("MIXINR", WM8993_POWER_MANAGEMENT_2, 8, 0, @@ -867,8 +884,11 @@ EXPORT_SYMBOL_GPL(wm_hubs_add_analogue_controls); int wm_hubs_add_analogue_routes(struct snd_soc_codec *codec, int lineout1_diff, int lineout2_diff) { + struct wm_hubs_data *hubs = snd_soc_codec_get_drvdata(codec); struct snd_soc_dapm_context *dapm = &codec->dapm; + init_completion(&hubs->dcs_done); + snd_soc_dapm_add_routes(dapm, analogue_routes, ARRAY_SIZE(analogue_routes)); diff --git a/sound/soc/codecs/wm_hubs.h b/sound/soc/codecs/wm_hubs.h index f8a5e976b5e..676b1252ab9 100644 --- a/sound/soc/codecs/wm_hubs.h +++ b/sound/soc/codecs/wm_hubs.h @@ -14,6 +14,9 @@ #ifndef _WM_HUBS_H #define _WM_HUBS_H +#include <linux/completion.h> +#include <linux/interrupt.h> + struct snd_soc_codec; extern const unsigned int wm_hubs_spkmix_tlv[]; @@ -23,9 +26,14 @@ struct wm_hubs_data { int dcs_codes; int dcs_readback_mode; int hp_startup_mode; + int series_startup; + int no_series_update; bool class_w; u16 class_w_dcs; + + bool dcs_done_irq; + struct completion dcs_done; }; extern int wm_hubs_add_analogue_controls(struct snd_soc_codec *); @@ -36,4 +44,6 @@ extern int wm_hubs_handle_analogue_pdata(struct snd_soc_codec *, int jd_scthr, int jd_thr, int micbias1_lvl, int micbias2_lvl); +extern irqreturn_t wm_hubs_dcs_done(int irq, void *data); + #endif |