Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
snd-firewire-digi00x-y := amdtp-dot.o digi00x-stream.o digi00x-proc.o \
digi00x-pcm.o digi00x-hwdep.o \
digi00x-transaction.o digi00x-midi.o digi00x.o
obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += snd-firewire-digi00x.o
@@ -0,0 +1,412 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* amdtp-dot.c - a part of driver for Digidesign Digi 002/003 family
*
* Copyright (c) 2014-2015 Takashi Sakamoto
* Copyright (C) 2012 Robin Gareus <robin@gareus.org>
* Copyright (C) 2012 Damien Zammit <damien@zamaudio.com>
*/
#include <sound/pcm.h>
#include "digi00x.h"
#define CIP_FMT_AM 0x10
/* 'Clock-based rate control mode' is just supported. */
#define AMDTP_FDF_AM824 0x00
/*
* Nominally 3125 bytes/second, but the MIDI port's clock might be
* 1% too slow, and the bus clock 100 ppm too fast.
*/
#define MIDI_BYTES_PER_SECOND 3093
/*
* Several devices look only at the first eight data blocks.
* In any case, this is more than enough for the MIDI data rate.
*/
#define MAX_MIDI_RX_BLOCKS 8
/* 3 = MAX(DOT_MIDI_IN_PORTS, DOT_MIDI_OUT_PORTS) + 1. */
#define MAX_MIDI_PORTS 3
/*
* The double-oh-three algorithm was discovered by Robin Gareus and Damien
* Zammit in 2012, with reverse-engineering for Digi 003 Rack.
*/
struct dot_state {
u8 carry;
u8 idx;
unsigned int off;
};
struct amdtp_dot {
unsigned int pcm_channels;
struct dot_state state;
struct snd_rawmidi_substream *midi[MAX_MIDI_PORTS];
int midi_fifo_used[MAX_MIDI_PORTS];
int midi_fifo_limit;
};
/*
* double-oh-three look up table
*
* @param idx index byte (audio-sample data) 0x00..0xff
* @param off channel offset shift
* @return salt to XOR with given data
*/
#define BYTE_PER_SAMPLE (4)
#define MAGIC_DOT_BYTE (2)
#define MAGIC_BYTE_OFF(x) (((x) * BYTE_PER_SAMPLE) + MAGIC_DOT_BYTE)
static u8 dot_scrt(const u8 idx, const unsigned int off)
{
/*
* the length of the added pattern only depends on the lower nibble
* of the last non-zero data
*/
static const u8 len[16] = {0, 1, 3, 5, 7, 9, 11, 13, 14,
12, 10, 8, 6, 4, 2, 0};
/*
* the lower nibble of the salt. Interleaved sequence.
* this is walked backwards according to len[]
*/
static const u8 nib[15] = {0x8, 0x7, 0x9, 0x6, 0xa, 0x5, 0xb, 0x4,
0xc, 0x3, 0xd, 0x2, 0xe, 0x1, 0xf};
/* circular list for the salt's hi nibble. */
static const u8 hir[15] = {0x0, 0x6, 0xf, 0x8, 0x7, 0x5, 0x3, 0x4,
0xc, 0xd, 0xe, 0x1, 0x2, 0xb, 0xa};
/*
* start offset for upper nibble mapping.
* note: 9 is /special/. In the case where the high nibble == 0x9,
* hir[] is not used and - coincidentally - the salt's hi nibble is
* 0x09 regardless of the offset.
*/
static const u8 hio[16] = {0, 11, 12, 6, 7, 5, 1, 4,
3, 0x00, 14, 13, 8, 9, 10, 2};
const u8 ln = idx & 0xf;
const u8 hn = (idx >> 4) & 0xf;
const u8 hr = (hn == 0x9) ? 0x9 : hir[(hio[hn] + off) % 15];
if (len[ln] < off)
return 0x00;
return ((nib[14 + off - len[ln]]) | (hr << 4));
}
static void dot_encode_step(struct dot_state *state, __be32 *const buffer)
{
u8 * const data = (u8 *) buffer;
if (data[MAGIC_DOT_BYTE] != 0x00) {
state->off = 0;
state->idx = data[MAGIC_DOT_BYTE] ^ state->carry;
}
data[MAGIC_DOT_BYTE] ^= state->carry;
state->carry = dot_scrt(state->idx, ++(state->off));
}
int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate,
unsigned int pcm_channels)
{
struct amdtp_dot *p = s->protocol;
int err;
if (amdtp_stream_running(s))
return -EBUSY;
/*
* A first data channel is for MIDI messages, the rest is Multi Bit
* Linear Audio data channel.
*/
err = amdtp_stream_set_parameters(s, rate, pcm_channels + 1, 1);
if (err < 0)
return err;
s->ctx_data.rx.fdf = AMDTP_FDF_AM824 | s->sfc;
p->pcm_channels = pcm_channels;
/*
* We do not know the actual MIDI FIFO size of most devices. Just
* assume two bytes, i.e., one byte can be received over the bus while
* the previous one is transmitted over MIDI.
* (The value here is adjusted for midi_ratelimit_per_packet().)
*/
p->midi_fifo_limit = rate - MIDI_BYTES_PER_SECOND * s->syt_interval + 1;
return 0;
}
static void write_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm,
__be32 *buffer, unsigned int frames,
unsigned int pcm_frames)
{
struct amdtp_dot *p = s->protocol;
unsigned int channels = p->pcm_channels;
struct snd_pcm_runtime *runtime = pcm->runtime;
unsigned int pcm_buffer_pointer;
int remaining_frames;
const u32 *src;
int i, c;
pcm_buffer_pointer = s->pcm_buffer_pointer + pcm_frames;
pcm_buffer_pointer %= runtime->buffer_size;
src = (void *)runtime->dma_area +
frames_to_bytes(runtime, pcm_buffer_pointer);
remaining_frames = runtime->buffer_size - pcm_buffer_pointer;
buffer++;
for (i = 0; i < frames; ++i) {
for (c = 0; c < channels; ++c) {
buffer[c] = cpu_to_be32((*src >> 8) | 0x40000000);
dot_encode_step(&p->state, &buffer[c]);
src++;
}
buffer += s->data_block_quadlets;
if (--remaining_frames == 0)
src = (void *)runtime->dma_area;
}
}
static void read_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm,
__be32 *buffer, unsigned int frames,
unsigned int pcm_frames)
{
struct amdtp_dot *p = s->protocol;
unsigned int channels = p->pcm_channels;
struct snd_pcm_runtime *runtime = pcm->runtime;
unsigned int pcm_buffer_pointer;
int remaining_frames;
u32 *dst;
int i, c;
pcm_buffer_pointer = s->pcm_buffer_pointer + pcm_frames;
pcm_buffer_pointer %= runtime->buffer_size;
dst = (void *)runtime->dma_area +
frames_to_bytes(runtime, pcm_buffer_pointer);
remaining_frames = runtime->buffer_size - pcm_buffer_pointer;
buffer++;
for (i = 0; i < frames; ++i) {
for (c = 0; c < channels; ++c) {
*dst = be32_to_cpu(buffer[c]) << 8;
dst++;
}
buffer += s->data_block_quadlets;
if (--remaining_frames == 0)
dst = (void *)runtime->dma_area;
}
}
static void write_pcm_silence(struct amdtp_stream *s, __be32 *buffer,
unsigned int data_blocks)
{
struct amdtp_dot *p = s->protocol;
unsigned int channels, i, c;
channels = p->pcm_channels;
buffer++;
for (i = 0; i < data_blocks; ++i) {
for (c = 0; c < channels; ++c)
buffer[c] = cpu_to_be32(0x40000000);
buffer += s->data_block_quadlets;
}
}
static bool midi_ratelimit_per_packet(struct amdtp_stream *s, unsigned int port)
{
struct amdtp_dot *p = s->protocol;
int used;
used = p->midi_fifo_used[port];
if (used == 0)
return true;
used -= MIDI_BYTES_PER_SECOND * s->syt_interval;
used = max(used, 0);
p->midi_fifo_used[port] = used;
return used < p->midi_fifo_limit;
}
static inline void midi_use_bytes(struct amdtp_stream *s,
unsigned int port, unsigned int count)
{
struct amdtp_dot *p = s->protocol;
p->midi_fifo_used[port] += amdtp_rate_table[s->sfc] * count;
}
static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer,
unsigned int data_blocks, unsigned int data_block_counter)
{
struct amdtp_dot *p = s->protocol;
unsigned int f, port;
int len;
u8 *b;
for (f = 0; f < data_blocks; f++) {
port = (data_block_counter + f) % 8;
b = (u8 *)&buffer[0];
len = 0;
if (port < MAX_MIDI_PORTS &&
midi_ratelimit_per_packet(s, port) &&
p->midi[port] != NULL)
len = snd_rawmidi_transmit(p->midi[port], b + 1, 2);
if (len > 0) {
/*
* Upper 4 bits of LSB represent port number.
* - 0000b: physical MIDI port 1.
* - 0010b: physical MIDI port 2.
* - 1110b: console MIDI port.
*/
if (port == 2)
b[3] = 0xe0;
else if (port == 1)
b[3] = 0x20;
else
b[3] = 0x00;
b[3] |= len;
midi_use_bytes(s, port, len);
} else {
b[1] = 0;
b[2] = 0;
b[3] = 0;
}
b[0] = 0x80;
buffer += s->data_block_quadlets;
}
}
static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer,
unsigned int data_blocks)
{
struct amdtp_dot *p = s->protocol;
unsigned int f, port, len;
u8 *b;
for (f = 0; f < data_blocks; f++) {
b = (u8 *)&buffer[0];
len = b[3] & 0x0f;
if (len > 0) {
/*
* Upper 4 bits of LSB represent port number.
* - 0000b: physical MIDI port 1. Use port 0.
* - 1110b: console MIDI port. Use port 2.
*/
if (b[3] >> 4 > 0)
port = 2;
else
port = 0;
if (port < MAX_MIDI_PORTS && p->midi[port])
snd_rawmidi_receive(p->midi[port], b + 1, len);
}
buffer += s->data_block_quadlets;
}
}
int amdtp_dot_add_pcm_hw_constraints(struct amdtp_stream *s,
struct snd_pcm_runtime *runtime)
{
int err;
/* This protocol delivers 24 bit data in 32bit data channel. */
err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
if (err < 0)
return err;
return amdtp_stream_add_pcm_hw_constraints(s, runtime);
}
void amdtp_dot_midi_trigger(struct amdtp_stream *s, unsigned int port,
struct snd_rawmidi_substream *midi)
{
struct amdtp_dot *p = s->protocol;
if (port < MAX_MIDI_PORTS)
WRITE_ONCE(p->midi[port], midi);
}
static void process_ir_ctx_payloads(struct amdtp_stream *s, const struct pkt_desc *desc,
unsigned int count, struct snd_pcm_substream *pcm)
{
unsigned int pcm_frames = 0;
int i;
for (i = 0; i < count; ++i) {
__be32 *buf = desc->ctx_payload;
unsigned int data_blocks = desc->data_blocks;
if (pcm) {
read_pcm_s32(s, pcm, buf, data_blocks, pcm_frames);
pcm_frames += data_blocks;
}
read_midi_messages(s, buf, data_blocks);
desc = amdtp_stream_next_packet_desc(s, desc);
}
}
static void process_it_ctx_payloads(struct amdtp_stream *s, const struct pkt_desc *desc,
unsigned int count, struct snd_pcm_substream *pcm)
{
unsigned int pcm_frames = 0;
int i;
for (i = 0; i < count; ++i) {
__be32 *buf = desc->ctx_payload;
unsigned int data_blocks = desc->data_blocks;
if (pcm) {
write_pcm_s32(s, pcm, buf, data_blocks, pcm_frames);
pcm_frames += data_blocks;
} else {
write_pcm_silence(s, buf, data_blocks);
}
write_midi_messages(s, buf, data_blocks,
desc->data_block_counter);
desc = amdtp_stream_next_packet_desc(s, desc);
}
}
int amdtp_dot_init(struct amdtp_stream *s, struct fw_unit *unit,
enum amdtp_stream_direction dir)
{
amdtp_stream_process_ctx_payloads_t process_ctx_payloads;
unsigned int flags = CIP_NONBLOCKING | CIP_UNAWARE_SYT;
// Use different mode between incoming/outgoing.
if (dir == AMDTP_IN_STREAM)
process_ctx_payloads = process_ir_ctx_payloads;
else
process_ctx_payloads = process_it_ctx_payloads;
return amdtp_stream_init(s, unit, dir, flags, CIP_FMT_AM,
process_ctx_payloads, sizeof(struct amdtp_dot));
}
void amdtp_dot_reset(struct amdtp_stream *s)
{
struct amdtp_dot *p = s->protocol;
p->state.carry = 0x00;
p->state.idx = 0x00;
p->state.off = 0;
}
@@ -0,0 +1,181 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* digi00x-hwdep.c - a part of driver for Digidesign Digi 002/003 family
*
* Copyright (c) 2014-2015 Takashi Sakamoto
*/
/*
* This codes give three functionality.
*
* 1.get firewire node information
* 2.get notification about starting/stopping stream
* 3.lock/unlock stream
* 4.get asynchronous messaging
*/
#include "digi00x.h"
static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
loff_t *offset)
{
struct snd_dg00x *dg00x = hwdep->private_data;
DEFINE_WAIT(wait);
union snd_firewire_event event;
spin_lock_irq(&dg00x->lock);
while (!dg00x->dev_lock_changed && dg00x->msg == 0) {
prepare_to_wait(&dg00x->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
spin_unlock_irq(&dg00x->lock);
schedule();
finish_wait(&dg00x->hwdep_wait, &wait);
if (signal_pending(current))
return -ERESTARTSYS;
spin_lock_irq(&dg00x->lock);
}
memset(&event, 0, sizeof(event));
if (dg00x->dev_lock_changed) {
event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
event.lock_status.status = (dg00x->dev_lock_count > 0);
dg00x->dev_lock_changed = false;
count = min_t(long, count, sizeof(event.lock_status));
} else {
event.digi00x_message.type =
SNDRV_FIREWIRE_EVENT_DIGI00X_MESSAGE;
event.digi00x_message.message = dg00x->msg;
dg00x->msg = 0;
count = min_t(long, count, sizeof(event.digi00x_message));
}
spin_unlock_irq(&dg00x->lock);
if (copy_to_user(buf, &event, count))
return -EFAULT;
return count;
}
static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
poll_table *wait)
{
struct snd_dg00x *dg00x = hwdep->private_data;
poll_wait(file, &dg00x->hwdep_wait, wait);
guard(spinlock_irq)(&dg00x->lock);
if (dg00x->dev_lock_changed || dg00x->msg)
return EPOLLIN | EPOLLRDNORM;
else
return 0;
}
static int hwdep_get_info(struct snd_dg00x *dg00x, void __user *arg)
{
struct fw_device *dev = fw_parent_device(dg00x->unit);
struct snd_firewire_get_info info;
memset(&info, 0, sizeof(info));
info.type = SNDRV_FIREWIRE_TYPE_DIGI00X;
info.card = dev->card->index;
*(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);
*(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);
strscpy(info.device_name, dev_name(&dev->device),
sizeof(info.device_name));
if (copy_to_user(arg, &info, sizeof(info)))
return -EFAULT;
return 0;
}
static int hwdep_lock(struct snd_dg00x *dg00x)
{
guard(spinlock_irq)(&dg00x->lock);
if (dg00x->dev_lock_count == 0) {
dg00x->dev_lock_count = -1;
return 0;
} else {
return -EBUSY;
}
}
static int hwdep_unlock(struct snd_dg00x *dg00x)
{
guard(spinlock_irq)(&dg00x->lock);
if (dg00x->dev_lock_count == -1) {
dg00x->dev_lock_count = 0;
return 0;
} else {
return -EBADFD;
}
}
static int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
{
struct snd_dg00x *dg00x = hwdep->private_data;
guard(spinlock_irq)(&dg00x->lock);
if (dg00x->dev_lock_count == -1)
dg00x->dev_lock_count = 0;
return 0;
}
static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct snd_dg00x *dg00x = hwdep->private_data;
switch (cmd) {
case SNDRV_FIREWIRE_IOCTL_GET_INFO:
return hwdep_get_info(dg00x, (void __user *)arg);
case SNDRV_FIREWIRE_IOCTL_LOCK:
return hwdep_lock(dg00x);
case SNDRV_FIREWIRE_IOCTL_UNLOCK:
return hwdep_unlock(dg00x);
default:
return -ENOIOCTLCMD;
}
}
#ifdef CONFIG_COMPAT
static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
unsigned int cmd, unsigned long arg)
{
return hwdep_ioctl(hwdep, file, cmd,
(unsigned long)compat_ptr(arg));
}
#else
#define hwdep_compat_ioctl NULL
#endif
int snd_dg00x_create_hwdep_device(struct snd_dg00x *dg00x)
{
static const struct snd_hwdep_ops ops = {
.read = hwdep_read,
.release = hwdep_release,
.poll = hwdep_poll,
.ioctl = hwdep_ioctl,
.ioctl_compat = hwdep_compat_ioctl,
};
struct snd_hwdep *hwdep;
int err;
err = snd_hwdep_new(dg00x->card, "Digi00x", 0, &hwdep);
if (err < 0)
return err;
strscpy(hwdep->name, "Digi00x");
hwdep->iface = SNDRV_HWDEP_IFACE_FW_DIGI00X;
hwdep->ops = ops;
hwdep->private_data = dg00x;
hwdep->exclusive = true;
return err;
}
@@ -0,0 +1,167 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* digi00x-midi.h - a part of driver for Digidesign Digi 002/003 family
*
* Copyright (c) 2014-2015 Takashi Sakamoto
*/
#include "digi00x.h"
static int midi_open(struct snd_rawmidi_substream *substream)
{
struct snd_dg00x *dg00x = substream->rmidi->private_data;
int err;
err = snd_dg00x_stream_lock_try(dg00x);
if (err < 0)
return err;
scoped_guard(mutex, &dg00x->mutex) {
err = snd_dg00x_stream_reserve_duplex(dg00x, 0, 0, 0);
if (err >= 0) {
++dg00x->substreams_counter;
err = snd_dg00x_stream_start_duplex(dg00x);
if (err < 0)
--dg00x->substreams_counter;
}
}
if (err < 0)
snd_dg00x_stream_lock_release(dg00x);
return err;
}
static int midi_close(struct snd_rawmidi_substream *substream)
{
struct snd_dg00x *dg00x = substream->rmidi->private_data;
scoped_guard(mutex, &dg00x->mutex) {
--dg00x->substreams_counter;
snd_dg00x_stream_stop_duplex(dg00x);
}
snd_dg00x_stream_lock_release(dg00x);
return 0;
}
static void midi_capture_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct snd_dg00x *dg00x = substream->rmidi->private_data;
unsigned int port;
if (substream->rmidi->device == 0)
port = substream->number;
else
port = 2;
guard(spinlock_irqsave)(&dg00x->lock);
if (up)
amdtp_dot_midi_trigger(&dg00x->tx_stream, port, substream);
else
amdtp_dot_midi_trigger(&dg00x->tx_stream, port, NULL);
}
static void midi_playback_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct snd_dg00x *dg00x = substream->rmidi->private_data;
unsigned int port;
if (substream->rmidi->device == 0)
port = substream->number;
else
port = 2;
guard(spinlock_irqsave)(&dg00x->lock);
if (up)
amdtp_dot_midi_trigger(&dg00x->rx_stream, port, substream);
else
amdtp_dot_midi_trigger(&dg00x->rx_stream, port, NULL);
}
static void set_substream_names(struct snd_dg00x *dg00x,
struct snd_rawmidi *rmidi, bool is_console)
{
struct snd_rawmidi_substream *subs;
struct snd_rawmidi_str *str;
int i;
for (i = 0; i < 2; ++i) {
str = &rmidi->streams[i];
list_for_each_entry(subs, &str->substreams, list) {
if (!is_console) {
scnprintf(subs->name, sizeof(subs->name),
"%s MIDI %d",
dg00x->card->shortname,
subs->number + 1);
} else {
scnprintf(subs->name, sizeof(subs->name),
"%s control",
dg00x->card->shortname);
}
}
}
}
static int add_substream_pair(struct snd_dg00x *dg00x, unsigned int out_ports,
unsigned int in_ports, bool is_console)
{
static const struct snd_rawmidi_ops capture_ops = {
.open = midi_open,
.close = midi_close,
.trigger = midi_capture_trigger,
};
static const struct snd_rawmidi_ops playback_ops = {
.open = midi_open,
.close = midi_close,
.trigger = midi_playback_trigger,
};
const char *label;
struct snd_rawmidi *rmidi;
int err;
/* Add physical midi ports. */
err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, is_console,
out_ports, in_ports, &rmidi);
if (err < 0)
return err;
rmidi->private_data = dg00x;
if (!is_console)
label = "%s control";
else
label = "%s MIDI";
snprintf(rmidi->name, sizeof(rmidi->name), label,
dg00x->card->shortname);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &playback_ops);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &capture_ops);
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT |
SNDRV_RAWMIDI_INFO_OUTPUT |
SNDRV_RAWMIDI_INFO_DUPLEX;
set_substream_names(dg00x, rmidi, is_console);
return 0;
}
int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x)
{
int err;
/* Add physical midi ports. */
err = add_substream_pair(dg00x, DOT_MIDI_OUT_PORTS, DOT_MIDI_IN_PORTS,
false);
if (err < 0)
return err;
if (dg00x->is_console)
err = add_substream_pair(dg00x, 1, 1, true);
return err;
}
@@ -0,0 +1,346 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* digi00x-pcm.c - a part of driver for Digidesign Digi 002/003 family
*
* Copyright (c) 2014-2015 Takashi Sakamoto
*/
#include "digi00x.h"
static int hw_rule_rate(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
struct snd_interval *r =
hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
const struct snd_interval *c =
hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
struct snd_interval t = {
.min = UINT_MAX, .max = 0, .integer = 1,
};
unsigned int i;
for (i = 0; i < SND_DG00X_RATE_COUNT; i++) {
if (!snd_interval_test(c,
snd_dg00x_stream_pcm_channels[i]))
continue;
t.min = min(t.min, snd_dg00x_stream_rates[i]);
t.max = max(t.max, snd_dg00x_stream_rates[i]);
}
return snd_interval_refine(r, &t);
}
static int hw_rule_channels(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
struct snd_interval *c =
hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
const struct snd_interval *r =
hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE);
struct snd_interval t = {
.min = UINT_MAX, .max = 0, .integer = 1,
};
unsigned int i;
for (i = 0; i < SND_DG00X_RATE_COUNT; i++) {
if (!snd_interval_test(r, snd_dg00x_stream_rates[i]))
continue;
t.min = min(t.min, snd_dg00x_stream_pcm_channels[i]);
t.max = max(t.max, snd_dg00x_stream_pcm_channels[i]);
}
return snd_interval_refine(c, &t);
}
static int pcm_init_hw_params(struct snd_dg00x *dg00x,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_pcm_hardware *hw = &runtime->hw;
struct amdtp_stream *s;
int err;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
substream->runtime->hw.formats = SNDRV_PCM_FMTBIT_S32;
s = &dg00x->tx_stream;
} else {
substream->runtime->hw.formats = SNDRV_PCM_FMTBIT_S32;
s = &dg00x->rx_stream;
}
hw->channels_min = 10;
hw->channels_max = 18;
hw->rates = SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_88200 |
SNDRV_PCM_RATE_96000;
snd_pcm_limit_hw_rates(runtime);
err = snd_pcm_hw_rule_add(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_CHANNELS,
hw_rule_channels, NULL,
SNDRV_PCM_HW_PARAM_RATE, -1);
if (err < 0)
return err;
err = snd_pcm_hw_rule_add(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_RATE,
hw_rule_rate, NULL,
SNDRV_PCM_HW_PARAM_CHANNELS, -1);
if (err < 0)
return err;
return amdtp_dot_add_pcm_hw_constraints(s, substream->runtime);
}
static int pcm_open(struct snd_pcm_substream *substream)
{
struct snd_dg00x *dg00x = substream->private_data;
struct amdtp_domain *d = &dg00x->domain;
enum snd_dg00x_clock clock;
bool detect;
int err;
err = snd_dg00x_stream_lock_try(dg00x);
if (err < 0)
return err;
err = pcm_init_hw_params(dg00x, substream);
if (err < 0)
goto err_locked;
/* Check current clock source. */
err = snd_dg00x_stream_get_clock(dg00x, &clock);
if (err < 0)
goto err_locked;
if (clock != SND_DG00X_CLOCK_INTERNAL) {
err = snd_dg00x_stream_check_external_clock(dg00x, &detect);
if (err < 0)
goto err_locked;
if (!detect) {
err = -EBUSY;
goto err_locked;
}
}
scoped_guard(mutex, &dg00x->mutex) {
// When source of clock is not internal or any stream is reserved for
// transmission of PCM frames, the available sampling rate is limited
// at current one.
if ((clock != SND_DG00X_CLOCK_INTERNAL) ||
(dg00x->substreams_counter > 0 && d->events_per_period > 0)) {
unsigned int frames_per_period = d->events_per_period;
unsigned int frames_per_buffer = d->events_per_buffer;
unsigned int rate;
err = snd_dg00x_stream_get_external_rate(dg00x, &rate);
if (err < 0)
goto err_locked;
substream->runtime->hw.rate_min = rate;
substream->runtime->hw.rate_max = rate;
if (frames_per_period > 0) {
err = snd_pcm_hw_constraint_minmax(substream->runtime,
SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
frames_per_period, frames_per_period);
if (err < 0)
goto err_locked;
err = snd_pcm_hw_constraint_minmax(substream->runtime,
SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
frames_per_buffer, frames_per_buffer);
if (err < 0)
goto err_locked;
}
}
}
snd_pcm_set_sync(substream);
return 0;
err_locked:
snd_dg00x_stream_lock_release(dg00x);
return err;
}
static int pcm_close(struct snd_pcm_substream *substream)
{
struct snd_dg00x *dg00x = substream->private_data;
snd_dg00x_stream_lock_release(dg00x);
return 0;
}
static int pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_dg00x *dg00x = substream->private_data;
int err = 0;
if (substream->runtime->state == SNDRV_PCM_STATE_OPEN) {
unsigned int rate = params_rate(hw_params);
unsigned int frames_per_period = params_period_size(hw_params);
unsigned int frames_per_buffer = params_buffer_size(hw_params);
guard(mutex)(&dg00x->mutex);
err = snd_dg00x_stream_reserve_duplex(dg00x, rate,
frames_per_period, frames_per_buffer);
if (err >= 0)
++dg00x->substreams_counter;
}
return err;
}
static int pcm_hw_free(struct snd_pcm_substream *substream)
{
struct snd_dg00x *dg00x = substream->private_data;
guard(mutex)(&dg00x->mutex);
if (substream->runtime->state != SNDRV_PCM_STATE_OPEN)
--dg00x->substreams_counter;
snd_dg00x_stream_stop_duplex(dg00x);
return 0;
}
static int pcm_capture_prepare(struct snd_pcm_substream *substream)
{
struct snd_dg00x *dg00x = substream->private_data;
int err;
guard(mutex)(&dg00x->mutex);
err = snd_dg00x_stream_start_duplex(dg00x);
if (err >= 0)
amdtp_stream_pcm_prepare(&dg00x->tx_stream);
return err;
}
static int pcm_playback_prepare(struct snd_pcm_substream *substream)
{
struct snd_dg00x *dg00x = substream->private_data;
int err;
guard(mutex)(&dg00x->mutex);
err = snd_dg00x_stream_start_duplex(dg00x);
if (err >= 0) {
amdtp_stream_pcm_prepare(&dg00x->rx_stream);
amdtp_dot_reset(&dg00x->rx_stream);
}
return err;
}
static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_dg00x *dg00x = substream->private_data;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
amdtp_stream_pcm_trigger(&dg00x->tx_stream, substream);
break;
case SNDRV_PCM_TRIGGER_STOP:
amdtp_stream_pcm_trigger(&dg00x->tx_stream, NULL);
break;
default:
return -EINVAL;
}
return 0;
}
static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_dg00x *dg00x = substream->private_data;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
amdtp_stream_pcm_trigger(&dg00x->rx_stream, substream);
break;
case SNDRV_PCM_TRIGGER_STOP:
amdtp_stream_pcm_trigger(&dg00x->rx_stream, NULL);
break;
default:
return -EINVAL;
}
return 0;
}
static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
{
struct snd_dg00x *dg00x = sbstrm->private_data;
return amdtp_domain_stream_pcm_pointer(&dg00x->domain, &dg00x->tx_stream);
}
static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
{
struct snd_dg00x *dg00x = sbstrm->private_data;
return amdtp_domain_stream_pcm_pointer(&dg00x->domain, &dg00x->rx_stream);
}
static int pcm_capture_ack(struct snd_pcm_substream *substream)
{
struct snd_dg00x *dg00x = substream->private_data;
return amdtp_domain_stream_pcm_ack(&dg00x->domain, &dg00x->tx_stream);
}
static int pcm_playback_ack(struct snd_pcm_substream *substream)
{
struct snd_dg00x *dg00x = substream->private_data;
return amdtp_domain_stream_pcm_ack(&dg00x->domain, &dg00x->rx_stream);
}
int snd_dg00x_create_pcm_devices(struct snd_dg00x *dg00x)
{
static const struct snd_pcm_ops capture_ops = {
.open = pcm_open,
.close = pcm_close,
.hw_params = pcm_hw_params,
.hw_free = pcm_hw_free,
.prepare = pcm_capture_prepare,
.trigger = pcm_capture_trigger,
.pointer = pcm_capture_pointer,
.ack = pcm_capture_ack,
};
static const struct snd_pcm_ops playback_ops = {
.open = pcm_open,
.close = pcm_close,
.hw_params = pcm_hw_params,
.hw_free = pcm_hw_free,
.prepare = pcm_playback_prepare,
.trigger = pcm_playback_trigger,
.pointer = pcm_playback_pointer,
.ack = pcm_playback_ack,
};
struct snd_pcm *pcm;
int err;
err = snd_pcm_new(dg00x->card, dg00x->card->driver, 0, 1, 1, &pcm);
if (err < 0)
return err;
pcm->private_data = dg00x;
pcm->nonatomic = true;
snprintf(pcm->name, sizeof(pcm->name),
"%s PCM", dg00x->card->shortname);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops);
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0);
return 0;
}
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* digi00x-proc.c - a part of driver for Digidesign Digi 002/003 family
*
* Copyright (c) 2014-2015 Takashi Sakamoto
*/
#include "digi00x.h"
static int get_optical_iface_mode(struct snd_dg00x *dg00x,
enum snd_dg00x_optical_mode *mode)
{
__be32 data;
int err;
err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST,
DG00X_ADDR_BASE + DG00X_OFFSET_OPT_IFACE_MODE,
&data, sizeof(data), 0);
if (err >= 0)
*mode = be32_to_cpu(data) & 0x01;
return err;
}
static void proc_read_clock(struct snd_info_entry *entry,
struct snd_info_buffer *buf)
{
static const char *const source_name[] = {
[SND_DG00X_CLOCK_INTERNAL] = "internal",
[SND_DG00X_CLOCK_SPDIF] = "s/pdif",
[SND_DG00X_CLOCK_ADAT] = "adat",
[SND_DG00X_CLOCK_WORD] = "word clock",
};
static const char *const optical_name[] = {
[SND_DG00X_OPT_IFACE_MODE_ADAT] = "adat",
[SND_DG00X_OPT_IFACE_MODE_SPDIF] = "s/pdif",
};
struct snd_dg00x *dg00x = entry->private_data;
enum snd_dg00x_optical_mode mode;
unsigned int rate;
enum snd_dg00x_clock clock;
bool detect;
if (get_optical_iface_mode(dg00x, &mode) < 0)
return;
if (snd_dg00x_stream_get_local_rate(dg00x, &rate) < 0)
return;
if (snd_dg00x_stream_get_clock(dg00x, &clock) < 0)
return;
snd_iprintf(buf, "Optical mode: %s\n", optical_name[mode]);
snd_iprintf(buf, "Sampling Rate: %d\n", rate);
snd_iprintf(buf, "Clock Source: %s\n", source_name[clock]);
if (clock == SND_DG00X_CLOCK_INTERNAL)
return;
if (snd_dg00x_stream_check_external_clock(dg00x, &detect) < 0)
return;
snd_iprintf(buf, "External source: %s\n", detect ? "detected" : "not");
if (!detect)
return;
if (snd_dg00x_stream_get_external_rate(dg00x, &rate) >= 0)
snd_iprintf(buf, "External sampling rate: %d\n", rate);
}
void snd_dg00x_proc_init(struct snd_dg00x *dg00x)
{
struct snd_info_entry *root, *entry;
/*
* All nodes are automatically removed at snd_card_disconnect(),
* by following to link list.
*/
root = snd_info_create_card_entry(dg00x->card, "firewire",
dg00x->card->proc_root);
if (root == NULL)
return;
root->mode = S_IFDIR | 0555;
entry = snd_info_create_card_entry(dg00x->card, "clock", root);
if (entry)
snd_info_set_text_ops(entry, dg00x, proc_read_clock);
}
@@ -0,0 +1,450 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* digi00x-stream.c - a part of driver for Digidesign Digi 002/003 family
*
* Copyright (c) 2014-2015 Takashi Sakamoto
*/
#include "digi00x.h"
#define READY_TIMEOUT_MS 200
const unsigned int snd_dg00x_stream_rates[SND_DG00X_RATE_COUNT] = {
[SND_DG00X_RATE_44100] = 44100,
[SND_DG00X_RATE_48000] = 48000,
[SND_DG00X_RATE_88200] = 88200,
[SND_DG00X_RATE_96000] = 96000,
};
/* Multi Bit Linear Audio data channels for each sampling transfer frequency. */
const unsigned int
snd_dg00x_stream_pcm_channels[SND_DG00X_RATE_COUNT] = {
/* Analog/ADAT/SPDIF */
[SND_DG00X_RATE_44100] = (8 + 8 + 2),
[SND_DG00X_RATE_48000] = (8 + 8 + 2),
/* Analog/SPDIF */
[SND_DG00X_RATE_88200] = (8 + 2),
[SND_DG00X_RATE_96000] = (8 + 2),
};
int snd_dg00x_stream_get_local_rate(struct snd_dg00x *dg00x, unsigned int *rate)
{
u32 data;
__be32 reg;
int err;
err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST,
DG00X_ADDR_BASE + DG00X_OFFSET_LOCAL_RATE,
&reg, sizeof(reg), 0);
if (err < 0)
return err;
data = be32_to_cpu(reg) & 0x0f;
if (data < ARRAY_SIZE(snd_dg00x_stream_rates))
*rate = snd_dg00x_stream_rates[data];
else
err = -EIO;
return err;
}
int snd_dg00x_stream_set_local_rate(struct snd_dg00x *dg00x, unsigned int rate)
{
__be32 reg;
unsigned int i;
for (i = 0; i < ARRAY_SIZE(snd_dg00x_stream_rates); i++) {
if (rate == snd_dg00x_stream_rates[i])
break;
}
if (i == ARRAY_SIZE(snd_dg00x_stream_rates))
return -EINVAL;
reg = cpu_to_be32(i);
return snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST,
DG00X_ADDR_BASE + DG00X_OFFSET_LOCAL_RATE,
&reg, sizeof(reg), 0);
}
int snd_dg00x_stream_get_clock(struct snd_dg00x *dg00x,
enum snd_dg00x_clock *clock)
{
__be32 reg;
int err;
err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST,
DG00X_ADDR_BASE + DG00X_OFFSET_CLOCK_SOURCE,
&reg, sizeof(reg), 0);
if (err < 0)
return err;
*clock = be32_to_cpu(reg) & 0x0f;
if (*clock >= SND_DG00X_CLOCK_COUNT)
err = -EIO;
return err;
}
int snd_dg00x_stream_check_external_clock(struct snd_dg00x *dg00x, bool *detect)
{
__be32 reg;
int err;
err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST,
DG00X_ADDR_BASE + DG00X_OFFSET_DETECT_EXTERNAL,
&reg, sizeof(reg), 0);
if (err >= 0)
*detect = be32_to_cpu(reg) > 0;
return err;
}
int snd_dg00x_stream_get_external_rate(struct snd_dg00x *dg00x,
unsigned int *rate)
{
u32 data;
__be32 reg;
int err;
err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST,
DG00X_ADDR_BASE + DG00X_OFFSET_EXTERNAL_RATE,
&reg, sizeof(reg), 0);
if (err < 0)
return err;
data = be32_to_cpu(reg) & 0x0f;
if (data < ARRAY_SIZE(snd_dg00x_stream_rates))
*rate = snd_dg00x_stream_rates[data];
/* This means desync. */
else
err = -EBUSY;
return err;
}
static void finish_session(struct snd_dg00x *dg00x)
{
__be32 data;
data = cpu_to_be32(0x00000003);
snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST,
DG00X_ADDR_BASE + DG00X_OFFSET_STREAMING_SET,
&data, sizeof(data), 0);
// Unregister isochronous channels for both direction.
data = 0;
snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST,
DG00X_ADDR_BASE + DG00X_OFFSET_ISOC_CHANNELS,
&data, sizeof(data), 0);
// Just after finishing the session, the device may lost transmitting
// functionality for a short time.
msleep(50);
}
static int begin_session(struct snd_dg00x *dg00x)
{
__be32 data;
u32 curr;
int err;
// Register isochronous channels for both direction.
data = cpu_to_be32((dg00x->tx_resources.channel << 16) |
dg00x->rx_resources.channel);
err = snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST,
DG00X_ADDR_BASE + DG00X_OFFSET_ISOC_CHANNELS,
&data, sizeof(data), 0);
if (err < 0)
return err;
err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST,
DG00X_ADDR_BASE + DG00X_OFFSET_STREAMING_STATE,
&data, sizeof(data), 0);
if (err < 0)
return err;
curr = be32_to_cpu(data);
if (curr == 0)
curr = 2;
curr--;
while (curr > 0) {
data = cpu_to_be32(curr);
err = snd_fw_transaction(dg00x->unit,
TCODE_WRITE_QUADLET_REQUEST,
DG00X_ADDR_BASE +
DG00X_OFFSET_STREAMING_SET,
&data, sizeof(data), 0);
if (err < 0)
break;
msleep(20);
curr--;
}
return err;
}
static int keep_resources(struct snd_dg00x *dg00x, struct amdtp_stream *stream,
unsigned int rate)
{
struct fw_iso_resources *resources;
int i;
int err;
// Check sampling rate.
for (i = 0; i < SND_DG00X_RATE_COUNT; i++) {
if (snd_dg00x_stream_rates[i] == rate)
break;
}
if (i == SND_DG00X_RATE_COUNT)
return -EINVAL;
if (stream == &dg00x->tx_stream)
resources = &dg00x->tx_resources;
else
resources = &dg00x->rx_resources;
err = amdtp_dot_set_parameters(stream, rate,
snd_dg00x_stream_pcm_channels[i]);
if (err < 0)
return err;
return fw_iso_resources_allocate(resources,
amdtp_stream_get_max_payload(stream),
fw_parent_device(dg00x->unit)->max_speed);
}
static int init_stream(struct snd_dg00x *dg00x, struct amdtp_stream *s)
{
struct fw_iso_resources *resources;
enum amdtp_stream_direction dir;
int err;
if (s == &dg00x->tx_stream) {
resources = &dg00x->tx_resources;
dir = AMDTP_IN_STREAM;
} else {
resources = &dg00x->rx_resources;
dir = AMDTP_OUT_STREAM;
}
err = fw_iso_resources_init(resources, dg00x->unit);
if (err < 0)
return err;
err = amdtp_dot_init(s, dg00x->unit, dir);
if (err < 0)
fw_iso_resources_destroy(resources);
return err;
}
static void destroy_stream(struct snd_dg00x *dg00x, struct amdtp_stream *s)
{
amdtp_stream_destroy(s);
if (s == &dg00x->tx_stream)
fw_iso_resources_destroy(&dg00x->tx_resources);
else
fw_iso_resources_destroy(&dg00x->rx_resources);
}
int snd_dg00x_stream_init_duplex(struct snd_dg00x *dg00x)
{
int err;
err = init_stream(dg00x, &dg00x->rx_stream);
if (err < 0)
return err;
err = init_stream(dg00x, &dg00x->tx_stream);
if (err < 0) {
destroy_stream(dg00x, &dg00x->rx_stream);
return err;
}
err = amdtp_domain_init(&dg00x->domain);
if (err < 0) {
destroy_stream(dg00x, &dg00x->rx_stream);
destroy_stream(dg00x, &dg00x->tx_stream);
}
return err;
}
/*
* This function should be called before starting streams or after stopping
* streams.
*/
void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x)
{
amdtp_domain_destroy(&dg00x->domain);
destroy_stream(dg00x, &dg00x->rx_stream);
destroy_stream(dg00x, &dg00x->tx_stream);
}
int snd_dg00x_stream_reserve_duplex(struct snd_dg00x *dg00x, unsigned int rate,
unsigned int frames_per_period,
unsigned int frames_per_buffer)
{
unsigned int curr_rate;
int err;
err = snd_dg00x_stream_get_local_rate(dg00x, &curr_rate);
if (err < 0)
return err;
if (rate == 0)
rate = curr_rate;
if (dg00x->substreams_counter == 0 || curr_rate != rate) {
amdtp_domain_stop(&dg00x->domain);
finish_session(dg00x);
fw_iso_resources_free(&dg00x->tx_resources);
fw_iso_resources_free(&dg00x->rx_resources);
err = snd_dg00x_stream_set_local_rate(dg00x, rate);
if (err < 0)
return err;
err = keep_resources(dg00x, &dg00x->rx_stream, rate);
if (err < 0)
return err;
err = keep_resources(dg00x, &dg00x->tx_stream, rate);
if (err < 0) {
fw_iso_resources_free(&dg00x->rx_resources);
return err;
}
err = amdtp_domain_set_events_per_period(&dg00x->domain,
frames_per_period, frames_per_buffer);
if (err < 0) {
fw_iso_resources_free(&dg00x->rx_resources);
fw_iso_resources_free(&dg00x->tx_resources);
return err;
}
}
return 0;
}
int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x)
{
unsigned int generation = dg00x->rx_resources.generation;
int err = 0;
if (dg00x->substreams_counter == 0)
return 0;
if (amdtp_streaming_error(&dg00x->tx_stream) ||
amdtp_streaming_error(&dg00x->rx_stream)) {
amdtp_domain_stop(&dg00x->domain);
finish_session(dg00x);
}
if (generation != fw_parent_device(dg00x->unit)->card->generation) {
err = fw_iso_resources_update(&dg00x->tx_resources);
if (err < 0)
goto error;
err = fw_iso_resources_update(&dg00x->rx_resources);
if (err < 0)
goto error;
}
/*
* No packets are transmitted without receiving packets, reagardless of
* which source of clock is used.
*/
if (!amdtp_stream_running(&dg00x->rx_stream)) {
int spd = fw_parent_device(dg00x->unit)->max_speed;
err = begin_session(dg00x);
if (err < 0)
goto error;
err = amdtp_domain_add_stream(&dg00x->domain, &dg00x->rx_stream,
dg00x->rx_resources.channel, spd);
if (err < 0)
goto error;
err = amdtp_domain_add_stream(&dg00x->domain, &dg00x->tx_stream,
dg00x->tx_resources.channel, spd);
if (err < 0)
goto error;
// NOTE: The device doesn't start packet transmission till receiving any packet.
// It ignores presentation time expressed by the value of syt field of CIP header
// in received packets. The sequence of the number of data blocks per packet is
// important for media clock recovery.
err = amdtp_domain_start(&dg00x->domain, 0, true, true);
if (err < 0)
goto error;
if (!amdtp_domain_wait_ready(&dg00x->domain, READY_TIMEOUT_MS)) {
err = -ETIMEDOUT;
goto error;
}
}
return 0;
error:
amdtp_domain_stop(&dg00x->domain);
finish_session(dg00x);
return err;
}
void snd_dg00x_stream_stop_duplex(struct snd_dg00x *dg00x)
{
if (dg00x->substreams_counter == 0) {
amdtp_domain_stop(&dg00x->domain);
finish_session(dg00x);
fw_iso_resources_free(&dg00x->tx_resources);
fw_iso_resources_free(&dg00x->rx_resources);
}
}
void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x)
{
fw_iso_resources_update(&dg00x->tx_resources);
fw_iso_resources_update(&dg00x->rx_resources);
amdtp_stream_update(&dg00x->tx_stream);
amdtp_stream_update(&dg00x->rx_stream);
}
void snd_dg00x_stream_lock_changed(struct snd_dg00x *dg00x)
{
dg00x->dev_lock_changed = true;
wake_up(&dg00x->hwdep_wait);
}
int snd_dg00x_stream_lock_try(struct snd_dg00x *dg00x)
{
guard(spinlock_irq)(&dg00x->lock);
/* user land lock this */
if (dg00x->dev_lock_count < 0)
return -EBUSY;
/* this is the first time */
if (dg00x->dev_lock_count++ == 0)
snd_dg00x_stream_lock_changed(dg00x);
return 0;
}
void snd_dg00x_stream_lock_release(struct snd_dg00x *dg00x)
{
guard(spinlock_irq)(&dg00x->lock);
if (WARN_ON(dg00x->dev_lock_count <= 0))
return;
if (--dg00x->dev_lock_count == 0)
snd_dg00x_stream_lock_changed(dg00x);
}
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* digi00x-transaction.c - a part of driver for Digidesign Digi 002/003 family
*
* Copyright (c) 2014-2015 Takashi Sakamoto
*/
#include <sound/asound.h>
#include "digi00x.h"
static void handle_unknown_message(struct snd_dg00x *dg00x,
unsigned long long offset, __be32 *buf)
{
scoped_guard(spinlock_irqsave, &dg00x->lock) {
dg00x->msg = be32_to_cpu(*buf);
}
wake_up(&dg00x->hwdep_wait);
}
static void handle_message(struct fw_card *card, struct fw_request *request,
int tcode, int destination, int source,
int generation, unsigned long long offset,
void *data, size_t length, void *callback_data)
{
struct snd_dg00x *dg00x = callback_data;
__be32 *buf = (__be32 *)data;
fw_send_response(card, request, RCODE_COMPLETE);
if (offset == dg00x->async_handler.offset)
handle_unknown_message(dg00x, offset, buf);
}
int snd_dg00x_transaction_reregister(struct snd_dg00x *dg00x)
{
struct fw_device *device = fw_parent_device(dg00x->unit);
__be32 data[2];
/* Unknown. 4bytes. */
data[0] = cpu_to_be32((device->card->node_id << 16) |
(dg00x->async_handler.offset >> 32));
data[1] = cpu_to_be32(dg00x->async_handler.offset);
return snd_fw_transaction(dg00x->unit, TCODE_WRITE_BLOCK_REQUEST,
DG00X_ADDR_BASE + DG00X_OFFSET_MESSAGE_ADDR,
&data, sizeof(data), 0);
}
void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x)
{
if (dg00x->async_handler.callback_data == NULL)
return;
fw_core_remove_address_handler(&dg00x->async_handler);
dg00x->async_handler.callback_data = NULL;
}
int snd_dg00x_transaction_register(struct snd_dg00x *dg00x)
{
static const struct fw_address_region resp_register_region = {
.start = 0xffffe0000000ull,
.end = 0xffffe000ffffull,
};
int err;
dg00x->async_handler.length = 4;
dg00x->async_handler.address_callback = handle_message;
dg00x->async_handler.callback_data = dg00x;
err = fw_core_add_address_handler(&dg00x->async_handler,
&resp_register_region);
if (err < 0)
return err;
err = snd_dg00x_transaction_reregister(dg00x);
if (err < 0)
snd_dg00x_transaction_unregister(dg00x);
return err;
}
@@ -0,0 +1,176 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* digi00x.c - a part of driver for Digidesign Digi 002/003 family
*
* Copyright (c) 2014-2015 Takashi Sakamoto
*/
#include "digi00x.h"
MODULE_DESCRIPTION("Digidesign Digi 002/003 family Driver");
MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
MODULE_LICENSE("GPL");
#define VENDOR_DIGIDESIGN 0x00a07e
#define MODEL_CONSOLE 0x000001
#define MODEL_RACK 0x000002
#define SPEC_VERSION 0x000001
static int name_card(struct snd_dg00x *dg00x)
{
struct fw_device *fw_dev = fw_parent_device(dg00x->unit);
char name[32] = {0};
char *model;
int err;
err = fw_csr_string(dg00x->unit->directory, CSR_MODEL, name,
sizeof(name));
if (err < 0)
return err;
model = skip_spaces(name);
strscpy(dg00x->card->driver, "Digi00x");
strscpy(dg00x->card->shortname, model);
strscpy(dg00x->card->mixername, model);
snprintf(dg00x->card->longname, sizeof(dg00x->card->longname),
"Digidesign %s, GUID %08x%08x at %s, S%d", model,
fw_dev->config_rom[3], fw_dev->config_rom[4],
dev_name(&dg00x->unit->device), 100 << fw_dev->max_speed);
return 0;
}
static void dg00x_card_free(struct snd_card *card)
{
struct snd_dg00x *dg00x = card->private_data;
snd_dg00x_stream_destroy_duplex(dg00x);
snd_dg00x_transaction_unregister(dg00x);
mutex_destroy(&dg00x->mutex);
fw_unit_put(dg00x->unit);
}
static int snd_dg00x_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry)
{
struct snd_card *card;
struct snd_dg00x *dg00x;
int err;
err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, sizeof(*dg00x), &card);
if (err < 0)
return err;
card->private_free = dg00x_card_free;
dg00x = card->private_data;
dg00x->unit = fw_unit_get(unit);
dev_set_drvdata(&unit->device, dg00x);
dg00x->card = card;
mutex_init(&dg00x->mutex);
spin_lock_init(&dg00x->lock);
init_waitqueue_head(&dg00x->hwdep_wait);
dg00x->is_console = entry->model_id == MODEL_CONSOLE;
err = name_card(dg00x);
if (err < 0)
goto error;
err = snd_dg00x_stream_init_duplex(dg00x);
if (err < 0)
goto error;
snd_dg00x_proc_init(dg00x);
err = snd_dg00x_create_pcm_devices(dg00x);
if (err < 0)
goto error;
err = snd_dg00x_create_midi_devices(dg00x);
if (err < 0)
goto error;
err = snd_dg00x_create_hwdep_device(dg00x);
if (err < 0)
goto error;
err = snd_dg00x_transaction_register(dg00x);
if (err < 0)
goto error;
err = snd_card_register(card);
if (err < 0)
goto error;
return 0;
error:
snd_card_free(card);
return err;
}
static void snd_dg00x_update(struct fw_unit *unit)
{
struct snd_dg00x *dg00x = dev_get_drvdata(&unit->device);
snd_dg00x_transaction_reregister(dg00x);
guard(mutex)(&dg00x->mutex);
snd_dg00x_stream_update_duplex(dg00x);
}
static void snd_dg00x_remove(struct fw_unit *unit)
{
struct snd_dg00x *dg00x = dev_get_drvdata(&unit->device);
// Block till all of ALSA character devices are released.
snd_card_free(dg00x->card);
}
static const struct ieee1394_device_id snd_dg00x_id_table[] = {
/* Both of 002/003 use the same ID. */
{
.match_flags = IEEE1394_MATCH_VENDOR_ID |
IEEE1394_MATCH_VERSION |
IEEE1394_MATCH_MODEL_ID,
.vendor_id = VENDOR_DIGIDESIGN,
.version = SPEC_VERSION,
.model_id = MODEL_CONSOLE,
},
{
.match_flags = IEEE1394_MATCH_VENDOR_ID |
IEEE1394_MATCH_VERSION |
IEEE1394_MATCH_MODEL_ID,
.vendor_id = VENDOR_DIGIDESIGN,
.version = SPEC_VERSION,
.model_id = MODEL_RACK,
},
{}
};
MODULE_DEVICE_TABLE(ieee1394, snd_dg00x_id_table);
static struct fw_driver dg00x_driver = {
.driver = {
.owner = THIS_MODULE,
.name = KBUILD_MODNAME,
.bus = &fw_bus_type,
},
.probe = snd_dg00x_probe,
.update = snd_dg00x_update,
.remove = snd_dg00x_remove,
.id_table = snd_dg00x_id_table,
};
static int __init snd_dg00x_init(void)
{
return driver_register(&dg00x_driver.driver);
}
static void __exit snd_dg00x_exit(void)
{
driver_unregister(&dg00x_driver.driver);
}
module_init(snd_dg00x_init);
module_exit(snd_dg00x_exit);
@@ -0,0 +1,159 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* digi00x.h - a part of driver for Digidesign Digi 002/003 family
*
* Copyright (c) 2014-2015 Takashi Sakamoto
*/
#ifndef SOUND_DIGI00X_H_INCLUDED
#define SOUND_DIGI00X_H_INCLUDED
#include <linux/compat.h>
#include <linux/device.h>
#include <linux/firewire.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/sched/signal.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/info.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/firewire.h>
#include <sound/hwdep.h>
#include <sound/rawmidi.h>
#include "../lib.h"
#include "../iso-resources.h"
#include "../amdtp-stream.h"
struct snd_dg00x {
struct snd_card *card;
struct fw_unit *unit;
struct mutex mutex;
spinlock_t lock;
struct amdtp_stream tx_stream;
struct fw_iso_resources tx_resources;
struct amdtp_stream rx_stream;
struct fw_iso_resources rx_resources;
unsigned int substreams_counter;
/* for uapi */
int dev_lock_count;
bool dev_lock_changed;
wait_queue_head_t hwdep_wait;
/* For asynchronous messages. */
struct fw_address_handler async_handler;
u32 msg;
/* Console models have additional MIDI ports for control surface. */
bool is_console;
struct amdtp_domain domain;
};
#define DG00X_ADDR_BASE 0xffffe0000000ull
#define DG00X_OFFSET_STREAMING_STATE 0x0000
#define DG00X_OFFSET_STREAMING_SET 0x0004
/* unknown but address in host space 0x0008 */
/* For LSB of the address 0x000c */
/* unknown 0x0010 */
#define DG00X_OFFSET_MESSAGE_ADDR 0x0014
/* For LSB of the address 0x0018 */
/* unknown 0x001c */
/* unknown 0x0020 */
/* not used 0x0024--0x00ff */
#define DG00X_OFFSET_ISOC_CHANNELS 0x0100
/* unknown 0x0104 */
/* unknown 0x0108 */
/* unknown 0x010c */
#define DG00X_OFFSET_LOCAL_RATE 0x0110
#define DG00X_OFFSET_EXTERNAL_RATE 0x0114
#define DG00X_OFFSET_CLOCK_SOURCE 0x0118
#define DG00X_OFFSET_OPT_IFACE_MODE 0x011c
/* unknown 0x0120 */
/* Mixer control on/off 0x0124 */
/* unknown 0x0128 */
#define DG00X_OFFSET_DETECT_EXTERNAL 0x012c
/* unknown 0x0138 */
#define DG00X_OFFSET_MMC 0x0400
enum snd_dg00x_rate {
SND_DG00X_RATE_44100 = 0,
SND_DG00X_RATE_48000,
SND_DG00X_RATE_88200,
SND_DG00X_RATE_96000,
SND_DG00X_RATE_COUNT,
};
enum snd_dg00x_clock {
SND_DG00X_CLOCK_INTERNAL = 0,
SND_DG00X_CLOCK_SPDIF,
SND_DG00X_CLOCK_ADAT,
SND_DG00X_CLOCK_WORD,
SND_DG00X_CLOCK_COUNT,
};
enum snd_dg00x_optical_mode {
SND_DG00X_OPT_IFACE_MODE_ADAT = 0,
SND_DG00X_OPT_IFACE_MODE_SPDIF,
SND_DG00X_OPT_IFACE_MODE_COUNT,
};
#define DOT_MIDI_IN_PORTS 1
#define DOT_MIDI_OUT_PORTS 2
int amdtp_dot_init(struct amdtp_stream *s, struct fw_unit *unit,
enum amdtp_stream_direction dir);
int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate,
unsigned int pcm_channels);
void amdtp_dot_reset(struct amdtp_stream *s);
int amdtp_dot_add_pcm_hw_constraints(struct amdtp_stream *s,
struct snd_pcm_runtime *runtime);
void amdtp_dot_midi_trigger(struct amdtp_stream *s, unsigned int port,
struct snd_rawmidi_substream *midi);
int snd_dg00x_transaction_register(struct snd_dg00x *dg00x);
int snd_dg00x_transaction_reregister(struct snd_dg00x *dg00x);
void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x);
extern const unsigned int snd_dg00x_stream_rates[SND_DG00X_RATE_COUNT];
extern const unsigned int snd_dg00x_stream_pcm_channels[SND_DG00X_RATE_COUNT];
int snd_dg00x_stream_get_external_rate(struct snd_dg00x *dg00x,
unsigned int *rate);
int snd_dg00x_stream_get_local_rate(struct snd_dg00x *dg00x,
unsigned int *rate);
int snd_dg00x_stream_set_local_rate(struct snd_dg00x *dg00x, unsigned int rate);
int snd_dg00x_stream_get_clock(struct snd_dg00x *dg00x,
enum snd_dg00x_clock *clock);
int snd_dg00x_stream_check_external_clock(struct snd_dg00x *dg00x,
bool *detect);
int snd_dg00x_stream_init_duplex(struct snd_dg00x *dg00x);
int snd_dg00x_stream_reserve_duplex(struct snd_dg00x *dg00x, unsigned int rate,
unsigned int frames_per_period,
unsigned int frames_per_buffer);
int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x);
void snd_dg00x_stream_stop_duplex(struct snd_dg00x *dg00x);
void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x);
void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x);
void snd_dg00x_stream_lock_changed(struct snd_dg00x *dg00x);
int snd_dg00x_stream_lock_try(struct snd_dg00x *dg00x);
void snd_dg00x_stream_lock_release(struct snd_dg00x *dg00x);
void snd_dg00x_proc_init(struct snd_dg00x *dg00x);
int snd_dg00x_create_pcm_devices(struct snd_dg00x *dg00x);
int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x);
int snd_dg00x_create_hwdep_device(struct snd_dg00x *dg00x);
#endif