mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-28 05:25:20 +07:00
b88f4d7c38
After bus reset, isochronous resource manager releases all of allocated isochronous resources. The nodes to restart packet streaming should request reallocation of the resources. However, between the bus-reset and invocation of 'struct fw_driver.update' handler, ALSA PCM application can detect this situation by XRUN because the target device cancelled to transmit packets once bus-reset occurs. Due to the above mechanism, ALSA fireface driver just stops packet streaming in the update handler, thus pcm.prepare handler should request the reallocation. This commit requests the reallocation in pcm.prepare callback when bus generation is changed. Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp> Signed-off-by: Takashi Iwai <tiwai@suse.de>
606 lines
16 KiB
C
606 lines
16 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
// ff-protocol-former.c - 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 FORMER_REG_SYNC_STATUS 0x0000801c0000ull
|
|
/* For block write request. */
|
|
#define FORMER_REG_FETCH_PCM_FRAMES 0x0000801c0000ull
|
|
#define FORMER_REG_CLOCK_CONFIG 0x0000801c0004ull
|
|
|
|
static int parse_clock_bits(u32 data, unsigned int *rate,
|
|
enum snd_ff_clock_src *src)
|
|
{
|
|
static const struct {
|
|
unsigned int rate;
|
|
u32 mask;
|
|
} *rate_entry, rate_entries[] = {
|
|
{ 32000, 0x00000002, },
|
|
{ 44100, 0x00000000, },
|
|
{ 48000, 0x00000006, },
|
|
{ 64000, 0x0000000a, },
|
|
{ 88200, 0x00000008, },
|
|
{ 96000, 0x0000000e, },
|
|
{ 128000, 0x00000012, },
|
|
{ 176400, 0x00000010, },
|
|
{ 192000, 0x00000016, },
|
|
};
|
|
static const struct {
|
|
enum snd_ff_clock_src src;
|
|
u32 mask;
|
|
} *clk_entry, clk_entries[] = {
|
|
{ SND_FF_CLOCK_SRC_ADAT1, 0x00000000, },
|
|
{ SND_FF_CLOCK_SRC_ADAT2, 0x00000400, },
|
|
{ SND_FF_CLOCK_SRC_SPDIF, 0x00000c00, },
|
|
{ SND_FF_CLOCK_SRC_WORD, 0x00001000, },
|
|
{ SND_FF_CLOCK_SRC_LTC, 0x00001800, },
|
|
};
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(rate_entries); ++i) {
|
|
rate_entry = rate_entries + i;
|
|
if ((data & 0x0000001e) == rate_entry->mask) {
|
|
*rate = rate_entry->rate;
|
|
break;
|
|
}
|
|
}
|
|
if (i == ARRAY_SIZE(rate_entries))
|
|
return -EIO;
|
|
|
|
if (data & 0x00000001) {
|
|
*src = SND_FF_CLOCK_SRC_INTERNAL;
|
|
} else {
|
|
for (i = 0; i < ARRAY_SIZE(clk_entries); ++i) {
|
|
clk_entry = clk_entries + i;
|
|
if ((data & 0x00001c00) == clk_entry->mask) {
|
|
*src = clk_entry->src;
|
|
break;
|
|
}
|
|
}
|
|
if (i == ARRAY_SIZE(clk_entries))
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int former_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,
|
|
FORMER_REG_CLOCK_CONFIG, ®, sizeof(reg), 0);
|
|
if (err < 0)
|
|
return err;
|
|
data = le32_to_cpu(reg);
|
|
|
|
return parse_clock_bits(data, rate, src);
|
|
}
|
|
|
|
static int former_switch_fetching_mode(struct snd_ff *ff, bool enable)
|
|
{
|
|
unsigned int count;
|
|
__le32 *reg;
|
|
int i;
|
|
int err;
|
|
|
|
count = 0;
|
|
for (i = 0; i < SND_FF_STREAM_MODE_COUNT; ++i)
|
|
count = max(count, ff->spec->pcm_playback_channels[i]);
|
|
|
|
reg = kcalloc(count, sizeof(__le32), GFP_KERNEL);
|
|
if (!reg)
|
|
return -ENOMEM;
|
|
|
|
if (!enable) {
|
|
/*
|
|
* Each quadlet is corresponding to data channels in a data
|
|
* blocks in reverse order. Precisely, quadlets for available
|
|
* data channels should be enabled. Here, I take second best
|
|
* to fetch PCM frames from all of data channels regardless of
|
|
* stf.
|
|
*/
|
|
for (i = 0; i < count; ++i)
|
|
reg[i] = cpu_to_le32(0x00000001);
|
|
}
|
|
|
|
err = snd_fw_transaction(ff->unit, TCODE_WRITE_BLOCK_REQUEST,
|
|
FORMER_REG_FETCH_PCM_FRAMES, reg,
|
|
sizeof(__le32) * count, 0);
|
|
kfree(reg);
|
|
return err;
|
|
}
|
|
|
|
static void dump_clock_config(struct snd_ff *ff, struct snd_info_buffer *buffer)
|
|
{
|
|
__le32 reg;
|
|
u32 data;
|
|
unsigned int rate;
|
|
enum snd_ff_clock_src src;
|
|
const char *label;
|
|
int err;
|
|
|
|
err = snd_fw_transaction(ff->unit, TCODE_READ_BLOCK_REQUEST,
|
|
FORMER_REG_CLOCK_CONFIG, ®, sizeof(reg), 0);
|
|
if (err < 0)
|
|
return;
|
|
data = le32_to_cpu(reg);
|
|
|
|
snd_iprintf(buffer, "Output S/PDIF format: %s (Emphasis: %s)\n",
|
|
(data & 0x00000020) ? "Professional" : "Consumer",
|
|
(data & 0x00000040) ? "on" : "off");
|
|
|
|
snd_iprintf(buffer, "Optical output interface format: %s\n",
|
|
(data & 0x00000100) ? "S/PDIF" : "ADAT");
|
|
|
|
snd_iprintf(buffer, "Word output single speed: %s\n",
|
|
(data & 0x00002000) ? "on" : "off");
|
|
|
|
snd_iprintf(buffer, "S/PDIF input interface: %s\n",
|
|
(data & 0x00000200) ? "Optical" : "Coaxial");
|
|
|
|
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, "Clock configuration: %d %s\n", rate, label);
|
|
}
|
|
|
|
static void dump_sync_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[] = {
|
|
{ "WDClk", 0x40000000, 0x20000000, },
|
|
{ "S/PDIF", 0x00080000, 0x00040000, },
|
|
{ "ADAT1", 0x00000400, 0x00001000, },
|
|
{ "ADAT2", 0x00000800, 0x00002000, },
|
|
};
|
|
static const struct {
|
|
char *const label;
|
|
u32 mask;
|
|
} *referred_entry, referred_entries[] = {
|
|
{ "ADAT1", 0x00000000, },
|
|
{ "ADAT2", 0x00400000, },
|
|
{ "S/PDIF", 0x00c00000, },
|
|
{ "WDclk", 0x01000000, },
|
|
{ "TCO", 0x01400000, },
|
|
};
|
|
static const struct {
|
|
unsigned int rate;
|
|
u32 mask;
|
|
} *rate_entry, rate_entries[] = {
|
|
{ 32000, 0x02000000, },
|
|
{ 44100, 0x04000000, },
|
|
{ 48000, 0x06000000, },
|
|
{ 64000, 0x08000000, },
|
|
{ 88200, 0x0a000000, },
|
|
{ 96000, 0x0c000000, },
|
|
{ 128000, 0x0e000000, },
|
|
{ 176400, 0x10000000, },
|
|
{ 192000, 0x12000000, },
|
|
};
|
|
__le32 reg[2];
|
|
u32 data[2];
|
|
int i;
|
|
int err;
|
|
|
|
err = snd_fw_transaction(ff->unit, TCODE_READ_BLOCK_REQUEST,
|
|
FORMER_REG_SYNC_STATUS, reg, sizeof(reg), 0);
|
|
if (err < 0)
|
|
return;
|
|
data[0] = le32_to_cpu(reg[0]);
|
|
data[1] = le32_to_cpu(reg[1]);
|
|
|
|
snd_iprintf(buffer, "External source detection:\n");
|
|
|
|
for (i = 0; i < ARRAY_SIZE(clk_entries); ++i) {
|
|
const char *state;
|
|
|
|
clk_entry = clk_entries + i;
|
|
if (data[0] & clk_entry->locked_mask) {
|
|
if (data[0] & clk_entry->synced_mask)
|
|
state = "sync";
|
|
else
|
|
state = "lock";
|
|
} else {
|
|
state = "none";
|
|
}
|
|
|
|
snd_iprintf(buffer, "%s: %s\n", clk_entry->label, state);
|
|
}
|
|
|
|
snd_iprintf(buffer, "Referred clock:\n");
|
|
|
|
if (data[1] & 0x00000001) {
|
|
snd_iprintf(buffer, "Internal\n");
|
|
} else {
|
|
unsigned int rate;
|
|
const char *label;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(referred_entries); ++i) {
|
|
referred_entry = referred_entries + i;
|
|
if ((data[0] & 0x1e0000) == referred_entry->mask) {
|
|
label = referred_entry->label;
|
|
break;
|
|
}
|
|
}
|
|
if (i == ARRAY_SIZE(referred_entries))
|
|
label = "none";
|
|
|
|
for (i = 0; i < ARRAY_SIZE(rate_entries); ++i) {
|
|
rate_entry = rate_entries + i;
|
|
if ((data[0] & 0x1e000000) == rate_entry->mask) {
|
|
rate = rate_entry->rate;
|
|
break;
|
|
}
|
|
}
|
|
if (i == ARRAY_SIZE(rate_entries))
|
|
rate = 0;
|
|
|
|
snd_iprintf(buffer, "%s %d\n", label, rate);
|
|
}
|
|
}
|
|
|
|
static void former_dump_status(struct snd_ff *ff,
|
|
struct snd_info_buffer *buffer)
|
|
{
|
|
dump_clock_config(ff, buffer);
|
|
dump_sync_status(ff, buffer);
|
|
}
|
|
|
|
static int former_fill_midi_msg(struct snd_ff *ff,
|
|
struct snd_rawmidi_substream *substream,
|
|
unsigned int port)
|
|
{
|
|
u8 *buf = (u8 *)ff->msg_buf[port];
|
|
int len;
|
|
int i;
|
|
|
|
len = snd_rawmidi_transmit_peek(substream, buf,
|
|
SND_FF_MAXIMIM_MIDI_QUADS);
|
|
if (len <= 0)
|
|
return len;
|
|
|
|
// One quadlet includes one byte.
|
|
for (i = len - 1; i >= 0; --i)
|
|
ff->msg_buf[port][i] = cpu_to_le32(buf[i]);
|
|
ff->rx_bytes[port] = len;
|
|
|
|
return len;
|
|
}
|
|
|
|
#define FF800_STF 0x0000fc88f000
|
|
#define FF800_RX_PACKET_FORMAT 0x0000fc88f004
|
|
#define FF800_ALLOC_TX_STREAM 0x0000fc88f008
|
|
#define FF800_ISOC_COMM_START 0x0000fc88f00c
|
|
#define FF800_TX_S800_FLAG 0x00000800
|
|
#define FF800_ISOC_COMM_STOP 0x0000fc88f010
|
|
|
|
#define FF800_TX_PACKET_ISOC_CH 0x0000801c0008
|
|
|
|
static int allocate_tx_resources(struct snd_ff *ff)
|
|
{
|
|
__le32 reg;
|
|
unsigned int count;
|
|
unsigned int tx_isoc_channel;
|
|
int err;
|
|
|
|
reg = cpu_to_le32(ff->tx_stream.data_block_quadlets);
|
|
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
|
FF800_ALLOC_TX_STREAM, ®, sizeof(reg), 0);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
// Wait till the format of tx packet is available.
|
|
count = 0;
|
|
while (count++ < 10) {
|
|
u32 data;
|
|
err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST,
|
|
FF800_TX_PACKET_ISOC_CH, ®, sizeof(reg), 0);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
data = le32_to_cpu(reg);
|
|
if (data != 0xffffffff) {
|
|
tx_isoc_channel = data;
|
|
break;
|
|
}
|
|
|
|
msleep(50);
|
|
}
|
|
if (count >= 10)
|
|
return -ETIMEDOUT;
|
|
|
|
// NOTE: this is a makeshift to start OHCI 1394 IR context in the
|
|
// channel. On the other hand, 'struct fw_iso_resources.allocated' is
|
|
// not true and it's not deallocated at stop.
|
|
ff->tx_resources.channel = tx_isoc_channel;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ff800_allocate_resources(struct snd_ff *ff, unsigned int rate)
|
|
{
|
|
u32 data;
|
|
__le32 reg;
|
|
int err;
|
|
|
|
reg = cpu_to_le32(rate);
|
|
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
|
FF800_STF, ®, sizeof(reg), 0);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
// If starting isochronous communication immediately, change of STF has
|
|
// no effect. In this case, the communication runs based on former STF.
|
|
// Let's sleep for a bit.
|
|
msleep(100);
|
|
|
|
// Controllers should allocate isochronous resources for rx stream.
|
|
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)
|
|
return err;
|
|
|
|
// Set isochronous channel and the number of quadlets of rx packets.
|
|
// This should be done before the allocation of tx resources to avoid
|
|
// periodical noise.
|
|
data = ff->rx_stream.data_block_quadlets << 3;
|
|
data = (data << 8) | ff->rx_resources.channel;
|
|
reg = cpu_to_le32(data);
|
|
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
|
FF800_RX_PACKET_FORMAT, ®, sizeof(reg), 0);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return allocate_tx_resources(ff);
|
|
}
|
|
|
|
static int ff800_begin_session(struct snd_ff *ff, unsigned int rate)
|
|
{
|
|
unsigned int generation = ff->rx_resources.generation;
|
|
__le32 reg;
|
|
|
|
if (generation != fw_parent_device(ff->unit)->card->generation) {
|
|
int err = fw_iso_resources_update(&ff->rx_resources);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
reg = cpu_to_le32(0x80000000);
|
|
reg |= cpu_to_le32(ff->tx_stream.data_block_quadlets);
|
|
if (fw_parent_device(ff->unit)->max_speed == SCODE_800)
|
|
reg |= cpu_to_le32(FF800_TX_S800_FLAG);
|
|
return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
|
FF800_ISOC_COMM_START, ®, sizeof(reg), 0);
|
|
}
|
|
|
|
static void ff800_finish_session(struct snd_ff *ff)
|
|
{
|
|
__le32 reg;
|
|
|
|
reg = cpu_to_le32(0x80000000);
|
|
snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
|
FF800_ISOC_COMM_STOP, ®, sizeof(reg), 0);
|
|
}
|
|
|
|
// Fireface 800 doesn't allow drivers to register lower 4 bytes of destination
|
|
// address.
|
|
// A write transaction to clear registered higher 4 bytes of destination address
|
|
// has an effect to suppress asynchronous transaction from device.
|
|
static void ff800_handle_midi_msg(struct snd_ff *ff, unsigned int offset,
|
|
__le32 *buf, size_t length)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < length / 4; i++) {
|
|
u8 byte = le32_to_cpu(buf[i]) & 0xff;
|
|
struct snd_rawmidi_substream *substream;
|
|
|
|
substream = READ_ONCE(ff->tx_midi_substreams[0]);
|
|
if (substream)
|
|
snd_rawmidi_receive(substream, &byte, 1);
|
|
}
|
|
}
|
|
|
|
const struct snd_ff_protocol snd_ff_protocol_ff800 = {
|
|
.handle_midi_msg = ff800_handle_midi_msg,
|
|
.fill_midi_msg = former_fill_midi_msg,
|
|
.get_clock = former_get_clock,
|
|
.switch_fetching_mode = former_switch_fetching_mode,
|
|
.allocate_resources = ff800_allocate_resources,
|
|
.begin_session = ff800_begin_session,
|
|
.finish_session = ff800_finish_session,
|
|
.dump_status = former_dump_status,
|
|
};
|
|
|
|
#define FF400_STF 0x000080100500ull
|
|
#define FF400_RX_PACKET_FORMAT 0x000080100504ull
|
|
#define FF400_ISOC_COMM_START 0x000080100508ull
|
|
#define FF400_TX_PACKET_FORMAT 0x00008010050cull
|
|
#define FF400_ISOC_COMM_STOP 0x000080100510ull
|
|
|
|
// Fireface 400 manages isochronous channel number in 3 bit field. Therefore,
|
|
// we can allocate between 0 and 7 channel.
|
|
static int ff400_allocate_resources(struct snd_ff *ff, unsigned int rate)
|
|
{
|
|
__le32 reg;
|
|
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;
|
|
|
|
// Set the number of data blocks transferred in a second.
|
|
reg = cpu_to_le32(rate);
|
|
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
|
FF400_STF, ®, sizeof(reg), 0);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
msleep(100);
|
|
|
|
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 ff400_begin_session(struct snd_ff *ff, unsigned int rate)
|
|
{
|
|
unsigned int generation = ff->rx_resources.generation;
|
|
__le32 reg;
|
|
int err;
|
|
|
|
if (generation != fw_parent_device(ff->unit)->card->generation) {
|
|
err = fw_iso_resources_update(&ff->tx_resources);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
err = fw_iso_resources_update(&ff->rx_resources);
|
|
if (err < 0)
|
|
return err;
|
|
}
|
|
|
|
// Set isochronous channel and the number of quadlets of received
|
|
// packets.
|
|
reg = cpu_to_le32(((ff->rx_stream.data_block_quadlets << 3) << 8) |
|
|
ff->rx_resources.channel);
|
|
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
|
FF400_RX_PACKET_FORMAT, ®, sizeof(reg), 0);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
// Set isochronous channel and the number of quadlets of transmitted
|
|
// packet.
|
|
// TODO: investigate the purpose of this 0x80.
|
|
reg = cpu_to_le32((0x80 << 24) |
|
|
(ff->tx_resources.channel << 5) |
|
|
(ff->tx_stream.data_block_quadlets));
|
|
err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
|
FF400_TX_PACKET_FORMAT, ®, sizeof(reg), 0);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
// Allow to transmit packets.
|
|
reg = cpu_to_le32(0x00000001);
|
|
return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
|
FF400_ISOC_COMM_START, ®, sizeof(reg), 0);
|
|
}
|
|
|
|
static void ff400_finish_session(struct snd_ff *ff)
|
|
{
|
|
__le32 reg;
|
|
|
|
reg = cpu_to_le32(0x80000000);
|
|
snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
|
|
FF400_ISOC_COMM_STOP, ®, sizeof(reg), 0);
|
|
}
|
|
|
|
// For Fireface 400, lower 4 bytes of destination address is configured by bit
|
|
// flag in quadlet register (little endian) at 0x'0000'801'0051c. Drivers can
|
|
// select one of 4 options:
|
|
//
|
|
// bit flags: offset of destination address
|
|
// - 0x04000000: 0x'....'....'0000'0000
|
|
// - 0x08000000: 0x'....'....'0000'0080
|
|
// - 0x10000000: 0x'....'....'0000'0100
|
|
// - 0x20000000: 0x'....'....'0000'0180
|
|
//
|
|
// Drivers can suppress the device to transfer asynchronous transactions by
|
|
// using below 2 bits.
|
|
// - 0x01000000: suppress transmission
|
|
// - 0x02000000: suppress transmission
|
|
//
|
|
// Actually, the register is write-only and includes the other options such as
|
|
// input attenuation. This driver allocates destination address with '0000'0000
|
|
// in its lower offset and expects userspace application to configure the
|
|
// register for it.
|
|
static void ff400_handle_midi_msg(struct snd_ff *ff, unsigned int offset,
|
|
__le32 *buf, size_t length)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < length / 4; i++) {
|
|
u32 quad = le32_to_cpu(buf[i]);
|
|
u8 byte;
|
|
unsigned int index;
|
|
struct snd_rawmidi_substream *substream;
|
|
|
|
/* Message in first port. */
|
|
/*
|
|
* This value may represent the index of this unit when the same
|
|
* units are on the same IEEE 1394 bus. This driver doesn't use
|
|
* it.
|
|
*/
|
|
index = (quad >> 8) & 0xff;
|
|
if (index > 0) {
|
|
substream = READ_ONCE(ff->tx_midi_substreams[0]);
|
|
if (substream != NULL) {
|
|
byte = quad & 0xff;
|
|
snd_rawmidi_receive(substream, &byte, 1);
|
|
}
|
|
}
|
|
|
|
/* Message in second port. */
|
|
index = (quad >> 24) & 0xff;
|
|
if (index > 0) {
|
|
substream = READ_ONCE(ff->tx_midi_substreams[1]);
|
|
if (substream != NULL) {
|
|
byte = (quad >> 16) & 0xff;
|
|
snd_rawmidi_receive(substream, &byte, 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const struct snd_ff_protocol snd_ff_protocol_ff400 = {
|
|
.handle_midi_msg = ff400_handle_midi_msg,
|
|
.fill_midi_msg = former_fill_midi_msg,
|
|
.get_clock = former_get_clock,
|
|
.switch_fetching_mode = former_switch_fetching_mode,
|
|
.allocate_resources = ff400_allocate_resources,
|
|
.begin_session = ff400_begin_session,
|
|
.finish_session = ff400_finish_session,
|
|
.dump_status = former_dump_status,
|
|
};
|