ASoC: qcom: qdsp6: Add MI2S clock control

Mohammad Rafi Shaik <mohammad.rafi.shaik@oss.qualcomm.com> says:

Add support for MI2S clock control within q6apm-lpass DAIs, including
handling of MCLK, BCLK via the DAI .set_sysclk callback. Each MI2S port
now retrieves its clock handles from the device tree, allowing per-port
clock configuration and proper enable/disable during startup and shutdown.

On platforms such as Monaco and Lemans, third-party codecs are
hardware-wired to the SoC and do not always have an in-tree codec
driver to manage their clocks. For these designs, clock line
enablement must be driven from the platform side, and this
series provides the necessary support for that.

On QAIF-based platforms such as Shikra and Hawi, responsibility
for voting I2S MCLK and bit-clock has moved from the DSP to the
kernel. This series introduces the required device tree binding
support to represent and vote for these clocks from the kernel.

Enhances the sc8280xp machine driver to set the boards spacific
configurations.

Link: https://patch.msgid.link/20260724182446.1484894-1-mohammad.rafi.shaik@oss.qualcomm.com
This commit is contained in:
Mark Brown 2026-07-26 18:35:51 +01:00
commit 2bb3727d7b
No known key found for this signature in database
GPG Key ID: 24D68B725D5487D0
4 changed files with 450 additions and 22 deletions

View File

@ -21,6 +21,43 @@ properties:
'#sound-dai-cells':
const: 1
'#address-cells':
const: 1
'#size-cells':
const: 0
# Digital Audio Interfaces
patternProperties:
'^dai@[0-9a-f]+$':
type: object
description:
Q6DSP Digital Audio Interfaces.
properties:
reg:
maxItems: 1
description:
Digital Audio Interface ID
clocks:
minItems: 1
maxItems: 2
clock-names:
minItems: 1
items:
- enum: [bclk, mclk]
- const: mclk
dependencies:
clocks: [clock-names]
required:
- reg
additionalProperties: false
required:
- compatible
- '#sound-dai-cells'
@ -29,7 +66,20 @@ unevaluatedProperties: false
examples:
- |
#include <dt-bindings/sound/qcom,q6afe.h>
dais {
compatible = "qcom,q6apm-lpass-dais";
#sound-dai-cells = <1>;
#address-cells = <1>;
#size-cells = <0>;
dai@10 {
reg = <PRIMARY_MI2S_RX>;
clocks = <&q6prmcc LPASS_CLK_ID_PRI_MI2S_IBIT
LPASS_CLK_ATTRIBUTE_COUPLE_NO>,
<&q6prmcc LPASS_CLK_ID_MCLK_1
LPASS_CLK_ATTRIBUTE_COUPLE_NO>;
clock-names = "bclk", "mclk";
};
};

View File

