codebase refactor & cleanup

- MIDI working without active audio streaming
- overall refactor and cleanup
- card & devices naming fixes
This commit is contained in:
Šerif Rami 2026-01-19 17:34:06 +01:00
parent 6a39e6fd6d
commit 8299661b7b
10 changed files with 629 additions and 1101 deletions

View File

@ -1,467 +1,108 @@
Document Version: 13.0
Date: 2025-06-27
Subject: This document provides a complete and re-verified specification for the TASCAM US-144 MKII's playback and feedback mechanism across all supported sample rates, based on extensive reverse-engineering of the official macOS and Windows drivers, including dynamic USB sniffing data and static USB device descriptor reports. It is intended as a precise technical reference for implementing a functional Linux driver.
1. Core Architecture
Document Version: 14.0
Date: 2026-01-19
Subject: Technical Specification for TASCAM US-144 MKII Linux Driver
Device: TEAC Corp. US-144 MKII (ID 0644:8020)
Status: Implemented
Playback is achieved via a host-paced asynchronous isochronous feedback loop.
Executive Summary
Audio Data (OUT): Endpoint 0x02. The host sends continuous audio data.
This document describes the implementation details of the Linux ALSA driver for the TASCAM US-144 MKII. The driver is implemented as a vendor-specific USB audio driver using the ALSA kernel API. It handles audio streaming, sample rate control, and MIDI I/O using a proprietary protocol over High-Speed USB 2.0.
Clock Feedback (IN): Endpoint 0x81. The device sends its master clock rate to the host, allowing the driver to correct for drift.
Hardware Interface (Derived from Device Descriptors)
2. USB Device Descriptors & Configuration (Verified by USB Report)
Vendor ID: 0x0644 (TEAC Corp.)
Product ID: 0x8020 (US-144 MKII)
Speed: High Speed (480 Mbps)
Device Descriptor:
2.1. Interfaces
The device exposes two interfaces. Both must be set to Alternate Setting 1 to enable functionality.
bLength: 0x12 (18 bytes), bDescriptorType: 0x01 (Device Descriptor), bcdUSB: 0x200 (USB Version 2.0)
Interface 0 (Audio Output & MIDI):
- Endpoint 0x02 (OUT): Isochronous, Asynchronous. Used for Playback Data.
- Endpoint 0x83 (IN): Bulk. Used for MIDI Input.
- Endpoint 0x04 (OUT): Bulk. Used for MIDI Output.
bDeviceClass: 0xFF (Vendor Specific), bDeviceSubClass: 0xFF, bDeviceProtocol: 0xFF
Interface 1 (Audio Input & Clock):
- Endpoint 0x81 (IN): Isochronous, Asynchronous. Used for explicit Feedback (Clock).
- Endpoint 0x86 (IN): Bulk. Used for Capture Data.
bMaxPacketSize0: 0x40 (64 bytes)
Driver Architecture
idVendor: 0x0644 (TEAC Corporation), idProduct: 0x8020
3.1. Implicit Feedback / Ghost Stream Mechanism
The device requires the Playback stream (EP 0x02) to be active for any other function (Capture, MIDI, Feedback) to work. To support cases where the user is recording or using MIDI without playing audio, the driver implements an "Implicit Stream" reference counter (stream_refs).
bcdDevice: 0x0100
If stream_refs > 0 and no ALSA Playback is active: The driver submits "Ghost" URBs to EP 0x02 containing zeroed-out (silent) buffers.
iManufacturer: 0x01 (String "TASCAM"), iProduct: 0x02 (String "US-144 MKII"), iSerialNumber: 0x03 (String "no serial number")
If ALSA Playback starts: The driver seamlessly transitions the active URBs to carry real audio data.
bNumConfigurations: 0x01 (1 Configuration)
If ALSA Playback stops but stream_refs > 0: The driver transitions back to sending silence.
Configuration Descriptor (Configuration 1):
Audio Streaming Implementation
bLength: 0x09 (9 bytes), bDescriptorType: 0x02 (Configuration Descriptor)
4.1. Playback (Endpoint 0x02)
- Format: 24-bit Little Endian, 4 Channels (Interleaved).
- URB Structure:
- 4 active URBs (Double/Quad buffering).
- 8 Packets per URB (Aligns with 1ms at High Speed).
- Packet Size: Variable, determined by the Feedback Loop.
- Frame Size: 12 bytes (4 channels * 3 bytes).
wTotalLength: 0x0050 (80 bytes) - This is the total size of the configuration descriptor including all interface and endpoint descriptors.
4.2. Capture (Endpoint 0x86)
- Format: Bulk Transfer.
- Packet Size: 4096 bytes (Driver buffer size).
- Data Format: Proprietary bit-shuffled format.
- Decoding: The driver applies a software "Butterfly" bit-transposition to decode the incoming 64-byte chunks into standard PCM 32-bit integer format.
bNumInterfaces: 0x02 (2 Interfaces), bConfigurationValue: 0x01 (Configuration 1)
4.3. Clock Synchronization (Feedback Endpoint 0x81)
- Format: 3 bytes per packet.
- Algorithm:
1. Sum the bytes received in the packet.
2. Convert to Q16.16 frequency format.
3. Apply a recursive moving average filter (Weight 3:1) to smooth jitter.
4. Use this frequency to calculate the exact number of frames to send in the next Playback URB.
bmAttributes: 0x80 (Bus Powered, No Remote Wakeup), MaxPower: 0xF0 (480 mA)
MIDI Implementation
Interfaces & Alternate Settings:
5.1. Protocol
MIDI is encapsulated in a custom 9-byte packet format sent over Bulk endpoints.
Interface 0 (Audio/MIDI Control):
Packet Structure:
Byte 0: 0xE0 (Header)
Byte 1-8: MIDI Payload (Up to 8 bytes)
bInterfaceNumber: 0x00
Padding: If the MIDI message is shorter than 8 bytes, the remaining bytes are padded with 0xFD.
Alternate Setting 0: bNumEndpoints=0x00. bInterfaceClass=0xFF (Vendor Specific).
5.2. Endpoints
Alternate Setting 1: bNumEndpoints=0x03. bInterfaceClass=0xFF (Vendor Specific). This is the active setting for audio streaming.
Output: Endpoint 0x04.
Interface 1 (Audio/MIDI Streaming):
Input: Endpoint 0x83.
bInterfaceNumber: 0x01
5.3. Dependency
MIDI I/O requires the implicit playback stream to be active. The driver automatically increments stream_refs on MIDI Open and decrements on MIDI Close.
Alternate Setting 0: bNumEndpoints=0x00. bInterfaceClass=0xFF (Vendor Specific).
Control Messages (Sample Rate)
Alternate Setting 1: bNumEndpoints=0x02. bInterfaceClass=0xFF (Vendor Specific). This is the active setting for audio streaming.
Sample rate changes are strictly ordered using USB Control Messages to Interface 0.
Endpoints (Detailed from Descriptors):
Sequence:
Interface 0, Alternate Setting 1 Endpoints:
VENDOR_REQ_MODE_CONTROL (0x49): Value 0x0010 (Config Mode).
Endpoint 0x02 (Playback OUT): bEndpointAddress=0x02 (OUT, EP 2), bmAttributes=0x05 (Isochronous, Asynchronous, Data), wMaxPacketSize=0x009C (156 bytes), bInterval=0x01 (1 microframe -> 0.125 ms).
UAC_SET_CUR (0x01): Set Rate on EP 0x86 (Capture).
Endpoint 0x83 (MIDI/Control IN): bEndpointAddress=0x83 (IN, EP 3), bmAttributes=0x02 (Bulk), wMaxPacketSize=0x0200 (512 bytes), bInterval=0x04 (0.5 ms).
UAC_SET_CUR (0x01): Set Rate on EP 0x02 (Playback).
Endpoint 0x04 (MIDI/Control OUT): bEndpointAddress=0x04 (OUT, EP 4), bmAttributes=0x02 (Bulk), wMaxPacketSize=0x0200 (512 bytes), bInterval=0x04 (0.5 ms).
VENDOR_REQ_REGISTER_WRITE (0x41): Write hardware register settings.
Interface 1, Alternate Setting 1 Endpoints:
44.1 kHz: Reg 0x1000
Endpoint 0x81 (Feedback IN): bEndpointAddress=0x81 (IN, EP 1), bmAttributes=0x05 (Isochronous, Asynchronous, Data), wMaxPacketSize=0x0040 (64 bytes), bInterval=0x04 (8 microframes -> 1 ms).
48.0 kHz: Reg 0x1002
Endpoint 0x86 (Capture IN): bEndpointAddress=0x86 (IN, EP 6), bmAttributes=0x02 (Bulk), wMaxPacketSize=0x0200 (512 bytes), bInterval=0x01 (0.125 ms).
88.2 kHz: Reg 0x1008
Initial Control Messages (Detailed Sequence from Sniffing Data):
The following sequence of usb_control_msg calls is observed during device initialization and sample rate setting. These are crucial for putting the device into an operational state. The bmRequestType values are interpreted as (Direction << 7) | (Type << 5) | Recipient.
96.0 kHz: Reg 0x100a
SET_CONFIGURATION (Standard Request):
VENDOR_REQ_MODE_CONTROL (0x49): Value 0x0030 (Start Stream).
bmRequestType: 0x00 (Host-to-Device, Standard, Device)
bRequest: 0x09 (SET_CONFIGURATION)
wValue: 0x0001 (Configuration 1)
wIndex: 0x0000
wLength: 0
(Observed during a sample rate change event, indicating a re-configuration)
GET DESCRIPTOR (DEVICE, STRING, CONFIGURATION) (Standard Requests):
Standard USB enumeration requests to retrieve device, string (Manufacturer, Product, Serial), and configuration descriptors. These are standard and typically handled by the USB core.
Handshake Read (Device-to-Host Vendor Request):
bmRequestType: 0xc0 (D2H, Vendor, Device)
bRequest: 0x49 (73, VENDOR_REQ_MODE_CONTROL)
wValue: 0x0000
wIndex: 0x0000
wLength: 1
Expected Response Data: 0x12 (1 byte)
Set Initial Mode (Host-to-Device Vendor Request):
bmRequestType: 0x40 (H2D, Vendor, Device)
bRequest: 0x49 (73, VENDOR_REQ_MODE_CONTROL)
wValue: 0x0010
wIndex: 0x0000
wLength: 0
Set Sample Rate on Capture EP (Host-to-Device Class Request):
bmRequestType: 0x22 (H2D, Class, Endpoint)
bRequest: 0x01 (1, UAC_SET_CUR)
wValue: 0x0100 (UAC_SAMPLING_FREQ_CONTROL)
wIndex: 0x0086 (Endpoint 0x86 - Capture IN)
wLength: 3
Data Fragment (3 bytes, Rate-Dependent):
| Sample Rate | Hex Payload |
| :---------- | :---------- |
| 44100 Hz | 0x44ac00 |
| 48000 Hz | 0x80bb00 |
| 88200 Hz | 0x885801 |
| 96000 Hz | 0x007701 |
Set Sample Rate on Playback EP (Host-to-Device Class Request):
bmRequestType: 0x22 (H2D, Class, Endpoint)
bRequest: 0x01 (1, UAC_SET_CUR)
wValue: 0x0100 (UAC_SAMPLING_FREQ_CONTROL)
wIndex: 0x0002 (Endpoint 0x02 - Playback OUT)
wLength: 3
Data Fragment (3 bytes, Rate-Dependent): (Same as for Capture EP)
| Sample Rate | Hex Payload |
| :---------- | :---------- |
| 44100 Hz | 0x44ac00 |
| 48000 Hz | 0x80bb00 |
| 88200 Hz | 0x885801 |
| 96000 Hz | 0x007701 |
Vendor Register Write 1 (Host-to-Device Vendor Request):
bmRequestType: 0x40 (H2D, Vendor, Device)
bRequest: 0x41 (65, VENDOR_REQ_REGISTER_WRITE)
wValue: 0x0d04
wIndex: 0x0101 (257)
wLength: 0
Vendor Register Write 2 (Host-to-Device Vendor Request):
bmRequestType: 0x40 (H2D, Vendor, Device)
bRequest: 0x41 (65, VENDOR_REQ_REGISTER_WRITE)
wValue: 0x0e00
wIndex: 0x0101 (257)
wLength: 0
Vendor Register Write 3 (Host-to-Device Vendor Request):
bmRequestType: 0x40 (H2D, Vendor, Device)
bRequest: 0x41 (65, VENDOR_REQ_REGISTER_WRITE)
wValue: 0x0f00
wIndex: 0x0101 (257)
wLength: 0
Vendor Register Write (Rate-Dependent) (Host-to-Device Vendor Request):
bmRequestType: 0x40 (H2D, Vendor, Device)
bRequest: 0x41 (65, VENDOR_REQ_REGISTER_WRITE)
wValue (Rate-Dependent):
| Sample Rate | wValue |
| :---------- | :------- |
| 44100 Hz | 0x1000 |
| 48000 Hz | 0x1002 |
| 88200 Hz | 0x1008 |
| 96000 Hz | 0x100a |
wIndex: 0x0101 (257)
wLength: 0
Vendor Register Write 4 (Host-to-Device Vendor Request):
bmRequestType: 0x40 (H2D, Vendor, Device)
bRequest: 0x41 (65, VENDOR_REQ_REGISTER_WRITE)
wValue: 0x110b
wIndex: 0x0101 (257)
wLength: 0
Enable Streaming (Host-to-Device Vendor Request):
bmRequestType: 0x40 (H2D, Vendor, Device)
bRequest: 0x49 (73, VENDOR_REQ_MODE_CONTROL)
wValue: 0x0030
wIndex: 0x0000
wLength: 0
3. Playback (OUT) Stream (Endpoint 0x02)
URB Structure (Confirmed by Sniffing Data, Constant Across All Latency Settings):
number_of_packets: 40
transfer_buffer_length: 2880 bytes (This is the total data length for the URB).
Packet Structure (Dynamic, "Packet Fixing" Output):
Each of the 40 packets has a nominal size of 72 bytes. However, the actual length of each packet (urb->iso_frame_desc[i].length) is dynamically determined by the "Packet Fixing" synchronization engine.
The number of frames for each packet (frames_for_this_packet) is read from the feedback_accumulator_pattern (see Section 5.4).
packet_size_bytes = frames_for_this_packet * 12 (where 12 bytes/frame = 4 channels * 3 bytes/sample).
urb->iso_frame_desc[i].offset is the cumulative sum of previous packet lengths.
Audio Data Format (from pcmTo24LSB_US144_00 logic, Confirmed by Sniffing Data):
Input: 32-bit signed interleaved PCM (from ALSA).
Output: 24-bit Little Endian PCM, 4 channels.
Channel Mapping (Software Patchbay): The driver implements a software patchbay. The mapping of 2-channel PC input (Left, Right) to the 4 device output channels (1, 2, 3, 4) is determined by the user's settings in the TASCAM control panel.
Default Behavior (No explicit routing set):
Output Channel 1 = Input Channel 1 (Left)
Output Channel 2 = Input Channel 2 (Right)
Output Channel 3 = Digital Silence (Zeros)
Output Channel 4 = Digital Silence (Zeros)
Example (Channels 3 & 4 routed to 1 & 2 via control panel):
Output Channel 1 = Input Channel 1 (Left)
Output Channel 2 = Input Channel 2 (Right)
Output Channel 3 = Input Channel 1 (Left)
Output Channel 4 = Input Channel 2 (Right)
Example (Channels 1 & 2 routed to 3 & 4 via control panel):
Output Channel 1 = Digital Silence (Zeros)
Output Channel 2 = Digital Silence (Zeros)
Output Channel 3 = Input Channel 1 (Left)
Output Channel 4 = Input Channel 2 (Right)
Action: The Linux driver's audio conversion function must be flexible enough to implement these different channel mappings based on an internal state variable. This state would be controlled by ALSA mixer controls (requiring further reverse engineering of the control panel's USB messages). For a basic driver, the "Default Behavior" (zero-padding) is the recommended starting point.
4. Feedback (IN) Stream (Endpoint 0x81)
Feedback Packet Format: 3 bytes. The first byte contains the feedback value (e.g., 0x30 for 48 frames/ms). The remaining two bytes (0x3030) are present but not used for calculation.
URB Structure (Latency-Dependent, Confirmed by Sniffing Data):
iso_frame_desc[i].length: 3 bytes for each packet.
transfer_buffer_length: (Number of packets) * 3 bytes.
number_of_packets varies by latency setting:
Latency Setting Feedback URB number_of_packets Feedback URB transfer_buffer_length Feedback Interval (Approx.)
Lowest 5 15 bytes ~5 ms
Low 1 3 bytes ~1 ms
Normal 2 6 bytes ~2 ms
High 5 15 bytes ~5 ms
Highest 5 15 bytes ~5 ms
Generated code
* **Note:** The `bInterval` for EP 0x81 is `0x04` (8 microframes = 1ms). The varying feedback intervals are achieved by changing the `number_of_packets` in the URB, not by changing the endpoint's `bInterval`. For example, 5 packets at 1ms interval per packet results in a 5ms total feedback URB completion time.
5. The "Packet Fixing" Synchronization Engine (PGFramePatternObserver Logic)
This is the core clock recovery mechanism, implemented as a sophisticated digital Phase-Locked Loop (PLL).
5.1. State Variables (to be implemented in tascam_card struct):
feedback_accumulator_pattern[128]: An array of unsigned int. This is the primary circular buffer holding the frames-per-microframe values.
feedback_pattern_out_idx: Read-index for feedback_accumulator_pattern (0-127).
feedback_pattern_in_idx: Write-index for feedback_accumulator_pattern (0-127).
feedback_synced: Boolean flag, set to true when the feedback loop is stable.
playback_frames_consumed: A long long counter for total frames consumed by the device. This represents the hardware pointer.
5.2. Initialization:
The feedback_accumulator_pattern is initialized by filling all 128 entries with the nominal frames per microframe, which is 6 for 48kHz (48000 frames/sec / 8000 microframes/sec = 6).
feedback_pattern_out_idx and feedback_pattern_in_idx are initialized to 0.
feedback_synced is initialized to false.
5.3. Pattern Tables (Generated by fpoInitPattern for all supported rates):
These tables are pre-calculated lookup tables. When a feedback value is received, the corresponding table is used to update the feedback_accumulator_pattern. Each table has 8 entries, corresponding to 8 USB microframes (1ms).
Sample Rate Feedback Value (from EP 0x81) Pattern Table (8 entries, sum in parentheses)
44.1 kHz (Needs specific sniffing/RE) (Needs specific sniffing/RE)
48 kHz 46 (0x2E) {5, 6, 6, 6, 5, 6, 6, 6} (sum 46)
47 (0x2F) {5, 6, 6, 6, 6, 6, 6, 6} (sum 47)
48 (0x30) {6, 6, 6, 6, 6, 6, 6, 6} (sum 48)
49 (0x31) {7, 6, 6, 6, 6, 6, 6, 6} (sum 49)
50 (0x32) {7, 6, 6, 6, 7, 6, 6, 6} (sum 50)
88.2 kHz (Needs specific sniffing/RE) (Needs specific sniffing/RE)
96 kHz (Needs specific sniffing/RE) (Needs specific sniffing/RE)
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.
IGNORE_WHEN_COPYING_END
5.4. Feedback Processing (feedback_urb_complete Logic):
Triggered by completion of a Feedback IN URB.
For each received 3-byte packet in the URB:
Extract feedback_value = feedback_data[0].
Validate feedback_value is in the range [46, 50] (for 48kHz). This range will be rate-dependent.
If valid:
Calculate pattern_index = feedback_value - (Base Feedback Value for Rate).
Select current_pattern = feedback_patterns_lookup[pattern_index].
Update Accumulator: Copy the 8 entries from current_pattern into feedback_accumulator_pattern starting at feedback_pattern_in_idx.
Advance write-index: feedback_pattern_in_idx = (feedback_pattern_in_idx + 8) % 128.
Set feedback_synced = true.
If invalid/error: Set feedback_synced = false.
Update playback_frames_consumed:
Calculate frames_this_interval by summing the frames_for_this_packet values from the 8 entries just written into the accumulator, repeated for the number of packets in the feedback URB.
For example, for "Normal Latency" (2 packets), this means summing 16 entries from the feedback_accumulator_pattern (8 entries for the first packet's microframes, and 8 entries for the second packet's microframes).
playback_frames_consumed += frames_this_interval.
Resubmit the feedback URB.
Call snd_pcm_period_elapsed(tascam->playback_substream) to signal ALSA that a period (or multiple periods) has been consumed by the hardware. This should be based on playback_frames_consumed crossing period boundaries.
5.5. Playback URB Preparation (playback_urb_complete Logic):
Triggered by completion of a Playback OUT URB.
Determine frames_for_this_packet (for each of the 40 packets):
Read frames_for_this_packet = feedback_accumulator_pattern[feedback_pattern_out_idx].
Advance read-index: feedback_pattern_out_idx = (feedback_pattern_out_idx + 1) % 128.
Calculate packet_size_bytes = frames_for_this_packet * PLAYBACK_BYTES_PER_FRAME.
Set urb->iso_frame_desc[i].length = packet_size_bytes.
Set urb->iso_frame_desc[i].offset as the cumulative sum of previous packet lengths.
Copy frames_for_this_packet (from ALSA buffer) into the current packet's buffer, using the appropriate convert_and_copy_audio logic (zero-padding or duplication based on routing state).
Resubmit the playback URB.
6. ALSA Pointer Callback (tascam_pcm_pointer)
Returns tascam->playback_frames_consumed % runtime->buffer_size. This provides ALSA with the device's actual playback position, corrected for clock drift.
TASCAM US-144MKII ALSA Driver Logic Table
Table 1: 44.1kHz Latency Settings
Latency Profile ASIO Buffer (Reported) Hardware Feedback (Observed)
Lowest 49 smp (1.1ms) 2 ms (1 packet/URB)
Low 64 smp (1.5ms) 2 ms (1 packet/URB)
Normal 128 smp (2.9ms) 2 ms (2 packets/URB)
High 256 smp (5.8ms) 5 ms (5 packets/URB)
Highest 512 smp (11.6ms) 5 ms (5 packets/URB)
Table 2: 48kHz Latency Settings
Latency Profile ASIO Buffer (Reported) Hardware Feedback (Observed)
Lowest 48 smp (1.0ms) 1 ms (1 packet/URB)
Low 64 smp (1.3ms) 2 ms (1 packet/URB)
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
Latency Profile ASIO Buffer (Reported) Hardware Feedback (Observed)
Lowest 98 smp (1.1ms) 1 ms (1 packet/URB)
Low 128 smp (1.5ms) 2 ms (1 packet/URB)
Normal 256 smp (2.9ms) 2 ms (2 packets/URB)
High 512 smp (5.8ms) 5 ms (5 packets/URB)
Highest 1024 smp (11.6ms) 5 ms (5 packets/URB)
Table 4: 96kHz Latency Settings
Latency Profile ASIO Buffer (Reported) Hardware Feedback (Observed)
Lowest 96 smp (1.0ms) 1 ms (1 packet/URB)
Low 128 smp (1.3ms) 2 ms (1 packet/URB)
Normal 256 smp (2.7ms) 2 ms (2 packets/URB)
High 512 smp (5.3ms) 5 ms (5 packets/URB)
Highest 1024 smp (10.7ms) 5 ms (5 packets/URB)
Supported Rates: 44100, 48000, 88200, 96000 Hz.

