ALSA: us144mkii: Refactor for improved low-latency performance

Refactor the TASCAM US-144MKII driver to improve performance and
stability for low-latency audio applications.

The key changes include:
- Improved stream and URB management for more reliable audio streaming.
- Reworked synchronization logic to prevent race conditions.
This commit is contained in:
Šerif Rami 2025-12-01 09:55:14 +01:00
parent 1670d814ab
commit cceb012fe3
25 changed files with 970 additions and 3197 deletions

1
.gitignore vendored
View File

@ -55,3 +55,4 @@ dkms.conf
*.cmake
/tascam_controls/build
/tascam_controls/.tools
raw_jack_us144mkii/jack_tascam

View File

@ -358,7 +358,6 @@ Generated code
* **Note:** The pattern tables provided are specifically for 48kHz. To support other sample rates (44.1kHz, 88.2kHz, 96kHz), the corresponding `fpoInitPattern` calls from the macOS driver's `PGFramePatternObserver::init` would need to be analyzed to derive their specific pattern tables. The `mExpectedFramesPerMicroframe` (`a1 + 24`) and `mExpectedFeedbackValue` (`a1 + 36`) would change based on the sample rate.
IGNORE_WHEN_COPYING_START
Use code with caution.
@ -447,7 +446,7 @@ Normal 128 smp (2.7ms) 2 ms (2 packets/URB)
High 256 smp (5.3ms) 5 ms (5 packets/URB)
Highest 512 smp (10.7ms) 5 ms (5 packets/URB)
Table 3: 88.2kHz Latency Settings (Placeholder)
Table 3: 88.2kHz Latency Settings
Latency Profile ASIO Buffer (Reported) Hardware Feedback (Observed)

View File

@ -1,5 +1,5 @@
obj-m += snd-usb-us144mkii.o
snd-usb-us144mkii-y := us144mkii.o us144mkii_pcm.o us144mkii_playback.o us144mkii_capture.o us144mkii_midi.o us144mkii_controls.o
snd-usb-us144mkii-y := us144mkii.o us144mkii_pcm.o us144mkii_playback.o us144mkii_capture.o us144mkii_midi.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

105
README.md
View File

