1 // SPDX-License-Identifier: GPL-2.0-only
3 // tegra_audio_graph_card.c - Audio Graph based Tegra Machine Driver
5 // Copyright (c) 2020-2021 NVIDIA CORPORATION. All rights reserved.
7 #include <linux/math64.h>
8 #include <linux/module.h>
10 #include <linux/platform_device.h>
11 #include <sound/graph_card.h>
12 #include <sound/pcm_params.h>
13 #include <sound/soc-dai.h>
15 #define MAX_PLLA_OUT0_DIV 128
17 #define simple_to_tegra_priv(simple) \
18 container_of(simple, struct tegra_audio_priv, simple)
22 * Sample rates multiple of 8000 Hz and below are supported:
23 * ( 8000, 16000, 32000, 48000, 96000, 192000 Hz )
28 * Sample rates multiple of 11025 Hz and below are supported:
29 * ( 11025, 22050, 44100, 88200, 176400 Hz )
36 struct tegra_audio_priv
{
37 struct simple_util_priv simple
;
38 struct clk
*clk_plla_out0
;
42 /* Tegra audio chip data */
43 struct tegra_audio_cdata
{
44 unsigned int plla_rates
[NUM_RATE_TYPE
];
45 unsigned int plla_out0_rates
[NUM_RATE_TYPE
];
48 static bool need_clk_update(struct snd_soc_dai
*dai
)
50 if (snd_soc_dai_is_dummy(dai
) ||
55 if (strstr(dai
->driver
->name
, "I2S") ||
56 strstr(dai
->driver
->name
, "DMIC") ||
57 strstr(dai
->driver
->name
, "DSPK"))
63 /* Setup PLL clock as per the given sample rate */
64 static int tegra_audio_graph_update_pll(struct snd_pcm_substream
*substream
,
65 struct snd_pcm_hw_params
*params
)
67 struct snd_soc_pcm_runtime
*rtd
= snd_soc_substream_to_rtd(substream
);
68 struct simple_util_priv
*simple
= snd_soc_card_get_drvdata(rtd
->card
);
69 struct tegra_audio_priv
*priv
= simple_to_tegra_priv(simple
);
70 struct device
*dev
= rtd
->card
->dev
;
71 const struct tegra_audio_cdata
*data
= of_device_get_match_data(dev
);
72 unsigned int plla_rate
, plla_out0_rate
, bclk
;
73 unsigned int srate
= params_rate(params
);
82 plla_out0_rate
= data
->plla_out0_rates
[x11_RATE
];
83 plla_rate
= data
->plla_rates
[x11_RATE
];
91 plla_out0_rate
= data
->plla_out0_rates
[x8_RATE
];
92 plla_rate
= data
->plla_rates
[x8_RATE
];
95 dev_err(rtd
->card
->dev
, "Unsupported sample rate %u\n",
101 * Below is the clock relation:
114 * Default PLLA_OUT0 rate might be too high when I/O is running
115 * at minimum PCM configurations. This may result in incorrect
116 * clock rates and glitchy audio. The maximum divider is 128
117 * and any thing higher than that won't work. Thus reduce PLLA_OUT0
118 * to work for lower configurations.
120 * This problem is seen for I2S only, as DMIC and DSPK minimum
121 * clock requirements are under allowed divider limits.
123 bclk
= srate
* params_channels(params
) * params_width(params
);
124 if (div_u64(plla_out0_rate
, bclk
) > MAX_PLLA_OUT0_DIV
)
125 plla_out0_rate
>>= 1;
127 dev_dbg(rtd
->card
->dev
,
128 "Update clock rates: PLLA(= %u Hz) and PLLA_OUT0(= %u Hz)\n",
129 plla_rate
, plla_out0_rate
);
132 err
= clk_set_rate(priv
->clk_plla
, plla_rate
);
134 dev_err(rtd
->card
->dev
,
135 "Can't set plla rate for %u, err: %d\n",
140 /* Set PLLA_OUT0 rate */
141 err
= clk_set_rate(priv
->clk_plla_out0
, plla_out0_rate
);
143 dev_err(rtd
->card
->dev
,
144 "Can't set plla_out0 rate %u, err: %d\n",
145 plla_out0_rate
, err
);
152 static int tegra_audio_graph_hw_params(struct snd_pcm_substream
*substream
,
153 struct snd_pcm_hw_params
*params
)
155 struct snd_soc_pcm_runtime
*rtd
= snd_soc_substream_to_rtd(substream
);
156 struct snd_soc_dai
*cpu_dai
= snd_soc_rtd_to_cpu(rtd
, 0);
159 if (need_clk_update(cpu_dai
)) {
160 err
= tegra_audio_graph_update_pll(substream
, params
);
165 return simple_util_hw_params(substream
, params
);
168 static const struct snd_soc_ops tegra_audio_graph_ops
= {
169 .startup
= simple_util_startup
,
170 .shutdown
= simple_util_shutdown
,
171 .hw_params
= tegra_audio_graph_hw_params
,
174 static int tegra_audio_graph_card_probe(struct snd_soc_card
*card
)
176 struct simple_util_priv
*simple
= snd_soc_card_get_drvdata(card
);
177 struct tegra_audio_priv
*priv
= simple_to_tegra_priv(simple
);
179 priv
->clk_plla
= devm_clk_get(card
->dev
, "pll_a");
180 if (IS_ERR(priv
->clk_plla
)) {
181 dev_err(card
->dev
, "Can't retrieve clk pll_a\n");
182 return PTR_ERR(priv
->clk_plla
);
185 priv
->clk_plla_out0
= devm_clk_get(card
->dev
, "plla_out0");
186 if (IS_ERR(priv
->clk_plla_out0
)) {
187 dev_err(card
->dev
, "Can't retrieve clk plla_out0\n");
188 return PTR_ERR(priv
->clk_plla_out0
);
191 return graph_util_card_probe(card
);
194 static int tegra_audio_graph_probe(struct platform_device
*pdev
)
196 struct tegra_audio_priv
*priv
;
197 struct device
*dev
= &pdev
->dev
;
198 struct snd_soc_card
*card
;
200 priv
= devm_kzalloc(dev
, sizeof(*priv
), GFP_KERNEL
);
204 card
= simple_priv_to_card(&priv
->simple
);
205 card
->driver_name
= "tegra-ape";
207 card
->probe
= tegra_audio_graph_card_probe
;
209 /* audio_graph_parse_of() depends on below */
210 card
->component_chaining
= 1;
211 priv
->simple
.ops
= &tegra_audio_graph_ops
;
212 priv
->simple
.force_dpcm
= 1;
214 return audio_graph_parse_of(&priv
->simple
, dev
);
217 static const struct tegra_audio_cdata tegra210_data
= {
219 .plla_rates
[x8_RATE
] = 368640000,
220 .plla_rates
[x11_RATE
] = 338688000,
222 .plla_out0_rates
[x8_RATE
] = 49152000,
223 .plla_out0_rates
[x11_RATE
] = 45158400,
226 static const struct tegra_audio_cdata tegra186_data
= {
228 .plla_rates
[x8_RATE
] = 245760000,
229 .plla_rates
[x11_RATE
] = 270950400,
231 .plla_out0_rates
[x8_RATE
] = 49152000,
232 .plla_out0_rates
[x11_RATE
] = 45158400,
235 static const struct of_device_id graph_of_tegra_match
[] = {
236 { .compatible
= "nvidia,tegra210-audio-graph-card",
237 .data
= &tegra210_data
},
238 { .compatible
= "nvidia,tegra186-audio-graph-card",
239 .data
= &tegra186_data
},
242 MODULE_DEVICE_TABLE(of
, graph_of_tegra_match
);
244 static struct platform_driver tegra_audio_graph_card
= {
246 .name
= "tegra-audio-graph-card",
247 .pm
= &snd_soc_pm_ops
,
248 .of_match_table
= graph_of_tegra_match
,
250 .probe
= tegra_audio_graph_probe
,
251 .remove
= simple_util_remove
,
253 module_platform_driver(tegra_audio_graph_card
);
255 MODULE_LICENSE("GPL v2");
256 MODULE_DESCRIPTION("ASoC Tegra Audio Graph Sound Card");
257 MODULE_AUTHOR("Sameer Pujar <spujar@nvidia.com>");