1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | // |
3 | // tegra210_mvc.c - Tegra210 MVC driver |
4 | // |
5 | // Copyright (c) 2021 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_mvc.h" |
21 | #include "tegra_cif.h" |
22 | |
23 | static const struct reg_default tegra210_mvc_reg_defaults[] = { |
24 | { TEGRA210_MVC_RX_INT_MASK, 0x00000001}, |
25 | { TEGRA210_MVC_RX_CIF_CTRL, 0x00007700}, |
26 | { TEGRA210_MVC_TX_INT_MASK, 0x00000001}, |
27 | { TEGRA210_MVC_TX_CIF_CTRL, 0x00007700}, |
28 | { TEGRA210_MVC_CG, 0x1}, |
29 | { TEGRA210_MVC_CTRL, TEGRA210_MVC_CTRL_DEFAULT}, |
30 | { TEGRA210_MVC_INIT_VOL, 0x00800000}, |
31 | { TEGRA210_MVC_TARGET_VOL, 0x00800000}, |
32 | { TEGRA210_MVC_DURATION, 0x000012c0}, |
33 | { TEGRA210_MVC_DURATION_INV, 0x0006d3a0}, |
34 | { TEGRA210_MVC_POLY_N1, 0x0000007d}, |
35 | { TEGRA210_MVC_POLY_N2, 0x00000271}, |
36 | { TEGRA210_MVC_PEAK_CTRL, 0x000012c0}, |
37 | { TEGRA210_MVC_CFG_RAM_CTRL, 0x00004000}, |
38 | }; |
39 | |
40 | static const struct tegra210_mvc_gain_params gain_params = { |
41 | .poly_coeff = { 23738319, 659403, -3680, |
42 | 15546680, 2530732, -120985, |
43 | 12048422, 5527252, -785042 }, |
44 | .poly_n1 = 16, |
45 | .poly_n2 = 63, |
46 | .duration = 150, |
47 | .duration_inv = 14316558, |
48 | }; |
49 | |
50 | static int __maybe_unused tegra210_mvc_runtime_suspend(struct device *dev) |
51 | { |
52 | struct tegra210_mvc *mvc = dev_get_drvdata(dev); |
53 | |
54 | regmap_read(map: mvc->regmap, TEGRA210_MVC_CTRL, val: &(mvc->ctrl_value)); |
55 | |
56 | regcache_cache_only(map: mvc->regmap, enable: true); |
57 | regcache_mark_dirty(map: mvc->regmap); |
58 | |
59 | return 0; |
60 | } |
61 | |
62 | static int __maybe_unused tegra210_mvc_runtime_resume(struct device *dev) |
63 | { |
64 | struct tegra210_mvc *mvc = dev_get_drvdata(dev); |
65 | |
66 | regcache_cache_only(map: mvc->regmap, enable: false); |
67 | regcache_sync(map: mvc->regmap); |
68 | |
69 | regmap_write(map: mvc->regmap, TEGRA210_MVC_CTRL, val: mvc->ctrl_value); |
70 | regmap_update_bits(map: mvc->regmap, |
71 | TEGRA210_MVC_SWITCH, |
72 | TEGRA210_MVC_VOLUME_SWITCH_MASK, |
73 | TEGRA210_MVC_VOLUME_SWITCH_TRIGGER); |
74 | |
75 | return 0; |
76 | } |
77 | |
78 | static void tegra210_mvc_write_ram(struct regmap *regmap) |
79 | { |
80 | int i; |
81 | |
82 | regmap_write(map: regmap, TEGRA210_MVC_CFG_RAM_CTRL, |
83 | TEGRA210_MVC_CFG_RAM_CTRL_SEQ_ACCESS_EN | |
84 | TEGRA210_MVC_CFG_RAM_CTRL_ADDR_INIT_EN | |
85 | TEGRA210_MVC_CFG_RAM_CTRL_RW_WRITE); |
86 | |
87 | for (i = 0; i < NUM_GAIN_POLY_COEFFS; i++) |
88 | regmap_write(map: regmap, TEGRA210_MVC_CFG_RAM_DATA, |
89 | val: gain_params.poly_coeff[i]); |
90 | } |
91 | |
92 | static void tegra210_mvc_conv_vol(struct tegra210_mvc *mvc, u8 chan, s32 val) |
93 | { |
94 | /* |
95 | * Volume control read from mixer control is with |
96 | * 100x scaling; for CURVE_POLY the reg range |
97 | * is 0-100 (linear, Q24) and for CURVE_LINEAR |
98 | * it is -120dB to +40dB (Q8) |
99 | */ |
100 | if (mvc->curve_type == CURVE_POLY) { |
101 | if (val > 10000) |
102 | val = 10000; |
103 | mvc->volume[chan] = ((val * (1<<8)) / 100) << 16; |
104 | } else { |
105 | val -= 12000; |
106 | mvc->volume[chan] = (val * (1<<8)) / 100; |
107 | } |
108 | } |
109 | |
110 | static u32 tegra210_mvc_get_ctrl_reg(struct snd_kcontrol *kcontrol) |
111 | { |
112 | struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); |
113 | struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(c: cmpnt); |
114 | u32 val; |
115 | |
116 | pm_runtime_get_sync(dev: cmpnt->dev); |
117 | regmap_read(map: mvc->regmap, TEGRA210_MVC_CTRL, val: &val); |
118 | pm_runtime_put(dev: cmpnt->dev); |
119 | |
120 | return val; |
121 | } |
122 | |
123 | static int tegra210_mvc_get_mute(struct snd_kcontrol *kcontrol, |
124 | struct snd_ctl_elem_value *ucontrol) |
125 | { |
126 | u32 val = tegra210_mvc_get_ctrl_reg(kcontrol); |
127 | u8 mute_mask = TEGRA210_GET_MUTE_VAL(val); |
128 | |
129 | /* |
130 | * If per channel control is enabled, then return |
131 | * exact mute/unmute setting of all channels. |
132 | * |
133 | * Else report setting based on CH0 bit to reflect |
134 | * the correct HW state. |
135 | */ |
136 | if (val & TEGRA210_MVC_PER_CHAN_CTRL_EN) { |
137 | ucontrol->value.integer.value[0] = mute_mask; |
138 | } else { |
139 | if (mute_mask & TEGRA210_MVC_CH0_MUTE_EN) |
140 | ucontrol->value.integer.value[0] = |
141 | TEGRA210_MUTE_MASK_EN; |
142 | else |
143 | ucontrol->value.integer.value[0] = 0; |
144 | } |
145 | |
146 | return 0; |
147 | } |
148 | |
149 | static int tegra210_mvc_get_master_mute(struct snd_kcontrol *kcontrol, |
150 | struct snd_ctl_elem_value *ucontrol) |
151 | { |
152 | u32 val = tegra210_mvc_get_ctrl_reg(kcontrol); |
153 | u8 mute_mask = TEGRA210_GET_MUTE_VAL(val); |
154 | |
155 | /* |
156 | * If per channel control is disabled, then return |
157 | * master mute/unmute setting based on CH0 bit. |
158 | * |
159 | * Else report settings based on state of all |
160 | * channels. |
161 | */ |
162 | if (!(val & TEGRA210_MVC_PER_CHAN_CTRL_EN)) { |
163 | ucontrol->value.integer.value[0] = |
164 | mute_mask & TEGRA210_MVC_CH0_MUTE_EN; |
165 | } else { |
166 | if (mute_mask == TEGRA210_MUTE_MASK_EN) |
167 | ucontrol->value.integer.value[0] = |
168 | TEGRA210_MVC_CH0_MUTE_EN; |
169 | else |
170 | ucontrol->value.integer.value[0] = 0; |
171 | } |
172 | |
173 | return 0; |
174 | } |
175 | |
176 | static int tegra210_mvc_volume_switch_timeout(struct snd_soc_component *cmpnt) |
177 | { |
178 | struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(c: cmpnt); |
179 | u32 value; |
180 | int err; |
181 | |
182 | err = regmap_read_poll_timeout(mvc->regmap, TEGRA210_MVC_SWITCH, |
183 | value, !(value & TEGRA210_MVC_VOLUME_SWITCH_MASK), |
184 | 10, 10000); |
185 | if (err < 0) |
186 | dev_err(cmpnt->dev, |
187 | "Volume switch trigger is still active, err = %d\n" , |
188 | err); |
189 | |
190 | return err; |
191 | } |
192 | |
193 | static int tegra210_mvc_update_mute(struct snd_kcontrol *kcontrol, |
194 | struct snd_ctl_elem_value *ucontrol, |
195 | bool per_chan_ctrl) |
196 | { |
197 | struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); |
198 | struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(c: cmpnt); |
199 | u32 mute_val = ucontrol->value.integer.value[0]; |
200 | u32 per_ch_ctrl_val; |
201 | bool change = false; |
202 | int err; |
203 | |
204 | pm_runtime_get_sync(dev: cmpnt->dev); |
205 | |
206 | err = tegra210_mvc_volume_switch_timeout(cmpnt); |
207 | if (err < 0) |
208 | goto end; |
209 | |
210 | if (per_chan_ctrl) { |
211 | per_ch_ctrl_val = TEGRA210_MVC_PER_CHAN_CTRL_EN; |
212 | } else { |
213 | per_ch_ctrl_val = 0; |
214 | |
215 | if (mute_val) |
216 | mute_val = TEGRA210_MUTE_MASK_EN; |
217 | } |
218 | |
219 | regmap_update_bits_check(map: mvc->regmap, TEGRA210_MVC_CTRL, |
220 | TEGRA210_MVC_MUTE_MASK, |
221 | val: mute_val << TEGRA210_MVC_MUTE_SHIFT, |
222 | change: &change); |
223 | |
224 | if (change) { |
225 | regmap_update_bits(map: mvc->regmap, TEGRA210_MVC_CTRL, |
226 | TEGRA210_MVC_PER_CHAN_CTRL_EN_MASK, |
227 | val: per_ch_ctrl_val); |
228 | |
229 | regmap_update_bits(map: mvc->regmap, TEGRA210_MVC_SWITCH, |
230 | TEGRA210_MVC_VOLUME_SWITCH_MASK, |
231 | TEGRA210_MVC_VOLUME_SWITCH_TRIGGER); |
232 | } |
233 | |
234 | end: |
235 | pm_runtime_put(dev: cmpnt->dev); |
236 | |
237 | if (err < 0) |
238 | return err; |
239 | |
240 | if (change) |
241 | return 1; |
242 | |
243 | return 0; |
244 | } |
245 | |
246 | static int tegra210_mvc_put_mute(struct snd_kcontrol *kcontrol, |
247 | struct snd_ctl_elem_value *ucontrol) |
248 | { |
249 | return tegra210_mvc_update_mute(kcontrol, ucontrol, per_chan_ctrl: true); |
250 | } |
251 | |
252 | static int tegra210_mvc_put_master_mute(struct snd_kcontrol *kcontrol, |
253 | struct snd_ctl_elem_value *ucontrol) |
254 | { |
255 | return tegra210_mvc_update_mute(kcontrol, ucontrol, per_chan_ctrl: false); |
256 | } |
257 | |
258 | static int tegra210_mvc_get_vol(struct snd_kcontrol *kcontrol, |
259 | struct snd_ctl_elem_value *ucontrol) |
260 | { |
261 | struct soc_mixer_control *mc = |
262 | (struct soc_mixer_control *)kcontrol->private_value; |
263 | struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); |
264 | struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(c: cmpnt); |
265 | u8 chan = TEGRA210_MVC_GET_CHAN(mc->reg, TEGRA210_MVC_TARGET_VOL); |
266 | s32 val = mvc->volume[chan]; |
267 | |
268 | if (mvc->curve_type == CURVE_POLY) { |
269 | val = ((val >> 16) * 100) >> 8; |
270 | } else { |
271 | val = (val * 100) >> 8; |
272 | val += 12000; |
273 | } |
274 | |
275 | ucontrol->value.integer.value[0] = val; |
276 | |
277 | return 0; |
278 | } |
279 | |
280 | static int tegra210_mvc_get_master_vol(struct snd_kcontrol *kcontrol, |
281 | struct snd_ctl_elem_value *ucontrol) |
282 | { |
283 | return tegra210_mvc_get_vol(kcontrol, ucontrol); |
284 | } |
285 | |
286 | static int tegra210_mvc_update_vol(struct snd_kcontrol *kcontrol, |
287 | struct snd_ctl_elem_value *ucontrol, |
288 | bool per_ch_enable) |
289 | { |
290 | struct soc_mixer_control *mc = |
291 | (struct soc_mixer_control *)kcontrol->private_value; |
292 | struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); |
293 | struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(c: cmpnt); |
294 | u8 chan = TEGRA210_MVC_GET_CHAN(mc->reg, TEGRA210_MVC_TARGET_VOL); |
295 | int old_volume = mvc->volume[chan]; |
296 | int err, i; |
297 | |
298 | pm_runtime_get_sync(dev: cmpnt->dev); |
299 | |
300 | err = tegra210_mvc_volume_switch_timeout(cmpnt); |
301 | if (err < 0) |
302 | goto end; |
303 | |
304 | tegra210_mvc_conv_vol(mvc, chan, val: ucontrol->value.integer.value[0]); |
305 | |
306 | if (mvc->volume[chan] == old_volume) { |
307 | err = 0; |
308 | goto end; |
309 | } |
310 | |
311 | if (per_ch_enable) { |
312 | regmap_update_bits(map: mvc->regmap, TEGRA210_MVC_CTRL, |
313 | TEGRA210_MVC_PER_CHAN_CTRL_EN_MASK, |
314 | TEGRA210_MVC_PER_CHAN_CTRL_EN); |
315 | } else { |
316 | regmap_update_bits(map: mvc->regmap, TEGRA210_MVC_CTRL, |
317 | TEGRA210_MVC_PER_CHAN_CTRL_EN_MASK, val: 0); |
318 | |
319 | for (i = 1; i < TEGRA210_MVC_MAX_CHAN_COUNT; i++) |
320 | mvc->volume[i] = mvc->volume[chan]; |
321 | } |
322 | |
323 | /* Configure init volume same as target volume */ |
324 | regmap_write(map: mvc->regmap, |
325 | TEGRA210_MVC_REG_OFFSET(TEGRA210_MVC_INIT_VOL, chan), |
326 | val: mvc->volume[chan]); |
327 | |
328 | regmap_write(map: mvc->regmap, reg: mc->reg, val: mvc->volume[chan]); |
329 | |
330 | regmap_update_bits(map: mvc->regmap, TEGRA210_MVC_SWITCH, |
331 | TEGRA210_MVC_VOLUME_SWITCH_MASK, |
332 | TEGRA210_MVC_VOLUME_SWITCH_TRIGGER); |
333 | |
334 | err = 1; |
335 | |
336 | end: |
337 | pm_runtime_put(dev: cmpnt->dev); |
338 | |
339 | return err; |
340 | } |
341 | |
342 | static int tegra210_mvc_put_vol(struct snd_kcontrol *kcontrol, |
343 | struct snd_ctl_elem_value *ucontrol) |
344 | { |
345 | return tegra210_mvc_update_vol(kcontrol, ucontrol, per_ch_enable: true); |
346 | } |
347 | |
348 | static int tegra210_mvc_put_master_vol(struct snd_kcontrol *kcontrol, |
349 | struct snd_ctl_elem_value *ucontrol) |
350 | { |
351 | return tegra210_mvc_update_vol(kcontrol, ucontrol, per_ch_enable: false); |
352 | } |
353 | |
354 | static void tegra210_mvc_reset_vol_settings(struct tegra210_mvc *mvc, |
355 | struct device *dev) |
356 | { |
357 | int i; |
358 | |
359 | /* Change volume to default init for new curve type */ |
360 | if (mvc->curve_type == CURVE_POLY) { |
361 | for (i = 0; i < TEGRA210_MVC_MAX_CHAN_COUNT; i++) |
362 | mvc->volume[i] = TEGRA210_MVC_INIT_VOL_DEFAULT_POLY; |
363 | } else { |
364 | for (i = 0; i < TEGRA210_MVC_MAX_CHAN_COUNT; i++) |
365 | mvc->volume[i] = TEGRA210_MVC_INIT_VOL_DEFAULT_LINEAR; |
366 | } |
367 | |
368 | pm_runtime_get_sync(dev); |
369 | |
370 | /* Program curve type */ |
371 | regmap_update_bits(map: mvc->regmap, TEGRA210_MVC_CTRL, |
372 | TEGRA210_MVC_CURVE_TYPE_MASK, |
373 | val: mvc->curve_type << |
374 | TEGRA210_MVC_CURVE_TYPE_SHIFT); |
375 | |
376 | /* Init volume for all channels */ |
377 | for (i = 0; i < TEGRA210_MVC_MAX_CHAN_COUNT; i++) { |
378 | regmap_write(map: mvc->regmap, |
379 | TEGRA210_MVC_REG_OFFSET(TEGRA210_MVC_INIT_VOL, i), |
380 | val: mvc->volume[i]); |
381 | regmap_write(map: mvc->regmap, |
382 | TEGRA210_MVC_REG_OFFSET(TEGRA210_MVC_TARGET_VOL, i), |
383 | val: mvc->volume[i]); |
384 | } |
385 | |
386 | /* Trigger volume switch */ |
387 | regmap_update_bits(map: mvc->regmap, TEGRA210_MVC_SWITCH, |
388 | TEGRA210_MVC_VOLUME_SWITCH_MASK, |
389 | TEGRA210_MVC_VOLUME_SWITCH_TRIGGER); |
390 | |
391 | pm_runtime_put(dev); |
392 | } |
393 | |
394 | static int tegra210_mvc_get_curve_type(struct snd_kcontrol *kcontrol, |
395 | struct snd_ctl_elem_value *ucontrol) |
396 | { |
397 | struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); |
398 | struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(c: cmpnt); |
399 | |
400 | ucontrol->value.enumerated.item[0] = mvc->curve_type; |
401 | |
402 | return 0; |
403 | } |
404 | |
405 | static int tegra210_mvc_put_curve_type(struct snd_kcontrol *kcontrol, |
406 | struct snd_ctl_elem_value *ucontrol) |
407 | { |
408 | struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol); |
409 | struct tegra210_mvc *mvc = snd_soc_component_get_drvdata(c: cmpnt); |
410 | unsigned int value; |
411 | |
412 | regmap_read(map: mvc->regmap, TEGRA210_MVC_ENABLE, val: &value); |
413 | if (value & TEGRA210_MVC_EN) { |
414 | dev_err(cmpnt->dev, |
415 | "Curve type can't be set when MVC is running\n" ); |
416 | return -EINVAL; |
417 | } |
418 | |
419 | if (mvc->curve_type == ucontrol->value.enumerated.item[0]) |
420 | return 0; |
421 | |
422 | mvc->curve_type = ucontrol->value.enumerated.item[0]; |
423 | |
424 | tegra210_mvc_reset_vol_settings(mvc, dev: cmpnt->dev); |
425 | |
426 | return 1; |
427 | } |
428 | |
429 | static int tegra210_mvc_set_audio_cif(struct tegra210_mvc *mvc, |
430 | struct snd_pcm_hw_params *params, |
431 | unsigned int reg) |
432 | { |
433 | unsigned int channels, audio_bits; |
434 | struct tegra_cif_conf cif_conf; |
435 | |
436 | memset(&cif_conf, 0, sizeof(struct tegra_cif_conf)); |
437 | |
438 | channels = params_channels(p: params); |
439 | |
440 | switch (params_format(p: params)) { |
441 | case SNDRV_PCM_FORMAT_S16_LE: |
442 | audio_bits = TEGRA_ACIF_BITS_16; |
443 | break; |
444 | case SNDRV_PCM_FORMAT_S32_LE: |
445 | audio_bits = TEGRA_ACIF_BITS_32; |
446 | break; |
447 | default: |
448 | return -EINVAL; |
449 | } |
450 | |
451 | cif_conf.audio_ch = channels; |
452 | cif_conf.client_ch = channels; |
453 | cif_conf.audio_bits = audio_bits; |
454 | cif_conf.client_bits = audio_bits; |
455 | |
456 | tegra_set_cif(regmap: mvc->regmap, reg, conf: &cif_conf); |
457 | |
458 | return 0; |
459 | } |
460 | |
461 | static int tegra210_mvc_hw_params(struct snd_pcm_substream *substream, |
462 | struct snd_pcm_hw_params *params, |
463 | struct snd_soc_dai *dai) |
464 | { |
465 | struct device *dev = dai->dev; |
466 | struct tegra210_mvc *mvc = snd_soc_dai_get_drvdata(dai); |
467 | int err, val; |
468 | |
469 | /* |
470 | * Soft Reset: Below performs module soft reset which clears |
471 | * all FSM logic, flushes flow control of FIFO and resets the |
472 | * state register. It also brings module back to disabled |
473 | * state (without flushing the data in the pipe). |
474 | */ |
475 | regmap_write(map: mvc->regmap, TEGRA210_MVC_SOFT_RESET, val: 1); |
476 | |
477 | err = regmap_read_poll_timeout(mvc->regmap, TEGRA210_MVC_SOFT_RESET, |
478 | val, !val, 10, 10000); |
479 | if (err < 0) { |
480 | dev_err(dev, "SW reset failed, err = %d\n" , err); |
481 | return err; |
482 | } |
483 | |
484 | /* Set RX CIF */ |
485 | err = tegra210_mvc_set_audio_cif(mvc, params, TEGRA210_MVC_RX_CIF_CTRL); |
486 | if (err) { |
487 | dev_err(dev, "Can't set MVC RX CIF: %d\n" , err); |
488 | return err; |
489 | } |
490 | |
491 | /* Set TX CIF */ |
492 | err = tegra210_mvc_set_audio_cif(mvc, params, TEGRA210_MVC_TX_CIF_CTRL); |
493 | if (err) { |
494 | dev_err(dev, "Can't set MVC TX CIF: %d\n" , err); |
495 | return err; |
496 | } |
497 | |
498 | tegra210_mvc_write_ram(regmap: mvc->regmap); |
499 | |
500 | /* Program poly_n1, poly_n2, duration */ |
501 | regmap_write(map: mvc->regmap, TEGRA210_MVC_POLY_N1, val: gain_params.poly_n1); |
502 | regmap_write(map: mvc->regmap, TEGRA210_MVC_POLY_N2, val: gain_params.poly_n2); |
503 | regmap_write(map: mvc->regmap, TEGRA210_MVC_DURATION, val: gain_params.duration); |
504 | |
505 | /* Program duration_inv */ |
506 | regmap_write(map: mvc->regmap, TEGRA210_MVC_DURATION_INV, |
507 | val: gain_params.duration_inv); |
508 | |
509 | return 0; |
510 | } |
511 | |
512 | static const struct snd_soc_dai_ops tegra210_mvc_dai_ops = { |
513 | .hw_params = tegra210_mvc_hw_params, |
514 | }; |
515 | |
516 | static const char * const tegra210_mvc_curve_type_text[] = { |
517 | "Poly" , |
518 | "Linear" , |
519 | }; |
520 | |
521 | static const struct soc_enum tegra210_mvc_curve_type_ctrl = |
522 | SOC_ENUM_SINGLE_EXT(2, tegra210_mvc_curve_type_text); |
523 | |
524 | #define TEGRA210_MVC_VOL_CTRL(chan) \ |
525 | SOC_SINGLE_EXT("Channel" #chan " Volume", \ |
526 | TEGRA210_MVC_REG_OFFSET(TEGRA210_MVC_TARGET_VOL, \ |
527 | (chan - 1)), \ |
528 | 0, 16000, 0, tegra210_mvc_get_vol, \ |
529 | tegra210_mvc_put_vol) |
530 | |
531 | static const struct snd_kcontrol_new tegra210_mvc_vol_ctrl[] = { |
532 | /* Per channel volume control */ |
533 | TEGRA210_MVC_VOL_CTRL(1), |
534 | TEGRA210_MVC_VOL_CTRL(2), |
535 | TEGRA210_MVC_VOL_CTRL(3), |
536 | TEGRA210_MVC_VOL_CTRL(4), |
537 | TEGRA210_MVC_VOL_CTRL(5), |
538 | TEGRA210_MVC_VOL_CTRL(6), |
539 | TEGRA210_MVC_VOL_CTRL(7), |
540 | TEGRA210_MVC_VOL_CTRL(8), |
541 | |
542 | /* Per channel mute */ |
543 | SOC_SINGLE_EXT("Per Chan Mute Mask" , |
544 | TEGRA210_MVC_CTRL, 0, TEGRA210_MUTE_MASK_EN, 0, |
545 | tegra210_mvc_get_mute, tegra210_mvc_put_mute), |
546 | |
547 | /* Master volume */ |
548 | SOC_SINGLE_EXT("Volume" , TEGRA210_MVC_TARGET_VOL, 0, 16000, 0, |
549 | tegra210_mvc_get_master_vol, |
550 | tegra210_mvc_put_master_vol), |
551 | |
552 | /* Master mute */ |
553 | SOC_SINGLE_EXT("Mute" , TEGRA210_MVC_CTRL, 0, 1, 0, |
554 | tegra210_mvc_get_master_mute, |
555 | tegra210_mvc_put_master_mute), |
556 | |
557 | SOC_ENUM_EXT("Curve Type" , tegra210_mvc_curve_type_ctrl, |
558 | tegra210_mvc_get_curve_type, tegra210_mvc_put_curve_type), |
559 | }; |
560 | |
561 | static struct snd_soc_dai_driver tegra210_mvc_dais[] = { |
562 | /* Input */ |
563 | { |
564 | .name = "MVC-RX-CIF" , |
565 | .playback = { |
566 | .stream_name = "RX-CIF-Playback" , |
567 | .channels_min = 1, |
568 | .channels_max = 8, |
569 | .rates = SNDRV_PCM_RATE_8000_192000, |
570 | .formats = SNDRV_PCM_FMTBIT_S8 | |
571 | SNDRV_PCM_FMTBIT_S16_LE | |
572 | SNDRV_PCM_FMTBIT_S32_LE, |
573 | }, |
574 | .capture = { |
575 | .stream_name = "RX-CIF-Capture" , |
576 | .channels_min = 1, |
577 | .channels_max = 8, |
578 | .rates = SNDRV_PCM_RATE_8000_192000, |
579 | .formats = SNDRV_PCM_FMTBIT_S8 | |
580 | SNDRV_PCM_FMTBIT_S16_LE | |
581 | SNDRV_PCM_FMTBIT_S32_LE, |
582 | }, |
583 | }, |
584 | |
585 | /* Output */ |
586 | { |
587 | .name = "MVC-TX-CIF" , |
588 | .playback = { |
589 | .stream_name = "TX-CIF-Playback" , |
590 | .channels_min = 1, |
591 | .channels_max = 8, |
592 | .rates = SNDRV_PCM_RATE_8000_192000, |
593 | .formats = SNDRV_PCM_FMTBIT_S8 | |
594 | SNDRV_PCM_FMTBIT_S16_LE | |
595 | SNDRV_PCM_FMTBIT_S32_LE, |
596 | }, |
597 | .capture = { |
598 | .stream_name = "TX-CIF-Capture" , |
599 | .channels_min = 1, |
600 | .channels_max = 8, |
601 | .rates = SNDRV_PCM_RATE_8000_192000, |
602 | .formats = SNDRV_PCM_FMTBIT_S8 | |
603 | SNDRV_PCM_FMTBIT_S16_LE | |
604 | SNDRV_PCM_FMTBIT_S32_LE, |
605 | }, |
606 | .ops = &tegra210_mvc_dai_ops, |
607 | } |
608 | }; |
609 | |
610 | static const struct snd_soc_dapm_widget tegra210_mvc_widgets[] = { |
611 | SND_SOC_DAPM_AIF_IN("RX" , NULL, 0, SND_SOC_NOPM, 0, 0), |
612 | SND_SOC_DAPM_AIF_OUT("TX" , NULL, 0, TEGRA210_MVC_ENABLE, |
613 | TEGRA210_MVC_EN_SHIFT, 0), |
614 | }; |
615 | |
616 | #define MVC_ROUTES(sname) \ |
617 | { "RX XBAR-" sname, NULL, "XBAR-TX" }, \ |
618 | { "RX-CIF-" sname, NULL, "RX XBAR-" sname }, \ |
619 | { "RX", NULL, "RX-CIF-" sname }, \ |
620 | { "TX-CIF-" sname, NULL, "TX" }, \ |
621 | { "TX XBAR-" sname, NULL, "TX-CIF-" sname }, \ |
622 | { "XBAR-RX", NULL, "TX XBAR-" sname } |
623 | |
624 | static const struct snd_soc_dapm_route tegra210_mvc_routes[] = { |
625 | { "TX" , NULL, "RX" }, |
626 | MVC_ROUTES("Playback" ), |
627 | MVC_ROUTES("Capture" ), |
628 | }; |
629 | |
630 | static const struct snd_soc_component_driver tegra210_mvc_cmpnt = { |
631 | .dapm_widgets = tegra210_mvc_widgets, |
632 | .num_dapm_widgets = ARRAY_SIZE(tegra210_mvc_widgets), |
633 | .dapm_routes = tegra210_mvc_routes, |
634 | .num_dapm_routes = ARRAY_SIZE(tegra210_mvc_routes), |
635 | .controls = tegra210_mvc_vol_ctrl, |
636 | .num_controls = ARRAY_SIZE(tegra210_mvc_vol_ctrl), |
637 | }; |
638 | |
639 | static bool tegra210_mvc_rd_reg(struct device *dev, unsigned int reg) |
640 | { |
641 | switch (reg) { |
642 | case TEGRA210_MVC_RX_STATUS ... TEGRA210_MVC_CONFIG_ERR_TYPE: |
643 | return true; |
644 | default: |
645 | return false; |
646 | }; |
647 | } |
648 | |
649 | static bool tegra210_mvc_wr_reg(struct device *dev, unsigned int reg) |
650 | { |
651 | switch (reg) { |
652 | case TEGRA210_MVC_RX_INT_MASK ... TEGRA210_MVC_RX_CIF_CTRL: |
653 | case TEGRA210_MVC_TX_INT_MASK ... TEGRA210_MVC_TX_CIF_CTRL: |
654 | case TEGRA210_MVC_ENABLE ... TEGRA210_MVC_CG: |
655 | case TEGRA210_MVC_CTRL ... TEGRA210_MVC_CFG_RAM_DATA: |
656 | return true; |
657 | default: |
658 | return false; |
659 | } |
660 | } |
661 | |
662 | static bool tegra210_mvc_volatile_reg(struct device *dev, unsigned int reg) |
663 | { |
664 | switch (reg) { |
665 | case TEGRA210_MVC_RX_STATUS: |
666 | case TEGRA210_MVC_RX_INT_STATUS: |
667 | case TEGRA210_MVC_RX_INT_SET: |
668 | |
669 | case TEGRA210_MVC_TX_STATUS: |
670 | case TEGRA210_MVC_TX_INT_STATUS: |
671 | case TEGRA210_MVC_TX_INT_SET: |
672 | |
673 | case TEGRA210_MVC_SOFT_RESET: |
674 | case TEGRA210_MVC_STATUS: |
675 | case TEGRA210_MVC_INT_STATUS: |
676 | case TEGRA210_MVC_SWITCH: |
677 | case TEGRA210_MVC_CFG_RAM_CTRL: |
678 | case TEGRA210_MVC_CFG_RAM_DATA: |
679 | case TEGRA210_MVC_PEAK_VALUE: |
680 | case TEGRA210_MVC_CTRL: |
681 | return true; |
682 | default: |
683 | return false; |
684 | } |
685 | } |
686 | |
687 | static const struct regmap_config tegra210_mvc_regmap_config = { |
688 | .reg_bits = 32, |
689 | .reg_stride = 4, |
690 | .val_bits = 32, |
691 | .max_register = TEGRA210_MVC_CONFIG_ERR_TYPE, |
692 | .writeable_reg = tegra210_mvc_wr_reg, |
693 | .readable_reg = tegra210_mvc_rd_reg, |
694 | .volatile_reg = tegra210_mvc_volatile_reg, |
695 | .reg_defaults = tegra210_mvc_reg_defaults, |
696 | .num_reg_defaults = ARRAY_SIZE(tegra210_mvc_reg_defaults), |
697 | .cache_type = REGCACHE_FLAT, |
698 | }; |
699 | |
700 | static const struct of_device_id tegra210_mvc_of_match[] = { |
701 | { .compatible = "nvidia,tegra210-mvc" }, |
702 | {}, |
703 | }; |
704 | MODULE_DEVICE_TABLE(of, tegra210_mvc_of_match); |
705 | |
706 | static int tegra210_mvc_platform_probe(struct platform_device *pdev) |
707 | { |
708 | struct device *dev = &pdev->dev; |
709 | struct tegra210_mvc *mvc; |
710 | void __iomem *regs; |
711 | int err; |
712 | |
713 | mvc = devm_kzalloc(dev, size: sizeof(*mvc), GFP_KERNEL); |
714 | if (!mvc) |
715 | return -ENOMEM; |
716 | |
717 | dev_set_drvdata(dev, data: mvc); |
718 | |
719 | mvc->curve_type = CURVE_LINEAR; |
720 | mvc->ctrl_value = TEGRA210_MVC_CTRL_DEFAULT; |
721 | |
722 | regs = devm_platform_ioremap_resource(pdev, index: 0); |
723 | if (IS_ERR(ptr: regs)) |
724 | return PTR_ERR(ptr: regs); |
725 | |
726 | mvc->regmap = devm_regmap_init_mmio(dev, regs, |
727 | &tegra210_mvc_regmap_config); |
728 | if (IS_ERR(ptr: mvc->regmap)) { |
729 | dev_err(dev, "regmap init failed\n" ); |
730 | return PTR_ERR(ptr: mvc->regmap); |
731 | } |
732 | |
733 | regcache_cache_only(map: mvc->regmap, enable: true); |
734 | |
735 | err = devm_snd_soc_register_component(dev, component_driver: &tegra210_mvc_cmpnt, |
736 | dai_drv: tegra210_mvc_dais, |
737 | ARRAY_SIZE(tegra210_mvc_dais)); |
738 | if (err) { |
739 | dev_err(dev, "can't register MVC component, err: %d\n" , err); |
740 | return err; |
741 | } |
742 | |
743 | pm_runtime_enable(dev); |
744 | |
745 | tegra210_mvc_reset_vol_settings(mvc, dev: &pdev->dev); |
746 | |
747 | return 0; |
748 | } |
749 | |
750 | static void tegra210_mvc_platform_remove(struct platform_device *pdev) |
751 | { |
752 | pm_runtime_disable(dev: &pdev->dev); |
753 | } |
754 | |
755 | static const struct dev_pm_ops tegra210_mvc_pm_ops = { |
756 | SET_RUNTIME_PM_OPS(tegra210_mvc_runtime_suspend, |
757 | tegra210_mvc_runtime_resume, NULL) |
758 | SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, |
759 | pm_runtime_force_resume) |
760 | }; |
761 | |
762 | static struct platform_driver tegra210_mvc_driver = { |
763 | .driver = { |
764 | .name = "tegra210-mvc" , |
765 | .of_match_table = tegra210_mvc_of_match, |
766 | .pm = &tegra210_mvc_pm_ops, |
767 | }, |
768 | .probe = tegra210_mvc_platform_probe, |
769 | .remove_new = tegra210_mvc_platform_remove, |
770 | }; |
771 | module_platform_driver(tegra210_mvc_driver) |
772 | |
773 | MODULE_AUTHOR("Arun Shamanna Lakshmi <aruns@nvidia.com>" ); |
774 | MODULE_DESCRIPTION("Tegra210 MVC ASoC driver" ); |
775 | MODULE_LICENSE("GPL v2" ); |
776 | |