ALSA: firewire-motu: add support for MOTU 828

MOTU 828 is a first model in MOTU FireWire series, produced in 2001. This
model consists of three chips:
 * Texas Instruments TSB41AB1 (Physical layer for IEEE 1394 bus)
 * Philips Semiconductors 1394L21BE (Link layer for IEEE 1394 bus and
   packet processing layer)
 * QuickLogic QuickRAM QL4016 (Data block processing layer and digital
   signal processing)

This commit adds a support for this model, with its unique protocol as
version 1. The features of this protocol are:

 * no MIDI support.
 * Rx packets have no data chunks for control and status messages.
 * Tx packets have 2 data chunks for control and status messages in the
   end of each data block. The chunks consist of data block counter
   (4 byte) and message (2 byte).
 * All of settings are represented in bit flag in one quadlet address
   (0x'ffff'f000'0b00).
 * When optical interface is configured as S/PDIF, signals of the interface
   is multiplexed for packets, instead of signals of coaxial interface.
 * The internal multiplexer is not configured by software.

I note that the device has a quirk to mute output voluntarily during
receiving batch of packets in the beginning of packet streaming. The
operation to unmute should be done by software enough after the device
shifts the state, however it's not deterministic. Furthermore, just
after switching rate of sampling clock, the device keeps the state longer.
This patch manages to sleep 100 msec before unmute operation, but it may
fail to release the mute in the case that the rate is changed. As a
workaround, users can restart packet streaming at the same rate, or write
to specific register from userspace.

$ python3 crpp < /sys/bus/firewire/devices/fw1/config_rom
               ROM header and bus information block
               -----------------------------------------------------------------
400  04105c54  bus_info_length 4, crc_length 16, crc 23636
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  00005015  device_id 0000005015  | EUI-64 0001f20000005015

               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  00035052  directory_length 3, crc 20562
42c  120001f2  specifier id
430  13000001  version
434  17101800  model

               eui-64 leaf at 438
               -----------------------------------------------------------------
438  0002eeb6  leaf_length 2, crc 61110
43c  0001f200  company_id 0001f2     |
440  00005015  device_id 0000005015  | EUI-64 0001f20000005015

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Link: https://lore.kernel.org/r/20210616082847.124688-2-o-takashi@sakamocchi.jp
Signed-off-by: Takashi Iwai <tiwai@suse.de>
This commit is contained in:
Takashi Sakamoto 2021-06-16 17:28:46 +09:00 committed by Takashi Iwai
parent 36d1a6729b
commit d13d6b284d
5 changed files with 252 additions and 1 deletions

View File

@ -165,6 +165,7 @@ config SND_FIREWIRE_MOTU
select SND_HWDEP select SND_HWDEP
help help
Say Y here to enable support for FireWire devices which MOTU produced: Say Y here to enable support for FireWire devices which MOTU produced:
* 828
* 828mk2 * 828mk2
* Traveler * Traveler
* Ultralite * Ultralite

View File

@ -3,5 +3,6 @@ CFLAGS_amdtp-motu.o := -I$(src)
snd-firewire-motu-objs := motu.o amdtp-motu.o motu-transaction.o motu-stream.o \ 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-proc.o motu-pcm.o motu-midi.o motu-hwdep.o \
motu-protocol-v2.o motu-protocol-v3.o motu-protocol-v2.o motu-protocol-v3.o \
motu-protocol-v1.o
obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o obj-$(CONFIG_SND_FIREWIRE_MOTU) += snd-firewire-motu.o

View File

@ -0,0 +1,225 @@
// SPDX-License-Identifier: GPL-2.0-only
// motu-protocol-v1.c - a part of driver for MOTU FireWire series
//
// Copyright (c) 2021 Takashi Sakamoto <o-takashi@sakamocchi.jp>
//
// Licensed under the terms of the GNU General Public License, version 2.
#include "motu.h"
#include <linux/delay.h>
// Status register for MOTU 828 (0x'ffff'f000'0b00).
//
// 0xffff0000: ISOC_COMM_CONTROL_MASK in motu-stream.c.
// 0x00008000: mode of optical input interface.
// 0x00008000: for S/PDIF signal.
// 0x00000000: disabled or for ADAT signal.
// 0x00004000: mode of optical output interface.
// 0x00004000: for S/PDIF signal.
// 0x00000000: disabled or for ADAT signal.
// 0x00003f40: monitor input mode.
// 0x00000800: analog-1/2
// 0x00001a00: analog-3/4
// 0x00002c00: analog-5/6
// 0x00003e00: analog-7/8
// 0x00000000: analog-1
// 0x00000900: analog-2
// 0x00001200: analog-3
// 0x00001b00: analog-4
// 0x00002400: analog-5
// 0x00002d00: analog-6
// 0x00003600: analog-7
// 0x00003f00: analog-8
// 0x00000040: disabled
// 0x00000004: rate of sampling clock.
// 0x00000004: 48.0 kHz
// 0x00000000: 44.1 kHz
// 0x00000023: source of sampling clock.
// 0x00000002: S/PDIF on optical/coaxial interface.
// 0x00000021: ADAT on optical interface
// 0x00000001: ADAT on Dsub 9pin
// 0x00000000: internal or SMPTE
#define CLK_828_STATUS_OFFSET 0x0b00
#define CLK_828_STATUS_MASK 0x0000ffff
#define CLK_828_STATUS_FLAG_OPT_IN_IFACE_IS_SPDIF 0x00008000
#define CLK_828_STATUS_FLAG_OPT_OUT_IFACE_IS_SPDIF 0x00004000
#define CLK_828_STATUS_FLAG_FETCH_PCM_FRAMES 0x00000080
#define CLK_828_STATUS_FLAG_SRC_IS_NOT_FROM_ADAT_DSUB 0x00000020
#define CLK_828_STATUS_FLAG_OUTPUT_MUTE 0x00000008
#define CLK_828_STATUS_FLAG_RATE_48000 0x00000004
#define CLK_828_STATUS_FLAG_SRC_SPDIF_ON_OPT_OR_COAX 0x00000002
#define CLK_828_STATUS_FLAG_SRC_ADAT_ON_OPT_OR_DSUB 0x00000001
static void parse_clock_rate_828(u32 data, unsigned int *rate)
{
if (data & CLK_828_STATUS_FLAG_RATE_48000)
*rate = 48000;
else
*rate = 44100;
}
static int get_clock_rate_828(struct snd_motu *motu, unsigned int *rate)
{
__be32 reg;
int err;
err = snd_motu_transaction_read(motu, CLK_828_STATUS_OFFSET, &reg, sizeof(reg));
if (err < 0)
return err;
parse_clock_rate_828(be32_to_cpu(reg), rate);
return 0;
}
int snd_motu_protocol_v1_get_clock_rate(struct snd_motu *motu, unsigned int *rate)
{
if (motu->spec == &snd_motu_spec_828)
return get_clock_rate_828(motu, rate);
else
return -ENXIO;
}
static int set_clock_rate_828(struct snd_motu *motu, unsigned int rate)
{
__be32 reg;
u32 data;
int err;
err = snd_motu_transaction_read(motu, CLK_828_STATUS_OFFSET, &reg, sizeof(reg));
if (err < 0)
return err;
data = be32_to_cpu(reg) & CLK_828_STATUS_MASK;
data &= ~CLK_828_STATUS_FLAG_RATE_48000;
if (rate == 48000)
data |= CLK_828_STATUS_FLAG_RATE_48000;
reg = cpu_to_be32(data);
return snd_motu_transaction_write(motu, CLK_828_STATUS_OFFSET, &reg, sizeof(reg));
}
int snd_motu_protocol_v1_set_clock_rate(struct snd_motu *motu, unsigned int rate)
{
if (motu->spec == &snd_motu_spec_828)
return set_clock_rate_828(motu, rate);
else
return -ENXIO;
}
static int get_clock_source_828(struct snd_motu *motu, enum snd_motu_clock_source *src)
{
__be32 reg;
u32 data;
int err;
err = snd_motu_transaction_read(motu, CLK_828_STATUS_OFFSET, &reg, sizeof(reg));
if (err < 0)
return err;
data = be32_to_cpu(reg) & CLK_828_STATUS_MASK;
if (data & CLK_828_STATUS_FLAG_SRC_ADAT_ON_OPT_OR_DSUB) {
if (data & CLK_828_STATUS_FLAG_SRC_IS_NOT_FROM_ADAT_DSUB)
*src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_OPT;
else
*src = SND_MOTU_CLOCK_SOURCE_ADAT_ON_DSUB;
} else if (data & CLK_828_STATUS_FLAG_SRC_SPDIF_ON_OPT_OR_COAX) {
if (data & CLK_828_STATUS_FLAG_OPT_IN_IFACE_IS_SPDIF)
*src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_OPT;
else
*src = SND_MOTU_CLOCK_SOURCE_SPDIF_ON_COAX;
} else {
*src = SND_MOTU_CLOCK_SOURCE_INTERNAL;
}
return 0;
}
int snd_motu_protocol_v1_get_clock_source(struct snd_motu *motu, enum snd_motu_clock_source *src)
{
if (motu->spec == &snd_motu_spec_828)
return get_clock_source_828(motu, src);
else
return -ENXIO;
}
static int switch_fetching_mode_828(struct snd_motu *motu, bool enable)
{
__be32 reg;
u32 data;
int err;
err = snd_motu_transaction_read(motu, CLK_828_STATUS_OFFSET, &reg, sizeof(reg));
if (err < 0)
return err;
data = be32_to_cpu(reg) & CLK_828_STATUS_MASK;
data &= ~(CLK_828_STATUS_FLAG_FETCH_PCM_FRAMES | CLK_828_STATUS_FLAG_OUTPUT_MUTE);
if (enable) {
// This transaction should be initiated after the device receives batch of packets
// since the device voluntarily mutes outputs. As a workaround, yield processor over
// 100 msec.
msleep(100);
data |= CLK_828_STATUS_FLAG_FETCH_PCM_FRAMES | CLK_828_STATUS_FLAG_OUTPUT_MUTE;
}
reg = cpu_to_be32(data);
return snd_motu_transaction_write(motu, CLK_828_STATUS_OFFSET, &reg, sizeof(reg));
}
int snd_motu_protocol_v1_switch_fetching_mode(struct snd_motu *motu, bool enable)
{
if (motu->spec == &snd_motu_spec_828)
return switch_fetching_mode_828(motu, enable);
else
return -ENXIO;
}
static int detect_packet_formats_828(struct snd_motu *motu)
{
__be32 reg;
u32 data;
int err;
motu->tx_packet_formats.pcm_byte_offset = 4;
motu->tx_packet_formats.msg_chunks = 2;
motu->rx_packet_formats.pcm_byte_offset = 4;
motu->rx_packet_formats.msg_chunks = 0;
err = snd_motu_transaction_read(motu, CLK_828_STATUS_OFFSET, &reg, sizeof(reg));
if (err < 0)
return err;
data = be32_to_cpu(reg) & CLK_828_STATUS_MASK;
// The number of chunks is just reduced when SPDIF is activated.
if (!(data & CLK_828_STATUS_FLAG_OPT_IN_IFACE_IS_SPDIF))
motu->tx_packet_formats.pcm_chunks[0] += 8;
if (!(data & CLK_828_STATUS_FLAG_OPT_OUT_IFACE_IS_SPDIF))
motu->rx_packet_formats.pcm_chunks[0] += 8;
return 0;
}
int snd_motu_protocol_v1_cache_packet_formats(struct snd_motu *motu)
{
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_828)
return detect_packet_formats_828(motu);
else
return 0;
}
const struct snd_motu_spec snd_motu_spec_828 = {
.name = "828",
.protocol_version = SND_MOTU_PROTOCOL_V1,
.tx_fixed_pcm_chunks = {10, 0, 0},
.rx_fixed_pcm_chunks = {10, 0, 0},
};