@ -4,13 +4,7 @@ An unofficial ALSA kernel module for the TASCAM US-144MKII USB audio interface.
## 📢 Project Status
**Upstreamed** — This driver has been merged into the [`sound/for-next`](https://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git/log/?h=for-next) branch for inclusion in an upcoming Linux kernel release.
📦 This repository will be **archived** once the driver appears in the official kernel. Until then, the out-of-tree version here can still be built and tested.
## ❗ Current Status: Work in Progress
This driver is under active development.
*--- OLD VERSION --- ✅ **Upstreamed** — This driver has been merged into the [`sound/for-next`](https://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound.git/log/?h=for-next) branch for inclusion in an upcoming Linux kernel release.
### ✅ Implemented Features
* **Audio Playback:**
@ -18,7 +12,6 @@ This driver is under active development.
* **MIDI IN/OUT:**
### 📝 To-Do & Known Limitations
* Find Bugs, if possible improve performance/stablity
* *MIDI IN/OUT works only in active audio streaming(DAW ALSA/JACK or browser audio)
* Non MKII US-144 needs testing to see if the driver will work with it.
@ -26,6 +19,15 @@ This driver is under active development.
This is an out-of-tree kernel module, meaning you must compile it against the headers for your specific kernel version.
For Arch users, a community-maintained DKMS package is available in AUR.
Install it via:
```bash
paru -S us144mkii-dkms-git
```
or if you are using yay:
```bash
yay -S us144mkii-dkms-git
```
### Step 1: Blacklist the Stock `snd-usb-us122l` Driver
The standard kernel includes a driver that will conflict with our custom module. You must prevent it from loading.
@ -115,17 +117,17 @@ cd us144mkii/
4. Connect your TASCAM US-144MKII. Verify that the driver loaded and the audio card is recognized by the system:
```bash
# Check if the kernel module is loaded
lsmod | grep us144mkii
lsmod | grep snd_usb_us144mkii
# Check if ALSA sees the new sound card
aplay -l
```
The first command should show `us144mkii`. The second command should list your "TASCAM US-144MKII" as an available playback device. You should now be able to select it in your audio settings and play sound.
The first command should show `snd_usb_us144mkii`. The second command should list your "TASCAM US-144MKII" as an available playback device. You should now be able to select it in your audio settings and play sound.
### Step 4: Install for Automatic Loading on Boot
To make the driver load automatically every time you start your computer, follow these steps after you have successfully compiled it in Step 3.
You can use build_install script to do automate this process just `sudo chmod +x build_install.sh` before you run it with `./build_install.sh` or just do it
You can use build_and_install script to do automate this process just `sudo chmod +x build_and_install.sh` before you run it with `./build_and_install.sh` or just do it
the manual way.
1. **Copy the compiled module to the kernel's extra modules directory.** This makes it available to system tools.
@ -141,87 +143,6 @@ the manual way.
Now, after a reboot, the `us144mkii` driver should load automatically.
### Tascam Control Panel
<img width="543" height="480" alt="image" src="https://github.com/user-attachments/assets/43981c8d-c59e-4d43-b1c8-33e512085219" />
A control panel app built with Qt6 and ALSA.
Get it from releases or build it.
## Prerequisites
Before building the application, ensure you have the following installed on your system:
* **CMake** (version 3.16 or higher)
* **C++ Compiler** (supporting C++17, e.g., GCC/G++)
* **Qt6 Development Libraries** (specifically the `Widgets` module)
* **ALSA Development Libraries**
* **Make** (or Ninja)
### Installation of Prerequisites by Distribution
#### Debian/Ubuntu
sudo apt update
sudo apt install cmake build-essential qt6-base-dev qt6-base-dev-tools libasound2-dev
#### Fedora/RHEL/CentOS
sudo dnf install cmake "Development Tools" qt6-qtbase-devel alsa-lib-devel
#### Arch Linux
sudo pacman -Syu
sudo pacman -S cmake base-devel qt6-base alsa-lib
#### openSUSE
sudo zypper install cmake gcc-c++ libqt6-qtbase-devel alsa-devel
## Building the Application
Follow these steps to build the `TascamControlPanel` application from source:
1. **Clone the repository** (if you haven't already):
```git clone https://github.com/serifpersia/us144mkii.git```
```cd tascam_controls/```
2. **Create a build directory** and navigate into it:
```mkdir build```
```cd build```
4. **Configure the project** with CMake:
```cmake ..```
This step will check for all necessary dependencies and generate the build files.
5. **Build the application**:
```make -j$(nproc)```
This command compiles the source code. The -j$(nproc) option uses all available CPU cores to speed up the compilation process.
## Running the Application
After a successful build, the executable will be located in the `build` directory.
```./TascamControlPanel```
## Cleaning the Build
To remove all compiled files and intermediate artifacts, simply delete the `build` directory:
cd ..
rm -rf build
## Reporting Issues & Feedback
If you test this driver, please share your feedback to help improve it. Include:

View File

@ -1,29 +0,0 @@
cmake_minimum_required(VERSION 3.16)
project(TascamControlPanel LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
find_package(Qt6 6.2 COMPONENTS Widgets REQUIRED)
find_package(ALSA REQUIRED)
add_executable(TascamControlPanel
src/main.cpp
src/mainwindow.h
src/mainwindow.cpp
src/alsacontroller.h
src/alsacontroller.cpp
resources/resources.qrc
)
target_link_libraries(TascamControlPanel PRIVATE
Qt6::Widgets
ALSA::ALSA
)
install(TARGETS TascamControlPanel
RUNTIME DESTINATION bin
)

View File

@ -1,74 +0,0 @@
#!/bin/bash
set -e
APP_NAME="TascamControlPanel"
PROJECT_DIR=$(pwd)
BUILD_DIR="${PROJECT_DIR}/build"
TOOLS_DIR="${PROJECT_DIR}/.tools"
LINUXDEPLOY_FILENAME="linuxdeploy-xnormal.AppImage"
LINUXDEPLOY_PATH="${TOOLS_DIR}/${LINUXDEPLOY_FILENAME}"
LINUXDEPLOY_URL="https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage"
QT_PLUGIN_FILENAME="linuxdeploy-plugin-qt"
QT_PLUGIN_PATH="${TOOLS_DIR}/${QT_PLUGIN_FILENAME}"
QT_PLUGIN_URL="https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage"
echo "--- Checking for deployment tools ---"
mkdir -p "${TOOLS_DIR}"
if [ ! -f "${LINUXDEPLOY_PATH}" ]; then
echo "linuxdeploy not found. Downloading..."
wget -c -O "${LINUXDEPLOY_PATH}" "${LINUXDEPLOY_URL}"
chmod +x "${LINUXDEPLOY_PATH}"
fi
if [ ! -f "${QT_PLUGIN_PATH}" ]; then
echo "linuxdeploy-plugin-qt not found. Downloading..."
wget -c -O "${QT_PLUGIN_PATH}" "${QT_PLUGIN_URL}"
chmod +x "${QT_PLUGIN_PATH}"
fi
echo "All tools are ready."
echo "--- Building the C++ application ---"
mkdir -p ${BUILD_DIR}
cd ${BUILD_DIR}
cmake ..
make -j$(nproc)
cd ${PROJECT_DIR}
echo "--- Running linuxdeploy to create the AppImage ---"
rm -rf AppDir
export NO_STRIP=1
export PATH="${TOOLS_DIR}:${PATH}"
echo "--- Detecting Qt6 qmake ---"
if command -v qmake6 &> /dev/null; then
export QMAKE=$(command -v qmake6)
elif command -v qt6-qmake &> /dev/null; then
export QMAKE=$(command -v qt6-qmake)
elif command -v qmake &> /dev/null && qmake -v | grep -q "Qt version 6"; then
export QMAKE=$(command -v qmake)
else
echo "ERROR: Could not find a Qt6 qmake executable."
echo "Please install the Qt6 development package for your distribution."
echo "(e.g., 'sudo pacman -S qt6-base' or 'sudo apt install qt6-base-dev')"
exit 1
fi
echo "Found qmake at: ${QMAKE}"
"${LINUXDEPLOY_PATH}" --appdir AppDir \
-e "${BUILD_DIR}/${APP_NAME}" \
-i "${PROJECT_DIR}/resources/tascam-control-panel.png" \
-d "${PROJECT_DIR}/tascam-control-panel.desktop" \
--plugin qt \
--output appimage
echo ""
echo "--- DONE ---"
echo "AppImage created successfully!"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -1,9 +0,0 @@
<!DOCTYPE RCC>
<RCC version="1.0">
<qresource prefix="/">
<file>bg.png</file>
<file>logo.png</file>
<file>device.png</file>
<file>tascam-control-panel.png</file>
</qresource>
</RCC>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 349 KiB

View File

@ -1,130 +0,0 @@
#include "alsacontroller.h"
#include <alsa/asoundlib.h>
#include <fstream>
#include <iostream>
#include <vector>
AlsaController::AlsaController(const std::vector<std::string>& target_card_names)
{
int card = -1;
if (snd_card_next(&card) < 0 || card < 0) {
std::cerr << "No sound cards found." << std::endl;
return;
}
while (card >= 0) {
char* long_name = nullptr;
snd_card_get_longname(card, &long_name);
if (long_name) {
bool found_match_and_freed = false;
for (const auto& name : target_card_names) {
if (std::string(long_name).find(name) != std::string::npos) {
m_card_num = card;
m_card_id_str = "hw:" + std::to_string(card);
m_card_found = true;
free(long_name);
found_match_and_freed = true;
break;
}
}
if (!found_match_and_freed) {
free(long_name);
}
}
if (m_card_found) break;
if (snd_card_next(&card) < 0) {
break;
}
}
if (!m_card_found) {
std::cerr << "Target sound card(s) not found." << std::endl;
}
}
std::optional<std::string> AlsaController::getCardId() const {
if (m_card_found) {
return m_card_id_str;
}
return std::nullopt;
}
int AlsaController::getCardNumber() const {
return m_card_num;
}
bool AlsaController::isCardFound() const {
return m_card_found;
}
long AlsaController::getControlValue(const std::string& control_name) {
if (!m_card_found) return 0;
snd_ctl_t *handle;
if (snd_ctl_open(&handle, m_card_id_str.c_str(), 0) < 0) return 0;
snd_ctl_elem_id_t *id;
snd_ctl_elem_value_t *control;
snd_ctl_elem_id_alloca(&id);
snd_ctl_elem_value_alloca(&control);
snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
snd_ctl_elem_id_set_name(id, control_name.c_str());
snd_ctl_elem_value_set_id(control, id);
if (snd_ctl_elem_read(handle, control) < 0) {
snd_ctl_close(handle);
return 0;
}
long value = snd_ctl_elem_value_get_integer(control, 0);
snd_ctl_close(handle);
return value;
}
bool AlsaController::setControlValue(const std::string& control_name, long value) {
if (!m_card_found) return false;
snd_ctl_t *handle;
if (snd_ctl_open(&handle, m_card_id_str.c_str(), 0) < 0) return false;
snd_ctl_elem_id_t *id;
snd_ctl_elem_value_t *control;
snd_ctl_elem_id_alloca(&id);
snd_ctl_elem_value_alloca(&control);
snd_ctl_elem_id_set_interface(id, SND_CTL_ELEM_IFACE_MIXER);
snd_ctl_elem_id_set_name(id, control_name.c_str());
snd_ctl_elem_value_set_id(control, id);
if (snd_ctl_elem_read(handle, control) < 0) {
snd_ctl_close(handle);
return false;
}
snd_ctl_elem_value_set_integer(control, 0, value);
if (snd_ctl_elem_write(handle, control) < 0) {
snd_ctl_close(handle);
return false;
}
snd_ctl_close(handle);
return true;
}
std::string AlsaController::readSysfsAttr(const std::string& attr_name) {
if (!m_card_found) return "N/A";
std::string path = "/sys/class/sound/card" + std::to_string(m_card_num) + "/device/" + attr_name;
std::ifstream file(path);
if (file.is_open()) {
std::string line;
std::getline(file, line);
return line;
}
return "N/A";
}

View File

@ -1,27 +0,0 @@
#ifndef ALSACONTROLLER_H
#define ALSACONTROLLER_H
#include <optional>
#include <string>
#include <vector>
class AlsaController {
public:
AlsaController(const std::vector<std::string> &target_card_names = {
"US-144MKII", "US-144"});
std::optional<std::string> getCardId() const;
int getCardNumber() const;
bool isCardFound() const;
long getControlValue(const std::string &control_name);
bool setControlValue(const std::string &control_name, long value);
std::string readSysfsAttr(const std::string &attr_name);
private:
std::string m_card_id_str;
int m_card_num = -1;
bool m_card_found = false;
};
#endif

View File

@ -1,12 +0,0 @@
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow window;
window.show();
return app.exec();
}

View File

@ -1,279 +0,0 @@
#include "mainwindow.h"
#include <QApplication>
#include <QDialog>
#include <QDialogButtonBox>
#include <QMessageBox>
#include <QPainter>
#include <QDebug>
#include <QTimer>
#include <QLabel>
#include <QComboBox>
#include <QPushButton>
#include <QGridLayout>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QIcon>
const QString DARK_STYLESHEET = R"(
QWidget {
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: rgba(10, 10, 20, 0.25);
border: 1px solid #3A4760;
border-radius: 4px;
padding: 4px;
color: #DAE0ED;
}
QComboBox:hover {
background-color: rgba(15, 15, 25, 0.35);
border: 1px solid #6482B4;
}
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;
}
QPushButton {
background-color: rgba(10, 10, 20, 0.25);
border: 1px solid #3A4760;
border-radius: 4px;
padding: 5px;
color: #92E8FF;
}
QPushButton:hover {
background-color: rgba(15, 15, 25, 0.35);
border: 1px solid #6482B4;
}
QPushButton:pressed {
background-color: rgba(20, 20, 30, 0.45);
border: 1px solid #A020F0;
}
)";
MainWindow::MainWindow(QWidget *parent)
: QWidget(parent)
, m_alsa()
, m_aboutDialog(nullptr)
{
if (!m_alsa.isCardFound()) {
QMessageBox::critical(this, "Error", "TASCAM US-144/US-144MKII Not Found.\nPlease ensure the device is connected and the 'us144mkii' driver is loaded.");
QTimer::singleShot(0, this, &QWidget::close);
return;
}
initUi();
loadDynamicSettings();
}
void MainWindow::initUi() {
setWindowTitle("TASCAM US-144MKII Control Panel");
setWindowIcon(QIcon(":/tascam-control-panel.png"));
setFixedSize(550, 450);
setStyleSheet(DARK_STYLESHEET);
m_background.load(":/bg.png");
if (m_background.isNull()) {
qWarning() << "Failed to load background image from resources!";
}
auto *topLevelLayout = new QHBoxLayout(this);
topLevelLayout->setContentsMargins(20, 20, 20, 20);
topLevelLayout->setSpacing(25);
auto *leftPanel = new QVBoxLayout();
auto *middlePanel = new QVBoxLayout();
auto *logoLabel = new QLabel();
logoLabel->setPixmap(QPixmap(":/logo.png").scaledToWidth(250, Qt::SmoothTransformation));
auto *titleLabel = new QLabel("US-144 MKII Control Panel");
titleLabel->setObjectName("Title");
auto *infoGrid = new QGridLayout();
infoGrid->setSpacing(5);
const QMap<QString, QString> infoData = {
{"Device:", "device"},
{"Sample Width:", "sample_width"}, {"Sample Rate:", "sample_rate"}
};
int row = 0;
for (auto it = infoData.constBegin(); it != infoData.constEnd(); ++it) {
auto *label = new QLabel(it.key());
label->setFont(QFont("Arial", 9, QFont::Bold));
auto *valueLabel = new QLabel("N/A");
valueLabel->setFont(QFont("Arial", 9));
infoGrid->addWidget(label, row, 0);
infoGrid->addWidget(valueLabel, row, 1);
m_infoLabels[it.value()] = valueLabel;
row++;
}
auto *deviceImageLabel = new QLabel();
deviceImageLabel->setPixmap(QPixmap(":/device.png").scaled(250, 250, Qt::KeepAspectRatio, Qt::SmoothTransformation));
deviceImageLabel->setAlignment(Qt::AlignCenter);
leftPanel->addWidget(logoLabel);
leftPanel->addWidget(titleLabel);
leftPanel->addLayout(infoGrid);
leftPanel->addStretch();
leftPanel->addWidget(deviceImageLabel);
middlePanel->setSpacing(0);
auto addSection = [&](const QString& title, QWidget* widget) {
auto* header = new QLabel(title);
header->setObjectName("SectionHeader");
middlePanel->addWidget(header);
middlePanel->addWidget(widget);
};
auto capture12Pair = createControlWidget("Ch1/2 Capture Source", {"Analog In", "Digital In"});
m_capture12Combo = capture12Pair.second;
m_capture12Combo->setToolTip("Select the source for capture channels 1 and 2.");
auto capture34Pair = createControlWidget("Ch3/4 Capture Source", {"Analog In", "Digital In"});
m_capture34Combo = capture34Pair.second;
m_capture34Combo->setToolTip("Select the source for capture channels 3 and 4.");
addSection("INPUTS", capture12Pair.first);
middlePanel->addWidget(capture34Pair.first);
auto lineOutPair = createControlWidget("Line Playback Source", {"Playback 1-2", "Playback 3-4"});
m_lineOutCombo = lineOutPair.second;
m_lineOutCombo->setToolTip("Select the source for the line outputs.");
addSection("LINE OUTPUTS", lineOutPair.first);
auto digitalOutPair = createControlWidget("Digital Playback Source", {"Playback 1-2", "Playback 3-4"});
m_digitalOutCombo = digitalOutPair.second;
m_digitalOutCombo->setToolTip("Select the source for the digital outputs.");
addSection("DIGITAL OUTPUTS", digitalOutPair.first);
middlePanel->addStretch();
auto *buttonLayout = new QHBoxLayout();
auto *aboutButton = new QPushButton("About");
aboutButton->setFixedSize(100, 30);
connect(aboutButton, &QPushButton::clicked, this, &MainWindow::showAboutDialog);
buttonLayout->addWidget(aboutButton);
middlePanel->addLayout(buttonLayout);
topLevelLayout->addLayout(leftPanel, 1);
topLevelLayout->addLayout(middlePanel, 1);
connect(m_lineOutCombo, &QComboBox::currentIndexChanged, this, [this](int index){ onControlChanged("Line Playback Source", index, m_lineOutCombo); });
connect(m_digitalOutCombo, &QComboBox::currentIndexChanged, this, [this](int index){ onControlChanged("Digital Playback Source", index, m_digitalOutCombo); });
connect(m_capture12Combo, &QComboBox::currentIndexChanged, this, [this](int index){ onControlChanged("Ch1/2 Capture Source", index, m_capture12Combo); });
connect(m_capture34Combo, &QComboBox::currentIndexChanged, this, [this](int index){ onControlChanged("Ch3/4 Capture Source", index, m_capture34Combo); });
}
void MainWindow::loadDynamicSettings() {
m_infoLabels["device"]->setText("US-144 MKII");
m_infoLabels["sample_width"]->setText("24 bits");
long rate_val = m_alsa.getControlValue("Sample Rate");
m_infoLabels["sample_rate"]->setText(rate_val > 0 ? QString("%1 kHz").arg(rate_val / 1000.0, 0, 'f', 1) : "N/A (inactive)");
updateCombo(m_lineOutCombo, "Line Playback Source");
updateCombo(m_digitalOutCombo, "Digital Playback Source");
updateCombo(m_capture12Combo, "Ch1/2 Capture Source");
updateCombo(m_capture34Combo, "Ch3/4 Capture Source");
}
void MainWindow::updateCombo(QComboBox* combo, const std::string& controlName) {
long value = m_alsa.getControlValue(controlName);
combo->blockSignals(true);
combo->setCurrentIndex(static_cast<int>(value));
combo->blockSignals(false);
}
void MainWindow::onControlChanged(const std::string& controlName, int index, QComboBox* combo) {
m_alsa.setControlValue(controlName, index);
if (combo) {
QString originalStyle = combo->styleSheet();
combo->setStyleSheet(originalStyle + " QComboBox { border: 1px solid #A020F0; }");
QTimer::singleShot(200, [combo, originalStyle]() {
combo->setStyleSheet(originalStyle);
});
}
}
std::pair<QWidget*, QComboBox*> MainWindow::createControlWidget(const QString& labelText, const QStringList& items) {
auto *container = new QWidget();
auto *layout = new QVBoxLayout(container);
layout->setContentsMargins(0, 8, 0, 8);
layout->setSpacing(2);
auto *label = new QLabel(labelText);
label->setObjectName("ControlLabel");
auto *combo = new QComboBox();
combo->addItems(items);
layout->addWidget(label);
layout->addWidget(combo);
return {container, combo};
}
void MainWindow::showAboutDialog() {
if (m_aboutDialog && m_aboutDialog->isVisible()) {
m_aboutDialog->raise();
m_aboutDialog->activateWindow();
return;
}
if (!m_aboutDialog) {
m_aboutDialog = new QDialog(this);
m_aboutDialog->setWindowTitle("About TASCAM US-144MKII Control Panel");
m_aboutDialog->setWindowIcon(QIcon(":/tascam-control-panel.png"));
m_aboutDialog->setFixedSize(400, 250);
auto *layout = new QVBoxLayout(m_aboutDialog);
auto *textLabel = new QLabel(m_aboutDialog);
textLabel->setTextFormat(Qt::RichText);
textLabel->setOpenExternalLinks(true);
textLabel->setText(QString("<b>TASCAM US-144MKII Control Panel</b><br>"
"Copyright @serifpersia 2025<br><br>"
"This application provides a graphical interface to control the TASCAM US-144MKII audio interface on Linux. "
"It utilizes the 'us144mkii' ALSA driver.<br><br>"
"For more information, bug reports, and contributions, please visit the GitHub repository:<br>"
"<a href='https://github.com/serifpersia/us144mkii'>https://github.com/serifpersia/us144mkii</a>"));
textLabel->setWordWrap(true);
layout->addWidget(textLabel);
}
m_aboutDialog->show();
}
void MainWindow::paintEvent(QPaintEvent *event) {
QPainter painter(this);
if (!m_background.isNull()) {
painter.drawPixmap(this->rect(), m_background);
}
QWidget::paintEvent(event);
}

View File

@ -1,47 +0,0 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include "alsacontroller.h"
#include <QMap>
#include <QPixmap>
#include <QWidget>
class QLabel;
class QComboBox;
class QPushButton;
class QDialog;
class MainWindow : public QWidget {
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
protected:
void paintEvent(QPaintEvent *event) override;
private:
void initUi();
void loadDynamicSettings();
std::pair<QWidget *, QComboBox *>
createControlWidget(const QString &labelText, const QStringList &items);
void updateCombo(QComboBox *combo, const std::string &controlName);
private slots:
void onControlChanged(const std::string &controlName, int index,
QComboBox *combo);
void showAboutDialog();
private:
AlsaController m_alsa;
QPixmap m_background;
QMap<QString, QLabel *> m_infoLabels;
QComboBox *m_capture12Combo;
QComboBox *m_capture34Combo;
QComboBox *m_lineOutCombo;
QComboBox *m_digitalOutCombo;
QDialog *m_aboutDialog;
};
#endif

View File

@ -1,6 +0,0 @@
[Desktop Entry]
Name=TASCAM US-144MKII Control Panel
Exec=TascamControlPanel
Icon=tascam-control-panel
Type=Application
Categories=AudioVideo;Audio;

View File

@ -1,8 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2025 Šerif Rami <ramiserifpersia@gmail.com>
/*
* ALSA Driver for TASCAM US-144MKII Audio Interface
*/
#include "us144mkii.h"
@ -10,128 +7,98 @@ MODULE_AUTHOR("Šerif Rami <ramiserifpersia@gmail.com>");
MODULE_DESCRIPTION("ALSA Driver for TASCAM US-144MKII");
MODULE_LICENSE("GPL");
/**
* @brief Module parameters for ALSA card instantiation.
*
* These parameters allow users to configure how the ALSA sound card
* for the TASCAM US-144MKII is instantiated.
*
* @param index: Array of integers specifying the ALSA card index for each
* device. Defaults to -1 (automatic).
* @param id: Array of strings specifying the ALSA card ID for each device.
* Defaults to "US144MKII".
* @param enable: Array of booleans to enable or disable each device.
* Defaults to {1, 0, ..., 0} (first device enabled).
* @param dev_idx: Internal counter for the number of TASCAM devices probed.
*/
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static bool enable[SNDRV_CARDS] = { 1, [1 ...(SNDRV_CARDS - 1)] = 0 };
static int dev_idx;
static atomic_t dev_idx = ATOMIC_INIT(0);
static int tascam_probe(struct usb_interface *intf,
const struct usb_device_id *usb_id);
const struct usb_device_id *usb_id);
static void tascam_disconnect(struct usb_interface *intf);
static int tascam_suspend(struct usb_interface *intf, pm_message_t message);
static int tascam_resume(struct usb_interface *intf);
/**
* tascam_free_urbs - free all URBs
* @tascam: the tascam_card instance
*/
void tascam_free_urbs(struct tascam_card *tascam)
{
int i;
usb_kill_anchored_urbs(&tascam->playback_anchor);
usb_kill_anchored_urbs(&tascam->feedback_anchor);
usb_kill_anchored_urbs(&tascam->capture_anchor);
usb_kill_anchored_urbs(&tascam->midi_anchor);
for (i = 0; i < NUM_PLAYBACK_URBS; i++) {
if (tascam->playback_urbs[i]) {
usb_free_coherent(
tascam->dev, tascam->playback_urb_alloc_size,
tascam->playback_urbs[i]->transfer_buffer,
tascam->playback_urbs[i]->transfer_dma);
usb_free_coherent(tascam->dev, tascam->playback_urb_alloc_size,
tascam->playback_urbs[i]->transfer_buffer,
tascam->playback_urbs[i]->transfer_dma);
usb_free_urb(tascam->playback_urbs[i]);
tascam->playback_urbs[i] = NULL;
}
}
usb_kill_anchored_urbs(&tascam->feedback_anchor);
for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
if (tascam->feedback_urbs[i]) {
usb_free_coherent(
tascam->dev, tascam->feedback_urb_alloc_size,
tascam->feedback_urbs[i]->transfer_buffer,
tascam->feedback_urbs[i]->transfer_dma);
usb_free_coherent(tascam->dev, tascam->feedback_urb_alloc_size,
tascam->feedback_urbs[i]->transfer_buffer,
tascam->feedback_urbs[i]->transfer_dma);
usb_free_urb(tascam->feedback_urbs[i]);
tascam->feedback_urbs[i] = NULL;
}
}
usb_kill_anchored_urbs(&tascam->capture_anchor);
for (i = 0; i < NUM_CAPTURE_URBS; i++) {
if (tascam->capture_urbs[i]) {
usb_free_coherent(
tascam->dev, tascam->capture_urb_alloc_size,
tascam->capture_urbs[i]->transfer_buffer,
tascam->capture_urbs[i]->transfer_dma);
usb_free_coherent(tascam->dev, CAPTURE_PACKET_SIZE,
tascam->capture_urbs[i]->transfer_buffer,
tascam->capture_urbs[i]->transfer_dma);
usb_free_urb(tascam->capture_urbs[i]);
tascam->capture_urbs[i] = NULL;
}
}
usb_kill_anchored_urbs(&tascam->midi_in_anchor);
for (i = 0; i < NUM_MIDI_IN_URBS; i++) {
if (tascam->midi_in_urbs[i]) {
usb_free_coherent(
tascam->dev, MIDI_IN_BUF_SIZE,
tascam->midi_in_urbs[i]->transfer_buffer,
tascam->midi_in_urbs[i]->transfer_dma);
usb_free_urb(tascam->midi_in_urbs[i]);
tascam->midi_in_urbs[i] = NULL;
}
if (tascam->midi_out_urb) {
usb_free_coherent(tascam->dev, MIDI_PACKET_SIZE,
tascam->midi_out_buf,
tascam->midi_out_urb->transfer_dma);
usb_free_urb(tascam->midi_out_urb);
tascam->midi_out_urb = NULL;
}
usb_kill_anchored_urbs(&tascam->midi_out_anchor);
for (i = 0; i < NUM_MIDI_OUT_URBS; i++) {
if (tascam->midi_out_urbs[i]) {
usb_free_coherent(
tascam->dev, MIDI_OUT_BUF_SIZE,
tascam->midi_out_urbs[i]->transfer_buffer,
tascam->midi_out_urbs[i]->transfer_dma);
usb_free_urb(tascam->midi_out_urbs[i]);
tascam->midi_out_urbs[i] = NULL;
}
if (tascam->midi_in_urb) {
usb_free_coherent(tascam->dev, MIDI_PACKET_SIZE,
tascam->midi_in_buf,
tascam->midi_in_urb->transfer_dma);
usb_free_urb(tascam->midi_in_urb);
tascam->midi_in_urb = NULL;
}
kfree(tascam->capture_routing_buffer);
tascam->capture_routing_buffer = NULL;
kfree(tascam->capture_decode_dst_block);
tascam->capture_decode_dst_block = NULL;
kfree(tascam->capture_decode_raw_block);
tascam->capture_decode_raw_block = NULL;
kfree(tascam->capture_ring_buffer);
tascam->capture_ring_buffer = NULL;
}
/**
* tascam_alloc_urbs - allocate all URBs
* @tascam: the tascam_card instance
*
* Return: 0 on success, or a negative error code on failure.
*/
int tascam_alloc_urbs(struct tascam_card *tascam)
{
int i;
size_t max_packet_size;
max_packet_size = ((96000 / 8000) + 2) * BYTES_PER_FRAME;
tascam->playback_urb_alloc_size =
max_packet_size * PLAYBACK_URB_PACKETS;
tascam->playback_urb_alloc_size = PLAYBACK_URB_PACKETS * (12 + 2) * BYTES_PER_FRAME;
for (i = 0; i < NUM_PLAYBACK_URBS; i++) {
struct urb *urb =
usb_alloc_urb(PLAYBACK_URB_PACKETS, GFP_KERNEL);
struct urb *urb = usb_alloc_urb(PLAYBACK_URB_PACKETS, GFP_KERNEL);
if (!urb)
goto error;
return -ENOMEM;
tascam->playback_urbs[i] = urb;
urb->transfer_buffer = usb_alloc_coherent(
tascam->dev, tascam->playback_urb_alloc_size,
GFP_KERNEL, &urb->transfer_dma);
urb->transfer_buffer = usb_alloc_coherent(tascam->dev,
tascam->playback_urb_alloc_size,
GFP_KERNEL, &urb->transfer_dma);
if (!urb->transfer_buffer)
goto error;
return -ENOMEM;
urb->dev = tascam->dev;
urb->pipe = usb_sndisocpipe(tascam->dev, EP_AUDIO_OUT);
urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
@ -140,150 +107,67 @@ int tascam_alloc_urbs(struct tascam_card *tascam)
urb->complete = playback_urb_complete;
}
tascam->feedback_urb_alloc_size =
FEEDBACK_PACKET_SIZE * FEEDBACK_URB_PACKETS;
tascam->feedback_urb_alloc_size = FEEDBACK_URB_PACKETS * FEEDBACK_PACKET_SIZE;
for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
struct urb *f_urb =
usb_alloc_urb(FEEDBACK_URB_PACKETS, GFP_KERNEL);
struct urb *urb = usb_alloc_urb(FEEDBACK_URB_PACKETS, GFP_KERNEL);
if (!f_urb)
goto error;
tascam->feedback_urbs[i] = f_urb;
f_urb->transfer_buffer = usb_alloc_coherent(
tascam->dev, tascam->feedback_urb_alloc_size,
GFP_KERNEL, &f_urb->transfer_dma);
if (!f_urb->transfer_buffer)
goto error;
f_urb->dev = tascam->dev;
f_urb->pipe =
usb_rcvisocpipe(tascam->dev, EP_PLAYBACK_FEEDBACK);
f_urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
f_urb->interval = 4;
f_urb->context = tascam;
f_urb->complete = feedback_urb_complete;
if (!urb)
return -ENOMEM;
tascam->feedback_urbs[i] = urb;
urb->transfer_buffer = usb_alloc_coherent(tascam->dev,
tascam->feedback_urb_alloc_size,
GFP_KERNEL, &urb->transfer_dma);
if (!urb->transfer_buffer)
return -ENOMEM;
urb->dev = tascam->dev;
urb->pipe = usb_rcvisocpipe(tascam->dev, EP_PLAYBACK_FEEDBACK);
urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
urb->interval = 4;
urb->context = tascam;
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);
struct urb *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;
if (!urb)
return -ENOMEM;
tascam->capture_urbs[i] = urb;
void *buf = usb_alloc_coherent(tascam->dev, CAPTURE_PACKET_SIZE,
GFP_KERNEL, &urb->transfer_dma);
if (!buf)
return -ENOMEM;
usb_fill_bulk_urb(urb, tascam->dev,
usb_rcvbulkpipe(tascam->dev, EP_AUDIO_IN),
buf, CAPTURE_PACKET_SIZE,
capture_urb_complete, tascam);
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
}
/* MIDI URB and buffer allocation */
for (i = 0; i < NUM_MIDI_IN_URBS; i++) {
struct urb *m_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!m_urb)
goto error;
tascam->midi_in_urbs[i] = m_urb;
m_urb->transfer_buffer =
usb_alloc_coherent(tascam->dev, MIDI_IN_BUF_SIZE,
GFP_KERNEL, &m_urb->transfer_dma);
if (!m_urb->transfer_buffer)
goto error;
usb_fill_bulk_urb(m_urb, tascam->dev,
usb_rcvbulkpipe(tascam->dev, EP_MIDI_IN),
m_urb->transfer_buffer, MIDI_IN_BUF_SIZE,
tascam_midi_in_urb_complete, tascam);
m_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
}
for (i = 0; i < NUM_MIDI_OUT_URBS; i++) {
struct urb *m_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!m_urb)
goto error;
tascam->midi_out_urbs[i] = m_urb;
m_urb->transfer_buffer =
usb_alloc_coherent(tascam->dev, MIDI_OUT_BUF_SIZE,
GFP_KERNEL, &m_urb->transfer_dma);
if (!m_urb->transfer_buffer)
goto error;
usb_fill_bulk_urb(m_urb, tascam->dev,
usb_sndbulkpipe(tascam->dev, EP_MIDI_OUT),
m_urb->transfer_buffer,
0, /* length set later */
tascam_midi_out_urb_complete, tascam);
m_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
}
tascam->capture_ring_buffer =
kmalloc(CAPTURE_RING_BUFFER_SIZE, GFP_KERNEL);
if (!tascam->capture_ring_buffer)
goto error;
tascam->capture_decode_raw_block =
kmalloc(RAW_BYTES_PER_DECODE_BLOCK, GFP_KERNEL);
if (!tascam->capture_decode_raw_block)
goto error;
tascam->capture_decode_dst_block =
kmalloc(FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME *
DECODED_SAMPLE_SIZE,
GFP_KERNEL);
if (!tascam->capture_decode_dst_block)
goto error;
tascam->capture_routing_buffer =
kmalloc(FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME *
DECODED_SAMPLE_SIZE,
GFP_KERNEL);
if (!tascam->capture_routing_buffer)
goto error;
return 0;
error:
dev_err(tascam->card->dev, "Failed to allocate URBs\n");
tascam_free_urbs(tascam);
return -ENOMEM;
}
/**
* tascam_stop_work_handler - work handler to stop all streams
* @work: pointer to the work_struct
*/
void tascam_stop_work_handler(struct work_struct *work)
{
struct tascam_card *tascam =
container_of(work, struct tascam_card, stop_work);
struct tascam_card *tascam = container_of(work, struct tascam_card, stop_work);
usb_kill_anchored_urbs(&tascam->playback_anchor);
usb_kill_anchored_urbs(&tascam->feedback_anchor);
usb_kill_anchored_urbs(&tascam->capture_anchor);
usb_kill_anchored_urbs(&tascam->midi_anchor);
atomic_set(&tascam->active_urbs, 0);
}
/**
* tascam_card_private_free() - Frees private data associated with the sound
* card.
* @card: Pointer to the ALSA sound card instance.
*
* This function is called when the sound card is being freed. It releases
* resources allocated for the tascam_card structure, including the MIDI
* input FIFO and decrements the USB device reference count.
*/
static void tascam_card_private_free(struct snd_card *card)
{
struct tascam_card *tascam = card->private_data;
if (tascam) {
kfifo_free(&tascam->midi_in_fifo);
tascam_free_urbs(tascam);
if (tascam->dev) {
usb_put_dev(tascam->dev);
tascam->dev = NULL;
@ -291,17 +175,6 @@ static void tascam_card_private_free(struct snd_card *card)
}
}
/**
* tascam_suspend() - Handles device suspension.
* @intf: The USB interface being suspended.
* @message: Power management message.
*
* This function is called when the device is suspended. It stops all active
* streams, kills all URBs, and sends a vendor-specific deep sleep command
* to the device to ensure a stable low-power state.
*
* Return: 0 on success.
*/
static int tascam_suspend(struct usb_interface *intf, pm_message_t message)
{
struct tascam_card *tascam = usb_get_intfdata(intf);
@ -310,38 +183,20 @@ static int tascam_suspend(struct usb_interface *intf, pm_message_t message)
return 0;
snd_pcm_suspend_all(tascam->pcm);
cancel_work_sync(&tascam->stop_work);
cancel_work_sync(&tascam->capture_work);
cancel_work_sync(&tascam->midi_in_work);
cancel_work_sync(&tascam->midi_out_work);
cancel_work_sync(&tascam->stop_pcm_work);
usb_kill_anchored_urbs(&tascam->playback_anchor);
usb_kill_anchored_urbs(&tascam->capture_anchor);
usb_kill_anchored_urbs(&tascam->feedback_anchor);
usb_kill_anchored_urbs(&tascam->midi_in_anchor);
usb_kill_anchored_urbs(&tascam->midi_out_anchor);
dev_info(&intf->dev, "sending deep sleep command\n");
int err = usb_control_msg(tascam->dev, usb_sndctrlpipe(tascam->dev, 0),
VENDOR_REQ_DEEP_SLEEP, RT_H2D_VENDOR_DEV,
0x0000, 0x0000, NULL, 0, USB_CTRL_TIMEOUT_MS);
if (err < 0)
dev_err(&intf->dev, "deep sleep command failed: %d\n", err);
usb_kill_anchored_urbs(&tascam->capture_anchor);
usb_kill_anchored_urbs(&tascam->midi_anchor);
usb_control_msg(tascam->dev, usb_sndctrlpipe(tascam->dev, 0),
VENDOR_REQ_DEEP_SLEEP, RT_H2D_VENDOR_DEV,
0x0000, 0x0000, NULL, 0, USB_CTRL_TIMEOUT_MS);
return 0;
}
/**
* tascam_resume() - Handles device resumption from suspend.
* @intf: The USB interface being resumed.
*
* This function is called when the device resumes from suspend. It
* re-establishes the active USB interface settings and re-configures the sample
* rate if it was previously active.
*
* Return: 0 on success, or a negative error code on failure.
*/
static int tascam_resume(struct usb_interface *intf)
{
struct tascam_card *tascam = usb_get_intfdata(intf);
@ -350,137 +205,66 @@ static int tascam_resume(struct usb_interface *intf)
if (!tascam)
return 0;
dev_info(&intf->dev, "resuming TASCAM US-144MKII\n");
/*
* The device requires a full re-initialization sequence upon resume.
* First, re-establish the active USB interface settings.
*/
err = usb_set_interface(tascam->dev, 0, 1);
if (err < 0) {
dev_err(&intf->dev,
"resume: failed to set alt setting on intf 0: %d\n",
err);
if (err < 0)
return err;
}
err = usb_set_interface(tascam->dev, 1, 1);
if (err < 0) {
dev_err(&intf->dev,
"resume: failed to set alt setting on intf 1: %d\n",
err);
if (err < 0)
return err;
}
/* Re-configure the sample rate if one was previously active */
if (tascam->current_rate > 0)
us144mkii_configure_device_for_rate(tascam,
tascam->current_rate);
us144mkii_configure_device_for_rate(tascam, tascam->current_rate);
return 0;
}
static void tascam_error_timer(struct timer_list *t)
{
struct tascam_card *tascam =
container_of(t, struct tascam_card, error_timer);
if (atomic_read(&tascam->midi_in_active))
schedule_work(&tascam->midi_in_work);
if (atomic_read(&tascam->midi_out_active))
schedule_work(&tascam->midi_out_work);
}
/**
* tascam_probe() - Probes for the TASCAM US-144MKII device.
* @intf: The USB interface being probed.
* @usb_id: The USB device ID.
*
* This function is the entry point for the USB driver when a matching device
* is found. It performs initial device setup, including:
* - Checking for the second interface (MIDI) and associating it.
* - Performing a vendor-specific handshake with the device.
* - Setting alternate settings for USB interfaces.
* - Creating and registering the ALSA sound card, PCM device, and MIDI device.
* - Allocating and initializing URBs for audio and MIDI transfers.
*
* Return: 0 on success, or a negative error code on failure.
*/
static int tascam_probe(struct usb_interface *intf,
const struct usb_device_id *usb_id)
static int tascam_probe(struct usb_interface *intf, const struct usb_device_id *usb_id)
{
struct usb_device *dev = interface_to_usbdev(intf);
struct snd_card *card;
struct tascam_card *tascam;
int err;
int idx;
char *handshake_buf __free(kfree) = NULL;
if (dev->speed != USB_SPEED_HIGH)
dev_info(
&dev->dev,
"Device is connected to a USB 1.1 port, this is not supported.\n");
/* The device has two interfaces; we drive both from this driver. */
if (intf->cur_altsetting->desc.bInterfaceNumber == 1) {
tascam = usb_get_intfdata(usb_ifnum_to_if(dev, 0));
if (tascam) {
usb_set_intfdata(intf, tascam);
tascam->iface1 = intf;
}
return 0; /* Let the core handle this interface */
}
if (intf->cur_altsetting->desc.bInterfaceNumber == 1)
return 0;
if (dev_idx >= SNDRV_CARDS) {
dev_err(&dev->dev, "Too many TASCAM devices present");
idx = atomic_fetch_inc(&dev_idx);
if (idx >= SNDRV_CARDS) {
atomic_dec(&dev_idx);
return -ENODEV;
}
if (!enable[dev_idx]) {
dev_info(&dev->dev, "TASCAM US-144MKII device disabled");
if (!enable[idx]) {
atomic_dec(&dev_idx);
return -ENOENT;
}
handshake_buf = kmalloc(1, GFP_KERNEL);
if (!handshake_buf)
if (!handshake_buf) {
atomic_dec(&dev_idx);
return -ENOMEM;
}
/* Perform vendor-specific handshake */
err = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
VENDOR_REQ_MODE_CONTROL, RT_D2H_VENDOR_DEV,
MODE_VAL_HANDSHAKE_READ, 0x0000, handshake_buf, 1,
USB_CTRL_TIMEOUT_MS);
VENDOR_REQ_MODE_CONTROL, RT_D2H_VENDOR_DEV,
MODE_VAL_HANDSHAKE_READ, 0x0000, handshake_buf, 1,
USB_CTRL_TIMEOUT_MS);
if (err < 0) {
dev_err(&dev->dev, "Handshake read failed with %d\n", err);
dev_err(&dev->dev, "Handshake failed: %d\n", err);
atomic_dec(&dev_idx);
return err;
}
if (handshake_buf[0] != 0x12 && handshake_buf[0] != 0x16 &&
handshake_buf[0] != 0x30 && handshake_buf[0] != 0x32) {
dev_err(&dev->dev, "Unexpected handshake value: 0x%x\n",
handshake_buf[0]);
return -ENODEV;
}
usb_set_interface(dev, 0, 1);
usb_set_interface(dev, 1, 1);
/* Set alternate settings to enable audio/MIDI endpoints */
err = usb_set_interface(dev, 0, 1);
err = snd_card_new(&dev->dev, index[idx], id[idx], THIS_MODULE,
sizeof(struct tascam_card), &card);
if (err < 0) {
dev_err(&dev->dev,
"Failed to set alt setting 1 on interface 0: %d\n",
err);
return err;
}
err = usb_set_interface(dev, 1, 1);
if (err < 0) {
dev_err(&dev->dev,
"Failed to set alt setting 1 on interface 1: %d\n",
err);
return err;
}
err = snd_card_new(&dev->dev, index[dev_idx], id[dev_idx], THIS_MODULE,
sizeof(struct tascam_card), &card);
if (err < 0) {
dev_err(&dev->dev, "Failed to create sound card instance\n");
atomic_dec(&dev_idx);
return err;
}
@ -489,90 +273,63 @@ static int tascam_probe(struct usb_interface *intf,
tascam->dev = usb_get_dev(dev);
tascam->card = card;
tascam->iface0 = intf;
tascam->digital_out_source = 1;
tascam->capture_34_source = 1;
spin_lock_init(&tascam->lock);
spin_lock_init(&tascam->midi_in_lock);
spin_lock_init(&tascam->midi_out_lock);
init_usb_anchor(&tascam->playback_anchor);
init_usb_anchor(&tascam->capture_anchor);
init_usb_anchor(&tascam->feedback_anchor);
init_usb_anchor(&tascam->midi_in_anchor);
init_usb_anchor(&tascam->midi_out_anchor);
timer_setup(&tascam->error_timer, tascam_error_timer, 0);
init_usb_anchor(&tascam->capture_anchor);
init_usb_anchor(&tascam->midi_anchor);
INIT_WORK(&tascam->stop_work, tascam_stop_work_handler);
INIT_WORK(&tascam->stop_pcm_work, tascam_stop_pcm_work_handler);
INIT_WORK(&tascam->capture_work, tascam_capture_work_handler);
init_completion(&tascam->midi_out_drain_completion);
if (kfifo_alloc(&tascam->midi_in_fifo, MIDI_IN_FIFO_SIZE, GFP_KERNEL)) {
snd_card_free(card);
return -ENOMEM;
}
strscpy(card->driver, DRIVER_NAME, sizeof(card->driver));
if (le16_to_cpu(dev->descriptor.idProduct) == USB_PID_TASCAM_US144) {
strscpy(card->shortname, "TASCAM US-144",
sizeof(card->shortname));
} else if (le16_to_cpu(dev->descriptor.idProduct) == USB_PID_TASCAM_US144MKII) {
strscpy(card->shortname, "TASCAM US-144MKII",
sizeof(card->shortname));
} else {
strscpy(card->shortname, "TASCAM Unknown",
sizeof(card->shortname));
}
if (le16_to_cpu(dev->descriptor.idProduct) == USB_PID_TASCAM_US144)
strscpy(card->shortname, "US-144", sizeof(card->shortname));
else
strscpy(card->shortname, "US-144MKII", sizeof(card->shortname));
snprintf(card->longname, sizeof(card->longname), "%s (%04x:%04x) at %s",
card->shortname, USB_VID_TASCAM, dev->descriptor.idProduct,
dev_name(&dev->dev));
card->shortname, USB_VID_TASCAM, dev->descriptor.idProduct,
dev_name(&dev->dev));
err = snd_pcm_new(card, "US144MKII PCM", 0, 1, 1, &tascam->pcm);
if (err < 0)
goto free_card;
tascam->pcm->private_data = tascam;
strscpy(tascam->pcm->name, "US144MKII PCM", sizeof(tascam->pcm->name));
snd_pcm_set_ops(tascam->pcm, SNDRV_PCM_STREAM_PLAYBACK, &tascam_playback_ops);
snd_pcm_set_ops(tascam->pcm, SNDRV_PCM_STREAM_CAPTURE, &tascam_capture_ops);
err = tascam_init_pcm(tascam->pcm);
if (err < 0)
goto free_card;
snd_pcm_set_managed_buffer_all(tascam->pcm, SNDRV_DMA_TYPE_VMALLOC,
NULL, 0, 0);
err = tascam_create_midi(tascam);
if (err < 0)
goto free_card;
err = tascam_create_controls(tascam);
if (err < 0)
goto free_card;
err = tascam_alloc_urbs(tascam);
if (err < 0)
goto free_card;
if (us144mkii_configure_device_for_rate(tascam, 48000) < 0)
dev_warn(&dev->dev, "Failed to initialize device at 48khz\n");
else
tascam->current_rate = 48000;
err = snd_card_register(card);
if (err < 0)
goto free_card;
usb_set_intfdata(intf, tascam);
dev_idx++;
return 0;
free_card:
tascam_free_urbs(tascam);
snd_card_free(card);
atomic_dec(&dev_idx);
return err;
}
/**
* tascam_disconnect() - Disconnects the TASCAM US-144MKII device.
* @intf: The USB interface being disconnected.
*
* This function is called when the device is disconnected from the system.
* It cleans up all allocated resources, including killing URBs, freeing
* the sound card, and releasing memory.
*/
static void tascam_disconnect(struct usb_interface *intf)
{
struct tascam_card *tascam = usb_get_intfdata(intf);
@ -581,30 +338,28 @@ static void tascam_disconnect(struct usb_interface *intf)
return;
if (intf->cur_altsetting->desc.bInterfaceNumber == 0) {
/* Ensure all deferred work is complete before freeing resources */
snd_card_disconnect(tascam->card);
cancel_work_sync(&tascam->stop_work);
cancel_work_sync(&tascam->capture_work);
cancel_work_sync(&tascam->midi_in_work);
cancel_work_sync(&tascam->midi_out_work);
cancel_work_sync(&tascam->stop_pcm_work);
atomic_set(&tascam->playback_active, 0);
atomic_set(&tascam->capture_active, 0);
usb_kill_anchored_urbs(&tascam->playback_anchor);
usb_kill_anchored_urbs(&tascam->capture_anchor);
usb_kill_anchored_urbs(&tascam->feedback_anchor);
usb_kill_anchored_urbs(&tascam->midi_in_anchor);
usb_kill_anchored_urbs(&tascam->midi_out_anchor);
timer_delete_sync(&tascam->error_timer);
tascam_free_urbs(tascam);
usb_kill_anchored_urbs(&tascam->capture_anchor);
usb_kill_anchored_urbs(&tascam->midi_anchor);
snd_card_disconnect(tascam->card);
cancel_work_sync(&tascam->stop_work);
cancel_work_sync(&tascam->stop_pcm_work);
usb_set_intfdata(intf, NULL);
snd_card_free(tascam->card);
dev_idx--;
atomic_dec(&dev_idx);
}
}
static const struct usb_device_id tascam_usb_ids[] = {
{ USB_DEVICE(USB_VID_TASCAM, USB_PID_TASCAM_US144) },
{ USB_DEVICE(USB_VID_TASCAM, USB_PID_TASCAM_US144MKII) },
{ /* Terminating entry */ }
{ }
};
MODULE_DEVICE_TABLE(usb, tascam_usb_ids);

View File

@ -4,32 +4,27 @@
#ifndef __US144MKII_H
#define __US144MKII_H
#include <linux/kfifo.h>
#include <linux/timer.h>
#include <linux/usb.h>
#include <linux/workqueue.h>
#include <sound/control.h>
#include <linux/atomic.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/rawmidi.h>
#define DRIVER_NAME "us144mkii"
#define DRIVER_VERSION "1.7.6"
/* --- USB Device Identification --- */
#define USB_VID_TASCAM 0x0644
#define USB_PID_TASCAM_US144 0x800f
#define USB_PID_TASCAM_US144MKII 0x8020
/* --- USB Endpoints (Alternate Setting 1) --- */
#define EP_PLAYBACK_FEEDBACK 0x81
#define EP_AUDIO_OUT 0x02
#define EP_MIDI_IN 0x83
#define EP_MIDI_OUT 0x04
#define EP_AUDIO_IN 0x86
/* --- USB Control Message Protocol --- */
#define RT_H2D_CLASS_EP (USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_ENDPOINT)
#define RT_D2H_CLASS_EP (USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_ENDPOINT)
#define RT_H2D_VENDOR_DEV (USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE)
@ -56,8 +51,6 @@ enum tascam_mode_value {
MODE_VAL_STREAM_START = 0x0030,
};
#define HANDSHAKE_SUCCESS_VAL 0x12
enum tascam_register {
REG_ADDR_UNKNOWN_0D = 0x0d04,
REG_ADDR_UNKNOWN_0E = 0x0e00,
@ -71,298 +64,130 @@ enum tascam_register {
#define REG_VAL_ENABLE 0x0101
/* --- URB Configuration --- */
#define NUM_PLAYBACK_URBS 4
#define PLAYBACK_URB_PACKETS 8
#define NUM_FEEDBACK_URBS 4
#define NUM_PLAYBACK_URBS 8
#define PLAYBACK_URB_PACKETS 2
#define NUM_FEEDBACK_URBS 2
#define FEEDBACK_URB_PACKETS 1
#define FEEDBACK_PACKET_SIZE 3
#define NUM_CAPTURE_URBS 8
#define CAPTURE_URB_SIZE 512
#define CAPTURE_RING_BUFFER_SIZE (CAPTURE_URB_SIZE * NUM_CAPTURE_URBS * 4)
#define NUM_MIDI_IN_URBS 4
#define MIDI_IN_BUF_SIZE 64
#define MIDI_IN_FIFO_SIZE (MIDI_IN_BUF_SIZE * NUM_MIDI_IN_URBS)
#define MIDI_OUT_BUF_SIZE 64
#define NUM_MIDI_OUT_URBS 4
#define USB_CTRL_TIMEOUT_MS 1000
#define FEEDBACK_SYNC_LOSS_THRESHOLD 41
#define NUM_CAPTURE_URBS 4
#define CAPTURE_PACKET_SIZE 512
#define MIDI_PACKET_SIZE 9
#define MIDI_PAYLOAD_SIZE 8
/* --- Audio Format Configuration --- */
#define BYTES_PER_SAMPLE 3
#define NUM_CHANNELS 4
#define BYTES_PER_FRAME (NUM_CHANNELS * BYTES_PER_SAMPLE)
#define FEEDBACK_ACCUMULATOR_SIZE 128
/* --- Capture Decoding Defines --- */
#define DECODED_CHANNELS_PER_FRAME 4
#define DECODED_SAMPLE_SIZE 4
#define FRAMES_PER_DECODE_BLOCK 8
#define RAW_BYTES_PER_DECODE_BLOCK 512
#define PLL_FILTER_OLD_WEIGHT 3
#define PLL_FILTER_NEW_WEIGHT 1
#define PLL_FILTER_DIVISOR (PLL_FILTER_OLD_WEIGHT + PLL_FILTER_NEW_WEIGHT)
#define USB_CTRL_TIMEOUT_MS 1000
/**
* struct us144mkii_frame_pattern_observer - State for dynamic feedback
* patterns.
* @sample_rate_khz: The current sample rate in kHz.
* @base_feedback_value: The nominal feedback value for the current rate.
* @feedback_offset: An offset to align the feedback value range.
* @full_frame_patterns: A 2D array of pre-calculated packet size patterns.
* @current_index: The current index into the pattern array.
* @previous_index: The previous index, used for state tracking.
* @sync_locked: A flag indicating if the pattern has locked to the stream.
*/
struct us144mkii_frame_pattern_observer {
unsigned int sample_rate_khz;
unsigned int base_feedback_value;
int feedback_offset;
unsigned int full_frame_patterns[5][8];
unsigned int current_index;
unsigned int previous_index;
bool sync_locked;
};
/**
* struct tascam_card - Main driver data structure for the TASCAM US-144MKII.
* @dev: Pointer to the USB device.
* @iface0: Pointer to USB interface 0 (audio).
* @iface1: Pointer to USB interface 1 (MIDI).
* @card: Pointer to the ALSA sound card instance.
* @pcm: Pointer to the ALSA PCM device.
* @rmidi: Pointer to the ALSA rawmidi device.
*
* @playback_substream: Pointer to the active playback PCM substream.
* @playback_urbs: Array of URBs for playback.
* @playback_urb_alloc_size: Size of allocated buffer for each playback URB.
* @feedback_urbs: Array of URBs for feedback.
* @feedback_urb_alloc_size: Size of allocated buffer for each feedback URB.
* @playback_active: Atomic flag indicating if playback is active.
* @playback_frames_consumed: Total frames consumed by playback.
* @driver_playback_pos: Current position in the ALSA playback buffer (frames).
* @last_period_pos: Last reported period position for playback.
*
* @capture_substream: Pointer to the active capture PCM substream.
* @capture_urbs: Array of URBs for capture.
* @capture_urb_alloc_size: Size of allocated buffer for each capture URB.
* @capture_active: Atomic flag indicating if capture is active.
* @driver_capture_pos: Current position in the ALSA capture buffer (frames).
* @capture_frames_processed: Total frames processed for capture.
* @last_capture_period_pos: Last reported period position for capture.
* @capture_ring_buffer: Ring buffer for raw capture data from USB.
* @capture_ring_buffer_read_ptr: Read pointer for the capture ring buffer.
* @capture_ring_buffer_write_ptr: Write pointer for the capture ring buffer.
* @capture_decode_raw_block: Buffer for a raw 512-byte capture block.
* @capture_decode_dst_block: Buffer for decoded 32-bit capture samples.
* @capture_routing_buffer: Intermediate buffer for capture routing.
* @capture_work: Work struct for deferred capture processing.
* @stop_work: Work struct for deferred stream stopping.
* @stop_pcm_work: Work struct for stopping PCM due to a fatal error (e.g.
* xrun).
*
* @midi_in_substream: Pointer to the active MIDI input substream.
* @midi_out_substream: Pointer to the active MIDI output substream.
* @midi_in_urbs: Array of URBs for MIDI input.
* @midi_out_urbs: Array of URBs for MIDI output.
* @midi_in_active: Atomic flag indicating if MIDI input is active.
* @midi_out_active: Atomic flag indicating if MIDI output is active.
* @midi_in_fifo: FIFO for raw MIDI input data.
* @midi_in_work: Work struct for deferred MIDI input processing.
* @midi_out_work: Work struct for deferred MIDI output processing.
* @midi_in_lock: Spinlock for MIDI input FIFO.
* @midi_out_lock: Spinlock for MIDI output.
* @midi_out_urbs_in_flight: Bitmap of MIDI output URBs currently in flight.
* @midi_running_status: Stores the last MIDI status byte for running status.
* @error_timer: Timer for MIDI error retry logic.
*
* @lock: Main spinlock for protecting shared driver state.
* @active_urbs: Atomic counter for active URBs.
* @current_rate: Currently configured sample rate of the device.
* @line_out_source: Source for Line Outputs (0: Playback 1-2, 1: Playback 3-4).
* @digital_out_source: Source for Digital Outputs (0: Playback 1-2, 1: Playback
* 3-4).
* @capture_12_source: Source for Capture channels 1-2 (0: Analog In, 1: Digital
* In).
* @capture_34_source: Source for Capture channels 3-4 (0: Analog In, 1: Digital
* In).
*
* @feedback_accumulator_pattern: Stores the calculated frames per packet for
* feedback.
* @feedback_pattern_out_idx: Read index for feedback_accumulator_pattern.
* @feedback_pattern_in_idx: Write index for feedback_accumulator_pattern.
* @feedback_synced: Flag indicating if feedback is synced.
* @feedback_consecutive_errors: Counter for consecutive feedback errors.
* @feedback_urb_skip_count: Number of feedback URBs to skip initially for
* stabilization.
* @fpo: Holds the state for the dynamic feedback pattern generation.
*
* @playback_anchor: USB anchor for playback URBs.
* @capture_anchor: USB anchor for capture URBs.
* @feedback_anchor: USB anchor for feedback URBs.
* @midi_in_anchor: USB anchor for MIDI input URBs.
* @midi_out_anchor: USB anchor for MIDI output URBs.
* struct tascam_card - private data for the TASCAM US-144MKII driver
* @dev: pointer to the USB device
* @iface0: pointer to the first interface
* @card: pointer to the ALSA sound card
* @pcm: pointer to the ALSA PCM device
* @rmidi: pointer to the ALSA raw MIDI device
* @playback_substream: pointer to the PCM playback substream
* @capture_substream: pointer to the PCM capture substream
* @playback_urbs: array of URBs for PCM playback
* @playback_urb_alloc_size: allocated size of each playback URB
* @feedback_urbs: array of URBs for feedback
* @feedback_urb_alloc_size: allocated size of each feedback URB
* @capture_urbs: array of URBs for PCM capture
* @playback_anchor: anchor for playback URBs
* @feedback_anchor: anchor for feedback URBs
* @capture_anchor: anchor for capture URBs
* @midi_input: pointer to the MIDI input substream
* @midi_output: pointer to the MIDI output substream
* @midi_in_urb: URB for MIDI input
* @midi_out_urb: URB for MIDI output
* @midi_in_buf: buffer for MIDI input
* @midi_out_buf: buffer for MIDI output
* @midi_anchor: anchor for MIDI URBs
* @midi_out_active: flag indicating if MIDI output is active
* @midi_lock: spinlock for MIDI operations
* @lock: spinlock for PCM operations
* @playback_active: atomic flag indicating if PCM playback is active
* @capture_active: atomic flag indicating if PCM capture is active
* @active_urbs: atomic counter for active URBs
* @current_rate: current sample rate
* @playback_frames_consumed: number of frames consumed by the playback device
* @driver_playback_pos: playback position in the ring buffer
* @last_pb_period_pos: last playback period position
* @capture_frames_processed: number of frames processed from the capture device
* @driver_capture_pos: capture position in the ring buffer
* @last_cap_period_pos: last capture period position
* @phase_accum: phase accumulator for the playback PLL
* @freq_q16: current frequency for the playback PLL in Q16.16 format
* @feedback_synced: flag indicating if feedback is synced
* @feedback_urb_skip_count: number of feedback URBs to skip at startup
* @stop_work: work struct for stopping all streams
* @stop_pcm_work: work struct for stopping PCM streams
*/
struct tascam_card {
/* --- Core device pointers --- */
struct usb_device *dev;
struct usb_interface *iface0;
struct usb_interface *iface1;
struct snd_card *card;
struct snd_pcm *pcm;
struct snd_rawmidi *rmidi;
/* --- PCM Substreams --- */
struct snd_pcm_substream *playback_substream;
struct snd_pcm_substream *capture_substream;
/* --- URBs and Anchors --- */
struct urb *playback_urbs[NUM_PLAYBACK_URBS];
size_t playback_urb_alloc_size;
struct urb *feedback_urbs[NUM_FEEDBACK_URBS];
size_t feedback_urb_alloc_size;
struct urb *capture_urbs[NUM_CAPTURE_URBS];
size_t capture_urb_alloc_size;
struct urb *midi_in_urbs[NUM_MIDI_IN_URBS];
struct urb *midi_out_urbs[NUM_MIDI_OUT_URBS];
struct usb_anchor playback_anchor;
struct usb_anchor capture_anchor;
struct usb_anchor feedback_anchor;
struct usb_anchor midi_in_anchor;
struct usb_anchor midi_out_anchor;
/* --- Stream State --- */
struct usb_anchor playback_anchor;
struct usb_anchor feedback_anchor;
struct usb_anchor capture_anchor;
struct snd_rawmidi_substream *midi_input;
struct snd_rawmidi_substream *midi_output;
struct urb *midi_in_urb;
struct urb *midi_out_urb;
u8 *midi_in_buf;
u8 *midi_out_buf;
struct usb_anchor midi_anchor;
bool midi_out_active;
spinlock_t midi_lock;
spinlock_t lock;
atomic_t playback_active;
atomic_t capture_active;
atomic_t active_urbs;
int current_rate;
/* --- Playback State --- */
u64 playback_frames_consumed;
snd_pcm_uframes_t driver_playback_pos;
u64 last_period_pos;
u64 last_pb_period_pos;
/* --- Capture State --- */
u64 capture_frames_processed;
snd_pcm_uframes_t driver_capture_pos;
u64 last_capture_period_pos;
u8 *capture_ring_buffer;
size_t capture_ring_buffer_read_ptr;
size_t capture_ring_buffer_write_ptr;
u8 *capture_decode_raw_block;
s32 *capture_decode_dst_block;
s32 *capture_routing_buffer;
u64 last_cap_period_pos;
/* --- MIDI State --- */
struct snd_rawmidi_substream *midi_in_substream;
struct snd_rawmidi_substream *midi_out_substream;
atomic_t midi_in_active;
atomic_t midi_out_active;
struct kfifo midi_in_fifo;
spinlock_t midi_in_lock;
spinlock_t midi_out_lock;
unsigned long midi_out_urbs_in_flight;
u8 midi_running_status;
struct timer_list error_timer;
struct completion midi_out_drain_completion;
/* --- Feedback Sync State --- */
unsigned int feedback_accumulator_pattern[FEEDBACK_ACCUMULATOR_SIZE];
unsigned int feedback_pattern_out_idx;
unsigned int feedback_pattern_in_idx;
u32 phase_accum;
u32 freq_q16;
bool feedback_synced;
unsigned int feedback_consecutive_errors;
unsigned int feedback_urb_skip_count;
struct us144mkii_frame_pattern_observer fpo;
/* --- Workqueues --- */
struct work_struct stop_work;
struct work_struct stop_pcm_work;
struct work_struct capture_work;
struct work_struct midi_in_work;
struct work_struct midi_out_work;
/* --- Mixer/Routing State --- */
unsigned int line_out_source;
unsigned int digital_out_source;
unsigned int capture_12_source;
unsigned int capture_34_source;
};
/* main.c */
/**
* tascam_free_urbs() - Free all allocated URBs and associated buffers.
* @tascam: the tascam_card instance
*
* This function kills, unlinks, and frees all playback, feedback, capture,
* and MIDI URBs, along with their transfer buffers and the capture
* ring/decode buffers.
*/
void tascam_free_urbs(struct tascam_card *tascam);
/**
* tascam_alloc_urbs() - Allocate all URBs and associated buffers.
* @tascam: the tascam_card instance
*
* This function allocates and initializes all URBs for playback, feedback,
* capture, and MIDI, as well as the necessary buffers for data processing.
*
* Return: 0 on success, or a negative error code on failure.
*/
int tascam_alloc_urbs(struct tascam_card *tascam);
/**
* tascam_stop_work_handler() - Work handler to stop all active streams.
* @work: Pointer to the work_struct.
*
* This function is scheduled to stop all active URBs (playback, feedback,
* capture) and reset the active_urbs counter.
*/
void tascam_stop_work_handler(struct work_struct *work);
/* us144mkii_pcm.h */
#include "us144mkii_pcm.h"
/* us144mkii_midi.c */
/**
* tascam_midi_in_urb_complete() - Completion handler for MIDI IN URBs
* @urb: The completed URB.
*
* This function runs in interrupt context. It places the raw data from the
* USB endpoint into a kfifo and schedules a work item to process it later,
* ensuring the interrupt handler remains fast.
*/
void tascam_midi_in_urb_complete(struct urb *urb);
/**
* tascam_midi_out_urb_complete() - Completion handler for MIDI OUT bulk URB.
* @urb: The completed URB.
*
* This function runs in interrupt context. It marks the output URB as no
* longer in-flight. It then re-schedules the work handler to check for and
* send any more data waiting in the ALSA buffer. This is a safe, non-blocking
* way to continue the data transmission chain.
*/
void tascam_midi_out_urb_complete(struct urb *urb);
/**
* tascam_create_midi() - Create and initialize the ALSA rawmidi device.
* @tascam: The driver instance.
*
* Return: 0 on success, or a negative error code on failure.
*/
int tascam_create_midi(struct tascam_card *tascam);
/* us144mkii_controls.c */
/**
* tascam_create_controls() - Creates and adds ALSA mixer controls for the
* device.
* @tascam: The driver instance.
*
* This function registers custom ALSA controls for managing audio routing
* (line out source, digital out source, capture 1-2 source, capture 3-4 source)
* and displaying the current sample rate.
*
* Return: 0 on success, or a negative error code on failure.
*/
int tascam_create_controls(struct tascam_card *tascam);
#endif /* __US144MKII_H */

View File

@ -1,319 +1,268 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2025 Šerif Rami <ramiserifpersia@gmail.com>
#include "us144mkii.h"
#include "us144mkii_pcm.h"
const struct snd_pcm_hardware tascam_capture_hw = {
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
.formats = SNDRV_PCM_FMTBIT_S32_LE,
.rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000),
.rate_min = 44100,
.rate_max = 96000,
.channels_min = NUM_CHANNELS,
.channels_max = NUM_CHANNELS,
.buffer_bytes_max = 1024 * 1024,
.period_bytes_min = 32 * BYTES_PER_FRAME,
.period_bytes_max = 1024 * BYTES_PER_FRAME,
.periods_min = 2,
.periods_max = 1024,
};
/**
* tascam_capture_open() - Opens the PCM capture substream.
* @substream: The ALSA PCM substream to open.
*
* This function sets the hardware parameters for the capture substream
* and stores a reference to the substream in the driver's private data.
*
* Return: 0 on success.
*/
static int tascam_capture_open(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
substream->runtime->hw = tascam_pcm_hw;
substream->runtime->hw = tascam_capture_hw;
tascam->capture_substream = substream;
atomic_set(&tascam->capture_active, 0);
return 0;
}
/**
* tascam_capture_close() - Closes the PCM capture substream.
* @substream: The ALSA PCM substream to close.
*
* This function clears the reference to the capture substream in the
* driver's private data.
*
* Return: 0 on success.
*/
static int tascam_capture_close(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
tascam->capture_substream = NULL;
return 0;
}
/**
* tascam_capture_prepare() - Prepares the PCM capture substream for use.
* @substream: The ALSA PCM substream to prepare.
*
* This function initializes capture-related counters and ring buffer pointers.
*
* Return: 0 on success.
*/
static int tascam_capture_prepare(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
usb_kill_anchored_urbs(&tascam->capture_anchor);
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;
tascam->last_cap_period_pos = 0;
return 0;
}
/**
* tascam_capture_pointer() - Returns the current capture pointer position.
* @substream: The ALSA PCM substream.
*
* This function returns the current position of the capture pointer within
* the ALSA ring buffer, in frames.
*
* Return: The current capture pointer position in frames.
*/
static snd_pcm_uframes_t
tascam_capture_pointer(struct snd_pcm_substream *substream)
static snd_pcm_uframes_t tascam_capture_pointer(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
unsigned long flags;
u64 pos;
snd_pcm_uframes_t buffer_size = substream->runtime->buffer_size;
if (!atomic_read(&tascam->capture_active))
return 0;
spin_lock_irqsave(&tascam->lock, flags);
pos = tascam->capture_frames_processed;
spin_unlock_irqrestore(&tascam->lock, flags);
scoped_guard(spinlock_irqsave, &tascam->lock) {
pos = tascam->capture_frames_processed;
return (snd_pcm_uframes_t)(pos % buffer_size);
}
static int tascam_capture_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
int i, ret = 0;
bool start = false;
bool stop = false;
unsigned long flags;
spin_lock_irqsave(&tascam->lock, flags);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
if (!atomic_read(&tascam->capture_active)) {
atomic_set(&tascam->capture_active, 1);
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);
stop = true;
break;
default:
ret = -EINVAL;
break;
}
spin_unlock_irqrestore(&tascam->lock, flags);
if (stop) {
/* Ensure capture_active is updated before unlinking URBs */
smp_mb();
for (i = 0; i < NUM_CAPTURE_URBS; i++) {
if (tascam->capture_urbs[i])
usb_unlink_urb(tascam->capture_urbs[i]);
}
}
if (runtime->buffer_size == 0)
return 0;
if (start) {
for (i = 0; i < NUM_CAPTURE_URBS; i++) {
usb_anchor_urb(tascam->capture_urbs[i], &tascam->capture_anchor);
if (usb_submit_urb(tascam->capture_urbs[i], GFP_ATOMIC) < 0) {
usb_unanchor_urb(tascam->capture_urbs[i]);
atomic_set(&tascam->capture_active, 0);
/* Ensure capture_active is cleared before unlinking URBs */
smp_mb();
for (int j = 0; j < i; j++)
usb_unlink_urb(tascam->capture_urbs[j]);
ret = -EIO;
break;
}
atomic_inc(&tascam->active_urbs);
}
}
return ret;
}
return do_div(pos, runtime->buffer_size);
static inline u8 tascam_pack_byte(const u8 *src, int bit_offset)
{
return (((src[0] >> bit_offset) & 1) << 7) |
(((src[1] >> bit_offset) & 1) << 6) |
(((src[2] >> bit_offset) & 1) << 5) |
(((src[3] >> bit_offset) & 1) << 4) |
(((src[4] >> bit_offset) & 1) << 3) |
(((src[5] >> bit_offset) & 1) << 2) |
(((src[6] >> bit_offset) & 1) << 1) |
(((src[7] >> bit_offset) & 1));
}
static void tascam_decode_capture_chunk(const u8 *src, u32 *dst, int frames_to_decode)
{
int frame;
u8 h, m, l;
for (frame = 0; frame < frames_to_decode; frame++) {
const u8 *p_src_a = src + (frame * 64);
const u8 *p_src_b = src + (frame * 64) + 32;
h = tascam_pack_byte(p_src_a, 0);
m = tascam_pack_byte(p_src_a + 8, 0);
l = tascam_pack_byte(p_src_a + 16, 0);
*dst++ = (h << 24) | (m << 16) | (l << 8);
h = tascam_pack_byte(p_src_b, 0);
m = tascam_pack_byte(p_src_b + 8, 0);
l = tascam_pack_byte(p_src_b + 16, 0);
*dst++ = (h << 24) | (m << 16) | (l << 8);
h = tascam_pack_byte(p_src_a, 1);
m = tascam_pack_byte(p_src_a + 8, 1);
l = tascam_pack_byte(p_src_a + 16, 1);
*dst++ = (h << 24) | (m << 16) | (l << 8);
h = tascam_pack_byte(p_src_b, 1);
m = tascam_pack_byte(p_src_b + 8, 1);
l = tascam_pack_byte(p_src_b + 16, 1);
*dst++ = (h << 24) | (m << 16) | (l << 8);
}
}
/**
* tascam_capture_ops - ALSA PCM operations for capture.
* capture_urb_complete() - completion handler for capture bulk URBs
* @urb: the completed URB
*
* This structure defines the callback functions for capture stream operations,
* including open, close, ioctl, hardware parameters, hardware free, prepare,
* trigger, and pointer.
* This function runs in interrupt context. It copies the received raw data
* into an intermediate ring buffer and then schedules the workqueue to process
* it. It then resubmits the URB to receive more data.
*/
void capture_urb_complete(struct urb *urb)
{
struct tascam_card *tascam = urb->context;
struct snd_pcm_substream *substream;
struct snd_pcm_runtime *runtime;
unsigned long flags;
int frames_received;
snd_pcm_uframes_t write_pos;
snd_pcm_uframes_t buffer_size, period_size;
bool need_period_elapsed = false;
if (!tascam)
return;
if (urb->status) {
atomic_dec(&tascam->active_urbs);
return;
}
if (!atomic_read(&tascam->capture_active)) {
atomic_dec(&tascam->active_urbs);
return;
}
substream = tascam->capture_substream;
if (!substream) {
atomic_dec(&tascam->active_urbs);
return;
}
runtime = substream->runtime;
if (!runtime) {
atomic_dec(&tascam->active_urbs);
return;
}
buffer_size = runtime->buffer_size;
period_size = runtime->period_size;
frames_received = urb->actual_length / 64;
if (frames_received > 0) {
spin_lock_irqsave(&tascam->lock, flags);
write_pos = tascam->driver_capture_pos;
spin_unlock_irqrestore(&tascam->lock, flags);
u32 *dma_ptr = (u32 *)(runtime->dma_area + frames_to_bytes(runtime, write_pos));
if (write_pos + frames_received <= buffer_size) {
tascam_decode_capture_chunk(urb->transfer_buffer, dma_ptr, frames_received);
} else {
int part1 = buffer_size - write_pos;
int part2 = frames_received - part1;
tascam_decode_capture_chunk(urb->transfer_buffer, dma_ptr, part1);
tascam_decode_capture_chunk(urb->transfer_buffer + (part1 * 64), (u32 *)runtime->dma_area, part2);
}
spin_lock_irqsave(&tascam->lock, flags);
tascam->driver_capture_pos += frames_received;
if (tascam->driver_capture_pos >= buffer_size)
tascam->driver_capture_pos -= buffer_size;
tascam->capture_frames_processed += frames_received;
if (period_size > 0) {
u64 current_period = div_u64(tascam->capture_frames_processed, period_size);
if (current_period > tascam->last_cap_period_pos) {
tascam->last_cap_period_pos = current_period;
need_period_elapsed = true;
}
}
spin_unlock_irqrestore(&tascam->lock, flags);
if (need_period_elapsed)
snd_pcm_period_elapsed(substream);
}
if (usb_submit_urb(urb, GFP_ATOMIC) < 0)
atomic_dec(&tascam->active_urbs);
}
const struct snd_pcm_ops tascam_capture_ops = {
.open = tascam_capture_open,
.close = tascam_capture_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = tascam_pcm_hw_params,
.hw_free = tascam_pcm_hw_free,
.hw_free = NULL,
.prepare = tascam_capture_prepare,
.trigger = tascam_pcm_trigger,
.trigger = tascam_capture_trigger,
.pointer = tascam_capture_pointer,
};
/**
* decode_tascam_capture_block() - Decodes a raw 512-byte block from the device.
* @src_block: Pointer to the 512-byte raw source block.
* @dst_block: Pointer to the destination buffer for decoded audio frames.
*
* The device sends audio data in a complex, multiplexed format. This function
* demultiplexes the bits from the raw block into 8 frames of 4-channel,
* 24-bit audio (stored in 32-bit containers).
*/
static void decode_tascam_capture_block(const u8 *src_block, s32 *dst_block)
{
int frame, bit;
memset(dst_block, 0,
FRAMES_PER_DECODE_BLOCK * DECODED_CHANNELS_PER_FRAME *
DECODED_SAMPLE_SIZE);
for (frame = 0; frame < FRAMES_PER_DECODE_BLOCK; ++frame) {
const u8 *p_src_frame_base = src_block + frame * 64;
s32 *p_dst_frame = dst_block + frame * 4;
s32 ch[4] = { 0 };
for (bit = 0; bit < 24; ++bit) {
u8 byte1 = p_src_frame_base[bit];
u8 byte2 = p_src_frame_base[bit + 32];
ch[0] = (ch[0] << 1) | (byte1 & 1);
ch[2] = (ch[2] << 1) | ((byte1 >> 1) & 1);
ch[1] = (ch[1] << 1) | (byte2 & 1);
ch[3] = (ch[3] << 1) | ((byte2 >> 1) & 1);
}
/*
* The result is a 24-bit sample. Shift left by 8 to align it to
* the most significant bits of a 32-bit integer (S32_LE format).
*/
p_dst_frame[0] = ch[0] << 8;
p_dst_frame[1] = ch[1] << 8;
p_dst_frame[2] = ch[2] << 8;
p_dst_frame[3] = ch[3] << 8;
}
}
void tascam_capture_work_handler(struct work_struct *work)
{
struct tascam_card *tascam =
container_of(work, struct tascam_card, capture_work);
struct snd_pcm_substream *substream = tascam->capture_substream;
struct snd_pcm_runtime *runtime;
u8 *raw_block = tascam->capture_decode_raw_block;
s32 *decoded_block = tascam->capture_decode_dst_block;
s32 *routed_block = tascam->capture_routing_buffer;
if (!substream || !substream->runtime)
return;
runtime = substream->runtime;
if (!raw_block || !decoded_block || !routed_block) {
dev_err(tascam->card->dev,
"Capture decode/routing buffers not allocated!\n");
return;
}
while (atomic_read(&tascam->capture_active)) {
size_t write_ptr, read_ptr, available_data;
bool can_process;
scoped_guard(spinlock_irqsave, &tascam->lock) {
write_ptr = tascam->capture_ring_buffer_write_ptr;
read_ptr = tascam->capture_ring_buffer_read_ptr;
available_data = (write_ptr >= read_ptr) ?
(write_ptr - read_ptr) :
(CAPTURE_RING_BUFFER_SIZE -
read_ptr + write_ptr);
can_process =
(available_data >= RAW_BYTES_PER_DECODE_BLOCK);
if (can_process) {
size_t bytes_to_end =
CAPTURE_RING_BUFFER_SIZE - read_ptr;
if (bytes_to_end >=
RAW_BYTES_PER_DECODE_BLOCK) {
memcpy(raw_block,
tascam->capture_ring_buffer +
read_ptr,
RAW_BYTES_PER_DECODE_BLOCK);
} else {
memcpy(raw_block,
tascam->capture_ring_buffer +
read_ptr,
bytes_to_end);
memcpy(raw_block + bytes_to_end,
tascam->capture_ring_buffer,
RAW_BYTES_PER_DECODE_BLOCK -
bytes_to_end);
}
tascam->capture_ring_buffer_read_ptr =
(read_ptr +
RAW_BYTES_PER_DECODE_BLOCK) %
CAPTURE_RING_BUFFER_SIZE;
}
}
if (!can_process)
break;
decode_tascam_capture_block(raw_block, decoded_block);
process_capture_routing_us144mkii(tascam, decoded_block,
routed_block);
scoped_guard(spinlock_irqsave, &tascam->lock) {
if (atomic_read(&tascam->capture_active)) {
int f;
for (f = 0; f < FRAMES_PER_DECODE_BLOCK; ++f) {
u8 *dst_frame_start =
runtime->dma_area +
frames_to_bytes(
runtime,
tascam->driver_capture_pos);
s32 *routed_frame_start =
routed_block +
(f * NUM_CHANNELS);
int c;
for (c = 0; c < NUM_CHANNELS; c++) {
u8 *dst_channel =
dst_frame_start +
(c * BYTES_PER_SAMPLE);
s32 *src_channel_s32 =
routed_frame_start + c;
memcpy(dst_channel,
((char *)src_channel_s32) +
1,
3);
}
tascam->driver_capture_pos =
(tascam->driver_capture_pos +
1) %
runtime->buffer_size;
}
}
}
}
}
void capture_urb_complete(struct urb *urb)
{
struct tascam_card *tascam = urb->context;
int ret;
if (urb->status) {
if (urb->status != -ENOENT && urb->status != -ECONNRESET &&
urb->status != -ESHUTDOWN && urb->status != -ENODEV &&
urb->status != -EPROTO)
dev_err_ratelimited(tascam->card->dev,
"Capture URB failed: %d\n",
urb->status);
goto out;
}
if (!tascam || !atomic_read(&tascam->capture_active))
goto out;
if (urb->actual_length > 0) {
scoped_guard(spinlock_irqsave, &tascam->lock) {
size_t write_ptr = tascam->capture_ring_buffer_write_ptr;
size_t bytes_to_end = CAPTURE_RING_BUFFER_SIZE - write_ptr;
if (urb->actual_length > bytes_to_end) {
memcpy(tascam->capture_ring_buffer + write_ptr,
urb->transfer_buffer, bytes_to_end);
memcpy(tascam->capture_ring_buffer,
urb->transfer_buffer + bytes_to_end,
urb->actual_length - bytes_to_end);
} else {
memcpy(tascam->capture_ring_buffer + write_ptr,
urb->transfer_buffer,
urb->actual_length);
}
tascam->capture_ring_buffer_write_ptr =
(write_ptr + urb->actual_length) %
CAPTURE_RING_BUFFER_SIZE;
}
schedule_work(&tascam->capture_work);
}
usb_get_urb(urb);
usb_anchor_urb(urb, &tascam->capture_anchor);
ret = usb_submit_urb(urb, GFP_ATOMIC);
if (ret < 0) {
dev_err_ratelimited(tascam->card->dev,
"Failed to resubmit capture URB: %d\n",
ret);
usb_unanchor_urb(urb);
usb_put_urb(urb);
atomic_dec(
&tascam->active_urbs); /* Decrement on failed resubmission */
}
out:
usb_put_urb(urb);
}

View File

@ -1,444 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2025 Šerif Rami <ramiserifpersia@gmail.com>
#include "us144mkii.h"
/**
* @brief Text descriptions for playback output source options.
*
* Used by ALSA kcontrol elements to provide user-friendly names for
* the playback routing options (e.g., "Playback 1-2", "Playback 3-4").
*/
static const char *const playback_source_texts[] = { "Playback 1-2",
"Playback 3-4" };
/**
* @brief Text descriptions for capture input source options.
*
* Used by ALSA kcontrol elements to provide user-friendly names for
* the capture routing options (e.g., "Analog In", "Digital In").
*/
static const char *const capture_source_texts[] = { "Analog In", "Digital In" };
/**
* tascam_playback_source_info() - ALSA control info callback for playback
* source.
* @kcontrol: The ALSA kcontrol instance.
* @uinfo: The ALSA control element info structure to fill.
*
* This function provides information about the enumerated playback source
* control, including its type, count, and available items (Playback 1-2,
* Playback 3-4).
*
* Return: 0 on success.
*/
static int tascam_playback_source_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
return snd_ctl_enum_info(uinfo, 1, 2, playback_source_texts);
}
/**
* tascam_line_out_get() - ALSA control get callback for Line Outputs Source.
* @kcontrol: The ALSA kcontrol instance.
* @ucontrol: The ALSA control element value structure to fill.
*
* This function retrieves the current selection for the Line Outputs source
* (Playback 1-2 or Playback 3-4) from the driver's private data and populates
* the ALSA control element value.
*
* Return: 0 on success.
*/
static int tascam_line_out_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
scoped_guard(spinlock_irqsave, &tascam->lock) {
ucontrol->value.enumerated.item[0] = tascam->line_out_source;
}
return 0;
}
/**
* tascam_line_out_put() - ALSA control put callback for Line Outputs Source.
* @kcontrol: The ALSA kcontrol instance.
* @ucontrol: The ALSA control element value structure containing the new value.
*
* This function sets the Line Outputs source (Playback 1-2 or Playback 3-4)
* based on the user's selection from the ALSA control element. It validates
* the input and updates the driver's private data.
*
* Return: 1 if the value was changed, 0 if unchanged, or a negative error code.
*/
static int tascam_line_out_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
int changed = 0;
if (ucontrol->value.enumerated.item[0] > 1)
return -EINVAL;
scoped_guard(spinlock_irqsave, &tascam->lock) {
if (tascam->line_out_source != ucontrol->value.enumerated.item[0]) {
tascam->line_out_source = ucontrol->value.enumerated.item[0];
changed = 1;
}
}
return changed;
}
/**
* tascam_line_out_control - ALSA kcontrol definition for Line Outputs Source.
*
* This defines a new ALSA mixer control named "Line OUTPUTS Source" that allows
* the user to select between "Playback 1-2" and "Playback 3-4" for the analog
* line outputs of the device. It uses the `tascam_playback_source_info` for
* information and `tascam_line_out_get`/`tascam_line_out_put` for value
* handling.
*/
static const struct snd_kcontrol_new tascam_line_out_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Line Playback Source",
.info = tascam_playback_source_info,
.get = tascam_line_out_get,
.put = tascam_line_out_put,
};
/**
* tascam_digital_out_get() - ALSA control get callback for Digital Outputs
* Source.
* @kcontrol: The ALSA kcontrol instance.
* @ucontrol: The ALSA control element value structure to fill.
*
* This function retrieves the current selection for the Digital Outputs source
* (Playback 1-2 or Playback 3-4) from the driver's private data and populates
* the ALSA control element value.
*
* Return: 0 on success.
*/
static int tascam_digital_out_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
scoped_guard(spinlock_irqsave, &tascam->lock) {
ucontrol->value.enumerated.item[0] = tascam->digital_out_source;
}
return 0;
}
/**
* tascam_digital_out_put() - ALSA control put callback for Digital Outputs
* Source.
* @kcontrol: The ALSA kcontrol instance.
* @ucontrol: The ALSA control element value structure containing the new value.
*
* This function sets the Digital Outputs source (Playback 1-2 or Playback 3-4)
* based on the user's selection from the ALSA control element. It validates
* the input and updates the driver's private data.
*
* Return: 1 if the value was changed, 0 if unchanged, or a negative error code.
*/
static int tascam_digital_out_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
int changed = 0;
if (ucontrol->value.enumerated.item[0] > 1)
return -EINVAL;
scoped_guard(spinlock_irqsave, &tascam->lock) {
if (tascam->digital_out_source != ucontrol->value.enumerated.item[0]) {
tascam->digital_out_source = ucontrol->value.enumerated.item[0];
changed = 1;
}
}
return changed;
}
/**
* tascam_digital_out_control - ALSA kcontrol definition for Digital Outputs
* Source.
*
* This defines a new ALSA mixer control named "Digital OUTPUTS Source" that
* allows the user to select between "Playback 1-2" and "Playback 3-4" for the
* digital outputs of the device. It uses the `tascam_playback_source_info` for
* information and `tascam_digital_out_get`/`tascam_digital_out_put` for value
* handling.
*/
static const struct snd_kcontrol_new tascam_digital_out_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Digital Playback Source",
.info = tascam_playback_source_info,
.get = tascam_digital_out_get,
.put = tascam_digital_out_put,
};
/**
* tascam_capture_source_info() - ALSA control info callback for capture source.
* @kcontrol: The ALSA kcontrol instance.
* @uinfo: The ALSA control element info structure to fill.
*
* This function provides information about the enumerated capture source
* control, including its type, count, and available items (Analog In, Digital
* In).
*
* Return: 0 on success.
*/
static int tascam_capture_source_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
return snd_ctl_enum_info(uinfo, 1, 2, capture_source_texts);
}
/**
* tascam_capture_12_get() - ALSA control get callback for Capture channels 1
* and 2 Source.
* @kcontrol: The ALSA kcontrol instance.
* @ucontrol: The ALSA control element value structure to fill.
*
* This function retrieves the current selection for the Capture channels 1 and
* 2 source (Analog In or Digital In) from the driver's private data and
* populates the ALSA control element value.
*
* Return: 0 on success.
*/
static int tascam_capture_12_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
scoped_guard(spinlock_irqsave, &tascam->lock) {
ucontrol->value.enumerated.item[0] = tascam->capture_12_source;
}
return 0;
}
/**
* tascam_capture_12_put() - ALSA control put callback for Capture channels 1
* and 2 Source.
* @kcontrol: The ALSA kcontrol instance.
* @ucontrol: The ALSA control element value structure containing the new value.
*
* This function sets the Capture channels 1 and 2 source (Analog In or Digital
* In) based on the user's selection from the ALSA control element. It validates
* the input and updates the driver's private data.
*
* Return: 1 if the value was changed, 0 if unchanged, or a negative error code.
*/
static int tascam_capture_12_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
int changed = 0;
if (ucontrol->value.enumerated.item[0] > 1)
return -EINVAL;
scoped_guard(spinlock_irqsave, &tascam->lock) {
if (tascam->capture_12_source != ucontrol->value.enumerated.item[0]) {
tascam->capture_12_source = ucontrol->value.enumerated.item[0];
changed = 1;
}
}
return changed;
}
/**
* tascam_capture_12_control - ALSA kcontrol definition for Capture channels 1
* and 2 Source.
*
* This defines a new ALSA mixer control named "ch1 and ch2 Source" that allows
* the user to select between "Analog In" and "Digital In" for the first two
* capture channels of the device. It uses the `tascam_capture_source_info` for
* information and `tascam_capture_12_get`/`tascam_capture_12_put` for value
* handling.
*/
static const struct snd_kcontrol_new tascam_capture_12_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Ch1/2 Capture Source",
.info = tascam_capture_source_info,
.get = tascam_capture_12_get,
.put = tascam_capture_12_put,
};
/**
* tascam_capture_34_get() - ALSA control get callback for Capture channels 3
* and 4 Source.
* @kcontrol: The ALSA kcontrol instance.
* @ucontrol: The ALSA control element value structure to fill.
*
* This function retrieves the current selection for the Capture channels 3 and
* 4 source (Analog In or Digital In) from the driver's private data and
* populates the ALSA control element value.
*
* Return: 0 on success.
*/
static int tascam_capture_34_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
scoped_guard(spinlock_irqsave, &tascam->lock) {
ucontrol->value.enumerated.item[0] = tascam->capture_34_source;
}
return 0;
}
/**
* tascam_capture_34_put() - ALSA control put callback for Capture channels 3
* and 4 Source.
* @kcontrol: The ALSA kcontrol instance.
* @ucontrol: The ALSA control element value structure containing the new value.
*
* This function sets the Capture channels 3 and 4 source (Analog In or Digital
* In) based on the user's selection from the ALSA control element. It validates
* the input and updates the driver's private data.
*
* Return: 1 if the value was changed, 0 if unchanged, or a negative error code.
*/
static int tascam_capture_34_put(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
int changed = 0;
if (ucontrol->value.enumerated.item[0] > 1)
return -EINVAL;
scoped_guard(spinlock_irqsave, &tascam->lock) {
if (tascam->capture_34_source != ucontrol->value.enumerated.item[0]) {
tascam->capture_34_source = ucontrol->value.enumerated.item[0];
changed = 1;
}
}
return changed;
}
/**
* tascam_capture_34_control - ALSA kcontrol definition for Capture channels 3
* and 4 Source.
*
* This defines a new ALSA mixer control named "ch3 and ch4 Source" that allows
* the user to select between "Analog In" and "Digital In" for the third and
* fourth capture channels of the device. It uses the
* `tascam_capture_source_info` for information and
* `tascam_capture_34_get`/`tascam_capture_34_put` for value handling.
*/
static const struct snd_kcontrol_new tascam_capture_34_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Ch3/4 Capture Source",
.info = tascam_capture_source_info,
.get = tascam_capture_34_get,
.put = tascam_capture_34_put,
};
/**
* tascam_samplerate_info() - ALSA control info callback for Sample Rate.
* @kcontrol: The ALSA kcontrol instance.
* @uinfo: The ALSA control element info structure to fill.
*
* This function provides information about the Sample Rate control, defining
* it as an integer type with a minimum value of 0 and a maximum of 96000.
*
* Return: 0 on success.
*/
static int tascam_samplerate_info(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_info *uinfo)
{
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = 1;
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 96000;
return 0;
}
/**
* tascam_samplerate_get() - ALSA control get callback for Sample Rate.
* @kcontrol: The ALSA kcontrol instance.
* @ucontrol: The ALSA control element value structure to fill.
*
* This function retrieves the current sample rate from the device via a USB
* control message and populates the ALSA control element value. If the rate
* is already known (i.e., `current_rate` is set), it returns that value
* directly.
*
* Return: 0 on success, or a negative error code on failure.
*/
static int tascam_samplerate_get(struct snd_kcontrol *kcontrol,
struct snd_ctl_elem_value *ucontrol)
{
struct tascam_card *tascam =
(struct tascam_card *)snd_kcontrol_chip(kcontrol);
u8 *buf __free(kfree) = NULL;
int err;
u32 rate = 0;
scoped_guard(spinlock_irqsave, &tascam->lock) {
if (tascam->current_rate > 0) {
ucontrol->value.integer.value[0] = tascam->current_rate;
return 0;
}
}
buf = kmalloc(3, GFP_KERNEL);
if (!buf)
return -ENOMEM;
err = usb_control_msg(tascam->dev, usb_rcvctrlpipe(tascam->dev, 0),
UAC_GET_CUR, RT_D2H_CLASS_EP,
UAC_SAMPLING_FREQ_CONTROL, EP_AUDIO_IN, buf, 3,
USB_CTRL_TIMEOUT_MS);
if (err >= 3)
rate = buf[0] | (buf[1] << 8) | (buf[2] << 16);
ucontrol->value.integer.value[0] = rate;
return 0;
}
/**
* tascam_samplerate_control - ALSA kcontrol definition for Sample Rate.
*
* This defines a new ALSA mixer control named "Sample Rate" that displays
* the current sample rate of the device. It is a read-only control.
*/
static const struct snd_kcontrol_new tascam_samplerate_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Sample Rate",
.info = tascam_samplerate_info,
.get = tascam_samplerate_get,
.access = SNDRV_CTL_ELEM_ACCESS_READ,
};
int tascam_create_controls(struct tascam_card *tascam)
{
int err;
err = snd_ctl_add(tascam->card,
snd_ctl_new1(&tascam_line_out_control, tascam));
if (err < 0)
return err;
err = snd_ctl_add(tascam->card,
snd_ctl_new1(&tascam_digital_out_control, tascam));
if (err < 0)
return err;
err = snd_ctl_add(tascam->card,
snd_ctl_new1(&tascam_capture_12_control, tascam));
if (err < 0)
return err;
err = snd_ctl_add(tascam->card,
snd_ctl_new1(&tascam_capture_34_control, tascam));
if (err < 0)
return err;
err = snd_ctl_add(tascam->card,
snd_ctl_new1(&tascam_samplerate_control, tascam));
if (err < 0)
return err;
return 0;
}

