linux-stable/sound/firewire/motu/motu-protocol-v2.c
Takashi Sakamoto 23c671be97 ALSA: firewire-motu: add support for MOTU 896HD
Mark of the Unicorn (MOTU) shipped 896HD 2003 as one of models in second
generation of its FireWire series, and already discontinued it. The model
consists of below ICs:

 * Texas Instruments TSB41AB2
 * Phillips Semiconductors PDI1394L40
 * Altera cyclone EP1C3
 * Texas Instruments TMS320VC5402

It supports sampling transmission frequency up to 192.0 kHz. The packet
format differs depending on both of sampling transfer frequency and enabling
ADAT channels. The model doesn't support MIDI message transmission.

This commit adds support for it.

$ python3 crpp < /sys/bus/firewire/devices/fw1/config_rom
               ROM header and bus information block
               -----------------------------------------------------------------
400  04101b66  bus_info_length 4, crc_length 16, crc 7014
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  0001dbce  device_id 000001dbce  | EUI-64 0001f2000001dbce

               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  0003dcc1  directory_length 3, crc 56513
42c  120001f2  specifier id
430  13000005  version
434  17102800  model

               eui-64 leaf at 438
               -----------------------------------------------------------------
438  000264f2  leaf_length 2, crc 25842
43c  0001f200  company_id 0001f2     |
440  0001dbce  device_id 000001dbce  | EUI-64 0001f2000001dbce

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Link: https://lore.kernel.org/r/20210823085741.33864-1-o-takashi@sakamocchi.jp
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2021-08-23 10:58:40 +02:00

317 lines
8.2 KiB
C

// 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_SRC_AESEBU_ON_XLR 0x07 // In Traveler.
#define V2_CLOCK_SRC_ADAT_ON_DSUB 0x05
#define V2_CLOCK_SRC_WORD_ON_BNC 0x04
#define V2_CLOCK_SRC_SPH 0x03
#define V2_CLOCK_SRC_SPDIF 0x02 // on either coaxial or optical. AES/EBU in 896HD.
#define V2_CLOCK_SRC_ADAT_ON_OPT 0x01
#define V2_CLOCK_SRC_INTERNAL 0x00
#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, &reg,
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, &reg,
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, &reg,
sizeof(reg));
}
static int get_clock_source(struct snd_motu *motu, u32 data,
enum snd_motu_clock_source *src)
{
switch (data & V2_CLOCK_SRC_MASK) {
case V2_CLOCK_SRC_INTERNAL:
*src = SND_MOTU_CLOCK_SOURCE_INTERNAL;
break;
case V2_CLOCK_SRC_ADAT_ON_OPT:
*src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT;
break;
case V2_CLOCK_SRC_SPDIF:
{
bool support_iec60958_on_opt = (motu->spec == &snd_motu_spec_828mk2 ||
motu->spec == &snd_motu_spec_traveler);
if (motu->spec == &snd_motu_spec_896hd) {
*src = SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR;
} else if (!support_iec60958_on_opt) {
*src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX;
} else {
__be32 reg;
// To check the configuration of optical interface.
int err = snd_motu_transaction_read(motu, V2_IN_OUT_CONF_OFFSET, &reg,
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 V2_CLOCK_SRC_SPH:
*src = SND_MOTU_CLOCK_SOURCE_SPH;
break;
case V2_CLOCK_SRC_WORD_ON_BNC:
*src = SND_MOTU_CLOCK_SOURCE_WORD_ON_BNC;
break;
case V2_CLOCK_SRC_ADAT_ON_DSUB:
*src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB;
break;
case V2_CLOCK_SRC_AESEBU_ON_XLR:
// For Traveler.
*src = SND_MOTU_CLOCK_SOURCE_AESEBU_ON_XLR;
break;
default:
*src = SND_MOTU_CLOCK_SOURCE_UNKNOWN;
break;
}
return 0;
}
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, &reg,
sizeof(reg));
if (err < 0)
return err;
return get_clock_source(motu, be32_to_cpu(reg), src);
}
// Expected for Traveler, 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 if (motu->spec == &snd_motu_spec_896hd) {
// 896HD implements Altera Cyclone EP1C3 but nothing to do.
return 0;
} else {
__be32 reg;
u32 data;
int err;
err = snd_motu_transaction_read(motu, V2_CLOCK_STATUS_OFFSET,
&reg, 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,
&reg, sizeof(reg));
}
}
int snd_motu_protocol_v2_cache_packet_formats(struct snd_motu *motu)
{
bool has_two_opt_ifaces = (motu->spec == &snd_motu_spec_8pre);
__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, &reg,
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 (((data & V2_OPT_IN_IFACE_MASK) >> V2_OPT_IN_IFACE_SHIFT) == V2_OPT_IFACE_MODE_ADAT) {
motu->tx_packet_formats.pcm_chunks[0] += 8;
if (!has_two_opt_ifaces)
motu->tx_packet_formats.pcm_chunks[1] += 4;
else
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;
if (!has_two_opt_ifaces)
motu->rx_packet_formats.pcm_chunks[1] += 4;
else
motu->rx_packet_formats.pcm_chunks[1] += 8;
}
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_896hd = {
.name = "896HD",
.protocol_version = SND_MOTU_PROTOCOL_V2,
// No support for MIDI.
.tx_fixed_pcm_chunks = {14, 14, 8},
.rx_fixed_pcm_chunks = {14, 14, 8},
};
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},
};