1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * |
4 | * Support for CX23885 analog audio capture |
5 | * |
6 | * (c) 2008 Mijhail Moreyra <mijhail.moreyra@gmail.com> |
7 | * Adapted from cx88-alsa.c |
8 | * (c) 2009 Steven Toth <stoth@kernellabs.com> |
9 | */ |
10 | |
11 | #include "cx23885.h" |
12 | #include "cx23885-reg.h" |
13 | |
14 | #include <linux/module.h> |
15 | #include <linux/init.h> |
16 | #include <linux/device.h> |
17 | #include <linux/interrupt.h> |
18 | #include <linux/vmalloc.h> |
19 | #include <linux/dma-mapping.h> |
20 | #include <linux/pci.h> |
21 | |
22 | #include <asm/delay.h> |
23 | |
24 | #include <sound/core.h> |
25 | #include <sound/pcm.h> |
26 | #include <sound/pcm_params.h> |
27 | #include <sound/control.h> |
28 | #include <sound/initval.h> |
29 | |
30 | #include <sound/tlv.h> |
31 | |
32 | #define AUDIO_SRAM_CHANNEL SRAM_CH07 |
33 | |
34 | #define dprintk(level, fmt, arg...) do { \ |
35 | if (audio_debug + 1 > level) \ |
36 | printk(KERN_DEBUG pr_fmt("%s: alsa: " fmt), \ |
37 | chip->dev->name, ##arg); \ |
38 | } while(0) |
39 | |
40 | /**************************************************************************** |
41 | Module global static vars |
42 | ****************************************************************************/ |
43 | |
44 | static unsigned int disable_analog_audio; |
45 | module_param(disable_analog_audio, int, 0644); |
46 | MODULE_PARM_DESC(disable_analog_audio, "disable analog audio ALSA driver" ); |
47 | |
48 | static unsigned int audio_debug; |
49 | module_param(audio_debug, int, 0644); |
50 | MODULE_PARM_DESC(audio_debug, "enable debug messages [analog audio]" ); |
51 | |
52 | /**************************************************************************** |
53 | Board specific functions |
54 | ****************************************************************************/ |
55 | |
56 | /* Constants taken from cx88-reg.h */ |
57 | #define AUD_INT_DN_RISCI1 (1 << 0) |
58 | #define AUD_INT_UP_RISCI1 (1 << 1) |
59 | #define AUD_INT_RDS_DN_RISCI1 (1 << 2) |
60 | #define AUD_INT_DN_RISCI2 (1 << 4) /* yes, 3 is skipped */ |
61 | #define AUD_INT_UP_RISCI2 (1 << 5) |
62 | #define AUD_INT_RDS_DN_RISCI2 (1 << 6) |
63 | #define AUD_INT_DN_SYNC (1 << 12) |
64 | #define AUD_INT_UP_SYNC (1 << 13) |
65 | #define AUD_INT_RDS_DN_SYNC (1 << 14) |
66 | #define AUD_INT_OPC_ERR (1 << 16) |
67 | #define AUD_INT_BER_IRQ (1 << 20) |
68 | #define AUD_INT_MCHG_IRQ (1 << 21) |
69 | #define GP_COUNT_CONTROL_RESET 0x3 |
70 | |
71 | static int cx23885_alsa_dma_init(struct cx23885_audio_dev *chip, |
72 | unsigned long nr_pages) |
73 | { |
74 | struct cx23885_audio_buffer *buf = chip->buf; |
75 | struct page *pg; |
76 | int i; |
77 | |
78 | buf->vaddr = vmalloc_32(size: nr_pages << PAGE_SHIFT); |
79 | if (NULL == buf->vaddr) { |
80 | dprintk(1, "vmalloc_32(%lu pages) failed\n" , nr_pages); |
81 | return -ENOMEM; |
82 | } |
83 | |
84 | dprintk(1, "vmalloc is at addr %p, size=%lu\n" , |
85 | buf->vaddr, nr_pages << PAGE_SHIFT); |
86 | |
87 | memset(buf->vaddr, 0, nr_pages << PAGE_SHIFT); |
88 | buf->nr_pages = nr_pages; |
89 | |
90 | buf->sglist = vzalloc(array_size(sizeof(*buf->sglist), buf->nr_pages)); |
91 | if (NULL == buf->sglist) |
92 | goto vzalloc_err; |
93 | |
94 | sg_init_table(buf->sglist, buf->nr_pages); |
95 | for (i = 0; i < buf->nr_pages; i++) { |
96 | pg = vmalloc_to_page(addr: buf->vaddr + i * PAGE_SIZE); |
97 | if (NULL == pg) |
98 | goto vmalloc_to_page_err; |
99 | sg_set_page(sg: &buf->sglist[i], page: pg, PAGE_SIZE, offset: 0); |
100 | } |
101 | return 0; |
102 | |
103 | vmalloc_to_page_err: |
104 | vfree(addr: buf->sglist); |
105 | buf->sglist = NULL; |
106 | vzalloc_err: |
107 | vfree(addr: buf->vaddr); |
108 | buf->vaddr = NULL; |
109 | return -ENOMEM; |
110 | } |
111 | |
112 | static int cx23885_alsa_dma_map(struct cx23885_audio_dev *dev) |
113 | { |
114 | struct cx23885_audio_buffer *buf = dev->buf; |
115 | |
116 | buf->sglen = dma_map_sg(&dev->pci->dev, buf->sglist, |
117 | buf->nr_pages, DMA_FROM_DEVICE); |
118 | |
119 | if (0 == buf->sglen) { |
120 | pr_warn("%s: cx23885_alsa_map_sg failed\n" , __func__); |
121 | return -ENOMEM; |
122 | } |
123 | return 0; |
124 | } |
125 | |
126 | static int cx23885_alsa_dma_unmap(struct cx23885_audio_dev *dev) |
127 | { |
128 | struct cx23885_audio_buffer *buf = dev->buf; |
129 | |
130 | if (!buf->sglen) |
131 | return 0; |
132 | |
133 | dma_unmap_sg(&dev->pci->dev, buf->sglist, buf->nr_pages, DMA_FROM_DEVICE); |
134 | buf->sglen = 0; |
135 | return 0; |
136 | } |
137 | |
138 | static int cx23885_alsa_dma_free(struct cx23885_audio_buffer *buf) |
139 | { |
140 | vfree(addr: buf->sglist); |
141 | buf->sglist = NULL; |
142 | vfree(addr: buf->vaddr); |
143 | buf->vaddr = NULL; |
144 | return 0; |
145 | } |
146 | |
147 | /* |
148 | * BOARD Specific: Sets audio DMA |
149 | */ |
150 | |
151 | static int cx23885_start_audio_dma(struct cx23885_audio_dev *chip) |
152 | { |
153 | struct cx23885_audio_buffer *buf = chip->buf; |
154 | struct cx23885_dev *dev = chip->dev; |
155 | struct sram_channel *audio_ch = |
156 | &dev->sram_channels[AUDIO_SRAM_CHANNEL]; |
157 | |
158 | dprintk(1, "%s()\n" , __func__); |
159 | |
160 | /* Make sure RISC/FIFO are off before changing FIFO/RISC settings */ |
161 | cx_clear(AUD_INT_DMA_CTL, 0x11); |
162 | |
163 | /* setup fifo + format - out channel */ |
164 | cx23885_sram_channel_setup(dev: chip->dev, ch: audio_ch, bpl: buf->bpl, |
165 | risc: buf->risc.dma); |
166 | |
167 | /* sets bpl size */ |
168 | cx_write(AUD_INT_A_LNGTH, buf->bpl); |
169 | |
170 | /* This is required to get good audio (1 seems to be ok) */ |
171 | cx_write(AUD_INT_A_MODE, 1); |
172 | |
173 | /* reset counter */ |
174 | cx_write(AUD_INT_A_GPCNT_CTL, GP_COUNT_CONTROL_RESET); |
175 | atomic_set(v: &chip->count, i: 0); |
176 | |
177 | dprintk(1, "Start audio DMA, %d B/line, %d lines/FIFO, %d periods, %d byte buffer\n" , |
178 | buf->bpl, cx_read(audio_ch->cmds_start+12)>>1, |
179 | chip->num_periods, buf->bpl * chip->num_periods); |
180 | |
181 | /* Enables corresponding bits at AUD_INT_STAT */ |
182 | cx_write(AUDIO_INT_INT_MSK, AUD_INT_OPC_ERR | AUD_INT_DN_SYNC | |
183 | AUD_INT_DN_RISCI1); |
184 | |
185 | /* Clean any pending interrupt bits already set */ |
186 | cx_write(AUDIO_INT_INT_STAT, ~0); |
187 | |
188 | /* enable audio irqs */ |
189 | cx_set(PCI_INT_MSK, chip->dev->pci_irqmask | PCI_MSK_AUD_INT); |
190 | |
191 | /* start dma */ |
192 | cx_set(DEV_CNTRL2, (1<<5)); /* Enables Risc Processor */ |
193 | cx_set(AUD_INT_DMA_CTL, 0x11); /* audio downstream FIFO and |
194 | RISC enable */ |
195 | if (audio_debug) |
196 | cx23885_sram_channel_dump(dev: chip->dev, ch: audio_ch); |
197 | |
198 | return 0; |
199 | } |
200 | |
201 | /* |
202 | * BOARD Specific: Resets audio DMA |
203 | */ |
204 | static int cx23885_stop_audio_dma(struct cx23885_audio_dev *chip) |
205 | { |
206 | struct cx23885_dev *dev = chip->dev; |
207 | dprintk(1, "Stopping audio DMA\n" ); |
208 | |
209 | /* stop dma */ |
210 | cx_clear(AUD_INT_DMA_CTL, 0x11); |
211 | |
212 | /* disable irqs */ |
213 | cx_clear(PCI_INT_MSK, PCI_MSK_AUD_INT); |
214 | cx_clear(AUDIO_INT_INT_MSK, AUD_INT_OPC_ERR | AUD_INT_DN_SYNC | |
215 | AUD_INT_DN_RISCI1); |
216 | |
217 | if (audio_debug) |
218 | cx23885_sram_channel_dump(dev: chip->dev, |
219 | ch: &dev->sram_channels[AUDIO_SRAM_CHANNEL]); |
220 | |
221 | return 0; |
222 | } |
223 | |
224 | /* |
225 | * BOARD Specific: Handles audio IRQ |
226 | */ |
227 | int cx23885_audio_irq(struct cx23885_dev *dev, u32 status, u32 mask) |
228 | { |
229 | struct cx23885_audio_dev *chip = dev->audio_dev; |
230 | |
231 | if (0 == (status & mask)) |
232 | return 0; |
233 | |
234 | cx_write(AUDIO_INT_INT_STAT, status); |
235 | |
236 | /* risc op code error */ |
237 | if (status & AUD_INT_OPC_ERR) { |
238 | pr_warn("%s/1: Audio risc op code error\n" , |
239 | dev->name); |
240 | cx_clear(AUD_INT_DMA_CTL, 0x11); |
241 | cx23885_sram_channel_dump(dev, |
242 | ch: &dev->sram_channels[AUDIO_SRAM_CHANNEL]); |
243 | } |
244 | if (status & AUD_INT_DN_SYNC) { |
245 | dprintk(1, "Downstream sync error\n" ); |
246 | cx_write(AUD_INT_A_GPCNT_CTL, GP_COUNT_CONTROL_RESET); |
247 | return 1; |
248 | } |
249 | /* risc1 downstream */ |
250 | if (status & AUD_INT_DN_RISCI1) { |
251 | atomic_set(v: &chip->count, cx_read(AUD_INT_A_GPCNT)); |
252 | snd_pcm_period_elapsed(substream: chip->substream); |
253 | } |
254 | /* FIXME: Any other status should deserve a special handling? */ |
255 | |
256 | return 1; |
257 | } |
258 | |
259 | static int dsp_buffer_free(struct cx23885_audio_dev *chip) |
260 | { |
261 | struct cx23885_riscmem *risc; |
262 | |
263 | BUG_ON(!chip->dma_size); |
264 | |
265 | dprintk(2, "Freeing buffer\n" ); |
266 | cx23885_alsa_dma_unmap(dev: chip); |
267 | cx23885_alsa_dma_free(buf: chip->buf); |
268 | risc = &chip->buf->risc; |
269 | dma_free_coherent(dev: &chip->pci->dev, size: risc->size, cpu_addr: risc->cpu, dma_handle: risc->dma); |
270 | kfree(objp: chip->buf); |
271 | |
272 | chip->buf = NULL; |
273 | chip->dma_size = 0; |
274 | |
275 | return 0; |
276 | } |
277 | |
278 | /**************************************************************************** |
279 | ALSA PCM Interface |
280 | ****************************************************************************/ |
281 | |
282 | /* |
283 | * Digital hardware definition |
284 | */ |
285 | #define DEFAULT_FIFO_SIZE 4096 |
286 | |
287 | static const struct snd_pcm_hardware snd_cx23885_digital_hw = { |
288 | .info = SNDRV_PCM_INFO_MMAP | |
289 | SNDRV_PCM_INFO_INTERLEAVED | |
290 | SNDRV_PCM_INFO_BLOCK_TRANSFER | |
291 | SNDRV_PCM_INFO_MMAP_VALID, |
292 | .formats = SNDRV_PCM_FMTBIT_S16_LE, |
293 | |
294 | .rates = SNDRV_PCM_RATE_48000, |
295 | .rate_min = 48000, |
296 | .rate_max = 48000, |
297 | .channels_min = 2, |
298 | .channels_max = 2, |
299 | /* Analog audio output will be full of clicks and pops if there |
300 | are not exactly four lines in the SRAM FIFO buffer. */ |
301 | .period_bytes_min = DEFAULT_FIFO_SIZE/4, |
302 | .period_bytes_max = DEFAULT_FIFO_SIZE/4, |
303 | .periods_min = 1, |
304 | .periods_max = 1024, |
305 | .buffer_bytes_max = (1024*1024), |
306 | }; |
307 | |
308 | /* |
309 | * audio pcm capture open callback |
310 | */ |
311 | static int snd_cx23885_pcm_open(struct snd_pcm_substream *substream) |
312 | { |
313 | struct cx23885_audio_dev *chip = snd_pcm_substream_chip(substream); |
314 | struct snd_pcm_runtime *runtime = substream->runtime; |
315 | int err; |
316 | |
317 | if (!chip) { |
318 | pr_err("BUG: cx23885 can't find device struct. Can't proceed with open\n" ); |
319 | return -ENODEV; |
320 | } |
321 | |
322 | err = snd_pcm_hw_constraint_pow2(runtime, cond: 0, |
323 | SNDRV_PCM_HW_PARAM_PERIODS); |
324 | if (err < 0) |
325 | goto _error; |
326 | |
327 | chip->substream = substream; |
328 | |
329 | runtime->hw = snd_cx23885_digital_hw; |
330 | |
331 | if (chip->dev->sram_channels[AUDIO_SRAM_CHANNEL].fifo_size != |
332 | DEFAULT_FIFO_SIZE) { |
333 | unsigned int bpl = chip->dev-> |
334 | sram_channels[AUDIO_SRAM_CHANNEL].fifo_size / 4; |
335 | bpl &= ~7; /* must be multiple of 8 */ |
336 | runtime->hw.period_bytes_min = bpl; |
337 | runtime->hw.period_bytes_max = bpl; |
338 | } |
339 | |
340 | return 0; |
341 | _error: |
342 | dprintk(1, "Error opening PCM!\n" ); |
343 | return err; |
344 | } |
345 | |
346 | /* |
347 | * audio close callback |
348 | */ |
349 | static int snd_cx23885_close(struct snd_pcm_substream *substream) |
350 | { |
351 | return 0; |
352 | } |
353 | |
354 | |
355 | /* |
356 | * hw_params callback |
357 | */ |
358 | static int snd_cx23885_hw_params(struct snd_pcm_substream *substream, |
359 | struct snd_pcm_hw_params *hw_params) |
360 | { |
361 | struct cx23885_audio_dev *chip = snd_pcm_substream_chip(substream); |
362 | struct cx23885_audio_buffer *buf; |
363 | int ret; |
364 | |
365 | if (substream->runtime->dma_area) { |
366 | dsp_buffer_free(chip); |
367 | substream->runtime->dma_area = NULL; |
368 | } |
369 | |
370 | chip->period_size = params_period_bytes(p: hw_params); |
371 | chip->num_periods = params_periods(p: hw_params); |
372 | chip->dma_size = chip->period_size * params_periods(p: hw_params); |
373 | |
374 | BUG_ON(!chip->dma_size); |
375 | BUG_ON(chip->num_periods & (chip->num_periods-1)); |
376 | |
377 | buf = kzalloc(size: sizeof(*buf), GFP_KERNEL); |
378 | if (NULL == buf) |
379 | return -ENOMEM; |
380 | |
381 | buf->bpl = chip->period_size; |
382 | chip->buf = buf; |
383 | |
384 | ret = cx23885_alsa_dma_init(chip, |
385 | nr_pages: (PAGE_ALIGN(chip->dma_size) >> PAGE_SHIFT)); |
386 | if (ret < 0) |
387 | goto error; |
388 | |
389 | ret = cx23885_alsa_dma_map(dev: chip); |
390 | if (ret < 0) |
391 | goto error; |
392 | |
393 | ret = cx23885_risc_databuffer(pci: chip->pci, risc: &buf->risc, sglist: buf->sglist, |
394 | bpl: chip->period_size, lines: chip->num_periods, lpi: 1); |
395 | if (ret < 0) |
396 | goto error; |
397 | |
398 | /* Loop back to start of program */ |
399 | buf->risc.jmp[0] = cpu_to_le32(RISC_JUMP|RISC_IRQ1|RISC_CNT_INC); |
400 | buf->risc.jmp[1] = cpu_to_le32(buf->risc.dma); |
401 | buf->risc.jmp[2] = cpu_to_le32(0); /* bits 63-32 */ |
402 | |
403 | substream->runtime->dma_area = chip->buf->vaddr; |
404 | substream->runtime->dma_bytes = chip->dma_size; |
405 | substream->runtime->dma_addr = 0; |
406 | |
407 | return 0; |
408 | |
409 | error: |
410 | kfree(objp: buf); |
411 | chip->buf = NULL; |
412 | return ret; |
413 | } |
414 | |
415 | /* |
416 | * hw free callback |
417 | */ |
418 | static int snd_cx23885_hw_free(struct snd_pcm_substream *substream) |
419 | { |
420 | |
421 | struct cx23885_audio_dev *chip = snd_pcm_substream_chip(substream); |
422 | |
423 | if (substream->runtime->dma_area) { |
424 | dsp_buffer_free(chip); |
425 | substream->runtime->dma_area = NULL; |
426 | } |
427 | |
428 | return 0; |
429 | } |
430 | |
431 | /* |
432 | * prepare callback |
433 | */ |
434 | static int snd_cx23885_prepare(struct snd_pcm_substream *substream) |
435 | { |
436 | return 0; |
437 | } |
438 | |
439 | /* |
440 | * trigger callback |
441 | */ |
442 | static int snd_cx23885_card_trigger(struct snd_pcm_substream *substream, |
443 | int cmd) |
444 | { |
445 | struct cx23885_audio_dev *chip = snd_pcm_substream_chip(substream); |
446 | int err; |
447 | |
448 | /* Local interrupts are already disabled by ALSA */ |
449 | spin_lock(lock: &chip->lock); |
450 | |
451 | switch (cmd) { |
452 | case SNDRV_PCM_TRIGGER_START: |
453 | err = cx23885_start_audio_dma(chip); |
454 | break; |
455 | case SNDRV_PCM_TRIGGER_STOP: |
456 | err = cx23885_stop_audio_dma(chip); |
457 | break; |
458 | default: |
459 | err = -EINVAL; |
460 | break; |
461 | } |
462 | |
463 | spin_unlock(lock: &chip->lock); |
464 | |
465 | return err; |
466 | } |
467 | |
468 | /* |
469 | * pointer callback |
470 | */ |
471 | static snd_pcm_uframes_t snd_cx23885_pointer( |
472 | struct snd_pcm_substream *substream) |
473 | { |
474 | struct cx23885_audio_dev *chip = snd_pcm_substream_chip(substream); |
475 | struct snd_pcm_runtime *runtime = substream->runtime; |
476 | u16 count; |
477 | |
478 | count = atomic_read(v: &chip->count); |
479 | |
480 | return runtime->period_size * (count & (runtime->periods-1)); |
481 | } |
482 | |
483 | /* |
484 | * page callback (needed for mmap) |
485 | */ |
486 | static struct page *snd_cx23885_page(struct snd_pcm_substream *substream, |
487 | unsigned long offset) |
488 | { |
489 | void *pageptr = substream->runtime->dma_area + offset; |
490 | return vmalloc_to_page(addr: pageptr); |
491 | } |
492 | |
493 | /* |
494 | * operators |
495 | */ |
496 | static const struct snd_pcm_ops snd_cx23885_pcm_ops = { |
497 | .open = snd_cx23885_pcm_open, |
498 | .close = snd_cx23885_close, |
499 | .hw_params = snd_cx23885_hw_params, |
500 | .hw_free = snd_cx23885_hw_free, |
501 | .prepare = snd_cx23885_prepare, |
502 | .trigger = snd_cx23885_card_trigger, |
503 | .pointer = snd_cx23885_pointer, |
504 | .page = snd_cx23885_page, |
505 | }; |
506 | |
507 | /* |
508 | * create a PCM device |
509 | */ |
510 | static int snd_cx23885_pcm(struct cx23885_audio_dev *chip, int device, |
511 | char *name) |
512 | { |
513 | int err; |
514 | struct snd_pcm *pcm; |
515 | |
516 | err = snd_pcm_new(card: chip->card, id: name, device, playback_count: 0, capture_count: 1, rpcm: &pcm); |
517 | if (err < 0) |
518 | return err; |
519 | pcm->private_data = chip; |
520 | strscpy(pcm->name, name, sizeof(pcm->name)); |
521 | snd_pcm_set_ops(pcm, direction: SNDRV_PCM_STREAM_CAPTURE, ops: &snd_cx23885_pcm_ops); |
522 | |
523 | return 0; |
524 | } |
525 | |
526 | /**************************************************************************** |
527 | Basic Flow for Sound Devices |
528 | ****************************************************************************/ |
529 | |
530 | /* |
531 | * Alsa Constructor - Component probe |
532 | */ |
533 | |
534 | struct cx23885_audio_dev *cx23885_audio_register(struct cx23885_dev *dev) |
535 | { |
536 | struct snd_card *card; |
537 | struct cx23885_audio_dev *chip; |
538 | int err; |
539 | |
540 | if (disable_analog_audio) |
541 | return NULL; |
542 | |
543 | if (dev->sram_channels[AUDIO_SRAM_CHANNEL].cmds_start == 0) { |
544 | pr_warn("%s(): Missing SRAM channel configuration for analog TV Audio\n" , |
545 | __func__); |
546 | return NULL; |
547 | } |
548 | |
549 | err = snd_card_new(parent: &dev->pci->dev, |
550 | SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1, |
551 | THIS_MODULE, extra_size: sizeof(struct cx23885_audio_dev), card_ret: &card); |
552 | if (err < 0) |
553 | goto error_msg; |
554 | |
555 | chip = (struct cx23885_audio_dev *) card->private_data; |
556 | chip->dev = dev; |
557 | chip->pci = dev->pci; |
558 | chip->card = card; |
559 | spin_lock_init(&chip->lock); |
560 | |
561 | err = snd_cx23885_pcm(chip, device: 0, name: "CX23885 Digital" ); |
562 | if (err < 0) |
563 | goto error; |
564 | |
565 | strscpy(card->driver, "CX23885" , sizeof(card->driver)); |
566 | sprintf(buf: card->shortname, fmt: "Conexant CX23885" ); |
567 | sprintf(buf: card->longname, fmt: "%s at %s" , card->shortname, dev->name); |
568 | |
569 | err = snd_card_register(card); |
570 | if (err < 0) |
571 | goto error; |
572 | |
573 | dprintk(0, "registered ALSA audio device\n" ); |
574 | |
575 | return chip; |
576 | |
577 | error: |
578 | snd_card_free(card); |
579 | error_msg: |
580 | pr_err("%s(): Failed to register analog audio adapter\n" , |
581 | __func__); |
582 | |
583 | return NULL; |
584 | } |
585 | |
586 | /* |
587 | * ALSA destructor |
588 | */ |
589 | void cx23885_audio_unregister(struct cx23885_dev *dev) |
590 | { |
591 | struct cx23885_audio_dev *chip = dev->audio_dev; |
592 | |
593 | snd_card_free(card: chip->card); |
594 | } |
595 | |