From 2c3c1048746a4622d8c89a29670120dc8fab93c4 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:49:45 +0200 Subject: Adding upstream version 6.1.76. Signed-off-by: Daniel Baumann --- sound/firewire/bebob/bebob_stream.c | 996 ++++++++++++++++++++++++++++++++++++ 1 file changed, 996 insertions(+) create mode 100644 sound/firewire/bebob/bebob_stream.c (limited to 'sound/firewire/bebob/bebob_stream.c') diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c new file mode 100644 index 000000000..8629b14de --- /dev/null +++ b/sound/firewire/bebob/bebob_stream.c @@ -0,0 +1,996 @@ +// 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); +} -- cgit v1.2.3