From: Richard Acayan <mailingradian@gmail.com>
To: Srinivas Kandagatla <srini@kernel.org>,
Liam Girdwood <lgirdwood@gmail.com>,
Mark Brown <broonie@kernel.org>, Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Jaroslav Kysela <perex@perex.cz>, Takashi Iwai <tiwai@suse.com>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
Wesley Cheng <quic_wcheng@quicinc.com>,
Johan Hovold <johan@kernel.org>,
Konrad Dybcio <konradybcio@kernel.org>,
linux-arm-msm@vger.kernel.org, linux-sound@vger.kernel.org,
devicetree@vger.kernel.org
Cc: Nickolay Goppen <setotau@mainlining.org>,
Richard Acayan <mailingradian@gmail.com>
Subject: [PATCH 09/10] ASoC: qcom: add sdm660 internal sound card support
Date: Tue, 10 Feb 2026 21:03:01 -0500 [thread overview]
Message-ID: <20260211020302.2674-10-mailingradian@gmail.com> (raw)
In-Reply-To: <20260211020302.2674-1-mailingradian@gmail.com>
The Snapdragon 670 and Snapdragon 660 both share the same drivers for
the sound cards. These different sound cards are tasha, tavil, and
internal. Add support for the internal sound card.
Signed-off-by: Richard Acayan <mailingradian@gmail.com>
---
sound/soc/qcom/Kconfig | 12 ++
sound/soc/qcom/Makefile | 2 +
sound/soc/qcom/sdm660-internal.c | 271 +++++++++++++++++++++++++++++++
3 files changed, 285 insertions(+)
create mode 100644 sound/soc/qcom/sdm660-internal.c
diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig
index e6e24f3b9922..86b2778adc1a 100644
--- a/sound/soc/qcom/Kconfig
+++ b/sound/soc/qcom/Kconfig
@@ -167,6 +167,18 @@ config SND_SOC_MSM8996
APQ8096 SoC-based systems.
Say Y if you want to use audio device on this SoCs
+config SND_SOC_SDM660_INT
+ tristate "SoC Machine driver for SDM660 and SDM670 boards"
+ depends on QCOM_APR
+ depends on OF
+ depends on PM
+ select SND_SOC_QDSP6
+ select SND_SOC_QCOM_COMMON
+ help
+ This adds support for audio on Qualcomm Technologies Inc.
+ SDM660 and SDM670 SoC-based systems.
+ Say Y if you want to use audio devices on these SoCs.
+
config SND_SOC_SDM845
tristate "SoC Machine driver for SDM845 boards"
depends on QCOM_APR && I2C && SOUNDWIRE
diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile
index 985ce2ae286b..9a0da6279299 100644
--- a/sound/soc/qcom/Makefile
+++ b/sound/soc/qcom/Makefile
@@ -24,6 +24,7 @@ snd-soc-apq8016-sbc-y := apq8016_sbc.o
snd-soc-apq8096-y := apq8096.o
snd-soc-sc7180-y := sc7180.o
snd-soc-sc7280-y := sc7280.o
+snd-soc-sdm660-int-y := sdm660-internal.o
snd-soc-sdm845-y := sdm845.o
snd-soc-sm8250-y := sm8250.o
snd-soc-sc8280xp-y := sc8280xp.o
@@ -38,6 +39,7 @@ obj-$(CONFIG_SND_SOC_MSM8996) += snd-soc-apq8096.o
obj-$(CONFIG_SND_SOC_SC7180) += snd-soc-sc7180.o
obj-$(CONFIG_SND_SOC_SC7280) += snd-soc-sc7280.o
obj-$(CONFIG_SND_SOC_SC8280XP) += snd-soc-sc8280xp.o
+obj-$(CONFIG_SND_SOC_SDM660_INT) += snd-soc-sdm660-int.o
obj-$(CONFIG_SND_SOC_SDM845) += snd-soc-sdm845.o
obj-$(CONFIG_SND_SOC_SM8250) += snd-soc-sm8250.o
obj-$(CONFIG_SND_SOC_QCOM_COMMON) += snd-soc-qcom-common.o
diff --git a/sound/soc/qcom/sdm660-internal.c b/sound/soc/qcom/sdm660-internal.c
new file mode 100644
index 000000000000..beb810aa4eb9
--- /dev/null
+++ b/sound/soc/qcom/sdm660-internal.c
@@ -0,0 +1,271 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2015-2020, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2023, Richard Acayan. All rights reserved.
+ */
+
+#include <dt-bindings/sound/qcom,q6dsp-lpass-ports.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/jack.h>
+#include <sound/soc.h>
+#include <sound/soc-card.h>
+#include <sound/soc-dai.h>
+#include <sound/soc-dapm.h>
+#include <sound/soc-jack.h>
+
+#include "common.h"
+#include "qdsp6/q6afe.h"
+
+#define DEFAULT_SAMPLE_RATE_48K 48000
+#define DEFAULT_INT_MCLK_RATE 9600000
+#define MI2S_BCLK_RATE 1536000
+
+struct sdm660_int_snd_data {
+ struct snd_soc_jack jack;
+ bool jack_setup;
+ uint32_t int0_mi2s_clk_count;
+ uint32_t int3_mi2s_clk_count;
+};
+
+static int snd_sdm660_int_startup(struct snd_pcm_substream *stream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(stream);
+ struct sdm660_int_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *cpu = snd_soc_rtd_to_cpu(rtd, 0);
+
+ switch (cpu->id) {
+ case INT0_MI2S_RX:
+ data->int0_mi2s_clk_count++;
+ if (data->int0_mi2s_clk_count == 1)
+ snd_soc_dai_set_sysclk(cpu,
+ Q6AFE_LPASS_CLK_ID_INT0_MI2S_IBIT,
+ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK);
+
+ /*
+ * Downstream specifies that the AFE is a clock consumer, but
+ * the sound is distorted (loud on the right channel and sped
+ * up) unless we set it as a producer.
+ */
+ snd_soc_dai_set_fmt(cpu, SND_SOC_DAIFMT_CBP_CFP);
+
+ break;
+ case INT3_MI2S_TX:
+ data->int3_mi2s_clk_count++;
+ if (data->int3_mi2s_clk_count == 1)
+ snd_soc_dai_set_sysclk(cpu,
+ Q6AFE_LPASS_CLK_ID_INT3_MI2S_IBIT,
+ MI2S_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK);
+
+ /*
+ * Downstream specifies that the AFE is a clock consumer, but
+ * the sound is distorted (slowed down) unless we set it as a
+ * producer.
+ */
+ snd_soc_dai_set_fmt(cpu, SND_SOC_DAIFMT_CBP_CFP);
+
+ break;
+ default:
+ dev_err(rtd->dev, "%s: invalid dai id 0x%x\n", __func__,
+ cpu->id);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void snd_sdm660_int_shutdown(struct snd_pcm_substream *stream)
+{
+ struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(stream);
+ struct sdm660_int_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
+ struct snd_soc_dai *cpu = snd_soc_rtd_to_cpu(rtd, 0);
+
+ switch (cpu->id) {
+ case INT0_MI2S_RX:
+ data->int0_mi2s_clk_count--;
+ if (data->int0_mi2s_clk_count == 0)
+ snd_soc_dai_set_sysclk(cpu,
+ Q6AFE_LPASS_CLK_ID_INT0_MI2S_IBIT,
+ 0, SNDRV_PCM_STREAM_PLAYBACK);
+
+ break;
+ case INT3_MI2S_TX:
+ data->int3_mi2s_clk_count--;
+ if (data->int3_mi2s_clk_count == 0)
+ snd_soc_dai_set_sysclk(cpu,
+ Q6AFE_LPASS_CLK_ID_INT3_MI2S_IBIT,
+ 0, SNDRV_PCM_STREAM_PLAYBACK);
+
+ break;
+ default:
+ dev_err(rtd->dev, "%s: invalid dai id 0x%x\n", __func__,
+ cpu->id);
+ break;
+ }
+}
+
+static int snd_sdm660_int_hw_free(struct snd_pcm_substream *stream)
+{
+ return 0;
+}
+
+static int snd_sdm660_int_prepare(struct snd_pcm_substream *stream)
+{
+ return 0;
+}
+
+static const struct snd_soc_ops sdm660_int_ops = {
+ .startup = snd_sdm660_int_startup,
+ .shutdown = snd_sdm660_int_shutdown,
+ .hw_free = snd_sdm660_int_hw_free,
+ .prepare = snd_sdm660_int_prepare,
+};
+
+static int sdm660_int_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;
+ snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE);
+
+ channels->min = channels->max = 2;
+
+ return 0;
+}
+
+static void sdm660_int_jack_free(struct snd_jack *jack)
+{
+ struct snd_soc_component *component = jack->private_data;
+
+ snd_soc_component_set_jack(component, NULL, NULL);
+}
+
+static int sdm660_int_dai_init(struct snd_soc_pcm_runtime *rtd)
+{
+ struct snd_soc_card *card = rtd->card;
+ struct sdm660_int_snd_data *data = snd_soc_card_get_drvdata(card);
+ struct snd_soc_dai *cpu = snd_soc_rtd_to_cpu(rtd, 0);
+ /* first codec on INT0_MI2S_RX must be the analog codec */
+ struct snd_soc_dai *codec = snd_soc_rtd_to_codec(rtd, 0);
+ struct snd_jack *jack;
+ int ret;
+
+ if (!data->jack_setup) {
+ /* headset buttons not tested */
+ ret = snd_soc_card_jack_new(card, "Headset Jack",
+ SND_JACK_HEADSET | SND_JACK_BTN_0
+ | SND_JACK_BTN_1 | SND_JACK_BTN_2
+ | SND_JACK_BTN_3 | SND_JACK_BTN_4,
+ &data->jack);
+ if (ret < 0) {
+ dev_err(card->dev, "could not create headset jack\n");
+ return ret;
+ }
+
+ data->jack_setup = true;
+ }
+
+ switch (cpu->id) {
+ case INT0_MI2S_RX:
+ jack = data->jack.jack;
+
+ jack->private_data = codec->component;
+ jack->private_free = sdm660_int_jack_free;
+
+ ret = snd_soc_component_set_jack(codec->component,
+ &data->jack,
+ NULL);
+ if (ret < 0) {
+ dev_err(card->dev, "could not set headset jack\n");
+ return ret;
+ }
+
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void snd_sdm660_int_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 = &sdm660_int_ops;
+ link->be_hw_params_fixup = sdm660_int_be_hw_params_fixup;
+ }
+
+ link->init = sdm660_int_dai_init;
+ }
+}
+
+static const struct snd_soc_dapm_widget snd_sdm660_int_dapm_widgets[] = {
+};
+
+static int snd_sdm660_int_probe(struct platform_device *pdev)
+{
+ struct snd_soc_card *card;
+ struct sdm660_int_snd_data *data;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ card = devm_kzalloc(dev, sizeof(struct snd_soc_card), GFP_KERNEL);
+ if (!card)
+ return -ENOMEM;
+
+ data = devm_kzalloc(dev, sizeof(struct sdm660_int_snd_data), GFP_KERNEL);
+ if (!card)
+ return -ENOMEM;
+
+ card->driver_name = "sdm660-internal";
+ card->dapm_widgets = snd_sdm660_int_dapm_widgets;
+ card->num_dapm_widgets = ARRAY_SIZE(snd_sdm660_int_dapm_widgets);
+ card->dev = dev;
+ card->owner = THIS_MODULE;
+
+ ret = qcom_snd_parse_of(card);
+ if (ret)
+ return ret;
+
+ snd_soc_card_set_drvdata(card, data);
+
+ snd_sdm660_int_add_ops(card);
+
+ return devm_snd_soc_register_card(dev, card);
+}
+
+static void snd_sdm660_int_remove(struct platform_device *pdev)
+{
+}
+
+static const struct of_device_id snd_sdm660_int_device_id[] = {
+ { .compatible = "qcom,sdm660-internal-sndcard", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, snd_sdm660_int_device_id);
+
+static struct platform_driver snd_sdm660_int_driver = {
+ .probe = snd_sdm660_int_probe,
+ .remove = snd_sdm660_int_remove,
+ .driver = {
+ .name = "sdm660-int-sndcard",
+ .of_match_table = snd_sdm660_int_device_id,
+ .pm = &snd_soc_pm_ops,
+ },
+};
+module_platform_driver(snd_sdm660_int_driver);
+
+MODULE_DESCRIPTION("sdm660 Internal ASoC Machine Driver");
+MODULE_LICENSE("GPL");
--
2.53.0
next prev parent reply other threads:[~2026-02-11 2:03 UTC|newest]
Thread overview: 26+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-02-11 2:02 [PATCH 00/10] SDM660 internal sound card support Richard Acayan
2026-02-11 2:02 ` [PATCH 01/10] ASoC: dt-bindings: qcom,sm8250: add compatible for sdm660-internal Richard Acayan
2026-02-11 2:11 ` Dmitry Baryshkov
2026-02-11 6:51 ` Krzysztof Kozlowski
2026-02-11 21:55 ` Nickolay Goppen
2026-02-12 7:16 ` Krzysztof Kozlowski
2026-02-12 9:34 ` Konrad Dybcio
2026-02-14 3:38 ` Richard Acayan
2026-02-16 10:53 ` Konrad Dybcio
2026-02-16 13:57 ` Dmitry Baryshkov
2026-02-16 14:32 ` Konrad Dybcio
2026-02-24 2:05 ` Richard Acayan
2026-02-11 2:02 ` [PATCH 02/10] ASoC: dt-bindings: qcom: q6dsp: add internal mi2s support Richard Acayan
2026-02-11 2:02 ` [PATCH 03/10] ASoC: dt-bindings: pm8916-analog-codec: Add PM660L compatible Richard Acayan
2026-02-11 6:54 ` Krzysztof Kozlowski
2026-02-12 9:54 ` Konrad Dybcio
2026-02-11 2:02 ` [PATCH 04/10] ASoC: dt-bindings: msm8916-digital-codec: Add SDM660 compatible Richard Acayan
2026-02-11 6:57 ` Krzysztof Kozlowski
2026-02-11 2:02 ` [PATCH 05/10] ASoC: qdsp6: q6dsp-lpass-ports: add internal mi2s support Richard Acayan
2026-02-11 2:02 ` [PATCH 06/10] ASoC: qdsp6: q6afe: " Richard Acayan
2026-02-11 2:02 ` [PATCH 07/10] ASoC: qdsp6: q6afe-dai: " Richard Acayan
2026-02-11 2:03 ` [PATCH 08/10] ASoC: qdsp6: q6routing: " Richard Acayan
2026-02-11 2:03 ` Richard Acayan [this message]
2026-02-16 13:34 ` [PATCH 09/10] ASoC: qcom: add sdm660 internal sound card support Srinivas Kandagatla
2026-02-20 15:49 ` Richard Acayan
2026-02-11 2:03 ` [PATCH 10/10] ASoC: msm8916-wcd-analog: add quirk for cajon 2.0 Richard Acayan
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260211020302.2674-10-mailingradian@gmail.com \
--to=mailingradian@gmail.com \
--cc=broonie@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=gregkh@linuxfoundation.org \
--cc=johan@kernel.org \
--cc=konradybcio@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=lgirdwood@gmail.com \
--cc=linux-arm-msm@vger.kernel.org \
--cc=linux-sound@vger.kernel.org \
--cc=perex@perex.cz \
--cc=quic_wcheng@quicinc.com \
--cc=robh@kernel.org \
--cc=setotau@mainlining.org \
--cc=srini@kernel.org \
--cc=tiwai@suse.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox