• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: GPL-2.0
2 // Support for audio capture for tm5600/6000/6010
3 // Copyright (c) 2007-2008 Mauro Carvalho Chehab <mchehab@kernel.org>
4 //
5 // Based on cx88-alsa.c
6 
7 #include <linux/module.h>
8 #include <linux/init.h>
9 #include <linux/device.h>
10 #include <linux/interrupt.h>
11 #include <linux/usb.h>
12 #include <linux/slab.h>
13 
14 #include <linux/delay.h>
15 #include <sound/core.h>
16 #include <sound/pcm.h>
17 #include <sound/pcm_params.h>
18 #include <sound/control.h>
19 #include <sound/initval.h>
20 
21 
22 #include "tm6000.h"
23 #include "tm6000-regs.h"
24 
25 #undef dprintk
26 
27 #define dprintk(level, fmt, arg...) do {				   \
28 	if (debug >= level)						   \
29 		printk(KERN_INFO "%s/1: " fmt, chip->core->name , ## arg); \
30 	} while (0)
31 
32 /****************************************************************************
33 			Module global static vars
34  ****************************************************************************/
35 
36 static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
37 
38 static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
39 
40 module_param_array(enable, bool, NULL, 0444);
41 MODULE_PARM_DESC(enable, "Enable tm6000x soundcard. default enabled.");
42 
43 module_param_array(index, int, NULL, 0444);
44 MODULE_PARM_DESC(index, "Index value for tm6000x capture interface(s).");
45 
46 
47 /****************************************************************************
48 				Module macros
49  ****************************************************************************/
50 
51 MODULE_DESCRIPTION("ALSA driver module for tm5600/tm6000/tm6010 based TV cards");
52 MODULE_AUTHOR("Mauro Carvalho Chehab");
53 MODULE_LICENSE("GPL v2");
54 MODULE_SUPPORTED_DEVICE("{{Trident,tm5600},{{Trident,tm6000},{{Trident,tm6010}");
55 static unsigned int debug;
56 module_param(debug, int, 0644);
57 MODULE_PARM_DESC(debug, "enable debug messages");
58 
59 /****************************************************************************
60 			Module specific functions
61  ****************************************************************************/
62 
63 /*
64  * BOARD Specific: Sets audio DMA
65  */
66 
_tm6000_start_audio_dma(struct snd_tm6000_card * chip)67 static int _tm6000_start_audio_dma(struct snd_tm6000_card *chip)
68 {
69 	struct tm6000_core *core = chip->core;
70 
71 	dprintk(1, "Starting audio DMA\n");
72 
73 	/* Enables audio */
74 	tm6000_set_reg_mask(core, TM6010_REQ07_RCC_ACTIVE_IF, 0x40, 0x40);
75 
76 	tm6000_set_audio_bitrate(core, 48000);
77 
78 	return 0;
79 }
80 
81 /*
82  * BOARD Specific: Resets audio DMA
83  */
_tm6000_stop_audio_dma(struct snd_tm6000_card * chip)84 static int _tm6000_stop_audio_dma(struct snd_tm6000_card *chip)
85 {
86 	struct tm6000_core *core = chip->core;
87 
88 	dprintk(1, "Stopping audio DMA\n");
89 
90 	/* Disables audio */
91 	tm6000_set_reg_mask(core, TM6010_REQ07_RCC_ACTIVE_IF, 0x00, 0x40);
92 
93 	return 0;
94 }
95 
96 /****************************************************************************
97 				ALSA PCM Interface
98  ****************************************************************************/
99 
100 /*
101  * Digital hardware definition
102  */
103 #define DEFAULT_FIFO_SIZE	4096
104 
105 static const struct snd_pcm_hardware snd_tm6000_digital_hw = {
106 	.info = SNDRV_PCM_INFO_BATCH |
107 		SNDRV_PCM_INFO_MMAP |
108 		SNDRV_PCM_INFO_INTERLEAVED |
109 		SNDRV_PCM_INFO_BLOCK_TRANSFER |
110 		SNDRV_PCM_INFO_MMAP_VALID,
111 	.formats = SNDRV_PCM_FMTBIT_S16_LE,
112 
113 	.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT,
114 	.rate_min = 48000,
115 	.rate_max = 48000,
116 	.channels_min = 2,
117 	.channels_max = 2,
118 	.period_bytes_min = 64,
119 	.period_bytes_max = 12544,
120 	.periods_min = 2,
121 	.periods_max = 98,
122 	.buffer_bytes_max = 62720 * 8,
123 };
124 
125 /*
126  * audio pcm capture open callback
127  */
snd_tm6000_pcm_open(struct snd_pcm_substream * substream)128 static int snd_tm6000_pcm_open(struct snd_pcm_substream *substream)
129 {
130 	struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
131 	struct snd_pcm_runtime *runtime = substream->runtime;
132 	int err;
133 
134 	err = snd_pcm_hw_constraint_pow2(runtime, 0,
135 					 SNDRV_PCM_HW_PARAM_PERIODS);
136 	if (err < 0)
137 		goto _error;
138 
139 	chip->substream = substream;
140 
141 	runtime->hw = snd_tm6000_digital_hw;
142 	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
143 
144 	return 0;
145 _error:
146 	dprintk(1, "Error opening PCM!\n");
147 	return err;
148 }
149 
150 /*
151  * audio close callback
152  */
snd_tm6000_close(struct snd_pcm_substream * substream)153 static int snd_tm6000_close(struct snd_pcm_substream *substream)
154 {
155 	struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
156 	struct tm6000_core *core = chip->core;
157 
158 	if (atomic_read(&core->stream_started) > 0) {
159 		atomic_set(&core->stream_started, 0);
160 		schedule_work(&core->wq_trigger);
161 	}
162 
163 	return 0;
164 }
165 
tm6000_fillbuf(struct tm6000_core * core,char * buf,int size)166 static int tm6000_fillbuf(struct tm6000_core *core, char *buf, int size)
167 {
168 	struct snd_tm6000_card *chip = core->adev;
169 	struct snd_pcm_substream *substream = chip->substream;
170 	struct snd_pcm_runtime *runtime;
171 	int period_elapsed = 0;
172 	unsigned int stride, buf_pos;
173 	int length;
174 
175 	if (atomic_read(&core->stream_started) == 0)
176 		return 0;
177 
178 	if (!size || !substream) {
179 		dprintk(1, "substream was NULL\n");
180 		return -EINVAL;
181 	}
182 
183 	runtime = substream->runtime;
184 	if (!runtime || !runtime->dma_area) {
185 		dprintk(1, "runtime was NULL\n");
186 		return -EINVAL;
187 	}
188 
189 	buf_pos = chip->buf_pos;
190 	stride = runtime->frame_bits >> 3;
191 
192 	if (stride == 0) {
193 		dprintk(1, "stride is zero\n");
194 		return -EINVAL;
195 	}
196 
197 	length = size / stride;
198 	if (length == 0) {
199 		dprintk(1, "%s: length was zero\n", __func__);
200 		return -EINVAL;
201 	}
202 
203 	dprintk(1, "Copying %d bytes at %p[%d] - buf size=%d x %d\n", size,
204 		runtime->dma_area, buf_pos,
205 		(unsigned int)runtime->buffer_size, stride);
206 
207 	if (buf_pos + length >= runtime->buffer_size) {
208 		unsigned int cnt = runtime->buffer_size - buf_pos;
209 		memcpy(runtime->dma_area + buf_pos * stride, buf, cnt * stride);
210 		memcpy(runtime->dma_area, buf + cnt * stride,
211 			length * stride - cnt * stride);
212 	} else
213 		memcpy(runtime->dma_area + buf_pos * stride, buf,
214 			length * stride);
215 
216 	snd_pcm_stream_lock(substream);
217 
218 	chip->buf_pos += length;
219 	if (chip->buf_pos >= runtime->buffer_size)
220 		chip->buf_pos -= runtime->buffer_size;
221 
222 	chip->period_pos += length;
223 	if (chip->period_pos >= runtime->period_size) {
224 		chip->period_pos -= runtime->period_size;
225 		period_elapsed = 1;
226 	}
227 
228 	snd_pcm_stream_unlock(substream);
229 
230 	if (period_elapsed)
231 		snd_pcm_period_elapsed(substream);
232 
233 	return 0;
234 }
235 
236 /*
237  * prepare callback
238  */
snd_tm6000_prepare(struct snd_pcm_substream * substream)239 static int snd_tm6000_prepare(struct snd_pcm_substream *substream)
240 {
241 	struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
242 
243 	chip->buf_pos = 0;
244 	chip->period_pos = 0;
245 
246 	return 0;
247 }
248 
249 
250 /*
251  * trigger callback
252  */
audio_trigger(struct work_struct * work)253 static void audio_trigger(struct work_struct *work)
254 {
255 	struct tm6000_core *core = container_of(work, struct tm6000_core,
256 						wq_trigger);
257 	struct snd_tm6000_card *chip = core->adev;
258 
259 	if (atomic_read(&core->stream_started)) {
260 		dprintk(1, "starting capture");
261 		_tm6000_start_audio_dma(chip);
262 	} else {
263 		dprintk(1, "stopping capture");
264 		_tm6000_stop_audio_dma(chip);
265 	}
266 }
267 
snd_tm6000_card_trigger(struct snd_pcm_substream * substream,int cmd)268 static int snd_tm6000_card_trigger(struct snd_pcm_substream *substream, int cmd)
269 {
270 	struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
271 	struct tm6000_core *core = chip->core;
272 	int err = 0;
273 
274 	switch (cmd) {
275 	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
276 	case SNDRV_PCM_TRIGGER_RESUME:
277 	case SNDRV_PCM_TRIGGER_START:
278 		atomic_set(&core->stream_started, 1);
279 		break;
280 	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
281 	case SNDRV_PCM_TRIGGER_SUSPEND:
282 	case SNDRV_PCM_TRIGGER_STOP:
283 		atomic_set(&core->stream_started, 0);
284 		break;
285 	default:
286 		err = -EINVAL;
287 		break;
288 	}
289 	schedule_work(&core->wq_trigger);
290 
291 	return err;
292 }
293 /*
294  * pointer callback
295  */
snd_tm6000_pointer(struct snd_pcm_substream * substream)296 static snd_pcm_uframes_t snd_tm6000_pointer(struct snd_pcm_substream *substream)
297 {
298 	struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
299 
300 	return chip->buf_pos;
301 }
302 
303 /*
304  * operators
305  */
306 static const struct snd_pcm_ops snd_tm6000_pcm_ops = {
307 	.open = snd_tm6000_pcm_open,
308 	.close = snd_tm6000_close,
309 	.prepare = snd_tm6000_prepare,
310 	.trigger = snd_tm6000_card_trigger,
311 	.pointer = snd_tm6000_pointer,
312 };
313 
314 /*
315  * create a PCM device
316  */
317 
318 /* FIXME: Control interface - How to control volume/mute? */
319 
320 /****************************************************************************
321 			Basic Flow for Sound Devices
322  ****************************************************************************/
323 
324 /*
325  * Alsa Constructor - Component probe
326  */
tm6000_audio_init(struct tm6000_core * dev)327 static int tm6000_audio_init(struct tm6000_core *dev)
328 {
329 	struct snd_card		*card;
330 	struct snd_tm6000_card	*chip;
331 	int			rc;
332 	static int		devnr;
333 	char			component[14];
334 	struct snd_pcm		*pcm;
335 
336 	if (!dev)
337 		return 0;
338 
339 	if (devnr >= SNDRV_CARDS)
340 		return -ENODEV;
341 
342 	if (!enable[devnr])
343 		return -ENOENT;
344 
345 	rc = snd_card_new(&dev->udev->dev, index[devnr], "tm6000",
346 			  THIS_MODULE, 0, &card);
347 	if (rc < 0) {
348 		snd_printk(KERN_ERR "cannot create card instance %d\n", devnr);
349 		return rc;
350 	}
351 	strscpy(card->driver, "tm6000-alsa", sizeof(card->driver));
352 	strscpy(card->shortname, "TM5600/60x0", sizeof(card->shortname));
353 	sprintf(card->longname, "TM5600/60x0 Audio at bus %d device %d",
354 		dev->udev->bus->busnum, dev->udev->devnum);
355 
356 	sprintf(component, "USB%04x:%04x",
357 		le16_to_cpu(dev->udev->descriptor.idVendor),
358 		le16_to_cpu(dev->udev->descriptor.idProduct));
359 	snd_component_add(card, component);
360 
361 	chip = kzalloc(sizeof(struct snd_tm6000_card), GFP_KERNEL);
362 	if (!chip) {
363 		rc = -ENOMEM;
364 		goto error;
365 	}
366 
367 	chip->core = dev;
368 	chip->card = card;
369 	dev->adev = chip;
370 	spin_lock_init(&chip->reg_lock);
371 
372 	rc = snd_pcm_new(card, "TM6000 Audio", 0, 0, 1, &pcm);
373 	if (rc < 0)
374 		goto error_chip;
375 
376 	pcm->info_flags = 0;
377 	pcm->private_data = chip;
378 	strscpy(pcm->name, "Trident TM5600/60x0", sizeof(pcm->name));
379 
380 	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_tm6000_pcm_ops);
381 	snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0);
382 
383 	INIT_WORK(&dev->wq_trigger, audio_trigger);
384 	rc = snd_card_register(card);
385 	if (rc < 0)
386 		goto error_chip;
387 
388 	dprintk(1, "Registered audio driver for %s\n", card->longname);
389 
390 	return 0;
391 
392 error_chip:
393 	kfree(chip);
394 	dev->adev = NULL;
395 error:
396 	snd_card_free(card);
397 	return rc;
398 }
399 
tm6000_audio_fini(struct tm6000_core * dev)400 static int tm6000_audio_fini(struct tm6000_core *dev)
401 {
402 	struct snd_tm6000_card *chip;
403 
404 	if (!dev)
405 		return 0;
406 	chip = dev->adev;
407 
408 	if (!chip)
409 		return 0;
410 
411 	if (!chip->card)
412 		return 0;
413 
414 	snd_card_free(chip->card);
415 	chip->card = NULL;
416 	kfree(chip);
417 	dev->adev = NULL;
418 
419 	return 0;
420 }
421 
422 static struct tm6000_ops audio_ops = {
423 	.type	= TM6000_AUDIO,
424 	.name	= "TM6000 Audio Extension",
425 	.init	= tm6000_audio_init,
426 	.fini	= tm6000_audio_fini,
427 	.fillbuf = tm6000_fillbuf,
428 };
429 
tm6000_alsa_register(void)430 static int __init tm6000_alsa_register(void)
431 {
432 	return tm6000_register_extension(&audio_ops);
433 }
434 
tm6000_alsa_unregister(void)435 static void __exit tm6000_alsa_unregister(void)
436 {
437 	tm6000_unregister_extension(&audio_ops);
438 }
439 
440 module_init(tm6000_alsa_register);
441 module_exit(tm6000_alsa_unregister);
442