From patchwork Tue May 13 14:27:47 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Takashi Sakamoto X-Patchwork-Id: 4168151 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 48C199F334 for ; Tue, 13 May 2014 14:39:15 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id E37F72010B for ; Tue, 13 May 2014 14:39:13 +0000 (UTC) Received: from alsa0.perex.cz (alsa0.perex.cz [77.48.224.243]) by mail.kernel.org (Postfix) with ESMTP id 82FA42020E for ; Tue, 13 May 2014 14:39:10 +0000 (UTC) Received: by alsa0.perex.cz (Postfix, from userid 1000) id 1CE61265686; Tue, 13 May 2014 16:39:09 +0200 (CEST) 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 2D7E92656B1; Tue, 13 May 2014 16:31:17 +0200 (CEST) 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 176152656B1; Tue, 13 May 2014 16:31:16 +0200 (CEST) Received: from smtp303.phy.lolipop.jp (smtp303.phy.lolipop.jp [210.157.22.87]) by alsa0.perex.cz (Postfix) with ESMTP id 804A3265575 for ; Tue, 13 May 2014 16:28:17 +0200 (CEST) Received: from smtp303.phy.lolipop.lan (HELO smtp303.phy.lolipop.jp) (172.17.1.87) (smtp-auth username m12129643-o-takashi, mechanism plain) by smtp303.phy.lolipop.jp (qpsmtpd/0.82) with ESMTPA; Tue, 13 May 2014 23:28:16 +0900 Received: from 127.0.0.1 (127.0.0.1) by smtp303.phy.lolipop.jp (LOLIPOP-Fsecure); Tue, 13 May 2014 23:27:53 +0900 (JST) X-Virus-Status: clean(LOLIPOP-Fsecure) From: Takashi Sakamoto To: clemens@ladisch.de Date: Tue, 13 May 2014 23:27:47 +0900 Message-Id: <1399991272-5807-11-git-send-email-o-takashi@sakamocchi.jp> X-Mailer: git-send-email 1.8.3.2 In-Reply-To: <1399991272-5807-1-git-send-email-o-takashi@sakamocchi.jp> References: <1399991272-5807-1-git-send-email-o-takashi@sakamocchi.jp> MIME-Version: 1.0 Cc: alsa-devel@alsa-project.org, stefanr@s5r6.in-berlin.de, fenlason@redhat.com, ffado-devel@lists.sf.net, linux1394-devel@lists.sourceforge.net Subject: [alsa-devel] [PATCH 10/15] oxfw: Add support for AV/C stream format command to get supported stream formation 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 OXFW970/971 may supports AV/C Stream Format Information Specification 1.1 Working Draft (Apr 2005, 1394TA). By using this command, drivers can get to know stream formations which device supports. This commit adds 'EXTENDED STREAM FORMAT INFORMATION' command. This command has two subfunctions, 'SINGLE' and 'LIST'. Drivers can use 'SINGLE' subfunction to know/set current formation of AMDTP stream, Drivers can use 'LIST' subfunction to know an available formation of AMDTP stream in a certain sampling rate. But some devices don't implement the 'LIST' subfunction. So this commit uses an assumption that 'if they don't implement it, they don't change stream formation depending on current each sampling rate'. With this assumption, this driver generates formations for such devices by: 1.getting current formation by SINGLE subfunction 2.getting supported sampling rates 3.applying current formation for all of supported sampling rates Signed-off-by: Takashi Sakamoto --- sound/firewire/oxfw/Makefile | 3 +- sound/firewire/oxfw/oxfw.c | 5 +- sound/firewire/oxfw/oxfw.h | 34 ++++- sound/firewire/oxfw/oxfw_command.c | 113 +++++++++++++++ sound/firewire/oxfw/oxfw_stream.c | 275 +++++++++++++++++++++++++++++++++---- 5 files changed, 400 insertions(+), 30 deletions(-) create mode 100644 sound/firewire/oxfw/oxfw_command.c diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile index 53b5572..4ee2c97 100644 --- a/sound/firewire/oxfw/Makefile +++ b/sound/firewire/oxfw/Makefile @@ -1,2 +1,3 @@ -snd-oxfw-objs := oxfw_stream.o oxfw_control.o oxfw_proc.o oxfw_pcm.o oxfw.o +snd-oxfw-objs := oxfw_command.o oxfw_stream.o oxfw_control.o oxfw_proc.o \ + oxfw_pcm.o oxfw.o obj-m += snd-oxfw.o diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index d900718..825377a 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -97,10 +97,7 @@ static int oxfw_probe(struct fw_unit *unit, oxfw->unit = unit; oxfw->device_info = (const struct device_info *)id->driver_data; - if (oxfw->device_info == &griffin_firewave) - err = firewave_stream_discover(oxfw); - else - err = lacie_speakers_stream_discover(oxfw); + err = snd_oxfw_stream_discover(oxfw); if (err < 0) goto error; diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index e845581..b21dfce 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -59,15 +59,45 @@ struct snd_oxfw { s16 volume_max; }; +/* + * AV/C Stream Format Information Specification 1.1 Working Draft + * (Apr 2005, 1394TA) + */ +int avc_stream_get_format(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len, unsigned int eid); +static inline int +avc_stream_get_format_single(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len) +{ + return avc_stream_get_format(unit, dir, pid, buf, len, 0xff); +} +static inline int +avc_stream_get_format_list(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len, + unsigned int eid) +{ + return avc_stream_get_format(unit, dir, pid, buf, len, eid); +} + +/* + * AV/C Digital Interface Command Set General Specification 4.2 + * (Sep 2004, 1394TA) + */ +int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate, + enum avc_general_plug_dir dir, + unsigned short pid); + int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw); int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw, unsigned int rate, unsigned int pcm_channels); void snd_oxfw_stream_stop_simplex(struct snd_oxfw *oxfw); void snd_oxfw_stream_destroy_simplex(struct snd_oxfw *oxfw); void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw); +int snd_oxfw_stream_discover(struct snd_oxfw *oxfw); -int firewave_stream_discover(struct snd_oxfw *oxfw); -int lacie_speakers_stream_discover(struct snd_oxfw *oxfw); int snd_oxfw_create_pcm(struct snd_oxfw *oxfw); int snd_oxfw_create_mixer(struct snd_oxfw *oxfw); diff --git a/sound/firewire/oxfw/oxfw_command.c b/sound/firewire/oxfw/oxfw_command.c new file mode 100644 index 0000000..e3599d1 --- /dev/null +++ b/sound/firewire/oxfw/oxfw_command.c @@ -0,0 +1,113 @@ +/* + * oxfw_command.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "oxfw.h" + +int avc_stream_get_format(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len, unsigned int eid) +{ + unsigned int subfunc; + int err; + + if (eid == 0xff) + subfunc = 0xc0; /* SINGLE */ + else + subfunc = 0xc1; /* LIST */ + + buf[0] = 0x01; /* STATUS */ + buf[1] = 0xff; /* UNIT */ + buf[2] = 0xbf; /* EXTENDED STREAM FORMAT INFORMATION */ + buf[3] = subfunc; /* SINGLE or LIST */ + buf[4] = dir; /* Plug Direction */ + buf[5] = 0x00; /* Unit */ + buf[6] = 0x00; /* PCR (Isochronous Plug) */ + buf[7] = 0xff & pid; /* Plug ID */ + buf[8] = 0xff; /* Padding */ + buf[9] = 0xff; /* support status in response */ + buf[10] = 0xff & eid; /* entry ID for LIST subfunction */ + buf[11] = 0xff; /* padding */ + + /* do transaction and check buf[1-7] 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)); + if ((err > 0) && (err < 10)) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + /* LIST subfunction has entry ID */ + else if ((subfunc == 0xc1) && (buf[10] != eid)) + err = -EIO; + if (err < 0) + goto end; + + /* keep just stream format information */ + if (subfunc == 0xc0) { + memmove(buf, buf + 10, err - 10); + *len = err - 10; + } else { + memmove(buf, buf + 11, err - 11); + *len = err - 11; + } + + err = 0; +end: + return err; +} + +int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate, + enum avc_general_plug_dir dir, + unsigned short pid) +{ + unsigned int sfc; + u8 *buf; + int err; + + for (sfc = 0; sfc < CIP_SFC_COUNT; sfc++) { + if (amdtp_rate_table[sfc] == rate) + break; + } + if (sfc == CIP_SFC_COUNT) + return -EINVAL; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x02; /* SPECIFIC INQUIRY */ + buf[1] = 0xff; /* UNIT */ + if (dir == AVC_GENERAL_PLUG_DIR_IN) + buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */ + else + buf[2] = 0x18; /* OUTPUT PLUG SIGNAL FORMAT */ + buf[3] = 0xff & pid; /* plug id */ + buf[4] = 0x90; /* EOH_1, Form_1, FMT. AM824 */ + buf[5] = 0x07 & sfc; /* FDF-hi. AM824, frequency */ + buf[6] = 0xff; /* FDF-mid. AM824, SYT hi (not used) */ + buf[7] = 0xff; /* FDF-low. AM824, SYT lo (not used) */ + + /* do transaction and check buf[1-5] are the same against command */ + err = fcp_avc_transaction(unit, buf, 8, buf, 8, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5)); + if ((err > 0) && (err < 8)) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + if (err < 0) + goto end; + + err = 0; +end: + kfree(buf); + return err; +} diff --git a/sound/firewire/oxfw/oxfw_stream.c b/sound/firewire/oxfw/oxfw_stream.c index 2d09df8..0d81bba 100644 --- a/sound/firewire/oxfw/oxfw_stream.c +++ b/sound/firewire/oxfw/oxfw_stream.c @@ -8,6 +8,7 @@ #include "oxfw.h" +#define AVC_GENERIC_FRAME_MAXIMUM_BYTES 512 #define CALLBACK_TIMEOUT 200 /* @@ -24,6 +25,19 @@ static const unsigned int oxfw_rate_table[] = { [5] = 192000, }; +/* + * See Table 5.7 – Sampling frequency for Multi-bit Audio + * at AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) + */ +static const unsigned int avc_stream_rate_table[] = { + [0] = 0x02, + [1] = 0x03, + [2] = 0x04, + [3] = 0x0a, + [4] = 0x05, + [5] = 0x07, +}; + int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw) { int err; @@ -109,6 +123,28 @@ end: return err; } +static int check_connection_used_by_others(struct snd_oxfw *oxfw) +{ + struct cmp_connection *conn; + struct amdtp_stream *stream; + bool used; + int err; + + stream = &oxfw->rx_stream; + conn = &oxfw->in_conn; + + err = cmp_connection_check_used(conn, &used); + if ((err >= 0) && used && !amdtp_stream_running(stream)) { + dev_err(&oxfw->unit->device, + "Connection established by others: %cPCR[%d]\n", + (conn->direction == CMP_OUTPUT) ? 'o' : 'i', + conn->pcr_index); + err = -EBUSY; + } + + return err; +} + int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw, unsigned int rate, unsigned int pcm_channels) { @@ -117,6 +153,14 @@ int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw, unsigned int rate, mutex_lock(&oxfw->mutex); + /* + * Considering JACK/FFADO streaming: + * TODO: This can be removed hwdep functionality becomes popular. + */ + err = check_connection_used_by_others(oxfw); + if (err < 0) + goto end; + /* packet queueing error */ if (amdtp_streaming_error(&oxfw->rx_stream)) stop_stream(oxfw); @@ -182,36 +226,221 @@ void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw) } } -int firewave_stream_discover(struct snd_oxfw *oxfw) +/* + * See Table 6.16 - AM824 Stream Format + * Figure 6.19 - format_information field for AM824 Compound + * in AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) + * Also 'Clause 12 AM824 sequence adaption layers' in IEC 61883-6:2005 + */ +static int parse_stream_formation(u8 *buf, unsigned int len, + struct snd_oxfw_stream_formation *formation) { - /* 6 channels for PCM at 32.0/44.1/48.0/96.0kHz */ - oxfw->rx_stream_formations[0].rate = oxfw_rate_table[0]; - oxfw->rx_stream_formations[1].rate = oxfw_rate_table[1]; - oxfw->rx_stream_formations[2].rate = oxfw_rate_table[2]; - oxfw->rx_stream_formations[3].rate = oxfw_rate_table[4]; - oxfw->rx_stream_formations[0].pcm = 6; - oxfw->rx_stream_formations[1].pcm = 6; - oxfw->rx_stream_formations[2].pcm = 6; - oxfw->rx_stream_formations[3].pcm = 6; - - /* 2 channels for PCM at 48.0/96.0kHz */ - oxfw->rx_stream_formations[4].rate = oxfw_rate_table[2]; - oxfw->rx_stream_formations[5].rate = oxfw_rate_table[4]; - oxfw->rx_stream_formations[4].pcm = 2; - oxfw->rx_stream_formations[5].pcm = 2; + unsigned int i, e, channels, format; + + if (len < 3) + return -EINVAL; + + /* + * 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 the sampling rate */ + for (i = 0; i < ARRAY_SIZE(avc_stream_rate_table); i++) { + if (buf[2] == avc_stream_rate_table[i]) + break; + } + if (i == ARRAY_SIZE(avc_stream_rate_table)) + return -ENOSYS; + + memset(formation, 0, sizeof(struct snd_oxfw_stream_formation)); + formation->rate = oxfw_rate_table[i]; + + 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 (Raw) */ + case 0x06: + formation->pcm += channels; + break; + /* MIDI Conformant */ + case 0x0d: + formation->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 */ + /* SMPTE Time-Code conformant */ + case 0x0e: + /* Sample Count */ + case 0x0f: + /* Anciliary Data */ + case 0x10: + /* Synchronization Stream (Stereo Raw audio) */ + case 0x40: + /* Don't care */ + case 0xff: + default: + return -ENOSYS; /* not supported */ + } + } + + if (formation->pcm > AMDTP_MAX_CHANNELS_FOR_PCM || + formation->midi > AMDTP_MAX_CHANNELS_FOR_MIDI) + return -ENOSYS; return 0; } -int lacie_speakers_stream_discover(struct snd_oxfw *oxfw) +static int +assume_stream_formations(struct snd_oxfw *oxfw, enum avc_general_plug_dir dir, + unsigned int pid, u8 *buf, unsigned int *len, + struct snd_oxfw_stream_formation *formations) { - unsigned int i; + unsigned int rate, pcm_channels, midi_channels, i, eid; + int err; - /* 2 channels for MBLA at 32.0/44.1/48.0/88.2/96.0kHz */ - for (i = 0; i < 5; i++) { - oxfw->rx_stream_formations[i].rate = oxfw_rate_table[i]; - oxfw->rx_stream_formations[i].pcm = 2; + /* get formation at current sampling rate */ + err = avc_stream_get_format_single(oxfw->unit, dir, pid, buf, len); + if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to get current stream format for isoc %s plug %d:%d\n", + (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" : "out", + pid, err); + goto end; } - return 0; + /* parse and set stream formation */ + eid = 0; + err = parse_stream_formation(buf, *len, &formations[eid]); + if (err < 0) + goto end; + + rate = formations[eid].rate; + pcm_channels = formations[eid].pcm; + midi_channels = formations[eid].midi; + + /* apply the formation for each available sampling rate */ + for (i = 0; i < ARRAY_SIZE(oxfw_rate_table); i++) { + if (rate == oxfw_rate_table[i]) + continue; + + err = avc_general_inquiry_sig_fmt(oxfw->unit, + oxfw_rate_table[i], + dir, pid); + if (err < 0) + continue; + + eid++; + formations[eid].rate = oxfw_rate_table[i]; + formations[eid].pcm = pcm_channels; + formations[eid].midi = midi_channels; + } + + err = 0; +end: + return err; +} + +static int fill_stream_formations(struct snd_oxfw *oxfw, + enum avc_general_plug_dir dir, + unsigned short pid) +{ + u8 *buf; + struct snd_oxfw_stream_formation *formations; + unsigned int len, eid = 0; + int err; + + buf = kmalloc(AVC_GENERIC_FRAME_MAXIMUM_BYTES, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + formations = oxfw->rx_stream_formations; + + /* get first entry */ + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + err = avc_stream_get_format_list(oxfw->unit, dir, 0, buf, &len, 0); + if (err == -ENOSYS) { + /* LIST subfunction is not implemented */ + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + err = assume_stream_formations(oxfw, dir, pid, buf, &len, + formations); + goto end; + } else if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to get stream format %d for isoc %s plug %d:%d\n", + eid, (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" : "out", + pid, err); + goto end; + } + + /* LIST subfunction is implemented */ + while (eid < SND_OXFW_STREAM_FORMAT_ENTRIES) { + /* parse and set stream formation */ + err = parse_stream_formation(buf, len, &formations[eid]); + if (err < 0) + break; + + /* get next entry */ + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + err = avc_stream_get_format_list(oxfw->unit, dir, 0, + buf, &len, ++eid); + /* No entries remained. */ + if (err == -EINVAL) { + err = 0; + break; + } else if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to get stream format %d for isoc %s plug %d:%d\n", + eid, (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" : + "out", + pid, err); + break; + } + } +end: + kfree(buf); + return err; +} + +int snd_oxfw_stream_discover(struct snd_oxfw *oxfw) +{ + u8 plugs[AVC_PLUG_INFO_BUF_BYTES]; + int err; + + /* the number of plugs for isoc in/out, ext in/out */ + err = avc_general_get_plug_info(oxfw->unit, 0x1f, 0x07, 0x00, plugs); + if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to get info for isoc/external in/out plugs: %d\n", + err); + goto end; + } else if (plugs[0] == 0) { + err = -ENOSYS; + goto end; + } + + /* use iPCR[0] if exists */ + if (plugs[0] > 0) + err = fill_stream_formations(oxfw, AVC_GENERAL_PLUG_DIR_IN, 0); +end: + return err; }