diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2018-02-07 12:11:09 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-02-07 12:11:09 -0800 |
commit | 7590e37bdaeec25ae325f4ba450be13e2aac6c8d (patch) | |
tree | 8249846959a09148a467269c65756d4c278d4f07 /sound/soc | |
parent | 7e6127c1240ed569cdda2a67c8f03836f9f28c05 (diff) | |
parent | 3d0a352a552e6cdf052e8baf66f1fe6418c122fe (diff) | |
download | op-kernel-dev-7590e37bdaeec25ae325f4ba450be13e2aac6c8d.zip op-kernel-dev-7590e37bdaeec25ae325f4ba450be13e2aac6c8d.tar.gz |
Merge tag 'asoc-v4.16-5' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound
Pull more ASoC updates from Mark Brown:
"With the merge window having been delayed for another week here's
another batch of updates that came in during that week.
There's a few important fixes in here, mainly a fix for I/O on a
number of devices caused by some of the component rework and a fix for
a potential issue if more than one component in a link provides
compressed operations. The I/O fixes are particularly important as the
problem causes a power regression on a number of OMAP platforms"
* tag 'asoc-v4.16-5' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound: (22 commits)
ASoC: stm32: add of dependency for stm32 drivers
ASoC: mt8173-rt5650: fix child-node lookup
ASoC: dapm: fix debugfs read using path->connected
ASoC: compress: Fixup error messages
ASoC: compress: Remove some extraneous blank lines
ASoC: compress: Correct handling of copy callback
ASoC: Intel: kbl: Enable mclk and ssp sclk early
ASoC: Intel: Skylake: Add extended I2S config blob support in Clock driver
ASoC: Intel: Skylake: Add ssp clock driver
ASoC: Fix twl4030 and 6040 regression by adding back read and write
ASoC: sun8i-codec: Add ADC support for a33
ASoC: rockchip: Use dummy_dai for rt5514 dsp dailink
ASoC: soc-pcm: rename .pmdown_time to .use_pmdown_time for Component
ASoC: ak4613: call dummy write for PW_MGMT1/3 when Playback
ASoC: soc-pcm: don't call flush_delayed_work() many times in soc_pcm_private_free()
ASoC: soc-core: snd_soc_rtdcom_lookup() cares component driver name
ASoC: sam9x5_wm8731: Drop 'ASoC' prefix from error messages
ASoC: sam9g20_wm8731: use dev_*() logging functions
ASoC: max98373 Changed SPDX header in C++ comments style
ASoC: dmic: Fix check of return value from read of 'num-channels'
...
Diffstat (limited to 'sound/soc')
28 files changed, 918 insertions, 106 deletions
diff --git a/sound/soc/atmel/sam9g20_wm8731.c b/sound/soc/atmel/sam9g20_wm8731.c index d7469cd..98f93e7 100644 --- a/sound/soc/atmel/sam9g20_wm8731.c +++ b/sound/soc/atmel/sam9g20_wm8731.c @@ -110,16 +110,15 @@ static const struct snd_soc_dapm_route intercon[] = { static int at91sam9g20ek_wm8731_init(struct snd_soc_pcm_runtime *rtd) { struct snd_soc_dai *codec_dai = rtd->codec_dai; + struct device *dev = rtd->dev; int ret; - printk(KERN_DEBUG - "at91sam9g20ek_wm8731 " - ": at91sam9g20ek_wm8731_init() called\n"); + dev_dbg(dev, "%s called\n", __func__); ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_MCLK, - MCLK_RATE, SND_SOC_CLOCK_IN); + MCLK_RATE, SND_SOC_CLOCK_IN); if (ret < 0) { - printk(KERN_ERR "Failed to set WM8731 SYSCLK: %d\n", ret); + dev_err(dev, "Failed to set WM8731 SYSCLK: %d\n", ret); return ret; } @@ -179,21 +178,21 @@ static int at91sam9g20ek_audio_probe(struct platform_device *pdev) */ mclk = clk_get(NULL, "pck0"); if (IS_ERR(mclk)) { - printk(KERN_ERR "ASoC: Failed to get MCLK\n"); + dev_err(&pdev->dev, "Failed to get MCLK\n"); ret = PTR_ERR(mclk); goto err; } pllb = clk_get(NULL, "pllb"); if (IS_ERR(pllb)) { - printk(KERN_ERR "ASoC: Failed to get PLLB\n"); + dev_err(&pdev->dev, "Failed to get PLLB\n"); ret = PTR_ERR(pllb); goto err_mclk; } ret = clk_set_parent(mclk, pllb); clk_put(pllb); if (ret != 0) { - printk(KERN_ERR "ASoC: Failed to set MCLK parent\n"); + dev_err(&pdev->dev, "Failed to set MCLK parent\n"); goto err_mclk; } @@ -236,7 +235,7 @@ static int at91sam9g20ek_audio_probe(struct platform_device *pdev) ret = snd_soc_register_card(card); if (ret) { - printk(KERN_ERR "ASoC: snd_soc_register_card() failed\n"); + dev_err(&pdev->dev, "snd_soc_register_card() failed\n"); } return ret; diff --git a/sound/soc/atmel/sam9x5_wm8731.c b/sound/soc/atmel/sam9x5_wm8731.c index ccdf547..e6c303a 100644 --- a/sound/soc/atmel/sam9x5_wm8731.c +++ b/sound/soc/atmel/sam9x5_wm8731.c @@ -49,13 +49,13 @@ static int sam9x5_wm8731_init(struct snd_soc_pcm_runtime *rtd) struct device *dev = rtd->dev; int ret; - dev_dbg(dev, "ASoC: %s called\n", __func__); + dev_dbg(dev, "%s called\n", __func__); /* set the codec system clock for DAC and ADC */ ret = snd_soc_dai_set_sysclk(codec_dai, WM8731_SYSCLK_XTAL, MCLK_RATE, SND_SOC_CLOCK_IN); if (ret < 0) { - dev_err(dev, "ASoC: Failed to set WM8731 SYSCLK: %d\n", ret); + dev_err(dev, "Failed to set WM8731 SYSCLK: %d\n", ret); return ret; } @@ -146,8 +146,7 @@ static int sam9x5_wm8731_driver_probe(struct platform_device *pdev) ret = atmel_ssc_set_audio(priv->ssc_id); if (ret != 0) { - dev_err(&pdev->dev, - "ASoC: Failed to set SSC %d for audio: %d\n", + dev_err(&pdev->dev, "Failed to set SSC %d for audio: %d\n", ret, priv->ssc_id); goto out; } @@ -157,12 +156,11 @@ static int sam9x5_wm8731_driver_probe(struct platform_device *pdev) ret = snd_soc_register_card(card); if (ret) { - dev_err(&pdev->dev, - "ASoC: Platform device allocation failed\n"); + dev_err(&pdev->dev, "Platform device allocation failed\n"); goto out_put_audio; } - dev_dbg(&pdev->dev, "ASoC: %s ok\n", __func__); + dev_dbg(&pdev->dev, "%s ok\n", __func__); return ret; diff --git a/sound/soc/codecs/ak4613.c b/sound/soc/codecs/ak4613.c index b95bb8b..3d1cf47 100644 --- a/sound/soc/codecs/ak4613.c +++ b/sound/soc/codecs/ak4613.c @@ -15,6 +15,7 @@ */ #include <linux/clk.h> +#include <linux/delay.h> #include <linux/i2c.h> #include <linux/slab.h> #include <linux/of_device.h> @@ -95,6 +96,9 @@ struct ak4613_priv { struct mutex lock; const struct ak4613_interface *iface; struct snd_pcm_hw_constraint_list constraint; + struct work_struct dummy_write_work; + struct snd_soc_component *component; + unsigned int rate; unsigned int sysclk; unsigned int fmt; @@ -392,6 +396,7 @@ static int ak4613_dai_hw_params(struct snd_pcm_substream *substream, default: return -EINVAL; } + priv->rate = rate; /* * FIXME @@ -467,11 +472,83 @@ static int ak4613_set_bias_level(struct snd_soc_codec *codec, return 0; } +static void ak4613_dummy_write(struct work_struct *work) +{ + struct ak4613_priv *priv = container_of(work, + struct ak4613_priv, + dummy_write_work); + struct snd_soc_component *component = priv->component; + unsigned int mgmt1; + unsigned int mgmt3; + + /* + * PW_MGMT1 / PW_MGMT3 needs dummy write at least after 5 LR clocks + * + * Note + * + * To avoid extra delay, we want to avoid preemption here, + * but we can't. Because it uses I2C access which is using IRQ + * and sleep. Thus, delay might be more than 5 LR clocks + * see also + * ak4613_dai_trigger() + */ + udelay(5000000 / priv->rate); + + snd_soc_component_read(component, PW_MGMT1, &mgmt1); + snd_soc_component_read(component, PW_MGMT3, &mgmt3); + + snd_soc_component_write(component, PW_MGMT1, mgmt1); + snd_soc_component_write(component, PW_MGMT3, mgmt3); +} + +static int ak4613_dai_trigger(struct snd_pcm_substream *substream, int cmd, + struct snd_soc_dai *dai) +{ + struct snd_soc_codec *codec = dai->codec; + struct ak4613_priv *priv = snd_soc_codec_get_drvdata(codec); + + /* + * FIXME + * + * PW_MGMT1 / PW_MGMT3 needs dummy write at least after 5 LR clocks + * from Power Down Release. Otherwise, Playback volume will be 0dB. + * To avoid complex multiple delay/dummy_write method from + * ak4613_set_bias_level() / SND_SOC_DAPM_DAC_E("DACx", ...), + * call it once here. + * + * But, unfortunately, we can't "write" here because here is atomic + * context (It uses I2C access for writing). + * Thus, use schedule_work() to switching to normal context + * immediately. + * + * Note + * + * Calling ak4613_dummy_write() function might be delayed. + * In such case, ak4613 volume might be temporarily 0dB when + * beggining of playback. + * see also + * ak4613_dummy_write() + */ + + if ((cmd != SNDRV_PCM_TRIGGER_START) && + (cmd != SNDRV_PCM_TRIGGER_RESUME)) + return 0; + + if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) + return 0; + + priv->component = &codec->component; + schedule_work(&priv->dummy_write_work); + + return 0; +} + static const struct snd_soc_dai_ops ak4613_dai_ops = { .startup = ak4613_dai_startup, .shutdown = ak4613_dai_shutdown, .set_sysclk = ak4613_dai_set_sysclk, .set_fmt = ak4613_dai_set_fmt, + .trigger = ak4613_dai_trigger, .hw_params = ak4613_dai_hw_params, }; @@ -590,6 +667,7 @@ static int ak4613_i2c_probe(struct i2c_client *i2c, priv->iface = NULL; priv->cnt = 0; priv->sysclk = 0; + INIT_WORK(&priv->dummy_write_work, ak4613_dummy_write); mutex_init(&priv->lock); diff --git a/sound/soc/codecs/dmic.c b/sound/soc/codecs/dmic.c index c88f974..cf83c42 100644 --- a/sound/soc/codecs/dmic.c +++ b/sound/soc/codecs/dmic.c @@ -113,7 +113,7 @@ static int dmic_dev_probe(struct platform_device *pdev) if (pdev->dev.of_node) { err = of_property_read_u32(pdev->dev.of_node, "num-channels", &chans); - if (err && (err != -ENOENT)) + if (err && (err != -EINVAL)) return err; if (!err) { diff --git a/sound/soc/codecs/max98373.c b/sound/soc/codecs/max98373.c index 31b0864..562e887 100644 --- a/sound/soc/codecs/max98373.c +++ b/sound/soc/codecs/max98373.c @@ -1,5 +1,5 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* Copyright (c) 2017, Maxim Integrated */ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2017, Maxim Integrated #include <linux/acpi.h> #include <linux/i2c.h> diff --git a/sound/soc/codecs/max98373.h b/sound/soc/codecs/max98373.h index d0b359d..f6a37aa 100644 --- a/sound/soc/codecs/max98373.h +++ b/sound/soc/codecs/max98373.h @@ -1,5 +1,6 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* Copyright (c) 2017, Maxim Integrated */ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2017, Maxim Integrated + #ifndef _MAX98373_H #define _MAX98373_H diff --git a/sound/soc/codecs/sgtl5000.c b/sound/soc/codecs/sgtl5000.c index 633cdcf..e1ab553 100644 --- a/sound/soc/codecs/sgtl5000.c +++ b/sound/soc/codecs/sgtl5000.c @@ -1392,7 +1392,7 @@ static int sgtl5000_i2c_probe(struct i2c_client *client, ana_pwr |= SGTL5000_LINEREG_D_POWERUP; dev_info(&client->dev, - "Using internal LDO instead of VDDD: check ER1\n"); + "Using internal LDO instead of VDDD: check ER1 erratum\n"); } else { /* using external LDO for VDDD * Clear startup powerup and simple powerup diff --git a/sound/soc/codecs/twl4030.c b/sound/soc/codecs/twl4030.c index 8798182..e4d7f39 100644 --- a/sound/soc/codecs/twl4030.c +++ b/sound/soc/codecs/twl4030.c @@ -2195,6 +2195,8 @@ static int twl4030_soc_remove(struct snd_soc_codec *codec) static const struct snd_soc_codec_driver soc_codec_dev_twl4030 = { .probe = twl4030_soc_probe, .remove = twl4030_soc_remove, + .read = twl4030_read, + .write = twl4030_write, .set_bias_level = twl4030_set_bias_level, .idle_bias_off = true, diff --git a/sound/soc/codecs/twl6040.c b/sound/soc/codecs/twl6040.c index 3b895b4..573a523 100644 --- a/sound/soc/codecs/twl6040.c +++ b/sound/soc/codecs/twl6040.c @@ -1158,6 +1158,8 @@ static int twl6040_remove(struct snd_soc_codec *codec) static const struct snd_soc_codec_driver soc_codec_dev_twl6040 = { .probe = twl6040_probe, .remove = twl6040_remove, + .read = twl6040_read, + .write = twl6040_write, .set_bias_level = twl6040_set_bias_level, .suspend_bias_off = true, .ignore_pmdown_time = true, diff --git a/sound/soc/intel/Kconfig b/sound/soc/intel/Kconfig index f2c9e8c..ceb105c 100644 --- a/sound/soc/intel/Kconfig +++ b/sound/soc/intel/Kconfig @@ -97,6 +97,9 @@ config SND_SST_ATOM_HIFI2_PLATFORM codec, then enable this option by saying Y or m. This is a recommended option +config SND_SOC_INTEL_SKYLAKE_SSP_CLK + tristate + config SND_SOC_INTEL_SKYLAKE tristate "SKL/BXT/KBL/GLK/CNL... Platforms" depends on PCI && ACPI diff --git a/sound/soc/intel/boards/Kconfig b/sound/soc/intel/boards/Kconfig index d4e1036..fefb1ee 100644 --- a/sound/soc/intel/boards/Kconfig +++ b/sound/soc/intel/boards/Kconfig @@ -235,6 +235,7 @@ config SND_SOC_INTEL_KBL_RT5663_MAX98927_MACH select SND_SOC_MAX98927 select SND_SOC_DMIC select SND_SOC_HDAC_HDMI + select SND_SOC_INTEL_SKYLAKE_SSP_CLK help This adds support for ASoC Onboard Codec I2S machine driver. This will create an alsa sound card for RT5663 + MAX98927. diff --git a/sound/soc/intel/boards/kbl_rt5663_max98927.c b/sound/soc/intel/boards/kbl_rt5663_max98927.c index bf7014c..f5df6bc 100644 --- a/sound/soc/intel/boards/kbl_rt5663_max98927.c +++ b/sound/soc/intel/boards/kbl_rt5663_max98927.c @@ -28,6 +28,9 @@ #include "../../codecs/rt5663.h" #include "../../codecs/hdac_hdmi.h" #include "../skylake/skl.h" +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/clkdev.h> #define KBL_REALTEK_CODEC_DAI "rt5663-aif" #define KBL_MAXIM_CODEC_DAI "max98927-aif1" @@ -48,6 +51,8 @@ struct kbl_hdmi_pcm { struct kbl_rt5663_private { struct snd_soc_jack kabylake_headset; struct list_head hdmi_pcm_list; + struct clk *mclk; + struct clk *sclk; }; enum { @@ -69,6 +74,61 @@ static const struct snd_kcontrol_new kabylake_controls[] = { SOC_DAPM_PIN_SWITCH("Right Spk"), }; +static int platform_clock_control(struct snd_soc_dapm_widget *w, + struct snd_kcontrol *k, int event) +{ + struct snd_soc_dapm_context *dapm = w->dapm; + struct snd_soc_card *card = dapm->card; + struct kbl_rt5663_private *priv = snd_soc_card_get_drvdata(card); + int ret = 0; + + /* + * MCLK/SCLK need to be ON early for a successful synchronization of + * codec internal clock. And the clocks are turned off during + * POST_PMD after the stream is stopped. + */ + switch (event) { + case SND_SOC_DAPM_PRE_PMU: + /* Enable MCLK */ + ret = clk_set_rate(priv->mclk, 24000000); + if (ret < 0) { + dev_err(card->dev, "Can't set rate for mclk, err: %d\n", + ret); + return ret; + } + + ret = clk_prepare_enable(priv->mclk); + if (ret < 0) { + dev_err(card->dev, "Can't enable mclk, err: %d\n", ret); + return ret; + } + + /* Enable SCLK */ + ret = clk_set_rate(priv->sclk, 3072000); + if (ret < 0) { + dev_err(card->dev, "Can't set rate for sclk, err: %d\n", + ret); + clk_disable_unprepare(priv->mclk); + return ret; + } + + ret = clk_prepare_enable(priv->sclk); + if (ret < 0) { + dev_err(card->dev, "Can't enable sclk, err: %d\n", ret); + clk_disable_unprepare(priv->mclk); + } + break; + case SND_SOC_DAPM_POST_PMD: + clk_disable_unprepare(priv->mclk); + clk_disable_unprepare(priv->sclk); + break; + default: + return 0; + } + + return 0; +} + static const struct snd_soc_dapm_widget kabylake_widgets[] = { SND_SOC_DAPM_HP("Headphone Jack", NULL), SND_SOC_DAPM_MIC("Headset Mic", NULL), @@ -78,11 +138,14 @@ static const struct snd_soc_dapm_widget kabylake_widgets[] = { SND_SOC_DAPM_SPK("HDMI1", NULL), SND_SOC_DAPM_SPK("HDMI2", NULL), SND_SOC_DAPM_SPK("HDMI3", NULL), - + SND_SOC_DAPM_SUPPLY("Platform Clock", SND_SOC_NOPM, 0, 0, + platform_clock_control, SND_SOC_DAPM_PRE_PMU | + SND_SOC_DAPM_POST_PMD), }; static const struct snd_soc_dapm_route kabylake_map[] = { /* HP jack connectors - unknown if we have jack detection */ + { "Headphone Jack", NULL, "Platform Clock" }, { "Headphone Jack", NULL, "HPOL" }, { "Headphone Jack", NULL, "HPOR" }, @@ -91,6 +154,7 @@ static const struct snd_soc_dapm_route kabylake_map[] = { { "Right Spk", NULL, "Right BE_OUT" }, /* other jacks */ + { "Headset Mic", NULL, "Platform Clock" }, { "IN1P", NULL, "Headset Mic" }, { "IN1N", NULL, "Headset Mic" }, { "DMic", NULL, "SoC DMIC" }, @@ -901,6 +965,7 @@ static int kabylake_audio_probe(struct platform_device *pdev) { struct kbl_rt5663_private *ctx; struct skl_machine_pdata *pdata; + int ret; ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_ATOMIC); if (!ctx) @@ -919,6 +984,34 @@ static int kabylake_audio_probe(struct platform_device *pdev) dmic_constraints = pdata->dmic_num == 2 ? &constraints_dmic_2ch : &constraints_dmic_channels; + ctx->mclk = devm_clk_get(&pdev->dev, "ssp1_mclk"); + if (IS_ERR(ctx->mclk)) { + ret = PTR_ERR(ctx->mclk); + if (ret == -ENOENT) { + dev_info(&pdev->dev, + "Failed to get ssp1_sclk, defer probe\n"); + return -EPROBE_DEFER; + } + + dev_err(&pdev->dev, "Failed to get ssp1_mclk with err:%d\n", + ret); + return ret; + } + + ctx->sclk = devm_clk_get(&pdev->dev, "ssp1_sclk"); + if (IS_ERR(ctx->sclk)) { + ret = PTR_ERR(ctx->sclk); + if (ret == -ENOENT) { + dev_info(&pdev->dev, + "Failed to get ssp1_sclk, defer probe\n"); + return -EPROBE_DEFER; + } + + dev_err(&pdev->dev, "Failed to get ssp1_sclk with err:%d\n", + ret); + return ret; + } + return devm_snd_soc_register_card(&pdev->dev, kabylake_audio_card); } diff --git a/sound/soc/intel/skylake/Makefile b/sound/soc/intel/skylake/Makefile index d1ccbec..86f6e1d 100644 --- a/sound/soc/intel/skylake/Makefile +++ b/sound/soc/intel/skylake/Makefile @@ -14,3 +14,8 @@ snd-soc-skl-ipc-objs := skl-sst-ipc.o skl-sst-dsp.o cnl-sst-dsp.o \ skl-sst-utils.o obj-$(CONFIG_SND_SOC_INTEL_SKYLAKE) += snd-soc-skl-ipc.o + +#Skylake Clock device support +snd-soc-skl-ssp-clk-objs := skl-ssp-clk.o + +obj-$(CONFIG_SND_SOC_INTEL_SKYLAKE_SSP_CLK) += snd-soc-skl-ssp-clk.o diff --git a/sound/soc/intel/skylake/skl-i2s.h b/sound/soc/intel/skylake/skl-i2s.h index dcf819b..ad0a1bb 100644 --- a/sound/soc/intel/skylake/skl-i2s.h +++ b/sound/soc/intel/skylake/skl-i2s.h @@ -27,6 +27,12 @@ #define SKL_SHIFT(x) (ffs(x) - 1) #define SKL_MCLK_DIV_RATIO_MASK GENMASK(11, 0) +#define is_legacy_blob(x) (x.signature != 0xEE) +#define ext_to_legacy_blob(i2s_config_blob_ext) \ + ((struct skl_i2s_config_blob_legacy *) i2s_config_blob_ext) + +#define get_clk_src(mclk, mask) \ + ((mclk.mdivctrl & mask) >> SKL_SHIFT(mask)) struct skl_i2s_config { u32 ssc0; u32 ssc1; @@ -45,6 +51,24 @@ struct skl_i2s_config_mclk { u32 mdivr; }; +struct skl_i2s_config_mclk_ext { + u32 mdivctrl; + u32 mdivr_count; + u32 mdivr[0]; +} __packed; + +struct skl_i2s_config_blob_signature { + u32 minor_ver : 8; + u32 major_ver : 8; + u32 resvdz : 8; + u32 signature : 8; +} __packed; + +struct skl_i2s_config_blob_header { + struct skl_i2s_config_blob_signature sig; + u32 size; +}; + /** * struct skl_i2s_config_blob_legacy - Structure defines I2S Gateway * configuration legacy blob @@ -61,4 +85,11 @@ struct skl_i2s_config_blob_legacy { struct skl_i2s_config_mclk mclk; }; +struct skl_i2s_config_blob_ext { + u32 gtw_attr; + struct skl_i2s_config_blob_header hdr; + u32 tdm_ts_group[SKL_I2S_MAX_TIME_SLOTS]; + struct skl_i2s_config i2s_cfg; + struct skl_i2s_config_mclk_ext mclk; +} __packed; #endif /* __SOUND_SOC_SKL_I2S_H */ diff --git a/sound/soc/intel/skylake/skl-messages.c b/sound/soc/intel/skylake/skl-messages.c index 8cbf080..60d76ad 100644 --- a/sound/soc/intel/skylake/skl-messages.c +++ b/sound/soc/intel/skylake/skl-messages.c @@ -675,6 +675,7 @@ int skl_dsp_set_dma_control(struct skl_sst *ctx, u32 *caps, kfree(dma_ctrl); return err; } +EXPORT_SYMBOL_GPL(skl_dsp_set_dma_control); static void skl_setup_out_format(struct skl_sst *ctx, struct skl_module_cfg *mconfig, diff --git a/sound/soc/intel/skylake/skl-nhlt.c b/sound/soc/intel/skylake/skl-nhlt.c index 3b1d2b8..b9b1402 100644 --- a/sound/soc/intel/skylake/skl-nhlt.c +++ b/sound/soc/intel/skylake/skl-nhlt.c @@ -28,6 +28,7 @@ static guid_t osc_guid = GUID_INIT(0xA69F886E, 0x6CEB, 0x4594, 0xA4, 0x1F, 0x7B, 0x5D, 0xCE, 0x24, 0xC5, 0x53); + struct nhlt_acpi_table *skl_nhlt_init(struct device *dev) { acpi_handle handle; @@ -287,6 +288,7 @@ void skl_nhlt_remove_sysfs(struct skl *skl) static void skl_get_ssp_clks(struct skl *skl, struct skl_ssp_clk *ssp_clks, struct nhlt_fmt *fmt, u8 id) { + struct skl_i2s_config_blob_ext *i2s_config_ext; struct skl_i2s_config_blob_legacy *i2s_config; struct skl_clk_parent_src *parent; struct skl_ssp_clk *sclk, *sclkfs; @@ -347,12 +349,18 @@ static void skl_get_ssp_clks(struct skl *skl, struct skl_ssp_clk *ssp_clks, /* Fill rate and parent for sclk/sclkfs */ if (!present) { - /* MCLK Divider Source Select */ - i2s_config = (struct skl_i2s_config_blob_legacy *) + i2s_config_ext = (struct skl_i2s_config_blob_ext *) fmt->fmt_config[0].config.caps; - clk_src = ((i2s_config->mclk.mdivctrl) - & SKL_MNDSS_DIV_CLK_SRC_MASK) >> - SKL_SHIFT(SKL_MNDSS_DIV_CLK_SRC_MASK); + + /* MCLK Divider Source Select */ + if (is_legacy_blob(i2s_config_ext->hdr.sig)) { + i2s_config = ext_to_legacy_blob(i2s_config_ext); + clk_src = get_clk_src(i2s_config->mclk, + SKL_MNDSS_DIV_CLK_SRC_MASK); + } else { + clk_src = get_clk_src(i2s_config_ext->mclk, + SKL_MNDSS_DIV_CLK_SRC_MASK); + } parent = skl_get_parent_clk(clk_src); @@ -378,6 +386,7 @@ static void skl_get_ssp_clks(struct skl *skl, struct skl_ssp_clk *ssp_clks, static void skl_get_mclk(struct skl *skl, struct skl_ssp_clk *mclk, struct nhlt_fmt *fmt, u8 id) { + struct skl_i2s_config_blob_ext *i2s_config_ext; struct skl_i2s_config_blob_legacy *i2s_config; struct nhlt_specific_cfg *fmt_cfg; struct skl_clk_parent_src *parent; @@ -385,13 +394,21 @@ static void skl_get_mclk(struct skl *skl, struct skl_ssp_clk *mclk, u8 clk_src; fmt_cfg = &fmt->fmt_config[0].config; - i2s_config = (struct skl_i2s_config_blob_legacy *)fmt_cfg->caps; - - /* MCLK Divider Source Select */ - clk_src = ((i2s_config->mclk.mdivctrl) & SKL_MCLK_DIV_CLK_SRC_MASK) >> - SKL_SHIFT(SKL_MCLK_DIV_CLK_SRC_MASK); - - clkdiv = i2s_config->mclk.mdivr & SKL_MCLK_DIV_RATIO_MASK; + i2s_config_ext = (struct skl_i2s_config_blob_ext *)fmt_cfg->caps; + + /* MCLK Divider Source Select and divider */ + if (is_legacy_blob(i2s_config_ext->hdr.sig)) { + i2s_config = ext_to_legacy_blob(i2s_config_ext); + clk_src = get_clk_src(i2s_config->mclk, + SKL_MCLK_DIV_CLK_SRC_MASK); + clkdiv = i2s_config->mclk.mdivr & + SKL_MCLK_DIV_RATIO_MASK; + } else { + clk_src = get_clk_src(i2s_config_ext->mclk, + SKL_MCLK_DIV_CLK_SRC_MASK); + clkdiv = i2s_config_ext->mclk.mdivr[0] & + SKL_MCLK_DIV_RATIO_MASK; + } /* bypass divider */ div_ratio = 1; diff --git a/sound/soc/intel/skylake/skl-ssp-clk.c b/sound/soc/intel/skylake/skl-ssp-clk.c new file mode 100644 index 0000000..7fbddf5 --- /dev/null +++ b/sound/soc/intel/skylake/skl-ssp-clk.c @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright(c) 2015-17 Intel Corporation + +/* + * skl-ssp-clk.c - ASoC skylake ssp clock driver + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/clk-provider.h> +#include <linux/clkdev.h> +#include "skl.h" +#include "skl-ssp-clk.h" +#include "skl-topology.h" + +#define to_skl_clk(_hw) container_of(_hw, struct skl_clk, hw) + +struct skl_clk_parent { + struct clk_hw *hw; + struct clk_lookup *lookup; +}; + +struct skl_clk { + struct clk_hw hw; + struct clk_lookup *lookup; + unsigned long rate; + struct skl_clk_pdata *pdata; + u32 id; +}; + +struct skl_clk_data { + struct skl_clk_parent parent[SKL_MAX_CLK_SRC]; + struct skl_clk *clk[SKL_MAX_CLK_CNT]; + u8 avail_clk_cnt; +}; + +static int skl_get_clk_type(u32 index) +{ + switch (index) { + case 0 ... (SKL_SCLK_OFS - 1): + return SKL_MCLK; + + case SKL_SCLK_OFS ... (SKL_SCLKFS_OFS - 1): + return SKL_SCLK; + + case SKL_SCLKFS_OFS ... (SKL_MAX_CLK_CNT - 1): + return SKL_SCLK_FS; + + default: + return -EINVAL; + } +} + +static int skl_get_vbus_id(u32 index, u8 clk_type) +{ + switch (clk_type) { + case SKL_MCLK: + return index; + + case SKL_SCLK: + return index - SKL_SCLK_OFS; + + case SKL_SCLK_FS: + return index - SKL_SCLKFS_OFS; + + default: + return -EINVAL; + } +} + +static void skl_fill_clk_ipc(struct skl_clk_rate_cfg_table *rcfg, u8 clk_type) +{ + struct nhlt_fmt_cfg *fmt_cfg; + union skl_clk_ctrl_ipc *ipc; + struct wav_fmt *wfmt; + + if (!rcfg) + return; + + ipc = &rcfg->dma_ctl_ipc; + if (clk_type == SKL_SCLK_FS) { + fmt_cfg = (struct nhlt_fmt_cfg *)rcfg->config; + wfmt = &fmt_cfg->fmt_ext.fmt; + + /* Remove TLV Header size */ + ipc->sclk_fs.hdr.size = sizeof(struct skl_dmactrl_sclkfs_cfg) - + sizeof(struct skl_tlv_hdr); + ipc->sclk_fs.sampling_frequency = wfmt->samples_per_sec; + ipc->sclk_fs.bit_depth = wfmt->bits_per_sample; + ipc->sclk_fs.valid_bit_depth = + fmt_cfg->fmt_ext.sample.valid_bits_per_sample; + ipc->sclk_fs.number_of_channels = wfmt->channels; + } else { + ipc->mclk.hdr.type = DMA_CLK_CONTROLS; + /* Remove TLV Header size */ + ipc->mclk.hdr.size = sizeof(struct skl_dmactrl_mclk_cfg) - + sizeof(struct skl_tlv_hdr); + } +} + +/* Sends dma control IPC to turn the clock ON/OFF */ +static int skl_send_clk_dma_control(struct skl *skl, + struct skl_clk_rate_cfg_table *rcfg, + u32 vbus_id, u8 clk_type, + bool enable) +{ + struct nhlt_specific_cfg *sp_cfg; + u32 i2s_config_size, node_id = 0; + struct nhlt_fmt_cfg *fmt_cfg; + union skl_clk_ctrl_ipc *ipc; + void *i2s_config = NULL; + u8 *data, size; + int ret; + + if (!rcfg) + return -EIO; + + ipc = &rcfg->dma_ctl_ipc; + fmt_cfg = (struct nhlt_fmt_cfg *)rcfg->config; + sp_cfg = &fmt_cfg->config; + + if (clk_type == SKL_SCLK_FS) { + ipc->sclk_fs.hdr.type = + enable ? DMA_TRANSMITION_START : DMA_TRANSMITION_STOP; + data = (u8 *)&ipc->sclk_fs; + size = sizeof(struct skl_dmactrl_sclkfs_cfg); + } else { + /* 1 to enable mclk, 0 to enable sclk */ + if (clk_type == SKL_SCLK) + ipc->mclk.mclk = 0; + else + ipc->mclk.mclk = 1; + + ipc->mclk.keep_running = enable; + ipc->mclk.warm_up_over = enable; + ipc->mclk.clk_stop_over = !enable; + data = (u8 *)&ipc->mclk; + size = sizeof(struct skl_dmactrl_mclk_cfg); + } + + i2s_config_size = sp_cfg->size + size; + i2s_config = kzalloc(i2s_config_size, GFP_KERNEL); + if (!i2s_config) + return -ENOMEM; + + /* copy blob */ + memcpy(i2s_config, sp_cfg->caps, sp_cfg->size); + + /* copy additional dma controls information */ + memcpy(i2s_config + sp_cfg->size, data, size); + + node_id = ((SKL_DMA_I2S_LINK_INPUT_CLASS << 8) | (vbus_id << 4)); + ret = skl_dsp_set_dma_control(skl->skl_sst, (u32 *)i2s_config, + i2s_config_size, node_id); + kfree(i2s_config); + + return ret; +} + +static struct skl_clk_rate_cfg_table *skl_get_rate_cfg( + struct skl_clk_rate_cfg_table *rcfg, + unsigned long rate) +{ + int i; + + for (i = 0; (i < SKL_MAX_CLK_RATES) && rcfg[i].rate; i++) { + if (rcfg[i].rate == rate) + return &rcfg[i]; + } + + return NULL; +} + +static int skl_clk_change_status(struct skl_clk *clkdev, + bool enable) +{ + struct skl_clk_rate_cfg_table *rcfg; + int vbus_id, clk_type; + + clk_type = skl_get_clk_type(clkdev->id); + if (clk_type < 0) + return clk_type; + + vbus_id = skl_get_vbus_id(clkdev->id, clk_type); + if (vbus_id < 0) + return vbus_id; + + rcfg = skl_get_rate_cfg(clkdev->pdata->ssp_clks[clkdev->id].rate_cfg, + clkdev->rate); + if (!rcfg) + return -EINVAL; + + return skl_send_clk_dma_control(clkdev->pdata->pvt_data, rcfg, + vbus_id, clk_type, enable); +} + +static int skl_clk_prepare(struct clk_hw *hw) +{ + struct skl_clk *clkdev = to_skl_clk(hw); + + return skl_clk_change_status(clkdev, true); +} + +static void skl_clk_unprepare(struct clk_hw *hw) +{ + struct skl_clk *clkdev = to_skl_clk(hw); + + skl_clk_change_status(clkdev, false); +} + +static int skl_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct skl_clk *clkdev = to_skl_clk(hw); + struct skl_clk_rate_cfg_table *rcfg; + int clk_type; + + if (!rate) + return -EINVAL; + + rcfg = skl_get_rate_cfg(clkdev->pdata->ssp_clks[clkdev->id].rate_cfg, + rate); + if (!rcfg) + return -EINVAL; + + clk_type = skl_get_clk_type(clkdev->id); + if (clk_type < 0) + return clk_type; + + skl_fill_clk_ipc(rcfg, clk_type); + clkdev->rate = rate; + + return 0; +} + +static unsigned long skl_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct skl_clk *clkdev = to_skl_clk(hw); + + if (clkdev->rate) + return clkdev->rate; + + return 0; +} + +/* Not supported by clk driver. Implemented to satisfy clk fw */ +long skl_clk_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + return rate; +} + +/* + * prepare/unprepare are used instead of enable/disable as IPC will be sent + * in non-atomic context. + */ +static const struct clk_ops skl_clk_ops = { + .prepare = skl_clk_prepare, + .unprepare = skl_clk_unprepare, + .set_rate = skl_clk_set_rate, + .round_rate = skl_clk_round_rate, + .recalc_rate = skl_clk_recalc_rate, +}; + +static void unregister_parent_src_clk(struct skl_clk_parent *pclk, + unsigned int id) +{ + while (id--) { + clkdev_drop(pclk[id].lookup); + clk_hw_unregister_fixed_rate(pclk[id].hw); + } +} + +static void unregister_src_clk(struct skl_clk_data *dclk) +{ + u8 cnt = dclk->avail_clk_cnt; + + while (cnt--) + clkdev_drop(dclk->clk[cnt]->lookup); +} + +static int skl_register_parent_clks(struct device *dev, + struct skl_clk_parent *parent, + struct skl_clk_parent_src *pclk) +{ + int i, ret; + + for (i = 0; i < SKL_MAX_CLK_SRC; i++) { + + /* Register Parent clock */ + parent[i].hw = clk_hw_register_fixed_rate(dev, pclk[i].name, + pclk[i].parent_name, 0, pclk[i].rate); + if (IS_ERR(parent[i].hw)) { + ret = PTR_ERR(parent[i].hw); + goto err; + } + + parent[i].lookup = clkdev_hw_create(parent[i].hw, pclk[i].name, + NULL); + if (!parent[i].lookup) { + clk_hw_unregister_fixed_rate(parent[i].hw); + ret = -ENOMEM; + goto err; + } + } + + return 0; +err: + unregister_parent_src_clk(parent, i); + return ret; +} + +/* Assign fmt_config to clk_data */ +static struct skl_clk *register_skl_clk(struct device *dev, + struct skl_ssp_clk *clk, + struct skl_clk_pdata *clk_pdata, int id) +{ + struct clk_init_data init; + struct skl_clk *clkdev; + int ret; + + clkdev = devm_kzalloc(dev, sizeof(*clkdev), GFP_KERNEL); + if (!clkdev) + return ERR_PTR(-ENOMEM); + + init.name = clk->name; + init.ops = &skl_clk_ops; + init.flags = CLK_SET_RATE_GATE; + init.parent_names = &clk->parent_name; + init.num_parents = 1; + clkdev->hw.init = &init; + clkdev->pdata = clk_pdata; + + clkdev->id = id; + ret = devm_clk_hw_register(dev, &clkdev->hw); + if (ret) { + clkdev = ERR_PTR(ret); + return clkdev; + } + + clkdev->lookup = clkdev_hw_create(&clkdev->hw, init.name, NULL); + if (!clkdev->lookup) + clkdev = ERR_PTR(-ENOMEM); + + return clkdev; +} + +static int skl_clk_dev_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device *parent_dev = dev->parent; + struct skl_clk_parent_src *parent_clks; + struct skl_clk_pdata *clk_pdata; + struct skl_clk_data *data; + struct skl_ssp_clk *clks; + int ret, i; + + clk_pdata = dev_get_platdata(&pdev->dev); + parent_clks = clk_pdata->parent_clks; + clks = clk_pdata->ssp_clks; + if (!parent_clks || !clks) + return -EIO; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + /* Register Parent clock */ + ret = skl_register_parent_clks(parent_dev, data->parent, parent_clks); + if (ret < 0) + return ret; + + for (i = 0; i < clk_pdata->num_clks; i++) { + /* + * Only register valid clocks + * i.e. for which nhlt entry is present. + */ + if (clks[i].rate_cfg[0].rate == 0) + continue; + + data->clk[i] = register_skl_clk(dev, &clks[i], clk_pdata, i); + if (IS_ERR(data->clk[i])) { + ret = PTR_ERR(data->clk[i]); + goto err_unreg_skl_clk; + } + + data->avail_clk_cnt++; + } + + platform_set_drvdata(pdev, data); + + return 0; + +err_unreg_skl_clk: + unregister_src_clk(data); + unregister_parent_src_clk(data->parent, SKL_MAX_CLK_SRC); + + return ret; +} + +static int skl_clk_dev_remove(struct platform_device *pdev) +{ + struct skl_clk_data *data; + + data = platform_get_drvdata(pdev); + unregister_src_clk(data); + unregister_parent_src_clk(data->parent, SKL_MAX_CLK_SRC); + + return 0; +} + +static struct platform_driver skl_clk_driver = { + .driver = { + .name = "skl-ssp-clk", + }, + .probe = skl_clk_dev_probe, + .remove = skl_clk_dev_remove, +}; + +module_platform_driver(skl_clk_driver); + +MODULE_DESCRIPTION("Skylake clock driver"); +MODULE_AUTHOR("Jaikrishna Nemallapudi <jaikrishnax.nemallapudi@intel.com>"); +MODULE_AUTHOR("Subhransu S. Prusty <subhransu.s.prusty@intel.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:skl-ssp-clk"); diff --git a/sound/soc/intel/skylake/skl-ssp-clk.h b/sound/soc/intel/skylake/skl-ssp-clk.h index c9ea840..d1be50f 100644 --- a/sound/soc/intel/skylake/skl-ssp-clk.h +++ b/sound/soc/intel/skylake/skl-ssp-clk.h @@ -54,8 +54,46 @@ struct skl_clk_parent_src { const char *parent_name; }; +struct skl_tlv_hdr { + u32 type; + u32 size; +}; + +struct skl_dmactrl_mclk_cfg { + struct skl_tlv_hdr hdr; + /* DMA Clk TLV params */ + u32 clk_warm_up:16; + u32 mclk:1; + u32 warm_up_over:1; + u32 rsvd0:14; + u32 clk_stop_delay:16; + u32 keep_running:1; + u32 clk_stop_over:1; + u32 rsvd1:14; +}; + +struct skl_dmactrl_sclkfs_cfg { + struct skl_tlv_hdr hdr; + /* DMA SClk&FS TLV params */ + u32 sampling_frequency; + u32 bit_depth; + u32 channel_map; + u32 channel_config; + u32 interleaving_style; + u32 number_of_channels : 8; + u32 valid_bit_depth : 8; + u32 sample_type : 8; + u32 reserved : 8; +}; + +union skl_clk_ctrl_ipc { + struct skl_dmactrl_mclk_cfg mclk; + struct skl_dmactrl_sclkfs_cfg sclk_fs; +}; + struct skl_clk_rate_cfg_table { unsigned long rate; + union skl_clk_ctrl_ipc dma_ctl_ipc; void *config; }; diff --git a/sound/soc/intel/skylake/skl.h b/sound/soc/intel/skylake/skl.h index f411579..2d13f3f 100644 --- a/sound/soc/intel/skylake/skl.h +++ b/sound/soc/intel/skylake/skl.h @@ -38,6 +38,10 @@ /* D0I3C Register fields */ #define AZX_REG_VS_D0I3C_CIP 0x1 /* Command in progress */ #define AZX_REG_VS_D0I3C_I3 0x4 /* D0i3 enable */ +#define SKL_MAX_DMACTRL_CFG 18 +#define DMA_CLK_CONTROLS 1 +#define DMA_TRANSMITION_START 2 +#define DMA_TRANSMITION_STOP 3 struct skl_dsp_resource { u32 max_mcps; @@ -147,6 +151,8 @@ int skl_nhlt_create_sysfs(struct skl *skl); void skl_nhlt_remove_sysfs(struct skl *skl); void skl_get_clks(struct skl *skl, struct skl_ssp_clk *ssp_clks); struct skl_clk_parent_src *skl_get_parent_clk(u8 clk_id); +int skl_dsp_set_dma_control(struct skl_sst *ctx, u32 *caps, + u32 caps_size, u32 node_id); struct skl_module_cfg; diff --git a/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c index 5bc4e00..d68b53f 100644 --- a/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c +++ b/sound/soc/mediatek/mt2701/mt2701-afe-pcm.c @@ -1405,9 +1405,24 @@ static int mt2701_afe_runtime_resume(struct device *dev) return mt2701_afe_enable_clock(afe); } -static int mt2701_afe_pcm_dev_probe(struct platform_device *pdev) +static int mt2701_afe_add_component(struct mtk_base_afe *afe) { struct snd_soc_component *component; + + component = kzalloc(sizeof(*component), GFP_KERNEL); + if (!component) + return -ENOMEM; + + component->regmap = afe->regmap; + + return snd_soc_add_component(afe->dev, component, + &mt2701_afe_pcm_dai_component, + mt2701_afe_pcm_dais, + ARRAY_SIZE(mt2701_afe_pcm_dais)); +} + +static int mt2701_afe_pcm_dev_probe(struct platform_device *pdev) +{ struct mtk_base_afe *afe; struct mt2701_afe_private *afe_priv; struct device *dev; @@ -1477,12 +1492,6 @@ static int mt2701_afe_pcm_dev_probe(struct platform_device *pdev) = &mt2701_i2s_data[i][I2S_IN]; } - component = kzalloc(sizeof(*component), GFP_KERNEL); - if (!component) - return -ENOMEM; - - component->regmap = afe->regmap; - afe->mtk_afe_hardware = &mt2701_afe_hardware; afe->memif_fs = mt2701_memif_fs; afe->irq_fs = mt2701_irq_fs; @@ -1495,7 +1504,7 @@ static int mt2701_afe_pcm_dev_probe(struct platform_device *pdev) ret = mt2701_init_clock(afe); if (ret) { dev_err(dev, "init clock error\n"); - goto err_init_clock; + return ret; } platform_set_drvdata(pdev, afe); @@ -1514,10 +1523,7 @@ static int mt2701_afe_pcm_dev_probe(struct platform_device *pdev) goto err_platform; } - ret = snd_soc_add_component(dev, component, - &mt2701_afe_pcm_dai_component, - mt2701_afe_pcm_dais, - ARRAY_SIZE(mt2701_afe_pcm_dais)); + ret = mt2701_afe_add_component(afe); if (ret) { dev_warn(dev, "err_dai_component\n"); goto err_dai_component; @@ -1531,8 +1537,6 @@ err_platform: pm_runtime_put_sync(dev); err_pm_disable: pm_runtime_disable(dev); -err_init_clock: - kfree(component); return ret; } diff --git a/sound/soc/mediatek/mt8173/mt8173-rt5650.c b/sound/soc/mediatek/mt8173/mt8173-rt5650.c index 40ebefd..679fc8b 100644 --- a/sound/soc/mediatek/mt8173/mt8173-rt5650.c +++ b/sound/soc/mediatek/mt8173/mt8173-rt5650.c @@ -272,15 +272,10 @@ static int mt8173_rt5650_dev_probe(struct platform_device *pdev) } mt8173_rt5650_codecs[1].of_node = mt8173_rt5650_codecs[0].of_node; - if (of_find_node_by_name(platform_node, "codec-capture")) { - np = of_get_child_by_name(pdev->dev.of_node, "codec-capture"); - if (!np) { - dev_err(&pdev->dev, - "%s: Can't find codec-capture DT node\n", - __func__); - return -EINVAL; - } + np = of_get_child_by_name(pdev->dev.of_node, "codec-capture"); + if (np) { ret = snd_soc_of_get_dai_name(np, &codec_capture_dai); + of_node_put(np); if (ret < 0) { dev_err(&pdev->dev, "%s codec_capture_dai name fail %d\n", diff --git a/sound/soc/rockchip/rk3399_gru_sound.c b/sound/soc/rockchip/rk3399_gru_sound.c index fa6cd1d..214bfc7 100644 --- a/sound/soc/rockchip/rk3399_gru_sound.c +++ b/sound/soc/rockchip/rk3399_gru_sound.c @@ -368,7 +368,8 @@ static const struct snd_soc_dai_link rockchip_dais[] = { [DAILINK_RT5514_DSP] = { .name = "RT5514 DSP", .stream_name = "Wake on Voice", - .codec_dai_name = "rt5514-dsp-cpu-dai", + .codec_name = "snd-soc-dummy", + .codec_dai_name = "snd-soc-dummy-dai", }, }; @@ -529,7 +530,18 @@ static int rockchip_sound_of_parse_dais(struct device *dev, if (index < 0) continue; - np_cpu = (index == DAILINK_CDNDP) ? np_cpu1 : np_cpu0; + switch (index) { + case DAILINK_CDNDP: + np_cpu = np_cpu1; + break; + case DAILINK_RT5514_DSP: + np_cpu = np_codec; + break; + default: + np_cpu = np_cpu0; + break; + } + if (!np_cpu) { dev_err(dev, "Missing 'rockchip,cpu' for %s\n", rockchip_dais[index].name); @@ -539,7 +551,8 @@ static int rockchip_sound_of_parse_dais(struct device *dev, dai = &card->dai_link[card->num_links++]; *dai = rockchip_dais[index]; - dai->codec_of_node = np_codec; + if (!dai->codec_name) + dai->codec_of_node = np_codec; dai->platform_of_node = np_cpu; dai->cpu_of_node = np_cpu; diff --git a/sound/soc/soc-compress.c b/sound/soc/soc-compress.c index 81232f4..8240268 100644 --- a/sound/soc/soc-compress.c +++ b/sound/soc/soc-compress.c @@ -40,7 +40,8 @@ static int soc_compr_open(struct snd_compr_stream *cstream) if (cpu_dai->driver->cops && cpu_dai->driver->cops->startup) { ret = cpu_dai->driver->cops->startup(cstream, cpu_dai); if (ret < 0) { - dev_err(cpu_dai->dev, "Compress ASoC: can't open interface %s: %d\n", + dev_err(cpu_dai->dev, + "Compress ASoC: can't open interface %s: %d\n", cpu_dai->name, ret); goto out; } @@ -49,8 +50,9 @@ static int soc_compr_open(struct snd_compr_stream *cstream) if (platform && platform->driver->compr_ops && platform->driver->compr_ops->open) { ret = platform->driver->compr_ops->open(cstream); if (ret < 0) { - pr_err("compress asoc: can't open platform %s\n", - platform->component.name); + dev_err(platform->dev, + "Compress ASoC: can't open platform %s: %d\n", + platform->component.name, ret); goto plat_err; } } @@ -68,8 +70,9 @@ static int soc_compr_open(struct snd_compr_stream *cstream) __ret = component->driver->compr_ops->open(cstream); if (__ret < 0) { - pr_err("compress asoc: can't open platform %s\n", - component->name); + dev_err(component->dev, + "Compress ASoC: can't open platform %s: %d\n", + component->name, __ret); ret = __ret; } } @@ -79,7 +82,9 @@ static int soc_compr_open(struct snd_compr_stream *cstream) if (rtd->dai_link->compr_ops && rtd->dai_link->compr_ops->startup) { ret = rtd->dai_link->compr_ops->startup(cstream); if (ret < 0) { - pr_err("compress asoc: %s startup failed\n", rtd->dai_link->name); + dev_err(rtd->dev, + "Compress ASoC: %s startup failed: %d\n", + rtd->dai_link->name, ret); goto machine_err; } } @@ -139,18 +144,19 @@ static int soc_compr_open_fe(struct snd_compr_stream *cstream) if (cpu_dai->driver->cops && cpu_dai->driver->cops->startup) { ret = cpu_dai->driver->cops->startup(cstream, cpu_dai); if (ret < 0) { - dev_err(cpu_dai->dev, "Compress ASoC: can't open interface %s: %d\n", + dev_err(cpu_dai->dev, + "Compress ASoC: can't open interface %s: %d\n", cpu_dai->name, ret); goto out; } } - if (platform && platform->driver->compr_ops && platform->driver->compr_ops->open) { ret = platform->driver->compr_ops->open(cstream); if (ret < 0) { - pr_err("compress asoc: can't open platform %s\n", - platform->component.name); + dev_err(platform->dev, + "Compress ASoC: can't open platform %s: %d\n", + platform->component.name, ret); goto plat_err; } } @@ -168,8 +174,9 @@ static int soc_compr_open_fe(struct snd_compr_stream *cstream) __ret = component->driver->compr_ops->open(cstream); if (__ret < 0) { - pr_err("compress asoc: can't open platform %s\n", - component->name); + dev_err(component->dev, + "Compress ASoC: can't open platform %s: %d\n", + component->name, __ret); ret = __ret; } } @@ -179,7 +186,8 @@ static int soc_compr_open_fe(struct snd_compr_stream *cstream) if (fe->dai_link->compr_ops && fe->dai_link->compr_ops->startup) { ret = fe->dai_link->compr_ops->startup(cstream); if (ret < 0) { - pr_err("compress asoc: %s startup failed\n", fe->dai_link->name); + pr_err("Compress ASoC: %s startup failed: %d\n", + fe->dai_link->name, ret); goto machine_err; } } @@ -190,7 +198,7 @@ static int soc_compr_open_fe(struct snd_compr_stream *cstream) if (ret < 0) goto fe_err; else if (ret == 0) - dev_dbg(fe->dev, "ASoC: %s no valid %s route\n", + dev_dbg(fe->dev, "Compress ASoC: %s no valid %s route\n", fe->dai_link->name, stream ? "capture" : "playback"); /* calculate valid and active FE <-> BE dpcms */ @@ -265,10 +273,11 @@ static void close_delayed_work(struct work_struct *work) mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); - dev_dbg(rtd->dev, "ASoC: pop wq checking: %s status: %s waiting: %s\n", - codec_dai->driver->playback.stream_name, - codec_dai->playback_active ? "active" : "inactive", - rtd->pop_wait ? "yes" : "no"); + dev_dbg(rtd->dev, + "Compress ASoC: pop wq checking: %s status: %s waiting: %s\n", + codec_dai->driver->playback.stream_name, + codec_dai->playback_active ? "active" : "inactive", + rtd->pop_wait ? "yes" : "no"); /* are we waiting on this codec DAI stream */ if (rtd->pop_wait == 1) { @@ -307,7 +316,6 @@ static int soc_compr_free(struct snd_compr_stream *cstream) if (!codec_dai->active) codec_dai->rate = 0; - if (rtd->dai_link->compr_ops && rtd->dai_link->compr_ops->shutdown) rtd->dai_link->compr_ops->shutdown(cstream); @@ -376,7 +384,7 @@ static int soc_compr_free_fe(struct snd_compr_stream *cstream) ret = dpcm_be_dai_hw_free(fe, stream); if (ret < 0) - dev_err(fe->dev, "compressed hw_free failed %d\n", ret); + dev_err(fe->dev, "Compressed ASoC: hw_free failed: %d\n", ret); ret = dpcm_be_dai_shutdown(fe, stream); @@ -460,7 +468,6 @@ static int soc_compr_trigger(struct snd_compr_stream *cstream, int cmd) if (cpu_dai->driver->cops && cpu_dai->driver->cops->trigger) cpu_dai->driver->cops->trigger(cstream, cmd, cpu_dai); - switch (cmd) { case SNDRV_PCM_TRIGGER_START: snd_soc_dai_digital_mute(codec_dai, 0, cstream->direction); @@ -944,7 +951,7 @@ static int soc_compr_copy(struct snd_compr_stream *cstream, struct snd_soc_platform *platform = rtd->platform; struct snd_soc_component *component; struct snd_soc_rtdcom_list *rtdcom; - int ret = 0, __ret; + int ret = 0; mutex_lock_nested(&rtd->pcm_mutex, rtd->pcm_subclass); @@ -965,10 +972,10 @@ static int soc_compr_copy(struct snd_compr_stream *cstream, !component->driver->compr_ops->copy) continue; - __ret = component->driver->compr_ops->copy(cstream, buf, count); - if (__ret < 0) - ret = __ret; + ret = component->driver->compr_ops->copy(cstream, buf, count); + break; } + err: mutex_unlock(&rtd->pcm_mutex); return ret; @@ -1108,7 +1115,8 @@ int snd_soc_new_compress(struct snd_soc_pcm_runtime *rtd, int num) int playback = 0, capture = 0; if (rtd->num_codecs > 1) { - dev_err(rtd->card->dev, "Multicodec not supported for compressed stream\n"); + dev_err(rtd->card->dev, + "Compress ASoC: Multicodec not supported\n"); return -EINVAL; } @@ -1126,8 +1134,9 @@ int snd_soc_new_compress(struct snd_soc_pcm_runtime *rtd, int num) * should be set, check for that (xor) */ if (playback + capture != 1) { - dev_err(rtd->card->dev, "Invalid direction for compress P %d, C %d\n", - playback, capture); + dev_err(rtd->card->dev, + "Compress ASoC: Invalid direction for P %d, C %d\n", + playback, capture); return -EINVAL; } @@ -1155,8 +1164,9 @@ int snd_soc_new_compress(struct snd_soc_pcm_runtime *rtd, int num) rtd->dai_link->dpcm_playback, rtd->dai_link->dpcm_capture, &be_pcm); if (ret < 0) { - dev_err(rtd->card->dev, "ASoC: can't create compressed for %s\n", - rtd->dai_link->name); + dev_err(rtd->card->dev, + "Compress ASoC: can't create compressed for %s: %d\n", + rtd->dai_link->name, ret); goto compr_err; } @@ -1199,8 +1209,9 @@ int snd_soc_new_compress(struct snd_soc_pcm_runtime *rtd, int num) new_name, compr); if (ret < 0) { component = rtd->codec_dai->component; - pr_err("compress asoc: can't create compress for codec %s\n", - component->name); + dev_err(component->dev, + "Compress ASoC: can't create compress for codec %s: %d\n", + component->name, ret); goto compr_err; } @@ -1210,8 +1221,8 @@ int snd_soc_new_compress(struct snd_soc_pcm_runtime *rtd, int num) rtd->compr = compr; compr->private_data = rtd; - printk(KERN_INFO "compress asoc: %s <-> %s mapping ok\n", codec_dai->name, - cpu_dai->name); + dev_info(rtd->card->dev, "Compress ASoC: %s <-> %s mapping ok\n", + codec_dai->name, cpu_dai->name); return ret; compr_err: diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c index 88efc84..96c44f6 100644 --- a/sound/soc/soc-core.c +++ b/sound/soc/soc-core.c @@ -553,9 +553,17 @@ struct snd_soc_component *snd_soc_rtdcom_lookup(struct snd_soc_pcm_runtime *rtd, { struct snd_soc_rtdcom_list *rtdcom; + if (!driver_name) + return NULL; + for_each_rtdcom(rtd, rtdcom) { - if ((rtdcom->component->driver->name == driver_name) || - strcmp(rtdcom->component->driver->name, driver_name) == 0) + const char *component_name = rtdcom->component->driver->name; + + if (!component_name) + continue; + + if ((component_name == driver_name) || + strcmp(component_name, driver_name) == 0) return rtdcom->component; } diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index d1977ce..92894d9 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c @@ -2026,7 +2026,7 @@ static ssize_t dapm_widget_power_read_file(struct file *file, snd_soc_dapm_for_each_direction(dir) { rdir = SND_SOC_DAPM_DIR_REVERSE(dir); snd_soc_dapm_widget_for_each_path(w, dir, p) { - if (p->connected && !p->connected(w, p->node[rdir])) + if (p->connected && !p->connected(p->source, p->sink)) continue; if (!p->connect) diff --git a/sound/soc/soc-pcm.c b/sound/soc/soc-pcm.c index 8075856..0841254 100644 --- a/sound/soc/soc-pcm.c +++ b/sound/soc/soc-pcm.c @@ -144,7 +144,7 @@ bool snd_soc_runtime_ignore_pmdown_time(struct snd_soc_pcm_runtime *rtd) for_each_rtdcom(rtd, rtdcom) { component = rtdcom->component; - ignore &= !component->driver->pmdown_time; + ignore &= !component->driver->use_pmdown_time; } /* this will be removed */ @@ -2831,10 +2831,9 @@ static void soc_pcm_private_free(struct snd_pcm *pcm) struct snd_soc_rtdcom_list *rtdcom; struct snd_soc_component *component; + /* need to sync the delayed work before releasing resources */ + flush_delayed_work(&rtd->delayed_work); for_each_rtdcom(rtd, rtdcom) { - /* need to sync the delayed work before releasing resources */ - - flush_delayed_work(&rtd->delayed_work); component = rtdcom->component; if (component->pcm_free) diff --git a/sound/soc/stm/Kconfig b/sound/soc/stm/Kconfig index 3ad881f..48f9ddd 100644 --- a/sound/soc/stm/Kconfig +++ b/sound/soc/stm/Kconfig @@ -2,7 +2,7 @@ menu "STMicroelectronics STM32 SOC audio support" config SND_SOC_STM32_SAI tristate "STM32 SAI interface (Serial Audio Interface) support" - depends on ARCH_STM32 || COMPILE_TEST + depends on (ARCH_STM32 && OF) || COMPILE_TEST depends on SND_SOC select SND_SOC_GENERIC_DMAENGINE_PCM select REGMAP_MMIO @@ -11,7 +11,7 @@ config SND_SOC_STM32_SAI config SND_SOC_STM32_I2S tristate "STM32 I2S interface (SPI/I2S block) support" - depends on ARCH_STM32 || COMPILE_TEST + depends on (ARCH_STM32 && OF) || COMPILE_TEST depends on SND_SOC select SND_SOC_GENERIC_DMAENGINE_PCM select REGMAP_MMIO @@ -20,7 +20,7 @@ config SND_SOC_STM32_I2S config SND_SOC_STM32_SPDIFRX tristate "STM32 S/PDIF receiver (SPDIFRX) support" - depends on ARCH_STM32 || COMPILE_TEST + depends on (ARCH_STM32 && OF) || COMPILE_TEST depends on SND_SOC select SND_SOC_GENERIC_DMAENGINE_PCM select REGMAP_MMIO diff --git a/sound/soc/sunxi/sun8i-codec.c b/sound/soc/sunxi/sun8i-codec.c index 3dd183b..7a15df9 100644 --- a/sound/soc/sunxi/sun8i-codec.c +++ b/sound/soc/sunxi/sun8i-codec.c @@ -37,9 +37,11 @@ #define SUN8I_SYSCLK_CTL_SYSCLK_SRC 0 #define SUN8I_MOD_CLK_ENA 0x010 #define SUN8I_MOD_CLK_ENA_AIF1 15 +#define SUN8I_MOD_CLK_ENA_ADC 3 #define SUN8I_MOD_CLK_ENA_DAC 2 #define SUN8I_MOD_RST_CTL 0x014 #define SUN8I_MOD_RST_CTL_AIF1 15 +#define SUN8I_MOD_RST_CTL_ADC 3 #define SUN8I_MOD_RST_CTL_DAC 2 #define SUN8I_SYS_SR_CTRL 0x018 #define SUN8I_SYS_SR_CTRL_AIF1_FS 12 @@ -54,9 +56,25 @@ #define SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ 4 #define SUN8I_AIF1CLK_CTRL_AIF1_WORD_SIZ_16 (1 << 4) #define SUN8I_AIF1CLK_CTRL_AIF1_DATA_FMT 2 +#define SUN8I_AIF1_ADCDAT_CTRL 0x044 +#define SUN8I_AIF1_ADCDAT_CTRL_AIF1_DA0L_ENA 15 +#define SUN8I_AIF1_ADCDAT_CTRL_AIF1_DA0R_ENA 14 #define SUN8I_AIF1_DACDAT_CTRL 0x048 #define SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0L_ENA 15 #define SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0R_ENA 14 +#define SUN8I_AIF1_MXR_SRC 0x04c +#define SUN8I_AIF1_MXR_SRC_AD0L_MXL_SRC_AIF1DA0L 15 +#define SUN8I_AIF1_MXR_SRC_AD0L_MXL_SRC_AIF2DACL 14 +#define SUN8I_AIF1_MXR_SRC_AD0L_MXL_SRC_ADCL 13 +#define SUN8I_AIF1_MXR_SRC_AD0L_MXL_SRC_AIF2DACR 12 +#define SUN8I_AIF1_MXR_SRC_AD0R_MXR_SRC_AIF1DA0R 11 +#define SUN8I_AIF1_MXR_SRC_AD0R_MXR_SRC_AIF2DACR 10 +#define SUN8I_AIF1_MXR_SRC_AD0R_MXR_SRC_ADCR 9 +#define SUN8I_AIF1_MXR_SRC_AD0R_MXR_SRC_AIF2DACL 8 +#define SUN8I_ADC_DIG_CTRL 0x100 +#define SUN8I_ADC_DIG_CTRL_ENDA 15 +#define SUN8I_ADC_DIG_CTRL_ADOUT_DTS 2 +#define SUN8I_ADC_DIG_CTRL_ADOUT_DLY 1 #define SUN8I_DAC_DIG_CTRL 0x120 #define SUN8I_DAC_DIG_CTRL_ENDA 15 #define SUN8I_DAC_MXR_SRC 0x130 @@ -338,10 +356,30 @@ static const struct snd_kcontrol_new sun8i_dac_mixer_controls[] = { SUN8I_DAC_MXR_SRC_DACR_MXR_SRC_ADCR, 1, 0), }; +static const struct snd_kcontrol_new sun8i_input_mixer_controls[] = { + SOC_DAPM_DOUBLE("AIF1 Slot 0 Digital ADC Capture Switch", + SUN8I_AIF1_MXR_SRC, + SUN8I_AIF1_MXR_SRC_AD0L_MXL_SRC_AIF1DA0L, + SUN8I_AIF1_MXR_SRC_AD0R_MXR_SRC_AIF1DA0R, 1, 0), + SOC_DAPM_DOUBLE("AIF2 Digital ADC Capture Switch", SUN8I_AIF1_MXR_SRC, + SUN8I_AIF1_MXR_SRC_AD0L_MXL_SRC_AIF2DACL, + SUN8I_AIF1_MXR_SRC_AD0R_MXR_SRC_AIF2DACR, 1, 0), + SOC_DAPM_DOUBLE("AIF1 Data Digital ADC Capture Switch", + SUN8I_AIF1_MXR_SRC, + SUN8I_AIF1_MXR_SRC_AD0L_MXL_SRC_ADCL, + SUN8I_AIF1_MXR_SRC_AD0R_MXR_SRC_ADCR, 1, 0), + SOC_DAPM_DOUBLE("AIF2 Inv Digital ADC Capture Switch", + SUN8I_AIF1_MXR_SRC, + SUN8I_AIF1_MXR_SRC_AD0L_MXL_SRC_AIF2DACR, + SUN8I_AIF1_MXR_SRC_AD0R_MXR_SRC_AIF2DACL, 1, 0), +}; + static const struct snd_soc_dapm_widget sun8i_codec_dapm_widgets[] = { - /* Digital parts of the DACs */ + /* Digital parts of the DACs and ADC */ SND_SOC_DAPM_SUPPLY("DAC", SUN8I_DAC_DIG_CTRL, SUN8I_DAC_DIG_CTRL_ENDA, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("ADC", SUN8I_ADC_DIG_CTRL, SUN8I_ADC_DIG_CTRL_ENDA, + 0, NULL, 0), /* Analog DAC AIF */ SND_SOC_DAPM_AIF_IN("AIF1 Slot 0 Left", "Playback", 0, @@ -351,17 +389,31 @@ static const struct snd_soc_dapm_widget sun8i_codec_dapm_widgets[] = { SUN8I_AIF1_DACDAT_CTRL, SUN8I_AIF1_DACDAT_CTRL_AIF1_DA0R_ENA, 0), - /* DAC Mixers */ + /* Analog ADC AIF */ + SND_SOC_DAPM_AIF_IN("AIF1 Slot 0 Left ADC", "Capture", 0, + SUN8I_AIF1_ADCDAT_CTRL, + SUN8I_AIF1_ADCDAT_CTRL_AIF1_DA0L_ENA, 0), + SND_SOC_DAPM_AIF_IN("AIF1 Slot 0 Right ADC", "Capture", 0, + SUN8I_AIF1_ADCDAT_CTRL, + SUN8I_AIF1_ADCDAT_CTRL_AIF1_DA0R_ENA, 0), + + /* DAC and ADC Mixers */ SOC_MIXER_ARRAY("Left Digital DAC Mixer", SND_SOC_NOPM, 0, 0, sun8i_dac_mixer_controls), SOC_MIXER_ARRAY("Right Digital DAC Mixer", SND_SOC_NOPM, 0, 0, sun8i_dac_mixer_controls), + SOC_MIXER_ARRAY("Left Digital ADC Mixer", SND_SOC_NOPM, 0, 0, + sun8i_input_mixer_controls), + SOC_MIXER_ARRAY("Right Digital ADC Mixer", SND_SOC_NOPM, 0, 0, + sun8i_input_mixer_controls), /* Clocks */ SND_SOC_DAPM_SUPPLY("MODCLK AFI1", SUN8I_MOD_CLK_ENA, SUN8I_MOD_CLK_ENA_AIF1, 0, NULL, 0), SND_SOC_DAPM_SUPPLY("MODCLK DAC", SUN8I_MOD_CLK_ENA, SUN8I_MOD_CLK_ENA_DAC, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("MODCLK ADC", SUN8I_MOD_CLK_ENA, + SUN8I_MOD_CLK_ENA_ADC, 0, NULL, 0), SND_SOC_DAPM_SUPPLY("AIF1", SUN8I_SYSCLK_CTL, SUN8I_SYSCLK_CTL_AIF1CLK_ENA, 0, NULL, 0), SND_SOC_DAPM_SUPPLY("SYSCLK", SUN8I_SYSCLK_CTL, @@ -378,6 +430,12 @@ static const struct snd_soc_dapm_widget sun8i_codec_dapm_widgets[] = { SUN8I_MOD_RST_CTL_AIF1, 0, NULL, 0), SND_SOC_DAPM_SUPPLY("RST DAC", SUN8I_MOD_RST_CTL, SUN8I_MOD_RST_CTL_DAC, 0, NULL, 0), + SND_SOC_DAPM_SUPPLY("RST ADC", SUN8I_MOD_RST_CTL, + SUN8I_MOD_RST_CTL_ADC, 0, NULL, 0), + + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Mic", NULL), + }; static const struct snd_soc_dapm_route sun8i_codec_dapm_routes[] = { @@ -387,11 +445,16 @@ static const struct snd_soc_dapm_route sun8i_codec_dapm_routes[] = { { "RST AIF1", NULL, "AIF1 PLL" }, { "MODCLK AFI1", NULL, "RST AIF1" }, { "DAC", NULL, "MODCLK AFI1" }, + { "ADC", NULL, "MODCLK AFI1" }, { "RST DAC", NULL, "SYSCLK" }, { "MODCLK DAC", NULL, "RST DAC" }, { "DAC", NULL, "MODCLK DAC" }, + { "RST ADC", NULL, "SYSCLK" }, + { "MODCLK ADC", NULL, "RST ADC" }, + { "ADC", NULL, "MODCLK ADC" }, + /* DAC Routes */ { "AIF1 Slot 0 Right", NULL, "DAC" }, { "AIF1 Slot 0 Left", NULL, "DAC" }, @@ -401,6 +464,12 @@ static const struct snd_soc_dapm_route sun8i_codec_dapm_routes[] = { "AIF1 Slot 0 Left"}, { "Right Digital DAC Mixer", "AIF1 Slot 0 Digital DAC Playback Switch", "AIF1 Slot 0 Right"}, + + /* ADC routes */ + { "Left Digital ADC Mixer", "AIF1 Data Digital ADC Capture Switch", + "AIF1 Slot 0 Left ADC" }, + { "Right Digital ADC Mixer", "AIF1 Data Digital ADC Capture Switch", + "AIF1 Slot 0 Right ADC" }, }; static const struct snd_soc_dai_ops sun8i_codec_dai_ops = { @@ -418,6 +487,15 @@ static struct snd_soc_dai_driver sun8i_codec_dai = { .rates = SNDRV_PCM_RATE_8000_192000, .formats = SNDRV_PCM_FMTBIT_S16_LE, }, + /* capture capabilities */ + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 2, + .rates = SNDRV_PCM_RATE_8000_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .sig_bits = 24, + }, /* pcm operations */ .ops = &sun8i_codec_dai_ops, }; |