1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // |
3 | // tegra_audio_graph_card.c - Audio Graph based Tegra Machine Driver |
4 | // |
5 | // Copyright (c) 2020-2021 NVIDIA CORPORATION. All rights reserved. |
6 | |
7 | #include <linux/math64.h> |
8 | #include <linux/module.h> |
9 | #include <linux/of.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> |
14 | |
15 | #define MAX_PLLA_OUT0_DIV 128 |
16 | |
17 | #define simple_to_tegra_priv(simple) \ |
18 | container_of(simple, struct tegra_audio_priv, simple) |
19 | |
20 | enum srate_type { |
21 | /* |
22 | * Sample rates multiple of 8000 Hz and below are supported: |
23 | * ( 8000, 16000, 32000, 48000, 96000, 192000 Hz ) |
24 | */ |
25 | x8_RATE, |
26 | |
27 | /* |
28 | * Sample rates multiple of 11025 Hz and below are supported: |
29 | * ( 11025, 22050, 44100, 88200, 176400 Hz ) |
30 | */ |
31 | x11_RATE, |
32 | |
33 | NUM_RATE_TYPE, |
34 | }; |
35 | |
36 | struct tegra_audio_priv { |
37 | struct simple_util_priv simple; |
38 | struct clk *clk_plla_out0; |
39 | struct clk *clk_plla; |
40 | }; |
41 | |
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]; |
46 | }; |
47 | |
48 | static bool need_clk_update(struct snd_soc_dai *dai) |
49 | { |
50 | if (snd_soc_dai_is_dummy(dai) || |
51 | !dai->driver->ops || |
52 | !dai->driver->name) |
53 | return false; |
54 | |
55 | if (strstr(dai->driver->name, "I2S" ) || |
56 | strstr(dai->driver->name, "DMIC" ) || |
57 | strstr(dai->driver->name, "DSPK" )) |
58 | return true; |
59 | |
60 | return false; |
61 | } |
62 | |
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) |
66 | { |
67 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
68 | struct simple_util_priv *simple = snd_soc_card_get_drvdata(card: 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(p: params); |
74 | int err; |
75 | |
76 | switch (srate) { |
77 | case 11025: |
78 | case 22050: |
79 | case 44100: |
80 | case 88200: |
81 | case 176400: |
82 | plla_out0_rate = data->plla_out0_rates[x11_RATE]; |
83 | plla_rate = data->plla_rates[x11_RATE]; |
84 | break; |
85 | case 8000: |
86 | case 16000: |
87 | case 32000: |
88 | case 48000: |
89 | case 96000: |
90 | case 192000: |
91 | plla_out0_rate = data->plla_out0_rates[x8_RATE]; |
92 | plla_rate = data->plla_rates[x8_RATE]; |
93 | break; |
94 | default: |
95 | dev_err(rtd->card->dev, "Unsupported sample rate %u\n" , |
96 | srate); |
97 | return -EINVAL; |
98 | } |
99 | |
100 | /* |
101 | * Below is the clock relation: |
102 | * |
103 | * PLLA |
104 | * | |
105 | * |--> PLLA_OUT0 |
106 | * | |
107 | * |---> I2S modules |
108 | * | |
109 | * |---> DMIC modules |
110 | * | |
111 | * |---> DSPK modules |
112 | * |
113 | * |
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. |
119 | * |
120 | * This problem is seen for I2S only, as DMIC and DSPK minimum |
121 | * clock requirements are under allowed divider limits. |
122 | */ |
123 | bclk = srate * params_channels(p: params) * params_width(p: params); |
124 | if (div_u64(dividend: plla_out0_rate, divisor: bclk) > MAX_PLLA_OUT0_DIV) |
125 | plla_out0_rate >>= 1; |
126 | |
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); |
130 | |
131 | /* Set PLLA rate */ |
132 | err = clk_set_rate(clk: priv->clk_plla, rate: plla_rate); |
133 | if (err) { |
134 | dev_err(rtd->card->dev, |
135 | "Can't set plla rate for %u, err: %d\n" , |
136 | plla_rate, err); |
137 | return err; |
138 | } |
139 | |
140 | /* Set PLLA_OUT0 rate */ |
141 | err = clk_set_rate(clk: priv->clk_plla_out0, rate: plla_out0_rate); |
142 | if (err) { |
143 | dev_err(rtd->card->dev, |
144 | "Can't set plla_out0 rate %u, err: %d\n" , |
145 | plla_out0_rate, err); |
146 | return err; |
147 | } |
148 | |
149 | return err; |
150 | } |
151 | |
152 | static int tegra_audio_graph_hw_params(struct snd_pcm_substream *substream, |
153 | struct snd_pcm_hw_params *params) |
154 | { |
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); |
157 | int err; |
158 | |
159 | if (need_clk_update(dai: cpu_dai)) { |
160 | err = tegra_audio_graph_update_pll(substream, params); |
161 | if (err) |
162 | return err; |
163 | } |
164 | |
165 | return simple_util_hw_params(substream, params); |
166 | } |
167 | |
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, |
172 | }; |
173 | |
174 | static int tegra_audio_graph_card_probe(struct snd_soc_card *card) |
175 | { |
176 | struct simple_util_priv *simple = snd_soc_card_get_drvdata(card); |
177 | struct tegra_audio_priv *priv = simple_to_tegra_priv(simple); |
178 | |
179 | priv->clk_plla = devm_clk_get(dev: card->dev, id: "pll_a" ); |
180 | if (IS_ERR(ptr: priv->clk_plla)) { |
181 | dev_err(card->dev, "Can't retrieve clk pll_a\n" ); |
182 | return PTR_ERR(ptr: priv->clk_plla); |
183 | } |
184 | |
185 | priv->clk_plla_out0 = devm_clk_get(dev: card->dev, id: "plla_out0" ); |
186 | if (IS_ERR(ptr: priv->clk_plla_out0)) { |
187 | dev_err(card->dev, "Can't retrieve clk plla_out0\n" ); |
188 | return PTR_ERR(ptr: priv->clk_plla_out0); |
189 | } |
190 | |
191 | return graph_util_card_probe(card); |
192 | } |
193 | |
194 | static int tegra_audio_graph_probe(struct platform_device *pdev) |
195 | { |
196 | struct tegra_audio_priv *priv; |
197 | struct device *dev = &pdev->dev; |
198 | struct snd_soc_card *card; |
199 | |
200 | priv = devm_kzalloc(dev, size: sizeof(*priv), GFP_KERNEL); |
201 | if (!priv) |
202 | return -ENOMEM; |
203 | |
204 | card = simple_priv_to_card(&priv->simple); |
205 | card->driver_name = "tegra-ape" ; |
206 | |
207 | card->probe = tegra_audio_graph_card_probe; |
208 | |
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; |
213 | |
214 | return audio_graph_parse_of(priv: &priv->simple, dev); |
215 | } |
216 | |
217 | static const struct tegra_audio_cdata tegra210_data = { |
218 | /* PLLA */ |
219 | .plla_rates[x8_RATE] = 368640000, |
220 | .plla_rates[x11_RATE] = 338688000, |
221 | /* PLLA_OUT0 */ |
222 | .plla_out0_rates[x8_RATE] = 49152000, |
223 | .plla_out0_rates[x11_RATE] = 45158400, |
224 | }; |
225 | |
226 | static const struct tegra_audio_cdata tegra186_data = { |
227 | /* PLLA */ |
228 | .plla_rates[x8_RATE] = 245760000, |
229 | .plla_rates[x11_RATE] = 270950400, |
230 | /* PLLA_OUT0 */ |
231 | .plla_out0_rates[x8_RATE] = 49152000, |
232 | .plla_out0_rates[x11_RATE] = 45158400, |
233 | }; |
234 | |
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 }, |
240 | {}, |
241 | }; |
242 | MODULE_DEVICE_TABLE(of, graph_of_tegra_match); |
243 | |
244 | static struct platform_driver tegra_audio_graph_card = { |
245 | .driver = { |
246 | .name = "tegra-audio-graph-card" , |
247 | .pm = &snd_soc_pm_ops, |
248 | .of_match_table = graph_of_tegra_match, |
249 | }, |
250 | .probe = tegra_audio_graph_probe, |
251 | .remove_new = simple_util_remove, |
252 | }; |
253 | module_platform_driver(tegra_audio_graph_card); |
254 | |
255 | MODULE_LICENSE("GPL v2" ); |
256 | MODULE_DESCRIPTION("ASoC Tegra Audio Graph Sound Card" ); |
257 | MODULE_AUTHOR("Sameer Pujar <spujar@nvidia.com>" ); |
258 | |