View File

@ -14,7 +14,6 @@ For TASCAM US-122MKII check us122mkii branch.
* **MIDI IN/OUT:**
### 📝 To-Do & Known Limitations
* *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.
## Installation and Usage

View File

@ -12,12 +12,6 @@ static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static bool enable[SNDRV_CARDS] = { 1, [1 ...(SNDRV_CARDS - 1)] = 0 };
static atomic_t dev_idx = ATOMIC_INIT(0);
static int tascam_probe(struct usb_interface *intf,
const struct usb_device_id *usb_id);
static void tascam_disconnect(struct usb_interface *intf);
static int tascam_suspend(struct usb_interface *intf, pm_message_t message);
static int tascam_resume(struct usb_interface *intf);
/**
* tascam_free_urbs - free all URBs
* @tascam: the tascam_card instance
@ -25,7 +19,6 @@ static int tascam_resume(struct usb_interface *intf);
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);
@ -34,46 +27,31 @@ void tascam_free_urbs(struct tascam_card *tascam)
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);
tascam->playback_urbs[i]->transfer_buffer, tascam->playback_urbs[i]->transfer_dma);
usb_free_urb(tascam->playback_urbs[i]);
tascam->playback_urbs[i] = NULL;
}
}
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);
tascam->feedback_urbs[i]->transfer_buffer, tascam->feedback_urbs[i]->transfer_dma);
usb_free_urb(tascam->feedback_urbs[i]);
tascam->feedback_urbs[i] = NULL;
}
}
for (i = 0; i < NUM_CAPTURE_URBS; i++) {
if (tascam->capture_urbs[i]) {
usb_free_coherent(tascam->dev, CAPTURE_PACKET_SIZE,
tascam->capture_urbs[i]->transfer_buffer,
tascam->capture_urbs[i]->transfer_dma);
tascam->capture_urbs[i]->transfer_buffer, tascam->capture_urbs[i]->transfer_dma);
usb_free_urb(tascam->capture_urbs[i]);
tascam->capture_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_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;
}
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_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;
}
}
@ -86,18 +64,15 @@ void tascam_free_urbs(struct tascam_card *tascam)
int tascam_alloc_urbs(struct tascam_card *tascam)
{
int i;
tascam->playback_urb_alloc_size = PLAYBACK_URB_PACKETS * 156;
for (i = 0; i < NUM_PLAYBACK_URBS; i++) {
struct urb *urb = usb_alloc_urb(PLAYBACK_URB_PACKETS, GFP_KERNEL);
if (!urb)
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)
return -ENOMEM;
urb->dev = tascam->dev;
@ -111,13 +86,11 @@ int tascam_alloc_urbs(struct tascam_card *tascam)
tascam->feedback_urb_alloc_size = FEEDBACK_URB_PACKETS * FEEDBACK_PACKET_SIZE;
for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
struct urb *urb = usb_alloc_urb(FEEDBACK_URB_PACKETS, GFP_KERNEL);
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);
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;
@ -131,21 +104,16 @@ int tascam_alloc_urbs(struct tascam_card *tascam)
for (i = 0; i < NUM_CAPTURE_URBS; i++) {
struct urb *urb = usb_alloc_urb(0, GFP_KERNEL);
void *buf;
if (!urb)
return -ENOMEM;
tascam->capture_urbs[i] = urb;
buf = usb_alloc_coherent(tascam->dev, CAPTURE_PACKET_SIZE,
GFP_KERNEL, &urb->transfer_dma);
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);
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;
}
return 0;
}
@ -156,70 +124,47 @@ int tascam_alloc_urbs(struct tascam_card *tascam)
void tascam_stop_work_handler(struct work_struct *work)
{
struct tascam_card *tascam = container_of(work, struct tascam_card, stop_work);
usb_kill_anchored_urbs(&tascam->playback_anchor);
usb_kill_anchored_urbs(&tascam->feedback_anchor);
usb_kill_anchored_urbs(&tascam->capture_anchor);
}
static void tascam_card_private_free(struct snd_card *card)
{
struct tascam_card *tascam = card->private_data;
if (tascam) {
tascam_free_urbs(tascam);
if (tascam->dev) {
if (tascam->dev)
usb_put_dev(tascam->dev);
tascam->dev = NULL;
}
}
}
static int tascam_suspend(struct usb_interface *intf, pm_message_t message)
{
struct tascam_card *tascam = usb_get_intfdata(intf);
if (!tascam)
return 0;
snd_pcm_suspend_all(tascam->pcm);
cancel_work_sync(&tascam->stop_work);
cancel_work_sync(&tascam->stop_pcm_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);
return 0;
}
static int tascam_resume(struct usb_interface *intf)
{
struct tascam_card *tascam = usb_get_intfdata(intf);
int err;
unsigned long flags;
int current_rate;
if (!tascam)
return 0;
err = usb_set_interface(tascam->dev, 0, 1);
if (err < 0)
return err;
err = usb_set_interface(tascam->dev, 1, 1);
if (err < 0)
return err;
spin_lock_irqsave(&tascam->lock, flags);
current_rate = tascam->current_rate;
spin_unlock_irqrestore(&tascam->lock, flags);
if (current_rate > 0)
us144mkii_configure_device_for_rate(tascam, current_rate);
usb_set_interface(tascam->dev, 0, 1);
usb_set_interface(tascam->dev, 1, 1);
if (tascam->current_rate > 0)
us144mkii_configure_device_for_rate(tascam, tascam->current_rate);
return 0;
}
@ -228,18 +173,15 @@ static int tascam_probe(struct usb_interface *intf, const struct usb_device_id *
struct usb_device *dev = interface_to_usbdev(intf);
struct snd_card *card;
struct tascam_card *tascam;
int err;
int idx;
int err, idx;
const char *model_name;
char pcm_name[32];
if (intf->cur_altsetting->desc.bInterfaceNumber == 1)
return 0;
idx = atomic_fetch_inc(&dev_idx);
if (idx >= SNDRV_CARDS) {
atomic_dec(&dev_idx);
return -ENODEV;
}
if (!enable[idx]) {
if (idx >= SNDRV_CARDS || !enable[idx]) {
atomic_dec(&dev_idx);
return -ENOENT;
}
@ -256,7 +198,6 @@ static int tascam_probe(struct usb_interface *intf, const struct usb_device_id *
tascam->dev = usb_get_dev(dev);
tascam->card = card;
tascam->iface0 = intf;
tascam->dev_id = le16_to_cpu(dev->descriptor.idProduct);
spin_lock_init(&tascam->lock);
@ -264,30 +205,31 @@ static int tascam_probe(struct usb_interface *intf, const struct usb_device_id *
init_usb_anchor(&tascam->feedback_anchor);
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);
atomic_set(&tascam->stream_refs, 0);
strscpy(card->driver, DRIVER_NAME, sizeof(card->driver));
if (tascam->dev_id == USB_PID_TASCAM_US144)
strscpy(card->shortname, "US-144", sizeof(card->shortname));
else
strscpy(card->shortname, "US-144MKII", sizeof(card->shortname));
/* Correct naming logic based on Device ID */
model_name = (tascam->dev_id == USB_PID_TASCAM_US144) ? "US-144" : "US-144MKII";
strscpy(card->shortname, model_name, sizeof(card->shortname));
snprintf(card->longname, sizeof(card->longname), "%s (%04x:%04x) at %s",
card->shortname, USB_VID_TASCAM, dev->descriptor.idProduct,
dev_name(&dev->dev));
err = snd_pcm_new(card, "US144MKII PCM", 0, 1, 1, &tascam->pcm);
snprintf(pcm_name, sizeof(pcm_name), "%s PCM", model_name);
err = snd_pcm_new(card, pcm_name, 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));
strscpy(tascam->pcm->name, pcm_name, 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);
snd_pcm_set_managed_buffer_all(tascam->pcm, SNDRV_DMA_TYPE_VMALLOC,
NULL, 0, 0);
snd_pcm_set_managed_buffer_all(tascam->pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0);
err = tascam_create_midi(tascam);
if (err < 0)
@ -303,26 +245,12 @@ static int tascam_probe(struct usb_interface *intf, const struct usb_device_id *
goto free_card;
}
err = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
VENDOR_REQ_MODE_CONTROL, RT_D2H_VENDOR_DEV,
MODE_VAL_HANDSHAKE_READ, 0x0000, tascam->scratch_buf, 1,
USB_CTRL_TIMEOUT_MS);
if (err < 0) {
dev_err(&dev->dev, "Handshake failed: %d\n", err);
goto free_card;
}
usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), VENDOR_REQ_MODE_CONTROL,
RT_D2H_VENDOR_DEV, MODE_VAL_HANDSHAKE_READ, 0x0000,
tascam->scratch_buf, 1, USB_CTRL_TIMEOUT_MS);
err = usb_set_interface(dev, 0, 1);
if (err < 0) {
dev_err(&dev->dev, "Failed to set interface 0: %d\n", err);
goto free_card;
}
err = usb_set_interface(dev, 1, 1);
if (err < 0) {
dev_err(&dev->dev, "Failed to set interface 1: %d\n", err);
goto free_card;
}
usb_set_interface(dev, 0, 1);
usb_set_interface(dev, 1, 1);
if (us144mkii_configure_device_for_rate(tascam, 48000) < 0)
dev_warn(&dev->dev, "Failed to initialize device at 48khz\n");

