1c942fddfSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
20c0d06caSMauro Carvalho Chehab /*
30c0d06caSMauro Carvalho Chehab  *  Conexant Cx231xx audio extension
40c0d06caSMauro Carvalho Chehab  *
50c0d06caSMauro Carvalho Chehab  *  Copyright (C) 2008 <srinivasa.deevi at conexant dot com>
60c0d06caSMauro Carvalho Chehab  *       Based on em28xx driver
70c0d06caSMauro Carvalho Chehab  */
80c0d06caSMauro Carvalho Chehab 
9589dadf2SMauro Carvalho Chehab #include "cx231xx.h"
100c0d06caSMauro Carvalho Chehab #include <linux/kernel.h>
110c0d06caSMauro Carvalho Chehab #include <linux/init.h>
120c0d06caSMauro Carvalho Chehab #include <linux/sound.h>
130c0d06caSMauro Carvalho Chehab #include <linux/spinlock.h>
140c0d06caSMauro Carvalho Chehab #include <linux/soundcard.h>
150c0d06caSMauro Carvalho Chehab #include <linux/slab.h>
160c0d06caSMauro Carvalho Chehab #include <linux/module.h>
170c0d06caSMauro Carvalho Chehab #include <sound/core.h>
180c0d06caSMauro Carvalho Chehab #include <sound/pcm.h>
190c0d06caSMauro Carvalho Chehab #include <sound/pcm_params.h>
200c0d06caSMauro Carvalho Chehab #include <sound/info.h>
210c0d06caSMauro Carvalho Chehab #include <sound/initval.h>
220c0d06caSMauro Carvalho Chehab #include <sound/control.h>
230c0d06caSMauro Carvalho Chehab #include <media/v4l2-common.h>
240c0d06caSMauro Carvalho Chehab 
250c0d06caSMauro Carvalho Chehab static int debug;
260c0d06caSMauro Carvalho Chehab module_param(debug, int, 0644);
270c0d06caSMauro Carvalho Chehab MODULE_PARM_DESC(debug, "activates debug info");
280c0d06caSMauro Carvalho Chehab 
290c0d06caSMauro Carvalho Chehab static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
300c0d06caSMauro Carvalho Chehab 
cx231xx_isoc_audio_deinit(struct cx231xx * dev)310c0d06caSMauro Carvalho Chehab static int cx231xx_isoc_audio_deinit(struct cx231xx *dev)
320c0d06caSMauro Carvalho Chehab {
330c0d06caSMauro Carvalho Chehab 	int i;
340c0d06caSMauro Carvalho Chehab 
35336fea92SMauro Carvalho Chehab 	dev_dbg(dev->dev, "Stopping isoc\n");
360c0d06caSMauro Carvalho Chehab 
370c0d06caSMauro Carvalho Chehab 	for (i = 0; i < CX231XX_AUDIO_BUFS; i++) {
380c0d06caSMauro Carvalho Chehab 		if (dev->adev.urb[i]) {
390c0d06caSMauro Carvalho Chehab 			if (!irqs_disabled())
400c0d06caSMauro Carvalho Chehab 				usb_kill_urb(dev->adev.urb[i]);
410c0d06caSMauro Carvalho Chehab 			else
420c0d06caSMauro Carvalho Chehab 				usb_unlink_urb(dev->adev.urb[i]);
430c0d06caSMauro Carvalho Chehab 
440c0d06caSMauro Carvalho Chehab 			usb_free_urb(dev->adev.urb[i]);
450c0d06caSMauro Carvalho Chehab 			dev->adev.urb[i] = NULL;
460c0d06caSMauro Carvalho Chehab 
470c0d06caSMauro Carvalho Chehab 			kfree(dev->adev.transfer_buffer[i]);
480c0d06caSMauro Carvalho Chehab 			dev->adev.transfer_buffer[i] = NULL;
490c0d06caSMauro Carvalho Chehab 		}
500c0d06caSMauro Carvalho Chehab 	}
510c0d06caSMauro Carvalho Chehab 
520c0d06caSMauro Carvalho Chehab 	return 0;
530c0d06caSMauro Carvalho Chehab }
540c0d06caSMauro Carvalho Chehab 
cx231xx_bulk_audio_deinit(struct cx231xx * dev)550c0d06caSMauro Carvalho Chehab static int cx231xx_bulk_audio_deinit(struct cx231xx *dev)
560c0d06caSMauro Carvalho Chehab {
570c0d06caSMauro Carvalho Chehab 	int i;
580c0d06caSMauro Carvalho Chehab 
59336fea92SMauro Carvalho Chehab 	dev_dbg(dev->dev, "Stopping bulk\n");
600c0d06caSMauro Carvalho Chehab 
610c0d06caSMauro Carvalho Chehab 	for (i = 0; i < CX231XX_AUDIO_BUFS; i++) {
620c0d06caSMauro Carvalho Chehab 		if (dev->adev.urb[i]) {
630c0d06caSMauro Carvalho Chehab 			if (!irqs_disabled())
640c0d06caSMauro Carvalho Chehab 				usb_kill_urb(dev->adev.urb[i]);
650c0d06caSMauro Carvalho Chehab 			else
660c0d06caSMauro Carvalho Chehab 				usb_unlink_urb(dev->adev.urb[i]);
670c0d06caSMauro Carvalho Chehab 
680c0d06caSMauro Carvalho Chehab 			usb_free_urb(dev->adev.urb[i]);
690c0d06caSMauro Carvalho Chehab 			dev->adev.urb[i] = NULL;
700c0d06caSMauro Carvalho Chehab 
710c0d06caSMauro Carvalho Chehab 			kfree(dev->adev.transfer_buffer[i]);
720c0d06caSMauro Carvalho Chehab 			dev->adev.transfer_buffer[i] = NULL;
730c0d06caSMauro Carvalho Chehab 		}
740c0d06caSMauro Carvalho Chehab 	}
750c0d06caSMauro Carvalho Chehab 
760c0d06caSMauro Carvalho Chehab 	return 0;
770c0d06caSMauro Carvalho Chehab }
780c0d06caSMauro Carvalho Chehab 
cx231xx_audio_isocirq(struct urb * urb)790c0d06caSMauro Carvalho Chehab static void cx231xx_audio_isocirq(struct urb *urb)
800c0d06caSMauro Carvalho Chehab {
810c0d06caSMauro Carvalho Chehab 	struct cx231xx *dev = urb->context;
820c0d06caSMauro Carvalho Chehab 	int i;
830c0d06caSMauro Carvalho Chehab 	unsigned int oldptr;
840c0d06caSMauro Carvalho Chehab 	int period_elapsed = 0;
850c0d06caSMauro Carvalho Chehab 	int status;
860c0d06caSMauro Carvalho Chehab 	unsigned char *cp;
870c0d06caSMauro Carvalho Chehab 	unsigned int stride;
880c0d06caSMauro Carvalho Chehab 	struct snd_pcm_substream *substream;
890c0d06caSMauro Carvalho Chehab 	struct snd_pcm_runtime *runtime;
900c0d06caSMauro Carvalho Chehab 
910c0d06caSMauro Carvalho Chehab 	if (dev->state & DEV_DISCONNECTED)
920c0d06caSMauro Carvalho Chehab 		return;
930c0d06caSMauro Carvalho Chehab 
940c0d06caSMauro Carvalho Chehab 	switch (urb->status) {
950c0d06caSMauro Carvalho Chehab 	case 0:		/* success */
960c0d06caSMauro Carvalho Chehab 	case -ETIMEDOUT:	/* NAK */
970c0d06caSMauro Carvalho Chehab 		break;
980c0d06caSMauro Carvalho Chehab 	case -ECONNRESET:	/* kill */
990c0d06caSMauro Carvalho Chehab 	case -ENOENT:
1000c0d06caSMauro Carvalho Chehab 	case -ESHUTDOWN:
1010c0d06caSMauro Carvalho Chehab 		return;
1020c0d06caSMauro Carvalho Chehab 	default:		/* error */
103854bb4ecSColin Ian King 		dev_dbg(dev->dev, "urb completion error %d.\n",
10456d8a3b0SMauro Carvalho Chehab 			urb->status);
1050c0d06caSMauro Carvalho Chehab 		break;
1060c0d06caSMauro Carvalho Chehab 	}
1070c0d06caSMauro Carvalho Chehab 
1080c0d06caSMauro Carvalho Chehab 	if (atomic_read(&dev->stream_started) == 0)
1090c0d06caSMauro Carvalho Chehab 		return;
1100c0d06caSMauro Carvalho Chehab 
1110c0d06caSMauro Carvalho Chehab 	if (dev->adev.capture_pcm_substream) {
1120c0d06caSMauro Carvalho Chehab 		substream = dev->adev.capture_pcm_substream;
1130c0d06caSMauro Carvalho Chehab 		runtime = substream->runtime;
1140c0d06caSMauro Carvalho Chehab 		stride = runtime->frame_bits >> 3;
1150c0d06caSMauro Carvalho Chehab 
1160c0d06caSMauro Carvalho Chehab 		for (i = 0; i < urb->number_of_packets; i++) {
117aa53bf0bSSebastian Andrzej Siewior 			unsigned long flags;
1180c0d06caSMauro Carvalho Chehab 			int length = urb->iso_frame_desc[i].actual_length /
1190c0d06caSMauro Carvalho Chehab 				     stride;
1200c0d06caSMauro Carvalho Chehab 			cp = (unsigned char *)urb->transfer_buffer +
1210c0d06caSMauro Carvalho Chehab 					      urb->iso_frame_desc[i].offset;
1220c0d06caSMauro Carvalho Chehab 
1230c0d06caSMauro Carvalho Chehab 			if (!length)
1240c0d06caSMauro Carvalho Chehab 				continue;
1250c0d06caSMauro Carvalho Chehab 
1260c0d06caSMauro Carvalho Chehab 			oldptr = dev->adev.hwptr_done_capture;
1270c0d06caSMauro Carvalho Chehab 			if (oldptr + length >= runtime->buffer_size) {
1280c0d06caSMauro Carvalho Chehab 				unsigned int cnt;
1290c0d06caSMauro Carvalho Chehab 
1300c0d06caSMauro Carvalho Chehab 				cnt = runtime->buffer_size - oldptr;
1310c0d06caSMauro Carvalho Chehab 				memcpy(runtime->dma_area + oldptr * stride, cp,
1320c0d06caSMauro Carvalho Chehab 				       cnt * stride);
1330c0d06caSMauro Carvalho Chehab 				memcpy(runtime->dma_area, cp + cnt * stride,
1340c0d06caSMauro Carvalho Chehab 				       length * stride - cnt * stride);
1350c0d06caSMauro Carvalho Chehab 			} else {
1360c0d06caSMauro Carvalho Chehab 				memcpy(runtime->dma_area + oldptr * stride, cp,
1370c0d06caSMauro Carvalho Chehab 				       length * stride);
1380c0d06caSMauro Carvalho Chehab 			}
1390c0d06caSMauro Carvalho Chehab 
140aa53bf0bSSebastian Andrzej Siewior 			snd_pcm_stream_lock_irqsave(substream, flags);
1410c0d06caSMauro Carvalho Chehab 
1420c0d06caSMauro Carvalho Chehab 			dev->adev.hwptr_done_capture += length;
1430c0d06caSMauro Carvalho Chehab 			if (dev->adev.hwptr_done_capture >=
1440c0d06caSMauro Carvalho Chehab 						runtime->buffer_size)
1450c0d06caSMauro Carvalho Chehab 				dev->adev.hwptr_done_capture -=
1460c0d06caSMauro Carvalho Chehab 						runtime->buffer_size;
1470c0d06caSMauro Carvalho Chehab 
1480c0d06caSMauro Carvalho Chehab 			dev->adev.capture_transfer_done += length;
1490c0d06caSMauro Carvalho Chehab 			if (dev->adev.capture_transfer_done >=
1500c0d06caSMauro Carvalho Chehab 				runtime->period_size) {
1510c0d06caSMauro Carvalho Chehab 				dev->adev.capture_transfer_done -=
1520c0d06caSMauro Carvalho Chehab 						runtime->period_size;
1530c0d06caSMauro Carvalho Chehab 				period_elapsed = 1;
1540c0d06caSMauro Carvalho Chehab 			}
155aa53bf0bSSebastian Andrzej Siewior 			snd_pcm_stream_unlock_irqrestore(substream, flags);
1560c0d06caSMauro Carvalho Chehab 		}
1570c0d06caSMauro Carvalho Chehab 		if (period_elapsed)
1580c0d06caSMauro Carvalho Chehab 			snd_pcm_period_elapsed(substream);
1590c0d06caSMauro Carvalho Chehab 	}
1600c0d06caSMauro Carvalho Chehab 	urb->status = 0;
1610c0d06caSMauro Carvalho Chehab 
1620c0d06caSMauro Carvalho Chehab 	status = usb_submit_urb(urb, GFP_ATOMIC);
1630c0d06caSMauro Carvalho Chehab 	if (status < 0) {
164336fea92SMauro Carvalho Chehab 		dev_err(dev->dev,
165b7085c08SMauro Carvalho Chehab 			"resubmit of audio urb failed (error=%i)\n",
1660c0d06caSMauro Carvalho Chehab 			status);
1670c0d06caSMauro Carvalho Chehab 	}
1680c0d06caSMauro Carvalho Chehab 	return;
1690c0d06caSMauro Carvalho Chehab }
1700c0d06caSMauro Carvalho Chehab 
cx231xx_audio_bulkirq(struct urb * urb)1710c0d06caSMauro Carvalho Chehab static void cx231xx_audio_bulkirq(struct urb *urb)
1720c0d06caSMauro Carvalho Chehab {
1730c0d06caSMauro Carvalho Chehab 	struct cx231xx *dev = urb->context;
1740c0d06caSMauro Carvalho Chehab 	unsigned int oldptr;
1750c0d06caSMauro Carvalho Chehab 	int period_elapsed = 0;
1760c0d06caSMauro Carvalho Chehab 	int status;
1770c0d06caSMauro Carvalho Chehab 	unsigned char *cp;
1780c0d06caSMauro Carvalho Chehab 	unsigned int stride;
1790c0d06caSMauro Carvalho Chehab 	struct snd_pcm_substream *substream;
1800c0d06caSMauro Carvalho Chehab 	struct snd_pcm_runtime *runtime;
1810c0d06caSMauro Carvalho Chehab 
1820c0d06caSMauro Carvalho Chehab 	if (dev->state & DEV_DISCONNECTED)
1830c0d06caSMauro Carvalho Chehab 		return;
1840c0d06caSMauro Carvalho Chehab 
1850c0d06caSMauro Carvalho Chehab 	switch (urb->status) {
1860c0d06caSMauro Carvalho Chehab 	case 0:		/* success */
1870c0d06caSMauro Carvalho Chehab 	case -ETIMEDOUT:	/* NAK */
1880c0d06caSMauro Carvalho Chehab 		break;
1890c0d06caSMauro Carvalho Chehab 	case -ECONNRESET:	/* kill */
1900c0d06caSMauro Carvalho Chehab 	case -ENOENT:
1910c0d06caSMauro Carvalho Chehab 	case -ESHUTDOWN:
1920c0d06caSMauro Carvalho Chehab 		return;
1930c0d06caSMauro Carvalho Chehab 	default:		/* error */
194854bb4ecSColin Ian King 		dev_dbg(dev->dev, "urb completion error %d.\n",
19556d8a3b0SMauro Carvalho Chehab 			urb->status);
1960c0d06caSMauro Carvalho Chehab 		break;
1970c0d06caSMauro Carvalho Chehab 	}
1980c0d06caSMauro Carvalho Chehab 
1990c0d06caSMauro Carvalho Chehab 	if (atomic_read(&dev->stream_started) == 0)
2000c0d06caSMauro Carvalho Chehab 		return;
2010c0d06caSMauro Carvalho Chehab 
2020c0d06caSMauro Carvalho Chehab 	if (dev->adev.capture_pcm_substream) {
2030c0d06caSMauro Carvalho Chehab 		substream = dev->adev.capture_pcm_substream;
2040c0d06caSMauro Carvalho Chehab 		runtime = substream->runtime;
2050c0d06caSMauro Carvalho Chehab 		stride = runtime->frame_bits >> 3;
2060c0d06caSMauro Carvalho Chehab 
2070c0d06caSMauro Carvalho Chehab 		if (1) {
208aa53bf0bSSebastian Andrzej Siewior 			unsigned long flags;
2090c0d06caSMauro Carvalho Chehab 			int length = urb->actual_length /
2100c0d06caSMauro Carvalho Chehab 				     stride;
2110c0d06caSMauro Carvalho Chehab 			cp = (unsigned char *)urb->transfer_buffer;
2120c0d06caSMauro Carvalho Chehab 
2130c0d06caSMauro Carvalho Chehab 			oldptr = dev->adev.hwptr_done_capture;
2140c0d06caSMauro Carvalho Chehab 			if (oldptr + length >= runtime->buffer_size) {
2150c0d06caSMauro Carvalho Chehab 				unsigned int cnt;
2160c0d06caSMauro Carvalho Chehab 
2170c0d06caSMauro Carvalho Chehab 				cnt = runtime->buffer_size - oldptr;
2180c0d06caSMauro Carvalho Chehab 				memcpy(runtime->dma_area + oldptr * stride, cp,
2190c0d06caSMauro Carvalho Chehab 				       cnt * stride);
2200c0d06caSMauro Carvalho Chehab 				memcpy(runtime->dma_area, cp + cnt * stride,
2210c0d06caSMauro Carvalho Chehab 				       length * stride - cnt * stride);
2220c0d06caSMauro Carvalho Chehab 			} else {
2230c0d06caSMauro Carvalho Chehab 				memcpy(runtime->dma_area + oldptr * stride, cp,
2240c0d06caSMauro Carvalho Chehab 				       length * stride);
2250c0d06caSMauro Carvalho Chehab 			}
2260c0d06caSMauro Carvalho Chehab 
227aa53bf0bSSebastian Andrzej Siewior 			snd_pcm_stream_lock_irqsave(substream, flags);
2280c0d06caSMauro Carvalho Chehab 
2290c0d06caSMauro Carvalho Chehab 			dev->adev.hwptr_done_capture += length;
2300c0d06caSMauro Carvalho Chehab 			if (dev->adev.hwptr_done_capture >=
2310c0d06caSMauro Carvalho Chehab 						runtime->buffer_size)
2320c0d06caSMauro Carvalho Chehab 				dev->adev.hwptr_done_capture -=
2330c0d06caSMauro Carvalho Chehab 						runtime->buffer_size;
2340c0d06caSMauro Carvalho Chehab 
2350c0d06caSMauro Carvalho Chehab 			dev->adev.capture_transfer_done += length;
2360c0d06caSMauro Carvalho Chehab 			if (dev->adev.capture_transfer_done >=
2370c0d06caSMauro Carvalho Chehab 				runtime->period_size) {
2380c0d06caSMauro Carvalho Chehab 				dev->adev.capture_transfer_done -=
2390c0d06caSMauro Carvalho Chehab 						runtime->period_size;
2400c0d06caSMauro Carvalho Chehab 				period_elapsed = 1;
2410c0d06caSMauro Carvalho Chehab 			}
242aa53bf0bSSebastian Andrzej Siewior 			snd_pcm_stream_unlock_irqrestore(substream, flags);
2430c0d06caSMauro Carvalho Chehab 		}
2440c0d06caSMauro Carvalho Chehab 		if (period_elapsed)
2450c0d06caSMauro Carvalho Chehab 			snd_pcm_period_elapsed(substream);
2460c0d06caSMauro Carvalho Chehab 	}
2470c0d06caSMauro Carvalho Chehab 	urb->status = 0;
2480c0d06caSMauro Carvalho Chehab 
2490c0d06caSMauro Carvalho Chehab 	status = usb_submit_urb(urb, GFP_ATOMIC);
2500c0d06caSMauro Carvalho Chehab 	if (status < 0) {
251336fea92SMauro Carvalho Chehab 		dev_err(dev->dev,
252b7085c08SMauro Carvalho Chehab 			"resubmit of audio urb failed (error=%i)\n",
2530c0d06caSMauro Carvalho Chehab 			status);
2540c0d06caSMauro Carvalho Chehab 	}
2550c0d06caSMauro Carvalho Chehab 	return;
2560c0d06caSMauro Carvalho Chehab }
2570c0d06caSMauro Carvalho Chehab 
cx231xx_init_audio_isoc(struct cx231xx * dev)2580c0d06caSMauro Carvalho Chehab static int cx231xx_init_audio_isoc(struct cx231xx *dev)
2590c0d06caSMauro Carvalho Chehab {
2600c0d06caSMauro Carvalho Chehab 	int i, errCode;
2610c0d06caSMauro Carvalho Chehab 	int sb_size;
2620c0d06caSMauro Carvalho Chehab 
263336fea92SMauro Carvalho Chehab 	dev_dbg(dev->dev,
264b7085c08SMauro Carvalho Chehab 		"%s: Starting ISO AUDIO transfers\n", __func__);
2650c0d06caSMauro Carvalho Chehab 
2660c0d06caSMauro Carvalho Chehab 	if (dev->state & DEV_DISCONNECTED)
2670c0d06caSMauro Carvalho Chehab 		return -ENODEV;
2680c0d06caSMauro Carvalho Chehab 
2690c0d06caSMauro Carvalho Chehab 	sb_size = CX231XX_ISO_NUM_AUDIO_PACKETS * dev->adev.max_pkt_size;
2700c0d06caSMauro Carvalho Chehab 
2710c0d06caSMauro Carvalho Chehab 	for (i = 0; i < CX231XX_AUDIO_BUFS; i++) {
2720c0d06caSMauro Carvalho Chehab 		struct urb *urb;
2730c0d06caSMauro Carvalho Chehab 		int j, k;
2740c0d06caSMauro Carvalho Chehab 
2750c0d06caSMauro Carvalho Chehab 		dev->adev.transfer_buffer[i] = kmalloc(sb_size, GFP_ATOMIC);
2760c0d06caSMauro Carvalho Chehab 		if (!dev->adev.transfer_buffer[i])
2770c0d06caSMauro Carvalho Chehab 			return -ENOMEM;
2780c0d06caSMauro Carvalho Chehab 
2790c0d06caSMauro Carvalho Chehab 		memset(dev->adev.transfer_buffer[i], 0x80, sb_size);
2800c0d06caSMauro Carvalho Chehab 		urb = usb_alloc_urb(CX231XX_ISO_NUM_AUDIO_PACKETS, GFP_ATOMIC);
2810c0d06caSMauro Carvalho Chehab 		if (!urb) {
2820c0d06caSMauro Carvalho Chehab 			for (j = 0; j < i; j++) {
2830c0d06caSMauro Carvalho Chehab 				usb_free_urb(dev->adev.urb[j]);
2840c0d06caSMauro Carvalho Chehab 				kfree(dev->adev.transfer_buffer[j]);
2850c0d06caSMauro Carvalho Chehab 			}
2860c0d06caSMauro Carvalho Chehab 			return -ENOMEM;
2870c0d06caSMauro Carvalho Chehab 		}
2880c0d06caSMauro Carvalho Chehab 
2890c0d06caSMauro Carvalho Chehab 		urb->dev = dev->udev;
2900c0d06caSMauro Carvalho Chehab 		urb->context = dev;
2910c0d06caSMauro Carvalho Chehab 		urb->pipe = usb_rcvisocpipe(dev->udev,
2920c0d06caSMauro Carvalho Chehab 						dev->adev.end_point_addr);
2930c0d06caSMauro Carvalho Chehab 		urb->transfer_flags = URB_ISO_ASAP;
2940c0d06caSMauro Carvalho Chehab 		urb->transfer_buffer = dev->adev.transfer_buffer[i];
2950c0d06caSMauro Carvalho Chehab 		urb->interval = 1;
2960c0d06caSMauro Carvalho Chehab 		urb->complete = cx231xx_audio_isocirq;
2970c0d06caSMauro Carvalho Chehab 		urb->number_of_packets = CX231XX_ISO_NUM_AUDIO_PACKETS;
2980c0d06caSMauro Carvalho Chehab 		urb->transfer_buffer_length = sb_size;
2990c0d06caSMauro Carvalho Chehab 
3000c0d06caSMauro Carvalho Chehab 		for (j = k = 0; j < CX231XX_ISO_NUM_AUDIO_PACKETS;
3010c0d06caSMauro Carvalho Chehab 			j++, k += dev->adev.max_pkt_size) {
3020c0d06caSMauro Carvalho Chehab 			urb->iso_frame_desc[j].offset = k;
3030c0d06caSMauro Carvalho Chehab 			urb->iso_frame_desc[j].length = dev->adev.max_pkt_size;
3040c0d06caSMauro Carvalho Chehab 		}
3050c0d06caSMauro Carvalho Chehab 		dev->adev.urb[i] = urb;
3060c0d06caSMauro Carvalho Chehab 	}
3070c0d06caSMauro Carvalho Chehab 
3080c0d06caSMauro Carvalho Chehab 	for (i = 0; i < CX231XX_AUDIO_BUFS; i++) {
3090c0d06caSMauro Carvalho Chehab 		errCode = usb_submit_urb(dev->adev.urb[i], GFP_ATOMIC);
3100c0d06caSMauro Carvalho Chehab 		if (errCode < 0) {
3110c0d06caSMauro Carvalho Chehab 			cx231xx_isoc_audio_deinit(dev);
3120c0d06caSMauro Carvalho Chehab 			return errCode;
3130c0d06caSMauro Carvalho Chehab 		}
3140c0d06caSMauro Carvalho Chehab 	}
3150c0d06caSMauro Carvalho Chehab 
3160c0d06caSMauro Carvalho Chehab 	return errCode;
3170c0d06caSMauro Carvalho Chehab }
3180c0d06caSMauro Carvalho Chehab 
cx231xx_init_audio_bulk(struct cx231xx * dev)3190c0d06caSMauro Carvalho Chehab static int cx231xx_init_audio_bulk(struct cx231xx *dev)
3200c0d06caSMauro Carvalho Chehab {
3210c0d06caSMauro Carvalho Chehab 	int i, errCode;
3220c0d06caSMauro Carvalho Chehab 	int sb_size;
3230c0d06caSMauro Carvalho Chehab 
324336fea92SMauro Carvalho Chehab 	dev_dbg(dev->dev,
325b7085c08SMauro Carvalho Chehab 		"%s: Starting BULK AUDIO transfers\n", __func__);
3260c0d06caSMauro Carvalho Chehab 
3270c0d06caSMauro Carvalho Chehab 	if (dev->state & DEV_DISCONNECTED)
3280c0d06caSMauro Carvalho Chehab 		return -ENODEV;
3290c0d06caSMauro Carvalho Chehab 
3300c0d06caSMauro Carvalho Chehab 	sb_size = CX231XX_NUM_AUDIO_PACKETS * dev->adev.max_pkt_size;
3310c0d06caSMauro Carvalho Chehab 
3320c0d06caSMauro Carvalho Chehab 	for (i = 0; i < CX231XX_AUDIO_BUFS; i++) {
3330c0d06caSMauro Carvalho Chehab 		struct urb *urb;
3340c0d06caSMauro Carvalho Chehab 		int j;
3350c0d06caSMauro Carvalho Chehab 
3360c0d06caSMauro Carvalho Chehab 		dev->adev.transfer_buffer[i] = kmalloc(sb_size, GFP_ATOMIC);
3370c0d06caSMauro Carvalho Chehab 		if (!dev->adev.transfer_buffer[i])
3380c0d06caSMauro Carvalho Chehab 			return -ENOMEM;
3390c0d06caSMauro Carvalho Chehab 
3400c0d06caSMauro Carvalho Chehab 		memset(dev->adev.transfer_buffer[i], 0x80, sb_size);
3410c0d06caSMauro Carvalho Chehab 		urb = usb_alloc_urb(CX231XX_NUM_AUDIO_PACKETS, GFP_ATOMIC);
3420c0d06caSMauro Carvalho Chehab 		if (!urb) {
3430c0d06caSMauro Carvalho Chehab 			for (j = 0; j < i; j++) {
3440c0d06caSMauro Carvalho Chehab 				usb_free_urb(dev->adev.urb[j]);
3450c0d06caSMauro Carvalho Chehab 				kfree(dev->adev.transfer_buffer[j]);
3460c0d06caSMauro Carvalho Chehab 			}
3470c0d06caSMauro Carvalho Chehab 			return -ENOMEM;
3480c0d06caSMauro Carvalho Chehab 		}
3490c0d06caSMauro Carvalho Chehab 
3500c0d06caSMauro Carvalho Chehab 		urb->dev = dev->udev;
3510c0d06caSMauro Carvalho Chehab 		urb->context = dev;
3520c0d06caSMauro Carvalho Chehab 		urb->pipe = usb_rcvbulkpipe(dev->udev,
3530c0d06caSMauro Carvalho Chehab 						dev->adev.end_point_addr);
3540c0d06caSMauro Carvalho Chehab 		urb->transfer_flags = 0;
3550c0d06caSMauro Carvalho Chehab 		urb->transfer_buffer = dev->adev.transfer_buffer[i];
3560c0d06caSMauro Carvalho Chehab 		urb->complete = cx231xx_audio_bulkirq;
3570c0d06caSMauro Carvalho Chehab 		urb->transfer_buffer_length = sb_size;
3580c0d06caSMauro Carvalho Chehab 
3590c0d06caSMauro Carvalho Chehab 		dev->adev.urb[i] = urb;
3600c0d06caSMauro Carvalho Chehab 
3610c0d06caSMauro Carvalho Chehab 	}
3620c0d06caSMauro Carvalho Chehab 
3630c0d06caSMauro Carvalho Chehab 	for (i = 0; i < CX231XX_AUDIO_BUFS; i++) {
3640c0d06caSMauro Carvalho Chehab 		errCode = usb_submit_urb(dev->adev.urb[i], GFP_ATOMIC);
3650c0d06caSMauro Carvalho Chehab 		if (errCode < 0) {
3660c0d06caSMauro Carvalho Chehab 			cx231xx_bulk_audio_deinit(dev);
3670c0d06caSMauro Carvalho Chehab 			return errCode;
3680c0d06caSMauro Carvalho Chehab 		}
3690c0d06caSMauro Carvalho Chehab 	}
3700c0d06caSMauro Carvalho Chehab 
3710c0d06caSMauro Carvalho Chehab 	return errCode;
3720c0d06caSMauro Carvalho Chehab }
3730c0d06caSMauro Carvalho Chehab 
37438c4f03aSBhumika Goyal static const struct snd_pcm_hardware snd_cx231xx_hw_capture = {
3750c0d06caSMauro Carvalho Chehab 	.info = SNDRV_PCM_INFO_BLOCK_TRANSFER	|
3760c0d06caSMauro Carvalho Chehab 	    SNDRV_PCM_INFO_MMAP			|
3770c0d06caSMauro Carvalho Chehab 	    SNDRV_PCM_INFO_INTERLEAVED		|
3780c0d06caSMauro Carvalho Chehab 	    SNDRV_PCM_INFO_MMAP_VALID,
3790c0d06caSMauro Carvalho Chehab 
3800c0d06caSMauro Carvalho Chehab 	.formats = SNDRV_PCM_FMTBIT_S16_LE,
3810c0d06caSMauro Carvalho Chehab 
3820c0d06caSMauro Carvalho Chehab 	.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT,
3830c0d06caSMauro Carvalho Chehab 
3840c0d06caSMauro Carvalho Chehab 	.rate_min = 48000,
3850c0d06caSMauro Carvalho Chehab 	.rate_max = 48000,
3860c0d06caSMauro Carvalho Chehab 	.channels_min = 2,
3870c0d06caSMauro Carvalho Chehab 	.channels_max = 2,
3880c0d06caSMauro Carvalho Chehab 	.buffer_bytes_max = 62720 * 8,	/* just about the value in usbaudio.c */
3890c0d06caSMauro Carvalho Chehab 	.period_bytes_min = 64,		/* 12544/2, */
3900c0d06caSMauro Carvalho Chehab 	.period_bytes_max = 12544,
3910c0d06caSMauro Carvalho Chehab 	.periods_min = 2,
3920c0d06caSMauro Carvalho Chehab 	.periods_max = 98,		/* 12544, */
3930c0d06caSMauro Carvalho Chehab };
3940c0d06caSMauro Carvalho Chehab 
snd_cx231xx_capture_open(struct snd_pcm_substream * substream)3950c0d06caSMauro Carvalho Chehab static int snd_cx231xx_capture_open(struct snd_pcm_substream *substream)
3960c0d06caSMauro Carvalho Chehab {
3970c0d06caSMauro Carvalho Chehab 	struct cx231xx *dev = snd_pcm_substream_chip(substream);
3980c0d06caSMauro Carvalho Chehab 	struct snd_pcm_runtime *runtime = substream->runtime;
3990c0d06caSMauro Carvalho Chehab 	int ret = 0;
4000c0d06caSMauro Carvalho Chehab 
401336fea92SMauro Carvalho Chehab 	dev_dbg(dev->dev,
40256d8a3b0SMauro Carvalho Chehab 		"opening device and trying to acquire exclusive lock\n");
4030c0d06caSMauro Carvalho Chehab 
4040c0d06caSMauro Carvalho Chehab 	if (dev->state & DEV_DISCONNECTED) {
405336fea92SMauro Carvalho Chehab 		dev_err(dev->dev,
406b7085c08SMauro Carvalho Chehab 			"Can't open. the device was removed.\n");
4070c0d06caSMauro Carvalho Chehab 		return -ENODEV;
4080c0d06caSMauro Carvalho Chehab 	}
4090c0d06caSMauro Carvalho Chehab 
4100c0d06caSMauro Carvalho Chehab 	/* set alternate setting for audio interface */
4110c0d06caSMauro Carvalho Chehab 	/* 1 - 48000 samples per sec */
4120c0d06caSMauro Carvalho Chehab 	mutex_lock(&dev->lock);
4130c0d06caSMauro Carvalho Chehab 	if (dev->USE_ISO)
4140c0d06caSMauro Carvalho Chehab 		ret = cx231xx_set_alt_setting(dev, INDEX_AUDIO, 1);
4150c0d06caSMauro Carvalho Chehab 	else
4160c0d06caSMauro Carvalho Chehab 		ret = cx231xx_set_alt_setting(dev, INDEX_AUDIO, 0);
4170c0d06caSMauro Carvalho Chehab 	mutex_unlock(&dev->lock);
4180c0d06caSMauro Carvalho Chehab 	if (ret < 0) {
419336fea92SMauro Carvalho Chehab 		dev_err(dev->dev,
420b7085c08SMauro Carvalho Chehab 			"failed to set alternate setting !\n");
4210c0d06caSMauro Carvalho Chehab 
4220c0d06caSMauro Carvalho Chehab 		return ret;
4230c0d06caSMauro Carvalho Chehab 	}
4240c0d06caSMauro Carvalho Chehab 
4250c0d06caSMauro Carvalho Chehab 	runtime->hw = snd_cx231xx_hw_capture;
4260c0d06caSMauro Carvalho Chehab 
4270c0d06caSMauro Carvalho Chehab 	mutex_lock(&dev->lock);
4280c0d06caSMauro Carvalho Chehab 	/* inform hardware to start streaming */
4290c0d06caSMauro Carvalho Chehab 	ret = cx231xx_capture_start(dev, 1, Audio);
4300c0d06caSMauro Carvalho Chehab 
4310c0d06caSMauro Carvalho Chehab 	dev->adev.users++;
4320c0d06caSMauro Carvalho Chehab 	mutex_unlock(&dev->lock);
4330c0d06caSMauro Carvalho Chehab 
4340c0d06caSMauro Carvalho Chehab 	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
4350c0d06caSMauro Carvalho Chehab 	dev->adev.capture_pcm_substream = substream;
4360c0d06caSMauro Carvalho Chehab 	runtime->private_data = dev;
4370c0d06caSMauro Carvalho Chehab 
4380c0d06caSMauro Carvalho Chehab 	return 0;
4390c0d06caSMauro Carvalho Chehab }
4400c0d06caSMauro Carvalho Chehab 
snd_cx231xx_pcm_close(struct snd_pcm_substream * substream)4410c0d06caSMauro Carvalho Chehab static int snd_cx231xx_pcm_close(struct snd_pcm_substream *substream)
4420c0d06caSMauro Carvalho Chehab {
4430c0d06caSMauro Carvalho Chehab 	int ret;
4440c0d06caSMauro Carvalho Chehab 	struct cx231xx *dev = snd_pcm_substream_chip(substream);
4450c0d06caSMauro Carvalho Chehab 
446336fea92SMauro Carvalho Chehab 	dev_dbg(dev->dev, "closing device\n");
4470c0d06caSMauro Carvalho Chehab 
4480c0d06caSMauro Carvalho Chehab 	/* inform hardware to stop streaming */
4490c0d06caSMauro Carvalho Chehab 	mutex_lock(&dev->lock);
4500c0d06caSMauro Carvalho Chehab 	ret = cx231xx_capture_start(dev, 0, Audio);
4510c0d06caSMauro Carvalho Chehab 
4520c0d06caSMauro Carvalho Chehab 	/* set alternate setting for audio interface */
4530c0d06caSMauro Carvalho Chehab 	/* 1 - 48000 samples per sec */
4540c0d06caSMauro Carvalho Chehab 	ret = cx231xx_set_alt_setting(dev, INDEX_AUDIO, 0);
4550c0d06caSMauro Carvalho Chehab 	if (ret < 0) {
456336fea92SMauro Carvalho Chehab 		dev_err(dev->dev,
457b7085c08SMauro Carvalho Chehab 			"failed to set alternate setting !\n");
4580c0d06caSMauro Carvalho Chehab 
4590c0d06caSMauro Carvalho Chehab 		mutex_unlock(&dev->lock);
4600c0d06caSMauro Carvalho Chehab 		return ret;
4610c0d06caSMauro Carvalho Chehab 	}
4620c0d06caSMauro Carvalho Chehab 
4630c0d06caSMauro Carvalho Chehab 	dev->adev.users--;
4640c0d06caSMauro Carvalho Chehab 	mutex_unlock(&dev->lock);
4650c0d06caSMauro Carvalho Chehab 
4660c0d06caSMauro Carvalho Chehab 	if (dev->adev.users == 0 && dev->adev.shutdown == 1) {
467336fea92SMauro Carvalho Chehab 		dev_dbg(dev->dev, "audio users: %d\n", dev->adev.users);
468336fea92SMauro Carvalho Chehab 		dev_dbg(dev->dev, "disabling audio stream!\n");
4690c0d06caSMauro Carvalho Chehab 		dev->adev.shutdown = 0;
470336fea92SMauro Carvalho Chehab 		dev_dbg(dev->dev, "released lock\n");
4710c0d06caSMauro Carvalho Chehab 		if (atomic_read(&dev->stream_started) > 0) {
4720c0d06caSMauro Carvalho Chehab 			atomic_set(&dev->stream_started, 0);
4730c0d06caSMauro Carvalho Chehab 			schedule_work(&dev->wq_trigger);
4740c0d06caSMauro Carvalho Chehab 		}
4750c0d06caSMauro Carvalho Chehab 	}
4760c0d06caSMauro Carvalho Chehab 	return 0;
4770c0d06caSMauro Carvalho Chehab }
4780c0d06caSMauro Carvalho Chehab 
snd_cx231xx_prepare(struct snd_pcm_substream * substream)4790c0d06caSMauro Carvalho Chehab static int snd_cx231xx_prepare(struct snd_pcm_substream *substream)
4800c0d06caSMauro Carvalho Chehab {
4810c0d06caSMauro Carvalho Chehab 	struct cx231xx *dev = snd_pcm_substream_chip(substream);
4820c0d06caSMauro Carvalho Chehab 
4830c0d06caSMauro Carvalho Chehab 	dev->adev.hwptr_done_capture = 0;
4840c0d06caSMauro Carvalho Chehab 	dev->adev.capture_transfer_done = 0;
4850c0d06caSMauro Carvalho Chehab 
4860c0d06caSMauro Carvalho Chehab 	return 0;
4870c0d06caSMauro Carvalho Chehab }
4880c0d06caSMauro Carvalho Chehab 
audio_trigger(struct work_struct * work)4890c0d06caSMauro Carvalho Chehab static void audio_trigger(struct work_struct *work)
4900c0d06caSMauro Carvalho Chehab {
4910c0d06caSMauro Carvalho Chehab 	struct cx231xx *dev = container_of(work, struct cx231xx, wq_trigger);
4920c0d06caSMauro Carvalho Chehab 
4930c0d06caSMauro Carvalho Chehab 	if (atomic_read(&dev->stream_started)) {
494336fea92SMauro Carvalho Chehab 		dev_dbg(dev->dev, "starting capture");
4950c0d06caSMauro Carvalho Chehab 		if (is_fw_load(dev) == 0)
4960c0d06caSMauro Carvalho Chehab 			cx25840_call(dev, core, load_fw);
4970c0d06caSMauro Carvalho Chehab 		if (dev->USE_ISO)
4980c0d06caSMauro Carvalho Chehab 			cx231xx_init_audio_isoc(dev);
4990c0d06caSMauro Carvalho Chehab 		else
5000c0d06caSMauro Carvalho Chehab 			cx231xx_init_audio_bulk(dev);
5010c0d06caSMauro Carvalho Chehab 	} else {
502336fea92SMauro Carvalho Chehab 		dev_dbg(dev->dev, "stopping capture");
5030c0d06caSMauro Carvalho Chehab 		cx231xx_isoc_audio_deinit(dev);
5040c0d06caSMauro Carvalho Chehab 	}
5050c0d06caSMauro Carvalho Chehab }
5060c0d06caSMauro Carvalho Chehab 
snd_cx231xx_capture_trigger(struct snd_pcm_substream * substream,int cmd)5070c0d06caSMauro Carvalho Chehab static int snd_cx231xx_capture_trigger(struct snd_pcm_substream *substream,
5080c0d06caSMauro Carvalho Chehab 				       int cmd)
5090c0d06caSMauro Carvalho Chehab {
5100c0d06caSMauro Carvalho Chehab 	struct cx231xx *dev = snd_pcm_substream_chip(substream);
5110c0d06caSMauro Carvalho Chehab 	int retval = 0;
5120c0d06caSMauro Carvalho Chehab 
5130c0d06caSMauro Carvalho Chehab 	if (dev->state & DEV_DISCONNECTED)
5140c0d06caSMauro Carvalho Chehab 		return -ENODEV;
5150c0d06caSMauro Carvalho Chehab 
5160c0d06caSMauro Carvalho Chehab 	spin_lock(&dev->adev.slock);
5170c0d06caSMauro Carvalho Chehab 	switch (cmd) {
5180c0d06caSMauro Carvalho Chehab 	case SNDRV_PCM_TRIGGER_START:
5190c0d06caSMauro Carvalho Chehab 		atomic_set(&dev->stream_started, 1);
5200c0d06caSMauro Carvalho Chehab 		break;
5210c0d06caSMauro Carvalho Chehab 	case SNDRV_PCM_TRIGGER_STOP:
5220c0d06caSMauro Carvalho Chehab 		atomic_set(&dev->stream_started, 0);
5230c0d06caSMauro Carvalho Chehab 		break;
5240c0d06caSMauro Carvalho Chehab 	default:
5250c0d06caSMauro Carvalho Chehab 		retval = -EINVAL;
5260c0d06caSMauro Carvalho Chehab 		break;
5270c0d06caSMauro Carvalho Chehab 	}
5280c0d06caSMauro Carvalho Chehab 	spin_unlock(&dev->adev.slock);
5290c0d06caSMauro Carvalho Chehab 
5300c0d06caSMauro Carvalho Chehab 	schedule_work(&dev->wq_trigger);
5310c0d06caSMauro Carvalho Chehab 
5320c0d06caSMauro Carvalho Chehab 	return retval;
5330c0d06caSMauro Carvalho Chehab }
5340c0d06caSMauro Carvalho Chehab 
snd_cx231xx_capture_pointer(struct snd_pcm_substream * substream)5350c0d06caSMauro Carvalho Chehab static snd_pcm_uframes_t snd_cx231xx_capture_pointer(struct snd_pcm_substream
5360c0d06caSMauro Carvalho Chehab 						     *substream)
5370c0d06caSMauro Carvalho Chehab {
5380c0d06caSMauro Carvalho Chehab 	struct cx231xx *dev;
5390c0d06caSMauro Carvalho Chehab 	unsigned long flags;
5400c0d06caSMauro Carvalho Chehab 	snd_pcm_uframes_t hwptr_done;
5410c0d06caSMauro Carvalho Chehab 
5420c0d06caSMauro Carvalho Chehab 	dev = snd_pcm_substream_chip(substream);
5430c0d06caSMauro Carvalho Chehab 
5440c0d06caSMauro Carvalho Chehab 	spin_lock_irqsave(&dev->adev.slock, flags);
5450c0d06caSMauro Carvalho Chehab 	hwptr_done = dev->adev.hwptr_done_capture;
5460c0d06caSMauro Carvalho Chehab 	spin_unlock_irqrestore(&dev->adev.slock, flags);
5470c0d06caSMauro Carvalho Chehab 
5480c0d06caSMauro Carvalho Chehab 	return hwptr_done;
5490c0d06caSMauro Carvalho Chehab }
5500c0d06caSMauro Carvalho Chehab 
55122511cfaSJulia Lawall static const struct snd_pcm_ops snd_cx231xx_pcm_capture = {
5520c0d06caSMauro Carvalho Chehab 	.open = snd_cx231xx_capture_open,
5530c0d06caSMauro Carvalho Chehab 	.close = snd_cx231xx_pcm_close,
5540c0d06caSMauro Carvalho Chehab 	.prepare = snd_cx231xx_prepare,
5550c0d06caSMauro Carvalho Chehab 	.trigger = snd_cx231xx_capture_trigger,
5560c0d06caSMauro Carvalho Chehab 	.pointer = snd_cx231xx_capture_pointer,
5570c0d06caSMauro Carvalho Chehab };
5580c0d06caSMauro Carvalho Chehab 
cx231xx_audio_init(struct cx231xx * dev)5590c0d06caSMauro Carvalho Chehab static int cx231xx_audio_init(struct cx231xx *dev)
5600c0d06caSMauro Carvalho Chehab {
5610c0d06caSMauro Carvalho Chehab 	struct cx231xx_audio *adev = &dev->adev;
5620c0d06caSMauro Carvalho Chehab 	struct snd_pcm *pcm;
5630c0d06caSMauro Carvalho Chehab 	struct snd_card *card;
5640c0d06caSMauro Carvalho Chehab 	static int devnr;
5650c0d06caSMauro Carvalho Chehab 	int err;
5660c0d06caSMauro Carvalho Chehab 	struct usb_interface *uif;
5670c0d06caSMauro Carvalho Chehab 	int i, isoc_pipe = 0;
5680c0d06caSMauro Carvalho Chehab 
5690c0d06caSMauro Carvalho Chehab 	if (dev->has_alsa_audio != 1) {
5700c0d06caSMauro Carvalho Chehab 		/* This device does not support the extension (in this case
5710c0d06caSMauro Carvalho Chehab 		   the device is expecting the snd-usb-audio module or
5720c0d06caSMauro Carvalho Chehab 		   doesn't have analog audio support at all) */
5730c0d06caSMauro Carvalho Chehab 		return 0;
5740c0d06caSMauro Carvalho Chehab 	}
5750c0d06caSMauro Carvalho Chehab 
576336fea92SMauro Carvalho Chehab 	dev_dbg(dev->dev,
577b7085c08SMauro Carvalho Chehab 		"probing for cx231xx non standard usbaudio\n");
5780c0d06caSMauro Carvalho Chehab 
579336fea92SMauro Carvalho Chehab 	err = snd_card_new(dev->dev, index[devnr], "Cx231xx Audio",
580e7356888STakashi Iwai 			   THIS_MODULE, 0, &card);
5810c0d06caSMauro Carvalho Chehab 	if (err < 0)
5820c0d06caSMauro Carvalho Chehab 		return err;
5830c0d06caSMauro Carvalho Chehab 
5840c0d06caSMauro Carvalho Chehab 	spin_lock_init(&adev->slock);
5850c0d06caSMauro Carvalho Chehab 	err = snd_pcm_new(card, "Cx231xx Audio", 0, 0, 1, &pcm);
586fff1abc4SJohan Hovold 	if (err < 0)
587fff1abc4SJohan Hovold 		goto err_free_card;
5880c0d06caSMauro Carvalho Chehab 
5890c0d06caSMauro Carvalho Chehab 	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
5900c0d06caSMauro Carvalho Chehab 			&snd_cx231xx_pcm_capture);
591355b9a0cSTakashi Iwai 	snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0);
5920c0d06caSMauro Carvalho Chehab 	pcm->info_flags = 0;
5930c0d06caSMauro Carvalho Chehab 	pcm->private_data = dev;
594cc1e6315SMauro Carvalho Chehab 	strscpy(pcm->name, "Conexant cx231xx Capture", sizeof(pcm->name));
595cc1e6315SMauro Carvalho Chehab 	strscpy(card->driver, "Cx231xx-Audio", sizeof(card->driver));
596cc1e6315SMauro Carvalho Chehab 	strscpy(card->shortname, "Cx231xx Audio", sizeof(card->shortname));
597cc1e6315SMauro Carvalho Chehab 	strscpy(card->longname, "Conexant cx231xx Audio", sizeof(card->longname));
5980c0d06caSMauro Carvalho Chehab 
5990c0d06caSMauro Carvalho Chehab 	INIT_WORK(&dev->wq_trigger, audio_trigger);
6000c0d06caSMauro Carvalho Chehab 
6010c0d06caSMauro Carvalho Chehab 	err = snd_card_register(card);
602fff1abc4SJohan Hovold 	if (err < 0)
603fff1abc4SJohan Hovold 		goto err_free_card;
604fff1abc4SJohan Hovold 
6050c0d06caSMauro Carvalho Chehab 	adev->sndcard = card;
6060c0d06caSMauro Carvalho Chehab 	adev->udev = dev->udev;
6070c0d06caSMauro Carvalho Chehab 
6080c0d06caSMauro Carvalho Chehab 	/* compute alternate max packet sizes for Audio */
6090c0d06caSMauro Carvalho Chehab 	uif =
6100c0d06caSMauro Carvalho Chehab 	    dev->udev->actconfig->interface[dev->current_pcb_config.
6110c0d06caSMauro Carvalho Chehab 					    hs_config_info[0].interface_info.
6120c0d06caSMauro Carvalho Chehab 					    audio_index + 1];
6130c0d06caSMauro Carvalho Chehab 
61465f92164SJohan Hovold 	if (uif->altsetting[0].desc.bNumEndpoints < isoc_pipe + 1) {
61565f92164SJohan Hovold 		err = -ENODEV;
61665f92164SJohan Hovold 		goto err_free_card;
61765f92164SJohan Hovold 	}
61865f92164SJohan Hovold 
6190c0d06caSMauro Carvalho Chehab 	adev->end_point_addr =
62069a11a32SHans Verkuil 	    uif->altsetting[0].endpoint[isoc_pipe].desc.
62169a11a32SHans Verkuil 			bEndpointAddress;
6220c0d06caSMauro Carvalho Chehab 
6230c0d06caSMauro Carvalho Chehab 	adev->num_alt = uif->num_altsetting;
624336fea92SMauro Carvalho Chehab 	dev_info(dev->dev,
625b7085c08SMauro Carvalho Chehab 		"audio EndPoint Addr 0x%x, Alternate settings: %i\n",
6260c0d06caSMauro Carvalho Chehab 		adev->end_point_addr, adev->num_alt);
6276da2ec56SKees Cook 	adev->alt_max_pkt_size = kmalloc_array(32, adev->num_alt, GFP_KERNEL);
628fff1abc4SJohan Hovold 	if (!adev->alt_max_pkt_size) {
629fff1abc4SJohan Hovold 		err = -ENOMEM;
630fff1abc4SJohan Hovold 		goto err_free_card;
631fff1abc4SJohan Hovold 	}
6320c0d06caSMauro Carvalho Chehab 
6330c0d06caSMauro Carvalho Chehab 	for (i = 0; i < adev->num_alt; i++) {
63465f92164SJohan Hovold 		u16 tmp;
63565f92164SJohan Hovold 
63665f92164SJohan Hovold 		if (uif->altsetting[i].desc.bNumEndpoints < isoc_pipe + 1) {
63765f92164SJohan Hovold 			err = -ENODEV;
63865f92164SJohan Hovold 			goto err_free_pkt_size;
63965f92164SJohan Hovold 		}
64065f92164SJohan Hovold 
64165f92164SJohan Hovold 		tmp = le16_to_cpu(uif->altsetting[i].endpoint[isoc_pipe].desc.
6420c0d06caSMauro Carvalho Chehab 				wMaxPacketSize);
6430c0d06caSMauro Carvalho Chehab 		adev->alt_max_pkt_size[i] =
6440c0d06caSMauro Carvalho Chehab 		    (tmp & 0x07ff) * (((tmp & 0x1800) >> 11) + 1);
645336fea92SMauro Carvalho Chehab 		dev_dbg(dev->dev,
646b7085c08SMauro Carvalho Chehab 			"audio alternate setting %i, max size= %i\n", i,
6470c0d06caSMauro Carvalho Chehab 			adev->alt_max_pkt_size[i]);
6480c0d06caSMauro Carvalho Chehab 	}
6490c0d06caSMauro Carvalho Chehab 
6500c0d06caSMauro Carvalho Chehab 	return 0;
651fff1abc4SJohan Hovold 
65265f92164SJohan Hovold err_free_pkt_size:
65365f92164SJohan Hovold 	kfree(adev->alt_max_pkt_size);
654fff1abc4SJohan Hovold err_free_card:
655fff1abc4SJohan Hovold 	snd_card_free(card);
656fff1abc4SJohan Hovold 
657fff1abc4SJohan Hovold 	return err;
6580c0d06caSMauro Carvalho Chehab }
6590c0d06caSMauro Carvalho Chehab 
cx231xx_audio_fini(struct cx231xx * dev)6600c0d06caSMauro Carvalho Chehab static int cx231xx_audio_fini(struct cx231xx *dev)
6610c0d06caSMauro Carvalho Chehab {
6620c0d06caSMauro Carvalho Chehab 	if (dev == NULL)
6630c0d06caSMauro Carvalho Chehab 		return 0;
6640c0d06caSMauro Carvalho Chehab 
6650c0d06caSMauro Carvalho Chehab 	if (dev->has_alsa_audio != 1) {
6660c0d06caSMauro Carvalho Chehab 		/* This device does not support the extension (in this case
6670c0d06caSMauro Carvalho Chehab 		   the device is expecting the snd-usb-audio module or
6680c0d06caSMauro Carvalho Chehab 		   doesn't have analog audio support at all) */
6690c0d06caSMauro Carvalho Chehab 		return 0;
6700c0d06caSMauro Carvalho Chehab 	}
6710c0d06caSMauro Carvalho Chehab 
6720c0d06caSMauro Carvalho Chehab 	if (dev->adev.sndcard) {
673*88c66084SZebediah Figura 		snd_card_free_when_closed(dev->adev.sndcard);
6740c0d06caSMauro Carvalho Chehab 		kfree(dev->adev.alt_max_pkt_size);
6750c0d06caSMauro Carvalho Chehab 		dev->adev.sndcard = NULL;
6760c0d06caSMauro Carvalho Chehab 	}
6770c0d06caSMauro Carvalho Chehab 
6780c0d06caSMauro Carvalho Chehab 	return 0;
6790c0d06caSMauro Carvalho Chehab }
6800c0d06caSMauro Carvalho Chehab 
6810c0d06caSMauro Carvalho Chehab static struct cx231xx_ops audio_ops = {
6820c0d06caSMauro Carvalho Chehab 	.id = CX231XX_AUDIO,
6830c0d06caSMauro Carvalho Chehab 	.name = "Cx231xx Audio Extension",
6840c0d06caSMauro Carvalho Chehab 	.init = cx231xx_audio_init,
6850c0d06caSMauro Carvalho Chehab 	.fini = cx231xx_audio_fini,
6860c0d06caSMauro Carvalho Chehab };
6870c0d06caSMauro Carvalho Chehab 
cx231xx_alsa_register(void)6880c0d06caSMauro Carvalho Chehab static int __init cx231xx_alsa_register(void)
6890c0d06caSMauro Carvalho Chehab {
6900c0d06caSMauro Carvalho Chehab 	return cx231xx_register_extension(&audio_ops);
6910c0d06caSMauro Carvalho Chehab }
6920c0d06caSMauro Carvalho Chehab 
cx231xx_alsa_unregister(void)6930c0d06caSMauro Carvalho Chehab static void __exit cx231xx_alsa_unregister(void)
6940c0d06caSMauro Carvalho Chehab {
6950c0d06caSMauro Carvalho Chehab 	cx231xx_unregister_extension(&audio_ops);
6960c0d06caSMauro Carvalho Chehab }
6970c0d06caSMauro Carvalho Chehab 
6980c0d06caSMauro Carvalho Chehab MODULE_LICENSE("GPL");
6990c0d06caSMauro Carvalho Chehab MODULE_AUTHOR("Srinivasa Deevi <srinivasa.deevi@conexant.com>");
7000c0d06caSMauro Carvalho Chehab MODULE_DESCRIPTION("Cx231xx Audio driver");
7010c0d06caSMauro Carvalho Chehab 
7020c0d06caSMauro Carvalho Chehab module_init(cx231xx_alsa_register);
7030c0d06caSMauro Carvalho Chehab module_exit(cx231xx_alsa_unregister);
704