243 lines
7.5 KiB
C
243 lines
7.5 KiB
C
// 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");
|
|
|
|
/**
|
|
* @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 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);
|
|
|
|
/**
|
|
* 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
|
|
* the reference to the USB device.
|
|
*/
|
|
static void tascam_card_private_free(struct snd_card *card) {
|
|
struct tascam_card *tascam = card->private_data;
|
|
|
|
if (tascam && tascam->dev) {
|
|
usb_put_dev(tascam->dev);
|
|
tascam->dev = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 and PCM device.
|
|
*
|
|
* 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 __free(kfree);
|
|
|
|
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) {
|
|
tascam = usb_get_intfdata(usb_ifnum_to_if(dev, 0));
|
|
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;
|
|
}
|
|
|
|
handshake_buf = 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) {
|
|
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;
|
|
|
|
spin_lock_init(&tascam->lock);
|
|
|
|
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;
|
|
|
|
strscpy(card->driver, DRIVER_NAME, sizeof(card->driver));
|
|
if (dev->descriptor.idProduct == USB_PID_TASCAM_US144) {
|
|
strscpy(card->shortname, "TASCAM US-144", sizeof(card->shortname));
|
|
} else if (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_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 by freeing the sound card.
|
|
*/
|
|
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);
|
|
snd_card_free(tascam->card);
|
|
dev_idx--;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* tascam_suspend() - Handles device suspension.
|
|
* @intf: The USB interface being suspended.
|
|
* @message: Power management message.
|
|
*
|
|
* This function is a stub for handling device suspension.
|
|
*
|
|
* Return: 0 on success.
|
|
*/
|
|
static int tascam_suspend(struct usb_interface *intf, pm_message_t message) {
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* tascam_resume() - Handles device resumption from suspend.
|
|
* @intf: The USB interface being resumed.
|
|
*
|
|
* This function is a stub for handling device resumption.
|
|
*
|
|
* Return: 0 on success.
|
|
*/
|
|
static int tascam_resume(struct usb_interface *intf) { return 0; }
|
|
|
|
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);
|