Commit fd1cc9de authored by Takashi Sakamoto's avatar Takashi Sakamoto Committed by Takashi Iwai

ALSA: fireface: add support for Fireface UCX

Fireface UFX was shipped by RME GmbH in 2012. This model supports later
protocol for management of isochronous communication and synchronization
of sampling transmission frequency.

This commit adds support for the model. At present, it's not clear how
to encode MIDI messages and decide destination address for asynchronous
transaction, thus this commit adds support for isochronous communication
for PCM frames only.
Signed-off-by: default avatarTakashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: default avatarTakashi Iwai <tiwai@suse.de>
parent 4c4871a8
...@@ -163,5 +163,6 @@ config SND_FIREFACE ...@@ -163,5 +163,6 @@ config SND_FIREFACE
Say Y here to include support for RME fireface series. Say Y here to include support for RME fireface series.
* Fireface 400 * Fireface 400
* Fireface 800 * Fireface 800
* Fireface UCX
endif # SND_FIREWIRE endif # SND_FIREWIRE
snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \ 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-stream.o ff-pcm.o ff-hwdep.o ff-protocol-former.o \
ff-protocol-latter.o
obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o
// 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 0xffff00000004
#define LATTER_ISOC_CHANNELS 0xffff00000008
#define LATTER_ISOC_START 0xffff0000000c
#define LATTER_FETCH_MODE 0xffff00000010
#define LATTER_SYNC_STATUS 0x0000801c0000
static int parse_clock_bits(u32 data, unsigned int *rate,
enum snd_ff_clock_src *src)
{
static const struct {
unsigned int rate;
u32 flag;
} *rate_entry, rate_entries[] = {
{ 32000, 0x00000000, },
{ 44100, 0x01000000, },
{ 48000, 0x02000000, },
{ 64000, 0x04000000, },
{ 88200, 0x05000000, },
{ 96000, 0x06000000, },
{ 128000, 0x08000000, },
{ 176400, 0x09000000, },
{ 192000, 0x0a000000, },
};
static const struct {
enum snd_ff_clock_src src;
u32 flag;
} *clk_entry, 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, },
};
int i;
for (i = 0; i < ARRAY_SIZE(rate_entries); ++i) {
rate_entry = rate_entries + i;
if ((data & 0x0f000000) == rate_entry->flag) {
*rate = rate_entry->rate;
break;
}
}
if (i == ARRAY_SIZE(rate_entries))
return -EIO;
for (i = 0; i < ARRAY_SIZE(clk_entries); ++i) {
clk_entry = clk_entries + i;
if ((data & 0x000e00) == clk_entry->flag) {
*src = clk_entry->src;
break;
}
}
if (i == ARRAY_SIZE(clk_entries))
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, &reg, sizeof(reg), 0);
if (err < 0)
return err;
data = le32_to_cpu(reg);
return parse_clock_bits(data, rate, src);
}
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, &reg, sizeof(reg), 0);
}
static int keep_resources(struct snd_ff *ff, unsigned int rate)
{
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;
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)
{
static const struct {
unsigned int stf;
unsigned int code;
unsigned int flag;
} *entry, rate_table[] = {
{ 32000, 0x00, 0x92, },
{ 44100, 0x02, 0x92, },
{ 48000, 0x04, 0x92, },
{ 64000, 0x08, 0x8e, },
{ 88200, 0x0a, 0x8e, },
{ 96000, 0x0c, 0x8e, },
{ 128000, 0x10, 0x8c, },
{ 176400, 0x12, 0x8c, },
{ 192000, 0x14, 0x8c, },
};
u32 data;
__le32 reg;
unsigned int count;
int i;
int err;
for (i = 0; i < ARRAY_SIZE(rate_table); ++i) {
entry = rate_table + i;
if (entry->stf == rate)
break;
}
if (i == ARRAY_SIZE(rate_table))
return -EINVAL;
reg = cpu_to_le32(entry->code);
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
LATTER_STF, &reg, 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;
err = keep_resources(ff, rate);
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, &reg, sizeof(reg), 0);
if (err < 0)
return err;
// Always use the maximum number of data channels in data block of
// packet.
reg = cpu_to_le32(entry->flag);
return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
LATTER_ISOC_START, &reg, 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, &reg, 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[] = {
{ "S/PDIF", 0x00000001, 0x00000010, },
{ "ADAT", 0x00000002, 0x00000020, },
{ "WDClk", 0x00000004, 0x00000040, },
};
__le32 reg;
u32 data;
unsigned int rate;
enum snd_ff_clock_src src;
const char *label;
int i;
int err;
err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST,
LATTER_SYNC_STATUS, &reg, sizeof(reg), 0);
if (err < 0)
return;
data = le32_to_cpu(reg);
snd_iprintf(buffer, "External source detection:\n");
for (i = 0; i < ARRAY_SIZE(clk_entries); ++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);
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);
}
const struct snd_ff_protocol snd_ff_protocol_latter = {
.get_clock = latter_get_clock,
.switch_fetching_mode = latter_switch_fetching_mode,
.begin_session = latter_begin_session,
.finish_session = latter_finish_session,
.dump_status = latter_dump_status,
};
...@@ -32,6 +32,7 @@ static void ff_card_free(struct snd_card *card) ...@@ -32,6 +32,7 @@ static void ff_card_free(struct snd_card *card)
struct snd_ff *ff = card->private_data; struct snd_ff *ff = card->private_data;
snd_ff_stream_destroy_duplex(ff); snd_ff_stream_destroy_duplex(ff);
if (ff->spec->midi_high_addr > 0)
snd_ff_transaction_unregister(ff); snd_ff_transaction_unregister(ff);
} }
...@@ -50,9 +51,11 @@ static void do_registration(struct work_struct *work) ...@@ -50,9 +51,11 @@ static void do_registration(struct work_struct *work)
ff->card->private_free = ff_card_free; ff->card->private_free = ff_card_free;
ff->card->private_data = ff; ff->card->private_data = ff;
if (ff->spec->midi_high_addr > 0) {
err = snd_ff_transaction_register(ff); err = snd_ff_transaction_register(ff);
if (err < 0) if (err < 0)
goto error; goto error;
}
name_card(ff); name_card(ff);
...@@ -62,9 +65,11 @@ static void do_registration(struct work_struct *work) ...@@ -62,9 +65,11 @@ static void do_registration(struct work_struct *work)
snd_ff_proc_init(ff); snd_ff_proc_init(ff);
if (ff->spec->midi_in_ports > 0 || ff->spec->midi_out_ports > 0) {
err = snd_ff_create_midi_devices(ff); err = snd_ff_create_midi_devices(ff);
if (err < 0) if (err < 0)
goto error; goto error;
}
err = snd_ff_create_pcm_devices(ff); err = snd_ff_create_pcm_devices(ff);
if (err < 0) if (err < 0)
...@@ -119,6 +124,7 @@ static void snd_ff_update(struct fw_unit *unit) ...@@ -119,6 +124,7 @@ static void snd_ff_update(struct fw_unit *unit)
if (!ff->registered) if (!ff->registered)
snd_fw_schedule_registration(unit, &ff->dwork); snd_fw_schedule_registration(unit, &ff->dwork);
if (ff->spec->midi_high_addr > 0)
snd_ff_transaction_reregister(ff); snd_ff_transaction_reregister(ff);
if (ff->registered) if (ff->registered)
...@@ -165,6 +171,13 @@ static const struct snd_ff_spec spec_ff400 = { ...@@ -165,6 +171,13 @@ static const struct snd_ff_spec spec_ff400 = {
.midi_high_addr = 0x0000801003f4ull, .midi_high_addr = 0x0000801003f4ull,
}; };
static const struct snd_ff_spec spec_ucx = {
.name = "FirefaceUCX",
.pcm_capture_channels = {18, 14, 12},
.pcm_playback_channels = {18, 14, 12},
.protocol = &snd_ff_protocol_latter,
};
static const struct ieee1394_device_id snd_ff_id_table[] = { static const struct ieee1394_device_id snd_ff_id_table[] = {
/* Fireface 800 */ /* Fireface 800 */
{ {
...@@ -190,6 +203,18 @@ static const struct ieee1394_device_id snd_ff_id_table[] = { ...@@ -190,6 +203,18 @@ static const struct ieee1394_device_id snd_ff_id_table[] = {
.model_id = 0x101800, .model_id = 0x101800,
.driver_data = (kernel_ulong_t)&spec_ff400, .driver_data = (kernel_ulong_t)&spec_ff400,
}, },
// 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 = 0x000004,
.model_id = 0x101800,
.driver_data = (kernel_ulong_t)&spec_ucx,
},
{} {}
}; };
MODULE_DEVICE_TABLE(ieee1394, snd_ff_id_table); MODULE_DEVICE_TABLE(ieee1394, snd_ff_id_table);
......
...@@ -114,6 +114,7 @@ struct snd_ff_protocol { ...@@ -114,6 +114,7 @@ struct snd_ff_protocol {
extern const struct snd_ff_protocol snd_ff_protocol_ff800; 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_ff400;
extern const struct snd_ff_protocol snd_ff_protocol_latter;
int snd_ff_transaction_register(struct snd_ff *ff); int snd_ff_transaction_register(struct snd_ff *ff);
int snd_ff_transaction_reregister(struct snd_ff *ff); int snd_ff_transaction_reregister(struct snd_ff *ff);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment