From patchwork Fri Nov 20 03:24:51 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Huang Shijie X-Patchwork-Id: 2133 Return-path: Envelope-to: mchehab@infradead.org Delivery-date: Fri, 20 Nov 2009 03:25:35 +0000 Received: from bombadil.infradead.org [18.85.46.34] by pedra.chehab.org with IMAP (fetchmail-6.3.6) for (single-drop); Fri, 20 Nov 2009 09:02:52 -0200 (BRST) Received: from vger.kernel.org ([209.132.176.167]) by bombadil.infradead.org with esmtp (Exim 4.69 #1 (Red Hat Linux)) id 1NBK7q-00066x-TS; Fri, 20 Nov 2009 03:25:35 +0000 Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1758267AbZKTDZZ (ORCPT + 1 other); Thu, 19 Nov 2009 22:25:25 -0500 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1758266AbZKTDZZ (ORCPT ); Thu, 19 Nov 2009 22:25:25 -0500 Received: from mail-yx0-f187.google.com ([209.85.210.187]:58859 "EHLO mail-yx0-f187.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1758258AbZKTDZT (ORCPT ); Thu, 19 Nov 2009 22:25:19 -0500 Received: by mail-yx0-f187.google.com with SMTP id 17so2630577yxe.33 for ; Thu, 19 Nov 2009 19:25:26 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:received:received:from:to:cc:subject:date :message-id:x-mailer:in-reply-to:references; bh=HsyyOVeXPEX3Zk5qgXYXfT8jaIM5bQQP0mtK2/Ypgeo=; b=sHzSzLWMEpMYqyS0nWh9oTmOOOxTon0u4E98aGWXkkt6cvmb4JzJzbqgU4pY0czorb Yu6heboyUeogs7UZWiXKfG5nbqLXEkbB1387IF6mrLs2bY2gJ357qUkrOd/JfjvVLRwQ oItRqj7WKqam7KSbHzubNm0UZt2bJPYNdn8P0= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references; b=cOKtzYur+oU3wuK9UYrpqjkNsw0nZGzmJAp5ZYIM6sw3RJZTLGIH8RyS8XPgizVvLi g/K+gKL7sA/Ho+YAX8wu6vualvuSlUVqzr1xgz2Yhvbq7YynNvbYsawT7Vy2lBwzzMBe PGwjpupsG4OM9KV4EZ7KuU8eAQb4lkiyvISv0= Received: by 10.150.162.18 with SMTP id k18mr1620165ybe.155.1258687526132; Thu, 19 Nov 2009 19:25:26 -0800 (PST) Received: from localhost.localdomain ([211.144.218.162]) by mx.google.com with ESMTPS id 9sm192108ywe.26.2009.11.19.19.25.23 (version=SSLv3 cipher=RC4-MD5); Thu, 19 Nov 2009 19:25:25 -0800 (PST) From: Huang Shijie To: mchehab@redhat.com Cc: linux-media@vger.kernel.org, Huang Shijie Subject: [PATCH 09/11] add audio support for tlg2300 Date: Fri, 20 Nov 2009 11:24:51 +0800 Message-Id: <1258687493-4012-10-git-send-email-shijie8@gmail.com> X-Mailer: git-send-email 1.6.0.6 In-Reply-To: <1258687493-4012-9-git-send-email-shijie8@gmail.com> References: <1258687493-4012-1-git-send-email-shijie8@gmail.com> <1258687493-4012-2-git-send-email-shijie8@gmail.com> <1258687493-4012-3-git-send-email-shijie8@gmail.com> <1258687493-4012-4-git-send-email-shijie8@gmail.com> <1258687493-4012-5-git-send-email-shijie8@gmail.com> <1258687493-4012-6-git-send-email-shijie8@gmail.com> <1258687493-4012-7-git-send-email-shijie8@gmail.com> <1258687493-4012-8-git-send-email-shijie8@gmail.com> <1258687493-4012-9-git-send-email-shijie8@gmail.com> Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org The module uses ALSA for the audio, it will register a new card for tlg2300. Signed-off-by: Huang Shijie --- drivers/media/video/tlg2300/pd-alsa.c | 379 +++++++++++++++++++++++++++++++++ 1 files changed, 379 insertions(+), 0 deletions(-) create mode 100644 drivers/media/video/tlg2300/pd-alsa.c diff --git a/drivers/media/video/tlg2300/pd-alsa.c b/drivers/media/video/tlg2300/pd-alsa.c new file mode 100644 index 0000000..54a5aaa --- /dev/null +++ b/drivers/media/video/tlg2300/pd-alsa.c @@ -0,0 +1,379 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pd-common.h" +#include "vendorcmds.h" + +static void complete_handler_audio(struct urb *urb); +#define ANOLOG_AUDIO_ID (0) +#define FM_ID (1) +#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; + kref_put(&p->kref, poseidon_delete); + usb_autopm_put_interface(p->interface); + 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++) { + struct urb *urb = pa->urb[i]; + + if (!urb) + continue; + usb_buffer_free(p->udev, urb->transfer_buffer_length, + urb->transfer_buffer, urb->transfer_dma); + usb_free_urb(urb); + pa->urb[i] = NULL; + } + return 0; +} + +static int audio_buf_init(struct poseidon *p) +{ + int i, ret; + struct poseidon_audio *pa = &p->audio; + + for (i = 0; i < AUDIO_BUFS; i++) { + struct urb *urb; + char *mem = NULL; + + if (pa->urb[i]) + continue; + + urb = usb_alloc_urb(0, GFP_ATOMIC); + if (!urb) { + ret = -ENOMEM; + goto init_err; + } + mem = usb_buffer_alloc(p->udev, AUDIO_BUF_SIZE, + GFP_ATOMIC, &urb->transfer_dma); + if (!mem) { + usb_free_urb(urb); + goto init_err; + } + usb_fill_bulk_urb(urb, p->udev, + usb_rcvbulkpipe(p->udev, AUDIO_EP), + mem, AUDIO_BUF_SIZE, + complete_handler_audio, + pa); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + pa->urb[i] = urb; + } + return 0; + +init_err: + audio_buf_free(p); + return ret; +} + +static int snd_pd_hw_capture_free(struct snd_pcm_substream *substream) +{ + struct poseidon *p = snd_pcm_substream_chip(substream); + + 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_OFF) + return; + + if (urb->status != 0) { + /*if (urb->status == -ESHUTDOWN)*/ + return; + } + + if (pa->capture_stream == STREAM_ON && substream && !urb->status) { + 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 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; + int i, ret; + + if (debug_mode) + log("cmd %d\n", cmd); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_START: + if (audio_in_hibernate(p)) + return 0; + if (pa->capture_stream == STREAM_ON) + return 0; + + pa->rcv_position = pa->copied_position = 0; + pa->capture_stream = STREAM_ON; + + audio_buf_init(p); + for (i = 0; i < AUDIO_BUFS; i++) { + ret = usb_submit_urb(pa->urb[i], GFP_KERNEL); + if (ret) + log("urb err : %d", ret); + } + return 0; + case SNDRV_PCM_TRIGGER_SUSPEND: + 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) +{ + struct poseidon_audio *pa = &p->audio; + int i; + + snd_pcm_suspend(pa->capture_pcm_substream); + + for (i = 0; i < AUDIO_BUFS; i++) + usb_kill_urb(pa->urb[i]); + + audio_buf_free(p); + return 0; +} + +int pm_alsa_resume(struct poseidon *p) +{ + struct poseidon_audio *pa = &p->audio; + + if (audio_in_hibernate(p)) { + pa->pm_state = 0; + usb_autopm_get_interface(p->interface); + } + snd_pd_capture_trigger(pa->capture_pcm_substream, + SNDRV_PCM_TRIGGER_START); + 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; + + if (audio_in_hibernate(p)) + return 0; + + ret = snd_card_create(-1, "poseidon_audio", THIS_MODULE, 0, &card); + if (ret != 0) + return ret; + + ret = snd_pcm_new(card, "poseidon audio", 0, 0, 1, &pcm); + 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 (audio_in_hibernate(p)) + return 0; + + if (pa->card) + snd_card_free(pa->card); + return 0; +}