// SPDX-License-Identifier: GPL-2.0+
//
// Empiatech em28x1 audio extension
//
// Copyright (C) 2006 Markus Rechberger <mrechberger@gmail.com>
//
// Copyright (C) 2007-2016 Mauro Carvalho Chehab
// - Port to work with the in-kernel driver
// - Cleanups, fixes, alsa-controls, etc.
//
// This driver is based on my previous au600 usb pstn audio driver
// and inherits all the copyrights
#include "em28xx.h"
#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/slab.h>
#include <linux/module.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 <sound/tlv.h>
#include <sound/ac97_codec.h>
#include <media/v4l2-common.h>
static int debug;
module_param(debug, int, 0644);
MODULE_PARM_DESC(debug, "activates debug info");
#define EM28XX_MAX_AUDIO_BUFS 5
#define EM28XX_MIN_AUDIO_PACKETS 64
#define dprintk(fmt, arg...) do { \
if (debug) \
dev_printk(KERN_DEBUG, &dev->intf->dev, \
"video: %s: " fmt, __func__, ## arg); \
} while (0)
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static int em28xx_deinit_isoc_audio(struct em28xx *dev)
{
int i;
dprintk("Stopping isoc\n");
for (i = 0; i < dev->adev.num_urb; i++) {
struct urb *urb = dev->adev.urb[i];
if (!irqs_disabled())
usb_kill_urb(urb);
else
usb_unlink_urb(urb);
}
return 0;
}
static void em28xx_audio_isocirq(struct urb *urb)
{
struct em28xx *dev = urb->context;
int i;
unsigned int oldptr;
int period_elapsed = 0;
int status;
unsigned char *cp;
unsigned int stride;
struct snd_pcm_substream *substream;
struct snd_pcm_runtime *runtime;
if (dev->disconnected) {
dprintk("device disconnected while streaming. URB status=%d.\n",
urb->status);
atomic_set(&dev->adev.stream_started, 0);
return;
}
switch (urb->status) {
case 0: /* success */
case -ETIMEDOUT: /* NAK */
break;
case -ECONNRESET: /* kill */
case -ENOENT:
case -ESHUTDOWN:
return;
default: /* error */
dprintk("urb completion error %d.\n", urb->status);
break;
}
if (atomic_read(&dev->adev.stream_started) == 0)
return;
if (dev->adev.capture_pcm_substream) {
substream = dev->adev.capture_pcm_substream;
runtime = substream->runtime;
stride = runtime->frame_bits >> 3;
for (i = 0; i < urb->number_of_packets; i++) {
unsigned long flags;
int length =
urb->iso_frame_desc[i].actual_length / stride;
cp = (unsigned char *)urb->transfer_buffer +
urb->iso_frame_desc[i].offset;
if (!length)
continue;
oldptr = dev->adev.hwptr_done_capture;
if (oldptr + length >= 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,
length * stride - cnt * stride);
} else {
memcpy(runtime->dma_area + oldptr * stride, cp,
length * stride);
}
snd_pcm_stream_lock_irqsave(substream, flags);
dev->adev.hwptr_done_capture += length;
if (dev->adev.hwptr_done_capture >=
runtime->buffer_size)
dev->adev.hwptr_done_capture -=
runtime->buffer_size;
dev->adev.capture_transfer_done += length;
if (dev->adev.capture_transfer_done >=
runtime->period_size) {
dev->adev.capture_transfer_done -=
runtime->period_size;
period_elapsed = 1;
}
snd_pcm_stream_unlock_irqrestore(substream, flags);
}
if (period_elapsed)
snd_pcm_period_elapsed(substream);
}
urb->status = 0;
status = usb_submit_urb(urb, GFP_ATOMIC);
if (status < 0)
dev_err(&dev->intf->dev,
"resubmit of audio urb failed (error=%i)\n",
status);
}
static int em28xx_init_audio_isoc(struct em28xx *dev)
{
int i, err;
dprintk("Starting isoc transfers\n");
/* Start streaming */
for (i = 0; i < dev->adev.num_urb; i++) {
memset(dev->adev.transfer_buffer[i], 0x80,
dev->adev.urb[i]->transfer_buffer_length);
err = usb_submit_urb(dev->adev.urb[i], GFP_ATOMIC);
if (err) {
dev_err(&dev->intf->dev,
"submit of audio urb failed (error=%i)\n",
err);
em28xx_deinit_isoc_audio(dev);
atomic_set(&dev->adev.stream_started, 0);
return err;
}
}
return 0;
}
static const struct snd_pcm_hardware snd_em28xx_hw_capture = {
.info = SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BATCH |
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 = 62720 * 8, <