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