From patchwork Wed Mar 5 10:48:16 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 3773011 X-Patchwork-Delegate: tiwai@suse.de Return-Path: X-Original-To: patchwork-alsa-devel@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id DD9899F1EE for ; Wed, 5 Mar 2014 11:08:12 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id CF0A02024C for ; Wed, 5 Mar 2014 11:08:10 +0000 (UTC) Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.kernel.org (Postfix) with ESMTP id 7F2D520237 for ; Wed, 5 Mar 2014 11:08:07 +0000 (UTC) Received: by alsa0.perex.cz (Postfix, from userid 1000) id 07C2D265D4D; Wed, 5 Mar 2014 12:08:04 +0100 (CET) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 Received: from alsa0.perex.cz (localhost [IPv6:::1]) by alsa0.perex.cz (Postfix) with ESMTP id E975E265AAF; Wed, 5 Mar 2014 11:55:09 +0100 (CET) X-Original-To: alsa-devel@alsa-project.org Delivered-To: alsa-devel@alsa-project.org Received: by alsa0.perex.cz (Postfix, from userid 1000) id 5A7F2265903; Wed, 5 Mar 2014 11:55:03 +0100 (CET) Received: from smtp311.phy.lolipop.jp (smtp311.phy.lolipop.jp [210.157.22.79]) by alsa0.perex.cz (Postfix) with ESMTP id 1DF0F265903 for ; Wed, 5 Mar 2014 11:49:20 +0100 (CET) Received: from smtp311.phy.lolipop.lan (HELO smtp311.phy.lolipop.jp) (172.17.1.11) (smtp-auth username m12129643-o-takashi, mechanism plain) by smtp311.phy.lolipop.jp (qpsmtpd/0.82) with ESMTPA; Wed, 05 Mar 2014 19:49:19 +0900 Received: from 127.0.0.1 (127.0.0.1) by smtp311.phy.lolipop.jp (LOLIPOP-Fsecure); Wed, 05 Mar 2014 19:48:28 +0900 (JST) X-Virus-Status: clean(LOLIPOP-Fsecure) From: Takashi Sakamoto To: clemens@ladisch.de, tiwai@suse.de, perex@perex.cz Date: Wed, 5 Mar 2014 19:48:16 +0900 Message-Id: <1394016507-15761-29-git-send-email-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 1.8.3.2 In-Reply-To: <1394016507-15761-1-git-send-email-o-takashi@sakamocchi.jp> References: <5316963F.1000206@sakamocchi.jp> <1394016507-15761-1-git-send-email-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org, ffado-devel@lists.sf.net Subject: [alsa-devel] [PATCH 28/39] bebob: Add commands and connections/streams management X-BeenThere: alsa-devel@alsa-project.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Alsa-devel mailing list for ALSA developers - http://www.alsa-project.org" List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: alsa-devel-bounces@alsa-project.org Sender: alsa-devel-bounces@alsa-project.org X-Virus-Scanned: ClamAV using ClamSMTP This commit adds management functionality for connections and streams. BeBoB uses CMP to manage connections and uses AMDTP for streams. This commit also adds some BridgeCo's AV/C extension commands. There are many BridgeCo's AV/C extension commands but this commit adds below commands to get device's capability and status: 1.Extended Plug Info commands - Plug Channel Position Specific Data - Plug Type Specific Data - Cluster(Section) Info Specific Data - Plug Input Specific Data 2.Extended Stream Format Information commands - Extended Stream Format Information Command - Single Request For Extended Plug Info commands for Cluster Info Specific Data, I pick up 'section' instead of 'cluster' from document to prevent from misunderstanding because 'cluster' is also used in IEC 61883-6. --- sound/firewire/bebob/Makefile | 2 +- sound/firewire/bebob/bebob.c | 10 + sound/firewire/bebob/bebob.h | 122 ++++++ sound/firewire/bebob/bebob_command.c | 273 ++++++++++++ sound/firewire/bebob/bebob_stream.c | 784 +++++++++++++++++++++++++++++++++++ 5 files changed, 1190 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/bebob/bebob_command.c create mode 100644 sound/firewire/bebob/bebob_stream.c diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile index c6f0141..5cece62 100644 --- a/sound/firewire/bebob/Makefile +++ b/sound/firewire/bebob/Makefile @@ -1,2 +1,2 @@ -snd-bebob-objs := bebob.o +snd-bebob-objs := bebob_command.o bebob_stream.o bebob.o obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index 3626557..39fe17c 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -157,6 +157,14 @@ bebob_probe(struct fw_unit *unit, if (err < 0) goto error; + err = snd_bebob_stream_discover(bebob); + if (err < 0) + goto error; + + err = snd_bebob_stream_init_duplex(bebob); + if (err < 0) + goto error; + err = snd_card_register(card); if (err < 0) { snd_card_free(card); @@ -179,12 +187,14 @@ bebob_update(struct fw_unit *unit) { struct snd_bebob *bebob = dev_get_drvdata(&unit->device); fcp_bus_reset(bebob->unit); + snd_bebob_stream_update_duplex(bebob); } static void bebob_remove(struct fw_unit *unit) { struct snd_bebob *bebob = dev_get_drvdata(&unit->device); + snd_bebob_stream_destroy_duplex(bebob); snd_card_disconnect(bebob->card); snd_card_free_when_closed(bebob->card); } diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index 173f4558..b3a641c 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -23,11 +23,25 @@ #include "../lib.h" #include "../fcp.h" +#include "../packets-buffer.h" +#include "../iso-resources.h" +#include "../amdtp.h" +#include "../cmp.h" /* basic register addresses on DM1000 */ #define BEBOB_ADDR_REG_INFO 0xffffc8020000 #define BEBOB_ADDR_REG_REQ 0xffffc8021000 +struct snd_bebob; + +#define SND_BEBOB_STRM_FMT_ENTRIES 7 +struct snd_bebob_stream_formation { + unsigned int pcm; + unsigned int midi; +}; +/* this is a lookup table for index of stream formations */ +extern const unsigned int snd_bebob_rate_table[SND_BEBOB_STRM_FMT_ENTRIES]; + struct snd_bebob { struct snd_card *card; struct fw_unit *unit; @@ -35,6 +49,23 @@ struct snd_bebob { struct mutex mutex; spinlock_t lock; + + unsigned int midi_input_ports; + unsigned int midi_output_ports; + + struct cmp_connection out_conn; + struct amdtp_stream tx_stream; + struct cmp_connection in_conn; + struct amdtp_stream rx_stream; + unsigned int capture_substreams; + unsigned int playback_substreams; + + struct snd_bebob_stream_formation + tx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES]; + struct snd_bebob_stream_formation + rx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES]; + + int sync_input_plug; }; static inline int @@ -53,6 +84,97 @@ snd_bebob_read_quad(struct fw_unit *unit, u64 addr, u32 *buf) (void *)buf, sizeof(u32), 0); } +/* + * AVC command extensions, AV/C Unit and Subunit, Revision 17 + * (Nov 2003, BridgeCo) + */ +#define AVC_BRIDGECO_ADDR_BYTES 6 +enum avc_bridgeco_plug_dir { + AVC_BRIDGECO_PLUG_DIR_IN = 0x00, + AVC_BRIDGECO_PLUG_DIR_OUT = 0x01 +}; +enum avc_bridgeco_plug_mode { + AVC_BRIDGECO_PLUG_MODE_UNIT = 0x00, + AVC_BRIDGECO_PLUG_MODE_SUBUNIT = 0x01, + AVC_BRIDGECO_PLUG_MODE_FUNCTION_BLOCK = 0x02 +}; +enum avc_bridgeco_plug_unit { + AVC_BRIDGECO_PLUG_UNIT_ISOC = 0x00, + AVC_BRIDGECO_PLUG_UNIT_EXT = 0x01, + AVC_BRIDGECO_PLUG_UNIT_ASYNC = 0x02 +}; +enum avc_bridgeco_plug_type { + AVC_BRIDGECO_PLUG_TYPE_ISOC = 0x00, + AVC_BRIDGECO_PLUG_TYPE_ASYNC = 0x01, + AVC_BRIDGECO_PLUG_TYPE_MIDI = 0x02, + AVC_BRIDGECO_PLUG_TYPE_SYNC = 0x03, + AVC_BRIDGECO_PLUG_TYPE_ANA = 0x04, + AVC_BRIDGECO_PLUG_TYPE_DIG = 0x05 +}; +static inline void +avc_bridgeco_fill_unit_addr(u8 buf[AVC_BRIDGECO_ADDR_BYTES], + enum avc_bridgeco_plug_dir dir, + enum avc_bridgeco_plug_unit unit, + unsigned int pid) +{ + buf[0] = 0xff; /* Unit */ + buf[1] = dir; + buf[2] = AVC_BRIDGECO_PLUG_MODE_UNIT; + buf[3] = unit; + buf[4] = 0xff & pid; + buf[5] = 0xff; /* reserved */ +} +static inline void +avc_bridgeco_fill_subunit_addr(u8 buf[AVC_BRIDGECO_ADDR_BYTES], + unsigned int mode, + enum avc_bridgeco_plug_dir dir, + unsigned int pid) +{ + buf[0] = 0xff & mode; /* Subunit */ + buf[1] = dir; + buf[2] = AVC_BRIDGECO_PLUG_MODE_SUBUNIT; + buf[3] = 0xff & pid; + buf[4] = 0xff; /* reserved */ + buf[5] = 0xff; /* reserved */ +} +int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit, + u8 add[AVC_BRIDGECO_ADDR_BYTES], + u8 *buf, unsigned int len); +int avc_bridgeco_get_plug_type(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + enum avc_bridgeco_plug_type *type); +int avc_bridgeco_get_plug_section_type(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + unsigned int section_id, u8 *ctype); +int avc_bridgeco_get_plug_input(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + u8 input[7]); +int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + unsigned int entryid, u8 *buf, + unsigned int *len); + +int snd_bebob_get_rate(struct snd_bebob *bebob, unsigned int *rate, + enum avc_general_plug_dir dir); +int snd_bebob_set_rate(struct snd_bebob *bebob, unsigned int rate, + enum avc_general_plug_dir dir); + +/* for AMDTP streaming */ +int snd_bebob_stream_get_rate(struct snd_bebob *bebob, unsigned int *rate); +int snd_bebob_stream_set_rate(struct snd_bebob *bebob, unsigned int rate); +int snd_bebob_stream_check_internal_clock(struct snd_bebob *bebob, + bool *internal); +int snd_bebob_stream_discover(struct snd_bebob *bebob); +int snd_bebob_stream_map(struct snd_bebob *bebob, + struct amdtp_stream *stream); +int snd_bebob_stream_init_duplex(struct snd_bebob *bebob); +int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, + struct amdtp_stream *stream, + unsigned int sampling_rate); +int snd_bebob_stream_stop_duplex(struct snd_bebob *bebob); +void snd_bebob_stream_update_duplex(struct snd_bebob *bebob); +void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob); + #define SND_BEBOB_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/bebob/bebob_command.c b/sound/firewire/bebob/bebob_command.c new file mode 100644 index 0000000..60ba13d --- /dev/null +++ b/sound/firewire/bebob/bebob_command.c @@ -0,0 +1,273 @@ +/* + * bebob_command.c - driver for BeBoB based devices + * + * Copyright (c) 2013 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +#define BEBOB_COMMAND_MAX_TRIAL 3 +#define BEBOB_COMMAND_WAIT_MSEC 100 + +static inline void +avc_bridgeco_fill_command_base(u8 *buf, unsigned int ctype, unsigned int opcode, + unsigned int subfunction, + u8 addr[AVC_BRIDGECO_ADDR_BYTES]) +{ + buf[0] = 0x7 & ctype; + buf[1] = addr[0]; + buf[2] = 0xff & opcode; + buf[3] = 0xff & subfunction; + buf[4] = addr[1]; + buf[5] = addr[2]; + buf[6] = addr[3]; + buf[7] = addr[4]; + buf[8] = addr[5]; +} + +int avc_bridgeco_get_plug_type(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + enum avc_bridgeco_plug_type *type) +{ + u8 *buf; + int err; + + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + /* status for plug info with bridgeco extension */ + avc_bridgeco_fill_command_base(buf, 0x01, 0x02, 0xc0, addr); + buf[9] = 0x00; /* info type is 'plug type' */ + buf[10] = 0xff; /* plug type in response */ + + /* do transaction and check buf[1-7,9] are the same against command */ + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(9)); + if (err < 0) + goto end; + /* IMPLEMENTED/STABLE is OK */ + else if ((err < 6) || (buf[0] != 0x0c)) { + err = -EIO; + goto end; + } + + *type = buf[10]; + err = 0; +end: + kfree(buf); + return err; +} + +int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + u8 *buf, unsigned int len) +{ + unsigned int trial; + int err; + + /* check given buffer */ + if ((buf == NULL) || (len < 256)) { + err = -EINVAL; + goto end; + } + + /* status for plug info with bridgeco extension */ + avc_bridgeco_fill_command_base(buf, 0x01, 0x02, 0xc0, addr); + + /* info type is 'channel position' */ + buf[9] = 0x03; + + /* + * NOTE: + * M-Audio Firewire 410 returns 0x09 (ACCEPTED) just after changing + * signal format even if this command asks STATUS. This is not in + * AV/C command specification. + */ + for (trial = 0; trial < BEBOB_COMMAND_MAX_TRIAL; trial++) { + /* do transaction and check buf[1-7,9] are the same */ + err = fcp_avc_transaction(unit, buf, 12, buf, 256, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | + BIT(5) | BIT(6) | BIT(7) | BIT(9)); + if (err < 0) + goto end; + else if (err < 6) { + err = -EIO; + goto end; + } else if (buf[0] == 0x0c) + break; + else if (trial < BEBOB_COMMAND_MAX_TRIAL) + msleep(BEBOB_COMMAND_WAIT_MSEC); + else { + err = -EIO; + goto end; + } + } + + /* strip command header */ + memmove(buf, buf + 10, err - 10); + err = 0; +end: + return err; +} + +int avc_bridgeco_get_plug_section_type(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + unsigned int section_id, u8 *type) +{ + u8 *buf; + int err; + + /* section info includes charactors but this module don't need it */ + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + /* status for plug info with bridgeco extension */ + avc_bridgeco_fill_command_base(buf, 0x01, 0x02, 0xc0, addr); + + buf[9] = 0x07; /* info type is 'section info' */ + buf[10] = 0xff & (section_id + 1); /* section id */ + buf[11] = 0x00; /* type in response */ + + /* do transaction and check buf[1-7,9,10] are the same */ + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(9) | BIT(10)); + if (err < 0) + goto end; + else if ((err < 12) && (buf[0] != 0x0c)) { + err = -EIO; + goto end; + } + + *type = buf[11]; + err = 0; +end: + kfree(buf); + return err; +} + +int avc_bridgeco_get_plug_input(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 input[7]) +{ + int err; + u8 *buf; + + buf = kzalloc(18, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + /* status for plug info with bridgeco extension */ + avc_bridgeco_fill_command_base(buf, 0x01, 0x02, 0xc0, addr); + + /* info type is 'Plug Input Specific Data' */ + buf[9] = 0x05; + + /* do transaction and check buf[1-7] are the same */ + err = fcp_avc_transaction(unit, buf, 16, buf, 16, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7)); + if (err < 0) + goto end; + else if ((err < 18) && (buf[0] != 0x0c)) { + err = -EIO; + goto end; + } + + memcpy(input, buf + 10, 5); +end: + kfree(buf); + return err; +} + +int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit, + u8 addr[AVC_BRIDGECO_ADDR_BYTES], + unsigned int entryid, u8 *buf, + unsigned int *len) +{ + int err; + + /* check given buffer */ + if ((buf == NULL) || (*len < 12)) { + err = -EINVAL; + goto end; + } + + /* status for plug info with bridgeco extension */ + avc_bridgeco_fill_command_base(buf, 0x01, 0x2f, 0xc1, addr); + + buf[9] = 0xff; /* stream status in response */ + buf[10] = 0xff & entryid; /* entry ID */ + + /* do transaction and check buf[1-7,10] are the same against command */ + err = fcp_avc_transaction(unit, buf, 12, buf, *len, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(10)); + if (err < 0) + goto end; + /* reach the end of entries */ + else if (buf[0] == 0x0a) { + err = 0; + *len = 0; + goto end; + } else if (buf[0] != 0x0c) { + err = -EINVAL; + goto end; + /* the header of this command is 11 bytes */ + } else if (err < 12) { + err = -EIO; + goto end; + } else if (buf[10] != entryid) { + err = -EIO; + goto end; + } + + /* strip command header */ + memmove(buf, buf + 11, err - 11); + *len = err - 11; + err = 0; +end: + return err; +} + +int snd_bebob_get_rate(struct snd_bebob *bebob, unsigned int *rate, + enum avc_general_plug_dir dir) +{ + int err; + + err = avc_general_get_sig_fmt(bebob->unit, rate, dir, 0); + if (err < 0) + goto end; + + /* IMPLEMENTED/STABLE is OK */ + if (err != 0x0c) { + dev_err(&bebob->unit->device, + "failed to get sampling rate\n"); + err = -EIO; + } +end: + return err; +} + +int snd_bebob_set_rate(struct snd_bebob *bebob, unsigned int rate, + enum avc_general_plug_dir dir) +{ + int err; + + err = avc_general_set_sig_fmt(bebob->unit, rate, dir, 0); + if (err < 0) + goto end; + + /* ACCEPTED or INTERIM is OK */ + if ((err != 0x0f) && (err != 0x09)) { + dev_err(&bebob->unit->device, + "failed to set sampling rate\n"); + err = -EIO; + } +end: + return err; +} diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c new file mode 100644 index 0000000..497d1c6 --- /dev/null +++ b/sound/firewire/bebob/bebob_stream.c @@ -0,0 +1,784 @@ +/* + * bebob_stream.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +#define CALLBACK_TIMEOUT 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 streams. 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 < sizeof(snd_bebob_rate_table); i++) { + if (snd_bebob_rate_table[i] == rate) + return i; + } + return -1; +} + +int +snd_bebob_stream_get_rate(struct snd_bebob *bebob, unsigned int *curr_rate) +{ + unsigned int tx_rate, rx_rate; + int err; + + err = snd_bebob_get_rate(bebob, &tx_rate, AVC_GENERAL_PLUG_DIR_OUT); + if (err < 0) + goto end; + + err = snd_bebob_get_rate(bebob, &rx_rate, AVC_GENERAL_PLUG_DIR_IN); + 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 = snd_bebob_set_rate(bebob, rx_rate, AVC_GENERAL_PLUG_DIR_IN); +end: + return err; +} + +int +snd_bebob_stream_set_rate(struct snd_bebob *bebob, unsigned int rate) +{ + int err; + + err = snd_bebob_set_rate(bebob, rate, AVC_GENERAL_PLUG_DIR_OUT); + if (err < 0) + goto end; + + err = snd_bebob_set_rate(bebob, rate, AVC_GENERAL_PLUG_DIR_IN); +end: + return err; +} + +int +snd_bebob_stream_check_internal_clock(struct snd_bebob *bebob, bool *internal) +{ + u8 addr[AVC_BRIDGECO_ADDR_BYTES], input[7]; + int err = 0; + + *internal = false; + + /* + * 1.The device don't support for switching source of clock + * then assumed to use internal clock always + */ + if (bebob->sync_input_plug < 0) { + *internal = true; + goto end; + } + + /* + * 2.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_subunit_addr(addr, 0x60, AVC_BRIDGECO_PLUG_DIR_IN, + bebob->sync_input_plug); + err = avc_bridgeco_get_plug_input(bebob->unit, addr, input); + if (err < 0) + 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) + 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) + 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; + + 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; + + /* 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); +end: + return err; +} + +static void +break_both_connections(struct snd_bebob *bebob) +{ + cmp_connection_break(&bebob->in_conn); + cmp_connection_break(&bebob->out_conn); + return; +} + +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_roles(struct snd_bebob *bebob, enum cip_flags *sync_mode, + struct amdtp_stream **master, struct amdtp_stream **slave) +{ + /* currently this module doesn't support SYT-Match mode */ + *sync_mode = CIP_SYNC_TO_DEVICE; + *master = &bebob->tx_stream; + *slave = &bebob->rx_stream; + + 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 */ + 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) { + destroy_both_connections(bebob); + goto end; + } + + err = amdtp_stream_init(&bebob->rx_stream, bebob->unit, + AMDTP_OUT_STREAM, CIP_BLOCKING); + if (err < 0) { + amdtp_stream_destroy(&bebob->tx_stream); + destroy_both_connections(bebob); + } +end: + return err; +} + +int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, + struct amdtp_stream *request, + unsigned int rate) +{ + struct amdtp_stream *master, *slave; + enum cip_flags sync_mode; + unsigned int curr_rate; + bool slave_flag; + int err; + + mutex_lock(&bebob->mutex); + + err = get_roles(bebob, &sync_mode, &master, &slave); + if (err < 0) + goto end; + + /* + * 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; + + /* need to touch slave stream */ + slave_flag = (request == slave) || amdtp_stream_running(slave); + + /* packet queueing error */ + if (amdtp_streaming_error(slave)) + amdtp_stream_stop(slave); + if (amdtp_streaming_error(master)) + amdtp_stream_stop(master); + + /* stop streams if rate is different */ + err = snd_bebob_stream_get_rate(bebob, &curr_rate); + if (err < 0) + goto end; + if (rate == 0) + rate = curr_rate; + if (rate != curr_rate) { + amdtp_stream_stop(slave); + amdtp_stream_stop(master); + break_both_connections(bebob); + } + + /* master should be always running */ + if (!amdtp_stream_running(master)) { + amdtp_stream_set_sync(sync_mode, master, slave); + + /* + * NOTE: + * If establishing connections at first, Yamaha GO46 + * (and maybe Terratec X24) don't generate sound. + */ + err = snd_bebob_stream_set_rate(bebob, rate); + if (err < 0) + 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; + } + + /* 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 (slave_flag && !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; +} + +int snd_bebob_stream_stop_duplex(struct snd_bebob *bebob) +{ + struct amdtp_stream *master, *slave; + enum cip_flags sync_mode; + unsigned int slave_substreams; + int err; + + mutex_lock(&bebob->mutex); + + err = get_roles(bebob, &sync_mode, &master, &slave); + if (err < 0) + goto end; + + if (slave == &bebob->tx_stream) + slave_substreams = bebob->capture_substreams; + else + slave_substreams = bebob->playback_substreams; + + if (slave_substreams > 0) + goto end; + + amdtp_stream_stop(slave); + + if ((bebob->capture_substreams > 0) || (bebob->playback_substreams > 0)) + goto end; + + amdtp_stream_stop(master); + break_both_connections(bebob); +end: + mutex_unlock(&bebob->mutex); + return err; +} + +void snd_bebob_stream_update_duplex(struct snd_bebob *bebob) +{ + 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); + mutex_lock(&bebob->mutex); + amdtp_stream_stop(&bebob->rx_stream); + amdtp_stream_stop(&bebob->tx_stream); + break_both_connections(bebob); + mutex_unlock(&bebob->mutex); + } else { + amdtp_stream_update(&bebob->rx_stream); + amdtp_stream_update(&bebob->tx_stream); + } +} + +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); + 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) + */ +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 < sizeof(bridgeco_freq_table); i++) { + if (buf[2] == bridgeco_freq_table[i]) + break; + } + if (i == sizeof(bridgeco_freq_table)) + return -ENOSYS; + + for (e = 0; e < buf[4]; e++) { + channels = buf[5 + e * 2]; + format = buf[6 + e * 2]; + + switch (format) { + /* PCM for IEC 60958-3 */ + case 0x00: + /* PCM for Multi bit linear audio (raw) */ + case 0x06: + formation[i].pcm += channels; + break; + /* MIDI comformant (MMA/AMEI RP-027) */ + case 0x0d: + formation[i].midi += channels; + break; + /* PCM for Multi bit linear audio (DVD-audio) */ + case 0x07: + /* IEC 61937-3 to 7 */ + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + /* One Bit Audio */ + case 0x08: /* (Plain) Raw */ + case 0x09: /* (Plain) SACD */ + case 0x0a: /* (Encoded) Raw */ + case 0x0b: /* (ENcoded) SACD */ + /* High precision Multi-bit Linear Audio */ + case 0x0c: + /* Synchronization Stream (Stereo Raw audio) */ + case 0x40: + /* Don't care */ + case 0xff: + default: + return -ENOSYS; /* not supported */ + } + } + + 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; + + memset(buf, 0, len); + avc_bridgeco_fill_unit_addr(addr, dir, + AVC_BRIDGECO_PLUG_UNIT_ISOC, pid); + err = avc_bridgeco_get_plug_strm_fmt(bebob->unit, addr, + eid, buf, &len); + if (err < 0) + goto end; + else if (len < 3) + break; + + /* parse and set stream formation */ + err = parse_stream_formation(buf, len, formations); + if (err < 0) + goto end; + } +end: + kfree(buf); + return err; +} + +static int +seek_msu_sync_input_plug(struct snd_bebob *bebob) +{ + u8 plugs[AVC_PLUG_INFO_BUF_COUNT], addr[AVC_BRIDGECO_ADDR_BYTES]; + unsigned int i, type; + int err; + + /* get information about Music Sub Unit */ + err = avc_general_get_plug_info(bebob->unit, 0x0c, 0x00, 0x00, plugs); + if (err < 0) + goto end; + + /* seek destination plugs for 'MSU sync input' */ + bebob->sync_input_plug = -1; + for (i = 0; i < plugs[0]; i++) { + avc_bridgeco_fill_subunit_addr(addr, 0x60, + AVC_BRIDGECO_PLUG_DIR_IN, i); + err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type); + if (err < 0) + goto end; + + if (type == AVC_BRIDGECO_PLUG_TYPE_SYNC) + bebob->sync_input_plug = i; + } +end: + return err; +} + +/* In this function, 2 means input and output */ +int snd_bebob_stream_discover(struct snd_bebob *bebob) +{ + u8 plugs[AVC_PLUG_INFO_BUF_COUNT], 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) + goto end; + + /* + * This module supports one ISOC input plug and one ISOC output plug + * then ignores the others. + */ + if (plugs[0] == 0) { + err = -EIO; + 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) + goto end; + else if (type != AVC_BRIDGECO_PLUG_TYPE_ISOC) { + err = -EIO; + goto end; + } + + if (plugs[1] == 0) { + err = -EIO; + 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) + goto end; + else if (type != AVC_BRIDGECO_PLUG_TYPE_ISOC) { + err = -EIO; + goto end; + } + + /* store formations */ + for (i = 0; i < 2; i++) { + err = fill_stream_formations(bebob, i, 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) + 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) + goto end; + else if (type == AVC_BRIDGECO_PLUG_TYPE_MIDI) + bebob->midi_output_ports++; + } + + /* for check source of clock later */ + err = seek_msu_sync_input_plug(bebob); +end: + return err; +}