Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0
snd-usb-usx2y-y := usbusx2y.o usX2Yhwdep.o usx2yhwdeppcm.o
snd-usb-us122l-y := us122l.o
snd-usb-us144mkii-y := us144mkii.o us144mkii_pcm.o us144mkii_playback.o us144mkii_capture.o us144mkii_midi.o us144mkii_controls.o
obj-$(CONFIG_SND_USB_USX2Y) += snd-usb-usx2y.o
obj-$(CONFIG_SND_USB_US122L) += snd-usb-us122l.o
obj-$(CONFIG_SND_USB_US144MKII) += snd-usb-us144mkii.o
+682
View File
@@ -0,0 +1,682 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2007, 2008 Karsten Wiese <fzu@wemgehoertderstaat.de>
*/
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/usb/audio.h>
#include <linux/module.h>
#include <sound/core.h>
#include <sound/hwdep.h>
#include <sound/pcm.h>
#include <sound/initval.h>
#define MODNAME "US122L"
#include "usb_stream.c"
#include "../usbaudio.h"
#include "../midi.h"
#include "us122l.h"
MODULE_AUTHOR("Karsten Wiese <fzu@wemgehoertderstaat.de>");
MODULE_DESCRIPTION("TASCAM "NAME_ALLCAPS" Version 0.5");
MODULE_LICENSE("GPL");
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */
/* Enable this card */
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for "NAME_ALLCAPS".");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for "NAME_ALLCAPS".");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable "NAME_ALLCAPS".");
/* driver_info flags */
#define US122L_FLAG_US144 BIT(0)
static int snd_us122l_card_used[SNDRV_CARDS];
static int us122l_create_usbmidi(struct snd_card *card)
{
static const struct snd_usb_midi_endpoint_info quirk_data = {
.out_ep = 4,
.in_ep = 3,
.out_cables = 0x001,
.in_cables = 0x001
};
static const struct snd_usb_audio_quirk quirk = {
.vendor_name = "US122L",
.product_name = NAME_ALLCAPS,
.ifnum = 1,
.type = QUIRK_MIDI_US122L,
.data = &quirk_data
};
struct usb_device *dev = US122L(card)->dev;
struct usb_interface *iface = usb_ifnum_to_if(dev, 1);
return snd_usbmidi_create(card, iface,
&US122L(card)->midi_list, &quirk);
}
static int us144_create_usbmidi(struct snd_card *card)
{
static const struct snd_usb_midi_endpoint_info quirk_data = {
.out_ep = 4,
.in_ep = 3,
.out_cables = 0x001,
.in_cables = 0x001
};
static const struct snd_usb_audio_quirk quirk = {
.vendor_name = "US144",
.product_name = NAME_ALLCAPS,
.ifnum = 0,
.type = QUIRK_MIDI_US122L,
.data = &quirk_data
};
struct usb_device *dev = US122L(card)->dev;
struct usb_interface *iface = usb_ifnum_to_if(dev, 0);
return snd_usbmidi_create(card, iface,
&US122L(card)->midi_list, &quirk);
}
static void pt_info_set(struct usb_device *dev, u8 v)
{
usb_control_msg_send(dev, 0, 'I',
USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
v, 0, NULL, 0, 1000, GFP_NOIO);
}
static vm_fault_t usb_stream_hwdep_vm_fault(struct vm_fault *vmf)
{
unsigned long offset;
struct page *page;
void *vaddr;
struct us122l *us122l = vmf->vma->vm_private_data;
struct usb_stream *s;
guard(mutex)(&us122l->mutex);
s = us122l->sk.s;
if (!s)
return VM_FAULT_SIGBUS;
offset = vmf->pgoff << PAGE_SHIFT;
if (offset < PAGE_ALIGN(s->read_size)) {
vaddr = (char *)s + offset;
} else {
offset -= PAGE_ALIGN(s->read_size);
if (offset >= PAGE_ALIGN(s->write_size))
return VM_FAULT_SIGBUS;
vaddr = us122l->sk.write_page + offset;
}
page = virt_to_page(vaddr);
get_page(page);
vmf->page = page;
return 0;
}
static const struct vm_operations_struct usb_stream_hwdep_vm_ops = {
.fault = usb_stream_hwdep_vm_fault,
};
static int usb_stream_hwdep_open(struct snd_hwdep *hw, struct file *file)
{
struct us122l *us122l = hw->private_data;
struct usb_interface *iface;
if (hw->used >= 2)
return -EBUSY;
if (!us122l->first)
us122l->first = file;
if (us122l->is_us144) {
iface = usb_ifnum_to_if(us122l->dev, 0);
usb_autopm_get_interface(iface);
}
iface = usb_ifnum_to_if(us122l->dev, 1);
usb_autopm_get_interface(iface);
return 0;
}
static int usb_stream_hwdep_release(struct snd_hwdep *hw, struct file *file)
{
struct us122l *us122l = hw->private_data;
struct usb_interface *iface;
if (us122l->is_us144) {
iface = usb_ifnum_to_if(us122l->dev, 0);
usb_autopm_put_interface(iface);
}
iface = usb_ifnum_to_if(us122l->dev, 1);
usb_autopm_put_interface(iface);
if (us122l->first == file)
us122l->first = NULL;
guard(mutex)(&us122l->mutex);
if (us122l->master == file)
us122l->master = us122l->slave;
us122l->slave = NULL;
return 0;
}
static int usb_stream_hwdep_mmap(struct snd_hwdep *hw,
struct file *filp, struct vm_area_struct *area)
{
unsigned long size = area->vm_end - area->vm_start;
struct us122l *us122l = hw->private_data;
unsigned long offset;
struct usb_stream *s;
bool read;
offset = area->vm_pgoff << PAGE_SHIFT;
guard(mutex)(&us122l->mutex);
s = us122l->sk.s;
read = offset < s->read_size;
if (read && area->vm_flags & VM_WRITE)
return -EPERM;
/* if userspace tries to mmap beyond end of our buffer, fail */
if (size > PAGE_ALIGN(read ? s->read_size : s->write_size)) {
dev_warn(hw->card->dev, "%s: size %lu > %u\n", __func__,
size, read ? s->read_size : s->write_size);
return -EINVAL;
}
area->vm_ops = &usb_stream_hwdep_vm_ops;
vm_flags_set(area, VM_DONTDUMP);
if (!read)
vm_flags_set(area, VM_DONTEXPAND);
area->vm_private_data = us122l;
return 0;
}
static __poll_t usb_stream_hwdep_poll(struct snd_hwdep *hw,
struct file *file, poll_table *wait)
{
struct us122l *us122l = hw->private_data;
unsigned int *polled;
__poll_t mask;
poll_wait(file, &us122l->sk.sleep, wait);
mask = EPOLLIN | EPOLLOUT | EPOLLWRNORM | EPOLLERR;
if (mutex_trylock(&us122l->mutex)) {
struct usb_stream *s = us122l->sk.s;
if (s && s->state == usb_stream_ready) {
if (us122l->first == file)
polled = &s->periods_polled;
else
polled = &us122l->second_periods_polled;
if (*polled != s->periods_done) {
*polled = s->periods_done;
mask = EPOLLIN | EPOLLOUT | EPOLLWRNORM;
} else {
mask = 0;
}
}
mutex_unlock(&us122l->mutex);
}
return mask;
}
static void us122l_stop(struct us122l *us122l)
{
struct list_head *p;
list_for_each(p, &us122l->midi_list)
snd_usbmidi_input_stop(p);
usb_stream_stop(&us122l->sk);
usb_stream_free(&us122l->sk);
}
static int us122l_set_sample_rate(struct usb_device *dev, int rate)
{
unsigned int ep = 0x81;
unsigned char data[3];
int err;
data[0] = rate;
data[1] = rate >> 8;
data[2] = rate >> 16;
err = usb_control_msg_send(dev, 0, UAC_SET_CUR,
USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT,
UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, data, 3,
1000, GFP_NOIO);
if (err)
dev_err(&dev->dev, "%d: cannot set freq %d to ep 0x%x\n",
dev->devnum, rate, ep);
return err;
}
static bool us122l_start(struct us122l *us122l,
unsigned int rate, unsigned int period_frames)
{
struct list_head *p;
int err;
unsigned int use_packsize = 0;
bool success = false;
if (us122l->dev->speed == USB_SPEED_HIGH) {
/* The us-122l's descriptor defaults to iso max_packsize 78,
which isn't needed for samplerates <= 48000.
Lets save some memory:
*/
switch (rate) {
case 44100:
use_packsize = 36;
break;
case 48000:
use_packsize = 42;
break;
case 88200:
use_packsize = 72;
break;
}
}
if (!usb_stream_new(&us122l->sk, us122l->dev, 1, 2,
rate, use_packsize, period_frames, 6))
goto out;
err = us122l_set_sample_rate(us122l->dev, rate);
if (err < 0) {
us122l_stop(us122l);
dev_err(&us122l->dev->dev, "us122l_set_sample_rate error\n");
goto out;
}
err = usb_stream_start(&us122l->sk);
if (err < 0) {
us122l_stop(us122l);
dev_err(&us122l->dev->dev, "%s error %i\n", __func__, err);
goto out;
}
list_for_each(p, &us122l->midi_list)
snd_usbmidi_input_start(p);
success = true;
out:
return success;
}
static int usb_stream_hwdep_ioctl(struct snd_hwdep *hw, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct usb_stream_config cfg;
struct us122l *us122l = hw->private_data;
struct usb_stream *s;
unsigned int min_period_frames;
int err = 0;
bool high_speed;
if (cmd != SNDRV_USB_STREAM_IOCTL_SET_PARAMS)
return -ENOTTY;
if (copy_from_user(&cfg, (void __user *)arg, sizeof(cfg)))
return -EFAULT;
if (cfg.version != USB_STREAM_INTERFACE_VERSION)
return -ENXIO;
high_speed = us122l->dev->speed == USB_SPEED_HIGH;
if ((cfg.sample_rate != 44100 && cfg.sample_rate != 48000 &&
(!high_speed ||
(cfg.sample_rate != 88200 && cfg.sample_rate != 96000))) ||
cfg.frame_size != 6 ||
cfg.period_frames > 0x3000)
return -EINVAL;
switch (cfg.sample_rate) {
case 44100:
min_period_frames = 48;
break;
case 48000:
min_period_frames = 52;
break;
default:
min_period_frames = 104;
break;
}
if (!high_speed)
min_period_frames <<= 1;
if (cfg.period_frames < min_period_frames)
return -EINVAL;
snd_power_wait(hw->card);
guard(mutex)(&us122l->mutex);
s = us122l->sk.s;
if (!us122l->master) {
us122l->master = file;
} else if (us122l->master != file) {
if (!s || memcmp(&cfg, &s->cfg, sizeof(cfg))) {
err = -EIO;
goto unlock;
}
us122l->slave = file;
}
if (!s || memcmp(&cfg, &s->cfg, sizeof(cfg)) ||
s->state == usb_stream_xrun) {
us122l_stop(us122l);
if (!us122l_start(us122l, cfg.sample_rate, cfg.period_frames))
err = -EIO;
else
err = 1;
}
unlock:
wake_up_all(&us122l->sk.sleep);
return err;
}
#define SND_USB_STREAM_ID "USB STREAM"
static int usb_stream_hwdep_new(struct snd_card *card)
{
int err;
struct snd_hwdep *hw;
struct usb_device *dev = US122L(card)->dev;
err = snd_hwdep_new(card, SND_USB_STREAM_ID, 0, &hw);
if (err < 0)
return err;
hw->iface = SNDRV_HWDEP_IFACE_USB_STREAM;
hw->private_data = US122L(card);
hw->ops.open = usb_stream_hwdep_open;
hw->ops.release = usb_stream_hwdep_release;
hw->ops.ioctl = usb_stream_hwdep_ioctl;
hw->ops.ioctl_compat = usb_stream_hwdep_ioctl;
hw->ops.mmap = usb_stream_hwdep_mmap;
hw->ops.poll = usb_stream_hwdep_poll;
sprintf(hw->name, "/dev/bus/usb/%03d/%03d/hwdeppcm",
dev->bus->busnum, dev->devnum);
return 0;
}
static bool us122l_create_card(struct snd_card *card)
{
int err;
struct us122l *us122l = US122L(card);
if (us122l->is_us144) {
err = usb_set_interface(us122l->dev, 0, 1);
if (err) {
dev_err(card->dev, "usb_set_interface error\n");
return false;
}
}
err = usb_set_interface(us122l->dev, 1, 1);
if (err) {
dev_err(card->dev, "usb_set_interface error\n");
return false;
}
pt_info_set(us122l->dev, 0x11);
pt_info_set(us122l->dev, 0x10);
if (!us122l_start(us122l, 44100, 256))
return false;
if (us122l->is_us144)
err = us144_create_usbmidi(card);
else
err = us122l_create_usbmidi(card);
if (err < 0) {
dev_err(card->dev, "us122l_create_usbmidi error %i\n", err);
goto stop;
}
err = usb_stream_hwdep_new(card);
if (err < 0) {
/* release the midi resources */
struct list_head *p;
list_for_each(p, &us122l->midi_list)
snd_usbmidi_disconnect(p);
goto stop;
}
return true;
stop:
us122l_stop(us122l);
return false;
}
static void snd_us122l_free(struct snd_card *card)
{
struct us122l *us122l = US122L(card);
int index = us122l->card_index;
if (index >= 0 && index < SNDRV_CARDS)
snd_us122l_card_used[index] = 0;
}
static int usx2y_create_card(struct usb_device *device,
struct usb_interface *intf,
struct snd_card **cardp,
unsigned long flags)
{
int dev;
struct snd_card *card;
int err;
for (dev = 0; dev < SNDRV_CARDS; ++dev)
if (enable[dev] && !snd_us122l_card_used[dev])
break;
if (dev >= SNDRV_CARDS)
return -ENODEV;
err = snd_card_new(&intf->dev, index[dev], id[dev], THIS_MODULE,
sizeof(struct us122l), &card);
if (err < 0)
return err;
snd_us122l_card_used[US122L(card)->card_index = dev] = 1;
card->private_free = snd_us122l_free;
US122L(card)->dev = device;
mutex_init(&US122L(card)->mutex);
US122L(card)->sk.dev = device;
init_waitqueue_head(&US122L(card)->sk.sleep);
US122L(card)->is_us144 = flags & US122L_FLAG_US144;
INIT_LIST_HEAD(&US122L(card)->midi_list);
strscpy(card->driver, "USB "NAME_ALLCAPS"");
sprintf(card->shortname, "TASCAM "NAME_ALLCAPS"");
sprintf(card->longname, "%s (%x:%x if %d at %03d/%03d)",
card->shortname,
le16_to_cpu(device->descriptor.idVendor),
le16_to_cpu(device->descriptor.idProduct),
0,
US122L(card)->dev->bus->busnum,
US122L(card)->dev->devnum
);
*cardp = card;
return 0;
}
static int us122l_usb_probe(struct usb_interface *intf,
const struct usb_device_id *device_id,
struct snd_card **cardp)
{
struct usb_device *device = interface_to_usbdev(intf);
struct snd_card *card;
int err;
err = usx2y_create_card(device, intf, &card, device_id->driver_info);
if (err < 0)
return err;
if (!us122l_create_card(card)) {
snd_card_free(card);
return -EINVAL;
}
err = snd_card_register(card);
if (err < 0) {
snd_card_free(card);
return err;
}
*cardp = card;
return 0;
}
static int snd_us122l_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *device = interface_to_usbdev(intf);
struct snd_card *card;
int err;
if (id->driver_info & US122L_FLAG_US144 &&
device->speed == USB_SPEED_HIGH) {
dev_err(&device->dev, "disable ehci-hcd to run US-144\n");
return -ENODEV;
}
if (intf->cur_altsetting->desc.bInterfaceNumber != 1)
return 0;
err = us122l_usb_probe(intf, id, &card);
if (err < 0)
return err;
usb_set_intfdata(intf, card);
return 0;
}
static void snd_us122l_disconnect(struct usb_interface *intf)
{
struct snd_card *card;
struct us122l *us122l;
struct list_head *p;
card = usb_get_intfdata(intf);
if (!card)
return;
snd_card_disconnect(card);
us122l = US122L(card);
scoped_guard(mutex, &us122l->mutex) {
us122l_stop(us122l);
}
/* release the midi resources */
list_for_each(p, &us122l->midi_list) {
snd_usbmidi_disconnect(p);
}
snd_card_free_when_closed(card);
}
static int snd_us122l_suspend(struct usb_interface *intf, pm_message_t message)
{
struct snd_card *card;
struct us122l *us122l;
struct list_head *p;
card = usb_get_intfdata(intf);
if (!card)
return 0;
snd_power_change_state(card, SNDRV_CTL_POWER_D3hot);
us122l = US122L(card);
if (!us122l)
return 0;
list_for_each(p, &us122l->midi_list)
snd_usbmidi_input_stop(p);
guard(mutex)(&us122l->mutex);
usb_stream_stop(&us122l->sk);
return 0;
}
static int snd_us122l_resume(struct usb_interface *intf)
{
struct snd_card *card;
struct us122l *us122l;
struct list_head *p;
int err;
card = usb_get_intfdata(intf);
if (!card)
return 0;
us122l = US122L(card);
if (!us122l)
return 0;
guard(mutex)(&us122l->mutex);
/* needed, doesn't restart without: */
if (us122l->is_us144) {
err = usb_set_interface(us122l->dev, 0, 1);
if (err) {
dev_err(&us122l->dev->dev, "usb_set_interface error\n");
goto unlock;
}
}
err = usb_set_interface(us122l->dev, 1, 1);
if (err) {
dev_err(&us122l->dev->dev, "usb_set_interface error\n");
goto unlock;
}
pt_info_set(us122l->dev, 0x11);
pt_info_set(us122l->dev, 0x10);
err = us122l_set_sample_rate(us122l->dev,
us122l->sk.s->cfg.sample_rate);
if (err < 0) {
dev_err(&us122l->dev->dev, "us122l_set_sample_rate error\n");
goto unlock;
}
err = usb_stream_start(&us122l->sk);
if (err)
goto unlock;
list_for_each(p, &us122l->midi_list)
snd_usbmidi_input_start(p);
unlock:
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
return err;
}
static const struct usb_device_id snd_us122l_usb_id_table[] = {
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = 0x0644,
.idProduct = USB_ID_US122L
},
{ /* US-144 only works at USB1.1! Disable module ehci-hcd. */
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = 0x0644,
.idProduct = USB_ID_US144,
.driver_info = US122L_FLAG_US144
},
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = 0x0644,
.idProduct = USB_ID_US122MKII
},
{ /* terminator */ }
};
MODULE_DEVICE_TABLE(usb, snd_us122l_usb_id_table);
static struct usb_driver snd_us122l_usb_driver = {
.name = "snd-usb-us122l",
.probe = snd_us122l_probe,
.disconnect = snd_us122l_disconnect,
.suspend = snd_us122l_suspend,
.resume = snd_us122l_resume,
.reset_resume = snd_us122l_resume,
.id_table = snd_us122l_usb_id_table,
.supports_autosuspend = 1
};
module_usb_driver(snd_us122l_usb_driver);
+32
View File
@@ -0,0 +1,32 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef US122L_H
#define US122L_H
struct us122l {
struct usb_device *dev;
int card_index;
int stride;
struct usb_stream_kernel sk;
struct mutex mutex;
struct file *first;
unsigned int second_periods_polled;
struct file *master;
struct file *slave;
struct list_head midi_list;
bool is_us144;
};
#define US122L(c) ((struct us122l *)(c)->private_data)
#define NAME_ALLCAPS "US-122L"
#define USB_ID_US122L 0x800E
#define USB_ID_US144 0x800F
#define USB_ID_US122MKII 0x8021
#define USB_ID_US144MKII 0x8020
#endif
+629
View File
@@ -0,0 +1,629 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2025 Šerif Rami <ramiserifpersia@gmail.com>
/*
* ALSA Driver for TASCAM US-144MKII Audio Interface
*/
#include "us144mkii.h"
MODULE_AUTHOR("Šerif Rami <ramiserifpersia@gmail.com>");
MODULE_DESCRIPTION("ALSA Driver for TASCAM US-144MKII");
MODULE_LICENSE("GPL");
/*
* Module parameters for ALSA card instantiation.
*
* These parameters allow users to configure how the ALSA sound card
* for the TASCAM US-144MKII is instantiated.
*
* @param index: Array of integers specifying the ALSA card index for each
* device. Defaults to -1 (automatic).
* @param id: Array of strings specifying the ALSA card ID for each device.
* Defaults to "US144MKII".
* @param enable: Array of booleans to enable or disable each device.
* Defaults to {1, 0, ..., 0} (first device enabled).
* @param dev_idx: Internal counter for the number of TASCAM devices probed.
*/
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static bool enable[SNDRV_CARDS] = { 1, [1 ...(SNDRV_CARDS - 1)] = 0 };
static int dev_idx;
static int tascam_probe(struct usb_interface *intf,
const struct usb_device_id *usb_id);
static void tascam_disconnect(struct usb_interface *intf);
static int tascam_suspend(struct usb_interface *intf, pm_message_t message);
static int tascam_resume(struct usb_interface *intf);
void tascam_free_urbs(struct tascam_card *tascam)
{
int i;
usb_kill_anchored_urbs(&tascam->playback_anchor);
for (i = 0; i < NUM_PLAYBACK_URBS; i++) {
if (tascam->playback_urbs[i]) {
usb_free_coherent(
tascam->dev, tascam->playback_urb_alloc_size,
tascam->playback_urbs[i]->transfer_buffer,
tascam->playback_urbs[i]->transfer_dma);
usb_free_urb(tascam->playback_urbs[i]);
tascam->playback_urbs[i] = NULL;
}
}
usb_kill_anchored_urbs(&tascam->feedback_anchor);
for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
if (tascam->feedback_urbs[i]) {
usb_free_coherent(
tascam->dev, tascam->feedback_urb_alloc_size,
tascam->feedback_urbs[i]->transfer_buffer,
tascam->feedback_urbs[i]->transfer_dma);
usb_free_urb(tascam->feedback_urbs[i]);
tascam->feedback_urbs[i] = NULL;
}
}
usb_kill_anchored_urbs(&tascam->capture_anchor);
for (i = 0; i < NUM_CAPTURE_URBS; i++) {
if (tascam->capture_urbs[i]) {
usb_free_coherent(
tascam->dev, tascam->capture_urb_alloc_size,
tascam->capture_urbs[i]->transfer_buffer,
tascam->capture_urbs[i]->transfer_dma);
usb_free_urb(tascam->capture_urbs[i]);
tascam->capture_urbs[i] = NULL;
}
}
usb_kill_anchored_urbs(&tascam->midi_in_anchor);
for (i = 0; i < NUM_MIDI_IN_URBS; i++) {
if (tascam->midi_in_urbs[i]) {
usb_free_coherent(
tascam->dev, MIDI_IN_BUF_SIZE,
tascam->midi_in_urbs[i]->transfer_buffer,
tascam->midi_in_urbs[i]->transfer_dma);
usb_free_urb(tascam->midi_in_urbs[i]);
tascam->midi_in_urbs[i] = NULL;
}
}
usb_kill_anchored_urbs(&tascam->midi_out_anchor);
for (i = 0; i < NUM_MIDI_OUT_URBS; i++) {
if (tascam->midi_out_urbs[i]) {
usb_free_coherent(
tascam->dev, MIDI_OUT_BUF_SIZE,
tascam->midi_out_urbs[i]->transfer_buffer,
tascam->midi_out_urbs[i]->transfer_dma);
usb_free_urb(tascam->midi_out_urbs[i]);
tascam->midi_out_urbs[i] = NULL;
}
}
kfree(tascam->capture_routing_buffer);
tascam->capture_routing_buffer = NULL;
kfree(tascam->capture_decode_dst_block);
tascam->capture_decode_dst_block = NULL;
kfree(tascam->capture_decode_raw_block);
tascam->capture_decode_raw_block = NULL;
kfree(tascam->capture_ring_buffer);
tascam->capture_ring_buffer = NULL;
}
int tascam_alloc_urbs(struct tascam_card *tascam)
{
int i;
size_t max_packet_size;
max_packet_size = ((96000 / 8000) + 2) * BYTES_PER_FRAME;
tascam->playback_urb_alloc_size =
max_packet_size * PLAYBACK_URB_PACKETS;
for (i = 0; i < NUM_PLAYBACK_URBS; i++) {
struct urb *urb =
usb_alloc_urb(PLAYBACK_URB_PACKETS, GFP_KERNEL);
if (!urb)
goto error;
tascam->playback_urbs[i] = urb;
urb->transfer_buffer = usb_alloc_coherent(
tascam->dev, tascam->playback_urb_alloc_size,
GFP_KERNEL, &urb->transfer_dma);
if (!urb->transfer_buffer)
goto error;
urb->dev = tascam->dev;
urb->pipe = usb_sndisocpipe(tascam->dev, EP_AUDIO_OUT);
urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
urb->interval = 1;
urb->context = tascam;
urb->complete = playback_urb_complete;
}
tascam->feedback_urb_alloc_size =
FEEDBACK_PACKET_SIZE * FEEDBACK_URB_PACKETS;
for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
struct urb *f_urb =
usb_alloc_urb(FEEDBACK_URB_PACKETS, GFP_KERNEL);
if (!f_urb)
goto error;
tascam->feedback_urbs[i] = f_urb;
f_urb->transfer_buffer = usb_alloc_coherent(
tascam->dev, tascam->feedback_urb_alloc_size,
GFP_KERNEL, &f_urb->transfer_dma);
if (!f_urb->transfer_buffer)
goto error;
f_urb->dev = tascam->dev;
f_urb->pipe =
usb_rcvisocpipe(tascam->dev, EP_PLAYBACK_FEEDBACK);
f_urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
f_urb->interval = 4;
f_urb->context = tascam;
f_urb->complete = feedback_urb_complete;
}
tascam->capture_urb_alloc_size = CAPTURE_URB_SIZE;
for (i = 0; i < NUM_CAPTURE_URBS; i++) {
struct urb *c_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!c_urb)
goto error;
tascam->capture_urbs[i] = c_urb;
c_urb->transfer_buffer = usb_alloc_coherent(
tascam->dev, tascam->capture_urb_alloc_size, GFP_KERNEL,
&c_urb->transfer_dma);
if (!c_urb->transfer_buffer)
goto error;
usb_fill_bulk_urb(c_urb, tascam->dev,
usb_rcvbulkpipe(tascam->dev, EP_AUDIO_IN),
c_urb->transfer_buffer,
tascam->capture_urb_alloc_size,
capture_urb_complete, tascam);
c_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
}
/* MIDI URB and buffer allocation */
for (i = 0; i < NUM_MIDI_IN_URBS; i++) {
struct urb *m_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!m_urb)
goto error;
tascam->midi_in_urbs[i] = m_urb;
m_urb->transfer_buffer =
usb_alloc_coherent(tascam->dev, MIDI_IN_BUF_SIZE,
GFP_KERNEL, &m_urb->transfer_dma);
if (!m_urb->transfer_buffer)
goto error;
usb_fill_bulk_urb(m_urb, tascam->dev,
usb_rcvbulkpipe(tascam->dev, EP_MIDI_IN),
m_urb->transfer_buffer, MIDI_IN_BUF_SIZE,
tascam_midi_in_urb_complete, tascam);
m_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
}
for (i = 0; i < NUM_MIDI_OUT_URBS; i++) {
struct urb *m_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!m_urb)
goto error;
tascam->midi_out_urbs[i] = m_urb;
m_urb->transfer_buffer =
usb_alloc_coherent(tascam->dev, MIDI_OUT_BUF_SIZE,
GFP_KERNEL, &m_urb->transfer_dma);
if (!m_urb->transfer_buffer)
goto error;
usb_fill_bulk_urb(m_urb, tascam->dev,
usb_sndbulkpipe(tascam->dev, EP_MIDI_OUT),
m_urb->transfer_buffer,
0, /* length set later */
tascam_midi_out_urb_complete, tascam);
m_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
}
tascam->capture_ring_buffer =
kmalloc(CAPTURE_RING_BUFFER_SIZE, GFP_KERNEL);
if (!tascam->capture_ring_buffer)
goto error;
tascam->capture_decode_raw_block =
kmalloc(RAW_BYTES_PER_DECODE_BLOCK, GFP_KERNEL);
if (!tascam->capture_decode_raw_block)
goto error;
tascam->capture_decode_dst_block =
kmalloc(FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME *
DECODED_SAMPLE_SIZE,
GFP_KERNEL);
if (!tascam->capture_decode_dst_block)
goto error;
tascam->capture_routing_buffer =
kmalloc(FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME *
DECODED_SAMPLE_SIZE,
GFP_KERNEL);
if (!tascam->capture_routing_buffer)
goto error;
return 0;
error:
dev_err(tascam->card->dev, "Failed to allocate URBs\n");
tascam_free_urbs(tascam);
return -ENOMEM;
}
void tascam_stop_work_handler(struct work_struct *work)
{
struct tascam_card *tascam =
container_of(work, struct tascam_card, stop_work);
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);
}
/*
* tascam_card_private_free() - Frees private data associated with the sound
* card.
* @card: Pointer to the ALSA sound card instance.
*
* This function is called when the sound card is being freed. It releases
* resources allocated for the tascam_card structure, including the MIDI
* input FIFO and decrements the USB device reference count.
*/
static void tascam_card_private_free(struct snd_card *card)
{
struct tascam_card *tascam = card->private_data;
if (tascam) {
kfifo_free(&tascam->midi_in_fifo);
if (tascam->dev) {
usb_put_dev(tascam->dev);
tascam->dev = NULL;
}
}
}
/*
* tascam_suspend() - Handles device suspension.
* @intf: The USB interface being suspended.
* @message: Power management message.
*
* This function is called when the device is suspended. It stops all active
* streams, kills all URBs, and sends a vendor-specific deep sleep command
* to the device to ensure a stable low-power state.
*
* Return: 0 on success.
*/
static int tascam_suspend(struct usb_interface *intf, pm_message_t message)
{
struct tascam_card *tascam = usb_get_intfdata(intf);
if (!tascam)
return 0;
snd_pcm_suspend_all(tascam->pcm);
cancel_work_sync(&tascam->stop_work);
cancel_work_sync(&tascam->capture_work);
cancel_work_sync(&tascam->midi_in_work);
cancel_work_sync(&tascam->midi_out_work);
cancel_work_sync(&tascam->stop_pcm_work);
usb_kill_anchored_urbs(&tascam->playback_anchor);
usb_kill_anchored_urbs(&tascam->capture_anchor);
usb_kill_anchored_urbs(&tascam->feedback_anchor);
usb_kill_anchored_urbs(&tascam->midi_in_anchor);
usb_kill_anchored_urbs(&tascam->midi_out_anchor);
dev_info(&intf->dev, "sending deep sleep command\n");
int err = usb_control_msg(tascam->dev, usb_sndctrlpipe(tascam->dev, 0),
VENDOR_REQ_DEEP_SLEEP, RT_H2D_VENDOR_DEV,
0x0000, 0x0000, NULL, 0, USB_CTRL_TIMEOUT_MS);
if (err < 0)
dev_err(&intf->dev, "deep sleep command failed: %d\n", err);
return 0;
}
/*
* tascam_resume() - Handles device resumption from suspend.
* @intf: The USB interface being resumed.
*
* This function is called when the device resumes from suspend. It
* re-establishes the active USB interface settings and re-configures the sample
* rate if it was previously active.
*
* Return: 0 on success, or a negative error code on failure.
*/
static int tascam_resume(struct usb_interface *intf)
{
struct tascam_card *tascam = usb_get_intfdata(intf);
int err;
if (!tascam)
return 0;
dev_info(&intf->dev, "resuming TASCAM US-144MKII\n");
/*
* The device requires a full re-initialization sequence upon resume.
* First, re-establish the active USB interface settings.
*/
err = usb_set_interface(tascam->dev, 0, 1);
if (err < 0) {
dev_err(&intf->dev,
"resume: failed to set alt setting on intf 0: %d\n",
err);
return err;
}
err = usb_set_interface(tascam->dev, 1, 1);
if (err < 0) {
dev_err(&intf->dev,
"resume: failed to set alt setting on intf 1: %d\n",
err);
return err;
}
/* Re-configure the sample rate if one was previously active */
if (tascam->current_rate > 0)
us144mkii_configure_device_for_rate(tascam,
tascam->current_rate);
return 0;
}
static void tascam_error_timer(struct timer_list *t)
{
struct tascam_card *tascam =
container_of(t, struct tascam_card, error_timer);
if (atomic_read(&tascam->midi_in_active))
schedule_work(&tascam->midi_in_work);
if (atomic_read(&tascam->midi_out_active))
schedule_work(&tascam->midi_out_work);
}
/*
* tascam_probe() - Probes for the TASCAM US-144MKII device.
* @intf: The USB interface being probed.
* @usb_id: The USB device ID.
*
* This function is the entry point for the USB driver when a matching device
* is found. It performs initial device setup, including:
* - Checking for the second interface (MIDI) and associating it.
* - Performing a vendor-specific handshake with the device.
* - Setting alternate settings for USB interfaces.
* - Creating and registering the ALSA sound card, PCM device, and MIDI device.
* - Allocating and initializing URBs for audio and MIDI transfers.
*
* Return: 0 on success, or a negative error code on failure.
*/
static int tascam_probe(struct usb_interface *intf,
const struct usb_device_id *usb_id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct snd_card *card;
struct tascam_card *tascam;
int err;
if (dev->speed != USB_SPEED_HIGH)
dev_info(
&dev->dev,
"Device is connected to a USB 1.1 port, this is not supported.\n");
/* The device has two interfaces; we drive both from this driver. */
if (intf->cur_altsetting->desc.bInterfaceNumber == 1) {
struct usb_interface *intf_zero = usb_ifnum_to_if(dev, 0);
if (!intf_zero)
return -ENODEV;
tascam = usb_get_intfdata(intf_zero);
if (tascam) {
usb_set_intfdata(intf, tascam);
tascam->iface1 = intf;
}
return 0; /* Let the core handle this interface */
}
if (dev_idx >= SNDRV_CARDS) {
dev_err(&dev->dev, "Too many TASCAM devices present");
return -ENODEV;
}
if (!enable[dev_idx]) {
dev_info(&dev->dev, "TASCAM US-144MKII device disabled");
return -ENOENT;
}
char *handshake_buf __free(kfree) =
kmalloc(1, GFP_KERNEL);
if (!handshake_buf)
return -ENOMEM;
/* Perform vendor-specific handshake */
err = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
VENDOR_REQ_MODE_CONTROL, RT_D2H_VENDOR_DEV,
MODE_VAL_HANDSHAKE_READ, 0x0000, handshake_buf, 1,
USB_CTRL_TIMEOUT_MS);
if (err < 0) {
dev_err(&dev->dev, "Handshake read failed with %d\n", err);
return err;
}
if (handshake_buf[0] != 0x12 && handshake_buf[0] != 0x16 &&
handshake_buf[0] != 0x30 && handshake_buf[0] != 0x32) {
dev_err(&dev->dev, "Unexpected handshake value: 0x%x\n",
handshake_buf[0]);
return -ENODEV;
}
/* Set alternate settings to enable audio/MIDI endpoints */
err = usb_set_interface(dev, 0, 1);
if (err < 0) {
dev_err(&dev->dev,
"Failed to set alt setting 1 on interface 0: %d\n",
err);
return err;
}
err = usb_set_interface(dev, 1, 1);
if (err < 0) {
dev_err(&dev->dev,
"Failed to set alt setting 1 on interface 1: %d\n",
err);
return err;
}
err = snd_card_new(&dev->dev, index[dev_idx], id[dev_idx], THIS_MODULE,
sizeof(struct tascam_card), &card);
if (err < 0) {
dev_err(&dev->dev, "Failed to create sound card instance\n");
return err;
}
tascam = card->private_data;
card->private_free = tascam_card_private_free;
tascam->dev = usb_get_dev(dev);
tascam->card = card;
tascam->iface0 = intf;
tascam->digital_out_source = 1;
tascam->capture_34_source = 1;
spin_lock_init(&tascam->lock);
spin_lock_init(&tascam->midi_in_lock);
spin_lock_init(&tascam->midi_out_lock);
init_usb_anchor(&tascam->playback_anchor);
init_usb_anchor(&tascam->capture_anchor);
init_usb_anchor(&tascam->feedback_anchor);
init_usb_anchor(&tascam->midi_in_anchor);
init_usb_anchor(&tascam->midi_out_anchor);
timer_setup(&tascam->error_timer, tascam_error_timer, 0);
INIT_WORK(&tascam->stop_work, tascam_stop_work_handler);
INIT_WORK(&tascam->stop_pcm_work, tascam_stop_pcm_work_handler);
INIT_WORK(&tascam->capture_work, tascam_capture_work_handler);
init_completion(&tascam->midi_out_drain_completion);
if (kfifo_alloc(&tascam->midi_in_fifo, MIDI_IN_FIFO_SIZE, GFP_KERNEL)) {
snd_card_free(card);
return -ENOMEM;
}
strscpy(card->driver, DRIVER_NAME, sizeof(card->driver));
if (le16_to_cpu(dev->descriptor.idProduct) == USB_PID_TASCAM_US144) {
strscpy(card->shortname, "TASCAM US-144",
sizeof(card->shortname));
} else if (le16_to_cpu(dev->descriptor.idProduct) == USB_PID_TASCAM_US144MKII) {
strscpy(card->shortname, "TASCAM US-144MKII",
sizeof(card->shortname));
} else {
strscpy(card->shortname, "TASCAM Unknown",
sizeof(card->shortname));
}
snprintf(card->longname, sizeof(card->longname), "%s (%04x:%04x) at %s",
card->shortname, USB_VID_TASCAM, dev->descriptor.idProduct,
dev_name(&dev->dev));
err = snd_pcm_new(card, "US144MKII PCM", 0, 1, 1, &tascam->pcm);
if (err < 0)
goto free_card;
tascam->pcm->private_data = tascam;
strscpy(tascam->pcm->name, "US144MKII PCM", sizeof(tascam->pcm->name));
err = tascam_init_pcm(tascam->pcm);
if (err < 0)
goto free_card;
err = tascam_create_midi(tascam);
if (err < 0)
goto free_card;
err = tascam_create_controls(tascam);
if (err < 0)
goto free_card;
err = tascam_alloc_urbs(tascam);
if (err < 0)
goto free_card;
err = snd_card_register(card);
if (err < 0)
goto free_card;
usb_set_intfdata(intf, tascam);
dev_idx++;
return 0;
free_card:
tascam_free_urbs(tascam);
snd_card_free(card);
return err;
}
/*
* tascam_disconnect() - Disconnects the TASCAM US-144MKII device.
* @intf: The USB interface being disconnected.
*
* This function is called when the device is disconnected from the system.
* It cleans up all allocated resources, including killing URBs, freeing
* the sound card, and releasing memory.
*/
static void tascam_disconnect(struct usb_interface *intf)
{
struct tascam_card *tascam = usb_get_intfdata(intf);
if (!tascam)
return;
if (intf->cur_altsetting->desc.bInterfaceNumber == 0) {
snd_card_disconnect(tascam->card);
/*
* Kill the URBs before cancelling the work, so a late URB
* completion cannot re-arm a work that then runs after
* snd_card_free().
*/
usb_kill_anchored_urbs(&tascam->playback_anchor);
usb_kill_anchored_urbs(&tascam->capture_anchor);
usb_kill_anchored_urbs(&tascam->feedback_anchor);
usb_kill_anchored_urbs(&tascam->midi_in_anchor);
usb_kill_anchored_urbs(&tascam->midi_out_anchor);
cancel_work_sync(&tascam->stop_work);
cancel_work_sync(&tascam->capture_work);
cancel_work_sync(&tascam->midi_in_work);
cancel_work_sync(&tascam->midi_out_work);
cancel_work_sync(&tascam->stop_pcm_work);
timer_delete_sync(&tascam->error_timer);
tascam_free_urbs(tascam);
snd_card_free(tascam->card);
dev_idx--;
}
}
static const struct usb_device_id tascam_usb_ids[] = {
{ USB_DEVICE(USB_VID_TASCAM, USB_PID_TASCAM_US144) },
{ USB_DEVICE(USB_VID_TASCAM, USB_PID_TASCAM_US144MKII) },
{ /* Terminating entry */ }
};
MODULE_DEVICE_TABLE(usb, tascam_usb_ids);
static struct usb_driver tascam_alsa_driver = {
.name = DRIVER_NAME,
.probe = tascam_probe,
.disconnect = tascam_disconnect,
.suspend = tascam_suspend,
.resume = tascam_resume,
.id_table = tascam_usb_ids,
};
module_usb_driver(tascam_alsa_driver);
+367
View File
@@ -0,0 +1,367 @@
/* SPDX-License-Identifier: GPL-2.0-only */
// Copyright (c) 2025 Šerif Rami <ramiserifpersia@gmail.com>
#ifndef __US144MKII_H
#define __US144MKII_H
#include <linux/kfifo.h>
#include <linux/timer.h>
#include <linux/usb.h>
#include <linux/workqueue.h>
#include <sound/control.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/rawmidi.h>
#define DRIVER_NAME "us144mkii"
/* --- USB Device Identification --- */
#define USB_VID_TASCAM 0x0644
#define USB_PID_TASCAM_US144 0x800f
#define USB_PID_TASCAM_US144MKII 0x8020
/* --- USB Endpoints (Alternate Setting 1) --- */
#define EP_PLAYBACK_FEEDBACK 0x81
#define EP_AUDIO_OUT 0x02
#define EP_MIDI_IN 0x83
#define EP_MIDI_OUT 0x04
#define EP_AUDIO_IN 0x86
/* --- USB Control Message Protocol --- */
#define RT_H2D_CLASS_EP (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT)
#define RT_D2H_CLASS_EP (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT)
#define RT_H2D_VENDOR_DEV (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE)
#define RT_D2H_VENDOR_DEV (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE)
enum uac_request {
UAC_SET_CUR = 0x01,
UAC_GET_CUR = 0x81,
};
enum uac_control_selector {
UAC_SAMPLING_FREQ_CONTROL = 0x0100,
};
enum tascam_vendor_request {
VENDOR_REQ_REGISTER_WRITE = 0x41,
VENDOR_REQ_DEEP_SLEEP = 0x44,
VENDOR_REQ_MODE_CONTROL = 0x49,
};
enum tascam_mode_value {
MODE_VAL_HANDSHAKE_READ = 0x0000,
MODE_VAL_CONFIG = 0x0010,
MODE_VAL_STREAM_START = 0x0030,
};
#define HANDSHAKE_SUCCESS_VAL 0x12
enum tascam_register {
REG_ADDR_UNKNOWN_0D = 0x0d04,
REG_ADDR_UNKNOWN_0E = 0x0e00,
REG_ADDR_UNKNOWN_0F = 0x0f00,
REG_ADDR_RATE_44100 = 0x1000,
REG_ADDR_RATE_48000 = 0x1002,
REG_ADDR_RATE_88200 = 0x1008,
REG_ADDR_RATE_96000 = 0x100a,
REG_ADDR_UNKNOWN_11 = 0x110b,
};
#define REG_VAL_ENABLE 0x0101
/* --- URB Configuration --- */
#define NUM_PLAYBACK_URBS 4
#define PLAYBACK_URB_PACKETS 8
#define NUM_FEEDBACK_URBS 4
#define FEEDBACK_URB_PACKETS 1
#define FEEDBACK_PACKET_SIZE 3
#define NUM_CAPTURE_URBS 8
#define CAPTURE_URB_SIZE 512
#define CAPTURE_RING_BUFFER_SIZE (CAPTURE_URB_SIZE * NUM_CAPTURE_URBS * 4)
#define NUM_MIDI_IN_URBS 4
#define MIDI_IN_BUF_SIZE 64
#define MIDI_IN_FIFO_SIZE (MIDI_IN_BUF_SIZE * NUM_MIDI_IN_URBS)
#define MIDI_OUT_BUF_SIZE 64
#define NUM_MIDI_OUT_URBS 4
#define USB_CTRL_TIMEOUT_MS 1000
#define FEEDBACK_SYNC_LOSS_THRESHOLD 41
/* --- Audio Format Configuration --- */
#define BYTES_PER_SAMPLE 3
#define NUM_CHANNELS 4
#define BYTES_PER_FRAME (NUM_CHANNELS * BYTES_PER_SAMPLE)
#define FEEDBACK_ACCUMULATOR_SIZE 128
/* --- Capture Decoding Defines --- */
#define DECODED_CHANNELS_PER_FRAME 4
#define DECODED_SAMPLE_SIZE 4
#define FRAMES_PER_DECODE_BLOCK 8
#define RAW_BYTES_PER_DECODE_BLOCK 512
/**
* struct us144mkii_frame_pattern_observer - State for dynamic feedback
* patterns.
* @sample_rate_khz: The current sample rate in kHz.
* @base_feedback_value: The nominal feedback value for the current rate.
* @feedback_offset: An offset to align the feedback value range.
* @full_frame_patterns: A 2D array of pre-calculated packet size patterns.
* @current_index: The current index into the pattern array.
* @previous_index: The previous index, used for state tracking.
* @sync_locked: A flag indicating if the pattern has locked to the stream.
*/
struct us144mkii_frame_pattern_observer {
unsigned int sample_rate_khz;
unsigned int base_feedback_value;
int feedback_offset;
unsigned int full_frame_patterns[5][8];
unsigned int current_index;
unsigned int previous_index;
bool sync_locked;
};
/**
* struct tascam_card - Main driver data structure for the TASCAM US-144MKII.
* @dev: Pointer to the USB device.
* @iface0: Pointer to USB interface 0 (audio).
* @iface1: Pointer to USB interface 1 (MIDI).
* @card: Pointer to the ALSA sound card instance.
* @pcm: Pointer to the ALSA PCM device.
* @rmidi: Pointer to the ALSA rawmidi device.
*
* @playback_substream: Pointer to the active playback PCM substream.
* @playback_urbs: Array of URBs for playback.
* @playback_urb_alloc_size: Size of allocated buffer for each playback URB.
* @feedback_urbs: Array of URBs for feedback.
* @feedback_urb_alloc_size: Size of allocated buffer for each feedback URB.
* @playback_active: Atomic flag indicating if playback is active.
* @playback_frames_consumed: Total frames consumed by playback.
* @driver_playback_pos: Current position in the ALSA playback buffer (frames).
* @last_period_pos: Last reported period position for playback.
*
* @capture_substream: Pointer to the active capture PCM substream.
* @capture_urbs: Array of URBs for capture.
* @capture_urb_alloc_size: Size of allocated buffer for each capture URB.
* @capture_active: Atomic flag indicating if capture is active.
* @driver_capture_pos: Current position in the ALSA capture buffer (frames).
* @capture_frames_processed: Total frames processed for capture.
* @last_capture_period_pos: Last reported period position for capture.
* @capture_ring_buffer: Ring buffer for raw capture data from USB.
* @capture_ring_buffer_read_ptr: Read pointer for the capture ring buffer.
* @capture_ring_buffer_write_ptr: Write pointer for the capture ring buffer.
* @capture_decode_raw_block: Buffer for a raw 512-byte capture block.
* @capture_decode_dst_block: Buffer for decoded 32-bit capture samples.
* @capture_routing_buffer: Intermediate buffer for capture routing.
* @capture_work: Work struct for deferred capture processing.
* @stop_work: Work struct for deferred stream stopping.
* @stop_pcm_work: Work struct for stopping PCM due to a fatal error (e.g.
* xrun).
*
* @midi_in_substream: Pointer to the active MIDI input substream.
* @midi_out_substream: Pointer to the active MIDI output substream.
* @midi_in_urbs: Array of URBs for MIDI input.
* @midi_out_urbs: Array of URBs for MIDI output.
* @midi_in_active: Atomic flag indicating if MIDI input is active.
* @midi_out_active: Atomic flag indicating if MIDI output is active.
* @midi_in_fifo: FIFO for raw MIDI input data.
* @midi_in_work: Work struct for deferred MIDI input processing.
* @midi_out_work: Work struct for deferred MIDI output processing.
* @midi_in_lock: Spinlock for MIDI input FIFO.
* @midi_out_lock: Spinlock for MIDI output.
* @midi_out_urbs_in_flight: Bitmap of MIDI output URBs currently in flight.
* @midi_running_status: Stores the last MIDI status byte for running status.
* @error_timer: Timer for MIDI error retry logic.
*
* @lock: Main spinlock for protecting shared driver state.
* @active_urbs: Atomic counter for active URBs.
* @current_rate: Currently configured sample rate of the device.
* @line_out_source: Source for Line Outputs (0: Playback 1-2, 1: Playback 3-4).
* @digital_out_source: Source for Digital Outputs (0: Playback 1-2, 1: Playback
* 3-4).
* @capture_12_source: Source for Capture channels 1-2 (0: Analog In, 1: Digital
* In).
* @capture_34_source: Source for Capture channels 3-4 (0: Analog In, 1: Digital
* In).
*
* @feedback_accumulator_pattern: Stores the calculated frames per packet for
* feedback.
* @feedback_pattern_out_idx: Read index for feedback_accumulator_pattern.
* @feedback_pattern_in_idx: Write index for feedback_accumulator_pattern.
* @feedback_synced: Flag indicating if feedback is synced.
* @feedback_consecutive_errors: Counter for consecutive feedback errors.
* @feedback_urb_skip_count: Number of feedback URBs to skip initially for
* stabilization.
* @fpo: Holds the state for the dynamic feedback pattern generation.
*
* @playback_anchor: USB anchor for playback URBs.
* @capture_anchor: USB anchor for capture URBs.
* @feedback_anchor: USB anchor for feedback URBs.
* @midi_in_anchor: USB anchor for MIDI input URBs.
* @midi_out_anchor: USB anchor for MIDI output URBs.
*/
struct tascam_card {
/* --- Core device pointers --- */
struct usb_device *dev;
struct usb_interface *iface0;
struct usb_interface *iface1;
struct snd_card *card;
struct snd_pcm *pcm;
struct snd_rawmidi *rmidi;
/* --- PCM Substreams --- */
struct snd_pcm_substream *playback_substream;
struct snd_pcm_substream *capture_substream;
/* --- URBs and Anchors --- */
struct urb *playback_urbs[NUM_PLAYBACK_URBS];
size_t playback_urb_alloc_size;
struct urb *feedback_urbs[NUM_FEEDBACK_URBS];
size_t feedback_urb_alloc_size;
struct urb *capture_urbs[NUM_CAPTURE_URBS];
size_t capture_urb_alloc_size;
struct urb *midi_in_urbs[NUM_MIDI_IN_URBS];
struct urb *midi_out_urbs[NUM_MIDI_OUT_URBS];
struct usb_anchor playback_anchor;
struct usb_anchor capture_anchor;
struct usb_anchor feedback_anchor;
struct usb_anchor midi_in_anchor;
struct usb_anchor midi_out_anchor;
/* --- Stream State --- */
spinlock_t lock;
atomic_t playback_active;
atomic_t capture_active;
atomic_t active_urbs;
int current_rate;
/* --- Playback State --- */
u64 playback_frames_consumed;
snd_pcm_uframes_t driver_playback_pos;
u64 last_period_pos;
/* --- Capture State --- */
u64 capture_frames_processed;
snd_pcm_uframes_t driver_capture_pos;
u64 last_capture_period_pos;
u8 *capture_ring_buffer;
size_t capture_ring_buffer_read_ptr;
size_t capture_ring_buffer_write_ptr;
u8 *capture_decode_raw_block;
s32 *capture_decode_dst_block;
s32 *capture_routing_buffer;
/* --- MIDI State --- */
struct snd_rawmidi_substream *midi_in_substream;
struct snd_rawmidi_substream *midi_out_substream;
atomic_t midi_in_active;
atomic_t midi_out_active;
struct kfifo midi_in_fifo;
spinlock_t midi_in_lock;
spinlock_t midi_out_lock;
unsigned long midi_out_urbs_in_flight;
u8 midi_running_status;
struct timer_list error_timer;
struct completion midi_out_drain_completion;
/* --- Feedback Sync State --- */
unsigned int feedback_accumulator_pattern[FEEDBACK_ACCUMULATOR_SIZE];
unsigned int feedback_pattern_out_idx;
unsigned int feedback_pattern_in_idx;
bool feedback_synced;
unsigned int feedback_consecutive_errors;
unsigned int feedback_urb_skip_count;
struct us144mkii_frame_pattern_observer fpo;
/* --- Workqueues --- */
struct work_struct stop_work;
struct work_struct stop_pcm_work;
struct work_struct capture_work;
struct work_struct midi_in_work;
struct work_struct midi_out_work;
/* --- Mixer/Routing State --- */
unsigned int line_out_source;
unsigned int digital_out_source;
unsigned int capture_12_source;
unsigned int capture_34_source;
};
/* main.c */
/**
* tascam_free_urbs() - Free all allocated URBs and associated buffers.
* @tascam: the tascam_card instance
*
* This function kills, unlinks, and frees all playback, feedback, capture,
* and MIDI URBs, along with their transfer buffers and the capture
* ring/decode buffers.
*/
void tascam_free_urbs(struct tascam_card *tascam);
/**
* tascam_alloc_urbs() - Allocate all URBs and associated buffers.
* @tascam: the tascam_card instance
*
* This function allocates and initializes all URBs for playback, feedback,
* capture, and MIDI, as well as the necessary buffers for data processing.
*
* Return: 0 on success, or a negative error code on failure.
*/
int tascam_alloc_urbs(struct tascam_card *tascam);
/**
* tascam_stop_work_handler() - Work handler to stop all active streams.
* @work: Pointer to the work_struct.
*
* This function is scheduled to stop all active URBs (playback, feedback,
* capture) and reset the active_urbs counter.
*/
void tascam_stop_work_handler(struct work_struct *work);
/* us144mkii_pcm.h */
#include "us144mkii_pcm.h"
/* us144mkii_midi.c */
/**
* tascam_midi_in_urb_complete() - Completion handler for MIDI IN URBs
* @urb: The completed URB.
*
* This function runs in interrupt context. It places the raw data from the
* USB endpoint into a kfifo and schedules a work item to process it later,
* ensuring the interrupt handler remains fast.
*/
void tascam_midi_in_urb_complete(struct urb *urb);
/**
* tascam_midi_out_urb_complete() - Completion handler for MIDI OUT bulk URB.
* @urb: The completed URB.
*
* This function runs in interrupt context. It marks the output URB as no
* longer in-flight. It then re-schedules the work handler to check for and
* send any more data waiting in the ALSA buffer. This is a safe, non-blocking
* way to continue the data transmission chain.
*/
void tascam_midi_out_urb_complete(struct urb *urb);
/**
* tascam_create_midi() - Create and initialize the ALSA rawmidi device.
* @tascam: The driver instance.
*
* Return: 0 on success, or a negative error code on failure.
*/
int tascam_create_midi(struct tascam_card *tascam);
/* us144mkii_controls.c */
/**
* tascam_create_controls() - Creates and adds ALSA mixer controls for the
* device.
* @tascam: The driver instance.
*
* This function registers custom ALSA controls for managing audio routing
* (line out source, digital out source, capture 1-2 source, capture 3-4 source)
* and displaying the current sample rate.
*
* Return: 0 on success, or a negative error code on failure.
*/
int tascam_create_controls(struct tascam_card *tascam);
#endif /* __US144MKII_H */
@@ -0,0 +1,320 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2025 Šerif Rami <ramiserifpersia@gmail.com>
#include "us144mkii.h"
/*
* tascam_capture_open() - Opens the PCM capture substream.
* @substream: The ALSA PCM substream to open.
*
* This function sets the hardware parameters for the capture substream
* and stores a reference to the substream in the driver's private data.
*
* Return: 0 on success.
*/
static int tascam_capture_open(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
substream->runtime->hw = tascam_pcm_hw;
tascam->capture_substream = substream;
atomic_set(&tascam->capture_active, 0);
return 0;
}
/*
* tascam_capture_close() - Closes the PCM capture substream.
* @substream: The ALSA PCM substream to close.
*
* This function clears the reference to the capture substream in the
* driver's private data.
*
* Return: 0 on success.
*/
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;
}
/*
* tascam_capture_prepare() - Prepares the PCM capture substream for use.
* @substream: The ALSA PCM substream to prepare.
*
* This function initializes capture-related counters and ring buffer pointers.
*
* Return: 0 on success.
*/
static int tascam_capture_prepare(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
tascam->driver_capture_pos = 0;
tascam->capture_frames_processed = 0;
tascam->last_capture_period_pos = 0;
tascam->capture_ring_buffer_read_ptr = 0;
tascam->capture_ring_buffer_write_ptr = 0;
return 0;
}
/*
* tascam_capture_pointer() - Returns the current capture pointer position.
* @substream: The ALSA PCM substream.
*
* This function returns the current position of the capture pointer within
* the ALSA ring buffer, in frames.
*
* Return: The current capture pointer position in frames.
*/
static snd_pcm_uframes_t
tascam_capture_pointer(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
u64 pos;
if (!atomic_read(&tascam->capture_active))
return 0;
scoped_guard(spinlock_irqsave, &tascam->lock) {
pos = tascam->capture_frames_processed;
}
if (runtime->buffer_size == 0)
return 0;
return do_div(pos, runtime->buffer_size);
}
/*
* tascam_capture_ops - ALSA PCM operations for capture.
*
* This structure defines the callback functions for capture stream operations,
* including open, close, ioctl, hardware parameters, hardware free, prepare,
* trigger, and pointer.
*/
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 = tascam_pcm_hw_free,
.prepare = tascam_capture_prepare,
.trigger = tascam_pcm_trigger,
.pointer = tascam_capture_pointer,
};
/*
* decode_tascam_capture_block() - Decodes a raw 512-byte block from the device.
* @src_block: Pointer to the 512-byte raw source block.
* @dst_block: Pointer to the destination buffer for decoded audio frames.
*
* The device sends audio data in a complex, multiplexed format. This function
* demultiplexes the bits from the raw block into 8 frames of 4-channel,
* 24-bit audio (stored in 32-bit containers).
*/
static void decode_tascam_capture_block(const u8 *src_block, s32 *dst_block)
{
int frame, bit;
memset(dst_block, 0,
FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME *
DECODED_SAMPLE_SIZE);
for (frame = 0; frame < FRAMES_PER_DECODE_BLOCK; ++frame) {
const u8 *p_src_frame_base = src_block + frame * 64;
s32 *p_dst_frame = dst_block + frame * 4;
s32 ch[4] = { 0 };
for (bit = 0; bit < 24; ++bit) {
u8 byte1 = p_src_frame_base[bit];
u8 byte2 = p_src_frame_base[bit + 32];
ch[0] = (ch[0] << 1) | (byte1 & 1);
ch[2] = (ch[2] << 1) | ((byte1 >> 1) & 1);
ch[1] = (ch[1] << 1) | (byte2 & 1);
ch[3] = (ch[3] << 1) | ((byte2 >> 1) & 1);
}
/*
* The result is a 24-bit sample. Shift left by 8 to align it to
* the most significant bits of a 32-bit integer (S32_LE format).
*/
p_dst_frame[0] = ch[0] << 8;
p_dst_frame[1] = ch[1] << 8;
p_dst_frame[2] = ch[2] << 8;
p_dst_frame[3] = ch[3] << 8;
}
}
void tascam_capture_work_handler(struct work_struct *work)
{
struct tascam_card *tascam =
container_of(work, struct tascam_card, capture_work);
struct snd_pcm_substream *substream = tascam->capture_substream;
struct snd_pcm_runtime *runtime;
u8 *raw_block = tascam->capture_decode_raw_block;
s32 *decoded_block = tascam->capture_decode_dst_block;
s32 *routed_block = tascam->capture_routing_buffer;
if (!substream || !substream->runtime)
return;
runtime = substream->runtime;
if (!raw_block || !decoded_block || !routed_block) {
dev_err(tascam->card->dev,
"Capture decode/routing buffers not allocated!\n");
return;
}
while (atomic_read(&tascam->capture_active)) {
size_t write_ptr, read_ptr, available_data;
bool can_process;
scoped_guard(spinlock_irqsave, &tascam->lock) {
write_ptr = tascam->capture_ring_buffer_write_ptr;
read_ptr = tascam->capture_ring_buffer_read_ptr;
available_data = (write_ptr >= read_ptr) ?
(write_ptr - read_ptr) :
(CAPTURE_RING_BUFFER_SIZE -
read_ptr + write_ptr);
can_process =
(available_data >= RAW_BYTES_PER_DECODE_BLOCK);
if (can_process) {
size_t bytes_to_end =
CAPTURE_RING_BUFFER_SIZE - read_ptr;
if (bytes_to_end >=
RAW_BYTES_PER_DECODE_BLOCK) {
memcpy(raw_block,
tascam->capture_ring_buffer +
read_ptr,
RAW_BYTES_PER_DECODE_BLOCK);
} else {
memcpy(raw_block,
tascam->capture_ring_buffer +
read_ptr,
bytes_to_end);
memcpy(raw_block + bytes_to_end,
tascam->capture_ring_buffer,
RAW_BYTES_PER_DECODE_BLOCK -
bytes_to_end);
}
tascam->capture_ring_buffer_read_ptr =
(read_ptr +
RAW_BYTES_PER_DECODE_BLOCK) %
CAPTURE_RING_BUFFER_SIZE;
}
}
if (!can_process)
break;
decode_tascam_capture_block(raw_block, decoded_block);
process_capture_routing_us144mkii(tascam, decoded_block,
routed_block);
scoped_guard(spinlock_irqsave, &tascam->lock) {
if (atomic_read(&tascam->capture_active)) {
int f;
for (f = 0; f < FRAMES_PER_DECODE_BLOCK; ++f) {
u8 *dst_frame_start =
runtime->dma_area +
frames_to_bytes(
runtime,
tascam->driver_capture_pos);
s32 *routed_frame_start =
routed_block +
(f * NUM_CHANNELS);
int c;
for (c = 0; c < NUM_CHANNELS; c++) {
u8 *dst_channel =
dst_frame_start +
(c * BYTES_PER_SAMPLE);
s32 *src_channel_s32 =
routed_frame_start + c;
memcpy(dst_channel,
((char *)src_channel_s32) +
1,
3);
}
tascam->driver_capture_pos =
(tascam->driver_capture_pos +
1) %
runtime->buffer_size;
}
}
}
}
}
void capture_urb_complete(struct urb *urb)
{
struct tascam_card *tascam = urb->context;
int ret;
if (urb->status) {
if (urb->status != -ENOENT && urb->status != -ECONNRESET &&
urb->status != -ESHUTDOWN && urb->status != -ENODEV &&
urb->status != -EPROTO)
dev_err_ratelimited(tascam->card->dev,
"Capture URB failed: %d\n",
urb->status);
goto out;
}
if (!tascam || !atomic_read(&tascam->capture_active))
goto out;
if (urb->actual_length > 0) {
scoped_guard(spinlock_irqsave, &tascam->lock) {
size_t write_ptr = tascam->capture_ring_buffer_write_ptr;
size_t bytes_to_end = CAPTURE_RING_BUFFER_SIZE - write_ptr;
if (urb->actual_length > bytes_to_end) {
memcpy(tascam->capture_ring_buffer + write_ptr,
urb->transfer_buffer, bytes_to_end);
memcpy(tascam->capture_ring_buffer,
urb->transfer_buffer + bytes_to_end,
urb->actual_length - bytes_to_end);
} else {
memcpy(tascam->capture_ring_buffer + write_ptr,
urb->transfer_buffer,
urb->actual_length);
}
tascam->capture_ring_buffer_write_ptr =
(write_ptr + urb->actual_length) %
CAPTURE_RING_BUFFER_SIZE;
}
schedule_work(&tascam->capture_work);
}
usb_get_urb(urb);
usb_anchor_urb(urb, &tascam->capture_anchor);
ret = usb_submit_urb(urb, GFP_ATOMIC);
if (ret < 0) {
dev_err_ratelimited(tascam->card->dev,
"Failed to resubmit capture URB: %d\n",
ret);
usb_unanchor_urb(urb);
usb_put_urb(urb);
atomic_dec(
&tascam->active_urbs); /* Decrement on failed resubmission */
return;
}
out:
usb_put_urb(urb);
}
@@ -0,0 +1,444 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2025 Šerif Rami <ramiserifpersia@gmail.com>
#include "us144mkii.h"
/*
* Text descriptions for playback output source options.
*
* Used by ALSA kcontrol elements to provide user-friendly names for
* the playback routing options (e.g., "Playback 1-2", "Playback 3-4").
*/
static const char *const playback_source_texts[] = { "Playback 1-2",
"Playback 3-4" };
/*
* Text descriptions for capture input source options.
*
* Used by ALSA kcontrol elements to provide user-friendly names for
* the capture routing options (e.g., "Analog In", "Digital In").
*/
static const char *const capture_source_texts[] = { "Analog In", "Digital In" };
/*
* tascam_playback_source_info() - ALSA control info callback for playback
* source.
* @kcontrol: The ALSA kcontrol instance.
* @uinfo: The ALSA control element info structure to fill.
*
* This function provides information about the enumerated playback source
* control, including its type, count, and available items (Playback 1-2,
* Playback 3-4).
*
* Return: 0 on success.
*/
static int tascam_playback_source_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
return snd_ctl_enum_info(uinfo, 1, 2, playback_source_texts);
}
/*
* tascam_line_out_get() - ALSA control get callback for Line Outputs Source.
* @kcontrol: The ALSA kcontrol instance.
* @ucontrol: The ALSA control element value structure to fill.
*
* This function retrieves the current selection for the Line Outputs source
* (Playback 1-2 or Playback 3-4) from the driver's private data and populates
* the ALSA control element value.
*
* Return: 0 on success.
*/
static int tascam_line_out_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
scoped_guard(spinlock_irqsave, &tascam->lock) {
ucontrol->value.enumerated.item[0] = tascam->line_out_source;
}
return 0;
}
/*
* tascam_line_out_put() - ALSA control put callback for Line Outputs Source.
* @kcontrol: The ALSA kcontrol instance.
* @ucontrol: The ALSA control element value structure containing the new value.
*
* This function sets the Line Outputs source (Playback 1-2 or Playback 3-4)
* based on the user's selection from the ALSA control element. It validates
* the input and updates the driver's private data.
*
* Return: 1 if the value was changed, 0 if unchanged, or a negative error code.
*/
static int tascam_line_out_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
int changed = 0;
if (ucontrol->value.enumerated.item[0] > 1)
return -EINVAL;
scoped_guard(spinlock_irqsave, &tascam->lock) {
if (tascam->line_out_source != ucontrol->value.enumerated.item[0]) {
tascam->line_out_source = ucontrol->value.enumerated.item[0];
changed = 1;
}
}
return changed;
}
/*
* tascam_line_out_control - ALSA kcontrol definition for Line Outputs Source.
*
* This defines a new ALSA mixer control named "Line OUTPUTS Source" that allows
* the user to select between "Playback 1-2" and "Playback 3-4" for the analog
* line outputs of the device. It uses the `tascam_playback_source_info` for
* information and `tascam_line_out_get`/`tascam_line_out_put` for value
* handling.
*/
static const struct snd_kcontrol_new tascam_line_out_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Line Playback Source",
.info = tascam_playback_source_info,
.get = tascam_line_out_get,
.put = tascam_line_out_put,
};
/*
* tascam_digital_out_get() - ALSA control get callback for Digital Outputs
* Source.
* @kcontrol: The ALSA kcontrol instance.
* @ucontrol: The ALSA control element value structure to fill.
*
* This function retrieves the current selection for the Digital Outputs source
* (Playback 1-2 or Playback 3-4) from the driver's private data and populates
* the ALSA control element value.
*
* Return: 0 on success.
*/
static int tascam_digital_out_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
scoped_guard(spinlock_irqsave, &tascam->lock) {
ucontrol->value.enumerated.item[0] = tascam->digital_out_source;
}
return 0;
}
/*
* tascam_digital_out_put() - ALSA control put callback for Digital Outputs
* Source.
* @kcontrol: The ALSA kcontrol instance.
* @ucontrol: The ALSA control element value structure containing the new value.
*
* This function sets the Digital Outputs source (Playback 1-2 or Playback 3-4)
* based on the user's selection from the ALSA control element. It validates
* the input and updates the driver's private data.
*
* Return: 1 if the value was changed, 0 if unchanged, or a negative error code.
*/
static int tascam_digital_out_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
int changed = 0;
if (ucontrol->value.enumerated.item[0] > 1)
return -EINVAL;
scoped_guard(spinlock_irqsave, &tascam->lock) {
if (tascam->digital_out_source != ucontrol->value.enumerated.item[0]) {
tascam->digital_out_source = ucontrol->value.enumerated.item[0];
changed = 1;
}
}
return changed;
}
/*
* tascam_digital_out_control - ALSA kcontrol definition for Digital Outputs
* Source.
*
* This defines a new ALSA mixer control named "Digital OUTPUTS Source" that
* allows the user to select between "Playback 1-2" and "Playback 3-4" for the
* digital outputs of the device. It uses the `tascam_playback_source_info` for
* information and `tascam_digital_out_get`/`tascam_digital_out_put` for value
* handling.
*/
static const struct snd_kcontrol_new tascam_digital_out_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Digital Playback Source",
.info = tascam_playback_source_info,
.get = tascam_digital_out_get,
.put = tascam_digital_out_put,
};
/*
* tascam_capture_source_info() - ALSA control info callback for capture source.
* @kcontrol: The ALSA kcontrol instance.
* @uinfo: The ALSA control element info structure to fill.
*
* This function provides information about the enumerated capture source
* control, including its type, count, and available items (Analog In, Digital
* In).
*
* Return: 0 on success.
*/
static int tascam_capture_source_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
return snd_ctl_enum_info(uinfo, 1, 2, capture_source_texts);
}
/*
* tascam_capture_12_get() - ALSA control get callback for Capture channels 1
* and 2 Source.
* @kcontrol: The ALSA kcontrol instance.
* @ucontrol: The ALSA control element value structure to fill.
*
* This function retrieves the current selection for the Capture channels 1 and
* 2 source (Analog In or Digital In) from the driver's private data and
* populates the ALSA control element value.
*
* Return: 0 on success.
*/
static int tascam_capture_12_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
scoped_guard(spinlock_irqsave, &tascam->lock) {
ucontrol->value.enumerated.item[0] = tascam->capture_12_source;
}
return 0;
}
/*
* tascam_capture_12_put() - ALSA control put callback for Capture channels 1
* and 2 Source.
* @kcontrol: The ALSA kcontrol instance.
* @ucontrol: The ALSA control element value structure containing the new value.
*
* This function sets the Capture channels 1 and 2 source (Analog In or Digital
* In) based on the user's selection from the ALSA control element. It validates
* the input and updates the driver's private data.
*
* Return: 1 if the value was changed, 0 if unchanged, or a negative error code.
*/
static int tascam_capture_12_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
int changed = 0;
if (ucontrol->value.enumerated.item[0] > 1)
return -EINVAL;
scoped_guard(spinlock_irqsave, &tascam->lock) {
if (tascam->capture_12_source != ucontrol->value.enumerated.item[0]) {
tascam->capture_12_source = ucontrol->value.enumerated.item[0];
changed = 1;
}
}
return changed;
}
/*
* tascam_capture_12_control - ALSA kcontrol definition for Capture channels 1
* and 2 Source.
*
* This defines a new ALSA mixer control named "ch1 and ch2 Source" that allows
* the user to select between "Analog In" and "Digital In" for the first two
* capture channels of the device. It uses the `tascam_capture_source_info` for
* information and `tascam_capture_12_get`/`tascam_capture_12_put` for value
* handling.
*/
static const struct snd_kcontrol_new tascam_capture_12_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Ch1/2 Capture Source",
.info = tascam_capture_source_info,
.get = tascam_capture_12_get,
.put = tascam_capture_12_put,
};
/*
* tascam_capture_34_get() - ALSA control get callback for Capture channels 3
* and 4 Source.
* @kcontrol: The ALSA kcontrol instance.
* @ucontrol: The ALSA control element value structure to fill.
*
* This function retrieves the current selection for the Capture channels 3 and
* 4 source (Analog In or Digital In) from the driver's private data and
* populates the ALSA control element value.
*
* Return: 0 on success.
*/
static int tascam_capture_34_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
scoped_guard(spinlock_irqsave, &tascam->lock) {
ucontrol->value.enumerated.item[0] = tascam->capture_34_source;
}
return 0;
}
/*
* tascam_capture_34_put() - ALSA control put callback for Capture channels 3
* and 4 Source.
* @kcontrol: The ALSA kcontrol instance.
* @ucontrol: The ALSA control element value structure containing the new value.
*
* This function sets the Capture channels 3 and 4 source (Analog In or Digital
* In) based on the user's selection from the ALSA control element. It validates
* the input and updates the driver's private data.
*
* Return: 1 if the value was changed, 0 if unchanged, or a negative error code.
*/
static int tascam_capture_34_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
int changed = 0;
if (ucontrol->value.enumerated.item[0] > 1)
return -EINVAL;
scoped_guard(spinlock_irqsave, &tascam->lock) {
if (tascam->capture_34_source != ucontrol->value.enumerated.item[0]) {
tascam->capture_34_source = ucontrol->value.enumerated.item[0];
changed = 1;
}
}
return changed;
}
/*
* tascam_capture_34_control - ALSA kcontrol definition for Capture channels 3
* and 4 Source.
*
* This defines a new ALSA mixer control named "ch3 and ch4 Source" that allows
* the user to select between "Analog In" and "Digital In" for the third and
* fourth capture channels of the device. It uses the
* `tascam_capture_source_info` for information and
* `tascam_capture_34_get`/`tascam_capture_34_put` for value handling.
*/
static const struct snd_kcontrol_new tascam_capture_34_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Ch3/4 Capture Source",
.info = tascam_capture_source_info,
.get = tascam_capture_34_get,
.put = tascam_capture_34_put,
};
/*
* tascam_samplerate_info() - ALSA control info callback for Sample Rate.
* @kcontrol: The ALSA kcontrol instance.
* @uinfo: The ALSA control element info structure to fill.
*
* This function provides information about the Sample Rate control, defining
* it as an integer type with a minimum value of 0 and a maximum of 96000.
*
* Return: 0 on success.
*/
static int tascam_samplerate_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 96000;
return 0;
}
/*
* tascam_samplerate_get() - ALSA control get callback for Sample Rate.
* @kcontrol: The ALSA kcontrol instance.
* @ucontrol: The ALSA control element value structure to fill.
*
* This function retrieves the current sample rate from the device via a USB
* control message and populates the ALSA control element value. If the rate
* is already known (i.e., `current_rate` is set), it returns that value
* directly.
*
* Return: 0 on success, or a negative error code on failure.
*/
static int tascam_samplerate_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct tascam_card *tascam =
(struct tascam_card *)snd_kcontrol_chip(kcontrol);
int err;
u32 rate = 0;
scoped_guard(spinlock_irqsave, &tascam->lock) {
if (tascam->current_rate > 0) {
ucontrol->value.integer.value[0] = tascam->current_rate;
return 0;
}
}
u8 *buf __free(kfree) =
kmalloc(3, GFP_KERNEL);
if (!buf)
return -ENOMEM;
err = usb_control_msg(tascam->dev, usb_rcvctrlpipe(tascam->dev, 0),
UAC_GET_CUR, RT_D2H_CLASS_EP,
UAC_SAMPLING_FREQ_CONTROL, EP_AUDIO_IN, buf, 3,
USB_CTRL_TIMEOUT_MS);
if (err >= 3)
rate = buf[0] | (buf[1] << 8) | (buf[2] << 16);
ucontrol->value.integer.value[0] = rate;
return 0;
}
/*
* tascam_samplerate_control - ALSA kcontrol definition for Sample Rate.
*
* This defines a new ALSA mixer control named "Sample Rate" that displays
* the current sample rate of the device. It is a read-only control.
*/
static const struct snd_kcontrol_new tascam_samplerate_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Sample Rate",
.info = tascam_samplerate_info,
.get = tascam_samplerate_get,
.access = SNDRV_CTL_ELEM_ACCESS_READ,
};
int tascam_create_controls(struct tascam_card *tascam)
{
int err;
err = snd_ctl_add(tascam->card,
snd_ctl_new1(&tascam_line_out_control, tascam));
if (err < 0)
return err;
err = snd_ctl_add(tascam->card,
snd_ctl_new1(&tascam_digital_out_control, tascam));
if (err < 0)
return err;
err = snd_ctl_add(tascam->card,
snd_ctl_new1(&tascam_capture_12_control, tascam));
if (err < 0)
return err;
err = snd_ctl_add(tascam->card,
snd_ctl_new1(&tascam_capture_34_control, tascam));
if (err < 0)
return err;
err = snd_ctl_add(tascam->card,
snd_ctl_new1(&tascam_samplerate_control, tascam));
if (err < 0)
return err;
return 0;
}
@@ -0,0 +1,403 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2025 Šerif Rami <ramiserifpersia@gmail.com>
#include "us144mkii.h"
/*
* tascam_midi_in_work_handler() - Deferred work for processing MIDI input.
* @work: The work_struct instance.
*
* This function runs in a thread context. It safely reads raw USB data from
* the kfifo, processes it by stripping protocol-specific padding bytes, and
* passes the clean MIDI data to the ALSA rawmidi subsystem.
*/
static void tascam_midi_in_work_handler(struct work_struct *work)
{
struct tascam_card *tascam =
container_of(work, struct tascam_card, midi_in_work);
u8 buf[9];
u8 clean_buf[8];
unsigned int count, clean_count;
if (!tascam->midi_in_substream)
return;
while (kfifo_out_spinlocked(&tascam->midi_in_fifo, buf, sizeof(buf),
&tascam->midi_in_lock) == sizeof(buf)) {
clean_count = 0;
for (count = 0; count < 8; ++count) {
if (buf[count] != 0xfd)
clean_buf[clean_count++] = buf[count];
}
if (clean_count > 0)
snd_rawmidi_receive(tascam->midi_in_substream,
clean_buf, clean_count);
}
}
void tascam_midi_in_urb_complete(struct urb *urb)
{
struct tascam_card *tascam = urb->context;
int ret;
if (!tascam)
goto out;
if (urb->status) {
if (urb->status != -ENOENT && urb->status != -ECONNRESET &&
urb->status != -ESHUTDOWN && urb->status != -EPROTO) {
dev_err_ratelimited(tascam->card->dev,
"MIDI IN URB failed: status %d\n",
urb->status);
}
goto out;
}
if (atomic_read(&tascam->midi_in_active) &&
urb->actual_length > 0) {
kfifo_in_spinlocked(&tascam->midi_in_fifo, urb->transfer_buffer,
urb->actual_length, &tascam->midi_in_lock);
schedule_work(&tascam->midi_in_work);
}
usb_get_urb(urb);
usb_anchor_urb(urb, &tascam->midi_in_anchor);
ret = usb_submit_urb(urb, GFP_ATOMIC);
if (ret < 0) {
dev_err(tascam->card->dev,
"Failed to resubmit MIDI IN URB: error %d\n", ret);
usb_unanchor_urb(urb);
goto out;
}
out:
usb_put_urb(urb);
}
/*
* tascam_midi_in_open() - Opens the MIDI input substream.
* @substream: The ALSA rawmidi substream to open.
*
* This function stores a reference to the MIDI input substream in the
* driver's private data.
*
* Return: 0 on success.
*/
static int tascam_midi_in_open(struct snd_rawmidi_substream *substream)
{
struct tascam_card *tascam = substream->rmidi->private_data;
tascam->midi_in_substream = substream;
return 0;
}
/*
* tascam_midi_in_close() - Closes the MIDI input substream.
* @substream: The ALSA rawmidi substream to close.
*
* Return: 0 on success.
*/
static int tascam_midi_in_close(struct snd_rawmidi_substream *substream)
{
return 0;
}
/*
* tascam_midi_in_trigger() - Triggers MIDI input stream activity.
* @substream: The ALSA rawmidi substream.
* @up: Boolean indicating whether to start (1) or stop (0) the stream.
*
* This function starts or stops the MIDI input URBs based on the 'up'
* parameter. When starting, it resets the kfifo and submits all MIDI input
* URBs. When stopping, it kills all anchored MIDI input URBs and cancels the
* associated workqueue.
*/
static void tascam_midi_in_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct tascam_card *tascam = substream->rmidi->private_data;
int i, err;
if (up) {
if (atomic_xchg(&tascam->midi_in_active, 1) == 0) {
scoped_guard(spinlock_irqsave, &tascam->midi_in_lock)
{
kfifo_reset(&tascam->midi_in_fifo);
}
for (i = 0; i < NUM_MIDI_IN_URBS; i++) {
usb_get_urb(tascam->midi_in_urbs[i]);
usb_anchor_urb(tascam->midi_in_urbs[i],
&tascam->midi_in_anchor);
err = usb_submit_urb(tascam->midi_in_urbs[i],
GFP_KERNEL);
if (err < 0) {
dev_err(tascam->card->dev,
"Failed to submit MIDI IN URB %d: %d\n",
i, err);
usb_unanchor_urb(
tascam->midi_in_urbs[i]);
usb_put_urb(tascam->midi_in_urbs[i]);
}
}
}
} else {
if (atomic_xchg(&tascam->midi_in_active, 0) == 1) {
usb_kill_anchored_urbs(&tascam->midi_in_anchor);
cancel_work_sync(&tascam->midi_in_work);
}
}
}
/*
* tascam_midi_in_ops - ALSA rawmidi operations for MIDI input.
*
* This structure defines the callback functions for MIDI input stream
* operations, including open, close, and trigger.
*/
static const struct snd_rawmidi_ops tascam_midi_in_ops = {
.open = tascam_midi_in_open,
.close = tascam_midi_in_close,
.trigger = tascam_midi_in_trigger,
};
void tascam_midi_out_urb_complete(struct urb *urb)
{
struct tascam_card *tascam = urb->context;
int i, urb_index = -1;
if (urb->status) {
if (urb->status != -ENOENT && urb->status != -ECONNRESET &&
urb->status != -ESHUTDOWN) {
dev_err_ratelimited(tascam->card->dev,
"MIDI OUT URB failed: %d\n",
urb->status);
}
goto out;
}
if (!tascam)
goto out;
for (i = 0; i < NUM_MIDI_OUT_URBS; i++) {
if (tascam->midi_out_urbs[i] == urb) {
urb_index = i;
break;
}
}
if (urb_index < 0) {
dev_err_ratelimited(tascam->card->dev,
"Unknown MIDI OUT URB completed!\n");
goto out;
}
scoped_guard(spinlock_irqsave, &tascam->midi_out_lock)
{
clear_bit(urb_index, &tascam->midi_out_urbs_in_flight);
}
if (atomic_read(&tascam->midi_out_active))
schedule_work(&tascam->midi_out_work);
out:
usb_put_urb(urb);
}
/*
* tascam_midi_out_work_handler() - Deferred work for sending MIDI data
* @work: The work_struct instance.
*
* This function handles the proprietary output protocol: take the raw MIDI
* message bytes from the application, place them at the start of a 9-byte
* buffer, pad the rest with 0xFD, and add a terminator byte (0x00).
* This function pulls as many bytes as will fit into one packet from the
* ALSA buffer and sends them.
*/
static void tascam_midi_out_work_handler(struct work_struct *work)
{
struct tascam_card *tascam =
container_of(work, struct tascam_card, midi_out_work);
struct snd_rawmidi_substream *substream = tascam->midi_out_substream;
int i;
if (!substream || !atomic_read(&tascam->midi_out_active))
return;
while (snd_rawmidi_transmit_peek(substream, (u8[]){ 0 }, 1) == 1) {
int urb_index;
struct urb *urb;
u8 *buf;
int bytes_to_send;
scoped_guard(spinlock_irqsave, &tascam->midi_out_lock) {
urb_index = -1;
for (i = 0; i < NUM_MIDI_OUT_URBS; i++) {
if (!test_bit(
i,
&tascam->midi_out_urbs_in_flight)) {
urb_index = i;
break;
}
}
if (urb_index < 0)
return; /* No free URBs, will be rescheduled by
* completion handler
*/
urb = tascam->midi_out_urbs[urb_index];
buf = urb->transfer_buffer;
bytes_to_send = snd_rawmidi_transmit(substream, buf, 8);
if (bytes_to_send <= 0)
break; /* No more data */
if (bytes_to_send < 9)
memset(buf + bytes_to_send, 0xfd,
9 - bytes_to_send);
buf[8] = 0xe0;
set_bit(urb_index, &tascam->midi_out_urbs_in_flight);
urb->transfer_buffer_length = 9;
}
usb_get_urb(urb);
usb_anchor_urb(urb, &tascam->midi_out_anchor);
if (usb_submit_urb(urb, GFP_KERNEL) < 0) {
dev_err_ratelimited(
tascam->card->dev,
"Failed to submit MIDI OUT URB %d\n",
urb_index);
scoped_guard(spinlock_irqsave, &tascam->midi_out_lock)
{
clear_bit(urb_index,
&tascam->midi_out_urbs_in_flight);
}
usb_unanchor_urb(urb);
usb_put_urb(urb);
break; /* Stop on error */
}
}
}
/*
* tascam_midi_out_open() - Opens the MIDI output substream.
* @substream: The ALSA rawmidi substream to open.
*
* This function stores a reference to the MIDI output substream in the
* driver's private data and initializes the MIDI running status.
*
* Return: 0 on success.
*/
static int tascam_midi_out_open(struct snd_rawmidi_substream *substream)
{
struct tascam_card *tascam = substream->rmidi->private_data;
tascam->midi_out_substream = substream;
/* Initialize the running status state for the packet packer. */
tascam->midi_running_status = 0;
return 0;
}
/*
* tascam_midi_out_close() - Closes the MIDI output substream.
* @substream: The ALSA rawmidi substream to close.
*
* Return: 0 on success.
*/
static int tascam_midi_out_close(struct snd_rawmidi_substream *substream)
{
return 0;
}
/*
* tascam_midi_out_drain() - Drains the MIDI output stream.
* @substream: The ALSA rawmidi substream.
*
* This function cancels any pending MIDI output work and kills all
* anchored MIDI output URBs, ensuring all data is sent or discarded.
*/
static void tascam_midi_out_drain(struct snd_rawmidi_substream *substream)
{
struct tascam_card *tascam = substream->rmidi->private_data;
bool in_flight = true;
while (in_flight) {
in_flight = false;
for (int i = 0; i < NUM_MIDI_OUT_URBS; i++) {
if (test_bit(i, &tascam->midi_out_urbs_in_flight)) {
in_flight = true;
break;
}
}
if (in_flight)
schedule_timeout_uninterruptible(1);
}
cancel_work_sync(&tascam->midi_out_work);
usb_kill_anchored_urbs(&tascam->midi_out_anchor);
}
/*
* tascam_midi_out_trigger() - Triggers MIDI output stream activity.
* @substream: The ALSA rawmidi substream.
* @up: Boolean indicating whether to start (1) or stop (0) the stream.
*
* This function starts or stops the MIDI output workqueue based on the
* 'up' parameter.
*/
static void tascam_midi_out_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct tascam_card *tascam = substream->rmidi->private_data;
if (up) {
atomic_set(&tascam->midi_out_active, 1);
schedule_work(&tascam->midi_out_work);
} else {
atomic_set(&tascam->midi_out_active, 0);
}
}
/*
* tascam_midi_out_ops - ALSA rawmidi operations for MIDI output.
*
* This structure defines the callback functions for MIDI output stream
* operations, including open, close, trigger, and drain.
*/
static const struct snd_rawmidi_ops tascam_midi_out_ops = {
.open = tascam_midi_out_open,
.close = tascam_midi_out_close,
.trigger = tascam_midi_out_trigger,
.drain = tascam_midi_out_drain,
};
int tascam_create_midi(struct tascam_card *tascam)
{
int err;
err = snd_rawmidi_new(tascam->card, "US144MKII MIDI", 0, 1, 1,
&tascam->rmidi);
if (err < 0)
return err;
strscpy(tascam->rmidi->name, "US144MKII MIDI",
sizeof(tascam->rmidi->name));
tascam->rmidi->private_data = tascam;
snd_rawmidi_set_ops(tascam->rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
&tascam_midi_in_ops);
snd_rawmidi_set_ops(tascam->rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
&tascam_midi_out_ops);
tascam->rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT |
SNDRV_RAWMIDI_INFO_OUTPUT |
SNDRV_RAWMIDI_INFO_DUPLEX;
INIT_WORK(&tascam->midi_in_work, tascam_midi_in_work_handler);
INIT_WORK(&tascam->midi_out_work, tascam_midi_out_work_handler);
return 0;
}
+370
View File
@@ -0,0 +1,370 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2025 Šerif Rami <ramiserifpersia@gmail.com>
#include "us144mkii.h"
/**
* fpo_init_pattern() - Generates a packet distribution pattern.
* @size: The number of elements in the pattern array (e.g., 8).
* @pattern_array: Pointer to the array to be populated.
* @initial_value: The base value to initialize each element with.
* @target_sum: The desired sum of all elements in the final array.
*
* This function initializes an array with a base value and then iteratively
* adjusts the elements to match a target sum, distributing the difference
* as evenly as possible.
*/
static void fpo_init_pattern(unsigned int size, unsigned int *pattern_array,
unsigned int initial_value, int target_sum)
{
int diff, i;
if (!size)
return;
for (i = 0; i < size; ++i)
pattern_array[i] = initial_value;
diff = target_sum - (size * initial_value);
for (i = 0; i < abs(diff); ++i) {
if (diff > 0)
pattern_array[i]++;
else
pattern_array[i]--;
}
}
const struct snd_pcm_hardware tascam_pcm_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_S24_3LE,
.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 = 48 * BYTES_PER_FRAME,
.period_bytes_max = 1024 * BYTES_PER_FRAME,
.periods_min = 2,
.periods_max = 1024,
};
void process_playback_routing_us144mkii(struct tascam_card *tascam,
const u8 *src_buffer, u8 *dst_buffer,
size_t frames)
{
size_t f;
const u8 *src_12, *src_34;
u8 *dst_line, *dst_digital;
for (f = 0; f < frames; ++f) {
src_12 = src_buffer + f * BYTES_PER_FRAME;
src_34 = src_12 + (2 * BYTES_PER_SAMPLE);
dst_line = dst_buffer + f * BYTES_PER_FRAME;
dst_digital = dst_line + (2 * BYTES_PER_SAMPLE);
/* LINE OUTPUTS (ch1/2 on device) */
if (tascam->line_out_source == 0) /* "ch1 and ch2" */
memcpy(dst_line, src_12, 2 * BYTES_PER_SAMPLE);
else /* "ch3 and ch4" */
memcpy(dst_line, src_34, 2 * BYTES_PER_SAMPLE);
/* DIGITAL OUTPUTS (ch3/4 on device) */
if (tascam->digital_out_source == 0) /* "ch1 and ch2" */
memcpy(dst_digital, src_12, 2 * BYTES_PER_SAMPLE);
else /* "ch3 and ch4" */
memcpy(dst_digital, src_34, 2 * BYTES_PER_SAMPLE);
}
}
void process_capture_routing_us144mkii(struct tascam_card *tascam,
const s32 *decoded_block,
s32 *routed_block)
{
int f;
const s32 *src_frame;
s32 *dst_frame;
for (f = 0; f < FRAMES_PER_DECODE_BLOCK; f++) {
src_frame = decoded_block + (f * DECODED_CHANNELS_PER_FRAME);
dst_frame = routed_block + (f * DECODED_CHANNELS_PER_FRAME);
/* ch1 and ch2 Source */
if (tascam->capture_12_source == 0) { /* analog inputs */
dst_frame[0] = src_frame[0]; /* Analog L */
dst_frame[1] = src_frame[1]; /* Analog R */
} else { /* digital inputs */
dst_frame[0] = src_frame[2]; /* Digital L */
dst_frame[1] = src_frame[3]; /* Digital R */
}
/* ch3 and ch4 Source */
if (tascam->capture_34_source == 0) { /* analog inputs */
dst_frame[2] = src_frame[0]; /* Analog L (Duplicate) */
dst_frame[3] = src_frame[1]; /* Analog R (Duplicate) */
} else { /* digital inputs */
dst_frame[2] = src_frame[2]; /* Digital L */
dst_frame[3] = src_frame[3]; /* Digital R */
}
}
}
int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int rate)
{
struct usb_device *dev = tascam->dev;
u16 rate_vendor_wValue;
int err = 0;
const u8 *current_payload_src;
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 };
switch (rate) {
case 44100:
current_payload_src = payload_44100;
rate_vendor_wValue = REG_ADDR_RATE_44100;
break;
case 48000:
current_payload_src = payload_48000;
rate_vendor_wValue = REG_ADDR_RATE_48000;
break;
case 88200:
current_payload_src = payload_88200;
rate_vendor_wValue = REG_ADDR_RATE_88200;
break;
case 96000:
current_payload_src = payload_96000;
rate_vendor_wValue = REG_ADDR_RATE_96000;
break;
default:
dev_err(&dev->dev,
"Unsupported sample rate %d for configuration\n", rate);
return -EINVAL;
}
u8 *rate_payload_buf __free(kfree) =
kmemdup(current_payload_src, 3, GFP_KERNEL);
if (!rate_payload_buf)
return -ENOMEM;
dev_info(&dev->dev, "Configuring device for %d Hz\n", rate);
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 fail;
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_buf, 3,
USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto fail;
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_buf, 3,
USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto fail;
err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV,
REG_ADDR_UNKNOWN_0D, REG_VAL_ENABLE, NULL, 0,
USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto fail;
err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV,
REG_ADDR_UNKNOWN_0E, REG_VAL_ENABLE, NULL, 0,
USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto fail;
err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV,
REG_ADDR_UNKNOWN_0F, REG_VAL_ENABLE, NULL, 0,
USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto fail;
err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV,
rate_vendor_wValue, REG_VAL_ENABLE, NULL, 0,
USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto fail;
err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV,
REG_ADDR_UNKNOWN_11, REG_VAL_ENABLE, NULL, 0,
USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto fail;
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);
if (err < 0)
goto fail;
return 0;
fail:
dev_err(&dev->dev,
"Device configuration failed at rate %d with error %d\n", rate,
err);
return err;
}
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);
int err;
unsigned int rate = params_rate(params);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
tascam->fpo.sample_rate_khz = rate / 1000;
tascam->fpo.base_feedback_value = tascam->fpo.sample_rate_khz;
tascam->fpo.feedback_offset = 2;
tascam->fpo.current_index = 0;
tascam->fpo.previous_index = 0;
tascam->fpo.sync_locked = false;
unsigned int initial_value = tascam->fpo.sample_rate_khz / 8;
for (int i = 0; i < 5; i++) {
int target_sum = tascam->fpo.sample_rate_khz -
tascam->fpo.feedback_offset + i;
fpo_init_pattern(8, tascam->fpo.full_frame_patterns[i],
initial_value, target_sum);
}
}
if (tascam->current_rate != rate) {
err = us144mkii_configure_device_for_rate(tascam, rate);
if (err < 0) {
tascam->current_rate = 0;
return err;
}
tascam->current_rate = rate;
}
return 0;
}
int tascam_pcm_hw_free(struct snd_pcm_substream *substream)
{
return 0;
}
int tascam_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
int err = 0;
int i;
bool do_start = false;
bool do_stop = false;
scoped_guard(spinlock_irqsave, &tascam->lock) {
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
if (!atomic_read(&tascam->playback_active)) {
atomic_set(&tascam->playback_active, 1);
atomic_set(&tascam->capture_active, 1);
do_start = true;
}
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (atomic_read(&tascam->playback_active)) {
atomic_set(&tascam->playback_active, 0);
atomic_set(&tascam->capture_active, 0);
do_stop = true;
}
break;
default:
err = -EINVAL;
break;
}
}
if (do_start) {
if (atomic_read(&tascam->active_urbs) > 0) {
dev_warn(tascam->card->dev,
"Cannot start, URBs still active.\n");
return -EAGAIN;
}
for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
usb_get_urb(tascam->feedback_urbs[i]);
usb_anchor_urb(tascam->feedback_urbs[i],
&tascam->feedback_anchor);
err = usb_submit_urb(tascam->feedback_urbs[i],
GFP_ATOMIC);
if (err < 0) {
usb_unanchor_urb(tascam->feedback_urbs[i]);
usb_put_urb(tascam->feedback_urbs[i]);
atomic_dec(&tascam->active_urbs);
goto start_rollback;
}
atomic_inc(&tascam->active_urbs);
}
for (i = 0; i < NUM_PLAYBACK_URBS; i++) {
usb_get_urb(tascam->playback_urbs[i]);
usb_anchor_urb(tascam->playback_urbs[i],
&tascam->playback_anchor);
err = usb_submit_urb(tascam->playback_urbs[i],
GFP_ATOMIC);
if (err < 0) {
usb_unanchor_urb(tascam->playback_urbs[i]);
usb_put_urb(tascam->playback_urbs[i]);
atomic_dec(&tascam->active_urbs);
goto start_rollback;
}
atomic_inc(&tascam->active_urbs);
}
for (i = 0; i < NUM_CAPTURE_URBS; i++) {
usb_get_urb(tascam->capture_urbs[i]);
usb_anchor_urb(tascam->capture_urbs[i],
&tascam->capture_anchor);
err = usb_submit_urb(tascam->capture_urbs[i],
GFP_ATOMIC);
if (err < 0) {
usb_unanchor_urb(tascam->capture_urbs[i]);
usb_put_urb(tascam->capture_urbs[i]);
atomic_dec(&tascam->active_urbs);
goto start_rollback;
}
atomic_inc(&tascam->active_urbs);
}
return 0;
start_rollback:
dev_err(tascam->card->dev,
"Failed to submit URBs to start stream: %d\n", err);
do_stop = true;
}
if (do_stop)
schedule_work(&tascam->stop_work);
return err;
}
int tascam_init_pcm(struct snd_pcm *pcm)
{
struct tascam_card *tascam = pcm->private_data;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &tascam_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &tascam_capture_ops);
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
tascam->dev->dev.parent, 64 * 1024,
tascam_pcm_hw.buffer_bytes_max);
return 0;
}
+165
View File
@@ -0,0 +1,165 @@
/* SPDX-License-Identifier: GPL-2.0-only */
// Copyright (c) 2025 Šerif Rami <ramiserifpersia@gmail.com>
#ifndef __US144MKII_PCM_H
#define __US144MKII_PCM_H
#include "us144mkii.h"
/**
* tascam_pcm_hw - Hardware capabilities for TASCAM US-144MKII PCM.
*
* Defines the supported PCM formats, rates, channels, and buffer/period sizes
* for the TASCAM US-144MKII audio interface.
*/
extern const struct snd_pcm_hardware tascam_pcm_hw;
/**
* tascam_playback_ops - ALSA PCM operations for playback.
*
* This structure defines the callback functions for playback stream operations.
*/
extern const struct snd_pcm_ops tascam_playback_ops;
/**
* tascam_capture_ops - ALSA PCM operations for capture.
*
* This structure defines the callback functions for capture stream operations.
*/
extern const struct snd_pcm_ops tascam_capture_ops;
/**
* playback_urb_complete() - Completion handler for playback isochronous URBs.
* @urb: the completed URB
*
* This function runs in interrupt context. It calculates the number of bytes
* to send in the next set of packets based on the feedback-driven clock,
* copies the audio data from the ALSA ring buffer, and resubmits the URB.
*/
void playback_urb_complete(struct urb *urb);
/**
* feedback_urb_complete() - Completion handler for feedback isochronous URBs.
* @urb: the completed URB
*
* This is the master clock for the driver. It runs in interrupt context.
* It reads the feedback value from the device, which indicates how many
* samples the device has consumed. This information is used to adjust the
* playback rate and to advance the capture stream pointer, keeping both
* streams in sync. It then calls snd_pcm_period_elapsed if necessary and
* resubmits itself.
*/
void feedback_urb_complete(struct urb *urb);
/**
* capture_urb_complete() - Completion handler for capture bulk URBs.
* @urb: the completed URB
*
* This function runs in interrupt context. It copies the received raw data
* into an intermediate ring buffer and then schedules the workqueue to process
* it. It then resubmits the URB to receive more data.
*/
void capture_urb_complete(struct urb *urb);
/**
* tascam_stop_pcm_work_handler() - Work handler to stop PCM streams.
* @work: Pointer to the work_struct.
*
* This function is scheduled to stop PCM streams (playback and capture)
* from a workqueue context, avoiding blocking operations in interrupt context.
*/
void tascam_stop_pcm_work_handler(struct work_struct *work);
/**
* tascam_init_pcm() - Initializes the ALSA PCM device.
* @pcm: Pointer to the ALSA PCM device to initialize.
*
* This function sets up the PCM operations, adds ALSA controls for routing
* and sample rate, and preallocates pages for the PCM buffer.
*
* Return: 0 on success, or a negative error code on failure.
*/
int tascam_init_pcm(struct snd_pcm *pcm);
/**
* 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);
/**
* process_playback_routing_us144mkii() - Apply playback routing matrix
* @tascam: The driver instance.
* @src_buffer: Buffer containing 4 channels of S24_3LE audio from ALSA.
* @dst_buffer: Buffer to be filled for the USB device.
* @frames: Number of frames to process.
*/
void process_playback_routing_us144mkii(struct tascam_card *tascam,
const u8 *src_buffer, u8 *dst_buffer,
size_t frames);
/**
* process_capture_routing_us144mkii() - Apply capture routing matrix
* @tascam: The driver instance.
* @decoded_block: Buffer containing 4 channels of S32LE decoded audio.
* @routed_block: Buffer to be filled for ALSA.
*/
void process_capture_routing_us144mkii(struct tascam_card *tascam,
const s32 *decoded_block,
s32 *routed_block);
/**
* tascam_pcm_hw_params() - Configures 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);
/**
* tascam_pcm_hw_free() - Frees hardware parameters for PCM streams.
* @substream: The ALSA PCM substream.
*
* This function is a stub for freeing hardware-related resources.
*
* Return: 0 on success.
*/
int tascam_pcm_hw_free(struct snd_pcm_substream *substream);
/**
* tascam_pcm_trigger() - Triggers the start or stop of PCM streams.
* @substream: The ALSA PCM substream.
* @cmd: The trigger command (e.g., SNDRV_PCM_TRIGGER_START).
*
* This function handles starting and stopping of playback and capture streams
* by submitting or killing the associated URBs.
*
* Return: 0 on success, or a negative error code on failure.
*/
int tascam_pcm_trigger(struct snd_pcm_substream *substream, int cmd);
/**
* tascam_capture_work_handler() - Deferred work for processing capture data.
* @work: the work_struct instance
*
* This function runs in a kernel thread context, not an IRQ context. It reads
* raw data from the capture ring buffer, decodes it, applies routing, and
* copies the final audio data into the ALSA capture ring buffer. This offloads
* the CPU-intensive decoding from the time-sensitive URB completion handlers.
*/
void tascam_capture_work_handler(struct work_struct *work);
#endif /* __US144MKII_PCM_H */
@@ -0,0 +1,456 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2025 Šerif Rami <ramiserifpersia@gmail.com>
#include "us144mkii.h"
/*
* tascam_playback_open() - Opens the PCM playback substream.
* @substream: The ALSA PCM substream to open.
*
* This function sets the hardware parameters for the playback substream
* and stores a reference to the substream in the driver's private data.
*
* Return: 0 on success.
*/
static int tascam_playback_open(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
substream->runtime->hw = tascam_pcm_hw;
tascam->playback_substream = substream;
atomic_set(&tascam->playback_active, 0);
return 0;
}
/*
* tascam_playback_close() - Closes the PCM playback substream.
* @substream: The ALSA PCM substream to close.
*
* This function clears the reference to the playback substream in the
* driver's private data.
*
* Return: 0 on success.
*/
static int tascam_playback_close(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
tascam->playback_substream = NULL;
return 0;
}
/*
* tascam_playback_prepare() - Prepares the PCM playback substream for use.
* @substream: The ALSA PCM substream to prepare.
*
* This function initializes playback-related counters and flags, and configures
* the playback URBs with appropriate packet sizes based on the nominal frame
* rate.
*
* Return: 0 on success.
*/
static int tascam_playback_prepare(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
int i, u;
size_t nominal_frames_per_packet, nominal_bytes_per_packet;
size_t total_bytes_in_urb;
tascam->driver_playback_pos = 0;
tascam->playback_frames_consumed = 0;
tascam->last_period_pos = 0;
tascam->feedback_pattern_in_idx = 0;
tascam->feedback_pattern_out_idx = 0;
tascam->feedback_synced = false;
tascam->feedback_consecutive_errors = 0;
tascam->feedback_urb_skip_count = NUM_FEEDBACK_URBS;
nominal_frames_per_packet = runtime->rate / 8000;
for (i = 0; i < FEEDBACK_ACCUMULATOR_SIZE; i++)
tascam->feedback_accumulator_pattern[i] =
nominal_frames_per_packet;
for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
struct urb *f_urb = tascam->feedback_urbs[i];
int j;
f_urb->number_of_packets = FEEDBACK_URB_PACKETS;
f_urb->transfer_buffer_length =
FEEDBACK_URB_PACKETS * FEEDBACK_PACKET_SIZE;
for (j = 0; j < FEEDBACK_URB_PACKETS; j++) {
f_urb->iso_frame_desc[j].offset =
j * FEEDBACK_PACKET_SIZE;
f_urb->iso_frame_desc[j].length = FEEDBACK_PACKET_SIZE;
}
}
nominal_bytes_per_packet = nominal_frames_per_packet * BYTES_PER_FRAME;
total_bytes_in_urb = nominal_bytes_per_packet * PLAYBACK_URB_PACKETS;
for (u = 0; u < NUM_PLAYBACK_URBS; u++) {
struct urb *urb = tascam->playback_urbs[u];
memset(urb->transfer_buffer, 0,
tascam->playback_urb_alloc_size);
urb->transfer_buffer_length = total_bytes_in_urb;
urb->number_of_packets = PLAYBACK_URB_PACKETS;
for (i = 0; i < PLAYBACK_URB_PACKETS; i++) {
urb->iso_frame_desc[i].offset =
i * nominal_bytes_per_packet;
urb->iso_frame_desc[i].length =
nominal_bytes_per_packet;
}
}
return 0;
}
/*
* tascam_playback_pointer() - Returns the current playback pointer position.
* @substream: The ALSA PCM substream.
*
* This function returns the current position of the playback pointer within
* the ALSA ring buffer, in frames.
*
* Return: The current playback pointer position in frames.
*/
static snd_pcm_uframes_t
tascam_playback_pointer(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
u64 pos;
if (!atomic_read(&tascam->playback_active))
return 0;
scoped_guard(spinlock_irqsave, &tascam->lock) {
pos = tascam->playback_frames_consumed;
}
if (runtime->buffer_size == 0)
return 0;
return do_div(pos, runtime->buffer_size);
}
/*
* tascam_playback_ops - ALSA PCM operations for playback.
*
* This structure defines the callback functions for playback stream operations,
* including open, close, ioctl, hardware parameters, hardware free, prepare,
* trigger, and pointer.
*/
const struct snd_pcm_ops tascam_playback_ops = {
.open = tascam_playback_open,
.close = tascam_playback_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = tascam_pcm_hw_params,
.hw_free = tascam_pcm_hw_free,
.prepare = tascam_playback_prepare,
.trigger = tascam_pcm_trigger,
.pointer = tascam_playback_pointer,
};
void playback_urb_complete(struct urb *urb)
{
struct tascam_card *tascam = urb->context;
struct snd_pcm_substream *substream;
struct snd_pcm_runtime *runtime;
size_t total_bytes_for_urb = 0;
snd_pcm_uframes_t offset_frames;
snd_pcm_uframes_t frames_to_copy;
int ret, i;
if (urb->status) {
if (urb->status != -ENOENT && urb->status != -ECONNRESET &&
urb->status != -ESHUTDOWN && urb->status != -ENODEV)
dev_err_ratelimited(tascam->card->dev,
"Playback URB failed: %d\n",
urb->status);
goto out;
}
if (!tascam || !atomic_read(&tascam->playback_active))
goto out;
substream = tascam->playback_substream;
if (!substream || !substream->runtime)
goto out;
runtime = substream->runtime;
scoped_guard(spinlock_irqsave, &tascam->lock) {
for (i = 0; i < urb->number_of_packets; i++) {
unsigned int frames_for_packet;
size_t bytes_for_packet;
if (tascam->feedback_synced) {
frames_for_packet =
tascam->feedback_accumulator_pattern
[tascam->feedback_pattern_out_idx];
tascam->feedback_pattern_out_idx =
(tascam->feedback_pattern_out_idx + 1) %
FEEDBACK_ACCUMULATOR_SIZE;
} else {
frames_for_packet = runtime->rate / 8000;
}
bytes_for_packet = frames_for_packet * BYTES_PER_FRAME;
urb->iso_frame_desc[i].offset = total_bytes_for_urb;
urb->iso_frame_desc[i].length = bytes_for_packet;
total_bytes_for_urb += bytes_for_packet;
}
urb->transfer_buffer_length = total_bytes_for_urb;
offset_frames = tascam->driver_playback_pos;
frames_to_copy = bytes_to_frames(runtime, total_bytes_for_urb);
tascam->driver_playback_pos =
(offset_frames + frames_to_copy) % runtime->buffer_size;
}
if (total_bytes_for_urb > 0) {
u8 *dst_buf = urb->transfer_buffer;
/* Handle ring buffer wrap-around */
if (offset_frames + frames_to_copy > runtime->buffer_size) {
size_t first_chunk_bytes = frames_to_bytes(
runtime, runtime->buffer_size - offset_frames);
size_t second_chunk_bytes =
total_bytes_for_urb - first_chunk_bytes;
memcpy(dst_buf,
runtime->dma_area +
frames_to_bytes(runtime, offset_frames),
first_chunk_bytes);
memcpy(dst_buf + first_chunk_bytes, runtime->dma_area,
second_chunk_bytes);
} else {
memcpy(dst_buf,
runtime->dma_area +
frames_to_bytes(runtime, offset_frames),
total_bytes_for_urb);
}
process_playback_routing_us144mkii(tascam, dst_buf, dst_buf,
frames_to_copy);
}
urb->dev = tascam->dev;
usb_get_urb(urb);
usb_anchor_urb(urb, &tascam->playback_anchor);
ret = usb_submit_urb(urb, GFP_ATOMIC);
if (ret < 0) {
dev_err_ratelimited(tascam->card->dev,
"Failed to resubmit playback URB: %d\n",
ret);
usb_unanchor_urb(urb);
usb_put_urb(urb);
atomic_dec(
&tascam->active_urbs); /* Decrement on failed resubmission */
}
out:
usb_put_urb(urb);
}
void feedback_urb_complete(struct urb *urb)
{
struct tascam_card *tascam = urb->context;
struct snd_pcm_substream *playback_ss, *capture_ss;
struct snd_pcm_runtime *playback_rt, *capture_rt;
u64 total_frames_in_urb = 0;
int ret, p;
unsigned int old_in_idx, new_in_idx;
bool playback_period_elapsed = false;
bool capture_period_elapsed = false;
if (urb->status) {
if (urb->status != -ENOENT && urb->status != -ECONNRESET &&
urb->status != -ESHUTDOWN && urb->status != -ENODEV) {
dev_err_ratelimited(tascam->card->dev,
"Feedback URB failed: %d\n",
urb->status);
atomic_dec(
&tascam->active_urbs); /* Decrement on failed resubmission */
}
goto out;
}
if (!tascam || !atomic_read(&tascam->playback_active))
goto out;
playback_ss = tascam->playback_substream;
if (!playback_ss || !playback_ss->runtime)
goto out;
playback_rt = playback_ss->runtime;
capture_ss = tascam->capture_substream;
capture_rt = capture_ss ? capture_ss->runtime : NULL;
scoped_guard(spinlock_irqsave, &tascam->lock) {
if (tascam->feedback_urb_skip_count > 0) {
tascam->feedback_urb_skip_count--;
break;
}
old_in_idx = tascam->feedback_pattern_in_idx;
for (p = 0; p < urb->number_of_packets; p++) {
u8 feedback_value = 0;
const unsigned int *pattern;
bool packet_ok =
(urb->iso_frame_desc[p].status == 0 &&
urb->iso_frame_desc[p].actual_length >= 1);
if (packet_ok)
feedback_value =
*((u8 *)urb->transfer_buffer +
urb->iso_frame_desc[p].offset);
if (packet_ok) {
int delta = feedback_value -
tascam->fpo.base_feedback_value +
tascam->fpo.feedback_offset;
int pattern_idx;
if (delta < 0) {
pattern_idx =
0; // Clamp to the lowest pattern
} else if (delta >= 5) {
pattern_idx =
4; // Clamp to the highest pattern
} else {
pattern_idx = delta;
}
pattern =
tascam->fpo
.full_frame_patterns[pattern_idx];
tascam->feedback_consecutive_errors = 0;
int i;
for (i = 0; i < 8; i++) {
unsigned int in_idx =
(tascam->feedback_pattern_in_idx +
i) %
FEEDBACK_ACCUMULATOR_SIZE;
tascam->feedback_accumulator_pattern
[in_idx] = pattern[i];
total_frames_in_urb += pattern[i];
}
} else {
unsigned int nominal_frames =
playback_rt->rate / 8000;
int i;
if (tascam->feedback_synced) {
tascam->feedback_consecutive_errors++;
if (tascam->feedback_consecutive_errors >
FEEDBACK_SYNC_LOSS_THRESHOLD) {
dev_err(tascam->card->dev,
"Fatal: Feedback sync lost. Stopping stream.\n");
schedule_work(
&tascam->stop_pcm_work);
tascam->feedback_synced = false;
break;
}
}
for (i = 0; i < 8; i++) {
unsigned int in_idx =
(tascam->feedback_pattern_in_idx +
i) %
FEEDBACK_ACCUMULATOR_SIZE;
tascam->feedback_accumulator_pattern
[in_idx] = nominal_frames;
total_frames_in_urb += nominal_frames;
}
}
tascam->feedback_pattern_in_idx =
(tascam->feedback_pattern_in_idx + 8) %
FEEDBACK_ACCUMULATOR_SIZE;
}
new_in_idx = tascam->feedback_pattern_in_idx;
if (!tascam->feedback_synced) {
unsigned int out_idx = tascam->feedback_pattern_out_idx;
bool is_ahead = (new_in_idx - out_idx) %
FEEDBACK_ACCUMULATOR_SIZE <
(FEEDBACK_ACCUMULATOR_SIZE / 2);
bool was_behind = (old_in_idx - out_idx) %
FEEDBACK_ACCUMULATOR_SIZE >=
(FEEDBACK_ACCUMULATOR_SIZE / 2);
if (is_ahead && was_behind) {
dev_dbg(tascam->card->dev,
"Sync Acquired! (in: %u, out: %u)\n",
new_in_idx, out_idx);
tascam->feedback_synced = true;
tascam->feedback_consecutive_errors = 0;
}
}
if (total_frames_in_urb > 0) {
tascam->playback_frames_consumed += total_frames_in_urb;
if (atomic_read(&tascam->capture_active))
tascam->capture_frames_processed +=
total_frames_in_urb;
}
if (playback_rt->period_size > 0) {
u64 current_period =
div_u64(tascam->playback_frames_consumed,
playback_rt->period_size);
if (current_period > tascam->last_period_pos) {
tascam->last_period_pos = current_period;
playback_period_elapsed = true;
}
}
if (atomic_read(&tascam->capture_active) && capture_rt &&
capture_rt->period_size > 0) {
u64 current_capture_period =
div_u64(tascam->capture_frames_processed,
capture_rt->period_size);
if (current_capture_period >
tascam->last_capture_period_pos) {
tascam->last_capture_period_pos =
current_capture_period;
capture_period_elapsed = true;
}
}
}
if (playback_period_elapsed)
snd_pcm_period_elapsed(playback_ss);
if (capture_period_elapsed)
snd_pcm_period_elapsed(capture_ss);
urb->dev = tascam->dev;
usb_get_urb(urb);
usb_anchor_urb(urb, &tascam->feedback_anchor);
ret = usb_submit_urb(urb, GFP_ATOMIC);
if (ret < 0) {
dev_err_ratelimited(tascam->card->dev,
"Failed to resubmit feedback URB: %d\n",
ret);
usb_unanchor_urb(urb);
usb_put_urb(urb);
}
out:
usb_put_urb(urb);
}
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->playback_substream)
snd_pcm_stop(tascam->playback_substream, SNDRV_PCM_STATE_XRUN);
if (tascam->capture_substream)
snd_pcm_stop(tascam->capture_substream, SNDRV_PCM_STATE_XRUN);
}
+242
View File
@@ -0,0 +1,242 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Tascam US-X2Y USB soundcards
*
* FPGA Loader + ALSA Startup
*
* Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de>
*/
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <sound/core.h>
#include <sound/memalloc.h>
#include <sound/pcm.h>
#include <sound/hwdep.h>
#include "usx2y.h"
#include "usbusx2y.h"
#include "usX2Yhwdep.h"
static vm_fault_t snd_us428ctls_vm_fault(struct vm_fault *vmf)
{
unsigned long offset;
struct page *page;
void *vaddr;
offset = vmf->pgoff << PAGE_SHIFT;
if (offset >= US428_SHAREDMEM_PAGES)
return VM_FAULT_SIGBUS;
vaddr = (char *)((struct usx2ydev *)vmf->vma->vm_private_data)->us428ctls_sharedmem + offset;
page = virt_to_page(vaddr);
get_page(page);
vmf->page = page;
return 0;
}
static const struct vm_operations_struct us428ctls_vm_ops = {
.fault = snd_us428ctls_vm_fault,
};
static int snd_us428ctls_mmap(struct snd_hwdep *hw, struct file *filp, struct vm_area_struct *area)
{
unsigned long size = (unsigned long)(area->vm_end - area->vm_start);
struct usx2ydev *us428 = hw->private_data;
// FIXME this hwdep interface is used twice: fpga download and mmap for controlling Lights etc. Maybe better using 2 hwdep devs?
// so as long as the device isn't fully initialised yet we return -EBUSY here.
if (!(us428->chip_status & USX2Y_STAT_CHIP_INIT))
return -EBUSY;
/* if userspace tries to mmap beyond end of our buffer, fail */
if (size > US428_SHAREDMEM_PAGES) {
dev_dbg(hw->card->dev, "%s: mmap size %lu > %lu\n", __func__,
size, (unsigned long)US428_SHAREDMEM_PAGES);
return -EINVAL;
}
area->vm_ops = &us428ctls_vm_ops;
vm_flags_set(area, VM_DONTEXPAND | VM_DONTDUMP);
area->vm_private_data = hw->private_data;
return 0;
}
static __poll_t snd_us428ctls_poll(struct snd_hwdep *hw, struct file *file, poll_table *wait)
{
__poll_t mask = 0;
struct usx2ydev *us428 = hw->private_data;
struct us428ctls_sharedmem *shm = us428->us428ctls_sharedmem;
if (us428->chip_status & USX2Y_STAT_CHIP_HUP)
return EPOLLHUP;
poll_wait(file, &us428->us428ctls_wait_queue_head, wait);
if (shm && shm->ctl_snapshot_last != shm->ctl_snapshot_red)
mask |= EPOLLIN;
return mask;
}
static int snd_usx2y_hwdep_dsp_status(struct snd_hwdep *hw,
struct snd_hwdep_dsp_status *info)
{
static const char * const type_ids[USX2Y_TYPE_NUMS] = {
[USX2Y_TYPE_122] = "us122",
[USX2Y_TYPE_224] = "us224",
[USX2Y_TYPE_428] = "us428",
};
struct usx2ydev *us428 = hw->private_data;
int id = -1;
switch (le16_to_cpu(us428->dev->descriptor.idProduct)) {
case USB_ID_US122:
id = USX2Y_TYPE_122;
break;
case USB_ID_US224:
id = USX2Y_TYPE_224;
break;
case USB_ID_US428:
id = USX2Y_TYPE_428;
break;
}
if (id < 0)
return -ENODEV;
strscpy(info->id, type_ids[id]);
info->num_dsps = 2; // 0: Prepad Data, 1: FPGA Code
if (us428->chip_status & USX2Y_STAT_CHIP_INIT)
info->chip_ready = 1;
info->version = USX2Y_DRIVER_VERSION;
return 0;
}
static int usx2y_create_usbmidi(struct snd_card *card)
{
static const struct snd_usb_midi_endpoint_info quirk_data_1 = {
.out_ep = 0x06,
.in_ep = 0x06,
.out_cables = 0x001,
.in_cables = 0x001
};
static const struct snd_usb_audio_quirk quirk_1 = {
.vendor_name = "TASCAM",
.product_name = NAME_ALLCAPS,
.ifnum = 0,
.type = QUIRK_MIDI_FIXED_ENDPOINT,
.data = &quirk_data_1
};
static const struct snd_usb_midi_endpoint_info quirk_data_2 = {
.out_ep = 0x06,
.in_ep = 0x06,
.out_cables = 0x003,
.in_cables = 0x003
};
static const struct snd_usb_audio_quirk quirk_2 = {
.vendor_name = "TASCAM",
.product_name = "US428",
.ifnum = 0,
.type = QUIRK_MIDI_FIXED_ENDPOINT,
.data = &quirk_data_2
};
struct usb_device *dev = usx2y(card)->dev;
struct usb_interface *iface = usb_ifnum_to_if(dev, 0);
const struct snd_usb_audio_quirk *quirk =
le16_to_cpu(dev->descriptor.idProduct) == USB_ID_US428 ?
&quirk_2 : &quirk_1;
return snd_usbmidi_create(card, iface, &usx2y(card)->midi_list, quirk);
}
static int usx2y_create_alsa_devices(struct snd_card *card)
{
int err;
err = usx2y_create_usbmidi(card);
if (err < 0) {
dev_err(card->dev, "%s: usx2y_create_usbmidi error %i\n",
__func__, err);
return err;
}
err = usx2y_audio_create(card);
if (err < 0)
return err;
err = usx2y_hwdep_pcm_new(card);
if (err < 0)
return err;
err = snd_card_register(card);
if (err < 0)
return err;
return 0;
}
static int snd_usx2y_hwdep_dsp_load(struct snd_hwdep *hw,
struct snd_hwdep_dsp_image *dsp)
{
struct usx2ydev *priv = hw->private_data;
struct usb_device *dev = priv->dev;
int lret, err;
char *buf;
buf = memdup_user(dsp->image, dsp->length);
if (IS_ERR(buf))
return PTR_ERR(buf);
err = usb_set_interface(dev, 0, 1);
if (err)
dev_err(&dev->dev, "usb_set_interface error\n");
else
err = usb_bulk_msg(dev, usb_sndbulkpipe(dev, 2), buf, dsp->length, &lret, 6000);
kfree(buf);
if (err)
return err;
if (dsp->index == 1) {
msleep(250); // give the device some time
err = usx2y_async_seq04_init(priv);
if (err) {
dev_err(&dev->dev, "usx2y_async_seq04_init error\n");
return err;
}
err = usx2y_in04_init(priv);
if (err) {
dev_err(&dev->dev, "usx2y_in04_init error\n");
return err;
}
err = usx2y_create_alsa_devices(hw->card);
if (err) {
dev_err(&dev->dev, "usx2y_create_alsa_devices error %i\n", err);
return err;
}
priv->chip_status |= USX2Y_STAT_CHIP_INIT;
}
return err;
}
int usx2y_hwdep_new(struct snd_card *card, struct usb_device *device)
{
int err;
struct snd_hwdep *hw;
struct usx2ydev *us428 = usx2y(card);
err = snd_hwdep_new(card, SND_USX2Y_LOADER_ID, 0, &hw);
if (err < 0)
return err;
hw->iface = SNDRV_HWDEP_IFACE_USX2Y;
hw->private_data = us428;
hw->ops.dsp_status = snd_usx2y_hwdep_dsp_status;
hw->ops.dsp_load = snd_usx2y_hwdep_dsp_load;
hw->ops.mmap = snd_us428ctls_mmap;
hw->ops.poll = snd_us428ctls_poll;
hw->exclusive = 1;
sprintf(hw->name, "/dev/bus/usb/%03d/%03d", device->bus->busnum, device->devnum);
us428->us428ctls_sharedmem = alloc_pages_exact(US428_SHAREDMEM_PAGES, GFP_KERNEL);
if (!us428->us428ctls_sharedmem)
return -ENOMEM;
memset(us428->us428ctls_sharedmem, -1, US428_SHAREDMEM_PAGES);
us428->us428ctls_sharedmem->ctl_snapshot_last = -2;
return 0;
}
+7
View File
@@ -0,0 +1,7 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef USX2YHWDEP_H
#define USX2YHWDEP_H
int usx2y_hwdep_new(struct snd_card *card, struct usb_device *device);
#endif
+775
View File
@@ -0,0 +1,775 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2007, 2008 Karsten Wiese <fzu@wemgehoertderstaat.de>
*/
#include <linux/usb.h>
#include <linux/gfp.h>
#include "usb_stream.h"
/* setup */
static unsigned int usb_stream_next_packet_size(struct usb_stream_kernel *sk)
{
struct usb_stream *s = sk->s;
sk->out_phase_peeked = (sk->out_phase & 0xffff) + sk->freqn;
return (sk->out_phase_peeked >> 16) * s->cfg.frame_size;
}
static void playback_prep_freqn(struct usb_stream_kernel *sk, struct urb *urb)
{
struct usb_stream *s = sk->s;
int pack, lb = 0;
for (pack = 0; pack < sk->n_o_ps; pack++) {
int l = usb_stream_next_packet_size(sk);
if (s->idle_outsize + lb + l > s->period_size)
goto check;
sk->out_phase = sk->out_phase_peeked;
urb->iso_frame_desc[pack].offset = lb;
urb->iso_frame_desc[pack].length = l;
lb += l;
}
check:
urb->number_of_packets = pack;
urb->transfer_buffer_length = lb;
s->idle_outsize += lb - s->period_size;
}
static int init_pipe_urbs(struct usb_stream_kernel *sk,
unsigned int use_packsize,
struct urb **urbs, char *transfer,
struct usb_device *dev, int pipe)
{
int u, p;
int maxpacket = use_packsize ?
use_packsize : usb_maxpacket(dev, pipe);
int transfer_length = maxpacket * sk->n_o_ps;
for (u = 0; u < USB_STREAM_NURBS;
++u, transfer += transfer_length) {
struct urb *urb = urbs[u];
struct usb_iso_packet_descriptor *desc;
urb->transfer_buffer = transfer;
urb->dev = dev;
urb->pipe = pipe;
urb->number_of_packets = sk->n_o_ps;
urb->context = sk;
urb->interval = 1;
if (usb_pipeout(pipe))
continue;
if (usb_urb_ep_type_check(urb))
return -EINVAL;
urb->transfer_buffer_length = transfer_length;
desc = urb->iso_frame_desc;
desc->offset = 0;
desc->length = maxpacket;
for (p = 1; p < sk->n_o_ps; ++p) {
desc[p].offset = desc[p - 1].offset + maxpacket;
desc[p].length = maxpacket;
}
}
return 0;
}
static int init_urbs(struct usb_stream_kernel *sk, unsigned int use_packsize,
struct usb_device *dev, int in_pipe, int out_pipe)
{
struct usb_stream *s = sk->s;
char *indata =
(char *)s + sizeof(*s) + sizeof(struct usb_stream_packet) * s->inpackets;
int u;
for (u = 0; u < USB_STREAM_NURBS; ++u) {
sk->inurb[u] = usb_alloc_urb(sk->n_o_ps, GFP_KERNEL);
if (!sk->inurb[u])
return -ENOMEM;
sk->outurb[u] = usb_alloc_urb(sk->n_o_ps, GFP_KERNEL);
if (!sk->outurb[u])
return -ENOMEM;
}
if (init_pipe_urbs(sk, use_packsize, sk->inurb, indata, dev, in_pipe) ||
init_pipe_urbs(sk, use_packsize, sk->outurb, sk->write_page, dev,
out_pipe))
return -EINVAL;
return 0;
}
/*
* convert a sampling rate into our full speed format (fs/1000 in Q16.16)
* this will overflow at approx 524 kHz
*/
static inline unsigned int get_usb_full_speed_rate(unsigned int rate)
{
return ((rate << 13) + 62) / 125;
}
/*
* convert a sampling rate into USB high speed format (fs/8000 in Q16.16)
* this will overflow at approx 4 MHz
*/
static inline unsigned int get_usb_high_speed_rate(unsigned int rate)
{
return ((rate << 10) + 62) / 125;
}
void usb_stream_free(struct usb_stream_kernel *sk)
{
struct usb_stream *s;
unsigned int u;
for (u = 0; u < USB_STREAM_NURBS; ++u) {
usb_free_urb(sk->inurb[u]);
sk->inurb[u] = NULL;
usb_free_urb(sk->outurb[u]);
sk->outurb[u] = NULL;
}
s = sk->s;
if (!s)
return;
if (sk->write_page) {
free_pages_exact(sk->write_page, s->write_size);
sk->write_page = NULL;
}
free_pages_exact(s, s->read_size);
sk->s = NULL;
}
struct usb_stream *usb_stream_new(struct usb_stream_kernel *sk,
struct usb_device *dev,
unsigned int in_endpoint,
unsigned int out_endpoint,
unsigned int sample_rate,
unsigned int use_packsize,
unsigned int period_frames,
unsigned int frame_size)
{
int packets, max_packsize;
int in_pipe, out_pipe;
int read_size = sizeof(struct usb_stream);
int write_size;
int usb_frames = dev->speed == USB_SPEED_HIGH ? 8000 : 1000;
in_pipe = usb_rcvisocpipe(dev, in_endpoint);
out_pipe = usb_sndisocpipe(dev, out_endpoint);
max_packsize = use_packsize ?
use_packsize : usb_maxpacket(dev, in_pipe);
/*
t_period = period_frames / sample_rate
iso_packs = t_period / t_iso_frame
= (period_frames / sample_rate) * (1 / t_iso_frame)
*/
packets = period_frames * usb_frames / sample_rate + 1;
if (dev->speed == USB_SPEED_HIGH)
packets = (packets + 7) & ~7;
read_size += packets * USB_STREAM_URBDEPTH *
(max_packsize + sizeof(struct usb_stream_packet));
max_packsize = usb_maxpacket(dev, out_pipe);
write_size = max_packsize * packets * USB_STREAM_URBDEPTH;
if (read_size >= 256*PAGE_SIZE || write_size >= 256*PAGE_SIZE) {
dev_warn(&dev->dev, "%s: a size exceeds 128*PAGE_SIZE\n", __func__);
goto out;
}
sk->s = alloc_pages_exact(read_size,
GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN);
if (!sk->s) {
dev_warn(&dev->dev, "us122l: couldn't allocate read buffer\n");
goto out;
}
sk->s->cfg.version = USB_STREAM_INTERFACE_VERSION;
sk->s->read_size = read_size;
sk->s->cfg.sample_rate = sample_rate;
sk->s->cfg.frame_size = frame_size;
sk->n_o_ps = packets;
sk->s->inpackets = packets * USB_STREAM_URBDEPTH;
sk->s->cfg.period_frames = period_frames;
sk->s->period_size = frame_size * period_frames;
sk->s->write_size = write_size;
sk->write_page = alloc_pages_exact(write_size,
GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN);
if (!sk->write_page) {
dev_warn(&dev->dev, "us122l: couldn't allocate write buffer\n");
usb_stream_free(sk);
return NULL;
}
/* calculate the frequency in 16.16 format */
if (dev->speed == USB_SPEED_FULL)
sk->freqn = get_usb_full_speed_rate(sample_rate);
else
sk->freqn = get_usb_high_speed_rate(sample_rate);
if (init_urbs(sk, use_packsize, dev, in_pipe, out_pipe) < 0) {
usb_stream_free(sk);
return NULL;
}
sk->s->state = usb_stream_stopped;
out:
return sk->s;
}
/* start */
static bool balance_check(struct usb_stream_kernel *sk, struct urb *urb)
{
bool r;
if (unlikely(urb->status)) {
if (urb->status != -ESHUTDOWN && urb->status != -ENOENT)
dev_warn(&sk->dev->dev, "%s: status=%i\n", __func__,
urb->status);
sk->iso_frame_balance = 0x7FFFFFFF;
return false;
}
r = sk->iso_frame_balance == 0;
if (!r)
sk->i_urb = urb;
return r;
}
static bool balance_playback(struct usb_stream_kernel *sk, struct urb *urb)
{
sk->iso_frame_balance += urb->number_of_packets;
return balance_check(sk, urb);
}
static bool balance_capture(struct usb_stream_kernel *sk, struct urb *urb)
{
sk->iso_frame_balance -= urb->number_of_packets;
return balance_check(sk, urb);
}
static void subs_set_complete(struct urb **urbs, void (*complete)(struct urb *))
{
int u;
for (u = 0; u < USB_STREAM_NURBS; u++) {
struct urb *urb = urbs[u];
urb->complete = complete;
}
}
static int usb_stream_prepare_playback(struct usb_stream_kernel *sk,
struct urb *inurb)
{
struct usb_stream *s = sk->s;
struct urb *io;
struct usb_iso_packet_descriptor *id, *od;
int p = 0, lb = 0, l = 0;
io = sk->idle_outurb;
od = io->iso_frame_desc;
for (; s->sync_packet < 0; ++p, ++s->sync_packet) {
struct urb *ii = sk->completed_inurb;
id = ii->iso_frame_desc +
ii->number_of_packets + s->sync_packet;
l = id->actual_length;
od[p].length = l;
od[p].offset = lb;
lb += l;
}
for (;
s->sync_packet < inurb->number_of_packets && p < sk->n_o_ps;
++p, ++s->sync_packet) {
l = inurb->iso_frame_desc[s->sync_packet].actual_length;
if (s->idle_outsize + lb + l > s->period_size)
goto check_ok;
od[p].length = l;
od[p].offset = lb;
lb += l;
}
check_ok:
s->sync_packet -= inurb->number_of_packets;
if (unlikely(s->sync_packet < -2 || s->sync_packet > 0)) {
dev_warn(&sk->dev->dev,
"%s: invalid sync_packet = %i; p=%i nop=%i %i %x %x %x > %x\n",
__func__,
s->sync_packet, p, inurb->number_of_packets,
s->idle_outsize + lb + l,
s->idle_outsize, lb, l,
s->period_size);
return -1;
}
if (unlikely(lb % s->cfg.frame_size)) {
dev_warn(&sk->dev->dev, "%s: invalid outsize = %i\n",
__func__, lb);
return -1;
}
s->idle_outsize += lb - s->period_size;
io->number_of_packets = p;
io->transfer_buffer_length = lb;
if (s->idle_outsize <= 0)
return 0;
dev_warn(&sk->dev->dev, "%s: idle=%i\n", __func__, s->idle_outsize);
return -1;
}
static void prepare_inurb(int number_of_packets, struct urb *iu)
{
struct usb_iso_packet_descriptor *id;
int p;
iu->number_of_packets = number_of_packets;
id = iu->iso_frame_desc;
id->offset = 0;
for (p = 0; p < iu->number_of_packets - 1; ++p)
id[p + 1].offset = id[p].offset + id[p].length;
iu->transfer_buffer_length =
id[0].length * iu->number_of_packets;
}
static int submit_urbs(struct usb_stream_kernel *sk,
struct urb *inurb, struct urb *outurb)
{
int err;
prepare_inurb(sk->idle_outurb->number_of_packets, sk->idle_inurb);
err = usb_submit_urb(sk->idle_inurb, GFP_ATOMIC);
if (err < 0)
goto report_failure;
sk->idle_inurb = sk->completed_inurb;
sk->completed_inurb = inurb;
err = usb_submit_urb(sk->idle_outurb, GFP_ATOMIC);
if (err < 0)
goto report_failure;
sk->idle_outurb = sk->completed_outurb;
sk->completed_outurb = outurb;
return 0;
report_failure:
dev_err(&sk->dev->dev, "%s error: %i\n", __func__, err);
return err;
}
#ifdef DEBUG_LOOP_BACK
/*
This loop_back() shows how to read/write the period data.
*/
static void loop_back(struct usb_stream *s)
{
char *i, *o;
int il, ol, l, p;
struct urb *iu;
struct usb_iso_packet_descriptor *id;
o = s->playback1st_to;
ol = s->playback1st_size;
l = 0;
if (s->insplit_pack >= 0) {
iu = sk->idle_inurb;
id = iu->iso_frame_desc;
p = s->insplit_pack;
} else
goto second;
loop:
for (; p < iu->number_of_packets && l < s->period_size; ++p) {
i = iu->transfer_buffer + id[p].offset;
il = id[p].actual_length;
if (l + il > s->period_size)
il = s->period_size - l;
if (il <= ol) {
memcpy(o, i, il);
o += il;
ol -= il;
} else {
memcpy(o, i, ol);
singen_6pack(o, ol);
o = s->playback_to;
memcpy(o, i + ol, il - ol);
o += il - ol;
ol = s->period_size - s->playback1st_size;
}
l += il;
}
if (iu == sk->completed_inurb) {
if (l != s->period_size)
dev_dbg(&sk->dev->dev, "%s:%i %i\n", __func__, __LINE__,
l/(int)s->cfg.frame_size);
return;
}
second:
iu = sk->completed_inurb;
id = iu->iso_frame_desc;
p = 0;
goto loop;
}
#else
static void loop_back(struct usb_stream *s)
{
}
#endif
static void stream_idle(struct usb_stream_kernel *sk,
struct urb *inurb, struct urb *outurb)
{
struct usb_stream *s = sk->s;
int l, p;
int insize = s->idle_insize;
int urb_size = 0;
s->inpacket_split = s->next_inpacket_split;
s->inpacket_split_at = s->next_inpacket_split_at;
s->next_inpacket_split = -1;
s->next_inpacket_split_at = 0;
for (p = 0; p < inurb->number_of_packets; ++p) {
struct usb_iso_packet_descriptor *id = inurb->iso_frame_desc;
l = id[p].actual_length;
if (unlikely(l == 0 || id[p].status)) {
dev_warn(&sk->dev->dev, "%s: underrun, status=%u\n",
__func__, id[p].status);
goto err_out;
}
s->inpacket_head++;
s->inpacket_head %= s->inpackets;
if (s->inpacket_split == -1)
s->inpacket_split = s->inpacket_head;
s->inpacket[s->inpacket_head].offset =
id[p].offset + (inurb->transfer_buffer - (void *)s);
s->inpacket[s->inpacket_head].length = l;
if (insize + l > s->period_size &&
s->next_inpacket_split == -1) {
s->next_inpacket_split = s->inpacket_head;
s->next_inpacket_split_at = s->period_size - insize;
}
insize += l;
urb_size += l;
}
s->idle_insize += urb_size - s->period_size;
if (s->idle_insize < 0) {
dev_warn(&sk->dev->dev, "%s error: %i\n", __func__,
(s->idle_insize)/(int)s->cfg.frame_size);
goto err_out;
}
s->insize_done += urb_size;
l = s->idle_outsize;
s->outpacket[0].offset = (sk->idle_outurb->transfer_buffer -
sk->write_page) - l;
if (usb_stream_prepare_playback(sk, inurb) < 0)
goto err_out;
s->outpacket[0].length = sk->idle_outurb->transfer_buffer_length + l;
s->outpacket[1].offset = sk->completed_outurb->transfer_buffer -
sk->write_page;
if (submit_urbs(sk, inurb, outurb) < 0)
goto err_out;
loop_back(s);
s->periods_done++;
wake_up_all(&sk->sleep);
return;
err_out:
s->state = usb_stream_xrun;
wake_up_all(&sk->sleep);
}
static void i_capture_idle(struct urb *urb)
{
struct usb_stream_kernel *sk = urb->context;
if (balance_capture(sk, urb))
stream_idle(sk, urb, sk->i_urb);
}
static void i_playback_idle(struct urb *urb)
{
struct usb_stream_kernel *sk = urb->context;
if (balance_playback(sk, urb))
stream_idle(sk, sk->i_urb, urb);
}
static void stream_start(struct usb_stream_kernel *sk,
struct urb *inurb, struct urb *outurb)
{
struct usb_stream *s = sk->s;
if (s->state >= usb_stream_sync1) {
int l, p, max_diff, max_diff_0;
int urb_size = 0;
unsigned int frames_per_packet, min_frames = 0;
frames_per_packet = (s->period_size - s->idle_insize);
frames_per_packet <<= 8;
frames_per_packet /=
s->cfg.frame_size * inurb->number_of_packets;
frames_per_packet++;
max_diff_0 = s->cfg.frame_size;
if (s->cfg.period_frames >= 256)
max_diff_0 <<= 1;
if (s->cfg.period_frames >= 1024)
max_diff_0 <<= 1;
max_diff = max_diff_0;
for (p = 0; p < inurb->number_of_packets; ++p) {
int diff;
l = inurb->iso_frame_desc[p].actual_length;
urb_size += l;
min_frames += frames_per_packet;
diff = urb_size -
(min_frames >> 8) * s->cfg.frame_size;
if (diff < max_diff)
max_diff = diff;
}
s->idle_insize -= max_diff - max_diff_0;
s->idle_insize += urb_size - s->period_size;
if (s->idle_insize < 0) {
dev_warn(&sk->dev->dev, "%s idle_insize: %i %i %i\n",
__func__,
s->idle_insize, urb_size, s->period_size);
return;
} else if (s->idle_insize == 0) {
s->next_inpacket_split =
(s->inpacket_head + 1) % s->inpackets;
s->next_inpacket_split_at = 0;
} else {
unsigned int split = s->inpacket_head;
l = s->idle_insize;
while (l > s->inpacket[split].length) {
l -= s->inpacket[split].length;
if (split == 0)
split = s->inpackets - 1;
else
split--;
}
s->next_inpacket_split = split;
s->next_inpacket_split_at =
s->inpacket[split].length - l;
}
s->insize_done += urb_size;
if (usb_stream_prepare_playback(sk, inurb) < 0)
return;
} else
playback_prep_freqn(sk, sk->idle_outurb);
if (submit_urbs(sk, inurb, outurb) < 0)
return;
if (s->state == usb_stream_sync1 && s->insize_done > 360000) {
/* just guesswork ^^^^^^ */
s->state = usb_stream_ready;
subs_set_complete(sk->inurb, i_capture_idle);
subs_set_complete(sk->outurb, i_playback_idle);
}
}
static void i_capture_start(struct urb *urb)
{
struct usb_iso_packet_descriptor *id = urb->iso_frame_desc;
struct usb_stream_kernel *sk = urb->context;
struct usb_stream *s = sk->s;
int p;
int empty = 0;
if (urb->status) {
dev_warn(&sk->dev->dev, "%s: status=%i\n", __func__, urb->status);
return;
}
for (p = 0; p < urb->number_of_packets; ++p) {
int l = id[p].actual_length;
if (l < s->cfg.frame_size) {
++empty;
if (s->state >= usb_stream_sync0) {
dev_warn(&sk->dev->dev, "%s: length %i\n", __func__, l);
return;
}
}
s->inpacket_head++;
s->inpacket_head %= s->inpackets;
s->inpacket[s->inpacket_head].offset =
id[p].offset + (urb->transfer_buffer - (void *)s);
s->inpacket[s->inpacket_head].length = l;
}
#ifdef SHOW_EMPTY
if (empty) {
dev_dbg(&sk->dev->dev, "%s:%i: %i", __func__, __LINE__,
urb->iso_frame_desc[0].actual_length);
for (pack = 1; pack < urb->number_of_packets; ++pack) {
int l = urb->iso_frame_desc[pack].actual_length;
printk(KERN_CONT " %i", l);
}
printk(KERN_CONT "\n");
}
#endif
if (!empty && s->state < usb_stream_sync1)
++s->state;
if (balance_capture(sk, urb))
stream_start(sk, urb, sk->i_urb);
}
static void i_playback_start(struct urb *urb)
{
struct usb_stream_kernel *sk = urb->context;
if (balance_playback(sk, urb))
stream_start(sk, sk->i_urb, urb);
}
int usb_stream_start(struct usb_stream_kernel *sk)
{
struct usb_stream *s = sk->s;
int frame = 0;
int u, err;
int try = 0;
if (s->state != usb_stream_stopped)
return -EAGAIN;
subs_set_complete(sk->inurb, i_capture_start);
subs_set_complete(sk->outurb, i_playback_start);
memset(sk->write_page, 0, s->write_size);
dotry:
s->insize_done = 0;
s->idle_insize = 0;
s->idle_outsize = 0;
s->sync_packet = -1;
s->inpacket_head = -1;
sk->iso_frame_balance = 0;
++try;
for (u = 0; u < 2; u++) {
struct urb *inurb = sk->inurb[u];
struct urb *outurb = sk->outurb[u];
playback_prep_freqn(sk, outurb);
inurb->number_of_packets = outurb->number_of_packets;
inurb->transfer_buffer_length =
inurb->number_of_packets *
inurb->iso_frame_desc[0].length;
if (u == 0) {
int now;
struct usb_device *dev = inurb->dev;
frame = usb_get_current_frame_number(dev);
do {
now = usb_get_current_frame_number(dev);
} while (now > -1 && now == frame);
}
err = usb_submit_urb(inurb, GFP_ATOMIC);
if (err < 0) {
dev_err(&sk->dev->dev,
"%s: usb_submit_urb(sk->inurb[%i]) returned %i\n",
__func__, u, err);
return err;
}
err = usb_submit_urb(outurb, GFP_ATOMIC);
if (err < 0) {
dev_err(&sk->dev->dev,
"%s: usb_submit_urb(sk->outurb[%i]) returned %i\n",
__func__, u, err);
return err;
}
if (inurb->start_frame != outurb->start_frame) {
dev_dbg(&sk->dev->dev,
"%s: u[%i] start_frames differ in:%u out:%u\n",
__func__, u, inurb->start_frame, outurb->start_frame);
goto check_retry;
}
}
try = 0;
check_retry:
if (try) {
usb_stream_stop(sk);
if (try < 5) {
msleep(1500);
dev_dbg(&sk->dev->dev, "goto dotry;\n");
goto dotry;
}
dev_warn(&sk->dev->dev,
"%s: couldn't start all urbs on the same start_frame.\n",
__func__);
return -EFAULT;
}
sk->idle_inurb = sk->inurb[USB_STREAM_NURBS - 2];
sk->idle_outurb = sk->outurb[USB_STREAM_NURBS - 2];
sk->completed_inurb = sk->inurb[USB_STREAM_NURBS - 1];
sk->completed_outurb = sk->outurb[USB_STREAM_NURBS - 1];
/* wait, check */
{
int wait_ms = 3000;
while (s->state != usb_stream_ready && wait_ms > 0) {
msleep(200);
wait_ms -= 200;
}
}
return s->state == usb_stream_ready ? 0 : -EFAULT;
}
/* stop */
void usb_stream_stop(struct usb_stream_kernel *sk)
{
int u;
if (!sk->s)
return;
for (u = 0; u < USB_STREAM_NURBS; ++u) {
usb_kill_urb(sk->inurb[u]);
usb_kill_urb(sk->outurb[u]);
}
sk->s->state = usb_stream_stopped;
msleep(400);
}
+47
View File
@@ -0,0 +1,47 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __USB_STREAM_H
#define __USB_STREAM_H
#include <uapi/sound/usb_stream.h>
#define USB_STREAM_NURBS 4
#define USB_STREAM_URBDEPTH 4
struct usb_stream_kernel {
struct usb_stream *s;
struct usb_device *dev;
void *write_page;
unsigned int n_o_ps;
struct urb *inurb[USB_STREAM_NURBS];
struct urb *idle_inurb;
struct urb *completed_inurb;
struct urb *outurb[USB_STREAM_NURBS];
struct urb *idle_outurb;
struct urb *completed_outurb;
struct urb *i_urb;
int iso_frame_balance;
wait_queue_head_t sleep;
unsigned int out_phase;
unsigned int out_phase_peeked;
unsigned int freqn;
};
struct usb_stream *usb_stream_new(struct usb_stream_kernel *sk,
struct usb_device *dev,
unsigned int in_endpoint,
unsigned int out_endpoint,
unsigned int sample_rate,
unsigned int use_packsize,
unsigned int period_frames,
unsigned int frame_size);
void usb_stream_free(struct usb_stream_kernel *sk);
int usb_stream_start(struct usb_stream_kernel *sk);
void usb_stream_stop(struct usb_stream_kernel *sk);
#endif /* __USB_STREAM_H */
@@ -0,0 +1,93 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
*
* Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de>
*/
enum E_IN84 {
E_FADER_0 = 0,
E_FADER_1,
E_FADER_2,
E_FADER_3,
E_FADER_4,
E_FADER_5,
E_FADER_6,
E_FADER_7,
E_FADER_M,
E_TRANSPORT,
E_MODIFIER = 10,
E_FILTER_SELECT,
E_SELECT,
E_MUTE,
E_SWITCH = 15,
E_WHEEL_GAIN,
E_WHEEL_FREQ,
E_WHEEL_Q,
E_WHEEL_PAN,
E_WHEEL = 20
};
#define T_RECORD 1
#define T_PLAY 2
#define T_STOP 4
#define T_F_FWD 8
#define T_REW 0x10
#define T_SOLO 0x20
#define T_REC 0x40
#define T_NULL 0x80
struct us428_ctls {
unsigned char fader[9];
unsigned char transport;
unsigned char modifier;
unsigned char filters_elect;
unsigned char select;
unsigned char mute;
unsigned char unknown;
unsigned char wswitch;
unsigned char wheel[5];
};
struct us428_set_byte {
unsigned char offset,
value;
};
enum {
ELT_VOLUME = 0,
ELT_LIGHT
};
struct usx2y_volume {
unsigned char channel,
lh,
ll,
rh,
rl;
};
struct us428_lights {
struct us428_set_byte light[7];
};
struct us428_p4out {
char type;
union {
struct usx2y_volume vol;
struct us428_lights lights;
} val;
};
#define N_US428_CTL_BUFS 16
#define N_US428_P4OUT_BUFS 16
struct us428ctls_sharedmem {
struct us428_ctls ctl_snapshot[N_US428_CTL_BUFS];
int ctl_snapshot_differs_at[N_US428_CTL_BUFS];
int ctl_snapshot_last, ctl_snapshot_red;
struct us428_p4out p4out[N_US428_P4OUT_BUFS];
int p4out_last, p4out_sent;
};
#define US428_SHAREDMEM_PAGES PAGE_ALIGN(sizeof(struct us428ctls_sharedmem))
+483
View File
@@ -0,0 +1,483 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* usbusx2y.c - ALSA USB US-428 Driver
*
2005-04-14 Karsten Wiese
Version 0.8.7.2:
Call snd_card_free() instead of snd_card_free_in_thread() to prevent oops with dead keyboard symptom.
Tested ok with kernel 2.6.12-rc2.
2004-12-14 Karsten Wiese
Version 0.8.7.1:
snd_pcm_open for rawusb pcm-devices now returns -EBUSY if called without rawusb's hwdep device being open.
2004-12-02 Karsten Wiese
Version 0.8.7:
Use macro usb_maxpacket() for portability.
2004-10-26 Karsten Wiese
Version 0.8.6:
wake_up() process waiting in usx2y_urbs_start() on error.
2004-10-21 Karsten Wiese
Version 0.8.5:
nrpacks is runtime or compiletime configurable now with tested values from 1 to 4.
2004-10-03 Karsten Wiese
Version 0.8.2:
Avoid any possible racing while in prepare callback.
2004-09-30 Karsten Wiese
Version 0.8.0:
Simplified things and made ohci work again.
2004-09-20 Karsten Wiese
Version 0.7.3:
Use usb_kill_urb() instead of deprecated (kernel 2.6.9) usb_unlink_urb().
2004-07-13 Karsten Wiese
Version 0.7.1:
Don't sleep in START/STOP callbacks anymore.
us428 channels C/D not handled just for this version, sorry.
2004-06-21 Karsten Wiese
Version 0.6.4:
Temporarely suspend midi input
to sanely call usb_set_interface() when setting format.
2004-06-12 Karsten Wiese
Version 0.6.3:
Made it thus the following rule is enforced:
"All pcm substreams of one usx2y have to operate at the same rate & format."
2004-04-06 Karsten Wiese
Version 0.6.0:
Runs on 2.6.5 kernel without any "--with-debug=" things.
us224 reported running.
2004-01-14 Karsten Wiese
Version 0.5.1:
Runs with 2.6.1 kernel.
2003-12-30 Karsten Wiese
Version 0.4.1:
Fix 24Bit 4Channel capturing for the us428.
2003-11-27 Karsten Wiese, Martin Langer
Version 0.4:
us122 support.
us224 could be tested by uncommenting the sections containing USB_ID_US224
2003-11-03 Karsten Wiese
Version 0.3:
24Bit support.
"arecord -D hw:1 -c 2 -r 48000 -M -f S24_3LE|aplay -D hw:1 -c 2 -r 48000 -M -f S24_3LE" works.
2003-08-22 Karsten Wiese
Version 0.0.8:
Removed EZUSB Firmware. First Stage Firmwaredownload is now done by tascam-firmware downloader.
See:
http://usb-midi-fw.sourceforge.net/tascam-firmware.tar.gz
2003-06-18 Karsten Wiese
Version 0.0.5:
changed to compile with kernel 2.4.21 and alsa 0.9.4
2002-10-16 Karsten Wiese
Version 0.0.4:
compiles again with alsa-current.
USB_ISO_ASAP not used anymore (most of the time), instead
urb->start_frame is calculated here now, some calls inside usb-driver don't need to happen anymore.
To get the best out of this:
Disable APM-support in the kernel as APM-BIOS calls (once each second) hard disable interrupt for many precious milliseconds.
This helped me much on my slowish PII 400 & PIII 500.
ACPI yet untested but might cause the same bad behaviour.
Use a kernel with lowlatency and preemptiv patches applied.
To autoload snd-usb-midi append a line
post-install snd-usb-us428 modprobe snd-usb-midi
to /etc/modules.conf.
known problems:
sliders, knobs, lights not yet handled except MASTER Volume slider.
"pcm -c 2" doesn't work. "pcm -c 2 -m direct_interleaved" does.
KDE3: "Enable full duplex operation" deadlocks.
2002-08-31 Karsten Wiese
Version 0.0.3: audio also simplex;
simplifying: iso urbs only 1 packet, melted structs.
ASYNC_UNLINK not used anymore: no more crashes so far.....
for alsa 0.9 rc3.
2002-08-09 Karsten Wiese
Version 0.0.2: midi works with snd-usb-midi, audio (only fullduplex now) with i.e. bristol.
The firmware has been sniffed from win2k us-428 driver 3.09.
* Copyright (c) 2002 - 2004 Karsten Wiese
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/usb.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/rawmidi.h>
#include "usx2y.h"
#include "usbusx2y.h"
#include "usX2Yhwdep.h"
MODULE_AUTHOR("Karsten Wiese <annabellesgarden@yahoo.de>");
MODULE_DESCRIPTION("TASCAM "NAME_ALLCAPS" Version 0.8.7.2");
MODULE_LICENSE("GPL");
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for "NAME_ALLCAPS".");
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for "NAME_ALLCAPS".");
module_param_array(enable, bool, NULL, 0444);
MODULE_PARM_DESC(enable, "Enable "NAME_ALLCAPS".");
static int snd_usx2y_card_used[SNDRV_CARDS];
static void snd_usx2y_card_private_free(struct snd_card *card);
static void usx2y_unlinkseq(struct snd_usx2y_async_seq *s);
#ifdef USX2Y_NRPACKS_VARIABLE
int nrpacks = USX2Y_NRPACKS; /* number of packets per urb */
module_param(nrpacks, int, 0444);
MODULE_PARM_DESC(nrpacks, "Number of packets per URB.");
#endif
/*
* pipe 4 is used for switching the lamps, setting samplerate, volumes ....
*/
static void i_usx2y_out04_int(struct urb *urb)
{
#ifdef CONFIG_SND_DEBUG
if (urb->status) {
int i;
struct usx2ydev *usx2y = urb->context;
for (i = 0; i < 10 && usx2y->as04.urb[i] != urb; i++)
;
dev_dbg(&urb->dev->dev, "%s urb %i status=%i\n", __func__, i, urb->status);
}
#endif
}
static void i_usx2y_in04_int(struct urb *urb)
{
int err = 0;
struct usx2ydev *usx2y = urb->context;
struct us428ctls_sharedmem *us428ctls = usx2y->us428ctls_sharedmem;
struct us428_p4out *p4out;
int i, j, n, diff, send, len;
usx2y->in04_int_calls++;
if (urb->status) {
dev_dbg(&urb->dev->dev, "Interrupt Pipe 4 came back with status=%i\n", urb->status);
return;
}
if (us428ctls) {
diff = -1;
if (us428ctls->ctl_snapshot_last == -2) {
diff = 0;
memcpy(usx2y->in04_last, usx2y->in04_buf, sizeof(usx2y->in04_last));
us428ctls->ctl_snapshot_last = -1;
} else {
for (i = 0; i < 21; i++) {
if (usx2y->in04_last[i] != ((char *)usx2y->in04_buf)[i]) {
if (diff < 0)
diff = i;
usx2y->in04_last[i] = ((char *)usx2y->in04_buf)[i];
}
}
}
if (diff >= 0) {
n = us428ctls->ctl_snapshot_last + 1;
if (n >= N_US428_CTL_BUFS || n < 0)
n = 0;
memcpy(us428ctls->ctl_snapshot + n, usx2y->in04_buf, sizeof(us428ctls->ctl_snapshot[0]));
us428ctls->ctl_snapshot_differs_at[n] = diff;
us428ctls->ctl_snapshot_last = n;
wake_up(&usx2y->us428ctls_wait_queue_head);
}
}
if (usx2y->us04) {
if (!usx2y->us04->submitted) {
do {
err = usb_submit_urb(usx2y->us04->urb[usx2y->us04->submitted++], GFP_ATOMIC);
} while (!err && usx2y->us04->submitted < usx2y->us04->len);
}
} else {
while (us428ctls &&
us428ctls->p4out_last >= 0 &&
us428ctls->p4out_last < N_US428_P4OUT_BUFS &&
us428ctls->p4out_last != us428ctls->p4out_sent) {
for (j = 0; j < URBS_ASYNC_SEQ && !err; ++j) {
if (!usx2y->as04.urb[j]->status) {
send = us428ctls->p4out_sent + 1;
if (send >= N_US428_P4OUT_BUFS)
send = 0;
p4out = us428ctls->p4out + send;
len = p4out->type == ELT_LIGHT ?
sizeof(struct us428_lights) : 5;
memcpy(usx2y->as04.urb[j]->transfer_buffer,
&p4out->val.vol, len);
usx2y->as04.urb[j]->transfer_buffer_length = len;
err = usb_submit_urb(usx2y->as04.urb[j], GFP_ATOMIC);
if (!err)
us428ctls->p4out_sent = send;
break;
}
}
if (j >= URBS_ASYNC_SEQ || err)
break;
}
}
if (err)
dev_err(&urb->dev->dev, "in04_int() usb_submit_urb err=%i\n", err);
urb->dev = usx2y->dev;
usb_submit_urb(urb, GFP_ATOMIC);
}
/*
* Prepare some urbs
*/
int usx2y_async_seq04_init(struct usx2ydev *usx2y)
{
int err = 0, i;
if (WARN_ON(usx2y->as04.buffer))
return -EBUSY;
usx2y->as04.buffer = kmalloc_array(URBS_ASYNC_SEQ,
URB_DATA_LEN_ASYNC_SEQ, GFP_KERNEL);
if (!usx2y->as04.buffer) {
err = -ENOMEM;
} else {
for (i = 0; i < URBS_ASYNC_SEQ; ++i) {
usx2y->as04.urb[i] = usb_alloc_urb(0, GFP_KERNEL);
if (!usx2y->as04.urb[i]) {
err = -ENOMEM;
break;
}
usb_fill_bulk_urb(usx2y->as04.urb[i], usx2y->dev,
usb_sndbulkpipe(usx2y->dev, 0x04),
usx2y->as04.buffer + URB_DATA_LEN_ASYNC_SEQ * i, 0,
i_usx2y_out04_int, usx2y);
err = usb_urb_ep_type_check(usx2y->as04.urb[i]);
if (err < 0)
break;
}
}
if (err)
usx2y_unlinkseq(&usx2y->as04);
return err;
}
int usx2y_in04_init(struct usx2ydev *usx2y)
{
int err;
if (WARN_ON(usx2y->in04_urb))
return -EBUSY;
usx2y->in04_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!usx2y->in04_urb) {
err = -ENOMEM;
goto error;
}
usx2y->in04_buf = kmalloc(21, GFP_KERNEL);
if (!usx2y->in04_buf) {
err = -ENOMEM;
goto error;
}
init_waitqueue_head(&usx2y->in04_wait_queue);
usb_fill_int_urb(usx2y->in04_urb, usx2y->dev, usb_rcvintpipe(usx2y->dev, 0x4),
usx2y->in04_buf, 21,
i_usx2y_in04_int, usx2y,
10);
if (usb_urb_ep_type_check(usx2y->in04_urb)) {
err = -EINVAL;
goto error;
}
return usb_submit_urb(usx2y->in04_urb, GFP_KERNEL);
error:
kfree(usx2y->in04_buf);
usb_free_urb(usx2y->in04_urb);
usx2y->in04_buf = NULL;
usx2y->in04_urb = NULL;
return err;
}
static void usx2y_unlinkseq(struct snd_usx2y_async_seq *s)
{
int i;
for (i = 0; i < URBS_ASYNC_SEQ; ++i) {
if (!s->urb[i])
continue;
usb_kill_urb(s->urb[i]);
usb_free_urb(s->urb[i]);
s->urb[i] = NULL;
}
kfree(s->buffer);
s->buffer = NULL;
}
static const struct usb_device_id snd_usx2y_usb_id_table[] = {
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = 0x1604,
.idProduct = USB_ID_US428
},
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = 0x1604,
.idProduct = USB_ID_US122
},
{
.match_flags = USB_DEVICE_ID_MATCH_DEVICE,
.idVendor = 0x1604,
.idProduct = USB_ID_US224
},
{ /* terminator */ }
};
MODULE_DEVICE_TABLE(usb, snd_usx2y_usb_id_table);
static int usx2y_create_card(struct usb_device *device,
struct usb_interface *intf,
struct snd_card **cardp)
{
int dev;
struct snd_card *card;
int err;
for (dev = 0; dev < SNDRV_CARDS; ++dev)
if (enable[dev] && !snd_usx2y_card_used[dev])
break;
if (dev >= SNDRV_CARDS)
return -ENODEV;
err = snd_card_new(&intf->dev, index[dev], id[dev], THIS_MODULE,
sizeof(struct usx2ydev), &card);
if (err < 0)
return err;
snd_usx2y_card_used[usx2y(card)->card_index = dev] = 1;
card->private_free = snd_usx2y_card_private_free;
usx2y(card)->dev = device;
init_waitqueue_head(&usx2y(card)->prepare_wait_queue);
init_waitqueue_head(&usx2y(card)->us428ctls_wait_queue_head);
mutex_init(&usx2y(card)->pcm_mutex);
INIT_LIST_HEAD(&usx2y(card)->midi_list);
strscpy(card->driver, "USB "NAME_ALLCAPS"");
sprintf(card->shortname, "TASCAM "NAME_ALLCAPS"");
sprintf(card->longname, "%s (%x:%x if %d at %03d/%03d)",
card->shortname,
le16_to_cpu(device->descriptor.idVendor),
le16_to_cpu(device->descriptor.idProduct),
0,//us428(card)->usbmidi.ifnum,
usx2y(card)->dev->bus->busnum, usx2y(card)->dev->devnum);
*cardp = card;
return 0;
}
static void snd_usx2y_card_private_free(struct snd_card *card)
{
struct usx2ydev *usx2y = usx2y(card);
kfree(usx2y->in04_buf);
usb_free_urb(usx2y->in04_urb);
if (usx2y->us428ctls_sharedmem)
free_pages_exact(usx2y->us428ctls_sharedmem,
US428_SHAREDMEM_PAGES);
if (usx2y->card_index >= 0 && usx2y->card_index < SNDRV_CARDS)
snd_usx2y_card_used[usx2y->card_index] = 0;
}
static void snd_usx2y_disconnect(struct usb_interface *intf)
{
struct snd_card *card;
struct usx2ydev *usx2y;
struct list_head *p;
card = usb_get_intfdata(intf);
if (!card)
return;
usx2y = usx2y(card);
usx2y->chip_status = USX2Y_STAT_CHIP_HUP;
usx2y_unlinkseq(&usx2y->as04);
usb_kill_urb(usx2y->in04_urb);
snd_card_disconnect(card);
/* release the midi resources */
list_for_each(p, &usx2y->midi_list) {
snd_usbmidi_disconnect(p);
}
if (usx2y->us428ctls_sharedmem)
wake_up(&usx2y->us428ctls_wait_queue_head);
snd_card_free_when_closed(card);
}
static int snd_usx2y_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct usb_device *device = interface_to_usbdev(intf);
struct snd_card *card;
int err;
#ifdef USX2Y_NRPACKS_VARIABLE
if (nrpacks < 0 || nrpacks > USX2Y_NRPACKS_MAX)
return -EINVAL;
#endif
if (le16_to_cpu(device->descriptor.idVendor) != 0x1604 ||
(le16_to_cpu(device->descriptor.idProduct) != USB_ID_US122 &&
le16_to_cpu(device->descriptor.idProduct) != USB_ID_US224 &&
le16_to_cpu(device->descriptor.idProduct) != USB_ID_US428))
return -EINVAL;
err = usx2y_create_card(device, intf, &card);
if (err < 0)
return err;
err = usx2y_hwdep_new(card, device);
if (err < 0)
goto error;
err = snd_card_register(card);
if (err < 0)
goto error;
dev_set_drvdata(&intf->dev, card);
return 0;
error:
snd_card_free(card);
return err;
}
static struct usb_driver snd_usx2y_usb_driver = {
.name = "snd-usb-usx2y",
.probe = snd_usx2y_probe,
.disconnect = snd_usx2y_disconnect,
.id_table = snd_usx2y_usb_id_table,
};
module_usb_driver(snd_usx2y_usb_driver);
+114
View File
@@ -0,0 +1,114 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef USBUSX2Y_H
#define USBUSX2Y_H
#include "../usbaudio.h"
#include "../midi.h"
#include "usbus428ctldefs.h"
#define NRURBS 2
/* Default value used for nr of packs per urb.
* 1 to 4 have been tested ok on uhci.
* To use 3 on ohci, you'd need a patch:
* look for "0000425-linux-2.6.9-rc4-mm1_ohci-hcd.patch.gz" on
* "https://bugtrack.alsa-project.org/alsa-bug/bug_view_page.php?bug_id=0000425"
*
* 1, 2 and 4 work out of the box on ohci, if I recall correctly.
* Bigger is safer operation, smaller gives lower latencies.
*/
#define USX2Y_NRPACKS 4
#define USX2Y_NRPACKS_MAX 1024
/* If your system works ok with this module's parameter
* nrpacks set to 1, you might as well comment
* this define out, and thereby produce smaller, faster code.
* You'd also set USX2Y_NRPACKS to 1 then.
*/
#define USX2Y_NRPACKS_VARIABLE 1
#ifdef USX2Y_NRPACKS_VARIABLE
extern int nrpacks;
#define nr_of_packs() nrpacks
#else
#define nr_of_packs() USX2Y_NRPACKS
#endif
#define URBS_ASYNC_SEQ 10
#define URB_DATA_LEN_ASYNC_SEQ 32
struct snd_usx2y_async_seq {
struct urb *urb[URBS_ASYNC_SEQ];
char *buffer;
};
struct snd_usx2y_urb_seq {
int submitted;
int len;
struct urb *urb[] __counted_by(len);
};
#include "usx2yhwdeppcm.h"
struct usx2ydev {
struct usb_device *dev;
int card_index;
int stride;
struct urb *in04_urb;
void *in04_buf;
char in04_last[24];
unsigned int in04_int_calls;
struct snd_usx2y_urb_seq *us04;
wait_queue_head_t in04_wait_queue;
struct snd_usx2y_async_seq as04;
unsigned int rate,
format;
int chip_status;
struct mutex pcm_mutex;
struct us428ctls_sharedmem *us428ctls_sharedmem;
int wait_iso_frame;
wait_queue_head_t us428ctls_wait_queue_head;
struct snd_usx2y_hwdep_pcm_shm *hwdep_pcm_shm;
struct snd_usx2y_substream *subs[4];
struct snd_usx2y_substream * volatile prepare_subs;
wait_queue_head_t prepare_wait_queue;
struct list_head midi_list;
int pcm_devs;
};
struct snd_usx2y_substream {
struct usx2ydev *usx2y;
struct snd_pcm_substream *pcm_substream;
int endpoint;
unsigned int maxpacksize; /* max packet size in bytes */
atomic_t state;
#define STATE_STOPPED 0
#define STATE_STARTING1 1
#define STATE_STARTING2 2
#define STATE_STARTING3 3
#define STATE_PREPARED 4
#define STATE_PRERUNNING 6
#define STATE_RUNNING 8
int hwptr; /* free frame position in the buffer (only for playback) */
int hwptr_done; /* processed frame position in the buffer */
int transfer_done; /* processed frames since last period update */
struct urb *urb[NRURBS]; /* data urb table */
struct urb *completed_urb;
char *tmpbuf; /* temporary buffer for playback */
};
#define usx2y(c) ((struct usx2ydev *)(c)->private_data)
int usx2y_audio_create(struct snd_card *card);
int usx2y_async_seq04_init(struct usx2ydev *usx2y);
int usx2y_in04_init(struct usx2ydev *usx2y);
#define NAME_ALLCAPS "US-X2Y"
#endif
File diff suppressed because it is too large Load Diff
+38
View File
@@ -0,0 +1,38 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Driver for Tascam US-X2Y USB soundcards
*
* Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de>
*/
#ifndef __SOUND_USX2Y_COMMON_H
#define __SOUND_USX2Y_COMMON_H
#define USX2Y_DRIVER_VERSION 0x0100 /* 0.1.0 */
/* hwdep id string */
#define SND_USX2Y_LOADER_ID "USX2Y Loader"
#define SND_USX2Y_USBPCM_ID "USX2Y USBPCM"
/* hardware type */
enum {
USX2Y_TYPE_122,
USX2Y_TYPE_224,
USX2Y_TYPE_428,
USX2Y_TYPE_NUMS
};
#define USB_ID_US122 0x8007
#define USB_ID_US224 0x8005
#define USB_ID_US428 0x8001
/* chip status */
enum {
USX2Y_STAT_CHIP_INIT = (1 << 0), /* all operational */
USX2Y_STAT_CHIP_MMAP_PCM_URBS = (1 << 1), /* pcm transport over mmaped urbs */
USX2Y_STAT_CHIP_HUP = (1 << 31), /* all operational */
};
#endif /* __SOUND_USX2Y_COMMON_H */
+774
View File
@@ -0,0 +1,774 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
*/
/* USX2Y "rawusb" aka hwdep_pcm implementation
Its usb's unableness to atomically handle power of 2 period sized data chuncs
at standard samplerates,
what led to this part of the usx2y module:
It provides the alsa kernel half of the usx2y-alsa-jack driver pair.
The pair uses a hardware dependent alsa-device for mmaped pcm transport.
Advantage achieved:
The usb_hc moves pcm data from/into memory via DMA.
That memory is mmaped by jack's usx2y driver.
Jack's usx2y driver is the first/last to read/write pcm data.
Read/write is a combination of power of 2 period shaping and
float/int conversation.
Compared to mainline alsa/jack we leave out power of 2 period shaping inside
snd-usb-usx2y which needs memcpy() and additional buffers.
As a side effect possible unwanted pcm-data coruption resulting of
standard alsa's snd-usb-usx2y period shaping scheme falls away.
Result is sane jack operation at buffering schemes down to 128frames,
2 periods.
plain usx2y alsa mode is able to achieve 64frames, 4periods, but only at the
cost of easier triggered i.e. aeolus xruns (128 or 256frames,
2periods works but is useless cause of crackling).
This is a first "proof of concept" implementation.
Later, functionalities should migrate to more appropriate places:
Userland:
- The jackd could mmap its float-pcm buffers directly from alsa-lib.
- alsa-lib could provide power of 2 period sized shaping combined with int/float
conversation.
Currently the usx2y jack driver provides above 2 services.
Kernel:
- rawusb dma pcm buffer transport should go to snd-usb-lib, so also snd-usb-audio
devices can use it.
Currently rawusb dma pcm buffer transport (this file) is only available to snd-usb-usx2y.
*/
#include <linux/delay.h>
#include <linux/gfp.h>
#include "usbusx2yaudio.c"
#if defined(USX2Y_NRPACKS_VARIABLE) || USX2Y_NRPACKS == 1
#include <sound/hwdep.h>
static int usx2y_usbpcm_urb_capt_retire(struct snd_usx2y_substream *subs)
{
struct urb *urb = subs->completed_urb;
struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
int i, lens = 0, hwptr_done = subs->hwptr_done;
struct usx2ydev *usx2y = subs->usx2y;
int head;
if (usx2y->hwdep_pcm_shm->capture_iso_start < 0) { //FIXME
head = usx2y->hwdep_pcm_shm->captured_iso_head + 1;
if (head >= ARRAY_SIZE(usx2y->hwdep_pcm_shm->captured_iso))
head = 0;
usx2y->hwdep_pcm_shm->capture_iso_start = head;
dev_dbg(&usx2y->dev->dev, "cap start %i\n", head);
}
for (i = 0; i < nr_of_packs(); i++) {
if (urb->iso_frame_desc[i].status) { /* active? hmm, skip this */
dev_err(&usx2y->dev->dev,
"active frame status %i. Most probably some hardware problem.\n",
urb->iso_frame_desc[i].status);
return urb->iso_frame_desc[i].status;
}
lens += urb->iso_frame_desc[i].actual_length / usx2y->stride;
}
hwptr_done += lens;
if (hwptr_done >= runtime->buffer_size)
hwptr_done -= runtime->buffer_size;
subs->hwptr_done = hwptr_done;
subs->transfer_done += lens;
/* update the pointer, call callback if necessary */
if (subs->transfer_done >= runtime->period_size) {
subs->transfer_done -= runtime->period_size;
snd_pcm_period_elapsed(subs->pcm_substream);
}
return 0;
}
static int usx2y_iso_frames_per_buffer(struct snd_pcm_runtime *runtime,
struct usx2ydev *usx2y)
{
return (runtime->buffer_size * 1000) / usx2y->rate + 1; //FIXME: so far only correct period_size == 2^x ?
}
/*
* prepare urb for playback data pipe
*
* we copy the data directly from the pcm buffer.
* the current position to be copied is held in hwptr field.
* since a urb can handle only a single linear buffer, if the total
* transferred area overflows the buffer boundary, we cannot send
* it directly from the buffer. thus the data is once copied to
* a temporary buffer and urb points to that.
*/
static int usx2y_hwdep_urb_play_prepare(struct snd_usx2y_substream *subs,
struct urb *urb)
{
int count, counts, pack;
struct usx2ydev *usx2y = subs->usx2y;
struct snd_usx2y_hwdep_pcm_shm *shm = usx2y->hwdep_pcm_shm;
struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime;
if (shm->playback_iso_start < 0) {
shm->playback_iso_start = shm->captured_iso_head -
usx2y_iso_frames_per_buffer(runtime, usx2y);
if (shm->playback_iso_start < 0)
shm->playback_iso_start += ARRAY_SIZE(shm->captured_iso);
shm->playback_iso_head = shm->playback_iso_start;
}
count = 0;
for (pack = 0; pack < nr_of_packs(); pack++) {
/* calculate the size of a packet */
counts = shm->captured_iso[shm->playback_iso_head].length / usx2y->stride;
if (counts < 43 || counts > 50) {
dev_err(&usx2y->dev->dev, "should not be here with counts=%i\n", counts);
return -EPIPE;
}
/* set up descriptor */
urb->iso_frame_desc[pack].offset = shm->captured_iso[shm->playback_iso_head].offset;
urb->iso_frame_desc[pack].length = shm->captured_iso[shm->playback_iso_head].length;
if (atomic_read(&subs->state) != STATE_RUNNING)
memset((char *)urb->transfer_buffer + urb->iso_frame_desc[pack].offset, 0,
urb->iso_frame_desc[pack].length);
if (++shm->playback_iso_head >= ARRAY_SIZE(shm->captured_iso))
shm->playback_iso_head = 0;
count += counts;
}
urb->transfer_buffer_length = count * usx2y->stride;
return 0;
}
static void usx2y_usbpcm_urb_capt_iso_advance(struct snd_usx2y_substream *subs,
struct urb *urb)
{
struct usb_iso_packet_descriptor *desc;
struct snd_usx2y_hwdep_pcm_shm *shm;
int pack, head;
for (pack = 0; pack < nr_of_packs(); ++pack) {
desc = urb->iso_frame_desc + pack;
if (subs) {
shm = subs->usx2y->hwdep_pcm_shm;
head = shm->captured_iso_head + 1;
if (head >= ARRAY_SIZE(shm->captured_iso))
head = 0;
shm->captured_iso[head].frame = urb->start_frame + pack;
shm->captured_iso[head].offset = desc->offset;
shm->captured_iso[head].length = desc->actual_length;
shm->captured_iso_head = head;
shm->captured_iso_frames++;
}
desc->offset += desc->length * NRURBS * nr_of_packs();
if (desc->offset + desc->length >= SSS)
desc->offset -= (SSS - desc->length);
}
}
static int usx2y_usbpcm_usbframe_complete(struct snd_usx2y_substream *capsubs,
struct snd_usx2y_substream *capsubs2,
struct snd_usx2y_substream *playbacksubs,
int frame)
{
int err, state;
struct urb *urb = playbacksubs->completed_urb;
state = atomic_read(&playbacksubs->state);
if (urb) {
if (state == STATE_RUNNING)
usx2y_urb_play_retire(playbacksubs, urb);
else if (state >= STATE_PRERUNNING)
atomic_inc(&playbacksubs->state);
} else {
switch (state) {
case STATE_STARTING1:
urb = playbacksubs->urb[0];
atomic_inc(&playbacksubs->state);
break;
case STATE_STARTING2:
urb = playbacksubs->urb[1];
atomic_inc(&playbacksubs->state);
break;
}
}
if (urb) {
err = usx2y_hwdep_urb_play_prepare(playbacksubs, urb);
if (err)
return err;
err = usx2y_hwdep_urb_play_prepare(playbacksubs, urb);
if (err)
return err;
}
playbacksubs->completed_urb = NULL;
state = atomic_read(&capsubs->state);
if (state >= STATE_PREPARED) {
if (state == STATE_RUNNING) {
err = usx2y_usbpcm_urb_capt_retire(capsubs);
if (err)
return err;
} else if (state >= STATE_PRERUNNING) {
atomic_inc(&capsubs->state);
}
usx2y_usbpcm_urb_capt_iso_advance(capsubs, capsubs->completed_urb);
if (capsubs2)
usx2y_usbpcm_urb_capt_iso_advance(NULL, capsubs2->completed_urb);
err = usx2y_urb_submit(capsubs, capsubs->completed_urb, frame);
if (err)
return err;
if (capsubs2) {
err = usx2y_urb_submit(capsubs2, capsubs2->completed_urb, frame);
if (err)
return err;
}
}
capsubs->completed_urb = NULL;
if (capsubs2)
capsubs2->completed_urb = NULL;
return 0;
}
static void i_usx2y_usbpcm_urb_complete(struct urb *urb)
{
struct snd_usx2y_substream *subs = urb->context;
struct usx2ydev *usx2y = subs->usx2y;
struct snd_usx2y_substream *capsubs, *capsubs2, *playbacksubs;
if (unlikely(atomic_read(&subs->state) < STATE_PREPARED)) {
dev_dbg(&usx2y->dev->dev,
"hcd_frame=%i ep=%i%s status=%i start_frame=%i\n",
usb_get_current_frame_number(usx2y->dev),
subs->endpoint, usb_pipein(urb->pipe) ? "in" : "out",
urb->status, urb->start_frame);
return;
}
if (unlikely(urb->status)) {
usx2y_error_urb_status(usx2y, subs, urb);
return;
}
subs->completed_urb = urb;
capsubs = usx2y->subs[SNDRV_PCM_STREAM_CAPTURE];
capsubs2 = usx2y->subs[SNDRV_PCM_STREAM_CAPTURE + 2];
playbacksubs = usx2y->subs[SNDRV_PCM_STREAM_PLAYBACK];
if (capsubs->completed_urb && atomic_read(&capsubs->state) >= STATE_PREPARED &&
(!capsubs2 || capsubs2->completed_urb) &&
(playbacksubs->completed_urb || atomic_read(&playbacksubs->state) < STATE_PREPARED)) {
if (!usx2y_usbpcm_usbframe_complete(capsubs, capsubs2, playbacksubs, urb->start_frame)) {
usx2y->wait_iso_frame += nr_of_packs();
} else {
usx2y_clients_stop(usx2y);
}
}
}
static void usx2y_hwdep_urb_release(struct urb **urb)
{
usb_kill_urb(*urb);
usb_free_urb(*urb);
*urb = NULL;
}
/*
* release a substream
*/
static void usx2y_usbpcm_urbs_release(struct snd_usx2y_substream *subs)
{
int i;
dev_dbg(&subs->usx2y->dev->dev,
"snd_usx2y_urbs_release() %i\n", subs->endpoint);
for (i = 0; i < NRURBS; i++)
usx2y_hwdep_urb_release(subs->urb + i);
}
static void usx2y_usbpcm_subs_startup_finish(struct usx2ydev *usx2y)
{
usx2y_urbs_set_complete(usx2y, i_usx2y_usbpcm_urb_complete);
usx2y->prepare_subs = NULL;
}
static void i_usx2y_usbpcm_subs_startup(struct urb *urb)
{
struct snd_usx2y_substream *subs = urb->context;
struct usx2ydev *usx2y = subs->usx2y;
struct snd_usx2y_substream *prepare_subs = usx2y->prepare_subs;
struct snd_usx2y_substream *cap_subs2;
if (prepare_subs &&
urb->start_frame == prepare_subs->urb[0]->start_frame) {
atomic_inc(&prepare_subs->state);
if (prepare_subs == usx2y->subs[SNDRV_PCM_STREAM_CAPTURE]) {
cap_subs2 = usx2y->subs[SNDRV_PCM_STREAM_CAPTURE + 2];
if (cap_subs2)
atomic_inc(&cap_subs2->state);
}
usx2y_usbpcm_subs_startup_finish(usx2y);
wake_up(&usx2y->prepare_wait_queue);
}
i_usx2y_usbpcm_urb_complete(urb);
}
/*
* initialize a substream's urbs
*/
static int usx2y_usbpcm_urbs_allocate(struct snd_usx2y_substream *subs)
{
int i;
unsigned int pipe;
int is_playback = subs == subs->usx2y->subs[SNDRV_PCM_STREAM_PLAYBACK];
struct usb_device *dev = subs->usx2y->dev;
struct urb **purb;
pipe = is_playback ? usb_sndisocpipe(dev, subs->endpoint) :
usb_rcvisocpipe(dev, subs->endpoint);
subs->maxpacksize = usb_maxpacket(dev, pipe);
if (!subs->maxpacksize)
return -EINVAL;
/* allocate and initialize data urbs */
for (i = 0; i < NRURBS; i++) {
purb = subs->urb + i;
if (*purb) {
usb_kill_urb(*purb);
continue;
}
*purb = usb_alloc_urb(nr_of_packs(), GFP_KERNEL);
if (!*purb) {
usx2y_usbpcm_urbs_release(subs);
return -ENOMEM;
}
(*purb)->transfer_buffer = is_playback ?
subs->usx2y->hwdep_pcm_shm->playback : (
subs->endpoint == 0x8 ?
subs->usx2y->hwdep_pcm_shm->capture0x8 :
subs->usx2y->hwdep_pcm_shm->capture0xA);
(*purb)->dev = dev;
(*purb)->pipe = pipe;
(*purb)->number_of_packets = nr_of_packs();
(*purb)->context = subs;
(*purb)->interval = 1;
(*purb)->complete = i_usx2y_usbpcm_subs_startup;
}
return 0;
}
/*
* free the buffer
*/
static int snd_usx2y_usbpcm_hw_free(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_usx2y_substream *subs = runtime->private_data;
struct snd_usx2y_substream *cap_subs;
struct snd_usx2y_substream *playback_subs;
struct snd_usx2y_substream *cap_subs2;
guard(mutex)(&subs->usx2y->pcm_mutex);
dev_dbg(&subs->usx2y->dev->dev, "%s(%p)\n", __func__, substream);
cap_subs2 = subs->usx2y->subs[SNDRV_PCM_STREAM_CAPTURE + 2];
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
cap_subs = subs->usx2y->subs[SNDRV_PCM_STREAM_CAPTURE];
atomic_set(&subs->state, STATE_STOPPED);
usx2y_usbpcm_urbs_release(subs);
if (!cap_subs->pcm_substream ||
!cap_subs->pcm_substream->runtime ||
cap_subs->pcm_substream->runtime->state < SNDRV_PCM_STATE_PREPARED) {
atomic_set(&cap_subs->state, STATE_STOPPED);
if (cap_subs2)
atomic_set(&cap_subs2->state, STATE_STOPPED);
usx2y_usbpcm_urbs_release(cap_subs);
if (cap_subs2)
usx2y_usbpcm_urbs_release(cap_subs2);
}
} else {
playback_subs = subs->usx2y->subs[SNDRV_PCM_STREAM_PLAYBACK];
if (atomic_read(&playback_subs->state) < STATE_PREPARED) {
atomic_set(&subs->state, STATE_STOPPED);
if (cap_subs2)
atomic_set(&cap_subs2->state, STATE_STOPPED);
usx2y_usbpcm_urbs_release(subs);
if (cap_subs2)
usx2y_usbpcm_urbs_release(cap_subs2);
}
}
return 0;
}
static void usx2y_usbpcm_subs_startup(struct snd_usx2y_substream *subs)
{
struct usx2ydev *usx2y = subs->usx2y;
usx2y->prepare_subs = subs;
subs->urb[0]->start_frame = -1;
smp_wmb(); // Make sure above modifications are seen by i_usx2y_subs_startup()
usx2y_urbs_set_complete(usx2y, i_usx2y_usbpcm_subs_startup);
}
static int usx2y_usbpcm_urbs_start(struct snd_usx2y_substream *subs)
{
int p, u, err, stream = subs->pcm_substream->stream;
struct usx2ydev *usx2y = subs->usx2y;
struct urb *urb;
unsigned long pack;
if (stream == SNDRV_PCM_STREAM_CAPTURE) {
usx2y->hwdep_pcm_shm->captured_iso_head = -1;
usx2y->hwdep_pcm_shm->captured_iso_frames = 0;
}
for (p = 0; 3 >= (stream + p); p += 2) {
struct snd_usx2y_substream *subs = usx2y->subs[stream + p];
if (subs) {
err = usx2y_usbpcm_urbs_allocate(subs);
if (err < 0)
return err;
subs->completed_urb = NULL;
}
}
for (p = 0; p < 4; p++) {
struct snd_usx2y_substream *subs = usx2y->subs[p];
if (subs && atomic_read(&subs->state) >= STATE_PREPARED)
goto start;
}
start:
usx2y_usbpcm_subs_startup(subs);
for (u = 0; u < NRURBS; u++) {
for (p = 0; 3 >= (stream + p); p += 2) {
struct snd_usx2y_substream *subs = usx2y->subs[stream + p];
if (!subs)
continue;
urb = subs->urb[u];
if (usb_pipein(urb->pipe)) {
if (!u)
atomic_set(&subs->state, STATE_STARTING3);
urb->dev = usx2y->dev;
for (pack = 0; pack < nr_of_packs(); pack++) {
urb->iso_frame_desc[pack].offset = subs->maxpacksize * (pack + u * nr_of_packs());
urb->iso_frame_desc[pack].length = subs->maxpacksize;
}
urb->transfer_buffer_length = subs->maxpacksize * nr_of_packs();
err = usb_submit_urb(urb, GFP_KERNEL);
if (err < 0) {
dev_err(&urb->dev->dev,
"cannot usb_submit_urb() for urb %d, err = %d\n",
u, err);
err = -EPIPE;
goto cleanup;
} else {
if (!u)
usx2y->wait_iso_frame = urb->start_frame;
}
urb->transfer_flags = 0;
} else {
atomic_set(&subs->state, STATE_STARTING1);
break;
}
}
}
err = 0;
wait_event(usx2y->prepare_wait_queue, !usx2y->prepare_subs);
if (atomic_read(&subs->state) != STATE_PREPARED)
err = -EPIPE;
cleanup:
if (err) {
usx2y_subs_startup_finish(usx2y); // Call it now
usx2y_clients_stop(usx2y); // something is completely wrong > stop everything
}
return err;
}
#define USX2Y_HWDEP_PCM_PAGES \
PAGE_ALIGN(sizeof(struct snd_usx2y_hwdep_pcm_shm))
/*
* prepare callback
*
* set format and initialize urbs
*/
static int snd_usx2y_usbpcm_prepare(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_usx2y_substream *subs = runtime->private_data;
struct usx2ydev *usx2y = subs->usx2y;
struct snd_usx2y_substream *capsubs = subs->usx2y->subs[SNDRV_PCM_STREAM_CAPTURE];
int err = 0;
dev_dbg(&usx2y->dev->dev, "snd_usx2y_pcm_prepare(%p)\n", substream);
guard(mutex)(&usx2y->pcm_mutex);
if (!usx2y->hwdep_pcm_shm) {
usx2y->hwdep_pcm_shm = alloc_pages_exact(USX2Y_HWDEP_PCM_PAGES,
GFP_KERNEL);
if (!usx2y->hwdep_pcm_shm)
return -ENOMEM;
memset(usx2y->hwdep_pcm_shm, 0, USX2Y_HWDEP_PCM_PAGES);
}
usx2y_subs_prepare(subs);
// Start hardware streams
// SyncStream first....
if (atomic_read(&capsubs->state) < STATE_PREPARED) {
if (usx2y->format != runtime->format) {
err = usx2y_format_set(usx2y, runtime->format);
if (err < 0)
return err;
}
if (usx2y->rate != runtime->rate) {
err = usx2y_rate_set(usx2y, runtime->rate);
if (err < 0)
return err;
}
dev_dbg(&usx2y->dev->dev,
"starting capture pipe for %s\n", subs == capsubs ?
"self" : "playpipe");
err = usx2y_usbpcm_urbs_start(capsubs);
if (err < 0)
return err;
}
if (subs != capsubs) {
usx2y->hwdep_pcm_shm->playback_iso_start = -1;
if (atomic_read(&subs->state) < STATE_PREPARED) {
while (usx2y_iso_frames_per_buffer(runtime, usx2y) >
usx2y->hwdep_pcm_shm->captured_iso_frames) {
dev_dbg(&usx2y->dev->dev,
"Wait: iso_frames_per_buffer=%i,captured_iso_frames=%i\n",
usx2y_iso_frames_per_buffer(runtime, usx2y),
usx2y->hwdep_pcm_shm->captured_iso_frames);
if (msleep_interruptible(10))
return -ERESTARTSYS;
}
err = usx2y_usbpcm_urbs_start(subs);
if (err < 0)
return err;
}
dev_dbg(&usx2y->dev->dev,
"Ready: iso_frames_per_buffer=%i,captured_iso_frames=%i\n",
usx2y_iso_frames_per_buffer(runtime, usx2y),
usx2y->hwdep_pcm_shm->captured_iso_frames);
} else {
usx2y->hwdep_pcm_shm->capture_iso_start = -1;
}
return err;
}
static const struct snd_pcm_hardware snd_usx2y_4c = {
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID),
.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE,
.rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000,
.rate_min = 44100,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 4,
.buffer_bytes_max = (2*128*1024),
.period_bytes_min = 64,
.period_bytes_max = (128*1024),
.periods_min = 2,
.periods_max = 1024,
.fifo_size = 0
};
static int snd_usx2y_usbpcm_open(struct snd_pcm_substream *substream)
{
struct snd_usx2y_substream *subs =
((struct snd_usx2y_substream **)
snd_pcm_substream_chip(substream))[substream->stream];
struct snd_pcm_runtime *runtime = substream->runtime;
if (!(subs->usx2y->chip_status & USX2Y_STAT_CHIP_MMAP_PCM_URBS))
return -EBUSY;
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
runtime->hw = snd_usx2y_2c;
else
runtime->hw = (subs->usx2y->subs[3] ? snd_usx2y_4c : snd_usx2y_2c);
runtime->private_data = subs;
subs->pcm_substream = substream;
snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 1000, 200000);
return 0;
}
static int snd_usx2y_usbpcm_close(struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_usx2y_substream *subs = runtime->private_data;
subs->pcm_substream = NULL;
return 0;
}
static const struct snd_pcm_ops snd_usx2y_usbpcm_ops = {
.open = snd_usx2y_usbpcm_open,
.close = snd_usx2y_usbpcm_close,
.hw_params = snd_usx2y_pcm_hw_params,
.hw_free = snd_usx2y_usbpcm_hw_free,
.prepare = snd_usx2y_usbpcm_prepare,
.trigger = snd_usx2y_pcm_trigger,
.pointer = snd_usx2y_pcm_pointer,
};
static int usx2y_pcms_busy_check(struct snd_card *card)
{
struct usx2ydev *dev = usx2y(card);
struct snd_usx2y_substream *subs;
int i;
for (i = 0; i < dev->pcm_devs * 2; i++) {
subs = dev->subs[i];
if (subs && subs->pcm_substream &&
SUBSTREAM_BUSY(subs->pcm_substream))
return -EBUSY;
}
return 0;
}
static int snd_usx2y_hwdep_pcm_open(struct snd_hwdep *hw, struct file *file)
{
struct snd_card *card = hw->card;
int err;
guard(mutex)(&usx2y(card)->pcm_mutex);
err = usx2y_pcms_busy_check(card);
if (!err)
usx2y(card)->chip_status |= USX2Y_STAT_CHIP_MMAP_PCM_URBS;
return err;
}
static int snd_usx2y_hwdep_pcm_release(struct snd_hwdep *hw, struct file *file)
{
struct snd_card *card = hw->card;
int err;
guard(mutex)(&usx2y(card)->pcm_mutex);
err = usx2y_pcms_busy_check(card);
if (!err)
usx2y(hw->card)->chip_status &= ~USX2Y_STAT_CHIP_MMAP_PCM_URBS;
return err;
}
static void snd_usx2y_hwdep_pcm_vm_open(struct vm_area_struct *area)
{
}
static void snd_usx2y_hwdep_pcm_vm_close(struct vm_area_struct *area)
{
}
static vm_fault_t snd_usx2y_hwdep_pcm_vm_fault(struct vm_fault *vmf)
{
unsigned long offset;
void *vaddr;
offset = vmf->pgoff << PAGE_SHIFT;
if (offset >= USX2Y_HWDEP_PCM_PAGES)
return VM_FAULT_SIGBUS;
vaddr = (char *)((struct usx2ydev *)vmf->vma->vm_private_data)->hwdep_pcm_shm + offset;
vmf->page = virt_to_page(vaddr);
get_page(vmf->page);
return 0;
}
static const struct vm_operations_struct snd_usx2y_hwdep_pcm_vm_ops = {
.open = snd_usx2y_hwdep_pcm_vm_open,
.close = snd_usx2y_hwdep_pcm_vm_close,
.fault = snd_usx2y_hwdep_pcm_vm_fault,
};
static int snd_usx2y_hwdep_pcm_mmap(struct snd_hwdep *hw, struct file *filp, struct vm_area_struct *area)
{
unsigned long size = (unsigned long)(area->vm_end - area->vm_start);
struct usx2ydev *usx2y = hw->private_data;
if (!(usx2y->chip_status & USX2Y_STAT_CHIP_INIT))
return -EBUSY;
/* if userspace tries to mmap beyond end of our buffer, fail */
if (size > USX2Y_HWDEP_PCM_PAGES) {
dev_dbg(hw->card->dev, "%s: %lu > %lu\n", __func__,
size, (unsigned long)USX2Y_HWDEP_PCM_PAGES);
return -EINVAL;
}
if (!usx2y->hwdep_pcm_shm)
return -ENODEV;
area->vm_ops = &snd_usx2y_hwdep_pcm_vm_ops;
vm_flags_set(area, VM_DONTEXPAND | VM_DONTDUMP);
area->vm_private_data = hw->private_data;
return 0;
}
static void snd_usx2y_hwdep_pcm_private_free(struct snd_hwdep *hwdep)
{
struct usx2ydev *usx2y = hwdep->private_data;
if (usx2y->hwdep_pcm_shm)
free_pages_exact(usx2y->hwdep_pcm_shm, USX2Y_HWDEP_PCM_PAGES);
}
int usx2y_hwdep_pcm_new(struct snd_card *card)
{
int err;
struct snd_hwdep *hw;
struct snd_pcm *pcm;
struct usb_device *dev = usx2y(card)->dev;
if (nr_of_packs() != 1)
return 0;
err = snd_hwdep_new(card, SND_USX2Y_USBPCM_ID, 1, &hw);
if (err < 0)
return err;
hw->iface = SNDRV_HWDEP_IFACE_USX2Y_PCM;
hw->private_data = usx2y(card);
hw->private_free = snd_usx2y_hwdep_pcm_private_free;
hw->ops.open = snd_usx2y_hwdep_pcm_open;
hw->ops.release = snd_usx2y_hwdep_pcm_release;
hw->ops.mmap = snd_usx2y_hwdep_pcm_mmap;
hw->exclusive = 1;
sprintf(hw->name, "/dev/bus/usb/%03d/%03d/hwdeppcm", dev->bus->busnum, dev->devnum);
err = snd_pcm_new(card, NAME_ALLCAPS" hwdep Audio", 2, 1, 1, &pcm);
if (err < 0)
return err;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_usx2y_usbpcm_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usx2y_usbpcm_ops);
pcm->private_data = usx2y(card)->subs;
pcm->info_flags = 0;
sprintf(pcm->name, NAME_ALLCAPS" hwdep Audio");
snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream,
SNDRV_DMA_TYPE_CONTINUOUS,
NULL,
64*1024, 128*1024);
snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
SNDRV_DMA_TYPE_CONTINUOUS,
NULL,
64*1024, 128*1024);
return 0;
}
#else
int usx2y_hwdep_pcm_new(struct snd_card *card)
{
return 0;
}
#endif
@@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-2.0 */
#define MAXPACK 50
#define MAXBUFFERMS 100
#define MAXSTRIDE 3
#define SSS (((MAXPACK*MAXBUFFERMS*MAXSTRIDE + 4096) / 4096) * 4096)
struct snd_usx2y_hwdep_pcm_shm {
char playback[SSS];
char capture0x8[SSS];
char capture0xA[SSS];
volatile int playback_iso_head;
int playback_iso_start;
struct {
int frame,
offset,
length;
} captured_iso[128];
volatile int captured_iso_head;
volatile unsigned captured_iso_frames;
int capture_iso_start;
};
int usx2y_hwdep_pcm_new(struct snd_card *card);