View File

@ -3,401 +3,247 @@
#include "us144mkii.h"
/**
* tascam_midi_in_work_handler() - Deferred work for processing MIDI input.
* @work: The work_struct instance.
*
* This function runs in a thread context. It safely reads raw USB data from
* the kfifo, processes it by stripping protocol-specific padding bytes, and
* passes the clean MIDI data to the ALSA rawmidi subsystem.
*/
static void tascam_midi_in_work_handler(struct work_struct *work)
{
struct tascam_card *tascam =
container_of(work, struct tascam_card, midi_in_work);
u8 buf[9];
u8 clean_buf[8];
unsigned int count, clean_count;
if (!tascam->midi_in_substream)
return;
while (kfifo_out_spinlocked(&tascam->midi_in_fifo, buf, sizeof(buf),
&tascam->midi_in_lock) == sizeof(buf)) {
clean_count = 0;
for (count = 0; count < 8; ++count) {
if (buf[count] != 0xfd)
clean_buf[clean_count++] = buf[count];
}
if (clean_count > 0)
snd_rawmidi_receive(tascam->midi_in_substream,
clean_buf, clean_count);
}
}
void tascam_midi_in_urb_complete(struct urb *urb)
static void tascam_midi_out_complete(struct urb *urb)
{
struct tascam_card *tascam = urb->context;
int ret;
unsigned long flags;
int count;
bool submit = false;
bool active;
if (!tascam)
goto out;
spin_lock_irqsave(&tascam->midi_lock, flags);
if (urb->status) {
if (urb->status != -ENOENT && urb->status != -ECONNRESET &&
urb->status != -ESHUTDOWN && urb->status != -EPROTO) {
dev_err_ratelimited(tascam->card->dev,
"MIDI IN URB failed: status %d\n",
urb->status);
if (urb->status || !tascam->midi_output) {
tascam->midi_out_active = false;
spin_unlock_irqrestore(&tascam->midi_lock, flags);
return;
}
active = tascam->midi_out_active;
spin_unlock_irqrestore(&tascam->midi_lock, flags);
if (!active)
return;
count = snd_rawmidi_transmit(tascam->midi_output, tascam->midi_out_buf, MIDI_PAYLOAD_SIZE);
if (count > 0) {
if (count < MIDI_PAYLOAD_SIZE)
memset(tascam->midi_out_buf + count, 0xFD, MIDI_PAYLOAD_SIZE - count);
tascam->midi_out_buf[8] = 0xE0;
urb->transfer_buffer_length = MIDI_PACKET_SIZE;
submit = true;
} else {
spin_lock_irqsave(&tascam->midi_lock, flags);
tascam->midi_out_active = false;
spin_unlock_irqrestore(&tascam->midi_lock, flags);
}
if (submit) {
if (usb_submit_urb(urb, GFP_ATOMIC) < 0) {
spin_lock_irqsave(&tascam->midi_lock, flags);
tascam->midi_out_active = false;
spin_unlock_irqrestore(&tascam->midi_lock, flags);
}
goto out;
}
if (atomic_read(&tascam->midi_in_active) &&
urb->actual_length > 0) {
kfifo_in_spinlocked(&tascam->midi_in_fifo, urb->transfer_buffer,
urb->actual_length, &tascam->midi_in_lock);
schedule_work(&tascam->midi_in_work);
}
usb_get_urb(urb);
usb_anchor_urb(urb, &tascam->midi_in_anchor);
ret = usb_submit_urb(urb, GFP_ATOMIC);
if (ret < 0) {
dev_err(tascam->card->dev,
"Failed to resubmit MIDI IN URB: error %d\n", ret);
usb_unanchor_urb(urb);
goto out;
}
out:
usb_put_urb(urb);
}
/**
* tascam_midi_in_open() - Opens the MIDI input substream.
* @substream: The ALSA rawmidi substream to open.
*
* This function stores a reference to the MIDI input substream in the
* driver's private data.
*
* Return: 0 on success.
*/
static int tascam_midi_in_open(struct snd_rawmidi_substream *substream)
static void tascam_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
{
struct tascam_card *tascam = substream->rmidi->private_data;
tascam->midi_in_substream = substream;
return 0;
}
/**
* tascam_midi_in_close() - Closes the MIDI input substream.
* @substream: The ALSA rawmidi substream to close.
*
* Return: 0 on success.
*/
static int tascam_midi_in_close(struct snd_rawmidi_substream *substream)
{
return 0;
}
/**
* tascam_midi_in_trigger() - Triggers MIDI input stream activity.
* @substream: The ALSA rawmidi substream.
* @up: Boolean indicating whether to start (1) or stop (0) the stream.
*
* This function starts or stops the MIDI input URBs based on the 'up'
* parameter. When starting, it resets the kfifo and submits all MIDI input
* URBs. When stopping, it kills all anchored MIDI input URBs and cancels the
* associated workqueue.
*/
static void tascam_midi_in_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct tascam_card *tascam = substream->rmidi->private_data;
int i, err;
unsigned long flags;
int count;
bool do_submit = false;
if (up) {
if (atomic_xchg(&tascam->midi_in_active, 1) == 0) {
scoped_guard(spinlock_irqsave, &tascam->midi_in_lock)
{
kfifo_reset(&tascam->midi_in_fifo);
}
spin_lock_irqsave(&tascam->midi_lock, flags);
tascam->midi_output = substream;
if (!tascam->midi_out_active) {
tascam->midi_out_active = true;
do_submit = true;
}
spin_unlock_irqrestore(&tascam->midi_lock, flags);
for (i = 0; i < NUM_MIDI_IN_URBS; i++) {
usb_get_urb(tascam->midi_in_urbs[i]);
usb_anchor_urb(tascam->midi_in_urbs[i],
&tascam->midi_in_anchor);
err = usb_submit_urb(tascam->midi_in_urbs[i],
GFP_KERNEL);
if (err < 0) {
dev_err(tascam->card->dev,
"Failed to submit MIDI IN URB %d: %d\n",
i, err);
usb_unanchor_urb(
tascam->midi_in_urbs[i]);
usb_put_urb(tascam->midi_in_urbs[i]);
if (do_submit) {
count = snd_rawmidi_transmit(substream, tascam->midi_out_buf, MIDI_PAYLOAD_SIZE);
if (count > 0) {
if (count < MIDI_PAYLOAD_SIZE)
memset(tascam->midi_out_buf + count, 0xFD, MIDI_PAYLOAD_SIZE - count);
tascam->midi_out_buf[8] = 0xE0;
tascam->midi_out_urb->transfer_buffer_length = MIDI_PACKET_SIZE;
usb_anchor_urb(tascam->midi_out_urb, &tascam->midi_anchor);
if (usb_submit_urb(tascam->midi_out_urb, GFP_ATOMIC) < 0) {
usb_unanchor_urb(tascam->midi_out_urb);
spin_lock_irqsave(&tascam->midi_lock, flags);
tascam->midi_out_active = false;
spin_unlock_irqrestore(&tascam->midi_lock, flags);
}
} else {
spin_lock_irqsave(&tascam->midi_lock, flags);
tascam->midi_out_active = false;
spin_unlock_irqrestore(&tascam->midi_lock, flags);
}
}
} else {
if (atomic_xchg(&tascam->midi_in_active, 0) == 1) {
usb_kill_anchored_urbs(&tascam->midi_in_anchor);
cancel_work_sync(&tascam->midi_in_work);
}
spin_lock_irqsave(&tascam->midi_lock, flags);
tascam->midi_output = NULL;
spin_unlock_irqrestore(&tascam->midi_lock, flags);
}
}
/**
* tascam_midi_in_ops - ALSA rawmidi operations for MIDI input.
*
* This structure defines the callback functions for MIDI input stream
* operations, including open, close, and trigger.
*/
static const struct snd_rawmidi_ops tascam_midi_in_ops = {
.open = tascam_midi_in_open,
.close = tascam_midi_in_close,
.trigger = tascam_midi_in_trigger,
};
void tascam_midi_out_urb_complete(struct urb *urb)
static void tascam_midi_in_complete(struct urb *urb)
{
struct tascam_card *tascam = urb->context;
int i, urb_index = -1;
if (urb->status) {
if (urb->status != -ENOENT && urb->status != -ECONNRESET &&
urb->status != -ESHUTDOWN) {
dev_err_ratelimited(tascam->card->dev,
"MIDI OUT URB failed: %d\n",
urb->status);
}
goto out;
}
if (!tascam)
goto out;
for (i = 0; i < NUM_MIDI_OUT_URBS; i++) {
if (tascam->midi_out_urbs[i] == urb) {
urb_index = i;
break;
}
}
if (urb_index < 0) {
dev_err_ratelimited(tascam->card->dev,
"Unknown MIDI OUT URB completed!\n");
goto out;
}
scoped_guard(spinlock_irqsave, &tascam->midi_out_lock)
{
clear_bit(urb_index, &tascam->midi_out_urbs_in_flight);
}
if (atomic_read(&tascam->midi_out_active))
schedule_work(&tascam->midi_out_work);
out:
usb_put_urb(urb);
}
/**
* tascam_midi_out_work_handler() - Deferred work for sending MIDI data
* @work: The work_struct instance.
*
* This function handles the proprietary output protocol: take the raw MIDI
* message bytes from the application, place them at the start of a 9-byte
* buffer, pad the rest with 0xFD, and add a terminator byte (0x00).
* This function pulls as many bytes as will fit into one packet from the
* ALSA buffer and sends them.
*/
static void tascam_midi_out_work_handler(struct work_struct *work)
{
struct tascam_card *tascam =
container_of(work, struct tascam_card, midi_out_work);
struct snd_rawmidi_substream *substream = tascam->midi_out_substream;
int i;
if (!substream || !atomic_read(&tascam->midi_out_active))
if (urb->status)
return;
while (snd_rawmidi_transmit_peek(substream, (u8[]){ 0 }, 1) == 1) {
int urb_index;
struct urb *urb;
u8 *buf;
int bytes_to_send;
if (urb->actual_length == MIDI_PACKET_SIZE && tascam->midi_input) {
u8 *data = urb->transfer_buffer;
scoped_guard(spinlock_irqsave, &tascam->midi_out_lock) {
urb_index = -1;
for (i = 0; i < NUM_MIDI_OUT_URBS; i++) {
if (!test_bit(
i,
&tascam->midi_out_urbs_in_flight)) {
urb_index = i;
break;
}
}
if (urb_index < 0)
return; /* No free URBs, will be rescheduled by
* completion handler
*/
urb = tascam->midi_out_urbs[urb_index];
buf = urb->transfer_buffer;
bytes_to_send = snd_rawmidi_transmit(substream, buf, 8);
if (bytes_to_send <= 0)
break; /* No more data */
if (bytes_to_send < 9)
memset(buf + bytes_to_send, 0xfd,
9 - bytes_to_send);
buf[8] = 0xe0;
set_bit(urb_index, &tascam->midi_out_urbs_in_flight);
urb->transfer_buffer_length = 9;
}
usb_get_urb(urb);
usb_anchor_urb(urb, &tascam->midi_out_anchor);
if (usb_submit_urb(urb, GFP_KERNEL) < 0) {
dev_err_ratelimited(
tascam->card->dev,
"Failed to submit MIDI OUT URB %d\n",
urb_index);
scoped_guard(spinlock_irqsave, &tascam->midi_out_lock)
{
clear_bit(urb_index,
&tascam->midi_out_urbs_in_flight);
}
usb_unanchor_urb(urb);
usb_put_urb(urb);
break; /* Stop on error */
}
}
}
/**
* tascam_midi_out_open() - Opens the MIDI output substream.
* @substream: The ALSA rawmidi substream to open.
*
* This function stores a reference to the MIDI output substream in the
* driver's private data and initializes the MIDI running status.
*
* Return: 0 on success.
*/
static int tascam_midi_out_open(struct snd_rawmidi_substream *substream)
{
struct tascam_card *tascam = substream->rmidi->private_data;
tascam->midi_out_substream = substream;
/* Initialize the running status state for the packet packer. */
tascam->midi_running_status = 0;
return 0;
}
/**
* tascam_midi_out_close() - Closes the MIDI output substream.
* @substream: The ALSA rawmidi substream to close.
*
* Return: 0 on success.
*/
static int tascam_midi_out_close(struct snd_rawmidi_substream *substream)
{
return 0;
}
/**
* tascam_midi_out_drain() - Drains the MIDI output stream.
* @substream: The ALSA rawmidi substream.
*
* This function cancels any pending MIDI output work and kills all
* anchored MIDI output URBs, ensuring all data is sent or discarded.
*/
static void tascam_midi_out_drain(struct snd_rawmidi_substream *substream)
{
struct tascam_card *tascam = substream->rmidi->private_data;
bool in_flight = true;
while (in_flight) {
in_flight = false;
for (int i = 0; i < NUM_MIDI_OUT_URBS; i++) {
if (test_bit(i, &tascam->midi_out_urbs_in_flight)) {
in_flight = true;
for (i = 0; i < MIDI_PAYLOAD_SIZE; i++) {
if (data[i] == 0xFD)
break;
}
snd_rawmidi_receive(tascam->midi_input, &data[i], 1);
}
if (in_flight)
schedule_timeout_uninterruptible(1);
}
cancel_work_sync(&tascam->midi_out_work);
usb_kill_anchored_urbs(&tascam->midi_out_anchor);
if (usb_submit_urb(urb, GFP_ATOMIC) < 0)
usb_unanchor_urb(urb);
}
/**
* tascam_midi_out_trigger() - Triggers MIDI output stream activity.
* @substream: The ALSA rawmidi substream.
* @up: Boolean indicating whether to start (1) or stop (0) the stream.
*
* This function starts or stops the MIDI output workqueue based on the
* 'up' parameter.
*/
static void tascam_midi_out_trigger(struct snd_rawmidi_substream *substream,
int up)
static void tascam_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
{
struct tascam_card *tascam = substream->rmidi->private_data;
if (up) {
atomic_set(&tascam->midi_out_active, 1);
schedule_work(&tascam->midi_out_work);
} else {
atomic_set(&tascam->midi_out_active, 0);
}
if (up)
tascam->midi_input = substream;
else
tascam->midi_input = NULL;
}
/**
* tascam_midi_out_ops - ALSA rawmidi operations for MIDI output.
*
* This structure defines the callback functions for MIDI output stream
* operations, including open, close, trigger, and drain.
*/
static const struct snd_rawmidi_ops tascam_midi_out_ops = {
.open = tascam_midi_out_open,
.close = tascam_midi_out_close,
.trigger = tascam_midi_out_trigger,
.drain = tascam_midi_out_drain,
static int tascam_midi_open(struct snd_rawmidi_substream *substream)
{
struct tascam_card *tascam = substream->rmidi->private_data;
if (substream->stream == SNDRV_RAWMIDI_STREAM_OUTPUT) {
unsigned long flags;
spin_lock_irqsave(&tascam->midi_lock, flags);
tascam->midi_out_active = false;
spin_unlock_irqrestore(&tascam->midi_lock, flags);
} else if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT) {
usb_anchor_urb(tascam->midi_in_urb, &tascam->midi_anchor);
if (usb_submit_urb(tascam->midi_in_urb, GFP_KERNEL) < 0) {
usb_unanchor_urb(tascam->midi_in_urb);
return -EIO;
}
}
return 0;
}
static int tascam_midi_close(struct snd_rawmidi_substream *substream)
{
struct tascam_card *tascam = substream->rmidi->private_data;
if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT)
usb_kill_urb(tascam->midi_in_urb);
return 0;
}
static const struct snd_rawmidi_ops midi_output_ops = {
.open = tascam_midi_open,
.close = tascam_midi_close,
.trigger = tascam_midi_output_trigger,
};
static const struct snd_rawmidi_ops midi_input_ops = {
.open = tascam_midi_open,
.close = tascam_midi_close,
.trigger = tascam_midi_input_trigger,
};
/**
* tascam_create_midi - create and initialize the MIDI device
* @tascam: the tascam_card instance
*
* Return: 0 on success, or a negative error code on failure.
*/
int tascam_create_midi(struct tascam_card *tascam)
{
int err;
struct snd_rawmidi *rmidi;
err = snd_rawmidi_new(tascam->card, "US144MKII MIDI", 0, 1, 1,
&tascam->rmidi);
err = snd_rawmidi_new(tascam->card, "TASCAM MIDI", 0, 1, 1, &rmidi);
if (err < 0)
return err;
strscpy(tascam->rmidi->name, "US144MKII MIDI",
sizeof(tascam->rmidi->name));
tascam->rmidi->private_data = tascam;
rmidi->private_data = tascam;
strscpy(rmidi->name, "TASCAM US-144MKII MIDI", sizeof(rmidi->name));
snd_rawmidi_set_ops(tascam->rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
&tascam_midi_in_ops);
snd_rawmidi_set_ops(tascam->rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
&tascam_midi_out_ops);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &midi_output_ops);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &midi_input_ops);
tascam->rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT |
SNDRV_RAWMIDI_INFO_OUTPUT |
SNDRV_RAWMIDI_INFO_DUPLEX;
rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
SNDRV_RAWMIDI_INFO_INPUT |
SNDRV_RAWMIDI_INFO_DUPLEX;
tascam->rmidi = rmidi;
INIT_WORK(&tascam->midi_in_work, tascam_midi_in_work_handler);
INIT_WORK(&tascam->midi_out_work, tascam_midi_out_work_handler);
tascam->midi_out_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!tascam->midi_out_urb) {
err = -ENOMEM;
goto err_out_urb;
}
tascam->midi_in_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!tascam->midi_in_urb) {
err = -ENOMEM;
goto err_in_urb;
}
tascam->midi_out_buf = usb_alloc_coherent(tascam->dev, MIDI_PACKET_SIZE,
GFP_KERNEL, &tascam->midi_out_urb->transfer_dma);
if (!tascam->midi_out_buf) {
err = -ENOMEM;
goto err_out_buf;
}
tascam->midi_in_buf = usb_alloc_coherent(tascam->dev, MIDI_PACKET_SIZE,
GFP_KERNEL, &tascam->midi_in_urb->transfer_dma);
if (!tascam->midi_in_buf) {
err = -ENOMEM;
goto err_in_buf;
}
usb_fill_bulk_urb(tascam->midi_out_urb, tascam->dev,
usb_sndbulkpipe(tascam->dev, EP_MIDI_OUT),
tascam->midi_out_buf, MIDI_PACKET_SIZE,
tascam_midi_out_complete, tascam);
tascam->midi_out_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
usb_fill_bulk_urb(tascam->midi_in_urb, tascam->dev,
usb_rcvbulkpipe(tascam->dev, EP_MIDI_IN),
tascam->midi_in_buf, MIDI_PACKET_SIZE,
tascam_midi_in_complete, tascam);
tascam->midi_in_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
spin_lock_init(&tascam->midi_lock);
init_usb_anchor(&tascam->midi_anchor);
return 0;
err_in_buf:
usb_free_coherent(tascam->dev, MIDI_PACKET_SIZE,
tascam->midi_out_buf, tascam->midi_out_urb->transfer_dma);
err_out_buf:
usb_free_urb(tascam->midi_in_urb);
tascam->midi_in_urb = NULL;
err_in_urb:
usb_free_urb(tascam->midi_out_urb);
tascam->midi_out_urb = NULL;
err_out_urb:
return err;
}

View File

@ -1,124 +1,40 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2025 Šerif Rami <ramiserifpersia@gmail.com>
#include "us144mkii.h"
#include "us144mkii_pcm.h"
static int tascam_write_regs(struct tascam_card *tascam, const u16 *regs, size_t count)
{
int i, err = 0;
struct usb_device *dev = tascam->dev;
for (i = 0; i < count; i++) {
err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV,
regs[i], REG_VAL_ENABLE, NULL, 0, USB_CTRL_TIMEOUT_MS);
if (err < 0)
return err;
}
return 0;
}
/**
* fpo_init_pattern() - Generates a packet distribution pattern.
* @size: The number of elements in the pattern array (e.g., 8).
* @pattern_array: Pointer to the array to be populated.
* @initial_value: The base value to initialize each element with.
* @target_sum: The desired sum of all elements in the final array.
* us144mkii_configure_device_for_rate() - set sample rate via USB control msgs
* @tascam: the tascam_card instance
* @rate: the target sample rate (e.g., 44100, 96000)
*
* This function initializes an array with a base value and then iteratively
* adjusts the elements to match a target sum, distributing the difference
* as evenly as possible.
* This function sends a sequence of vendor-specific and UAC control messages
* to configure the device hardware for the specified sample rate.
*
* Return: 0 on success, or a negative error code on failure.
*/
static void fpo_init_pattern(unsigned int size, unsigned int *pattern_array,
unsigned int initial_value, int target_sum)
{
int diff, i;
if (!size)
return;
for (i = 0; i < size; ++i)
pattern_array[i] = initial_value;
diff = target_sum - (size * initial_value);
for (i = 0; i < abs(diff); ++i) {
if (diff > 0)
pattern_array[i]++;
else
pattern_array[i]--;
}
}
const struct snd_pcm_hardware tascam_pcm_hw = {
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
.formats = SNDRV_PCM_FMTBIT_S24_3LE,
.rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000),
.rate_min = 44100,
.rate_max = 96000,
.channels_min = NUM_CHANNELS,
.channels_max = NUM_CHANNELS,
.buffer_bytes_max = 1024 * 1024,
.period_bytes_min = 48 * BYTES_PER_FRAME,
.period_bytes_max = 1024 * BYTES_PER_FRAME,
.periods_min = 2,
.periods_max = 1024,
};
void process_playback_routing_us144mkii(struct tascam_card *tascam,
const u8 *src_buffer, u8 *dst_buffer,
size_t frames)
{
size_t f;
const u8 *src_12, *src_34;
u8 *dst_line, *dst_digital;
for (f = 0; f < frames; ++f) {
src_12 = src_buffer + f * BYTES_PER_FRAME;
src_34 = src_12 + (2 * BYTES_PER_SAMPLE);
dst_line = dst_buffer + f * BYTES_PER_FRAME;
dst_digital = dst_line + (2 * BYTES_PER_SAMPLE);
/* LINE OUTPUTS (ch1/2 on device) */
if (tascam->line_out_source == 0) /* "ch1 and ch2" */
memcpy(dst_line, src_12, 2 * BYTES_PER_SAMPLE);
else /* "ch3 and ch4" */
memcpy(dst_line, src_34, 2 * BYTES_PER_SAMPLE);
/* DIGITAL OUTPUTS (ch3/4 on device) */
if (tascam->digital_out_source == 0) /* "ch1 and ch2" */
memcpy(dst_digital, src_12, 2 * BYTES_PER_SAMPLE);
else /* "ch3 and ch4" */
memcpy(dst_digital, src_34, 2 * BYTES_PER_SAMPLE);
}
}
void process_capture_routing_us144mkii(struct tascam_card *tascam,
const s32 *decoded_block,
s32 *routed_block)
{
int f;
const s32 *src_frame;
s32 *dst_frame;
for (f = 0; f < FRAMES_PER_DECODE_BLOCK; f++) {
src_frame = decoded_block + (f * DECODED_CHANNELS_PER_FRAME);
dst_frame = routed_block + (f * DECODED_CHANNELS_PER_FRAME);
/* ch1 and ch2 Source */
if (tascam->capture_12_source == 0) { /* analog inputs */
dst_frame[0] = src_frame[0]; /* Analog L */
dst_frame[1] = src_frame[1]; /* Analog R */
} else { /* digital inputs */
dst_frame[0] = src_frame[2]; /* Digital L */
dst_frame[1] = src_frame[3]; /* Digital R */
}
/* ch3 and ch4 Source */
if (tascam->capture_34_source == 0) { /* analog inputs */
dst_frame[2] = src_frame[0]; /* Analog L (Duplicate) */
dst_frame[3] = src_frame[1]; /* Analog R (Duplicate) */
} else { /* digital inputs */
dst_frame[2] = src_frame[2]; /* Digital L */
dst_frame[3] = src_frame[3]; /* Digital R */
}
}
}
int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int rate)
{
struct usb_device *dev = tascam->dev;
u8 *rate_payload_buf __free(kfree) = NULL;
u16 rate_vendor_wValue;
u8 *rate_payload_buf;
int err = 0;
const u8 *current_payload_src;
u16 rate_reg;
static const u8 payload_44100[] = { 0x44, 0xac, 0x00 };
static const u8 payload_48000[] = { 0x80, 0xbb, 0x00 };
@ -128,23 +44,21 @@ int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int rate)
switch (rate) {
case 44100:
current_payload_src = payload_44100;
rate_vendor_wValue = REG_ADDR_RATE_44100;
rate_reg = REG_ADDR_RATE_44100;
break;
case 48000:
current_payload_src = payload_48000;
rate_vendor_wValue = REG_ADDR_RATE_48000;
rate_reg = REG_ADDR_RATE_48000;
break;
case 88200:
current_payload_src = payload_88200;
rate_vendor_wValue = REG_ADDR_RATE_88200;
rate_reg = REG_ADDR_RATE_88200;
break;
case 96000:
current_payload_src = payload_96000;
rate_vendor_wValue = REG_ADDR_RATE_96000;
rate_reg = REG_ADDR_RATE_96000;
break;
default:
dev_err(&dev->dev,
"Unsupported sample rate %d for configuration\n", rate);
return -EINVAL;
}
@ -152,97 +66,69 @@ int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int rate)
if (!rate_payload_buf)
return -ENOMEM;
dev_info(&dev->dev, "Configuring device for %d Hz\n", rate);
err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
VENDOR_REQ_MODE_CONTROL, RT_H2D_VENDOR_DEV,
MODE_VAL_CONFIG, 0x0000, NULL, 0, USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto fail;
usb_control_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR,
RT_H2D_CLASS_EP, UAC_SAMPLING_FREQ_CONTROL,
EP_AUDIO_IN, rate_payload_buf, 3, USB_CTRL_TIMEOUT_MS);
usb_control_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR,
RT_H2D_CLASS_EP, UAC_SAMPLING_FREQ_CONTROL,
EP_AUDIO_OUT, rate_payload_buf, 3, USB_CTRL_TIMEOUT_MS);
{
const u16 regs_to_write[] = {
REG_ADDR_UNKNOWN_0D, REG_ADDR_UNKNOWN_0E,
REG_ADDR_UNKNOWN_0F, rate_reg, REG_ADDR_UNKNOWN_11
};
err = tascam_write_regs(tascam, regs_to_write, ARRAY_SIZE(regs_to_write));
if (err < 0)
goto fail;
}
err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
VENDOR_REQ_MODE_CONTROL, RT_H2D_VENDOR_DEV,
MODE_VAL_CONFIG, 0x0000, NULL, 0,
USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto fail;
err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR,
RT_H2D_CLASS_EP, UAC_SAMPLING_FREQ_CONTROL,
EP_AUDIO_IN, rate_payload_buf, 3,
USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto fail;
err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), UAC_SET_CUR,
RT_H2D_CLASS_EP, UAC_SAMPLING_FREQ_CONTROL,
EP_AUDIO_OUT, rate_payload_buf, 3,
USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto fail;
err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV,
REG_ADDR_UNKNOWN_0D, REG_VAL_ENABLE, NULL, 0,
USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto fail;
err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV,
REG_ADDR_UNKNOWN_0E, REG_VAL_ENABLE, NULL, 0,
USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto fail;
err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV,
REG_ADDR_UNKNOWN_0F, REG_VAL_ENABLE, NULL, 0,
USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto fail;
err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV,
rate_vendor_wValue, REG_VAL_ENABLE, NULL, 0,
USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto fail;
err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
VENDOR_REQ_REGISTER_WRITE, RT_H2D_VENDOR_DEV,
REG_ADDR_UNKNOWN_11, REG_VAL_ENABLE, NULL, 0,
USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto fail;
err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
VENDOR_REQ_MODE_CONTROL, RT_H2D_VENDOR_DEV,
MODE_VAL_STREAM_START, 0x0000, NULL, 0,
USB_CTRL_TIMEOUT_MS);
VENDOR_REQ_MODE_CONTROL, RT_H2D_VENDOR_DEV,
MODE_VAL_STREAM_START, 0x0000, NULL, 0, USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto fail;
kfree(rate_payload_buf);
return 0;
fail:
dev_err(&dev->dev,
"Device configuration failed at rate %d with error %d\n", rate,
err);
kfree(rate_payload_buf);
return err;
}
/**
* tascam_pcm_hw_params() - configure hardware parameters for PCM streams
* @substream: the ALSA PCM substream
* @params: the hardware parameters to apply
*
* This function allocates pages for the PCM buffer and, for playback streams,
* selects the appropriate feedback patterns based on the requested sample rate.
* It also configures the device hardware for the selected sample rate if it
* has changed.
*
* Return: 0 on success, or a negative error code on failure.
*/
int tascam_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
struct snd_pcm_hw_params *params)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
int err;
unsigned int rate = params_rate(params);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
tascam->fpo.sample_rate_khz = rate / 1000;
tascam->fpo.base_feedback_value = tascam->fpo.sample_rate_khz;
tascam->fpo.feedback_offset = 2;
tascam->fpo.current_index = 0;
tascam->fpo.previous_index = 0;
tascam->fpo.sync_locked = false;
unsigned int initial_value = tascam->fpo.sample_rate_khz / 8;
for (int i = 0; i < 5; i++) {
int target_sum = tascam->fpo.sample_rate_khz -
tascam->fpo.feedback_offset + i;
fpo_init_pattern(8, tascam->fpo.full_frame_patterns[i],
initial_value, target_sum);
}
}
int err;
if (tascam->current_rate != rate) {
usb_kill_anchored_urbs(&tascam->playback_anchor);
usb_kill_anchored_urbs(&tascam->feedback_anchor);
usb_kill_anchored_urbs(&tascam->capture_anchor);
atomic_set(&tascam->active_urbs, 0);
err = us144mkii_configure_device_for_rate(tascam, rate);
if (err < 0) {
tascam->current_rate = 0;
@ -250,121 +136,17 @@ int tascam_pcm_hw_params(struct snd_pcm_substream *substream,
}
tascam->current_rate = rate;
}
return 0;
}
int tascam_pcm_hw_free(struct snd_pcm_substream *substream)
/**
* tascam_stop_pcm_work_handler() - work handler to stop PCM streams
* @work: pointer to the work_struct
*/
void tascam_stop_pcm_work_handler(struct work_struct *work)
{
return 0;
}
int tascam_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
int err = 0;
int i;
bool do_start = false;
bool do_stop = false;
scoped_guard(spinlock_irqsave, &tascam->lock) {
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
if (!atomic_read(&tascam->playback_active)) {
atomic_set(&tascam->playback_active, 1);
atomic_set(&tascam->capture_active, 1);
do_start = true;
}
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
if (atomic_read(&tascam->playback_active)) {
atomic_set(&tascam->playback_active, 0);
atomic_set(&tascam->capture_active, 0);
do_stop = true;
}
break;
default:
err = -EINVAL;
break;
}
}
if (do_start) {
if (atomic_read(&tascam->active_urbs) > 0) {
dev_warn(tascam->card->dev,
"Cannot start, URBs still active.\n");
return -EAGAIN;
}
for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
usb_get_urb(tascam->feedback_urbs[i]);
usb_anchor_urb(tascam->feedback_urbs[i],
&tascam->feedback_anchor);
err = usb_submit_urb(tascam->feedback_urbs[i],
GFP_ATOMIC);
if (err < 0) {
usb_unanchor_urb(tascam->feedback_urbs[i]);
usb_put_urb(tascam->feedback_urbs[i]);
atomic_dec(&tascam->active_urbs);
goto start_rollback;
}
atomic_inc(&tascam->active_urbs);
}
for (i = 0; i < NUM_PLAYBACK_URBS; i++) {
usb_get_urb(tascam->playback_urbs[i]);
usb_anchor_urb(tascam->playback_urbs[i],
&tascam->playback_anchor);
err = usb_submit_urb(tascam->playback_urbs[i],
GFP_ATOMIC);
if (err < 0) {
usb_unanchor_urb(tascam->playback_urbs[i]);
usb_put_urb(tascam->playback_urbs[i]);
atomic_dec(&tascam->active_urbs);
goto start_rollback;
}
atomic_inc(&tascam->active_urbs);
}
for (i = 0; i < NUM_CAPTURE_URBS; i++) {
usb_get_urb(tascam->capture_urbs[i]);
usb_anchor_urb(tascam->capture_urbs[i],
&tascam->capture_anchor);
err = usb_submit_urb(tascam->capture_urbs[i],
GFP_ATOMIC);
if (err < 0) {
usb_unanchor_urb(tascam->capture_urbs[i]);
usb_put_urb(tascam->capture_urbs[i]);
atomic_dec(&tascam->active_urbs);
goto start_rollback;
}
atomic_inc(&tascam->active_urbs);
}
return 0;
start_rollback:
dev_err(tascam->card->dev,
"Failed to submit URBs to start stream: %d\n", err);
do_stop = true;
}
if (do_stop)
schedule_work(&tascam->stop_work);
return err;
}
int tascam_init_pcm(struct snd_pcm *pcm)
{
struct tascam_card *tascam = pcm->private_data;
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &tascam_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &tascam_capture_ops);
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
tascam->dev->dev.parent, 64 * 1024,
tascam_pcm_hw.buffer_bytes_max);
return 0;
struct tascam_card *tascam = container_of(work, struct tascam_card, stop_pcm_work);
if (tascam->playback_substream)
snd_pcm_stop(tascam->playback_substream, SNDRV_PCM_STATE_XRUN);
}

