607 lines
18 KiB
C
607 lines
18 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
// Copyright (c) 2025 serifpersia <ramiserifpersia@gmail.com>
|
|
/*
|
|
* ALSA Driver for TASCAM US-144MKII Audio Interface
|
|
*/
|
|
|
|
#include "us144mkii.h"
|
|
|
|
MODULE_AUTHOR("serifpersia <ramiserifpersia@gmail.com>");
|
|
MODULE_DESCRIPTION("ALSA Driver for TASCAM US-144MKII");
|
|
MODULE_LICENSE("GPL v2");
|
|
|
|
/**
|
|
* @brief 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 struct usb_driver tascam_alsa_driver;
|
|
|
|
/* --- Forward Declarations --- */
|
|
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);
|
|
|
|
/**
|
|
* driver_version_show() - Sysfs attribute to display the driver version.
|
|
* @dev: Pointer to the device structure.
|
|
* @attr: Pointer to the device attribute structure.
|
|
* @buf: Buffer to write the version string into.
|
|
*
|
|
* This function is a sysfs callback that provides the current driver version
|
|
* string to user-space when the 'driver_version' attribute is read.
|
|
*
|
|
* Return: The number of bytes written to the buffer.
|
|
*/
|
|
static ssize_t driver_version_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
return sysfs_emit(buf, "%s\n", DRIVER_VERSION);
|
|
}
|
|
static DEVICE_ATTR_RO(driver_version);
|
|
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
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->playback_routing_buffer);
|
|
tascam->playback_routing_buffer = 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;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
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 * MAX_FEEDBACK_PACKETS;
|
|
|
|
for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
|
|
struct urb *f_urb = usb_alloc_urb(MAX_FEEDBACK_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->playback_routing_buffer = kmalloc(tascam->playback_urb_alloc_size, GFP_KERNEL);
|
|
if (!tascam->playback_routing_buffer)
|
|
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);
|
|
cancel_work_sync(&tascam->capture_work);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
int err;
|
|
|
|
if (!tascam)
|
|
return 0;
|
|
|
|
/*
|
|
* The device requires a specific sequence to enter a stable low-power
|
|
* state. First, ensure all data transmission is stopped before
|
|
* sending the final vendor command.
|
|
*/
|
|
if (tascam->pcm)
|
|
snd_pcm_suspend_all(tascam->pcm);
|
|
|
|
/*
|
|
* Terminate all in-flight URBs to prevent access to the device
|
|
* after it has been put to sleep.
|
|
*/
|
|
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);
|
|
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);
|
|
|
|
/*
|
|
* Send the vendor-specific "Deep Sleep" command. Failure to send this
|
|
* command before host-initiated suspend can leave the device in an
|
|
* unstable state, leading to system freezes on idle (autosuspend).
|
|
*/
|
|
err = usb_control_msg(tascam->dev,
|
|
usb_sndctrlpipe(tascam->dev, 0),
|
|
0x00, /* bRequest */
|
|
0x40, /* bmRequestType: H2D, Vendor, Device */
|
|
0x0044, /* wValue */
|
|
0x0000, /* wIndex */
|
|
NULL, /* data */
|
|
0, /* size */
|
|
1000); /* timeout */
|
|
if (err < 0)
|
|
dev_err(&intf->dev, "failed to send deep sleep command: %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;
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
char *handshake_buf;
|
|
|
|
if (intf->cur_altsetting->desc.bInterfaceNumber == 1) {
|
|
tascam = usb_get_intfdata(usb_ifnum_to_if(dev, 0));
|
|
if (tascam) {
|
|
usb_set_intfdata(intf, tascam);
|
|
tascam->iface1 = intf;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
handshake_buf = kmalloc(1, GFP_KERNEL);
|
|
if (!handshake_buf)
|
|
return -ENOMEM;
|
|
|
|
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);
|
|
kfree(handshake_buf);
|
|
return err;
|
|
}
|
|
|
|
if (handshake_buf[0] != 0x12 && handshake_buf[0] != 0x16 && handshake_buf[0] != 0x30) {
|
|
dev_err(&dev->dev, "Unexpected handshake value: 0x%x\n", handshake_buf[0]);
|
|
kfree(handshake_buf);
|
|
return -ENODEV;
|
|
}
|
|
kfree(handshake_buf);
|
|
|
|
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;
|
|
tascam->dev = usb_get_dev(dev);
|
|
tascam->card = card;
|
|
tascam->iface0 = intf;
|
|
|
|
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);
|
|
|
|
INIT_WORK(&tascam->stop_work, tascam_stop_work_handler);
|
|
|
|
if (kfifo_alloc(&tascam->midi_in_fifo, MIDI_IN_FIFO_SIZE, GFP_KERNEL))
|
|
goto free_card;
|
|
|
|
card->private_free = tascam_card_private_free;
|
|
|
|
strscpy(card->driver, DRIVER_NAME, sizeof(card->driver));
|
|
strscpy(card->shortname, "TASCAM US-144MKII", sizeof(card->shortname));
|
|
snprintf(card->longname, sizeof(card->longname), "%s (%04x:%04x) at %s",
|
|
card->shortname, USB_VID_TASCAM, USB_PID_TASCAM_US144MKII, 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:
|
|
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) {
|
|
device_remove_file(&intf->dev, &dev_attr_driver_version);
|
|
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);
|
|
snd_card_disconnect(tascam->card);
|
|
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_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);
|