diff --git a/tascam_controls/bg.png b/tascam_controls/bg.png new file mode 100644 index 0000000..5eec530 Binary files /dev/null and b/tascam_controls/bg.png differ diff --git a/tascam_controls/tascam-controls.py b/tascam_controls/tascam-controls.py index 45c6cf3..6d1ae39 100644 --- a/tascam_controls/tascam-controls.py +++ b/tascam_controls/tascam-controls.py @@ -4,8 +4,9 @@ import re import os from PyQt6.QtWidgets import (QApplication, QWidget, QLabel, QComboBox, QPushButton, QGridLayout, QVBoxLayout, QHBoxLayout, QMessageBox) -from PyQt6.QtGui import QPixmap, QFont, QIcon -from PyQt6.QtCore import Qt +from PyQt6.QtGui import QPixmap, QFont, QIcon, QPainter +from PyQt6.QtCore import Qt, QBuffer, QIODevice +import base64 def resource_path(relative_path): try: @@ -16,41 +17,63 @@ def resource_path(relative_path): DARK_STYLESHEET = """ QWidget { - background-color: #2b2b2b; - color: #f0f0f0; + background-color: transparent; + color: #DAE0ED; 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: #3c3f41; - border: 1px solid #555; + background-color: rgba(10, 10, 20, 0.25); + border: 1px solid #3A4760; border-radius: 4px; padding: 4px; + color: #DAE0ED; } QComboBox:hover { - border: 1px solid #777; + background-color: rgba(15, 15, 25, 0.35); + border: 1px solid #6482B4; } QComboBox::drop-down { border: none; } QComboBox QAbstractItemView { - background-color: #3c3f41; - border: 1px solid #555; - selection-background-color: #5a5d5f; + background-color: rgba(15, 15, 25, 0.9); + border: 1px solid #3A4760; + selection-background-color: #6A3AB1; + color: #DAE0ED; } QPushButton { - background-color: #3c3f41; - border: 1px solid #555; + background-color: rgba(10, 10, 20, 0.25); + border: 1px solid #3A4760; border-radius: 4px; padding: 5px; + color: #92E8FF; } QPushButton:hover { - background-color: #4f5254; + background-color: rgba(15, 15, 25, 0.35); + border: 1px solid #6482B4; } QPushButton:pressed { - background-color: #5a5d5f; + background-color: rgba(20, 20, 30, 0.45); + border: 1px solid #A020F0; } """ @@ -81,21 +104,6 @@ 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 @@ -127,50 +135,88 @@ class TascamControlPanel(QWidget): def init_ui(self): self.setWindowTitle("TASCAM US-144MKII Control Panel") self.setWindowIcon(QIcon(resource_path("icon.ico"))) - self.setFixedSize(800, 450) + self.setFixedSize(820, 450) + self.setStyleSheet(DARK_STYLESHEET) - main_layout = QHBoxLayout(self) - left_panel, middle_panel, right_panel = QVBoxLayout(), QVBoxLayout(), QVBoxLayout() + self.background_label = QLabel(self) + self.background_label.setGeometry(self.rect()) + self.background_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - header_layout = 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() logo_label = QLabel() - logo_label.setPixmap(QPixmap(resource_path("logo.png")).scaled(250, 50, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)) + logo_label.setPixmap(QPixmap(resource_path("logo.png")).scaledToWidth(250, Qt.TransformationMode.SmoothTransformation)) title_label = QLabel("US-144 MKII Control Panel") - title_label.setFont(QFont("Arial", 15, QFont.Weight.Bold)) - header_layout.addWidget(logo_label) - header_layout.addWidget(title_label) - - info_layout = QGridLayout() + title_label.setObjectName("Title") + left_panel.addWidget(logo_label) + left_panel.addWidget(title_label) + info_grid.setSpacing(5) self.info_labels = {} info_data = { - "Driver Version:": "N/A", "Device:": "US-144 MKII", - "Sample Width:": "24 bits", "Sample Rate:": "N/A", - "Sample Clock Source:": "internal", "Digital Input Status:": "unavailable" + "Driver Version:": "driver_version", "Device:": "device", + "Sample Width:": "sample_width", "Sample Rate:": "sample_rate", + "Sample Clock Source:": "clock_source", "Digital Input Status:": "digital_status" } - 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) + 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) self.info_labels[key] = value_label - - left_panel.addLayout(info_layout) + row += 1 + left_panel.addLayout(info_grid) left_panel.addStretch() - 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"]) + middle_panel = QVBoxLayout() + middle_panel.setSpacing(0) + + 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(latency_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) @@ -181,27 +227,36 @@ class TascamControlPanel(QWidget): right_panel.addStretch() right_panel.addWidget(exit_button, 0, Qt.AlignmentFlag.AlignCenter) - 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) + top_level_layout.addLayout(left_panel, 3) + top_level_layout.addLayout(middle_panel, 3) + top_level_layout.addLayout(right_panel, 3) - self.latency_combo.currentIndexChanged.connect(self.on_latency_changed) - self.routing_combo.currentIndexChanged.connect(self.on_routing_changed) + 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) def create_control_widget(self, label_text, combo_items): container_widget = QWidget() layout = QVBoxLayout(container_widget) - layout.setContentsMargins(0,0,0,0) + layout.setContentsMargins(0, 8, 0, 8) layout.setSpacing(2) - label = QLabel(label_text, font=QFont("Arial", 10, QFont.Weight.Bold)) + label = QLabel(label_text) + label.setObjectName("ControlLabel") combo_box = QComboBox() combo_box.addItems(combo_items) layout.addWidget(label) @@ -209,30 +264,29 @@ class TascamControlPanel(QWidget): return container_widget, combo_box def load_dynamic_settings(self): - driver_ver = AmixerController.read_sysfs_attr(self.card_id, "driver_version") - self.info_labels['driver_version'].setText(driver_ver) + 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") rate_val = AmixerController.get_control_value(self.card_id, "Sample Rate") - 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)") + self.info_labels['sample_rate'].setText(f"{rate_val / 1000:.1f} kHz" if rate_val > 0 else "N/A (inactive)") - 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.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 on_routing_changed(self, index): - AmixerController.set_control_value(self.card_id, "Playback Routing", index) + def set_value(self, control_name, index): + AmixerController.set_control_value(self.card_id, control_name, index) def main(): app = QApplication(sys.argv) diff --git a/us144mkii.c b/us144mkii.c index 9167755..c27d27c 100644 --- a/us144mkii.c +++ b/us144mkii.c @@ -22,13 +22,7 @@ MODULE_DESCRIPTION("ALSA Driver for TASCAM US-144MKII"); MODULE_LICENSE("GPL"); #define DRIVER_NAME "us144mkii" -#define DRIVER_VERSION "1.0" - -/* - * TODO: - * - Implement audio input capture. - * - Implement MIDI IN/OUT. - */ +#define DRIVER_VERSION "1.1" /* --- Module Parameters --- */ static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; @@ -76,6 +70,9 @@ 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 --- */ @@ -84,6 +81,12 @@ 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; @@ -92,18 +95,37 @@ 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; - - spinlock_t lock; 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; int current_rate; unsigned int latency_profile; - unsigned int playback_routing; + 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 feedback_accumulator_pattern[FEEDBACK_ACCUMULATOR_SIZE]; unsigned int feedback_pattern_out_idx; @@ -111,10 +133,6 @@ 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; @@ -125,6 +143,7 @@ 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); @@ -132,14 +151,6 @@ 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) { @@ -149,6 +160,8 @@ 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) { @@ -163,97 +176,103 @@ 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 = (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; - } + struct tascam_card *tascam = snd_kcontrol_chip(kcontrol); + ucontrol->value.enumerated.item[0] = tascam->latency_profile; return 0; } static int tascam_latency_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_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; - } - + struct tascam_card *tascam = snd_kcontrol_chip(kcontrol); + unsigned int new_profile = ucontrol->value.enumerated.item[0]; + if (new_profile >= 3) + return -EINVAL; if (tascam->latency_profile != new_profile) { tascam->latency_profile = new_profile; - changed = true; + return 1; } - return changed; + return 0; } 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 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) +static int tascam_playback_source_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 = 3; - if (uinfo->value.enumerated.item >= 3) - uinfo->value.enumerated.item = 2; - strcpy(uinfo->value.enumerated.name, playback_routing_texts[uinfo->value.enumerated.item]); + 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]); return 0; } -static int tascam_routing_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) -{ - struct tascam_card *tascam = (struct tascam_card *)snd_kcontrol_chip(kcontrol); - ucontrol->value.enumerated.item[0] = tascam->playback_routing; - 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_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_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; } - if (new_routing >= 3) - return -EINVAL; - - 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, +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) +{ + 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]); + 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_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; } + +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, }; -/** - * 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; @@ -263,17 +282,6 @@ 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); @@ -311,7 +319,6 @@ 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}, @@ -349,10 +356,6 @@ 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; @@ -378,13 +381,21 @@ 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; @@ -433,6 +444,31 @@ 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: @@ -441,27 +477,55 @@ error: return -ENOMEM; } -static int tascam_pcm_open(struct snd_pcm_substream *substream) +static int tascam_playback_open(struct snd_pcm_substream *substream) { struct tascam_card *tascam = snd_pcm_substream_chip(substream); - struct snd_pcm_runtime *runtime = substream->runtime; - int err; + int err = 0; - runtime->hw = tascam_pcm_hw; + substream->runtime->hw = tascam_pcm_hw; tascam->playback_substream = substream; atomic_set(&tascam->playback_active, 0); - err = tascam_alloc_urbs(tascam); - if (err < 0) - return err; - + if (!tascam->capture_substream) { + err = tascam_alloc_urbs(tascam); + if (err < 0) + return err; + } return 0; } -static int tascam_pcm_close(struct snd_pcm_substream *substream) +static int tascam_capture_open(struct snd_pcm_substream *substream) { struct tascam_card *tascam = snd_pcm_substream_chip(substream); - tascam_free_urbs(tascam); + 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); return 0; } @@ -533,29 +597,29 @@ static int tascam_pcm_hw_params(struct snd_pcm_substream *substream, if (err < 0) return err; - /* 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; + 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; + } } - /* 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) { @@ -573,7 +637,7 @@ static int tascam_pcm_hw_free(struct snd_pcm_substream *substream) return snd_pcm_lib_free_pages(substream); } -static int tascam_pcm_prepare(struct snd_pcm_substream *substream) +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; @@ -582,7 +646,6 @@ static int tascam_pcm_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; @@ -591,32 +654,20 @@ static int tascam_pcm_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 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; + case 0: feedback_packets = 1; break; + case 1: feedback_packets = 2; break; + case 2: feedback_packets = 5; break; + default: 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++) { @@ -625,13 +676,11 @@ static int tascam_pcm_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; @@ -644,7 +693,7 @@ static int tascam_pcm_prepare(struct snd_pcm_substream *substream) return 0; } -static int tascam_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +static int tascam_playback_trigger(struct snd_pcm_substream *substream, int cmd) { struct tascam_card *tascam = snd_pcm_substream_chip(substream); int err = 0, i; @@ -689,7 +738,7 @@ rollback: return err; } -static snd_pcm_uframes_t tascam_pcm_pointer(struct snd_pcm_substream *substream) +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; @@ -707,18 +756,91 @@ static snd_pcm_uframes_t tascam_pcm_pointer(struct snd_pcm_substream *substream) } static struct snd_pcm_ops tascam_playback_ops = { - .open = tascam_pcm_open, .close = tascam_pcm_close, + .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_pcm_prepare, - .trigger = tascam_pcm_trigger, .pointer = tascam_pcm_pointer, + .hw_free = tascam_pcm_hw_free, .prepare = tascam_playback_prepare, + .trigger = tascam_playback_trigger, .pointer = tascam_playback_pointer, }; -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 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 struct snd_pcm_ops tascam_capture_ops = { - .open = tascam_capture_open_stub, .close = tascam_capture_close_stub, + .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, }; +/** + * 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; @@ -742,7 +864,6 @@ 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; @@ -761,15 +882,12 @@ 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; @@ -780,20 +898,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); - 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; - } + 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); } } @@ -803,6 +921,13 @@ 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; @@ -823,20 +948,18 @@ 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; /* Initialize to a known invalid value */ + 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); @@ -846,7 +969,6 @@ 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++) { @@ -855,10 +977,9 @@ 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) /* Only log if the packet itself was ok but the value was not */ + if (packet_ok) dev_warn_ratelimited(tascam->card->dev, "Invalid feedback value %u, using nominal rate.\n", feedback_value); for (i = 0; i < 8; i++) { @@ -867,14 +988,12 @@ 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; @@ -892,6 +1011,169 @@ 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; @@ -902,16 +1184,17 @@ 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_routing_control, tascam)); - if (err < 0) - return err; - + 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; 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; @@ -966,7 +1249,6 @@ 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); @@ -978,7 +1260,6 @@ 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); @@ -992,6 +1273,11 @@ 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); @@ -1022,10 +1308,13 @@ 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); - /* 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 */ + + 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; strscpy(card->driver, DRIVER_NAME, sizeof(card->driver)); strscpy(card->shortname, "TASCAM US-144MKII", sizeof(card->shortname)); @@ -1098,6 +1387,10 @@ 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); @@ -1119,7 +1412,6 @@ 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--;