163ddf68dSFederico Simoncelli /*
263ddf68dSFederico Simoncelli  * Copyright (c) 2013 Federico Simoncelli
363ddf68dSFederico Simoncelli  * All rights reserved.
463ddf68dSFederico Simoncelli  *
563ddf68dSFederico Simoncelli  * Redistribution and use in source and binary forms, with or without
663ddf68dSFederico Simoncelli  * modification, are permitted provided that the following conditions
763ddf68dSFederico Simoncelli  * are met:
863ddf68dSFederico Simoncelli  * 1. Redistributions of source code must retain the above copyright
963ddf68dSFederico Simoncelli  *    notice, this list of conditions, and the following disclaimer,
1063ddf68dSFederico Simoncelli  *    without modification.
1163ddf68dSFederico Simoncelli  * 2. The name of the author may not be used to endorse or promote products
1263ddf68dSFederico Simoncelli  *    derived from this software without specific prior written permission.
1363ddf68dSFederico Simoncelli  *
1463ddf68dSFederico Simoncelli  * Alternatively, this software may be distributed under the terms of the
1563ddf68dSFederico Simoncelli  * GNU General Public License ("GPL").
165ae1e2b2SLubomir Rintel  *
175ae1e2b2SLubomir Rintel  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
185ae1e2b2SLubomir Rintel  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
195ae1e2b2SLubomir Rintel  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
205ae1e2b2SLubomir Rintel  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
215ae1e2b2SLubomir Rintel  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
225ae1e2b2SLubomir Rintel  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
235ae1e2b2SLubomir Rintel  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
245ae1e2b2SLubomir Rintel  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
255ae1e2b2SLubomir Rintel  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
265ae1e2b2SLubomir Rintel  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
275ae1e2b2SLubomir Rintel  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
285ae1e2b2SLubomir Rintel  */
295ae1e2b2SLubomir Rintel /*
305ae1e2b2SLubomir Rintel  * Fushicai USBTV007 Audio-Video Grabber Driver
315ae1e2b2SLubomir Rintel  *
325ae1e2b2SLubomir Rintel  * Product web site:
335ae1e2b2SLubomir Rintel  * http://www.fushicai.com/products_detail/&productId=d05449ee-b690-42f9-a661-aa7353894bed.html
345ae1e2b2SLubomir Rintel  *
355ae1e2b2SLubomir Rintel  * No physical hardware was harmed running Windows during the
365ae1e2b2SLubomir Rintel  * reverse-engineering activity
3763ddf68dSFederico Simoncelli  */
3863ddf68dSFederico Simoncelli 
3963ddf68dSFederico Simoncelli #include <sound/core.h>
4063ddf68dSFederico Simoncelli #include <sound/initval.h>
4163ddf68dSFederico Simoncelli #include <sound/ac97_codec.h>
4263ddf68dSFederico Simoncelli #include <sound/pcm_params.h>
4363ddf68dSFederico Simoncelli 
4463ddf68dSFederico Simoncelli #include "usbtv.h"
4563ddf68dSFederico Simoncelli 
4638c4f03aSBhumika Goyal static const struct snd_pcm_hardware snd_usbtv_digital_hw = {
4763ddf68dSFederico Simoncelli 	.info = SNDRV_PCM_INFO_BATCH |
4863ddf68dSFederico Simoncelli 		SNDRV_PCM_INFO_MMAP |
4963ddf68dSFederico Simoncelli 		SNDRV_PCM_INFO_INTERLEAVED |
5063ddf68dSFederico Simoncelli 		SNDRV_PCM_INFO_BLOCK_TRANSFER |
5163ddf68dSFederico Simoncelli 		SNDRV_PCM_INFO_MMAP_VALID,
5263ddf68dSFederico Simoncelli 	.formats = SNDRV_PCM_FMTBIT_S16_LE,
5363ddf68dSFederico Simoncelli 	.rates = SNDRV_PCM_RATE_48000,
5463ddf68dSFederico Simoncelli 	.rate_min = 48000,
5563ddf68dSFederico Simoncelli 	.rate_max = 48000,
5663ddf68dSFederico Simoncelli 	.channels_min = 2,
5763ddf68dSFederico Simoncelli 	.channels_max = 2,
5863ddf68dSFederico Simoncelli 	.period_bytes_min = 11059,
5963ddf68dSFederico Simoncelli 	.period_bytes_max = 13516,
6063ddf68dSFederico Simoncelli 	.periods_min = 2,
6163ddf68dSFederico Simoncelli 	.periods_max = 98,
6263ddf68dSFederico Simoncelli 	.buffer_bytes_max = 62720 * 8, /* value in usbaudio.c */
6363ddf68dSFederico Simoncelli };
6463ddf68dSFederico Simoncelli 
snd_usbtv_pcm_open(struct snd_pcm_substream * substream)6563ddf68dSFederico Simoncelli static int snd_usbtv_pcm_open(struct snd_pcm_substream *substream)
6663ddf68dSFederico Simoncelli {
6763ddf68dSFederico Simoncelli 	struct usbtv *chip = snd_pcm_substream_chip(substream);
6863ddf68dSFederico Simoncelli 	struct snd_pcm_runtime *runtime = substream->runtime;
6963ddf68dSFederico Simoncelli 
7063ddf68dSFederico Simoncelli 	chip->snd_substream = substream;
7163ddf68dSFederico Simoncelli 	runtime->hw = snd_usbtv_digital_hw;
7263ddf68dSFederico Simoncelli 
7363ddf68dSFederico Simoncelli 	return 0;
7463ddf68dSFederico Simoncelli }
7563ddf68dSFederico Simoncelli 
snd_usbtv_pcm_close(struct snd_pcm_substream * substream)7663ddf68dSFederico Simoncelli static int snd_usbtv_pcm_close(struct snd_pcm_substream *substream)
7763ddf68dSFederico Simoncelli {
7863ddf68dSFederico Simoncelli 	struct usbtv *chip = snd_pcm_substream_chip(substream);
7963ddf68dSFederico Simoncelli 
8063ddf68dSFederico Simoncelli 	if (atomic_read(&chip->snd_stream)) {
8163ddf68dSFederico Simoncelli 		atomic_set(&chip->snd_stream, 0);
8263ddf68dSFederico Simoncelli 		schedule_work(&chip->snd_trigger);
8363ddf68dSFederico Simoncelli 	}
8463ddf68dSFederico Simoncelli 
8563ddf68dSFederico Simoncelli 	return 0;
8663ddf68dSFederico Simoncelli }
8763ddf68dSFederico Simoncelli 
snd_usbtv_prepare(struct snd_pcm_substream * substream)8863ddf68dSFederico Simoncelli static int snd_usbtv_prepare(struct snd_pcm_substream *substream)
8963ddf68dSFederico Simoncelli {
9063ddf68dSFederico Simoncelli 	struct usbtv *chip = snd_pcm_substream_chip(substream);
9163ddf68dSFederico Simoncelli 
9263ddf68dSFederico Simoncelli 	chip->snd_buffer_pos = 0;
9363ddf68dSFederico Simoncelli 	chip->snd_period_pos = 0;
9463ddf68dSFederico Simoncelli 
9563ddf68dSFederico Simoncelli 	return 0;
9663ddf68dSFederico Simoncelli }
9763ddf68dSFederico Simoncelli 
usbtv_audio_urb_received(struct urb * urb)9863ddf68dSFederico Simoncelli static void usbtv_audio_urb_received(struct urb *urb)
9963ddf68dSFederico Simoncelli {
10063ddf68dSFederico Simoncelli 	struct usbtv *chip = urb->context;
10163ddf68dSFederico Simoncelli 	struct snd_pcm_substream *substream = chip->snd_substream;
10263ddf68dSFederico Simoncelli 	struct snd_pcm_runtime *runtime = substream->runtime;
10363ddf68dSFederico Simoncelli 	size_t i, frame_bytes, chunk_length, buffer_pos, period_pos;
10463ddf68dSFederico Simoncelli 	int period_elapsed;
105320905baSSebastian Andrzej Siewior 	unsigned long flags;
10663ddf68dSFederico Simoncelli 	void *urb_current;
10763ddf68dSFederico Simoncelli 
10863ddf68dSFederico Simoncelli 	switch (urb->status) {
10963ddf68dSFederico Simoncelli 	case 0:
11063ddf68dSFederico Simoncelli 	case -ETIMEDOUT:
11163ddf68dSFederico Simoncelli 		break;
11263ddf68dSFederico Simoncelli 	case -ENOENT:
11363ddf68dSFederico Simoncelli 	case -EPROTO:
11463ddf68dSFederico Simoncelli 	case -ECONNRESET:
11563ddf68dSFederico Simoncelli 	case -ESHUTDOWN:
11663ddf68dSFederico Simoncelli 		return;
11763ddf68dSFederico Simoncelli 	default:
11863ddf68dSFederico Simoncelli 		dev_warn(chip->dev, "unknown audio urb status %i\n",
11963ddf68dSFederico Simoncelli 			urb->status);
12063ddf68dSFederico Simoncelli 	}
12163ddf68dSFederico Simoncelli 
12263ddf68dSFederico Simoncelli 	if (!atomic_read(&chip->snd_stream))
12363ddf68dSFederico Simoncelli 		return;
12463ddf68dSFederico Simoncelli 
12563ddf68dSFederico Simoncelli 	frame_bytes = runtime->frame_bits >> 3;
12663ddf68dSFederico Simoncelli 	chunk_length = USBTV_CHUNK / frame_bytes;
12763ddf68dSFederico Simoncelli 
12863ddf68dSFederico Simoncelli 	buffer_pos = chip->snd_buffer_pos;
12963ddf68dSFederico Simoncelli 	period_pos = chip->snd_period_pos;
13063ddf68dSFederico Simoncelli 	period_elapsed = 0;
13163ddf68dSFederico Simoncelli 
13263ddf68dSFederico Simoncelli 	for (i = 0; i < urb->actual_length; i += USBTV_CHUNK_SIZE) {
13363ddf68dSFederico Simoncelli 		urb_current = urb->transfer_buffer + i + USBTV_AUDIO_HDRSIZE;
13463ddf68dSFederico Simoncelli 
13563ddf68dSFederico Simoncelli 		if (buffer_pos + chunk_length >= runtime->buffer_size) {
13663ddf68dSFederico Simoncelli 			size_t cnt = (runtime->buffer_size - buffer_pos) *
13763ddf68dSFederico Simoncelli 				frame_bytes;
13863ddf68dSFederico Simoncelli 			memcpy(runtime->dma_area + buffer_pos * frame_bytes,
13963ddf68dSFederico Simoncelli 				urb_current, cnt);
14063ddf68dSFederico Simoncelli 			memcpy(runtime->dma_area, urb_current + cnt,
14163ddf68dSFederico Simoncelli 				chunk_length * frame_bytes - cnt);
14263ddf68dSFederico Simoncelli 		} else {
14363ddf68dSFederico Simoncelli 			memcpy(runtime->dma_area + buffer_pos * frame_bytes,
14463ddf68dSFederico Simoncelli 				urb_current, chunk_length * frame_bytes);
14563ddf68dSFederico Simoncelli 		}
14663ddf68dSFederico Simoncelli 
14763ddf68dSFederico Simoncelli 		buffer_pos += chunk_length;
14863ddf68dSFederico Simoncelli 		period_pos += chunk_length;
14963ddf68dSFederico Simoncelli 
15063ddf68dSFederico Simoncelli 		if (buffer_pos >= runtime->buffer_size)
15163ddf68dSFederico Simoncelli 			buffer_pos -= runtime->buffer_size;
15263ddf68dSFederico Simoncelli 
15363ddf68dSFederico Simoncelli 		if (period_pos >= runtime->period_size) {
15463ddf68dSFederico Simoncelli 			period_pos -= runtime->period_size;
15563ddf68dSFederico Simoncelli 			period_elapsed = 1;
15663ddf68dSFederico Simoncelli 		}
15763ddf68dSFederico Simoncelli 	}
15863ddf68dSFederico Simoncelli 
159320905baSSebastian Andrzej Siewior 	snd_pcm_stream_lock_irqsave(substream, flags);
16063ddf68dSFederico Simoncelli 
16163ddf68dSFederico Simoncelli 	chip->snd_buffer_pos = buffer_pos;
16263ddf68dSFederico Simoncelli 	chip->snd_period_pos = period_pos;
16363ddf68dSFederico Simoncelli 
164320905baSSebastian Andrzej Siewior 	snd_pcm_stream_unlock_irqrestore(substream, flags);
16563ddf68dSFederico Simoncelli 
16663ddf68dSFederico Simoncelli 	if (period_elapsed)
16763ddf68dSFederico Simoncelli 		snd_pcm_period_elapsed(substream);
16863ddf68dSFederico Simoncelli 
16963ddf68dSFederico Simoncelli 	usb_submit_urb(urb, GFP_ATOMIC);
17063ddf68dSFederico Simoncelli }
17163ddf68dSFederico Simoncelli 
usbtv_audio_start(struct usbtv * chip)17263ddf68dSFederico Simoncelli static int usbtv_audio_start(struct usbtv *chip)
17363ddf68dSFederico Simoncelli {
17463ddf68dSFederico Simoncelli 	unsigned int pipe;
17563ddf68dSFederico Simoncelli 	static const u16 setup[][2] = {
17663ddf68dSFederico Simoncelli 		/* These seem to enable the device. */
17763ddf68dSFederico Simoncelli 		{ USBTV_BASE + 0x0008, 0x0001 },
17863ddf68dSFederico Simoncelli 		{ USBTV_BASE + 0x01d0, 0x00ff },
17963ddf68dSFederico Simoncelli 		{ USBTV_BASE + 0x01d9, 0x0002 },
18063ddf68dSFederico Simoncelli 
18163ddf68dSFederico Simoncelli 		{ USBTV_BASE + 0x01da, 0x0013 },
18263ddf68dSFederico Simoncelli 		{ USBTV_BASE + 0x01db, 0x0012 },
18363ddf68dSFederico Simoncelli 		{ USBTV_BASE + 0x01e9, 0x0002 },
18463ddf68dSFederico Simoncelli 		{ USBTV_BASE + 0x01ec, 0x006c },
18563ddf68dSFederico Simoncelli 		{ USBTV_BASE + 0x0294, 0x0020 },
18663ddf68dSFederico Simoncelli 		{ USBTV_BASE + 0x0255, 0x00cf },
18763ddf68dSFederico Simoncelli 		{ USBTV_BASE + 0x0256, 0x0020 },
18863ddf68dSFederico Simoncelli 		{ USBTV_BASE + 0x01eb, 0x0030 },
18963ddf68dSFederico Simoncelli 		{ USBTV_BASE + 0x027d, 0x00a6 },
19063ddf68dSFederico Simoncelli 		{ USBTV_BASE + 0x0280, 0x0011 },
19163ddf68dSFederico Simoncelli 		{ USBTV_BASE + 0x0281, 0x0040 },
19263ddf68dSFederico Simoncelli 		{ USBTV_BASE + 0x0282, 0x0011 },
19363ddf68dSFederico Simoncelli 		{ USBTV_BASE + 0x0283, 0x0040 },
19463ddf68dSFederico Simoncelli 		{ 0xf891, 0x0010 },
19563ddf68dSFederico Simoncelli 
19663ddf68dSFederico Simoncelli 		/* this sets the input from composite */
19763ddf68dSFederico Simoncelli 		{ USBTV_BASE + 0x0284, 0x00aa },
19863ddf68dSFederico Simoncelli 	};
19963ddf68dSFederico Simoncelli 
20063ddf68dSFederico Simoncelli 	chip->snd_bulk_urb = usb_alloc_urb(0, GFP_KERNEL);
20163ddf68dSFederico Simoncelli 	if (chip->snd_bulk_urb == NULL)
20263ddf68dSFederico Simoncelli 		goto err_alloc_urb;
20363ddf68dSFederico Simoncelli 
20463ddf68dSFederico Simoncelli 	pipe = usb_rcvbulkpipe(chip->udev, USBTV_AUDIO_ENDP);
20563ddf68dSFederico Simoncelli 
20663ddf68dSFederico Simoncelli 	chip->snd_bulk_urb->transfer_buffer = kzalloc(
20763ddf68dSFederico Simoncelli 		USBTV_AUDIO_URBSIZE, GFP_KERNEL);
20863ddf68dSFederico Simoncelli 	if (chip->snd_bulk_urb->transfer_buffer == NULL)
20963ddf68dSFederico Simoncelli 		goto err_transfer_buffer;
21063ddf68dSFederico Simoncelli 
21163ddf68dSFederico Simoncelli 	usb_fill_bulk_urb(chip->snd_bulk_urb, chip->udev, pipe,
21263ddf68dSFederico Simoncelli 		chip->snd_bulk_urb->transfer_buffer, USBTV_AUDIO_URBSIZE,
21363ddf68dSFederico Simoncelli 		usbtv_audio_urb_received, chip);
21463ddf68dSFederico Simoncelli 
21563ddf68dSFederico Simoncelli 	/* starting the stream */
21663ddf68dSFederico Simoncelli 	usbtv_set_regs(chip, setup, ARRAY_SIZE(setup));
21763ddf68dSFederico Simoncelli 
21863ddf68dSFederico Simoncelli 	usb_clear_halt(chip->udev, pipe);
21963ddf68dSFederico Simoncelli 	usb_submit_urb(chip->snd_bulk_urb, GFP_ATOMIC);
22063ddf68dSFederico Simoncelli 
22163ddf68dSFederico Simoncelli 	return 0;
22263ddf68dSFederico Simoncelli 
22363ddf68dSFederico Simoncelli err_transfer_buffer:
22463ddf68dSFederico Simoncelli 	usb_free_urb(chip->snd_bulk_urb);
22563ddf68dSFederico Simoncelli 	chip->snd_bulk_urb = NULL;
22663ddf68dSFederico Simoncelli 
22763ddf68dSFederico Simoncelli err_alloc_urb:
22863ddf68dSFederico Simoncelli 	return -ENOMEM;
22963ddf68dSFederico Simoncelli }
23063ddf68dSFederico Simoncelli 
usbtv_audio_stop(struct usbtv * chip)23163ddf68dSFederico Simoncelli static int usbtv_audio_stop(struct usbtv *chip)
23263ddf68dSFederico Simoncelli {
23363ddf68dSFederico Simoncelli 	static const u16 setup[][2] = {
23463ddf68dSFederico Simoncelli 	/* The original windows driver sometimes sends also:
23563ddf68dSFederico Simoncelli 	 *   { USBTV_BASE + 0x00a2, 0x0013 }
23663ddf68dSFederico Simoncelli 	 * but it seems useless and its real effects are untested at
23763ddf68dSFederico Simoncelli 	 * the moment.
23863ddf68dSFederico Simoncelli 	 */
23963ddf68dSFederico Simoncelli 		{ USBTV_BASE + 0x027d, 0x0000 },
24063ddf68dSFederico Simoncelli 		{ USBTV_BASE + 0x0280, 0x0010 },
24163ddf68dSFederico Simoncelli 		{ USBTV_BASE + 0x0282, 0x0010 },
24263ddf68dSFederico Simoncelli 	};
24363ddf68dSFederico Simoncelli 
24463ddf68dSFederico Simoncelli 	if (chip->snd_bulk_urb) {
24563ddf68dSFederico Simoncelli 		usb_kill_urb(chip->snd_bulk_urb);
24663ddf68dSFederico Simoncelli 		kfree(chip->snd_bulk_urb->transfer_buffer);
24763ddf68dSFederico Simoncelli 		usb_free_urb(chip->snd_bulk_urb);
24863ddf68dSFederico Simoncelli 		chip->snd_bulk_urb = NULL;
24963ddf68dSFederico Simoncelli 	}
25063ddf68dSFederico Simoncelli 
25163ddf68dSFederico Simoncelli 	usbtv_set_regs(chip, setup, ARRAY_SIZE(setup));
25263ddf68dSFederico Simoncelli 
25363ddf68dSFederico Simoncelli 	return 0;
25463ddf68dSFederico Simoncelli }
25563ddf68dSFederico Simoncelli 
usbtv_audio_suspend(struct usbtv * usbtv)25663ddf68dSFederico Simoncelli void usbtv_audio_suspend(struct usbtv *usbtv)
25763ddf68dSFederico Simoncelli {
25863ddf68dSFederico Simoncelli 	if (atomic_read(&usbtv->snd_stream) && usbtv->snd_bulk_urb)
25963ddf68dSFederico Simoncelli 		usb_kill_urb(usbtv->snd_bulk_urb);
26063ddf68dSFederico Simoncelli }
26163ddf68dSFederico Simoncelli 
usbtv_audio_resume(struct usbtv * usbtv)26263ddf68dSFederico Simoncelli void usbtv_audio_resume(struct usbtv *usbtv)
26363ddf68dSFederico Simoncelli {
26463ddf68dSFederico Simoncelli 	if (atomic_read(&usbtv->snd_stream) && usbtv->snd_bulk_urb)
26563ddf68dSFederico Simoncelli 		usb_submit_urb(usbtv->snd_bulk_urb, GFP_ATOMIC);
26663ddf68dSFederico Simoncelli }
26763ddf68dSFederico Simoncelli 
snd_usbtv_trigger(struct work_struct * work)26863ddf68dSFederico Simoncelli static void snd_usbtv_trigger(struct work_struct *work)
26963ddf68dSFederico Simoncelli {
27063ddf68dSFederico Simoncelli 	struct usbtv *chip = container_of(work, struct usbtv, snd_trigger);
27163ddf68dSFederico Simoncelli 
2722a00932fSMatthew Leach 	if (!chip->snd)
2732a00932fSMatthew Leach 		return;
2742a00932fSMatthew Leach 
27563ddf68dSFederico Simoncelli 	if (atomic_read(&chip->snd_stream))
27663ddf68dSFederico Simoncelli 		usbtv_audio_start(chip);
27763ddf68dSFederico Simoncelli 	else
27863ddf68dSFederico Simoncelli 		usbtv_audio_stop(chip);
27963ddf68dSFederico Simoncelli }
28063ddf68dSFederico Simoncelli 
snd_usbtv_card_trigger(struct snd_pcm_substream * substream,int cmd)28163ddf68dSFederico Simoncelli static int snd_usbtv_card_trigger(struct snd_pcm_substream *substream, int cmd)
28263ddf68dSFederico Simoncelli {
28363ddf68dSFederico Simoncelli 	struct usbtv *chip = snd_pcm_substream_chip(substream);
28463ddf68dSFederico Simoncelli 
28563ddf68dSFederico Simoncelli 	switch (cmd) {
28663ddf68dSFederico Simoncelli 	case SNDRV_PCM_TRIGGER_START:
28763ddf68dSFederico Simoncelli 	case SNDRV_PCM_TRIGGER_RESUME:
28863ddf68dSFederico Simoncelli 	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
28963ddf68dSFederico Simoncelli 		atomic_set(&chip->snd_stream, 1);
29063ddf68dSFederico Simoncelli 		break;
29163ddf68dSFederico Simoncelli 	case SNDRV_PCM_TRIGGER_STOP:
29263ddf68dSFederico Simoncelli 	case SNDRV_PCM_TRIGGER_SUSPEND:
29363ddf68dSFederico Simoncelli 	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
29463ddf68dSFederico Simoncelli 		atomic_set(&chip->snd_stream, 0);
29563ddf68dSFederico Simoncelli 		break;
29663ddf68dSFederico Simoncelli 	default:
29763ddf68dSFederico Simoncelli 		return -EINVAL;
29863ddf68dSFederico Simoncelli 	}
29963ddf68dSFederico Simoncelli 
30063ddf68dSFederico Simoncelli 	schedule_work(&chip->snd_trigger);
30163ddf68dSFederico Simoncelli 
30263ddf68dSFederico Simoncelli 	return 0;
30363ddf68dSFederico Simoncelli }
30463ddf68dSFederico Simoncelli 
snd_usbtv_pointer(struct snd_pcm_substream * substream)30563ddf68dSFederico Simoncelli static snd_pcm_uframes_t snd_usbtv_pointer(struct snd_pcm_substream *substream)
30663ddf68dSFederico Simoncelli {
30763ddf68dSFederico Simoncelli 	struct usbtv *chip = snd_pcm_substream_chip(substream);
308146af9cbSAmber Thrall 
30963ddf68dSFederico Simoncelli 	return chip->snd_buffer_pos;
31063ddf68dSFederico Simoncelli }
31163ddf68dSFederico Simoncelli 
31222511cfaSJulia Lawall static const struct snd_pcm_ops snd_usbtv_pcm_ops = {
31363ddf68dSFederico Simoncelli 	.open = snd_usbtv_pcm_open,
31463ddf68dSFederico Simoncelli 	.close = snd_usbtv_pcm_close,
31563ddf68dSFederico Simoncelli 	.prepare = snd_usbtv_prepare,
31663ddf68dSFederico Simoncelli 	.trigger = snd_usbtv_card_trigger,
31763ddf68dSFederico Simoncelli 	.pointer = snd_usbtv_pointer,
31863ddf68dSFederico Simoncelli };
31963ddf68dSFederico Simoncelli 
usbtv_audio_init(struct usbtv * usbtv)32063ddf68dSFederico Simoncelli int usbtv_audio_init(struct usbtv *usbtv)
32163ddf68dSFederico Simoncelli {
32263ddf68dSFederico Simoncelli 	int rv;
32363ddf68dSFederico Simoncelli 	struct snd_card *card;
32463ddf68dSFederico Simoncelli 	struct snd_pcm *pcm;
32563ddf68dSFederico Simoncelli 
32663ddf68dSFederico Simoncelli 	INIT_WORK(&usbtv->snd_trigger, snd_usbtv_trigger);
32763ddf68dSFederico Simoncelli 	atomic_set(&usbtv->snd_stream, 0);
32863ddf68dSFederico Simoncelli 
32963ddf68dSFederico Simoncelli 	rv = snd_card_new(&usbtv->udev->dev, SNDRV_DEFAULT_IDX1, "usbtv",
33063ddf68dSFederico Simoncelli 		THIS_MODULE, 0, &card);
33163ddf68dSFederico Simoncelli 	if (rv < 0)
33263ddf68dSFederico Simoncelli 		return rv;
33363ddf68dSFederico Simoncelli 
334c0decac1SMauro Carvalho Chehab 	strscpy(card->driver, usbtv->dev->driver->name, sizeof(card->driver));
335c0decac1SMauro Carvalho Chehab 	strscpy(card->shortname, "usbtv", sizeof(card->shortname));
33663ddf68dSFederico Simoncelli 	snprintf(card->longname, sizeof(card->longname),
33763ddf68dSFederico Simoncelli 		"USBTV Audio at bus %d device %d", usbtv->udev->bus->busnum,
33863ddf68dSFederico Simoncelli 		usbtv->udev->devnum);
33963ddf68dSFederico Simoncelli 
34063ddf68dSFederico Simoncelli 	snd_card_set_dev(card, usbtv->dev);
34163ddf68dSFederico Simoncelli 
34263ddf68dSFederico Simoncelli 	usbtv->snd = card;
34363ddf68dSFederico Simoncelli 
34463ddf68dSFederico Simoncelli 	rv = snd_pcm_new(card, "USBTV Audio", 0, 0, 1, &pcm);
34563ddf68dSFederico Simoncelli 	if (rv < 0)
34663ddf68dSFederico Simoncelli 		goto err;
34763ddf68dSFederico Simoncelli 
348c0decac1SMauro Carvalho Chehab 	strscpy(pcm->name, "USBTV Audio Input", sizeof(pcm->name));
34963ddf68dSFederico Simoncelli 	pcm->info_flags = 0;
35063ddf68dSFederico Simoncelli 	pcm->private_data = usbtv;
35163ddf68dSFederico Simoncelli 
35263ddf68dSFederico Simoncelli 	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usbtv_pcm_ops);
3538079c5d5STakashi Iwai 	snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
354ef99d99bSTakashi Iwai 		NULL, USBTV_AUDIO_BUFFER, USBTV_AUDIO_BUFFER);
35563ddf68dSFederico Simoncelli 
35663ddf68dSFederico Simoncelli 	rv = snd_card_register(card);
35763ddf68dSFederico Simoncelli 	if (rv)
35863ddf68dSFederico Simoncelli 		goto err;
35963ddf68dSFederico Simoncelli 
36063ddf68dSFederico Simoncelli 	return 0;
36163ddf68dSFederico Simoncelli 
36263ddf68dSFederico Simoncelli err:
36363ddf68dSFederico Simoncelli 	usbtv->snd = NULL;
36463ddf68dSFederico Simoncelli 	snd_card_free(card);
36563ddf68dSFederico Simoncelli 
36663ddf68dSFederico Simoncelli 	return rv;
36763ddf68dSFederico Simoncelli }
36863ddf68dSFederico Simoncelli 
usbtv_audio_free(struct usbtv * usbtv)36963ddf68dSFederico Simoncelli void usbtv_audio_free(struct usbtv *usbtv)
37063ddf68dSFederico Simoncelli {
3712a00932fSMatthew Leach 	cancel_work_sync(&usbtv->snd_trigger);
3722a00932fSMatthew Leach 
37363ddf68dSFederico Simoncelli 	if (usbtv->snd && usbtv->udev) {
374*8a7e27fdSMaxim Mikityanskiy 		snd_card_free_when_closed(usbtv->snd);
37563ddf68dSFederico Simoncelli 		usbtv->snd = NULL;
37663ddf68dSFederico Simoncelli 	}
37763ddf68dSFederico Simoncelli }
378