forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
996 lines
24 KiB
996 lines
24 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* bebob_stream.c - a part of driver for BeBoB based devices |
|
* |
|
* Copyright (c) 2013-2014 Takashi Sakamoto |
|
*/ |
|
|
|
#include "./bebob.h" |
|
|
|
#define READY_TIMEOUT_MS 4000 |
|
|
|
/* |
|
* NOTE; |
|
* For BeBoB streams, Both of input and output CMP connection are important. |
|
* |
|
* For most devices, each CMP connection starts to transmit/receive a |
|
* corresponding stream. But for a few devices, both of CMP connection needs |
|
* to start transmitting stream. An example is 'M-Audio Firewire 410'. |
|
*/ |
|
|
|
/* 128 is an arbitrary length but it seems to be enough */ |
|
#define FORMAT_MAXIMUM_LENGTH 128 |
|
|
|
const unsigned int snd_bebob_rate_table[SND_BEBOB_STRM_FMT_ENTRIES] = { |
|
[0] = 32000, |
|
[1] = 44100, |
|
[2] = 48000, |
|
[3] = 88200, |
|
[4] = 96000, |
|
[5] = 176400, |
|
[6] = 192000, |
|
}; |
|
|
|
/* |
|
* See: Table 51: Extended Stream Format Info ‘Sampling Frequency’ |
|
* in Additional AVC commands (Nov 2003, BridgeCo) |
|
*/ |
|
static const unsigned int bridgeco_freq_table[] = { |
|
[0] = 0x02, |
|
[1] = 0x03, |
|
[2] = 0x04, |
|
[3] = 0x0a, |
|
[4] = 0x05, |
|
[5] = 0x06, |
|
[6] = 0x07, |
|
}; |
|
|
|
static int |
|
get_formation_index(unsigned int rate, unsigned int *index) |
|
{ |
|
unsigned int i; |
|
|
|
for (i = 0; i < ARRAY_SIZE(snd_bebob_rate_table); i++) { |
|
if (snd_bebob_rate_table[i] == rate) { |
|
*index = i; |
|
return 0; |
|
} |
|
} |
|
return -EINVAL; |
|
} |
|
|
|
int |
|
snd_bebob_stream_get_rate(struct snd_bebob *bebob, unsigned int *curr_rate) |
|
{ |
|
unsigned int tx_rate, rx_rate, trials; |
|
int err; |
|
|
|
trials = 0; |
|
do { |
|
err = avc_general_get_sig_fmt(bebob->unit, &tx_rate, |
|
AVC_GENERAL_PLUG_DIR_OUT, 0); |
|
} while (err == -EAGAIN && ++trials < 3); |
|
if (err < 0) |
|
goto end; |
|
|
|
trials = 0; |
|
do { |
|
err = avc_general_get_sig_fmt(bebob->unit, &rx_rate, |
|
AVC_GENERAL_PLUG_DIR_IN, 0); |
|
} while (err == -EAGAIN && ++trials < 3); |
|
if (err < 0) |
|
goto end; |
|
|
|
*curr_rate = rx_rate; |
|
if (rx_rate == tx_rate) |
|
goto end; |
|
|
|
/* synchronize receive stream rate to transmit stream rate */ |
|
err = avc_general_set_sig_fmt(bebob->unit, rx_rate, |
|
AVC_GENERAL_PLUG_DIR_IN, 0); |
|
end: |
|
return err; |
|
} |
|
|
|
int |
|
snd_bebob_stream_set_rate(struct snd_bebob *bebob, unsigned int rate) |
|
{ |
|
int err; |
|
|
|
err = avc_general_set_sig_fmt(bebob->unit, rate, |
|
AVC_GENERAL_PLUG_DIR_OUT, 0); |
|
if (err < 0) |
|
goto end; |
|
|
|
err = avc_general_set_sig_fmt(bebob->unit, rate, |
|
AVC_GENERAL_PLUG_DIR_IN, 0); |
|
if (err < 0) |
|
goto end; |
|
|
|
/* |
|
* Some devices need a bit time for transition. |
|
* 300msec is got by some experiments. |
|
*/ |
|
msleep(300); |
|
end: |
|
return err; |
|
} |
|
|
|
int snd_bebob_stream_get_clock_src(struct snd_bebob *bebob, |
|
enum snd_bebob_clock_type *src) |
|
{ |
|
const struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock; |
|
u8 addr[AVC_BRIDGECO_ADDR_BYTES], input[7]; |
|
unsigned int id; |
|
enum avc_bridgeco_plug_type type; |
|
int err = 0; |
|
|
|
/* 1.The device has its own operation to switch source of clock */ |
|
if (clk_spec) { |
|
err = clk_spec->get(bebob, &id); |
|
if (err < 0) { |
|
dev_err(&bebob->unit->device, |
|
"fail to get clock source: %d\n", err); |
|
goto end; |
|
} |
|
|
|
if (id >= clk_spec->num) { |
|
dev_err(&bebob->unit->device, |
|
"clock source %d out of range 0..%d\n", |
|
id, clk_spec->num - 1); |
|
err = -EIO; |
|
goto end; |
|
} |
|
|
|
*src = clk_spec->types[id]; |
|
goto end; |
|
} |
|
|
|
/* |
|
* 2.The device don't support to switch source of clock then assumed |
|
* to use internal clock always |
|
*/ |
|
if (bebob->sync_input_plug < 0) { |
|
*src = SND_BEBOB_CLOCK_TYPE_INTERNAL; |
|
goto end; |
|
} |
|
|
|
/* |
|
* 3.The device supports to switch source of clock by an usual way. |
|
* Let's check input for 'Music Sub Unit Sync Input' plug. |
|
*/ |
|
avc_bridgeco_fill_msu_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, |
|
bebob->sync_input_plug); |
|
err = avc_bridgeco_get_plug_input(bebob->unit, addr, input); |
|
if (err < 0) { |
|
dev_err(&bebob->unit->device, |
|
"fail to get an input for MSU in plug %d: %d\n", |
|
bebob->sync_input_plug, err); |
|
goto end; |
|
} |
|
|
|
/* |
|
* If there are no input plugs, all of fields are 0xff. |
|
* Here check the first field. This field is used for direction. |
|
*/ |
|
if (input[0] == 0xff) { |
|
*src = SND_BEBOB_CLOCK_TYPE_INTERNAL; |
|
goto end; |
|
} |
|
|
|
/* The source from any output plugs is for one purpose only. */ |
|
if (input[0] == AVC_BRIDGECO_PLUG_DIR_OUT) { |
|
/* |
|
* In BeBoB architecture, the source from music subunit may |
|
* bypass from oPCR[0]. This means that this source gives |
|
* synchronization to IEEE 1394 cycle start packet. |
|
*/ |
|
if (input[1] == AVC_BRIDGECO_PLUG_MODE_SUBUNIT && |
|
input[2] == 0x0c) { |
|
*src = SND_BEBOB_CLOCK_TYPE_INTERNAL; |
|
goto end; |
|
} |
|
/* The source from any input units is for several purposes. */ |
|
} else if (input[1] == AVC_BRIDGECO_PLUG_MODE_UNIT) { |
|
if (input[2] == AVC_BRIDGECO_PLUG_UNIT_ISOC) { |
|
if (input[3] == 0x00) { |
|
/* |
|
* This source comes from iPCR[0]. This means |
|
* that presentation timestamp calculated by |
|
* SYT series of the received packets. In |
|
* short, this driver is the master of |
|
* synchronization. |
|
*/ |
|
*src = SND_BEBOB_CLOCK_TYPE_SYT; |
|
goto end; |
|
} else { |
|
/* |
|
* This source comes from iPCR[1-29]. This |
|
* means that the synchronization stream is not |
|
* the Audio/MIDI compound stream. |
|
*/ |
|
*src = SND_BEBOB_CLOCK_TYPE_EXTERNAL; |
|
goto end; |
|
} |
|
} else if (input[2] == AVC_BRIDGECO_PLUG_UNIT_EXT) { |
|
/* Check type of this plug. */ |
|
avc_bridgeco_fill_unit_addr(addr, |
|
AVC_BRIDGECO_PLUG_DIR_IN, |
|
AVC_BRIDGECO_PLUG_UNIT_EXT, |
|
input[3]); |
|
err = avc_bridgeco_get_plug_type(bebob->unit, addr, |
|
&type); |
|
if (err < 0) |
|
goto end; |
|
|
|
if (type == AVC_BRIDGECO_PLUG_TYPE_DIG) { |
|
/* |
|
* SPDIF/ADAT or sometimes (not always) word |
|
* clock. |
|
*/ |
|
*src = SND_BEBOB_CLOCK_TYPE_EXTERNAL; |
|
goto end; |
|
} else if (type == AVC_BRIDGECO_PLUG_TYPE_SYNC) { |
|
/* Often word clock. */ |
|
*src = SND_BEBOB_CLOCK_TYPE_EXTERNAL; |
|
goto end; |
|
} else if (type == AVC_BRIDGECO_PLUG_TYPE_ADDITION) { |
|
/* |
|
* Not standard. |
|
* Mostly, additional internal clock. |
|
*/ |
|
*src = SND_BEBOB_CLOCK_TYPE_INTERNAL; |
|
goto end; |
|
} |
|
} |
|
} |
|
|
|
/* Not supported. */ |
|
err = -EIO; |
|
end: |
|
return err; |
|
} |
|
|
|
static int map_data_channels(struct snd_bebob *bebob, struct amdtp_stream *s) |
|
{ |
|
unsigned int sec, sections, ch, channels; |
|
unsigned int pcm, midi, location; |
|
unsigned int stm_pos, sec_loc, pos; |
|
u8 *buf, addr[AVC_BRIDGECO_ADDR_BYTES], type; |
|
enum avc_bridgeco_plug_dir dir; |
|
int err; |
|
|
|
/* |
|
* The length of return value of this command cannot be expected. Here |
|
* use the maximum length of FCP. |
|
*/ |
|
buf = kzalloc(256, GFP_KERNEL); |
|
if (buf == NULL) |
|
return -ENOMEM; |
|
|
|
if (s == &bebob->tx_stream) |
|
dir = AVC_BRIDGECO_PLUG_DIR_OUT; |
|
else |
|
dir = AVC_BRIDGECO_PLUG_DIR_IN; |
|
|
|
avc_bridgeco_fill_unit_addr(addr, dir, AVC_BRIDGECO_PLUG_UNIT_ISOC, 0); |
|
err = avc_bridgeco_get_plug_ch_pos(bebob->unit, addr, buf, 256); |
|
if (err < 0) { |
|
dev_err(&bebob->unit->device, |
|
"fail to get channel position for isoc %s plug 0: %d\n", |
|
(dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" : "out", |
|
err); |
|
goto end; |
|
} |
|
pos = 0; |
|
|
|
/* positions in I/O buffer */ |
|
pcm = 0; |
|
midi = 0; |
|
|
|
/* the number of sections in AMDTP packet */ |
|
sections = buf[pos++]; |
|
|
|
for (sec = 0; sec < sections; sec++) { |
|
/* type of this section */ |
|
avc_bridgeco_fill_unit_addr(addr, dir, |
|
AVC_BRIDGECO_PLUG_UNIT_ISOC, 0); |
|
err = avc_bridgeco_get_plug_section_type(bebob->unit, addr, |
|
sec, &type); |
|
if (err < 0) { |
|
dev_err(&bebob->unit->device, |
|
"fail to get section type for isoc %s plug 0: %d\n", |
|
(dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" : |
|
"out", |
|
err); |
|
goto end; |
|
} |
|
/* NoType */ |
|
if (type == 0xff) { |
|
err = -ENOSYS; |
|
goto end; |
|
} |
|
|
|
/* the number of channels in this section */ |
|
channels = buf[pos++]; |
|
|
|
for (ch = 0; ch < channels; ch++) { |
|
/* position of this channel in AMDTP packet */ |
|
stm_pos = buf[pos++] - 1; |
|
/* location of this channel in this section */ |
|
sec_loc = buf[pos++] - 1; |
|
|
|
/* |
|
* Basically the number of location is within the |
|
* number of channels in this section. But some models |
|
* of M-Audio don't follow this. Its location for MIDI |
|
* is the position of MIDI channels in AMDTP packet. |
|
*/ |
|
if (sec_loc >= channels) |
|
sec_loc = ch; |
|
|
|
switch (type) { |
|
/* for MIDI conformant data channel */ |
|
case 0x0a: |
|
/* AMDTP_MAX_CHANNELS_FOR_MIDI is 1. */ |
|
if ((midi > 0) && (stm_pos != midi)) { |
|
err = -ENOSYS; |
|
goto end; |
|
} |
|
amdtp_am824_set_midi_position(s, stm_pos); |
|
midi = stm_pos; |
|
break; |
|
/* for PCM data channel */ |
|
case 0x01: /* Headphone */ |
|
case 0x02: /* Microphone */ |
|
case 0x03: /* Line */ |
|
case 0x04: /* SPDIF */ |
|
case 0x05: /* ADAT */ |
|
case 0x06: /* TDIF */ |
|
case 0x07: /* MADI */ |
|
/* for undefined/changeable signal */ |
|
case 0x08: /* Analog */ |
|
case 0x09: /* Digital */ |
|
default: |
|
location = pcm + sec_loc; |
|
if (location >= AM824_MAX_CHANNELS_FOR_PCM) { |
|
err = -ENOSYS; |
|
goto end; |
|
} |
|
amdtp_am824_set_pcm_position(s, location, |
|
stm_pos); |
|
break; |
|
} |
|
} |
|
|
|
if (type != 0x0a) |
|
pcm += channels; |
|
else |
|
midi += channels; |
|
} |
|
end: |
|
kfree(buf); |
|
return err; |
|
} |
|
|
|
static int |
|
check_connection_used_by_others(struct snd_bebob *bebob, struct amdtp_stream *s) |
|
{ |
|
struct cmp_connection *conn; |
|
bool used; |
|
int err; |
|
|
|
if (s == &bebob->tx_stream) |
|
conn = &bebob->out_conn; |
|
else |
|
conn = &bebob->in_conn; |
|
|
|
err = cmp_connection_check_used(conn, &used); |
|
if ((err >= 0) && used && !amdtp_stream_running(s)) { |
|
dev_err(&bebob->unit->device, |
|
"Connection established by others: %cPCR[%d]\n", |
|
(conn->direction == CMP_OUTPUT) ? 'o' : 'i', |
|
conn->pcr_index); |
|
err = -EBUSY; |
|
} |
|
|
|
return err; |
|
} |
|
|
|
static void break_both_connections(struct snd_bebob *bebob) |
|
{ |
|
cmp_connection_break(&bebob->in_conn); |
|
cmp_connection_break(&bebob->out_conn); |
|
} |
|
|
|
static int start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream) |
|
{ |
|
struct cmp_connection *conn; |
|
int err = 0; |
|
|
|
if (stream == &bebob->rx_stream) |
|
conn = &bebob->in_conn; |
|
else |
|
conn = &bebob->out_conn; |
|
|
|
// channel mapping. |
|
if (bebob->maudio_special_quirk == NULL) { |
|
err = map_data_channels(bebob, stream); |
|
if (err < 0) |
|
return err; |
|
} |
|
|
|
err = cmp_connection_establish(conn); |
|
if (err < 0) |
|
return err; |
|
|
|
return amdtp_domain_add_stream(&bebob->domain, stream, |
|
conn->resources.channel, conn->speed); |
|
} |
|
|
|
static int init_stream(struct snd_bebob *bebob, struct amdtp_stream *stream) |
|
{ |
|
unsigned int flags = CIP_BLOCKING; |
|
enum amdtp_stream_direction dir_stream; |
|
struct cmp_connection *conn; |
|
enum cmp_direction dir_conn; |
|
int err; |
|
|
|
if (stream == &bebob->tx_stream) { |
|
dir_stream = AMDTP_IN_STREAM; |
|
conn = &bebob->out_conn; |
|
dir_conn = CMP_OUTPUT; |
|
} else { |
|
dir_stream = AMDTP_OUT_STREAM; |
|
conn = &bebob->in_conn; |
|
dir_conn = CMP_INPUT; |
|
} |
|
|
|
if (stream == &bebob->tx_stream) { |
|
if (bebob->quirks & SND_BEBOB_QUIRK_WRONG_DBC) |
|
flags |= CIP_EMPTY_HAS_WRONG_DBC; |
|
} |
|
|
|
err = cmp_connection_init(conn, bebob->unit, dir_conn, 0); |
|
if (err < 0) |
|
return err; |
|
|
|
err = amdtp_am824_init(stream, bebob->unit, dir_stream, flags); |
|
if (err < 0) { |
|
cmp_connection_destroy(conn); |
|
return err; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static void destroy_stream(struct snd_bebob *bebob, struct amdtp_stream *stream) |
|
{ |
|
amdtp_stream_destroy(stream); |
|
|
|
if (stream == &bebob->tx_stream) |
|
cmp_connection_destroy(&bebob->out_conn); |
|
else |
|
cmp_connection_destroy(&bebob->in_conn); |
|
} |
|
|
|
int snd_bebob_stream_init_duplex(struct snd_bebob *bebob) |
|
{ |
|
int err; |
|
|
|
err = init_stream(bebob, &bebob->tx_stream); |
|
if (err < 0) |
|
return err; |
|
|
|
err = init_stream(bebob, &bebob->rx_stream); |
|
if (err < 0) { |
|
destroy_stream(bebob, &bebob->tx_stream); |
|
return err; |
|
} |
|
|
|
err = amdtp_domain_init(&bebob->domain); |
|
if (err < 0) { |
|
destroy_stream(bebob, &bebob->tx_stream); |
|
destroy_stream(bebob, &bebob->rx_stream); |
|
} |
|
|
|
return err; |
|
} |
|
|
|
static int keep_resources(struct snd_bebob *bebob, struct amdtp_stream *stream, |
|
unsigned int rate, unsigned int index) |
|
{ |
|
unsigned int pcm_channels; |
|
unsigned int midi_ports; |
|
struct cmp_connection *conn; |
|
int err; |
|
|
|
if (stream == &bebob->tx_stream) { |
|
pcm_channels = bebob->tx_stream_formations[index].pcm; |
|
midi_ports = bebob->midi_input_ports; |
|
conn = &bebob->out_conn; |
|
} else { |
|
pcm_channels = bebob->rx_stream_formations[index].pcm; |
|
midi_ports = bebob->midi_output_ports; |
|
conn = &bebob->in_conn; |
|
} |
|
|
|
err = amdtp_am824_set_parameters(stream, rate, pcm_channels, midi_ports, false); |
|
if (err < 0) |
|
return err; |
|
|
|
return cmp_connection_reserve(conn, amdtp_stream_get_max_payload(stream)); |
|
} |
|
|
|
int snd_bebob_stream_reserve_duplex(struct snd_bebob *bebob, unsigned int rate, |
|
unsigned int frames_per_period, |
|
unsigned int frames_per_buffer) |
|
{ |
|
unsigned int curr_rate; |
|
int err; |
|
|
|
// Considering JACK/FFADO streaming: |
|
// TODO: This can be removed hwdep functionality becomes popular. |
|
err = check_connection_used_by_others(bebob, &bebob->rx_stream); |
|
if (err < 0) |
|
return err; |
|
|
|
err = bebob->spec->rate->get(bebob, &curr_rate); |
|
if (err < 0) |
|
return err; |
|
if (rate == 0) |
|
rate = curr_rate; |
|
if (curr_rate != rate) { |
|
amdtp_domain_stop(&bebob->domain); |
|
break_both_connections(bebob); |
|
|
|
cmp_connection_release(&bebob->out_conn); |
|
cmp_connection_release(&bebob->in_conn); |
|
} |
|
|
|
if (bebob->substreams_counter == 0 || curr_rate != rate) { |
|
unsigned int index; |
|
|
|
// NOTE: |
|
// If establishing connections at first, Yamaha GO46 |
|
// (and maybe Terratec X24) don't generate sound. |
|
// |
|
// For firmware customized by M-Audio, refer to next NOTE. |
|
err = bebob->spec->rate->set(bebob, rate); |
|
if (err < 0) { |
|
dev_err(&bebob->unit->device, |
|
"fail to set sampling rate: %d\n", |
|
err); |
|
return err; |
|
} |
|
|
|
err = get_formation_index(rate, &index); |
|
if (err < 0) |
|
return err; |
|
|
|
err = keep_resources(bebob, &bebob->tx_stream, rate, index); |
|
if (err < 0) |
|
return err; |
|
|
|
err = keep_resources(bebob, &bebob->rx_stream, rate, index); |
|
if (err < 0) { |
|
cmp_connection_release(&bebob->out_conn); |
|
return err; |
|
} |
|
|
|
err = amdtp_domain_set_events_per_period(&bebob->domain, |
|
frames_per_period, frames_per_buffer); |
|
if (err < 0) { |
|
cmp_connection_release(&bebob->out_conn); |
|
cmp_connection_release(&bebob->in_conn); |
|
return err; |
|
} |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int snd_bebob_stream_start_duplex(struct snd_bebob *bebob) |
|
{ |
|
int err; |
|
|
|
// Need no substreams. |
|
if (bebob->substreams_counter == 0) |
|
return -EIO; |
|
|
|
// packet queueing error or detecting discontinuity |
|
if (amdtp_streaming_error(&bebob->rx_stream) || |
|
amdtp_streaming_error(&bebob->tx_stream)) { |
|
amdtp_domain_stop(&bebob->domain); |
|
break_both_connections(bebob); |
|
} |
|
|
|
if (!amdtp_stream_running(&bebob->rx_stream)) { |
|
enum snd_bebob_clock_type src; |
|
unsigned int curr_rate; |
|
unsigned int tx_init_skip_cycles; |
|
|
|
if (bebob->maudio_special_quirk) { |
|
err = bebob->spec->rate->get(bebob, &curr_rate); |
|
if (err < 0) |
|
return err; |
|
} |
|
|
|
err = snd_bebob_stream_get_clock_src(bebob, &src); |
|
if (err < 0) |
|
return err; |
|
|
|
err = start_stream(bebob, &bebob->rx_stream); |
|
if (err < 0) |
|
goto error; |
|
|
|
err = start_stream(bebob, &bebob->tx_stream); |
|
if (err < 0) |
|
goto error; |
|
|
|
if (!(bebob->quirks & SND_BEBOB_QUIRK_INITIAL_DISCONTINUOUS_DBC)) |
|
tx_init_skip_cycles = 0; |
|
else |
|
tx_init_skip_cycles = 16000; |
|
|
|
// MEMO: Some devices start packet transmission long enough after establishment of |
|
// CMP connection. In the early stage of packet streaming, any device transfers |
|
// NODATA packets. After several hundred cycles, it begins to multiplex event into |
|
// the packet with adequate value of syt field in CIP header. Some devices are |
|
// strictly to generate any discontinuity in the sequence of tx packet when they |
|
// receives inadequate sequence of value in syt field of CIP header. In the case, |
|
// the request to break CMP connection is often corrupted, then any transaction |
|
// results in unrecoverable error, sometimes generate bus-reset. |
|
err = amdtp_domain_start(&bebob->domain, tx_init_skip_cycles, true, false); |
|
if (err < 0) |
|
goto error; |
|
|
|
// NOTE: |
|
// The firmware customized by M-Audio uses these commands to |
|
// start transmitting stream. This is not usual way. |
|
if (bebob->maudio_special_quirk) { |
|
err = bebob->spec->rate->set(bebob, curr_rate); |
|
if (err < 0) { |
|
dev_err(&bebob->unit->device, |
|
"fail to ensure sampling rate: %d\n", |
|
err); |
|
goto error; |
|
} |
|
} |
|
|
|
// Some devices postpone start of transmission mostly for 1 sec after receives |
|
// packets firstly. |
|
if (!amdtp_domain_wait_ready(&bebob->domain, READY_TIMEOUT_MS)) { |
|
err = -ETIMEDOUT; |
|
goto error; |
|
} |
|
} |
|
|
|
return 0; |
|
error: |
|
amdtp_domain_stop(&bebob->domain); |
|
break_both_connections(bebob); |
|
return err; |
|
} |
|
|
|
void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob) |
|
{ |
|
if (bebob->substreams_counter == 0) { |
|
amdtp_domain_stop(&bebob->domain); |
|
break_both_connections(bebob); |
|
|
|
cmp_connection_release(&bebob->out_conn); |
|
cmp_connection_release(&bebob->in_conn); |
|
} |
|
} |
|
|
|
/* |
|
* This function should be called before starting streams or after stopping |
|
* streams. |
|
*/ |
|
void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob) |
|
{ |
|
amdtp_domain_destroy(&bebob->domain); |
|
|
|
destroy_stream(bebob, &bebob->tx_stream); |
|
destroy_stream(bebob, &bebob->rx_stream); |
|
} |
|
|
|
/* |
|
* See: Table 50: Extended Stream Format Info Format Hierarchy Level 2’ |
|
* in Additional AVC commands (Nov 2003, BridgeCo) |
|
* Also 'Clause 12 AM824 sequence adaption layers' in IEC 61883-6:2005 |
|
*/ |
|
static int |
|
parse_stream_formation(u8 *buf, unsigned int len, |
|
struct snd_bebob_stream_formation *formation) |
|
{ |
|
unsigned int i, e, channels, format; |
|
|
|
/* |
|
* this module can support a hierarchy combination that: |
|
* Root: Audio and Music (0x90) |
|
* Level 1: AM824 Compound (0x40) |
|
*/ |
|
if ((buf[0] != 0x90) || (buf[1] != 0x40)) |
|
return -ENOSYS; |
|
|
|
/* check sampling rate */ |
|
for (i = 0; i < ARRAY_SIZE(bridgeco_freq_table); i++) { |
|
if (buf[2] == bridgeco_freq_table[i]) |
|
break; |
|
} |
|
if (i == ARRAY_SIZE(bridgeco_freq_table)) |
|
return -ENOSYS; |
|
|
|
/* Avoid double count by different entries for the same rate. */ |
|
memset(&formation[i], 0, sizeof(struct snd_bebob_stream_formation)); |
|
|
|
for (e = 0; e < buf[4]; e++) { |
|
channels = buf[5 + e * 2]; |
|
format = buf[6 + e * 2]; |
|
|
|
switch (format) { |
|
/* IEC 60958 Conformant, currently handled as MBLA */ |
|
case 0x00: |
|
/* Multi bit linear audio */ |
|
case 0x06: /* Raw */ |
|
formation[i].pcm += channels; |
|
break; |
|
/* MIDI Conformant */ |
|
case 0x0d: |
|
formation[i].midi += channels; |
|
break; |
|
/* IEC 61937-3 to 7 */ |
|
case 0x01: |
|
case 0x02: |
|
case 0x03: |
|
case 0x04: |
|
case 0x05: |
|
/* Multi bit linear audio */ |
|
case 0x07: /* DVD-Audio */ |
|
case 0x0c: /* High Precision */ |
|
/* One Bit Audio */ |
|
case 0x08: /* (Plain) Raw */ |
|
case 0x09: /* (Plain) SACD */ |
|
case 0x0a: /* (Encoded) Raw */ |
|
case 0x0b: /* (Encoded) SACD */ |
|
/* Synchronization Stream (Stereo Raw audio) */ |
|
case 0x40: |
|
/* Don't care */ |
|
case 0xff: |
|
default: |
|
return -ENOSYS; /* not supported */ |
|
} |
|
} |
|
|
|
if (formation[i].pcm > AM824_MAX_CHANNELS_FOR_PCM || |
|
formation[i].midi > AM824_MAX_CHANNELS_FOR_MIDI) |
|
return -ENOSYS; |
|
|
|
return 0; |
|
} |
|
|
|
static int fill_stream_formations(struct snd_bebob *bebob, u8 addr[AVC_BRIDGECO_ADDR_BYTES], |
|
enum avc_bridgeco_plug_dir plug_dir, unsigned int plug_id, |
|
struct snd_bebob_stream_formation *formations) |
|
{ |
|
enum avc_bridgeco_plug_type plug_type; |
|
u8 *buf; |
|
unsigned int len, eid; |
|
int err; |
|
|
|
avc_bridgeco_fill_unit_addr(addr, plug_dir, AVC_BRIDGECO_PLUG_UNIT_ISOC, plug_id); |
|
|
|
err = avc_bridgeco_get_plug_type(bebob->unit, addr, &plug_type); |
|
if (err < 0) { |
|
dev_err(&bebob->unit->device, |
|
"Fail to get type for isoc %d plug 0: %d\n", plug_dir, err); |
|
return err; |
|
} else if (plug_type != AVC_BRIDGECO_PLUG_TYPE_ISOC) |
|
return -ENXIO; |
|
|
|
buf = kmalloc(FORMAT_MAXIMUM_LENGTH, GFP_KERNEL); |
|
if (buf == NULL) |
|
return -ENOMEM; |
|
|
|
for (eid = 0; eid < SND_BEBOB_STRM_FMT_ENTRIES; ++eid) { |
|
avc_bridgeco_fill_unit_addr(addr, plug_dir, AVC_BRIDGECO_PLUG_UNIT_ISOC, plug_id); |
|
|
|
len = FORMAT_MAXIMUM_LENGTH; |
|
err = avc_bridgeco_get_plug_strm_fmt(bebob->unit, addr, buf, &len, eid); |
|
// No entries remained. |
|
if (err == -EINVAL && eid > 0) { |
|
err = 0; |
|
break; |
|
} else if (err < 0) { |
|
dev_err(&bebob->unit->device, |
|
"fail to get stream format %d for isoc %d plug %d:%d\n", |
|
eid, plug_dir, plug_id, err); |
|
break; |
|
} |
|
|
|
err = parse_stream_formation(buf, len, formations); |
|
if (err < 0) |
|
break; |
|
} |
|
|
|
kfree(buf); |
|
return err; |
|
} |
|
|
|
static int detect_midi_ports(struct snd_bebob *bebob, |
|
const struct snd_bebob_stream_formation *formats, |
|
u8 addr[AVC_BRIDGECO_ADDR_BYTES], enum avc_bridgeco_plug_dir plug_dir, |
|
unsigned int plug_count, unsigned int *midi_ports) |
|
{ |
|
int i; |
|
int err = 0; |
|
|
|
*midi_ports = 0; |
|
|
|
/// Detect the number of available MIDI ports when packet has MIDI conformant data channel. |
|
for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; ++i) { |
|
if (formats[i].midi > 0) |
|
break; |
|
} |
|
if (i >= SND_BEBOB_STRM_FMT_ENTRIES) |
|
return 0; |
|
|
|
for (i = 0; i < plug_count; ++i) { |
|
enum avc_bridgeco_plug_type plug_type; |
|
unsigned int ch_count; |
|
|
|
avc_bridgeco_fill_unit_addr(addr, plug_dir, AVC_BRIDGECO_PLUG_UNIT_EXT, i); |
|
|
|
err = avc_bridgeco_get_plug_type(bebob->unit, addr, &plug_type); |
|
if (err < 0) { |
|
dev_err(&bebob->unit->device, |
|
"fail to get type for external %d plug %d: %d\n", |
|
plug_dir, i, err); |
|
break; |
|
} else if (plug_type != AVC_BRIDGECO_PLUG_TYPE_MIDI) { |
|
continue; |
|
} |
|
|
|
err = avc_bridgeco_get_plug_ch_count(bebob->unit, addr, &ch_count); |
|
if (err < 0) |
|
break; |
|
// Yamaha GO44, GO46, Terratec Phase 24, Phase x24 reports 0 for the number of |
|
// channels in external output plug 3 (MIDI type) even if it has a pair of physical |
|
// MIDI jacks. As a workaround, assume it as one. |
|
if (ch_count == 0) |
|
ch_count = 1; |
|
*midi_ports += ch_count; |
|
} |
|
|
|
return err; |
|
} |
|
|
|
static int |
|
seek_msu_sync_input_plug(struct snd_bebob *bebob) |
|
{ |
|
u8 plugs[AVC_PLUG_INFO_BUF_BYTES], addr[AVC_BRIDGECO_ADDR_BYTES]; |
|
unsigned int i; |
|
enum avc_bridgeco_plug_type type; |
|
int err; |
|
|
|
/* Get the number of Music Sub Unit for both direction. */ |
|
err = avc_general_get_plug_info(bebob->unit, 0x0c, 0x00, 0x00, plugs); |
|
if (err < 0) { |
|
dev_err(&bebob->unit->device, |
|
"fail to get info for MSU in/out plugs: %d\n", |
|
err); |
|
goto end; |
|
} |
|
|
|
/* seek destination plugs for 'MSU sync input' */ |
|
bebob->sync_input_plug = -1; |
|
for (i = 0; i < plugs[0]; i++) { |
|
avc_bridgeco_fill_msu_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, i); |
|
err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); |
|
if (err < 0) { |
|
dev_err(&bebob->unit->device, |
|
"fail to get type for MSU in plug %d: %d\n", |
|
i, err); |
|
goto end; |
|
} |
|
|
|
if (type == AVC_BRIDGECO_PLUG_TYPE_SYNC) { |
|
bebob->sync_input_plug = i; |
|
break; |
|
} |
|
} |
|
end: |
|
return err; |
|
} |
|
|
|
int snd_bebob_stream_discover(struct snd_bebob *bebob) |
|
{ |
|
const struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock; |
|
u8 plugs[AVC_PLUG_INFO_BUF_BYTES], addr[AVC_BRIDGECO_ADDR_BYTES]; |
|
int err; |
|
|
|
/* the number of plugs for isoc in/out, ext in/out */ |
|
err = avc_general_get_plug_info(bebob->unit, 0x1f, 0x07, 0x00, plugs); |
|
if (err < 0) { |
|
dev_err(&bebob->unit->device, |
|
"fail to get info for isoc/external in/out plugs: %d\n", |
|
err); |
|
goto end; |
|
} |
|
|
|
/* |
|
* This module supports at least one isoc input plug and one isoc |
|
* output plug. |
|
*/ |
|
if ((plugs[0] == 0) || (plugs[1] == 0)) { |
|
err = -ENOSYS; |
|
goto end; |
|
} |
|
|
|
err = fill_stream_formations(bebob, addr, AVC_BRIDGECO_PLUG_DIR_IN, 0, |
|
bebob->rx_stream_formations); |
|
if (err < 0) |
|
goto end; |
|
|
|
err = fill_stream_formations(bebob, addr, AVC_BRIDGECO_PLUG_DIR_OUT, 0, |
|
bebob->tx_stream_formations); |
|
if (err < 0) |
|
goto end; |
|
|
|
err = detect_midi_ports(bebob, bebob->tx_stream_formations, addr, AVC_BRIDGECO_PLUG_DIR_IN, |
|
plugs[2], &bebob->midi_input_ports); |
|
if (err < 0) |
|
goto end; |
|
|
|
err = detect_midi_ports(bebob, bebob->rx_stream_formations, addr, AVC_BRIDGECO_PLUG_DIR_OUT, |
|
plugs[3], &bebob->midi_output_ports); |
|
if (err < 0) |
|
goto end; |
|
|
|
/* for check source of clock later */ |
|
if (!clk_spec) |
|
err = seek_msu_sync_input_plug(bebob); |
|
end: |
|
return err; |
|
} |
|
|
|
void snd_bebob_stream_lock_changed(struct snd_bebob *bebob) |
|
{ |
|
bebob->dev_lock_changed = true; |
|
wake_up(&bebob->hwdep_wait); |
|
} |
|
|
|
int snd_bebob_stream_lock_try(struct snd_bebob *bebob) |
|
{ |
|
int err; |
|
|
|
spin_lock_irq(&bebob->lock); |
|
|
|
/* user land lock this */ |
|
if (bebob->dev_lock_count < 0) { |
|
err = -EBUSY; |
|
goto end; |
|
} |
|
|
|
/* this is the first time */ |
|
if (bebob->dev_lock_count++ == 0) |
|
snd_bebob_stream_lock_changed(bebob); |
|
err = 0; |
|
end: |
|
spin_unlock_irq(&bebob->lock); |
|
return err; |
|
} |
|
|
|
void snd_bebob_stream_lock_release(struct snd_bebob *bebob) |
|
{ |
|
spin_lock_irq(&bebob->lock); |
|
|
|
if (WARN_ON(bebob->dev_lock_count <= 0)) |
|
goto end; |
|
if (--bebob->dev_lock_count == 0) |
|
snd_bebob_stream_lock_changed(bebob); |
|
end: |
|
spin_unlock_irq(&bebob->lock); |
|
}
|
|
|