View File

@ -6,160 +6,18 @@
#include "us144mkii.h"
/**
* tascam_pcm_hw - Hardware capabilities for TASCAM US-144MKII PCM.
*
* Defines the supported PCM formats, rates, channels, and buffer/period sizes
* for the TASCAM US-144MKII audio interface.
*/
extern const struct snd_pcm_hardware tascam_pcm_hw;
extern const struct snd_pcm_hardware tascam_playback_hw;
extern const struct snd_pcm_hardware tascam_capture_hw;
/**
* tascam_playback_ops - ALSA PCM operations for playback.
*
* This structure defines the callback functions for playback stream operations.
*/
extern const struct snd_pcm_ops tascam_playback_ops;
/**
* tascam_capture_ops - ALSA PCM operations for capture.
*
* This structure defines the callback functions for capture stream operations.
*/
extern const struct snd_pcm_ops tascam_capture_ops;
/**
* playback_urb_complete() - Completion handler for playback isochronous URBs.
* @urb: the completed URB
*
* This function runs in interrupt context. It calculates the number of bytes
* to send in the next set of packets based on the feedback-driven clock,
* copies the audio data from the ALSA ring buffer, and resubmits the URB.
*/
void playback_urb_complete(struct urb *urb);
/**
* feedback_urb_complete() - Completion handler for feedback isochronous URBs.
* @urb: the completed URB
*
* This is the master clock for the driver. It runs in interrupt context.
* It reads the feedback value from the device, which indicates how many
* samples the device has consumed. This information is used to adjust the
* playback rate and to advance the capture stream pointer, keeping both
* streams in sync. It then calls snd_pcm_period_elapsed if necessary and
* resubmits itself.
*/
void feedback_urb_complete(struct urb *urb);
/**
* capture_urb_complete() - Completion handler for capture bulk URBs.
* @urb: the completed URB
*
* This function runs in interrupt context. It copies the received raw data
* into an intermediate ring buffer and then schedules the workqueue to process
* it. It then resubmits the URB to receive more data.
*/
void capture_urb_complete(struct urb *urb);
/**
* tascam_stop_pcm_work_handler() - Work handler to stop PCM streams.
* @work: Pointer to the work_struct.
*
* This function is scheduled to stop PCM streams (playback and capture)
* from a workqueue context, avoiding blocking operations in interrupt context.
*/
void tascam_stop_pcm_work_handler(struct work_struct *work);
/**
* tascam_init_pcm() - Initializes the ALSA PCM device.
* @pcm: Pointer to the ALSA PCM device to initialize.
*
* This function sets up the PCM operations, adds ALSA controls for routing
* and sample rate, and preallocates pages for the PCM buffer.
*
* Return: 0 on success, or a negative error code on failure.
*/
int tascam_init_pcm(struct snd_pcm *pcm);
/**
* us144mkii_configure_device_for_rate() - Set sample rate via USB control msgs
* @tascam: the tascam_card instance
* @rate: the target sample rate (e.g., 44100, 96000)
*
* This function sends a sequence of vendor-specific and UAC control messages
* to configure the device hardware for the specified sample rate.
*
* Return: 0 on success, or a negative error code on failure.
*/
int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int rate);
/**
* process_playback_routing_us144mkii() - Apply playback routing matrix
* @tascam: The driver instance.
* @src_buffer: Buffer containing 4 channels of S24_3LE audio from ALSA.
* @dst_buffer: Buffer to be filled for the USB device.
* @frames: Number of frames to process.
*/
void process_playback_routing_us144mkii(struct tascam_card *tascam,
const u8 *src_buffer, u8 *dst_buffer,
size_t frames);
/**
* process_capture_routing_us144mkii() - Apply capture routing matrix
* @tascam: The driver instance.
* @decoded_block: Buffer containing 4 channels of S32LE decoded audio.
* @routed_block: Buffer to be filled for ALSA.
*/
void process_capture_routing_us144mkii(struct tascam_card *tascam,
const s32 *decoded_block,
s32 *routed_block);
/**
* tascam_pcm_hw_params() - Configures hardware parameters for PCM streams.
* @substream: The ALSA PCM substream.
* @params: The hardware parameters to apply.
*
* This function allocates pages for the PCM buffer and, for playback streams,
* selects the appropriate feedback patterns based on the requested sample rate.
* It also configures the device hardware for the selected sample rate if it
* has changed.
*
* Return: 0 on success, or a negative error code on failure.
*/
void tascam_stop_pcm_work_handler(struct work_struct *work);
int tascam_pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params);
/**
* tascam_pcm_hw_free() - Frees hardware parameters for PCM streams.
* @substream: The ALSA PCM substream.
*
* This function is a stub for freeing hardware-related resources.
*
* Return: 0 on success.
*/
int tascam_pcm_hw_free(struct snd_pcm_substream *substream);
/**
* tascam_pcm_trigger() - Triggers the start or stop of PCM streams.
* @substream: The ALSA PCM substream.
* @cmd: The trigger command (e.g., SNDRV_PCM_TRIGGER_START).
*
* This function handles starting and stopping of playback and capture streams
* by submitting or killing the associated URBs.
*
* Return: 0 on success, or a negative error code on failure.
*/
int tascam_pcm_trigger(struct snd_pcm_substream *substream, int cmd);
/**
* tascam_capture_work_handler() - Deferred work for processing capture data.
* @work: the work_struct instance
*
* This function runs in a kernel thread context, not an IRQ context. It reads
* raw data from the capture ring buffer, decodes it, applies routing, and
* copies the final audio data into the ALSA capture ring buffer. This offloads
* the CPU-intensive decoding from the time-sensitive URB completion handlers.
*/
void tascam_capture_work_handler(struct work_struct *work);
struct snd_pcm_hw_params *params);
#endif /* __US144MKII_PCM_H */

