![Takashi Sakamoto](/assets/img/avatar_default.png)
This commit adds support for MOTU 8pre FireWire, which was shipped 2007 and nowadays already discontinued. Userspace applications can transmit and receive PCM frames and MIDI messages for this model via ALSA PCM interface and RawMidi/Sequencer interfaces. Like the other models of MOTU FireWire series, this model has many quirks in its CIP. At first, data channels for two pairs of optical interfaces. At lower sampling transmission frequency, i.e. 44.1 and 48.0 kHz, one pair is available for ADAT data, thus 8 data chunks are transferred by CIP. At middle sampling transmission frequency, i.e. 88.2 and 96.0 kHz, two pairs are available to keep 8 chunks for ADAT data, thus CIP still includes 8 data chunks. Apart from data chunks for optical interface, CIP includes fixed number of data chunks. In tx stream, two chunks for status message, eight chunks for samples from analog 1-8 input, two chunks for mix-return. In rx stream, two chunks for control message, two chunks for main 1-2 output, two chunks for phone 1-2 output, two chunks for dummy 1-2. CIP header in tx stream includes quirks for its dbs and dbc fields. The value of dbs field is fixed to 0x13, against its actual size. The value of dbc field is firstly updated to 0x07 from zero, then it's incremented continuously according to actual number of data h blocks. Finally, the model has own bits to disable frame fetch. This commit uses several options to absorb the above quirks. $ python2 crpp < /sys/bus/firewire/devices/fw1/config_rom ROM header and bus information block ----------------------------------------------------------------- 400 0410b57d bus_info_length 4, crc_length 16, crc 46461 404 31333934 bus_name "1394" 408 20001000 irmc 0, cmc 0, isc 1, bmc 0, cyc_clk_acc 0, max_rec 1 (4) 40c 0001f200 company_id 0001f2 | 410 00083dfb device_id 0000083dfb | EUI-64 0001f20000083dfb root directory ----------------------------------------------------------------- 414 0004c65c directory_length 4, crc 50780 418 030001f2 vendor 41c 0c0083c0 node capabilities per IEEE 1394 420 8d000006 --> eui-64 leaf at 438 424 d1000001 --> unit directory at 428 unit directory at 428 ----------------------------------------------------------------- 428 0003991c directory_length 3, crc 39196 42c 120001f2 specifier id 430 1300000f version 434 17103800 model eui-64 leaf at 438 ----------------------------------------------------------------- 438 00022681 leaf_length 2, crc 9857 43c 0001f200 company_id 0001f2 | 440 00083dfb device_id 0000083dfb | EUI-64 0001f20000083dfb Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp> Signed-off-by: Takashi Iwai <tiwai@suse.de>
288 lines
7.2 KiB
C
288 lines
7.2 KiB
C
/*
|
|
* motu-protocol-v2.c - a part of driver for MOTU FireWire series
|
|
*
|
|
* Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
|
*
|
|
* Licensed under the terms of the GNU General Public License, version 2.
|
|
*/
|
|
|
|
#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_TRAVELER_FETCH_DISABLE 0x04000000
|
|
#define V2_CLOCK_TRAVELER_FETCH_ENABLE 0x03000000
|
|
#define V2_CLOCK_8PRE_FETCH_DISABLE 0x02000000
|
|
#define V2_CLOCK_8PRE_FETCH_ENABLE 0x00000000
|
|
|
|
#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 v2_get_clock_rate(struct snd_motu *motu, unsigned int *rate)
|
|
{
|
|
__be32 reg;
|
|
unsigned int index;
|
|
int err;
|
|
|
|
err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®,
|
|
sizeof(reg));
|
|
if (err < 0)
|
|
return err;
|
|
|
|
index = (be32_to_cpu(reg) & 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;
|
|
}
|
|
|
|
static int 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;
|
|
|
|
if (motu->spec == &snd_motu_spec_traveler) {
|
|
data &= ~V2_CLOCK_TRAVELER_FETCH_ENABLE;
|
|
data |= V2_CLOCK_TRAVELER_FETCH_DISABLE;
|
|
}
|
|
|
|
reg = cpu_to_be32(data);
|
|
return snd_motu_transaction_write(motu, V2_CLOCK_STATUS_OFFSET, ®,
|
|
sizeof(reg));
|
|
}
|
|
|
|
static int v2_get_clock_source(struct snd_motu *motu,
|
|
enum snd_motu_clock_source *src)
|
|
{
|
|
__be32 reg;
|
|
unsigned int index;
|
|
int err;
|
|
|
|
err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET, ®,
|
|
sizeof(reg));
|
|
if (err < 0)
|
|
return err;
|
|
|
|
index = be32_to_cpu(reg) & V2_CLOCK_SRC_MASK;
|
|
if (index > 5)
|
|
return -EIO;
|
|
|
|
/* To check the configuration of optical interface. */
|
|
err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, ®,
|
|
sizeof(reg));
|
|
if (err < 0)
|
|
return err;
|
|
|
|
switch (index) {
|
|
case 0:
|
|
*src = SND_MOTU_CLOCK_SOURCE_INTERNAL;
|
|
break;
|
|
case 1:
|
|
if (be32_to_cpu(reg) & 0x00000200)
|
|
*src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT;
|
|
else
|
|
*src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT;
|
|
break;
|
|
case 2:
|
|
*src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX;
|
|
break;
|
|
case 4:
|
|
*src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC;
|
|
break;
|
|
case 5:
|
|
*src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB;
|
|
break;
|
|
default:
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int v2_switch_fetching_mode(struct snd_motu *motu, bool enable)
|
|
{
|
|
__be32 reg;
|
|
u32 data;
|
|
int err = 0;
|
|
|
|
if (motu->spec == &snd_motu_spec_traveler ||
|
|
motu->spec == &snd_motu_spec_8pre) {
|
|
err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET,
|
|
®, sizeof(reg));
|
|
if (err < 0)
|
|
return err;
|
|
data = be32_to_cpu(reg);
|
|
|
|
if (motu->spec == &snd_motu_spec_traveler) {
|
|
data &= ~(V2_CLOCK_TRAVELER_FETCH_DISABLE |
|
|
V2_CLOCK_TRAVELER_FETCH_ENABLE);
|
|
|
|
if (enable)
|
|
data |= V2_CLOCK_TRAVELER_FETCH_ENABLE;
|
|
else
|
|
data |= V2_CLOCK_TRAVELER_FETCH_DISABLE;
|
|
} else if (motu->spec == &snd_motu_spec_8pre) {
|
|
data &= ~(V2_CLOCK_8PRE_FETCH_DISABLE |
|
|
V2_CLOCK_8PRE_FETCH_ENABLE);
|
|
|
|
if (enable)
|
|
data |= V2_CLOCK_8PRE_FETCH_DISABLE;
|
|
else
|
|
data |= V2_CLOCK_8PRE_FETCH_ENABLE;
|
|
}
|
|
|
|
reg = cpu_to_be32(data);
|
|
err = snd_motu_transaction_write(motu, V2_CLOCK_STATUS_OFFSET,
|
|
®, sizeof(reg));
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static void calculate_fixed_part(struct snd_motu_packet_format *formats,
|
|
enum amdtp_stream_direction dir,
|
|
enum snd_motu_spec_flags flags,
|
|
unsigned char analog_ports)
|
|
{
|
|
unsigned char pcm_chunks[3] = {0, 0, 0};
|
|
|
|
formats->msg_chunks = 2;
|
|
|
|
pcm_chunks[0] = analog_ports;
|
|
pcm_chunks[1] = analog_ports;
|
|
if (flags & SND_MOTU_SPEC_SUPPORT_CLOCK_X4)
|
|
pcm_chunks[2] = analog_ports;
|
|
|
|
if (dir == AMDTP_IN_STREAM) {
|
|
if (flags & SND_MOTU_SPEC_TX_MICINST_CHUNK) {
|
|
pcm_chunks[0] += 2;
|
|
pcm_chunks[1] += 2;
|
|
}
|
|
if (flags & SND_MOTU_SPEC_TX_RETURN_CHUNK) {
|
|
pcm_chunks[0] += 2;
|
|
pcm_chunks[1] += 2;
|
|
}
|
|
} else {
|
|
if (flags & SND_MOTU_SPEC_RX_SEPARETED_MAIN) {
|
|
pcm_chunks[0] += 2;
|
|
pcm_chunks[1] += 2;
|
|
}
|
|
|
|
// Packets to v2 units include 2 chunks for phone 1/2, except
|
|
// for 176.4/192.0 kHz.
|
|
pcm_chunks[0] += 2;
|
|
pcm_chunks[1] += 2;
|
|
}
|
|
|
|
if (flags & SND_MOTU_SPEC_HAS_AESEBU_IFACE) {
|
|
pcm_chunks[0] += 2;
|
|
pcm_chunks[1] += 2;
|
|
}
|
|
|
|
/*
|
|
* All of v2 models have a pair of coaxial interfaces for digital in/out
|
|
* port. At 44.1/48.0/88.2/96.0 kHz, packets includes PCM from these
|
|
* ports.
|
|
*/
|
|
pcm_chunks[0] += 2;
|
|
pcm_chunks[1] += 2;
|
|
|
|
formats->fixed_part_pcm_chunks[0] = pcm_chunks[0];
|
|
formats->fixed_part_pcm_chunks[1] = pcm_chunks[1];
|
|
formats->fixed_part_pcm_chunks[2] = pcm_chunks[2];
|
|
}
|
|
|
|
static void calculate_differed_part(struct snd_motu_packet_format *formats,
|
|
enum snd_motu_spec_flags flags,
|
|
u32 data, u32 mask, u32 shift)
|
|
{
|
|
unsigned char pcm_chunks[2] = {0, 0};
|
|
|
|
/*
|
|
* When optical interfaces are configured for S/PDIF (TOSLINK),
|
|
* the above PCM frames come from them, instead of coaxial
|
|
* interfaces.
|
|
*/
|
|
data = (data & mask) >> shift;
|
|
if (data == V2_OPT_IFACE_MODE_ADAT) {
|
|
if (flags & SND_MOTU_SPEC_HAS_OPT_IFACE_A) {
|
|
pcm_chunks[0] += 8;
|
|
pcm_chunks[1] += 4;
|
|
}
|
|
// 8pre has two sets of optical interface and doesn't reduce
|
|
// chunks for ADAT signals.
|
|
if (flags & SND_MOTU_SPEC_HAS_OPT_IFACE_B) {
|
|
pcm_chunks[1] += 4;
|
|
}
|
|
}
|
|
|
|
/* At mode x4, no data chunks are supported in this part. */
|
|
formats->differed_part_pcm_chunks[0] = pcm_chunks[0];
|
|
formats->differed_part_pcm_chunks[1] = pcm_chunks[1];
|
|
}
|
|
|
|
static int v2_cache_packet_formats(struct snd_motu *motu)
|
|
{
|
|
__be32 reg;
|
|
u32 data;
|
|
int err;
|
|
|
|
err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, ®,
|
|
sizeof(reg));
|
|
if (err < 0)
|
|
return err;
|
|
data = be32_to_cpu(reg);
|
|
|
|
calculate_fixed_part(&motu->tx_packet_formats, AMDTP_IN_STREAM,
|
|
motu->spec->flags, motu->spec->analog_in_ports);
|
|
calculate_differed_part(&motu->tx_packet_formats, motu->spec->flags,
|
|
data, V2_OPT_IN_IFACE_MASK, V2_OPT_IN_IFACE_SHIFT);
|
|
|
|
calculate_fixed_part(&motu->rx_packet_formats, AMDTP_OUT_STREAM,
|
|
motu->spec->flags, motu->spec->analog_out_ports);
|
|
calculate_differed_part(&motu->rx_packet_formats, motu->spec->flags,
|
|
data, V2_OPT_OUT_IFACE_MASK, V2_OPT_OUT_IFACE_SHIFT);
|
|
|
|
motu->tx_packet_formats.pcm_byte_offset = 10;
|
|
motu->rx_packet_formats.pcm_byte_offset = 10;
|
|
|
|
return 0;
|
|
}
|
|
|
|
const struct snd_motu_protocol snd_motu_protocol_v2 = {
|
|
.get_clock_rate = v2_get_clock_rate,
|
|
.set_clock_rate = v2_set_clock_rate,
|
|
.get_clock_source = v2_get_clock_source,
|
|
.switch_fetching_mode = v2_switch_fetching_mode,
|
|
.cache_packet_formats = v2_cache_packet_formats,
|
|
};
|