diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 10:05:51 +0000 |
commit | 5d1646d90e1f2cceb9f0828f4b28318cd0ec7744 (patch) | |
tree | a94efe259b9009378be6d90eb30d2b019d95c194 /sound/firewire | |
parent | Initial commit. (diff) | |
download | linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.tar.xz linux-5d1646d90e1f2cceb9f0828f4b28318cd0ec7744.zip |
Adding upstream version 5.10.209.upstream/5.10.209
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sound/firewire')
112 files changed, 29584 insertions, 0 deletions
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig new file mode 100644 index 000000000..12664c3a1 --- /dev/null +++ b/sound/firewire/Kconfig @@ -0,0 +1,176 @@ +# SPDX-License-Identifier: GPL-2.0-only +menuconfig SND_FIREWIRE + bool "FireWire sound devices" + depends on FIREWIRE + default y + help + Support for IEEE-1394/FireWire/iLink sound devices. + +if SND_FIREWIRE && FIREWIRE + +config SND_FIREWIRE_LIB + tristate + select SND_PCM + select SND_RAWMIDI + +config SND_DICE + tristate "DICE-based DACs support" + select SND_HWDEP + select SND_FIREWIRE_LIB + help + Say Y here to include support for many DACs based on the DICE + chip family (DICE-II/Jr/Mini) which TC Applied Technologies produces. + + To compile this driver as a module, choose M here: the module + will be called snd-dice. + +config SND_OXFW + tristate "Oxford Semiconductor FW970/971 chipset support" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to include support for FireWire devices based on + Oxford Semiconductor FW970/971 chipset. + * Griffin Firewave + * LaCie Firewire Speakers + * Behringer F-Control Audio 202 + * Mackie(Loud) Onyx-i series (former models) + * Mackie(Loud) Onyx 1640i (former model) + * Mackie(Loud) Onyx Satellite + * Mackie(Loud) Tapco Link.Firewire + * Mackie(Loud) d.2 pro/d.4 pro (built-in FireWire card with OXFW971 ASIC) + * Mackie(Loud) U.420/U.420d + * TASCAM FireOne + * Stanton Controllers & Systems 1 Deck/Mixer + * APOGEE duet FireWire + + To compile this driver as a module, choose M here: the module + will be called snd-oxfw. + +config SND_ISIGHT + tristate "Apple iSight microphone" + select SND_FIREWIRE_LIB + help + Say Y here to include support for the front and rear microphones + of the Apple iSight web camera. + + To compile this driver as a module, choose M here: the module + will be called snd-isight. + +config SND_FIREWORKS + tristate "Echo Fireworks board module support" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to include support for FireWire devices based + on Echo Digital Audio Fireworks board: + * Mackie Onyx 400F/1200F + * Echo AudioFire12/8(until 2009 July) + * Echo AudioFire2/4/Pre8/8(since 2009 July) + * Echo Fireworks 8/HDMI + * Gibson Robot Interface Pack/GoldTop + + To compile this driver as a module, choose M here: the module + will be called snd-fireworks. + +config SND_BEBOB + tristate "BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to include support for FireWire devices based + on BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware: + * Edirol FA-66/FA-101 + * PreSonus FIREBOX/FIREPOD/FP10/Inspire1394 + * BridgeCo RDAudio1/Audio5 + * Mackie Onyx 1220/1620/1640 (FireWire I/O Card) + * Mackie d.2 (optional FireWire card with DM1000 ASIC) + * Stanton FinalScratch 2 (ScratchAmp) + * Tascam IF-FW/DM + * Behringer XENIX UFX 1204/1604 + * Behringer Digital Mixer X32 series (X-UF Card) + * Behringer FCA610/1616 + * Apogee Rosetta 200/400 (X-FireWire card) + * Apogee DA/AD/DD-16X (X-FireWire card) + * Apogee Ensemble + * ESI QuataFire 610 + * AcousticReality eARMasterOne + * CME MatrixKFW + * Phonic Helix Board 12 MkII/18 MkII/24 MkII + * Phonic Helix Board 12 Universal/18 Universal/24 Universal + * Lynx Aurora 8/16 (LT-FW) + * ICON FireXon + * PrismSound Orpheus/ADA-8XR + * TerraTec PHASE 24 FW/PHASE X24 FW/PHASE 88 Rack FW + * TerraTec EWS MIC2/EWS MIC8 + * TerraTec Aureon 7.1 FireWire + * Yamaha GO44/GO46 + * Focusrite Saffire/Saffire LE/SaffirePro10 IO/SaffirePro26 IO + * M-Audio FireWire410/AudioPhile/Solo + * M-Audio Ozonic/NRV10/ProfireLightBridge + * M-Audio FireWire 1814/ProjectMix IO + * Digidesign Mbox 2 Pro + * ToneWeal FW66 + + To compile this driver as a module, choose M here: the module + will be called snd-bebob. + +config SND_FIREWIRE_DIGI00X + tristate "Digidesign Digi 002/003 family support" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to include support for Digidesign Digi 002/003 family. + * Digi 002 Console + * Digi 002 Rack + * Digi 003 Console + * Digi 003 Rack + * Digi 003 Rack+ + + To compile this driver as a module, choose M here: the module + will be called snd-firewire-digi00x. + +config SND_FIREWIRE_TASCAM + tristate "TASCAM FireWire series support" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to include support for TASCAM. + * FW-1884 + * FW-1082 + * FW-1804 + + To compile this driver as a module, choose M here: the module + will be called snd-firewire-tascam. + +config SND_FIREWIRE_MOTU + tristate "Mark of the unicorn FireWire series support" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to enable support for FireWire devices which MOTU produced: + * 828mk2 + * Traveler + * Ultralite + * 8pre + * 828mk3 (FireWire only) + * 828mk3 (Hybrid) + * Audio Express + * 4pre + + To compile this driver as a module, choose M here: the module + will be called snd-firewire-motu. + +config SND_FIREFACE + tristate "RME Fireface series support" + select SND_FIREWIRE_LIB + select SND_HWDEP + help + Say Y here to include support for RME fireface series. + * Fireface 400 + * Fireface 800 + * Fireface UFX + * Fireface UCX + * Fireface 802 + +endif # SND_FIREWIRE diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile new file mode 100644 index 000000000..44a7b510b --- /dev/null +++ b/sound/firewire/Makefile @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0 +# To find a header included by define_trace.h. +CFLAGS_amdtp-stream.o := -I$(src) + +snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \ + fcp.o cmp.o amdtp-stream.o amdtp-am824.o +snd-isight-objs := isight.o + +obj-$(CONFIG_SND_FIREWIRE_LIB) += snd-firewire-lib.o +obj-$(CONFIG_SND_DICE) += dice/ +obj-$(CONFIG_SND_OXFW) += oxfw/ +obj-$(CONFIG_SND_ISIGHT) += snd-isight.o +obj-$(CONFIG_SND_FIREWORKS) += fireworks/ +obj-$(CONFIG_SND_BEBOB) += bebob/ +obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += digi00x/ +obj-$(CONFIG_SND_FIREWIRE_TASCAM) += tascam/ +obj-$(CONFIG_SND_FIREWIRE_MOTU) += motu/ +obj-$(CONFIG_SND_FIREFACE) += fireface/ diff --git a/sound/firewire/amdtp-am824.c b/sound/firewire/amdtp-am824.c new file mode 100644 index 000000000..fea92e148 --- /dev/null +++ b/sound/firewire/amdtp-am824.c @@ -0,0 +1,428 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AM824 format in Audio and Music Data Transmission Protocol (IEC 61883-6) + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * Copyright (c) 2015 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#include <linux/slab.h> + +#include "amdtp-am824.h" + +#define CIP_FMT_AM 0x10 + +/* "Clock-based rate control mode" is just supported. */ +#define AMDTP_FDF_AM824 0x00 + +/* + * Nominally 3125 bytes/second, but the MIDI port's clock might be + * 1% too slow, and the bus clock 100 ppm too fast. + */ +#define MIDI_BYTES_PER_SECOND 3093 + +/* + * Several devices look only at the first eight data blocks. + * In any case, this is more than enough for the MIDI data rate. + */ +#define MAX_MIDI_RX_BLOCKS 8 + +struct amdtp_am824 { + struct snd_rawmidi_substream *midi[AM824_MAX_CHANNELS_FOR_MIDI * 8]; + int midi_fifo_limit; + int midi_fifo_used[AM824_MAX_CHANNELS_FOR_MIDI * 8]; + unsigned int pcm_channels; + unsigned int midi_ports; + + u8 pcm_positions[AM824_MAX_CHANNELS_FOR_PCM]; + u8 midi_position; + + unsigned int frame_multiplier; +}; + +/** + * amdtp_am824_set_parameters - set stream parameters + * @s: the AMDTP stream to configure + * @rate: the sample rate + * @pcm_channels: the number of PCM samples in each data block, to be encoded + * as AM824 multi-bit linear audio + * @midi_ports: the number of MIDI ports (i.e., MPX-MIDI Data Channels) + * @double_pcm_frames: one data block transfers two PCM frames + * + * The parameters must be set before the stream is started, and must not be + * changed while the stream is running. + */ +int amdtp_am824_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels, + unsigned int midi_ports, + bool double_pcm_frames) +{ + struct amdtp_am824 *p = s->protocol; + unsigned int midi_channels; + unsigned int i; + int err; + + if (amdtp_stream_running(s)) + return -EINVAL; + + if (pcm_channels > AM824_MAX_CHANNELS_FOR_PCM) + return -EINVAL; + + midi_channels = DIV_ROUND_UP(midi_ports, 8); + if (midi_channels > AM824_MAX_CHANNELS_FOR_MIDI) + return -EINVAL; + + if (WARN_ON(amdtp_stream_running(s)) || + WARN_ON(pcm_channels > AM824_MAX_CHANNELS_FOR_PCM) || + WARN_ON(midi_channels > AM824_MAX_CHANNELS_FOR_MIDI)) + return -EINVAL; + + err = amdtp_stream_set_parameters(s, rate, + pcm_channels + midi_channels); + if (err < 0) + return err; + + if (s->direction == AMDTP_OUT_STREAM) + s->ctx_data.rx.fdf = AMDTP_FDF_AM824 | s->sfc; + + p->pcm_channels = pcm_channels; + p->midi_ports = midi_ports; + + /* + * In IEC 61883-6, one data block represents one event. In ALSA, one + * event equals to one PCM frame. But Dice has a quirk at higher + * sampling rate to transfer two PCM frames in one data block. + */ + if (double_pcm_frames) + p->frame_multiplier = 2; + else + p->frame_multiplier = 1; + + /* init the position map for PCM and MIDI channels */ + for (i = 0; i < pcm_channels; i++) + p->pcm_positions[i] = i; + p->midi_position = p->pcm_channels; + + /* + * We do not know the actual MIDI FIFO size of most devices. Just + * assume two bytes, i.e., one byte can be received over the bus while + * the previous one is transmitted over MIDI. + * (The value here is adjusted for midi_ratelimit_per_packet().) + */ + p->midi_fifo_limit = rate - MIDI_BYTES_PER_SECOND * s->syt_interval + 1; + + return 0; +} +EXPORT_SYMBOL_GPL(amdtp_am824_set_parameters); + +/** + * amdtp_am824_set_pcm_position - set an index of data channel for a channel + * of PCM frame + * @s: the AMDTP stream + * @index: the index of data channel in an data block + * @position: the channel of PCM frame + */ +void amdtp_am824_set_pcm_position(struct amdtp_stream *s, unsigned int index, + unsigned int position) +{ + struct amdtp_am824 *p = s->protocol; + + if (index < p->pcm_channels) + p->pcm_positions[index] = position; +} +EXPORT_SYMBOL_GPL(amdtp_am824_set_pcm_position); + +/** + * amdtp_am824_set_midi_position - set a index of data channel for MIDI + * conformant data channel + * @s: the AMDTP stream + * @position: the index of data channel in an data block + */ +void amdtp_am824_set_midi_position(struct amdtp_stream *s, + unsigned int position) +{ + struct amdtp_am824 *p = s->protocol; + + p->midi_position = position; +} +EXPORT_SYMBOL_GPL(amdtp_am824_set_midi_position); + +static void write_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames, + unsigned int pcm_frames) +{ + struct amdtp_am824 *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[p->pcm_positions[c]] = + cpu_to_be32((*src >> 8) | 0x40000000); + src++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void read_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames, + unsigned int pcm_frames) +{ + struct amdtp_am824 *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 = be32_to_cpu(buffer[p->pcm_positions[c]]) << 8; + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, + __be32 *buffer, unsigned int frames) +{ + struct amdtp_am824 *p = s->protocol; + unsigned int i, c, channels = p->pcm_channels; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) + buffer[p->pcm_positions[c]] = cpu_to_be32(0x40000000); + buffer += s->data_block_quadlets; + } +} + +/** + * amdtp_am824_add_pcm_hw_constraints - add hw constraints for PCM substream + * @s: the AMDTP stream for AM824 data block, must be initialized. + * @runtime: the PCM substream runtime + * + */ +int amdtp_am824_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + err = amdtp_stream_add_pcm_hw_constraints(s, runtime); + if (err < 0) + return err; + + /* AM824 in IEC 61883-6 can deliver 24bit data. */ + return snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); +} +EXPORT_SYMBOL_GPL(amdtp_am824_add_pcm_hw_constraints); + +/** + * amdtp_am824_midi_trigger - start/stop playback/capture with a MIDI device + * @s: the AMDTP stream + * @port: index of MIDI port + * @midi: the MIDI device to be started, or %NULL to stop the current device + * + * Call this function on a running isochronous stream to enable the actual + * transmission of MIDI data. This function should be called from the MIDI + * device's .trigger callback. + */ +void amdtp_am824_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi) +{ + struct amdtp_am824 *p = s->protocol; + + if (port < p->midi_ports) + WRITE_ONCE(p->midi[port], midi); +} +EXPORT_SYMBOL_GPL(amdtp_am824_midi_trigger); + +/* + * To avoid sending MIDI bytes at too high a rate, assume that the receiving + * device has a FIFO, and track how much it is filled. This values increases + * by one whenever we send one byte in a packet, but the FIFO empties at + * a constant rate independent of our packet rate. One packet has syt_interval + * samples, so the number of bytes that empty out of the FIFO, per packet(!), + * is MIDI_BYTES_PER_SECOND * syt_interval / sample_rate. To avoid storing + * fractional values, the values in midi_fifo_used[] are measured in bytes + * multiplied by the sample rate. + */ +static bool midi_ratelimit_per_packet(struct amdtp_stream *s, unsigned int port) +{ + struct amdtp_am824 *p = s->protocol; + int used; + + used = p->midi_fifo_used[port]; + if (used == 0) /* common shortcut */ + return true; + + used -= MIDI_BYTES_PER_SECOND * s->syt_interval; + used = max(used, 0); + p->midi_fifo_used[port] = used; + + return used < p->midi_fifo_limit; +} + +static void midi_rate_use_one_byte(struct amdtp_stream *s, unsigned int port) +{ + struct amdtp_am824 *p = s->protocol; + + p->midi_fifo_used[port] += amdtp_rate_table[s->sfc]; +} + +static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int frames, unsigned int data_block_counter) +{ + struct amdtp_am824 *p = s->protocol; + unsigned int f, port; + u8 *b; + + for (f = 0; f < frames; f++) { + b = (u8 *)&buffer[p->midi_position]; + + port = (data_block_counter + f) % 8; + if (f < MAX_MIDI_RX_BLOCKS && + midi_ratelimit_per_packet(s, port) && + p->midi[port] != NULL && + snd_rawmidi_transmit(p->midi[port], &b[1], 1) == 1) { + midi_rate_use_one_byte(s, port); + b[0] = 0x81; + } else { + b[0] = 0x80; + b[1] = 0; + } + b[2] = 0; + b[3] = 0; + + buffer += s->data_block_quadlets; + } +} + +static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int frames, unsigned int data_block_counter) +{ + struct amdtp_am824 *p = s->protocol; + int len; + u8 *b; + int f; + + for (f = 0; f < frames; f++) { + unsigned int port = f; + + if (!(s->flags & CIP_UNALIGHED_DBC)) + port += data_block_counter; + port %= 8; + b = (u8 *)&buffer[p->midi_position]; + + len = b[0] - 0x80; + if ((1 <= len) && (len <= 3) && (p->midi[port])) + snd_rawmidi_receive(p->midi[port], b + 1, len); + + buffer += s->data_block_quadlets; + } +} + +static unsigned int process_it_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) +{ + struct amdtp_am824 *p = s->protocol; + unsigned int pcm_frames = 0; + int i; + + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buf = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; + + if (pcm) { + write_pcm_s32(s, pcm, buf, data_blocks, pcm_frames); + pcm_frames += data_blocks * p->frame_multiplier; + } else { + write_pcm_silence(s, buf, data_blocks); + } + + if (p->midi_ports) { + write_midi_messages(s, buf, data_blocks, + desc->data_block_counter); + } + } + + return pcm_frames; +} + +static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) +{ + struct amdtp_am824 *p = s->protocol; + unsigned int pcm_frames = 0; + int i; + + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buf = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; + + if (pcm) { + read_pcm_s32(s, pcm, buf, data_blocks, pcm_frames); + pcm_frames += data_blocks * p->frame_multiplier; + } + + if (p->midi_ports) { + read_midi_messages(s, buf, data_blocks, + desc->data_block_counter); + } + } + + return pcm_frames; +} + +/** + * amdtp_am824_init - initialize an AMDTP stream structure to handle AM824 + * data block + * @s: the AMDTP stream to initialize + * @unit: the target of the stream + * @dir: the direction of stream + * @flags: the packet transmission method to use + */ +int amdtp_am824_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, enum cip_flags flags) +{ + 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, flags, CIP_FMT_AM, + process_ctx_payloads, sizeof(struct amdtp_am824)); +} +EXPORT_SYMBOL_GPL(amdtp_am824_init); diff --git a/sound/firewire/amdtp-am824.h b/sound/firewire/amdtp-am824.h new file mode 100644 index 000000000..06d280783 --- /dev/null +++ b/sound/firewire/amdtp-am824.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef SOUND_FIREWIRE_AMDTP_AM824_H_INCLUDED +#define SOUND_FIREWIRE_AMDTP_AM824_H_INCLUDED + +#include <sound/pcm.h> +#include <sound/rawmidi.h> + +#include "amdtp-stream.h" + +#define AM824_IN_PCM_FORMAT_BITS SNDRV_PCM_FMTBIT_S32 + +#define AM824_OUT_PCM_FORMAT_BITS SNDRV_PCM_FMTBIT_S32 + +/* + * This module supports maximum 64 PCM channels for one PCM stream + * This is for our convenience. + */ +#define AM824_MAX_CHANNELS_FOR_PCM 64 + +/* + * AMDTP packet can include channels for MIDI conformant data. + * Each MIDI conformant data channel includes 8 MPX-MIDI data stream. + * Each MPX-MIDI data stream includes one data stream from/to MIDI ports. + * + * This module supports maximum 1 MIDI conformant data channels. + * Then this AMDTP packets can transfer maximum 8 MIDI data streams. + */ +#define AM824_MAX_CHANNELS_FOR_MIDI 1 + +int amdtp_am824_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels, + unsigned int midi_ports, + bool double_pcm_frames); + +void amdtp_am824_set_pcm_position(struct amdtp_stream *s, unsigned int index, + unsigned int position); + +void amdtp_am824_set_midi_position(struct amdtp_stream *s, + unsigned int position); + +int amdtp_am824_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); + +void amdtp_am824_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi); + +int amdtp_am824_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, enum cip_flags flags); +#endif diff --git a/sound/firewire/amdtp-stream-trace.h b/sound/firewire/amdtp-stream-trace.h new file mode 100644 index 000000000..aa53c13b8 --- /dev/null +++ b/sound/firewire/amdtp-stream-trace.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * amdtp-stream-trace.h - tracepoint definitions to dump a part of packet data + * + * Copyright (c) 2016 Takashi Sakamoto + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM snd_firewire_lib + +#if !defined(_AMDTP_STREAM_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _AMDTP_STREAM_TRACE_H + +#include <linux/tracepoint.h> + +TRACE_EVENT(amdtp_packet, + TP_PROTO(const struct amdtp_stream *s, u32 cycles, const __be32 *cip_header, unsigned int payload_length, unsigned int data_blocks, unsigned int data_block_counter, unsigned int packet_index, unsigned int index), + TP_ARGS(s, cycles, cip_header, payload_length, data_blocks, data_block_counter, packet_index, index), + TP_STRUCT__entry( + __field(unsigned int, second) + __field(unsigned int, cycle) + __field(int, channel) + __field(int, src) + __field(int, dest) + __dynamic_array(u8, cip_header, cip_header ? 8 : 0) + __field(unsigned int, payload_quadlets) + __field(unsigned int, data_blocks) + __field(unsigned int, data_block_counter) + __field(unsigned int, packet_index) + __field(unsigned int, irq) + __field(unsigned int, index) + ), + TP_fast_assign( + __entry->second = cycles / CYCLES_PER_SECOND; + __entry->cycle = cycles % CYCLES_PER_SECOND; + __entry->channel = s->context->channel; + if (s->direction == AMDTP_IN_STREAM) { + __entry->src = fw_parent_device(s->unit)->node_id; + __entry->dest = fw_parent_device(s->unit)->card->node_id; + } else { + __entry->src = fw_parent_device(s->unit)->card->node_id; + __entry->dest = fw_parent_device(s->unit)->node_id; + } + if (cip_header) { + memcpy(__get_dynamic_array(cip_header), cip_header, + __get_dynamic_array_len(cip_header)); + } + __entry->payload_quadlets = payload_length / sizeof(__be32); + __entry->data_blocks = data_blocks; + __entry->data_block_counter = data_block_counter, + __entry->packet_index = packet_index; + __entry->irq = !!in_interrupt(); + __entry->index = index; + ), + TP_printk( + "%02u %04u %04x %04x %02d %03u %02u %03u %02u %01u %02u %s", + __entry->second, + __entry->cycle, + __entry->src, + __entry->dest, + __entry->channel, + __entry->payload_quadlets, + __entry->data_blocks, + __entry->data_block_counter, + __entry->packet_index, + __entry->irq, + __entry->index, + __print_array(__get_dynamic_array(cip_header), + __get_dynamic_array_len(cip_header), 1)) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE amdtp-stream-trace +#include <trace/define_trace.h> diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c new file mode 100644 index 000000000..7a282d8e7 --- /dev/null +++ b/sound/firewire/amdtp-stream.c @@ -0,0 +1,1536 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Audio and Music Data Transmission Protocol (IEC 61883-6) streams + * with Common Isochronous Packet (IEC 61883-1) headers + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + */ + +#include <linux/device.h> +#include <linux/err.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include "amdtp-stream.h" + +#define TICKS_PER_CYCLE 3072 +#define CYCLES_PER_SECOND 8000 +#define TICKS_PER_SECOND (TICKS_PER_CYCLE * CYCLES_PER_SECOND) + +#define OHCI_MAX_SECOND 8 + +/* Always support Linux tracing subsystem. */ +#define CREATE_TRACE_POINTS +#include "amdtp-stream-trace.h" + +#define TRANSFER_DELAY_TICKS 0x2e00 /* 479.17 microseconds */ + +/* isochronous header parameters */ +#define ISO_DATA_LENGTH_SHIFT 16 +#define TAG_NO_CIP_HEADER 0 +#define TAG_CIP 1 + +/* common isochronous packet header parameters */ +#define CIP_EOH_SHIFT 31 +#define CIP_EOH (1u << CIP_EOH_SHIFT) +#define CIP_EOH_MASK 0x80000000 +#define CIP_SID_SHIFT 24 +#define CIP_SID_MASK 0x3f000000 +#define CIP_DBS_MASK 0x00ff0000 +#define CIP_DBS_SHIFT 16 +#define CIP_SPH_MASK 0x00000400 +#define CIP_SPH_SHIFT 10 +#define CIP_DBC_MASK 0x000000ff +#define CIP_FMT_SHIFT 24 +#define CIP_FMT_MASK 0x3f000000 +#define CIP_FDF_MASK 0x00ff0000 +#define CIP_FDF_SHIFT 16 +#define CIP_SYT_MASK 0x0000ffff +#define CIP_SYT_NO_INFO 0xffff + +/* Audio and Music transfer protocol specific parameters */ +#define CIP_FMT_AM 0x10 +#define AMDTP_FDF_NO_DATA 0xff + +// For iso header, tstamp and 2 CIP header. +#define IR_CTX_HEADER_SIZE_CIP 16 +// For iso header and tstamp. +#define IR_CTX_HEADER_SIZE_NO_CIP 8 +#define HEADER_TSTAMP_MASK 0x0000ffff + +#define IT_PKT_HEADER_SIZE_CIP 8 // For 2 CIP header. +#define IT_PKT_HEADER_SIZE_NO_CIP 0 // Nothing. + +static void pcm_period_work(struct work_struct *work); + +/** + * amdtp_stream_init - initialize an AMDTP stream structure + * @s: the AMDTP stream to initialize + * @unit: the target of the stream + * @dir: the direction of stream + * @flags: the packet transmission method to use + * @fmt: the value of fmt field in CIP header + * @process_ctx_payloads: callback handler to process payloads of isoc context + * @protocol_size: the size to allocate newly for protocol + */ +int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, enum cip_flags flags, + unsigned int fmt, + amdtp_stream_process_ctx_payloads_t process_ctx_payloads, + unsigned int protocol_size) +{ + if (process_ctx_payloads == NULL) + return -EINVAL; + + s->protocol = kzalloc(protocol_size, GFP_KERNEL); + if (!s->protocol) + return -ENOMEM; + + s->unit = unit; + s->direction = dir; + s->flags = flags; + s->context = ERR_PTR(-1); + mutex_init(&s->mutex); + INIT_WORK(&s->period_work, pcm_period_work); + s->packet_index = 0; + + init_waitqueue_head(&s->callback_wait); + s->callbacked = false; + + s->fmt = fmt; + s->process_ctx_payloads = process_ctx_payloads; + + if (dir == AMDTP_OUT_STREAM) + s->ctx_data.rx.syt_override = -1; + + return 0; +} +EXPORT_SYMBOL(amdtp_stream_init); + +/** + * amdtp_stream_destroy - free stream resources + * @s: the AMDTP stream to destroy + */ +void amdtp_stream_destroy(struct amdtp_stream *s) +{ + /* Not initialized. */ + if (s->protocol == NULL) + return; + + WARN_ON(amdtp_stream_running(s)); + kfree(s->protocol); + mutex_destroy(&s->mutex); +} +EXPORT_SYMBOL(amdtp_stream_destroy); + +const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT] = { + [CIP_SFC_32000] = 8, + [CIP_SFC_44100] = 8, + [CIP_SFC_48000] = 8, + [CIP_SFC_88200] = 16, + [CIP_SFC_96000] = 16, + [CIP_SFC_176400] = 32, + [CIP_SFC_192000] = 32, +}; +EXPORT_SYMBOL(amdtp_syt_intervals); + +const unsigned int amdtp_rate_table[CIP_SFC_COUNT] = { + [CIP_SFC_32000] = 32000, + [CIP_SFC_44100] = 44100, + [CIP_SFC_48000] = 48000, + [CIP_SFC_88200] = 88200, + [CIP_SFC_96000] = 96000, + [CIP_SFC_176400] = 176400, + [CIP_SFC_192000] = 192000, +}; +EXPORT_SYMBOL(amdtp_rate_table); + +static int apply_constraint_to_size(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *s = hw_param_interval(params, rule->var); + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval t = {0}; + unsigned int step = 0; + int i; + + for (i = 0; i < CIP_SFC_COUNT; ++i) { + if (snd_interval_test(r, amdtp_rate_table[i])) + step = max(step, amdtp_syt_intervals[i]); + } + + t.min = roundup(s->min, step); + t.max = rounddown(s->max, step); + t.integer = 1; + + return snd_interval_refine(s, &t); +} + +/** + * amdtp_stream_add_pcm_hw_constraints - add hw constraints for PCM substream + * @s: the AMDTP stream, which must be initialized. + * @runtime: the PCM substream runtime + */ +int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + struct snd_pcm_hardware *hw = &runtime->hw; + unsigned int ctx_header_size; + unsigned int maximum_usec_per_period; + int err; + + hw->info = SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID; + + /* SNDRV_PCM_INFO_BATCH */ + hw->periods_min = 2; + hw->periods_max = UINT_MAX; + + /* bytes for a frame */ + hw->period_bytes_min = 4 * hw->channels_max; + + /* Just to prevent from allocating much pages. */ + hw->period_bytes_max = hw->period_bytes_min * 2048; + hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min; + + // Linux driver for 1394 OHCI controller voluntarily flushes isoc + // context when total size of accumulated context header reaches + // PAGE_SIZE. This kicks work for the isoc context and brings + // callback in the middle of scheduled interrupts. + // Although AMDTP streams in the same domain use the same events per + // IRQ, use the largest size of context header between IT/IR contexts. + // Here, use the value of context header in IR context is for both + // contexts. + if (!(s->flags & CIP_NO_HEADER)) + ctx_header_size = IR_CTX_HEADER_SIZE_CIP; + else + ctx_header_size = IR_CTX_HEADER_SIZE_NO_CIP; + maximum_usec_per_period = USEC_PER_SEC * PAGE_SIZE / + CYCLES_PER_SECOND / ctx_header_size; + + // In IEC 61883-6, one isoc packet can transfer events up to the value + // of syt interval. This comes from the interval of isoc cycle. As 1394 + // OHCI controller can generate hardware IRQ per isoc packet, the + // interval is 125 usec. + // However, there are two ways of transmission in IEC 61883-6; blocking + // and non-blocking modes. In blocking mode, the sequence of isoc packet + // includes 'empty' or 'NODATA' packets which include no event. In + // non-blocking mode, the number of events per packet is variable up to + // the syt interval. + // Due to the above protocol design, the minimum PCM frames per + // interrupt should be double of the value of syt interval, thus it is + // 250 usec. + err = snd_pcm_hw_constraint_minmax(runtime, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + 250, maximum_usec_per_period); + if (err < 0) + goto end; + + /* Non-Blocking stream has no more constraints */ + if (!(s->flags & CIP_BLOCKING)) + goto end; + + /* + * One AMDTP packet can include some frames. In blocking mode, the + * number equals to SYT_INTERVAL. So the number is 8, 16 or 32, + * depending on its sampling rate. For accurate period interrupt, it's + * preferrable to align period/buffer sizes to current SYT_INTERVAL. + */ + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + apply_constraint_to_size, NULL, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + goto end; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + apply_constraint_to_size, NULL, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + goto end; +end: + return err; +} +EXPORT_SYMBOL(amdtp_stream_add_pcm_hw_constraints); + +/** + * amdtp_stream_set_parameters - set stream parameters + * @s: the AMDTP stream to configure + * @rate: the sample rate + * @data_block_quadlets: the size of a data block in quadlet unit + * + * The parameters must be set before the stream is started, and must not be + * changed while the stream is running. + */ +int amdtp_stream_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int data_block_quadlets) +{ + unsigned int sfc; + + for (sfc = 0; sfc < ARRAY_SIZE(amdtp_rate_table); ++sfc) { + if (amdtp_rate_table[sfc] == rate) + break; + } + if (sfc == ARRAY_SIZE(amdtp_rate_table)) + return -EINVAL; + + s->sfc = sfc; + s->data_block_quadlets = data_block_quadlets; + s->syt_interval = amdtp_syt_intervals[sfc]; + + // default buffering in the device. + if (s->direction == AMDTP_OUT_STREAM) { + s->ctx_data.rx.transfer_delay = + TRANSFER_DELAY_TICKS - TICKS_PER_CYCLE; + + if (s->flags & CIP_BLOCKING) { + // additional buffering needed to adjust for no-data + // packets. + s->ctx_data.rx.transfer_delay += + TICKS_PER_SECOND * s->syt_interval / rate; + } + } + + return 0; +} +EXPORT_SYMBOL(amdtp_stream_set_parameters); + +/** + * amdtp_stream_get_max_payload - get the stream's packet size + * @s: the AMDTP stream + * + * This function must not be called before the stream has been configured + * with amdtp_stream_set_parameters(). + */ +unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s) +{ + unsigned int multiplier = 1; + unsigned int cip_header_size = 0; + + if (s->flags & CIP_JUMBO_PAYLOAD) + multiplier = 5; + if (!(s->flags & CIP_NO_HEADER)) + cip_header_size = sizeof(__be32) * 2; + + return cip_header_size + + s->syt_interval * s->data_block_quadlets * sizeof(__be32) * multiplier; +} +EXPORT_SYMBOL(amdtp_stream_get_max_payload); + +/** + * amdtp_stream_pcm_prepare - prepare PCM device for running + * @s: the AMDTP stream + * + * This function should be called from the PCM device's .prepare callback. + */ +void amdtp_stream_pcm_prepare(struct amdtp_stream *s) +{ + cancel_work_sync(&s->period_work); + s->pcm_buffer_pointer = 0; + s->pcm_period_pointer = 0; +} +EXPORT_SYMBOL(amdtp_stream_pcm_prepare); + +static unsigned int calculate_data_blocks(unsigned int *data_block_state, + bool is_blocking, bool is_no_info, + unsigned int syt_interval, enum cip_sfc sfc) +{ + unsigned int data_blocks; + + /* Blocking mode. */ + if (is_blocking) { + /* This module generate empty packet for 'no data'. */ + if (is_no_info) + data_blocks = 0; + else + data_blocks = syt_interval; + /* Non-blocking mode. */ + } else { + if (!cip_sfc_is_base_44100(sfc)) { + // Sample_rate / 8000 is an integer, and precomputed. + data_blocks = *data_block_state; + } else { + unsigned int phase = *data_block_state; + + /* + * This calculates the number of data blocks per packet so that + * 1) the overall rate is correct and exactly synchronized to + * the bus clock, and + * 2) packets with a rounded-up number of blocks occur as early + * as possible in the sequence (to prevent underruns of the + * device's buffer). + */ + if (sfc == CIP_SFC_44100) + /* 6 6 5 6 5 6 5 ... */ + data_blocks = 5 + ((phase & 1) ^ + (phase == 0 || phase >= 40)); + else + /* 12 11 11 11 11 ... or 23 22 22 22 22 ... */ + data_blocks = 11 * (sfc >> 1) + (phase == 0); + if (++phase >= (80 >> (sfc >> 1))) + phase = 0; + *data_block_state = phase; + } + } + + return data_blocks; +} + +static unsigned int calculate_syt_offset(unsigned int *last_syt_offset, + unsigned int *syt_offset_state, enum cip_sfc sfc) +{ + unsigned int syt_offset; + + if (*last_syt_offset < TICKS_PER_CYCLE) { + if (!cip_sfc_is_base_44100(sfc)) + syt_offset = *last_syt_offset + *syt_offset_state; + else { + /* + * The time, in ticks, of the n'th SYT_INTERVAL sample is: + * n * SYT_INTERVAL * 24576000 / sample_rate + * Modulo TICKS_PER_CYCLE, the difference between successive + * elements is about 1386.23. Rounding the results of this + * formula to the SYT precision results in a sequence of + * differences that begins with: + * 1386 1386 1387 1386 1386 1386 1387 1386 1386 1386 1387 ... + * This code generates _exactly_ the same sequence. + */ + unsigned int phase = *syt_offset_state; + unsigned int index = phase % 13; + + syt_offset = *last_syt_offset; + syt_offset += 1386 + ((index && !(index & 3)) || + phase == 146); + if (++phase >= 147) + phase = 0; + *syt_offset_state = phase; + } + } else + syt_offset = *last_syt_offset - TICKS_PER_CYCLE; + *last_syt_offset = syt_offset; + + if (syt_offset >= TICKS_PER_CYCLE) + syt_offset = CIP_SYT_NO_INFO; + + return syt_offset; +} + +static void update_pcm_pointers(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + unsigned int frames) +{ + unsigned int ptr; + + ptr = s->pcm_buffer_pointer + frames; + if (ptr >= pcm->runtime->buffer_size) + ptr -= pcm->runtime->buffer_size; + WRITE_ONCE(s->pcm_buffer_pointer, ptr); + + s->pcm_period_pointer += frames; + if (s->pcm_period_pointer >= pcm->runtime->period_size) { + s->pcm_period_pointer -= pcm->runtime->period_size; + queue_work(system_highpri_wq, &s->period_work); + } +} + +static void pcm_period_work(struct work_struct *work) +{ + struct amdtp_stream *s = container_of(work, struct amdtp_stream, + period_work); + struct snd_pcm_substream *pcm = READ_ONCE(s->pcm); + + if (pcm) + snd_pcm_period_elapsed(pcm); +} + +static int queue_packet(struct amdtp_stream *s, struct fw_iso_packet *params, + bool sched_irq) +{ + int err; + + params->interrupt = sched_irq; + params->tag = s->tag; + params->sy = 0; + + err = fw_iso_context_queue(s->context, params, &s->buffer.iso_buffer, + s->buffer.packets[s->packet_index].offset); + if (err < 0) { + dev_err(&s->unit->device, "queueing error: %d\n", err); + goto end; + } + + if (++s->packet_index >= s->queue_size) + s->packet_index = 0; +end: + return err; +} + +static inline int queue_out_packet(struct amdtp_stream *s, + struct fw_iso_packet *params, bool sched_irq) +{ + params->skip = + !!(params->header_length == 0 && params->payload_length == 0); + return queue_packet(s, params, sched_irq); +} + +static inline int queue_in_packet(struct amdtp_stream *s, + struct fw_iso_packet *params) +{ + // Queue one packet for IR context. + params->header_length = s->ctx_data.tx.ctx_header_size; + params->payload_length = s->ctx_data.tx.max_ctx_payload_length; + params->skip = false; + return queue_packet(s, params, false); +} + +static void generate_cip_header(struct amdtp_stream *s, __be32 cip_header[2], + unsigned int data_block_counter, unsigned int syt) +{ + cip_header[0] = cpu_to_be32(READ_ONCE(s->source_node_id_field) | + (s->data_block_quadlets << CIP_DBS_SHIFT) | + ((s->sph << CIP_SPH_SHIFT) & CIP_SPH_MASK) | + data_block_counter); + cip_header[1] = cpu_to_be32(CIP_EOH | + ((s->fmt << CIP_FMT_SHIFT) & CIP_FMT_MASK) | + ((s->ctx_data.rx.fdf << CIP_FDF_SHIFT) & CIP_FDF_MASK) | + (syt & CIP_SYT_MASK)); +} + +static void build_it_pkt_header(struct amdtp_stream *s, unsigned int cycle, + struct fw_iso_packet *params, + unsigned int data_blocks, + unsigned int data_block_counter, + unsigned int syt, unsigned int index) +{ + unsigned int payload_length; + __be32 *cip_header; + + payload_length = data_blocks * sizeof(__be32) * s->data_block_quadlets; + params->payload_length = payload_length; + + if (!(s->flags & CIP_NO_HEADER)) { + cip_header = (__be32 *)params->header; + generate_cip_header(s, cip_header, data_block_counter, syt); + params->header_length = 2 * sizeof(__be32); + payload_length += params->header_length; + } else { + cip_header = NULL; + } + + trace_amdtp_packet(s, cycle, cip_header, payload_length, data_blocks, + data_block_counter, s->packet_index, index); +} + +static int check_cip_header(struct amdtp_stream *s, const __be32 *buf, + unsigned int payload_length, + unsigned int *data_blocks, + unsigned int *data_block_counter, unsigned int *syt) +{ + u32 cip_header[2]; + unsigned int sph; + unsigned int fmt; + unsigned int fdf; + unsigned int dbc; + bool lost; + + cip_header[0] = be32_to_cpu(buf[0]); + cip_header[1] = be32_to_cpu(buf[1]); + + /* + * This module supports 'Two-quadlet CIP header with SYT field'. + * For convenience, also check FMT field is AM824 or not. + */ + if ((((cip_header[0] & CIP_EOH_MASK) == CIP_EOH) || + ((cip_header[1] & CIP_EOH_MASK) != CIP_EOH)) && + (!(s->flags & CIP_HEADER_WITHOUT_EOH))) { + dev_info_ratelimited(&s->unit->device, + "Invalid CIP header for AMDTP: %08X:%08X\n", + cip_header[0], cip_header[1]); + return -EAGAIN; + } + + /* Check valid protocol or not. */ + sph = (cip_header[0] & CIP_SPH_MASK) >> CIP_SPH_SHIFT; + fmt = (cip_header[1] & CIP_FMT_MASK) >> CIP_FMT_SHIFT; + if (sph != s->sph || fmt != s->fmt) { + dev_info_ratelimited(&s->unit->device, + "Detect unexpected protocol: %08x %08x\n", + cip_header[0], cip_header[1]); + return -EAGAIN; + } + + /* Calculate data blocks */ + fdf = (cip_header[1] & CIP_FDF_MASK) >> CIP_FDF_SHIFT; + if (payload_length < sizeof(__be32) * 2 || + (fmt == CIP_FMT_AM && fdf == AMDTP_FDF_NO_DATA)) { + *data_blocks = 0; + } else { + unsigned int data_block_quadlets = + (cip_header[0] & CIP_DBS_MASK) >> CIP_DBS_SHIFT; + /* avoid division by zero */ + if (data_block_quadlets == 0) { + dev_err(&s->unit->device, + "Detect invalid value in dbs field: %08X\n", + cip_header[0]); + return -EPROTO; + } + if (s->flags & CIP_WRONG_DBS) + data_block_quadlets = s->data_block_quadlets; + + *data_blocks = (payload_length / sizeof(__be32) - 2) / + data_block_quadlets; + } + + /* Check data block counter continuity */ + dbc = cip_header[0] & CIP_DBC_MASK; + if (*data_blocks == 0 && (s->flags & CIP_EMPTY_HAS_WRONG_DBC) && + *data_block_counter != UINT_MAX) + dbc = *data_block_counter; + + if ((dbc == 0x00 && (s->flags & CIP_SKIP_DBC_ZERO_CHECK)) || + *data_block_counter == UINT_MAX) { + lost = false; + } else if (!(s->flags & CIP_DBC_IS_END_EVENT)) { + lost = dbc != *data_block_counter; + } else { + unsigned int dbc_interval; + + if (*data_blocks > 0 && s->ctx_data.tx.dbc_interval > 0) + dbc_interval = s->ctx_data.tx.dbc_interval; + else + dbc_interval = *data_blocks; + + lost = dbc != ((*data_block_counter + dbc_interval) & 0xff); + } + + if (lost) { + dev_err(&s->unit->device, + "Detect discontinuity of CIP: %02X %02X\n", + *data_block_counter, dbc); + return -EIO; + } + + *data_block_counter = dbc; + + *syt = cip_header[1] & CIP_SYT_MASK; + + return 0; +} + +static int parse_ir_ctx_header(struct amdtp_stream *s, unsigned int cycle, + const __be32 *ctx_header, + unsigned int *payload_length, + unsigned int *data_blocks, + unsigned int *data_block_counter, + unsigned int *syt, unsigned int packet_index, unsigned int index) +{ + const __be32 *cip_header; + unsigned int cip_header_size; + int err; + + *payload_length = be32_to_cpu(ctx_header[0]) >> ISO_DATA_LENGTH_SHIFT; + + if (!(s->flags & CIP_NO_HEADER)) + cip_header_size = 8; + else + cip_header_size = 0; + + if (*payload_length > cip_header_size + s->ctx_data.tx.max_ctx_payload_length) { + dev_err(&s->unit->device, + "Detect jumbo payload: %04x %04x\n", + *payload_length, cip_header_size + s->ctx_data.tx.max_ctx_payload_length); + return -EIO; + } + + if (cip_header_size > 0) { + cip_header = ctx_header + 2; + err = check_cip_header(s, cip_header, *payload_length, + data_blocks, data_block_counter, syt); + if (err < 0) + return err; + } else { + cip_header = NULL; + err = 0; + *data_blocks = *payload_length / sizeof(__be32) / + s->data_block_quadlets; + *syt = 0; + + if (*data_block_counter == UINT_MAX) + *data_block_counter = 0; + } + + trace_amdtp_packet(s, cycle, cip_header, *payload_length, *data_blocks, + *data_block_counter, packet_index, index); + + return err; +} + +// In CYCLE_TIMER register of IEEE 1394, 7 bits are used to represent second. On +// the other hand, in DMA descriptors of 1394 OHCI, 3 bits are used to represent +// it. Thus, via Linux firewire subsystem, we can get the 3 bits for second. +static inline u32 compute_cycle_count(__be32 ctx_header_tstamp) +{ + u32 tstamp = be32_to_cpu(ctx_header_tstamp) & HEADER_TSTAMP_MASK; + return (((tstamp >> 13) & 0x07) * 8000) + (tstamp & 0x1fff); +} + +static inline u32 increment_cycle_count(u32 cycle, unsigned int addend) +{ + cycle += addend; + if (cycle >= OHCI_MAX_SECOND * CYCLES_PER_SECOND) + cycle -= OHCI_MAX_SECOND * CYCLES_PER_SECOND; + return cycle; +} + +// Align to actual cycle count for the packet which is going to be scheduled. +// This module queued the same number of isochronous cycle as the size of queue +// to kip isochronous cycle, therefore it's OK to just increment the cycle by +// the size of queue for scheduled cycle. +static inline u32 compute_it_cycle(const __be32 ctx_header_tstamp, + unsigned int queue_size) +{ + u32 cycle = compute_cycle_count(ctx_header_tstamp); + return increment_cycle_count(cycle, queue_size); +} + +static int generate_device_pkt_descs(struct amdtp_stream *s, + struct pkt_desc *descs, + const __be32 *ctx_header, + unsigned int packets) +{ + unsigned int dbc = s->data_block_counter; + unsigned int packet_index = s->packet_index; + unsigned int queue_size = s->queue_size; + int i; + int err; + + for (i = 0; i < packets; ++i) { + struct pkt_desc *desc = descs + i; + unsigned int cycle; + unsigned int payload_length; + unsigned int data_blocks; + unsigned int syt; + + cycle = compute_cycle_count(ctx_header[1]); + + err = parse_ir_ctx_header(s, cycle, ctx_header, &payload_length, + &data_blocks, &dbc, &syt, packet_index, i); + if (err < 0) + return err; + + desc->cycle = cycle; + desc->syt = syt; + desc->data_blocks = data_blocks; + desc->data_block_counter = dbc; + desc->ctx_payload = s->buffer.packets[packet_index].buffer; + + if (!(s->flags & CIP_DBC_IS_END_EVENT)) + dbc = (dbc + desc->data_blocks) & 0xff; + + ctx_header += + s->ctx_data.tx.ctx_header_size / sizeof(*ctx_header); + + packet_index = (packet_index + 1) % queue_size; + } + + s->data_block_counter = dbc; + + return 0; +} + +static unsigned int compute_syt(unsigned int syt_offset, unsigned int cycle, + unsigned int transfer_delay) +{ + unsigned int syt; + + syt_offset += transfer_delay; + syt = ((cycle + syt_offset / TICKS_PER_CYCLE) << 12) | + (syt_offset % TICKS_PER_CYCLE); + return syt & CIP_SYT_MASK; +} + +static void generate_pkt_descs(struct amdtp_stream *s, struct pkt_desc *descs, + const __be32 *ctx_header, unsigned int packets, + const struct seq_desc *seq_descs, + unsigned int seq_size) +{ + unsigned int dbc = s->data_block_counter; + unsigned int seq_index = s->ctx_data.rx.seq_index; + int i; + + for (i = 0; i < packets; ++i) { + struct pkt_desc *desc = descs + i; + unsigned int index = (s->packet_index + i) % s->queue_size; + const struct seq_desc *seq = seq_descs + seq_index; + unsigned int syt; + + desc->cycle = compute_it_cycle(*ctx_header, s->queue_size); + + syt = seq->syt_offset; + if (syt != CIP_SYT_NO_INFO) { + syt = compute_syt(syt, desc->cycle, + s->ctx_data.rx.transfer_delay); + } + desc->syt = syt; + desc->data_blocks = seq->data_blocks; + + if (s->flags & CIP_DBC_IS_END_EVENT) + dbc = (dbc + desc->data_blocks) & 0xff; + + desc->data_block_counter = dbc; + + if (!(s->flags & CIP_DBC_IS_END_EVENT)) + dbc = (dbc + desc->data_blocks) & 0xff; + + desc->ctx_payload = s->buffer.packets[index].buffer; + + seq_index = (seq_index + 1) % seq_size; + + ++ctx_header; + } + + s->data_block_counter = dbc; + s->ctx_data.rx.seq_index = seq_index; +} + +static inline void cancel_stream(struct amdtp_stream *s) +{ + s->packet_index = -1; + if (in_interrupt()) + amdtp_stream_pcm_abort(s); + WRITE_ONCE(s->pcm_buffer_pointer, SNDRV_PCM_POS_XRUN); +} + +static void process_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets) +{ + struct snd_pcm_substream *pcm; + unsigned int pcm_frames; + + pcm = READ_ONCE(s->pcm); + pcm_frames = s->process_ctx_payloads(s, descs, packets, pcm); + if (pcm) + update_pcm_pointers(s, pcm, pcm_frames); +} + +static void out_stream_callback(struct fw_iso_context *context, u32 tstamp, + size_t header_length, void *header, + void *private_data) +{ + struct amdtp_stream *s = private_data; + const struct amdtp_domain *d = s->domain; + const __be32 *ctx_header = header; + unsigned int events_per_period = s->ctx_data.rx.events_per_period; + unsigned int event_count = s->ctx_data.rx.event_count; + unsigned int packets; + int i; + + if (s->packet_index < 0) + return; + + // Calculate the number of packets in buffer and check XRUN. + packets = header_length / sizeof(*ctx_header); + + generate_pkt_descs(s, s->pkt_descs, ctx_header, packets, d->seq_descs, + d->seq_size); + + process_ctx_payloads(s, s->pkt_descs, packets); + + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = s->pkt_descs + i; + unsigned int syt; + struct { + struct fw_iso_packet params; + __be32 header[IT_PKT_HEADER_SIZE_CIP / sizeof(__be32)]; + } template = { {0}, {0} }; + bool sched_irq = false; + + if (s->ctx_data.rx.syt_override < 0) + syt = desc->syt; + else + syt = s->ctx_data.rx.syt_override; + + build_it_pkt_header(s, desc->cycle, &template.params, + desc->data_blocks, desc->data_block_counter, + syt, i); + + if (s == s->domain->irq_target) { + event_count += desc->data_blocks; + if (event_count >= events_per_period) { + event_count -= events_per_period; + sched_irq = true; + } + } + + if (queue_out_packet(s, &template.params, sched_irq) < 0) { + cancel_stream(s); + return; + } + } + + s->ctx_data.rx.event_count = event_count; +} + +static void in_stream_callback(struct fw_iso_context *context, u32 tstamp, + size_t header_length, void *header, + void *private_data) +{ + struct amdtp_stream *s = private_data; + __be32 *ctx_header = header; + unsigned int packets; + int i; + int err; + + if (s->packet_index < 0) + return; + + // Calculate the number of packets in buffer and check XRUN. + packets = header_length / s->ctx_data.tx.ctx_header_size; + + err = generate_device_pkt_descs(s, s->pkt_descs, ctx_header, packets); + if (err < 0) { + if (err != -EAGAIN) { + cancel_stream(s); + return; + } + } else { + process_ctx_payloads(s, s->pkt_descs, packets); + } + + for (i = 0; i < packets; ++i) { + struct fw_iso_packet params = {0}; + + if (queue_in_packet(s, ¶ms) < 0) { + cancel_stream(s); + return; + } + } +} + +static void pool_ideal_seq_descs(struct amdtp_domain *d, unsigned int packets) +{ + struct amdtp_stream *irq_target = d->irq_target; + unsigned int seq_tail = d->seq_tail; + unsigned int seq_size = d->seq_size; + unsigned int min_avail; + struct amdtp_stream *s; + + min_avail = d->seq_size; + list_for_each_entry(s, &d->streams, list) { + unsigned int seq_index; + unsigned int avail; + + if (s->direction == AMDTP_IN_STREAM) + continue; + + seq_index = s->ctx_data.rx.seq_index; + avail = d->seq_tail; + if (seq_index > avail) + avail += d->seq_size; + avail -= seq_index; + + if (avail < min_avail) + min_avail = avail; + } + + while (min_avail < packets) { + struct seq_desc *desc = d->seq_descs + seq_tail; + + desc->syt_offset = calculate_syt_offset(&d->last_syt_offset, + &d->syt_offset_state, irq_target->sfc); + desc->data_blocks = calculate_data_blocks(&d->data_block_state, + !!(irq_target->flags & CIP_BLOCKING), + desc->syt_offset == CIP_SYT_NO_INFO, + irq_target->syt_interval, irq_target->sfc); + + ++seq_tail; + seq_tail %= seq_size; + + ++min_avail; + } + + d->seq_tail = seq_tail; +} + +static void irq_target_callback(struct fw_iso_context *context, u32 tstamp, + size_t header_length, void *header, + void *private_data) +{ + struct amdtp_stream *irq_target = private_data; + struct amdtp_domain *d = irq_target->domain; + unsigned int packets = header_length / sizeof(__be32); + struct amdtp_stream *s; + + // Record enough entries with extra 3 cycles at least. + pool_ideal_seq_descs(d, packets + 3); + + out_stream_callback(context, tstamp, header_length, header, irq_target); + if (amdtp_streaming_error(irq_target)) + goto error; + + list_for_each_entry(s, &d->streams, list) { + if (s != irq_target && amdtp_stream_running(s)) { + fw_iso_context_flush_completions(s->context); + if (amdtp_streaming_error(s)) + goto error; + } + } + + return; +error: + if (amdtp_stream_running(irq_target)) + cancel_stream(irq_target); + + list_for_each_entry(s, &d->streams, list) { + if (amdtp_stream_running(s)) + cancel_stream(s); + } +} + +// this is executed one time. +static void amdtp_stream_first_callback(struct fw_iso_context *context, + u32 tstamp, size_t header_length, + void *header, void *private_data) +{ + struct amdtp_stream *s = private_data; + const __be32 *ctx_header = header; + u32 cycle; + + /* + * For in-stream, first packet has come. + * For out-stream, prepared to transmit first packet + */ + s->callbacked = true; + wake_up(&s->callback_wait); + + if (s->direction == AMDTP_IN_STREAM) { + cycle = compute_cycle_count(ctx_header[1]); + + context->callback.sc = in_stream_callback; + } else { + cycle = compute_it_cycle(*ctx_header, s->queue_size); + + if (s == s->domain->irq_target) + context->callback.sc = irq_target_callback; + else + context->callback.sc = out_stream_callback; + } + + s->start_cycle = cycle; + + context->callback.sc(context, tstamp, header_length, header, s); +} + +/** + * amdtp_stream_start - start transferring packets + * @s: the AMDTP stream to start + * @channel: the isochronous channel on the bus + * @speed: firewire speed code + * @start_cycle: the isochronous cycle to start the context. Start immediately + * if negative value is given. + * @queue_size: The number of packets in the queue. + * @idle_irq_interval: the interval to queue packet during initial state. + * + * The stream cannot be started until it has been configured with + * amdtp_stream_set_parameters() and it must be started before any PCM or MIDI + * device can be started. + */ +static int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed, + int start_cycle, unsigned int queue_size, + unsigned int idle_irq_interval) +{ + bool is_irq_target = (s == s->domain->irq_target); + unsigned int ctx_header_size; + unsigned int max_ctx_payload_size; + enum dma_data_direction dir; + int type, tag, err; + + mutex_lock(&s->mutex); + + if (WARN_ON(amdtp_stream_running(s) || + (s->data_block_quadlets < 1))) { + err = -EBADFD; + goto err_unlock; + } + + if (s->direction == AMDTP_IN_STREAM) { + // NOTE: IT context should be used for constant IRQ. + if (is_irq_target) { + err = -EINVAL; + goto err_unlock; + } + + s->data_block_counter = UINT_MAX; + } else { + s->data_block_counter = 0; + } + + // initialize packet buffer. + max_ctx_payload_size = amdtp_stream_get_max_payload(s); + if (s->direction == AMDTP_IN_STREAM) { + dir = DMA_FROM_DEVICE; + type = FW_ISO_CONTEXT_RECEIVE; + if (!(s->flags & CIP_NO_HEADER)) { + max_ctx_payload_size -= 8; + ctx_header_size = IR_CTX_HEADER_SIZE_CIP; + } else { + ctx_header_size = IR_CTX_HEADER_SIZE_NO_CIP; + } + } else { + dir = DMA_TO_DEVICE; + type = FW_ISO_CONTEXT_TRANSMIT; + ctx_header_size = 0; // No effect for IT context. + + if (!(s->flags & CIP_NO_HEADER)) + max_ctx_payload_size -= IT_PKT_HEADER_SIZE_CIP; + } + + err = iso_packets_buffer_init(&s->buffer, s->unit, queue_size, + max_ctx_payload_size, dir); + if (err < 0) + goto err_unlock; + s->queue_size = queue_size; + + s->context = fw_iso_context_create(fw_parent_device(s->unit)->card, + type, channel, speed, ctx_header_size, + amdtp_stream_first_callback, s); + if (IS_ERR(s->context)) { + err = PTR_ERR(s->context); + if (err == -EBUSY) + dev_err(&s->unit->device, + "no free stream on this controller\n"); + goto err_buffer; + } + + amdtp_stream_update(s); + + if (s->direction == AMDTP_IN_STREAM) { + s->ctx_data.tx.max_ctx_payload_length = max_ctx_payload_size; + s->ctx_data.tx.ctx_header_size = ctx_header_size; + } + + if (s->flags & CIP_NO_HEADER) + s->tag = TAG_NO_CIP_HEADER; + else + s->tag = TAG_CIP; + + s->pkt_descs = kcalloc(s->queue_size, sizeof(*s->pkt_descs), + GFP_KERNEL); + if (!s->pkt_descs) { + err = -ENOMEM; + goto err_context; + } + + s->packet_index = 0; + do { + struct fw_iso_packet params; + + if (s->direction == AMDTP_IN_STREAM) { + err = queue_in_packet(s, ¶ms); + } else { + bool sched_irq = false; + + params.header_length = 0; + params.payload_length = 0; + + if (is_irq_target) { + sched_irq = !((s->packet_index + 1) % + idle_irq_interval); + } + + err = queue_out_packet(s, ¶ms, sched_irq); + } + if (err < 0) + goto err_pkt_descs; + } while (s->packet_index > 0); + + /* NOTE: TAG1 matches CIP. This just affects in stream. */ + tag = FW_ISO_CONTEXT_MATCH_TAG1; + if ((s->flags & CIP_EMPTY_WITH_TAG0) || (s->flags & CIP_NO_HEADER)) + tag |= FW_ISO_CONTEXT_MATCH_TAG0; + + s->callbacked = false; + err = fw_iso_context_start(s->context, start_cycle, 0, tag); + if (err < 0) + goto err_pkt_descs; + + mutex_unlock(&s->mutex); + + return 0; +err_pkt_descs: + kfree(s->pkt_descs); +err_context: + fw_iso_context_destroy(s->context); + s->context = ERR_PTR(-1); +err_buffer: + iso_packets_buffer_destroy(&s->buffer, s->unit); +err_unlock: + mutex_unlock(&s->mutex); + + return err; +} + +/** + * amdtp_domain_stream_pcm_pointer - get the PCM buffer position + * @d: the AMDTP domain. + * @s: the AMDTP stream that transports the PCM data + * + * Returns the current buffer position, in frames. + */ +unsigned long amdtp_domain_stream_pcm_pointer(struct amdtp_domain *d, + struct amdtp_stream *s) +{ + struct amdtp_stream *irq_target = d->irq_target; + + if (irq_target && amdtp_stream_running(irq_target)) { + // This function is called in software IRQ context of + // period_work or process context. + // + // When the software IRQ context was scheduled by software IRQ + // context of IT contexts, queued packets were already handled. + // Therefore, no need to flush the queue in buffer furthermore. + // + // When the process context reach here, some packets will be + // already queued in the buffer. These packets should be handled + // immediately to keep better granularity of PCM pointer. + // + // Later, the process context will sometimes schedules software + // IRQ context of the period_work. Then, no need to flush the + // queue by the same reason as described in the above + if (current_work() != &s->period_work) { + // Queued packet should be processed without any kernel + // preemption to keep latency against bus cycle. + preempt_disable(); + fw_iso_context_flush_completions(irq_target->context); + preempt_enable(); + } + } + + return READ_ONCE(s->pcm_buffer_pointer); +} +EXPORT_SYMBOL_GPL(amdtp_domain_stream_pcm_pointer); + +/** + * amdtp_domain_stream_pcm_ack - acknowledge queued PCM frames + * @d: the AMDTP domain. + * @s: the AMDTP stream that transfers the PCM frames + * + * Returns zero always. + */ +int amdtp_domain_stream_pcm_ack(struct amdtp_domain *d, struct amdtp_stream *s) +{ + struct amdtp_stream *irq_target = d->irq_target; + + // Process isochronous packets for recent isochronous cycle to handle + // queued PCM frames. + if (irq_target && amdtp_stream_running(irq_target)) { + // Queued packet should be processed without any kernel + // preemption to keep latency against bus cycle. + preempt_disable(); + fw_iso_context_flush_completions(irq_target->context); + preempt_enable(); + } + + return 0; +} +EXPORT_SYMBOL_GPL(amdtp_domain_stream_pcm_ack); + +/** + * amdtp_stream_update - update the stream after a bus reset + * @s: the AMDTP stream + */ +void amdtp_stream_update(struct amdtp_stream *s) +{ + /* Precomputing. */ + WRITE_ONCE(s->source_node_id_field, + (fw_parent_device(s->unit)->card->node_id << CIP_SID_SHIFT) & CIP_SID_MASK); +} +EXPORT_SYMBOL(amdtp_stream_update); + +/** + * amdtp_stream_stop - stop sending packets + * @s: the AMDTP stream to stop + * + * All PCM and MIDI devices of the stream must be stopped before the stream + * itself can be stopped. + */ +static void amdtp_stream_stop(struct amdtp_stream *s) +{ + mutex_lock(&s->mutex); + + if (!amdtp_stream_running(s)) { + mutex_unlock(&s->mutex); + return; + } + + cancel_work_sync(&s->period_work); + fw_iso_context_stop(s->context); + fw_iso_context_destroy(s->context); + s->context = ERR_PTR(-1); + iso_packets_buffer_destroy(&s->buffer, s->unit); + kfree(s->pkt_descs); + + s->callbacked = false; + + mutex_unlock(&s->mutex); +} + +/** + * amdtp_stream_pcm_abort - abort the running PCM device + * @s: the AMDTP stream about to be stopped + * + * If the isochronous stream needs to be stopped asynchronously, call this + * function first to stop the PCM device. + */ +void amdtp_stream_pcm_abort(struct amdtp_stream *s) +{ + struct snd_pcm_substream *pcm; + + pcm = READ_ONCE(s->pcm); + if (pcm) + snd_pcm_stop_xrun(pcm); +} +EXPORT_SYMBOL(amdtp_stream_pcm_abort); + +/** + * amdtp_domain_init - initialize an AMDTP domain structure + * @d: the AMDTP domain to initialize. + */ +int amdtp_domain_init(struct amdtp_domain *d) +{ + INIT_LIST_HEAD(&d->streams); + + d->events_per_period = 0; + + d->seq_descs = NULL; + + return 0; +} +EXPORT_SYMBOL_GPL(amdtp_domain_init); + +/** + * amdtp_domain_destroy - destroy an AMDTP domain structure + * @d: the AMDTP domain to destroy. + */ +void amdtp_domain_destroy(struct amdtp_domain *d) +{ + // At present nothing to do. + return; +} +EXPORT_SYMBOL_GPL(amdtp_domain_destroy); + +/** + * amdtp_domain_add_stream - register isoc context into the domain. + * @d: the AMDTP domain. + * @s: the AMDTP stream. + * @channel: the isochronous channel on the bus. + * @speed: firewire speed code. + */ +int amdtp_domain_add_stream(struct amdtp_domain *d, struct amdtp_stream *s, + int channel, int speed) +{ + struct amdtp_stream *tmp; + + list_for_each_entry(tmp, &d->streams, list) { + if (s == tmp) + return -EBUSY; + } + + list_add(&s->list, &d->streams); + + s->channel = channel; + s->speed = speed; + s->domain = d; + + return 0; +} +EXPORT_SYMBOL_GPL(amdtp_domain_add_stream); + +static int get_current_cycle_time(struct fw_card *fw_card, int *cur_cycle) +{ + int generation; + int rcode; + __be32 reg; + u32 data; + + // This is a request to local 1394 OHCI controller and expected to + // complete without any event waiting. + generation = fw_card->generation; + smp_rmb(); // node_id vs. generation. + rcode = fw_run_transaction(fw_card, TCODE_READ_QUADLET_REQUEST, + fw_card->node_id, generation, SCODE_100, + CSR_REGISTER_BASE + CSR_CYCLE_TIME, + ®, sizeof(reg)); + if (rcode != RCODE_COMPLETE) + return -EIO; + + data = be32_to_cpu(reg); + *cur_cycle = data >> 12; + + return 0; +} + +/** + * amdtp_domain_start - start sending packets for isoc context in the domain. + * @d: the AMDTP domain. + * @ir_delay_cycle: the cycle delay to start all IR contexts. + */ +int amdtp_domain_start(struct amdtp_domain *d, unsigned int ir_delay_cycle) +{ + static const struct { + unsigned int data_block; + unsigned int syt_offset; + } *entry, initial_state[] = { + [CIP_SFC_32000] = { 4, 3072 }, + [CIP_SFC_48000] = { 6, 1024 }, + [CIP_SFC_96000] = { 12, 1024 }, + [CIP_SFC_192000] = { 24, 1024 }, + [CIP_SFC_44100] = { 0, 67 }, + [CIP_SFC_88200] = { 0, 67 }, + [CIP_SFC_176400] = { 0, 67 }, + }; + unsigned int events_per_buffer = d->events_per_buffer; + unsigned int events_per_period = d->events_per_period; + unsigned int idle_irq_interval; + unsigned int queue_size; + struct amdtp_stream *s; + int cycle; + bool found = false; + int err; + + // Select an IT context as IRQ target. + list_for_each_entry(s, &d->streams, list) { + if (s->direction == AMDTP_OUT_STREAM) { + found = true; + break; + } + } + if (!found) + return -ENXIO; + d->irq_target = s; + + // This is a case that AMDTP streams in domain run just for MIDI + // substream. Use the number of events equivalent to 10 msec as + // interval of hardware IRQ. + if (events_per_period == 0) + events_per_period = amdtp_rate_table[d->irq_target->sfc] / 100; + if (events_per_buffer == 0) + events_per_buffer = events_per_period * 3; + + queue_size = DIV_ROUND_UP(CYCLES_PER_SECOND * events_per_buffer, + amdtp_rate_table[d->irq_target->sfc]); + + d->seq_descs = kcalloc(queue_size, sizeof(*d->seq_descs), GFP_KERNEL); + if (!d->seq_descs) + return -ENOMEM; + d->seq_size = queue_size; + d->seq_tail = 0; + + entry = &initial_state[s->sfc]; + d->data_block_state = entry->data_block; + d->syt_offset_state = entry->syt_offset; + d->last_syt_offset = TICKS_PER_CYCLE; + + if (ir_delay_cycle > 0) { + struct fw_card *fw_card = fw_parent_device(s->unit)->card; + + err = get_current_cycle_time(fw_card, &cycle); + if (err < 0) + goto error; + + // No need to care overflow in cycle field because of enough + // width. + cycle += ir_delay_cycle; + + // Round up to sec field. + if ((cycle & 0x00001fff) >= CYCLES_PER_SECOND) { + unsigned int sec; + + // The sec field can overflow. + sec = (cycle & 0xffffe000) >> 13; + cycle = (++sec << 13) | + ((cycle & 0x00001fff) / CYCLES_PER_SECOND); + } + + // In OHCI 1394 specification, lower 2 bits are available for + // sec field. + cycle &= 0x00007fff; + } else { + cycle = -1; + } + + list_for_each_entry(s, &d->streams, list) { + int cycle_match; + + if (s->direction == AMDTP_IN_STREAM) { + cycle_match = cycle; + } else { + // IT context starts immediately. + cycle_match = -1; + s->ctx_data.rx.seq_index = 0; + } + + if (s != d->irq_target) { + err = amdtp_stream_start(s, s->channel, s->speed, + cycle_match, queue_size, 0); + if (err < 0) + goto error; + } + } + + s = d->irq_target; + s->ctx_data.rx.events_per_period = events_per_period; + s->ctx_data.rx.event_count = 0; + s->ctx_data.rx.seq_index = 0; + + idle_irq_interval = DIV_ROUND_UP(CYCLES_PER_SECOND * events_per_period, + amdtp_rate_table[d->irq_target->sfc]); + err = amdtp_stream_start(s, s->channel, s->speed, -1, queue_size, + idle_irq_interval); + if (err < 0) + goto error; + + return 0; +error: + list_for_each_entry(s, &d->streams, list) + amdtp_stream_stop(s); + kfree(d->seq_descs); + d->seq_descs = NULL; + return err; +} +EXPORT_SYMBOL_GPL(amdtp_domain_start); + +/** + * amdtp_domain_stop - stop sending packets for isoc context in the same domain. + * @d: the AMDTP domain to which the isoc contexts belong. + */ +void amdtp_domain_stop(struct amdtp_domain *d) +{ + struct amdtp_stream *s, *next; + + if (d->irq_target) + amdtp_stream_stop(d->irq_target); + + list_for_each_entry_safe(s, next, &d->streams, list) { + list_del(&s->list); + + if (s != d->irq_target) + amdtp_stream_stop(s); + } + + d->events_per_period = 0; + d->irq_target = NULL; + + kfree(d->seq_descs); + d->seq_descs = NULL; +} +EXPORT_SYMBOL_GPL(amdtp_domain_stop); diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h new file mode 100644 index 000000000..2ceb57d1d --- /dev/null +++ b/sound/firewire/amdtp-stream.h @@ -0,0 +1,322 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef SOUND_FIREWIRE_AMDTP_H_INCLUDED +#define SOUND_FIREWIRE_AMDTP_H_INCLUDED + +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <sound/asound.h> +#include "packets-buffer.h" + +/** + * enum cip_flags - describes details of the streaming protocol + * @CIP_NONBLOCKING: In non-blocking mode, each packet contains + * sample_rate/8000 samples, with rounding up or down to adjust + * for clock skew and left-over fractional samples. This should + * be used if supported by the device. + * @CIP_BLOCKING: In blocking mode, each packet contains either zero or + * SYT_INTERVAL samples, with these two types alternating so that + * the overall sample rate comes out right. + * @CIP_EMPTY_WITH_TAG0: Only for in-stream. Empty in-packets have TAG0. + * @CIP_DBC_IS_END_EVENT: The value of dbc in an packet corresponds to the end + * of event in the packet. Out of IEC 61883. + * @CIP_WRONG_DBS: Only for in-stream. The value of dbs is wrong in in-packets. + * The value of data_block_quadlets is used instead of reported value. + * @CIP_SKIP_DBC_ZERO_CHECK: Only for in-stream. Packets with zero in dbc is + * skipped for detecting discontinuity. + * @CIP_EMPTY_HAS_WRONG_DBC: Only for in-stream. The value of dbc in empty + * packet is wrong but the others are correct. + * @CIP_JUMBO_PAYLOAD: Only for in-stream. The number of data blocks in an + * packet is larger than IEC 61883-6 defines. Current implementation + * allows 5 times as large as IEC 61883-6 defines. + * @CIP_HEADER_WITHOUT_EOH: Only for in-stream. CIP Header doesn't include + * valid EOH. + * @CIP_NO_HEADERS: a lack of headers in packets + * @CIP_UNALIGHED_DBC: Only for in-stream. The value of dbc is not alighed to + * the value of current SYT_INTERVAL; e.g. initial value is not zero. + */ +enum cip_flags { + CIP_NONBLOCKING = 0x00, + CIP_BLOCKING = 0x01, + CIP_EMPTY_WITH_TAG0 = 0x02, + CIP_DBC_IS_END_EVENT = 0x04, + CIP_WRONG_DBS = 0x08, + CIP_SKIP_DBC_ZERO_CHECK = 0x10, + CIP_EMPTY_HAS_WRONG_DBC = 0x20, + CIP_JUMBO_PAYLOAD = 0x40, + CIP_HEADER_WITHOUT_EOH = 0x80, + CIP_NO_HEADER = 0x100, + CIP_UNALIGHED_DBC = 0x200, +}; + +/** + * enum cip_sfc - supported Sampling Frequency Codes (SFCs) + * @CIP_SFC_32000: 32,000 data blocks + * @CIP_SFC_44100: 44,100 data blocks + * @CIP_SFC_48000: 48,000 data blocks + * @CIP_SFC_88200: 88,200 data blocks + * @CIP_SFC_96000: 96,000 data blocks + * @CIP_SFC_176400: 176,400 data blocks + * @CIP_SFC_192000: 192,000 data blocks + * @CIP_SFC_COUNT: the number of supported SFCs + * + * These values are used to show nominal Sampling Frequency Code in + * Format Dependent Field (FDF) of AMDTP packet header. In IEC 61883-6:2002, + * this code means the number of events per second. Actually the code + * represents the number of data blocks transferred per second in an AMDTP + * stream. + * + * In IEC 61883-6:2005, some extensions were added to support more types of + * data such as 'One Bit LInear Audio', therefore the meaning of SFC became + * different depending on the types. + * + * Currently our implementation is compatible with IEC 61883-6:2002. + */ +enum cip_sfc { + CIP_SFC_32000 = 0, + CIP_SFC_44100 = 1, + CIP_SFC_48000 = 2, + CIP_SFC_88200 = 3, + CIP_SFC_96000 = 4, + CIP_SFC_176400 = 5, + CIP_SFC_192000 = 6, + CIP_SFC_COUNT +}; + +struct fw_unit; +struct fw_iso_context; +struct snd_pcm_substream; +struct snd_pcm_runtime; + +enum amdtp_stream_direction { + AMDTP_OUT_STREAM = 0, + AMDTP_IN_STREAM +}; + +struct pkt_desc { + u32 cycle; + u32 syt; + unsigned int data_blocks; + unsigned int data_block_counter; + __be32 *ctx_payload; +}; + +struct amdtp_stream; +typedef unsigned int (*amdtp_stream_process_ctx_payloads_t)( + struct amdtp_stream *s, + const struct pkt_desc *desc, + unsigned int packets, + struct snd_pcm_substream *pcm); + +struct amdtp_domain; +struct amdtp_stream { + struct fw_unit *unit; + enum cip_flags flags; + enum amdtp_stream_direction direction; + struct mutex mutex; + + /* For packet processing. */ + struct fw_iso_context *context; + struct iso_packets_buffer buffer; + unsigned int queue_size; + int packet_index; + struct pkt_desc *pkt_descs; + int tag; + union { + struct { + unsigned int ctx_header_size; + + // limit for payload of iso packet. + unsigned int max_ctx_payload_length; + + // For quirks of CIP headers. + // Fixed interval of dbc between previos/current + // packets. + unsigned int dbc_interval; + } tx; + struct { + // To calculate CIP data blocks and tstamp. + unsigned int transfer_delay; + unsigned int seq_index; + + // To generate CIP header. + unsigned int fdf; + int syt_override; + + // To generate constant hardware IRQ. + unsigned int event_count; + unsigned int events_per_period; + } rx; + } ctx_data; + + /* For CIP headers. */ + unsigned int source_node_id_field; + unsigned int data_block_quadlets; + unsigned int data_block_counter; + unsigned int sph; + unsigned int fmt; + + /* Internal flags. */ + enum cip_sfc sfc; + unsigned int syt_interval; + + /* For a PCM substream processing. */ + struct snd_pcm_substream *pcm; + struct work_struct period_work; + snd_pcm_uframes_t pcm_buffer_pointer; + unsigned int pcm_period_pointer; + + /* To wait for first packet. */ + bool callbacked; + wait_queue_head_t callback_wait; + u32 start_cycle; + + /* For backends to process data blocks. */ + void *protocol; + amdtp_stream_process_ctx_payloads_t process_ctx_payloads; + + // For domain. + int channel; + int speed; + struct list_head list; + struct amdtp_domain *domain; +}; + +int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, enum cip_flags flags, + unsigned int fmt, + amdtp_stream_process_ctx_payloads_t process_ctx_payloads, + unsigned int protocol_size); +void amdtp_stream_destroy(struct amdtp_stream *s); + +int amdtp_stream_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int data_block_quadlets); +unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s); + +void amdtp_stream_update(struct amdtp_stream *s); + +int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); + +void amdtp_stream_pcm_prepare(struct amdtp_stream *s); +void amdtp_stream_pcm_abort(struct amdtp_stream *s); + +extern const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT]; +extern const unsigned int amdtp_rate_table[CIP_SFC_COUNT]; + +/** + * amdtp_stream_running - check stream is running or not + * @s: the AMDTP stream + * + * If this function returns true, the stream is running. + */ +static inline bool amdtp_stream_running(struct amdtp_stream *s) +{ + return !IS_ERR(s->context); +} + +/** + * amdtp_streaming_error - check for streaming error + * @s: the AMDTP stream + * + * If this function returns true, the stream's packet queue has stopped due to + * an asynchronous error. + */ +static inline bool amdtp_streaming_error(struct amdtp_stream *s) +{ + return s->packet_index < 0; +} + +/** + * amdtp_stream_pcm_running - check PCM substream is running or not + * @s: the AMDTP stream + * + * If this function returns true, PCM substream in the AMDTP stream is running. + */ +static inline bool amdtp_stream_pcm_running(struct amdtp_stream *s) +{ + return !!s->pcm; +} + +/** + * amdtp_stream_pcm_trigger - start/stop playback from a PCM device + * @s: the AMDTP stream + * @pcm: the PCM device to be started, or %NULL to stop the current device + * + * Call this function on a running isochronous stream to enable the actual + * transmission of PCM data. This function should be called from the PCM + * device's .trigger callback. + */ +static inline void amdtp_stream_pcm_trigger(struct amdtp_stream *s, + struct snd_pcm_substream *pcm) +{ + WRITE_ONCE(s->pcm, pcm); +} + +static inline bool cip_sfc_is_base_44100(enum cip_sfc sfc) +{ + return sfc & 1; +} + +/** + * amdtp_stream_wait_callback - sleep till callbacked or timeout + * @s: the AMDTP stream + * @timeout: msec till timeout + * + * If this function return false, the AMDTP stream should be stopped. + */ +static inline bool amdtp_stream_wait_callback(struct amdtp_stream *s, + unsigned int timeout) +{ + return wait_event_timeout(s->callback_wait, + s->callbacked == true, + msecs_to_jiffies(timeout)) > 0; +} + +struct seq_desc { + unsigned int syt_offset; + unsigned int data_blocks; +}; + +struct amdtp_domain { + struct list_head streams; + + unsigned int events_per_period; + unsigned int events_per_buffer; + + struct amdtp_stream *irq_target; + + struct seq_desc *seq_descs; + unsigned int seq_size; + unsigned int seq_tail; + + unsigned int data_block_state; + unsigned int syt_offset_state; + unsigned int last_syt_offset; +}; + +int amdtp_domain_init(struct amdtp_domain *d); +void amdtp_domain_destroy(struct amdtp_domain *d); + +int amdtp_domain_add_stream(struct amdtp_domain *d, struct amdtp_stream *s, + int channel, int speed); + +int amdtp_domain_start(struct amdtp_domain *d, unsigned int ir_delay_cycle); +void amdtp_domain_stop(struct amdtp_domain *d); + +static inline int amdtp_domain_set_events_per_period(struct amdtp_domain *d, + unsigned int events_per_period, + unsigned int events_per_buffer) +{ + d->events_per_period = events_per_period; + d->events_per_buffer = events_per_buffer; + + return 0; +} + +unsigned long amdtp_domain_stream_pcm_pointer(struct amdtp_domain *d, + struct amdtp_stream *s); +int amdtp_domain_stream_pcm_ack(struct amdtp_domain *d, struct amdtp_stream *s); + +#endif diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile new file mode 100644 index 000000000..14bc84c51 --- /dev/null +++ b/sound/firewire/bebob/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob_midi.o \ + bebob_pcm.o bebob_hwdep.o bebob_terratec.o \ + bebob_yamaha_terratec.o bebob_focusrite.o bebob_maudio.o \ + bebob.o +obj-$(CONFIG_SND_BEBOB) += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c new file mode 100644 index 000000000..67fa0f217 --- /dev/null +++ b/sound/firewire/bebob/bebob.c @@ -0,0 +1,537 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * bebob.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + */ + +/* + * BeBoB is 'BridgeCo enhanced Breakout Box'. This is installed to firewire + * devices with DM1000/DM1100/DM1500 chipset. It gives common way for host + * system to handle BeBoB based devices. + */ + +#include "bebob.h" + +MODULE_DESCRIPTION("BridgeCo BeBoB driver"); +MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>"); +MODULE_LICENSE("GPL v2"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "card index"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string"); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "enable BeBoB sound card"); + +static DEFINE_MUTEX(devices_mutex); +static DECLARE_BITMAP(devices_used, SNDRV_CARDS); + +/* Offsets from information register. */ +#define INFO_OFFSET_BEBOB_VERSION 0x08 +#define INFO_OFFSET_GUID 0x10 +#define INFO_OFFSET_HW_MODEL_ID 0x18 +#define INFO_OFFSET_HW_MODEL_REVISION 0x1c + +#define VEN_EDIROL 0x000040ab +#define VEN_PRESONUS 0x00000a92 +#define VEN_BRIDGECO 0x000007f5 +#define VEN_MACKIE1 0x0000000f +#define VEN_MACKIE2 0x00000ff2 +#define VEN_STANTON 0x00001260 +#define VEN_TASCAM 0x0000022e +#define VEN_BEHRINGER 0x00001564 +#define VEN_APOGEE 0x000003db +#define VEN_ESI 0x00000f1b +#define VEN_ACOUSTIC 0x00000002 +#define VEN_CME 0x0000000a +#define VEN_PHONIC 0x00001496 +#define VEN_LYNX 0x000019e5 +#define VEN_ICON 0x00001a9e +#define VEN_PRISMSOUND 0x00001198 +#define VEN_TERRATEC 0x00000aac +#define VEN_YAMAHA 0x0000a0de +#define VEN_FOCUSRITE 0x0000130e +#define VEN_MAUDIO1 0x00000d6c +#define VEN_MAUDIO2 0x000007f5 +#define VEN_DIGIDESIGN 0x00a07e +#define OUI_SHOUYO 0x002327 + +#define MODEL_FOCUSRITE_SAFFIRE_BOTH 0x00000000 +#define MODEL_MAUDIO_AUDIOPHILE_BOTH 0x00010060 +#define MODEL_MAUDIO_FW1814 0x00010071 +#define MODEL_MAUDIO_PROJECTMIX 0x00010091 + +static int +name_device(struct snd_bebob *bebob) +{ + struct fw_device *fw_dev = fw_parent_device(bebob->unit); + char vendor[24] = {0}; + char model[32] = {0}; + u32 hw_id; + u32 data[2] = {0}; + u32 revision; + u32 version; + int err; + + /* get vendor name from root directory */ + err = fw_csr_string(fw_dev->config_rom + 5, CSR_VENDOR, + vendor, sizeof(vendor)); + if (err < 0) + goto end; + + /* get model name from unit directory */ + err = fw_csr_string(bebob->unit->directory, CSR_MODEL, + model, sizeof(model)); + if (err < 0) + goto end; + + /* get hardware id */ + err = snd_bebob_read_quad(bebob->unit, INFO_OFFSET_HW_MODEL_ID, + &hw_id); + if (err < 0) + goto end; + + /* get hardware revision */ + err = snd_bebob_read_quad(bebob->unit, INFO_OFFSET_HW_MODEL_REVISION, + &revision); + if (err < 0) + goto end; + + /* get GUID */ + err = snd_bebob_read_block(bebob->unit, INFO_OFFSET_GUID, + data, sizeof(data)); + if (err < 0) + goto end; + + err = snd_bebob_read_quad(bebob->unit, INFO_OFFSET_BEBOB_VERSION, + &version); + if (err < 0) + goto end; + bebob->version = version; + + strcpy(bebob->card->driver, "BeBoB"); + strcpy(bebob->card->shortname, model); + strcpy(bebob->card->mixername, model); + snprintf(bebob->card->longname, sizeof(bebob->card->longname), + "%s %s (id:%d, rev:%d), GUID %08x%08x at %s, S%d", + vendor, model, hw_id, revision, + data[0], data[1], dev_name(&bebob->unit->device), + 100 << fw_dev->max_speed); +end: + return err; +} + +static void +bebob_card_free(struct snd_card *card) +{ + struct snd_bebob *bebob = card->private_data; + + mutex_lock(&devices_mutex); + clear_bit(bebob->card_index, devices_used); + mutex_unlock(&devices_mutex); + + snd_bebob_stream_destroy_duplex(bebob); +} + +static const struct snd_bebob_spec * +get_saffire_spec(struct fw_unit *unit) +{ + char name[24] = {0}; + + if (fw_csr_string(unit->directory, CSR_MODEL, name, sizeof(name)) < 0) + return NULL; + + if (strcmp(name, "SaffireLE") == 0) + return &saffire_le_spec; + else + return &saffire_spec; +} + +static bool +check_audiophile_booted(struct fw_unit *unit) +{ + char name[28] = {0}; + + if (fw_csr_string(unit->directory, CSR_MODEL, name, sizeof(name)) < 0) + return false; + + return strncmp(name, "FW Audiophile Bootloader", 24) != 0; +} + +static void +do_registration(struct work_struct *work) +{ + struct snd_bebob *bebob = + container_of(work, struct snd_bebob, dwork.work); + unsigned int card_index; + int err; + + if (bebob->registered) + return; + + mutex_lock(&devices_mutex); + for (card_index = 0; card_index < SNDRV_CARDS; card_index++) { + if (!test_bit(card_index, devices_used) && enable[card_index]) + break; + } + if (card_index >= SNDRV_CARDS) { + mutex_unlock(&devices_mutex); + return; + } + + err = snd_card_new(&bebob->unit->device, index[card_index], + id[card_index], THIS_MODULE, 0, &bebob->card); + if (err < 0) { + mutex_unlock(&devices_mutex); + return; + } + set_bit(card_index, devices_used); + mutex_unlock(&devices_mutex); + + bebob->card->private_free = bebob_card_free; + bebob->card->private_data = bebob; + + err = name_device(bebob); + if (err < 0) + goto error; + + if (bebob->spec == &maudio_special_spec) { + if (bebob->entry->model_id == MODEL_MAUDIO_FW1814) + err = snd_bebob_maudio_special_discover(bebob, true); + else + err = snd_bebob_maudio_special_discover(bebob, false); + } else { + err = snd_bebob_stream_discover(bebob); + } + if (err < 0) + goto error; + + err = snd_bebob_stream_init_duplex(bebob); + if (err < 0) + goto error; + + snd_bebob_proc_init(bebob); + + if (bebob->midi_input_ports > 0 || bebob->midi_output_ports > 0) { + err = snd_bebob_create_midi_devices(bebob); + if (err < 0) + goto error; + } + + err = snd_bebob_create_pcm_devices(bebob); + if (err < 0) + goto error; + + err = snd_bebob_create_hwdep_device(bebob); + if (err < 0) + goto error; + + err = snd_card_register(bebob->card); + if (err < 0) + goto error; + + bebob->registered = true; + + return; +error: + snd_card_free(bebob->card); + dev_info(&bebob->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int +bebob_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry) +{ + struct snd_bebob *bebob; + const struct snd_bebob_spec *spec; + + if (entry->vendor_id == VEN_FOCUSRITE && + entry->model_id == MODEL_FOCUSRITE_SAFFIRE_BOTH) + spec = get_saffire_spec(unit); + else if (entry->vendor_id == VEN_MAUDIO1 && + entry->model_id == MODEL_MAUDIO_AUDIOPHILE_BOTH && + !check_audiophile_booted(unit)) + spec = NULL; + else + spec = (const struct snd_bebob_spec *)entry->driver_data; + + if (spec == NULL) { + if (entry->vendor_id == VEN_MAUDIO1 || + entry->vendor_id == VEN_MAUDIO2) + return snd_bebob_maudio_load_firmware(unit); + else + return -ENODEV; + } + + /* Allocate this independent of sound card instance. */ + bebob = devm_kzalloc(&unit->device, sizeof(struct snd_bebob), + GFP_KERNEL); + if (!bebob) + return -ENOMEM; + bebob->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, bebob); + + bebob->entry = entry; + bebob->spec = spec; + mutex_init(&bebob->mutex); + spin_lock_init(&bebob->lock); + init_waitqueue_head(&bebob->hwdep_wait); + + /* Allocate and register this sound card later. */ + INIT_DEFERRABLE_WORK(&bebob->dwork, do_registration); + + if (entry->vendor_id != VEN_MAUDIO1 || + (entry->model_id != MODEL_MAUDIO_FW1814 && + entry->model_id != MODEL_MAUDIO_PROJECTMIX)) { + snd_fw_schedule_registration(unit, &bebob->dwork); + } else { + /* + * This is a workaround. This bus reset seems to have an effect + * to make devices correctly handling transactions. Without + * this, the devices have gap_count mismatch. This causes much + * failure of transaction. + * + * Just after registration, user-land application receive + * signals from dbus and starts I/Os. To avoid I/Os till the + * future bus reset, registration is done in next update(). + */ + fw_schedule_bus_reset(fw_parent_device(bebob->unit)->card, + false, true); + } + + return 0; +} + +/* + * This driver doesn't update streams in bus reset handler. + * + * DM1000/ DM1100/DM1500 chipsets with BeBoB firmware transfer packets with + * discontinued counter at bus reset. This discontinuity is immediately + * detected in packet streaming layer, then it sets XRUN to PCM substream. + * + * ALSA PCM applications can know the XRUN by getting -EPIPE from PCM operation. + * Then, they can recover the PCM substream by executing ioctl(2) with + * SNDRV_PCM_IOCTL_PREPARE. 'struct snd_pcm_ops.prepare' is called and drivers + * restart packet streaming. + * + * The above processing may be executed before this bus-reset handler is + * executed. When this handler updates streams with current isochronous + * channels, the streams already have the current ones. + */ +static void +bebob_update(struct fw_unit *unit) +{ + struct snd_bebob *bebob = dev_get_drvdata(&unit->device); + + if (bebob == NULL) + return; + + /* Postpone a workqueue for deferred registration. */ + if (!bebob->registered) + snd_fw_schedule_registration(unit, &bebob->dwork); + else + fcp_bus_reset(bebob->unit); +} + +static void bebob_remove(struct fw_unit *unit) +{ + struct snd_bebob *bebob = dev_get_drvdata(&unit->device); + + if (bebob == NULL) + return; + + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_delayed_work_sync(&bebob->dwork); + + if (bebob->registered) { + // Block till all of ALSA character devices are released. + snd_card_free(bebob->card); + } + + mutex_destroy(&bebob->mutex); + fw_unit_put(bebob->unit); +} + +static const struct snd_bebob_rate_spec normal_rate_spec = { + .get = &snd_bebob_stream_get_rate, + .set = &snd_bebob_stream_set_rate +}; +static const struct snd_bebob_spec spec_normal = { + .clock = NULL, + .rate = &normal_rate_spec, + .meter = NULL +}; + +static const struct ieee1394_device_id bebob_id_table[] = { + /* Edirol, FA-66 */ + SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010049, &spec_normal), + /* Edirol, FA-101 */ + SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010048, &spec_normal), + /* Presonus, FIREBOX */ + SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010000, &spec_normal), + /* PreSonus, FIREPOD/FP10 */ + SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010066, &spec_normal), + /* PreSonus, Inspire1394 */ + SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010001, &spec_normal), + /* BridgeCo, RDAudio1 */ + SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010048, &spec_normal), + /* BridgeCo, Audio5 */ + SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010049, &spec_normal), + /* Mackie, Onyx 1220/1620/1640 (Firewire I/O Card) */ + SND_BEBOB_DEV_ENTRY(VEN_MACKIE2, 0x00010065, &spec_normal), + // Mackie, d.2 (optional Firewire card with DM1000). + SND_BEBOB_DEV_ENTRY(VEN_MACKIE1, 0x00010067, &spec_normal), + /* Stanton, ScratchAmp */ + SND_BEBOB_DEV_ENTRY(VEN_STANTON, 0x00000001, &spec_normal), + /* Tascam, IF-FW DM */ + SND_BEBOB_DEV_ENTRY(VEN_TASCAM, 0x00010067, &spec_normal), + /* Behringer, XENIX UFX 1204 */ + SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00001204, &spec_normal), + /* Behringer, XENIX UFX 1604 */ + SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00001604, &spec_normal), + /* Behringer, Digital Mixer X32 series (X-UF Card) */ + SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00000006, &spec_normal), + /* Behringer, F-Control Audio 1616 */ + SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x001616, &spec_normal), + /* Behringer, F-Control Audio 610 */ + SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x000610, &spec_normal), + /* Apogee Electronics, Rosetta 200/400 (X-FireWire card) */ + /* Apogee Electronics, DA/AD/DD-16X (X-FireWire card) */ + SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x00010048, &spec_normal), + /* Apogee Electronics, Ensemble */ + SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x01eeee, &spec_normal), + /* ESI, Quatafire610 */ + SND_BEBOB_DEV_ENTRY(VEN_ESI, 0x00010064, &spec_normal), + /* AcousticReality, eARMasterOne */ + SND_BEBOB_DEV_ENTRY(VEN_ACOUSTIC, 0x00000002, &spec_normal), + /* CME, MatrixKFW */ + SND_BEBOB_DEV_ENTRY(VEN_CME, 0x00030000, &spec_normal), + /* Phonic, Helix Board 12 MkII */ + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00050000, &spec_normal), + /* Phonic, Helix Board 18 MkII */ + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00060000, &spec_normal), + /* Phonic, Helix Board 24 MkII */ + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00070000, &spec_normal), + /* Phonic, Helix Board 12 Universal/18 Universal/24 Universal */ + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00000000, &spec_normal), + /* Lynx, Aurora 8/16 (LT-FW) */ + SND_BEBOB_DEV_ENTRY(VEN_LYNX, 0x00000001, &spec_normal), + /* ICON, FireXon */ + SND_BEBOB_DEV_ENTRY(VEN_ICON, 0x00000001, &spec_normal), + /* PrismSound, Orpheus */ + SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x00010048, &spec_normal), + /* PrismSound, ADA-8XR */ + SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x0000ada8, &spec_normal), + /* TerraTec Electronic GmbH, PHASE 88 Rack FW */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000003, &phase88_rack_spec), + /* TerraTec Electronic GmbH, PHASE 24 FW */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000004, &yamaha_terratec_spec), + /* TerraTec Electronic GmbH, Phase X24 FW */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000007, &yamaha_terratec_spec), + /* TerraTec Electronic GmbH, EWS MIC2/MIC8 */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000005, &spec_normal), + /* Terratec Electronic GmbH, Aureon 7.1 Firewire */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000002, &spec_normal), + /* Yamaha, GO44 */ + SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000b, &yamaha_terratec_spec), + /* YAMAHA, GO46 */ + SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000c, &yamaha_terratec_spec), + /* Focusrite, SaffirePro 26 I/O */ + SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, 0x00000003, &saffirepro_26_spec), + /* Focusrite, SaffirePro 10 I/O */ + { + // The combination of vendor_id and model_id is the same as the + // same as the one of Liquid Saffire 56. + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = VEN_FOCUSRITE, + .model_id = 0x000006, + .specifier_id = 0x00a02d, + .version = 0x010001, + .driver_data = (kernel_ulong_t)&saffirepro_10_spec, + }, + /* Focusrite, Saffire(no label and LE) */ + SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, MODEL_FOCUSRITE_SAFFIRE_BOTH, + &saffire_spec), + /* M-Audio, Firewire 410 */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO2, 0x00010058, NULL), /* bootloader */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO2, 0x00010046, &maudio_fw410_spec), + /* M-Audio, Firewire Audiophile */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_AUDIOPHILE_BOTH, + &maudio_audiophile_spec), + /* M-Audio, Firewire Solo */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010062, &maudio_solo_spec), + /* M-Audio, Ozonic */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x0000000a, &maudio_ozonic_spec), + /* M-Audio NRV10 */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010081, &maudio_nrv10_spec), + /* M-Audio, ProFireLightbridge */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x000100a1, &spec_normal), + /* Firewire 1814 */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010070, NULL), /* bootloader */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_FW1814, + &maudio_special_spec), + /* M-Audio ProjectMix */ + SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_PROJECTMIX, + &maudio_special_spec), + /* Digidesign Mbox 2 Pro */ + SND_BEBOB_DEV_ENTRY(VEN_DIGIDESIGN, 0x0000a9, &spec_normal), + // Toneweal FW66. + SND_BEBOB_DEV_ENTRY(OUI_SHOUYO, 0x020002, &spec_normal), + /* IDs are unknown but able to be supported */ + /* Apogee, Mini-ME Firewire */ + /* Apogee, Mini-DAC Firewire */ + /* Cakawalk, Sonar Power Studio 66 */ + /* CME, UF400e */ + /* ESI, Quotafire XL */ + /* Infrasonic, DewX */ + /* Infrasonic, Windy6 */ + /* Mackie, Digital X Bus x.200 */ + /* Mackie, Digital X Bus x.400 */ + /* Phonic, HB 12 */ + /* Phonic, HB 24 */ + /* Phonic, HB 18 */ + /* Phonic, FireFly 202 */ + /* Phonic, FireFly 302 */ + /* Rolf Spuler, Firewire Guitar */ + {} +}; +MODULE_DEVICE_TABLE(ieee1394, bebob_id_table); + +static struct fw_driver bebob_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + }, + .probe = bebob_probe, + .update = bebob_update, + .remove = bebob_remove, + .id_table = bebob_id_table, +}; + +static int __init +snd_bebob_init(void) +{ + return driver_register(&bebob_driver.driver); +} + +static void __exit +snd_bebob_exit(void) +{ + driver_unregister(&bebob_driver.driver); +} + +module_init(snd_bebob_init); +module_exit(snd_bebob_exit); diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h new file mode 100644 index 000000000..d1ad9a845 --- /dev/null +++ b/sound/firewire/bebob/bebob.h @@ -0,0 +1,264 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * bebob.h - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + */ + +#ifndef SOUND_BEBOB_H_INCLUDED +#define SOUND_BEBOB_H_INCLUDED + +#include <linux/compat.h> +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/sched/signal.h> + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/info.h> +#include <sound/rawmidi.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/firewire.h> +#include <sound/hwdep.h> + +#include "../lib.h" +#include "../fcp.h" +#include "../packets-buffer.h" +#include "../iso-resources.h" +#include "../amdtp-am824.h" +#include "../cmp.h" + +/* basic register addresses on DM1000/DM1100/DM1500 */ +#define BEBOB_ADDR_REG_INFO 0xffffc8020000ULL +#define BEBOB_ADDR_REG_REQ 0xffffc8021000ULL + +struct snd_bebob; + +#define SND_BEBOB_STRM_FMT_ENTRIES 7 +struct snd_bebob_stream_formation { + unsigned int pcm; + unsigned int midi; +}; +/* this is a lookup table for index of stream formations */ +extern const unsigned int snd_bebob_rate_table[SND_BEBOB_STRM_FMT_ENTRIES]; + +/* device specific operations */ +enum snd_bebob_clock_type { + SND_BEBOB_CLOCK_TYPE_INTERNAL = 0, + SND_BEBOB_CLOCK_TYPE_EXTERNAL, + SND_BEBOB_CLOCK_TYPE_SYT, +}; +struct snd_bebob_clock_spec { + unsigned int num; + const char *const *labels; + const enum snd_bebob_clock_type *types; + int (*get)(struct snd_bebob *bebob, unsigned int *id); +}; +struct snd_bebob_rate_spec { + int (*get)(struct snd_bebob *bebob, unsigned int *rate); + int (*set)(struct snd_bebob *bebob, unsigned int rate); +}; +struct snd_bebob_meter_spec { + unsigned int num; + const char *const *labels; + int (*get)(struct snd_bebob *bebob, u32 *target, unsigned int size); +}; +struct snd_bebob_spec { + const struct snd_bebob_clock_spec *clock; + const struct snd_bebob_rate_spec *rate; + const struct snd_bebob_meter_spec *meter; +}; + +struct snd_bebob { + struct snd_card *card; + struct fw_unit *unit; + int card_index; + + struct mutex mutex; + spinlock_t lock; + + bool registered; + struct delayed_work dwork; + + const struct ieee1394_device_id *entry; + const struct snd_bebob_spec *spec; + + unsigned int midi_input_ports; + unsigned int midi_output_ports; + + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + struct cmp_connection out_conn; + struct cmp_connection in_conn; + unsigned int substreams_counter; + + struct snd_bebob_stream_formation + tx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES]; + struct snd_bebob_stream_formation + rx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES]; + + int sync_input_plug; + + /* for uapi */ + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; + + /* for M-Audio special devices */ + void *maudio_special_quirk; + + /* For BeBoB version quirk. */ + unsigned int version; + + struct amdtp_domain domain; +}; + +static inline int +snd_bebob_read_block(struct fw_unit *unit, u64 addr, void *buf, int size) +{ + return snd_fw_transaction(unit, TCODE_READ_BLOCK_REQUEST, + BEBOB_ADDR_REG_INFO + addr, + buf, size, 0); +} + +static inline int +snd_bebob_read_quad(struct fw_unit *unit, u64 addr, u32 *buf) +{ + return snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, + BEBOB_ADDR_REG_INFO + addr, + (void *)buf, sizeof(u32), 0); +} + +/* AV/C Audio Subunit Specification 1.0 (Oct 2000, 1394TA) */ +int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id, + unsigned int fb_id, unsigned int num); +int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id, + unsigned int fb_id, unsigned int *num); + +/* + * AVC command extensions, AV/C Unit and Subunit, Revision 17 + * (Nov 2003, BridgeCo) + */ +#define AVC_BRIDGECO_ADDR_BYTES 6 +enum avc_bridgeco_plug_dir { + AVC_BRIDGECO_PLUG_DIR_IN = 0x00, + AVC_BRIDGECO_PLUG_DIR_OUT = 0x01 +}; +enum avc_bridgeco_plug_mode { + AVC_BRIDGECO_PLUG_MODE_UNIT = 0x00, + AVC_BRIDGECO_PLUG_MODE_SUBUNIT = 0x01, + AVC_BRIDGECO_PLUG_MODE_FUNCTION_BLOCK = 0x02 +}; +enum avc_bridgeco_plug_unit { + AVC_BRIDGECO_PLUG_UNIT_ISOC = 0x00, + AVC_BRIDGECO_PLUG_UNIT_EXT = 0x01, + AVC_BRIDGECO_PLUG_UNIT_ASYNC = 0x02 +}; +enum avc_bridgeco_plug_type { + AVC_BRIDGECO_PLUG_TYPE_ISOC = 0x00, + AVC_BRIDGECO_PLUG_TYPE_ASYNC = 0x01, + AVC_BRIDGECO_PLUG_TYPE_MIDI = 0x02, + AVC_BRIDGECO_PLUG_TYPE_SYNC = 0x03, + AVC_BRIDGECO_PLUG_TYPE_ANA = 0x04, + AVC_BRIDGECO_PLUG_TYPE_DIG = 0x05, + AVC_BRIDGECO_PLUG_TYPE_ADDITION = 0x06 +}; +static inline void +avc_bridgeco_fill_unit_addr(u8 buf[AVC_BRIDGECO_ADDR_BYTES], + enum avc_bridgeco_plug_dir dir, + enum avc_bridgeco_plug_unit unit, + unsigned int pid) +{ + buf[0] = 0xff; /* Unit */ + buf[1] = dir; + buf[2] = AVC_BRIDGECO_PLUG_MODE_UNIT; + buf[3] = unit; + buf[4] = 0xff & pid; + buf[5] = 0xff; /* reserved */ +} +static inline void +avc_bridgeco_fill_msu_addr(u8 buf[AVC_BRIDGECO_ADDR_BYTES], + enum avc_bridgeco_plug_dir dir, + unsigned int pid) +{ + buf[0] = 0x60; /* Music subunit */ + buf[1] = dir; + buf[2] = AVC_BRIDGECO_PLUG_MODE_SUBUNIT; + buf[3] = 0xff & pid; + buf[4] = 0xff; /* reserved */ + buf[5] = 0xff; /* reserved */ +} +int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + u8 *buf, unsigned int len); +int avc_bridgeco_get_plug_type(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + enum avc_bridgeco_plug_type *type); +int avc_bridgeco_get_plug_section_type(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + unsigned int id, u8 *type); +int avc_bridgeco_get_plug_input(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + u8 input[7]); +int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 *buf, + unsigned int *len, unsigned int eid); + +/* for AMDTP streaming */ +int snd_bebob_stream_get_rate(struct snd_bebob *bebob, unsigned int *rate); +int snd_bebob_stream_set_rate(struct snd_bebob *bebob, unsigned int rate); +int snd_bebob_stream_get_clock_src(struct snd_bebob *bebob, + enum snd_bebob_clock_type *src); +int snd_bebob_stream_discover(struct snd_bebob *bebob); +int snd_bebob_stream_init_duplex(struct snd_bebob *bebob); +int snd_bebob_stream_reserve_duplex(struct snd_bebob *bebob, unsigned int rate, + unsigned int frames_per_period, + unsigned int frames_per_buffer); +int snd_bebob_stream_start_duplex(struct snd_bebob *bebob); +void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob); +void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob); + +void snd_bebob_stream_lock_changed(struct snd_bebob *bebob); +int snd_bebob_stream_lock_try(struct snd_bebob *bebob); +void snd_bebob_stream_lock_release(struct snd_bebob *bebob); + +void snd_bebob_proc_init(struct snd_bebob *bebob); + +int snd_bebob_create_midi_devices(struct snd_bebob *bebob); + +int snd_bebob_create_pcm_devices(struct snd_bebob *bebob); + +int snd_bebob_create_hwdep_device(struct snd_bebob *bebob); + +/* model specific operations */ +extern const struct snd_bebob_spec phase88_rack_spec; +extern const struct snd_bebob_spec yamaha_terratec_spec; +extern const struct snd_bebob_spec saffirepro_26_spec; +extern const struct snd_bebob_spec saffirepro_10_spec; +extern const struct snd_bebob_spec saffire_le_spec; +extern const struct snd_bebob_spec saffire_spec; +extern const struct snd_bebob_spec maudio_fw410_spec; +extern const struct snd_bebob_spec maudio_audiophile_spec; +extern const struct snd_bebob_spec maudio_solo_spec; +extern const struct snd_bebob_spec maudio_ozonic_spec; +extern const struct snd_bebob_spec maudio_nrv10_spec; +extern const struct snd_bebob_spec maudio_special_spec; +int snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814); +int snd_bebob_maudio_load_firmware(struct fw_unit *unit); + +#define SND_BEBOB_DEV_ENTRY(vendor, model, data) \ +{ \ + .match_flags = IEEE1394_MATCH_VENDOR_ID | \ + IEEE1394_MATCH_MODEL_ID, \ + .vendor_id = vendor, \ + .model_id = model, \ + .driver_data = (kernel_ulong_t)data \ +} + +#endif diff --git a/sound/firewire/bebob/bebob_command.c b/sound/firewire/bebob/bebob_command.c new file mode 100644 index 000000000..e276ab8f9 --- /dev/null +++ b/sound/firewire/bebob/bebob_command.c @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * bebob_command.c - driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + */ + +#include "./bebob.h" + +int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id, + unsigned int fb_id, unsigned int num) +{ + u8 *buf; + int err; + + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x00; /* AV/C CONTROL */ + buf[1] = 0x08 | (0x07 & subunit_id); /* AUDIO SUBUNIT ID */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x80; /* type is 'selector'*/ + buf[4] = 0xff & fb_id; /* function block id */ + buf[5] = 0x10; /* control attribute is CURRENT */ + buf[6] = 0x02; /* selector length is 2 */ + buf[7] = 0xff & num; /* input function block plug number */ + buf[8] = 0x01; /* control selector is SELECTOR_CONTROL */ + + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(8)); + if (err < 0) + ; + else if (err < 9) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else + err = 0; + + kfree(buf); + return err; +} + +int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id, + unsigned int fb_id, unsigned int *num) +{ + u8 *buf; + int err; + + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x01; /* AV/C STATUS */ + buf[1] = 0x08 | (0x07 & subunit_id); /* AUDIO SUBUNIT ID */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x80; /* type is 'selector'*/ + buf[4] = 0xff & fb_id; /* function block id */ + buf[5] = 0x10; /* control attribute is CURRENT */ + buf[6] = 0x02; /* selector length is 2 */ + buf[7] = 0xff; /* input function block plug number */ + buf[8] = 0x01; /* control selector is SELECTOR_CONTROL */ + + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(8)); + if (err < 0) + ; + else if (err < 9) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + *num = buf[7]; + err = 0; +end: + kfree(buf); + return err; +} + +static inline void +avc_bridgeco_fill_extension_addr(u8 *buf, u8 *addr) +{ + buf[1] = addr[0]; + memcpy(buf + 4, addr + 1, 5); +} + +static inline void +avc_bridgeco_fill_plug_info_extension_command(u8 *buf, u8 *addr, + unsigned int itype) +{ + buf[0] = 0x01; /* AV/C STATUS */ + buf[2] = 0x02; /* AV/C GENERAL PLUG INFO */ + buf[3] = 0xc0; /* BridgeCo extension */ + avc_bridgeco_fill_extension_addr(buf, addr); + buf[9] = itype; /* info type */ +} + +int avc_bridgeco_get_plug_type(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + enum avc_bridgeco_plug_type *type) +{ + u8 *buf; + int err; + + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + /* Info type is 'plug type'. */ + avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x00); + + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(9)); + if (err < 0) + ; + else if (err < 11) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + *type = buf[10]; + err = 0; +end: + kfree(buf); + return err; +} + +int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + u8 *buf, unsigned int len) +{ + int err; + + /* Info type is 'channel position'. */ + avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x03); + + err = fcp_avc_transaction(unit, buf, 12, buf, 256, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | + BIT(5) | BIT(6) | BIT(7) | BIT(9)); + if (err < 0) + ; + else if (err < 11) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + /* Pick up specific data. */ + memmove(buf, buf + 10, err - 10); + err = 0; +end: + return err; +} + +int avc_bridgeco_get_plug_section_type(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + unsigned int id, u8 *type) +{ + u8 *buf; + int err; + + /* section info includes charactors but this module don't need it */ + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + /* Info type is 'section info'. */ + avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x07); + buf[10] = 0xff & ++id; /* section id */ + + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(9) | BIT(10)); + if (err < 0) + ; + else if (err < 12) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + *type = buf[11]; + err = 0; +end: + kfree(buf); + return err; +} + +int avc_bridgeco_get_plug_input(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 input[7]) +{ + int err; + u8 *buf; + + buf = kzalloc(18, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + /* Info type is 'plug input'. */ + avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x05); + + err = fcp_avc_transaction(unit, buf, 16, buf, 16, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7)); + if (err < 0) + ; + else if (err < 16) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + memcpy(input, buf + 10, 5); + err = 0; +end: + kfree(buf); + return err; +} + +int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 *buf, + unsigned int *len, unsigned int eid) +{ + int err; + + /* check given buffer */ + if ((buf == NULL) || (*len < 12)) { + err = -EINVAL; + goto end; + } + + buf[0] = 0x01; /* AV/C STATUS */ + buf[2] = 0x2f; /* AV/C STREAM FORMAT SUPPORT */ + buf[3] = 0xc1; /* Bridgeco extension - List Request */ + avc_bridgeco_fill_extension_addr(buf, addr); + buf[10] = 0xff & eid; /* Entry ID */ + + err = fcp_avc_transaction(unit, buf, 12, buf, *len, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(10)); + if (err < 0) + ; + else if (err < 12) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + else if (buf[10] != eid) + err = -EIO; + if (err < 0) + goto end; + + /* Pick up 'stream format info'. */ + memmove(buf, buf + 11, err - 11); + *len = err - 11; + err = 0; +end: + return err; +} diff --git a/sound/firewire/bebob/bebob_focusrite.c b/sound/firewire/bebob/bebob_focusrite.c new file mode 100644 index 000000000..06d6a37cd --- /dev/null +++ b/sound/firewire/bebob/bebob_focusrite.c @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * bebob_focusrite.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + */ + +#include "./bebob.h" + +#define ANA_IN "Analog In" +#define DIG_IN "Digital In" +#define ANA_OUT "Analog Out" +#define DIG_OUT "Digital Out" +#define STM_IN "Stream In" + +#define SAFFIRE_ADDRESS_BASE 0x000100000000ULL + +#define SAFFIRE_OFFSET_CLOCK_SOURCE 0x00f8 +#define SAFFIREPRO_OFFSET_CLOCK_SOURCE 0x0174 + +/* whether sync to external device or not */ +#define SAFFIRE_OFFSET_CLOCK_SYNC_EXT 0x013c +#define SAFFIRE_LE_OFFSET_CLOCK_SYNC_EXT 0x0432 +#define SAFFIREPRO_OFFSET_CLOCK_SYNC_EXT 0x0164 + +#define SAFFIRE_CLOCK_SOURCE_INTERNAL 0 +#define SAFFIRE_CLOCK_SOURCE_SPDIF 1 + +/* clock sources as returned from register of Saffire Pro 10 and 26 */ +#define SAFFIREPRO_CLOCK_SOURCE_SELECT_MASK 0x000000ff +#define SAFFIREPRO_CLOCK_SOURCE_DETECT_MASK 0x0000ff00 +#define SAFFIREPRO_CLOCK_SOURCE_INTERNAL 0 +#define SAFFIREPRO_CLOCK_SOURCE_SKIP 1 /* never used on hardware */ +#define SAFFIREPRO_CLOCK_SOURCE_SPDIF 2 +#define SAFFIREPRO_CLOCK_SOURCE_ADAT1 3 /* not used on s.pro. 10 */ +#define SAFFIREPRO_CLOCK_SOURCE_ADAT2 4 /* not used on s.pro. 10 */ +#define SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK 5 +#define SAFFIREPRO_CLOCK_SOURCE_COUNT 6 + +/* S/PDIF, ADAT1, ADAT2 is enabled or not. three quadlets */ +#define SAFFIREPRO_ENABLE_DIG_IFACES 0x01a4 + +/* saffirepro has its own parameter for sampling frequency */ +#define SAFFIREPRO_RATE_NOREBOOT 0x01cc +/* index is the value for this register */ +static const unsigned int rates[] = { + [0] = 0, + [1] = 44100, + [2] = 48000, + [3] = 88200, + [4] = 96000, + [5] = 176400, + [6] = 192000 +}; + +/* saffire(no label)/saffire LE has metering */ +#define SAFFIRE_OFFSET_METER 0x0100 +#define SAFFIRE_LE_OFFSET_METER 0x0168 + +static inline int +saffire_read_block(struct snd_bebob *bebob, u64 offset, + u32 *buf, unsigned int size) +{ + unsigned int i; + int err; + __be32 *tmp = (__be32 *)buf; + + err = snd_fw_transaction(bebob->unit, TCODE_READ_BLOCK_REQUEST, + SAFFIRE_ADDRESS_BASE + offset, + tmp, size, 0); + if (err < 0) + goto end; + + for (i = 0; i < size / sizeof(u32); i++) + buf[i] = be32_to_cpu(tmp[i]); +end: + return err; +} + +static inline int +saffire_read_quad(struct snd_bebob *bebob, u64 offset, u32 *value) +{ + int err; + __be32 tmp; + + err = snd_fw_transaction(bebob->unit, TCODE_READ_QUADLET_REQUEST, + SAFFIRE_ADDRESS_BASE + offset, + &tmp, sizeof(__be32), 0); + if (err < 0) + goto end; + + *value = be32_to_cpu(tmp); +end: + return err; +} + +static inline int +saffire_write_quad(struct snd_bebob *bebob, u64 offset, u32 value) +{ + __be32 data = cpu_to_be32(value); + + return snd_fw_transaction(bebob->unit, TCODE_WRITE_QUADLET_REQUEST, + SAFFIRE_ADDRESS_BASE + offset, + &data, sizeof(__be32), 0); +} + +static const enum snd_bebob_clock_type saffirepro_10_clk_src_types[] = { + SND_BEBOB_CLOCK_TYPE_INTERNAL, + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* S/PDIF */ + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* Word Clock */ +}; +static const enum snd_bebob_clock_type saffirepro_26_clk_src_types[] = { + SND_BEBOB_CLOCK_TYPE_INTERNAL, + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* S/PDIF */ + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* ADAT1 */ + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* ADAT2 */ + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* Word Clock */ +}; +/* Value maps between registers and labels for SaffirePro 10/26. */ +static const signed char saffirepro_clk_maps[][SAFFIREPRO_CLOCK_SOURCE_COUNT] = { + /* SaffirePro 10 */ + [0] = { + [SAFFIREPRO_CLOCK_SOURCE_INTERNAL] = 0, + [SAFFIREPRO_CLOCK_SOURCE_SKIP] = -1, /* not supported */ + [SAFFIREPRO_CLOCK_SOURCE_SPDIF] = 1, + [SAFFIREPRO_CLOCK_SOURCE_ADAT1] = -1, /* not supported */ + [SAFFIREPRO_CLOCK_SOURCE_ADAT2] = -1, /* not supported */ + [SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK] = 2, + }, + /* SaffirePro 26 */ + [1] = { + [SAFFIREPRO_CLOCK_SOURCE_INTERNAL] = 0, + [SAFFIREPRO_CLOCK_SOURCE_SKIP] = -1, /* not supported */ + [SAFFIREPRO_CLOCK_SOURCE_SPDIF] = 1, + [SAFFIREPRO_CLOCK_SOURCE_ADAT1] = 2, + [SAFFIREPRO_CLOCK_SOURCE_ADAT2] = 3, + [SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK] = 4, + } +}; + +static int +saffirepro_both_clk_freq_get(struct snd_bebob *bebob, unsigned int *rate) +{ + u32 id; + int err; + + err = saffire_read_quad(bebob, SAFFIREPRO_RATE_NOREBOOT, &id); + if (err < 0) + goto end; + if (id >= ARRAY_SIZE(rates)) + err = -EIO; + else + *rate = rates[id]; +end: + return err; +} +static int +saffirepro_both_clk_freq_set(struct snd_bebob *bebob, unsigned int rate) +{ + u32 id; + + for (id = 0; id < ARRAY_SIZE(rates); id++) { + if (rates[id] == rate) + break; + } + if (id == ARRAY_SIZE(rates)) + return -EINVAL; + + return saffire_write_quad(bebob, SAFFIREPRO_RATE_NOREBOOT, id); +} + +/* + * query hardware for current clock source, return our internally + * used clock index in *id, depending on hardware. + */ +static int +saffirepro_both_clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + int err; + u32 value; /* clock source read from hw register */ + const signed char *map; + + err = saffire_read_quad(bebob, SAFFIREPRO_OFFSET_CLOCK_SOURCE, &value); + if (err < 0) + goto end; + + /* depending on hardware, use a different mapping */ + if (bebob->spec->clock->types == saffirepro_10_clk_src_types) + map = saffirepro_clk_maps[0]; + else + map = saffirepro_clk_maps[1]; + + /* In a case that this driver cannot handle the value of register. */ + value &= SAFFIREPRO_CLOCK_SOURCE_SELECT_MASK; + if (value >= SAFFIREPRO_CLOCK_SOURCE_COUNT || map[value] < 0) { + err = -EIO; + goto end; + } + + *id = (unsigned int)map[value]; +end: + return err; +} + +const struct snd_bebob_spec saffire_le_spec; +static const enum snd_bebob_clock_type saffire_both_clk_src_types[] = { + SND_BEBOB_CLOCK_TYPE_INTERNAL, + SND_BEBOB_CLOCK_TYPE_EXTERNAL, +}; +static int +saffire_both_clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + int err; + u32 value; + + err = saffire_read_quad(bebob, SAFFIRE_OFFSET_CLOCK_SOURCE, &value); + if (err >= 0) + *id = 0xff & value; + + return err; +}; +static const char *const saffire_le_meter_labels[] = { + ANA_IN, ANA_IN, DIG_IN, + ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, + STM_IN, STM_IN +}; +static const char *const saffire_meter_labels[] = { + ANA_IN, ANA_IN, + STM_IN, STM_IN, STM_IN, STM_IN, STM_IN, +}; +static int +saffire_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size) +{ + const struct snd_bebob_meter_spec *spec = bebob->spec->meter; + unsigned int channels; + u64 offset; + int err; + + if (spec->labels == saffire_le_meter_labels) + offset = SAFFIRE_LE_OFFSET_METER; + else + offset = SAFFIRE_OFFSET_METER; + + channels = spec->num * 2; + if (size < channels * sizeof(u32)) + return -EIO; + + err = saffire_read_block(bebob, offset, buf, size); + if (err >= 0 && spec->labels == saffire_le_meter_labels) { + swap(buf[1], buf[3]); + swap(buf[2], buf[3]); + swap(buf[3], buf[4]); + + swap(buf[7], buf[10]); + swap(buf[8], buf[10]); + swap(buf[9], buf[11]); + swap(buf[11], buf[12]); + + swap(buf[15], buf[16]); + } + + return err; +} + +static const struct snd_bebob_rate_spec saffirepro_both_rate_spec = { + .get = &saffirepro_both_clk_freq_get, + .set = &saffirepro_both_clk_freq_set, +}; +/* Saffire Pro 26 I/O */ +static const struct snd_bebob_clock_spec saffirepro_26_clk_spec = { + .num = ARRAY_SIZE(saffirepro_26_clk_src_types), + .types = saffirepro_26_clk_src_types, + .get = &saffirepro_both_clk_src_get, +}; +const struct snd_bebob_spec saffirepro_26_spec = { + .clock = &saffirepro_26_clk_spec, + .rate = &saffirepro_both_rate_spec, + .meter = NULL +}; +/* Saffire Pro 10 I/O */ +static const struct snd_bebob_clock_spec saffirepro_10_clk_spec = { + .num = ARRAY_SIZE(saffirepro_10_clk_src_types), + .types = saffirepro_10_clk_src_types, + .get = &saffirepro_both_clk_src_get, +}; +const struct snd_bebob_spec saffirepro_10_spec = { + .clock = &saffirepro_10_clk_spec, + .rate = &saffirepro_both_rate_spec, + .meter = NULL +}; + +static const struct snd_bebob_rate_spec saffire_both_rate_spec = { + .get = &snd_bebob_stream_get_rate, + .set = &snd_bebob_stream_set_rate, +}; +static const struct snd_bebob_clock_spec saffire_both_clk_spec = { + .num = ARRAY_SIZE(saffire_both_clk_src_types), + .types = saffire_both_clk_src_types, + .get = &saffire_both_clk_src_get, +}; +/* Saffire LE */ +static const struct snd_bebob_meter_spec saffire_le_meter_spec = { + .num = ARRAY_SIZE(saffire_le_meter_labels), + .labels = saffire_le_meter_labels, + .get = &saffire_meter_get, +}; +const struct snd_bebob_spec saffire_le_spec = { + .clock = &saffire_both_clk_spec, + .rate = &saffire_both_rate_spec, + .meter = &saffire_le_meter_spec +}; +/* Saffire */ +static const struct snd_bebob_meter_spec saffire_meter_spec = { + .num = ARRAY_SIZE(saffire_meter_labels), + .labels = saffire_meter_labels, + .get = &saffire_meter_get, +}; +const struct snd_bebob_spec saffire_spec = { + .clock = &saffire_both_clk_spec, + .rate = &saffire_both_rate_spec, + .meter = &saffire_meter_spec +}; diff --git a/sound/firewire/bebob/bebob_hwdep.c b/sound/firewire/bebob/bebob_hwdep.c new file mode 100644 index 000000000..c362eb38a --- /dev/null +++ b/sound/firewire/bebob/bebob_hwdep.c @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * bebob_hwdep.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + */ + +/* + * This codes give three functionality. + * + * 1.get firewire node infomation + * 2.get notification about starting/stopping stream + * 3.lock/unlock stream + */ + +#include "bebob.h" + +static long +hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_bebob *bebob = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&bebob->lock); + + while (!bebob->dev_lock_changed) { + prepare_to_wait(&bebob->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&bebob->lock); + schedule(); + finish_wait(&bebob->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&bebob->lock); + } + + memset(&event, 0, sizeof(event)); + count = min_t(long, count, sizeof(event.lock_status)); + if (bebob->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (bebob->dev_lock_count > 0); + bebob->dev_lock_changed = false; + } + + spin_unlock_irq(&bebob->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static __poll_t +hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait) +{ + struct snd_bebob *bebob = hwdep->private_data; + __poll_t events; + + poll_wait(file, &bebob->hwdep_wait, wait); + + spin_lock_irq(&bebob->lock); + if (bebob->dev_lock_changed) + events = EPOLLIN | EPOLLRDNORM; + else + events = 0; + spin_unlock_irq(&bebob->lock); + + return events; +} + +static int +hwdep_get_info(struct snd_bebob *bebob, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(bebob->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_BEBOB; + 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]); + strlcpy(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_bebob *bebob) +{ + int err; + + spin_lock_irq(&bebob->lock); + + if (bebob->dev_lock_count == 0) { + bebob->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&bebob->lock); + + return err; +} + +static int +hwdep_unlock(struct snd_bebob *bebob) +{ + int err; + + spin_lock_irq(&bebob->lock); + + if (bebob->dev_lock_count == -1) { + bebob->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&bebob->lock); + + return err; +} + +static int +hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_bebob *bebob = hwdep->private_data; + + spin_lock_irq(&bebob->lock); + if (bebob->dev_lock_count == -1) + bebob->dev_lock_count = 0; + spin_unlock_irq(&bebob->lock); + + return 0; +} + +static int +hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_bebob *bebob = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(bebob, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(bebob); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(bebob); + 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_bebob_create_hwdep_device(struct snd_bebob *bebob) +{ + static const struct snd_hwdep_ops ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(bebob->card, "BeBoB", 0, &hwdep); + if (err < 0) + goto end; + strcpy(hwdep->name, "BeBoB"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_BEBOB; + hwdep->ops = ops; + hwdep->private_data = bebob; + hwdep->exclusive = true; +end: + return err; +} + diff --git a/sound/firewire/bebob/bebob_maudio.c b/sound/firewire/bebob/bebob_maudio.c new file mode 100644 index 000000000..177699e1b --- /dev/null +++ b/sound/firewire/bebob/bebob_maudio.c @@ -0,0 +1,797 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * bebob_maudio.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + */ + +#include "./bebob.h" +#include <sound/control.h> + +/* + * Just powering on, Firewire 410/Audiophile/1814 and ProjectMix I/O wait to + * download firmware blob. To enable these devices, drivers should upload + * firmware blob and send a command to initialize configuration to factory + * settings when completing uploading. Then these devices generate bus reset + * and are recognized as new devices with the firmware. + * + * But with firmware version 5058 or later, the firmware is stored to flash + * memory in the device and drivers can tell bootloader to load the firmware + * by sending a cue. This cue must be sent one time. + * + * For streaming, both of output and input streams are needed for Firewire 410 + * and Ozonic. The single stream is OK for the other devices even if the clock + * source is not SYT-Match (I note no devices use SYT-Match). + * + * Without streaming, the devices except for Firewire Audiophile can mix any + * input and output. For this reason, Audiophile cannot be used as standalone + * mixer. + * + * Firewire 1814 and ProjectMix I/O uses special firmware. It will be freezed + * when receiving any commands which the firmware can't understand. These + * devices utilize completely different system to control. It is some + * write-transaction directly into a certain address. All of addresses for mixer + * functionality is between 0xffc700700000 to 0xffc70070009c. + */ + +/* Offset from information register */ +#define INFO_OFFSET_SW_DATE 0x20 + +/* Bootloader Protocol Version 1 */ +#define MAUDIO_BOOTLOADER_CUE1 0x00000001 +/* + * Initializing configuration to factory settings (= 0x1101), (swapped in line), + * Command code is zero (= 0x00), + * the number of operands is zero (= 0x00)(at least significant byte) + */ +#define MAUDIO_BOOTLOADER_CUE2 0x01110000 +/* padding */ +#define MAUDIO_BOOTLOADER_CUE3 0x00000000 + +#define MAUDIO_SPECIFIC_ADDRESS 0xffc700000000ULL + +#define METER_OFFSET 0x00600000 + +/* some device has sync info after metering data */ +#define METER_SIZE_SPECIAL 84 /* with sync info */ +#define METER_SIZE_FW410 76 /* with sync info */ +#define METER_SIZE_AUDIOPHILE 60 /* with sync info */ +#define METER_SIZE_SOLO 52 /* with sync info */ +#define METER_SIZE_OZONIC 48 +#define METER_SIZE_NRV10 80 + +/* labels for metering */ +#define ANA_IN "Analog In" +#define ANA_OUT "Analog Out" +#define DIG_IN "Digital In" +#define SPDIF_IN "S/PDIF In" +#define ADAT_IN "ADAT In" +#define DIG_OUT "Digital Out" +#define SPDIF_OUT "S/PDIF Out" +#define ADAT_OUT "ADAT Out" +#define STRM_IN "Stream In" +#define AUX_OUT "Aux Out" +#define HP_OUT "HP Out" +/* for NRV */ +#define UNKNOWN_METER "Unknown" + +struct special_params { + bool is1814; + unsigned int clk_src; + unsigned int dig_in_fmt; + unsigned int dig_out_fmt; + unsigned int clk_lock; + struct snd_ctl_elem_id *ctl_id_sync; +}; + +/* + * For some M-Audio devices, this module just send cue to load firmware. After + * loading, the device generates bus reset and newly detected. + * + * If we make any transactions to load firmware, the operation may failed. + */ +int snd_bebob_maudio_load_firmware(struct fw_unit *unit) +{ + struct fw_device *device = fw_parent_device(unit); + int err, rcode; + u64 date; + __le32 *cues; + + /* check date of software used to build */ + err = snd_bebob_read_block(unit, INFO_OFFSET_SW_DATE, + &date, sizeof(u64)); + if (err < 0) + return err; + /* + * firmware version 5058 or later has date later than "20070401", but + * 'date' is not null-terminated. + */ + if (date < 0x3230303730343031LL) { + dev_err(&unit->device, + "Use firmware version 5058 or later\n"); + return -ENXIO; + } + + cues = kmalloc_array(3, sizeof(*cues), GFP_KERNEL); + if (!cues) + return -ENOMEM; + + cues[0] = cpu_to_le32(MAUDIO_BOOTLOADER_CUE1); + cues[1] = cpu_to_le32(MAUDIO_BOOTLOADER_CUE2); + cues[2] = cpu_to_le32(MAUDIO_BOOTLOADER_CUE3); + + rcode = fw_run_transaction(device->card, TCODE_WRITE_BLOCK_REQUEST, + device->node_id, device->generation, + device->max_speed, BEBOB_ADDR_REG_REQ, + cues, 3 * sizeof(*cues)); + kfree(cues); + if (rcode != RCODE_COMPLETE) { + dev_err(&unit->device, + "Failed to send a cue to load firmware\n"); + err = -EIO; + } + + return err; +} + +static inline int +get_meter(struct snd_bebob *bebob, void *buf, unsigned int size) +{ + return snd_fw_transaction(bebob->unit, TCODE_READ_BLOCK_REQUEST, + MAUDIO_SPECIFIC_ADDRESS + METER_OFFSET, + buf, size, 0); +} + +static int +check_clk_sync(struct snd_bebob *bebob, unsigned int size, bool *sync) +{ + int err; + u8 *buf; + + buf = kmalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + err = get_meter(bebob, buf, size); + if (err < 0) + goto end; + + /* if synced, this value is the same as SFC of FDF in CIP header */ + *sync = (buf[size - 2] != 0xff); +end: + kfree(buf); + return err; +} + +/* + * dig_fmt: 0x00:S/PDIF, 0x01:ADAT + * clk_lock: 0x00:unlock, 0x01:lock + */ +static int +avc_maudio_set_special_clk(struct snd_bebob *bebob, unsigned int clk_src, + unsigned int dig_in_fmt, unsigned int dig_out_fmt, + unsigned int clk_lock) +{ + struct special_params *params = bebob->maudio_special_quirk; + int err; + u8 *buf; + + if (amdtp_stream_running(&bebob->rx_stream) || + amdtp_stream_running(&bebob->tx_stream)) + return -EBUSY; + + buf = kmalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x00; /* CONTROL */ + buf[1] = 0xff; /* UNIT */ + buf[2] = 0x00; /* vendor dependent */ + buf[3] = 0x04; /* company ID high */ + buf[4] = 0x00; /* company ID middle */ + buf[5] = 0x04; /* company ID low */ + buf[6] = 0xff & clk_src; /* clock source */ + buf[7] = 0xff & dig_in_fmt; /* input digital format */ + buf[8] = 0xff & dig_out_fmt; /* output digital format */ + buf[9] = 0xff & clk_lock; /* lock these settings */ + buf[10] = 0x00; /* padding */ + buf[11] = 0x00; /* padding */ + + err = fcp_avc_transaction(bebob->unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | + BIT(5) | BIT(6) | BIT(7) | BIT(8) | + BIT(9)); + if ((err > 0) && (err < 10)) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + if (err < 0) + goto end; + + params->clk_src = buf[6]; + params->dig_in_fmt = buf[7]; + params->dig_out_fmt = buf[8]; + params->clk_lock = buf[9]; + + if (params->ctl_id_sync) + snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE, + params->ctl_id_sync); + + err = 0; +end: + kfree(buf); + return err; +} +static void +special_stream_formation_set(struct snd_bebob *bebob) +{ + static const unsigned int ch_table[2][2][3] = { + /* AMDTP_OUT_STREAM */ + { { 6, 6, 4 }, /* SPDIF */ + { 12, 8, 4 } }, /* ADAT */ + /* AMDTP_IN_STREAM */ + { { 10, 10, 2 }, /* SPDIF */ + { 16, 12, 2 } } /* ADAT */ + }; + struct special_params *params = bebob->maudio_special_quirk; + unsigned int i, max; + + max = SND_BEBOB_STRM_FMT_ENTRIES - 1; + if (!params->is1814) + max -= 2; + + for (i = 0; i < max; i++) { + bebob->tx_stream_formations[i + 1].pcm = + ch_table[AMDTP_IN_STREAM][params->dig_in_fmt][i / 2]; + bebob->tx_stream_formations[i + 1].midi = 1; + + bebob->rx_stream_formations[i + 1].pcm = + ch_table[AMDTP_OUT_STREAM][params->dig_out_fmt][i / 2]; + bebob->rx_stream_formations[i + 1].midi = 1; + } +} + +static int add_special_controls(struct snd_bebob *bebob); +int +snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814) +{ + struct special_params *params; + int err; + + params = devm_kzalloc(&bebob->card->card_dev, + sizeof(struct special_params), GFP_KERNEL); + if (!params) + return -ENOMEM; + + mutex_lock(&bebob->mutex); + + bebob->maudio_special_quirk = (void *)params; + params->is1814 = is1814; + + /* initialize these parameters because driver is not allowed to ask */ + bebob->rx_stream.context = ERR_PTR(-1); + bebob->tx_stream.context = ERR_PTR(-1); + err = avc_maudio_set_special_clk(bebob, 0x03, 0x00, 0x00, 0x00); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to initialize clock params: %d\n", err); + goto end; + } + + err = add_special_controls(bebob); + if (err < 0) + goto end; + + special_stream_formation_set(bebob); + + if (params->is1814) { + bebob->midi_input_ports = 1; + bebob->midi_output_ports = 1; + } else { + bebob->midi_input_ports = 2; + bebob->midi_output_ports = 2; + } +end: + mutex_unlock(&bebob->mutex); + return err; +} + +/* Input plug shows actual rate. Output plug is needless for this purpose. */ +static int special_get_rate(struct snd_bebob *bebob, unsigned int *rate) +{ + int err, trials; + + trials = 0; + do { + err = avc_general_get_sig_fmt(bebob->unit, rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + } while (err == -EAGAIN && ++trials < 3); + + return err; +} +static int special_set_rate(struct snd_bebob *bebob, unsigned int rate) +{ + struct special_params *params = bebob->maudio_special_quirk; + int err; + + err = avc_general_set_sig_fmt(bebob->unit, rate, + AVC_GENERAL_PLUG_DIR_OUT, 0); + if (err < 0) + goto end; + + /* + * Just after changing sampling rate for output, a followed command + * for input is easy to fail. This is a workaround fot this issue. + */ + msleep(100); + + err = avc_general_set_sig_fmt(bebob->unit, rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) + goto end; + + if (params->ctl_id_sync) + snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE, + params->ctl_id_sync); +end: + return err; +} + +/* Clock source control for special firmware */ +static const enum snd_bebob_clock_type special_clk_types[] = { + SND_BEBOB_CLOCK_TYPE_INTERNAL, /* With digital mute */ + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* SPDIF/ADAT */ + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* Word Clock */ + SND_BEBOB_CLOCK_TYPE_INTERNAL, +}; +static int special_clk_get(struct snd_bebob *bebob, unsigned int *id) +{ + struct special_params *params = bebob->maudio_special_quirk; + *id = params->clk_src; + return 0; +} +static int special_clk_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + static const char *const special_clk_labels[] = { + "Internal with Digital Mute", + "Digital", + "Word Clock", + "Internal" + }; + return snd_ctl_enum_info(einf, 1, ARRAY_SIZE(special_clk_types), + special_clk_labels); +} +static int special_clk_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + uval->value.enumerated.item[0] = params->clk_src; + return 0; +} +static int special_clk_ctl_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + int err, id; + + id = uval->value.enumerated.item[0]; + if (id >= ARRAY_SIZE(special_clk_types)) + return -EINVAL; + + mutex_lock(&bebob->mutex); + + err = avc_maudio_set_special_clk(bebob, id, + params->dig_in_fmt, + params->dig_out_fmt, + params->clk_lock); + mutex_unlock(&bebob->mutex); + + if (err >= 0) + err = 1; + + return err; +} +static const struct snd_kcontrol_new special_clk_ctl = { + .name = "Clock Source", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = special_clk_ctl_info, + .get = special_clk_ctl_get, + .put = special_clk_ctl_put +}; + +/* Clock synchronization control for special firmware */ +static int special_sync_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + einf->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + einf->count = 1; + einf->value.integer.min = 0; + einf->value.integer.max = 1; + + return 0; +} +static int special_sync_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + int err; + bool synced = 0; + + err = check_clk_sync(bebob, METER_SIZE_SPECIAL, &synced); + if (err >= 0) + uval->value.integer.value[0] = synced; + + return 0; +} +static const struct snd_kcontrol_new special_sync_ctl = { + .name = "Sync Status", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = special_sync_ctl_info, + .get = special_sync_ctl_get, +}; + +/* Digital input interface control for special firmware */ +static const char *const special_dig_in_iface_labels[] = { + "S/PDIF Optical", "S/PDIF Coaxial", "ADAT Optical" +}; +static int special_dig_in_iface_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + return snd_ctl_enum_info(einf, 1, + ARRAY_SIZE(special_dig_in_iface_labels), + special_dig_in_iface_labels); +} +static int special_dig_in_iface_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + unsigned int dig_in_iface; + int err, val; + + mutex_lock(&bebob->mutex); + + err = avc_audio_get_selector(bebob->unit, 0x00, 0x04, + &dig_in_iface); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get digital input interface: %d\n", err); + goto end; + } + + /* encoded id for user value */ + val = (params->dig_in_fmt << 1) | (dig_in_iface & 0x01); + + /* for ADAT Optical */ + if (val > 2) + val = 2; + + uval->value.enumerated.item[0] = val; +end: + mutex_unlock(&bebob->mutex); + return err; +} +static int special_dig_in_iface_ctl_set(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + unsigned int id, dig_in_fmt, dig_in_iface; + int err; + + id = uval->value.enumerated.item[0]; + if (id >= ARRAY_SIZE(special_dig_in_iface_labels)) + return -EINVAL; + + /* decode user value */ + dig_in_fmt = (id >> 1) & 0x01; + dig_in_iface = id & 0x01; + + mutex_lock(&bebob->mutex); + + err = avc_maudio_set_special_clk(bebob, + params->clk_src, + dig_in_fmt, + params->dig_out_fmt, + params->clk_lock); + if (err < 0) + goto end; + + /* For ADAT, optical interface is only available. */ + if (params->dig_in_fmt > 0) { + err = 1; + goto end; + } + + /* For S/PDIF, optical/coaxial interfaces are selectable. */ + err = avc_audio_set_selector(bebob->unit, 0x00, 0x04, dig_in_iface); + if (err < 0) + dev_err(&bebob->unit->device, + "fail to set digital input interface: %d\n", err); + err = 1; +end: + special_stream_formation_set(bebob); + mutex_unlock(&bebob->mutex); + return err; +} +static const struct snd_kcontrol_new special_dig_in_iface_ctl = { + .name = "Digital Input Interface", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = special_dig_in_iface_ctl_info, + .get = special_dig_in_iface_ctl_get, + .put = special_dig_in_iface_ctl_set +}; + +/* Digital output interface control for special firmware */ +static const char *const special_dig_out_iface_labels[] = { + "S/PDIF Optical and Coaxial", "ADAT Optical" +}; +static int special_dig_out_iface_ctl_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + return snd_ctl_enum_info(einf, 1, + ARRAY_SIZE(special_dig_out_iface_labels), + special_dig_out_iface_labels); +} +static int special_dig_out_iface_ctl_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + mutex_lock(&bebob->mutex); + uval->value.enumerated.item[0] = params->dig_out_fmt; + mutex_unlock(&bebob->mutex); + return 0; +} +static int special_dig_out_iface_ctl_set(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct special_params *params = bebob->maudio_special_quirk; + unsigned int id; + int err; + + id = uval->value.enumerated.item[0]; + if (id >= ARRAY_SIZE(special_dig_out_iface_labels)) + return -EINVAL; + + mutex_lock(&bebob->mutex); + + err = avc_maudio_set_special_clk(bebob, + params->clk_src, + params->dig_in_fmt, + id, params->clk_lock); + if (err >= 0) { + special_stream_formation_set(bebob); + err = 1; + } + + mutex_unlock(&bebob->mutex); + return err; +} +static const struct snd_kcontrol_new special_dig_out_iface_ctl = { + .name = "Digital Output Interface", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = special_dig_out_iface_ctl_info, + .get = special_dig_out_iface_ctl_get, + .put = special_dig_out_iface_ctl_set +}; + +static int add_special_controls(struct snd_bebob *bebob) +{ + struct snd_kcontrol *kctl; + struct special_params *params = bebob->maudio_special_quirk; + int err; + + kctl = snd_ctl_new1(&special_clk_ctl, bebob); + err = snd_ctl_add(bebob->card, kctl); + if (err < 0) + goto end; + + kctl = snd_ctl_new1(&special_sync_ctl, bebob); + err = snd_ctl_add(bebob->card, kctl); + if (err < 0) + goto end; + params->ctl_id_sync = &kctl->id; + + kctl = snd_ctl_new1(&special_dig_in_iface_ctl, bebob); + err = snd_ctl_add(bebob->card, kctl); + if (err < 0) + goto end; + + kctl = snd_ctl_new1(&special_dig_out_iface_ctl, bebob); + err = snd_ctl_add(bebob->card, kctl); +end: + return err; +} + +/* Hardware metering for special firmware */ +static const char *const special_meter_labels[] = { + ANA_IN, ANA_IN, ANA_IN, ANA_IN, + SPDIF_IN, + ADAT_IN, ADAT_IN, ADAT_IN, ADAT_IN, + ANA_OUT, ANA_OUT, + SPDIF_OUT, + ADAT_OUT, ADAT_OUT, ADAT_OUT, ADAT_OUT, + HP_OUT, HP_OUT, + AUX_OUT +}; +static int +special_meter_get(struct snd_bebob *bebob, u32 *target, unsigned int size) +{ + __be16 *buf; + unsigned int i, c, channels; + int err; + + channels = ARRAY_SIZE(special_meter_labels) * 2; + if (size < channels * sizeof(u32)) + return -EINVAL; + + /* omit last 4 bytes because it's clock info. */ + buf = kmalloc(METER_SIZE_SPECIAL - 4, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + err = get_meter(bebob, (void *)buf, METER_SIZE_SPECIAL - 4); + if (err < 0) + goto end; + + /* Its format is u16 and some channels are unknown. */ + i = 0; + for (c = 2; c < channels + 2; c++) + target[i++] = be16_to_cpu(buf[c]) << 16; +end: + kfree(buf); + return err; +} + +/* last 4 bytes are omitted because it's clock info. */ +static const char *const fw410_meter_labels[] = { + ANA_IN, DIG_IN, + ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, DIG_OUT, + HP_OUT +}; +static const char *const audiophile_meter_labels[] = { + ANA_IN, DIG_IN, + ANA_OUT, ANA_OUT, DIG_OUT, + HP_OUT, AUX_OUT, +}; +static const char *const solo_meter_labels[] = { + ANA_IN, DIG_IN, + STRM_IN, STRM_IN, + ANA_OUT, DIG_OUT +}; + +/* no clock info */ +static const char *const ozonic_meter_labels[] = { + ANA_IN, ANA_IN, + STRM_IN, STRM_IN, + ANA_OUT, ANA_OUT +}; +/* TODO: need testers. these positions are based on authour's assumption */ +static const char *const nrv10_meter_labels[] = { + ANA_IN, ANA_IN, ANA_IN, ANA_IN, + DIG_IN, + ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, + DIG_IN +}; +static int +normal_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size) +{ + const struct snd_bebob_meter_spec *spec = bebob->spec->meter; + unsigned int c, channels; + int err; + + channels = spec->num * 2; + if (size < channels * sizeof(u32)) + return -EINVAL; + + err = get_meter(bebob, (void *)buf, size); + if (err < 0) + goto end; + + for (c = 0; c < channels; c++) + be32_to_cpus(&buf[c]); + + /* swap stream channels because inverted */ + if (spec->labels == solo_meter_labels) { + swap(buf[4], buf[6]); + swap(buf[5], buf[7]); + } +end: + return err; +} + +/* for special customized devices */ +static const struct snd_bebob_rate_spec special_rate_spec = { + .get = &special_get_rate, + .set = &special_set_rate, +}; +static const struct snd_bebob_clock_spec special_clk_spec = { + .num = ARRAY_SIZE(special_clk_types), + .types = special_clk_types, + .get = &special_clk_get, +}; +static const struct snd_bebob_meter_spec special_meter_spec = { + .num = ARRAY_SIZE(special_meter_labels), + .labels = special_meter_labels, + .get = &special_meter_get +}; +const struct snd_bebob_spec maudio_special_spec = { + .clock = &special_clk_spec, + .rate = &special_rate_spec, + .meter = &special_meter_spec +}; + +/* Firewire 410 specification */ +static const struct snd_bebob_rate_spec usual_rate_spec = { + .get = &snd_bebob_stream_get_rate, + .set = &snd_bebob_stream_set_rate, +}; +static const struct snd_bebob_meter_spec fw410_meter_spec = { + .num = ARRAY_SIZE(fw410_meter_labels), + .labels = fw410_meter_labels, + .get = &normal_meter_get +}; +const struct snd_bebob_spec maudio_fw410_spec = { + .clock = NULL, + .rate = &usual_rate_spec, + .meter = &fw410_meter_spec +}; + +/* Firewire Audiophile specification */ +static const struct snd_bebob_meter_spec audiophile_meter_spec = { + .num = ARRAY_SIZE(audiophile_meter_labels), + .labels = audiophile_meter_labels, + .get = &normal_meter_get +}; +const struct snd_bebob_spec maudio_audiophile_spec = { + .clock = NULL, + .rate = &usual_rate_spec, + .meter = &audiophile_meter_spec +}; + +/* Firewire Solo specification */ +static const struct snd_bebob_meter_spec solo_meter_spec = { + .num = ARRAY_SIZE(solo_meter_labels), + .labels = solo_meter_labels, + .get = &normal_meter_get +}; +const struct snd_bebob_spec maudio_solo_spec = { + .clock = NULL, + .rate = &usual_rate_spec, + .meter = &solo_meter_spec +}; + +/* Ozonic specification */ +static const struct snd_bebob_meter_spec ozonic_meter_spec = { + .num = ARRAY_SIZE(ozonic_meter_labels), + .labels = ozonic_meter_labels, + .get = &normal_meter_get +}; +const struct snd_bebob_spec maudio_ozonic_spec = { + .clock = NULL, + .rate = &usual_rate_spec, + .meter = &ozonic_meter_spec +}; + +/* NRV10 specification */ +static const struct snd_bebob_meter_spec nrv10_meter_spec = { + .num = ARRAY_SIZE(nrv10_meter_labels), + .labels = nrv10_meter_labels, + .get = &normal_meter_get +}; +const struct snd_bebob_spec maudio_nrv10_spec = { + .clock = NULL, + .rate = &usual_rate_spec, + .meter = &nrv10_meter_spec +}; diff --git a/sound/firewire/bebob/bebob_midi.c b/sound/firewire/bebob/bebob_midi.c new file mode 100644 index 000000000..6f597d03e --- /dev/null +++ b/sound/firewire/bebob/bebob_midi.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * bebob_midi.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + */ + +#include "bebob.h" + +static int midi_open(struct snd_rawmidi_substream *substream) +{ + struct snd_bebob *bebob = substream->rmidi->private_data; + int err; + + err = snd_bebob_stream_lock_try(bebob); + if (err < 0) + return err; + + mutex_lock(&bebob->mutex); + err = snd_bebob_stream_reserve_duplex(bebob, 0, 0, 0); + if (err >= 0) { + ++bebob->substreams_counter; + err = snd_bebob_stream_start_duplex(bebob); + if (err < 0) + --bebob->substreams_counter; + } + mutex_unlock(&bebob->mutex); + if (err < 0) + snd_bebob_stream_lock_release(bebob); + + return err; +} + +static int midi_close(struct snd_rawmidi_substream *substream) +{ + struct snd_bebob *bebob = substream->rmidi->private_data; + + mutex_lock(&bebob->mutex); + bebob->substreams_counter--; + snd_bebob_stream_stop_duplex(bebob); + mutex_unlock(&bebob->mutex); + + snd_bebob_stream_lock_release(bebob); + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_bebob *bebob = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&bebob->lock, flags); + + if (up) + amdtp_am824_midi_trigger(&bebob->tx_stream, + substrm->number, substrm); + else + amdtp_am824_midi_trigger(&bebob->tx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&bebob->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_bebob *bebob = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&bebob->lock, flags); + + if (up) + amdtp_am824_midi_trigger(&bebob->rx_stream, + substrm->number, substrm); + else + amdtp_am824_midi_trigger(&bebob->rx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&bebob->lock, flags); +} + +static void set_midi_substream_names(struct snd_bebob *bebob, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", + bebob->card->shortname, subs->number + 1); + } +} + +int snd_bebob_create_midi_devices(struct snd_bebob *bebob) +{ + static const struct snd_rawmidi_ops capture_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_capture_trigger, + }; + static const struct snd_rawmidi_ops playback_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_playback_trigger, + }; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + /* create midi ports */ + err = snd_rawmidi_new(bebob->card, bebob->card->driver, 0, + bebob->midi_output_ports, bebob->midi_input_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", bebob->card->shortname); + rmidi->private_data = bebob; + + if (bebob->midi_input_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &capture_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + + set_midi_substream_names(bebob, str); + } + + if (bebob->midi_output_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &playback_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + set_midi_substream_names(bebob, str); + } + + if ((bebob->midi_output_ports > 0) && (bebob->midi_input_ports > 0)) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/bebob/bebob_pcm.c b/sound/firewire/bebob/bebob_pcm.c new file mode 100644 index 000000000..f8d9a2041 --- /dev/null +++ b/sound/firewire/bebob/bebob_pcm.c @@ -0,0 +1,377 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * bebob_pcm.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + */ + +#include "./bebob.h" + +static int +hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) +{ + struct snd_bebob_stream_formation *formations = 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 < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + /* entry is invalid */ + if (formations[i].pcm == 0) + continue; + + if (!snd_interval_test(c, formations[i].pcm)) + continue; + + t.min = min(t.min, snd_bebob_rate_table[i]); + t.max = max(t.max, snd_bebob_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) +{ + struct snd_bebob_stream_formation *formations = 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 < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + /* entry is invalid */ + if (formations[i].pcm == 0) + continue; + + if (!snd_interval_test(r, snd_bebob_rate_table[i])) + continue; + + t.min = min(t.min, formations[i].pcm); + t.max = max(t.max, formations[i].pcm); + } + + return snd_interval_refine(c, &t); +} + +static void +limit_channels_and_rates(struct snd_pcm_hardware *hw, + struct snd_bebob_stream_formation *formations) +{ + unsigned int i; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + hw->rate_min = UINT_MAX; + hw->rate_max = 0; + hw->rates = 0; + + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + /* entry has no PCM channels */ + if (formations[i].pcm == 0) + continue; + + hw->channels_min = min(hw->channels_min, formations[i].pcm); + hw->channels_max = max(hw->channels_max, formations[i].pcm); + + hw->rate_min = min(hw->rate_min, snd_bebob_rate_table[i]); + hw->rate_max = max(hw->rate_max, snd_bebob_rate_table[i]); + hw->rates |= snd_pcm_rate_to_rate_bit(snd_bebob_rate_table[i]); + } +} + +static int +pcm_init_hw_params(struct snd_bebob *bebob, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct amdtp_stream *s; + struct snd_bebob_stream_formation *formations; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = AM824_IN_PCM_FORMAT_BITS; + s = &bebob->tx_stream; + formations = bebob->tx_stream_formations; + } else { + runtime->hw.formats = AM824_OUT_PCM_FORMAT_BITS; + s = &bebob->rx_stream; + formations = bebob->rx_stream_formations; + } + + limit_channels_and_rates(&runtime->hw, formations); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, formations, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + goto end; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, formations, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + goto end; + + err = amdtp_am824_add_pcm_hw_constraints(s, runtime); +end: + return err; +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + const struct snd_bebob_rate_spec *spec = bebob->spec->rate; + struct amdtp_domain *d = &bebob->domain; + enum snd_bebob_clock_type src; + int err; + + err = snd_bebob_stream_lock_try(bebob); + if (err < 0) + return err; + + err = pcm_init_hw_params(bebob, substream); + if (err < 0) + goto err_locked; + + err = snd_bebob_stream_get_clock_src(bebob, &src); + if (err < 0) + goto err_locked; + + mutex_lock(&bebob->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_BEBOB_CLOCK_TYPE_EXTERNAL || + (bebob->substreams_counter > 0 && d->events_per_period > 0)) { + unsigned int frames_per_period = d->events_per_period; + unsigned int frames_per_buffer = d->events_per_buffer; + unsigned int sampling_rate; + + err = spec->get(bebob, &sampling_rate); + if (err < 0) { + mutex_unlock(&bebob->mutex); + dev_err(&bebob->unit->device, + "fail to get sampling rate: %d\n", err); + goto err_locked; + } + + substream->runtime->hw.rate_min = sampling_rate; + substream->runtime->hw.rate_max = sampling_rate; + + if (frames_per_period > 0) { + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + frames_per_period, frames_per_period); + if (err < 0) { + mutex_unlock(&bebob->mutex); + goto err_locked; + } + + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + frames_per_buffer, frames_per_buffer); + if (err < 0) { + mutex_unlock(&bebob->mutex); + goto err_locked; + } + } + } + + mutex_unlock(&bebob->mutex); + + snd_pcm_set_sync(substream); + + return 0; +err_locked: + snd_bebob_stream_lock_release(bebob); + return err; +} + +static int +pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + snd_bebob_stream_lock_release(bebob); + return 0; +} + +static int pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_bebob *bebob = substream->private_data; + int err = 0; + + if (substream->runtime->status->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); + + mutex_lock(&bebob->mutex); + err = snd_bebob_stream_reserve_duplex(bebob, rate, + frames_per_period, frames_per_buffer); + if (err >= 0) + ++bebob->substreams_counter; + mutex_unlock(&bebob->mutex); + } + + return err; +} + +static int pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + + mutex_lock(&bebob->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + bebob->substreams_counter--; + + snd_bebob_stream_stop_duplex(bebob); + + mutex_unlock(&bebob->mutex); + + return 0; +} + +static int +pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + int err; + + err = snd_bebob_stream_start_duplex(bebob); + if (err >= 0) + amdtp_stream_pcm_prepare(&bebob->tx_stream); + + return err; +} +static int +pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + int err; + + err = snd_bebob_stream_start_duplex(bebob); + if (err >= 0) + amdtp_stream_pcm_prepare(&bebob->rx_stream); + + return err; +} + +static int +pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_bebob *bebob = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&bebob->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&bebob->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int +pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_bebob *bebob = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&bebob->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&bebob->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_bebob *bebob = sbstrm->private_data; + + return amdtp_domain_stream_pcm_pointer(&bebob->domain, + &bebob->tx_stream); +} +static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_bebob *bebob = sbstrm->private_data; + + return amdtp_domain_stream_pcm_pointer(&bebob->domain, + &bebob->rx_stream); +} + +static int pcm_capture_ack(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + + return amdtp_domain_stream_pcm_ack(&bebob->domain, &bebob->tx_stream); +} + +static int pcm_playback_ack(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + + return amdtp_domain_stream_pcm_ack(&bebob->domain, &bebob->rx_stream); +} + +int snd_bebob_create_pcm_devices(struct snd_bebob *bebob) +{ + static const struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .hw_params = pcm_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .ack = pcm_capture_ack, + }; + static const struct snd_pcm_ops playback_ops = { + .open = pcm_open, + .close = pcm_close, + .hw_params = pcm_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .ack = pcm_playback_ack, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(bebob->card, bebob->card->driver, 0, 1, 1, &pcm); + if (err < 0) + goto end; + + pcm->private_data = bebob; + snprintf(pcm->name, sizeof(pcm->name), + "%s PCM", bebob->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0); +end: + return err; +} diff --git a/sound/firewire/bebob/bebob_proc.c b/sound/firewire/bebob/bebob_proc.c new file mode 100644 index 000000000..f659b888a --- /dev/null +++ b/sound/firewire/bebob/bebob_proc.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * bebob_proc.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + */ + +#include "./bebob.h" + +/* contents of information register */ +struct hw_info { + u64 manufacturer; + u32 protocol_ver; + u32 bld_ver; + u32 guid[2]; + u32 model_id; + u32 model_rev; + u64 fw_date; + u64 fw_time; + u32 fw_id; + u32 fw_ver; + u32 base_addr; + u32 max_size; + u64 bld_date; + u64 bld_time; +/* may not used in product + u64 dbg_date; + u64 dbg_time; + u32 dbg_id; + u32 dbg_version; +*/ +} __packed; + +static void +proc_read_hw_info(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_bebob *bebob = entry->private_data; + struct hw_info *info; + + info = kzalloc(sizeof(struct hw_info), GFP_KERNEL); + if (info == NULL) + return; + + if (snd_bebob_read_block(bebob->unit, 0, + info, sizeof(struct hw_info)) < 0) + goto end; + + snd_iprintf(buffer, "Manufacturer:\t%.8s\n", + (char *)&info->manufacturer); + snd_iprintf(buffer, "Protocol Ver:\t%d\n", info->protocol_ver); + snd_iprintf(buffer, "Build Ver:\t%d\n", info->bld_ver); + snd_iprintf(buffer, "GUID:\t\t0x%.8X%.8X\n", + info->guid[0], info->guid[1]); + snd_iprintf(buffer, "Model ID:\t0x%02X\n", info->model_id); + snd_iprintf(buffer, "Model Rev:\t%d\n", info->model_rev); + snd_iprintf(buffer, "Firmware Date:\t%.8s\n", (char *)&info->fw_date); + snd_iprintf(buffer, "Firmware Time:\t%.8s\n", (char *)&info->fw_time); + snd_iprintf(buffer, "Firmware ID:\t0x%X\n", info->fw_id); + snd_iprintf(buffer, "Firmware Ver:\t%d\n", info->fw_ver); + snd_iprintf(buffer, "Base Addr:\t0x%X\n", info->base_addr); + snd_iprintf(buffer, "Max Size:\t%d\n", info->max_size); + snd_iprintf(buffer, "Loader Date:\t%.8s\n", (char *)&info->bld_date); + snd_iprintf(buffer, "Loader Time:\t%.8s\n", (char *)&info->bld_time); + +end: + kfree(info); +} + +static void +proc_read_meters(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_bebob *bebob = entry->private_data; + const struct snd_bebob_meter_spec *spec = bebob->spec->meter; + u32 *buf; + unsigned int i, c, channels, size; + + if (spec == NULL) + return; + + channels = spec->num * 2; + size = channels * sizeof(u32); + buf = kmalloc(size, GFP_KERNEL); + if (buf == NULL) + return; + + if (spec->get(bebob, buf, size) < 0) + goto end; + + for (i = 0, c = 1; i < channels; i++) { + snd_iprintf(buffer, "%s %d:\t%d\n", + spec->labels[i / 2], c++, buf[i]); + if ((i + 1 < channels - 1) && + (strcmp(spec->labels[i / 2], + spec->labels[(i + 1) / 2]) != 0)) + c = 1; + } +end: + kfree(buf); +} + +static void +proc_read_formation(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_bebob *bebob = entry->private_data; + struct snd_bebob_stream_formation *formation; + unsigned int i; + + snd_iprintf(buffer, "Output Stream from device:\n"); + snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); + formation = bebob->tx_stream_formations; + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + snd_iprintf(buffer, + "\t%d\t%d\t%d\n", snd_bebob_rate_table[i], + formation[i].pcm, formation[i].midi); + } + + snd_iprintf(buffer, "Input Stream to device:\n"); + snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); + formation = bebob->rx_stream_formations; + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + snd_iprintf(buffer, + "\t%d\t%d\t%d\n", snd_bebob_rate_table[i], + formation[i].pcm, formation[i].midi); + } +} + +static void +proc_read_clock(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + static const char *const clk_labels[] = { + "Internal", + "External", + "SYT-Match", + }; + struct snd_bebob *bebob = entry->private_data; + const struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate; + const struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock; + enum snd_bebob_clock_type src; + unsigned int rate; + + if (rate_spec->get(bebob, &rate) >= 0) + snd_iprintf(buffer, "Sampling rate: %d\n", rate); + + if (snd_bebob_stream_get_clock_src(bebob, &src) >= 0) { + if (clk_spec) + snd_iprintf(buffer, "Clock Source: %s\n", + clk_labels[src]); + else + snd_iprintf(buffer, "Clock Source: %s (MSU-dest: %d)\n", + clk_labels[src], bebob->sync_input_plug); + } +} + +static void +add_node(struct snd_bebob *bebob, 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(bebob->card, name, root); + if (entry) + snd_info_set_text_ops(entry, bebob, op); +} + +void snd_bebob_proc_init(struct snd_bebob *bebob) +{ + 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(bebob->card, "firewire", + bebob->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | 0555; + + add_node(bebob, root, "clock", proc_read_clock); + add_node(bebob, root, "firmware", proc_read_hw_info); + add_node(bebob, root, "formation", proc_read_formation); + + if (bebob->spec->meter != NULL) + add_node(bebob, root, "meter", proc_read_meters); +} diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c new file mode 100644 index 000000000..c18017e0a --- /dev/null +++ b/sound/firewire/bebob/bebob_stream.c @@ -0,0 +1,1019 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * bebob_stream.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + */ + +#include "./bebob.h" + +#define CALLBACK_TIMEOUT 2500 +#define FW_ISO_RESOURCE_DELAY 1000 + +/* + * NOTE; + * For BeBoB streams, Both of input and output CMP connection are important. + * + * For most devices, each CMP connection starts to transmit/receive a + * corresponding stream. But for a few devices, both of CMP connection needs + * to start transmitting stream. An example is 'M-Audio Firewire 410'. + */ + +/* 128 is an arbitrary length but it seems to be enough */ +#define FORMAT_MAXIMUM_LENGTH 128 + +const unsigned int snd_bebob_rate_table[SND_BEBOB_STRM_FMT_ENTRIES] = { + [0] = 32000, + [1] = 44100, + [2] = 48000, + [3] = 88200, + [4] = 96000, + [5] = 176400, + [6] = 192000, +}; + +/* + * See: Table 51: Extended Stream Format Info ‘Sampling Frequency’ + * in Additional AVC commands (Nov 2003, BridgeCo) + */ +static const unsigned int bridgeco_freq_table[] = { + [0] = 0x02, + [1] = 0x03, + [2] = 0x04, + [3] = 0x0a, + [4] = 0x05, + [5] = 0x06, + [6] = 0x07, +}; + +static int +get_formation_index(unsigned int rate, unsigned int *index) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(snd_bebob_rate_table); i++) { + if (snd_bebob_rate_table[i] == rate) { + *index = i; + return 0; + } + } + return -EINVAL; +} + +int +snd_bebob_stream_get_rate(struct snd_bebob *bebob, unsigned int *curr_rate) +{ + unsigned int tx_rate, rx_rate, trials; + int err; + + trials = 0; + do { + err = avc_general_get_sig_fmt(bebob->unit, &tx_rate, + AVC_GENERAL_PLUG_DIR_OUT, 0); + } while (err == -EAGAIN && ++trials < 3); + if (err < 0) + goto end; + + trials = 0; + do { + err = avc_general_get_sig_fmt(bebob->unit, &rx_rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + } while (err == -EAGAIN && ++trials < 3); + if (err < 0) + goto end; + + *curr_rate = rx_rate; + if (rx_rate == tx_rate) + goto end; + + /* synchronize receive stream rate to transmit stream rate */ + err = avc_general_set_sig_fmt(bebob->unit, rx_rate, + AVC_GENERAL_PLUG_DIR_IN, 0); +end: + return err; +} + +int +snd_bebob_stream_set_rate(struct snd_bebob *bebob, unsigned int rate) +{ + int err; + + err = avc_general_set_sig_fmt(bebob->unit, rate, + AVC_GENERAL_PLUG_DIR_OUT, 0); + if (err < 0) + goto end; + + err = avc_general_set_sig_fmt(bebob->unit, rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) + goto end; + + /* + * Some devices need a bit time for transition. + * 300msec is got by some experiments. + */ + msleep(300); +end: + return err; +} + +int snd_bebob_stream_get_clock_src(struct snd_bebob *bebob, + enum snd_bebob_clock_type *src) +{ + const struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock; + u8 addr[AVC_BRIDGECO_ADDR_BYTES], input[7]; + unsigned int id; + enum avc_bridgeco_plug_type type; + int err = 0; + + /* 1.The device has its own operation to switch source of clock */ + if (clk_spec) { + err = clk_spec->get(bebob, &id); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get clock source: %d\n", err); + goto end; + } + + if (id >= clk_spec->num) { + dev_err(&bebob->unit->device, + "clock source %d out of range 0..%d\n", + id, clk_spec->num - 1); + err = -EIO; + goto end; + } + + *src = clk_spec->types[id]; + goto end; + } + + /* + * 2.The device don't support to switch source of clock then assumed + * to use internal clock always + */ + if (bebob->sync_input_plug < 0) { + *src = SND_BEBOB_CLOCK_TYPE_INTERNAL; + goto end; + } + + /* + * 3.The device supports to switch source of clock by an usual way. + * Let's check input for 'Music Sub Unit Sync Input' plug. + */ + avc_bridgeco_fill_msu_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, + bebob->sync_input_plug); + err = avc_bridgeco_get_plug_input(bebob->unit, addr, input); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get an input for MSU in plug %d: %d\n", + bebob->sync_input_plug, err); + goto end; + } + + /* + * If there are no input plugs, all of fields are 0xff. + * Here check the first field. This field is used for direction. + */ + if (input[0] == 0xff) { + *src = SND_BEBOB_CLOCK_TYPE_INTERNAL; + goto end; + } + + /* The source from any output plugs is for one purpose only. */ + if (input[0] == AVC_BRIDGECO_PLUG_DIR_OUT) { + /* + * In BeBoB architecture, the source from music subunit may + * bypass from oPCR[0]. This means that this source gives + * synchronization to IEEE 1394 cycle start packet. + */ + if (input[1] == AVC_BRIDGECO_PLUG_MODE_SUBUNIT && + input[2] == 0x0c) { + *src = SND_BEBOB_CLOCK_TYPE_INTERNAL; + goto end; + } + /* The source from any input units is for several purposes. */ + } else if (input[1] == AVC_BRIDGECO_PLUG_MODE_UNIT) { + if (input[2] == AVC_BRIDGECO_PLUG_UNIT_ISOC) { + if (input[3] == 0x00) { + /* + * This source comes from iPCR[0]. This means + * that presentation timestamp calculated by + * SYT series of the received packets. In + * short, this driver is the master of + * synchronization. + */ + *src = SND_BEBOB_CLOCK_TYPE_SYT; + goto end; + } else { + /* + * This source comes from iPCR[1-29]. This + * means that the synchronization stream is not + * the Audio/MIDI compound stream. + */ + *src = SND_BEBOB_CLOCK_TYPE_EXTERNAL; + goto end; + } + } else if (input[2] == AVC_BRIDGECO_PLUG_UNIT_EXT) { + /* Check type of this plug. */ + avc_bridgeco_fill_unit_addr(addr, + AVC_BRIDGECO_PLUG_DIR_IN, + AVC_BRIDGECO_PLUG_UNIT_EXT, + input[3]); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, + &type); + if (err < 0) + goto end; + + if (type == AVC_BRIDGECO_PLUG_TYPE_DIG) { + /* + * SPDIF/ADAT or sometimes (not always) word + * clock. + */ + *src = SND_BEBOB_CLOCK_TYPE_EXTERNAL; + goto end; + } else if (type == AVC_BRIDGECO_PLUG_TYPE_SYNC) { + /* Often word clock. */ + *src = SND_BEBOB_CLOCK_TYPE_EXTERNAL; + goto end; + } else if (type == AVC_BRIDGECO_PLUG_TYPE_ADDITION) { + /* + * Not standard. + * Mostly, additional internal clock. + */ + *src = SND_BEBOB_CLOCK_TYPE_INTERNAL; + goto end; + } + } + } + + /* Not supported. */ + err = -EIO; +end: + return err; +} + +static int map_data_channels(struct snd_bebob *bebob, struct amdtp_stream *s) +{ + unsigned int sec, sections, ch, channels; + unsigned int pcm, midi, location; + unsigned int stm_pos, sec_loc, pos; + u8 *buf, addr[AVC_BRIDGECO_ADDR_BYTES], type; + enum avc_bridgeco_plug_dir dir; + int err; + + /* + * The length of return value of this command cannot be expected. Here + * use the maximum length of FCP. + */ + buf = kzalloc(256, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + if (s == &bebob->tx_stream) + dir = AVC_BRIDGECO_PLUG_DIR_OUT; + else + dir = AVC_BRIDGECO_PLUG_DIR_IN; + + avc_bridgeco_fill_unit_addr(addr, dir, AVC_BRIDGECO_PLUG_UNIT_ISOC, 0); + err = avc_bridgeco_get_plug_ch_pos(bebob->unit, addr, buf, 256); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get channel position for isoc %s plug 0: %d\n", + (dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" : "out", + err); + goto end; + } + pos = 0; + + /* positions in I/O buffer */ + pcm = 0; + midi = 0; + + /* the number of sections in AMDTP packet */ + sections = buf[pos++]; + + for (sec = 0; sec < sections; sec++) { + /* type of this section */ + avc_bridgeco_fill_unit_addr(addr, dir, + AVC_BRIDGECO_PLUG_UNIT_ISOC, 0); + err = avc_bridgeco_get_plug_section_type(bebob->unit, addr, + sec, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get section type for isoc %s plug 0: %d\n", + (dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" : + "out", + err); + goto end; + } + /* NoType */ + if (type == 0xff) { + err = -ENOSYS; + goto end; + } + + /* the number of channels in this section */ + channels = buf[pos++]; + + for (ch = 0; ch < channels; ch++) { + /* position of this channel in AMDTP packet */ + stm_pos = buf[pos++] - 1; + /* location of this channel in this section */ + sec_loc = buf[pos++] - 1; + + /* + * Basically the number of location is within the + * number of channels in this section. But some models + * of M-Audio don't follow this. Its location for MIDI + * is the position of MIDI channels in AMDTP packet. + */ + if (sec_loc >= channels) + sec_loc = ch; + + switch (type) { + /* for MIDI conformant data channel */ + case 0x0a: + /* AMDTP_MAX_CHANNELS_FOR_MIDI is 1. */ + if ((midi > 0) && (stm_pos != midi)) { + err = -ENOSYS; + goto end; + } + amdtp_am824_set_midi_position(s, stm_pos); + midi = stm_pos; + break; + /* for PCM data channel */ + case 0x01: /* Headphone */ + case 0x02: /* Microphone */ + case 0x03: /* Line */ + case 0x04: /* SPDIF */ + case 0x05: /* ADAT */ + case 0x06: /* TDIF */ + case 0x07: /* MADI */ + /* for undefined/changeable signal */ + case 0x08: /* Analog */ + case 0x09: /* Digital */ + default: + location = pcm + sec_loc; + if (location >= AM824_MAX_CHANNELS_FOR_PCM) { + err = -ENOSYS; + goto end; + } + amdtp_am824_set_pcm_position(s, location, + stm_pos); + break; + } + } + + if (type != 0x0a) + pcm += channels; + else + midi += channels; + } +end: + kfree(buf); + return err; +} + +static int +check_connection_used_by_others(struct snd_bebob *bebob, struct amdtp_stream *s) +{ + struct cmp_connection *conn; + bool used; + int err; + + if (s == &bebob->tx_stream) + conn = &bebob->out_conn; + else + conn = &bebob->in_conn; + + err = cmp_connection_check_used(conn, &used); + if ((err >= 0) && used && !amdtp_stream_running(s)) { + dev_err(&bebob->unit->device, + "Connection established by others: %cPCR[%d]\n", + (conn->direction == CMP_OUTPUT) ? 'o' : 'i', + conn->pcr_index); + err = -EBUSY; + } + + return err; +} + +static void break_both_connections(struct snd_bebob *bebob) +{ + cmp_connection_break(&bebob->in_conn); + cmp_connection_break(&bebob->out_conn); + + // These models seem to be in transition state for a longer time. When + // accessing in the state, any transactions is corrupted. In the worst + // case, the device is going to reboot. + if (bebob->version < 2) + msleep(600); +} + +static int start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + int err = 0; + + if (stream == &bebob->rx_stream) + conn = &bebob->in_conn; + else + conn = &bebob->out_conn; + + // channel mapping. + if (bebob->maudio_special_quirk == NULL) { + err = map_data_channels(bebob, stream); + if (err < 0) + return err; + } + + err = cmp_connection_establish(conn); + if (err < 0) + return err; + + return amdtp_domain_add_stream(&bebob->domain, stream, + conn->resources.channel, conn->speed); +} + +static int init_stream(struct snd_bebob *bebob, struct amdtp_stream *stream) +{ + enum amdtp_stream_direction dir_stream; + struct cmp_connection *conn; + enum cmp_direction dir_conn; + int err; + + if (stream == &bebob->tx_stream) { + dir_stream = AMDTP_IN_STREAM; + conn = &bebob->out_conn; + dir_conn = CMP_OUTPUT; + } else { + dir_stream = AMDTP_OUT_STREAM; + conn = &bebob->in_conn; + dir_conn = CMP_INPUT; + } + + err = cmp_connection_init(conn, bebob->unit, dir_conn, 0); + if (err < 0) + return err; + + err = amdtp_am824_init(stream, bebob->unit, dir_stream, CIP_BLOCKING); + if (err < 0) { + cmp_connection_destroy(conn); + return err; + } + + if (stream == &bebob->tx_stream) { + // BeBoB v3 transfers packets with these qurks: + // - In the beginning of streaming, the value of dbc is + // incremented even if no data blocks are transferred. + // - The value of dbc is reset suddenly. + if (bebob->version > 2) + bebob->tx_stream.flags |= CIP_EMPTY_HAS_WRONG_DBC | + CIP_SKIP_DBC_ZERO_CHECK; + + // At high sampling rate, M-Audio special firmware transmits + // empty packet with the value of dbc incremented by 8 but the + // others are valid to IEC 61883-1. + if (bebob->maudio_special_quirk) + bebob->tx_stream.flags |= CIP_EMPTY_HAS_WRONG_DBC; + } + + return 0; +} + +static void destroy_stream(struct snd_bebob *bebob, struct amdtp_stream *stream) +{ + amdtp_stream_destroy(stream); + + if (stream == &bebob->tx_stream) + cmp_connection_destroy(&bebob->out_conn); + else + cmp_connection_destroy(&bebob->in_conn); +} + +int snd_bebob_stream_init_duplex(struct snd_bebob *bebob) +{ + int err; + + err = init_stream(bebob, &bebob->tx_stream); + if (err < 0) + return err; + + err = init_stream(bebob, &bebob->rx_stream); + if (err < 0) { + destroy_stream(bebob, &bebob->tx_stream); + return err; + } + + err = amdtp_domain_init(&bebob->domain); + if (err < 0) { + destroy_stream(bebob, &bebob->tx_stream); + destroy_stream(bebob, &bebob->rx_stream); + } + + return err; +} + +static int keep_resources(struct snd_bebob *bebob, struct amdtp_stream *stream, + unsigned int rate, unsigned int index) +{ + unsigned int pcm_channels; + unsigned int midi_ports; + struct cmp_connection *conn; + int err; + + if (stream == &bebob->tx_stream) { + pcm_channels = bebob->tx_stream_formations[index].pcm; + midi_ports = bebob->midi_input_ports; + conn = &bebob->out_conn; + } else { + pcm_channels = bebob->rx_stream_formations[index].pcm; + midi_ports = bebob->midi_output_ports; + conn = &bebob->in_conn; + } + + err = amdtp_am824_set_parameters(stream, rate, pcm_channels, midi_ports, false); + if (err < 0) + return err; + + return cmp_connection_reserve(conn, amdtp_stream_get_max_payload(stream)); +} + +int snd_bebob_stream_reserve_duplex(struct snd_bebob *bebob, unsigned int rate, + unsigned int frames_per_period, + unsigned int frames_per_buffer) +{ + unsigned int curr_rate; + int err; + + // Considering JACK/FFADO streaming: + // TODO: This can be removed hwdep functionality becomes popular. + err = check_connection_used_by_others(bebob, &bebob->rx_stream); + if (err < 0) + return err; + + err = bebob->spec->rate->get(bebob, &curr_rate); + if (err < 0) + return err; + if (rate == 0) + rate = curr_rate; + if (curr_rate != rate) { + amdtp_domain_stop(&bebob->domain); + break_both_connections(bebob); + + cmp_connection_release(&bebob->out_conn); + cmp_connection_release(&bebob->in_conn); + } + + if (bebob->substreams_counter == 0 || curr_rate != rate) { + unsigned int index; + + // NOTE: + // If establishing connections at first, Yamaha GO46 + // (and maybe Terratec X24) don't generate sound. + // + // For firmware customized by M-Audio, refer to next NOTE. + err = bebob->spec->rate->set(bebob, rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to set sampling rate: %d\n", + err); + return err; + } + + err = get_formation_index(rate, &index); + if (err < 0) + return err; + + err = keep_resources(bebob, &bebob->tx_stream, rate, index); + if (err < 0) + return err; + + err = keep_resources(bebob, &bebob->rx_stream, rate, index); + if (err < 0) { + cmp_connection_release(&bebob->out_conn); + return err; + } + + err = amdtp_domain_set_events_per_period(&bebob->domain, + frames_per_period, frames_per_buffer); + if (err < 0) { + cmp_connection_release(&bebob->out_conn); + cmp_connection_release(&bebob->in_conn); + return err; + } + } + + return 0; +} + +int snd_bebob_stream_start_duplex(struct snd_bebob *bebob) +{ + int err; + + // Need no substreams. + if (bebob->substreams_counter == 0) + return -EIO; + + // packet queueing error or detecting discontinuity + if (amdtp_streaming_error(&bebob->rx_stream) || + amdtp_streaming_error(&bebob->tx_stream)) { + amdtp_domain_stop(&bebob->domain); + break_both_connections(bebob); + } + + if (!amdtp_stream_running(&bebob->rx_stream)) { + enum snd_bebob_clock_type src; + struct amdtp_stream *master, *slave; + unsigned int curr_rate; + unsigned int ir_delay_cycle; + + if (bebob->maudio_special_quirk) { + err = bebob->spec->rate->get(bebob, &curr_rate); + if (err < 0) + return err; + } + + err = snd_bebob_stream_get_clock_src(bebob, &src); + if (err < 0) + return err; + + if (src != SND_BEBOB_CLOCK_TYPE_SYT) { + master = &bebob->tx_stream; + slave = &bebob->rx_stream; + } else { + master = &bebob->rx_stream; + slave = &bebob->tx_stream; + } + + err = start_stream(bebob, master); + if (err < 0) + goto error; + + err = start_stream(bebob, slave); + if (err < 0) + goto error; + + // The device postpones start of transmission mostly for 1 sec + // after receives packets firstly. For safe, IR context starts + // 0.4 sec (=3200 cycles) later to version 1 or 2 firmware, + // 2.0 sec (=16000 cycles) for version 3 firmware. This is + // within 2.5 sec (=CALLBACK_TIMEOUT). + // Furthermore, some devices transfer isoc packets with + // discontinuous counter in the beginning of packet streaming. + // The delay has an effect to avoid detection of this + // discontinuity. + if (bebob->version < 2) + ir_delay_cycle = 3200; + else + ir_delay_cycle = 16000; + err = amdtp_domain_start(&bebob->domain, ir_delay_cycle); + if (err < 0) + goto error; + + // NOTE: + // The firmware customized by M-Audio uses these commands to + // start transmitting stream. This is not usual way. + if (bebob->maudio_special_quirk) { + err = bebob->spec->rate->set(bebob, curr_rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to ensure sampling rate: %d\n", + err); + goto error; + } + } + + if (!amdtp_stream_wait_callback(&bebob->rx_stream, + CALLBACK_TIMEOUT) || + !amdtp_stream_wait_callback(&bebob->tx_stream, + CALLBACK_TIMEOUT)) { + err = -ETIMEDOUT; + goto error; + } + } + + return 0; +error: + amdtp_domain_stop(&bebob->domain); + break_both_connections(bebob); + return err; +} + +void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob) +{ + if (bebob->substreams_counter == 0) { + amdtp_domain_stop(&bebob->domain); + break_both_connections(bebob); + + cmp_connection_release(&bebob->out_conn); + cmp_connection_release(&bebob->in_conn); + } +} + +/* + * This function should be called before starting streams or after stopping + * streams. + */ +void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob) +{ + amdtp_domain_destroy(&bebob->domain); + + destroy_stream(bebob, &bebob->tx_stream); + destroy_stream(bebob, &bebob->rx_stream); +} + +/* + * See: Table 50: Extended Stream Format Info Format Hierarchy Level 2’ + * in Additional AVC commands (Nov 2003, BridgeCo) + * Also 'Clause 12 AM824 sequence adaption layers' in IEC 61883-6:2005 + */ +static int +parse_stream_formation(u8 *buf, unsigned int len, + struct snd_bebob_stream_formation *formation) +{ + unsigned int i, e, channels, format; + + /* + * this module can support a hierarchy combination that: + * Root: Audio and Music (0x90) + * Level 1: AM824 Compound (0x40) + */ + if ((buf[0] != 0x90) || (buf[1] != 0x40)) + return -ENOSYS; + + /* check sampling rate */ + for (i = 0; i < ARRAY_SIZE(bridgeco_freq_table); i++) { + if (buf[2] == bridgeco_freq_table[i]) + break; + } + if (i == ARRAY_SIZE(bridgeco_freq_table)) + return -ENOSYS; + + /* Avoid double count by different entries for the same rate. */ + memset(&formation[i], 0, sizeof(struct snd_bebob_stream_formation)); + + for (e = 0; e < buf[4]; e++) { + channels = buf[5 + e * 2]; + format = buf[6 + e * 2]; + + switch (format) { + /* IEC 60958 Conformant, currently handled as MBLA */ + case 0x00: + /* Multi bit linear audio */ + case 0x06: /* Raw */ + formation[i].pcm += channels; + break; + /* MIDI Conformant */ + case 0x0d: + formation[i].midi += channels; + break; + /* IEC 61937-3 to 7 */ + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + /* Multi bit linear audio */ + case 0x07: /* DVD-Audio */ + case 0x0c: /* High Precision */ + /* One Bit Audio */ + case 0x08: /* (Plain) Raw */ + case 0x09: /* (Plain) SACD */ + case 0x0a: /* (Encoded) Raw */ + case 0x0b: /* (Encoded) SACD */ + /* Synchronization Stream (Stereo Raw audio) */ + case 0x40: + /* Don't care */ + case 0xff: + default: + return -ENOSYS; /* not supported */ + } + } + + if (formation[i].pcm > AM824_MAX_CHANNELS_FOR_PCM || + formation[i].midi > AM824_MAX_CHANNELS_FOR_MIDI) + return -ENOSYS; + + return 0; +} + +static int +fill_stream_formations(struct snd_bebob *bebob, enum avc_bridgeco_plug_dir dir, + unsigned short pid) +{ + u8 *buf; + struct snd_bebob_stream_formation *formations; + unsigned int len, eid; + u8 addr[AVC_BRIDGECO_ADDR_BYTES]; + int err; + + buf = kmalloc(FORMAT_MAXIMUM_LENGTH, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + if (dir == AVC_BRIDGECO_PLUG_DIR_IN) + formations = bebob->rx_stream_formations; + else + formations = bebob->tx_stream_formations; + + for (eid = 0; eid < SND_BEBOB_STRM_FMT_ENTRIES; eid++) { + len = FORMAT_MAXIMUM_LENGTH; + avc_bridgeco_fill_unit_addr(addr, dir, + AVC_BRIDGECO_PLUG_UNIT_ISOC, pid); + err = avc_bridgeco_get_plug_strm_fmt(bebob->unit, addr, buf, + &len, eid); + /* No entries remained. */ + if (err == -EINVAL && eid > 0) { + err = 0; + break; + } else if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get stream format %d for isoc %s plug %d:%d\n", + eid, + (dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" : + "out", + pid, err); + break; + } + + err = parse_stream_formation(buf, len, formations); + if (err < 0) + break; + } + + kfree(buf); + return err; +} + +static int +seek_msu_sync_input_plug(struct snd_bebob *bebob) +{ + u8 plugs[AVC_PLUG_INFO_BUF_BYTES], addr[AVC_BRIDGECO_ADDR_BYTES]; + unsigned int i; + enum avc_bridgeco_plug_type type; + int err; + + /* Get the number of Music Sub Unit for both direction. */ + err = avc_general_get_plug_info(bebob->unit, 0x0c, 0x00, 0x00, plugs); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get info for MSU in/out plugs: %d\n", + err); + goto end; + } + + /* seek destination plugs for 'MSU sync input' */ + bebob->sync_input_plug = -1; + for (i = 0; i < plugs[0]; i++) { + avc_bridgeco_fill_msu_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, i); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get type for MSU in plug %d: %d\n", + i, err); + goto end; + } + + if (type == AVC_BRIDGECO_PLUG_TYPE_SYNC) { + bebob->sync_input_plug = i; + break; + } + } +end: + return err; +} + +int snd_bebob_stream_discover(struct snd_bebob *bebob) +{ + const struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock; + u8 plugs[AVC_PLUG_INFO_BUF_BYTES], addr[AVC_BRIDGECO_ADDR_BYTES]; + enum avc_bridgeco_plug_type type; + unsigned int i; + int err; + + /* the number of plugs for isoc in/out, ext in/out */ + err = avc_general_get_plug_info(bebob->unit, 0x1f, 0x07, 0x00, plugs); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get info for isoc/external in/out plugs: %d\n", + err); + goto end; + } + + /* + * This module supports at least one isoc input plug and one isoc + * output plug. + */ + if ((plugs[0] == 0) || (plugs[1] == 0)) { + err = -ENOSYS; + goto end; + } + + avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, + AVC_BRIDGECO_PLUG_UNIT_ISOC, 0); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get type for isoc in plug 0: %d\n", err); + goto end; + } else if (type != AVC_BRIDGECO_PLUG_TYPE_ISOC) { + err = -ENOSYS; + goto end; + } + err = fill_stream_formations(bebob, AVC_BRIDGECO_PLUG_DIR_IN, 0); + if (err < 0) + goto end; + + avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_OUT, + AVC_BRIDGECO_PLUG_UNIT_ISOC, 0); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get type for isoc out plug 0: %d\n", err); + goto end; + } else if (type != AVC_BRIDGECO_PLUG_TYPE_ISOC) { + err = -ENOSYS; + goto end; + } + err = fill_stream_formations(bebob, AVC_BRIDGECO_PLUG_DIR_OUT, 0); + if (err < 0) + goto end; + + /* count external input plugs for MIDI */ + bebob->midi_input_ports = 0; + for (i = 0; i < plugs[2]; i++) { + avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, + AVC_BRIDGECO_PLUG_UNIT_EXT, i); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get type for external in plug %d: %d\n", + i, err); + goto end; + } else if (type == AVC_BRIDGECO_PLUG_TYPE_MIDI) { + bebob->midi_input_ports++; + } + } + + /* count external output plugs for MIDI */ + bebob->midi_output_ports = 0; + for (i = 0; i < plugs[3]; i++) { + avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_OUT, + AVC_BRIDGECO_PLUG_UNIT_EXT, i); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get type for external out plug %d: %d\n", + i, err); + goto end; + } else if (type == AVC_BRIDGECO_PLUG_TYPE_MIDI) { + bebob->midi_output_ports++; + } + } + + /* for check source of clock later */ + if (!clk_spec) + err = seek_msu_sync_input_plug(bebob); +end: + return err; +} + +void snd_bebob_stream_lock_changed(struct snd_bebob *bebob) +{ + bebob->dev_lock_changed = true; + wake_up(&bebob->hwdep_wait); +} + +int snd_bebob_stream_lock_try(struct snd_bebob *bebob) +{ + int err; + + spin_lock_irq(&bebob->lock); + + /* user land lock this */ + if (bebob->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (bebob->dev_lock_count++ == 0) + snd_bebob_stream_lock_changed(bebob); + err = 0; +end: + spin_unlock_irq(&bebob->lock); + return err; +} + +void snd_bebob_stream_lock_release(struct snd_bebob *bebob) +{ + spin_lock_irq(&bebob->lock); + + if (WARN_ON(bebob->dev_lock_count <= 0)) + goto end; + if (--bebob->dev_lock_count == 0) + snd_bebob_stream_lock_changed(bebob); +end: + spin_unlock_irq(&bebob->lock); +} diff --git a/sound/firewire/bebob/bebob_terratec.c b/sound/firewire/bebob/bebob_terratec.c new file mode 100644 index 000000000..c2dd074ec --- /dev/null +++ b/sound/firewire/bebob/bebob_terratec.c @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * bebob_terratec.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + */ + +#include "./bebob.h" + +static const enum snd_bebob_clock_type phase88_rack_clk_src_types[] = { + SND_BEBOB_CLOCK_TYPE_INTERNAL, + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* S/PDIF */ + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* Word Clock */ +}; +static int +phase88_rack_clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + unsigned int enable_ext, enable_word; + int err; + + err = avc_audio_get_selector(bebob->unit, 0, 9, &enable_ext); + if (err < 0) + goto end; + err = avc_audio_get_selector(bebob->unit, 0, 8, &enable_word); + if (err < 0) + goto end; + + if (enable_ext == 0) + *id = 0; + else if (enable_word == 0) + *id = 1; + else + *id = 2; +end: + return err; +} + +static const struct snd_bebob_rate_spec phase_series_rate_spec = { + .get = &snd_bebob_stream_get_rate, + .set = &snd_bebob_stream_set_rate, +}; + +/* PHASE 88 Rack FW */ +static const struct snd_bebob_clock_spec phase88_rack_clk = { + .num = ARRAY_SIZE(phase88_rack_clk_src_types), + .types = phase88_rack_clk_src_types, + .get = &phase88_rack_clk_src_get, +}; +const struct snd_bebob_spec phase88_rack_spec = { + .clock = &phase88_rack_clk, + .rate = &phase_series_rate_spec, + .meter = NULL +}; diff --git a/sound/firewire/bebob/bebob_yamaha_terratec.c b/sound/firewire/bebob/bebob_yamaha_terratec.c new file mode 100644 index 000000000..ce1975e6a --- /dev/null +++ b/sound/firewire/bebob/bebob_yamaha_terratec.c @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * bebob_yamaha.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + */ + +#include "./bebob.h" + +/* + * NOTE: + * Yamaha GO44 is not designed to be used as stand-alone mixer. So any streams + * must be accompanied. If changing the state, a LED on the device starts to + * blink and its sync status is false. In this state, the device sounds nothing + * even if streaming. To start streaming at the current sampling rate is only + * way to recover this state. GO46 is better for stand-alone mixer. + * + * Both of them have a capability to change its sampling rate up to 192.0kHz. + * At 192.0kHz, the device reports 4 PCM-in, 1 MIDI-in, 6 PCM-out, 1 MIDI-out. + * But Yamaha's driver reduce 2 PCM-in, 1 MIDI-in, 2 PCM-out, 1 MIDI-out to use + * 'Extended Stream Format Information Command - Single Request' in 'Additional + * AVC commands' defined by BridgeCo. + * This ALSA driver don't do this because a bit tiresome. Then isochronous + * streaming with many asynchronous transactions brings sounds with noises. + * Unfortunately current 'ffado-mixer' generated many asynchronous transaction + * to observe device's state, mainly check cmp connection and signal format. I + * recommend users to close ffado-mixer at 192.0kHz if mixer is needless. + * + * Terratec PHASE 24 FW and PHASE X24 FW are internally the same as + * Yamaha GO 44 and GO 46. Yamaha and Terratec had cooperated for these models. + */ + +static const enum snd_bebob_clock_type clk_src_types[] = { + SND_BEBOB_CLOCK_TYPE_INTERNAL, + SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* S/PDIF */ +}; +static int +clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + int err; + + err = avc_audio_get_selector(bebob->unit, 0, 4, id); + if (err < 0) + return err; + + if (*id >= ARRAY_SIZE(clk_src_types)) + return -EIO; + + return 0; +} +static const struct snd_bebob_clock_spec clock_spec = { + .num = ARRAY_SIZE(clk_src_types), + .types = clk_src_types, + .get = &clk_src_get, +}; +static const struct snd_bebob_rate_spec rate_spec = { + .get = &snd_bebob_stream_get_rate, + .set = &snd_bebob_stream_set_rate, +}; +const struct snd_bebob_spec yamaha_terratec_spec = { + .clock = &clock_spec, + .rate = &rate_spec, + .meter = NULL +}; diff --git a/sound/firewire/cmp.c b/sound/firewire/cmp.c new file mode 100644 index 000000000..b596bec19 --- /dev/null +++ b/sound/firewire/cmp.c @@ -0,0 +1,415 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Connection Management Procedures (IEC 61883-1) helper functions + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + */ + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/sched.h> +#include "lib.h" +#include "iso-resources.h" +#include "cmp.h" + +/* MPR common fields */ +#define MPR_SPEED_MASK 0xc0000000 +#define MPR_SPEED_SHIFT 30 +#define MPR_XSPEED_MASK 0x00000060 +#define MPR_XSPEED_SHIFT 5 +#define MPR_PLUGS_MASK 0x0000001f + +/* PCR common fields */ +#define PCR_ONLINE 0x80000000 +#define PCR_BCAST_CONN 0x40000000 +#define PCR_P2P_CONN_MASK 0x3f000000 +#define PCR_P2P_CONN_SHIFT 24 +#define PCR_CHANNEL_MASK 0x003f0000 +#define PCR_CHANNEL_SHIFT 16 + +/* oPCR specific fields */ +#define OPCR_XSPEED_MASK 0x00C00000 +#define OPCR_XSPEED_SHIFT 22 +#define OPCR_SPEED_MASK 0x0000C000 +#define OPCR_SPEED_SHIFT 14 +#define OPCR_OVERHEAD_ID_MASK 0x00003C00 +#define OPCR_OVERHEAD_ID_SHIFT 10 + +enum bus_reset_handling { + ABORT_ON_BUS_RESET, + SUCCEED_ON_BUS_RESET, +}; + +static __printf(2, 3) +void cmp_error(struct cmp_connection *c, const char *fmt, ...) +{ + va_list va; + + va_start(va, fmt); + dev_err(&c->resources.unit->device, "%cPCR%u: %pV", + (c->direction == CMP_INPUT) ? 'i' : 'o', + c->pcr_index, &(struct va_format){ fmt, &va }); + va_end(va); +} + +static u64 mpr_address(struct cmp_connection *c) +{ + if (c->direction == CMP_INPUT) + return CSR_REGISTER_BASE + CSR_IMPR; + else + return CSR_REGISTER_BASE + CSR_OMPR; +} + +static u64 pcr_address(struct cmp_connection *c) +{ + if (c->direction == CMP_INPUT) + return CSR_REGISTER_BASE + CSR_IPCR(c->pcr_index); + else + return CSR_REGISTER_BASE + CSR_OPCR(c->pcr_index); +} + +static int pcr_modify(struct cmp_connection *c, + __be32 (*modify)(struct cmp_connection *c, __be32 old), + int (*check)(struct cmp_connection *c, __be32 pcr), + enum bus_reset_handling bus_reset_handling) +{ + __be32 old_arg, buffer[2]; + int err; + + buffer[0] = c->last_pcr_value; + for (;;) { + old_arg = buffer[0]; + buffer[1] = modify(c, buffer[0]); + + err = snd_fw_transaction( + c->resources.unit, TCODE_LOCK_COMPARE_SWAP, + pcr_address(c), buffer, 8, + FW_FIXED_GENERATION | c->resources.generation); + + if (err < 0) { + if (err == -EAGAIN && + bus_reset_handling == SUCCEED_ON_BUS_RESET) + err = 0; + return err; + } + + if (buffer[0] == old_arg) /* success? */ + break; + + if (check) { + err = check(c, buffer[0]); + if (err < 0) + return err; + } + } + c->last_pcr_value = buffer[1]; + + return 0; +} + + +/** + * cmp_connection_init - initializes a connection manager + * @c: the connection manager to initialize + * @unit: a unit of the target device + * @direction: input or output + * @pcr_index: the index of the iPCR/oPCR on the target device + */ +int cmp_connection_init(struct cmp_connection *c, + struct fw_unit *unit, + enum cmp_direction direction, + unsigned int pcr_index) +{ + __be32 mpr_be; + u32 mpr; + int err; + + c->direction = direction; + err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, + mpr_address(c), &mpr_be, 4, 0); + if (err < 0) + return err; + mpr = be32_to_cpu(mpr_be); + + if (pcr_index >= (mpr & MPR_PLUGS_MASK)) + return -EINVAL; + + err = fw_iso_resources_init(&c->resources, unit); + if (err < 0) + return err; + + c->connected = false; + mutex_init(&c->mutex); + c->last_pcr_value = cpu_to_be32(0x80000000); + c->pcr_index = pcr_index; + c->max_speed = (mpr & MPR_SPEED_MASK) >> MPR_SPEED_SHIFT; + if (c->max_speed == SCODE_BETA) + c->max_speed += (mpr & MPR_XSPEED_MASK) >> MPR_XSPEED_SHIFT; + + return 0; +} +EXPORT_SYMBOL(cmp_connection_init); + +/** + * cmp_connection_check_used - check connection is already esablished or not + * @c: the connection manager to be checked + * @used: the pointer to store the result of checking the connection + */ +int cmp_connection_check_used(struct cmp_connection *c, bool *used) +{ + __be32 pcr; + int err; + + err = snd_fw_transaction( + c->resources.unit, TCODE_READ_QUADLET_REQUEST, + pcr_address(c), &pcr, 4, 0); + if (err >= 0) + *used = !!(pcr & cpu_to_be32(PCR_BCAST_CONN | + PCR_P2P_CONN_MASK)); + + return err; +} +EXPORT_SYMBOL(cmp_connection_check_used); + +/** + * cmp_connection_destroy - free connection manager resources + * @c: the connection manager + */ +void cmp_connection_destroy(struct cmp_connection *c) +{ + WARN_ON(c->connected); + mutex_destroy(&c->mutex); + fw_iso_resources_destroy(&c->resources); +} +EXPORT_SYMBOL(cmp_connection_destroy); + +int cmp_connection_reserve(struct cmp_connection *c, + unsigned int max_payload_bytes) +{ + int err; + + mutex_lock(&c->mutex); + + if (WARN_ON(c->resources.allocated)) { + err = -EBUSY; + goto end; + } + + c->speed = min(c->max_speed, + fw_parent_device(c->resources.unit)->max_speed); + + err = fw_iso_resources_allocate(&c->resources, max_payload_bytes, + c->speed); +end: + mutex_unlock(&c->mutex); + + return err; +} +EXPORT_SYMBOL(cmp_connection_reserve); + +void cmp_connection_release(struct cmp_connection *c) +{ + mutex_lock(&c->mutex); + fw_iso_resources_free(&c->resources); + mutex_unlock(&c->mutex); +} +EXPORT_SYMBOL(cmp_connection_release); + +static __be32 ipcr_set_modify(struct cmp_connection *c, __be32 ipcr) +{ + ipcr &= ~cpu_to_be32(PCR_BCAST_CONN | + PCR_P2P_CONN_MASK | + PCR_CHANNEL_MASK); + ipcr |= cpu_to_be32(1 << PCR_P2P_CONN_SHIFT); + ipcr |= cpu_to_be32(c->resources.channel << PCR_CHANNEL_SHIFT); + + return ipcr; +} + +static int get_overhead_id(struct cmp_connection *c) +{ + int id; + + /* + * apply "oPCR overhead ID encoding" + * the encoding table can convert up to 512. + * here the value over 512 is converted as the same way as 512. + */ + for (id = 1; id < 16; id++) { + if (c->resources.bandwidth_overhead < (id << 5)) + break; + } + if (id == 16) + id = 0; + + return id; +} + +static __be32 opcr_set_modify(struct cmp_connection *c, __be32 opcr) +{ + unsigned int spd, xspd; + + /* generate speed and extended speed field value */ + if (c->speed > SCODE_400) { + spd = SCODE_800; + xspd = c->speed - SCODE_800; + } else { + spd = c->speed; + xspd = 0; + } + + opcr &= ~cpu_to_be32(PCR_BCAST_CONN | + PCR_P2P_CONN_MASK | + OPCR_XSPEED_MASK | + PCR_CHANNEL_MASK | + OPCR_SPEED_MASK | + OPCR_OVERHEAD_ID_MASK); + opcr |= cpu_to_be32(1 << PCR_P2P_CONN_SHIFT); + opcr |= cpu_to_be32(xspd << OPCR_XSPEED_SHIFT); + opcr |= cpu_to_be32(c->resources.channel << PCR_CHANNEL_SHIFT); + opcr |= cpu_to_be32(spd << OPCR_SPEED_SHIFT); + opcr |= cpu_to_be32(get_overhead_id(c) << OPCR_OVERHEAD_ID_SHIFT); + + return opcr; +} + +static int pcr_set_check(struct cmp_connection *c, __be32 pcr) +{ + if (pcr & cpu_to_be32(PCR_BCAST_CONN | + PCR_P2P_CONN_MASK)) { + cmp_error(c, "plug is already in use\n"); + return -EBUSY; + } + if (!(pcr & cpu_to_be32(PCR_ONLINE))) { + cmp_error(c, "plug is not on-line\n"); + return -ECONNREFUSED; + } + + return 0; +} + +/** + * cmp_connection_establish - establish a connection to the target + * @c: the connection manager + * + * This function establishes a point-to-point connection from the local + * computer to the target by allocating isochronous resources (channel and + * bandwidth) and setting the target's input/output plug control register. + * When this function succeeds, the caller is responsible for starting + * transmitting packets. + */ +int cmp_connection_establish(struct cmp_connection *c) +{ + int err; + + mutex_lock(&c->mutex); + + if (WARN_ON(c->connected)) { + mutex_unlock(&c->mutex); + return -EISCONN; + } + +retry_after_bus_reset: + if (c->direction == CMP_OUTPUT) + err = pcr_modify(c, opcr_set_modify, pcr_set_check, + ABORT_ON_BUS_RESET); + else + err = pcr_modify(c, ipcr_set_modify, pcr_set_check, + ABORT_ON_BUS_RESET); + + if (err == -EAGAIN) { + err = fw_iso_resources_update(&c->resources); + if (err >= 0) + goto retry_after_bus_reset; + } + if (err >= 0) + c->connected = true; + + mutex_unlock(&c->mutex); + + return err; +} +EXPORT_SYMBOL(cmp_connection_establish); + +/** + * cmp_connection_update - update the connection after a bus reset + * @c: the connection manager + * + * This function must be called from the driver's .update handler to + * reestablish any connection that might have been active. + * + * Returns zero on success, or a negative error code. On an error, the + * connection is broken and the caller must stop transmitting iso packets. + */ +int cmp_connection_update(struct cmp_connection *c) +{ + int err; + + mutex_lock(&c->mutex); + + if (!c->connected) { + mutex_unlock(&c->mutex); + return 0; + } + + err = fw_iso_resources_update(&c->resources); + if (err < 0) + goto err_unconnect; + + if (c->direction == CMP_OUTPUT) + err = pcr_modify(c, opcr_set_modify, pcr_set_check, + SUCCEED_ON_BUS_RESET); + else + err = pcr_modify(c, ipcr_set_modify, pcr_set_check, + SUCCEED_ON_BUS_RESET); + + if (err < 0) + goto err_unconnect; + + mutex_unlock(&c->mutex); + + return 0; + +err_unconnect: + c->connected = false; + mutex_unlock(&c->mutex); + + return err; +} +EXPORT_SYMBOL(cmp_connection_update); + +static __be32 pcr_break_modify(struct cmp_connection *c, __be32 pcr) +{ + return pcr & ~cpu_to_be32(PCR_BCAST_CONN | PCR_P2P_CONN_MASK); +} + +/** + * cmp_connection_break - break the connection to the target + * @c: the connection manager + * + * This function deactives the connection in the target's input/output plug + * control register, and frees the isochronous resources of the connection. + * Before calling this function, the caller should cease transmitting packets. + */ +void cmp_connection_break(struct cmp_connection *c) +{ + int err; + + mutex_lock(&c->mutex); + + if (!c->connected) { + mutex_unlock(&c->mutex); + return; + } + + err = pcr_modify(c, pcr_break_modify, NULL, SUCCEED_ON_BUS_RESET); + if (err < 0) + cmp_error(c, "plug is still connected\n"); + + c->connected = false; + + mutex_unlock(&c->mutex); +} +EXPORT_SYMBOL(cmp_connection_break); diff --git a/sound/firewire/cmp.h b/sound/firewire/cmp.h new file mode 100644 index 000000000..26ab88000 --- /dev/null +++ b/sound/firewire/cmp.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef SOUND_FIREWIRE_CMP_H_INCLUDED +#define SOUND_FIREWIRE_CMP_H_INCLUDED + +#include <linux/mutex.h> +#include <linux/types.h> +#include "iso-resources.h" + +struct fw_unit; + +enum cmp_direction { + CMP_INPUT = 0, + CMP_OUTPUT, +}; + +/** + * struct cmp_connection - manages an isochronous connection to a device + * @speed: the connection's actual speed + * + * This structure manages (using CMP) an isochronous stream between the local + * computer and a device's input plug (iPCR) and output plug (oPCR). + * + * There is no corresponding oPCR created on the local computer, so it is not + * possible to overlay connections on top of this one. + */ +struct cmp_connection { + int speed; + /* private: */ + bool connected; + struct mutex mutex; + struct fw_iso_resources resources; + __be32 last_pcr_value; + unsigned int pcr_index; + unsigned int max_speed; + enum cmp_direction direction; +}; + +int cmp_connection_init(struct cmp_connection *connection, + struct fw_unit *unit, + enum cmp_direction direction, + unsigned int pcr_index); +int cmp_connection_check_used(struct cmp_connection *connection, bool *used); +void cmp_connection_destroy(struct cmp_connection *connection); + +int cmp_connection_reserve(struct cmp_connection *connection, + unsigned int max_payload); +void cmp_connection_release(struct cmp_connection *connection); + +int cmp_connection_establish(struct cmp_connection *connection); +int cmp_connection_update(struct cmp_connection *connection); +void cmp_connection_break(struct cmp_connection *connection); + +#endif diff --git a/sound/firewire/dice/Makefile b/sound/firewire/dice/Makefile new file mode 100644 index 000000000..7a62dafd0 --- /dev/null +++ b/sound/firewire/dice/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +snd-dice-objs := dice-transaction.o dice-stream.o dice-proc.o dice-midi.o \ + dice-pcm.o dice-hwdep.o dice.o dice-tcelectronic.o \ + dice-alesis.o dice-extension.o dice-mytek.o dice-presonus.o +obj-$(CONFIG_SND_DICE) += snd-dice.o diff --git a/sound/firewire/dice/dice-alesis.c b/sound/firewire/dice/dice-alesis.c new file mode 100644 index 000000000..27c13b9cc --- /dev/null +++ b/sound/firewire/dice/dice-alesis.c @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dice-alesis.c - a part of driver for DICE based devices + * + * Copyright (c) 2018 Takashi Sakamoto + */ + +#include "dice.h" + +static const unsigned int +alesis_io14_tx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT] = { + {6, 6, 4}, /* Tx0 = Analog + S/PDIF. */ + {8, 4, 0}, /* Tx1 = ADAT1. */ +}; + +static const unsigned int +alesis_io26_tx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT] = { + {10, 10, 4}, /* Tx0 = Analog + S/PDIF. */ + {16, 4, 0}, /* Tx1 = ADAT1 + ADAT2 (available at low rate). */ +}; + +int snd_dice_detect_alesis_formats(struct snd_dice *dice) +{ + __be32 reg; + u32 data; + int i; + int err; + + err = snd_dice_transaction_read_tx(dice, TX_NUMBER_AUDIO, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + if (data == 4 || data == 6) { + memcpy(dice->tx_pcm_chs, alesis_io14_tx_pcm_chs, + MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * + sizeof(unsigned int)); + } else { + memcpy(dice->tx_pcm_chs, alesis_io26_tx_pcm_chs, + MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * + sizeof(unsigned int)); + } + + for (i = 0; i < SND_DICE_RATE_MODE_COUNT; ++i) + dice->rx_pcm_chs[0][i] = 8; + + dice->tx_midi_ports[0] = 1; + dice->rx_midi_ports[0] = 1; + + return 0; +} + +int snd_dice_detect_alesis_mastercontrol_formats(struct snd_dice *dice) +{ + int i; + + dice->tx_pcm_chs[0][SND_DICE_RATE_MODE_LOW] = 16; + dice->tx_pcm_chs[1][SND_DICE_RATE_MODE_LOW] = 12; + dice->tx_pcm_chs[0][SND_DICE_RATE_MODE_MIDDLE] = 12; + dice->tx_pcm_chs[1][SND_DICE_RATE_MODE_MIDDLE] = 4; + dice->tx_pcm_chs[0][SND_DICE_RATE_MODE_HIGH] = 8; + dice->tx_pcm_chs[1][SND_DICE_RATE_MODE_HIGH] = 0; + + for (i = 0; i < SND_DICE_RATE_MODE_COUNT; ++i) { + dice->rx_pcm_chs[0][i] = 6; + dice->rx_pcm_chs[1][i] = 0; + } + + for (i = 0; i < MAX_STREAMS; ++i) { + dice->tx_midi_ports[i] = 2; + dice->rx_midi_ports[i] = 2; + } + + return 0; +} diff --git a/sound/firewire/dice/dice-extension.c b/sound/firewire/dice/dice-extension.c new file mode 100644 index 000000000..02f4a8318 --- /dev/null +++ b/sound/firewire/dice/dice-extension.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dice-extension.c - a part of driver for DICE based devices + * + * Copyright (c) 2018 Takashi Sakamoto + */ + +#include "dice.h" + +/* For TCD2210/2220, TCAT defines extension of application protocol. */ + +#define DICE_EXT_APP_SPACE 0xffffe0200000uLL + +#define DICE_EXT_APP_CAPS_OFFSET 0x00 +#define DICE_EXT_APP_CAPS_SIZE 0x04 +#define DICE_EXT_APP_CMD_OFFSET 0x08 +#define DICE_EXT_APP_CMD_SIZE 0x0c +#define DICE_EXT_APP_MIXER_OFFSET 0x10 +#define DICE_EXT_APP_MIXER_SIZE 0x14 +#define DICE_EXT_APP_PEAK_OFFSET 0x18 +#define DICE_EXT_APP_PEAK_SIZE 0x1c +#define DICE_EXT_APP_ROUTER_OFFSET 0x20 +#define DICE_EXT_APP_ROUTER_SIZE 0x24 +#define DICE_EXT_APP_STREAM_OFFSET 0x28 +#define DICE_EXT_APP_STREAM_SIZE 0x2c +#define DICE_EXT_APP_CURRENT_OFFSET 0x30 +#define DICE_EXT_APP_CURRENT_SIZE 0x34 +#define DICE_EXT_APP_STANDALONE_OFFSET 0x38 +#define DICE_EXT_APP_STANDALONE_SIZE 0x3c +#define DICE_EXT_APP_APPLICATION_OFFSET 0x40 +#define DICE_EXT_APP_APPLICATION_SIZE 0x44 + +#define EXT_APP_STREAM_TX_NUMBER 0x0000 +#define EXT_APP_STREAM_RX_NUMBER 0x0004 +#define EXT_APP_STREAM_ENTRIES 0x0008 +#define EXT_APP_STREAM_ENTRY_SIZE 0x010c +#define EXT_APP_NUMBER_AUDIO 0x0000 +#define EXT_APP_NUMBER_MIDI 0x0004 +#define EXT_APP_NAMES 0x0008 +#define EXT_APP_NAMES_SIZE 256 +#define EXT_APP_AC3 0x0108 + +#define EXT_APP_CONFIG_LOW_ROUTER 0x0000 +#define EXT_APP_CONFIG_LOW_STREAM 0x1000 +#define EXT_APP_CONFIG_MIDDLE_ROUTER 0x2000 +#define EXT_APP_CONFIG_MIDDLE_STREAM 0x3000 +#define EXT_APP_CONFIG_HIGH_ROUTER 0x4000 +#define EXT_APP_CONFIG_HIGH_STREAM 0x5000 + +static inline int read_transaction(struct snd_dice *dice, u64 section_addr, + u32 offset, void *buf, size_t len) +{ + return snd_fw_transaction(dice->unit, + len == 4 ? TCODE_READ_QUADLET_REQUEST : + TCODE_READ_BLOCK_REQUEST, + section_addr + offset, buf, len, 0); +} + +static int read_stream_entries(struct snd_dice *dice, u64 section_addr, + u32 base_offset, unsigned int stream_count, + unsigned int mode, + unsigned int pcm_channels[MAX_STREAMS][3], + unsigned int midi_ports[MAX_STREAMS]) +{ + u32 entry_offset; + __be32 reg[2]; + int err; + int i; + + for (i = 0; i < stream_count; ++i) { + entry_offset = base_offset + i * EXT_APP_STREAM_ENTRY_SIZE; + err = read_transaction(dice, section_addr, + entry_offset + EXT_APP_NUMBER_AUDIO, + reg, sizeof(reg)); + if (err < 0) + return err; + pcm_channels[i][mode] = be32_to_cpu(reg[0]); + midi_ports[i] = max(midi_ports[i], be32_to_cpu(reg[1])); + } + + return 0; +} + +static int detect_stream_formats(struct snd_dice *dice, u64 section_addr) +{ + u32 base_offset; + __be32 reg[2]; + unsigned int stream_count; + int mode; + int err = 0; + + for (mode = 0; mode < SND_DICE_RATE_MODE_COUNT; ++mode) { + unsigned int cap; + + /* + * Some models report stream formats at highest mode, however + * they don't support the mode. Check clock capabilities. + */ + if (mode == 2) { + cap = CLOCK_CAP_RATE_176400 | CLOCK_CAP_RATE_192000; + } else if (mode == 1) { + cap = CLOCK_CAP_RATE_88200 | CLOCK_CAP_RATE_96000; + } else { + cap = CLOCK_CAP_RATE_32000 | CLOCK_CAP_RATE_44100 | + CLOCK_CAP_RATE_48000; + } + if (!(cap & dice->clock_caps)) + continue; + + base_offset = 0x2000 * mode + 0x1000; + + err = read_transaction(dice, section_addr, + base_offset + EXT_APP_STREAM_TX_NUMBER, + ®, sizeof(reg)); + if (err < 0) + break; + + base_offset += EXT_APP_STREAM_ENTRIES; + stream_count = be32_to_cpu(reg[0]); + err = read_stream_entries(dice, section_addr, base_offset, + stream_count, mode, + dice->tx_pcm_chs, + dice->tx_midi_ports); + if (err < 0) + break; + + base_offset += stream_count * EXT_APP_STREAM_ENTRY_SIZE; + stream_count = be32_to_cpu(reg[1]); + err = read_stream_entries(dice, section_addr, base_offset, + stream_count, + mode, dice->rx_pcm_chs, + dice->rx_midi_ports); + if (err < 0) + break; + } + + return err; +} + +int snd_dice_detect_extension_formats(struct snd_dice *dice) +{ + __be32 *pointers; + unsigned int i; + u64 section_addr; + int err; + + pointers = kmalloc_array(9, sizeof(__be32) * 2, GFP_KERNEL); + if (pointers == NULL) + return -ENOMEM; + + err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, + DICE_EXT_APP_SPACE, pointers, + 9 * sizeof(__be32) * 2, 0); + if (err < 0) + goto end; + + /* Check two of them for offset have the same value or not. */ + for (i = 0; i < 9; ++i) { + int j; + + for (j = i + 1; j < 9; ++j) { + if (pointers[i * 2] == pointers[j * 2]) { + // Fallback to limited functionality. + err = -ENXIO; + goto end; + } + } + } + + section_addr = DICE_EXT_APP_SPACE + be32_to_cpu(pointers[12]) * 4; + err = detect_stream_formats(dice, section_addr); +end: + kfree(pointers); + return err; +} diff --git a/sound/firewire/dice/dice-hwdep.c b/sound/firewire/dice/dice-hwdep.c new file mode 100644 index 000000000..f69f79967 --- /dev/null +++ b/sound/firewire/dice/dice-hwdep.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * dice_hwdep.c - a part of driver for DICE based devices + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * Copyright (c) 2014 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#include "dice.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, + long count, loff_t *offset) +{ + struct snd_dice *dice = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&dice->lock); + + while (!dice->dev_lock_changed && dice->notification_bits == 0) { + prepare_to_wait(&dice->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&dice->lock); + schedule(); + finish_wait(&dice->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&dice->lock); + } + + memset(&event, 0, sizeof(event)); + if (dice->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = dice->dev_lock_count > 0; + dice->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } else { + event.dice_notification.type = + SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION; + event.dice_notification.notification = dice->notification_bits; + dice->notification_bits = 0; + + count = min_t(long, count, sizeof(event.dice_notification)); + } + + spin_unlock_irq(&dice->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_dice *dice = hwdep->private_data; + __poll_t events; + + poll_wait(file, &dice->hwdep_wait, wait); + + spin_lock_irq(&dice->lock); + if (dice->dev_lock_changed || dice->notification_bits != 0) + events = EPOLLIN | EPOLLRDNORM; + else + events = 0; + spin_unlock_irq(&dice->lock); + + return events; +} + +static int hwdep_get_info(struct snd_dice *dice, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(dice->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_DICE; + 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]); + strlcpy(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_dice *dice) +{ + int err; + + spin_lock_irq(&dice->lock); + + if (dice->dev_lock_count == 0) { + dice->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&dice->lock); + + return err; +} + +static int hwdep_unlock(struct snd_dice *dice) +{ + int err; + + spin_lock_irq(&dice->lock); + + if (dice->dev_lock_count == -1) { + dice->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&dice->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_dice *dice = hwdep->private_data; + + spin_lock_irq(&dice->lock); + if (dice->dev_lock_count == -1) + dice->dev_lock_count = 0; + spin_unlock_irq(&dice->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_dice *dice = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(dice, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(dice); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(dice); + 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_dice_create_hwdep(struct snd_dice *dice) +{ + static const struct snd_hwdep_ops ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(dice->card, "DICE", 0, &hwdep); + if (err < 0) + return err; + strcpy(hwdep->name, "DICE"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_DICE; + hwdep->ops = ops; + hwdep->private_data = dice; + hwdep->exclusive = true; + + return 0; +} diff --git a/sound/firewire/dice/dice-interface.h b/sound/firewire/dice/dice-interface.h new file mode 100644 index 000000000..9cad3d608 --- /dev/null +++ b/sound/firewire/dice/dice-interface.h @@ -0,0 +1,378 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef SOUND_FIREWIRE_DICE_INTERFACE_H_INCLUDED +#define SOUND_FIREWIRE_DICE_INTERFACE_H_INCLUDED + +/* + * DICE device interface definitions + */ + +/* + * Generally, all registers can be read like memory, i.e., with quadlet read or + * block read transactions with at least quadlet-aligned offset and length. + * Writes are not allowed except where noted; quadlet-sized registers must be + * written with a quadlet write transaction. + * + * All values are in big endian. The DICE firmware runs on a little-endian CPU + * and just byte-swaps _all_ quadlets on the bus, so values without endianness + * (e.g. strings) get scrambled and must be byte-swapped again by the driver. + */ + +/* + * Streaming is handled by the "DICE driver" interface. Its registers are + * located in this private address space. + */ +#define DICE_PRIVATE_SPACE 0xffffe0000000uLL + +/* + * The registers are organized in several sections, which are organized + * separately to allow them to be extended individually. Whether a register is + * supported can be detected by checking its offset against its section's size. + * + * The section offset values are relative to DICE_PRIVATE_SPACE; the offset/ + * size values are measured in quadlets. Read-only. + */ +#define DICE_GLOBAL_OFFSET 0x00 +#define DICE_GLOBAL_SIZE 0x04 +#define DICE_TX_OFFSET 0x08 +#define DICE_TX_SIZE 0x0c +#define DICE_RX_OFFSET 0x10 +#define DICE_RX_SIZE 0x14 +#define DICE_EXT_SYNC_OFFSET 0x18 +#define DICE_EXT_SYNC_SIZE 0x1c +#define DICE_UNUSED2_OFFSET 0x20 +#define DICE_UNUSED2_SIZE 0x24 + +/* + * Global settings. + */ + +/* + * Stores the full 64-bit address (node ID and offset in the node's address + * space) where the device will send notifications. Must be changed with + * a compare/swap transaction by the owner. This register is automatically + * cleared on a bus reset. + */ +#define GLOBAL_OWNER 0x000 +#define OWNER_NO_OWNER 0xffff000000000000uLL +#define OWNER_NODE_SHIFT 48 + +/* + * A bitmask with asynchronous events; read-only. When any event(s) happen, + * the bits of previous events are cleared, and the value of this register is + * also written to the address stored in the owner register. + */ +#define GLOBAL_NOTIFICATION 0x008 +/* Some registers in the Rx/Tx sections may have changed. */ +#define NOTIFY_RX_CFG_CHG 0x00000001 +#define NOTIFY_TX_CFG_CHG 0x00000002 +/* Lock status of the current clock source may have changed. */ +#define NOTIFY_LOCK_CHG 0x00000010 +/* Write to the clock select register has been finished. */ +#define NOTIFY_CLOCK_ACCEPTED 0x00000020 +/* Lock status of some clock source has changed. */ +#define NOTIFY_EXT_STATUS 0x00000040 +/* Other bits may be used for device-specific events. */ + +/* + * A name that can be customized for each device; read/write. Padded with zero + * bytes. Quadlets are byte-swapped. The encoding is whatever the host driver + * happens to be using. + */ +#define GLOBAL_NICK_NAME 0x00c +#define NICK_NAME_SIZE 64 + +/* + * The current sample rate and clock source; read/write. Whether a clock + * source or sample rate is supported is device-specific; the internal clock + * source is always available. Low/mid/high = up to 48/96/192 kHz. This + * register can be changed even while streams are running. + */ +#define GLOBAL_CLOCK_SELECT 0x04c +#define CLOCK_SOURCE_MASK 0x000000ff +#define CLOCK_SOURCE_AES1 0x00000000 +#define CLOCK_SOURCE_AES2 0x00000001 +#define CLOCK_SOURCE_AES3 0x00000002 +#define CLOCK_SOURCE_AES4 0x00000003 +#define CLOCK_SOURCE_AES_ANY 0x00000004 +#define CLOCK_SOURCE_ADAT 0x00000005 +#define CLOCK_SOURCE_TDIF 0x00000006 +#define CLOCK_SOURCE_WC 0x00000007 +#define CLOCK_SOURCE_ARX1 0x00000008 +#define CLOCK_SOURCE_ARX2 0x00000009 +#define CLOCK_SOURCE_ARX3 0x0000000a +#define CLOCK_SOURCE_ARX4 0x0000000b +#define CLOCK_SOURCE_INTERNAL 0x0000000c +#define CLOCK_RATE_MASK 0x0000ff00 +#define CLOCK_RATE_32000 0x00000000 +#define CLOCK_RATE_44100 0x00000100 +#define CLOCK_RATE_48000 0x00000200 +#define CLOCK_RATE_88200 0x00000300 +#define CLOCK_RATE_96000 0x00000400 +#define CLOCK_RATE_176400 0x00000500 +#define CLOCK_RATE_192000 0x00000600 +#define CLOCK_RATE_ANY_LOW 0x00000700 +#define CLOCK_RATE_ANY_MID 0x00000800 +#define CLOCK_RATE_ANY_HIGH 0x00000900 +#define CLOCK_RATE_NONE 0x00000a00 +#define CLOCK_RATE_SHIFT 8 + +/* + * Enable streaming; read/write. Writing a non-zero value (re)starts all + * streams that have a valid iso channel set; zero stops all streams. The + * streams' parameters must be configured before starting. This register is + * automatically cleared on a bus reset. + */ +#define GLOBAL_ENABLE 0x050 + +/* + * Status of the sample clock; read-only. + */ +#define GLOBAL_STATUS 0x054 +/* The current clock source is locked. */ +#define STATUS_SOURCE_LOCKED 0x00000001 +/* The actual sample rate; CLOCK_RATE_32000-_192000 or _NONE. */ +#define STATUS_NOMINAL_RATE_MASK 0x0000ff00 + +/* + * Status of all clock sources; read-only. + */ +#define GLOBAL_EXTENDED_STATUS 0x058 +/* + * The _LOCKED bits always show the current status; any change generates + * a notification. + */ +#define EXT_STATUS_AES1_LOCKED 0x00000001 +#define EXT_STATUS_AES2_LOCKED 0x00000002 +#define EXT_STATUS_AES3_LOCKED 0x00000004 +#define EXT_STATUS_AES4_LOCKED 0x00000008 +#define EXT_STATUS_ADAT_LOCKED 0x00000010 +#define EXT_STATUS_TDIF_LOCKED 0x00000020 +#define EXT_STATUS_ARX1_LOCKED 0x00000040 +#define EXT_STATUS_ARX2_LOCKED 0x00000080 +#define EXT_STATUS_ARX3_LOCKED 0x00000100 +#define EXT_STATUS_ARX4_LOCKED 0x00000200 +#define EXT_STATUS_WC_LOCKED 0x00000400 +/* + * The _SLIP bits do not generate notifications; a set bit indicates that an + * error occurred since the last time when this register was read with + * a quadlet read transaction. + */ +#define EXT_STATUS_AES1_SLIP 0x00010000 +#define EXT_STATUS_AES2_SLIP 0x00020000 +#define EXT_STATUS_AES3_SLIP 0x00040000 +#define EXT_STATUS_AES4_SLIP 0x00080000 +#define EXT_STATUS_ADAT_SLIP 0x00100000 +#define EXT_STATUS_TDIF_SLIP 0x00200000 +#define EXT_STATUS_ARX1_SLIP 0x00400000 +#define EXT_STATUS_ARX2_SLIP 0x00800000 +#define EXT_STATUS_ARX3_SLIP 0x01000000 +#define EXT_STATUS_ARX4_SLIP 0x02000000 +#define EXT_STATUS_WC_SLIP 0x04000000 + +/* + * The measured rate of the current clock source, in Hz; read-only. + */ +#define GLOBAL_SAMPLE_RATE 0x05c + +/* + * Some old firmware versions do not have the following global registers. + * Windows drivers produced by TCAT lost backward compatibility in its + * early release because they can handle firmware only which supports the + * following registers. + */ + +/* + * The version of the DICE driver specification that this device conforms to; + * read-only. + */ +#define GLOBAL_VERSION 0x060 + +/* + * Supported sample rates and clock sources; read-only. + */ +#define GLOBAL_CLOCK_CAPABILITIES 0x064 +#define CLOCK_CAP_RATE_32000 0x00000001 +#define CLOCK_CAP_RATE_44100 0x00000002 +#define CLOCK_CAP_RATE_48000 0x00000004 +#define CLOCK_CAP_RATE_88200 0x00000008 +#define CLOCK_CAP_RATE_96000 0x00000010 +#define CLOCK_CAP_RATE_176400 0x00000020 +#define CLOCK_CAP_RATE_192000 0x00000040 +#define CLOCK_CAP_SOURCE_AES1 0x00010000 +#define CLOCK_CAP_SOURCE_AES2 0x00020000 +#define CLOCK_CAP_SOURCE_AES3 0x00040000 +#define CLOCK_CAP_SOURCE_AES4 0x00080000 +#define CLOCK_CAP_SOURCE_AES_ANY 0x00100000 +#define CLOCK_CAP_SOURCE_ADAT 0x00200000 +#define CLOCK_CAP_SOURCE_TDIF 0x00400000 +#define CLOCK_CAP_SOURCE_WC 0x00800000 +#define CLOCK_CAP_SOURCE_ARX1 0x01000000 +#define CLOCK_CAP_SOURCE_ARX2 0x02000000 +#define CLOCK_CAP_SOURCE_ARX3 0x04000000 +#define CLOCK_CAP_SOURCE_ARX4 0x08000000 +#define CLOCK_CAP_SOURCE_INTERNAL 0x10000000 + +/* + * Names of all clock sources; read-only. Quadlets are byte-swapped. Names + * are separated with one backslash, the list is terminated with two + * backslashes. Unused clock sources are included. + */ +#define GLOBAL_CLOCK_SOURCE_NAMES 0x068 +#define CLOCK_SOURCE_NAMES_SIZE 256 + +/* + * Capture stream settings. This section includes the number/size registers + * and the registers of all streams. + */ + +/* + * The number of supported capture streams; read-only. + */ +#define TX_NUMBER 0x000 + +/* + * The size of one stream's register block, in quadlets; read-only. The + * registers of the first stream follow immediately afterwards; the registers + * of the following streams are offset by this register's value. + */ +#define TX_SIZE 0x004 + +/* + * The isochronous channel number on which packets are sent, or -1 if the + * stream is not to be used; read/write. + */ +#define TX_ISOCHRONOUS 0x008 + +/* + * The number of audio channels; read-only. There will be one quadlet per + * channel; the first channel is the first quadlet in a data block. + */ +#define TX_NUMBER_AUDIO 0x00c + +/* + * The number of MIDI ports, 0-8; read-only. If > 0, there will be one + * additional quadlet in each data block, following the audio quadlets. + */ +#define TX_NUMBER_MIDI 0x010 + +/* + * The speed at which the packets are sent, SCODE_100-_400; read/write. + * SCODE_800 is only available in Dice III. + */ +#define TX_SPEED 0x014 + +/* + * Names of all audio channels; read-only. Quadlets are byte-swapped. Names + * are separated with one backslash, the list is terminated with two + * backslashes. + */ +#define TX_NAMES 0x018 +#define TX_NAMES_SIZE 256 + +/* + * Audio IEC60958 capabilities; read-only. Bitmask with one bit per audio + * channel. + */ +#define TX_AC3_CAPABILITIES 0x118 + +/* + * Send audio data with IEC60958 label; read/write. Bitmask with one bit per + * audio channel. This register can be changed even while the stream is + * running. + */ +#define TX_AC3_ENABLE 0x11c + +/* + * Playback stream settings. This section includes the number/size registers + * and the registers of all streams. + */ + +/* + * The number of supported playback streams; read-only. + */ +#define RX_NUMBER 0x000 + +/* + * The size of one stream's register block, in quadlets; read-only. The + * registers of the first stream follow immediately afterwards; the registers + * of the following streams are offset by this register's value. + */ +#define RX_SIZE 0x004 + +/* + * The isochronous channel number on which packets are received, or -1 if the + * stream is not to be used; read/write. + */ +#define RX_ISOCHRONOUS 0x008 + +/* + * Index of first quadlet to be interpreted; read/write. If > 0, that many + * quadlets at the beginning of each data block will be ignored, and all the + * audio and MIDI quadlets will follow. + */ +#define RX_SEQ_START 0x00c + +/* + * The number of audio channels; read-only. There will be one quadlet per + * channel. + */ +#define RX_NUMBER_AUDIO 0x010 + +/* + * The number of MIDI ports, 0-8; read-only. If > 0, there will be one + * additional quadlet in each data block, following the audio quadlets. + */ +#define RX_NUMBER_MIDI 0x014 + +/* + * Names of all audio channels; read-only. Quadlets are byte-swapped. Names + * are separated with one backslash, the list is terminated with two + * backslashes. + */ +#define RX_NAMES 0x018 +#define RX_NAMES_SIZE 256 + +/* + * Audio IEC60958 capabilities; read-only. Bitmask with one bit per audio + * channel. + */ +#define RX_AC3_CAPABILITIES 0x118 + +/* + * Receive audio data with IEC60958 label; read/write. Bitmask with one bit + * per audio channel. This register can be changed even while the stream is + * running. + */ +#define RX_AC3_ENABLE 0x11c + +/* + * Extended synchronization information. + * This section can be read completely with a block read request. + */ + +/* + * Current clock source; read-only. + */ +#define EXT_SYNC_CLOCK_SOURCE 0x000 + +/* + * Clock source is locked (boolean); read-only. + */ +#define EXT_SYNC_LOCKED 0x004 + +/* + * Current sample rate (CLOCK_RATE_* >> CLOCK_RATE_SHIFT), _32000-_192000 or + * _NONE; read-only. + */ +#define EXT_SYNC_RATE 0x008 + +/* + * ADAT user data bits; read-only. + */ +#define EXT_SYNC_ADAT_USER_DATA 0x00c +/* The data bits, if available. */ +#define ADAT_USER_DATA_MASK 0x0f +/* The data bits are not available. */ +#define ADAT_USER_DATA_NO_DATA 0x10 + +#endif diff --git a/sound/firewire/dice/dice-midi.c b/sound/firewire/dice/dice-midi.c new file mode 100644 index 000000000..4c2998034 --- /dev/null +++ b/sound/firewire/dice/dice-midi.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * dice_midi.c - a part of driver for Dice based devices + * + * Copyright (c) 2014 Takashi Sakamoto + */ +#include "dice.h" + +static int midi_open(struct snd_rawmidi_substream *substream) +{ + struct snd_dice *dice = substream->rmidi->private_data; + int err; + + err = snd_dice_stream_lock_try(dice); + if (err < 0) + return err; + + mutex_lock(&dice->mutex); + + err = snd_dice_stream_reserve_duplex(dice, 0, 0, 0); + if (err >= 0) { + ++dice->substreams_counter; + err = snd_dice_stream_start_duplex(dice); + if (err < 0) + --dice->substreams_counter; + } + + mutex_unlock(&dice->mutex); + + if (err < 0) + snd_dice_stream_lock_release(dice); + + return err; +} + +static int midi_close(struct snd_rawmidi_substream *substream) +{ + struct snd_dice *dice = substream->rmidi->private_data; + + mutex_lock(&dice->mutex); + + --dice->substreams_counter; + snd_dice_stream_stop_duplex(dice); + + mutex_unlock(&dice->mutex); + + snd_dice_stream_lock_release(dice); + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_dice *dice = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&dice->lock, flags); + + if (up) + amdtp_am824_midi_trigger(&dice->tx_stream[0], + substrm->number, substrm); + else + amdtp_am824_midi_trigger(&dice->tx_stream[0], + substrm->number, NULL); + + spin_unlock_irqrestore(&dice->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_dice *dice = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&dice->lock, flags); + + if (up) + amdtp_am824_midi_trigger(&dice->rx_stream[0], + substrm->number, substrm); + else + amdtp_am824_midi_trigger(&dice->rx_stream[0], + substrm->number, NULL); + + spin_unlock_irqrestore(&dice->lock, flags); +} + +static void set_midi_substream_names(struct snd_dice *dice, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", dice->card->shortname, subs->number + 1); + } +} + +int snd_dice_create_midi(struct snd_dice *dice) +{ + static const struct snd_rawmidi_ops capture_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_capture_trigger, + }; + static const struct snd_rawmidi_ops playback_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_playback_trigger, + }; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + unsigned int midi_in_ports, midi_out_ports; + int i; + int err; + + midi_in_ports = 0; + midi_out_ports = 0; + for (i = 0; i < MAX_STREAMS; ++i) { + midi_in_ports = max(midi_in_ports, dice->tx_midi_ports[i]); + midi_out_ports = max(midi_out_ports, dice->rx_midi_ports[i]); + } + + if (midi_in_ports + midi_out_ports == 0) + return 0; + + /* create midi ports */ + err = snd_rawmidi_new(dice->card, dice->card->driver, 0, + midi_out_ports, midi_in_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", dice->card->shortname); + rmidi->private_data = dice; + + if (midi_in_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &capture_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + + set_midi_substream_names(dice, str); + } + + if (midi_out_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &playback_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + set_midi_substream_names(dice, str); + } + + if ((midi_out_ports > 0) && (midi_in_ports > 0)) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/dice/dice-mytek.c b/sound/firewire/dice/dice-mytek.c new file mode 100644 index 000000000..eb7d5492d --- /dev/null +++ b/sound/firewire/dice/dice-mytek.c @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dice-mytek.c - a part of driver for DICE based devices + * + * Copyright (c) 2018 Melvin Vermeeren + */ + +#include "dice.h" + +struct dice_mytek_spec { + unsigned int tx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT]; + unsigned int rx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT]; +}; + +static const struct dice_mytek_spec stereo_192_dsd_dac = { + /* AES, TOSLINK, SPDIF, ADAT inputs on device */ + .tx_pcm_chs = {{8, 8, 8}, {0, 0, 0} }, + /* PCM 44.1-192, native DSD64/DSD128 to device */ + .rx_pcm_chs = {{4, 4, 4}, {0, 0, 0} } +}; + +/* + * Mytek has a few other firewire-capable devices, though newer models appear + * to lack the port more often than not. As I don't have access to any of them + * they are missing here. An example is the Mytek 8x192 ADDA, which is DICE. + */ + +int snd_dice_detect_mytek_formats(struct snd_dice *dice) +{ + int i; + const struct dice_mytek_spec *dev; + + dev = &stereo_192_dsd_dac; + + memcpy(dice->tx_pcm_chs, dev->tx_pcm_chs, + MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * sizeof(unsigned int)); + memcpy(dice->rx_pcm_chs, dev->rx_pcm_chs, + MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * sizeof(unsigned int)); + + for (i = 0; i < MAX_STREAMS; ++i) { + dice->tx_midi_ports[i] = 0; + dice->rx_midi_ports[i] = 0; + } + + return 0; +} diff --git a/sound/firewire/dice/dice-pcm.c b/sound/firewire/dice/dice-pcm.c new file mode 100644 index 000000000..af8a90ee4 --- /dev/null +++ b/sound/firewire/dice/dice-pcm.c @@ -0,0 +1,459 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * dice_pcm.c - a part of driver for DICE based devices + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * Copyright (c) 2014 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#include "dice.h" + +static int dice_rate_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_pcm_substream *substream = rule->private; + struct snd_dice *dice = substream->private_data; + unsigned int index = substream->pcm->device; + + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval rates = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int *pcm_channels; + enum snd_dice_rate_mode mode; + unsigned int i, rate; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + pcm_channels = dice->tx_pcm_chs[index]; + else + pcm_channels = dice->rx_pcm_chs[index]; + + for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i) { + rate = snd_dice_rates[i]; + if (snd_dice_stream_get_rate_mode(dice, rate, &mode) < 0) + continue; + + if (!snd_interval_test(c, pcm_channels[mode])) + continue; + + rates.min = min(rates.min, rate); + rates.max = max(rates.max, rate); + } + + return snd_interval_refine(r, &rates); +} + +static int dice_channels_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_pcm_substream *substream = rule->private; + struct snd_dice *dice = substream->private_data; + unsigned int index = substream->pcm->device; + + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval channels = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int *pcm_channels; + enum snd_dice_rate_mode mode; + unsigned int i, rate; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + pcm_channels = dice->tx_pcm_chs[index]; + else + pcm_channels = dice->rx_pcm_chs[index]; + + for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i) { + rate = snd_dice_rates[i]; + if (snd_dice_stream_get_rate_mode(dice, rate, &mode) < 0) + continue; + + if (!snd_interval_test(r, rate)) + continue; + + channels.min = min(channels.min, pcm_channels[mode]); + channels.max = max(channels.max, pcm_channels[mode]); + } + + return snd_interval_refine(c, &channels); +} + +static int limit_channels_and_rates(struct snd_dice *dice, + struct snd_pcm_runtime *runtime, + enum amdtp_stream_direction dir, + unsigned int index) +{ + struct snd_pcm_hardware *hw = &runtime->hw; + unsigned int *pcm_channels; + unsigned int i; + + if (dir == AMDTP_IN_STREAM) + pcm_channels = dice->tx_pcm_chs[index]; + else + pcm_channels = dice->rx_pcm_chs[index]; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i) { + enum snd_dice_rate_mode mode; + unsigned int rate, channels; + + rate = snd_dice_rates[i]; + if (snd_dice_stream_get_rate_mode(dice, rate, &mode) < 0) + continue; + hw->rates |= snd_pcm_rate_to_rate_bit(rate); + + channels = pcm_channels[mode]; + if (channels == 0) + continue; + hw->channels_min = min(hw->channels_min, channels); + hw->channels_max = max(hw->channels_max, channels); + } + + snd_pcm_limit_hw_rates(runtime); + + return 0; +} + +static int init_hw_info(struct snd_dice *dice, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hardware *hw = &runtime->hw; + unsigned int index = substream->pcm->device; + enum amdtp_stream_direction dir; + struct amdtp_stream *stream; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + hw->formats = AM824_IN_PCM_FORMAT_BITS; + dir = AMDTP_IN_STREAM; + stream = &dice->tx_stream[index]; + } else { + hw->formats = AM824_OUT_PCM_FORMAT_BITS; + dir = AMDTP_OUT_STREAM; + stream = &dice->rx_stream[index]; + } + + err = limit_channels_and_rates(dice, substream->runtime, dir, + index); + if (err < 0) + return err; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + dice_rate_constraint, substream, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + dice_channels_constraint, substream, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + + return amdtp_am824_add_pcm_hw_constraints(stream, runtime); +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + struct amdtp_domain *d = &dice->domain; + unsigned int source; + bool internal; + int err; + + err = snd_dice_stream_lock_try(dice); + if (err < 0) + return err; + + err = init_hw_info(dice, substream); + if (err < 0) + goto err_locked; + + err = snd_dice_transaction_get_clock_source(dice, &source); + if (err < 0) + goto err_locked; + switch (source) { + case CLOCK_SOURCE_AES1: + case CLOCK_SOURCE_AES2: + case CLOCK_SOURCE_AES3: + case CLOCK_SOURCE_AES4: + case CLOCK_SOURCE_AES_ANY: + case CLOCK_SOURCE_ADAT: + case CLOCK_SOURCE_TDIF: + case CLOCK_SOURCE_WC: + internal = false; + break; + default: + internal = true; + break; + } + + mutex_lock(&dice->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 (!internal || + (dice->substreams_counter > 0 && d->events_per_period > 0)) { + unsigned int frames_per_period = d->events_per_period; + unsigned int frames_per_buffer = d->events_per_buffer; + unsigned int rate; + + err = snd_dice_transaction_get_rate(dice, &rate); + if (err < 0) { + mutex_unlock(&dice->mutex); + goto err_locked; + } + + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + + if (frames_per_period > 0) { + // For double_pcm_frame quirk. + if (rate > 96000) { + frames_per_period *= 2; + frames_per_buffer *= 2; + } + + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + frames_per_period, frames_per_period); + if (err < 0) { + mutex_unlock(&dice->mutex); + goto err_locked; + } + + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + frames_per_buffer, frames_per_buffer); + if (err < 0) { + mutex_unlock(&dice->mutex); + goto err_locked; + } + } + } + + mutex_unlock(&dice->mutex); + + snd_pcm_set_sync(substream); + + return 0; +err_locked: + snd_dice_stream_lock_release(dice); + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + + snd_dice_stream_lock_release(dice); + + return 0; +} + +static int pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_dice *dice = substream->private_data; + int err = 0; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + unsigned int rate = params_rate(hw_params); + unsigned int events_per_period = params_period_size(hw_params); + unsigned int events_per_buffer = params_buffer_size(hw_params); + + mutex_lock(&dice->mutex); + // For double_pcm_frame quirk. + if (rate > 96000) { + events_per_period /= 2; + events_per_buffer /= 2; + } + err = snd_dice_stream_reserve_duplex(dice, rate, + events_per_period, events_per_buffer); + if (err >= 0) + ++dice->substreams_counter; + mutex_unlock(&dice->mutex); + } + + return err; +} + +static int pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + + mutex_lock(&dice->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + --dice->substreams_counter; + + snd_dice_stream_stop_duplex(dice); + + mutex_unlock(&dice->mutex); + + return 0; +} + +static int capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + struct amdtp_stream *stream = &dice->tx_stream[substream->pcm->device]; + int err; + + mutex_lock(&dice->mutex); + err = snd_dice_stream_start_duplex(dice); + mutex_unlock(&dice->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(stream); + + return 0; +} +static int playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + struct amdtp_stream *stream = &dice->rx_stream[substream->pcm->device]; + int err; + + mutex_lock(&dice->mutex); + err = snd_dice_stream_start_duplex(dice); + mutex_unlock(&dice->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(stream); + + return err; +} + +static int capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_dice *dice = substream->private_data; + struct amdtp_stream *stream = &dice->tx_stream[substream->pcm->device]; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_dice *dice = substream->private_data; + struct amdtp_stream *stream = &dice->rx_stream[substream->pcm->device]; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + struct amdtp_stream *stream = &dice->tx_stream[substream->pcm->device]; + + return amdtp_domain_stream_pcm_pointer(&dice->domain, stream); +} +static snd_pcm_uframes_t playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + struct amdtp_stream *stream = &dice->rx_stream[substream->pcm->device]; + + return amdtp_domain_stream_pcm_pointer(&dice->domain, stream); +} + +static int capture_ack(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + struct amdtp_stream *stream = &dice->tx_stream[substream->pcm->device]; + + return amdtp_domain_stream_pcm_ack(&dice->domain, stream); +} + +static int playback_ack(struct snd_pcm_substream *substream) +{ + struct snd_dice *dice = substream->private_data; + struct amdtp_stream *stream = &dice->rx_stream[substream->pcm->device]; + + return amdtp_domain_stream_pcm_ack(&dice->domain, stream); +} + +int snd_dice_create_pcm(struct snd_dice *dice) +{ + static const struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .hw_params = pcm_hw_params, + .hw_free = pcm_hw_free, + .prepare = capture_prepare, + .trigger = capture_trigger, + .pointer = capture_pointer, + .ack = capture_ack, + }; + static const struct snd_pcm_ops playback_ops = { + .open = pcm_open, + .close = pcm_close, + .hw_params = pcm_hw_params, + .hw_free = pcm_hw_free, + .prepare = playback_prepare, + .trigger = playback_trigger, + .pointer = playback_pointer, + .ack = playback_ack, + }; + struct snd_pcm *pcm; + unsigned int capture, playback; + int i, j; + int err; + + for (i = 0; i < MAX_STREAMS; i++) { + capture = playback = 0; + for (j = 0; j < SND_DICE_RATE_MODE_COUNT; ++j) { + if (dice->tx_pcm_chs[i][j] > 0) + capture = 1; + if (dice->rx_pcm_chs[i][j] > 0) + playback = 1; + } + + err = snd_pcm_new(dice->card, "DICE", i, playback, capture, + &pcm); + if (err < 0) + return err; + pcm->private_data = dice; + strcpy(pcm->name, dice->card->shortname); + + if (capture > 0) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &capture_ops); + + if (playback > 0) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &playback_ops); + + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, + NULL, 0, 0); + } + + return 0; +} diff --git a/sound/firewire/dice/dice-presonus.c b/sound/firewire/dice/dice-presonus.c new file mode 100644 index 000000000..503f462a8 --- /dev/null +++ b/sound/firewire/dice/dice-presonus.c @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0 +// dice-presonus.c - a part of driver for DICE based devices +// +// Copyright (c) 2019 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#include "dice.h" + +struct dice_presonus_spec { + unsigned int tx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT]; + unsigned int rx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT]; + bool has_midi; +}; + +static const struct dice_presonus_spec dice_presonus_firesutio = { + .tx_pcm_chs = {{16, 16, 0}, {10, 2, 0} }, + .rx_pcm_chs = {{16, 16, 0}, {10, 2, 0} }, + .has_midi = true, +}; + +int snd_dice_detect_presonus_formats(struct snd_dice *dice) +{ + static const struct { + u32 model_id; + const struct dice_presonus_spec *spec; + } *entry, entries[] = { + {0x000008, &dice_presonus_firesutio}, + }; + struct fw_csr_iterator it; + int key, val, model_id; + int i; + + model_id = 0; + fw_csr_iterator_init(&it, dice->unit->directory); + while (fw_csr_iterator_next(&it, &key, &val)) { + if (key == CSR_MODEL) { + model_id = val; + break; + } + } + + for (i = 0; i < ARRAY_SIZE(entries); ++i) { + entry = entries + i; + if (entry->model_id == model_id) + break; + } + if (i == ARRAY_SIZE(entries)) + return -ENODEV; + + memcpy(dice->tx_pcm_chs, entry->spec->tx_pcm_chs, + MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * sizeof(unsigned int)); + memcpy(dice->rx_pcm_chs, entry->spec->rx_pcm_chs, + MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * sizeof(unsigned int)); + + if (entry->spec->has_midi) { + dice->tx_midi_ports[0] = 1; + dice->rx_midi_ports[0] = 1; + } + + return 0; +} diff --git a/sound/firewire/dice/dice-proc.c b/sound/firewire/dice/dice-proc.c new file mode 100644 index 000000000..db0a03123 --- /dev/null +++ b/sound/firewire/dice/dice-proc.c @@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * dice_proc.c - a part of driver for Dice based devices + * + * Copyright (c) Clemens Ladisch + * Copyright (c) 2014 Takashi Sakamoto + */ + +#include "dice.h" + +static int dice_proc_read_mem(struct snd_dice *dice, void *buffer, + unsigned int offset_q, unsigned int quadlets) +{ + unsigned int i; + int err; + + err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, + DICE_PRIVATE_SPACE + 4 * offset_q, + buffer, 4 * quadlets, 0); + if (err < 0) + return err; + + for (i = 0; i < quadlets; ++i) + be32_to_cpus(&((u32 *)buffer)[i]); + + return 0; +} + +static const char *str_from_array(const char *const strs[], unsigned int count, + unsigned int i) +{ + if (i < count) + return strs[i]; + + return "(unknown)"; +} + +static void dice_proc_fixup_string(char *s, unsigned int size) +{ + unsigned int i; + + for (i = 0; i < size; i += 4) + cpu_to_le32s((u32 *)(s + i)); + + for (i = 0; i < size - 2; ++i) { + if (s[i] == '\0') + return; + if (s[i] == '\\' && s[i + 1] == '\\') { + s[i + 2] = '\0'; + return; + } + } + s[size - 1] = '\0'; +} + +static void dice_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + static const char *const section_names[5] = { + "global", "tx", "rx", "ext_sync", "unused2" + }; + static const char *const clock_sources[] = { + "aes1", "aes2", "aes3", "aes4", "aes", "adat", "tdif", + "wc", "arx1", "arx2", "arx3", "arx4", "internal" + }; + static const char *const rates[] = { + "32000", "44100", "48000", "88200", "96000", "176400", "192000", + "any low", "any mid", "any high", "none" + }; + struct snd_dice *dice = entry->private_data; + u32 sections[ARRAY_SIZE(section_names) * 2]; + struct { + u32 number; + u32 size; + } tx_rx_header; + union { + struct { + u32 owner_hi, owner_lo; + u32 notification; + char nick_name[NICK_NAME_SIZE]; + u32 clock_select; + u32 enable; + u32 status; + u32 extended_status; + u32 sample_rate; + u32 version; + u32 clock_caps; + char clock_source_names[CLOCK_SOURCE_NAMES_SIZE]; + } global; + struct { + u32 iso; + u32 number_audio; + u32 number_midi; + u32 speed; + char names[TX_NAMES_SIZE]; + u32 ac3_caps; + u32 ac3_enable; + } tx; + struct { + u32 iso; + u32 seq_start; + u32 number_audio; + u32 number_midi; + char names[RX_NAMES_SIZE]; + u32 ac3_caps; + u32 ac3_enable; + } rx; + struct { + u32 clock_source; + u32 locked; + u32 rate; + u32 adat_user_data; + } ext_sync; + } buf; + unsigned int quadlets, stream, i; + + if (dice_proc_read_mem(dice, sections, 0, ARRAY_SIZE(sections)) < 0) + return; + snd_iprintf(buffer, "sections:\n"); + for (i = 0; i < ARRAY_SIZE(section_names); ++i) + snd_iprintf(buffer, " %s: offset %u, size %u\n", + section_names[i], + sections[i * 2], sections[i * 2 + 1]); + + quadlets = min_t(u32, sections[1], sizeof(buf.global) / 4); + if (dice_proc_read_mem(dice, &buf.global, sections[0], quadlets) < 0) + return; + snd_iprintf(buffer, "global:\n"); + snd_iprintf(buffer, " owner: %04x:%04x%08x\n", + buf.global.owner_hi >> 16, + buf.global.owner_hi & 0xffff, buf.global.owner_lo); + snd_iprintf(buffer, " notification: %08x\n", buf.global.notification); + dice_proc_fixup_string(buf.global.nick_name, NICK_NAME_SIZE); + snd_iprintf(buffer, " nick name: %s\n", buf.global.nick_name); + snd_iprintf(buffer, " clock select: %s %s\n", + str_from_array(clock_sources, ARRAY_SIZE(clock_sources), + buf.global.clock_select & CLOCK_SOURCE_MASK), + str_from_array(rates, ARRAY_SIZE(rates), + (buf.global.clock_select & CLOCK_RATE_MASK) + >> CLOCK_RATE_SHIFT)); + snd_iprintf(buffer, " enable: %u\n", buf.global.enable); + snd_iprintf(buffer, " status: %slocked %s\n", + buf.global.status & STATUS_SOURCE_LOCKED ? "" : "un", + str_from_array(rates, ARRAY_SIZE(rates), + (buf.global.status & + STATUS_NOMINAL_RATE_MASK) + >> CLOCK_RATE_SHIFT)); + snd_iprintf(buffer, " ext status: %08x\n", buf.global.extended_status); + snd_iprintf(buffer, " sample rate: %u\n", buf.global.sample_rate); + if (quadlets >= 90) { + snd_iprintf(buffer, " version: %u.%u.%u.%u\n", + (buf.global.version >> 24) & 0xff, + (buf.global.version >> 16) & 0xff, + (buf.global.version >> 8) & 0xff, + (buf.global.version >> 0) & 0xff); + snd_iprintf(buffer, " clock caps:"); + for (i = 0; i <= 6; ++i) + if (buf.global.clock_caps & (1 << i)) + snd_iprintf(buffer, " %s", rates[i]); + for (i = 0; i <= 12; ++i) + if (buf.global.clock_caps & (1 << (16 + i))) + snd_iprintf(buffer, " %s", clock_sources[i]); + snd_iprintf(buffer, "\n"); + dice_proc_fixup_string(buf.global.clock_source_names, + CLOCK_SOURCE_NAMES_SIZE); + snd_iprintf(buffer, " clock source names: %s\n", + buf.global.clock_source_names); + } + + if (dice_proc_read_mem(dice, &tx_rx_header, sections[2], 2) < 0) + return; + quadlets = min_t(u32, tx_rx_header.size, sizeof(buf.tx) / 4); + for (stream = 0; stream < tx_rx_header.number; ++stream) { + if (dice_proc_read_mem(dice, &buf.tx, sections[2] + 2 + + stream * tx_rx_header.size, + quadlets) < 0) + break; + snd_iprintf(buffer, "tx %u:\n", stream); + snd_iprintf(buffer, " iso channel: %d\n", (int)buf.tx.iso); + snd_iprintf(buffer, " audio channels: %u\n", + buf.tx.number_audio); + snd_iprintf(buffer, " midi ports: %u\n", buf.tx.number_midi); + snd_iprintf(buffer, " speed: S%u\n", 100u << buf.tx.speed); + if (quadlets >= 68) { + dice_proc_fixup_string(buf.tx.names, TX_NAMES_SIZE); + snd_iprintf(buffer, " names: %s\n", buf.tx.names); + } + if (quadlets >= 70) { + snd_iprintf(buffer, " ac3 caps: %08x\n", + buf.tx.ac3_caps); + snd_iprintf(buffer, " ac3 enable: %08x\n", + buf.tx.ac3_enable); + } + } + + if (dice_proc_read_mem(dice, &tx_rx_header, sections[4], 2) < 0) + return; + quadlets = min_t(u32, tx_rx_header.size, sizeof(buf.rx) / 4); + for (stream = 0; stream < tx_rx_header.number; ++stream) { + if (dice_proc_read_mem(dice, &buf.rx, sections[4] + 2 + + stream * tx_rx_header.size, + quadlets) < 0) + break; + snd_iprintf(buffer, "rx %u:\n", stream); + snd_iprintf(buffer, " iso channel: %d\n", (int)buf.rx.iso); + snd_iprintf(buffer, " sequence start: %u\n", buf.rx.seq_start); + snd_iprintf(buffer, " audio channels: %u\n", + buf.rx.number_audio); + snd_iprintf(buffer, " midi ports: %u\n", buf.rx.number_midi); + if (quadlets >= 68) { + dice_proc_fixup_string(buf.rx.names, RX_NAMES_SIZE); + snd_iprintf(buffer, " names: %s\n", buf.rx.names); + } + if (quadlets >= 70) { + snd_iprintf(buffer, " ac3 caps: %08x\n", + buf.rx.ac3_caps); + snd_iprintf(buffer, " ac3 enable: %08x\n", + buf.rx.ac3_enable); + } + } + + quadlets = min_t(u32, sections[7], sizeof(buf.ext_sync) / 4); + if (quadlets >= 4) { + if (dice_proc_read_mem(dice, &buf.ext_sync, + sections[6], 4) < 0) + return; + snd_iprintf(buffer, "ext status:\n"); + snd_iprintf(buffer, " clock source: %s\n", + str_from_array(clock_sources, + ARRAY_SIZE(clock_sources), + buf.ext_sync.clock_source)); + snd_iprintf(buffer, " locked: %u\n", buf.ext_sync.locked); + snd_iprintf(buffer, " rate: %s\n", + str_from_array(rates, ARRAY_SIZE(rates), + buf.ext_sync.rate)); + snd_iprintf(buffer, " adat user data: "); + if (buf.ext_sync.adat_user_data & ADAT_USER_DATA_NO_DATA) + snd_iprintf(buffer, "-\n"); + else + snd_iprintf(buffer, "%x\n", + buf.ext_sync.adat_user_data); + } +} + +static void dice_proc_read_formation(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + static const char *const rate_labels[] = { + [SND_DICE_RATE_MODE_LOW] = "low", + [SND_DICE_RATE_MODE_MIDDLE] = "middle", + [SND_DICE_RATE_MODE_HIGH] = "high", + }; + struct snd_dice *dice = entry->private_data; + int i, j; + + snd_iprintf(buffer, "Output stream from unit:\n"); + for (i = 0; i < SND_DICE_RATE_MODE_COUNT; ++i) + snd_iprintf(buffer, "\t%s", rate_labels[i]); + snd_iprintf(buffer, "\tMIDI\n"); + for (i = 0; i < MAX_STREAMS; ++i) { + snd_iprintf(buffer, "Tx %u:", i); + for (j = 0; j < SND_DICE_RATE_MODE_COUNT; ++j) + snd_iprintf(buffer, "\t%u", dice->tx_pcm_chs[i][j]); + snd_iprintf(buffer, "\t%u\n", dice->tx_midi_ports[i]); + } + + snd_iprintf(buffer, "Input stream to unit:\n"); + for (i = 0; i < SND_DICE_RATE_MODE_COUNT; ++i) + snd_iprintf(buffer, "\t%s", rate_labels[i]); + snd_iprintf(buffer, "\n"); + for (i = 0; i < MAX_STREAMS; ++i) { + snd_iprintf(buffer, "Rx %u:", i); + for (j = 0; j < SND_DICE_RATE_MODE_COUNT; ++j) + snd_iprintf(buffer, "\t%u", dice->rx_pcm_chs[i][j]); + snd_iprintf(buffer, "\t%u\n", dice->rx_midi_ports[i]); + } +} + +static void add_node(struct snd_dice *dice, struct snd_info_entry *root, + const char *name, + void (*op)(struct snd_info_entry *entry, + struct snd_info_buffer *buffer)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(dice->card, name, root); + if (entry) + snd_info_set_text_ops(entry, dice, op); +} + +void snd_dice_create_proc(struct snd_dice *dice) +{ + 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(dice->card, "firewire", + dice->card->proc_root); + if (!root) + return; + root->mode = S_IFDIR | 0555; + + add_node(dice, root, "dice", dice_proc_read); + add_node(dice, root, "formation", dice_proc_read_formation); +} diff --git a/sound/firewire/dice/dice-stream.c b/sound/firewire/dice/dice-stream.c new file mode 100644 index 000000000..1a14c083e --- /dev/null +++ b/sound/firewire/dice/dice-stream.c @@ -0,0 +1,724 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * dice_stream.c - a part of driver for DICE based devices + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * Copyright (c) 2014 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#include "dice.h" + +#define CALLBACK_TIMEOUT 200 +#define NOTIFICATION_TIMEOUT_MS (2 * MSEC_PER_SEC) + +struct reg_params { + unsigned int count; + unsigned int size; +}; + +const unsigned int snd_dice_rates[SND_DICE_RATES_COUNT] = { + /* mode 0 */ + [0] = 32000, + [1] = 44100, + [2] = 48000, + /* mode 1 */ + [3] = 88200, + [4] = 96000, + /* mode 2 */ + [5] = 176400, + [6] = 192000, +}; + +int snd_dice_stream_get_rate_mode(struct snd_dice *dice, unsigned int rate, + enum snd_dice_rate_mode *mode) +{ + /* Corresponding to each entry in snd_dice_rates. */ + static const enum snd_dice_rate_mode modes[] = { + [0] = SND_DICE_RATE_MODE_LOW, + [1] = SND_DICE_RATE_MODE_LOW, + [2] = SND_DICE_RATE_MODE_LOW, + [3] = SND_DICE_RATE_MODE_MIDDLE, + [4] = SND_DICE_RATE_MODE_MIDDLE, + [5] = SND_DICE_RATE_MODE_HIGH, + [6] = SND_DICE_RATE_MODE_HIGH, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(snd_dice_rates); i++) { + if (!(dice->clock_caps & BIT(i))) + continue; + if (snd_dice_rates[i] != rate) + continue; + + *mode = modes[i]; + return 0; + } + + return -EINVAL; +} + +/* + * This operation has an effect to synchronize GLOBAL_STATUS/GLOBAL_SAMPLE_RATE + * to GLOBAL_STATUS. Especially, just after powering on, these are different. + */ +static int ensure_phase_lock(struct snd_dice *dice, unsigned int rate) +{ + __be32 reg, nominal; + u32 data; + int i; + int err; + + err = snd_dice_transaction_read_global(dice, GLOBAL_CLOCK_SELECT, + ®, sizeof(reg)); + if (err < 0) + return err; + + data = be32_to_cpu(reg); + + data &= ~CLOCK_RATE_MASK; + for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i) { + if (snd_dice_rates[i] == rate) + break; + } + if (i == ARRAY_SIZE(snd_dice_rates)) + return -EINVAL; + data |= i << CLOCK_RATE_SHIFT; + + if (completion_done(&dice->clock_accepted)) + reinit_completion(&dice->clock_accepted); + + reg = cpu_to_be32(data); + err = snd_dice_transaction_write_global(dice, GLOBAL_CLOCK_SELECT, + ®, sizeof(reg)); + if (err < 0) + return err; + + if (wait_for_completion_timeout(&dice->clock_accepted, + msecs_to_jiffies(NOTIFICATION_TIMEOUT_MS)) == 0) { + /* + * Old versions of Dice firmware transfer no notification when + * the same clock status as current one is set. In this case, + * just check current clock status. + */ + err = snd_dice_transaction_read_global(dice, GLOBAL_STATUS, + &nominal, sizeof(nominal)); + if (err < 0) + return err; + if (!(be32_to_cpu(nominal) & STATUS_SOURCE_LOCKED)) + return -ETIMEDOUT; + } + + return 0; +} + +static int get_register_params(struct snd_dice *dice, + struct reg_params *tx_params, + struct reg_params *rx_params) +{ + __be32 reg[2]; + int err; + + err = snd_dice_transaction_read_tx(dice, TX_NUMBER, reg, sizeof(reg)); + if (err < 0) + return err; + tx_params->count = + min_t(unsigned int, be32_to_cpu(reg[0]), MAX_STREAMS); + tx_params->size = be32_to_cpu(reg[1]) * 4; + + err = snd_dice_transaction_read_rx(dice, RX_NUMBER, reg, sizeof(reg)); + if (err < 0) + return err; + rx_params->count = + min_t(unsigned int, be32_to_cpu(reg[0]), MAX_STREAMS); + rx_params->size = be32_to_cpu(reg[1]) * 4; + + return 0; +} + +static void release_resources(struct snd_dice *dice) +{ + int i; + + for (i = 0; i < MAX_STREAMS; ++i) { + fw_iso_resources_free(&dice->tx_resources[i]); + fw_iso_resources_free(&dice->rx_resources[i]); + } +} + +static void stop_streams(struct snd_dice *dice, enum amdtp_stream_direction dir, + struct reg_params *params) +{ + __be32 reg; + unsigned int i; + + for (i = 0; i < params->count; i++) { + reg = cpu_to_be32((u32)-1); + if (dir == AMDTP_IN_STREAM) { + snd_dice_transaction_write_tx(dice, + params->size * i + TX_ISOCHRONOUS, + ®, sizeof(reg)); + } else { + snd_dice_transaction_write_rx(dice, + params->size * i + RX_ISOCHRONOUS, + ®, sizeof(reg)); + } + } +} + +static int keep_resources(struct snd_dice *dice, struct amdtp_stream *stream, + struct fw_iso_resources *resources, unsigned int rate, + unsigned int pcm_chs, unsigned int midi_ports) +{ + bool double_pcm_frames; + unsigned int i; + int err; + + // At 176.4/192.0 kHz, Dice has a quirk to transfer two PCM frames in + // one data block of AMDTP packet. Thus sampling transfer frequency is + // a half of PCM sampling frequency, i.e. PCM frames at 192.0 kHz are + // transferred on AMDTP packets at 96 kHz. Two successive samples of a + // channel are stored consecutively in the packet. This quirk is called + // as 'Dual Wire'. + // For this quirk, blocking mode is required and PCM buffer size should + // be aligned to SYT_INTERVAL. + double_pcm_frames = rate > 96000; + if (double_pcm_frames) { + rate /= 2; + pcm_chs *= 2; + } + + err = amdtp_am824_set_parameters(stream, rate, pcm_chs, midi_ports, + double_pcm_frames); + if (err < 0) + return err; + + if (double_pcm_frames) { + pcm_chs /= 2; + + for (i = 0; i < pcm_chs; i++) { + amdtp_am824_set_pcm_position(stream, i, i * 2); + amdtp_am824_set_pcm_position(stream, i + pcm_chs, + i * 2 + 1); + } + } + + return fw_iso_resources_allocate(resources, + amdtp_stream_get_max_payload(stream), + fw_parent_device(dice->unit)->max_speed); +} + +static int keep_dual_resources(struct snd_dice *dice, unsigned int rate, + enum amdtp_stream_direction dir, + struct reg_params *params) +{ + enum snd_dice_rate_mode mode; + int i; + int err; + + err = snd_dice_stream_get_rate_mode(dice, rate, &mode); + if (err < 0) + return err; + + for (i = 0; i < params->count; ++i) { + __be32 reg[2]; + struct amdtp_stream *stream; + struct fw_iso_resources *resources; + unsigned int pcm_cache; + unsigned int pcm_chs; + unsigned int midi_ports; + + if (dir == AMDTP_IN_STREAM) { + stream = &dice->tx_stream[i]; + resources = &dice->tx_resources[i]; + + pcm_cache = dice->tx_pcm_chs[i][mode]; + err = snd_dice_transaction_read_tx(dice, + params->size * i + TX_NUMBER_AUDIO, + reg, sizeof(reg)); + } else { + stream = &dice->rx_stream[i]; + resources = &dice->rx_resources[i]; + + pcm_cache = dice->rx_pcm_chs[i][mode]; + err = snd_dice_transaction_read_rx(dice, + params->size * i + RX_NUMBER_AUDIO, + reg, sizeof(reg)); + } + if (err < 0) + return err; + pcm_chs = be32_to_cpu(reg[0]); + midi_ports = be32_to_cpu(reg[1]); + + // These are important for developer of this driver. + if (pcm_chs != pcm_cache) { + dev_info(&dice->unit->device, + "cache mismatch: pcm: %u:%u, midi: %u\n", + pcm_chs, pcm_cache, midi_ports); + return -EPROTO; + } + + err = keep_resources(dice, stream, resources, rate, pcm_chs, + midi_ports); + if (err < 0) + return err; + } + + return 0; +} + +static void finish_session(struct snd_dice *dice, struct reg_params *tx_params, + struct reg_params *rx_params) +{ + stop_streams(dice, AMDTP_IN_STREAM, tx_params); + stop_streams(dice, AMDTP_OUT_STREAM, rx_params); + + snd_dice_transaction_clear_enable(dice); +} + +int snd_dice_stream_reserve_duplex(struct snd_dice *dice, unsigned int rate, + unsigned int events_per_period, + unsigned int events_per_buffer) +{ + unsigned int curr_rate; + int err; + + // Check sampling transmission frequency. + err = snd_dice_transaction_get_rate(dice, &curr_rate); + if (err < 0) + return err; + if (rate == 0) + rate = curr_rate; + + if (dice->substreams_counter == 0 || curr_rate != rate) { + struct reg_params tx_params, rx_params; + + amdtp_domain_stop(&dice->domain); + + err = get_register_params(dice, &tx_params, &rx_params); + if (err < 0) + return err; + finish_session(dice, &tx_params, &rx_params); + + release_resources(dice); + + // Just after owning the unit (GLOBAL_OWNER), the unit can + // return invalid stream formats. Selecting clock parameters + // have an effect for the unit to refine it. + err = ensure_phase_lock(dice, rate); + if (err < 0) + return err; + + // After changing sampling transfer frequency, the value of + // register can be changed. + err = get_register_params(dice, &tx_params, &rx_params); + if (err < 0) + return err; + + err = keep_dual_resources(dice, rate, AMDTP_IN_STREAM, + &tx_params); + if (err < 0) + goto error; + + err = keep_dual_resources(dice, rate, AMDTP_OUT_STREAM, + &rx_params); + if (err < 0) + goto error; + + err = amdtp_domain_set_events_per_period(&dice->domain, + events_per_period, events_per_buffer); + if (err < 0) + goto error; + } + + return 0; +error: + release_resources(dice); + return err; +} + +static int start_streams(struct snd_dice *dice, enum amdtp_stream_direction dir, + unsigned int rate, struct reg_params *params) +{ + unsigned int max_speed = fw_parent_device(dice->unit)->max_speed; + int i; + int err; + + for (i = 0; i < params->count; i++) { + struct amdtp_stream *stream; + struct fw_iso_resources *resources; + __be32 reg; + + if (dir == AMDTP_IN_STREAM) { + stream = dice->tx_stream + i; + resources = dice->tx_resources + i; + } else { + stream = dice->rx_stream + i; + resources = dice->rx_resources + i; + } + + reg = cpu_to_be32(resources->channel); + if (dir == AMDTP_IN_STREAM) { + err = snd_dice_transaction_write_tx(dice, + params->size * i + TX_ISOCHRONOUS, + ®, sizeof(reg)); + } else { + err = snd_dice_transaction_write_rx(dice, + params->size * i + RX_ISOCHRONOUS, + ®, sizeof(reg)); + } + if (err < 0) + return err; + + if (dir == AMDTP_IN_STREAM) { + reg = cpu_to_be32(max_speed); + err = snd_dice_transaction_write_tx(dice, + params->size * i + TX_SPEED, + ®, sizeof(reg)); + if (err < 0) + return err; + } + + err = amdtp_domain_add_stream(&dice->domain, stream, + resources->channel, max_speed); + if (err < 0) + return err; + } + + return 0; +} + +/* + * MEMO: After this function, there're two states of streams: + * - None streams are running. + * - All streams are running. + */ +int snd_dice_stream_start_duplex(struct snd_dice *dice) +{ + unsigned int generation = dice->rx_resources[0].generation; + struct reg_params tx_params, rx_params; + unsigned int i; + unsigned int rate; + enum snd_dice_rate_mode mode; + int err; + + if (dice->substreams_counter == 0) + return -EIO; + + err = get_register_params(dice, &tx_params, &rx_params); + if (err < 0) + return err; + + // Check error of packet streaming. + for (i = 0; i < MAX_STREAMS; ++i) { + if (amdtp_streaming_error(&dice->tx_stream[i]) || + amdtp_streaming_error(&dice->rx_stream[i])) { + amdtp_domain_stop(&dice->domain); + finish_session(dice, &tx_params, &rx_params); + break; + } + } + + if (generation != fw_parent_device(dice->unit)->card->generation) { + for (i = 0; i < MAX_STREAMS; ++i) { + if (i < tx_params.count) + fw_iso_resources_update(dice->tx_resources + i); + if (i < rx_params.count) + fw_iso_resources_update(dice->rx_resources + i); + } + } + + // Check required streams are running or not. + err = snd_dice_transaction_get_rate(dice, &rate); + if (err < 0) + return err; + err = snd_dice_stream_get_rate_mode(dice, rate, &mode); + if (err < 0) + return err; + for (i = 0; i < MAX_STREAMS; ++i) { + if (dice->tx_pcm_chs[i][mode] > 0 && + !amdtp_stream_running(&dice->tx_stream[i])) + break; + if (dice->rx_pcm_chs[i][mode] > 0 && + !amdtp_stream_running(&dice->rx_stream[i])) + break; + } + if (i < MAX_STREAMS) { + // Start both streams. + err = start_streams(dice, AMDTP_IN_STREAM, rate, &tx_params); + if (err < 0) + goto error; + + err = start_streams(dice, AMDTP_OUT_STREAM, rate, &rx_params); + if (err < 0) + goto error; + + err = snd_dice_transaction_set_enable(dice); + if (err < 0) { + dev_err(&dice->unit->device, + "fail to enable interface\n"); + goto error; + } + + err = amdtp_domain_start(&dice->domain, 0); + if (err < 0) + goto error; + + for (i = 0; i < MAX_STREAMS; i++) { + if ((i < tx_params.count && + !amdtp_stream_wait_callback(&dice->tx_stream[i], + CALLBACK_TIMEOUT)) || + (i < rx_params.count && + !amdtp_stream_wait_callback(&dice->rx_stream[i], + CALLBACK_TIMEOUT))) { + err = -ETIMEDOUT; + goto error; + } + } + } + + return 0; +error: + amdtp_domain_stop(&dice->domain); + finish_session(dice, &tx_params, &rx_params); + return err; +} + +/* + * MEMO: After this function, there're two states of streams: + * - None streams are running. + * - All streams are running. + */ +void snd_dice_stream_stop_duplex(struct snd_dice *dice) +{ + struct reg_params tx_params, rx_params; + + if (dice->substreams_counter == 0) { + if (get_register_params(dice, &tx_params, &rx_params) >= 0) + finish_session(dice, &tx_params, &rx_params); + + amdtp_domain_stop(&dice->domain); + release_resources(dice); + } +} + +static int init_stream(struct snd_dice *dice, enum amdtp_stream_direction dir, + unsigned int index) +{ + struct amdtp_stream *stream; + struct fw_iso_resources *resources; + int err; + + if (dir == AMDTP_IN_STREAM) { + stream = &dice->tx_stream[index]; + resources = &dice->tx_resources[index]; + } else { + stream = &dice->rx_stream[index]; + resources = &dice->rx_resources[index]; + } + + err = fw_iso_resources_init(resources, dice->unit); + if (err < 0) + goto end; + resources->channels_mask = 0x00000000ffffffffuLL; + + err = amdtp_am824_init(stream, dice->unit, dir, CIP_BLOCKING); + if (err < 0) { + amdtp_stream_destroy(stream); + fw_iso_resources_destroy(resources); + } +end: + return err; +} + +/* + * This function should be called before starting streams or after stopping + * streams. + */ +static void destroy_stream(struct snd_dice *dice, + enum amdtp_stream_direction dir, + unsigned int index) +{ + struct amdtp_stream *stream; + struct fw_iso_resources *resources; + + if (dir == AMDTP_IN_STREAM) { + stream = &dice->tx_stream[index]; + resources = &dice->tx_resources[index]; + } else { + stream = &dice->rx_stream[index]; + resources = &dice->rx_resources[index]; + } + + amdtp_stream_destroy(stream); + fw_iso_resources_destroy(resources); +} + +int snd_dice_stream_init_duplex(struct snd_dice *dice) +{ + int i, err; + + for (i = 0; i < MAX_STREAMS; i++) { + err = init_stream(dice, AMDTP_IN_STREAM, i); + if (err < 0) { + for (; i >= 0; i--) + destroy_stream(dice, AMDTP_IN_STREAM, i); + goto end; + } + } + + for (i = 0; i < MAX_STREAMS; i++) { + err = init_stream(dice, AMDTP_OUT_STREAM, i); + if (err < 0) { + for (; i >= 0; i--) + destroy_stream(dice, AMDTP_OUT_STREAM, i); + for (i = 0; i < MAX_STREAMS; i++) + destroy_stream(dice, AMDTP_IN_STREAM, i); + goto end; + } + } + + err = amdtp_domain_init(&dice->domain); + if (err < 0) { + for (i = 0; i < MAX_STREAMS; ++i) { + destroy_stream(dice, AMDTP_OUT_STREAM, i); + destroy_stream(dice, AMDTP_IN_STREAM, i); + } + } +end: + return err; +} + +void snd_dice_stream_destroy_duplex(struct snd_dice *dice) +{ + unsigned int i; + + for (i = 0; i < MAX_STREAMS; i++) { + destroy_stream(dice, AMDTP_IN_STREAM, i); + destroy_stream(dice, AMDTP_OUT_STREAM, i); + } + + amdtp_domain_destroy(&dice->domain); +} + +void snd_dice_stream_update_duplex(struct snd_dice *dice) +{ + struct reg_params tx_params, rx_params; + + /* + * On a bus reset, the DICE firmware disables streaming and then goes + * off contemplating its own navel for hundreds of milliseconds before + * it can react to any of our attempts to reenable streaming. This + * means that we lose synchronization anyway, so we force our streams + * to stop so that the application can restart them in an orderly + * manner. + */ + dice->global_enabled = false; + + if (get_register_params(dice, &tx_params, &rx_params) == 0) { + amdtp_domain_stop(&dice->domain); + + stop_streams(dice, AMDTP_IN_STREAM, &tx_params); + stop_streams(dice, AMDTP_OUT_STREAM, &rx_params); + } +} + +int snd_dice_stream_detect_current_formats(struct snd_dice *dice) +{ + unsigned int rate; + enum snd_dice_rate_mode mode; + __be32 reg[2]; + struct reg_params tx_params, rx_params; + int i; + int err; + + /* If extended protocol is available, detect detail spec. */ + err = snd_dice_detect_extension_formats(dice); + if (err >= 0) + return err; + + /* + * Available stream format is restricted at current mode of sampling + * clock. + */ + err = snd_dice_transaction_get_rate(dice, &rate); + if (err < 0) + return err; + + err = snd_dice_stream_get_rate_mode(dice, rate, &mode); + if (err < 0) + return err; + + /* + * Just after owning the unit (GLOBAL_OWNER), the unit can return + * invalid stream formats. Selecting clock parameters have an effect + * for the unit to refine it. + */ + err = ensure_phase_lock(dice, rate); + if (err < 0) + return err; + + err = get_register_params(dice, &tx_params, &rx_params); + if (err < 0) + return err; + + for (i = 0; i < tx_params.count; ++i) { + err = snd_dice_transaction_read_tx(dice, + tx_params.size * i + TX_NUMBER_AUDIO, + reg, sizeof(reg)); + if (err < 0) + return err; + dice->tx_pcm_chs[i][mode] = be32_to_cpu(reg[0]); + dice->tx_midi_ports[i] = max_t(unsigned int, + be32_to_cpu(reg[1]), dice->tx_midi_ports[i]); + } + for (i = 0; i < rx_params.count; ++i) { + err = snd_dice_transaction_read_rx(dice, + rx_params.size * i + RX_NUMBER_AUDIO, + reg, sizeof(reg)); + if (err < 0) + return err; + dice->rx_pcm_chs[i][mode] = be32_to_cpu(reg[0]); + dice->rx_midi_ports[i] = max_t(unsigned int, + be32_to_cpu(reg[1]), dice->rx_midi_ports[i]); + } + + return 0; +} + +static void dice_lock_changed(struct snd_dice *dice) +{ + dice->dev_lock_changed = true; + wake_up(&dice->hwdep_wait); +} + +int snd_dice_stream_lock_try(struct snd_dice *dice) +{ + int err; + + spin_lock_irq(&dice->lock); + + if (dice->dev_lock_count < 0) { + err = -EBUSY; + goto out; + } + + if (dice->dev_lock_count++ == 0) + dice_lock_changed(dice); + err = 0; +out: + spin_unlock_irq(&dice->lock); + return err; +} + +void snd_dice_stream_lock_release(struct snd_dice *dice) +{ + spin_lock_irq(&dice->lock); + + if (WARN_ON(dice->dev_lock_count <= 0)) + goto out; + + if (--dice->dev_lock_count == 0) + dice_lock_changed(dice); +out: + spin_unlock_irq(&dice->lock); +} diff --git a/sound/firewire/dice/dice-tcelectronic.c b/sound/firewire/dice/dice-tcelectronic.c new file mode 100644 index 000000000..43a3bcb15 --- /dev/null +++ b/sound/firewire/dice/dice-tcelectronic.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * dice-tc_electronic.c - a part of driver for DICE based devices + * + * Copyright (c) 2018 Takashi Sakamoto + */ + +#include "dice.h" + +struct dice_tc_spec { + unsigned int tx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT]; + unsigned int rx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT]; + bool has_midi; +}; + +static const struct dice_tc_spec desktop_konnekt6 = { + .tx_pcm_chs = {{6, 6, 2}, {0, 0, 0} }, + .rx_pcm_chs = {{6, 6, 4}, {0, 0, 0} }, + .has_midi = false, +}; + +static const struct dice_tc_spec impact_twin = { + .tx_pcm_chs = {{14, 10, 6}, {0, 0, 0} }, + .rx_pcm_chs = {{14, 10, 6}, {0, 0, 0} }, + .has_midi = true, +}; + +static const struct dice_tc_spec konnekt_8 = { + .tx_pcm_chs = {{4, 4, 3}, {0, 0, 0} }, + .rx_pcm_chs = {{4, 4, 3}, {0, 0, 0} }, + .has_midi = true, +}; + +static const struct dice_tc_spec konnekt_24d = { + .tx_pcm_chs = {{16, 16, 6}, {0, 0, 0} }, + .rx_pcm_chs = {{16, 16, 6}, {0, 0, 0} }, + .has_midi = true, +}; + +static const struct dice_tc_spec konnekt_live = { + .tx_pcm_chs = {{16, 16, 6}, {0, 0, 0} }, + .rx_pcm_chs = {{16, 16, 6}, {0, 0, 0} }, + .has_midi = true, +}; + +static const struct dice_tc_spec studio_konnekt_48 = { + .tx_pcm_chs = {{16, 16, 8}, {16, 16, 7} }, + .rx_pcm_chs = {{16, 16, 8}, {14, 14, 7} }, + .has_midi = true, +}; + +static const struct dice_tc_spec digital_konnekt_x32 = { + .tx_pcm_chs = {{16, 16, 4}, {0, 0, 0} }, + .rx_pcm_chs = {{16, 16, 4}, {0, 0, 0} }, + .has_midi = false, +}; + +int snd_dice_detect_tcelectronic_formats(struct snd_dice *dice) +{ + static const struct { + u32 model_id; + const struct dice_tc_spec *spec; + } *entry, entries[] = { + {0x00000020, &konnekt_24d}, + {0x00000021, &konnekt_8}, + {0x00000022, &studio_konnekt_48}, + {0x00000023, &konnekt_live}, + {0x00000024, &desktop_konnekt6}, + {0x00000027, &impact_twin}, + {0x00000030, &digital_konnekt_x32}, + }; + struct fw_csr_iterator it; + int key, val, model_id; + int i; + + model_id = 0; + fw_csr_iterator_init(&it, dice->unit->directory); + while (fw_csr_iterator_next(&it, &key, &val)) { + if (key == CSR_MODEL) { + model_id = val; + break; + } + } + + for (i = 0; i < ARRAY_SIZE(entries); ++i) { + entry = entries + i; + if (entry->model_id == model_id) + break; + } + if (i == ARRAY_SIZE(entries)) + return -ENODEV; + + memcpy(dice->tx_pcm_chs, entry->spec->tx_pcm_chs, + MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * sizeof(unsigned int)); + memcpy(dice->rx_pcm_chs, entry->spec->rx_pcm_chs, + MAX_STREAMS * SND_DICE_RATE_MODE_COUNT * sizeof(unsigned int)); + + if (entry->spec->has_midi) { + dice->tx_midi_ports[0] = 1; + dice->rx_midi_ports[0] = 1; + } + + return 0; +} diff --git a/sound/firewire/dice/dice-transaction.c b/sound/firewire/dice/dice-transaction.c new file mode 100644 index 000000000..2c0dde29a --- /dev/null +++ b/sound/firewire/dice/dice-transaction.c @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * dice_transaction.c - a part of driver for Dice based devices + * + * Copyright (c) Clemens Ladisch + * Copyright (c) 2014 Takashi Sakamoto + */ + +#include "dice.h" + +static u64 get_subaddr(struct snd_dice *dice, enum snd_dice_addr_type type, + u64 offset) +{ + switch (type) { + case SND_DICE_ADDR_TYPE_TX: + offset += dice->tx_offset; + break; + case SND_DICE_ADDR_TYPE_RX: + offset += dice->rx_offset; + break; + case SND_DICE_ADDR_TYPE_SYNC: + offset += dice->sync_offset; + break; + case SND_DICE_ADDR_TYPE_RSRV: + offset += dice->rsrv_offset; + break; + case SND_DICE_ADDR_TYPE_GLOBAL: + default: + offset += dice->global_offset; + break; + } + offset += DICE_PRIVATE_SPACE; + return offset; +} + +int snd_dice_transaction_write(struct snd_dice *dice, + enum snd_dice_addr_type type, + unsigned int offset, void *buf, unsigned int len) +{ + return snd_fw_transaction(dice->unit, + (len == 4) ? TCODE_WRITE_QUADLET_REQUEST : + TCODE_WRITE_BLOCK_REQUEST, + get_subaddr(dice, type, offset), buf, len, 0); +} + +int snd_dice_transaction_read(struct snd_dice *dice, + enum snd_dice_addr_type type, unsigned int offset, + void *buf, unsigned int len) +{ + return snd_fw_transaction(dice->unit, + (len == 4) ? TCODE_READ_QUADLET_REQUEST : + TCODE_READ_BLOCK_REQUEST, + get_subaddr(dice, type, offset), buf, len, 0); +} + +static unsigned int get_clock_info(struct snd_dice *dice, __be32 *info) +{ + return snd_dice_transaction_read_global(dice, GLOBAL_CLOCK_SELECT, + info, 4); +} + +int snd_dice_transaction_get_clock_source(struct snd_dice *dice, + unsigned int *source) +{ + __be32 info; + int err; + + err = get_clock_info(dice, &info); + if (err >= 0) + *source = be32_to_cpu(info) & CLOCK_SOURCE_MASK; + + return err; +} + +int snd_dice_transaction_get_rate(struct snd_dice *dice, unsigned int *rate) +{ + __be32 info; + unsigned int index; + int err; + + err = get_clock_info(dice, &info); + if (err < 0) + goto end; + + index = (be32_to_cpu(info) & CLOCK_RATE_MASK) >> CLOCK_RATE_SHIFT; + if (index >= SND_DICE_RATES_COUNT) { + err = -ENOSYS; + goto end; + } + + *rate = snd_dice_rates[index]; +end: + return err; +} + +int snd_dice_transaction_set_enable(struct snd_dice *dice) +{ + __be32 value; + int err = 0; + + if (dice->global_enabled) + goto end; + + value = cpu_to_be32(1); + err = snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST, + get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL, + GLOBAL_ENABLE), + &value, 4, + FW_FIXED_GENERATION | dice->owner_generation); + if (err < 0) + goto end; + + dice->global_enabled = true; +end: + return err; +} + +void snd_dice_transaction_clear_enable(struct snd_dice *dice) +{ + __be32 value; + + value = 0; + snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST, + get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL, + GLOBAL_ENABLE), + &value, 4, FW_QUIET | + FW_FIXED_GENERATION | dice->owner_generation); + + dice->global_enabled = false; +} + +static void dice_notification(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_dice *dice = callback_data; + u32 bits; + unsigned long flags; + + if (tcode != TCODE_WRITE_QUADLET_REQUEST) { + fw_send_response(card, request, RCODE_TYPE_ERROR); + return; + } + if ((offset & 3) != 0) { + fw_send_response(card, request, RCODE_ADDRESS_ERROR); + return; + } + + bits = be32_to_cpup(data); + + spin_lock_irqsave(&dice->lock, flags); + dice->notification_bits |= bits; + spin_unlock_irqrestore(&dice->lock, flags); + + fw_send_response(card, request, RCODE_COMPLETE); + + if (bits & NOTIFY_LOCK_CHG) + complete(&dice->clock_accepted); + wake_up(&dice->hwdep_wait); +} + +static int register_notification_address(struct snd_dice *dice, bool retry) +{ + struct fw_device *device = fw_parent_device(dice->unit); + __be64 *buffer; + unsigned int retries; + int err; + + retries = (retry) ? 3 : 0; + + buffer = kmalloc(2 * 8, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + for (;;) { + buffer[0] = cpu_to_be64(OWNER_NO_OWNER); + buffer[1] = cpu_to_be64( + ((u64)device->card->node_id << OWNER_NODE_SHIFT) | + dice->notification_handler.offset); + + dice->owner_generation = device->generation; + smp_rmb(); /* node_id vs. generation */ + err = snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP, + get_subaddr(dice, + SND_DICE_ADDR_TYPE_GLOBAL, + GLOBAL_OWNER), + buffer, 2 * 8, + FW_FIXED_GENERATION | + dice->owner_generation); + if (err == 0) { + /* success */ + if (buffer[0] == cpu_to_be64(OWNER_NO_OWNER)) + break; + /* The address seems to be already registered. */ + if (buffer[0] == buffer[1]) + break; + + dev_err(&dice->unit->device, + "device is already in use\n"); + err = -EBUSY; + } + if (err != -EAGAIN || retries-- > 0) + break; + + msleep(20); + } + + kfree(buffer); + + if (err < 0) + dice->owner_generation = -1; + + return err; +} + +static void unregister_notification_address(struct snd_dice *dice) +{ + struct fw_device *device = fw_parent_device(dice->unit); + __be64 *buffer; + + buffer = kmalloc(2 * 8, GFP_KERNEL); + if (buffer == NULL) + return; + + buffer[0] = cpu_to_be64( + ((u64)device->card->node_id << OWNER_NODE_SHIFT) | + dice->notification_handler.offset); + buffer[1] = cpu_to_be64(OWNER_NO_OWNER); + snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP, + get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL, + GLOBAL_OWNER), + buffer, 2 * 8, FW_QUIET | + FW_FIXED_GENERATION | dice->owner_generation); + + kfree(buffer); + + dice->owner_generation = -1; +} + +void snd_dice_transaction_destroy(struct snd_dice *dice) +{ + struct fw_address_handler *handler = &dice->notification_handler; + + if (handler->callback_data == NULL) + return; + + unregister_notification_address(dice); + + fw_core_remove_address_handler(handler); + handler->callback_data = NULL; +} + +int snd_dice_transaction_reinit(struct snd_dice *dice) +{ + struct fw_address_handler *handler = &dice->notification_handler; + + if (handler->callback_data == NULL) + return -EINVAL; + + return register_notification_address(dice, false); +} + +static int get_subaddrs(struct snd_dice *dice) +{ + static const int min_values[10] = { + 10, 0x60 / 4, + 10, 0x18 / 4, + 10, 0x18 / 4, + 0, 0, + 0, 0, + }; + __be32 *pointers; + __be32 version; + u32 data; + unsigned int i; + int err; + + pointers = kmalloc_array(ARRAY_SIZE(min_values), sizeof(__be32), + GFP_KERNEL); + if (pointers == NULL) + return -ENOMEM; + + /* + * Check that the sub address spaces exist and are located inside the + * private address space. The minimum values are chosen so that all + * minimally required registers are included. + */ + err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, + DICE_PRIVATE_SPACE, pointers, + sizeof(__be32) * ARRAY_SIZE(min_values), 0); + if (err < 0) + goto end; + + for (i = 0; i < ARRAY_SIZE(min_values); ++i) { + data = be32_to_cpu(pointers[i]); + if (data < min_values[i] || data >= 0x40000) { + err = -ENODEV; + goto end; + } + } + + if (be32_to_cpu(pointers[1]) > 0x18) { + /* + * Check that the implemented DICE driver specification major + * version number matches. + */ + err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, + DICE_PRIVATE_SPACE + + be32_to_cpu(pointers[0]) * 4 + GLOBAL_VERSION, + &version, sizeof(version), 0); + if (err < 0) + goto end; + + if ((version & cpu_to_be32(0xff000000)) != + cpu_to_be32(0x01000000)) { + dev_err(&dice->unit->device, + "unknown DICE version: 0x%08x\n", + be32_to_cpu(version)); + err = -ENODEV; + goto end; + } + + /* Set up later. */ + dice->clock_caps = 1; + } + + dice->global_offset = be32_to_cpu(pointers[0]) * 4; + dice->tx_offset = be32_to_cpu(pointers[2]) * 4; + dice->rx_offset = be32_to_cpu(pointers[4]) * 4; + + /* Old firmware doesn't support these fields. */ + if (pointers[7]) + dice->sync_offset = be32_to_cpu(pointers[6]) * 4; + if (pointers[9]) + dice->rsrv_offset = be32_to_cpu(pointers[8]) * 4; +end: + kfree(pointers); + return err; +} + +int snd_dice_transaction_init(struct snd_dice *dice) +{ + struct fw_address_handler *handler = &dice->notification_handler; + int err; + + err = get_subaddrs(dice); + if (err < 0) + return err; + + /* Allocation callback in address space over host controller */ + handler->length = 4; + handler->address_callback = dice_notification; + handler->callback_data = dice; + err = fw_core_add_address_handler(handler, &fw_high_memory_region); + if (err < 0) { + handler->callback_data = NULL; + return err; + } + + /* Register the address space */ + err = register_notification_address(dice, true); + if (err < 0) { + fw_core_remove_address_handler(handler); + handler->callback_data = NULL; + } + + return err; +} diff --git a/sound/firewire/dice/dice.c b/sound/firewire/dice/dice.c new file mode 100644 index 000000000..06c94f009 --- /dev/null +++ b/sound/firewire/dice/dice.c @@ -0,0 +1,422 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TC Applied Technologies Digital Interface Communications Engine driver + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + */ + +#include "dice.h" + +MODULE_DESCRIPTION("DICE driver"); +MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); +MODULE_LICENSE("GPL v2"); + +#define OUI_WEISS 0x001c6a +#define OUI_LOUD 0x000ff2 +#define OUI_FOCUSRITE 0x00130e +#define OUI_TCELECTRONIC 0x000166 +#define OUI_ALESIS 0x000595 +#define OUI_MAUDIO 0x000d6c +#define OUI_MYTEK 0x001ee8 +#define OUI_SSL 0x0050c2 // Actually ID reserved by IEEE. +#define OUI_PRESONUS 0x000a92 + +#define DICE_CATEGORY_ID 0x04 +#define WEISS_CATEGORY_ID 0x00 +#define LOUD_CATEGORY_ID 0x10 + +#define MODEL_ALESIS_IO_BOTH 0x000001 + +static int check_dice_category(struct fw_unit *unit) +{ + struct fw_device *device = fw_parent_device(unit); + struct fw_csr_iterator it; + int key, val, vendor = -1, model = -1; + unsigned int category; + + /* + * Check that GUID and unit directory are constructed according to DICE + * rules, i.e., that the specifier ID is the GUID's OUI, and that the + * GUID chip ID consists of the 8-bit category ID, the 10-bit product + * ID, and a 22-bit serial number. + */ + fw_csr_iterator_init(&it, unit->directory); + while (fw_csr_iterator_next(&it, &key, &val)) { + switch (key) { + case CSR_SPECIFIER_ID: + vendor = val; + break; + case CSR_MODEL: + model = val; + break; + } + } + + if (vendor == OUI_WEISS) + category = WEISS_CATEGORY_ID; + else if (vendor == OUI_LOUD) + category = LOUD_CATEGORY_ID; + else + category = DICE_CATEGORY_ID; + if (device->config_rom[3] != ((vendor << 8) | category) || + device->config_rom[4] >> 22 != model) + return -ENODEV; + + return 0; +} + +static int check_clock_caps(struct snd_dice *dice) +{ + __be32 value; + int err; + + /* some very old firmwares don't tell about their clock support */ + if (dice->clock_caps > 0) { + err = snd_dice_transaction_read_global(dice, + GLOBAL_CLOCK_CAPABILITIES, + &value, 4); + if (err < 0) + return err; + dice->clock_caps = be32_to_cpu(value); + } else { + /* this should be supported by any device */ + dice->clock_caps = CLOCK_CAP_RATE_44100 | + CLOCK_CAP_RATE_48000 | + CLOCK_CAP_SOURCE_ARX1 | + CLOCK_CAP_SOURCE_INTERNAL; + } + + return 0; +} + +static void dice_card_strings(struct snd_dice *dice) +{ + struct snd_card *card = dice->card; + struct fw_device *dev = fw_parent_device(dice->unit); + char vendor[32], model[32]; + unsigned int i; + int err; + + strcpy(card->driver, "DICE"); + + strcpy(card->shortname, "DICE"); + BUILD_BUG_ON(NICK_NAME_SIZE < sizeof(card->shortname)); + err = snd_dice_transaction_read_global(dice, GLOBAL_NICK_NAME, + card->shortname, + sizeof(card->shortname)); + if (err >= 0) { + /* DICE strings are returned in "always-wrong" endianness */ + BUILD_BUG_ON(sizeof(card->shortname) % 4 != 0); + for (i = 0; i < sizeof(card->shortname); i += 4) + swab32s((u32 *)&card->shortname[i]); + card->shortname[sizeof(card->shortname) - 1] = '\0'; + } + + strcpy(vendor, "?"); + fw_csr_string(dev->config_rom + 5, CSR_VENDOR, vendor, sizeof(vendor)); + strcpy(model, "?"); + fw_csr_string(dice->unit->directory, CSR_MODEL, model, sizeof(model)); + snprintf(card->longname, sizeof(card->longname), + "%s %s (serial %u) at %s, S%d", + vendor, model, dev->config_rom[4] & 0x3fffff, + dev_name(&dice->unit->device), 100 << dev->max_speed); + + strcpy(card->mixername, "DICE"); +} + +static void dice_card_free(struct snd_card *card) +{ + struct snd_dice *dice = card->private_data; + + snd_dice_stream_destroy_duplex(dice); + snd_dice_transaction_destroy(dice); +} + +static void do_registration(struct work_struct *work) +{ + struct snd_dice *dice = container_of(work, struct snd_dice, dwork.work); + int err; + + if (dice->registered) + return; + + err = snd_card_new(&dice->unit->device, -1, NULL, THIS_MODULE, 0, + &dice->card); + if (err < 0) + return; + dice->card->private_free = dice_card_free; + dice->card->private_data = dice; + + err = snd_dice_transaction_init(dice); + if (err < 0) + goto error; + + err = check_clock_caps(dice); + if (err < 0) + goto error; + + dice_card_strings(dice); + + err = dice->detect_formats(dice); + if (err < 0) + goto error; + + err = snd_dice_stream_init_duplex(dice); + if (err < 0) + goto error; + + snd_dice_create_proc(dice); + + err = snd_dice_create_pcm(dice); + if (err < 0) + goto error; + + err = snd_dice_create_midi(dice); + if (err < 0) + goto error; + + err = snd_dice_create_hwdep(dice); + if (err < 0) + goto error; + + err = snd_card_register(dice->card); + if (err < 0) + goto error; + + dice->registered = true; + + return; +error: + snd_card_free(dice->card); + dev_info(&dice->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int dice_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_dice *dice; + int err; + + if (!entry->driver_data && entry->vendor_id != OUI_SSL) { + err = check_dice_category(unit); + if (err < 0) + return -ENODEV; + } + + /* Allocate this independent of sound card instance. */ + dice = devm_kzalloc(&unit->device, sizeof(struct snd_dice), GFP_KERNEL); + if (!dice) + return -ENOMEM; + dice->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, dice); + + if (!entry->driver_data) { + dice->detect_formats = snd_dice_stream_detect_current_formats; + } else { + dice->detect_formats = + (snd_dice_detect_formats_t)entry->driver_data; + } + + spin_lock_init(&dice->lock); + mutex_init(&dice->mutex); + init_completion(&dice->clock_accepted); + init_waitqueue_head(&dice->hwdep_wait); + + /* Allocate and register this sound card later. */ + INIT_DEFERRABLE_WORK(&dice->dwork, do_registration); + snd_fw_schedule_registration(unit, &dice->dwork); + + return 0; +} + +static void dice_remove(struct fw_unit *unit) +{ + struct snd_dice *dice = dev_get_drvdata(&unit->device); + + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_delayed_work_sync(&dice->dwork); + + if (dice->registered) { + // Block till all of ALSA character devices are released. + snd_card_free(dice->card); + } + + mutex_destroy(&dice->mutex); + fw_unit_put(dice->unit); +} + +static void dice_bus_reset(struct fw_unit *unit) +{ + struct snd_dice *dice = dev_get_drvdata(&unit->device); + + /* Postpone a workqueue for deferred registration. */ + if (!dice->registered) + snd_fw_schedule_registration(unit, &dice->dwork); + + /* The handler address register becomes initialized. */ + snd_dice_transaction_reinit(dice); + + /* + * After registration, userspace can start packet streaming, then this + * code block works fine. + */ + if (dice->registered) { + mutex_lock(&dice->mutex); + snd_dice_stream_update_duplex(dice); + mutex_unlock(&dice->mutex); + } +} + +#define DICE_INTERFACE 0x000001 + +static const struct ieee1394_device_id dice_id_table[] = { + /* M-Audio Profire 2626 has a different value in version field. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_MAUDIO, + .model_id = 0x000010, + .driver_data = (kernel_ulong_t)snd_dice_detect_extension_formats, + }, + /* M-Audio Profire 610 has a different value in version field. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_MAUDIO, + .model_id = 0x000011, + .driver_data = (kernel_ulong_t)snd_dice_detect_extension_formats, + }, + /* TC Electronic Konnekt 24D. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_TCELECTRONIC, + .model_id = 0x000020, + .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, + }, + /* TC Electronic Konnekt 8. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_TCELECTRONIC, + .model_id = 0x000021, + .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, + }, + /* TC Electronic Studio Konnekt 48. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_TCELECTRONIC, + .model_id = 0x000022, + .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, + }, + /* TC Electronic Konnekt Live. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_TCELECTRONIC, + .model_id = 0x000023, + .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, + }, + /* TC Electronic Desktop Konnekt 6. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_TCELECTRONIC, + .model_id = 0x000024, + .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, + }, + /* TC Electronic Impact Twin. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_TCELECTRONIC, + .model_id = 0x000027, + .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, + }, + /* TC Electronic Digital Konnekt x32. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_TCELECTRONIC, + .model_id = 0x000030, + .driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats, + }, + /* Alesis iO14/iO26. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_ALESIS, + .model_id = MODEL_ALESIS_IO_BOTH, + .driver_data = (kernel_ulong_t)snd_dice_detect_alesis_formats, + }, + // Alesis MasterControl. + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_ALESIS, + .model_id = 0x000002, + .driver_data = (kernel_ulong_t)snd_dice_detect_alesis_mastercontrol_formats, + }, + /* Mytek Stereo 192 DSD-DAC. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_MYTEK, + .model_id = 0x000002, + .driver_data = (kernel_ulong_t)snd_dice_detect_mytek_formats, + }, + // Solid State Logic, Duende Classic and Mini. + // NOTE: each field of GUID in config ROM is not compliant to standard + // DICE scheme. + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_SSL, + .model_id = 0x000070, + }, + // Presonus FireStudio. + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_PRESONUS, + .model_id = 0x000008, + .driver_data = (kernel_ulong_t)snd_dice_detect_presonus_formats, + }, + { + .match_flags = IEEE1394_MATCH_VERSION, + .version = DICE_INTERFACE, + }, + { } +}; +MODULE_DEVICE_TABLE(ieee1394, dice_id_table); + +static struct fw_driver dice_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + }, + .probe = dice_probe, + .update = dice_bus_reset, + .remove = dice_remove, + .id_table = dice_id_table, +}; + +static int __init alsa_dice_init(void) +{ + return driver_register(&dice_driver.driver); +} + +static void __exit alsa_dice_exit(void) +{ + driver_unregister(&dice_driver.driver); +} + +module_init(alsa_dice_init); +module_exit(alsa_dice_exit); diff --git a/sound/firewire/dice/dice.h b/sound/firewire/dice/dice.h new file mode 100644 index 000000000..7fbffcab9 --- /dev/null +++ b/sound/firewire/dice/dice.h @@ -0,0 +1,237 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * dice.h - a part of driver for Dice based devices + * + * Copyright (c) Clemens Ladisch + * Copyright (c) 2014 Takashi Sakamoto + */ + +#ifndef SOUND_DICE_H_INCLUDED +#define SOUND_DICE_H_INCLUDED + +#include <linux/compat.h> +#include <linux/completion.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/wait.h> +#include <linux/sched/signal.h> + +#include <sound/control.h> +#include <sound/core.h> +#include <sound/firewire.h> +#include <sound/hwdep.h> +#include <sound/info.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/rawmidi.h> + +#include "../amdtp-am824.h" +#include "../iso-resources.h" +#include "../lib.h" +#include "dice-interface.h" + +/* + * This module support maximum 2 pairs of tx/rx isochronous streams for + * our convinience. + * + * In documents for ASICs called with a name of 'DICE': + * - ASIC for DICE II: + * - Maximum 2 tx and 4 rx are supported. + * - A packet supports maximum 16 data channels. + * - TCD2210/2210-E (so-called 'Dice Mini'): + * - Maximum 2 tx and 2 rx are supported. + * - A packet supports maximum 16 data channels. + * - TCD2220/2220-E (so-called 'Dice Jr.') + * - 2 tx and 2 rx are supported. + * - A packet supports maximum 16 data channels. + * - TCD3070-CH (so-called 'Dice III') + * - Maximum 2 tx and 2 rx are supported. + * - A packet supports maximum 32 data channels. + * + * For the above, MIDI conformant data channel is just on the first isochronous + * stream. + */ +#define MAX_STREAMS 2 + +enum snd_dice_rate_mode { + SND_DICE_RATE_MODE_LOW = 0, + SND_DICE_RATE_MODE_MIDDLE, + SND_DICE_RATE_MODE_HIGH, + SND_DICE_RATE_MODE_COUNT, +}; + +struct snd_dice; +typedef int (*snd_dice_detect_formats_t)(struct snd_dice *dice); + +struct snd_dice { + struct snd_card *card; + struct fw_unit *unit; + spinlock_t lock; + struct mutex mutex; + + bool registered; + struct delayed_work dwork; + + /* Offsets for sub-addresses */ + unsigned int global_offset; + unsigned int rx_offset; + unsigned int tx_offset; + unsigned int sync_offset; + unsigned int rsrv_offset; + + unsigned int clock_caps; + unsigned int tx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT]; + unsigned int rx_pcm_chs[MAX_STREAMS][SND_DICE_RATE_MODE_COUNT]; + unsigned int tx_midi_ports[MAX_STREAMS]; + unsigned int rx_midi_ports[MAX_STREAMS]; + snd_dice_detect_formats_t detect_formats; + + struct fw_address_handler notification_handler; + int owner_generation; + u32 notification_bits; + + /* For uapi */ + int dev_lock_count; /* > 0 driver, < 0 userspace */ + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; + + /* For streaming */ + struct fw_iso_resources tx_resources[MAX_STREAMS]; + struct fw_iso_resources rx_resources[MAX_STREAMS]; + struct amdtp_stream tx_stream[MAX_STREAMS]; + struct amdtp_stream rx_stream[MAX_STREAMS]; + bool global_enabled; + struct completion clock_accepted; + unsigned int substreams_counter; + + struct amdtp_domain domain; +}; + +enum snd_dice_addr_type { + SND_DICE_ADDR_TYPE_PRIVATE, + SND_DICE_ADDR_TYPE_GLOBAL, + SND_DICE_ADDR_TYPE_TX, + SND_DICE_ADDR_TYPE_RX, + SND_DICE_ADDR_TYPE_SYNC, + SND_DICE_ADDR_TYPE_RSRV, +}; + +int snd_dice_transaction_write(struct snd_dice *dice, + enum snd_dice_addr_type type, + unsigned int offset, + void *buf, unsigned int len); +int snd_dice_transaction_read(struct snd_dice *dice, + enum snd_dice_addr_type type, unsigned int offset, + void *buf, unsigned int len); + +static inline int snd_dice_transaction_write_global(struct snd_dice *dice, + unsigned int offset, + void *buf, unsigned int len) +{ + return snd_dice_transaction_write(dice, + SND_DICE_ADDR_TYPE_GLOBAL, offset, + buf, len); +} +static inline int snd_dice_transaction_read_global(struct snd_dice *dice, + unsigned int offset, + void *buf, unsigned int len) +{ + return snd_dice_transaction_read(dice, + SND_DICE_ADDR_TYPE_GLOBAL, offset, + buf, len); +} +static inline int snd_dice_transaction_write_tx(struct snd_dice *dice, + unsigned int offset, + void *buf, unsigned int len) +{ + return snd_dice_transaction_write(dice, SND_DICE_ADDR_TYPE_TX, offset, + buf, len); +} +static inline int snd_dice_transaction_read_tx(struct snd_dice *dice, + unsigned int offset, + void *buf, unsigned int len) +{ + return snd_dice_transaction_read(dice, SND_DICE_ADDR_TYPE_TX, offset, + buf, len); +} +static inline int snd_dice_transaction_write_rx(struct snd_dice *dice, + unsigned int offset, + void *buf, unsigned int len) +{ + return snd_dice_transaction_write(dice, SND_DICE_ADDR_TYPE_RX, offset, + buf, len); +} +static inline int snd_dice_transaction_read_rx(struct snd_dice *dice, + unsigned int offset, + void *buf, unsigned int len) +{ + return snd_dice_transaction_read(dice, SND_DICE_ADDR_TYPE_RX, offset, + buf, len); +} +static inline int snd_dice_transaction_write_sync(struct snd_dice *dice, + unsigned int offset, + void *buf, unsigned int len) +{ + return snd_dice_transaction_write(dice, SND_DICE_ADDR_TYPE_SYNC, offset, + buf, len); +} +static inline int snd_dice_transaction_read_sync(struct snd_dice *dice, + unsigned int offset, + void *buf, unsigned int len) +{ + return snd_dice_transaction_read(dice, SND_DICE_ADDR_TYPE_SYNC, offset, + buf, len); +} + +int snd_dice_transaction_get_clock_source(struct snd_dice *dice, + unsigned int *source); +int snd_dice_transaction_get_rate(struct snd_dice *dice, unsigned int *rate); +int snd_dice_transaction_set_enable(struct snd_dice *dice); +void snd_dice_transaction_clear_enable(struct snd_dice *dice); +int snd_dice_transaction_init(struct snd_dice *dice); +int snd_dice_transaction_reinit(struct snd_dice *dice); +void snd_dice_transaction_destroy(struct snd_dice *dice); + +#define SND_DICE_RATES_COUNT 7 +extern const unsigned int snd_dice_rates[SND_DICE_RATES_COUNT]; + +int snd_dice_stream_get_rate_mode(struct snd_dice *dice, unsigned int rate, + enum snd_dice_rate_mode *mode); +int snd_dice_stream_start_duplex(struct snd_dice *dice); +void snd_dice_stream_stop_duplex(struct snd_dice *dice); +int snd_dice_stream_init_duplex(struct snd_dice *dice); +void snd_dice_stream_destroy_duplex(struct snd_dice *dice); +int snd_dice_stream_reserve_duplex(struct snd_dice *dice, unsigned int rate, + unsigned int events_per_period, + unsigned int events_per_buffer); +void snd_dice_stream_update_duplex(struct snd_dice *dice); +int snd_dice_stream_detect_current_formats(struct snd_dice *dice); + +int snd_dice_stream_lock_try(struct snd_dice *dice); +void snd_dice_stream_lock_release(struct snd_dice *dice); + +int snd_dice_create_pcm(struct snd_dice *dice); + +int snd_dice_create_hwdep(struct snd_dice *dice); + +void snd_dice_create_proc(struct snd_dice *dice); + +int snd_dice_create_midi(struct snd_dice *dice); + +int snd_dice_detect_tcelectronic_formats(struct snd_dice *dice); +int snd_dice_detect_alesis_formats(struct snd_dice *dice); +int snd_dice_detect_alesis_mastercontrol_formats(struct snd_dice *dice); +int snd_dice_detect_extension_formats(struct snd_dice *dice); +int snd_dice_detect_mytek_formats(struct snd_dice *dice); +int snd_dice_detect_presonus_formats(struct snd_dice *dice); + +#endif diff --git a/sound/firewire/digi00x/Makefile b/sound/firewire/digi00x/Makefile new file mode 100644 index 000000000..8add0cd9a --- /dev/null +++ b/sound/firewire/digi00x/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +snd-firewire-digi00x-objs := amdtp-dot.o digi00x-stream.o digi00x-proc.o \ + digi00x-pcm.o digi00x-hwdep.o \ + digi00x-transaction.o digi00x-midi.o digi00x.o +obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += snd-firewire-digi00x.o diff --git a/sound/firewire/digi00x/amdtp-dot.c b/sound/firewire/digi00x/amdtp-dot.c new file mode 100644 index 000000000..d613642a2 --- /dev/null +++ b/sound/firewire/digi00x/amdtp-dot.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * amdtp-dot.c - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + * Copyright (C) 2012 Robin Gareus <robin@gareus.org> + * Copyright (C) 2012 Damien Zammit <damien@zamaudio.com> + */ + +#include <sound/pcm.h> +#include "digi00x.h" + +#define CIP_FMT_AM 0x10 + +/* 'Clock-based rate control mode' is just supported. */ +#define AMDTP_FDF_AM824 0x00 + +/* + * Nominally 3125 bytes/second, but the MIDI port's clock might be + * 1% too slow, and the bus clock 100 ppm too fast. + */ +#define MIDI_BYTES_PER_SECOND 3093 + +/* + * Several devices look only at the first eight data blocks. + * In any case, this is more than enough for the MIDI data rate. + */ +#define MAX_MIDI_RX_BLOCKS 8 + +/* 3 = MAX(DOT_MIDI_IN_PORTS, DOT_MIDI_OUT_PORTS) + 1. */ +#define MAX_MIDI_PORTS 3 + +/* + * The double-oh-three algorithm was discovered by Robin Gareus and Damien + * Zammit in 2012, with reverse-engineering for Digi 003 Rack. + */ +struct dot_state { + u8 carry; + u8 idx; + unsigned int off; +}; + +struct amdtp_dot { + unsigned int pcm_channels; + struct dot_state state; + + struct snd_rawmidi_substream *midi[MAX_MIDI_PORTS]; + int midi_fifo_used[MAX_MIDI_PORTS]; + int midi_fifo_limit; +}; + +/* + * double-oh-three look up table + * + * @param idx index byte (audio-sample data) 0x00..0xff + * @param off channel offset shift + * @return salt to XOR with given data + */ +#define BYTE_PER_SAMPLE (4) +#define MAGIC_DOT_BYTE (2) +#define MAGIC_BYTE_OFF(x) (((x) * BYTE_PER_SAMPLE) + MAGIC_DOT_BYTE) +static u8 dot_scrt(const u8 idx, const unsigned int off) +{ + /* + * the length of the added pattern only depends on the lower nibble + * of the last non-zero data + */ + static const u8 len[16] = {0, 1, 3, 5, 7, 9, 11, 13, 14, + 12, 10, 8, 6, 4, 2, 0}; + + /* + * the lower nibble of the salt. Interleaved sequence. + * this is walked backwards according to len[] + */ + static const u8 nib[15] = {0x8, 0x7, 0x9, 0x6, 0xa, 0x5, 0xb, 0x4, + 0xc, 0x3, 0xd, 0x2, 0xe, 0x1, 0xf}; + + /* circular list for the salt's hi nibble. */ + static const u8 hir[15] = {0x0, 0x6, 0xf, 0x8, 0x7, 0x5, 0x3, 0x4, + 0xc, 0xd, 0xe, 0x1, 0x2, 0xb, 0xa}; + + /* + * start offset for upper nibble mapping. + * note: 9 is /special/. In the case where the high nibble == 0x9, + * hir[] is not used and - coincidentally - the salt's hi nibble is + * 0x09 regardless of the offset. + */ + static const u8 hio[16] = {0, 11, 12, 6, 7, 5, 1, 4, + 3, 0x00, 14, 13, 8, 9, 10, 2}; + + const u8 ln = idx & 0xf; + const u8 hn = (idx >> 4) & 0xf; + const u8 hr = (hn == 0x9) ? 0x9 : hir[(hio[hn] + off) % 15]; + + if (len[ln] < off) + return 0x00; + + return ((nib[14 + off - len[ln]]) | (hr << 4)); +} + +static void dot_encode_step(struct dot_state *state, __be32 *const buffer) +{ + u8 * const data = (u8 *) buffer; + + if (data[MAGIC_DOT_BYTE] != 0x00) { + state->off = 0; + state->idx = data[MAGIC_DOT_BYTE] ^ state->carry; + } + data[MAGIC_DOT_BYTE] ^= state->carry; + state->carry = dot_scrt(state->idx, ++(state->off)); +} + +int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels) +{ + struct amdtp_dot *p = s->protocol; + int err; + + if (amdtp_stream_running(s)) + return -EBUSY; + + /* + * A first data channel is for MIDI messages, the rest is Multi Bit + * Linear Audio data channel. + */ + err = amdtp_stream_set_parameters(s, rate, pcm_channels + 1); + if (err < 0) + return err; + + s->ctx_data.rx.fdf = AMDTP_FDF_AM824 | s->sfc; + + p->pcm_channels = pcm_channels; + + /* + * We do not know the actual MIDI FIFO size of most devices. Just + * assume two bytes, i.e., one byte can be received over the bus while + * the previous one is transmitted over MIDI. + * (The value here is adjusted for midi_ratelimit_per_packet().) + */ + p->midi_fifo_limit = rate - MIDI_BYTES_PER_SECOND * s->syt_interval + 1; + + return 0; +} + +static void write_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames, + unsigned int pcm_frames) +{ + struct amdtp_dot *p = s->protocol; + unsigned int channels = p->pcm_channels; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int pcm_buffer_pointer; + int remaining_frames; + const u32 *src; + int i, c; + + pcm_buffer_pointer = s->pcm_buffer_pointer + pcm_frames; + pcm_buffer_pointer %= runtime->buffer_size; + + src = (void *)runtime->dma_area + + frames_to_bytes(runtime, pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - pcm_buffer_pointer; + + buffer++; + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + buffer[c] = cpu_to_be32((*src >> 8) | 0x40000000); + dot_encode_step(&p->state, &buffer[c]); + src++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void read_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames, + unsigned int pcm_frames) +{ + struct amdtp_dot *p = s->protocol; + unsigned int channels = p->pcm_channels; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int pcm_buffer_pointer; + int remaining_frames; + u32 *dst; + int i, c; + + pcm_buffer_pointer = s->pcm_buffer_pointer + pcm_frames; + pcm_buffer_pointer %= runtime->buffer_size; + + dst = (void *)runtime->dma_area + + frames_to_bytes(runtime, pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - pcm_buffer_pointer; + + buffer++; + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + *dst = be32_to_cpu(buffer[c]) << 8; + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_dot *p = s->protocol; + unsigned int channels, i, c; + + channels = p->pcm_channels; + + buffer++; + for (i = 0; i < data_blocks; ++i) { + for (c = 0; c < channels; ++c) + buffer[c] = cpu_to_be32(0x40000000); + buffer += s->data_block_quadlets; + } +} + +static bool midi_ratelimit_per_packet(struct amdtp_stream *s, unsigned int port) +{ + struct amdtp_dot *p = s->protocol; + int used; + + used = p->midi_fifo_used[port]; + if (used == 0) + return true; + + used -= MIDI_BYTES_PER_SECOND * s->syt_interval; + used = max(used, 0); + p->midi_fifo_used[port] = used; + + return used < p->midi_fifo_limit; +} + +static inline void midi_use_bytes(struct amdtp_stream *s, + unsigned int port, unsigned int count) +{ + struct amdtp_dot *p = s->protocol; + + p->midi_fifo_used[port] += amdtp_rate_table[s->sfc] * count; +} + +static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks, unsigned int data_block_counter) +{ + struct amdtp_dot *p = s->protocol; + unsigned int f, port; + int len; + u8 *b; + + for (f = 0; f < data_blocks; f++) { + port = (data_block_counter + f) % 8; + b = (u8 *)&buffer[0]; + + len = 0; + if (port < MAX_MIDI_PORTS && + midi_ratelimit_per_packet(s, port) && + p->midi[port] != NULL) + len = snd_rawmidi_transmit(p->midi[port], b + 1, 2); + + if (len > 0) { + /* + * Upper 4 bits of LSB represent port number. + * - 0000b: physical MIDI port 1. + * - 0010b: physical MIDI port 2. + * - 1110b: console MIDI port. + */ + if (port == 2) + b[3] = 0xe0; + else if (port == 1) + b[3] = 0x20; + else + b[3] = 0x00; + b[3] |= len; + midi_use_bytes(s, port, len); + } else { + b[1] = 0; + b[2] = 0; + b[3] = 0; + } + b[0] = 0x80; + + buffer += s->data_block_quadlets; + } +} + +static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_dot *p = s->protocol; + unsigned int f, port, len; + u8 *b; + + for (f = 0; f < data_blocks; f++) { + b = (u8 *)&buffer[0]; + + len = b[3] & 0x0f; + if (len > 0) { + /* + * Upper 4 bits of LSB represent port number. + * - 0000b: physical MIDI port 1. Use port 0. + * - 1110b: console MIDI port. Use port 2. + */ + if (b[3] >> 4 > 0) + port = 2; + else + port = 0; + + if (port < MAX_MIDI_PORTS && p->midi[port]) + snd_rawmidi_receive(p->midi[port], b + 1, len); + } + + buffer += s->data_block_quadlets; + } +} + +int amdtp_dot_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + /* This protocol delivers 24 bit data in 32bit data channel. */ + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + return err; + + return amdtp_stream_add_pcm_hw_constraints(s, runtime); +} + +void amdtp_dot_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi) +{ + struct amdtp_dot *p = s->protocol; + + if (port < MAX_MIDI_PORTS) + WRITE_ONCE(p->midi[port], midi); +} + +static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) +{ + unsigned int pcm_frames = 0; + int i; + + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buf = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; + + if (pcm) { + read_pcm_s32(s, pcm, buf, data_blocks, pcm_frames); + pcm_frames += data_blocks; + } + + read_midi_messages(s, buf, data_blocks); + } + + return pcm_frames; +} + +static unsigned int process_it_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) +{ + unsigned int pcm_frames = 0; + int i; + + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buf = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; + + if (pcm) { + write_pcm_s32(s, pcm, buf, data_blocks, pcm_frames); + pcm_frames += data_blocks; + } else { + write_pcm_silence(s, buf, data_blocks); + } + + write_midi_messages(s, buf, data_blocks, + desc->data_block_counter); + } + + return pcm_frames; +} + +int amdtp_dot_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir) +{ + amdtp_stream_process_ctx_payloads_t process_ctx_payloads; + enum cip_flags flags; + + // Use different mode between incoming/outgoing. + if (dir == AMDTP_IN_STREAM) { + flags = CIP_NONBLOCKING; + process_ctx_payloads = process_ir_ctx_payloads; + } else { + flags = CIP_BLOCKING; + process_ctx_payloads = process_it_ctx_payloads; + } + + return amdtp_stream_init(s, unit, dir, flags, CIP_FMT_AM, + process_ctx_payloads, sizeof(struct amdtp_dot)); +} + +void amdtp_dot_reset(struct amdtp_stream *s) +{ + struct amdtp_dot *p = s->protocol; + + p->state.carry = 0x00; + p->state.idx = 0x00; + p->state.off = 0; +} diff --git a/sound/firewire/digi00x/digi00x-hwdep.c b/sound/firewire/digi00x/digi00x-hwdep.c new file mode 100644 index 000000000..41c5857c6 --- /dev/null +++ b/sound/firewire/digi00x/digi00x-hwdep.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * digi00x-hwdep.c - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + */ + +/* + * This codes give three functionality. + * + * 1.get firewire node information + * 2.get notification about starting/stopping stream + * 3.lock/unlock stream + * 4.get asynchronous messaging + */ + +#include "digi00x.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_dg00x *dg00x = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&dg00x->lock); + + while (!dg00x->dev_lock_changed && dg00x->msg == 0) { + prepare_to_wait(&dg00x->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&dg00x->lock); + schedule(); + finish_wait(&dg00x->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&dg00x->lock); + } + + memset(&event, 0, sizeof(event)); + if (dg00x->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (dg00x->dev_lock_count > 0); + dg00x->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } else { + event.digi00x_message.type = + SNDRV_FIREWIRE_EVENT_DIGI00X_MESSAGE; + event.digi00x_message.message = dg00x->msg; + dg00x->msg = 0; + + count = min_t(long, count, sizeof(event.digi00x_message)); + } + + spin_unlock_irq(&dg00x->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_dg00x *dg00x = hwdep->private_data; + __poll_t events; + + poll_wait(file, &dg00x->hwdep_wait, wait); + + spin_lock_irq(&dg00x->lock); + if (dg00x->dev_lock_changed || dg00x->msg) + events = EPOLLIN | EPOLLRDNORM; + else + events = 0; + spin_unlock_irq(&dg00x->lock); + + return events; +} + +static int hwdep_get_info(struct snd_dg00x *dg00x, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(dg00x->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_DIGI00X; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int hwdep_lock(struct snd_dg00x *dg00x) +{ + int err; + + spin_lock_irq(&dg00x->lock); + + if (dg00x->dev_lock_count == 0) { + dg00x->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&dg00x->lock); + + return err; +} + +static int hwdep_unlock(struct snd_dg00x *dg00x) +{ + int err; + + spin_lock_irq(&dg00x->lock); + + if (dg00x->dev_lock_count == -1) { + dg00x->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&dg00x->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_dg00x *dg00x = hwdep->private_data; + + spin_lock_irq(&dg00x->lock); + if (dg00x->dev_lock_count == -1) + dg00x->dev_lock_count = 0; + spin_unlock_irq(&dg00x->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_dg00x *dg00x = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(dg00x, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(dg00x); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(dg00x); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +int snd_dg00x_create_hwdep_device(struct snd_dg00x *dg00x) +{ + static const struct snd_hwdep_ops ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(dg00x->card, "Digi00x", 0, &hwdep); + if (err < 0) + return err; + + strcpy(hwdep->name, "Digi00x"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_DIGI00X; + hwdep->ops = ops; + hwdep->private_data = dg00x; + hwdep->exclusive = true; + + return err; +} diff --git a/sound/firewire/digi00x/digi00x-midi.c b/sound/firewire/digi00x/digi00x-midi.c new file mode 100644 index 000000000..68eb8c39a --- /dev/null +++ b/sound/firewire/digi00x/digi00x-midi.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * digi00x-midi.h - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + */ + +#include "digi00x.h" + +static int midi_open(struct snd_rawmidi_substream *substream) +{ + struct snd_dg00x *dg00x = substream->rmidi->private_data; + int err; + + err = snd_dg00x_stream_lock_try(dg00x); + if (err < 0) + return err; + + mutex_lock(&dg00x->mutex); + err = snd_dg00x_stream_reserve_duplex(dg00x, 0, 0, 0); + if (err >= 0) { + ++dg00x->substreams_counter; + err = snd_dg00x_stream_start_duplex(dg00x); + if (err < 0) + --dg00x->substreams_counter; + } + mutex_unlock(&dg00x->mutex); + if (err < 0) + snd_dg00x_stream_lock_release(dg00x); + + return err; +} + +static int midi_close(struct snd_rawmidi_substream *substream) +{ + struct snd_dg00x *dg00x = substream->rmidi->private_data; + + mutex_lock(&dg00x->mutex); + --dg00x->substreams_counter; + snd_dg00x_stream_stop_duplex(dg00x); + mutex_unlock(&dg00x->mutex); + + snd_dg00x_stream_lock_release(dg00x); + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_dg00x *dg00x = substream->rmidi->private_data; + unsigned int port; + unsigned long flags; + + if (substream->rmidi->device == 0) + port = substream->number; + else + port = 2; + + spin_lock_irqsave(&dg00x->lock, flags); + + if (up) + amdtp_dot_midi_trigger(&dg00x->tx_stream, port, substream); + else + amdtp_dot_midi_trigger(&dg00x->tx_stream, port, NULL); + + spin_unlock_irqrestore(&dg00x->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_dg00x *dg00x = substream->rmidi->private_data; + unsigned int port; + unsigned long flags; + + if (substream->rmidi->device == 0) + port = substream->number; + else + port = 2; + + spin_lock_irqsave(&dg00x->lock, flags); + + if (up) + amdtp_dot_midi_trigger(&dg00x->rx_stream, port, substream); + else + amdtp_dot_midi_trigger(&dg00x->rx_stream, port, NULL); + + spin_unlock_irqrestore(&dg00x->lock, flags); +} + +static void set_substream_names(struct snd_dg00x *dg00x, + struct snd_rawmidi *rmidi, bool is_console) +{ + struct snd_rawmidi_substream *subs; + struct snd_rawmidi_str *str; + int i; + + for (i = 0; i < 2; ++i) { + str = &rmidi->streams[i]; + + list_for_each_entry(subs, &str->substreams, list) { + if (!is_console) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", + dg00x->card->shortname, + subs->number + 1); + } else { + snprintf(subs->name, sizeof(subs->name), + "%s control", + dg00x->card->shortname); + } + } + } +} + +static int add_substream_pair(struct snd_dg00x *dg00x, unsigned int out_ports, + unsigned int in_ports, bool is_console) +{ + static const struct snd_rawmidi_ops capture_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_capture_trigger, + }; + static const struct snd_rawmidi_ops playback_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_playback_trigger, + }; + const char *label; + struct snd_rawmidi *rmidi; + int err; + + /* Add physical midi ports. */ + err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, is_console, + out_ports, in_ports, &rmidi); + if (err < 0) + return err; + rmidi->private_data = dg00x; + + if (!is_console) + label = "%s control"; + else + label = "%s MIDI"; + snprintf(rmidi->name, sizeof(rmidi->name), label, + dg00x->card->shortname); + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &playback_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &capture_ops); + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + set_substream_names(dg00x, rmidi, is_console); + + return 0; +} + +int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x) +{ + int err; + + /* Add physical midi ports. */ + err = add_substream_pair(dg00x, DOT_MIDI_OUT_PORTS, DOT_MIDI_IN_PORTS, + false); + if (err < 0) + return err; + + if (dg00x->is_console) + err = add_substream_pair(dg00x, 1, 1, true); + + return err; +} diff --git a/sound/firewire/digi00x/digi00x-pcm.c b/sound/firewire/digi00x/digi00x-pcm.c new file mode 100644 index 000000000..b7f6eda09 --- /dev/null +++ b/sound/firewire/digi00x/digi00x-pcm.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * digi00x-pcm.c - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + */ + +#include "digi00x.h" + +static int hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1, + }; + unsigned int i; + + for (i = 0; i < SND_DG00X_RATE_COUNT; i++) { + if (!snd_interval_test(c, + snd_dg00x_stream_pcm_channels[i])) + continue; + + t.min = min(t.min, snd_dg00x_stream_rates[i]); + t.max = max(t.max, snd_dg00x_stream_rates[i]); + } + + return snd_interval_refine(r, &t); +} + +static int hw_rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1, + }; + unsigned int i; + + for (i = 0; i < SND_DG00X_RATE_COUNT; i++) { + if (!snd_interval_test(r, snd_dg00x_stream_rates[i])) + continue; + + t.min = min(t.min, snd_dg00x_stream_pcm_channels[i]); + t.max = max(t.max, snd_dg00x_stream_pcm_channels[i]); + } + + return snd_interval_refine(c, &t); +} + +static int pcm_init_hw_params(struct snd_dg00x *dg00x, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hardware *hw = &runtime->hw; + struct amdtp_stream *s; + int err; + + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + substream->runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + s = &dg00x->tx_stream; + } else { + substream->runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + s = &dg00x->rx_stream; + } + + hw->channels_min = 10; + hw->channels_max = 18; + + hw->rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000; + snd_pcm_limit_hw_rates(runtime); + + err = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, NULL, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + + err = snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, NULL, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + + return amdtp_dot_add_pcm_hw_constraints(s, substream->runtime); +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_dg00x *dg00x = substream->private_data; + struct amdtp_domain *d = &dg00x->domain; + enum snd_dg00x_clock clock; + bool detect; + int err; + + err = snd_dg00x_stream_lock_try(dg00x); + if (err < 0) + return err; + + err = pcm_init_hw_params(dg00x, substream); + if (err < 0) + goto err_locked; + + /* Check current clock source. */ + err = snd_dg00x_stream_get_clock(dg00x, &clock); + if (err < 0) + goto err_locked; + if (clock != SND_DG00X_CLOCK_INTERNAL) { + err = snd_dg00x_stream_check_external_clock(dg00x, &detect); + if (err < 0) + goto err_locked; + if (!detect) { + err = -EBUSY; + goto err_locked; + } + } + + mutex_lock(&dg00x->mutex); + + // When source of clock is not internal or any stream is reserved for + // transmission of PCM frames, the available sampling rate is limited + // at current one. + if ((clock != SND_DG00X_CLOCK_INTERNAL) || + (dg00x->substreams_counter > 0 && d->events_per_period > 0)) { + unsigned int frames_per_period = d->events_per_period; + unsigned int frames_per_buffer = d->events_per_buffer; + unsigned int rate; + + err = snd_dg00x_stream_get_external_rate(dg00x, &rate); + if (err < 0) { + mutex_unlock(&dg00x->mutex); + goto err_locked; + } + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + + if (frames_per_period > 0) { + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + frames_per_period, frames_per_period); + if (err < 0) { + mutex_unlock(&dg00x->mutex); + goto err_locked; + } + + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + frames_per_buffer, frames_per_buffer); + if (err < 0) { + mutex_unlock(&dg00x->mutex); + goto err_locked; + } + } + } + + mutex_unlock(&dg00x->mutex); + + snd_pcm_set_sync(substream); + + return 0; +err_locked: + snd_dg00x_stream_lock_release(dg00x); + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_dg00x *dg00x = substream->private_data; + + snd_dg00x_stream_lock_release(dg00x); + + return 0; +} + +static int pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_dg00x *dg00x = substream->private_data; + int err = 0; + + if (substream->runtime->status->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); + + mutex_lock(&dg00x->mutex); + err = snd_dg00x_stream_reserve_duplex(dg00x, rate, + frames_per_period, frames_per_buffer); + if (err >= 0) + ++dg00x->substreams_counter; + mutex_unlock(&dg00x->mutex); + } + + return err; +} + +static int pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_dg00x *dg00x = substream->private_data; + + mutex_lock(&dg00x->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + --dg00x->substreams_counter; + + snd_dg00x_stream_stop_duplex(dg00x); + + mutex_unlock(&dg00x->mutex); + + return 0; +} + +static int pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_dg00x *dg00x = substream->private_data; + int err; + + mutex_lock(&dg00x->mutex); + + err = snd_dg00x_stream_start_duplex(dg00x); + if (err >= 0) + amdtp_stream_pcm_prepare(&dg00x->tx_stream); + + mutex_unlock(&dg00x->mutex); + + return err; +} + +static int pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_dg00x *dg00x = substream->private_data; + int err; + + mutex_lock(&dg00x->mutex); + + err = snd_dg00x_stream_start_duplex(dg00x); + if (err >= 0) { + amdtp_stream_pcm_prepare(&dg00x->rx_stream); + amdtp_dot_reset(&dg00x->rx_stream); + } + + mutex_unlock(&dg00x->mutex); + + return err; +} + +static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_dg00x *dg00x = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&dg00x->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&dg00x->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_dg00x *dg00x = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&dg00x->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&dg00x->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_dg00x *dg00x = sbstrm->private_data; + + return amdtp_domain_stream_pcm_pointer(&dg00x->domain, &dg00x->tx_stream); +} + +static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_dg00x *dg00x = sbstrm->private_data; + + return amdtp_domain_stream_pcm_pointer(&dg00x->domain, &dg00x->rx_stream); +} + +static int pcm_capture_ack(struct snd_pcm_substream *substream) +{ + struct snd_dg00x *dg00x = substream->private_data; + + return amdtp_domain_stream_pcm_ack(&dg00x->domain, &dg00x->tx_stream); +} + +static int pcm_playback_ack(struct snd_pcm_substream *substream) +{ + struct snd_dg00x *dg00x = substream->private_data; + + return amdtp_domain_stream_pcm_ack(&dg00x->domain, &dg00x->rx_stream); +} + +int snd_dg00x_create_pcm_devices(struct snd_dg00x *dg00x) +{ + static const struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .hw_params = pcm_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .ack = pcm_capture_ack, + }; + static const struct snd_pcm_ops playback_ops = { + .open = pcm_open, + .close = pcm_close, + .hw_params = pcm_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .ack = pcm_playback_ack, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(dg00x->card, dg00x->card->driver, 0, 1, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = dg00x; + snprintf(pcm->name, sizeof(pcm->name), + "%s PCM", dg00x->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0); + + return 0; +} diff --git a/sound/firewire/digi00x/digi00x-proc.c b/sound/firewire/digi00x/digi00x-proc.c new file mode 100644 index 000000000..00b047fef --- /dev/null +++ b/sound/firewire/digi00x/digi00x-proc.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * digi00x-proc.c - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + */ + +#include "digi00x.h" + +static int get_optical_iface_mode(struct snd_dg00x *dg00x, + enum snd_dg00x_optical_mode *mode) +{ + __be32 data; + int err; + + err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_OPT_IFACE_MODE, + &data, sizeof(data), 0); + if (err >= 0) + *mode = be32_to_cpu(data) & 0x01; + + return err; +} + +static void proc_read_clock(struct snd_info_entry *entry, + struct snd_info_buffer *buf) +{ + static const char *const source_name[] = { + [SND_DG00X_CLOCK_INTERNAL] = "internal", + [SND_DG00X_CLOCK_SPDIF] = "s/pdif", + [SND_DG00X_CLOCK_ADAT] = "adat", + [SND_DG00X_CLOCK_WORD] = "word clock", + }; + static const char *const optical_name[] = { + [SND_DG00X_OPT_IFACE_MODE_ADAT] = "adat", + [SND_DG00X_OPT_IFACE_MODE_SPDIF] = "s/pdif", + }; + struct snd_dg00x *dg00x = entry->private_data; + enum snd_dg00x_optical_mode mode; + unsigned int rate; + enum snd_dg00x_clock clock; + bool detect; + + if (get_optical_iface_mode(dg00x, &mode) < 0) + return; + if (snd_dg00x_stream_get_local_rate(dg00x, &rate) < 0) + return; + if (snd_dg00x_stream_get_clock(dg00x, &clock) < 0) + return; + + snd_iprintf(buf, "Optical mode: %s\n", optical_name[mode]); + snd_iprintf(buf, "Sampling Rate: %d\n", rate); + snd_iprintf(buf, "Clock Source: %s\n", source_name[clock]); + + if (clock == SND_DG00X_CLOCK_INTERNAL) + return; + + if (snd_dg00x_stream_check_external_clock(dg00x, &detect) < 0) + return; + snd_iprintf(buf, "External source: %s\n", detect ? "detected" : "not"); + if (!detect) + return; + + if (snd_dg00x_stream_get_external_rate(dg00x, &rate) >= 0) + snd_iprintf(buf, "External sampling rate: %d\n", rate); +} + +void snd_dg00x_proc_init(struct snd_dg00x *dg00x) +{ + struct snd_info_entry *root, *entry; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(dg00x->card, "firewire", + dg00x->card->proc_root); + if (root == NULL) + return; + + root->mode = S_IFDIR | 0555; + + entry = snd_info_create_card_entry(dg00x->card, "clock", root); + if (entry) + snd_info_set_text_ops(entry, dg00x, proc_read_clock); +} diff --git a/sound/firewire/digi00x/digi00x-stream.c b/sound/firewire/digi00x/digi00x-stream.c new file mode 100644 index 000000000..62a54f5ab --- /dev/null +++ b/sound/firewire/digi00x/digi00x-stream.c @@ -0,0 +1,458 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * digi00x-stream.c - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + */ + +#include "digi00x.h" + +#define CALLBACK_TIMEOUT 500 + +const unsigned int snd_dg00x_stream_rates[SND_DG00X_RATE_COUNT] = { + [SND_DG00X_RATE_44100] = 44100, + [SND_DG00X_RATE_48000] = 48000, + [SND_DG00X_RATE_88200] = 88200, + [SND_DG00X_RATE_96000] = 96000, +}; + +/* Multi Bit Linear Audio data channels for each sampling transfer frequency. */ +const unsigned int +snd_dg00x_stream_pcm_channels[SND_DG00X_RATE_COUNT] = { + /* Analog/ADAT/SPDIF */ + [SND_DG00X_RATE_44100] = (8 + 8 + 2), + [SND_DG00X_RATE_48000] = (8 + 8 + 2), + /* Analog/SPDIF */ + [SND_DG00X_RATE_88200] = (8 + 2), + [SND_DG00X_RATE_96000] = (8 + 2), +}; + +int snd_dg00x_stream_get_local_rate(struct snd_dg00x *dg00x, unsigned int *rate) +{ + u32 data; + __be32 reg; + int err; + + err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_LOCAL_RATE, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + data = be32_to_cpu(reg) & 0x0f; + if (data < ARRAY_SIZE(snd_dg00x_stream_rates)) + *rate = snd_dg00x_stream_rates[data]; + else + err = -EIO; + + return err; +} + +int snd_dg00x_stream_set_local_rate(struct snd_dg00x *dg00x, unsigned int rate) +{ + __be32 reg; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(snd_dg00x_stream_rates); i++) { + if (rate == snd_dg00x_stream_rates[i]) + break; + } + if (i == ARRAY_SIZE(snd_dg00x_stream_rates)) + return -EINVAL; + + reg = cpu_to_be32(i); + return snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_LOCAL_RATE, + ®, sizeof(reg), 0); +} + +int snd_dg00x_stream_get_clock(struct snd_dg00x *dg00x, + enum snd_dg00x_clock *clock) +{ + __be32 reg; + int err; + + err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_CLOCK_SOURCE, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + *clock = be32_to_cpu(reg) & 0x0f; + if (*clock >= SND_DG00X_CLOCK_COUNT) + err = -EIO; + + return err; +} + +int snd_dg00x_stream_check_external_clock(struct snd_dg00x *dg00x, bool *detect) +{ + __be32 reg; + int err; + + err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_DETECT_EXTERNAL, + ®, sizeof(reg), 0); + if (err >= 0) + *detect = be32_to_cpu(reg) > 0; + + return err; +} + +int snd_dg00x_stream_get_external_rate(struct snd_dg00x *dg00x, + unsigned int *rate) +{ + u32 data; + __be32 reg; + int err; + + err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_EXTERNAL_RATE, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + data = be32_to_cpu(reg) & 0x0f; + if (data < ARRAY_SIZE(snd_dg00x_stream_rates)) + *rate = snd_dg00x_stream_rates[data]; + /* This means desync. */ + else + err = -EBUSY; + + return err; +} + +static void finish_session(struct snd_dg00x *dg00x) +{ + __be32 data; + + data = cpu_to_be32(0x00000003); + snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_STREAMING_SET, + &data, sizeof(data), 0); + + // Unregister isochronous channels for both direction. + data = 0; + snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_ISOC_CHANNELS, + &data, sizeof(data), 0); + + // Just after finishing the session, the device may lost transmitting + // functionality for a short time. + msleep(50); +} + +static int begin_session(struct snd_dg00x *dg00x) +{ + __be32 data; + u32 curr; + int err; + + // Register isochronous channels for both direction. + data = cpu_to_be32((dg00x->tx_resources.channel << 16) | + dg00x->rx_resources.channel); + err = snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_ISOC_CHANNELS, + &data, sizeof(data), 0); + if (err < 0) + return err; + + err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_STREAMING_STATE, + &data, sizeof(data), 0); + if (err < 0) + return err; + curr = be32_to_cpu(data); + + if (curr == 0) + curr = 2; + + curr--; + while (curr > 0) { + data = cpu_to_be32(curr); + err = snd_fw_transaction(dg00x->unit, + TCODE_WRITE_QUADLET_REQUEST, + DG00X_ADDR_BASE + + DG00X_OFFSET_STREAMING_SET, + &data, sizeof(data), 0); + if (err < 0) + break; + + msleep(20); + curr--; + } + + return err; +} + +static int keep_resources(struct snd_dg00x *dg00x, struct amdtp_stream *stream, + unsigned int rate) +{ + struct fw_iso_resources *resources; + int i; + int err; + + // Check sampling rate. + for (i = 0; i < SND_DG00X_RATE_COUNT; i++) { + if (snd_dg00x_stream_rates[i] == rate) + break; + } + if (i == SND_DG00X_RATE_COUNT) + return -EINVAL; + + if (stream == &dg00x->tx_stream) + resources = &dg00x->tx_resources; + else + resources = &dg00x->rx_resources; + + err = amdtp_dot_set_parameters(stream, rate, + snd_dg00x_stream_pcm_channels[i]); + if (err < 0) + return err; + + return fw_iso_resources_allocate(resources, + amdtp_stream_get_max_payload(stream), + fw_parent_device(dg00x->unit)->max_speed); +} + +static int init_stream(struct snd_dg00x *dg00x, struct amdtp_stream *s) +{ + struct fw_iso_resources *resources; + enum amdtp_stream_direction dir; + int err; + + if (s == &dg00x->tx_stream) { + resources = &dg00x->tx_resources; + dir = AMDTP_IN_STREAM; + } else { + resources = &dg00x->rx_resources; + dir = AMDTP_OUT_STREAM; + } + + err = fw_iso_resources_init(resources, dg00x->unit); + if (err < 0) + return err; + + err = amdtp_dot_init(s, dg00x->unit, dir); + if (err < 0) + fw_iso_resources_destroy(resources); + + return err; +} + +static void destroy_stream(struct snd_dg00x *dg00x, struct amdtp_stream *s) +{ + amdtp_stream_destroy(s); + + if (s == &dg00x->tx_stream) + fw_iso_resources_destroy(&dg00x->tx_resources); + else + fw_iso_resources_destroy(&dg00x->rx_resources); +} + +int snd_dg00x_stream_init_duplex(struct snd_dg00x *dg00x) +{ + int err; + + err = init_stream(dg00x, &dg00x->rx_stream); + if (err < 0) + return err; + + err = init_stream(dg00x, &dg00x->tx_stream); + if (err < 0) { + destroy_stream(dg00x, &dg00x->rx_stream); + return err; + } + + err = amdtp_domain_init(&dg00x->domain); + if (err < 0) { + destroy_stream(dg00x, &dg00x->rx_stream); + destroy_stream(dg00x, &dg00x->tx_stream); + } + + return err; +} + +/* + * This function should be called before starting streams or after stopping + * streams. + */ +void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x) +{ + amdtp_domain_destroy(&dg00x->domain); + + destroy_stream(dg00x, &dg00x->rx_stream); + destroy_stream(dg00x, &dg00x->tx_stream); +} + +int snd_dg00x_stream_reserve_duplex(struct snd_dg00x *dg00x, unsigned int rate, + unsigned int frames_per_period, + unsigned int frames_per_buffer) +{ + unsigned int curr_rate; + int err; + + err = snd_dg00x_stream_get_local_rate(dg00x, &curr_rate); + if (err < 0) + return err; + if (rate == 0) + rate = curr_rate; + + if (dg00x->substreams_counter == 0 || curr_rate != rate) { + amdtp_domain_stop(&dg00x->domain); + + finish_session(dg00x); + + fw_iso_resources_free(&dg00x->tx_resources); + fw_iso_resources_free(&dg00x->rx_resources); + + err = snd_dg00x_stream_set_local_rate(dg00x, rate); + if (err < 0) + return err; + + err = keep_resources(dg00x, &dg00x->rx_stream, rate); + if (err < 0) + return err; + + err = keep_resources(dg00x, &dg00x->tx_stream, rate); + if (err < 0) { + fw_iso_resources_free(&dg00x->rx_resources); + return err; + } + + err = amdtp_domain_set_events_per_period(&dg00x->domain, + frames_per_period, frames_per_buffer); + if (err < 0) { + fw_iso_resources_free(&dg00x->rx_resources); + fw_iso_resources_free(&dg00x->tx_resources); + return err; + } + } + + return 0; +} + +int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x) +{ + unsigned int generation = dg00x->rx_resources.generation; + int err = 0; + + if (dg00x->substreams_counter == 0) + return 0; + + if (amdtp_streaming_error(&dg00x->tx_stream) || + amdtp_streaming_error(&dg00x->rx_stream)) { + amdtp_domain_stop(&dg00x->domain); + finish_session(dg00x); + } + + if (generation != fw_parent_device(dg00x->unit)->card->generation) { + err = fw_iso_resources_update(&dg00x->tx_resources); + if (err < 0) + goto error; + + err = fw_iso_resources_update(&dg00x->rx_resources); + if (err < 0) + goto error; + } + + /* + * No packets are transmitted without receiving packets, reagardless of + * which source of clock is used. + */ + if (!amdtp_stream_running(&dg00x->rx_stream)) { + int spd = fw_parent_device(dg00x->unit)->max_speed; + + err = begin_session(dg00x); + if (err < 0) + goto error; + + err = amdtp_domain_add_stream(&dg00x->domain, &dg00x->rx_stream, + dg00x->rx_resources.channel, spd); + if (err < 0) + goto error; + + err = amdtp_domain_add_stream(&dg00x->domain, &dg00x->tx_stream, + dg00x->tx_resources.channel, spd); + if (err < 0) + goto error; + + err = amdtp_domain_start(&dg00x->domain, 0); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&dg00x->rx_stream, + CALLBACK_TIMEOUT) || + !amdtp_stream_wait_callback(&dg00x->tx_stream, + CALLBACK_TIMEOUT)) { + err = -ETIMEDOUT; + goto error; + } + } + + return 0; +error: + amdtp_domain_stop(&dg00x->domain); + finish_session(dg00x); + + return err; +} + +void snd_dg00x_stream_stop_duplex(struct snd_dg00x *dg00x) +{ + if (dg00x->substreams_counter == 0) { + amdtp_domain_stop(&dg00x->domain); + finish_session(dg00x); + + fw_iso_resources_free(&dg00x->tx_resources); + fw_iso_resources_free(&dg00x->rx_resources); + } +} + +void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x) +{ + fw_iso_resources_update(&dg00x->tx_resources); + fw_iso_resources_update(&dg00x->rx_resources); + + amdtp_stream_update(&dg00x->tx_stream); + amdtp_stream_update(&dg00x->rx_stream); +} + +void snd_dg00x_stream_lock_changed(struct snd_dg00x *dg00x) +{ + dg00x->dev_lock_changed = true; + wake_up(&dg00x->hwdep_wait); +} + +int snd_dg00x_stream_lock_try(struct snd_dg00x *dg00x) +{ + int err; + + spin_lock_irq(&dg00x->lock); + + /* user land lock this */ + if (dg00x->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (dg00x->dev_lock_count++ == 0) + snd_dg00x_stream_lock_changed(dg00x); + err = 0; +end: + spin_unlock_irq(&dg00x->lock); + return err; +} + +void snd_dg00x_stream_lock_release(struct snd_dg00x *dg00x) +{ + spin_lock_irq(&dg00x->lock); + + if (WARN_ON(dg00x->dev_lock_count <= 0)) + goto end; + if (--dg00x->dev_lock_count == 0) + snd_dg00x_stream_lock_changed(dg00x); +end: + spin_unlock_irq(&dg00x->lock); +} diff --git a/sound/firewire/digi00x/digi00x-transaction.c b/sound/firewire/digi00x/digi00x-transaction.c new file mode 100644 index 000000000..cf0bcf1c5 --- /dev/null +++ b/sound/firewire/digi00x/digi00x-transaction.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * digi00x-transaction.c - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + */ + +#include <sound/asound.h> +#include "digi00x.h" + +static void handle_unknown_message(struct snd_dg00x *dg00x, + unsigned long long offset, __be32 *buf) +{ + unsigned long flags; + + spin_lock_irqsave(&dg00x->lock, flags); + dg00x->msg = be32_to_cpu(*buf); + spin_unlock_irqrestore(&dg00x->lock, flags); + + wake_up(&dg00x->hwdep_wait); +} + +static void handle_message(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + struct snd_dg00x *dg00x = callback_data; + __be32 *buf = (__be32 *)data; + + fw_send_response(card, request, RCODE_COMPLETE); + + if (offset == dg00x->async_handler.offset) + handle_unknown_message(dg00x, offset, buf); +} + +int snd_dg00x_transaction_reregister(struct snd_dg00x *dg00x) +{ + struct fw_device *device = fw_parent_device(dg00x->unit); + __be32 data[2]; + + /* Unknown. 4bytes. */ + data[0] = cpu_to_be32((device->card->node_id << 16) | + (dg00x->async_handler.offset >> 32)); + data[1] = cpu_to_be32(dg00x->async_handler.offset); + return snd_fw_transaction(dg00x->unit, TCODE_WRITE_BLOCK_REQUEST, + DG00X_ADDR_BASE + DG00X_OFFSET_MESSAGE_ADDR, + &data, sizeof(data), 0); +} + +void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x) +{ + if (dg00x->async_handler.callback_data == NULL) + return; + + fw_core_remove_address_handler(&dg00x->async_handler); + + dg00x->async_handler.callback_data = NULL; +} + +int snd_dg00x_transaction_register(struct snd_dg00x *dg00x) +{ + static const struct fw_address_region resp_register_region = { + .start = 0xffffe0000000ull, + .end = 0xffffe000ffffull, + }; + int err; + + dg00x->async_handler.length = 4; + dg00x->async_handler.address_callback = handle_message; + dg00x->async_handler.callback_data = dg00x; + + err = fw_core_add_address_handler(&dg00x->async_handler, + &resp_register_region); + if (err < 0) + return err; + + err = snd_dg00x_transaction_reregister(dg00x); + if (err < 0) + snd_dg00x_transaction_unregister(dg00x); + + return err; +} diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c new file mode 100644 index 000000000..ab8408966 --- /dev/null +++ b/sound/firewire/digi00x/digi00x.c @@ -0,0 +1,220 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * digi00x.c - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + */ + +#include "digi00x.h" + +MODULE_DESCRIPTION("Digidesign Digi 002/003 family Driver"); +MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>"); +MODULE_LICENSE("GPL v2"); + +#define VENDOR_DIGIDESIGN 0x00a07e +#define MODEL_CONSOLE 0x000001 +#define MODEL_RACK 0x000002 +#define SPEC_VERSION 0x000001 + +static int name_card(struct snd_dg00x *dg00x) +{ + struct fw_device *fw_dev = fw_parent_device(dg00x->unit); + char name[32] = {0}; + char *model; + int err; + + err = fw_csr_string(dg00x->unit->directory, CSR_MODEL, name, + sizeof(name)); + if (err < 0) + return err; + + model = skip_spaces(name); + + strcpy(dg00x->card->driver, "Digi00x"); + strcpy(dg00x->card->shortname, model); + strcpy(dg00x->card->mixername, model); + snprintf(dg00x->card->longname, sizeof(dg00x->card->longname), + "Digidesign %s, GUID %08x%08x at %s, S%d", model, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&dg00x->unit->device), 100 << fw_dev->max_speed); + + return 0; +} + +static void dg00x_card_free(struct snd_card *card) +{ + struct snd_dg00x *dg00x = card->private_data; + + snd_dg00x_stream_destroy_duplex(dg00x); + snd_dg00x_transaction_unregister(dg00x); +} + +static void do_registration(struct work_struct *work) +{ + struct snd_dg00x *dg00x = + container_of(work, struct snd_dg00x, dwork.work); + int err; + + if (dg00x->registered) + return; + + err = snd_card_new(&dg00x->unit->device, -1, NULL, THIS_MODULE, 0, + &dg00x->card); + if (err < 0) + return; + dg00x->card->private_free = dg00x_card_free; + dg00x->card->private_data = dg00x; + + err = name_card(dg00x); + if (err < 0) + goto error; + + err = snd_dg00x_stream_init_duplex(dg00x); + if (err < 0) + goto error; + + snd_dg00x_proc_init(dg00x); + + err = snd_dg00x_create_pcm_devices(dg00x); + if (err < 0) + goto error; + + err = snd_dg00x_create_midi_devices(dg00x); + if (err < 0) + goto error; + + err = snd_dg00x_create_hwdep_device(dg00x); + if (err < 0) + goto error; + + err = snd_dg00x_transaction_register(dg00x); + if (err < 0) + goto error; + + err = snd_card_register(dg00x->card); + if (err < 0) + goto error; + + dg00x->registered = true; + + return; +error: + snd_card_free(dg00x->card); + dev_info(&dg00x->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int snd_dg00x_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_dg00x *dg00x; + + /* Allocate this independent of sound card instance. */ + dg00x = devm_kzalloc(&unit->device, sizeof(struct snd_dg00x), + GFP_KERNEL); + if (!dg00x) + return -ENOMEM; + + dg00x->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, dg00x); + + mutex_init(&dg00x->mutex); + spin_lock_init(&dg00x->lock); + init_waitqueue_head(&dg00x->hwdep_wait); + + dg00x->is_console = entry->model_id == MODEL_CONSOLE; + + /* Allocate and register this sound card later. */ + INIT_DEFERRABLE_WORK(&dg00x->dwork, do_registration); + snd_fw_schedule_registration(unit, &dg00x->dwork); + + return 0; +} + +static void snd_dg00x_update(struct fw_unit *unit) +{ + struct snd_dg00x *dg00x = dev_get_drvdata(&unit->device); + + /* Postpone a workqueue for deferred registration. */ + if (!dg00x->registered) + snd_fw_schedule_registration(unit, &dg00x->dwork); + + snd_dg00x_transaction_reregister(dg00x); + + /* + * After registration, userspace can start packet streaming, then this + * code block works fine. + */ + if (dg00x->registered) { + mutex_lock(&dg00x->mutex); + snd_dg00x_stream_update_duplex(dg00x); + mutex_unlock(&dg00x->mutex); + } +} + +static void snd_dg00x_remove(struct fw_unit *unit) +{ + struct snd_dg00x *dg00x = dev_get_drvdata(&unit->device); + + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_delayed_work_sync(&dg00x->dwork); + + if (dg00x->registered) { + // Block till all of ALSA character devices are released. + snd_card_free(dg00x->card); + } + + mutex_destroy(&dg00x->mutex); + fw_unit_put(dg00x->unit); +} + +static const struct ieee1394_device_id snd_dg00x_id_table[] = { + /* Both of 002/003 use the same ID. */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_VERSION | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = VENDOR_DIGIDESIGN, + .version = SPEC_VERSION, + .model_id = MODEL_CONSOLE, + }, + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_VERSION | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = VENDOR_DIGIDESIGN, + .version = SPEC_VERSION, + .model_id = MODEL_RACK, + }, + {} +}; +MODULE_DEVICE_TABLE(ieee1394, snd_dg00x_id_table); + +static struct fw_driver dg00x_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + }, + .probe = snd_dg00x_probe, + .update = snd_dg00x_update, + .remove = snd_dg00x_remove, + .id_table = snd_dg00x_id_table, +}; + +static int __init snd_dg00x_init(void) +{ + return driver_register(&dg00x_driver.driver); +} + +static void __exit snd_dg00x_exit(void) +{ + driver_unregister(&dg00x_driver.driver); +} + +module_init(snd_dg00x_init); +module_exit(snd_dg00x_exit); diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h new file mode 100644 index 000000000..129de8edd --- /dev/null +++ b/sound/firewire/digi00x/digi00x.h @@ -0,0 +1,163 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * digi00x.h - a part of driver for Digidesign Digi 002/003 family + * + * Copyright (c) 2014-2015 Takashi Sakamoto + */ + +#ifndef SOUND_DIGI00X_H_INCLUDED +#define SOUND_DIGI00X_H_INCLUDED + +#include <linux/compat.h> +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/sched/signal.h> + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/info.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/firewire.h> +#include <sound/hwdep.h> +#include <sound/rawmidi.h> + +#include "../lib.h" +#include "../iso-resources.h" +#include "../amdtp-stream.h" + +struct snd_dg00x { + struct snd_card *card; + struct fw_unit *unit; + + struct mutex mutex; + spinlock_t lock; + + bool registered; + struct delayed_work dwork; + + struct amdtp_stream tx_stream; + struct fw_iso_resources tx_resources; + + struct amdtp_stream rx_stream; + struct fw_iso_resources rx_resources; + + unsigned int substreams_counter; + + /* for uapi */ + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; + + /* For asynchronous messages. */ + struct fw_address_handler async_handler; + u32 msg; + + /* Console models have additional MIDI ports for control surface. */ + bool is_console; + + struct amdtp_domain domain; +}; + +#define DG00X_ADDR_BASE 0xffffe0000000ull + +#define DG00X_OFFSET_STREAMING_STATE 0x0000 +#define DG00X_OFFSET_STREAMING_SET 0x0004 +/* unknown but address in host space 0x0008 */ +/* For LSB of the address 0x000c */ +/* unknown 0x0010 */ +#define DG00X_OFFSET_MESSAGE_ADDR 0x0014 +/* For LSB of the address 0x0018 */ +/* unknown 0x001c */ +/* unknown 0x0020 */ +/* not used 0x0024--0x00ff */ +#define DG00X_OFFSET_ISOC_CHANNELS 0x0100 +/* unknown 0x0104 */ +/* unknown 0x0108 */ +/* unknown 0x010c */ +#define DG00X_OFFSET_LOCAL_RATE 0x0110 +#define DG00X_OFFSET_EXTERNAL_RATE 0x0114 +#define DG00X_OFFSET_CLOCK_SOURCE 0x0118 +#define DG00X_OFFSET_OPT_IFACE_MODE 0x011c +/* unknown 0x0120 */ +/* Mixer control on/off 0x0124 */ +/* unknown 0x0128 */ +#define DG00X_OFFSET_DETECT_EXTERNAL 0x012c +/* unknown 0x0138 */ +#define DG00X_OFFSET_MMC 0x0400 + +enum snd_dg00x_rate { + SND_DG00X_RATE_44100 = 0, + SND_DG00X_RATE_48000, + SND_DG00X_RATE_88200, + SND_DG00X_RATE_96000, + SND_DG00X_RATE_COUNT, +}; + +enum snd_dg00x_clock { + SND_DG00X_CLOCK_INTERNAL = 0, + SND_DG00X_CLOCK_SPDIF, + SND_DG00X_CLOCK_ADAT, + SND_DG00X_CLOCK_WORD, + SND_DG00X_CLOCK_COUNT, +}; + +enum snd_dg00x_optical_mode { + SND_DG00X_OPT_IFACE_MODE_ADAT = 0, + SND_DG00X_OPT_IFACE_MODE_SPDIF, + SND_DG00X_OPT_IFACE_MODE_COUNT, +}; + +#define DOT_MIDI_IN_PORTS 1 +#define DOT_MIDI_OUT_PORTS 2 + +int amdtp_dot_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir); +int amdtp_dot_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels); +void amdtp_dot_reset(struct amdtp_stream *s); +int amdtp_dot_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); +void amdtp_dot_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi); + +int snd_dg00x_transaction_register(struct snd_dg00x *dg00x); +int snd_dg00x_transaction_reregister(struct snd_dg00x *dg00x); +void snd_dg00x_transaction_unregister(struct snd_dg00x *dg00x); + +extern const unsigned int snd_dg00x_stream_rates[SND_DG00X_RATE_COUNT]; +extern const unsigned int snd_dg00x_stream_pcm_channels[SND_DG00X_RATE_COUNT]; +int snd_dg00x_stream_get_external_rate(struct snd_dg00x *dg00x, + unsigned int *rate); +int snd_dg00x_stream_get_local_rate(struct snd_dg00x *dg00x, + unsigned int *rate); +int snd_dg00x_stream_set_local_rate(struct snd_dg00x *dg00x, unsigned int rate); +int snd_dg00x_stream_get_clock(struct snd_dg00x *dg00x, + enum snd_dg00x_clock *clock); +int snd_dg00x_stream_check_external_clock(struct snd_dg00x *dg00x, + bool *detect); +int snd_dg00x_stream_init_duplex(struct snd_dg00x *dg00x); +int snd_dg00x_stream_reserve_duplex(struct snd_dg00x *dg00x, unsigned int rate, + unsigned int frames_per_period, + unsigned int frames_per_buffer); +int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x); +void snd_dg00x_stream_stop_duplex(struct snd_dg00x *dg00x); +void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x); +void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x); + +void snd_dg00x_stream_lock_changed(struct snd_dg00x *dg00x); +int snd_dg00x_stream_lock_try(struct snd_dg00x *dg00x); +void snd_dg00x_stream_lock_release(struct snd_dg00x *dg00x); + +void snd_dg00x_proc_init(struct snd_dg00x *dg00x); + +int snd_dg00x_create_pcm_devices(struct snd_dg00x *dg00x); + +int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x); + +int snd_dg00x_create_hwdep_device(struct snd_dg00x *dg00x); +#endif diff --git a/sound/firewire/fcp.c b/sound/firewire/fcp.c new file mode 100644 index 000000000..df44dd5dc --- /dev/null +++ b/sound/firewire/fcp.c @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Function Control Protocol (IEC 61883-1) helper functions + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + */ + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/wait.h> +#include <linux/delay.h> +#include "fcp.h" +#include "lib.h" +#include "amdtp-stream.h" + +#define CTS_AVC 0x00 + +#define ERROR_RETRIES 3 +#define ERROR_DELAY_MS 5 +#define FCP_TIMEOUT_MS 125 + +int avc_general_set_sig_fmt(struct fw_unit *unit, unsigned int rate, + enum avc_general_plug_dir dir, + unsigned short pid) +{ + unsigned int sfc; + u8 *buf; + bool flag; + int err; + + flag = false; + for (sfc = 0; sfc < CIP_SFC_COUNT; sfc++) { + if (amdtp_rate_table[sfc] == rate) { + flag = true; + break; + } + } + if (!flag) + return -EINVAL; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x00; /* AV/C CONTROL */ + buf[1] = 0xff; /* UNIT */ + if (dir == AVC_GENERAL_PLUG_DIR_IN) + buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */ + else + buf[2] = 0x18; /* OUTPUT PLUG SIGNAL FORMAT */ + buf[3] = 0xff & pid; /* plug id */ + buf[4] = 0x90; /* EOH_1, Form_1, FMT. AM824 */ + buf[5] = 0x07 & sfc; /* FDF-hi. AM824, frequency */ + buf[6] = 0xff; /* FDF-mid. AM824, SYT hi (not used)*/ + buf[7] = 0xff; /* FDF-low. AM824, SYT lo (not used) */ + + /* do transaction and check buf[1-5] are the same against command */ + err = fcp_avc_transaction(unit, buf, 8, buf, 8, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5)); + if (err < 0) + ; + else if (err < 8) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + if (err < 0) + goto end; + + err = 0; +end: + kfree(buf); + return err; +} +EXPORT_SYMBOL(avc_general_set_sig_fmt); + +int avc_general_get_sig_fmt(struct fw_unit *unit, unsigned int *rate, + enum avc_general_plug_dir dir, + unsigned short pid) +{ + unsigned int sfc; + u8 *buf; + int err; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x01; /* AV/C STATUS */ + buf[1] = 0xff; /* Unit */ + if (dir == AVC_GENERAL_PLUG_DIR_IN) + buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */ + else + buf[2] = 0x18; /* OUTPUT PLUG SIGNAL FORMAT */ + buf[3] = 0xff & pid; /* plug id */ + buf[4] = 0x90; /* EOH_1, Form_1, FMT. AM824 */ + buf[5] = 0xff; /* FDF-hi. AM824, frequency */ + buf[6] = 0xff; /* FDF-mid. AM824, SYT hi (not used) */ + buf[7] = 0xff; /* FDF-low. AM824, SYT lo (not used) */ + + /* do transaction and check buf[1-4] are the same against command */ + err = fcp_avc_transaction(unit, buf, 8, buf, 8, + BIT(1) | BIT(2) | BIT(3) | BIT(4)); + if (err < 0) + ; + else if (err < 8) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + /* check sfc field and pick up rate */ + sfc = 0x07 & buf[5]; + if (sfc >= CIP_SFC_COUNT) { + err = -EAGAIN; /* also in transition */ + goto end; + } + + *rate = amdtp_rate_table[sfc]; + err = 0; +end: + kfree(buf); + return err; +} +EXPORT_SYMBOL(avc_general_get_sig_fmt); + +int avc_general_get_plug_info(struct fw_unit *unit, unsigned int subunit_type, + unsigned int subunit_id, unsigned int subfunction, + u8 info[AVC_PLUG_INFO_BUF_BYTES]) +{ + u8 *buf; + int err; + + /* extended subunit in spec.4.2 is not supported */ + if ((subunit_type == 0x1E) || (subunit_id == 5)) + return -EINVAL; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x01; /* AV/C STATUS */ + /* UNIT or Subunit, Functionblock */ + buf[1] = ((subunit_type & 0x1f) << 3) | (subunit_id & 0x7); + buf[2] = 0x02; /* PLUG INFO */ + buf[3] = 0xff & subfunction; + + err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2)); + if (err < 0) + ; + else if (err < 8) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + info[0] = buf[4]; + info[1] = buf[5]; + info[2] = buf[6]; + info[3] = buf[7]; + + err = 0; +end: + kfree(buf); + return err; +} +EXPORT_SYMBOL(avc_general_get_plug_info); + +static DEFINE_SPINLOCK(transactions_lock); +static LIST_HEAD(transactions); + +enum fcp_state { + STATE_PENDING, + STATE_BUS_RESET, + STATE_COMPLETE, + STATE_DEFERRED, +}; + +struct fcp_transaction { + struct list_head list; + struct fw_unit *unit; + void *response_buffer; + unsigned int response_size; + unsigned int response_match_bytes; + enum fcp_state state; + wait_queue_head_t wait; + bool deferrable; +}; + +/** + * fcp_avc_transaction - send an AV/C command and wait for its response + * @unit: a unit on the target device + * @command: a buffer containing the command frame; must be DMA-able + * @command_size: the size of @command + * @response: a buffer for the response frame + * @response_size: the maximum size of @response + * @response_match_bytes: a bitmap specifying the bytes used to detect the + * correct response frame + * + * This function sends a FCP command frame to the target and waits for the + * corresponding response frame to be returned. + * + * Because it is possible for multiple FCP transactions to be active at the + * same time, the correct response frame is detected by the value of certain + * bytes. These bytes must be set in @response before calling this function, + * and the corresponding bits must be set in @response_match_bytes. + * + * @command and @response can point to the same buffer. + * + * Returns the actual size of the response frame, or a negative error code. + */ +int fcp_avc_transaction(struct fw_unit *unit, + const void *command, unsigned int command_size, + void *response, unsigned int response_size, + unsigned int response_match_bytes) +{ + struct fcp_transaction t; + int tcode, ret, tries = 0; + + t.unit = unit; + t.response_buffer = response; + t.response_size = response_size; + t.response_match_bytes = response_match_bytes; + t.state = STATE_PENDING; + init_waitqueue_head(&t.wait); + t.deferrable = (*(const u8 *)command == 0x00 || *(const u8 *)command == 0x03); + + spin_lock_irq(&transactions_lock); + list_add_tail(&t.list, &transactions); + spin_unlock_irq(&transactions_lock); + + for (;;) { + tcode = command_size == 4 ? TCODE_WRITE_QUADLET_REQUEST + : TCODE_WRITE_BLOCK_REQUEST; + ret = snd_fw_transaction(t.unit, tcode, + CSR_REGISTER_BASE + CSR_FCP_COMMAND, + (void *)command, command_size, 0); + if (ret < 0) + break; +deferred: + wait_event_timeout(t.wait, t.state != STATE_PENDING, + msecs_to_jiffies(FCP_TIMEOUT_MS)); + + if (t.state == STATE_DEFERRED) { + /* + * 'AV/C General Specification' define no time limit + * on command completion once an INTERIM response has + * been sent. but we promise to finish this function + * for a caller. Here we use FCP_TIMEOUT_MS for next + * interval. This is not in the specification. + */ + t.state = STATE_PENDING; + goto deferred; + } else if (t.state == STATE_COMPLETE) { + ret = t.response_size; + break; + } else if (t.state == STATE_BUS_RESET) { + msleep(ERROR_DELAY_MS); + } else if (++tries >= ERROR_RETRIES) { + dev_err(&t.unit->device, "FCP command timed out\n"); + ret = -EIO; + break; + } + } + + spin_lock_irq(&transactions_lock); + list_del(&t.list); + spin_unlock_irq(&transactions_lock); + + return ret; +} +EXPORT_SYMBOL(fcp_avc_transaction); + +/** + * fcp_bus_reset - inform the target handler about a bus reset + * @unit: the unit that might be used by fcp_avc_transaction() + * + * This function must be called from the driver's .update handler to inform + * the FCP transaction handler that a bus reset has happened. Any pending FCP + * transactions are retried. + */ +void fcp_bus_reset(struct fw_unit *unit) +{ + struct fcp_transaction *t; + + spin_lock_irq(&transactions_lock); + list_for_each_entry(t, &transactions, list) { + if (t->unit == unit && + (t->state == STATE_PENDING || + t->state == STATE_DEFERRED)) { + t->state = STATE_BUS_RESET; + wake_up(&t->wait); + } + } + spin_unlock_irq(&transactions_lock); +} +EXPORT_SYMBOL(fcp_bus_reset); + +/* checks whether the response matches the masked bytes in response_buffer */ +static bool is_matching_response(struct fcp_transaction *transaction, + const void *response, size_t length) +{ + const u8 *p1, *p2; + unsigned int mask, i; + + p1 = response; + p2 = transaction->response_buffer; + mask = transaction->response_match_bytes; + + for (i = 0; ; ++i) { + if ((mask & 1) && p1[i] != p2[i]) + return false; + mask >>= 1; + if (!mask) + return true; + if (--length == 0) + return false; + } +} + +static void fcp_response(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 fcp_transaction *t; + unsigned long flags; + + if (length < 1 || (*(const u8 *)data & 0xf0) != CTS_AVC) + return; + + spin_lock_irqsave(&transactions_lock, flags); + list_for_each_entry(t, &transactions, list) { + struct fw_device *device = fw_parent_device(t->unit); + if (device->card != card || + device->generation != generation) + continue; + smp_rmb(); /* node_id vs. generation */ + if (device->node_id != source) + continue; + + if (t->state == STATE_PENDING && + is_matching_response(t, data, length)) { + if (t->deferrable && *(const u8 *)data == 0x0f) { + t->state = STATE_DEFERRED; + } else { + t->state = STATE_COMPLETE; + t->response_size = min_t(unsigned int, length, + t->response_size); + memcpy(t->response_buffer, data, + t->response_size); + } + wake_up(&t->wait); + } + } + spin_unlock_irqrestore(&transactions_lock, flags); +} + +static struct fw_address_handler response_register_handler = { + .length = 0x200, + .address_callback = fcp_response, +}; + +static int __init fcp_module_init(void) +{ + static const struct fw_address_region response_register_region = { + .start = CSR_REGISTER_BASE + CSR_FCP_RESPONSE, + .end = CSR_REGISTER_BASE + CSR_FCP_END, + }; + + fw_core_add_address_handler(&response_register_handler, + &response_register_region); + + return 0; +} + +static void __exit fcp_module_exit(void) +{ + WARN_ON(!list_empty(&transactions)); + fw_core_remove_address_handler(&response_register_handler); +} + +module_init(fcp_module_init); +module_exit(fcp_module_exit); diff --git a/sound/firewire/fcp.h b/sound/firewire/fcp.h new file mode 100644 index 000000000..512f7c409 --- /dev/null +++ b/sound/firewire/fcp.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef SOUND_FIREWIRE_FCP_H_INCLUDED +#define SOUND_FIREWIRE_FCP_H_INCLUDED + +#define AVC_PLUG_INFO_BUF_BYTES 4 + +struct fw_unit; + +/* + * AV/C Digital Interface Command Set General Specification 4.2 + * (Sep 2004, 1394TA) + */ +enum avc_general_plug_dir { + AVC_GENERAL_PLUG_DIR_IN = 0, + AVC_GENERAL_PLUG_DIR_OUT = 1, + AVC_GENERAL_PLUG_DIR_COUNT +}; +int avc_general_set_sig_fmt(struct fw_unit *unit, unsigned int rate, + enum avc_general_plug_dir dir, + unsigned short plug); +int avc_general_get_sig_fmt(struct fw_unit *unit, unsigned int *rate, + enum avc_general_plug_dir dir, + unsigned short plug); +int avc_general_get_plug_info(struct fw_unit *unit, unsigned int subunit_type, + unsigned int subunit_id, unsigned int subfunction, + u8 info[AVC_PLUG_INFO_BUF_BYTES]); + +int fcp_avc_transaction(struct fw_unit *unit, + const void *command, unsigned int command_size, + void *response, unsigned int response_size, + unsigned int response_match_bytes); +void fcp_bus_reset(struct fw_unit *unit); + +#endif diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile new file mode 100644 index 000000000..3aef221ce --- /dev/null +++ b/sound/firewire/fireface/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +snd-fireface-objs := 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..119c0076b --- /dev/null +++ b/sound/firewire/fireface/amdtp-ff.c @@ -0,0 +1,173 @@ +// 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); +} + +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 unsigned int process_it_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) +{ + unsigned int pcm_frames = 0; + int i; + + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + 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); + } + } + + return pcm_frames; +} + +static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) +{ + unsigned int pcm_frames = 0; + int i; + + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + 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; + } + } + + return pcm_frames; +} + +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_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..e73e8d286 --- /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 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); + union snd_firewire_event event; + + spin_lock_irq(&ff->lock); + + while (!ff->dev_lock_changed) { + 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); + } + + memset(&event, 0, sizeof(event)); + if (ff->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (ff->dev_lock_count > 0); + ff->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } + + spin_unlock_irq(&ff->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_ff *ff = hwdep->private_data; + __poll_t events; + + poll_wait(file, &ff->hwdep_wait, wait); + + spin_lock_irq(&ff->lock); + if (ff->dev_lock_changed) + events = EPOLLIN | EPOLLRDNORM; + else + events = 0; + spin_unlock_irq(&ff->lock); + + return events; +} + +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]); + strlcpy(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) +{ + int err; + + spin_lock_irq(&ff->lock); + + if (ff->dev_lock_count == 0) { + ff->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&ff->lock); + + return err; +} + +static int hwdep_unlock(struct snd_ff *ff) +{ + int err; + + spin_lock_irq(&ff->lock); + + if (ff->dev_lock_count == -1) { + ff->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&ff->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_ff *ff = hwdep->private_data; + + spin_lock_irq(&ff->lock); + if (ff->dev_lock_count == -1) + ff->dev_lock_count = 0; + spin_unlock_irq(&ff->lock); + + 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; + + strcpy(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..25821d186 --- /dev/null +++ b/sound/firewire/fireface/ff-midi.c @@ -0,0 +1,128 @@ +// 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; + unsigned long flags; + + spin_lock_irqsave(&ff->lock, flags); + + if (up) + WRITE_ONCE(ff->tx_midi_substreams[substream->number], + substream); + else + WRITE_ONCE(ff->tx_midi_substreams[substream->number], NULL); + + spin_unlock_irqrestore(&ff->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_ff *ff = substream->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&ff->lock, flags); + + if (up || !ff->rx_midi_error[substream->number]) + schedule_work(&ff->rx_midi_work[substream->number]); + + spin_unlock_irqrestore(&ff->lock, flags); +} + +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) { + snprintf(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..f978cc2fe --- /dev/null +++ b/sound/firewire/fireface/ff-pcm.c @@ -0,0 +1,400 @@ +// 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; + + mutex_lock(&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) { + mutex_unlock(&ff->mutex); + 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) { + mutex_unlock(&ff->mutex); + 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) { + mutex_unlock(&ff->mutex); + goto release_lock; + } + } + } + + mutex_unlock(&ff->mutex); + + 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->status->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); + + mutex_lock(&ff->mutex); + err = snd_ff_stream_reserve_duplex(ff, rate, frames_per_period, + frames_per_buffer); + if (err >= 0) + ++ff->substreams_counter; + mutex_unlock(&ff->mutex); + } + + return err; +} + +static int pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + + mutex_lock(&ff->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + --ff->substreams_counter; + + snd_ff_stream_stop_duplex(ff); + + mutex_unlock(&ff->mutex); + + 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; + + mutex_lock(&ff->mutex); + + err = snd_ff_stream_start_duplex(ff, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&ff->tx_stream); + + mutex_unlock(&ff->mutex); + + 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; + + mutex_lock(&ff->mutex); + + err = snd_ff_stream_start_duplex(ff, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&ff->rx_stream); + + mutex_unlock(&ff->mutex); + + 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; + 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..bf44cad79 --- /dev/null +++ b/sound/firewire/fireface/ff-protocol-former.c @@ -0,0 +1,605 @@ +// SPDX-License-Identifier: GPL-2.0 +// ff-protocol-former.c - a part of driver for RME Fireface series +// +// Copyright (c) 2019 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#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", + (data & 0x00000040) ? "on" : "off"); + + snd_iprintf(buffer, "Optical output interface format: %s\n", + (data & 0x00000100) ? "S/PDIF" : "ADAT"); + + snd_iprintf(buffer, "Word output single speed: %s\n", + (data & 0x00002000) ? "on" : "off"); + + 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, + __le32 *buf, size_t length) +{ + 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_midi_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); +} + +// 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. +static void ff400_handle_midi_msg(struct snd_ff *ff, unsigned int offset, + __le32 *buf, size_t length) +{ + int i; + + for (i = 0; i < length / 4; i++) { + u32 quad = le32_to_cpu(buf[i]); + u8 byte; + unsigned int index; + struct snd_rawmidi_substream *substream; + + /* Message in first port. */ + /* + * This value may represent the index of this unit when the same + * units are on the same IEEE 1394 bus. This driver doesn't use + * it. + */ + index = (quad >> 8) & 0xff; + if (index > 0) { + substream = READ_ONCE(ff->tx_midi_substreams[0]); + if (substream != NULL) { + byte = quad & 0xff; + snd_rawmidi_receive(substream, &byte, 1); + } + } + + /* Message in second port. */ + index = (quad >> 24) & 0xff; + if (index > 0) { + substream = READ_ONCE(ff->tx_midi_substreams[1]); + if (substream != NULL) { + byte = (quad >> 16) & 0xff; + snd_rawmidi_receive(substream, &byte, 1); + } + } + } +} + +const struct snd_ff_protocol snd_ff_protocol_ff400 = { + .handle_midi_msg = ff400_handle_midi_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..7ddb7b97f --- /dev/null +++ b/sound/firewire/fireface/ff-protocol-latter.c @@ -0,0 +1,542 @@ +// SPDX-License-Identifier: GPL-2.0 +// ff-protocol-latter - a part of driver for RME Fireface series +// +// Copyright (c) 2019 Takashi Sakamoto +// +// Licensed under the terms of the GNU General Public License, version 2. + +#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, + __le32 *buf, size_t length) +{ + 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_midi_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..5452115c0 --- /dev/null +++ b/sound/firewire/fireface/ff-stream.c @@ -0,0 +1,284 @@ +// 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 CALLBACK_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; + + err = amdtp_domain_start(&ff->domain, 0); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&ff->rx_stream, + CALLBACK_TIMEOUT_MS) || + !amdtp_stream_wait_callback(&ff->tx_stream, + CALLBACK_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) +{ + int err; + + spin_lock_irq(&ff->lock); + + /* user land lock this */ + if (ff->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (ff->dev_lock_count++ == 0) + snd_ff_stream_lock_changed(ff); + err = 0; +end: + spin_unlock_irq(&ff->lock); + return err; +} + +void snd_ff_stream_lock_release(struct snd_ff *ff) +{ + spin_lock_irq(&ff->lock); + + if (WARN_ON(ff->dev_lock_count <= 0)) + goto end; + if (--ff->dev_lock_count == 0) + snd_ff_stream_lock_changed(ff); +end: + spin_unlock_irq(&ff->lock); +} diff --git a/sound/firewire/fireface/ff-transaction.c b/sound/firewire/fireface/ff-transaction.c new file mode 100644 index 000000000..ee7122c46 --- /dev/null +++ b/sound/firewire/fireface/ff-transaction.c @@ -0,0 +1,232 @@ +// 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_midi_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; + + fw_send_response(card, request, RCODE_COMPLETE); + + offset -= ff->async_handler.offset; + ff->spec->protocol->handle_midi_msg(ff, (unsigned int)offset, buf, + length); +} + +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_midi_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..bc3926941 --- /dev/null +++ b/sound/firewire/fireface/ff.c @@ -0,0 +1,290 @@ +// 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 v2"); + +static void name_card(struct snd_ff *ff) +{ + struct fw_device *fw_dev = fw_parent_device(ff->unit); + 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]; + + strcpy(ff->card->driver, "Fireface"); + strcpy(ff->card->shortname, name); + strcpy(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); +} + +static void do_registration(struct work_struct *work) +{ + struct snd_ff *ff = container_of(work, struct snd_ff, dwork.work); + int err; + + if (ff->registered) + return; + + err = snd_card_new(&ff->unit->device, -1, NULL, THIS_MODULE, 0, + &ff->card); + if (err < 0) + return; + ff->card->private_free = ff_card_free; + ff->card->private_data = ff; + + 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; + + err = snd_card_register(ff->card); + if (err < 0) + goto error; + + ff->registered = true; + + return; +error: + snd_card_free(ff->card); + dev_info(&ff->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int snd_ff_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_ff *ff; + + ff = devm_kzalloc(&unit->device, sizeof(struct snd_ff), GFP_KERNEL); + if (!ff) + return -ENOMEM; + ff->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, ff); + + mutex_init(&ff->mutex); + spin_lock_init(&ff->lock); + init_waitqueue_head(&ff->hwdep_wait); + + ff->unit_version = entry->version; + ff->spec = (const struct snd_ff_spec *)entry->driver_data; + + /* Register this sound card later. */ + INIT_DEFERRABLE_WORK(&ff->dwork, do_registration); + snd_fw_schedule_registration(unit, &ff->dwork); + + return 0; +} + +static void snd_ff_update(struct fw_unit *unit) +{ + struct snd_ff *ff = dev_get_drvdata(&unit->device); + + /* Postpone a workqueue for deferred registration. */ + if (!ff->registered) + snd_fw_schedule_registration(unit, &ff->dwork); + + snd_ff_transaction_reregister(ff); + + if (ff->registered) + snd_ff_stream_update_duplex(ff); +} + +static void snd_ff_remove(struct fw_unit *unit) +{ + struct snd_ff *ff = dev_get_drvdata(&unit->device); + + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_work_sync(&ff->dwork.work); + + if (ff->registered) { + // Block till all of ALSA character devices are released. + snd_card_free(ff->card); + } + + mutex_destroy(&ff->mutex); + fw_unit_put(ff->unit); +} + +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 = (kernel_ulong_t)&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 = (kernel_ulong_t)&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 = (kernel_ulong_t)&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 = (kernel_ulong_t)&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 = (kernel_ulong_t)&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..705e7df4f --- /dev/null +++ b/sound/firewire/fireface/ff.h @@ -0,0 +1,169 @@ +/* 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/mod_devicetable.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; + + bool registered; + struct delayed_work dwork; + + 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; +}; + +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 { + void (*handle_midi_msg)(struct snd_ff *ff, unsigned int offset, + __le32 *buf, size_t length); + 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 diff --git a/sound/firewire/fireworks/Makefile b/sound/firewire/fireworks/Makefile new file mode 100644 index 000000000..3386121b2 --- /dev/null +++ b/sound/firewire/fireworks/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +snd-fireworks-objs := fireworks_transaction.o fireworks_command.o \ + fireworks_stream.o fireworks_proc.o fireworks_midi.o \ + fireworks_pcm.o fireworks_hwdep.o fireworks.o +obj-$(CONFIG_SND_FIREWORKS) += snd-fireworks.o diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c new file mode 100644 index 000000000..b1cc013a3 --- /dev/null +++ b/sound/firewire/fireworks/fireworks.c @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * fireworks.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013-2014 Takashi Sakamoto + */ + +/* + * Fireworks is a board module which Echo Audio produced. This module consists + * of three chipsets: + * - Communication chipset for IEEE1394 PHY/Link and IEC 61883-1/6 + * - DSP or/and FPGA for signal processing + * - Flash Memory to store firmwares + */ + +#include "fireworks.h" + +MODULE_DESCRIPTION("Echo Fireworks driver"); +MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>"); +MODULE_LICENSE("GPL v2"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +unsigned int snd_efw_resp_buf_size = 1024; +bool snd_efw_resp_buf_debug = false; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "card index"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string"); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "enable Fireworks sound card"); +module_param_named(resp_buf_size, snd_efw_resp_buf_size, uint, 0444); +MODULE_PARM_DESC(resp_buf_size, + "response buffer size (max 4096, default 1024)"); +module_param_named(resp_buf_debug, snd_efw_resp_buf_debug, bool, 0444); +MODULE_PARM_DESC(resp_buf_debug, "store all responses to buffer"); + +static DEFINE_MUTEX(devices_mutex); +static DECLARE_BITMAP(devices_used, SNDRV_CARDS); + +#define VENDOR_LOUD 0x000ff2 +#define MODEL_MACKIE_400F 0x00400f +#define MODEL_MACKIE_1200F 0x01200f + +#define VENDOR_ECHO 0x001486 +#define MODEL_ECHO_AUDIOFIRE_12 0x00af12 +#define MODEL_ECHO_AUDIOFIRE_12HD 0x0af12d +#define MODEL_ECHO_AUDIOFIRE_12_APPLE 0x0af12a +/* This is applied for AudioFire8 (until 2009 July) */ +#define MODEL_ECHO_AUDIOFIRE_8 0x000af8 +#define MODEL_ECHO_AUDIOFIRE_2 0x000af2 +#define MODEL_ECHO_AUDIOFIRE_4 0x000af4 +/* AudioFire9 is applied for AudioFire8(since 2009 July) and AudioFirePre8 */ +#define MODEL_ECHO_AUDIOFIRE_9 0x000af9 +/* unknown as product */ +#define MODEL_ECHO_FIREWORKS_8 0x0000f8 +#define MODEL_ECHO_FIREWORKS_HDMI 0x00afd1 + +#define VENDOR_GIBSON 0x00075b +/* for Robot Interface Pack of Dark Fire, Dusk Tiger, Les Paul Standard 2010 */ +#define MODEL_GIBSON_RIP 0x00afb2 +/* unknown as product */ +#define MODEL_GIBSON_GOLDTOP 0x00afb9 + +/* part of hardware capability flags */ +#define FLAG_RESP_ADDR_CHANGABLE 0 + +static int +get_hardware_info(struct snd_efw *efw) +{ + struct fw_device *fw_dev = fw_parent_device(efw->unit); + struct snd_efw_hwinfo *hwinfo; + char version[12] = {0}; + int err; + + hwinfo = kzalloc(sizeof(struct snd_efw_hwinfo), GFP_KERNEL); + if (hwinfo == NULL) + return -ENOMEM; + + err = snd_efw_command_get_hwinfo(efw, hwinfo); + if (err < 0) + goto end; + + /* firmware version for communication chipset */ + snprintf(version, sizeof(version), "%u.%u", + (hwinfo->arm_version >> 24) & 0xff, + (hwinfo->arm_version >> 16) & 0xff); + efw->firmware_version = hwinfo->arm_version; + + strcpy(efw->card->driver, "Fireworks"); + strcpy(efw->card->shortname, hwinfo->model_name); + strcpy(efw->card->mixername, hwinfo->model_name); + snprintf(efw->card->longname, sizeof(efw->card->longname), + "%s %s v%s, GUID %08x%08x at %s, S%d", + hwinfo->vendor_name, hwinfo->model_name, version, + hwinfo->guid_hi, hwinfo->guid_lo, + dev_name(&efw->unit->device), 100 << fw_dev->max_speed); + + if (hwinfo->flags & BIT(FLAG_RESP_ADDR_CHANGABLE)) + efw->resp_addr_changable = true; + + efw->supported_sampling_rate = 0; + if ((hwinfo->min_sample_rate <= 22050) + && (22050 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_22050; + if ((hwinfo->min_sample_rate <= 32000) + && (32000 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_32000; + if ((hwinfo->min_sample_rate <= 44100) + && (44100 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_44100; + if ((hwinfo->min_sample_rate <= 48000) + && (48000 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_48000; + if ((hwinfo->min_sample_rate <= 88200) + && (88200 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_88200; + if ((hwinfo->min_sample_rate <= 96000) + && (96000 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_96000; + if ((hwinfo->min_sample_rate <= 176400) + && (176400 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_176400; + if ((hwinfo->min_sample_rate <= 192000) + && (192000 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_192000; + + /* the number of MIDI ports, not of MIDI conformant data channels */ + if (hwinfo->midi_out_ports > SND_EFW_MAX_MIDI_OUT_PORTS || + hwinfo->midi_in_ports > SND_EFW_MAX_MIDI_IN_PORTS) { + err = -EIO; + goto end; + } + efw->midi_out_ports = hwinfo->midi_out_ports; + efw->midi_in_ports = hwinfo->midi_in_ports; + + if (hwinfo->amdtp_tx_pcm_channels > AM824_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_tx_pcm_channels_2x > AM824_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_tx_pcm_channels_4x > AM824_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_rx_pcm_channels > AM824_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_rx_pcm_channels_2x > AM824_MAX_CHANNELS_FOR_PCM || + hwinfo->amdtp_rx_pcm_channels_4x > AM824_MAX_CHANNELS_FOR_PCM) { + err = -ENOSYS; + goto end; + } + efw->pcm_capture_channels[0] = hwinfo->amdtp_tx_pcm_channels; + efw->pcm_capture_channels[1] = hwinfo->amdtp_tx_pcm_channels_2x; + efw->pcm_capture_channels[2] = hwinfo->amdtp_tx_pcm_channels_4x; + efw->pcm_playback_channels[0] = hwinfo->amdtp_rx_pcm_channels; + efw->pcm_playback_channels[1] = hwinfo->amdtp_rx_pcm_channels_2x; + efw->pcm_playback_channels[2] = hwinfo->amdtp_rx_pcm_channels_4x; + + /* Hardware metering. */ + if (hwinfo->phys_in_grp_count > HWINFO_MAX_CAPS_GROUPS || + hwinfo->phys_out_grp_count > HWINFO_MAX_CAPS_GROUPS) { + err = -EIO; + goto end; + } + efw->phys_in = hwinfo->phys_in; + efw->phys_out = hwinfo->phys_out; + efw->phys_in_grp_count = hwinfo->phys_in_grp_count; + efw->phys_out_grp_count = hwinfo->phys_out_grp_count; + memcpy(&efw->phys_in_grps, hwinfo->phys_in_grps, + sizeof(struct snd_efw_phys_grp) * hwinfo->phys_in_grp_count); + memcpy(&efw->phys_out_grps, hwinfo->phys_out_grps, + sizeof(struct snd_efw_phys_grp) * hwinfo->phys_out_grp_count); + + /* AudioFire8 (since 2009) and AudioFirePre8 */ + if (hwinfo->type == MODEL_ECHO_AUDIOFIRE_9) + efw->is_af9 = true; + /* These models uses the same firmware. */ + if (hwinfo->type == MODEL_ECHO_AUDIOFIRE_2 || + hwinfo->type == MODEL_ECHO_AUDIOFIRE_4 || + hwinfo->type == MODEL_ECHO_AUDIOFIRE_9 || + hwinfo->type == MODEL_GIBSON_RIP || + hwinfo->type == MODEL_GIBSON_GOLDTOP) + efw->is_fireworks3 = true; +end: + kfree(hwinfo); + return err; +} + +static void +efw_card_free(struct snd_card *card) +{ + struct snd_efw *efw = card->private_data; + + mutex_lock(&devices_mutex); + clear_bit(efw->card_index, devices_used); + mutex_unlock(&devices_mutex); + + snd_efw_stream_destroy_duplex(efw); + snd_efw_transaction_remove_instance(efw); +} + +static void +do_registration(struct work_struct *work) +{ + struct snd_efw *efw = container_of(work, struct snd_efw, dwork.work); + unsigned int card_index; + int err; + + if (efw->registered) + return; + + /* check registered cards */ + mutex_lock(&devices_mutex); + for (card_index = 0; card_index < SNDRV_CARDS; ++card_index) { + if (!test_bit(card_index, devices_used) && enable[card_index]) + break; + } + if (card_index >= SNDRV_CARDS) { + mutex_unlock(&devices_mutex); + return; + } + + err = snd_card_new(&efw->unit->device, index[card_index], + id[card_index], THIS_MODULE, 0, &efw->card); + if (err < 0) { + mutex_unlock(&devices_mutex); + return; + } + set_bit(card_index, devices_used); + mutex_unlock(&devices_mutex); + + efw->card->private_free = efw_card_free; + efw->card->private_data = efw; + + /* prepare response buffer */ + snd_efw_resp_buf_size = clamp(snd_efw_resp_buf_size, + SND_EFW_RESPONSE_MAXIMUM_BYTES, 4096U); + efw->resp_buf = devm_kzalloc(&efw->card->card_dev, + snd_efw_resp_buf_size, GFP_KERNEL); + if (!efw->resp_buf) { + err = -ENOMEM; + goto error; + } + efw->pull_ptr = efw->push_ptr = efw->resp_buf; + snd_efw_transaction_add_instance(efw); + + err = get_hardware_info(efw); + if (err < 0) + goto error; + + err = snd_efw_stream_init_duplex(efw); + if (err < 0) + goto error; + + snd_efw_proc_init(efw); + + if (efw->midi_out_ports || efw->midi_in_ports) { + err = snd_efw_create_midi_devices(efw); + if (err < 0) + goto error; + } + + err = snd_efw_create_pcm_devices(efw); + if (err < 0) + goto error; + + err = snd_efw_create_hwdep_device(efw); + if (err < 0) + goto error; + + err = snd_card_register(efw->card); + if (err < 0) + goto error; + + efw->registered = true; + + return; +error: + snd_card_free(efw->card); + dev_info(&efw->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int +efw_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry) +{ + struct snd_efw *efw; + + efw = devm_kzalloc(&unit->device, sizeof(struct snd_efw), GFP_KERNEL); + if (efw == NULL) + return -ENOMEM; + efw->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, efw); + + mutex_init(&efw->mutex); + spin_lock_init(&efw->lock); + init_waitqueue_head(&efw->hwdep_wait); + + /* Allocate and register this sound card later. */ + INIT_DEFERRABLE_WORK(&efw->dwork, do_registration); + snd_fw_schedule_registration(unit, &efw->dwork); + + return 0; +} + +static void efw_update(struct fw_unit *unit) +{ + struct snd_efw *efw = dev_get_drvdata(&unit->device); + + /* Postpone a workqueue for deferred registration. */ + if (!efw->registered) + snd_fw_schedule_registration(unit, &efw->dwork); + + snd_efw_transaction_bus_reset(efw->unit); + + /* + * After registration, userspace can start packet streaming, then this + * code block works fine. + */ + if (efw->registered) { + mutex_lock(&efw->mutex); + snd_efw_stream_update_duplex(efw); + mutex_unlock(&efw->mutex); + } +} + +static void efw_remove(struct fw_unit *unit) +{ + struct snd_efw *efw = dev_get_drvdata(&unit->device); + + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_delayed_work_sync(&efw->dwork); + + if (efw->registered) { + // Block till all of ALSA character devices are released. + snd_card_free(efw->card); + } + + mutex_destroy(&efw->mutex); + fw_unit_put(efw->unit); +} + +static const struct ieee1394_device_id efw_id_table[] = { + SND_EFW_DEV_ENTRY(VENDOR_LOUD, MODEL_MACKIE_400F), + SND_EFW_DEV_ENTRY(VENDOR_LOUD, MODEL_MACKIE_1200F), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_8), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_12), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_12HD), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_12_APPLE), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_2), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_4), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_9), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_FIREWORKS_8), + SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_FIREWORKS_HDMI), + SND_EFW_DEV_ENTRY(VENDOR_GIBSON, MODEL_GIBSON_RIP), + SND_EFW_DEV_ENTRY(VENDOR_GIBSON, MODEL_GIBSON_GOLDTOP), + {} +}; +MODULE_DEVICE_TABLE(ieee1394, efw_id_table); + +static struct fw_driver efw_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + }, + .probe = efw_probe, + .update = efw_update, + .remove = efw_remove, + .id_table = efw_id_table, +}; + +static int __init snd_efw_init(void) +{ + int err; + + err = snd_efw_transaction_register(); + if (err < 0) + goto end; + + err = driver_register(&efw_driver.driver); + if (err < 0) + snd_efw_transaction_unregister(); + +end: + return err; +} + +static void __exit snd_efw_exit(void) +{ + snd_efw_transaction_unregister(); + driver_unregister(&efw_driver.driver); +} + +module_init(snd_efw_init); +module_exit(snd_efw_exit); diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h new file mode 100644 index 000000000..654e28a66 --- /dev/null +++ b/sound/firewire/fireworks/fireworks.h @@ -0,0 +1,238 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * fireworks.h - a part of driver for Fireworks based devices + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013-2014 Takashi Sakamoto + */ +#ifndef SOUND_FIREWORKS_H_INCLUDED +#define SOUND_FIREWORKS_H_INCLUDED + +#include <linux/compat.h> +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/sched/signal.h> + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/info.h> +#include <sound/rawmidi.h> +#include <sound/pcm_params.h> +#include <sound/firewire.h> +#include <sound/hwdep.h> + +#include "../packets-buffer.h" +#include "../iso-resources.h" +#include "../amdtp-am824.h" +#include "../cmp.h" +#include "../lib.h" + +#define SND_EFW_MAX_MIDI_OUT_PORTS 2 +#define SND_EFW_MAX_MIDI_IN_PORTS 2 + +#define SND_EFW_MULTIPLIER_MODES 3 +#define HWINFO_NAME_SIZE_BYTES 32 +#define HWINFO_MAX_CAPS_GROUPS 8 + +/* + * This should be greater than maximum bytes for EFW response content. + * Currently response against command for isochronous channel mapping is + * confirmed to be the maximum one. But for flexibility, use maximum data + * payload for asynchronous primary packets at S100 (Cable base rate) in + * IEEE Std 1394-1995. + */ +#define SND_EFW_RESPONSE_MAXIMUM_BYTES 0x200U + +extern unsigned int snd_efw_resp_buf_size; +extern bool snd_efw_resp_buf_debug; + +struct snd_efw_phys_grp { + u8 type; /* see enum snd_efw_grp_type */ + u8 count; +} __packed; + +struct snd_efw { + struct snd_card *card; + struct fw_unit *unit; + int card_index; + + struct mutex mutex; + spinlock_t lock; + + bool registered; + struct delayed_work dwork; + + /* for transaction */ + u32 seqnum; + bool resp_addr_changable; + + /* for quirks */ + bool is_af9; + bool is_fireworks3; + u32 firmware_version; + + unsigned int midi_in_ports; + unsigned int midi_out_ports; + + unsigned int supported_sampling_rate; + unsigned int pcm_capture_channels[SND_EFW_MULTIPLIER_MODES]; + unsigned int pcm_playback_channels[SND_EFW_MULTIPLIER_MODES]; + + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + struct cmp_connection out_conn; + struct cmp_connection in_conn; + unsigned int substreams_counter; + + /* hardware metering parameters */ + unsigned int phys_out; + unsigned int phys_in; + unsigned int phys_out_grp_count; + unsigned int phys_in_grp_count; + struct snd_efw_phys_grp phys_out_grps[HWINFO_MAX_CAPS_GROUPS]; + struct snd_efw_phys_grp phys_in_grps[HWINFO_MAX_CAPS_GROUPS]; + + /* for uapi */ + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; + + /* response queue */ + u8 *resp_buf; + u8 *pull_ptr; + u8 *push_ptr; + + struct amdtp_domain domain; +}; + +int snd_efw_transaction_cmd(struct fw_unit *unit, + const void *cmd, unsigned int size); +int snd_efw_transaction_run(struct fw_unit *unit, + const void *cmd, unsigned int cmd_size, + void *resp, unsigned int resp_size); +int snd_efw_transaction_register(void); +void snd_efw_transaction_unregister(void); +void snd_efw_transaction_bus_reset(struct fw_unit *unit); +void snd_efw_transaction_add_instance(struct snd_efw *efw); +void snd_efw_transaction_remove_instance(struct snd_efw *efw); + +struct snd_efw_hwinfo { + u32 flags; + u32 guid_hi; + u32 guid_lo; + u32 type; + u32 version; + char vendor_name[HWINFO_NAME_SIZE_BYTES]; + char model_name[HWINFO_NAME_SIZE_BYTES]; + u32 supported_clocks; + u32 amdtp_rx_pcm_channels; + u32 amdtp_tx_pcm_channels; + u32 phys_out; + u32 phys_in; + u32 phys_out_grp_count; + struct snd_efw_phys_grp phys_out_grps[HWINFO_MAX_CAPS_GROUPS]; + u32 phys_in_grp_count; + struct snd_efw_phys_grp phys_in_grps[HWINFO_MAX_CAPS_GROUPS]; + u32 midi_out_ports; + u32 midi_in_ports; + u32 max_sample_rate; + u32 min_sample_rate; + u32 dsp_version; + u32 arm_version; + u32 mixer_playback_channels; + u32 mixer_capture_channels; + u32 fpga_version; + u32 amdtp_rx_pcm_channels_2x; + u32 amdtp_tx_pcm_channels_2x; + u32 amdtp_rx_pcm_channels_4x; + u32 amdtp_tx_pcm_channels_4x; + u32 reserved[16]; +} __packed; +enum snd_efw_grp_type { + SND_EFW_CH_TYPE_ANALOG = 0, + SND_EFW_CH_TYPE_SPDIF = 1, + SND_EFW_CH_TYPE_ADAT = 2, + SND_EFW_CH_TYPE_SPDIF_OR_ADAT = 3, + SND_EFW_CH_TYPE_ANALOG_MIRRORING = 4, + SND_EFW_CH_TYPE_HEADPHONES = 5, + SND_EFW_CH_TYPE_I2S = 6, + SND_EFW_CH_TYPE_GUITAR = 7, + SND_EFW_CH_TYPE_PIEZO_GUITAR = 8, + SND_EFW_CH_TYPE_GUITAR_STRING = 9, + SND_EFW_CH_TYPE_DUMMY +}; +struct snd_efw_phys_meters { + u32 status; /* guitar state/midi signal/clock input detect */ + u32 reserved0; + u32 reserved1; + u32 reserved2; + u32 reserved3; + u32 out_meters; + u32 in_meters; + u32 reserved4; + u32 reserved5; + u32 values[]; +} __packed; +enum snd_efw_clock_source { + SND_EFW_CLOCK_SOURCE_INTERNAL = 0, + SND_EFW_CLOCK_SOURCE_SYTMATCH = 1, + SND_EFW_CLOCK_SOURCE_WORDCLOCK = 2, + SND_EFW_CLOCK_SOURCE_SPDIF = 3, + SND_EFW_CLOCK_SOURCE_ADAT_1 = 4, + SND_EFW_CLOCK_SOURCE_ADAT_2 = 5, + SND_EFW_CLOCK_SOURCE_CONTINUOUS = 6 /* internal variable clock */ +}; +enum snd_efw_transport_mode { + SND_EFW_TRANSPORT_MODE_WINDOWS = 0, + SND_EFW_TRANSPORT_MODE_IEC61883 = 1, +}; +int snd_efw_command_set_resp_addr(struct snd_efw *efw, + u16 addr_high, u32 addr_low); +int snd_efw_command_set_tx_mode(struct snd_efw *efw, + enum snd_efw_transport_mode mode); +int snd_efw_command_get_hwinfo(struct snd_efw *efw, + struct snd_efw_hwinfo *hwinfo); +int snd_efw_command_get_phys_meters(struct snd_efw *efw, + struct snd_efw_phys_meters *meters, + unsigned int len); +int snd_efw_command_get_clock_source(struct snd_efw *efw, + enum snd_efw_clock_source *source); +int snd_efw_command_get_sampling_rate(struct snd_efw *efw, unsigned int *rate); +int snd_efw_command_set_sampling_rate(struct snd_efw *efw, unsigned int rate); + +int snd_efw_stream_init_duplex(struct snd_efw *efw); +int snd_efw_stream_reserve_duplex(struct snd_efw *efw, unsigned int rate, + unsigned int frames_per_period, + unsigned int frames_per_buffer); +int snd_efw_stream_start_duplex(struct snd_efw *efw); +void snd_efw_stream_stop_duplex(struct snd_efw *efw); +void snd_efw_stream_update_duplex(struct snd_efw *efw); +void snd_efw_stream_destroy_duplex(struct snd_efw *efw); +void snd_efw_stream_lock_changed(struct snd_efw *efw); +int snd_efw_stream_lock_try(struct snd_efw *efw); +void snd_efw_stream_lock_release(struct snd_efw *efw); + +void snd_efw_proc_init(struct snd_efw *efw); + +int snd_efw_create_midi_devices(struct snd_efw *efw); + +int snd_efw_create_pcm_devices(struct snd_efw *efw); +int snd_efw_get_multiplier_mode(unsigned int sampling_rate, unsigned int *mode); + +int snd_efw_create_hwdep_device(struct snd_efw *efw); + +#define SND_EFW_DEV_ENTRY(vendor, model) \ +{ \ + .match_flags = IEEE1394_MATCH_VENDOR_ID | \ + IEEE1394_MATCH_MODEL_ID, \ + .vendor_id = vendor,\ + .model_id = model \ +} + +#endif diff --git a/sound/firewire/fireworks/fireworks_command.c b/sound/firewire/fireworks/fireworks_command.c new file mode 100644 index 000000000..7e255fc2c --- /dev/null +++ b/sound/firewire/fireworks/fireworks_command.c @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * fireworks_command.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + */ + +#include "./fireworks.h" + +/* + * This driver uses transaction version 1 or later to use extended hardware + * information. Then too old devices are not available. + * + * Each commands are not required to have continuous sequence numbers. This + * number is just used to match command and response. + * + * This module support a part of commands. Please see FFADO if you want to see + * whole commands. But there are some commands which FFADO don't implement. + * + * Fireworks also supports AV/C general commands and AV/C Stream Format + * Information commands. But this module don't use them. + */ + +#define KERNEL_SEQNUM_MIN (SND_EFW_TRANSACTION_USER_SEQNUM_MAX + 2) +#define KERNEL_SEQNUM_MAX ((u32)~0) + +/* for clock source and sampling rate */ +struct efc_clock { + u32 source; + u32 sampling_rate; + u32 index; +}; + +/* command categories */ +enum efc_category { + EFC_CAT_HWINFO = 0, + EFC_CAT_TRANSPORT = 2, + EFC_CAT_HWCTL = 3, +}; + +/* hardware info category commands */ +enum efc_cmd_hwinfo { + EFC_CMD_HWINFO_GET_CAPS = 0, + EFC_CMD_HWINFO_GET_POLLED = 1, + EFC_CMD_HWINFO_SET_RESP_ADDR = 2 +}; + +enum efc_cmd_transport { + EFC_CMD_TRANSPORT_SET_TX_MODE = 0 +}; + +/* hardware control category commands */ +enum efc_cmd_hwctl { + EFC_CMD_HWCTL_SET_CLOCK = 0, + EFC_CMD_HWCTL_GET_CLOCK = 1, + EFC_CMD_HWCTL_IDENTIFY = 5 +}; + +/* return values in response */ +enum efr_status { + EFR_STATUS_OK = 0, + EFR_STATUS_BAD = 1, + EFR_STATUS_BAD_COMMAND = 2, + EFR_STATUS_COMM_ERR = 3, + EFR_STATUS_BAD_QUAD_COUNT = 4, + EFR_STATUS_UNSUPPORTED = 5, + EFR_STATUS_1394_TIMEOUT = 6, + EFR_STATUS_DSP_TIMEOUT = 7, + EFR_STATUS_BAD_RATE = 8, + EFR_STATUS_BAD_CLOCK = 9, + EFR_STATUS_BAD_CHANNEL = 10, + EFR_STATUS_BAD_PAN = 11, + EFR_STATUS_FLASH_BUSY = 12, + EFR_STATUS_BAD_MIRROR = 13, + EFR_STATUS_BAD_LED = 14, + EFR_STATUS_BAD_PARAMETER = 15, + EFR_STATUS_INCOMPLETE = 0x80000000 +}; + +static const char *const efr_status_names[] = { + [EFR_STATUS_OK] = "OK", + [EFR_STATUS_BAD] = "bad", + [EFR_STATUS_BAD_COMMAND] = "bad command", + [EFR_STATUS_COMM_ERR] = "comm err", + [EFR_STATUS_BAD_QUAD_COUNT] = "bad quad count", + [EFR_STATUS_UNSUPPORTED] = "unsupported", + [EFR_STATUS_1394_TIMEOUT] = "1394 timeout", + [EFR_STATUS_DSP_TIMEOUT] = "DSP timeout", + [EFR_STATUS_BAD_RATE] = "bad rate", + [EFR_STATUS_BAD_CLOCK] = "bad clock", + [EFR_STATUS_BAD_CHANNEL] = "bad channel", + [EFR_STATUS_BAD_PAN] = "bad pan", + [EFR_STATUS_FLASH_BUSY] = "flash busy", + [EFR_STATUS_BAD_MIRROR] = "bad mirror", + [EFR_STATUS_BAD_LED] = "bad LED", + [EFR_STATUS_BAD_PARAMETER] = "bad parameter", + [EFR_STATUS_BAD_PARAMETER + 1] = "incomplete" +}; + +static int +efw_transaction(struct snd_efw *efw, unsigned int category, + unsigned int command, + const __be32 *params, unsigned int param_bytes, + const __be32 *resp, unsigned int resp_bytes) +{ + struct snd_efw_transaction *header; + __be32 *buf; + u32 seqnum; + unsigned int buf_bytes, cmd_bytes; + int err; + + /* calculate buffer size*/ + buf_bytes = sizeof(struct snd_efw_transaction) + + max(param_bytes, resp_bytes); + + /* keep buffer */ + buf = kzalloc(buf_bytes, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + /* to keep consistency of sequence number */ + spin_lock(&efw->lock); + if ((efw->seqnum < KERNEL_SEQNUM_MIN) || + (efw->seqnum >= KERNEL_SEQNUM_MAX - 2)) + efw->seqnum = KERNEL_SEQNUM_MIN; + else + efw->seqnum += 2; + seqnum = efw->seqnum; + spin_unlock(&efw->lock); + + /* fill transaction header fields */ + cmd_bytes = sizeof(struct snd_efw_transaction) + param_bytes; + header = (struct snd_efw_transaction *)buf; + header->length = cpu_to_be32(cmd_bytes / sizeof(__be32)); + header->version = cpu_to_be32(1); + header->seqnum = cpu_to_be32(seqnum); + header->category = cpu_to_be32(category); + header->command = cpu_to_be32(command); + header->status = 0; + + /* fill transaction command parameters */ + memcpy(header->params, params, param_bytes); + + err = snd_efw_transaction_run(efw->unit, buf, cmd_bytes, + buf, buf_bytes); + if (err < 0) + goto end; + + /* check transaction header fields */ + if ((be32_to_cpu(header->version) < 1) || + (be32_to_cpu(header->category) != category) || + (be32_to_cpu(header->command) != command) || + (be32_to_cpu(header->status) != EFR_STATUS_OK)) { + dev_err(&efw->unit->device, "EFW command failed [%u/%u]: %s\n", + be32_to_cpu(header->category), + be32_to_cpu(header->command), + efr_status_names[be32_to_cpu(header->status)]); + err = -EIO; + goto end; + } + + if (resp == NULL) + goto end; + + /* fill transaction response parameters */ + memset((void *)resp, 0, resp_bytes); + resp_bytes = min_t(unsigned int, resp_bytes, + be32_to_cpu(header->length) * sizeof(__be32) - + sizeof(struct snd_efw_transaction)); + memcpy((void *)resp, &buf[6], resp_bytes); +end: + kfree(buf); + return err; +} + +/* + * The address in host system for transaction response is changable when the + * device supports. struct hwinfo.flags includes its flag. The default is + * MEMORY_SPACE_EFW_RESPONSE. + */ +int snd_efw_command_set_resp_addr(struct snd_efw *efw, + u16 addr_high, u32 addr_low) +{ + __be32 addr[2]; + + addr[0] = cpu_to_be32(addr_high); + addr[1] = cpu_to_be32(addr_low); + + if (!efw->resp_addr_changable) + return -ENOSYS; + + return efw_transaction(efw, EFC_CAT_HWCTL, + EFC_CMD_HWINFO_SET_RESP_ADDR, + addr, sizeof(addr), NULL, 0); +} + +/* + * This is for timestamp processing. In Windows mode, all 32bit fields of second + * CIP header in AMDTP transmit packet is used for 'presentation timestamp'. In + * 'no data' packet the value of this field is 0x90ffffff. + */ +int snd_efw_command_set_tx_mode(struct snd_efw *efw, + enum snd_efw_transport_mode mode) +{ + __be32 param = cpu_to_be32(mode); + return efw_transaction(efw, EFC_CAT_TRANSPORT, + EFC_CMD_TRANSPORT_SET_TX_MODE, + ¶m, sizeof(param), NULL, 0); +} + +int snd_efw_command_get_hwinfo(struct snd_efw *efw, + struct snd_efw_hwinfo *hwinfo) +{ + int err; + + err = efw_transaction(efw, EFC_CAT_HWINFO, + EFC_CMD_HWINFO_GET_CAPS, + NULL, 0, (__be32 *)hwinfo, sizeof(*hwinfo)); + if (err < 0) + goto end; + + be32_to_cpus(&hwinfo->flags); + be32_to_cpus(&hwinfo->guid_hi); + be32_to_cpus(&hwinfo->guid_lo); + be32_to_cpus(&hwinfo->type); + be32_to_cpus(&hwinfo->version); + be32_to_cpus(&hwinfo->supported_clocks); + be32_to_cpus(&hwinfo->amdtp_rx_pcm_channels); + be32_to_cpus(&hwinfo->amdtp_tx_pcm_channels); + be32_to_cpus(&hwinfo->phys_out); + be32_to_cpus(&hwinfo->phys_in); + be32_to_cpus(&hwinfo->phys_out_grp_count); + be32_to_cpus(&hwinfo->phys_in_grp_count); + be32_to_cpus(&hwinfo->midi_out_ports); + be32_to_cpus(&hwinfo->midi_in_ports); + be32_to_cpus(&hwinfo->max_sample_rate); + be32_to_cpus(&hwinfo->min_sample_rate); + be32_to_cpus(&hwinfo->dsp_version); + be32_to_cpus(&hwinfo->arm_version); + be32_to_cpus(&hwinfo->mixer_playback_channels); + be32_to_cpus(&hwinfo->mixer_capture_channels); + be32_to_cpus(&hwinfo->fpga_version); + be32_to_cpus(&hwinfo->amdtp_rx_pcm_channels_2x); + be32_to_cpus(&hwinfo->amdtp_tx_pcm_channels_2x); + be32_to_cpus(&hwinfo->amdtp_rx_pcm_channels_4x); + be32_to_cpus(&hwinfo->amdtp_tx_pcm_channels_4x); + + /* ensure terminated */ + hwinfo->vendor_name[HWINFO_NAME_SIZE_BYTES - 1] = '\0'; + hwinfo->model_name[HWINFO_NAME_SIZE_BYTES - 1] = '\0'; +end: + return err; +} + +int snd_efw_command_get_phys_meters(struct snd_efw *efw, + struct snd_efw_phys_meters *meters, + unsigned int len) +{ + u32 *buf = (u32 *)meters; + unsigned int i; + int err; + + err = efw_transaction(efw, EFC_CAT_HWINFO, + EFC_CMD_HWINFO_GET_POLLED, + NULL, 0, (__be32 *)meters, len); + if (err >= 0) + for (i = 0; i < len / sizeof(u32); i++) + be32_to_cpus(&buf[i]); + + return err; +} + +static int +command_get_clock(struct snd_efw *efw, struct efc_clock *clock) +{ + int err; + + err = efw_transaction(efw, EFC_CAT_HWCTL, + EFC_CMD_HWCTL_GET_CLOCK, + NULL, 0, + (__be32 *)clock, sizeof(struct efc_clock)); + if (err >= 0) { + be32_to_cpus(&clock->source); + be32_to_cpus(&clock->sampling_rate); + be32_to_cpus(&clock->index); + } + + return err; +} + +/* give UINT_MAX if set nothing */ +static int +command_set_clock(struct snd_efw *efw, + unsigned int source, unsigned int rate) +{ + struct efc_clock clock = {0}; + int err; + + /* check arguments */ + if ((source == UINT_MAX) && (rate == UINT_MAX)) { + err = -EINVAL; + goto end; + } + + /* get current status */ + err = command_get_clock(efw, &clock); + if (err < 0) + goto end; + + /* no need */ + if ((clock.source == source) && (clock.sampling_rate == rate)) + goto end; + + /* set params */ + if ((source != UINT_MAX) && (clock.source != source)) + clock.source = source; + if ((rate != UINT_MAX) && (clock.sampling_rate != rate)) + clock.sampling_rate = rate; + clock.index = 0; + + cpu_to_be32s(&clock.source); + cpu_to_be32s(&clock.sampling_rate); + cpu_to_be32s(&clock.index); + + err = efw_transaction(efw, EFC_CAT_HWCTL, + EFC_CMD_HWCTL_SET_CLOCK, + (__be32 *)&clock, sizeof(struct efc_clock), + NULL, 0); + if (err < 0) + goto end; + + /* + * With firmware version 5.8, just after changing clock state, these + * parameters are not immediately retrieved by get command. In my + * trial, there needs to be 100msec to get changed parameters. + */ + msleep(150); +end: + return err; +} + +int snd_efw_command_get_clock_source(struct snd_efw *efw, + enum snd_efw_clock_source *source) +{ + int err; + struct efc_clock clock = {0}; + + err = command_get_clock(efw, &clock); + if (err >= 0) + *source = clock.source; + + return err; +} + +int snd_efw_command_get_sampling_rate(struct snd_efw *efw, unsigned int *rate) +{ + int err; + struct efc_clock clock = {0}; + + err = command_get_clock(efw, &clock); + if (err >= 0) + *rate = clock.sampling_rate; + + return err; +} + +int snd_efw_command_set_sampling_rate(struct snd_efw *efw, unsigned int rate) +{ + return command_set_clock(efw, UINT_MAX, rate); +} + diff --git a/sound/firewire/fireworks/fireworks_hwdep.c b/sound/firewire/fireworks/fireworks_hwdep.c new file mode 100644 index 000000000..c739173c6 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_hwdep.c @@ -0,0 +1,330 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * fireworks_hwdep.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + */ + +/* + * This codes have five functionalities. + * + * 1.get information about firewire node + * 2.get notification about starting/stopping stream + * 3.lock/unlock streaming + * 4.transmit command of EFW transaction + * 5.receive response of EFW transaction + * + */ + +#include "fireworks.h" + +static long +hwdep_read_resp_buf(struct snd_efw *efw, char __user *buf, long remained, + loff_t *offset) +{ + unsigned int length, till_end, type; + struct snd_efw_transaction *t; + u8 *pull_ptr; + long count = 0; + + if (remained < sizeof(type) + sizeof(struct snd_efw_transaction)) + return -ENOSPC; + + /* data type is SNDRV_FIREWIRE_EVENT_EFW_RESPONSE */ + type = SNDRV_FIREWIRE_EVENT_EFW_RESPONSE; + if (copy_to_user(buf, &type, sizeof(type))) + return -EFAULT; + count += sizeof(type); + remained -= sizeof(type); + buf += sizeof(type); + + /* write into buffer as many responses as possible */ + spin_lock_irq(&efw->lock); + + /* + * When another task reaches here during this task's access to user + * space, it picks up current position in buffer and can read the same + * series of responses. + */ + pull_ptr = efw->pull_ptr; + + while (efw->push_ptr != pull_ptr) { + t = (struct snd_efw_transaction *)(pull_ptr); + length = be32_to_cpu(t->length) * sizeof(__be32); + + /* confirm enough space for this response */ + if (remained < length) + break; + + /* copy from ring buffer to user buffer */ + while (length > 0) { + till_end = snd_efw_resp_buf_size - + (unsigned int)(pull_ptr - efw->resp_buf); + till_end = min_t(unsigned int, length, till_end); + + spin_unlock_irq(&efw->lock); + + if (copy_to_user(buf, pull_ptr, till_end)) + return -EFAULT; + + spin_lock_irq(&efw->lock); + + pull_ptr += till_end; + if (pull_ptr >= efw->resp_buf + snd_efw_resp_buf_size) + pull_ptr -= snd_efw_resp_buf_size; + + length -= till_end; + buf += till_end; + count += till_end; + remained -= till_end; + } + } + + /* + * All of tasks can read from the buffer nearly simultaneously, but the + * last position for each task is different depending on the length of + * given buffer. Here, for simplicity, a position of buffer is set by + * the latest task. It's better for a listening application to allow one + * thread to read from the buffer. Unless, each task can read different + * sequence of responses depending on variation of buffer length. + */ + efw->pull_ptr = pull_ptr; + + spin_unlock_irq(&efw->lock); + + return count; +} + +static long +hwdep_read_locked(struct snd_efw *efw, char __user *buf, long count, + loff_t *offset) +{ + union snd_firewire_event event = { + .lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS, + }; + + spin_lock_irq(&efw->lock); + + event.lock_status.status = (efw->dev_lock_count > 0); + efw->dev_lock_changed = false; + + spin_unlock_irq(&efw->lock); + + count = min_t(long, count, sizeof(event.lock_status)); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static long +hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_efw *efw = hwdep->private_data; + DEFINE_WAIT(wait); + bool dev_lock_changed; + bool queued; + + spin_lock_irq(&efw->lock); + + dev_lock_changed = efw->dev_lock_changed; + queued = efw->push_ptr != efw->pull_ptr; + + while (!dev_lock_changed && !queued) { + prepare_to_wait(&efw->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&efw->lock); + schedule(); + finish_wait(&efw->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&efw->lock); + dev_lock_changed = efw->dev_lock_changed; + queued = efw->push_ptr != efw->pull_ptr; + } + + spin_unlock_irq(&efw->lock); + + if (dev_lock_changed) + count = hwdep_read_locked(efw, buf, count, offset); + else if (queued) + count = hwdep_read_resp_buf(efw, buf, count, offset); + + return count; +} + +static long +hwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count, + loff_t *offset) +{ + struct snd_efw *efw = hwdep->private_data; + u32 seqnum; + u8 *buf; + + if (count < sizeof(struct snd_efw_transaction) || + SND_EFW_RESPONSE_MAXIMUM_BYTES < count) + return -EINVAL; + + buf = memdup_user(data, count); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + /* check seqnum is not for kernel-land */ + seqnum = be32_to_cpu(((struct snd_efw_transaction *)buf)->seqnum); + if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX) { + count = -EINVAL; + goto end; + } + + if (snd_efw_transaction_cmd(efw->unit, buf, count) < 0) + count = -EIO; +end: + kfree(buf); + return count; +} + +static __poll_t +hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait) +{ + struct snd_efw *efw = hwdep->private_data; + __poll_t events; + + poll_wait(file, &efw->hwdep_wait, wait); + + spin_lock_irq(&efw->lock); + if (efw->dev_lock_changed || efw->pull_ptr != efw->push_ptr) + events = EPOLLIN | EPOLLRDNORM; + else + events = 0; + spin_unlock_irq(&efw->lock); + + return events | EPOLLOUT; +} + +static int +hwdep_get_info(struct snd_efw *efw, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(efw->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_FIREWORKS; + 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]); + strlcpy(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_efw *efw) +{ + int err; + + spin_lock_irq(&efw->lock); + + if (efw->dev_lock_count == 0) { + efw->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&efw->lock); + + return err; +} + +static int +hwdep_unlock(struct snd_efw *efw) +{ + int err; + + spin_lock_irq(&efw->lock); + + if (efw->dev_lock_count == -1) { + efw->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&efw->lock); + + return err; +} + +static int +hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_efw *efw = hwdep->private_data; + + spin_lock_irq(&efw->lock); + if (efw->dev_lock_count == -1) + efw->dev_lock_count = 0; + spin_unlock_irq(&efw->lock); + + return 0; +} + +static int +hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_efw *efw = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(efw, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(efw); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(efw); + 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_efw_create_hwdep_device(struct snd_efw *efw) +{ + static const struct snd_hwdep_ops ops = { + .read = hwdep_read, + .write = hwdep_write, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(efw->card, "Fireworks", 0, &hwdep); + if (err < 0) + goto end; + strcpy(hwdep->name, "Fireworks"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREWORKS; + hwdep->ops = ops; + hwdep->private_data = efw; + hwdep->exclusive = true; +end: + return err; +} + diff --git a/sound/firewire/fireworks/fireworks_midi.c b/sound/firewire/fireworks/fireworks_midi.c new file mode 100644 index 000000000..84621e356 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_midi.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * fireworks_midi.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013-2014 Takashi Sakamoto + */ +#include "fireworks.h" + +static int midi_open(struct snd_rawmidi_substream *substream) +{ + struct snd_efw *efw = substream->rmidi->private_data; + int err; + + err = snd_efw_stream_lock_try(efw); + if (err < 0) + goto end; + + mutex_lock(&efw->mutex); + err = snd_efw_stream_reserve_duplex(efw, 0, 0, 0); + if (err >= 0) { + ++efw->substreams_counter; + err = snd_efw_stream_start_duplex(efw); + if (err < 0) + --efw->substreams_counter; + } + mutex_unlock(&efw->mutex); + if (err < 0) + snd_efw_stream_lock_release(efw); +end: + return err; +} + +static int midi_close(struct snd_rawmidi_substream *substream) +{ + struct snd_efw *efw = substream->rmidi->private_data; + + mutex_lock(&efw->mutex); + --efw->substreams_counter; + snd_efw_stream_stop_duplex(efw); + mutex_unlock(&efw->mutex); + + snd_efw_stream_lock_release(efw); + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_efw *efw = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&efw->lock, flags); + + if (up) + amdtp_am824_midi_trigger(&efw->tx_stream, + substrm->number, substrm); + else + amdtp_am824_midi_trigger(&efw->tx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&efw->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_efw *efw = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&efw->lock, flags); + + if (up) + amdtp_am824_midi_trigger(&efw->rx_stream, + substrm->number, substrm); + else + amdtp_am824_midi_trigger(&efw->rx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&efw->lock, flags); +} + +static void set_midi_substream_names(struct snd_efw *efw, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", efw->card->shortname, subs->number + 1); + } +} + +int snd_efw_create_midi_devices(struct snd_efw *efw) +{ + static const struct snd_rawmidi_ops capture_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_capture_trigger, + }; + static const struct snd_rawmidi_ops playback_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_playback_trigger, + }; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + /* create midi ports */ + err = snd_rawmidi_new(efw->card, efw->card->driver, 0, + efw->midi_out_ports, efw->midi_in_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", efw->card->shortname); + rmidi->private_data = efw; + + if (efw->midi_in_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &capture_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + + set_midi_substream_names(efw, str); + } + + if (efw->midi_out_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &playback_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + set_midi_substream_names(efw, str); + } + + if ((efw->midi_out_ports > 0) && (efw->midi_in_ports > 0)) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/fireworks/fireworks_pcm.c b/sound/firewire/fireworks/fireworks_pcm.c new file mode 100644 index 000000000..a0d5db1d8 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_pcm.c @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * fireworks_pcm.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013-2014 Takashi Sakamoto + */ +#include "./fireworks.h" + +/* + * NOTE: + * Fireworks changes its AMDTP channels for PCM data according to its sampling + * rate. There are three modes. Here _XX is either _rx or _tx. + * 0: 32.0- 48.0 kHz then snd_efw_hwinfo.amdtp_XX_pcm_channels applied + * 1: 88.2- 96.0 kHz then snd_efw_hwinfo.amdtp_XX_pcm_channels_2x applied + * 2: 176.4-192.0 kHz then snd_efw_hwinfo.amdtp_XX_pcm_channels_4x applied + * + * The number of PCM channels for analog input and output are always fixed but + * the number of PCM channels for digital input and output are differed. + * + * Additionally, according to "AudioFire Owner's Manual Version 2.2", in some + * model, the number of PCM channels for digital input has more restriction + * depending on which digital interface is selected. + * - S/PDIF coaxial and optical : use input 1-2 + * - ADAT optical at 32.0-48.0 kHz : use input 1-8 + * - ADAT optical at 88.2-96.0 kHz : use input 1-4 (S/MUX format) + * + * The data in AMDTP channels for blank PCM channels are zero. + */ +static const unsigned int freq_table[] = { + /* multiplier mode 0 */ + [0] = 32000, + [1] = 44100, + [2] = 48000, + /* multiplier mode 1 */ + [3] = 88200, + [4] = 96000, + /* multiplier mode 2 */ + [5] = 176400, + [6] = 192000, +}; + +static inline unsigned int +get_multiplier_mode_with_index(unsigned int index) +{ + return ((int)index - 1) / 2; +} + +int snd_efw_get_multiplier_mode(unsigned int sampling_rate, unsigned int *mode) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(freq_table); i++) { + if (freq_table[i] == sampling_rate) { + *mode = get_multiplier_mode_with_index(i); + return 0; + } + } + + return -EINVAL; +} + +static int +hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) +{ + 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, mode; + + for (i = 0; i < ARRAY_SIZE(freq_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (!snd_interval_test(c, pcm_channels[mode])) + continue; + + t.min = min(t.min, freq_table[i]); + t.max = max(t.max, freq_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) +{ + 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, mode; + + for (i = 0; i < ARRAY_SIZE(freq_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (!snd_interval_test(r, freq_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(struct snd_pcm_hardware *hw, unsigned int *pcm_channels) +{ + unsigned int i, mode; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + for (i = 0; i < ARRAY_SIZE(freq_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (pcm_channels[mode] == 0) + continue; + + hw->channels_min = min(hw->channels_min, pcm_channels[mode]); + hw->channels_max = max(hw->channels_max, pcm_channels[mode]); + } +} + +static int +pcm_init_hw_params(struct snd_efw *efw, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct amdtp_stream *s; + unsigned int *pcm_channels; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = AM824_IN_PCM_FORMAT_BITS; + s = &efw->tx_stream; + pcm_channels = efw->pcm_capture_channels; + } else { + runtime->hw.formats = AM824_OUT_PCM_FORMAT_BITS; + s = &efw->rx_stream; + pcm_channels = efw->pcm_playback_channels; + } + + /* limit rates */ + runtime->hw.rates = efw->supported_sampling_rate; + snd_pcm_limit_hw_rates(runtime); + + limit_channels(&runtime->hw, pcm_channels); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, pcm_channels, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + goto end; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, pcm_channels, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + goto end; + + err = amdtp_am824_add_pcm_hw_constraints(s, runtime); +end: + return err; +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + struct amdtp_domain *d = &efw->domain; + enum snd_efw_clock_source clock_source; + int err; + + err = snd_efw_stream_lock_try(efw); + if (err < 0) + return err; + + err = pcm_init_hw_params(efw, substream); + if (err < 0) + goto err_locked; + + err = snd_efw_command_get_clock_source(efw, &clock_source); + if (err < 0) + goto err_locked; + + mutex_lock(&efw->mutex); + + // When source of clock is not internal or any stream is reserved for + // transmission of PCM frames, the available sampling rate is limited + // at current one. + if ((clock_source != SND_EFW_CLOCK_SOURCE_INTERNAL) || + (efw->substreams_counter > 0 && d->events_per_period > 0)) { + unsigned int frames_per_period = d->events_per_period; + unsigned int frames_per_buffer = d->events_per_buffer; + unsigned int sampling_rate; + + err = snd_efw_command_get_sampling_rate(efw, &sampling_rate); + if (err < 0) { + mutex_unlock(&efw->mutex); + goto err_locked; + } + substream->runtime->hw.rate_min = sampling_rate; + substream->runtime->hw.rate_max = sampling_rate; + + if (frames_per_period > 0) { + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + frames_per_period, frames_per_period); + if (err < 0) { + mutex_unlock(&efw->mutex); + goto err_locked; + } + + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + frames_per_buffer, frames_per_buffer); + if (err < 0) { + mutex_unlock(&efw->mutex); + goto err_locked; + } + } + } + + mutex_unlock(&efw->mutex); + + snd_pcm_set_sync(substream); + + return 0; +err_locked: + snd_efw_stream_lock_release(efw); + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + snd_efw_stream_lock_release(efw); + return 0; +} + +static int pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_efw *efw = substream->private_data; + int err = 0; + + if (substream->runtime->status->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); + + mutex_lock(&efw->mutex); + err = snd_efw_stream_reserve_duplex(efw, rate, + frames_per_period, frames_per_buffer); + if (err >= 0) + ++efw->substreams_counter; + mutex_unlock(&efw->mutex); + } + + return err; +} + +static int pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + + mutex_lock(&efw->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + --efw->substreams_counter; + + snd_efw_stream_stop_duplex(efw); + + mutex_unlock(&efw->mutex); + + return 0; +} + +static int pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + int err; + + err = snd_efw_stream_start_duplex(efw); + if (err >= 0) + amdtp_stream_pcm_prepare(&efw->tx_stream); + + return err; +} +static int pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + int err; + + err = snd_efw_stream_start_duplex(efw); + if (err >= 0) + amdtp_stream_pcm_prepare(&efw->rx_stream); + + return err; +} + +static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_efw *efw = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&efw->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&efw->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_efw *efw = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&efw->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&efw->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_efw *efw = sbstrm->private_data; + + return amdtp_domain_stream_pcm_pointer(&efw->domain, &efw->tx_stream); +} +static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_efw *efw = sbstrm->private_data; + + return amdtp_domain_stream_pcm_pointer(&efw->domain, &efw->rx_stream); +} + +static int pcm_capture_ack(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + + return amdtp_domain_stream_pcm_ack(&efw->domain, &efw->tx_stream); +} + +static int pcm_playback_ack(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + + return amdtp_domain_stream_pcm_ack(&efw->domain, &efw->rx_stream); +} + +int snd_efw_create_pcm_devices(struct snd_efw *efw) +{ + static const struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .hw_params = pcm_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .ack = pcm_capture_ack, + }; + static const struct snd_pcm_ops playback_ops = { + .open = pcm_open, + .close = pcm_close, + .hw_params = pcm_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .ack = pcm_playback_ack, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(efw->card, efw->card->driver, 0, 1, 1, &pcm); + if (err < 0) + goto end; + + pcm->private_data = efw; + snprintf(pcm->name, sizeof(pcm->name), "%s PCM", efw->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0); +end: + return err; +} + diff --git a/sound/firewire/fireworks/fireworks_proc.c b/sound/firewire/fireworks/fireworks_proc.c new file mode 100644 index 000000000..12288567b --- /dev/null +++ b/sound/firewire/fireworks/fireworks_proc.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * fireworks_proc.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013-2014 Takashi Sakamoto + */ + +#include "./fireworks.h" + +static inline const char* +get_phys_name(struct snd_efw_phys_grp *grp, bool input) +{ + static const char *const ch_type[] = { + "Analog", "S/PDIF", "ADAT", "S/PDIF or ADAT", "Mirroring", + "Headphones", "I2S", "Guitar", "Pirzo Guitar", "Guitar String", + }; + + if (grp->type < ARRAY_SIZE(ch_type)) + return ch_type[grp->type]; + else if (input) + return "Input"; + else + return "Output"; +} + +static void +proc_read_hwinfo(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_efw *efw = entry->private_data; + unsigned short i; + struct snd_efw_hwinfo *hwinfo; + + hwinfo = kmalloc(sizeof(struct snd_efw_hwinfo), GFP_KERNEL); + if (hwinfo == NULL) + return; + + if (snd_efw_command_get_hwinfo(efw, hwinfo) < 0) + goto end; + + snd_iprintf(buffer, "guid_hi: 0x%X\n", hwinfo->guid_hi); + snd_iprintf(buffer, "guid_lo: 0x%X\n", hwinfo->guid_lo); + snd_iprintf(buffer, "type: 0x%X\n", hwinfo->type); + snd_iprintf(buffer, "version: 0x%X\n", hwinfo->version); + snd_iprintf(buffer, "vendor_name: %s\n", hwinfo->vendor_name); + snd_iprintf(buffer, "model_name: %s\n", hwinfo->model_name); + + snd_iprintf(buffer, "dsp_version: 0x%X\n", hwinfo->dsp_version); + snd_iprintf(buffer, "arm_version: 0x%X\n", hwinfo->arm_version); + snd_iprintf(buffer, "fpga_version: 0x%X\n", hwinfo->fpga_version); + + snd_iprintf(buffer, "flags: 0x%X\n", hwinfo->flags); + + snd_iprintf(buffer, "max_sample_rate: 0x%X\n", hwinfo->max_sample_rate); + snd_iprintf(buffer, "min_sample_rate: 0x%X\n", hwinfo->min_sample_rate); + snd_iprintf(buffer, "supported_clock: 0x%X\n", + hwinfo->supported_clocks); + + snd_iprintf(buffer, "phys out: 0x%X\n", hwinfo->phys_out); + snd_iprintf(buffer, "phys in: 0x%X\n", hwinfo->phys_in); + + snd_iprintf(buffer, "phys in grps: 0x%X\n", + hwinfo->phys_in_grp_count); + for (i = 0; i < hwinfo->phys_in_grp_count; i++) { + snd_iprintf(buffer, + "phys in grp[%d]: type 0x%X, count 0x%X\n", + i, hwinfo->phys_out_grps[i].type, + hwinfo->phys_out_grps[i].count); + } + + snd_iprintf(buffer, "phys out grps: 0x%X\n", + hwinfo->phys_out_grp_count); + for (i = 0; i < hwinfo->phys_out_grp_count; i++) { + snd_iprintf(buffer, + "phys out grps[%d]: type 0x%X, count 0x%X\n", + i, hwinfo->phys_out_grps[i].type, + hwinfo->phys_out_grps[i].count); + } + + snd_iprintf(buffer, "amdtp rx pcm channels 1x: 0x%X\n", + hwinfo->amdtp_rx_pcm_channels); + snd_iprintf(buffer, "amdtp tx pcm channels 1x: 0x%X\n", + hwinfo->amdtp_tx_pcm_channels); + snd_iprintf(buffer, "amdtp rx pcm channels 2x: 0x%X\n", + hwinfo->amdtp_rx_pcm_channels_2x); + snd_iprintf(buffer, "amdtp tx pcm channels 2x: 0x%X\n", + hwinfo->amdtp_tx_pcm_channels_2x); + snd_iprintf(buffer, "amdtp rx pcm channels 4x: 0x%X\n", + hwinfo->amdtp_rx_pcm_channels_4x); + snd_iprintf(buffer, "amdtp tx pcm channels 4x: 0x%X\n", + hwinfo->amdtp_tx_pcm_channels_4x); + + snd_iprintf(buffer, "midi out ports: 0x%X\n", hwinfo->midi_out_ports); + snd_iprintf(buffer, "midi in ports: 0x%X\n", hwinfo->midi_in_ports); + + snd_iprintf(buffer, "mixer playback channels: 0x%X\n", + hwinfo->mixer_playback_channels); + snd_iprintf(buffer, "mixer capture channels: 0x%X\n", + hwinfo->mixer_capture_channels); +end: + kfree(hwinfo); +} + +static void +proc_read_clock(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_efw *efw = entry->private_data; + enum snd_efw_clock_source clock_source; + unsigned int sampling_rate; + + if (snd_efw_command_get_clock_source(efw, &clock_source) < 0) + return; + + if (snd_efw_command_get_sampling_rate(efw, &sampling_rate) < 0) + return; + + snd_iprintf(buffer, "Clock Source: %d\n", clock_source); + snd_iprintf(buffer, "Sampling Rate: %d\n", sampling_rate); +} + +/* + * NOTE: + * dB = 20 * log10(linear / 0x01000000) + * -144.0 dB when linear is 0 + */ +static void +proc_read_phys_meters(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_efw *efw = entry->private_data; + struct snd_efw_phys_meters *meters; + unsigned int g, c, m, max, size; + const char *name; + u32 *linear; + int err; + + size = sizeof(struct snd_efw_phys_meters) + + (efw->phys_in + efw->phys_out) * sizeof(u32); + meters = kzalloc(size, GFP_KERNEL); + if (meters == NULL) + return; + + err = snd_efw_command_get_phys_meters(efw, meters, size); + if (err < 0) + goto end; + + snd_iprintf(buffer, "Physical Meters:\n"); + + m = 0; + max = min(efw->phys_out, meters->out_meters); + linear = meters->values; + snd_iprintf(buffer, " %d Outputs:\n", max); + for (g = 0; g < efw->phys_out_grp_count; g++) { + name = get_phys_name(&efw->phys_out_grps[g], false); + for (c = 0; c < efw->phys_out_grps[g].count; c++) { + if (m < max) + snd_iprintf(buffer, "\t%s [%d]: %d\n", + name, c, linear[m++]); + } + } + + m = 0; + max = min(efw->phys_in, meters->in_meters); + linear = meters->values + meters->out_meters; + snd_iprintf(buffer, " %d Inputs:\n", max); + for (g = 0; g < efw->phys_in_grp_count; g++) { + name = get_phys_name(&efw->phys_in_grps[g], true); + for (c = 0; c < efw->phys_in_grps[g].count; c++) + if (m < max) + snd_iprintf(buffer, "\t%s [%d]: %d\n", + name, c, linear[m++]); + } +end: + kfree(meters); +} + +static void +proc_read_queues_state(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_efw *efw = entry->private_data; + unsigned int consumed; + + if (efw->pull_ptr > efw->push_ptr) + consumed = snd_efw_resp_buf_size - + (unsigned int)(efw->pull_ptr - efw->push_ptr); + else + consumed = (unsigned int)(efw->push_ptr - efw->pull_ptr); + + snd_iprintf(buffer, "%d/%d\n", + consumed, snd_efw_resp_buf_size); +} + +static void +add_node(struct snd_efw *efw, 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(efw->card, name, root); + if (entry) + snd_info_set_text_ops(entry, efw, op); +} + +void snd_efw_proc_init(struct snd_efw *efw) +{ + 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(efw->card, "firewire", + efw->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | 0555; + + add_node(efw, root, "clock", proc_read_clock); + add_node(efw, root, "firmware", proc_read_hwinfo); + add_node(efw, root, "meters", proc_read_phys_meters); + add_node(efw, root, "queues", proc_read_queues_state); +} diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c new file mode 100644 index 000000000..2206af0fe --- /dev/null +++ b/sound/firewire/fireworks/fireworks_stream.c @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * fireworks_stream.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + */ +#include "./fireworks.h" + +#define CALLBACK_TIMEOUT 100 + +static int init_stream(struct snd_efw *efw, struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + enum cmp_direction c_dir; + enum amdtp_stream_direction s_dir; + int err; + + if (stream == &efw->tx_stream) { + conn = &efw->out_conn; + c_dir = CMP_OUTPUT; + s_dir = AMDTP_IN_STREAM; + } else { + conn = &efw->in_conn; + c_dir = CMP_INPUT; + s_dir = AMDTP_OUT_STREAM; + } + + err = cmp_connection_init(conn, efw->unit, c_dir, 0); + if (err < 0) + return err; + + err = amdtp_am824_init(stream, efw->unit, s_dir, CIP_BLOCKING); + if (err < 0) { + amdtp_stream_destroy(stream); + cmp_connection_destroy(conn); + return err; + } + + if (stream == &efw->tx_stream) { + // Fireworks transmits NODATA packets with TAG0. + efw->tx_stream.flags |= CIP_EMPTY_WITH_TAG0; + // Fireworks has its own meaning for dbc. + efw->tx_stream.flags |= CIP_DBC_IS_END_EVENT; + // Fireworks reset dbc at bus reset. + efw->tx_stream.flags |= CIP_SKIP_DBC_ZERO_CHECK; + // But Recent firmwares starts packets with non-zero dbc. + // Driver version 5.7.6 installs firmware version 5.7.3. + if (efw->is_fireworks3 && + (efw->firmware_version == 0x5070000 || + efw->firmware_version == 0x5070300 || + efw->firmware_version == 0x5080000)) + efw->tx_stream.flags |= CIP_UNALIGHED_DBC; + // AudioFire9 always reports wrong dbs. + if (efw->is_af9) + efw->tx_stream.flags |= CIP_WRONG_DBS; + // Firmware version 5.5 reports fixed interval for dbc. + if (efw->firmware_version == 0x5050000) + efw->tx_stream.ctx_data.tx.dbc_interval = 8; + } + + return err; +} + +static int start_stream(struct snd_efw *efw, struct amdtp_stream *stream, + unsigned int rate) +{ + struct cmp_connection *conn; + int err; + + if (stream == &efw->tx_stream) + conn = &efw->out_conn; + else + conn = &efw->in_conn; + + // Establish connection via CMP. + err = cmp_connection_establish(conn); + if (err < 0) + return err; + + // Start amdtp stream. + err = amdtp_domain_add_stream(&efw->domain, stream, + conn->resources.channel, conn->speed); + if (err < 0) { + cmp_connection_break(conn); + return err; + } + + return 0; +} + +// This function should be called before starting the stream or after stopping +// the streams. +static void destroy_stream(struct snd_efw *efw, struct amdtp_stream *stream) +{ + amdtp_stream_destroy(stream); + + if (stream == &efw->tx_stream) + cmp_connection_destroy(&efw->out_conn); + else + cmp_connection_destroy(&efw->in_conn); +} + +static int +check_connection_used_by_others(struct snd_efw *efw, struct amdtp_stream *s) +{ + struct cmp_connection *conn; + bool used; + int err; + + if (s == &efw->tx_stream) + conn = &efw->out_conn; + else + conn = &efw->in_conn; + + err = cmp_connection_check_used(conn, &used); + if ((err >= 0) && used && !amdtp_stream_running(s)) { + dev_err(&efw->unit->device, + "Connection established by others: %cPCR[%d]\n", + (conn->direction == CMP_OUTPUT) ? 'o' : 'i', + conn->pcr_index); + err = -EBUSY; + } + + return err; +} + +int snd_efw_stream_init_duplex(struct snd_efw *efw) +{ + int err; + + err = init_stream(efw, &efw->tx_stream); + if (err < 0) + return err; + + err = init_stream(efw, &efw->rx_stream); + if (err < 0) { + destroy_stream(efw, &efw->tx_stream); + return err; + } + + err = amdtp_domain_init(&efw->domain); + if (err < 0) { + destroy_stream(efw, &efw->tx_stream); + destroy_stream(efw, &efw->rx_stream); + return err; + } + + // set IEC61883 compliant mode (actually not fully compliant...). + err = snd_efw_command_set_tx_mode(efw, SND_EFW_TRANSPORT_MODE_IEC61883); + if (err < 0) { + destroy_stream(efw, &efw->tx_stream); + destroy_stream(efw, &efw->rx_stream); + } + + return err; +} + +static int keep_resources(struct snd_efw *efw, struct amdtp_stream *stream, + unsigned int rate, unsigned int mode) +{ + unsigned int pcm_channels; + unsigned int midi_ports; + struct cmp_connection *conn; + int err; + + if (stream == &efw->tx_stream) { + pcm_channels = efw->pcm_capture_channels[mode]; + midi_ports = efw->midi_out_ports; + conn = &efw->out_conn; + } else { + pcm_channels = efw->pcm_playback_channels[mode]; + midi_ports = efw->midi_in_ports; + conn = &efw->in_conn; + } + + err = amdtp_am824_set_parameters(stream, rate, pcm_channels, + midi_ports, false); + if (err < 0) + return err; + + return cmp_connection_reserve(conn, amdtp_stream_get_max_payload(stream)); +} + +int snd_efw_stream_reserve_duplex(struct snd_efw *efw, unsigned int rate, + unsigned int frames_per_period, + unsigned int frames_per_buffer) +{ + unsigned int curr_rate; + int err; + + // Considering JACK/FFADO streaming: + // TODO: This can be removed hwdep functionality becomes popular. + err = check_connection_used_by_others(efw, &efw->rx_stream); + if (err < 0) + return err; + + // stop streams if rate is different. + err = snd_efw_command_get_sampling_rate(efw, &curr_rate); + if (err < 0) + return err; + if (rate == 0) + rate = curr_rate; + if (rate != curr_rate) { + amdtp_domain_stop(&efw->domain); + + cmp_connection_break(&efw->out_conn); + cmp_connection_break(&efw->in_conn); + + cmp_connection_release(&efw->out_conn); + cmp_connection_release(&efw->in_conn); + } + + if (efw->substreams_counter == 0 || rate != curr_rate) { + unsigned int mode; + + err = snd_efw_command_set_sampling_rate(efw, rate); + if (err < 0) + return err; + + err = snd_efw_get_multiplier_mode(rate, &mode); + if (err < 0) + return err; + + err = keep_resources(efw, &efw->tx_stream, rate, mode); + if (err < 0) + return err; + + err = keep_resources(efw, &efw->rx_stream, rate, mode); + if (err < 0) { + cmp_connection_release(&efw->in_conn); + return err; + } + + err = amdtp_domain_set_events_per_period(&efw->domain, + frames_per_period, frames_per_buffer); + if (err < 0) { + cmp_connection_release(&efw->in_conn); + cmp_connection_release(&efw->out_conn); + return err; + } + } + + return 0; +} + +int snd_efw_stream_start_duplex(struct snd_efw *efw) +{ + unsigned int rate; + int err = 0; + + // Need no substreams. + if (efw->substreams_counter == 0) + return -EIO; + + if (amdtp_streaming_error(&efw->rx_stream) || + amdtp_streaming_error(&efw->tx_stream)) { + amdtp_domain_stop(&efw->domain); + cmp_connection_break(&efw->out_conn); + cmp_connection_break(&efw->in_conn); + } + + err = snd_efw_command_get_sampling_rate(efw, &rate); + if (err < 0) + return err; + + if (!amdtp_stream_running(&efw->rx_stream)) { + err = start_stream(efw, &efw->rx_stream, rate); + if (err < 0) + goto error; + + err = start_stream(efw, &efw->tx_stream, rate); + if (err < 0) + goto error; + + err = amdtp_domain_start(&efw->domain, 0); + if (err < 0) + goto error; + + // Wait first callback. + if (!amdtp_stream_wait_callback(&efw->rx_stream, + CALLBACK_TIMEOUT) || + !amdtp_stream_wait_callback(&efw->tx_stream, + CALLBACK_TIMEOUT)) { + err = -ETIMEDOUT; + goto error; + } + } + + return 0; +error: + amdtp_domain_stop(&efw->domain); + + cmp_connection_break(&efw->out_conn); + cmp_connection_break(&efw->in_conn); + + return err; +} + +void snd_efw_stream_stop_duplex(struct snd_efw *efw) +{ + if (efw->substreams_counter == 0) { + amdtp_domain_stop(&efw->domain); + + cmp_connection_break(&efw->out_conn); + cmp_connection_break(&efw->in_conn); + + cmp_connection_release(&efw->out_conn); + cmp_connection_release(&efw->in_conn); + } +} + +void snd_efw_stream_update_duplex(struct snd_efw *efw) +{ + amdtp_domain_stop(&efw->domain); + + cmp_connection_break(&efw->out_conn); + cmp_connection_break(&efw->in_conn); + + amdtp_stream_pcm_abort(&efw->rx_stream); + amdtp_stream_pcm_abort(&efw->tx_stream); +} + +void snd_efw_stream_destroy_duplex(struct snd_efw *efw) +{ + amdtp_domain_destroy(&efw->domain); + + destroy_stream(efw, &efw->rx_stream); + destroy_stream(efw, &efw->tx_stream); +} + +void snd_efw_stream_lock_changed(struct snd_efw *efw) +{ + efw->dev_lock_changed = true; + wake_up(&efw->hwdep_wait); +} + +int snd_efw_stream_lock_try(struct snd_efw *efw) +{ + int err; + + spin_lock_irq(&efw->lock); + + /* user land lock this */ + if (efw->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (efw->dev_lock_count++ == 0) + snd_efw_stream_lock_changed(efw); + err = 0; +end: + spin_unlock_irq(&efw->lock); + return err; +} + +void snd_efw_stream_lock_release(struct snd_efw *efw) +{ + spin_lock_irq(&efw->lock); + + if (WARN_ON(efw->dev_lock_count <= 0)) + goto end; + if (--efw->dev_lock_count == 0) + snd_efw_stream_lock_changed(efw); +end: + spin_unlock_irq(&efw->lock); +} diff --git a/sound/firewire/fireworks/fireworks_transaction.c b/sound/firewire/fireworks/fireworks_transaction.c new file mode 100644 index 000000000..9f8c53b39 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_transaction.c @@ -0,0 +1,324 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * fireworks_transaction.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + */ + +/* + * Fireworks have its own transaction. The transaction can be delivered by AV/C + * Vendor Specific command frame or usual asynchronous transaction. At least, + * Windows driver and firmware version 5.5 or later don't use AV/C command. + * + * Transaction substance: + * At first, 6 data exist. Following to the data, parameters for each command + * exist. All of the parameters are 32 bit aligned to big endian. + * data[0]: Length of transaction substance + * data[1]: Transaction version + * data[2]: Sequence number. This is incremented by the device + * data[3]: Transaction category + * data[4]: Transaction command + * data[5]: Return value in response. + * data[6-]: Parameters + * + * Transaction address: + * command: 0xecc000000000 + * response: 0xecc080000000 (default) + * + * I note that the address for response can be changed by command. But this + * module uses the default address. + */ +#include "./fireworks.h" + +#define MEMORY_SPACE_EFW_COMMAND 0xecc000000000ULL +#define MEMORY_SPACE_EFW_RESPONSE 0xecc080000000ULL + +#define ERROR_RETRIES 3 +#define ERROR_DELAY_MS 5 +#define EFC_TIMEOUT_MS 125 + +static DEFINE_SPINLOCK(instances_lock); +static struct snd_efw *instances[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + +static DEFINE_SPINLOCK(transaction_queues_lock); +static LIST_HEAD(transaction_queues); + +enum transaction_queue_state { + STATE_PENDING, + STATE_BUS_RESET, + STATE_COMPLETE +}; + +struct transaction_queue { + struct list_head list; + struct fw_unit *unit; + void *buf; + unsigned int size; + u32 seqnum; + enum transaction_queue_state state; + wait_queue_head_t wait; +}; + +int snd_efw_transaction_cmd(struct fw_unit *unit, + const void *cmd, unsigned int size) +{ + return snd_fw_transaction(unit, TCODE_WRITE_BLOCK_REQUEST, + MEMORY_SPACE_EFW_COMMAND, + (void *)cmd, size, 0); +} + +int snd_efw_transaction_run(struct fw_unit *unit, + const void *cmd, unsigned int cmd_size, + void *resp, unsigned int resp_size) +{ + struct transaction_queue t; + unsigned int tries; + int ret; + + t.unit = unit; + t.buf = resp; + t.size = resp_size; + t.seqnum = be32_to_cpu(((struct snd_efw_transaction *)cmd)->seqnum) + 1; + t.state = STATE_PENDING; + init_waitqueue_head(&t.wait); + + spin_lock_irq(&transaction_queues_lock); + list_add_tail(&t.list, &transaction_queues); + spin_unlock_irq(&transaction_queues_lock); + + tries = 0; + do { + ret = snd_efw_transaction_cmd(t.unit, (void *)cmd, cmd_size); + if (ret < 0) + break; + + wait_event_timeout(t.wait, t.state != STATE_PENDING, + msecs_to_jiffies(EFC_TIMEOUT_MS)); + + if (t.state == STATE_COMPLETE) { + ret = t.size; + break; + } else if (t.state == STATE_BUS_RESET) { + msleep(ERROR_DELAY_MS); + } else if (++tries >= ERROR_RETRIES) { + dev_err(&t.unit->device, "EFW transaction timed out\n"); + ret = -EIO; + break; + } + } while (1); + + spin_lock_irq(&transaction_queues_lock); + list_del(&t.list); + spin_unlock_irq(&transaction_queues_lock); + + return ret; +} + +static void +copy_resp_to_buf(struct snd_efw *efw, void *data, size_t length, int *rcode) +{ + size_t capacity, till_end; + struct snd_efw_transaction *t; + + t = (struct snd_efw_transaction *)data; + length = min_t(size_t, be32_to_cpu(t->length) * sizeof(u32), length); + + spin_lock(&efw->lock); + + if (efw->push_ptr < efw->pull_ptr) + capacity = (unsigned int)(efw->pull_ptr - efw->push_ptr); + else + capacity = snd_efw_resp_buf_size - + (unsigned int)(efw->push_ptr - efw->pull_ptr); + + /* confirm enough space for this response */ + if (capacity < length) { + *rcode = RCODE_CONFLICT_ERROR; + goto end; + } + + /* copy to ring buffer */ + while (length > 0) { + till_end = snd_efw_resp_buf_size - + (unsigned int)(efw->push_ptr - efw->resp_buf); + till_end = min_t(unsigned int, length, till_end); + + memcpy(efw->push_ptr, data, till_end); + + efw->push_ptr += till_end; + if (efw->push_ptr >= efw->resp_buf + snd_efw_resp_buf_size) + efw->push_ptr -= snd_efw_resp_buf_size; + + length -= till_end; + data += till_end; + } + + /* for hwdep */ + wake_up(&efw->hwdep_wait); + + *rcode = RCODE_COMPLETE; +end: + spin_unlock_irq(&efw->lock); +} + +static void +handle_resp_for_user(struct fw_card *card, int generation, int source, + void *data, size_t length, int *rcode) +{ + struct fw_device *device; + struct snd_efw *efw; + unsigned int i; + + spin_lock_irq(&instances_lock); + + for (i = 0; i < SNDRV_CARDS; i++) { + efw = instances[i]; + if (efw == NULL) + continue; + device = fw_parent_device(efw->unit); + if ((device->card != card) || + (device->generation != generation)) + continue; + smp_rmb(); /* node id vs. generation */ + if (device->node_id != source) + continue; + + break; + } + if (i == SNDRV_CARDS) + goto end; + + copy_resp_to_buf(efw, data, length, rcode); +end: + spin_unlock(&instances_lock); +} + +static void +handle_resp_for_kernel(struct fw_card *card, int generation, int source, + void *data, size_t length, int *rcode, u32 seqnum) +{ + struct fw_device *device; + struct transaction_queue *t; + unsigned long flags; + + spin_lock_irqsave(&transaction_queues_lock, flags); + list_for_each_entry(t, &transaction_queues, list) { + device = fw_parent_device(t->unit); + if ((device->card != card) || + (device->generation != generation)) + continue; + smp_rmb(); /* node_id vs. generation */ + if (device->node_id != source) + continue; + + if ((t->state == STATE_PENDING) && (t->seqnum == seqnum)) { + t->state = STATE_COMPLETE; + t->size = min_t(unsigned int, length, t->size); + memcpy(t->buf, data, t->size); + wake_up(&t->wait); + *rcode = RCODE_COMPLETE; + } + } + spin_unlock_irqrestore(&transaction_queues_lock, flags); +} + +static void +efw_response(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) +{ + int rcode, dummy; + u32 seqnum; + + rcode = RCODE_TYPE_ERROR; + if (length < sizeof(struct snd_efw_transaction)) { + rcode = RCODE_DATA_ERROR; + goto end; + } else if (offset != MEMORY_SPACE_EFW_RESPONSE) { + rcode = RCODE_ADDRESS_ERROR; + goto end; + } + + seqnum = be32_to_cpu(((struct snd_efw_transaction *)data)->seqnum); + if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX + 1) { + handle_resp_for_kernel(card, generation, source, + data, length, &rcode, seqnum); + if (snd_efw_resp_buf_debug) + handle_resp_for_user(card, generation, source, + data, length, &dummy); + } else { + handle_resp_for_user(card, generation, source, + data, length, &rcode); + } +end: + fw_send_response(card, request, rcode); +} + +void snd_efw_transaction_add_instance(struct snd_efw *efw) +{ + unsigned int i; + + spin_lock_irq(&instances_lock); + + for (i = 0; i < SNDRV_CARDS; i++) { + if (instances[i] != NULL) + continue; + instances[i] = efw; + break; + } + + spin_unlock_irq(&instances_lock); +} + +void snd_efw_transaction_remove_instance(struct snd_efw *efw) +{ + unsigned int i; + + spin_lock_irq(&instances_lock); + + for (i = 0; i < SNDRV_CARDS; i++) { + if (instances[i] != efw) + continue; + instances[i] = NULL; + } + + spin_unlock_irq(&instances_lock); +} + +void snd_efw_transaction_bus_reset(struct fw_unit *unit) +{ + struct transaction_queue *t; + + spin_lock_irq(&transaction_queues_lock); + list_for_each_entry(t, &transaction_queues, list) { + if ((t->unit == unit) && + (t->state == STATE_PENDING)) { + t->state = STATE_BUS_RESET; + wake_up(&t->wait); + } + } + spin_unlock_irq(&transaction_queues_lock); +} + +static struct fw_address_handler resp_register_handler = { + .length = SND_EFW_RESPONSE_MAXIMUM_BYTES, + .address_callback = efw_response +}; + +int snd_efw_transaction_register(void) +{ + static const struct fw_address_region resp_register_region = { + .start = MEMORY_SPACE_EFW_RESPONSE, + .end = MEMORY_SPACE_EFW_RESPONSE + + SND_EFW_RESPONSE_MAXIMUM_BYTES + }; + return fw_core_add_address_handler(&resp_register_handler, + &resp_register_region); +} + +void snd_efw_transaction_unregister(void) +{ + WARN_ON(!list_empty(&transaction_queues)); + fw_core_remove_address_handler(&resp_register_handler); +} diff --git a/sound/firewire/isight.c b/sound/firewire/isight.c new file mode 100644 index 000000000..6655af53b --- /dev/null +++ b/sound/firewire/isight.c @@ -0,0 +1,738 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Apple iSight audio driver + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + */ + +#include <asm/byteorder.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/string.h> +#include <sound/control.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/tlv.h> +#include "lib.h" +#include "iso-resources.h" +#include "packets-buffer.h" + +#define OUI_APPLE 0x000a27 +#define MODEL_APPLE_ISIGHT 0x000008 +#define SW_ISIGHT_AUDIO 0x000010 + +#define REG_AUDIO_ENABLE 0x000 +#define AUDIO_ENABLE 0x80000000 +#define REG_DEF_AUDIO_GAIN 0x204 +#define REG_GAIN_RAW_START 0x210 +#define REG_GAIN_RAW_END 0x214 +#define REG_GAIN_DB_START 0x218 +#define REG_GAIN_DB_END 0x21c +#define REG_SAMPLE_RATE_INQUIRY 0x280 +#define REG_ISO_TX_CONFIG 0x300 +#define SPEED_SHIFT 16 +#define REG_SAMPLE_RATE 0x400 +#define RATE_48000 0x80000000 +#define REG_GAIN 0x500 +#define REG_MUTE 0x504 + +#define MAX_FRAMES_PER_PACKET 475 + +#define QUEUE_LENGTH 20 + +struct isight { + struct snd_card *card; + struct fw_unit *unit; + struct fw_device *device; + u64 audio_base; + struct snd_pcm_substream *pcm; + struct mutex mutex; + struct iso_packets_buffer buffer; + struct fw_iso_resources resources; + struct fw_iso_context *context; + bool pcm_active; + bool pcm_running; + bool first_packet; + int packet_index; + u32 total_samples; + unsigned int buffer_pointer; + unsigned int period_counter; + s32 gain_min, gain_max; + unsigned int gain_tlv[4]; +}; + +struct audio_payload { + __be32 sample_count; + __be32 signature; + __be32 sample_total; + __be32 reserved; + __be16 samples[2 * MAX_FRAMES_PER_PACKET]; +}; + +MODULE_DESCRIPTION("iSight audio driver"); +MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); +MODULE_LICENSE("GPL v2"); + +static struct fw_iso_packet audio_packet = { + .payload_length = sizeof(struct audio_payload), + .interrupt = 1, + .header_length = 4, +}; + +static void isight_update_pointers(struct isight *isight, unsigned int count) +{ + struct snd_pcm_runtime *runtime = isight->pcm->runtime; + unsigned int ptr; + + smp_wmb(); /* update buffer data before buffer pointer */ + + ptr = isight->buffer_pointer; + ptr += count; + if (ptr >= runtime->buffer_size) + ptr -= runtime->buffer_size; + WRITE_ONCE(isight->buffer_pointer, ptr); + + isight->period_counter += count; + if (isight->period_counter >= runtime->period_size) { + isight->period_counter -= runtime->period_size; + snd_pcm_period_elapsed(isight->pcm); + } +} + +static void isight_samples(struct isight *isight, + const __be16 *samples, unsigned int count) +{ + struct snd_pcm_runtime *runtime; + unsigned int count1; + + if (!READ_ONCE(isight->pcm_running)) + return; + + runtime = isight->pcm->runtime; + if (isight->buffer_pointer + count <= runtime->buffer_size) { + memcpy(runtime->dma_area + isight->buffer_pointer * 4, + samples, count * 4); + } else { + count1 = runtime->buffer_size - isight->buffer_pointer; + memcpy(runtime->dma_area + isight->buffer_pointer * 4, + samples, count1 * 4); + samples += count1 * 2; + memcpy(runtime->dma_area, samples, (count - count1) * 4); + } + + isight_update_pointers(isight, count); +} + +static void isight_pcm_abort(struct isight *isight) +{ + if (READ_ONCE(isight->pcm_active)) + snd_pcm_stop_xrun(isight->pcm); +} + +static void isight_dropped_samples(struct isight *isight, unsigned int total) +{ + struct snd_pcm_runtime *runtime; + u32 dropped; + unsigned int count1; + + if (!READ_ONCE(isight->pcm_running)) + return; + + runtime = isight->pcm->runtime; + dropped = total - isight->total_samples; + if (dropped < runtime->buffer_size) { + if (isight->buffer_pointer + dropped <= runtime->buffer_size) { + memset(runtime->dma_area + isight->buffer_pointer * 4, + 0, dropped * 4); + } else { + count1 = runtime->buffer_size - isight->buffer_pointer; + memset(runtime->dma_area + isight->buffer_pointer * 4, + 0, count1 * 4); + memset(runtime->dma_area, 0, (dropped - count1) * 4); + } + isight_update_pointers(isight, dropped); + } else { + isight_pcm_abort(isight); + } +} + +static void isight_packet(struct fw_iso_context *context, u32 cycle, + size_t header_length, void *header, void *data) +{ + struct isight *isight = data; + const struct audio_payload *payload; + unsigned int index, length, count, total; + int err; + + if (isight->packet_index < 0) + return; + index = isight->packet_index; + payload = isight->buffer.packets[index].buffer; + length = be32_to_cpup(header) >> 16; + + if (likely(length >= 16 && + payload->signature == cpu_to_be32(0x73676874/*"sght"*/))) { + count = be32_to_cpu(payload->sample_count); + if (likely(count <= (length - 16) / 4)) { + total = be32_to_cpu(payload->sample_total); + if (unlikely(total != isight->total_samples)) { + if (!isight->first_packet) + isight_dropped_samples(isight, total); + isight->first_packet = false; + isight->total_samples = total; + } + + isight_samples(isight, payload->samples, count); + isight->total_samples += count; + } + } + + err = fw_iso_context_queue(isight->context, &audio_packet, + &isight->buffer.iso_buffer, + isight->buffer.packets[index].offset); + if (err < 0) { + dev_err(&isight->unit->device, "queueing error: %d\n", err); + isight_pcm_abort(isight); + isight->packet_index = -1; + return; + } + fw_iso_context_queue_flush(isight->context); + + if (++index >= QUEUE_LENGTH) + index = 0; + isight->packet_index = index; +} + +static int isight_connect(struct isight *isight) +{ + int ch, err; + __be32 value; + +retry_after_bus_reset: + ch = fw_iso_resources_allocate(&isight->resources, + sizeof(struct audio_payload), + isight->device->max_speed); + if (ch < 0) { + err = ch; + goto error; + } + + value = cpu_to_be32(ch | (isight->device->max_speed << SPEED_SHIFT)); + err = snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, + isight->audio_base + REG_ISO_TX_CONFIG, + &value, 4, FW_FIXED_GENERATION | + isight->resources.generation); + if (err == -EAGAIN) { + fw_iso_resources_free(&isight->resources); + goto retry_after_bus_reset; + } else if (err < 0) { + goto err_resources; + } + + return 0; + +err_resources: + fw_iso_resources_free(&isight->resources); +error: + return err; +} + +static int isight_open(struct snd_pcm_substream *substream) +{ + static const struct snd_pcm_hardware hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = SNDRV_PCM_FMTBIT_S16_BE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 4 * 1024 * 1024, + .period_bytes_min = MAX_FRAMES_PER_PACKET * 4, + .period_bytes_max = 1024 * 1024, + .periods_min = 2, + .periods_max = UINT_MAX, + }; + struct isight *isight = substream->private_data; + + substream->runtime->hw = hardware; + + return iso_packets_buffer_init(&isight->buffer, isight->unit, + QUEUE_LENGTH, + sizeof(struct audio_payload), + DMA_FROM_DEVICE); +} + +static int isight_close(struct snd_pcm_substream *substream) +{ + struct isight *isight = substream->private_data; + + iso_packets_buffer_destroy(&isight->buffer, isight->unit); + + return 0; +} + +static int isight_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct isight *isight = substream->private_data; + + WRITE_ONCE(isight->pcm_active, true); + + return 0; +} + +static int reg_read(struct isight *isight, int offset, __be32 *value) +{ + return snd_fw_transaction(isight->unit, TCODE_READ_QUADLET_REQUEST, + isight->audio_base + offset, value, 4, 0); +} + +static int reg_write(struct isight *isight, int offset, __be32 value) +{ + return snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, + isight->audio_base + offset, &value, 4, 0); +} + +static void isight_stop_streaming(struct isight *isight) +{ + __be32 value; + + if (!isight->context) + return; + + fw_iso_context_stop(isight->context); + fw_iso_context_destroy(isight->context); + isight->context = NULL; + fw_iso_resources_free(&isight->resources); + value = 0; + snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, + isight->audio_base + REG_AUDIO_ENABLE, + &value, 4, FW_QUIET); +} + +static int isight_hw_free(struct snd_pcm_substream *substream) +{ + struct isight *isight = substream->private_data; + + WRITE_ONCE(isight->pcm_active, false); + + mutex_lock(&isight->mutex); + isight_stop_streaming(isight); + mutex_unlock(&isight->mutex); + + return 0; +} + +static int isight_start_streaming(struct isight *isight) +{ + unsigned int i; + int err; + + if (isight->context) { + if (isight->packet_index < 0) + isight_stop_streaming(isight); + else + return 0; + } + + err = reg_write(isight, REG_SAMPLE_RATE, cpu_to_be32(RATE_48000)); + if (err < 0) + goto error; + + err = isight_connect(isight); + if (err < 0) + goto error; + + err = reg_write(isight, REG_AUDIO_ENABLE, cpu_to_be32(AUDIO_ENABLE)); + if (err < 0) + goto err_resources; + + isight->context = fw_iso_context_create(isight->device->card, + FW_ISO_CONTEXT_RECEIVE, + isight->resources.channel, + isight->device->max_speed, + 4, isight_packet, isight); + if (IS_ERR(isight->context)) { + err = PTR_ERR(isight->context); + isight->context = NULL; + goto err_resources; + } + + for (i = 0; i < QUEUE_LENGTH; ++i) { + err = fw_iso_context_queue(isight->context, &audio_packet, + &isight->buffer.iso_buffer, + isight->buffer.packets[i].offset); + if (err < 0) + goto err_context; + } + + isight->first_packet = true; + isight->packet_index = 0; + + err = fw_iso_context_start(isight->context, -1, 0, + FW_ISO_CONTEXT_MATCH_ALL_TAGS/*?*/); + if (err < 0) + goto err_context; + + return 0; + +err_context: + fw_iso_context_destroy(isight->context); + isight->context = NULL; +err_resources: + fw_iso_resources_free(&isight->resources); + reg_write(isight, REG_AUDIO_ENABLE, 0); +error: + return err; +} + +static int isight_prepare(struct snd_pcm_substream *substream) +{ + struct isight *isight = substream->private_data; + int err; + + isight->buffer_pointer = 0; + isight->period_counter = 0; + + mutex_lock(&isight->mutex); + err = isight_start_streaming(isight); + mutex_unlock(&isight->mutex); + + return err; +} + +static int isight_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct isight *isight = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + WRITE_ONCE(isight->pcm_running, true); + break; + case SNDRV_PCM_TRIGGER_STOP: + WRITE_ONCE(isight->pcm_running, false); + break; + default: + return -EINVAL; + } + return 0; +} + +static snd_pcm_uframes_t isight_pointer(struct snd_pcm_substream *substream) +{ + struct isight *isight = substream->private_data; + + return READ_ONCE(isight->buffer_pointer); +} + +static int isight_create_pcm(struct isight *isight) +{ + static const struct snd_pcm_ops ops = { + .open = isight_open, + .close = isight_close, + .hw_params = isight_hw_params, + .hw_free = isight_hw_free, + .prepare = isight_prepare, + .trigger = isight_trigger, + .pointer = isight_pointer, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(isight->card, "iSight", 0, 0, 1, &pcm); + if (err < 0) + return err; + pcm->private_data = isight; + strcpy(pcm->name, "iSight"); + isight->pcm = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + isight->pcm->ops = &ops; + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0); + + return 0; +} + +static int isight_gain_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + struct isight *isight = ctl->private_data; + + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = 1; + info->value.integer.min = isight->gain_min; + info->value.integer.max = isight->gain_max; + + return 0; +} + +static int isight_gain_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct isight *isight = ctl->private_data; + __be32 gain; + int err; + + err = reg_read(isight, REG_GAIN, &gain); + if (err < 0) + return err; + + value->value.integer.value[0] = (s32)be32_to_cpu(gain); + + return 0; +} + +static int isight_gain_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct isight *isight = ctl->private_data; + + if (value->value.integer.value[0] < isight->gain_min || + value->value.integer.value[0] > isight->gain_max) + return -EINVAL; + + return reg_write(isight, REG_GAIN, + cpu_to_be32(value->value.integer.value[0])); +} + +static int isight_mute_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct isight *isight = ctl->private_data; + __be32 mute; + int err; + + err = reg_read(isight, REG_MUTE, &mute); + if (err < 0) + return err; + + value->value.integer.value[0] = !mute; + + return 0; +} + +static int isight_mute_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct isight *isight = ctl->private_data; + + return reg_write(isight, REG_MUTE, + (__force __be32)!value->value.integer.value[0]); +} + +static int isight_create_mixer(struct isight *isight) +{ + static const struct snd_kcontrol_new gain_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Capture Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = isight_gain_info, + .get = isight_gain_get, + .put = isight_gain_put, + }; + static const struct snd_kcontrol_new mute_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Capture Switch", + .info = snd_ctl_boolean_mono_info, + .get = isight_mute_get, + .put = isight_mute_put, + }; + __be32 value; + struct snd_kcontrol *ctl; + int err; + + err = reg_read(isight, REG_GAIN_RAW_START, &value); + if (err < 0) + return err; + isight->gain_min = be32_to_cpu(value); + + err = reg_read(isight, REG_GAIN_RAW_END, &value); + if (err < 0) + return err; + isight->gain_max = be32_to_cpu(value); + + isight->gain_tlv[SNDRV_CTL_TLVO_TYPE] = SNDRV_CTL_TLVT_DB_MINMAX; + isight->gain_tlv[SNDRV_CTL_TLVO_LEN] = 2 * sizeof(unsigned int); + + err = reg_read(isight, REG_GAIN_DB_START, &value); + if (err < 0) + return err; + isight->gain_tlv[SNDRV_CTL_TLVO_DB_MINMAX_MIN] = + (s32)be32_to_cpu(value) * 100; + + err = reg_read(isight, REG_GAIN_DB_END, &value); + if (err < 0) + return err; + isight->gain_tlv[SNDRV_CTL_TLVO_DB_MINMAX_MAX] = + (s32)be32_to_cpu(value) * 100; + + ctl = snd_ctl_new1(&gain_control, isight); + if (ctl) + ctl->tlv.p = isight->gain_tlv; + err = snd_ctl_add(isight->card, ctl); + if (err < 0) + return err; + + err = snd_ctl_add(isight->card, snd_ctl_new1(&mute_control, isight)); + if (err < 0) + return err; + + return 0; +} + +static void isight_card_free(struct snd_card *card) +{ + struct isight *isight = card->private_data; + + fw_iso_resources_destroy(&isight->resources); +} + +static u64 get_unit_base(struct fw_unit *unit) +{ + struct fw_csr_iterator i; + int key, value; + + fw_csr_iterator_init(&i, unit->directory); + while (fw_csr_iterator_next(&i, &key, &value)) + if (key == CSR_OFFSET) + return CSR_REGISTER_BASE + value * 4; + return 0; +} + +static int isight_probe(struct fw_unit *unit, + const struct ieee1394_device_id *id) +{ + struct fw_device *fw_dev = fw_parent_device(unit); + struct snd_card *card; + struct isight *isight; + int err; + + err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, + sizeof(*isight), &card); + if (err < 0) + return err; + + isight = card->private_data; + isight->card = card; + mutex_init(&isight->mutex); + isight->unit = fw_unit_get(unit); + isight->device = fw_dev; + isight->audio_base = get_unit_base(unit); + if (!isight->audio_base) { + dev_err(&unit->device, "audio unit base not found\n"); + err = -ENXIO; + goto error; + } + fw_iso_resources_init(&isight->resources, unit); + + card->private_free = isight_card_free; + + strcpy(card->driver, "iSight"); + strcpy(card->shortname, "Apple iSight"); + snprintf(card->longname, sizeof(card->longname), + "Apple iSight (GUID %08x%08x) at %s, S%d", + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&unit->device), 100 << fw_dev->max_speed); + strcpy(card->mixername, "iSight"); + + err = isight_create_pcm(isight); + if (err < 0) + goto error; + + err = isight_create_mixer(isight); + if (err < 0) + goto error; + + err = snd_card_register(card); + if (err < 0) + goto error; + + dev_set_drvdata(&unit->device, isight); + + return 0; +error: + snd_card_free(card); + + mutex_destroy(&isight->mutex); + fw_unit_put(isight->unit); + + return err; +} + +static void isight_bus_reset(struct fw_unit *unit) +{ + struct isight *isight = dev_get_drvdata(&unit->device); + + if (fw_iso_resources_update(&isight->resources) < 0) { + isight_pcm_abort(isight); + + mutex_lock(&isight->mutex); + isight_stop_streaming(isight); + mutex_unlock(&isight->mutex); + } +} + +static void isight_remove(struct fw_unit *unit) +{ + struct isight *isight = dev_get_drvdata(&unit->device); + + isight_pcm_abort(isight); + + snd_card_disconnect(isight->card); + + mutex_lock(&isight->mutex); + isight_stop_streaming(isight); + mutex_unlock(&isight->mutex); + + // Block till all of ALSA character devices are released. + snd_card_free(isight->card); + + mutex_destroy(&isight->mutex); + fw_unit_put(isight->unit); +} + +static const struct ieee1394_device_id isight_id_table[] = { + { + .match_flags = IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .specifier_id = OUI_APPLE, + .version = SW_ISIGHT_AUDIO, + }, + { } +}; +MODULE_DEVICE_TABLE(ieee1394, isight_id_table); + +static struct fw_driver isight_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + }, + .probe = isight_probe, + .update = isight_bus_reset, + .remove = isight_remove, + .id_table = isight_id_table, +}; + +static int __init alsa_isight_init(void) +{ + return driver_register(&isight_driver.driver); +} + +static void __exit alsa_isight_exit(void) +{ + driver_unregister(&isight_driver.driver); +} + +module_init(alsa_isight_init); +module_exit(alsa_isight_exit); diff --git a/sound/firewire/iso-resources.c b/sound/firewire/iso-resources.c new file mode 100644 index 000000000..84f71b2ea --- /dev/null +++ b/sound/firewire/iso-resources.c @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * isochronous resources helper functions + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + */ + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/export.h> +#include <linux/jiffies.h> +#include <linux/mutex.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include "iso-resources.h" + +/** + * fw_iso_resources_init - initializes a &struct fw_iso_resources + * @r: the resource manager to initialize + * @unit: the device unit for which the resources will be needed + * + * If the device does not support all channel numbers, change @r->channels_mask + * after calling this function. + */ +int fw_iso_resources_init(struct fw_iso_resources *r, struct fw_unit *unit) +{ + r->channels_mask = ~0uLL; + r->unit = unit; + mutex_init(&r->mutex); + r->allocated = false; + + return 0; +} +EXPORT_SYMBOL(fw_iso_resources_init); + +/** + * fw_iso_resources_destroy - destroy a resource manager + * @r: the resource manager that is no longer needed + */ +void fw_iso_resources_destroy(struct fw_iso_resources *r) +{ + WARN_ON(r->allocated); + mutex_destroy(&r->mutex); +} +EXPORT_SYMBOL(fw_iso_resources_destroy); + +static unsigned int packet_bandwidth(unsigned int max_payload_bytes, int speed) +{ + unsigned int bytes, s400_bytes; + + /* iso packets have three header quadlets and quadlet-aligned payload */ + bytes = 3 * 4 + ALIGN(max_payload_bytes, 4); + + /* convert to bandwidth units (quadlets at S1600 = bytes at S400) */ + if (speed <= SCODE_400) + s400_bytes = bytes * (1 << (SCODE_400 - speed)); + else + s400_bytes = DIV_ROUND_UP(bytes, 1 << (speed - SCODE_400)); + + return s400_bytes; +} + +static int current_bandwidth_overhead(struct fw_card *card) +{ + /* + * Under the usual pessimistic assumption (cable length 4.5 m), the + * isochronous overhead for N cables is 1.797 µs + N * 0.494 µs, or + * 88.3 + N * 24.3 in bandwidth units. + * + * The calculation below tries to deduce N from the current gap count. + * If the gap count has been optimized by measuring the actual packet + * transmission time, this derived overhead should be near the actual + * overhead as well. + */ + return card->gap_count < 63 ? card->gap_count * 97 / 10 + 89 : 512; +} + +static int wait_isoch_resource_delay_after_bus_reset(struct fw_card *card) +{ + for (;;) { + s64 delay = (card->reset_jiffies + HZ) - get_jiffies_64(); + if (delay <= 0) + return 0; + if (schedule_timeout_interruptible(delay) > 0) + return -ERESTARTSYS; + } +} + +/** + * fw_iso_resources_allocate - allocate isochronous channel and bandwidth + * @r: the resource manager + * @max_payload_bytes: the amount of data (including CIP headers) per packet + * @speed: the speed (e.g., SCODE_400) at which the packets will be sent + * + * This function allocates one isochronous channel and enough bandwidth for the + * specified packet size. + * + * Returns the channel number that the caller must use for streaming, or + * a negative error code. Due to potentionally long delays, this function is + * interruptible and can return -ERESTARTSYS. On success, the caller is + * responsible for calling fw_iso_resources_update() on bus resets, and + * fw_iso_resources_free() when the resources are not longer needed. + */ +int fw_iso_resources_allocate(struct fw_iso_resources *r, + unsigned int max_payload_bytes, int speed) +{ + struct fw_card *card = fw_parent_device(r->unit)->card; + int bandwidth, channel, err; + + if (WARN_ON(r->allocated)) + return -EBADFD; + + r->bandwidth = packet_bandwidth(max_payload_bytes, speed); + +retry_after_bus_reset: + spin_lock_irq(&card->lock); + r->generation = card->generation; + r->bandwidth_overhead = current_bandwidth_overhead(card); + spin_unlock_irq(&card->lock); + + err = wait_isoch_resource_delay_after_bus_reset(card); + if (err < 0) + return err; + + mutex_lock(&r->mutex); + + bandwidth = r->bandwidth + r->bandwidth_overhead; + fw_iso_resource_manage(card, r->generation, r->channels_mask, + &channel, &bandwidth, true); + if (channel == -EAGAIN) { + mutex_unlock(&r->mutex); + goto retry_after_bus_reset; + } + if (channel >= 0) { + r->channel = channel; + r->allocated = true; + } else { + if (channel == -EBUSY) + dev_err(&r->unit->device, + "isochronous resources exhausted\n"); + else + dev_err(&r->unit->device, + "isochronous resource allocation failed\n"); + } + + mutex_unlock(&r->mutex); + + return channel; +} +EXPORT_SYMBOL(fw_iso_resources_allocate); + +/** + * fw_iso_resources_update - update resource allocations after a bus reset + * @r: the resource manager + * + * This function must be called from the driver's .update handler to reallocate + * any resources that were allocated before the bus reset. It is safe to call + * this function if no resources are currently allocated. + * + * Returns a negative error code on failure. If this happens, the caller must + * stop streaming. + */ +int fw_iso_resources_update(struct fw_iso_resources *r) +{ + struct fw_card *card = fw_parent_device(r->unit)->card; + int bandwidth, channel; + + mutex_lock(&r->mutex); + + if (!r->allocated) { + mutex_unlock(&r->mutex); + return 0; + } + + spin_lock_irq(&card->lock); + r->generation = card->generation; + r->bandwidth_overhead = current_bandwidth_overhead(card); + spin_unlock_irq(&card->lock); + + bandwidth = r->bandwidth + r->bandwidth_overhead; + + fw_iso_resource_manage(card, r->generation, 1uLL << r->channel, + &channel, &bandwidth, true); + /* + * When another bus reset happens, pretend that the allocation + * succeeded; we will try again for the new generation later. + */ + if (channel < 0 && channel != -EAGAIN) { + r->allocated = false; + if (channel == -EBUSY) + dev_err(&r->unit->device, + "isochronous resources exhausted\n"); + else + dev_err(&r->unit->device, + "isochronous resource allocation failed\n"); + } + + mutex_unlock(&r->mutex); + + return channel; +} +EXPORT_SYMBOL(fw_iso_resources_update); + +/** + * fw_iso_resources_free - frees allocated resources + * @r: the resource manager + * + * This function deallocates the channel and bandwidth, if allocated. + */ +void fw_iso_resources_free(struct fw_iso_resources *r) +{ + struct fw_card *card; + int bandwidth, channel; + + /* Not initialized. */ + if (r->unit == NULL) + return; + card = fw_parent_device(r->unit)->card; + + mutex_lock(&r->mutex); + + if (r->allocated) { + bandwidth = r->bandwidth + r->bandwidth_overhead; + fw_iso_resource_manage(card, r->generation, 1uLL << r->channel, + &channel, &bandwidth, false); + if (channel < 0) + dev_err(&r->unit->device, + "isochronous resource deallocation failed\n"); + + r->allocated = false; + } + + mutex_unlock(&r->mutex); +} +EXPORT_SYMBOL(fw_iso_resources_free); diff --git a/sound/firewire/iso-resources.h b/sound/firewire/iso-resources.h new file mode 100644 index 000000000..34f85e9e8 --- /dev/null +++ b/sound/firewire/iso-resources.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef SOUND_FIREWIRE_ISO_RESOURCES_H_INCLUDED +#define SOUND_FIREWIRE_ISO_RESOURCES_H_INCLUDED + +#include <linux/mutex.h> +#include <linux/types.h> + +struct fw_unit; + +/** + * struct fw_iso_resources - manages channel/bandwidth allocation + * @channels_mask: if the device does not support all channel numbers, set this + * bit mask to something else than the default (all ones) + * + * This structure manages (de)allocation of isochronous resources (channel and + * bandwidth) for one isochronous stream. + */ +struct fw_iso_resources { + u64 channels_mask; + /* private: */ + struct fw_unit *unit; + struct mutex mutex; + unsigned int channel; + unsigned int bandwidth; /* in bandwidth units, without overhead */ + unsigned int bandwidth_overhead; + int generation; /* in which allocation is valid */ + bool allocated; +}; + +int fw_iso_resources_init(struct fw_iso_resources *r, + struct fw_unit *unit); +void fw_iso_resources_destroy(struct fw_iso_resources *r); + +int fw_iso_resources_allocate(struct fw_iso_resources *r, + unsigned int max_payload_bytes, int speed); +int fw_iso_resources_update(struct fw_iso_resources *r); +void fw_iso_resources_free(struct fw_iso_resources *r); + +#endif diff --git a/sound/firewire/lib.c b/sound/firewire/lib.c new file mode 100644 index 000000000..85c4f4477 --- /dev/null +++ b/sound/firewire/lib.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * miscellaneous helper functions + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + */ + +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/module.h> +#include <linux/slab.h> +#include "lib.h" + +#define ERROR_RETRY_DELAY_MS 20 + +/** + * snd_fw_transaction - send a request and wait for its completion + * @unit: the driver's unit on the target device + * @tcode: the transaction code + * @offset: the address in the target's address space + * @buffer: input/output data + * @length: length of @buffer + * @flags: use %FW_FIXED_GENERATION and add the generation value to attempt the + * request only in that generation; use %FW_QUIET to suppress error + * messages + * + * Submits an asynchronous request to the target device, and waits for the + * response. The node ID and the current generation are derived from @unit. + * On a bus reset or an error, the transaction is retried a few times. + * Returns zero on success, or a negative error code. + */ +int snd_fw_transaction(struct fw_unit *unit, int tcode, + u64 offset, void *buffer, size_t length, + unsigned int flags) +{ + struct fw_device *device = fw_parent_device(unit); + int generation, rcode, tries = 0; + + generation = flags & FW_GENERATION_MASK; + for (;;) { + if (!(flags & FW_FIXED_GENERATION)) { + generation = device->generation; + smp_rmb(); /* node_id vs. generation */ + } + rcode = fw_run_transaction(device->card, tcode, + device->node_id, generation, + device->max_speed, offset, + buffer, length); + + if (rcode == RCODE_COMPLETE) + return 0; + + if (rcode == RCODE_GENERATION && (flags & FW_FIXED_GENERATION)) + return -EAGAIN; + + if (rcode_is_permanent_error(rcode) || ++tries >= 3) { + if (!(flags & FW_QUIET)) + dev_err(&unit->device, + "transaction failed: %s\n", + fw_rcode_string(rcode)); + return -EIO; + } + + msleep(ERROR_RETRY_DELAY_MS); + } +} +EXPORT_SYMBOL(snd_fw_transaction); + +#define PROBE_DELAY_MS (2 * MSEC_PER_SEC) + +/** + * snd_fw_schedule_registration - schedule work for sound card registration + * @unit: an instance for unit on IEEE 1394 bus + * @dwork: delayed work with callback function + * + * This function is not designed for general purposes. When new unit is + * connected to IEEE 1394 bus, the bus is under bus-reset state because of + * topological change. In this state, units tend to fail both of asynchronous + * and isochronous communication. To avoid this problem, this function is used + * to postpone sound card registration after the state. The callers must + * set up instance of delayed work in advance. + */ +void snd_fw_schedule_registration(struct fw_unit *unit, + struct delayed_work *dwork) +{ + u64 now, delay; + + now = get_jiffies_64(); + delay = fw_parent_device(unit)->card->reset_jiffies + + msecs_to_jiffies(PROBE_DELAY_MS); + + if (time_after64(delay, now)) + delay -= now; + else + delay = 0; + + mod_delayed_work(system_wq, dwork, delay); +} +EXPORT_SYMBOL(snd_fw_schedule_registration); + +MODULE_DESCRIPTION("FireWire audio helper functions"); +MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/firewire/lib.h b/sound/firewire/lib.h new file mode 100644 index 000000000..dc815dc39 --- /dev/null +++ b/sound/firewire/lib.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef SOUND_FIREWIRE_LIB_H_INCLUDED +#define SOUND_FIREWIRE_LIB_H_INCLUDED + +#include <linux/firewire-constants.h> +#include <linux/types.h> +#include <linux/sched.h> +#include <sound/rawmidi.h> + +struct fw_unit; + +#define FW_GENERATION_MASK 0x00ff +#define FW_FIXED_GENERATION 0x0100 +#define FW_QUIET 0x0200 + +int snd_fw_transaction(struct fw_unit *unit, int tcode, + u64 offset, void *buffer, size_t length, + unsigned int flags); + +/* returns true if retrying the transaction would not make sense */ +static inline bool rcode_is_permanent_error(int rcode) +{ + return rcode == RCODE_TYPE_ERROR || rcode == RCODE_ADDRESS_ERROR; +} + +void snd_fw_schedule_registration(struct fw_unit *unit, + struct delayed_work *dwork); + +#endif diff --git a/sound/firewire/motu/Makefile b/sound/firewire/motu/Makefile new file mode 100644 index 000000000..7c502d351 --- /dev/null +++ b/sound/firewire/motu/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +CFLAGS_amdtp-motu.o := -I$(src) + +snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \ + motu-proc.o motu-pcm.o motu-midi.o motu-hwdep.o \ + motu-protocol-v2.o motu-protocol-v3.o +obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o diff --git a/sound/firewire/motu/amdtp-motu-trace.h b/sound/firewire/motu/amdtp-motu-trace.h new file mode 100644 index 000000000..3d36f125c --- /dev/null +++ b/sound/firewire/motu/amdtp-motu-trace.h @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * amdtp-motu-trace.h - tracepoint definitions to dump a part of packet data + * + * Copyright (c) 2017 Takashi Sakamoto + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM snd_firewire_motu + +#if !defined(_SND_FIREWIRE_MOTU_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _SND_FIREWIRE_MOTU_TRACE_H + +#include <linux/tracepoint.h> + +static void copy_sph(u32 *frame, __be32 *buffer, unsigned int data_blocks, + unsigned int data_block_quadlets); +static void copy_message(u64 *frames, __be32 *buffer, unsigned int data_blocks, + unsigned int data_block_quadlets); + +TRACE_EVENT(data_block_sph, + TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer), + TP_ARGS(s, data_blocks, buffer), + TP_STRUCT__entry( + __field(int, src) + __field(int, dst) + __field(unsigned int, data_blocks) + __dynamic_array(u32, tstamps, data_blocks) + ), + TP_fast_assign( + if (s->direction == AMDTP_IN_STREAM) { + __entry->src = fw_parent_device(s->unit)->node_id; + __entry->dst = fw_parent_device(s->unit)->card->node_id; + } else { + __entry->src = fw_parent_device(s->unit)->card->node_id; + __entry->dst = fw_parent_device(s->unit)->node_id; + } + __entry->data_blocks = data_blocks; + copy_sph(__get_dynamic_array(tstamps), buffer, data_blocks, s->data_block_quadlets); + ), + TP_printk( + "%04x %04x %u %s", + __entry->src, + __entry->dst, + __entry->data_blocks, + __print_array(__get_dynamic_array(tstamps), __entry->data_blocks, 4) + ) +); + +TRACE_EVENT(data_block_message, + TP_PROTO(struct amdtp_stream *s, unsigned int data_blocks, __be32 *buffer), + TP_ARGS(s, data_blocks, buffer), + TP_STRUCT__entry( + __field(int, src) + __field(int, dst) + __field(unsigned int, data_blocks) + __dynamic_array(u64, messages, data_blocks) + ), + TP_fast_assign( + if (s->direction == AMDTP_IN_STREAM) { + __entry->src = fw_parent_device(s->unit)->node_id; + __entry->dst = fw_parent_device(s->unit)->card->node_id; + } else { + __entry->src = fw_parent_device(s->unit)->card->node_id; + __entry->dst = fw_parent_device(s->unit)->node_id; + } + __entry->data_blocks = data_blocks; + copy_message(__get_dynamic_array(messages), buffer, data_blocks, s->data_block_quadlets); + ), + TP_printk( + "%04x %04x %u %s", + __entry->src, + __entry->dst, + __entry->data_blocks, + __print_array(__get_dynamic_array(messages), __entry->data_blocks, 8) + ) +); + +#endif + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE amdtp-motu-trace +#include <trace/define_trace.h> diff --git a/sound/firewire/motu/amdtp-motu.c b/sound/firewire/motu/amdtp-motu.c new file mode 100644 index 000000000..edb31ac26 --- /dev/null +++ b/sound/firewire/motu/amdtp-motu.c @@ -0,0 +1,486 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * amdtp-motu.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#include <linux/slab.h> +#include <sound/pcm.h> +#include "motu.h" + +#define CREATE_TRACE_POINTS +#include "amdtp-motu-trace.h" + +#define CIP_FMT_MOTU 0x02 +#define CIP_FMT_MOTU_TX_V3 0x22 +#define MOTU_FDF_AM824 0x22 + +/* + * Nominally 3125 bytes/second, but the MIDI port's clock might be + * 1% too slow, and the bus clock 100 ppm too fast. + */ +#define MIDI_BYTES_PER_SECOND 3093 + +struct amdtp_motu { + /* For timestamp processing. */ + unsigned int quotient_ticks_per_event; + unsigned int remainder_ticks_per_event; + unsigned int next_ticks; + unsigned int next_accumulated; + unsigned int next_cycles; + unsigned int next_seconds; + + unsigned int pcm_chunks; + unsigned int pcm_byte_offset; + + struct snd_rawmidi_substream *midi; + unsigned int midi_ports; + unsigned int midi_flag_offset; + unsigned int midi_byte_offset; + + int midi_db_count; + unsigned int midi_db_interval; +}; + +int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int midi_ports, + struct snd_motu_packet_format *formats) +{ + static const struct { + unsigned int quotient_ticks_per_event; + unsigned int remainder_ticks_per_event; + } params[] = { + [CIP_SFC_44100] = { 557, 123 }, + [CIP_SFC_48000] = { 512, 0 }, + [CIP_SFC_88200] = { 278, 282 }, + [CIP_SFC_96000] = { 256, 0 }, + [CIP_SFC_176400] = { 139, 141 }, + [CIP_SFC_192000] = { 128, 0 }, + }; + struct amdtp_motu *p = s->protocol; + unsigned int pcm_chunks, data_chunks, data_block_quadlets; + unsigned int delay; + unsigned int mode; + int i, err; + + if (amdtp_stream_running(s)) + return -EBUSY; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + if (snd_motu_clock_rates[i] == rate) { + mode = i >> 1; + break; + } + } + if (i == ARRAY_SIZE(snd_motu_clock_rates)) + return -EINVAL; + + // Each data block includes SPH in its head. Data chunks follow with + // 3 byte alignment. Padding follows with zero to conform to quadlet + // alignment. + pcm_chunks = formats->pcm_chunks[mode]; + data_chunks = formats->msg_chunks + pcm_chunks; + data_block_quadlets = 1 + DIV_ROUND_UP(data_chunks * 3, 4); + + err = amdtp_stream_set_parameters(s, rate, data_block_quadlets); + if (err < 0) + return err; + + p->pcm_chunks = pcm_chunks; + p->pcm_byte_offset = formats->pcm_byte_offset; + + p->midi_ports = midi_ports; + p->midi_flag_offset = formats->midi_flag_offset; + p->midi_byte_offset = formats->midi_byte_offset; + + p->midi_db_count = 0; + p->midi_db_interval = rate / MIDI_BYTES_PER_SECOND; + + /* IEEE 1394 bus requires. */ + delay = 0x2e00; + + /* For no-data or empty packets to adjust PCM sampling frequency. */ + delay += 8000 * 3072 * s->syt_interval / rate; + + p->next_seconds = 0; + p->next_cycles = delay / 3072; + p->quotient_ticks_per_event = params[s->sfc].quotient_ticks_per_event; + p->remainder_ticks_per_event = params[s->sfc].remainder_ticks_per_event; + p->next_ticks = delay % 3072; + p->next_accumulated = 0; + + return 0; +} + +static void read_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int data_blocks, + unsigned int pcm_frames) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels = p->pcm_chunks; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int pcm_buffer_pointer; + int remaining_frames; + u8 *byte; + 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 < data_blocks; ++i) { + byte = (u8 *)buffer + p->pcm_byte_offset; + + for (c = 0; c < channels; ++c) { + *dst = (byte[0] << 24) | + (byte[1] << 16) | + (byte[2] << 8); + byte += 3; + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int data_blocks, + unsigned int pcm_frames) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels = p->pcm_chunks; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int pcm_buffer_pointer; + int remaining_frames; + u8 *byte; + 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 < data_blocks; ++i) { + byte = (u8 *)buffer + p->pcm_byte_offset; + + for (c = 0; c < channels; ++c) { + byte[0] = (*src >> 24) & 0xff; + byte[1] = (*src >> 16) & 0xff; + byte[2] = (*src >> 8) & 0xff; + byte += 3; + src++; + } + + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int channels, i, c; + u8 *byte; + + channels = p->pcm_chunks; + + for (i = 0; i < data_blocks; ++i) { + byte = (u8 *)buffer + p->pcm_byte_offset; + + for (c = 0; c < channels; ++c) { + byte[0] = 0; + byte[1] = 0; + byte[2] = 0; + byte += 3; + } + + buffer += s->data_block_quadlets; + } +} + +int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + /* TODO: how to set an constraint for exactly 24bit PCM sample? */ + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + return err; + + return amdtp_stream_add_pcm_hw_constraints(s, runtime); +} + +void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi) +{ + struct amdtp_motu *p = s->protocol; + + if (port < p->midi_ports) + WRITE_ONCE(p->midi, midi); +} + +static void write_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + struct snd_rawmidi_substream *midi = READ_ONCE(p->midi); + u8 *b; + int i; + + for (i = 0; i < data_blocks; i++) { + b = (u8 *)buffer; + + if (midi && p->midi_db_count == 0 && + snd_rawmidi_transmit(midi, b + p->midi_byte_offset, 1) == 1) { + b[p->midi_flag_offset] = 0x01; + } else { + b[p->midi_byte_offset] = 0x00; + b[p->midi_flag_offset] = 0x00; + } + + buffer += s->data_block_quadlets; + + if (--p->midi_db_count < 0) + p->midi_db_count = p->midi_db_interval; + } +} + +static void read_midi_messages(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + struct snd_rawmidi_substream *midi; + u8 *b; + int i; + + for (i = 0; i < data_blocks; i++) { + b = (u8 *)buffer; + midi = READ_ONCE(p->midi); + + if (midi && (b[p->midi_flag_offset] & 0x01)) + snd_rawmidi_receive(midi, b + p->midi_byte_offset, 1); + + buffer += s->data_block_quadlets; + } +} + +/* For tracepoints. */ +static void __maybe_unused copy_sph(u32 *frames, __be32 *buffer, + unsigned int data_blocks, + unsigned int data_block_quadlets) +{ + unsigned int i; + + for (i = 0; i < data_blocks; ++i) { + *frames = be32_to_cpu(*buffer); + buffer += data_block_quadlets; + frames++; + } +} + +/* For tracepoints. */ +static void __maybe_unused copy_message(u64 *frames, __be32 *buffer, + unsigned int data_blocks, + unsigned int data_block_quadlets) +{ + unsigned int i; + + /* This is just for v2/v3 protocol. */ + for (i = 0; i < data_blocks; ++i) { + *frames = (be32_to_cpu(buffer[1]) << 16) | + (be32_to_cpu(buffer[2]) >> 16); + buffer += data_block_quadlets; + frames++; + } +} + +static void probe_tracepoints_events(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets) +{ + int i; + + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buf = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; + + trace_data_block_sph(s, data_blocks, buf); + trace_data_block_message(s, data_blocks, buf); + } +} + +static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) +{ + struct amdtp_motu *p = s->protocol; + unsigned int pcm_frames = 0; + int i; + + // For data block processing. + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buf = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; + + if (pcm) { + read_pcm_s32(s, pcm, buf, data_blocks, pcm_frames); + pcm_frames += data_blocks; + } + + if (p->midi_ports) + read_midi_messages(s, buf, data_blocks); + } + + // For tracepoints. + if (trace_data_block_sph_enabled() || + trace_data_block_message_enabled()) + probe_tracepoints_events(s, descs, packets); + + return pcm_frames; +} + +static inline void compute_next_elapse_from_start(struct amdtp_motu *p) +{ + p->next_accumulated += p->remainder_ticks_per_event; + if (p->next_accumulated >= 441) { + p->next_accumulated -= 441; + p->next_ticks++; + } + + p->next_ticks += p->quotient_ticks_per_event; + if (p->next_ticks >= 3072) { + p->next_ticks -= 3072; + p->next_cycles++; + } + + if (p->next_cycles >= 8000) { + p->next_cycles -= 8000; + p->next_seconds++; + } + + if (p->next_seconds >= 128) + p->next_seconds -= 128; +} + +static void write_sph(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_motu *p = s->protocol; + unsigned int next_cycles; + unsigned int i; + u32 sph; + + for (i = 0; i < data_blocks; i++) { + next_cycles = (s->start_cycle + p->next_cycles) % 8000; + sph = ((next_cycles << 12) | p->next_ticks) & 0x01ffffff; + *buffer = cpu_to_be32(sph); + + compute_next_elapse_from_start(p); + + buffer += s->data_block_quadlets; + } +} + +static unsigned int process_it_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) +{ + struct amdtp_motu *p = s->protocol; + unsigned int pcm_frames = 0; + int i; + + // For data block processing. + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buf = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; + + if (pcm) { + write_pcm_s32(s, pcm, buf, data_blocks, pcm_frames); + pcm_frames += data_blocks; + } else { + write_pcm_silence(s, buf, data_blocks); + } + + if (p->midi_ports) + write_midi_messages(s, buf, data_blocks); + + // TODO: how to interact control messages between userspace? + + write_sph(s, buf, data_blocks); + } + + // For tracepoints. + if (trace_data_block_sph_enabled() || + trace_data_block_message_enabled()) + probe_tracepoints_events(s, descs, packets); + + return pcm_frames; +} + +int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, + const struct snd_motu_spec *spec) +{ + amdtp_stream_process_ctx_payloads_t process_ctx_payloads; + int fmt = CIP_FMT_MOTU; + int flags = CIP_BLOCKING; + int err; + + if (dir == AMDTP_IN_STREAM) { + process_ctx_payloads = process_ir_ctx_payloads; + + /* + * Units of version 3 transmits packets with invalid CIP header + * against IEC 61883-1. + */ + if (spec->protocol_version == SND_MOTU_PROTOCOL_V3) { + flags |= CIP_WRONG_DBS | + CIP_SKIP_DBC_ZERO_CHECK | + CIP_HEADER_WITHOUT_EOH; + fmt = CIP_FMT_MOTU_TX_V3; + } + + if (spec == &snd_motu_spec_8pre || + spec == &snd_motu_spec_ultralite) { + // 8pre has some quirks. + flags |= CIP_WRONG_DBS | + CIP_SKIP_DBC_ZERO_CHECK; + } + } else { + process_ctx_payloads = process_it_ctx_payloads; + flags |= CIP_DBC_IS_END_EVENT; + } + + err = amdtp_stream_init(s, unit, dir, flags, fmt, process_ctx_payloads, + sizeof(struct amdtp_motu)); + if (err < 0) + return err; + + s->sph = 1; + + if (dir == AMDTP_OUT_STREAM) { + // Use fixed value for FDF field. + s->ctx_data.rx.fdf = MOTU_FDF_AM824; + // Not used. + s->ctx_data.rx.syt_override = 0xffff; + } + + return 0; +} diff --git a/sound/firewire/motu/motu-hwdep.c b/sound/firewire/motu/motu-hwdep.c new file mode 100644 index 000000000..0764a4770 --- /dev/null +++ b/sound/firewire/motu/motu-hwdep.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * motu-hwdep.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +/* + * This codes have five functionalities. + * + * 1.get information about firewire node + * 2.get notification about starting/stopping stream + * 3.lock/unlock streaming + * + */ + +#include "motu.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_motu *motu = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&motu->lock); + + while (!motu->dev_lock_changed && motu->msg == 0) { + prepare_to_wait(&motu->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&motu->lock); + schedule(); + finish_wait(&motu->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&motu->lock); + } + + memset(&event, 0, sizeof(event)); + if (motu->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (motu->dev_lock_count > 0); + motu->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } else { + event.motu_notification.type = SNDRV_FIREWIRE_EVENT_MOTU_NOTIFICATION; + event.motu_notification.message = motu->msg; + motu->msg = 0; + + count = min_t(long, count, sizeof(event.motu_notification)); + } + + spin_unlock_irq(&motu->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_motu *motu = hwdep->private_data; + __poll_t events; + + poll_wait(file, &motu->hwdep_wait, wait); + + spin_lock_irq(&motu->lock); + if (motu->dev_lock_changed || motu->msg) + events = EPOLLIN | EPOLLRDNORM; + else + events = 0; + spin_unlock_irq(&motu->lock); + + return events | EPOLLOUT; +} + +static int hwdep_get_info(struct snd_motu *motu, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(motu->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_MOTU; + 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]); + strlcpy(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_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count == 0) { + motu->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&motu->lock); + + return err; +} + +static int hwdep_unlock(struct snd_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count == -1) { + motu->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&motu->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_motu *motu = hwdep->private_data; + + spin_lock_irq(&motu->lock); + if (motu->dev_lock_count == -1) + motu->dev_lock_count = 0; + spin_unlock_irq(&motu->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_motu *motu = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(motu, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(motu); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(motu); + 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_motu_create_hwdep_device(struct snd_motu *motu) +{ + static const struct snd_hwdep_ops ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(motu->card, motu->card->driver, 0, &hwdep); + if (err < 0) + return err; + + strcpy(hwdep->name, "MOTU"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_MOTU; + hwdep->ops = ops; + hwdep->private_data = motu; + hwdep->exclusive = true; + + return 0; +} diff --git a/sound/firewire/motu/motu-midi.c b/sound/firewire/motu/motu-midi.c new file mode 100644 index 000000000..2365f7dfd --- /dev/null +++ b/sound/firewire/motu/motu-midi.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * motu-midi.h - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ +#include "motu.h" + +static int midi_open(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + int err; + + err = snd_motu_stream_lock_try(motu); + if (err < 0) + return err; + + mutex_lock(&motu->mutex); + + err = snd_motu_stream_reserve_duplex(motu, 0, 0, 0); + if (err >= 0) { + ++motu->substreams_counter; + err = snd_motu_stream_start_duplex(motu); + if (err < 0) + --motu->substreams_counter; + } + + mutex_unlock(&motu->mutex); + + if (err < 0) + snd_motu_stream_lock_release(motu); + + return err; +} + +static int midi_close(struct snd_rawmidi_substream *substream) +{ + struct snd_motu *motu = substream->rmidi->private_data; + + mutex_lock(&motu->mutex); + + --motu->substreams_counter; + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + snd_motu_stream_lock_release(motu); + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_motu *motu = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&motu->lock, flags); + + if (up) + amdtp_motu_midi_trigger(&motu->tx_stream, substrm->number, + substrm); + else + amdtp_motu_midi_trigger(&motu->tx_stream, substrm->number, + NULL); + + spin_unlock_irqrestore(&motu->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_motu *motu = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&motu->lock, flags); + + if (up) + amdtp_motu_midi_trigger(&motu->rx_stream, substrm->number, + substrm); + else + amdtp_motu_midi_trigger(&motu->rx_stream, substrm->number, + NULL); + + spin_unlock_irqrestore(&motu->lock, flags); +} + +static void set_midi_substream_names(struct snd_motu *motu, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", motu->card->shortname, subs->number + 1); + } +} + +int snd_motu_create_midi_devices(struct snd_motu *motu) +{ + static const struct snd_rawmidi_ops capture_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_capture_trigger, + }; + static const struct snd_rawmidi_ops playback_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_playback_trigger, + }; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + /* create midi ports */ + err = snd_rawmidi_new(motu->card, motu->card->driver, 0, 1, 1, &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", motu->card->shortname); + rmidi->private_data = motu; + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &capture_ops); + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + set_midi_substream_names(motu, str); + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &playback_ops); + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + set_midi_substream_names(motu, str); + + return 0; +} diff --git a/sound/firewire/motu/motu-pcm.c b/sound/firewire/motu/motu-pcm.c new file mode 100644 index 000000000..8e1437371 --- /dev/null +++ b/sound/firewire/motu/motu-pcm.c @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * motu-pcm.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#include <sound/pcm_params.h> +#include "motu.h" + +static int motu_rate_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_motu_packet_format *formats = rule->private; + + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval rates = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, pcm_channels, rate, mode; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + rate = snd_motu_clock_rates[i]; + mode = i / 2; + + pcm_channels = formats->pcm_chunks[mode]; + if (!snd_interval_test(c, pcm_channels)) + continue; + + rates.min = min(rates.min, rate); + rates.max = max(rates.max, rate); + } + + return snd_interval_refine(r, &rates); +} + +static int motu_channels_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_motu_packet_format *formats = rule->private; + + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval channels = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, pcm_channels, rate, mode; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + rate = snd_motu_clock_rates[i]; + mode = i / 2; + + if (!snd_interval_test(r, rate)) + continue; + + pcm_channels = formats->pcm_chunks[mode]; + channels.min = min(channels.min, pcm_channels); + channels.max = max(channels.max, pcm_channels); + } + + return snd_interval_refine(c, &channels); +} + +static void limit_channels_and_rates(struct snd_motu *motu, + struct snd_pcm_runtime *runtime, + struct snd_motu_packet_format *formats) +{ + struct snd_pcm_hardware *hw = &runtime->hw; + unsigned int i, pcm_channels, rate, mode; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + rate = snd_motu_clock_rates[i]; + mode = i / 2; + + pcm_channels = formats->pcm_chunks[mode]; + if (pcm_channels == 0) + continue; + + hw->rates |= snd_pcm_rate_to_rate_bit(rate); + hw->channels_min = min(hw->channels_min, pcm_channels); + hw->channels_max = max(hw->channels_max, pcm_channels); + } + + snd_pcm_limit_hw_rates(runtime); +} + +static int init_hw_info(struct snd_motu *motu, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hardware *hw = &runtime->hw; + struct amdtp_stream *stream; + struct snd_motu_packet_format *formats; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + hw->formats = SNDRV_PCM_FMTBIT_S32; + stream = &motu->tx_stream; + formats = &motu->tx_packet_formats; + } else { + hw->formats = SNDRV_PCM_FMTBIT_S32; + stream = &motu->rx_stream; + formats = &motu->rx_packet_formats; + } + + limit_channels_and_rates(motu, runtime, formats); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + motu_rate_constraint, formats, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + motu_channels_constraint, formats, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + + return amdtp_motu_add_pcm_hw_constraints(stream, runtime); +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + struct amdtp_domain *d = &motu->domain; + enum snd_motu_clock_source src; + int err; + + err = snd_motu_stream_lock_try(motu); + if (err < 0) + return err; + + mutex_lock(&motu->mutex); + + err = snd_motu_stream_cache_packet_formats(motu); + if (err < 0) + goto err_locked; + + err = init_hw_info(motu, substream); + if (err < 0) + goto err_locked; + + err = snd_motu_protocol_get_clock_source(motu, &src); + if (err < 0) + goto err_locked; + + // 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_MOTU_CLOCK_SOURCE_INTERNAL && + src != SND_MOTU_CLOCK_SOURCE_SPH) || + (motu->substreams_counter > 0 && d->events_per_period > 0)) { + unsigned int frames_per_period = d->events_per_period; + unsigned int frames_per_buffer = d->events_per_buffer; + unsigned int rate; + + err = snd_motu_protocol_get_clock_rate(motu, &rate); + if (err < 0) + goto err_locked; + + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + + if (frames_per_period > 0) { + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + frames_per_period, frames_per_period); + if (err < 0) + goto err_locked; + + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + frames_per_buffer, frames_per_buffer); + if (err < 0) + goto err_locked; + } + } + + snd_pcm_set_sync(substream); + + mutex_unlock(&motu->mutex); + + return 0; +err_locked: + mutex_unlock(&motu->mutex); + snd_motu_stream_lock_release(motu); + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + snd_motu_stream_lock_release(motu); + + return 0; +} + +static int pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_motu *motu = substream->private_data; + int err = 0; + + if (substream->runtime->status->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); + + mutex_lock(&motu->mutex); + err = snd_motu_stream_reserve_duplex(motu, rate, + frames_per_period, frames_per_buffer); + if (err >= 0) + ++motu->substreams_counter; + mutex_unlock(&motu->mutex); + } + + return err; +} + +static int pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + mutex_lock(&motu->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + --motu->substreams_counter; + + snd_motu_stream_stop_duplex(motu); + + mutex_unlock(&motu->mutex); + + return 0; +} + +static int capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + int err; + + mutex_lock(&motu->mutex); + err = snd_motu_stream_start_duplex(motu); + mutex_unlock(&motu->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(&motu->tx_stream); + + return 0; +} +static int playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + int err; + + mutex_lock(&motu->mutex); + err = snd_motu_stream_start_duplex(motu); + mutex_unlock(&motu->mutex); + if (err >= 0) + amdtp_stream_pcm_prepare(&motu->rx_stream); + + return err; +} + +static int capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_motu *motu = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&motu->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&motu->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_motu *motu = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&motu->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&motu->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t capture_pointer(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_domain_stream_pcm_pointer(&motu->domain, &motu->tx_stream); +} +static snd_pcm_uframes_t playback_pointer(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_domain_stream_pcm_pointer(&motu->domain, &motu->rx_stream); +} + +static int capture_ack(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_domain_stream_pcm_ack(&motu->domain, &motu->tx_stream); +} + +static int playback_ack(struct snd_pcm_substream *substream) +{ + struct snd_motu *motu = substream->private_data; + + return amdtp_domain_stream_pcm_ack(&motu->domain, &motu->rx_stream); +} + +int snd_motu_create_pcm_devices(struct snd_motu *motu) +{ + static const struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .hw_params = pcm_hw_params, + .hw_free = pcm_hw_free, + .prepare = capture_prepare, + .trigger = capture_trigger, + .pointer = capture_pointer, + .ack = capture_ack, + }; + static const struct snd_pcm_ops playback_ops = { + .open = pcm_open, + .close = pcm_close, + .hw_params = pcm_hw_params, + .hw_free = pcm_hw_free, + .prepare = playback_prepare, + .trigger = playback_trigger, + .pointer = playback_pointer, + .ack = playback_ack, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(motu->card, motu->card->driver, 0, 1, 1, &pcm); + if (err < 0) + return err; + pcm->private_data = motu; + strcpy(pcm->name, motu->card->shortname); + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0); + + return 0; +} diff --git a/sound/firewire/motu/motu-proc.c b/sound/firewire/motu/motu-proc.c new file mode 100644 index 000000000..f009cf7aa --- /dev/null +++ b/sound/firewire/motu/motu-proc.c @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * motu-proc.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#include "./motu.h" + +static const char *const clock_names[] = { + [SND_MOTU_CLOCK_SOURCE_INTERNAL] = "Internal", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB] = "ADAT on Dsub-9pin interface", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT] = "ADAT on optical interface", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A] = "ADAT on optical interface A", + [SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B] = "ADAT on optical interface B", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT] = "S/PDIF on optical interface", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A] = "S/PDIF on optical interface A", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B] = "S/PDIF on optical interface B", + [SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX] = "S/PDIF on coaxial interface", + [SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR] = "AESEBU on XLR interface", + [SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC] = "Word clock on BNC interface", + [SND_MOTU_CLOCK_SOURCE_SPH] = "Source packet header", + [SND_MOTU_CLOCK_SOURCE_UNKNOWN] = "Unknown", +}; + +static void proc_read_clock(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + + struct snd_motu *motu = entry->private_data; + unsigned int rate; + enum snd_motu_clock_source source; + + if (snd_motu_protocol_get_clock_rate(motu, &rate) < 0) + return; + if (snd_motu_protocol_get_clock_source(motu, &source) < 0) + return; + + snd_iprintf(buffer, "Rate:\t%d\n", rate); + snd_iprintf(buffer, "Source:\t%s\n", clock_names[source]); +} + +static void proc_read_format(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_motu *motu = entry->private_data; + unsigned int mode; + struct snd_motu_packet_format *formats; + int i; + + if (snd_motu_protocol_cache_packet_formats(motu) < 0) + return; + + snd_iprintf(buffer, "tx:\tmsg\tfixed\ttotal\n"); + for (i = 0; i < SND_MOTU_CLOCK_RATE_COUNT; ++i) { + mode = i >> 1; + + formats = &motu->tx_packet_formats; + snd_iprintf(buffer, + "%u:\t%u\t%u\t%u\n", + snd_motu_clock_rates[i], + formats->msg_chunks, + motu->spec->tx_fixed_pcm_chunks[mode], + formats->pcm_chunks[mode]); + } + + snd_iprintf(buffer, "rx:\tmsg\tfixed\ttotal\n"); + for (i = 0; i < SND_MOTU_CLOCK_RATE_COUNT; ++i) { + mode = i >> 1; + + formats = &motu->rx_packet_formats; + snd_iprintf(buffer, + "%u:\t%u\t%u\t%u\n", + snd_motu_clock_rates[i], + formats->msg_chunks, + motu->spec->rx_fixed_pcm_chunks[mode], + formats->pcm_chunks[mode]); + } +} + +static void add_node(struct snd_motu *motu, 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(motu->card, name, root); + if (entry) + snd_info_set_text_ops(entry, motu, op); +} + +void snd_motu_proc_init(struct snd_motu *motu) +{ + 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(motu->card, "firewire", + motu->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | 0555; + + add_node(motu, root, "clock", proc_read_clock); + add_node(motu, root, "format", proc_read_format); +} diff --git a/sound/firewire/motu/motu-protocol-v2.c b/sound/firewire/motu/motu-protocol-v2.c new file mode 100644 index 000000000..f0a0ecad4 --- /dev/null +++ b/sound/firewire/motu/motu-protocol-v2.c @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * motu-protocol-v2.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#include "motu.h" + +#define V2_CLOCK_STATUS_OFFSET 0x0b14 +#define V2_CLOCK_RATE_MASK 0x00000038 +#define V2_CLOCK_RATE_SHIFT 3 +#define V2_CLOCK_SRC_MASK 0x00000007 +#define V2_CLOCK_SRC_SHIFT 0 +#define V2_CLOCK_FETCH_ENABLE 0x02000000 +#define V2_CLOCK_MODEL_SPECIFIC 0x04000000 + +#define V2_IN_OUT_CONF_OFFSET 0x0c04 +#define V2_OPT_OUT_IFACE_MASK 0x00000c00 +#define V2_OPT_OUT_IFACE_SHIFT 10 +#define V2_OPT_IN_IFACE_MASK 0x00000300 +#define V2_OPT_IN_IFACE_SHIFT 8 +#define V2_OPT_IFACE_MODE_NONE 0 +#define V2_OPT_IFACE_MODE_ADAT 1 +#define V2_OPT_IFACE_MODE_SPDIF 2 + +static int get_clock_rate(u32 data, unsigned int *rate) +{ + unsigned int index = (data & V2_CLOCK_RATE_MASK) >> V2_CLOCK_RATE_SHIFT; + if (index >= ARRAY_SIZE(snd_motu_clock_rates)) + return -EIO; + + *rate = snd_motu_clock_rates[index]; + + return 0; +} + +int snd_motu_protocol_v2_get_clock_rate(struct snd_motu *motu, + unsigned int *rate) +{ + __be32 reg; + int err; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + return get_clock_rate(be32_to_cpu(reg), rate); +} + +int snd_motu_protocol_v2_set_clock_rate(struct snd_motu *motu, + unsigned int rate) +{ + __be32 reg; + u32 data; + int i; + int err; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + if (snd_motu_clock_rates[i] == rate) + break; + } + if (i == ARRAY_SIZE(snd_motu_clock_rates)) + return -EINVAL; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~V2_CLOCK_RATE_MASK; + data |= i << V2_CLOCK_RATE_SHIFT; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); +} + +static int detect_clock_source_optical_model(struct snd_motu *motu, u32 data, + enum snd_motu_clock_source *src) +{ + switch (data) { + case 0: + *src = SND_MOTU_CLOCK_SOURCE_INTERNAL; + break; + case 1: + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT; + break; + case 2: + { + __be32 reg; + + // To check the configuration of optical interface. + int err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, ®, sizeof(reg)); + if (err < 0) + return err; + + if (((data & V2_OPT_IN_IFACE_MASK) >> V2_OPT_IN_IFACE_SHIFT) == V2_OPT_IFACE_MODE_SPDIF) + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT; + else + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX; + break; + } + case 3: + *src = SND_MOTU_CLOCK_SOURCE_SPH; + break; + case 4: + *src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC; + break; + case 5: + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB; + break; + default: + *src = SND_MOTU_CLOCK_SOURCE_UNKNOWN; + break; + } + + return 0; +} + +static int v2_detect_clock_source(struct snd_motu *motu, u32 data, + enum snd_motu_clock_source *src) +{ + switch (data) { + case 0: + *src = SND_MOTU_CLOCK_SOURCE_INTERNAL; + break; + case 2: + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX; + break; + case 3: + *src = SND_MOTU_CLOCK_SOURCE_SPH; + break; + case 4: + *src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC; + break; + default: + *src = SND_MOTU_CLOCK_SOURCE_UNKNOWN; + break; + } + + return 0; +} + +static int get_clock_source(struct snd_motu *motu, u32 data, + enum snd_motu_clock_source *src) +{ + data &= V2_CLOCK_SRC_MASK; + if (motu->spec == &snd_motu_spec_828mk2 || + motu->spec == &snd_motu_spec_traveler) + return detect_clock_source_optical_model(motu, data, src); + else + return v2_detect_clock_source(motu, data, src); +} + +int snd_motu_protocol_v2_get_clock_source(struct snd_motu *motu, + enum snd_motu_clock_source *src) +{ + __be32 reg; + int err; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + return get_clock_source(motu, be32_to_cpu(reg), src); +} + +// Expected for Traveler and 896HD, which implements Altera Cyclone EP1C3. +static int switch_fetching_mode_cyclone(struct snd_motu *motu, u32 *data, + bool enable) +{ + *data |= V2_CLOCK_MODEL_SPECIFIC; + + return 0; +} + +// For UltraLite and 8pre, which implements Xilinx Spartan XC3S200. +static int switch_fetching_mode_spartan(struct snd_motu *motu, u32 *data, + bool enable) +{ + unsigned int rate; + enum snd_motu_clock_source src; + int err; + + err = get_clock_source(motu, *data, &src); + if (err < 0) + return err; + + err = get_clock_rate(*data, &rate); + if (err < 0) + return err; + + if (src == SND_MOTU_CLOCK_SOURCE_SPH && rate > 48000) + *data |= V2_CLOCK_MODEL_SPECIFIC; + + return 0; +} + +int snd_motu_protocol_v2_switch_fetching_mode(struct snd_motu *motu, + bool enable) +{ + if (motu->spec == &snd_motu_spec_828mk2) { + // 828mkII implements Altera ACEX 1K EP1K30. Nothing to do. + return 0; + } else { + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, + ®, sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~(V2_CLOCK_FETCH_ENABLE | V2_CLOCK_MODEL_SPECIFIC); + if (enable) + data |= V2_CLOCK_FETCH_ENABLE; + + if (motu->spec == &snd_motu_spec_traveler) + err = switch_fetching_mode_cyclone(motu, &data, enable); + else + err = switch_fetching_mode_spartan(motu, &data, enable); + if (err < 0) + return err; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, V2_CLOCK_STATUS_OFFSET, + ®, sizeof(reg)); + } +} + +static int detect_packet_formats_828mk2(struct snd_motu *motu, u32 data) +{ + if (((data & V2_OPT_IN_IFACE_MASK) >> V2_OPT_IN_IFACE_SHIFT) == + V2_OPT_IFACE_MODE_ADAT) { + motu->tx_packet_formats.pcm_chunks[0] += 8; + motu->tx_packet_formats.pcm_chunks[1] += 4; + } + + if (((data & V2_OPT_OUT_IFACE_MASK) >> V2_OPT_OUT_IFACE_SHIFT) == + V2_OPT_IFACE_MODE_ADAT) { + motu->rx_packet_formats.pcm_chunks[0] += 8; + motu->rx_packet_formats.pcm_chunks[1] += 4; + } + + return 0; +} + +static int detect_packet_formats_traveler(struct snd_motu *motu, u32 data) +{ + if (((data & V2_OPT_IN_IFACE_MASK) >> V2_OPT_IN_IFACE_SHIFT) == + V2_OPT_IFACE_MODE_ADAT) { + motu->tx_packet_formats.pcm_chunks[0] += 8; + motu->tx_packet_formats.pcm_chunks[1] += 4; + } + + if (((data & V2_OPT_OUT_IFACE_MASK) >> V2_OPT_OUT_IFACE_SHIFT) == + V2_OPT_IFACE_MODE_ADAT) { + motu->rx_packet_formats.pcm_chunks[0] += 8; + motu->rx_packet_formats.pcm_chunks[1] += 4; + } + + return 0; +} + +static int detect_packet_formats_8pre(struct snd_motu *motu, u32 data) +{ + if (((data & V2_OPT_IN_IFACE_MASK) >> V2_OPT_IN_IFACE_SHIFT) == + V2_OPT_IFACE_MODE_ADAT) { + motu->tx_packet_formats.pcm_chunks[0] += 8; + motu->tx_packet_formats.pcm_chunks[1] += 8; + } + + if (((data & V2_OPT_OUT_IFACE_MASK) >> V2_OPT_OUT_IFACE_SHIFT) == + V2_OPT_IFACE_MODE_ADAT) { + motu->rx_packet_formats.pcm_chunks[0] += 8; + motu->rx_packet_formats.pcm_chunks[1] += 8; + } + + return 0; +} + +int snd_motu_protocol_v2_cache_packet_formats(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + motu->tx_packet_formats.pcm_byte_offset = 10; + motu->rx_packet_formats.pcm_byte_offset = 10; + + motu->tx_packet_formats.msg_chunks = 2; + motu->rx_packet_formats.msg_chunks = 2; + + err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + memcpy(motu->tx_packet_formats.pcm_chunks, + motu->spec->tx_fixed_pcm_chunks, + sizeof(motu->tx_packet_formats.pcm_chunks)); + memcpy(motu->rx_packet_formats.pcm_chunks, + motu->spec->rx_fixed_pcm_chunks, + sizeof(motu->rx_packet_formats.pcm_chunks)); + + if (motu->spec == &snd_motu_spec_828mk2) + return detect_packet_formats_828mk2(motu, data); + else if (motu->spec == &snd_motu_spec_traveler) + return detect_packet_formats_traveler(motu, data); + else if (motu->spec == &snd_motu_spec_8pre) + return detect_packet_formats_8pre(motu, data); + else + return 0; +} + +const struct snd_motu_spec snd_motu_spec_828mk2 = { + .name = "828mk2", + .protocol_version = SND_MOTU_PROTOCOL_V2, + .flags = SND_MOTU_SPEC_RX_MIDI_2ND_Q | + SND_MOTU_SPEC_TX_MIDI_2ND_Q, + .tx_fixed_pcm_chunks = {14, 14, 0}, + .rx_fixed_pcm_chunks = {14, 14, 0}, +}; + +const struct snd_motu_spec snd_motu_spec_traveler = { + .name = "Traveler", + .protocol_version = SND_MOTU_PROTOCOL_V2, + .flags = SND_MOTU_SPEC_RX_MIDI_2ND_Q | + SND_MOTU_SPEC_TX_MIDI_2ND_Q, + .tx_fixed_pcm_chunks = {14, 14, 8}, + .rx_fixed_pcm_chunks = {14, 14, 8}, +}; + +const struct snd_motu_spec snd_motu_spec_ultralite = { + .name = "UltraLite", + .protocol_version = SND_MOTU_PROTOCOL_V2, + .flags = SND_MOTU_SPEC_RX_MIDI_2ND_Q | + SND_MOTU_SPEC_TX_MIDI_2ND_Q, + .tx_fixed_pcm_chunks = {14, 14, 0}, + .rx_fixed_pcm_chunks = {14, 14, 0}, +}; + +const struct snd_motu_spec snd_motu_spec_8pre = { + .name = "8pre", + .protocol_version = SND_MOTU_PROTOCOL_V2, + .flags = SND_MOTU_SPEC_RX_MIDI_2ND_Q | + SND_MOTU_SPEC_TX_MIDI_2ND_Q, + // Two dummy chunks always in the end of data block. + .tx_fixed_pcm_chunks = {10, 10, 0}, + .rx_fixed_pcm_chunks = {6, 6, 0}, +}; diff --git a/sound/firewire/motu/motu-protocol-v3.c b/sound/firewire/motu/motu-protocol-v3.c new file mode 100644 index 000000000..4e6b0e449 --- /dev/null +++ b/sound/firewire/motu/motu-protocol-v3.c @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * motu-protocol-v3.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#include <linux/delay.h> +#include "motu.h" + +#define V3_CLOCK_STATUS_OFFSET 0x0b14 +#define V3_FETCH_PCM_FRAMES 0x02000000 +#define V3_CLOCK_RATE_MASK 0x0000ff00 +#define V3_CLOCK_RATE_SHIFT 8 +#define V3_CLOCK_SOURCE_MASK 0x000000ff + +#define V3_OPT_IFACE_MODE_OFFSET 0x0c94 +#define V3_ENABLE_OPT_IN_IFACE_A 0x00000001 +#define V3_ENABLE_OPT_IN_IFACE_B 0x00000002 +#define V3_ENABLE_OPT_OUT_IFACE_A 0x00000100 +#define V3_ENABLE_OPT_OUT_IFACE_B 0x00000200 +#define V3_NO_ADAT_OPT_IN_IFACE_A 0x00010000 +#define V3_NO_ADAT_OPT_IN_IFACE_B 0x00100000 +#define V3_NO_ADAT_OPT_OUT_IFACE_A 0x00040000 +#define V3_NO_ADAT_OPT_OUT_IFACE_B 0x00400000 + +#define V3_MSG_FLAG_CLK_CHANGED 0x00000002 +#define V3_CLK_WAIT_MSEC 4000 + +int snd_motu_protocol_v3_get_clock_rate(struct snd_motu *motu, + unsigned int *rate) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data = (data & V3_CLOCK_RATE_MASK) >> V3_CLOCK_RATE_SHIFT; + if (data >= ARRAY_SIZE(snd_motu_clock_rates)) + return -EIO; + + *rate = snd_motu_clock_rates[data]; + + return 0; +} + +int snd_motu_protocol_v3_set_clock_rate(struct snd_motu *motu, + unsigned int rate) +{ + __be32 reg; + u32 data; + bool need_to_wait; + int i, err; + + for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { + if (snd_motu_clock_rates[i] == rate) + break; + } + if (i == ARRAY_SIZE(snd_motu_clock_rates)) + return -EINVAL; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~(V3_CLOCK_RATE_MASK | V3_FETCH_PCM_FRAMES); + data |= i << V3_CLOCK_RATE_SHIFT; + + need_to_wait = data != be32_to_cpu(reg); + + reg = cpu_to_be32(data); + err = snd_motu_transaction_write(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + + if (need_to_wait) { + int result; + + motu->msg = 0; + result = wait_event_interruptible_timeout(motu->hwdep_wait, + motu->msg & V3_MSG_FLAG_CLK_CHANGED, + msecs_to_jiffies(V3_CLK_WAIT_MSEC)); + if (result < 0) + return result; + if (result == 0) + return -ETIMEDOUT; + } + + return 0; +} + +static int detect_clock_source_828mk3(struct snd_motu *motu, u32 data, + enum snd_motu_clock_source *src) +{ + switch (data) { + case 0x00: + *src = SND_MOTU_CLOCK_SOURCE_INTERNAL; + break; + case 0x01: + *src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC; + break; + case 0x02: + *src = SND_MOTU_CLOCK_SOURCE_SPH; + break; + case 0x10: + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX; + break; + case 0x18: + case 0x19: + { + __be32 reg; + u32 options; + int err; + + err = snd_motu_transaction_read(motu, + V3_OPT_IFACE_MODE_OFFSET, ®, sizeof(reg)); + if (err < 0) + return err; + options = be32_to_cpu(reg); + + if (data == 0x18) { + if (options & V3_NO_ADAT_OPT_IN_IFACE_A) + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A; + else + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A; + } else { + if (options & V3_NO_ADAT_OPT_IN_IFACE_B) + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B; + else + *src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B; + } + + break; + } + default: + *src = SND_MOTU_CLOCK_SOURCE_UNKNOWN; + break; + } + + return 0; +} + +static int v3_detect_clock_source(struct snd_motu *motu, u32 data, + enum snd_motu_clock_source *src) +{ + switch (data) { + case 0x00: + *src = SND_MOTU_CLOCK_SOURCE_INTERNAL; + break; + case 0x01: + *src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC; + break; + case 0x02: + *src = SND_MOTU_CLOCK_SOURCE_SPH; + break; + case 0x10: + *src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX; + break; + default: + *src = SND_MOTU_CLOCK_SOURCE_UNKNOWN; + break; + } + + return 0; +} + +int snd_motu_protocol_v3_get_clock_source(struct snd_motu *motu, + enum snd_motu_clock_source *src) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg) & V3_CLOCK_SOURCE_MASK; + + if (motu->spec == &snd_motu_spec_828mk3) + return detect_clock_source_828mk3(motu, data, src); + else + return v3_detect_clock_source(motu, data, src); +} + +int snd_motu_protocol_v3_switch_fetching_mode(struct snd_motu *motu, + bool enable) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return 0; + data = be32_to_cpu(reg); + + if (enable) + data |= V3_FETCH_PCM_FRAMES; + else + data &= ~V3_FETCH_PCM_FRAMES; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, V3_CLOCK_STATUS_OFFSET, ®, + sizeof(reg)); +} + +static int detect_packet_formats_828mk3(struct snd_motu *motu, u32 data) +{ + if (data & V3_ENABLE_OPT_IN_IFACE_A) { + if (data & V3_NO_ADAT_OPT_IN_IFACE_A) { + motu->tx_packet_formats.pcm_chunks[0] += 4; + motu->tx_packet_formats.pcm_chunks[1] += 4; + } else { + motu->tx_packet_formats.pcm_chunks[0] += 8; + motu->tx_packet_formats.pcm_chunks[1] += 4; + } + } + + if (data & V3_ENABLE_OPT_IN_IFACE_B) { + if (data & V3_NO_ADAT_OPT_IN_IFACE_B) { + motu->tx_packet_formats.pcm_chunks[0] += 4; + motu->tx_packet_formats.pcm_chunks[1] += 4; + } else { + motu->tx_packet_formats.pcm_chunks[0] += 8; + motu->tx_packet_formats.pcm_chunks[1] += 4; + } + } + + if (data & V3_ENABLE_OPT_OUT_IFACE_A) { + if (data & V3_NO_ADAT_OPT_OUT_IFACE_A) { + motu->rx_packet_formats.pcm_chunks[0] += 4; + motu->rx_packet_formats.pcm_chunks[1] += 4; + } else { + motu->rx_packet_formats.pcm_chunks[0] += 8; + motu->rx_packet_formats.pcm_chunks[1] += 4; + } + } + + if (data & V3_ENABLE_OPT_OUT_IFACE_B) { + if (data & V3_NO_ADAT_OPT_OUT_IFACE_B) { + motu->rx_packet_formats.pcm_chunks[0] += 4; + motu->rx_packet_formats.pcm_chunks[1] += 4; + } else { + motu->rx_packet_formats.pcm_chunks[0] += 8; + motu->rx_packet_formats.pcm_chunks[1] += 4; + } + } + + return 0; +} + +int snd_motu_protocol_v3_cache_packet_formats(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + motu->tx_packet_formats.pcm_byte_offset = 10; + motu->rx_packet_formats.pcm_byte_offset = 10; + + motu->tx_packet_formats.msg_chunks = 2; + motu->rx_packet_formats.msg_chunks = 2; + + err = snd_motu_transaction_read(motu, V3_OPT_IFACE_MODE_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + memcpy(motu->tx_packet_formats.pcm_chunks, + motu->spec->tx_fixed_pcm_chunks, + sizeof(motu->tx_packet_formats.pcm_chunks)); + memcpy(motu->rx_packet_formats.pcm_chunks, + motu->spec->rx_fixed_pcm_chunks, + sizeof(motu->rx_packet_formats.pcm_chunks)); + + if (motu->spec == &snd_motu_spec_828mk3) + return detect_packet_formats_828mk3(motu, data); + else + return 0; +} + + +const struct snd_motu_spec snd_motu_spec_828mk3 = { + .name = "828mk3", + .protocol_version = SND_MOTU_PROTOCOL_V3, + .flags = SND_MOTU_SPEC_RX_MIDI_3RD_Q | + SND_MOTU_SPEC_TX_MIDI_3RD_Q, + .tx_fixed_pcm_chunks = {18, 18, 14}, + .rx_fixed_pcm_chunks = {14, 14, 10}, +}; + +const struct snd_motu_spec snd_motu_spec_ultralite_mk3 = { + .name = "UltraLiteMk3", + .protocol_version = SND_MOTU_PROTOCOL_V3, + .flags = SND_MOTU_SPEC_RX_MIDI_3RD_Q | + SND_MOTU_SPEC_TX_MIDI_3RD_Q, + .tx_fixed_pcm_chunks = {18, 14, 10}, + .rx_fixed_pcm_chunks = {14, 14, 14}, +}; + +const struct snd_motu_spec snd_motu_spec_audio_express = { + .name = "AudioExpress", + .protocol_version = SND_MOTU_PROTOCOL_V3, + .flags = SND_MOTU_SPEC_RX_MIDI_2ND_Q | + SND_MOTU_SPEC_TX_MIDI_3RD_Q, + .tx_fixed_pcm_chunks = {10, 10, 0}, + .rx_fixed_pcm_chunks = {10, 10, 0}, +}; + +const struct snd_motu_spec snd_motu_spec_4pre = { + .name = "4pre", + .protocol_version = SND_MOTU_PROTOCOL_V3, + .tx_fixed_pcm_chunks = {10, 10, 0}, + .rx_fixed_pcm_chunks = {10, 10, 0}, +}; diff --git a/sound/firewire/motu/motu-stream.c b/sound/firewire/motu/motu-stream.c new file mode 100644 index 000000000..2028c5419 --- /dev/null +++ b/sound/firewire/motu/motu-stream.c @@ -0,0 +1,408 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * motu-stream.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#include "motu.h" + +#define CALLBACK_TIMEOUT 200 + +#define ISOC_COMM_CONTROL_OFFSET 0x0b00 +#define ISOC_COMM_CONTROL_MASK 0xffff0000 +#define CHANGE_RX_ISOC_COMM_STATE 0x80000000 +#define RX_ISOC_COMM_IS_ACTIVATED 0x40000000 +#define RX_ISOC_COMM_CHANNEL_MASK 0x3f000000 +#define RX_ISOC_COMM_CHANNEL_SHIFT 24 +#define CHANGE_TX_ISOC_COMM_STATE 0x00800000 +#define TX_ISOC_COMM_IS_ACTIVATED 0x00400000 +#define TX_ISOC_COMM_CHANNEL_MASK 0x003f0000 +#define TX_ISOC_COMM_CHANNEL_SHIFT 16 + +#define PACKET_FORMAT_OFFSET 0x0b10 +#define TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS 0x00000080 +#define RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS 0x00000040 +#define TX_PACKET_TRANSMISSION_SPEED_MASK 0x0000000f + +static int keep_resources(struct snd_motu *motu, unsigned int rate, + struct amdtp_stream *stream) +{ + struct fw_iso_resources *resources; + struct snd_motu_packet_format *packet_format; + unsigned int midi_ports = 0; + int err; + + if (stream == &motu->rx_stream) { + resources = &motu->rx_resources; + packet_format = &motu->rx_packet_formats; + + if ((motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_2ND_Q) || + (motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_3RD_Q)) + midi_ports = 1; + } else { + resources = &motu->tx_resources; + packet_format = &motu->tx_packet_formats; + + if ((motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_2ND_Q) || + (motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_3RD_Q)) + midi_ports = 1; + } + + err = amdtp_motu_set_parameters(stream, rate, midi_ports, + packet_format); + if (err < 0) + return err; + + return fw_iso_resources_allocate(resources, + amdtp_stream_get_max_payload(stream), + fw_parent_device(motu->unit)->max_speed); +} + +static int begin_session(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + // Configure the unit to start isochronous communication. + err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg) & ~ISOC_COMM_CONTROL_MASK; + + data |= CHANGE_RX_ISOC_COMM_STATE | RX_ISOC_COMM_IS_ACTIVATED | + (motu->rx_resources.channel << RX_ISOC_COMM_CHANNEL_SHIFT) | + CHANGE_TX_ISOC_COMM_STATE | TX_ISOC_COMM_IS_ACTIVATED | + (motu->tx_resources.channel << TX_ISOC_COMM_CHANNEL_SHIFT); + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); +} + +static void finish_session(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_protocol_switch_fetching_mode(motu, false); + if (err < 0) + return; + + err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return; + data = be32_to_cpu(reg); + + data &= ~(RX_ISOC_COMM_IS_ACTIVATED | TX_ISOC_COMM_IS_ACTIVATED); + data |= CHANGE_RX_ISOC_COMM_STATE | CHANGE_TX_ISOC_COMM_STATE; + + reg = cpu_to_be32(data); + snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, ®, + sizeof(reg)); +} + +int snd_motu_stream_cache_packet_formats(struct snd_motu *motu) +{ + int err; + + err = snd_motu_protocol_cache_packet_formats(motu); + if (err < 0) + return err; + + if (motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_2ND_Q) { + motu->tx_packet_formats.midi_flag_offset = 4; + motu->tx_packet_formats.midi_byte_offset = 6; + } else if (motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_3RD_Q) { + motu->tx_packet_formats.midi_flag_offset = 8; + motu->tx_packet_formats.midi_byte_offset = 7; + } + + if (motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_2ND_Q) { + motu->rx_packet_formats.midi_flag_offset = 4; + motu->rx_packet_formats.midi_byte_offset = 6; + } else if (motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_3RD_Q) { + motu->rx_packet_formats.midi_flag_offset = 8; + motu->rx_packet_formats.midi_byte_offset = 7; + } + + return 0; +} + +int snd_motu_stream_reserve_duplex(struct snd_motu *motu, unsigned int rate, + unsigned int frames_per_period, + unsigned int frames_per_buffer) +{ + unsigned int curr_rate; + int err; + + err = snd_motu_protocol_get_clock_rate(motu, &curr_rate); + if (err < 0) + return err; + if (rate == 0) + rate = curr_rate; + + if (motu->substreams_counter == 0 || curr_rate != rate) { + amdtp_domain_stop(&motu->domain); + finish_session(motu); + + fw_iso_resources_free(&motu->tx_resources); + fw_iso_resources_free(&motu->rx_resources); + + err = snd_motu_protocol_set_clock_rate(motu, rate); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to set sampling rate: %d\n", err); + return err; + } + + err = snd_motu_stream_cache_packet_formats(motu); + if (err < 0) + return err; + + err = keep_resources(motu, rate, &motu->tx_stream); + if (err < 0) + return err; + + err = keep_resources(motu, rate, &motu->rx_stream); + if (err < 0) { + fw_iso_resources_free(&motu->tx_resources); + return err; + } + + err = amdtp_domain_set_events_per_period(&motu->domain, + frames_per_period, frames_per_buffer); + if (err < 0) { + fw_iso_resources_free(&motu->tx_resources); + fw_iso_resources_free(&motu->rx_resources); + return err; + } + } + + return 0; +} + +static int ensure_packet_formats(struct snd_motu *motu) +{ + __be32 reg; + u32 data; + int err; + + err = snd_motu_transaction_read(motu, PACKET_FORMAT_OFFSET, ®, + sizeof(reg)); + if (err < 0) + return err; + data = be32_to_cpu(reg); + + data &= ~(TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS | + RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS| + TX_PACKET_TRANSMISSION_SPEED_MASK); + if (motu->spec->tx_fixed_pcm_chunks[0] == motu->tx_packet_formats.pcm_chunks[0]) + data |= TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS; + if (motu->spec->rx_fixed_pcm_chunks[0] == motu->rx_packet_formats.pcm_chunks[0]) + data |= RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS; + data |= fw_parent_device(motu->unit)->max_speed; + + reg = cpu_to_be32(data); + return snd_motu_transaction_write(motu, PACKET_FORMAT_OFFSET, ®, + sizeof(reg)); +} + +int snd_motu_stream_start_duplex(struct snd_motu *motu) +{ + unsigned int generation = motu->rx_resources.generation; + int err = 0; + + if (motu->substreams_counter == 0) + return 0; + + if (amdtp_streaming_error(&motu->rx_stream) || + amdtp_streaming_error(&motu->tx_stream)) { + amdtp_domain_stop(&motu->domain); + finish_session(motu); + } + + if (generation != fw_parent_device(motu->unit)->card->generation) { + err = fw_iso_resources_update(&motu->rx_resources); + if (err < 0) + return err; + + err = fw_iso_resources_update(&motu->tx_resources); + if (err < 0) + return err; + } + + if (!amdtp_stream_running(&motu->rx_stream)) { + int spd = fw_parent_device(motu->unit)->max_speed; + + err = ensure_packet_formats(motu); + if (err < 0) + return err; + + err = begin_session(motu); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to start isochronous comm: %d\n", err); + goto stop_streams; + } + + err = amdtp_domain_add_stream(&motu->domain, &motu->tx_stream, + motu->tx_resources.channel, spd); + if (err < 0) + goto stop_streams; + + err = amdtp_domain_add_stream(&motu->domain, &motu->rx_stream, + motu->rx_resources.channel, spd); + if (err < 0) + goto stop_streams; + + err = amdtp_domain_start(&motu->domain, 0); + if (err < 0) + goto stop_streams; + + if (!amdtp_stream_wait_callback(&motu->tx_stream, + CALLBACK_TIMEOUT) || + !amdtp_stream_wait_callback(&motu->rx_stream, + CALLBACK_TIMEOUT)) { + err = -ETIMEDOUT; + goto stop_streams; + } + + err = snd_motu_protocol_switch_fetching_mode(motu, true); + if (err < 0) { + dev_err(&motu->unit->device, + "fail to enable frame fetching: %d\n", err); + goto stop_streams; + } + } + + return 0; + +stop_streams: + amdtp_domain_stop(&motu->domain); + finish_session(motu); + return err; +} + +void snd_motu_stream_stop_duplex(struct snd_motu *motu) +{ + if (motu->substreams_counter == 0) { + amdtp_domain_stop(&motu->domain); + finish_session(motu); + + fw_iso_resources_free(&motu->tx_resources); + fw_iso_resources_free(&motu->rx_resources); + } +} + +static int init_stream(struct snd_motu *motu, struct amdtp_stream *s) +{ + struct fw_iso_resources *resources; + enum amdtp_stream_direction dir; + int err; + + if (s == &motu->tx_stream) { + resources = &motu->tx_resources; + dir = AMDTP_IN_STREAM; + } else { + resources = &motu->rx_resources; + dir = AMDTP_OUT_STREAM; + } + + err = fw_iso_resources_init(resources, motu->unit); + if (err < 0) + return err; + + err = amdtp_motu_init(s, motu->unit, dir, motu->spec); + if (err < 0) + fw_iso_resources_destroy(resources); + + return err; +} + +static void destroy_stream(struct snd_motu *motu, struct amdtp_stream *s) +{ + amdtp_stream_destroy(s); + + if (s == &motu->tx_stream) + fw_iso_resources_destroy(&motu->tx_resources); + else + fw_iso_resources_destroy(&motu->rx_resources); +} + +int snd_motu_stream_init_duplex(struct snd_motu *motu) +{ + int err; + + err = init_stream(motu, &motu->tx_stream); + if (err < 0) + return err; + + err = init_stream(motu, &motu->rx_stream); + if (err < 0) { + destroy_stream(motu, &motu->tx_stream); + return err; + } + + err = amdtp_domain_init(&motu->domain); + if (err < 0) { + destroy_stream(motu, &motu->tx_stream); + destroy_stream(motu, &motu->rx_stream); + } + + return err; +} + +// This function should be called before starting streams or after stopping +// streams. +void snd_motu_stream_destroy_duplex(struct snd_motu *motu) +{ + amdtp_domain_destroy(&motu->domain); + + destroy_stream(motu, &motu->rx_stream); + destroy_stream(motu, &motu->tx_stream); + + motu->substreams_counter = 0; +} + +static void motu_lock_changed(struct snd_motu *motu) +{ + motu->dev_lock_changed = true; + wake_up(&motu->hwdep_wait); +} + +int snd_motu_stream_lock_try(struct snd_motu *motu) +{ + int err; + + spin_lock_irq(&motu->lock); + + if (motu->dev_lock_count < 0) { + err = -EBUSY; + goto out; + } + + if (motu->dev_lock_count++ == 0) + motu_lock_changed(motu); + err = 0; +out: + spin_unlock_irq(&motu->lock); + return err; +} + +void snd_motu_stream_lock_release(struct snd_motu *motu) +{ + spin_lock_irq(&motu->lock); + + if (WARN_ON(motu->dev_lock_count <= 0)) + goto out; + + if (--motu->dev_lock_count == 0) + motu_lock_changed(motu); +out: + spin_unlock_irq(&motu->lock); +} diff --git a/sound/firewire/motu/motu-transaction.c b/sound/firewire/motu/motu-transaction.c new file mode 100644 index 000000000..2dc1d6e59 --- /dev/null +++ b/sound/firewire/motu/motu-transaction.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * motu-transaction.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + + +#include "motu.h" + +#define SND_MOTU_ADDR_BASE 0xfffff0000000ULL +#define ASYNC_ADDR_HI 0x0b04 +#define ASYNC_ADDR_LO 0x0b08 + +int snd_motu_transaction_read(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size) +{ + int tcode; + + if (size % sizeof(__be32) > 0 || size <= 0) + return -EINVAL; + if (size == sizeof(__be32)) + tcode = TCODE_READ_QUADLET_REQUEST; + else + tcode = TCODE_READ_BLOCK_REQUEST; + + return snd_fw_transaction(motu->unit, tcode, + SND_MOTU_ADDR_BASE + offset, reg, size, 0); +} + +int snd_motu_transaction_write(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size) +{ + int tcode; + + if (size % sizeof(__be32) > 0 || size <= 0) + return -EINVAL; + if (size == sizeof(__be32)) + tcode = TCODE_WRITE_QUADLET_REQUEST; + else + tcode = TCODE_WRITE_BLOCK_REQUEST; + + return snd_fw_transaction(motu->unit, tcode, + SND_MOTU_ADDR_BASE + offset, reg, size, 0); +} + +static void handle_message(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + struct snd_motu *motu = callback_data; + __be32 *buf = (__be32 *)data; + unsigned long flags; + + if (tcode != TCODE_WRITE_QUADLET_REQUEST) { + fw_send_response(card, request, RCODE_COMPLETE); + return; + } + + if (offset != motu->async_handler.offset || length != 4) { + fw_send_response(card, request, RCODE_ADDRESS_ERROR); + return; + } + + spin_lock_irqsave(&motu->lock, flags); + motu->msg = be32_to_cpu(*buf); + spin_unlock_irqrestore(&motu->lock, flags); + + fw_send_response(card, request, RCODE_COMPLETE); + + wake_up(&motu->hwdep_wait); +} + +int snd_motu_transaction_reregister(struct snd_motu *motu) +{ + struct fw_device *device = fw_parent_device(motu->unit); + __be32 data; + int err; + + if (motu->async_handler.callback_data == NULL) + return -EINVAL; + + /* Register messaging address. Block transaction is not allowed. */ + data = cpu_to_be32((device->card->node_id << 16) | + (motu->async_handler.offset >> 32)); + err = snd_motu_transaction_write(motu, ASYNC_ADDR_HI, &data, + sizeof(data)); + if (err < 0) + return err; + + data = cpu_to_be32(motu->async_handler.offset); + return snd_motu_transaction_write(motu, ASYNC_ADDR_LO, &data, + sizeof(data)); +} + +int snd_motu_transaction_register(struct snd_motu *motu) +{ + static const struct fw_address_region resp_register_region = { + .start = 0xffffe0000000ull, + .end = 0xffffe000ffffull, + }; + int err; + + /* Perhaps, 4 byte messages are transferred. */ + motu->async_handler.length = 4; + motu->async_handler.address_callback = handle_message; + motu->async_handler.callback_data = motu; + + err = fw_core_add_address_handler(&motu->async_handler, + &resp_register_region); + if (err < 0) + return err; + + err = snd_motu_transaction_reregister(motu); + if (err < 0) { + fw_core_remove_address_handler(&motu->async_handler); + motu->async_handler.address_callback = NULL; + } + + return err; +} + +void snd_motu_transaction_unregister(struct snd_motu *motu) +{ + __be32 data; + + if (motu->async_handler.address_callback != NULL) + fw_core_remove_address_handler(&motu->async_handler); + motu->async_handler.address_callback = NULL; + + /* Unregister the address. */ + data = cpu_to_be32(0x00000000); + snd_motu_transaction_write(motu, ASYNC_ADDR_HI, &data, sizeof(data)); + snd_motu_transaction_write(motu, ASYNC_ADDR_LO, &data, sizeof(data)); +} diff --git a/sound/firewire/motu/motu.c b/sound/firewire/motu/motu.c new file mode 100644 index 000000000..a4929c130 --- /dev/null +++ b/sound/firewire/motu/motu.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * motu.c - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#include "motu.h" + +#define OUI_MOTU 0x0001f2 + +MODULE_DESCRIPTION("MOTU FireWire driver"); +MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>"); +MODULE_LICENSE("GPL v2"); + +const unsigned int snd_motu_clock_rates[SND_MOTU_CLOCK_RATE_COUNT] = { + /* mode 0 */ + [0] = 44100, + [1] = 48000, + /* mode 1 */ + [2] = 88200, + [3] = 96000, + /* mode 2 */ + [4] = 176400, + [5] = 192000, +}; + +static void name_card(struct snd_motu *motu) +{ + struct fw_device *fw_dev = fw_parent_device(motu->unit); + struct fw_csr_iterator it; + int key, val; + u32 version = 0; + + fw_csr_iterator_init(&it, motu->unit->directory); + while (fw_csr_iterator_next(&it, &key, &val)) { + switch (key) { + case CSR_MODEL: + version = val; + break; + } + } + + strcpy(motu->card->driver, "FW-MOTU"); + strcpy(motu->card->shortname, motu->spec->name); + strcpy(motu->card->mixername, motu->spec->name); + snprintf(motu->card->longname, sizeof(motu->card->longname), + "MOTU %s (version:%06x), GUID %08x%08x at %s, S%d", + motu->spec->name, version, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&motu->unit->device), 100 << fw_dev->max_speed); +} + +static void motu_card_free(struct snd_card *card) +{ + struct snd_motu *motu = card->private_data; + + snd_motu_transaction_unregister(motu); + snd_motu_stream_destroy_duplex(motu); +} + +static void do_registration(struct work_struct *work) +{ + struct snd_motu *motu = container_of(work, struct snd_motu, dwork.work); + int err; + + if (motu->registered) + return; + + err = snd_card_new(&motu->unit->device, -1, NULL, THIS_MODULE, 0, + &motu->card); + if (err < 0) + return; + motu->card->private_free = motu_card_free; + motu->card->private_data = motu; + + name_card(motu); + + err = snd_motu_transaction_register(motu); + if (err < 0) + goto error; + + err = snd_motu_stream_init_duplex(motu); + if (err < 0) + goto error; + + snd_motu_proc_init(motu); + + err = snd_motu_create_pcm_devices(motu); + if (err < 0) + goto error; + + if ((motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_2ND_Q) || + (motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_3RD_Q) || + (motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_2ND_Q) || + (motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_3RD_Q)) { + err = snd_motu_create_midi_devices(motu); + if (err < 0) + goto error; + } + + err = snd_motu_create_hwdep_device(motu); + if (err < 0) + goto error; + + err = snd_card_register(motu->card); + if (err < 0) + goto error; + + motu->registered = true; + + return; +error: + snd_card_free(motu->card); + dev_info(&motu->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int motu_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_motu *motu; + + /* Allocate this independently of sound card instance. */ + motu = devm_kzalloc(&unit->device, sizeof(struct snd_motu), GFP_KERNEL); + if (!motu) + return -ENOMEM; + motu->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, motu); + + motu->spec = (const struct snd_motu_spec *)entry->driver_data; + mutex_init(&motu->mutex); + spin_lock_init(&motu->lock); + init_waitqueue_head(&motu->hwdep_wait); + + /* Allocate and register this sound card later. */ + INIT_DEFERRABLE_WORK(&motu->dwork, do_registration); + snd_fw_schedule_registration(unit, &motu->dwork); + + return 0; +} + +static void motu_remove(struct fw_unit *unit) +{ + struct snd_motu *motu = dev_get_drvdata(&unit->device); + + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_delayed_work_sync(&motu->dwork); + + if (motu->registered) { + // Block till all of ALSA character devices are released. + snd_card_free(motu->card); + } + + mutex_destroy(&motu->mutex); + fw_unit_put(motu->unit); +} + +static void motu_bus_update(struct fw_unit *unit) +{ + struct snd_motu *motu = dev_get_drvdata(&unit->device); + + /* Postpone a workqueue for deferred registration. */ + if (!motu->registered) + snd_fw_schedule_registration(unit, &motu->dwork); + + /* The handler address register becomes initialized. */ + snd_motu_transaction_reregister(motu); +} + +#define SND_MOTU_DEV_ENTRY(model, data) \ +{ \ + .match_flags = IEEE1394_MATCH_VENDOR_ID | \ + IEEE1394_MATCH_SPECIFIER_ID | \ + IEEE1394_MATCH_VERSION, \ + .vendor_id = OUI_MOTU, \ + .specifier_id = OUI_MOTU, \ + .version = model, \ + .driver_data = (kernel_ulong_t)data, \ +} + +static const struct ieee1394_device_id motu_id_table[] = { + SND_MOTU_DEV_ENTRY(0x000003, &snd_motu_spec_828mk2), + SND_MOTU_DEV_ENTRY(0x000009, &snd_motu_spec_traveler), + SND_MOTU_DEV_ENTRY(0x00000d, &snd_motu_spec_ultralite), + SND_MOTU_DEV_ENTRY(0x00000f, &snd_motu_spec_8pre), + SND_MOTU_DEV_ENTRY(0x000015, &snd_motu_spec_828mk3), // FireWire only. + SND_MOTU_DEV_ENTRY(0x000019, &snd_motu_spec_ultralite_mk3), // FireWire only. + SND_MOTU_DEV_ENTRY(0x000035, &snd_motu_spec_828mk3), // Hybrid. + SND_MOTU_DEV_ENTRY(0x000033, &snd_motu_spec_audio_express), + SND_MOTU_DEV_ENTRY(0x000045, &snd_motu_spec_4pre), + { } +}; +MODULE_DEVICE_TABLE(ieee1394, motu_id_table); + +static struct fw_driver motu_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + }, + .probe = motu_probe, + .update = motu_bus_update, + .remove = motu_remove, + .id_table = motu_id_table, +}; + +static int __init alsa_motu_init(void) +{ + return driver_register(&motu_driver.driver); +} + +static void __exit alsa_motu_exit(void) +{ + driver_unregister(&motu_driver.driver); +} + +module_init(alsa_motu_init); +module_exit(alsa_motu_exit); diff --git a/sound/firewire/motu/motu.h b/sound/firewire/motu/motu.h new file mode 100644 index 000000000..3d0236ee6 --- /dev/null +++ b/sound/firewire/motu/motu.h @@ -0,0 +1,237 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * motu.h - a part of driver for MOTU FireWire series + * + * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#ifndef SOUND_FIREWIRE_MOTU_H_INCLUDED +#define SOUND_FIREWIRE_MOTU_H_INCLUDED + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/compat.h> +#include <linux/sched/signal.h> + +#include <sound/control.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/info.h> +#include <sound/rawmidi.h> +#include <sound/firewire.h> +#include <sound/hwdep.h> + +#include "../lib.h" +#include "../amdtp-stream.h" +#include "../iso-resources.h" + +struct snd_motu_packet_format { + unsigned char midi_flag_offset; + unsigned char midi_byte_offset; + unsigned char pcm_byte_offset; + + unsigned char msg_chunks; + unsigned char pcm_chunks[3]; +}; + +struct snd_motu { + struct snd_card *card; + struct fw_unit *unit; + struct mutex mutex; + spinlock_t lock; + + bool registered; + struct delayed_work dwork; + + /* Model dependent information. */ + const struct snd_motu_spec *spec; + + /* For packet streaming */ + struct snd_motu_packet_format tx_packet_formats; + struct snd_motu_packet_format rx_packet_formats; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + struct fw_iso_resources tx_resources; + struct fw_iso_resources rx_resources; + unsigned int substreams_counter; + + /* For notification. */ + struct fw_address_handler async_handler; + u32 msg; + + /* For uapi */ + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; + + struct amdtp_domain domain; +}; + +enum snd_motu_spec_flags { + SND_MOTU_SPEC_RX_MIDI_2ND_Q = 0x0001, + SND_MOTU_SPEC_RX_MIDI_3RD_Q = 0x0002, + SND_MOTU_SPEC_TX_MIDI_2ND_Q = 0x0004, + SND_MOTU_SPEC_TX_MIDI_3RD_Q = 0x0008, +}; + +#define SND_MOTU_CLOCK_RATE_COUNT 6 +extern const unsigned int snd_motu_clock_rates[SND_MOTU_CLOCK_RATE_COUNT]; + +enum snd_motu_clock_source { + SND_MOTU_CLOCK_SOURCE_INTERNAL, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_A, + SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT_B, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_A, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT_B, + SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX, + SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR, + SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC, + SND_MOTU_CLOCK_SOURCE_SPH, + SND_MOTU_CLOCK_SOURCE_UNKNOWN, +}; + +enum snd_motu_protocol_version { + SND_MOTU_PROTOCOL_V2, + SND_MOTU_PROTOCOL_V3, +}; + +struct snd_motu_spec { + const char *const name; + enum snd_motu_protocol_version protocol_version; + enum snd_motu_spec_flags flags; + + unsigned char tx_fixed_pcm_chunks[3]; + unsigned char rx_fixed_pcm_chunks[3]; +}; + +extern const struct snd_motu_spec snd_motu_spec_828mk2; +extern const struct snd_motu_spec snd_motu_spec_traveler; +extern const struct snd_motu_spec snd_motu_spec_ultralite; +extern const struct snd_motu_spec snd_motu_spec_8pre; + +extern const struct snd_motu_spec snd_motu_spec_828mk3; +extern const struct snd_motu_spec snd_motu_spec_ultralite_mk3; +extern const struct snd_motu_spec snd_motu_spec_audio_express; +extern const struct snd_motu_spec snd_motu_spec_4pre; + +int amdtp_motu_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, + const struct snd_motu_spec *spec); +int amdtp_motu_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int midi_ports, + struct snd_motu_packet_format *formats); +int amdtp_motu_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); +void amdtp_motu_midi_trigger(struct amdtp_stream *s, unsigned int port, + struct snd_rawmidi_substream *midi); + +int snd_motu_transaction_read(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size); +int snd_motu_transaction_write(struct snd_motu *motu, u32 offset, __be32 *reg, + size_t size); +int snd_motu_transaction_register(struct snd_motu *motu); +int snd_motu_transaction_reregister(struct snd_motu *motu); +void snd_motu_transaction_unregister(struct snd_motu *motu); + +int snd_motu_stream_init_duplex(struct snd_motu *motu); +void snd_motu_stream_destroy_duplex(struct snd_motu *motu); +int snd_motu_stream_cache_packet_formats(struct snd_motu *motu); +int snd_motu_stream_reserve_duplex(struct snd_motu *motu, unsigned int rate, + unsigned int frames_per_period, + unsigned int frames_per_buffer); +int snd_motu_stream_start_duplex(struct snd_motu *motu); +void snd_motu_stream_stop_duplex(struct snd_motu *motu); +int snd_motu_stream_lock_try(struct snd_motu *motu); +void snd_motu_stream_lock_release(struct snd_motu *motu); + +void snd_motu_proc_init(struct snd_motu *motu); + +int snd_motu_create_pcm_devices(struct snd_motu *motu); + +int snd_motu_create_midi_devices(struct snd_motu *motu); + +int snd_motu_create_hwdep_device(struct snd_motu *motu); + +int snd_motu_protocol_v2_get_clock_rate(struct snd_motu *motu, + unsigned int *rate); +int snd_motu_protocol_v2_set_clock_rate(struct snd_motu *motu, + unsigned int rate); +int snd_motu_protocol_v2_get_clock_source(struct snd_motu *motu, + enum snd_motu_clock_source *src); +int snd_motu_protocol_v2_switch_fetching_mode(struct snd_motu *motu, + bool enable); +int snd_motu_protocol_v2_cache_packet_formats(struct snd_motu *motu); + +int snd_motu_protocol_v3_get_clock_rate(struct snd_motu *motu, + unsigned int *rate); +int snd_motu_protocol_v3_set_clock_rate(struct snd_motu *motu, + unsigned int rate); +int snd_motu_protocol_v3_get_clock_source(struct snd_motu *motu, + enum snd_motu_clock_source *src); +int snd_motu_protocol_v3_switch_fetching_mode(struct snd_motu *motu, + bool enable); +int snd_motu_protocol_v3_cache_packet_formats(struct snd_motu *motu); + +static inline int snd_motu_protocol_get_clock_rate(struct snd_motu *motu, + unsigned int *rate) +{ + if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V2) + return snd_motu_protocol_v2_get_clock_rate(motu, rate); + else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V3) + return snd_motu_protocol_v3_get_clock_rate(motu, rate); + else + return -ENXIO; +} + +static inline int snd_motu_protocol_set_clock_rate(struct snd_motu *motu, + unsigned int rate) +{ + if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V2) + return snd_motu_protocol_v2_set_clock_rate(motu, rate); + else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V3) + return snd_motu_protocol_v3_set_clock_rate(motu, rate); + else + return -ENXIO; +} + +static inline int snd_motu_protocol_get_clock_source(struct snd_motu *motu, + enum snd_motu_clock_source *source) +{ + if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V2) + return snd_motu_protocol_v2_get_clock_source(motu, source); + else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V3) + return snd_motu_protocol_v3_get_clock_source(motu, source); + else + return -ENXIO; +} + +static inline int snd_motu_protocol_switch_fetching_mode(struct snd_motu *motu, + bool enable) +{ + if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V2) + return snd_motu_protocol_v2_switch_fetching_mode(motu, enable); + else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V3) + return snd_motu_protocol_v3_switch_fetching_mode(motu, enable); + else + return -ENXIO; +} + +static inline int snd_motu_protocol_cache_packet_formats(struct snd_motu *motu) +{ + if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V2) + return snd_motu_protocol_v2_cache_packet_formats(motu); + else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V3) + return snd_motu_protocol_v3_cache_packet_formats(motu); + else + return -ENXIO; +} + +#endif diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile new file mode 100644 index 000000000..669d1e823 --- /dev/null +++ b/sound/firewire/oxfw/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +snd-oxfw-objs := oxfw-command.o oxfw-stream.o oxfw-pcm.o oxfw-proc.o \ + oxfw-midi.o oxfw-hwdep.o oxfw-spkr.o oxfw-scs1x.o oxfw.o +obj-$(CONFIG_SND_OXFW) += snd-oxfw.o diff --git a/sound/firewire/oxfw/oxfw-command.c b/sound/firewire/oxfw/oxfw-command.c new file mode 100644 index 000000000..d2e57c760 --- /dev/null +++ b/sound/firewire/oxfw/oxfw-command.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * oxfw_command.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2014 Takashi Sakamoto + */ + +#include "oxfw.h" + +int avc_stream_set_format(struct fw_unit *unit, enum avc_general_plug_dir dir, + unsigned int pid, u8 *format, unsigned int len) +{ + u8 *buf; + int err; + + buf = kmalloc(len + 10, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x00; /* CONTROL */ + buf[1] = 0xff; /* UNIT */ + buf[2] = 0xbf; /* EXTENDED STREAM FORMAT INFORMATION */ + buf[3] = 0xc0; /* SINGLE subfunction */ + buf[4] = dir; /* Plug Direction */ + buf[5] = 0x00; /* UNIT */ + buf[6] = 0x00; /* PCR (Isochronous Plug) */ + buf[7] = 0xff & pid; /* Plug ID */ + buf[8] = 0xff; /* Padding */ + buf[9] = 0xff; /* Support status in response */ + memcpy(buf + 10, format, len); + + /* do transaction and check buf[1-8] are the same against command */ + err = fcp_avc_transaction(unit, buf, len + 10, buf, len + 10, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(8)); + if (err < 0) + ; + else if (err < len + 10) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENXIO; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else + err = 0; + + kfree(buf); + + return err; +} + +int avc_stream_get_format(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len, unsigned int eid) +{ + unsigned int subfunc; + int err; + + if (eid == 0xff) + subfunc = 0xc0; /* SINGLE */ + else + subfunc = 0xc1; /* LIST */ + + buf[0] = 0x01; /* STATUS */ + buf[1] = 0xff; /* UNIT */ + buf[2] = 0xbf; /* EXTENDED STREAM FORMAT INFORMATION */ + buf[3] = subfunc; /* SINGLE or LIST */ + buf[4] = dir; /* Plug Direction */ + buf[5] = 0x00; /* Unit */ + buf[6] = 0x00; /* PCR (Isochronous Plug) */ + buf[7] = 0xff & pid; /* Plug ID */ + buf[8] = 0xff; /* Padding */ + buf[9] = 0xff; /* support status in response */ + buf[10] = 0xff & eid; /* entry ID for LIST subfunction */ + buf[11] = 0xff; /* padding */ + + /* do transaction and check buf[1-7] are the same against command */ + err = fcp_avc_transaction(unit, buf, 12, buf, *len, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7)); + if (err < 0) + ; + else if (err < 12) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENXIO; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + /* LIST subfunction has entry ID */ + else if ((subfunc == 0xc1) && (buf[10] != eid)) + err = -EIO; + if (err < 0) + goto end; + + /* keep just stream format information */ + if (subfunc == 0xc0) { + memmove(buf, buf + 10, err - 10); + *len = err - 10; + } else { + memmove(buf, buf + 11, err - 11); + *len = err - 11; + } + + err = 0; +end: + return err; +} + +int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate, + enum avc_general_plug_dir dir, + unsigned short pid) +{ + unsigned int sfc; + u8 *buf; + int err; + + for (sfc = 0; sfc < CIP_SFC_COUNT; sfc++) { + if (amdtp_rate_table[sfc] == rate) + break; + } + if (sfc == CIP_SFC_COUNT) + return -EINVAL; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x02; /* SPECIFIC INQUIRY */ + buf[1] = 0xff; /* UNIT */ + if (dir == AVC_GENERAL_PLUG_DIR_IN) + buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */ + else + buf[2] = 0x18; /* OUTPUT PLUG SIGNAL FORMAT */ + buf[3] = 0xff & pid; /* plug id */ + buf[4] = 0x90; /* EOH_1, Form_1, FMT. AM824 */ + buf[5] = 0x07 & sfc; /* FDF-hi. AM824, frequency */ + buf[6] = 0xff; /* FDF-mid. AM824, SYT hi (not used) */ + buf[7] = 0xff; /* FDF-low. AM824, SYT lo (not used) */ + + /* do transaction and check buf[1-5] are the same against command */ + err = fcp_avc_transaction(unit, buf, 8, buf, 8, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5)); + if (err < 0) + ; + else if (err < 8) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENXIO; + if (err < 0) + goto end; + + err = 0; +end: + kfree(buf); + return err; +} diff --git a/sound/firewire/oxfw/oxfw-hwdep.c b/sound/firewire/oxfw/oxfw-hwdep.c new file mode 100644 index 000000000..eba33d050 --- /dev/null +++ b/sound/firewire/oxfw/oxfw-hwdep.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * oxfw_hwdep.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2014 Takashi Sakamoto + */ + +/* + * This codes give three functionality. + * + * 1.get firewire node information + * 2.get notification about starting/stopping stream + * 3.lock/unlock stream + */ + +#include "oxfw.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_oxfw *oxfw = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&oxfw->lock); + + while (!oxfw->dev_lock_changed) { + prepare_to_wait(&oxfw->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&oxfw->lock); + schedule(); + finish_wait(&oxfw->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&oxfw->lock); + } + + memset(&event, 0, sizeof(event)); + if (oxfw->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (oxfw->dev_lock_count > 0); + oxfw->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } + + spin_unlock_irq(&oxfw->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_oxfw *oxfw = hwdep->private_data; + __poll_t events; + + poll_wait(file, &oxfw->hwdep_wait, wait); + + spin_lock_irq(&oxfw->lock); + if (oxfw->dev_lock_changed) + events = EPOLLIN | EPOLLRDNORM; + else + events = 0; + spin_unlock_irq(&oxfw->lock); + + return events; +} + +static int hwdep_get_info(struct snd_oxfw *oxfw, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(oxfw->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_OXFW; + 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]); + strlcpy(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_oxfw *oxfw) +{ + int err; + + spin_lock_irq(&oxfw->lock); + + if (oxfw->dev_lock_count == 0) { + oxfw->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&oxfw->lock); + + return err; +} + +static int hwdep_unlock(struct snd_oxfw *oxfw) +{ + int err; + + spin_lock_irq(&oxfw->lock); + + if (oxfw->dev_lock_count == -1) { + oxfw->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&oxfw->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_oxfw *oxfw = hwdep->private_data; + + spin_lock_irq(&oxfw->lock); + if (oxfw->dev_lock_count == -1) + oxfw->dev_lock_count = 0; + spin_unlock_irq(&oxfw->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_oxfw *oxfw = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(oxfw, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(oxfw); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(oxfw); + 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_oxfw_create_hwdep(struct snd_oxfw *oxfw) +{ + 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(oxfw->card, oxfw->card->driver, 0, &hwdep); + if (err < 0) + goto end; + strcpy(hwdep->name, oxfw->card->driver); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_OXFW; + hwdep->ops = hwdep_ops; + hwdep->private_data = oxfw; + hwdep->exclusive = true; +end: + return err; +} diff --git a/sound/firewire/oxfw/oxfw-midi.c b/sound/firewire/oxfw/oxfw-midi.c new file mode 100644 index 000000000..775cba3f1 --- /dev/null +++ b/sound/firewire/oxfw/oxfw-midi.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * oxfw_midi.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2014 Takashi Sakamoto + */ + +#include "oxfw.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + struct snd_oxfw *oxfw = substream->rmidi->private_data; + int err; + + err = snd_oxfw_stream_lock_try(oxfw); + if (err < 0) + return err; + + mutex_lock(&oxfw->mutex); + + err = snd_oxfw_stream_reserve_duplex(oxfw, &oxfw->tx_stream, 0, 0, 0, 0); + if (err >= 0) { + ++oxfw->substreams_count; + err = snd_oxfw_stream_start_duplex(oxfw); + if (err < 0) + --oxfw->substreams_count; + } + + mutex_unlock(&oxfw->mutex); + + if (err < 0) + snd_oxfw_stream_lock_release(oxfw); + + return err; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_oxfw *oxfw = substream->rmidi->private_data; + int err; + + err = snd_oxfw_stream_lock_try(oxfw); + if (err < 0) + return err; + + mutex_lock(&oxfw->mutex); + + err = snd_oxfw_stream_reserve_duplex(oxfw, &oxfw->rx_stream, 0, 0, 0, 0); + if (err >= 0) { + ++oxfw->substreams_count; + err = snd_oxfw_stream_start_duplex(oxfw); + } + + mutex_unlock(&oxfw->mutex); + + if (err < 0) + snd_oxfw_stream_lock_release(oxfw); + + return err; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + struct snd_oxfw *oxfw = substream->rmidi->private_data; + + mutex_lock(&oxfw->mutex); + + --oxfw->substreams_count; + snd_oxfw_stream_stop_duplex(oxfw); + + mutex_unlock(&oxfw->mutex); + + snd_oxfw_stream_lock_release(oxfw); + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_oxfw *oxfw = substream->rmidi->private_data; + + mutex_lock(&oxfw->mutex); + + --oxfw->substreams_count; + snd_oxfw_stream_stop_duplex(oxfw); + + mutex_unlock(&oxfw->mutex); + + snd_oxfw_stream_lock_release(oxfw); + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_oxfw *oxfw = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&oxfw->lock, flags); + + if (up) + amdtp_am824_midi_trigger(&oxfw->tx_stream, + substrm->number, substrm); + else + amdtp_am824_midi_trigger(&oxfw->tx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&oxfw->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_oxfw *oxfw = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&oxfw->lock, flags); + + if (up) + amdtp_am824_midi_trigger(&oxfw->rx_stream, + substrm->number, substrm); + else + amdtp_am824_midi_trigger(&oxfw->rx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&oxfw->lock, flags); +} + +static void set_midi_substream_names(struct snd_oxfw *oxfw, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", + oxfw->card->shortname, subs->number + 1); + } +} + +int snd_oxfw_create_midi(struct snd_oxfw *oxfw) +{ + static const struct snd_rawmidi_ops capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, + }; + static const struct snd_rawmidi_ops playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, + }; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + if (oxfw->midi_input_ports == 0 && oxfw->midi_output_ports == 0) + return 0; + + /* create midi ports */ + err = snd_rawmidi_new(oxfw->card, oxfw->card->driver, 0, + oxfw->midi_output_ports, oxfw->midi_input_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", oxfw->card->shortname); + rmidi->private_data = oxfw; + + if (oxfw->midi_input_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &capture_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + + set_midi_substream_names(oxfw, str); + } + + if (oxfw->midi_output_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &playback_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + set_midi_substream_names(oxfw, str); + } + + if ((oxfw->midi_output_ports > 0) && (oxfw->midi_input_ports > 0)) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/oxfw/oxfw-pcm.c b/sound/firewire/oxfw/oxfw-pcm.c new file mode 100644 index 000000000..2dfa7e179 --- /dev/null +++ b/sound/firewire/oxfw/oxfw-pcm.c @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * oxfw_pcm.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + */ + +#include "oxfw.h" + +static int hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + u8 **formats = 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 + }; + struct snd_oxfw_stream_formation formation; + int i, err; + + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + if (formats[i] == NULL) + continue; + + err = snd_oxfw_stream_parse_format(formats[i], &formation); + if (err < 0) + continue; + if (!snd_interval_test(c, formation.pcm)) + continue; + + t.min = min(t.min, formation.rate); + t.max = max(t.max, formation.rate); + + } + return snd_interval_refine(r, &t); +} + +static int hw_rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + u8 **formats = 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_oxfw_stream_formation formation; + int i, j, err; + unsigned int count, list[SND_OXFW_STREAM_FORMAT_ENTRIES] = {0}; + + count = 0; + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + if (formats[i] == NULL) + break; + + err = snd_oxfw_stream_parse_format(formats[i], &formation); + if (err < 0) + continue; + if (!snd_interval_test(r, formation.rate)) + continue; + if (list[count] == formation.pcm) + continue; + + for (j = 0; j < ARRAY_SIZE(list); j++) { + if (list[j] == formation.pcm) + break; + } + if (j == ARRAY_SIZE(list)) { + list[count] = formation.pcm; + if (++count == ARRAY_SIZE(list)) + break; + } + } + + return snd_interval_list(c, count, list, 0); +} + +static void limit_channels_and_rates(struct snd_pcm_hardware *hw, u8 **formats) +{ + struct snd_oxfw_stream_formation formation; + int i, err; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + hw->rate_min = UINT_MAX; + hw->rate_max = 0; + hw->rates = 0; + + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + if (formats[i] == NULL) + break; + + err = snd_oxfw_stream_parse_format(formats[i], &formation); + if (err < 0) + continue; + + hw->channels_min = min(hw->channels_min, formation.pcm); + hw->channels_max = max(hw->channels_max, formation.pcm); + + hw->rate_min = min(hw->rate_min, formation.rate); + hw->rate_max = max(hw->rate_max, formation.rate); + hw->rates |= snd_pcm_rate_to_rate_bit(formation.rate); + } +} + +static int init_hw_params(struct snd_oxfw *oxfw, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + u8 **formats; + struct amdtp_stream *stream; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = AM824_IN_PCM_FORMAT_BITS; + stream = &oxfw->tx_stream; + formats = oxfw->tx_stream_formats; + } else { + runtime->hw.formats = AM824_OUT_PCM_FORMAT_BITS; + stream = &oxfw->rx_stream; + formats = oxfw->rx_stream_formats; + } + + limit_channels_and_rates(&runtime->hw, formats); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, formats, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + goto end; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, formats, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + goto end; + + err = amdtp_am824_add_pcm_hw_constraints(stream, runtime); +end: + return err; +} + +static int limit_to_current_params(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + struct snd_oxfw_stream_formation formation; + enum avc_general_plug_dir dir; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + dir = AVC_GENERAL_PLUG_DIR_OUT; + else + dir = AVC_GENERAL_PLUG_DIR_IN; + + err = snd_oxfw_stream_get_current_formation(oxfw, dir, &formation); + if (err < 0) + goto end; + + substream->runtime->hw.channels_min = formation.pcm; + substream->runtime->hw.channels_max = formation.pcm; + substream->runtime->hw.rate_min = formation.rate; + substream->runtime->hw.rate_max = formation.rate; +end: + return err; +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + struct amdtp_domain *d = &oxfw->domain; + int err; + + err = snd_oxfw_stream_lock_try(oxfw); + if (err < 0) + return err; + + err = init_hw_params(oxfw, substream); + if (err < 0) + goto err_locked; + + mutex_lock(&oxfw->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 (oxfw->substreams_count > 0 && d->events_per_period > 0) { + unsigned int frames_per_period = d->events_per_period; + unsigned int frames_per_buffer = d->events_per_buffer; + + err = limit_to_current_params(substream); + if (err < 0) { + mutex_unlock(&oxfw->mutex); + goto err_locked; + } + + if (frames_per_period > 0) { + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + frames_per_period, frames_per_period); + if (err < 0) { + mutex_unlock(&oxfw->mutex); + goto err_locked; + } + + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + frames_per_buffer, frames_per_buffer); + if (err < 0) { + mutex_unlock(&oxfw->mutex); + goto err_locked; + } + } + } + + mutex_unlock(&oxfw->mutex); + + snd_pcm_set_sync(substream); + + return 0; +err_locked: + snd_oxfw_stream_lock_release(oxfw); + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + + snd_oxfw_stream_lock_release(oxfw); + return 0; +} + +static int pcm_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_oxfw *oxfw = substream->private_data; + int err = 0; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + unsigned int rate = params_rate(hw_params); + unsigned int channels = params_channels(hw_params); + unsigned int frames_per_period = params_period_size(hw_params); + unsigned int frames_per_buffer = params_buffer_size(hw_params); + + mutex_lock(&oxfw->mutex); + err = snd_oxfw_stream_reserve_duplex(oxfw, &oxfw->tx_stream, + rate, channels, frames_per_period, + frames_per_buffer); + if (err >= 0) + ++oxfw->substreams_count; + mutex_unlock(&oxfw->mutex); + } + + return err; +} +static int pcm_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_oxfw *oxfw = substream->private_data; + int err = 0; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + unsigned int rate = params_rate(hw_params); + unsigned int channels = params_channels(hw_params); + unsigned int frames_per_period = params_period_size(hw_params); + unsigned int frames_per_buffer = params_buffer_size(hw_params); + + mutex_lock(&oxfw->mutex); + err = snd_oxfw_stream_reserve_duplex(oxfw, &oxfw->rx_stream, + rate, channels, frames_per_period, + frames_per_buffer); + if (err >= 0) + ++oxfw->substreams_count; + mutex_unlock(&oxfw->mutex); + } + + return err; +} + +static int pcm_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + + mutex_lock(&oxfw->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + --oxfw->substreams_count; + + snd_oxfw_stream_stop_duplex(oxfw); + + mutex_unlock(&oxfw->mutex); + + return 0; +} +static int pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + + mutex_lock(&oxfw->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + --oxfw->substreams_count; + + snd_oxfw_stream_stop_duplex(oxfw); + + mutex_unlock(&oxfw->mutex); + + return 0; +} + +static int pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + int err; + + mutex_lock(&oxfw->mutex); + err = snd_oxfw_stream_start_duplex(oxfw); + mutex_unlock(&oxfw->mutex); + if (err < 0) + goto end; + + amdtp_stream_pcm_prepare(&oxfw->tx_stream); +end: + return err; +} +static int pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + int err; + + mutex_lock(&oxfw->mutex); + err = snd_oxfw_stream_start_duplex(oxfw); + mutex_unlock(&oxfw->mutex); + if (err < 0) + goto end; + + amdtp_stream_pcm_prepare(&oxfw->rx_stream); +end: + return err; +} + +static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_oxfw *oxfw = substream->private_data; + struct snd_pcm_substream *pcm; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + pcm = substream; + break; + case SNDRV_PCM_TRIGGER_STOP: + pcm = NULL; + break; + default: + return -EINVAL; + } + amdtp_stream_pcm_trigger(&oxfw->tx_stream, pcm); + return 0; +} +static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_oxfw *oxfw = substream->private_data; + struct snd_pcm_substream *pcm; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + pcm = substream; + break; + case SNDRV_PCM_TRIGGER_STOP: + pcm = NULL; + break; + default: + return -EINVAL; + } + amdtp_stream_pcm_trigger(&oxfw->rx_stream, pcm); + return 0; +} + +static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstm) +{ + struct snd_oxfw *oxfw = sbstm->private_data; + + return amdtp_domain_stream_pcm_pointer(&oxfw->domain, &oxfw->tx_stream); +} +static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstm) +{ + struct snd_oxfw *oxfw = sbstm->private_data; + + return amdtp_domain_stream_pcm_pointer(&oxfw->domain, &oxfw->rx_stream); +} + +static int pcm_capture_ack(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + + return amdtp_domain_stream_pcm_ack(&oxfw->domain, &oxfw->tx_stream); +} + +static int pcm_playback_ack(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + + return amdtp_domain_stream_pcm_ack(&oxfw->domain, &oxfw->rx_stream); +} + +int snd_oxfw_create_pcm(struct snd_oxfw *oxfw) +{ + static const struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .hw_params = pcm_capture_hw_params, + .hw_free = pcm_capture_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .ack = pcm_capture_ack, + }; + static const struct snd_pcm_ops playback_ops = { + .open = pcm_open, + .close = pcm_close, + .hw_params = pcm_playback_hw_params, + .hw_free = pcm_playback_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .ack = pcm_playback_ack, + }; + struct snd_pcm *pcm; + unsigned int cap = 0; + int err; + + if (oxfw->has_output) + cap = 1; + + err = snd_pcm_new(oxfw->card, oxfw->card->driver, 0, 1, cap, &pcm); + if (err < 0) + return err; + + pcm->private_data = oxfw; + strcpy(pcm->name, oxfw->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + if (cap > 0) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0); + + return 0; +} diff --git a/sound/firewire/oxfw/oxfw-proc.c b/sound/firewire/oxfw/oxfw-proc.c new file mode 100644 index 000000000..260c60364 --- /dev/null +++ b/sound/firewire/oxfw/oxfw-proc.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * oxfw_proc.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2014 Takashi Sakamoto + */ + +#include "./oxfw.h" + +static void proc_read_formation(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_oxfw *oxfw = entry->private_data; + struct snd_oxfw_stream_formation formation, curr; + u8 *format; + char flag; + int i, err; + + /* Show input. */ + err = snd_oxfw_stream_get_current_formation(oxfw, + AVC_GENERAL_PLUG_DIR_IN, + &curr); + if (err < 0) + return; + + snd_iprintf(buffer, "Input Stream to device:\n"); + snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + format = oxfw->rx_stream_formats[i]; + if (format == NULL) + continue; + + err = snd_oxfw_stream_parse_format(format, &formation); + if (err < 0) + continue; + + if (memcmp(&formation, &curr, sizeof(curr)) == 0) + flag = '*'; + else + flag = ' '; + + snd_iprintf(buffer, "%c\t%d\t%d\t%d\n", flag, + formation.rate, formation.pcm, formation.midi); + } + + if (!oxfw->has_output) + return; + + /* Show output. */ + err = snd_oxfw_stream_get_current_formation(oxfw, + AVC_GENERAL_PLUG_DIR_OUT, + &curr); + if (err < 0) + return; + + snd_iprintf(buffer, "Output Stream from device:\n"); + snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + format = oxfw->tx_stream_formats[i]; + if (format == NULL) + continue; + + err = snd_oxfw_stream_parse_format(format, &formation); + if (err < 0) + continue; + + if (memcmp(&formation, &curr, sizeof(curr)) == 0) + flag = '*'; + else + flag = ' '; + + snd_iprintf(buffer, "%c\t%d\t%d\t%d\n", flag, + formation.rate, formation.pcm, formation.midi); + } +} + +static void add_node(struct snd_oxfw *oxfw, 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(oxfw->card, name, root); + if (entry) + snd_info_set_text_ops(entry, oxfw, op); +} + +void snd_oxfw_proc_init(struct snd_oxfw *oxfw) +{ + 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(oxfw->card, "firewire", + oxfw->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | 0555; + + add_node(oxfw, root, "formation", proc_read_formation); +} diff --git a/sound/firewire/oxfw/oxfw-scs1x.c b/sound/firewire/oxfw/oxfw-scs1x.c new file mode 100644 index 000000000..21412a3ca --- /dev/null +++ b/sound/firewire/oxfw/oxfw-scs1x.c @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * oxfw-scs1x.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * Copyright (c) 2015 Takashi Sakamoto <o-takashi@sakamocchi.jp> + */ + +#include "oxfw.h" + +#define HSS1394_ADDRESS 0xc007dedadadaULL +#define HSS1394_MAX_PACKET_SIZE 64 +#define HSS1394_TAG_USER_DATA 0x00 +#define HSS1394_TAG_CHANGE_ADDRESS 0xf1 + +struct fw_scs1x { + struct fw_address_handler hss_handler; + u8 input_escape_count; + struct snd_rawmidi_substream *input; + + /* For MIDI playback. */ + struct snd_rawmidi_substream *output; + bool output_idle; + u8 output_status; + u8 output_bytes; + bool output_escaped; + bool output_escape_high_nibble; + struct work_struct work; + wait_queue_head_t idle_wait; + u8 buffer[HSS1394_MAX_PACKET_SIZE]; + bool transaction_running; + struct fw_transaction transaction; + unsigned int transaction_bytes; + bool error; + struct fw_device *fw_dev; +}; + +static const u8 sysex_escape_prefix[] = { + 0xf0, /* SysEx begin */ + 0x00, 0x01, 0x60, /* Stanton DJ */ + 0x48, 0x53, 0x53, /* "HSS" */ +}; + +static void midi_input_escaped_byte(struct snd_rawmidi_substream *stream, + u8 byte) +{ + u8 nibbles[2]; + + nibbles[0] = byte >> 4; + nibbles[1] = byte & 0x0f; + snd_rawmidi_receive(stream, nibbles, 2); +} + +static void midi_input_byte(struct fw_scs1x *scs, + struct snd_rawmidi_substream *stream, u8 byte) +{ + const u8 eox = 0xf7; + + if (scs->input_escape_count > 0) { + midi_input_escaped_byte(stream, byte); + scs->input_escape_count--; + if (scs->input_escape_count == 0) + snd_rawmidi_receive(stream, &eox, sizeof(eox)); + } else if (byte == 0xf9) { + snd_rawmidi_receive(stream, sysex_escape_prefix, + ARRAY_SIZE(sysex_escape_prefix)); + midi_input_escaped_byte(stream, 0x00); + midi_input_escaped_byte(stream, 0xf9); + scs->input_escape_count = 3; + } else { + snd_rawmidi_receive(stream, &byte, 1); + } +} + +static void midi_input_packet(struct fw_scs1x *scs, + struct snd_rawmidi_substream *stream, + const u8 *data, unsigned int bytes) +{ + unsigned int i; + const u8 eox = 0xf7; + + if (data[0] == HSS1394_TAG_USER_DATA) { + for (i = 1; i < bytes; ++i) + midi_input_byte(scs, stream, data[i]); + } else { + snd_rawmidi_receive(stream, sysex_escape_prefix, + ARRAY_SIZE(sysex_escape_prefix)); + for (i = 0; i < bytes; ++i) + midi_input_escaped_byte(stream, data[i]); + snd_rawmidi_receive(stream, &eox, sizeof(eox)); + } +} + +static void handle_hss(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 fw_scs1x *scs = callback_data; + struct snd_rawmidi_substream *stream; + int rcode; + + if (offset != scs->hss_handler.offset) { + rcode = RCODE_ADDRESS_ERROR; + goto end; + } + if (tcode != TCODE_WRITE_QUADLET_REQUEST && + tcode != TCODE_WRITE_BLOCK_REQUEST) { + rcode = RCODE_TYPE_ERROR; + goto end; + } + + if (length >= 1) { + stream = READ_ONCE(scs->input); + if (stream) + midi_input_packet(scs, stream, data, length); + } + + rcode = RCODE_COMPLETE; +end: + fw_send_response(card, request, rcode); +} + +static void scs_write_callback(struct fw_card *card, int rcode, + void *data, size_t length, void *callback_data) +{ + struct fw_scs1x *scs = callback_data; + + if (!rcode_is_permanent_error(rcode)) { + /* Don't retry for this data. */ + if (rcode == RCODE_COMPLETE) + scs->transaction_bytes = 0; + } else { + scs->error = true; + } + + scs->transaction_running = false; + schedule_work(&scs->work); +} + +static bool is_valid_running_status(u8 status) +{ + return status >= 0x80 && status <= 0xef; +} + +static bool is_one_byte_cmd(u8 status) +{ + return status == 0xf6 || + status >= 0xf8; +} + +static bool is_two_bytes_cmd(u8 status) +{ + return (status >= 0xc0 && status <= 0xdf) || + status == 0xf1 || + status == 0xf3; +} + +static bool is_three_bytes_cmd(u8 status) +{ + return (status >= 0x80 && status <= 0xbf) || + (status >= 0xe0 && status <= 0xef) || + status == 0xf2; +} + +static bool is_invalid_cmd(u8 status) +{ + return status == 0xf4 || + status == 0xf5 || + status == 0xf9 || + status == 0xfd; +} + +static void scs_output_work(struct work_struct *work) +{ + struct fw_scs1x *scs = container_of(work, struct fw_scs1x, work); + struct snd_rawmidi_substream *stream; + unsigned int i; + u8 byte; + int generation; + + if (scs->transaction_running) + return; + + stream = READ_ONCE(scs->output); + if (!stream || scs->error) { + scs->output_idle = true; + wake_up(&scs->idle_wait); + return; + } + + if (scs->transaction_bytes > 0) + goto retry; + + i = scs->output_bytes; + for (;;) { + if (snd_rawmidi_transmit(stream, &byte, 1) != 1) { + scs->output_bytes = i; + scs->output_idle = true; + wake_up(&scs->idle_wait); + return; + } + /* + * Convert from real MIDI to what I think the device expects (no + * running status, one command per packet, unescaped SysExs). + */ + if (scs->output_escaped && byte < 0x80) { + if (scs->output_escape_high_nibble) { + if (i < HSS1394_MAX_PACKET_SIZE) { + scs->buffer[i] = byte << 4; + scs->output_escape_high_nibble = false; + } + } else { + scs->buffer[i++] |= byte & 0x0f; + scs->output_escape_high_nibble = true; + } + } else if (byte < 0x80) { + if (i == 1) { + if (!is_valid_running_status( + scs->output_status)) + continue; + scs->buffer[0] = HSS1394_TAG_USER_DATA; + scs->buffer[i++] = scs->output_status; + } + scs->buffer[i++] = byte; + if ((i == 3 && is_two_bytes_cmd(scs->output_status)) || + (i == 4 && is_three_bytes_cmd(scs->output_status))) + break; + if (i == 1 + ARRAY_SIZE(sysex_escape_prefix) && + !memcmp(scs->buffer + 1, sysex_escape_prefix, + ARRAY_SIZE(sysex_escape_prefix))) { + scs->output_escaped = true; + scs->output_escape_high_nibble = true; + i = 0; + } + if (i >= HSS1394_MAX_PACKET_SIZE) + i = 1; + } else if (byte == 0xf7) { + if (scs->output_escaped) { + if (i >= 1 && scs->output_escape_high_nibble && + scs->buffer[0] != + HSS1394_TAG_CHANGE_ADDRESS) + break; + } else { + if (i > 1 && scs->output_status == 0xf0) { + scs->buffer[i++] = 0xf7; + break; + } + } + i = 1; + scs->output_escaped = false; + } else if (!is_invalid_cmd(byte) && byte < 0xf8) { + i = 1; + scs->buffer[0] = HSS1394_TAG_USER_DATA; + scs->buffer[i++] = byte; + scs->output_status = byte; + scs->output_escaped = false; + if (is_one_byte_cmd(byte)) + break; + } + } + scs->output_bytes = 1; + scs->output_escaped = false; + + scs->transaction_bytes = i; +retry: + scs->transaction_running = true; + generation = scs->fw_dev->generation; + smp_rmb(); /* node_id vs. generation */ + fw_send_request(scs->fw_dev->card, &scs->transaction, + TCODE_WRITE_BLOCK_REQUEST, scs->fw_dev->node_id, + generation, scs->fw_dev->max_speed, HSS1394_ADDRESS, + scs->buffer, scs->transaction_bytes, + scs_write_callback, scs); +} + +static int midi_capture_open(struct snd_rawmidi_substream *stream) +{ + return 0; +} + +static int midi_capture_close(struct snd_rawmidi_substream *stream) +{ + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *stream, int up) +{ + struct fw_scs1x *scs = stream->rmidi->private_data; + + if (up) { + scs->input_escape_count = 0; + WRITE_ONCE(scs->input, stream); + } else { + WRITE_ONCE(scs->input, NULL); + } +} + +static int midi_playback_open(struct snd_rawmidi_substream *stream) +{ + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *stream) +{ + return 0; +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *stream, int up) +{ + struct fw_scs1x *scs = stream->rmidi->private_data; + + if (up) { + scs->output_status = 0; + scs->output_bytes = 1; + scs->output_escaped = false; + scs->output_idle = false; + scs->transaction_bytes = 0; + scs->error = false; + + WRITE_ONCE(scs->output, stream); + schedule_work(&scs->work); + } else { + WRITE_ONCE(scs->output, NULL); + } +} +static void midi_playback_drain(struct snd_rawmidi_substream *stream) +{ + struct fw_scs1x *scs = stream->rmidi->private_data; + + wait_event(scs->idle_wait, scs->output_idle); +} + +static int register_address(struct snd_oxfw *oxfw) +{ + struct fw_scs1x *scs = oxfw->spec; + __be64 data; + + data = cpu_to_be64(((u64)HSS1394_TAG_CHANGE_ADDRESS << 56) | + scs->hss_handler.offset); + return snd_fw_transaction(oxfw->unit, TCODE_WRITE_BLOCK_REQUEST, + HSS1394_ADDRESS, &data, sizeof(data), 0); +} + +static void remove_scs1x(struct snd_rawmidi *rmidi) +{ + struct fw_scs1x *scs = rmidi->private_data; + + fw_core_remove_address_handler(&scs->hss_handler); +} + +void snd_oxfw_scs1x_update(struct snd_oxfw *oxfw) +{ + register_address(oxfw); +} + +int snd_oxfw_scs1x_add(struct snd_oxfw *oxfw) +{ + 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, + .drain = midi_playback_drain, + }; + struct snd_rawmidi *rmidi; + struct fw_scs1x *scs; + int err; + + scs = devm_kzalloc(&oxfw->card->card_dev, sizeof(struct fw_scs1x), + GFP_KERNEL); + if (!scs) + return -ENOMEM; + scs->fw_dev = fw_parent_device(oxfw->unit); + oxfw->spec = scs; + + /* Allocate own handler for imcoming asynchronous transaction. */ + scs->hss_handler.length = HSS1394_MAX_PACKET_SIZE; + scs->hss_handler.address_callback = handle_hss; + scs->hss_handler.callback_data = scs; + err = fw_core_add_address_handler(&scs->hss_handler, + &fw_high_memory_region); + if (err < 0) + return err; + + err = register_address(oxfw); + if (err < 0) + goto err_allocated; + + /* Use unique name for backward compatibility to scs1x module. */ + err = snd_rawmidi_new(oxfw->card, "SCS.1x", 0, 1, 1, &rmidi); + if (err < 0) + goto err_allocated; + rmidi->private_data = scs; + rmidi->private_free = remove_scs1x; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", oxfw->card->shortname); + + rmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &midi_capture_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &midi_playback_ops); + + INIT_WORK(&scs->work, scs_output_work); + init_waitqueue_head(&scs->idle_wait); + scs->output_idle = true; + + return 0; +err_allocated: + fw_core_remove_address_handler(&scs->hss_handler); + return err; +} diff --git a/sound/firewire/oxfw/oxfw-spkr.c b/sound/firewire/oxfw/oxfw-spkr.c new file mode 100644 index 000000000..f2767fb19 --- /dev/null +++ b/sound/firewire/oxfw/oxfw-spkr.c @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * oxfw-spkr.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + */ + +#include "oxfw.h" + +struct fw_spkr { + bool mute; + s16 volume[6]; + s16 volume_min; + s16 volume_max; + + unsigned int mixer_channels; + u8 mute_fb_id; + u8 volume_fb_id; +}; + +enum control_action { CTL_READ, CTL_WRITE }; +enum control_attribute { + CTL_MIN = 0x02, + CTL_MAX = 0x03, + CTL_CURRENT = 0x10, +}; + +static int avc_audio_feature_mute(struct fw_unit *unit, u8 fb_id, bool *value, + enum control_action action) +{ + u8 *buf; + u8 response_ok; + int err; + + buf = kmalloc(11, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (action == CTL_READ) { + buf[0] = 0x01; /* AV/C, STATUS */ + response_ok = 0x0c; /* STABLE */ + } else { + buf[0] = 0x00; /* AV/C, CONTROL */ + response_ok = 0x09; /* ACCEPTED */ + } + buf[1] = 0x08; /* audio unit 0 */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x81; /* function block type: feature */ + buf[4] = fb_id; /* function block ID */ + buf[5] = 0x10; /* control attribute: current */ + buf[6] = 0x02; /* selector length */ + buf[7] = 0x00; /* audio channel number */ + buf[8] = 0x01; /* control selector: mute */ + buf[9] = 0x01; /* control data length */ + if (action == CTL_READ) + buf[10] = 0xff; + else + buf[10] = *value ? 0x70 : 0x60; + + err = fcp_avc_transaction(unit, buf, 11, buf, 11, 0x3fe); + if (err < 0) + goto error; + if (err < 11) { + dev_err(&unit->device, "short FCP response\n"); + err = -EIO; + goto error; + } + if (buf[0] != response_ok) { + dev_err(&unit->device, "mute command failed\n"); + err = -EIO; + goto error; + } + if (action == CTL_READ) + *value = buf[10] == 0x70; + + err = 0; + +error: + kfree(buf); + + return err; +} + +static int avc_audio_feature_volume(struct fw_unit *unit, u8 fb_id, s16 *value, + unsigned int channel, + enum control_attribute attribute, + enum control_action action) +{ + u8 *buf; + u8 response_ok; + int err; + + buf = kmalloc(12, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (action == CTL_READ) { + buf[0] = 0x01; /* AV/C, STATUS */ + response_ok = 0x0c; /* STABLE */ + } else { + buf[0] = 0x00; /* AV/C, CONTROL */ + response_ok = 0x09; /* ACCEPTED */ + } + buf[1] = 0x08; /* audio unit 0 */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x81; /* function block type: feature */ + buf[4] = fb_id; /* function block ID */ + buf[5] = attribute; /* control attribute */ + buf[6] = 0x02; /* selector length */ + buf[7] = channel; /* audio channel number */ + buf[8] = 0x02; /* control selector: volume */ + buf[9] = 0x02; /* control data length */ + if (action == CTL_READ) { + buf[10] = 0xff; + buf[11] = 0xff; + } else { + buf[10] = *value >> 8; + buf[11] = *value; + } + + err = fcp_avc_transaction(unit, buf, 12, buf, 12, 0x3fe); + if (err < 0) + goto error; + if (err < 12) { + dev_err(&unit->device, "short FCP response\n"); + err = -EIO; + goto error; + } + if (buf[0] != response_ok) { + dev_err(&unit->device, "volume command failed\n"); + err = -EIO; + goto error; + } + if (action == CTL_READ) + *value = (buf[10] << 8) | buf[11]; + + err = 0; + +error: + kfree(buf); + + return err; +} + +static int spkr_mute_get(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct snd_oxfw *oxfw = control->private_data; + struct fw_spkr *spkr = oxfw->spec; + + value->value.integer.value[0] = !spkr->mute; + + return 0; +} + +static int spkr_mute_put(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct snd_oxfw *oxfw = control->private_data; + struct fw_spkr *spkr = oxfw->spec; + bool mute; + int err; + + mute = !value->value.integer.value[0]; + + if (mute == spkr->mute) + return 0; + + err = avc_audio_feature_mute(oxfw->unit, spkr->mute_fb_id, &mute, + CTL_WRITE); + if (err < 0) + return err; + spkr->mute = mute; + + return 1; +} + +static int spkr_volume_info(struct snd_kcontrol *control, + struct snd_ctl_elem_info *info) +{ + struct snd_oxfw *oxfw = control->private_data; + struct fw_spkr *spkr = oxfw->spec; + + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = spkr->mixer_channels; + info->value.integer.min = spkr->volume_min; + info->value.integer.max = spkr->volume_max; + + return 0; +} + +static const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 }; + +static int spkr_volume_get(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct snd_oxfw *oxfw = control->private_data; + struct fw_spkr *spkr = oxfw->spec; + unsigned int i; + + for (i = 0; i < spkr->mixer_channels; ++i) + value->value.integer.value[channel_map[i]] = spkr->volume[i]; + + return 0; +} + +static int spkr_volume_put(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct snd_oxfw *oxfw = control->private_data; + struct fw_spkr *spkr = oxfw->spec; + unsigned int i, changed_channels; + bool equal_values = true; + s16 volume; + int err; + + for (i = 0; i < spkr->mixer_channels; ++i) { + if (value->value.integer.value[i] < spkr->volume_min || + value->value.integer.value[i] > spkr->volume_max) + return -EINVAL; + if (value->value.integer.value[i] != + value->value.integer.value[0]) + equal_values = false; + } + + changed_channels = 0; + for (i = 0; i < spkr->mixer_channels; ++i) + if (value->value.integer.value[channel_map[i]] != + spkr->volume[i]) + changed_channels |= 1 << (i + 1); + + if (equal_values && changed_channels != 0) + changed_channels = 1 << 0; + + for (i = 0; i <= spkr->mixer_channels; ++i) { + volume = value->value.integer.value[channel_map[i ? i - 1 : 0]]; + if (changed_channels & (1 << i)) { + err = avc_audio_feature_volume(oxfw->unit, + spkr->volume_fb_id, &volume, + i, CTL_CURRENT, CTL_WRITE); + if (err < 0) + return err; + } + if (i > 0) + spkr->volume[i - 1] = volume; + } + + return changed_channels != 0; +} + +int snd_oxfw_add_spkr(struct snd_oxfw *oxfw, bool is_lacie) +{ + static const struct snd_kcontrol_new controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = spkr_mute_get, + .put = spkr_mute_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .info = spkr_volume_info, + .get = spkr_volume_get, + .put = spkr_volume_put, + }, + }; + struct fw_spkr *spkr; + unsigned int i, first_ch; + int err; + + spkr = devm_kzalloc(&oxfw->card->card_dev, sizeof(struct fw_spkr), + GFP_KERNEL); + if (!spkr) + return -ENOMEM; + oxfw->spec = spkr; + + if (is_lacie) { + spkr->mixer_channels = 1; + spkr->mute_fb_id = 0x01; + spkr->volume_fb_id = 0x01; + } else { + spkr->mixer_channels = 6; + spkr->mute_fb_id = 0x01; + spkr->volume_fb_id = 0x02; + } + + err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id, + &spkr->volume_min, 0, CTL_MIN, CTL_READ); + if (err < 0) + return err; + err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id, + &spkr->volume_max, 0, CTL_MAX, CTL_READ); + if (err < 0) + return err; + + err = avc_audio_feature_mute(oxfw->unit, spkr->mute_fb_id, &spkr->mute, + CTL_READ); + if (err < 0) + return err; + + first_ch = spkr->mixer_channels == 1 ? 0 : 1; + for (i = 0; i < spkr->mixer_channels; ++i) { + err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id, + &spkr->volume[i], first_ch + i, + CTL_CURRENT, CTL_READ); + if (err < 0) + return err; + } + + for (i = 0; i < ARRAY_SIZE(controls); ++i) { + err = snd_ctl_add(oxfw->card, + snd_ctl_new1(&controls[i], oxfw)); + if (err < 0) + return err; + } + + return 0; +} diff --git a/sound/firewire/oxfw/oxfw-stream.c b/sound/firewire/oxfw/oxfw-stream.c new file mode 100644 index 000000000..80c9dc13f --- /dev/null +++ b/sound/firewire/oxfw/oxfw-stream.c @@ -0,0 +1,831 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * oxfw_stream.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2014 Takashi Sakamoto + */ + +#include "oxfw.h" +#include <linux/delay.h> + +#define AVC_GENERIC_FRAME_MAXIMUM_BYTES 512 +#define CALLBACK_TIMEOUT 200 + +/* + * According to datasheet of Oxford Semiconductor: + * OXFW970: 32.0/44.1/48.0/96.0 Khz, 8 audio channels I/O + * OXFW971: 32.0/44.1/48.0/88.2/96.0/192.0 kHz, 16 audio channels I/O, MIDI I/O + */ +static const unsigned int oxfw_rate_table[] = { + [0] = 32000, + [1] = 44100, + [2] = 48000, + [3] = 88200, + [4] = 96000, + [5] = 192000, +}; + +/* + * See Table 5.7 – Sampling frequency for Multi-bit Audio + * in AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) + */ +static const unsigned int avc_stream_rate_table[] = { + [0] = 0x02, + [1] = 0x03, + [2] = 0x04, + [3] = 0x0a, + [4] = 0x05, + [5] = 0x07, +}; + +static int set_rate(struct snd_oxfw *oxfw, unsigned int rate) +{ + int err; + + err = avc_general_set_sig_fmt(oxfw->unit, rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) + goto end; + + if (oxfw->has_output) + err = avc_general_set_sig_fmt(oxfw->unit, rate, + AVC_GENERAL_PLUG_DIR_OUT, 0); +end: + return err; +} + +static int set_stream_format(struct snd_oxfw *oxfw, struct amdtp_stream *s, + unsigned int rate, unsigned int pcm_channels) +{ + u8 **formats; + struct snd_oxfw_stream_formation formation; + enum avc_general_plug_dir dir; + unsigned int len; + int i, err; + + if (s == &oxfw->tx_stream) { + formats = oxfw->tx_stream_formats; + dir = AVC_GENERAL_PLUG_DIR_OUT; + } else { + formats = oxfw->rx_stream_formats; + dir = AVC_GENERAL_PLUG_DIR_IN; + } + + /* Seek stream format for requirements. */ + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + err = snd_oxfw_stream_parse_format(formats[i], &formation); + if (err < 0) + return err; + + if ((formation.rate == rate) && (formation.pcm == pcm_channels)) + break; + } + if (i == SND_OXFW_STREAM_FORMAT_ENTRIES) + return -EINVAL; + + /* If assumed, just change rate. */ + if (oxfw->assumed) + return set_rate(oxfw, rate); + + /* Calculate format length. */ + len = 5 + formats[i][4] * 2; + + err = avc_stream_set_format(oxfw->unit, dir, 0, formats[i], len); + if (err < 0) + return err; + + /* Some requests just after changing format causes freezing. */ + msleep(100); + + return 0; +} + +static int start_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + int err; + + if (stream == &oxfw->rx_stream) + conn = &oxfw->in_conn; + else + conn = &oxfw->out_conn; + + err = cmp_connection_establish(conn); + if (err < 0) + return err; + + err = amdtp_domain_add_stream(&oxfw->domain, stream, + conn->resources.channel, conn->speed); + if (err < 0) { + cmp_connection_break(conn); + return err; + } + + return 0; +} + +static int check_connection_used_by_others(struct snd_oxfw *oxfw, + struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + bool used; + int err; + + if (stream == &oxfw->tx_stream) + conn = &oxfw->out_conn; + else + conn = &oxfw->in_conn; + + err = cmp_connection_check_used(conn, &used); + if ((err >= 0) && used && !amdtp_stream_running(stream)) { + dev_err(&oxfw->unit->device, + "Connection established by others: %cPCR[%d]\n", + (conn->direction == CMP_OUTPUT) ? 'o' : 'i', + conn->pcr_index); + err = -EBUSY; + } + + return err; +} + +static int init_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + enum cmp_direction c_dir; + enum amdtp_stream_direction s_dir; + int err; + + if (stream == &oxfw->tx_stream) { + conn = &oxfw->out_conn; + c_dir = CMP_OUTPUT; + s_dir = AMDTP_IN_STREAM; + } else { + conn = &oxfw->in_conn; + c_dir = CMP_INPUT; + s_dir = AMDTP_OUT_STREAM; + } + + err = cmp_connection_init(conn, oxfw->unit, c_dir, 0); + if (err < 0) + return err; + + err = amdtp_am824_init(stream, oxfw->unit, s_dir, CIP_NONBLOCKING); + if (err < 0) { + cmp_connection_destroy(conn); + return err; + } + + /* + * OXFW starts to transmit packets with non-zero dbc. + * OXFW postpone transferring packets till handling any asynchronous + * packets. As a result, next isochronous packet includes more data + * blocks than IEC 61883-6 defines. + */ + if (stream == &oxfw->tx_stream) { + oxfw->tx_stream.flags |= CIP_JUMBO_PAYLOAD; + if (oxfw->wrong_dbs) + oxfw->tx_stream.flags |= CIP_WRONG_DBS; + } + + return 0; +} + +static int keep_resources(struct snd_oxfw *oxfw, struct amdtp_stream *stream) +{ + enum avc_general_plug_dir dir; + u8 **formats; + struct snd_oxfw_stream_formation formation; + struct cmp_connection *conn; + int i; + int err; + + if (stream == &oxfw->rx_stream) { + dir = AVC_GENERAL_PLUG_DIR_IN; + formats = oxfw->rx_stream_formats; + conn = &oxfw->in_conn; + } else { + dir = AVC_GENERAL_PLUG_DIR_OUT; + formats = oxfw->tx_stream_formats; + conn = &oxfw->out_conn; + } + + err = snd_oxfw_stream_get_current_formation(oxfw, dir, &formation); + if (err < 0) + return err; + + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + struct snd_oxfw_stream_formation fmt; + + if (formats[i] == NULL) + break; + + err = snd_oxfw_stream_parse_format(formats[i], &fmt); + if (err < 0) + return err; + + if (fmt.rate == formation.rate && fmt.pcm == formation.pcm && + fmt.midi == formation.midi) + break; + } + if (i == SND_OXFW_STREAM_FORMAT_ENTRIES) + return -EINVAL; + + // The stream should have one pcm channels at least. + if (formation.pcm == 0) + return -EINVAL; + + err = amdtp_am824_set_parameters(stream, formation.rate, formation.pcm, + formation.midi * 8, false); + if (err < 0) + return err; + + return cmp_connection_reserve(conn, amdtp_stream_get_max_payload(stream)); +} + +int snd_oxfw_stream_reserve_duplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream, + unsigned int rate, unsigned int pcm_channels, + unsigned int frames_per_period, + unsigned int frames_per_buffer) +{ + struct snd_oxfw_stream_formation formation; + enum avc_general_plug_dir dir; + int err; + + // Considering JACK/FFADO streaming: + // TODO: This can be removed hwdep functionality becomes popular. + err = check_connection_used_by_others(oxfw, &oxfw->rx_stream); + if (err < 0) + return err; + if (oxfw->has_output) { + err = check_connection_used_by_others(oxfw, &oxfw->tx_stream); + if (err < 0) + return err; + } + + if (stream == &oxfw->tx_stream) + dir = AVC_GENERAL_PLUG_DIR_OUT; + else + dir = AVC_GENERAL_PLUG_DIR_IN; + + err = snd_oxfw_stream_get_current_formation(oxfw, dir, &formation); + if (err < 0) + return err; + if (rate == 0) { + rate = formation.rate; + pcm_channels = formation.pcm; + } + if (formation.rate != rate || formation.pcm != pcm_channels) { + amdtp_domain_stop(&oxfw->domain); + + cmp_connection_break(&oxfw->in_conn); + cmp_connection_release(&oxfw->in_conn); + + if (oxfw->has_output) { + cmp_connection_break(&oxfw->out_conn); + cmp_connection_release(&oxfw->out_conn); + } + } + + if (oxfw->substreams_count == 0 || + formation.rate != rate || formation.pcm != pcm_channels) { + err = set_stream_format(oxfw, stream, rate, pcm_channels); + if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to set stream format: %d\n", err); + return err; + } + + err = keep_resources(oxfw, &oxfw->rx_stream); + if (err < 0) + return err; + + if (oxfw->has_output) { + err = keep_resources(oxfw, &oxfw->tx_stream); + if (err < 0) { + cmp_connection_release(&oxfw->in_conn); + return err; + } + } + + err = amdtp_domain_set_events_per_period(&oxfw->domain, + frames_per_period, frames_per_buffer); + if (err < 0) { + cmp_connection_release(&oxfw->in_conn); + if (oxfw->has_output) + cmp_connection_release(&oxfw->out_conn); + return err; + } + } + + return 0; +} + +int snd_oxfw_stream_start_duplex(struct snd_oxfw *oxfw) +{ + int err; + + if (oxfw->substreams_count == 0) + return -EIO; + + if (amdtp_streaming_error(&oxfw->rx_stream) || + amdtp_streaming_error(&oxfw->tx_stream)) { + amdtp_domain_stop(&oxfw->domain); + + cmp_connection_break(&oxfw->in_conn); + if (oxfw->has_output) + cmp_connection_break(&oxfw->out_conn); + } + + if (!amdtp_stream_running(&oxfw->rx_stream)) { + err = start_stream(oxfw, &oxfw->rx_stream); + if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to prepare rx stream: %d\n", err); + goto error; + } + + if (oxfw->has_output && + !amdtp_stream_running(&oxfw->tx_stream)) { + err = start_stream(oxfw, &oxfw->tx_stream); + if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to prepare tx stream: %d\n", err); + goto error; + } + } + + err = amdtp_domain_start(&oxfw->domain, 0); + if (err < 0) + goto error; + + // Wait first packet. + if (!amdtp_stream_wait_callback(&oxfw->rx_stream, + CALLBACK_TIMEOUT)) { + err = -ETIMEDOUT; + goto error; + } + + if (oxfw->has_output) { + if (!amdtp_stream_wait_callback(&oxfw->tx_stream, + CALLBACK_TIMEOUT)) { + err = -ETIMEDOUT; + goto error; + } + } + } + + return 0; +error: + amdtp_domain_stop(&oxfw->domain); + + cmp_connection_break(&oxfw->in_conn); + if (oxfw->has_output) + cmp_connection_break(&oxfw->out_conn); + + return err; +} + +void snd_oxfw_stream_stop_duplex(struct snd_oxfw *oxfw) +{ + if (oxfw->substreams_count == 0) { + amdtp_domain_stop(&oxfw->domain); + + cmp_connection_break(&oxfw->in_conn); + cmp_connection_release(&oxfw->in_conn); + + if (oxfw->has_output) { + cmp_connection_break(&oxfw->out_conn); + cmp_connection_release(&oxfw->out_conn); + } + } +} + +static void destroy_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + + if (stream == &oxfw->tx_stream) + conn = &oxfw->out_conn; + else + conn = &oxfw->in_conn; + + amdtp_stream_destroy(stream); + cmp_connection_destroy(conn); +} + +int snd_oxfw_stream_init_duplex(struct snd_oxfw *oxfw) +{ + int err; + + err = init_stream(oxfw, &oxfw->rx_stream); + if (err < 0) + return err; + + if (oxfw->has_output) { + err = init_stream(oxfw, &oxfw->tx_stream); + if (err < 0) { + destroy_stream(oxfw, &oxfw->rx_stream); + return err; + } + } + + err = amdtp_domain_init(&oxfw->domain); + if (err < 0) { + destroy_stream(oxfw, &oxfw->rx_stream); + if (oxfw->has_output) + destroy_stream(oxfw, &oxfw->tx_stream); + } + + return err; +} + +// This function should be called before starting the stream or after stopping +// the streams. +void snd_oxfw_stream_destroy_duplex(struct snd_oxfw *oxfw) +{ + amdtp_domain_destroy(&oxfw->domain); + + destroy_stream(oxfw, &oxfw->rx_stream); + + if (oxfw->has_output) + destroy_stream(oxfw, &oxfw->tx_stream); +} + +void snd_oxfw_stream_update_duplex(struct snd_oxfw *oxfw) +{ + amdtp_domain_stop(&oxfw->domain); + + cmp_connection_break(&oxfw->in_conn); + + amdtp_stream_pcm_abort(&oxfw->rx_stream); + + if (oxfw->has_output) { + cmp_connection_break(&oxfw->out_conn); + + amdtp_stream_pcm_abort(&oxfw->tx_stream); + } +} + +int snd_oxfw_stream_get_current_formation(struct snd_oxfw *oxfw, + enum avc_general_plug_dir dir, + struct snd_oxfw_stream_formation *formation) +{ + u8 *format; + unsigned int len; + int err; + + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + format = kmalloc(len, GFP_KERNEL); + if (format == NULL) + return -ENOMEM; + + err = avc_stream_get_format_single(oxfw->unit, dir, 0, format, &len); + if (err < 0) + goto end; + if (len < 3) { + err = -EIO; + goto end; + } + + err = snd_oxfw_stream_parse_format(format, formation); +end: + kfree(format); + return err; +} + +/* + * See Table 6.16 - AM824 Stream Format + * Figure 6.19 - format_information field for AM824 Compound + * in AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) + * Also 'Clause 12 AM824 sequence adaption layers' in IEC 61883-6:2005 + */ +int snd_oxfw_stream_parse_format(u8 *format, + struct snd_oxfw_stream_formation *formation) +{ + unsigned int i, e, channels, type; + + memset(formation, 0, sizeof(struct snd_oxfw_stream_formation)); + + /* + * this module can support a hierarchy combination that: + * Root: Audio and Music (0x90) + * Level 1: AM824 Compound (0x40) + */ + if ((format[0] != 0x90) || (format[1] != 0x40)) + return -ENXIO; + + /* check the sampling rate */ + for (i = 0; i < ARRAY_SIZE(avc_stream_rate_table); i++) { + if (format[2] == avc_stream_rate_table[i]) + break; + } + if (i == ARRAY_SIZE(avc_stream_rate_table)) + return -ENXIO; + + formation->rate = oxfw_rate_table[i]; + + for (e = 0; e < format[4]; e++) { + channels = format[5 + e * 2]; + type = format[6 + e * 2]; + + switch (type) { + /* IEC 60958 Conformant, currently handled as MBLA */ + case 0x00: + /* Multi Bit Linear Audio (Raw) */ + case 0x06: + formation->pcm += channels; + break; + /* MIDI Conformant */ + case 0x0d: + formation->midi = channels; + break; + /* IEC 61937-3 to 7 */ + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + /* Multi Bit Linear Audio */ + case 0x07: /* DVD-Audio */ + case 0x0c: /* High Precision */ + /* One Bit Audio */ + case 0x08: /* (Plain) Raw */ + case 0x09: /* (Plain) SACD */ + case 0x0a: /* (Encoded) Raw */ + case 0x0b: /* (Encoded) SACD */ + /* SMPTE Time-Code conformant */ + case 0x0e: + /* Sample Count */ + case 0x0f: + /* Anciliary Data */ + case 0x10: + /* Synchronization Stream (Stereo Raw audio) */ + case 0x40: + /* Don't care */ + case 0xff: + default: + return -ENXIO; /* not supported */ + } + } + + if (formation->pcm > AM824_MAX_CHANNELS_FOR_PCM || + formation->midi > AM824_MAX_CHANNELS_FOR_MIDI) + return -ENXIO; + + return 0; +} + +static int +assume_stream_formats(struct snd_oxfw *oxfw, enum avc_general_plug_dir dir, + unsigned int pid, u8 *buf, unsigned int *len, + u8 **formats) +{ + struct snd_oxfw_stream_formation formation; + unsigned int i, eid; + int err; + + /* get format at current sampling rate */ + err = avc_stream_get_format_single(oxfw->unit, dir, pid, buf, len); + if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to get current stream format for isoc %s plug %d:%d\n", + (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" : "out", + pid, err); + goto end; + } + + /* parse and set stream format */ + eid = 0; + err = snd_oxfw_stream_parse_format(buf, &formation); + if (err < 0) + goto end; + + formats[eid] = devm_kmemdup(&oxfw->card->card_dev, buf, *len, + GFP_KERNEL); + if (!formats[eid]) { + err = -ENOMEM; + goto end; + } + + /* apply the format for each available sampling rate */ + for (i = 0; i < ARRAY_SIZE(oxfw_rate_table); i++) { + if (formation.rate == oxfw_rate_table[i]) + continue; + + err = avc_general_inquiry_sig_fmt(oxfw->unit, + oxfw_rate_table[i], + dir, pid); + if (err < 0) + continue; + + eid++; + formats[eid] = devm_kmemdup(&oxfw->card->card_dev, buf, *len, + GFP_KERNEL); + if (formats[eid] == NULL) { + err = -ENOMEM; + goto end; + } + formats[eid][2] = avc_stream_rate_table[i]; + } + + err = 0; + oxfw->assumed = true; +end: + return err; +} + +static int fill_stream_formats(struct snd_oxfw *oxfw, + enum avc_general_plug_dir dir, + unsigned short pid) +{ + u8 *buf, **formats; + unsigned int len, eid = 0; + struct snd_oxfw_stream_formation dummy; + int err; + + buf = kmalloc(AVC_GENERIC_FRAME_MAXIMUM_BYTES, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + if (dir == AVC_GENERAL_PLUG_DIR_OUT) + formats = oxfw->tx_stream_formats; + else + formats = oxfw->rx_stream_formats; + + /* get first entry */ + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + err = avc_stream_get_format_list(oxfw->unit, dir, 0, buf, &len, 0); + if (err == -ENXIO) { + /* LIST subfunction is not implemented */ + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + err = assume_stream_formats(oxfw, dir, pid, buf, &len, + formats); + goto end; + } else if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to get stream format %d for isoc %s plug %d:%d\n", + eid, (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" : "out", + pid, err); + goto end; + } + + /* LIST subfunction is implemented */ + while (eid < SND_OXFW_STREAM_FORMAT_ENTRIES) { + /* The format is too short. */ + if (len < 3) { + err = -EIO; + break; + } + + /* parse and set stream format */ + err = snd_oxfw_stream_parse_format(buf, &dummy); + if (err < 0) + break; + + formats[eid] = devm_kmemdup(&oxfw->card->card_dev, buf, len, + GFP_KERNEL); + if (!formats[eid]) { + err = -ENOMEM; + break; + } + + /* get next entry */ + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + err = avc_stream_get_format_list(oxfw->unit, dir, 0, + buf, &len, ++eid); + /* No entries remained. */ + if (err == -EINVAL) { + err = 0; + break; + } else if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to get stream format %d for isoc %s plug %d:%d\n", + eid, (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" : + "out", + pid, err); + break; + } + } +end: + kfree(buf); + return err; +} + +int snd_oxfw_stream_discover(struct snd_oxfw *oxfw) +{ + u8 plugs[AVC_PLUG_INFO_BUF_BYTES]; + struct snd_oxfw_stream_formation formation; + u8 *format; + unsigned int i; + int err; + + /* the number of plugs for isoc in/out, ext in/out */ + err = avc_general_get_plug_info(oxfw->unit, 0x1f, 0x07, 0x00, plugs); + if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to get info for isoc/external in/out plugs: %d\n", + err); + goto end; + } else if ((plugs[0] == 0) && (plugs[1] == 0)) { + err = -ENXIO; + goto end; + } + + /* use oPCR[0] if exists */ + if (plugs[1] > 0) { + err = fill_stream_formats(oxfw, AVC_GENERAL_PLUG_DIR_OUT, 0); + if (err < 0) { + if (err != -ENXIO) + return err; + + // The oPCR is not available for isoc communication. + err = 0; + } else { + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + format = oxfw->tx_stream_formats[i]; + if (format == NULL) + continue; + err = snd_oxfw_stream_parse_format(format, + &formation); + if (err < 0) + continue; + + /* Add one MIDI port. */ + if (formation.midi > 0) + oxfw->midi_input_ports = 1; + } + + oxfw->has_output = true; + } + } + + /* use iPCR[0] if exists */ + if (plugs[0] > 0) { + err = fill_stream_formats(oxfw, AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) { + if (err != -ENXIO) + return err; + + // The iPCR is not available for isoc communication. + err = 0; + } else { + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + format = oxfw->rx_stream_formats[i]; + if (format == NULL) + continue; + err = snd_oxfw_stream_parse_format(format, + &formation); + if (err < 0) + continue; + + /* Add one MIDI port. */ + if (formation.midi > 0) + oxfw->midi_output_ports = 1; + } + + oxfw->has_input = true; + } + } +end: + return err; +} + +void snd_oxfw_stream_lock_changed(struct snd_oxfw *oxfw) +{ + oxfw->dev_lock_changed = true; + wake_up(&oxfw->hwdep_wait); +} + +int snd_oxfw_stream_lock_try(struct snd_oxfw *oxfw) +{ + int err; + + spin_lock_irq(&oxfw->lock); + + /* user land lock this */ + if (oxfw->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (oxfw->dev_lock_count++ == 0) + snd_oxfw_stream_lock_changed(oxfw); + err = 0; +end: + spin_unlock_irq(&oxfw->lock); + return err; +} + +void snd_oxfw_stream_lock_release(struct snd_oxfw *oxfw) +{ + spin_lock_irq(&oxfw->lock); + + if (WARN_ON(oxfw->dev_lock_count <= 0)) + goto end; + if (--oxfw->dev_lock_count == 0) + snd_oxfw_stream_lock_changed(oxfw); +end: + spin_unlock_irq(&oxfw->lock); +} diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c new file mode 100644 index 000000000..5490637d2 --- /dev/null +++ b/sound/firewire/oxfw/oxfw.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * oxfw.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + */ + +#include "oxfw.h" + +#define OXFORD_FIRMWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x50000) +/* 0x970?vvvv or 0x971?vvvv, where vvvv = firmware version */ + +#define OXFORD_HARDWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x90020) +#define OXFORD_HARDWARE_ID_OXFW970 0x39443841 +#define OXFORD_HARDWARE_ID_OXFW971 0x39373100 + +#define VENDOR_LOUD 0x000ff2 +#define VENDOR_GRIFFIN 0x001292 +#define VENDOR_BEHRINGER 0x001564 +#define VENDOR_LACIE 0x00d04b +#define VENDOR_TASCAM 0x00022e +#define OUI_STANTON 0x001260 +#define OUI_APOGEE 0x0003db + +#define MODEL_SATELLITE 0x00200f + +#define SPECIFIER_1394TA 0x00a02d +#define VERSION_AVC 0x010001 + +MODULE_DESCRIPTION("Oxford Semiconductor FW970/971 driver"); +MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("snd-firewire-speakers"); +MODULE_ALIAS("snd-scs1x"); + +struct compat_info { + const char *driver_name; + const char *vendor_name; + const char *model_name; +}; + +static bool detect_loud_models(struct fw_unit *unit) +{ + const char *const models[] = { + "Onyxi", + "Onyx-i", + "Onyx 1640i", + "d.Pro", + "Mackie Onyx Satellite", + "Tapco LINK.firewire 4x6", + "U.420"}; + char model[32]; + int err; + + err = fw_csr_string(unit->directory, CSR_MODEL, + model, sizeof(model)); + if (err < 0) + return false; + + return match_string(models, ARRAY_SIZE(models), model) >= 0; +} + +static int name_card(struct snd_oxfw *oxfw) +{ + struct fw_device *fw_dev = fw_parent_device(oxfw->unit); + const struct compat_info *info; + char vendor[24]; + char model[32]; + const char *d, *v, *m; + u32 firmware; + int err; + + /* get vendor name from root directory */ + err = fw_csr_string(fw_dev->config_rom + 5, CSR_VENDOR, + vendor, sizeof(vendor)); + if (err < 0) + goto end; + + /* get model name from unit directory */ + err = fw_csr_string(oxfw->unit->directory, CSR_MODEL, + model, sizeof(model)); + if (err < 0) + goto end; + + err = snd_fw_transaction(oxfw->unit, TCODE_READ_QUADLET_REQUEST, + OXFORD_FIRMWARE_ID_ADDRESS, &firmware, 4, 0); + if (err < 0) + goto end; + be32_to_cpus(&firmware); + + /* to apply card definitions */ + if (oxfw->entry->vendor_id == VENDOR_GRIFFIN || + oxfw->entry->vendor_id == VENDOR_LACIE) { + info = (const struct compat_info *)oxfw->entry->driver_data; + d = info->driver_name; + v = info->vendor_name; + m = info->model_name; + } else { + d = "OXFW"; + v = vendor; + m = model; + } + + strcpy(oxfw->card->driver, d); + strcpy(oxfw->card->mixername, m); + strcpy(oxfw->card->shortname, m); + + snprintf(oxfw->card->longname, sizeof(oxfw->card->longname), + "%s %s (OXFW%x %04x), GUID %08x%08x at %s, S%d", + v, m, firmware >> 20, firmware & 0xffff, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&oxfw->unit->device), 100 << fw_dev->max_speed); +end: + return err; +} + +static void oxfw_card_free(struct snd_card *card) +{ + struct snd_oxfw *oxfw = card->private_data; + + if (oxfw->has_output || oxfw->has_input) + snd_oxfw_stream_destroy_duplex(oxfw); +} + +static int detect_quirks(struct snd_oxfw *oxfw) +{ + struct fw_device *fw_dev = fw_parent_device(oxfw->unit); + struct fw_csr_iterator it; + int key, val; + int vendor, model; + + /* + * Add ALSA control elements for two models to keep compatibility to + * old firewire-speaker module. + */ + if (oxfw->entry->vendor_id == VENDOR_GRIFFIN) + return snd_oxfw_add_spkr(oxfw, false); + if (oxfw->entry->vendor_id == VENDOR_LACIE) + return snd_oxfw_add_spkr(oxfw, true); + + /* + * Stanton models supports asynchronous transactions for unique MIDI + * messages. + */ + if (oxfw->entry->vendor_id == OUI_STANTON) { + /* No physical MIDI ports. */ + oxfw->midi_input_ports = 0; + oxfw->midi_output_ports = 0; + + return snd_oxfw_scs1x_add(oxfw); + } + + /* + * TASCAM FireOne has physical control and requires a pair of additional + * MIDI ports. + */ + if (oxfw->entry->vendor_id == VENDOR_TASCAM) { + oxfw->midi_input_ports++; + oxfw->midi_output_ports++; + return 0; + } + + /* Seek from Root Directory of Config ROM. */ + vendor = model = 0; + fw_csr_iterator_init(&it, fw_dev->config_rom + 5); + while (fw_csr_iterator_next(&it, &key, &val)) { + if (key == CSR_VENDOR) + vendor = val; + else if (key == CSR_MODEL) + model = val; + } + + /* + * Mackie Onyx Satellite with base station has a quirk to report a wrong + * value in 'dbs' field of CIP header against its format information. + */ + if (vendor == VENDOR_LOUD && model == MODEL_SATELLITE) + oxfw->wrong_dbs = true; + + return 0; +} + +static void do_registration(struct work_struct *work) +{ + struct snd_oxfw *oxfw = container_of(work, struct snd_oxfw, dwork.work); + int err; + + if (oxfw->registered) + return; + + err = snd_card_new(&oxfw->unit->device, -1, NULL, THIS_MODULE, 0, + &oxfw->card); + if (err < 0) + return; + oxfw->card->private_free = oxfw_card_free; + oxfw->card->private_data = oxfw; + + err = name_card(oxfw); + if (err < 0) + goto error; + + err = snd_oxfw_stream_discover(oxfw); + if (err < 0) + goto error; + + err = detect_quirks(oxfw); + if (err < 0) + goto error; + + if (oxfw->has_output || oxfw->has_input) { + err = snd_oxfw_stream_init_duplex(oxfw); + if (err < 0) + goto error; + + err = snd_oxfw_create_pcm(oxfw); + if (err < 0) + goto error; + + snd_oxfw_proc_init(oxfw); + + err = snd_oxfw_create_midi(oxfw); + if (err < 0) + goto error; + + err = snd_oxfw_create_hwdep(oxfw); + if (err < 0) + goto error; + } + + err = snd_card_register(oxfw->card); + if (err < 0) + goto error; + + oxfw->registered = true; + + return; +error: + snd_card_free(oxfw->card); + dev_info(&oxfw->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int oxfw_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_oxfw *oxfw; + + if (entry->vendor_id == VENDOR_LOUD && !detect_loud_models(unit)) + return -ENODEV; + + /* Allocate this independent of sound card instance. */ + oxfw = devm_kzalloc(&unit->device, sizeof(struct snd_oxfw), GFP_KERNEL); + if (!oxfw) + return -ENOMEM; + oxfw->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, oxfw); + + oxfw->entry = entry; + mutex_init(&oxfw->mutex); + spin_lock_init(&oxfw->lock); + init_waitqueue_head(&oxfw->hwdep_wait); + + /* Allocate and register this sound card later. */ + INIT_DEFERRABLE_WORK(&oxfw->dwork, do_registration); + snd_fw_schedule_registration(unit, &oxfw->dwork); + + return 0; +} + +static void oxfw_bus_reset(struct fw_unit *unit) +{ + struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device); + + if (!oxfw->registered) + snd_fw_schedule_registration(unit, &oxfw->dwork); + + fcp_bus_reset(oxfw->unit); + + if (oxfw->registered) { + if (oxfw->has_output || oxfw->has_input) { + mutex_lock(&oxfw->mutex); + snd_oxfw_stream_update_duplex(oxfw); + mutex_unlock(&oxfw->mutex); + } + + if (oxfw->entry->vendor_id == OUI_STANTON) + snd_oxfw_scs1x_update(oxfw); + } +} + +static void oxfw_remove(struct fw_unit *unit) +{ + struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device); + + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_delayed_work_sync(&oxfw->dwork); + + if (oxfw->registered) { + // Block till all of ALSA character devices are released. + snd_card_free(oxfw->card); + } + + mutex_destroy(&oxfw->mutex); + fw_unit_put(oxfw->unit); +} + +static const struct compat_info griffin_firewave = { + .driver_name = "FireWave", + .vendor_name = "Griffin", + .model_name = "FireWave", +}; + +static const struct compat_info lacie_speakers = { + .driver_name = "FWSpeakers", + .vendor_name = "LaCie", + .model_name = "FireWire Speakers", +}; + +static const struct ieee1394_device_id oxfw_id_table[] = { + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = VENDOR_GRIFFIN, + .model_id = 0x00f970, + .specifier_id = SPECIFIER_1394TA, + .version = VERSION_AVC, + .driver_data = (kernel_ulong_t)&griffin_firewave, + }, + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = VENDOR_LACIE, + .model_id = 0x00f970, + .specifier_id = SPECIFIER_1394TA, + .version = VERSION_AVC, + .driver_data = (kernel_ulong_t)&lacie_speakers, + }, + /* Behringer,F-Control Audio 202 */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = VENDOR_BEHRINGER, + .model_id = 0x00fc22, + }, + /* + * Any Mackie(Loud) models (name string/model id): + * Onyx-i series (former models): 0x081216 + * Mackie Onyx Satellite: 0x00200f + * Tapco LINK.firewire 4x6: 0x000460 + * d.2 pro/d.4 pro (built-in card): Unknown + * U.420: Unknown + * U.420d: Unknown + */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = VENDOR_LOUD, + .specifier_id = SPECIFIER_1394TA, + .version = VERSION_AVC, + }, + /* TASCAM, FireOne */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = VENDOR_TASCAM, + .model_id = 0x800007, + }, + /* Stanton, Stanton Controllers & Systems 1 Mixer (SCS.1m) */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_STANTON, + .model_id = 0x001000, + }, + /* Stanton, Stanton Controllers & Systems 1 Deck (SCS.1d) */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_STANTON, + .model_id = 0x002000, + }, + // APOGEE, duet FireWire + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_APOGEE, + .model_id = 0x01dddd, + }, + { } +}; +MODULE_DEVICE_TABLE(ieee1394, oxfw_id_table); + +static struct fw_driver oxfw_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + }, + .probe = oxfw_probe, + .update = oxfw_bus_reset, + .remove = oxfw_remove, + .id_table = oxfw_id_table, +}; + +static int __init snd_oxfw_init(void) +{ + return driver_register(&oxfw_driver.driver); +} + +static void __exit snd_oxfw_exit(void) +{ + driver_unregister(&oxfw_driver.driver); +} + +module_init(snd_oxfw_init); +module_exit(snd_oxfw_exit); diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h new file mode 100644 index 000000000..fa2d7f9e2 --- /dev/null +++ b/sound/firewire/oxfw/oxfw.h @@ -0,0 +1,142 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * oxfw.h - a part of driver for OXFW970/971 based devices + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + */ + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/compat.h> +#include <linux/sched/signal.h> + +#include <sound/control.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/info.h> +#include <sound/rawmidi.h> +#include <sound/firewire.h> +#include <sound/hwdep.h> + +#include "../lib.h" +#include "../fcp.h" +#include "../packets-buffer.h" +#include "../iso-resources.h" +#include "../amdtp-am824.h" +#include "../cmp.h" + +/* This is an arbitrary number for convinience. */ +#define SND_OXFW_STREAM_FORMAT_ENTRIES 10 +struct snd_oxfw { + struct snd_card *card; + struct fw_unit *unit; + struct mutex mutex; + spinlock_t lock; + + bool registered; + struct delayed_work dwork; + + bool wrong_dbs; + bool has_output; + bool has_input; + u8 *tx_stream_formats[SND_OXFW_STREAM_FORMAT_ENTRIES]; + u8 *rx_stream_formats[SND_OXFW_STREAM_FORMAT_ENTRIES]; + bool assumed; + struct cmp_connection out_conn; + struct cmp_connection in_conn; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + unsigned int substreams_count; + + unsigned int midi_input_ports; + unsigned int midi_output_ports; + + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; + + const struct ieee1394_device_id *entry; + void *spec; + + struct amdtp_domain domain; +}; + +/* + * AV/C Stream Format Information Specification 1.1 Working Draft + * (Apr 2005, 1394TA) + */ +int avc_stream_set_format(struct fw_unit *unit, enum avc_general_plug_dir dir, + unsigned int pid, u8 *format, unsigned int len); +int avc_stream_get_format(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len, unsigned int eid); +static inline int +avc_stream_get_format_single(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len) +{ + return avc_stream_get_format(unit, dir, pid, buf, len, 0xff); +} +static inline int +avc_stream_get_format_list(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len, + unsigned int eid) +{ + return avc_stream_get_format(unit, dir, pid, buf, len, eid); +} + +/* + * AV/C Digital Interface Command Set General Specification 4.2 + * (Sep 2004, 1394TA) + */ +int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate, + enum avc_general_plug_dir dir, + unsigned short pid); + +int snd_oxfw_stream_init_duplex(struct snd_oxfw *oxfw); +int snd_oxfw_stream_reserve_duplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream, + unsigned int rate, unsigned int pcm_channels, + unsigned int frames_per_period, + unsigned int frames_per_buffer); +int snd_oxfw_stream_start_duplex(struct snd_oxfw *oxfw); +void snd_oxfw_stream_stop_duplex(struct snd_oxfw *oxfw); +void snd_oxfw_stream_destroy_duplex(struct snd_oxfw *oxfw); +void snd_oxfw_stream_update_duplex(struct snd_oxfw *oxfw); + +struct snd_oxfw_stream_formation { + unsigned int rate; + unsigned int pcm; + unsigned int midi; +}; +int snd_oxfw_stream_parse_format(u8 *format, + struct snd_oxfw_stream_formation *formation); +int snd_oxfw_stream_get_current_formation(struct snd_oxfw *oxfw, + enum avc_general_plug_dir dir, + struct snd_oxfw_stream_formation *formation); + +int snd_oxfw_stream_discover(struct snd_oxfw *oxfw); + +void snd_oxfw_stream_lock_changed(struct snd_oxfw *oxfw); +int snd_oxfw_stream_lock_try(struct snd_oxfw *oxfw); +void snd_oxfw_stream_lock_release(struct snd_oxfw *oxfw); + +int snd_oxfw_create_pcm(struct snd_oxfw *oxfw); + +void snd_oxfw_proc_init(struct snd_oxfw *oxfw); + +int snd_oxfw_create_midi(struct snd_oxfw *oxfw); + +int snd_oxfw_create_hwdep(struct snd_oxfw *oxfw); + +int snd_oxfw_add_spkr(struct snd_oxfw *oxfw, bool is_lacie); +int snd_oxfw_scs1x_add(struct snd_oxfw *oxfw); +void snd_oxfw_scs1x_update(struct snd_oxfw *oxfw); diff --git a/sound/firewire/packets-buffer.c b/sound/firewire/packets-buffer.c new file mode 100644 index 000000000..0ecafd0c6 --- /dev/null +++ b/sound/firewire/packets-buffer.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * helpers for managing a buffer for many packets + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + */ + +#include <linux/firewire.h> +#include <linux/export.h> +#include <linux/slab.h> +#include "packets-buffer.h" + +/** + * iso_packets_buffer_init - allocates the memory for packets + * @b: the buffer structure to initialize + * @unit: the device at the other end of the stream + * @count: the number of packets + * @packet_size: the (maximum) size of a packet, in bytes + * @direction: %DMA_TO_DEVICE or %DMA_FROM_DEVICE + */ +int iso_packets_buffer_init(struct iso_packets_buffer *b, struct fw_unit *unit, + unsigned int count, unsigned int packet_size, + enum dma_data_direction direction) +{ + unsigned int packets_per_page, pages; + unsigned int i, page_index, offset_in_page; + void *p; + int err; + + b->packets = kmalloc_array(count, sizeof(*b->packets), GFP_KERNEL); + if (!b->packets) { + err = -ENOMEM; + goto error; + } + + packet_size = L1_CACHE_ALIGN(packet_size); + packets_per_page = PAGE_SIZE / packet_size; + if (WARN_ON(!packets_per_page)) { + err = -EINVAL; + goto err_packets; + } + pages = DIV_ROUND_UP(count, packets_per_page); + + err = fw_iso_buffer_init(&b->iso_buffer, fw_parent_device(unit)->card, + pages, direction); + if (err < 0) + goto err_packets; + + for (i = 0; i < count; ++i) { + page_index = i / packets_per_page; + p = page_address(b->iso_buffer.pages[page_index]); + offset_in_page = (i % packets_per_page) * packet_size; + b->packets[i].buffer = p + offset_in_page; + b->packets[i].offset = page_index * PAGE_SIZE + offset_in_page; + } + + return 0; + +err_packets: + kfree(b->packets); +error: + return err; +} +EXPORT_SYMBOL(iso_packets_buffer_init); + +/** + * iso_packets_buffer_destroy - frees packet buffer resources + * @b: the buffer structure to free + * @unit: the device at the other end of the stream + */ +void iso_packets_buffer_destroy(struct iso_packets_buffer *b, + struct fw_unit *unit) +{ + fw_iso_buffer_destroy(&b->iso_buffer, fw_parent_device(unit)->card); + kfree(b->packets); +} +EXPORT_SYMBOL(iso_packets_buffer_destroy); diff --git a/sound/firewire/packets-buffer.h b/sound/firewire/packets-buffer.h new file mode 100644 index 000000000..99e963c27 --- /dev/null +++ b/sound/firewire/packets-buffer.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef SOUND_FIREWIRE_PACKETS_BUFFER_H_INCLUDED +#define SOUND_FIREWIRE_PACKETS_BUFFER_H_INCLUDED + +#include <linux/dma-mapping.h> +#include <linux/firewire.h> + +/** + * struct iso_packets_buffer - manages a buffer for many packets + * @iso_buffer: the memory containing the packets + * @packets: an array, with each element pointing to one packet + */ +struct iso_packets_buffer { + struct fw_iso_buffer iso_buffer; + struct { + void *buffer; + unsigned int offset; + } *packets; +}; + +int iso_packets_buffer_init(struct iso_packets_buffer *b, struct fw_unit *unit, + unsigned int count, unsigned int packet_size, + enum dma_data_direction direction); +void iso_packets_buffer_destroy(struct iso_packets_buffer *b, + struct fw_unit *unit); + +#endif diff --git a/sound/firewire/tascam/Makefile b/sound/firewire/tascam/Makefile new file mode 100644 index 000000000..a1d21f244 --- /dev/null +++ b/sound/firewire/tascam/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +snd-firewire-tascam-objs := tascam-proc.o amdtp-tascam.o tascam-stream.o \ + tascam-pcm.o tascam-hwdep.o tascam-transaction.o \ + tascam-midi.o tascam.o +obj-$(CONFIG_SND_FIREWIRE_TASCAM) += snd-firewire-tascam.o diff --git a/sound/firewire/tascam/amdtp-tascam.c b/sound/firewire/tascam/amdtp-tascam.c new file mode 100644 index 000000000..f823a2ab3 --- /dev/null +++ b/sound/firewire/tascam/amdtp-tascam.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * amdtp-tascam.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + */ + +#include <sound/pcm.h> +#include "tascam.h" + +#define AMDTP_FMT_TSCM_TX 0x1e +#define AMDTP_FMT_TSCM_RX 0x3e + +struct amdtp_tscm { + unsigned int pcm_channels; +}; + +int amdtp_tscm_set_parameters(struct amdtp_stream *s, unsigned int rate) +{ + struct amdtp_tscm *p = s->protocol; + unsigned int data_channels; + + if (amdtp_stream_running(s)) + return -EBUSY; + + data_channels = p->pcm_channels; + + /* Packets in in-stream have extra 2 data channels. */ + if (s->direction == AMDTP_IN_STREAM) + data_channels += 2; + + return amdtp_stream_set_parameters(s, rate, data_channels); +} + +static void write_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames, + unsigned int pcm_frames) +{ + struct amdtp_tscm *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_be32(*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, + __be32 *buffer, unsigned int frames, + unsigned int pcm_frames) +{ + struct amdtp_tscm *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; + + /* The first data channel is for event counter. */ + buffer += 1; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + *dst = be32_to_cpu(buffer[c]); + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, __be32 *buffer, + unsigned int data_blocks) +{ + struct amdtp_tscm *p = s->protocol; + unsigned int channels, i, c; + + channels = p->pcm_channels; + + for (i = 0; i < data_blocks; ++i) { + for (c = 0; c < channels; ++c) + buffer[c] = 0x00000000; + buffer += s->data_block_quadlets; + } +} + +int amdtp_tscm_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + /* + * Our implementation allows this protocol to deliver 24 bit sample in + * 32bit data channel. + */ + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + return err; + + return amdtp_stream_add_pcm_hw_constraints(s, runtime); +} + +static void read_status_messages(struct amdtp_stream *s, + __be32 *buffer, unsigned int data_blocks) +{ + struct snd_tscm *tscm = container_of(s, struct snd_tscm, tx_stream); + bool used = READ_ONCE(tscm->hwdep->used); + int i; + + for (i = 0; i < data_blocks; i++) { + unsigned int index; + __be32 before; + __be32 after; + + index = be32_to_cpu(buffer[0]) % SNDRV_FIREWIRE_TASCAM_STATE_COUNT; + before = tscm->state[index]; + after = buffer[s->data_block_quadlets - 1]; + + if (used && index > 4 && index < 16) { + __be32 mask; + + if (index == 5) + mask = cpu_to_be32(~0x0000ffff); + else if (index == 6) + mask = cpu_to_be32(~0x0000ffff); + else if (index == 8) + mask = cpu_to_be32(~0x000f0f00); + else + mask = cpu_to_be32(~0x00000000); + + if ((before ^ after) & mask) { + struct snd_firewire_tascam_change *entry = + &tscm->queue[tscm->push_pos]; + unsigned long flag; + + spin_lock_irqsave(&tscm->lock, flag); + entry->index = index; + entry->before = before; + entry->after = after; + if (++tscm->push_pos >= SND_TSCM_QUEUE_COUNT) + tscm->push_pos = 0; + spin_unlock_irqrestore(&tscm->lock, flag); + + wake_up(&tscm->hwdep_wait); + } + } + + tscm->state[index] = after; + buffer += s->data_block_quadlets; + } +} + +static unsigned int process_ir_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) +{ + unsigned int pcm_frames = 0; + int i; + + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buf = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; + + if (pcm) { + read_pcm_s32(s, pcm, buf, data_blocks, pcm_frames); + pcm_frames += data_blocks; + } + + read_status_messages(s, buf, data_blocks); + } + + return pcm_frames; +} + +static unsigned int process_it_ctx_payloads(struct amdtp_stream *s, + const struct pkt_desc *descs, + unsigned int packets, + struct snd_pcm_substream *pcm) +{ + unsigned int pcm_frames = 0; + int i; + + for (i = 0; i < packets; ++i) { + const struct pkt_desc *desc = descs + i; + __be32 *buf = desc->ctx_payload; + unsigned int data_blocks = desc->data_blocks; + + if (pcm) { + write_pcm_s32(s, pcm, buf, data_blocks, pcm_frames); + pcm_frames += data_blocks; + } else { + write_pcm_silence(s, buf, data_blocks); + } + } + + return pcm_frames; +} + +int amdtp_tscm_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, unsigned int pcm_channels) +{ + amdtp_stream_process_ctx_payloads_t process_ctx_payloads; + struct amdtp_tscm *p; + unsigned int fmt; + int err; + + if (dir == AMDTP_IN_STREAM) { + fmt = AMDTP_FMT_TSCM_TX; + process_ctx_payloads = process_ir_ctx_payloads; + } else { + fmt = AMDTP_FMT_TSCM_RX; + process_ctx_payloads = process_it_ctx_payloads; + } + + err = amdtp_stream_init(s, unit, dir, + CIP_NONBLOCKING | CIP_SKIP_DBC_ZERO_CHECK, fmt, + process_ctx_payloads, sizeof(struct amdtp_tscm)); + if (err < 0) + return 0; + + if (dir == AMDTP_OUT_STREAM) { + // Use fixed value for FDF field. + s->ctx_data.rx.fdf = 0x00; + // Not used. + s->ctx_data.rx.syt_override = 0x0000; + } + + /* This protocol uses fixed number of data channels for PCM samples. */ + p = s->protocol; + p->pcm_channels = pcm_channels; + + return 0; +} diff --git a/sound/firewire/tascam/tascam-hwdep.c b/sound/firewire/tascam/tascam-hwdep.c new file mode 100644 index 000000000..6f38335fe --- /dev/null +++ b/sound/firewire/tascam/tascam-hwdep.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tascam-hwdep.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + */ + +/* + * This codes give three functionality. + * + * 1.get firewire node information + * 2.get notification about starting/stopping stream + * 3.lock/unlock stream + */ + +#include "tascam.h" + +static long tscm_hwdep_read_locked(struct snd_tscm *tscm, char __user *buf, + long count, loff_t *offset) + __releases(&tscm->lock) +{ + struct snd_firewire_event_lock_status event = { + .type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS, + }; + + event.status = (tscm->dev_lock_count > 0); + tscm->dev_lock_changed = false; + count = min_t(long, count, sizeof(event)); + + spin_unlock_irq(&tscm->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static long tscm_hwdep_read_queue(struct snd_tscm *tscm, char __user *buf, + long remained, loff_t *offset) + __releases(&tscm->lock) +{ + char __user *pos = buf; + unsigned int type = SNDRV_FIREWIRE_EVENT_TASCAM_CONTROL; + struct snd_firewire_tascam_change *entries = tscm->queue; + long count; + + // At least, one control event can be copied. + if (remained < sizeof(type) + sizeof(*entries)) { + spin_unlock_irq(&tscm->lock); + return -EINVAL; + } + + // Copy the type field later. + count = sizeof(type); + remained -= sizeof(type); + pos += sizeof(type); + + while (true) { + unsigned int head_pos; + unsigned int tail_pos; + unsigned int length; + + if (tscm->pull_pos == tscm->push_pos) + break; + else if (tscm->pull_pos < tscm->push_pos) + tail_pos = tscm->push_pos; + else + tail_pos = SND_TSCM_QUEUE_COUNT; + head_pos = tscm->pull_pos; + + length = (tail_pos - head_pos) * sizeof(*entries); + if (remained < length) + length = rounddown(remained, sizeof(*entries)); + if (length == 0) + break; + + spin_unlock_irq(&tscm->lock); + if (copy_to_user(pos, &entries[head_pos], length)) + return -EFAULT; + + spin_lock_irq(&tscm->lock); + + tscm->pull_pos = tail_pos % SND_TSCM_QUEUE_COUNT; + + count += length; + remained -= length; + pos += length; + } + + spin_unlock_irq(&tscm->lock); + + if (copy_to_user(buf, &type, sizeof(type))) + return -EFAULT; + + return count; +} + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_tscm *tscm = hwdep->private_data; + DEFINE_WAIT(wait); + + spin_lock_irq(&tscm->lock); + + while (!tscm->dev_lock_changed && tscm->push_pos == tscm->pull_pos) { + prepare_to_wait(&tscm->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&tscm->lock); + schedule(); + finish_wait(&tscm->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&tscm->lock); + } + + // NOTE: The acquired lock should be released in callee side. + if (tscm->dev_lock_changed) { + count = tscm_hwdep_read_locked(tscm, buf, count, offset); + } else if (tscm->push_pos != tscm->pull_pos) { + count = tscm_hwdep_read_queue(tscm, buf, count, offset); + } else { + spin_unlock_irq(&tscm->lock); + count = 0; + } + + return count; +} + +static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_tscm *tscm = hwdep->private_data; + __poll_t events; + + poll_wait(file, &tscm->hwdep_wait, wait); + + spin_lock_irq(&tscm->lock); + if (tscm->dev_lock_changed || tscm->push_pos != tscm->pull_pos) + events = EPOLLIN | EPOLLRDNORM; + else + events = 0; + spin_unlock_irq(&tscm->lock); + + return events; +} + +static int hwdep_get_info(struct snd_tscm *tscm, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(tscm->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_TASCAM; + 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]); + strlcpy(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_tscm *tscm) +{ + int err; + + spin_lock_irq(&tscm->lock); + + if (tscm->dev_lock_count == 0) { + tscm->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&tscm->lock); + + return err; +} + +static int hwdep_unlock(struct snd_tscm *tscm) +{ + int err; + + spin_lock_irq(&tscm->lock); + + if (tscm->dev_lock_count == -1) { + tscm->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&tscm->lock); + + return err; +} + +static int tscm_hwdep_state(struct snd_tscm *tscm, void __user *arg) +{ + if (copy_to_user(arg, tscm->state, sizeof(tscm->state))) + return -EFAULT; + + return 0; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_tscm *tscm = hwdep->private_data; + + spin_lock_irq(&tscm->lock); + if (tscm->dev_lock_count == -1) + tscm->dev_lock_count = 0; + spin_unlock_irq(&tscm->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_tscm *tscm = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(tscm, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(tscm); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(tscm); + case SNDRV_FIREWIRE_IOCTL_TASCAM_STATE: + return tscm_hwdep_state(tscm, (void __user *)arg); + 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_tscm_create_hwdep_device(struct snd_tscm *tscm) +{ + static const struct snd_hwdep_ops ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(tscm->card, "Tascam", 0, &hwdep); + if (err < 0) + return err; + + strcpy(hwdep->name, "Tascam"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_TASCAM; + hwdep->ops = ops; + hwdep->private_data = tscm; + hwdep->exclusive = true; + + tscm->hwdep = hwdep; + + return err; +} diff --git a/sound/firewire/tascam/tascam-midi.c b/sound/firewire/tascam/tascam-midi.c new file mode 100644 index 000000000..02eed2dce --- /dev/null +++ b/sound/firewire/tascam/tascam-midi.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tascam-midi.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + */ + +#include "tascam.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_tscm *tscm = substream->rmidi->private_data; + + snd_fw_async_midi_port_init(&tscm->out_ports[substream->number]); + + 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) +{ + return 0; +} + +static void midi_playback_drain(struct snd_rawmidi_substream *substream) +{ + struct snd_tscm *tscm = substream->rmidi->private_data; + + snd_fw_async_midi_port_finish(&tscm->out_ports[substream->number]); +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_tscm *tscm = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&tscm->lock, flags); + + if (up) + tscm->tx_midi_substreams[substrm->number] = substrm; + else + tscm->tx_midi_substreams[substrm->number] = NULL; + + spin_unlock_irqrestore(&tscm->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_tscm *tscm = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&tscm->lock, flags); + + if (up) + snd_fw_async_midi_port_run(&tscm->out_ports[substrm->number], + substrm); + + spin_unlock_irqrestore(&tscm->lock, flags); +} + +int snd_tscm_create_midi_devices(struct snd_tscm *tscm) +{ + static const struct snd_rawmidi_ops capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, + }; + static const struct snd_rawmidi_ops playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .drain = midi_playback_drain, + .trigger = midi_playback_trigger, + }; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *stream; + struct snd_rawmidi_substream *subs; + int err; + + err = snd_rawmidi_new(tscm->card, tscm->card->driver, 0, + tscm->spec->midi_playback_ports, + tscm->spec->midi_capture_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", tscm->card->shortname); + rmidi->private_data = tscm; + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &capture_ops); + stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + + /* Set port names for MIDI input. */ + list_for_each_entry(subs, &stream->substreams, list) { + /* TODO: support virtual MIDI ports. */ + if (subs->number < tscm->spec->midi_capture_ports) { + /* Hardware MIDI ports. */ + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", + tscm->card->shortname, subs->number + 1); + } + } + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &playback_ops); + stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + /* Set port names for MIDI ourput. */ + list_for_each_entry(subs, &stream->substreams, list) { + if (subs->number < tscm->spec->midi_playback_ports) { + /* Hardware MIDI ports only. */ + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", + tscm->card->shortname, subs->number + 1); + } + } + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/tascam/tascam-pcm.c b/sound/firewire/tascam/tascam-pcm.c new file mode 100644 index 000000000..36c1353f2 --- /dev/null +++ b/sound/firewire/tascam/tascam-pcm.c @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tascam-pcm.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + */ + +#include "tascam.h" + +static int pcm_init_hw_params(struct snd_tscm *tscm, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_pcm_hardware *hw = &runtime->hw; + struct amdtp_stream *stream; + unsigned int pcm_channels; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + stream = &tscm->tx_stream; + pcm_channels = tscm->spec->pcm_capture_analog_channels; + } else { + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + stream = &tscm->rx_stream; + pcm_channels = tscm->spec->pcm_playback_analog_channels; + } + + if (tscm->spec->has_adat) + pcm_channels += 8; + if (tscm->spec->has_spdif) + pcm_channels += 2; + runtime->hw.channels_min = runtime->hw.channels_max = pcm_channels; + + hw->rates = SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000; + snd_pcm_limit_hw_rates(runtime); + + return amdtp_tscm_add_pcm_hw_constraints(stream, runtime); +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + struct amdtp_domain *d = &tscm->domain; + enum snd_tscm_clock clock; + int err; + + err = snd_tscm_stream_lock_try(tscm); + if (err < 0) + return err; + + err = pcm_init_hw_params(tscm, substream); + if (err < 0) + goto err_locked; + + err = snd_tscm_stream_get_clock(tscm, &clock); + if (err < 0) + goto err_locked; + + mutex_lock(&tscm->mutex); + + // When source of clock is not internal or any stream is reserved for + // transmission of PCM frames, the available sampling rate is limited + // at current one. + if (clock != SND_TSCM_CLOCK_INTERNAL || tscm->substreams_counter > 0) { + unsigned int frames_per_period = d->events_per_period; + unsigned int frames_per_buffer = d->events_per_buffer; + unsigned int rate; + + err = snd_tscm_stream_get_rate(tscm, &rate); + if (err < 0) { + mutex_unlock(&tscm->mutex); + goto err_locked; + } + 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) { + mutex_unlock(&tscm->mutex); + goto err_locked; + } + + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + frames_per_buffer, frames_per_buffer); + if (err < 0) { + mutex_unlock(&tscm->mutex); + goto err_locked; + } + } + + mutex_unlock(&tscm->mutex); + + snd_pcm_set_sync(substream); + + return 0; +err_locked: + snd_tscm_stream_lock_release(tscm); + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + + snd_tscm_stream_lock_release(tscm); + + return 0; +} + +static int pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_tscm *tscm = substream->private_data; + int err = 0; + + if (substream->runtime->status->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); + + mutex_lock(&tscm->mutex); + err = snd_tscm_stream_reserve_duplex(tscm, rate, + frames_per_period, frames_per_buffer); + if (err >= 0) + ++tscm->substreams_counter; + mutex_unlock(&tscm->mutex); + } + + return err; +} + +static int pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + + mutex_lock(&tscm->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + --tscm->substreams_counter; + + snd_tscm_stream_stop_duplex(tscm); + + mutex_unlock(&tscm->mutex); + + return 0; +} + +static int pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&tscm->mutex); + + err = snd_tscm_stream_start_duplex(tscm, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&tscm->tx_stream); + + mutex_unlock(&tscm->mutex); + + return err; +} + +static int pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&tscm->mutex); + + err = snd_tscm_stream_start_duplex(tscm, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&tscm->rx_stream); + + mutex_unlock(&tscm->mutex); + + return err; +} + +static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_tscm *tscm = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&tscm->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&tscm->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_tscm *tscm = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&tscm->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&tscm->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_tscm *tscm = sbstrm->private_data; + + return amdtp_domain_stream_pcm_pointer(&tscm->domain, &tscm->tx_stream); +} + +static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_tscm *tscm = sbstrm->private_data; + + return amdtp_domain_stream_pcm_pointer(&tscm->domain, &tscm->rx_stream); +} + +static int pcm_capture_ack(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + + return amdtp_domain_stream_pcm_ack(&tscm->domain, &tscm->tx_stream); +} + +static int pcm_playback_ack(struct snd_pcm_substream *substream) +{ + struct snd_tscm *tscm = substream->private_data; + + return amdtp_domain_stream_pcm_ack(&tscm->domain, &tscm->rx_stream); +} + +int snd_tscm_create_pcm_devices(struct snd_tscm *tscm) +{ + static const struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .hw_params = pcm_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .ack = pcm_capture_ack, + }; + static const struct snd_pcm_ops playback_ops = { + .open = pcm_open, + .close = pcm_close, + .hw_params = pcm_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .ack = pcm_playback_ack, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(tscm->card, tscm->card->driver, 0, 1, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = tscm; + snprintf(pcm->name, sizeof(pcm->name), + "%s PCM", tscm->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); + snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0); + + return 0; +} diff --git a/sound/firewire/tascam/tascam-proc.c b/sound/firewire/tascam/tascam-proc.c new file mode 100644 index 000000000..53846aeff --- /dev/null +++ b/sound/firewire/tascam/tascam-proc.c @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tascam-proc.h - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + */ + +#include "./tascam.h" + +static void proc_read_firmware(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_tscm *tscm = entry->private_data; + __be32 data; + unsigned int reg, fpga, arm, hw; + int err; + + err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_FIRMWARE_REGISTER, + &data, sizeof(data), 0); + if (err < 0) + return; + reg = be32_to_cpu(data); + + err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_FIRMWARE_FPGA, + &data, sizeof(data), 0); + if (err < 0) + return; + fpga = be32_to_cpu(data); + + err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_FIRMWARE_ARM, + &data, sizeof(data), 0); + if (err < 0) + return; + arm = be32_to_cpu(data); + + err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_FIRMWARE_HW, + &data, sizeof(data), 0); + if (err < 0) + return; + hw = be32_to_cpu(data); + + snd_iprintf(buffer, "Register: %d (0x%08x)\n", reg & 0xffff, reg); + snd_iprintf(buffer, "FPGA: %d (0x%08x)\n", fpga & 0xffff, fpga); + snd_iprintf(buffer, "ARM: %d (0x%08x)\n", arm & 0xffff, arm); + snd_iprintf(buffer, "Hardware: %d (0x%08x)\n", hw >> 16, hw); +} + +static void add_node(struct snd_tscm *tscm, 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(tscm->card, name, root); + if (entry) + snd_info_set_text_ops(entry, tscm, op); +} + +void snd_tscm_proc_init(struct snd_tscm *tscm) +{ + 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(tscm->card, "firewire", + tscm->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | 0555; + + add_node(tscm, root, "firmware", proc_read_firmware); +} diff --git a/sound/firewire/tascam/tascam-stream.c b/sound/firewire/tascam/tascam-stream.c new file mode 100644 index 000000000..47de9727a --- /dev/null +++ b/sound/firewire/tascam/tascam-stream.c @@ -0,0 +1,545 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tascam-stream.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + */ + +#include <linux/delay.h> +#include "tascam.h" + +#define CLOCK_STATUS_MASK 0xffff0000 +#define CLOCK_CONFIG_MASK 0x0000ffff + +#define CALLBACK_TIMEOUT 500 + +static int get_clock(struct snd_tscm *tscm, u32 *data) +{ + int trial = 0; + __be32 reg; + int err; + + while (trial++ < 5) { + err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_CLOCK_STATUS, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + *data = be32_to_cpu(reg); + if (*data & CLOCK_STATUS_MASK) + break; + + // In intermediate state after changing clock status. + msleep(50); + } + + // Still in the intermediate state. + if (trial >= 5) + return -EAGAIN; + + return 0; +} + +static int set_clock(struct snd_tscm *tscm, unsigned int rate, + enum snd_tscm_clock clock) +{ + u32 data; + __be32 reg; + int err; + + err = get_clock(tscm, &data); + if (err < 0) + return err; + data &= CLOCK_CONFIG_MASK; + + if (rate > 0) { + data &= 0x000000ff; + /* Base rate. */ + if ((rate % 44100) == 0) { + data |= 0x00000100; + /* Multiplier. */ + if (rate / 44100 == 2) + data |= 0x00008000; + } else if ((rate % 48000) == 0) { + data |= 0x00000200; + /* Multiplier. */ + if (rate / 48000 == 2) + data |= 0x00008000; + } else { + return -EAGAIN; + } + } + + if (clock != INT_MAX) { + data &= 0x0000ff00; + data |= clock + 1; + } + + reg = cpu_to_be32(data); + + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_CLOCK_STATUS, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + if (data & 0x00008000) + reg = cpu_to_be32(0x0000001a); + else + reg = cpu_to_be32(0x0000000d); + + return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MULTIPLEX_MODE, + ®, sizeof(reg), 0); +} + +int snd_tscm_stream_get_rate(struct snd_tscm *tscm, unsigned int *rate) +{ + u32 data; + int err; + + err = get_clock(tscm, &data); + if (err < 0) + return err; + + data = (data & 0xff000000) >> 24; + + /* Check base rate. */ + if ((data & 0x0f) == 0x01) + *rate = 44100; + else if ((data & 0x0f) == 0x02) + *rate = 48000; + else + return -EAGAIN; + + /* Check multiplier. */ + if ((data & 0xf0) == 0x80) + *rate *= 2; + else if ((data & 0xf0) != 0x00) + return -EAGAIN; + + return err; +} + +int snd_tscm_stream_get_clock(struct snd_tscm *tscm, enum snd_tscm_clock *clock) +{ + u32 data; + int err; + + err = get_clock(tscm, &data); + if (err < 0) + return err; + + *clock = ((data & 0x00ff0000) >> 16) - 1; + if (*clock < 0 || *clock > SND_TSCM_CLOCK_ADAT) + return -EIO; + + return 0; +} + +static int enable_data_channels(struct snd_tscm *tscm) +{ + __be32 reg; + u32 data; + unsigned int i; + int err; + + data = 0; + for (i = 0; i < tscm->spec->pcm_capture_analog_channels; ++i) + data |= BIT(i); + if (tscm->spec->has_adat) + data |= 0x0000ff00; + if (tscm->spec->has_spdif) + data |= 0x00030000; + + reg = cpu_to_be32(data); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_TX_PCM_CHANNELS, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + data = 0; + for (i = 0; i < tscm->spec->pcm_playback_analog_channels; ++i) + data |= BIT(i); + if (tscm->spec->has_adat) + data |= 0x0000ff00; + if (tscm->spec->has_spdif) + data |= 0x00030000; + + reg = cpu_to_be32(data); + return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_RX_PCM_CHANNELS, + ®, sizeof(reg), 0); +} + +static int set_stream_formats(struct snd_tscm *tscm, unsigned int rate) +{ + __be32 reg; + int err; + + // Set an option for unknown purpose. + reg = cpu_to_be32(0x00200000); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_SET_OPTION, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + return enable_data_channels(tscm); +} + +static void finish_session(struct snd_tscm *tscm) +{ + __be32 reg; + + reg = 0; + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_START_STREAMING, + ®, sizeof(reg), 0); + + reg = 0; + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_ON, + ®, sizeof(reg), 0); + + // Unregister channels. + reg = cpu_to_be32(0x00000000); + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_CH, + ®, sizeof(reg), 0); + reg = cpu_to_be32(0x00000000); + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_UNKNOWN, + ®, sizeof(reg), 0); + reg = cpu_to_be32(0x00000000); + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_CH, + ®, sizeof(reg), 0); +} + +static int begin_session(struct snd_tscm *tscm) +{ + __be32 reg; + int err; + + // Register the isochronous channel for transmitting stream. + reg = cpu_to_be32(tscm->tx_resources.channel); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_CH, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + // Unknown. + reg = cpu_to_be32(0x00000002); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_UNKNOWN, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + // Register the isochronous channel for receiving stream. + reg = cpu_to_be32(tscm->rx_resources.channel); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_CH, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + reg = cpu_to_be32(0x00000001); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_START_STREAMING, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + reg = cpu_to_be32(0x00000001); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_ON, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + // Set an option for unknown purpose. + reg = cpu_to_be32(0x00002000); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_SET_OPTION, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + // Start multiplexing PCM samples on packets. + reg = cpu_to_be32(0x00000001); + return snd_fw_transaction(tscm->unit, + TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_ON, + ®, sizeof(reg), 0); +} + +static int keep_resources(struct snd_tscm *tscm, unsigned int rate, + struct amdtp_stream *stream) +{ + struct fw_iso_resources *resources; + int err; + + if (stream == &tscm->tx_stream) + resources = &tscm->tx_resources; + else + resources = &tscm->rx_resources; + + err = amdtp_tscm_set_parameters(stream, rate); + if (err < 0) + return err; + + return fw_iso_resources_allocate(resources, + amdtp_stream_get_max_payload(stream), + fw_parent_device(tscm->unit)->max_speed); +} + +static int init_stream(struct snd_tscm *tscm, struct amdtp_stream *s) +{ + struct fw_iso_resources *resources; + enum amdtp_stream_direction dir; + unsigned int pcm_channels; + int err; + + if (s == &tscm->tx_stream) { + resources = &tscm->tx_resources; + dir = AMDTP_IN_STREAM; + pcm_channels = tscm->spec->pcm_capture_analog_channels; + } else { + resources = &tscm->rx_resources; + dir = AMDTP_OUT_STREAM; + pcm_channels = tscm->spec->pcm_playback_analog_channels; + } + + if (tscm->spec->has_adat) + pcm_channels += 8; + if (tscm->spec->has_spdif) + pcm_channels += 2; + + err = fw_iso_resources_init(resources, tscm->unit); + if (err < 0) + return err; + + err = amdtp_tscm_init(s, tscm->unit, dir, pcm_channels); + if (err < 0) + fw_iso_resources_free(resources); + + return err; +} + +static void destroy_stream(struct snd_tscm *tscm, struct amdtp_stream *s) +{ + amdtp_stream_destroy(s); + + if (s == &tscm->tx_stream) + fw_iso_resources_destroy(&tscm->tx_resources); + else + fw_iso_resources_destroy(&tscm->rx_resources); +} + +int snd_tscm_stream_init_duplex(struct snd_tscm *tscm) +{ + int err; + + err = init_stream(tscm, &tscm->tx_stream); + if (err < 0) + return err; + + err = init_stream(tscm, &tscm->rx_stream); + if (err < 0) { + destroy_stream(tscm, &tscm->tx_stream); + return err; + } + + err = amdtp_domain_init(&tscm->domain); + if (err < 0) { + destroy_stream(tscm, &tscm->tx_stream); + destroy_stream(tscm, &tscm->rx_stream); + } + + return err; +} + +// At bus reset, streaming is stopped and some registers are clear. +void snd_tscm_stream_update_duplex(struct snd_tscm *tscm) +{ + amdtp_domain_stop(&tscm->domain); + + amdtp_stream_pcm_abort(&tscm->tx_stream); + amdtp_stream_pcm_abort(&tscm->rx_stream); +} + +// This function should be called before starting streams or after stopping +// streams. +void snd_tscm_stream_destroy_duplex(struct snd_tscm *tscm) +{ + amdtp_domain_destroy(&tscm->domain); + + destroy_stream(tscm, &tscm->rx_stream); + destroy_stream(tscm, &tscm->tx_stream); +} + +int snd_tscm_stream_reserve_duplex(struct snd_tscm *tscm, unsigned int rate, + unsigned int frames_per_period, + unsigned int frames_per_buffer) +{ + unsigned int curr_rate; + int err; + + err = snd_tscm_stream_get_rate(tscm, &curr_rate); + if (err < 0) + return err; + + if (tscm->substreams_counter == 0 || rate != curr_rate) { + amdtp_domain_stop(&tscm->domain); + + finish_session(tscm); + + fw_iso_resources_free(&tscm->tx_resources); + fw_iso_resources_free(&tscm->rx_resources); + + err = set_clock(tscm, rate, INT_MAX); + if (err < 0) + return err; + + err = keep_resources(tscm, rate, &tscm->tx_stream); + if (err < 0) + return err; + + err = keep_resources(tscm, rate, &tscm->rx_stream); + if (err < 0) { + fw_iso_resources_free(&tscm->tx_resources); + return err; + } + + err = amdtp_domain_set_events_per_period(&tscm->domain, + frames_per_period, frames_per_buffer); + if (err < 0) { + fw_iso_resources_free(&tscm->tx_resources); + fw_iso_resources_free(&tscm->rx_resources); + return err; + } + } + + return 0; +} + +int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate) +{ + unsigned int generation = tscm->rx_resources.generation; + int err; + + if (tscm->substreams_counter == 0) + return 0; + + if (amdtp_streaming_error(&tscm->rx_stream) || + amdtp_streaming_error(&tscm->tx_stream)) { + amdtp_domain_stop(&tscm->domain); + finish_session(tscm); + } + + if (generation != fw_parent_device(tscm->unit)->card->generation) { + err = fw_iso_resources_update(&tscm->tx_resources); + if (err < 0) + goto error; + + err = fw_iso_resources_update(&tscm->rx_resources); + if (err < 0) + goto error; + } + + if (!amdtp_stream_running(&tscm->rx_stream)) { + int spd = fw_parent_device(tscm->unit)->max_speed; + + err = set_stream_formats(tscm, rate); + if (err < 0) + goto error; + + err = begin_session(tscm); + if (err < 0) + goto error; + + err = amdtp_domain_add_stream(&tscm->domain, &tscm->rx_stream, + tscm->rx_resources.channel, spd); + if (err < 0) + goto error; + + err = amdtp_domain_add_stream(&tscm->domain, &tscm->tx_stream, + tscm->tx_resources.channel, spd); + if (err < 0) + goto error; + + err = amdtp_domain_start(&tscm->domain, 0); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&tscm->rx_stream, + CALLBACK_TIMEOUT) || + !amdtp_stream_wait_callback(&tscm->tx_stream, + CALLBACK_TIMEOUT)) { + err = -ETIMEDOUT; + goto error; + } + } + + return 0; +error: + amdtp_domain_stop(&tscm->domain); + finish_session(tscm); + + return err; +} + +void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm) +{ + if (tscm->substreams_counter == 0) { + amdtp_domain_stop(&tscm->domain); + finish_session(tscm); + + fw_iso_resources_free(&tscm->tx_resources); + fw_iso_resources_free(&tscm->rx_resources); + } +} + +void snd_tscm_stream_lock_changed(struct snd_tscm *tscm) +{ + tscm->dev_lock_changed = true; + wake_up(&tscm->hwdep_wait); +} + +int snd_tscm_stream_lock_try(struct snd_tscm *tscm) +{ + int err; + + spin_lock_irq(&tscm->lock); + + /* user land lock this */ + if (tscm->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (tscm->dev_lock_count++ == 0) + snd_tscm_stream_lock_changed(tscm); + err = 0; +end: + spin_unlock_irq(&tscm->lock); + return err; +} + +void snd_tscm_stream_lock_release(struct snd_tscm *tscm) +{ + spin_lock_irq(&tscm->lock); + + if (WARN_ON(tscm->dev_lock_count <= 0)) + goto end; + if (--tscm->dev_lock_count == 0) + snd_tscm_stream_lock_changed(tscm); +end: + spin_unlock_irq(&tscm->lock); +} diff --git a/sound/firewire/tascam/tascam-transaction.c b/sound/firewire/tascam/tascam-transaction.c new file mode 100644 index 000000000..a073cece4 --- /dev/null +++ b/sound/firewire/tascam/tascam-transaction.c @@ -0,0 +1,399 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tascam-transaction.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + */ + +#include "tascam.h" + +/* + * 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 fill_message(struct snd_fw_async_midi_port *port, + struct snd_rawmidi_substream *substream) +{ + int i, len, consume; + u8 *label, *msg; + u8 status; + + /* The first byte is used for label, the rest for MIDI bytes. */ + label = port->buf; + msg = port->buf + 1; + + consume = snd_rawmidi_transmit_peek(substream, msg, 3); + if (consume == 0) + return 0; + + /* On exclusive message. */ + if (port->on_sysex) { + /* Seek the end of exclusives. */ + for (i = 0; i < consume; ++i) { + if (msg[i] == 0xf7) { + port->on_sysex = false; + break; + } + } + + /* At the end of exclusive message, use label 0x07. */ + if (!port->on_sysex) { + consume = i + 1; + *label = (substream->number << 4) | 0x07; + /* During exclusive message, use label 0x04. */ + } else if (consume == 3) { + *label = (substream->number << 4) | 0x04; + /* We need to fill whole 3 bytes. Go to next change. */ + } else { + return 0; + } + + len = consume; + } else { + /* The beginning of exclusives. */ + if (msg[0] == 0xf0) { + /* Transfer it in next chance in another condition. */ + port->on_sysex = true; + return 0; + } else { + /* On running-status. */ + if ((msg[0] & 0x80) != 0x80) + status = port->running_status; + else + status = msg[0]; + + /* Calculate consume bytes. */ + len = calculate_message_bytes(status); + if (len <= 0) + return 0; + + /* On running-status. */ + if ((msg[0] & 0x80) != 0x80) { + /* Enough MIDI bytes were not retrieved. */ + if (consume < len - 1) + return 0; + consume = len - 1; + + msg[2] = msg[1]; + msg[1] = msg[0]; + msg[0] = port->running_status; + } else { + /* Enough MIDI bytes were not retrieved. */ + if (consume < len) + return 0; + consume = len; + + port->running_status = msg[0]; + } + } + + *label = (substream->number << 4) | (msg[0] >> 4); + } + + if (len > 0 && len < 3) + memset(msg + len, 0, 3 - len); + + return consume; +} + +static void async_midi_port_callback(struct fw_card *card, int rcode, + void *data, size_t length, + void *callback_data) +{ + struct snd_fw_async_midi_port *port = callback_data; + struct snd_rawmidi_substream *substream = READ_ONCE(port->substream); + + /* This port is closed. */ + if (substream == NULL) + return; + + if (rcode == RCODE_COMPLETE) + snd_rawmidi_transmit_ack(substream, port->consume_bytes); + else if (!rcode_is_permanent_error(rcode)) + /* To start next transaction immediately for recovery. */ + port->next_ktime = 0; + else + /* Don't continue processing. */ + port->error = true; + + port->idling = true; + + if (!snd_rawmidi_transmit_empty(substream)) + schedule_work(&port->work); +} + +static void midi_port_work(struct work_struct *work) +{ + struct snd_fw_async_midi_port *port = + container_of(work, struct snd_fw_async_midi_port, work); + struct snd_rawmidi_substream *substream = READ_ONCE(port->substream); + int generation; + + /* Under transacting or error state. */ + if (!port->idling || port->error) + return; + + /* Nothing to do. */ + if (substream == NULL || snd_rawmidi_transmit_empty(substream)) + return; + + /* Do it in next chance. */ + if (ktime_after(port->next_ktime, ktime_get())) { + schedule_work(&port->work); + return; + } + + /* + * Fill the buffer. The callee must use snd_rawmidi_transmit_peek(). + * Later, snd_rawmidi_transmit_ack() is called. + */ + memset(port->buf, 0, 4); + port->consume_bytes = fill_message(port, substream); + if (port->consume_bytes <= 0) { + /* Do it in next chance, immediately. */ + if (port->consume_bytes == 0) { + port->next_ktime = 0; + schedule_work(&port->work); + } else { + /* Fatal error. */ + port->error = true; + } + return; + } + + /* Set interval to next transaction. */ + port->next_ktime = ktime_add_ns(ktime_get(), + port->consume_bytes * 8 * (NSEC_PER_SEC / 31250)); + + /* Start this transaction. */ + port->idling = false; + + /* + * 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 = port->parent->generation; + smp_rmb(); + + fw_send_request(port->parent->card, &port->transaction, + TCODE_WRITE_QUADLET_REQUEST, + port->parent->node_id, generation, + port->parent->max_speed, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_RX_QUAD, + port->buf, 4, async_midi_port_callback, + port); +} + +void snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port) +{ + port->idling = true; + port->error = false; + port->running_status = 0; + port->on_sysex = false; +} + +static void handle_midi_tx(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_tscm *tscm = callback_data; + u32 *buf = (u32 *)data; + unsigned int messages; + unsigned int i; + unsigned int port; + struct snd_rawmidi_substream *substream; + u8 *b; + int bytes; + + if (offset != tscm->async_handler.offset) + goto end; + + messages = length / 8; + for (i = 0; i < messages; i++) { + b = (u8 *)(buf + i * 2); + + port = b[0] >> 4; + /* TODO: support virtual MIDI ports. */ + if (port >= tscm->spec->midi_capture_ports) + goto end; + + /* Assume the message length. */ + bytes = calculate_message_bytes(b[1]); + /* On MIDI data or exclusives. */ + if (bytes <= 0) { + /* Seek the end of exclusives. */ + for (bytes = 1; bytes < 4; bytes++) { + if (b[bytes] == 0xf7) + break; + } + if (bytes == 4) + bytes = 3; + } + + substream = READ_ONCE(tscm->tx_midi_substreams[port]); + if (substream != NULL) + snd_rawmidi_receive(substream, b + 1, bytes); + } +end: + fw_send_response(card, request, RCODE_COMPLETE); +} + +int snd_tscm_transaction_register(struct snd_tscm *tscm) +{ + static const struct fw_address_region resp_register_region = { + .start = 0xffffe0000000ull, + .end = 0xffffe000ffffull, + }; + unsigned int i; + int err; + + /* + * Usually, two quadlets are transferred by one transaction. The first + * quadlet has MIDI messages, the rest includes timestamp. + * Sometimes, 8 set of the data is transferred by a block transaction. + */ + tscm->async_handler.length = 8 * 8; + tscm->async_handler.address_callback = handle_midi_tx; + tscm->async_handler.callback_data = tscm; + + err = fw_core_add_address_handler(&tscm->async_handler, + &resp_register_region); + if (err < 0) + return err; + + err = snd_tscm_transaction_reregister(tscm); + if (err < 0) + goto error; + + for (i = 0; i < TSCM_MIDI_OUT_PORT_MAX; i++) { + tscm->out_ports[i].parent = fw_parent_device(tscm->unit); + tscm->out_ports[i].next_ktime = 0; + INIT_WORK(&tscm->out_ports[i].work, midi_port_work); + } + + return err; +error: + fw_core_remove_address_handler(&tscm->async_handler); + tscm->async_handler.callback_data = NULL; + return err; +} + +/* At bus reset, these registers are cleared. */ +int snd_tscm_transaction_reregister(struct snd_tscm *tscm) +{ + struct fw_device *device = fw_parent_device(tscm->unit); + __be32 reg; + int err; + + /* Register messaging address. Block transaction is not allowed. */ + reg = cpu_to_be32((device->card->node_id << 16) | + (tscm->async_handler.offset >> 32)); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_HI, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + reg = cpu_to_be32(tscm->async_handler.offset); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_LO, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* Turn on messaging. */ + reg = cpu_to_be32(0x00000001); + err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ON, + ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* Turn on FireWire LED. */ + reg = cpu_to_be32(0x0001008e); + return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_LED_POWER, + ®, sizeof(reg), 0); +} + +void snd_tscm_transaction_unregister(struct snd_tscm *tscm) +{ + __be32 reg; + + if (tscm->async_handler.callback_data == NULL) + return; + + /* Turn off FireWire LED. */ + reg = cpu_to_be32(0x0000008e); + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_LED_POWER, + ®, sizeof(reg), 0); + + /* Turn off messaging. */ + reg = cpu_to_be32(0x00000000); + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ON, + ®, sizeof(reg), 0); + + /* Unregister the address. */ + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_HI, + ®, sizeof(reg), 0); + snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, + TSCM_ADDR_BASE + TSCM_OFFSET_MIDI_TX_ADDR_LO, + ®, sizeof(reg), 0); + + fw_core_remove_address_handler(&tscm->async_handler); + tscm->async_handler.callback_data = NULL; +} diff --git a/sound/firewire/tascam/tascam.c b/sound/firewire/tascam/tascam.c new file mode 100644 index 000000000..75f2edd8e --- /dev/null +++ b/sound/firewire/tascam/tascam.c @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * tascam.c - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + */ + +#include "tascam.h" + +MODULE_DESCRIPTION("TASCAM FireWire series Driver"); +MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>"); +MODULE_LICENSE("GPL v2"); + +static const struct snd_tscm_spec model_specs[] = { + { + .name = "FW-1884", + .has_adat = true, + .has_spdif = true, + .pcm_capture_analog_channels = 8, + .pcm_playback_analog_channels = 8, + .midi_capture_ports = 4, + .midi_playback_ports = 4, + }, + { + .name = "FW-1082", + .has_adat = false, + .has_spdif = true, + .pcm_capture_analog_channels = 8, + .pcm_playback_analog_channels = 2, + .midi_capture_ports = 2, + .midi_playback_ports = 2, + }, + { + .name = "FW-1804", + .has_adat = true, + .has_spdif = true, + .pcm_capture_analog_channels = 8, + .pcm_playback_analog_channels = 2, + .midi_capture_ports = 2, + .midi_playback_ports = 4, + }, +}; + +static int identify_model(struct snd_tscm *tscm) +{ + struct fw_device *fw_dev = fw_parent_device(tscm->unit); + const u32 *config_rom = fw_dev->config_rom; + char model[9]; + unsigned int i; + u8 c; + + if (fw_dev->config_rom_length < 30) { + dev_err(&tscm->unit->device, + "Configuration ROM is too short.\n"); + return -ENODEV; + } + + /* Pick up model name from certain addresses. */ + for (i = 0; i < 8; i++) { + c = config_rom[28 + i / 4] >> (24 - 8 * (i % 4)); + if (c == '\0') + break; + model[i] = c; + } + model[i] = '\0'; + + for (i = 0; i < ARRAY_SIZE(model_specs); i++) { + if (strcmp(model, model_specs[i].name) == 0) { + tscm->spec = &model_specs[i]; + break; + } + } + if (tscm->spec == NULL) + return -ENODEV; + + strcpy(tscm->card->driver, "FW-TASCAM"); + strcpy(tscm->card->shortname, model); + strcpy(tscm->card->mixername, model); + snprintf(tscm->card->longname, sizeof(tscm->card->longname), + "TASCAM %s, GUID %08x%08x at %s, S%d", model, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&tscm->unit->device), 100 << fw_dev->max_speed); + + return 0; +} + +static void tscm_card_free(struct snd_card *card) +{ + struct snd_tscm *tscm = card->private_data; + + snd_tscm_transaction_unregister(tscm); + snd_tscm_stream_destroy_duplex(tscm); +} + +static void do_registration(struct work_struct *work) +{ + struct snd_tscm *tscm = container_of(work, struct snd_tscm, dwork.work); + int err; + + err = snd_card_new(&tscm->unit->device, -1, NULL, THIS_MODULE, 0, + &tscm->card); + if (err < 0) + return; + tscm->card->private_free = tscm_card_free; + tscm->card->private_data = tscm; + + err = identify_model(tscm); + if (err < 0) + goto error; + + err = snd_tscm_transaction_register(tscm); + if (err < 0) + goto error; + + err = snd_tscm_stream_init_duplex(tscm); + if (err < 0) + goto error; + + snd_tscm_proc_init(tscm); + + err = snd_tscm_create_pcm_devices(tscm); + if (err < 0) + goto error; + + err = snd_tscm_create_midi_devices(tscm); + if (err < 0) + goto error; + + err = snd_tscm_create_hwdep_device(tscm); + if (err < 0) + goto error; + + err = snd_card_register(tscm->card); + if (err < 0) + goto error; + + tscm->registered = true; + + return; +error: + snd_card_free(tscm->card); + dev_info(&tscm->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int snd_tscm_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_tscm *tscm; + + /* Allocate this independent of sound card instance. */ + tscm = devm_kzalloc(&unit->device, sizeof(struct snd_tscm), GFP_KERNEL); + if (!tscm) + return -ENOMEM; + tscm->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, tscm); + + mutex_init(&tscm->mutex); + spin_lock_init(&tscm->lock); + init_waitqueue_head(&tscm->hwdep_wait); + + /* Allocate and register this sound card later. */ + INIT_DEFERRABLE_WORK(&tscm->dwork, do_registration); + snd_fw_schedule_registration(unit, &tscm->dwork); + + return 0; +} + +static void snd_tscm_update(struct fw_unit *unit) +{ + struct snd_tscm *tscm = dev_get_drvdata(&unit->device); + + /* Postpone a workqueue for deferred registration. */ + if (!tscm->registered) + snd_fw_schedule_registration(unit, &tscm->dwork); + + snd_tscm_transaction_reregister(tscm); + + /* + * After registration, userspace can start packet streaming, then this + * code block works fine. + */ + if (tscm->registered) { + mutex_lock(&tscm->mutex); + snd_tscm_stream_update_duplex(tscm); + mutex_unlock(&tscm->mutex); + } +} + +static void snd_tscm_remove(struct fw_unit *unit) +{ + struct snd_tscm *tscm = dev_get_drvdata(&unit->device); + + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_delayed_work_sync(&tscm->dwork); + + if (tscm->registered) { + // Block till all of ALSA character devices are released. + snd_card_free(tscm->card); + } + + mutex_destroy(&tscm->mutex); + fw_unit_put(tscm->unit); +} + +static const struct ieee1394_device_id snd_tscm_id_table[] = { + // Tascam, FW-1884. + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = 0x00022e, + .specifier_id = 0x00022e, + .version = 0x800000, + }, + // Tascam, FE-8 (.version = 0x800001) + // This kernel module doesn't support FE-8 because the most of features + // can be implemented in userspace without any specific support of this + // module. + // + // .version = 0x800002 is unknown. + // + // Tascam, FW-1082. + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = 0x00022e, + .specifier_id = 0x00022e, + .version = 0x800003, + }, + // Tascam, FW-1804. + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = 0x00022e, + .specifier_id = 0x00022e, + .version = 0x800004, + }, + {} +}; +MODULE_DEVICE_TABLE(ieee1394, snd_tscm_id_table); + +static struct fw_driver tscm_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + }, + .probe = snd_tscm_probe, + .update = snd_tscm_update, + .remove = snd_tscm_remove, + .id_table = snd_tscm_id_table, +}; + +static int __init snd_tscm_init(void) +{ + return driver_register(&tscm_driver.driver); +} + +static void __exit snd_tscm_exit(void) +{ + driver_unregister(&tscm_driver.driver); +} + +module_init(snd_tscm_init); +module_exit(snd_tscm_exit); diff --git a/sound/firewire/tascam/tascam.h b/sound/firewire/tascam/tascam.h new file mode 100644 index 000000000..78b7a0898 --- /dev/null +++ b/sound/firewire/tascam/tascam.h @@ -0,0 +1,213 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * tascam.h - a part of driver for TASCAM FireWire series + * + * Copyright (c) 2015 Takashi Sakamoto + */ + +#ifndef SOUND_TASCAM_H_INCLUDED +#define SOUND_TASCAM_H_INCLUDED + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/compat.h> +#include <linux/sched/signal.h> + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/info.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/firewire.h> +#include <sound/hwdep.h> +#include <sound/rawmidi.h> + +#include "../lib.h" +#include "../amdtp-stream.h" +#include "../iso-resources.h" + +struct snd_tscm_spec { + const char *const name; + bool has_adat; + bool has_spdif; + unsigned int pcm_capture_analog_channels; + unsigned int pcm_playback_analog_channels; + unsigned int midi_capture_ports; + unsigned int midi_playback_ports; +}; + +#define TSCM_MIDI_IN_PORT_MAX 4 +#define TSCM_MIDI_OUT_PORT_MAX 4 + +struct snd_fw_async_midi_port { + struct fw_device *parent; + struct work_struct work; + bool idling; + ktime_t next_ktime; + bool error; + + struct fw_transaction transaction; + + u8 buf[4]; + u8 running_status; + bool on_sysex; + + struct snd_rawmidi_substream *substream; + int consume_bytes; +}; + +#define SND_TSCM_QUEUE_COUNT 16 + +struct snd_tscm { + struct snd_card *card; + struct fw_unit *unit; + + struct mutex mutex; + spinlock_t lock; + + bool registered; + struct delayed_work dwork; + const struct snd_tscm_spec *spec; + + struct fw_iso_resources tx_resources; + struct fw_iso_resources rx_resources; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + unsigned int substreams_counter; + + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; + + /* For MIDI message incoming transactions. */ + struct fw_address_handler async_handler; + struct snd_rawmidi_substream *tx_midi_substreams[TSCM_MIDI_IN_PORT_MAX]; + + /* For MIDI message outgoing transactions. */ + struct snd_fw_async_midi_port out_ports[TSCM_MIDI_OUT_PORT_MAX]; + + // A cache of status information in tx isoc packets. + __be32 state[SNDRV_FIREWIRE_TASCAM_STATE_COUNT]; + struct snd_hwdep *hwdep; + struct snd_firewire_tascam_change queue[SND_TSCM_QUEUE_COUNT]; + unsigned int pull_pos; + unsigned int push_pos; + + struct amdtp_domain domain; +}; + +#define TSCM_ADDR_BASE 0xffff00000000ull + +#define TSCM_OFFSET_FIRMWARE_REGISTER 0x0000 +#define TSCM_OFFSET_FIRMWARE_FPGA 0x0004 +#define TSCM_OFFSET_FIRMWARE_ARM 0x0008 +#define TSCM_OFFSET_FIRMWARE_HW 0x000c + +#define TSCM_OFFSET_ISOC_TX_CH 0x0200 +#define TSCM_OFFSET_UNKNOWN 0x0204 +#define TSCM_OFFSET_START_STREAMING 0x0208 +#define TSCM_OFFSET_ISOC_RX_CH 0x020c +#define TSCM_OFFSET_ISOC_RX_ON 0x0210 /* Little conviction. */ +#define TSCM_OFFSET_TX_PCM_CHANNELS 0x0214 +#define TSCM_OFFSET_RX_PCM_CHANNELS 0x0218 +#define TSCM_OFFSET_MULTIPLEX_MODE 0x021c +#define TSCM_OFFSET_ISOC_TX_ON 0x0220 +/* Unknown 0x0224 */ +#define TSCM_OFFSET_CLOCK_STATUS 0x0228 +#define TSCM_OFFSET_SET_OPTION 0x022c + +#define TSCM_OFFSET_MIDI_TX_ON 0x0300 +#define TSCM_OFFSET_MIDI_TX_ADDR_HI 0x0304 +#define TSCM_OFFSET_MIDI_TX_ADDR_LO 0x0308 + +#define TSCM_OFFSET_LED_POWER 0x0404 + +#define TSCM_OFFSET_MIDI_RX_QUAD 0x4000 + +// Although FE-8 supports the above registers, it has no I/O interfaces for +// audio samples and music messages. Otherwise it supports another notification +// for status and control message as well as LED brightening. The message +// consists of quadlet-aligned data up to 32 quadlets. The first byte of message +// is fixed to 0x40. The second byte is between 0x00 to 0x1f and represent each +// control: +// fader: 0x00-0x07 +// button: 0x0d, 0x0e +// knob: 0x14-0x1b +// sensing: 0x0b +// +// The rest two bytes represent state of the controls; e.g. current value for +// fader and knob, bitmasks for button and sensing. +// Just after turning on, 32 quadlets messages with 0x00-0x1f are immediately +// sent in one transaction. After, several quadlets are sent in one transaction. +// +// TSCM_OFFSET_FE8_CTL_TX_ON 0x0310 +// TSCM_OFFSET_FE8_CTL_TX_ADDR_HI 0x0314 +// TSCM_OFFSET_FE8_CTL_TX_ADDR_LO 0x0318 + +enum snd_tscm_clock { + SND_TSCM_CLOCK_INTERNAL = 0, + SND_TSCM_CLOCK_WORD = 1, + SND_TSCM_CLOCK_SPDIF = 2, + SND_TSCM_CLOCK_ADAT = 3, +}; + +int amdtp_tscm_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir, unsigned int pcm_channels); +int amdtp_tscm_set_parameters(struct amdtp_stream *s, unsigned int rate); +int amdtp_tscm_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); + +int snd_tscm_stream_get_rate(struct snd_tscm *tscm, unsigned int *rate); +int snd_tscm_stream_get_clock(struct snd_tscm *tscm, + enum snd_tscm_clock *clock); +int snd_tscm_stream_init_duplex(struct snd_tscm *tscm); +void snd_tscm_stream_update_duplex(struct snd_tscm *tscm); +void snd_tscm_stream_destroy_duplex(struct snd_tscm *tscm); +int snd_tscm_stream_reserve_duplex(struct snd_tscm *tscm, unsigned int rate, + unsigned int frames_per_period, + unsigned int frames_per_buffer); +int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate); +void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm); + +void snd_tscm_stream_lock_changed(struct snd_tscm *tscm); +int snd_tscm_stream_lock_try(struct snd_tscm *tscm); +void snd_tscm_stream_lock_release(struct snd_tscm *tscm); + +void snd_fw_async_midi_port_init(struct snd_fw_async_midi_port *port); + +static inline void +snd_fw_async_midi_port_run(struct snd_fw_async_midi_port *port, + struct snd_rawmidi_substream *substream) +{ + if (!port->error) { + port->substream = substream; + schedule_work(&port->work); + } +} + +static inline void +snd_fw_async_midi_port_finish(struct snd_fw_async_midi_port *port) +{ + port->substream = NULL; + cancel_work_sync(&port->work); + port->error = false; +} + +int snd_tscm_transaction_register(struct snd_tscm *tscm); +int snd_tscm_transaction_reregister(struct snd_tscm *tscm); +void snd_tscm_transaction_unregister(struct snd_tscm *tscm); + +void snd_tscm_proc_init(struct snd_tscm *tscm); + +int snd_tscm_create_pcm_devices(struct snd_tscm *tscm); + +int snd_tscm_create_midi_devices(struct snd_tscm *tscm); + +int snd_tscm_create_hwdep_device(struct snd_tscm *tscm); + +#endif |