From 0038be9a84dc1a4fbea9ddffe32c1cd141843447 Mon Sep 17 00:00:00 2001 From: Mengdong Lin Date: Tue, 26 Jul 2016 14:32:37 +0800 Subject: ASoC: topology: Add support for configuring existing BE DAIs The platform driver may just specify the BE (Back End) DAI name and ID. And topology will find the existing BE DAI by its name and ID, and then configure its stream caps and flags. Signed-off-by: Mengdong Lin Signed-off-by: Mark Brown --- sound/soc/soc-topology.c | 113 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 111 insertions(+), 2 deletions(-) (limited to 'sound') diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index ee7f15aa46fc..05a18f68bfd0 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -48,9 +48,10 @@ #define SOC_TPLG_PASS_PCM_DAI 4 #define SOC_TPLG_PASS_GRAPH 5 #define SOC_TPLG_PASS_PINS 6 +#define SOC_TPLG_PASS_BE_DAI 7 #define SOC_TPLG_PASS_START SOC_TPLG_PASS_MANIFEST -#define SOC_TPLG_PASS_END SOC_TPLG_PASS_PINS +#define SOC_TPLG_PASS_END SOC_TPLG_PASS_BE_DAI struct soc_tplg { const struct firmware *fw; @@ -1556,6 +1557,24 @@ static void set_stream_info(struct snd_soc_pcm_stream *stream, stream->formats = caps->formats; } +static void set_dai_flags(struct snd_soc_dai_driver *dai_drv, + unsigned int flag_mask, unsigned int flags) +{ + if (flag_mask & SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_RATES) + dai_drv->symmetric_rates = + flags & SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_RATES ? 1 : 0; + + if (flag_mask & SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_CHANNELS) + dai_drv->symmetric_channels = + flags & SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_CHANNELS ? + 1 : 0; + + if (flag_mask & SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_SAMPLEBITS) + dai_drv->symmetric_samplebits = + flags & SND_SOC_TPLG_DAI_FLGBIT_SYMMETRIC_SAMPLEBITS ? + 1 : 0; +} + static int soc_tplg_dai_create(struct soc_tplg *tplg, struct snd_soc_tplg_pcm *pcm) { @@ -1690,8 +1709,96 @@ static int soc_tplg_pcm_elems_load(struct soc_tplg *tplg, return 0; } +/* * + * soc_tplg_be_dai_config - Find and configure an existing BE DAI. + * @tplg: topology context + * @be: topology BE DAI configs. + * + * The BE dai should already be registered by the platform driver. The + * platform driver should specify the BE DAI name and ID for matching. + */ +static int soc_tplg_be_dai_config(struct soc_tplg *tplg, + struct snd_soc_tplg_be_dai *be) +{ + struct snd_soc_dai_link_component dai_component = {0}; + struct snd_soc_dai *dai; + struct snd_soc_dai_driver *dai_drv; + struct snd_soc_pcm_stream *stream; + struct snd_soc_tplg_stream_caps *caps; + int ret; + + dai_component.dai_name = be->dai_name; + dai = snd_soc_find_dai(&dai_component); + if (!dai) { + dev_err(tplg->dev, "ASoC: BE DAI %s not registered\n", + be->dai_name); + return -EINVAL; + } + + if (be->dai_id != dai->id) { + dev_err(tplg->dev, "ASoC: BE DAI %s id mismatch\n", + be->dai_name); + return -EINVAL; + } + + dai_drv = dai->driver; + if (!dai_drv) + return -EINVAL; + + if (be->playback) { + stream = &dai_drv->playback; + caps = &be->caps[SND_SOC_TPLG_STREAM_PLAYBACK]; + set_stream_info(stream, caps); + } + + if (be->capture) { + stream = &dai_drv->capture; + caps = &be->caps[SND_SOC_TPLG_STREAM_CAPTURE]; + set_stream_info(stream, caps); + } + + if (be->flag_mask) + set_dai_flags(dai_drv, be->flag_mask, be->flags); + + /* pass control to component driver for optional further init */ + ret = soc_tplg_dai_load(tplg, dai_drv); + if (ret < 0) { + dev_err(tplg->comp->dev, "ASoC: DAI loading failed\n"); + return ret; + } + + return 0; +} + +static int soc_tplg_be_dai_elems_load(struct soc_tplg *tplg, + struct snd_soc_tplg_hdr *hdr) +{ + struct snd_soc_tplg_be_dai *be; + int count = hdr->count; + int i; + + if (tplg->pass != SOC_TPLG_PASS_BE_DAI) + return 0; + + /* config the existing BE DAIs */ + for (i = 0; i < count; i++) { + be = (struct snd_soc_tplg_be_dai *)tplg->pos; + if (be->size != sizeof(*be)) { + dev_err(tplg->dev, "ASoC: invalid BE DAI size\n"); + return -EINVAL; + } + + soc_tplg_be_dai_config(tplg, be); + tplg->pos += (sizeof(*be) + be->priv.size); + } + + dev_dbg(tplg->dev, "ASoC: Configure %d BE DAIs\n", count); + return 0; +} + + static int soc_tplg_manifest_load(struct soc_tplg *tplg, - struct snd_soc_tplg_hdr *hdr) + struct snd_soc_tplg_hdr *hdr) { struct snd_soc_tplg_manifest *manifest; @@ -1793,6 +1900,8 @@ static int soc_tplg_load_header(struct soc_tplg *tplg, return soc_tplg_dapm_widget_elems_load(tplg, hdr); case SND_SOC_TPLG_TYPE_PCM: return soc_tplg_pcm_elems_load(tplg, hdr); + case SND_SOC_TPLG_TYPE_BE_DAI: + return soc_tplg_be_dai_elems_load(tplg, hdr); case SND_SOC_TPLG_TYPE_MANIFEST: return soc_tplg_manifest_load(tplg, hdr); default: -- cgit v1.2.3 From 8ae3ea48df0d746b663057cf0b972a18d0777b7b Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Wed, 10 Aug 2016 13:43:12 +0000 Subject: ASoC: topology: Fix error return code in soc_tplg_dapm_widget_create() Fix to return error code -ENOMEM instead of 0 when failed to create widget, as done elsewhere in this function. Fixes: 8a9782346dcc ("ASoC: topology: Add topology core") Signed-off-by: Wei Yongjun Signed-off-by: Mark Brown --- sound/soc/soc-topology.c | 1 + 1 file changed, 1 insertion(+) (limited to 'sound') diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index 05a18f68bfd0..a9e83a2dd91c 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -1476,6 +1476,7 @@ widget: if (widget == NULL) { dev_err(tplg->dev, "ASoC: failed to create widget %s controls\n", w->name); + ret = -ENOMEM; goto hdr_err; } -- cgit v1.2.3 From 04445681f710eb3a6a263504fa3e6f4199f12d87 Mon Sep 17 00:00:00 2001 From: Marcel Ziswiler Date: Sun, 19 Jun 2016 03:00:00 +0200 Subject: ASoC: tegra: add tegra sgtl5000 machine driver This binding and driver describe/support playback to headphones, and capture from line-in and microphone. This driver is useful for the Toradex Apalis T30, Apalis TK1 and Colibri T30 modules. Signed-off-by: Marcel Ziswiler Reviewed-by: Stephen Warren Signed-off-by: Marcel Ziswiler Acked-by: Rob Herring Signed-off-by: Mark Brown --- sound/soc/tegra/Kconfig | 11 ++ sound/soc/tegra/Makefile | 2 + sound/soc/tegra/tegra_sgtl5000.c | 212 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 225 insertions(+) create mode 100644 sound/soc/tegra/tegra_sgtl5000.c (limited to 'sound') diff --git a/sound/soc/tegra/Kconfig b/sound/soc/tegra/Kconfig index a6768f832c6f..efbe8d4c019e 100644 --- a/sound/soc/tegra/Kconfig +++ b/sound/soc/tegra/Kconfig @@ -138,3 +138,14 @@ config SND_SOC_TEGRA_RT5677 help Say Y or M here if you want to add support for SoC audio on Tegra boards using the RT5677 codec, such as Ryu. + +config SND_SOC_TEGRA_SGTL5000 + tristate "SoC Audio support for Tegra boards using a SGTL5000 codec" + depends on SND_SOC_TEGRA && I2C && GPIOLIB + select SND_SOC_TEGRA20_I2S if ARCH_TEGRA_2x_SOC + select SND_SOC_TEGRA30_I2S if ARCH_TEGRA_3x_SOC + select SND_SOC_SGTL5000 + help + Say Y or M here if you want to add support for SoC audio on Tegra + boards using the SGTL5000 codec, such as Apalis T30, Apalis TK1 or + Colibri T30. diff --git a/sound/soc/tegra/Makefile b/sound/soc/tegra/Makefile index 9171655ad843..f214a3fd0024 100644 --- a/sound/soc/tegra/Makefile +++ b/sound/soc/tegra/Makefile @@ -26,6 +26,7 @@ snd-soc-tegra-wm9712-objs := tegra_wm9712.o snd-soc-tegra-trimslice-objs := trimslice.o snd-soc-tegra-alc5632-objs := tegra_alc5632.o snd-soc-tegra-max98090-objs := tegra_max98090.o +snd-soc-tegra-sgtl5000-objs := tegra_sgtl5000.o obj-$(CONFIG_SND_SOC_TEGRA_RT5640) += snd-soc-tegra-rt5640.o obj-$(CONFIG_SND_SOC_TEGRA_RT5677) += snd-soc-tegra-rt5677.o @@ -35,3 +36,4 @@ obj-$(CONFIG_SND_SOC_TEGRA_WM9712) += snd-soc-tegra-wm9712.o obj-$(CONFIG_SND_SOC_TEGRA_TRIMSLICE) += snd-soc-tegra-trimslice.o obj-$(CONFIG_SND_SOC_TEGRA_ALC5632) += snd-soc-tegra-alc5632.o obj-$(CONFIG_SND_SOC_TEGRA_MAX98090) += snd-soc-tegra-max98090.o +obj-$(CONFIG_SND_SOC_TEGRA_SGTL5000) += snd-soc-tegra-sgtl5000.o \ No newline at end of file diff --git a/sound/soc/tegra/tegra_sgtl5000.c b/sound/soc/tegra/tegra_sgtl5000.c new file mode 100644 index 000000000000..1e76869dd488 --- /dev/null +++ b/sound/soc/tegra/tegra_sgtl5000.c @@ -0,0 +1,212 @@ +/* + * tegra_sgtl5000.c - Tegra machine ASoC driver for boards using SGTL5000 codec + * + * Author: Marcel Ziswiler + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Based on code copyright/by: + * + * Copyright (C) 2010-2012 - NVIDIA, Inc. + * (c) 2009, 2010 Nvidia Graphics Pvt. Ltd. + * Copyright 2007 Wolfson Microelectronics PLC. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "../codecs/sgtl5000.h" + +#include "tegra_asoc_utils.h" + +#define DRV_NAME "tegra-snd-sgtl5000" + +struct tegra_sgtl5000 { + struct tegra_asoc_utils_data util_data; +}; + +static int tegra_sgtl5000_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_card *card = rtd->card; + struct tegra_sgtl5000 *machine = snd_soc_card_get_drvdata(card); + int srate, mclk; + int err; + + srate = params_rate(params); + switch (srate) { + case 11025: + case 22050: + case 44100: + case 88200: + mclk = 11289600; + break; + default: + mclk = 12288000; + break; + } + + err = tegra_asoc_utils_set_rate(&machine->util_data, srate, mclk); + if (err < 0) { + dev_err(card->dev, "Can't configure clocks\n"); + return err; + } + + err = snd_soc_dai_set_sysclk(codec_dai, SGTL5000_SYSCLK, 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 tegra_sgtl5000_ops = { + .hw_params = tegra_sgtl5000_hw_params, +}; + +static const struct snd_soc_dapm_widget tegra_sgtl5000_dapm_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_LINE("Line In Jack", NULL), + SND_SOC_DAPM_MIC("Mic Jack", NULL), +}; + +static struct snd_soc_dai_link tegra_sgtl5000_dai = { + .name = "sgtl5000", + .stream_name = "HiFi", + .codec_dai_name = "sgtl5000", + .ops = &tegra_sgtl5000_ops, + .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | + SND_SOC_DAIFMT_CBS_CFS, +}; + +static struct snd_soc_card snd_soc_tegra_sgtl5000 = { + .name = "tegra-sgtl5000", + .owner = THIS_MODULE, + .dai_link = &tegra_sgtl5000_dai, + .num_links = 1, + .dapm_widgets = tegra_sgtl5000_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(tegra_sgtl5000_dapm_widgets), + .fully_routed = true, +}; + +static int tegra_sgtl5000_driver_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct snd_soc_card *card = &snd_soc_tegra_sgtl5000; + struct tegra_sgtl5000 *machine; + int ret; + + machine = devm_kzalloc(&pdev->dev, sizeof(struct tegra_sgtl5000), + GFP_KERNEL); + if (!machine) { + dev_err(&pdev->dev, "Can't allocate tegra_sgtl5000 struct\n"); + return -ENOMEM; + } + + card->dev = &pdev->dev; + platform_set_drvdata(pdev, card); + snd_soc_card_set_drvdata(card, machine); + + ret = snd_soc_of_parse_card_name(card, "nvidia,model"); + if (ret) + goto err; + + ret = snd_soc_of_parse_audio_routing(card, "nvidia,audio-routing"); + if (ret) + goto err; + + tegra_sgtl5000_dai.codec_of_node = of_parse_phandle(np, + "nvidia,audio-codec", 0); + if (!tegra_sgtl5000_dai.codec_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,audio-codec' missing or invalid\n"); + ret = -EINVAL; + goto err; + } + + tegra_sgtl5000_dai.cpu_of_node = of_parse_phandle(np, + "nvidia,i2s-controller", 0); + if (!tegra_sgtl5000_dai.cpu_of_node) { + dev_err(&pdev->dev, + "Property 'nvidia,i2s-controller' missing/invalid\n"); + ret = -EINVAL; + goto err; + } + + tegra_sgtl5000_dai.platform_of_node = tegra_sgtl5000_dai.cpu_of_node; + + ret = tegra_asoc_utils_init(&machine->util_data, &pdev->dev); + if (ret) + goto err; + + 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(&machine->util_data); +err: + return ret; +} + +static int tegra_sgtl5000_driver_remove(struct platform_device *pdev) +{ + struct snd_soc_card *card = platform_get_drvdata(pdev); + struct tegra_sgtl5000 *machine = snd_soc_card_get_drvdata(card); + int ret; + + ret = snd_soc_unregister_card(card); + + tegra_asoc_utils_fini(&machine->util_data); + + return ret; +} + +static const struct of_device_id tegra_sgtl5000_of_match[] = { + { .compatible = "nvidia,tegra-audio-sgtl5000", }, + { /* sentinel */ }, +}; + +static struct platform_driver tegra_sgtl5000_driver = { + .driver = { + .name = DRV_NAME, + .pm = &snd_soc_pm_ops, + .of_match_table = tegra_sgtl5000_of_match, + }, + .probe = tegra_sgtl5000_driver_probe, + .remove = tegra_sgtl5000_driver_remove, +}; +module_platform_driver(tegra_sgtl5000_driver); + +MODULE_AUTHOR("Marcel Ziswiler "); +MODULE_DESCRIPTION("Tegra SGTL5000 machine ASoC driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DEVICE_TABLE(of, tegra_sgtl5000_of_match); -- cgit v1.2.3 From f918e1697b1a8f2f26a4813db053cfbcafc48046 Mon Sep 17 00:00:00 2001 From: Mengdong Lin Date: Fri, 19 Aug 2016 18:12:46 +0800 Subject: ASoC: topology: ABI - Add sig_bits to stream caps Kernel struct snd_soc_pcm_stream, SoC PCM stream information, needs this field. Although current topology users don't configure this, we define it for future extension. Signed-off-by: Mengdong Lin Signed-off-by: Mark Brown --- sound/soc/soc-topology.c | 1 + 1 file changed, 1 insertion(+) (limited to 'sound') diff --git a/sound/soc/soc-topology.c b/sound/soc/soc-topology.c index a9e83a2dd91c..6b05047a4134 100644 --- a/sound/soc/soc-topology.c +++ b/sound/soc/soc-topology.c @@ -1556,6 +1556,7 @@ static void set_stream_info(struct snd_soc_pcm_stream *stream, stream->rate_min = caps->rate_min; stream->rate_max = caps->rate_max; stream->formats = caps->formats; + stream->sig_bits = caps->sig_bits; } static void set_dai_flags(struct snd_soc_dai_driver *dai_drv, -- cgit v1.2.3 From e56375155e95019cd4abc55d30c2c1a415037e27 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Wed, 24 Aug 2016 07:48:06 +0100 Subject: ASoC: tas5086: fix typo: "Inavlid" -> "Invalid" trivial typo fix in dev_err message Signed-off-by: Colin Ian King Signed-off-by: Mark Brown --- sound/soc/codecs/tas5086.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/codecs/tas5086.c b/sound/soc/codecs/tas5086.c index d49d25d51957..1666ea697647 100644 --- a/sound/soc/codecs/tas5086.c +++ b/sound/soc/codecs/tas5086.c @@ -387,7 +387,7 @@ static int tas5086_hw_params(struct snd_pcm_substream *substream, val = index_in_array(tas5086_ratios, ARRAY_SIZE(tas5086_ratios), priv->mclk / priv->rate); if (val < 0) { - dev_err(codec->dev, "Inavlid MCLK / Fs ratio\n"); + dev_err(codec->dev, "Invalid MCLK / Fs ratio\n"); return -EINVAL; } -- cgit v1.2.3 From c614a31287033945478053cd060c3c803d7bc94f Mon Sep 17 00:00:00 2001 From: Nicolin Chen Date: Wed, 24 Aug 2016 17:26:15 -0700 Subject: ASoC: tegra_rt5640: Correct a copy and paste typo in the comments This patch corrects a copy and paste typo. Signed-off-by: Nicolin Chen Signed-off-by: Mark Brown --- sound/soc/tegra/tegra_rt5640.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'sound') diff --git a/sound/soc/tegra/tegra_rt5640.c b/sound/soc/tegra/tegra_rt5640.c index 773daecaa5e8..e5ef4e9c4ac5 100644 --- a/sound/soc/tegra/tegra_rt5640.c +++ b/sound/soc/tegra/tegra_rt5640.c @@ -1,5 +1,5 @@ /* -* tegra_rt5640.c - Tegra machine ASoC driver for boards using WM8903 codec. +* tegra_rt5640.c - Tegra machine ASoC driver for boards using RT5640 codec. * * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. * -- cgit v1.2.3 From 8891098246d07e6dda964a0cffbd504de566c4c3 Mon Sep 17 00:00:00 2001 From: Bhaktipriya Shridhar Date: Sun, 4 Sep 2016 21:27:32 +0530 Subject: ASoC: tlv320dac33: Remove deprecated create_singlethread_workqueue The workqueue "dac33_wq" queues a single work item &dac33->work and hence doesn't require ordering. Also, it is not being used on a memory reclaim path. Hence, it has been converted to use system_wq. System workqueues have been able to handle high level of concurrency for a long time now and hence it's not required to have a singlethreaded workqueue just to gain concurrency. Unlike a dedicated per-cpu workqueue created with create_singlethread_workqueue(), system_wq allows multiple work items to overlap executions even on the same CPU; however, a per-cpu workqueue doesn't have any CPU locality or global ordering guarantee unless the target CPU is explicitly specified and thus the increase of local concurrency shouldn't make any difference. The work item has been flushed in dac33_soc_remove to ensure that there are no pending tasks while disconnecting the driver. Signed-off-by: Bhaktipriya Shridhar Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320dac33.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/tlv320dac33.c b/sound/soc/codecs/tlv320dac33.c index f7a6ce7e5fb1..6822ac1c25b0 100644 --- a/sound/soc/codecs/tlv320dac33.c +++ b/sound/soc/codecs/tlv320dac33.c @@ -90,7 +90,6 @@ static const char *dac33_supply_names[DAC33_NUM_SUPPLIES] = { struct tlv320dac33_priv { struct mutex mutex; - struct workqueue_struct *dac33_wq; struct work_struct work; struct snd_soc_codec *codec; struct regulator_bulk_data supplies[DAC33_NUM_SUPPLIES]; @@ -771,7 +770,7 @@ static irqreturn_t dac33_interrupt_handler(int irq, void *dev) /* Do not schedule the workqueue in Mode7 */ if (dac33->fifo_mode != DAC33_FIFO_MODE7) - queue_work(dac33->dac33_wq, &dac33->work); + schedule_work(&dac33->work); return IRQ_HANDLED; } @@ -1127,7 +1126,7 @@ static int dac33_pcm_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: if (dac33->fifo_mode) { dac33->state = DAC33_PREFILL; - queue_work(dac33->dac33_wq, &dac33->work); + schedule_work(&dac33->work); } break; case SNDRV_PCM_TRIGGER_STOP: @@ -1135,7 +1134,7 @@ static int dac33_pcm_trigger(struct snd_pcm_substream *substream, int cmd, case SNDRV_PCM_TRIGGER_PAUSE_PUSH: if (dac33->fifo_mode) { dac33->state = DAC33_FLUSH; - queue_work(dac33->dac33_wq, &dac33->work); + schedule_work(&dac33->work); } break; default: @@ -1410,14 +1409,6 @@ static int dac33_soc_probe(struct snd_soc_codec *codec) dac33->irq = -1; } if (dac33->irq != -1) { - /* Setup work queue */ - dac33->dac33_wq = - create_singlethread_workqueue("tlv320dac33"); - if (dac33->dac33_wq == NULL) { - free_irq(dac33->irq, codec); - return -ENOMEM; - } - INIT_WORK(&dac33->work, dac33_work); } } @@ -1437,7 +1428,7 @@ static int dac33_soc_remove(struct snd_soc_codec *codec) if (dac33->irq >= 0) { free_irq(dac33->irq, dac33->codec); - destroy_workqueue(dac33->dac33_wq); + flush_work(&dac33->work); } return 0; } -- cgit v1.2.3 From ef9656b6936fb7f66e7e25d284c955f4893ac421 Mon Sep 17 00:00:00 2001 From: Nikita Yushchenko Date: Fri, 23 Sep 2016 14:52:52 +0300 Subject: ASoC: tlv320aic31xx: add explicit support for tlv320dac31xx tlv320dac31xx is a subset of tlv320aic31xx: - it does not have MIC inputs and ADC, thus capture is not supported, - it has analog inputs AIN1/AIN2 that can be mixed into output. Although tlv320dac31xx does work with tlv320aic31xx driver, this setup does register non-existent widgets and non-existent capture stream. Thus userspace lists non-existent objects in user interfaces, an can access these, causing operations with device registers that are declared as "reserved" in tlv320dac31xx datasheet. This patch fixes this situation by separating controls/widgets/routes into common, aic31xx-specific, and dac31xx-specific parts. Only parts that match actual hardware (as declared in "compatible" device tree property) are registered. Changes from v1: - update device tree binding documentation, - rebased on top of "ASoC: codec duplicated callback function goes to component on tlv320aic31xx" commit. Signed-off-by: Nikita Yushchenko Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic31xx.c | 212 ++++++++++++++++++++++++++++----------- sound/soc/codecs/tlv320aic31xx.h | 2 + 2 files changed, 158 insertions(+), 56 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c index e46fb472e48d..725173b12725 100644 --- a/sound/soc/codecs/tlv320aic31xx.c +++ b/sound/soc/codecs/tlv320aic31xx.c @@ -273,10 +273,20 @@ static const DECLARE_TLV_DB_SCALE(sp_vol_tlv, -6350, 50, 0); /* * controls to be exported to the user space */ -static const struct snd_kcontrol_new aic31xx_snd_controls[] = { +static const struct snd_kcontrol_new common31xx_snd_controls[] = { SOC_DOUBLE_R_S_TLV("DAC Playback Volume", AIC31XX_LDACVOL, AIC31XX_RDACVOL, 0, -127, 48, 7, 0, dac_vol_tlv), + SOC_DOUBLE_R("HP Driver Playback Switch", AIC31XX_HPLGAIN, + AIC31XX_HPRGAIN, 2, 1, 0), + SOC_DOUBLE_R_TLV("HP Driver Playback Volume", AIC31XX_HPLGAIN, + AIC31XX_HPRGAIN, 3, 0x09, 0, hp_drv_tlv), + + SOC_DOUBLE_R_TLV("HP Analog Playback Volume", AIC31XX_LANALOGHPL, + AIC31XX_RANALOGHPR, 0, 0x7F, 1, hp_vol_tlv), +}; + +static const struct snd_kcontrol_new aic31xx_snd_controls[] = { SOC_SINGLE_TLV("ADC Fine Capture Volume", AIC31XX_ADCFGA, 4, 4, 1, adc_fgain_tlv), @@ -286,14 +296,6 @@ static const struct snd_kcontrol_new aic31xx_snd_controls[] = { SOC_SINGLE_TLV("Mic PGA Capture Volume", AIC31XX_MICPGA, 0, 119, 0, mic_pga_tlv), - - SOC_DOUBLE_R("HP Driver Playback Switch", AIC31XX_HPLGAIN, - AIC31XX_HPRGAIN, 2, 1, 0), - SOC_DOUBLE_R_TLV("HP Driver Playback Volume", AIC31XX_HPLGAIN, - AIC31XX_HPRGAIN, 3, 0x09, 0, hp_drv_tlv), - - SOC_DOUBLE_R_TLV("HP Analog Playback Volume", AIC31XX_LANALOGHPL, - AIC31XX_RANALOGHPR, 0, 0x7F, 1, hp_vol_tlv), }; static const struct snd_kcontrol_new aic311x_snd_controls[] = { @@ -397,17 +399,28 @@ static int aic31xx_dapm_power_event(struct snd_soc_dapm_widget *w, return 0; } -static const struct snd_kcontrol_new left_output_switches[] = { +static const struct snd_kcontrol_new aic31xx_left_output_switches[] = { SOC_DAPM_SINGLE("From Left DAC", AIC31XX_DACMIXERROUTE, 6, 1, 0), SOC_DAPM_SINGLE("From MIC1LP", AIC31XX_DACMIXERROUTE, 5, 1, 0), SOC_DAPM_SINGLE("From MIC1RP", AIC31XX_DACMIXERROUTE, 4, 1, 0), }; -static const struct snd_kcontrol_new right_output_switches[] = { +static const struct snd_kcontrol_new aic31xx_right_output_switches[] = { SOC_DAPM_SINGLE("From Right DAC", AIC31XX_DACMIXERROUTE, 2, 1, 0), SOC_DAPM_SINGLE("From MIC1RP", AIC31XX_DACMIXERROUTE, 1, 1, 0), }; +static const struct snd_kcontrol_new dac31xx_left_output_switches[] = { + SOC_DAPM_SINGLE("From Left DAC", AIC31XX_DACMIXERROUTE, 6, 1, 0), + SOC_DAPM_SINGLE("From AIN1", AIC31XX_DACMIXERROUTE, 5, 1, 0), + SOC_DAPM_SINGLE("From AIN2", AIC31XX_DACMIXERROUTE, 4, 1, 0), +}; + +static const struct snd_kcontrol_new dac31xx_right_output_switches[] = { + SOC_DAPM_SINGLE("From Right DAC", AIC31XX_DACMIXERROUTE, 2, 1, 0), + SOC_DAPM_SINGLE("From AIN2", AIC31XX_DACMIXERROUTE, 1, 1, 0), +}; + static const struct snd_kcontrol_new p_term_mic1lp = SOC_DAPM_ENUM("MIC1LP P-Terminal", mic1lp_p_enum); @@ -457,7 +470,7 @@ static int mic_bias_event(struct snd_soc_dapm_widget *w, return 0; } -static const struct snd_soc_dapm_widget aic31xx_dapm_widgets[] = { +static const struct snd_soc_dapm_widget common31xx_dapm_widgets[] = { SND_SOC_DAPM_AIF_IN("DAC IN", "DAC Playback", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_MUX("DAC Left Input", @@ -473,14 +486,7 @@ static const struct snd_soc_dapm_widget aic31xx_dapm_widgets[] = { AIC31XX_DACSETUP, 6, 0, aic31xx_dapm_power_event, SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_POST_PMD), - /* Output Mixers */ - SND_SOC_DAPM_MIXER("Output Left", SND_SOC_NOPM, 0, 0, - left_output_switches, - ARRAY_SIZE(left_output_switches)), - SND_SOC_DAPM_MIXER("Output Right", SND_SOC_NOPM, 0, 0, - right_output_switches, - ARRAY_SIZE(right_output_switches)), - + /* HP */ SND_SOC_DAPM_SWITCH("HP Left", SND_SOC_NOPM, 0, 0, &aic31xx_dapm_hpl_switch), SND_SOC_DAPM_SWITCH("HP Right", SND_SOC_NOPM, 0, 0, @@ -494,10 +500,34 @@ static const struct snd_soc_dapm_widget aic31xx_dapm_widgets[] = { NULL, 0, aic31xx_dapm_power_event, SND_SOC_DAPM_POST_PMD | SND_SOC_DAPM_POST_PMU), - /* ADC */ - SND_SOC_DAPM_ADC_E("ADC", "Capture", AIC31XX_ADCSETUP, 7, 0, - aic31xx_dapm_power_event, SND_SOC_DAPM_POST_PMU | - SND_SOC_DAPM_POST_PMD), + /* Mic Bias */ + SND_SOC_DAPM_SUPPLY("MICBIAS", SND_SOC_NOPM, 0, 0, mic_bias_event, + SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), + + /* Outputs */ + SND_SOC_DAPM_OUTPUT("HPL"), + SND_SOC_DAPM_OUTPUT("HPR"), +}; + +static const struct snd_soc_dapm_widget dac31xx_dapm_widgets[] = { + /* Inputs */ + SND_SOC_DAPM_INPUT("AIN1"), + SND_SOC_DAPM_INPUT("AIN2"), + + /* Output Mixers */ + SND_SOC_DAPM_MIXER("Output Left", SND_SOC_NOPM, 0, 0, + dac31xx_left_output_switches, + ARRAY_SIZE(dac31xx_left_output_switches)), + SND_SOC_DAPM_MIXER("Output Right", SND_SOC_NOPM, 0, 0, + dac31xx_right_output_switches, + ARRAY_SIZE(dac31xx_right_output_switches)), +}; + +static const struct snd_soc_dapm_widget aic31xx_dapm_widgets[] = { + /* Inputs */ + SND_SOC_DAPM_INPUT("MIC1LP"), + SND_SOC_DAPM_INPUT("MIC1RP"), + SND_SOC_DAPM_INPUT("MIC1LM"), /* Input Selection to MIC_PGA */ SND_SOC_DAPM_MUX("MIC1LP P-Terminal", SND_SOC_NOPM, 0, 0, @@ -507,24 +537,25 @@ static const struct snd_soc_dapm_widget aic31xx_dapm_widgets[] = { SND_SOC_DAPM_MUX("MIC1LM P-Terminal", SND_SOC_NOPM, 0, 0, &p_term_mic1lm), + /* ADC */ + SND_SOC_DAPM_ADC_E("ADC", "Capture", AIC31XX_ADCSETUP, 7, 0, + aic31xx_dapm_power_event, SND_SOC_DAPM_POST_PMU | + SND_SOC_DAPM_POST_PMD), + SND_SOC_DAPM_MUX("MIC1LM M-Terminal", SND_SOC_NOPM, 0, 0, &m_term_mic1lm), + /* Enabling & Disabling MIC Gain Ctl */ SND_SOC_DAPM_PGA("MIC_GAIN_CTL", AIC31XX_MICPGA, 7, 1, NULL, 0), - /* Mic Bias */ - SND_SOC_DAPM_SUPPLY("MICBIAS", SND_SOC_NOPM, 0, 0, mic_bias_event, - SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD), - - /* Outputs */ - SND_SOC_DAPM_OUTPUT("HPL"), - SND_SOC_DAPM_OUTPUT("HPR"), - - /* Inputs */ - SND_SOC_DAPM_INPUT("MIC1LP"), - SND_SOC_DAPM_INPUT("MIC1RP"), - SND_SOC_DAPM_INPUT("MIC1LM"), + /* Output Mixers */ + SND_SOC_DAPM_MIXER("Output Left", SND_SOC_NOPM, 0, 0, + aic31xx_left_output_switches, + ARRAY_SIZE(aic31xx_left_output_switches)), + SND_SOC_DAPM_MIXER("Output Right", SND_SOC_NOPM, 0, 0, + aic31xx_right_output_switches, + ARRAY_SIZE(aic31xx_right_output_switches)), }; static const struct snd_soc_dapm_widget aic311x_dapm_widgets[] = { @@ -554,7 +585,7 @@ static const struct snd_soc_dapm_widget aic310x_dapm_widgets[] = { }; static const struct snd_soc_dapm_route -aic31xx_audio_map[] = { +common31xx_audio_map[] = { /* DAC Input Routing */ {"DAC Left Input", "Left Data", "DAC IN"}, {"DAC Left Input", "Right Data", "DAC IN"}, @@ -565,6 +596,31 @@ aic31xx_audio_map[] = { {"DAC Left", NULL, "DAC Left Input"}, {"DAC Right", NULL, "DAC Right Input"}, + /* HPL path */ + {"HP Left", "Switch", "Output Left"}, + {"HPL Driver", NULL, "HP Left"}, + {"HPL", NULL, "HPL Driver"}, + + /* HPR path */ + {"HP Right", "Switch", "Output Right"}, + {"HPR Driver", NULL, "HP Right"}, + {"HPR", NULL, "HPR Driver"}, +}; + +static const struct snd_soc_dapm_route +dac31xx_audio_map[] = { + /* Left Output */ + {"Output Left", "From Left DAC", "DAC Left"}, + {"Output Left", "From AIN1", "AIN1"}, + {"Output Left", "From AIN2", "AIN2"}, + + /* Right Output */ + {"Output Right", "From Right DAC", "DAC Right"}, + {"Output Right", "From AIN2", "AIN2"}, +}; + +static const struct snd_soc_dapm_route +aic31xx_audio_map[] = { /* Mic input */ {"MIC1LP P-Terminal", "FFR 10 Ohm", "MIC1LP"}, {"MIC1LP P-Terminal", "FFR 20 Ohm", "MIC1LP"}, @@ -595,16 +651,6 @@ aic31xx_audio_map[] = { /* Right Output */ {"Output Right", "From Right DAC", "DAC Right"}, {"Output Right", "From MIC1RP", "MIC1RP"}, - - /* HPL path */ - {"HP Left", "Switch", "Output Left"}, - {"HPL Driver", NULL, "HP Left"}, - {"HPL", NULL, "HPL Driver"}, - - /* HPR path */ - {"HP Right", "Switch", "Output Right"}, - {"HPR Driver", NULL, "HP Right"}, - {"HPR", NULL, "HPR Driver"}, }; static const struct snd_soc_dapm_route @@ -633,6 +679,13 @@ static int aic31xx_add_controls(struct snd_soc_codec *codec) int ret = 0; struct aic31xx_priv *aic31xx = snd_soc_codec_get_drvdata(codec); + if (!(aic31xx->pdata.codec_type & DAC31XX_BIT)) + ret = snd_soc_add_codec_controls( + codec, aic31xx_snd_controls, + ARRAY_SIZE(aic31xx_snd_controls)); + if (ret) + return ret; + if (aic31xx->pdata.codec_type & AIC31XX_STEREO_CLASS_D_BIT) ret = snd_soc_add_codec_controls( codec, aic311x_snd_controls, @@ -651,6 +704,30 @@ static int aic31xx_add_widgets(struct snd_soc_codec *codec) struct aic31xx_priv *aic31xx = snd_soc_codec_get_drvdata(codec); int ret = 0; + if (aic31xx->pdata.codec_type & DAC31XX_BIT) { + ret = snd_soc_dapm_new_controls( + dapm, dac31xx_dapm_widgets, + ARRAY_SIZE(dac31xx_dapm_widgets)); + if (ret) + return ret; + + ret = snd_soc_dapm_add_routes(dapm, dac31xx_audio_map, + ARRAY_SIZE(dac31xx_audio_map)); + if (ret) + return ret; + } else { + ret = snd_soc_dapm_new_controls( + dapm, aic31xx_dapm_widgets, + ARRAY_SIZE(aic31xx_dapm_widgets)); + if (ret) + return ret; + + ret = snd_soc_dapm_add_routes(dapm, aic31xx_audio_map, + ARRAY_SIZE(aic31xx_audio_map)); + if (ret) + return ret; + } + if (aic31xx->pdata.codec_type & AIC31XX_STEREO_CLASS_D_BIT) { ret = snd_soc_dapm_new_controls( dapm, aic311x_dapm_widgets, @@ -1115,12 +1192,12 @@ static struct snd_soc_codec_driver soc_codec_driver_aic31xx = { .suspend_bias_off = true, .component_driver = { - .controls = aic31xx_snd_controls, - .num_controls = ARRAY_SIZE(aic31xx_snd_controls), - .dapm_widgets = aic31xx_dapm_widgets, - .num_dapm_widgets = ARRAY_SIZE(aic31xx_dapm_widgets), - .dapm_routes = aic31xx_audio_map, - .num_dapm_routes = ARRAY_SIZE(aic31xx_audio_map), + .controls = common31xx_snd_controls, + .num_controls = ARRAY_SIZE(common31xx_snd_controls), + .dapm_widgets = common31xx_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(common31xx_dapm_widgets), + .dapm_routes = common31xx_audio_map, + .num_dapm_routes = ARRAY_SIZE(common31xx_audio_map), }, }; @@ -1131,6 +1208,21 @@ static const struct snd_soc_dai_ops aic31xx_dai_ops = { .digital_mute = aic31xx_dac_mute, }; +static struct snd_soc_dai_driver dac31xx_dai_driver[] = { + { + .name = "tlv32dac31xx-hifi", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 2, + .rates = AIC31XX_RATES, + .formats = AIC31XX_FORMATS, + }, + .ops = &aic31xx_dai_ops, + .symmetric_rates = 1, + } +}; + static struct snd_soc_dai_driver aic31xx_dai_driver[] = { { .name = "tlv320aic31xx-hifi", @@ -1261,9 +1353,16 @@ static int aic31xx_i2c_probe(struct i2c_client *i2c, if (ret) return ret; - return snd_soc_register_codec(&i2c->dev, &soc_codec_driver_aic31xx, - aic31xx_dai_driver, - ARRAY_SIZE(aic31xx_dai_driver)); + if (aic31xx->pdata.codec_type & DAC31XX_BIT) + return snd_soc_register_codec(&i2c->dev, + &soc_codec_driver_aic31xx, + dac31xx_dai_driver, + ARRAY_SIZE(dac31xx_dai_driver)); + else + return snd_soc_register_codec(&i2c->dev, + &soc_codec_driver_aic31xx, + aic31xx_dai_driver, + ARRAY_SIZE(aic31xx_dai_driver)); } static int aic31xx_i2c_remove(struct i2c_client *i2c) @@ -1279,6 +1378,7 @@ static const struct i2c_device_id aic31xx_i2c_id[] = { { "tlv320aic3110", AIC3110 }, { "tlv320aic3120", AIC3120 }, { "tlv320aic3111", AIC3111 }, + { "tlv320dac3100", DAC3100 }, { } }; MODULE_DEVICE_TABLE(i2c, aic31xx_i2c_id); diff --git a/sound/soc/codecs/tlv320aic31xx.h b/sound/soc/codecs/tlv320aic31xx.h index ac9b146526eb..5acd5b69fb83 100644 --- a/sound/soc/codecs/tlv320aic31xx.h +++ b/sound/soc/codecs/tlv320aic31xx.h @@ -24,12 +24,14 @@ #define AIC31XX_STEREO_CLASS_D_BIT 0x1 #define AIC31XX_MINIDSP_BIT 0x2 +#define DAC31XX_BIT 0x4 enum aic31xx_type { AIC3100 = 0, AIC3110 = AIC31XX_STEREO_CLASS_D_BIT, AIC3120 = AIC31XX_MINIDSP_BIT, AIC3111 = (AIC31XX_STEREO_CLASS_D_BIT | AIC31XX_MINIDSP_BIT), + DAC3100 = DAC31XX_BIT, }; struct aic31xx_pdata { -- cgit v1.2.3 From 3520646dbb22832fa65dae6899d8cb068257d4aa Mon Sep 17 00:00:00 2001 From: Nikita Yushchenko Date: Tue, 27 Sep 2016 11:30:15 +0300 Subject: ASoC: tlv320aic31xx: do not declare support for mono DAI This hardware supports only 2-channel DAI, even mono ADC digital output has two channels with the same data. Having min_channels=1 results in broken playback of mono files in setups where CPU DAI supports mono. Signed-off-by: Nikita Yushchenko Signed-off-by: Mark Brown --- sound/soc/codecs/tlv320aic31xx.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'sound') diff --git a/sound/soc/codecs/tlv320aic31xx.c b/sound/soc/codecs/tlv320aic31xx.c index 725173b12725..be1a64bfd320 100644 --- a/sound/soc/codecs/tlv320aic31xx.c +++ b/sound/soc/codecs/tlv320aic31xx.c @@ -1213,7 +1213,7 @@ static struct snd_soc_dai_driver dac31xx_dai_driver[] = { .name = "tlv32dac31xx-hifi", .playback = { .stream_name = "Playback", - .channels_min = 1, + .channels_min = 2, .channels_max = 2, .rates = AIC31XX_RATES, .formats = AIC31XX_FORMATS, @@ -1228,14 +1228,14 @@ static struct snd_soc_dai_driver aic31xx_dai_driver[] = { .name = "tlv320aic31xx-hifi", .playback = { .stream_name = "Playback", - .channels_min = 1, + .channels_min = 2, .channels_max = 2, .rates = AIC31XX_RATES, .formats = AIC31XX_FORMATS, }, .capture = { .stream_name = "Capture", - .channels_min = 1, + .channels_min = 2, .channels_max = 2, .rates = AIC31XX_RATES, .formats = AIC31XX_FORMATS, -- cgit v1.2.3