View File

@ -26,14 +26,10 @@
#define EP_AUDIO_IN 0x86
#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)
#define RT_D2H_VENDOR_DEV (USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE)
enum uac_request {
UAC_SET_CUR = 0x01,
UAC_GET_CUR = 0x81,
};
#define UAC_SET_CUR 0x01
enum uac_control_selector {
UAC_SAMPLING_FREQ_CONTROL = 0x0100,
@ -50,17 +46,6 @@ enum tascam_mode_value {
MODE_VAL_STREAM_START = 0x0030,
};
enum tascam_register {
REG_ADDR_INIT_0D = 0x0d04,
REG_ADDR_INIT_0E = 0x0e00,
REG_ADDR_INIT_0F = 0x0f00,
REG_ADDR_RATE_44100 = 0x1000,
REG_ADDR_RATE_48000 = 0x1002,
REG_ADDR_RATE_88200 = 0x1008,
REG_ADDR_RATE_96000 = 0x100a,
REG_ADDR_INIT_11 = 0x110b,
};
#define REG_VAL_ENABLE 0x0101
#define NUM_PLAYBACK_URBS 4
@ -74,10 +59,7 @@ enum tascam_register {
#define MIDI_PACKET_SIZE 9
#define MIDI_PAYLOAD_SIZE 8
#define BYTES_PER_SAMPLE 3
#define NUM_CHANNELS 4
#define PLAYBACK_FRAME_SIZE (NUM_CHANNELS * BYTES_PER_SAMPLE)
#define PLAYBACK_FRAME_SIZE 12
#define MAX_FRAMES_PER_PACKET 13
#define PLL_FILTER_OLD_WEIGHT 3
@ -93,6 +75,7 @@ enum tascam_register {
* @card: pointer to the ALSA sound card
* @pcm: pointer to the ALSA PCM device
* @rmidi: pointer to the ALSA raw MIDI device
* @dev_id: USB Product ID (used to distinguish US-144 from MKII)
* @scratch_buf: temporary buffer for control messages
* @playback_substream: pointer to the PCM playback substream
* @capture_substream: pointer to the PCM capture substream
@ -116,6 +99,7 @@ enum tascam_register {
* @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
* @stream_refs: reference count for implicit stream users (Capture/MIDI)
* @active_urbs: atomic counter for active URBs
* @current_rate: current sample rate
* @playback_frames_consumed: number of frames consumed by the playback device
@ -140,7 +124,6 @@ struct tascam_card {
struct snd_rawmidi *rmidi;
u16 dev_id;
u8 *scratch_buf;
struct snd_pcm_substream *playback_substream;
@ -169,6 +152,7 @@ struct tascam_card {
spinlock_t lock;
atomic_t playback_active;
atomic_t capture_active;
atomic_t stream_refs;
atomic_t active_urbs;
int current_rate;
@ -193,9 +177,10 @@ struct tascam_card {
void tascam_free_urbs(struct tascam_card *tascam);
int tascam_alloc_urbs(struct tascam_card *tascam);
void tascam_stop_work_handler(struct work_struct *work);
void us144mkii_maybe_start_stream(struct tascam_card *tascam);
void us144mkii_maybe_stop_stream(struct tascam_card *tascam);
#include "us144mkii_pcm.h"
int tascam_create_midi(struct tascam_card *tascam);
#endif /* __US144MKII_H */

View File

@ -13,8 +13,8 @@ const struct snd_pcm_hardware tascam_capture_hw = {
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000),
.rate_min = 44100,
.rate_max = 96000,
.channels_min = NUM_CHANNELS,
.channels_max = NUM_CHANNELS,
.channels_min = 4,
.channels_max = 4,
.buffer_bytes_max = 1024 * 1024,
.period_bytes_min = 768,
.period_bytes_max = 1024 * 1024,
@ -25,7 +25,6 @@ const struct snd_pcm_hardware tascam_capture_hw = {
static int tascam_capture_open(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
substream->runtime->hw = tascam_capture_hw;
tascam->capture_substream = substream;
atomic_set(&tascam->capture_active, 0);
@ -35,7 +34,6 @@ static int tascam_capture_open(struct snd_pcm_substream *substream)
static int tascam_capture_close(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
atomic_set(&tascam->capture_active, 0);
usb_kill_anchored_urbs(&tascam->capture_anchor);
tascam->capture_substream = NULL;
@ -45,37 +43,18 @@ static int tascam_capture_close(struct snd_pcm_substream *substream)
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_cap_period_pos = 0;
return 0;
}
static snd_pcm_uframes_t tascam_capture_pointer(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
unsigned long flags;
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);
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;
bool start = false, stop = false;
unsigned long flags;
spin_lock_irqsave(&tascam->lock, flags);
@ -95,132 +74,64 @@ static int tascam_capture_trigger(struct snd_pcm_substream *substream, int cmd)
break;
default:
ret = -EINVAL;
break;
}
if (stop) {
if (tascam->running_ghost_playback) {
tascam->running_ghost_playback = false;
for (i = 0; i < NUM_PLAYBACK_URBS; i++) {
if (tascam->playback_urbs[i])
usb_unlink_urb(tascam->playback_urbs[i]);
}
for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
if (tascam->feedback_urbs[i])
usb_unlink_urb(tascam->feedback_urbs[i]);
}
}
}
spin_unlock_irqrestore(&tascam->lock, flags);
if (start) {
us144mkii_maybe_start_stream(tascam);
spin_lock_irqsave(&tascam->lock, flags);
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);
smp_mb();
for (int j = 0; j < i; j++)
usb_unlink_urb(tascam->capture_urbs[j]);
ret = -EIO;
break;
}
atomic_inc(&tascam->active_urbs);
}
spin_unlock_irqrestore(&tascam->lock, flags);
if (ret == 0 && !atomic_read(&tascam->playback_active)) {
int u;
tascam->running_ghost_playback = true;
tascam->phase_accum = 0;
tascam->freq_q16 = div_u64(((u64)tascam->current_rate << 16), 8000);
tascam->feedback_urb_skip_count = 4;
tascam->feedback_synced = false;
for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
struct urb *f_urb = tascam->feedback_urbs[i];
f_urb->number_of_packets = FEEDBACK_URB_PACKETS;
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;
}
usb_anchor_urb(f_urb, &tascam->feedback_anchor);
if (usb_submit_urb(f_urb, GFP_ATOMIC) < 0) {
usb_unanchor_urb(f_urb);
} else {
atomic_inc(&tascam->active_urbs);
}
}
size_t nominal_bytes = (tascam->current_rate / 8000) * PLAYBACK_FRAME_SIZE;
for (u = 0; u < NUM_PLAYBACK_URBS; u++) {
struct urb *urb = tascam->playback_urbs[u];
size_t total_bytes = 0;
urb->number_of_packets = PLAYBACK_URB_PACKETS;
for (i = 0; i < PLAYBACK_URB_PACKETS; i++) {
urb->iso_frame_desc[i].offset = i * nominal_bytes;
urb->iso_frame_desc[i].length = nominal_bytes;
total_bytes += nominal_bytes;
}
urb->transfer_buffer_length = total_bytes;
memset(urb->transfer_buffer, 0, total_bytes);
usb_anchor_urb(urb, &tascam->playback_anchor);
if (usb_submit_urb(urb, GFP_ATOMIC) < 0) {
usb_unanchor_urb(urb);
} else {
atomic_inc(&tascam->active_urbs);
}
}
}
if (ret < 0)
us144mkii_maybe_stop_stream(tascam);
}
spin_unlock_irqrestore(&tascam->lock, flags);
if (stop) {
spin_lock_irqsave(&tascam->lock, flags);
for (i = 0; i < NUM_CAPTURE_URBS; i++) {
if (tascam->capture_urbs[i])
usb_unlink_urb(tascam->capture_urbs[i]);
}
spin_unlock_irqrestore(&tascam->lock, flags);
us144mkii_maybe_stop_stream(tascam);
}
return ret;
}
static inline void tascam_unpack_8bytes(const u8 *src, u8 *out_bit0, u8 *out_bit1)
{
/* The hardware sends bits in a layout that requires both transposition
* and bit-reversal within the result. swab64() + Butterfly Transpose
* achieves exactly the same mapping as the original bit-by-bit loop.
*/
u64 x = get_unaligned_le64(src);
u64 t;
/* Stage 0: Reverse byte order to handle the hardware's MSB-first nature */
x = __builtin_bswap64(x);
/* 8x8 Bit Transposition (Butterfly) */
t = (x ^ (x >> 7)) & 0x00AA00AA00AA00AAULL;
x = x ^ t ^ (t << 7);
t = (x ^ (x >> 14)) & 0x0000CCCC0000CCCCULL;
x = x ^ t ^ (t << 14);
t = (x ^ (x >> 28)) & 0x00000000F0F0F0F0ULL;
x = x ^ t ^ (t << 28);
/* Extract the untangled bits for the first two sample planes */
*out_bit0 = (u8)(x >> 0);
*out_bit1 = (u8)(x >> 8);
}
static void tascam_decode_capture_chunk(const u8 *src, u32 *dst, int frames_to_decode)
static void tascam_decode_capture_chunk(const u8 *src, u32 *dst, int frames)
{
int i;
u8 h[4], m[4], l[4];
u64 x, t;
for (i = 0; i < frames_to_decode; i++) {
const u8 *p_src_a = src + (i * 64);
const u8 *p_src_b = src + (i * 64) + 32;
/*
* Unpack using bit-reversal and butterfly transposition
* to handle the device's specific bit layout.
*/
for (i = 0; i < frames; i++) {
const u8 *sa = src + (i * 64), *sb = sa + 32;
tascam_unpack_8bytes(p_src_a, &h[0], &h[2]);
tascam_unpack_8bytes(p_src_a + 8, &m[0], &m[2]);
tascam_unpack_8bytes(p_src_a + 16, &l[0], &l[2]);
#define UNPACK(s, o0, o1) do { \
x = __builtin_bswap64(get_unaligned_le64(s)); \
t = (x ^ (x >> 7)) & 0x00AA00AA00AA00AAULL; x = x ^ t ^ (t << 7); \
t = (x ^ (x >> 14)) & 0x0000CCCC0000CCCCULL; x = x ^ t ^ (t << 14); \
t = (x ^ (x >> 28)) & 0x00000000F0F0F0F0ULL; x = x ^ t ^ (t << 28); \
*o0 = (u8)x; *o1 = (u8)(x >> 8); \
} while (0)
tascam_unpack_8bytes(p_src_b, &h[1], &h[3]);
tascam_unpack_8bytes(p_src_b + 8, &m[1], &m[3]);
tascam_unpack_8bytes(p_src_b + 16, &l[1], &l[3]);
UNPACK(sa, &h[0], &h[2]); UNPACK(sa + 8, &m[0], &m[2]); UNPACK(sa + 16, &l[0], &l[2]);
UNPACK(sb, &h[1], &h[3]); UNPACK(sb + 8, &m[1], &m[3]); UNPACK(sb + 16, &l[1], &l[3]);
put_unaligned_le32((h[0] << 24) | (m[0] << 16) | (l[0] << 8), dst++);
put_unaligned_le32((h[1] << 24) | (m[1] << 16) | (l[1] << 8), dst++);
@ -229,102 +140,86 @@ static void tascam_decode_capture_chunk(const u8 *src, u32 *dst, int frames_to_d
}
}
/**
* capture_urb_complete() - completion handler for capture URBs
* @urb: the completed URB
*
* Decodes audio data, updates ring buffer, and handles period elapsed.
*/
void capture_urb_complete(struct urb *urb)
{
struct tascam_card *tascam = urb->context;
struct snd_pcm_substream *substream;
struct snd_pcm_runtime *runtime;
int frames, part1;
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 || !tascam || !tascam->dev)
goto exit;
if (!tascam->dev) {
usb_unanchor_urb(urb);
atomic_dec(&tascam->active_urbs);
return;
}
if (!tascam->capture_substream || !tascam->capture_substream->runtime)
goto exit;
if (urb->status) {
usb_unanchor_urb(urb);
atomic_dec(&tascam->active_urbs);
return;
}
runtime = tascam->capture_substream->runtime;
frames = urb->actual_length / 64;
substream = tascam->capture_substream;
if (!substream || !substream->runtime) {
usb_unanchor_urb(urb);
atomic_dec(&tascam->active_urbs);
return;
}
runtime = substream->runtime;
if (!runtime->dma_area) {
usb_unanchor_urb(urb);
atomic_dec(&tascam->active_urbs);
return;
}
buffer_size = runtime->buffer_size;
period_size = runtime->period_size;
if (urb->actual_length % 64 != 0)
dev_warn_ratelimited(&tascam->dev->dev, "Unaligned capture packet size: %d\n", urb->actual_length);
frames_received = urb->actual_length / 64;
if (frames_received > 0) {
if (frames > 0) {
spin_lock_irqsave(&tascam->lock, flags);
if (!atomic_read(&tascam->capture_active)) {
spin_unlock_irqrestore(&tascam->lock, flags);
atomic_dec(&tascam->active_urbs);
return;
goto exit;
}
write_pos = tascam->driver_capture_pos;
u32 *dma_ptr = (u32 *)(runtime->dma_area + frames_to_bytes(runtime, write_pos));
u32 *dma = (u32 *)(runtime->dma_area + frames_to_bytes(runtime, tascam->driver_capture_pos));
if (write_pos + frames_received <= buffer_size) {
tascam_decode_capture_chunk(urb->transfer_buffer, dma_ptr, frames_received);
if (tascam->driver_capture_pos + frames <= runtime->buffer_size) {
tascam_decode_capture_chunk(urb->transfer_buffer, dma, frames);
} 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);
part1 = runtime->buffer_size - tascam->driver_capture_pos;
tascam_decode_capture_chunk(urb->transfer_buffer, dma, part1);
tascam_decode_capture_chunk(urb->transfer_buffer + (part1 * 64),
(u32 *)runtime->dma_area, frames - part1);
}
tascam->driver_capture_pos += frames_received;
if (tascam->driver_capture_pos >= buffer_size)
tascam->driver_capture_pos -= buffer_size;
tascam->driver_capture_pos = (tascam->driver_capture_pos + frames) % runtime->buffer_size;
tascam->capture_frames_processed += frames;
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;
}
if (div_u64(tascam->capture_frames_processed, runtime->period_size) > tascam->last_cap_period_pos) {
tascam->last_cap_period_pos = div_u64(tascam->capture_frames_processed, runtime->period_size);
need_period_elapsed = true;
}
spin_unlock_irqrestore(&tascam->lock, flags);
}
usb_anchor_urb(urb, &tascam->capture_anchor);
if (usb_submit_urb(urb, GFP_ATOMIC) < 0) {
usb_unanchor_urb(urb);
atomic_dec(&tascam->active_urbs);
spin_unlock_irqrestore(&tascam->lock, flags);
return;
}
if (need_period_elapsed)
snd_pcm_period_elapsed(tascam->capture_substream);
return;
exit:
usb_unanchor_urb(urb);
atomic_dec(&tascam->active_urbs);
}
static snd_pcm_uframes_t tascam_capture_pointer(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
unsigned long flags;
snd_pcm_uframes_t ptr;
spin_lock_irqsave(&tascam->lock, flags);
ptr = tascam->driver_capture_pos;
spin_unlock_irqrestore(&tascam->lock, flags);
if (need_period_elapsed && substream)
snd_pcm_period_elapsed(substream);
return ptr;
}
const struct snd_pcm_ops tascam_capture_ops = {
@ -332,7 +227,6 @@ const struct snd_pcm_ops tascam_capture_ops = {
.close = tascam_capture_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = tascam_pcm_hw_params,
.hw_free = NULL,
.prepare = tascam_capture_prepare,
.trigger = tascam_capture_trigger,
.pointer = tascam_capture_pointer,

View File

@ -6,71 +6,56 @@
static void tascam_midi_out_complete(struct urb *urb)
{
struct tascam_card *tascam = urb->context;
struct snd_rawmidi_substream *substream;
struct snd_rawmidi_substream *sub = tascam->midi_output;
unsigned long flags;
int count;
bool submit = false;
bool active;
spin_lock_irqsave(&tascam->midi_lock, flags);
substream = tascam->midi_output;
active = tascam->midi_out_active;
if (urb->status || !substream) {
if (urb->status || !sub || !tascam->midi_out_active) {
tascam->midi_out_active = false;
spin_unlock_irqrestore(&tascam->midi_lock, flags);
return;
}
if (!active) {
spin_unlock_irqrestore(&tascam->midi_lock, flags);
return;
}
count = snd_rawmidi_transmit(substream, tascam->midi_out_buf, MIDI_PAYLOAD_SIZE);
count = snd_rawmidi_transmit(sub, tascam->midi_out_buf, 8);
if (count > 0) {
if (count < MIDI_PAYLOAD_SIZE)
memset(tascam->midi_out_buf + count, 0xFD, MIDI_PAYLOAD_SIZE - count);
if (count < 8)
memset(tascam->midi_out_buf + count, 0xFD, 8 - count);
tascam->midi_out_buf[8] = 0xE0;
urb->transfer_buffer_length = MIDI_PACKET_SIZE;
submit = true;
} else {
tascam->midi_out_active = false;
}
urb->transfer_buffer_length = 9;
if (submit) {
usb_anchor_urb(urb, &tascam->midi_anchor);
if (usb_submit_urb(urb, GFP_ATOMIC) < 0) {
usb_unanchor_urb(urb);
tascam->midi_out_active = false;
}
} else {
tascam->midi_out_active = false;
}
spin_unlock_irqrestore(&tascam->midi_lock, flags);
}
static void tascam_midi_output_trigger(struct snd_rawmidi_substream *substream, int up)
static void tascam_midi_output_trigger(struct snd_rawmidi_substream *sub, int up)
{
struct tascam_card *tascam = substream->rmidi->private_data;
struct tascam_card *tascam = sub->rmidi->private_data;
unsigned long flags;
int count;
spin_lock_irqsave(&tascam->midi_lock, flags);
if (up) {
tascam->midi_output = substream;
tascam->midi_output = sub;
if (!tascam->midi_out_active) {
tascam->midi_out_active = true;
count = snd_rawmidi_transmit(substream, tascam->midi_out_buf, MIDI_PAYLOAD_SIZE);
count = snd_rawmidi_transmit(sub, tascam->midi_out_buf, 8);
if (count > 0) {
if (count < MIDI_PAYLOAD_SIZE)
memset(tascam->midi_out_buf + count, 0xFD, MIDI_PAYLOAD_SIZE - count);
if (count < 8)
memset(tascam->midi_out_buf + count, 0xFD, 8 - count);
tascam->midi_out_buf[8] = 0xE0;
tascam->midi_out_urb->transfer_buffer_length = MIDI_PACKET_SIZE;
tascam->midi_out_urb->transfer_buffer_length = 9;
usb_anchor_urb(tascam->midi_out_urb, &tascam->midi_anchor);
if (usb_submit_urb(tascam->midi_out_urb, GFP_ATOMIC) < 0) {
@ -84,33 +69,28 @@ static void tascam_midi_output_trigger(struct snd_rawmidi_substream *substream,
} else {
tascam->midi_output = NULL;
}
spin_unlock_irqrestore(&tascam->midi_lock, flags);
}
static void tascam_midi_in_complete(struct urb *urb)
{
struct tascam_card *tascam = urb->context;
struct snd_rawmidi_substream *substream;
struct snd_rawmidi_substream *sub;
unsigned long flags;
int len = 0;
if (urb->status)
return;
spin_lock_irqsave(&tascam->midi_lock, flags);
substream = tascam->midi_input;
sub = tascam->midi_input;
spin_unlock_irqrestore(&tascam->midi_lock, flags);
if (urb->actual_length == MIDI_PACKET_SIZE && substream) {
u8 *data = urb->transfer_buffer;
int len = 0;
/* Find the actual length of the MIDI message (stop at 0xFD padding) */
while (len < MIDI_PAYLOAD_SIZE && data[len] != 0xFD)
if (urb->actual_length == 9 && sub) {
while (len < 8 && ((u8 *)urb->transfer_buffer)[len] != 0xFD)
len++;
if (len > 0)
snd_rawmidi_receive(substream, data, len);
snd_rawmidi_receive(sub, urb->transfer_buffer, len);
}
usb_anchor_urb(urb, &tascam->midi_anchor);
@ -118,38 +98,40 @@ static void tascam_midi_in_complete(struct urb *urb)
usb_unanchor_urb(urb);
}
static void tascam_midi_input_trigger(struct snd_rawmidi_substream *substream, int up)
static void tascam_midi_input_trigger(struct snd_rawmidi_substream *sub, int up)
{
struct tascam_card *tascam = substream->rmidi->private_data;
struct tascam_card *tascam = sub->rmidi->private_data;
unsigned long flags;
spin_lock_irqsave(&tascam->midi_lock, flags);
if (up) {
tascam->midi_input = substream;
} else {
tascam->midi_input = NULL;
}
tascam->midi_input = up ? sub : NULL;
spin_unlock_irqrestore(&tascam->midi_lock, flags);
}
static int tascam_midi_open(struct snd_rawmidi_substream *substream)
{
struct tascam_card *tascam = substream->rmidi->private_data;
int err = 0;
us144mkii_maybe_start_stream(tascam);
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) {
} else {
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;
err = -EIO;
}
}
return 0;
if (err < 0)
us144mkii_maybe_stop_stream(tascam);
return err;
}
static int tascam_midi_close(struct snd_rawmidi_substream *substream)
@ -158,16 +140,18 @@ static int tascam_midi_close(struct snd_rawmidi_substream *substream)
if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT)
usb_kill_urb(tascam->midi_in_urb);
us144mkii_maybe_stop_stream(tascam);
return 0;
}
static const struct snd_rawmidi_ops midi_output_ops = {
static const struct snd_rawmidi_ops midi_out_ops = {
.open = tascam_midi_open,
.close = tascam_midi_close,
.trigger = tascam_midi_output_trigger,
};
static const struct snd_rawmidi_ops midi_input_ops = {
static const struct snd_rawmidi_ops midi_in_ops = {
.open = tascam_midi_open,
.close = tascam_midi_close,
.trigger = tascam_midi_input_trigger,
@ -181,18 +165,21 @@ static const struct snd_rawmidi_ops midi_input_ops = {
*/
int tascam_create_midi(struct tascam_card *tascam)
{
int err;
struct snd_rawmidi *rmidi;
int err;
char midi_name[48];
const char *model_name = (tascam->dev_id == USB_PID_TASCAM_US144) ? "US-144" : "US-144MKII";
err = snd_rawmidi_new(tascam->card, "TASCAM MIDI", 0, 1, 1, &rmidi);
if (err < 0)
return err;
rmidi->private_data = tascam;
strscpy(rmidi->name, "TASCAM US-144MKII MIDI", sizeof(rmidi->name));
snprintf(midi_name, sizeof(midi_name), "TASCAM %s MIDI", model_name);
strscpy(rmidi->name, midi_name, sizeof(rmidi->name));
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &midi_output_ops);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &midi_input_ops);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &midi_out_ops);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &midi_in_ops);
rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
SNDRV_RAWMIDI_INFO_INPUT |
@ -200,30 +187,16 @@ int tascam_create_midi(struct tascam_card *tascam)
tascam->rmidi = rmidi;
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;
}
if (!tascam->midi_out_urb || !tascam->midi_in_urb)
return -ENOMEM;
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;
}
if (!tascam->midi_out_buf || !tascam->midi_in_buf)
return -ENOMEM;
usb_fill_bulk_urb(tascam->midi_out_urb, tascam->dev,
usb_sndbulkpipe(tascam->dev, EP_MIDI_OUT),
@ -241,16 +214,4 @@ int tascam_create_midi(struct tascam_card *tascam)
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

@ -3,6 +3,19 @@
#include "us144mkii_pcm.h"
struct rate_config {
int rate;
u8 data[3];
u16 reg;
};
static const struct rate_config rate_map[] = {
{ 44100, {0x44, 0xac, 0x00}, 0x1000 },
{ 48000, {0x80, 0xbb, 0x00}, 0x1002 },
{ 88200, {0x88, 0x58, 0x01}, 0x1008 },
{ 96000, {0x00, 0x77, 0x01}, 0x100a },
};
static int tascam_write_regs(struct tascam_card *tascam, const u16 *regs, size_t count)
{
int i, err = 0;
@ -31,77 +44,54 @@ static int tascam_write_regs(struct tascam_card *tascam, const u16 *regs, size_t
int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int rate)
{
struct usb_device *dev = tascam->dev;
u8 *rate_payload;
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 };
static const u8 payload_88200[] = { 0x88, 0x58, 0x01 };
static const u8 payload_96000[] = { 0x00, 0x77, 0x01 };
int i, err;
const struct rate_config *cfg = NULL;
u8 *payload;
if (!dev)
return -ENODEV;
switch (rate) {
case 44100:
current_payload_src = payload_44100;
rate_reg = REG_ADDR_RATE_44100;
for (i = 0; i < ARRAY_SIZE(rate_map); i++) {
if (rate_map[i].rate == rate) {
cfg = &rate_map[i];
break;
case 48000:
current_payload_src = payload_48000;
rate_reg = REG_ADDR_RATE_48000;
break;
case 88200:
current_payload_src = payload_88200;
rate_reg = REG_ADDR_RATE_88200;
break;
case 96000:
current_payload_src = payload_96000;
rate_reg = REG_ADDR_RATE_96000;
break;
default:
return -EINVAL;
}
}
if (!cfg)
return -EINVAL;
rate_payload = kmemdup(current_payload_src, 3, GFP_KERNEL);
if (!rate_payload)
payload = kmemdup(cfg->data, 3, GFP_KERNEL);
if (!payload)
return -ENOMEM;
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);
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 out;
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, 3, USB_CTRL_TIMEOUT_MS);
EP_AUDIO_IN, payload, 3, USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto out;
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, 3, USB_CTRL_TIMEOUT_MS);
EP_AUDIO_OUT, payload, 3, USB_CTRL_TIMEOUT_MS);
if (err < 0)
goto out;
{
const u16 regs_to_write[] = {
REG_ADDR_INIT_0D, REG_ADDR_INIT_0E,
REG_ADDR_INIT_0F, rate_reg, REG_ADDR_INIT_11
0x0d04, 0x0e00, 0x0f00, cfg->reg, 0x110b
};
err = tascam_write_regs(tascam, regs_to_write, ARRAY_SIZE(regs_to_write));
if (err < 0)
goto out;
}
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);
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);
out:
kfree(rate_payload);
kfree(payload);
return err;
}
@ -110,20 +100,17 @@ int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int rate)
* @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.
* This function allocates pages for the PCM buffer and 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)
int tascam_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
unsigned int rate = params_rate(params);
int err;
unsigned long flags;
int err;
spin_lock_irqsave(&tascam->lock, flags);
if (tascam->current_rate == rate) {
@ -131,32 +118,30 @@ int tascam_pcm_hw_params(struct snd_pcm_substream *substream,
return 0;
}
if (atomic_read(&tascam->playback_active) ||
atomic_read(&tascam->capture_active)) {
spin_unlock_irqrestore(&tascam->lock, flags);
return -EBUSY;
}
if (atomic_read(&tascam->playback_active) || atomic_read(&tascam->capture_active)) {
spin_unlock_irqrestore(&tascam->lock, flags);
return -EBUSY;
}
spin_unlock_irqrestore(&tascam->lock, flags);
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) {
spin_lock_irqsave(&tascam->lock, flags);
tascam->current_rate = 0;
spin_unlock_irqrestore(&tascam->lock, flags);
return err;
}
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) {
spin_lock_irqsave(&tascam->lock, flags);
tascam->current_rate = rate;
tascam->current_rate = 0;
spin_unlock_irqrestore(&tascam->lock, flags);
return err;
}
return 0;
spin_lock_irqsave(&tascam->lock, flags);
tascam->current_rate = rate;
spin_unlock_irqrestore(&tascam->lock, flags);
return 0;
}
/**
@ -167,9 +152,6 @@ 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->dev)
return;
if (tascam->playback_substream)
if (tascam->dev && tascam->playback_substream)
snd_pcm_stop(tascam->playback_substream, SNDRV_PCM_STATE_XRUN);
}

View File

@ -8,7 +8,6 @@
extern const struct snd_pcm_hardware tascam_playback_hw;
extern const struct snd_pcm_hardware tascam_capture_hw;
extern const struct snd_pcm_ops tascam_playback_ops;
extern const struct snd_pcm_ops tascam_capture_ops;
@ -17,7 +16,6 @@ void feedback_urb_complete(struct urb *urb);
void capture_urb_complete(struct urb *urb);
int us144mkii_configure_device_for_rate(struct tascam_card *tascam, int rate);
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);
int tascam_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params);
#endif /* __US144MKII_PCM_H */