@ -2,10 +2,12 @@
// Copyright (c) 2021, Linaro Limited
#include <dt-bindings/sound/qcom,q6dsp-lpass-ports.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <sound/pcm.h>
@ -15,15 +17,54 @@
#include "q6dsp-common.h"
#include "audioreach.h"
#include "q6apm.h"
#include "q6prm.h"
#define AUDIOREACH_BE_PCM_BASE 16
struct q6apm_dai_priv_data {
struct clk *mclk;
struct clk *bclk;
bool mclk_enabled, bclk_enabled;
};
struct q6apm_lpass_dai_data {
struct q6apm_graph *graph[APM_PORT_MAX];
bool is_port_started[APM_PORT_MAX];
struct audioreach_module_config module_config[APM_PORT_MAX];
struct q6apm_dai_priv_data priv[APM_PORT_MAX];
};
static void q6apm_lpass_dai_disable_clocks(struct q6apm_lpass_dai_data *dai_data, int id)
{
if (dai_data->priv[id].mclk_enabled) {
clk_disable_unprepare(dai_data->priv[id].mclk);
dai_data->priv[id].mclk_enabled = false;
}
if (dai_data->priv[id].bclk_enabled) {
clk_disable_unprepare(dai_data->priv[id].bclk);
dai_data->priv[id].bclk_enabled = false;
}
}
static void q6apm_lpass_dai_put_clocks(struct q6apm_lpass_dai_data *dai_data)
{
int i;
for (i = 0; i < APM_PORT_MAX; i++) {
q6apm_lpass_dai_disable_clocks(dai_data, i);
if (dai_data->priv[i].mclk) {
clk_put(dai_data->priv[i].mclk);
dai_data->priv[i].mclk = NULL;
}
if (dai_data->priv[i].bclk) {
clk_put(dai_data->priv[i].bclk);
dai_data->priv[i].bclk = NULL;
}
}
}
static int q6dma_set_channel_map(struct snd_soc_dai *dai,
unsigned int tx_num,
const unsigned int *tx_ch_mask,
@ -251,6 +292,62 @@ static int q6apm_lpass_dai_startup(struct snd_pcm_substream *substream, struct s
return 0;
}
static int q6i2s_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
return q6apm_lpass_dai_startup(substream, dai);
}
static void q6i2s_lpass_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
{
struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev);
q6apm_lpass_dai_shutdown(substream, dai);
q6apm_lpass_dai_disable_clocks(dai_data, dai->id);
}
static int q6i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir)
{
struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev);
struct clk *sysclk = NULL;
bool *enabled = NULL;
int ret = 0;
switch (clk_id) {
case LPAIF_MI2S_MCLK:
sysclk = dai_data->priv[dai->id].mclk;
enabled = &dai_data->priv[dai->id].mclk_enabled;
break;
case LPAIF_MI2S_BCLK:
sysclk = dai_data->priv[dai->id].bclk;
enabled = &dai_data->priv[dai->id].bclk_enabled;
break;
default:
return -EINVAL;
}
if (sysclk) {
ret = clk_set_rate(sysclk, freq);
if (ret) {
dev_err(dai->dev, "Error, Unable to set rate (%d) for sysclk %d\n",
freq, clk_id);
return ret;
}
if (*enabled)
return 0;
ret = clk_prepare_enable(sysclk);
if (ret) {
dev_err(dai->dev, "Error, Unable to prepare (%d) sysclk\n", clk_id);
return ret;
}
*enabled = true;
}
return ret;
}
static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev);
@ -272,11 +369,12 @@ static const struct snd_soc_dai_ops q6dma_ops = {
static const struct snd_soc_dai_ops q6i2s_ops = {
.prepare = q6apm_lpass_dai_prepare,
.startup = q6apm_lpass_dai_startup,
.shutdown = q6apm_lpass_dai_shutdown,
.startup = q6i2s_dai_startup,
.shutdown = q6i2s_lpass_dai_shutdown,
.set_channel_map = q6dma_set_channel_map,
.hw_params = q6dma_hw_params,
.set_fmt = q6i2s_set_fmt,
.set_sysclk = q6i2s_set_sysclk,
.trigger = q6apm_lpass_dai_trigger,
};
@ -297,6 +395,64 @@ static const struct snd_soc_component_driver q6apm_lpass_dai_component = {
.remove_order = SND_SOC_COMP_ORDER_FIRST,
};
static int of_q6apm_parse_dai_data(struct device *dev,
struct q6apm_lpass_dai_data *data)
{
int ret;
for_each_child_of_node_scoped(dev->of_node, node) {
struct q6apm_dai_priv_data *priv;
int id;
ret = of_property_read_u32(node, "reg", &id);
if (ret || id < 0 || id >= APM_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:
case QUINARY_MI2S_RX ... QUINARY_MI2S_TX:
case SENARY_MI2S_RX ... SENARY_MI2S_TX:
priv = &data->priv[id];
priv->mclk = of_clk_get_by_name(node, "mclk");
if (IS_ERR(priv->mclk)) {
int err = PTR_ERR(priv->mclk);
priv->mclk = NULL;
if (err == -EPROBE_DEFER) {
q6apm_lpass_dai_put_clocks(data);
return dev_err_probe(dev, err,
"unable to get mi2s mclk\n");
}
}
priv->bclk = of_clk_get_by_name(node, "bclk");
if (IS_ERR(priv->bclk)) {
int err = PTR_ERR(priv->bclk);
priv->bclk = NULL;
if (err == -EPROBE_DEFER) {
q6apm_lpass_dai_put_clocks(data);
return dev_err_probe(dev, err,
"unable to get mi2s bclk\n");
}
}
break;
default:
break;
}
}
return 0;
}
static void q6apm_lpass_dai_clocks_action(void *data)
{
q6apm_lpass_dai_put_clocks(data);
}
static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev)
{
struct q6dsp_audio_port_dai_driver_config cfg;
@ -304,12 +460,20 @@ static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev)
struct snd_soc_dai_driver *dais;
struct device *dev = &pdev->dev;
int num_dais;
int ret;
dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL);
if (!dai_data)
return -ENOMEM;
dev_set_drvdata(dev, dai_data);
ret = of_q6apm_parse_dai_data(dev, dai_data);
if (ret)
return ret;
ret = devm_add_action_or_reset(dev, q6apm_lpass_dai_clocks_action, dai_data);
if (ret)
return ret;
memset(&cfg, 0, sizeof(cfg));
cfg.q6i2s_ops = &q6i2s_ops;

