linux-stable/sound/firewire/motu/motu-protocol-v3.c
Takashi Sakamoto 675398674c ALSA: firewire-motu: wait for notification when changing clock configuration for protocol v3
It costs expensive to change clock configuration for models of protocol
version 3. In current implementation, speculative strategy is used to
finish the operation; just waiting for 4 seconds. As long as I investigate,
when accepting and changing clock status actually, the device sends
notification with mask.

This commit uses wise way to wait for the notification after changing
sampling clock rate during 4 seconds.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Link: https://lore.kernel.org/r/20200614141221.53527-1-o-takashi@sakamocchi.jp
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2020-06-15 15:59:19 +02:00

326 lines
7.7 KiB
C

// 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, &reg,
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, &reg,
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, &reg,
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, &reg, 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, &reg,
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, &reg,
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, &reg,
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, &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 (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},
};