View File

@ -150,6 +150,7 @@ static void motu_bus_update(struct fw_unit *unit)
} }
static const struct ieee1394_device_id motu_id_table[] = { static const struct ieee1394_device_id motu_id_table[] = {
SND_MOTU_DEV_ENTRY(0x000001, &snd_motu_spec_828),
SND_MOTU_DEV_ENTRY(0x000003, &snd_motu_spec_828mk2), SND_MOTU_DEV_ENTRY(0x000003, &snd_motu_spec_828mk2),
SND_MOTU_DEV_ENTRY(0x000009, &snd_motu_spec_traveler), SND_MOTU_DEV_ENTRY(0x000009, &snd_motu_spec_traveler),
SND_MOTU_DEV_ENTRY(0x00000d, &snd_motu_spec_ultralite), SND_MOTU_DEV_ENTRY(0x00000d, &snd_motu_spec_ultralite),

View File

@ -107,6 +107,7 @@ enum snd_motu_clock_source {
}; };
enum snd_motu_protocol_version { enum snd_motu_protocol_version {
SND_MOTU_PROTOCOL_V1,
SND_MOTU_PROTOCOL_V2, SND_MOTU_PROTOCOL_V2,
SND_MOTU_PROTOCOL_V3, SND_MOTU_PROTOCOL_V3,
}; };
@ -121,6 +122,8 @@ struct snd_motu_spec {
unsigned char rx_fixed_pcm_chunks[3]; unsigned char rx_fixed_pcm_chunks[3];
}; };
extern const struct snd_motu_spec snd_motu_spec_828;
extern const struct snd_motu_spec snd_motu_spec_828mk2; 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_traveler;
extern const struct snd_motu_spec snd_motu_spec_ultralite; extern const struct snd_motu_spec snd_motu_spec_ultralite;
@ -170,6 +173,16 @@ int snd_motu_create_midi_devices(struct snd_motu *motu);
int snd_motu_create_hwdep_device(struct snd_motu *motu); int snd_motu_create_hwdep_device(struct snd_motu *motu);
int snd_motu_protocol_v1_get_clock_rate(struct snd_motu *motu,
unsigned int *rate);
int snd_motu_protocol_v1_set_clock_rate(struct snd_motu *motu,
unsigned int rate);
int snd_motu_protocol_v1_get_clock_source(struct snd_motu *motu,
enum snd_motu_clock_source *src);
int snd_motu_protocol_v1_switch_fetching_mode(struct snd_motu *motu,
bool enable);
int snd_motu_protocol_v1_cache_packet_formats(struct snd_motu *motu);
int snd_motu_protocol_v2_get_clock_rate(struct snd_motu *motu, int snd_motu_protocol_v2_get_clock_rate(struct snd_motu *motu,
unsigned int *rate); unsigned int *rate);
int snd_motu_protocol_v2_set_clock_rate(struct snd_motu *motu, int snd_motu_protocol_v2_set_clock_rate(struct snd_motu *motu,
@ -197,6 +210,8 @@ static inline int snd_motu_protocol_get_clock_rate(struct snd_motu *motu,
return snd_motu_protocol_v2_get_clock_rate(motu, rate); return snd_motu_protocol_v2_get_clock_rate(motu, rate);
else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V3) else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V3)
return snd_motu_protocol_v3_get_clock_rate(motu, rate); return snd_motu_protocol_v3_get_clock_rate(motu, rate);
else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V1)
return snd_motu_protocol_v1_get_clock_rate(motu, rate);
else else
return -ENXIO; return -ENXIO;
} }
@ -208,6 +223,8 @@ static inline int snd_motu_protocol_set_clock_rate(struct snd_motu *motu,
return snd_motu_protocol_v2_set_clock_rate(motu, rate); return snd_motu_protocol_v2_set_clock_rate(motu, rate);
else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V3) else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V3)
return snd_motu_protocol_v3_set_clock_rate(motu, rate); return snd_motu_protocol_v3_set_clock_rate(motu, rate);
else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V1)
return snd_motu_protocol_v1_set_clock_rate(motu, rate);
else else
return -ENXIO; return -ENXIO;
} }
@ -219,6 +236,8 @@ static inline int snd_motu_protocol_get_clock_source(struct snd_motu *motu,
return snd_motu_protocol_v2_get_clock_source(motu, source); return snd_motu_protocol_v2_get_clock_source(motu, source);
else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V3) else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V3)
return snd_motu_protocol_v3_get_clock_source(motu, source); return snd_motu_protocol_v3_get_clock_source(motu, source);
else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V1)
return snd_motu_protocol_v1_get_clock_source(motu, source);
else else
return -ENXIO; return -ENXIO;
} }
@ -230,6 +249,8 @@ static inline int snd_motu_protocol_switch_fetching_mode(struct snd_motu *motu,
return snd_motu_protocol_v2_switch_fetching_mode(motu, enable); return snd_motu_protocol_v2_switch_fetching_mode(motu, enable);
else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V3) else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V3)
return snd_motu_protocol_v3_switch_fetching_mode(motu, enable); return snd_motu_protocol_v3_switch_fetching_mode(motu, enable);
else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V1)
return snd_motu_protocol_v1_switch_fetching_mode(motu, enable);
else else
return -ENXIO; return -ENXIO;
} }
@ -240,6 +261,8 @@ static inline int snd_motu_protocol_cache_packet_formats(struct snd_motu *motu)
return snd_motu_protocol_v2_cache_packet_formats(motu); return snd_motu_protocol_v2_cache_packet_formats(motu);
else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V3) else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V3)
return snd_motu_protocol_v3_cache_packet_formats(motu); return snd_motu_protocol_v3_cache_packet_formats(motu);
else if (motu->spec->protocol_version == SND_MOTU_PROTOCOL_V1)
return snd_motu_protocol_v1_cache_packet_formats(motu);
else else
return -ENXIO; return -ENXIO;
} }