View File

@ -1,160 +1,196 @@
// SPDX-License-Identifier: GPL-2.0-only
// Copyright (c) 2025 Šerif Rami <ramiserifpersia@gmail.com>
#include "us144mkii.h"
#include "us144mkii_pcm.h"
const struct snd_pcm_hardware tascam_playback_hw = {
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME),
.formats = SNDRV_PCM_FMTBIT_S24_3LE,
.rates = (SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000),
.rate_min = 44100,
.rate_max = 96000,
.channels_min = NUM_CHANNELS,
.channels_max = NUM_CHANNELS,
.buffer_bytes_max = 1024 * 1024,
.period_bytes_min = 32 * BYTES_PER_FRAME,
.period_bytes_max = 1024 * BYTES_PER_FRAME,
.periods_min = 2,
.periods_max = 1024,
};
/**
* tascam_playback_open() - Opens the PCM playback substream.
* @substream: The ALSA PCM substream to open.
*
* This function sets the hardware parameters for the playback substream
* and stores a reference to the substream in the driver's private data.
*
* Return: 0 on success.
*/
static int tascam_playback_open(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
substream->runtime->hw = tascam_pcm_hw;
substream->runtime->hw = tascam_playback_hw;
tascam->playback_substream = substream;
atomic_set(&tascam->playback_active, 0);
return 0;
}
/**
* tascam_playback_close() - Closes the PCM playback substream.
* @substream: The ALSA PCM substream to close.
*
* This function clears the reference to the playback substream in the
* driver's private data.
*
* Return: 0 on success.
*/
static int tascam_playback_close(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
cancel_work_sync(&tascam->stop_pcm_work);
tascam->playback_substream = NULL;
return 0;
}
/**
* tascam_playback_prepare() - Prepares the PCM playback substream for use.
* @substream: The ALSA PCM substream to prepare.
*
* This function initializes playback-related counters and flags, and configures
* the playback URBs with appropriate packet sizes based on the nominal frame
* rate.
*
* Return: 0 on success.
*/
static int tascam_playback_prepare(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
struct snd_pcm_runtime *runtime = substream->runtime;
int i, u;
size_t nominal_frames_per_packet, nominal_bytes_per_packet;
size_t total_bytes_in_urb;
u32 nominal_q16 = (runtime->rate << 16) / 8000;
size_t nominal_bytes = (runtime->rate / 8000) * BYTES_PER_FRAME;
usb_kill_anchored_urbs(&tascam->playback_anchor);
usb_kill_anchored_urbs(&tascam->feedback_anchor);
tascam->driver_playback_pos = 0;
tascam->playback_frames_consumed = 0;
tascam->last_period_pos = 0;
tascam->feedback_pattern_in_idx = 0;
tascam->feedback_pattern_out_idx = 0;
tascam->last_pb_period_pos = 0;
tascam->feedback_synced = false;
tascam->feedback_consecutive_errors = 0;
tascam->feedback_urb_skip_count = NUM_FEEDBACK_URBS;
nominal_frames_per_packet = runtime->rate / 8000;
for (i = 0; i < FEEDBACK_ACCUMULATOR_SIZE; i++)
tascam->feedback_accumulator_pattern[i] =
nominal_frames_per_packet;
tascam->phase_accum = 0;
tascam->freq_q16 = nominal_q16;
for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
struct urb *f_urb = tascam->feedback_urbs[i];
int j;
f_urb->number_of_packets = FEEDBACK_URB_PACKETS;
f_urb->transfer_buffer_length =
FEEDBACK_URB_PACKETS * FEEDBACK_PACKET_SIZE;
for (j = 0; j < FEEDBACK_URB_PACKETS; j++) {
f_urb->iso_frame_desc[j].offset =
j * FEEDBACK_PACKET_SIZE;
f_urb->iso_frame_desc[j].length = FEEDBACK_PACKET_SIZE;
f_urb->transfer_buffer_length = FEEDBACK_URB_PACKETS * FEEDBACK_PACKET_SIZE;
for (u = 0; u < FEEDBACK_URB_PACKETS; u++) {
f_urb->iso_frame_desc[u].offset = u * FEEDBACK_PACKET_SIZE;
f_urb->iso_frame_desc[u].length = FEEDBACK_PACKET_SIZE;
}
}
nominal_bytes_per_packet = nominal_frames_per_packet * BYTES_PER_FRAME;
total_bytes_in_urb = nominal_bytes_per_packet * PLAYBACK_URB_PACKETS;
for (u = 0; u < NUM_PLAYBACK_URBS; u++) {
struct urb *urb = tascam->playback_urbs[u];
memset(urb->transfer_buffer, 0,
tascam->playback_urb_alloc_size);
urb->transfer_buffer_length = total_bytes_in_urb;
memset(urb->transfer_buffer, 0, tascam->playback_urb_alloc_size);
urb->number_of_packets = PLAYBACK_URB_PACKETS;
urb->transfer_buffer_length = PLAYBACK_URB_PACKETS * nominal_bytes;
for (i = 0; i < PLAYBACK_URB_PACKETS; i++) {
urb->iso_frame_desc[i].offset =
i * nominal_bytes_per_packet;
urb->iso_frame_desc[i].length =
nominal_bytes_per_packet;
urb->iso_frame_desc[i].offset = i * nominal_bytes;
urb->iso_frame_desc[i].length = nominal_bytes;
}
}
return 0;
}
/**
* tascam_playback_pointer() - Returns the current playback pointer position.
* @substream: The ALSA PCM substream.
*
* This function returns the current position of the playback pointer within
* the ALSA ring buffer, in frames.
*
* Return: The current playback pointer position in frames.
*/
static snd_pcm_uframes_t
tascam_playback_pointer(struct snd_pcm_substream *substream)
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;
unsigned long flags;
u64 pos;
snd_pcm_uframes_t buffer_size = substream->runtime->buffer_size;
if (!atomic_read(&tascam->playback_active))
return 0;
spin_lock_irqsave(&tascam->lock, flags);
pos = tascam->playback_frames_consumed;
spin_unlock_irqrestore(&tascam->lock, flags);
scoped_guard(spinlock_irqsave, &tascam->lock) {
pos = tascam->playback_frames_consumed;
return (snd_pcm_uframes_t)(pos % buffer_size);
}
static int tascam_playback_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
int i, ret = 0;
bool start = false;
bool stop = false;
unsigned long flags;
spin_lock_irqsave(&tascam->lock, flags);
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
case SNDRV_PCM_TRIGGER_RESUME:
if (!atomic_read(&tascam->playback_active)) {
atomic_set(&tascam->playback_active, 1);
start = true;
}
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
atomic_set(&tascam->playback_active, 0);
stop = true;
break;
default:
ret = -EINVAL;
break;
}
spin_unlock_irqrestore(&tascam->lock, flags);
if (stop) {
/* Ensure playback_active is updated before unlinking URBs */
smp_mb();
for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
if (tascam->feedback_urbs[i])
usb_unlink_urb(tascam->feedback_urbs[i]);
}
for (i = 0; i < NUM_PLAYBACK_URBS; i++) {
if (tascam->playback_urbs[i])
usb_unlink_urb(tascam->playback_urbs[i]);
}
}
if (runtime->buffer_size == 0)
return 0;
if (start) {
for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
usb_anchor_urb(tascam->feedback_urbs[i], &tascam->feedback_anchor);
if (usb_submit_urb(tascam->feedback_urbs[i], GFP_ATOMIC) < 0) {
usb_unanchor_urb(tascam->feedback_urbs[i]);
dev_err(&tascam->dev->dev, "Failed to submit feedback URB %d\n", i);
atomic_set(&tascam->playback_active, 0);
/* Ensure playback_active is cleared before unlinking URBs */
smp_mb();
for (int j = 0; j < i; j++)
usb_unlink_urb(tascam->feedback_urbs[j]);
ret = -EIO;
goto error;
}
atomic_inc(&tascam->active_urbs);
}
for (i = 0; i < NUM_PLAYBACK_URBS; i++) {
usb_anchor_urb(tascam->playback_urbs[i], &tascam->playback_anchor);
if (usb_submit_urb(tascam->playback_urbs[i], GFP_ATOMIC) < 0) {
usb_unanchor_urb(tascam->playback_urbs[i]);
dev_err(&tascam->dev->dev, "Failed to submit playback URB %d\n", i);
atomic_set(&tascam->playback_active, 0);
/* Ensure playback_active is cleared before unlinking URBs */
smp_mb();
for (int j = 0; j < NUM_FEEDBACK_URBS; j++)
usb_unlink_urb(tascam->feedback_urbs[j]);
for (int j = 0; j < i; j++)
usb_unlink_urb(tascam->playback_urbs[j]);
ret = -EIO;
goto error;
}
atomic_inc(&tascam->active_urbs);
}
} else if (stop && ret == 0) {
schedule_work(&tascam->stop_work);
}
return do_div(pos, runtime->buffer_size);
error:
return ret;
}
/**
* tascam_playback_ops - ALSA PCM operations for playback.
* playback_urb_complete() - completion handler for playback isochronous URBs
* @urb: the completed URB
*
* This structure defines the callback functions for playback stream operations,
* including open, close, ioctl, hardware parameters, hardware free, prepare,
* trigger, and pointer.
* This function runs in interrupt context. It calculates the number of bytes
* to send in the next set of packets based on the feedback-driven clock,
* copies the audio data from the ALSA ring buffer, and resubmits the URB.
*/
const struct snd_pcm_ops tascam_playback_ops = {
.open = tascam_playback_open,
.close = tascam_playback_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = tascam_pcm_hw_params,
.hw_free = tascam_pcm_hw_free,
.prepare = tascam_playback_prepare,
.trigger = tascam_pcm_trigger,
.pointer = tascam_playback_pointer,
};
void playback_urb_complete(struct urb *urb)
{
struct tascam_card *tascam = urb->context;
@ -163,294 +199,152 @@ void playback_urb_complete(struct urb *urb)
size_t total_bytes_for_urb = 0;
snd_pcm_uframes_t offset_frames;
snd_pcm_uframes_t frames_to_copy;
int ret, i;
int i;
unsigned long flags;
bool need_period_elapsed = false;
snd_pcm_uframes_t buffer_size, period_size;
if (!tascam)
return;
if (urb->status) {
if (urb->status != -ENOENT && urb->status != -ECONNRESET &&
urb->status != -ESHUTDOWN && urb->status != -ENODEV)
dev_err_ratelimited(tascam->card->dev,
"Playback URB failed: %d\n",
urb->status);
goto out;
atomic_dec(&tascam->active_urbs);
return;
}
if (!atomic_read(&tascam->playback_active)) {
atomic_dec(&tascam->active_urbs);
return;
}
if (!tascam || !atomic_read(&tascam->playback_active))
goto out;
substream = tascam->playback_substream;
if (!substream || !substream->runtime)
goto out;
runtime = substream->runtime;
scoped_guard(spinlock_irqsave, &tascam->lock) {
for (i = 0; i < urb->number_of_packets; i++) {
unsigned int frames_for_packet;
size_t bytes_for_packet;
if (tascam->feedback_synced) {
frames_for_packet =
tascam->feedback_accumulator_pattern
[tascam->feedback_pattern_out_idx];
tascam->feedback_pattern_out_idx =
(tascam->feedback_pattern_out_idx + 1) %
FEEDBACK_ACCUMULATOR_SIZE;
} else {
frames_for_packet = runtime->rate / 8000;
}
bytes_for_packet = frames_for_packet * BYTES_PER_FRAME;
urb->iso_frame_desc[i].offset = total_bytes_for_urb;
urb->iso_frame_desc[i].length = bytes_for_packet;
total_bytes_for_urb += bytes_for_packet;
}
urb->transfer_buffer_length = total_bytes_for_urb;
offset_frames = tascam->driver_playback_pos;
frames_to_copy = bytes_to_frames(runtime, total_bytes_for_urb);
tascam->driver_playback_pos =
(offset_frames + frames_to_copy) % runtime->buffer_size;
if (!substream) {
atomic_dec(&tascam->active_urbs);
return;
}
runtime = substream->runtime;
if (!runtime) {
atomic_dec(&tascam->active_urbs);
return;
}
buffer_size = runtime->buffer_size;
period_size = runtime->period_size;
spin_lock_irqsave(&tascam->lock, flags);
for (i = 0; i < urb->number_of_packets; i++) {
unsigned int frames_for_packet;
tascam->phase_accum += tascam->freq_q16;
frames_for_packet = tascam->phase_accum >> 16;
tascam->phase_accum &= 0xFFFF;
urb->iso_frame_desc[i].offset = total_bytes_for_urb;
urb->iso_frame_desc[i].length = frames_for_packet * BYTES_PER_FRAME;
total_bytes_for_urb += urb->iso_frame_desc[i].length;
}
urb->transfer_buffer_length = total_bytes_for_urb;
offset_frames = tascam->driver_playback_pos;
frames_to_copy = bytes_to_frames(runtime, total_bytes_for_urb);
tascam->driver_playback_pos = (offset_frames + frames_to_copy) % buffer_size;
spin_unlock_irqrestore(&tascam->lock, flags);
if (total_bytes_for_urb > 0) {
u8 *dst_buf = urb->transfer_buffer;
size_t ptr_bytes = frames_to_bytes(runtime, offset_frames);
/* Handle ring buffer wrap-around */
if (offset_frames + frames_to_copy > runtime->buffer_size) {
size_t first_chunk_bytes = frames_to_bytes(
runtime, runtime->buffer_size - offset_frames);
size_t second_chunk_bytes =
total_bytes_for_urb - first_chunk_bytes;
if (offset_frames + frames_to_copy > buffer_size) {
size_t part1 = buffer_size - offset_frames;
memcpy(dst_buf,
runtime->dma_area +
frames_to_bytes(runtime, offset_frames),
first_chunk_bytes);
memcpy(dst_buf + first_chunk_bytes, runtime->dma_area,
second_chunk_bytes);
memcpy(dst_buf, runtime->dma_area + ptr_bytes, frames_to_bytes(runtime, part1));
memcpy(dst_buf + frames_to_bytes(runtime, part1), runtime->dma_area, total_bytes_for_urb - frames_to_bytes(runtime, part1));
} else {
memcpy(dst_buf,
runtime->dma_area +
frames_to_bytes(runtime, offset_frames),
total_bytes_for_urb);
memcpy(dst_buf, runtime->dma_area + ptr_bytes, total_bytes_for_urb);
}
process_playback_routing_us144mkii(tascam, dst_buf, dst_buf,
frames_to_copy);
}
urb->dev = tascam->dev;
usb_get_urb(urb);
usb_anchor_urb(urb, &tascam->playback_anchor);
ret = usb_submit_urb(urb, GFP_ATOMIC);
if (ret < 0) {
dev_err_ratelimited(tascam->card->dev,
"Failed to resubmit playback URB: %d\n",
ret);
usb_unanchor_urb(urb);
usb_put_urb(urb);
atomic_dec(
&tascam->active_urbs); /* Decrement on failed resubmission */
spin_lock_irqsave(&tascam->lock, flags);
tascam->playback_frames_consumed += frames_to_copy;
if (period_size > 0) {
u64 current_period = div_u64(tascam->playback_frames_consumed, period_size);
if (current_period > tascam->last_pb_period_pos) {
tascam->last_pb_period_pos = current_period;
need_period_elapsed = true;
}
}
out:
usb_put_urb(urb);
spin_unlock_irqrestore(&tascam->lock, flags);
if (need_period_elapsed)
snd_pcm_period_elapsed(substream);
if (usb_submit_urb(urb, GFP_ATOMIC) < 0)
atomic_dec(&tascam->active_urbs);
}
/**
* feedback_urb_complete() - completion handler for feedback isochronous URBs
* @urb: the completed URB
*
* This is the master clock for the driver. It runs in interrupt context.
* It reads the feedback value from the device, which indicates how many
* samples the device has consumed. This information is used to adjust the
* playback rate and to advance the capture stream pointer, keeping both
* streams in sync. It then calls snd_pcm_period_elapsed if necessary and
* resubmits itself.
*/
void feedback_urb_complete(struct urb *urb)
{
struct tascam_card *tascam = urb->context;
struct snd_pcm_substream *playback_ss, *capture_ss;
struct snd_pcm_runtime *playback_rt, *capture_rt;
u64 total_frames_in_urb = 0;
int ret, p;
unsigned int old_in_idx, new_in_idx;
bool playback_period_elapsed = false;
bool capture_period_elapsed = false;
unsigned long flags;
if (!tascam)
return;
if (urb->status) {
if (urb->status != -ENOENT && urb->status != -ECONNRESET &&
urb->status != -ESHUTDOWN && urb->status != -ENODEV) {
dev_err_ratelimited(tascam->card->dev,
"Feedback URB failed: %d\n",
urb->status);
atomic_dec(
&tascam->active_urbs); /* Decrement on failed resubmission */
}
goto out;
atomic_dec(&tascam->active_urbs);
return;
}
if (!atomic_read(&tascam->playback_active)) {
atomic_dec(&tascam->active_urbs);
return;
}
if (!tascam || !atomic_read(&tascam->playback_active))
goto out;
playback_ss = tascam->playback_substream;
if (!playback_ss || !playback_ss->runtime)
goto out;
playback_rt = playback_ss->runtime;
spin_lock_irqsave(&tascam->lock, flags);
if (tascam->feedback_urb_skip_count > 0) {
tascam->feedback_urb_skip_count--;
spin_unlock_irqrestore(&tascam->lock, flags);
goto resubmit;
}
capture_ss = tascam->capture_substream;
capture_rt = capture_ss ? capture_ss->runtime : NULL;
for (p = 0; p < urb->number_of_packets; p++) {
if (urb->iso_frame_desc[p].status == 0 && urb->iso_frame_desc[p].actual_length >= 3) {
u8 *data = (u8 *)urb->transfer_buffer + urb->iso_frame_desc[p].offset;
u32 sum = data[0] + data[1] + data[2];
u32 target_freq_q16 = ((sum * 65536) / 3) / 8;
scoped_guard(spinlock_irqsave, &tascam->lock) {
if (tascam->feedback_urb_skip_count > 0) {
tascam->feedback_urb_skip_count--;
break;
}
old_in_idx = tascam->feedback_pattern_in_idx;
for (p = 0; p < urb->number_of_packets; p++) {
u8 feedback_value = 0;
const unsigned int *pattern;
bool packet_ok =
(urb->iso_frame_desc[p].status == 0 &&
urb->iso_frame_desc[p].actual_length >= 1);
if (packet_ok)
feedback_value =
*((u8 *)urb->transfer_buffer +
urb->iso_frame_desc[p].offset);
if (packet_ok) {
int delta = feedback_value -
tascam->fpo.base_feedback_value +
tascam->fpo.feedback_offset;
int pattern_idx;
if (delta < 0) {
pattern_idx =
0; // Clamp to the lowest pattern
} else if (delta >= 5) {
pattern_idx =
4; // Clamp to the highest pattern
} else {
pattern_idx = delta;
}
pattern =
tascam->fpo
.full_frame_patterns[pattern_idx];
tascam->feedback_consecutive_errors = 0;
int i;
for (i = 0; i < 8; i++) {
unsigned int in_idx =
(tascam->feedback_pattern_in_idx +
i) %
FEEDBACK_ACCUMULATOR_SIZE;
tascam->feedback_accumulator_pattern
[in_idx] = pattern[i];
total_frames_in_urb += pattern[i];
}
} else {
unsigned int nominal_frames =
playback_rt->rate / 8000;
int i;
if (tascam->feedback_synced) {
tascam->feedback_consecutive_errors++;
if (tascam->feedback_consecutive_errors >
FEEDBACK_SYNC_LOSS_THRESHOLD) {
dev_err(tascam->card->dev,
"Fatal: Feedback sync lost. Stopping stream.\n");
schedule_work(
&tascam->stop_pcm_work);
tascam->feedback_synced = false;
break;
}
}
for (i = 0; i < 8; i++) {
unsigned int in_idx =
(tascam->feedback_pattern_in_idx +
i) %
FEEDBACK_ACCUMULATOR_SIZE;
tascam->feedback_accumulator_pattern
[in_idx] = nominal_frames;
total_frames_in_urb += nominal_frames;
}
}
tascam->feedback_pattern_in_idx =
(tascam->feedback_pattern_in_idx + 8) %
FEEDBACK_ACCUMULATOR_SIZE;
}
new_in_idx = tascam->feedback_pattern_in_idx;
if (!tascam->feedback_synced) {
unsigned int out_idx = tascam->feedback_pattern_out_idx;
bool is_ahead = (new_in_idx - out_idx) %
FEEDBACK_ACCUMULATOR_SIZE <
(FEEDBACK_ACCUMULATOR_SIZE / 2);
bool was_behind = (old_in_idx - out_idx) %
FEEDBACK_ACCUMULATOR_SIZE >=
(FEEDBACK_ACCUMULATOR_SIZE / 2);
if (is_ahead && was_behind) {
dev_dbg(tascam->card->dev,
"Sync Acquired! (in: %u, out: %u)\n",
new_in_idx, out_idx);
tascam->feedback_synced = true;
tascam->feedback_consecutive_errors = 0;
}
}
if (total_frames_in_urb > 0) {
tascam->playback_frames_consumed += total_frames_in_urb;
if (atomic_read(&tascam->capture_active))
tascam->capture_frames_processed +=
total_frames_in_urb;
}
if (playback_rt->period_size > 0) {
u64 current_period =
div_u64(tascam->playback_frames_consumed,
playback_rt->period_size);
if (current_period > tascam->last_period_pos) {
tascam->last_period_pos = current_period;
playback_period_elapsed = true;
}
}
if (atomic_read(&tascam->capture_active) && capture_rt &&
capture_rt->period_size > 0) {
u64 current_capture_period =
div_u64(tascam->capture_frames_processed,
capture_rt->period_size);
if (current_capture_period >
tascam->last_capture_period_pos) {
tascam->last_capture_period_pos =
current_capture_period;
capture_period_elapsed = true;
}
tascam->freq_q16 = (tascam->freq_q16 * PLL_FILTER_OLD_WEIGHT + target_freq_q16 * PLL_FILTER_NEW_WEIGHT) / PLL_FILTER_DIVISOR;
tascam->feedback_synced = true;
}
}
if (playback_period_elapsed)
snd_pcm_period_elapsed(playback_ss);
if (capture_period_elapsed)
snd_pcm_period_elapsed(capture_ss);
spin_unlock_irqrestore(&tascam->lock, flags);
urb->dev = tascam->dev;
usb_get_urb(urb);
usb_anchor_urb(urb, &tascam->feedback_anchor);
resubmit:
ret = usb_submit_urb(urb, GFP_ATOMIC);
if (ret < 0) {
dev_err_ratelimited(tascam->card->dev,
"Failed to resubmit feedback URB: %d\n",
ret);
usb_unanchor_urb(urb);
usb_put_urb(urb);
atomic_dec(&tascam->active_urbs);
}
out:
usb_put_urb(urb);
}
void tascam_stop_pcm_work_handler(struct work_struct *work)
{
struct tascam_card *tascam =
container_of(work, struct tascam_card, stop_pcm_work);
if (tascam->playback_substream)
snd_pcm_stop(tascam->playback_substream, SNDRV_PCM_STATE_XRUN);
if (tascam->capture_substream)
snd_pcm_stop(tascam->capture_substream, SNDRV_PCM_STATE_XRUN);
}
const struct snd_pcm_ops tascam_playback_ops = {
.open = tascam_playback_open,
.close = tascam_playback_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = tascam_pcm_hw_params,
.hw_free = NULL,
.prepare = tascam_playback_prepare,
.trigger = tascam_playback_trigger,
.pointer = tascam_playback_pointer,
};