1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // |
3 | // tegra210_ope.c - Tegra210 OPE driver |
4 | // |
5 | // Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. |
6 | |
7 | #include <linux/clk.h> |
8 | #include <linux/device.h> |
9 | #include <linux/io.h> |
10 | #include <linux/mod_devicetable.h> |
11 | #include <linux/module.h> |
12 | #include <linux/platform_device.h> |
13 | #include <linux/pm_runtime.h> |
14 | #include <linux/regmap.h> |
15 | #include <sound/core.h> |
16 | #include <sound/pcm.h> |
17 | #include <sound/pcm_params.h> |
18 | #include <sound/soc.h> |
19 | |
20 | #include "tegra210_mbdrc.h" |
21 | #include "tegra210_ope.h" |
22 | #include "tegra210_peq.h" |
23 | #include "tegra_cif.h" |
24 | |
25 | static const struct reg_default tegra210_ope_reg_defaults[] = { |
26 | { TEGRA210_OPE_RX_INT_MASK, 0x00000001}, |
27 | { TEGRA210_OPE_RX_CIF_CTRL, 0x00007700}, |
28 | { TEGRA210_OPE_TX_INT_MASK, 0x00000001}, |
29 | { TEGRA210_OPE_TX_CIF_CTRL, 0x00007700}, |
30 | { TEGRA210_OPE_CG, 0x1}, |
31 | }; |
32 | |
33 | static int tegra210_ope_set_audio_cif(struct tegra210_ope *ope, |
34 | struct snd_pcm_hw_params *params, |
35 | unsigned int reg) |
36 | { |
37 | int channels, audio_bits; |
38 | struct tegra_cif_conf cif_conf; |
39 | |
40 | memset(&cif_conf, 0, sizeof(struct tegra_cif_conf)); |
41 | |
42 | channels = params_channels(p: params); |
43 | if (channels < 2) |
44 | return -EINVAL; |
45 | |
46 | switch (params_format(p: params)) { |
47 | case SNDRV_PCM_FORMAT_S16_LE: |
48 | audio_bits = TEGRA_ACIF_BITS_16; |
49 | break; |
50 | case SNDRV_PCM_FORMAT_S32_LE: |
51 | audio_bits = TEGRA_ACIF_BITS_32; |
52 | break; |
53 | default: |
54 | return -EINVAL; |
55 | } |
56 | |
57 | cif_conf.audio_ch = channels; |
58 | cif_conf.client_ch = channels; |
59 | cif_conf.audio_bits = audio_bits; |
60 | cif_conf.client_bits = audio_bits; |
61 | |
62 | tegra_set_cif(regmap: ope->regmap, reg, conf: &cif_conf); |
63 | |
64 | return 0; |
65 | } |
66 | |
67 | static int tegra210_ope_hw_params(struct snd_pcm_substream *substream, |
68 | struct snd_pcm_hw_params *params, |
69 | struct snd_soc_dai *dai) |
70 | { |
71 | struct device *dev = dai->dev; |
72 | struct tegra210_ope *ope = snd_soc_dai_get_drvdata(dai); |
73 | int err; |
74 | |
75 | /* Set RX and TX CIF */ |
76 | err = tegra210_ope_set_audio_cif(ope, params, |
77 | TEGRA210_OPE_RX_CIF_CTRL); |
78 | if (err) { |
79 | dev_err(dev, "Can't set OPE RX CIF: %d\n" , err); |
80 | return err; |
81 | } |
82 | |
83 | err = tegra210_ope_set_audio_cif(ope, params, |
84 | TEGRA210_OPE_TX_CIF_CTRL); |
85 | if (err) { |
86 | dev_err(dev, "Can't set OPE TX CIF: %d\n" , err); |
87 | return err; |
88 | } |
89 | |
90 | tegra210_mbdrc_hw_params(cmpnt: dai->component); |
91 | |
92 | return err; |
93 | } |
94 | |
95 | static int tegra210_ope_component_probe(struct snd_soc_component *cmpnt) |
96 | { |
97 | struct tegra210_ope *ope = dev_get_drvdata(dev: cmpnt->dev); |
98 | |
99 | tegra210_peq_component_init(cmpnt); |
100 | tegra210_mbdrc_component_init(cmpnt); |
101 | |
102 | /* |
103 | * The OPE, PEQ and MBDRC functionalities are combined under one |
104 | * device registered by OPE driver. In fact OPE HW block includes |
105 | * sub blocks PEQ and MBDRC. However driver registers separate |
106 | * regmap interfaces for each of these. ASoC core depends on |
107 | * dev_get_regmap() to populate the regmap field for a given ASoC |
108 | * component. A component can have one regmap reference and since |
109 | * the DAPM routes depend on OPE regmap only, below explicit |
110 | * assignment is done to highlight this. This is needed for ASoC |
111 | * core to access correct regmap during DAPM path setup. |
112 | */ |
113 | snd_soc_component_init_regmap(component: cmpnt, regmap: ope->regmap); |
114 | |
115 | return 0; |
116 | } |
117 | |
118 | static const struct snd_soc_dai_ops tegra210_ope_dai_ops = { |
119 | .hw_params = tegra210_ope_hw_params, |
120 | }; |
121 | |
122 | static struct snd_soc_dai_driver tegra210_ope_dais[] = { |
123 | { |
124 | .name = "OPE-RX-CIF" , |
125 | .playback = { |
126 | .stream_name = "RX-CIF-Playback" , |
127 | .channels_min = 1, |
128 | .channels_max = 8, |
129 | .rates = SNDRV_PCM_RATE_8000_192000, |
130 | .formats = SNDRV_PCM_FMTBIT_S8 | |
131 | SNDRV_PCM_FMTBIT_S16_LE | |
132 | SNDRV_PCM_FMTBIT_S32_LE, |
133 | }, |
134 | .capture = { |
135 | .stream_name = "RX-CIF-Capture" , |
136 | .channels_min = 1, |
137 | .channels_max = 8, |
138 | .rates = SNDRV_PCM_RATE_8000_192000, |
139 | .formats = SNDRV_PCM_FMTBIT_S8 | |
140 | SNDRV_PCM_FMTBIT_S16_LE | |
141 | SNDRV_PCM_FMTBIT_S32_LE, |
142 | }, |
143 | }, |
144 | { |
145 | .name = "OPE-TX-CIF" , |
146 | .playback = { |
147 | .stream_name = "TX-CIF-Playback" , |
148 | .channels_min = 1, |
149 | .channels_max = 8, |
150 | .rates = SNDRV_PCM_RATE_8000_192000, |
151 | .formats = SNDRV_PCM_FMTBIT_S8 | |
152 | SNDRV_PCM_FMTBIT_S16_LE | |
153 | SNDRV_PCM_FMTBIT_S32_LE, |
154 | }, |
155 | .capture = { |
156 | .stream_name = "TX-CIF-Capture" , |
157 | .channels_min = 1, |
158 | .channels_max = 8, |
159 | .rates = SNDRV_PCM_RATE_8000_192000, |
160 | .formats = SNDRV_PCM_FMTBIT_S8 | |
161 | SNDRV_PCM_FMTBIT_S16_LE | |
162 | SNDRV_PCM_FMTBIT_S32_LE, |
163 | }, |
164 | .ops = &tegra210_ope_dai_ops, |
165 | } |
166 | }; |
167 | |
168 | static const struct snd_soc_dapm_widget tegra210_ope_widgets[] = { |
169 | SND_SOC_DAPM_AIF_IN("RX" , NULL, 0, SND_SOC_NOPM, 0, 0), |
170 | SND_SOC_DAPM_AIF_OUT("TX" , NULL, 0, TEGRA210_OPE_ENABLE, |
171 | TEGRA210_OPE_EN_SHIFT, 0), |
172 | }; |
173 | |
174 | #define OPE_ROUTES(sname) \ |
175 | { "RX XBAR-" sname, NULL, "XBAR-TX" }, \ |
176 | { "RX-CIF-" sname, NULL, "RX XBAR-" sname }, \ |
177 | { "RX", NULL, "RX-CIF-" sname }, \ |
178 | { "TX-CIF-" sname, NULL, "TX" }, \ |
179 | { "TX XBAR-" sname, NULL, "TX-CIF-" sname }, \ |
180 | { "XBAR-RX", NULL, "TX XBAR-" sname } |
181 | |
182 | static const struct snd_soc_dapm_route tegra210_ope_routes[] = { |
183 | { "TX" , NULL, "RX" }, |
184 | OPE_ROUTES("Playback" ), |
185 | OPE_ROUTES("Capture" ), |
186 | }; |
187 | |
188 | static const char * const tegra210_ope_data_dir_text[] = { |
189 | "MBDRC to PEQ" , |
190 | "PEQ to MBDRC" |
191 | }; |
192 | |
193 | static const struct soc_enum tegra210_ope_data_dir_enum = |
194 | SOC_ENUM_SINGLE(TEGRA210_OPE_DIR, TEGRA210_OPE_DIR_SHIFT, |
195 | 2, tegra210_ope_data_dir_text); |
196 | |
197 | static int tegra210_ope_get_data_dir(struct snd_kcontrol *kcontrol, |
198 | struct snd_ctl_elem_value *ucontrol) |
199 | { |
200 | struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); |
201 | struct tegra210_ope *ope = snd_soc_component_get_drvdata(c: cmpnt); |
202 | |
203 | ucontrol->value.enumerated.item[0] = ope->data_dir; |
204 | |
205 | return 0; |
206 | } |
207 | |
208 | static int tegra210_ope_put_data_dir(struct snd_kcontrol *kcontrol, |
209 | struct snd_ctl_elem_value *ucontrol) |
210 | { |
211 | struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); |
212 | struct tegra210_ope *ope = snd_soc_component_get_drvdata(c: cmpnt); |
213 | unsigned int value = ucontrol->value.enumerated.item[0]; |
214 | |
215 | if (value == ope->data_dir) |
216 | return 0; |
217 | |
218 | ope->data_dir = value; |
219 | |
220 | return 1; |
221 | } |
222 | |
223 | static const struct snd_kcontrol_new tegra210_ope_controls[] = { |
224 | SOC_ENUM_EXT("Data Flow Direction" , tegra210_ope_data_dir_enum, |
225 | tegra210_ope_get_data_dir, tegra210_ope_put_data_dir), |
226 | }; |
227 | |
228 | static const struct snd_soc_component_driver tegra210_ope_cmpnt = { |
229 | .probe = tegra210_ope_component_probe, |
230 | .dapm_widgets = tegra210_ope_widgets, |
231 | .num_dapm_widgets = ARRAY_SIZE(tegra210_ope_widgets), |
232 | .dapm_routes = tegra210_ope_routes, |
233 | .num_dapm_routes = ARRAY_SIZE(tegra210_ope_routes), |
234 | .controls = tegra210_ope_controls, |
235 | .num_controls = ARRAY_SIZE(tegra210_ope_controls), |
236 | }; |
237 | |
238 | static bool tegra210_ope_wr_reg(struct device *dev, unsigned int reg) |
239 | { |
240 | switch (reg) { |
241 | case TEGRA210_OPE_RX_INT_MASK ... TEGRA210_OPE_RX_CIF_CTRL: |
242 | case TEGRA210_OPE_TX_INT_MASK ... TEGRA210_OPE_TX_CIF_CTRL: |
243 | case TEGRA210_OPE_ENABLE ... TEGRA210_OPE_CG: |
244 | case TEGRA210_OPE_DIR: |
245 | return true; |
246 | default: |
247 | return false; |
248 | } |
249 | } |
250 | |
251 | static bool tegra210_ope_rd_reg(struct device *dev, unsigned int reg) |
252 | { |
253 | if (tegra210_ope_wr_reg(dev, reg)) |
254 | return true; |
255 | |
256 | switch (reg) { |
257 | case TEGRA210_OPE_RX_STATUS: |
258 | case TEGRA210_OPE_RX_INT_STATUS: |
259 | case TEGRA210_OPE_TX_STATUS: |
260 | case TEGRA210_OPE_TX_INT_STATUS: |
261 | case TEGRA210_OPE_STATUS: |
262 | case TEGRA210_OPE_INT_STATUS: |
263 | return true; |
264 | default: |
265 | return false; |
266 | } |
267 | } |
268 | |
269 | static bool tegra210_ope_volatile_reg(struct device *dev, unsigned int reg) |
270 | { |
271 | switch (reg) { |
272 | case TEGRA210_OPE_RX_STATUS: |
273 | case TEGRA210_OPE_RX_INT_STATUS: |
274 | case TEGRA210_OPE_TX_STATUS: |
275 | case TEGRA210_OPE_TX_INT_STATUS: |
276 | case TEGRA210_OPE_SOFT_RESET: |
277 | case TEGRA210_OPE_STATUS: |
278 | case TEGRA210_OPE_INT_STATUS: |
279 | return true; |
280 | default: |
281 | return false; |
282 | } |
283 | } |
284 | |
285 | static const struct regmap_config tegra210_ope_regmap_config = { |
286 | .reg_bits = 32, |
287 | .reg_stride = 4, |
288 | .val_bits = 32, |
289 | .max_register = TEGRA210_OPE_DIR, |
290 | .writeable_reg = tegra210_ope_wr_reg, |
291 | .readable_reg = tegra210_ope_rd_reg, |
292 | .volatile_reg = tegra210_ope_volatile_reg, |
293 | .reg_defaults = tegra210_ope_reg_defaults, |
294 | .num_reg_defaults = ARRAY_SIZE(tegra210_ope_reg_defaults), |
295 | .cache_type = REGCACHE_FLAT, |
296 | }; |
297 | |
298 | static int tegra210_ope_probe(struct platform_device *pdev) |
299 | { |
300 | struct device *dev = &pdev->dev; |
301 | struct tegra210_ope *ope; |
302 | void __iomem *regs; |
303 | int err; |
304 | |
305 | ope = devm_kzalloc(dev, size: sizeof(*ope), GFP_KERNEL); |
306 | if (!ope) |
307 | return -ENOMEM; |
308 | |
309 | regs = devm_platform_ioremap_resource(pdev, index: 0); |
310 | if (IS_ERR(ptr: regs)) |
311 | return PTR_ERR(ptr: regs); |
312 | |
313 | ope->regmap = devm_regmap_init_mmio(dev, regs, |
314 | &tegra210_ope_regmap_config); |
315 | if (IS_ERR(ptr: ope->regmap)) { |
316 | dev_err(dev, "regmap init failed\n" ); |
317 | return PTR_ERR(ptr: ope->regmap); |
318 | } |
319 | |
320 | regcache_cache_only(map: ope->regmap, enable: true); |
321 | |
322 | dev_set_drvdata(dev, data: ope); |
323 | |
324 | err = tegra210_peq_regmap_init(pdev); |
325 | if (err < 0) { |
326 | dev_err(dev, "PEQ init failed\n" ); |
327 | return err; |
328 | } |
329 | |
330 | err = tegra210_mbdrc_regmap_init(pdev); |
331 | if (err < 0) { |
332 | dev_err(dev, "MBDRC init failed\n" ); |
333 | return err; |
334 | } |
335 | |
336 | err = devm_snd_soc_register_component(dev, component_driver: &tegra210_ope_cmpnt, |
337 | dai_drv: tegra210_ope_dais, |
338 | ARRAY_SIZE(tegra210_ope_dais)); |
339 | if (err) { |
340 | dev_err(dev, "can't register OPE component, err: %d\n" , err); |
341 | return err; |
342 | } |
343 | |
344 | pm_runtime_enable(dev); |
345 | |
346 | return 0; |
347 | } |
348 | |
349 | static void tegra210_ope_remove(struct platform_device *pdev) |
350 | { |
351 | pm_runtime_disable(dev: &pdev->dev); |
352 | } |
353 | |
354 | static int __maybe_unused tegra210_ope_runtime_suspend(struct device *dev) |
355 | { |
356 | struct tegra210_ope *ope = dev_get_drvdata(dev); |
357 | |
358 | tegra210_peq_save(regmap: ope->peq_regmap, biquad_gains: ope->peq_biquad_gains, |
359 | biquad_shifts: ope->peq_biquad_shifts); |
360 | |
361 | regcache_cache_only(map: ope->mbdrc_regmap, enable: true); |
362 | regcache_cache_only(map: ope->peq_regmap, enable: true); |
363 | regcache_cache_only(map: ope->regmap, enable: true); |
364 | |
365 | regcache_mark_dirty(map: ope->regmap); |
366 | regcache_mark_dirty(map: ope->peq_regmap); |
367 | regcache_mark_dirty(map: ope->mbdrc_regmap); |
368 | |
369 | return 0; |
370 | } |
371 | |
372 | static int __maybe_unused tegra210_ope_runtime_resume(struct device *dev) |
373 | { |
374 | struct tegra210_ope *ope = dev_get_drvdata(dev); |
375 | |
376 | regcache_cache_only(map: ope->regmap, enable: false); |
377 | regcache_cache_only(map: ope->peq_regmap, enable: false); |
378 | regcache_cache_only(map: ope->mbdrc_regmap, enable: false); |
379 | |
380 | regcache_sync(map: ope->regmap); |
381 | regcache_sync(map: ope->peq_regmap); |
382 | regcache_sync(map: ope->mbdrc_regmap); |
383 | |
384 | tegra210_peq_restore(regmap: ope->peq_regmap, biquad_gains: ope->peq_biquad_gains, |
385 | biquad_shifts: ope->peq_biquad_shifts); |
386 | |
387 | return 0; |
388 | } |
389 | |
390 | static const struct dev_pm_ops tegra210_ope_pm_ops = { |
391 | SET_RUNTIME_PM_OPS(tegra210_ope_runtime_suspend, |
392 | tegra210_ope_runtime_resume, NULL) |
393 | SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, |
394 | pm_runtime_force_resume) |
395 | }; |
396 | |
397 | static const struct of_device_id tegra210_ope_of_match[] = { |
398 | { .compatible = "nvidia,tegra210-ope" }, |
399 | {}, |
400 | }; |
401 | MODULE_DEVICE_TABLE(of, tegra210_ope_of_match); |
402 | |
403 | static struct platform_driver tegra210_ope_driver = { |
404 | .driver = { |
405 | .name = "tegra210-ope" , |
406 | .of_match_table = tegra210_ope_of_match, |
407 | .pm = &tegra210_ope_pm_ops, |
408 | }, |
409 | .probe = tegra210_ope_probe, |
410 | .remove_new = tegra210_ope_remove, |
411 | }; |
412 | module_platform_driver(tegra210_ope_driver) |
413 | |
414 | MODULE_AUTHOR("Sumit Bhattacharya <sumitb@nvidia.com>" ); |
415 | MODULE_DESCRIPTION("Tegra210 OPE ASoC driver" ); |
416 | MODULE_LICENSE("GPL" ); |
417 | |