[alsa-devel] [PATCH 3/8] oxfw: Add some AV/C commands for channel formation of AMDTP stream

Takashi Sakamoto o-takashi at sakamocchi.jp
Sun Jan 5 12:13:32 CET 2014


OXFW970/971 may supports AV/C Stream Format Information Specification 1.1
Working Draft (Apr 2005, 1394TA). So this driver uses 'EXTENDED STREAM FORMAT
INFORMATION' command.

This command has two subfunctions, SINGLE and LIST. To use SINGLE subfunction,
drivers can know current formation of AMDTP stream. To use LIST subfunction,
drivers can know all of available formations of AMDTP stream. But there are
some devices which don't implement LIST subfunction.

When device don't implement LIST subfunction, this driver assume formation of
AMDTP stream. The way of assumption is:
1.getting current formation with SINGLE subfunction
2.checking all of available sampling rate
3.applying current formation for all of available sampling rate

I note that this assumption is too bad when the device don't implement LIST
subfunction and different formation for AMDTP streams at every available
sampling rates. I believe this is rare.

This driver also uses another assumption for available MIDI ports. If one of
formations has MIDI conformant data channel, then this driver assume the device
has one MIDI ports. I believe it rare that device has several MIDI ports.

Signed-off-by: Takashi Sakamoto <o-takashi at sakamocchi.jp>
---
 sound/firewire/oxfw/Makefile       |   2 +-
 sound/firewire/oxfw/oxfw.c         |   4 +-
 sound/firewire/oxfw/oxfw.h         |  55 +++++++++
 sound/firewire/oxfw/oxfw_command.c | 161 ++++++++++++++++++++++++
 sound/firewire/oxfw/oxfw_stream.c  | 243 +++++++++++++++++++++++++++++++++++++
 5 files changed, 463 insertions(+), 2 deletions(-)
 create mode 100644 sound/firewire/oxfw/oxfw_command.c
 create mode 100644 sound/firewire/oxfw/oxfw_stream.c

diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile
index 9ca49c0..5eb3d87 100644
--- a/sound/firewire/oxfw/Makefile
+++ b/sound/firewire/oxfw/Makefile
@@ -1,2 +1,2 @@
-snd-oxfw-objs := oxfw.o
+snd-oxfw-objs := oxfw_command.o oxfw_stream.o oxfw.o
 obj-m += snd-oxfw.o
diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c
index e90c53a..c030fc0 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -155,7 +155,9 @@ error:
 static void
 oxfw_update(struct fw_unit *unit)
 {
-	return;
+	struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
+
+	fcp_bus_reset(oxfw->unit);
 }
 
 static void
diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h
index e75eb70..e82717a 100644
--- a/sound/firewire/oxfw/oxfw.h
+++ b/sound/firewire/oxfw/oxfw.h
@@ -22,6 +22,16 @@
 #include <sound/initval.h>
 
 #include "../lib.h"
