diff --git a/tascam_controls/bg.png b/tascam_controls/bg.png deleted file mode 100644 index 5eec530..0000000 Binary files a/tascam_controls/bg.png and /dev/null differ diff --git a/tascam_controls/tascam-controls.py b/tascam_controls/tascam-controls.py index 51aa04c..45c6cf3 100644 --- a/tascam_controls/tascam-controls.py +++ b/tascam_controls/tascam-controls.py @@ -4,9 +4,8 @@ import re import os from PyQt6.QtWidgets import (QApplication, QWidget, QLabel, QComboBox, QPushButton, QGridLayout, QVBoxLayout, QHBoxLayout, QMessageBox) -from PyQt6.QtGui import QPixmap, QFont, QIcon, QPainter -from PyQt6.QtCore import Qt, QBuffer, QIODevice -import base64 +from PyQt6.QtGui import QPixmap, QFont, QIcon +from PyQt6.QtCore import Qt def resource_path(relative_path): try: @@ -17,63 +16,41 @@ def resource_path(relative_path): DARK_STYLESHEET = """ QWidget { - background-color: transparent; - color: #DAE0ED; + background-color: #2b2b2b; + color: #f0f0f0; font-family: Arial; } QLabel { background-color: transparent; } -QLabel#Title { - font-size: 15pt; - font-weight: bold; - color: #FFFFFF; -} -QLabel#SectionHeader { - font-size: 11pt; - font-weight: bold; - color: #92E8FF; - margin-top: 10px; - margin-bottom: 3px; -} -QLabel#ControlLabel { - font-size: 9pt; - color: #CBD2E6; -} QComboBox { - background-color: rgba(10, 10, 20, 0.25); - border: 1px solid #3A4760; + background-color: #3c3f41; + border: 1px solid #555; border-radius: 4px; padding: 4px; - color: #DAE0ED; } QComboBox:hover { - background-color: rgba(15, 15, 25, 0.35); - border: 1px solid #6482B4; + border: 1px solid #777; } QComboBox::drop-down { border: none; } QComboBox QAbstractItemView { - background-color: rgba(15, 15, 25, 0.9); - border: 1px solid #3A4760; - selection-background-color: #6A3AB1; - color: #DAE0ED; + background-color: #3c3f41; + border: 1px solid #555; + selection-background-color: #5a5d5f; } QPushButton { - background-color: rgba(10, 10, 20, 0.25); - border: 1px solid #3A4760; + background-color: #3c3f41; + border: 1px solid #555; border-radius: 4px; padding: 5px; - color: #92E8FF; } QPushButton:hover { - background-color: rgba(15, 15, 25, 0.35); - border: 1px solid #6482B4; + background-color: #4f5254; } QPushButton:pressed { - background-color: rgba(20, 20, 30, 0.45); - border: 1px solid #A020F0; + background-color: #5a5d5f; } """ @@ -104,6 +81,21 @@ class AmixerController: return 0 return 0 + @staticmethod + def get_control_string(card_id, control_name): + if not card_id: return "N/A" + try: + cmd = ['amixer', '-c', card_id, 'cget', f"name='{control_name}'"] + output = subprocess.check_output(cmd, text=True, stderr=subprocess.DEVNULL) + for line in output.splitlines(): + if ': values=' in line: + value_str = line.split('=')[1] + byte_values = [int(b, 16) for b in value_str.split(',')] + return bytes(byte_values).partition(b'\0')[0].decode('utf-8', errors='ignore').strip() + except (FileNotFoundError, subprocess.CalledProcessError, IndexError, ValueError): + return "Error" + return "N/A" + @staticmethod def set_control_value(card_id, control_name, value): if not card_id: return False @@ -135,96 +127,50 @@ class TascamControlPanel(QWidget): def init_ui(self): self.setWindowTitle("TASCAM US-144MKII Control Panel") self.setWindowIcon(QIcon(resource_path("icon.ico"))) - self.setFixedSize(820, 450) - + self.setFixedSize(800, 450) self.setStyleSheet(DARK_STYLESHEET) - self.background_label = QLabel(self) - self.background_label.setGeometry(self.rect()) - self.background_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + main_layout = QHBoxLayout(self) + left_panel, middle_panel, right_panel = QVBoxLayout(), QVBoxLayout(), QVBoxLayout() - bg_image_path = resource_path("bg.png") - self.original_bg_pixmap = QPixmap(bg_image_path) - - if self.original_bg_pixmap.isNull(): - print(f"Warning: Could not load background image from {bg_image_path}. Using solid color.") - self.setStyleSheet(self.styleSheet() + "TascamControlPanel { background-color: #1a1a1a; }") - else: - self._update_background_pixmap() - - self.background_label.lower() - - content_container = QWidget(self) - top_level_layout = QHBoxLayout(content_container) - top_level_layout.setContentsMargins(20, 20, 20, 20) - top_level_layout.setSpacing(25) - - main_overall_layout = QVBoxLayout(self) - main_overall_layout.setContentsMargins(0, 0, 0, 0) - main_overall_layout.addWidget(content_container) - - left_panel = QVBoxLayout() - info_grid = QGridLayout() + header_layout = QVBoxLayout() logo_label = QLabel() - logo_label.setPixmap(QPixmap(resource_path("logo.png")).scaledToWidth(250, Qt.TransformationMode.SmoothTransformation)) + logo_label.setPixmap(QPixmap(resource_path("logo.png")).scaled(250, 50, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)) title_label = QLabel("US-144 MKII Control Panel") - title_label.setObjectName("Title") - left_panel.addWidget(logo_label) - left_panel.addWidget(title_label) - info_grid.setSpacing(5) + title_label.setFont(QFont("Arial", 15, QFont.Weight.Bold)) + header_layout.addWidget(logo_label) + header_layout.addWidget(title_label) + + info_layout = QGridLayout() self.info_labels = {} info_data = { - "Driver Version:": "driver_version", "Device:": "device", - "Sample Width:": "sample_width", "Sample Rate:": "sample_rate", - "Sample Clock Source:": "clock_source", "Digital Input Status:": "digital_status" + "Driver Version:": "N/A", "Device:": "US-144 MKII", + "Sample Width:": "24 bits", "Sample Rate:": "N/A", + "Sample Clock Source:": "internal", "Digital Input Status:": "unavailable" } - row = 0 - for label_text, key in info_data.items(): - label = QLabel(label_text) - label.setFont(QFont("Arial", 9, QFont.Weight.Bold)) - value_label = QLabel("N/A") - value_label.setFont(QFont("Arial", 9)) - info_grid.addWidget(label, row, 0) - info_grid.addWidget(value_label, row, 1) + for row, (label_text, value_text) in enumerate(info_data.items()): + key = label_text.replace(":", "").replace(" ", "_").lower() + label = QLabel(label_text, font=QFont("Arial", 10, QFont.Weight.Bold)) + value_label = QLabel(value_text, font=QFont("Arial", 10)) + info_layout.addWidget(label, row, 0, Qt.AlignmentFlag.AlignLeft) + info_layout.addWidget(value_label, row, 1, Qt.AlignmentFlag.AlignLeft) self.info_labels[key] = value_label - row += 1 - left_panel.addLayout(info_grid) + + left_panel.addLayout(info_layout) left_panel.addStretch() - middle_panel = QVBoxLayout() - middle_panel.setSpacing(0) + middle_panel.setSpacing(15) + latency_container, self.latency_combo = self.create_control_widget("Audio Performance", ["low latency", "normal latency", "high latency"]) + mock_container1, _ = self.create_control_widget("Sample Clock Source", ["internal", "auto"]) + mock_container2, _ = self.create_control_widget("Digital Output Format", ["S/PDIF"]) + routing_container, self.routing_combo = self.create_control_widget("LINE OUTPUTS", ["Stereo to All", "Swapped", "Digital In to All"]) - # --- Latency Setting Re-added --- - latency_header = QLabel("AUDIO PERFORMANCE") - latency_header.setObjectName("SectionHeader") - latency_container, self.latency_combo = self.create_control_widget("Latency Profile", ["low latency", "normal latency", "high latency"]) - middle_panel.addWidget(latency_header) middle_panel.addWidget(latency_container) - # --- End Latency Setting Re-added --- - - inputs_header = QLabel("INPUTS") - inputs_header.setObjectName("SectionHeader") - capture_12_container, self.capture_12_combo = self.create_control_widget("ch1 and ch2", ["Analog In", "Digital In"]) - capture_34_container, self.capture_34_combo = self.create_control_widget("ch3 and ch4", ["Analog In", "Digital In"]) - middle_panel.addWidget(inputs_header) - middle_panel.addWidget(capture_12_container) - middle_panel.addWidget(capture_34_container) - - line_header = QLabel("LINE") - line_header.setObjectName("SectionHeader") - line_out_container, self.line_out_combo = self.create_control_widget("ch1 and ch2", ["Playback 1-2", "Playback 3-4"]) - middle_panel.addWidget(line_header) - middle_panel.addWidget(line_out_container) - - digital_header = QLabel("DIGITAL") - digital_header.setObjectName("SectionHeader") - digital_out_container, self.digital_out_combo = self.create_control_widget("ch3 and ch4", ["Playback 1-2", "Playback 3-4"]) - middle_panel.addWidget(digital_header) - middle_panel.addWidget(digital_out_container) - + middle_panel.addWidget(mock_container1) + middle_panel.addWidget(mock_container2) + middle_panel.addWidget(routing_container) middle_panel.addStretch() - right_panel = QVBoxLayout() device_image_label = QLabel() device_image_label.setPixmap(QPixmap(resource_path("device.png")).scaled(250, 250, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)) device_image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) @@ -235,40 +181,27 @@ class TascamControlPanel(QWidget): right_panel.addStretch() right_panel.addWidget(exit_button, 0, Qt.AlignmentFlag.AlignCenter) - top_level_layout.addLayout(left_panel, 3) - top_level_layout.addLayout(middle_panel, 3) - top_level_layout.addLayout(right_panel, 3) + top_level_layout = QVBoxLayout() + top_level_layout.addLayout(header_layout) + top_level_layout.addSpacing(20) + panels_layout = QHBoxLayout() + panels_layout.setContentsMargins(10, 0, 10, 0) + panels_layout.addLayout(left_panel, 1) + panels_layout.addLayout(middle_panel, 1) + panels_layout.addLayout(right_panel, 1) + top_level_layout.addLayout(panels_layout) + main_layout.addLayout(top_level_layout) + self.setLayout(main_layout) - # --- Latency Signal Connection Re-added --- - self.latency_combo.currentIndexChanged.connect(lambda i: self.set_value("Latency Profile", i)) - # --- End Latency Signal Connection Re-added --- - - self.line_out_combo.currentIndexChanged.connect(lambda i: self.set_value("Line Out Source", i)) - self.digital_out_combo.currentIndexChanged.connect(lambda i: self.set_value("Digital Out Source", i)) - self.capture_12_combo.currentIndexChanged.connect(lambda i: self.set_value("Capture 1-2 Source", i)) - self.capture_34_combo.currentIndexChanged.connect(lambda i: self.set_value("Capture 3-4 Source", i)) - - def _update_background_pixmap(self): - if not self.original_bg_pixmap.isNull(): - scaled_pixmap = self.original_bg_pixmap.scaled( - self.size(), - Qt.AspectRatioMode.KeepAspectRatioByExpanding, - Qt.TransformationMode.SmoothTransformation - ) - self.background_label.setPixmap(scaled_pixmap) - - def resizeEvent(self, event): - self.background_label.setGeometry(self.rect()) - self._update_background_pixmap() - super().resizeEvent(event) + self.latency_combo.currentIndexChanged.connect(self.on_latency_changed) + self.routing_combo.currentIndexChanged.connect(self.on_routing_changed) def create_control_widget(self, label_text, combo_items): container_widget = QWidget() layout = QVBoxLayout(container_widget) - layout.setContentsMargins(0, 8, 0, 8) + layout.setContentsMargins(0,0,0,0) layout.setSpacing(2) - label = QLabel(label_text) - label.setObjectName("ControlLabel") + label = QLabel(label_text, font=QFont("Arial", 10, QFont.Weight.Bold)) combo_box = QComboBox() combo_box.addItems(combo_items) layout.addWidget(label) @@ -276,32 +209,30 @@ class TascamControlPanel(QWidget): return container_widget, combo_box def load_dynamic_settings(self): - self.info_labels['driver_version'].setText(AmixerController.read_sysfs_attr(self.card_id, "driver_version")) - self.info_labels['device'].setText("US-144 MKII") - self.info_labels['sample_width'].setText("24 bits") - self.info_labels['clock_source'].setText("internal") - self.info_labels['digital_status'].setText("unavailable") + driver_ver = AmixerController.read_sysfs_attr(self.card_id, "driver_version") + self.info_labels['driver_version'].setText(driver_ver) rate_val = AmixerController.get_control_value(self.card_id, "Sample Rate") - self.info_labels['sample_rate'].setText(f"{rate_val / 1000:.1f} kHz" if rate_val > 0 else "N/A (inactive)") + if rate_val > 0: + self.info_labels['sample_rate'].setText(f"{rate_val / 1000:.1f} kHz") + else: + self.info_labels['sample_rate'].setText("N/A (inactive)") - # --- Latency Setting Load Re-added --- - self.update_combo(self.latency_combo, "Latency Profile") - # --- End Latency Setting Load Re-added --- + latency_val = AmixerController.get_control_value(self.card_id, "Latency Profile") + self.latency_combo.blockSignals(True) + self.latency_combo.setCurrentIndex(latency_val) + self.latency_combo.blockSignals(False) - self.update_combo(self.line_out_combo, "Line Out Source") - self.update_combo(self.digital_out_combo, "Digital Out Source") - self.update_combo(self.capture_12_combo, "Capture 1-2 Source") - self.update_combo(self.capture_34_combo, "Capture 3-4 Source") + routing_val = AmixerController.get_control_value(self.card_id, "Playback Routing") + self.routing_combo.blockSignals(True) + self.routing_combo.setCurrentIndex(routing_val) + self.routing_combo.blockSignals(False) - def update_combo(self, combo, control_name): - value = AmixerController.get_control_value(self.card_id, control_name) - combo.blockSignals(True) - combo.setCurrentIndex(value) - combo.blockSignals(False) + def on_latency_changed(self, index): + AmixerController.set_control_value(self.card_id, "Latency Profile", index) - def set_value(self, control_name, index): - AmixerController.set_control_value(self.card_id, control_name, index) + def on_routing_changed(self, index): + AmixerController.set_control_value(self.card_id, "Playback Routing", index) def main(): app = QApplication(sys.argv) diff --git a/us144mkii.c b/us144mkii.c index c27d27c..9167755 100644 --- a/us144mkii.c +++ b/us144mkii.c @@ -22,7 +22,13 @@ MODULE_DESCRIPTION("ALSA Driver for TASCAM US-144MKII"); MODULE_LICENSE("GPL"); #define DRIVER_NAME "us144mkii" -#define DRIVER_VERSION "1.1" +#define DRIVER_VERSION "1.0" + +/* + * TODO: + * - Implement audio input capture. + * - Implement MIDI IN/OUT. + */ /* --- Module Parameters --- */ static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; @@ -70,9 +76,6 @@ static int dev_idx; #define NUM_FEEDBACK_URBS 4 #define MAX_FEEDBACK_PACKETS 5 #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 * 2) #define USB_CTRL_TIMEOUT_MS 1000 /* --- Audio Format Configuration --- */ @@ -81,12 +84,6 @@ static int dev_idx; #define BYTES_PER_FRAME (NUM_CHANNELS * BYTES_PER_SAMPLE) #define FEEDBACK_ACCUMULATOR_SIZE 128 -/* --- Capture Decoding Defines --- */ -#define DECODED_CHANNELS_PER_FRAME 24 -#define DECODED_SAMPLE_SIZE 4 /* 32-bit */ -#define FRAMES_PER_DECODE_BLOCK 4 -#define RAW_BYTES_PER_DECODE_BLOCK 512 - /* --- Main Driver Data Structure --- */ struct tascam_card { struct usb_device *dev; @@ -95,37 +92,18 @@ struct tascam_card { struct snd_card *card; struct snd_pcm *pcm; - /* Playback stream */ struct snd_pcm_substream *playback_substream; 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; - atomic_t playback_active; - u64 playback_frames_consumed; - snd_pcm_uframes_t driver_playback_pos; - u64 last_period_pos; - /* Capture stream */ - struct snd_pcm_substream *capture_substream; - struct urb *capture_urbs[NUM_CAPTURE_URBS]; - size_t capture_urb_alloc_size; - atomic_t capture_active; - snd_pcm_uframes_t driver_capture_pos; - u64 capture_frames_processed; - u64 last_capture_period_pos; - u8 *capture_ring_buffer; - size_t capture_ring_buffer_read_ptr; - volatile size_t capture_ring_buffer_write_ptr; - - /* Shared state & Routing Matrix */ spinlock_t lock; + atomic_t playback_active; int current_rate; unsigned int latency_profile; - unsigned int line_out_source; /* 0: Playback 1-2, 1: Playback 3-4 */ - unsigned int digital_out_source; /* 0: Playback 1-2, 1: Playback 3-4 */ - unsigned int capture_12_source; /* 0: Analog In, 1: Digital In */ - unsigned int capture_34_source; /* 0: Analog In, 1: Digital In */ + unsigned int playback_routing; unsigned int feedback_accumulator_pattern[FEEDBACK_ACCUMULATOR_SIZE]; unsigned int feedback_pattern_out_idx; @@ -133,6 +111,10 @@ struct tascam_card { bool feedback_synced; unsigned int feedback_urb_skip_count; + u64 playback_frames_consumed; + snd_pcm_uframes_t driver_playback_pos; + u64 last_period_pos; + const unsigned int (*feedback_patterns)[8]; unsigned int feedback_base_value; unsigned int feedback_max_value; @@ -143,7 +125,6 @@ static struct usb_driver tascam_alsa_driver; /* --- Forward Declarations --- */ static void playback_urb_complete(struct urb *urb); static void feedback_urb_complete(struct urb *urb); -static void capture_urb_complete(struct urb *urb); static int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int rate); static int tascam_probe(struct usb_interface *intf, const struct usb_device_id *id); static void tascam_disconnect(struct usb_interface *intf); @@ -151,6 +132,14 @@ static int tascam_suspend(struct usb_interface *intf, pm_message_t message); static int tascam_resume(struct usb_interface *intf); /* --- Sysfs Attribute for Driver Version --- */ +/** + * driver_version_show - Sysfs callback to show the driver version. + * @dev: The device structure. + * @attr: The device attribute structure. + * @buf: The buffer to write the version string into. + * + * Returns: The number of bytes written. + */ static ssize_t driver_version_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -160,8 +149,6 @@ static DEVICE_ATTR_RO(driver_version); /* --- ALSA Control Definitions --- */ static const char * const latency_profile_texts[] = {"Low", "Normal", "High"}; -static const char * const playback_source_texts[] = {"Playback 1-2", "Playback 3-4"}; -static const char * const capture_source_texts[] = {"Analog In", "Digital In"}; static int tascam_latency_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { @@ -176,103 +163,97 @@ static int tascam_latency_info(struct snd_kcontrol *kcontrol, struct snd_ctl_ele static int tascam_latency_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct tascam_card *tascam = snd_kcontrol_chip(kcontrol); - ucontrol->value.enumerated.item[0] = tascam->latency_profile; + struct tascam_card *tascam = (struct tascam_card *)snd_kcontrol_chip(kcontrol); + switch (tascam->latency_profile) { + case 1: ucontrol->value.enumerated.item[0] = 0; break; + case 2: ucontrol->value.enumerated.item[0] = 1; break; + case 5: ucontrol->value.enumerated.item[0] = 2; break; + default: ucontrol->value.enumerated.item[0] = 1; break; + } return 0; } static int tascam_latency_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - struct tascam_card *tascam = snd_kcontrol_chip(kcontrol); - unsigned int new_profile = ucontrol->value.enumerated.item[0]; - if (new_profile >= 3) - return -EINVAL; + struct tascam_card *tascam = (struct tascam_card *)snd_kcontrol_chip(kcontrol); + unsigned int new_profile; + bool changed = false; + + switch (ucontrol->value.enumerated.item[0]) { + case 0: new_profile = 1; break; + case 1: new_profile = 2; break; + case 2: new_profile = 5; break; + default: return -EINVAL; + } + if (tascam->latency_profile != new_profile) { tascam->latency_profile = new_profile; - return 1; + changed = true; } - return 0; + return changed; } static const struct snd_kcontrol_new tascam_latency_control = { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Latency Profile", - .info = tascam_latency_info, .get = tascam_latency_get, .put = tascam_latency_put, + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Latency Profile", + .info = tascam_latency_info, + .get = tascam_latency_get, + .put = tascam_latency_put, }; -static int tascam_playback_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +static const char * const playback_routing_texts[] = {"Stereo to All", "Swapped", "Digital In to All"}; + +static int tascam_routing_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) { uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; uinfo->count = 1; - uinfo->value.enumerated.items = 2; - if (uinfo->value.enumerated.item >= 2) - uinfo->value.enumerated.item = 1; - strcpy(uinfo->value.enumerated.name, playback_source_texts[uinfo->value.enumerated.item]); + uinfo->value.enumerated.items = 3; + if (uinfo->value.enumerated.item >= 3) + uinfo->value.enumerated.item = 2; + strcpy(uinfo->value.enumerated.name, playback_routing_texts[uinfo->value.enumerated.item]); return 0; } -static int tascam_line_out_get(struct snd_kcontrol *k, struct snd_ctl_elem_value *u) -{ u->value.enumerated.item[0] = ((struct tascam_card *)snd_kcontrol_chip(k))->line_out_source; return 0; } - -static int tascam_line_out_put(struct snd_kcontrol *k, struct snd_ctl_elem_value *u) -{ struct tascam_card *t = snd_kcontrol_chip(k); if (u->value.enumerated.item[0] > 1) return -EINVAL; - if (t->line_out_source == u->value.enumerated.item[0]) return 0; - t->line_out_source = u->value.enumerated.item[0]; return 1; } - -static const struct snd_kcontrol_new tascam_line_out_control = { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Line Out Source", - .info = tascam_playback_source_info, .get = tascam_line_out_get, .put = tascam_line_out_put, -}; - -static int tascam_digital_out_get(struct snd_kcontrol *k, struct snd_ctl_elem_value *u) -{ u->value.enumerated.item[0] = ((struct tascam_card *)snd_kcontrol_chip(k))->digital_out_source; return 0; } - -static int tascam_digital_out_put(struct snd_kcontrol *k, struct snd_ctl_elem_value *u) -{ struct tascam_card *t = snd_kcontrol_chip(k); if (u->value.enumerated.item[0] > 1) return -EINVAL; - if (t->digital_out_source == u->value.enumerated.item[0]) return 0; - t->digital_out_source = u->value.enumerated.item[0]; return 1; } - -static const struct snd_kcontrol_new tascam_digital_out_control = { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Digital Out Source", - .info = tascam_playback_source_info, .get = tascam_digital_out_get, .put = tascam_digital_out_put, -}; - -static int tascam_capture_source_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo) +static int tascam_routing_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) { - uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; - uinfo->count = 1; - uinfo->value.enumerated.items = 2; - if (uinfo->value.enumerated.item >= 2) - uinfo->value.enumerated.item = 1; - strcpy(uinfo->value.enumerated.name, capture_source_texts[uinfo->value.enumerated.item]); + struct tascam_card *tascam = (struct tascam_card *)snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = tascam->playback_routing; return 0; } -static int tascam_capture_12_get(struct snd_kcontrol *k, struct snd_ctl_elem_value *u) -{ u->value.enumerated.item[0] = ((struct tascam_card *)snd_kcontrol_chip(k))->capture_12_source; return 0; } +static int tascam_routing_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct tascam_card *tascam = (struct tascam_card *)snd_kcontrol_chip(kcontrol); + unsigned int new_routing = ucontrol->value.enumerated.item[0]; + bool changed = false; -static int tascam_capture_12_put(struct snd_kcontrol *k, struct snd_ctl_elem_value *u) -{ struct tascam_card *t = snd_kcontrol_chip(k); if (u->value.enumerated.item[0] > 1) return -EINVAL; - if (t->capture_12_source == u->value.enumerated.item[0]) return 0; - t->capture_12_source = u->value.enumerated.item[0]; return 1; } + if (new_routing >= 3) + return -EINVAL; -static const struct snd_kcontrol_new tascam_capture_12_control = { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture 1-2 Source", - .info = tascam_capture_source_info, .get = tascam_capture_12_get, .put = tascam_capture_12_put, -}; - -static int tascam_capture_34_get(struct snd_kcontrol *k, struct snd_ctl_elem_value *u) -{ u->value.enumerated.item[0] = ((struct tascam_card *)snd_kcontrol_chip(k))->capture_34_source; return 0; } - -static int tascam_capture_34_put(struct snd_kcontrol *k, struct snd_ctl_elem_value *u) -{ struct tascam_card *t = snd_kcontrol_chip(k); if (u->value.enumerated.item[0] > 1) return -EINVAL; - if (t->capture_34_source == u->value.enumerated.item[0]) return 0; - t->capture_34_source = u->value.enumerated.item[0]; return 1; } - -static const struct snd_kcontrol_new tascam_capture_34_control = { - .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = "Capture 3-4 Source", - .info = tascam_capture_source_info, .get = tascam_capture_34_get, .put = tascam_capture_34_put, + if (tascam->playback_routing != new_routing) { + tascam->playback_routing = new_routing; + changed = true; + } + return changed; +} + +static const struct snd_kcontrol_new tascam_routing_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Playback Routing", + .info = tascam_routing_info, + .get = tascam_routing_get, + .put = tascam_routing_put, }; +/** + * tascam_samplerate_info - ALSA control info callback for the sample rate. + * @kcontrol: The kcontrol instance. + * @uinfo: The user control element info structure to fill. + * + * Provides information about the read-only sample rate control. + * + * Returns: 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; @@ -282,6 +263,17 @@ static int tascam_samplerate_info(struct snd_kcontrol *kcontrol, struct snd_ctl_ return 0; } +/** + * tascam_samplerate_get - ALSA control get callback for the sample rate. + * @kcontrol: The kcontrol instance. + * @ucontrol: The user control element value structure to fill. + * + * Reports the current sample rate of the device. It first checks the driver's + * internal state. If no stream is active, it queries the device directly via + * a USB control message. + * + * Returns: 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); @@ -319,6 +311,7 @@ static const struct snd_kcontrol_new tascam_samplerate_control = { .access = SNDRV_CTL_ELEM_ACCESS_READ, }; +/* --- Rate-to-Packet Fixing Data (Verified) --- */ static const unsigned int patterns_48khz[5][8] = { {5, 6, 6, 6, 5, 6, 6, 6}, {5, 6, 6, 6, 6, 6, 6, 6}, {6, 6, 6, 6, 6, 6, 6, 6}, {7, 6, 6, 6, 6, 6, 6, 6}, @@ -356,6 +349,10 @@ static const struct snd_pcm_hardware tascam_pcm_hw = { .periods_min = 2, .periods_max = 1024, }; +/** + * tascam_free_urbs - Free all allocated URBs and their buffers. + * @tascam: The card instance. + */ static void tascam_free_urbs(struct tascam_card *tascam) { int i; @@ -381,21 +378,13 @@ static void tascam_free_urbs(struct tascam_card *tascam) tascam->feedback_urbs[i] = NULL; } } - - for (i = 0; i < NUM_CAPTURE_URBS; i++) { - if (tascam->capture_urbs[i]) { - usb_kill_urb(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; - } - } - kfree(tascam->capture_ring_buffer); - tascam->capture_ring_buffer = NULL; } +/** + * tascam_alloc_urbs - Allocate URBs and their buffers. + * @tascam: The card instance. + * Returns: 0 on success, or a negative error code on failure. + */ static int tascam_alloc_urbs(struct tascam_card *tascam) { int i; @@ -444,31 +433,6 @@ static int tascam_alloc_urbs(struct tascam_card *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; - } - - tascam->capture_ring_buffer = kmalloc(CAPTURE_RING_BUFFER_SIZE, GFP_KERNEL); - if (!tascam->capture_ring_buffer) - goto error; - return 0; error: @@ -477,55 +441,27 @@ error: return -ENOMEM; } -static int tascam_playback_open(struct snd_pcm_substream *substream) +static int tascam_pcm_open(struct snd_pcm_substream *substream) { struct tascam_card *tascam = snd_pcm_substream_chip(substream); - int err = 0; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; - substream->runtime->hw = tascam_pcm_hw; + runtime->hw = tascam_pcm_hw; tascam->playback_substream = substream; atomic_set(&tascam->playback_active, 0); - if (!tascam->capture_substream) { - err = tascam_alloc_urbs(tascam); - if (err < 0) - return err; - } + err = tascam_alloc_urbs(tascam); + if (err < 0) + return err; + return 0; } -static int tascam_capture_open(struct snd_pcm_substream *substream) +static int tascam_pcm_close(struct snd_pcm_substream *substream) { struct tascam_card *tascam = snd_pcm_substream_chip(substream); - int err = 0; - - substream->runtime->hw = tascam_pcm_hw; - tascam->capture_substream = substream; - atomic_set(&tascam->capture_active, 0); - - if (!tascam->playback_substream) { - err = tascam_alloc_urbs(tascam); - if (err < 0) - return err; - } - return 0; -} - -static int tascam_playback_close(struct snd_pcm_substream *substream) -{ - struct tascam_card *tascam = snd_pcm_substream_chip(substream); - tascam->playback_substream = NULL; - if (!tascam->capture_substream) - tascam_free_urbs(tascam); - return 0; -} - -static int tascam_capture_close(struct snd_pcm_substream *substream) -{ - struct tascam_card *tascam = snd_pcm_substream_chip(substream); - tascam->capture_substream = NULL; - if (!tascam->playback_substream) - tascam_free_urbs(tascam); + tascam_free_urbs(tascam); return 0; } @@ -597,29 +533,29 @@ static int tascam_pcm_hw_params(struct snd_pcm_substream *substream, if (err < 0) return err; - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - switch (rate) { - case 44100: - tascam->feedback_patterns = patterns_44khz; - tascam->feedback_base_value = 42; tascam->feedback_max_value = 46; - break; - case 48000: - tascam->feedback_patterns = patterns_48khz; - tascam->feedback_base_value = 46; tascam->feedback_max_value = 50; - break; - case 88200: - tascam->feedback_patterns = patterns_88khz; - tascam->feedback_base_value = 86; tascam->feedback_max_value = 90; - break; - case 96000: - tascam->feedback_patterns = patterns_96khz; - tascam->feedback_base_value = 94; tascam->feedback_max_value = 98; - break; - default: - return -EINVAL; - } + /* Set rate-dependent feedback patterns and values */ + switch (rate) { + case 44100: + tascam->feedback_patterns = patterns_44khz; + tascam->feedback_base_value = 42; tascam->feedback_max_value = 46; + break; + case 48000: + tascam->feedback_patterns = patterns_48khz; + tascam->feedback_base_value = 46; tascam->feedback_max_value = 50; + break; + case 88200: + tascam->feedback_patterns = patterns_88khz; + tascam->feedback_base_value = 86; tascam->feedback_max_value = 90; + break; + case 96000: + tascam->feedback_patterns = patterns_96khz; + tascam->feedback_base_value = 94; tascam->feedback_max_value = 98; + break; + default: + return -EINVAL; } + /* Re-configure hardware only if the sample rate has changed */ if (tascam->current_rate != rate) { err = us144mkii_configure_device_for_rate(tascam, rate); if (err < 0) { @@ -637,7 +573,7 @@ static int tascam_pcm_hw_free(struct snd_pcm_substream *substream) return snd_pcm_lib_free_pages(substream); } -static int tascam_playback_prepare(struct snd_pcm_substream *substream) +static int tascam_pcm_prepare(struct snd_pcm_substream *substream) { struct tascam_card *tascam = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; @@ -646,6 +582,7 @@ static int tascam_playback_prepare(struct snd_pcm_substream *substream) size_t total_bytes_in_urb; unsigned int feedback_packets; + /* Reset driver state for the new stream */ tascam->driver_playback_pos = 0; tascam->playback_frames_consumed = 0; tascam->last_period_pos = 0; @@ -654,20 +591,32 @@ static int tascam_playback_prepare(struct snd_pcm_substream *substream) tascam->feedback_synced = false; tascam->feedback_urb_skip_count = NUM_FEEDBACK_URBS * 2; + /* Initialize feedback accumulator with nominal values */ nominal_frames_per_packet = runtime->rate / 8000; for (i = 0; i < FEEDBACK_ACCUMULATOR_SIZE; i++) tascam->feedback_accumulator_pattern[i] = nominal_frames_per_packet; + /* Validate and apply latency profile */ switch (tascam->latency_profile) { - case 0: feedback_packets = 1; break; - case 1: feedback_packets = 2; break; - case 2: feedback_packets = 5; break; - default: feedback_packets = 2; + case 1: + case 2: + case 5: + feedback_packets = tascam->latency_profile; + break; + default: + dev_warn(tascam->card->dev, "Invalid latency_profile value %d, falling back to default (2).\n", tascam->latency_profile); + tascam->latency_profile = 2; + feedback_packets = 2; } + dev_info(tascam->card->dev, "Prepare: Using latency profile %u (%u feedback packets) for %u Hz\n", + tascam->latency_profile, feedback_packets, runtime->rate); + + /* Configure Feedback URBs */ for (i = 0; i < NUM_FEEDBACK_URBS; i++) { struct urb *f_urb = tascam->feedback_urbs[i]; int j; + f_urb->number_of_packets = feedback_packets; f_urb->transfer_buffer_length = feedback_packets * FEEDBACK_PACKET_SIZE; for (j = 0; j < feedback_packets; j++) { @@ -676,11 +625,13 @@ static int tascam_playback_prepare(struct snd_pcm_substream *substream) } } + /* Configure Playback URBs */ 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; @@ -693,7 +644,7 @@ static int tascam_playback_prepare(struct snd_pcm_substream *substream) return 0; } -static int tascam_playback_trigger(struct snd_pcm_substream *substream, int cmd) +static int tascam_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { struct tascam_card *tascam = snd_pcm_substream_chip(substream); int err = 0, i; @@ -738,7 +689,7 @@ rollback: return err; } -static snd_pcm_uframes_t tascam_playback_pointer(struct snd_pcm_substream *substream) +static snd_pcm_uframes_t tascam_pcm_pointer(struct snd_pcm_substream *substream) { struct tascam_card *tascam = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; @@ -756,91 +707,18 @@ static snd_pcm_uframes_t tascam_playback_pointer(struct snd_pcm_substream *subst } static struct snd_pcm_ops tascam_playback_ops = { - .open = tascam_playback_open, .close = tascam_playback_close, + .open = tascam_pcm_open, .close = tascam_pcm_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = tascam_pcm_hw_params, - .hw_free = tascam_pcm_hw_free, .prepare = tascam_playback_prepare, - .trigger = tascam_playback_trigger, .pointer = tascam_playback_pointer, + .hw_free = tascam_pcm_hw_free, .prepare = tascam_pcm_prepare, + .trigger = tascam_pcm_trigger, .pointer = tascam_pcm_pointer, }; -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; -} - -static int tascam_capture_trigger(struct snd_pcm_substream *substream, int cmd) -{ - struct tascam_card *tascam = snd_pcm_substream_chip(substream); - int err = 0, i; - bool start = false; - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME: - if (atomic_xchg(&tascam->capture_active, 1) == 0) - start = true; - break; - case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - atomic_set(&tascam->capture_active, 0); - break; - default: return -EINVAL; - } - - if (start) { - for (i = 0; i < NUM_CAPTURE_URBS; i++) { - err = usb_submit_urb(tascam->capture_urbs[i], GFP_ATOMIC); - if (err < 0) - goto rollback; - } - } else { - for (i = 0; i < NUM_CAPTURE_URBS; i++) - usb_unlink_urb(tascam->capture_urbs[i]); - } - return 0; - -rollback: - dev_err(tascam->card->dev, "Failed to submit capture URBs: %d\n", err); - atomic_set(&tascam->capture_active, 0); - for (i = 0; i < NUM_CAPTURE_URBS; i++) - usb_unlink_urb(tascam->capture_urbs[i]); - return err; -} - -static snd_pcm_uframes_t tascam_capture_pointer(struct snd_pcm_substream *substream) -{ - struct tascam_card *tascam = snd_pcm_substream_chip(substream); - unsigned long flags; - snd_pcm_uframes_t pos; - - spin_lock_irqsave(&tascam->lock, flags); - pos = tascam->driver_capture_pos; - spin_unlock_irqrestore(&tascam->lock, flags); - - return pos; -} - +static int tascam_capture_open_stub(struct snd_pcm_substream *s) { return -ENODEV; } +static int tascam_capture_close_stub(struct snd_pcm_substream *s) { return 0; } static 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_capture_trigger, .pointer = tascam_capture_pointer, + .open = tascam_capture_open_stub, .close = tascam_capture_close_stub, }; -/** - * playback_urb_complete - Completion handler for playback isochronous URBs. - * @urb: The completed URB. - * - * This function calculates the required packet sizes based on feedback, - * copies audio data from the ALSA buffer to the URB, applies the routing - * matrix for Line and Digital outputs, and resubmits the URB. - */ static void playback_urb_complete(struct urb *urb) { struct tascam_card *tascam = urb->context; @@ -864,6 +742,7 @@ static void playback_urb_complete(struct urb *urb) spin_lock_irqsave(&tascam->lock, flags); + /* Phase 1: Populate the isochronous frame descriptors from the accumulator. */ for (i = 0; i < urb->number_of_packets; i++) { unsigned int frames_for_packet; size_t bytes_for_packet; @@ -882,12 +761,15 @@ static void playback_urb_complete(struct urb *urb) } urb->transfer_buffer_length = total_bytes_for_urb; + /* Phase 2: Atomically update the driver's position. */ 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; + /* --- End of Critical Section --- */ spin_unlock_irqrestore(&tascam->lock, flags); + /* Phase 3: Perform the data copy OUTSIDE the lock. */ if (total_bytes_for_urb > 0) { int f; src_buf = runtime->dma_area; @@ -898,20 +780,20 @@ static void playback_urb_complete(struct urb *urb) char *src_frame = src_buf + frames_to_bytes(runtime, current_frame_pos); char *dst_frame = dst_buf + (f * BYTES_PER_FRAME); - char *src_12 = src_frame; - char *src_34 = src_frame + 6; - char *dst_line_out = dst_frame; - char *dst_digital_out = dst_frame + 6; - - if (tascam->line_out_source == 0) - memcpy(dst_line_out, src_12, 6); - else - memcpy(dst_line_out, src_34, 6); - - if (tascam->digital_out_source == 0) - memcpy(dst_digital_out, src_12, 6); - else - memcpy(dst_digital_out, src_34, 6); + switch (tascam->playback_routing) { + case 0: /* Stereo to All */ + memcpy(dst_frame, src_frame, 6); /* Copy L/R to Out 1/2 */ + memcpy(dst_frame + 6, src_frame, 6); /* Copy L/R to Out 3/4 */ + break; + case 1: /* Swapped */ + memcpy(dst_frame, src_frame + 6, 6); /* Copy 3/4 to Out 1/2 */ + memcpy(dst_frame + 6, src_frame, 6); /* Copy 1/2 to Out 3/4 */ + break; + case 2: /* Digital In to All */ + memcpy(dst_frame, src_frame + 6, 6); /* Copy 3/4 to Out 1/2 */ + memcpy(dst_frame + 6, src_frame + 6, 6); /* Copy 3/4 to Out 3/4 */ + break; + } } } @@ -921,13 +803,6 @@ static void playback_urb_complete(struct urb *urb) dev_err_ratelimited(tascam->card->dev, "Failed to resubmit playback URB: %d\n", ret); } -/** - * feedback_urb_complete - Completion handler for feedback isochronous URBs. - * @urb: The completed URB. - * - * This function reads the feedback value from the device to adjust the - * playback rate, ensuring sync between the host and the device clock. - */ static void feedback_urb_complete(struct urb *urb) { struct tascam_card *tascam = urb->context; @@ -948,18 +823,20 @@ static void feedback_urb_complete(struct urb *urb) spin_lock_irqsave(&tascam->lock, flags); + /* Let a few URBs pass to allow the hardware to stabilize. */ if (tascam->feedback_urb_skip_count > 0) { tascam->feedback_urb_skip_count--; goto unlock_and_resubmit; } + /* After the initial skip, we consider the stream synced. */ if (!tascam->feedback_synced) { dev_dbg(tascam->card->dev, "Sync Acquired!\n"); tascam->feedback_synced = true; } for (p = 0; p < urb->number_of_packets; p++) { - u8 feedback_value = 0; + u8 feedback_value = 0; /* Initialize to a known invalid value */ const unsigned int *pattern; bool packet_ok = (urb->iso_frame_desc[p].status == 0 && urb->iso_frame_desc[p].actual_length >= 1); @@ -969,6 +846,7 @@ static void feedback_urb_complete(struct urb *urb) if (packet_ok && feedback_value >= tascam->feedback_base_value && feedback_value <= tascam->feedback_max_value) { + /* Valid Feedback: Use the pattern from the table. */ pattern = tascam->feedback_patterns[feedback_value - tascam->feedback_base_value]; int i; for (i = 0; i < 8; i++) { @@ -977,9 +855,10 @@ static void feedback_urb_complete(struct urb *urb) total_frames_in_urb += pattern[i]; } } else { + /* Invalid Feedback: Use the nominal pattern as a fallback. */ unsigned int nominal_frames = runtime->rate / 8000; int i; - if (packet_ok) + if (packet_ok) /* Only log if the packet itself was ok but the value was not */ dev_warn_ratelimited(tascam->card->dev, "Invalid feedback value %u, using nominal rate.\n", feedback_value); for (i = 0; i < 8; i++) { @@ -988,12 +867,14 @@ static void feedback_urb_complete(struct urb *urb) total_frames_in_urb += nominal_frames; } } + /* Always advance the accumulator index */ tascam->feedback_pattern_in_idx = (tascam->feedback_pattern_in_idx + 8) % FEEDBACK_ACCUMULATOR_SIZE; } if (total_frames_in_urb > 0) tascam->playback_frames_consumed += total_frames_in_urb; + /* Check if a period has elapsed and notify ALSA */ current_period = div_u64(tascam->playback_frames_consumed, runtime->period_size); if (current_period > tascam->last_period_pos) { tascam->last_period_pos = current_period; @@ -1011,169 +892,6 @@ resubmit: dev_err_ratelimited(tascam->card->dev, "Failed to resubmit feedback URB: %d\n", ret); } -static void decode_tascam_capture_block(const u8 *src_block, s32 *dst_block) -{ - int frame, bit_idx, ch_idx; - - 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 __le16 *p_src_frame = (const __le16 *)(src_block + (frame * (RAW_BYTES_PER_DECODE_BLOCK / FRAMES_PER_DECODE_BLOCK))); - s32 *p_dst_frame = dst_block + (frame * DECODED_CHANNELS_PER_FRAME); - - const __le16 *src_half_even_ch = p_src_frame; - const __le16 *src_half_odd_ch = p_src_frame + 32; - - for (bit_idx = 0; bit_idx < 24; ++bit_idx) { - u16 word_even = le16_to_cpu(src_half_even_ch[bit_idx]); - u16 word_odd = le16_to_cpu(src_half_odd_ch[bit_idx]); - - for (ch_idx = 0; ch_idx < 12; ++ch_idx) { - p_dst_frame[ch_idx * 2] = (p_dst_frame[ch_idx * 2] << 1) | ((word_even >> ch_idx) & 1); - p_dst_frame[ch_idx * 2 + 1] = (p_dst_frame[ch_idx * 2 + 1] << 1) | ((word_odd >> ch_idx) & 1); - } - } - } - - for (frame = 0; frame < FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME; ++frame) - dst_block[frame] <<= 8; -} - -static void process_capture_data(struct tascam_card *tascam) -{ - struct snd_pcm_substream *substream = tascam->capture_substream; - struct snd_pcm_runtime *runtime; - u8 *raw_block; - s32 *decoded_block; - unsigned long flags; - - if (!substream || !substream->runtime) - return; - runtime = substream->runtime; - - raw_block = kmalloc(RAW_BYTES_PER_DECODE_BLOCK, GFP_ATOMIC); - if (!raw_block) - return; - decoded_block = kmalloc(FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME * DECODED_SAMPLE_SIZE, GFP_ATOMIC); - if (!decoded_block) { - kfree(raw_block); - return; - } - - while (atomic_read(&tascam->capture_active)) { - size_t write_ptr, read_ptr, available_data; - bool can_process; - - spin_lock_irqsave(&tascam->lock, flags); - 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 i; - for (i = 0; i < RAW_BYTES_PER_DECODE_BLOCK; i++) - raw_block[i] = tascam->capture_ring_buffer[(read_ptr + i) % CAPTURE_RING_BUFFER_SIZE]; - tascam->capture_ring_buffer_read_ptr = (read_ptr + RAW_BYTES_PER_DECODE_BLOCK) % CAPTURE_RING_BUFFER_SIZE; - } - spin_unlock_irqrestore(&tascam->lock, flags); - - if (!can_process) - break; - - decode_tascam_capture_block(raw_block, decoded_block); - - spin_lock_irqsave(&tascam->lock, flags); - if (atomic_read(&tascam->capture_active)) { - int f; - u64 current_period; - bool period_elapsed = false; - - for (f = 0; f < FRAMES_PER_DECODE_BLOCK; ++f) { - s32 *decoded_frame = decoded_block + (f * DECODED_CHANNELS_PER_FRAME); - char *dst_frame = runtime->dma_area + frames_to_bytes(runtime, tascam->driver_capture_pos); - - s32 *src_analog = decoded_frame; - s32 *src_digital = decoded_frame + 2; - - if (tascam->capture_12_source == 0) { - memcpy(dst_frame, ((char *)src_analog) + 1, 3); - memcpy(dst_frame + 3, ((char *)src_analog + 4) + 1, 3); - } else { - memcpy(dst_frame, ((char *)src_digital) + 1, 3); - memcpy(dst_frame + 3, ((char *)src_digital + 4) + 1, 3); - } - - if (tascam->capture_34_source == 0) { - memcpy(dst_frame + 6, ((char *)src_analog) + 1, 3); - memcpy(dst_frame + 9, ((char *)src_analog + 4) + 1, 3); - } else { - memcpy(dst_frame + 6, ((char *)src_digital) + 1, 3); - memcpy(dst_frame + 9, ((char *)src_digital + 4) + 1, 3); - } - - tascam->driver_capture_pos++; - if (tascam->driver_capture_pos == runtime->buffer_size) - tascam->driver_capture_pos = 0; - } - - tascam->capture_frames_processed += FRAMES_PER_DECODE_BLOCK; - current_period = div_u64(tascam->capture_frames_processed, runtime->period_size); - if (current_period > tascam->last_capture_period_pos) { - tascam->last_capture_period_pos = current_period; - period_elapsed = true; - } - - spin_unlock_irqrestore(&tascam->lock, flags); - - if (period_elapsed) - snd_pcm_period_elapsed(substream); - } else { - spin_unlock_irqrestore(&tascam->lock, flags); - } - } - - kfree(decoded_block); - kfree(raw_block); -} - -/** - * capture_urb_complete - Completion handler for capture bulk URBs. - * @urb: The completed URB. - * - * This function copies the raw captured data into a ring buffer and - * triggers the processing function to decode and route it. - */ -static void capture_urb_complete(struct urb *urb) -{ - struct tascam_card *tascam = urb->context; - int ret; - unsigned long flags; - - if (urb->status || !tascam || !atomic_read(&tascam->capture_active)) - return; - - if (urb->actual_length > 0) { - size_t i; - size_t write_ptr; - - spin_lock_irqsave(&tascam->lock, flags); - write_ptr = tascam->capture_ring_buffer_write_ptr; - for (i = 0; i < urb->actual_length; i++) { - tascam->capture_ring_buffer[write_ptr] = ((u8 *)urb->transfer_buffer)[i]; - write_ptr = (write_ptr + 1) % CAPTURE_RING_BUFFER_SIZE; - } - tascam->capture_ring_buffer_write_ptr = write_ptr; - spin_unlock_irqrestore(&tascam->lock, flags); - - process_capture_data(tascam); - } - - ret = usb_submit_urb(urb, GFP_ATOMIC); - if (ret < 0) - dev_err_ratelimited(tascam->card->dev, "Failed to resubmit capture URB: %d\n", ret); -} - static int tascam_create_pcm(struct tascam_card *tascam) { struct snd_pcm *pcm; @@ -1184,17 +902,16 @@ static int tascam_create_pcm(struct tascam_card *tascam) return err; err = snd_ctl_add(tascam->card, snd_ctl_new1(&tascam_latency_control, tascam)); - if (err < 0) return 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; + if (err < 0) + return err; + + err = snd_ctl_add(tascam->card, snd_ctl_new1(&tascam_routing_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; + if (err < 0) + return err; tascam->pcm = pcm; pcm->private_data = tascam; @@ -1249,6 +966,7 @@ static int tascam_resume(struct usb_interface *intf) dev = tascam->dev; dev_info(&intf->dev, "Resuming and re-initializing device...\n"); + /* Re-establish alternate settings for both interfaces */ err = usb_set_interface(dev, 0, 1); if (err < 0) { dev_err(&intf->dev, "Resume: Set Alt Setting on Intf 0 failed: %d\n", err); @@ -1260,6 +978,7 @@ static int tascam_resume(struct usb_interface *intf) return err; } + /* Re-configure the device for the last used sample rate. */ if (tascam->current_rate > 0) { dev_info(&intf->dev, "Restoring sample rate to %d Hz\n", tascam->current_rate); err = us144mkii_configure_device_for_rate(tascam, tascam->current_rate); @@ -1273,11 +992,6 @@ static int tascam_resume(struct usb_interface *intf) return 0; } -/** - * tascam_probe - Called when the USB device is connected. - * @intf: The USB interface. - * @usb_id: The USB device ID. - */ static int tascam_probe(struct usb_interface *intf, const struct usb_device_id *usb_id) { struct usb_device *dev = interface_to_usbdev(intf); @@ -1308,13 +1022,10 @@ static int tascam_probe(struct usb_interface *intf, const struct usb_device_id * card->private_free = tascam_card_private_free; usb_set_intfdata(intf, tascam); spin_lock_init(&tascam->lock); - - tascam->latency_profile = 1; - tascam->line_out_source = 0; - tascam->digital_out_source = 1; - tascam->capture_12_source = 0; - tascam->capture_34_source = 1; - tascam->current_rate = 0; + /* Initialize mixer controls to default values */ + tascam->latency_profile = 2; /* Default to Normal Latency */ + tascam->playback_routing = 0; /* Default to Stereo to All */ + tascam->current_rate = 0; /* Not known until hw_params */ strscpy(card->driver, DRIVER_NAME, sizeof(card->driver)); strscpy(card->shortname, "TASCAM US-144MKII", sizeof(card->shortname)); @@ -1387,10 +1098,6 @@ free_card_obj: return err; } -/** - * tascam_disconnect - Called when the USB device is disconnected. - * @intf: The USB interface. - */ static void tascam_disconnect(struct usb_interface *intf) { struct tascam_card *tascam = usb_get_intfdata(intf); @@ -1412,6 +1119,7 @@ static void tascam_disconnect(struct usb_interface *intf) tascam->iface1 = NULL; } + /* Decrement the device index to allow the next probe to use this slot. */ if (dev_idx > 0) dev_idx--;