Linux Audio

Check our new training course

Loading...
  1#include <linux/kernel.h>
  2#include <linux/usb.h>
  3#include <linux/init.h>
  4#include <linux/sound.h>
  5#include <linux/spinlock.h>
  6#include <linux/soundcard.h>
  7#include <linux/vmalloc.h>
  8#include <linux/proc_fs.h>
  9#include <linux/module.h>
 10#include <linux/gfp.h>
 11#include <sound/core.h>
 12#include <sound/pcm.h>
 13#include <sound/pcm_params.h>
 14#include <sound/info.h>
 15#include <sound/initval.h>
 16#include <sound/control.h>
 17#include <media/v4l2-common.h>
 18#include "pd-common.h"
 19#include "vendorcmds.h"
 20
 21static void complete_handler_audio(struct urb *urb);
 22#define AUDIO_EP	(0x83)
 23#define AUDIO_BUF_SIZE	(512)
 24#define PERIOD_SIZE	(1024 * 8)
 25#define PERIOD_MIN	(4)
 26#define PERIOD_MAX 	PERIOD_MIN
 27
 28static struct snd_pcm_hardware snd_pd_hw_capture = {
 29	.info = SNDRV_PCM_INFO_BLOCK_TRANSFER |
 30		SNDRV_PCM_INFO_MMAP           |
 31		SNDRV_PCM_INFO_INTERLEAVED |
 32		SNDRV_PCM_INFO_MMAP_VALID,
 33
 34	.formats = SNDRV_PCM_FMTBIT_S16_LE,
 35	.rates = SNDRV_PCM_RATE_48000,
 36
 37	.rate_min = 48000,
 38	.rate_max = 48000,
 39	.channels_min = 2,
 40	.channels_max = 2,
 41	.buffer_bytes_max = PERIOD_SIZE * PERIOD_MIN,
 42	.period_bytes_min = PERIOD_SIZE,
 43	.period_bytes_max = PERIOD_SIZE,
 44	.periods_min = PERIOD_MIN,
 45	.periods_max = PERIOD_MAX,
 46	/*
 47	.buffer_bytes_max = 62720 * 8,
 48	.period_bytes_min = 64,
 49	.period_bytes_max = 12544,
 50	.periods_min = 2,
 51	.periods_max = 98
 52	*/
 53};
 54
 55static int snd_pd_capture_open(struct snd_pcm_substream *substream)
 56{
 57	struct poseidon *p = snd_pcm_substream_chip(substream);
 58	struct poseidon_audio *pa = &p->audio;
 59	struct snd_pcm_runtime *runtime = substream->runtime;
 60
 61	if (!p)
 62		return -ENODEV;
 63	pa->users++;
 64	pa->card_close 		= 0;
 65	pa->capture_pcm_substream	= substream;
 66	runtime->private_data		= p;
 67
 68	runtime->hw = snd_pd_hw_capture;
 69	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
 70	usb_autopm_get_interface(p->interface);
 71	kref_get(&p->kref);
 72	return 0;
 73}
 74
 75static int snd_pd_pcm_close(struct snd_pcm_substream *substream)
 76{
 77	struct poseidon *p = snd_pcm_substream_chip(substream);
 78	struct poseidon_audio *pa = &p->audio;
 79
 80	pa->users--;
 81	pa->card_close 		= 1;
 82	usb_autopm_put_interface(p->interface);
 83	kref_put(&p->kref, poseidon_delete);
 84	return 0;
 85}
 86
 87static int snd_pd_hw_capture_params(struct snd_pcm_substream *substream,
 88					struct snd_pcm_hw_params *hw_params)
 89{
 90	struct snd_pcm_runtime *runtime = substream->runtime;
 91	unsigned int size;
 92
 93	size = params_buffer_bytes(hw_params);
 94	if (runtime->dma_area) {
 95		if (runtime->dma_bytes > size)
 96			return 0;
 97		vfree(runtime->dma_area);
 98	}
 99	runtime->dma_area = vmalloc(size);
100	if (!runtime->dma_area)
101		return -ENOMEM;
102	else
103		runtime->dma_bytes = size;
104	return 0;
105}
106
107static int audio_buf_free(struct poseidon *p)
108{
109	struct poseidon_audio *pa = &p->audio;
110	int i;
111
112	for (i = 0; i < AUDIO_BUFS; i++)
113		if (pa->urb_array[i])
114			usb_kill_urb(pa->urb_array[i]);
115	free_all_urb_generic(pa->urb_array, AUDIO_BUFS);
116	logpm();
117	return 0;
118}
119
120static int snd_pd_hw_capture_free(struct snd_pcm_substream *substream)
121{
122	struct poseidon *p = snd_pcm_substream_chip(substream);
123
124	logpm();
125	audio_buf_free(p);
126	return 0;
127}
128
129static int snd_pd_prepare(struct snd_pcm_substream *substream)
130{
131	return 0;
132}
133
134#define AUDIO_TRAILER_SIZE	(16)
135static inline void handle_audio_data(struct urb *urb, int *period_elapsed)
136{
137	struct poseidon_audio *pa = urb->context;
138	struct snd_pcm_runtime *runtime = pa->capture_pcm_substream->runtime;
139
140	int stride	= runtime->frame_bits >> 3;
141	int len		= urb->actual_length / stride;
142	unsigned char *cp	= urb->transfer_buffer;
143	unsigned int oldptr	= pa->rcv_position;
144
145	if (urb->actual_length == AUDIO_BUF_SIZE - 4)
146		len -= (AUDIO_TRAILER_SIZE / stride);
147
148	/* do the copy */
149	if (oldptr + len >= runtime->buffer_size) {
150		unsigned int cnt = runtime->buffer_size - oldptr;
151
152		memcpy(runtime->dma_area + oldptr * stride, cp, cnt * stride);
153		memcpy(runtime->dma_area, (cp + cnt * stride),
154					(len * stride - cnt * stride));
155	} else
156		memcpy(runtime->dma_area + oldptr * stride, cp, len * stride);
157
158	/* update the statas */
159	snd_pcm_stream_lock(pa->capture_pcm_substream);
160	pa->rcv_position	+= len;
161	if (pa->rcv_position >= runtime->buffer_size)
162		pa->rcv_position -= runtime->buffer_size;
163
164	pa->copied_position += (len);
165	if (pa->copied_position >= runtime->period_size) {
166		pa->copied_position -= runtime->period_size;
167		*period_elapsed = 1;
168	}
169	snd_pcm_stream_unlock(pa->capture_pcm_substream);
170}
171
172static void complete_handler_audio(struct urb *urb)
173{
174	struct poseidon_audio *pa = urb->context;
175	struct snd_pcm_substream *substream = pa->capture_pcm_substream;
176	int    period_elapsed = 0;
177	int    ret;
178
179	if (1 == pa->card_close || pa->capture_stream != STREAM_ON)
180		return;
181
182	if (urb->status != 0) {
183		/*if (urb->status == -ESHUTDOWN)*/
184			return;
185	}
186
187	if (substream) {
188		if (urb->actual_length) {
189			handle_audio_data(urb, &period_elapsed);
190			if (period_elapsed)
191				snd_pcm_period_elapsed(substream);
192		}
193	}
194
195	ret = usb_submit_urb(urb, GFP_ATOMIC);
196	if (ret < 0)
197		log("audio urb failed (errcod = %i)", ret);
198	return;
199}
200
201static int fire_audio_urb(struct poseidon *p)
202{
203	int i, ret = 0;
204	struct poseidon_audio *pa = &p->audio;
205
206	alloc_bulk_urbs_generic(pa->urb_array, AUDIO_BUFS,
207			p->udev, AUDIO_EP,
208			AUDIO_BUF_SIZE, GFP_ATOMIC,
209			complete_handler_audio, pa);
210
211	for (i = 0; i < AUDIO_BUFS; i++) {
212		ret = usb_submit_urb(pa->urb_array[i], GFP_KERNEL);
213		if (ret)
214			log("urb err : %d", ret);
215	}
216	log();
217	return ret;
218}
219
220static int snd_pd_capture_trigger(struct snd_pcm_substream *substream, int cmd)
221{
222	struct poseidon *p = snd_pcm_substream_chip(substream);
223	struct poseidon_audio *pa = &p->audio;
224
225	if (debug_mode)
226		log("cmd %d, audio stat : %d\n", cmd, pa->capture_stream);
227
228	switch (cmd) {
229	case SNDRV_PCM_TRIGGER_RESUME:
230	case SNDRV_PCM_TRIGGER_START:
231		if (pa->capture_stream == STREAM_ON)
232			return 0;
233
234		pa->rcv_position = pa->copied_position = 0;
235		pa->capture_stream = STREAM_ON;
236
237		if (in_hibernation(p))
238			return 0;
239		fire_audio_urb(p);
240		return 0;
241
242	case SNDRV_PCM_TRIGGER_SUSPEND:
243		pa->capture_stream = STREAM_SUSPEND;
244		return 0;
245	case SNDRV_PCM_TRIGGER_STOP:
246		pa->capture_stream = STREAM_OFF;
247		return 0;
248	default:
249		return -EINVAL;
250	}
251}
252
253static snd_pcm_uframes_t
254snd_pd_capture_pointer(struct snd_pcm_substream *substream)
255{
256	struct poseidon *p = snd_pcm_substream_chip(substream);
257	struct poseidon_audio *pa = &p->audio;
258	return pa->rcv_position;
259}
260
261static struct page *snd_pcm_pd_get_page(struct snd_pcm_substream *subs,
262					     unsigned long offset)
263{
264	void *pageptr = subs->runtime->dma_area + offset;
265	return vmalloc_to_page(pageptr);
266}
267
268static struct snd_pcm_ops pcm_capture_ops = {
269	.open      = snd_pd_capture_open,
270	.close     = snd_pd_pcm_close,
271	.ioctl     = snd_pcm_lib_ioctl,
272	.hw_params = snd_pd_hw_capture_params,
273	.hw_free   = snd_pd_hw_capture_free,
274	.prepare   = snd_pd_prepare,
275	.trigger   = snd_pd_capture_trigger,
276	.pointer   = snd_pd_capture_pointer,
277	.page      = snd_pcm_pd_get_page,
278};
279
280#ifdef CONFIG_PM
281int pm_alsa_suspend(struct poseidon *p)
282{
283	logpm(p);
284	audio_buf_free(p);
285	return 0;
286}
287
288int pm_alsa_resume(struct poseidon *p)
289{
290	logpm(p);
291	fire_audio_urb(p);
292	return 0;
293}
294#endif
295
296int poseidon_audio_init(struct poseidon *p)
297{
298	struct poseidon_audio *pa = &p->audio;
299	struct snd_card *card;
300	struct snd_pcm *pcm;
301	int ret;
302
303	ret = snd_card_create(-1, "Telegent", THIS_MODULE, 0, &card);
304	if (ret != 0)
305		return ret;
306
307	ret = snd_pcm_new(card, "poseidon audio", 0, 0, 1, &pcm);
308	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops);
309	pcm->info_flags   = 0;
310	pcm->private_data = p;
311	strcpy(pcm->name, "poseidon audio capture");
312
313	strcpy(card->driver, "ALSA driver");
314	strcpy(card->shortname, "poseidon Audio");
315	strcpy(card->longname, "poseidon ALSA Audio");
316
317	if (snd_card_register(card)) {
318		snd_card_free(card);
319		return -ENOMEM;
320	}
321	pa->card = card;
322	return 0;
323}
324
325int poseidon_audio_free(struct poseidon *p)
326{
327	struct poseidon_audio *pa = &p->audio;
328
329	if (pa->card)
330		snd_card_free(pa->card);
331	return 0;
332}