diff options
| author | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 09:55:01 -0400 |
|---|---|---|
| committer | Steven Rostedt <rostedt@goodmis.org> | 2026-09-02 11:08:21 -0400 |
| commit | 4617721c502b2ddaa4e324e86da4997edf738fa5 (patch) | |
| tree | 350aa83b47da0b574a814795392b20b0bed83cbc /sound/firewire/fireface | |
| download | linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.tar.gz linux-stable-4617721c502b2ddaa4e324e86da4997edf738fa5.zip | |
ftrace: Synchronize the initialization of ftrace_opsgrafted
There's some internal state that ftrace_ops needs to have set, but since
it can be declared outside of the ftrace.c code, it calls
ftrace_ops_init() on the ops in every global function. The issue is that
if two tasks call it on the same ops at the same time it is possible to
have the initialization of one corrupt the initialization of the other
call.
Create a ops_mutex to use to synchronize every initialization of the
ftrace_ops. The mutex is taken within checking the ftrace_ops flag that
states it was initializied but the flag is checked again after the mutex
has been taken. Checking first outside the mutex allows it to shortcut
having to take the mutex. But then the check needs to be done again after
the mute is taken in case of races.
Cc: stable@vger.kernel.org
Link: https://patch.msgid.link/20260902095501.6b59af20@gandalf.local.home
Fixes: f04f24fb7e48d ("ftrace, kprobes: Fix a deadlock on ftrace_regex_lock")
Reported-by: sashiko-bot@kernel.org
Close: https://lore.kernel.org/all/20260829025528.49A831F000E9@smtp.kernel.org/
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
Diffstat (limited to 'sound/firewire/fireface')
| -rw-r--r-- | sound/firewire/fireface/Makefile | 5 | ||||
| -rw-r--r-- | sound/firewire/fireface/amdtp-ff.c | 167 | ||||
| -rw-r--r-- | sound/firewire/fireface/ff-hwdep.c | 190 | ||||
| -rw-r--r-- | sound/firewire/fireface/ff-midi.c | 122 | ||||
| -rw-r--r-- | sound/firewire/fireface/ff-pcm.c | 387 | ||||
| -rw-r--r-- | sound/firewire/fireface/ff-proc.c | 62 | ||||
| -rw-r--r-- | sound/firewire/fireface/ff-protocol-former.c | 733 | ||||
| -rw-r--r-- | sound/firewire/fireface/ff-protocol-latter.c | 540 | ||||
| -rw-r--r-- | sound/firewire/fireface/ff-stream.c | 276 | ||||
| -rw-r--r-- | sound/firewire/fireface/ff-transaction.c | 233 | ||||
| -rw-r--r-- | sound/firewire/fireface/ff.c | 266 | ||||
| -rw-r--r-- | sound/firewire/fireface/ff.h | 170 |
12 files changed, 3151 insertions, 0 deletions
diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile new file mode 100644 index 000000000..b397d9587 --- /dev/null +++ b/sound/firewire/fireface/Makefile @@ -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 diff --git a/sound/firewire/fireface/amdtp-ff.c b/sound/firewire/fireface/amdtp-ff.c new file mode 100644 index 000000000..76c9d33ed --- /dev/null +++ b/sound/firewire/fireface/amdtp-ff.c @@ -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)); +} diff --git a/sound/firewire/fireface/ff-hwdep.c b/sound/firewire/fireface/ff-hwdep.c new file mode 100644 index 000000000..5976abf2e --- /dev/null +++ b/sound/firewire/fireface/ff-hwdep.c @@ -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; +} diff --git a/sound/firewire/fireface/ff-midi.c b/sound/firewire/fireface/ff-midi.c new file mode 100644 index 000000000..9f6aa490e --- /dev/null +++ b/sound/firewire/fireface/ff-midi.c @@ -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; +} diff --git a/sound/firewire/fireface/ff-pcm.c b/sound/firewire/fireface/ff-pcm.c new file mode 100644 index 000000000..7ad8204fb --- /dev/null +++ b/sound/firewire/fireface/ff-pcm.c @@ -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; +} diff --git a/sound/firewire/fireface/ff-proc.c b/sound/firewire/fireface/ff-proc.c new file mode 100644 index 000000000..4aecc8dcb --- /dev/null +++ b/sound/firewire/fireface/ff-proc.c @@ -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); +} diff --git a/sound/firewire/fireface/ff-protocol-former.c b/sound/firewire/fireface/ff-protocol-former.c new file mode 100644 index 000000000..0907d0a22 --- /dev/null +++ b/sound/firewire/fireface/ff-protocol-former.c @@ -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, ®, 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, ®, 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, ®, 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, ®, 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, ®, 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, ®, 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, ®, 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, ®, 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, ®, 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, ®, 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, ®, 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, ®, 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, ®, 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, +}; diff --git a/sound/firewire/fireface/ff-protocol-latter.c b/sound/firewire/fireface/ff-protocol-latter.c new file mode 100644 index 000000000..9947e0c2e --- /dev/null +++ b/sound/firewire/fireface/ff-protocol-latter.c @@ -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, ®, 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, ®, 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, ®, 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, ®, 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, ®, 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, ®, 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, ®, 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, +}; diff --git a/sound/firewire/fireface/ff-stream.c b/sound/firewire/fireface/ff-stream.c new file mode 100644 index 000000000..ba42490f2 --- /dev/null +++ b/sound/firewire/fireface/ff-stream.c @@ -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); +} diff --git a/sound/firewire/fireface/ff-transaction.c b/sound/firewire/fireface/ff-transaction.c new file mode 100644 index 000000000..436da0a3b --- /dev/null +++ b/sound/firewire/fireface/ff-transaction.c @@ -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, + ®, 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, + ®, sizeof(reg), 0); + + fw_core_remove_address_handler(&ff->async_handler); +} diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c new file mode 100644 index 000000000..13472822d --- /dev/null +++ b/sound/firewire/fireface/ff.c @@ -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); diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h new file mode 100644 index 000000000..c9c35c67d --- /dev/null +++ b/sound/firewire/fireface/ff.h @@ -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 |
