usbusx2yaudio.c

来自「Linux Kernel 2.6.9 for OMAP1710」· C语言 代码 · 共 1,028 行 · 第 1/2 页

C
1,028
字号
/* *   US-428 AUDIO *   Copyright (c) 2002-2003 by Karsten Wiese  *   based on *   (Tentative) USB Audio Driver for ALSA * *   Main and PCM part * *   Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de> * *   Many codes borrowed from audio.c by  *	    Alan Cox (alan@lxorguk.ukuu.org.uk) *	    Thomas Sailer (sailer@ife.ee.ethz.ch) * * *   This program is free software; you can redistribute it and/or modify *   it under the terms of the GNU General Public License as published by *   the Free Software Foundation; either version 2 of the License, or *   (at your option) any later version. * *   This program is distributed in the hope that it will be useful, *   but WITHOUT ANY WARRANTY; without even the implied warranty of *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the *   GNU General Public License for more details. * *   You should have received a copy of the GNU General Public License *   along with this program; if not, write to the Free Software *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA */#include <sound/driver.h>#include <linux/interrupt.h>#include <linux/usb.h>#include <sound/core.h>#include <sound/info.h>#include <sound/pcm.h>#include <sound/pcm_params.h>#include "usx2y.h"#include "usbusx2y.h"struct snd_usX2Y_substream {	usX2Ydev_t	*usX2Y;	snd_pcm_substream_t *pcm_substream;	unsigned char		endpoint;			unsigned int		datapipe;  		/* the data i/o pipe */	unsigned int		maxpacksize;		/* max packet size in bytes */	char			prepared,				running,				stalled;	int			hwptr;			/* free frame position in the buffer (only for playback) */	int			hwptr_done;		/* processed frame position in the buffer */	int			transfer_done;		/* processed frames since last period update */	struct urb		*urb[NRURBS];	/* data urb table */	int			next_urb_complete;	struct urb		*completed_urb;	char			*tmpbuf;			/* temporary buffer for playback */	volatile int		submitted_urbs;	wait_queue_head_t	wait_queue;};static int usX2Y_urb_capt_retire(snd_usX2Y_substream_t *subs){	struct urb	*urb = subs->completed_urb;	snd_pcm_runtime_t *runtime = subs->pcm_substream->runtime;	unsigned char	*cp;	int 		i, len, lens = 0, hwptr_done = subs->hwptr_done;	usX2Ydev_t	*usX2Y = subs->usX2Y;	for (i = 0; i < NRPACKS; i++) {		cp = (unsigned char*)urb->transfer_buffer + urb->iso_frame_desc[i].offset;		if (urb->iso_frame_desc[i].status) { /* active? hmm, skip this */			snd_printdd("activ frame status %i\n", urb->iso_frame_desc[i].status);			return urb->iso_frame_desc[i].status;		}		len = urb->iso_frame_desc[i].actual_length / usX2Y->stride;		if (! len) {			snd_printk("0 == len ERROR!\n");			continue;		}		/* copy a data chunk */		if ((hwptr_done + len) > runtime->buffer_size) {			int cnt = runtime->buffer_size - hwptr_done;			int blen = cnt * usX2Y->stride;			memcpy(runtime->dma_area + hwptr_done * usX2Y->stride, cp, blen);			memcpy(runtime->dma_area, cp + blen, len * usX2Y->stride - blen);		} else {			memcpy(runtime->dma_area + hwptr_done * usX2Y->stride, cp, len * usX2Y->stride);		}		lens += len;		if ((hwptr_done += len) >= runtime->buffer_size)			hwptr_done -= runtime->buffer_size;	}	subs->hwptr_done = hwptr_done;	subs->transfer_done += lens;	/* update the pointer, call callback if necessary */	if (subs->transfer_done >= runtime->period_size) {		subs->transfer_done -= runtime->period_size;		snd_pcm_period_elapsed(subs->pcm_substream);	}	return 0;}/* * prepare urb for playback data pipe * * we copy the data directly from the pcm buffer. * the current position to be copied is held in hwptr field. * since a urb can handle only a single linear buffer, if the total * transferred area overflows the buffer boundary, we cannot send * it directly from the buffer.  thus the data is once copied to * a temporary buffer and urb points to that. */static int usX2Y_urb_play_prepare(snd_usX2Y_substream_t *subs,				  struct urb *cap_urb,				  struct urb *urb){	int count, counts, pack;	usX2Ydev_t* usX2Y = subs->usX2Y;	snd_pcm_runtime_t *runtime = subs->pcm_substream->runtime;	count = 0;	for (pack = 0; pack < NRPACKS; pack++) {		/* calculate the size of a packet */		counts = cap_urb->iso_frame_desc[pack].actual_length / usX2Y->stride;		count += counts;		if (counts < 43 || counts > 50) {			snd_printk("should not be here with counts=%i\n", counts);			return -EPIPE;		}		/* set up descriptor */		urb->iso_frame_desc[pack].offset = pack ? urb->iso_frame_desc[pack - 1].offset + urb->iso_frame_desc[pack - 1].length : 0;		urb->iso_frame_desc[pack].length = counts * usX2Y->stride;	}	if (subs->hwptr + count > runtime->buffer_size) {		/* err, the transferred area goes over buffer boundary.		 * copy the data to the temp buffer.		 */		int len;		len = runtime->buffer_size - subs->hwptr;		urb->transfer_buffer = subs->tmpbuf;		memcpy(subs->tmpbuf, runtime->dma_area + subs->hwptr * usX2Y->stride, len * usX2Y->stride);		memcpy(subs->tmpbuf + len * usX2Y->stride, runtime->dma_area, (count - len) * usX2Y->stride);		subs->hwptr += count;		subs->hwptr -= runtime->buffer_size;	} else {		/* set the buffer pointer */		urb->transfer_buffer = runtime->dma_area + subs->hwptr * usX2Y->stride;		if ((subs->hwptr += count) >= runtime->buffer_size)			subs->hwptr -= runtime->buffer_size;				}	urb->transfer_buffer_length = count * usX2Y->stride;	return 0;}/* * process after playback data complete * * update the current position and call callback if a period is processed. */inline static int usX2Y_urb_play_retire(snd_usX2Y_substream_t *subs, struct urb *urb){	snd_pcm_runtime_t *runtime = subs->pcm_substream->runtime;	int		len = (urb->iso_frame_desc[0].actual_length#if NRPACKS > 1			       + urb->iso_frame_desc[1].actual_length#endif		               ) / subs->usX2Y->stride;	subs->transfer_done += len;	subs->hwptr_done +=  len;	if (subs->hwptr_done >= runtime->buffer_size)		subs->hwptr_done -= runtime->buffer_size;	if (subs->transfer_done >= runtime->period_size) {		subs->transfer_done -= runtime->period_size;		snd_pcm_period_elapsed(subs->pcm_substream);	}	return 0;}inline static int usX2Y_urb_submit(snd_usX2Y_substream_t *subs, struct urb *urb, int frame){	int err;	if (!urb)		return -ENODEV;	urb->start_frame = (frame + NRURBS*NRPACKS) & (1024 - 1);	urb->hcpriv = NULL;	urb->dev = subs->usX2Y->chip.dev; /* we need to set this at each time */	if ((err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {		snd_printk("%i\n", err);		return err;	} else {		subs->submitted_urbs++;		if (subs->next_urb_complete < 0) 			subs->next_urb_complete = 0;	}	return 0;}static inline int frame_distance(int from, int to){	int distance = to - from;	if (distance < -512)		distance += 1024;	else		if (distance > 511)			distance -= 1024;	return distance;}static void usX2Y_subs_set_next_urb_complete(snd_usX2Y_substream_t *subs){	int next_urb_complete = subs->next_urb_complete + 1;	int distance;	if (next_urb_complete >= NRURBS)		next_urb_complete = 0;	distance = frame_distance(subs->completed_urb->start_frame,				  subs->urb[next_urb_complete]->start_frame);	if (1 == distance) {		subs->next_urb_complete = next_urb_complete;	} else {		snd_printdd("distance %i not set_nuc %i %i %i \n", distance, subs->endpoint, next_urb_complete, subs->urb[next_urb_complete]->status);		subs->next_urb_complete = -1;	}}static inline void usX2Y_usbframe_complete(snd_usX2Y_substream_t *capsubs, snd_usX2Y_substream_t *playbacksubs, int frame){	{		struct urb *urb;		if ((urb = playbacksubs->completed_urb)) {			if (playbacksubs->prepared)				usX2Y_urb_play_retire(playbacksubs, urb);			usX2Y_subs_set_next_urb_complete(playbacksubs);		}		if (playbacksubs->running) {			if (NULL == urb)				urb = playbacksubs->urb[playbacksubs->next_urb_complete + 1];			if (urb && 0 == usX2Y_urb_play_prepare(playbacksubs,							       capsubs->completed_urb,							       urb)) {				if (usX2Y_urb_submit(playbacksubs, urb, frame) < 0)					return;			} else				snd_pcm_stop(playbacksubs->pcm_substream, SNDRV_PCM_STATE_XRUN);		}		playbacksubs->completed_urb = NULL;	}	if (capsubs->running)		usX2Y_urb_capt_retire(capsubs);	usX2Y_subs_set_next_urb_complete(capsubs);	if (capsubs->prepared)		usX2Y_urb_submit(capsubs, capsubs->completed_urb, frame);	capsubs->completed_urb = NULL;}static void usX2Y_clients_stop(snd_usX2Y_substream_t *subs){	usX2Ydev_t *usX2Y = subs->usX2Y;	int i;	for (i = 0; i < 4; i++) {		snd_usX2Y_substream_t *substream = usX2Y->substream[i];		if (substream && substream->running)			snd_pcm_stop(substream->pcm_substream, SNDRV_PCM_STATE_XRUN);	}}static void i_usX2Y_urb_complete(struct urb *urb, struct pt_regs *regs){	snd_usX2Y_substream_t *subs = (snd_usX2Y_substream_t*)urb->context;	subs->submitted_urbs--;	if (urb->status) {		snd_printk("ep=%i stalled with status=%i\n", subs->endpoint, urb->status);		subs->stalled = 1;		usX2Y_clients_stop(subs);		urb->status = 0;		return;	}	if (urb == subs->urb[subs->next_urb_complete]) {		subs->completed_urb = urb;	} else {		snd_printk("Sequence Error!(ep=%i;nuc=%i,frame=%i)\n",			   subs->endpoint, subs->next_urb_complete, urb->start_frame);		subs->stalled = 1;		usX2Y_clients_stop(subs);		return;	}	if (waitqueue_active(&subs->wait_queue))		wake_up(&subs->wait_queue);	{		snd_usX2Y_substream_t *capsubs = subs->usX2Y->substream[SNDRV_PCM_STREAM_CAPTURE],			*playbacksubs = subs->usX2Y->substream[SNDRV_PCM_STREAM_PLAYBACK];		if (capsubs->completed_urb &&		    (playbacksubs->completed_urb ||		     !playbacksubs->prepared ||		     (playbacksubs->prepared && (playbacksubs->next_urb_complete < 0 ||	// not started yet						 frame_distance(capsubs->completed_urb->start_frame,								playbacksubs->urb[playbacksubs->next_urb_complete]->start_frame)						 > 0 ||					// other expected later						 playbacksubs->stalled))))			usX2Y_usbframe_complete(capsubs, playbacksubs, urb->start_frame);	}}static int usX2Y_urbs_capt_start(snd_usX2Y_substream_t *subs){	int i, err;	for (i = 0; i < NRURBS; i++) {		unsigned long pack;		struct urb *urb = subs->urb[i];		urb->dev = subs->usX2Y->chip.dev;		urb->transfer_flags = URB_ISO_ASAP;		for (pack = 0; pack < NRPACKS; pack++) {			urb->iso_frame_desc[pack].offset = subs->maxpacksize * pack;			urb->iso_frame_desc[pack].length = subs->maxpacksize;		}		urb->transfer_buffer_length = subs->maxpacksize * NRPACKS; 		if ((err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {			snd_printk (KERN_ERR "cannot submit datapipe for urb %d, err = %d\n", i, err);			return -EPIPE;		} else {			subs->submitted_urbs++;		}		urb->transfer_flags = 0;	}	subs->stalled = 0;	subs->next_urb_complete = 0;	subs->prepared = 1;	return 0;}/*  *  wait until all urbs are processed. */static int usX2Y_urbs_wait_clear(snd_usX2Y_substream_t *subs){	int timeout = HZ;	do {		if (0 == subs->submitted_urbs)			break;		set_current_state(TASK_UNINTERRUPTIBLE);		snd_printdd("snd_usX2Y_urbs_wait_clear waiting\n");		schedule_timeout(1);	} while (--timeout > 0);	if (subs->submitted_urbs)		snd_printk(KERN_ERR "timeout: still %d active urbs..\n", subs->submitted_urbs);	return 0;}/* * return the current pcm pointer.  just return the hwptr_done value. */static snd_pcm_uframes_t snd_usX2Y_pcm_pointer(snd_pcm_substream_t *substream){	snd_usX2Y_substream_t *subs = (snd_usX2Y_substream_t *)substream->runtime->private_data;	return subs->hwptr_done;}/* * start/stop substream */static int snd_usX2Y_pcm_trigger(snd_pcm_substream_t *substream, int cmd){	snd_usX2Y_substream_t *subs = (snd_usX2Y_substream_t *)substream->runtime->private_data;	switch (cmd) {	case SNDRV_PCM_TRIGGER_START:		snd_printdd("snd_usX2Y_pcm_trigger(START)\n");		if (subs->usX2Y->substream[SNDRV_PCM_STREAM_CAPTURE]->stalled)			return -EPIPE;		else			subs->running = 1;		break;	case SNDRV_PCM_TRIGGER_STOP:		snd_printdd("snd_usX2Y_pcm_trigger(STOP)\n");		subs->running = 0;		break;	default:		return -EINVAL;	}	return 0;}static void usX2Y_urb_release(struct urb** urb, int free_tb){	if (*urb) {		if (free_tb)			kfree((*urb)->transfer_buffer);		usb_free_urb(*urb);		*urb = NULL;	}}/* * release a substream */static void usX2Y_urbs_release(snd_usX2Y_substream_t *subs){	int i;	snd_printdd("snd_usX2Y_urbs_release() %i\n", subs->endpoint);	usX2Y_urbs_wait_clear(subs);	for (i = 0; i < NRURBS; i++)		usX2Y_urb_release(subs->urb + i, subs != subs->usX2Y->substream[SNDRV_PCM_STREAM_PLAYBACK]);	if (subs->tmpbuf) {		kfree(subs->tmpbuf);		subs->tmpbuf = NULL;	}}static void usX2Y_substream_prepare(snd_usX2Y_substream_t *subs){	snd_printdd("usX2Y_substream_prepare() ep=%i urb0=%p urb1=%p\n", subs->endpoint, subs->urb[0], subs->urb[1]);	/* reset the pointer */	subs->hwptr = 0;	subs->hwptr_done = 0;	subs->transfer_done = 0;}/* * initialize a substream's urbs */static int usX2Y_urbs_allocate(snd_usX2Y_substream_t *subs){	int i;	int is_playback = subs == subs->usX2Y->substream[SNDRV_PCM_STREAM_PLAYBACK];	struct usb_device *dev = subs->usX2Y->chip.dev;	snd_assert(!subs->prepared, return 0);	if (is_playback) {	/* allocate a temporary buffer for playback */		subs->datapipe = usb_sndisocpipe(dev, subs->endpoint);		subs->maxpacksize = dev->epmaxpacketout[subs->endpoint];		if (NULL == subs->tmpbuf) {			subs->tmpbuf = kcalloc(NRPACKS, subs->maxpacksize, GFP_KERNEL);			if (NULL == subs->tmpbuf) {				snd_printk(KERN_ERR "cannot malloc tmpbuf\n");				return -ENOMEM;			}		}	} else {		subs->datapipe = usb_rcvisocpipe(dev, subs->endpoint);		subs->maxpacksize = dev->epmaxpacketin[subs->endpoint];	}	/* allocate and initialize data urbs */	for (i = 0; i < NRURBS; i++) {		struct urb** purb = subs->urb + i;		if (*purb)			continue;		*purb = usb_alloc_urb(NRPACKS, GFP_KERNEL);		if (NULL == *purb) {			usX2Y_urbs_release(subs);			return -ENOMEM;		}		if (!is_playback && !(*purb)->transfer_buffer) {			/* allocate a capture buffer per urb */			(*purb)->transfer_buffer = kmalloc(subs->maxpacksize*NRPACKS, GFP_KERNEL);			if (NULL == (*purb)->transfer_buffer) {				usX2Y_urbs_release(subs);				return -ENOMEM;			}		}		(*purb)->dev = dev;		(*purb)->pipe = subs->datapipe;		(*purb)->number_of_packets = NRPACKS;		(*purb)->context = subs;		(*purb)->interval = 1;		(*purb)->complete = snd_usb_complete_callback(i_usX2Y_urb_complete);	}	return 0;}static void i_usX2Y_04Int(struct urb* urb, struct pt_regs *regs){	usX2Ydev_t*	usX2Y = urb->context;		if (urb->status) {		snd_printk("snd_usX2Y_04Int() urb->status=%i\n", urb->status);		return;	}	if (0 == --usX2Y->US04->len)		wake_up(&usX2Y->In04WaitQueue);}/* * allocate a buffer, setup samplerate * * so far we use a physically linear buffer although packetize transfer * doesn't need a continuous area. * if sg buffer is supported on the later version of alsa, we'll follow

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?