| /* |
| * bebob_stream.c - a part of driver for BeBoB based devices |
| * |
| * Copyright (c) 2013-2014 Takashi Sakamoto |
| * |
| * Licensed under the terms of the GNU General Public License, version 2. |
| */ |
| |
| #include "./bebob.h" |
| |
| #define CALLBACK_TIMEOUT 1000 |
| #define FW_ISO_RESOURCE_DELAY 1000 |
| |
| /* |
| * 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 unsigned int |
| get_formation_index(unsigned int rate) |
| { |
| unsigned int i; |
| |
| for (i = 0; i < ARRAY_SIZE(snd_bebob_rate_table); i++) { |
| if (snd_bebob_rate_table[i] == rate) |
| return i; |
| } |
| 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_check_internal_clock(struct snd_bebob *bebob, bool *internal) |
| { |
| struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock; |
| u8 addr[AVC_BRIDGECO_ADDR_BYTES], input[7]; |
| unsigned int id; |
| int err = 0; |
| |
| *internal = false; |
| |
| /* 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); |
| else if (strncmp(clk_spec->labels[id], SND_BEBOB_CLOCK_INTERNAL, |
| strlen(SND_BEBOB_CLOCK_INTERNAL)) == 0) |
| *internal = true; |
| 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) { |
| *internal = true; |
| 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) { |
| *internal = true; |
| goto end; |
| } |
| |
| /* |
| * If source of clock is internal CSR, Music Sub Unit Sync Input is |
| * a destination of Music Sub Unit Sync Output. |
| */ |
| *internal = ((input[0] == AVC_BRIDGECO_PLUG_DIR_OUT) && |
| (input[1] == AVC_BRIDGECO_PLUG_MODE_SUBUNIT) && |
| (input[2] == 0x0c) && |
| (input[3] == 0x00)); |
| end: |
| return err; |
| } |
| |
| static unsigned 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; |
| } |
| s->midi_position = 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 >= AMDTP_MAX_CHANNELS_FOR_PCM) { |
| err = -ENOSYS; |
| goto end; |
| } |
| s->pcm_positions[location] = stm_pos; |
| break; |
| } |
| } |
| |
| if (type != 0x0a) |
| pcm += channels; |
| else |
| midi += channels; |
| } |
| end: |
| kfree(buf); |
| return err; |
| } |
| |
| static int |
| init_both_connections(struct snd_bebob *bebob) |
| { |
| int err; |
| |
| err = cmp_connection_init(&bebob->in_conn, |
| bebob->unit, CMP_INPUT, 0); |
| if (err < 0) |
| goto end; |
| |
| err = cmp_connection_init(&bebob->out_conn, |
| bebob->unit, CMP_OUTPUT, 0); |
| if (err < 0) |
| cmp_connection_destroy(&bebob->in_conn); |
| end: |
| 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 int |
| make_both_connections(struct snd_bebob *bebob, unsigned int rate) |
| { |
| int index, pcm_channels, midi_channels, err = 0; |
| |
| if (bebob->connected) |
| goto end; |
| |
| /* confirm params for both streams */ |
| index = get_formation_index(rate); |
| pcm_channels = bebob->tx_stream_formations[index].pcm; |
| midi_channels = bebob->tx_stream_formations[index].midi; |
| amdtp_stream_set_parameters(&bebob->tx_stream, |
| rate, pcm_channels, midi_channels * 8); |
| pcm_channels = bebob->rx_stream_formations[index].pcm; |
| midi_channels = bebob->rx_stream_formations[index].midi; |
| amdtp_stream_set_parameters(&bebob->rx_stream, |
| rate, pcm_channels, midi_channels * 8); |
| |
| /* establish connections for both streams */ |
| err = cmp_connection_establish(&bebob->out_conn, |
| amdtp_stream_get_max_payload(&bebob->tx_stream)); |
| if (err < 0) |
| goto end; |
| err = cmp_connection_establish(&bebob->in_conn, |
| amdtp_stream_get_max_payload(&bebob->rx_stream)); |
| if (err < 0) { |
| cmp_connection_break(&bebob->out_conn); |
| goto end; |
| } |
| |
| bebob->connected = true; |
| end: |
| return err; |
| } |
| |
| static void |
| break_both_connections(struct snd_bebob *bebob) |
| { |
| cmp_connection_break(&bebob->in_conn); |
| cmp_connection_break(&bebob->out_conn); |
| |
| bebob->connected = false; |
| |
| /* These models seems to be in transition state for a longer time. */ |
| if (bebob->maudio_special_quirk != NULL) |
| msleep(200); |
| } |
| |
| static void |
| destroy_both_connections(struct snd_bebob *bebob) |
| { |
| break_both_connections(bebob); |
| |
| cmp_connection_destroy(&bebob->in_conn); |
| cmp_connection_destroy(&bebob->out_conn); |
| } |
| |
| static int |
| get_sync_mode(struct snd_bebob *bebob, enum cip_flags *sync_mode) |
| { |
| /* currently this module doesn't support SYT-Match mode */ |
| *sync_mode = CIP_SYNC_TO_DEVICE; |
| return 0; |
| } |
| |
| static int |
| start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream, |
| unsigned int rate) |
| { |
| 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) |
| goto end; |
| } |
| |
| /* start amdtp stream */ |
| err = amdtp_stream_start(stream, |
| conn->resources.channel, |
| conn->speed); |
| end: |
| return err; |
| } |
| |
| int snd_bebob_stream_init_duplex(struct snd_bebob *bebob) |
| { |
| int err; |
| |
| err = init_both_connections(bebob); |
| if (err < 0) |
| goto end; |
| |
| err = amdtp_stream_init(&bebob->tx_stream, bebob->unit, |
| AMDTP_IN_STREAM, CIP_BLOCKING); |
| if (err < 0) { |
| amdtp_stream_destroy(&bebob->tx_stream); |
| destroy_both_connections(bebob); |
| goto end; |
| } |
| /* See comments in next function */ |
| init_completion(&bebob->bus_reset); |
| bebob->tx_stream.flags |= CIP_SKIP_INIT_DBC_CHECK; |
| /* |
| * At high sampling rate, M-Audio special firmware transmits empty |
| * packet with the value of dbc incremented by 8 but the others are |
| * valid to IEC 61883-1. |
| */ |
| if (bebob->maudio_special_quirk) |
| bebob->tx_stream.flags |= CIP_EMPTY_HAS_WRONG_DBC; |
| |
| err = amdtp_stream_init(&bebob->rx_stream, bebob->unit, |
| AMDTP_OUT_STREAM, CIP_BLOCKING); |
| if (err < 0) { |
| amdtp_stream_destroy(&bebob->tx_stream); |
| amdtp_stream_destroy(&bebob->rx_stream); |
| destroy_both_connections(bebob); |
| } |
| /* |
| * The firmware for these devices ignore MIDI messages in more than |
| * first 8 data blocks of an received AMDTP packet. |
| */ |
| if (bebob->spec == &maudio_fw410_spec || |
| bebob->spec == &maudio_special_spec) |
| bebob->rx_stream.rx_blocks_for_midi = 8; |
| end: |
| return err; |
| } |
| |
| int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, unsigned int rate) |
| { |
| struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate; |
| struct amdtp_stream *master, *slave; |
| atomic_t *slave_substreams; |
| enum cip_flags sync_mode; |
| unsigned int curr_rate; |
| bool updated = false; |
| int err = 0; |
| |
| /* |
| * Normal BeBoB firmware has a quirk at bus reset to transmits packets |
| * with discontinuous value in dbc field. |
| * |
| * This 'struct completion' is used to call .update() at first to update |
| * connections/streams. Next following codes handle streaming error. |
| */ |
| if (amdtp_streaming_error(&bebob->tx_stream)) { |
| if (completion_done(&bebob->bus_reset)) |
| reinit_completion(&bebob->bus_reset); |
| |
| updated = (wait_for_completion_interruptible_timeout( |
| &bebob->bus_reset, |
| msecs_to_jiffies(FW_ISO_RESOURCE_DELAY)) > 0); |
| } |
| |
| mutex_lock(&bebob->mutex); |
| |
| /* Need no substreams */ |
| if (atomic_read(&bebob->playback_substreams) == 0 && |
| atomic_read(&bebob->capture_substreams) == 0) |
| goto end; |
| |
| err = get_sync_mode(bebob, &sync_mode); |
| if (err < 0) |
| goto end; |
| if (sync_mode == CIP_SYNC_TO_DEVICE) { |
| master = &bebob->tx_stream; |
| slave = &bebob->rx_stream; |
| slave_substreams = &bebob->playback_substreams; |
| } else { |
| master = &bebob->rx_stream; |
| slave = &bebob->tx_stream; |
| slave_substreams = &bebob->capture_substreams; |
| } |
| |
| /* |
| * Considering JACK/FFADO streaming: |
| * TODO: This can be removed hwdep functionality becomes popular. |
| */ |
| err = check_connection_used_by_others(bebob, master); |
| if (err < 0) |
| goto end; |
| |
| /* |
| * packet queueing error or detecting discontinuity |
| * |
| * At bus reset, connections should not be broken here. So streams need |
| * to be re-started. This is a reason to use SKIP_INIT_DBC_CHECK flag. |
| */ |
| if (amdtp_streaming_error(master)) |
| amdtp_stream_stop(master); |
| if (amdtp_streaming_error(slave)) |
| amdtp_stream_stop(slave); |
| if (!updated && |
| !amdtp_stream_running(master) && !amdtp_stream_running(slave)) |
| break_both_connections(bebob); |
| |
| /* stop streams if rate is different */ |
| err = rate_spec->get(bebob, &curr_rate); |
| if (err < 0) { |
| dev_err(&bebob->unit->device, |
| "fail to get sampling rate: %d\n", err); |
| goto end; |
| } |
| if (rate == 0) |
| rate = curr_rate; |
| if (rate != curr_rate) { |
| amdtp_stream_stop(master); |
| amdtp_stream_stop(slave); |
| break_both_connections(bebob); |
| } |
| |
| /* master should be always running */ |
| if (!amdtp_stream_running(master)) { |
| amdtp_stream_set_sync(sync_mode, master, slave); |
| bebob->master = master; |
| |
| /* |
| * 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. |
| */ |
| if (bebob->maudio_special_quirk == NULL) { |
| err = rate_spec->set(bebob, rate); |
| if (err < 0) { |
| dev_err(&bebob->unit->device, |
| "fail to set sampling rate: %d\n", |
| err); |
| goto end; |
| } |
| } |
| |
| err = make_both_connections(bebob, rate); |
| if (err < 0) |
| goto end; |
| |
| err = start_stream(bebob, master, rate); |
| if (err < 0) { |
| dev_err(&bebob->unit->device, |
| "fail to run AMDTP master stream:%d\n", err); |
| break_both_connections(bebob); |
| goto end; |
| } |
| |
| /* |
| * NOTE: |
| * The firmware customized by M-Audio uses these commands to |
| * start transmitting stream. This is not usual way. |
| */ |
| if (bebob->maudio_special_quirk != NULL) { |
| err = rate_spec->set(bebob, rate); |
| if (err < 0) { |
| dev_err(&bebob->unit->device, |
| "fail to ensure sampling rate: %d\n", |
| err); |
| amdtp_stream_stop(master); |
| break_both_connections(bebob); |
| goto end; |
| } |
| } |
| |
| /* wait first callback */ |
| if (!amdtp_stream_wait_callback(master, CALLBACK_TIMEOUT)) { |
| amdtp_stream_stop(master); |
| break_both_connections(bebob); |
| err = -ETIMEDOUT; |
| goto end; |
| } |
| } |
| |
| /* start slave if needed */ |
| if (atomic_read(slave_substreams) > 0 && !amdtp_stream_running(slave)) { |
| err = start_stream(bebob, slave, rate); |
| if (err < 0) { |
| dev_err(&bebob->unit->device, |
| "fail to run AMDTP slave stream:%d\n", err); |
| amdtp_stream_stop(master); |
| break_both_connections(bebob); |
| goto end; |
| } |
| |
| /* wait first callback */ |
| if (!amdtp_stream_wait_callback(slave, CALLBACK_TIMEOUT)) { |
| amdtp_stream_stop(slave); |
| amdtp_stream_stop(master); |
| break_both_connections(bebob); |
| err = -ETIMEDOUT; |
| } |
| } |
| end: |
| mutex_unlock(&bebob->mutex); |
| return err; |
| } |
| |
| void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob) |
| { |
| struct amdtp_stream *master, *slave; |
| atomic_t *master_substreams, *slave_substreams; |
| |
| if (bebob->master == &bebob->rx_stream) { |
| slave = &bebob->tx_stream; |
| master = &bebob->rx_stream; |
| slave_substreams = &bebob->capture_substreams; |
| master_substreams = &bebob->playback_substreams; |
| } else { |
| slave = &bebob->rx_stream; |
| master = &bebob->tx_stream; |
| slave_substreams = &bebob->playback_substreams; |
| master_substreams = &bebob->capture_substreams; |
| } |
| |
| mutex_lock(&bebob->mutex); |
| |
| if (atomic_read(slave_substreams) == 0) { |
| amdtp_stream_pcm_abort(slave); |
| amdtp_stream_stop(slave); |
| |
| if (atomic_read(master_substreams) == 0) { |
| amdtp_stream_pcm_abort(master); |
| amdtp_stream_stop(master); |
| break_both_connections(bebob); |
| } |
| } |
| |
| mutex_unlock(&bebob->mutex); |
| } |
| |
| void snd_bebob_stream_update_duplex(struct snd_bebob *bebob) |
| { |
| /* vs. XRUN recovery due to discontinuity at bus reset */ |
| mutex_lock(&bebob->mutex); |
| |
| if ((cmp_connection_update(&bebob->in_conn) < 0) || |
| (cmp_connection_update(&bebob->out_conn) < 0)) { |
| amdtp_stream_pcm_abort(&bebob->rx_stream); |
| amdtp_stream_pcm_abort(&bebob->tx_stream); |
| amdtp_stream_stop(&bebob->rx_stream); |
| amdtp_stream_stop(&bebob->tx_stream); |
| break_both_connections(bebob); |
| } else { |
| amdtp_stream_update(&bebob->rx_stream); |
| amdtp_stream_update(&bebob->tx_stream); |
| } |
| |
| /* wake up stream_start_duplex() */ |
| if (!completion_done(&bebob->bus_reset)) |
| complete_all(&bebob->bus_reset); |
| |
| mutex_unlock(&bebob->mutex); |
| } |
| |
| void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob) |
| { |
| mutex_lock(&bebob->mutex); |
| |
| amdtp_stream_pcm_abort(&bebob->rx_stream); |
| amdtp_stream_pcm_abort(&bebob->tx_stream); |
| |
| amdtp_stream_stop(&bebob->rx_stream); |
| amdtp_stream_stop(&bebob->tx_stream); |
| |
| amdtp_stream_destroy(&bebob->rx_stream); |
| amdtp_stream_destroy(&bebob->tx_stream); |
| |
| destroy_both_connections(bebob); |
| |
| mutex_unlock(&bebob->mutex); |
| } |
| |
| /* |
| * 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 > AMDTP_MAX_CHANNELS_FOR_PCM || |
| formation[i].midi > AMDTP_MAX_CHANNELS_FOR_MIDI) |
| return -ENOSYS; |
| |
| return 0; |
| } |
| |
| static int |
| fill_stream_formations(struct snd_bebob *bebob, enum avc_bridgeco_plug_dir dir, |
| unsigned short pid) |
| { |
| u8 *buf; |
| struct snd_bebob_stream_formation *formations; |
| unsigned int len, eid; |
| u8 addr[AVC_BRIDGECO_ADDR_BYTES]; |
| int err; |
| |
| buf = kmalloc(FORMAT_MAXIMUM_LENGTH, GFP_KERNEL); |
| if (buf == NULL) |
| return -ENOMEM; |
| |
| if (dir == AVC_BRIDGECO_PLUG_DIR_IN) |
| formations = bebob->rx_stream_formations; |
| else |
| formations = bebob->tx_stream_formations; |
| |
| for (eid = 0; eid < SND_BEBOB_STRM_FMT_ENTRIES; eid++) { |
| len = FORMAT_MAXIMUM_LENGTH; |
| avc_bridgeco_fill_unit_addr(addr, dir, |
| AVC_BRIDGECO_PLUG_UNIT_ISOC, pid); |
| 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 %s plug %d:%d\n", |
| eid, |
| (dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" : |
| "out", |
| pid, err); |
| break; |
| } |
| |
| err = parse_stream_formation(buf, len, formations); |
| if (err < 0) |
| break; |
| } |
| |
| kfree(buf); |
| 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) |
| { |
| struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock; |
| u8 plugs[AVC_PLUG_INFO_BUF_BYTES], addr[AVC_BRIDGECO_ADDR_BYTES]; |
| enum avc_bridgeco_plug_type type; |
| unsigned int i; |
| 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; |
| } |
| |
| avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, |
| AVC_BRIDGECO_PLUG_UNIT_ISOC, 0); |
| err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); |
| if (err < 0) { |
| dev_err(&bebob->unit->device, |
| "fail to get type for isoc in plug 0: %d\n", err); |
| goto end; |
| } else if (type != AVC_BRIDGECO_PLUG_TYPE_ISOC) { |
| err = -ENOSYS; |
| goto end; |
| } |
| err = fill_stream_formations(bebob, AVC_BRIDGECO_PLUG_DIR_IN, 0); |
| if (err < 0) |
| goto end; |
| |
| avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_OUT, |
| AVC_BRIDGECO_PLUG_UNIT_ISOC, 0); |
| err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); |
| if (err < 0) { |
| dev_err(&bebob->unit->device, |
| "fail to get type for isoc out plug 0: %d\n", err); |
| goto end; |
| } else if (type != AVC_BRIDGECO_PLUG_TYPE_ISOC) { |
| err = -ENOSYS; |
| goto end; |
| } |
| err = fill_stream_formations(bebob, AVC_BRIDGECO_PLUG_DIR_OUT, 0); |
| if (err < 0) |
| goto end; |
| |
| /* count external input plugs for MIDI */ |
| bebob->midi_input_ports = 0; |
| for (i = 0; i < plugs[2]; i++) { |
| avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, |
| AVC_BRIDGECO_PLUG_UNIT_EXT, i); |
| err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); |
| if (err < 0) { |
| dev_err(&bebob->unit->device, |
| "fail to get type for external in plug %d: %d\n", |
| i, err); |
| goto end; |
| } else if (type == AVC_BRIDGECO_PLUG_TYPE_MIDI) { |
| bebob->midi_input_ports++; |
| } |
| } |
| |
| /* count external output plugs for MIDI */ |
| bebob->midi_output_ports = 0; |
| for (i = 0; i < plugs[3]; i++) { |
| avc_bridgeco_fill_unit_addr(addr, AVC_BRIDGECO_PLUG_DIR_OUT, |
| AVC_BRIDGECO_PLUG_UNIT_EXT, i); |
| err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); |
| if (err < 0) { |
| dev_err(&bebob->unit->device, |
| "fail to get type for external out plug %d: %d\n", |
| i, err); |
| goto end; |
| } else if (type == AVC_BRIDGECO_PLUG_TYPE_MIDI) { |
| bebob->midi_output_ports++; |
| } |
| } |
| |
| /* 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); |
| } |