us144mkii/us144mkii_capture.c

235 lines
6.7 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2025 Šerif Rami <ramiserifpersia@gmail.com>
#include "us144mkii_pcm.h"
const struct snd_pcm_hardware tascam_capture_hw = {
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000),
.rate_min = 44100,
.rate_max = 96000,
.channels_min = NUM_CHANNELS,
.channels_max = NUM_CHANNELS,
.buffer_bytes_max = 1024 * 1024,
.period_bytes_min = 64 * BYTES_PER_FRAME,
.period_bytes_max = 1024 * BYTES_PER_FRAME,
.periods_min = 2,
.periods_max = 1024,
};
static int tascam_capture_open(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
substream->runtime->hw = tascam_capture_hw;
tascam->capture_substream = substream;
atomic_set(&tascam->capture_active, 0);
return 0;
}
static int tascam_capture_close(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
tascam->capture_substream = NULL;
return 0;
}
static int tascam_capture_prepare(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
usb_kill_anchored_urbs(&tascam->capture_anchor);
tascam->driver_capture_pos = 0;
tascam->capture_frames_processed = 0;
tascam->last_cap_period_pos = 0;
return 0;
}
static snd_pcm_uframes_t tascam_capture_pointer(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
unsigned long flags;
u64 pos;
if (!atomic_read(&tascam->capture_active)) return 0;
spin_lock_irqsave(&tascam->lock, flags);
pos = tascam->capture_frames_processed;
spin_unlock_irqrestore(&tascam->lock, flags);
return do_div(pos, substream->runtime->buffer_size);
}
static int tascam_capture_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
int i, ret = 0;
bool start = false;
unsigned long flags;
spin_lock_irqsave(&tascam->lock, flags);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
if (!atomic_read(&tascam->capture_active)) {
atomic_set(&tascam->capture_active, 1);
start = true;
}
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
atomic_set(&tascam->capture_active, 0);
for (i = 0; i < NUM_CAPTURE_URBS; i++) {
if (tascam->capture_urbs[i])
usb_unlink_urb(tascam->capture_urbs[i]);
}
break;
default:
ret = -EINVAL;
break;
}
spin_unlock_irqrestore(&tascam->lock, flags);
if (start) {
for (i = 0; i < NUM_CAPTURE_URBS; i++) {
usb_anchor_urb(tascam->capture_urbs[i], &tascam->capture_anchor);
if (usb_submit_urb(tascam->capture_urbs[i], GFP_ATOMIC) < 0) {
atomic_set(&tascam->capture_active, 0);
for (int j = 0; j <= i; j++)
usb_unlink_urb(tascam->capture_urbs[j]);
ret = -EIO;
break;
}
atomic_inc(&tascam->active_urbs);
}
}
return ret;
}
static inline u8 tascam_pack_byte(const u8 *src, int bit_offset)
{
return (((src[0] >> bit_offset) & 1) << 7) |
(((src[1] >> bit_offset) & 1) << 6) |
(((src[2] >> bit_offset) & 1) << 5) |
(((src[3] >> bit_offset) & 1) << 4) |
(((src[4] >> bit_offset) & 1) << 3) |
(((src[5] >> bit_offset) & 1) << 2) |
(((src[6] >> bit_offset) & 1) << 1) |
(((src[7] >> bit_offset) & 1));
}
static void tascam_decode_capture_chunk(const u8 *src, u32 *dst, int frames_to_decode)
{
int frame;
u8 h, m, l;
for (frame = 0; frame < frames_to_decode; frame++) {
const u8 *p_src_a = src + (frame * 64);
const u8 *p_src_b = src + (frame * 64) + 32;
h = tascam_pack_byte(p_src_a, 0);
m = tascam_pack_byte(p_src_a + 8, 0);
l = tascam_pack_byte(p_src_a + 16, 0);
*dst++ = (h << 24) | (m << 16) | (l << 8);
h = tascam_pack_byte(p_src_b, 0);
m = tascam_pack_byte(p_src_b + 8, 0);
l = tascam_pack_byte(p_src_b + 16, 0);
*dst++ = (h << 24) | (m << 16) | (l << 8);
h = tascam_pack_byte(p_src_a, 1);
m = tascam_pack_byte(p_src_a + 8, 1);
l = tascam_pack_byte(p_src_a + 16, 1);
*dst++ = (h << 24) | (m << 16) | (l << 8);
h = tascam_pack_byte(p_src_b, 1);
m = tascam_pack_byte(p_src_b + 8, 1);
l = tascam_pack_byte(p_src_b + 16, 1);
*dst++ = (h << 24) | (m << 16) | (l << 8);
}
}
void capture_urb_complete(struct urb *urb)
{
struct tascam_card *tascam = urb->context;
struct snd_pcm_substream *substream;
struct snd_pcm_runtime *runtime;
unsigned long flags;
int frames_received;
snd_pcm_uframes_t write_pos;
if (urb->status) {
atomic_dec(&tascam->active_urbs);
return;
}
if (!tascam || !atomic_read(&tascam->capture_active)) {
atomic_dec(&tascam->active_urbs);
return;
}
substream = tascam->capture_substream;
if (!substream || !substream->runtime) {
atomic_dec(&tascam->active_urbs);
return;
}
runtime = substream->runtime;
frames_received = urb->actual_length / 64;
if (frames_received > 0) {
spin_lock_irqsave(&tascam->lock, flags);
write_pos = tascam->driver_capture_pos;
spin_unlock_irqrestore(&tascam->lock, flags);
u32 *dma_ptr = (u32 *)(runtime->dma_area + frames_to_bytes(runtime, write_pos));
if (write_pos + frames_received <= runtime->buffer_size) {
tascam_decode_capture_chunk(urb->transfer_buffer, dma_ptr, frames_received);
} else {
int part1 = runtime->buffer_size - write_pos;
int part2 = frames_received - part1;
tascam_decode_capture_chunk(urb->transfer_buffer, dma_ptr, part1);
tascam_decode_capture_chunk(urb->transfer_buffer + (part1 * 64), (u32 *)runtime->dma_area, part2);
}
spin_lock_irqsave(&tascam->lock, flags);
tascam->driver_capture_pos += frames_received;
if (tascam->driver_capture_pos >= runtime->buffer_size)
tascam->driver_capture_pos -= runtime->buffer_size;
tascam->capture_frames_processed += frames_received;
if (runtime->period_size > 0) {
u64 current_period = div_u64(tascam->capture_frames_processed, runtime->period_size);
if (current_period > tascam->last_cap_period_pos) {
tascam->last_cap_period_pos = current_period;
spin_unlock_irqrestore(&tascam->lock, flags);
snd_pcm_period_elapsed(substream);
spin_lock_irqsave(&tascam->lock, flags);
}
}
spin_unlock_irqrestore(&tascam->lock, flags);
}
if (usb_submit_urb(urb, GFP_ATOMIC) < 0)
atomic_dec(&tascam->active_urbs);
}
const struct snd_pcm_ops tascam_capture_ops = {
.open = tascam_capture_open,
.close = tascam_capture_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = tascam_pcm_hw_params,
.hw_free = NULL,
.prepare = tascam_capture_prepare,
.trigger = tascam_capture_trigger,
.pointer = tascam_capture_pointer,
};