raw driver for jack use
This commit is contained in:
parent
b013e2e0f8
commit
f5a9550048
|
|
@ -55,3 +55,4 @@ dkms.conf
|
|||
*.cmake
|
||||
/tascam_controls/build
|
||||
/tascam_controls/.tools
|
||||
raw_jack_us144mkii/jack_tascam
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
obj-m += tascam_raw.o
|
||||
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -O2
|
||||
LDFLAGS = -ljack
|
||||
|
||||
KDIR ?= /lib/modules/$(shell uname -r)/build
|
||||
PWD := $(shell pwd)
|
||||
|
||||
all: module client
|
||||
|
||||
module:
|
||||
$(MAKE) -C $(KDIR) M=$(PWD) modules
|
||||
|
||||
client: jack_tascam
|
||||
|
||||
jack_tascam: jack_tascam.c
|
||||
$(CC) $(CFLAGS) -o jack_tascam jack_tascam.c $(LDFLAGS)
|
||||
|
||||
clean:
|
||||
$(MAKE) -C $(KDIR) M=$(PWD) clean
|
||||
rm -f jack_tascam
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <jack/jack.h>
|
||||
#include <jack/midiport.h>
|
||||
#include <stdint.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <signal.h>
|
||||
#include "tascam_common.h"
|
||||
|
||||
#define DEVICE_NODE "/dev/tascam_raw"
|
||||
#define MIDI_NODE "/dev/tascam_midi"
|
||||
#define CHANNELS 4
|
||||
#define BYTES_PER_SAMPLE 3
|
||||
#define S24_MAX 8388607.0
|
||||
#define RAW_BLOCK_SIZE 512
|
||||
#define FRAMES_PER_BLOCK 8
|
||||
|
||||
int fd, midi_fd;
|
||||
volatile sig_atomic_t keep_running = 1;
|
||||
|
||||
jack_client_t *client_pb;
|
||||
jack_client_t *client_cap;
|
||||
jack_client_t *client_midi;
|
||||
|
||||
jack_port_t *pb_ports[CHANNELS];
|
||||
jack_port_t *cap_ports[CHANNELS];
|
||||
jack_port_t *midi_in_port;
|
||||
jack_port_t *midi_out_port;
|
||||
|
||||
const char *pb_port_names[] = {
|
||||
"Analog Left", "Analog Right",
|
||||
"Digital Left", "Digital Right"
|
||||
};
|
||||
|
||||
const char *cap_port_names[] = {
|
||||
"Analog Left", "Analog Right",
|
||||
"Digital Left", "Digital Right"
|
||||
};
|
||||
|
||||
void signal_handler(int sig) {
|
||||
keep_running = 0;
|
||||
}
|
||||
|
||||
void decode_block(const uint8_t *src, float **dst_ch, int offset) {
|
||||
int frame, bit;
|
||||
for (frame = 0; frame < FRAMES_PER_BLOCK; ++frame) {
|
||||
const uint8_t *p_src = src + frame * 64;
|
||||
int32_t ch[4] = {0};
|
||||
for (bit = 0; bit < 24; ++bit) {
|
||||
uint8_t byte1 = p_src[bit];
|
||||
ch[0] = (ch[0] << 1) | (byte1 & 1);
|
||||
ch[2] = (ch[2] << 1) | ((byte1 >> 1) & 1);
|
||||
}
|
||||
for (bit = 0; bit < 24; ++bit) {
|
||||
uint8_t byte2 = p_src[bit + 32];
|
||||
ch[1] = (ch[1] << 1) | (byte2 & 1);
|
||||
ch[3] = (ch[3] << 1) | ((byte2 >> 1) & 1);
|
||||
}
|
||||
for(int c=0; c<4; c++) {
|
||||
int32_t val = ch[c];
|
||||
if (val & 0x800000) val |= 0xFF000000;
|
||||
dst_ch[c][offset + frame] = (float)val / S24_MAX;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int process_pb(jack_nframes_t nframes, void *arg) {
|
||||
jack_default_audio_sample_t *pb_in[CHANNELS];
|
||||
|
||||
for (int i = 0; i < CHANNELS; i++) {
|
||||
pb_in[i] = (jack_default_audio_sample_t *)jack_port_get_buffer(pb_ports[i], nframes);
|
||||
}
|
||||
|
||||
size_t pb_bytes = nframes * CHANNELS * BYTES_PER_SAMPLE;
|
||||
unsigned char *pb_buf = malloc(pb_bytes);
|
||||
if (!pb_buf) return 0;
|
||||
|
||||
unsigned char *ptr = pb_buf;
|
||||
for (int i = 0; i < nframes; i++) {
|
||||
for (int c = 0; c < CHANNELS; c++) {
|
||||
float s = pb_in[c][i];
|
||||
if (s > 1.0f) s = 1.0f; else if (s < -1.0f) s = -1.0f;
|
||||
int32_t val = (int32_t)(s * S24_MAX);
|
||||
*ptr++ = (val >> 0) & 0xFF;
|
||||
*ptr++ = (val >> 8) & 0xFF;
|
||||
*ptr++ = (val >> 16) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
if (write(fd, pb_buf, pb_bytes)) {};
|
||||
free(pb_buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int process_cap(jack_nframes_t nframes, void *arg) {
|
||||
jack_default_audio_sample_t *cap_out[CHANNELS];
|
||||
for (int i = 0; i < CHANNELS; i++) {
|
||||
cap_out[i] = (jack_default_audio_sample_t *)jack_port_get_buffer(cap_ports[i], nframes);
|
||||
}
|
||||
|
||||
int blocks_needed = nframes / FRAMES_PER_BLOCK;
|
||||
if (nframes % FRAMES_PER_BLOCK != 0) blocks_needed++;
|
||||
|
||||
size_t raw_bytes = blocks_needed * RAW_BLOCK_SIZE;
|
||||
unsigned char *raw_buf = malloc(raw_bytes);
|
||||
if (!raw_buf) return 0;
|
||||
|
||||
ssize_t read_len = read(fd, raw_buf, raw_bytes);
|
||||
|
||||
float *dst_ptrs[4] = { cap_out[0], cap_out[1], cap_out[2], cap_out[3] };
|
||||
if (read_len > 0) {
|
||||
int blocks_read = read_len / RAW_BLOCK_SIZE;
|
||||
for(int b=0; b<blocks_read; b++) {
|
||||
if ((b * FRAMES_PER_BLOCK) < nframes) {
|
||||
decode_block(raw_buf + (b * RAW_BLOCK_SIZE), dst_ptrs, b * FRAMES_PER_BLOCK);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(int c=0; c<4; c++) memset(cap_out[c], 0, nframes * sizeof(float));
|
||||
}
|
||||
free(raw_buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int process_midi(jack_nframes_t nframes, void *arg) {
|
||||
void *midi_in_buf = jack_port_get_buffer(midi_in_port, nframes);
|
||||
void *midi_out_buf = jack_port_get_buffer(midi_out_port, nframes);
|
||||
jack_midi_clear_buffer(midi_out_buf);
|
||||
|
||||
jack_nframes_t event_count = jack_midi_get_event_count(midi_in_buf);
|
||||
jack_midi_event_t event;
|
||||
for(int i=0; i<event_count; i++) {
|
||||
jack_midi_event_get(&event, midi_in_buf, i);
|
||||
if (event.size > 8) continue;
|
||||
uint8_t packet[9];
|
||||
memcpy(packet, event.buffer, event.size);
|
||||
if (event.size < 8) memset(packet + event.size, 0xFD, 8 - event.size);
|
||||
packet[8] = 0xE0;
|
||||
if (write(midi_fd, packet, 9)) {};
|
||||
}
|
||||
|
||||
uint8_t raw_midi[9 * 16];
|
||||
ssize_t m_read = read(midi_fd, raw_midi, sizeof(raw_midi));
|
||||
if (m_read > 0) {
|
||||
int packets = m_read / 9;
|
||||
for(int p=0; p<packets; p++) {
|
||||
uint8_t *pkt = raw_midi + (p*9);
|
||||
int len = 0;
|
||||
while(len < 8 && pkt[len] != 0xFD) len++;
|
||||
if (len > 0) jack_midi_event_write(midi_out_buf, 0, pkt, len);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main() {
|
||||
signal(SIGINT, signal_handler);
|
||||
signal(SIGTERM, signal_handler);
|
||||
|
||||
fd = open(DEVICE_NODE, O_RDWR);
|
||||
if (fd < 0) { fprintf(stderr, "Could not open %s\n", DEVICE_NODE); return 1; }
|
||||
|
||||
midi_fd = open(MIDI_NODE, O_RDWR | O_NONBLOCK);
|
||||
if (midi_fd < 0) { fprintf(stderr, "Could not open %s\n", MIDI_NODE); return 1; }
|
||||
|
||||
client_pb = jack_client_open("TASCAM Output", JackNullOption, NULL);
|
||||
if (!client_pb) { fprintf(stderr, "Failed to create Playback client\n"); return 1; }
|
||||
|
||||
int rate = jack_get_sample_rate(client_pb);
|
||||
printf("JACK Sample Rate: %d Hz\n", rate);
|
||||
if (ioctl(fd, TASCAM_IOC_SET_RATE, &rate) < 0) { perror("IOCTL failed"); return 1; }
|
||||
|
||||
jack_set_process_callback(client_pb, process_pb, NULL);
|
||||
for (int i = 0; i < CHANNELS; i++) {
|
||||
pb_ports[i] = jack_port_register(client_pb, pb_port_names[i], JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput | JackPortIsTerminal, 0);
|
||||
}
|
||||
|
||||
client_cap = jack_client_open("TASCAM Input", JackNullOption, NULL);
|
||||
if (!client_cap) { fprintf(stderr, "Failed to create Capture client\n"); return 1; }
|
||||
|
||||
jack_set_process_callback(client_cap, process_cap, NULL);
|
||||
for (int i = 0; i < CHANNELS; i++) {
|
||||
cap_ports[i] = jack_port_register(client_cap, cap_port_names[i], JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput | JackPortIsTerminal, 0);
|
||||
}
|
||||
|
||||
client_midi = jack_client_open("TASCAM MIDI", JackNullOption, NULL);
|
||||
if (!client_midi) { fprintf(stderr, "Failed to create MIDI client\n"); return 1; }
|
||||
|
||||
jack_set_process_callback(client_midi, process_midi, NULL);
|
||||
midi_in_port = jack_port_register(client_midi, "MIDI OUT", JACK_DEFAULT_MIDI_TYPE, JackPortIsInput | JackPortIsTerminal, 0);
|
||||
midi_out_port = jack_port_register(client_midi, "MIDI IN", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput | JackPortIsTerminal, 0);
|
||||
|
||||
if (jack_activate(client_pb)) return 1;
|
||||
if (jack_activate(client_cap)) return 1;
|
||||
if (jack_activate(client_midi)) return 1;
|
||||
|
||||
printf("TASCAM Raw JACK Clients Running.\n");
|
||||
printf(" [1] TASCAM Output (Playback)\n");
|
||||
printf(" [2] TASCAM Input (Capture)\n");
|
||||
printf(" [3] TASCAM MIDI (MIDI)\n");
|
||||
printf("Running in background. Send SIGINT (Ctrl+C) to stop.\n");
|
||||
|
||||
while(keep_running) {
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
printf("\nStopping...\n");
|
||||
jack_client_close(client_pb);
|
||||
jack_client_close(client_cap);
|
||||
jack_client_close(client_midi);
|
||||
close(fd);
|
||||
close(midi_fd);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
#!/bin/bash
|
||||
|
||||
DRIVER_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
|
||||
echo "========================================="
|
||||
echo " TASCAM RAW DRIVER LAUNCHER"
|
||||
echo "========================================="
|
||||
|
||||
echo "Select Sample Rate:"
|
||||
echo " 1) 44100 Hz"
|
||||
echo " 2) 48000 Hz (Recommended)"
|
||||
echo " 3) 88200 Hz"
|
||||
echo " 4) 96000 Hz"
|
||||
read -p "Enter choice [1-4]: " rate_choice
|
||||
|
||||
case $rate_choice in
|
||||
1) RATE=44100 ;;
|
||||
2) RATE=48000 ;;
|
||||
3) RATE=88200 ;;
|
||||
4) RATE=96000 ;;
|
||||
*) echo "Invalid choice. Defaulting to 48000."; RATE=48000 ;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
read -p "Enter Buffer Size (frames) [e.g., 64]: " BUFFER
|
||||
if ! [[ "$BUFFER" =~ ^[0-9]+$ ]]; then
|
||||
echo "Invalid buffer size. Defaulting to 64."
|
||||
BUFFER=64
|
||||
fi
|
||||
|
||||
LATENCY=$(awk "BEGIN { printf \"%.2f\", (($BUFFER * 2 * 1000 / $RATE) + 1.0) }")
|
||||
echo ""
|
||||
echo "-----------------------------------------"
|
||||
echo " Estimated Playback Latency: ${LATENCY} ms"
|
||||
echo "-----------------------------------------"
|
||||
echo ""
|
||||
|
||||
cleanup() {
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo " SHUTTING DOWN"
|
||||
echo "========================================="
|
||||
|
||||
if [ ! -z "$CLIENT_PID" ]; then
|
||||
echo "[*] Stopping Client..."
|
||||
kill $CLIENT_PID 2>/dev/null
|
||||
wait $CLIENT_PID 2>/dev/null
|
||||
fi
|
||||
|
||||
if [ ! -z "$A2J_PID" ]; then
|
||||
echo "[*] Stopping a2jmidid..."
|
||||
kill $A2J_PID 2>/dev/null
|
||||
wait $A2J_PID 2>/dev/null
|
||||
fi
|
||||
|
||||
if [ ! -z "$JACK_PID" ]; then
|
||||
echo "[*] Stopping JACK..."
|
||||
kill $JACK_PID 2>/dev/null
|
||||
wait $JACK_PID 2>/dev/null
|
||||
fi
|
||||
|
||||
echo "[*] Unloading Raw Driver..."
|
||||
for i in {1..10}; do
|
||||
if sudo rmmod tascam_raw 2>/dev/null; then
|
||||
break
|
||||
fi
|
||||
sleep 0.5
|
||||
done
|
||||
|
||||
echo "[*] Restoring Standard ALSA Driver..."
|
||||
sudo modprobe snd_usb_us144mkii 2>/dev/null
|
||||
|
||||
echo "[*] Restarting PipeWire/PulseAudio..."
|
||||
systemctl --user start pipewire.socket pipewire-pulse.socket wireplumber.service 2>/dev/null
|
||||
|
||||
echo "Done. Desktop audio restored."
|
||||
exit
|
||||
}
|
||||
|
||||
trap cleanup SIGINT
|
||||
|
||||
echo ""
|
||||
echo "--- INITIALIZING ---"
|
||||
|
||||
cd "$DRIVER_DIR"
|
||||
if [ ! -f "jack_tascam" ] || [ "jack_tascam.c" -nt "jack_tascam" ] || [ ! -f "tascam_raw.ko" ]; then
|
||||
echo "[1/6] Compiling..."
|
||||
make clean > /dev/null
|
||||
make > /dev/null
|
||||
if [ $? -ne 0 ]; then echo "Error: Compilation failed."; exit 1; fi
|
||||
fi
|
||||
|
||||
echo "[2/6] Stopping Desktop Audio Services..."
|
||||
systemctl --user stop pipewire.socket pipewire-pulse.socket wireplumber.service 2>/dev/null
|
||||
killall -9 jackd 2>/dev/null
|
||||
killall -9 a2jmidid 2>/dev/null
|
||||
|
||||
echo "[3/6] Loading Kernel Driver..."
|
||||
sudo rmmod snd_usb_us144mkii 2>/dev/null
|
||||
sudo rmmod snd_usb_audio 2>/dev/null
|
||||
sudo rmmod tascam_raw 2>/dev/null
|
||||
|
||||
TASCAM_BUS_ID=$(grep -l "0644" /sys/bus/usb/devices/*/idVendor | xargs grep -l -E "8020|800f" | sed 's|/idProduct||' | xargs -I{} basename {})
|
||||
if [ ! -z "$TASCAM_BUS_ID" ]; then
|
||||
if [ -e "/sys/bus/usb/devices/$TASCAM_BUS_ID:1.0/driver/unbind" ]; then
|
||||
echo "$TASCAM_BUS_ID:1.0" | sudo tee /sys/bus/usb/devices/$TASCAM_BUS_ID:1.0/driver/unbind > /dev/null
|
||||
fi
|
||||
if [ -e "/sys/bus/usb/devices/$TASCAM_BUS_ID:1.1/driver/unbind" ]; then
|
||||
echo "$TASCAM_BUS_ID:1.1" | sudo tee /sys/bus/usb/devices/$TASCAM_BUS_ID:1.1/driver/unbind > /dev/null
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! sudo insmod tascam_raw.ko; then
|
||||
echo "Error: Failed to insert kernel module."
|
||||
cleanup
|
||||
fi
|
||||
|
||||
echo " Waiting for device node..."
|
||||
for ((i=0; i<50; i++)); do
|
||||
if [ -e /dev/tascam_raw ]; then break; fi
|
||||
sleep 0.1
|
||||
done
|
||||
|
||||
if [ ! -e /dev/tascam_raw ]; then
|
||||
echo "Error: /dev/tascam_raw was not created."
|
||||
sudo dmesg | tail -n 5
|
||||
cleanup
|
||||
fi
|
||||
|
||||
sudo chmod 666 /dev/tascam_raw /dev/tascam_midi 2>/dev/null
|
||||
|
||||
echo "[4/6] Starting JACK ($RATE Hz / $BUFFER frames)..."
|
||||
jackd -R -d dummy -r $RATE -p $BUFFER >/dev/null 2>&1 &
|
||||
JACK_PID=$!
|
||||
sleep 2
|
||||
|
||||
if ! ps -p $JACK_PID > /dev/null; then
|
||||
echo "Error: JACK failed to start."
|
||||
cleanup
|
||||
fi
|
||||
|
||||
if command -v a2jmidid &> /dev/null; then
|
||||
echo "[5/6] Starting a2jmidid..."
|
||||
a2jmidid -e >/dev/null 2>&1 &
|
||||
A2J_PID=$!
|
||||
else
|
||||
echo "[5/6] a2jmidid not found (skipping MIDI bridge)."
|
||||
fi
|
||||
|
||||
echo "[6/6] Starting TASCAM Bridge Client..."
|
||||
./jack_tascam &
|
||||
CLIENT_PID=$!
|
||||
sleep 1
|
||||
|
||||
if ! ps -p $CLIENT_PID > /dev/null; then
|
||||
echo "Error: Client failed to start."
|
||||
cleanup
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo " SYSTEM LIVE!"
|
||||
echo " Rate: $RATE Hz | Buffer: $BUFFER"
|
||||
echo " Estimated Playback Latency: ${LATENCY} ms"
|
||||
echo " Connect your apps in JACK now."
|
||||
echo " Press Ctrl+C to stop and restore."
|
||||
echo "========================================="
|
||||
|
||||
wait $CLIENT_PID
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
#ifndef TASCAM_COMMON_H
|
||||
#define TASCAM_COMMON_H
|
||||
|
||||
#include <linux/ioctl.h>
|
||||
|
||||
#define TASCAM_IOC_MAGIC 'T'
|
||||
#define TASCAM_IOC_SET_RATE _IOW(TASCAM_IOC_MAGIC, 1, int)
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,636 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/usb.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/minmax.h>
|
||||
#include "tascam_common.h"
|
||||
|
||||
#define DRIVER_NAME "tascam_raw"
|
||||
#define TASCAM_VID 0x0644
|
||||
#define TASCAM_PID_144 0x800f
|
||||
#define TASCAM_PID_144MKII 0x8020
|
||||
|
||||
#define EP_AUDIO_OUT 0x02
|
||||
#define EP_FEEDBACK_IN 0x81
|
||||
#define EP_AUDIO_IN 0x86
|
||||
#define EP_MIDI_IN 0x83
|
||||
#define EP_MIDI_OUT 0x04
|
||||
|
||||
#define NUM_CHANNELS 4
|
||||
#define BYTES_PER_SAMPLE 3
|
||||
#define TASCAM_FRAME_SIZE (NUM_CHANNELS * BYTES_PER_SAMPLE)
|
||||
#define PACKETS_PER_URB 8
|
||||
#define NUM_PLAYBACK_URBS 2
|
||||
#define NUM_FEEDBACK_URBS 2
|
||||
#define NUM_CAPTURE_URBS 4
|
||||
#define CAPTURE_URB_SIZE 512
|
||||
#define RING_BUFFER_SIZE (128 * 1024)
|
||||
|
||||
#define NUM_MIDI_IN_URBS 4
|
||||
#define NUM_MIDI_OUT_URBS 4
|
||||
#define MIDI_PACKET_SIZE 9
|
||||
#define MIDI_BUF_SIZE 4096
|
||||
|
||||
#define FEEDBACK_ACCUMULATOR_SIZE 128
|
||||
#define FEEDBACK_SYNC_LOSS_THRESHOLD 41
|
||||
|
||||
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_raw {
|
||||
struct usb_device *udev;
|
||||
|
||||
struct urb *playback_urbs[NUM_PLAYBACK_URBS];
|
||||
struct urb *feedback_urbs[NUM_FEEDBACK_URBS];
|
||||
struct urb *capture_urbs[NUM_CAPTURE_URBS];
|
||||
|
||||
struct urb *midi_in_urbs[NUM_MIDI_IN_URBS];
|
||||
struct urb *midi_out_urbs[NUM_MIDI_OUT_URBS];
|
||||
|
||||
int major;
|
||||
struct cdev cdev;
|
||||
struct class *cls;
|
||||
|
||||
unsigned char *pb_ring_buf;
|
||||
unsigned int pb_head;
|
||||
unsigned int pb_tail;
|
||||
unsigned char *cap_ring_buf;
|
||||
unsigned int cap_head;
|
||||
unsigned int cap_tail;
|
||||
|
||||
unsigned char *midi_in_buf;
|
||||
unsigned int midi_in_head;
|
||||
unsigned int midi_in_tail;
|
||||
|
||||
unsigned char *midi_out_buf;
|
||||
unsigned int midi_out_head;
|
||||
unsigned int midi_out_tail;
|
||||
bool midi_out_busy[NUM_MIDI_OUT_URBS];
|
||||
|
||||
spinlock_t lock;
|
||||
wait_queue_head_t write_wait;
|
||||
wait_queue_head_t read_wait;
|
||||
|
||||
bool running;
|
||||
int current_rate;
|
||||
|
||||
unsigned int feedback_accumulator_pattern[FEEDBACK_ACCUMULATOR_SIZE];
|
||||
unsigned int feedback_pattern_out_idx;
|
||||
unsigned int feedback_pattern_in_idx;
|
||||
bool feedback_synced;
|
||||
unsigned int feedback_consecutive_errors;
|
||||
unsigned int feedback_urb_skip_count;
|
||||
struct us144mkii_frame_pattern_observer fpo;
|
||||
};
|
||||
|
||||
static struct tascam_raw *g_dev = NULL;
|
||||
|
||||
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]--;
|
||||
}
|
||||
}
|
||||
|
||||
static void try_send_midi(struct tascam_raw *dev) {
|
||||
int i;
|
||||
unsigned long flags;
|
||||
spin_lock_irqsave(&dev->lock, flags);
|
||||
|
||||
for (i = 0; i < NUM_MIDI_OUT_URBS; i++) {
|
||||
if (dev->midi_out_busy[i]) continue;
|
||||
|
||||
int available = (dev->midi_out_head - dev->midi_out_tail) & (MIDI_BUF_SIZE - 1);
|
||||
if (available >= MIDI_PACKET_SIZE) {
|
||||
int tail = dev->midi_out_tail;
|
||||
int j;
|
||||
for(j=0; j<MIDI_PACKET_SIZE; j++) {
|
||||
((u8*)dev->midi_out_urbs[i]->transfer_buffer)[j] = dev->midi_out_buf[(tail + j) & (MIDI_BUF_SIZE - 1)];
|
||||
}
|
||||
dev->midi_out_tail = (tail + MIDI_PACKET_SIZE) & (MIDI_BUF_SIZE - 1);
|
||||
dev->midi_out_busy[i] = true;
|
||||
|
||||
usb_submit_urb(dev->midi_out_urbs[i], GFP_ATOMIC);
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
}
|
||||
|
||||
static void midi_out_complete(struct urb *urb) {
|
||||
struct tascam_raw *dev = urb->context;
|
||||
int i;
|
||||
for(i=0; i<NUM_MIDI_OUT_URBS; i++) {
|
||||
if (dev->midi_out_urbs[i] == urb) {
|
||||
dev->midi_out_busy[i] = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dev->running) try_send_midi(dev);
|
||||
}
|
||||
|
||||
static void midi_in_complete(struct urb *urb) {
|
||||
struct tascam_raw *dev = urb->context;
|
||||
unsigned long flags;
|
||||
if (!dev->running) return;
|
||||
|
||||
if (urb->status == 0 && urb->actual_length > 0) {
|
||||
spin_lock_irqsave(&dev->lock, flags);
|
||||
int available = (MIDI_BUF_SIZE - 1) - ((dev->midi_in_head - dev->midi_in_tail) & (MIDI_BUF_SIZE - 1));
|
||||
if (available >= urb->actual_length) {
|
||||
int i;
|
||||
u8 *src = urb->transfer_buffer;
|
||||
for(i=0; i<urb->actual_length; i++) {
|
||||
dev->midi_in_buf[dev->midi_in_head] = src[i];
|
||||
dev->midi_in_head = (dev->midi_in_head + 1) & (MIDI_BUF_SIZE - 1);
|
||||
}
|
||||
}
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
wake_up_interruptible(&dev->read_wait);
|
||||
}
|
||||
usb_submit_urb(urb, GFP_ATOMIC);
|
||||
}
|
||||
|
||||
static void capture_complete(struct urb *urb) {
|
||||
struct tascam_raw *dev = urb->context;
|
||||
unsigned long flags;
|
||||
if (!dev->running) return;
|
||||
|
||||
if (urb->status == 0 && urb->actual_length > 0) {
|
||||
spin_lock_irqsave(&dev->lock, flags);
|
||||
int available = (RING_BUFFER_SIZE - 1) - ((dev->cap_head - dev->cap_tail) & (RING_BUFFER_SIZE - 1));
|
||||
if (available >= urb->actual_length) {
|
||||
int chunk1 = min_t(int, urb->actual_length, RING_BUFFER_SIZE - dev->cap_head);
|
||||
memcpy(dev->cap_ring_buf + dev->cap_head, urb->transfer_buffer, chunk1);
|
||||
if (chunk1 < urb->actual_length) {
|
||||
memcpy(dev->cap_ring_buf, urb->transfer_buffer + chunk1, urb->actual_length - chunk1);
|
||||
}
|
||||
dev->cap_head = (dev->cap_head + urb->actual_length) & (RING_BUFFER_SIZE - 1);
|
||||
}
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
wake_up_interruptible(&dev->read_wait);
|
||||
}
|
||||
usb_submit_urb(urb, GFP_ATOMIC);
|
||||
}
|
||||
|
||||
static void feedback_complete(struct urb *urb) {
|
||||
struct tascam_raw *dev = urb->context;
|
||||
int p;
|
||||
if (!dev->running) return;
|
||||
spin_lock(&dev->lock);
|
||||
if (dev->feedback_urb_skip_count > 0) {
|
||||
dev->feedback_urb_skip_count--;
|
||||
spin_unlock(&dev->lock);
|
||||
goto resubmit;
|
||||
}
|
||||
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 - dev->fpo.base_feedback_value + dev->fpo.feedback_offset;
|
||||
int pattern_idx = (delta < 0) ? 0 : (delta >= 5 ? 4 : delta);
|
||||
pattern = dev->fpo.full_frame_patterns[pattern_idx];
|
||||
dev->feedback_consecutive_errors = 0;
|
||||
int i;
|
||||
for (i = 0; i < 8; i++) {
|
||||
unsigned int in_idx = (dev->feedback_pattern_in_idx + i) % FEEDBACK_ACCUMULATOR_SIZE;
|
||||
dev->feedback_accumulator_pattern[in_idx] = pattern[i];
|
||||
}
|
||||
} else {
|
||||
unsigned int nominal_frames = dev->current_rate / 8000;
|
||||
int i;
|
||||
if (dev->feedback_synced) {
|
||||
dev->feedback_consecutive_errors++;
|
||||
if (dev->feedback_consecutive_errors > FEEDBACK_SYNC_LOSS_THRESHOLD) dev->feedback_synced = false;
|
||||
}
|
||||
for (i = 0; i < 8; i++) {
|
||||
unsigned int in_idx = (dev->feedback_pattern_in_idx + i) % FEEDBACK_ACCUMULATOR_SIZE;
|
||||
dev->feedback_accumulator_pattern[in_idx] = nominal_frames;
|
||||
}
|
||||
}
|
||||
dev->feedback_pattern_in_idx = (dev->feedback_pattern_in_idx + 8) % FEEDBACK_ACCUMULATOR_SIZE;
|
||||
}
|
||||
if (!dev->feedback_synced) {
|
||||
unsigned int out_idx = dev->feedback_pattern_out_idx;
|
||||
unsigned int new_in_idx = dev->feedback_pattern_in_idx;
|
||||
bool is_ahead = (new_in_idx - out_idx + FEEDBACK_ACCUMULATOR_SIZE) % FEEDBACK_ACCUMULATOR_SIZE < (FEEDBACK_ACCUMULATOR_SIZE / 2);
|
||||
if (is_ahead) dev->feedback_synced = true;
|
||||
}
|
||||
spin_unlock(&dev->lock);
|
||||
resubmit:
|
||||
usb_submit_urb(urb, GFP_ATOMIC);
|
||||
}
|
||||
|
||||
static void playback_complete(struct urb *urb) {
|
||||
struct tascam_raw *dev = urb->context;
|
||||
unsigned long flags;
|
||||
int i, total_bytes_for_urb = 0;
|
||||
if (!dev->running) return;
|
||||
spin_lock_irqsave(&dev->lock, flags);
|
||||
for (i = 0; i < urb->number_of_packets; i++) {
|
||||
unsigned int frames_for_packet;
|
||||
if (dev->feedback_synced) {
|
||||
frames_for_packet = dev->feedback_accumulator_pattern[dev->feedback_pattern_out_idx];
|
||||
dev->feedback_pattern_out_idx = (dev->feedback_pattern_out_idx + 1) % FEEDBACK_ACCUMULATOR_SIZE;
|
||||
} else {
|
||||
frames_for_packet = dev->current_rate / 8000;
|
||||
}
|
||||
size_t bytes_for_packet = frames_for_packet * TASCAM_FRAME_SIZE;
|
||||
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;
|
||||
uint8_t *dst_buf = urb->transfer_buffer;
|
||||
int available = (dev->pb_head - dev->pb_tail) & (RING_BUFFER_SIZE - 1);
|
||||
if (available >= total_bytes_for_urb) {
|
||||
int chunk1 = min_t(int, total_bytes_for_urb, RING_BUFFER_SIZE - dev->pb_tail);
|
||||
memcpy(dst_buf, dev->pb_ring_buf + dev->pb_tail, chunk1);
|
||||
if (chunk1 < total_bytes_for_urb) memcpy(dst_buf + chunk1, dev->pb_ring_buf, total_bytes_for_urb - chunk1);
|
||||
dev->pb_tail = (dev->pb_tail + total_bytes_for_urb) & (RING_BUFFER_SIZE - 1);
|
||||
} else {
|
||||
memset(dst_buf, 0, total_bytes_for_urb);
|
||||
}
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
wake_up_interruptible(&dev->write_wait);
|
||||
usb_submit_urb(urb, GFP_ATOMIC);
|
||||
}
|
||||
|
||||
struct rate_config { int rate; u8 payload[3]; u16 reg_value; };
|
||||
static const struct rate_config rates[] = {
|
||||
{ 44100, {0x44, 0xac, 0x00}, 0x1000 },
|
||||
{ 48000, {0x80, 0xbb, 0x00}, 0x1002 },
|
||||
{ 88200, {0x88, 0x58, 0x01}, 0x1008 },
|
||||
{ 96000, {0x00, 0x77, 0x01}, 0x100a },
|
||||
};
|
||||
|
||||
static int tascam_start_stream(struct tascam_raw *dev, int rate) {
|
||||
int i;
|
||||
const struct rate_config *cfg = NULL;
|
||||
for (i = 0; i < 4; i++) { if (rates[i].rate == rate) { cfg = &rates[i]; break; } }
|
||||
if (!cfg) return -EINVAL;
|
||||
|
||||
dev->running = false;
|
||||
for (i=0; i<NUM_PLAYBACK_URBS; i++) usb_kill_urb(dev->playback_urbs[i]);
|
||||
for (i=0; i<NUM_FEEDBACK_URBS; i++) usb_kill_urb(dev->feedback_urbs[i]);
|
||||
for (i=0; i<NUM_CAPTURE_URBS; i++) usb_kill_urb(dev->capture_urbs[i]);
|
||||
for (i=0; i<NUM_MIDI_IN_URBS; i++) usb_kill_urb(dev->midi_in_urbs[i]);
|
||||
for (i=0; i<NUM_MIDI_OUT_URBS; i++) usb_kill_urb(dev->midi_out_urbs[i]);
|
||||
|
||||
dev->current_rate = rate;
|
||||
dev->pb_head = dev->pb_tail = 0;
|
||||
dev->cap_head = dev->cap_tail = 0;
|
||||
dev->midi_in_head = dev->midi_in_tail = 0;
|
||||
dev->midi_out_head = dev->midi_out_tail = 0;
|
||||
for(i=0; i<NUM_MIDI_OUT_URBS; i++) dev->midi_out_busy[i] = false;
|
||||
|
||||
dev->fpo.sample_rate_khz = rate / 1000;
|
||||
dev->fpo.base_feedback_value = dev->fpo.sample_rate_khz;
|
||||
dev->fpo.feedback_offset = 2;
|
||||
dev->fpo.current_index = 0;
|
||||
dev->fpo.previous_index = 0;
|
||||
dev->fpo.sync_locked = false;
|
||||
unsigned int initial_value = dev->fpo.sample_rate_khz / 8;
|
||||
for (i = 0; i < 5; i++) {
|
||||
int target_sum = dev->fpo.sample_rate_khz - dev->fpo.feedback_offset + i;
|
||||
fpo_init_pattern(8, dev->fpo.full_frame_patterns[i], initial_value, target_sum);
|
||||
}
|
||||
dev->feedback_pattern_in_idx = 0;
|
||||
dev->feedback_pattern_out_idx = 0;
|
||||
dev->feedback_synced = false;
|
||||
dev->feedback_consecutive_errors = 0;
|
||||
dev->feedback_urb_skip_count = NUM_FEEDBACK_URBS;
|
||||
unsigned int nominal = rate / 8000;
|
||||
for (i = 0; i < FEEDBACK_ACCUMULATOR_SIZE; i++) dev->feedback_accumulator_pattern[i] = nominal;
|
||||
|
||||
usb_set_interface(dev->udev, 0, 1);
|
||||
usb_set_interface(dev->udev, 1, 1);
|
||||
|
||||
u8 *buf = kmalloc(64, GFP_KERNEL);
|
||||
usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), 0x49, 0xc0, 0x0000, 0x0000, buf, 1, 1000);
|
||||
usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), 0x49, 0x40, 0x0010, 0x0000, NULL, 0, 1000);
|
||||
|
||||
u8 *rate_payload = kmemdup(cfg->payload, 3, GFP_KERNEL);
|
||||
usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), 0x01, 0x22, 0x0100, EP_AUDIO_OUT, rate_payload, 3, 1000);
|
||||
usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), 0x01, 0x22, 0x0100, EP_FEEDBACK_IN, rate_payload, 3, 1000);
|
||||
usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), 0x01, 0x22, 0x0100, EP_AUDIO_IN, rate_payload, 3, 1000);
|
||||
kfree(rate_payload);
|
||||
|
||||
usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), 0x41, 0x40, 0x0d04, 0x0101, NULL, 0, 1000);
|
||||
usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), 0x41, 0x40, 0x0e00, 0x0101, NULL, 0, 1000);
|
||||
usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), 0x41, 0x40, 0x0f00, 0x0101, NULL, 0, 1000);
|
||||
usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), 0x41, 0x40, cfg->reg_value, 0x0101, NULL, 0, 1000);
|
||||
usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), 0x41, 0x40, 0x110b, 0x0101, NULL, 0, 1000);
|
||||
usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), 0x49, 0x40, 0x0030, 0x0000, NULL, 0, 1000);
|
||||
kfree(buf);
|
||||
|
||||
dev->running = true;
|
||||
|
||||
for (i=0; i<NUM_FEEDBACK_URBS; i++) {
|
||||
dev->feedback_urbs[i]->dev = dev->udev;
|
||||
dev->feedback_urbs[i]->number_of_packets = 1;
|
||||
dev->feedback_urbs[i]->transfer_buffer_length = 3;
|
||||
dev->feedback_urbs[i]->iso_frame_desc[0].offset = 0;
|
||||
dev->feedback_urbs[i]->iso_frame_desc[0].length = 3;
|
||||
usb_submit_urb(dev->feedback_urbs[i], GFP_KERNEL);
|
||||
}
|
||||
for (i=0; i<NUM_CAPTURE_URBS; i++) {
|
||||
dev->capture_urbs[i]->dev = dev->udev;
|
||||
usb_submit_urb(dev->capture_urbs[i], GFP_KERNEL);
|
||||
}
|
||||
for (i=0; i<NUM_MIDI_IN_URBS; i++) {
|
||||
dev->midi_in_urbs[i]->dev = dev->udev;
|
||||
usb_submit_urb(dev->midi_in_urbs[i], GFP_KERNEL);
|
||||
}
|
||||
for (i=0; i<NUM_PLAYBACK_URBS; i++) {
|
||||
dev->playback_urbs[i]->dev = dev->udev;
|
||||
int j, total = 0;
|
||||
for(j=0; j<PACKETS_PER_URB; j++) {
|
||||
int len = nominal * TASCAM_FRAME_SIZE;
|
||||
dev->playback_urbs[i]->iso_frame_desc[j].offset = total;
|
||||
dev->playback_urbs[i]->iso_frame_desc[j].length = len;
|
||||
total += len;
|
||||
}
|
||||
dev->playback_urbs[i]->transfer_buffer_length = total;
|
||||
memset(dev->playback_urbs[i]->transfer_buffer, 0, total);
|
||||
usb_submit_urb(dev->playback_urbs[i], GFP_KERNEL);
|
||||
}
|
||||
|
||||
printk(KERN_INFO "Tascam Raw: Started stream at %d Hz\n", rate);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dev_open(struct inode *inode, struct file *file) {
|
||||
if (!g_dev) return -ENODEV;
|
||||
file->private_data = g_dev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
|
||||
struct tascam_raw *dev = file->private_data;
|
||||
int new_rate;
|
||||
if (iminor(file_inode(file)) != 0) return -ENOTTY;
|
||||
switch (cmd) {
|
||||
case TASCAM_IOC_SET_RATE:
|
||||
if (copy_from_user(&new_rate, (int __user *)arg, sizeof(int))) return -EFAULT;
|
||||
return tascam_start_stream(dev, new_rate);
|
||||
default: return -ENOTTY;
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t dev_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) {
|
||||
struct tascam_raw *dev = file->private_data;
|
||||
int written = 0;
|
||||
unsigned long flags;
|
||||
if (!dev->running) return -EIO;
|
||||
|
||||
int minor = iminor(file_inode(file));
|
||||
|
||||
if (minor == 1) {
|
||||
spin_lock_irqsave(&dev->lock, flags);
|
||||
int available = (MIDI_BUF_SIZE - 1) - ((dev->midi_out_head - dev->midi_out_tail) & (MIDI_BUF_SIZE - 1));
|
||||
int to_copy = min_t(int, count, available);
|
||||
int chunk1 = min_t(int, to_copy, MIDI_BUF_SIZE - dev->midi_out_head);
|
||||
|
||||
if (copy_from_user(dev->midi_out_buf + dev->midi_out_head, user_buf, chunk1)) {
|
||||
spin_unlock_irqrestore(&dev->lock, flags); return -EFAULT;
|
||||
}
|
||||
if (chunk1 < to_copy) {
|
||||
if (copy_from_user(dev->midi_out_buf, user_buf + chunk1, to_copy - chunk1)) {
|
||||
spin_unlock_irqrestore(&dev->lock, flags); return -EFAULT;
|
||||
}
|
||||
}
|
||||
dev->midi_out_head = (dev->midi_out_head + to_copy) & (MIDI_BUF_SIZE - 1);
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
|
||||
try_send_midi(dev);
|
||||
return to_copy;
|
||||
}
|
||||
|
||||
while (written < count) {
|
||||
spin_lock_irqsave(&dev->lock, flags);
|
||||
int available = (RING_BUFFER_SIZE - 1) - ((dev->pb_head - dev->pb_tail) & (RING_BUFFER_SIZE - 1));
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
|
||||
if (available == 0) {
|
||||
if (file->f_flags & O_NONBLOCK) return written > 0 ? written : -EAGAIN;
|
||||
if (wait_event_interruptible(dev->write_wait,
|
||||
((RING_BUFFER_SIZE - 1) - ((dev->pb_head - dev->pb_tail) & (RING_BUFFER_SIZE - 1))) > 0))
|
||||
return -ERESTARTSYS;
|
||||
continue;
|
||||
}
|
||||
|
||||
int to_copy = min_t(int, count - written, available);
|
||||
int chunk1 = min_t(int, to_copy, RING_BUFFER_SIZE - dev->pb_head);
|
||||
|
||||
if (copy_from_user(dev->pb_ring_buf + dev->pb_head, user_buf + written, chunk1)) return -EFAULT;
|
||||
if (chunk1 < to_copy) {
|
||||
if (copy_from_user(dev->pb_ring_buf, user_buf + written + chunk1, to_copy - chunk1)) return -EFAULT;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&dev->lock, flags);
|
||||
dev->pb_head = (dev->pb_head + to_copy) & (RING_BUFFER_SIZE - 1);
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
written += to_copy;
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
static ssize_t dev_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) {
|
||||
struct tascam_raw *dev = file->private_data;
|
||||
int read = 0;
|
||||
unsigned long flags;
|
||||
if (!dev->running) return -EIO;
|
||||
|
||||
int minor = iminor(file_inode(file));
|
||||
|
||||
if (minor == 1) {
|
||||
spin_lock_irqsave(&dev->lock, flags);
|
||||
int available = (dev->midi_in_head - dev->midi_in_tail) & (MIDI_BUF_SIZE - 1);
|
||||
if (available == 0) {
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
if (file->f_flags & O_NONBLOCK) return -EAGAIN;
|
||||
return 0;
|
||||
}
|
||||
int to_copy = min_t(int, count, available);
|
||||
int chunk1 = min_t(int, to_copy, MIDI_BUF_SIZE - dev->midi_in_tail);
|
||||
|
||||
if (copy_to_user(user_buf, dev->midi_in_buf + dev->midi_in_tail, chunk1)) {
|
||||
spin_unlock_irqrestore(&dev->lock, flags); return -EFAULT;
|
||||
}
|
||||
if (chunk1 < to_copy) {
|
||||
if (copy_to_user(user_buf + chunk1, dev->midi_in_buf, to_copy - chunk1)) {
|
||||
spin_unlock_irqrestore(&dev->lock, flags); return -EFAULT;
|
||||
}
|
||||
}
|
||||
dev->midi_in_tail = (dev->midi_in_tail + to_copy) & (MIDI_BUF_SIZE - 1);
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
return to_copy;
|
||||
}
|
||||
|
||||
while (read < count) {
|
||||
spin_lock_irqsave(&dev->lock, flags);
|
||||
int available = (dev->cap_head - dev->cap_tail) & (RING_BUFFER_SIZE - 1);
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
|
||||
if (available == 0) {
|
||||
if (file->f_flags & O_NONBLOCK) return read > 0 ? read : -EAGAIN;
|
||||
if (wait_event_interruptible(dev->read_wait,
|
||||
((dev->cap_head - dev->cap_tail) & (RING_BUFFER_SIZE - 1)) > 0))
|
||||
return -ERESTARTSYS;
|
||||
continue;
|
||||
}
|
||||
|
||||
int to_copy = min_t(int, count - read, available);
|
||||
int chunk1 = min_t(int, to_copy, RING_BUFFER_SIZE - dev->cap_tail);
|
||||
|
||||
if (copy_to_user(user_buf + read, dev->cap_ring_buf + dev->cap_tail, chunk1)) return -EFAULT;
|
||||
if (chunk1 < to_copy) {
|
||||
if (copy_to_user(user_buf + read + chunk1, dev->cap_ring_buf, to_copy - chunk1)) return -EFAULT;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&dev->lock, flags);
|
||||
dev->cap_tail = (dev->cap_tail + to_copy) & (RING_BUFFER_SIZE - 1);
|
||||
spin_unlock_irqrestore(&dev->lock, flags);
|
||||
read += to_copy;
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
static const struct file_operations fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = dev_open,
|
||||
.write = dev_write,
|
||||
.read = dev_read,
|
||||
.unlocked_ioctl = dev_ioctl,
|
||||
};
|
||||
|
||||
static int tascam_probe(struct usb_interface *intf, const struct usb_device_id *id) {
|
||||
struct tascam_raw *dev;
|
||||
int ret, i;
|
||||
|
||||
if (intf->cur_altsetting->desc.bInterfaceNumber != 0) return -ENODEV;
|
||||
|
||||
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
|
||||
if (!dev) return -ENOMEM;
|
||||
|
||||
dev->udev = interface_to_usbdev(intf);
|
||||
dev->pb_ring_buf = kzalloc(RING_BUFFER_SIZE, GFP_KERNEL);
|
||||
dev->cap_ring_buf = kzalloc(RING_BUFFER_SIZE, GFP_KERNEL);
|
||||
dev->midi_in_buf = kzalloc(MIDI_BUF_SIZE, GFP_KERNEL);
|
||||
dev->midi_out_buf = kzalloc(MIDI_BUF_SIZE, GFP_KERNEL);
|
||||
spin_lock_init(&dev->lock);
|
||||
init_waitqueue_head(&dev->write_wait);
|
||||
init_waitqueue_head(&dev->read_wait);
|
||||
|
||||
g_dev = dev;
|
||||
|
||||
dev->major = register_chrdev(0, DRIVER_NAME, &fops);
|
||||
if (dev->major < 0) goto err_free;
|
||||
|
||||
dev->cls = class_create(DRIVER_NAME);
|
||||
device_create(dev->cls, NULL, MKDEV(dev->major, 0), NULL, DRIVER_NAME);
|
||||
device_create(dev->cls, NULL, MKDEV(dev->major, 1), NULL, "tascam_midi");
|
||||
|
||||
for (i=0; i<NUM_FEEDBACK_URBS; i++) {
|
||||
dev->feedback_urbs[i] = usb_alloc_urb(1, GFP_KERNEL);
|
||||
usb_fill_int_urb(dev->feedback_urbs[i], dev->udev, usb_rcvintpipe(dev->udev, EP_FEEDBACK_IN),
|
||||
kzalloc(4, GFP_KERNEL), 4, feedback_complete, dev, 1);
|
||||
}
|
||||
for (i=0; i<NUM_PLAYBACK_URBS; i++) {
|
||||
dev->playback_urbs[i] = usb_alloc_urb(PACKETS_PER_URB, GFP_KERNEL);
|
||||
dev->playback_urbs[i]->dev = dev->udev;
|
||||
dev->playback_urbs[i]->context = dev;
|
||||
dev->playback_urbs[i]->pipe = usb_sndisocpipe(dev->udev, EP_AUDIO_OUT);
|
||||
dev->playback_urbs[i]->transfer_flags = URB_ISO_ASAP;
|
||||
dev->playback_urbs[i]->interval = 1;
|
||||
dev->playback_urbs[i]->complete = playback_complete;
|
||||
dev->playback_urbs[i]->number_of_packets = PACKETS_PER_URB;
|
||||
dev->playback_urbs[i]->transfer_buffer = kzalloc(PACKETS_PER_URB * 144, GFP_KERNEL);
|
||||
dev->playback_urbs[i]->transfer_buffer_length = PACKETS_PER_URB * 144;
|
||||
}
|
||||
for (i=0; i<NUM_CAPTURE_URBS; i++) {
|
||||
dev->capture_urbs[i] = usb_alloc_urb(0, GFP_KERNEL);
|
||||
dev->capture_urbs[i]->transfer_buffer = kzalloc(CAPTURE_URB_SIZE, GFP_KERNEL);
|
||||
usb_fill_bulk_urb(dev->capture_urbs[i], dev->udev, usb_rcvbulkpipe(dev->udev, EP_AUDIO_IN),
|
||||
dev->capture_urbs[i]->transfer_buffer, CAPTURE_URB_SIZE, capture_complete, dev);
|
||||
}
|
||||
for (i=0; i<NUM_MIDI_IN_URBS; i++) {
|
||||
dev->midi_in_urbs[i] = usb_alloc_urb(0, GFP_KERNEL);
|
||||
dev->midi_in_urbs[i]->transfer_buffer = kzalloc(MIDI_PACKET_SIZE, GFP_KERNEL);
|
||||
usb_fill_bulk_urb(dev->midi_in_urbs[i], dev->udev, usb_rcvbulkpipe(dev->udev, EP_MIDI_IN),
|
||||
dev->midi_in_urbs[i]->transfer_buffer, MIDI_PACKET_SIZE, midi_in_complete, dev);
|
||||
}
|
||||
for (i=0; i<NUM_MIDI_OUT_URBS; i++) {
|
||||
dev->midi_out_urbs[i] = usb_alloc_urb(0, GFP_KERNEL);
|
||||
dev->midi_out_urbs[i]->transfer_buffer = kzalloc(MIDI_PACKET_SIZE, GFP_KERNEL);
|
||||
usb_fill_bulk_urb(dev->midi_out_urbs[i], dev->udev, usb_sndbulkpipe(dev->udev, EP_MIDI_OUT),
|
||||
dev->midi_out_urbs[i]->transfer_buffer, MIDI_PACKET_SIZE, midi_out_complete, dev);
|
||||
}
|
||||
|
||||
printk(KERN_INFO "Tascam Raw: Probed. Waiting for IOCTL.\n");
|
||||
return 0;
|
||||
|
||||
err_free:
|
||||
kfree(dev->pb_ring_buf);
|
||||
kfree(dev->cap_ring_buf);
|
||||
kfree(dev->midi_in_buf);
|
||||
kfree(dev->midi_out_buf);
|
||||
kfree(dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void tascam_disconnect(struct usb_interface *intf) {
|
||||
struct tascam_raw *dev = g_dev;
|
||||
int i;
|
||||
if (!dev) return;
|
||||
|
||||
dev->running = false;
|
||||
for (i=0; i<NUM_PLAYBACK_URBS; i++) { usb_kill_urb(dev->playback_urbs[i]); usb_free_urb(dev->playback_urbs[i]); }
|
||||
for (i=0; i<NUM_FEEDBACK_URBS; i++) { usb_kill_urb(dev->feedback_urbs[i]); usb_free_urb(dev->feedback_urbs[i]); }
|
||||
for (i=0; i<NUM_CAPTURE_URBS; i++) { usb_kill_urb(dev->capture_urbs[i]); usb_free_urb(dev->capture_urbs[i]); }
|
||||
for (i=0; i<NUM_MIDI_IN_URBS; i++) { usb_kill_urb(dev->midi_in_urbs[i]); usb_free_urb(dev->midi_in_urbs[i]); }
|
||||
for (i=0; i<NUM_MIDI_OUT_URBS; i++) { usb_kill_urb(dev->midi_out_urbs[i]); usb_free_urb(dev->midi_out_urbs[i]); }
|
||||
|
||||
device_destroy(dev->cls, MKDEV(dev->major, 0));
|
||||
device_destroy(dev->cls, MKDEV(dev->major, 1));
|
||||
class_destroy(dev->cls);
|
||||
unregister_chrdev(dev->major, DRIVER_NAME);
|
||||
|
||||
kfree(dev->pb_ring_buf);
|
||||
kfree(dev->cap_ring_buf);
|
||||
kfree(dev->midi_in_buf);
|
||||
kfree(dev->midi_out_buf);
|
||||
kfree(dev);
|
||||
g_dev = NULL;
|
||||
}
|
||||
|
||||
static struct usb_device_id tascam_table[] = {
|
||||
{ USB_DEVICE(TASCAM_VID, TASCAM_PID_144) },
|
||||
{ USB_DEVICE(TASCAM_VID, TASCAM_PID_144MKII) },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(usb, tascam_table);
|
||||
static struct usb_driver tascam_driver = { .name = DRIVER_NAME, .probe = tascam_probe, .disconnect = tascam_disconnect, .id_table = tascam_table };
|
||||
module_usb_driver(tascam_driver);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("TASCAM US-144/US-144MKII Raw Driver (Audio + MIDI)");
|
||||
Loading…
Reference in New Issue