View File

@ -12,8 +12,8 @@ const struct snd_pcm_hardware tascam_playback_hw = {
SNDRV_PCM_RATE_88200 | SNDRV_PCM_RATE_96000),
.rate_min = 44100,
.rate_max = 96000,
.channels_min = NUM_CHANNELS,
.channels_max = NUM_CHANNELS,
.channels_min = 4,
.channels_max = 4,
.buffer_bytes_max = 1024 * 1024,
.period_bytes_min = 576,
.period_bytes_max = 1024 * 1024,
@ -21,54 +21,27 @@ const struct snd_pcm_hardware tascam_playback_hw = {
.periods_max = 1024,
};
static int tascam_playback_open(struct snd_pcm_substream *substream)
static int submit_urbs(struct tascam_card *tascam, struct urb **urbs, int count, struct usb_anchor *anchor)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
substream->runtime->hw = tascam_playback_hw;
tascam->playback_substream = substream;
atomic_set(&tascam->playback_active, 0);
int i;
for (i = 0; i < count; i++) {
usb_anchor_urb(urbs[i], anchor);
if (usb_submit_urb(urbs[i], GFP_ATOMIC) < 0) {
usb_unanchor_urb(urbs[i]);
return -EIO;
}
atomic_inc(&tascam->active_urbs);
}
return 0;
}
static int tascam_playback_close(struct snd_pcm_substream *substream)
static void prepare_urb_descriptors(struct tascam_card *tascam)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
atomic_set(&tascam->playback_active, 0);
usb_kill_anchored_urbs(&tascam->playback_anchor);
usb_kill_anchored_urbs(&tascam->feedback_anchor);
tascam->playback_substream = NULL;
return 0;
}
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_bytes = (runtime->rate / 8000) * PLAYBACK_FRAME_SIZE;
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_pb_period_pos = 0;
tascam->feedback_synced = false;
tascam->running_ghost_playback = false;
tascam->feedback_urb_skip_count = 4;
tascam->phase_accum = 0;
tascam->freq_q16 = div_u64(((u64)runtime->rate << 16), 8000);
size_t nominal_bytes = (tascam->current_rate / 8000) * PLAYBACK_FRAME_SIZE;
for (i = 0; i < NUM_FEEDBACK_URBS; i++) {
struct urb *f_urb = tascam->feedback_urbs[i];
f_urb->number_of_packets = FEEDBACK_URB_PACKETS;
f_urb->transfer_buffer_length = FEEDBACK_URB_PACKETS * FEEDBACK_PACKET_SIZE;
for (u = 0; u < FEEDBACK_URB_PACKETS; u++) {
@ -79,22 +52,147 @@ static int tascam_playback_prepare(struct snd_pcm_substream *substream)
for (u = 0; u < NUM_PLAYBACK_URBS; u++) {
struct urb *urb = tascam->playback_urbs[u];
size_t total_bytes = 0;
urb->number_of_packets = PLAYBACK_URB_PACKETS;
for (i = 0; i < PLAYBACK_URB_PACKETS; i++) {
urb->iso_frame_desc[i].offset = i * nominal_bytes;
urb->iso_frame_desc[i].length = nominal_bytes;
total_bytes += nominal_bytes;
}
urb->transfer_buffer_length = total_bytes;
memset(urb->transfer_buffer, 0, total_bytes);
urb->transfer_buffer_length = PLAYBACK_URB_PACKETS * nominal_bytes;
memset(urb->transfer_buffer, 0, urb->transfer_buffer_length);
}
}
/**
* us144mkii_maybe_start_stream() - Start implicit playback for capture/MIDI
* @tascam: the tascam_card instance
*
* Increments reference count and starts the ghost playback stream if no
* real playback is active.
*/
void us144mkii_maybe_start_stream(struct tascam_card *tascam)
{
unsigned long flags;
atomic_inc(&tascam->stream_refs);
spin_lock_irqsave(&tascam->lock, flags);
if (!atomic_read(&tascam->playback_active) && !tascam->running_ghost_playback) {
tascam->running_ghost_playback = true;
tascam->phase_accum = 0;
tascam->freq_q16 = div_u64(((u64)tascam->current_rate << 16), 8000);
tascam->feedback_urb_skip_count = 4;
tascam->feedback_synced = false;
prepare_urb_descriptors(tascam);
submit_urbs(tascam, tascam->feedback_urbs, NUM_FEEDBACK_URBS, &tascam->feedback_anchor);
submit_urbs(tascam, tascam->playback_urbs, NUM_PLAYBACK_URBS, &tascam->playback_anchor);
}
spin_unlock_irqrestore(&tascam->lock, flags);
}
/**
* us144mkii_maybe_stop_stream() - Stop implicit playback
* @tascam: the tascam_card instance
*
* Decrements reference count and stops the ghost playback stream if it
* reaches zero and no real playback is active.
*/
void us144mkii_maybe_stop_stream(struct tascam_card *tascam)
{
unsigned long flags;
int i;
if (atomic_dec_return(&tascam->stream_refs) > 0)
return;
spin_lock_irqsave(&tascam->lock, flags);
if (!atomic_read(&tascam->playback_active) && tascam->running_ghost_playback) {
tascam->running_ghost_playback = false;
for (i = 0; i < NUM_PLAYBACK_URBS; i++)
usb_unlink_urb(tascam->playback_urbs[i]);
for (i = 0; i < NUM_FEEDBACK_URBS; i++)
usb_unlink_urb(tascam->feedback_urbs[i]);
}
spin_unlock_irqrestore(&tascam->lock, flags);
}
static int tascam_playback_open(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
substream->runtime->hw = tascam_playback_hw;
tascam->playback_substream = substream;
atomic_set(&tascam->playback_active, 0);
return 0;
}
static int tascam_playback_close(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
atomic_set(&tascam->playback_active, 0);
usb_kill_anchored_urbs(&tascam->playback_anchor);
usb_kill_anchored_urbs(&tascam->feedback_anchor);
tascam->playback_substream = NULL;
return 0;
}
static int tascam_playback_prepare(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
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_pb_period_pos = 0;
tascam->feedback_synced = false;
tascam->running_ghost_playback = false;
tascam->feedback_urb_skip_count = 4;
tascam->phase_accum = 0;
tascam->freq_q16 = div_u64(((u64)tascam->current_rate << 16), 8000);
prepare_urb_descriptors(tascam);
return 0;
}
static int tascam_playback_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
unsigned long flags;
int ret = 0;
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);
/* If ghost playback is running, just takeover flag */
if (tascam->running_ghost_playback) {
tascam->running_ghost_playback = false;
} else {
submit_urbs(tascam, tascam->feedback_urbs, NUM_FEEDBACK_URBS, &tascam->feedback_anchor);
submit_urbs(tascam, tascam->playback_urbs, NUM_PLAYBACK_URBS, &tascam->playback_anchor);
}
}
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
atomic_set(&tascam->playback_active, 0);
/* Fall back to ghost playback if capture/midi active */
if (atomic_read(&tascam->stream_refs) > 0)
tascam->running_ghost_playback = true;
break;
default:
ret = -EINVAL;
break;
}
spin_unlock_irqrestore(&tascam->lock, flags);
return ret;
}
static snd_pcm_uframes_t tascam_playback_pointer(struct snd_pcm_substream *substream)
{
struct tascam_card *tascam = snd_pcm_substream_chip(substream);
@ -111,159 +209,102 @@ static snd_pcm_uframes_t tascam_playback_pointer(struct snd_pcm_substream *subst
return (snd_pcm_uframes_t)(pos % substream->runtime->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;
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);
tascam->running_ghost_playback = false;
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]);
} else {
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]);
} else {
atomic_inc(&tascam->active_urbs);
}
}
}
break;
case SNDRV_PCM_TRIGGER_STOP:
case SNDRV_PCM_TRIGGER_SUSPEND:
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
atomic_set(&tascam->playback_active, 0);
break;
default:
ret = -EINVAL;
break;
}
spin_unlock_irqrestore(&tascam->lock, flags);
return ret;
}
/**
* 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.
* copies the audio data from the ALSA ring buffer (or zero for ghost stream),
* and resubmits the URB.
*/
void playback_urb_complete(struct urb *urb)
{
struct tascam_card *tascam = urb->context;
struct snd_pcm_substream *substream;
struct snd_pcm_runtime *runtime;
size_t total_bytes_for_urb = 0;
snd_pcm_uframes_t frames_to_copy;
int i;
size_t total_bytes = 0;
size_t ptr_bytes, part1_bytes;
unsigned int frames, i;
unsigned long flags;
bool need_period_elapsed = false;
if (urb->status == -ENOENT || urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN || !tascam) {
goto exit_clear;
if (urb->status || !tascam ||
(!atomic_read(&tascam->playback_active) &&
!tascam->running_ghost_playback)) {
if (tascam) {
usb_unanchor_urb(urb);
atomic_dec(&tascam->active_urbs);
}
if (!atomic_read(&tascam->playback_active) && !tascam->running_ghost_playback)
goto exit_clear;
return;
}
spin_lock_irqsave(&tascam->lock, flags);
substream = tascam->playback_substream;
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;
frames = min((tascam->phase_accum >> 16), (u32)MAX_FRAMES_PER_PACKET);
tascam->phase_accum &= 0xFFFF;
if (frames_for_packet > MAX_FRAMES_PER_PACKET)
frames_for_packet = MAX_FRAMES_PER_PACKET;
urb->iso_frame_desc[i].offset = total_bytes_for_urb;
urb->iso_frame_desc[i].length = frames_for_packet * PLAYBACK_FRAME_SIZE;
total_bytes_for_urb += urb->iso_frame_desc[i].length;
urb->iso_frame_desc[i].offset = total_bytes;
urb->iso_frame_desc[i].length = frames * PLAYBACK_FRAME_SIZE;
total_bytes += frames * PLAYBACK_FRAME_SIZE;
}
urb->transfer_buffer_length = total_bytes_for_urb;
urb->transfer_buffer_length = total_bytes;
if (total_bytes_for_urb == 0) {
/* Ghost Playback: Send Silence */
if (!atomic_read(&tascam->playback_active)) {
if (tascam->running_ghost_playback) {
memset(urb->transfer_buffer, 0, total_bytes);
spin_unlock_irqrestore(&tascam->lock, flags);
goto resubmit;
}
spin_unlock_irqrestore(&tascam->lock, flags);
usb_unanchor_urb(urb);
atomic_dec(&tascam->active_urbs);
return;
}
if (!tascam->playback_substream) {
memset(urb->transfer_buffer, 0, total_bytes);
spin_unlock_irqrestore(&tascam->lock, flags);
goto resubmit;
}
if (!substream) {
memset(urb->transfer_buffer, 0, total_bytes_for_urb);
spin_unlock_irqrestore(&tascam->lock, flags);
goto resubmit;
}
runtime = substream->runtime;
size_t playback_pos = tascam->driver_playback_pos;
runtime = tascam->playback_substream->runtime;
ptr_bytes = frames_to_bytes(runtime, tascam->driver_playback_pos);
spin_unlock_irqrestore(&tascam->lock, flags);
/* Copy from ALSA DMA buffer - outside of the main device lock */
u8 *dst_buf = urb->transfer_buffer;
size_t ptr_bytes = frames_to_bytes(runtime, playback_pos);
frames_to_copy = bytes_to_frames(runtime, total_bytes_for_urb);
if (playback_pos + frames_to_copy > runtime->buffer_size) {
size_t part1 = runtime->buffer_size - playback_pos;
size_t part1_bytes = frames_to_bytes(runtime, part1);
memcpy(dst_buf, runtime->dma_area + ptr_bytes, part1_bytes);
memcpy(dst_buf + part1_bytes, runtime->dma_area, total_bytes_for_urb - part1_bytes);
/* Copy from ALSA Buffer */
if (total_bytes + ptr_bytes > frames_to_bytes(runtime, runtime->buffer_size)) {
part1_bytes = frames_to_bytes(runtime, runtime->buffer_size) - ptr_bytes;
memcpy(urb->transfer_buffer, runtime->dma_area + ptr_bytes, part1_bytes);
memcpy(urb->transfer_buffer + part1_bytes, runtime->dma_area, total_bytes - part1_bytes);
} else {
memcpy(dst_buf, runtime->dma_area + ptr_bytes, total_bytes_for_urb);
memcpy(urb->transfer_buffer, runtime->dma_area + ptr_bytes, total_bytes);
}
/* Re-acquire lock to update shared stream state */
spin_lock_irqsave(&tascam->lock, flags);
tascam->driver_playback_pos += frames_to_copy;
tascam->driver_playback_pos += bytes_to_frames(runtime, total_bytes);
if (tascam->driver_playback_pos >= runtime->buffer_size)
tascam->driver_playback_pos -= runtime->buffer_size;
tascam->playback_frames_consumed += frames_to_copy;
if (runtime->period_size > 0 &&
div_u64(tascam->playback_frames_consumed, runtime->period_size) > tascam->last_pb_period_pos) {
tascam->playback_frames_consumed += bytes_to_frames(runtime, total_bytes);
if (div_u64(tascam->playback_frames_consumed, runtime->period_size) > tascam->last_pb_period_pos) {
tascam->last_pb_period_pos = div_u64(tascam->playback_frames_consumed, runtime->period_size);
need_period_elapsed = true;
}
spin_unlock_irqrestore(&tascam->lock, flags);
resubmit:
usb_anchor_urb(urb, &tascam->playback_anchor);
if (usb_submit_urb(urb, GFP_ATOMIC) < 0) {
goto exit_clear_locked;
}
if (need_period_elapsed && substream)
snd_pcm_period_elapsed(substream);
if (usb_submit_urb(urb, GFP_ATOMIC) < 0)
goto exit_unanchor;
if (need_period_elapsed && tascam->playback_substream)
snd_pcm_period_elapsed(tascam->playback_substream);
return;
exit_clear_locked:
spin_lock_irqsave(&tascam->lock, flags);
exit_clear:
exit_unanchor:
usb_unanchor_urb(urb);
spin_unlock_irqrestore(&tascam->lock, flags);
atomic_dec(&tascam->active_urbs);
}
@ -271,12 +312,7 @@ exit_clear:
* 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.
* Updates the PLL based on the number of samples consumed by the device.
*/
void feedback_urb_complete(struct urb *urb)
{
@ -284,8 +320,7 @@ void feedback_urb_complete(struct urb *urb)
unsigned long flags;
int p;
if (urb->status || !tascam ||
(!atomic_read(&tascam->playback_active) && !tascam->running_ghost_playback)) {
if (urb->status || !tascam || (!atomic_read(&tascam->playback_active) && !tascam->running_ghost_playback)) {
usb_unanchor_urb(urb);
atomic_dec(&tascam->active_urbs);
return;
@ -295,33 +330,20 @@ void feedback_urb_complete(struct urb *urb)
if (tascam->feedback_urb_skip_count > 0) {
tascam->feedback_urb_skip_count--;
spin_unlock_irqrestore(&tascam->lock, flags);
goto resubmit;
}
for (p = 0; p < urb->number_of_packets; p++) {
if (urb->iso_frame_desc[p].status == 0 && urb->iso_frame_desc[p].actual_length >= 1) {
u8 *data = (u8 *)urb->transfer_buffer + urb->iso_frame_desc[p].offset;
u32 sum_frames_3ms;
u32 target_freq_q16;
if (urb->iso_frame_desc[p].actual_length >= 3) {
sum_frames_3ms = data[0] + data[1] + data[2];
} else {
sum_frames_3ms = data[0] * 3;
} else {
for (p = 0; p < urb->number_of_packets; p++) {
if (urb->iso_frame_desc[p].actual_length > 0) {
u8 *d = urb->transfer_buffer + urb->iso_frame_desc[p].offset;
u32 val = (urb->iso_frame_desc[p].actual_length >= 3) ? (d[0] + d[1] + d[2]) : (d[0] * 3);
u32 target = (val << 16) / 24;
tascam->freq_q16 = (tascam->freq_q16 * PLL_FILTER_OLD_WEIGHT +
target * PLL_FILTER_NEW_WEIGHT) / PLL_FILTER_DIVISOR;
tascam->feedback_synced = true;
}
target_freq_q16 = (sum_frames_3ms << 16) / 24;
tascam->freq_q16 = (tascam->freq_q16 * PLL_FILTER_OLD_WEIGHT +
target_freq_q16 * PLL_FILTER_NEW_WEIGHT) / PLL_FILTER_DIVISOR;
tascam->feedback_synced = true;
}
}
spin_unlock_irqrestore(&tascam->lock, flags);
resubmit:
usb_anchor_urb(urb, &tascam->feedback_anchor);
if (usb_submit_urb(urb, GFP_ATOMIC) < 0) {
usb_unanchor_urb(urb);
@ -334,7 +356,6 @@ const struct snd_pcm_ops tascam_playback_ops = {
.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,

119
usb_info Normal file
View File

@ -0,0 +1,119 @@
us 001 Device 004: ID 0644:8020 TEAC Corp. US-144 MKII
Couldn't open device, some information will be missing
Negotiated speed: High Speed (480Mbps)
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 255 Vendor Specific Class
bDeviceSubClass 255 Vendor Specific Subclass
bDeviceProtocol 255 Vendor Specific Protocol
bMaxPacketSize0 64
idVendor 0x0644 TEAC Corp.
idProduct 0x8020 US-144 MKII
bcdDevice 1.00
iManufacturer 1 TASCAM
iProduct 2 US-144 MKII
iSerial 3 no serial number
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0050
bNumInterfaces 2
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 480mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 0
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 0 [unknown]
bInterfaceProtocol 0
iInterface 0
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 1
bNumEndpoints 3
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 0 [unknown]
bInterfaceProtocol 0
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 5
Transfer Type Isochronous
Synch Type Asynchronous
Usage Type Data
wMaxPacketSize 0x009c 1x 156 bytes
bInterval 1
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x83 EP 3 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0200 1x 512 bytes
bInterval 4
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x04 EP 4 OUT
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0200 1x 512 bytes
bInterval 4
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 0
bNumEndpoints 0
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 0 [unknown]
bInterfaceProtocol 0
iInterface 0
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 1
bAlternateSetting 1
bNumEndpoints 2
bInterfaceClass 255 Vendor Specific Class
bInterfaceSubClass 0 [unknown]
bInterfaceProtocol 0
iInterface 0
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 5
Transfer Type Isochronous
Synch Type Asynchronous
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 4
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x86 EP 6 IN
bmAttributes 2
Transfer Type Bulk
Synch Type None
Usage Type Data
wMaxPacketSize 0x0200 1x 512 bytes
bInterval 1