| #include <linux/kernel.h> |
| #include <linux/usb.h> |
| #include <linux/init.h> |
| #include <linux/sound.h> |
| #include <linux/spinlock.h> |
| #include <linux/soundcard.h> |
| #include <linux/vmalloc.h> |
| #include <linux/proc_fs.h> |
| #include <linux/module.h> |
| #include <linux/gfp.h> |
| #include <sound/core.h> |
| #include <sound/pcm.h> |
| #include <sound/pcm_params.h> |
| #include <sound/info.h> |
| #include <sound/initval.h> |
| #include <sound/control.h> |
| #include <media/v4l2-common.h> |
| #include "pd-common.h" |
| #include "vendorcmds.h" |
| |
| static void complete_handler_audio(struct urb *urb); |
| #define AUDIO_EP (0x83) |
| #define AUDIO_BUF_SIZE (512) |
| #define PERIOD_SIZE (1024 * 8) |
| #define PERIOD_MIN (4) |
| #define PERIOD_MAX PERIOD_MIN |
| |
| static struct snd_pcm_hardware snd_pd_hw_capture = { |
| .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | |
| SNDRV_PCM_INFO_MMAP | |
| SNDRV_PCM_INFO_INTERLEAVED | |
| SNDRV_PCM_INFO_MMAP_VALID, |
| |
| .formats = SNDRV_PCM_FMTBIT_S16_LE, |
| .rates = SNDRV_PCM_RATE_48000, |
| |
| .rate_min = 48000, |
| .rate_max = 48000, |
| .channels_min = 2, |
| .channels_max = 2, |
| .buffer_bytes_max = PERIOD_SIZE * PERIOD_MIN, |
| .period_bytes_min = PERIOD_SIZE, |
| .period_bytes_max = PERIOD_SIZE, |
| .periods_min = PERIOD_MIN, |
| .periods_max = PERIOD_MAX, |
| /* |
| .buffer_bytes_max = 62720 * 8, |
| .period_bytes_min = 64, |
| .period_bytes_max = 12544, |
| .periods_min = 2, |
| .periods_max = 98 |
| */ |
| }; |
| |
| static int snd_pd_capture_open(struct snd_pcm_substream *substream) |
| { |
| struct poseidon *p = snd_pcm_substream_chip(substream); |
| struct poseidon_audio *pa = &p->audio; |
| struct snd_pcm_runtime *runtime = substream->runtime; |
| |
| if (!p) |
| return -ENODEV; |
| pa->users++; |
| pa->card_close = 0; |
| pa->capture_pcm_substream = substream; |
| runtime->private_data = p; |
| |
| runtime->hw = snd_pd_hw_capture; |
| snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); |
| usb_autopm_get_interface(p->interface); |
| kref_get(&p->kref); |
| return 0; |
| } |
| |
| static int snd_pd_pcm_close(struct snd_pcm_substream *substream) |
| { |
| struct poseidon *p = snd_pcm_substream_chip(substream); |
| struct poseidon_audio *pa = &p->audio; |
| |
| pa->users--; |
| pa->card_close = 1; |
| usb_autopm_put_interface(p->interface); |
| kref_put(&p->kref, poseidon_delete); |
| return 0; |
| } |
| |
| static int snd_pd_hw_capture_params(struct snd_pcm_substream *substream, |
| struct snd_pcm_hw_params *hw_params) |
| { |
| struct snd_pcm_runtime *runtime = substream->runtime; |
| unsigned int size; |
| |
| size = params_buffer_bytes(hw_params); |
| if (runtime->dma_area) { |
| if (runtime->dma_bytes > size) |
| return 0; |
| vfree(runtime->dma_area); |
| } |
| runtime->dma_area = vmalloc(size); |
| if (!runtime->dma_area) |
| return -ENOMEM; |
| else |
| runtime->dma_bytes = size; |
| return 0; |
| } |
| |
| static int audio_buf_free(struct poseidon *p) |
| { |
| struct poseidon_audio *pa = &p->audio; |
| int i; |
| |
| for (i = 0; i < AUDIO_BUFS; i++) |
| if (pa->urb_array[i]) |
| usb_kill_urb(pa->urb_array[i]); |
| free_all_urb_generic(pa->urb_array, AUDIO_BUFS); |
| logpm(); |
| return 0; |
| } |
| |
| static int snd_pd_hw_capture_free(struct snd_pcm_substream *substream) |
| { |
| struct poseidon *p = snd_pcm_substream_chip(substream); |
| |
| logpm(); |
| audio_buf_free(p); |
| return 0; |
| } |
| |
| static int snd_pd_prepare(struct snd_pcm_substream *substream) |
| { |
| return 0; |
| } |
| |
| #define AUDIO_TRAILER_SIZE (16) |
| static inline void handle_audio_data(struct urb *urb, int *period_elapsed) |
| { |
| struct poseidon_audio *pa = urb->context; |
| struct snd_pcm_runtime *runtime = pa->capture_pcm_substream->runtime; |
| |
| int stride = runtime->frame_bits >> 3; |
| int len = urb->actual_length / stride; |
| unsigned char *cp = urb->transfer_buffer; |
| unsigned int oldptr = pa->rcv_position; |
| |
| if (urb->actual_length == AUDIO_BUF_SIZE - 4) |
| len -= (AUDIO_TRAILER_SIZE / stride); |
| |
| /* do the copy */ |
| if (oldptr + len >= runtime->buffer_size) { |
| unsigned int cnt = runtime->buffer_size - oldptr; |
| |
| memcpy(runtime->dma_area + oldptr * stride, cp, cnt * stride); |
| memcpy(runtime->dma_area, (cp + cnt * stride), |
| (len * stride - cnt * stride)); |
| } else |
| memcpy(runtime->dma_area + oldptr * stride, cp, len * stride); |
| |
| /* update the statas */ |
| snd_pcm_stream_lock(pa->capture_pcm_substream); |
| pa->rcv_position += len; |
| if (pa->rcv_position >= runtime->buffer_size) |
| pa->rcv_position -= runtime->buffer_size; |
| |
| pa->copied_position += (len); |
| if (pa->copied_position >= runtime->period_size) { |
| pa->copied_position -= runtime->period_size; |
| *period_elapsed = 1; |
| } |
| snd_pcm_stream_unlock(pa->capture_pcm_substream); |
| } |
| |
| static void complete_handler_audio(struct urb *urb) |
| { |
| struct poseidon_audio *pa = urb->context; |
| struct snd_pcm_substream *substream = pa->capture_pcm_substream; |
| int period_elapsed = 0; |
| int ret; |
| |
| if (1 == pa->card_close || pa->capture_stream != STREAM_ON) |
| return; |
| |
| if (urb->status != 0) { |
| /*if (urb->status == -ESHUTDOWN)*/ |
| return; |
| } |
| |
| if (substream) { |
| if (urb->actual_length) { |
| handle_audio_data(urb, &period_elapsed); |
| if (period_elapsed) |
| snd_pcm_period_elapsed(substream); |
| } |
| } |
| |
| ret = usb_submit_urb(urb, GFP_ATOMIC); |
| if (ret < 0) |
| log("audio urb failed (errcod = %i)", ret); |
| return; |
| } |
| |
| static int fire_audio_urb(struct poseidon *p) |
| { |
| int i, ret = 0; |
| struct poseidon_audio *pa = &p->audio; |
| |
| alloc_bulk_urbs_generic(pa->urb_array, AUDIO_BUFS, |
| p->udev, AUDIO_EP, |
| AUDIO_BUF_SIZE, GFP_ATOMIC, |
| complete_handler_audio, pa); |
| |
| for (i = 0; i < AUDIO_BUFS; i++) { |
| ret = usb_submit_urb(pa->urb_array[i], GFP_KERNEL); |
| if (ret) |
| log("urb err : %d", ret); |
| } |
| log(); |
| return ret; |
| } |
| |
| static int snd_pd_capture_trigger(struct snd_pcm_substream *substream, int cmd) |
| { |
| struct poseidon *p = snd_pcm_substream_chip(substream); |
| struct poseidon_audio *pa = &p->audio; |
| |
| if (debug_mode) |
| log("cmd %d, audio stat : %d\n", cmd, pa->capture_stream); |
| |
| switch (cmd) { |
| case SNDRV_PCM_TRIGGER_RESUME: |
| case SNDRV_PCM_TRIGGER_START: |
| if (pa->capture_stream == STREAM_ON) |
| return 0; |
| |
| pa->rcv_position = pa->copied_position = 0; |
| pa->capture_stream = STREAM_ON; |
| |
| if (in_hibernation(p)) |
| return 0; |
| fire_audio_urb(p); |
| return 0; |
| |
| case SNDRV_PCM_TRIGGER_SUSPEND: |
| pa->capture_stream = STREAM_SUSPEND; |
| return 0; |
| case SNDRV_PCM_TRIGGER_STOP: |
| pa->capture_stream = STREAM_OFF; |
| return 0; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| static snd_pcm_uframes_t |
| snd_pd_capture_pointer(struct snd_pcm_substream *substream) |
| { |
| struct poseidon *p = snd_pcm_substream_chip(substream); |
| struct poseidon_audio *pa = &p->audio; |
| return pa->rcv_position; |
| } |
| |
| static struct page *snd_pcm_pd_get_page(struct snd_pcm_substream *subs, |
| unsigned long offset) |
| { |
| void *pageptr = subs->runtime->dma_area + offset; |
| return vmalloc_to_page(pageptr); |
| } |
| |
| static struct snd_pcm_ops pcm_capture_ops = { |
| .open = snd_pd_capture_open, |
| .close = snd_pd_pcm_close, |
| .ioctl = snd_pcm_lib_ioctl, |
| .hw_params = snd_pd_hw_capture_params, |
| .hw_free = snd_pd_hw_capture_free, |
| .prepare = snd_pd_prepare, |
| .trigger = snd_pd_capture_trigger, |
| .pointer = snd_pd_capture_pointer, |
| .page = snd_pcm_pd_get_page, |
| }; |
| |
| #ifdef CONFIG_PM |
| int pm_alsa_suspend(struct poseidon *p) |
| { |
| logpm(p); |
| audio_buf_free(p); |
| return 0; |
| } |
| |
| int pm_alsa_resume(struct poseidon *p) |
| { |
| logpm(p); |
| fire_audio_urb(p); |
| return 0; |
| } |
| #endif |
| |
| int poseidon_audio_init(struct poseidon *p) |
| { |
| struct poseidon_audio *pa = &p->audio; |
| struct snd_card *card; |
| struct snd_pcm *pcm; |
| int ret; |
| |
| ret = snd_card_new(&p->interface->dev, -1, "Telegent", |
| THIS_MODULE, 0, &card); |
| if (ret != 0) |
| return ret; |
| |
| ret = snd_pcm_new(card, "poseidon audio", 0, 0, 1, &pcm); |
| if (ret < 0) { |
| snd_card_free(card); |
| return ret; |
| } |
| snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops); |
| pcm->info_flags = 0; |
| pcm->private_data = p; |
| strcpy(pcm->name, "poseidon audio capture"); |
| |
| strcpy(card->driver, "ALSA driver"); |
| strcpy(card->shortname, "poseidon Audio"); |
| strcpy(card->longname, "poseidon ALSA Audio"); |
| |
| if (snd_card_register(card)) { |
| snd_card_free(card); |
| return -ENOMEM; |
| } |
| pa->card = card; |
| return 0; |
| } |
| |
| int poseidon_audio_free(struct poseidon *p) |
| { |
| struct poseidon_audio *pa = &p->audio; |
| |
| if (pa->card) |
| snd_card_free(pa->card); |
| return 0; |
| } |