us144mkii/us144mkii_pcm.c

176 lines
4.9 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2025 Šerif Rami <ramiserifpersia@gmail.com>
#include "us144mkii_pcm.h"
static int tascam_write_regs(struct tascam_card *tascam, const u16 *regs, size_t count)
{
int i, err = 0;
struct usb_device *dev = tascam->dev;
for (i = 0; i < count; i++) {
err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV,
regs[i], REG_VAL_ENABLE, NULL, 0, USB_CTRL_TIMEOUT_MS);
if (err < 0)
return err;
}
return 0;
}
/**
* us144mkii_configure_device_for_rate() - set sample rate via USB control msgs
* @tascam: the tascam_card instance
* @rate: the target sample rate (e.g., 44100, 96000)
*
* This function sends a sequence of vendor-specific and UAC control messages
* to configure the device hardware for the specified sample rate.
*
* Return: 0 on success, or a negative error code on failure.
*/
int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int rate)
{
struct usb_device *dev = tascam->dev;
u8 *rate_payload;
int err = 0;
const u8 *current_payload_src;
u16 rate_reg;
static const u8 payload_44100[] = { 0x44, 0xac, 0x00 };
static const u8 payload_48000[] = { 0x80, 0xbb, 0x00 };
static const u8 payload_88200[] = { 0x88, 0x58, 0x01 };
static const u8 payload_96000[] = { 0x00, 0x77, 0x01 };
if (!dev)
return -ENODEV;
switch (rate) {
case 44100:
current_payload_src = payload_44100;
rate_reg = REG_ADDR_RATE_44100;
break;
case 48000:
current_payload_src = payload_48000;
rate_reg = REG_ADDR_RATE_48000;
break;
case 88200:
current_payload_src = payload_88200;
rate_reg = REG_ADDR_RATE_88200;
break;
case 96000:
current_payload_src = payload_96000;
rate_reg = REG_ADDR_RATE_96000;
break;
default:
return -EINVAL;
}
rate_payload = kmemdup(current_payload_src, 3, GFP_KERNEL);
if (!rate_payload)
return -ENOMEM;
err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
VENDOR_REQ_MODE_CONTROL, RT_H2D_VENDOR_DEV,
MODE_VAL_CONFIG, 0x0000, NULL, 0, USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto out;
err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR,
RT_H2D_CLASS_EP, UAC_SAMPLING_FREQ_CONTROL,
EP_AUDIO_IN, rate_payload, 3, USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto out;
err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR,
RT_H2D_CLASS_EP, UAC_SAMPLING_FREQ_CONTROL,
EP_AUDIO_OUT, rate_payload, 3, USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto out;
{
const u16 regs_to_write[] = {
REG_ADDR_INIT_0D, REG_ADDR_INIT_0E,
REG_ADDR_INIT_0F, rate_reg, REG_ADDR_INIT_11
};
err = tascam_write_regs(tascam, regs_to_write, ARRAY_SIZE(regs_to_write));
if (err < 0)
goto out;
}
err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
VENDOR_REQ_MODE_CONTROL, RT_H2D_VENDOR_DEV,
MODE_VAL_STREAM_START, 0x0000, NULL, 0, USB_CTRL_TIMEOUT_MS);
out:
kfree(rate_payload);
return err;
}
/**
* tascam_pcm_hw_params() - configure hardware parameters for PCM streams
* @substream: the ALSA PCM substream
* @params: the hardware parameters to apply
*
* This function allocates pages for the PCM buffer and, for playback streams,
* selects the appropriate feedback patterns based on the requested sample rate.
* It also configures the device hardware for the selected sample rate if it
* has changed.
*
* Return: 0 on success, or a negative error code on failure.
*/
int tascam_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
unsigned int rate = params_rate(params);
int err;
unsigned long flags;
spin_lock_irqsave(&tascam->lock, flags);
if (tascam->current_rate == rate) {
spin_unlock_irqrestore(&tascam->lock, flags);
return 0;
}
if (atomic_read(&tascam->playback_active) ||
atomic_read(&tascam->capture_active)) {
spin_unlock_irqrestore(&tascam->lock, flags);
return -EBUSY;
}
spin_unlock_irqrestore(&tascam->lock, flags);
usb_kill_anchored_urbs(&tascam->playback_anchor);
usb_kill_anchored_urbs(&tascam->feedback_anchor);
usb_kill_anchored_urbs(&tascam->capture_anchor);
atomic_set(&tascam->active_urbs, 0);
err = us144mkii_configure_device_for_rate(tascam, rate);
if (err < 0) {
spin_lock_irqsave(&tascam->lock, flags);
tascam->current_rate = 0;
spin_unlock_irqrestore(&tascam->lock, flags);
return err;
}
spin_lock_irqsave(&tascam->lock, flags);
tascam->current_rate = rate;
spin_unlock_irqrestore(&tascam->lock, flags);
return 0;
}
/**
* tascam_stop_pcm_work_handler() - work handler to stop PCM streams
* @work: pointer to the work_struct
*/
void tascam_stop_pcm_work_handler(struct work_struct *work)
{
struct tascam_card *tascam = container_of(work, struct tascam_card, stop_pcm_work);
if (!tascam->dev)
return;
if (tascam->playback_substream)
snd_pcm_stop(tascam->playback_substream, SNDRV_PCM_STATE_XRUN);
}