+#include "../fcp.h"
+#include "../amdtp.h"
+
+#define	SND_OXFW_RATE_TABLE_ENTRIES	7
+struct snd_oxfw_stream_formation {
+	unsigned int pcm;
+	unsigned int midi;
+};
+/* this is a lookup table for index of stream formations */
+extern const unsigned int snd_oxfw_rate_table[SND_OXFW_RATE_TABLE_ENTRIES];
 
 struct snd_oxfw {
 	struct snd_card *card;
@@ -31,8 +41,53 @@ struct snd_oxfw {
 
 	struct mutex mutex;
 	spinlock_t lock;
+
+	struct snd_oxfw_stream_formation
+		tx_stream_formations[SND_OXFW_RATE_TABLE_ENTRIES];
+	struct snd_oxfw_stream_formation
+		rx_stream_formations[SND_OXFW_RATE_TABLE_ENTRIES];
+
+	unsigned int midi_input_ports;
+	unsigned int midi_output_ports;
 };
 
+/* AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) */
+#define AVC_GENERIC_FRAME_MAXIMUM_BYTES	512
+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_get_rate(struct snd_oxfw *oxfw, unsigned int *rate,
+		      enum avc_general_plug_dir dir);
+int snd_oxfw_set_rate(struct snd_oxfw *oxfw, unsigned int rate,
+		      enum avc_general_plug_dir dir);
+
+/* for AMDTP streaming */
+int snd_oxfw_stream_discover(struct snd_oxfw *oxfw);
+
 #define SND_OXFW_DEV_ENTRY(vendor, model) \
 { \
 	.match_flags	= IEEE1394_MATCH_VENDOR_ID | \
diff --git a/sound/firewire/oxfw/oxfw_command.c b/sound/firewire/oxfw/oxfw_command.c
new file mode 100644
index 0000000..526518f
--- /dev/null
+++ b/sound/firewire/oxfw/oxfw_command.c
@@ -0,0 +1,161 @@
+/*
+ * oxfw_command.c - driver for OXFW970/971 based devices
+ *
+ * Copyright (c) 2013 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;
+
+	/* check given buffer */
+	if ((buf == NULL) || (*len < 12)) {
+		err = -EINVAL;
+		goto end;
+	}
+
+	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 */
+	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) {
+		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 content starts at 11th bytes */
+	} else if (err < 9) {
+		err = -EIO;
+		goto end;
+	} else if ((subfunc == 0xc1) && (buf[10] != eid)) {
+		err = -EIO;
+		goto end;
+	}
+
+	/* strip */
+	memmove(buf, buf + 10, err - 10);
+	*len = err - 10;
+	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)
+		goto end;
+
+	/* check length */
+	if (err != 8) {
+		dev_err(&unit->device, "failed to inquiry sample rate\n");
+		err = -EIO;
+		goto end;
+	}
+
+	/* return response code */
+	err = buf[0];
+end:
+	kfree(buf);
+	return err;
+}
+
+int snd_oxfw_get_rate(struct snd_oxfw *oxfw, unsigned int *rate,
+		      enum avc_general_plug_dir dir)
+{
+	int err;
+
+	err = avc_general_get_sig_fmt(oxfw->unit, rate, dir, 0);
+	if (err < 0)
+		goto end;
+
+	/* IMPLEMENTED/STABLE is OK */
+	if (err != 0x0c) {
+		dev_err(&oxfw->unit->device,
+			"failed to get sampling rate\n");
+		err = -EIO;
+	}
+end:
+	return err;
+}
+
+int snd_oxfw_set_rate(struct snd_oxfw *oxfw, unsigned int rate,
+		       enum avc_general_plug_dir dir)
+{
+	int err;
+
+	err = avc_general_set_sig_fmt(oxfw->unit, rate, dir, 0);
+	if (err < 0)
+		goto end;
+
+	/* ACCEPTED or INTERIM is OK */
+	if ((err != 0x0f) && (err != 0x09)) {
+		dev_err(&oxfw->unit->device,
+			"failed to set sampling rate\n");
+		err = -EIO;
+	}
+end:
+	return err;
+}
diff --git a/sound/firewire/oxfw/oxfw_stream.c b/sound/firewire/oxfw/oxfw_stream.c
new file mode 100644
index 0000000..60ad700
--- /dev/null
+++ b/sound/firewire/oxfw/oxfw_stream.c
@@ -0,0 +1,243 @@
+/*
+ * oxfw_stream.c - a part of driver for OXFW970/971 based devices
+ *
+ * Copyright (c) 2013 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "./oxfw.h"
+
+/*
+ * According to their datasheet:
+ *  OXFW970 32.0/44.1/48.0/96.0 Khz, 4 audio channels I/O
+ *  OXFW971: 32.0/44.1/48.0/88.2/96.0 kHz, 16 audio channels I/O, MIDI I/O
+ *
+ * OXFW970 seems not to implement 'LIST' subfunction for 'EXTENDED STREAM
+ * FORMAT INFORMATION' defined in 'AV/C Stream Format Information
+ * Specification 1.1 (Apr 2005, 1394TA)'. So this module uses an assumption
+ * that OXFW970 doesn't change its formation of channels in AMDTP stream.
+ */
+const unsigned int snd_oxfw_rate_table[SND_OXFW_RATE_TABLE_ENTRIES] = {
+	[0] = 32000,
+	[1] = 44100,
+	[2] = 48000,
+	[3] = 88200,
+	[4] = 96000,
+	[5] = 176400,
+	[6] = 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] = 0x06,
+	[6] = 0x07,
+};
+
+/*
+ * See Table 6.16 - AM824 Stream Format
+ *     Figure 6.19 - format_information field for AM824 Compound
+ * at AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA)
+ */
+static int
+parse_stream_formation(u8 *buf, unsigned int len,
+		       struct snd_oxfw_stream_formation *formation,
+		       unsigned int *index)
+{
+	unsigned int 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 the sampling rate */
+	for (*index = 0; *index < sizeof(avc_stream_rate_table); *index += 1) {
+		if (buf[2] == avc_stream_rate_table[*index])
+			break;
+	}
+	if (*index == sizeof(avc_stream_rate_table))
+		return -ENOSYS;
+
+	for (e = 0; e < buf[4]; e++) {
+		channels = buf[5 + e * 2];
+		format = buf[6 + e * 2];
+
+		switch (format) {
+		/* IEC 60958-3 */
+		case 0x00:
+		/* Multi Bit Linear Audio (Raw) */
+		case 0x06:
+			formation[*index].pcm += channels;
+			break;
+		/* MIDI comformant */
+		case 0x0d:
+			formation[*index].midi += channels;
+			break;
+		/* 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:
+		/* 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:
+			break;	/* not supported */
+		}
+	}
+
+	return 0;
+}
+
+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, pcm_channels, midi_channels;
+	int err;
+
+	/* get formation at current sampling rate */
+	err = avc_stream_get_format_single(oxfw->unit, dir, pid, buf, len);
+	if ((err < 0) || (err == 0x80) /* NOT IMPLEMENTED */)
+		goto end;
+
+	/* parse and set stream formation */
+	err = parse_stream_formation(buf, *len, formations, &i);
+	if (err < 0)
+		goto end;
+
+	pcm_channels = formations[i].pcm;
+	midi_channels = formations[i].midi;
+
+	/* apply the formation for each available sampling rate */
+	for (i = 0; i < SND_OXFW_RATE_TABLE_ENTRIES; i++) {
+		err = avc_general_inquiry_sig_fmt(oxfw->unit,
+						  snd_oxfw_rate_table[i],
+						  dir, pid);
+		if ((err < 0) || (err == 0x08) /* NOT IMPLEMENTED */)
+			continue;
+
+		formations[i].pcm = pcm_channels;
+		formations[i].midi = midi_channels;
+	}
+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 i, len, eid;
+	int err;
+
+	buf = kmalloc(AVC_GENERIC_FRAME_MAXIMUM_BYTES, GFP_KERNEL);
+	if (buf == NULL)
+		return -ENOMEM;
+
+	if (dir == AVC_GENERAL_PLUG_DIR_IN)
+		formations = oxfw->rx_stream_formations;
+	else
+		formations = oxfw->tx_stream_formations;
+
+	/* initialize parameters here because of checking implementation */
+	eid = 0;
+	len = AVC_GENERIC_FRAME_MAXIMUM_BYTES;
+	memset(buf, 0, len);
+
+	/* get first entry */
+	err = avc_stream_get_format_list(oxfw->unit, dir, 0, buf, &len, eid);
+	if ((err < 0) || (len < 3)) {
+		/* LIST subfunction is not implemented */
+		err = assume_stream_formations(oxfw, dir, pid, buf, &len,
+					       formations);
+		goto end;
+	}
+
+	/* LIST subfunction is implemented */
+	do {
+		/* parse and set stream formation */
+		err = parse_stream_formation(buf, len, formations, &i);
+		if (err < 0)
+			continue;
+
+		/* get next entry */
+		len = AVC_GENERIC_FRAME_MAXIMUM_BYTES;
+		memset(buf, 0, len);
+		err = avc_stream_get_format_list(oxfw->unit, dir, 0,
+						 buf, &len, ++eid);
+		if ((err < 0) || (len < 3))
+			break;
+	} while (eid < SND_OXFW_RATE_TABLE_ENTRIES);
+end:
+	kfree(buf);
+	return err;
+}
+
+int snd_oxfw_stream_discover(struct snd_oxfw *oxfw)
+{
+	u8 plugs[AVC_PLUG_INFO_BUF_COUNT];
+	unsigned int i;
+	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)
+		goto end;
+	if ((plugs[0] == 0) || (plugs[0] == 0)) {
+		err = -EIO;
+		goto end;
+	}
+
+	/* use oPCR[0] */
+	err = fill_stream_formations(oxfw, AVC_GENERAL_PLUG_DIR_OUT, 0);
+	if (err < 0)
+		goto end;
+
+	/* use iPCR[0] */
+	err = fill_stream_formations(oxfw, AVC_GENERAL_PLUG_DIR_IN, 0);
+	if (err < 0)
+		goto end;
+
+	/* if its stream has MIDI conformant data channel, add one MIDI port */
+	for (i = 0; i < SND_OXFW_RATE_TABLE_ENTRIES; i++) {
+		if (oxfw->tx_stream_formations[i].midi > 0)
+			oxfw->midi_input_ports = 1;
+		else if (oxfw->rx_stream_formations[i].midi > 0)
+			oxfw->midi_output_ports = 1;
+	}
+end:
+	return err;
+}
-- 
1.8.3.2



More information about the Alsa-devel mailing list