1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * McBSP Sidetone support |
4 | * |
5 | * Copyright (C) 2004 Nokia Corporation |
6 | * Author: Samuel Ortiz <samuel.ortiz@nokia.com> |
7 | * |
8 | * Contact: Jarkko Nikula <jarkko.nikula@bitmer.com> |
9 | * Peter Ujfalusi <peter.ujfalusi@ti.com> |
10 | */ |
11 | |
12 | #include <linux/module.h> |
13 | #include <linux/init.h> |
14 | #include <linux/device.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/interrupt.h> |
17 | #include <linux/err.h> |
18 | #include <linux/clk.h> |
19 | #include <linux/delay.h> |
20 | #include <linux/io.h> |
21 | #include <linux/slab.h> |
22 | |
23 | #include "omap-mcbsp.h" |
24 | #include "omap-mcbsp-priv.h" |
25 | |
26 | /* OMAP3 sidetone control registers */ |
27 | #define OMAP_ST_REG_REV 0x00 |
28 | #define OMAP_ST_REG_SYSCONFIG 0x10 |
29 | #define OMAP_ST_REG_IRQSTATUS 0x18 |
30 | #define OMAP_ST_REG_IRQENABLE 0x1C |
31 | #define OMAP_ST_REG_SGAINCR 0x24 |
32 | #define OMAP_ST_REG_SFIRCR 0x28 |
33 | #define OMAP_ST_REG_SSELCR 0x2C |
34 | |
35 | /********************** McBSP SSELCR bit definitions ***********************/ |
36 | #define SIDETONEEN BIT(10) |
37 | |
38 | /********************** McBSP Sidetone SYSCONFIG bit definitions ***********/ |
39 | #define ST_AUTOIDLE BIT(0) |
40 | |
41 | /********************** McBSP Sidetone SGAINCR bit definitions *************/ |
42 | #define ST_CH0GAIN(value) ((value) & 0xffff) /* Bits 0:15 */ |
43 | #define ST_CH1GAIN(value) (((value) & 0xffff) << 16) /* Bits 16:31 */ |
44 | |
45 | /********************** McBSP Sidetone SFIRCR bit definitions **************/ |
46 | #define ST_FIRCOEFF(value) ((value) & 0xffff) /* Bits 0:15 */ |
47 | |
48 | /********************** McBSP Sidetone SSELCR bit definitions **************/ |
49 | #define ST_SIDETONEEN BIT(0) |
50 | #define ST_COEFFWREN BIT(1) |
51 | #define ST_COEFFWRDONE BIT(2) |
52 | |
53 | struct omap_mcbsp_st_data { |
54 | void __iomem *io_base_st; |
55 | struct clk *mcbsp_iclk; |
56 | bool running; |
57 | bool enabled; |
58 | s16 taps[128]; /* Sidetone filter coefficients */ |
59 | int nr_taps; /* Number of filter coefficients in use */ |
60 | s16 ch0gain; |
61 | s16 ch1gain; |
62 | }; |
63 | |
64 | static void omap_mcbsp_st_write(struct omap_mcbsp *mcbsp, u16 reg, u32 val) |
65 | { |
66 | writel_relaxed(val, mcbsp->st_data->io_base_st + reg); |
67 | } |
68 | |
69 | static int omap_mcbsp_st_read(struct omap_mcbsp *mcbsp, u16 reg) |
70 | { |
71 | return readl_relaxed(mcbsp->st_data->io_base_st + reg); |
72 | } |
73 | |
74 | #define MCBSP_ST_READ(mcbsp, reg) omap_mcbsp_st_read(mcbsp, OMAP_ST_REG_##reg) |
75 | #define MCBSP_ST_WRITE(mcbsp, reg, val) \ |
76 | omap_mcbsp_st_write(mcbsp, OMAP_ST_REG_##reg, val) |
77 | |
78 | static void omap_mcbsp_st_on(struct omap_mcbsp *mcbsp) |
79 | { |
80 | unsigned int w; |
81 | |
82 | if (mcbsp->pdata->force_ick_on) |
83 | mcbsp->pdata->force_ick_on(mcbsp->st_data->mcbsp_iclk, true); |
84 | |
85 | /* Disable Sidetone clock auto-gating for normal operation */ |
86 | w = MCBSP_ST_READ(mcbsp, SYSCONFIG); |
87 | MCBSP_ST_WRITE(mcbsp, SYSCONFIG, w & ~(ST_AUTOIDLE)); |
88 | |
89 | /* Enable McBSP Sidetone */ |
90 | w = MCBSP_READ(mcbsp, SSELCR); |
91 | MCBSP_WRITE(mcbsp, SSELCR, w | SIDETONEEN); |
92 | |
93 | /* Enable Sidetone from Sidetone Core */ |
94 | w = MCBSP_ST_READ(mcbsp, SSELCR); |
95 | MCBSP_ST_WRITE(mcbsp, SSELCR, w | ST_SIDETONEEN); |
96 | } |
97 | |
98 | static void omap_mcbsp_st_off(struct omap_mcbsp *mcbsp) |
99 | { |
100 | unsigned int w; |
101 | |
102 | w = MCBSP_ST_READ(mcbsp, SSELCR); |
103 | MCBSP_ST_WRITE(mcbsp, SSELCR, w & ~(ST_SIDETONEEN)); |
104 | |
105 | w = MCBSP_READ(mcbsp, SSELCR); |
106 | MCBSP_WRITE(mcbsp, SSELCR, w & ~(SIDETONEEN)); |
107 | |
108 | /* Enable Sidetone clock auto-gating to reduce power consumption */ |
109 | w = MCBSP_ST_READ(mcbsp, SYSCONFIG); |
110 | MCBSP_ST_WRITE(mcbsp, SYSCONFIG, w | ST_AUTOIDLE); |
111 | |
112 | if (mcbsp->pdata->force_ick_on) |
113 | mcbsp->pdata->force_ick_on(mcbsp->st_data->mcbsp_iclk, false); |
114 | } |
115 | |
116 | static void omap_mcbsp_st_fir_write(struct omap_mcbsp *mcbsp, s16 *fir) |
117 | { |
118 | u16 val, i; |
119 | |
120 | val = MCBSP_ST_READ(mcbsp, SSELCR); |
121 | |
122 | if (val & ST_COEFFWREN) |
123 | MCBSP_ST_WRITE(mcbsp, SSELCR, val & ~(ST_COEFFWREN)); |
124 | |
125 | MCBSP_ST_WRITE(mcbsp, SSELCR, val | ST_COEFFWREN); |
126 | |
127 | for (i = 0; i < 128; i++) |
128 | MCBSP_ST_WRITE(mcbsp, SFIRCR, fir[i]); |
129 | |
130 | i = 0; |
131 | |
132 | val = MCBSP_ST_READ(mcbsp, SSELCR); |
133 | while (!(val & ST_COEFFWRDONE) && (++i < 1000)) |
134 | val = MCBSP_ST_READ(mcbsp, SSELCR); |
135 | |
136 | MCBSP_ST_WRITE(mcbsp, SSELCR, val & ~(ST_COEFFWREN)); |
137 | |
138 | if (i == 1000) |
139 | dev_err(mcbsp->dev, "McBSP FIR load error!\n" ); |
140 | } |
141 | |
142 | static void omap_mcbsp_st_chgain(struct omap_mcbsp *mcbsp) |
143 | { |
144 | struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
145 | |
146 | MCBSP_ST_WRITE(mcbsp, SGAINCR, ST_CH0GAIN(st_data->ch0gain) | |
147 | ST_CH1GAIN(st_data->ch1gain)); |
148 | } |
149 | |
150 | static int omap_mcbsp_st_set_chgain(struct omap_mcbsp *mcbsp, int channel, |
151 | s16 chgain) |
152 | { |
153 | struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
154 | int ret = 0; |
155 | |
156 | if (!st_data) |
157 | return -ENOENT; |
158 | |
159 | spin_lock_irq(lock: &mcbsp->lock); |
160 | if (channel == 0) |
161 | st_data->ch0gain = chgain; |
162 | else if (channel == 1) |
163 | st_data->ch1gain = chgain; |
164 | else |
165 | ret = -EINVAL; |
166 | |
167 | if (st_data->enabled) |
168 | omap_mcbsp_st_chgain(mcbsp); |
169 | spin_unlock_irq(lock: &mcbsp->lock); |
170 | |
171 | return ret; |
172 | } |
173 | |
174 | static int omap_mcbsp_st_get_chgain(struct omap_mcbsp *mcbsp, int channel, |
175 | s16 *chgain) |
176 | { |
177 | struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
178 | int ret = 0; |
179 | |
180 | if (!st_data) |
181 | return -ENOENT; |
182 | |
183 | spin_lock_irq(lock: &mcbsp->lock); |
184 | if (channel == 0) |
185 | *chgain = st_data->ch0gain; |
186 | else if (channel == 1) |
187 | *chgain = st_data->ch1gain; |
188 | else |
189 | ret = -EINVAL; |
190 | spin_unlock_irq(lock: &mcbsp->lock); |
191 | |
192 | return ret; |
193 | } |
194 | |
195 | static int omap_mcbsp_st_enable(struct omap_mcbsp *mcbsp) |
196 | { |
197 | struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
198 | |
199 | if (!st_data) |
200 | return -ENODEV; |
201 | |
202 | spin_lock_irq(lock: &mcbsp->lock); |
203 | st_data->enabled = 1; |
204 | omap_mcbsp_st_start(mcbsp); |
205 | spin_unlock_irq(lock: &mcbsp->lock); |
206 | |
207 | return 0; |
208 | } |
209 | |
210 | static int omap_mcbsp_st_disable(struct omap_mcbsp *mcbsp) |
211 | { |
212 | struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
213 | int ret = 0; |
214 | |
215 | if (!st_data) |
216 | return -ENODEV; |
217 | |
218 | spin_lock_irq(lock: &mcbsp->lock); |
219 | omap_mcbsp_st_stop(mcbsp); |
220 | st_data->enabled = 0; |
221 | spin_unlock_irq(lock: &mcbsp->lock); |
222 | |
223 | return ret; |
224 | } |
225 | |
226 | static int omap_mcbsp_st_is_enabled(struct omap_mcbsp *mcbsp) |
227 | { |
228 | struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
229 | |
230 | if (!st_data) |
231 | return -ENODEV; |
232 | |
233 | return st_data->enabled; |
234 | } |
235 | |
236 | static ssize_t st_taps_show(struct device *dev, |
237 | struct device_attribute *attr, char *buf) |
238 | { |
239 | struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); |
240 | struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
241 | ssize_t status = 0; |
242 | int i; |
243 | |
244 | spin_lock_irq(lock: &mcbsp->lock); |
245 | for (i = 0; i < st_data->nr_taps; i++) |
246 | status += sysfs_emit_at(buf, at: status, fmt: (i ? ", %d" : "%d" ), |
247 | st_data->taps[i]); |
248 | if (i) |
249 | status += sysfs_emit_at(buf, at: status, fmt: "\n" ); |
250 | spin_unlock_irq(lock: &mcbsp->lock); |
251 | |
252 | return status; |
253 | } |
254 | |
255 | static ssize_t st_taps_store(struct device *dev, |
256 | struct device_attribute *attr, |
257 | const char *buf, size_t size) |
258 | { |
259 | struct omap_mcbsp *mcbsp = dev_get_drvdata(dev); |
260 | struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
261 | int val, tmp, status, i = 0; |
262 | |
263 | spin_lock_irq(lock: &mcbsp->lock); |
264 | memset(st_data->taps, 0, sizeof(st_data->taps)); |
265 | st_data->nr_taps = 0; |
266 | |
267 | do { |
268 | status = sscanf(buf, "%d%n" , &val, &tmp); |
269 | if (status < 0 || status == 0) { |
270 | size = -EINVAL; |
271 | goto out; |
272 | } |
273 | if (val < -32768 || val > 32767) { |
274 | size = -EINVAL; |
275 | goto out; |
276 | } |
277 | st_data->taps[i++] = val; |
278 | buf += tmp; |
279 | if (*buf != ',') |
280 | break; |
281 | buf++; |
282 | } while (1); |
283 | |
284 | st_data->nr_taps = i; |
285 | |
286 | out: |
287 | spin_unlock_irq(lock: &mcbsp->lock); |
288 | |
289 | return size; |
290 | } |
291 | |
292 | static DEVICE_ATTR_RW(st_taps); |
293 | |
294 | static const struct attribute *sidetone_attrs[] = { |
295 | &dev_attr_st_taps.attr, |
296 | NULL, |
297 | }; |
298 | |
299 | static const struct attribute_group sidetone_attr_group = { |
300 | .attrs = (struct attribute **)sidetone_attrs, |
301 | }; |
302 | |
303 | int omap_mcbsp_st_start(struct omap_mcbsp *mcbsp) |
304 | { |
305 | struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
306 | |
307 | if (st_data->enabled && !st_data->running) { |
308 | omap_mcbsp_st_fir_write(mcbsp, fir: st_data->taps); |
309 | omap_mcbsp_st_chgain(mcbsp); |
310 | |
311 | if (!mcbsp->free) { |
312 | omap_mcbsp_st_on(mcbsp); |
313 | st_data->running = 1; |
314 | } |
315 | } |
316 | |
317 | return 0; |
318 | } |
319 | |
320 | int omap_mcbsp_st_stop(struct omap_mcbsp *mcbsp) |
321 | { |
322 | struct omap_mcbsp_st_data *st_data = mcbsp->st_data; |
323 | |
324 | if (st_data->running) { |
325 | if (!mcbsp->free) { |
326 | omap_mcbsp_st_off(mcbsp); |
327 | st_data->running = 0; |
328 | } |
329 | } |
330 | |
331 | return 0; |
332 | } |
333 | |
334 | int omap_mcbsp_st_init(struct platform_device *pdev) |
335 | { |
336 | struct omap_mcbsp *mcbsp = platform_get_drvdata(pdev); |
337 | struct omap_mcbsp_st_data *st_data; |
338 | struct resource *res; |
339 | int ret; |
340 | |
341 | res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sidetone" ); |
342 | if (!res) |
343 | return 0; |
344 | |
345 | st_data = devm_kzalloc(dev: mcbsp->dev, size: sizeof(*mcbsp->st_data), GFP_KERNEL); |
346 | if (!st_data) |
347 | return -ENOMEM; |
348 | |
349 | st_data->mcbsp_iclk = devm_clk_get(dev: mcbsp->dev, id: "ick" ); |
350 | if (IS_ERR(ptr: st_data->mcbsp_iclk)) { |
351 | dev_warn(mcbsp->dev, |
352 | "Failed to get ick, sidetone might be broken\n" ); |
353 | st_data->mcbsp_iclk = NULL; |
354 | } |
355 | |
356 | st_data->io_base_st = devm_ioremap(dev: mcbsp->dev, offset: res->start, |
357 | size: resource_size(res)); |
358 | if (!st_data->io_base_st) |
359 | return -ENOMEM; |
360 | |
361 | ret = devm_device_add_group(dev: mcbsp->dev, grp: &sidetone_attr_group); |
362 | if (ret) |
363 | return ret; |
364 | |
365 | mcbsp->st_data = st_data; |
366 | |
367 | return 0; |
368 | } |
369 | |
370 | static int omap_mcbsp_st_info_volsw(struct snd_kcontrol *kcontrol, |
371 | struct snd_ctl_elem_info *uinfo) |
372 | { |
373 | struct soc_mixer_control *mc = |
374 | (struct soc_mixer_control *)kcontrol->private_value; |
375 | int max = mc->max; |
376 | int min = mc->min; |
377 | |
378 | uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; |
379 | uinfo->count = 1; |
380 | uinfo->value.integer.min = min; |
381 | uinfo->value.integer.max = max; |
382 | return 0; |
383 | } |
384 | |
385 | #define OMAP_MCBSP_ST_CHANNEL_VOLUME(channel) \ |
386 | static int \ |
387 | omap_mcbsp_set_st_ch##channel##_volume(struct snd_kcontrol *kc, \ |
388 | struct snd_ctl_elem_value *uc) \ |
389 | { \ |
390 | struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kc); \ |
391 | struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); \ |
392 | struct soc_mixer_control *mc = \ |
393 | (struct soc_mixer_control *)kc->private_value; \ |
394 | int max = mc->max; \ |
395 | int min = mc->min; \ |
396 | int val = uc->value.integer.value[0]; \ |
397 | \ |
398 | if (val < min || val > max) \ |
399 | return -EINVAL; \ |
400 | \ |
401 | /* OMAP McBSP implementation uses index values 0..4 */ \ |
402 | return omap_mcbsp_st_set_chgain(mcbsp, channel, val); \ |
403 | } \ |
404 | \ |
405 | static int \ |
406 | omap_mcbsp_get_st_ch##channel##_volume(struct snd_kcontrol *kc, \ |
407 | struct snd_ctl_elem_value *uc) \ |
408 | { \ |
409 | struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kc); \ |
410 | struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(cpu_dai); \ |
411 | s16 chgain; \ |
412 | \ |
413 | if (omap_mcbsp_st_get_chgain(mcbsp, channel, &chgain)) \ |
414 | return -EAGAIN; \ |
415 | \ |
416 | uc->value.integer.value[0] = chgain; \ |
417 | return 0; \ |
418 | } |
419 | |
420 | OMAP_MCBSP_ST_CHANNEL_VOLUME(0) |
421 | OMAP_MCBSP_ST_CHANNEL_VOLUME(1) |
422 | |
423 | static int omap_mcbsp_st_put_mode(struct snd_kcontrol *kcontrol, |
424 | struct snd_ctl_elem_value *ucontrol) |
425 | { |
426 | struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); |
427 | struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(dai: cpu_dai); |
428 | u8 value = ucontrol->value.integer.value[0]; |
429 | |
430 | if (value == omap_mcbsp_st_is_enabled(mcbsp)) |
431 | return 0; |
432 | |
433 | if (value) |
434 | omap_mcbsp_st_enable(mcbsp); |
435 | else |
436 | omap_mcbsp_st_disable(mcbsp); |
437 | |
438 | return 1; |
439 | } |
440 | |
441 | static int omap_mcbsp_st_get_mode(struct snd_kcontrol *kcontrol, |
442 | struct snd_ctl_elem_value *ucontrol) |
443 | { |
444 | struct snd_soc_dai *cpu_dai = snd_kcontrol_chip(kcontrol); |
445 | struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(dai: cpu_dai); |
446 | |
447 | ucontrol->value.integer.value[0] = omap_mcbsp_st_is_enabled(mcbsp); |
448 | return 0; |
449 | } |
450 | |
451 | #define OMAP_MCBSP_SOC_SINGLE_S16_EXT(xname, xmin, xmax, \ |
452 | xhandler_get, xhandler_put) \ |
453 | { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \ |
454 | .info = omap_mcbsp_st_info_volsw, \ |
455 | .get = xhandler_get, .put = xhandler_put, \ |
456 | .private_value = (unsigned long)&(struct soc_mixer_control) \ |
457 | {.min = xmin, .max = xmax} } |
458 | |
459 | #define OMAP_MCBSP_ST_CONTROLS(port) \ |
460 | static const struct snd_kcontrol_new omap_mcbsp##port##_st_controls[] = { \ |
461 | SOC_SINGLE_EXT("McBSP" #port " Sidetone Switch", 1, 0, 1, 0, \ |
462 | omap_mcbsp_st_get_mode, omap_mcbsp_st_put_mode), \ |
463 | OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP" #port " Sidetone Channel 0 Volume", \ |
464 | -32768, 32767, \ |
465 | omap_mcbsp_get_st_ch0_volume, \ |
466 | omap_mcbsp_set_st_ch0_volume), \ |
467 | OMAP_MCBSP_SOC_SINGLE_S16_EXT("McBSP" #port " Sidetone Channel 1 Volume", \ |
468 | -32768, 32767, \ |
469 | omap_mcbsp_get_st_ch1_volume, \ |
470 | omap_mcbsp_set_st_ch1_volume), \ |
471 | } |
472 | |
473 | OMAP_MCBSP_ST_CONTROLS(2); |
474 | OMAP_MCBSP_ST_CONTROLS(3); |
475 | |
476 | int omap_mcbsp_st_add_controls(struct snd_soc_pcm_runtime *rtd, int port_id) |
477 | { |
478 | struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); |
479 | struct omap_mcbsp *mcbsp = snd_soc_dai_get_drvdata(dai: cpu_dai); |
480 | |
481 | if (!mcbsp->st_data) { |
482 | dev_warn(mcbsp->dev, "No sidetone data for port\n" ); |
483 | return 0; |
484 | } |
485 | |
486 | switch (port_id) { |
487 | case 2: /* McBSP 2 */ |
488 | return snd_soc_add_dai_controls(dai: cpu_dai, |
489 | controls: omap_mcbsp2_st_controls, |
490 | ARRAY_SIZE(omap_mcbsp2_st_controls)); |
491 | case 3: /* McBSP 3 */ |
492 | return snd_soc_add_dai_controls(dai: cpu_dai, |
493 | controls: omap_mcbsp3_st_controls, |
494 | ARRAY_SIZE(omap_mcbsp3_st_controls)); |
495 | default: |
496 | dev_err(mcbsp->dev, "Port %d not supported\n" , port_id); |
497 | break; |
498 | } |
499 | |
500 | return -EINVAL; |
501 | } |
502 | EXPORT_SYMBOL_GPL(omap_mcbsp_st_add_controls); |
503 | |