From 1307394afd2d6cf8f41f5e691b43ffc9e027630c Mon Sep 17 00:00:00 2001 From: Mike Rapoport Date: Tue, 26 Apr 2011 11:52:42 +0300 Subject: [PATCH 01/29] ASoC: tegra: TrimSlice machine support Signed-off-by: Mike Rapoport Acked-by: Stephen Warren Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/tegra/Kconfig | 8 ++ sound/soc/tegra/Makefile | 2 + sound/soc/tegra/trimslice.c | 228 ++++++++++++++++++++++++++++++++++++ 3 files changed, 238 insertions(+) create mode 100644 sound/soc/tegra/trimslice.c diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig index 0f5bd8298a0a..035d39a4beb4 100644 --- a/sound/soc/tegra/Kconfig +++ b/sound/soc/tegra/Kconfig @@ -30,3 +30,11 @@ config SND_SOC_TEGRA_WM8903 boards using the WM8093 codec. Currently, the supported boards are Harmony, Ventana, Seaboard, Kaen, and Aebl. +config SND_SOC_TEGRA_TRIMSLICE + tristate "SoC Audio support for TrimSlice board" + depends on SND_SOC_TEGRA && MACH_TRIMSLICE && I2C + select SND_SOC_TEGRA_I2S + select SND_SOC_TLV320AIC23 + help + Say Y or M here if you want to add support for SoC audio on the + TrimSlice platform. diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile index 13bef8d572c9..fa6574d92a31 100644 --- a/sound/soc/tegra/Makefile +++ b/sound/soc/tegra/Makefile @@ -11,5 +11,7 @@ obj-$(CONFIG_SND_SOC_TEGRA_I2S) += snd-soc-tegra-i2s.o # Tegra machine Support snd-soc-tegra-wm8903-objs := tegra_wm8903.o +snd-soc-tegra-trimslice-objs := trimslice.o obj-$(CONFIG_SND_SOC_TEGRA_WM8903) += snd-soc-tegra-wm8903.o +obj-$(CONFIG_SND_SOC_TEGRA_TRIMSLICE) += snd-soc-tegra-trimslice.o diff --git a/sound/soc/tegra/trimslice.c b/sound/soc/tegra/trimslice.c new file mode 100644 index 000000000000..8fc07e9adf2e --- /dev/null +++ b/sound/soc/tegra/trimslice.c @@ -0,0 +1,228 @@ +/* + * trimslice.c - TrimSlice machine ASoC driver + * + * Copyright (C) 2011 - CompuLab, Ltd. + * Author: Mike Rapoport + * + * Based on code copyright/by: + * Author: Stephen Warren + * Copyright (C) 2010-2011 - NVIDIA, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../codecs/tlv320aic23.h" + +#include "tegra_das.h" +#include "tegra_i2s.h" +#include "tegra_pcm.h" +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-trimslice" + +struct tegra_trimslice { + struct tegra_asoc_utils_data util_data; +}; + +static int trimslice_asoc_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = substream->private_data; + struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct snd_soc_dai *cpu_dai = rtd->cpu_dai; + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_card *card = codec->card; + struct tegra_trimslice *trimslice = snd_soc_card_get_drvdata(card); + int srate, mclk; + int err; + + srate = params_rate(params); + mclk = 128 * srate; + + err = tegra_asoc_utils_set_rate(&trimslice->util_data, srate, mclk); + if (err < 0) { + dev_err(card->dev, "Can't configure clocks\n"); + return err; + } + + err = snd_soc_dai_set_fmt(codec_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (err < 0) { + dev_err(card->dev, "codec_dai fmt not set\n"); + return err; + } + + err = snd_soc_dai_set_fmt(cpu_dai, + SND_SOC_DAIFMT_I2S | + SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS); + if (err < 0) { + dev_err(card->dev, "cpu_dai fmt not set\n"); + return err; + } + + err = snd_soc_dai_set_sysclk(codec_dai, 0, mclk, + SND_SOC_CLOCK_IN); + if (err < 0) { + dev_err(card->dev, "codec_dai clock not set\n"); + return err; + } + + return 0; +} + +static struct snd_soc_ops trimslice_asoc_ops = { + .hw_params = trimslice_asoc_hw_params, +}; + +static const struct snd_soc_dapm_widget trimslice_dapm_widgets[] = { + SND_SOC_DAPM_HP("Line Out", NULL), + SND_SOC_DAPM_LINE("Line In", NULL), +}; + +static const struct snd_soc_dapm_route trimslice_audio_map[] = { + {"Line Out", NULL, "LOUT"}, + {"Line Out", NULL, "ROUT"}, + + {"LLINEIN", NULL, "Line In"}, + {"RLINEIN", NULL, "Line In"}, +}; + +static int trimslice_asoc_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_codec *codec = rtd->codec; + struct snd_soc_dapm_context *dapm = &codec->dapm; + + snd_soc_dapm_nc_pin(dapm, "LHPOUT"); + snd_soc_dapm_nc_pin(dapm, "RHPOUT"); + snd_soc_dapm_nc_pin(dapm, "MICIN"); + + snd_soc_dapm_sync(dapm); + + return 0; +} + +static struct snd_soc_dai_link trimslice_tlv320aic23_dai = { + .name = "TLV320AIC23", + .stream_name = "AIC23", + .codec_name = "tlv320aic23-codec.2-001a", + .platform_name = "tegra-pcm-audio", + .cpu_dai_name = "tegra-i2s.0", + .codec_dai_name = "tlv320aic23-hifi", + .init = trimslice_asoc_init, + .ops = &trimslice_asoc_ops, +}; + +static struct snd_soc_card snd_soc_trimslice = { + .name = "tegra-trimslice", + .dai_link = &trimslice_tlv320aic23_dai, + .num_links = 1, + + .dapm_widgets = trimslice_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(trimslice_dapm_widgets), + .dapm_routes = trimslice_audio_map, + .num_dapm_routes = ARRAY_SIZE(trimslice_audio_map), +}; + +static __devinit int tegra_snd_trimslice_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card = &snd_soc_trimslice; + struct tegra_trimslice *trimslice; + int ret; + + trimslice = kzalloc(sizeof(struct tegra_trimslice), GFP_KERNEL); + if (!trimslice) { + dev_err(&pdev->dev, "Can't allocate tegra_trimslice\n"); + return -ENOMEM; + } + + ret = tegra_asoc_utils_init(&trimslice->util_data, &pdev->dev); + if (ret) + goto err_free_trimslice; + + card->dev = &pdev->dev; + platform_set_drvdata(pdev, card); + snd_soc_card_set_drvdata(card, trimslice); + + ret = snd_soc_register_card(card); + if (ret) { + dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", + ret); + goto err_fini_utils; + } + + return 0; + +err_fini_utils: + tegra_asoc_utils_fini(&trimslice->util_data); +err_free_trimslice: + kfree(trimslice); + return ret; +} + +static int __devexit tegra_snd_trimslice_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct tegra_trimslice *trimslice = snd_soc_card_get_drvdata(card); + + snd_soc_unregister_card(card); + + tegra_asoc_utils_fini(&trimslice->util_data); + + kfree(trimslice); + + return 0; +} + +static struct platform_driver tegra_snd_trimslice_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + .probe = tegra_snd_trimslice_probe, + .remove = __devexit_p(tegra_snd_trimslice_remove), +}; + +static int __init snd_tegra_trimslice_init(void) +{ + return platform_driver_register(&tegra_snd_trimslice_driver); +} +module_init(snd_tegra_trimslice_init); + +static void __exit snd_tegra_trimslice_exit(void) +{ + platform_driver_unregister(&tegra_snd_trimslice_driver); +} +module_exit(snd_tegra_trimslice_exit); + +MODULE_AUTHOR("Mike Rapoport "); +MODULE_DESCRIPTION("Trimslice machine ASoC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); From 0aaae527c71e6af571093d90474f37c8662008d5 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 30 Apr 2011 19:45:47 +0200 Subject: [PATCH 02/29] ASoC: Free the card's DAPM context Free the card's DAPM context when the card is removed. Signed-off-by: Lars-Peter Clausen Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index a823654ef367..6a3cb53185cb 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -2047,6 +2047,8 @@ static int soc_cleanup_card_resources(struct snd_soc_card *card) if (card->remove) card->remove(card); + snd_soc_dapm_free(&card->dapm); + kfree(card->rtd); snd_card_free(card->snd_card); return 0; From 8eecaf62445e175572ffabaab090b471001c5a2c Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 30 Apr 2011 19:45:48 +0200 Subject: [PATCH 03/29] ASoC: Move DAPM debugfs directory creation to snd_soc_dapm_debugfs_init Move the creation of the DAPM debugfs directory to snd_soc_dapm_debugfs_init instead of having the same duplicated code in both codec and card DAPM setup. Signed-off-by: Lars-Peter Clausen Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- include/sound/soc-dapm.h | 3 ++- sound/soc/soc-core.c | 16 ++-------------- sound/soc/soc-dapm.c | 13 ++++++++++--- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index d5f1b9a9b8ff..f8a7c9a6f12a 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -356,7 +356,8 @@ void snd_soc_dapm_shutdown(struct snd_soc_card *card); /* dapm sys fs - used by the core */ int snd_soc_dapm_sys_add(struct device *dev); -void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm); +void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm, + struct dentry *parent); /* dapm audio pin control and status */ int snd_soc_dapm_enable_pin(struct snd_soc_dapm_context *dapm, diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 6a3cb53185cb..983ec640d4d7 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -302,13 +302,7 @@ static void soc_init_codec_debugfs(struct snd_soc_codec *codec) printk(KERN_WARNING "ASoC: Failed to create codec register debugfs file\n"); - codec->dapm.debugfs_dapm = debugfs_create_dir("dapm", - codec->debugfs_codec_root); - if (!codec->dapm.debugfs_dapm) - printk(KERN_WARNING - "Failed to create DAPM debugfs directory\n"); - - snd_soc_dapm_debugfs_init(&codec->dapm); + snd_soc_dapm_debugfs_init(&codec->dapm, codec->debugfs_codec_root); } static void soc_cleanup_codec_debugfs(struct snd_soc_codec *codec) @@ -1926,13 +1920,7 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) card->num_dapm_routes); #ifdef CONFIG_DEBUG_FS - card->dapm.debugfs_dapm = debugfs_create_dir("dapm", - card->debugfs_card_root); - if (!card->dapm.debugfs_dapm) - printk(KERN_WARNING - "Failed to create card DAPM debugfs directory\n"); - - snd_soc_dapm_debugfs_init(&card->dapm); + snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root); #endif snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname), diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 378f08adee56..ffed456b2142 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -1254,13 +1254,19 @@ static const struct file_operations dapm_bias_fops = { .llseek = default_llseek, }; -void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm) +void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm, + struct dentry *parent) { struct snd_soc_dapm_widget *w; struct dentry *d; - if (!dapm->debugfs_dapm) + dapm->debugfs_dapm = debugfs_create_dir("dapm", parent); + + if (!dapm->debugfs_dapm) { + printk(KERN_WARNING + "Failed to create DAPM debugfs directory\n"); return; + } d = debugfs_create_file("bias_level", 0444, dapm->debugfs_dapm, dapm, @@ -1283,7 +1289,8 @@ void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm) } } #else -void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm) +void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm, + struct dentry *parent) { } #endif From d5d1e0bef4385a0cd726613c0fc1909cd23efd39 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 30 Apr 2011 19:45:49 +0200 Subject: [PATCH 04/29] ASoC: Move DAPM widget debugfs entry creation to snd_soc_dapm_new_widgets Currently debugfs entries for a DAPM widgets are only added in snd_soc_dapm_debugfs_init. If a widget is added later (for example in the dai_link's probe callback) it will not show up in debugfs. This patch moves the creation of the widget debugfs entry to snd_soc_dapm_new_widgets where it will be added after the widget has been properly instantiated. As a side-effect this will also reduce the number of times the DAPM widget list is iterated during a card's instantiation. Since it is possible that snd_soc_dapm_new_widgets is invoked form the codecs or cards probe callbacks, the creation of the debugfs dapm directory has to be moved before these are called. Signed-off-by: Lars-Peter Clausen Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 13 +++++++------ sound/soc/soc-dapm.c | 39 +++++++++++++++++++++++++-------------- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 983ec640d4d7..fbd011ebe19f 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1493,6 +1493,8 @@ static int soc_probe_codec(struct snd_soc_card *card, if (!try_module_get(codec->dev->driver->owner)) return -ENODEV; + soc_init_codec_debugfs(codec); + if (driver->probe) { ret = driver->probe(codec); if (ret < 0) { @@ -1513,8 +1515,6 @@ static int soc_probe_codec(struct snd_soc_card *card, snd_soc_dapm_add_routes(&codec->dapm, driver->dapm_routes, driver->num_dapm_routes); - soc_init_codec_debugfs(codec); - /* mark codec as probed and add to card codec list */ codec->probed = 1; list_add(&codec->card_list, &card->codec_dev_list); @@ -1523,6 +1523,7 @@ static int soc_probe_codec(struct snd_soc_card *card, return 0; err_probe: + soc_cleanup_codec_debugfs(codec); module_put(codec->dev->driver->owner); return ret; @@ -1873,6 +1874,10 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) card->dapm.card = card; list_add(&card->dapm.list, &card->dapm_list); +#ifdef CONFIG_DEBUG_FS + snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root); +#endif + #ifdef CONFIG_PM_SLEEP /* deferred resume work */ INIT_WORK(&card->deferred_resume_work, soc_resume_deferred); @@ -1919,10 +1924,6 @@ static void snd_soc_instantiate_card(struct snd_soc_card *card) snd_soc_dapm_add_routes(&card->dapm, card->dapm_routes, card->num_dapm_routes); -#ifdef CONFIG_DEBUG_FS - snd_soc_dapm_debugfs_init(&card->dapm, card->debugfs_card_root); -#endif - snprintf(card->snd_card->shortname, sizeof(card->snd_card->shortname), "%s", card->name); snprintf(card->snd_card->longname, sizeof(card->snd_card->longname), diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index ffed456b2142..8454cc2967d6 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -1257,7 +1257,6 @@ static const struct file_operations dapm_bias_fops = { void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm, struct dentry *parent) { - struct snd_soc_dapm_widget *w; struct dentry *d; dapm->debugfs_dapm = debugfs_create_dir("dapm", parent); @@ -1274,25 +1273,35 @@ void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm, if (!d) dev_warn(dapm->dev, "ASoC: Failed to create bias level debugfs file\n"); - - list_for_each_entry(w, &dapm->card->widgets, list) { - if (!w->name || w->dapm != dapm) - continue; - - d = debugfs_create_file(w->name, 0444, - dapm->debugfs_dapm, w, - &dapm_widget_power_fops); - if (!d) - dev_warn(w->dapm->dev, - "ASoC: Failed to create %s debugfs file\n", - w->name); - } } + +static void dapm_debugfs_add_widget(struct snd_soc_dapm_widget *w) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct dentry *d; + + if (!dapm->debugfs_dapm || !w->name) + return; + + d = debugfs_create_file(w->name, 0444, + dapm->debugfs_dapm, w, + &dapm_widget_power_fops); + if (!d) + dev_warn(w->dapm->dev, + "ASoC: Failed to create %s debugfs file\n", + w->name); +} + #else void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm, struct dentry *parent) { } + +static inline void dapm_debugfs_add_widget(struct snd_soc_dapm_widget *w) +{ +} + #endif /* test and update the power status of a mux widget */ @@ -1765,6 +1774,8 @@ int snd_soc_dapm_new_widgets(struct snd_soc_dapm_context *dapm) } w->new = 1; + + dapm_debugfs_add_widget(w); } dapm_power_widgets(dapm, SND_SOC_DAPM_STREAM_NOP); From 6c45e126567eb9f96519ca97917ce317fcbe5218 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 30 Apr 2011 19:45:50 +0200 Subject: [PATCH 05/29] ASoC: Remove DAPM debugfs entries before freeing widgets Remove the DAPM debugfs entries before freeing the context's widgets, otherwise a use after free situation might occur. Signed-off-by: Lars-Peter Clausen Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 8454cc2967d6..169e1767ebd0 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -1292,6 +1292,11 @@ static void dapm_debugfs_add_widget(struct snd_soc_dapm_widget *w) w->name); } +static void dapm_debugfs_cleanup(struct snd_soc_dapm_context *dapm) +{ + debugfs_remove_recursive(dapm->debugfs_dapm); +} + #else void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm, struct dentry *parent) @@ -1302,6 +1307,10 @@ static inline void dapm_debugfs_add_widget(struct snd_soc_dapm_widget *w) { } +static inline void dapm_debugfs_cleanup(struct snd_soc_dapm_context *dapm) +{ +} + #endif /* test and update the power status of a mux widget */ @@ -2445,6 +2454,7 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_ignore_suspend); void snd_soc_dapm_free(struct snd_soc_dapm_context *dapm) { snd_soc_dapm_sys_remove(dapm->dev); + dapm_debugfs_cleanup(dapm); dapm_free_widgets(dapm); list_del(&dapm->list); } From 005967a1df80980acb47c72d758ec05059105492 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sat, 30 Apr 2011 22:28:20 +0200 Subject: [PATCH 06/29] ASoC: JZ4740: Fix i2s shutdown The i2s shutdown callback has the check whether it should be disabled reversed. Currently it is disabled if another stream is still active, but kept enabled if the last stream is closed. This patch fixes it. Signed-off-by: Lars-Peter Clausen Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/jz4740/jz4740-i2s.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/jz4740/jz4740-i2s.c b/sound/soc/jz4740/jz4740-i2s.c index 419bf4f5534a..cd22a54b2f14 100644 --- a/sound/soc/jz4740/jz4740-i2s.c +++ b/sound/soc/jz4740/jz4740-i2s.c @@ -133,7 +133,7 @@ static void jz4740_i2s_shutdown(struct snd_pcm_substream *substream, struct jz4740_i2s *i2s = snd_soc_dai_get_drvdata(dai); uint32_t conf; - if (!dai->active) + if (dai->active) return; conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF); From 82cfecdc03499be63262d60daf859b4cc1ea3fba Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Thu, 28 Apr 2011 17:37:58 -0600 Subject: [PATCH 07/29] ASoC: s/w->kcontrols/w->kcontrol_news/g A future change will modify struct snd_soc_dapm_widget to store the actual kcontrol pointers for each kcontrol_new in a field named kcontrols. Rename the existing kcontrols field to enable this. Signed-off-by: Stephen Warren Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- include/sound/soc-dapm.h | 68 +++++++++++++++---------------- sound/soc/codecs/88pm860x-codec.c | 2 +- sound/soc/soc-dapm.c | 28 +++++++------ 3 files changed, 50 insertions(+), 48 deletions(-) diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index f8a7c9a6f12a..76714be19e9d 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -39,30 +39,30 @@ /* codec domain */ #define SND_SOC_DAPM_VMID(wname) \ -{ .id = snd_soc_dapm_vmid, .name = wname, .kcontrols = NULL, \ +{ .id = snd_soc_dapm_vmid, .name = wname, .kcontrol_news = NULL, \ .num_kcontrols = 0} /* platform domain */ #define SND_SOC_DAPM_INPUT(wname) \ -{ .id = snd_soc_dapm_input, .name = wname, .kcontrols = NULL, \ +{ .id = snd_soc_dapm_input, .name = wname, .kcontrol_news = NULL, \ .num_kcontrols = 0, .reg = SND_SOC_NOPM } #define SND_SOC_DAPM_OUTPUT(wname) \ -{ .id = snd_soc_dapm_output, .name = wname, .kcontrols = NULL, \ +{ .id = snd_soc_dapm_output, .name = wname, .kcontrol_news = NULL, \ .num_kcontrols = 0, .reg = SND_SOC_NOPM } #define SND_SOC_DAPM_MIC(wname, wevent) \ -{ .id = snd_soc_dapm_mic, .name = wname, .kcontrols = NULL, \ +{ .id = snd_soc_dapm_mic, .name = wname, .kcontrol_news = NULL, \ .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \ .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD} #define SND_SOC_DAPM_HP(wname, wevent) \ -{ .id = snd_soc_dapm_hp, .name = wname, .kcontrols = NULL, \ +{ .id = snd_soc_dapm_hp, .name = wname, .kcontrol_news = NULL, \ .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \ .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD} #define SND_SOC_DAPM_SPK(wname, wevent) \ -{ .id = snd_soc_dapm_spk, .name = wname, .kcontrols = NULL, \ +{ .id = snd_soc_dapm_spk, .name = wname, .kcontrol_news = NULL, \ .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \ .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD} #define SND_SOC_DAPM_LINE(wname, wevent) \ -{ .id = snd_soc_dapm_line, .name = wname, .kcontrols = NULL, \ +{ .id = snd_soc_dapm_line, .name = wname, .kcontrol_news = NULL, \ .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \ .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD} @@ -70,91 +70,91 @@ #define SND_SOC_DAPM_PGA(wname, wreg, wshift, winvert,\ wcontrols, wncontrols) \ { .id = snd_soc_dapm_pga, .name = wname, .reg = wreg, .shift = wshift, \ - .invert = winvert, .kcontrols = wcontrols, .num_kcontrols = wncontrols} + .invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = wncontrols} #define SND_SOC_DAPM_OUT_DRV(wname, wreg, wshift, winvert,\ wcontrols, wncontrols) \ { .id = snd_soc_dapm_out_drv, .name = wname, .reg = wreg, .shift = wshift, \ - .invert = winvert, .kcontrols = wcontrols, .num_kcontrols = wncontrols} + .invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = wncontrols} #define SND_SOC_DAPM_MIXER(wname, wreg, wshift, winvert, \ wcontrols, wncontrols)\ { .id = snd_soc_dapm_mixer, .name = wname, .reg = wreg, .shift = wshift, \ - .invert = winvert, .kcontrols = wcontrols, .num_kcontrols = wncontrols} + .invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = wncontrols} #define SND_SOC_DAPM_MIXER_NAMED_CTL(wname, wreg, wshift, winvert, \ wcontrols, wncontrols)\ { .id = snd_soc_dapm_mixer_named_ctl, .name = wname, .reg = wreg, \ - .shift = wshift, .invert = winvert, .kcontrols = wcontrols, \ + .shift = wshift, .invert = winvert, .kcontrol_news = wcontrols, \ .num_kcontrols = wncontrols} #define SND_SOC_DAPM_MICBIAS(wname, wreg, wshift, winvert) \ { .id = snd_soc_dapm_micbias, .name = wname, .reg = wreg, .shift = wshift, \ - .invert = winvert, .kcontrols = NULL, .num_kcontrols = 0} + .invert = winvert, .kcontrol_news = NULL, .num_kcontrols = 0} #define SND_SOC_DAPM_SWITCH(wname, wreg, wshift, winvert, wcontrols) \ { .id = snd_soc_dapm_switch, .name = wname, .reg = wreg, .shift = wshift, \ - .invert = winvert, .kcontrols = wcontrols, .num_kcontrols = 1} + .invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = 1} #define SND_SOC_DAPM_MUX(wname, wreg, wshift, winvert, wcontrols) \ { .id = snd_soc_dapm_mux, .name = wname, .reg = wreg, .shift = wshift, \ - .invert = winvert, .kcontrols = wcontrols, .num_kcontrols = 1} + .invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = 1} #define SND_SOC_DAPM_VIRT_MUX(wname, wreg, wshift, winvert, wcontrols) \ { .id = snd_soc_dapm_virt_mux, .name = wname, .reg = wreg, .shift = wshift, \ - .invert = winvert, .kcontrols = wcontrols, .num_kcontrols = 1} + .invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = 1} #define SND_SOC_DAPM_VALUE_MUX(wname, wreg, wshift, winvert, wcontrols) \ { .id = snd_soc_dapm_value_mux, .name = wname, .reg = wreg, \ - .shift = wshift, .invert = winvert, .kcontrols = wcontrols, \ + .shift = wshift, .invert = winvert, .kcontrol_news = wcontrols, \ .num_kcontrols = 1} /* Simplified versions of above macros, assuming wncontrols = ARRAY_SIZE(wcontrols) */ #define SOC_PGA_ARRAY(wname, wreg, wshift, winvert,\ wcontrols) \ { .id = snd_soc_dapm_pga, .name = wname, .reg = wreg, .shift = wshift, \ - .invert = winvert, .kcontrols = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols)} + .invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols)} #define SOC_MIXER_ARRAY(wname, wreg, wshift, winvert, \ wcontrols)\ { .id = snd_soc_dapm_mixer, .name = wname, .reg = wreg, .shift = wshift, \ - .invert = winvert, .kcontrols = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols)} + .invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols)} #define SOC_MIXER_NAMED_CTL_ARRAY(wname, wreg, wshift, winvert, \ wcontrols)\ { .id = snd_soc_dapm_mixer_named_ctl, .name = wname, .reg = wreg, \ - .shift = wshift, .invert = winvert, .kcontrols = wcontrols, \ + .shift = wshift, .invert = winvert, .kcontrol_news = wcontrols, \ .num_kcontrols = ARRAY_SIZE(wcontrols)} /* path domain with event - event handler must return 0 for success */ #define SND_SOC_DAPM_PGA_E(wname, wreg, wshift, winvert, wcontrols, \ wncontrols, wevent, wflags) \ { .id = snd_soc_dapm_pga, .name = wname, .reg = wreg, .shift = wshift, \ - .invert = winvert, .kcontrols = wcontrols, .num_kcontrols = wncontrols, \ + .invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = wncontrols, \ .event = wevent, .event_flags = wflags} #define SND_SOC_DAPM_OUT_DRV_E(wname, wreg, wshift, winvert, wcontrols, \ wncontrols, wevent, wflags) \ { .id = snd_soc_dapm_out_drv, .name = wname, .reg = wreg, .shift = wshift, \ - .invert = winvert, .kcontrols = wcontrols, .num_kcontrols = wncontrols, \ + .invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = wncontrols, \ .event = wevent, .event_flags = wflags} #define SND_SOC_DAPM_MIXER_E(wname, wreg, wshift, winvert, wcontrols, \ wncontrols, wevent, wflags) \ { .id = snd_soc_dapm_mixer, .name = wname, .reg = wreg, .shift = wshift, \ - .invert = winvert, .kcontrols = wcontrols, .num_kcontrols = wncontrols, \ + .invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = wncontrols, \ .event = wevent, .event_flags = wflags} #define SND_SOC_DAPM_MIXER_NAMED_CTL_E(wname, wreg, wshift, winvert, \ wcontrols, wncontrols, wevent, wflags) \ { .id = snd_soc_dapm_mixer, .name = wname, .reg = wreg, .shift = wshift, \ - .invert = winvert, .kcontrols = wcontrols, \ + .invert = winvert, .kcontrol_news = wcontrols, \ .num_kcontrols = wncontrols, .event = wevent, .event_flags = wflags} #define SND_SOC_DAPM_MICBIAS_E(wname, wreg, wshift, winvert, wevent, wflags) \ { .id = snd_soc_dapm_micbias, .name = wname, .reg = wreg, .shift = wshift, \ - .invert = winvert, .kcontrols = NULL, .num_kcontrols = 0, \ + .invert = winvert, .kcontrol_news = NULL, .num_kcontrols = 0, \ .event = wevent, .event_flags = wflags} #define SND_SOC_DAPM_SWITCH_E(wname, wreg, wshift, winvert, wcontrols, \ wevent, wflags) \ { .id = snd_soc_dapm_switch, .name = wname, .reg = wreg, .shift = wshift, \ - .invert = winvert, .kcontrols = wcontrols, .num_kcontrols = 1, \ + .invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = 1, \ .event = wevent, .event_flags = wflags} #define SND_SOC_DAPM_MUX_E(wname, wreg, wshift, winvert, wcontrols, \ wevent, wflags) \ { .id = snd_soc_dapm_mux, .name = wname, .reg = wreg, .shift = wshift, \ - .invert = winvert, .kcontrols = wcontrols, .num_kcontrols = 1, \ + .invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = 1, \ .event = wevent, .event_flags = wflags} #define SND_SOC_DAPM_VIRT_MUX_E(wname, wreg, wshift, winvert, wcontrols, \ wevent, wflags) \ { .id = snd_soc_dapm_virt_mux, .name = wname, .reg = wreg, .shift = wshift, \ - .invert = winvert, .kcontrols = wcontrols, .num_kcontrols = 1, \ + .invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = 1, \ .event = wevent, .event_flags = wflags} /* additional sequencing control within an event type */ @@ -173,26 +173,26 @@ #define SOC_PGA_E_ARRAY(wname, wreg, wshift, winvert, wcontrols, \ wevent, wflags) \ { .id = snd_soc_dapm_pga, .name = wname, .reg = wreg, .shift = wshift, \ - .invert = winvert, .kcontrols = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols), \ + .invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols), \ .event = wevent, .event_flags = wflags} #define SOC_MIXER_E_ARRAY(wname, wreg, wshift, winvert, wcontrols, \ wevent, wflags) \ { .id = snd_soc_dapm_mixer, .name = wname, .reg = wreg, .shift = wshift, \ - .invert = winvert, .kcontrols = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols), \ + .invert = winvert, .kcontrol_news = wcontrols, .num_kcontrols = ARRAY_SIZE(wcontrols), \ .event = wevent, .event_flags = wflags} #define SOC_MIXER_NAMED_CTL_E_ARRAY(wname, wreg, wshift, winvert, \ wcontrols, wevent, wflags) \ { .id = snd_soc_dapm_mixer, .name = wname, .reg = wreg, .shift = wshift, \ - .invert = winvert, .kcontrols = wcontrols, \ + .invert = winvert, .kcontrol_news = wcontrols, \ .num_kcontrols = ARRAY_SIZE(wcontrols), .event = wevent, .event_flags = wflags} /* events that are pre and post DAPM */ #define SND_SOC_DAPM_PRE(wname, wevent) \ -{ .id = snd_soc_dapm_pre, .name = wname, .kcontrols = NULL, \ +{ .id = snd_soc_dapm_pre, .name = wname, .kcontrol_news = NULL, \ .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \ .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_PRE_PMD} #define SND_SOC_DAPM_POST(wname, wevent) \ -{ .id = snd_soc_dapm_post, .name = wname, .kcontrols = NULL, \ +{ .id = snd_soc_dapm_post, .name = wname, .kcontrol_news = NULL, \ .num_kcontrols = 0, .reg = SND_SOC_NOPM, .event = wevent, \ .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD} @@ -232,7 +232,7 @@ /* generic widgets */ #define SND_SOC_DAPM_REG(wid, wname, wreg, wshift, wmask, won_val, woff_val) \ -{ .id = wid, .name = wname, .kcontrols = NULL, .num_kcontrols = 0, \ +{ .id = wid, .name = wname, .kcontrol_news = NULL, .num_kcontrols = 0, \ .reg = -((wreg) + 1), .shift = wshift, .mask = wmask, \ .on_val = won_val, .off_val = woff_val, .event = dapm_reg_event, \ .event_flags = SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD} @@ -473,7 +473,7 @@ struct snd_soc_dapm_widget { /* kcontrols that relate to this widget */ int num_kcontrols; - const struct snd_kcontrol_new *kcontrols; + const struct snd_kcontrol_new *kcontrol_news; /* widget input and outputs */ struct list_head sources; diff --git a/sound/soc/codecs/88pm860x-codec.c b/sound/soc/codecs/88pm860x-codec.c index 06b6981b8d6d..19241576b6b5 100644 --- a/sound/soc/codecs/88pm860x-codec.c +++ b/sound/soc/codecs/88pm860x-codec.c @@ -120,7 +120,7 @@ */ #define PM860X_DAPM_OUTPUT(wname, wevent) \ { .id = snd_soc_dapm_pga, .name = wname, .reg = SND_SOC_NOPM, \ - .shift = 0, .invert = 0, .kcontrols = NULL, \ + .shift = 0, .invert = 0, .kcontrol_news = NULL, \ .num_kcontrols = 0, .event = wevent, \ .event_flags = SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD, } diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 169e1767ebd0..35cc1ed00a44 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -187,7 +187,7 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, case snd_soc_dapm_mixer_named_ctl: { int val; struct soc_mixer_control *mc = (struct soc_mixer_control *) - w->kcontrols[i].private_value; + w->kcontrol_news[i].private_value; unsigned int reg = mc->reg; unsigned int shift = mc->shift; int max = mc->max; @@ -204,7 +204,8 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, } break; case snd_soc_dapm_mux: { - struct soc_enum *e = (struct soc_enum *)w->kcontrols[i].private_value; + struct soc_enum *e = (struct soc_enum *) + w->kcontrol_news[i].private_value; int val, item, bitmask; for (bitmask = 1; bitmask < e->max; bitmask <<= 1) @@ -220,7 +221,8 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, } break; case snd_soc_dapm_virt_mux: { - struct soc_enum *e = (struct soc_enum *)w->kcontrols[i].private_value; + struct soc_enum *e = (struct soc_enum *) + w->kcontrol_news[i].private_value; p->connect = 0; /* since a virtual mux has no backing registers to @@ -235,7 +237,7 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, break; case snd_soc_dapm_value_mux: { struct soc_enum *e = (struct soc_enum *) - w->kcontrols[i].private_value; + w->kcontrol_news[i].private_value; int val, item; val = snd_soc_read(w->codec, e->reg); @@ -310,11 +312,11 @@ static int dapm_connect_mixer(struct snd_soc_dapm_context *dapm, /* search for mixer kcontrol */ for (i = 0; i < dest->num_kcontrols; i++) { - if (!strcmp(control_name, dest->kcontrols[i].name)) { + if (!strcmp(control_name, dest->kcontrol_news[i].name)) { list_add(&path->list, &dapm->card->paths); list_add(&path->list_sink, &dest->sources); list_add(&path->list_source, &src->sinks); - path->name = dest->kcontrols[i].name; + path->name = dest->kcontrol_news[i].name; dapm_set_path_status(dest, path, i); return 0; } @@ -349,7 +351,7 @@ static int dapm_new_mixer(struct snd_soc_dapm_context *dapm, list_for_each_entry(path, &w->sources, list_sink) { /* mixer/mux paths name must match control name */ - if (path->name != (char*)w->kcontrols[i].name) + if (path->name != (char *)w->kcontrol_news[i].name) continue; /* add dapm control with long name. @@ -358,7 +360,7 @@ static int dapm_new_mixer(struct snd_soc_dapm_context *dapm, * for dapm_mixer_named_ctl this is simply the * kcontrol name. */ - name_len = strlen(w->kcontrols[i].name) + 1; + name_len = strlen(w->kcontrol_news[i].name) + 1; if (w->id != snd_soc_dapm_mixer_named_ctl) name_len += 1 + strlen(w->name); @@ -377,17 +379,17 @@ static int dapm_new_mixer(struct snd_soc_dapm_context *dapm, */ snprintf(path->long_name, name_len, "%s %s", w->name + prefix_len, - w->kcontrols[i].name); + w->kcontrol_news[i].name); break; case snd_soc_dapm_mixer_named_ctl: snprintf(path->long_name, name_len, "%s", - w->kcontrols[i].name); + w->kcontrol_news[i].name); break; } path->long_name[name_len - 1] = '\0'; - path->kcontrol = snd_soc_cnew(&w->kcontrols[i], w, + path->kcontrol = snd_soc_cnew(&w->kcontrol_news[i], w, path->long_name, prefix); ret = snd_ctl_add(card, path->kcontrol); if (ret < 0) { @@ -433,7 +435,7 @@ static int dapm_new_mux(struct snd_soc_dapm_context *dapm, * process but we're also using the same prefix for widgets so * cut the prefix off the front of the widget name. */ - kcontrol = snd_soc_cnew(&w->kcontrols[0], w, w->name + prefix_len, + kcontrol = snd_soc_cnew(&w->kcontrol_news[0], w, w->name + prefix_len, prefix); ret = snd_ctl_add(card, kcontrol); @@ -1648,7 +1650,7 @@ static int snd_soc_dapm_add_route(struct snd_soc_dapm_context *dapm, case snd_soc_dapm_virt_mux: case snd_soc_dapm_value_mux: ret = dapm_connect_mux(dapm, wsource, wsink, path, control, - &wsink->kcontrols[0]); + &wsink->kcontrol_news[0]); if (ret != 0) goto err; break; From fad598887dc0d89ffee3e51281a8143beb2ae58c Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Thu, 28 Apr 2011 17:37:59 -0600 Subject: [PATCH 08/29] ASoC: Add w->kcontrols, and populate it Future changes will need reference to the kcontrol created for a given kcontrol_new. Store the created kcontrol values now. Signed-off-by: Stephen Warren Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- include/sound/soc-dapm.h | 1 + sound/soc/soc-dapm.c | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index 76714be19e9d..b25bf0ffc947 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -474,6 +474,7 @@ struct snd_soc_dapm_widget { /* kcontrols that relate to this widget */ int num_kcontrols; const struct snd_kcontrol_new *kcontrol_news; + struct snd_kcontrol **kcontrols; /* widget input and outputs */ struct list_head sources; diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 35cc1ed00a44..85b2c94535f4 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -400,6 +400,7 @@ static int dapm_new_mixer(struct snd_soc_dapm_context *dapm, path->long_name = NULL; return ret; } + w->kcontrols[i] = path->kcontrol; } } return ret; @@ -442,6 +443,8 @@ static int dapm_new_mux(struct snd_soc_dapm_context *dapm, if (ret < 0) goto err; + w->kcontrols[0] = kcontrol; + list_for_each_entry(path, &w->sources, list_sink) path->kcontrol = kcontrol; @@ -1480,6 +1483,7 @@ static void dapm_free_widgets(struct snd_soc_dapm_context *dapm) kfree(p->long_name); kfree(p); } + kfree(w->kcontrols); kfree(w->name); kfree(w); } @@ -1730,6 +1734,14 @@ int snd_soc_dapm_new_widgets(struct snd_soc_dapm_context *dapm) if (w->new) continue; + if (w->num_kcontrols) { + w->kcontrols = kzalloc(w->num_kcontrols * + sizeof(struct snd_kcontrol *), + GFP_KERNEL); + if (!w->kcontrols) + return -ENOMEM; + } + switch(w->id) { case snd_soc_dapm_switch: case snd_soc_dapm_mixer: From fafd2176f72148e83c64a1f818ff33fceed83d08 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Thu, 28 Apr 2011 17:38:00 -0600 Subject: [PATCH 09/29] ASoC: Store a list of widgets in a DAPM mux/mixer kcontrol A future change will allow multiple widgets to be affected by the same control. For example, a single register bit that controls separate muxes in both the L and R audio paths. This change updates the code that handles relevant controls to be able to iterate over a list of affected widgets. Note that only the put functions need significant modification to implement the iteration; the get functions do not need to iterate, nor unify the results, since all affected widgets reference the same kcontrol. When creating the list of widgets, always create a 1-sized list, since the control sharing is not implemented in this change. Signed-off-by: Stephen Warren Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- include/sound/soc-dapm.h | 6 ++ sound/soc/soc-dapm.c | 187 +++++++++++++++++++++++++++------------ 2 files changed, 137 insertions(+), 56 deletions(-) diff --git a/include/sound/soc-dapm.h b/include/sound/soc-dapm.h index b25bf0ffc947..c46e7d89561d 100644 --- a/include/sound/soc-dapm.h +++ b/include/sound/soc-dapm.h @@ -518,4 +518,10 @@ struct snd_soc_dapm_context { #endif }; +/* A list of widgets associated with an object, typically a snd_kcontrol */ +struct snd_soc_dapm_widget_list { + int num_widgets; + struct snd_soc_dapm_widget *widgets[0]; +}; + #endif diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 85b2c94535f4..79b836c1045d 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -333,6 +333,8 @@ static int dapm_new_mixer(struct snd_soc_dapm_context *dapm, struct snd_soc_dapm_path *path; struct snd_card *card = dapm->card->snd_card; const char *prefix; + struct snd_soc_dapm_widget_list *wlist; + size_t wlistsize; if (dapm->codec) prefix = dapm->codec->name_prefix; @@ -354,6 +356,18 @@ static int dapm_new_mixer(struct snd_soc_dapm_context *dapm, if (path->name != (char *)w->kcontrol_news[i].name) continue; + wlistsize = sizeof(struct snd_soc_dapm_widget_list) + + sizeof(struct snd_soc_dapm_widget *), + wlist = kzalloc(wlistsize, GFP_KERNEL); + if (wlist == NULL) { + dev_err(dapm->dev, + "asoc: can't allocate widget list for %s\n", + w->name); + return -ENOMEM; + } + wlist->num_widgets = 1; + wlist->widgets[0] = w; + /* add dapm control with long name. * for dapm_mixer this is the concatenation of the * mixer and kcontrol name. @@ -366,8 +380,10 @@ static int dapm_new_mixer(struct snd_soc_dapm_context *dapm, path->long_name = kmalloc(name_len, GFP_KERNEL); - if (path->long_name == NULL) + if (path->long_name == NULL) { + kfree(wlist); return -ENOMEM; + } switch (w->id) { default: @@ -389,13 +405,15 @@ static int dapm_new_mixer(struct snd_soc_dapm_context *dapm, path->long_name[name_len - 1] = '\0'; - path->kcontrol = snd_soc_cnew(&w->kcontrol_news[i], w, - path->long_name, prefix); + path->kcontrol = snd_soc_cnew(&w->kcontrol_news[i], + wlist, path->long_name, + prefix); ret = snd_ctl_add(card, path->kcontrol); if (ret < 0) { dev_err(dapm->dev, "asoc: failed to add dapm kcontrol %s: %d\n", path->long_name, ret); + kfree(wlist); kfree(path->long_name); path->long_name = NULL; return ret; @@ -416,12 +434,25 @@ static int dapm_new_mux(struct snd_soc_dapm_context *dapm, const char *prefix; size_t prefix_len; int ret = 0; + struct snd_soc_dapm_widget_list *wlist; + size_t wlistsize; if (!w->num_kcontrols) { dev_err(dapm->dev, "asoc: mux %s has no controls\n", w->name); return -EINVAL; } + wlistsize = sizeof(struct snd_soc_dapm_widget_list) + + sizeof(struct snd_soc_dapm_widget *), + wlist = kzalloc(wlistsize, GFP_KERNEL); + if (wlist == NULL) { + dev_err(dapm->dev, + "asoc: can't allocate widget list for %s\n", w->name); + return -ENOMEM; + } + wlist->num_widgets = 1; + wlist->widgets[0] = w; + if (dapm->codec) prefix = dapm->codec->name_prefix; else @@ -436,8 +467,8 @@ static int dapm_new_mux(struct snd_soc_dapm_context *dapm, * process but we're also using the same prefix for widgets so * cut the prefix off the front of the widget name. */ - kcontrol = snd_soc_cnew(&w->kcontrol_news[0], w, w->name + prefix_len, - prefix); + kcontrol = snd_soc_cnew(&w->kcontrol_news[0], wlist, + w->name + prefix_len, prefix); ret = snd_ctl_add(card, kcontrol); if (ret < 0) @@ -452,6 +483,7 @@ static int dapm_new_mux(struct snd_soc_dapm_context *dapm, err: dev_err(dapm->dev, "asoc: failed to add kcontrol %s\n", w->name); + kfree(wlist); return ret; } @@ -1818,7 +1850,8 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_new_widgets); int snd_soc_dapm_get_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; unsigned int reg = mc->reg; @@ -1857,7 +1890,9 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_get_volsw); int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct snd_soc_codec *codec = widget->codec; struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; unsigned int reg = mc->reg; @@ -1868,6 +1903,7 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, unsigned int val; int connect, change; struct snd_soc_dapm_update update; + int wi; val = (ucontrol->value.integer.value[0] & mask); @@ -1876,31 +1912,36 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, mask = mask << shift; val = val << shift; - mutex_lock(&widget->codec->mutex); - widget->value = val; + if (val) + /* new connection */ + connect = invert ? 0 : 1; + else + /* old connection must be powered down */ + connect = invert ? 1 : 0; + + mutex_lock(&codec->mutex); change = snd_soc_test_bits(widget->codec, reg, mask, val); if (change) { - if (val) - /* new connection */ - connect = invert ? 0:1; - else - /* old connection must be powered down */ - connect = invert ? 1:0; + for (wi = 0; wi < wlist->num_widgets; wi++) { + widget = wlist->widgets[wi]; - update.kcontrol = kcontrol; - update.widget = widget; - update.reg = reg; - update.mask = mask; - update.val = val; - widget->dapm->update = &update; + widget->value = val; - dapm_mixer_update_power(widget, kcontrol, connect); + update.kcontrol = kcontrol; + update.widget = widget; + update.reg = reg; + update.mask = mask; + update.val = val; + widget->dapm->update = &update; - widget->dapm->update = NULL; + dapm_mixer_update_power(widget, kcontrol, connect); + + widget->dapm->update = NULL; + } } - mutex_unlock(&widget->codec->mutex); + mutex_unlock(&codec->mutex); return 0; } EXPORT_SYMBOL_GPL(snd_soc_dapm_put_volsw); @@ -1917,7 +1958,8 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_put_volsw); int snd_soc_dapm_get_enum_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned int val, bitmask; @@ -1945,11 +1987,14 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_get_enum_double); int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct snd_soc_codec *codec = widget->codec; struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned int val, mux, change; unsigned int mask, bitmask; struct snd_soc_dapm_update update; + int wi; for (bitmask = 1; bitmask < e->max; bitmask <<= 1) ; @@ -1965,22 +2010,29 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, mask |= (bitmask - 1) << e->shift_r; } - mutex_lock(&widget->codec->mutex); - widget->value = val; + mutex_lock(&codec->mutex); + change = snd_soc_test_bits(widget->codec, e->reg, mask, val); + if (change) { + for (wi = 0; wi < wlist->num_widgets; wi++) { + widget = wlist->widgets[wi]; - update.kcontrol = kcontrol; - update.widget = widget; - update.reg = e->reg; - update.mask = mask; - update.val = val; - widget->dapm->update = &update; + widget->value = val; - dapm_mux_update_power(widget, kcontrol, change, mux, e); + update.kcontrol = kcontrol; + update.widget = widget; + update.reg = e->reg; + update.mask = mask; + update.val = val; + widget->dapm->update = &update; - widget->dapm->update = NULL; + dapm_mux_update_power(widget, kcontrol, change, mux, e); - mutex_unlock(&widget->codec->mutex); + widget->dapm->update = NULL; + } + } + + mutex_unlock(&codec->mutex); return change; } EXPORT_SYMBOL_GPL(snd_soc_dapm_put_enum_double); @@ -1995,7 +2047,8 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_put_enum_double); int snd_soc_dapm_get_enum_virt(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; ucontrol->value.enumerated.item[0] = widget->value; @@ -2013,22 +2066,33 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_get_enum_virt); int snd_soc_dapm_put_enum_virt(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct snd_soc_codec *codec = widget->codec; struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; int change; int ret = 0; + int wi; if (ucontrol->value.enumerated.item[0] >= e->max) return -EINVAL; - mutex_lock(&widget->codec->mutex); + mutex_lock(&codec->mutex); change = widget->value != ucontrol->value.enumerated.item[0]; - widget->value = ucontrol->value.enumerated.item[0]; - dapm_mux_update_power(widget, kcontrol, change, widget->value, e); + if (change) { + for (wi = 0; wi < wlist->num_widgets; wi++) { + widget = wlist->widgets[wi]; - mutex_unlock(&widget->codec->mutex); + widget->value = ucontrol->value.enumerated.item[0]; + + dapm_mux_update_power(widget, kcontrol, change, + widget->value, e); + } + } + + mutex_unlock(&codec->mutex); return ret; } EXPORT_SYMBOL_GPL(snd_soc_dapm_put_enum_virt); @@ -2049,7 +2113,8 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_put_enum_virt); int snd_soc_dapm_get_value_enum_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned int reg_val, val, mux; @@ -2089,11 +2154,14 @@ EXPORT_SYMBOL_GPL(snd_soc_dapm_get_value_enum_double); int snd_soc_dapm_put_value_enum_double(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget_list *wlist = snd_kcontrol_chip(kcontrol); + struct snd_soc_dapm_widget *widget = wlist->widgets[0]; + struct snd_soc_codec *codec = widget->codec; struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; unsigned int val, mux, change; unsigned int mask; struct snd_soc_dapm_update update; + int wi; if (ucontrol->value.enumerated.item[0] > e->max - 1) return -EINVAL; @@ -2107,22 +2175,29 @@ int snd_soc_dapm_put_value_enum_double(struct snd_kcontrol *kcontrol, mask |= e->mask << e->shift_r; } - mutex_lock(&widget->codec->mutex); - widget->value = val; + mutex_lock(&codec->mutex); + change = snd_soc_test_bits(widget->codec, e->reg, mask, val); + if (change) { + for (wi = 0; wi < wlist->num_widgets; wi++) { + widget = wlist->widgets[wi]; - update.kcontrol = kcontrol; - update.widget = widget; - update.reg = e->reg; - update.mask = mask; - update.val = val; - widget->dapm->update = &update; + widget->value = val; - dapm_mux_update_power(widget, kcontrol, change, mux, e); + update.kcontrol = kcontrol; + update.widget = widget; + update.reg = e->reg; + update.mask = mask; + update.val = val; + widget->dapm->update = &update; - widget->dapm->update = NULL; + dapm_mux_update_power(widget, kcontrol, change, mux, e); - mutex_unlock(&widget->codec->mutex); + widget->dapm->update = NULL; + } + } + + mutex_unlock(&codec->mutex); return change; } EXPORT_SYMBOL_GPL(snd_soc_dapm_put_value_enum_double); From af46800b9a3947724baeffb1a1649276971297c7 Mon Sep 17 00:00:00 2001 From: Stephen Warren Date: Thu, 28 Apr 2011 17:38:01 -0600 Subject: [PATCH 10/29] ASoC: Implement mux control sharing Control sharing is enabled when two widgets include pointers to the same kcontrol_new in their definition. Specifically: static const struct snd_kcontrol_new adcinput_mux = SOC_DAPM_ENUM("ADC Input", adcinput_enum); static const struct snd_soc_dapm_widget wm8903_dapm_widgets[] = { SND_SOC_DAPM_MUX("Left ADC Input", SND_SOC_NOPM, 0, 0, &adcinput_mux), SND_SOC_DAPM_MUX("Right ADC Input", SND_SOC_NOPM, 0, 0, &adcinput_mux), }; This is useful when a single register bit or field affects multiple muxes at once. The common case is to have separate control bits or fields for each mux (channel). An alternative way of looking at this is that the mux is a stereo (or even n-channel) mux, rather than independant mono muxes. Without this change, a separate kcontrol will be created for each DAPM_MUX. This has the following disadvantages: * Confuses the user/programmer with redundant controls that don't map to separate hardware. * When one of the controls is changed, ASoC fails to update the DAPM logic for paths solely affected by the other controls impacted by the same register bits. This causes some paths not to be correctly powered up or down. Prior to this change, to work around this, the user or programmer had to manually toggle all duplicate controls away from the intended setting, and then back to it. Control sharing implies that the control is named based on the kcontrol_new itself, not any of the widgets that are affected by it. Control sharing is implemented by: When creating kcontrols, if a kcontrol does not yet exist for a particular kcontrol_new, then a new kcontrol is created with a list of widgets containing just a single entry. This is the normal case. However, if a kcontrol does already exists for the given kcontrol_new, the current widget is simply added to that kcontrol's list of affected widgets. Signed-off-by: Stephen Warren Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/soc-dapm.c | 104 ++++++++++++++++++++++++++++++------------- 1 file changed, 74 insertions(+), 30 deletions(-) diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 79b836c1045d..456617e63789 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -324,6 +324,28 @@ static int dapm_connect_mixer(struct snd_soc_dapm_context *dapm, return -ENODEV; } +static int dapm_is_shared_kcontrol(struct snd_soc_dapm_context *dapm, + const struct snd_kcontrol_new *kcontrol_new, + struct snd_kcontrol **kcontrol) +{ + struct snd_soc_dapm_widget *w; + int i; + + *kcontrol = NULL; + + list_for_each_entry(w, &dapm->card->widgets, list) { + for (i = 0; i < w->num_kcontrols; i++) { + if (&w->kcontrol_news[i] == kcontrol_new) { + if (w->kcontrols) + *kcontrol = w->kcontrols[i]; + return 1; + } + } + } + + return 0; +} + /* create new dapm mixer control */ static int dapm_new_mixer(struct snd_soc_dapm_context *dapm, struct snd_soc_dapm_widget *w) @@ -433,58 +455,80 @@ static int dapm_new_mux(struct snd_soc_dapm_context *dapm, struct snd_card *card = dapm->card->snd_card; const char *prefix; size_t prefix_len; - int ret = 0; + int ret; struct snd_soc_dapm_widget_list *wlist; + int shared, wlistentries; size_t wlistsize; + char *name; - if (!w->num_kcontrols) { - dev_err(dapm->dev, "asoc: mux %s has no controls\n", w->name); + if (w->num_kcontrols != 1) { + dev_err(dapm->dev, + "asoc: mux %s has incorrect number of controls\n", + w->name); return -EINVAL; } + shared = dapm_is_shared_kcontrol(dapm, &w->kcontrol_news[0], + &kcontrol); + if (kcontrol) { + wlist = kcontrol->private_data; + wlistentries = wlist->num_widgets + 1; + } else { + wlist = NULL; + wlistentries = 1; + } wlistsize = sizeof(struct snd_soc_dapm_widget_list) + - sizeof(struct snd_soc_dapm_widget *), - wlist = kzalloc(wlistsize, GFP_KERNEL); + wlistentries * sizeof(struct snd_soc_dapm_widget *), + wlist = krealloc(wlist, wlistsize, GFP_KERNEL); if (wlist == NULL) { dev_err(dapm->dev, "asoc: can't allocate widget list for %s\n", w->name); return -ENOMEM; } - wlist->num_widgets = 1; - wlist->widgets[0] = w; + wlist->num_widgets = wlistentries; + wlist->widgets[wlistentries - 1] = w; - if (dapm->codec) - prefix = dapm->codec->name_prefix; - else - prefix = NULL; + if (!kcontrol) { + if (dapm->codec) + prefix = dapm->codec->name_prefix; + else + prefix = NULL; - if (prefix) - prefix_len = strlen(prefix) + 1; - else - prefix_len = 0; + if (shared) { + name = w->kcontrol_news[0].name; + prefix_len = 0; + } else { + name = w->name; + if (prefix) + prefix_len = strlen(prefix) + 1; + else + prefix_len = 0; + } - /* The control will get a prefix from the control creation - * process but we're also using the same prefix for widgets so - * cut the prefix off the front of the widget name. - */ - kcontrol = snd_soc_cnew(&w->kcontrol_news[0], wlist, - w->name + prefix_len, prefix); - ret = snd_ctl_add(card, kcontrol); + /* + * The control will get a prefix from the control creation + * process but we're also using the same prefix for widgets so + * cut the prefix off the front of the widget name. + */ + kcontrol = snd_soc_cnew(&w->kcontrol_news[0], wlist, + name + prefix_len, prefix); + ret = snd_ctl_add(card, kcontrol); + if (ret < 0) { + dev_err(dapm->dev, + "asoc: failed to add kcontrol %s\n", w->name); + kfree(wlist); + return ret; + } + } - if (ret < 0) - goto err; + kcontrol->private_data = wlist; w->kcontrols[0] = kcontrol; list_for_each_entry(path, &w->sources, list_sink) path->kcontrol = kcontrol; - return ret; - -err: - dev_err(dapm->dev, "asoc: failed to add kcontrol %s\n", w->name); - kfree(wlist); - return ret; + return 0; } /* create new dapm volume control */ From ed77cc122a8402db8f9c3492649aa0c3fee7b385 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 3 May 2011 18:25:34 +0100 Subject: [PATCH 11/29] ASoC: Don't crash on PM operations The move over to exposing snd_soc_register_card() let the initialisation of the driver data we use to find the card in PM operations go AWOL. Fix this by setting the driver data when we register the card. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/soc-core.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index d8562ce4de7a..dd55d1069468 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -3291,6 +3291,8 @@ int snd_soc_register_card(struct snd_soc_card *card) if (!card->name || !card->dev) return -EINVAL; + dev_set_drvdata(card->dev, card); + snd_soc_initialize_card_lists(card); soc_init_card_debugfs(card); From abc9d5aa085a4316e1abcd3c3f12ff0bb0133d57 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 3 May 2011 19:29:52 +0100 Subject: [PATCH 12/29] ASoC: Use shared controls for input signal path in WM8915 Gives finer grained power management. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/wm8915.c | 45 ++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/sound/soc/codecs/wm8915.c b/sound/soc/codecs/wm8915.c index 4a3c5cc77e55..0dc1dd70a138 100644 --- a/sound/soc/codecs/wm8915.c +++ b/sound/soc/codecs/wm8915.c @@ -991,10 +991,10 @@ SND_SOC_DAPM_MICBIAS("MICB1", WM8915_POWER_MANAGEMENT_1, 8, 0), SND_SOC_DAPM_PGA("IN1L PGA", WM8915_POWER_MANAGEMENT_2, 5, 0, NULL, 0), SND_SOC_DAPM_PGA("IN1R PGA", WM8915_POWER_MANAGEMENT_2, 4, 0, NULL, 0), -SND_SOC_DAPM_PGA("ADC", SND_SOC_NOPM, 0, 0, NULL, 0), - -SND_SOC_DAPM_MUX("IN1 Mux", SND_SOC_NOPM, 0, 0, &in1_mux), -SND_SOC_DAPM_MUX("IN2 Mux", SND_SOC_NOPM, 0, 0, &in2_mux), +SND_SOC_DAPM_MUX("IN1L Mux", SND_SOC_NOPM, 0, 0, &in1_mux), +SND_SOC_DAPM_MUX("IN1R Mux", SND_SOC_NOPM, 0, 0, &in1_mux), +SND_SOC_DAPM_MUX("IN2L Mux", SND_SOC_NOPM, 0, 0, &in2_mux), +SND_SOC_DAPM_MUX("IN2R Mux", SND_SOC_NOPM, 0, 0, &in2_mux), SND_SOC_DAPM_PGA("IN1L", WM8915_POWER_MANAGEMENT_7, 2, 0, NULL, 0), SND_SOC_DAPM_PGA("IN1R", WM8915_POWER_MANAGEMENT_7, 3, 0, NULL, 0), @@ -1172,28 +1172,33 @@ static const struct snd_soc_dapm_route wm8915_dapm_routes[] = { { "DMIC1L", NULL, "DMIC1" }, { "DMIC1R", NULL, "DMIC1" }, - { "ADC", NULL, "ADCL" }, - { "ADC", NULL, "ADCR" }, + { "IN1L Mux", "ADC", "ADCL" }, + { "IN1L Mux", "DMIC1", "DMIC1L" }, + { "IN1L Mux", "DMIC2", "DMIC2L" }, - { "IN1 Mux", "ADC", "ADC" }, - { "IN1 Mux", "DMIC1", "DMIC1" }, - { "IN1 Mux", "DMIC2", "DMIC2" }, + { "IN1R Mux", "ADC", "ADCR" }, + { "IN1R Mux", "DMIC1", "DMIC1R" }, + { "IN1R Mux", "DMIC2", "DMIC2R" }, - { "IN2 Mux", "ADC", "ADC" }, - { "IN2 Mux", "DMIC1", "DMIC1" }, - { "IN2 Mux", "DMIC2", "DMIC2" }, + { "IN2L Mux", "ADC", "ADCL" }, + { "IN2L Mux", "DMIC1", "DMIC1L" }, + { "IN2L Mux", "DMIC2", "DMIC2L" }, - { "Left Sidetone", "IN1", "IN1 Mux" }, - { "Left Sidetone", "IN2", "IN2 Mux" }, + { "IN2R Mux", "ADC", "ADCR" }, + { "IN2R Mux", "DMIC1", "DMIC1R" }, + { "IN2R Mux", "DMIC2", "DMIC2R" }, - { "Right Sidetone", "IN1", "IN1 Mux" }, - { "Right Sidetone", "IN2", "IN2 Mux" }, + { "Left Sidetone", "IN1", "IN1L Mux" }, + { "Left Sidetone", "IN2", "IN2L Mux" }, - { "DSP1TXL", "IN1 Switch", "IN1 Mux" }, - { "DSP1TXR", "IN1 Switch", "IN1 Mux" }, + { "Right Sidetone", "IN1", "IN1R Mux" }, + { "Right Sidetone", "IN2", "IN2R Mux" }, - { "DSP2TXL", "IN1 Switch", "IN2 Mux" }, - { "DSP2TXR", "IN1 Switch", "IN2 Mux" }, + { "DSP1TXL", "IN1 Switch", "IN1L Mux" }, + { "DSP1TXR", "IN1 Switch", "IN1R Mux" }, + + { "DSP2TXL", "IN1 Switch", "IN2L Mux" }, + { "DSP2TXR", "IN1 Switch", "IN2R Mux" }, { "AIF1TX0", NULL, "DSP1TXL" }, { "AIF1TX1", NULL, "DSP1TXR" }, From e1a02066080da55026c193603c7468c2d95d14a9 Mon Sep 17 00:00:00 2001 From: Mark Brown Date: Tue, 3 May 2011 19:31:20 +0100 Subject: [PATCH 13/29] ASoC: Remove outdated FIXME from WM8915 Actually the current code is perfectly sensible given the hardware. Signed-off-by: Mark Brown Acked-by: Liam Girdwood --- sound/soc/codecs/wm8915.c | 1 - 1 file changed, 1 deletion(-) diff --git a/sound/soc/codecs/wm8915.c b/sound/soc/codecs/wm8915.c index 0dc1dd70a138..ccc9bd832794 100644 --- a/sound/soc/codecs/wm8915.c +++ b/sound/soc/codecs/wm8915.c @@ -1001,7 +1001,6 @@ SND_SOC_DAPM_PGA("IN1R", WM8915_POWER_MANAGEMENT_7, 3, 0, NULL, 0), SND_SOC_DAPM_PGA("IN2L", WM8915_POWER_MANAGEMENT_7, 6, 0, NULL, 0), SND_SOC_DAPM_PGA("IN2R", WM8915_POWER_MANAGEMENT_7, 7, 0, NULL, 0), -/* FIXME - these need to be concentrator widgets */ SND_SOC_DAPM_SUPPLY("DMIC2", WM8915_POWER_MANAGEMENT_7, 9, 0, NULL, 0), SND_SOC_DAPM_SUPPLY("DMIC1", WM8915_POWER_MANAGEMENT_7, 8, 0, NULL, 0), From 9ab88434e8b5ffc5a638b5b1d3b9a67dceb28e5d Mon Sep 17 00:00:00 2001 From: xingchao Date: Wed, 27 Apr 2011 16:58:54 -0400 Subject: [PATCH 14/29] ASoC: sst_platform: add hw_free callback to fix resource leak Signed-off-by: xingchao Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/mid-x86/sst_platform.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sound/soc/mid-x86/sst_platform.c b/sound/soc/mid-x86/sst_platform.c index d567c322a2fb..6b1f9d3bf34e 100644 --- a/sound/soc/mid-x86/sst_platform.c +++ b/sound/soc/mid-x86/sst_platform.c @@ -376,6 +376,11 @@ static int sst_platform_pcm_hw_params(struct snd_pcm_substream *substream, return 0; } +static int sst_platform_pcm_hw_free(struct snd_pcm_substream *substream) +{ + return snd_pcm_lib_free_pages(substream); +} + static struct snd_pcm_ops sst_platform_ops = { .open = sst_platform_open, .close = sst_platform_close, @@ -384,6 +389,7 @@ static struct snd_pcm_ops sst_platform_ops = { .trigger = sst_platform_pcm_trigger, .pointer = sst_platform_pcm_pointer, .hw_params = sst_platform_pcm_hw_params, + .hw_free = sst_platform_pcm_hw_free, }; static void sst_pcm_free(struct snd_pcm *pcm) From 77530150fba769d7b7e260b3f16ed2294c1737b6 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Thu, 5 May 2011 16:59:09 +0200 Subject: [PATCH 15/29] ASoC: Create codec DAPM widgets before calling the codecs probe function This allows to create DAPM routes depending on those widgets in the codecs probe function. This is helpful when supporting similar codecs with minor differences in the DAPM routing with the same driver. Something similar has already been done for cards in commit a841ebb9 (ASoC: Create card DAPM widgets early so they can be used in callbacks). Signed-off-by: Lars-Peter Clausen Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/soc-core.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 133edeb9728a..a477e218aa28 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -1495,6 +1495,10 @@ static int soc_probe_codec(struct snd_soc_card *card, soc_init_codec_debugfs(codec); + if (driver->dapm_widgets) + snd_soc_dapm_new_controls(&codec->dapm, driver->dapm_widgets, + driver->num_dapm_widgets); + if (driver->probe) { ret = driver->probe(codec); if (ret < 0) { @@ -1508,9 +1512,6 @@ static int soc_probe_codec(struct snd_soc_card *card, if (driver->controls) snd_soc_add_controls(codec, driver->controls, driver->num_controls); - if (driver->dapm_widgets) - snd_soc_dapm_new_controls(&codec->dapm, driver->dapm_widgets, - driver->num_dapm_widgets); if (driver->dapm_routes) snd_soc_dapm_add_routes(&codec->dapm, driver->dapm_routes, driver->num_dapm_routes); From 64d27069755db41daa36e4770d88ebc57617559d Mon Sep 17 00:00:00 2001 From: Dimitris Papastamos Date: Thu, 5 May 2011 14:18:11 +0100 Subject: [PATCH 16/29] ASoC: soc-cache: Allow codec->cache_bypass to be used with snd_soc_hw_bulk_write_raw() If we specifically want to write a block of data to the hw bypassing the cache, then allow this to happen inside snd_soc_hw_bulk_write_raw(). Signed-off-by: Dimitris Papastamos Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/soc-cache.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/sound/soc/soc-cache.c b/sound/soc/soc-cache.c index a217db256700..687beec56476 100644 --- a/sound/soc/soc-cache.c +++ b/sound/soc/soc-cache.c @@ -404,12 +404,13 @@ static int snd_soc_hw_bulk_write_raw(struct snd_soc_codec *codec, unsigned int r { int ret; - /* Ensure that the base register is volatile. Subsequently - * any other register that is touched by this routine should be - * volatile as well to ensure that we don't get out of sync with - * the cache. + /* To ensure that we don't get out of sync with the cache, check + * whether the base register is volatile or if we've directly asked + * to bypass the cache. Out of bounds registers are considered + * volatile. */ - if (!snd_soc_codec_volatile_register(codec, reg) + if (!codec->cache_bypass + && !snd_soc_codec_volatile_register(codec, reg) && reg < codec->driver->reg_cache_size) return -EINVAL; From 04b894553fd6e6fd7439e8440fd6bf5b6a17d9ae Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Thu, 5 May 2011 16:59:12 +0200 Subject: [PATCH 17/29] ASoC: SSM2602: Properly annotate i2c probe and remove functions Annotate the i2c probe and remove functions with __devinit and __devexit. Signed-off-by: Lars-Peter Clausen Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/ssm2602.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index 2727befd158e..f7c1ce57b359 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -614,7 +614,7 @@ static struct snd_soc_codec_driver soc_codec_dev_ssm2602 = { * low = 0x1a * high = 0x1b */ -static int ssm2602_i2c_probe(struct i2c_client *i2c, +static int __devinit ssm2602_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct ssm2602_priv *ssm2602; @@ -635,7 +635,7 @@ static int ssm2602_i2c_probe(struct i2c_client *i2c, return ret; } -static int ssm2602_i2c_remove(struct i2c_client *client) +static int __devexit ssm2602_i2c_remove(struct i2c_client *client) { snd_soc_unregister_codec(&client->dev); kfree(i2c_get_clientdata(client)); @@ -655,7 +655,7 @@ static struct i2c_driver ssm2602_i2c_driver = { .owner = THIS_MODULE, }, .probe = ssm2602_i2c_probe, - .remove = ssm2602_i2c_remove, + .remove = __devexit_p(ssm2602_i2c_remove), .id_table = ssm2602_i2c_id, }; #endif From 36c90ab33feabbd63da775bd92ad356e5bd5cf56 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Thu, 5 May 2011 16:59:16 +0200 Subject: [PATCH 18/29] ASoC: SSM2602: Fix 'Mic Boost2' control The 'Mic Boost2' control's shift was off by one and thus was not working. Signed-off-by: Lars-Peter Clausen Acked-by: Liam Girdwood Signed-off-by: Mark Brown Cc: stable@kernel.org --- sound/soc/codecs/ssm2602.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index f7c1ce57b359..946797dbb0c5 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -139,7 +139,7 @@ SOC_DOUBLE_R("Capture Volume", SSM2602_LINVOL, SSM2602_RINVOL, 0, 31, 0), SOC_DOUBLE_R("Capture Switch", SSM2602_LINVOL, SSM2602_RINVOL, 7, 1, 1), SOC_SINGLE("Mic Boost (+20dB)", SSM2602_APANA, 0, 1, 0), -SOC_SINGLE("Mic Boost2 (+20dB)", SSM2602_APANA, 7, 1, 0), +SOC_SINGLE("Mic Boost2 (+20dB)", SSM2602_APANA, 8, 1, 0), SOC_SINGLE("Mic Switch", SSM2602_APANA, 1, 1, 1), SOC_SINGLE("Sidetone Playback Volume", SSM2602_APANA, 6, 3, 1), From 8fc63fe9412634c72676db42649f357eaac04566 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Thu, 5 May 2011 16:59:14 +0200 Subject: [PATCH 19/29] ASoC: SSM2602: Fix reg_cache_size reg_cache_size is supposed to be the number of elements in the register cache, not the size in bytes. Signed-off-by: Lars-Peter Clausen Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/ssm2602.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index 946797dbb0c5..b04d28039c16 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -602,7 +602,7 @@ static struct snd_soc_codec_driver soc_codec_dev_ssm2602 = { .read = ssm2602_read_reg_cache, .write = ssm2602_write, .set_bias_level = ssm2602_set_bias_level, - .reg_cache_size = sizeof(ssm2602_reg), + .reg_cache_size = ARRAY_SIZE(ssm2602_reg), .reg_word_size = sizeof(u16), .reg_cache_default = ssm2602_reg, }; From 0b4cd2e01c63cf303bba570c9896331e4932a9df Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Thu, 5 May 2011 16:59:10 +0200 Subject: [PATCH 20/29] ASoC: SSM2602: Cleanup coeff handling Drop unused field from the coeff struct, precalculate the srate register at compile-time and cleanup up the naming. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown --- sound/soc/codecs/ssm2602.c | 75 ++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 40 deletions(-) diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index 565bc72fb173..73b447eba297 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -167,83 +167,78 @@ static int ssm2602_add_widgets(struct snd_soc_codec *codec) return 0; } -struct _coeff_div { +struct ssm2602_coeff { u32 mclk; u32 rate; - u16 fs; - u8 sr:4; - u8 bosr:1; - u8 usb:1; + u8 srate; }; -/* codec mclk clock divider coefficients */ -static const struct _coeff_div coeff_div[] = { +#define SSM2602_COEFF_SRATE(sr, bosr, usb) (((sr) << 2) | ((bosr) << 1) | (usb)) + +/* codec mclk clock coefficients */ +static const struct ssm2602_coeff ssm2602_coeff_table[] = { /* 48k */ - {12288000, 48000, 256, 0x0, 0x0, 0x0}, - {18432000, 48000, 384, 0x0, 0x1, 0x0}, - {12000000, 48000, 250, 0x0, 0x0, 0x1}, + {12288000, 48000, SSM2602_COEFF_SRATE(0x0, 0x0, 0x0)}, + {18432000, 48000, SSM2602_COEFF_SRATE(0x0, 0x1, 0x0)}, + {12000000, 48000, SSM2602_COEFF_SRATE(0x0, 0x0, 0x1)}, /* 32k */ - {12288000, 32000, 384, 0x6, 0x0, 0x0}, - {18432000, 32000, 576, 0x6, 0x1, 0x0}, - {12000000, 32000, 375, 0x6, 0x0, 0x1}, + {12288000, 32000, SSM2602_COEFF_SRATE(0x6, 0x0, 0x0)}, + {18432000, 32000, SSM2602_COEFF_SRATE(0x6, 0x1, 0x0)}, + {12000000, 32000, SSM2602_COEFF_SRATE(0x6, 0x0, 0x1)}, /* 8k */ - {12288000, 8000, 1536, 0x3, 0x0, 0x0}, - {18432000, 8000, 2304, 0x3, 0x1, 0x0}, - {11289600, 8000, 1408, 0xb, 0x0, 0x0}, - {16934400, 8000, 2112, 0xb, 0x1, 0x0}, - {12000000, 8000, 1500, 0x3, 0x0, 0x1}, + {12288000, 8000, SSM2602_COEFF_SRATE(0x3, 0x0, 0x0)}, + {18432000, 8000, SSM2602_COEFF_SRATE(0x3, 0x1, 0x0)}, + {11289600, 8000, SSM2602_COEFF_SRATE(0xb, 0x0, 0x0)}, + {16934400, 8000, SSM2602_COEFF_SRATE(0xb, 0x1, 0x0)}, + {12000000, 8000, SSM2602_COEFF_SRATE(0x3, 0x0, 0x1)}, /* 96k */ - {12288000, 96000, 128, 0x7, 0x0, 0x0}, - {18432000, 96000, 192, 0x7, 0x1, 0x0}, - {12000000, 96000, 125, 0x7, 0x0, 0x1}, + {12288000, 96000, SSM2602_COEFF_SRATE(0x7, 0x0, 0x0)}, + {18432000, 96000, SSM2602_COEFF_SRATE(0x7, 0x1, 0x0)}, + {12000000, 96000, SSM2602_COEFF_SRATE(0x7, 0x0, 0x1)}, /* 44.1k */ - {11289600, 44100, 256, 0x8, 0x0, 0x0}, - {16934400, 44100, 384, 0x8, 0x1, 0x0}, - {12000000, 44100, 272, 0x8, 0x1, 0x1}, + {11289600, 44100, SSM2602_COEFF_SRATE(0x8, 0x0, 0x0)}, + {16934400, 44100, SSM2602_COEFF_SRATE(0x8, 0x1, 0x0)}, + {12000000, 44100, SSM2602_COEFF_SRATE(0x8, 0x1, 0x1)}, /* 88.2k */ - {11289600, 88200, 128, 0xf, 0x0, 0x0}, - {16934400, 88200, 192, 0xf, 0x1, 0x0}, - {12000000, 88200, 136, 0xf, 0x1, 0x1}, + {11289600, 88200, SSM2602_COEFF_SRATE(0xf, 0x0, 0x0)}, + {16934400, 88200, SSM2602_COEFF_SRATE(0xf, 0x1, 0x0)}, + {12000000, 88200, SSM2602_COEFF_SRATE(0xf, 0x1, 0x1)}, }; -static inline int get_coeff(int mclk, int rate) +static inline int ssm2602_get_coeff(int mclk, int rate) { int i; - for (i = 0; i < ARRAY_SIZE(coeff_div); i++) { - if (coeff_div[i].rate == rate && coeff_div[i].mclk == mclk) - return i; + for (i = 0; i < ARRAY_SIZE(ssm2602_coeff_table); i++) { + if (ssm2602_coeff_table[i].rate == rate && + ssm2602_coeff_table[i].mclk == mclk) + return ssm2602_coeff_table[i].srate; } - return i; + return -EINVAL; } static int ssm2602_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { - u16 srate; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_codec *codec = rtd->codec; struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec); u16 iface = snd_soc_read(codec, SSM2602_IFACE) & 0xfff3; - int i = get_coeff(ssm2602->sysclk, params_rate(params)); + int srate = ssm2602_get_coeff(ssm2602->sysclk, params_rate(params)); if (substream == ssm2602->slave_substream) { dev_dbg(codec->dev, "Ignoring hw_params for slave substream\n"); return 0; } - /*no match is found*/ - if (i == ARRAY_SIZE(coeff_div)) - return -EINVAL; - - srate = (coeff_div[i].sr << 2) | - (coeff_div[i].bosr << 1) | coeff_div[i].usb; + if (srate < 0) + return srate; snd_soc_write(codec, SSM2602_ACTIVE, 0); snd_soc_write(codec, SSM2602_SRATE, srate); From ffd13c39c74c140925a11bb4595f41badf78142d Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Thu, 5 May 2011 16:59:11 +0200 Subject: [PATCH 21/29] ASoC: SSM2602: Remove duplicate control There are currently two controls which allow selecting the capture source, one as a normal control, the other as part of a DAPM_MUX widget. Remove the normal control. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown --- sound/soc/codecs/ssm2602.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index 73b447eba297..30229cf1bb54 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -99,8 +99,6 @@ SOC_SINGLE("Sidetone Playback Volume", SSM2602_APANA, 6, 3, 1), SOC_SINGLE("ADC High Pass Filter Switch", SSM2602_APDIGI, 0, 1, 1), SOC_SINGLE("Store DC Offset Switch", SSM2602_APDIGI, 4, 1, 0), -SOC_ENUM("Capture Source", ssm2602_enum[0]), - SOC_ENUM("Playback De-emphasis", ssm2602_enum[1]), }; From 5663940e2a9a9c9031cdba9ca170060de14da83f Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Thu, 5 May 2011 16:59:13 +0200 Subject: [PATCH 22/29] ASoC: SSM2602: Remove unused struct and define Those are leftovers from a pre-multicomponent era. Signed-off-by: Lars-Peter Clausen Signed-off-by: Mark Brown --- sound/soc/codecs/ssm2602.h | 6 ------ 1 file changed, 6 deletions(-) diff --git a/sound/soc/codecs/ssm2602.h b/sound/soc/codecs/ssm2602.h index 42a47d0f8e25..b98c69168036 100644 --- a/sound/soc/codecs/ssm2602.h +++ b/sound/soc/codecs/ssm2602.h @@ -117,11 +117,5 @@ #define SSM2602_CACHEREGNUM 10 #define SSM2602_SYSCLK 0 -#define SSM2602_DAI 0 - -struct ssm2602_setup_data { - int i2c_bus; - unsigned short i2c_address; -}; #endif From bf707de21fec7bb203dace2d0a2bbd124d1b36ca Mon Sep 17 00:00:00 2001 From: Marek Belisko Date: Tue, 3 May 2011 14:46:32 +0200 Subject: [PATCH 23/29] ASoC: UDA134x: Remove POWER_OFF_ON_STANDBY define. Define POWER_OFF_ON_STANDBY cause trobles when trying to get some sound from codec because code for bias setup was not compiled (define wasn't defined). This define was removed in commit: cc3202f5 but again introduced by commit: f0fba2ad1 which then completely break codec functionality so remove it again. Signed-off-by: Marek Belisko Acked-by: Liam Girdwood Signed-off-by: Mark Brown Cc: stable@kernel.org --- sound/soc/codecs/uda134x.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/sound/soc/codecs/uda134x.c b/sound/soc/codecs/uda134x.c index 48ffd406a71d..a7b8f301bad3 100644 --- a/sound/soc/codecs/uda134x.c +++ b/sound/soc/codecs/uda134x.c @@ -601,9 +601,7 @@ static struct snd_soc_codec_driver soc_codec_dev_uda134x = { .reg_cache_step = 1, .read = uda134x_read_reg_cache, .write = uda134x_write, -#ifdef POWER_OFF_ON_STANDBY .set_bias_level = uda134x_set_bias_level, -#endif }; static int __devinit uda134x_codec_probe(struct platform_device *pdev) From 7164bdb643cd9c919d69a5ea55b6f8477b90657c Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sun, 8 May 2011 09:24:41 -0700 Subject: [PATCH 24/29] ASoC: SSM2602: Fix default register cache Some of the values in the default register cache did not represent the codecs state after reset. This patch fixes it. Signed-off-by: Lars-Peter Clausen Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/ssm2602.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index 30229cf1bb54..a09d66c0e70e 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -60,8 +60,8 @@ struct ssm2602_priv { * There is no point in caching the reset register */ static const u16 ssm2602_reg[SSM2602_CACHEREGNUM] = { - 0x0017, 0x0017, 0x0079, 0x0079, - 0x0000, 0x0000, 0x0000, 0x000a, + 0x0097, 0x0097, 0x0079, 0x0079, + 0x000a, 0x0008, 0x009f, 0x000a, 0x0000, 0x0000 }; From f6c1f2d5e5d50366910fd687e88d07ebaabe00ab Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sun, 8 May 2011 09:24:42 -0700 Subject: [PATCH 25/29] ASoC: SSM2602: Do not power the codec up in probe It is not required to have the codec powered at this stage and DAPM will power the ADC and DAC down again after probe has run anyway. Thus we avoid some unnecessary writes by this change. Signed-off-by: Lars-Peter Clausen Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/ssm2602.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index a09d66c0e70e..e1ebf3d04935 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -509,8 +509,6 @@ static int ssm2602_probe(struct snd_soc_codec *codec) return ret; } - /*power on device*/ - snd_soc_write(codec, SSM2602_ACTIVE, 0); /* set the update bits */ reg = snd_soc_read(codec, SSM2602_LINVOL); snd_soc_write(codec, SSM2602_LINVOL, reg | LINVOL_LRIN_BOTH); @@ -523,7 +521,6 @@ static int ssm2602_probe(struct snd_soc_codec *codec) /*select Line in as default input*/ snd_soc_write(codec, SSM2602_APANA, APANA_SELECT_DAC | APANA_ENABLE_MIC_BOOST); - snd_soc_write(codec, SSM2602_PWR, 0); snd_soc_add_controls(codec, ssm2602_snd_controls, ARRAY_SIZE(ssm2602_snd_controls)); From b1f7b2b56b98d9eedc6a1b127d5bc5e9c51d2f73 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sun, 8 May 2011 09:24:43 -0700 Subject: [PATCH 26/29] ASoC: SSM2602: Add SSM2604 support The SSM2604 is basically a lightweight variant of the SSM2602 with a compatible register layout. Thus we can easily support both devices by the same driver, by providing a slightly set of controls, widgets and routes. Compared to the SSM2602 the SSM2604 has no microphone input and no headphone output. Signed-off-by: Lars-Peter Clausen Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/ssm2602.c | 179 +++++++++++++++++++++++++------------ 1 file changed, 121 insertions(+), 58 deletions(-) diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index e1ebf3d04935..b0306cf09dfe 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -45,12 +45,19 @@ #define SSM2602_VERSION "0.1" +enum ssm2602_type { + SSM2602, + SSM2604, +}; + /* codec private data */ struct ssm2602_priv { unsigned int sysclk; enum snd_soc_control_type control_type; struct snd_pcm_substream *master_substream; struct snd_pcm_substream *slave_substream; + + enum ssm2602_type type; }; /* @@ -80,90 +87,97 @@ static const struct soc_enum ssm2602_enum[] = { SOC_ENUM_SINGLE(SSM2602_APDIGI, 1, 4, ssm2602_deemph), }; -static const struct snd_kcontrol_new ssm2602_snd_controls[] = { - -SOC_DOUBLE_R("Master Playback Volume", SSM2602_LOUT1V, SSM2602_ROUT1V, - 0, 127, 0), -SOC_DOUBLE_R("Master Playback ZC Switch", SSM2602_LOUT1V, SSM2602_ROUT1V, - 7, 1, 0), - +static const struct snd_kcontrol_new ssm260x_snd_controls[] = { SOC_DOUBLE_R("Capture Volume", SSM2602_LINVOL, SSM2602_RINVOL, 0, 31, 0), SOC_DOUBLE_R("Capture Switch", SSM2602_LINVOL, SSM2602_RINVOL, 7, 1, 1), -SOC_SINGLE("Mic Boost (+20dB)", SSM2602_APANA, 0, 1, 0), -SOC_SINGLE("Mic Boost2 (+20dB)", SSM2602_APANA, 8, 1, 0), -SOC_SINGLE("Mic Switch", SSM2602_APANA, 1, 1, 1), - -SOC_SINGLE("Sidetone Playback Volume", SSM2602_APANA, 6, 3, 1), - SOC_SINGLE("ADC High Pass Filter Switch", SSM2602_APDIGI, 0, 1, 1), SOC_SINGLE("Store DC Offset Switch", SSM2602_APDIGI, 4, 1, 0), SOC_ENUM("Playback De-emphasis", ssm2602_enum[1]), }; +static const struct snd_kcontrol_new ssm2602_snd_controls[] = { +SOC_DOUBLE_R("Master Playback Volume", SSM2602_LOUT1V, SSM2602_ROUT1V, + 0, 127, 0), +SOC_DOUBLE_R("Master Playback ZC Switch", SSM2602_LOUT1V, SSM2602_ROUT1V, + 7, 1, 0), + +SOC_SINGLE("Sidetone Playback Volume", SSM2602_APANA, 6, 3, 1), + +SOC_SINGLE("Mic Boost (+20dB)", SSM2602_APANA, 0, 1, 0), +SOC_SINGLE("Mic Boost2 (+20dB)", SSM2602_APANA, 8, 1, 0), +SOC_SINGLE("Mic Switch", SSM2602_APANA, 1, 1, 1), +}; + /* Output Mixer */ -static const struct snd_kcontrol_new ssm2602_output_mixer_controls[] = { +static const struct snd_kcontrol_new ssm260x_output_mixer_controls[] = { SOC_DAPM_SINGLE("Line Bypass Switch", SSM2602_APANA, 3, 1, 0), -SOC_DAPM_SINGLE("Mic Sidetone Switch", SSM2602_APANA, 5, 1, 0), SOC_DAPM_SINGLE("HiFi Playback Switch", SSM2602_APANA, 4, 1, 0), +SOC_DAPM_SINGLE("Mic Sidetone Switch", SSM2602_APANA, 5, 1, 0), }; /* Input mux */ static const struct snd_kcontrol_new ssm2602_input_mux_controls = SOC_DAPM_ENUM("Input Select", ssm2602_enum[0]); -static const struct snd_soc_dapm_widget ssm2602_dapm_widgets[] = { -SND_SOC_DAPM_MIXER("Output Mixer", SSM2602_PWR, 4, 1, - &ssm2602_output_mixer_controls[0], - ARRAY_SIZE(ssm2602_output_mixer_controls)), +static const struct snd_soc_dapm_widget ssm260x_dapm_widgets[] = { SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SSM2602_PWR, 3, 1), -SND_SOC_DAPM_OUTPUT("LOUT"), -SND_SOC_DAPM_OUTPUT("LHPOUT"), -SND_SOC_DAPM_OUTPUT("ROUT"), -SND_SOC_DAPM_OUTPUT("RHPOUT"), SND_SOC_DAPM_ADC("ADC", "HiFi Capture", SSM2602_PWR, 2, 1), -SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, &ssm2602_input_mux_controls), SND_SOC_DAPM_PGA("Line Input", SSM2602_PWR, 0, 1, NULL, 0), -SND_SOC_DAPM_MICBIAS("Mic Bias", SSM2602_PWR, 1, 1), -SND_SOC_DAPM_INPUT("MICIN"), + +SND_SOC_DAPM_OUTPUT("LOUT"), +SND_SOC_DAPM_OUTPUT("ROUT"), SND_SOC_DAPM_INPUT("RLINEIN"), SND_SOC_DAPM_INPUT("LLINEIN"), }; -static const struct snd_soc_dapm_route audio_conn[] = { - /* output mixer */ +static const struct snd_soc_dapm_widget ssm2602_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Output Mixer", SSM2602_PWR, 4, 1, + ssm260x_output_mixer_controls, + ARRAY_SIZE(ssm260x_output_mixer_controls)), + +SND_SOC_DAPM_MUX("Input Mux", SND_SOC_NOPM, 0, 0, &ssm2602_input_mux_controls), +SND_SOC_DAPM_MICBIAS("Mic Bias", SSM2602_PWR, 1, 1), + +SND_SOC_DAPM_OUTPUT("LHPOUT"), +SND_SOC_DAPM_OUTPUT("RHPOUT"), +SND_SOC_DAPM_INPUT("MICIN"), +}; + +static const struct snd_soc_dapm_widget ssm2604_dapm_widgets[] = { +SND_SOC_DAPM_MIXER("Output Mixer", SND_SOC_NOPM, 0, 0, + ssm260x_output_mixer_controls, + ARRAY_SIZE(ssm260x_output_mixer_controls) - 1), /* Last element is the mic */ +}; + +static const struct snd_soc_dapm_route ssm260x_routes[] = { {"Output Mixer", "Line Bypass Switch", "Line Input"}, {"Output Mixer", "HiFi Playback Switch", "DAC"}, - {"Output Mixer", "Mic Sidetone Switch", "Mic Bias"}, - /* outputs */ - {"RHPOUT", NULL, "Output Mixer"}, {"ROUT", NULL, "Output Mixer"}, - {"LHPOUT", NULL, "Output Mixer"}, {"LOUT", NULL, "Output Mixer"}, - /* input mux */ + {"Line Input", NULL, "LLINEIN"}, + {"Line Input", NULL, "RLINEIN"}, +}; + +static const struct snd_soc_dapm_route ssm2602_routes[] = { + {"Output Mixer", "Mic Sidetone Switch", "Mic Bias"}, + + {"RHPOUT", NULL, "Output Mixer"}, + {"LHPOUT", NULL, "Output Mixer"}, + {"Input Mux", "Line", "Line Input"}, {"Input Mux", "Mic", "Mic Bias"}, {"ADC", NULL, "Input Mux"}, - /* inputs */ - {"Line Input", NULL, "LLINEIN"}, - {"Line Input", NULL, "RLINEIN"}, {"Mic Bias", NULL, "MICIN"}, }; -static int ssm2602_add_widgets(struct snd_soc_codec *codec) -{ - struct snd_soc_dapm_context *dapm = &codec->dapm; - - snd_soc_dapm_new_controls(dapm, ssm2602_dapm_widgets, - ARRAY_SIZE(ssm2602_dapm_widgets)); - snd_soc_dapm_add_routes(dapm, audio_conn, ARRAY_SIZE(audio_conn)); - - return 0; -} +static const struct snd_soc_dapm_route ssm2604_routes[] = { + {"ADC", NULL, "Line Input"}, +}; struct ssm2602_coeff { u32 mclk; @@ -491,9 +505,47 @@ static int ssm2602_resume(struct snd_soc_codec *codec) } static int ssm2602_probe(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_context *dapm = &codec->dapm; + int ret, reg; + + reg = snd_soc_read(codec, SSM2602_LOUT1V); + snd_soc_write(codec, SSM2602_LOUT1V, reg | LOUT1V_LRHP_BOTH); + reg = snd_soc_read(codec, SSM2602_ROUT1V); + snd_soc_write(codec, SSM2602_ROUT1V, reg | ROUT1V_RLHP_BOTH); + + ret = snd_soc_add_controls(codec, ssm2602_snd_controls, + ARRAY_SIZE(ssm2602_snd_controls)); + if (ret) + return ret; + + ret = snd_soc_dapm_new_controls(dapm, ssm2602_dapm_widgets, + ARRAY_SIZE(ssm2602_dapm_widgets)); + if (ret) + return ret; + + return snd_soc_dapm_add_routes(dapm, ssm2602_routes, + ARRAY_SIZE(ssm2602_routes)); +} + +static int ssm2604_probe(struct snd_soc_codec *codec) +{ + struct snd_soc_dapm_context *dapm = &codec->dapm; + int ret; + + ret = snd_soc_dapm_new_controls(dapm, ssm2604_dapm_widgets, + ARRAY_SIZE(ssm2604_dapm_widgets)); + if (ret) + return ret; + + return snd_soc_dapm_add_routes(dapm, ssm2604_routes, + ARRAY_SIZE(ssm2604_routes)); +} + +static int ssm260x_probe(struct snd_soc_codec *codec) { struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec); - int ret = 0, reg; + int ret, reg; pr_info("ssm2602 Audio Codec %s", SSM2602_VERSION); @@ -514,19 +566,20 @@ static int ssm2602_probe(struct snd_soc_codec *codec) snd_soc_write(codec, SSM2602_LINVOL, reg | LINVOL_LRIN_BOTH); reg = snd_soc_read(codec, SSM2602_RINVOL); snd_soc_write(codec, SSM2602_RINVOL, reg | RINVOL_RLIN_BOTH); - reg = snd_soc_read(codec, SSM2602_LOUT1V); - snd_soc_write(codec, SSM2602_LOUT1V, reg | LOUT1V_LRHP_BOTH); - reg = snd_soc_read(codec, SSM2602_ROUT1V); - snd_soc_write(codec, SSM2602_ROUT1V, reg | ROUT1V_RLHP_BOTH); /*select Line in as default input*/ snd_soc_write(codec, SSM2602_APANA, APANA_SELECT_DAC | APANA_ENABLE_MIC_BOOST); - snd_soc_add_controls(codec, ssm2602_snd_controls, - ARRAY_SIZE(ssm2602_snd_controls)); - ssm2602_add_widgets(codec); + switch (ssm2602->type) { + case SSM2602: + ret = ssm2602_probe(codec); + break; + case SSM2604: + ret = ssm2604_probe(codec); + break; + } - return 0; + return ret; } /* remove everything here */ @@ -537,7 +590,7 @@ static int ssm2602_remove(struct snd_soc_codec *codec) } static struct snd_soc_codec_driver soc_codec_dev_ssm2602 = { - .probe = ssm2602_probe, + .probe = ssm260x_probe, .remove = ssm2602_remove, .suspend = ssm2602_suspend, .resume = ssm2602_resume, @@ -545,6 +598,13 @@ static struct snd_soc_codec_driver soc_codec_dev_ssm2602 = { .reg_cache_size = ARRAY_SIZE(ssm2602_reg), .reg_word_size = sizeof(u16), .reg_cache_default = ssm2602_reg, + + .controls = ssm260x_snd_controls, + .num_controls = ARRAY_SIZE(ssm260x_snd_controls), + .dapm_widgets = ssm260x_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(ssm260x_dapm_widgets), + .dapm_routes = ssm260x_routes, + .num_dapm_routes = ARRAY_SIZE(ssm260x_routes), }; #if defined(CONFIG_SPI_MASTER) @@ -559,6 +619,7 @@ static int __devinit ssm2602_spi_probe(struct spi_device *spi) spi_set_drvdata(spi, ssm2602); ssm2602->control_type = SND_SOC_SPI; + ssm2602->type = SSM2602; ret = snd_soc_register_codec(&spi->dev, &soc_codec_dev_ssm2602, &ssm2602_dai, 1); @@ -603,6 +664,7 @@ static int __devinit ssm2602_i2c_probe(struct i2c_client *i2c, i2c_set_clientdata(i2c, ssm2602); ssm2602->control_type = SND_SOC_I2C; + ssm2602->type = id->driver_data; ret = snd_soc_register_codec(&i2c->dev, &soc_codec_dev_ssm2602, &ssm2602_dai, 1); @@ -619,7 +681,8 @@ static int __devexit ssm2602_i2c_remove(struct i2c_client *client) } static const struct i2c_device_id ssm2602_i2c_id[] = { - { "ssm2602", 0 }, + { "ssm2602", SSM2602 }, + { "ssm2604", SSM2604 }, { } }; MODULE_DEVICE_TABLE(i2c, ssm2602_i2c_id); @@ -669,6 +732,6 @@ static void __exit ssm2602_exit(void) } module_exit(ssm2602_exit); -MODULE_DESCRIPTION("ASoC ssm2602 driver"); +MODULE_DESCRIPTION("ASoC SSM2602/SSM2604 driver"); MODULE_AUTHOR("Cliff Cai"); MODULE_LICENSE("GPL"); From 7dcf2760bf9d127e6b582977d72316ca58612854 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sun, 8 May 2011 09:24:44 -0700 Subject: [PATCH 27/29] ASoC: SSM2602: Add entry for the ssm2603 to the device id table The SSM2603 is mostly register compatible with the SSM2602 and can be supported by the current driver without any changes. Signed-off-by: Lars-Peter Clausen Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/ssm2602.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index b0306cf09dfe..d8287219eeab 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -682,6 +682,7 @@ static int __devexit ssm2602_i2c_remove(struct i2c_client *client) static const struct i2c_device_id ssm2602_i2c_id[] = { { "ssm2602", SSM2602 }, + { "ssm2603", SSM2602 }, { "ssm2604", SSM2604 }, { } }; @@ -732,6 +733,6 @@ static void __exit ssm2602_exit(void) } module_exit(ssm2602_exit); -MODULE_DESCRIPTION("ASoC SSM2602/SSM2604 driver"); +MODULE_DESCRIPTION("ASoC SSM2602/SSM2603/SSM2604 driver"); MODULE_AUTHOR("Cliff Cai"); MODULE_LICENSE("GPL"); From 2a43801a76893286ead35e742e486591e75803a2 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sun, 8 May 2011 09:24:45 -0700 Subject: [PATCH 28/29] ASoC: SSM2602: Model power supply for the digital core as a DAPM widget Model the power supply for the digital core as a DAPM_SUPPLY widget. This allows to cleanup the code a bit. Signed-off-by: Lars-Peter Clausen Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/ssm2602.c | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index d8287219eeab..763d392f12a1 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -126,6 +126,8 @@ SND_SOC_DAPM_DAC("DAC", "HiFi Playback", SSM2602_PWR, 3, 1), SND_SOC_DAPM_ADC("ADC", "HiFi Capture", SSM2602_PWR, 2, 1), SND_SOC_DAPM_PGA("Line Input", SSM2602_PWR, 0, 1, NULL, 0), +SND_SOC_DAPM_SUPPLY("Digital Core Power", SSM2602_ACTIVE, 0, 0, 0, 0), + SND_SOC_DAPM_OUTPUT("LOUT"), SND_SOC_DAPM_OUTPUT("ROUT"), SND_SOC_DAPM_INPUT("RLINEIN"), @@ -152,6 +154,9 @@ SND_SOC_DAPM_MIXER("Output Mixer", SND_SOC_NOPM, 0, 0, }; static const struct snd_soc_dapm_route ssm260x_routes[] = { + {"DAC", NULL, "Digital Core Power"}, + {"ADC", NULL, "Digital Core Power"}, + {"Output Mixer", "Line Bypass Switch", "Line Input"}, {"Output Mixer", "HiFi Playback Switch", "DAC"}, @@ -252,7 +257,6 @@ static int ssm2602_hw_params(struct snd_pcm_substream *substream, if (srate < 0) return srate; - snd_soc_write(codec, SSM2602_ACTIVE, 0); snd_soc_write(codec, SSM2602_SRATE, srate); /* bit size */ @@ -270,7 +274,6 @@ static int ssm2602_hw_params(struct snd_pcm_substream *substream, break; } snd_soc_write(codec, SSM2602_IFACE, iface); - snd_soc_write(codec, SSM2602_ACTIVE, ACTIVE_ACTIVATE_CODEC); return 0; } @@ -312,17 +315,6 @@ static int ssm2602_startup(struct snd_pcm_substream *substream, return 0; } -static int ssm2602_pcm_prepare(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) -{ - struct snd_soc_pcm_runtime *rtd = substream->private_data; - struct snd_soc_codec *codec = rtd->codec; - /* set active */ - snd_soc_write(codec, SSM2602_ACTIVE, ACTIVE_ACTIVATE_CODEC); - - return 0; -} - static void ssm2602_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { @@ -330,16 +322,13 @@ static void ssm2602_shutdown(struct snd_pcm_substream *substream, struct snd_soc_codec *codec = rtd->codec; struct ssm2602_priv *ssm2602 = snd_soc_codec_get_drvdata(codec); - /* deactivate */ - if (!codec->active) - snd_soc_write(codec, SSM2602_ACTIVE, 0); - if (ssm2602->master_substream == substream) ssm2602->master_substream = ssm2602->slave_substream; ssm2602->slave_substream = NULL; } + static int ssm2602_mute(struct snd_soc_dai *dai, int mute) { struct snd_soc_codec *codec = dai->codec; @@ -446,7 +435,6 @@ static int ssm2602_set_bias_level(struct snd_soc_codec *codec, break; case SND_SOC_BIAS_OFF: /* everything off, dac mute, inactive */ - snd_soc_write(codec, SSM2602_ACTIVE, 0); snd_soc_write(codec, SSM2602_PWR, 0xffff); break; @@ -464,7 +452,6 @@ static int ssm2602_set_bias_level(struct snd_soc_codec *codec, static struct snd_soc_dai_ops ssm2602_dai_ops = { .startup = ssm2602_startup, - .prepare = ssm2602_pcm_prepare, .hw_params = ssm2602_hw_params, .shutdown = ssm2602_shutdown, .digital_mute = ssm2602_mute, From f3eee00da39cba3c8a4db7048458969c620ac6d8 Mon Sep 17 00:00:00 2001 From: Lars-Peter Clausen Date: Sun, 8 May 2011 09:24:46 -0700 Subject: [PATCH 29/29] ASoC: SSM2602: Provide dB ranges for the volume controls Also fix the maximum value for the capture volume control. Signed-off-by: Lars-Peter Clausen Acked-by: Liam Girdwood Signed-off-by: Mark Brown --- sound/soc/codecs/ssm2602.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/sound/soc/codecs/ssm2602.c b/sound/soc/codecs/ssm2602.c index 763d392f12a1..70099c9d63c7 100644 --- a/sound/soc/codecs/ssm2602.c +++ b/sound/soc/codecs/ssm2602.c @@ -40,6 +40,7 @@ #include #include #include +#include #include "ssm2602.h" @@ -87,8 +88,18 @@ static const struct soc_enum ssm2602_enum[] = { SOC_ENUM_SINGLE(SSM2602_APDIGI, 1, 4, ssm2602_deemph), }; +static const unsigned int ssm260x_outmix_tlv[] = { + TLV_DB_RANGE_HEAD(2), + 0, 47, TLV_DB_SCALE_ITEM(TLV_DB_GAIN_MUTE, 0, 0), + 48, 127, TLV_DB_SCALE_ITEM(-7400, 100, 0), +}; + +static const DECLARE_TLV_DB_SCALE(ssm260x_inpga_tlv, -3450, 150, 0); +static const DECLARE_TLV_DB_SCALE(ssm260x_sidetone_tlv, -1500, 300, 0); + static const struct snd_kcontrol_new ssm260x_snd_controls[] = { -SOC_DOUBLE_R("Capture Volume", SSM2602_LINVOL, SSM2602_RINVOL, 0, 31, 0), +SOC_DOUBLE_R_TLV("Capture Volume", SSM2602_LINVOL, SSM2602_RINVOL, 0, 45, 0, + ssm260x_inpga_tlv), SOC_DOUBLE_R("Capture Switch", SSM2602_LINVOL, SSM2602_RINVOL, 7, 1, 1), SOC_SINGLE("ADC High Pass Filter Switch", SSM2602_APDIGI, 0, 1, 1), @@ -98,12 +109,12 @@ SOC_ENUM("Playback De-emphasis", ssm2602_enum[1]), }; static const struct snd_kcontrol_new ssm2602_snd_controls[] = { -SOC_DOUBLE_R("Master Playback Volume", SSM2602_LOUT1V, SSM2602_ROUT1V, - 0, 127, 0), +SOC_DOUBLE_R_TLV("Master Playback Volume", SSM2602_LOUT1V, SSM2602_ROUT1V, + 0, 127, 0, ssm260x_outmix_tlv), SOC_DOUBLE_R("Master Playback ZC Switch", SSM2602_LOUT1V, SSM2602_ROUT1V, 7, 1, 0), - -SOC_SINGLE("Sidetone Playback Volume", SSM2602_APANA, 6, 3, 1), +SOC_SINGLE_TLV("Sidetone Playback Volume", SSM2602_APANA, 6, 3, 1, + ssm260x_sidetone_tlv), SOC_SINGLE("Mic Boost (+20dB)", SSM2602_APANA, 0, 1, 0), SOC_SINGLE("Mic Boost2 (+20dB)", SSM2602_APANA, 8, 1, 0),