diff options
Diffstat (limited to '')
34 files changed, 14615 insertions, 0 deletions
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig new file mode 100644 index 000000000..a824f7938 --- /dev/null +++ b/sound/soc/qcom/Kconfig @@ -0,0 +1,131 @@ +# SPDX-License-Identifier: GPL-2.0-only +menuconfig SND_SOC_QCOM + tristate "ASoC support for QCOM platforms" + depends on ARCH_QCOM || COMPILE_TEST + help + Say Y or M if you want to add support to use audio devices + in Qualcomm Technologies SOC-based platforms. + +if SND_SOC_QCOM + +config SND_SOC_LPASS_CPU + tristate + select REGMAP_MMIO + +config SND_SOC_LPASS_HDMI + tristate + select REGMAP_MMIO + +config SND_SOC_LPASS_PLATFORM + tristate + select REGMAP_MMIO + +config SND_SOC_LPASS_IPQ806X + tristate + select SND_SOC_LPASS_CPU + select SND_SOC_LPASS_PLATFORM + +config SND_SOC_LPASS_APQ8016 + tristate + select SND_SOC_LPASS_CPU + select SND_SOC_LPASS_PLATFORM + +config SND_SOC_LPASS_SC7180 + tristate + select SND_SOC_LPASS_CPU + select SND_SOC_LPASS_PLATFORM + select SND_SOC_LPASS_HDMI + +config SND_SOC_STORM + tristate "ASoC I2S support for Storm boards" + select SND_SOC_LPASS_IPQ806X + select SND_SOC_MAX98357A + help + Say Y or M if you want add support for SoC audio on the + Qualcomm Technologies IPQ806X-based Storm board. + +config SND_SOC_APQ8016_SBC + tristate "SoC Audio support for APQ8016 SBC platforms" + select SND_SOC_LPASS_APQ8016 + select SND_SOC_QCOM_COMMON + help + Support for Qualcomm Technologies LPASS audio block in + APQ8016 SOC-based systems. + Say Y if you want to use audio devices on MI2S. + +config SND_SOC_QCOM_COMMON + tristate + +config SND_SOC_QDSP6_COMMON + tristate + +config SND_SOC_QDSP6_CORE + tristate + +config SND_SOC_QDSP6_AFE + tristate + +config SND_SOC_QDSP6_AFE_DAI + tristate + +config SND_SOC_QDSP6_AFE_CLOCKS + tristate + +config SND_SOC_QDSP6_ADM + tristate + +config SND_SOC_QDSP6_ROUTING + tristate + +config SND_SOC_QDSP6_ASM + tristate + +config SND_SOC_QDSP6_ASM_DAI + select SND_SOC_COMPRESS + tristate + +config SND_SOC_QDSP6 + tristate "SoC ALSA audio driver for QDSP6" + depends on QCOM_APR + depends on COMMON_CLK + select SND_SOC_QDSP6_COMMON + select SND_SOC_QDSP6_CORE + select SND_SOC_QDSP6_AFE + select SND_SOC_QDSP6_AFE_DAI + select SND_SOC_QDSP6_AFE_CLOCKS + select SND_SOC_QDSP6_ADM + select SND_SOC_QDSP6_ROUTING + select SND_SOC_QDSP6_ASM + select SND_SOC_QDSP6_ASM_DAI + help + To add support for MSM QDSP6 Soc Audio. + This will enable sound soc platform specific + audio drivers. This includes q6asm, q6adm, + q6afe interfaces to DSP using apr. + +config SND_SOC_MSM8996 + tristate "SoC Machine driver for MSM8996 and APQ8096 boards" + depends on QCOM_APR + depends on COMMON_CLK + select SND_SOC_QDSP6 + select SND_SOC_QCOM_COMMON + help + Support for Qualcomm Technologies LPASS audio block in + APQ8096 SoC-based systems. + Say Y if you want to use audio device on this SoCs + +config SND_SOC_SDM845 + tristate "SoC Machine driver for SDM845 boards" + depends on QCOM_APR && I2C && SOUNDWIRE + depends on COMMON_CLK + select SND_SOC_QDSP6 + select SND_SOC_QCOM_COMMON + select SND_SOC_RT5663 + select SND_SOC_MAX98927 + imply SND_SOC_CROS_EC_CODEC + help + To add support for audio on Qualcomm Technologies Inc. + SDM845 SoC-based systems. + Say Y if you want to use audio device on this SoCs. + +endif #SND_SOC_QCOM diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile new file mode 100644 index 000000000..0bd90d74e --- /dev/null +++ b/sound/soc/qcom/Makefile @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: GPL-2.0 +# Platform +snd-soc-lpass-cpu-objs := lpass-cpu.o +snd-soc-lpass-hdmi-objs := lpass-hdmi.o +snd-soc-lpass-platform-objs := lpass-platform.o +snd-soc-lpass-ipq806x-objs := lpass-ipq806x.o +snd-soc-lpass-apq8016-objs := lpass-apq8016.o +snd-soc-lpass-sc7180-objs := lpass-sc7180.o + +obj-$(CONFIG_SND_SOC_LPASS_CPU) += snd-soc-lpass-cpu.o +obj-$(CONFIG_SND_SOC_LPASS_HDMI) += snd-soc-lpass-hdmi.o +obj-$(CONFIG_SND_SOC_LPASS_PLATFORM) += snd-soc-lpass-platform.o +obj-$(CONFIG_SND_SOC_LPASS_IPQ806X) += snd-soc-lpass-ipq806x.o +obj-$(CONFIG_SND_SOC_LPASS_APQ8016) += snd-soc-lpass-apq8016.o +obj-$(CONFIG_SND_SOC_LPASS_SC7180) += snd-soc-lpass-sc7180.o + +# Machine +snd-soc-storm-objs := storm.o +snd-soc-apq8016-sbc-objs := apq8016_sbc.o +snd-soc-apq8096-objs := apq8096.o +snd-soc-sdm845-objs := sdm845.o +snd-soc-qcom-common-objs := common.o + +obj-$(CONFIG_SND_SOC_STORM) += snd-soc-storm.o +obj-$(CONFIG_SND_SOC_APQ8016_SBC) += snd-soc-apq8016-sbc.o +obj-$(CONFIG_SND_SOC_MSM8996) += snd-soc-apq8096.o +obj-$(CONFIG_SND_SOC_SDM845) += snd-soc-sdm845.o +obj-$(CONFIG_SND_SOC_QCOM_COMMON) += snd-soc-qcom-common.o + +#DSP lib +obj-$(CONFIG_SND_SOC_QDSP6) += qdsp6/ diff --git a/sound/soc/qcom/apq8016_sbc.c b/sound/soc/qcom/apq8016_sbc.c new file mode 100644 index 000000000..575e2aefe --- /dev/null +++ b/sound/soc/qcom/apq8016_sbc.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015 The Linux Foundation. All rights reserved. + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/jack.h> +#include <sound/soc.h> +#include <uapi/linux/input-event-codes.h> +#include <dt-bindings/sound/apq8016-lpass.h> +#include "common.h" + +struct apq8016_sbc_data { + struct snd_soc_card card; + void __iomem *mic_iomux; + void __iomem *spkr_iomux; + struct snd_soc_jack jack; + bool jack_setup; +}; + +#define MIC_CTRL_TER_WS_SLAVE_SEL BIT(21) +#define MIC_CTRL_QUA_WS_SLAVE_SEL_10 BIT(17) +#define MIC_CTRL_TLMM_SCLK_EN BIT(1) +#define SPKR_CTL_PRI_WS_SLAVE_SEL_11 (BIT(17) | BIT(16)) +#define DEFAULT_MCLK_RATE 9600000 + +static int apq8016_sbc_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai; + struct snd_soc_component *component; + struct snd_soc_card *card = rtd->card; + struct apq8016_sbc_data *pdata = snd_soc_card_get_drvdata(card); + int i, rval; + + switch (cpu_dai->id) { + case MI2S_PRIMARY: + writel(readl(pdata->spkr_iomux) | SPKR_CTL_PRI_WS_SLAVE_SEL_11, + pdata->spkr_iomux); + break; + + case MI2S_QUATERNARY: + /* Configure the Quat MI2S to TLMM */ + writel(readl(pdata->mic_iomux) | MIC_CTRL_QUA_WS_SLAVE_SEL_10 | + MIC_CTRL_TLMM_SCLK_EN, + pdata->mic_iomux); + break; + case MI2S_TERTIARY: + writel(readl(pdata->mic_iomux) | MIC_CTRL_TER_WS_SLAVE_SEL | + MIC_CTRL_TLMM_SCLK_EN, + pdata->mic_iomux); + + break; + + default: + dev_err(card->dev, "unsupported cpu dai configuration\n"); + return -EINVAL; + + } + + if (!pdata->jack_setup) { + struct snd_jack *jack; + + rval = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADSET | + SND_JACK_HEADPHONE | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3 | + SND_JACK_BTN_4, + &pdata->jack, NULL, 0); + + if (rval < 0) { + dev_err(card->dev, "Unable to add Headphone Jack\n"); + return rval; + } + + jack = pdata->jack.jack; + + snd_jack_set_key(jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + pdata->jack_setup = true; + } + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + + component = codec_dai->component; + /* Set default mclk for internal codec */ + rval = snd_soc_component_set_sysclk(component, 0, 0, DEFAULT_MCLK_RATE, + SND_SOC_CLOCK_IN); + if (rval != 0 && rval != -ENOTSUPP) { + dev_warn(card->dev, "Failed to set mclk: %d\n", rval); + return rval; + } + rval = snd_soc_component_set_jack(component, &pdata->jack, NULL); + if (rval != 0 && rval != -ENOTSUPP) { + dev_warn(card->dev, "Failed to set jack: %d\n", rval); + return rval; + } + } + + return 0; +} + +static void apq8016_sbc_add_ops(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *link; + int i; + + for_each_card_prelinks(card, i, link) + link->init = apq8016_sbc_dai_init; +} + +static const struct snd_soc_dapm_widget apq8016_sbc_dapm_widgets[] = { + + SND_SOC_DAPM_MIC("Handset Mic", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_MIC("Secondary Mic", NULL), + SND_SOC_DAPM_MIC("Digital Mic1", NULL), + SND_SOC_DAPM_MIC("Digital Mic2", NULL), +}; + +static int apq8016_sbc_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct snd_soc_card *card; + struct apq8016_sbc_data *data; + struct resource *res; + int ret; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + card = &data->card; + card->dev = dev; + card->owner = THIS_MODULE; + card->dapm_widgets = apq8016_sbc_dapm_widgets; + card->num_dapm_widgets = ARRAY_SIZE(apq8016_sbc_dapm_widgets); + + ret = qcom_snd_parse_of(card); + if (ret) + return ret; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mic-iomux"); + data->mic_iomux = devm_ioremap_resource(dev, res); + if (IS_ERR(data->mic_iomux)) + return PTR_ERR(data->mic_iomux); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "spkr-iomux"); + data->spkr_iomux = devm_ioremap_resource(dev, res); + if (IS_ERR(data->spkr_iomux)) + return PTR_ERR(data->spkr_iomux); + + snd_soc_card_set_drvdata(card, data); + + apq8016_sbc_add_ops(card); + return devm_snd_soc_register_card(&pdev->dev, card); +} + +static const struct of_device_id apq8016_sbc_device_id[] = { + { .compatible = "qcom,apq8016-sbc-sndcard" }, + {}, +}; +MODULE_DEVICE_TABLE(of, apq8016_sbc_device_id); + +static struct platform_driver apq8016_sbc_platform_driver = { + .driver = { + .name = "qcom-apq8016-sbc", + .of_match_table = of_match_ptr(apq8016_sbc_device_id), + }, + .probe = apq8016_sbc_platform_probe, +}; +module_platform_driver(apq8016_sbc_platform_driver); + +MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org"); +MODULE_DESCRIPTION("APQ8016 ASoC Machine Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/apq8096.c b/sound/soc/qcom/apq8096.c new file mode 100644 index 000000000..1a69baefc --- /dev/null +++ b/sound/soc/qcom/apq8096.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2018, Linaro Limited + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of_device.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include "common.h" + +#define SLIM_MAX_TX_PORTS 16 +#define SLIM_MAX_RX_PORTS 16 +#define WCD9335_DEFAULT_MCLK_RATE 9600000 + +static int apq8096_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + + rate->min = rate->max = 48000; + channels->min = channels->max = 2; + + return 0; +} + +static int msm_snd_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + u32 rx_ch[SLIM_MAX_RX_PORTS], tx_ch[SLIM_MAX_TX_PORTS]; + u32 rx_ch_cnt = 0, tx_ch_cnt = 0; + int ret = 0; + + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt, rx_ch); + if (ret != 0 && ret != -ENOTSUPP) { + pr_err("failed to get codec chan map, err:%d\n", ret); + goto end; + } else if (ret == -ENOTSUPP) { + return 0; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, NULL, + rx_ch_cnt, rx_ch); + else + ret = snd_soc_dai_set_channel_map(cpu_dai, tx_ch_cnt, tx_ch, + 0, NULL); + if (ret != 0 && ret != -ENOTSUPP) + pr_err("Failed to set cpu chan map, err:%d\n", ret); + else if (ret == -ENOTSUPP) + ret = 0; +end: + return ret; +} + +static struct snd_soc_ops apq8096_ops = { + .hw_params = msm_snd_hw_params, +}; + +static int apq8096_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + + /* + * Codec SLIMBUS configuration + * RX1, RX2, RX3, RX4, RX5, RX6, RX7, RX8, RX9, RX10, RX11, RX12, RX13 + * TX1, TX2, TX3, TX4, TX5, TX6, TX7, TX8, TX9, TX10, TX11, TX12, TX13 + * TX14, TX15, TX16 + */ + unsigned int rx_ch[SLIM_MAX_RX_PORTS] = {144, 145, 146, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156}; + unsigned int tx_ch[SLIM_MAX_TX_PORTS] = {128, 129, 130, 131, 132, 133, + 134, 135, 136, 137, 138, 139, + 140, 141, 142, 143}; + + snd_soc_dai_set_channel_map(codec_dai, ARRAY_SIZE(tx_ch), + tx_ch, ARRAY_SIZE(rx_ch), rx_ch); + + snd_soc_dai_set_sysclk(codec_dai, 0, WCD9335_DEFAULT_MCLK_RATE, + SNDRV_PCM_STREAM_PLAYBACK); + + return 0; +} + +static void apq8096_add_be_ops(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *link; + int i; + + for_each_card_prelinks(card, i, link) { + if (link->no_pcm == 1) { + link->be_hw_params_fixup = apq8096_be_hw_params_fixup; + link->init = apq8096_init; + link->ops = &apq8096_ops; + } + } +} + +static int apq8096_platform_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card; + struct device *dev = &pdev->dev; + int ret; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->dev = dev; + card->owner = THIS_MODULE; + dev_set_drvdata(dev, card); + ret = qcom_snd_parse_of(card); + if (ret) + return ret; + + apq8096_add_be_ops(card); + return devm_snd_soc_register_card(dev, card); +} + +static const struct of_device_id msm_snd_apq8096_dt_match[] = { + {.compatible = "qcom,apq8096-sndcard"}, + {} +}; + +MODULE_DEVICE_TABLE(of, msm_snd_apq8096_dt_match); + +static struct platform_driver msm_snd_apq8096_driver = { + .probe = apq8096_platform_probe, + .driver = { + .name = "msm-snd-apq8096", + .of_match_table = msm_snd_apq8096_dt_match, + }, +}; +module_platform_driver(msm_snd_apq8096_driver); +MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org"); +MODULE_DESCRIPTION("APQ8096 ASoC Machine Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/common.c b/sound/soc/qcom/common.c new file mode 100644 index 000000000..09af00700 --- /dev/null +++ b/sound/soc/qcom/common.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2018, Linaro Limited. +// Copyright (c) 2018, The Linux Foundation. All rights reserved. + +#include <linux/module.h> +#include "common.h" + +int qcom_snd_parse_of(struct snd_soc_card *card) +{ + struct device_node *np; + struct device_node *codec = NULL; + struct device_node *platform = NULL; + struct device_node *cpu = NULL; + struct device *dev = card->dev; + struct snd_soc_dai_link *link; + struct of_phandle_args args; + struct snd_soc_dai_link_component *dlc; + int ret, num_links; + + ret = snd_soc_of_parse_card_name(card, "model"); + if (ret == 0 && !card->name) + /* Deprecated, only for compatibility with old device trees */ + ret = snd_soc_of_parse_card_name(card, "qcom,model"); + if (ret) { + dev_err(dev, "Error parsing card name: %d\n", ret); + return ret; + } + + /* DAPM routes */ + if (of_property_read_bool(dev->of_node, "audio-routing")) { + ret = snd_soc_of_parse_audio_routing(card, "audio-routing"); + if (ret) + return ret; + } + /* Deprecated, only for compatibility with old device trees */ + if (of_property_read_bool(dev->of_node, "qcom,audio-routing")) { + ret = snd_soc_of_parse_audio_routing(card, "qcom,audio-routing"); + if (ret) + return ret; + } + + ret = snd_soc_of_parse_aux_devs(card, "aux-devs"); + if (ret) + return ret; + + /* Populate links */ + num_links = of_get_child_count(dev->of_node); + + /* Allocate the DAI link array */ + card->dai_link = devm_kcalloc(dev, num_links, sizeof(*link), GFP_KERNEL); + if (!card->dai_link) + return -ENOMEM; + + card->num_links = num_links; + link = card->dai_link; + + for_each_child_of_node(dev->of_node, np) { + dlc = devm_kzalloc(dev, 2 * sizeof(*dlc), GFP_KERNEL); + if (!dlc) { + ret = -ENOMEM; + goto err_put_np; + } + + link->cpus = &dlc[0]; + link->platforms = &dlc[1]; + + link->num_cpus = 1; + link->num_platforms = 1; + + ret = of_property_read_string(np, "link-name", &link->name); + if (ret) { + dev_err(card->dev, "error getting codec dai_link name\n"); + goto err_put_np; + } + + cpu = of_get_child_by_name(np, "cpu"); + platform = of_get_child_by_name(np, "platform"); + codec = of_get_child_by_name(np, "codec"); + + if (!cpu) { + dev_err(dev, "%s: Can't find cpu DT node\n", link->name); + ret = -EINVAL; + goto err; + } + + ret = of_parse_phandle_with_args(cpu, "sound-dai", + "#sound-dai-cells", 0, &args); + if (ret) { + dev_err(card->dev, "%s: error getting cpu phandle\n", link->name); + goto err; + } + link->cpus->of_node = args.np; + link->id = args.args[0]; + + ret = snd_soc_of_get_dai_name(cpu, &link->cpus->dai_name); + if (ret) { + if (ret != -EPROBE_DEFER) + dev_err(card->dev, "%s: error getting cpu dai name: %d\n", + link->name, ret); + goto err; + } + + if (platform) { + link->platforms->of_node = of_parse_phandle(platform, + "sound-dai", + 0); + if (!link->platforms->of_node) { + dev_err(card->dev, "%s: platform dai not found\n", link->name); + ret = -EINVAL; + goto err; + } + } else { + link->platforms->of_node = link->cpus->of_node; + } + + if (codec) { + ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); + if (ret < 0) { + if (ret != -EPROBE_DEFER) + dev_err(card->dev, "%s: codec dai not found: %d\n", + link->name, ret); + goto err; + } + + if (platform) { + /* DPCM backend */ + link->no_pcm = 1; + link->ignore_pmdown_time = 1; + } + } else { + /* DPCM frontend */ + dlc = devm_kzalloc(dev, sizeof(*dlc), GFP_KERNEL); + if (!dlc) { + ret = -ENOMEM; + goto err; + } + + link->codecs = dlc; + link->num_codecs = 1; + + link->codecs->dai_name = "snd-soc-dummy-dai"; + link->codecs->name = "snd-soc-dummy"; + link->dynamic = 1; + } + + if (platform || !codec) { + /* DPCM */ + snd_soc_dai_link_set_capabilities(link); + link->ignore_suspend = 1; + link->nonatomic = 1; + } + + link->stream_name = link->name; + link++; + + of_node_put(cpu); + of_node_put(codec); + of_node_put(platform); + } + + return 0; +err: + of_node_put(cpu); + of_node_put(codec); + of_node_put(platform); +err_put_np: + of_node_put(np); + return ret; +} +EXPORT_SYMBOL(qcom_snd_parse_of); + +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/common.h b/sound/soc/qcom/common.h new file mode 100644 index 000000000..f05c05b12 --- /dev/null +++ b/sound/soc/qcom/common.h @@ -0,0 +1,11 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +// Copyright (c) 2018, The Linux Foundation. All rights reserved. + +#ifndef __QCOM_SND_COMMON_H__ +#define __QCOM_SND_COMMON_H__ + +#include <sound/soc.h> + +int qcom_snd_parse_of(struct snd_soc_card *card); + +#endif diff --git a/sound/soc/qcom/lpass-apq8016.c b/sound/soc/qcom/lpass-apq8016.c new file mode 100644 index 000000000..7c0e774ad --- /dev/null +++ b/sound/soc/qcom/lpass-apq8016.c @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved. + * + * lpass-apq8016.c -- ALSA SoC CPU DAI driver for APQ8016 LPASS + */ + + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> + +#include <dt-bindings/sound/apq8016-lpass.h> +#include "lpass-lpaif-reg.h" +#include "lpass.h" + +static struct snd_soc_dai_driver apq8016_lpass_cpu_dai_driver[] = { + [MI2S_PRIMARY] = { + .id = MI2S_PRIMARY, + .name = "Primary MI2S", + .playback = { + .stream_name = "Primary Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + }, + .probe = &asoc_qcom_lpass_cpu_dai_probe, + .ops = &asoc_qcom_lpass_cpu_dai_ops, + }, + [MI2S_SECONDARY] = { + .id = MI2S_SECONDARY, + .name = "Secondary MI2S", + .playback = { + .stream_name = "Secondary Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + }, + .probe = &asoc_qcom_lpass_cpu_dai_probe, + .ops = &asoc_qcom_lpass_cpu_dai_ops, + }, + [MI2S_TERTIARY] = { + .id = MI2S_TERTIARY, + .name = "Tertiary MI2S", + .capture = { + .stream_name = "Tertiary Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + }, + .probe = &asoc_qcom_lpass_cpu_dai_probe, + .ops = &asoc_qcom_lpass_cpu_dai_ops, + }, + [MI2S_QUATERNARY] = { + .id = MI2S_QUATERNARY, + .name = "Quatenary MI2S", + .playback = { + .stream_name = "Quatenary Playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + }, + .capture = { + .stream_name = "Quatenary Capture", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + }, + .probe = &asoc_qcom_lpass_cpu_dai_probe, + .ops = &asoc_qcom_lpass_cpu_dai_ops, + }, +}; + +static int apq8016_lpass_alloc_dma_channel(struct lpass_data *drvdata, + int direction, unsigned int dai_id) +{ + struct lpass_variant *v = drvdata->variant; + int chan = 0; + + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { + chan = find_first_zero_bit(&drvdata->dma_ch_bit_map, + v->rdma_channels); + + if (chan >= v->rdma_channels) + return -EBUSY; + } else { + chan = find_next_zero_bit(&drvdata->dma_ch_bit_map, + v->wrdma_channel_start + + v->wrdma_channels, + v->wrdma_channel_start); + + if (chan >= v->wrdma_channel_start + v->wrdma_channels) + return -EBUSY; + } + + set_bit(chan, &drvdata->dma_ch_bit_map); + + return chan; +} + +static int apq8016_lpass_free_dma_channel(struct lpass_data *drvdata, int chan, unsigned int dai_id) +{ + clear_bit(chan, &drvdata->dma_ch_bit_map); + + return 0; +} + +static int apq8016_lpass_init(struct platform_device *pdev) +{ + struct lpass_data *drvdata = platform_get_drvdata(pdev); + struct lpass_variant *variant = drvdata->variant; + struct device *dev = &pdev->dev; + int ret, i; + + + drvdata->clks = devm_kcalloc(dev, variant->num_clks, + sizeof(*drvdata->clks), GFP_KERNEL); + if (!drvdata->clks) + return -ENOMEM; + drvdata->num_clks = variant->num_clks; + + for (i = 0; i < drvdata->num_clks; i++) + drvdata->clks[i].id = variant->clk_name[i]; + + ret = devm_clk_bulk_get(dev, drvdata->num_clks, drvdata->clks); + if (ret) { + dev_err(dev, "Failed to get clocks %d\n", ret); + return ret; + } + + ret = clk_bulk_prepare_enable(drvdata->num_clks, drvdata->clks); + if (ret) { + dev_err(dev, "apq8016 clk_enable failed\n"); + return ret; + } + + drvdata->ahbix_clk = devm_clk_get(dev, "ahbix-clk"); + if (IS_ERR(drvdata->ahbix_clk)) { + dev_err(dev, "error getting ahbix-clk: %ld\n", + PTR_ERR(drvdata->ahbix_clk)); + ret = PTR_ERR(drvdata->ahbix_clk); + goto err_ahbix_clk; + } + + ret = clk_set_rate(drvdata->ahbix_clk, LPASS_AHBIX_CLOCK_FREQUENCY); + if (ret) { + dev_err(dev, "error setting rate on ahbix_clk: %d\n", ret); + goto err_ahbix_clk; + } + dev_dbg(dev, "set ahbix_clk rate to %lu\n", + clk_get_rate(drvdata->ahbix_clk)); + + ret = clk_prepare_enable(drvdata->ahbix_clk); + if (ret) { + dev_err(dev, "error enabling ahbix_clk: %d\n", ret); + goto err_ahbix_clk; + } + + return 0; + +err_ahbix_clk: + clk_bulk_disable_unprepare(drvdata->num_clks, drvdata->clks); + return ret; +} + +static int apq8016_lpass_exit(struct platform_device *pdev) +{ + struct lpass_data *drvdata = platform_get_drvdata(pdev); + + clk_bulk_disable_unprepare(drvdata->num_clks, drvdata->clks); + clk_disable_unprepare(drvdata->ahbix_clk); + + return 0; +} + + +static struct lpass_variant apq8016_data = { + .i2sctrl_reg_base = 0x1000, + .i2sctrl_reg_stride = 0x1000, + .i2s_ports = 4, + .irq_reg_base = 0x6000, + .irq_reg_stride = 0x1000, + .irq_ports = 3, + .rdma_reg_base = 0x8400, + .rdma_reg_stride = 0x1000, + .rdma_channels = 2, + .dmactl_audif_start = 1, + .wrdma_reg_base = 0xB000, + .wrdma_reg_stride = 0x1000, + .wrdma_channel_start = 5, + .wrdma_channels = 2, + .loopback = REG_FIELD_ID(0x1000, 15, 15, 4, 0x1000), + .spken = REG_FIELD_ID(0x1000, 14, 14, 4, 0x1000), + .spkmode = REG_FIELD_ID(0x1000, 10, 13, 4, 0x1000), + .spkmono = REG_FIELD_ID(0x1000, 9, 9, 4, 0x1000), + .micen = REG_FIELD_ID(0x1000, 8, 8, 4, 0x1000), + .micmode = REG_FIELD_ID(0x1000, 4, 7, 4, 0x1000), + .micmono = REG_FIELD_ID(0x1000, 3, 3, 4, 0x1000), + .wssrc = REG_FIELD_ID(0x1000, 2, 2, 4, 0x1000), + .bitwidth = REG_FIELD_ID(0x1000, 0, 1, 4, 0x1000), + + .rdma_dyncclk = REG_FIELD_ID(0x8400, 12, 12, 2, 0x1000), + .rdma_bursten = REG_FIELD_ID(0x8400, 11, 11, 2, 0x1000), + .rdma_wpscnt = REG_FIELD_ID(0x8400, 8, 10, 2, 0x1000), + .rdma_intf = REG_FIELD_ID(0x8400, 4, 7, 2, 0x1000), + .rdma_fifowm = REG_FIELD_ID(0x8400, 1, 3, 2, 0x1000), + .rdma_enable = REG_FIELD_ID(0x8400, 0, 0, 2, 0x1000), + + .wrdma_dyncclk = REG_FIELD_ID(0xB000, 12, 12, 2, 0x1000), + .wrdma_bursten = REG_FIELD_ID(0xB000, 11, 11, 2, 0x1000), + .wrdma_wpscnt = REG_FIELD_ID(0xB000, 8, 10, 2, 0x1000), + .wrdma_intf = REG_FIELD_ID(0xB000, 4, 7, 2, 0x1000), + .wrdma_fifowm = REG_FIELD_ID(0xB000, 1, 3, 2, 0x1000), + .wrdma_enable = REG_FIELD_ID(0xB000, 0, 0, 2, 0x1000), + + .clk_name = (const char*[]) { + "pcnoc-mport-clk", + "pcnoc-sway-clk", + }, + .num_clks = 2, + .dai_driver = apq8016_lpass_cpu_dai_driver, + .num_dai = ARRAY_SIZE(apq8016_lpass_cpu_dai_driver), + .dai_osr_clk_names = (const char *[]) { + "mi2s-osr-clk0", + "mi2s-osr-clk1", + "mi2s-osr-clk2", + "mi2s-osr-clk3", + }, + .dai_bit_clk_names = (const char *[]) { + "mi2s-bit-clk0", + "mi2s-bit-clk1", + "mi2s-bit-clk2", + "mi2s-bit-clk3", + }, + .init = apq8016_lpass_init, + .exit = apq8016_lpass_exit, + .alloc_dma_channel = apq8016_lpass_alloc_dma_channel, + .free_dma_channel = apq8016_lpass_free_dma_channel, +}; + +static const struct of_device_id apq8016_lpass_cpu_device_id[] = { + { .compatible = "qcom,lpass-cpu-apq8016", .data = &apq8016_data }, + {} +}; +MODULE_DEVICE_TABLE(of, apq8016_lpass_cpu_device_id); + +static struct platform_driver apq8016_lpass_cpu_platform_driver = { + .driver = { + .name = "apq8016-lpass-cpu", + .of_match_table = of_match_ptr(apq8016_lpass_cpu_device_id), + }, + .probe = asoc_qcom_lpass_cpu_platform_probe, + .remove = asoc_qcom_lpass_cpu_platform_remove, +}; +module_platform_driver(apq8016_lpass_cpu_platform_driver); + +MODULE_DESCRIPTION("APQ8016 LPASS CPU Driver"); +MODULE_LICENSE("GPL v2"); + diff --git a/sound/soc/qcom/lpass-cpu.c b/sound/soc/qcom/lpass-cpu.c new file mode 100644 index 000000000..9e70c193d --- /dev/null +++ b/sound/soc/qcom/lpass-cpu.c @@ -0,0 +1,998 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved. + * + * lpass-cpu.c -- ALSA SoC CPU DAI driver for QTi LPASS + */ + +#include <linux/clk.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <linux/regmap.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include "lpass-lpaif-reg.h" +#include "lpass.h" + +#define LPASS_CPU_MAX_MI2S_LINES 4 +#define LPASS_CPU_I2S_SD0_MASK BIT(0) +#define LPASS_CPU_I2S_SD1_MASK BIT(1) +#define LPASS_CPU_I2S_SD2_MASK BIT(2) +#define LPASS_CPU_I2S_SD3_MASK BIT(3) +#define LPASS_CPU_I2S_SD0_1_MASK GENMASK(1, 0) +#define LPASS_CPU_I2S_SD2_3_MASK GENMASK(3, 2) +#define LPASS_CPU_I2S_SD0_1_2_MASK GENMASK(2, 0) +#define LPASS_CPU_I2S_SD0_1_2_3_MASK GENMASK(3, 0) + +static int lpass_cpu_init_i2sctl_bitfields(struct device *dev, + struct lpaif_i2sctl *i2sctl, struct regmap *map) +{ + struct lpass_data *drvdata = dev_get_drvdata(dev); + struct lpass_variant *v = drvdata->variant; + + i2sctl->loopback = devm_regmap_field_alloc(dev, map, v->loopback); + i2sctl->spken = devm_regmap_field_alloc(dev, map, v->spken); + i2sctl->spkmode = devm_regmap_field_alloc(dev, map, v->spkmode); + i2sctl->spkmono = devm_regmap_field_alloc(dev, map, v->spkmono); + i2sctl->micen = devm_regmap_field_alloc(dev, map, v->micen); + i2sctl->micmode = devm_regmap_field_alloc(dev, map, v->micmode); + i2sctl->micmono = devm_regmap_field_alloc(dev, map, v->micmono); + i2sctl->wssrc = devm_regmap_field_alloc(dev, map, v->wssrc); + i2sctl->bitwidth = devm_regmap_field_alloc(dev, map, v->bitwidth); + + if (IS_ERR(i2sctl->loopback) || IS_ERR(i2sctl->spken) || + IS_ERR(i2sctl->spkmode) || IS_ERR(i2sctl->spkmono) || + IS_ERR(i2sctl->micen) || IS_ERR(i2sctl->micmode) || + IS_ERR(i2sctl->micmono) || IS_ERR(i2sctl->wssrc) || + IS_ERR(i2sctl->bitwidth)) + return -EINVAL; + + return 0; +} + +static int lpass_cpu_daiops_set_sysclk(struct snd_soc_dai *dai, int clk_id, + unsigned int freq, int dir) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = clk_set_rate(drvdata->mi2s_osr_clk[dai->driver->id], freq); + if (ret) + dev_err(dai->dev, "error setting mi2s osrclk to %u: %d\n", + freq, ret); + + return ret; +} + +static int lpass_cpu_daiops_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret; + + ret = clk_prepare_enable(drvdata->mi2s_osr_clk[dai->driver->id]); + if (ret) { + dev_err(dai->dev, "error in enabling mi2s osr clk: %d\n", ret); + return ret; + } + ret = clk_prepare(drvdata->mi2s_bit_clk[dai->driver->id]); + if (ret) { + dev_err(dai->dev, "error in enabling mi2s bit clk: %d\n", ret); + clk_disable_unprepare(drvdata->mi2s_osr_clk[dai->driver->id]); + return ret; + } + return 0; +} + +static void lpass_cpu_daiops_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + struct lpaif_i2sctl *i2sctl = drvdata->i2sctl; + unsigned int id = dai->driver->id; + + clk_disable_unprepare(drvdata->mi2s_osr_clk[dai->driver->id]); + /* + * Ensure LRCLK is disabled even in device node validation. + * Will not impact if disabled in lpass_cpu_daiops_trigger() + * suspend. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + regmap_fields_write(i2sctl->spken, id, LPAIF_I2SCTL_SPKEN_DISABLE); + else + regmap_fields_write(i2sctl->micen, id, LPAIF_I2SCTL_MICEN_DISABLE); + + /* + * BCLK may not be enabled if lpass_cpu_daiops_prepare is called before + * lpass_cpu_daiops_shutdown. It's paired with the clk_enable in + * lpass_cpu_daiops_prepare. + */ + if (drvdata->mi2s_was_prepared[dai->driver->id]) { + drvdata->mi2s_was_prepared[dai->driver->id] = false; + clk_disable(drvdata->mi2s_bit_clk[dai->driver->id]); + } + + clk_unprepare(drvdata->mi2s_bit_clk[dai->driver->id]); +} + +static int lpass_cpu_daiops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + struct lpaif_i2sctl *i2sctl = drvdata->i2sctl; + unsigned int id = dai->driver->id; + snd_pcm_format_t format = params_format(params); + unsigned int channels = params_channels(params); + unsigned int rate = params_rate(params); + unsigned int mode; + unsigned int regval; + int bitwidth, ret; + + bitwidth = snd_pcm_format_width(format); + if (bitwidth < 0) { + dev_err(dai->dev, "invalid bit width given: %d\n", bitwidth); + return bitwidth; + } + + ret = regmap_fields_write(i2sctl->loopback, id, + LPAIF_I2SCTL_LOOPBACK_DISABLE); + if (ret) { + dev_err(dai->dev, "error updating loopback field: %d\n", ret); + return ret; + } + + ret = regmap_fields_write(i2sctl->wssrc, id, + LPAIF_I2SCTL_WSSRC_INTERNAL); + if (ret) { + dev_err(dai->dev, "error updating wssrc field: %d\n", ret); + return ret; + } + + switch (bitwidth) { + case 16: + regval = LPAIF_I2SCTL_BITWIDTH_16; + break; + case 24: + regval = LPAIF_I2SCTL_BITWIDTH_24; + break; + case 32: + regval = LPAIF_I2SCTL_BITWIDTH_32; + break; + default: + dev_err(dai->dev, "invalid bitwidth given: %d\n", bitwidth); + return -EINVAL; + } + + ret = regmap_fields_write(i2sctl->bitwidth, id, regval); + if (ret) { + dev_err(dai->dev, "error updating bitwidth field: %d\n", ret); + return ret; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + mode = drvdata->mi2s_playback_sd_mode[id]; + else + mode = drvdata->mi2s_capture_sd_mode[id]; + + if (!mode) { + dev_err(dai->dev, "no line is assigned\n"); + return -EINVAL; + } + + switch (channels) { + case 1: + case 2: + switch (mode) { + case LPAIF_I2SCTL_MODE_QUAD01: + case LPAIF_I2SCTL_MODE_6CH: + case LPAIF_I2SCTL_MODE_8CH: + mode = LPAIF_I2SCTL_MODE_SD0; + break; + case LPAIF_I2SCTL_MODE_QUAD23: + mode = LPAIF_I2SCTL_MODE_SD2; + break; + } + + break; + case 4: + if (mode < LPAIF_I2SCTL_MODE_QUAD01) { + dev_err(dai->dev, "cannot configure 4 channels with mode %d\n", + mode); + return -EINVAL; + } + + switch (mode) { + case LPAIF_I2SCTL_MODE_6CH: + case LPAIF_I2SCTL_MODE_8CH: + mode = LPAIF_I2SCTL_MODE_QUAD01; + break; + } + break; + case 6: + if (mode < LPAIF_I2SCTL_MODE_6CH) { + dev_err(dai->dev, "cannot configure 6 channels with mode %d\n", + mode); + return -EINVAL; + } + + switch (mode) { + case LPAIF_I2SCTL_MODE_8CH: + mode = LPAIF_I2SCTL_MODE_6CH; + break; + } + break; + case 8: + if (mode < LPAIF_I2SCTL_MODE_8CH) { + dev_err(dai->dev, "cannot configure 8 channels with mode %d\n", + mode); + return -EINVAL; + } + break; + default: + dev_err(dai->dev, "invalid channels given: %u\n", channels); + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = regmap_fields_write(i2sctl->spkmode, id, + LPAIF_I2SCTL_SPKMODE(mode)); + if (ret) { + dev_err(dai->dev, "error writing to i2sctl spkr mode: %d\n", + ret); + return ret; + } + if (channels >= 2) + ret = regmap_fields_write(i2sctl->spkmono, id, + LPAIF_I2SCTL_SPKMONO_STEREO); + else + ret = regmap_fields_write(i2sctl->spkmono, id, + LPAIF_I2SCTL_SPKMONO_MONO); + } else { + ret = regmap_fields_write(i2sctl->micmode, id, + LPAIF_I2SCTL_MICMODE(mode)); + if (ret) { + dev_err(dai->dev, "error writing to i2sctl mic mode: %d\n", + ret); + return ret; + } + if (channels >= 2) + ret = regmap_fields_write(i2sctl->micmono, id, + LPAIF_I2SCTL_MICMONO_STEREO); + else + ret = regmap_fields_write(i2sctl->micmono, id, + LPAIF_I2SCTL_MICMONO_MONO); + } + + if (ret) { + dev_err(dai->dev, "error writing to i2sctl channels mode: %d\n", + ret); + return ret; + } + + ret = clk_set_rate(drvdata->mi2s_bit_clk[id], + rate * bitwidth * 2); + if (ret) { + dev_err(dai->dev, "error setting mi2s bitclk to %u: %d\n", + rate * bitwidth * 2, ret); + return ret; + } + + return 0; +} + +static int lpass_cpu_daiops_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + struct lpaif_i2sctl *i2sctl = drvdata->i2sctl; + unsigned int id = dai->driver->id; + int ret = -EINVAL; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + /* + * Ensure lpass BCLK/LRCLK is enabled during + * device resume as lpass_cpu_daiops_prepare() is not called + * after the device resumes. We don't check mi2s_was_prepared before + * enable/disable BCLK in trigger events because: + * 1. These trigger events are paired, so the BCLK + * enable_count is balanced. + * 2. the BCLK can be shared (ex: headset and headset mic), + * we need to increase the enable_count so that we don't + * turn off the shared BCLK while other devices are using + * it. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = regmap_fields_write(i2sctl->spken, id, + LPAIF_I2SCTL_SPKEN_ENABLE); + } else { + ret = regmap_fields_write(i2sctl->micen, id, + LPAIF_I2SCTL_MICEN_ENABLE); + } + if (ret) + dev_err(dai->dev, "error writing to i2sctl reg: %d\n", + ret); + + ret = clk_enable(drvdata->mi2s_bit_clk[id]); + if (ret) { + dev_err(dai->dev, "error in enabling mi2s bit clk: %d\n", ret); + clk_disable(drvdata->mi2s_osr_clk[id]); + return ret; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + /* + * To ensure lpass BCLK/LRCLK is disabled during + * device suspend. + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = regmap_fields_write(i2sctl->spken, id, + LPAIF_I2SCTL_SPKEN_DISABLE); + } else { + ret = regmap_fields_write(i2sctl->micen, id, + LPAIF_I2SCTL_MICEN_DISABLE); + } + if (ret) + dev_err(dai->dev, "error writing to i2sctl reg: %d\n", + ret); + + clk_disable(drvdata->mi2s_bit_clk[dai->driver->id]); + + break; + } + + return ret; +} + +static int lpass_cpu_daiops_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + struct lpaif_i2sctl *i2sctl = drvdata->i2sctl; + unsigned int id = dai->driver->id; + int ret; + + /* + * Ensure lpass BCLK/LRCLK is enabled bit before playback/capture + * data flow starts. This allows other codec to have some delay before + * the data flow. + * (ex: to drop start up pop noise before capture starts). + */ + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = regmap_fields_write(i2sctl->spken, id, LPAIF_I2SCTL_SPKEN_ENABLE); + else + ret = regmap_fields_write(i2sctl->micen, id, LPAIF_I2SCTL_MICEN_ENABLE); + + if (ret) { + dev_err(dai->dev, "error writing to i2sctl reg: %d\n", ret); + return ret; + } + + /* + * Check mi2s_was_prepared before enabling BCLK as lpass_cpu_daiops_prepare can + * be called multiple times. It's paired with the clk_disable in + * lpass_cpu_daiops_shutdown. + */ + if (!drvdata->mi2s_was_prepared[dai->driver->id]) { + ret = clk_enable(drvdata->mi2s_bit_clk[id]); + if (ret) { + dev_err(dai->dev, "error in enabling mi2s bit clk: %d\n", ret); + return ret; + } + drvdata->mi2s_was_prepared[dai->driver->id] = true; + } + return 0; +} + +const struct snd_soc_dai_ops asoc_qcom_lpass_cpu_dai_ops = { + .set_sysclk = lpass_cpu_daiops_set_sysclk, + .startup = lpass_cpu_daiops_startup, + .shutdown = lpass_cpu_daiops_shutdown, + .hw_params = lpass_cpu_daiops_hw_params, + .trigger = lpass_cpu_daiops_trigger, + .prepare = lpass_cpu_daiops_prepare, +}; +EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_dai_ops); + +int asoc_qcom_lpass_cpu_dai_probe(struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + int ret; + + /* ensure audio hardware is disabled */ + ret = regmap_write(drvdata->lpaif_map, + LPAIF_I2SCTL_REG(drvdata->variant, dai->driver->id), 0); + if (ret) + dev_err(dai->dev, "error writing to i2sctl reg: %d\n", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_dai_probe); + +static int asoc_qcom_of_xlate_dai_name(struct snd_soc_component *component, + struct of_phandle_args *args, + const char **dai_name) +{ + struct lpass_data *drvdata = snd_soc_component_get_drvdata(component); + struct lpass_variant *variant = drvdata->variant; + int id = args->args[0]; + int ret = -EINVAL; + int i; + + for (i = 0; i < variant->num_dai; i++) { + if (variant->dai_driver[i].id == id) { + *dai_name = variant->dai_driver[i].name; + ret = 0; + break; + } + } + + return ret; +} + +static const struct snd_soc_component_driver lpass_cpu_comp_driver = { + .name = "lpass-cpu", + .of_xlate_dai_name = asoc_qcom_of_xlate_dai_name, +}; + +static bool lpass_cpu_regmap_writeable(struct device *dev, unsigned int reg) +{ + struct lpass_data *drvdata = dev_get_drvdata(dev); + struct lpass_variant *v = drvdata->variant; + int i; + + for (i = 0; i < v->i2s_ports; ++i) + if (reg == LPAIF_I2SCTL_REG(v, i)) + return true; + + for (i = 0; i < v->irq_ports; ++i) { + if (reg == LPAIF_IRQEN_REG(v, i)) + return true; + if (reg == LPAIF_IRQCLEAR_REG(v, i)) + return true; + } + + for (i = 0; i < v->rdma_channels; ++i) { + if (reg == LPAIF_RDMACTL_REG(v, i)) + return true; + if (reg == LPAIF_RDMABASE_REG(v, i)) + return true; + if (reg == LPAIF_RDMABUFF_REG(v, i)) + return true; + if (reg == LPAIF_RDMAPER_REG(v, i)) + return true; + } + + for (i = 0; i < v->wrdma_channels; ++i) { + if (reg == LPAIF_WRDMACTL_REG(v, i + v->wrdma_channel_start)) + return true; + if (reg == LPAIF_WRDMABASE_REG(v, i + v->wrdma_channel_start)) + return true; + if (reg == LPAIF_WRDMABUFF_REG(v, i + v->wrdma_channel_start)) + return true; + if (reg == LPAIF_WRDMAPER_REG(v, i + v->wrdma_channel_start)) + return true; + } + + return false; +} + +static bool lpass_cpu_regmap_readable(struct device *dev, unsigned int reg) +{ + struct lpass_data *drvdata = dev_get_drvdata(dev); + struct lpass_variant *v = drvdata->variant; + int i; + + for (i = 0; i < v->i2s_ports; ++i) + if (reg == LPAIF_I2SCTL_REG(v, i)) + return true; + + for (i = 0; i < v->irq_ports; ++i) { + if (reg == LPAIF_IRQEN_REG(v, i)) + return true; + if (reg == LPAIF_IRQSTAT_REG(v, i)) + return true; + } + + for (i = 0; i < v->rdma_channels; ++i) { + if (reg == LPAIF_RDMACTL_REG(v, i)) + return true; + if (reg == LPAIF_RDMABASE_REG(v, i)) + return true; + if (reg == LPAIF_RDMABUFF_REG(v, i)) + return true; + if (reg == LPAIF_RDMACURR_REG(v, i)) + return true; + if (reg == LPAIF_RDMAPER_REG(v, i)) + return true; + } + + for (i = 0; i < v->wrdma_channels; ++i) { + if (reg == LPAIF_WRDMACTL_REG(v, i + v->wrdma_channel_start)) + return true; + if (reg == LPAIF_WRDMABASE_REG(v, i + v->wrdma_channel_start)) + return true; + if (reg == LPAIF_WRDMABUFF_REG(v, i + v->wrdma_channel_start)) + return true; + if (reg == LPAIF_WRDMACURR_REG(v, i + v->wrdma_channel_start)) + return true; + if (reg == LPAIF_WRDMAPER_REG(v, i + v->wrdma_channel_start)) + return true; + } + + return false; +} + +static bool lpass_cpu_regmap_volatile(struct device *dev, unsigned int reg) +{ + struct lpass_data *drvdata = dev_get_drvdata(dev); + struct lpass_variant *v = drvdata->variant; + int i; + + for (i = 0; i < v->irq_ports; ++i) + if (reg == LPAIF_IRQSTAT_REG(v, i)) + return true; + + for (i = 0; i < v->rdma_channels; ++i) + if (reg == LPAIF_RDMACURR_REG(v, i)) + return true; + + for (i = 0; i < v->wrdma_channels; ++i) + if (reg == LPAIF_WRDMACURR_REG(v, i + v->wrdma_channel_start)) + return true; + + return false; +} + +static struct regmap_config lpass_cpu_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .writeable_reg = lpass_cpu_regmap_writeable, + .readable_reg = lpass_cpu_regmap_readable, + .volatile_reg = lpass_cpu_regmap_volatile, + .cache_type = REGCACHE_FLAT, +}; + +static int lpass_hdmi_init_bitfields(struct device *dev, struct regmap *map) +{ + struct lpass_data *drvdata = dev_get_drvdata(dev); + struct lpass_variant *v = drvdata->variant; + unsigned int i; + struct lpass_hdmi_tx_ctl *tx_ctl; + struct regmap_field *legacy_en; + struct lpass_vbit_ctrl *vbit_ctl; + struct regmap_field *tx_parity; + struct lpass_dp_metadata_ctl *meta_ctl; + struct lpass_sstream_ctl *sstream_ctl; + struct regmap_field *ch_msb; + struct regmap_field *ch_lsb; + struct lpass_hdmitx_dmactl *tx_dmactl; + int rval; + + tx_ctl = devm_kzalloc(dev, sizeof(*tx_ctl), GFP_KERNEL); + if (!tx_ctl) + return -ENOMEM; + + QCOM_REGMAP_FIELD_ALLOC(dev, map, v->soft_reset, tx_ctl->soft_reset); + QCOM_REGMAP_FIELD_ALLOC(dev, map, v->force_reset, tx_ctl->force_reset); + drvdata->tx_ctl = tx_ctl; + + QCOM_REGMAP_FIELD_ALLOC(dev, map, v->legacy_en, legacy_en); + drvdata->hdmitx_legacy_en = legacy_en; + + vbit_ctl = devm_kzalloc(dev, sizeof(*vbit_ctl), GFP_KERNEL); + if (!vbit_ctl) + return -ENOMEM; + + QCOM_REGMAP_FIELD_ALLOC(dev, map, v->replace_vbit, vbit_ctl->replace_vbit); + QCOM_REGMAP_FIELD_ALLOC(dev, map, v->vbit_stream, vbit_ctl->vbit_stream); + drvdata->vbit_ctl = vbit_ctl; + + + QCOM_REGMAP_FIELD_ALLOC(dev, map, v->calc_en, tx_parity); + drvdata->hdmitx_parity_calc_en = tx_parity; + + meta_ctl = devm_kzalloc(dev, sizeof(*meta_ctl), GFP_KERNEL); + if (!meta_ctl) + return -ENOMEM; + + rval = devm_regmap_field_bulk_alloc(dev, map, &meta_ctl->mute, &v->mute, 7); + if (rval) + return rval; + drvdata->meta_ctl = meta_ctl; + + sstream_ctl = devm_kzalloc(dev, sizeof(*sstream_ctl), GFP_KERNEL); + if (!sstream_ctl) + return -ENOMEM; + + rval = devm_regmap_field_bulk_alloc(dev, map, &sstream_ctl->sstream_en, &v->sstream_en, 9); + if (rval) + return rval; + + drvdata->sstream_ctl = sstream_ctl; + + for (i = 0; i < LPASS_MAX_HDMI_DMA_CHANNELS; i++) { + QCOM_REGMAP_FIELD_ALLOC(dev, map, v->msb_bits, ch_msb); + drvdata->hdmitx_ch_msb[i] = ch_msb; + + QCOM_REGMAP_FIELD_ALLOC(dev, map, v->lsb_bits, ch_lsb); + drvdata->hdmitx_ch_lsb[i] = ch_lsb; + + tx_dmactl = devm_kzalloc(dev, sizeof(*tx_dmactl), GFP_KERNEL); + if (!tx_dmactl) + return -ENOMEM; + + QCOM_REGMAP_FIELD_ALLOC(dev, map, v->use_hw_chs, tx_dmactl->use_hw_chs); + QCOM_REGMAP_FIELD_ALLOC(dev, map, v->use_hw_usr, tx_dmactl->use_hw_usr); + QCOM_REGMAP_FIELD_ALLOC(dev, map, v->hw_chs_sel, tx_dmactl->hw_chs_sel); + QCOM_REGMAP_FIELD_ALLOC(dev, map, v->hw_usr_sel, tx_dmactl->hw_usr_sel); + drvdata->hdmi_tx_dmactl[i] = tx_dmactl; + } + return 0; +} + +static bool lpass_hdmi_regmap_writeable(struct device *dev, unsigned int reg) +{ + struct lpass_data *drvdata = dev_get_drvdata(dev); + struct lpass_variant *v = drvdata->variant; + int i; + + if (reg == LPASS_HDMI_TX_CTL_ADDR(v)) + return true; + if (reg == LPASS_HDMI_TX_LEGACY_ADDR(v)) + return true; + if (reg == LPASS_HDMI_TX_VBIT_CTL_ADDR(v)) + return true; + if (reg == LPASS_HDMI_TX_PARITY_ADDR(v)) + return true; + if (reg == LPASS_HDMI_TX_DP_ADDR(v)) + return true; + if (reg == LPASS_HDMI_TX_SSTREAM_ADDR(v)) + return true; + if (reg == LPASS_HDMITX_APP_IRQEN_REG(v)) + return true; + if (reg == LPASS_HDMITX_APP_IRQCLEAR_REG(v)) + return true; + + for (i = 0; i < v->hdmi_rdma_channels; i++) { + if (reg == LPASS_HDMI_TX_CH_LSB_ADDR(v, i)) + return true; + if (reg == LPASS_HDMI_TX_CH_MSB_ADDR(v, i)) + return true; + if (reg == LPASS_HDMI_TX_DMA_ADDR(v, i)) + return true; + } + + for (i = 0; i < v->hdmi_rdma_channels; ++i) { + if (reg == LPAIF_HDMI_RDMACTL_REG(v, i)) + return true; + if (reg == LPAIF_HDMI_RDMABASE_REG(v, i)) + return true; + if (reg == LPAIF_HDMI_RDMABUFF_REG(v, i)) + return true; + if (reg == LPAIF_HDMI_RDMAPER_REG(v, i)) + return true; + } + return false; +} + +static bool lpass_hdmi_regmap_readable(struct device *dev, unsigned int reg) +{ + struct lpass_data *drvdata = dev_get_drvdata(dev); + struct lpass_variant *v = drvdata->variant; + int i; + + if (reg == LPASS_HDMI_TX_CTL_ADDR(v)) + return true; + if (reg == LPASS_HDMI_TX_LEGACY_ADDR(v)) + return true; + if (reg == LPASS_HDMI_TX_VBIT_CTL_ADDR(v)) + return true; + + for (i = 0; i < v->hdmi_rdma_channels; i++) { + if (reg == LPASS_HDMI_TX_CH_LSB_ADDR(v, i)) + return true; + if (reg == LPASS_HDMI_TX_CH_MSB_ADDR(v, i)) + return true; + if (reg == LPASS_HDMI_TX_DMA_ADDR(v, i)) + return true; + } + + if (reg == LPASS_HDMI_TX_PARITY_ADDR(v)) + return true; + if (reg == LPASS_HDMI_TX_DP_ADDR(v)) + return true; + if (reg == LPASS_HDMI_TX_SSTREAM_ADDR(v)) + return true; + if (reg == LPASS_HDMITX_APP_IRQEN_REG(v)) + return true; + if (reg == LPASS_HDMITX_APP_IRQSTAT_REG(v)) + return true; + + for (i = 0; i < v->hdmi_rdma_channels; ++i) { + if (reg == LPAIF_HDMI_RDMACTL_REG(v, i)) + return true; + if (reg == LPAIF_HDMI_RDMABASE_REG(v, i)) + return true; + if (reg == LPAIF_HDMI_RDMABUFF_REG(v, i)) + return true; + if (reg == LPAIF_HDMI_RDMAPER_REG(v, i)) + return true; + if (reg == LPAIF_HDMI_RDMACURR_REG(v, i)) + return true; + } + + return false; +} + +static bool lpass_hdmi_regmap_volatile(struct device *dev, unsigned int reg) +{ + struct lpass_data *drvdata = dev_get_drvdata(dev); + struct lpass_variant *v = drvdata->variant; + int i; + + if (reg == LPASS_HDMITX_APP_IRQSTAT_REG(v)) + return true; + if (reg == LPASS_HDMI_TX_LEGACY_ADDR(v)) + return true; + if (reg == LPASS_HDMI_TX_VBIT_CTL_ADDR(v)) + return true; + if (reg == LPASS_HDMI_TX_PARITY_ADDR(v)) + return true; + + for (i = 0; i < v->hdmi_rdma_channels; ++i) { + if (reg == LPAIF_HDMI_RDMACURR_REG(v, i)) + return true; + if (reg == LPASS_HDMI_TX_DMA_ADDR(v, i)) + return true; + if (reg == LPASS_HDMI_TX_CH_LSB_ADDR(v, i)) + return true; + if (reg == LPASS_HDMI_TX_CH_MSB_ADDR(v, i)) + return true; + } + return false; +} + +struct regmap_config lpass_hdmi_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .writeable_reg = lpass_hdmi_regmap_writeable, + .readable_reg = lpass_hdmi_regmap_readable, + .volatile_reg = lpass_hdmi_regmap_volatile, + .cache_type = REGCACHE_FLAT, +}; + +static unsigned int of_lpass_cpu_parse_sd_lines(struct device *dev, + struct device_node *node, + const char *name) +{ + unsigned int lines[LPASS_CPU_MAX_MI2S_LINES]; + unsigned int sd_line_mask = 0; + int num_lines, i; + + num_lines = of_property_read_variable_u32_array(node, name, lines, 0, + LPASS_CPU_MAX_MI2S_LINES); + if (num_lines < 0) + return LPAIF_I2SCTL_MODE_NONE; + + for (i = 0; i < num_lines; i++) + sd_line_mask |= BIT(lines[i]); + + switch (sd_line_mask) { + case LPASS_CPU_I2S_SD0_MASK: + return LPAIF_I2SCTL_MODE_SD0; + case LPASS_CPU_I2S_SD1_MASK: + return LPAIF_I2SCTL_MODE_SD1; + case LPASS_CPU_I2S_SD2_MASK: + return LPAIF_I2SCTL_MODE_SD2; + case LPASS_CPU_I2S_SD3_MASK: + return LPAIF_I2SCTL_MODE_SD3; + case LPASS_CPU_I2S_SD0_1_MASK: + return LPAIF_I2SCTL_MODE_QUAD01; + case LPASS_CPU_I2S_SD2_3_MASK: + return LPAIF_I2SCTL_MODE_QUAD23; + case LPASS_CPU_I2S_SD0_1_2_MASK: + return LPAIF_I2SCTL_MODE_6CH; + case LPASS_CPU_I2S_SD0_1_2_3_MASK: + return LPAIF_I2SCTL_MODE_8CH; + default: + dev_err(dev, "Unsupported SD line mask: %#x\n", sd_line_mask); + return LPAIF_I2SCTL_MODE_NONE; + } +} + +static void of_lpass_cpu_parse_dai_data(struct device *dev, + struct lpass_data *data) +{ + struct device_node *node; + int ret, i, id; + + /* Allow all channels by default for backwards compatibility */ + for (i = 0; i < data->variant->num_dai; i++) { + id = data->variant->dai_driver[i].id; + data->mi2s_playback_sd_mode[id] = LPAIF_I2SCTL_MODE_8CH; + data->mi2s_capture_sd_mode[id] = LPAIF_I2SCTL_MODE_8CH; + } + + for_each_child_of_node(dev->of_node, node) { + ret = of_property_read_u32(node, "reg", &id); + if (ret || id < 0) { + dev_err(dev, "valid dai id not found: %d\n", ret); + continue; + } + if (id == LPASS_DP_RX) { + data->hdmi_port_enable = 1; + } else { + data->mi2s_playback_sd_mode[id] = + of_lpass_cpu_parse_sd_lines(dev, node, + "qcom,playback-sd-lines"); + data->mi2s_capture_sd_mode[id] = + of_lpass_cpu_parse_sd_lines(dev, node, + "qcom,capture-sd-lines"); + } + } +} + +int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev) +{ + struct lpass_data *drvdata; + struct device_node *dsp_of_node; + struct resource *res; + struct lpass_variant *variant; + struct device *dev = &pdev->dev; + const struct of_device_id *match; + int ret, i, dai_id; + + dsp_of_node = of_parse_phandle(pdev->dev.of_node, "qcom,adsp", 0); + if (dsp_of_node) { + dev_err(dev, "DSP exists and holds audio resources\n"); + of_node_put(dsp_of_node); + return -EBUSY; + } + + drvdata = devm_kzalloc(dev, sizeof(struct lpass_data), GFP_KERNEL); + if (!drvdata) + return -ENOMEM; + platform_set_drvdata(pdev, drvdata); + + match = of_match_device(dev->driver->of_match_table, dev); + if (!match || !match->data) + return -EINVAL; + + drvdata->variant = (struct lpass_variant *)match->data; + variant = drvdata->variant; + + of_lpass_cpu_parse_dai_data(dev, drvdata); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lpass-lpaif"); + + drvdata->lpaif = devm_ioremap_resource(dev, res); + if (IS_ERR((void const __force *)drvdata->lpaif)) { + dev_err(dev, "error mapping reg resource: %ld\n", + PTR_ERR((void const __force *)drvdata->lpaif)); + return PTR_ERR((void const __force *)drvdata->lpaif); + } + + lpass_cpu_regmap_config.max_register = LPAIF_WRDMAPER_REG(variant, + variant->wrdma_channels + + variant->wrdma_channel_start); + + drvdata->lpaif_map = devm_regmap_init_mmio(dev, drvdata->lpaif, + &lpass_cpu_regmap_config); + if (IS_ERR(drvdata->lpaif_map)) { + dev_err(dev, "error initializing regmap: %ld\n", + PTR_ERR(drvdata->lpaif_map)); + return PTR_ERR(drvdata->lpaif_map); + } + + if (drvdata->hdmi_port_enable) { + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lpass-hdmiif"); + + drvdata->hdmiif = devm_ioremap_resource(dev, res); + if (IS_ERR((void const __force *)drvdata->hdmiif)) { + dev_err(dev, "error mapping reg resource: %ld\n", + PTR_ERR((void const __force *)drvdata->hdmiif)); + return PTR_ERR((void const __force *)drvdata->hdmiif); + } + + lpass_hdmi_regmap_config.max_register = LPAIF_HDMI_RDMAPER_REG(variant, + variant->hdmi_rdma_channels - 1); + drvdata->hdmiif_map = devm_regmap_init_mmio(dev, drvdata->hdmiif, + &lpass_hdmi_regmap_config); + if (IS_ERR(drvdata->hdmiif_map)) { + dev_err(dev, "error initializing regmap: %ld\n", + PTR_ERR(drvdata->hdmiif_map)); + return PTR_ERR(drvdata->hdmiif_map); + } + } + + if (variant->init) { + ret = variant->init(pdev); + if (ret) { + dev_err(dev, "error initializing variant: %d\n", ret); + return ret; + } + } + + for (i = 0; i < variant->num_dai; i++) { + dai_id = variant->dai_driver[i].id; + if (dai_id == LPASS_DP_RX) + continue; + + drvdata->mi2s_osr_clk[dai_id] = devm_clk_get_optional(dev, + variant->dai_osr_clk_names[i]); + drvdata->mi2s_bit_clk[dai_id] = devm_clk_get(dev, + variant->dai_bit_clk_names[i]); + if (IS_ERR(drvdata->mi2s_bit_clk[dai_id])) { + dev_err(dev, + "error getting %s: %ld\n", + variant->dai_bit_clk_names[i], + PTR_ERR(drvdata->mi2s_bit_clk[dai_id])); + return PTR_ERR(drvdata->mi2s_bit_clk[dai_id]); + } + } + + /* Allocation for i2sctl regmap fields */ + drvdata->i2sctl = devm_kzalloc(&pdev->dev, sizeof(struct lpaif_i2sctl), + GFP_KERNEL); + + /* Initialize bitfields for dai I2SCTL register */ + ret = lpass_cpu_init_i2sctl_bitfields(dev, drvdata->i2sctl, + drvdata->lpaif_map); + if (ret) { + dev_err(dev, "error init i2sctl field: %d\n", ret); + return ret; + } + + if (drvdata->hdmi_port_enable) { + ret = lpass_hdmi_init_bitfields(dev, drvdata->hdmiif_map); + if (ret) { + dev_err(dev, "%s error hdmi init failed\n", __func__); + return ret; + } + } + ret = devm_snd_soc_register_component(dev, + &lpass_cpu_comp_driver, + variant->dai_driver, + variant->num_dai); + if (ret) { + dev_err(dev, "error registering cpu driver: %d\n", ret); + goto err; + } + + ret = asoc_qcom_lpass_platform_register(pdev); + if (ret) { + dev_err(dev, "error registering platform driver: %d\n", ret); + goto err; + } + +err: + return ret; +} +EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_platform_probe); + +int asoc_qcom_lpass_cpu_platform_remove(struct platform_device *pdev) +{ + struct lpass_data *drvdata = platform_get_drvdata(pdev); + + if (drvdata->variant->exit) + drvdata->variant->exit(pdev); + + + return 0; +} +EXPORT_SYMBOL_GPL(asoc_qcom_lpass_cpu_platform_remove); + +MODULE_DESCRIPTION("QTi LPASS CPU Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/lpass-hdmi.c b/sound/soc/qcom/lpass-hdmi.c new file mode 100644 index 000000000..abfb8737a --- /dev/null +++ b/sound/soc/qcom/lpass-hdmi.c @@ -0,0 +1,258 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020 The Linux Foundation. All rights reserved. + * + * lpass-hdmi.c -- ALSA SoC HDMI-CPU DAI driver for QTi LPASS HDMI + */ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <sound/pcm_params.h> +#include <linux/regmap.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include <dt-bindings/sound/sc7180-lpass.h> +#include "lpass-lpaif-reg.h" +#include "lpass.h" + +static int lpass_hdmi_daiops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + snd_pcm_format_t format = params_format(params); + unsigned int rate = params_rate(params); + unsigned int channels = params_channels(params); + unsigned int ret; + int bitwidth; + unsigned int word_length; + unsigned int ch_sts_buf0; + unsigned int ch_sts_buf1; + unsigned int data_format; + unsigned int sampling_freq; + unsigned int ch = 0; + struct lpass_dp_metadata_ctl *meta_ctl = drvdata->meta_ctl; + struct lpass_sstream_ctl *sstream_ctl = drvdata->sstream_ctl; + + bitwidth = snd_pcm_format_width(format); + if (bitwidth < 0) { + dev_err(dai->dev, "%s invalid bit width given : %d\n", + __func__, bitwidth); + return bitwidth; + } + + switch (bitwidth) { + case 16: + word_length = LPASS_DP_AUDIO_BITWIDTH16; + break; + case 24: + word_length = LPASS_DP_AUDIO_BITWIDTH24; + break; + default: + dev_err(dai->dev, "%s invalid bit width given : %d\n", + __func__, bitwidth); + return -EINVAL; + } + + switch (rate) { + case 32000: + sampling_freq = LPASS_SAMPLING_FREQ32; + break; + case 44100: + sampling_freq = LPASS_SAMPLING_FREQ44; + break; + case 48000: + sampling_freq = LPASS_SAMPLING_FREQ48; + break; + default: + dev_err(dai->dev, "%s invalid bit width given : %d\n", + __func__, bitwidth); + return -EINVAL; + } + data_format = LPASS_DATA_FORMAT_LINEAR; + ch_sts_buf0 = (((data_format << LPASS_DATA_FORMAT_SHIFT) & LPASS_DATA_FORMAT_MASK) + | ((sampling_freq << LPASS_FREQ_BIT_SHIFT) & LPASS_FREQ_BIT_MASK)); + ch_sts_buf1 = (word_length) & LPASS_WORDLENGTH_MASK; + + ret = regmap_field_write(drvdata->tx_ctl->soft_reset, LPASS_TX_CTL_RESET); + if (ret) + return ret; + + ret = regmap_field_write(drvdata->tx_ctl->soft_reset, LPASS_TX_CTL_CLEAR); + if (ret) + return ret; + + ret = regmap_field_write(drvdata->hdmitx_legacy_en, LPASS_HDMITX_LEGACY_DISABLE); + if (ret) + return ret; + + ret = regmap_field_write(drvdata->hdmitx_parity_calc_en, HDMITX_PARITY_CALC_EN); + if (ret) + return ret; + + ret = regmap_field_write(drvdata->vbit_ctl->replace_vbit, REPLACE_VBIT); + if (ret) + return ret; + + ret = regmap_field_write(drvdata->vbit_ctl->vbit_stream, LINEAR_PCM_DATA); + if (ret) + return ret; + + ret = regmap_field_write(drvdata->hdmitx_ch_msb[0], ch_sts_buf1); + if (ret) + return ret; + + ret = regmap_field_write(drvdata->hdmitx_ch_lsb[0], ch_sts_buf0); + if (ret) + return ret; + + ret = regmap_field_write(drvdata->hdmi_tx_dmactl[0]->use_hw_chs, HW_MODE); + if (ret) + return ret; + + ret = regmap_field_write(drvdata->hdmi_tx_dmactl[0]->hw_chs_sel, SW_MODE); + if (ret) + return ret; + + ret = regmap_field_write(drvdata->hdmi_tx_dmactl[0]->use_hw_usr, HW_MODE); + if (ret) + return ret; + + ret = regmap_field_write(drvdata->hdmi_tx_dmactl[0]->hw_usr_sel, SW_MODE); + if (ret) + return ret; + + ret = regmap_field_write(meta_ctl->mute, LPASS_MUTE_ENABLE); + if (ret) + return ret; + + ret = regmap_field_write(meta_ctl->as_sdp_cc, channels - 1); + if (ret) + return ret; + + ret = regmap_field_write(meta_ctl->as_sdp_ct, LPASS_META_DEFAULT_VAL); + if (ret) + return ret; + + ret = regmap_field_write(meta_ctl->aif_db4, LPASS_META_DEFAULT_VAL); + if (ret) + return ret; + + ret = regmap_field_write(meta_ctl->frequency, sampling_freq); + if (ret) + return ret; + + ret = regmap_field_write(meta_ctl->mst_index, LPASS_META_DEFAULT_VAL); + if (ret) + return ret; + + ret = regmap_field_write(meta_ctl->dptx_index, LPASS_META_DEFAULT_VAL); + if (ret) + return ret; + + ret = regmap_field_write(sstream_ctl->sstream_en, LPASS_SSTREAM_DISABLE); + if (ret) + return ret; + + ret = regmap_field_write(sstream_ctl->dma_sel, ch); + if (ret) + return ret; + + ret = regmap_field_write(sstream_ctl->auto_bbit_en, LPASS_SSTREAM_DEFAULT_ENABLE); + if (ret) + return ret; + + ret = regmap_field_write(sstream_ctl->layout, LPASS_SSTREAM_DEFAULT_DISABLE); + if (ret) + return ret; + + ret = regmap_field_write(sstream_ctl->layout_sp, LPASS_LAYOUT_SP_DEFAULT); + if (ret) + return ret; + + ret = regmap_field_write(sstream_ctl->dp_audio, LPASS_SSTREAM_DEFAULT_ENABLE); + if (ret) + return ret; + + ret = regmap_field_write(sstream_ctl->set_sp_on_en, LPASS_SSTREAM_DEFAULT_ENABLE); + if (ret) + return ret; + + ret = regmap_field_write(sstream_ctl->dp_sp_b_hw_en, LPASS_SSTREAM_DEFAULT_ENABLE); + if (ret) + return ret; + + ret = regmap_field_write(sstream_ctl->dp_staffing_en, LPASS_SSTREAM_DEFAULT_ENABLE); + if (ret) + return ret; + + return ret; +} + +static int lpass_hdmi_daiops_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + int ret; + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + + ret = regmap_field_write(drvdata->sstream_ctl->sstream_en, LPASS_SSTREAM_ENABLE); + if (ret) + return ret; + + ret = regmap_field_write(drvdata->meta_ctl->mute, LPASS_MUTE_DISABLE); + if (ret) + return ret; + + return ret; +} + +static int lpass_hdmi_daiops_trigger(struct snd_pcm_substream *substream, + int cmd, struct snd_soc_dai *dai) +{ + struct lpass_data *drvdata = snd_soc_dai_get_drvdata(dai); + struct lpass_dp_metadata_ctl *meta_ctl = drvdata->meta_ctl; + struct lpass_sstream_ctl *sstream_ctl = drvdata->sstream_ctl; + int ret = -EINVAL; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = regmap_field_write(sstream_ctl->sstream_en, LPASS_SSTREAM_ENABLE); + if (ret) + return ret; + + ret = regmap_field_write(meta_ctl->mute, LPASS_MUTE_DISABLE); + if (ret) + return ret; + + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = regmap_field_write(sstream_ctl->sstream_en, LPASS_SSTREAM_DISABLE); + if (ret) + return ret; + + ret = regmap_field_write(meta_ctl->mute, LPASS_MUTE_ENABLE); + if (ret) + return ret; + + ret = regmap_field_write(sstream_ctl->dp_audio, 0); + if (ret) + return ret; + + break; + } + return ret; +} + +const struct snd_soc_dai_ops asoc_qcom_lpass_hdmi_dai_ops = { + .hw_params = lpass_hdmi_daiops_hw_params, + .prepare = lpass_hdmi_daiops_prepare, + .trigger = lpass_hdmi_daiops_trigger, +}; +EXPORT_SYMBOL_GPL(asoc_qcom_lpass_hdmi_dai_ops); + +MODULE_DESCRIPTION("QTi LPASS HDMI Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/lpass-hdmi.h b/sound/soc/qcom/lpass-hdmi.h new file mode 100644 index 000000000..ee74d7830 --- /dev/null +++ b/sound/soc/qcom/lpass-hdmi.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020 The Linux Foundation. All rights reserved. + * + * lpass_hdmi.h - Definitions for the QTi LPASS HDMI + */ + +#ifndef __LPASS_HDMI_H__ +#define __LPASS_HDMI_H__ + +#include <linux/regmap.h> + +#define LPASS_HDMITX_LEGACY_DISABLE 0x0 +#define LPASS_HDMITX_LEGACY_ENABLE 0x1 +#define LPASS_DP_AUDIO_BITWIDTH16 0x0 +#define LPASS_DP_AUDIO_BITWIDTH24 0xb +#define LPASS_DATA_FORMAT_SHIFT 0x1 +#define LPASS_FREQ_BIT_SHIFT 24 +#define LPASS_DATA_FORMAT_LINEAR 0x0 +#define LPASS_DATA_FORMAT_NON_LINEAR 0x1 +#define LPASS_SAMPLING_FREQ32 0x3 +#define LPASS_SAMPLING_FREQ44 0x0 +#define LPASS_SAMPLING_FREQ48 0x2 +#define LPASS_TX_CTL_RESET 0x1 +#define LPASS_TX_CTL_CLEAR 0x0 +#define LPASS_SSTREAM_ENABLE 1 +#define LPASS_SSTREAM_DISABLE 0 +#define LPASS_LAYOUT_SP_DEFAULT 0xf +#define LPASS_SSTREAM_DEFAULT_ENABLE 1 +#define LPASS_SSTREAM_DEFAULT_DISABLE 0 +#define LPASS_MUTE_ENABLE 1 +#define LPASS_MUTE_DISABLE 0 +#define LPASS_META_DEFAULT_VAL 0 +#define HW_MODE 1 +#define SW_MODE 0 +#define LEGACY_LPASS_LPAIF 1 +#define LEGACY_LPASS_HDMI 0 +#define REPLACE_VBIT 0x1 +#define LINEAR_PCM_DATA 0x0 +#define NON_LINEAR_PCM_DATA 0x1 +#define HDMITX_PARITY_CALC_EN 0x1 +#define HDMITX_PARITY_CALC_DIS 0x0 +#define LPASS_DATA_FORMAT_MASK GENMASK(1, 1) +#define LPASS_WORDLENGTH_MASK GENMASK(3, 0) +#define LPASS_FREQ_BIT_MASK GENMASK(27, 24) + +#define LPASS_HDMI_TX_CTL_ADDR(v) (v->hdmi_tx_ctl_addr) +#define LPASS_HDMI_TX_LEGACY_ADDR(v) (v->hdmi_legacy_addr) +#define LPASS_HDMI_TX_VBIT_CTL_ADDR(v) (v->hdmi_vbit_addr) +#define LPASS_HDMI_TX_PARITY_ADDR(v) (v->hdmi_parity_addr) +#define LPASS_HDMI_TX_DP_ADDR(v) (v->hdmi_DP_addr) +#define LPASS_HDMI_TX_SSTREAM_ADDR(v) (v->hdmi_sstream_addr) + +#define LPASS_HDMI_TX_CH_LSB_ADDR(v, port) \ + (v->hdmi_ch_lsb_addr + v->ch_stride * (port)) +#define LPASS_HDMI_TX_CH_MSB_ADDR(v, port) \ + (v->hdmi_ch_msb_addr + v->ch_stride * (port)) +#define LPASS_HDMI_TX_DMA_ADDR(v, port) \ + (v->hdmi_dmactl_addr + v->hdmi_dma_stride * (port)) + +struct lpass_sstream_ctl { + struct regmap_field *sstream_en; + struct regmap_field *dma_sel; + struct regmap_field *auto_bbit_en; + struct regmap_field *layout; + struct regmap_field *layout_sp; + struct regmap_field *set_sp_on_en; + struct regmap_field *dp_audio; + struct regmap_field *dp_staffing_en; + struct regmap_field *dp_sp_b_hw_en; +}; + +struct lpass_dp_metadata_ctl { + struct regmap_field *mute; + struct regmap_field *as_sdp_cc; + struct regmap_field *as_sdp_ct; + struct regmap_field *aif_db4; + struct regmap_field *frequency; + struct regmap_field *mst_index; + struct regmap_field *dptx_index; +}; + +struct lpass_hdmi_tx_ctl { + struct regmap_field *soft_reset; + struct regmap_field *force_reset; +}; + +struct lpass_hdmitx_dmactl { + struct regmap_field *use_hw_chs; + struct regmap_field *use_hw_usr; + struct regmap_field *hw_chs_sel; + struct regmap_field *hw_usr_sel; +}; + +struct lpass_vbit_ctrl { + struct regmap_field *replace_vbit; + struct regmap_field *vbit_stream; +}; + +extern const struct snd_soc_dai_ops asoc_qcom_lpass_hdmi_dai_ops; + +#endif /* __LPASS_HDMI_H__ */ diff --git a/sound/soc/qcom/lpass-ipq806x.c b/sound/soc/qcom/lpass-ipq806x.c new file mode 100644 index 000000000..3a45e6a26 --- /dev/null +++ b/sound/soc/qcom/lpass-ipq806x.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved. + * + * lpass-ipq806x.c -- ALSA SoC CPU DAI driver for QTi LPASS + * Splited out the IPQ8064 soc specific from lpass-cpu.c + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> + +#include "lpass-lpaif-reg.h" +#include "lpass.h" + +enum lpaif_i2s_ports { + IPQ806X_LPAIF_I2S_PORT_CODEC_SPK, + IPQ806X_LPAIF_I2S_PORT_CODEC_MIC, + IPQ806X_LPAIF_I2S_PORT_SEC_SPK, + IPQ806X_LPAIF_I2S_PORT_SEC_MIC, + IPQ806X_LPAIF_I2S_PORT_MI2S, +}; + +enum lpaif_dma_channels { + IPQ806X_LPAIF_RDMA_CHAN_MI2S, + IPQ806X_LPAIF_RDMA_CHAN_PCM0, + IPQ806X_LPAIF_RDMA_CHAN_PCM1, +}; + +static struct snd_soc_dai_driver ipq806x_lpass_cpu_dai_driver = { + .id = IPQ806X_LPAIF_I2S_PORT_MI2S, + .playback = { + .stream_name = "lpass-cpu-playback", + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000, + .rate_min = 8000, + .rate_max = 96000, + .channels_min = 1, + .channels_max = 8, + }, + .probe = &asoc_qcom_lpass_cpu_dai_probe, + .ops = &asoc_qcom_lpass_cpu_dai_ops, +}; + +static int ipq806x_lpass_init(struct platform_device *pdev) +{ + struct lpass_data *drvdata = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + int ret; + + drvdata->ahbix_clk = devm_clk_get(dev, "ahbix-clk"); + if (IS_ERR(drvdata->ahbix_clk)) { + dev_err(dev, "error getting ahbix-clk: %ld\n", + PTR_ERR(drvdata->ahbix_clk)); + ret = PTR_ERR(drvdata->ahbix_clk); + goto err_ahbix_clk; + } + + ret = clk_set_rate(drvdata->ahbix_clk, LPASS_AHBIX_CLOCK_FREQUENCY); + if (ret) { + dev_err(dev, "error setting rate on ahbix_clk: %d\n", ret); + goto err_ahbix_clk; + } + dev_dbg(dev, "set ahbix_clk rate to %lu\n", + clk_get_rate(drvdata->ahbix_clk)); + + ret = clk_prepare_enable(drvdata->ahbix_clk); + if (ret) { + dev_err(dev, "error enabling ahbix_clk: %d\n", ret); + goto err_ahbix_clk; + } + +err_ahbix_clk: + return ret; +} + +static int ipq806x_lpass_exit(struct platform_device *pdev) +{ + struct lpass_data *drvdata = platform_get_drvdata(pdev); + + clk_disable_unprepare(drvdata->ahbix_clk); + + return 0; +} + +static int ipq806x_lpass_alloc_dma_channel(struct lpass_data *drvdata, int dir, unsigned int dai_id) +{ + if (dir == SNDRV_PCM_STREAM_PLAYBACK) + return IPQ806X_LPAIF_RDMA_CHAN_MI2S; + else /* Capture currently not implemented */ + return -EINVAL; +} + +static int ipq806x_lpass_free_dma_channel(struct lpass_data *drvdata, int chan, unsigned int dai_id) +{ + return 0; +} + +static struct lpass_variant ipq806x_data = { + .i2sctrl_reg_base = 0x0010, + .i2sctrl_reg_stride = 0x04, + .i2s_ports = 5, + .irq_reg_base = 0x3000, + .irq_reg_stride = 0x1000, + .irq_ports = 3, + .rdma_reg_base = 0x6000, + .rdma_reg_stride = 0x1000, + .rdma_channels = 4, + .wrdma_reg_base = 0xB000, + .wrdma_reg_stride = 0x1000, + .wrdma_channel_start = 5, + .wrdma_channels = 4, + .loopback = REG_FIELD_ID(0x0010, 15, 15, 5, 0x4), + .spken = REG_FIELD_ID(0x0010, 14, 14, 5, 0x4), + .spkmode = REG_FIELD_ID(0x0010, 10, 13, 5, 0x4), + .spkmono = REG_FIELD_ID(0x0010, 9, 9, 5, 0x4), + .micen = REG_FIELD_ID(0x0010, 8, 8, 5, 0x4), + .micmode = REG_FIELD_ID(0x0010, 4, 7, 5, 0x4), + .micmono = REG_FIELD_ID(0x0010, 3, 3, 5, 0x4), + .wssrc = REG_FIELD_ID(0x0010, 2, 2, 5, 0x4), + .bitwidth = REG_FIELD_ID(0x0010, 0, 1, 5, 0x4), + + .rdma_dyncclk = REG_FIELD_ID(0x6000, 12, 12, 4, 0x1000), + .rdma_bursten = REG_FIELD_ID(0x6000, 11, 11, 4, 0x1000), + .rdma_wpscnt = REG_FIELD_ID(0x6000, 8, 10, 4, 0x1000), + .rdma_intf = REG_FIELD_ID(0x6000, 4, 7, 4, 0x1000), + .rdma_fifowm = REG_FIELD_ID(0x6000, 1, 3, 4, 0x1000), + .rdma_enable = REG_FIELD_ID(0x6000, 0, 0, 4, 0x1000), + + .wrdma_dyncclk = REG_FIELD_ID(0xB000, 12, 12, 4, 0x1000), + .wrdma_bursten = REG_FIELD_ID(0xB000, 11, 11, 4, 0x1000), + .wrdma_wpscnt = REG_FIELD_ID(0xB000, 8, 10, 4, 0x1000), + .wrdma_intf = REG_FIELD_ID(0xB000, 4, 7, 4, 0x1000), + .wrdma_fifowm = REG_FIELD_ID(0xB000, 1, 3, 4, 0x1000), + .wrdma_enable = REG_FIELD_ID(0xB000, 0, 0, 4, 0x1000), + + .dai_driver = &ipq806x_lpass_cpu_dai_driver, + .num_dai = 1, + .dai_osr_clk_names = (const char *[]) { + "mi2s-osr-clk", + }, + .dai_bit_clk_names = (const char *[]) { + "mi2s-bit-clk", + }, + .init = ipq806x_lpass_init, + .exit = ipq806x_lpass_exit, + .alloc_dma_channel = ipq806x_lpass_alloc_dma_channel, + .free_dma_channel = ipq806x_lpass_free_dma_channel, +}; + +static const struct of_device_id ipq806x_lpass_cpu_device_id[] = { + { .compatible = "qcom,lpass-cpu", .data = &ipq806x_data }, + {} +}; +MODULE_DEVICE_TABLE(of, ipq806x_lpass_cpu_device_id); + +static struct platform_driver ipq806x_lpass_cpu_platform_driver = { + .driver = { + .name = "lpass-cpu", + .of_match_table = of_match_ptr(ipq806x_lpass_cpu_device_id), + }, + .probe = asoc_qcom_lpass_cpu_platform_probe, + .remove = asoc_qcom_lpass_cpu_platform_remove, +}; +module_platform_driver(ipq806x_lpass_cpu_platform_driver); + +MODULE_DESCRIPTION("QTi LPASS CPU Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/lpass-lpaif-reg.h b/sound/soc/qcom/lpass-lpaif-reg.h new file mode 100644 index 000000000..2eb03ad9b --- /dev/null +++ b/sound/soc/qcom/lpass-lpaif-reg.h @@ -0,0 +1,204 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved. + */ + +#ifndef __LPASS_LPAIF_REG_H__ +#define __LPASS_LPAIF_REG_H__ + +/* LPAIF I2S */ + +#define LPAIF_I2SCTL_REG_ADDR(v, addr, port) \ + (v->i2sctrl_reg_base + (addr) + v->i2sctrl_reg_stride * (port)) + +#define LPAIF_I2SCTL_REG(v, port) LPAIF_I2SCTL_REG_ADDR(v, 0x0, (port)) + +#define LPAIF_I2SCTL_LOOPBACK_DISABLE 0 +#define LPAIF_I2SCTL_LOOPBACK_ENABLE 1 + +#define LPAIF_I2SCTL_SPKEN_DISABLE 0 +#define LPAIF_I2SCTL_SPKEN_ENABLE 1 + +#define LPAIF_I2SCTL_MODE_NONE 0 +#define LPAIF_I2SCTL_MODE_SD0 1 +#define LPAIF_I2SCTL_MODE_SD1 2 +#define LPAIF_I2SCTL_MODE_SD2 3 +#define LPAIF_I2SCTL_MODE_SD3 4 +#define LPAIF_I2SCTL_MODE_QUAD01 5 +#define LPAIF_I2SCTL_MODE_QUAD23 6 +#define LPAIF_I2SCTL_MODE_6CH 7 +#define LPAIF_I2SCTL_MODE_8CH 8 +#define LPAIF_I2SCTL_MODE_10CH 9 +#define LPAIF_I2SCTL_MODE_12CH 10 +#define LPAIF_I2SCTL_MODE_14CH 11 +#define LPAIF_I2SCTL_MODE_16CH 12 +#define LPAIF_I2SCTL_MODE_SD4 13 +#define LPAIF_I2SCTL_MODE_SD5 14 +#define LPAIF_I2SCTL_MODE_SD6 15 +#define LPAIF_I2SCTL_MODE_SD7 16 +#define LPAIF_I2SCTL_MODE_QUAD45 17 +#define LPAIF_I2SCTL_MODE_QUAD47 18 +#define LPAIF_I2SCTL_MODE_8CH_2 19 + +#define LPAIF_I2SCTL_SPKMODE(mode) mode + +#define LPAIF_I2SCTL_SPKMONO_STEREO 0 +#define LPAIF_I2SCTL_SPKMONO_MONO 1 + +#define LPAIF_I2SCTL_MICEN_DISABLE 0 +#define LPAIF_I2SCTL_MICEN_ENABLE 1 + +#define LPAIF_I2SCTL_MICMODE(mode) mode + +#define LPAIF_I2SCTL_MICMONO_STEREO 0 +#define LPAIF_I2SCTL_MICMONO_MONO 1 + +#define LPAIF_I2SCTL_WSSRC_INTERNAL 0 +#define LPAIF_I2SCTL_WSSRC_EXTERNAL 1 + +#define LPAIF_I2SCTL_BITWIDTH_16 0 +#define LPAIF_I2SCTL_BITWIDTH_24 1 +#define LPAIF_I2SCTL_BITWIDTH_32 2 + +#define LPAIF_I2SCTL_RESET_STATE 0x003C0004 +#define LPAIF_DMACTL_RESET_STATE 0x00200000 + + +/* LPAIF IRQ */ +#define LPAIF_IRQ_REG_ADDR(v, addr, port) \ + (v->irq_reg_base + (addr) + v->irq_reg_stride * (port)) + +#define LPAIF_IRQ_PORT_HOST 0 + +#define LPAIF_IRQEN_REG(v, port) LPAIF_IRQ_REG_ADDR(v, 0x0, (port)) +#define LPAIF_IRQSTAT_REG(v, port) LPAIF_IRQ_REG_ADDR(v, 0x4, (port)) +#define LPAIF_IRQCLEAR_REG(v, port) LPAIF_IRQ_REG_ADDR(v, 0xC, (port)) + + +#define LPASS_HDMITX_APP_IRQ_REG_ADDR(v, addr) \ + ((v->hdmi_irq_reg_base) + (addr)) + +#define LPASS_HDMITX_APP_IRQEN_REG(v) LPASS_HDMITX_APP_IRQ_REG_ADDR(v, 0x4) +#define LPASS_HDMITX_APP_IRQSTAT_REG(v) LPASS_HDMITX_APP_IRQ_REG_ADDR(v, 0x8) +#define LPASS_HDMITX_APP_IRQCLEAR_REG(v) LPASS_HDMITX_APP_IRQ_REG_ADDR(v, 0xC) + +#define LPAIF_IRQ_BITSTRIDE 3 + +#define LPAIF_IRQ_PER(chan) (1 << (LPAIF_IRQ_BITSTRIDE * (chan))) +#define LPAIF_IRQ_XRUN(chan) (2 << (LPAIF_IRQ_BITSTRIDE * (chan))) +#define LPAIF_IRQ_ERR(chan) (4 << (LPAIF_IRQ_BITSTRIDE * (chan))) + +#define LPAIF_IRQ_ALL(chan) (7 << (LPAIF_IRQ_BITSTRIDE * (chan))) +#define LPAIF_IRQ_HDMI_REQ_ON_PRELOAD(chan) (1 << (14 + chan)) +#define LPAIF_IRQ_HDMI_SDEEP_AUD_DIS(chan) (1 << (24 + chan)) +#define LPAIF_IRQ_HDMI_METADONE BIT(23) + +/* LPAIF DMA */ +#define LPAIF_HDMI_RDMA_REG_ADDR(v, addr, chan) \ + (v->hdmi_rdma_reg_base + (addr) + v->hdmi_rdma_reg_stride * (chan)) + +#define LPAIF_HDMI_RDMACTL_AUDINTF(id) (id << LPAIF_RDMACTL_AUDINTF_SHIFT) + +#define LPAIF_HDMI_RDMACTL_REG(v, chan) LPAIF_HDMI_RDMA_REG_ADDR(v, 0x00, (chan)) +#define LPAIF_HDMI_RDMABASE_REG(v, chan) LPAIF_HDMI_RDMA_REG_ADDR(v, 0x04, (chan)) +#define LPAIF_HDMI_RDMABUFF_REG(v, chan) LPAIF_HDMI_RDMA_REG_ADDR(v, 0x08, (chan)) +#define LPAIF_HDMI_RDMACURR_REG(v, chan) LPAIF_HDMI_RDMA_REG_ADDR(v, 0x0C, (chan)) +#define LPAIF_HDMI_RDMAPER_REG(v, chan) LPAIF_HDMI_RDMA_REG_ADDR(v, 0x10, (chan)) +#define LPAIF_HDMI_RDMAPERCNT_REG(v, chan) LPAIF_HDMI_RDMA_REG_ADDR(v, 0x14, (chan)) + +#define LPAIF_RDMA_REG_ADDR(v, addr, chan) \ + (v->rdma_reg_base + (addr) + v->rdma_reg_stride * (chan)) + +#define LPAIF_RDMACTL_AUDINTF(id) (id << LPAIF_RDMACTL_AUDINTF_SHIFT) + +#define LPAIF_RDMACTL_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x00, (chan)) +#define LPAIF_RDMABASE_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x04, (chan)) +#define LPAIF_RDMABUFF_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x08, (chan)) +#define LPAIF_RDMACURR_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x0C, (chan)) +#define LPAIF_RDMAPER_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x10, (chan)) +#define LPAIF_RDMAPERCNT_REG(v, chan) LPAIF_RDMA_REG_ADDR(v, 0x14, (chan)) + +#define LPAIF_WRDMA_REG_ADDR(v, addr, chan) \ + (v->wrdma_reg_base + (addr) + \ + v->wrdma_reg_stride * (chan - v->wrdma_channel_start)) + +#define LPAIF_WRDMACTL_REG(v, chan) LPAIF_WRDMA_REG_ADDR(v, 0x00, (chan)) +#define LPAIF_WRDMABASE_REG(v, chan) LPAIF_WRDMA_REG_ADDR(v, 0x04, (chan)) +#define LPAIF_WRDMABUFF_REG(v, chan) LPAIF_WRDMA_REG_ADDR(v, 0x08, (chan)) +#define LPAIF_WRDMACURR_REG(v, chan) LPAIF_WRDMA_REG_ADDR(v, 0x0C, (chan)) +#define LPAIF_WRDMAPER_REG(v, chan) LPAIF_WRDMA_REG_ADDR(v, 0x10, (chan)) +#define LPAIF_WRDMAPERCNT_REG(v, chan) LPAIF_WRDMA_REG_ADDR(v, 0x14, (chan)) + +#define LPAIF_INTFDMA_REG(v, chan, reg, dai_id) \ + ((dai_id == LPASS_DP_RX) ? \ + LPAIF_HDMI_RDMA##reg##_REG(v, chan) : \ + LPAIF_RDMA##reg##_REG(v, chan)) + +#define __LPAIF_DMA_REG(v, chan, dir, reg, dai_id) \ + ((dir == SNDRV_PCM_STREAM_PLAYBACK) ? \ + (LPAIF_INTFDMA_REG(v, chan, reg, dai_id)) : \ + LPAIF_WRDMA##reg##_REG(v, chan)) + +#define LPAIF_DMACTL_REG(v, chan, dir, dai_id) __LPAIF_DMA_REG(v, chan, dir, CTL, dai_id) +#define LPAIF_DMABASE_REG(v, chan, dir, dai_id) __LPAIF_DMA_REG(v, chan, dir, BASE, dai_id) +#define LPAIF_DMABUFF_REG(v, chan, dir, dai_id) __LPAIF_DMA_REG(v, chan, dir, BUFF, dai_id) +#define LPAIF_DMACURR_REG(v, chan, dir, dai_id) __LPAIF_DMA_REG(v, chan, dir, CURR, dai_id) +#define LPAIF_DMAPER_REG(v, chan, dir, dai_id) __LPAIF_DMA_REG(v, chan, dir, PER, dai_id) +#define LPAIF_DMAPERCNT_REG(v, chan, dir, dai_id) __LPAIF_DMA_REG(v, chan, dir, PERCNT, dai_id) + +#define LPAIF_DMACTL_BURSTEN_SINGLE 0 +#define LPAIF_DMACTL_BURSTEN_INCR4 1 + +#define LPAIF_DMACTL_WPSCNT_ONE 0 +#define LPAIF_DMACTL_WPSCNT_TWO 1 +#define LPAIF_DMACTL_WPSCNT_THREE 2 +#define LPAIF_DMACTL_WPSCNT_FOUR 3 +#define LPAIF_DMACTL_WPSCNT_SIX 5 +#define LPAIF_DMACTL_WPSCNT_EIGHT 7 +#define LPAIF_DMACTL_WPSCNT_TEN 9 +#define LPAIF_DMACTL_WPSCNT_TWELVE 11 +#define LPAIF_DMACTL_WPSCNT_FOURTEEN 13 +#define LPAIF_DMACTL_WPSCNT_SIXTEEN 15 + +#define LPAIF_DMACTL_AUDINTF(id) id + +#define LPAIF_DMACTL_FIFOWM_1 0 +#define LPAIF_DMACTL_FIFOWM_2 1 +#define LPAIF_DMACTL_FIFOWM_3 2 +#define LPAIF_DMACTL_FIFOWM_4 3 +#define LPAIF_DMACTL_FIFOWM_5 4 +#define LPAIF_DMACTL_FIFOWM_6 5 +#define LPAIF_DMACTL_FIFOWM_7 6 +#define LPAIF_DMACTL_FIFOWM_8 7 +#define LPAIF_DMACTL_FIFOWM_9 8 +#define LPAIF_DMACTL_FIFOWM_10 9 +#define LPAIF_DMACTL_FIFOWM_11 10 +#define LPAIF_DMACTL_FIFOWM_12 11 +#define LPAIF_DMACTL_FIFOWM_13 12 +#define LPAIF_DMACTL_FIFOWM_14 13 +#define LPAIF_DMACTL_FIFOWM_15 14 +#define LPAIF_DMACTL_FIFOWM_16 15 +#define LPAIF_DMACTL_FIFOWM_17 16 +#define LPAIF_DMACTL_FIFOWM_18 17 +#define LPAIF_DMACTL_FIFOWM_19 18 +#define LPAIF_DMACTL_FIFOWM_20 19 +#define LPAIF_DMACTL_FIFOWM_21 20 +#define LPAIF_DMACTL_FIFOWM_22 21 +#define LPAIF_DMACTL_FIFOWM_23 22 +#define LPAIF_DMACTL_FIFOWM_24 23 +#define LPAIF_DMACTL_FIFOWM_25 24 +#define LPAIF_DMACTL_FIFOWM_26 25 +#define LPAIF_DMACTL_FIFOWM_27 26 +#define LPAIF_DMACTL_FIFOWM_28 27 +#define LPAIF_DMACTL_FIFOWM_29 28 +#define LPAIF_DMACTL_FIFOWM_30 29 +#define LPAIF_DMACTL_FIFOWM_31 30 +#define LPAIF_DMACTL_FIFOWM_32 31 + +#define LPAIF_DMACTL_ENABLE_OFF 0 +#define LPAIF_DMACTL_ENABLE_ON 1 + +#define LPAIF_DMACTL_DYNCLK_OFF 0 +#define LPAIF_DMACTL_DYNCLK_ON 1 + +#endif /* __LPASS_LPAIF_REG_H__ */ diff --git a/sound/soc/qcom/lpass-platform.c b/sound/soc/qcom/lpass-platform.c new file mode 100644 index 000000000..71122e9eb --- /dev/null +++ b/sound/soc/qcom/lpass-platform.c @@ -0,0 +1,919 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved. + * + * lpass-platform.c -- ALSA SoC platform driver for QTi LPASS + */ + +#include <linux/dma-mapping.h> +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <sound/pcm_params.h> +#include <linux/regmap.h> +#include <sound/soc.h> +#include "lpass-lpaif-reg.h" +#include "lpass.h" + +#define DRV_NAME "lpass-platform" + +struct lpass_pcm_data { + int dma_ch; + int i2s_port; +}; + +#define LPASS_PLATFORM_BUFFER_SIZE (24 * 2 * 1024) +#define LPASS_PLATFORM_PERIODS 2 + +static const struct snd_pcm_hardware lpass_platform_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | + SNDRV_PCM_INFO_RESUME, + .formats = SNDRV_PCM_FMTBIT_S16 | + SNDRV_PCM_FMTBIT_S24 | + SNDRV_PCM_FMTBIT_S32, + .rates = SNDRV_PCM_RATE_8000_192000, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = LPASS_PLATFORM_BUFFER_SIZE, + .period_bytes_max = LPASS_PLATFORM_BUFFER_SIZE / + LPASS_PLATFORM_PERIODS, + .period_bytes_min = LPASS_PLATFORM_BUFFER_SIZE / + LPASS_PLATFORM_PERIODS, + .periods_min = LPASS_PLATFORM_PERIODS, + .periods_max = LPASS_PLATFORM_PERIODS, + .fifo_size = 0, +}; + +static int lpass_platform_alloc_dmactl_fields(struct device *dev, + struct regmap *map) +{ + struct lpass_data *drvdata = dev_get_drvdata(dev); + struct lpass_variant *v = drvdata->variant; + struct lpaif_dmactl *rd_dmactl, *wr_dmactl; + int rval; + + drvdata->rd_dmactl = devm_kzalloc(dev, sizeof(struct lpaif_dmactl), + GFP_KERNEL); + if (drvdata->rd_dmactl == NULL) + return -ENOMEM; + + drvdata->wr_dmactl = devm_kzalloc(dev, sizeof(struct lpaif_dmactl), + GFP_KERNEL); + if (drvdata->wr_dmactl == NULL) + return -ENOMEM; + + rd_dmactl = drvdata->rd_dmactl; + wr_dmactl = drvdata->wr_dmactl; + + rval = devm_regmap_field_bulk_alloc(dev, map, &rd_dmactl->intf, + &v->rdma_intf, 6); + if (rval) + return rval; + + return devm_regmap_field_bulk_alloc(dev, map, &wr_dmactl->intf, + &v->wrdma_intf, 6); +} + +static int lpass_platform_alloc_hdmidmactl_fields(struct device *dev, + struct regmap *map) +{ + struct lpass_data *drvdata = dev_get_drvdata(dev); + struct lpass_variant *v = drvdata->variant; + struct lpaif_dmactl *rd_dmactl; + + rd_dmactl = devm_kzalloc(dev, sizeof(struct lpaif_dmactl), GFP_KERNEL); + if (rd_dmactl == NULL) + return -ENOMEM; + + drvdata->hdmi_rd_dmactl = rd_dmactl; + + return devm_regmap_field_bulk_alloc(dev, map, &rd_dmactl->bursten, + &v->hdmi_rdma_bursten, 8); +} + +static int lpass_platform_pcmops_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_runtime, 0); + struct lpass_data *drvdata = snd_soc_component_get_drvdata(component); + struct lpass_variant *v = drvdata->variant; + int ret, dma_ch, dir = substream->stream; + struct lpass_pcm_data *data; + struct regmap *map; + unsigned int dai_id = cpu_dai->driver->id; + + component->id = dai_id; + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->i2s_port = cpu_dai->driver->id; + runtime->private_data = data; + + if (v->alloc_dma_channel) + dma_ch = v->alloc_dma_channel(drvdata, dir, dai_id); + else + dma_ch = 0; + + if (dma_ch < 0) { + kfree(data); + return dma_ch; + } + + if (cpu_dai->driver->id == LPASS_DP_RX) { + map = drvdata->hdmiif_map; + drvdata->hdmi_substream[dma_ch] = substream; + } else { + map = drvdata->lpaif_map; + drvdata->substream[dma_ch] = substream; + } + data->dma_ch = dma_ch; + ret = regmap_write(map, + LPAIF_DMACTL_REG(v, dma_ch, dir, data->i2s_port), 0); + if (ret) { + dev_err(soc_runtime->dev, + "error writing to rdmactl reg: %d\n", ret); + return ret; + } + snd_soc_set_runtime_hwparams(substream, &lpass_platform_pcm_hardware); + + runtime->dma_bytes = lpass_platform_pcm_hardware.buffer_bytes_max; + + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) { + kfree(data); + dev_err(soc_runtime->dev, "setting constraints failed: %d\n", + ret); + return -EINVAL; + } + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int lpass_platform_pcmops_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_runtime, 0); + struct lpass_data *drvdata = snd_soc_component_get_drvdata(component); + struct lpass_variant *v = drvdata->variant; + struct lpass_pcm_data *data; + unsigned int dai_id = cpu_dai->driver->id; + + data = runtime->private_data; + if (dai_id == LPASS_DP_RX) + drvdata->hdmi_substream[data->dma_ch] = NULL; + else + drvdata->substream[data->dma_ch] = NULL; + if (v->free_dma_channel) + v->free_dma_channel(drvdata, data->dma_ch, dai_id); + + kfree(data); + return 0; +} + +static int lpass_platform_pcmops_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_runtime, 0); + struct lpass_data *drvdata = snd_soc_component_get_drvdata(component); + struct snd_pcm_runtime *rt = substream->runtime; + struct lpass_pcm_data *pcm_data = rt->private_data; + struct lpass_variant *v = drvdata->variant; + snd_pcm_format_t format = params_format(params); + unsigned int channels = params_channels(params); + unsigned int regval; + struct lpaif_dmactl *dmactl; + int id, dir = substream->stream; + int bitwidth; + int ret, dma_port = pcm_data->i2s_port + v->dmactl_audif_start; + unsigned int dai_id = cpu_dai->driver->id; + + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + id = pcm_data->dma_ch; + if (dai_id == LPASS_DP_RX) + dmactl = drvdata->hdmi_rd_dmactl; + else + dmactl = drvdata->rd_dmactl; + + } else { + dmactl = drvdata->wr_dmactl; + id = pcm_data->dma_ch - v->wrdma_channel_start; + } + + bitwidth = snd_pcm_format_width(format); + if (bitwidth < 0) { + dev_err(soc_runtime->dev, "invalid bit width given: %d\n", + bitwidth); + return bitwidth; + } + + ret = regmap_fields_write(dmactl->bursten, id, LPAIF_DMACTL_BURSTEN_INCR4); + if (ret) { + dev_err(soc_runtime->dev, "error updating bursten field: %d\n", ret); + return ret; + } + + ret = regmap_fields_write(dmactl->fifowm, id, LPAIF_DMACTL_FIFOWM_8); + if (ret) { + dev_err(soc_runtime->dev, "error updating fifowm field: %d\n", ret); + return ret; + } + + switch (dai_id) { + case LPASS_DP_RX: + ret = regmap_fields_write(dmactl->burst8, id, + LPAIF_DMACTL_BURSTEN_INCR4); + if (ret) { + dev_err(soc_runtime->dev, "error updating burst8en field: %d\n", ret); + return ret; + } + ret = regmap_fields_write(dmactl->burst16, id, + LPAIF_DMACTL_BURSTEN_INCR4); + if (ret) { + dev_err(soc_runtime->dev, "error updating burst16en field: %d\n", ret); + return ret; + } + ret = regmap_fields_write(dmactl->dynburst, id, + LPAIF_DMACTL_BURSTEN_INCR4); + if (ret) { + dev_err(soc_runtime->dev, "error updating dynbursten field: %d\n", ret); + return ret; + } + break; + case MI2S_PRIMARY: + case MI2S_SECONDARY: + case MI2S_TERTIARY: + case MI2S_QUATERNARY: + case MI2S_QUINARY: + ret = regmap_fields_write(dmactl->intf, id, + LPAIF_DMACTL_AUDINTF(dma_port)); + if (ret) { + dev_err(soc_runtime->dev, "error updating audio interface field: %d\n", + ret); + return ret; + } + + break; + default: + dev_err(soc_runtime->dev, "%s: invalid interface: %d\n", __func__, dai_id); + break; + } + switch (bitwidth) { + case 16: + switch (channels) { + case 1: + case 2: + regval = LPAIF_DMACTL_WPSCNT_ONE; + break; + case 4: + regval = LPAIF_DMACTL_WPSCNT_TWO; + break; + case 6: + regval = LPAIF_DMACTL_WPSCNT_THREE; + break; + case 8: + regval = LPAIF_DMACTL_WPSCNT_FOUR; + break; + default: + dev_err(soc_runtime->dev, "invalid PCM config given: bw=%d, ch=%u\n", + bitwidth, channels); + return -EINVAL; + } + break; + case 24: + case 32: + switch (channels) { + case 1: + regval = LPAIF_DMACTL_WPSCNT_ONE; + break; + case 2: + regval = (dai_id == LPASS_DP_RX ? + LPAIF_DMACTL_WPSCNT_ONE : + LPAIF_DMACTL_WPSCNT_TWO); + break; + case 4: + regval = (dai_id == LPASS_DP_RX ? + LPAIF_DMACTL_WPSCNT_TWO : + LPAIF_DMACTL_WPSCNT_FOUR); + break; + case 6: + regval = (dai_id == LPASS_DP_RX ? + LPAIF_DMACTL_WPSCNT_THREE : + LPAIF_DMACTL_WPSCNT_SIX); + break; + case 8: + regval = (dai_id == LPASS_DP_RX ? + LPAIF_DMACTL_WPSCNT_FOUR : + LPAIF_DMACTL_WPSCNT_EIGHT); + break; + default: + dev_err(soc_runtime->dev, "invalid PCM config given: bw=%d, ch=%u\n", + bitwidth, channels); + return -EINVAL; + } + break; + default: + dev_err(soc_runtime->dev, "invalid PCM config given: bw=%d, ch=%u\n", + bitwidth, channels); + return -EINVAL; + } + + ret = regmap_fields_write(dmactl->wpscnt, id, regval); + if (ret) { + dev_err(soc_runtime->dev, "error writing to dmactl reg: %d\n", + ret); + return ret; + } + + return 0; +} + +static int lpass_platform_pcmops_hw_free(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_runtime, 0); + struct lpass_data *drvdata = snd_soc_component_get_drvdata(component); + struct snd_pcm_runtime *rt = substream->runtime; + struct lpass_pcm_data *pcm_data = rt->private_data; + struct lpass_variant *v = drvdata->variant; + unsigned int reg; + int ret; + struct regmap *map; + unsigned int dai_id = cpu_dai->driver->id; + + if (dai_id == LPASS_DP_RX) + map = drvdata->hdmiif_map; + else + map = drvdata->lpaif_map; + + reg = LPAIF_DMACTL_REG(v, pcm_data->dma_ch, substream->stream, dai_id); + ret = regmap_write(map, reg, 0); + if (ret) + dev_err(soc_runtime->dev, "error writing to rdmactl reg: %d\n", + ret); + + return ret; +} + +static int lpass_platform_pcmops_prepare(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_runtime, 0); + struct lpass_data *drvdata = snd_soc_component_get_drvdata(component); + struct snd_pcm_runtime *rt = substream->runtime; + struct lpass_pcm_data *pcm_data = rt->private_data; + struct lpass_variant *v = drvdata->variant; + struct lpaif_dmactl *dmactl; + struct regmap *map; + int ret, id, ch, dir = substream->stream; + unsigned int dai_id = cpu_dai->driver->id; + + + ch = pcm_data->dma_ch; + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + if (dai_id == LPASS_DP_RX) { + dmactl = drvdata->hdmi_rd_dmactl; + map = drvdata->hdmiif_map; + } else { + dmactl = drvdata->rd_dmactl; + map = drvdata->lpaif_map; + } + + id = pcm_data->dma_ch; + } else { + dmactl = drvdata->wr_dmactl; + id = pcm_data->dma_ch - v->wrdma_channel_start; + map = drvdata->lpaif_map; + } + + ret = regmap_write(map, LPAIF_DMABASE_REG(v, ch, dir, dai_id), + runtime->dma_addr); + if (ret) { + dev_err(soc_runtime->dev, "error writing to rdmabase reg: %d\n", + ret); + return ret; + } + + ret = regmap_write(map, LPAIF_DMABUFF_REG(v, ch, dir, dai_id), + (snd_pcm_lib_buffer_bytes(substream) >> 2) - 1); + if (ret) { + dev_err(soc_runtime->dev, "error writing to rdmabuff reg: %d\n", + ret); + return ret; + } + + ret = regmap_write(map, LPAIF_DMAPER_REG(v, ch, dir, dai_id), + (snd_pcm_lib_period_bytes(substream) >> 2) - 1); + if (ret) { + dev_err(soc_runtime->dev, "error writing to rdmaper reg: %d\n", + ret); + return ret; + } + + ret = regmap_fields_write(dmactl->enable, id, LPAIF_DMACTL_ENABLE_ON); + if (ret) { + dev_err(soc_runtime->dev, "error writing to rdmactl reg: %d\n", + ret); + return ret; + } + + return 0; +} + +static int lpass_platform_pcmops_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + int cmd) +{ + struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_runtime, 0); + struct lpass_data *drvdata = snd_soc_component_get_drvdata(component); + struct snd_pcm_runtime *rt = substream->runtime; + struct lpass_pcm_data *pcm_data = rt->private_data; + struct lpass_variant *v = drvdata->variant; + struct lpaif_dmactl *dmactl; + struct regmap *map; + int ret, ch, id; + int dir = substream->stream; + unsigned int reg_irqclr = 0, val_irqclr = 0; + unsigned int reg_irqen = 0, val_irqen = 0, val_mask = 0; + unsigned int dai_id = cpu_dai->driver->id; + + ch = pcm_data->dma_ch; + if (dir == SNDRV_PCM_STREAM_PLAYBACK) { + id = pcm_data->dma_ch; + if (dai_id == LPASS_DP_RX) { + dmactl = drvdata->hdmi_rd_dmactl; + map = drvdata->hdmiif_map; + } else { + dmactl = drvdata->rd_dmactl; + map = drvdata->lpaif_map; + } + } else { + dmactl = drvdata->wr_dmactl; + id = pcm_data->dma_ch - v->wrdma_channel_start; + map = drvdata->lpaif_map; + } + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = regmap_fields_write(dmactl->enable, id, + LPAIF_DMACTL_ENABLE_ON); + if (ret) { + dev_err(soc_runtime->dev, + "error writing to rdmactl reg: %d\n", ret); + return ret; + } + switch (dai_id) { + case LPASS_DP_RX: + ret = regmap_fields_write(dmactl->dyncclk, id, + LPAIF_DMACTL_DYNCLK_ON); + if (ret) { + dev_err(soc_runtime->dev, + "error writing to rdmactl reg: %d\n", ret); + return ret; + } + reg_irqclr = LPASS_HDMITX_APP_IRQCLEAR_REG(v); + val_irqclr = (LPAIF_IRQ_ALL(ch) | + LPAIF_IRQ_HDMI_REQ_ON_PRELOAD(ch) | + LPAIF_IRQ_HDMI_METADONE | + LPAIF_IRQ_HDMI_SDEEP_AUD_DIS(ch)); + + reg_irqen = LPASS_HDMITX_APP_IRQEN_REG(v); + val_mask = (LPAIF_IRQ_ALL(ch) | + LPAIF_IRQ_HDMI_REQ_ON_PRELOAD(ch) | + LPAIF_IRQ_HDMI_METADONE | + LPAIF_IRQ_HDMI_SDEEP_AUD_DIS(ch)); + val_irqen = (LPAIF_IRQ_ALL(ch) | + LPAIF_IRQ_HDMI_REQ_ON_PRELOAD(ch) | + LPAIF_IRQ_HDMI_METADONE | + LPAIF_IRQ_HDMI_SDEEP_AUD_DIS(ch)); + break; + case MI2S_PRIMARY: + case MI2S_SECONDARY: + case MI2S_TERTIARY: + case MI2S_QUATERNARY: + case MI2S_QUINARY: + reg_irqclr = LPAIF_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST); + val_irqclr = LPAIF_IRQ_ALL(ch); + + + reg_irqen = LPAIF_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST); + val_mask = LPAIF_IRQ_ALL(ch); + val_irqen = LPAIF_IRQ_ALL(ch); + break; + default: + dev_err(soc_runtime->dev, "%s: invalid %d interface\n", __func__, dai_id); + return -EINVAL; + } + + ret = regmap_write(map, reg_irqclr, val_irqclr); + if (ret) { + dev_err(soc_runtime->dev, "error writing to irqclear reg: %d\n", ret); + return ret; + } + ret = regmap_update_bits(map, reg_irqen, val_mask, val_irqen); + if (ret) { + dev_err(soc_runtime->dev, "error writing to irqen reg: %d\n", ret); + return ret; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = regmap_fields_write(dmactl->enable, id, + LPAIF_DMACTL_ENABLE_OFF); + if (ret) { + dev_err(soc_runtime->dev, + "error writing to rdmactl reg: %d\n", ret); + return ret; + } + switch (dai_id) { + case LPASS_DP_RX: + ret = regmap_fields_write(dmactl->dyncclk, id, + LPAIF_DMACTL_DYNCLK_OFF); + if (ret) { + dev_err(soc_runtime->dev, + "error writing to rdmactl reg: %d\n", ret); + return ret; + } + reg_irqen = LPASS_HDMITX_APP_IRQEN_REG(v); + val_mask = (LPAIF_IRQ_ALL(ch) | + LPAIF_IRQ_HDMI_REQ_ON_PRELOAD(ch) | + LPAIF_IRQ_HDMI_METADONE | + LPAIF_IRQ_HDMI_SDEEP_AUD_DIS(ch)); + val_irqen = 0; + break; + case MI2S_PRIMARY: + case MI2S_SECONDARY: + case MI2S_TERTIARY: + case MI2S_QUATERNARY: + case MI2S_QUINARY: + reg_irqen = LPAIF_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST); + val_mask = LPAIF_IRQ_ALL(ch); + val_irqen = 0; + break; + default: + dev_err(soc_runtime->dev, "%s: invalid %d interface\n", __func__, dai_id); + return -EINVAL; + } + + ret = regmap_update_bits(map, reg_irqen, val_mask, val_irqen); + if (ret) { + dev_err(soc_runtime->dev, + "error writing to irqen reg: %d\n", ret); + return ret; + } + break; + } + + return 0; +} + +static snd_pcm_uframes_t lpass_platform_pcmops_pointer( + struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_runtime, 0); + struct lpass_data *drvdata = snd_soc_component_get_drvdata(component); + struct snd_pcm_runtime *rt = substream->runtime; + struct lpass_pcm_data *pcm_data = rt->private_data; + struct lpass_variant *v = drvdata->variant; + unsigned int base_addr, curr_addr; + int ret, ch, dir = substream->stream; + struct regmap *map; + unsigned int dai_id = cpu_dai->driver->id; + + if (dai_id == LPASS_DP_RX) + map = drvdata->hdmiif_map; + else + map = drvdata->lpaif_map; + + ch = pcm_data->dma_ch; + + ret = regmap_read(map, + LPAIF_DMABASE_REG(v, ch, dir, dai_id), &base_addr); + if (ret) { + dev_err(soc_runtime->dev, + "error reading from rdmabase reg: %d\n", ret); + return ret; + } + + ret = regmap_read(map, + LPAIF_DMACURR_REG(v, ch, dir, dai_id), &curr_addr); + if (ret) { + dev_err(soc_runtime->dev, + "error reading from rdmacurr reg: %d\n", ret); + return ret; + } + + return bytes_to_frames(substream->runtime, curr_addr - base_addr); +} + +static int lpass_platform_pcmops_mmap(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + + return dma_mmap_coherent(component->dev, vma, runtime->dma_area, + runtime->dma_addr, runtime->dma_bytes); +} + +static irqreturn_t lpass_dma_interrupt_handler( + struct snd_pcm_substream *substream, + struct lpass_data *drvdata, + int chan, u32 interrupts) +{ + struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_runtime, 0); + struct lpass_variant *v = drvdata->variant; + irqreturn_t ret = IRQ_NONE; + int rv; + unsigned int reg = 0, val = 0; + struct regmap *map; + unsigned int dai_id = cpu_dai->driver->id; + + switch (dai_id) { + case LPASS_DP_RX: + map = drvdata->hdmiif_map; + reg = LPASS_HDMITX_APP_IRQCLEAR_REG(v); + val = (LPAIF_IRQ_HDMI_REQ_ON_PRELOAD(chan) | + LPAIF_IRQ_HDMI_METADONE | + LPAIF_IRQ_HDMI_SDEEP_AUD_DIS(chan)); + break; + case MI2S_PRIMARY: + case MI2S_SECONDARY: + case MI2S_TERTIARY: + case MI2S_QUATERNARY: + case MI2S_QUINARY: + map = drvdata->lpaif_map; + reg = LPAIF_IRQCLEAR_REG(v, LPAIF_IRQ_PORT_HOST); + val = 0; + break; + default: + dev_err(soc_runtime->dev, "%s: invalid %d interface\n", __func__, dai_id); + return -EINVAL; + } + if (interrupts & LPAIF_IRQ_PER(chan)) { + + rv = regmap_write(map, reg, LPAIF_IRQ_PER(chan) | val); + if (rv) { + dev_err(soc_runtime->dev, + "error writing to irqclear reg: %d\n", rv); + return IRQ_NONE; + } + snd_pcm_period_elapsed(substream); + ret = IRQ_HANDLED; + } + + if (interrupts & LPAIF_IRQ_XRUN(chan)) { + rv = regmap_write(map, reg, LPAIF_IRQ_XRUN(chan) | val); + if (rv) { + dev_err(soc_runtime->dev, + "error writing to irqclear reg: %d\n", rv); + return IRQ_NONE; + } + dev_warn(soc_runtime->dev, "xrun warning\n"); + snd_pcm_stop_xrun(substream); + ret = IRQ_HANDLED; + } + + if (interrupts & LPAIF_IRQ_ERR(chan)) { + rv = regmap_write(map, reg, LPAIF_IRQ_ERR(chan) | val); + if (rv) { + dev_err(soc_runtime->dev, + "error writing to irqclear reg: %d\n", rv); + return IRQ_NONE; + } + dev_err(soc_runtime->dev, "bus access error\n"); + snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED); + ret = IRQ_HANDLED; + } + + if (interrupts & val) { + rv = regmap_write(map, reg, val); + if (rv) { + dev_err(soc_runtime->dev, + "error writing to irqclear reg: %d\n", rv); + return IRQ_NONE; + } + ret = IRQ_HANDLED; + } + + return ret; +} + +static irqreturn_t lpass_platform_lpaif_irq(int irq, void *data) +{ + struct lpass_data *drvdata = data; + struct lpass_variant *v = drvdata->variant; + unsigned int irqs; + int rv, chan; + + rv = regmap_read(drvdata->lpaif_map, + LPAIF_IRQSTAT_REG(v, LPAIF_IRQ_PORT_HOST), &irqs); + if (rv) { + pr_err("error reading from irqstat reg: %d\n", rv); + return IRQ_NONE; + } + + /* Handle per channel interrupts */ + for (chan = 0; chan < LPASS_MAX_DMA_CHANNELS; chan++) { + if (irqs & LPAIF_IRQ_ALL(chan) && drvdata->substream[chan]) { + rv = lpass_dma_interrupt_handler( + drvdata->substream[chan], + drvdata, chan, irqs); + if (rv != IRQ_HANDLED) + return rv; + } + } + + return IRQ_HANDLED; +} + +static irqreturn_t lpass_platform_hdmiif_irq(int irq, void *data) +{ + struct lpass_data *drvdata = data; + struct lpass_variant *v = drvdata->variant; + unsigned int irqs; + int rv, chan; + + rv = regmap_read(drvdata->hdmiif_map, + LPASS_HDMITX_APP_IRQSTAT_REG(v), &irqs); + if (rv) { + pr_err("error reading from irqstat reg: %d\n", rv); + return IRQ_NONE; + } + + /* Handle per channel interrupts */ + for (chan = 0; chan < LPASS_MAX_HDMI_DMA_CHANNELS; chan++) { + if (irqs & (LPAIF_IRQ_ALL(chan) | LPAIF_IRQ_HDMI_REQ_ON_PRELOAD(chan) | + LPAIF_IRQ_HDMI_METADONE | + LPAIF_IRQ_HDMI_SDEEP_AUD_DIS(chan)) + && drvdata->hdmi_substream[chan]) { + rv = lpass_dma_interrupt_handler( + drvdata->hdmi_substream[chan], + drvdata, chan, irqs); + if (rv != IRQ_HANDLED) + return rv; + } + } + + return IRQ_HANDLED; +} + +static int lpass_platform_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *soc_runtime) +{ + struct snd_pcm *pcm = soc_runtime->pcm; + struct snd_pcm_substream *psubstream, *csubstream; + int ret = -EINVAL; + size_t size = lpass_platform_pcm_hardware.buffer_bytes_max; + + psubstream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + if (psubstream) { + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, + component->dev, + size, &psubstream->dma_buffer); + if (ret) { + dev_err(soc_runtime->dev, "Cannot allocate buffer(s)\n"); + return ret; + } + } + + csubstream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + if (csubstream) { + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, + component->dev, + size, &csubstream->dma_buffer); + if (ret) { + dev_err(soc_runtime->dev, "Cannot allocate buffer(s)\n"); + if (psubstream) + snd_dma_free_pages(&psubstream->dma_buffer); + return ret; + } + + } + + return 0; +} + +static void lpass_platform_pcm_free(struct snd_soc_component *component, + struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + int i; + + for_each_pcm_streams(i) { + substream = pcm->streams[i].substream; + if (substream) { + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } + } +} + +static const struct snd_soc_component_driver lpass_component_driver = { + .name = DRV_NAME, + .open = lpass_platform_pcmops_open, + .close = lpass_platform_pcmops_close, + .hw_params = lpass_platform_pcmops_hw_params, + .hw_free = lpass_platform_pcmops_hw_free, + .prepare = lpass_platform_pcmops_prepare, + .trigger = lpass_platform_pcmops_trigger, + .pointer = lpass_platform_pcmops_pointer, + .mmap = lpass_platform_pcmops_mmap, + .pcm_construct = lpass_platform_pcm_new, + .pcm_destruct = lpass_platform_pcm_free, + +}; + +int asoc_qcom_lpass_platform_register(struct platform_device *pdev) +{ + struct lpass_data *drvdata = platform_get_drvdata(pdev); + struct lpass_variant *v = drvdata->variant; + int ret; + + drvdata->lpaif_irq = platform_get_irq_byname(pdev, "lpass-irq-lpaif"); + if (drvdata->lpaif_irq < 0) + return -ENODEV; + + /* ensure audio hardware is disabled */ + ret = regmap_write(drvdata->lpaif_map, + LPAIF_IRQEN_REG(v, LPAIF_IRQ_PORT_HOST), 0); + if (ret) { + dev_err(&pdev->dev, "error writing to irqen reg: %d\n", ret); + return ret; + } + + ret = devm_request_irq(&pdev->dev, drvdata->lpaif_irq, + lpass_platform_lpaif_irq, IRQF_TRIGGER_RISING, + "lpass-irq-lpaif", drvdata); + if (ret) { + dev_err(&pdev->dev, "irq request failed: %d\n", ret); + return ret; + } + + ret = lpass_platform_alloc_dmactl_fields(&pdev->dev, + drvdata->lpaif_map); + if (ret) { + dev_err(&pdev->dev, + "error initializing dmactl fields: %d\n", ret); + return ret; + } + + if (drvdata->hdmi_port_enable) { + drvdata->hdmiif_irq = platform_get_irq_byname(pdev, "lpass-irq-hdmi"); + if (drvdata->hdmiif_irq < 0) + return -ENODEV; + + ret = devm_request_irq(&pdev->dev, drvdata->hdmiif_irq, + lpass_platform_hdmiif_irq, 0, "lpass-irq-hdmi", drvdata); + if (ret) { + dev_err(&pdev->dev, "irq hdmi request failed: %d\n", ret); + return ret; + } + ret = regmap_write(drvdata->hdmiif_map, + LPASS_HDMITX_APP_IRQEN_REG(v), 0); + if (ret) { + dev_err(&pdev->dev, "error writing to hdmi irqen reg: %d\n", ret); + return ret; + } + + ret = lpass_platform_alloc_hdmidmactl_fields(&pdev->dev, + drvdata->hdmiif_map); + if (ret) { + dev_err(&pdev->dev, + "error initializing hdmidmactl fields: %d\n", ret); + return ret; + } + } + return devm_snd_soc_register_component(&pdev->dev, + &lpass_component_driver, NULL, 0); +} +EXPORT_SYMBOL_GPL(asoc_qcom_lpass_platform_register); + +MODULE_DESCRIPTION("QTi LPASS Platform Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/lpass-sc7180.c b/sound/soc/qcom/lpass-sc7180.c new file mode 100644 index 000000000..cb4e9017c --- /dev/null +++ b/sound/soc/qcom/lpass-sc7180.c @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + * + * lpass-sc7180.c -- ALSA SoC platform-machine driver for QTi LPASS + */ + +#include <linux/clk.h> +#include <linux/device.h> +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <dt-bindings/sound/sc7180-lpass.h> +#include <sound/pcm.h> +#include <sound/soc.h> + +#include "lpass-lpaif-reg.h" +#include "lpass.h" + +static struct snd_soc_dai_driver sc7180_lpass_cpu_dai_driver[] = { + { + .id = MI2S_PRIMARY, + .name = "Primary MI2S", + .playback = { + .stream_name = "Primary Playback", + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + }, + .capture = { + .stream_name = "Primary Capture", + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + }, + .probe = &asoc_qcom_lpass_cpu_dai_probe, + .ops = &asoc_qcom_lpass_cpu_dai_ops, + }, { + .id = MI2S_SECONDARY, + .name = "Secondary MI2S", + .playback = { + .stream_name = "Secondary Playback", + .formats = SNDRV_PCM_FMTBIT_S16, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + }, + .probe = &asoc_qcom_lpass_cpu_dai_probe, + .ops = &asoc_qcom_lpass_cpu_dai_ops, + }, { + .id = LPASS_DP_RX, + .name = "Hdmi", + .playback = { + .stream_name = "Hdmi Playback", + .formats = SNDRV_PCM_FMTBIT_S24, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + }, + .ops = &asoc_qcom_lpass_hdmi_dai_ops, + }, +}; + +static int sc7180_lpass_alloc_dma_channel(struct lpass_data *drvdata, + int direction, unsigned int dai_id) +{ + struct lpass_variant *v = drvdata->variant; + int chan = 0; + + if (dai_id == LPASS_DP_RX) { + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { + chan = find_first_zero_bit(&drvdata->hdmi_dma_ch_bit_map, + v->hdmi_rdma_channels); + + if (chan >= v->hdmi_rdma_channels) + return -EBUSY; + } + set_bit(chan, &drvdata->hdmi_dma_ch_bit_map); + } else { + if (direction == SNDRV_PCM_STREAM_PLAYBACK) { + chan = find_first_zero_bit(&drvdata->dma_ch_bit_map, + v->rdma_channels); + + if (chan >= v->rdma_channels) + return -EBUSY; + } else { + chan = find_next_zero_bit(&drvdata->dma_ch_bit_map, + v->wrdma_channel_start + + v->wrdma_channels, + v->wrdma_channel_start); + + if (chan >= v->wrdma_channel_start + v->wrdma_channels) + return -EBUSY; + } + + set_bit(chan, &drvdata->dma_ch_bit_map); + } + return chan; +} + +static int sc7180_lpass_free_dma_channel(struct lpass_data *drvdata, int chan, unsigned int dai_id) +{ + if (dai_id == LPASS_DP_RX) + clear_bit(chan, &drvdata->hdmi_dma_ch_bit_map); + else + clear_bit(chan, &drvdata->dma_ch_bit_map); + + return 0; +} + +static int sc7180_lpass_init(struct platform_device *pdev) +{ + struct lpass_data *drvdata = platform_get_drvdata(pdev); + struct lpass_variant *variant = drvdata->variant; + struct device *dev = &pdev->dev; + int ret, i; + + drvdata->clks = devm_kcalloc(dev, variant->num_clks, + sizeof(*drvdata->clks), GFP_KERNEL); + if (!drvdata->clks) + return -ENOMEM; + + drvdata->num_clks = variant->num_clks; + + for (i = 0; i < drvdata->num_clks; i++) + drvdata->clks[i].id = variant->clk_name[i]; + + ret = devm_clk_bulk_get(dev, drvdata->num_clks, drvdata->clks); + if (ret) { + dev_err(dev, "Failed to get clocks %d\n", ret); + return ret; + } + + ret = clk_bulk_prepare_enable(drvdata->num_clks, drvdata->clks); + if (ret) { + dev_err(dev, "sc7180 clk_enable failed\n"); + return ret; + } + + return 0; +} + +static int sc7180_lpass_exit(struct platform_device *pdev) +{ + struct lpass_data *drvdata = platform_get_drvdata(pdev); + + clk_bulk_disable_unprepare(drvdata->num_clks, drvdata->clks); + + return 0; +} + +static struct lpass_variant sc7180_data = { + .i2sctrl_reg_base = 0x1000, + .i2sctrl_reg_stride = 0x1000, + .i2s_ports = 3, + .irq_reg_base = 0x9000, + .irq_reg_stride = 0x1000, + .irq_ports = 3, + .rdma_reg_base = 0xC000, + .rdma_reg_stride = 0x1000, + .rdma_channels = 5, + .hdmi_rdma_reg_base = 0x64000, + .hdmi_rdma_reg_stride = 0x1000, + .hdmi_rdma_channels = 4, + .dmactl_audif_start = 1, + .wrdma_reg_base = 0x18000, + .wrdma_reg_stride = 0x1000, + .wrdma_channel_start = 5, + .wrdma_channels = 4, + + .loopback = REG_FIELD_ID(0x1000, 17, 17, 3, 0x1000), + .spken = REG_FIELD_ID(0x1000, 16, 16, 3, 0x1000), + .spkmode = REG_FIELD_ID(0x1000, 11, 15, 3, 0x1000), + .spkmono = REG_FIELD_ID(0x1000, 10, 10, 3, 0x1000), + .micen = REG_FIELD_ID(0x1000, 9, 9, 3, 0x1000), + .micmode = REG_FIELD_ID(0x1000, 4, 8, 3, 0x1000), + .micmono = REG_FIELD_ID(0x1000, 3, 3, 3, 0x1000), + .wssrc = REG_FIELD_ID(0x1000, 2, 2, 3, 0x1000), + .bitwidth = REG_FIELD_ID(0x1000, 0, 1, 3, 0x1000), + + .rdma_dyncclk = REG_FIELD_ID(0xC000, 21, 21, 5, 0x1000), + .rdma_bursten = REG_FIELD_ID(0xC000, 20, 20, 5, 0x1000), + .rdma_wpscnt = REG_FIELD_ID(0xC000, 16, 19, 5, 0x1000), + .rdma_intf = REG_FIELD_ID(0xC000, 12, 15, 5, 0x1000), + .rdma_fifowm = REG_FIELD_ID(0xC000, 1, 5, 5, 0x1000), + .rdma_enable = REG_FIELD_ID(0xC000, 0, 0, 5, 0x1000), + + .wrdma_dyncclk = REG_FIELD_ID(0x18000, 22, 22, 4, 0x1000), + .wrdma_bursten = REG_FIELD_ID(0x18000, 21, 21, 4, 0x1000), + .wrdma_wpscnt = REG_FIELD_ID(0x18000, 17, 20, 4, 0x1000), + .wrdma_intf = REG_FIELD_ID(0x18000, 12, 16, 4, 0x1000), + .wrdma_fifowm = REG_FIELD_ID(0x18000, 1, 5, 4, 0x1000), + .wrdma_enable = REG_FIELD_ID(0x18000, 0, 0, 4, 0x1000), + + .hdmi_tx_ctl_addr = 0x1000, + .hdmi_legacy_addr = 0x1008, + .hdmi_vbit_addr = 0x610c0, + .hdmi_ch_lsb_addr = 0x61048, + .hdmi_ch_msb_addr = 0x6104c, + .ch_stride = 0x8, + .hdmi_parity_addr = 0x61034, + .hdmi_dmactl_addr = 0x61038, + .hdmi_dma_stride = 0x4, + .hdmi_DP_addr = 0x610c8, + .hdmi_sstream_addr = 0x6101c, + .hdmi_irq_reg_base = 0x63000, + .hdmi_irq_ports = 1, + + .hdmi_rdma_dyncclk = REG_FIELD_ID(0x64000, 14, 14, 4, 0x1000), + .hdmi_rdma_bursten = REG_FIELD_ID(0x64000, 13, 13, 4, 0x1000), + .hdmi_rdma_burst8 = REG_FIELD_ID(0x64000, 15, 15, 4, 0x1000), + .hdmi_rdma_burst16 = REG_FIELD_ID(0x64000, 16, 16, 4, 0x1000), + .hdmi_rdma_dynburst = REG_FIELD_ID(0x64000, 18, 18, 4, 0x1000), + .hdmi_rdma_wpscnt = REG_FIELD_ID(0x64000, 10, 12, 4, 0x1000), + .hdmi_rdma_fifowm = REG_FIELD_ID(0x64000, 1, 5, 4, 0x1000), + .hdmi_rdma_enable = REG_FIELD_ID(0x64000, 0, 0, 4, 0x1000), + + .sstream_en = REG_FIELD(0x6101c, 0, 0), + .dma_sel = REG_FIELD(0x6101c, 1, 2), + .auto_bbit_en = REG_FIELD(0x6101c, 3, 3), + .layout = REG_FIELD(0x6101c, 4, 4), + .layout_sp = REG_FIELD(0x6101c, 5, 8), + .set_sp_on_en = REG_FIELD(0x6101c, 10, 10), + .dp_audio = REG_FIELD(0x6101c, 11, 11), + .dp_staffing_en = REG_FIELD(0x6101c, 12, 12), + .dp_sp_b_hw_en = REG_FIELD(0x6101c, 13, 13), + + .mute = REG_FIELD(0x610c8, 0, 0), + .as_sdp_cc = REG_FIELD(0x610c8, 1, 3), + .as_sdp_ct = REG_FIELD(0x610c8, 4, 7), + .aif_db4 = REG_FIELD(0x610c8, 8, 15), + .frequency = REG_FIELD(0x610c8, 16, 21), + .mst_index = REG_FIELD(0x610c8, 28, 29), + .dptx_index = REG_FIELD(0x610c8, 30, 31), + + .soft_reset = REG_FIELD(0x1000, 31, 31), + .force_reset = REG_FIELD(0x1000, 30, 30), + + .use_hw_chs = REG_FIELD(0x61038, 0, 0), + .use_hw_usr = REG_FIELD(0x61038, 1, 1), + .hw_chs_sel = REG_FIELD(0x61038, 2, 4), + .hw_usr_sel = REG_FIELD(0x61038, 5, 6), + + .replace_vbit = REG_FIELD(0x610c0, 0, 0), + .vbit_stream = REG_FIELD(0x610c0, 1, 1), + + .legacy_en = REG_FIELD(0x1008, 0, 0), + .calc_en = REG_FIELD(0x61034, 0, 0), + .lsb_bits = REG_FIELD(0x61048, 0, 31), + .msb_bits = REG_FIELD(0x6104c, 0, 31), + + + .clk_name = (const char*[]) { + "pcnoc-sway-clk", + "audio-core", + "pcnoc-mport-clk", + }, + .num_clks = 3, + .dai_driver = sc7180_lpass_cpu_dai_driver, + .num_dai = ARRAY_SIZE(sc7180_lpass_cpu_dai_driver), + .dai_osr_clk_names = (const char *[]) { + "mclk0", + "null", + }, + .dai_bit_clk_names = (const char *[]) { + "mi2s-bit-clk0", + "mi2s-bit-clk1", + }, + .init = sc7180_lpass_init, + .exit = sc7180_lpass_exit, + .alloc_dma_channel = sc7180_lpass_alloc_dma_channel, + .free_dma_channel = sc7180_lpass_free_dma_channel, +}; + +static const struct of_device_id sc7180_lpass_cpu_device_id[] = { + {.compatible = "qcom,sc7180-lpass-cpu", .data = &sc7180_data}, + {} +}; +MODULE_DEVICE_TABLE(of, sc7180_lpass_cpu_device_id); + +static struct platform_driver sc7180_lpass_cpu_platform_driver = { + .driver = { + .name = "sc7180-lpass-cpu", + .of_match_table = of_match_ptr(sc7180_lpass_cpu_device_id), + }, + .probe = asoc_qcom_lpass_cpu_platform_probe, + .remove = asoc_qcom_lpass_cpu_platform_remove, +}; + +module_platform_driver(sc7180_lpass_cpu_platform_driver); + +MODULE_DESCRIPTION("SC7180 LPASS CPU DRIVER"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/lpass.h b/sound/soc/qcom/lpass.h new file mode 100644 index 000000000..0484ad39b --- /dev/null +++ b/sound/soc/qcom/lpass.h @@ -0,0 +1,266 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2010-2011,2013-2015,2020 The Linux Foundation. All rights reserved. + * + * lpass.h - Definitions for the QTi LPASS + */ + +#ifndef __LPASS_H__ +#define __LPASS_H__ + +#include <linux/clk.h> +#include <linux/compiler.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <dt-bindings/sound/qcom,lpass.h> +#include "lpass-hdmi.h" + +#define LPASS_AHBIX_CLOCK_FREQUENCY 131072000 +#define LPASS_MAX_MI2S_PORTS (8) +#define LPASS_MAX_DMA_CHANNELS (8) +#define LPASS_MAX_HDMI_DMA_CHANNELS (4) + +#define QCOM_REGMAP_FIELD_ALLOC(d, m, f, mf) \ + do { \ + mf = devm_regmap_field_alloc(d, m, f); \ + if (IS_ERR(mf)) \ + return -EINVAL; \ + } while (0) + +struct lpaif_i2sctl { + struct regmap_field *loopback; + struct regmap_field *spken; + struct regmap_field *spkmode; + struct regmap_field *spkmono; + struct regmap_field *micen; + struct regmap_field *micmode; + struct regmap_field *micmono; + struct regmap_field *wssrc; + struct regmap_field *bitwidth; +}; + + +struct lpaif_dmactl { + struct regmap_field *intf; + struct regmap_field *bursten; + struct regmap_field *wpscnt; + struct regmap_field *fifowm; + struct regmap_field *enable; + struct regmap_field *dyncclk; + struct regmap_field *burst8; + struct regmap_field *burst16; + struct regmap_field *dynburst; +}; + +/* Both the CPU DAI and platform drivers will access this data */ +struct lpass_data { + + /* AHB-I/X bus clocks inside the low-power audio subsystem (LPASS) */ + struct clk *ahbix_clk; + + /* MI2S system clock */ + struct clk *mi2s_osr_clk[LPASS_MAX_MI2S_PORTS]; + + /* MI2S bit clock (derived from system clock by a divider */ + struct clk *mi2s_bit_clk[LPASS_MAX_MI2S_PORTS]; + + /* MI2S SD lines to use for playback/capture */ + unsigned int mi2s_playback_sd_mode[LPASS_MAX_MI2S_PORTS]; + unsigned int mi2s_capture_sd_mode[LPASS_MAX_MI2S_PORTS]; + + /* The state of MI2S prepare dai_ops was called */ + bool mi2s_was_prepared[LPASS_MAX_MI2S_PORTS]; + + int hdmi_port_enable; + + /* low-power audio interface (LPAIF) registers */ + void __iomem *lpaif; + void __iomem *hdmiif; + + /* regmap backed by the low-power audio interface (LPAIF) registers */ + struct regmap *lpaif_map; + struct regmap *hdmiif_map; + + /* interrupts from the low-power audio interface (LPAIF) */ + int lpaif_irq; + int hdmiif_irq; + /* SOC specific variations in the LPASS IP integration */ + struct lpass_variant *variant; + + /* bit map to keep track of static channel allocations */ + unsigned long dma_ch_bit_map; + unsigned long hdmi_dma_ch_bit_map; + + /* used it for handling interrupt per dma channel */ + struct snd_pcm_substream *substream[LPASS_MAX_DMA_CHANNELS]; + struct snd_pcm_substream *hdmi_substream[LPASS_MAX_HDMI_DMA_CHANNELS]; + + /* SOC specific clock list */ + struct clk_bulk_data *clks; + int num_clks; + + /* Regmap fields of I2SCTL & DMACTL registers bitfields */ + struct lpaif_i2sctl *i2sctl; + struct lpaif_dmactl *rd_dmactl; + struct lpaif_dmactl *wr_dmactl; + struct lpaif_dmactl *hdmi_rd_dmactl; + /* Regmap fields of HDMI_CTRL registers*/ + struct regmap_field *hdmitx_legacy_en; + struct regmap_field *hdmitx_parity_calc_en; + struct regmap_field *hdmitx_ch_msb[LPASS_MAX_HDMI_DMA_CHANNELS]; + struct regmap_field *hdmitx_ch_lsb[LPASS_MAX_HDMI_DMA_CHANNELS]; + struct lpass_hdmi_tx_ctl *tx_ctl; + struct lpass_vbit_ctrl *vbit_ctl; + struct lpass_hdmitx_dmactl *hdmi_tx_dmactl[LPASS_MAX_HDMI_DMA_CHANNELS]; + struct lpass_dp_metadata_ctl *meta_ctl; + struct lpass_sstream_ctl *sstream_ctl; +}; + +/* Vairant data per each SOC */ +struct lpass_variant { + u32 irq_reg_base; + u32 irq_reg_stride; + u32 irq_ports; + u32 rdma_reg_base; + u32 rdma_reg_stride; + u32 rdma_channels; + u32 hdmi_rdma_reg_base; + u32 hdmi_rdma_reg_stride; + u32 hdmi_rdma_channels; + u32 wrdma_reg_base; + u32 wrdma_reg_stride; + u32 wrdma_channels; + u32 i2sctrl_reg_base; + u32 i2sctrl_reg_stride; + u32 i2s_ports; + + /* I2SCTL Register fields */ + struct reg_field loopback; + struct reg_field spken; + struct reg_field spkmode; + struct reg_field spkmono; + struct reg_field micen; + struct reg_field micmode; + struct reg_field micmono; + struct reg_field wssrc; + struct reg_field bitwidth; + + u32 hdmi_irq_reg_base; + u32 hdmi_irq_reg_stride; + u32 hdmi_irq_ports; + + /* HDMI specific controls */ + u32 hdmi_tx_ctl_addr; + u32 hdmi_legacy_addr; + u32 hdmi_vbit_addr; + u32 hdmi_ch_lsb_addr; + u32 hdmi_ch_msb_addr; + u32 ch_stride; + u32 hdmi_parity_addr; + u32 hdmi_dmactl_addr; + u32 hdmi_dma_stride; + u32 hdmi_DP_addr; + u32 hdmi_sstream_addr; + + /* HDMI SSTREAM CTRL fields */ + struct reg_field sstream_en; + struct reg_field dma_sel; + struct reg_field auto_bbit_en; + struct reg_field layout; + struct reg_field layout_sp; + struct reg_field set_sp_on_en; + struct reg_field dp_audio; + struct reg_field dp_staffing_en; + struct reg_field dp_sp_b_hw_en; + + /* HDMI DP METADATA CTL fields */ + struct reg_field mute; + struct reg_field as_sdp_cc; + struct reg_field as_sdp_ct; + struct reg_field aif_db4; + struct reg_field frequency; + struct reg_field mst_index; + struct reg_field dptx_index; + + /* HDMI TX CTRL fields */ + struct reg_field soft_reset; + struct reg_field force_reset; + + /* HDMI TX DMA CTRL */ + struct reg_field use_hw_chs; + struct reg_field use_hw_usr; + struct reg_field hw_chs_sel; + struct reg_field hw_usr_sel; + + /* HDMI VBIT CTRL */ + struct reg_field replace_vbit; + struct reg_field vbit_stream; + + /* HDMI TX LEGACY */ + struct reg_field legacy_en; + + /* HDMI TX PARITY */ + struct reg_field calc_en; + + /* HDMI CH LSB */ + struct reg_field lsb_bits; + + /* HDMI CH MSB */ + struct reg_field msb_bits; + + struct reg_field hdmi_rdma_bursten; + struct reg_field hdmi_rdma_wpscnt; + struct reg_field hdmi_rdma_fifowm; + struct reg_field hdmi_rdma_enable; + struct reg_field hdmi_rdma_dyncclk; + struct reg_field hdmi_rdma_burst8; + struct reg_field hdmi_rdma_burst16; + struct reg_field hdmi_rdma_dynburst; + + /* RD_DMA Register fields */ + struct reg_field rdma_intf; + struct reg_field rdma_bursten; + struct reg_field rdma_wpscnt; + struct reg_field rdma_fifowm; + struct reg_field rdma_enable; + struct reg_field rdma_dyncclk; + + /* WR_DMA Register fields */ + struct reg_field wrdma_intf; + struct reg_field wrdma_bursten; + struct reg_field wrdma_wpscnt; + struct reg_field wrdma_fifowm; + struct reg_field wrdma_enable; + struct reg_field wrdma_dyncclk; + + /** + * on SOCs like APQ8016 the channel control bits start + * at different offset to ipq806x + **/ + u32 dmactl_audif_start; + u32 wrdma_channel_start; + /* SOC specific initialization like clocks */ + int (*init)(struct platform_device *pdev); + int (*exit)(struct platform_device *pdev); + int (*alloc_dma_channel)(struct lpass_data *data, int direction, unsigned int dai_id); + int (*free_dma_channel)(struct lpass_data *data, int ch, unsigned int dai_id); + + /* SOC specific dais */ + struct snd_soc_dai_driver *dai_driver; + int num_dai; + const char * const *dai_osr_clk_names; + const char * const *dai_bit_clk_names; + + /* SOC specific clocks configuration */ + const char **clk_name; + int num_clks; +}; + +/* register the platform driver from the CPU DAI driver */ +int asoc_qcom_lpass_platform_register(struct platform_device *); +int asoc_qcom_lpass_cpu_platform_remove(struct platform_device *pdev); +int asoc_qcom_lpass_cpu_platform_probe(struct platform_device *pdev); +int asoc_qcom_lpass_cpu_dai_probe(struct snd_soc_dai *dai); +extern const struct snd_soc_dai_ops asoc_qcom_lpass_cpu_dai_ops; + +#endif /* __LPASS_H__ */ diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile new file mode 100644 index 000000000..3c1dd9f32 --- /dev/null +++ b/sound/soc/qcom/qdsp6/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_SND_SOC_QDSP6_COMMON) += q6dsp-common.o +obj-$(CONFIG_SND_SOC_QDSP6_CORE) += q6core.o +obj-$(CONFIG_SND_SOC_QDSP6_AFE) += q6afe.o +obj-$(CONFIG_SND_SOC_QDSP6_AFE_DAI) += q6afe-dai.o +obj-$(CONFIG_SND_SOC_QDSP6_AFE_CLOCKS) += q6afe-clocks.o +obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o +obj-$(CONFIG_SND_SOC_QDSP6_ROUTING) += q6routing.o +obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o +obj-$(CONFIG_SND_SOC_QDSP6_ASM_DAI) += q6asm-dai.o diff --git a/sound/soc/qcom/qdsp6/q6adm.c b/sound/soc/qcom/qdsp6/q6adm.c new file mode 100644 index 000000000..182d36a34 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6adm.c @@ -0,0 +1,634 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2018, Linaro Limited + +#include <linux/device.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/kref.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/soc/qcom/apr.h> +#include <linux/wait.h> +#include <sound/asound.h> +#include "q6adm.h" +#include "q6afe.h" +#include "q6core.h" +#include "q6dsp-common.h" +#include "q6dsp-errno.h" + +#define ADM_CMD_DEVICE_OPEN_V5 0x00010326 +#define ADM_CMDRSP_DEVICE_OPEN_V5 0x00010329 +#define ADM_CMD_DEVICE_CLOSE_V5 0x00010327 +#define ADM_CMD_MATRIX_MAP_ROUTINGS_V5 0x00010325 + +#define TIMEOUT_MS 1000 +#define RESET_COPP_ID 99 +#define INVALID_COPP_ID 0xFF +/* Definition for a legacy device session. */ +#define ADM_LEGACY_DEVICE_SESSION 0 +#define ADM_MATRIX_ID_AUDIO_RX 0 +#define ADM_MATRIX_ID_AUDIO_TX 1 + +struct q6copp { + int afe_port; + int copp_idx; + int id; + int topology; + int mode; + int rate; + int bit_width; + int channels; + int app_type; + int acdb_id; + + struct aprv2_ibasic_rsp_result_t result; + struct kref refcount; + wait_queue_head_t wait; + struct list_head node; + struct q6adm *adm; +}; + +struct q6adm { + struct apr_device *apr; + struct device *dev; + struct q6core_svc_api_info ainfo; + unsigned long copp_bitmap[AFE_MAX_PORTS]; + struct list_head copps_list; + spinlock_t copps_list_lock; + struct aprv2_ibasic_rsp_result_t result; + struct mutex lock; + wait_queue_head_t matrix_map_wait; +}; + +struct q6adm_cmd_device_open_v5 { + u16 flags; + u16 mode_of_operation; + u16 endpoint_id_1; + u16 endpoint_id_2; + u32 topology_id; + u16 dev_num_channel; + u16 bit_width; + u32 sample_rate; + u8 dev_channel_mapping[8]; +} __packed; + +struct q6adm_cmd_matrix_map_routings_v5 { + u32 matrix_id; + u32 num_sessions; +} __packed; + +struct q6adm_session_map_node_v5 { + u16 session_id; + u16 num_copps; +} __packed; + +static struct q6copp *q6adm_find_copp(struct q6adm *adm, int port_idx, + int copp_idx) +{ + struct q6copp *c = NULL; + struct q6copp *ret = NULL; + unsigned long flags; + + spin_lock_irqsave(&adm->copps_list_lock, flags); + list_for_each_entry(c, &adm->copps_list, node) { + if ((port_idx == c->afe_port) && (copp_idx == c->copp_idx)) { + ret = c; + kref_get(&c->refcount); + break; + } + } + + spin_unlock_irqrestore(&adm->copps_list_lock, flags); + + return ret; + +} + +static void q6adm_free_copp(struct kref *ref) +{ + struct q6copp *c = container_of(ref, struct q6copp, refcount); + struct q6adm *adm = c->adm; + unsigned long flags; + + spin_lock_irqsave(&adm->copps_list_lock, flags); + clear_bit(c->copp_idx, &adm->copp_bitmap[c->afe_port]); + list_del(&c->node); + spin_unlock_irqrestore(&adm->copps_list_lock, flags); + kfree(c); +} + +static int q6adm_callback(struct apr_device *adev, struct apr_resp_pkt *data) +{ + struct aprv2_ibasic_rsp_result_t *result = data->payload; + int port_idx, copp_idx; + struct apr_hdr *hdr = &data->hdr; + struct q6copp *copp; + struct q6adm *adm = dev_get_drvdata(&adev->dev); + + if (!data->payload_size) + return 0; + + copp_idx = (hdr->token) & 0XFF; + port_idx = ((hdr->token) >> 16) & 0xFF; + if (port_idx < 0 || port_idx >= AFE_MAX_PORTS) { + dev_err(&adev->dev, "Invalid port idx %d token %d\n", + port_idx, hdr->token); + return 0; + } + if (copp_idx < 0 || copp_idx >= MAX_COPPS_PER_PORT) { + dev_err(&adev->dev, "Invalid copp idx %d token %d\n", + copp_idx, hdr->token); + return 0; + } + + switch (hdr->opcode) { + case APR_BASIC_RSP_RESULT: { + if (result->status != 0) { + dev_err(&adev->dev, "cmd = 0x%x return error = 0x%x\n", + result->opcode, result->status); + } + switch (result->opcode) { + case ADM_CMD_DEVICE_OPEN_V5: + case ADM_CMD_DEVICE_CLOSE_V5: + copp = q6adm_find_copp(adm, port_idx, copp_idx); + if (!copp) + return 0; + + copp->result = *result; + wake_up(&copp->wait); + kref_put(&copp->refcount, q6adm_free_copp); + break; + case ADM_CMD_MATRIX_MAP_ROUTINGS_V5: + adm->result = *result; + wake_up(&adm->matrix_map_wait); + break; + + default: + dev_err(&adev->dev, "Unknown Cmd: 0x%x\n", + result->opcode); + break; + } + return 0; + } + case ADM_CMDRSP_DEVICE_OPEN_V5: { + struct adm_cmd_rsp_device_open_v5 { + u32 status; + u16 copp_id; + u16 reserved; + } __packed * open = data->payload; + + copp = q6adm_find_copp(adm, port_idx, copp_idx); + if (!copp) + return 0; + + if (open->copp_id == INVALID_COPP_ID) { + dev_err(&adev->dev, "Invalid coppid rxed %d\n", + open->copp_id); + copp->result.status = ADSP_EBADPARAM; + wake_up(&copp->wait); + kref_put(&copp->refcount, q6adm_free_copp); + break; + } + copp->result.opcode = hdr->opcode; + copp->id = open->copp_id; + wake_up(&copp->wait); + kref_put(&copp->refcount, q6adm_free_copp); + } + break; + default: + dev_err(&adev->dev, "Unknown cmd:0x%x\n", + hdr->opcode); + break; + } + + return 0; +} + +static struct q6copp *q6adm_alloc_copp(struct q6adm *adm, int port_idx) +{ + struct q6copp *c; + int idx; + + idx = find_first_zero_bit(&adm->copp_bitmap[port_idx], + MAX_COPPS_PER_PORT); + + if (idx >= MAX_COPPS_PER_PORT) + return ERR_PTR(-EBUSY); + + c = kzalloc(sizeof(*c), GFP_ATOMIC); + if (!c) + return ERR_PTR(-ENOMEM); + + set_bit(idx, &adm->copp_bitmap[port_idx]); + c->copp_idx = idx; + c->afe_port = port_idx; + c->adm = adm; + + init_waitqueue_head(&c->wait); + + return c; +} + +static int q6adm_apr_send_copp_pkt(struct q6adm *adm, struct q6copp *copp, + struct apr_pkt *pkt, uint32_t rsp_opcode) +{ + struct device *dev = adm->dev; + uint32_t opcode = pkt->hdr.opcode; + int ret; + + mutex_lock(&adm->lock); + copp->result.opcode = 0; + copp->result.status = 0; + ret = apr_send_pkt(adm->apr, pkt); + if (ret < 0) { + dev_err(dev, "Failed to send APR packet\n"); + ret = -EINVAL; + goto err; + } + + /* Wait for the callback with copp id */ + if (rsp_opcode) + ret = wait_event_timeout(copp->wait, + (copp->result.opcode == opcode) || + (copp->result.opcode == rsp_opcode), + msecs_to_jiffies(TIMEOUT_MS)); + else + ret = wait_event_timeout(copp->wait, + (copp->result.opcode == opcode), + msecs_to_jiffies(TIMEOUT_MS)); + + if (!ret) { + dev_err(dev, "ADM copp cmd timedout\n"); + ret = -ETIMEDOUT; + } else if (copp->result.status > 0) { + dev_err(dev, "DSP returned error[%d]\n", + copp->result.status); + ret = -EINVAL; + } + +err: + mutex_unlock(&adm->lock); + return ret; +} + +static int q6adm_device_close(struct q6adm *adm, struct q6copp *copp, + int port_id, int copp_idx) +{ + struct apr_pkt close; + + close.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + close.hdr.pkt_size = sizeof(close); + close.hdr.src_port = port_id; + close.hdr.dest_port = copp->id; + close.hdr.token = port_id << 16 | copp_idx; + close.hdr.opcode = ADM_CMD_DEVICE_CLOSE_V5; + + return q6adm_apr_send_copp_pkt(adm, copp, &close, 0); +} + +static struct q6copp *q6adm_find_matching_copp(struct q6adm *adm, + int port_id, int topology, + int mode, int rate, + int channel_mode, int bit_width, + int app_type) +{ + struct q6copp *c = NULL; + struct q6copp *ret = NULL; + unsigned long flags; + + spin_lock_irqsave(&adm->copps_list_lock, flags); + + list_for_each_entry(c, &adm->copps_list, node) { + if ((port_id == c->afe_port) && (topology == c->topology) && + (mode == c->mode) && (rate == c->rate) && + (bit_width == c->bit_width) && (app_type == c->app_type)) { + ret = c; + kref_get(&c->refcount); + } + } + spin_unlock_irqrestore(&adm->copps_list_lock, flags); + + return ret; +} + +static int q6adm_device_open(struct q6adm *adm, struct q6copp *copp, + int port_id, int path, int topology, + int channel_mode, int bit_width, int rate) +{ + struct q6adm_cmd_device_open_v5 *open; + int afe_port = q6afe_get_port_id(port_id); + struct apr_pkt *pkt; + void *p; + int ret, pkt_size; + + pkt_size = APR_HDR_SIZE + sizeof(*open); + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + open = p + APR_HDR_SIZE; + pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + pkt->hdr.pkt_size = pkt_size; + pkt->hdr.src_port = afe_port; + pkt->hdr.dest_port = afe_port; + pkt->hdr.token = port_id << 16 | copp->copp_idx; + pkt->hdr.opcode = ADM_CMD_DEVICE_OPEN_V5; + open->flags = ADM_LEGACY_DEVICE_SESSION; + open->mode_of_operation = path; + open->endpoint_id_1 = afe_port; + open->topology_id = topology; + open->dev_num_channel = channel_mode & 0x00FF; + open->bit_width = bit_width; + open->sample_rate = rate; + + ret = q6dsp_map_channels(&open->dev_channel_mapping[0], + channel_mode); + if (ret) + goto err; + + ret = q6adm_apr_send_copp_pkt(adm, copp, pkt, + ADM_CMDRSP_DEVICE_OPEN_V5); + +err: + kfree(pkt); + return ret; +} + +/** + * q6adm_open() - open adm and grab a free copp + * + * @dev: Pointer to adm child device. + * @port_id: port id + * @path: playback or capture path. + * @rate: rate at which copp is required. + * @channel_mode: channel mode + * @topology: adm topology id + * @perf_mode: performace mode. + * @bit_width: audio sample bit width + * @app_type: Application type. + * @acdb_id: ACDB id + * + * Return: Will be an negative on error or a valid copp pointer on success. + */ +struct q6copp *q6adm_open(struct device *dev, int port_id, int path, int rate, + int channel_mode, int topology, int perf_mode, + uint16_t bit_width, int app_type, int acdb_id) +{ + struct q6adm *adm = dev_get_drvdata(dev->parent); + struct q6copp *copp; + unsigned long flags; + int ret = 0; + + if (port_id < 0) { + dev_err(dev, "Invalid port_id 0x%x\n", port_id); + return ERR_PTR(-EINVAL); + } + + copp = q6adm_find_matching_copp(adm, port_id, topology, perf_mode, + rate, channel_mode, bit_width, app_type); + if (copp) { + dev_err(dev, "Found Matching Copp 0x%x\n", copp->copp_idx); + return copp; + } + + spin_lock_irqsave(&adm->copps_list_lock, flags); + copp = q6adm_alloc_copp(adm, port_id); + if (IS_ERR(copp)) { + spin_unlock_irqrestore(&adm->copps_list_lock, flags); + return ERR_CAST(copp); + } + + list_add_tail(&copp->node, &adm->copps_list); + spin_unlock_irqrestore(&adm->copps_list_lock, flags); + + kref_init(&copp->refcount); + copp->topology = topology; + copp->mode = perf_mode; + copp->rate = rate; + copp->channels = channel_mode; + copp->bit_width = bit_width; + copp->app_type = app_type; + + ret = q6adm_device_open(adm, copp, port_id, path, topology, + channel_mode, bit_width, rate); + if (ret < 0) { + kref_put(&copp->refcount, q6adm_free_copp); + return ERR_PTR(ret); + } + + return copp; +} +EXPORT_SYMBOL_GPL(q6adm_open); + +/** + * q6adm_get_copp_id() - get copp index + * + * @copp: Pointer to valid copp + * + * Return: Will be an negative on error or a valid copp index on success. + **/ +int q6adm_get_copp_id(struct q6copp *copp) +{ + if (!copp) + return -EINVAL; + + return copp->copp_idx; +} +EXPORT_SYMBOL_GPL(q6adm_get_copp_id); + +/** + * q6adm_matrix_map() - Map asm streams and afe ports using payload + * + * @dev: Pointer to adm child device. + * @path: playback or capture path. + * @payload_map: map between session id and afe ports. + * @perf_mode: Performace mode. + * + * Return: Will be an negative on error or a zero on success. + */ +int q6adm_matrix_map(struct device *dev, int path, + struct route_payload payload_map, int perf_mode) +{ + struct q6adm *adm = dev_get_drvdata(dev->parent); + struct q6adm_cmd_matrix_map_routings_v5 *route; + struct q6adm_session_map_node_v5 *node; + struct apr_pkt *pkt; + uint16_t *copps_list; + int pkt_size, ret, i, copp_idx; + void *matrix_map = NULL; + struct q6copp *copp; + + /* Assumes port_ids have already been validated during adm_open */ + pkt_size = (APR_HDR_SIZE + sizeof(*route) + sizeof(*node) + + (sizeof(uint32_t) * payload_map.num_copps)); + + matrix_map = kzalloc(pkt_size, GFP_KERNEL); + if (!matrix_map) + return -ENOMEM; + + pkt = matrix_map; + route = matrix_map + APR_HDR_SIZE; + node = matrix_map + APR_HDR_SIZE + sizeof(*route); + copps_list = matrix_map + APR_HDR_SIZE + sizeof(*route) + sizeof(*node); + + pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + pkt->hdr.pkt_size = pkt_size; + pkt->hdr.token = 0; + pkt->hdr.opcode = ADM_CMD_MATRIX_MAP_ROUTINGS_V5; + route->num_sessions = 1; + + switch (path) { + case ADM_PATH_PLAYBACK: + route->matrix_id = ADM_MATRIX_ID_AUDIO_RX; + break; + case ADM_PATH_LIVE_REC: + route->matrix_id = ADM_MATRIX_ID_AUDIO_TX; + break; + default: + dev_err(dev, "Wrong path set[%d]\n", path); + break; + } + + node->session_id = payload_map.session_id; + node->num_copps = payload_map.num_copps; + + for (i = 0; i < payload_map.num_copps; i++) { + int port_idx = payload_map.port_id[i]; + + if (port_idx < 0) { + dev_err(dev, "Invalid port_id 0x%x\n", + payload_map.port_id[i]); + kfree(pkt); + return -EINVAL; + } + copp_idx = payload_map.copp_idx[i]; + + copp = q6adm_find_copp(adm, port_idx, copp_idx); + if (!copp) { + kfree(pkt); + return -EINVAL; + } + + copps_list[i] = copp->id; + kref_put(&copp->refcount, q6adm_free_copp); + } + + mutex_lock(&adm->lock); + adm->result.status = 0; + adm->result.opcode = 0; + + ret = apr_send_pkt(adm->apr, pkt); + if (ret < 0) { + dev_err(dev, "routing for stream %d failed ret %d\n", + payload_map.session_id, ret); + goto fail_cmd; + } + ret = wait_event_timeout(adm->matrix_map_wait, + adm->result.opcode == pkt->hdr.opcode, + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + dev_err(dev, "routing for stream %d failed\n", + payload_map.session_id); + ret = -ETIMEDOUT; + goto fail_cmd; + } else if (adm->result.status > 0) { + dev_err(dev, "DSP returned error[%d]\n", + adm->result.status); + ret = -EINVAL; + goto fail_cmd; + } + +fail_cmd: + mutex_unlock(&adm->lock); + kfree(pkt); + return ret; +} +EXPORT_SYMBOL_GPL(q6adm_matrix_map); + +/** + * q6adm_close() - Close adm copp + * + * @dev: Pointer to adm child device. + * @copp: pointer to previously opened copp + * + * Return: Will be an negative on error or a zero on success. + */ +int q6adm_close(struct device *dev, struct q6copp *copp) +{ + struct q6adm *adm = dev_get_drvdata(dev->parent); + int ret = 0; + + ret = q6adm_device_close(adm, copp, copp->afe_port, copp->copp_idx); + if (ret < 0) { + dev_err(adm->dev, "Failed to close copp %d\n", ret); + return ret; + } + + kref_put(&copp->refcount, q6adm_free_copp); + + return 0; +} +EXPORT_SYMBOL_GPL(q6adm_close); + +static int q6adm_probe(struct apr_device *adev) +{ + struct device *dev = &adev->dev; + struct q6adm *adm; + + adm = devm_kzalloc(dev, sizeof(*adm), GFP_KERNEL); + if (!adm) + return -ENOMEM; + + adm->apr = adev; + dev_set_drvdata(dev, adm); + adm->dev = dev; + q6core_get_svc_api_info(adev->svc_id, &adm->ainfo); + mutex_init(&adm->lock); + init_waitqueue_head(&adm->matrix_map_wait); + + INIT_LIST_HEAD(&adm->copps_list); + spin_lock_init(&adm->copps_list_lock); + + return of_platform_populate(dev->of_node, NULL, NULL, dev); +} + +static int q6adm_remove(struct apr_device *adev) +{ + of_platform_depopulate(&adev->dev); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id q6adm_device_id[] = { + { .compatible = "qcom,q6adm" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6adm_device_id); +#endif + +static struct apr_driver qcom_q6adm_driver = { + .probe = q6adm_probe, + .remove = q6adm_remove, + .callback = q6adm_callback, + .driver = { + .name = "qcom-q6adm", + .of_match_table = of_match_ptr(q6adm_device_id), + }, +}; + +module_apr_driver(qcom_q6adm_driver); +MODULE_DESCRIPTION("Q6 Audio Device Manager"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6adm.h b/sound/soc/qcom/qdsp6/q6adm.h new file mode 100644 index 000000000..4f56999b7 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6adm.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __Q6_ADM_V2_H__ +#define __Q6_ADM_V2_H__ + +#define ADM_PATH_PLAYBACK 0x1 +#define ADM_PATH_LIVE_REC 0x2 +#define MAX_COPPS_PER_PORT 8 +#define NULL_COPP_TOPOLOGY 0x00010312 + +/* multiple copp per stream. */ +struct route_payload { + int num_copps; + int session_id; + int copp_idx[MAX_COPPS_PER_PORT]; + int port_id[MAX_COPPS_PER_PORT]; +}; + +struct q6copp; +struct q6copp *q6adm_open(struct device *dev, int port_id, int path, int rate, + int channel_mode, int topology, int perf_mode, + uint16_t bit_width, int app_type, int acdb_id); +int q6adm_close(struct device *dev, struct q6copp *copp); +int q6adm_get_copp_id(struct q6copp *copp); +int q6adm_matrix_map(struct device *dev, int path, + struct route_payload payload_map, int perf_mode); + +#endif /* __Q6_ADM_V2_H__ */ diff --git a/sound/soc/qcom/qdsp6/q6afe-clocks.c b/sound/soc/qcom/qdsp6/q6afe-clocks.c new file mode 100644 index 000000000..acfc0c698 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6afe-clocks.c @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020, Linaro Limited + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/clk-provider.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/slab.h> +#include "q6afe.h" + +#define Q6AFE_CLK(id) &(struct q6afe_clk) { \ + .clk_id = id, \ + .afe_clk_id = Q6AFE_##id, \ + .name = #id, \ + .attributes = LPASS_CLK_ATTRIBUTE_COUPLE_NO, \ + .rate = 19200000, \ + .hw.init = &(struct clk_init_data) { \ + .ops = &clk_q6afe_ops, \ + .name = #id, \ + }, \ + } + +#define Q6AFE_VOTE_CLK(id, blkid, n) &(struct q6afe_clk) { \ + .clk_id = id, \ + .afe_clk_id = blkid, \ + .name = #n, \ + .hw.init = &(struct clk_init_data) { \ + .ops = &clk_vote_q6afe_ops, \ + .name = #id, \ + }, \ + } + +struct q6afe_clk { + struct device *dev; + int clk_id; + int afe_clk_id; + char *name; + int attributes; + int rate; + uint32_t handle; + struct clk_hw hw; +}; + +#define to_q6afe_clk(_hw) container_of(_hw, struct q6afe_clk, hw) + +struct q6afe_cc { + struct device *dev; + struct q6afe_clk **clks; + int num_clks; +}; + +static int clk_q6afe_prepare(struct clk_hw *hw) +{ + struct q6afe_clk *clk = to_q6afe_clk(hw); + + return q6afe_set_lpass_clock(clk->dev, clk->afe_clk_id, clk->attributes, + Q6AFE_LPASS_CLK_ROOT_DEFAULT, clk->rate); +} + +static void clk_q6afe_unprepare(struct clk_hw *hw) +{ + struct q6afe_clk *clk = to_q6afe_clk(hw); + + q6afe_set_lpass_clock(clk->dev, clk->afe_clk_id, clk->attributes, + Q6AFE_LPASS_CLK_ROOT_DEFAULT, 0); +} + +static int clk_q6afe_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct q6afe_clk *clk = to_q6afe_clk(hw); + + clk->rate = rate; + + return 0; +} + +static unsigned long clk_q6afe_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct q6afe_clk *clk = to_q6afe_clk(hw); + + return clk->rate; +} + +static long clk_q6afe_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + return rate; +} + +static const struct clk_ops clk_q6afe_ops = { + .prepare = clk_q6afe_prepare, + .unprepare = clk_q6afe_unprepare, + .set_rate = clk_q6afe_set_rate, + .round_rate = clk_q6afe_round_rate, + .recalc_rate = clk_q6afe_recalc_rate, +}; + +static int clk_vote_q6afe_block(struct clk_hw *hw) +{ + struct q6afe_clk *clk = to_q6afe_clk(hw); + + return q6afe_vote_lpass_core_hw(clk->dev, clk->afe_clk_id, + clk->name, &clk->handle); +} + +static void clk_unvote_q6afe_block(struct clk_hw *hw) +{ + struct q6afe_clk *clk = to_q6afe_clk(hw); + + q6afe_unvote_lpass_core_hw(clk->dev, clk->afe_clk_id, clk->handle); +} + +static const struct clk_ops clk_vote_q6afe_ops = { + .prepare = clk_vote_q6afe_block, + .unprepare = clk_unvote_q6afe_block, +}; + +struct q6afe_clk *q6afe_clks[Q6AFE_MAX_CLK_ID] = { + [LPASS_CLK_ID_PRI_MI2S_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_PRI_MI2S_IBIT), + [LPASS_CLK_ID_PRI_MI2S_EBIT] = Q6AFE_CLK(LPASS_CLK_ID_PRI_MI2S_EBIT), + [LPASS_CLK_ID_SEC_MI2S_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_SEC_MI2S_IBIT), + [LPASS_CLK_ID_SEC_MI2S_EBIT] = Q6AFE_CLK(LPASS_CLK_ID_SEC_MI2S_EBIT), + [LPASS_CLK_ID_TER_MI2S_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_TER_MI2S_IBIT), + [LPASS_CLK_ID_TER_MI2S_EBIT] = Q6AFE_CLK(LPASS_CLK_ID_TER_MI2S_EBIT), + [LPASS_CLK_ID_QUAD_MI2S_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_QUAD_MI2S_IBIT), + [LPASS_CLK_ID_QUAD_MI2S_EBIT] = Q6AFE_CLK(LPASS_CLK_ID_QUAD_MI2S_EBIT), + [LPASS_CLK_ID_SPEAKER_I2S_IBIT] = + Q6AFE_CLK(LPASS_CLK_ID_SPEAKER_I2S_IBIT), + [LPASS_CLK_ID_SPEAKER_I2S_EBIT] = + Q6AFE_CLK(LPASS_CLK_ID_SPEAKER_I2S_EBIT), + [LPASS_CLK_ID_SPEAKER_I2S_OSR] = + Q6AFE_CLK(LPASS_CLK_ID_SPEAKER_I2S_OSR), + [LPASS_CLK_ID_QUI_MI2S_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_QUI_MI2S_IBIT), + [LPASS_CLK_ID_QUI_MI2S_EBIT] = Q6AFE_CLK(LPASS_CLK_ID_QUI_MI2S_EBIT), + [LPASS_CLK_ID_SEN_MI2S_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_SEN_MI2S_IBIT), + [LPASS_CLK_ID_SEN_MI2S_EBIT] = Q6AFE_CLK(LPASS_CLK_ID_SEN_MI2S_EBIT), + [LPASS_CLK_ID_INT0_MI2S_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_INT0_MI2S_IBIT), + [LPASS_CLK_ID_INT1_MI2S_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_INT1_MI2S_IBIT), + [LPASS_CLK_ID_INT2_MI2S_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_INT2_MI2S_IBIT), + [LPASS_CLK_ID_INT3_MI2S_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_INT3_MI2S_IBIT), + [LPASS_CLK_ID_INT4_MI2S_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_INT4_MI2S_IBIT), + [LPASS_CLK_ID_INT5_MI2S_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_INT5_MI2S_IBIT), + [LPASS_CLK_ID_INT6_MI2S_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_INT6_MI2S_IBIT), + [LPASS_CLK_ID_QUI_MI2S_OSR] = Q6AFE_CLK(LPASS_CLK_ID_QUI_MI2S_OSR), + [LPASS_CLK_ID_PRI_PCM_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_PRI_PCM_IBIT), + [LPASS_CLK_ID_PRI_PCM_EBIT] = Q6AFE_CLK(LPASS_CLK_ID_PRI_PCM_EBIT), + [LPASS_CLK_ID_SEC_PCM_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_SEC_PCM_IBIT), + [LPASS_CLK_ID_SEC_PCM_EBIT] = Q6AFE_CLK(LPASS_CLK_ID_SEC_PCM_EBIT), + [LPASS_CLK_ID_TER_PCM_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_TER_PCM_IBIT), + [LPASS_CLK_ID_TER_PCM_EBIT] = Q6AFE_CLK(LPASS_CLK_ID_TER_PCM_EBIT), + [LPASS_CLK_ID_QUAD_PCM_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_QUAD_PCM_IBIT), + [LPASS_CLK_ID_QUAD_PCM_EBIT] = Q6AFE_CLK(LPASS_CLK_ID_QUAD_PCM_EBIT), + [LPASS_CLK_ID_QUIN_PCM_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_QUIN_PCM_IBIT), + [LPASS_CLK_ID_QUIN_PCM_EBIT] = Q6AFE_CLK(LPASS_CLK_ID_QUIN_PCM_EBIT), + [LPASS_CLK_ID_QUI_PCM_OSR] = Q6AFE_CLK(LPASS_CLK_ID_QUI_PCM_OSR), + [LPASS_CLK_ID_PRI_TDM_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_PRI_TDM_IBIT), + [LPASS_CLK_ID_PRI_TDM_EBIT] = Q6AFE_CLK(LPASS_CLK_ID_PRI_TDM_EBIT), + [LPASS_CLK_ID_SEC_TDM_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_SEC_TDM_IBIT), + [LPASS_CLK_ID_SEC_TDM_EBIT] = Q6AFE_CLK(LPASS_CLK_ID_SEC_TDM_EBIT), + [LPASS_CLK_ID_TER_TDM_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_TER_TDM_IBIT), + [LPASS_CLK_ID_TER_TDM_EBIT] = Q6AFE_CLK(LPASS_CLK_ID_TER_TDM_EBIT), + [LPASS_CLK_ID_QUAD_TDM_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_QUAD_TDM_IBIT), + [LPASS_CLK_ID_QUAD_TDM_EBIT] = Q6AFE_CLK(LPASS_CLK_ID_QUAD_TDM_EBIT), + [LPASS_CLK_ID_QUIN_TDM_IBIT] = Q6AFE_CLK(LPASS_CLK_ID_QUIN_TDM_IBIT), + [LPASS_CLK_ID_QUIN_TDM_EBIT] = Q6AFE_CLK(LPASS_CLK_ID_QUIN_TDM_EBIT), + [LPASS_CLK_ID_QUIN_TDM_OSR] = Q6AFE_CLK(LPASS_CLK_ID_QUIN_TDM_OSR), + [LPASS_CLK_ID_MCLK_1] = Q6AFE_CLK(LPASS_CLK_ID_MCLK_1), + [LPASS_CLK_ID_MCLK_2] = Q6AFE_CLK(LPASS_CLK_ID_MCLK_2), + [LPASS_CLK_ID_MCLK_3] = Q6AFE_CLK(LPASS_CLK_ID_MCLK_3), + [LPASS_CLK_ID_MCLK_4] = Q6AFE_CLK(LPASS_CLK_ID_MCLK_4), + [LPASS_CLK_ID_INTERNAL_DIGITAL_CODEC_CORE] = + Q6AFE_CLK(LPASS_CLK_ID_INTERNAL_DIGITAL_CODEC_CORE), + [LPASS_CLK_ID_INT_MCLK_0] = Q6AFE_CLK(LPASS_CLK_ID_INT_MCLK_0), + [LPASS_CLK_ID_INT_MCLK_1] = Q6AFE_CLK(LPASS_CLK_ID_INT_MCLK_1), + [LPASS_CLK_ID_WSA_CORE_MCLK] = Q6AFE_CLK(LPASS_CLK_ID_WSA_CORE_MCLK), + [LPASS_CLK_ID_WSA_CORE_NPL_MCLK] = + Q6AFE_CLK(LPASS_CLK_ID_WSA_CORE_NPL_MCLK), + [LPASS_CLK_ID_VA_CORE_MCLK] = Q6AFE_CLK(LPASS_CLK_ID_VA_CORE_MCLK), + [LPASS_CLK_ID_TX_CORE_MCLK] = Q6AFE_CLK(LPASS_CLK_ID_TX_CORE_MCLK), + [LPASS_CLK_ID_TX_CORE_NPL_MCLK] = + Q6AFE_CLK(LPASS_CLK_ID_TX_CORE_NPL_MCLK), + [LPASS_CLK_ID_RX_CORE_MCLK] = Q6AFE_CLK(LPASS_CLK_ID_RX_CORE_MCLK), + [LPASS_CLK_ID_RX_CORE_NPL_MCLK] = + Q6AFE_CLK(LPASS_CLK_ID_RX_CORE_NPL_MCLK), + [LPASS_CLK_ID_VA_CORE_2X_MCLK] = + Q6AFE_CLK(LPASS_CLK_ID_VA_CORE_2X_MCLK), + [LPASS_HW_AVTIMER_VOTE] = Q6AFE_VOTE_CLK(LPASS_HW_AVTIMER_VOTE, + Q6AFE_LPASS_CORE_AVTIMER_BLOCK, + "LPASS_AVTIMER_MACRO"), + [LPASS_HW_MACRO_VOTE] = Q6AFE_VOTE_CLK(LPASS_HW_MACRO_VOTE, + Q6AFE_LPASS_CORE_HW_MACRO_BLOCK, + "LPASS_HW_MACRO"), + [LPASS_HW_DCODEC_VOTE] = Q6AFE_VOTE_CLK(LPASS_HW_DCODEC_VOTE, + Q6AFE_LPASS_CORE_HW_DCODEC_BLOCK, + "LPASS_HW_DCODEC"), +}; + +static struct clk_hw *q6afe_of_clk_hw_get(struct of_phandle_args *clkspec, + void *data) +{ + struct q6afe_cc *cc = data; + unsigned int idx = clkspec->args[0]; + unsigned int attr = clkspec->args[1]; + + if (idx >= cc->num_clks || attr > LPASS_CLK_ATTRIBUTE_COUPLE_DIVISOR) { + dev_err(cc->dev, "Invalid clk specifier (%d, %d)\n", idx, attr); + return ERR_PTR(-EINVAL); + } + + if (cc->clks[idx]) { + cc->clks[idx]->attributes = attr; + return &cc->clks[idx]->hw; + } + + return ERR_PTR(-ENOENT); +} + +static int q6afe_clock_dev_probe(struct platform_device *pdev) +{ + struct q6afe_cc *cc; + struct device *dev = &pdev->dev; + int i, ret; + + cc = devm_kzalloc(dev, sizeof(*cc), GFP_KERNEL); + if (!cc) + return -ENOMEM; + + cc->clks = &q6afe_clks[0]; + cc->num_clks = ARRAY_SIZE(q6afe_clks); + for (i = 0; i < ARRAY_SIZE(q6afe_clks); i++) { + if (!q6afe_clks[i]) + continue; + + q6afe_clks[i]->dev = dev; + + ret = devm_clk_hw_register(dev, &q6afe_clks[i]->hw); + if (ret) + return ret; + } + + ret = of_clk_add_hw_provider(dev->of_node, q6afe_of_clk_hw_get, cc); + if (ret) + return ret; + + dev_set_drvdata(dev, cc); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id q6afe_clock_device_id[] = { + { .compatible = "qcom,q6afe-clocks" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6afe_clock_device_id); +#endif + +static struct platform_driver q6afe_clock_platform_driver = { + .driver = { + .name = "q6afe-clock", + .of_match_table = of_match_ptr(q6afe_clock_device_id), + }, + .probe = q6afe_clock_dev_probe, +}; +module_platform_driver(q6afe_clock_platform_driver); + +MODULE_DESCRIPTION("Q6 Audio Frontend clock driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c new file mode 100644 index 000000000..4e1f10128 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6afe-dai.c @@ -0,0 +1,1710 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2018, Linaro Limited + +#include <linux/err.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/pcm.h> +#include <sound/soc.h> +#include <sound/pcm_params.h> +#include "q6afe.h" + +#define Q6AFE_TDM_PB_DAI(pre, num, did) { \ + .playback = { \ + .stream_name = pre" TDM"#num" Playback", \ + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_176400, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + .channels_min = 1, \ + .channels_max = 8, \ + .rate_min = 8000, \ + .rate_max = 176400, \ + }, \ + .name = #did, \ + .ops = &q6tdm_ops, \ + .id = did, \ + .probe = msm_dai_q6_dai_probe, \ + .remove = msm_dai_q6_dai_remove, \ + } + +#define Q6AFE_TDM_CAP_DAI(pre, num, did) { \ + .capture = { \ + .stream_name = pre" TDM"#num" Capture", \ + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_176400, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + .channels_min = 1, \ + .channels_max = 8, \ + .rate_min = 8000, \ + .rate_max = 176400, \ + }, \ + .name = #did, \ + .ops = &q6tdm_ops, \ + .id = did, \ + .probe = msm_dai_q6_dai_probe, \ + .remove = msm_dai_q6_dai_remove, \ + } + +#define Q6AFE_CDC_DMA_RX_DAI(did) { \ + .playback = { \ + .stream_name = #did" Playback", \ + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_176400, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + .channels_min = 1, \ + .channels_max = 8, \ + .rate_min = 8000, \ + .rate_max = 176400, \ + }, \ + .name = #did, \ + .ops = &q6dma_ops, \ + .id = did, \ + .probe = msm_dai_q6_dai_probe, \ + .remove = msm_dai_q6_dai_remove, \ + } + +#define Q6AFE_CDC_DMA_TX_DAI(did) { \ + .capture = { \ + .stream_name = #did" Capture", \ + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 |\ + SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_48000 |\ + SNDRV_PCM_RATE_176400, \ + .formats = SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE | \ + SNDRV_PCM_FMTBIT_S32_LE, \ + .channels_min = 1, \ + .channels_max = 8, \ + .rate_min = 8000, \ + .rate_max = 176400, \ + }, \ + .name = #did, \ + .ops = &q6dma_ops, \ + .id = did, \ + .probe = msm_dai_q6_dai_probe, \ + .remove = msm_dai_q6_dai_remove, \ + } + +struct q6afe_dai_priv_data { + uint32_t sd_line_mask; + uint32_t sync_mode; + uint32_t sync_src; + uint32_t data_out_enable; + uint32_t invert_sync; + uint32_t data_delay; + uint32_t data_align; +}; + +struct q6afe_dai_data { + struct q6afe_port *port[AFE_PORT_MAX]; + struct q6afe_port_config port_config[AFE_PORT_MAX]; + bool is_port_started[AFE_PORT_MAX]; + struct q6afe_dai_priv_data priv[AFE_PORT_MAX]; +}; + +static int q6slim_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6afe_slim_cfg *slim = &dai_data->port_config[dai->id].slim; + + slim->sample_rate = params_rate(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + case SNDRV_PCM_FORMAT_SPECIAL: + slim->bit_width = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + slim->bit_width = 24; + break; + case SNDRV_PCM_FORMAT_S32_LE: + slim->bit_width = 32; + break; + default: + pr_err("%s: format %d\n", + __func__, params_format(params)); + return -EINVAL; + } + + return 0; +} + +static int q6hdmi_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + int channels = params_channels(params); + struct q6afe_hdmi_cfg *hdmi = &dai_data->port_config[dai->id].hdmi; + + hdmi->sample_rate = params_rate(params); + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + hdmi->bit_width = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + hdmi->bit_width = 24; + break; + } + + /* HDMI spec CEA-861-E: Table 28 Audio InfoFrame Data Byte 4 */ + switch (channels) { + case 2: + hdmi->channel_allocation = 0; + break; + case 3: + hdmi->channel_allocation = 0x02; + break; + case 4: + hdmi->channel_allocation = 0x06; + break; + case 5: + hdmi->channel_allocation = 0x0A; + break; + case 6: + hdmi->channel_allocation = 0x0B; + break; + case 7: + hdmi->channel_allocation = 0x12; + break; + case 8: + hdmi->channel_allocation = 0x13; + break; + default: + dev_err(dai->dev, "invalid Channels = %u\n", channels); + return -EINVAL; + } + + return 0; +} + +static int q6i2s_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6afe_i2s_cfg *i2s = &dai_data->port_config[dai->id].i2s_cfg; + + i2s->sample_rate = params_rate(params); + i2s->bit_width = params_width(params); + i2s->num_channels = params_channels(params); + i2s->sd_line_mask = dai_data->priv[dai->id].sd_line_mask; + + return 0; +} + +static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6afe_i2s_cfg *i2s = &dai_data->port_config[dai->id].i2s_cfg; + + i2s->fmt = fmt; + + return 0; +} + +static int q6tdm_set_tdm_slot(struct snd_soc_dai *dai, + unsigned int tx_mask, + unsigned int rx_mask, + int slots, int slot_width) +{ + + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6afe_tdm_cfg *tdm = &dai_data->port_config[dai->id].tdm; + unsigned int cap_mask; + int rc = 0; + + /* HW only supports 16 and 32 bit slot width configuration */ + if ((slot_width != 16) && (slot_width != 32)) { + dev_err(dai->dev, "%s: invalid slot_width %d\n", + __func__, slot_width); + return -EINVAL; + } + + /* HW supports 1-32 slots configuration. Typical: 1, 2, 4, 8, 16, 32 */ + switch (slots) { + case 2: + cap_mask = 0x03; + break; + case 4: + cap_mask = 0x0F; + break; + case 8: + cap_mask = 0xFF; + break; + case 16: + cap_mask = 0xFFFF; + break; + default: + dev_err(dai->dev, "%s: invalid slots %d\n", + __func__, slots); + return -EINVAL; + } + + switch (dai->id) { + case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + tdm->nslots_per_frame = slots; + tdm->slot_width = slot_width; + /* TDM RX dais ids are even and tx are odd */ + tdm->slot_mask = (dai->id & 0x1 ? tx_mask : rx_mask) & cap_mask; + break; + default: + dev_err(dai->dev, "%s: invalid dai id 0x%x\n", + __func__, dai->id); + return -EINVAL; + } + + return rc; +} + +static int q6tdm_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) +{ + + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6afe_tdm_cfg *tdm = &dai_data->port_config[dai->id].tdm; + int rc = 0; + int i = 0; + + switch (dai->id) { + case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + if (dai->id & 0x1) { + if (!tx_slot) { + dev_err(dai->dev, "tx slot not found\n"); + return -EINVAL; + } + if (tx_num > AFE_PORT_MAX_AUDIO_CHAN_CNT) { + dev_err(dai->dev, "invalid tx num %d\n", + tx_num); + return -EINVAL; + } + + for (i = 0; i < tx_num; i++) + tdm->ch_mapping[i] = tx_slot[i]; + + for (i = tx_num; i < AFE_PORT_MAX_AUDIO_CHAN_CNT; i++) + tdm->ch_mapping[i] = Q6AFE_CMAP_INVALID; + + tdm->num_channels = tx_num; + } else { + /* rx */ + if (!rx_slot) { + dev_err(dai->dev, "rx slot not found\n"); + return -EINVAL; + } + if (rx_num > AFE_PORT_MAX_AUDIO_CHAN_CNT) { + dev_err(dai->dev, "invalid rx num %d\n", + rx_num); + return -EINVAL; + } + + for (i = 0; i < rx_num; i++) + tdm->ch_mapping[i] = rx_slot[i]; + + for (i = rx_num; i < AFE_PORT_MAX_AUDIO_CHAN_CNT; i++) + tdm->ch_mapping[i] = Q6AFE_CMAP_INVALID; + + tdm->num_channels = rx_num; + } + + break; + default: + dev_err(dai->dev, "%s: invalid dai id 0x%x\n", + __func__, dai->id); + return -EINVAL; + } + + return rc; +} + +static int q6tdm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6afe_tdm_cfg *tdm = &dai_data->port_config[dai->id].tdm; + + tdm->bit_width = params_width(params); + tdm->sample_rate = params_rate(params); + tdm->num_channels = params_channels(params); + tdm->data_align_type = dai_data->priv[dai->id].data_align; + tdm->sync_src = dai_data->priv[dai->id].sync_src; + tdm->sync_mode = dai_data->priv[dai->id].sync_mode; + + return 0; +} + +static int q6dma_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_ch_mask, + unsigned int rx_num, unsigned int *rx_ch_mask) +{ + + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6afe_cdc_dma_cfg *cfg = &dai_data->port_config[dai->id].dma_cfg; + int ch_mask; + int rc = 0; + + switch (dai->id) { + case WSA_CODEC_DMA_TX_0: + case WSA_CODEC_DMA_TX_1: + case WSA_CODEC_DMA_TX_2: + case VA_CODEC_DMA_TX_0: + case VA_CODEC_DMA_TX_1: + case VA_CODEC_DMA_TX_2: + case TX_CODEC_DMA_TX_0: + case TX_CODEC_DMA_TX_1: + case TX_CODEC_DMA_TX_2: + case TX_CODEC_DMA_TX_3: + case TX_CODEC_DMA_TX_4: + case TX_CODEC_DMA_TX_5: + if (!tx_ch_mask) { + dev_err(dai->dev, "tx slot not found\n"); + return -EINVAL; + } + + if (tx_num > AFE_PORT_MAX_AUDIO_CHAN_CNT) { + dev_err(dai->dev, "invalid tx num %d\n", + tx_num); + return -EINVAL; + } + ch_mask = *tx_ch_mask; + + break; + case WSA_CODEC_DMA_RX_0: + case WSA_CODEC_DMA_RX_1: + case RX_CODEC_DMA_RX_0: + case RX_CODEC_DMA_RX_1: + case RX_CODEC_DMA_RX_2: + case RX_CODEC_DMA_RX_3: + case RX_CODEC_DMA_RX_4: + case RX_CODEC_DMA_RX_5: + case RX_CODEC_DMA_RX_6: + case RX_CODEC_DMA_RX_7: + /* rx */ + if (!rx_ch_mask) { + dev_err(dai->dev, "rx slot not found\n"); + return -EINVAL; + } + if (rx_num > AFE_PORT_MAX_AUDIO_CHAN_CNT) { + dev_err(dai->dev, "invalid rx num %d\n", + rx_num); + return -EINVAL; + } + ch_mask = *rx_ch_mask; + + break; + default: + dev_err(dai->dev, "%s: invalid dai id 0x%x\n", + __func__, dai->id); + return -EINVAL; + } + + cfg->active_channels_mask = ch_mask; + + return rc; +} + +static int q6dma_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6afe_cdc_dma_cfg *cfg = &dai_data->port_config[dai->id].dma_cfg; + + cfg->bit_width = params_width(params); + cfg->sample_rate = params_rate(params); + cfg->num_channels = params_channels(params); + + return 0; +} +static void q6afe_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc; + + if (!dai_data->is_port_started[dai->id]) + return; + + rc = q6afe_port_stop(dai_data->port[dai->id]); + if (rc < 0) + dev_err(dai->dev, "fail to close AFE port (%d)\n", rc); + + dai_data->is_port_started[dai->id] = false; + +} + +static int q6afe_dai_prepare(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + int rc; + + if (dai_data->is_port_started[dai->id]) { + /* stop the port and restart with new port config */ + rc = q6afe_port_stop(dai_data->port[dai->id]); + if (rc < 0) { + dev_err(dai->dev, "fail to close AFE port (%d)\n", rc); + return rc; + } + } + + switch (dai->id) { + case HDMI_RX: + case DISPLAY_PORT_RX: + q6afe_hdmi_port_prepare(dai_data->port[dai->id], + &dai_data->port_config[dai->id].hdmi); + break; + case SLIMBUS_0_RX ... SLIMBUS_6_TX: + q6afe_slim_port_prepare(dai_data->port[dai->id], + &dai_data->port_config[dai->id].slim); + break; + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: + rc = q6afe_i2s_port_prepare(dai_data->port[dai->id], + &dai_data->port_config[dai->id].i2s_cfg); + if (rc < 0) { + dev_err(dai->dev, "fail to prepare AFE port %x\n", + dai->id); + return rc; + } + break; + case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + q6afe_tdm_port_prepare(dai_data->port[dai->id], + &dai_data->port_config[dai->id].tdm); + break; + case WSA_CODEC_DMA_RX_0 ... RX_CODEC_DMA_RX_7: + q6afe_cdc_dma_port_prepare(dai_data->port[dai->id], + &dai_data->port_config[dai->id].dma_cfg); + break; + default: + return -EINVAL; + } + + rc = q6afe_port_start(dai_data->port[dai->id]); + if (rc < 0) { + dev_err(dai->dev, "fail to start AFE port %x\n", dai->id); + return rc; + } + dai_data->is_port_started[dai->id] = true; + + return 0; +} + +static int q6slim_set_channel_map(struct snd_soc_dai *dai, + unsigned int tx_num, unsigned int *tx_slot, + unsigned int rx_num, unsigned int *rx_slot) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6afe_port_config *pcfg = &dai_data->port_config[dai->id]; + int i; + + if (dai->id & 0x1) { + /* TX */ + if (!tx_slot) { + pr_err("%s: tx slot not found\n", __func__); + return -EINVAL; + } + + for (i = 0; i < tx_num; i++) + pcfg->slim.ch_mapping[i] = tx_slot[i]; + + pcfg->slim.num_channels = tx_num; + + + } else { + if (!rx_slot) { + pr_err("%s: rx slot not found\n", __func__); + return -EINVAL; + } + + for (i = 0; i < rx_num; i++) + pcfg->slim.ch_mapping[i] = rx_slot[i]; + + pcfg->slim.num_channels = rx_num; + + } + + return 0; +} + +static int q6afe_mi2s_set_sysclk(struct snd_soc_dai *dai, + int clk_id, unsigned int freq, int dir) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6afe_port *port = dai_data->port[dai->id]; + + switch (clk_id) { + case LPAIF_DIG_CLK: + return q6afe_port_set_sysclk(port, clk_id, 0, 5, freq, dir); + case LPAIF_BIT_CLK: + case LPAIF_OSR_CLK: + return q6afe_port_set_sysclk(port, clk_id, + Q6AFE_LPASS_CLK_SRC_INTERNAL, + Q6AFE_LPASS_CLK_ROOT_DEFAULT, + freq, dir); + case Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT ... Q6AFE_LPASS_CLK_ID_QUI_MI2S_OSR: + case Q6AFE_LPASS_CLK_ID_MCLK_1 ... Q6AFE_LPASS_CLK_ID_INT_MCLK_1: + case Q6AFE_LPASS_CLK_ID_WSA_CORE_MCLK ... Q6AFE_LPASS_CLK_ID_VA_CORE_2X_MCLK: + return q6afe_port_set_sysclk(port, clk_id, + Q6AFE_LPASS_CLK_ATTRIBUTE_COUPLE_NO, + Q6AFE_LPASS_CLK_ROOT_DEFAULT, + freq, dir); + case Q6AFE_LPASS_CLK_ID_PRI_TDM_IBIT ... Q6AFE_LPASS_CLK_ID_QUIN_TDM_EBIT: + return q6afe_port_set_sysclk(port, clk_id, + Q6AFE_LPASS_CLK_ATTRIBUTE_INVERT_COUPLE_NO, + Q6AFE_LPASS_CLK_ROOT_DEFAULT, + freq, dir); + } + + return 0; +} + +static const struct snd_soc_dapm_route q6afe_dapm_routes[] = { + {"HDMI Playback", NULL, "HDMI_RX"}, + {"Display Port Playback", NULL, "DISPLAY_PORT_RX"}, + {"Slimbus Playback", NULL, "SLIMBUS_0_RX"}, + {"Slimbus1 Playback", NULL, "SLIMBUS_1_RX"}, + {"Slimbus2 Playback", NULL, "SLIMBUS_2_RX"}, + {"Slimbus3 Playback", NULL, "SLIMBUS_3_RX"}, + {"Slimbus4 Playback", NULL, "SLIMBUS_4_RX"}, + {"Slimbus5 Playback", NULL, "SLIMBUS_5_RX"}, + {"Slimbus6 Playback", NULL, "SLIMBUS_6_RX"}, + + {"SLIMBUS_0_TX", NULL, "Slimbus Capture"}, + {"SLIMBUS_1_TX", NULL, "Slimbus1 Capture"}, + {"SLIMBUS_2_TX", NULL, "Slimbus2 Capture"}, + {"SLIMBUS_3_TX", NULL, "Slimbus3 Capture"}, + {"SLIMBUS_4_TX", NULL, "Slimbus4 Capture"}, + {"SLIMBUS_5_TX", NULL, "Slimbus5 Capture"}, + {"SLIMBUS_6_TX", NULL, "Slimbus6 Capture"}, + + {"Primary MI2S Playback", NULL, "PRI_MI2S_RX"}, + {"Secondary MI2S Playback", NULL, "SEC_MI2S_RX"}, + {"Tertiary MI2S Playback", NULL, "TERT_MI2S_RX"}, + {"Quaternary MI2S Playback", NULL, "QUAT_MI2S_RX"}, + + {"Primary TDM0 Playback", NULL, "PRIMARY_TDM_RX_0"}, + {"Primary TDM1 Playback", NULL, "PRIMARY_TDM_RX_1"}, + {"Primary TDM2 Playback", NULL, "PRIMARY_TDM_RX_2"}, + {"Primary TDM3 Playback", NULL, "PRIMARY_TDM_RX_3"}, + {"Primary TDM4 Playback", NULL, "PRIMARY_TDM_RX_4"}, + {"Primary TDM5 Playback", NULL, "PRIMARY_TDM_RX_5"}, + {"Primary TDM6 Playback", NULL, "PRIMARY_TDM_RX_6"}, + {"Primary TDM7 Playback", NULL, "PRIMARY_TDM_RX_7"}, + + {"Secondary TDM0 Playback", NULL, "SEC_TDM_RX_0"}, + {"Secondary TDM1 Playback", NULL, "SEC_TDM_RX_1"}, + {"Secondary TDM2 Playback", NULL, "SEC_TDM_RX_2"}, + {"Secondary TDM3 Playback", NULL, "SEC_TDM_RX_3"}, + {"Secondary TDM4 Playback", NULL, "SEC_TDM_RX_4"}, + {"Secondary TDM5 Playback", NULL, "SEC_TDM_RX_5"}, + {"Secondary TDM6 Playback", NULL, "SEC_TDM_RX_6"}, + {"Secondary TDM7 Playback", NULL, "SEC_TDM_RX_7"}, + + {"Tertiary TDM0 Playback", NULL, "TERT_TDM_RX_0"}, + {"Tertiary TDM1 Playback", NULL, "TERT_TDM_RX_1"}, + {"Tertiary TDM2 Playback", NULL, "TERT_TDM_RX_2"}, + {"Tertiary TDM3 Playback", NULL, "TERT_TDM_RX_3"}, + {"Tertiary TDM4 Playback", NULL, "TERT_TDM_RX_4"}, + {"Tertiary TDM5 Playback", NULL, "TERT_TDM_RX_5"}, + {"Tertiary TDM6 Playback", NULL, "TERT_TDM_RX_6"}, + {"Tertiary TDM7 Playback", NULL, "TERT_TDM_RX_7"}, + + {"Quaternary TDM0 Playback", NULL, "QUAT_TDM_RX_0"}, + {"Quaternary TDM1 Playback", NULL, "QUAT_TDM_RX_1"}, + {"Quaternary TDM2 Playback", NULL, "QUAT_TDM_RX_2"}, + {"Quaternary TDM3 Playback", NULL, "QUAT_TDM_RX_3"}, + {"Quaternary TDM4 Playback", NULL, "QUAT_TDM_RX_4"}, + {"Quaternary TDM5 Playback", NULL, "QUAT_TDM_RX_5"}, + {"Quaternary TDM6 Playback", NULL, "QUAT_TDM_RX_6"}, + {"Quaternary TDM7 Playback", NULL, "QUAT_TDM_RX_7"}, + + {"Quinary TDM0 Playback", NULL, "QUIN_TDM_RX_0"}, + {"Quinary TDM1 Playback", NULL, "QUIN_TDM_RX_1"}, + {"Quinary TDM2 Playback", NULL, "QUIN_TDM_RX_2"}, + {"Quinary TDM3 Playback", NULL, "QUIN_TDM_RX_3"}, + {"Quinary TDM4 Playback", NULL, "QUIN_TDM_RX_4"}, + {"Quinary TDM5 Playback", NULL, "QUIN_TDM_RX_5"}, + {"Quinary TDM6 Playback", NULL, "QUIN_TDM_RX_6"}, + {"Quinary TDM7 Playback", NULL, "QUIN_TDM_RX_7"}, + + {"PRIMARY_TDM_TX_0", NULL, "Primary TDM0 Capture"}, + {"PRIMARY_TDM_TX_1", NULL, "Primary TDM1 Capture"}, + {"PRIMARY_TDM_TX_2", NULL, "Primary TDM2 Capture"}, + {"PRIMARY_TDM_TX_3", NULL, "Primary TDM3 Capture"}, + {"PRIMARY_TDM_TX_4", NULL, "Primary TDM4 Capture"}, + {"PRIMARY_TDM_TX_5", NULL, "Primary TDM5 Capture"}, + {"PRIMARY_TDM_TX_6", NULL, "Primary TDM6 Capture"}, + {"PRIMARY_TDM_TX_7", NULL, "Primary TDM7 Capture"}, + + {"SEC_TDM_TX_0", NULL, "Secondary TDM0 Capture"}, + {"SEC_TDM_TX_1", NULL, "Secondary TDM1 Capture"}, + {"SEC_TDM_TX_2", NULL, "Secondary TDM2 Capture"}, + {"SEC_TDM_TX_3", NULL, "Secondary TDM3 Capture"}, + {"SEC_TDM_TX_4", NULL, "Secondary TDM4 Capture"}, + {"SEC_TDM_TX_5", NULL, "Secondary TDM5 Capture"}, + {"SEC_TDM_TX_6", NULL, "Secondary TDM6 Capture"}, + {"SEC_TDM_TX_7", NULL, "Secondary TDM7 Capture"}, + + {"TERT_TDM_TX_0", NULL, "Tertiary TDM0 Capture"}, + {"TERT_TDM_TX_1", NULL, "Tertiary TDM1 Capture"}, + {"TERT_TDM_TX_2", NULL, "Tertiary TDM2 Capture"}, + {"TERT_TDM_TX_3", NULL, "Tertiary TDM3 Capture"}, + {"TERT_TDM_TX_4", NULL, "Tertiary TDM4 Capture"}, + {"TERT_TDM_TX_5", NULL, "Tertiary TDM5 Capture"}, + {"TERT_TDM_TX_6", NULL, "Tertiary TDM6 Capture"}, + {"TERT_TDM_TX_7", NULL, "Tertiary TDM7 Capture"}, + + {"QUAT_TDM_TX_0", NULL, "Quaternary TDM0 Capture"}, + {"QUAT_TDM_TX_1", NULL, "Quaternary TDM1 Capture"}, + {"QUAT_TDM_TX_2", NULL, "Quaternary TDM2 Capture"}, + {"QUAT_TDM_TX_3", NULL, "Quaternary TDM3 Capture"}, + {"QUAT_TDM_TX_4", NULL, "Quaternary TDM4 Capture"}, + {"QUAT_TDM_TX_5", NULL, "Quaternary TDM5 Capture"}, + {"QUAT_TDM_TX_6", NULL, "Quaternary TDM6 Capture"}, + {"QUAT_TDM_TX_7", NULL, "Quaternary TDM7 Capture"}, + + {"QUIN_TDM_TX_0", NULL, "Quinary TDM0 Capture"}, + {"QUIN_TDM_TX_1", NULL, "Quinary TDM1 Capture"}, + {"QUIN_TDM_TX_2", NULL, "Quinary TDM2 Capture"}, + {"QUIN_TDM_TX_3", NULL, "Quinary TDM3 Capture"}, + {"QUIN_TDM_TX_4", NULL, "Quinary TDM4 Capture"}, + {"QUIN_TDM_TX_5", NULL, "Quinary TDM5 Capture"}, + {"QUIN_TDM_TX_6", NULL, "Quinary TDM6 Capture"}, + {"QUIN_TDM_TX_7", NULL, "Quinary TDM7 Capture"}, + + {"TERT_MI2S_TX", NULL, "Tertiary MI2S Capture"}, + {"PRI_MI2S_TX", NULL, "Primary MI2S Capture"}, + {"SEC_MI2S_TX", NULL, "Secondary MI2S Capture"}, + {"QUAT_MI2S_TX", NULL, "Quaternary MI2S Capture"}, + + {"WSA_CODEC_DMA_RX_0 Playback", NULL, "WSA_CODEC_DMA_RX_0"}, + {"WSA_CODEC_DMA_TX_0", NULL, "WSA_CODEC_DMA_TX_0 Capture"}, + {"WSA_CODEC_DMA_RX_1 Playback", NULL, "WSA_CODEC_DMA_RX_1"}, + {"WSA_CODEC_DMA_TX_1", NULL, "WSA_CODEC_DMA_TX_1 Capture"}, + {"WSA_CODEC_DMA_TX_2", NULL, "WSA_CODEC_DMA_TX_2 Capture"}, + {"VA_CODEC_DMA_TX_0", NULL, "VA_CODEC_DMA_TX_0 Capture"}, + {"VA_CODEC_DMA_TX_1", NULL, "VA_CODEC_DMA_TX_1 Capture"}, + {"VA_CODEC_DMA_TX_2", NULL, "VA_CODEC_DMA_TX_2 Capture"}, + {"RX_CODEC_DMA_RX_0 Playback", NULL, "RX_CODEC_DMA_RX_0"}, + {"TX_CODEC_DMA_TX_0", NULL, "TX_CODEC_DMA_TX_0 Capture"}, + {"RX_CODEC_DMA_RX_1 Playback", NULL, "RX_CODEC_DMA_RX_1"}, + {"TX_CODEC_DMA_TX_1", NULL, "TX_CODEC_DMA_TX_1 Capture"}, + {"RX_CODEC_DMA_RX_2 Playback", NULL, "RX_CODEC_DMA_RX_2"}, + {"TX_CODEC_DMA_TX_2", NULL, "TX_CODEC_DMA_TX_2 Capture"}, + {"RX_CODEC_DMA_RX_3 Playback", NULL, "RX_CODEC_DMA_RX_3"}, + {"TX_CODEC_DMA_TX_3", NULL, "TX_CODEC_DMA_TX_3 Capture"}, + {"RX_CODEC_DMA_RX_4 Playback", NULL, "RX_CODEC_DMA_RX_4"}, + {"TX_CODEC_DMA_TX_4", NULL, "TX_CODEC_DMA_TX_4 Capture"}, + {"RX_CODEC_DMA_RX_5 Playback", NULL, "RX_CODEC_DMA_RX_5"}, + {"TX_CODEC_DMA_TX_5", NULL, "TX_CODEC_DMA_TX_5 Capture"}, + {"RX_CODEC_DMA_RX_6 Playback", NULL, "RX_CODEC_DMA_RX_6"}, + {"RX_CODEC_DMA_RX_7 Playback", NULL, "RX_CODEC_DMA_RX_7"}, +}; + +static const struct snd_soc_dai_ops q6hdmi_ops = { + .prepare = q6afe_dai_prepare, + .hw_params = q6hdmi_hw_params, + .shutdown = q6afe_dai_shutdown, +}; + +static const struct snd_soc_dai_ops q6i2s_ops = { + .prepare = q6afe_dai_prepare, + .hw_params = q6i2s_hw_params, + .set_fmt = q6i2s_set_fmt, + .shutdown = q6afe_dai_shutdown, + .set_sysclk = q6afe_mi2s_set_sysclk, +}; + +static const struct snd_soc_dai_ops q6slim_ops = { + .prepare = q6afe_dai_prepare, + .hw_params = q6slim_hw_params, + .shutdown = q6afe_dai_shutdown, + .set_channel_map = q6slim_set_channel_map, +}; + +static const struct snd_soc_dai_ops q6tdm_ops = { + .prepare = q6afe_dai_prepare, + .shutdown = q6afe_dai_shutdown, + .set_sysclk = q6afe_mi2s_set_sysclk, + .set_tdm_slot = q6tdm_set_tdm_slot, + .set_channel_map = q6tdm_set_channel_map, + .hw_params = q6tdm_hw_params, +}; + +static const struct snd_soc_dai_ops q6dma_ops = { + .prepare = q6afe_dai_prepare, + .shutdown = q6afe_dai_shutdown, + .set_sysclk = q6afe_mi2s_set_sysclk, + .set_channel_map = q6dma_set_channel_map, + .hw_params = q6dma_hw_params, +}; + +static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + struct q6afe_port *port; + + port = q6afe_port_get_from_id(dai->dev, dai->id); + if (IS_ERR(port)) { + dev_err(dai->dev, "Unable to get afe port\n"); + return -EINVAL; + } + dai_data->port[dai->id] = port; + + return 0; +} + +static int msm_dai_q6_dai_remove(struct snd_soc_dai *dai) +{ + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); + + q6afe_port_put(dai_data->port[dai->id]); + dai_data->port[dai->id] = NULL; + + return 0; +} + +static struct snd_soc_dai_driver q6afe_dais[] = { + { + .playback = { + .stream_name = "HDMI Playback", + .rates = SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 2, + .channels_max = 8, + .rate_max = 192000, + .rate_min = 48000, + }, + .ops = &q6hdmi_ops, + .id = HDMI_RX, + .name = "HDMI", + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .name = "SLIMBUS_0_RX", + .ops = &q6slim_ops, + .id = SLIMBUS_0_RX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + .playback = { + .stream_name = "Slimbus Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .name = "SLIMBUS_0_TX", + .ops = &q6slim_ops, + .id = SLIMBUS_0_TX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + .capture = { + .stream_name = "Slimbus Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Slimbus1 Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "SLIMBUS_1_RX", + .ops = &q6slim_ops, + .id = SLIMBUS_1_RX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .name = "SLIMBUS_1_TX", + .ops = &q6slim_ops, + .id = SLIMBUS_1_TX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + .capture = { + .stream_name = "Slimbus1 Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Slimbus2 Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "SLIMBUS_2_RX", + .ops = &q6slim_ops, + .id = SLIMBUS_2_RX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + + }, { + .name = "SLIMBUS_2_TX", + .ops = &q6slim_ops, + .id = SLIMBUS_2_TX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + .capture = { + .stream_name = "Slimbus2 Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Slimbus3 Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "SLIMBUS_3_RX", + .ops = &q6slim_ops, + .id = SLIMBUS_3_RX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + + }, { + .name = "SLIMBUS_3_TX", + .ops = &q6slim_ops, + .id = SLIMBUS_3_TX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + .capture = { + .stream_name = "Slimbus3 Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Slimbus4 Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "SLIMBUS_4_RX", + .ops = &q6slim_ops, + .id = SLIMBUS_4_RX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + + }, { + .name = "SLIMBUS_4_TX", + .ops = &q6slim_ops, + .id = SLIMBUS_4_TX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + .capture = { + .stream_name = "Slimbus4 Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Slimbus5 Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .name = "SLIMBUS_5_RX", + .ops = &q6slim_ops, + .id = SLIMBUS_5_RX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + + }, { + .name = "SLIMBUS_5_TX", + .ops = &q6slim_ops, + .id = SLIMBUS_5_TX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + .capture = { + .stream_name = "Slimbus5 Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Slimbus6 Playback", + .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000 | + SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 2, + .rate_min = 8000, + .rate_max = 192000, + }, + .ops = &q6slim_ops, + .name = "SLIMBUS_6_RX", + .id = SLIMBUS_6_RX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + + }, { + .name = "SLIMBUS_6_TX", + .ops = &q6slim_ops, + .id = SLIMBUS_6_TX, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + .capture = { + .stream_name = "Slimbus6 Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 192000, + }, + }, { + .playback = { + .stream_name = "Primary MI2S Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .id = PRIMARY_MI2S_RX, + .name = "PRI_MI2S_RX", + .ops = &q6i2s_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .capture = { + .stream_name = "Primary MI2S Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .id = PRIMARY_MI2S_TX, + .name = "PRI_MI2S_TX", + .ops = &q6i2s_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .playback = { + .stream_name = "Secondary MI2S Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .name = "SEC_MI2S_RX", + .id = SECONDARY_MI2S_RX, + .ops = &q6i2s_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .capture = { + .stream_name = "Secondary MI2S Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .id = SECONDARY_MI2S_TX, + .name = "SEC_MI2S_TX", + .ops = &q6i2s_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .playback = { + .stream_name = "Tertiary MI2S Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .name = "TERT_MI2S_RX", + .id = TERTIARY_MI2S_RX, + .ops = &q6i2s_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .capture = { + .stream_name = "Tertiary MI2S Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .id = TERTIARY_MI2S_TX, + .name = "TERT_MI2S_TX", + .ops = &q6i2s_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .playback = { + .stream_name = "Quaternary MI2S Playback", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .name = "QUAT_MI2S_RX", + .id = QUATERNARY_MI2S_RX, + .ops = &q6i2s_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, { + .capture = { + .stream_name = "Quaternary MI2S Capture", + .rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_8000 | + SNDRV_PCM_RATE_16000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 1, + .channels_max = 8, + .rate_min = 8000, + .rate_max = 48000, + }, + .id = QUATERNARY_MI2S_TX, + .name = "QUAT_MI2S_TX", + .ops = &q6i2s_ops, + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, + Q6AFE_TDM_PB_DAI("Primary", 0, PRIMARY_TDM_RX_0), + Q6AFE_TDM_PB_DAI("Primary", 1, PRIMARY_TDM_RX_1), + Q6AFE_TDM_PB_DAI("Primary", 2, PRIMARY_TDM_RX_2), + Q6AFE_TDM_PB_DAI("Primary", 3, PRIMARY_TDM_RX_3), + Q6AFE_TDM_PB_DAI("Primary", 4, PRIMARY_TDM_RX_4), + Q6AFE_TDM_PB_DAI("Primary", 5, PRIMARY_TDM_RX_5), + Q6AFE_TDM_PB_DAI("Primary", 6, PRIMARY_TDM_RX_6), + Q6AFE_TDM_PB_DAI("Primary", 7, PRIMARY_TDM_RX_7), + Q6AFE_TDM_CAP_DAI("Primary", 0, PRIMARY_TDM_TX_0), + Q6AFE_TDM_CAP_DAI("Primary", 1, PRIMARY_TDM_TX_1), + Q6AFE_TDM_CAP_DAI("Primary", 2, PRIMARY_TDM_TX_2), + Q6AFE_TDM_CAP_DAI("Primary", 3, PRIMARY_TDM_TX_3), + Q6AFE_TDM_CAP_DAI("Primary", 4, PRIMARY_TDM_TX_4), + Q6AFE_TDM_CAP_DAI("Primary", 5, PRIMARY_TDM_TX_5), + Q6AFE_TDM_CAP_DAI("Primary", 6, PRIMARY_TDM_TX_6), + Q6AFE_TDM_CAP_DAI("Primary", 7, PRIMARY_TDM_TX_7), + Q6AFE_TDM_PB_DAI("Secondary", 0, SECONDARY_TDM_RX_0), + Q6AFE_TDM_PB_DAI("Secondary", 1, SECONDARY_TDM_RX_1), + Q6AFE_TDM_PB_DAI("Secondary", 2, SECONDARY_TDM_RX_2), + Q6AFE_TDM_PB_DAI("Secondary", 3, SECONDARY_TDM_RX_3), + Q6AFE_TDM_PB_DAI("Secondary", 4, SECONDARY_TDM_RX_4), + Q6AFE_TDM_PB_DAI("Secondary", 5, SECONDARY_TDM_RX_5), + Q6AFE_TDM_PB_DAI("Secondary", 6, SECONDARY_TDM_RX_6), + Q6AFE_TDM_PB_DAI("Secondary", 7, SECONDARY_TDM_RX_7), + Q6AFE_TDM_CAP_DAI("Secondary", 0, SECONDARY_TDM_TX_0), + Q6AFE_TDM_CAP_DAI("Secondary", 1, SECONDARY_TDM_TX_1), + Q6AFE_TDM_CAP_DAI("Secondary", 2, SECONDARY_TDM_TX_2), + Q6AFE_TDM_CAP_DAI("Secondary", 3, SECONDARY_TDM_TX_3), + Q6AFE_TDM_CAP_DAI("Secondary", 4, SECONDARY_TDM_TX_4), + Q6AFE_TDM_CAP_DAI("Secondary", 5, SECONDARY_TDM_TX_5), + Q6AFE_TDM_CAP_DAI("Secondary", 6, SECONDARY_TDM_TX_6), + Q6AFE_TDM_CAP_DAI("Secondary", 7, SECONDARY_TDM_TX_7), + Q6AFE_TDM_PB_DAI("Tertiary", 0, TERTIARY_TDM_RX_0), + Q6AFE_TDM_PB_DAI("Tertiary", 1, TERTIARY_TDM_RX_1), + Q6AFE_TDM_PB_DAI("Tertiary", 2, TERTIARY_TDM_RX_2), + Q6AFE_TDM_PB_DAI("Tertiary", 3, TERTIARY_TDM_RX_3), + Q6AFE_TDM_PB_DAI("Tertiary", 4, TERTIARY_TDM_RX_4), + Q6AFE_TDM_PB_DAI("Tertiary", 5, TERTIARY_TDM_RX_5), + Q6AFE_TDM_PB_DAI("Tertiary", 6, TERTIARY_TDM_RX_6), + Q6AFE_TDM_PB_DAI("Tertiary", 7, TERTIARY_TDM_RX_7), + Q6AFE_TDM_CAP_DAI("Tertiary", 0, TERTIARY_TDM_TX_0), + Q6AFE_TDM_CAP_DAI("Tertiary", 1, TERTIARY_TDM_TX_1), + Q6AFE_TDM_CAP_DAI("Tertiary", 2, TERTIARY_TDM_TX_2), + Q6AFE_TDM_CAP_DAI("Tertiary", 3, TERTIARY_TDM_TX_3), + Q6AFE_TDM_CAP_DAI("Tertiary", 4, TERTIARY_TDM_TX_4), + Q6AFE_TDM_CAP_DAI("Tertiary", 5, TERTIARY_TDM_TX_5), + Q6AFE_TDM_CAP_DAI("Tertiary", 6, TERTIARY_TDM_TX_6), + Q6AFE_TDM_CAP_DAI("Tertiary", 7, TERTIARY_TDM_TX_7), + Q6AFE_TDM_PB_DAI("Quaternary", 0, QUATERNARY_TDM_RX_0), + Q6AFE_TDM_PB_DAI("Quaternary", 1, QUATERNARY_TDM_RX_1), + Q6AFE_TDM_PB_DAI("Quaternary", 2, QUATERNARY_TDM_RX_2), + Q6AFE_TDM_PB_DAI("Quaternary", 3, QUATERNARY_TDM_RX_3), + Q6AFE_TDM_PB_DAI("Quaternary", 4, QUATERNARY_TDM_RX_4), + Q6AFE_TDM_PB_DAI("Quaternary", 5, QUATERNARY_TDM_RX_5), + Q6AFE_TDM_PB_DAI("Quaternary", 6, QUATERNARY_TDM_RX_6), + Q6AFE_TDM_PB_DAI("Quaternary", 7, QUATERNARY_TDM_RX_7), + Q6AFE_TDM_CAP_DAI("Quaternary", 0, QUATERNARY_TDM_TX_0), + Q6AFE_TDM_CAP_DAI("Quaternary", 1, QUATERNARY_TDM_TX_1), + Q6AFE_TDM_CAP_DAI("Quaternary", 2, QUATERNARY_TDM_TX_2), + Q6AFE_TDM_CAP_DAI("Quaternary", 3, QUATERNARY_TDM_TX_3), + Q6AFE_TDM_CAP_DAI("Quaternary", 4, QUATERNARY_TDM_TX_4), + Q6AFE_TDM_CAP_DAI("Quaternary", 5, QUATERNARY_TDM_TX_5), + Q6AFE_TDM_CAP_DAI("Quaternary", 6, QUATERNARY_TDM_TX_6), + Q6AFE_TDM_CAP_DAI("Quaternary", 7, QUATERNARY_TDM_TX_7), + Q6AFE_TDM_PB_DAI("Quinary", 0, QUINARY_TDM_RX_0), + Q6AFE_TDM_PB_DAI("Quinary", 1, QUINARY_TDM_RX_1), + Q6AFE_TDM_PB_DAI("Quinary", 2, QUINARY_TDM_RX_2), + Q6AFE_TDM_PB_DAI("Quinary", 3, QUINARY_TDM_RX_3), + Q6AFE_TDM_PB_DAI("Quinary", 4, QUINARY_TDM_RX_4), + Q6AFE_TDM_PB_DAI("Quinary", 5, QUINARY_TDM_RX_5), + Q6AFE_TDM_PB_DAI("Quinary", 6, QUINARY_TDM_RX_6), + Q6AFE_TDM_PB_DAI("Quinary", 7, QUINARY_TDM_RX_7), + Q6AFE_TDM_CAP_DAI("Quinary", 0, QUINARY_TDM_TX_0), + Q6AFE_TDM_CAP_DAI("Quinary", 1, QUINARY_TDM_TX_1), + Q6AFE_TDM_CAP_DAI("Quinary", 2, QUINARY_TDM_TX_2), + Q6AFE_TDM_CAP_DAI("Quinary", 3, QUINARY_TDM_TX_3), + Q6AFE_TDM_CAP_DAI("Quinary", 4, QUINARY_TDM_TX_4), + Q6AFE_TDM_CAP_DAI("Quinary", 5, QUINARY_TDM_TX_5), + Q6AFE_TDM_CAP_DAI("Quinary", 6, QUINARY_TDM_TX_6), + Q6AFE_TDM_CAP_DAI("Quinary", 7, QUINARY_TDM_TX_7), + { + .playback = { + .stream_name = "Display Port Playback", + .rates = SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_96000 | + SNDRV_PCM_RATE_192000, + .formats = SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE, + .channels_min = 2, + .channels_max = 8, + .rate_max = 192000, + .rate_min = 48000, + }, + .ops = &q6hdmi_ops, + .id = DISPLAY_PORT_RX, + .name = "DISPLAY_PORT", + .probe = msm_dai_q6_dai_probe, + .remove = msm_dai_q6_dai_remove, + }, + Q6AFE_CDC_DMA_RX_DAI(WSA_CODEC_DMA_RX_0), + Q6AFE_CDC_DMA_TX_DAI(WSA_CODEC_DMA_TX_0), + Q6AFE_CDC_DMA_RX_DAI(WSA_CODEC_DMA_RX_1), + Q6AFE_CDC_DMA_TX_DAI(WSA_CODEC_DMA_TX_1), + Q6AFE_CDC_DMA_TX_DAI(WSA_CODEC_DMA_TX_2), + Q6AFE_CDC_DMA_TX_DAI(VA_CODEC_DMA_TX_0), + Q6AFE_CDC_DMA_TX_DAI(VA_CODEC_DMA_TX_1), + Q6AFE_CDC_DMA_TX_DAI(VA_CODEC_DMA_TX_2), + Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_0), + Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_0), + Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_1), + Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_1), + Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_2), + Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_2), + Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_3), + Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_3), + Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_4), + Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_4), + Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_5), + Q6AFE_CDC_DMA_TX_DAI(TX_CODEC_DMA_TX_5), + Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_6), + Q6AFE_CDC_DMA_RX_DAI(RX_CODEC_DMA_RX_7), +}; + +static int q6afe_of_xlate_dai_name(struct snd_soc_component *component, + struct of_phandle_args *args, + const char **dai_name) +{ + int id = args->args[0]; + int ret = -EINVAL; + int i; + + for (i = 0; i < ARRAY_SIZE(q6afe_dais); i++) { + if (q6afe_dais[i].id == id) { + *dai_name = q6afe_dais[i].name; + ret = 0; + break; + } + } + + return ret; +} + +static const struct snd_soc_dapm_widget q6afe_dai_widgets[] = { + SND_SOC_DAPM_AIF_IN("HDMI_RX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_0_RX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_1_RX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_2_RX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_3_RX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_4_RX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_5_RX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SLIMBUS_6_RX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_0_TX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_1_TX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_2_TX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_3_TX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_4_TX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_5_TX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SLIMBUS_6_TX", NULL, 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("QUAT_MI2S_RX", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUAT_MI2S_TX", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("TERT_MI2S_RX", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("TERT_MI2S_TX", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SEC_MI2S_RX", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_MI2S_TX", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SEC_MI2S_RX_SD1", + "Secondary MI2S Playback SD1", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("PRI_MI2S_RX", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("PRI_MI2S_TX", NULL, + 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_0", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_1", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_2", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_3", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_4", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_5", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_6", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("PRIMARY_TDM_RX_7", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_0", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_1", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_2", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_3", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_4", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_5", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_6", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("PRIMARY_TDM_TX_7", NULL, + 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_0", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_1", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_2", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_3", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_4", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_5", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_6", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("SEC_TDM_RX_7", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_0", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_1", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_2", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_3", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_4", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_5", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_6", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("SEC_TDM_TX_7", NULL, + 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_0", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_1", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_2", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_3", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_4", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_5", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_6", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("TERT_TDM_RX_7", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_0", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_1", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_2", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_3", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_4", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_5", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_6", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("TERT_TDM_TX_7", NULL, + 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_0", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_1", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_2", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_3", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_4", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_5", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_6", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("QUAT_TDM_RX_7", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_0", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_1", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_2", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_3", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_4", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_5", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_6", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUAT_TDM_TX_7", NULL, + 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_0", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_1", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_2", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_3", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_4", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_5", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_6", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("QUIN_TDM_RX_7", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_0", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_1", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_2", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_3", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_4", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_5", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_6", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("QUIN_TDM_TX_7", NULL, + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("DISPLAY_PORT_RX", "NULL", 0, SND_SOC_NOPM, 0, 0), + + SND_SOC_DAPM_AIF_IN("WSA_CODEC_DMA_RX_0", "NULL", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("WSA_CODEC_DMA_TX_0", "NULL", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("WSA_CODEC_DMA_RX_1", "NULL", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("WSA_CODEC_DMA_TX_1", "NULL", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("WSA_CODEC_DMA_TX_2", "NULL", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("VA_CODEC_DMA_TX_0", "NULL", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("VA_CODEC_DMA_TX_1", "NULL", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("VA_CODEC_DMA_TX_2", "NULL", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX_CODEC_DMA_RX_0", "NULL", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("TX_CODEC_DMA_TX_0", "NULL", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX_CODEC_DMA_RX_1", "NULL", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("TX_CODEC_DMA_TX_1", "NULL", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX_CODEC_DMA_RX_2", "NULL", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("TX_CODEC_DMA_TX_2", "NULL", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX_CODEC_DMA_RX_3", "NULL", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("TX_CODEC_DMA_TX_3", "NULL", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX_CODEC_DMA_RX_4", "NULL", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("TX_CODEC_DMA_TX_4", "NULL", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX_CODEC_DMA_RX_5", "NULL", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("TX_CODEC_DMA_TX_5", "NULL", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX_CODEC_DMA_RX_6", "NULL", + 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("RX_CODEC_DMA_RX_7", "NULL", + 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_component_driver q6afe_dai_component = { + .name = "q6afe-dai-component", + .dapm_widgets = q6afe_dai_widgets, + .num_dapm_widgets = ARRAY_SIZE(q6afe_dai_widgets), + .dapm_routes = q6afe_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(q6afe_dapm_routes), + .of_xlate_dai_name = q6afe_of_xlate_dai_name, + +}; + +static void of_q6afe_parse_dai_data(struct device *dev, + struct q6afe_dai_data *data) +{ + struct device_node *node; + int ret; + + for_each_child_of_node(dev->of_node, node) { + unsigned int lines[Q6AFE_MAX_MI2S_LINES]; + struct q6afe_dai_priv_data *priv; + int id, i, num_lines; + + ret = of_property_read_u32(node, "reg", &id); + if (ret || id < 0 || id >= AFE_PORT_MAX) { + dev_err(dev, "valid dai id not found:%d\n", ret); + continue; + } + + switch (id) { + /* MI2S specific properties */ + case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX: + priv = &data->priv[id]; + ret = of_property_read_variable_u32_array(node, + "qcom,sd-lines", + lines, 0, + Q6AFE_MAX_MI2S_LINES); + if (ret < 0) + num_lines = 0; + else + num_lines = ret; + + priv->sd_line_mask = 0; + + for (i = 0; i < num_lines; i++) + priv->sd_line_mask |= BIT(lines[i]); + + break; + case PRIMARY_TDM_RX_0 ... QUINARY_TDM_TX_7: + priv = &data->priv[id]; + ret = of_property_read_u32(node, "qcom,tdm-sync-mode", + &priv->sync_mode); + if (ret) { + dev_err(dev, "No Sync mode from DT\n"); + break; + } + ret = of_property_read_u32(node, "qcom,tdm-sync-src", + &priv->sync_src); + if (ret) { + dev_err(dev, "No Sync Src from DT\n"); + break; + } + ret = of_property_read_u32(node, "qcom,tdm-data-out", + &priv->data_out_enable); + if (ret) { + dev_err(dev, "No Data out enable from DT\n"); + break; + } + ret = of_property_read_u32(node, "qcom,tdm-invert-sync", + &priv->invert_sync); + if (ret) { + dev_err(dev, "No Invert sync from DT\n"); + break; + } + ret = of_property_read_u32(node, "qcom,tdm-data-delay", + &priv->data_delay); + if (ret) { + dev_err(dev, "No Data Delay from DT\n"); + break; + } + ret = of_property_read_u32(node, "qcom,tdm-data-align", + &priv->data_align); + if (ret) { + dev_err(dev, "No Data align from DT\n"); + break; + } + break; + default: + break; + } + } +} + +static int q6afe_dai_dev_probe(struct platform_device *pdev) +{ + struct q6afe_dai_data *dai_data; + struct device *dev = &pdev->dev; + + dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL); + if (!dai_data) + return -ENOMEM; + + dev_set_drvdata(dev, dai_data); + + of_q6afe_parse_dai_data(dev, dai_data); + + return devm_snd_soc_register_component(dev, &q6afe_dai_component, + q6afe_dais, ARRAY_SIZE(q6afe_dais)); +} + +#ifdef CONFIG_OF +static const struct of_device_id q6afe_dai_device_id[] = { + { .compatible = "qcom,q6afe-dais" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6afe_dai_device_id); +#endif + +static struct platform_driver q6afe_dai_platform_driver = { + .driver = { + .name = "q6afe-dai", + .of_match_table = of_match_ptr(q6afe_dai_device_id), + }, + .probe = q6afe_dai_dev_probe, +}; +module_platform_driver(q6afe_dai_platform_driver); + +MODULE_DESCRIPTION("Q6 Audio Fronend dai driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c new file mode 100644 index 000000000..0ca1e4aae --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6afe.c @@ -0,0 +1,1774 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2018, Linaro Limited + +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/uaccess.h> +#include <linux/wait.h> +#include <linux/jiffies.h> +#include <linux/sched.h> +#include <linux/module.h> +#include <linux/kref.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/spinlock.h> +#include <linux/delay.h> +#include <linux/soc/qcom/apr.h> +#include <sound/soc.h> +#include <sound/soc-dai.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include "q6dsp-errno.h" +#include "q6core.h" +#include "q6afe.h" + +/* AFE CMDs */ +#define AFE_PORT_CMD_DEVICE_START 0x000100E5 +#define AFE_PORT_CMD_DEVICE_STOP 0x000100E6 +#define AFE_PORT_CMD_SET_PARAM_V2 0x000100EF +#define AFE_SVC_CMD_SET_PARAM 0x000100f3 +#define AFE_PORT_CMDRSP_GET_PARAM_V2 0x00010106 +#define AFE_PARAM_ID_HDMI_CONFIG 0x00010210 +#define AFE_MODULE_AUDIO_DEV_INTERFACE 0x0001020C +#define AFE_MODULE_TDM 0x0001028A + +#define AFE_PARAM_ID_CDC_SLIMBUS_SLAVE_CFG 0x00010235 + +#define AFE_PARAM_ID_LPAIF_CLK_CONFIG 0x00010238 +#define AFE_PARAM_ID_INT_DIGITAL_CDC_CLK_CONFIG 0x00010239 + +#define AFE_PARAM_ID_SLIMBUS_CONFIG 0x00010212 +#define AFE_PARAM_ID_I2S_CONFIG 0x0001020D +#define AFE_PARAM_ID_TDM_CONFIG 0x0001029D +#define AFE_PARAM_ID_PORT_SLOT_MAPPING_CONFIG 0x00010297 +#define AFE_PARAM_ID_CODEC_DMA_CONFIG 0x000102B8 +#define AFE_CMD_REMOTE_LPASS_CORE_HW_VOTE_REQUEST 0x000100f4 +#define AFE_CMD_RSP_REMOTE_LPASS_CORE_HW_VOTE_REQUEST 0x000100f5 +#define AFE_CMD_REMOTE_LPASS_CORE_HW_DEVOTE_REQUEST 0x000100f6 + +/* I2S config specific */ +#define AFE_API_VERSION_I2S_CONFIG 0x1 +#define AFE_PORT_I2S_SD0 0x1 +#define AFE_PORT_I2S_SD1 0x2 +#define AFE_PORT_I2S_SD2 0x3 +#define AFE_PORT_I2S_SD3 0x4 +#define AFE_PORT_I2S_SD0_MASK BIT(0x0) +#define AFE_PORT_I2S_SD1_MASK BIT(0x1) +#define AFE_PORT_I2S_SD2_MASK BIT(0x2) +#define AFE_PORT_I2S_SD3_MASK BIT(0x3) +#define AFE_PORT_I2S_SD0_1_MASK GENMASK(1, 0) +#define AFE_PORT_I2S_SD2_3_MASK GENMASK(3, 2) +#define AFE_PORT_I2S_SD0_1_2_MASK GENMASK(2, 0) +#define AFE_PORT_I2S_SD0_1_2_3_MASK GENMASK(3, 0) +#define AFE_PORT_I2S_QUAD01 0x5 +#define AFE_PORT_I2S_QUAD23 0x6 +#define AFE_PORT_I2S_6CHS 0x7 +#define AFE_PORT_I2S_8CHS 0x8 +#define AFE_PORT_I2S_MONO 0x0 +#define AFE_PORT_I2S_STEREO 0x1 +#define AFE_PORT_CONFIG_I2S_WS_SRC_EXTERNAL 0x0 +#define AFE_PORT_CONFIG_I2S_WS_SRC_INTERNAL 0x1 +#define AFE_LINEAR_PCM_DATA 0x0 + + +/* Port IDs */ +#define AFE_API_VERSION_HDMI_CONFIG 0x1 +#define AFE_PORT_ID_MULTICHAN_HDMI_RX 0x100E +#define AFE_PORT_ID_HDMI_OVER_DP_RX 0x6020 + +#define AFE_API_VERSION_SLIMBUS_CONFIG 0x1 +/* Clock set API version */ +#define AFE_API_VERSION_CLOCK_SET 1 +#define Q6AFE_LPASS_CLK_CONFIG_API_VERSION 0x1 +#define AFE_MODULE_CLOCK_SET 0x0001028F +#define AFE_PARAM_ID_CLOCK_SET 0x00010290 + +/* SLIMbus Rx port on channel 0. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX 0x4000 +/* SLIMbus Tx port on channel 0. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_TX 0x4001 +/* SLIMbus Rx port on channel 1. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX 0x4002 +/* SLIMbus Tx port on channel 1. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_TX 0x4003 +/* SLIMbus Rx port on channel 2. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_RX 0x4004 +/* SLIMbus Tx port on channel 2. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_TX 0x4005 +/* SLIMbus Rx port on channel 3. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_RX 0x4006 +/* SLIMbus Tx port on channel 3. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_TX 0x4007 +/* SLIMbus Rx port on channel 4. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_RX 0x4008 +/* SLIMbus Tx port on channel 4. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_TX 0x4009 +/* SLIMbus Rx port on channel 5. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_RX 0x400a +/* SLIMbus Tx port on channel 5. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_TX 0x400b +/* SLIMbus Rx port on channel 6. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_RX 0x400c +/* SLIMbus Tx port on channel 6. */ +#define AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_TX 0x400d +#define AFE_PORT_ID_PRIMARY_MI2S_RX 0x1000 +#define AFE_PORT_ID_PRIMARY_MI2S_TX 0x1001 +#define AFE_PORT_ID_SECONDARY_MI2S_RX 0x1002 +#define AFE_PORT_ID_SECONDARY_MI2S_TX 0x1003 +#define AFE_PORT_ID_TERTIARY_MI2S_RX 0x1004 +#define AFE_PORT_ID_TERTIARY_MI2S_TX 0x1005 +#define AFE_PORT_ID_QUATERNARY_MI2S_RX 0x1006 +#define AFE_PORT_ID_QUATERNARY_MI2S_TX 0x1007 + +/* Start of the range of port IDs for TDM devices. */ +#define AFE_PORT_ID_TDM_PORT_RANGE_START 0x9000 + +/* End of the range of port IDs for TDM devices. */ +#define AFE_PORT_ID_TDM_PORT_RANGE_END \ + (AFE_PORT_ID_TDM_PORT_RANGE_START+0x50-1) + +/* Size of the range of port IDs for TDM ports. */ +#define AFE_PORT_ID_TDM_PORT_RANGE_SIZE \ + (AFE_PORT_ID_TDM_PORT_RANGE_END - \ + AFE_PORT_ID_TDM_PORT_RANGE_START+1) + +#define AFE_PORT_ID_PRIMARY_TDM_RX \ + (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x00) +#define AFE_PORT_ID_PRIMARY_TDM_RX_1 \ + (AFE_PORT_ID_PRIMARY_TDM_RX + 0x02) +#define AFE_PORT_ID_PRIMARY_TDM_RX_2 \ + (AFE_PORT_ID_PRIMARY_TDM_RX + 0x04) +#define AFE_PORT_ID_PRIMARY_TDM_RX_3 \ + (AFE_PORT_ID_PRIMARY_TDM_RX + 0x06) +#define AFE_PORT_ID_PRIMARY_TDM_RX_4 \ + (AFE_PORT_ID_PRIMARY_TDM_RX + 0x08) +#define AFE_PORT_ID_PRIMARY_TDM_RX_5 \ + (AFE_PORT_ID_PRIMARY_TDM_RX + 0x0A) +#define AFE_PORT_ID_PRIMARY_TDM_RX_6 \ + (AFE_PORT_ID_PRIMARY_TDM_RX + 0x0C) +#define AFE_PORT_ID_PRIMARY_TDM_RX_7 \ + (AFE_PORT_ID_PRIMARY_TDM_RX + 0x0E) + +#define AFE_PORT_ID_PRIMARY_TDM_TX \ + (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x01) +#define AFE_PORT_ID_PRIMARY_TDM_TX_1 \ + (AFE_PORT_ID_PRIMARY_TDM_TX + 0x02) +#define AFE_PORT_ID_PRIMARY_TDM_TX_2 \ + (AFE_PORT_ID_PRIMARY_TDM_TX + 0x04) +#define AFE_PORT_ID_PRIMARY_TDM_TX_3 \ + (AFE_PORT_ID_PRIMARY_TDM_TX + 0x06) +#define AFE_PORT_ID_PRIMARY_TDM_TX_4 \ + (AFE_PORT_ID_PRIMARY_TDM_TX + 0x08) +#define AFE_PORT_ID_PRIMARY_TDM_TX_5 \ + (AFE_PORT_ID_PRIMARY_TDM_TX + 0x0A) +#define AFE_PORT_ID_PRIMARY_TDM_TX_6 \ + (AFE_PORT_ID_PRIMARY_TDM_TX + 0x0C) +#define AFE_PORT_ID_PRIMARY_TDM_TX_7 \ + (AFE_PORT_ID_PRIMARY_TDM_TX + 0x0E) + +#define AFE_PORT_ID_SECONDARY_TDM_RX \ + (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x10) +#define AFE_PORT_ID_SECONDARY_TDM_RX_1 \ + (AFE_PORT_ID_SECONDARY_TDM_RX + 0x02) +#define AFE_PORT_ID_SECONDARY_TDM_RX_2 \ + (AFE_PORT_ID_SECONDARY_TDM_RX + 0x04) +#define AFE_PORT_ID_SECONDARY_TDM_RX_3 \ + (AFE_PORT_ID_SECONDARY_TDM_RX + 0x06) +#define AFE_PORT_ID_SECONDARY_TDM_RX_4 \ + (AFE_PORT_ID_SECONDARY_TDM_RX + 0x08) +#define AFE_PORT_ID_SECONDARY_TDM_RX_5 \ + (AFE_PORT_ID_SECONDARY_TDM_RX + 0x0A) +#define AFE_PORT_ID_SECONDARY_TDM_RX_6 \ + (AFE_PORT_ID_SECONDARY_TDM_RX + 0x0C) +#define AFE_PORT_ID_SECONDARY_TDM_RX_7 \ + (AFE_PORT_ID_SECONDARY_TDM_RX + 0x0E) + +#define AFE_PORT_ID_SECONDARY_TDM_TX \ + (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x11) +#define AFE_PORT_ID_SECONDARY_TDM_TX_1 \ + (AFE_PORT_ID_SECONDARY_TDM_TX + 0x02) +#define AFE_PORT_ID_SECONDARY_TDM_TX_2 \ + (AFE_PORT_ID_SECONDARY_TDM_TX + 0x04) +#define AFE_PORT_ID_SECONDARY_TDM_TX_3 \ + (AFE_PORT_ID_SECONDARY_TDM_TX + 0x06) +#define AFE_PORT_ID_SECONDARY_TDM_TX_4 \ + (AFE_PORT_ID_SECONDARY_TDM_TX + 0x08) +#define AFE_PORT_ID_SECONDARY_TDM_TX_5 \ + (AFE_PORT_ID_SECONDARY_TDM_TX + 0x0A) +#define AFE_PORT_ID_SECONDARY_TDM_TX_6 \ + (AFE_PORT_ID_SECONDARY_TDM_TX + 0x0C) +#define AFE_PORT_ID_SECONDARY_TDM_TX_7 \ + (AFE_PORT_ID_SECONDARY_TDM_TX + 0x0E) + +#define AFE_PORT_ID_TERTIARY_TDM_RX \ + (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x20) +#define AFE_PORT_ID_TERTIARY_TDM_RX_1 \ + (AFE_PORT_ID_TERTIARY_TDM_RX + 0x02) +#define AFE_PORT_ID_TERTIARY_TDM_RX_2 \ + (AFE_PORT_ID_TERTIARY_TDM_RX + 0x04) +#define AFE_PORT_ID_TERTIARY_TDM_RX_3 \ + (AFE_PORT_ID_TERTIARY_TDM_RX + 0x06) +#define AFE_PORT_ID_TERTIARY_TDM_RX_4 \ + (AFE_PORT_ID_TERTIARY_TDM_RX + 0x08) +#define AFE_PORT_ID_TERTIARY_TDM_RX_5 \ + (AFE_PORT_ID_TERTIARY_TDM_RX + 0x0A) +#define AFE_PORT_ID_TERTIARY_TDM_RX_6 \ + (AFE_PORT_ID_TERTIARY_TDM_RX + 0x0C) +#define AFE_PORT_ID_TERTIARY_TDM_RX_7 \ + (AFE_PORT_ID_TERTIARY_TDM_RX + 0x0E) + +#define AFE_PORT_ID_TERTIARY_TDM_TX \ + (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x21) +#define AFE_PORT_ID_TERTIARY_TDM_TX_1 \ + (AFE_PORT_ID_TERTIARY_TDM_TX + 0x02) +#define AFE_PORT_ID_TERTIARY_TDM_TX_2 \ + (AFE_PORT_ID_TERTIARY_TDM_TX + 0x04) +#define AFE_PORT_ID_TERTIARY_TDM_TX_3 \ + (AFE_PORT_ID_TERTIARY_TDM_TX + 0x06) +#define AFE_PORT_ID_TERTIARY_TDM_TX_4 \ + (AFE_PORT_ID_TERTIARY_TDM_TX + 0x08) +#define AFE_PORT_ID_TERTIARY_TDM_TX_5 \ + (AFE_PORT_ID_TERTIARY_TDM_TX + 0x0A) +#define AFE_PORT_ID_TERTIARY_TDM_TX_6 \ + (AFE_PORT_ID_TERTIARY_TDM_TX + 0x0C) +#define AFE_PORT_ID_TERTIARY_TDM_TX_7 \ + (AFE_PORT_ID_TERTIARY_TDM_TX + 0x0E) + +#define AFE_PORT_ID_QUATERNARY_TDM_RX \ + (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x30) +#define AFE_PORT_ID_QUATERNARY_TDM_RX_1 \ + (AFE_PORT_ID_QUATERNARY_TDM_RX + 0x02) +#define AFE_PORT_ID_QUATERNARY_TDM_RX_2 \ + (AFE_PORT_ID_QUATERNARY_TDM_RX + 0x04) +#define AFE_PORT_ID_QUATERNARY_TDM_RX_3 \ + (AFE_PORT_ID_QUATERNARY_TDM_RX + 0x06) +#define AFE_PORT_ID_QUATERNARY_TDM_RX_4 \ + (AFE_PORT_ID_QUATERNARY_TDM_RX + 0x08) +#define AFE_PORT_ID_QUATERNARY_TDM_RX_5 \ + (AFE_PORT_ID_QUATERNARY_TDM_RX + 0x0A) +#define AFE_PORT_ID_QUATERNARY_TDM_RX_6 \ + (AFE_PORT_ID_QUATERNARY_TDM_RX + 0x0C) +#define AFE_PORT_ID_QUATERNARY_TDM_RX_7 \ + (AFE_PORT_ID_QUATERNARY_TDM_RX + 0x0E) + +#define AFE_PORT_ID_QUATERNARY_TDM_TX \ + (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x31) +#define AFE_PORT_ID_QUATERNARY_TDM_TX_1 \ + (AFE_PORT_ID_QUATERNARY_TDM_TX + 0x02) +#define AFE_PORT_ID_QUATERNARY_TDM_TX_2 \ + (AFE_PORT_ID_QUATERNARY_TDM_TX + 0x04) +#define AFE_PORT_ID_QUATERNARY_TDM_TX_3 \ + (AFE_PORT_ID_QUATERNARY_TDM_TX + 0x06) +#define AFE_PORT_ID_QUATERNARY_TDM_TX_4 \ + (AFE_PORT_ID_QUATERNARY_TDM_TX + 0x08) +#define AFE_PORT_ID_QUATERNARY_TDM_TX_5 \ + (AFE_PORT_ID_QUATERNARY_TDM_TX + 0x0A) +#define AFE_PORT_ID_QUATERNARY_TDM_TX_6 \ + (AFE_PORT_ID_QUATERNARY_TDM_TX + 0x0C) +#define AFE_PORT_ID_QUATERNARY_TDM_TX_7 \ + (AFE_PORT_ID_QUATERNARY_TDM_TX + 0x0E) + +#define AFE_PORT_ID_QUINARY_TDM_RX \ + (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x40) +#define AFE_PORT_ID_QUINARY_TDM_RX_1 \ + (AFE_PORT_ID_QUINARY_TDM_RX + 0x02) +#define AFE_PORT_ID_QUINARY_TDM_RX_2 \ + (AFE_PORT_ID_QUINARY_TDM_RX + 0x04) +#define AFE_PORT_ID_QUINARY_TDM_RX_3 \ + (AFE_PORT_ID_QUINARY_TDM_RX + 0x06) +#define AFE_PORT_ID_QUINARY_TDM_RX_4 \ + (AFE_PORT_ID_QUINARY_TDM_RX + 0x08) +#define AFE_PORT_ID_QUINARY_TDM_RX_5 \ + (AFE_PORT_ID_QUINARY_TDM_RX + 0x0A) +#define AFE_PORT_ID_QUINARY_TDM_RX_6 \ + (AFE_PORT_ID_QUINARY_TDM_RX + 0x0C) +#define AFE_PORT_ID_QUINARY_TDM_RX_7 \ + (AFE_PORT_ID_QUINARY_TDM_RX + 0x0E) + +#define AFE_PORT_ID_QUINARY_TDM_TX \ + (AFE_PORT_ID_TDM_PORT_RANGE_START + 0x41) +#define AFE_PORT_ID_QUINARY_TDM_TX_1 \ + (AFE_PORT_ID_QUINARY_TDM_TX + 0x02) +#define AFE_PORT_ID_QUINARY_TDM_TX_2 \ + (AFE_PORT_ID_QUINARY_TDM_TX + 0x04) +#define AFE_PORT_ID_QUINARY_TDM_TX_3 \ + (AFE_PORT_ID_QUINARY_TDM_TX + 0x06) +#define AFE_PORT_ID_QUINARY_TDM_TX_4 \ + (AFE_PORT_ID_QUINARY_TDM_TX + 0x08) +#define AFE_PORT_ID_QUINARY_TDM_TX_5 \ + (AFE_PORT_ID_QUINARY_TDM_TX + 0x0A) +#define AFE_PORT_ID_QUINARY_TDM_TX_6 \ + (AFE_PORT_ID_QUINARY_TDM_TX + 0x0C) +#define AFE_PORT_ID_QUINARY_TDM_TX_7 \ + (AFE_PORT_ID_QUINARY_TDM_TX + 0x0E) + +/* AFE WSA Codec DMA Rx port 0 */ +#define AFE_PORT_ID_WSA_CODEC_DMA_RX_0 0xB000 +/* AFE WSA Codec DMA Tx port 0 */ +#define AFE_PORT_ID_WSA_CODEC_DMA_TX_0 0xB001 +/* AFE WSA Codec DMA Rx port 1 */ +#define AFE_PORT_ID_WSA_CODEC_DMA_RX_1 0xB002 +/* AFE WSA Codec DMA Tx port 1 */ +#define AFE_PORT_ID_WSA_CODEC_DMA_TX_1 0xB003 +/* AFE WSA Codec DMA Tx port 2 */ +#define AFE_PORT_ID_WSA_CODEC_DMA_TX_2 0xB005 +/* AFE VA Codec DMA Tx port 0 */ +#define AFE_PORT_ID_VA_CODEC_DMA_TX_0 0xB021 +/* AFE VA Codec DMA Tx port 1 */ +#define AFE_PORT_ID_VA_CODEC_DMA_TX_1 0xB023 +/* AFE VA Codec DMA Tx port 2 */ +#define AFE_PORT_ID_VA_CODEC_DMA_TX_2 0xB025 +/* AFE Rx Codec DMA Rx port 0 */ +#define AFE_PORT_ID_RX_CODEC_DMA_RX_0 0xB030 +/* AFE Tx Codec DMA Tx port 0 */ +#define AFE_PORT_ID_TX_CODEC_DMA_TX_0 0xB031 +/* AFE Rx Codec DMA Rx port 1 */ +#define AFE_PORT_ID_RX_CODEC_DMA_RX_1 0xB032 +/* AFE Tx Codec DMA Tx port 1 */ +#define AFE_PORT_ID_TX_CODEC_DMA_TX_1 0xB033 +/* AFE Rx Codec DMA Rx port 2 */ +#define AFE_PORT_ID_RX_CODEC_DMA_RX_2 0xB034 +/* AFE Tx Codec DMA Tx port 2 */ +#define AFE_PORT_ID_TX_CODEC_DMA_TX_2 0xB035 +/* AFE Rx Codec DMA Rx port 3 */ +#define AFE_PORT_ID_RX_CODEC_DMA_RX_3 0xB036 +/* AFE Tx Codec DMA Tx port 3 */ +#define AFE_PORT_ID_TX_CODEC_DMA_TX_3 0xB037 +/* AFE Rx Codec DMA Rx port 4 */ +#define AFE_PORT_ID_RX_CODEC_DMA_RX_4 0xB038 +/* AFE Tx Codec DMA Tx port 4 */ +#define AFE_PORT_ID_TX_CODEC_DMA_TX_4 0xB039 +/* AFE Rx Codec DMA Rx port 5 */ +#define AFE_PORT_ID_RX_CODEC_DMA_RX_5 0xB03A +/* AFE Tx Codec DMA Tx port 5 */ +#define AFE_PORT_ID_TX_CODEC_DMA_TX_5 0xB03B +/* AFE Rx Codec DMA Rx port 6 */ +#define AFE_PORT_ID_RX_CODEC_DMA_RX_6 0xB03C +/* AFE Rx Codec DMA Rx port 7 */ +#define AFE_PORT_ID_RX_CODEC_DMA_RX_7 0xB03E + +#define Q6AFE_LPASS_MODE_CLK1_VALID 1 +#define Q6AFE_LPASS_MODE_CLK2_VALID 2 +#define Q6AFE_LPASS_CLK_SRC_INTERNAL 1 +#define Q6AFE_LPASS_CLK_ROOT_DEFAULT 0 +#define AFE_API_VERSION_TDM_CONFIG 1 +#define AFE_API_VERSION_SLOT_MAPPING_CONFIG 1 +#define AFE_API_VERSION_CODEC_DMA_CONFIG 1 + +#define TIMEOUT_MS 1000 +#define AFE_CMD_RESP_AVAIL 0 +#define AFE_CMD_RESP_NONE 1 +#define AFE_CLK_TOKEN 1024 + +struct q6afe { + struct apr_device *apr; + struct device *dev; + struct q6core_svc_api_info ainfo; + struct mutex lock; + struct aprv2_ibasic_rsp_result_t result; + wait_queue_head_t wait; + struct list_head port_list; + spinlock_t port_list_lock; +}; + +struct afe_port_cmd_device_start { + u16 port_id; + u16 reserved; +} __packed; + +struct afe_port_cmd_device_stop { + u16 port_id; + u16 reserved; +/* Reserved for 32-bit alignment. This field must be set to 0.*/ +} __packed; + +struct afe_port_param_data_v2 { + u32 module_id; + u32 param_id; + u16 param_size; + u16 reserved; +} __packed; + +struct afe_svc_cmd_set_param { + uint32_t payload_size; + uint32_t payload_address_lsw; + uint32_t payload_address_msw; + uint32_t mem_map_handle; +} __packed; + +struct afe_port_cmd_set_param_v2 { + u16 port_id; + u16 payload_size; + u32 payload_address_lsw; + u32 payload_address_msw; + u32 mem_map_handle; +} __packed; + +struct afe_param_id_hdmi_multi_chan_audio_cfg { + u32 hdmi_cfg_minor_version; + u16 datatype; + u16 channel_allocation; + u32 sample_rate; + u16 bit_width; + u16 reserved; +} __packed; + +struct afe_param_id_slimbus_cfg { + u32 sb_cfg_minor_version; +/* Minor version used for tracking the version of the SLIMBUS + * configuration interface. + * Supported values: #AFE_API_VERSION_SLIMBUS_CONFIG + */ + + u16 slimbus_dev_id; +/* SLIMbus hardware device ID, which is required to handle + * multiple SLIMbus hardware blocks. + * Supported values: - #AFE_SLIMBUS_DEVICE_1 - #AFE_SLIMBUS_DEVICE_2 + */ + u16 bit_width; +/* Bit width of the sample. + * Supported values: 16, 24 + */ + u16 data_format; +/* Data format supported by the SLIMbus hardware. The default is + * 0 (#AFE_SB_DATA_FORMAT_NOT_INDICATED), which indicates the + * hardware does not perform any format conversions before the data + * transfer. + */ + u16 num_channels; +/* Number of channels. + * Supported values: 1 to #AFE_PORT_MAX_AUDIO_CHAN_CNT + */ + u8 shared_ch_mapping[AFE_PORT_MAX_AUDIO_CHAN_CNT]; +/* Mapping of shared channel IDs (128 to 255) to which the + * master port is to be connected. + * Shared_channel_mapping[i] represents the shared channel assigned + * for audio channel i in multichannel audio data. + */ + u32 sample_rate; +/* Sampling rate of the port. + * Supported values: + * - #AFE_PORT_SAMPLE_RATE_8K + * - #AFE_PORT_SAMPLE_RATE_16K + * - #AFE_PORT_SAMPLE_RATE_48K + * - #AFE_PORT_SAMPLE_RATE_96K + * - #AFE_PORT_SAMPLE_RATE_192K + */ +} __packed; + +struct afe_clk_cfg { + u32 i2s_cfg_minor_version; + u32 clk_val1; + u32 clk_val2; + u16 clk_src; + u16 clk_root; + u16 clk_set_mode; + u16 reserved; +} __packed; + +struct afe_digital_clk_cfg { + u32 i2s_cfg_minor_version; + u32 clk_val; + u16 clk_root; + u16 reserved; +} __packed; + +struct afe_param_id_i2s_cfg { + u32 i2s_cfg_minor_version; + u16 bit_width; + u16 channel_mode; + u16 mono_stereo; + u16 ws_src; + u32 sample_rate; + u16 data_format; + u16 reserved; +} __packed; + +struct afe_param_id_tdm_cfg { + u32 tdm_cfg_minor_version; + u32 num_channels; + u32 sample_rate; + u32 bit_width; + u16 data_format; + u16 sync_mode; + u16 sync_src; + u16 nslots_per_frame; + u16 ctrl_data_out_enable; + u16 ctrl_invert_sync_pulse; + u16 ctrl_sync_data_delay; + u16 slot_width; + u32 slot_mask; +} __packed; + +struct afe_param_id_cdc_dma_cfg { + u32 cdc_dma_cfg_minor_version; + u32 sample_rate; + u16 bit_width; + u16 data_format; + u16 num_channels; + u16 active_channels_mask; +} __packed; + +union afe_port_config { + struct afe_param_id_hdmi_multi_chan_audio_cfg hdmi_multi_ch; + struct afe_param_id_slimbus_cfg slim_cfg; + struct afe_param_id_i2s_cfg i2s_cfg; + struct afe_param_id_tdm_cfg tdm_cfg; + struct afe_param_id_cdc_dma_cfg dma_cfg; +} __packed; + + +struct afe_clk_set { + uint32_t clk_set_minor_version; + uint32_t clk_id; + uint32_t clk_freq_in_hz; + uint16_t clk_attri; + uint16_t clk_root; + uint32_t enable; +}; + +struct afe_param_id_slot_mapping_cfg { + u32 minor_version; + u16 num_channels; + u16 bitwidth; + u32 data_align_type; + u16 ch_mapping[AFE_PORT_MAX_AUDIO_CHAN_CNT]; +} __packed; + +struct q6afe_port { + wait_queue_head_t wait; + union afe_port_config port_cfg; + struct afe_param_id_slot_mapping_cfg *scfg; + struct aprv2_ibasic_rsp_result_t result; + int token; + int id; + int cfg_type; + struct q6afe *afe; + struct kref refcount; + struct list_head node; +}; + +struct afe_cmd_remote_lpass_core_hw_vote_request { + uint32_t hw_block_id; + char client_name[8]; +} __packed; + +struct afe_cmd_remote_lpass_core_hw_devote_request { + uint32_t hw_block_id; + uint32_t client_handle; +} __packed; + + + +struct afe_port_map { + int port_id; + int token; + int is_rx; + int is_dig_pcm; +}; + +/* + * Mapping between Virtual Port IDs to DSP AFE Port ID + * On B Family SoCs DSP Port IDs are consistent across multiple SoCs + * on A Family SoCs DSP port IDs are same as virtual Port IDs. + */ + +static struct afe_port_map port_maps[AFE_PORT_MAX] = { + [HDMI_RX] = { AFE_PORT_ID_MULTICHAN_HDMI_RX, HDMI_RX, 1, 1}, + [SLIMBUS_0_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX, + SLIMBUS_0_RX, 1, 1}, + [SLIMBUS_1_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX, + SLIMBUS_1_RX, 1, 1}, + [SLIMBUS_2_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_RX, + SLIMBUS_2_RX, 1, 1}, + [SLIMBUS_3_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_RX, + SLIMBUS_3_RX, 1, 1}, + [SLIMBUS_4_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_RX, + SLIMBUS_4_RX, 1, 1}, + [SLIMBUS_5_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_RX, + SLIMBUS_5_RX, 1, 1}, + [SLIMBUS_6_RX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_RX, + SLIMBUS_6_RX, 1, 1}, + [SLIMBUS_0_TX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_TX, + SLIMBUS_0_TX, 0, 1}, + [SLIMBUS_1_TX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_TX, + SLIMBUS_1_TX, 0, 1}, + [SLIMBUS_2_TX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_TX, + SLIMBUS_2_TX, 0, 1}, + [SLIMBUS_3_TX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_TX, + SLIMBUS_3_TX, 0, 1}, + [SLIMBUS_4_TX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_TX, + SLIMBUS_4_TX, 0, 1}, + [SLIMBUS_5_TX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_TX, + SLIMBUS_5_TX, 0, 1}, + [SLIMBUS_6_TX] = { AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_TX, + SLIMBUS_6_TX, 0, 1}, + [PRIMARY_MI2S_RX] = { AFE_PORT_ID_PRIMARY_MI2S_RX, + PRIMARY_MI2S_RX, 1, 1}, + [PRIMARY_MI2S_TX] = { AFE_PORT_ID_PRIMARY_MI2S_TX, + PRIMARY_MI2S_RX, 0, 1}, + [SECONDARY_MI2S_RX] = { AFE_PORT_ID_SECONDARY_MI2S_RX, + SECONDARY_MI2S_RX, 1, 1}, + [SECONDARY_MI2S_TX] = { AFE_PORT_ID_SECONDARY_MI2S_TX, + SECONDARY_MI2S_TX, 0, 1}, + [TERTIARY_MI2S_RX] = { AFE_PORT_ID_TERTIARY_MI2S_RX, + TERTIARY_MI2S_RX, 1, 1}, + [TERTIARY_MI2S_TX] = { AFE_PORT_ID_TERTIARY_MI2S_TX, + TERTIARY_MI2S_TX, 0, 1}, + [QUATERNARY_MI2S_RX] = { AFE_PORT_ID_QUATERNARY_MI2S_RX, + QUATERNARY_MI2S_RX, 1, 1}, + [QUATERNARY_MI2S_TX] = { AFE_PORT_ID_QUATERNARY_MI2S_TX, + QUATERNARY_MI2S_TX, 0, 1}, + [PRIMARY_TDM_RX_0] = { AFE_PORT_ID_PRIMARY_TDM_RX, + PRIMARY_TDM_RX_0, 1, 1}, + [PRIMARY_TDM_TX_0] = { AFE_PORT_ID_PRIMARY_TDM_TX, + PRIMARY_TDM_TX_0, 0, 1}, + [PRIMARY_TDM_RX_1] = { AFE_PORT_ID_PRIMARY_TDM_RX_1, + PRIMARY_TDM_RX_1, 1, 1}, + [PRIMARY_TDM_TX_1] = { AFE_PORT_ID_PRIMARY_TDM_TX_1, + PRIMARY_TDM_TX_1, 0, 1}, + [PRIMARY_TDM_RX_2] = { AFE_PORT_ID_PRIMARY_TDM_RX_2, + PRIMARY_TDM_RX_2, 1, 1}, + [PRIMARY_TDM_TX_2] = { AFE_PORT_ID_PRIMARY_TDM_TX_2, + PRIMARY_TDM_TX_2, 0, 1}, + [PRIMARY_TDM_RX_3] = { AFE_PORT_ID_PRIMARY_TDM_RX_3, + PRIMARY_TDM_RX_3, 1, 1}, + [PRIMARY_TDM_TX_3] = { AFE_PORT_ID_PRIMARY_TDM_TX_3, + PRIMARY_TDM_TX_3, 0, 1}, + [PRIMARY_TDM_RX_4] = { AFE_PORT_ID_PRIMARY_TDM_RX_4, + PRIMARY_TDM_RX_4, 1, 1}, + [PRIMARY_TDM_TX_4] = { AFE_PORT_ID_PRIMARY_TDM_TX_4, + PRIMARY_TDM_TX_4, 0, 1}, + [PRIMARY_TDM_RX_5] = { AFE_PORT_ID_PRIMARY_TDM_RX_5, + PRIMARY_TDM_RX_5, 1, 1}, + [PRIMARY_TDM_TX_5] = { AFE_PORT_ID_PRIMARY_TDM_TX_5, + PRIMARY_TDM_TX_5, 0, 1}, + [PRIMARY_TDM_RX_6] = { AFE_PORT_ID_PRIMARY_TDM_RX_6, + PRIMARY_TDM_RX_6, 1, 1}, + [PRIMARY_TDM_TX_6] = { AFE_PORT_ID_PRIMARY_TDM_TX_6, + PRIMARY_TDM_TX_6, 0, 1}, + [PRIMARY_TDM_RX_7] = { AFE_PORT_ID_PRIMARY_TDM_RX_7, + PRIMARY_TDM_RX_7, 1, 1}, + [PRIMARY_TDM_TX_7] = { AFE_PORT_ID_PRIMARY_TDM_TX_7, + PRIMARY_TDM_TX_7, 0, 1}, + [SECONDARY_TDM_RX_0] = { AFE_PORT_ID_SECONDARY_TDM_RX, + SECONDARY_TDM_RX_0, 1, 1}, + [SECONDARY_TDM_TX_0] = { AFE_PORT_ID_SECONDARY_TDM_TX, + SECONDARY_TDM_TX_0, 0, 1}, + [SECONDARY_TDM_RX_1] = { AFE_PORT_ID_SECONDARY_TDM_RX_1, + SECONDARY_TDM_RX_1, 1, 1}, + [SECONDARY_TDM_TX_1] = { AFE_PORT_ID_SECONDARY_TDM_TX_1, + SECONDARY_TDM_TX_1, 0, 1}, + [SECONDARY_TDM_RX_2] = { AFE_PORT_ID_SECONDARY_TDM_RX_2, + SECONDARY_TDM_RX_2, 1, 1}, + [SECONDARY_TDM_TX_2] = { AFE_PORT_ID_SECONDARY_TDM_TX_2, + SECONDARY_TDM_TX_2, 0, 1}, + [SECONDARY_TDM_RX_3] = { AFE_PORT_ID_SECONDARY_TDM_RX_3, + SECONDARY_TDM_RX_3, 1, 1}, + [SECONDARY_TDM_TX_3] = { AFE_PORT_ID_SECONDARY_TDM_TX_3, + SECONDARY_TDM_TX_3, 0, 1}, + [SECONDARY_TDM_RX_4] = { AFE_PORT_ID_SECONDARY_TDM_RX_4, + SECONDARY_TDM_RX_4, 1, 1}, + [SECONDARY_TDM_TX_4] = { AFE_PORT_ID_SECONDARY_TDM_TX_4, + SECONDARY_TDM_TX_4, 0, 1}, + [SECONDARY_TDM_RX_5] = { AFE_PORT_ID_SECONDARY_TDM_RX_5, + SECONDARY_TDM_RX_5, 1, 1}, + [SECONDARY_TDM_TX_5] = { AFE_PORT_ID_SECONDARY_TDM_TX_5, + SECONDARY_TDM_TX_5, 0, 1}, + [SECONDARY_TDM_RX_6] = { AFE_PORT_ID_SECONDARY_TDM_RX_6, + SECONDARY_TDM_RX_6, 1, 1}, + [SECONDARY_TDM_TX_6] = { AFE_PORT_ID_SECONDARY_TDM_TX_6, + SECONDARY_TDM_TX_6, 0, 1}, + [SECONDARY_TDM_RX_7] = { AFE_PORT_ID_SECONDARY_TDM_RX_7, + SECONDARY_TDM_RX_7, 1, 1}, + [SECONDARY_TDM_TX_7] = { AFE_PORT_ID_SECONDARY_TDM_TX_7, + SECONDARY_TDM_TX_7, 0, 1}, + [TERTIARY_TDM_RX_0] = { AFE_PORT_ID_TERTIARY_TDM_RX, + TERTIARY_TDM_RX_0, 1, 1}, + [TERTIARY_TDM_TX_0] = { AFE_PORT_ID_TERTIARY_TDM_TX, + TERTIARY_TDM_TX_0, 0, 1}, + [TERTIARY_TDM_RX_1] = { AFE_PORT_ID_TERTIARY_TDM_RX_1, + TERTIARY_TDM_RX_1, 1, 1}, + [TERTIARY_TDM_TX_1] = { AFE_PORT_ID_TERTIARY_TDM_TX_1, + TERTIARY_TDM_TX_1, 0, 1}, + [TERTIARY_TDM_RX_2] = { AFE_PORT_ID_TERTIARY_TDM_RX_2, + TERTIARY_TDM_RX_2, 1, 1}, + [TERTIARY_TDM_TX_2] = { AFE_PORT_ID_TERTIARY_TDM_TX_2, + TERTIARY_TDM_TX_2, 0, 1}, + [TERTIARY_TDM_RX_3] = { AFE_PORT_ID_TERTIARY_TDM_RX_3, + TERTIARY_TDM_RX_3, 1, 1}, + [TERTIARY_TDM_TX_3] = { AFE_PORT_ID_TERTIARY_TDM_TX_3, + TERTIARY_TDM_TX_3, 0, 1}, + [TERTIARY_TDM_RX_4] = { AFE_PORT_ID_TERTIARY_TDM_RX_4, + TERTIARY_TDM_RX_4, 1, 1}, + [TERTIARY_TDM_TX_4] = { AFE_PORT_ID_TERTIARY_TDM_TX_4, + TERTIARY_TDM_TX_4, 0, 1}, + [TERTIARY_TDM_RX_5] = { AFE_PORT_ID_TERTIARY_TDM_RX_5, + TERTIARY_TDM_RX_5, 1, 1}, + [TERTIARY_TDM_TX_5] = { AFE_PORT_ID_TERTIARY_TDM_TX_5, + TERTIARY_TDM_TX_5, 0, 1}, + [TERTIARY_TDM_RX_6] = { AFE_PORT_ID_TERTIARY_TDM_RX_6, + TERTIARY_TDM_RX_6, 1, 1}, + [TERTIARY_TDM_TX_6] = { AFE_PORT_ID_TERTIARY_TDM_TX_6, + TERTIARY_TDM_TX_6, 0, 1}, + [TERTIARY_TDM_RX_7] = { AFE_PORT_ID_TERTIARY_TDM_RX_7, + TERTIARY_TDM_RX_7, 1, 1}, + [TERTIARY_TDM_TX_7] = { AFE_PORT_ID_TERTIARY_TDM_TX_7, + TERTIARY_TDM_TX_7, 0, 1}, + [QUATERNARY_TDM_RX_0] = { AFE_PORT_ID_QUATERNARY_TDM_RX, + QUATERNARY_TDM_RX_0, 1, 1}, + [QUATERNARY_TDM_TX_0] = { AFE_PORT_ID_QUATERNARY_TDM_TX, + QUATERNARY_TDM_TX_0, 0, 1}, + [QUATERNARY_TDM_RX_1] = { AFE_PORT_ID_QUATERNARY_TDM_RX_1, + QUATERNARY_TDM_RX_1, 1, 1}, + [QUATERNARY_TDM_TX_1] = { AFE_PORT_ID_QUATERNARY_TDM_TX_1, + QUATERNARY_TDM_TX_1, 0, 1}, + [QUATERNARY_TDM_RX_2] = { AFE_PORT_ID_QUATERNARY_TDM_RX_2, + QUATERNARY_TDM_RX_2, 1, 1}, + [QUATERNARY_TDM_TX_2] = { AFE_PORT_ID_QUATERNARY_TDM_TX_2, + QUATERNARY_TDM_TX_2, 0, 1}, + [QUATERNARY_TDM_RX_3] = { AFE_PORT_ID_QUATERNARY_TDM_RX_3, + QUATERNARY_TDM_RX_3, 1, 1}, + [QUATERNARY_TDM_TX_3] = { AFE_PORT_ID_QUATERNARY_TDM_TX_3, + QUATERNARY_TDM_TX_3, 0, 1}, + [QUATERNARY_TDM_RX_4] = { AFE_PORT_ID_QUATERNARY_TDM_RX_4, + QUATERNARY_TDM_RX_4, 1, 1}, + [QUATERNARY_TDM_TX_4] = { AFE_PORT_ID_QUATERNARY_TDM_TX_4, + QUATERNARY_TDM_TX_4, 0, 1}, + [QUATERNARY_TDM_RX_5] = { AFE_PORT_ID_QUATERNARY_TDM_RX_5, + QUATERNARY_TDM_RX_5, 1, 1}, + [QUATERNARY_TDM_TX_5] = { AFE_PORT_ID_QUATERNARY_TDM_TX_5, + QUATERNARY_TDM_TX_5, 0, 1}, + [QUATERNARY_TDM_RX_6] = { AFE_PORT_ID_QUATERNARY_TDM_RX_6, + QUATERNARY_TDM_RX_6, 1, 1}, + [QUATERNARY_TDM_TX_6] = { AFE_PORT_ID_QUATERNARY_TDM_TX_6, + QUATERNARY_TDM_TX_6, 0, 1}, + [QUATERNARY_TDM_RX_7] = { AFE_PORT_ID_QUATERNARY_TDM_RX_7, + QUATERNARY_TDM_RX_7, 1, 1}, + [QUATERNARY_TDM_TX_7] = { AFE_PORT_ID_QUATERNARY_TDM_TX_7, + QUATERNARY_TDM_TX_7, 0, 1}, + [QUINARY_TDM_RX_0] = { AFE_PORT_ID_QUINARY_TDM_RX, + QUINARY_TDM_RX_0, 1, 1}, + [QUINARY_TDM_TX_0] = { AFE_PORT_ID_QUINARY_TDM_TX, + QUINARY_TDM_TX_0, 0, 1}, + [QUINARY_TDM_RX_1] = { AFE_PORT_ID_QUINARY_TDM_RX_1, + QUINARY_TDM_RX_1, 1, 1}, + [QUINARY_TDM_TX_1] = { AFE_PORT_ID_QUINARY_TDM_TX_1, + QUINARY_TDM_TX_1, 0, 1}, + [QUINARY_TDM_RX_2] = { AFE_PORT_ID_QUINARY_TDM_RX_2, + QUINARY_TDM_RX_2, 1, 1}, + [QUINARY_TDM_TX_2] = { AFE_PORT_ID_QUINARY_TDM_TX_2, + QUINARY_TDM_TX_2, 0, 1}, + [QUINARY_TDM_RX_3] = { AFE_PORT_ID_QUINARY_TDM_RX_3, + QUINARY_TDM_RX_3, 1, 1}, + [QUINARY_TDM_TX_3] = { AFE_PORT_ID_QUINARY_TDM_TX_3, + QUINARY_TDM_TX_3, 0, 1}, + [QUINARY_TDM_RX_4] = { AFE_PORT_ID_QUINARY_TDM_RX_4, + QUINARY_TDM_RX_4, 1, 1}, + [QUINARY_TDM_TX_4] = { AFE_PORT_ID_QUINARY_TDM_TX_4, + QUINARY_TDM_TX_4, 0, 1}, + [QUINARY_TDM_RX_5] = { AFE_PORT_ID_QUINARY_TDM_RX_5, + QUINARY_TDM_RX_5, 1, 1}, + [QUINARY_TDM_TX_5] = { AFE_PORT_ID_QUINARY_TDM_TX_5, + QUINARY_TDM_TX_5, 0, 1}, + [QUINARY_TDM_RX_6] = { AFE_PORT_ID_QUINARY_TDM_RX_6, + QUINARY_TDM_RX_6, 1, 1}, + [QUINARY_TDM_TX_6] = { AFE_PORT_ID_QUINARY_TDM_TX_6, + QUINARY_TDM_TX_6, 0, 1}, + [QUINARY_TDM_RX_7] = { AFE_PORT_ID_QUINARY_TDM_RX_7, + QUINARY_TDM_RX_7, 1, 1}, + [QUINARY_TDM_TX_7] = { AFE_PORT_ID_QUINARY_TDM_TX_7, + QUINARY_TDM_TX_7, 0, 1}, + [DISPLAY_PORT_RX] = { AFE_PORT_ID_HDMI_OVER_DP_RX, + DISPLAY_PORT_RX, 1, 1}, + [WSA_CODEC_DMA_RX_0] = { AFE_PORT_ID_WSA_CODEC_DMA_RX_0, + WSA_CODEC_DMA_RX_0, 1, 1}, + [WSA_CODEC_DMA_TX_0] = { AFE_PORT_ID_WSA_CODEC_DMA_TX_0, + WSA_CODEC_DMA_TX_0, 0, 1}, + [WSA_CODEC_DMA_RX_1] = { AFE_PORT_ID_WSA_CODEC_DMA_RX_1, + WSA_CODEC_DMA_RX_1, 1, 1}, + [WSA_CODEC_DMA_TX_1] = { AFE_PORT_ID_WSA_CODEC_DMA_TX_1, + WSA_CODEC_DMA_TX_1, 0, 1}, + [WSA_CODEC_DMA_TX_2] = { AFE_PORT_ID_WSA_CODEC_DMA_TX_2, + WSA_CODEC_DMA_TX_2, 0, 1}, + [VA_CODEC_DMA_TX_0] = { AFE_PORT_ID_VA_CODEC_DMA_TX_0, + VA_CODEC_DMA_TX_0, 0, 1}, + [VA_CODEC_DMA_TX_1] = { AFE_PORT_ID_VA_CODEC_DMA_TX_1, + VA_CODEC_DMA_TX_1, 0, 1}, + [VA_CODEC_DMA_TX_2] = { AFE_PORT_ID_VA_CODEC_DMA_TX_2, + VA_CODEC_DMA_TX_2, 0, 1}, + [RX_CODEC_DMA_RX_0] = { AFE_PORT_ID_RX_CODEC_DMA_RX_0, + RX_CODEC_DMA_RX_0, 1, 1}, + [TX_CODEC_DMA_TX_0] = { AFE_PORT_ID_TX_CODEC_DMA_TX_0, + TX_CODEC_DMA_TX_0, 0, 1}, + [RX_CODEC_DMA_RX_1] = { AFE_PORT_ID_RX_CODEC_DMA_RX_1, + RX_CODEC_DMA_RX_1, 1, 1}, + [TX_CODEC_DMA_TX_1] = { AFE_PORT_ID_TX_CODEC_DMA_TX_1, + TX_CODEC_DMA_TX_1, 0, 1}, + [RX_CODEC_DMA_RX_2] = { AFE_PORT_ID_RX_CODEC_DMA_RX_2, + RX_CODEC_DMA_RX_2, 1, 1}, + [TX_CODEC_DMA_TX_2] = { AFE_PORT_ID_TX_CODEC_DMA_TX_2, + TX_CODEC_DMA_TX_2, 0, 1}, + [RX_CODEC_DMA_RX_3] = { AFE_PORT_ID_RX_CODEC_DMA_RX_3, + RX_CODEC_DMA_RX_3, 1, 1}, + [TX_CODEC_DMA_TX_3] = { AFE_PORT_ID_TX_CODEC_DMA_TX_3, + TX_CODEC_DMA_TX_3, 0, 1}, + [RX_CODEC_DMA_RX_4] = { AFE_PORT_ID_RX_CODEC_DMA_RX_4, + RX_CODEC_DMA_RX_4, 1, 1}, + [TX_CODEC_DMA_TX_4] = { AFE_PORT_ID_TX_CODEC_DMA_TX_4, + TX_CODEC_DMA_TX_4, 0, 1}, + [RX_CODEC_DMA_RX_5] = { AFE_PORT_ID_RX_CODEC_DMA_RX_5, + RX_CODEC_DMA_RX_5, 1, 1}, + [TX_CODEC_DMA_TX_5] = { AFE_PORT_ID_TX_CODEC_DMA_TX_5, + TX_CODEC_DMA_TX_5, 0, 1}, + [RX_CODEC_DMA_RX_6] = { AFE_PORT_ID_RX_CODEC_DMA_RX_6, + RX_CODEC_DMA_RX_6, 1, 1}, + [RX_CODEC_DMA_RX_7] = { AFE_PORT_ID_RX_CODEC_DMA_RX_7, + RX_CODEC_DMA_RX_7, 1, 1}, +}; + +static void q6afe_port_free(struct kref *ref) +{ + struct q6afe_port *port; + struct q6afe *afe; + unsigned long flags; + + port = container_of(ref, struct q6afe_port, refcount); + afe = port->afe; + spin_lock_irqsave(&afe->port_list_lock, flags); + list_del(&port->node); + spin_unlock_irqrestore(&afe->port_list_lock, flags); + kfree(port->scfg); + kfree(port); +} + +static struct q6afe_port *q6afe_find_port(struct q6afe *afe, int token) +{ + struct q6afe_port *p = NULL; + struct q6afe_port *ret = NULL; + unsigned long flags; + + spin_lock_irqsave(&afe->port_list_lock, flags); + list_for_each_entry(p, &afe->port_list, node) + if (p->token == token) { + ret = p; + kref_get(&p->refcount); + break; + } + + spin_unlock_irqrestore(&afe->port_list_lock, flags); + return ret; +} + +static int q6afe_callback(struct apr_device *adev, struct apr_resp_pkt *data) +{ + struct q6afe *afe = dev_get_drvdata(&adev->dev); + struct aprv2_ibasic_rsp_result_t *res; + struct apr_hdr *hdr = &data->hdr; + struct q6afe_port *port; + + if (!data->payload_size) + return 0; + + res = data->payload; + switch (hdr->opcode) { + case APR_BASIC_RSP_RESULT: { + if (res->status) { + dev_err(afe->dev, "cmd = 0x%x returned error = 0x%x\n", + res->opcode, res->status); + } + switch (res->opcode) { + case AFE_PORT_CMD_SET_PARAM_V2: + case AFE_PORT_CMD_DEVICE_STOP: + case AFE_PORT_CMD_DEVICE_START: + case AFE_SVC_CMD_SET_PARAM: + port = q6afe_find_port(afe, hdr->token); + if (port) { + port->result = *res; + wake_up(&port->wait); + kref_put(&port->refcount, q6afe_port_free); + } else if (hdr->token == AFE_CLK_TOKEN) { + afe->result = *res; + wake_up(&afe->wait); + } + break; + default: + dev_err(afe->dev, "Unknown cmd 0x%x\n", res->opcode); + break; + } + } + break; + case AFE_CMD_RSP_REMOTE_LPASS_CORE_HW_VOTE_REQUEST: + afe->result.opcode = hdr->opcode; + afe->result.status = res->status; + wake_up(&afe->wait); + break; + default: + break; + } + + return 0; +} + +/** + * q6afe_get_port_id() - Get port id from a given port index + * + * @index: port index + * + * Return: Will be an negative on error or valid port_id on success + */ +int q6afe_get_port_id(int index) +{ + if (index < 0 || index >= AFE_PORT_MAX) + return -EINVAL; + + return port_maps[index].port_id; +} +EXPORT_SYMBOL_GPL(q6afe_get_port_id); + +static int afe_apr_send_pkt(struct q6afe *afe, struct apr_pkt *pkt, + struct q6afe_port *port, uint32_t rsp_opcode) +{ + wait_queue_head_t *wait = &port->wait; + struct aprv2_ibasic_rsp_result_t *result; + int ret; + + mutex_lock(&afe->lock); + if (port) { + wait = &port->wait; + result = &port->result; + } else { + result = &afe->result; + wait = &afe->wait; + } + + result->opcode = 0; + result->status = 0; + + ret = apr_send_pkt(afe->apr, pkt); + if (ret < 0) { + dev_err(afe->dev, "packet not transmitted (%d)\n", ret); + ret = -EINVAL; + goto err; + } + + ret = wait_event_timeout(*wait, (result->opcode == rsp_opcode), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + ret = -ETIMEDOUT; + } else if (result->status > 0) { + dev_err(afe->dev, "DSP returned error[%x]\n", + result->status); + ret = -EINVAL; + } else { + ret = 0; + } + +err: + mutex_unlock(&afe->lock); + + return ret; +} + +static int q6afe_set_param(struct q6afe *afe, struct q6afe_port *port, + void *data, int param_id, int module_id, int psize, + int token) +{ + struct afe_svc_cmd_set_param *param; + struct afe_port_param_data_v2 *pdata; + struct apr_pkt *pkt; + int ret, pkt_size; + void *p, *pl; + + pkt_size = APR_HDR_SIZE + sizeof(*param) + sizeof(*pdata) + psize; + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + param = p + APR_HDR_SIZE; + pdata = p + APR_HDR_SIZE + sizeof(*param); + pl = p + APR_HDR_SIZE + sizeof(*param) + sizeof(*pdata); + memcpy(pl, data, psize); + + pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + pkt->hdr.pkt_size = pkt_size; + pkt->hdr.src_port = 0; + pkt->hdr.dest_port = 0; + pkt->hdr.token = token; + pkt->hdr.opcode = AFE_SVC_CMD_SET_PARAM; + + param->payload_size = sizeof(*pdata) + psize; + param->payload_address_lsw = 0x00; + param->payload_address_msw = 0x00; + param->mem_map_handle = 0x00; + pdata->module_id = module_id; + pdata->param_id = param_id; + pdata->param_size = psize; + + ret = afe_apr_send_pkt(afe, pkt, port, AFE_SVC_CMD_SET_PARAM); + if (ret) + dev_err(afe->dev, "AFE set params failed %d\n", ret); + + kfree(pkt); + return ret; +} + +static int q6afe_port_set_param(struct q6afe_port *port, void *data, + int param_id, int module_id, int psize) +{ + return q6afe_set_param(port->afe, port, data, param_id, module_id, + psize, port->token); +} + +static int q6afe_port_set_param_v2(struct q6afe_port *port, void *data, + int param_id, int module_id, int psize) +{ + struct afe_port_cmd_set_param_v2 *param; + struct afe_port_param_data_v2 *pdata; + struct q6afe *afe = port->afe; + struct apr_pkt *pkt; + u16 port_id = port->id; + int ret, pkt_size; + void *p, *pl; + + pkt_size = APR_HDR_SIZE + sizeof(*param) + sizeof(*pdata) + psize; + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + param = p + APR_HDR_SIZE; + pdata = p + APR_HDR_SIZE + sizeof(*param); + pl = p + APR_HDR_SIZE + sizeof(*param) + sizeof(*pdata); + memcpy(pl, data, psize); + + pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + pkt->hdr.pkt_size = pkt_size; + pkt->hdr.src_port = 0; + pkt->hdr.dest_port = 0; + pkt->hdr.token = port->token; + pkt->hdr.opcode = AFE_PORT_CMD_SET_PARAM_V2; + + param->port_id = port_id; + param->payload_size = sizeof(*pdata) + psize; + param->payload_address_lsw = 0x00; + param->payload_address_msw = 0x00; + param->mem_map_handle = 0x00; + pdata->module_id = module_id; + pdata->param_id = param_id; + pdata->param_size = psize; + + ret = afe_apr_send_pkt(afe, pkt, port, AFE_PORT_CMD_SET_PARAM_V2); + if (ret) + dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n", + port_id, ret); + + kfree(pkt); + return ret; +} + +static int q6afe_port_set_lpass_clock(struct q6afe_port *port, + struct afe_clk_cfg *cfg) +{ + return q6afe_port_set_param_v2(port, cfg, + AFE_PARAM_ID_LPAIF_CLK_CONFIG, + AFE_MODULE_AUDIO_DEV_INTERFACE, + sizeof(*cfg)); +} + +static int q6afe_set_lpass_clock_v2(struct q6afe_port *port, + struct afe_clk_set *cfg) +{ + return q6afe_port_set_param(port, cfg, AFE_PARAM_ID_CLOCK_SET, + AFE_MODULE_CLOCK_SET, sizeof(*cfg)); +} + +static int q6afe_set_digital_codec_core_clock(struct q6afe_port *port, + struct afe_digital_clk_cfg *cfg) +{ + return q6afe_port_set_param_v2(port, cfg, + AFE_PARAM_ID_INT_DIGITAL_CDC_CLK_CONFIG, + AFE_MODULE_AUDIO_DEV_INTERFACE, + sizeof(*cfg)); +} + +int q6afe_set_lpass_clock(struct device *dev, int clk_id, int attri, + int clk_root, unsigned int freq) +{ + struct q6afe *afe = dev_get_drvdata(dev->parent); + struct afe_clk_set cset = {0,}; + + cset.clk_set_minor_version = AFE_API_VERSION_CLOCK_SET; + cset.clk_id = clk_id; + cset.clk_freq_in_hz = freq; + cset.clk_attri = attri; + cset.clk_root = clk_root; + cset.enable = !!freq; + + return q6afe_set_param(afe, NULL, &cset, AFE_PARAM_ID_CLOCK_SET, + AFE_MODULE_CLOCK_SET, sizeof(cset), + AFE_CLK_TOKEN); +} +EXPORT_SYMBOL_GPL(q6afe_set_lpass_clock); + +int q6afe_port_set_sysclk(struct q6afe_port *port, int clk_id, + int clk_src, int clk_root, + unsigned int freq, int dir) +{ + struct afe_clk_cfg ccfg = {0,}; + struct afe_clk_set cset = {0,}; + struct afe_digital_clk_cfg dcfg = {0,}; + int ret; + + switch (clk_id) { + case LPAIF_DIG_CLK: + dcfg.i2s_cfg_minor_version = AFE_API_VERSION_I2S_CONFIG; + dcfg.clk_val = freq; + dcfg.clk_root = clk_root; + ret = q6afe_set_digital_codec_core_clock(port, &dcfg); + break; + case LPAIF_BIT_CLK: + ccfg.i2s_cfg_minor_version = AFE_API_VERSION_I2S_CONFIG; + ccfg.clk_val1 = freq; + ccfg.clk_src = clk_src; + ccfg.clk_root = clk_root; + ccfg.clk_set_mode = Q6AFE_LPASS_MODE_CLK1_VALID; + ret = q6afe_port_set_lpass_clock(port, &ccfg); + break; + + case LPAIF_OSR_CLK: + ccfg.i2s_cfg_minor_version = AFE_API_VERSION_I2S_CONFIG; + ccfg.clk_val2 = freq; + ccfg.clk_src = clk_src; + ccfg.clk_root = clk_root; + ccfg.clk_set_mode = Q6AFE_LPASS_MODE_CLK2_VALID; + ret = q6afe_port_set_lpass_clock(port, &ccfg); + break; + case Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT ... Q6AFE_LPASS_CLK_ID_QUI_MI2S_OSR: + case Q6AFE_LPASS_CLK_ID_MCLK_1 ... Q6AFE_LPASS_CLK_ID_INT_MCLK_1: + case Q6AFE_LPASS_CLK_ID_PRI_TDM_IBIT ... Q6AFE_LPASS_CLK_ID_QUIN_TDM_EBIT: + case Q6AFE_LPASS_CLK_ID_WSA_CORE_MCLK ... Q6AFE_LPASS_CLK_ID_VA_CORE_2X_MCLK: + cset.clk_set_minor_version = AFE_API_VERSION_CLOCK_SET; + cset.clk_id = clk_id; + cset.clk_freq_in_hz = freq; + cset.clk_attri = clk_src; + cset.clk_root = clk_root; + cset.enable = !!freq; + ret = q6afe_set_lpass_clock_v2(port, &cset); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} +EXPORT_SYMBOL_GPL(q6afe_port_set_sysclk); + +/** + * q6afe_port_stop() - Stop a afe port + * + * @port: Instance of port to stop + * + * Return: Will be an negative on packet size on success. + */ +int q6afe_port_stop(struct q6afe_port *port) +{ + struct afe_port_cmd_device_stop *stop; + struct q6afe *afe = port->afe; + struct apr_pkt *pkt; + int port_id = port->id; + int ret = 0; + int index, pkt_size; + void *p; + + port_id = port->id; + index = port->token; + if (index < 0 || index >= AFE_PORT_MAX) { + dev_err(afe->dev, "AFE port index[%d] invalid!\n", index); + return -EINVAL; + } + + pkt_size = APR_HDR_SIZE + sizeof(*stop); + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + stop = p + APR_HDR_SIZE; + + pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + pkt->hdr.pkt_size = pkt_size; + pkt->hdr.src_port = 0; + pkt->hdr.dest_port = 0; + pkt->hdr.token = index; + pkt->hdr.opcode = AFE_PORT_CMD_DEVICE_STOP; + stop->port_id = port_id; + stop->reserved = 0; + + ret = afe_apr_send_pkt(afe, pkt, port, AFE_PORT_CMD_DEVICE_STOP); + if (ret) + dev_err(afe->dev, "AFE close failed %d\n", ret); + + kfree(pkt); + return ret; +} +EXPORT_SYMBOL_GPL(q6afe_port_stop); + +/** + * q6afe_slim_port_prepare() - Prepare slim afe port. + * + * @port: Instance of afe port + * @cfg: SLIM configuration for the afe port + * + */ +void q6afe_slim_port_prepare(struct q6afe_port *port, + struct q6afe_slim_cfg *cfg) +{ + union afe_port_config *pcfg = &port->port_cfg; + + pcfg->slim_cfg.sb_cfg_minor_version = AFE_API_VERSION_SLIMBUS_CONFIG; + pcfg->slim_cfg.sample_rate = cfg->sample_rate; + pcfg->slim_cfg.bit_width = cfg->bit_width; + pcfg->slim_cfg.num_channels = cfg->num_channels; + pcfg->slim_cfg.data_format = cfg->data_format; + pcfg->slim_cfg.shared_ch_mapping[0] = cfg->ch_mapping[0]; + pcfg->slim_cfg.shared_ch_mapping[1] = cfg->ch_mapping[1]; + pcfg->slim_cfg.shared_ch_mapping[2] = cfg->ch_mapping[2]; + pcfg->slim_cfg.shared_ch_mapping[3] = cfg->ch_mapping[3]; + +} +EXPORT_SYMBOL_GPL(q6afe_slim_port_prepare); + +/** + * q6afe_tdm_port_prepare() - Prepare tdm afe port. + * + * @port: Instance of afe port + * @cfg: TDM configuration for the afe port + * + */ +void q6afe_tdm_port_prepare(struct q6afe_port *port, + struct q6afe_tdm_cfg *cfg) +{ + union afe_port_config *pcfg = &port->port_cfg; + + pcfg->tdm_cfg.tdm_cfg_minor_version = AFE_API_VERSION_TDM_CONFIG; + pcfg->tdm_cfg.num_channels = cfg->num_channels; + pcfg->tdm_cfg.sample_rate = cfg->sample_rate; + pcfg->tdm_cfg.bit_width = cfg->bit_width; + pcfg->tdm_cfg.data_format = cfg->data_format; + pcfg->tdm_cfg.sync_mode = cfg->sync_mode; + pcfg->tdm_cfg.sync_src = cfg->sync_src; + pcfg->tdm_cfg.nslots_per_frame = cfg->nslots_per_frame; + + pcfg->tdm_cfg.slot_width = cfg->slot_width; + pcfg->tdm_cfg.slot_mask = cfg->slot_mask; + port->scfg = kzalloc(sizeof(*port->scfg), GFP_KERNEL); + if (!port->scfg) + return; + + port->scfg->minor_version = AFE_API_VERSION_SLOT_MAPPING_CONFIG; + port->scfg->num_channels = cfg->num_channels; + port->scfg->bitwidth = cfg->bit_width; + port->scfg->data_align_type = cfg->data_align_type; + memcpy(port->scfg->ch_mapping, cfg->ch_mapping, + sizeof(u16) * AFE_PORT_MAX_AUDIO_CHAN_CNT); +} +EXPORT_SYMBOL_GPL(q6afe_tdm_port_prepare); + +/** + * q6afe_hdmi_port_prepare() - Prepare hdmi afe port. + * + * @port: Instance of afe port + * @cfg: HDMI configuration for the afe port + * + */ +void q6afe_hdmi_port_prepare(struct q6afe_port *port, + struct q6afe_hdmi_cfg *cfg) +{ + union afe_port_config *pcfg = &port->port_cfg; + + pcfg->hdmi_multi_ch.hdmi_cfg_minor_version = + AFE_API_VERSION_HDMI_CONFIG; + pcfg->hdmi_multi_ch.datatype = cfg->datatype; + pcfg->hdmi_multi_ch.channel_allocation = cfg->channel_allocation; + pcfg->hdmi_multi_ch.sample_rate = cfg->sample_rate; + pcfg->hdmi_multi_ch.bit_width = cfg->bit_width; +} +EXPORT_SYMBOL_GPL(q6afe_hdmi_port_prepare); + +/** + * q6afe_i2s_port_prepare() - Prepare i2s afe port. + * + * @port: Instance of afe port + * @cfg: I2S configuration for the afe port + * Return: Will be an negative on error and zero on success. + */ +int q6afe_i2s_port_prepare(struct q6afe_port *port, struct q6afe_i2s_cfg *cfg) +{ + union afe_port_config *pcfg = &port->port_cfg; + struct device *dev = port->afe->dev; + int num_sd_lines; + + pcfg->i2s_cfg.i2s_cfg_minor_version = AFE_API_VERSION_I2S_CONFIG; + pcfg->i2s_cfg.sample_rate = cfg->sample_rate; + pcfg->i2s_cfg.bit_width = cfg->bit_width; + pcfg->i2s_cfg.data_format = AFE_LINEAR_PCM_DATA; + + switch (cfg->fmt & SND_SOC_DAIFMT_MASTER_MASK) { + case SND_SOC_DAIFMT_CBS_CFS: + pcfg->i2s_cfg.ws_src = AFE_PORT_CONFIG_I2S_WS_SRC_INTERNAL; + break; + case SND_SOC_DAIFMT_CBM_CFM: + /* CPU is slave */ + pcfg->i2s_cfg.ws_src = AFE_PORT_CONFIG_I2S_WS_SRC_EXTERNAL; + break; + default: + break; + } + + num_sd_lines = hweight_long(cfg->sd_line_mask); + + switch (num_sd_lines) { + case 0: + dev_err(dev, "no line is assigned\n"); + return -EINVAL; + case 1: + switch (cfg->sd_line_mask) { + case AFE_PORT_I2S_SD0_MASK: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD0; + break; + case AFE_PORT_I2S_SD1_MASK: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD1; + break; + case AFE_PORT_I2S_SD2_MASK: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD2; + break; + case AFE_PORT_I2S_SD3_MASK: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD3; + break; + default: + dev_err(dev, "Invalid SD lines\n"); + return -EINVAL; + } + break; + case 2: + switch (cfg->sd_line_mask) { + case AFE_PORT_I2S_SD0_1_MASK: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_QUAD01; + break; + case AFE_PORT_I2S_SD2_3_MASK: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_QUAD23; + break; + default: + dev_err(dev, "Invalid SD lines\n"); + return -EINVAL; + } + break; + case 3: + switch (cfg->sd_line_mask) { + case AFE_PORT_I2S_SD0_1_2_MASK: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_6CHS; + break; + default: + dev_err(dev, "Invalid SD lines\n"); + return -EINVAL; + } + break; + case 4: + switch (cfg->sd_line_mask) { + case AFE_PORT_I2S_SD0_1_2_3_MASK: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_8CHS; + + break; + default: + dev_err(dev, "Invalid SD lines\n"); + return -EINVAL; + } + break; + default: + dev_err(dev, "Invalid SD lines\n"); + return -EINVAL; + } + + switch (cfg->num_channels) { + case 1: + case 2: + switch (pcfg->i2s_cfg.channel_mode) { + case AFE_PORT_I2S_QUAD01: + case AFE_PORT_I2S_6CHS: + case AFE_PORT_I2S_8CHS: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD0; + break; + case AFE_PORT_I2S_QUAD23: + pcfg->i2s_cfg.channel_mode = AFE_PORT_I2S_SD2; + break; + } + + if (cfg->num_channels == 2) + pcfg->i2s_cfg.mono_stereo = AFE_PORT_I2S_STEREO; + else + pcfg->i2s_cfg.mono_stereo = AFE_PORT_I2S_MONO; + + break; + case 3: + case 4: + if (pcfg->i2s_cfg.channel_mode < AFE_PORT_I2S_QUAD01) { + dev_err(dev, "Invalid Channel mode\n"); + return -EINVAL; + } + break; + case 5: + case 6: + if (pcfg->i2s_cfg.channel_mode < AFE_PORT_I2S_6CHS) { + dev_err(dev, "Invalid Channel mode\n"); + return -EINVAL; + } + break; + case 7: + case 8: + if (pcfg->i2s_cfg.channel_mode < AFE_PORT_I2S_8CHS) { + dev_err(dev, "Invalid Channel mode\n"); + return -EINVAL; + } + break; + default: + break; + } + + return 0; +} +EXPORT_SYMBOL_GPL(q6afe_i2s_port_prepare); + +/** + * q6afe_dam_port_prepare() - Prepare dma afe port. + * + * @port: Instance of afe port + * @cfg: DMA configuration for the afe port + * + */ +void q6afe_cdc_dma_port_prepare(struct q6afe_port *port, + struct q6afe_cdc_dma_cfg *cfg) +{ + union afe_port_config *pcfg = &port->port_cfg; + struct afe_param_id_cdc_dma_cfg *dma_cfg = &pcfg->dma_cfg; + + dma_cfg->cdc_dma_cfg_minor_version = AFE_API_VERSION_CODEC_DMA_CONFIG; + dma_cfg->sample_rate = cfg->sample_rate; + dma_cfg->bit_width = cfg->bit_width; + dma_cfg->data_format = cfg->data_format; + dma_cfg->num_channels = cfg->num_channels; + if (!cfg->active_channels_mask) + dma_cfg->active_channels_mask = (1 << cfg->num_channels) - 1; +} +EXPORT_SYMBOL_GPL(q6afe_cdc_dma_port_prepare); +/** + * q6afe_port_start() - Start a afe port + * + * @port: Instance of port to start + * + * Return: Will be an negative on packet size on success. + */ +int q6afe_port_start(struct q6afe_port *port) +{ + struct afe_port_cmd_device_start *start; + struct q6afe *afe = port->afe; + int port_id = port->id; + int ret, param_id = port->cfg_type; + struct apr_pkt *pkt; + int pkt_size; + void *p; + + ret = q6afe_port_set_param_v2(port, &port->port_cfg, param_id, + AFE_MODULE_AUDIO_DEV_INTERFACE, + sizeof(port->port_cfg)); + if (ret) { + dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n", + port_id, ret); + return ret; + } + + if (port->scfg) { + ret = q6afe_port_set_param_v2(port, port->scfg, + AFE_PARAM_ID_PORT_SLOT_MAPPING_CONFIG, + AFE_MODULE_TDM, sizeof(*port->scfg)); + if (ret) { + dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n", + port_id, ret); + return ret; + } + } + + pkt_size = APR_HDR_SIZE + sizeof(*start); + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + start = p + APR_HDR_SIZE; + + pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + pkt->hdr.pkt_size = pkt_size; + pkt->hdr.src_port = 0; + pkt->hdr.dest_port = 0; + pkt->hdr.token = port->token; + pkt->hdr.opcode = AFE_PORT_CMD_DEVICE_START; + + start->port_id = port_id; + + ret = afe_apr_send_pkt(afe, pkt, port, AFE_PORT_CMD_DEVICE_START); + if (ret) + dev_err(afe->dev, "AFE enable for port 0x%x failed %d\n", + port_id, ret); + + kfree(pkt); + return ret; +} +EXPORT_SYMBOL_GPL(q6afe_port_start); + +/** + * q6afe_port_get_from_id() - Get port instance from a port id + * + * @dev: Pointer to afe child device. + * @id: port id + * + * Return: Will be an error pointer on error or a valid afe port + * on success. + */ +struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id) +{ + int port_id; + struct q6afe *afe = dev_get_drvdata(dev->parent); + struct q6afe_port *port; + unsigned long flags; + int cfg_type; + + if (id < 0 || id >= AFE_PORT_MAX) { + dev_err(dev, "AFE port token[%d] invalid!\n", id); + return ERR_PTR(-EINVAL); + } + + /* if port is multiple times bind/unbind before callback finishes */ + port = q6afe_find_port(afe, id); + if (port) { + dev_err(dev, "AFE Port already open\n"); + return port; + } + + port_id = port_maps[id].port_id; + + switch (port_id) { + case AFE_PORT_ID_MULTICHAN_HDMI_RX: + case AFE_PORT_ID_HDMI_OVER_DP_RX: + cfg_type = AFE_PARAM_ID_HDMI_CONFIG; + break; + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_TX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_TX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_TX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_TX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_TX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_TX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_TX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_0_RX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_1_RX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_2_RX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_3_RX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_4_RX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_5_RX: + case AFE_PORT_ID_SLIMBUS_MULTI_CHAN_6_RX: + cfg_type = AFE_PARAM_ID_SLIMBUS_CONFIG; + break; + + case AFE_PORT_ID_PRIMARY_MI2S_RX: + case AFE_PORT_ID_PRIMARY_MI2S_TX: + case AFE_PORT_ID_SECONDARY_MI2S_RX: + case AFE_PORT_ID_SECONDARY_MI2S_TX: + case AFE_PORT_ID_TERTIARY_MI2S_RX: + case AFE_PORT_ID_TERTIARY_MI2S_TX: + case AFE_PORT_ID_QUATERNARY_MI2S_RX: + case AFE_PORT_ID_QUATERNARY_MI2S_TX: + cfg_type = AFE_PARAM_ID_I2S_CONFIG; + break; + case AFE_PORT_ID_PRIMARY_TDM_RX ... AFE_PORT_ID_QUINARY_TDM_TX_7: + cfg_type = AFE_PARAM_ID_TDM_CONFIG; + break; + case AFE_PORT_ID_WSA_CODEC_DMA_RX_0 ... AFE_PORT_ID_RX_CODEC_DMA_RX_7: + cfg_type = AFE_PARAM_ID_CODEC_DMA_CONFIG; + break; + default: + dev_err(dev, "Invalid port id 0x%x\n", port_id); + return ERR_PTR(-EINVAL); + } + + port = kzalloc(sizeof(*port), GFP_KERNEL); + if (!port) + return ERR_PTR(-ENOMEM); + + init_waitqueue_head(&port->wait); + + port->token = id; + port->id = port_id; + port->afe = afe; + port->cfg_type = cfg_type; + kref_init(&port->refcount); + + spin_lock_irqsave(&afe->port_list_lock, flags); + list_add_tail(&port->node, &afe->port_list); + spin_unlock_irqrestore(&afe->port_list_lock, flags); + + return port; + +} +EXPORT_SYMBOL_GPL(q6afe_port_get_from_id); + +/** + * q6afe_port_put() - Release port reference + * + * @port: Instance of port to put + */ +void q6afe_port_put(struct q6afe_port *port) +{ + kref_put(&port->refcount, q6afe_port_free); +} +EXPORT_SYMBOL_GPL(q6afe_port_put); + +int q6afe_unvote_lpass_core_hw(struct device *dev, uint32_t hw_block_id, + uint32_t client_handle) +{ + struct q6afe *afe = dev_get_drvdata(dev->parent); + struct afe_cmd_remote_lpass_core_hw_devote_request *vote_cfg; + struct apr_pkt *pkt; + int ret = 0; + int pkt_size; + void *p; + + pkt_size = APR_HDR_SIZE + sizeof(*vote_cfg); + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + vote_cfg = p + APR_HDR_SIZE; + + pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + pkt->hdr.pkt_size = pkt_size; + pkt->hdr.src_port = 0; + pkt->hdr.dest_port = 0; + pkt->hdr.token = hw_block_id; + pkt->hdr.opcode = AFE_CMD_REMOTE_LPASS_CORE_HW_DEVOTE_REQUEST; + vote_cfg->hw_block_id = hw_block_id; + vote_cfg->client_handle = client_handle; + + ret = apr_send_pkt(afe->apr, pkt); + if (ret < 0) + dev_err(afe->dev, "AFE failed to unvote (%d)\n", hw_block_id); + + kfree(pkt); + return ret; +} +EXPORT_SYMBOL(q6afe_unvote_lpass_core_hw); + +int q6afe_vote_lpass_core_hw(struct device *dev, uint32_t hw_block_id, + char *client_name, uint32_t *client_handle) +{ + struct q6afe *afe = dev_get_drvdata(dev->parent); + struct afe_cmd_remote_lpass_core_hw_vote_request *vote_cfg; + struct apr_pkt *pkt; + int ret = 0; + int pkt_size; + void *p; + + pkt_size = APR_HDR_SIZE + sizeof(*vote_cfg); + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + vote_cfg = p + APR_HDR_SIZE; + + pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), + APR_PKT_VER); + pkt->hdr.pkt_size = pkt_size; + pkt->hdr.src_port = 0; + pkt->hdr.dest_port = 0; + pkt->hdr.token = hw_block_id; + pkt->hdr.opcode = AFE_CMD_REMOTE_LPASS_CORE_HW_VOTE_REQUEST; + vote_cfg->hw_block_id = hw_block_id; + strlcpy(vote_cfg->client_name, client_name, + sizeof(vote_cfg->client_name)); + + ret = afe_apr_send_pkt(afe, pkt, NULL, + AFE_CMD_RSP_REMOTE_LPASS_CORE_HW_VOTE_REQUEST); + if (ret) + dev_err(afe->dev, "AFE failed to vote (%d)\n", hw_block_id); + + + kfree(pkt); + return ret; +} +EXPORT_SYMBOL(q6afe_vote_lpass_core_hw); + +static int q6afe_probe(struct apr_device *adev) +{ + struct q6afe *afe; + struct device *dev = &adev->dev; + + afe = devm_kzalloc(dev, sizeof(*afe), GFP_KERNEL); + if (!afe) + return -ENOMEM; + + q6core_get_svc_api_info(adev->svc_id, &afe->ainfo); + afe->apr = adev; + mutex_init(&afe->lock); + init_waitqueue_head(&afe->wait); + afe->dev = dev; + INIT_LIST_HEAD(&afe->port_list); + spin_lock_init(&afe->port_list_lock); + + dev_set_drvdata(dev, afe); + + return of_platform_populate(dev->of_node, NULL, NULL, dev); +} + +static int q6afe_remove(struct apr_device *adev) +{ + of_platform_depopulate(&adev->dev); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id q6afe_device_id[] = { + { .compatible = "qcom,q6afe" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6afe_device_id); +#endif + +static struct apr_driver qcom_q6afe_driver = { + .probe = q6afe_probe, + .remove = q6afe_remove, + .callback = q6afe_callback, + .driver = { + .name = "qcom-q6afe", + .of_match_table = of_match_ptr(q6afe_device_id), + + }, +}; + +module_apr_driver(qcom_q6afe_driver); +MODULE_DESCRIPTION("Q6 Audio Front End"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h new file mode 100644 index 000000000..22e10269a --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6afe.h @@ -0,0 +1,242 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __Q6AFE_H__ +#define __Q6AFE_H__ + +#include <dt-bindings/sound/qcom,q6afe.h> + +#define AFE_PORT_MAX 127 + +#define MSM_AFE_PORT_TYPE_RX 0 +#define MSM_AFE_PORT_TYPE_TX 1 +#define AFE_MAX_PORTS AFE_PORT_MAX + +#define Q6AFE_MAX_MI2S_LINES 4 + +#define AFE_MAX_CHAN_COUNT 8 +#define AFE_PORT_MAX_AUDIO_CHAN_CNT 0x8 + +#define Q6AFE_LPASS_CLK_SRC_INTERNAL 1 +#define Q6AFE_LPASS_CLK_ROOT_DEFAULT 0 + +#define LPAIF_DIG_CLK 1 +#define LPAIF_BIT_CLK 2 +#define LPAIF_OSR_CLK 3 + +/* Clock ID for Primary I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT 0x100 +/* Clock ID for Primary I2S EBIT */ +#define Q6AFE_LPASS_CLK_ID_PRI_MI2S_EBIT 0x101 +/* Clock ID for Secondary I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_SEC_MI2S_IBIT 0x102 +/* Clock ID for Secondary I2S EBIT */ +#define Q6AFE_LPASS_CLK_ID_SEC_MI2S_EBIT 0x103 +/* Clock ID for Tertiary I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_TER_MI2S_IBIT 0x104 +/* Clock ID for Tertiary I2S EBIT */ +#define Q6AFE_LPASS_CLK_ID_TER_MI2S_EBIT 0x105 +/* Clock ID for Quartnery I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_QUAD_MI2S_IBIT 0x106 +/* Clock ID for Quartnery I2S EBIT */ +#define Q6AFE_LPASS_CLK_ID_QUAD_MI2S_EBIT 0x107 +/* Clock ID for Speaker I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_SPEAKER_I2S_IBIT 0x108 +/* Clock ID for Speaker I2S EBIT */ +#define Q6AFE_LPASS_CLK_ID_SPEAKER_I2S_EBIT 0x109 +/* Clock ID for Speaker I2S OSR */ +#define Q6AFE_LPASS_CLK_ID_SPEAKER_I2S_OSR 0x10A + +/* Clock ID for QUINARY I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_QUI_MI2S_IBIT 0x10B +/* Clock ID for QUINARY I2S EBIT */ +#define Q6AFE_LPASS_CLK_ID_QUI_MI2S_EBIT 0x10C +/* Clock ID for SENARY I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_SEN_MI2S_IBIT 0x10D +/* Clock ID for SENARY I2S EBIT */ +#define Q6AFE_LPASS_CLK_ID_SEN_MI2S_EBIT 0x10E +/* Clock ID for INT0 I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_INT0_MI2S_IBIT 0x10F +/* Clock ID for INT1 I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_INT1_MI2S_IBIT 0x110 +/* Clock ID for INT2 I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_INT2_MI2S_IBIT 0x111 +/* Clock ID for INT3 I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_INT3_MI2S_IBIT 0x112 +/* Clock ID for INT4 I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_INT4_MI2S_IBIT 0x113 +/* Clock ID for INT5 I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_INT5_MI2S_IBIT 0x114 +/* Clock ID for INT6 I2S IBIT */ +#define Q6AFE_LPASS_CLK_ID_INT6_MI2S_IBIT 0x115 + +/* Clock ID for QUINARY MI2S OSR CLK */ +#define Q6AFE_LPASS_CLK_ID_QUI_MI2S_OSR 0x116 + +/* Clock ID for Primary PCM IBIT */ +#define Q6AFE_LPASS_CLK_ID_PRI_PCM_IBIT 0x200 +/* Clock ID for Primary PCM EBIT */ +#define Q6AFE_LPASS_CLK_ID_PRI_PCM_EBIT 0x201 +/* Clock ID for Secondary PCM IBIT */ +#define Q6AFE_LPASS_CLK_ID_SEC_PCM_IBIT 0x202 +/* Clock ID for Secondary PCM EBIT */ +#define Q6AFE_LPASS_CLK_ID_SEC_PCM_EBIT 0x203 +/* Clock ID for Tertiary PCM IBIT */ +#define Q6AFE_LPASS_CLK_ID_TER_PCM_IBIT 0x204 +/* Clock ID for Tertiary PCM EBIT */ +#define Q6AFE_LPASS_CLK_ID_TER_PCM_EBIT 0x205 +/* Clock ID for Quartery PCM IBIT */ +#define Q6AFE_LPASS_CLK_ID_QUAD_PCM_IBIT 0x206 +/* Clock ID for Quartery PCM EBIT */ +#define Q6AFE_LPASS_CLK_ID_QUAD_PCM_EBIT 0x207 +/* Clock ID for Quinary PCM IBIT */ +#define Q6AFE_LPASS_CLK_ID_QUIN_PCM_IBIT 0x208 +/* Clock ID for Quinary PCM EBIT */ +#define Q6AFE_LPASS_CLK_ID_QUIN_PCM_EBIT 0x209 +/* Clock ID for QUINARY PCM OSR */ +#define Q6AFE_LPASS_CLK_ID_QUI_PCM_OSR 0x20A + +/** Clock ID for Primary TDM IBIT */ +#define Q6AFE_LPASS_CLK_ID_PRI_TDM_IBIT 0x200 +/** Clock ID for Primary TDM EBIT */ +#define Q6AFE_LPASS_CLK_ID_PRI_TDM_EBIT 0x201 +/** Clock ID for Secondary TDM IBIT */ +#define Q6AFE_LPASS_CLK_ID_SEC_TDM_IBIT 0x202 +/** Clock ID for Secondary TDM EBIT */ +#define Q6AFE_LPASS_CLK_ID_SEC_TDM_EBIT 0x203 +/** Clock ID for Tertiary TDM IBIT */ +#define Q6AFE_LPASS_CLK_ID_TER_TDM_IBIT 0x204 +/** Clock ID for Tertiary TDM EBIT */ +#define Q6AFE_LPASS_CLK_ID_TER_TDM_EBIT 0x205 +/** Clock ID for Quartery TDM IBIT */ +#define Q6AFE_LPASS_CLK_ID_QUAD_TDM_IBIT 0x206 +/** Clock ID for Quartery TDM EBIT */ +#define Q6AFE_LPASS_CLK_ID_QUAD_TDM_EBIT 0x207 +/** Clock ID for Quinary TDM IBIT */ +#define Q6AFE_LPASS_CLK_ID_QUIN_TDM_IBIT 0x208 +/** Clock ID for Quinary TDM EBIT */ +#define Q6AFE_LPASS_CLK_ID_QUIN_TDM_EBIT 0x209 +/** Clock ID for Quinary TDM OSR */ +#define Q6AFE_LPASS_CLK_ID_QUIN_TDM_OSR 0x20A + +/* Clock ID for MCLK1 */ +#define Q6AFE_LPASS_CLK_ID_MCLK_1 0x300 +/* Clock ID for MCLK2 */ +#define Q6AFE_LPASS_CLK_ID_MCLK_2 0x301 +/* Clock ID for MCLK3 */ +#define Q6AFE_LPASS_CLK_ID_MCLK_3 0x302 +/* Clock ID for MCLK4 */ +#define Q6AFE_LPASS_CLK_ID_MCLK_4 0x304 +/* Clock ID for Internal Digital Codec Core */ +#define Q6AFE_LPASS_CLK_ID_INTERNAL_DIGITAL_CODEC_CORE 0x303 +/* Clock ID for INT MCLK0 */ +#define Q6AFE_LPASS_CLK_ID_INT_MCLK_0 0x305 +/* Clock ID for INT MCLK1 */ +#define Q6AFE_LPASS_CLK_ID_INT_MCLK_1 0x306 + +#define Q6AFE_LPASS_CLK_ID_WSA_CORE_MCLK 0x309 +#define Q6AFE_LPASS_CLK_ID_WSA_CORE_NPL_MCLK 0x30a +#define Q6AFE_LPASS_CLK_ID_TX_CORE_MCLK 0x30c +#define Q6AFE_LPASS_CLK_ID_TX_CORE_NPL_MCLK 0x30d +#define Q6AFE_LPASS_CLK_ID_RX_CORE_MCLK 0x30e +#define Q6AFE_LPASS_CLK_ID_RX_CORE_NPL_MCLK 0x30f +#define Q6AFE_LPASS_CLK_ID_VA_CORE_MCLK 0x30b +#define Q6AFE_LPASS_CLK_ID_VA_CORE_2X_MCLK 0x310 + +#define Q6AFE_LPASS_CORE_AVTIMER_BLOCK 0x2 +#define Q6AFE_LPASS_CORE_HW_MACRO_BLOCK 0x3 +#define Q6AFE_LPASS_CORE_HW_DCODEC_BLOCK 0x4 + +/* Clock attribute for invalid use (reserved for internal usage) */ +#define Q6AFE_LPASS_CLK_ATTRIBUTE_INVALID 0x0 +/* Clock attribute for no couple case */ +#define Q6AFE_LPASS_CLK_ATTRIBUTE_COUPLE_NO 0x1 +/* Clock attribute for dividend couple case */ +#define Q6AFE_LPASS_CLK_ATTRIBUTE_COUPLE_DIVIDEND 0x2 +/* Clock attribute for divisor couple case */ +#define Q6AFE_LPASS_CLK_ATTRIBUTE_COUPLE_DIVISOR 0x3 +/* Clock attribute for invert and no couple case */ +#define Q6AFE_LPASS_CLK_ATTRIBUTE_INVERT_COUPLE_NO 0x4 + +#define Q6AFE_CMAP_INVALID 0xFFFF + +struct q6afe_hdmi_cfg { + u16 datatype; + u16 channel_allocation; + u32 sample_rate; + u16 bit_width; +}; + +struct q6afe_slim_cfg { + u32 sample_rate; + u16 bit_width; + u16 data_format; + u16 num_channels; + u8 ch_mapping[AFE_MAX_CHAN_COUNT]; +}; + +struct q6afe_i2s_cfg { + u32 sample_rate; + u16 bit_width; + u16 data_format; + u16 num_channels; + u32 sd_line_mask; + int fmt; +}; + +struct q6afe_tdm_cfg { + u16 num_channels; + u32 sample_rate; + u16 bit_width; + u16 data_format; + u16 sync_mode; + u16 sync_src; + u16 nslots_per_frame; + u16 slot_width; + u16 slot_mask; + u32 data_align_type; + u16 ch_mapping[AFE_MAX_CHAN_COUNT]; +}; + +struct q6afe_cdc_dma_cfg { + u16 sample_rate; + u16 bit_width; + u16 data_format; + u16 num_channels; + u16 active_channels_mask; +}; + + +struct q6afe_port_config { + struct q6afe_hdmi_cfg hdmi; + struct q6afe_slim_cfg slim; + struct q6afe_i2s_cfg i2s_cfg; + struct q6afe_tdm_cfg tdm; + struct q6afe_cdc_dma_cfg dma_cfg; +}; + +struct q6afe_port; + +struct q6afe_port *q6afe_port_get_from_id(struct device *dev, int id); +int q6afe_port_start(struct q6afe_port *port); +int q6afe_port_stop(struct q6afe_port *port); +void q6afe_port_put(struct q6afe_port *port); +int q6afe_get_port_id(int index); +void q6afe_hdmi_port_prepare(struct q6afe_port *port, + struct q6afe_hdmi_cfg *cfg); +void q6afe_slim_port_prepare(struct q6afe_port *port, + struct q6afe_slim_cfg *cfg); +int q6afe_i2s_port_prepare(struct q6afe_port *port, struct q6afe_i2s_cfg *cfg); +void q6afe_tdm_port_prepare(struct q6afe_port *port, struct q6afe_tdm_cfg *cfg); +void q6afe_cdc_dma_port_prepare(struct q6afe_port *port, + struct q6afe_cdc_dma_cfg *cfg); + +int q6afe_port_set_sysclk(struct q6afe_port *port, int clk_id, + int clk_src, int clk_root, + unsigned int freq, int dir); +int q6afe_set_lpass_clock(struct device *dev, int clk_id, int clk_src, + int clk_root, unsigned int freq); +int q6afe_vote_lpass_core_hw(struct device *dev, uint32_t hw_block_id, + char *client_name, uint32_t *client_handle); +int q6afe_unvote_lpass_core_hw(struct device *dev, uint32_t hw_block_id, + uint32_t client_handle); +#endif /* __Q6AFE_H__ */ diff --git a/sound/soc/qcom/qdsp6/q6asm-dai.c b/sound/soc/qcom/qdsp6/q6asm-dai.c new file mode 100644 index 000000000..84cf190aa --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6asm-dai.c @@ -0,0 +1,1383 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2018, Linaro Limited + +#include <linux/init.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <linux/spinlock.h> +#include <sound/compress_driver.h> +#include <asm/dma.h> +#include <linux/dma-mapping.h> +#include <linux/of_device.h> +#include <sound/pcm_params.h> +#include "q6asm.h" +#include "q6routing.h" +#include "q6dsp-errno.h" + +#define DRV_NAME "q6asm-fe-dai" + +#define PLAYBACK_MIN_NUM_PERIODS 2 +#define PLAYBACK_MAX_NUM_PERIODS 8 +#define PLAYBACK_MAX_PERIOD_SIZE 65536 +#define PLAYBACK_MIN_PERIOD_SIZE 128 +#define CAPTURE_MIN_NUM_PERIODS 2 +#define CAPTURE_MAX_NUM_PERIODS 8 +#define CAPTURE_MAX_PERIOD_SIZE 4096 +#define CAPTURE_MIN_PERIOD_SIZE 320 +#define SID_MASK_DEFAULT 0xF + +/* Default values used if user space does not set */ +#define COMPR_PLAYBACK_MIN_FRAGMENT_SIZE (8 * 1024) +#define COMPR_PLAYBACK_MAX_FRAGMENT_SIZE (128 * 1024) +#define COMPR_PLAYBACK_MIN_NUM_FRAGMENTS (4) +#define COMPR_PLAYBACK_MAX_NUM_FRAGMENTS (16 * 4) + +#define ALAC_CH_LAYOUT_MONO ((101 << 16) | 1) +#define ALAC_CH_LAYOUT_STEREO ((101 << 16) | 2) + +enum stream_state { + Q6ASM_STREAM_IDLE = 0, + Q6ASM_STREAM_STOPPED, + Q6ASM_STREAM_RUNNING, +}; + +struct q6asm_dai_rtd { + struct snd_pcm_substream *substream; + struct snd_compr_stream *cstream; + struct snd_codec codec; + struct snd_dma_buffer dma_buffer; + spinlock_t lock; + phys_addr_t phys; + unsigned int pcm_size; + unsigned int pcm_count; + unsigned int pcm_irq_pos; /* IRQ position */ + unsigned int periods; + unsigned int bytes_sent; + unsigned int bytes_received; + unsigned int copied_total; + uint16_t bits_per_sample; + uint16_t source; /* Encoding source bit mask */ + struct audio_client *audio_client; + uint32_t next_track_stream_id; + bool next_track; + uint32_t stream_id; + uint16_t session_id; + enum stream_state state; + uint32_t initial_samples_drop; + uint32_t trailing_samples_drop; + bool notify_on_drain; +}; + +struct q6asm_dai_data { + struct snd_soc_dai_driver *dais; + int num_dais; + long long int sid; +}; + +static const struct snd_pcm_hardware q6asm_dai_hardware_capture = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE), + .rates = SNDRV_PCM_RATE_8000_48000, + .rate_min = 8000, + .rate_max = 48000, + .channels_min = 1, + .channels_max = 4, + .buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * + CAPTURE_MAX_PERIOD_SIZE, + .period_bytes_min = CAPTURE_MIN_PERIOD_SIZE, + .period_bytes_max = CAPTURE_MAX_PERIOD_SIZE, + .periods_min = CAPTURE_MIN_NUM_PERIODS, + .periods_max = CAPTURE_MAX_NUM_PERIODS, + .fifo_size = 0, +}; + +static struct snd_pcm_hardware q6asm_dai_hardware_playback = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), + .formats = (SNDRV_PCM_FMTBIT_S16_LE | + SNDRV_PCM_FMTBIT_S24_LE), + .rates = SNDRV_PCM_RATE_8000_192000, + .rate_min = 8000, + .rate_max = 192000, + .channels_min = 1, + .channels_max = 8, + .buffer_bytes_max = (PLAYBACK_MAX_NUM_PERIODS * + PLAYBACK_MAX_PERIOD_SIZE), + .period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE, + .period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE, + .periods_min = PLAYBACK_MIN_NUM_PERIODS, + .periods_max = PLAYBACK_MAX_NUM_PERIODS, + .fifo_size = 0, +}; + +#define Q6ASM_FEDAI_DRIVER(num) { \ + .playback = { \ + .stream_name = "MultiMedia"#num" Playback", \ + .rates = (SNDRV_PCM_RATE_8000_192000| \ + SNDRV_PCM_RATE_KNOT), \ + .formats = (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE), \ + .channels_min = 1, \ + .channels_max = 8, \ + .rate_min = 8000, \ + .rate_max = 192000, \ + }, \ + .capture = { \ + .stream_name = "MultiMedia"#num" Capture", \ + .rates = (SNDRV_PCM_RATE_8000_48000| \ + SNDRV_PCM_RATE_KNOT), \ + .formats = (SNDRV_PCM_FMTBIT_S16_LE | \ + SNDRV_PCM_FMTBIT_S24_LE), \ + .channels_min = 1, \ + .channels_max = 4, \ + .rate_min = 8000, \ + .rate_max = 48000, \ + }, \ + .name = "MultiMedia"#num, \ + .id = MSM_FRONTEND_DAI_MULTIMEDIA##num, \ + } + +/* Conventional and unconventional sample rate supported */ +static unsigned int supported_sample_rates[] = { + 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, + 88200, 96000, 176400, 192000 +}; + +static struct snd_pcm_hw_constraint_list constraints_sample_rates = { + .count = ARRAY_SIZE(supported_sample_rates), + .list = supported_sample_rates, + .mask = 0, +}; + +static const struct snd_compr_codec_caps q6asm_compr_caps = { + .num_descriptors = 1, + .descriptor[0].max_ch = 2, + .descriptor[0].sample_rates = { 8000, 11025, 12000, 16000, 22050, + 24000, 32000, 44100, 48000, 88200, + 96000, 176400, 192000 }, + .descriptor[0].num_sample_rates = 13, + .descriptor[0].bit_rate[0] = 320, + .descriptor[0].bit_rate[1] = 128, + .descriptor[0].num_bitrates = 2, + .descriptor[0].profiles = 0, + .descriptor[0].modes = SND_AUDIOCHANMODE_MP3_STEREO, + .descriptor[0].formats = 0, +}; + +static void event_handler(uint32_t opcode, uint32_t token, + void *payload, void *priv) +{ + struct q6asm_dai_rtd *prtd = priv; + struct snd_pcm_substream *substream = prtd->substream; + + switch (opcode) { + case ASM_CLIENT_EVENT_CMD_RUN_DONE: + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + q6asm_write_async(prtd->audio_client, prtd->stream_id, + prtd->pcm_count, 0, 0, 0); + break; + case ASM_CLIENT_EVENT_CMD_EOS_DONE: + prtd->state = Q6ASM_STREAM_STOPPED; + break; + case ASM_CLIENT_EVENT_DATA_WRITE_DONE: { + prtd->pcm_irq_pos += prtd->pcm_count; + snd_pcm_period_elapsed(substream); + if (prtd->state == Q6ASM_STREAM_RUNNING) + q6asm_write_async(prtd->audio_client, prtd->stream_id, + prtd->pcm_count, 0, 0, 0); + + break; + } + case ASM_CLIENT_EVENT_DATA_READ_DONE: + prtd->pcm_irq_pos += prtd->pcm_count; + snd_pcm_period_elapsed(substream); + if (prtd->state == Q6ASM_STREAM_RUNNING) + q6asm_read(prtd->audio_client, prtd->stream_id); + + break; + default: + break; + } +} + +static int q6asm_dai_prepare(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = asoc_substream_to_rtd(substream); + struct q6asm_dai_rtd *prtd = runtime->private_data; + struct q6asm_dai_data *pdata; + struct device *dev = component->dev; + int ret, i; + + pdata = snd_soc_component_get_drvdata(component); + if (!pdata) + return -EINVAL; + + if (!prtd || !prtd->audio_client) { + dev_err(dev, "%s: private data null or audio client freed\n", + __func__); + return -EINVAL; + } + + prtd->pcm_count = snd_pcm_lib_period_bytes(substream); + prtd->pcm_irq_pos = 0; + /* rate and channels are sent to audio driver */ + if (prtd->state) { + /* clear the previous setup if any */ + q6asm_cmd(prtd->audio_client, prtd->stream_id, CMD_CLOSE); + q6asm_unmap_memory_regions(substream->stream, + prtd->audio_client); + q6routing_stream_close(soc_prtd->dai_link->id, + substream->stream); + } + + ret = q6asm_map_memory_regions(substream->stream, prtd->audio_client, + prtd->phys, + (prtd->pcm_size / prtd->periods), + prtd->periods); + + if (ret < 0) { + dev_err(dev, "Audio Start: Buffer Allocation failed rc = %d\n", + ret); + return -ENOMEM; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = q6asm_open_write(prtd->audio_client, prtd->stream_id, + FORMAT_LINEAR_PCM, + 0, prtd->bits_per_sample, false); + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + ret = q6asm_open_read(prtd->audio_client, prtd->stream_id, + FORMAT_LINEAR_PCM, + prtd->bits_per_sample); + } + + if (ret < 0) { + dev_err(dev, "%s: q6asm_open_write failed\n", __func__); + goto open_err; + } + + prtd->session_id = q6asm_get_session_id(prtd->audio_client); + ret = q6routing_stream_open(soc_prtd->dai_link->id, LEGACY_PCM_MODE, + prtd->session_id, substream->stream); + if (ret) { + dev_err(dev, "%s: stream reg failed ret:%d\n", __func__, ret); + goto routing_err; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = q6asm_media_format_block_multi_ch_pcm( + prtd->audio_client, prtd->stream_id, + runtime->rate, runtime->channels, NULL, + prtd->bits_per_sample); + } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + ret = q6asm_enc_cfg_blk_pcm_format_support(prtd->audio_client, + prtd->stream_id, + runtime->rate, + runtime->channels, + prtd->bits_per_sample); + + /* Queue the buffers */ + for (i = 0; i < runtime->periods; i++) + q6asm_read(prtd->audio_client, prtd->stream_id); + + } + if (ret < 0) + dev_info(dev, "%s: CMD Format block failed\n", __func__); + else + prtd->state = Q6ASM_STREAM_RUNNING; + + return ret; + +routing_err: + q6asm_cmd(prtd->audio_client, prtd->stream_id, CMD_CLOSE); +open_err: + q6asm_unmap_memory_regions(substream->stream, prtd->audio_client); + q6asm_audio_client_free(prtd->audio_client); + prtd->audio_client = NULL; + + return ret; +} + +static int q6asm_dai_trigger(struct snd_soc_component *component, + struct snd_pcm_substream *substream, int cmd) +{ + int ret = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + struct q6asm_dai_rtd *prtd = runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = q6asm_run_nowait(prtd->audio_client, prtd->stream_id, + 0, 0, 0); + break; + case SNDRV_PCM_TRIGGER_STOP: + prtd->state = Q6ASM_STREAM_STOPPED; + ret = q6asm_cmd_nowait(prtd->audio_client, prtd->stream_id, + CMD_EOS); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = q6asm_cmd_nowait(prtd->audio_client, prtd->stream_id, + CMD_PAUSE); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int q6asm_dai_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(soc_prtd, 0); + struct q6asm_dai_rtd *prtd; + struct q6asm_dai_data *pdata; + struct device *dev = component->dev; + int ret = 0; + int stream_id; + + stream_id = cpu_dai->driver->id; + + pdata = snd_soc_component_get_drvdata(component); + if (!pdata) { + dev_err(dev, "Drv data not found ..\n"); + return -EINVAL; + } + + prtd = kzalloc(sizeof(struct q6asm_dai_rtd), GFP_KERNEL); + if (prtd == NULL) + return -ENOMEM; + + prtd->substream = substream; + prtd->audio_client = q6asm_audio_client_alloc(dev, + (q6asm_cb)event_handler, prtd, stream_id, + LEGACY_PCM_MODE); + if (IS_ERR(prtd->audio_client)) { + dev_info(dev, "%s: Could not allocate memory\n", __func__); + ret = PTR_ERR(prtd->audio_client); + kfree(prtd); + return ret; + } + + /* DSP expects stream id from 1 */ + prtd->stream_id = 1; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + runtime->hw = q6asm_dai_hardware_playback; + else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + runtime->hw = q6asm_dai_hardware_capture; + + ret = snd_pcm_hw_constraint_list(runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + &constraints_sample_rates); + if (ret < 0) + dev_info(dev, "snd_pcm_hw_constraint_list failed\n"); + /* Ensure that buffer size is a multiple of period size */ + ret = snd_pcm_hw_constraint_integer(runtime, + SNDRV_PCM_HW_PARAM_PERIODS); + if (ret < 0) + dev_info(dev, "snd_pcm_hw_constraint_integer failed\n"); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, + PLAYBACK_MIN_NUM_PERIODS * PLAYBACK_MIN_PERIOD_SIZE, + PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE); + if (ret < 0) { + dev_err(dev, "constraint for buffer bytes min max ret = %d\n", + ret); + } + } + + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); + if (ret < 0) { + dev_err(dev, "constraint for period bytes step ret = %d\n", + ret); + } + ret = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32); + if (ret < 0) { + dev_err(dev, "constraint for buffer bytes step ret = %d\n", + ret); + } + + runtime->private_data = prtd; + + snd_soc_set_runtime_hwparams(substream, &q6asm_dai_hardware_playback); + + runtime->dma_bytes = q6asm_dai_hardware_playback.buffer_bytes_max; + + + if (pdata->sid < 0) + prtd->phys = substream->dma_buffer.addr; + else + prtd->phys = substream->dma_buffer.addr | (pdata->sid << 32); + + snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); + + return 0; +} + +static int q6asm_dai_close(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_soc_pcm_runtime *soc_prtd = asoc_substream_to_rtd(substream); + struct q6asm_dai_rtd *prtd = runtime->private_data; + + if (prtd->audio_client) { + if (prtd->state) + q6asm_cmd(prtd->audio_client, prtd->stream_id, + CMD_CLOSE); + + q6asm_unmap_memory_regions(substream->stream, + prtd->audio_client); + q6asm_audio_client_free(prtd->audio_client); + prtd->audio_client = NULL; + } + q6routing_stream_close(soc_prtd->dai_link->id, + substream->stream); + kfree(prtd); + return 0; +} + +static snd_pcm_uframes_t q6asm_dai_pointer(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + + struct snd_pcm_runtime *runtime = substream->runtime; + struct q6asm_dai_rtd *prtd = runtime->private_data; + + if (prtd->pcm_irq_pos >= prtd->pcm_size) + prtd->pcm_irq_pos = 0; + + return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); +} + +static int q6asm_dai_mmap(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct vm_area_struct *vma) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct device *dev = component->dev; + + return dma_mmap_coherent(dev, vma, + runtime->dma_area, runtime->dma_addr, + runtime->dma_bytes); +} + +static int q6asm_dai_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct q6asm_dai_rtd *prtd = runtime->private_data; + + prtd->pcm_size = params_buffer_bytes(params); + prtd->periods = params_periods(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + prtd->bits_per_sample = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + prtd->bits_per_sample = 24; + break; + } + + return 0; +} + +static void compress_event_handler(uint32_t opcode, uint32_t token, + void *payload, void *priv) +{ + struct q6asm_dai_rtd *prtd = priv; + struct snd_compr_stream *substream = prtd->cstream; + unsigned long flags; + u32 wflags = 0; + uint64_t avail; + uint32_t bytes_written, bytes_to_write; + bool is_last_buffer = false; + + switch (opcode) { + case ASM_CLIENT_EVENT_CMD_RUN_DONE: + spin_lock_irqsave(&prtd->lock, flags); + if (!prtd->bytes_sent) { + q6asm_stream_remove_initial_silence(prtd->audio_client, + prtd->stream_id, + prtd->initial_samples_drop); + + q6asm_write_async(prtd->audio_client, prtd->stream_id, + prtd->pcm_count, 0, 0, 0); + prtd->bytes_sent += prtd->pcm_count; + } + + spin_unlock_irqrestore(&prtd->lock, flags); + break; + + case ASM_CLIENT_EVENT_CMD_EOS_DONE: + spin_lock_irqsave(&prtd->lock, flags); + if (prtd->notify_on_drain) { + if (substream->partial_drain) { + /* + * Close old stream and make it stale, switch + * the active stream now! + */ + q6asm_cmd_nowait(prtd->audio_client, + prtd->stream_id, + CMD_CLOSE); + /* + * vaild stream ids start from 1, So we are + * toggling this between 1 and 2. + */ + prtd->stream_id = (prtd->stream_id == 1 ? 2 : 1); + } + + snd_compr_drain_notify(prtd->cstream); + prtd->notify_on_drain = false; + + } else { + prtd->state = Q6ASM_STREAM_STOPPED; + } + spin_unlock_irqrestore(&prtd->lock, flags); + break; + + case ASM_CLIENT_EVENT_DATA_WRITE_DONE: + spin_lock_irqsave(&prtd->lock, flags); + + bytes_written = token >> ASM_WRITE_TOKEN_LEN_SHIFT; + prtd->copied_total += bytes_written; + snd_compr_fragment_elapsed(substream); + + if (prtd->state != Q6ASM_STREAM_RUNNING) { + spin_unlock_irqrestore(&prtd->lock, flags); + break; + } + + avail = prtd->bytes_received - prtd->bytes_sent; + if (avail > prtd->pcm_count) { + bytes_to_write = prtd->pcm_count; + } else { + if (substream->partial_drain || prtd->notify_on_drain) + is_last_buffer = true; + bytes_to_write = avail; + } + + if (bytes_to_write) { + if (substream->partial_drain && is_last_buffer) { + wflags |= ASM_LAST_BUFFER_FLAG; + q6asm_stream_remove_trailing_silence(prtd->audio_client, + prtd->stream_id, + prtd->trailing_samples_drop); + } + + q6asm_write_async(prtd->audio_client, prtd->stream_id, + bytes_to_write, 0, 0, wflags); + + prtd->bytes_sent += bytes_to_write; + } + + if (prtd->notify_on_drain && is_last_buffer) + q6asm_cmd_nowait(prtd->audio_client, + prtd->stream_id, CMD_EOS); + + spin_unlock_irqrestore(&prtd->lock, flags); + break; + + default: + break; + } +} + +static int q6asm_dai_compr_open(struct snd_soc_component *component, + struct snd_compr_stream *stream) +{ + struct snd_soc_pcm_runtime *rtd = stream->private_data; + struct snd_compr_runtime *runtime = stream->runtime; + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct q6asm_dai_data *pdata; + struct device *dev = component->dev; + struct q6asm_dai_rtd *prtd; + int stream_id, size, ret; + + stream_id = cpu_dai->driver->id; + pdata = snd_soc_component_get_drvdata(component); + if (!pdata) { + dev_err(dev, "Drv data not found ..\n"); + return -EINVAL; + } + + prtd = kzalloc(sizeof(*prtd), GFP_KERNEL); + if (!prtd) + return -ENOMEM; + + /* DSP expects stream id from 1 */ + prtd->stream_id = 1; + + prtd->cstream = stream; + prtd->audio_client = q6asm_audio_client_alloc(dev, + (q6asm_cb)compress_event_handler, + prtd, stream_id, LEGACY_PCM_MODE); + if (IS_ERR(prtd->audio_client)) { + dev_err(dev, "Could not allocate memory\n"); + ret = PTR_ERR(prtd->audio_client); + goto free_prtd; + } + + size = COMPR_PLAYBACK_MAX_FRAGMENT_SIZE * + COMPR_PLAYBACK_MAX_NUM_FRAGMENTS; + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size, + &prtd->dma_buffer); + if (ret) { + dev_err(dev, "Cannot allocate buffer(s)\n"); + goto free_client; + } + + if (pdata->sid < 0) + prtd->phys = prtd->dma_buffer.addr; + else + prtd->phys = prtd->dma_buffer.addr | (pdata->sid << 32); + + snd_compr_set_runtime_buffer(stream, &prtd->dma_buffer); + spin_lock_init(&prtd->lock); + runtime->private_data = prtd; + + return 0; + +free_client: + q6asm_audio_client_free(prtd->audio_client); +free_prtd: + kfree(prtd); + + return ret; +} + +static int q6asm_dai_compr_free(struct snd_soc_component *component, + struct snd_compr_stream *stream) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6asm_dai_rtd *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = stream->private_data; + + if (prtd->audio_client) { + if (prtd->state) { + q6asm_cmd(prtd->audio_client, prtd->stream_id, + CMD_CLOSE); + if (prtd->next_track_stream_id) { + q6asm_cmd(prtd->audio_client, + prtd->next_track_stream_id, + CMD_CLOSE); + } + } + + snd_dma_free_pages(&prtd->dma_buffer); + q6asm_unmap_memory_regions(stream->direction, + prtd->audio_client); + q6asm_audio_client_free(prtd->audio_client); + prtd->audio_client = NULL; + } + q6routing_stream_close(rtd->dai_link->id, stream->direction); + kfree(prtd); + + return 0; +} + +static int __q6asm_dai_compr_set_codec_params(struct snd_soc_component *component, + struct snd_compr_stream *stream, + struct snd_codec *codec, + int stream_id) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6asm_dai_rtd *prtd = runtime->private_data; + struct q6asm_flac_cfg flac_cfg; + struct q6asm_wma_cfg wma_cfg; + struct q6asm_alac_cfg alac_cfg; + struct q6asm_ape_cfg ape_cfg; + unsigned int wma_v9 = 0; + struct device *dev = component->dev; + int ret; + union snd_codec_options *codec_options; + struct snd_dec_flac *flac; + struct snd_dec_wma *wma; + struct snd_dec_alac *alac; + struct snd_dec_ape *ape; + + codec_options = &(prtd->codec.options); + + memcpy(&prtd->codec, codec, sizeof(*codec)); + + switch (codec->id) { + case SND_AUDIOCODEC_FLAC: + + memset(&flac_cfg, 0x0, sizeof(struct q6asm_flac_cfg)); + flac = &codec_options->flac_d; + + flac_cfg.ch_cfg = codec->ch_in; + flac_cfg.sample_rate = codec->sample_rate; + flac_cfg.stream_info_present = 1; + flac_cfg.sample_size = flac->sample_size; + flac_cfg.min_blk_size = flac->min_blk_size; + flac_cfg.max_blk_size = flac->max_blk_size; + flac_cfg.max_frame_size = flac->max_frame_size; + flac_cfg.min_frame_size = flac->min_frame_size; + + ret = q6asm_stream_media_format_block_flac(prtd->audio_client, + stream_id, + &flac_cfg); + if (ret < 0) { + dev_err(dev, "FLAC CMD Format block failed:%d\n", ret); + return -EIO; + } + break; + + case SND_AUDIOCODEC_WMA: + wma = &codec_options->wma_d; + + memset(&wma_cfg, 0x0, sizeof(struct q6asm_wma_cfg)); + + wma_cfg.sample_rate = codec->sample_rate; + wma_cfg.num_channels = codec->ch_in; + wma_cfg.bytes_per_sec = codec->bit_rate / 8; + wma_cfg.block_align = codec->align; + wma_cfg.bits_per_sample = prtd->bits_per_sample; + wma_cfg.enc_options = wma->encoder_option; + wma_cfg.adv_enc_options = wma->adv_encoder_option; + wma_cfg.adv_enc_options2 = wma->adv_encoder_option2; + + if (wma_cfg.num_channels == 1) + wma_cfg.channel_mask = 4; /* Mono Center */ + else if (wma_cfg.num_channels == 2) + wma_cfg.channel_mask = 3; /* Stereo FL/FR */ + else + return -EINVAL; + + /* check the codec profile */ + switch (codec->profile) { + case SND_AUDIOPROFILE_WMA9: + wma_cfg.fmtag = 0x161; + wma_v9 = 1; + break; + + case SND_AUDIOPROFILE_WMA10: + wma_cfg.fmtag = 0x166; + break; + + case SND_AUDIOPROFILE_WMA9_PRO: + wma_cfg.fmtag = 0x162; + break; + + case SND_AUDIOPROFILE_WMA9_LOSSLESS: + wma_cfg.fmtag = 0x163; + break; + + case SND_AUDIOPROFILE_WMA10_LOSSLESS: + wma_cfg.fmtag = 0x167; + break; + + default: + dev_err(dev, "Unknown WMA profile:%x\n", + codec->profile); + return -EIO; + } + + if (wma_v9) + ret = q6asm_stream_media_format_block_wma_v9( + prtd->audio_client, stream_id, + &wma_cfg); + else + ret = q6asm_stream_media_format_block_wma_v10( + prtd->audio_client, stream_id, + &wma_cfg); + if (ret < 0) { + dev_err(dev, "WMA9 CMD failed:%d\n", ret); + return -EIO; + } + break; + + case SND_AUDIOCODEC_ALAC: + memset(&alac_cfg, 0x0, sizeof(alac_cfg)); + alac = &codec_options->alac_d; + + alac_cfg.sample_rate = codec->sample_rate; + alac_cfg.avg_bit_rate = codec->bit_rate; + alac_cfg.bit_depth = prtd->bits_per_sample; + alac_cfg.num_channels = codec->ch_in; + + alac_cfg.frame_length = alac->frame_length; + alac_cfg.pb = alac->pb; + alac_cfg.mb = alac->mb; + alac_cfg.kb = alac->kb; + alac_cfg.max_run = alac->max_run; + alac_cfg.compatible_version = alac->compatible_version; + alac_cfg.max_frame_bytes = alac->max_frame_bytes; + + switch (codec->ch_in) { + case 1: + alac_cfg.channel_layout_tag = ALAC_CH_LAYOUT_MONO; + break; + case 2: + alac_cfg.channel_layout_tag = ALAC_CH_LAYOUT_STEREO; + break; + } + ret = q6asm_stream_media_format_block_alac(prtd->audio_client, + stream_id, + &alac_cfg); + if (ret < 0) { + dev_err(dev, "ALAC CMD Format block failed:%d\n", ret); + return -EIO; + } + break; + + case SND_AUDIOCODEC_APE: + memset(&ape_cfg, 0x0, sizeof(ape_cfg)); + ape = &codec_options->ape_d; + + ape_cfg.sample_rate = codec->sample_rate; + ape_cfg.num_channels = codec->ch_in; + ape_cfg.bits_per_sample = prtd->bits_per_sample; + + ape_cfg.compatible_version = ape->compatible_version; + ape_cfg.compression_level = ape->compression_level; + ape_cfg.format_flags = ape->format_flags; + ape_cfg.blocks_per_frame = ape->blocks_per_frame; + ape_cfg.final_frame_blocks = ape->final_frame_blocks; + ape_cfg.total_frames = ape->total_frames; + ape_cfg.seek_table_present = ape->seek_table_present; + + ret = q6asm_stream_media_format_block_ape(prtd->audio_client, + stream_id, + &ape_cfg); + if (ret < 0) { + dev_err(dev, "APE CMD Format block failed:%d\n", ret); + return -EIO; + } + break; + + default: + break; + } + + return 0; +} + +static int q6asm_dai_compr_set_params(struct snd_soc_component *component, + struct snd_compr_stream *stream, + struct snd_compr_params *params) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6asm_dai_rtd *prtd = runtime->private_data; + struct snd_soc_pcm_runtime *rtd = stream->private_data; + int dir = stream->direction; + struct q6asm_dai_data *pdata; + struct device *dev = component->dev; + int ret; + + pdata = snd_soc_component_get_drvdata(component); + if (!pdata) + return -EINVAL; + + if (!prtd || !prtd->audio_client) { + dev_err(dev, "private data null or audio client freed\n"); + return -EINVAL; + } + + prtd->periods = runtime->fragments; + prtd->pcm_count = runtime->fragment_size; + prtd->pcm_size = runtime->fragments * runtime->fragment_size; + prtd->bits_per_sample = 16; + + if (dir == SND_COMPRESS_PLAYBACK) { + ret = q6asm_open_write(prtd->audio_client, prtd->stream_id, params->codec.id, + params->codec.profile, prtd->bits_per_sample, + true); + + if (ret < 0) { + dev_err(dev, "q6asm_open_write failed\n"); + q6asm_audio_client_free(prtd->audio_client); + prtd->audio_client = NULL; + return ret; + } + } + + prtd->session_id = q6asm_get_session_id(prtd->audio_client); + ret = q6routing_stream_open(rtd->dai_link->id, LEGACY_PCM_MODE, + prtd->session_id, dir); + if (ret) { + dev_err(dev, "Stream reg failed ret:%d\n", ret); + return ret; + } + + ret = __q6asm_dai_compr_set_codec_params(component, stream, + ¶ms->codec, + prtd->stream_id); + if (ret) { + dev_err(dev, "codec param setup failed ret:%d\n", ret); + return ret; + } + + ret = q6asm_map_memory_regions(dir, prtd->audio_client, prtd->phys, + (prtd->pcm_size / prtd->periods), + prtd->periods); + + if (ret < 0) { + dev_err(dev, "Buffer Mapping failed ret:%d\n", ret); + return -ENOMEM; + } + + prtd->state = Q6ASM_STREAM_RUNNING; + + return 0; +} + +static int q6asm_dai_compr_set_metadata(struct snd_soc_component *component, + struct snd_compr_stream *stream, + struct snd_compr_metadata *metadata) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6asm_dai_rtd *prtd = runtime->private_data; + int ret = 0; + + switch (metadata->key) { + case SNDRV_COMPRESS_ENCODER_PADDING: + prtd->trailing_samples_drop = metadata->value[0]; + break; + case SNDRV_COMPRESS_ENCODER_DELAY: + prtd->initial_samples_drop = metadata->value[0]; + if (prtd->next_track_stream_id) { + ret = q6asm_open_write(prtd->audio_client, + prtd->next_track_stream_id, + prtd->codec.id, + prtd->codec.profile, + prtd->bits_per_sample, + true); + if (ret < 0) { + dev_err(component->dev, "q6asm_open_write failed\n"); + return ret; + } + ret = __q6asm_dai_compr_set_codec_params(component, stream, + &prtd->codec, + prtd->next_track_stream_id); + if (ret < 0) { + dev_err(component->dev, "q6asm_open_write failed\n"); + return ret; + } + + ret = q6asm_stream_remove_initial_silence(prtd->audio_client, + prtd->next_track_stream_id, + prtd->initial_samples_drop); + prtd->next_track_stream_id = 0; + + } + + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int q6asm_dai_compr_trigger(struct snd_soc_component *component, + struct snd_compr_stream *stream, int cmd) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6asm_dai_rtd *prtd = runtime->private_data; + int ret = 0; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: + ret = q6asm_run_nowait(prtd->audio_client, prtd->stream_id, + 0, 0, 0); + break; + case SNDRV_PCM_TRIGGER_STOP: + prtd->state = Q6ASM_STREAM_STOPPED; + ret = q6asm_cmd_nowait(prtd->audio_client, prtd->stream_id, + CMD_EOS); + break; + case SNDRV_PCM_TRIGGER_SUSPEND: + case SNDRV_PCM_TRIGGER_PAUSE_PUSH: + ret = q6asm_cmd_nowait(prtd->audio_client, prtd->stream_id, + CMD_PAUSE); + break; + case SND_COMPR_TRIGGER_NEXT_TRACK: + prtd->next_track = true; + prtd->next_track_stream_id = (prtd->stream_id == 1 ? 2 : 1); + break; + case SND_COMPR_TRIGGER_DRAIN: + case SND_COMPR_TRIGGER_PARTIAL_DRAIN: + prtd->notify_on_drain = true; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int q6asm_dai_compr_pointer(struct snd_soc_component *component, + struct snd_compr_stream *stream, + struct snd_compr_tstamp *tstamp) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6asm_dai_rtd *prtd = runtime->private_data; + unsigned long flags; + + spin_lock_irqsave(&prtd->lock, flags); + + tstamp->copied_total = prtd->copied_total; + tstamp->byte_offset = prtd->copied_total % prtd->pcm_size; + + spin_unlock_irqrestore(&prtd->lock, flags); + + return 0; +} + +static int q6asm_compr_copy(struct snd_soc_component *component, + struct snd_compr_stream *stream, char __user *buf, + size_t count) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6asm_dai_rtd *prtd = runtime->private_data; + unsigned long flags; + u32 wflags = 0; + int avail, bytes_in_flight = 0; + void *dstn; + size_t copy; + u32 app_pointer; + u32 bytes_received; + + bytes_received = prtd->bytes_received; + + /** + * Make sure that next track data pointer is aligned at 32 bit boundary + * This is a Mandatory requirement from DSP data buffers alignment + */ + if (prtd->next_track) + bytes_received = ALIGN(prtd->bytes_received, prtd->pcm_count); + + app_pointer = bytes_received/prtd->pcm_size; + app_pointer = bytes_received - (app_pointer * prtd->pcm_size); + dstn = prtd->dma_buffer.area + app_pointer; + + if (count < prtd->pcm_size - app_pointer) { + if (copy_from_user(dstn, buf, count)) + return -EFAULT; + } else { + copy = prtd->pcm_size - app_pointer; + if (copy_from_user(dstn, buf, copy)) + return -EFAULT; + if (copy_from_user(prtd->dma_buffer.area, buf + copy, + count - copy)) + return -EFAULT; + } + + spin_lock_irqsave(&prtd->lock, flags); + + bytes_in_flight = prtd->bytes_received - prtd->copied_total; + + if (prtd->next_track) { + prtd->next_track = false; + prtd->copied_total = ALIGN(prtd->copied_total, prtd->pcm_count); + prtd->bytes_sent = ALIGN(prtd->bytes_sent, prtd->pcm_count); + } + + prtd->bytes_received = bytes_received + count; + + /* Kick off the data to dsp if its starving!! */ + if (prtd->state == Q6ASM_STREAM_RUNNING && (bytes_in_flight == 0)) { + uint32_t bytes_to_write = prtd->pcm_count; + + avail = prtd->bytes_received - prtd->bytes_sent; + + if (avail < prtd->pcm_count) + bytes_to_write = avail; + + q6asm_write_async(prtd->audio_client, prtd->stream_id, + bytes_to_write, 0, 0, wflags); + prtd->bytes_sent += bytes_to_write; + } + + spin_unlock_irqrestore(&prtd->lock, flags); + + return count; +} + +static int q6asm_dai_compr_mmap(struct snd_soc_component *component, + struct snd_compr_stream *stream, + struct vm_area_struct *vma) +{ + struct snd_compr_runtime *runtime = stream->runtime; + struct q6asm_dai_rtd *prtd = runtime->private_data; + struct device *dev = component->dev; + + return dma_mmap_coherent(dev, vma, + prtd->dma_buffer.area, prtd->dma_buffer.addr, + prtd->dma_buffer.bytes); +} + +static int q6asm_dai_compr_get_caps(struct snd_soc_component *component, + struct snd_compr_stream *stream, + struct snd_compr_caps *caps) +{ + caps->direction = SND_COMPRESS_PLAYBACK; + caps->min_fragment_size = COMPR_PLAYBACK_MIN_FRAGMENT_SIZE; + caps->max_fragment_size = COMPR_PLAYBACK_MAX_FRAGMENT_SIZE; + caps->min_fragments = COMPR_PLAYBACK_MIN_NUM_FRAGMENTS; + caps->max_fragments = COMPR_PLAYBACK_MAX_NUM_FRAGMENTS; + caps->num_codecs = 5; + caps->codecs[0] = SND_AUDIOCODEC_MP3; + caps->codecs[1] = SND_AUDIOCODEC_FLAC; + caps->codecs[2] = SND_AUDIOCODEC_WMA; + caps->codecs[3] = SND_AUDIOCODEC_ALAC; + caps->codecs[4] = SND_AUDIOCODEC_APE; + + return 0; +} + +static int q6asm_dai_compr_get_codec_caps(struct snd_soc_component *component, + struct snd_compr_stream *stream, + struct snd_compr_codec_caps *codec) +{ + switch (codec->codec) { + case SND_AUDIOCODEC_MP3: + *codec = q6asm_compr_caps; + break; + default: + break; + } + + return 0; +} + +static struct snd_compress_ops q6asm_dai_compress_ops = { + .open = q6asm_dai_compr_open, + .free = q6asm_dai_compr_free, + .set_params = q6asm_dai_compr_set_params, + .set_metadata = q6asm_dai_compr_set_metadata, + .pointer = q6asm_dai_compr_pointer, + .trigger = q6asm_dai_compr_trigger, + .get_caps = q6asm_dai_compr_get_caps, + .get_codec_caps = q6asm_dai_compr_get_codec_caps, + .mmap = q6asm_dai_compr_mmap, + .copy = q6asm_compr_copy, +}; + +static int q6asm_dai_pcm_new(struct snd_soc_component *component, + struct snd_soc_pcm_runtime *rtd) +{ + struct snd_pcm_substream *psubstream, *csubstream; + struct snd_pcm *pcm = rtd->pcm; + struct device *dev; + int size, ret; + + dev = component->dev; + size = q6asm_dai_hardware_playback.buffer_bytes_max; + psubstream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; + if (psubstream) { + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size, + &psubstream->dma_buffer); + if (ret) { + dev_err(dev, "Cannot allocate buffer(s)\n"); + return ret; + } + } + + csubstream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + if (csubstream) { + ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size, + &csubstream->dma_buffer); + if (ret) { + dev_err(dev, "Cannot allocate buffer(s)\n"); + if (psubstream) + snd_dma_free_pages(&psubstream->dma_buffer); + return ret; + } + } + + return 0; +} + +static void q6asm_dai_pcm_free(struct snd_soc_component *component, + struct snd_pcm *pcm) +{ + struct snd_pcm_substream *substream; + int i; + + for (i = 0; i < ARRAY_SIZE(pcm->streams); i++) { + substream = pcm->streams[i].substream; + if (substream) { + snd_dma_free_pages(&substream->dma_buffer); + substream->dma_buffer.area = NULL; + substream->dma_buffer.addr = 0; + } + } +} + +static const struct snd_soc_dapm_widget q6asm_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("MM_DL1", "MultiMedia1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL2", "MultiMedia2 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL3", "MultiMedia3 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL4", "MultiMedia4 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL5", "MultiMedia5 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL6", "MultiMedia6 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL7", "MultiMedia7 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_IN("MM_DL8", "MultiMedia8 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL1", "MultiMedia1 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL2", "MultiMedia2 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL3", "MultiMedia3 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL4", "MultiMedia4 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL5", "MultiMedia5 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL6", "MultiMedia6 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL7", "MultiMedia7 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("MM_UL8", "MultiMedia8 Capture", 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_component_driver q6asm_fe_dai_component = { + .name = DRV_NAME, + .open = q6asm_dai_open, + .hw_params = q6asm_dai_hw_params, + .close = q6asm_dai_close, + .prepare = q6asm_dai_prepare, + .trigger = q6asm_dai_trigger, + .pointer = q6asm_dai_pointer, + .mmap = q6asm_dai_mmap, + .pcm_construct = q6asm_dai_pcm_new, + .pcm_destruct = q6asm_dai_pcm_free, + .compress_ops = &q6asm_dai_compress_ops, + .dapm_widgets = q6asm_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(q6asm_dapm_widgets), +}; + +static struct snd_soc_dai_driver q6asm_fe_dais_template[] = { + Q6ASM_FEDAI_DRIVER(1), + Q6ASM_FEDAI_DRIVER(2), + Q6ASM_FEDAI_DRIVER(3), + Q6ASM_FEDAI_DRIVER(4), + Q6ASM_FEDAI_DRIVER(5), + Q6ASM_FEDAI_DRIVER(6), + Q6ASM_FEDAI_DRIVER(7), + Q6ASM_FEDAI_DRIVER(8), +}; + +static int of_q6asm_parse_dai_data(struct device *dev, + struct q6asm_dai_data *pdata) +{ + struct snd_soc_dai_driver *dai_drv; + struct snd_soc_pcm_stream empty_stream; + struct device_node *node; + int ret, id, dir, idx = 0; + + + pdata->num_dais = of_get_child_count(dev->of_node); + if (!pdata->num_dais) { + dev_err(dev, "No dais found in DT\n"); + return -EINVAL; + } + + pdata->dais = devm_kcalloc(dev, pdata->num_dais, sizeof(*dai_drv), + GFP_KERNEL); + if (!pdata->dais) + return -ENOMEM; + + memset(&empty_stream, 0, sizeof(empty_stream)); + + for_each_child_of_node(dev->of_node, node) { + ret = of_property_read_u32(node, "reg", &id); + if (ret || id >= MAX_SESSIONS || id < 0) { + dev_err(dev, "valid dai id not found:%d\n", ret); + continue; + } + + dai_drv = &pdata->dais[idx++]; + *dai_drv = q6asm_fe_dais_template[id]; + + ret = of_property_read_u32(node, "direction", &dir); + if (ret) + continue; + + if (dir == Q6ASM_DAI_RX) + dai_drv->capture = empty_stream; + else if (dir == Q6ASM_DAI_TX) + dai_drv->playback = empty_stream; + + if (of_property_read_bool(node, "is-compress-dai")) + dai_drv->compress_new = snd_soc_new_compress; + } + + return 0; +} + +static int q6asm_dai_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + struct of_phandle_args args; + struct q6asm_dai_data *pdata; + int rc; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + rc = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args); + if (rc < 0) + pdata->sid = -1; + else + pdata->sid = args.args[0] & SID_MASK_DEFAULT; + + dev_set_drvdata(dev, pdata); + + rc = of_q6asm_parse_dai_data(dev, pdata); + if (rc) + return rc; + + return devm_snd_soc_register_component(dev, &q6asm_fe_dai_component, + pdata->dais, pdata->num_dais); +} + +#ifdef CONFIG_OF +static const struct of_device_id q6asm_dai_device_id[] = { + { .compatible = "qcom,q6asm-dais" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6asm_dai_device_id); +#endif + +static struct platform_driver q6asm_dai_platform_driver = { + .driver = { + .name = "q6asm-dai", + .of_match_table = of_match_ptr(q6asm_dai_device_id), + }, + .probe = q6asm_dai_probe, +}; +module_platform_driver(q6asm_dai_platform_driver); + +MODULE_DESCRIPTION("Q6ASM dai driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6asm.c b/sound/soc/qcom/qdsp6/q6asm.c new file mode 100644 index 000000000..c547c560c --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6asm.c @@ -0,0 +1,1760 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2018, Linaro Limited + +#include <linux/mutex.h> +#include <linux/wait.h> +#include <linux/module.h> +#include <linux/soc/qcom/apr.h> +#include <linux/device.h> +#include <linux/of_platform.h> +#include <linux/spinlock.h> +#include <linux/kref.h> +#include <linux/of.h> +#include <uapi/sound/asound.h> +#include <uapi/sound/compress_params.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include "q6asm.h" +#include "q6core.h" +#include "q6dsp-errno.h" +#include "q6dsp-common.h" + +#define ASM_STREAM_CMD_CLOSE 0x00010BCD +#define ASM_STREAM_CMD_FLUSH 0x00010BCE +#define ASM_SESSION_CMD_PAUSE 0x00010BD3 +#define ASM_DATA_CMD_EOS 0x00010BDB +#define ASM_DATA_EVENT_RENDERED_EOS 0x00010C1C +#define ASM_NULL_POPP_TOPOLOGY 0x00010C68 +#define ASM_STREAM_CMD_FLUSH_READBUFS 0x00010C09 +#define ASM_STREAM_CMD_SET_ENCDEC_PARAM 0x00010C10 +#define ASM_STREAM_POSTPROC_TOPO_ID_NONE 0x00010C68 +#define ASM_CMD_SHARED_MEM_MAP_REGIONS 0x00010D92 +#define ASM_CMDRSP_SHARED_MEM_MAP_REGIONS 0x00010D93 +#define ASM_CMD_SHARED_MEM_UNMAP_REGIONS 0x00010D94 +#define ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2 0x00010D98 +#define ASM_DATA_EVENT_WRITE_DONE_V2 0x00010D99 +#define ASM_PARAM_ID_ENCDEC_ENC_CFG_BLK_V2 0x00010DA3 +#define ASM_SESSION_CMD_RUN_V2 0x00010DAA +#define ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2 0x00010DA5 +#define ASM_MEDIA_FMT_MP3 0x00010BE9 +#define ASM_MEDIA_FMT_FLAC 0x00010C16 +#define ASM_MEDIA_FMT_WMA_V9 0x00010DA8 +#define ASM_MEDIA_FMT_WMA_V10 0x00010DA7 +#define ASM_DATA_CMD_WRITE_V2 0x00010DAB +#define ASM_DATA_CMD_READ_V2 0x00010DAC +#define ASM_SESSION_CMD_SUSPEND 0x00010DEC +#define ASM_STREAM_CMD_OPEN_WRITE_V3 0x00010DB3 +#define ASM_STREAM_CMD_OPEN_READ_V3 0x00010DB4 +#define ASM_DATA_EVENT_READ_DONE_V2 0x00010D9A +#define ASM_STREAM_CMD_OPEN_READWRITE_V2 0x00010D8D +#define ASM_MEDIA_FMT_ALAC 0x00012f31 +#define ASM_MEDIA_FMT_APE 0x00012f32 +#define ASM_DATA_CMD_REMOVE_INITIAL_SILENCE 0x00010D67 +#define ASM_DATA_CMD_REMOVE_TRAILING_SILENCE 0x00010D68 + + +#define ASM_LEGACY_STREAM_SESSION 0 +/* Bit shift for the stream_perf_mode subfield. */ +#define ASM_SHIFT_STREAM_PERF_MODE_FLAG_IN_OPEN_READ 29 +#define ASM_END_POINT_DEVICE_MATRIX 0 +#define ASM_DEFAULT_APP_TYPE 0 +#define ASM_SYNC_IO_MODE 0x0001 +#define ASM_ASYNC_IO_MODE 0x0002 +#define ASM_TUN_READ_IO_MODE 0x0004 /* tunnel read write mode */ +#define ASM_TUN_WRITE_IO_MODE 0x0008 /* tunnel read write mode */ +#define ASM_SHIFT_GAPLESS_MODE_FLAG 31 +#define ADSP_MEMORY_MAP_SHMEM8_4K_POOL 3 + +struct avs_cmd_shared_mem_map_regions { + u16 mem_pool_id; + u16 num_regions; + u32 property_flag; +} __packed; + +struct avs_shared_map_region_payload { + u32 shm_addr_lsw; + u32 shm_addr_msw; + u32 mem_size_bytes; +} __packed; + +struct avs_cmd_shared_mem_unmap_regions { + u32 mem_map_handle; +} __packed; + +struct asm_data_cmd_media_fmt_update_v2 { + u32 fmt_blk_size; +} __packed; + +struct asm_multi_channel_pcm_fmt_blk_v2 { + struct asm_data_cmd_media_fmt_update_v2 fmt_blk; + u16 num_channels; + u16 bits_per_sample; + u32 sample_rate; + u16 is_signed; + u16 reserved; + u8 channel_mapping[PCM_MAX_NUM_CHANNEL]; +} __packed; + +struct asm_flac_fmt_blk_v2 { + struct asm_data_cmd_media_fmt_update_v2 fmt_blk; + u16 is_stream_info_present; + u16 num_channels; + u16 min_blk_size; + u16 max_blk_size; + u16 md5_sum[8]; + u32 sample_rate; + u32 min_frame_size; + u32 max_frame_size; + u16 sample_size; + u16 reserved; +} __packed; + +struct asm_wmastdv9_fmt_blk_v2 { + struct asm_data_cmd_media_fmt_update_v2 fmt_blk; + u16 fmtag; + u16 num_channels; + u32 sample_rate; + u32 bytes_per_sec; + u16 blk_align; + u16 bits_per_sample; + u32 channel_mask; + u16 enc_options; + u16 reserved; +} __packed; + +struct asm_wmaprov10_fmt_blk_v2 { + struct asm_data_cmd_media_fmt_update_v2 fmt_blk; + u16 fmtag; + u16 num_channels; + u32 sample_rate; + u32 bytes_per_sec; + u16 blk_align; + u16 bits_per_sample; + u32 channel_mask; + u16 enc_options; + u16 advanced_enc_options1; + u32 advanced_enc_options2; +} __packed; + +struct asm_alac_fmt_blk_v2 { + struct asm_data_cmd_media_fmt_update_v2 fmt_blk; + u32 frame_length; + u8 compatible_version; + u8 bit_depth; + u8 pb; + u8 mb; + u8 kb; + u8 num_channels; + u16 max_run; + u32 max_frame_bytes; + u32 avg_bit_rate; + u32 sample_rate; + u32 channel_layout_tag; +} __packed; + +struct asm_ape_fmt_blk_v2 { + struct asm_data_cmd_media_fmt_update_v2 fmt_blk; + u16 compatible_version; + u16 compression_level; + u32 format_flags; + u32 blocks_per_frame; + u32 final_frame_blocks; + u32 total_frames; + u16 bits_per_sample; + u16 num_channels; + u32 sample_rate; + u32 seek_table_present; +} __packed; + +struct asm_stream_cmd_set_encdec_param { + u32 param_id; + u32 param_size; +} __packed; + +struct asm_enc_cfg_blk_param_v2 { + u32 frames_per_buf; + u32 enc_cfg_blk_size; +} __packed; + +struct asm_multi_channel_pcm_enc_cfg_v2 { + struct asm_stream_cmd_set_encdec_param encdec; + struct asm_enc_cfg_blk_param_v2 encblk; + uint16_t num_channels; + uint16_t bits_per_sample; + uint32_t sample_rate; + uint16_t is_signed; + uint16_t reserved; + uint8_t channel_mapping[8]; +} __packed; + +struct asm_data_cmd_read_v2 { + u32 buf_addr_lsw; + u32 buf_addr_msw; + u32 mem_map_handle; + u32 buf_size; + u32 seq_id; +} __packed; + +struct asm_data_cmd_read_v2_done { + u32 status; + u32 buf_addr_lsw; + u32 buf_addr_msw; +}; + +struct asm_stream_cmd_open_read_v3 { + u32 mode_flags; + u32 src_endpointype; + u32 preprocopo_id; + u32 enc_cfg_id; + u16 bits_per_sample; + u16 reserved; +} __packed; + +struct asm_data_cmd_write_v2 { + u32 buf_addr_lsw; + u32 buf_addr_msw; + u32 mem_map_handle; + u32 buf_size; + u32 seq_id; + u32 timestamp_lsw; + u32 timestamp_msw; + u32 flags; +} __packed; + +struct asm_stream_cmd_open_write_v3 { + uint32_t mode_flags; + uint16_t sink_endpointype; + uint16_t bits_per_sample; + uint32_t postprocopo_id; + uint32_t dec_fmt_id; +} __packed; + +struct asm_session_cmd_run_v2 { + u32 flags; + u32 time_lsw; + u32 time_msw; +} __packed; + +struct audio_buffer { + phys_addr_t phys; + uint32_t size; /* size of buffer */ +}; + +struct audio_port_data { + struct audio_buffer *buf; + uint32_t num_periods; + uint32_t dsp_buf; + uint32_t mem_map_handle; +}; + +struct q6asm { + struct apr_device *adev; + struct device *dev; + struct q6core_svc_api_info ainfo; + wait_queue_head_t mem_wait; + spinlock_t slock; + struct audio_client *session[MAX_SESSIONS + 1]; +}; + +struct audio_client { + int session; + q6asm_cb cb; + void *priv; + uint32_t io_mode; + struct apr_device *adev; + struct mutex cmd_lock; + spinlock_t lock; + struct kref refcount; + /* idx:1 out port, 0: in port */ + struct audio_port_data port[2]; + wait_queue_head_t cmd_wait; + struct aprv2_ibasic_rsp_result_t result; + int perf_mode; + struct q6asm *q6asm; + struct device *dev; +}; + +static inline void q6asm_add_hdr(struct audio_client *ac, struct apr_hdr *hdr, + uint32_t pkt_size, bool cmd_flg, + uint32_t stream_id) +{ + hdr->hdr_field = APR_SEQ_CMD_HDR_FIELD; + hdr->src_port = ((ac->session << 8) & 0xFF00) | (stream_id); + hdr->dest_port = ((ac->session << 8) & 0xFF00) | (stream_id); + hdr->pkt_size = pkt_size; + if (cmd_flg) + hdr->token = ac->session; +} + +static int q6asm_apr_send_session_pkt(struct q6asm *a, struct audio_client *ac, + struct apr_pkt *pkt, uint32_t rsp_opcode) +{ + struct apr_hdr *hdr = &pkt->hdr; + int rc; + + mutex_lock(&ac->cmd_lock); + ac->result.opcode = 0; + ac->result.status = 0; + rc = apr_send_pkt(a->adev, pkt); + if (rc < 0) + goto err; + + if (rsp_opcode) + rc = wait_event_timeout(a->mem_wait, + (ac->result.opcode == hdr->opcode) || + (ac->result.opcode == rsp_opcode), + 5 * HZ); + else + rc = wait_event_timeout(a->mem_wait, + (ac->result.opcode == hdr->opcode), + 5 * HZ); + + if (!rc) { + dev_err(a->dev, "CMD %x timeout\n", hdr->opcode); + rc = -ETIMEDOUT; + } else if (ac->result.status > 0) { + dev_err(a->dev, "DSP returned error[%x]\n", + ac->result.status); + rc = -EINVAL; + } + +err: + mutex_unlock(&ac->cmd_lock); + return rc; +} + +static int __q6asm_memory_unmap(struct audio_client *ac, + phys_addr_t buf_add, int dir) +{ + struct avs_cmd_shared_mem_unmap_regions *mem_unmap; + struct q6asm *a = dev_get_drvdata(ac->dev->parent); + struct apr_pkt *pkt; + int rc, pkt_size; + void *p; + + if (ac->port[dir].mem_map_handle == 0) { + dev_err(ac->dev, "invalid mem handle\n"); + return -EINVAL; + } + + pkt_size = APR_HDR_SIZE + sizeof(*mem_unmap); + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + mem_unmap = p + APR_HDR_SIZE; + + pkt->hdr.hdr_field = APR_SEQ_CMD_HDR_FIELD; + pkt->hdr.src_port = 0; + pkt->hdr.dest_port = 0; + pkt->hdr.pkt_size = pkt_size; + pkt->hdr.token = ((ac->session << 8) | dir); + + pkt->hdr.opcode = ASM_CMD_SHARED_MEM_UNMAP_REGIONS; + mem_unmap->mem_map_handle = ac->port[dir].mem_map_handle; + + rc = q6asm_apr_send_session_pkt(a, ac, pkt, 0); + if (rc < 0) { + kfree(pkt); + return rc; + } + + ac->port[dir].mem_map_handle = 0; + + kfree(pkt); + return 0; +} + + +static void q6asm_audio_client_free_buf(struct audio_client *ac, + struct audio_port_data *port) +{ + unsigned long flags; + + spin_lock_irqsave(&ac->lock, flags); + port->num_periods = 0; + kfree(port->buf); + port->buf = NULL; + spin_unlock_irqrestore(&ac->lock, flags); +} + +/** + * q6asm_unmap_memory_regions() - unmap memory regions in the dsp. + * + * @dir: direction of audio stream + * @ac: audio client instanace + * + * Return: Will be an negative value on failure or zero on success + */ +int q6asm_unmap_memory_regions(unsigned int dir, struct audio_client *ac) +{ + struct audio_port_data *port; + int cnt = 0; + int rc = 0; + + port = &ac->port[dir]; + if (!port->buf) { + rc = -EINVAL; + goto err; + } + + cnt = port->num_periods - 1; + if (cnt >= 0) { + rc = __q6asm_memory_unmap(ac, port->buf[dir].phys, dir); + if (rc < 0) { + dev_err(ac->dev, "%s: Memory_unmap_regions failed %d\n", + __func__, rc); + goto err; + } + } + + q6asm_audio_client_free_buf(ac, port); + +err: + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_unmap_memory_regions); + +static int __q6asm_memory_map_regions(struct audio_client *ac, int dir, + size_t period_sz, unsigned int periods, + bool is_contiguous) +{ + struct avs_cmd_shared_mem_map_regions *cmd = NULL; + struct avs_shared_map_region_payload *mregions = NULL; + struct q6asm *a = dev_get_drvdata(ac->dev->parent); + struct audio_port_data *port = NULL; + struct audio_buffer *ab = NULL; + struct apr_pkt *pkt; + void *p; + unsigned long flags; + uint32_t num_regions, buf_sz; + int rc, i, pkt_size; + + if (is_contiguous) { + num_regions = 1; + buf_sz = period_sz * periods; + } else { + buf_sz = period_sz; + num_regions = periods; + } + + /* DSP expects size should be aligned to 4K */ + buf_sz = ALIGN(buf_sz, 4096); + + pkt_size = APR_HDR_SIZE + sizeof(*cmd) + + (sizeof(*mregions) * num_regions); + + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + cmd = p + APR_HDR_SIZE; + mregions = p + APR_HDR_SIZE + sizeof(*cmd); + + pkt->hdr.hdr_field = APR_SEQ_CMD_HDR_FIELD; + pkt->hdr.src_port = 0; + pkt->hdr.dest_port = 0; + pkt->hdr.pkt_size = pkt_size; + pkt->hdr.token = ((ac->session << 8) | dir); + pkt->hdr.opcode = ASM_CMD_SHARED_MEM_MAP_REGIONS; + + cmd->mem_pool_id = ADSP_MEMORY_MAP_SHMEM8_4K_POOL; + cmd->num_regions = num_regions; + cmd->property_flag = 0x00; + + spin_lock_irqsave(&ac->lock, flags); + port = &ac->port[dir]; + + for (i = 0; i < num_regions; i++) { + ab = &port->buf[i]; + mregions->shm_addr_lsw = lower_32_bits(ab->phys); + mregions->shm_addr_msw = upper_32_bits(ab->phys); + mregions->mem_size_bytes = buf_sz; + ++mregions; + } + spin_unlock_irqrestore(&ac->lock, flags); + + rc = q6asm_apr_send_session_pkt(a, ac, pkt, + ASM_CMDRSP_SHARED_MEM_MAP_REGIONS); + + kfree(pkt); + + return rc; +} + +/** + * q6asm_map_memory_regions() - map memory regions in the dsp. + * + * @dir: direction of audio stream + * @ac: audio client instanace + * @phys: physcial address that needs mapping. + * @period_sz: audio period size + * @periods: number of periods + * + * Return: Will be an negative value on failure or zero on success + */ +int q6asm_map_memory_regions(unsigned int dir, struct audio_client *ac, + phys_addr_t phys, + size_t period_sz, unsigned int periods) +{ + struct audio_buffer *buf; + unsigned long flags; + int cnt; + int rc; + + spin_lock_irqsave(&ac->lock, flags); + if (ac->port[dir].buf) { + dev_err(ac->dev, "Buffer already allocated\n"); + spin_unlock_irqrestore(&ac->lock, flags); + return 0; + } + + buf = kzalloc(((sizeof(struct audio_buffer)) * periods), GFP_ATOMIC); + if (!buf) { + spin_unlock_irqrestore(&ac->lock, flags); + return -ENOMEM; + } + + + ac->port[dir].buf = buf; + + buf[0].phys = phys; + buf[0].size = period_sz; + + for (cnt = 1; cnt < periods; cnt++) { + if (period_sz > 0) { + buf[cnt].phys = buf[0].phys + (cnt * period_sz); + buf[cnt].size = period_sz; + } + } + ac->port[dir].num_periods = periods; + + spin_unlock_irqrestore(&ac->lock, flags); + + rc = __q6asm_memory_map_regions(ac, dir, period_sz, periods, 1); + if (rc < 0) { + dev_err(ac->dev, "Memory_map_regions failed\n"); + q6asm_audio_client_free_buf(ac, &ac->port[dir]); + } + + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_map_memory_regions); + +static void q6asm_audio_client_release(struct kref *ref) +{ + struct audio_client *ac; + struct q6asm *a; + unsigned long flags; + + ac = container_of(ref, struct audio_client, refcount); + a = ac->q6asm; + + spin_lock_irqsave(&a->slock, flags); + a->session[ac->session] = NULL; + spin_unlock_irqrestore(&a->slock, flags); + + kfree(ac); +} + +/** + * q6asm_audio_client_free() - Freee allocated audio client + * + * @ac: audio client to free + */ +void q6asm_audio_client_free(struct audio_client *ac) +{ + kref_put(&ac->refcount, q6asm_audio_client_release); +} +EXPORT_SYMBOL_GPL(q6asm_audio_client_free); + +static struct audio_client *q6asm_get_audio_client(struct q6asm *a, + int session_id) +{ + struct audio_client *ac = NULL; + unsigned long flags; + + spin_lock_irqsave(&a->slock, flags); + if ((session_id <= 0) || (session_id > MAX_SESSIONS)) { + dev_err(a->dev, "invalid session: %d\n", session_id); + goto err; + } + + /* check for valid session */ + if (!a->session[session_id]) + goto err; + else if (a->session[session_id]->session != session_id) + goto err; + + ac = a->session[session_id]; + kref_get(&ac->refcount); +err: + spin_unlock_irqrestore(&a->slock, flags); + return ac; +} + +static int32_t q6asm_stream_callback(struct apr_device *adev, + struct apr_resp_pkt *data, + int session_id) +{ + struct q6asm *q6asm = dev_get_drvdata(&adev->dev); + struct aprv2_ibasic_rsp_result_t *result; + struct apr_hdr *hdr = &data->hdr; + struct audio_port_data *port; + struct audio_client *ac; + uint32_t client_event = 0; + int ret = 0; + + ac = q6asm_get_audio_client(q6asm, session_id); + if (!ac)/* Audio client might already be freed by now */ + return 0; + + result = data->payload; + + switch (hdr->opcode) { + case APR_BASIC_RSP_RESULT: + switch (result->opcode) { + case ASM_SESSION_CMD_PAUSE: + client_event = ASM_CLIENT_EVENT_CMD_PAUSE_DONE; + break; + case ASM_SESSION_CMD_SUSPEND: + client_event = ASM_CLIENT_EVENT_CMD_SUSPEND_DONE; + break; + case ASM_STREAM_CMD_FLUSH: + client_event = ASM_CLIENT_EVENT_CMD_FLUSH_DONE; + break; + case ASM_SESSION_CMD_RUN_V2: + client_event = ASM_CLIENT_EVENT_CMD_RUN_DONE; + break; + case ASM_STREAM_CMD_CLOSE: + client_event = ASM_CLIENT_EVENT_CMD_CLOSE_DONE; + break; + case ASM_STREAM_CMD_FLUSH_READBUFS: + client_event = ASM_CLIENT_EVENT_CMD_OUT_FLUSH_DONE; + break; + case ASM_STREAM_CMD_OPEN_WRITE_V3: + case ASM_STREAM_CMD_OPEN_READ_V3: + case ASM_STREAM_CMD_OPEN_READWRITE_V2: + case ASM_STREAM_CMD_SET_ENCDEC_PARAM: + case ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2: + case ASM_DATA_CMD_REMOVE_INITIAL_SILENCE: + case ASM_DATA_CMD_REMOVE_TRAILING_SILENCE: + if (result->status != 0) { + dev_err(ac->dev, + "cmd = 0x%x returned error = 0x%x\n", + result->opcode, result->status); + ac->result = *result; + wake_up(&ac->cmd_wait); + ret = 0; + goto done; + } + break; + default: + dev_err(ac->dev, "command[0x%x] not expecting rsp\n", + result->opcode); + break; + } + + ac->result = *result; + wake_up(&ac->cmd_wait); + + if (ac->cb) + ac->cb(client_event, hdr->token, + data->payload, ac->priv); + + ret = 0; + goto done; + + case ASM_DATA_EVENT_WRITE_DONE_V2: + client_event = ASM_CLIENT_EVENT_DATA_WRITE_DONE; + if (ac->io_mode & ASM_SYNC_IO_MODE) { + phys_addr_t phys; + unsigned long flags; + int token = hdr->token & ASM_WRITE_TOKEN_MASK; + + spin_lock_irqsave(&ac->lock, flags); + + port = &ac->port[SNDRV_PCM_STREAM_PLAYBACK]; + + if (!port->buf) { + spin_unlock_irqrestore(&ac->lock, flags); + ret = 0; + goto done; + } + + phys = port->buf[token].phys; + + if (lower_32_bits(phys) != result->opcode || + upper_32_bits(phys) != result->status) { + dev_err(ac->dev, "Expected addr %pa\n", + &port->buf[token].phys); + spin_unlock_irqrestore(&ac->lock, flags); + ret = -EINVAL; + goto done; + } + spin_unlock_irqrestore(&ac->lock, flags); + } + break; + case ASM_DATA_EVENT_READ_DONE_V2: + client_event = ASM_CLIENT_EVENT_DATA_READ_DONE; + if (ac->io_mode & ASM_SYNC_IO_MODE) { + struct asm_data_cmd_read_v2_done *done = data->payload; + unsigned long flags; + phys_addr_t phys; + + spin_lock_irqsave(&ac->lock, flags); + port = &ac->port[SNDRV_PCM_STREAM_CAPTURE]; + if (!port->buf) { + spin_unlock_irqrestore(&ac->lock, flags); + ret = 0; + goto done; + } + + phys = port->buf[hdr->token].phys; + + if (upper_32_bits(phys) != done->buf_addr_msw || + lower_32_bits(phys) != done->buf_addr_lsw) { + dev_err(ac->dev, "Expected addr %pa %08x-%08x\n", + &port->buf[hdr->token].phys, + done->buf_addr_lsw, + done->buf_addr_msw); + spin_unlock_irqrestore(&ac->lock, flags); + ret = -EINVAL; + goto done; + } + spin_unlock_irqrestore(&ac->lock, flags); + } + + break; + case ASM_DATA_EVENT_RENDERED_EOS: + client_event = ASM_CLIENT_EVENT_CMD_EOS_DONE; + break; + } + + if (ac->cb) + ac->cb(client_event, hdr->token, data->payload, ac->priv); + +done: + kref_put(&ac->refcount, q6asm_audio_client_release); + return ret; +} + +static int q6asm_srvc_callback(struct apr_device *adev, + struct apr_resp_pkt *data) +{ + struct q6asm *q6asm = dev_get_drvdata(&adev->dev); + struct aprv2_ibasic_rsp_result_t *result; + struct audio_port_data *port; + struct audio_client *ac = NULL; + struct apr_hdr *hdr = &data->hdr; + struct q6asm *a; + uint32_t sid = 0; + uint32_t dir = 0; + int session_id; + + session_id = (hdr->dest_port >> 8) & 0xFF; + if (session_id) + return q6asm_stream_callback(adev, data, session_id); + + sid = (hdr->token >> 8) & 0x0F; + ac = q6asm_get_audio_client(q6asm, sid); + if (!ac) { + dev_err(&adev->dev, "Audio Client not active\n"); + return 0; + } + + a = dev_get_drvdata(ac->dev->parent); + dir = (hdr->token & 0x0F); + port = &ac->port[dir]; + result = data->payload; + + switch (hdr->opcode) { + case APR_BASIC_RSP_RESULT: + switch (result->opcode) { + case ASM_CMD_SHARED_MEM_MAP_REGIONS: + case ASM_CMD_SHARED_MEM_UNMAP_REGIONS: + ac->result = *result; + wake_up(&a->mem_wait); + break; + default: + dev_err(&adev->dev, "command[0x%x] not expecting rsp\n", + result->opcode); + break; + } + goto done; + case ASM_CMDRSP_SHARED_MEM_MAP_REGIONS: + ac->result.status = 0; + ac->result.opcode = hdr->opcode; + port->mem_map_handle = result->opcode; + wake_up(&a->mem_wait); + break; + case ASM_CMD_SHARED_MEM_UNMAP_REGIONS: + ac->result.opcode = hdr->opcode; + ac->result.status = 0; + port->mem_map_handle = 0; + wake_up(&a->mem_wait); + break; + default: + dev_dbg(&adev->dev, "command[0x%x]success [0x%x]\n", + result->opcode, result->status); + break; + } + + if (ac->cb) + ac->cb(hdr->opcode, hdr->token, data->payload, ac->priv); + +done: + kref_put(&ac->refcount, q6asm_audio_client_release); + + return 0; +} + +/** + * q6asm_get_session_id() - get session id for audio client + * + * @c: audio client pointer + * + * Return: Will be an session id of the audio client. + */ +int q6asm_get_session_id(struct audio_client *c) +{ + return c->session; +} +EXPORT_SYMBOL_GPL(q6asm_get_session_id); + +/** + * q6asm_audio_client_alloc() - Allocate a new audio client + * + * @dev: Pointer to asm child device. + * @cb: event callback. + * @priv: private data associated with this client. + * @session_id: session id + * @perf_mode: performace mode for this client + * + * Return: Will be an error pointer on error or a valid audio client + * on success. + */ +struct audio_client *q6asm_audio_client_alloc(struct device *dev, q6asm_cb cb, + void *priv, int session_id, + int perf_mode) +{ + struct q6asm *a = dev_get_drvdata(dev->parent); + struct audio_client *ac; + unsigned long flags; + + ac = q6asm_get_audio_client(a, session_id + 1); + if (ac) { + dev_err(dev, "Audio Client already active\n"); + return ac; + } + + ac = kzalloc(sizeof(*ac), GFP_KERNEL); + if (!ac) + return ERR_PTR(-ENOMEM); + + spin_lock_irqsave(&a->slock, flags); + a->session[session_id + 1] = ac; + spin_unlock_irqrestore(&a->slock, flags); + ac->session = session_id + 1; + ac->cb = cb; + ac->dev = dev; + ac->q6asm = a; + ac->priv = priv; + ac->io_mode = ASM_SYNC_IO_MODE; + ac->perf_mode = perf_mode; + ac->adev = a->adev; + kref_init(&ac->refcount); + + init_waitqueue_head(&ac->cmd_wait); + mutex_init(&ac->cmd_lock); + spin_lock_init(&ac->lock); + + return ac; +} +EXPORT_SYMBOL_GPL(q6asm_audio_client_alloc); + +static int q6asm_ac_send_cmd_sync(struct audio_client *ac, struct apr_pkt *pkt) +{ + struct apr_hdr *hdr = &pkt->hdr; + int rc; + + mutex_lock(&ac->cmd_lock); + ac->result.opcode = 0; + ac->result.status = 0; + + rc = apr_send_pkt(ac->adev, pkt); + if (rc < 0) + goto err; + + rc = wait_event_timeout(ac->cmd_wait, + (ac->result.opcode == hdr->opcode), 5 * HZ); + if (!rc) { + dev_err(ac->dev, "CMD %x timeout\n", hdr->opcode); + rc = -ETIMEDOUT; + goto err; + } + + if (ac->result.status > 0) { + dev_err(ac->dev, "DSP returned error[%x]\n", + ac->result.status); + rc = -EINVAL; + } else { + rc = 0; + } + + +err: + mutex_unlock(&ac->cmd_lock); + return rc; +} + +/** + * q6asm_open_write() - Open audio client for writing + * @ac: audio client pointer + * @stream_id: stream id of q6asm session + * @format: audio sample format + * @codec_profile: compressed format profile + * @bits_per_sample: bits per sample + * @is_gapless: flag to indicate if this is a gapless stream + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_open_write(struct audio_client *ac, uint32_t stream_id, + uint32_t format, u32 codec_profile, + uint16_t bits_per_sample, bool is_gapless) +{ + struct asm_stream_cmd_open_write_v3 *open; + struct apr_pkt *pkt; + void *p; + int rc, pkt_size; + + pkt_size = APR_HDR_SIZE + sizeof(*open); + + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + open = p + APR_HDR_SIZE; + q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id); + + pkt->hdr.opcode = ASM_STREAM_CMD_OPEN_WRITE_V3; + open->mode_flags = 0x00; + open->mode_flags |= ASM_LEGACY_STREAM_SESSION; + if (is_gapless) + open->mode_flags |= BIT(ASM_SHIFT_GAPLESS_MODE_FLAG); + + /* source endpoint : matrix */ + open->sink_endpointype = ASM_END_POINT_DEVICE_MATRIX; + open->bits_per_sample = bits_per_sample; + open->postprocopo_id = ASM_NULL_POPP_TOPOLOGY; + + switch (format) { + case SND_AUDIOCODEC_MP3: + open->dec_fmt_id = ASM_MEDIA_FMT_MP3; + break; + case FORMAT_LINEAR_PCM: + open->dec_fmt_id = ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2; + break; + case SND_AUDIOCODEC_FLAC: + open->dec_fmt_id = ASM_MEDIA_FMT_FLAC; + break; + case SND_AUDIOCODEC_WMA: + switch (codec_profile) { + case SND_AUDIOPROFILE_WMA9: + open->dec_fmt_id = ASM_MEDIA_FMT_WMA_V9; + break; + case SND_AUDIOPROFILE_WMA10: + case SND_AUDIOPROFILE_WMA9_PRO: + case SND_AUDIOPROFILE_WMA9_LOSSLESS: + case SND_AUDIOPROFILE_WMA10_LOSSLESS: + open->dec_fmt_id = ASM_MEDIA_FMT_WMA_V10; + break; + default: + dev_err(ac->dev, "Invalid codec profile 0x%x\n", + codec_profile); + rc = -EINVAL; + goto err; + } + break; + case SND_AUDIOCODEC_ALAC: + open->dec_fmt_id = ASM_MEDIA_FMT_ALAC; + break; + case SND_AUDIOCODEC_APE: + open->dec_fmt_id = ASM_MEDIA_FMT_APE; + break; + default: + dev_err(ac->dev, "Invalid format 0x%x\n", format); + rc = -EINVAL; + goto err; + } + + rc = q6asm_ac_send_cmd_sync(ac, pkt); + if (rc < 0) + goto err; + + ac->io_mode |= ASM_TUN_WRITE_IO_MODE; + +err: + kfree(pkt); + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_open_write); + +static int __q6asm_run(struct audio_client *ac, uint32_t stream_id, + uint32_t flags, uint32_t msw_ts, uint32_t lsw_ts, + bool wait) +{ + struct asm_session_cmd_run_v2 *run; + struct apr_pkt *pkt; + int pkt_size, rc; + void *p; + + pkt_size = APR_HDR_SIZE + sizeof(*run); + p = kzalloc(pkt_size, GFP_ATOMIC); + if (!p) + return -ENOMEM; + + pkt = p; + run = p + APR_HDR_SIZE; + + q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id); + + pkt->hdr.opcode = ASM_SESSION_CMD_RUN_V2; + run->flags = flags; + run->time_lsw = lsw_ts; + run->time_msw = msw_ts; + if (wait) { + rc = q6asm_ac_send_cmd_sync(ac, pkt); + } else { + rc = apr_send_pkt(ac->adev, pkt); + if (rc == pkt_size) + rc = 0; + } + + kfree(pkt); + return rc; +} + +/** + * q6asm_run() - start the audio client + * + * @ac: audio client pointer + * @stream_id: stream id of q6asm session + * @flags: flags associated with write + * @msw_ts: timestamp msw + * @lsw_ts: timestamp lsw + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_run(struct audio_client *ac, uint32_t stream_id, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts) +{ + return __q6asm_run(ac, stream_id, flags, msw_ts, lsw_ts, true); +} +EXPORT_SYMBOL_GPL(q6asm_run); + +/** + * q6asm_run_nowait() - start the audio client withou blocking + * + * @ac: audio client pointer + * @stream_id: stream id + * @flags: flags associated with write + * @msw_ts: timestamp msw + * @lsw_ts: timestamp lsw + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_run_nowait(struct audio_client *ac, uint32_t stream_id, + uint32_t flags, uint32_t msw_ts, uint32_t lsw_ts) +{ + return __q6asm_run(ac, stream_id, flags, msw_ts, lsw_ts, false); +} +EXPORT_SYMBOL_GPL(q6asm_run_nowait); + +/** + * q6asm_media_format_block_multi_ch_pcm() - setup pcm configuration + * + * @ac: audio client pointer + * @stream_id: stream id + * @rate: audio sample rate + * @channels: number of audio channels. + * @channel_map: channel map pointer + * @bits_per_sample: bits per sample + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_media_format_block_multi_ch_pcm(struct audio_client *ac, + uint32_t stream_id, + uint32_t rate, uint32_t channels, + u8 channel_map[PCM_MAX_NUM_CHANNEL], + uint16_t bits_per_sample) +{ + struct asm_multi_channel_pcm_fmt_blk_v2 *fmt; + struct apr_pkt *pkt; + u8 *channel_mapping; + void *p; + int rc, pkt_size; + + pkt_size = APR_HDR_SIZE + sizeof(*fmt); + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + fmt = p + APR_HDR_SIZE; + + q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id); + + pkt->hdr.opcode = ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2; + fmt->fmt_blk.fmt_blk_size = sizeof(*fmt) - sizeof(fmt->fmt_blk); + fmt->num_channels = channels; + fmt->bits_per_sample = bits_per_sample; + fmt->sample_rate = rate; + fmt->is_signed = 1; + + channel_mapping = fmt->channel_mapping; + + if (channel_map) { + memcpy(channel_mapping, channel_map, PCM_MAX_NUM_CHANNEL); + } else { + if (q6dsp_map_channels(channel_mapping, channels)) { + dev_err(ac->dev, " map channels failed %d\n", channels); + rc = -EINVAL; + goto err; + } + } + + rc = q6asm_ac_send_cmd_sync(ac, pkt); + +err: + kfree(pkt); + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_media_format_block_multi_ch_pcm); + +int q6asm_stream_media_format_block_flac(struct audio_client *ac, + uint32_t stream_id, + struct q6asm_flac_cfg *cfg) +{ + struct asm_flac_fmt_blk_v2 *fmt; + struct apr_pkt *pkt; + void *p; + int rc, pkt_size; + + pkt_size = APR_HDR_SIZE + sizeof(*fmt); + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + fmt = p + APR_HDR_SIZE; + + q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id); + + pkt->hdr.opcode = ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2; + fmt->fmt_blk.fmt_blk_size = sizeof(*fmt) - sizeof(fmt->fmt_blk); + fmt->is_stream_info_present = cfg->stream_info_present; + fmt->num_channels = cfg->ch_cfg; + fmt->min_blk_size = cfg->min_blk_size; + fmt->max_blk_size = cfg->max_blk_size; + fmt->sample_rate = cfg->sample_rate; + fmt->min_frame_size = cfg->min_frame_size; + fmt->max_frame_size = cfg->max_frame_size; + fmt->sample_size = cfg->sample_size; + + rc = q6asm_ac_send_cmd_sync(ac, pkt); + kfree(pkt); + + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_stream_media_format_block_flac); + +int q6asm_stream_media_format_block_wma_v9(struct audio_client *ac, + uint32_t stream_id, + struct q6asm_wma_cfg *cfg) +{ + struct asm_wmastdv9_fmt_blk_v2 *fmt; + struct apr_pkt *pkt; + void *p; + int rc, pkt_size; + + pkt_size = APR_HDR_SIZE + sizeof(*fmt); + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + fmt = p + APR_HDR_SIZE; + + q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id); + + pkt->hdr.opcode = ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2; + fmt->fmt_blk.fmt_blk_size = sizeof(*fmt) - sizeof(fmt->fmt_blk); + fmt->fmtag = cfg->fmtag; + fmt->num_channels = cfg->num_channels; + fmt->sample_rate = cfg->sample_rate; + fmt->bytes_per_sec = cfg->bytes_per_sec; + fmt->blk_align = cfg->block_align; + fmt->bits_per_sample = cfg->bits_per_sample; + fmt->channel_mask = cfg->channel_mask; + fmt->enc_options = cfg->enc_options; + fmt->reserved = 0; + + rc = q6asm_ac_send_cmd_sync(ac, pkt); + kfree(pkt); + + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_stream_media_format_block_wma_v9); + +int q6asm_stream_media_format_block_wma_v10(struct audio_client *ac, + uint32_t stream_id, + struct q6asm_wma_cfg *cfg) +{ + struct asm_wmaprov10_fmt_blk_v2 *fmt; + struct apr_pkt *pkt; + void *p; + int rc, pkt_size; + + pkt_size = APR_HDR_SIZE + sizeof(*fmt); + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + fmt = p + APR_HDR_SIZE; + + q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id); + + pkt->hdr.opcode = ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2; + fmt->fmt_blk.fmt_blk_size = sizeof(*fmt) - sizeof(fmt->fmt_blk); + fmt->fmtag = cfg->fmtag; + fmt->num_channels = cfg->num_channels; + fmt->sample_rate = cfg->sample_rate; + fmt->bytes_per_sec = cfg->bytes_per_sec; + fmt->blk_align = cfg->block_align; + fmt->bits_per_sample = cfg->bits_per_sample; + fmt->channel_mask = cfg->channel_mask; + fmt->enc_options = cfg->enc_options; + fmt->advanced_enc_options1 = cfg->adv_enc_options; + fmt->advanced_enc_options2 = cfg->adv_enc_options2; + + rc = q6asm_ac_send_cmd_sync(ac, pkt); + kfree(pkt); + + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_stream_media_format_block_wma_v10); + +int q6asm_stream_media_format_block_alac(struct audio_client *ac, + uint32_t stream_id, + struct q6asm_alac_cfg *cfg) +{ + struct asm_alac_fmt_blk_v2 *fmt; + struct apr_pkt *pkt; + void *p; + int rc, pkt_size; + + pkt_size = APR_HDR_SIZE + sizeof(*fmt); + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + fmt = p + APR_HDR_SIZE; + + q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id); + + pkt->hdr.opcode = ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2; + fmt->fmt_blk.fmt_blk_size = sizeof(*fmt) - sizeof(fmt->fmt_blk); + + fmt->frame_length = cfg->frame_length; + fmt->compatible_version = cfg->compatible_version; + fmt->bit_depth = cfg->bit_depth; + fmt->num_channels = cfg->num_channels; + fmt->max_run = cfg->max_run; + fmt->max_frame_bytes = cfg->max_frame_bytes; + fmt->avg_bit_rate = cfg->avg_bit_rate; + fmt->sample_rate = cfg->sample_rate; + fmt->channel_layout_tag = cfg->channel_layout_tag; + fmt->pb = cfg->pb; + fmt->mb = cfg->mb; + fmt->kb = cfg->kb; + + rc = q6asm_ac_send_cmd_sync(ac, pkt); + kfree(pkt); + + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_stream_media_format_block_alac); + +int q6asm_stream_media_format_block_ape(struct audio_client *ac, + uint32_t stream_id, + struct q6asm_ape_cfg *cfg) +{ + struct asm_ape_fmt_blk_v2 *fmt; + struct apr_pkt *pkt; + void *p; + int rc, pkt_size; + + pkt_size = APR_HDR_SIZE + sizeof(*fmt); + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + fmt = p + APR_HDR_SIZE; + + q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id); + + pkt->hdr.opcode = ASM_DATA_CMD_MEDIA_FMT_UPDATE_V2; + fmt->fmt_blk.fmt_blk_size = sizeof(*fmt) - sizeof(fmt->fmt_blk); + + fmt->compatible_version = cfg->compatible_version; + fmt->compression_level = cfg->compression_level; + fmt->format_flags = cfg->format_flags; + fmt->blocks_per_frame = cfg->blocks_per_frame; + fmt->final_frame_blocks = cfg->final_frame_blocks; + fmt->total_frames = cfg->total_frames; + fmt->bits_per_sample = cfg->bits_per_sample; + fmt->num_channels = cfg->num_channels; + fmt->sample_rate = cfg->sample_rate; + fmt->seek_table_present = cfg->seek_table_present; + + rc = q6asm_ac_send_cmd_sync(ac, pkt); + kfree(pkt); + + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_stream_media_format_block_ape); + +static int q6asm_stream_remove_silence(struct audio_client *ac, uint32_t stream_id, + uint32_t cmd, + uint32_t num_samples) +{ + uint32_t *samples; + struct apr_pkt *pkt; + void *p; + int rc, pkt_size; + + pkt_size = APR_HDR_SIZE + sizeof(uint32_t); + p = kzalloc(pkt_size, GFP_ATOMIC); + if (!p) + return -ENOMEM; + + pkt = p; + samples = p + APR_HDR_SIZE; + + q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id); + + pkt->hdr.opcode = cmd; + *samples = num_samples; + rc = apr_send_pkt(ac->adev, pkt); + if (rc == pkt_size) + rc = 0; + + kfree(pkt); + + return rc; +} + +int q6asm_stream_remove_initial_silence(struct audio_client *ac, + uint32_t stream_id, + uint32_t initial_samples) +{ + return q6asm_stream_remove_silence(ac, stream_id, + ASM_DATA_CMD_REMOVE_INITIAL_SILENCE, + initial_samples); +} +EXPORT_SYMBOL_GPL(q6asm_stream_remove_initial_silence); + +int q6asm_stream_remove_trailing_silence(struct audio_client *ac, uint32_t stream_id, + uint32_t trailing_samples) +{ + return q6asm_stream_remove_silence(ac, stream_id, + ASM_DATA_CMD_REMOVE_TRAILING_SILENCE, + trailing_samples); +} +EXPORT_SYMBOL_GPL(q6asm_stream_remove_trailing_silence); + +/** + * q6asm_enc_cfg_blk_pcm_format_support() - setup pcm configuration for capture + * + * @ac: audio client pointer + * @stream_id: stream id + * @rate: audio sample rate + * @channels: number of audio channels. + * @bits_per_sample: bits per sample + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_enc_cfg_blk_pcm_format_support(struct audio_client *ac, + uint32_t stream_id, uint32_t rate, + uint32_t channels, + uint16_t bits_per_sample) +{ + struct asm_multi_channel_pcm_enc_cfg_v2 *enc_cfg; + struct apr_pkt *pkt; + u8 *channel_mapping; + u32 frames_per_buf = 0; + int pkt_size, rc; + void *p; + + pkt_size = APR_HDR_SIZE + sizeof(*enc_cfg); + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + enc_cfg = p + APR_HDR_SIZE; + q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id); + + pkt->hdr.opcode = ASM_STREAM_CMD_SET_ENCDEC_PARAM; + enc_cfg->encdec.param_id = ASM_PARAM_ID_ENCDEC_ENC_CFG_BLK_V2; + enc_cfg->encdec.param_size = sizeof(*enc_cfg) - sizeof(enc_cfg->encdec); + enc_cfg->encblk.frames_per_buf = frames_per_buf; + enc_cfg->encblk.enc_cfg_blk_size = enc_cfg->encdec.param_size - + sizeof(struct asm_enc_cfg_blk_param_v2); + + enc_cfg->num_channels = channels; + enc_cfg->bits_per_sample = bits_per_sample; + enc_cfg->sample_rate = rate; + enc_cfg->is_signed = 1; + channel_mapping = enc_cfg->channel_mapping; + + if (q6dsp_map_channels(channel_mapping, channels)) { + rc = -EINVAL; + goto err; + } + + rc = q6asm_ac_send_cmd_sync(ac, pkt); +err: + kfree(pkt); + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_enc_cfg_blk_pcm_format_support); + + +/** + * q6asm_read() - read data of period size from audio client + * + * @ac: audio client pointer + * @stream_id: stream id + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_read(struct audio_client *ac, uint32_t stream_id) +{ + struct asm_data_cmd_read_v2 *read; + struct audio_port_data *port; + struct audio_buffer *ab; + struct apr_pkt *pkt; + unsigned long flags; + int pkt_size; + int rc = 0; + void *p; + + pkt_size = APR_HDR_SIZE + sizeof(*read); + p = kzalloc(pkt_size, GFP_ATOMIC); + if (!p) + return -ENOMEM; + + pkt = p; + read = p + APR_HDR_SIZE; + + spin_lock_irqsave(&ac->lock, flags); + port = &ac->port[SNDRV_PCM_STREAM_CAPTURE]; + q6asm_add_hdr(ac, &pkt->hdr, pkt_size, false, stream_id); + ab = &port->buf[port->dsp_buf]; + pkt->hdr.opcode = ASM_DATA_CMD_READ_V2; + read->buf_addr_lsw = lower_32_bits(ab->phys); + read->buf_addr_msw = upper_32_bits(ab->phys); + read->mem_map_handle = port->mem_map_handle; + + read->buf_size = ab->size; + read->seq_id = port->dsp_buf; + pkt->hdr.token = port->dsp_buf; + + port->dsp_buf++; + + if (port->dsp_buf >= port->num_periods) + port->dsp_buf = 0; + + spin_unlock_irqrestore(&ac->lock, flags); + rc = apr_send_pkt(ac->adev, pkt); + if (rc == pkt_size) + rc = 0; + else + pr_err("read op[0x%x]rc[%d]\n", pkt->hdr.opcode, rc); + + kfree(pkt); + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_read); + +static int __q6asm_open_read(struct audio_client *ac, uint32_t stream_id, + uint32_t format, uint16_t bits_per_sample) +{ + struct asm_stream_cmd_open_read_v3 *open; + struct apr_pkt *pkt; + int pkt_size, rc; + void *p; + + pkt_size = APR_HDR_SIZE + sizeof(*open); + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + open = p + APR_HDR_SIZE; + + q6asm_add_hdr(ac, &pkt->hdr, pkt_size, true, stream_id); + pkt->hdr.opcode = ASM_STREAM_CMD_OPEN_READ_V3; + /* Stream prio : High, provide meta info with encoded frames */ + open->src_endpointype = ASM_END_POINT_DEVICE_MATRIX; + + open->preprocopo_id = ASM_STREAM_POSTPROC_TOPO_ID_NONE; + open->bits_per_sample = bits_per_sample; + open->mode_flags = 0x0; + + open->mode_flags |= ASM_LEGACY_STREAM_SESSION << + ASM_SHIFT_STREAM_PERF_MODE_FLAG_IN_OPEN_READ; + + switch (format) { + case FORMAT_LINEAR_PCM: + open->mode_flags |= 0x00; + open->enc_cfg_id = ASM_MEDIA_FMT_MULTI_CHANNEL_PCM_V2; + break; + default: + pr_err("Invalid format[%d]\n", format); + } + + rc = q6asm_ac_send_cmd_sync(ac, pkt); + + kfree(pkt); + return rc; +} + +/** + * q6asm_open_read() - Open audio client for reading + * + * @ac: audio client pointer + * @stream_id: stream id + * @format: audio sample format + * @bits_per_sample: bits per sample + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_open_read(struct audio_client *ac, uint32_t stream_id, + uint32_t format, uint16_t bits_per_sample) +{ + return __q6asm_open_read(ac, stream_id, format, bits_per_sample); +} +EXPORT_SYMBOL_GPL(q6asm_open_read); + +/** + * q6asm_write_async() - non blocking write + * + * @ac: audio client pointer + * @stream_id: stream id + * @len: length in bytes + * @msw_ts: timestamp msw + * @lsw_ts: timestamp lsw + * @wflags: flags associated with write + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_write_async(struct audio_client *ac, uint32_t stream_id, uint32_t len, + uint32_t msw_ts, uint32_t lsw_ts, uint32_t wflags) +{ + struct asm_data_cmd_write_v2 *write; + struct audio_port_data *port; + struct audio_buffer *ab; + unsigned long flags; + struct apr_pkt *pkt; + int pkt_size; + int rc = 0; + void *p; + + pkt_size = APR_HDR_SIZE + sizeof(*write); + p = kzalloc(pkt_size, GFP_ATOMIC); + if (!p) + return -ENOMEM; + + pkt = p; + write = p + APR_HDR_SIZE; + + spin_lock_irqsave(&ac->lock, flags); + port = &ac->port[SNDRV_PCM_STREAM_PLAYBACK]; + q6asm_add_hdr(ac, &pkt->hdr, pkt_size, false, stream_id); + + ab = &port->buf[port->dsp_buf]; + pkt->hdr.token = port->dsp_buf | (len << ASM_WRITE_TOKEN_LEN_SHIFT); + pkt->hdr.opcode = ASM_DATA_CMD_WRITE_V2; + write->buf_addr_lsw = lower_32_bits(ab->phys); + write->buf_addr_msw = upper_32_bits(ab->phys); + write->buf_size = len; + write->seq_id = port->dsp_buf; + write->timestamp_lsw = lsw_ts; + write->timestamp_msw = msw_ts; + write->mem_map_handle = + ac->port[SNDRV_PCM_STREAM_PLAYBACK].mem_map_handle; + + write->flags = wflags; + + port->dsp_buf++; + + if (port->dsp_buf >= port->num_periods) + port->dsp_buf = 0; + + spin_unlock_irqrestore(&ac->lock, flags); + rc = apr_send_pkt(ac->adev, pkt); + if (rc == pkt_size) + rc = 0; + + kfree(pkt); + return rc; +} +EXPORT_SYMBOL_GPL(q6asm_write_async); + +static void q6asm_reset_buf_state(struct audio_client *ac) +{ + struct audio_port_data *port = NULL; + unsigned long flags; + + spin_lock_irqsave(&ac->lock, flags); + port = &ac->port[SNDRV_PCM_STREAM_PLAYBACK]; + port->dsp_buf = 0; + port = &ac->port[SNDRV_PCM_STREAM_CAPTURE]; + port->dsp_buf = 0; + spin_unlock_irqrestore(&ac->lock, flags); +} + +static int __q6asm_cmd(struct audio_client *ac, uint32_t stream_id, int cmd, + bool wait) +{ + struct apr_pkt pkt; + int rc; + + q6asm_add_hdr(ac, &pkt.hdr, APR_HDR_SIZE, true, stream_id); + + switch (cmd) { + case CMD_PAUSE: + pkt.hdr.opcode = ASM_SESSION_CMD_PAUSE; + break; + case CMD_SUSPEND: + pkt.hdr.opcode = ASM_SESSION_CMD_SUSPEND; + break; + case CMD_FLUSH: + pkt.hdr.opcode = ASM_STREAM_CMD_FLUSH; + break; + case CMD_OUT_FLUSH: + pkt.hdr.opcode = ASM_STREAM_CMD_FLUSH_READBUFS; + break; + case CMD_EOS: + pkt.hdr.opcode = ASM_DATA_CMD_EOS; + break; + case CMD_CLOSE: + pkt.hdr.opcode = ASM_STREAM_CMD_CLOSE; + break; + default: + return -EINVAL; + } + + if (wait) + rc = q6asm_ac_send_cmd_sync(ac, &pkt); + else + return apr_send_pkt(ac->adev, &pkt); + + if (rc < 0) + return rc; + + if (cmd == CMD_FLUSH) + q6asm_reset_buf_state(ac); + + return 0; +} + +/** + * q6asm_cmd() - run cmd on audio client + * + * @ac: audio client pointer + * @stream_id: stream id + * @cmd: command to run on audio client. + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_cmd(struct audio_client *ac, uint32_t stream_id, int cmd) +{ + return __q6asm_cmd(ac, stream_id, cmd, true); +} +EXPORT_SYMBOL_GPL(q6asm_cmd); + +/** + * q6asm_cmd_nowait() - non blocking, run cmd on audio client + * + * @ac: audio client pointer + * @stream_id: stream id + * @cmd: command to run on audio client. + * + * Return: Will be an negative value on error or zero on success + */ +int q6asm_cmd_nowait(struct audio_client *ac, uint32_t stream_id, int cmd) +{ + return __q6asm_cmd(ac, stream_id, cmd, false); +} +EXPORT_SYMBOL_GPL(q6asm_cmd_nowait); + +static int q6asm_probe(struct apr_device *adev) +{ + struct device *dev = &adev->dev; + struct q6asm *q6asm; + + q6asm = devm_kzalloc(dev, sizeof(*q6asm), GFP_KERNEL); + if (!q6asm) + return -ENOMEM; + + q6core_get_svc_api_info(adev->svc_id, &q6asm->ainfo); + + q6asm->dev = dev; + q6asm->adev = adev; + init_waitqueue_head(&q6asm->mem_wait); + spin_lock_init(&q6asm->slock); + dev_set_drvdata(dev, q6asm); + + return of_platform_populate(dev->of_node, NULL, NULL, dev); +} + +static int q6asm_remove(struct apr_device *adev) +{ + of_platform_depopulate(&adev->dev); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id q6asm_device_id[] = { + { .compatible = "qcom,q6asm" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6asm_device_id); +#endif + +static struct apr_driver qcom_q6asm_driver = { + .probe = q6asm_probe, + .remove = q6asm_remove, + .callback = q6asm_srvc_callback, + .driver = { + .name = "qcom-q6asm", + .of_match_table = of_match_ptr(q6asm_device_id), + }, +}; + +module_apr_driver(qcom_q6asm_driver); +MODULE_DESCRIPTION("Q6 Audio Stream Manager driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6asm.h b/sound/soc/qcom/qdsp6/q6asm.h new file mode 100644 index 000000000..82e584aa5 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6asm.h @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __Q6_ASM_H__ +#define __Q6_ASM_H__ +#include "q6dsp-common.h" +#include <dt-bindings/sound/qcom,q6asm.h> + +/* ASM client callback events */ +#define CMD_PAUSE 0x0001 +#define ASM_CLIENT_EVENT_CMD_PAUSE_DONE 0x1001 +#define CMD_FLUSH 0x0002 +#define ASM_CLIENT_EVENT_CMD_FLUSH_DONE 0x1002 +#define CMD_EOS 0x0003 +#define ASM_CLIENT_EVENT_CMD_EOS_DONE 0x1003 +#define CMD_CLOSE 0x0004 +#define ASM_CLIENT_EVENT_CMD_CLOSE_DONE 0x1004 +#define CMD_OUT_FLUSH 0x0005 +#define ASM_CLIENT_EVENT_CMD_OUT_FLUSH_DONE 0x1005 +#define CMD_SUSPEND 0x0006 +#define ASM_CLIENT_EVENT_CMD_SUSPEND_DONE 0x1006 +#define ASM_CLIENT_EVENT_CMD_RUN_DONE 0x1008 +#define ASM_CLIENT_EVENT_DATA_WRITE_DONE 0x1009 +#define ASM_CLIENT_EVENT_DATA_READ_DONE 0x100a +#define ASM_WRITE_TOKEN_MASK GENMASK(15, 0) +#define ASM_WRITE_TOKEN_LEN_MASK GENMASK(31, 16) +#define ASM_WRITE_TOKEN_LEN_SHIFT 16 + +enum { + LEGACY_PCM_MODE = 0, + LOW_LATENCY_PCM_MODE, + ULTRA_LOW_LATENCY_PCM_MODE, + ULL_POST_PROCESSING_PCM_MODE, +}; + +#define MAX_SESSIONS 8 +#define FORMAT_LINEAR_PCM 0x0000 +#define ASM_LAST_BUFFER_FLAG BIT(30) + +struct q6asm_flac_cfg { + u32 sample_rate; + u32 ext_sample_rate; + u32 min_frame_size; + u32 max_frame_size; + u16 stream_info_present; + u16 min_blk_size; + u16 max_blk_size; + u16 ch_cfg; + u16 sample_size; + u16 md5_sum; +}; + +struct q6asm_wma_cfg { + u32 fmtag; + u32 num_channels; + u32 sample_rate; + u32 bytes_per_sec; + u32 block_align; + u32 bits_per_sample; + u32 channel_mask; + u32 enc_options; + u32 adv_enc_options; + u32 adv_enc_options2; +}; + +struct q6asm_alac_cfg { + u32 frame_length; + u8 compatible_version; + u8 bit_depth; + u8 pb; + u8 mb; + u8 kb; + u8 num_channels; + u16 max_run; + u32 max_frame_bytes; + u32 avg_bit_rate; + u32 sample_rate; + u32 channel_layout_tag; +}; + +struct q6asm_ape_cfg { + u16 compatible_version; + u16 compression_level; + u32 format_flags; + u32 blocks_per_frame; + u32 final_frame_blocks; + u32 total_frames; + u16 bits_per_sample; + u16 num_channels; + u32 sample_rate; + u32 seek_table_present; +}; + +typedef void (*q6asm_cb) (uint32_t opcode, uint32_t token, + void *payload, void *priv); +struct audio_client; +struct audio_client *q6asm_audio_client_alloc(struct device *dev, + q6asm_cb cb, void *priv, + int session_id, int perf_mode); +void q6asm_audio_client_free(struct audio_client *ac); +int q6asm_write_async(struct audio_client *ac, uint32_t stream_id, uint32_t len, + uint32_t msw_ts, uint32_t lsw_ts, uint32_t flags); +int q6asm_open_write(struct audio_client *ac, uint32_t stream_id, + uint32_t format, u32 codec_profile, + uint16_t bits_per_sample, bool is_gapless); + +int q6asm_open_read(struct audio_client *ac, uint32_t stream_id, + uint32_t format, uint16_t bits_per_sample); +int q6asm_enc_cfg_blk_pcm_format_support(struct audio_client *ac, + uint32_t stream_id, uint32_t rate, + uint32_t channels, + uint16_t bits_per_sample); + +int q6asm_read(struct audio_client *ac, uint32_t stream_id); + +int q6asm_media_format_block_multi_ch_pcm(struct audio_client *ac, + uint32_t stream_id, + uint32_t rate, uint32_t channels, + u8 channel_map[PCM_MAX_NUM_CHANNEL], + uint16_t bits_per_sample); +int q6asm_stream_media_format_block_flac(struct audio_client *ac, + uint32_t stream_id, + struct q6asm_flac_cfg *cfg); +int q6asm_stream_media_format_block_wma_v9(struct audio_client *ac, + uint32_t stream_id, + struct q6asm_wma_cfg *cfg); +int q6asm_stream_media_format_block_wma_v10(struct audio_client *ac, + uint32_t stream_id, + struct q6asm_wma_cfg *cfg); +int q6asm_stream_media_format_block_alac(struct audio_client *ac, + uint32_t stream_id, + struct q6asm_alac_cfg *cfg); +int q6asm_stream_media_format_block_ape(struct audio_client *ac, + uint32_t stream_id, + struct q6asm_ape_cfg *cfg); +int q6asm_run(struct audio_client *ac, uint32_t stream_id, uint32_t flags, + uint32_t msw_ts, uint32_t lsw_ts); +int q6asm_run_nowait(struct audio_client *ac, uint32_t stream_id, + uint32_t flags, uint32_t msw_ts, uint32_t lsw_ts); +int q6asm_stream_remove_initial_silence(struct audio_client *ac, + uint32_t stream_id, + uint32_t initial_samples); +int q6asm_stream_remove_trailing_silence(struct audio_client *ac, + uint32_t stream_id, + uint32_t trailing_samples); +int q6asm_cmd(struct audio_client *ac, uint32_t stream_id, int cmd); +int q6asm_cmd_nowait(struct audio_client *ac, uint32_t stream_id, int cmd); +int q6asm_get_session_id(struct audio_client *ac); +int q6asm_map_memory_regions(unsigned int dir, + struct audio_client *ac, + phys_addr_t phys, + size_t bufsz, unsigned int bufcnt); +int q6asm_unmap_memory_regions(unsigned int dir, struct audio_client *ac); +#endif /* __Q6_ASM_H__ */ diff --git a/sound/soc/qcom/qdsp6/q6core.c b/sound/soc/qcom/qdsp6/q6core.c new file mode 100644 index 000000000..5358fefd4 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6core.c @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2018, Linaro Limited + +#include <linux/slab.h> +#include <linux/wait.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/jiffies.h> +#include <linux/soc/qcom/apr.h> +#include "q6core.h" +#include "q6dsp-errno.h" + +#define ADSP_STATE_READY_TIMEOUT_MS 3000 +#define Q6_READY_TIMEOUT_MS 100 +#define AVCS_CMD_ADSP_EVENT_GET_STATE 0x0001290C +#define AVCS_CMDRSP_ADSP_EVENT_GET_STATE 0x0001290D +#define AVCS_GET_VERSIONS 0x00012905 +#define AVCS_GET_VERSIONS_RSP 0x00012906 +#define AVCS_CMD_GET_FWK_VERSION 0x001292c +#define AVCS_CMDRSP_GET_FWK_VERSION 0x001292d + +struct avcs_svc_info { + uint32_t service_id; + uint32_t version; +} __packed; + +struct avcs_cmdrsp_get_version { + uint32_t build_id; + uint32_t num_services; + struct avcs_svc_info svc_api_info[]; +} __packed; + +/* for ADSP2.8 and above */ +struct avcs_svc_api_info { + uint32_t service_id; + uint32_t api_version; + uint32_t api_branch_version; +} __packed; + +struct avcs_cmdrsp_get_fwk_version { + uint32_t build_major_version; + uint32_t build_minor_version; + uint32_t build_branch_version; + uint32_t build_subbranch_version; + uint32_t num_services; + struct avcs_svc_api_info svc_api_info[]; +} __packed; + +struct q6core { + struct apr_device *adev; + wait_queue_head_t wait; + uint32_t avcs_state; + struct mutex lock; + bool resp_received; + uint32_t num_services; + struct avcs_cmdrsp_get_fwk_version *fwk_version; + struct avcs_cmdrsp_get_version *svc_version; + bool fwk_version_supported; + bool get_state_supported; + bool get_version_supported; + bool is_version_requested; +}; + +static struct q6core *g_core; + +static int q6core_callback(struct apr_device *adev, struct apr_resp_pkt *data) +{ + struct q6core *core = dev_get_drvdata(&adev->dev); + struct aprv2_ibasic_rsp_result_t *result; + struct apr_hdr *hdr = &data->hdr; + + result = data->payload; + switch (hdr->opcode) { + case APR_BASIC_RSP_RESULT:{ + result = data->payload; + switch (result->opcode) { + case AVCS_GET_VERSIONS: + if (result->status == ADSP_EUNSUPPORTED) + core->get_version_supported = false; + core->resp_received = true; + break; + case AVCS_CMD_GET_FWK_VERSION: + if (result->status == ADSP_EUNSUPPORTED) + core->fwk_version_supported = false; + core->resp_received = true; + break; + case AVCS_CMD_ADSP_EVENT_GET_STATE: + if (result->status == ADSP_EUNSUPPORTED) + core->get_state_supported = false; + core->resp_received = true; + break; + } + break; + } + case AVCS_CMDRSP_GET_FWK_VERSION: { + struct avcs_cmdrsp_get_fwk_version *fwk; + + fwk = data->payload; + + core->fwk_version = kmemdup(data->payload, + struct_size(fwk, svc_api_info, + fwk->num_services), + GFP_ATOMIC); + if (!core->fwk_version) + return -ENOMEM; + + core->fwk_version_supported = true; + core->resp_received = true; + + break; + } + case AVCS_GET_VERSIONS_RSP: { + struct avcs_cmdrsp_get_version *v; + + v = data->payload; + + core->svc_version = kmemdup(data->payload, + struct_size(v, svc_api_info, + v->num_services), + GFP_ATOMIC); + if (!core->svc_version) + return -ENOMEM; + + core->get_version_supported = true; + core->resp_received = true; + + break; + } + case AVCS_CMDRSP_ADSP_EVENT_GET_STATE: + core->get_state_supported = true; + core->avcs_state = result->opcode; + + core->resp_received = true; + break; + default: + dev_err(&adev->dev, "Message id from adsp core svc: 0x%x\n", + hdr->opcode); + break; + } + + if (core->resp_received) + wake_up(&core->wait); + + return 0; +} + +static int q6core_get_fwk_versions(struct q6core *core) +{ + struct apr_device *adev = core->adev; + struct apr_pkt pkt; + int rc; + + pkt.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + pkt.hdr.pkt_size = APR_HDR_SIZE; + pkt.hdr.opcode = AVCS_CMD_GET_FWK_VERSION; + + rc = apr_send_pkt(adev, &pkt); + if (rc < 0) + return rc; + + rc = wait_event_timeout(core->wait, (core->resp_received), + msecs_to_jiffies(Q6_READY_TIMEOUT_MS)); + if (rc > 0 && core->resp_received) { + core->resp_received = false; + + if (!core->fwk_version_supported) + return -ENOTSUPP; + else + return 0; + } + + + return rc; +} + +static int q6core_get_svc_versions(struct q6core *core) +{ + struct apr_device *adev = core->adev; + struct apr_pkt pkt; + int rc; + + pkt.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + pkt.hdr.pkt_size = APR_HDR_SIZE; + pkt.hdr.opcode = AVCS_GET_VERSIONS; + + rc = apr_send_pkt(adev, &pkt); + if (rc < 0) + return rc; + + rc = wait_event_timeout(core->wait, (core->resp_received), + msecs_to_jiffies(Q6_READY_TIMEOUT_MS)); + if (rc > 0 && core->resp_received) { + core->resp_received = false; + return 0; + } + + return rc; +} + +static bool __q6core_is_adsp_ready(struct q6core *core) +{ + struct apr_device *adev = core->adev; + struct apr_pkt pkt; + int rc; + + core->get_state_supported = false; + + pkt.hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + pkt.hdr.pkt_size = APR_HDR_SIZE; + pkt.hdr.opcode = AVCS_CMD_ADSP_EVENT_GET_STATE; + + rc = apr_send_pkt(adev, &pkt); + if (rc < 0) + return false; + + rc = wait_event_timeout(core->wait, (core->resp_received), + msecs_to_jiffies(Q6_READY_TIMEOUT_MS)); + if (rc > 0 && core->resp_received) { + core->resp_received = false; + + if (core->avcs_state) + return true; + } + + /* assume that the adsp is up if we not support this command */ + if (!core->get_state_supported) + return true; + + return false; +} + +/** + * q6core_get_svc_api_info() - Get version number of a service. + * + * @svc_id: service id of the service. + * @ainfo: Valid struct pointer to fill svc api information. + * + * Return: zero on success and error code on failure or unsupported + */ +int q6core_get_svc_api_info(int svc_id, struct q6core_svc_api_info *ainfo) +{ + int i; + int ret = -ENOTSUPP; + + if (!g_core || !ainfo) + return 0; + + mutex_lock(&g_core->lock); + if (!g_core->is_version_requested) { + if (q6core_get_fwk_versions(g_core) == -ENOTSUPP) + q6core_get_svc_versions(g_core); + g_core->is_version_requested = true; + } + + if (g_core->fwk_version_supported) { + for (i = 0; i < g_core->fwk_version->num_services; i++) { + struct avcs_svc_api_info *info; + + info = &g_core->fwk_version->svc_api_info[i]; + if (svc_id != info->service_id) + continue; + + ainfo->api_version = info->api_version; + ainfo->api_branch_version = info->api_branch_version; + ret = 0; + break; + } + } else if (g_core->get_version_supported) { + for (i = 0; i < g_core->svc_version->num_services; i++) { + struct avcs_svc_info *info; + + info = &g_core->svc_version->svc_api_info[i]; + if (svc_id != info->service_id) + continue; + + ainfo->api_version = info->version; + ainfo->api_branch_version = 0; + ret = 0; + break; + } + } + + mutex_unlock(&g_core->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(q6core_get_svc_api_info); + +/** + * q6core_is_adsp_ready() - Get status of adsp + * + * Return: Will be an true if adsp is ready and false if not. + */ +bool q6core_is_adsp_ready(void) +{ + unsigned long timeout; + bool ret = false; + + if (!g_core) + return false; + + mutex_lock(&g_core->lock); + timeout = jiffies + msecs_to_jiffies(ADSP_STATE_READY_TIMEOUT_MS); + for (;;) { + if (__q6core_is_adsp_ready(g_core)) { + ret = true; + break; + } + + if (!time_after(timeout, jiffies)) { + ret = false; + break; + } + } + + mutex_unlock(&g_core->lock); + return ret; +} +EXPORT_SYMBOL_GPL(q6core_is_adsp_ready); + +static int q6core_probe(struct apr_device *adev) +{ + g_core = kzalloc(sizeof(*g_core), GFP_KERNEL); + if (!g_core) + return -ENOMEM; + + dev_set_drvdata(&adev->dev, g_core); + + mutex_init(&g_core->lock); + g_core->adev = adev; + init_waitqueue_head(&g_core->wait); + return 0; +} + +static int q6core_exit(struct apr_device *adev) +{ + struct q6core *core = dev_get_drvdata(&adev->dev); + + if (core->fwk_version_supported) + kfree(core->fwk_version); + if (core->get_version_supported) + kfree(core->svc_version); + + g_core = NULL; + kfree(core); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id q6core_device_id[] = { + { .compatible = "qcom,q6core" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6core_device_id); +#endif + +static struct apr_driver qcom_q6core_driver = { + .probe = q6core_probe, + .remove = q6core_exit, + .callback = q6core_callback, + .driver = { + .name = "qcom-q6core", + .of_match_table = of_match_ptr(q6core_device_id), + }, +}; + +module_apr_driver(qcom_q6core_driver); +MODULE_DESCRIPTION("q6 core"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6core.h b/sound/soc/qcom/qdsp6/q6core.h new file mode 100644 index 000000000..4105b1d73 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6core.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __Q6CORE_H__ +#define __Q6CORE_H__ + +struct q6core_svc_api_info { + uint32_t service_id; + uint32_t api_version; + uint32_t api_branch_version; +}; + +bool q6core_is_adsp_ready(void); +int q6core_get_svc_api_info(int svc_id, struct q6core_svc_api_info *ainfo); + +#endif /* __Q6CORE_H__ */ diff --git a/sound/soc/qcom/qdsp6/q6dsp-common.c b/sound/soc/qcom/qdsp6/q6dsp-common.c new file mode 100644 index 000000000..d39300349 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6dsp-common.c @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2018, Linaro Limited + +#include "q6dsp-common.h" +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/errno.h> + +int q6dsp_map_channels(u8 ch_map[PCM_MAX_NUM_CHANNEL], int ch) +{ + memset(ch_map, 0, PCM_MAX_NUM_CHANNEL); + + switch (ch) { + case 1: + ch_map[0] = PCM_CHANNEL_FC; + break; + case 2: + ch_map[0] = PCM_CHANNEL_FL; + ch_map[1] = PCM_CHANNEL_FR; + break; + case 3: + ch_map[0] = PCM_CHANNEL_FL; + ch_map[1] = PCM_CHANNEL_FR; + ch_map[2] = PCM_CHANNEL_FC; + break; + case 4: + ch_map[0] = PCM_CHANNEL_FL; + ch_map[1] = PCM_CHANNEL_FR; + ch_map[2] = PCM_CHANNEL_LS; + ch_map[3] = PCM_CHANNEL_RS; + break; + case 5: + ch_map[0] = PCM_CHANNEL_FL; + ch_map[1] = PCM_CHANNEL_FR; + ch_map[2] = PCM_CHANNEL_FC; + ch_map[3] = PCM_CHANNEL_LS; + ch_map[4] = PCM_CHANNEL_RS; + break; + case 6: + ch_map[0] = PCM_CHANNEL_FL; + ch_map[1] = PCM_CHANNEL_FR; + ch_map[2] = PCM_CHANNEL_LFE; + ch_map[3] = PCM_CHANNEL_FC; + ch_map[4] = PCM_CHANNEL_LS; + ch_map[5] = PCM_CHANNEL_RS; + break; + case 8: + ch_map[0] = PCM_CHANNEL_FL; + ch_map[1] = PCM_CHANNEL_FR; + ch_map[2] = PCM_CHANNEL_LFE; + ch_map[3] = PCM_CHANNEL_FC; + ch_map[4] = PCM_CHANNEL_LS; + ch_map[5] = PCM_CHANNEL_RS; + ch_map[6] = PCM_CHANNEL_LB; + ch_map[7] = PCM_CHANNEL_RB; + break; + default: + return -EINVAL; + } + + return 0; +} +EXPORT_SYMBOL_GPL(q6dsp_map_channels); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6dsp-common.h b/sound/soc/qcom/qdsp6/q6dsp-common.h new file mode 100644 index 000000000..01094d108 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6dsp-common.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __Q6DSP_COMMON_H__ +#define __Q6DSP_COMMON_H__ + +#include <linux/kernel.h> + +#define PCM_MAX_NUM_CHANNEL 8 +#define PCM_CHANNEL_NULL 0 + +#define PCM_CHANNEL_FL 1 /* Front left channel. */ +#define PCM_CHANNEL_FR 2 /* Front right channel. */ +#define PCM_CHANNEL_FC 3 /* Front center channel. */ +#define PCM_CHANNEL_LS 4 /* Left surround channel. */ +#define PCM_CHANNEL_RS 5 /* Right surround channel. */ +#define PCM_CHANNEL_LFE 6 /* Low frequency effect channel. */ +#define PCM_CHANNEL_CS 7 /* Center surround channel; Rear center ch */ +#define PCM_CHANNEL_LB 8 /* Left back channel; Rear left channel. */ +#define PCM_CHANNEL_RB 9 /* Right back channel; Rear right channel. */ +#define PCM_CHANNELS 10 /* Top surround channel. */ + +int q6dsp_map_channels(u8 ch_map[PCM_MAX_NUM_CHANNEL], int ch); + +#endif /* __Q6DSP_COMMON_H__ */ diff --git a/sound/soc/qcom/qdsp6/q6dsp-errno.h b/sound/soc/qcom/qdsp6/q6dsp-errno.h new file mode 100644 index 000000000..1ec00ff8c --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6dsp-errno.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __Q6DSP_ERR_NO_H__ +#define __Q6DSP_ERR_NO_H__ +#include <linux/kernel.h> + +/* Success. The operation completed with no errors. */ +#define ADSP_EOK 0x00000000 +/* General failure. */ +#define ADSP_EFAILED 0x00000001 +/* Bad operation parameter. */ +#define ADSP_EBADPARAM 0x00000002 +/* Unsupported routine or operation. */ +#define ADSP_EUNSUPPORTED 0x00000003 +/* Unsupported version. */ +#define ADSP_EVERSION 0x00000004 +/* Unexpected problem encountered. */ +#define ADSP_EUNEXPECTED 0x00000005 +/* Unhandled problem occurred. */ +#define ADSP_EPANIC 0x00000006 +/* Unable to allocate resource. */ +#define ADSP_ENORESOURCE 0x00000007 +/* Invalid handle. */ +#define ADSP_EHANDLE 0x00000008 +/* Operation is already processed. */ +#define ADSP_EALREADY 0x00000009 +/* Operation is not ready to be processed. */ +#define ADSP_ENOTREADY 0x0000000A +/* Operation is pending completion. */ +#define ADSP_EPENDING 0x0000000B +/* Operation could not be accepted or processed. */ +#define ADSP_EBUSY 0x0000000C +/* Operation aborted due to an error. */ +#define ADSP_EABORTED 0x0000000D +/* Operation preempted by a higher priority. */ +#define ADSP_EPREEMPTED 0x0000000E +/* Operation requests intervention to complete. */ +#define ADSP_ECONTINUE 0x0000000F +/* Operation requests immediate intervention to complete. */ +#define ADSP_EIMMEDIATE 0x00000010 +/* Operation is not implemented. */ +#define ADSP_ENOTIMPL 0x00000011 +/* Operation needs more data or resources. */ +#define ADSP_ENEEDMORE 0x00000012 +/* Operation does not have memory. */ +#define ADSP_ENOMEMORY 0x00000014 +/* Item does not exist. */ +#define ADSP_ENOTEXIST 0x00000015 +/* Max count for adsp error code sent to HLOS*/ + +#endif /*__Q6DSP_ERR_NO_H__ */ diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c new file mode 100644 index 000000000..2026fa590 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6routing.c @@ -0,0 +1,1153 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2018, Linaro Limited + +#include <linux/init.h> +#include <linux/err.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of_platform.h> +#include <linux/bitops.h> +#include <linux/mutex.h> +#include <linux/of_device.h> +#include <linux/slab.h> +#include <sound/core.h> +#include <sound/soc.h> +#include <sound/soc-dapm.h> +#include <sound/pcm.h> +#include <sound/control.h> +#include <sound/asound.h> +#include <sound/pcm_params.h> +#include "q6afe.h" +#include "q6asm.h" +#include "q6adm.h" +#include "q6routing.h" + +#define DRV_NAME "q6routing-component" + +#define Q6ROUTING_RX_MIXERS(id) \ + SOC_SINGLE_EXT("MultiMedia1", id, \ + MSM_FRONTEND_DAI_MULTIMEDIA1, 1, 0, msm_routing_get_audio_mixer,\ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("MultiMedia2", id, \ + MSM_FRONTEND_DAI_MULTIMEDIA2, 1, 0, msm_routing_get_audio_mixer,\ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("MultiMedia3", id, \ + MSM_FRONTEND_DAI_MULTIMEDIA3, 1, 0, msm_routing_get_audio_mixer,\ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("MultiMedia4", id, \ + MSM_FRONTEND_DAI_MULTIMEDIA4, 1, 0, msm_routing_get_audio_mixer,\ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("MultiMedia5", id, \ + MSM_FRONTEND_DAI_MULTIMEDIA5, 1, 0, msm_routing_get_audio_mixer,\ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("MultiMedia6", id, \ + MSM_FRONTEND_DAI_MULTIMEDIA6, 1, 0, msm_routing_get_audio_mixer,\ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("MultiMedia7", id, \ + MSM_FRONTEND_DAI_MULTIMEDIA7, 1, 0, msm_routing_get_audio_mixer,\ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("MultiMedia8", id, \ + MSM_FRONTEND_DAI_MULTIMEDIA8, 1, 0, msm_routing_get_audio_mixer,\ + msm_routing_put_audio_mixer), + +#define Q6ROUTING_RX_DAPM_ROUTE(mix_name, s) \ + { mix_name, "MultiMedia1", "MM_DL1" }, \ + { mix_name, "MultiMedia2", "MM_DL2" }, \ + { mix_name, "MultiMedia3", "MM_DL3" }, \ + { mix_name, "MultiMedia4", "MM_DL4" }, \ + { mix_name, "MultiMedia5", "MM_DL5" }, \ + { mix_name, "MultiMedia6", "MM_DL6" }, \ + { mix_name, "MultiMedia7", "MM_DL7" }, \ + { mix_name, "MultiMedia8", "MM_DL8" }, \ + { s, NULL, mix_name } + +#define Q6ROUTING_TX_DAPM_ROUTE(mix_name) \ + { mix_name, "PRI_MI2S_TX", "PRI_MI2S_TX" }, \ + { mix_name, "SEC_MI2S_TX", "SEC_MI2S_TX" }, \ + { mix_name, "QUAT_MI2S_TX", "QUAT_MI2S_TX" }, \ + { mix_name, "TERT_MI2S_TX", "TERT_MI2S_TX" }, \ + { mix_name, "SLIMBUS_0_TX", "SLIMBUS_0_TX" }, \ + { mix_name, "SLIMBUS_1_TX", "SLIMBUS_1_TX" }, \ + { mix_name, "SLIMBUS_2_TX", "SLIMBUS_2_TX" }, \ + { mix_name, "SLIMBUS_3_TX", "SLIMBUS_3_TX" }, \ + { mix_name, "SLIMBUS_4_TX", "SLIMBUS_4_TX" }, \ + { mix_name, "SLIMBUS_5_TX", "SLIMBUS_5_TX" }, \ + { mix_name, "SLIMBUS_6_TX", "SLIMBUS_6_TX" }, \ + { mix_name, "PRIMARY_TDM_TX_0", "PRIMARY_TDM_TX_0"}, \ + { mix_name, "PRIMARY_TDM_TX_1", "PRIMARY_TDM_TX_1"}, \ + { mix_name, "PRIMARY_TDM_TX_2", "PRIMARY_TDM_TX_2"}, \ + { mix_name, "PRIMARY_TDM_TX_3", "PRIMARY_TDM_TX_3"}, \ + { mix_name, "PRIMARY_TDM_TX_4", "PRIMARY_TDM_TX_4"}, \ + { mix_name, "PRIMARY_TDM_TX_5", "PRIMARY_TDM_TX_5"}, \ + { mix_name, "PRIMARY_TDM_TX_6", "PRIMARY_TDM_TX_6"}, \ + { mix_name, "PRIMARY_TDM_TX_7", "PRIMARY_TDM_TX_7"}, \ + { mix_name, "SEC_TDM_TX_0", "SEC_TDM_TX_0"}, \ + { mix_name, "SEC_TDM_TX_1", "SEC_TDM_TX_1"}, \ + { mix_name, "SEC_TDM_TX_2", "SEC_TDM_TX_2"}, \ + { mix_name, "SEC_TDM_TX_3", "SEC_TDM_TX_3"}, \ + { mix_name, "SEC_TDM_TX_4", "SEC_TDM_TX_4"}, \ + { mix_name, "SEC_TDM_TX_5", "SEC_TDM_TX_5"}, \ + { mix_name, "SEC_TDM_TX_6", "SEC_TDM_TX_6"}, \ + { mix_name, "SEC_TDM_TX_7", "SEC_TDM_TX_7"}, \ + { mix_name, "TERT_TDM_TX_0", "TERT_TDM_TX_0"}, \ + { mix_name, "TERT_TDM_TX_1", "TERT_TDM_TX_1"}, \ + { mix_name, "TERT_TDM_TX_2", "TERT_TDM_TX_2"}, \ + { mix_name, "TERT_TDM_TX_3", "TERT_TDM_TX_3"}, \ + { mix_name, "TERT_TDM_TX_4", "TERT_TDM_TX_4"}, \ + { mix_name, "TERT_TDM_TX_5", "TERT_TDM_TX_5"}, \ + { mix_name, "TERT_TDM_TX_6", "TERT_TDM_TX_6"}, \ + { mix_name, "TERT_TDM_TX_7", "TERT_TDM_TX_7"}, \ + { mix_name, "QUAT_TDM_TX_0", "QUAT_TDM_TX_0"}, \ + { mix_name, "QUAT_TDM_TX_1", "QUAT_TDM_TX_1"}, \ + { mix_name, "QUAT_TDM_TX_2", "QUAT_TDM_TX_2"}, \ + { mix_name, "QUAT_TDM_TX_3", "QUAT_TDM_TX_3"}, \ + { mix_name, "QUAT_TDM_TX_4", "QUAT_TDM_TX_4"}, \ + { mix_name, "QUAT_TDM_TX_5", "QUAT_TDM_TX_5"}, \ + { mix_name, "QUAT_TDM_TX_6", "QUAT_TDM_TX_6"}, \ + { mix_name, "QUAT_TDM_TX_7", "QUAT_TDM_TX_7"}, \ + { mix_name, "QUIN_TDM_TX_0", "QUIN_TDM_TX_0"}, \ + { mix_name, "QUIN_TDM_TX_1", "QUIN_TDM_TX_1"}, \ + { mix_name, "QUIN_TDM_TX_2", "QUIN_TDM_TX_2"}, \ + { mix_name, "QUIN_TDM_TX_3", "QUIN_TDM_TX_3"}, \ + { mix_name, "QUIN_TDM_TX_4", "QUIN_TDM_TX_4"}, \ + { mix_name, "QUIN_TDM_TX_5", "QUIN_TDM_TX_5"}, \ + { mix_name, "QUIN_TDM_TX_6", "QUIN_TDM_TX_6"}, \ + { mix_name, "QUIN_TDM_TX_7", "QUIN_TDM_TX_7"}, \ + { mix_name, "WSA_CODEC_DMA_TX_0", "WSA_CODEC_DMA_TX_0"}, \ + { mix_name, "WSA_CODEC_DMA_TX_1", "WSA_CODEC_DMA_TX_1"}, \ + { mix_name, "WSA_CODEC_DMA_TX_2", "WSA_CODEC_DMA_TX_2"}, \ + { mix_name, "VA_CODEC_DMA_TX_0", "VA_CODEC_DMA_TX_0"}, \ + { mix_name, "VA_CODEC_DMA_TX_1", "VA_CODEC_DMA_TX_1"}, \ + { mix_name, "VA_CODEC_DMA_TX_2", "VA_CODEC_DMA_TX_2"}, \ + { mix_name, "TX_CODEC_DMA_TX_0", "TX_CODEC_DMA_TX_0"}, \ + { mix_name, "TX_CODEC_DMA_TX_1", "TX_CODEC_DMA_TX_1"}, \ + { mix_name, "TX_CODEC_DMA_TX_2", "TX_CODEC_DMA_TX_2"}, \ + { mix_name, "TX_CODEC_DMA_TX_3", "TX_CODEC_DMA_TX_3"}, \ + { mix_name, "TX_CODEC_DMA_TX_4", "TX_CODEC_DMA_TX_4"}, \ + { mix_name, "TX_CODEC_DMA_TX_5", "TX_CODEC_DMA_TX_5"} + +#define Q6ROUTING_TX_MIXERS(id) \ + SOC_SINGLE_EXT("PRI_MI2S_TX", PRIMARY_MI2S_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SEC_MI2S_TX", SECONDARY_MI2S_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TERT_MI2S_TX", TERTIARY_MI2S_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUAT_MI2S_TX", QUATERNARY_MI2S_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SLIMBUS_0_TX", SLIMBUS_0_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SLIMBUS_1_TX", SLIMBUS_1_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SLIMBUS_2_TX", SLIMBUS_2_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SLIMBUS_3_TX", SLIMBUS_3_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SLIMBUS_4_TX", SLIMBUS_4_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SLIMBUS_5_TX", SLIMBUS_5_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SLIMBUS_6_TX", SLIMBUS_6_TX, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("PRIMARY_TDM_TX_0", PRIMARY_TDM_TX_0, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("PRIMARY_TDM_TX_1", PRIMARY_TDM_TX_1, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("PRIMARY_TDM_TX_2", PRIMARY_TDM_TX_2, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("PRIMARY_TDM_TX_3", PRIMARY_TDM_TX_3, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("PRIMARY_TDM_TX_4", PRIMARY_TDM_TX_4, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("PRIMARY_TDM_TX_5", PRIMARY_TDM_TX_5, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("PRIMARY_TDM_TX_6", PRIMARY_TDM_TX_6, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("PRIMARY_TDM_TX_7", PRIMARY_TDM_TX_7, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SEC_TDM_TX_0", SECONDARY_TDM_TX_0, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SEC_TDM_TX_1", SECONDARY_TDM_TX_1, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SEC_TDM_TX_2", SECONDARY_TDM_TX_2, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SEC_TDM_TX_3", SECONDARY_TDM_TX_3, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SEC_TDM_TX_4", SECONDARY_TDM_TX_4, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SEC_TDM_TX_5", SECONDARY_TDM_TX_5, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SEC_TDM_TX_6", SECONDARY_TDM_TX_6, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("SEC_TDM_TX_7", SECONDARY_TDM_TX_7, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TERT_TDM_TX_0", TERTIARY_TDM_TX_0, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TERT_TDM_TX_1", TERTIARY_TDM_TX_1, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TERT_TDM_TX_2", TERTIARY_TDM_TX_2, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TERT_TDM_TX_3", TERTIARY_TDM_TX_3, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TERT_TDM_TX_4", TERTIARY_TDM_TX_4, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TERT_TDM_TX_5", TERTIARY_TDM_TX_5, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TERT_TDM_TX_6", TERTIARY_TDM_TX_6, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TERT_TDM_TX_7", TERTIARY_TDM_TX_7, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUAT_TDM_TX_0", QUATERNARY_TDM_TX_0, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUAT_TDM_TX_1", QUATERNARY_TDM_TX_1, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUAT_TDM_TX_2", QUATERNARY_TDM_TX_2, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUAT_TDM_TX_3", QUATERNARY_TDM_TX_3, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUAT_TDM_TX_4", QUATERNARY_TDM_TX_4, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUAT_TDM_TX_5", QUATERNARY_TDM_TX_5, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUAT_TDM_TX_6", QUATERNARY_TDM_TX_6, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUAT_TDM_TX_7", QUATERNARY_TDM_TX_7, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUIN_TDM_TX_0", QUINARY_TDM_TX_0, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUIN_TDM_TX_1", QUINARY_TDM_TX_1, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUIN_TDM_TX_2", QUINARY_TDM_TX_2, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUIN_TDM_TX_3", QUINARY_TDM_TX_3, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUIN_TDM_TX_4", QUINARY_TDM_TX_4, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUIN_TDM_TX_5", QUINARY_TDM_TX_5, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUIN_TDM_TX_6", QUINARY_TDM_TX_6, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("QUIN_TDM_TX_7", QUINARY_TDM_TX_7, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("WSA_CODEC_DMA_TX_0", WSA_CODEC_DMA_TX_0, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("WSA_CODEC_DMA_TX_1", WSA_CODEC_DMA_TX_1, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("WSA_CODEC_DMA_TX_2", WSA_CODEC_DMA_TX_2, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("VA_CODEC_DMA_TX_0", VA_CODEC_DMA_TX_0, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("VA_CODEC_DMA_TX_1", VA_CODEC_DMA_TX_1, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("VA_CODEC_DMA_TX_2", VA_CODEC_DMA_TX_2, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TX_CODEC_DMA_TX_0", TX_CODEC_DMA_TX_0, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TX_CODEC_DMA_TX_1", TX_CODEC_DMA_TX_1, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TX_CODEC_DMA_TX_2", TX_CODEC_DMA_TX_2, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TX_CODEC_DMA_TX_3", TX_CODEC_DMA_TX_3, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TX_CODEC_DMA_TX_4", TX_CODEC_DMA_TX_4, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), \ + SOC_SINGLE_EXT("TX_CODEC_DMA_TX_5", TX_CODEC_DMA_TX_5, \ + id, 1, 0, msm_routing_get_audio_mixer, \ + msm_routing_put_audio_mixer), + +struct session_data { + int state; + int port_id; + int path_type; + int app_type; + int acdb_id; + int sample_rate; + int bits_per_sample; + int channels; + int perf_mode; + int numcopps; + int fedai_id; + unsigned long copp_map; + struct q6copp *copps[MAX_COPPS_PER_PORT]; +}; + +struct msm_routing_data { + struct session_data sessions[MAX_SESSIONS]; + struct session_data port_data[AFE_MAX_PORTS]; + struct device *dev; + struct mutex lock; +}; + +static struct msm_routing_data *routing_data; + +/** + * q6routing_stream_open() - Register a new stream for route setup + * + * @fedai_id: Frontend dai id. + * @perf_mode: Performance mode. + * @stream_id: ASM stream id to map. + * @stream_type: Direction of stream + * + * Return: Will be an negative on error or a zero on success. + */ +int q6routing_stream_open(int fedai_id, int perf_mode, + int stream_id, int stream_type) +{ + int j, topology, num_copps = 0; + struct route_payload payload; + struct q6copp *copp; + int copp_idx; + struct session_data *session, *pdata; + + if (!routing_data) { + pr_err("Routing driver not yet ready\n"); + return -EINVAL; + } + + session = &routing_data->sessions[stream_id - 1]; + pdata = &routing_data->port_data[session->port_id]; + + mutex_lock(&routing_data->lock); + session->fedai_id = fedai_id; + + session->path_type = pdata->path_type; + session->sample_rate = pdata->sample_rate; + session->channels = pdata->channels; + session->bits_per_sample = pdata->bits_per_sample; + + payload.num_copps = 0; /* only RX needs to use payload */ + topology = NULL_COPP_TOPOLOGY; + copp = q6adm_open(routing_data->dev, session->port_id, + session->path_type, session->sample_rate, + session->channels, topology, perf_mode, + session->bits_per_sample, 0, 0); + + if (IS_ERR_OR_NULL(copp)) { + mutex_unlock(&routing_data->lock); + return -EINVAL; + } + + copp_idx = q6adm_get_copp_id(copp); + set_bit(copp_idx, &session->copp_map); + session->copps[copp_idx] = copp; + + for_each_set_bit(j, &session->copp_map, MAX_COPPS_PER_PORT) { + payload.port_id[num_copps] = session->port_id; + payload.copp_idx[num_copps] = j; + num_copps++; + } + + if (num_copps) { + payload.num_copps = num_copps; + payload.session_id = stream_id; + q6adm_matrix_map(routing_data->dev, session->path_type, + payload, perf_mode); + } + mutex_unlock(&routing_data->lock); + + return 0; +} +EXPORT_SYMBOL_GPL(q6routing_stream_open); + +static struct session_data *get_session_from_id(struct msm_routing_data *data, + int fedai_id) +{ + int i; + + for (i = 0; i < MAX_SESSIONS; i++) { + if (fedai_id == data->sessions[i].fedai_id) + return &data->sessions[i]; + } + + return NULL; +} +/** + * q6routing_stream_close() - Deregister a stream + * + * @fedai_id: Frontend dai id. + * @stream_type: Direction of stream + * + * Return: Will be an negative on error or a zero on success. + */ +void q6routing_stream_close(int fedai_id, int stream_type) +{ + struct session_data *session; + int idx; + + session = get_session_from_id(routing_data, fedai_id); + if (!session) + return; + + for_each_set_bit(idx, &session->copp_map, MAX_COPPS_PER_PORT) { + if (session->copps[idx]) { + q6adm_close(routing_data->dev, session->copps[idx]); + session->copps[idx] = NULL; + } + } + + session->fedai_id = -1; + session->copp_map = 0; +} +EXPORT_SYMBOL_GPL(q6routing_stream_close); + +static int msm_routing_get_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + int session_id = mc->shift; + struct snd_soc_component *c = snd_soc_dapm_to_component(dapm); + struct msm_routing_data *priv = dev_get_drvdata(c->dev); + struct session_data *session = &priv->sessions[session_id]; + + if (session->port_id == mc->reg) + ucontrol->value.integer.value[0] = 1; + else + ucontrol->value.integer.value[0] = 0; + + return 0; +} + +static int msm_routing_put_audio_mixer(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = + snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_component *c = snd_soc_dapm_to_component(dapm); + struct msm_routing_data *data = dev_get_drvdata(c->dev); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct snd_soc_dapm_update *update = NULL; + int be_id = mc->reg; + int session_id = mc->shift; + struct session_data *session = &data->sessions[session_id]; + + if (ucontrol->value.integer.value[0]) { + if (session->port_id == be_id) + return 0; + + session->port_id = be_id; + snd_soc_dapm_mixer_update_power(dapm, kcontrol, 1, update); + } else { + if (session->port_id == -1 || session->port_id != be_id) + return 0; + + session->port_id = -1; + snd_soc_dapm_mixer_update_power(dapm, kcontrol, 0, update); + } + + return 1; +} + +static const struct snd_kcontrol_new hdmi_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(HDMI_RX) }; + +static const struct snd_kcontrol_new display_port_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(DISPLAY_PORT_RX) }; + +static const struct snd_kcontrol_new primary_mi2s_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(PRIMARY_MI2S_RX) }; + +static const struct snd_kcontrol_new secondary_mi2s_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SECONDARY_MI2S_RX) }; + +static const struct snd_kcontrol_new quaternary_mi2s_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUATERNARY_MI2S_RX) }; + +static const struct snd_kcontrol_new tertiary_mi2s_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(TERTIARY_MI2S_RX) }; + +static const struct snd_kcontrol_new slimbus_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SLIMBUS_0_RX) }; + +static const struct snd_kcontrol_new slimbus_1_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SLIMBUS_1_RX) }; + +static const struct snd_kcontrol_new slimbus_2_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SLIMBUS_2_RX) }; + +static const struct snd_kcontrol_new slimbus_3_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SLIMBUS_3_RX) }; + +static const struct snd_kcontrol_new slimbus_4_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SLIMBUS_4_RX) }; + +static const struct snd_kcontrol_new slimbus_5_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SLIMBUS_5_RX) }; + +static const struct snd_kcontrol_new slimbus_6_rx_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SLIMBUS_6_RX) }; + +static const struct snd_kcontrol_new pri_tdm_rx_0_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_0) }; + +static const struct snd_kcontrol_new pri_tdm_rx_1_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_1) }; + +static const struct snd_kcontrol_new pri_tdm_rx_2_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_2) }; + +static const struct snd_kcontrol_new pri_tdm_rx_3_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_3) }; + +static const struct snd_kcontrol_new pri_tdm_rx_4_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_4) }; + +static const struct snd_kcontrol_new pri_tdm_rx_5_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_5) }; + +static const struct snd_kcontrol_new pri_tdm_rx_6_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_6) }; + +static const struct snd_kcontrol_new pri_tdm_rx_7_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(PRIMARY_TDM_RX_7) }; + +static const struct snd_kcontrol_new sec_tdm_rx_0_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_0) }; + +static const struct snd_kcontrol_new sec_tdm_rx_1_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_1) }; + +static const struct snd_kcontrol_new sec_tdm_rx_2_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_2) }; + +static const struct snd_kcontrol_new sec_tdm_rx_3_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_3) }; + +static const struct snd_kcontrol_new sec_tdm_rx_4_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_4) }; + +static const struct snd_kcontrol_new sec_tdm_rx_5_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_5) }; + +static const struct snd_kcontrol_new sec_tdm_rx_6_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_6) }; + +static const struct snd_kcontrol_new sec_tdm_rx_7_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(SECONDARY_TDM_RX_7) }; + +static const struct snd_kcontrol_new tert_tdm_rx_0_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_0) }; + +static const struct snd_kcontrol_new tert_tdm_rx_1_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_1) }; + +static const struct snd_kcontrol_new tert_tdm_rx_2_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_2) }; + +static const struct snd_kcontrol_new tert_tdm_rx_3_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_3) }; + +static const struct snd_kcontrol_new tert_tdm_rx_4_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_4) }; + +static const struct snd_kcontrol_new tert_tdm_rx_5_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_5) }; + +static const struct snd_kcontrol_new tert_tdm_rx_6_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_6) }; + +static const struct snd_kcontrol_new tert_tdm_rx_7_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(TERTIARY_TDM_RX_7) }; + +static const struct snd_kcontrol_new quat_tdm_rx_0_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_0) }; + +static const struct snd_kcontrol_new quat_tdm_rx_1_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_1) }; + +static const struct snd_kcontrol_new quat_tdm_rx_2_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_2) }; + +static const struct snd_kcontrol_new quat_tdm_rx_3_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_3) }; + +static const struct snd_kcontrol_new quat_tdm_rx_4_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_4) }; + +static const struct snd_kcontrol_new quat_tdm_rx_5_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_5) }; + +static const struct snd_kcontrol_new quat_tdm_rx_6_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_6) }; + +static const struct snd_kcontrol_new quat_tdm_rx_7_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUATERNARY_TDM_RX_7) }; + +static const struct snd_kcontrol_new quin_tdm_rx_0_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_0) }; + +static const struct snd_kcontrol_new quin_tdm_rx_1_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_1) }; + +static const struct snd_kcontrol_new quin_tdm_rx_2_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_2) }; + +static const struct snd_kcontrol_new quin_tdm_rx_3_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_3) }; + +static const struct snd_kcontrol_new quin_tdm_rx_4_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_4) }; + +static const struct snd_kcontrol_new quin_tdm_rx_5_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_5) }; + +static const struct snd_kcontrol_new quin_tdm_rx_6_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_6) }; + +static const struct snd_kcontrol_new quin_tdm_rx_7_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(QUINARY_TDM_RX_7) }; + +static const struct snd_kcontrol_new wsa_codec_dma_rx_0_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(WSA_CODEC_DMA_RX_0) }; + +static const struct snd_kcontrol_new wsa_codec_dma_rx_1_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(WSA_CODEC_DMA_RX_1) }; + +static const struct snd_kcontrol_new rx_codec_dma_rx_0_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(RX_CODEC_DMA_RX_0) }; + +static const struct snd_kcontrol_new rx_codec_dma_rx_1_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(RX_CODEC_DMA_RX_1) }; + +static const struct snd_kcontrol_new rx_codec_dma_rx_2_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(RX_CODEC_DMA_RX_2) }; + +static const struct snd_kcontrol_new rx_codec_dma_rx_3_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(RX_CODEC_DMA_RX_3) }; + +static const struct snd_kcontrol_new rx_codec_dma_rx_4_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(RX_CODEC_DMA_RX_4) }; + +static const struct snd_kcontrol_new rx_codec_dma_rx_5_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(RX_CODEC_DMA_RX_5) }; + +static const struct snd_kcontrol_new rxcodec_dma_rx_6_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(RX_CODEC_DMA_RX_6) }; + +static const struct snd_kcontrol_new rx_codec_dma_rx_7_mixer_controls[] = { + Q6ROUTING_RX_MIXERS(RX_CODEC_DMA_RX_7) }; + + +static const struct snd_kcontrol_new mmul1_mixer_controls[] = { + Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA1) }; + +static const struct snd_kcontrol_new mmul2_mixer_controls[] = { + Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA2) }; + +static const struct snd_kcontrol_new mmul3_mixer_controls[] = { + Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA3) }; + +static const struct snd_kcontrol_new mmul4_mixer_controls[] = { + Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA4) }; + +static const struct snd_kcontrol_new mmul5_mixer_controls[] = { + Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA5) }; + +static const struct snd_kcontrol_new mmul6_mixer_controls[] = { + Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA6) }; + +static const struct snd_kcontrol_new mmul7_mixer_controls[] = { + Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA7) }; + +static const struct snd_kcontrol_new mmul8_mixer_controls[] = { + Q6ROUTING_TX_MIXERS(MSM_FRONTEND_DAI_MULTIMEDIA8) }; + +static const struct snd_soc_dapm_widget msm_qdsp6_widgets[] = { + /* Mixer definitions */ + SND_SOC_DAPM_MIXER("HDMI Mixer", SND_SOC_NOPM, 0, 0, + hdmi_mixer_controls, + ARRAY_SIZE(hdmi_mixer_controls)), + + SND_SOC_DAPM_MIXER("DISPLAY_PORT_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + display_port_mixer_controls, + ARRAY_SIZE(display_port_mixer_controls)), + + SND_SOC_DAPM_MIXER("SLIMBUS_0_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_rx_mixer_controls, + ARRAY_SIZE(slimbus_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_1_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_1_rx_mixer_controls, + ARRAY_SIZE(slimbus_1_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_2_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_2_rx_mixer_controls, + ARRAY_SIZE(slimbus_2_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_3_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_3_rx_mixer_controls, + ARRAY_SIZE(slimbus_3_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_4_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_4_rx_mixer_controls, + ARRAY_SIZE(slimbus_4_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_5_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_5_rx_mixer_controls, + ARRAY_SIZE(slimbus_5_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SLIMBUS_6_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + slimbus_6_rx_mixer_controls, + ARRAY_SIZE(slimbus_6_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("PRI_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + primary_mi2s_rx_mixer_controls, + ARRAY_SIZE(primary_mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + secondary_mi2s_rx_mixer_controls, + ARRAY_SIZE(secondary_mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("QUAT_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + quaternary_mi2s_rx_mixer_controls, + ARRAY_SIZE(quaternary_mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("TERT_MI2S_RX Audio Mixer", SND_SOC_NOPM, 0, 0, + tertiary_mi2s_rx_mixer_controls, + ARRAY_SIZE(tertiary_mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0, + pri_tdm_rx_0_mixer_controls, + ARRAY_SIZE(pri_tdm_rx_0_mixer_controls)), + SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0, + pri_tdm_rx_1_mixer_controls, + ARRAY_SIZE(pri_tdm_rx_1_mixer_controls)), + SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0, + pri_tdm_rx_2_mixer_controls, + ARRAY_SIZE(pri_tdm_rx_2_mixer_controls)), + SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0, + pri_tdm_rx_3_mixer_controls, + ARRAY_SIZE(pri_tdm_rx_3_mixer_controls)), + SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0, + pri_tdm_rx_4_mixer_controls, + ARRAY_SIZE(pri_tdm_rx_4_mixer_controls)), + SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0, + pri_tdm_rx_5_mixer_controls, + ARRAY_SIZE(pri_tdm_rx_5_mixer_controls)), + SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0, + pri_tdm_rx_6_mixer_controls, + ARRAY_SIZE(pri_tdm_rx_6_mixer_controls)), + SND_SOC_DAPM_MIXER("PRIMARY_TDM_RX_7 Audio Mixer", SND_SOC_NOPM, 0, 0, + pri_tdm_rx_7_mixer_controls, + ARRAY_SIZE(pri_tdm_rx_7_mixer_controls)), + + SND_SOC_DAPM_MIXER("SEC_TDM_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0, + sec_tdm_rx_0_mixer_controls, + ARRAY_SIZE(sec_tdm_rx_0_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_TDM_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0, + sec_tdm_rx_1_mixer_controls, + ARRAY_SIZE(sec_tdm_rx_1_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_TDM_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0, + sec_tdm_rx_2_mixer_controls, + ARRAY_SIZE(sec_tdm_rx_2_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_TDM_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0, + sec_tdm_rx_3_mixer_controls, + ARRAY_SIZE(sec_tdm_rx_3_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_TDM_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0, + sec_tdm_rx_4_mixer_controls, + ARRAY_SIZE(sec_tdm_rx_4_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_TDM_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0, + sec_tdm_rx_5_mixer_controls, + ARRAY_SIZE(sec_tdm_rx_5_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_TDM_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0, + sec_tdm_rx_6_mixer_controls, + ARRAY_SIZE(sec_tdm_rx_6_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_TDM_RX_7 Audio Mixer", SND_SOC_NOPM, 0, 0, + sec_tdm_rx_7_mixer_controls, + ARRAY_SIZE(sec_tdm_rx_7_mixer_controls)), + + SND_SOC_DAPM_MIXER("TERT_TDM_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0, + tert_tdm_rx_0_mixer_controls, + ARRAY_SIZE(tert_tdm_rx_0_mixer_controls)), + SND_SOC_DAPM_MIXER("TERT_TDM_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0, + tert_tdm_rx_1_mixer_controls, + ARRAY_SIZE(tert_tdm_rx_1_mixer_controls)), + SND_SOC_DAPM_MIXER("TERT_TDM_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0, + tert_tdm_rx_2_mixer_controls, + ARRAY_SIZE(tert_tdm_rx_2_mixer_controls)), + SND_SOC_DAPM_MIXER("TERT_TDM_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0, + tert_tdm_rx_3_mixer_controls, + ARRAY_SIZE(tert_tdm_rx_3_mixer_controls)), + SND_SOC_DAPM_MIXER("TERT_TDM_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0, + tert_tdm_rx_4_mixer_controls, + ARRAY_SIZE(tert_tdm_rx_4_mixer_controls)), + SND_SOC_DAPM_MIXER("TERT_TDM_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0, + tert_tdm_rx_5_mixer_controls, + ARRAY_SIZE(tert_tdm_rx_5_mixer_controls)), + SND_SOC_DAPM_MIXER("TERT_TDM_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0, + tert_tdm_rx_6_mixer_controls, + ARRAY_SIZE(tert_tdm_rx_6_mixer_controls)), + SND_SOC_DAPM_MIXER("TERT_TDM_RX_7 Audio Mixer", SND_SOC_NOPM, 0, 0, + tert_tdm_rx_7_mixer_controls, + ARRAY_SIZE(tert_tdm_rx_7_mixer_controls)), + + SND_SOC_DAPM_MIXER("QUAT_TDM_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0, + quat_tdm_rx_0_mixer_controls, + ARRAY_SIZE(quat_tdm_rx_0_mixer_controls)), + SND_SOC_DAPM_MIXER("QUAT_TDM_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0, + quat_tdm_rx_1_mixer_controls, + ARRAY_SIZE(quat_tdm_rx_1_mixer_controls)), + SND_SOC_DAPM_MIXER("QUAT_TDM_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0, + quat_tdm_rx_2_mixer_controls, + ARRAY_SIZE(quat_tdm_rx_2_mixer_controls)), + SND_SOC_DAPM_MIXER("QUAT_TDM_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0, + quat_tdm_rx_3_mixer_controls, + ARRAY_SIZE(quat_tdm_rx_3_mixer_controls)), + SND_SOC_DAPM_MIXER("QUAT_TDM_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0, + quat_tdm_rx_4_mixer_controls, + ARRAY_SIZE(quat_tdm_rx_4_mixer_controls)), + SND_SOC_DAPM_MIXER("QUAT_TDM_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0, + quat_tdm_rx_5_mixer_controls, + ARRAY_SIZE(quat_tdm_rx_5_mixer_controls)), + SND_SOC_DAPM_MIXER("QUAT_TDM_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0, + quat_tdm_rx_6_mixer_controls, + ARRAY_SIZE(quat_tdm_rx_6_mixer_controls)), + SND_SOC_DAPM_MIXER("QUAT_TDM_RX_7 Audio Mixer", SND_SOC_NOPM, 0, 0, + quat_tdm_rx_7_mixer_controls, + ARRAY_SIZE(quat_tdm_rx_7_mixer_controls)), + + SND_SOC_DAPM_MIXER("QUIN_TDM_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0, + quin_tdm_rx_0_mixer_controls, + ARRAY_SIZE(quin_tdm_rx_0_mixer_controls)), + SND_SOC_DAPM_MIXER("QUIN_TDM_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0, + quin_tdm_rx_1_mixer_controls, + ARRAY_SIZE(quin_tdm_rx_1_mixer_controls)), + SND_SOC_DAPM_MIXER("QUIN_TDM_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0, + quin_tdm_rx_2_mixer_controls, + ARRAY_SIZE(quin_tdm_rx_2_mixer_controls)), + SND_SOC_DAPM_MIXER("QUIN_TDM_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0, + quin_tdm_rx_3_mixer_controls, + ARRAY_SIZE(quin_tdm_rx_3_mixer_controls)), + SND_SOC_DAPM_MIXER("QUIN_TDM_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0, + quin_tdm_rx_4_mixer_controls, + ARRAY_SIZE(quin_tdm_rx_4_mixer_controls)), + SND_SOC_DAPM_MIXER("QUIN_TDM_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0, + quin_tdm_rx_5_mixer_controls, + ARRAY_SIZE(quin_tdm_rx_5_mixer_controls)), + SND_SOC_DAPM_MIXER("QUIN_TDM_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0, + quin_tdm_rx_6_mixer_controls, + ARRAY_SIZE(quin_tdm_rx_6_mixer_controls)), + SND_SOC_DAPM_MIXER("QUIN_TDM_RX_7 Audio Mixer", SND_SOC_NOPM, 0, 0, + quin_tdm_rx_7_mixer_controls, + ARRAY_SIZE(quin_tdm_rx_7_mixer_controls)), + + SND_SOC_DAPM_MIXER("WSA_CODEC_DMA_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0, + wsa_codec_dma_rx_0_mixer_controls, + ARRAY_SIZE(wsa_codec_dma_rx_0_mixer_controls)), + SND_SOC_DAPM_MIXER("WSA_CODEC_DMA_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0, + wsa_codec_dma_rx_1_mixer_controls, + ARRAY_SIZE(wsa_codec_dma_rx_1_mixer_controls)), + SND_SOC_DAPM_MIXER("RX_CODEC_DMA_RX_0 Audio Mixer", SND_SOC_NOPM, 0, 0, + rx_codec_dma_rx_0_mixer_controls, + ARRAY_SIZE(rx_codec_dma_rx_0_mixer_controls)), + SND_SOC_DAPM_MIXER("RX_CODEC_DMA_RX_1 Audio Mixer", SND_SOC_NOPM, 0, 0, + rx_codec_dma_rx_1_mixer_controls, + ARRAY_SIZE(rx_codec_dma_rx_1_mixer_controls)), + SND_SOC_DAPM_MIXER("RX_CODEC_DMA_RX_2 Audio Mixer", SND_SOC_NOPM, 0, 0, + rx_codec_dma_rx_2_mixer_controls, + ARRAY_SIZE(rx_codec_dma_rx_2_mixer_controls)), + SND_SOC_DAPM_MIXER("RX_CODEC_DMA_RX_3 Audio Mixer", SND_SOC_NOPM, 0, 0, + rx_codec_dma_rx_3_mixer_controls, + ARRAY_SIZE(rx_codec_dma_rx_3_mixer_controls)), + SND_SOC_DAPM_MIXER("RX_CODEC_DMA_RX_4 Audio Mixer", SND_SOC_NOPM, 0, 0, + rx_codec_dma_rx_4_mixer_controls, + ARRAY_SIZE(rx_codec_dma_rx_4_mixer_controls)), + SND_SOC_DAPM_MIXER("RX_CODEC_DMA_RX_5 Audio Mixer", SND_SOC_NOPM, 0, 0, + rx_codec_dma_rx_5_mixer_controls, + ARRAY_SIZE(rx_codec_dma_rx_5_mixer_controls)), + SND_SOC_DAPM_MIXER("RX_CODEC_DMA_RX_6 Audio Mixer", SND_SOC_NOPM, 0, 0, + rxcodec_dma_rx_6_mixer_controls, + ARRAY_SIZE(rxcodec_dma_rx_6_mixer_controls)), + SND_SOC_DAPM_MIXER("RX_CODEC_DMA_RX_7 Audio Mixer", SND_SOC_NOPM, 0, 0, + rx_codec_dma_rx_7_mixer_controls, + ARRAY_SIZE(rx_codec_dma_rx_7_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia1 Mixer", SND_SOC_NOPM, 0, 0, + mmul1_mixer_controls, ARRAY_SIZE(mmul1_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia2 Mixer", SND_SOC_NOPM, 0, 0, + mmul2_mixer_controls, ARRAY_SIZE(mmul2_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia3 Mixer", SND_SOC_NOPM, 0, 0, + mmul3_mixer_controls, ARRAY_SIZE(mmul3_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia4 Mixer", SND_SOC_NOPM, 0, 0, + mmul4_mixer_controls, ARRAY_SIZE(mmul4_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia5 Mixer", SND_SOC_NOPM, 0, 0, + mmul5_mixer_controls, ARRAY_SIZE(mmul5_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia6 Mixer", SND_SOC_NOPM, 0, 0, + mmul6_mixer_controls, ARRAY_SIZE(mmul6_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia7 Mixer", SND_SOC_NOPM, 0, 0, + mmul7_mixer_controls, ARRAY_SIZE(mmul7_mixer_controls)), + SND_SOC_DAPM_MIXER("MultiMedia8 Mixer", SND_SOC_NOPM, 0, 0, + mmul8_mixer_controls, ARRAY_SIZE(mmul8_mixer_controls)), + +}; + +static const struct snd_soc_dapm_route intercon[] = { + Q6ROUTING_RX_DAPM_ROUTE("HDMI Mixer", "HDMI_RX"), + Q6ROUTING_RX_DAPM_ROUTE("DISPLAY_PORT_RX Audio Mixer", + "DISPLAY_PORT_RX"), + Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_0_RX Audio Mixer", "SLIMBUS_0_RX"), + Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_1_RX Audio Mixer", "SLIMBUS_1_RX"), + Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_2_RX Audio Mixer", "SLIMBUS_2_RX"), + Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_3_RX Audio Mixer", "SLIMBUS_3_RX"), + Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_4_RX Audio Mixer", "SLIMBUS_4_RX"), + Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_5_RX Audio Mixer", "SLIMBUS_5_RX"), + Q6ROUTING_RX_DAPM_ROUTE("SLIMBUS_6_RX Audio Mixer", "SLIMBUS_6_RX"), + Q6ROUTING_RX_DAPM_ROUTE("QUAT_MI2S_RX Audio Mixer", "QUAT_MI2S_RX"), + Q6ROUTING_RX_DAPM_ROUTE("TERT_MI2S_RX Audio Mixer", "TERT_MI2S_RX"), + Q6ROUTING_RX_DAPM_ROUTE("SEC_MI2S_RX Audio Mixer", "SEC_MI2S_RX"), + Q6ROUTING_RX_DAPM_ROUTE("PRI_MI2S_RX Audio Mixer", "PRI_MI2S_RX"), + Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_0 Audio Mixer", + "PRIMARY_TDM_RX_0"), + Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_1 Audio Mixer", + "PRIMARY_TDM_RX_1"), + Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_2 Audio Mixer", + "PRIMARY_TDM_RX_2"), + Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_3 Audio Mixer", + "PRIMARY_TDM_RX_3"), + Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_4 Audio Mixer", + "PRIMARY_TDM_RX_4"), + Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_5 Audio Mixer", + "PRIMARY_TDM_RX_5"), + Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_6 Audio Mixer", + "PRIMARY_TDM_RX_6"), + Q6ROUTING_RX_DAPM_ROUTE("PRIMARY_TDM_RX_7 Audio Mixer", + "PRIMARY_TDM_RX_7"), + Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_0 Audio Mixer", "SEC_TDM_RX_0"), + Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_1 Audio Mixer", "SEC_TDM_RX_1"), + Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_2 Audio Mixer", "SEC_TDM_RX_2"), + Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_3 Audio Mixer", "SEC_TDM_RX_3"), + Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_4 Audio Mixer", "SEC_TDM_RX_4"), + Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_5 Audio Mixer", "SEC_TDM_RX_5"), + Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_6 Audio Mixer", "SEC_TDM_RX_6"), + Q6ROUTING_RX_DAPM_ROUTE("SEC_TDM_RX_7 Audio Mixer", "SEC_TDM_RX_7"), + Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_0 Audio Mixer", "TERT_TDM_RX_0"), + Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_1 Audio Mixer", "TERT_TDM_RX_1"), + Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_2 Audio Mixer", "TERT_TDM_RX_2"), + Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_3 Audio Mixer", "TERT_TDM_RX_3"), + Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_4 Audio Mixer", "TERT_TDM_RX_4"), + Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_5 Audio Mixer", "TERT_TDM_RX_5"), + Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_6 Audio Mixer", "TERT_TDM_RX_6"), + Q6ROUTING_RX_DAPM_ROUTE("TERT_TDM_RX_7 Audio Mixer", "TERT_TDM_RX_7"), + Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_0 Audio Mixer", "QUAT_TDM_RX_0"), + Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_1 Audio Mixer", "QUAT_TDM_RX_1"), + Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_2 Audio Mixer", "QUAT_TDM_RX_2"), + Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_3 Audio Mixer", "QUAT_TDM_RX_3"), + Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_4 Audio Mixer", "QUAT_TDM_RX_4"), + Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_5 Audio Mixer", "QUAT_TDM_RX_5"), + Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_6 Audio Mixer", "QUAT_TDM_RX_6"), + Q6ROUTING_RX_DAPM_ROUTE("QUAT_TDM_RX_7 Audio Mixer", "QUAT_TDM_RX_7"), + Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_0 Audio Mixer", "QUIN_TDM_RX_0"), + Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_1 Audio Mixer", "QUIN_TDM_RX_1"), + Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_2 Audio Mixer", "QUIN_TDM_RX_2"), + Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_3 Audio Mixer", "QUIN_TDM_RX_3"), + Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_4 Audio Mixer", "QUIN_TDM_RX_4"), + Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_5 Audio Mixer", "QUIN_TDM_RX_5"), + Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_6 Audio Mixer", "QUIN_TDM_RX_6"), + Q6ROUTING_RX_DAPM_ROUTE("QUIN_TDM_RX_7 Audio Mixer", "QUIN_TDM_RX_7"), + Q6ROUTING_RX_DAPM_ROUTE("WSA_CODEC_DMA_RX_0 Audio Mixer", "WSA_CODEC_DMA_RX_0"), + Q6ROUTING_RX_DAPM_ROUTE("WSA_CODEC_DMA_RX_1 Audio Mixer", "WSA_CODEC_DMA_RX_1"), + Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_0 Audio Mixer", "RX_CODEC_DMA_RX_0"), + Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_1 Audio Mixer", "RX_CODEC_DMA_RX_1"), + Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_2 Audio Mixer", "RX_CODEC_DMA_RX_2"), + Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_3 Audio Mixer", "RX_CODEC_DMA_RX_3"), + Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_4 Audio Mixer", "RX_CODEC_DMA_RX_4"), + Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_5 Audio Mixer", "RX_CODEC_DMA_RX_5"), + Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_6 Audio Mixer", "RX_CODEC_DMA_RX_6"), + Q6ROUTING_RX_DAPM_ROUTE("RX_CODEC_DMA_RX_7 Audio Mixer", "RX_CODEC_DMA_RX_7"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia1 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia2 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia3 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia4 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia5 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia6 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia7 Mixer"), + Q6ROUTING_TX_DAPM_ROUTE("MultiMedia8 Mixer"), + + {"MM_UL1", NULL, "MultiMedia1 Mixer"}, + {"MM_UL2", NULL, "MultiMedia2 Mixer"}, + {"MM_UL3", NULL, "MultiMedia3 Mixer"}, + {"MM_UL4", NULL, "MultiMedia4 Mixer"}, + {"MM_UL5", NULL, "MultiMedia5 Mixer"}, + {"MM_UL6", NULL, "MultiMedia6 Mixer"}, + {"MM_UL7", NULL, "MultiMedia7 Mixer"}, + {"MM_UL8", NULL, "MultiMedia8 Mixer"}, +}; + +static int routing_hw_params(struct snd_soc_component *component, + struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct msm_routing_data *data = dev_get_drvdata(component->dev); + unsigned int be_id = asoc_rtd_to_cpu(rtd, 0)->id; + struct session_data *session; + int path_type; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + path_type = ADM_PATH_PLAYBACK; + else + path_type = ADM_PATH_LIVE_REC; + + if (be_id >= AFE_MAX_PORTS) + return -EINVAL; + + session = &data->port_data[be_id]; + + mutex_lock(&data->lock); + + session->path_type = path_type; + session->sample_rate = params_rate(params); + session->channels = params_channels(params); + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + session->bits_per_sample = 16; + break; + case SNDRV_PCM_FORMAT_S24_LE: + session->bits_per_sample = 24; + break; + default: + break; + } + + mutex_unlock(&data->lock); + return 0; +} + +static int msm_routing_probe(struct snd_soc_component *c) +{ + int i; + + for (i = 0; i < MAX_SESSIONS; i++) { + routing_data->sessions[i].port_id = -1; + routing_data->sessions[i].fedai_id = -1; + } + + return 0; +} + +static unsigned int q6routing_reg_read(struct snd_soc_component *component, + unsigned int reg) +{ + /* default value */ + return 0; +} + +static int q6routing_reg_write(struct snd_soc_component *component, + unsigned int reg, unsigned int val) +{ + /* dummy */ + return 0; +} + +static const struct snd_soc_component_driver msm_soc_routing_component = { + .probe = msm_routing_probe, + .name = DRV_NAME, + .hw_params = routing_hw_params, + .dapm_widgets = msm_qdsp6_widgets, + .num_dapm_widgets = ARRAY_SIZE(msm_qdsp6_widgets), + .dapm_routes = intercon, + .num_dapm_routes = ARRAY_SIZE(intercon), + .read = q6routing_reg_read, + .write = q6routing_reg_write, +}; + +static int q6pcm_routing_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + routing_data = kzalloc(sizeof(*routing_data), GFP_KERNEL); + if (!routing_data) + return -ENOMEM; + + routing_data->dev = dev; + + mutex_init(&routing_data->lock); + dev_set_drvdata(dev, routing_data); + + return devm_snd_soc_register_component(dev, &msm_soc_routing_component, + NULL, 0); +} + +static int q6pcm_routing_remove(struct platform_device *pdev) +{ + kfree(routing_data); + routing_data = NULL; + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id q6pcm_routing_device_id[] = { + { .compatible = "qcom,q6adm-routing" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6pcm_routing_device_id); +#endif + +static struct platform_driver q6pcm_routing_platform_driver = { + .driver = { + .name = "q6routing", + .of_match_table = of_match_ptr(q6pcm_routing_device_id), + }, + .probe = q6pcm_routing_probe, + .remove = q6pcm_routing_remove, +}; +module_platform_driver(q6pcm_routing_platform_driver); + +MODULE_DESCRIPTION("Q6 Routing platform"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6routing.h b/sound/soc/qcom/qdsp6/q6routing.h new file mode 100644 index 000000000..35514e651 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6routing.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _Q6_PCM_ROUTING_H +#define _Q6_PCM_ROUTING_H + +int q6routing_stream_open(int fedai_id, int perf_mode, + int stream_id, int stream_type); +void q6routing_stream_close(int fedai_id, int stream_type); + +#endif /*_Q6_PCM_ROUTING_H */ diff --git a/sound/soc/qcom/sdm845.c b/sound/soc/qcom/sdm845.c new file mode 100644 index 000000000..6be7a3293 --- /dev/null +++ b/sound/soc/qcom/sdm845.c @@ -0,0 +1,591 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/of_device.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/jack.h> +#include <sound/soc.h> +#include <linux/soundwire/sdw.h> +#include <uapi/linux/input-event-codes.h> +#include "common.h" +#include "qdsp6/q6afe.h" +#include "../codecs/rt5663.h" + +#define DRIVER_NAME "sdm845" +#define DEFAULT_SAMPLE_RATE_48K 48000 +#define DEFAULT_MCLK_RATE 24576000 +#define TDM_BCLK_RATE 6144000 +#define MI2S_BCLK_RATE 1536000 +#define LEFT_SPK_TDM_TX_MASK 0x30 +#define RIGHT_SPK_TDM_TX_MASK 0xC0 +#define SPK_TDM_RX_MASK 0x03 +#define NUM_TDM_SLOTS 8 +#define SLIM_MAX_TX_PORTS 16 +#define SLIM_MAX_RX_PORTS 13 +#define WCD934X_DEFAULT_MCLK_RATE 9600000 + +struct sdm845_snd_data { + struct snd_soc_jack jack; + bool jack_setup; + bool stream_prepared[AFE_PORT_MAX]; + struct snd_soc_card *card; + uint32_t pri_mi2s_clk_count; + uint32_t sec_mi2s_clk_count; + uint32_t quat_tdm_clk_count; + struct sdw_stream_runtime *sruntime[AFE_PORT_MAX]; +}; + +static unsigned int tdm_slot_offset[8] = {0, 4, 8, 12, 16, 20, 24, 28}; + +static int sdm845_slim_snd_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai; + struct sdm845_snd_data *pdata = snd_soc_card_get_drvdata(rtd->card); + u32 rx_ch[SLIM_MAX_RX_PORTS], tx_ch[SLIM_MAX_TX_PORTS]; + struct sdw_stream_runtime *sruntime; + u32 rx_ch_cnt = 0, tx_ch_cnt = 0; + int ret = 0, i; + + for_each_rtd_codec_dais(rtd, i, codec_dai) { + sruntime = snd_soc_dai_get_stream(codec_dai, + substream->stream); + if (sruntime != ERR_PTR(-ENOTSUPP)) + pdata->sruntime[cpu_dai->id] = sruntime; + + ret = snd_soc_dai_get_channel_map(codec_dai, + &tx_ch_cnt, tx_ch, &rx_ch_cnt, rx_ch); + + if (ret != 0 && ret != -ENOTSUPP) { + pr_err("failed to get codec chan map, err:%d\n", ret); + return ret; + } else if (ret == -ENOTSUPP) { + /* Ignore unsupported */ + continue; + } + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, NULL, + rx_ch_cnt, rx_ch); + else + ret = snd_soc_dai_set_channel_map(cpu_dai, tx_ch_cnt, + tx_ch, 0, NULL); + } + + return 0; +} + +static int sdm845_tdm_snd_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai; + int ret = 0, j; + int channels, slot_width; + + switch (params_format(params)) { + case SNDRV_PCM_FORMAT_S16_LE: + slot_width = 16; + break; + default: + dev_err(rtd->dev, "%s: invalid param format 0x%x\n", + __func__, params_format(params)); + return -EINVAL; + } + + channels = params_channels(params); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0x3, + 8, slot_width); + if (ret < 0) { + dev_err(rtd->dev, "%s: failed to set tdm slot, err:%d\n", + __func__, ret); + goto end; + } + + ret = snd_soc_dai_set_channel_map(cpu_dai, 0, NULL, + channels, tdm_slot_offset); + if (ret < 0) { + dev_err(rtd->dev, "%s: failed to set channel map, err:%d\n", + __func__, ret); + goto end; + } + } else { + ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0xf, 0, + 8, slot_width); + if (ret < 0) { + dev_err(rtd->dev, "%s: failed to set tdm slot, err:%d\n", + __func__, ret); + goto end; + } + + ret = snd_soc_dai_set_channel_map(cpu_dai, channels, + tdm_slot_offset, 0, NULL); + if (ret < 0) { + dev_err(rtd->dev, "%s: failed to set channel map, err:%d\n", + __func__, ret); + goto end; + } + } + + for_each_rtd_codec_dais(rtd, j, codec_dai) { + + if (!strcmp(codec_dai->component->name_prefix, "Left")) { + ret = snd_soc_dai_set_tdm_slot( + codec_dai, LEFT_SPK_TDM_TX_MASK, + SPK_TDM_RX_MASK, NUM_TDM_SLOTS, + slot_width); + if (ret < 0) { + dev_err(rtd->dev, + "DEV0 TDM slot err:%d\n", ret); + return ret; + } + } + + if (!strcmp(codec_dai->component->name_prefix, "Right")) { + ret = snd_soc_dai_set_tdm_slot( + codec_dai, RIGHT_SPK_TDM_TX_MASK, + SPK_TDM_RX_MASK, NUM_TDM_SLOTS, + slot_width); + if (ret < 0) { + dev_err(rtd->dev, + "DEV1 TDM slot err:%d\n", ret); + return ret; + } + } + } + +end: + return ret; +} + +static int sdm845_snd_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + int ret = 0; + + switch (cpu_dai->id) { + case PRIMARY_MI2S_RX: + case PRIMARY_MI2S_TX: + /* + * Use ASRC for internal clocks, as PLL rate isn't multiple + * of BCLK. + */ + rt5663_sel_asrc_clk_src( + codec_dai->component, + RT5663_DA_STEREO_FILTER | RT5663_AD_STEREO_FILTER, + RT5663_CLK_SEL_I2S1_ASRC); + ret = snd_soc_dai_set_sysclk( + codec_dai, RT5663_SCLK_S_MCLK, DEFAULT_MCLK_RATE, + SND_SOC_CLOCK_IN); + if (ret < 0) + dev_err(rtd->dev, + "snd_soc_dai_set_sysclk err = %d\n", ret); + break; + case QUATERNARY_TDM_RX_0: + case QUATERNARY_TDM_TX_0: + ret = sdm845_tdm_snd_hw_params(substream, params); + break; + case SLIMBUS_0_RX...SLIMBUS_6_TX: + ret = sdm845_slim_snd_hw_params(substream, params); + break; + case QUATERNARY_MI2S_RX: + break; + default: + pr_err("%s: invalid dai id 0x%x\n", __func__, cpu_dai->id); + break; + } + return ret; +} + +static void sdm845_jack_free(struct snd_jack *jack) +{ + struct snd_soc_component *component = jack->private_data; + + snd_soc_component_set_jack(component, NULL, NULL); +} + +static int sdm845_dai_init(struct snd_soc_pcm_runtime *rtd) +{ + struct snd_soc_component *component; + struct snd_soc_card *card = rtd->card; + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct sdm845_snd_data *pdata = snd_soc_card_get_drvdata(card); + struct snd_jack *jack; + /* + * Codec SLIMBUS configuration + * RX1, RX2, RX3, RX4, RX5, RX6, RX7, RX8, RX9, RX10, RX11, RX12, RX13 + * TX1, TX2, TX3, TX4, TX5, TX6, TX7, TX8, TX9, TX10, TX11, TX12, TX13 + * TX14, TX15, TX16 + */ + unsigned int rx_ch[SLIM_MAX_RX_PORTS] = {144, 145, 146, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156}; + unsigned int tx_ch[SLIM_MAX_TX_PORTS] = {128, 129, 130, 131, 132, 133, + 134, 135, 136, 137, 138, 139, + 140, 141, 142, 143}; + int rval, i; + + + if (!pdata->jack_setup) { + rval = snd_soc_card_jack_new(card, "Headset Jack", + SND_JACK_HEADSET | + SND_JACK_HEADPHONE | + SND_JACK_BTN_0 | SND_JACK_BTN_1 | + SND_JACK_BTN_2 | SND_JACK_BTN_3, + &pdata->jack, NULL, 0); + + if (rval < 0) { + dev_err(card->dev, "Unable to add Headphone Jack\n"); + return rval; + } + + jack = pdata->jack.jack; + + snd_jack_set_key(jack, SND_JACK_BTN_0, KEY_PLAYPAUSE); + snd_jack_set_key(jack, SND_JACK_BTN_1, KEY_VOICECOMMAND); + snd_jack_set_key(jack, SND_JACK_BTN_2, KEY_VOLUMEUP); + snd_jack_set_key(jack, SND_JACK_BTN_3, KEY_VOLUMEDOWN); + pdata->jack_setup = true; + } + + switch (cpu_dai->id) { + case PRIMARY_MI2S_RX: + jack = pdata->jack.jack; + component = codec_dai->component; + + jack->private_data = component; + jack->private_free = sdm845_jack_free; + rval = snd_soc_component_set_jack(component, + &pdata->jack, NULL); + if (rval != 0 && rval != -ENOTSUPP) { + dev_warn(card->dev, "Failed to set jack: %d\n", rval); + return rval; + } + break; + case SLIMBUS_0_RX...SLIMBUS_6_TX: + for_each_rtd_codec_dais(rtd, i, codec_dai) { + rval = snd_soc_dai_set_channel_map(codec_dai, + ARRAY_SIZE(tx_ch), + tx_ch, + ARRAY_SIZE(rx_ch), + rx_ch); + if (rval != 0 && rval != -ENOTSUPP) + return rval; + + snd_soc_dai_set_sysclk(codec_dai, 0, + WCD934X_DEFAULT_MCLK_RATE, + SNDRV_PCM_STREAM_PLAYBACK); + } + break; + default: + break; + } + + return 0; +} + + +static int sdm845_snd_startup(struct snd_pcm_substream *substream) +{ + unsigned int fmt = SND_SOC_DAIFMT_CBS_CFS; + unsigned int codec_dai_fmt = SND_SOC_DAIFMT_CBS_CFS; + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0); + int j; + int ret; + + switch (cpu_dai->id) { + case PRIMARY_MI2S_RX: + case PRIMARY_MI2S_TX: + codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF; + if (++(data->pri_mi2s_clk_count) == 1) { + snd_soc_dai_set_sysclk(cpu_dai, + Q6AFE_LPASS_CLK_ID_MCLK_1, + DEFAULT_MCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); + snd_soc_dai_set_sysclk(cpu_dai, + Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT, + MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); + } + snd_soc_dai_set_fmt(cpu_dai, fmt); + snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); + break; + + case SECONDARY_MI2S_TX: + codec_dai_fmt |= SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_I2S; + if (++(data->sec_mi2s_clk_count) == 1) { + snd_soc_dai_set_sysclk(cpu_dai, + Q6AFE_LPASS_CLK_ID_SEC_MI2S_IBIT, + MI2S_BCLK_RATE, SNDRV_PCM_STREAM_CAPTURE); + } + snd_soc_dai_set_fmt(cpu_dai, fmt); + snd_soc_dai_set_fmt(codec_dai, codec_dai_fmt); + break; + case QUATERNARY_MI2S_RX: + snd_soc_dai_set_sysclk(cpu_dai, + Q6AFE_LPASS_CLK_ID_QUAD_MI2S_IBIT, + MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); + snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_CBS_CFS); + + + break; + + case QUATERNARY_TDM_RX_0: + case QUATERNARY_TDM_TX_0: + if (++(data->quat_tdm_clk_count) == 1) { + snd_soc_dai_set_sysclk(cpu_dai, + Q6AFE_LPASS_CLK_ID_QUAD_TDM_IBIT, + TDM_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); + } + + codec_dai_fmt |= SND_SOC_DAIFMT_IB_NF | SND_SOC_DAIFMT_DSP_B; + + for_each_rtd_codec_dais(rtd, j, codec_dai) { + + if (!strcmp(codec_dai->component->name_prefix, + "Left")) { + ret = snd_soc_dai_set_fmt( + codec_dai, codec_dai_fmt); + if (ret < 0) { + dev_err(rtd->dev, + "Left TDM fmt err:%d\n", ret); + return ret; + } + } + + if (!strcmp(codec_dai->component->name_prefix, + "Right")) { + ret = snd_soc_dai_set_fmt( + codec_dai, codec_dai_fmt); + if (ret < 0) { + dev_err(rtd->dev, + "Right TDM slot err:%d\n", ret); + return ret; + } + } + } + break; + case SLIMBUS_0_RX...SLIMBUS_6_TX: + break; + + default: + pr_err("%s: invalid dai id 0x%x\n", __func__, cpu_dai->id); + break; + } + return 0; +} + +static void sdm845_snd_shutdown(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = rtd->card; + struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + + switch (cpu_dai->id) { + case PRIMARY_MI2S_RX: + case PRIMARY_MI2S_TX: + if (--(data->pri_mi2s_clk_count) == 0) { + snd_soc_dai_set_sysclk(cpu_dai, + Q6AFE_LPASS_CLK_ID_MCLK_1, + 0, SNDRV_PCM_STREAM_PLAYBACK); + snd_soc_dai_set_sysclk(cpu_dai, + Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT, + 0, SNDRV_PCM_STREAM_PLAYBACK); + } + break; + + case SECONDARY_MI2S_TX: + if (--(data->sec_mi2s_clk_count) == 0) { + snd_soc_dai_set_sysclk(cpu_dai, + Q6AFE_LPASS_CLK_ID_SEC_MI2S_IBIT, + 0, SNDRV_PCM_STREAM_CAPTURE); + } + break; + + case QUATERNARY_TDM_RX_0: + case QUATERNARY_TDM_TX_0: + if (--(data->quat_tdm_clk_count) == 0) { + snd_soc_dai_set_sysclk(cpu_dai, + Q6AFE_LPASS_CLK_ID_QUAD_TDM_IBIT, + 0, SNDRV_PCM_STREAM_PLAYBACK); + } + break; + case SLIMBUS_0_RX...SLIMBUS_6_TX: + case QUATERNARY_MI2S_RX: + break; + + default: + pr_err("%s: invalid dai id 0x%x\n", __func__, cpu_dai->id); + break; + } +} + +static int sdm845_snd_prepare(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct sdm845_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id]; + int ret; + + if (!sruntime) + return 0; + + if (data->stream_prepared[cpu_dai->id]) { + sdw_disable_stream(sruntime); + sdw_deprepare_stream(sruntime); + data->stream_prepared[cpu_dai->id] = false; + } + + ret = sdw_prepare_stream(sruntime); + if (ret) + return ret; + + /** + * NOTE: there is a strict hw requirement about the ordering of port + * enables and actual WSA881x PA enable. PA enable should only happen + * after soundwire ports are enabled if not DC on the line is + * accumulated resulting in Click/Pop Noise + * PA enable/mute are handled as part of codec DAPM and digital mute. + */ + + ret = sdw_enable_stream(sruntime); + if (ret) { + sdw_deprepare_stream(sruntime); + return ret; + } + data->stream_prepared[cpu_dai->id] = true; + + return ret; +} + +static int sdm845_snd_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream); + struct sdm845_snd_data *data = snd_soc_card_get_drvdata(rtd->card); + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); + struct sdw_stream_runtime *sruntime = data->sruntime[cpu_dai->id]; + + if (sruntime && data->stream_prepared[cpu_dai->id]) { + sdw_disable_stream(sruntime); + sdw_deprepare_stream(sruntime); + data->stream_prepared[cpu_dai->id] = false; + } + + return 0; +} + +static const struct snd_soc_ops sdm845_be_ops = { + .hw_params = sdm845_snd_hw_params, + .hw_free = sdm845_snd_hw_free, + .prepare = sdm845_snd_prepare, + .startup = sdm845_snd_startup, + .shutdown = sdm845_snd_shutdown, +}; + +static int sdm845_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, + struct snd_pcm_hw_params *params) +{ + struct snd_interval *rate = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = hw_param_interval(params, + SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); + + rate->min = rate->max = DEFAULT_SAMPLE_RATE_48K; + channels->min = channels->max = 2; + snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); + + return 0; +} + +static const struct snd_soc_dapm_widget sdm845_snd_widgets[] = { + SND_SOC_DAPM_HP("Headphone Jack", NULL), + SND_SOC_DAPM_MIC("Headset Mic", NULL), + SND_SOC_DAPM_SPK("Left Spk", NULL), + SND_SOC_DAPM_SPK("Right Spk", NULL), + SND_SOC_DAPM_MIC("Int Mic", NULL), +}; + +static void sdm845_add_ops(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *link; + int i; + + for_each_card_prelinks(card, i, link) { + if (link->no_pcm == 1) { + link->ops = &sdm845_be_ops; + link->be_hw_params_fixup = sdm845_be_hw_params_fixup; + } + link->init = sdm845_dai_init; + } +} + +static int sdm845_snd_platform_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card; + struct sdm845_snd_data *data; + struct device *dev = &pdev->dev; + int ret; + + card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + /* Allocate the private data */ + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + card->driver_name = DRIVER_NAME; + card->dapm_widgets = sdm845_snd_widgets; + card->num_dapm_widgets = ARRAY_SIZE(sdm845_snd_widgets); + card->dev = dev; + card->owner = THIS_MODULE; + dev_set_drvdata(dev, card); + ret = qcom_snd_parse_of(card); + if (ret) + return ret; + + data->card = card; + snd_soc_card_set_drvdata(card, data); + + sdm845_add_ops(card); + return devm_snd_soc_register_card(dev, card); +} + +static const struct of_device_id sdm845_snd_device_id[] = { + { .compatible = "qcom,sdm845-sndcard" }, + { .compatible = "qcom,db845c-sndcard" }, + { .compatible = "lenovo,yoga-c630-sndcard" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sdm845_snd_device_id); + +static struct platform_driver sdm845_snd_driver = { + .probe = sdm845_snd_platform_probe, + .driver = { + .name = "msm-snd-sdm845", + .of_match_table = sdm845_snd_device_id, + }, +}; +module_platform_driver(sdm845_snd_driver); + +MODULE_DESCRIPTION("sdm845 ASoC Machine Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/storm.c b/sound/soc/qcom/storm.c new file mode 100644 index 000000000..80c9cf2f2 --- /dev/null +++ b/sound/soc/qcom/storm.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2010-2011,2013-2015 The Linux Foundation. All rights reserved. + * + * storm.c -- ALSA SoC machine driver for QTi ipq806x-based Storm board + */ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/soc.h> + +#define STORM_SYSCLK_MULT 4 + +static int storm_ops_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *params) +{ + struct snd_soc_pcm_runtime *soc_runtime = asoc_substream_to_rtd(substream); + struct snd_soc_card *card = soc_runtime->card; + snd_pcm_format_t format = params_format(params); + unsigned int rate = params_rate(params); + unsigned int sysclk_freq; + int bitwidth, ret; + + bitwidth = snd_pcm_format_width(format); + if (bitwidth < 0) { + dev_err(card->dev, "invalid bit width given: %d\n", bitwidth); + return bitwidth; + } + + /* + * as the CPU DAI is the I2S bus master and no system clock is needed by + * the MAX98357a DAC, simply set the system clock to be a constant + * multiple of the bit clock for the clock divider + */ + sysclk_freq = rate * bitwidth * 2 * STORM_SYSCLK_MULT; + + ret = snd_soc_dai_set_sysclk(asoc_rtd_to_cpu(soc_runtime, 0), 0, sysclk_freq, 0); + if (ret) { + dev_err(card->dev, "error setting sysclk to %u: %d\n", + sysclk_freq, ret); + return ret; + } + + return 0; +} + +static const struct snd_soc_ops storm_soc_ops = { + .hw_params = storm_ops_hw_params, +}; + +SND_SOC_DAILINK_DEFS(hifi, + DAILINK_COMP_ARRAY(COMP_EMPTY()), + DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "HiFi")), + DAILINK_COMP_ARRAY(COMP_EMPTY())); + +static struct snd_soc_dai_link storm_dai_link = { + .name = "Primary", + .stream_name = "Primary", + .ops = &storm_soc_ops, + SND_SOC_DAILINK_REG(hifi), +}; + +static int storm_parse_of(struct snd_soc_card *card) +{ + struct snd_soc_dai_link *dai_link = card->dai_link; + struct device_node *np = card->dev->of_node; + + dai_link->cpus->of_node = of_parse_phandle(np, "cpu", 0); + if (!dai_link->cpus->of_node) { + dev_err(card->dev, "error getting cpu phandle\n"); + return -EINVAL; + } + dai_link->platforms->of_node = dai_link->cpus->of_node; + + dai_link->codecs->of_node = of_parse_phandle(np, "codec", 0); + if (!dai_link->codecs->of_node) { + dev_err(card->dev, "error getting codec phandle\n"); + return -EINVAL; + } + + return 0; +} + +static int storm_platform_probe(struct platform_device *pdev) +{ + struct snd_soc_card *card; + int ret; + + card = devm_kzalloc(&pdev->dev, sizeof(*card), GFP_KERNEL); + if (!card) + return -ENOMEM; + + card->dev = &pdev->dev; + card->owner = THIS_MODULE; + + ret = snd_soc_of_parse_card_name(card, "qcom,model"); + if (ret) { + dev_err(&pdev->dev, "error parsing card name: %d\n", ret); + return ret; + } + + card->dai_link = &storm_dai_link; + card->num_links = 1; + + ret = storm_parse_of(card); + if (ret) { + dev_err(&pdev->dev, "error resolving dai links: %d\n", ret); + return ret; + } + + ret = devm_snd_soc_register_card(&pdev->dev, card); + if (ret) + dev_err(&pdev->dev, "error registering soundcard: %d\n", ret); + + return ret; + +} + +#ifdef CONFIG_OF +static const struct of_device_id storm_device_id[] = { + { .compatible = "google,storm-audio" }, + {}, +}; +MODULE_DEVICE_TABLE(of, storm_device_id); +#endif + +static struct platform_driver storm_platform_driver = { + .driver = { + .name = "storm-audio", + .of_match_table = + of_match_ptr(storm_device_id), + }, + .probe = storm_platform_probe, +}; +module_platform_driver(storm_platform_driver); + +MODULE_DESCRIPTION("QTi IPQ806x-based Storm Machine Driver"); +MODULE_LICENSE("GPL v2"); |