| // 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) | |
| { | |
| guard(spinlock_irq)(&bebob->lock); | |
| /* user land lock this */ | |
| if (bebob->dev_lock_count < 0) | |
| return -EBUSY; | |
| /* this is the first time */ | |
| if (bebob->dev_lock_count++ == 0) | |
| snd_bebob_stream_lock_changed(bebob); | |
| return 0; | |
| } | |
| void snd_bebob_stream_lock_release(struct snd_bebob *bebob) | |
| { | |
| guard(spinlock_irq)(&bebob->lock); | |
| if (WARN_ON(bebob->dev_lock_count <= 0)) | |
| return; | |
| if (--bebob->dev_lock_count == 0) | |
| snd_bebob_stream_lock_changed(bebob); | |
| } |