View File

@ -3,6 +3,9 @@
#ifndef __Q6PRM_H__
#define __Q6PRM_H__
#define LPAIF_MI2S_MCLK 1
#define LPAIF_MI2S_BCLK 2
/* Clock ID for Primary I2S IBIT */
#define Q6PRM_LPASS_CLK_ID_PRI_MI2S_IBIT 0x100
/* Clock ID for Primary I2S EBIT */

View File

@ -12,17 +12,78 @@
#include <sound/jack.h>
#include <linux/input-event-codes.h>
#include "qdsp6/q6afe.h"
#include "qdsp6/q6apm.h"
#include "qdsp6/q6prm.h"
#include "common.h"
#include "sdw.h"
#define I2S_MCLKFS 256
#define I2S_MCLK_RATE(rate) \
((rate) * (I2S_MCLKFS))
#define I2S_BIT_RATE(rate, channels, format) \
((rate) * (channels) * (format))
static struct snd_soc_dapm_widget sc8280xp_dapm_widgets[] = {
SND_SOC_DAPM_HP("Headphone Jack", NULL),
SND_SOC_DAPM_MIC("Mic Jack", NULL),
SND_SOC_DAPM_SPK("DP0 Jack", NULL),
SND_SOC_DAPM_SPK("DP1 Jack", NULL),
SND_SOC_DAPM_SPK("DP2 Jack", NULL),
SND_SOC_DAPM_SPK("DP3 Jack", NULL),
SND_SOC_DAPM_SPK("DP4 Jack", NULL),
SND_SOC_DAPM_SPK("DP5 Jack", NULL),
SND_SOC_DAPM_SPK("DP6 Jack", NULL),
SND_SOC_DAPM_SPK("DP7 Jack", NULL),
};
struct snd_soc_common {
const char *driver_name;
const struct snd_soc_dapm_widget *dapm_widgets;
int num_dapm_widgets;
const struct snd_soc_dapm_route *dapm_routes;
int num_dapm_routes;
const struct snd_kcontrol_new *controls;
int num_controls;
unsigned int codec_dai_fmt;
bool codec_sysclk_set;
bool mi2s_mclk_enable;
bool mi2s_bclk_enable;
bool wcd_jack;
};
struct sc8280xp_snd_data {
bool stream_prepared[AFE_PORT_MAX];
struct snd_soc_card *card;
struct snd_soc_jack jack;
struct snd_soc_jack dp_jack[8];
const struct snd_soc_common *snd_soc_common_priv;
bool jack_setup;
};
static inline int sc8280xp_get_mclk_freq(struct snd_pcm_hw_params *params)
{
int rate = params_rate(params);
switch (rate) {
case 11025:
case 44100:
case 88200:
return I2S_MCLK_RATE(44100);
default:
break;
}
return I2S_MCLK_RATE(rate);
}
static inline int sc8280xp_get_bclk_freq(struct snd_pcm_hw_params *params)
{
return I2S_BIT_RATE(params_rate(params),
params_channels(params),
snd_pcm_format_width(params_format(params)));
}
static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd)
{
struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
@ -32,10 +93,6 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd)
int dp_pcm_id = 0;
switch (cpu_dai->id) {
case PRIMARY_MI2S_RX...QUATERNARY_MI2S_TX:
case QUINARY_MI2S_RX...QUINARY_MI2S_TX:
snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP);
break;
case WSA_CODEC_DMA_RX_0:
case WSA_CODEC_DMA_RX_1:
/*
@ -64,7 +121,10 @@ static int sc8280xp_snd_init(struct snd_soc_pcm_runtime *rtd)
if (dp_jack)
return qcom_snd_dp_jack_setup(rtd, dp_jack, dp_pcm_id);
return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup);
if (data->snd_soc_common_priv->wcd_jack)
return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup);
return 0;
}
static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
@ -96,6 +156,63 @@ static int sc8280xp_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
return 0;
}
static int sc8280xp_snd_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0);
struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
struct sc8280xp_snd_data *data = snd_soc_card_get_drvdata(rtd->card);
int mclk_freq = sc8280xp_get_mclk_freq(params);
int bclk_freq = sc8280xp_get_bclk_freq(params);
int ret;
switch (cpu_dai->id) {
case PRIMARY_MI2S_RX ... QUATERNARY_MI2S_TX:
case QUINARY_MI2S_RX ... QUINARY_MI2S_TX:
case SENARY_MI2S_RX ... SENARY_MI2S_TX:
ret = snd_soc_dai_set_fmt(cpu_dai, SND_SOC_DAIFMT_BP_FP);
if (ret && ret != -ENOTSUPP)
return ret;
if (data->snd_soc_common_priv->codec_dai_fmt) {
ret = snd_soc_dai_set_fmt(codec_dai,
data->snd_soc_common_priv->codec_dai_fmt);
if (ret && ret != -ENOTSUPP)
return ret;
}
if (data->snd_soc_common_priv->mi2s_mclk_enable) {
ret = snd_soc_dai_set_sysclk(cpu_dai,
LPAIF_MI2S_MCLK, mclk_freq,
SND_SOC_CLOCK_OUT);
if (ret)
return ret;
}
if (data->snd_soc_common_priv->mi2s_bclk_enable) {
ret = snd_soc_dai_set_sysclk(cpu_dai,
LPAIF_MI2S_BCLK, bclk_freq,
SND_SOC_CLOCK_OUT);
if (ret)
return ret;
}
if (data->snd_soc_common_priv->codec_sysclk_set) {
ret = snd_soc_dai_set_sysclk(codec_dai,
0, mclk_freq,
SND_SOC_CLOCK_IN);
if (ret)
return ret;
}
break;
default:
break;
}
return 0;
}
static int sc8280xp_snd_prepare(struct snd_pcm_substream *substream)
{
struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
@ -117,6 +234,7 @@ static int sc8280xp_snd_hw_free(struct snd_pcm_substream *substream)
static const struct snd_soc_ops sc8280xp_be_ops = {
.startup = qcom_snd_sdw_startup,
.shutdown = qcom_snd_sdw_shutdown,
.hw_params = sc8280xp_snd_hw_params,
.hw_free = sc8280xp_snd_hw_free,
.prepare = sc8280xp_snd_prepare,
};
@ -145,38 +263,131 @@ static int sc8280xp_platform_probe(struct platform_device *pdev)
card = devm_kzalloc(dev, sizeof(*card), GFP_KERNEL);
if (!card)
return -ENOMEM;
card->owner = THIS_MODULE;
/* Allocate the private data */
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->snd_soc_common_priv = of_device_get_match_data(dev);
if (!data->snd_soc_common_priv)
return -ENODEV;
card->owner = THIS_MODULE;
card->dev = dev;
dev_set_drvdata(dev, card);
snd_soc_card_set_drvdata(card, data);
card->dapm_widgets = data->snd_soc_common_priv->dapm_widgets;
card->num_dapm_widgets = data->snd_soc_common_priv->num_dapm_widgets;
card->dapm_routes = data->snd_soc_common_priv->dapm_routes;
card->num_dapm_routes = data->snd_soc_common_priv->num_dapm_routes;
card->controls = data->snd_soc_common_priv->controls;
card->num_controls = data->snd_soc_common_priv->num_controls;
ret = qcom_snd_parse_of(card);
if (ret)
return ret;
card->driver_name = of_device_get_match_data(dev);
card->driver_name = data->snd_soc_common_priv->driver_name;
sc8280xp_add_be_ops(card);
return devm_snd_soc_register_card(dev, card);
}
static const struct snd_soc_common eliza_priv_data = {
.driver_name = "eliza",
.dapm_widgets = sc8280xp_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
.wcd_jack = true,
};
static const struct snd_soc_common kaanapali_priv_data = {
.driver_name = "kaanapali",
.dapm_widgets = sc8280xp_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
.wcd_jack = true,
};
static const struct snd_soc_common qcs9100_priv_data = {
.driver_name = "sa8775p",
.dapm_widgets = sc8280xp_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
};
static const struct snd_soc_common qcs615_priv_data = {
.driver_name = "qcs615",
.dapm_widgets = sc8280xp_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
};
static const struct snd_soc_common qcm6490_priv_data = {
.driver_name = "qcm6490",
.dapm_widgets = sc8280xp_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
.wcd_jack = true,
};
static const struct snd_soc_common qcs6490_priv_data = {
.driver_name = "qcs6490",
.dapm_widgets = sc8280xp_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
.wcd_jack = true,
};
static const struct snd_soc_common qcs8275_priv_data = {
.driver_name = "qcs8300",
.dapm_widgets = sc8280xp_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
};
static const struct snd_soc_common sc8280xp_priv_data = {
.driver_name = "sc8280xp",
.dapm_widgets = sc8280xp_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
.wcd_jack = true,
};
static const struct snd_soc_common sm8450_priv_data = {
.driver_name = "sm8450",
.dapm_widgets = sc8280xp_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
.wcd_jack = true,
};
static const struct snd_soc_common sm8550_priv_data = {
.driver_name = "sm8550",
.dapm_widgets = sc8280xp_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
.wcd_jack = true,
};
static const struct snd_soc_common sm8650_priv_data = {
.driver_name = "sm8650",
.dapm_widgets = sc8280xp_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
.wcd_jack = true,
};
static const struct snd_soc_common sm8750_priv_data = {
.driver_name = "sm8750",
.dapm_widgets = sc8280xp_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(sc8280xp_dapm_widgets),
.wcd_jack = true,
};
static const struct of_device_id snd_sc8280xp_dt_match[] = {
{.compatible = "qcom,eliza-sndcard", "eliza"},
{.compatible = "qcom,kaanapali-sndcard", "kaanapali"},
{.compatible = "qcom,qcm6490-idp-sndcard", "qcm6490"},
{.compatible = "qcom,qcs615-sndcard", "qcs615"},
{.compatible = "qcom,qcs6490-rb3gen2-sndcard", "qcs6490"},
{.compatible = "qcom,qcs8275-sndcard", "qcs8300"},
{.compatible = "qcom,qcs9075-sndcard", "sa8775p"},
{.compatible = "qcom,qcs9100-sndcard", "sa8775p"},
{.compatible = "qcom,sc8280xp-sndcard", "sc8280xp"},
{.compatible = "qcom,sm8450-sndcard", "sm8450"},
{.compatible = "qcom,sm8550-sndcard", "sm8550"},
{.compatible = "qcom,sm8650-sndcard", "sm8650"},
{.compatible = "qcom,sm8750-sndcard", "sm8750"},
{ .compatible = "qcom,eliza-sndcard", .data = &eliza_priv_data },
{ .compatible = "qcom,kaanapali-sndcard", .data = &kaanapali_priv_data },
{ .compatible = "qcom,qcm6490-idp-sndcard", .data = &qcm6490_priv_data },
{ .compatible = "qcom,qcs615-sndcard", .data = &qcs615_priv_data },
{ .compatible = "qcom,qcs6490-rb3gen2-sndcard", .data = &qcs6490_priv_data },
{ .compatible = "qcom,qcs8275-sndcard", .data = &qcs8275_priv_data },
{ .compatible = "qcom,qcs9075-sndcard", .data = &qcs9100_priv_data },
{ .compatible = "qcom,qcs9100-sndcard", .data = &qcs9100_priv_data },
{ .compatible = "qcom,sc8280xp-sndcard", .data = &sc8280xp_priv_data },
{ .compatible = "qcom,sm8450-sndcard", .data = &sm8450_priv_data },
{ .compatible = "qcom,sm8550-sndcard", .data = &sm8550_priv_data },
{ .compatible = "qcom,sm8650-sndcard", .data = &sm8650_priv_data },
{ .compatible = "qcom,sm8750-sndcard", .data = &sm8750_priv_data },
{}
};