This file handles all setup of a new snd_card instance with one pcm_substream.
It's also responsible for parsing the raw pcm data received in the isoc 
transfers,
and passing it to the alsa buffers.

Signed-off-by: Jon Arne Jørgensen <jona...@jonarne.no>
---
 drivers/media/usb/smi2021/smi2021_audio.c | 385 ++++++++++++++++++++++++++++++
 1 file changed, 385 insertions(+)
 create mode 100644 drivers/media/usb/smi2021/smi2021_audio.c

diff --git a/drivers/media/usb/smi2021/smi2021_audio.c 
b/drivers/media/usb/smi2021/smi2021_audio.c
new file mode 100644
index 0000000..cd20181
--- /dev/null
+++ b/drivers/media/usb/smi2021/smi2021_audio.c
@@ -0,0 +1,385 @@
+/*******************************************************************************
+ * smi2021_audio.c                                                             
*
+ *                                                                             
*
+ * USB Driver for SMI2021 - EasyCap                                            
*
+ * USB ID 1c88:003c                                                            
*
+ *                                                                             
*
+ * 
*****************************************************************************
+ *
+ * Copyright 2011-2013 Jon Arne Jørgensen
+ * <jonjon.arnearne--a.t--gmail.com>
+ *
+ * Copyright 2011, 2012 Tony Brown, Michal Demin, Jeffry Johnston
+ *
+ * This file is part of SMI2021
+ * http://code.google.com/p/easycap-somagic-linux/
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * This driver is heavily influensed by the STK1160 driver.
+ * Copyright (C) 2012 Ezequiel Garcia
+ * <elezegarcia--a.t--gmail.com>
+ *
+ */
+
+#include "smi2021.h"
+
+static void pcm_buffer_free(struct snd_pcm_substream *substream)
+{
+       vfree(substream->runtime->dma_area);
+       substream->runtime->dma_area = NULL;
+       substream->runtime->dma_bytes = 0;
+}
+
+static int pcm_buffer_alloc(struct snd_pcm_substream *substream, int size)
+{
+       if (substream->runtime->dma_area) {
+               if (substream->runtime->dma_bytes > size)
+                       return 0;
+               pcm_buffer_free(substream);
+       }
+
+       substream->runtime->dma_area = vmalloc(size);
+       if (substream->runtime->dma_area == NULL)
+               return -ENOMEM;
+
+       substream->runtime->dma_bytes = size;
+
+       return 0;
+}
+
+static const struct snd_pcm_hardware smi2021_pcm_hw = {
+       .info = SNDRV_PCM_INFO_BLOCK_TRANSFER |
+               SNDRV_PCM_INFO_INTERLEAVED    |
+               SNDRV_PCM_INFO_MMAP           |
+               SNDRV_PCM_INFO_MMAP_VALID     |
+               SNDRV_PCM_INFO_BATCH,
+
+       .formats = SNDRV_PCM_FMTBIT_S32_LE,
+
+       .rates = SNDRV_PCM_RATE_48000,
+       .rate_min = 48000,
+       .rate_max = 48000,
+       .channels_min = 2,
+       .channels_max = 2,
+       .period_bytes_min = 992,        /* 32640 */ /* 15296 */
+       .period_bytes_max = 15872,      /* 65280 */
+       .periods_min = 1,               /* 1 */
+       .periods_max = 16,              /* 2 */
+       .buffer_bytes_max = 65280,      /* 65280 */
+};
+
+static int smi2021_pcm_open(struct snd_pcm_substream *substream)
+{
+       struct smi2021_dev *dev = snd_pcm_substream_chip(substream);
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       int rc;
+
+       rc = snd_pcm_hw_constraint_pow2(runtime, 0,
+                                       SNDRV_PCM_HW_PARAM_PERIODS);
+       if (rc < 0)
+               return rc;
+
+       dev->pcm_substream = substream;
+
+       runtime->hw = smi2021_pcm_hw;
+       snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+
+       smi2021_dbg("PCM device open!\n");
+
+       return 0;
+}
+
+static int smi2021_pcm_close(struct snd_pcm_substream *substream)
+{
+       struct smi2021_dev *dev = snd_pcm_substream_chip(substream);
+       smi2021_dbg("PCM device closing\n");
+
+       if (atomic_read(&dev->adev_capturing)) {
+               atomic_set(&dev->adev_capturing, 0);
+               schedule_work(&dev->adev_capture_trigger);
+       }
+       return 0;
+
+}
+
+
+static int smi2021_pcm_hw_params(struct snd_pcm_substream *substream,
+                               struct snd_pcm_hw_params *hw_params)
+{
+       int size, rc;
+       size = params_period_bytes(hw_params) * params_periods(hw_params);
+
+       rc = pcm_buffer_alloc(substream, size);
+       if (rc < 0)
+               return rc;
+
+
+       return 0;
+}
+
+static int smi2021_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+       struct smi2021_dev *dev = snd_pcm_substream_chip(substream);
+
+       if (atomic_read(&dev->adev_capturing)) {
+               atomic_set(&dev->adev_capturing, 0);
+               schedule_work(&dev->adev_capture_trigger);
+       }
+
+       pcm_buffer_free(substream);
+       return 0;
+}
+
+static int smi2021_pcm_prepare(struct snd_pcm_substream *substream)
+{
+       struct smi2021_dev *dev = snd_pcm_substream_chip(substream);
+
+       dev->pcm_complete_samples = 0;
+       dev->pcm_read_offset = 0;
+       dev->pcm_write_ptr = 0;
+
+       return 0;
+}
+
+static void capture_trigger(struct work_struct *work)
+{
+       struct smi2021_dev *dev = container_of(work, struct smi2021_dev,
+                                       adev_capture_trigger);
+
+       if (atomic_read(&dev->adev_capturing))
+               smi2021_write_reg(dev, 0, 0x1740, 0x1d);
+       else
+               smi2021_write_reg(dev, 0, 0x1740, 0x00);
+}
+
+/* This callback is ATOMIC, must not sleep */
+static int smi2021_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+       struct smi2021_dev *dev = snd_pcm_substream_chip(substream);
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+       case SNDRV_PCM_TRIGGER_RESUME:
+       case SNDRV_PCM_TRIGGER_START:
+               atomic_set(&dev->adev_capturing, 1);
+               break;
+       case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+       case SNDRV_PCM_TRIGGER_SUSPEND:
+       case SNDRV_PCM_TRIGGER_STOP:
+               atomic_set(&dev->adev_capturing, 0);
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       schedule_work(&dev->adev_capture_trigger);
+
+       return 0;
+}
+
+static snd_pcm_uframes_t smi2021_pcm_pointer(
+                                       struct snd_pcm_substream *substream)
+{
+       struct smi2021_dev *dev = snd_pcm_substream_chip(substream);
+       return dev->pcm_write_ptr / 8;
+}
+
+static struct page *smi2021_pcm_get_vmalloc_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 smi2021_pcm_ops = {
+       .open = smi2021_pcm_open,
+       .close = smi2021_pcm_close,
+       .ioctl = snd_pcm_lib_ioctl,
+       .hw_params = smi2021_pcm_hw_params,
+       .hw_free = smi2021_pcm_hw_free,
+       .prepare = smi2021_pcm_prepare,
+       .trigger = smi2021_pcm_trigger,
+       .pointer = smi2021_pcm_pointer,
+       .page = smi2021_pcm_get_vmalloc_page,
+};
+
+int smi2021_snd_register(struct smi2021_dev *dev)
+{
+       struct snd_card *card;
+       struct snd_pcm *pcm;
+       int rc = 0;
+
+       rc = snd_card_create(SNDRV_DEFAULT_IDX1, "smi2021 Audio", THIS_MODULE,
+                               0, &card);
+       if (rc < 0)
+               return rc;
+
+       rc = snd_pcm_new(card, "smi2021 Audio", 0, 0, 1, &pcm);
+       if (rc < 0)
+               goto err_free_card;
+
+       snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &smi2021_pcm_ops);
+       pcm->info_flags = 0;
+       pcm->private_data = dev;
+       strcpy(pcm->name, "Somagic smi2021 Capture");
+
+       strcpy(card->driver, "smi2021-Audio");
+       strcpy(card->shortname, "smi2021 Audio");
+       strcpy(card->longname, "Somagic smi2021 Audio");
+
+       INIT_WORK(&dev->adev_capture_trigger, capture_trigger);
+
+       rc = snd_card_register(card);
+       if (rc < 0)
+               goto err_free_card;
+
+       dev->snd_card = card;
+
+       return 0;
+
+err_free_card:
+       snd_card_free(card);
+       return rc;
+}
+
+void smi2021_snd_unregister(struct smi2021_dev *dev)
+{
+       if (!dev)
+               return;
+
+       if (!dev->snd_card)
+               return;
+
+       snd_card_free(dev->snd_card);
+       dev->snd_card = NULL;
+}
+
+void smi2021_audio(struct smi2021_dev *dev, u8 *data, int len)
+{
+       struct snd_pcm_runtime *runtime;
+       u8 offset;
+       int new_offset = 0;
+
+       int skip;
+       unsigned int stride, oldptr, headptr;
+
+       int diff = 0;
+       int samples = 0;
+       bool period_elapsed = false;
+
+
+       if (!dev->udev)
+               return;
+
+       if (atomic_read(&dev->adev_capturing) == 0)
+               return;
+
+       if (!dev->pcm_substream)
+               return;
+
+       runtime = dev->pcm_substream->runtime;
+       if (!runtime || !runtime->dma_area)
+               return;
+
+       offset = dev->pcm_read_offset;
+       stride = runtime->frame_bits >> 3;
+
+       if (stride == 0)
+               return;
+
+       diff = dev->pcm_write_ptr;
+
+       /* Check that the end of the last buffer was correct.
+        * If not correct, we mark any partial frames in buffer as complete
+        */
+       headptr = dev->pcm_write_ptr - offset - 4;
+       if (dev->pcm_write_ptr > 10 && runtime->dma_area[headptr] != 0x00) {
+               skip = stride - (dev->pcm_write_ptr % stride);
+               snd_pcm_stream_lock(dev->pcm_substream);
+               dev->pcm_write_ptr += skip;
+
+               if (dev->pcm_write_ptr >= runtime->dma_bytes)
+                       dev->pcm_write_ptr -= runtime->dma_bytes;
+
+               snd_pcm_stream_unlock(dev->pcm_substream);
+               offset = dev->pcm_read_offset = 0;
+       }
+       /* The device is actually sending 24Bit pcm data
+        * with 0x00 as the header byte before each sample.
+        * We look for this byte to make sure we did not
+        * loose any bytes during transfer.
+        */
+       while (len > stride && (data[offset] != 0x00 ||
+                       data[offset + (stride / 2)] != 0x00)) {
+               new_offset++;
+               data++;
+               len--;
+       }
+
+       if (len <= stride) {
+               /* We exhausted the buffer looking for 0x00 */
+               dev->pcm_read_offset = 0;
+               return;
+       }
+       if (new_offset != 0) {
+               /* This buffer can not be appended to the current buffer,
+                * so we mark any partial frames in the buffer as complete.
+                */
+               skip = stride - (dev->pcm_write_ptr % stride);
+               snd_pcm_stream_lock(dev->pcm_substream);
+               dev->pcm_write_ptr += skip;
+
+               if (dev->pcm_write_ptr >= runtime->dma_bytes)
+                       dev->pcm_write_ptr -= runtime->dma_bytes;
+
+               snd_pcm_stream_unlock(dev->pcm_substream);
+
+               offset = dev->pcm_read_offset = new_offset % (stride / 2);
+
+       }
+
+       oldptr = dev->pcm_write_ptr;
+       if (oldptr + len >= runtime->dma_bytes) {
+               unsigned int cnt = runtime->dma_bytes - oldptr;
+               memcpy(runtime->dma_area + oldptr, data, cnt);
+               memcpy(runtime->dma_area, data + cnt, len - cnt);
+       } else {
+               memcpy(runtime->dma_area + oldptr, data, len);
+       }
+
+       snd_pcm_stream_lock(dev->pcm_substream);
+       dev->pcm_write_ptr += len;
+
+       if (dev->pcm_write_ptr >= runtime->dma_bytes)
+               dev->pcm_write_ptr -= runtime->dma_bytes;
+
+       samples = dev->pcm_write_ptr - diff;
+       if (samples < 0)
+               samples += runtime->dma_bytes;
+
+       samples /= (stride / 2);
+
+       dev->pcm_complete_samples += samples;
+       if (dev->pcm_complete_samples / 2 >= runtime->period_size) {
+               dev->pcm_complete_samples -= runtime->period_size * 2;
+               period_elapsed = true;
+       }
+       snd_pcm_stream_unlock(dev->pcm_substream);
+
+       if (period_elapsed)
+               snd_pcm_period_elapsed(dev->pcm_substream);
+
+}
-- 
1.8.1.1

--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to