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-fireface-y := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \
ff-stream.o ff-pcm.o ff-hwdep.o ff-protocol-former.o \
ff-protocol-latter.o
obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o
@@ -0,0 +1,167 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* amdtp-ff.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*/
#include <sound/pcm.h>
#include "ff.h"
struct amdtp_ff {
unsigned int pcm_channels;
};
int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate,
unsigned int pcm_channels)
{
struct amdtp_ff *p = s->protocol;
unsigned int data_channels;
if (amdtp_stream_running(s))
return -EBUSY;
p->pcm_channels = pcm_channels;
data_channels = pcm_channels;
return amdtp_stream_set_parameters(s, rate, data_channels, 1);
}
static void write_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm,
__le32 *buffer, unsigned int frames,
unsigned int pcm_frames)
{
struct amdtp_ff *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;
for (i = 0; i < frames; ++i) {
for (c = 0; c < channels; ++c) {
buffer[c] = cpu_to_le32(*src);
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,
__le32 *buffer, unsigned int frames,
unsigned int pcm_frames)
{
struct amdtp_ff *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;
for (i = 0; i < frames; ++i) {
for (c = 0; c < channels; ++c) {
*dst = le32_to_cpu(buffer[c]) & 0xffffff00;
dst++;
}
buffer += s->data_block_quadlets;
if (--remaining_frames == 0)
dst = (void *)runtime->dma_area;
}
}
static void write_pcm_silence(struct amdtp_stream *s,
__le32 *buffer, unsigned int frames)
{
struct amdtp_ff *p = s->protocol;
unsigned int i, c, channels = p->pcm_channels;
for (i = 0; i < frames; ++i) {
for (c = 0; c < channels; ++c)
buffer[c] = cpu_to_le32(0x00000000);
buffer += s->data_block_quadlets;
}
}
int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s,
struct snd_pcm_runtime *runtime)
{
int err;
err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
if (err < 0)
return err;
return amdtp_stream_add_pcm_hw_constraints(s, runtime);
}
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) {
__le32 *buf = (__le32 *)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);
}
desc = amdtp_stream_next_packet_desc(s, desc);
}
}
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) {
__le32 *buf = (__le32 *)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;
}
desc = amdtp_stream_next_packet_desc(s, desc);
}
}
int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit,
enum amdtp_stream_direction dir)
{
amdtp_stream_process_ctx_payloads_t process_ctx_payloads;
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, CIP_BLOCKING | CIP_UNAWARE_SYT | CIP_NO_HEADER, 0,
process_ctx_payloads, sizeof(struct amdtp_ff));
}
@@ -0,0 +1,190 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ff-hwdep.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*/
/*
* This codes give three functionality.
*
* 1.get firewire node information
* 2.get notification about starting/stopping stream
* 3.lock/unlock stream
*/
#include "ff.h"
static bool has_msg(struct snd_ff *ff)
{
if (ff->spec->protocol->has_msg)
return ff->spec->protocol->has_msg(ff);
else
return 0;
}
static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
loff_t *offset)
{
struct snd_ff *ff = hwdep->private_data;
DEFINE_WAIT(wait);
spin_lock_irq(&ff->lock);
while (!ff->dev_lock_changed && !has_msg(ff)) {
prepare_to_wait(&ff->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
spin_unlock_irq(&ff->lock);
schedule();
finish_wait(&ff->hwdep_wait, &wait);
if (signal_pending(current))
return -ERESTARTSYS;
spin_lock_irq(&ff->lock);
}
if (ff->dev_lock_changed && count >= sizeof(struct snd_firewire_event_lock_status)) {
struct snd_firewire_event_lock_status ev = {
.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS,
.status = (ff->dev_lock_count > 0),
};
ff->dev_lock_changed = false;
spin_unlock_irq(&ff->lock);
if (copy_to_user(buf, &ev, sizeof(ev)))
return -EFAULT;
count = sizeof(ev);
} else if (has_msg(ff)) {
// NOTE: Acquired spin lock should be released before accessing to user space in the
// callback since the access can cause page fault.
count = ff->spec->protocol->copy_msg_to_user(ff, buf, count);
spin_unlock_irq(&ff->lock);
} else {
spin_unlock_irq(&ff->lock);
count = 0;
}
return count;
}
static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
poll_table *wait)
{
struct snd_ff *ff = hwdep->private_data;
poll_wait(file, &ff->hwdep_wait, wait);
guard(spinlock_irq)(&ff->lock);
if (ff->dev_lock_changed || has_msg(ff))
return EPOLLIN | EPOLLRDNORM;
else
return 0;
}
static int hwdep_get_info(struct snd_ff *ff, void __user *arg)
{
struct fw_device *dev = fw_parent_device(ff->unit);
struct snd_firewire_get_info info;
memset(&info, 0, sizeof(info));
info.type = SNDRV_FIREWIRE_TYPE_FIREFACE;
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_ff *ff)
{
guard(spinlock_irq)(&ff->lock);
if (ff->dev_lock_count == 0) {
ff->dev_lock_count = -1;
return 0;
} else {
return -EBUSY;
}
}
static int hwdep_unlock(struct snd_ff *ff)
{
guard(spinlock_irq)(&ff->lock);
if (ff->dev_lock_count == -1) {
ff->dev_lock_count = 0;
return 0;
} else {
return -EBADFD;
}
}
static int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
{
struct snd_ff *ff = hwdep->private_data;
guard(spinlock_irq)(&ff->lock);
if (ff->dev_lock_count == -1)
ff->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_ff *ff = hwdep->private_data;
switch (cmd) {
case SNDRV_FIREWIRE_IOCTL_GET_INFO:
return hwdep_get_info(ff, (void __user *)arg);
case SNDRV_FIREWIRE_IOCTL_LOCK:
return hwdep_lock(ff);
case SNDRV_FIREWIRE_IOCTL_UNLOCK:
return hwdep_unlock(ff);
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_ff_create_hwdep_devices(struct snd_ff *ff)
{
static const struct snd_hwdep_ops hwdep_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(ff->card, ff->card->driver, 0, &hwdep);
if (err < 0)
return err;
strscpy(hwdep->name, ff->card->driver);
hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREFACE;
hwdep->ops = hwdep_ops;
hwdep->private_data = ff;
hwdep->exclusive = true;
return 0;
}
@@ -0,0 +1,122 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ff-midi.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*/
#include "ff.h"
static int midi_capture_open(struct snd_rawmidi_substream *substream)
{
/* Do nothing. */
return 0;
}
static int midi_playback_open(struct snd_rawmidi_substream *substream)
{
struct snd_ff *ff = substream->rmidi->private_data;
/* Initialize internal status. */
ff->on_sysex[substream->number] = 0;
ff->rx_midi_error[substream->number] = false;
WRITE_ONCE(ff->rx_midi_substreams[substream->number], substream);
return 0;
}
static int midi_capture_close(struct snd_rawmidi_substream *substream)
{
/* Do nothing. */
return 0;
}
static int midi_playback_close(struct snd_rawmidi_substream *substream)
{
struct snd_ff *ff = substream->rmidi->private_data;
cancel_work_sync(&ff->rx_midi_work[substream->number]);
WRITE_ONCE(ff->rx_midi_substreams[substream->number], NULL);
return 0;
}
static void midi_capture_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct snd_ff *ff = substream->rmidi->private_data;
guard(spinlock_irqsave)(&ff->lock);
if (up)
WRITE_ONCE(ff->tx_midi_substreams[substream->number],
substream);
else
WRITE_ONCE(ff->tx_midi_substreams[substream->number], NULL);
}
static void midi_playback_trigger(struct snd_rawmidi_substream *substream,
int up)
{
struct snd_ff *ff = substream->rmidi->private_data;
guard(spinlock_irqsave)(&ff->lock);
if (up || !ff->rx_midi_error[substream->number])
schedule_work(&ff->rx_midi_work[substream->number]);
}
static void set_midi_substream_names(struct snd_rawmidi_str *stream,
const char *const name)
{
struct snd_rawmidi_substream *substream;
list_for_each_entry(substream, &stream->substreams, list) {
scnprintf(substream->name, sizeof(substream->name),
"%s MIDI %d", name, substream->number + 1);
}
}
int snd_ff_create_midi_devices(struct snd_ff *ff)
{
static const struct snd_rawmidi_ops midi_capture_ops = {
.open = midi_capture_open,
.close = midi_capture_close,
.trigger = midi_capture_trigger,
};
static const struct snd_rawmidi_ops midi_playback_ops = {
.open = midi_playback_open,
.close = midi_playback_close,
.trigger = midi_playback_trigger,
};
struct snd_rawmidi *rmidi;
struct snd_rawmidi_str *stream;
int err;
err = snd_rawmidi_new(ff->card, ff->card->driver, 0,
ff->spec->midi_out_ports, ff->spec->midi_in_ports,
&rmidi);
if (err < 0)
return err;
snprintf(rmidi->name, sizeof(rmidi->name),
"%s MIDI", ff->card->shortname);
rmidi->private_data = ff;
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
&midi_capture_ops);
stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT];
set_midi_substream_names(stream, ff->card->shortname);
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
&midi_playback_ops);
stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
set_midi_substream_names(stream, ff->card->shortname);
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
return 0;
}
@@ -0,0 +1,387 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ff-pcm.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*/
#include "ff.h"
static int hw_rule_rate(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
const unsigned int *pcm_channels = rule->private;
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 < ARRAY_SIZE(amdtp_rate_table); i++) {
enum snd_ff_stream_mode mode;
int err;
err = snd_ff_stream_get_multiplier_mode(i, &mode);
if (err < 0)
continue;
if (!snd_interval_test(c, pcm_channels[mode]))
continue;
t.min = min(t.min, amdtp_rate_table[i]);
t.max = max(t.max, amdtp_rate_table[i]);
}
return snd_interval_refine(r, &t);
}
static int hw_rule_channels(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
const unsigned int *pcm_channels = rule->private;
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 < ARRAY_SIZE(amdtp_rate_table); i++) {
enum snd_ff_stream_mode mode;
int err;
err = snd_ff_stream_get_multiplier_mode(i, &mode);
if (err < 0)
continue;
if (!snd_interval_test(r, amdtp_rate_table[i]))
continue;
t.min = min(t.min, pcm_channels[mode]);
t.max = max(t.max, pcm_channels[mode]);
}
return snd_interval_refine(c, &t);
}
static void limit_channels_and_rates(struct snd_pcm_hardware *hw,
const unsigned int *pcm_channels)
{
unsigned int rate, channels;
int i;
hw->channels_min = UINT_MAX;
hw->channels_max = 0;
hw->rate_min = UINT_MAX;
hw->rate_max = 0;
for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) {
enum snd_ff_stream_mode mode;
int err;
err = snd_ff_stream_get_multiplier_mode(i, &mode);
if (err < 0)
continue;
channels = pcm_channels[mode];
if (pcm_channels[mode] == 0)
continue;
hw->channels_min = min(hw->channels_min, channels);
hw->channels_max = max(hw->channels_max, channels);
rate = amdtp_rate_table[i];
hw->rates |= snd_pcm_rate_to_rate_bit(rate);
hw->rate_min = min(hw->rate_min, rate);
hw->rate_max = max(hw->rate_max, rate);
}
}
static int pcm_init_hw_params(struct snd_ff *ff,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct amdtp_stream *s;
const unsigned int *pcm_channels;
int err;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
runtime->hw.formats = SNDRV_PCM_FMTBIT_S32;
s = &ff->tx_stream;
pcm_channels = ff->spec->pcm_capture_channels;
} else {
runtime->hw.formats = SNDRV_PCM_FMTBIT_S32;
s = &ff->rx_stream;
pcm_channels = ff->spec->pcm_playback_channels;
}
limit_channels_and_rates(&runtime->hw, pcm_channels);
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
hw_rule_channels, (void *)pcm_channels,
SNDRV_PCM_HW_PARAM_RATE, -1);
if (err < 0)
return err;
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
hw_rule_rate, (void *)pcm_channels,
SNDRV_PCM_HW_PARAM_CHANNELS, -1);
if (err < 0)
return err;
return amdtp_ff_add_pcm_hw_constraints(s, runtime);
}
static int pcm_open(struct snd_pcm_substream *substream)
{
struct snd_ff *ff = substream->private_data;
struct amdtp_domain *d = &ff->domain;
unsigned int rate;
enum snd_ff_clock_src src;
int i, err;
err = snd_ff_stream_lock_try(ff);
if (err < 0)
return err;
err = pcm_init_hw_params(ff, substream);
if (err < 0)
goto release_lock;
err = ff->spec->protocol->get_clock(ff, &rate, &src);
if (err < 0)
goto release_lock;
scoped_guard(mutex, &ff->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 (src != SND_FF_CLOCK_SRC_INTERNAL) {
for (i = 0; i < CIP_SFC_COUNT; ++i) {
if (amdtp_rate_table[i] == rate)
break;
}
// The unit is configured at sampling frequency which packet
// streaming engine can't support.
if (i >= CIP_SFC_COUNT) {
err = -EIO;
goto release_lock;
}
substream->runtime->hw.rate_min = rate;
substream->runtime->hw.rate_max = rate;
} else {
if (ff->substreams_counter > 0) {
unsigned int frames_per_period = d->events_per_period;
unsigned int frames_per_buffer = d->events_per_buffer;
rate = amdtp_rate_table[ff->rx_stream.sfc];
substream->runtime->hw.rate_min = rate;
substream->runtime->hw.rate_max = rate;
err = snd_pcm_hw_constraint_minmax(substream->runtime,
SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
frames_per_period, frames_per_period);
if (err < 0)
goto release_lock;
err = snd_pcm_hw_constraint_minmax(substream->runtime,
SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
frames_per_buffer, frames_per_buffer);
if (err < 0)
goto release_lock;
}
}
}
snd_pcm_set_sync(substream);
return 0;
release_lock:
snd_ff_stream_lock_release(ff);
return err;
}
static int pcm_close(struct snd_pcm_substream *substream)
{
struct snd_ff *ff = substream->private_data;
snd_ff_stream_lock_release(ff);
return 0;
}
static int pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_ff *ff = 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)(&ff->mutex);
err = snd_ff_stream_reserve_duplex(ff, rate, frames_per_period,
frames_per_buffer);
if (err >= 0)
++ff->substreams_counter;
}
return err;
}
static int pcm_hw_free(struct snd_pcm_substream *substream)
{
struct snd_ff *ff = substream->private_data;
guard(mutex)(&ff->mutex);
if (substream->runtime->state != SNDRV_PCM_STATE_OPEN)
--ff->substreams_counter;
snd_ff_stream_stop_duplex(ff);
return 0;
}
static int pcm_capture_prepare(struct snd_pcm_substream *substream)
{
struct snd_ff *ff = substream->private_data;
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
guard(mutex)(&ff->mutex);
err = snd_ff_stream_start_duplex(ff, runtime->rate);
if (err >= 0)
amdtp_stream_pcm_prepare(&ff->tx_stream);
return err;
}
static int pcm_playback_prepare(struct snd_pcm_substream *substream)
{
struct snd_ff *ff = substream->private_data;
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
guard(mutex)(&ff->mutex);
err = snd_ff_stream_start_duplex(ff, runtime->rate);
if (err >= 0)
amdtp_stream_pcm_prepare(&ff->rx_stream);
return err;
}
static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_ff *ff = substream->private_data;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
amdtp_stream_pcm_trigger(&ff->tx_stream, substream);
break;
case SNDRV_PCM_TRIGGER_STOP:
amdtp_stream_pcm_trigger(&ff->tx_stream, NULL);
break;
default:
return -EINVAL;
}
return 0;
}
static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_ff *ff = substream->private_data;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
amdtp_stream_pcm_trigger(&ff->rx_stream, substream);
break;
case SNDRV_PCM_TRIGGER_STOP:
amdtp_stream_pcm_trigger(&ff->rx_stream, NULL);
break;
default:
return -EINVAL;
}
return 0;
}
static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
{
struct snd_ff *ff = sbstrm->private_data;
return amdtp_domain_stream_pcm_pointer(&ff->domain, &ff->tx_stream);
}
static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
{
struct snd_ff *ff = sbstrm->private_data;
return amdtp_domain_stream_pcm_pointer(&ff->domain, &ff->rx_stream);
}
static int pcm_capture_ack(struct snd_pcm_substream *substream)
{
struct snd_ff *ff = substream->private_data;
return amdtp_domain_stream_pcm_ack(&ff->domain, &ff->tx_stream);
}
static int pcm_playback_ack(struct snd_pcm_substream *substream)
{
struct snd_ff *ff = substream->private_data;
return amdtp_domain_stream_pcm_ack(&ff->domain, &ff->rx_stream);
}
int snd_ff_create_pcm_devices(struct snd_ff *ff)
{
static const struct snd_pcm_ops pcm_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 pcm_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(ff->card, ff->card->driver, 0, 1, 1, &pcm);
if (err < 0)
return err;
pcm->private_data = ff;
pcm->nonatomic = true;
snprintf(pcm->name, sizeof(pcm->name),
"%s PCM", ff->card->shortname);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops);
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0);
return 0;
}
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ff-proc.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*/
#include "./ff.h"
const char *snd_ff_proc_get_clk_label(enum snd_ff_clock_src src)
{
static const char *const labels[] = {
"Internal",
"S/PDIF",
"ADAT1",
"ADAT2",
"Word",
"LTC",
};
if (src >= ARRAY_SIZE(labels))
return NULL;
return labels[src];
}
static void proc_dump_status(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_ff *ff = entry->private_data;
ff->spec->protocol->dump_status(ff, buffer);
}
static void add_node(struct snd_ff *ff, struct snd_info_entry *root,
const char *name,
void (*op)(struct snd_info_entry *e,
struct snd_info_buffer *b))
{
struct snd_info_entry *entry;
entry = snd_info_create_card_entry(ff->card, name, root);
if (entry)
snd_info_set_text_ops(entry, ff, op);
}
void snd_ff_proc_init(struct snd_ff *ff)
{
struct snd_info_entry *root;
/*
* All nodes are automatically removed at snd_card_disconnect(),
* by following to link list.
*/
root = snd_info_create_card_entry(ff->card, "firewire",
ff->card->proc_root);
if (root == NULL)
return;
root->mode = S_IFDIR | 0555;
add_node(ff, root, "status", proc_dump_status);
}
@@ -0,0 +1,733 @@
// SPDX-License-Identifier: GPL-2.0
// ff-protocol-former.c - a part of driver for RME Fireface series
//
// Copyright (c) 2019 Takashi Sakamoto
#include <linux/delay.h>
#include "ff.h"
#define FORMER_REG_SYNC_STATUS 0x0000801c0000ull
/* For block write request. */
#define FORMER_REG_FETCH_PCM_FRAMES 0x0000801c0000ull
#define FORMER_REG_CLOCK_CONFIG 0x0000801c0004ull
static int parse_clock_bits(u32 data, unsigned int *rate,
enum snd_ff_clock_src *src)
{
static const struct {
unsigned int rate;
u32 mask;
} *rate_entry, rate_entries[] = {
{ 32000, 0x00000002, },
{ 44100, 0x00000000, },
{ 48000, 0x00000006, },
{ 64000, 0x0000000a, },
{ 88200, 0x00000008, },
{ 96000, 0x0000000e, },
{ 128000, 0x00000012, },
{ 176400, 0x00000010, },
{ 192000, 0x00000016, },
};
static const struct {
enum snd_ff_clock_src src;
u32 mask;
} *clk_entry, clk_entries[] = {
{ SND_FF_CLOCK_SRC_ADAT1, 0x00000000, },
{ SND_FF_CLOCK_SRC_ADAT2, 0x00000400, },
{ SND_FF_CLOCK_SRC_SPDIF, 0x00000c00, },
{ SND_FF_CLOCK_SRC_WORD, 0x00001000, },
{ SND_FF_CLOCK_SRC_LTC, 0x00001800, },
};
int i;
for (i = 0; i < ARRAY_SIZE(rate_entries); ++i) {
rate_entry = rate_entries + i;
if ((data & 0x0000001e) == rate_entry->mask) {
*rate = rate_entry->rate;
break;
}
}
if (i == ARRAY_SIZE(rate_entries))
return -EIO;
if (data & 0x00000001) {
*src = SND_FF_CLOCK_SRC_INTERNAL;
} else {
for (i = 0; i < ARRAY_SIZE(clk_entries); ++i) {
clk_entry = clk_entries + i;
if ((data & 0x00001c00) == clk_entry->mask) {
*src = clk_entry->src;
break;
}
}
if (i == ARRAY_SIZE(clk_entries))
return -EIO;
}
return 0;
}
static int former_get_clock(struct snd_ff *ff, unsigned int *rate,
enum snd_ff_clock_src *src)
{
__le32 reg;
u32 data;
int err;
err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST,
FORMER_REG_CLOCK_CONFIG, &reg, sizeof(reg), 0);
if (err < 0)
return err;
data = le32_to_cpu(reg);
return parse_clock_bits(data, rate, src);
}
static int former_switch_fetching_mode(struct snd_ff *ff, bool enable)
{
unsigned int count;
__le32 *reg;
int i;
int err;
count = 0;
for (i = 0; i < SND_FF_STREAM_MODE_COUNT; ++i)
count = max(count, ff->spec->pcm_playback_channels[i]);
reg = kcalloc(count, sizeof(__le32), GFP_KERNEL);
if (!reg)
return -ENOMEM;
if (!enable) {
/*
* Each quadlet is corresponding to data channels in a data
* blocks in reverse order. Precisely, quadlets for available
* data channels should be enabled. Here, I take second best
* to fetch PCM frames from all of data channels regardless of
* stf.
*/
for (i = 0; i < count; ++i)
reg[i] = cpu_to_le32(0x00000001);
}
err = snd_fw_transaction(ff->unit, TCODE_WRITE_BLOCK_REQUEST,
FORMER_REG_FETCH_PCM_FRAMES, reg,
sizeof(__le32) * count, 0);
kfree(reg);
return err;
}
static void dump_clock_config(struct snd_ff *ff, struct snd_info_buffer *buffer)
{
__le32 reg;
u32 data;
unsigned int rate;
enum snd_ff_clock_src src;
const char *label;
int err;
err = snd_fw_transaction(ff->unit, TCODE_READ_BLOCK_REQUEST,
FORMER_REG_CLOCK_CONFIG, &reg, sizeof(reg), 0);
if (err < 0)
return;
data = le32_to_cpu(reg);
snd_iprintf(buffer, "Output S/PDIF format: %s (Emphasis: %s)\n",
(data & 0x00000020) ? "Professional" : "Consumer",
str_on_off(data & 0x00000040));
snd_iprintf(buffer, "Optical output interface format: %s\n",
(data & 0x00000100) ? "S/PDIF" : "ADAT");
snd_iprintf(buffer, "Word output single speed: %s\n",
str_on_off(data & 0x00002000));
snd_iprintf(buffer, "S/PDIF input interface: %s\n",
(data & 0x00000200) ? "Optical" : "Coaxial");
err = parse_clock_bits(data, &rate, &src);
if (err < 0)
return;
label = snd_ff_proc_get_clk_label(src);
if (!label)
return;
snd_iprintf(buffer, "Clock configuration: %d %s\n", rate, label);
}
static void dump_sync_status(struct snd_ff *ff, struct snd_info_buffer *buffer)
{
static const struct {
char *const label;
u32 locked_mask;
u32 synced_mask;
} *clk_entry, clk_entries[] = {
{ "WDClk", 0x40000000, 0x20000000, },
{ "S/PDIF", 0x00080000, 0x00040000, },
{ "ADAT1", 0x00000400, 0x00001000, },
{ "ADAT2", 0x00000800, 0x00002000, },
};
static const struct {
char *const label;
u32 mask;
} *referred_entry, referred_entries[] = {
{ "ADAT1", 0x00000000, },
{ "ADAT2", 0x00400000, },
{ "S/PDIF", 0x00c00000, },
{ "WDclk", 0x01000000, },
{ "TCO", 0x01400000, },
};
static const struct {
unsigned int rate;
u32 mask;
} *rate_entry, rate_entries[] = {
{ 32000, 0x02000000, },
{ 44100, 0x04000000, },
{ 48000, 0x06000000, },
{ 64000, 0x08000000, },
{ 88200, 0x0a000000, },
{ 96000, 0x0c000000, },
{ 128000, 0x0e000000, },
{ 176400, 0x10000000, },
{ 192000, 0x12000000, },
};
__le32 reg[2];
u32 data[2];
int i;
int err;
err = snd_fw_transaction(ff->unit, TCODE_READ_BLOCK_REQUEST,
FORMER_REG_SYNC_STATUS, reg, sizeof(reg), 0);
if (err < 0)
return;
data[0] = le32_to_cpu(reg[0]);
data[1] = le32_to_cpu(reg[1]);
snd_iprintf(buffer, "External source detection:\n");
for (i = 0; i < ARRAY_SIZE(clk_entries); ++i) {
const char *state;
clk_entry = clk_entries + i;
if (data[0] & clk_entry->locked_mask) {
if (data[0] & clk_entry->synced_mask)
state = "sync";
else
state = "lock";
} else {
state = "none";
}
snd_iprintf(buffer, "%s: %s\n", clk_entry->label, state);
}
snd_iprintf(buffer, "Referred clock:\n");
if (data[1] & 0x00000001) {
snd_iprintf(buffer, "Internal\n");
} else {
unsigned int rate;
const char *label;
for (i = 0; i < ARRAY_SIZE(referred_entries); ++i) {
referred_entry = referred_entries + i;
if ((data[0] & 0x1e0000) == referred_entry->mask) {
label = referred_entry->label;
break;
}
}
if (i == ARRAY_SIZE(referred_entries))
label = "none";
for (i = 0; i < ARRAY_SIZE(rate_entries); ++i) {
rate_entry = rate_entries + i;
if ((data[0] & 0x1e000000) == rate_entry->mask) {
rate = rate_entry->rate;
break;
}
}
if (i == ARRAY_SIZE(rate_entries))
rate = 0;
snd_iprintf(buffer, "%s %d\n", label, rate);
}
}
static void former_dump_status(struct snd_ff *ff,
struct snd_info_buffer *buffer)
{
dump_clock_config(ff, buffer);
dump_sync_status(ff, buffer);
}
static int former_fill_midi_msg(struct snd_ff *ff,
struct snd_rawmidi_substream *substream,
unsigned int port)
{
u8 *buf = (u8 *)ff->msg_buf[port];
int len;
int i;
len = snd_rawmidi_transmit_peek(substream, buf,
SND_FF_MAXIMIM_MIDI_QUADS);
if (len <= 0)
return len;
// One quadlet includes one byte.
for (i = len - 1; i >= 0; --i)
ff->msg_buf[port][i] = cpu_to_le32(buf[i]);
ff->rx_bytes[port] = len;
return len;
}
#define FF800_STF 0x0000fc88f000
#define FF800_RX_PACKET_FORMAT 0x0000fc88f004
#define FF800_ALLOC_TX_STREAM 0x0000fc88f008
#define FF800_ISOC_COMM_START 0x0000fc88f00c
#define FF800_TX_S800_FLAG 0x00000800
#define FF800_ISOC_COMM_STOP 0x0000fc88f010
#define FF800_TX_PACKET_ISOC_CH 0x0000801c0008
static int allocate_tx_resources(struct snd_ff *ff)
{
__le32 reg;
unsigned int count;
unsigned int tx_isoc_channel;
int err;
reg = cpu_to_le32(ff->tx_stream.data_block_quadlets);
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
FF800_ALLOC_TX_STREAM, &reg, sizeof(reg), 0);
if (err < 0)
return err;
// Wait till the format of tx packet is available.
count = 0;
while (count++ < 10) {
u32 data;
err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST,
FF800_TX_PACKET_ISOC_CH, &reg, sizeof(reg), 0);
if (err < 0)
return err;
data = le32_to_cpu(reg);
if (data != 0xffffffff) {
tx_isoc_channel = data;
break;
}
msleep(50);
}
if (count >= 10)
return -ETIMEDOUT;
// NOTE: this is a makeshift to start OHCI 1394 IR context in the
// channel. On the other hand, 'struct fw_iso_resources.allocated' is
// not true and it's not deallocated at stop.
ff->tx_resources.channel = tx_isoc_channel;
return 0;
}
static int ff800_allocate_resources(struct snd_ff *ff, unsigned int rate)
{
u32 data;
__le32 reg;
int err;
reg = cpu_to_le32(rate);
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
FF800_STF, &reg, sizeof(reg), 0);
if (err < 0)
return err;
// If starting isochronous communication immediately, change of STF has
// no effect. In this case, the communication runs based on former STF.
// Let's sleep for a bit.
msleep(100);
// Controllers should allocate isochronous resources for rx stream.
err = fw_iso_resources_allocate(&ff->rx_resources,
amdtp_stream_get_max_payload(&ff->rx_stream),
fw_parent_device(ff->unit)->max_speed);
if (err < 0)
return err;
// Set isochronous channel and the number of quadlets of rx packets.
// This should be done before the allocation of tx resources to avoid
// periodical noise.
data = ff->rx_stream.data_block_quadlets << 3;
data = (data << 8) | ff->rx_resources.channel;
reg = cpu_to_le32(data);
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
FF800_RX_PACKET_FORMAT, &reg, sizeof(reg), 0);
if (err < 0)
return err;
return allocate_tx_resources(ff);
}
static int ff800_begin_session(struct snd_ff *ff, unsigned int rate)
{
unsigned int generation = ff->rx_resources.generation;
__le32 reg;
if (generation != fw_parent_device(ff->unit)->card->generation) {
int err = fw_iso_resources_update(&ff->rx_resources);
if (err < 0)
return err;
}
reg = cpu_to_le32(0x80000000);
reg |= cpu_to_le32(ff->tx_stream.data_block_quadlets);
if (fw_parent_device(ff->unit)->max_speed == SCODE_800)
reg |= cpu_to_le32(FF800_TX_S800_FLAG);
return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
FF800_ISOC_COMM_START, &reg, sizeof(reg), 0);
}
static void ff800_finish_session(struct snd_ff *ff)
{
__le32 reg;
reg = cpu_to_le32(0x80000000);
snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
FF800_ISOC_COMM_STOP, &reg, sizeof(reg), 0);
}
// Fireface 800 doesn't allow drivers to register lower 4 bytes of destination
// address.
// A write transaction to clear registered higher 4 bytes of destination address
// has an effect to suppress asynchronous transaction from device.
static void ff800_handle_midi_msg(struct snd_ff *ff, unsigned int offset, const __le32 *buf,
size_t length, u32 tstamp)
{
int i;
for (i = 0; i < length / 4; i++) {
u8 byte = le32_to_cpu(buf[i]) & 0xff;
struct snd_rawmidi_substream *substream;
substream = READ_ONCE(ff->tx_midi_substreams[0]);
if (substream)
snd_rawmidi_receive(substream, &byte, 1);
}
}
const struct snd_ff_protocol snd_ff_protocol_ff800 = {
.handle_msg = ff800_handle_midi_msg,
.fill_midi_msg = former_fill_midi_msg,
.get_clock = former_get_clock,
.switch_fetching_mode = former_switch_fetching_mode,
.allocate_resources = ff800_allocate_resources,
.begin_session = ff800_begin_session,
.finish_session = ff800_finish_session,
.dump_status = former_dump_status,
};
#define FF400_STF 0x000080100500ull
#define FF400_RX_PACKET_FORMAT 0x000080100504ull
#define FF400_ISOC_COMM_START 0x000080100508ull
#define FF400_TX_PACKET_FORMAT 0x00008010050cull
#define FF400_ISOC_COMM_STOP 0x000080100510ull
// Fireface 400 manages isochronous channel number in 3 bit field. Therefore,
// we can allocate between 0 and 7 channel.
static int ff400_allocate_resources(struct snd_ff *ff, unsigned int rate)
{
__le32 reg;
enum snd_ff_stream_mode mode;
int i;
int err;
// Check whether the given value is supported or not.
for (i = 0; i < CIP_SFC_COUNT; i++) {
if (amdtp_rate_table[i] == rate)
break;
}
if (i >= CIP_SFC_COUNT)
return -EINVAL;
// Set the number of data blocks transferred in a second.
reg = cpu_to_le32(rate);
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
FF400_STF, &reg, sizeof(reg), 0);
if (err < 0)
return err;
msleep(100);
err = snd_ff_stream_get_multiplier_mode(i, &mode);
if (err < 0)
return err;
// Keep resources for in-stream.
ff->tx_resources.channels_mask = 0x00000000000000ffuLL;
err = fw_iso_resources_allocate(&ff->tx_resources,
amdtp_stream_get_max_payload(&ff->tx_stream),
fw_parent_device(ff->unit)->max_speed);
if (err < 0)
return err;
// Keep resources for out-stream.
ff->rx_resources.channels_mask = 0x00000000000000ffuLL;
err = fw_iso_resources_allocate(&ff->rx_resources,
amdtp_stream_get_max_payload(&ff->rx_stream),
fw_parent_device(ff->unit)->max_speed);
if (err < 0)
fw_iso_resources_free(&ff->tx_resources);
return err;
}
static int ff400_begin_session(struct snd_ff *ff, unsigned int rate)
{
unsigned int generation = ff->rx_resources.generation;
__le32 reg;
int err;
if (generation != fw_parent_device(ff->unit)->card->generation) {
err = fw_iso_resources_update(&ff->tx_resources);
if (err < 0)
return err;
err = fw_iso_resources_update(&ff->rx_resources);
if (err < 0)
return err;
}
// Set isochronous channel and the number of quadlets of received
// packets.
reg = cpu_to_le32(((ff->rx_stream.data_block_quadlets << 3) << 8) |
ff->rx_resources.channel);
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
FF400_RX_PACKET_FORMAT, &reg, sizeof(reg), 0);
if (err < 0)
return err;
// Set isochronous channel and the number of quadlets of transmitted
// packet.
// TODO: investigate the purpose of this 0x80.
reg = cpu_to_le32((0x80 << 24) |
(ff->tx_resources.channel << 5) |
(ff->tx_stream.data_block_quadlets));
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
FF400_TX_PACKET_FORMAT, &reg, sizeof(reg), 0);
if (err < 0)
return err;
// Allow to transmit packets.
reg = cpu_to_le32(0x00000001);
return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
FF400_ISOC_COMM_START, &reg, sizeof(reg), 0);
}
static void ff400_finish_session(struct snd_ff *ff)
{
__le32 reg;
reg = cpu_to_le32(0x80000000);
snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
FF400_ISOC_COMM_STOP, &reg, sizeof(reg), 0);
}
static void parse_midi_msg(struct snd_ff *ff, u32 quad, unsigned int port)
{
struct snd_rawmidi_substream *substream = READ_ONCE(ff->tx_midi_substreams[port]);
if (substream != NULL) {
u8 byte = (quad >> (16 * port)) & 0x000000ff;
snd_rawmidi_receive(substream, &byte, 1);
}
}
#define FF400_QUEUE_SIZE 32
struct ff400_msg_parser {
struct {
u32 msg;
u32 tstamp;
} msgs[FF400_QUEUE_SIZE];
size_t push_pos;
size_t pull_pos;
};
static bool ff400_has_msg(struct snd_ff *ff)
{
struct ff400_msg_parser *parser = ff->msg_parser;
return (parser->push_pos != parser->pull_pos);
}
// For Fireface 400, lower 4 bytes of destination address is configured by bit
// flag in quadlet register (little endian) at 0x'0000'801'0051c. Drivers can
// select one of 4 options:
//
// bit flags: offset of destination address
// - 0x04000000: 0x'....'....'0000'0000
// - 0x08000000: 0x'....'....'0000'0080
// - 0x10000000: 0x'....'....'0000'0100
// - 0x20000000: 0x'....'....'0000'0180
//
// Drivers can suppress the device to transfer asynchronous transactions by
// using below 2 bits.
// - 0x01000000: suppress transmission
// - 0x02000000: suppress transmission
//
// Actually, the register is write-only and includes the other options such as
// input attenuation. This driver allocates destination address with '0000'0000
// in its lower offset and expects userspace application to configure the
// register for it.
// When the message is for signal level operation, the upper 4 bits in MSB expresses the pair of
// stereo physical port.
// - 0: Microphone input 0/1
// - 1: Line input 0/1
// - [2-4]: Line output 0-5
// - 5: Headphone output 0/1
// - 6: S/PDIF output 0/1
// - [7-10]: ADAT output 0-7
//
// The value of signal level can be detected by mask of 0x00fffc00. For signal level of microphone
// input:
//
// - 0: 0.0 dB
// - 10: +10.0 dB
// - 11: +11.0 dB
// - 12: +12.0 dB
// - ...
// - 63: +63.0 dB:
// - 64: +64.0 dB:
// - 65: +65.0 dB:
//
// For signal level of line input:
//
// - 0: 0.0 dB
// - 1: +0.5 dB
// - 2: +1.0 dB
// - 3: +1.5 dB
// - ...
// - 34: +17.0 dB:
// - 35: +17.5 dB:
// - 36: +18.0 dB:
//
// For signal level of any type of output:
//
// - 63: -infinite
// - 62: -58.0 dB
// - 61: -56.0 dB
// - 60: -54.0 dB
// - 59: -53.0 dB
// - 58: -52.0 dB
// - ...
// - 7: -1.0 dB
// - 6: 0.0 dB
// - 5: +1.0 dB
// - ...
// - 2: +4.0 dB
// - 1: +5.0 dB
// - 0: +6.0 dB
//
// When the message is not for signal level operation, it's for MIDI bytes. When matching to
// FF400_MSG_FLAG_IS_MIDI_PORT_0, one MIDI byte can be detected by mask of 0x000000ff. When
// matching to FF400_MSG_FLAG_IS_MIDI_PORT_1, one MIDI byte can be detected by mask of 0x00ff0000.
#define FF400_MSG_FLAG_IS_SIGNAL_LEVEL 0x04000000
#define FF400_MSG_FLAG_IS_RIGHT_CHANNEL 0x08000000
#define FF400_MSG_FLAG_IS_STEREO_PAIRED 0x02000000
#define FF400_MSG_MASK_STEREO_PAIR 0xf0000000
#define FF400_MSG_MASK_SIGNAL_LEVEL 0x00fffc00
#define FF400_MSG_FLAG_IS_MIDI_PORT_0 0x00000100
#define FF400_MSG_MASK_MIDI_PORT_0 0x000000ff
#define FF400_MSG_FLAG_IS_MIDI_PORT_1 0x01000000
#define FF400_MSG_MASK_MIDI_PORT_1 0x00ff0000
static void ff400_handle_msg(struct snd_ff *ff, unsigned int offset, const __le32 *buf,
size_t length, u32 tstamp)
{
bool need_hwdep_wake_up = false;
int i;
for (i = 0; i < length / 4; i++) {
u32 quad = le32_to_cpu(buf[i]);
if (quad & FF400_MSG_FLAG_IS_SIGNAL_LEVEL) {
struct ff400_msg_parser *parser = ff->msg_parser;
parser->msgs[parser->push_pos].msg = quad;
parser->msgs[parser->push_pos].tstamp = tstamp;
++parser->push_pos;
if (parser->push_pos >= FF400_QUEUE_SIZE)
parser->push_pos = 0;
need_hwdep_wake_up = true;
} else if (quad & FF400_MSG_FLAG_IS_MIDI_PORT_0) {
parse_midi_msg(ff, quad, 0);
} else if (quad & FF400_MSG_FLAG_IS_MIDI_PORT_1) {
parse_midi_msg(ff, quad, 1);
}
}
if (need_hwdep_wake_up)
wake_up(&ff->hwdep_wait);
}
static long ff400_copy_msg_to_user(struct snd_ff *ff, char __user *buf, long count)
{
struct snd_firewire_event_ff400_message ev = {
.type = SNDRV_FIREWIRE_EVENT_FF400_MESSAGE,
.message_count = 0,
};
struct ff400_msg_parser *parser = ff->msg_parser;
long consumed = 0;
long ret = 0;
if (count < sizeof(ev) || parser->pull_pos == parser->push_pos)
return 0;
count -= sizeof(ev);
consumed += sizeof(ev);
while (count >= sizeof(*parser->msgs) && parser->pull_pos != parser->push_pos) {
spin_unlock_irq(&ff->lock);
if (copy_to_user(buf + consumed, parser->msgs + parser->pull_pos,
sizeof(*parser->msgs)))
ret = -EFAULT;
spin_lock_irq(&ff->lock);
if (ret)
return ret;
++parser->pull_pos;
if (parser->pull_pos >= FF400_QUEUE_SIZE)
parser->pull_pos = 0;
++ev.message_count;
count -= sizeof(*parser->msgs);
consumed += sizeof(*parser->msgs);
}
spin_unlock_irq(&ff->lock);
if (copy_to_user(buf, &ev, sizeof(ev)))
ret = -EFAULT;
spin_lock_irq(&ff->lock);
if (ret)
return ret;
return consumed;
}
const struct snd_ff_protocol snd_ff_protocol_ff400 = {
.msg_parser_size = sizeof(struct ff400_msg_parser),
.has_msg = ff400_has_msg,
.copy_msg_to_user = ff400_copy_msg_to_user,
.handle_msg = ff400_handle_msg,
.fill_midi_msg = former_fill_midi_msg,
.get_clock = former_get_clock,
.switch_fetching_mode = former_switch_fetching_mode,
.allocate_resources = ff400_allocate_resources,
.begin_session = ff400_begin_session,
.finish_session = ff400_finish_session,
.dump_status = former_dump_status,
};
@@ -0,0 +1,540 @@
// SPDX-License-Identifier: GPL-2.0
// ff-protocol-latter.c - a part of driver for RME Fireface series
//
// Copyright (c) 2019 Takashi Sakamoto
#include <linux/delay.h>
#include "ff.h"
#define LATTER_STF 0xffff00000004ULL
#define LATTER_ISOC_CHANNELS 0xffff00000008ULL
#define LATTER_ISOC_START 0xffff0000000cULL
#define LATTER_FETCH_MODE 0xffff00000010ULL
#define LATTER_SYNC_STATUS 0x0000801c0000ULL
// The content of sync status register differs between models.
//
// Fireface UCX:
// 0xf0000000: (unidentified)
// 0x0f000000: effective rate of sampling clock
// 0x00f00000: detected rate of word clock on BNC interface
// 0x000f0000: detected rate of ADAT or S/PDIF on optical interface
// 0x0000f000: detected rate of S/PDIF on coaxial interface
// 0x00000e00: effective source of sampling clock
// 0x00000e00: Internal
// 0x00000800: (unidentified)
// 0x00000600: Word clock on BNC interface
// 0x00000400: ADAT on optical interface
// 0x00000200: S/PDIF on coaxial or optical interface
// 0x00000100: Optical interface is used for ADAT signal
// 0x00000080: (unidentified)
// 0x00000040: Synchronized to word clock on BNC interface
// 0x00000020: Synchronized to ADAT or S/PDIF on optical interface
// 0x00000010: Synchronized to S/PDIF on coaxial interface
// 0x00000008: (unidentified)
// 0x00000004: Lock word clock on BNC interface
// 0x00000002: Lock ADAT or S/PDIF on optical interface
// 0x00000001: Lock S/PDIF on coaxial interface
//
// Fireface 802 (and perhaps UFX):
// 0xf0000000: effective rate of sampling clock
// 0x0f000000: detected rate of ADAT-B on 2nd optical interface
// 0x00f00000: detected rate of ADAT-A on 1st optical interface
// 0x000f0000: detected rate of AES/EBU on XLR or coaxial interface
// 0x0000f000: detected rate of word clock on BNC interface
// 0x00000e00: effective source of sampling clock
// 0x00000e00: internal
// 0x00000800: ADAT-B
// 0x00000600: ADAT-A
// 0x00000400: AES/EBU
// 0x00000200: Word clock
// 0x00000080: Synchronized to ADAT-B on 2nd optical interface
// 0x00000040: Synchronized to ADAT-A on 1st optical interface
// 0x00000020: Synchronized to AES/EBU on XLR or 2nd optical interface
// 0x00000010: Synchronized to word clock on BNC interface
// 0x00000008: Lock ADAT-B on 2nd optical interface
// 0x00000004: Lock ADAT-A on 1st optical interface
// 0x00000002: Lock AES/EBU on XLR or 2nd optical interface
// 0x00000001: Lock word clock on BNC interface
//
// The pattern for rate bits:
// 0x00: 32.0 kHz
// 0x01: 44.1 kHz
// 0x02: 48.0 kHz
// 0x04: 64.0 kHz
// 0x05: 88.2 kHz
// 0x06: 96.0 kHz
// 0x08: 128.0 kHz
// 0x09: 176.4 kHz
// 0x0a: 192.0 kHz
static int parse_clock_bits(u32 data, unsigned int *rate,
enum snd_ff_clock_src *src,
enum snd_ff_unit_version unit_version)
{
static const struct {
unsigned int rate;
u32 flag;
} *rate_entry, rate_entries[] = {
{ 32000, 0x00, },
{ 44100, 0x01, },
{ 48000, 0x02, },
{ 64000, 0x04, },
{ 88200, 0x05, },
{ 96000, 0x06, },
{ 128000, 0x08, },
{ 176400, 0x09, },
{ 192000, 0x0a, },
};
static const struct {
enum snd_ff_clock_src src;
u32 flag;
} *clk_entry, *clk_entries, ucx_clk_entries[] = {
{ SND_FF_CLOCK_SRC_SPDIF, 0x00000200, },
{ SND_FF_CLOCK_SRC_ADAT1, 0x00000400, },
{ SND_FF_CLOCK_SRC_WORD, 0x00000600, },
{ SND_FF_CLOCK_SRC_INTERNAL, 0x00000e00, },
}, ufx_ff802_clk_entries[] = {
{ SND_FF_CLOCK_SRC_WORD, 0x00000200, },
{ SND_FF_CLOCK_SRC_SPDIF, 0x00000400, },
{ SND_FF_CLOCK_SRC_ADAT1, 0x00000600, },
{ SND_FF_CLOCK_SRC_ADAT2, 0x00000800, },
{ SND_FF_CLOCK_SRC_INTERNAL, 0x00000e00, },
};
u32 rate_bits;
unsigned int clk_entry_count;
int i;
if (unit_version == SND_FF_UNIT_VERSION_UCX) {
rate_bits = (data & 0x0f000000) >> 24;
clk_entries = ucx_clk_entries;
clk_entry_count = ARRAY_SIZE(ucx_clk_entries);
} else {
rate_bits = (data & 0xf0000000) >> 28;
clk_entries = ufx_ff802_clk_entries;
clk_entry_count = ARRAY_SIZE(ufx_ff802_clk_entries);
}
for (i = 0; i < ARRAY_SIZE(rate_entries); ++i) {
rate_entry = rate_entries + i;
if (rate_bits == rate_entry->flag) {
*rate = rate_entry->rate;
break;
}
}
if (i == ARRAY_SIZE(rate_entries))
return -EIO;
for (i = 0; i < clk_entry_count; ++i) {
clk_entry = clk_entries + i;
if ((data & 0x000e00) == clk_entry->flag) {
*src = clk_entry->src;
break;
}
}
if (i == clk_entry_count)
return -EIO;
return 0;
}
static int latter_get_clock(struct snd_ff *ff, unsigned int *rate,
enum snd_ff_clock_src *src)
{
__le32 reg;
u32 data;
int err;
err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST,
LATTER_SYNC_STATUS, &reg, sizeof(reg), 0);
if (err < 0)
return err;
data = le32_to_cpu(reg);
return parse_clock_bits(data, rate, src, ff->unit_version);
}
static int latter_switch_fetching_mode(struct snd_ff *ff, bool enable)
{
u32 data;
__le32 reg;
if (enable)
data = 0x00000000;
else
data = 0xffffffff;
reg = cpu_to_le32(data);
return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
LATTER_FETCH_MODE, &reg, sizeof(reg), 0);
}
static int latter_allocate_resources(struct snd_ff *ff, unsigned int rate)
{
enum snd_ff_stream_mode mode;
unsigned int code;
__le32 reg;
unsigned int count;
int i;
int err;
// Set the number of data blocks transferred in a second.
if (rate % 48000 == 0)
code = 0x04;
else if (rate % 44100 == 0)
code = 0x02;
else if (rate % 32000 == 0)
code = 0x00;
else
return -EINVAL;
if (rate >= 64000 && rate < 128000)
code |= 0x08;
else if (rate >= 128000)
code |= 0x10;
reg = cpu_to_le32(code);
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
LATTER_STF, &reg, sizeof(reg), 0);
if (err < 0)
return err;
// Confirm to shift transmission clock.
count = 0;
while (count++ < 10) {
unsigned int curr_rate;
enum snd_ff_clock_src src;
err = latter_get_clock(ff, &curr_rate, &src);
if (err < 0)
return err;
if (curr_rate == rate)
break;
}
if (count > 10)
return -ETIMEDOUT;
for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); ++i) {
if (rate == amdtp_rate_table[i])
break;
}
if (i == ARRAY_SIZE(amdtp_rate_table))
return -EINVAL;
err = snd_ff_stream_get_multiplier_mode(i, &mode);
if (err < 0)
return err;
// Keep resources for in-stream.
ff->tx_resources.channels_mask = 0x00000000000000ffuLL;
err = fw_iso_resources_allocate(&ff->tx_resources,
amdtp_stream_get_max_payload(&ff->tx_stream),
fw_parent_device(ff->unit)->max_speed);
if (err < 0)
return err;
// Keep resources for out-stream.
ff->rx_resources.channels_mask = 0x00000000000000ffuLL;
err = fw_iso_resources_allocate(&ff->rx_resources,
amdtp_stream_get_max_payload(&ff->rx_stream),
fw_parent_device(ff->unit)->max_speed);
if (err < 0)
fw_iso_resources_free(&ff->tx_resources);
return err;
}
static int latter_begin_session(struct snd_ff *ff, unsigned int rate)
{
unsigned int generation = ff->rx_resources.generation;
unsigned int flag;
u32 data;
__le32 reg;
int err;
if (ff->unit_version == SND_FF_UNIT_VERSION_UCX) {
// For Fireface UCX. Always use the maximum number of data
// channels in data block of packet.
if (rate >= 32000 && rate <= 48000)
flag = 0x92;
else if (rate >= 64000 && rate <= 96000)
flag = 0x8e;
else if (rate >= 128000 && rate <= 192000)
flag = 0x8c;
else
return -EINVAL;
} else {
// For Fireface UFX and 802. Due to bandwidth limitation on
// IEEE 1394a (400 Mbps), Analog 1-12 and AES are available
// without any ADAT at quadruple speed.
if (rate >= 32000 && rate <= 48000)
flag = 0x9e;
else if (rate >= 64000 && rate <= 96000)
flag = 0x96;
else if (rate >= 128000 && rate <= 192000)
flag = 0x8e;
else
return -EINVAL;
}
if (generation != fw_parent_device(ff->unit)->card->generation) {
err = fw_iso_resources_update(&ff->tx_resources);
if (err < 0)
return err;
err = fw_iso_resources_update(&ff->rx_resources);
if (err < 0)
return err;
}
data = (ff->tx_resources.channel << 8) | ff->rx_resources.channel;
reg = cpu_to_le32(data);
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
LATTER_ISOC_CHANNELS, &reg, sizeof(reg), 0);
if (err < 0)
return err;
reg = cpu_to_le32(flag);
return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
LATTER_ISOC_START, &reg, sizeof(reg), 0);
}
static void latter_finish_session(struct snd_ff *ff)
{
__le32 reg;
reg = cpu_to_le32(0x00000000);
snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
LATTER_ISOC_START, &reg, sizeof(reg), 0);
}
static void latter_dump_status(struct snd_ff *ff, struct snd_info_buffer *buffer)
{
static const struct {
char *const label;
u32 locked_mask;
u32 synced_mask;
} *clk_entry, *clk_entries, ucx_clk_entries[] = {
{ "S/PDIF", 0x00000001, 0x00000010, },
{ "ADAT", 0x00000002, 0x00000020, },
{ "WDClk", 0x00000004, 0x00000040, },
}, ufx_ff802_clk_entries[] = {
{ "WDClk", 0x00000001, 0x00000010, },
{ "AES/EBU", 0x00000002, 0x00000020, },
{ "ADAT-A", 0x00000004, 0x00000040, },
{ "ADAT-B", 0x00000008, 0x00000080, },
};
__le32 reg;
u32 data;
unsigned int rate;
enum snd_ff_clock_src src;
const char *label;
unsigned int clk_entry_count;
int i;
int err;
err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST,
LATTER_SYNC_STATUS, &reg, sizeof(reg), 0);
if (err < 0)
return;
data = le32_to_cpu(reg);
snd_iprintf(buffer, "External source detection:\n");
if (ff->unit_version == SND_FF_UNIT_VERSION_UCX) {
clk_entries = ucx_clk_entries;
clk_entry_count = ARRAY_SIZE(ucx_clk_entries);
} else {
clk_entries = ufx_ff802_clk_entries;
clk_entry_count = ARRAY_SIZE(ufx_ff802_clk_entries);
}
for (i = 0; i < clk_entry_count; ++i) {
clk_entry = clk_entries + i;
snd_iprintf(buffer, "%s: ", clk_entry->label);
if (data & clk_entry->locked_mask) {
if (data & clk_entry->synced_mask)
snd_iprintf(buffer, "sync\n");
else
snd_iprintf(buffer, "lock\n");
} else {
snd_iprintf(buffer, "none\n");
}
}
err = parse_clock_bits(data, &rate, &src, ff->unit_version);
if (err < 0)
return;
label = snd_ff_proc_get_clk_label(src);
if (!label)
return;
snd_iprintf(buffer, "Referred clock: %s %d\n", label, rate);
}
// NOTE: transactions are transferred within 0x00-0x7f in allocated range of
// address. This seems to be for check of discontinuity in receiver side.
//
// Like Fireface 400, drivers can select one of 4 options for lower 4 bytes of
// destination address by bit flags in quadlet register (little endian) at
// 0x'ffff'0000'0014:
//
// bit flags: offset of destination address
// - 0x00002000: 0x'....'....'0000'0000
// - 0x00004000: 0x'....'....'0000'0080
// - 0x00008000: 0x'....'....'0000'0100
// - 0x00010000: 0x'....'....'0000'0180
//
// Drivers can suppress the device to transfer asynchronous transactions by
// clear these bit flags.
//
// Actually, the register is write-only and includes the other settings such as
// input attenuation. This driver allocates for the first option
// (0x'....'....'0000'0000) and expects userspace application to configure the
// register for it.
static void latter_handle_midi_msg(struct snd_ff *ff, unsigned int offset, const __le32 *buf,
size_t length, u32 tstamp)
{
u32 data = le32_to_cpu(*buf);
unsigned int index = (data & 0x000000f0) >> 4;
u8 byte[3];
struct snd_rawmidi_substream *substream;
unsigned int len;
if (index >= ff->spec->midi_in_ports)
return;
switch (data & 0x0000000f) {
case 0x00000008:
case 0x00000009:
case 0x0000000a:
case 0x0000000b:
case 0x0000000e:
len = 3;
break;
case 0x0000000c:
case 0x0000000d:
len = 2;
break;
default:
len = data & 0x00000003;
if (len == 0)
len = 3;
break;
}
byte[0] = (data & 0x0000ff00) >> 8;
byte[1] = (data & 0x00ff0000) >> 16;
byte[2] = (data & 0xff000000) >> 24;
substream = READ_ONCE(ff->tx_midi_substreams[index]);
if (substream)
snd_rawmidi_receive(substream, byte, len);
}
/*
* When return minus value, given argument is not MIDI status.
* When return 0, given argument is a beginning of system exclusive.
* When return the others, given argument is MIDI data.
*/
static inline int calculate_message_bytes(u8 status)
{
switch (status) {
case 0xf6: /* Tune request. */
case 0xf8: /* Timing clock. */
case 0xfa: /* Start. */
case 0xfb: /* Continue. */
case 0xfc: /* Stop. */
case 0xfe: /* Active sensing. */
case 0xff: /* System reset. */
return 1;
case 0xf1: /* MIDI time code quarter frame. */
case 0xf3: /* Song select. */
return 2;
case 0xf2: /* Song position pointer. */
return 3;
case 0xf0: /* Exclusive. */
return 0;
case 0xf7: /* End of exclusive. */
break;
case 0xf4: /* Undefined. */
case 0xf5: /* Undefined. */
case 0xf9: /* Undefined. */
case 0xfd: /* Undefined. */
break;
default:
switch (status & 0xf0) {
case 0x80: /* Note on. */
case 0x90: /* Note off. */
case 0xa0: /* Polyphonic key pressure. */
case 0xb0: /* Control change and Mode change. */
case 0xe0: /* Pitch bend change. */
return 3;
case 0xc0: /* Program change. */
case 0xd0: /* Channel pressure. */
return 2;
default:
break;
}
break;
}
return -EINVAL;
}
static int latter_fill_midi_msg(struct snd_ff *ff,
struct snd_rawmidi_substream *substream,
unsigned int port)
{
u32 data = {0};
u8 *buf = (u8 *)&data;
int consumed;
buf[0] = port << 4;
consumed = snd_rawmidi_transmit_peek(substream, buf + 1, 3);
if (consumed <= 0)
return consumed;
if (!ff->on_sysex[port]) {
if (buf[1] != 0xf0) {
if (consumed < calculate_message_bytes(buf[1]))
return 0;
} else {
// The beginning of exclusives.
ff->on_sysex[port] = true;
}
buf[0] |= consumed;
} else {
if (buf[1] != 0xf7) {
if (buf[2] == 0xf7 || buf[3] == 0xf7) {
// Transfer end code at next time.
consumed -= 1;
}
buf[0] |= consumed;
} else {
// The end of exclusives.
ff->on_sysex[port] = false;
consumed = 1;
buf[0] |= 0x0f;
}
}
ff->msg_buf[port][0] = cpu_to_le32(data);
ff->rx_bytes[port] = consumed;
return 1;
}
const struct snd_ff_protocol snd_ff_protocol_latter = {
.handle_msg = latter_handle_midi_msg,
.fill_midi_msg = latter_fill_midi_msg,
.get_clock = latter_get_clock,
.switch_fetching_mode = latter_switch_fetching_mode,
.allocate_resources = latter_allocate_resources,
.begin_session = latter_begin_session,
.finish_session = latter_finish_session,
.dump_status = latter_dump_status,
};
@@ -0,0 +1,276 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ff-stream.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*/
#include "ff.h"
#define READY_TIMEOUT_MS 200
int snd_ff_stream_get_multiplier_mode(enum cip_sfc sfc,
enum snd_ff_stream_mode *mode)
{
static const enum snd_ff_stream_mode modes[] = {
[CIP_SFC_32000] = SND_FF_STREAM_MODE_LOW,
[CIP_SFC_44100] = SND_FF_STREAM_MODE_LOW,
[CIP_SFC_48000] = SND_FF_STREAM_MODE_LOW,
[CIP_SFC_88200] = SND_FF_STREAM_MODE_MID,
[CIP_SFC_96000] = SND_FF_STREAM_MODE_MID,
[CIP_SFC_176400] = SND_FF_STREAM_MODE_HIGH,
[CIP_SFC_192000] = SND_FF_STREAM_MODE_HIGH,
};
if (sfc >= CIP_SFC_COUNT)
return -EINVAL;
*mode = modes[sfc];
return 0;
}
static inline void finish_session(struct snd_ff *ff)
{
ff->spec->protocol->finish_session(ff);
ff->spec->protocol->switch_fetching_mode(ff, false);
}
static int init_stream(struct snd_ff *ff, struct amdtp_stream *s)
{
struct fw_iso_resources *resources;
enum amdtp_stream_direction dir;
int err;
if (s == &ff->tx_stream) {
resources = &ff->tx_resources;
dir = AMDTP_IN_STREAM;
} else {
resources = &ff->rx_resources;
dir = AMDTP_OUT_STREAM;
}
err = fw_iso_resources_init(resources, ff->unit);
if (err < 0)
return err;
err = amdtp_ff_init(s, ff->unit, dir);
if (err < 0)
fw_iso_resources_destroy(resources);
return err;
}
static void destroy_stream(struct snd_ff *ff, struct amdtp_stream *s)
{
amdtp_stream_destroy(s);
if (s == &ff->tx_stream)
fw_iso_resources_destroy(&ff->tx_resources);
else
fw_iso_resources_destroy(&ff->rx_resources);
}
int snd_ff_stream_init_duplex(struct snd_ff *ff)
{
int err;
err = init_stream(ff, &ff->rx_stream);
if (err < 0)
return err;
err = init_stream(ff, &ff->tx_stream);
if (err < 0) {
destroy_stream(ff, &ff->rx_stream);
return err;
}
err = amdtp_domain_init(&ff->domain);
if (err < 0) {
destroy_stream(ff, &ff->rx_stream);
destroy_stream(ff, &ff->tx_stream);
}
return err;
}
/*
* This function should be called before starting streams or after stopping
* streams.
*/
void snd_ff_stream_destroy_duplex(struct snd_ff *ff)
{
amdtp_domain_destroy(&ff->domain);
destroy_stream(ff, &ff->rx_stream);
destroy_stream(ff, &ff->tx_stream);
}
int snd_ff_stream_reserve_duplex(struct snd_ff *ff, unsigned int rate,
unsigned int frames_per_period,
unsigned int frames_per_buffer)
{
unsigned int curr_rate;
enum snd_ff_clock_src src;
int err;
err = ff->spec->protocol->get_clock(ff, &curr_rate, &src);
if (err < 0)
return err;
if (ff->substreams_counter == 0 || curr_rate != rate) {
enum snd_ff_stream_mode mode;
int i;
amdtp_domain_stop(&ff->domain);
finish_session(ff);
fw_iso_resources_free(&ff->tx_resources);
fw_iso_resources_free(&ff->rx_resources);
for (i = 0; i < CIP_SFC_COUNT; ++i) {
if (amdtp_rate_table[i] == rate)
break;
}
if (i >= CIP_SFC_COUNT)
return -EINVAL;
err = snd_ff_stream_get_multiplier_mode(i, &mode);
if (err < 0)
return err;
err = amdtp_ff_set_parameters(&ff->tx_stream, rate,
ff->spec->pcm_capture_channels[mode]);
if (err < 0)
return err;
err = amdtp_ff_set_parameters(&ff->rx_stream, rate,
ff->spec->pcm_playback_channels[mode]);
if (err < 0)
return err;
err = ff->spec->protocol->allocate_resources(ff, rate);
if (err < 0)
return err;
err = amdtp_domain_set_events_per_period(&ff->domain,
frames_per_period, frames_per_buffer);
if (err < 0) {
fw_iso_resources_free(&ff->tx_resources);
fw_iso_resources_free(&ff->rx_resources);
return err;
}
}
return 0;
}
int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate)
{
int err;
if (ff->substreams_counter == 0)
return 0;
if (amdtp_streaming_error(&ff->tx_stream) ||
amdtp_streaming_error(&ff->rx_stream)) {
amdtp_domain_stop(&ff->domain);
finish_session(ff);
}
/*
* Regardless of current source of clock signal, drivers transfer some
* packets. Then, the device transfers packets.
*/
if (!amdtp_stream_running(&ff->rx_stream)) {
int spd = fw_parent_device(ff->unit)->max_speed;
err = ff->spec->protocol->begin_session(ff, rate);
if (err < 0)
goto error;
err = amdtp_domain_add_stream(&ff->domain, &ff->rx_stream,
ff->rx_resources.channel, spd);
if (err < 0)
goto error;
err = amdtp_domain_add_stream(&ff->domain, &ff->tx_stream,
ff->tx_resources.channel, spd);
if (err < 0)
goto error;
// NOTE: The device doesn't transfer packets unless receiving any packet. The
// sequence of tx packets includes cycle skip corresponding to empty packet or
// NODATA packet in IEC 61883-1/6. The sequence of the number of data blocks per
// packet is important for media clock recovery.
err = amdtp_domain_start(&ff->domain, 0, true, true);
if (err < 0)
goto error;
if (!amdtp_domain_wait_ready(&ff->domain, READY_TIMEOUT_MS)) {
err = -ETIMEDOUT;
goto error;
}
err = ff->spec->protocol->switch_fetching_mode(ff, true);
if (err < 0)
goto error;
}
return 0;
error:
amdtp_domain_stop(&ff->domain);
finish_session(ff);
return err;
}
void snd_ff_stream_stop_duplex(struct snd_ff *ff)
{
if (ff->substreams_counter == 0) {
amdtp_domain_stop(&ff->domain);
finish_session(ff);
fw_iso_resources_free(&ff->tx_resources);
fw_iso_resources_free(&ff->rx_resources);
}
}
void snd_ff_stream_update_duplex(struct snd_ff *ff)
{
amdtp_domain_stop(&ff->domain);
// The device discontinue to transfer packets.
amdtp_stream_pcm_abort(&ff->tx_stream);
amdtp_stream_pcm_abort(&ff->rx_stream);
}
void snd_ff_stream_lock_changed(struct snd_ff *ff)
{
ff->dev_lock_changed = true;
wake_up(&ff->hwdep_wait);
}
int snd_ff_stream_lock_try(struct snd_ff *ff)
{
guard(spinlock_irq)(&ff->lock);
/* user land lock this */
if (ff->dev_lock_count < 0)
return -EBUSY;
/* this is the first time */
if (ff->dev_lock_count++ == 0)
snd_ff_stream_lock_changed(ff);
return 0;
}
void snd_ff_stream_lock_release(struct snd_ff *ff)
{
guard(spinlock_irq)(&ff->lock);
if (WARN_ON(ff->dev_lock_count <= 0))
return;
if (--ff->dev_lock_count == 0)
snd_ff_stream_lock_changed(ff);
}
@@ -0,0 +1,233 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ff-transaction.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*/
#include "ff.h"
static void finish_transmit_midi_msg(struct snd_ff *ff, unsigned int port,
int rcode)
{
struct snd_rawmidi_substream *substream =
READ_ONCE(ff->rx_midi_substreams[port]);
if (rcode_is_permanent_error(rcode)) {
ff->rx_midi_error[port] = true;
return;
}
if (rcode != RCODE_COMPLETE) {
/* Transfer the message again, immediately. */
ff->next_ktime[port] = 0;
schedule_work(&ff->rx_midi_work[port]);
return;
}
snd_rawmidi_transmit_ack(substream, ff->rx_bytes[port]);
ff->rx_bytes[port] = 0;
if (!snd_rawmidi_transmit_empty(substream))
schedule_work(&ff->rx_midi_work[port]);
}
static void finish_transmit_midi0_msg(struct fw_card *card, int rcode,
void *data, size_t length,
void *callback_data)
{
struct snd_ff *ff =
container_of(callback_data, struct snd_ff, transactions[0]);
finish_transmit_midi_msg(ff, 0, rcode);
}
static void finish_transmit_midi1_msg(struct fw_card *card, int rcode,
void *data, size_t length,
void *callback_data)
{
struct snd_ff *ff =
container_of(callback_data, struct snd_ff, transactions[1]);
finish_transmit_midi_msg(ff, 1, rcode);
}
static void transmit_midi_msg(struct snd_ff *ff, unsigned int port)
{
struct snd_rawmidi_substream *substream =
READ_ONCE(ff->rx_midi_substreams[port]);
int quad_count;
struct fw_device *fw_dev = fw_parent_device(ff->unit);
unsigned long long addr;
int generation;
fw_transaction_callback_t callback;
int tcode;
if (substream == NULL || snd_rawmidi_transmit_empty(substream))
return;
if (ff->rx_bytes[port] > 0 || ff->rx_midi_error[port])
return;
/* Do it in next chance. */
if (ktime_after(ff->next_ktime[port], ktime_get())) {
schedule_work(&ff->rx_midi_work[port]);
return;
}
quad_count = ff->spec->protocol->fill_midi_msg(ff, substream, port);
if (quad_count <= 0)
return;
if (port == 0) {
addr = ff->spec->midi_rx_addrs[0];
callback = finish_transmit_midi0_msg;
} else {
addr = ff->spec->midi_rx_addrs[1];
callback = finish_transmit_midi1_msg;
}
/* Set interval to next transaction. */
ff->next_ktime[port] = ktime_add_ns(ktime_get(),
ff->rx_bytes[port] * 8 * (NSEC_PER_SEC / 31250));
if (quad_count == 1)
tcode = TCODE_WRITE_QUADLET_REQUEST;
else
tcode = TCODE_WRITE_BLOCK_REQUEST;
/*
* In Linux FireWire core, when generation is updated with memory
* barrier, node id has already been updated. In this module, After
* this smp_rmb(), load/store instructions to memory are completed.
* Thus, both of generation and node id are available with recent
* values. This is a light-serialization solution to handle bus reset
* events on IEEE 1394 bus.
*/
generation = fw_dev->generation;
smp_rmb();
fw_send_request(fw_dev->card, &ff->transactions[port], tcode,
fw_dev->node_id, generation, fw_dev->max_speed,
addr, &ff->msg_buf[port], quad_count * 4,
callback, &ff->transactions[port]);
}
static void transmit_midi0_msg(struct work_struct *work)
{
struct snd_ff *ff = container_of(work, struct snd_ff, rx_midi_work[0]);
transmit_midi_msg(ff, 0);
}
static void transmit_midi1_msg(struct work_struct *work)
{
struct snd_ff *ff = container_of(work, struct snd_ff, rx_midi_work[1]);
transmit_midi_msg(ff, 1);
}
static void handle_msg(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_ff *ff = callback_data;
__le32 *buf = data;
u32 tstamp = fw_request_get_timestamp(request);
fw_send_response(card, request, RCODE_COMPLETE);
offset -= ff->async_handler.offset;
guard(spinlock_irqsave)(&ff->lock);
ff->spec->protocol->handle_msg(ff, (unsigned int)offset, buf, length, tstamp);
}
static int allocate_own_address(struct snd_ff *ff, int i)
{
struct fw_address_region midi_msg_region;
int err;
ff->async_handler.length = ff->spec->midi_addr_range;
ff->async_handler.address_callback = handle_msg;
ff->async_handler.callback_data = ff;
midi_msg_region.start = 0x000100000000ull * i;
midi_msg_region.end = midi_msg_region.start + ff->async_handler.length;
err = fw_core_add_address_handler(&ff->async_handler, &midi_msg_region);
if (err >= 0) {
/* Controllers are allowed to register this region. */
if (ff->async_handler.offset & 0x0000ffffffff) {
fw_core_remove_address_handler(&ff->async_handler);
err = -EAGAIN;
}
}
return err;
}
// Controllers are allowed to register higher 4 bytes of destination address to
// receive asynchronous transactions for MIDI messages, while the way to
// register lower 4 bytes of address is different depending on protocols. For
// details, please refer to comments in protocol implementations.
//
// This driver expects userspace applications to configure registers for the
// lower address because in most cases such registers has the other settings.
int snd_ff_transaction_reregister(struct snd_ff *ff)
{
struct fw_card *fw_card = fw_parent_device(ff->unit)->card;
u32 addr;
__le32 reg;
/*
* Controllers are allowed to register its node ID and upper 2 byte of
* local address to listen asynchronous transactions.
*/
addr = (fw_card->node_id << 16) | (ff->async_handler.offset >> 32);
reg = cpu_to_le32(addr);
return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
ff->spec->midi_high_addr,
&reg, sizeof(reg), 0);
}
int snd_ff_transaction_register(struct snd_ff *ff)
{
int i, err;
/*
* Allocate in Memory Space of IEC 13213, but lower 4 byte in LSB should
* be zero due to device specification.
*/
for (i = 0; i < 0xffff; i++) {
err = allocate_own_address(ff, i);
if (err != -EBUSY && err != -EAGAIN)
break;
}
if (err < 0)
return err;
err = snd_ff_transaction_reregister(ff);
if (err < 0)
return err;
INIT_WORK(&ff->rx_midi_work[0], transmit_midi0_msg);
INIT_WORK(&ff->rx_midi_work[1], transmit_midi1_msg);
return 0;
}
void snd_ff_transaction_unregister(struct snd_ff *ff)
{
__le32 reg;
if (ff->async_handler.callback_data == NULL)
return;
ff->async_handler.callback_data = NULL;
/* Release higher 4 bytes of address. */
reg = cpu_to_le32(0x00000000);
snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
ff->spec->midi_high_addr,
&reg, sizeof(reg), 0);
fw_core_remove_address_handler(&ff->async_handler);
}
+266
View File
@@ -0,0 +1,266 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ff.c - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*/
#include "ff.h"
#define OUI_RME 0x000a35
MODULE_DESCRIPTION("RME Fireface series Driver");
MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
MODULE_LICENSE("GPL");
static void name_card(struct snd_ff *ff)
{
struct fw_device *fw_dev = fw_parent_device(ff->unit);
static const char *const names[] = {
[SND_FF_UNIT_VERSION_FF800] = "Fireface800",
[SND_FF_UNIT_VERSION_FF400] = "Fireface400",
[SND_FF_UNIT_VERSION_UFX] = "FirefaceUFX",
[SND_FF_UNIT_VERSION_UCX] = "FirefaceUCX",
[SND_FF_UNIT_VERSION_802] = "Fireface802",
};
const char *name;
name = names[ff->unit_version];
strscpy(ff->card->driver, "Fireface");
strscpy(ff->card->shortname, name);
strscpy(ff->card->mixername, name);
snprintf(ff->card->longname, sizeof(ff->card->longname),
"RME %s, GUID %08x%08x at %s, S%d", name,
fw_dev->config_rom[3], fw_dev->config_rom[4],
dev_name(&ff->unit->device), 100 << fw_dev->max_speed);
}
static void ff_card_free(struct snd_card *card)
{
struct snd_ff *ff = card->private_data;
snd_ff_stream_destroy_duplex(ff);
snd_ff_transaction_unregister(ff);
kfree(ff->msg_parser);
mutex_destroy(&ff->mutex);
fw_unit_put(ff->unit);
}
static int snd_ff_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry)
{
struct snd_card *card;
struct snd_ff *ff;
int err;
err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, sizeof(*ff), &card);
if (err < 0)
return err;
card->private_free = ff_card_free;
ff = card->private_data;
ff->unit = fw_unit_get(unit);
dev_set_drvdata(&unit->device, ff);
ff->card = card;
mutex_init(&ff->mutex);
spin_lock_init(&ff->lock);
init_waitqueue_head(&ff->hwdep_wait);
ff->unit_version = entry->version;
ff->spec = entry->driver_data_ptr;
err = snd_ff_transaction_register(ff);
if (err < 0)
goto error;
name_card(ff);
err = snd_ff_stream_init_duplex(ff);
if (err < 0)
goto error;
snd_ff_proc_init(ff);
err = snd_ff_create_midi_devices(ff);
if (err < 0)
goto error;
err = snd_ff_create_pcm_devices(ff);
if (err < 0)
goto error;
err = snd_ff_create_hwdep_devices(ff);
if (err < 0)
goto error;
if (ff->spec->protocol->msg_parser_size > 0) {
ff->msg_parser = kzalloc(ff->spec->protocol->msg_parser_size, GFP_KERNEL);
if (!ff->msg_parser) {
err = -ENOMEM;
goto error;
}
}
err = snd_card_register(card);
if (err < 0)
goto error;
return 0;
error:
snd_card_free(card);
return err;
}
static void snd_ff_update(struct fw_unit *unit)
{
struct snd_ff *ff = dev_get_drvdata(&unit->device);
snd_ff_transaction_reregister(ff);
snd_ff_stream_update_duplex(ff);
}
static void snd_ff_remove(struct fw_unit *unit)
{
struct snd_ff *ff = dev_get_drvdata(&unit->device);
// Block till all of ALSA character devices are released.
snd_card_free(ff->card);
}
static const struct snd_ff_spec spec_ff800 = {
.pcm_capture_channels = {28, 20, 12},
.pcm_playback_channels = {28, 20, 12},
.midi_in_ports = 1,
.midi_out_ports = 1,
.protocol = &snd_ff_protocol_ff800,
.midi_high_addr = 0x000200000320ull,
.midi_addr_range = 12,
.midi_rx_addrs = {0x000080180000ull, 0},
};
static const struct snd_ff_spec spec_ff400 = {
.pcm_capture_channels = {18, 14, 10},
.pcm_playback_channels = {18, 14, 10},
.midi_in_ports = 2,
.midi_out_ports = 2,
.protocol = &snd_ff_protocol_ff400,
.midi_high_addr = 0x0000801003f4ull,
.midi_addr_range = SND_FF_MAXIMIM_MIDI_QUADS * 4,
.midi_rx_addrs = {0x000080180000ull, 0x000080190000ull},
};
static const struct snd_ff_spec spec_ucx = {
.pcm_capture_channels = {18, 14, 12},
.pcm_playback_channels = {18, 14, 12},
.midi_in_ports = 2,
.midi_out_ports = 2,
.protocol = &snd_ff_protocol_latter,
.midi_high_addr = 0xffff00000034ull,
.midi_addr_range = 0x80,
.midi_rx_addrs = {0xffff00000030ull, 0xffff00000030ull},
};
static const struct snd_ff_spec spec_ufx_802 = {
.pcm_capture_channels = {30, 22, 14},
.pcm_playback_channels = {30, 22, 14},
.midi_in_ports = 1,
.midi_out_ports = 1,
.protocol = &snd_ff_protocol_latter,
.midi_high_addr = 0xffff00000034ull,
.midi_addr_range = 0x80,
.midi_rx_addrs = {0xffff00000030ull, 0xffff00000030ull},
};
static const struct ieee1394_device_id snd_ff_id_table[] = {
/* Fireface 800 */
{
.match_flags = IEEE1394_MATCH_VENDOR_ID |
IEEE1394_MATCH_SPECIFIER_ID |
IEEE1394_MATCH_VERSION |
IEEE1394_MATCH_MODEL_ID,
.vendor_id = OUI_RME,
.specifier_id = OUI_RME,
.version = SND_FF_UNIT_VERSION_FF800,
.model_id = 0x101800,
.driver_data_ptr = &spec_ff800,
},
/* Fireface 400 */
{
.match_flags = IEEE1394_MATCH_VENDOR_ID |
IEEE1394_MATCH_SPECIFIER_ID |
IEEE1394_MATCH_VERSION |
IEEE1394_MATCH_MODEL_ID,
.vendor_id = OUI_RME,
.specifier_id = OUI_RME,
.version = SND_FF_UNIT_VERSION_FF400,
.model_id = 0x101800,
.driver_data_ptr = &spec_ff400,
},
// Fireface UFX.
{
.match_flags = IEEE1394_MATCH_VENDOR_ID |
IEEE1394_MATCH_SPECIFIER_ID |
IEEE1394_MATCH_VERSION |
IEEE1394_MATCH_MODEL_ID,
.vendor_id = OUI_RME,
.specifier_id = OUI_RME,
.version = SND_FF_UNIT_VERSION_UFX,
.model_id = 0x101800,
.driver_data_ptr = &spec_ufx_802,
},
// Fireface UCX.
{
.match_flags = IEEE1394_MATCH_VENDOR_ID |
IEEE1394_MATCH_SPECIFIER_ID |
IEEE1394_MATCH_VERSION |
IEEE1394_MATCH_MODEL_ID,
.vendor_id = OUI_RME,
.specifier_id = OUI_RME,
.version = SND_FF_UNIT_VERSION_UCX,
.model_id = 0x101800,
.driver_data_ptr = &spec_ucx,
},
// Fireface 802.
{
.match_flags = IEEE1394_MATCH_VENDOR_ID |
IEEE1394_MATCH_SPECIFIER_ID |
IEEE1394_MATCH_VERSION |
IEEE1394_MATCH_MODEL_ID,
.vendor_id = OUI_RME,
.specifier_id = OUI_RME,
.version = SND_FF_UNIT_VERSION_802,
.model_id = 0x101800,
.driver_data_ptr = &spec_ufx_802,
},
{}
};
MODULE_DEVICE_TABLE(ieee1394, snd_ff_id_table);
static struct fw_driver ff_driver = {
.driver = {
.owner = THIS_MODULE,
.name = KBUILD_MODNAME,
.bus = &fw_bus_type,
},
.probe = snd_ff_probe,
.update = snd_ff_update,
.remove = snd_ff_remove,
.id_table = snd_ff_id_table,
};
static int __init snd_ff_init(void)
{
return driver_register(&ff_driver.driver);
}
static void __exit snd_ff_exit(void)
{
driver_unregister(&ff_driver.driver);
}
module_init(snd_ff_init);
module_exit(snd_ff_exit);
+170
View File
@@ -0,0 +1,170 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* ff.h - a part of driver for RME Fireface series
*
* Copyright (c) 2015-2017 Takashi Sakamoto
*/
#ifndef SOUND_FIREFACE_H_INCLUDED
#define SOUND_FIREFACE_H_INCLUDED
#include <linux/device.h>
#include <linux/firewire.h>
#include <linux/firewire-constants.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/sched/signal.h>
#include <sound/core.h>
#include <sound/info.h>
#include <sound/rawmidi.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/hwdep.h>
#include <sound/firewire.h>
#include "../lib.h"
#include "../amdtp-stream.h"
#include "../iso-resources.h"
#define SND_FF_MAXIMIM_MIDI_QUADS 9
#define SND_FF_IN_MIDI_PORTS 2
#define SND_FF_OUT_MIDI_PORTS 2
enum snd_ff_unit_version {
SND_FF_UNIT_VERSION_FF800 = 0x000001,
SND_FF_UNIT_VERSION_FF400 = 0x000002,
SND_FF_UNIT_VERSION_UFX = 0x000003,
SND_FF_UNIT_VERSION_UCX = 0x000004,
SND_FF_UNIT_VERSION_802 = 0x000005,
};
enum snd_ff_stream_mode {
SND_FF_STREAM_MODE_LOW = 0,
SND_FF_STREAM_MODE_MID,
SND_FF_STREAM_MODE_HIGH,
SND_FF_STREAM_MODE_COUNT,
};
struct snd_ff_protocol;
struct snd_ff_spec {
const unsigned int pcm_capture_channels[SND_FF_STREAM_MODE_COUNT];
const unsigned int pcm_playback_channels[SND_FF_STREAM_MODE_COUNT];
unsigned int midi_in_ports;
unsigned int midi_out_ports;
const struct snd_ff_protocol *protocol;
u64 midi_high_addr;
u8 midi_addr_range;
u64 midi_rx_addrs[SND_FF_OUT_MIDI_PORTS];
};
struct snd_ff {
struct snd_card *card;
struct fw_unit *unit;
struct mutex mutex;
spinlock_t lock;
enum snd_ff_unit_version unit_version;
const struct snd_ff_spec *spec;
/* To handle MIDI tx. */
struct snd_rawmidi_substream *tx_midi_substreams[SND_FF_IN_MIDI_PORTS];
struct fw_address_handler async_handler;
/* TO handle MIDI rx. */
struct snd_rawmidi_substream *rx_midi_substreams[SND_FF_OUT_MIDI_PORTS];
bool on_sysex[SND_FF_OUT_MIDI_PORTS];
__le32 msg_buf[SND_FF_OUT_MIDI_PORTS][SND_FF_MAXIMIM_MIDI_QUADS];
struct work_struct rx_midi_work[SND_FF_OUT_MIDI_PORTS];
struct fw_transaction transactions[SND_FF_OUT_MIDI_PORTS];
ktime_t next_ktime[SND_FF_OUT_MIDI_PORTS];
bool rx_midi_error[SND_FF_OUT_MIDI_PORTS];
unsigned int rx_bytes[SND_FF_OUT_MIDI_PORTS];
unsigned int substreams_counter;
struct amdtp_stream tx_stream;
struct amdtp_stream rx_stream;
struct fw_iso_resources tx_resources;
struct fw_iso_resources rx_resources;
int dev_lock_count;
bool dev_lock_changed;
wait_queue_head_t hwdep_wait;
struct amdtp_domain domain;
void *msg_parser;
};
enum snd_ff_clock_src {
SND_FF_CLOCK_SRC_INTERNAL,
SND_FF_CLOCK_SRC_SPDIF,
SND_FF_CLOCK_SRC_ADAT1,
SND_FF_CLOCK_SRC_ADAT2,
SND_FF_CLOCK_SRC_WORD,
SND_FF_CLOCK_SRC_LTC,
/* TODO: perhaps TCO exists. */
};
struct snd_ff_protocol {
size_t msg_parser_size;
bool (*has_msg)(struct snd_ff *ff);
long (*copy_msg_to_user)(struct snd_ff *ff, char __user *buf, long count);
void (*handle_msg)(struct snd_ff *ff, unsigned int offset, const __le32 *buf,
size_t length, u32 tstamp);
int (*fill_midi_msg)(struct snd_ff *ff,
struct snd_rawmidi_substream *substream,
unsigned int port);
int (*get_clock)(struct snd_ff *ff, unsigned int *rate,
enum snd_ff_clock_src *src);
int (*switch_fetching_mode)(struct snd_ff *ff, bool enable);
int (*allocate_resources)(struct snd_ff *ff, unsigned int rate);
int (*begin_session)(struct snd_ff *ff, unsigned int rate);
void (*finish_session)(struct snd_ff *ff);
void (*dump_status)(struct snd_ff *ff, struct snd_info_buffer *buffer);
};
extern const struct snd_ff_protocol snd_ff_protocol_ff800;
extern const struct snd_ff_protocol snd_ff_protocol_ff400;
extern const struct snd_ff_protocol snd_ff_protocol_latter;
int snd_ff_transaction_register(struct snd_ff *ff);
int snd_ff_transaction_reregister(struct snd_ff *ff);
void snd_ff_transaction_unregister(struct snd_ff *ff);
int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate,
unsigned int pcm_channels);
int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s,
struct snd_pcm_runtime *runtime);
int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit,
enum amdtp_stream_direction dir);
int snd_ff_stream_get_multiplier_mode(enum cip_sfc sfc,
enum snd_ff_stream_mode *mode);
int snd_ff_stream_init_duplex(struct snd_ff *ff);
void snd_ff_stream_destroy_duplex(struct snd_ff *ff);
int snd_ff_stream_reserve_duplex(struct snd_ff *ff, unsigned int rate,
unsigned int frames_per_period,
unsigned int frames_per_buffer);
int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate);
void snd_ff_stream_stop_duplex(struct snd_ff *ff);
void snd_ff_stream_update_duplex(struct snd_ff *ff);
void snd_ff_stream_lock_changed(struct snd_ff *ff);
int snd_ff_stream_lock_try(struct snd_ff *ff);
void snd_ff_stream_lock_release(struct snd_ff *ff);
void snd_ff_proc_init(struct snd_ff *ff);
const char *snd_ff_proc_get_clk_label(enum snd_ff_clock_src src);
int snd_ff_create_midi_devices(struct snd_ff *ff);
int snd_ff_create_pcm_devices(struct snd_ff *ff);
int snd_ff_create_hwdep_devices(struct snd_ff *ff);
#endif