All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers
@ 2014-05-13 14:27 Takashi Sakamoto
  2014-05-13 14:27 ` [PATCH 01/15] speakers: Rename to oxfw and rename some members Takashi Sakamoto
                   ` (17 more replies)
  0 siblings, 18 replies; 35+ messages in thread
From: Takashi Sakamoto @ 2014-05-13 14:27 UTC (permalink / raw)
  To: clemens; +Cc: alsa-devel, stefanr, fenlason, ffado-devel, linux1394-devel

This patchset updates a part of my previous series:

[alsa-devel] [RFC v3] [PATCH 00/52] Enhancement for support of firewire devices
http://mailman.alsa-project.org/pipermail/alsa-devel/2014-January/071820.html

This patchset is based on this series. (This series is under reviewing.)
[alsa-devel] [PATCH 00/49 v4] Enhancement for support of Firewire devices
http://mailman.alsa-project.org/pipermail/alsa-devel/2014-April/075841.html

If you try this patchset, please apply the series in advance and apply this patchset.

Or just use backport codes in my github repository:
https://github.com/takaswie/snd-firewire-improve

Or if you use Ubuntu 14.04, DKMS package is available from David's PPA.
(Special thanks to David Henningsson.)
https://launchpad.net/~diwic/+archive/snd-firewire-improve

If you have Griffin FireWave, I would strongly request to test. Please see 'An issue for FireWave' in this message.

== Background
Currently, in ALSA, 'firewire-speakers' driver supports OXFW970/971 chipset which Oxford Semiconductor produced. But this driver has some restrictions:
 - Don't support MIDI substreams
 - Don't support substreams for PCM/MIDI capturing
There are several devices which are based on OXFW970/971. Especially, devices which Behringer and Mackie produced support playback/capture PCM samples/MIDI messages. So firewire-speakers needs re-work to support these devices.

This patchset renames 'firewire-speakers' to 'oxfw'. Maintainers already agreed to rename firewire-speakers to suitable name, in my first RFC in Jan 2014.


== Supported devices
Currently these devices are supported:
 * Griffin Firewave
 * LaCie Firewire Speakers
 * Behringer F-Control Audio 202
 * Mackie(Loud) Onyx-i series (former models)
 * Mackie(Loud) Onyx Satellite
 * Mackie(Loud) Tapco Link.Firewire

These devices can be supported but its IDs are unknown:
 * Mackie(Loud), d.2 pro
 * Mackie(Loud), d.4 pro 
 * Mackie(Loud), U.420 
 * Mackie(Loud), U.420d 

When you have unknown devices, please inform me output of this command:
$ hexdump -C /sys/bus/firewire/devices/fwX/config_rom
(X is node id for your device. Tipically '1'.)

I tested this driver with Behringer F-Control Audio 202.


== Features
 - Capturing/Playbacking PCM samples/MIDI messages at supported sampling rates via ALSA PCM/MIDI interfaces
 - Retrieving firewire node information via ALSA hwdep interface
 - Locking/Unlocking kernel streaming via ALSA hwdep interface
 - Receiving notification at starting/stopping kernel streaming via ALSA hwdep interface


== Solutions for reported regressions
Two regressions were reported for my previous series:
1.Some names in card structure is trimmed.
http://mailman.alsa-project.org/pipermail/alsa-devel/2014-February/073128.html
2.The number of PCM channels are not detected correctly.
http://mailman.alsa-project.org/pipermail/alsa-devel/2014-February/073145.html

The cause of issue 1 is that the device has too long names in its config rom. To solve this issue, for the driver, this patchset uses the same way which firewire-speakers uses, propper alias in device-specific structure.

The cause of issue 2 is that the device has several format entries for the same sampling rate:
http://mailman.alsa-project.org/pipermail/alsa-devel/2014-February/073561.html
To solve this issue, this patchset includes improved mechanism to detect stream formation.

Additionally, this patchset adds new device entry for Mackie(Loud) Tapco Link.Firewire, refering to FFADO bug ticket 236:
http://subversion.ffado.org/ticket/236


== An issue for FireWave

I have an issue for Griffin Firewave.

This device can handle AV/C Stream Format Information command and driver can get to know stream formations via the command. With Stefan's help, responses from FireWave against this command are clear.
http://mailman.alsa-project.org/pipermail/alsa-devel/2014-February/073561.html

According to these responses, supported formations are:
Input Stream to device:
	Rate	PCM	MIDI
	32000	6	0
	44100	6	0
	48000	6	0
	96000	6	0
	32000	2	0
	44100	2	0
	48000	2	0
	0	0	0
	0	0	0
	0	0	0
(I also expect to get this output from /proc/asound/FireWave/firewire/formation. I note it's order is not important.)

But current 'firewire-speakers' has PCM constraints below:
 - 6ch at 32/44.1/48/96 kHz
 - 2ch at 48/96 kHz
http://mailman.alsa-project.org/pipermail/alsa-devel/2014-February/073562.html

So this OXFW driver may lost a support for 2ch/96.0kHz for FireWave and gain 2ch/32.0kHz and 2ch/44.1kHz.

I don't know exactly the reason of these gaps. So I strongly hope testers to check these two items:
 - FireWave really can handle 2ch/96kHz with firewire-speakers driver.
 - FireWave really can handle 2ch/32.0kHz and 2ch/44.1kHz with oxfw driver.

I think a test with aplay is easy to execute:
$ aplay -D hw:FireWave -f S32_LE -c 2 -r 96000 /dev/urandom
$ aplay -D hw:FireWave -f S32_LE -c 2 -r 48000 /dev/urandom
$ aplay -D hw:FireWave -f S32_LE -c 2 -r 44100 /dev/urandom
$ aplay -D hw:FireWave -f S32_LE -c 2 -r 32000 /dev/urandom

If 2ch/96kHz is apparently available, I'll change codes as a quirk.

FYI, when I tested unavailable number of PCM channels with a hack, FCA202 playback PCM substreams but becomes freezed after stopping streaming.
$ cat /var/log/syslog
...
kernel: [10098.670390] snd_oxfw fw1.0: transaction failed: timeout
...

I believe the other OXFW970/971 based devices have a similar behaviour.

================
Takashi Sakamoto (15):
  speakers: Rename to oxfw and rename some members
  oxfw: Move to its own directory
  oxfw: Split stream functionality to a new file and add a header file
  oxfw: Split PCM functionality to a new file
  oxfw: Split control functionality to a new file
  oxfw: Change the way to name card
  oxfw: Change the way to make PCM rules/constraints
  oxfw: Add proc interface for debugging purpose
  oxfw: Change the way to start stream
  oxfw: Add support for AV/C stream format command to get supported
    stream formation
  oxfw: Add a quirk for Griffin FireWave
  oxfw: Add support for Behringer/Mackie devices
  oxfw: Add support AMDTP in-stream and PCM capture
  oxfw: Add support for capture/playback MIDI messages
  oxfw: Add hwdep interface

 include/uapi/sound/asound.h        |   3 +-
 include/uapi/sound/firewire.h      |   3 +-
 sound/firewire/Kconfig             |  18 +-
 sound/firewire/Makefile            |   3 +-
 sound/firewire/oxfw/Makefile       |   3 +
 sound/firewire/oxfw/oxfw.c         | 284 +++++++++++++
 sound/firewire/oxfw/oxfw.h         | 141 +++++++
 sound/firewire/oxfw/oxfw_command.c | 113 ++++++
 sound/firewire/oxfw/oxfw_control.c | 283 +++++++++++++
 sound/firewire/oxfw/oxfw_hwdep.c   | 190 +++++++++
 sound/firewire/oxfw/oxfw_midi.c    | 168 ++++++++
 sound/firewire/oxfw/oxfw_pcm.c     | 367 +++++++++++++++++
 sound/firewire/oxfw/oxfw_proc.c    |  83 ++++
 sound/firewire/oxfw/oxfw_stream.c  | 658 ++++++++++++++++++++++++++++++
 sound/firewire/speakers.c          | 792 -------------------------------------
 15 files changed, 2308 insertions(+), 801 deletions(-)
 create mode 100644 sound/firewire/oxfw/Makefile
 create mode 100644 sound/firewire/oxfw/oxfw.c
 create mode 100644 sound/firewire/oxfw/oxfw.h
 create mode 100644 sound/firewire/oxfw/oxfw_command.c
 create mode 100644 sound/firewire/oxfw/oxfw_control.c
 create mode 100644 sound/firewire/oxfw/oxfw_hwdep.c
 create mode 100644 sound/firewire/oxfw/oxfw_midi.c
 create mode 100644 sound/firewire/oxfw/oxfw_pcm.c
 create mode 100644 sound/firewire/oxfw/oxfw_proc.c
 create mode 100644 sound/firewire/oxfw/oxfw_stream.c
 delete mode 100644 sound/firewire/speakers.c

-- 
1.8.3.2

^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH 01/15] speakers: Rename to oxfw and rename some members
  2014-05-13 14:27 [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Takashi Sakamoto
@ 2014-05-13 14:27 ` Takashi Sakamoto
  2014-05-13 14:27 ` [PATCH 02/15] oxfw: Move to its own directory Takashi Sakamoto
                   ` (16 subsequent siblings)
  17 siblings, 0 replies; 35+ messages in thread
From: Takashi Sakamoto @ 2014-05-13 14:27 UTC (permalink / raw)
  To: clemens; +Cc: alsa-devel, fenlason, ffado-devel, linux1394-devel

This commit renames 'firewire-speakers' to 'oxfw' to enhance support for
devices which based on OXFW970/971. A line for MODULE_ALIAS is added.

Additionally, to help for works in followed paches, some members
in private structure are renamed.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/Kconfig    |  12 +-
 sound/firewire/Makefile   |   4 +-
 sound/firewire/oxfw.c     | 793 ++++++++++++++++++++++++++++++++++++++++++++++
 sound/firewire/speakers.c | 792 ---------------------------------------------
 4 files changed, 802 insertions(+), 799 deletions(-)
 create mode 100644 sound/firewire/oxfw.c
 delete mode 100644 sound/firewire/speakers.c

diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig
index 9f363fa..63f3cea 100644
--- a/sound/firewire/Kconfig
+++ b/sound/firewire/Kconfig
@@ -26,16 +26,18 @@ config SND_DICE
 	  To compile this driver as a module, choose M here: the module
 	  will be called snd-dice.
 
-config SND_FIREWIRE_SPEAKERS
-	tristate "FireWire speakers"
+config SND_OXFW
+	tristate "Oxford OXFW970/971 chipset support"
 	select SND_PCM
 	select SND_FIREWIRE_LIB
 	help
-	  Say Y here to include support for the Griffin FireWave Surround
-	  and the LaCie FireWire Speakers.
+	  Say Y here to include support for Firewire devices based on
+	  Oxford Semiconductor OXFW970/971 chipset.
+	   * Griffin Firewave
+	   * LaCie Firewire Speakers
 
 	  To compile this driver as a module, choose M here: the module
-	  will be called snd-firewire-speakers.
+	  will be called snd-oxfw.
 
 config SND_ISIGHT
 	tristate "Apple iSight microphone"
diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile
index fad8d49..cec888f 100644
--- a/sound/firewire/Makefile
+++ b/sound/firewire/Makefile
@@ -1,14 +1,14 @@
 snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \
 			 fcp.o cmp.o amdtp.o
 snd-dice-objs := dice.o
-snd-firewire-speakers-objs := speakers.o
+snd-oxfw-objs := oxfw.o
 snd-isight-objs := isight.o
 snd-scs1x-objs := scs1x.o
 
 obj-$(CONFIG_SND_FIREWIRE_LIB) += snd-firewire-lib.o
 obj-$(CONFIG_SND_DICE) += snd-dice.o
-obj-$(CONFIG_SND_FIREWIRE_SPEAKERS) += snd-firewire-speakers.o
 obj-$(CONFIG_SND_ISIGHT) += snd-isight.o
 obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o
 obj-$(CONFIG_SND_FIREWORKS) += fireworks/
 obj-$(CONFIG_SND_BEBOB) += bebob/
+obj-$(CONFIG_SND_OXFW) += snd-oxfw.o
diff --git a/sound/firewire/oxfw.c b/sound/firewire/oxfw.c
new file mode 100644
index 0000000..2dc5a58
--- /dev/null
+++ b/sound/firewire/oxfw.c
@@ -0,0 +1,793 @@
+/*
+ * oxfw.c - a part of driver for OXFW970/971 based devices
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include <linux/device.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "cmp.h"
+#include "fcp.h"
+#include "amdtp.h"
+#include "lib.h"
+
+#define OXFORD_FIRMWARE_ID_ADDRESS	(CSR_REGISTER_BASE + 0x50000)
+/* 0x970?vvvv or 0x971?vvvv, where vvvv = firmware version */
+
+#define OXFORD_HARDWARE_ID_ADDRESS	(CSR_REGISTER_BASE + 0x90020)
+#define OXFORD_HARDWARE_ID_OXFW970	0x39443841
+#define OXFORD_HARDWARE_ID_OXFW971	0x39373100
+
+#define VENDOR_GRIFFIN		0x001292
+#define VENDOR_LACIE		0x00d04b
+
+#define SPECIFIER_1394TA	0x00a02d
+#define VERSION_AVC		0x010001
+
+struct device_info {
+	const char *driver_name;
+	const char *short_name;
+	const char *long_name;
+	int (*pcm_constraints)(struct snd_pcm_runtime *runtime);
+	unsigned int mixer_channels;
+	u8 mute_fb_id;
+	u8 volume_fb_id;
+};
+
+struct snd_oxfw {
+	struct snd_card *card;
+	struct fw_unit *unit;
+	const struct device_info *device_info;
+	struct mutex mutex;
+	struct cmp_connection in_conn;
+	struct amdtp_stream rx_stream;
+	bool mute;
+	s16 volume[6];
+	s16 volume_min;
+	s16 volume_max;
+};
+
+MODULE_DESCRIPTION("Oxford OXFW970/971 driver");
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("snd-firewire-speakers");
+
+static int firewave_rate_constraint(struct snd_pcm_hw_params *params,
+				    struct snd_pcm_hw_rule *rule)
+{
+	static unsigned int stereo_rates[] = { 48000, 96000 };
+	struct snd_interval *channels =
+			hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_interval *rate =
+			hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+
+	/* two channels work only at 48/96 kHz */
+	if (snd_interval_max(channels) < 6)
+		return snd_interval_list(rate, 2, stereo_rates, 0);
+	return 0;
+}
+
+static int firewave_channels_constraint(struct snd_pcm_hw_params *params,
+					struct snd_pcm_hw_rule *rule)
+{
+	static const struct snd_interval all_channels = { .min = 6, .max = 6 };
+	struct snd_interval *rate =
+			hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	struct snd_interval *channels =
+			hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+
+	/* 32/44.1 kHz work only with all six channels */
+	if (snd_interval_max(rate) < 48000)
+		return snd_interval_refine(channels, &all_channels);
+	return 0;
+}
+
+static int firewave_constraints(struct snd_pcm_runtime *runtime)
+{
+	static unsigned int channels_list[] = { 2, 6 };
+	static struct snd_pcm_hw_constraint_list channels_list_constraint = {
+		.count = 2,
+		.list = channels_list,
+	};
+	int err;
+
+	runtime->hw.rates = SNDRV_PCM_RATE_32000 |
+			    SNDRV_PCM_RATE_44100 |
+			    SNDRV_PCM_RATE_48000 |
+			    SNDRV_PCM_RATE_96000;
+	runtime->hw.channels_max = 6;
+
+	err = snd_pcm_hw_constraint_list(runtime, 0,
+					 SNDRV_PCM_HW_PARAM_CHANNELS,
+					 &channels_list_constraint);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				  firewave_rate_constraint, NULL,
+				  SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+				  firewave_channels_constraint, NULL,
+				  SNDRV_PCM_HW_PARAM_RATE, -1);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int lacie_speakers_constraints(struct snd_pcm_runtime *runtime)
+{
+	runtime->hw.rates = SNDRV_PCM_RATE_32000 |
+			    SNDRV_PCM_RATE_44100 |
+			    SNDRV_PCM_RATE_48000 |
+			    SNDRV_PCM_RATE_88200 |
+			    SNDRV_PCM_RATE_96000;
+
+	return 0;
+}
+
+static int oxfw_open(struct snd_pcm_substream *substream)
+{
+	static const struct snd_pcm_hardware hardware = {
+		.info = SNDRV_PCM_INFO_MMAP |
+			SNDRV_PCM_INFO_MMAP_VALID |
+			SNDRV_PCM_INFO_BATCH |
+			SNDRV_PCM_INFO_INTERLEAVED |
+			SNDRV_PCM_INFO_BLOCK_TRANSFER,
+		.formats = AMDTP_OUT_PCM_FORMAT_BITS,
+		.channels_min = 2,
+		.channels_max = 2,
+		.buffer_bytes_max = 4 * 1024 * 1024,
+		.period_bytes_min = 1,
+		.period_bytes_max = UINT_MAX,
+		.periods_min = 1,
+		.periods_max = UINT_MAX,
+	};
+	struct snd_oxfw *oxfw = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	runtime->hw = hardware;
+
+	err = oxfw->device_info->pcm_constraints(runtime);
+	if (err < 0)
+		return err;
+	err = snd_pcm_limit_hw_rates(runtime);
+	if (err < 0)
+		return err;
+
+	err = amdtp_stream_add_pcm_hw_constraints(&oxfw->rx_stream, runtime);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int oxfw_close(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+static void oxfw_stop_stream(struct snd_oxfw *oxfw)
+{
+	if (amdtp_stream_running(&oxfw->rx_stream)) {
+		amdtp_stream_stop(&oxfw->rx_stream);
+		cmp_connection_break(&oxfw->in_conn);
+	}
+}
+
+static int oxfw_hw_params(struct snd_pcm_substream *substream,
+			   struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
+	int err;
+
+	mutex_lock(&oxfw->mutex);
+	oxfw_stop_stream(oxfw);
+	mutex_unlock(&oxfw->mutex);
+
+	err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
+					       params_buffer_bytes(hw_params));
+	if (err < 0)
+		goto error;
+
+	amdtp_stream_set_parameters(&oxfw->rx_stream,
+				    params_rate(hw_params),
+				    params_channels(hw_params),
+				    0);
+
+	amdtp_stream_set_pcm_format(&oxfw->rx_stream,
+				    params_format(hw_params));
+
+	err = avc_general_set_sig_fmt(oxfw->unit, params_rate(hw_params),
+				      AVC_GENERAL_PLUG_DIR_IN, 0);
+	if (err < 0) {
+		dev_err(&oxfw->unit->device, "failed to set sample rate\n");
+		goto err_buffer;
+	}
+
+	return 0;
+
+err_buffer:
+	snd_pcm_lib_free_vmalloc_buffer(substream);
+error:
+	return err;
+}
+
+static int oxfw_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
+
+	mutex_lock(&oxfw->mutex);
+	oxfw_stop_stream(oxfw);
+	mutex_unlock(&oxfw->mutex);
+
+	return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int oxfw_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
+	int err;
+
+	mutex_lock(&oxfw->mutex);
+
+	if (amdtp_streaming_error(&oxfw->rx_stream))
+		oxfw_stop_stream(oxfw);
+
+	if (!amdtp_stream_running(&oxfw->rx_stream)) {
+		err = cmp_connection_establish(&oxfw->in_conn,
+			amdtp_stream_get_max_payload(&oxfw->rx_stream));
+		if (err < 0)
+			goto err_mutex;
+
+		err = amdtp_stream_start(&oxfw->rx_stream,
+					 oxfw->in_conn.resources.channel,
+					 oxfw->in_conn.speed);
+		if (err < 0)
+			goto err_connection;
+	}
+
+	mutex_unlock(&oxfw->mutex);
+
+	amdtp_stream_pcm_prepare(&oxfw->rx_stream);
+
+	return 0;
+
+err_connection:
+	cmp_connection_break(&oxfw->in_conn);
+err_mutex:
+	mutex_unlock(&oxfw->mutex);
+
+	return err;
+}
+
+static int oxfw_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
+	struct snd_pcm_substream *pcm;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		pcm = substream;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		pcm = NULL;
+		break;
+	default:
+		return -EINVAL;
+	}
+	amdtp_stream_pcm_trigger(&oxfw->rx_stream, pcm);
+	return 0;
+}
+
+static snd_pcm_uframes_t oxfw_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
+
+	return amdtp_stream_pcm_pointer(&oxfw->rx_stream);
+}
+
+static int oxfw_create_pcm(struct snd_oxfw *oxfw)
+{
+	static struct snd_pcm_ops ops = {
+		.open      = oxfw_open,
+		.close     = oxfw_close,
+		.ioctl     = snd_pcm_lib_ioctl,
+		.hw_params = oxfw_hw_params,
+		.hw_free   = oxfw_hw_free,
+		.prepare   = oxfw_prepare,
+		.trigger   = oxfw_trigger,
+		.pointer   = oxfw_pointer,
+		.page      = snd_pcm_lib_get_vmalloc_page,
+		.mmap      = snd_pcm_lib_mmap_vmalloc,
+	};
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(oxfw->card, "OXFW970", 0, 1, 0, &pcm);
+	if (err < 0)
+		return err;
+	pcm->private_data = oxfw;
+	strcpy(pcm->name, oxfw->device_info->short_name);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &ops);
+	return 0;
+}
+
+enum control_action { CTL_READ, CTL_WRITE };
+enum control_attribute {
+	CTL_MIN		= 0x02,
+	CTL_MAX		= 0x03,
+	CTL_CURRENT	= 0x10,
+};
+
+static int oxfw_mute_command(struct snd_oxfw *oxfw, bool *value,
+			      enum control_action action)
+{
+	u8 *buf;
+	u8 response_ok;
+	int err;
+
+	buf = kmalloc(11, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	if (action == CTL_READ) {
+		buf[0] = 0x01;		/* AV/C, STATUS */
+		response_ok = 0x0c;	/*       STABLE */
+	} else {
+		buf[0] = 0x00;		/* AV/C, CONTROL */
+		response_ok = 0x09;	/*       ACCEPTED */
+	}
+	buf[1] = 0x08;			/* audio unit 0 */
+	buf[2] = 0xb8;			/* FUNCTION BLOCK */
+	buf[3] = 0x81;			/* function block type: feature */
+	buf[4] = oxfw->device_info->mute_fb_id; /* function block ID */
+	buf[5] = 0x10;			/* control attribute: current */
+	buf[6] = 0x02;			/* selector length */
+	buf[7] = 0x00;			/* audio channel number */
+	buf[8] = 0x01;			/* control selector: mute */
+	buf[9] = 0x01;			/* control data length */
+	if (action == CTL_READ)
+		buf[10] = 0xff;
+	else
+		buf[10] = *value ? 0x70 : 0x60;
+
+	err = fcp_avc_transaction(oxfw->unit, buf, 11, buf, 11, 0x3fe);
+	if (err < 0)
+		goto error;
+	if (err < 11) {
+		dev_err(&oxfw->unit->device, "short FCP response\n");
+		err = -EIO;
+		goto error;
+	}
+	if (buf[0] != response_ok) {
+		dev_err(&oxfw->unit->device, "mute command failed\n");
+		err = -EIO;
+		goto error;
+	}
+	if (action == CTL_READ)
+		*value = buf[10] == 0x70;
+
+	err = 0;
+
+error:
+	kfree(buf);
+
+	return err;
+}
+
+static int oxfw_volume_command(struct snd_oxfw *oxfw, s16 *value,
+				unsigned int channel,
+				enum control_attribute attribute,
+				enum control_action action)
+{
+	u8 *buf;
+	u8 response_ok;
+	int err;
+
+	buf = kmalloc(12, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	if (action == CTL_READ) {
+		buf[0] = 0x01;		/* AV/C, STATUS */
+		response_ok = 0x0c;	/*       STABLE */
+	} else {
+		buf[0] = 0x00;		/* AV/C, CONTROL */
+		response_ok = 0x09;	/*       ACCEPTED */
+	}
+	buf[1] = 0x08;			/* audio unit 0 */
+	buf[2] = 0xb8;			/* FUNCTION BLOCK */
+	buf[3] = 0x81;			/* function block type: feature */
+	buf[4] = oxfw->device_info->volume_fb_id; /* function block ID */
+	buf[5] = attribute;		/* control attribute */
+	buf[6] = 0x02;			/* selector length */
+	buf[7] = channel;		/* audio channel number */
+	buf[8] = 0x02;			/* control selector: volume */
+	buf[9] = 0x02;			/* control data length */
+	if (action == CTL_READ) {
+		buf[10] = 0xff;
+		buf[11] = 0xff;
+	} else {
+		buf[10] = *value >> 8;
+		buf[11] = *value;
+	}
+
+	err = fcp_avc_transaction(oxfw->unit, buf, 12, buf, 12, 0x3fe);
+	if (err < 0)
+		goto error;
+	if (err < 12) {
+		dev_err(&oxfw->unit->device, "short FCP response\n");
+		err = -EIO;
+		goto error;
+	}
+	if (buf[0] != response_ok) {
+		dev_err(&oxfw->unit->device, "volume command failed\n");
+		err = -EIO;
+		goto error;
+	}
+	if (action == CTL_READ)
+		*value = (buf[10] << 8) | buf[11];
+
+	err = 0;
+
+error:
+	kfree(buf);
+
+	return err;
+}
+
+static int oxfw_mute_get(struct snd_kcontrol *control,
+			  struct snd_ctl_elem_value *value)
+{
+	struct snd_oxfw *oxfw = control->private_data;
+
+	value->value.integer.value[0] = !oxfw->mute;
+
+	return 0;
+}
+
+static int oxfw_mute_put(struct snd_kcontrol *control,
+			  struct snd_ctl_elem_value *value)
+{
+	struct snd_oxfw *oxfw = control->private_data;
+	bool mute;
+	int err;
+
+	mute = !value->value.integer.value[0];
+
+	if (mute == oxfw->mute)
+		return 0;
+
+	err = oxfw_mute_command(oxfw, &mute, CTL_WRITE);
+	if (err < 0)
+		return err;
+	oxfw->mute = mute;
+
+	return 1;
+}
+
+static int oxfw_volume_info(struct snd_kcontrol *control,
+			     struct snd_ctl_elem_info *info)
+{
+	struct snd_oxfw *oxfw = control->private_data;
+
+	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	info->count = oxfw->device_info->mixer_channels;
+	info->value.integer.min = oxfw->volume_min;
+	info->value.integer.max = oxfw->volume_max;
+
+	return 0;
+}
+
+static const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 };
+
+static int oxfw_volume_get(struct snd_kcontrol *control,
+			    struct snd_ctl_elem_value *value)
+{
+	struct snd_oxfw *oxfw = control->private_data;
+	unsigned int i;
+
+	for (i = 0; i < oxfw->device_info->mixer_channels; ++i)
+		value->value.integer.value[channel_map[i]] = oxfw->volume[i];
+
+	return 0;
+}
+
+static int oxfw_volume_put(struct snd_kcontrol *control,
+			  struct snd_ctl_elem_value *value)
+{
+	struct snd_oxfw *oxfw = control->private_data;
+	unsigned int i, changed_channels;
+	bool equal_values = true;
+	s16 volume;
+	int err;
+
+	for (i = 0; i < oxfw->device_info->mixer_channels; ++i) {
+		if (value->value.integer.value[i] < oxfw->volume_min ||
+		    value->value.integer.value[i] > oxfw->volume_max)
+			return -EINVAL;
+		if (value->value.integer.value[i] !=
+		    value->value.integer.value[0])
+			equal_values = false;
+	}
+
+	changed_channels = 0;
+	for (i = 0; i < oxfw->device_info->mixer_channels; ++i)
+		if (value->value.integer.value[channel_map[i]] !=
+							oxfw->volume[i])
+			changed_channels |= 1 << (i + 1);
+
+	if (equal_values && changed_channels != 0)
+		changed_channels = 1 << 0;
+
+	for (i = 0; i <= oxfw->device_info->mixer_channels; ++i) {
+		volume = value->value.integer.value[channel_map[i ? i - 1 : 0]];
+		if (changed_channels & (1 << i)) {
+			err = oxfw_volume_command(oxfw, &volume, i,
+						   CTL_CURRENT, CTL_WRITE);
+			if (err < 0)
+				return err;
+		}
+		if (i > 0)
+			oxfw->volume[i - 1] = volume;
+	}
+
+	return changed_channels != 0;
+}
+
+static int oxfw_create_mixer(struct snd_oxfw *oxfw)
+{
+	static const struct snd_kcontrol_new controls[] = {
+		{
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.name = "PCM Playback Switch",
+			.info = snd_ctl_boolean_mono_info,
+			.get = oxfw_mute_get,
+			.put = oxfw_mute_put,
+		},
+		{
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.name = "PCM Playback Volume",
+			.info = oxfw_volume_info,
+			.get = oxfw_volume_get,
+			.put = oxfw_volume_put,
+		},
+	};
+	unsigned int i, first_ch;
+	int err;
+
+	err = oxfw_volume_command(oxfw, &oxfw->volume_min,
+				   0, CTL_MIN, CTL_READ);
+	if (err < 0)
+		return err;
+	err = oxfw_volume_command(oxfw, &oxfw->volume_max,
+				   0, CTL_MAX, CTL_READ);
+	if (err < 0)
+		return err;
+
+	err = oxfw_mute_command(oxfw, &oxfw->mute, CTL_READ);
+	if (err < 0)
+		return err;
+
+	first_ch = oxfw->device_info->mixer_channels == 1 ? 0 : 1;
+	for (i = 0; i < oxfw->device_info->mixer_channels; ++i) {
+		err = oxfw_volume_command(oxfw, &oxfw->volume[i],
+					   first_ch + i, CTL_CURRENT, CTL_READ);
+		if (err < 0)
+			return err;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(controls); ++i) {
+		err = snd_ctl_add(oxfw->card,
+				  snd_ctl_new1(&controls[i], oxfw));
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+static u32 oxfw_read_firmware_version(struct fw_unit *unit)
+{
+	__be32 data;
+	int err;
+
+	err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST,
+				 OXFORD_FIRMWARE_ID_ADDRESS, &data, 4, 0);
+	return err >= 0 ? be32_to_cpu(data) : 0;
+}
+
+static void oxfw_card_free(struct snd_card *card)
+{
+	struct snd_oxfw *oxfw = card->private_data;
+
+	amdtp_stream_destroy(&oxfw->rx_stream);
+	cmp_connection_destroy(&oxfw->in_conn);
+	fw_unit_put(oxfw->unit);
+	mutex_destroy(&oxfw->mutex);
+}
+
+static int oxfw_probe(struct fw_unit *unit,
+		       const struct ieee1394_device_id *id)
+{
+	struct fw_device *fw_dev = fw_parent_device(unit);
+	struct snd_card *card;
+	struct snd_oxfw *oxfw;
+	u32 firmware;
+	int err;
+
+	err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE,
+			   sizeof(*oxfw), &card);
+	if (err < 0)
+		return err;
+
+	oxfw = card->private_data;
+	oxfw->card = card;
+	mutex_init(&oxfw->mutex);
+	oxfw->unit = fw_unit_get(unit);
+	oxfw->device_info = (const struct device_info *)id->driver_data;
+
+	err = cmp_connection_init(&oxfw->in_conn, unit, CMP_INPUT, 0);
+	if (err < 0)
+		goto err_unit;
+
+	err = amdtp_stream_init(&oxfw->rx_stream, unit, AMDTP_OUT_STREAM,
+				CIP_NONBLOCKING);
+	if (err < 0)
+		goto err_connection;
+
+	card->private_free = oxfw_card_free;
+
+	strcpy(card->driver, oxfw->device_info->driver_name);
+	strcpy(card->shortname, oxfw->device_info->short_name);
+	firmware = oxfw_read_firmware_version(unit);
+	snprintf(card->longname, sizeof(card->longname),
+		 "%s (OXFW%x %04x), GUID %08x%08x at %s, S%d",
+		 oxfw->device_info->long_name,
+		 firmware >> 20, firmware & 0xffff,
+		 fw_dev->config_rom[3], fw_dev->config_rom[4],
+		 dev_name(&unit->device), 100 << fw_dev->max_speed);
+	strcpy(card->mixername, "OXFW970");
+
+	err = oxfw_create_pcm(oxfw);
+	if (err < 0)
+		goto error;
+
+	err = oxfw_create_mixer(oxfw);
+	if (err < 0)
+		goto error;
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto error;
+
+	dev_set_drvdata(&unit->device, oxfw);
+
+	return 0;
+
+err_connection:
+	cmp_connection_destroy(&oxfw->in_conn);
+err_unit:
+	fw_unit_put(oxfw->unit);
+	mutex_destroy(&oxfw->mutex);
+error:
+	snd_card_free(card);
+	return err;
+}
+
+static void oxfw_bus_reset(struct fw_unit *unit)
+{
+	struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
+
+	fcp_bus_reset(oxfw->unit);
+
+	if (cmp_connection_update(&oxfw->in_conn) < 0) {
+		amdtp_stream_pcm_abort(&oxfw->rx_stream);
+		mutex_lock(&oxfw->mutex);
+		oxfw_stop_stream(oxfw);
+		mutex_unlock(&oxfw->mutex);
+		return;
+	}
+
+	amdtp_stream_update(&oxfw->rx_stream);
+}
+
+static void oxfw_remove(struct fw_unit *unit)
+{
+	struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
+
+	amdtp_stream_pcm_abort(&oxfw->rx_stream);
+	snd_card_disconnect(oxfw->card);
+
+	mutex_lock(&oxfw->mutex);
+	oxfw_stop_stream(oxfw);
+	mutex_unlock(&oxfw->mutex);
+
+	snd_card_free_when_closed(oxfw->card);
+}
+
+static const struct device_info griffin_firewave = {
+	.driver_name = "FireWave",
+	.short_name  = "FireWave",
+	.long_name   = "Griffin FireWave Surround",
+	.pcm_constraints = firewave_constraints,
+	.mixer_channels = 6,
+	.mute_fb_id   = 0x01,
+	.volume_fb_id = 0x02,
+};
+
+static const struct device_info lacie_speakers = {
+	.driver_name = "FWSpeakers",
+	.short_name  = "FireWire Speakers",
+	.long_name   = "LaCie FireWire Speakers",
+	.pcm_constraints = lacie_speakers_constraints,
+	.mixer_channels = 1,
+	.mute_fb_id   = 0x01,
+	.volume_fb_id = 0x01,
+};
+
+static const struct ieee1394_device_id oxfw_id_table[] = {
+	{
+		.match_flags  = IEEE1394_MATCH_VENDOR_ID |
+				IEEE1394_MATCH_MODEL_ID |
+				IEEE1394_MATCH_SPECIFIER_ID |
+				IEEE1394_MATCH_VERSION,
+		.vendor_id    = VENDOR_GRIFFIN,
+		.model_id     = 0x00f970,
+		.specifier_id = SPECIFIER_1394TA,
+		.version      = VERSION_AVC,
+		.driver_data  = (kernel_ulong_t)&griffin_firewave,
+	},
+	{
+		.match_flags  = IEEE1394_MATCH_VENDOR_ID |
+				IEEE1394_MATCH_MODEL_ID |
+				IEEE1394_MATCH_SPECIFIER_ID |
+				IEEE1394_MATCH_VERSION,
+		.vendor_id    = VENDOR_LACIE,
+		.model_id     = 0x00f970,
+		.specifier_id = SPECIFIER_1394TA,
+		.version      = VERSION_AVC,
+		.driver_data  = (kernel_ulong_t)&lacie_speakers,
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(ieee1394, oxfw_id_table);
+
+static struct fw_driver oxfw_driver = {
+	.driver   = {
+		.owner	= THIS_MODULE,
+		.name	= KBUILD_MODNAME,
+		.bus	= &fw_bus_type,
+	},
+	.probe    = oxfw_probe,
+	.update   = oxfw_bus_reset,
+	.remove   = oxfw_remove,
+	.id_table = oxfw_id_table,
+};
+
+static int __init snd_oxfw_init(void)
+{
+	return driver_register(&oxfw_driver.driver);
+}
+
+static void __exit snd_oxfw_exit(void)
+{
+	driver_unregister(&oxfw_driver.driver);
+}
+
+module_init(snd_oxfw_init);
+module_exit(snd_oxfw_exit);
diff --git a/sound/firewire/speakers.c b/sound/firewire/speakers.c
deleted file mode 100644
index 8b362e7..0000000
--- a/sound/firewire/speakers.c
+++ /dev/null
@@ -1,792 +0,0 @@
-/*
- * OXFW970-based speakers driver
- *
- * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
- * Licensed under the terms of the GNU General Public License, version 2.
- */
-
-#include <linux/device.h>
-#include <linux/firewire.h>
-#include <linux/firewire-constants.h>
-#include <linux/module.h>
-#include <linux/mod_devicetable.h>
-#include <linux/mutex.h>
-#include <linux/slab.h>
-#include <sound/control.h>
-#include <sound/core.h>
-#include <sound/initval.h>
-#include <sound/pcm.h>
-#include <sound/pcm_params.h>
-#include "cmp.h"
-#include "fcp.h"
-#include "amdtp.h"
-#include "lib.h"
-
-#define OXFORD_FIRMWARE_ID_ADDRESS	(CSR_REGISTER_BASE + 0x50000)
-/* 0x970?vvvv or 0x971?vvvv, where vvvv = firmware version */
-
-#define OXFORD_HARDWARE_ID_ADDRESS	(CSR_REGISTER_BASE + 0x90020)
-#define OXFORD_HARDWARE_ID_OXFW970	0x39443841
-#define OXFORD_HARDWARE_ID_OXFW971	0x39373100
-
-#define VENDOR_GRIFFIN		0x001292
-#define VENDOR_LACIE		0x00d04b
-
-#define SPECIFIER_1394TA	0x00a02d
-#define VERSION_AVC		0x010001
-
-struct device_info {
-	const char *driver_name;
-	const char *short_name;
-	const char *long_name;
-	int (*pcm_constraints)(struct snd_pcm_runtime *runtime);
-	unsigned int mixer_channels;
-	u8 mute_fb_id;
-	u8 volume_fb_id;
-};
-
-struct fwspk {
-	struct snd_card *card;
-	struct fw_unit *unit;
-	const struct device_info *device_info;
-	struct mutex mutex;
-	struct cmp_connection connection;
-	struct amdtp_stream stream;
-	bool mute;
-	s16 volume[6];
-	s16 volume_min;
-	s16 volume_max;
-};
-
-MODULE_DESCRIPTION("FireWire speakers driver");
-MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
-MODULE_LICENSE("GPL v2");
-
-static int firewave_rate_constraint(struct snd_pcm_hw_params *params,
-				    struct snd_pcm_hw_rule *rule)
-{
-	static unsigned int stereo_rates[] = { 48000, 96000 };
-	struct snd_interval *channels =
-			hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
-	struct snd_interval *rate =
-			hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
-
-	/* two channels work only at 48/96 kHz */
-	if (snd_interval_max(channels) < 6)
-		return snd_interval_list(rate, 2, stereo_rates, 0);
-	return 0;
-}
-
-static int firewave_channels_constraint(struct snd_pcm_hw_params *params,
-					struct snd_pcm_hw_rule *rule)
-{
-	static const struct snd_interval all_channels = { .min = 6, .max = 6 };
-	struct snd_interval *rate =
-			hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
-	struct snd_interval *channels =
-			hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
-
-	/* 32/44.1 kHz work only with all six channels */
-	if (snd_interval_max(rate) < 48000)
-		return snd_interval_refine(channels, &all_channels);
-	return 0;
-}
-
-static int firewave_constraints(struct snd_pcm_runtime *runtime)
-{
-	static unsigned int channels_list[] = { 2, 6 };
-	static struct snd_pcm_hw_constraint_list channels_list_constraint = {
-		.count = 2,
-		.list = channels_list,
-	};
-	int err;
-
-	runtime->hw.rates = SNDRV_PCM_RATE_32000 |
-			    SNDRV_PCM_RATE_44100 |
-			    SNDRV_PCM_RATE_48000 |
-			    SNDRV_PCM_RATE_96000;
-	runtime->hw.channels_max = 6;
-
-	err = snd_pcm_hw_constraint_list(runtime, 0,
-					 SNDRV_PCM_HW_PARAM_CHANNELS,
-					 &channels_list_constraint);
-	if (err < 0)
-		return err;
-	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
-				  firewave_rate_constraint, NULL,
-				  SNDRV_PCM_HW_PARAM_CHANNELS, -1);
-	if (err < 0)
-		return err;
-	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
-				  firewave_channels_constraint, NULL,
-				  SNDRV_PCM_HW_PARAM_RATE, -1);
-	if (err < 0)
-		return err;
-
-	return 0;
-}
-
-static int lacie_speakers_constraints(struct snd_pcm_runtime *runtime)
-{
-	runtime->hw.rates = SNDRV_PCM_RATE_32000 |
-			    SNDRV_PCM_RATE_44100 |
-			    SNDRV_PCM_RATE_48000 |
-			    SNDRV_PCM_RATE_88200 |
-			    SNDRV_PCM_RATE_96000;
-
-	return 0;
-}
-
-static int fwspk_open(struct snd_pcm_substream *substream)
-{
-	static const struct snd_pcm_hardware hardware = {
-		.info = SNDRV_PCM_INFO_MMAP |
-			SNDRV_PCM_INFO_MMAP_VALID |
-			SNDRV_PCM_INFO_BATCH |
-			SNDRV_PCM_INFO_INTERLEAVED |
-			SNDRV_PCM_INFO_BLOCK_TRANSFER,
-		.formats = AMDTP_OUT_PCM_FORMAT_BITS,
-		.channels_min = 2,
-		.channels_max = 2,
-		.buffer_bytes_max = 4 * 1024 * 1024,
-		.period_bytes_min = 1,
-		.period_bytes_max = UINT_MAX,
-		.periods_min = 1,
-		.periods_max = UINT_MAX,
-	};
-	struct fwspk *fwspk = substream->private_data;
-	struct snd_pcm_runtime *runtime = substream->runtime;
-	int err;
-
-	runtime->hw = hardware;
-
-	err = fwspk->device_info->pcm_constraints(runtime);
-	if (err < 0)
-		return err;
-	err = snd_pcm_limit_hw_rates(runtime);
-	if (err < 0)
-		return err;
-
-	err = amdtp_stream_add_pcm_hw_constraints(fwspk->stream, runtime);
-	if (err < 0)
-		return err;
-
-	return 0;
-}
-
-static int fwspk_close(struct snd_pcm_substream *substream)
-{
-	return 0;
-}
-
-static void fwspk_stop_stream(struct fwspk *fwspk)
-{
-	if (amdtp_stream_running(&fwspk->stream)) {
-		amdtp_stream_stop(&fwspk->stream);
-		cmp_connection_break(&fwspk->connection);
-	}
-}
-
-static int fwspk_hw_params(struct snd_pcm_substream *substream,
-			   struct snd_pcm_hw_params *hw_params)
-{
-	struct fwspk *fwspk = substream->private_data;
-	int err;
-
-	mutex_lock(&fwspk->mutex);
-	fwspk_stop_stream(fwspk);
-	mutex_unlock(&fwspk->mutex);
-
-	err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
-					       params_buffer_bytes(hw_params));
-	if (err < 0)
-		goto error;
-
-	amdtp_stream_set_parameters(&fwspk->stream,
-				    params_rate(hw_params),
-				    params_channels(hw_params),
-				    0);
-
-	amdtp_stream_set_pcm_format(&fwspk->stream,
-				    params_format(hw_params));
-
-	err = avc_general_set_sig_fmt(fwspk->unit, params_rate(hw_params),
-				      AVC_GENERAL_PLUG_DIR_IN, 0);
-	if (err < 0) {
-		dev_err(&fwspk->unit->device, "failed to set sample rate\n");
-		goto err_buffer;
-	}
-
-	return 0;
-
-err_buffer:
-	snd_pcm_lib_free_vmalloc_buffer(substream);
-error:
-	return err;
-}
-
-static int fwspk_hw_free(struct snd_pcm_substream *substream)
-{
-	struct fwspk *fwspk = substream->private_data;
-
-	mutex_lock(&fwspk->mutex);
-	fwspk_stop_stream(fwspk);
-	mutex_unlock(&fwspk->mutex);
-
-	return snd_pcm_lib_free_vmalloc_buffer(substream);
-}
-
-static int fwspk_prepare(struct snd_pcm_substream *substream)
-{
-	struct fwspk *fwspk = substream->private_data;
-	int err;
-
-	mutex_lock(&fwspk->mutex);
-
-	if (amdtp_streaming_error(&fwspk->stream))
-		fwspk_stop_stream(fwspk);
-
-	if (!amdtp_stream_running(&fwspk->stream)) {
-		err = cmp_connection_establish(&fwspk->connection,
-			amdtp_stream_get_max_payload(&fwspk->stream));
-		if (err < 0)
-			goto err_mutex;
-
-		err = amdtp_stream_start(&fwspk->stream,
-					 fwspk->connection.resources.channel,
-					 fwspk->connection.speed);
-		if (err < 0)
-			goto err_connection;
-	}
-
-	mutex_unlock(&fwspk->mutex);
-
-	amdtp_stream_pcm_prepare(&fwspk->stream);
-
-	return 0;
-
-err_connection:
-	cmp_connection_break(&fwspk->connection);
-err_mutex:
-	mutex_unlock(&fwspk->mutex);
-
-	return err;
-}
-
-static int fwspk_trigger(struct snd_pcm_substream *substream, int cmd)
-{
-	struct fwspk *fwspk = substream->private_data;
-	struct snd_pcm_substream *pcm;
-
-	switch (cmd) {
-	case SNDRV_PCM_TRIGGER_START:
-		pcm = substream;
-		break;
-	case SNDRV_PCM_TRIGGER_STOP:
-		pcm = NULL;
-		break;
-	default:
-		return -EINVAL;
-	}
-	amdtp_stream_pcm_trigger(&fwspk->stream, pcm);
-	return 0;
-}
-
-static snd_pcm_uframes_t fwspk_pointer(struct snd_pcm_substream *substream)
-{
-	struct fwspk *fwspk = substream->private_data;
-
-	return amdtp_stream_pcm_pointer(&fwspk->stream);
-}
-
-static int fwspk_create_pcm(struct fwspk *fwspk)
-{
-	static struct snd_pcm_ops ops = {
-		.open      = fwspk_open,
-		.close     = fwspk_close,
-		.ioctl     = snd_pcm_lib_ioctl,
-		.hw_params = fwspk_hw_params,
-		.hw_free   = fwspk_hw_free,
-		.prepare   = fwspk_prepare,
-		.trigger   = fwspk_trigger,
-		.pointer   = fwspk_pointer,
-		.page      = snd_pcm_lib_get_vmalloc_page,
-		.mmap      = snd_pcm_lib_mmap_vmalloc,
-	};
-	struct snd_pcm *pcm;
-	int err;
-
-	err = snd_pcm_new(fwspk->card, "OXFW970", 0, 1, 0, &pcm);
-	if (err < 0)
-		return err;
-	pcm->private_data = fwspk;
-	strcpy(pcm->name, fwspk->device_info->short_name);
-	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &ops);
-	return 0;
-}
-
-enum control_action { CTL_READ, CTL_WRITE };
-enum control_attribute {
-	CTL_MIN		= 0x02,
-	CTL_MAX		= 0x03,
-	CTL_CURRENT	= 0x10,
-};
-
-static int fwspk_mute_command(struct fwspk *fwspk, bool *value,
-			      enum control_action action)
-{
-	u8 *buf;
-	u8 response_ok;
-	int err;
-
-	buf = kmalloc(11, GFP_KERNEL);
-	if (!buf)
-		return -ENOMEM;
-
-	if (action == CTL_READ) {
-		buf[0] = 0x01;		/* AV/C, STATUS */
-		response_ok = 0x0c;	/*       STABLE */
-	} else {
-		buf[0] = 0x00;		/* AV/C, CONTROL */
-		response_ok = 0x09;	/*       ACCEPTED */
-	}
-	buf[1] = 0x08;			/* audio unit 0 */
-	buf[2] = 0xb8;			/* FUNCTION BLOCK */
-	buf[3] = 0x81;			/* function block type: feature */
-	buf[4] = fwspk->device_info->mute_fb_id; /* function block ID */
-	buf[5] = 0x10;			/* control attribute: current */
-	buf[6] = 0x02;			/* selector length */
-	buf[7] = 0x00;			/* audio channel number */
-	buf[8] = 0x01;			/* control selector: mute */
-	buf[9] = 0x01;			/* control data length */
-	if (action == CTL_READ)
-		buf[10] = 0xff;
-	else
-		buf[10] = *value ? 0x70 : 0x60;
-
-	err = fcp_avc_transaction(fwspk->unit, buf, 11, buf, 11, 0x3fe);
-	if (err < 0)
-		goto error;
-	if (err < 11) {
-		dev_err(&fwspk->unit->device, "short FCP response\n");
-		err = -EIO;
-		goto error;
-	}
-	if (buf[0] != response_ok) {
-		dev_err(&fwspk->unit->device, "mute command failed\n");
-		err = -EIO;
-		goto error;
-	}
-	if (action == CTL_READ)
-		*value = buf[10] == 0x70;
-
-	err = 0;
-
-error:
-	kfree(buf);
-
-	return err;
-}
-
-static int fwspk_volume_command(struct fwspk *fwspk, s16 *value,
-				unsigned int channel,
-				enum control_attribute attribute,
-				enum control_action action)
-{
-	u8 *buf;
-	u8 response_ok;
-	int err;
-
-	buf = kmalloc(12, GFP_KERNEL);
-	if (!buf)
-		return -ENOMEM;
-
-	if (action == CTL_READ) {
-		buf[0] = 0x01;		/* AV/C, STATUS */
-		response_ok = 0x0c;	/*       STABLE */
-	} else {
-		buf[0] = 0x00;		/* AV/C, CONTROL */
-		response_ok = 0x09;	/*       ACCEPTED */
-	}
-	buf[1] = 0x08;			/* audio unit 0 */
-	buf[2] = 0xb8;			/* FUNCTION BLOCK */
-	buf[3] = 0x81;			/* function block type: feature */
-	buf[4] = fwspk->device_info->volume_fb_id; /* function block ID */
-	buf[5] = attribute;		/* control attribute */
-	buf[6] = 0x02;			/* selector length */
-	buf[7] = channel;		/* audio channel number */
-	buf[8] = 0x02;			/* control selector: volume */
-	buf[9] = 0x02;			/* control data length */
-	if (action == CTL_READ) {
-		buf[10] = 0xff;
-		buf[11] = 0xff;
-	} else {
-		buf[10] = *value >> 8;
-		buf[11] = *value;
-	}
-
-	err = fcp_avc_transaction(fwspk->unit, buf, 12, buf, 12, 0x3fe);
-	if (err < 0)
-		goto error;
-	if (err < 12) {
-		dev_err(&fwspk->unit->device, "short FCP response\n");
-		err = -EIO;
-		goto error;
-	}
-	if (buf[0] != response_ok) {
-		dev_err(&fwspk->unit->device, "volume command failed\n");
-		err = -EIO;
-		goto error;
-	}
-	if (action == CTL_READ)
-		*value = (buf[10] << 8) | buf[11];
-
-	err = 0;
-
-error:
-	kfree(buf);
-
-	return err;
-}
-
-static int fwspk_mute_get(struct snd_kcontrol *control,
-			  struct snd_ctl_elem_value *value)
-{
-	struct fwspk *fwspk = control->private_data;
-
-	value->value.integer.value[0] = !fwspk->mute;
-
-	return 0;
-}
-
-static int fwspk_mute_put(struct snd_kcontrol *control,
-			  struct snd_ctl_elem_value *value)
-{
-	struct fwspk *fwspk = control->private_data;
-	bool mute;
-	int err;
-
-	mute = !value->value.integer.value[0];
-
-	if (mute == fwspk->mute)
-		return 0;
-
-	err = fwspk_mute_command(fwspk, &mute, CTL_WRITE);
-	if (err < 0)
-		return err;
-	fwspk->mute = mute;
-
-	return 1;
-}
-
-static int fwspk_volume_info(struct snd_kcontrol *control,
-			     struct snd_ctl_elem_info *info)
-{
-	struct fwspk *fwspk = control->private_data;
-
-	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
-	info->count = fwspk->device_info->mixer_channels;
-	info->value.integer.min = fwspk->volume_min;
-	info->value.integer.max = fwspk->volume_max;
-
-	return 0;
-}
-
-static const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 };
-
-static int fwspk_volume_get(struct snd_kcontrol *control,
-			    struct snd_ctl_elem_value *value)
-{
-	struct fwspk *fwspk = control->private_data;
-	unsigned int i;
-
-	for (i = 0; i < fwspk->device_info->mixer_channels; ++i)
-		value->value.integer.value[channel_map[i]] = fwspk->volume[i];
-
-	return 0;
-}
-
-static int fwspk_volume_put(struct snd_kcontrol *control,
-			  struct snd_ctl_elem_value *value)
-{
-	struct fwspk *fwspk = control->private_data;
-	unsigned int i, changed_channels;
-	bool equal_values = true;
-	s16 volume;
-	int err;
-
-	for (i = 0; i < fwspk->device_info->mixer_channels; ++i) {
-		if (value->value.integer.value[i] < fwspk->volume_min ||
-		    value->value.integer.value[i] > fwspk->volume_max)
-			return -EINVAL;
-		if (value->value.integer.value[i] !=
-		    value->value.integer.value[0])
-			equal_values = false;
-	}
-
-	changed_channels = 0;
-	for (i = 0; i < fwspk->device_info->mixer_channels; ++i)
-		if (value->value.integer.value[channel_map[i]] !=
-							fwspk->volume[i])
-			changed_channels |= 1 << (i + 1);
-
-	if (equal_values && changed_channels != 0)
-		changed_channels = 1 << 0;
-
-	for (i = 0; i <= fwspk->device_info->mixer_channels; ++i) {
-		volume = value->value.integer.value[channel_map[i ? i - 1 : 0]];
-		if (changed_channels & (1 << i)) {
-			err = fwspk_volume_command(fwspk, &volume, i,
-						   CTL_CURRENT, CTL_WRITE);
-			if (err < 0)
-				return err;
-		}
-		if (i > 0)
-			fwspk->volume[i - 1] = volume;
-	}
-
-	return changed_channels != 0;
-}
-
-static int fwspk_create_mixer(struct fwspk *fwspk)
-{
-	static const struct snd_kcontrol_new controls[] = {
-		{
-			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-			.name = "PCM Playback Switch",
-			.info = snd_ctl_boolean_mono_info,
-			.get = fwspk_mute_get,
-			.put = fwspk_mute_put,
-		},
-		{
-			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-			.name = "PCM Playback Volume",
-			.info = fwspk_volume_info,
-			.get = fwspk_volume_get,
-			.put = fwspk_volume_put,
-		},
-	};
-	unsigned int i, first_ch;
-	int err;
-
-	err = fwspk_volume_command(fwspk, &fwspk->volume_min,
-				   0, CTL_MIN, CTL_READ);
-	if (err < 0)
-		return err;
-	err = fwspk_volume_command(fwspk, &fwspk->volume_max,
-				   0, CTL_MAX, CTL_READ);
-	if (err < 0)
-		return err;
-
-	err = fwspk_mute_command(fwspk, &fwspk->mute, CTL_READ);
-	if (err < 0)
-		return err;
-
-	first_ch = fwspk->device_info->mixer_channels == 1 ? 0 : 1;
-	for (i = 0; i < fwspk->device_info->mixer_channels; ++i) {
-		err = fwspk_volume_command(fwspk, &fwspk->volume[i],
-					   first_ch + i, CTL_CURRENT, CTL_READ);
-		if (err < 0)
-			return err;
-	}
-
-	for (i = 0; i < ARRAY_SIZE(controls); ++i) {
-		err = snd_ctl_add(fwspk->card,
-				  snd_ctl_new1(&controls[i], fwspk));
-		if (err < 0)
-			return err;
-	}
-
-	return 0;
-}
-
-static u32 fwspk_read_firmware_version(struct fw_unit *unit)
-{
-	__be32 data;
-	int err;
-
-	err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST,
-				 OXFORD_FIRMWARE_ID_ADDRESS, &data, 4, 0);
-	return err >= 0 ? be32_to_cpu(data) : 0;
-}
-
-static void fwspk_card_free(struct snd_card *card)
-{
-	struct fwspk *fwspk = card->private_data;
-
-	amdtp_stream_destroy(&fwspk->stream);
-	cmp_connection_destroy(&fwspk->connection);
-	fw_unit_put(fwspk->unit);
-	mutex_destroy(&fwspk->mutex);
-}
-
-static int fwspk_probe(struct fw_unit *unit,
-		       const struct ieee1394_device_id *id)
-{
-	struct fw_device *fw_dev = fw_parent_device(unit);
-	struct snd_card *card;
-	struct fwspk *fwspk;
-	u32 firmware;
-	int err;
-
-	err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE,
-			   sizeof(*fwspk), &card);
-	if (err < 0)
-		return err;
-
-	fwspk = card->private_data;
-	fwspk->card = card;
-	mutex_init(&fwspk->mutex);
-	fwspk->unit = fw_unit_get(unit);
-	fwspk->device_info = (const struct device_info *)id->driver_data;
-
-	err = cmp_connection_init(&fwspk->connection, unit, CMP_INPUT, 0);
-	if (err < 0)
-		goto err_unit;
-
-	err = amdtp_stream_init(&fwspk->stream, unit, AMDTP_OUT_STREAM,
-				CIP_NONBLOCKING);
-	if (err < 0)
-		goto err_connection;
-
-	card->private_free = fwspk_card_free;
-
-	strcpy(card->driver, fwspk->device_info->driver_name);
-	strcpy(card->shortname, fwspk->device_info->short_name);
-	firmware = fwspk_read_firmware_version(unit);
-	snprintf(card->longname, sizeof(card->longname),
-		 "%s (OXFW%x %04x), GUID %08x%08x at %s, S%d",
-		 fwspk->device_info->long_name,
-		 firmware >> 20, firmware & 0xffff,
-		 fw_dev->config_rom[3], fw_dev->config_rom[4],
-		 dev_name(&unit->device), 100 << fw_dev->max_speed);
-	strcpy(card->mixername, "OXFW970");
-
-	err = fwspk_create_pcm(fwspk);
-	if (err < 0)
-		goto error;
-
-	err = fwspk_create_mixer(fwspk);
-	if (err < 0)
-		goto error;
-
-	err = snd_card_register(card);
-	if (err < 0)
-		goto error;
-
-	dev_set_drvdata(&unit->device, fwspk);
-
-	return 0;
-
-err_connection:
-	cmp_connection_destroy(&fwspk->connection);
-err_unit:
-	fw_unit_put(fwspk->unit);
-	mutex_destroy(&fwspk->mutex);
-error:
-	snd_card_free(card);
-	return err;
-}
-
-static void fwspk_bus_reset(struct fw_unit *unit)
-{
-	struct fwspk *fwspk = dev_get_drvdata(&unit->device);
-
-	fcp_bus_reset(fwspk->unit);
-
-	if (cmp_connection_update(&fwspk->connection) < 0) {
-		amdtp_stream_pcm_abort(&fwspk->stream);
-		mutex_lock(&fwspk->mutex);
-		fwspk_stop_stream(fwspk);
-		mutex_unlock(&fwspk->mutex);
-		return;
-	}
-
-	amdtp_stream_update(&fwspk->stream);
-}
-
-static void fwspk_remove(struct fw_unit *unit)
-{
-	struct fwspk *fwspk = dev_get_drvdata(&unit->device);
-
-	amdtp_stream_pcm_abort(&fwspk->stream);
-	snd_card_disconnect(fwspk->card);
-
-	mutex_lock(&fwspk->mutex);
-	fwspk_stop_stream(fwspk);
-	mutex_unlock(&fwspk->mutex);
-
-	snd_card_free_when_closed(fwspk->card);
-}
-
-static const struct device_info griffin_firewave = {
-	.driver_name = "FireWave",
-	.short_name  = "FireWave",
-	.long_name   = "Griffin FireWave Surround",
-	.pcm_constraints = firewave_constraints,
-	.mixer_channels = 6,
-	.mute_fb_id   = 0x01,
-	.volume_fb_id = 0x02,
-};
-
-static const struct device_info lacie_speakers = {
-	.driver_name = "FWSpeakers",
-	.short_name  = "FireWire Speakers",
-	.long_name   = "LaCie FireWire Speakers",
-	.pcm_constraints = lacie_speakers_constraints,
-	.mixer_channels = 1,
-	.mute_fb_id   = 0x01,
-	.volume_fb_id = 0x01,
-};
-
-static const struct ieee1394_device_id fwspk_id_table[] = {
-	{
-		.match_flags  = IEEE1394_MATCH_VENDOR_ID |
-				IEEE1394_MATCH_MODEL_ID |
-				IEEE1394_MATCH_SPECIFIER_ID |
-				IEEE1394_MATCH_VERSION,
-		.vendor_id    = VENDOR_GRIFFIN,
-		.model_id     = 0x00f970,
-		.specifier_id = SPECIFIER_1394TA,
-		.version      = VERSION_AVC,
-		.driver_data  = (kernel_ulong_t)&griffin_firewave,
-	},
-	{
-		.match_flags  = IEEE1394_MATCH_VENDOR_ID |
-				IEEE1394_MATCH_MODEL_ID |
-				IEEE1394_MATCH_SPECIFIER_ID |
-				IEEE1394_MATCH_VERSION,
-		.vendor_id    = VENDOR_LACIE,
-		.model_id     = 0x00f970,
-		.specifier_id = SPECIFIER_1394TA,
-		.version      = VERSION_AVC,
-		.driver_data  = (kernel_ulong_t)&lacie_speakers,
-	},
-	{ }
-};
-MODULE_DEVICE_TABLE(ieee1394, fwspk_id_table);
-
-static struct fw_driver fwspk_driver = {
-	.driver   = {
-		.owner	= THIS_MODULE,
-		.name	= KBUILD_MODNAME,
-		.bus	= &fw_bus_type,
-	},
-	.probe    = fwspk_probe,
-	.update   = fwspk_bus_reset,
-	.remove   = fwspk_remove,
-	.id_table = fwspk_id_table,
-};
-
-static int __init alsa_fwspk_init(void)
-{
-	return driver_register(&fwspk_driver.driver);
-}
-
-static void __exit alsa_fwspk_exit(void)
-{
-	driver_unregister(&fwspk_driver.driver);
-}
-
-module_init(alsa_fwspk_init);
-module_exit(alsa_fwspk_exit);
-- 
1.8.3.2


------------------------------------------------------------------------------
"Accelerate Dev Cycles with Automated Cross-Browser Testing - For FREE
Instantly run your Selenium tests across 300+ browser/OS combos.
Get unparalleled scalability from the best Selenium testing platform available
Simple to use. Nothing to install. Get started now for free."
http://p.sf.net/sfu/SauceLabs

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 02/15] oxfw: Move to its own directory
  2014-05-13 14:27 [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Takashi Sakamoto
  2014-05-13 14:27 ` [PATCH 01/15] speakers: Rename to oxfw and rename some members Takashi Sakamoto
@ 2014-05-13 14:27 ` Takashi Sakamoto
  2014-05-13 14:27 ` [PATCH 03/15] oxfw: Split stream functionality to a new file and add a header file Takashi Sakamoto
                   ` (15 subsequent siblings)
  17 siblings, 0 replies; 35+ messages in thread
From: Takashi Sakamoto @ 2014-05-13 14:27 UTC (permalink / raw)
  To: clemens; +Cc: alsa-devel, fenlason, ffado-devel, linux1394-devel

Followed commits add much codes. To make the work easy, this commit creates
own directory and move current file to it.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/Makefile      |   3 +-
 sound/firewire/oxfw.c        | 793 -------------------------------------------
 sound/firewire/oxfw/Makefile |   2 +
 sound/firewire/oxfw/oxfw.c   | 793 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 796 insertions(+), 795 deletions(-)
 delete mode 100644 sound/firewire/oxfw.c
 create mode 100644 sound/firewire/oxfw/Makefile
 create mode 100644 sound/firewire/oxfw/oxfw.c

diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile
index cec888f..e297995 100644
--- a/sound/firewire/Makefile
+++ b/sound/firewire/Makefile
@@ -1,7 +1,6 @@
 snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \
 			 fcp.o cmp.o amdtp.o
 snd-dice-objs := dice.o
-snd-oxfw-objs := oxfw.o
 snd-isight-objs := isight.o
 snd-scs1x-objs := scs1x.o
 
@@ -11,4 +10,4 @@ obj-$(CONFIG_SND_ISIGHT) += snd-isight.o
 obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o
 obj-$(CONFIG_SND_FIREWORKS) += fireworks/
 obj-$(CONFIG_SND_BEBOB) += bebob/
-obj-$(CONFIG_SND_OXFW) += snd-oxfw.o
+obj-$(CONFIG_SND_OXFW) += oxfw/
diff --git a/sound/firewire/oxfw.c b/sound/firewire/oxfw.c
deleted file mode 100644
index 2dc5a58..0000000
--- a/sound/firewire/oxfw.c
+++ /dev/null
@@ -1,793 +0,0 @@
-/*
- * oxfw.c - a part of driver for OXFW970/971 based devices
- *
- * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
- * Licensed under the terms of the GNU General Public License, version 2.
- */
-
-#include <linux/device.h>
-#include <linux/firewire.h>
-#include <linux/firewire-constants.h>
-#include <linux/module.h>
-#include <linux/mod_devicetable.h>
-#include <linux/mutex.h>
-#include <linux/slab.h>
-#include <sound/control.h>
-#include <sound/core.h>
-#include <sound/initval.h>
-#include <sound/pcm.h>
-#include <sound/pcm_params.h>
-#include "cmp.h"
-#include "fcp.h"
-#include "amdtp.h"
-#include "lib.h"
-
-#define OXFORD_FIRMWARE_ID_ADDRESS	(CSR_REGISTER_BASE + 0x50000)
-/* 0x970?vvvv or 0x971?vvvv, where vvvv = firmware version */
-
-#define OXFORD_HARDWARE_ID_ADDRESS	(CSR_REGISTER_BASE + 0x90020)
-#define OXFORD_HARDWARE_ID_OXFW970	0x39443841
-#define OXFORD_HARDWARE_ID_OXFW971	0x39373100
-
-#define VENDOR_GRIFFIN		0x001292
-#define VENDOR_LACIE		0x00d04b
-
-#define SPECIFIER_1394TA	0x00a02d
-#define VERSION_AVC		0x010001
-
-struct device_info {
-	const char *driver_name;
-	const char *short_name;
-	const char *long_name;
-	int (*pcm_constraints)(struct snd_pcm_runtime *runtime);
-	unsigned int mixer_channels;
-	u8 mute_fb_id;
-	u8 volume_fb_id;
-};
-
-struct snd_oxfw {
-	struct snd_card *card;
-	struct fw_unit *unit;
-	const struct device_info *device_info;
-	struct mutex mutex;
-	struct cmp_connection in_conn;
-	struct amdtp_stream rx_stream;
-	bool mute;
-	s16 volume[6];
-	s16 volume_min;
-	s16 volume_max;
-};
-
-MODULE_DESCRIPTION("Oxford OXFW970/971 driver");
-MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
-MODULE_LICENSE("GPL v2");
-MODULE_ALIAS("snd-firewire-speakers");
-
-static int firewave_rate_constraint(struct snd_pcm_hw_params *params,
-				    struct snd_pcm_hw_rule *rule)
-{
-	static unsigned int stereo_rates[] = { 48000, 96000 };
-	struct snd_interval *channels =
-			hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
-	struct snd_interval *rate =
-			hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
-
-	/* two channels work only at 48/96 kHz */
-	if (snd_interval_max(channels) < 6)
-		return snd_interval_list(rate, 2, stereo_rates, 0);
-	return 0;
-}
-
-static int firewave_channels_constraint(struct snd_pcm_hw_params *params,
-					struct snd_pcm_hw_rule *rule)
-{
-	static const struct snd_interval all_channels = { .min = 6, .max = 6 };
-	struct snd_interval *rate =
-			hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
-	struct snd_interval *channels =
-			hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
-
-	/* 32/44.1 kHz work only with all six channels */
-	if (snd_interval_max(rate) < 48000)
-		return snd_interval_refine(channels, &all_channels);
-	return 0;
-}
-
-static int firewave_constraints(struct snd_pcm_runtime *runtime)
-{
-	static unsigned int channels_list[] = { 2, 6 };
-	static struct snd_pcm_hw_constraint_list channels_list_constraint = {
-		.count = 2,
-		.list = channels_list,
-	};
-	int err;
-
-	runtime->hw.rates = SNDRV_PCM_RATE_32000 |
-			    SNDRV_PCM_RATE_44100 |
-			    SNDRV_PCM_RATE_48000 |
-			    SNDRV_PCM_RATE_96000;
-	runtime->hw.channels_max = 6;
-
-	err = snd_pcm_hw_constraint_list(runtime, 0,
-					 SNDRV_PCM_HW_PARAM_CHANNELS,
-					 &channels_list_constraint);
-	if (err < 0)
-		return err;
-	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
-				  firewave_rate_constraint, NULL,
-				  SNDRV_PCM_HW_PARAM_CHANNELS, -1);
-	if (err < 0)
-		return err;
-	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
-				  firewave_channels_constraint, NULL,
-				  SNDRV_PCM_HW_PARAM_RATE, -1);
-	if (err < 0)
-		return err;
-
-	return 0;
-}
-
-static int lacie_speakers_constraints(struct snd_pcm_runtime *runtime)
-{
-	runtime->hw.rates = SNDRV_PCM_RATE_32000 |
-			    SNDRV_PCM_RATE_44100 |
-			    SNDRV_PCM_RATE_48000 |
-			    SNDRV_PCM_RATE_88200 |
-			    SNDRV_PCM_RATE_96000;
-
-	return 0;
-}
-
-static int oxfw_open(struct snd_pcm_substream *substream)
-{
-	static const struct snd_pcm_hardware hardware = {
-		.info = SNDRV_PCM_INFO_MMAP |
-			SNDRV_PCM_INFO_MMAP_VALID |
-			SNDRV_PCM_INFO_BATCH |
-			SNDRV_PCM_INFO_INTERLEAVED |
-			SNDRV_PCM_INFO_BLOCK_TRANSFER,
-		.formats = AMDTP_OUT_PCM_FORMAT_BITS,
-		.channels_min = 2,
-		.channels_max = 2,
-		.buffer_bytes_max = 4 * 1024 * 1024,
-		.period_bytes_min = 1,
-		.period_bytes_max = UINT_MAX,
-		.periods_min = 1,
-		.periods_max = UINT_MAX,
-	};
-	struct snd_oxfw *oxfw = substream->private_data;
-	struct snd_pcm_runtime *runtime = substream->runtime;
-	int err;
-
-	runtime->hw = hardware;
-
-	err = oxfw->device_info->pcm_constraints(runtime);
-	if (err < 0)
-		return err;
-	err = snd_pcm_limit_hw_rates(runtime);
-	if (err < 0)
-		return err;
-
-	err = amdtp_stream_add_pcm_hw_constraints(&oxfw->rx_stream, runtime);
-	if (err < 0)
-		return err;
-
-	return 0;
-}
-
-static int oxfw_close(struct snd_pcm_substream *substream)
-{
-	return 0;
-}
-
-static void oxfw_stop_stream(struct snd_oxfw *oxfw)
-{
-	if (amdtp_stream_running(&oxfw->rx_stream)) {
-		amdtp_stream_stop(&oxfw->rx_stream);
-		cmp_connection_break(&oxfw->in_conn);
-	}
-}
-
-static int oxfw_hw_params(struct snd_pcm_substream *substream,
-			   struct snd_pcm_hw_params *hw_params)
-{
-	struct snd_oxfw *oxfw = substream->private_data;
-	int err;
-
-	mutex_lock(&oxfw->mutex);
-	oxfw_stop_stream(oxfw);
-	mutex_unlock(&oxfw->mutex);
-
-	err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
-					       params_buffer_bytes(hw_params));
-	if (err < 0)
-		goto error;
-
-	amdtp_stream_set_parameters(&oxfw->rx_stream,
-				    params_rate(hw_params),
-				    params_channels(hw_params),
-				    0);
-
-	amdtp_stream_set_pcm_format(&oxfw->rx_stream,
-				    params_format(hw_params));
-
-	err = avc_general_set_sig_fmt(oxfw->unit, params_rate(hw_params),
-				      AVC_GENERAL_PLUG_DIR_IN, 0);
-	if (err < 0) {
-		dev_err(&oxfw->unit->device, "failed to set sample rate\n");
-		goto err_buffer;
-	}
-
-	return 0;
-
-err_buffer:
-	snd_pcm_lib_free_vmalloc_buffer(substream);
-error:
-	return err;
-}
-
-static int oxfw_hw_free(struct snd_pcm_substream *substream)
-{
-	struct snd_oxfw *oxfw = substream->private_data;
-
-	mutex_lock(&oxfw->mutex);
-	oxfw_stop_stream(oxfw);
-	mutex_unlock(&oxfw->mutex);
-
-	return snd_pcm_lib_free_vmalloc_buffer(substream);
-}
-
-static int oxfw_prepare(struct snd_pcm_substream *substream)
-{
-	struct snd_oxfw *oxfw = substream->private_data;
-	int err;
-
-	mutex_lock(&oxfw->mutex);
-
-	if (amdtp_streaming_error(&oxfw->rx_stream))
-		oxfw_stop_stream(oxfw);
-
-	if (!amdtp_stream_running(&oxfw->rx_stream)) {
-		err = cmp_connection_establish(&oxfw->in_conn,
-			amdtp_stream_get_max_payload(&oxfw->rx_stream));
-		if (err < 0)
-			goto err_mutex;
-
-		err = amdtp_stream_start(&oxfw->rx_stream,
-					 oxfw->in_conn.resources.channel,
-					 oxfw->in_conn.speed);
-		if (err < 0)
-			goto err_connection;
-	}
-
-	mutex_unlock(&oxfw->mutex);
-
-	amdtp_stream_pcm_prepare(&oxfw->rx_stream);
-
-	return 0;
-
-err_connection:
-	cmp_connection_break(&oxfw->in_conn);
-err_mutex:
-	mutex_unlock(&oxfw->mutex);
-
-	return err;
-}
-
-static int oxfw_trigger(struct snd_pcm_substream *substream, int cmd)
-{
-	struct snd_oxfw *oxfw = substream->private_data;
-	struct snd_pcm_substream *pcm;
-
-	switch (cmd) {
-	case SNDRV_PCM_TRIGGER_START:
-		pcm = substream;
-		break;
-	case SNDRV_PCM_TRIGGER_STOP:
-		pcm = NULL;
-		break;
-	default:
-		return -EINVAL;
-	}
-	amdtp_stream_pcm_trigger(&oxfw->rx_stream, pcm);
-	return 0;
-}
-
-static snd_pcm_uframes_t oxfw_pointer(struct snd_pcm_substream *substream)
-{
-	struct snd_oxfw *oxfw = substream->private_data;
-
-	return amdtp_stream_pcm_pointer(&oxfw->rx_stream);
-}
-
-static int oxfw_create_pcm(struct snd_oxfw *oxfw)
-{
-	static struct snd_pcm_ops ops = {
-		.open      = oxfw_open,
-		.close     = oxfw_close,
-		.ioctl     = snd_pcm_lib_ioctl,
-		.hw_params = oxfw_hw_params,
-		.hw_free   = oxfw_hw_free,
-		.prepare   = oxfw_prepare,
-		.trigger   = oxfw_trigger,
-		.pointer   = oxfw_pointer,
-		.page      = snd_pcm_lib_get_vmalloc_page,
-		.mmap      = snd_pcm_lib_mmap_vmalloc,
-	};
-	struct snd_pcm *pcm;
-	int err;
-
-	err = snd_pcm_new(oxfw->card, "OXFW970", 0, 1, 0, &pcm);
-	if (err < 0)
-		return err;
-	pcm->private_data = oxfw;
-	strcpy(pcm->name, oxfw->device_info->short_name);
-	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &ops);
-	return 0;
-}
-
-enum control_action { CTL_READ, CTL_WRITE };
-enum control_attribute {
-	CTL_MIN		= 0x02,
-	CTL_MAX		= 0x03,
-	CTL_CURRENT	= 0x10,
-};
-
-static int oxfw_mute_command(struct snd_oxfw *oxfw, bool *value,
-			      enum control_action action)
-{
-	u8 *buf;
-	u8 response_ok;
-	int err;
-
-	buf = kmalloc(11, GFP_KERNEL);
-	if (!buf)
-		return -ENOMEM;
-
-	if (action == CTL_READ) {
-		buf[0] = 0x01;		/* AV/C, STATUS */
-		response_ok = 0x0c;	/*       STABLE */
-	} else {
-		buf[0] = 0x00;		/* AV/C, CONTROL */
-		response_ok = 0x09;	/*       ACCEPTED */
-	}
-	buf[1] = 0x08;			/* audio unit 0 */
-	buf[2] = 0xb8;			/* FUNCTION BLOCK */
-	buf[3] = 0x81;			/* function block type: feature */
-	buf[4] = oxfw->device_info->mute_fb_id; /* function block ID */
-	buf[5] = 0x10;			/* control attribute: current */
-	buf[6] = 0x02;			/* selector length */
-	buf[7] = 0x00;			/* audio channel number */
-	buf[8] = 0x01;			/* control selector: mute */
-	buf[9] = 0x01;			/* control data length */
-	if (action == CTL_READ)
-		buf[10] = 0xff;
-	else
-		buf[10] = *value ? 0x70 : 0x60;
-
-	err = fcp_avc_transaction(oxfw->unit, buf, 11, buf, 11, 0x3fe);
-	if (err < 0)
-		goto error;
-	if (err < 11) {
-		dev_err(&oxfw->unit->device, "short FCP response\n");
-		err = -EIO;
-		goto error;
-	}
-	if (buf[0] != response_ok) {
-		dev_err(&oxfw->unit->device, "mute command failed\n");
-		err = -EIO;
-		goto error;
-	}
-	if (action == CTL_READ)
-		*value = buf[10] == 0x70;
-
-	err = 0;
-
-error:
-	kfree(buf);
-
-	return err;
-}
-
-static int oxfw_volume_command(struct snd_oxfw *oxfw, s16 *value,
-				unsigned int channel,
-				enum control_attribute attribute,
-				enum control_action action)
-{
-	u8 *buf;
-	u8 response_ok;
-	int err;
-
-	buf = kmalloc(12, GFP_KERNEL);
-	if (!buf)
-		return -ENOMEM;
-
-	if (action == CTL_READ) {
-		buf[0] = 0x01;		/* AV/C, STATUS */
-		response_ok = 0x0c;	/*       STABLE */
-	} else {
-		buf[0] = 0x00;		/* AV/C, CONTROL */
-		response_ok = 0x09;	/*       ACCEPTED */
-	}
-	buf[1] = 0x08;			/* audio unit 0 */
-	buf[2] = 0xb8;			/* FUNCTION BLOCK */
-	buf[3] = 0x81;			/* function block type: feature */
-	buf[4] = oxfw->device_info->volume_fb_id; /* function block ID */
-	buf[5] = attribute;		/* control attribute */
-	buf[6] = 0x02;			/* selector length */
-	buf[7] = channel;		/* audio channel number */
-	buf[8] = 0x02;			/* control selector: volume */
-	buf[9] = 0x02;			/* control data length */
-	if (action == CTL_READ) {
-		buf[10] = 0xff;
-		buf[11] = 0xff;
-	} else {
-		buf[10] = *value >> 8;
-		buf[11] = *value;
-	}
-
-	err = fcp_avc_transaction(oxfw->unit, buf, 12, buf, 12, 0x3fe);
-	if (err < 0)
-		goto error;
-	if (err < 12) {
-		dev_err(&oxfw->unit->device, "short FCP response\n");
-		err = -EIO;
-		goto error;
-	}
-	if (buf[0] != response_ok) {
-		dev_err(&oxfw->unit->device, "volume command failed\n");
-		err = -EIO;
-		goto error;
-	}
-	if (action == CTL_READ)
-		*value = (buf[10] << 8) | buf[11];
-
-	err = 0;
-
-error:
-	kfree(buf);
-
-	return err;
-}
-
-static int oxfw_mute_get(struct snd_kcontrol *control,
-			  struct snd_ctl_elem_value *value)
-{
-	struct snd_oxfw *oxfw = control->private_data;
-
-	value->value.integer.value[0] = !oxfw->mute;
-
-	return 0;
-}
-
-static int oxfw_mute_put(struct snd_kcontrol *control,
-			  struct snd_ctl_elem_value *value)
-{
-	struct snd_oxfw *oxfw = control->private_data;
-	bool mute;
-	int err;
-
-	mute = !value->value.integer.value[0];
-
-	if (mute == oxfw->mute)
-		return 0;
-
-	err = oxfw_mute_command(oxfw, &mute, CTL_WRITE);
-	if (err < 0)
-		return err;
-	oxfw->mute = mute;
-
-	return 1;
-}
-
-static int oxfw_volume_info(struct snd_kcontrol *control,
-			     struct snd_ctl_elem_info *info)
-{
-	struct snd_oxfw *oxfw = control->private_data;
-
-	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
-	info->count = oxfw->device_info->mixer_channels;
-	info->value.integer.min = oxfw->volume_min;
-	info->value.integer.max = oxfw->volume_max;
-
-	return 0;
-}
-
-static const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 };
-
-static int oxfw_volume_get(struct snd_kcontrol *control,
-			    struct snd_ctl_elem_value *value)
-{
-	struct snd_oxfw *oxfw = control->private_data;
-	unsigned int i;
-
-	for (i = 0; i < oxfw->device_info->mixer_channels; ++i)
-		value->value.integer.value[channel_map[i]] = oxfw->volume[i];
-
-	return 0;
-}
-
-static int oxfw_volume_put(struct snd_kcontrol *control,
-			  struct snd_ctl_elem_value *value)
-{
-	struct snd_oxfw *oxfw = control->private_data;
-	unsigned int i, changed_channels;
-	bool equal_values = true;
-	s16 volume;
-	int err;
-
-	for (i = 0; i < oxfw->device_info->mixer_channels; ++i) {
-		if (value->value.integer.value[i] < oxfw->volume_min ||
-		    value->value.integer.value[i] > oxfw->volume_max)
-			return -EINVAL;
-		if (value->value.integer.value[i] !=
-		    value->value.integer.value[0])
-			equal_values = false;
-	}
-
-	changed_channels = 0;
-	for (i = 0; i < oxfw->device_info->mixer_channels; ++i)
-		if (value->value.integer.value[channel_map[i]] !=
-							oxfw->volume[i])
-			changed_channels |= 1 << (i + 1);
-
-	if (equal_values && changed_channels != 0)
-		changed_channels = 1 << 0;
-
-	for (i = 0; i <= oxfw->device_info->mixer_channels; ++i) {
-		volume = value->value.integer.value[channel_map[i ? i - 1 : 0]];
-		if (changed_channels & (1 << i)) {
-			err = oxfw_volume_command(oxfw, &volume, i,
-						   CTL_CURRENT, CTL_WRITE);
-			if (err < 0)
-				return err;
-		}
-		if (i > 0)
-			oxfw->volume[i - 1] = volume;
-	}
-
-	return changed_channels != 0;
-}
-
-static int oxfw_create_mixer(struct snd_oxfw *oxfw)
-{
-	static const struct snd_kcontrol_new controls[] = {
-		{
-			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-			.name = "PCM Playback Switch",
-			.info = snd_ctl_boolean_mono_info,
-			.get = oxfw_mute_get,
-			.put = oxfw_mute_put,
-		},
-		{
-			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-			.name = "PCM Playback Volume",
-			.info = oxfw_volume_info,
-			.get = oxfw_volume_get,
-			.put = oxfw_volume_put,
-		},
-	};
-	unsigned int i, first_ch;
-	int err;
-
-	err = oxfw_volume_command(oxfw, &oxfw->volume_min,
-				   0, CTL_MIN, CTL_READ);
-	if (err < 0)
-		return err;
-	err = oxfw_volume_command(oxfw, &oxfw->volume_max,
-				   0, CTL_MAX, CTL_READ);
-	if (err < 0)
-		return err;
-
-	err = oxfw_mute_command(oxfw, &oxfw->mute, CTL_READ);
-	if (err < 0)
-		return err;
-
-	first_ch = oxfw->device_info->mixer_channels == 1 ? 0 : 1;
-	for (i = 0; i < oxfw->device_info->mixer_channels; ++i) {
-		err = oxfw_volume_command(oxfw, &oxfw->volume[i],
-					   first_ch + i, CTL_CURRENT, CTL_READ);
-		if (err < 0)
-			return err;
-	}
-
-	for (i = 0; i < ARRAY_SIZE(controls); ++i) {
-		err = snd_ctl_add(oxfw->card,
-				  snd_ctl_new1(&controls[i], oxfw));
-		if (err < 0)
-			return err;
-	}
-
-	return 0;
-}
-
-static u32 oxfw_read_firmware_version(struct fw_unit *unit)
-{
-	__be32 data;
-	int err;
-
-	err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST,
-				 OXFORD_FIRMWARE_ID_ADDRESS, &data, 4, 0);
-	return err >= 0 ? be32_to_cpu(data) : 0;
-}
-
-static void oxfw_card_free(struct snd_card *card)
-{
-	struct snd_oxfw *oxfw = card->private_data;
-
-	amdtp_stream_destroy(&oxfw->rx_stream);
-	cmp_connection_destroy(&oxfw->in_conn);
-	fw_unit_put(oxfw->unit);
-	mutex_destroy(&oxfw->mutex);
-}
-
-static int oxfw_probe(struct fw_unit *unit,
-		       const struct ieee1394_device_id *id)
-{
-	struct fw_device *fw_dev = fw_parent_device(unit);
-	struct snd_card *card;
-	struct snd_oxfw *oxfw;
-	u32 firmware;
-	int err;
-
-	err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE,
-			   sizeof(*oxfw), &card);
-	if (err < 0)
-		return err;
-
-	oxfw = card->private_data;
-	oxfw->card = card;
-	mutex_init(&oxfw->mutex);
-	oxfw->unit = fw_unit_get(unit);
-	oxfw->device_info = (const struct device_info *)id->driver_data;
-
-	err = cmp_connection_init(&oxfw->in_conn, unit, CMP_INPUT, 0);
-	if (err < 0)
-		goto err_unit;
-
-	err = amdtp_stream_init(&oxfw->rx_stream, unit, AMDTP_OUT_STREAM,
-				CIP_NONBLOCKING);
-	if (err < 0)
-		goto err_connection;
-
-	card->private_free = oxfw_card_free;
-
-	strcpy(card->driver, oxfw->device_info->driver_name);
-	strcpy(card->shortname, oxfw->device_info->short_name);
-	firmware = oxfw_read_firmware_version(unit);
-	snprintf(card->longname, sizeof(card->longname),
-		 "%s (OXFW%x %04x), GUID %08x%08x at %s, S%d",
-		 oxfw->device_info->long_name,
-		 firmware >> 20, firmware & 0xffff,
-		 fw_dev->config_rom[3], fw_dev->config_rom[4],
-		 dev_name(&unit->device), 100 << fw_dev->max_speed);
-	strcpy(card->mixername, "OXFW970");
-
-	err = oxfw_create_pcm(oxfw);
-	if (err < 0)
-		goto error;
-
-	err = oxfw_create_mixer(oxfw);
-	if (err < 0)
-		goto error;
-
-	err = snd_card_register(card);
-	if (err < 0)
-		goto error;
-
-	dev_set_drvdata(&unit->device, oxfw);
-
-	return 0;
-
-err_connection:
-	cmp_connection_destroy(&oxfw->in_conn);
-err_unit:
-	fw_unit_put(oxfw->unit);
-	mutex_destroy(&oxfw->mutex);
-error:
-	snd_card_free(card);
-	return err;
-}
-
-static void oxfw_bus_reset(struct fw_unit *unit)
-{
-	struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
-
-	fcp_bus_reset(oxfw->unit);
-
-	if (cmp_connection_update(&oxfw->in_conn) < 0) {
-		amdtp_stream_pcm_abort(&oxfw->rx_stream);
-		mutex_lock(&oxfw->mutex);
-		oxfw_stop_stream(oxfw);
-		mutex_unlock(&oxfw->mutex);
-		return;
-	}
-
-	amdtp_stream_update(&oxfw->rx_stream);
-}
-
-static void oxfw_remove(struct fw_unit *unit)
-{
-	struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
-
-	amdtp_stream_pcm_abort(&oxfw->rx_stream);
-	snd_card_disconnect(oxfw->card);
-
-	mutex_lock(&oxfw->mutex);
-	oxfw_stop_stream(oxfw);
-	mutex_unlock(&oxfw->mutex);
-
-	snd_card_free_when_closed(oxfw->card);
-}
-
-static const struct device_info griffin_firewave = {
-	.driver_name = "FireWave",
-	.short_name  = "FireWave",
-	.long_name   = "Griffin FireWave Surround",
-	.pcm_constraints = firewave_constraints,
-	.mixer_channels = 6,
-	.mute_fb_id   = 0x01,
-	.volume_fb_id = 0x02,
-};
-
-static const struct device_info lacie_speakers = {
-	.driver_name = "FWSpeakers",
-	.short_name  = "FireWire Speakers",
-	.long_name   = "LaCie FireWire Speakers",
-	.pcm_constraints = lacie_speakers_constraints,
-	.mixer_channels = 1,
-	.mute_fb_id   = 0x01,
-	.volume_fb_id = 0x01,
-};
-
-static const struct ieee1394_device_id oxfw_id_table[] = {
-	{
-		.match_flags  = IEEE1394_MATCH_VENDOR_ID |
-				IEEE1394_MATCH_MODEL_ID |
-				IEEE1394_MATCH_SPECIFIER_ID |
-				IEEE1394_MATCH_VERSION,
-		.vendor_id    = VENDOR_GRIFFIN,
-		.model_id     = 0x00f970,
-		.specifier_id = SPECIFIER_1394TA,
-		.version      = VERSION_AVC,
-		.driver_data  = (kernel_ulong_t)&griffin_firewave,
-	},
-	{
-		.match_flags  = IEEE1394_MATCH_VENDOR_ID |
-				IEEE1394_MATCH_MODEL_ID |
-				IEEE1394_MATCH_SPECIFIER_ID |
-				IEEE1394_MATCH_VERSION,
-		.vendor_id    = VENDOR_LACIE,
-		.model_id     = 0x00f970,
-		.specifier_id = SPECIFIER_1394TA,
-		.version      = VERSION_AVC,
-		.driver_data  = (kernel_ulong_t)&lacie_speakers,
-	},
-	{ }
-};
-MODULE_DEVICE_TABLE(ieee1394, oxfw_id_table);
-
-static struct fw_driver oxfw_driver = {
-	.driver   = {
-		.owner	= THIS_MODULE,
-		.name	= KBUILD_MODNAME,
-		.bus	= &fw_bus_type,
-	},
-	.probe    = oxfw_probe,
-	.update   = oxfw_bus_reset,
-	.remove   = oxfw_remove,
-	.id_table = oxfw_id_table,
-};
-
-static int __init snd_oxfw_init(void)
-{
-	return driver_register(&oxfw_driver.driver);
-}
-
-static void __exit snd_oxfw_exit(void)
-{
-	driver_unregister(&oxfw_driver.driver);
-}
-
-module_init(snd_oxfw_init);
-module_exit(snd_oxfw_exit);
diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile
new file mode 100644
index 0000000..9ca49c0
--- /dev/null
+++ b/sound/firewire/oxfw/Makefile
@@ -0,0 +1,2 @@
+snd-oxfw-objs := oxfw.o
+obj-m += snd-oxfw.o
diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c
new file mode 100644
index 0000000..e194f54
--- /dev/null
+++ b/sound/firewire/oxfw/oxfw.c
@@ -0,0 +1,793 @@
+/*
+ * oxfw.c - a part of driver for OXFW970/971 based devices
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include <linux/device.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "../cmp.h"
+#include "../fcp.h"
+#include "../amdtp.h"
+#include "../lib.h"
+
+#define OXFORD_FIRMWARE_ID_ADDRESS	(CSR_REGISTER_BASE + 0x50000)
+/* 0x970?vvvv or 0x971?vvvv, where vvvv = firmware version */
+
+#define OXFORD_HARDWARE_ID_ADDRESS	(CSR_REGISTER_BASE + 0x90020)
+#define OXFORD_HARDWARE_ID_OXFW970	0x39443841
+#define OXFORD_HARDWARE_ID_OXFW971	0x39373100
+
+#define VENDOR_GRIFFIN		0x001292
+#define VENDOR_LACIE		0x00d04b
+
+#define SPECIFIER_1394TA	0x00a02d
+#define VERSION_AVC		0x010001
+
+struct device_info {
+	const char *driver_name;
+	const char *short_name;
+	const char *long_name;
+	int (*pcm_constraints)(struct snd_pcm_runtime *runtime);
+	unsigned int mixer_channels;
+	u8 mute_fb_id;
+	u8 volume_fb_id;
+};
+
+struct snd_oxfw {
+	struct snd_card *card;
+	struct fw_unit *unit;
+	const struct device_info *device_info;
+	struct mutex mutex;
+	struct cmp_connection in_conn;
+	struct amdtp_stream rx_stream;
+	bool mute;
+	s16 volume[6];
+	s16 volume_min;
+	s16 volume_max;
+};
+
+MODULE_DESCRIPTION("Oxford OXFW970/971 driver");
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("snd-firewire-speakers");
+
+static int firewave_rate_constraint(struct snd_pcm_hw_params *params,
+				    struct snd_pcm_hw_rule *rule)
+{
+	static unsigned int stereo_rates[] = { 48000, 96000 };
+	struct snd_interval *channels =
+			hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_interval *rate =
+			hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+
+	/* two channels work only at 48/96 kHz */
+	if (snd_interval_max(channels) < 6)
+		return snd_interval_list(rate, 2, stereo_rates, 0);
+	return 0;
+}
+
+static int firewave_channels_constraint(struct snd_pcm_hw_params *params,
+					struct snd_pcm_hw_rule *rule)
+{
+	static const struct snd_interval all_channels = { .min = 6, .max = 6 };
+	struct snd_interval *rate =
+			hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	struct snd_interval *channels =
+			hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+
+	/* 32/44.1 kHz work only with all six channels */
+	if (snd_interval_max(rate) < 48000)
+		return snd_interval_refine(channels, &all_channels);
+	return 0;
+}
+
+static int firewave_constraints(struct snd_pcm_runtime *runtime)
+{
+	static unsigned int channels_list[] = { 2, 6 };
+	static struct snd_pcm_hw_constraint_list channels_list_constraint = {
+		.count = 2,
+		.list = channels_list,
+	};
+	int err;
+
+	runtime->hw.rates = SNDRV_PCM_RATE_32000 |
+			    SNDRV_PCM_RATE_44100 |
+			    SNDRV_PCM_RATE_48000 |
+			    SNDRV_PCM_RATE_96000;
+	runtime->hw.channels_max = 6;
+
+	err = snd_pcm_hw_constraint_list(runtime, 0,
+					 SNDRV_PCM_HW_PARAM_CHANNELS,
+					 &channels_list_constraint);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				  firewave_rate_constraint, NULL,
+				  SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+				  firewave_channels_constraint, NULL,
+				  SNDRV_PCM_HW_PARAM_RATE, -1);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int lacie_speakers_constraints(struct snd_pcm_runtime *runtime)
+{
+	runtime->hw.rates = SNDRV_PCM_RATE_32000 |
+			    SNDRV_PCM_RATE_44100 |
+			    SNDRV_PCM_RATE_48000 |
+			    SNDRV_PCM_RATE_88200 |
+			    SNDRV_PCM_RATE_96000;
+
+	return 0;
+}
+
+static int oxfw_open(struct snd_pcm_substream *substream)
+{
+	static const struct snd_pcm_hardware hardware = {
+		.info = SNDRV_PCM_INFO_MMAP |
+			SNDRV_PCM_INFO_MMAP_VALID |
+			SNDRV_PCM_INFO_BATCH |
+			SNDRV_PCM_INFO_INTERLEAVED |
+			SNDRV_PCM_INFO_BLOCK_TRANSFER,
+		.formats = AMDTP_OUT_PCM_FORMAT_BITS,
+		.channels_min = 2,
+		.channels_max = 2,
+		.buffer_bytes_max = 4 * 1024 * 1024,
+		.period_bytes_min = 1,
+		.period_bytes_max = UINT_MAX,
+		.periods_min = 1,
+		.periods_max = UINT_MAX,
+	};
+	struct snd_oxfw *oxfw = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	runtime->hw = hardware;
+
+	err = oxfw->device_info->pcm_constraints(runtime);
+	if (err < 0)
+		return err;
+	err = snd_pcm_limit_hw_rates(runtime);
+	if (err < 0)
+		return err;
+
+	err = amdtp_stream_add_pcm_hw_constraints(&oxfw->rx_stream, runtime);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int oxfw_close(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+static void oxfw_stop_stream(struct snd_oxfw *oxfw)
+{
+	if (amdtp_stream_running(&oxfw->rx_stream)) {
+		amdtp_stream_stop(&oxfw->rx_stream);
+		cmp_connection_break(&oxfw->in_conn);
+	}
+}
+
+static int oxfw_hw_params(struct snd_pcm_substream *substream,
+			   struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
+	int err;
+
+	mutex_lock(&oxfw->mutex);
+	oxfw_stop_stream(oxfw);
+	mutex_unlock(&oxfw->mutex);
+
+	err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
+					       params_buffer_bytes(hw_params));
+	if (err < 0)
+		goto error;
+
+	amdtp_stream_set_parameters(&oxfw->rx_stream,
+				    params_rate(hw_params),
+				    params_channels(hw_params),
+				    0);
+
+	amdtp_stream_set_pcm_format(&oxfw->rx_stream,
+				    params_format(hw_params));
+
+	err = avc_general_set_sig_fmt(oxfw->unit, params_rate(hw_params),
+				      AVC_GENERAL_PLUG_DIR_IN, 0);
+	if (err < 0) {
+		dev_err(&oxfw->unit->device, "failed to set sample rate\n");
+		goto err_buffer;
+	}
+
+	return 0;
+
+err_buffer:
+	snd_pcm_lib_free_vmalloc_buffer(substream);
+error:
+	return err;
+}
+
+static int oxfw_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
+
+	mutex_lock(&oxfw->mutex);
+	oxfw_stop_stream(oxfw);
+	mutex_unlock(&oxfw->mutex);
+
+	return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int oxfw_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
+	int err;
+
+	mutex_lock(&oxfw->mutex);
+
+	if (amdtp_streaming_error(&oxfw->rx_stream))
+		oxfw_stop_stream(oxfw);
+
+	if (!amdtp_stream_running(&oxfw->rx_stream)) {
+		err = cmp_connection_establish(&oxfw->in_conn,
+			amdtp_stream_get_max_payload(&oxfw->rx_stream));
+		if (err < 0)
+			goto err_mutex;
+
+		err = amdtp_stream_start(&oxfw->rx_stream,
+					 oxfw->in_conn.resources.channel,
+					 oxfw->in_conn.speed);
+		if (err < 0)
+			goto err_connection;
+	}
+
+	mutex_unlock(&oxfw->mutex);
+
+	amdtp_stream_pcm_prepare(&oxfw->rx_stream);
+
+	return 0;
+
+err_connection:
+	cmp_connection_break(&oxfw->in_conn);
+err_mutex:
+	mutex_unlock(&oxfw->mutex);
+
+	return err;
+}
+
+static int oxfw_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
+	struct snd_pcm_substream *pcm;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		pcm = substream;
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		pcm = NULL;
+		break;
+	default:
+		return -EINVAL;
+	}
+	amdtp_stream_pcm_trigger(&oxfw->rx_stream, pcm);
+	return 0;
+}
+
+static snd_pcm_uframes_t oxfw_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
+
+	return amdtp_stream_pcm_pointer(&oxfw->rx_stream);
+}
+
+static int oxfw_create_pcm(struct snd_oxfw *oxfw)
+{
+	static struct snd_pcm_ops ops = {
+		.open      = oxfw_open,
+		.close     = oxfw_close,
+		.ioctl     = snd_pcm_lib_ioctl,
+		.hw_params = oxfw_hw_params,
+		.hw_free   = oxfw_hw_free,
+		.prepare   = oxfw_prepare,
+		.trigger   = oxfw_trigger,
+		.pointer   = oxfw_pointer,
+		.page      = snd_pcm_lib_get_vmalloc_page,
+		.mmap      = snd_pcm_lib_mmap_vmalloc,
+	};
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(oxfw->card, "OXFW970", 0, 1, 0, &pcm);
+	if (err < 0)
+		return err;
+	pcm->private_data = oxfw;
+	strcpy(pcm->name, oxfw->device_info->short_name);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &ops);
+	return 0;
+}
+
+enum control_action { CTL_READ, CTL_WRITE };
+enum control_attribute {
+	CTL_MIN		= 0x02,
+	CTL_MAX		= 0x03,
+	CTL_CURRENT	= 0x10,
+};
+
+static int oxfw_mute_command(struct snd_oxfw *oxfw, bool *value,
+			      enum control_action action)
+{
+	u8 *buf;
+	u8 response_ok;
+	int err;
+
+	buf = kmalloc(11, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	if (action == CTL_READ) {
+		buf[0] = 0x01;		/* AV/C, STATUS */
+		response_ok = 0x0c;	/*       STABLE */
+	} else {
+		buf[0] = 0x00;		/* AV/C, CONTROL */
+		response_ok = 0x09;	/*       ACCEPTED */
+	}
+	buf[1] = 0x08;			/* audio unit 0 */
+	buf[2] = 0xb8;			/* FUNCTION BLOCK */
+	buf[3] = 0x81;			/* function block type: feature */
+	buf[4] = oxfw->device_info->mute_fb_id; /* function block ID */
+	buf[5] = 0x10;			/* control attribute: current */
+	buf[6] = 0x02;			/* selector length */
+	buf[7] = 0x00;			/* audio channel number */
+	buf[8] = 0x01;			/* control selector: mute */
+	buf[9] = 0x01;			/* control data length */
+	if (action == CTL_READ)
+		buf[10] = 0xff;
+	else
+		buf[10] = *value ? 0x70 : 0x60;
+
+	err = fcp_avc_transaction(oxfw->unit, buf, 11, buf, 11, 0x3fe);
+	if (err < 0)
+		goto error;
+	if (err < 11) {
+		dev_err(&oxfw->unit->device, "short FCP response\n");
+		err = -EIO;
+		goto error;
+	}
+	if (buf[0] != response_ok) {
+		dev_err(&oxfw->unit->device, "mute command failed\n");
+		err = -EIO;
+		goto error;
+	}
+	if (action == CTL_READ)
+		*value = buf[10] == 0x70;
+
+	err = 0;
+
+error:
+	kfree(buf);
+
+	return err;
+}
+
+static int oxfw_volume_command(struct snd_oxfw *oxfw, s16 *value,
+				unsigned int channel,
+				enum control_attribute attribute,
+				enum control_action action)
+{
+	u8 *buf;
+	u8 response_ok;
+	int err;
+
+	buf = kmalloc(12, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	if (action == CTL_READ) {
+		buf[0] = 0x01;		/* AV/C, STATUS */
+		response_ok = 0x0c;	/*       STABLE */
+	} else {
+		buf[0] = 0x00;		/* AV/C, CONTROL */
+		response_ok = 0x09;	/*       ACCEPTED */
+	}
+	buf[1] = 0x08;			/* audio unit 0 */
+	buf[2] = 0xb8;			/* FUNCTION BLOCK */
+	buf[3] = 0x81;			/* function block type: feature */
+	buf[4] = oxfw->device_info->volume_fb_id; /* function block ID */
+	buf[5] = attribute;		/* control attribute */
+	buf[6] = 0x02;			/* selector length */
+	buf[7] = channel;		/* audio channel number */
+	buf[8] = 0x02;			/* control selector: volume */
+	buf[9] = 0x02;			/* control data length */
+	if (action == CTL_READ) {
+		buf[10] = 0xff;
+		buf[11] = 0xff;
+	} else {
+		buf[10] = *value >> 8;
+		buf[11] = *value;
+	}
+
+	err = fcp_avc_transaction(oxfw->unit, buf, 12, buf, 12, 0x3fe);
+	if (err < 0)
+		goto error;
+	if (err < 12) {
+		dev_err(&oxfw->unit->device, "short FCP response\n");
+		err = -EIO;
+		goto error;
+	}
+	if (buf[0] != response_ok) {
+		dev_err(&oxfw->unit->device, "volume command failed\n");
+		err = -EIO;
+		goto error;
+	}
+	if (action == CTL_READ)
+		*value = (buf[10] << 8) | buf[11];
+
+	err = 0;
+
+error:
+	kfree(buf);
+
+	return err;
+}
+
+static int oxfw_mute_get(struct snd_kcontrol *control,
+			  struct snd_ctl_elem_value *value)
+{
+	struct snd_oxfw *oxfw = control->private_data;
+
+	value->value.integer.value[0] = !oxfw->mute;
+
+	return 0;
+}
+
+static int oxfw_mute_put(struct snd_kcontrol *control,
+			  struct snd_ctl_elem_value *value)
+{
+	struct snd_oxfw *oxfw = control->private_data;
+	bool mute;
+	int err;
+
+	mute = !value->value.integer.value[0];
+
+	if (mute == oxfw->mute)
+		return 0;
+
+	err = oxfw_mute_command(oxfw, &mute, CTL_WRITE);
+	if (err < 0)
+		return err;
+	oxfw->mute = mute;
+
+	return 1;
+}
+
+static int oxfw_volume_info(struct snd_kcontrol *control,
+			     struct snd_ctl_elem_info *info)
+{
+	struct snd_oxfw *oxfw = control->private_data;
+
+	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	info->count = oxfw->device_info->mixer_channels;
+	info->value.integer.min = oxfw->volume_min;
+	info->value.integer.max = oxfw->volume_max;
+
+	return 0;
+}
+
+static const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 };
+
+static int oxfw_volume_get(struct snd_kcontrol *control,
+			    struct snd_ctl_elem_value *value)
+{
+	struct snd_oxfw *oxfw = control->private_data;
+	unsigned int i;
+
+	for (i = 0; i < oxfw->device_info->mixer_channels; ++i)
+		value->value.integer.value[channel_map[i]] = oxfw->volume[i];
+
+	return 0;
+}
+
+static int oxfw_volume_put(struct snd_kcontrol *control,
+			  struct snd_ctl_elem_value *value)
+{
+	struct snd_oxfw *oxfw = control->private_data;
+	unsigned int i, changed_channels;
+	bool equal_values = true;
+	s16 volume;
+	int err;
+
+	for (i = 0; i < oxfw->device_info->mixer_channels; ++i) {
+		if (value->value.integer.value[i] < oxfw->volume_min ||
+		    value->value.integer.value[i] > oxfw->volume_max)
+			return -EINVAL;
+		if (value->value.integer.value[i] !=
+		    value->value.integer.value[0])
+			equal_values = false;
+	}
+
+	changed_channels = 0;
+	for (i = 0; i < oxfw->device_info->mixer_channels; ++i)
+		if (value->value.integer.value[channel_map[i]] !=
+							oxfw->volume[i])
+			changed_channels |= 1 << (i + 1);
+
+	if (equal_values && changed_channels != 0)
+		changed_channels = 1 << 0;
+
+	for (i = 0; i <= oxfw->device_info->mixer_channels; ++i) {
+		volume = value->value.integer.value[channel_map[i ? i - 1 : 0]];
+		if (changed_channels & (1 << i)) {
+			err = oxfw_volume_command(oxfw, &volume, i,
+						   CTL_CURRENT, CTL_WRITE);
+			if (err < 0)
+				return err;
+		}
+		if (i > 0)
+			oxfw->volume[i - 1] = volume;
+	}
+
+	return changed_channels != 0;
+}
+
+static int oxfw_create_mixer(struct snd_oxfw *oxfw)
+{
+	static const struct snd_kcontrol_new controls[] = {
+		{
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.name = "PCM Playback Switch",
+			.info = snd_ctl_boolean_mono_info,
+			.get = oxfw_mute_get,
+			.put = oxfw_mute_put,
+		},
+		{
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.name = "PCM Playback Volume",
+			.info = oxfw_volume_info,
+			.get = oxfw_volume_get,
+			.put = oxfw_volume_put,
+		},
+	};
+	unsigned int i, first_ch;
+	int err;
+
+	err = oxfw_volume_command(oxfw, &oxfw->volume_min,
+				   0, CTL_MIN, CTL_READ);
+	if (err < 0)
+		return err;
+	err = oxfw_volume_command(oxfw, &oxfw->volume_max,
+				   0, CTL_MAX, CTL_READ);
+	if (err < 0)
+		return err;
+
+	err = oxfw_mute_command(oxfw, &oxfw->mute, CTL_READ);
+	if (err < 0)
+		return err;
+
+	first_ch = oxfw->device_info->mixer_channels == 1 ? 0 : 1;
+	for (i = 0; i < oxfw->device_info->mixer_channels; ++i) {
+		err = oxfw_volume_command(oxfw, &oxfw->volume[i],
+					   first_ch + i, CTL_CURRENT, CTL_READ);
+		if (err < 0)
+			return err;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(controls); ++i) {
+		err = snd_ctl_add(oxfw->card,
+				  snd_ctl_new1(&controls[i], oxfw));
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+static u32 oxfw_read_firmware_version(struct fw_unit *unit)
+{
+	__be32 data;
+	int err;
+
+	err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST,
+				 OXFORD_FIRMWARE_ID_ADDRESS, &data, 4, 0);
+	return err >= 0 ? be32_to_cpu(data) : 0;
+}
+
+static void oxfw_card_free(struct snd_card *card)
+{
+	struct snd_oxfw *oxfw = card->private_data;
+
+	amdtp_stream_destroy(&oxfw->rx_stream);
+	cmp_connection_destroy(&oxfw->in_conn);
+	fw_unit_put(oxfw->unit);
+	mutex_destroy(&oxfw->mutex);
+}
+
+static int oxfw_probe(struct fw_unit *unit,
+		       const struct ieee1394_device_id *id)
+{
+	struct fw_device *fw_dev = fw_parent_device(unit);
+	struct snd_card *card;
+	struct snd_oxfw *oxfw;
+	u32 firmware;
+	int err;
+
+	err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE,
+			   sizeof(*oxfw), &card);
+	if (err < 0)
+		return err;
+
+	oxfw = card->private_data;
+	oxfw->card = card;
+	mutex_init(&oxfw->mutex);
+	oxfw->unit = fw_unit_get(unit);
+	oxfw->device_info = (const struct device_info *)id->driver_data;
+
+	err = cmp_connection_init(&oxfw->in_conn, unit, CMP_INPUT, 0);
+	if (err < 0)
+		goto err_unit;
+
+	err = amdtp_stream_init(&oxfw->rx_stream, unit, AMDTP_OUT_STREAM,
+				CIP_NONBLOCKING);
+	if (err < 0)
+		goto err_connection;
+
+	card->private_free = oxfw_card_free;
+
+	strcpy(card->driver, oxfw->device_info->driver_name);
+	strcpy(card->shortname, oxfw->device_info->short_name);
+	firmware = oxfw_read_firmware_version(unit);
+	snprintf(card->longname, sizeof(card->longname),
+		 "%s (OXFW%x %04x), GUID %08x%08x at %s, S%d",
+		 oxfw->device_info->long_name,
+		 firmware >> 20, firmware & 0xffff,
+		 fw_dev->config_rom[3], fw_dev->config_rom[4],
+		 dev_name(&unit->device), 100 << fw_dev->max_speed);
+	strcpy(card->mixername, "OXFW970");
+
+	err = oxfw_create_pcm(oxfw);
+	if (err < 0)
+		goto error;
+
+	err = oxfw_create_mixer(oxfw);
+	if (err < 0)
+		goto error;
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto error;
+
+	dev_set_drvdata(&unit->device, oxfw);
+
+	return 0;
+
+err_connection:
+	cmp_connection_destroy(&oxfw->in_conn);
+err_unit:
+	fw_unit_put(oxfw->unit);
+	mutex_destroy(&oxfw->mutex);
+error:
+	snd_card_free(card);
+	return err;
+}
+
+static void oxfw_bus_reset(struct fw_unit *unit)
+{
+	struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
+
+	fcp_bus_reset(oxfw->unit);
+
+	if (cmp_connection_update(&oxfw->in_conn) < 0) {
+		amdtp_stream_pcm_abort(&oxfw->rx_stream);
+		mutex_lock(&oxfw->mutex);
+		oxfw_stop_stream(oxfw);
+		mutex_unlock(&oxfw->mutex);
+		return;
+	}
+
+	amdtp_stream_update(&oxfw->rx_stream);
+}
+
+static void oxfw_remove(struct fw_unit *unit)
+{
+	struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
+
+	amdtp_stream_pcm_abort(&oxfw->rx_stream);
+	snd_card_disconnect(oxfw->card);
+
+	mutex_lock(&oxfw->mutex);
+	oxfw_stop_stream(oxfw);
+	mutex_unlock(&oxfw->mutex);
+
+	snd_card_free_when_closed(oxfw->card);
+}
+
+static const struct device_info griffin_firewave = {
+	.driver_name = "FireWave",
+	.short_name  = "FireWave",
+	.long_name   = "Griffin FireWave Surround",
+	.pcm_constraints = firewave_constraints,
+	.mixer_channels = 6,
+	.mute_fb_id   = 0x01,
+	.volume_fb_id = 0x02,
+};
+
+static const struct device_info lacie_speakers = {
+	.driver_name = "FWSpeakers",
+	.short_name  = "FireWire Speakers",
+	.long_name   = "LaCie FireWire Speakers",
+	.pcm_constraints = lacie_speakers_constraints,
+	.mixer_channels = 1,
+	.mute_fb_id   = 0x01,
+	.volume_fb_id = 0x01,
+};
+
+static const struct ieee1394_device_id oxfw_id_table[] = {
+	{
+		.match_flags  = IEEE1394_MATCH_VENDOR_ID |
+				IEEE1394_MATCH_MODEL_ID |
+				IEEE1394_MATCH_SPECIFIER_ID |
+				IEEE1394_MATCH_VERSION,
+		.vendor_id    = VENDOR_GRIFFIN,
+		.model_id     = 0x00f970,
+		.specifier_id = SPECIFIER_1394TA,
+		.version      = VERSION_AVC,
+		.driver_data  = (kernel_ulong_t)&griffin_firewave,
+	},
+	{
+		.match_flags  = IEEE1394_MATCH_VENDOR_ID |
+				IEEE1394_MATCH_MODEL_ID |
+				IEEE1394_MATCH_SPECIFIER_ID |
+				IEEE1394_MATCH_VERSION,
+		.vendor_id    = VENDOR_LACIE,
+		.model_id     = 0x00f970,
+		.specifier_id = SPECIFIER_1394TA,
+		.version      = VERSION_AVC,
+		.driver_data  = (kernel_ulong_t)&lacie_speakers,
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(ieee1394, oxfw_id_table);
+
+static struct fw_driver oxfw_driver = {
+	.driver   = {
+		.owner	= THIS_MODULE,
+		.name	= KBUILD_MODNAME,
+		.bus	= &fw_bus_type,
+	},
+	.probe    = oxfw_probe,
+	.update   = oxfw_bus_reset,
+	.remove   = oxfw_remove,
+	.id_table = oxfw_id_table,
+};
+
+static int __init snd_oxfw_init(void)
+{
+	return driver_register(&oxfw_driver.driver);
+}
+
+static void __exit snd_oxfw_exit(void)
+{
+	driver_unregister(&oxfw_driver.driver);
+}
+
+module_init(snd_oxfw_init);
+module_exit(snd_oxfw_exit);
-- 
1.8.3.2


------------------------------------------------------------------------------
"Accelerate Dev Cycles with Automated Cross-Browser Testing - For FREE
Instantly run your Selenium tests across 300+ browser/OS combos.
Get unparalleled scalability from the best Selenium testing platform available
Simple to use. Nothing to install. Get started now for free."
http://p.sf.net/sfu/SauceLabs

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 03/15] oxfw: Split stream functionality to a new file and add a header file
  2014-05-13 14:27 [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Takashi Sakamoto
  2014-05-13 14:27 ` [PATCH 01/15] speakers: Rename to oxfw and rename some members Takashi Sakamoto
  2014-05-13 14:27 ` [PATCH 02/15] oxfw: Move to its own directory Takashi Sakamoto
@ 2014-05-13 14:27 ` Takashi Sakamoto
  2014-05-13 14:27 ` [PATCH 04/15] oxfw: Split PCM functionality to a new file Takashi Sakamoto
                   ` (14 subsequent siblings)
  17 siblings, 0 replies; 35+ messages in thread
From: Takashi Sakamoto @ 2014-05-13 14:27 UTC (permalink / raw)
  To: clemens; +Cc: alsa-devel, fenlason, ffado-devel, linux1394-devel

This is a help for works in followed patches.

And this commit remove 'fw_unit_get()/fw_unit_put()' because these
are called by helper functions in 'snd-firewire-lib'.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/oxfw/Makefile      |   2 +-
 sound/firewire/oxfw/oxfw.c        | 136 ++++++--------------------------------
 sound/firewire/oxfw/oxfw.h        |  56 ++++++++++++++++
 sound/firewire/oxfw/oxfw_stream.c |  92 ++++++++++++++++++++++++++
 4 files changed, 168 insertions(+), 118 deletions(-)
 create mode 100644 sound/firewire/oxfw/oxfw.h
 create mode 100644 sound/firewire/oxfw/oxfw_stream.c

diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile
index 9ca49c0..510c1cb 100644
--- a/sound/firewire/oxfw/Makefile
+++ b/sound/firewire/oxfw/Makefile
@@ -1,2 +1,2 @@
-snd-oxfw-objs := oxfw.o
+snd-oxfw-objs := 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 e194f54..f393f34 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -5,22 +5,7 @@
  * Licensed under the terms of the GNU General Public License, version 2.
  */
 
-#include <linux/device.h>
-#include <linux/firewire.h>
-#include <linux/firewire-constants.h>
-#include <linux/module.h>
-#include <linux/mod_devicetable.h>
-#include <linux/mutex.h>
-#include <linux/slab.h>
-#include <sound/control.h>
-#include <sound/core.h>
-#include <sound/initval.h>
-#include <sound/pcm.h>
-#include <sound/pcm_params.h>
-#include "../cmp.h"
-#include "../fcp.h"
-#include "../amdtp.h"
-#include "../lib.h"
+#include "oxfw.h"
 
 #define OXFORD_FIRMWARE_ID_ADDRESS	(CSR_REGISTER_BASE + 0x50000)
 /* 0x970?vvvv or 0x971?vvvv, where vvvv = firmware version */
@@ -35,29 +20,6 @@
 #define SPECIFIER_1394TA	0x00a02d
 #define VERSION_AVC		0x010001
 
-struct device_info {
-	const char *driver_name;
-	const char *short_name;
-	const char *long_name;
-	int (*pcm_constraints)(struct snd_pcm_runtime *runtime);
-	unsigned int mixer_channels;
-	u8 mute_fb_id;
-	u8 volume_fb_id;
-};
-
-struct snd_oxfw {
-	struct snd_card *card;
-	struct fw_unit *unit;
-	const struct device_info *device_info;
-	struct mutex mutex;
-	struct cmp_connection in_conn;
-	struct amdtp_stream rx_stream;
-	bool mute;
-	s16 volume[6];
-	s16 volume_min;
-	s16 volume_max;
-};
-
 MODULE_DESCRIPTION("Oxford OXFW970/971 driver");
 MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
 MODULE_LICENSE("GPL v2");
@@ -180,23 +142,13 @@ static int oxfw_close(struct snd_pcm_substream *substream)
 	return 0;
 }
 
-static void oxfw_stop_stream(struct snd_oxfw *oxfw)
-{
-	if (amdtp_stream_running(&oxfw->rx_stream)) {
-		amdtp_stream_stop(&oxfw->rx_stream);
-		cmp_connection_break(&oxfw->in_conn);
-	}
-}
-
 static int oxfw_hw_params(struct snd_pcm_substream *substream,
 			   struct snd_pcm_hw_params *hw_params)
 {
 	struct snd_oxfw *oxfw = substream->private_data;
 	int err;
 
-	mutex_lock(&oxfw->mutex);
-	oxfw_stop_stream(oxfw);
-	mutex_unlock(&oxfw->mutex);
+	snd_oxfw_stream_stop_simplex(oxfw);
 
 	err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
 					       params_buffer_bytes(hw_params));
@@ -230,9 +182,7 @@ static int oxfw_hw_free(struct snd_pcm_substream *substream)
 {
 	struct snd_oxfw *oxfw = substream->private_data;
 
-	mutex_lock(&oxfw->mutex);
-	oxfw_stop_stream(oxfw);
-	mutex_unlock(&oxfw->mutex);
+	snd_oxfw_stream_stop_simplex(oxfw);
 
 	return snd_pcm_lib_free_vmalloc_buffer(substream);
 }
@@ -242,35 +192,14 @@ static int oxfw_prepare(struct snd_pcm_substream *substream)
 	struct snd_oxfw *oxfw = substream->private_data;
 	int err;
 
-	mutex_lock(&oxfw->mutex);
-
-	if (amdtp_streaming_error(&oxfw->rx_stream))
-		oxfw_stop_stream(oxfw);
+	snd_oxfw_stream_stop_simplex(oxfw);
 
-	if (!amdtp_stream_running(&oxfw->rx_stream)) {
-		err = cmp_connection_establish(&oxfw->in_conn,
-			amdtp_stream_get_max_payload(&oxfw->rx_stream));
-		if (err < 0)
-			goto err_mutex;
-
-		err = amdtp_stream_start(&oxfw->rx_stream,
-					 oxfw->in_conn.resources.channel,
-					 oxfw->in_conn.speed);
-		if (err < 0)
-			goto err_connection;
-	}
-
-	mutex_unlock(&oxfw->mutex);
+	err = snd_oxfw_stream_start_simplex(oxfw);
+	if (err < 0)
+		goto end;
 
 	amdtp_stream_pcm_prepare(&oxfw->rx_stream);
-
-	return 0;
-
-err_connection:
-	cmp_connection_break(&oxfw->in_conn);
-err_mutex:
-	mutex_unlock(&oxfw->mutex);
-
+end:
 	return err;
 }
 
@@ -615,9 +544,6 @@ static void oxfw_card_free(struct snd_card *card)
 {
 	struct snd_oxfw *oxfw = card->private_data;
 
-	amdtp_stream_destroy(&oxfw->rx_stream);
-	cmp_connection_destroy(&oxfw->in_conn);
-	fw_unit_put(oxfw->unit);
 	mutex_destroy(&oxfw->mutex);
 }
 
@@ -635,23 +561,13 @@ static int oxfw_probe(struct fw_unit *unit,
 	if (err < 0)
 		return err;
 
+	card->private_free = oxfw_card_free;
 	oxfw = card->private_data;
 	oxfw->card = card;
 	mutex_init(&oxfw->mutex);
-	oxfw->unit = fw_unit_get(unit);
+	oxfw->unit = unit;
 	oxfw->device_info = (const struct device_info *)id->driver_data;
 
-	err = cmp_connection_init(&oxfw->in_conn, unit, CMP_INPUT, 0);
-	if (err < 0)
-		goto err_unit;
-
-	err = amdtp_stream_init(&oxfw->rx_stream, unit, AMDTP_OUT_STREAM,
-				CIP_NONBLOCKING);
-	if (err < 0)
-		goto err_connection;
-
-	card->private_free = oxfw_card_free;
-
 	strcpy(card->driver, oxfw->device_info->driver_name);
 	strcpy(card->shortname, oxfw->device_info->short_name);
 	firmware = oxfw_read_firmware_version(unit);
@@ -671,19 +587,18 @@ static int oxfw_probe(struct fw_unit *unit,
 	if (err < 0)
 		goto error;
 
-	err = snd_card_register(card);
+	err = snd_oxfw_stream_init_simplex(oxfw);
 	if (err < 0)
 		goto error;
 
+	err = snd_card_register(card);
+	if (err < 0) {
+		snd_oxfw_stream_destroy_simplex(oxfw);
+		goto error;
+	}
 	dev_set_drvdata(&unit->device, oxfw);
 
 	return 0;
-
-err_connection:
-	cmp_connection_destroy(&oxfw->in_conn);
-err_unit:
-	fw_unit_put(oxfw->unit);
-	mutex_destroy(&oxfw->mutex);
 error:
 	snd_card_free(card);
 	return err;
@@ -694,29 +609,16 @@ static void oxfw_bus_reset(struct fw_unit *unit)
 	struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
 
 	fcp_bus_reset(oxfw->unit);
-
-	if (cmp_connection_update(&oxfw->in_conn) < 0) {
-		amdtp_stream_pcm_abort(&oxfw->rx_stream);
-		mutex_lock(&oxfw->mutex);
-		oxfw_stop_stream(oxfw);
-		mutex_unlock(&oxfw->mutex);
-		return;
-	}
-
-	amdtp_stream_update(&oxfw->rx_stream);
+	snd_oxfw_stream_update_simplex(oxfw);
 }
 
 static void oxfw_remove(struct fw_unit *unit)
 {
 	struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
 
-	amdtp_stream_pcm_abort(&oxfw->rx_stream);
-	snd_card_disconnect(oxfw->card);
-
-	mutex_lock(&oxfw->mutex);
-	oxfw_stop_stream(oxfw);
-	mutex_unlock(&oxfw->mutex);
+	snd_oxfw_stream_destroy_simplex(oxfw);
 
+	snd_card_disconnect(oxfw->card);
 	snd_card_free_when_closed(oxfw->card);
 }
 
diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h
new file mode 100644
index 0000000..e5836e0
--- /dev/null
+++ b/sound/firewire/oxfw/oxfw.h
@@ -0,0 +1,56 @@
+/*
+ * oxfw.h - a part of driver for OXFW970/971 based devices
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include <linux/device.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+
+#include "../lib.h"
+#include "../fcp.h"
+#include "../packets-buffer.h"
+#include "../iso-resources.h"
+#include "../amdtp.h"
+#include "../cmp.h"
+
+struct device_info {
+	const char *driver_name;
+	const char *short_name;
+	const char *long_name;
+	int (*pcm_constraints)(struct snd_pcm_runtime *runtime);
+	unsigned int mixer_channels;
+	u8 mute_fb_id;
+	u8 volume_fb_id;
+};
+
+struct snd_oxfw {
+	struct snd_card *card;
+	struct fw_unit *unit;
+	const struct device_info *device_info;
+	struct mutex mutex;
+	struct cmp_connection in_conn;
+	struct amdtp_stream rx_stream;
+	bool mute;
+	s16 volume[6];
+	s16 volume_min;
+	s16 volume_max;
+};
+
+int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw);
+int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw);
+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);
diff --git a/sound/firewire/oxfw/oxfw_stream.c b/sound/firewire/oxfw/oxfw_stream.c
new file mode 100644
index 0000000..59861b2
--- /dev/null
+++ b/sound/firewire/oxfw/oxfw_stream.c
@@ -0,0 +1,92 @@
+/*
+ * oxfw_stream.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 snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw)
+{
+	int err;
+
+	err = cmp_connection_init(&oxfw->in_conn, oxfw->unit,
+				  CMP_INPUT, 0);
+	if (err < 0)
+		goto end;
+
+	err = amdtp_stream_init(&oxfw->rx_stream, oxfw->unit,
+				AMDTP_OUT_STREAM, CIP_NONBLOCKING);
+	if (err < 0) {
+		amdtp_stream_destroy(&oxfw->rx_stream);
+		cmp_connection_destroy(&oxfw->in_conn);
+	}
+end:
+	return err;
+}
+
+static int stop_stream(struct snd_oxfw *oxfw)
+{
+	amdtp_stream_pcm_abort(&oxfw->rx_stream);
+	amdtp_stream_stop(&oxfw->rx_stream);
+	cmp_connection_break(&oxfw->in_conn);
+}
+
+int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw)
+{
+	int err = 0;
+
+	mutex_lock(&oxfw->mutex);
+
+	if (amdtp_streaming_error(&oxfw->rx_stream))
+		stop_stream(oxfw);
+
+	if (amdtp_stream_running(&oxfw->rx_stream))
+		goto end;
+
+	err = cmp_connection_establish(&oxfw->in_conn,
+			amdtp_stream_get_max_payload(&oxfw->rx_stream));
+	if (err < 0)
+		goto end;
+
+	err = amdtp_stream_start(&oxfw->rx_stream,
+				 oxfw->in_conn.resources.channel,
+				 oxfw->in_conn.speed);
+	if (err < 0)
+		stop_stream(oxfw);
+end:
+	mutex_unlock(&oxfw->mutex);
+	return err;
+}
+
+void snd_oxfw_stream_stop_simplex(struct snd_oxfw *oxfw)
+{
+	mutex_lock(&oxfw->mutex);
+	stop_stream(oxfw);
+	mutex_unlock(&oxfw->mutex);
+}
+
+void snd_oxfw_stream_destroy_simplex(struct snd_oxfw *oxfw)
+{
+	mutex_lock(&oxfw->mutex);
+
+	stop_stream(oxfw);
+
+	amdtp_stream_destroy(&oxfw->rx_stream);
+	cmp_connection_destroy(&oxfw->in_conn);
+
+	mutex_unlock(&oxfw->mutex);
+}
+
+void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw)
+{
+	if (cmp_connection_update(&oxfw->in_conn) < 0) {
+		mutex_lock(&oxfw->mutex);
+		stop_stream(oxfw);
+		mutex_unlock(&oxfw->mutex);
+	} else {
+		amdtp_stream_update(&oxfw->rx_stream);
+	}
+}
-- 
1.8.3.2


------------------------------------------------------------------------------
"Accelerate Dev Cycles with Automated Cross-Browser Testing - For FREE
Instantly run your Selenium tests across 300+ browser/OS combos.
Get unparalleled scalability from the best Selenium testing platform available
Simple to use. Nothing to install. Get started now for free."
http://p.sf.net/sfu/SauceLabs

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 04/15] oxfw: Split PCM functionality to a new file
  2014-05-13 14:27 [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Takashi Sakamoto
                   ` (2 preceding siblings ...)
  2014-05-13 14:27 ` [PATCH 03/15] oxfw: Split stream functionality to a new file and add a header file Takashi Sakamoto
@ 2014-05-13 14:27 ` Takashi Sakamoto
  2014-05-13 14:27 ` [PATCH 05/15] oxfw: Split control " Takashi Sakamoto
                   ` (13 subsequent siblings)
  17 siblings, 0 replies; 35+ messages in thread
From: Takashi Sakamoto @ 2014-05-13 14:27 UTC (permalink / raw)
  To: clemens; +Cc: alsa-devel, fenlason, ffado-devel, linux1394-devel

This is a help for works in followed patches.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/oxfw/Makefile   |   2 +-
 sound/firewire/oxfw/oxfw.c     | 232 +--------------------------------------
 sound/firewire/oxfw/oxfw.h     |   4 +
 sound/firewire/oxfw/oxfw_pcm.c | 239 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 245 insertions(+), 232 deletions(-)
 create mode 100644 sound/firewire/oxfw/oxfw_pcm.c

diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile
index 510c1cb..408b5e5 100644
--- a/sound/firewire/oxfw/Makefile
+++ b/sound/firewire/oxfw/Makefile
@@ -1,2 +1,2 @@
-snd-oxfw-objs := oxfw_stream.o oxfw.o
+snd-oxfw-objs := oxfw_stream.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 f393f34..6428d80 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -25,236 +25,6 @@ MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
 MODULE_LICENSE("GPL v2");
 MODULE_ALIAS("snd-firewire-speakers");
 
-static int firewave_rate_constraint(struct snd_pcm_hw_params *params,
-				    struct snd_pcm_hw_rule *rule)
-{
-	static unsigned int stereo_rates[] = { 48000, 96000 };
-	struct snd_interval *channels =
-			hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
-	struct snd_interval *rate =
-			hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
-
-	/* two channels work only at 48/96 kHz */
-	if (snd_interval_max(channels) < 6)
-		return snd_interval_list(rate, 2, stereo_rates, 0);
-	return 0;
-}
-
-static int firewave_channels_constraint(struct snd_pcm_hw_params *params,
-					struct snd_pcm_hw_rule *rule)
-{
-	static const struct snd_interval all_channels = { .min = 6, .max = 6 };
-	struct snd_interval *rate =
-			hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
-	struct snd_interval *channels =
-			hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
-
-	/* 32/44.1 kHz work only with all six channels */
-	if (snd_interval_max(rate) < 48000)
-		return snd_interval_refine(channels, &all_channels);
-	return 0;
-}
-
-static int firewave_constraints(struct snd_pcm_runtime *runtime)
-{
-	static unsigned int channels_list[] = { 2, 6 };
-	static struct snd_pcm_hw_constraint_list channels_list_constraint = {
-		.count = 2,
-		.list = channels_list,
-	};
-	int err;
-
-	runtime->hw.rates = SNDRV_PCM_RATE_32000 |
-			    SNDRV_PCM_RATE_44100 |
-			    SNDRV_PCM_RATE_48000 |
-			    SNDRV_PCM_RATE_96000;
-	runtime->hw.channels_max = 6;
-
-	err = snd_pcm_hw_constraint_list(runtime, 0,
-					 SNDRV_PCM_HW_PARAM_CHANNELS,
-					 &channels_list_constraint);
-	if (err < 0)
-		return err;
-	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
-				  firewave_rate_constraint, NULL,
-				  SNDRV_PCM_HW_PARAM_CHANNELS, -1);
-	if (err < 0)
-		return err;
-	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
-				  firewave_channels_constraint, NULL,
-				  SNDRV_PCM_HW_PARAM_RATE, -1);
-	if (err < 0)
-		return err;
-
-	return 0;
-}
-
-static int lacie_speakers_constraints(struct snd_pcm_runtime *runtime)
-{
-	runtime->hw.rates = SNDRV_PCM_RATE_32000 |
-			    SNDRV_PCM_RATE_44100 |
-			    SNDRV_PCM_RATE_48000 |
-			    SNDRV_PCM_RATE_88200 |
-			    SNDRV_PCM_RATE_96000;
-
-	return 0;
-}
-
-static int oxfw_open(struct snd_pcm_substream *substream)
-{
-	static const struct snd_pcm_hardware hardware = {
-		.info = SNDRV_PCM_INFO_MMAP |
-			SNDRV_PCM_INFO_MMAP_VALID |
-			SNDRV_PCM_INFO_BATCH |
-			SNDRV_PCM_INFO_INTERLEAVED |
-			SNDRV_PCM_INFO_BLOCK_TRANSFER,
-		.formats = AMDTP_OUT_PCM_FORMAT_BITS,
-		.channels_min = 2,
-		.channels_max = 2,
-		.buffer_bytes_max = 4 * 1024 * 1024,
-		.period_bytes_min = 1,
-		.period_bytes_max = UINT_MAX,
-		.periods_min = 1,
-		.periods_max = UINT_MAX,
-	};
-	struct snd_oxfw *oxfw = substream->private_data;
-	struct snd_pcm_runtime *runtime = substream->runtime;
-	int err;
-
-	runtime->hw = hardware;
-
-	err = oxfw->device_info->pcm_constraints(runtime);
-	if (err < 0)
-		return err;
-	err = snd_pcm_limit_hw_rates(runtime);
-	if (err < 0)
-		return err;
-
-	err = amdtp_stream_add_pcm_hw_constraints(&oxfw->rx_stream, runtime);
-	if (err < 0)
-		return err;
-
-	return 0;
-}
-
-static int oxfw_close(struct snd_pcm_substream *substream)
-{
-	return 0;
-}
-
-static int oxfw_hw_params(struct snd_pcm_substream *substream,
-			   struct snd_pcm_hw_params *hw_params)
-{
-	struct snd_oxfw *oxfw = substream->private_data;
-	int err;
-
-	snd_oxfw_stream_stop_simplex(oxfw);
-
-	err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
-					       params_buffer_bytes(hw_params));
-	if (err < 0)
-		goto error;
-
-	amdtp_stream_set_parameters(&oxfw->rx_stream,
-				    params_rate(hw_params),
-				    params_channels(hw_params),
-				    0);
-
-	amdtp_stream_set_pcm_format(&oxfw->rx_stream,
-				    params_format(hw_params));
-
-	err = avc_general_set_sig_fmt(oxfw->unit, params_rate(hw_params),
-				      AVC_GENERAL_PLUG_DIR_IN, 0);
-	if (err < 0) {
-		dev_err(&oxfw->unit->device, "failed to set sample rate\n");
-		goto err_buffer;
-	}
-
-	return 0;
-
-err_buffer:
-	snd_pcm_lib_free_vmalloc_buffer(substream);
-error:
-	return err;
-}
-
-static int oxfw_hw_free(struct snd_pcm_substream *substream)
-{
-	struct snd_oxfw *oxfw = substream->private_data;
-
-	snd_oxfw_stream_stop_simplex(oxfw);
-
-	return snd_pcm_lib_free_vmalloc_buffer(substream);
-}
-
-static int oxfw_prepare(struct snd_pcm_substream *substream)
-{
-	struct snd_oxfw *oxfw = substream->private_data;
-	int err;
-
-	snd_oxfw_stream_stop_simplex(oxfw);
-
-	err = snd_oxfw_stream_start_simplex(oxfw);
-	if (err < 0)
-		goto end;
-
-	amdtp_stream_pcm_prepare(&oxfw->rx_stream);
-end:
-	return err;
-}
-
-static int oxfw_trigger(struct snd_pcm_substream *substream, int cmd)
-{
-	struct snd_oxfw *oxfw = substream->private_data;
-	struct snd_pcm_substream *pcm;
-
-	switch (cmd) {
-	case SNDRV_PCM_TRIGGER_START:
-		pcm = substream;
-		break;
-	case SNDRV_PCM_TRIGGER_STOP:
-		pcm = NULL;
-		break;
-	default:
-		return -EINVAL;
-	}
-	amdtp_stream_pcm_trigger(&oxfw->rx_stream, pcm);
-	return 0;
-}
-
-static snd_pcm_uframes_t oxfw_pointer(struct snd_pcm_substream *substream)
-{
-	struct snd_oxfw *oxfw = substream->private_data;
-
-	return amdtp_stream_pcm_pointer(&oxfw->rx_stream);
-}
-
-static int oxfw_create_pcm(struct snd_oxfw *oxfw)
-{
-	static struct snd_pcm_ops ops = {
-		.open      = oxfw_open,
-		.close     = oxfw_close,
-		.ioctl     = snd_pcm_lib_ioctl,
-		.hw_params = oxfw_hw_params,
-		.hw_free   = oxfw_hw_free,
-		.prepare   = oxfw_prepare,
-		.trigger   = oxfw_trigger,
-		.pointer   = oxfw_pointer,
-		.page      = snd_pcm_lib_get_vmalloc_page,
-		.mmap      = snd_pcm_lib_mmap_vmalloc,
-	};
-	struct snd_pcm *pcm;
-	int err;
-
-	err = snd_pcm_new(oxfw->card, "OXFW970", 0, 1, 0, &pcm);
-	if (err < 0)
-		return err;
-	pcm->private_data = oxfw;
-	strcpy(pcm->name, oxfw->device_info->short_name);
-	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &ops);
-	return 0;
-}
-
 enum control_action { CTL_READ, CTL_WRITE };
 enum control_attribute {
 	CTL_MIN		= 0x02,
@@ -579,7 +349,7 @@ static int oxfw_probe(struct fw_unit *unit,
 		 dev_name(&unit->device), 100 << fw_dev->max_speed);
 	strcpy(card->mixername, "OXFW970");
 
-	err = oxfw_create_pcm(oxfw);
+	err = snd_oxfw_create_pcm(oxfw);
 	if (err < 0)
 		goto error;
 
diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h
index e5836e0..1196db8 100644
--- a/sound/firewire/oxfw/oxfw.h
+++ b/sound/firewire/oxfw/oxfw.h
@@ -54,3 +54,7 @@ int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw);
 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 firewave_constraints(struct snd_pcm_runtime *runtime);
+int lacie_speakers_constraints(struct snd_pcm_runtime *runtime);
+int snd_oxfw_create_pcm(struct snd_oxfw *oxfw);
diff --git a/sound/firewire/oxfw/oxfw_pcm.c b/sound/firewire/oxfw/oxfw_pcm.c
new file mode 100644
index 0000000..1fd76ce
--- /dev/null
+++ b/sound/firewire/oxfw/oxfw_pcm.c
@@ -0,0 +1,239 @@
+/*
+ * oxfw_pcm.c - a part of driver for OXFW970/971 based devices
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "oxfw.h"
+
+static int firewave_rate_constraint(struct snd_pcm_hw_params *params,
+				    struct snd_pcm_hw_rule *rule)
+{
+	static unsigned int stereo_rates[] = { 48000, 96000 };
+	struct snd_interval *channels =
+			hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_interval *rate =
+			hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+
+	/* two channels work only at 48/96 kHz */
+	if (snd_interval_max(channels) < 6)
+		return snd_interval_list(rate, 2, stereo_rates, 0);
+	return 0;
+}
+
+static int firewave_channels_constraint(struct snd_pcm_hw_params *params,
+					struct snd_pcm_hw_rule *rule)
+{
+	static const struct snd_interval all_channels = { .min = 6, .max = 6 };
+	struct snd_interval *rate =
+			hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	struct snd_interval *channels =
+			hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+
+	/* 32/44.1 kHz work only with all six channels */
+	if (snd_interval_max(rate) < 48000)
+		return snd_interval_refine(channels, &all_channels);
+	return 0;
+}
+
+int firewave_constraints(struct snd_pcm_runtime *runtime)
+{
+	static unsigned int channels_list[] = { 2, 6 };
+	static struct snd_pcm_hw_constraint_list channels_list_constraint = {
+		.count = 2,
+		.list = channels_list,
+	};
+	int err;
+
+	runtime->hw.rates = SNDRV_PCM_RATE_32000 |
+			    SNDRV_PCM_RATE_44100 |
+			    SNDRV_PCM_RATE_48000 |
+			    SNDRV_PCM_RATE_96000;
+	runtime->hw.channels_max = 6;
+
+	err = snd_pcm_hw_constraint_list(runtime, 0,
+					 SNDRV_PCM_HW_PARAM_CHANNELS,
+					 &channels_list_constraint);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				  firewave_rate_constraint, NULL,
+				  SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+				  firewave_channels_constraint, NULL,
+				  SNDRV_PCM_HW_PARAM_RATE, -1);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+int lacie_speakers_constraints(struct snd_pcm_runtime *runtime)
+{
+	runtime->hw.rates = SNDRV_PCM_RATE_32000 |
+			    SNDRV_PCM_RATE_44100 |
+			    SNDRV_PCM_RATE_48000 |
+			    SNDRV_PCM_RATE_88200 |
+			    SNDRV_PCM_RATE_96000;
+
+	return 0;
+}
+
+static int pcm_open(struct snd_pcm_substream *substream)
+{
+	static const struct snd_pcm_hardware hardware = {
+		.info = SNDRV_PCM_INFO_MMAP |
+			SNDRV_PCM_INFO_MMAP_VALID |
+			SNDRV_PCM_INFO_BATCH |
+			SNDRV_PCM_INFO_INTERLEAVED |
+			SNDRV_PCM_INFO_BLOCK_TRANSFER,
+		.formats = AMDTP_OUT_PCM_FORMAT_BITS,
+		.channels_min = 2,
+		.channels_max = 2,
+		.buffer_bytes_max = 4 * 1024 * 1024,
+		.period_bytes_min = 1,
+		.period_bytes_max = UINT_MAX,
+		.periods_min = 1,
+		.periods_max = UINT_MAX,
+	};
+	struct snd_oxfw *oxfw = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	bool used;
+	int err;
+
+	err = cmp_connection_check_used(&oxfw->in_conn, &used);
+	if ((err < 0) || used)
+		goto end;
+
+	runtime->hw = hardware;
+
+	err = oxfw->device_info->pcm_constraints(runtime);
+	if (err < 0)
+		goto end;
+	err = snd_pcm_limit_hw_rates(runtime);
+	if (err < 0)
+		goto end;
+
+	err = amdtp_stream_add_pcm_hw_constraints(&oxfw->rx_stream, runtime);
+end:
+	return err;
+}
+
+static int pcm_close(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+static int pcm_hw_params(struct snd_pcm_substream *substream,
+			 struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
+	int err;
+
+	snd_oxfw_stream_stop_simplex(oxfw);
+
+	err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
+					       params_buffer_bytes(hw_params));
+	if (err < 0)
+		goto error;
+
+	amdtp_stream_set_parameters(&oxfw->rx_stream,
+				    params_rate(hw_params),
+				    params_channels(hw_params),
+				    0);
+
+	amdtp_stream_set_pcm_format(&oxfw->rx_stream,
+				    params_format(hw_params));
+
+	err = avc_general_set_sig_fmt(oxfw->unit, params_rate(hw_params),
+				      AVC_GENERAL_PLUG_DIR_IN, 0);
+	if (err < 0) {
+		dev_err(&oxfw->unit->device, "failed to set sample rate\n");
+		goto err_buffer;
+	}
+
+	return 0;
+
+err_buffer:
+	snd_pcm_lib_free_vmalloc_buffer(substream);
+error:
+	return err;
+}
+
+static int pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
+
+	snd_oxfw_stream_stop_simplex(oxfw);
+
+	return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
+	int err;
+
+	snd_oxfw_stream_stop_simplex(oxfw);
+
+	err = snd_oxfw_stream_start_simplex(oxfw);
+	if (err < 0)
+		goto end;
+
+	amdtp_stream_pcm_prepare(&oxfw->rx_stream);
+end:
+	return err;
+}
+
+static int pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		amdtp_stream_pcm_trigger(&oxfw->rx_stream, substream);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		amdtp_stream_pcm_trigger(&oxfw->rx_stream, NULL);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static snd_pcm_uframes_t pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
+
+	return amdtp_stream_pcm_pointer(&oxfw->rx_stream);
+}
+
+int snd_oxfw_create_pcm(struct snd_oxfw *oxfw)
+{
+	static struct snd_pcm_ops ops = {
+		.open      = pcm_open,
+		.close     = pcm_close,
+		.ioctl     = snd_pcm_lib_ioctl,
+		.hw_params = pcm_hw_params,
+		.hw_free   = pcm_hw_free,
+		.prepare   = pcm_prepare,
+		.trigger   = pcm_trigger,
+		.pointer   = pcm_pointer,
+		.page      = snd_pcm_lib_get_vmalloc_page,
+		.mmap      = snd_pcm_lib_mmap_vmalloc,
+	};
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(oxfw->card, oxfw->card->driver, 0, 1, 0, &pcm);
+	if (err < 0)
+		return err;
+	pcm->private_data = oxfw;
+	strcpy(pcm->name, oxfw->card->shortname);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &ops);
+	return 0;
+}
-- 
1.8.3.2


------------------------------------------------------------------------------
"Accelerate Dev Cycles with Automated Cross-Browser Testing - For FREE
Instantly run your Selenium tests across 300+ browser/OS combos.
Get unparalleled scalability from the best Selenium testing platform available
Simple to use. Nothing to install. Get started now for free."
http://p.sf.net/sfu/SauceLabs

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 05/15] oxfw: Split control functionality to a new file
  2014-05-13 14:27 [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Takashi Sakamoto
                   ` (3 preceding siblings ...)
  2014-05-13 14:27 ` [PATCH 04/15] oxfw: Split PCM functionality to a new file Takashi Sakamoto
@ 2014-05-13 14:27 ` Takashi Sakamoto
  2014-05-13 14:27 ` [PATCH 06/15] oxfw: Change the way to name card Takashi Sakamoto
                   ` (12 subsequent siblings)
  17 siblings, 0 replies; 35+ messages in thread
From: Takashi Sakamoto @ 2014-05-13 14:27 UTC (permalink / raw)
  To: clemens; +Cc: alsa-devel, fenlason, ffado-devel, linux1394-devel

This is a help for works in followed patches.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/oxfw/Makefile       |   2 +-
 sound/firewire/oxfw/oxfw.c         | 275 -----------------------------------
 sound/firewire/oxfw/oxfw.h         |   2 +
 sound/firewire/oxfw/oxfw_control.c | 283 +++++++++++++++++++++++++++++++++++++
 4 files changed, 286 insertions(+), 276 deletions(-)
 create mode 100644 sound/firewire/oxfw/oxfw_control.c

diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile
index 408b5e5..3eaec6d 100644
--- a/sound/firewire/oxfw/Makefile
+++ b/sound/firewire/oxfw/Makefile
@@ -1,2 +1,2 @@
-snd-oxfw-objs := oxfw_stream.o oxfw_pcm.o oxfw.o
+snd-oxfw-objs := oxfw_stream.o oxfw_control.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 6428d80..3d4df12 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -25,281 +25,6 @@ MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
 MODULE_LICENSE("GPL v2");
 MODULE_ALIAS("snd-firewire-speakers");
 
-enum control_action { CTL_READ, CTL_WRITE };
-enum control_attribute {
-	CTL_MIN		= 0x02,
-	CTL_MAX		= 0x03,
-	CTL_CURRENT	= 0x10,
-};
-
-static int oxfw_mute_command(struct snd_oxfw *oxfw, bool *value,
-			      enum control_action action)
-{
-	u8 *buf;
-	u8 response_ok;
-	int err;
-
-	buf = kmalloc(11, GFP_KERNEL);
-	if (!buf)
-		return -ENOMEM;
-
-	if (action == CTL_READ) {
-		buf[0] = 0x01;		/* AV/C, STATUS */
-		response_ok = 0x0c;	/*       STABLE */
-	} else {
-		buf[0] = 0x00;		/* AV/C, CONTROL */
-		response_ok = 0x09;	/*       ACCEPTED */
-	}
-	buf[1] = 0x08;			/* audio unit 0 */
-	buf[2] = 0xb8;			/* FUNCTION BLOCK */
-	buf[3] = 0x81;			/* function block type: feature */
-	buf[4] = oxfw->device_info->mute_fb_id; /* function block ID */
-	buf[5] = 0x10;			/* control attribute: current */
-	buf[6] = 0x02;			/* selector length */
-	buf[7] = 0x00;			/* audio channel number */
-	buf[8] = 0x01;			/* control selector: mute */
-	buf[9] = 0x01;			/* control data length */
-	if (action == CTL_READ)
-		buf[10] = 0xff;
-	else
-		buf[10] = *value ? 0x70 : 0x60;
-
-	err = fcp_avc_transaction(oxfw->unit, buf, 11, buf, 11, 0x3fe);
-	if (err < 0)
-		goto error;
-	if (err < 11) {
-		dev_err(&oxfw->unit->device, "short FCP response\n");
-		err = -EIO;
-		goto error;
-	}
-	if (buf[0] != response_ok) {
-		dev_err(&oxfw->unit->device, "mute command failed\n");
-		err = -EIO;
-		goto error;
-	}
-	if (action == CTL_READ)
-		*value = buf[10] == 0x70;
-
-	err = 0;
-
-error:
-	kfree(buf);
-
-	return err;
-}
-
-static int oxfw_volume_command(struct snd_oxfw *oxfw, s16 *value,
-				unsigned int channel,
-				enum control_attribute attribute,
-				enum control_action action)
-{
-	u8 *buf;
-	u8 response_ok;
-	int err;
-
-	buf = kmalloc(12, GFP_KERNEL);
-	if (!buf)
-		return -ENOMEM;
-
-	if (action == CTL_READ) {
-		buf[0] = 0x01;		/* AV/C, STATUS */
-		response_ok = 0x0c;	/*       STABLE */
-	} else {
-		buf[0] = 0x00;		/* AV/C, CONTROL */
-		response_ok = 0x09;	/*       ACCEPTED */
-	}
-	buf[1] = 0x08;			/* audio unit 0 */
-	buf[2] = 0xb8;			/* FUNCTION BLOCK */
-	buf[3] = 0x81;			/* function block type: feature */
-	buf[4] = oxfw->device_info->volume_fb_id; /* function block ID */
-	buf[5] = attribute;		/* control attribute */
-	buf[6] = 0x02;			/* selector length */
-	buf[7] = channel;		/* audio channel number */
-	buf[8] = 0x02;			/* control selector: volume */
-	buf[9] = 0x02;			/* control data length */
-	if (action == CTL_READ) {
-		buf[10] = 0xff;
-		buf[11] = 0xff;
-	} else {
-		buf[10] = *value >> 8;
-		buf[11] = *value;
-	}
-
-	err = fcp_avc_transaction(oxfw->unit, buf, 12, buf, 12, 0x3fe);
-	if (err < 0)
-		goto error;
-	if (err < 12) {
-		dev_err(&oxfw->unit->device, "short FCP response\n");
-		err = -EIO;
-		goto error;
-	}
-	if (buf[0] != response_ok) {
-		dev_err(&oxfw->unit->device, "volume command failed\n");
-		err = -EIO;
-		goto error;
-	}
-	if (action == CTL_READ)
-		*value = (buf[10] << 8) | buf[11];
-
-	err = 0;
-
-error:
-	kfree(buf);
-
-	return err;
-}
-
-static int oxfw_mute_get(struct snd_kcontrol *control,
-			  struct snd_ctl_elem_value *value)
-{
-	struct snd_oxfw *oxfw = control->private_data;
-
-	value->value.integer.value[0] = !oxfw->mute;
-
-	return 0;
-}
-
-static int oxfw_mute_put(struct snd_kcontrol *control,
-			  struct snd_ctl_elem_value *value)
-{
-	struct snd_oxfw *oxfw = control->private_data;
-	bool mute;
-	int err;
-
-	mute = !value->value.integer.value[0];
-
-	if (mute == oxfw->mute)
-		return 0;
-
-	err = oxfw_mute_command(oxfw, &mute, CTL_WRITE);
-	if (err < 0)
-		return err;
-	oxfw->mute = mute;
-
-	return 1;
-}
-
-static int oxfw_volume_info(struct snd_kcontrol *control,
-			     struct snd_ctl_elem_info *info)
-{
-	struct snd_oxfw *oxfw = control->private_data;
-
-	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
-	info->count = oxfw->device_info->mixer_channels;
-	info->value.integer.min = oxfw->volume_min;
-	info->value.integer.max = oxfw->volume_max;
-
-	return 0;
-}
-
-static const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 };
-
-static int oxfw_volume_get(struct snd_kcontrol *control,
-			    struct snd_ctl_elem_value *value)
-{
-	struct snd_oxfw *oxfw = control->private_data;
-	unsigned int i;
-
-	for (i = 0; i < oxfw->device_info->mixer_channels; ++i)
-		value->value.integer.value[channel_map[i]] = oxfw->volume[i];
-
-	return 0;
-}
-
-static int oxfw_volume_put(struct snd_kcontrol *control,
-			  struct snd_ctl_elem_value *value)
-{
-	struct snd_oxfw *oxfw = control->private_data;
-	unsigned int i, changed_channels;
-	bool equal_values = true;
-	s16 volume;
-	int err;
-
-	for (i = 0; i < oxfw->device_info->mixer_channels; ++i) {
-		if (value->value.integer.value[i] < oxfw->volume_min ||
-		    value->value.integer.value[i] > oxfw->volume_max)
-			return -EINVAL;
-		if (value->value.integer.value[i] !=
-		    value->value.integer.value[0])
-			equal_values = false;
-	}
-
-	changed_channels = 0;
-	for (i = 0; i < oxfw->device_info->mixer_channels; ++i)
-		if (value->value.integer.value[channel_map[i]] !=
-							oxfw->volume[i])
-			changed_channels |= 1 << (i + 1);
-
-	if (equal_values && changed_channels != 0)
-		changed_channels = 1 << 0;
-
-	for (i = 0; i <= oxfw->device_info->mixer_channels; ++i) {
-		volume = value->value.integer.value[channel_map[i ? i - 1 : 0]];
-		if (changed_channels & (1 << i)) {
-			err = oxfw_volume_command(oxfw, &volume, i,
-						   CTL_CURRENT, CTL_WRITE);
-			if (err < 0)
-				return err;
-		}
-		if (i > 0)
-			oxfw->volume[i - 1] = volume;
-	}
-
-	return changed_channels != 0;
-}
-
-static int oxfw_create_mixer(struct snd_oxfw *oxfw)
-{
-	static const struct snd_kcontrol_new controls[] = {
-		{
-			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-			.name = "PCM Playback Switch",
-			.info = snd_ctl_boolean_mono_info,
-			.get = oxfw_mute_get,
-			.put = oxfw_mute_put,
-		},
-		{
-			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-			.name = "PCM Playback Volume",
-			.info = oxfw_volume_info,
-			.get = oxfw_volume_get,
-			.put = oxfw_volume_put,
-		},
-	};
-	unsigned int i, first_ch;
-	int err;
-
-	err = oxfw_volume_command(oxfw, &oxfw->volume_min,
-				   0, CTL_MIN, CTL_READ);
-	if (err < 0)
-		return err;
-	err = oxfw_volume_command(oxfw, &oxfw->volume_max,
-				   0, CTL_MAX, CTL_READ);
-	if (err < 0)
-		return err;
-
-	err = oxfw_mute_command(oxfw, &oxfw->mute, CTL_READ);
-	if (err < 0)
-		return err;
-
-	first_ch = oxfw->device_info->mixer_channels == 1 ? 0 : 1;
-	for (i = 0; i < oxfw->device_info->mixer_channels; ++i) {
-		err = oxfw_volume_command(oxfw, &oxfw->volume[i],
-					   first_ch + i, CTL_CURRENT, CTL_READ);
-		if (err < 0)
-			return err;
-	}
-
-	for (i = 0; i < ARRAY_SIZE(controls); ++i) {
-		err = snd_ctl_add(oxfw->card,
-				  snd_ctl_new1(&controls[i], oxfw));
-		if (err < 0)
-			return err;
-	}
-
-	return 0;
-}
-
 static u32 oxfw_read_firmware_version(struct fw_unit *unit)
 {
 	__be32 data;
diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h
index 1196db8..6164bf3 100644
--- a/sound/firewire/oxfw/oxfw.h
+++ b/sound/firewire/oxfw/oxfw.h
@@ -58,3 +58,5 @@ void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw);
 int firewave_constraints(struct snd_pcm_runtime *runtime);
 int lacie_speakers_constraints(struct snd_pcm_runtime *runtime);
 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_control.c b/sound/firewire/oxfw/oxfw_control.c
new file mode 100644
index 0000000..02a1cb9
--- /dev/null
+++ b/sound/firewire/oxfw/oxfw_control.c
@@ -0,0 +1,283 @@
+/*
+ * oxfw_stream.c - a part of driver for OXFW970/971 based devices
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "oxfw.h"
+
+enum control_action { CTL_READ, CTL_WRITE };
+enum control_attribute {
+	CTL_MIN		= 0x02,
+	CTL_MAX		= 0x03,
+	CTL_CURRENT	= 0x10,
+};
+
+static int oxfw_mute_command(struct snd_oxfw *oxfw, bool *value,
+			     enum control_action action)
+{
+	u8 *buf;
+	u8 response_ok;
+	int err;
+
+	buf = kmalloc(11, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	if (action == CTL_READ) {
+		buf[0] = 0x01;		/* AV/C, STATUS */
+		response_ok = 0x0c;	/*       STABLE */
+	} else {
+		buf[0] = 0x00;		/* AV/C, CONTROL */
+		response_ok = 0x09;	/*       ACCEPTED */
+	}
+	buf[1] = 0x08;			/* audio unit 0 */
+	buf[2] = 0xb8;			/* FUNCTION BLOCK */
+	buf[3] = 0x81;			/* function block type: feature */
+	buf[4] = oxfw->device_info->mute_fb_id; /* function block ID */
+	buf[5] = 0x10;			/* control attribute: current */
+	buf[6] = 0x02;			/* selector length */
+	buf[7] = 0x00;			/* audio channel number */
+	buf[8] = 0x01;			/* control selector: mute */
+	buf[9] = 0x01;			/* control data length */
+	if (action == CTL_READ)
+		buf[10] = 0xff;
+	else
+		buf[10] = *value ? 0x70 : 0x60;
+
+	err = fcp_avc_transaction(oxfw->unit, buf, 11, buf, 11, 0x3fe);
+	if (err < 0)
+		goto error;
+	if (err < 11) {
+		dev_err(&oxfw->unit->device, "short FCP response\n");
+		err = -EIO;
+		goto error;
+	}
+	if (buf[0] != response_ok) {
+		dev_err(&oxfw->unit->device, "mute command failed\n");
+		err = -EIO;
+		goto error;
+	}
+	if (action == CTL_READ)
+		*value = buf[10] == 0x70;
+
+	err = 0;
+
+error:
+	kfree(buf);
+
+	return err;
+}
+
+static int oxfw_volume_command(struct snd_oxfw *oxfw, s16 *value,
+			       unsigned int channel,
+			       enum control_attribute attribute,
+			       enum control_action action)
+{
+	u8 *buf;
+	u8 response_ok;
+	int err;
+
+	buf = kmalloc(12, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	if (action == CTL_READ) {
+		buf[0] = 0x01;		/* AV/C, STATUS */
+		response_ok = 0x0c;	/*       STABLE */
+	} else {
+		buf[0] = 0x00;		/* AV/C, CONTROL */
+		response_ok = 0x09;	/*       ACCEPTED */
+	}
+	buf[1] = 0x08;			/* audio unit 0 */
+	buf[2] = 0xb8;			/* FUNCTION BLOCK */
+	buf[3] = 0x81;			/* function block type: feature */
+	buf[4] = oxfw->device_info->volume_fb_id; /* function block ID */
+	buf[5] = attribute;		/* control attribute */
+	buf[6] = 0x02;			/* selector length */
+	buf[7] = channel;		/* audio channel number */
+	buf[8] = 0x02;			/* control selector: volume */
+	buf[9] = 0x02;			/* control data length */
+	if (action == CTL_READ) {
+		buf[10] = 0xff;
+		buf[11] = 0xff;
+	} else {
+		buf[10] = *value >> 8;
+		buf[11] = *value;
+	}
+
+	err = fcp_avc_transaction(oxfw->unit, buf, 12, buf, 12, 0x3fe);
+	if (err < 0)
+		goto error;
+	if (err < 12) {
+		dev_err(&oxfw->unit->device, "short FCP response\n");
+		err = -EIO;
+		goto error;
+	}
+	if (buf[0] != response_ok) {
+		dev_err(&oxfw->unit->device, "volume command failed\n");
+		err = -EIO;
+		goto error;
+	}
+	if (action == CTL_READ)
+		*value = (buf[10] << 8) | buf[11];
+
+	err = 0;
+
+error:
+	kfree(buf);
+
+	return err;
+}
+
+static int oxfw_mute_get(struct snd_kcontrol *control,
+			 struct snd_ctl_elem_value *value)
+{
+	struct snd_oxfw *oxfw = control->private_data;
+
+	value->value.integer.value[0] = !oxfw->mute;
+
+	return 0;
+}
+
+static int oxfw_mute_put(struct snd_kcontrol *control,
+			 struct snd_ctl_elem_value *value)
+{
+	struct snd_oxfw *oxfw = control->private_data;
+	bool mute;
+	int err;
+
+	mute = !value->value.integer.value[0];
+
+	if (mute == oxfw->mute)
+		return 0;
+
+	err = oxfw_mute_command(oxfw, &mute, CTL_WRITE);
+	if (err < 0)
+		return err;
+	oxfw->mute = mute;
+
+	return 1;
+}
+
+static int oxfw_volume_info(struct snd_kcontrol *control,
+			    struct snd_ctl_elem_info *info)
+{
+	struct snd_oxfw *oxfw = control->private_data;
+
+	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	info->count = oxfw->device_info->mixer_channels;
+	info->value.integer.min = oxfw->volume_min;
+	info->value.integer.max = oxfw->volume_max;
+
+	return 0;
+}
+
+static const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 };
+
+static int oxfw_volume_get(struct snd_kcontrol *control,
+			   struct snd_ctl_elem_value *value)
+{
+	struct snd_oxfw *oxfw = control->private_data;
+	unsigned int i;
+
+	for (i = 0; i < oxfw->device_info->mixer_channels; ++i)
+		value->value.integer.value[channel_map[i]] = oxfw->volume[i];
+
+	return 0;
+}
+
+static int oxfw_volume_put(struct snd_kcontrol *control,
+			   struct snd_ctl_elem_value *value)
+{
+	struct snd_oxfw *oxfw = control->private_data;
+	unsigned int i, changed_channels;
+	bool equal_values = true;
+	s16 volume;
+	int err;
+
+	for (i = 0; i < oxfw->device_info->mixer_channels; ++i) {
+		if (value->value.integer.value[i] < oxfw->volume_min ||
+		    value->value.integer.value[i] > oxfw->volume_max)
+			return -EINVAL;
+		if (value->value.integer.value[i] !=
+		    value->value.integer.value[0])
+			equal_values = false;
+	}
+
+	changed_channels = 0;
+	for (i = 0; i < oxfw->device_info->mixer_channels; ++i)
+		if (value->value.integer.value[channel_map[i]] !=
+							oxfw->volume[i])
+			changed_channels |= 1 << (i + 1);
+
+	if (equal_values && changed_channels != 0)
+		changed_channels = 1 << 0;
+
+	for (i = 0; i <= oxfw->device_info->mixer_channels; ++i) {
+		volume = value->value.integer.value[channel_map[i ? i - 1 : 0]];
+		if (changed_channels & (1 << i)) {
+			err = oxfw_volume_command(oxfw, &volume, i,
+						   CTL_CURRENT, CTL_WRITE);
+			if (err < 0)
+				return err;
+		}
+		if (i > 0)
+			oxfw->volume[i - 1] = volume;
+	}
+
+	return changed_channels != 0;
+}
+
+int snd_oxfw_create_mixer(struct snd_oxfw *oxfw)
+{
+	static const struct snd_kcontrol_new controls[] = {
+		{
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.name = "PCM Playback Switch",
+			.info = snd_ctl_boolean_mono_info,
+			.get = oxfw_mute_get,
+			.put = oxfw_mute_put,
+		},
+		{
+			.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+			.name = "PCM Playback Volume",
+			.info = oxfw_volume_info,
+			.get = oxfw_volume_get,
+			.put = oxfw_volume_put,
+		},
+	};
+	unsigned int i, first_ch;
+	int err;
+
+	err = oxfw_volume_command(oxfw, &oxfw->volume_min,
+				   0, CTL_MIN, CTL_READ);
+	if (err < 0)
+		return err;
+	err = oxfw_volume_command(oxfw, &oxfw->volume_max,
+				   0, CTL_MAX, CTL_READ);
+	if (err < 0)
+		return err;
+
+	err = oxfw_mute_command(oxfw, &oxfw->mute, CTL_READ);
+	if (err < 0)
+		return err;
+
+	first_ch = oxfw->device_info->mixer_channels == 1 ? 0 : 1;
+	for (i = 0; i < oxfw->device_info->mixer_channels; ++i) {
+		err = oxfw_volume_command(oxfw, &oxfw->volume[i],
+					   first_ch + i, CTL_CURRENT, CTL_READ);
+		if (err < 0)
+			return err;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(controls); ++i) {
+		err = snd_ctl_add(oxfw->card,
+				  snd_ctl_new1(&controls[i], oxfw));
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
-- 
1.8.3.2


------------------------------------------------------------------------------
"Accelerate Dev Cycles with Automated Cross-Browser Testing - For FREE
Instantly run your Selenium tests across 300+ browser/OS combos.
Get unparalleled scalability from the best Selenium testing platform available
Simple to use. Nothing to install. Get started now for free."
http://p.sf.net/sfu/SauceLabs

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 06/15] oxfw: Change the way to name card
  2014-05-13 14:27 [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Takashi Sakamoto
                   ` (4 preceding siblings ...)
  2014-05-13 14:27 ` [PATCH 05/15] oxfw: Split control " Takashi Sakamoto
@ 2014-05-13 14:27 ` Takashi Sakamoto
  2014-05-13 14:27 ` [PATCH 07/15] oxfw: Change the way to make PCM rules/constraints Takashi Sakamoto
                   ` (11 subsequent siblings)
  17 siblings, 0 replies; 35+ messages in thread
From: Takashi Sakamoto @ 2014-05-13 14:27 UTC (permalink / raw)
  To: clemens; +Cc: alsa-devel, fenlason, ffado-devel, linux1394-devel

The 'struct snd_card' has 4 members for name. They're 'driver', 'shortname',
'longname' and 'mixername'. This commit names these 4 members according to
two members in model-specific structure and reduce rest of members in the
structure.

The reason that alias names are still used it that currently supported devices,
Griffin FireWave and LaCie Firewire Speaker have too long names for model and
vendor field in their config rom to be over allocated buffer for the names.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/oxfw/oxfw.c | 51 +++++++++++++++++++++++++++-------------------
 sound/firewire/oxfw/oxfw.h |  3 +--
 2 files changed, 31 insertions(+), 23 deletions(-)

diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c
index 3d4df12..28e38cf 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -25,14 +25,34 @@ MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
 MODULE_LICENSE("GPL v2");
 MODULE_ALIAS("snd-firewire-speakers");
 
-static u32 oxfw_read_firmware_version(struct fw_unit *unit)
+static int name_card(struct snd_oxfw *oxfw)
 {
-	__be32 data;
+	struct fw_device *fw_dev = fw_parent_device(oxfw->unit);
+	const char *d, *v, *m;
+	u32 firmware;
 	int err;
 
-	err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST,
-				 OXFORD_FIRMWARE_ID_ADDRESS, &data, 4, 0);
-	return err >= 0 ? be32_to_cpu(data) : 0;
+	err = snd_fw_transaction(oxfw->unit, TCODE_READ_QUADLET_REQUEST,
+				 OXFORD_FIRMWARE_ID_ADDRESS, &firmware, 4, 0);
+	if (err < 0)
+		goto end;
+	be32_to_cpus(&firmware);
+
+	d = oxfw->device_info->driver_name;
+	v = oxfw->device_info->vendor_name;
+	m = oxfw->device_info->driver_name;
+
+	strcpy(oxfw->card->driver, d);
+	strcpy(oxfw->card->shortname, m);
+	strcpy(oxfw->card->mixername, m);
+
+	snprintf(oxfw->card->longname, sizeof(oxfw->card->longname),
+		 "%s %s (OXFW%x %04x), GUID %08x%08x at %s, S%d",
+		 v, m, firmware >> 20, firmware & 0xffff,
+		 fw_dev->config_rom[3], fw_dev->config_rom[4],
+		 dev_name(&oxfw->unit->device), 100 << fw_dev->max_speed);
+end:
+	return err;
 }
 
 static void oxfw_card_free(struct snd_card *card)
@@ -45,10 +65,8 @@ static void oxfw_card_free(struct snd_card *card)
 static int oxfw_probe(struct fw_unit *unit,
 		       const struct ieee1394_device_id *id)
 {
-	struct fw_device *fw_dev = fw_parent_device(unit);
 	struct snd_card *card;
 	struct snd_oxfw *oxfw;
-	u32 firmware;
 	int err;
 
 	err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE,
@@ -63,16 +81,9 @@ static int oxfw_probe(struct fw_unit *unit,
 	oxfw->unit = unit;
 	oxfw->device_info = (const struct device_info *)id->driver_data;
 
-	strcpy(card->driver, oxfw->device_info->driver_name);
-	strcpy(card->shortname, oxfw->device_info->short_name);
-	firmware = oxfw_read_firmware_version(unit);
-	snprintf(card->longname, sizeof(card->longname),
-		 "%s (OXFW%x %04x), GUID %08x%08x at %s, S%d",
-		 oxfw->device_info->long_name,
-		 firmware >> 20, firmware & 0xffff,
-		 fw_dev->config_rom[3], fw_dev->config_rom[4],
-		 dev_name(&unit->device), 100 << fw_dev->max_speed);
-	strcpy(card->mixername, "OXFW970");
+	err = name_card(oxfw);
+	if (err < 0)
+		goto error;
 
 	err = snd_oxfw_create_pcm(oxfw);
 	if (err < 0)
@@ -118,9 +129,8 @@ static void oxfw_remove(struct fw_unit *unit)
 }
 
 static const struct device_info griffin_firewave = {
+	.vendor_name = "Griffin",
 	.driver_name = "FireWave",
-	.short_name  = "FireWave",
-	.long_name   = "Griffin FireWave Surround",
 	.pcm_constraints = firewave_constraints,
 	.mixer_channels = 6,
 	.mute_fb_id   = 0x01,
@@ -128,9 +138,8 @@ static const struct device_info griffin_firewave = {
 };
 
 static const struct device_info lacie_speakers = {
+	.vendor_name = "LaCie",
 	.driver_name = "FWSpeakers",
-	.short_name  = "FireWire Speakers",
-	.long_name   = "LaCie FireWire Speakers",
 	.pcm_constraints = lacie_speakers_constraints,
 	.mixer_channels = 1,
 	.mute_fb_id   = 0x01,
diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h
index 6164bf3..b60e91d 100644
--- a/sound/firewire/oxfw/oxfw.h
+++ b/sound/firewire/oxfw/oxfw.h
@@ -27,9 +27,8 @@
 #include "../cmp.h"
 
 struct device_info {
+	const char *vendor_name;
 	const char *driver_name;
-	const char *short_name;
-	const char *long_name;
 	int (*pcm_constraints)(struct snd_pcm_runtime *runtime);
 	unsigned int mixer_channels;
 	u8 mute_fb_id;
-- 
1.8.3.2


------------------------------------------------------------------------------
"Accelerate Dev Cycles with Automated Cross-Browser Testing - For FREE
Instantly run your Selenium tests across 300+ browser/OS combos.
Get unparalleled scalability from the best Selenium testing platform available
Simple to use. Nothing to install. Get started now for free."
http://p.sf.net/sfu/SauceLabs

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 07/15] oxfw: Change the way to make PCM rules/constraints
  2014-05-13 14:27 [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Takashi Sakamoto
                   ` (5 preceding siblings ...)
  2014-05-13 14:27 ` [PATCH 06/15] oxfw: Change the way to name card Takashi Sakamoto
@ 2014-05-13 14:27 ` Takashi Sakamoto
  2014-05-13 14:27 ` [PATCH 08/15] oxfw: Add proc interface for debugging purpose Takashi Sakamoto
                   ` (10 subsequent siblings)
  17 siblings, 0 replies; 35+ messages in thread
From: Takashi Sakamoto @ 2014-05-13 14:27 UTC (permalink / raw)
  To: clemens; +Cc: alsa-devel, fenlason, ffado-devel, linux1394-devel

For PCM rules/constrains, drivers need to know stream formation at each
supported sampling rates in advance. This commit adds
'struct snd_oxfw_stream_formation' to save formation information to create
PCM rules/constraints.

According to datasheet of OXFW970/971, they support 32.0kHz to 196.0kHz.
As long as developers investigate, some devices are confirmed to have several
entries for the same sampling rate.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/oxfw/oxfw.c        |  41 ++++----
 sound/firewire/oxfw/oxfw.h        |  16 ++-
 sound/firewire/oxfw/oxfw_pcm.c    | 201 +++++++++++++++++++++++---------------
 sound/firewire/oxfw/oxfw_stream.c |  48 +++++++++
 4 files changed, 204 insertions(+), 102 deletions(-)

diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c
index 28e38cf..cfdf420 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -25,6 +25,22 @@ MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
 MODULE_LICENSE("GPL v2");
 MODULE_ALIAS("snd-firewire-speakers");
 
+static const struct device_info griffin_firewave = {
+	.vendor_name = "Griffin",
+	.driver_name = "FireWave",
+	.mixer_channels = 6,
+	.mute_fb_id   = 0x01,
+	.volume_fb_id = 0x02,
+};
+
+static const struct device_info lacie_speakers = {
+	.vendor_name = "LaCie",
+	.driver_name = "FWSpeakers",
+	.mixer_channels = 1,
+	.mute_fb_id   = 0x01,
+	.volume_fb_id = 0x01,
+};
+
 static int name_card(struct snd_oxfw *oxfw)
 {
 	struct fw_device *fw_dev = fw_parent_device(oxfw->unit);
@@ -81,6 +97,13 @@ 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);
+	if (err < 0)
+		goto error;
+
 	err = name_card(oxfw);
 	if (err < 0)
 		goto error;
@@ -128,24 +151,6 @@ static void oxfw_remove(struct fw_unit *unit)
 	snd_card_free_when_closed(oxfw->card);
 }
 
-static const struct device_info griffin_firewave = {
-	.vendor_name = "Griffin",
-	.driver_name = "FireWave",
-	.pcm_constraints = firewave_constraints,
-	.mixer_channels = 6,
-	.mute_fb_id   = 0x01,
-	.volume_fb_id = 0x02,
-};
-
-static const struct device_info lacie_speakers = {
-	.vendor_name = "LaCie",
-	.driver_name = "FWSpeakers",
-	.pcm_constraints = lacie_speakers_constraints,
-	.mixer_channels = 1,
-	.mute_fb_id   = 0x01,
-	.volume_fb_id = 0x01,
-};
-
 static const struct ieee1394_device_id oxfw_id_table[] = {
 	{
 		.match_flags  = IEEE1394_MATCH_VENDOR_ID |
diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h
index b60e91d..000c2af 100644
--- a/sound/firewire/oxfw/oxfw.h
+++ b/sound/firewire/oxfw/oxfw.h
@@ -29,19 +29,29 @@
 struct device_info {
 	const char *vendor_name;
 	const char *driver_name;
-	int (*pcm_constraints)(struct snd_pcm_runtime *runtime);
 	unsigned int mixer_channels;
 	u8 mute_fb_id;
 	u8 volume_fb_id;
 };
 
+/* This is an arbitrary number for convinience. */
+#define	SND_OXFW_STREAM_FORMAT_ENTRIES	10
+struct snd_oxfw_stream_formation {
+	unsigned int rate;
+	unsigned int pcm;
+	unsigned int midi;
+};
 struct snd_oxfw {
 	struct snd_card *card;
 	struct fw_unit *unit;
 	const struct device_info *device_info;
 	struct mutex mutex;
+
+	struct snd_oxfw_stream_formation
+		rx_stream_formations[SND_OXFW_STREAM_FORMAT_ENTRIES];
 	struct cmp_connection in_conn;
 	struct amdtp_stream rx_stream;
+
 	bool mute;
 	s16 volume[6];
 	s16 volume_min;
@@ -54,8 +64,8 @@ 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 firewave_constraints(struct snd_pcm_runtime *runtime);
-int lacie_speakers_constraints(struct snd_pcm_runtime *runtime);
+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_pcm.c b/sound/firewire/oxfw/oxfw_pcm.c
index 1fd76ce..75c0520 100644
--- a/sound/firewire/oxfw/oxfw_pcm.c
+++ b/sound/firewire/oxfw/oxfw_pcm.c
@@ -7,117 +7,156 @@
 
 #include "oxfw.h"
 
-static int firewave_rate_constraint(struct snd_pcm_hw_params *params,
-				    struct snd_pcm_hw_rule *rule)
+static int hw_rule_rate(struct snd_pcm_hw_params *params,
+			struct snd_pcm_hw_rule *rule)
 {
-	static unsigned int stereo_rates[] = { 48000, 96000 };
-	struct snd_interval *channels =
-			hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
-	struct snd_interval *rate =
-			hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
-
-	/* two channels work only at 48/96 kHz */
-	if (snd_interval_max(channels) < 6)
-		return snd_interval_list(rate, 2, stereo_rates, 0);
-	return 0;
+	struct snd_oxfw_stream_formation *formations = rule->private;
+	struct snd_interval *r =
+		hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	const struct snd_interval *c =
+		hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_interval t = {
+		.min = UINT_MAX, .max = 0, .integer = 1
+	};
+	unsigned int i;
+
+	for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
+		if (formations[i].rate == 0)
+			continue;
+
+		if (!snd_interval_test(c, formations[i].pcm))
+			continue;
+
+		t.min = min(t.min, formations[i].rate);
+		t.max = max(t.max, formations[i].rate);
+
+	}
+	return snd_interval_refine(r, &t);
 }
 
-static int firewave_channels_constraint(struct snd_pcm_hw_params *params,
-					struct snd_pcm_hw_rule *rule)
+static int hw_rule_channels(struct snd_pcm_hw_params *params,
+			    struct snd_pcm_hw_rule *rule)
 {
-	static const struct snd_interval all_channels = { .min = 6, .max = 6 };
-	struct snd_interval *rate =
-			hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
-	struct snd_interval *channels =
-			hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
-
-	/* 32/44.1 kHz work only with all six channels */
-	if (snd_interval_max(rate) < 48000)
-		return snd_interval_refine(channels, &all_channels);
-	return 0;
+	struct snd_oxfw_stream_formation *formations = rule->private;
+	struct snd_interval *c =
+		hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	const struct snd_interval *r =
+		hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE);
+	unsigned int i, j, count, list[SND_OXFW_STREAM_FORMAT_ENTRIES] = {0};
+
+	count = 0;
+	for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
+		if (formations[i].rate == 0)
+			continue;
+
+		if (!snd_interval_test(r, formations[i].rate))
+			continue;
+		if (list[count] == formations[i].pcm)
+			continue;
+
+		for (j = 0; j < ARRAY_SIZE(list); j++) {
+			if (list[j] == formations[i].pcm)
+				break;
+		}
+		if (j == ARRAY_SIZE(list)) {
+			list[count] = formations[i].pcm;
+			if (++count == ARRAY_SIZE(list))
+				break;
+		}
+	}
+
+	return snd_interval_list(c, count, list, 0);
 }
 
-int firewave_constraints(struct snd_pcm_runtime *runtime)
+static void limit_channels_and_rates(struct snd_pcm_hardware *hw,
+				struct snd_oxfw_stream_formation *formations)
 {
-	static unsigned int channels_list[] = { 2, 6 };
-	static struct snd_pcm_hw_constraint_list channels_list_constraint = {
-		.count = 2,
-		.list = channels_list,
-	};
-	int err;
+	unsigned int i;
 
-	runtime->hw.rates = SNDRV_PCM_RATE_32000 |
-			    SNDRV_PCM_RATE_44100 |
-			    SNDRV_PCM_RATE_48000 |
-			    SNDRV_PCM_RATE_96000;
-	runtime->hw.channels_max = 6;
+	hw->channels_min = UINT_MAX;
+	hw->channels_max = 0;
 
-	err = snd_pcm_hw_constraint_list(runtime, 0,
-					 SNDRV_PCM_HW_PARAM_CHANNELS,
-					 &channels_list_constraint);
-	if (err < 0)
-		return err;
-	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
-				  firewave_rate_constraint, NULL,
-				  SNDRV_PCM_HW_PARAM_CHANNELS, -1);
-	if (err < 0)
-		return err;
-	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
-				  firewave_channels_constraint, NULL,
-				  SNDRV_PCM_HW_PARAM_RATE, -1);
-	if (err < 0)
-		return err;
+	hw->rate_min = UINT_MAX;
+	hw->rate_max = 0;
+	hw->rates = 0;
 
-	return 0;
+	for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
+		if (formations[i].rate == 0)
+			continue;
+
+		hw->channels_min = min(hw->channels_min, formations[i].pcm);
+		hw->channels_max = max(hw->channels_max, formations[i].pcm);
+
+		hw->rate_min = min(hw->rate_min, formations[i].rate);
+		hw->rate_max = max(hw->rate_max, formations[i].rate);
+		hw->rates |= snd_pcm_rate_to_rate_bit(formations[i].rate);
+	}
 }
 
-int lacie_speakers_constraints(struct snd_pcm_runtime *runtime)
+static void limit_period_and_buffer(struct snd_pcm_hardware *hw)
 {
-	runtime->hw.rates = SNDRV_PCM_RATE_32000 |
-			    SNDRV_PCM_RATE_44100 |
-			    SNDRV_PCM_RATE_48000 |
-			    SNDRV_PCM_RATE_88200 |
-			    SNDRV_PCM_RATE_96000;
+	hw->periods_min = 2;		/* SNDRV_PCM_INFO_BATCH */
+	hw->periods_max = UINT_MAX;
 
-	return 0;
+	hw->period_bytes_min = 4 * hw->channels_max;	/* bytes for a frame */
+
+	/* Just to prevent from allocating much pages. */
+	hw->period_bytes_max = hw->period_bytes_min * 2048;
+	hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min;
 }
 
 static int pcm_open(struct snd_pcm_substream *substream)
 {
-	static const struct snd_pcm_hardware hardware = {
-		.info = SNDRV_PCM_INFO_MMAP |
-			SNDRV_PCM_INFO_MMAP_VALID |
-			SNDRV_PCM_INFO_BATCH |
-			SNDRV_PCM_INFO_INTERLEAVED |
-			SNDRV_PCM_INFO_BLOCK_TRANSFER,
-		.formats = AMDTP_OUT_PCM_FORMAT_BITS,
-		.channels_min = 2,
-		.channels_max = 2,
-		.buffer_bytes_max = 4 * 1024 * 1024,
-		.period_bytes_min = 1,
-		.period_bytes_max = UINT_MAX,
-		.periods_min = 1,
-		.periods_max = UINT_MAX,
-	};
 	struct snd_oxfw *oxfw = substream->private_data;
 	struct snd_pcm_runtime *runtime = substream->runtime;
-	bool used;
+	struct snd_oxfw_stream_formation *formations;
+	unsigned int rate;
 	int err;
 
-	err = cmp_connection_check_used(&oxfw->in_conn, &used);
-	if ((err < 0) || used)
-		goto end;
+	formations = oxfw->rx_stream_formations;
+
+	runtime->hw.info = SNDRV_PCM_INFO_BATCH |
+			   SNDRV_PCM_INFO_BLOCK_TRANSFER |
+			   SNDRV_PCM_INFO_INTERLEAVED |
+			   SNDRV_PCM_INFO_MMAP |
+			   SNDRV_PCM_INFO_MMAP_VALID;
 
-	runtime->hw = hardware;
+	limit_channels_and_rates(&runtime->hw, formations);
+	limit_period_and_buffer(&runtime->hw);
 
-	err = oxfw->device_info->pcm_constraints(runtime);
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+				  hw_rule_channels, formations,
+				  SNDRV_PCM_HW_PARAM_RATE, -1);
 	if (err < 0)
 		goto end;
-	err = snd_pcm_limit_hw_rates(runtime);
+
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				  hw_rule_rate, formations,
+				  SNDRV_PCM_HW_PARAM_CHANNELS, -1);
 	if (err < 0)
 		goto end;
 
 	err = amdtp_stream_add_pcm_hw_constraints(&oxfw->rx_stream, runtime);
+	if (err < 0)
+		goto end;
+
+	/*
+	 * When any PCM streams are already running, the available sampling
+	 * rate is limited at current value.
+	 */
+	if (amdtp_stream_pcm_running(&oxfw->rx_stream)) {
+		err = avc_general_get_sig_fmt(oxfw->unit, &rate,
+					      AVC_GENERAL_PLUG_DIR_IN, 0);
+		if (err < 0) {
+			dev_err(&oxfw->unit->device,
+				"fail to get sampling rate: %d\n", err);
+			goto end;
+		}
+		runtime->hw.rate_min = rate;
+		runtime->hw.rate_max = rate;
+	}
+
+	snd_pcm_set_sync(substream);
 end:
 	return err;
 }
diff --git a/sound/firewire/oxfw/oxfw_stream.c b/sound/firewire/oxfw/oxfw_stream.c
index 59861b2..954584e 100644
--- a/sound/firewire/oxfw/oxfw_stream.c
+++ b/sound/firewire/oxfw/oxfw_stream.c
@@ -8,6 +8,20 @@
 
 #include "oxfw.h"
 
+/*
+ * According to their datasheet:
+ *  OXFW970: 32.0/44.1/48.0/96.0 Khz, 8 audio channels I/O
+ *  OXFW971: 32.0/44.1/48.0/88.2/96.0/192.0 kHz, 16 audio channels I/O, MIDI I/O
+ */
+static const unsigned int oxfw_rate_table[] = {
+	[0] = 32000,
+	[1] = 44100,
+	[2] = 48000,
+	[3] = 88200,
+	[4] = 96000,
+	[5] = 192000,
+};
+
 int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw)
 {
 	int err;
@@ -90,3 +104,37 @@ void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw)
 		amdtp_stream_update(&oxfw->rx_stream);
 	}
 }
+
+int firewave_stream_discover(struct snd_oxfw *oxfw)
+{
+	/* 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;
+
+	return 0;
+}
+
+int lacie_speakers_stream_discover(struct snd_oxfw *oxfw)
+{
+	unsigned int i;
+
+	/* 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;
+	}
+
+	return 0;
+}
-- 
1.8.3.2


------------------------------------------------------------------------------
"Accelerate Dev Cycles with Automated Cross-Browser Testing - For FREE
Instantly run your Selenium tests across 300+ browser/OS combos.
Get unparalleled scalability from the best Selenium testing platform available
Simple to use. Nothing to install. Get started now for free."
http://p.sf.net/sfu/SauceLabs

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 08/15] oxfw: Add proc interface for debugging purpose
  2014-05-13 14:27 [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Takashi Sakamoto
                   ` (6 preceding siblings ...)
  2014-05-13 14:27 ` [PATCH 07/15] oxfw: Change the way to make PCM rules/constraints Takashi Sakamoto
@ 2014-05-13 14:27 ` Takashi Sakamoto
  2014-05-13 14:27 ` [PATCH 09/15] oxfw: Change the way to start stream Takashi Sakamoto
                   ` (9 subsequent siblings)
  17 siblings, 0 replies; 35+ messages in thread
From: Takashi Sakamoto @ 2014-05-13 14:27 UTC (permalink / raw)
  To: clemens; +Cc: alsa-devel, fenlason, ffado-devel, linux1394-devel

This commit adds proc interface to get information about:
 - stream formation
 - current sampling rate

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/oxfw/Makefile    |  2 +-
 sound/firewire/oxfw/oxfw.c      |  2 ++
 sound/firewire/oxfw/oxfw.h      |  3 ++
 sound/firewire/oxfw/oxfw_proc.c | 77 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 83 insertions(+), 1 deletion(-)
 create mode 100644 sound/firewire/oxfw/oxfw_proc.c

diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile
index 3eaec6d..53b5572 100644
--- a/sound/firewire/oxfw/Makefile
+++ b/sound/firewire/oxfw/Makefile
@@ -1,2 +1,2 @@
-snd-oxfw-objs := oxfw_stream.o oxfw_control.o oxfw_pcm.o oxfw.o
+snd-oxfw-objs := 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 cfdf420..d900718 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -120,6 +120,8 @@ static int oxfw_probe(struct fw_unit *unit,
 	if (err < 0)
 		goto error;
 
+	snd_oxfw_proc_init(oxfw);
+
 	err = snd_card_register(card);
 	if (err < 0) {
 		snd_oxfw_stream_destroy_simplex(oxfw);
diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h
index 000c2af..5d14424 100644
--- a/sound/firewire/oxfw/oxfw.h
+++ b/sound/firewire/oxfw/oxfw.h
@@ -18,6 +18,7 @@
 #include <sound/initval.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
+#include <sound/info.h>
 
 #include "../lib.h"
 #include "../fcp.h"
@@ -69,3 +70,5 @@ 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);
+
+void snd_oxfw_proc_init(struct snd_oxfw *oxfw);
diff --git a/sound/firewire/oxfw/oxfw_proc.c b/sound/firewire/oxfw/oxfw_proc.c
new file mode 100644
index 0000000..dd287dc
--- /dev/null
+++ b/sound/firewire/oxfw/oxfw_proc.c
@@ -0,0 +1,77 @@
+/*
+ * oxfw_proc.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"
+
+static void proc_read_formation(struct snd_info_entry *entry,
+				struct snd_info_buffer *buffer)
+{
+	struct snd_oxfw *oxfw = entry->private_data;
+	struct snd_oxfw_stream_formation *formation;
+	unsigned int i;
+
+	snd_iprintf(buffer, "Input Stream to device:\n");
+	snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n");
+	formation = oxfw->rx_stream_formations;
+	for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
+		snd_iprintf(buffer,
+			"\t%d\t%d\t%d\n", formation[i].rate,
+			formation[i].pcm, formation[i].midi);
+	}
+}
+
+static void proc_read_clock(struct snd_info_entry *entry,
+			    struct snd_info_buffer *buffer)
+{
+	struct snd_oxfw *oxfw = entry->private_data;
+	unsigned int rate;
+	int err;
+
+	err = avc_general_get_sig_fmt(oxfw->unit, &rate,
+				      AVC_GENERAL_PLUG_DIR_IN, 0);
+	if (err >= 0)
+		snd_iprintf(buffer, "Sampling rate: %d\n", rate);
+}
+
+static void add_node(struct snd_oxfw *oxfw, struct snd_info_entry *root,
+		     const char *name,
+		     void (*op)(struct snd_info_entry *e,
+				struct snd_info_buffer *b))
+{
+	struct snd_info_entry *entry;
+
+	entry = snd_info_create_card_entry(oxfw->card, name, root);
+	if (entry == NULL)
+		return;
+
+	snd_info_set_text_ops(entry, oxfw, op);
+	if (snd_info_register(entry) < 0)
+		snd_info_free_entry(entry);
+}
+
+void snd_oxfw_proc_init(struct snd_oxfw *oxfw)
+{
+	struct snd_info_entry *root;
+
+	/*
+	 * All nodes are automatically removed at snd_card_disconnect(),
+	 * by following to link list.
+	 */
+	root = snd_info_create_card_entry(oxfw->card, "firewire",
+					  oxfw->card->proc_root);
+	if (root == NULL)
+		return;
+	root->mode = S_IFDIR | S_IRUGO | S_IXUGO;
+	if (snd_info_register(root) < 0) {
+		snd_info_free_entry(root);
+		return;
+	}
+
+	add_node(oxfw, root, "clock", proc_read_clock);
+	add_node(oxfw, root, "formation", proc_read_formation);
+}
-- 
1.8.3.2


------------------------------------------------------------------------------
"Accelerate Dev Cycles with Automated Cross-Browser Testing - For FREE
Instantly run your Selenium tests across 300+ browser/OS combos.
Get unparalleled scalability from the best Selenium testing platform available
Simple to use. Nothing to install. Get started now for free."
http://p.sf.net/sfu/SauceLabs

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 09/15] oxfw: Change the way to start stream
  2014-05-13 14:27 [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Takashi Sakamoto
                   ` (7 preceding siblings ...)
  2014-05-13 14:27 ` [PATCH 08/15] oxfw: Add proc interface for debugging purpose Takashi Sakamoto
@ 2014-05-13 14:27 ` Takashi Sakamoto
  2014-05-13 14:27 ` [PATCH 10/15] oxfw: Add support for AV/C stream format command to get supported stream formation Takashi Sakamoto
                   ` (8 subsequent siblings)
  17 siblings, 0 replies; 35+ messages in thread
From: Takashi Sakamoto @ 2014-05-13 14:27 UTC (permalink / raw)
  To: clemens; +Cc: alsa-devel, fenlason, ffado-devel, linux1394-devel

In past commit, this driver can keep stream formations for each sampling
rate. So its stream functionality can decide stream formations when given
some parameters.

This commit moves related codes from PCM functionality to stream
functionality.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/oxfw/oxfw.h        |  3 +-
 sound/firewire/oxfw/oxfw_pcm.c    | 38 +++------------
 sound/firewire/oxfw/oxfw_stream.c | 99 ++++++++++++++++++++++++++++++++++-----
 3 files changed, 96 insertions(+), 44 deletions(-)

diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h
index 5d14424..e845581 100644
--- a/sound/firewire/oxfw/oxfw.h
+++ b/sound/firewire/oxfw/oxfw.h
@@ -60,7 +60,8 @@ struct snd_oxfw {
 };
 
 int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw);
-int snd_oxfw_stream_start_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);
diff --git a/sound/firewire/oxfw/oxfw_pcm.c b/sound/firewire/oxfw/oxfw_pcm.c
index 75c0520..8f65e10 100644
--- a/sound/firewire/oxfw/oxfw_pcm.c
+++ b/sound/firewire/oxfw/oxfw_pcm.c
@@ -170,36 +170,10 @@ static int pcm_hw_params(struct snd_pcm_substream *substream,
 			 struct snd_pcm_hw_params *hw_params)
 {
 	struct snd_oxfw *oxfw = substream->private_data;
-	int err;
-
-	snd_oxfw_stream_stop_simplex(oxfw);
-
-	err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
-					       params_buffer_bytes(hw_params));
-	if (err < 0)
-		goto error;
-
-	amdtp_stream_set_parameters(&oxfw->rx_stream,
-				    params_rate(hw_params),
-				    params_channels(hw_params),
-				    0);
 
-	amdtp_stream_set_pcm_format(&oxfw->rx_stream,
-				    params_format(hw_params));
-
-	err = avc_general_set_sig_fmt(oxfw->unit, params_rate(hw_params),
-				      AVC_GENERAL_PLUG_DIR_IN, 0);
-	if (err < 0) {
-		dev_err(&oxfw->unit->device, "failed to set sample rate\n");
-		goto err_buffer;
-	}
-
-	return 0;
-
-err_buffer:
-	snd_pcm_lib_free_vmalloc_buffer(substream);
-error:
-	return err;
+	amdtp_stream_set_pcm_format(&oxfw->rx_stream, params_format(hw_params));
+	return snd_pcm_lib_alloc_vmalloc_buffer(substream,
+						params_buffer_bytes(hw_params));
 }
 
 static int pcm_hw_free(struct snd_pcm_substream *substream)
@@ -214,11 +188,11 @@ static int pcm_hw_free(struct snd_pcm_substream *substream)
 static int pcm_prepare(struct snd_pcm_substream *substream)
 {
 	struct snd_oxfw *oxfw = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
 	int err;
 
-	snd_oxfw_stream_stop_simplex(oxfw);
-
-	err = snd_oxfw_stream_start_simplex(oxfw);
+	err = snd_oxfw_stream_start_simplex(oxfw, runtime->rate,
+					    runtime->channels);
 	if (err < 0)
 		goto end;
 
diff --git a/sound/firewire/oxfw/oxfw_stream.c b/sound/firewire/oxfw/oxfw_stream.c
index 954584e..2d09df8 100644
--- a/sound/firewire/oxfw/oxfw_stream.c
+++ b/sound/firewire/oxfw/oxfw_stream.c
@@ -8,6 +8,8 @@
 
 #include "oxfw.h"
 
+#define CALLBACK_TIMEOUT	200
+
 /*
  * According to their datasheet:
  *  OXFW970: 32.0/44.1/48.0/96.0 Khz, 8 audio channels I/O
@@ -48,29 +50,104 @@ static int stop_stream(struct snd_oxfw *oxfw)
 	cmp_connection_break(&oxfw->in_conn);
 }
 
-int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw)
+static int start_stream(struct snd_oxfw *oxfw, unsigned int rate,
+			unsigned int pcm_channels)
 {
-	int err = 0;
+	struct snd_oxfw_stream_formation *formations;
+	unsigned int i, midi_ports;
+	struct amdtp_stream *stream;
+	struct cmp_connection *conn;
+	int err;
 
-	mutex_lock(&oxfw->mutex);
+	stream = &oxfw->rx_stream;
+	formations = oxfw->rx_stream_formations;
+	conn = &oxfw->in_conn;
+
+	/* Get stream formation */
+	for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
+		if (formations[i].rate != rate)
+			continue;
+		if ((pcm_channels == 0) ||
+		    (formations[i].pcm == pcm_channels))
+			break;
+	}
+	if (i == SND_OXFW_STREAM_FORMAT_ENTRIES) {
+		err = -EINVAL;
+		goto end;
+	}
 
-	if (amdtp_streaming_error(&oxfw->rx_stream))
-		stop_stream(oxfw);
+	pcm_channels = formations[i].pcm;
+	midi_ports = formations[i].midi;
 
-	if (amdtp_stream_running(&oxfw->rx_stream))
+	/* The stream should have one pcm channels at least */
+	if (pcm_channels == 0) {
+		err = -EINVAL;
 		goto end;
+	}
+	amdtp_stream_set_parameters(stream, rate, pcm_channels, midi_ports);
 
-	err = cmp_connection_establish(&oxfw->in_conn,
-			amdtp_stream_get_max_payload(&oxfw->rx_stream));
+	/* Establish connection */
+	err = cmp_connection_establish(conn,
+				       amdtp_stream_get_max_payload(stream));
 	if (err < 0)
 		goto end;
 
-	err = amdtp_stream_start(&oxfw->rx_stream,
-				 oxfw->in_conn.resources.channel,
-				 oxfw->in_conn.speed);
+	/* Start stream */
+	err = amdtp_stream_start(stream,
+				 conn->resources.channel,
+				 conn->speed);
+	if (err < 0) {
+		cmp_connection_break(conn);
+		goto end;
+	}
+
+	/* Wait first callback */
+	err = amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT);
 	if (err < 0)
 		stop_stream(oxfw);
 end:
+	return err;
+}
+
+int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw, unsigned int rate,
+				  unsigned int pcm_channels)
+{
+	unsigned int curr_rate;
+	int err = 0;
+
+	mutex_lock(&oxfw->mutex);
+
+	/* packet queueing error */
+	if (amdtp_streaming_error(&oxfw->rx_stream))
+		stop_stream(oxfw);
+
+	/* arrange sampling rate */
+	err = avc_general_get_sig_fmt(oxfw->unit, &curr_rate,
+				      AVC_GENERAL_PLUG_DIR_IN, 0);
+	if (err < 0) {
+		dev_err(&oxfw->unit->device,
+			"fail to get sampling rate: %d\n", err);
+		goto end;
+	}
+	if (curr_rate != rate) {
+		stop_stream(oxfw);
+
+		err = avc_general_set_sig_fmt(oxfw->unit, rate,
+					      AVC_GENERAL_PLUG_DIR_IN, 0);
+		if (err < 0) {
+			dev_err(&oxfw->unit->device,
+				"fail to set sampling rate: %d\n", err);
+			goto end;
+		}
+	}
+
+	if (!amdtp_stream_running(&oxfw->rx_stream)) {
+		err = start_stream(oxfw, rate, pcm_channels);
+		if (err < 0)
+			dev_err(&oxfw->unit->device,
+				"fail to start stream: %d\n", err);
+	}
+end:
 	mutex_unlock(&oxfw->mutex);
 	return err;
 }
-- 
1.8.3.2


------------------------------------------------------------------------------
"Accelerate Dev Cycles with Automated Cross-Browser Testing - For FREE
Instantly run your Selenium tests across 300+ browser/OS combos.
Get unparalleled scalability from the best Selenium testing platform available
Simple to use. Nothing to install. Get started now for free."
http://p.sf.net/sfu/SauceLabs

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 10/15] oxfw: Add support for AV/C stream format command to get supported stream formation
  2014-05-13 14:27 [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Takashi Sakamoto
                   ` (8 preceding siblings ...)
  2014-05-13 14:27 ` [PATCH 09/15] oxfw: Change the way to start stream Takashi Sakamoto
@ 2014-05-13 14:27 ` Takashi Sakamoto
  2014-05-13 14:27 ` [PATCH 11/15] oxfw: Add a quirk for Griffin FireWave Takashi Sakamoto
                   ` (7 subsequent siblings)
  17 siblings, 0 replies; 35+ messages in thread
From: Takashi Sakamoto @ 2014-05-13 14:27 UTC (permalink / raw)
  To: clemens; +Cc: alsa-devel, fenlason, ffado-devel, linux1394-devel

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 <o-takashi@sakamocchi.jp>
---
 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;
 }
-- 
1.8.3.2


------------------------------------------------------------------------------
"Accelerate Dev Cycles with Automated Cross-Browser Testing - For FREE
Instantly run your Selenium tests across 300+ browser/OS combos.
Get unparalleled scalability from the best Selenium testing platform available
Simple to use. Nothing to install. Get started now for free."
http://p.sf.net/sfu/SauceLabs
_______________________________________________
mailing list linux1394-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linux1394-devel

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 11/15] oxfw: Add a quirk for Griffin FireWave
  2014-05-13 14:27 [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Takashi Sakamoto
                   ` (9 preceding siblings ...)
  2014-05-13 14:27 ` [PATCH 10/15] oxfw: Add support for AV/C stream format command to get supported stream formation Takashi Sakamoto
@ 2014-05-13 14:27 ` Takashi Sakamoto
  2014-05-13 14:27 ` [PATCH 12/15] oxfw: Add support for Behringer/Mackie devices Takashi Sakamoto
                   ` (6 subsequent siblings)
  17 siblings, 0 replies; 35+ messages in thread
From: Takashi Sakamoto @ 2014-05-13 14:27 UTC (permalink / raw)
  To: clemens; +Cc: alsa-devel, fenlason, ffado-devel, linux1394-devel

Griffin FireWave has 7 entries for stream formation. But via 'LIST'
subfunction of AV/C Stream Format Information command, 6 of
them can be retrieved. The rest can be retrieved via 'SINGLE'
subfunction of the command.

This commit adds a workaround for this quirk.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/oxfw/oxfw_stream.c | 23 +++++++++++++++++++++--
 1 file changed, 21 insertions(+), 2 deletions(-)

diff --git a/sound/firewire/oxfw/oxfw_stream.c b/sound/firewire/oxfw/oxfw_stream.c
index 0d81bba..4d66648 100644
--- a/sound/firewire/oxfw/oxfw_stream.c
+++ b/sound/firewire/oxfw/oxfw_stream.c
@@ -365,8 +365,8 @@ static int fill_stream_formations(struct snd_oxfw *oxfw,
 				  unsigned short pid)
 {
 	u8 *buf;
-	struct snd_oxfw_stream_formation *formations;
-	unsigned int len, eid = 0;
+	struct snd_oxfw_stream_formation *formations, tmp;
+	unsigned int i, len, eid = 0;
 	int err;
 
 	buf = kmalloc(AVC_GENERIC_FRAME_MAXIMUM_BYTES, GFP_KERNEL);
@@ -416,6 +416,25 @@ static int fill_stream_formations(struct snd_oxfw *oxfw,
 			break;
 		}
 	}
+
+	if (eid >= SND_OXFW_STREAM_FORMAT_ENTRIES)
+		goto end;
+
+	/* Griffin FireWave have another entry in current formation. */
+	len = AVC_GENERIC_FRAME_MAXIMUM_BYTES;
+	err = avc_stream_get_format_single(oxfw->unit, dir, 0, buf, &len);
+	if (err < 0)
+		goto end;
+	err = parse_stream_formation(buf, len, &tmp);
+	if (err < 0)
+		goto end;
+	/* Store this if no duplicates. */
+	for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
+		if (memcmp(&formations[i], &tmp, sizeof(tmp)) == 0)
+			break;
+	}
+	if (i == SND_OXFW_STREAM_FORMAT_ENTRIES)
+		formations[eid] = tmp;
 end:
 	kfree(buf);
 	return err;
-- 
1.8.3.2


------------------------------------------------------------------------------
"Accelerate Dev Cycles with Automated Cross-Browser Testing - For FREE
Instantly run your Selenium tests across 300+ browser/OS combos.
Get unparalleled scalability from the best Selenium testing platform available
Simple to use. Nothing to install. Get started now for free."
http://p.sf.net/sfu/SauceLabs

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 12/15] oxfw: Add support for Behringer/Mackie devices
  2014-05-13 14:27 [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Takashi Sakamoto
                   ` (10 preceding siblings ...)
  2014-05-13 14:27 ` [PATCH 11/15] oxfw: Add a quirk for Griffin FireWave Takashi Sakamoto
@ 2014-05-13 14:27 ` Takashi Sakamoto
  2014-08-10 15:54   ` [alsa-devel] " Clemens Ladisch
  2014-05-13 14:27 ` [PATCH 13/15] oxfw: Add support AMDTP in-stream and PCM capture Takashi Sakamoto
                   ` (5 subsequent siblings)
  17 siblings, 1 reply; 35+ messages in thread
From: Takashi Sakamoto @ 2014-05-13 14:27 UTC (permalink / raw)
  To: clemens; +Cc: alsa-devel, fenlason, ffado-devel, linux1394-devel

Some devices produced by Behringer/Mackie are based on OXFW970/971. This
commit adds support for them. Additionally, this commit changes the way to
name card with some information in config rom.

The devices support capture/playback of PCM-samples and some of them
supports capture/playback of MIDI messages. These functionalities are
implemented by followed commits.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/Kconfig     |  4 +++
 sound/firewire/oxfw/oxfw.c | 74 +++++++++++++++++++++++++++++++++++++++++-----
 2 files changed, 70 insertions(+), 8 deletions(-)

diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig
index 63f3cea..38027bd 100644
--- a/sound/firewire/Kconfig
+++ b/sound/firewire/Kconfig
@@ -35,6 +35,10 @@ config SND_OXFW
 	  Oxford Semiconductor OXFW970/971 chipset.
 	   * Griffin Firewave
 	   * LaCie Firewire Speakers
+	   * Behringer F-Control Audio 202
+	   * Mackie(Loud) Onyx-i series (former models)
+	   * Mackie(Loud) Onyx Satellite
+	   * Mackie(Loud) Tapco Link.Firewire
 
 	  To compile this driver as a module, choose M here: the module
 	  will be called snd-oxfw.
diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c
index 825377a..0c9b8e2 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -16,6 +16,8 @@
 
 #define VENDOR_GRIFFIN		0x001292
 #define VENDOR_LACIE		0x00d04b
+#define VEN_BEHRINGER		0x001564
+#define VEN_LOUD		0x000ff2
 
 #define SPECIFIER_1394TA	0x00a02d
 #define VERSION_AVC		0x010001
@@ -44,19 +46,40 @@ static const struct device_info lacie_speakers = {
 static int name_card(struct snd_oxfw *oxfw)
 {
 	struct fw_device *fw_dev = fw_parent_device(oxfw->unit);
+	char vendor[24] = {0};
+	char model[32] = {0};
 	const char *d, *v, *m;
 	u32 firmware;
 	int err;
 
+	/* get vendor name from root directory */
+	err = fw_csr_string(fw_dev->config_rom + 5, CSR_VENDOR,
+			    vendor, sizeof(vendor));
+	if (err < 0)
+		goto end;
+
+	/* get model name from unit directory */
+	err = fw_csr_string(oxfw->unit->directory, CSR_MODEL,
+			    model, sizeof(model));
+	if (err < 0)
+		goto end;
+
 	err = snd_fw_transaction(oxfw->unit, TCODE_READ_QUADLET_REQUEST,
 				 OXFORD_FIRMWARE_ID_ADDRESS, &firmware, 4, 0);
 	if (err < 0)
 		goto end;
 	be32_to_cpus(&firmware);
 
-	d = oxfw->device_info->driver_name;
-	v = oxfw->device_info->vendor_name;
-	m = oxfw->device_info->driver_name;
+	/* to apply card definitions */
+	if (oxfw->device_info) {
+		d = oxfw->device_info->driver_name;
+		v = oxfw->device_info->vendor_name;
+		m = oxfw->device_info->driver_name;
+	} else {
+		d = "OXFW";
+		v = vendor;
+		m = model;
+	}
 
 	strcpy(oxfw->card->driver, d);
 	strcpy(oxfw->card->shortname, m);
@@ -109,16 +132,18 @@ static int oxfw_probe(struct fw_unit *unit,
 	if (err < 0)
 		goto error;
 
-	err = oxfw_create_mixer(oxfw);
-	if (err < 0)
-		goto error;
+	if (oxfw->device_info) {
+		err = snd_oxfw_create_mixer(oxfw);
+		if (err < 0)
+			goto error;
+	}
+
+	snd_oxfw_proc_init(oxfw);
 
 	err = snd_oxfw_stream_init_simplex(oxfw);
 	if (err < 0)
 		goto error;
 
-	snd_oxfw_proc_init(oxfw);
-
 	err = snd_card_register(card);
 	if (err < 0) {
 		snd_oxfw_stream_destroy_simplex(oxfw);
@@ -173,6 +198,39 @@ static const struct ieee1394_device_id oxfw_id_table[] = {
 		.version      = VERSION_AVC,
 		.driver_data  = (kernel_ulong_t)&lacie_speakers,
 	},
+	/* Behringer,F-Control Audio 202 */
+	{
+		.match_flags	= IEEE1394_MATCH_VENDOR_ID |
+				  IEEE1394_MATCH_MODEL_ID,
+		.vendor_id	= VEN_BEHRINGER,
+		.model_id	= 0x00fc22,
+	},
+	/* Mackie(Loud), Onyx-i series (former models) */
+	{
+		.match_flags	= IEEE1394_MATCH_VENDOR_ID |
+				  IEEE1394_MATCH_MODEL_ID,
+		.vendor_id	= VEN_LOUD,
+		.model_id	= 0x081216,
+	},
+	/* Mackie(Loud), Onyx Satellite */
+	{
+		.match_flags	= IEEE1394_MATCH_VENDOR_ID |
+				  IEEE1394_MATCH_MODEL_ID,
+		.vendor_id	= VEN_LOUD,
+		.model_id	= 0x00200f,
+	},
+	/*  Mackie(Loud), Tapco Link.Firewire */
+	{
+		.match_flags	= IEEE1394_MATCH_VENDOR_ID |
+				  IEEE1394_MATCH_MODEL_ID,
+		.vendor_id	= VEN_LOUD,
+		.model_id	= 0x000460,
+	},
+	/* IDs are unknown but able to be supported */
+	/*  Mackie(Loud), d.2 pro */
+	/*  Mackie(Loud), d.4 pro */
+	/*  Mackie(Loud), U.420 */
+	/*  Mackie(Loud), U.420d */
 	{ }
 };
 MODULE_DEVICE_TABLE(ieee1394, oxfw_id_table);
-- 
1.8.3.2


------------------------------------------------------------------------------
"Accelerate Dev Cycles with Automated Cross-Browser Testing - For FREE
Instantly run your Selenium tests across 300+ browser/OS combos.
Get unparalleled scalability from the best Selenium testing platform available
Simple to use. Nothing to install. Get started now for free."
http://p.sf.net/sfu/SauceLabs

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 13/15] oxfw: Add support AMDTP in-stream and PCM capture
  2014-05-13 14:27 [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Takashi Sakamoto
                   ` (11 preceding siblings ...)
  2014-05-13 14:27 ` [PATCH 12/15] oxfw: Add support for Behringer/Mackie devices Takashi Sakamoto
@ 2014-05-13 14:27 ` Takashi Sakamoto
  2014-05-13 14:27 ` [PATCH 14/15] oxfw: Add support for capture/playback MIDI messages Takashi Sakamoto
                   ` (4 subsequent siblings)
  17 siblings, 0 replies; 35+ messages in thread
From: Takashi Sakamoto @ 2014-05-13 14:27 UTC (permalink / raw)
  To: clemens; +Cc: alsa-devel, fenlason, ffado-devel, linux1394-devel

Previous commit adds support for some devices which can capture PCM samples.
These devices transmit AMDTP stream in non-blocking mode. This commit adds
functionality to handle AMDTP incoming stream and to capture PCM samples.

OXFW seems to have two quirks:
 - Transmits packets with non-zero dbc in its beginning
 - Transmits packets with wrong values in syt field

For the first quirk, this commit adds CIP_SKIP_INIT_DBC_CHECK flag for
incoming stream to skip first check of dbc.

For the second quirk, this commit doesn't add duplex stream which
Fireworks/BeBoB drivers use. So OXFW driver generates syt value for outgoing
stream.

Here are examples of a sequence of packets transmitted by Behringer F-Control
Audio 202. There are differences between sequences of syt value when OXFW
driver transfers outgoing stream or not.

When driver gives no outgoing stream:
Index	Payload	CIP_Header_0	CIP_Header_1
38	14	00020092	900103D1
39	12	00020098	900102FF
40	12	0002009D	9001027F
41	14	000200A2	90010396
42	14	000200A8	900102E8
43	12	000200AE	90010219
44	14	000200B3	90010331
45	12	000200B9	9001025F
46	14	000200BE	90010376
47	12	000200C4	900102A1
00	12	000200C9	9001023E
01	14	000200CE	90010358
02	12	000200D4	90010289
03	16	000200D9	900103A3
04	12	000200E0	900102DD
05	14	000200E5	900103F1
06	12	000200EB	90010335
07	12	000200F0	90010263
08	14	000200F5	9001037C
09	12	000200FB	900102AE

When driver gives outgoing stream:
Index	Payload	CIP_Header_0	CIP_Header_1
38	12	000200BD	900104A8
39	14	000200C2	900104A8
40	12	000200C8	900104AC
41	14	000200CD	900104A9
42	12	000200D3	900104B1
43	14	000200D8	900104A8
44	12	000200DE	900104AA
45	14	000200E3	900104A9
46	14	000200E9	900104AE
47	12	000200EF	900104A8
00	14	000200F4	900104AD
01	12	000200FA	900104A7
02	14	000200FF	900104A9
03	12	00020005	900104A9
04	14	0002000A	900104B1
05	12	00020010	900104AA
06	14	00020015	900104AD
07	12	0002001B	900104A7
08	14	00020020	900104AC
09	12	00020026	900104A7

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/oxfw/oxfw.c        |  19 ++-
 sound/firewire/oxfw/oxfw.h        |  28 ++++-
 sound/firewire/oxfw/oxfw_pcm.c    | 164 +++++++++++++++++++++-----
 sound/firewire/oxfw/oxfw_proc.c   |  14 ++-
 sound/firewire/oxfw/oxfw_stream.c | 236 ++++++++++++++++++++++++++++++--------
 5 files changed, 370 insertions(+), 91 deletions(-)

diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c
index 0c9b8e2..a9e21c0 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -140,13 +140,20 @@ static int oxfw_probe(struct fw_unit *unit,
 
 	snd_oxfw_proc_init(oxfw);
 
-	err = snd_oxfw_stream_init_simplex(oxfw);
+	err = snd_oxfw_stream_init_simplex(oxfw, &oxfw->rx_stream);
 	if (err < 0)
 		goto error;
+	if (oxfw->has_output) {
+		err = snd_oxfw_stream_init_simplex(oxfw, &oxfw->tx_stream);
+		if (err < 0)
+			goto error;
+	}
 
 	err = snd_card_register(card);
 	if (err < 0) {
-		snd_oxfw_stream_destroy_simplex(oxfw);
+		snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->rx_stream);
+		if (oxfw->has_output)
+			snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->tx_stream);
 		goto error;
 	}
 	dev_set_drvdata(&unit->device, oxfw);
@@ -162,14 +169,18 @@ static void oxfw_bus_reset(struct fw_unit *unit)
 	struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
 
 	fcp_bus_reset(oxfw->unit);
-	snd_oxfw_stream_update_simplex(oxfw);
+	snd_oxfw_stream_update_simplex(oxfw, &oxfw->rx_stream);
+	if (oxfw->has_output)
+		snd_oxfw_stream_update_simplex(oxfw, &oxfw->tx_stream);
 }
 
 static void oxfw_remove(struct fw_unit *unit)
 {
 	struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
 
-	snd_oxfw_stream_destroy_simplex(oxfw);
+	snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->rx_stream);
+	if (oxfw->has_output)
+		snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->tx_stream);
 
 	snd_card_disconnect(oxfw->card);
 	snd_card_free_when_closed(oxfw->card);
diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h
index b21dfce..81b6627 100644
--- a/sound/firewire/oxfw/oxfw.h
+++ b/sound/firewire/oxfw/oxfw.h
@@ -48,10 +48,17 @@ struct snd_oxfw {
 	const struct device_info *device_info;
 	struct mutex mutex;
 
+	bool has_output;
+	struct snd_oxfw_stream_formation
+		tx_stream_formations[SND_OXFW_STREAM_FORMAT_ENTRIES];
 	struct snd_oxfw_stream_formation
 		rx_stream_formations[SND_OXFW_STREAM_FORMAT_ENTRIES];
+	struct cmp_connection out_conn;
 	struct cmp_connection in_conn;
+	struct amdtp_stream tx_stream;
 	struct amdtp_stream rx_stream;
+	atomic_t capture_substreams;
+	atomic_t playback_substreams;
 
 	bool mute;
 	s16 volume[6];
@@ -90,12 +97,21 @@ 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_get_rate(struct snd_oxfw *oxfw, unsigned int *rate);
+int snd_oxfw_stream_set_rate(struct snd_oxfw *oxfw, unsigned int rate);
+
+int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw,
+				 struct amdtp_stream *stream);
+int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw,
+				  struct amdtp_stream *stream,
+				  unsigned int rate, unsigned int pcm_channels);
+void snd_oxfw_stream_stop_simplex(struct snd_oxfw *oxfw,
+				  struct amdtp_stream *stream);
+void snd_oxfw_stream_destroy_simplex(struct snd_oxfw *oxfw,
+				     struct amdtp_stream *stream);
+void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw,
+				    struct amdtp_stream *stream);
+
 int snd_oxfw_stream_discover(struct snd_oxfw *oxfw);
 
 int snd_oxfw_create_pcm(struct snd_oxfw *oxfw);
diff --git a/sound/firewire/oxfw/oxfw_pcm.c b/sound/firewire/oxfw/oxfw_pcm.c
index 8f65e10..e763eef 100644
--- a/sound/firewire/oxfw/oxfw_pcm.c
+++ b/sound/firewire/oxfw/oxfw_pcm.c
@@ -105,22 +105,31 @@ static void limit_period_and_buffer(struct snd_pcm_hardware *hw)
 	hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min;
 }
 
-static int pcm_open(struct snd_pcm_substream *substream)
+static int init_hw_params(struct snd_oxfw *oxfw,
+			  struct snd_pcm_substream *substream)
 {
-	struct snd_oxfw *oxfw = substream->private_data;
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	struct snd_oxfw_stream_formation *formations;
-	unsigned int rate;
+	struct amdtp_stream *stream;
 	int err;
 
-	formations = oxfw->rx_stream_formations;
-
 	runtime->hw.info = SNDRV_PCM_INFO_BATCH |
 			   SNDRV_PCM_INFO_BLOCK_TRANSFER |
 			   SNDRV_PCM_INFO_INTERLEAVED |
+			   SNDRV_PCM_INFO_JOINT_DUPLEX |
 			   SNDRV_PCM_INFO_MMAP |
 			   SNDRV_PCM_INFO_MMAP_VALID;
 
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		runtime->hw.formats = AMDTP_IN_PCM_FORMAT_BITS;
+		stream = &oxfw->tx_stream;
+		formations = oxfw->tx_stream_formations;
+	} else {
+		runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS;
+		stream = &oxfw->rx_stream;
+		formations = oxfw->rx_stream_formations;
+	}
+
 	limit_channels_and_rates(&runtime->hw, formations);
 	limit_period_and_buffer(&runtime->hw);
 
@@ -136,7 +145,18 @@ static int pcm_open(struct snd_pcm_substream *substream)
 	if (err < 0)
 		goto end;
 
-	err = amdtp_stream_add_pcm_hw_constraints(&oxfw->rx_stream, runtime);
+	err = amdtp_stream_add_pcm_hw_constraints(stream, runtime);
+end:
+	return err;
+}
+
+static int pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
+	unsigned int rate;
+	int err;
+
+	err = init_hw_params(oxfw, substream);
 	if (err < 0)
 		goto end;
 
@@ -144,16 +164,16 @@ static int pcm_open(struct snd_pcm_substream *substream)
 	 * When any PCM streams are already running, the available sampling
 	 * rate is limited at current value.
 	 */
-	if (amdtp_stream_pcm_running(&oxfw->rx_stream)) {
-		err = avc_general_get_sig_fmt(oxfw->unit, &rate,
-					      AVC_GENERAL_PLUG_DIR_IN, 0);
+	if (amdtp_stream_pcm_running(&oxfw->tx_stream) ||
+	    amdtp_stream_pcm_running(&oxfw->rx_stream)) {
+		err = snd_oxfw_stream_get_rate(oxfw, &rate);
 		if (err < 0) {
 			dev_err(&oxfw->unit->device,
 				"fail to get sampling rate: %d\n", err);
 			goto end;
 		}
-		runtime->hw.rate_min = rate;
-		runtime->hw.rate_max = rate;
+		substream->runtime->hw.rate_min = rate;
+		substream->runtime->hw.rate_max = rate;
 	}
 
 	snd_pcm_set_sync(substream);
@@ -166,33 +186,77 @@ static int pcm_close(struct snd_pcm_substream *substream)
 	return 0;
 }
 
-static int pcm_hw_params(struct snd_pcm_substream *substream,
-			 struct snd_pcm_hw_params *hw_params)
+static int pcm_capture_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
 {
 	struct snd_oxfw *oxfw = substream->private_data;
 
+	if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN)
+		atomic_inc(&oxfw->capture_substreams);
+	amdtp_stream_set_pcm_format(&oxfw->tx_stream, params_format(hw_params));
+
+	return snd_pcm_lib_alloc_vmalloc_buffer(substream,
+						params_buffer_bytes(hw_params));
+}
+static int pcm_playback_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
+
+	if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN)
+		atomic_inc(&oxfw->playback_substreams);
 	amdtp_stream_set_pcm_format(&oxfw->rx_stream, params_format(hw_params));
+
 	return snd_pcm_lib_alloc_vmalloc_buffer(substream,
 						params_buffer_bytes(hw_params));
 }
 
-static int pcm_hw_free(struct snd_pcm_substream *substream)
+static int pcm_capture_hw_free(struct snd_pcm_substream *substream)
 {
 	struct snd_oxfw *oxfw = substream->private_data;
 
-	snd_oxfw_stream_stop_simplex(oxfw);
+	if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
+		atomic_dec(&oxfw->capture_substreams);
+
+	snd_oxfw_stream_stop_simplex(oxfw, &oxfw->tx_stream);
 
 	return snd_pcm_lib_free_vmalloc_buffer(substream);
 }
+static int pcm_playback_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
 
-static int pcm_prepare(struct snd_pcm_substream *substream)
+	if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
+		atomic_dec(&oxfw->playback_substreams);
+
+	snd_oxfw_stream_stop_simplex(oxfw, &oxfw->rx_stream);
+
+	return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int pcm_capture_prepare(struct snd_pcm_substream *substream)
 {
 	struct snd_oxfw *oxfw = substream->private_data;
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	int err;
 
-	err = snd_oxfw_stream_start_simplex(oxfw, runtime->rate,
-					    runtime->channels);
+	err = snd_oxfw_stream_start_simplex(oxfw, &oxfw->tx_stream,
+					    runtime->rate, runtime->channels);
+	if (err < 0)
+		goto end;
+
+	amdtp_stream_pcm_prepare(&oxfw->tx_stream);
+end:
+	return err;
+}
+static int pcm_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	err = snd_oxfw_stream_start_simplex(oxfw, &oxfw->rx_stream,
+					    runtime->rate, runtime->channels);
 	if (err < 0)
 		goto end;
 
@@ -201,7 +265,23 @@ end:
 	return err;
 }
 
-static int pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		amdtp_stream_pcm_trigger(&oxfw->tx_stream, substream);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		amdtp_stream_pcm_trigger(&oxfw->tx_stream, NULL);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
 {
 	struct snd_oxfw *oxfw = substream->private_data;
 
@@ -218,35 +298,61 @@ static int pcm_trigger(struct snd_pcm_substream *substream, int cmd)
 	return 0;
 }
 
-static snd_pcm_uframes_t pcm_pointer(struct snd_pcm_substream *substream)
+static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstm)
 {
-	struct snd_oxfw *oxfw = substream->private_data;
+	struct snd_oxfw *oxfw = sbstm->private_data;
+
+	return amdtp_stream_pcm_pointer(&oxfw->tx_stream);
+}
+static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstm)
+{
+	struct snd_oxfw *oxfw = sbstm->private_data;
 
 	return amdtp_stream_pcm_pointer(&oxfw->rx_stream);
 }
 
 int snd_oxfw_create_pcm(struct snd_oxfw *oxfw)
 {
-	static struct snd_pcm_ops ops = {
+	static struct snd_pcm_ops capture_ops = {
 		.open      = pcm_open,
 		.close     = pcm_close,
 		.ioctl     = snd_pcm_lib_ioctl,
-		.hw_params = pcm_hw_params,
-		.hw_free   = pcm_hw_free,
-		.prepare   = pcm_prepare,
-		.trigger   = pcm_trigger,
-		.pointer   = pcm_pointer,
+		.hw_params = pcm_capture_hw_params,
+		.hw_free   = pcm_capture_hw_free,
+		.prepare   = pcm_capture_prepare,
+		.trigger   = pcm_capture_trigger,
+		.pointer   = pcm_capture_pointer,
+		.page      = snd_pcm_lib_get_vmalloc_page,
+		.mmap      = snd_pcm_lib_mmap_vmalloc,
+	};
+	static struct snd_pcm_ops playback_ops = {
+		.open      = pcm_open,
+		.close     = pcm_close,
+		.ioctl     = snd_pcm_lib_ioctl,
+		.hw_params = pcm_playback_hw_params,
+		.hw_free   = pcm_playback_hw_free,
+		.prepare   = pcm_playback_prepare,
+		.trigger   = pcm_playback_trigger,
+		.pointer   = pcm_playback_pointer,
 		.page      = snd_pcm_lib_get_vmalloc_page,
 		.mmap      = snd_pcm_lib_mmap_vmalloc,
 	};
 	struct snd_pcm *pcm;
+	unsigned int cap = 0;
 	int err;
 
-	err = snd_pcm_new(oxfw->card, oxfw->card->driver, 0, 1, 0, &pcm);
+	if (oxfw->has_output)
+		cap = 1;
+
+	err = snd_pcm_new(oxfw->card, oxfw->card->driver, 0, 1, cap, &pcm);
 	if (err < 0)
 		return err;
+
 	pcm->private_data = oxfw;
 	strcpy(pcm->name, oxfw->card->shortname);
-	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
+	if (cap > 0)
+		snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops);
+
 	return 0;
 }
diff --git a/sound/firewire/oxfw/oxfw_proc.c b/sound/firewire/oxfw/oxfw_proc.c
index dd287dc..7a69aac 100644
--- a/sound/firewire/oxfw/oxfw_proc.c
+++ b/sound/firewire/oxfw/oxfw_proc.c
@@ -15,6 +15,15 @@ static void proc_read_formation(struct snd_info_entry *entry,
 	struct snd_oxfw_stream_formation *formation;
 	unsigned int i;
 
+	snd_iprintf(buffer, "Output Stream from device:\n");
+	snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n");
+	formation = oxfw->tx_stream_formations;
+	for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
+		snd_iprintf(buffer,
+			"\t%d\t%d\t%d\n", formation[i].rate,
+			formation[i].pcm, formation[i].midi);
+	}
+
 	snd_iprintf(buffer, "Input Stream to device:\n");
 	snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n");
 	formation = oxfw->rx_stream_formations;
@@ -30,11 +39,8 @@ static void proc_read_clock(struct snd_info_entry *entry,
 {
 	struct snd_oxfw *oxfw = entry->private_data;
 	unsigned int rate;
-	int err;
 
-	err = avc_general_get_sig_fmt(oxfw->unit, &rate,
-				      AVC_GENERAL_PLUG_DIR_IN, 0);
-	if (err >= 0)
+	if (snd_oxfw_stream_get_rate(oxfw, &rate) >= 0)
 		snd_iprintf(buffer, "Sampling rate: %d\n", rate);
 }
 
diff --git a/sound/firewire/oxfw/oxfw_stream.c b/sound/firewire/oxfw/oxfw_stream.c
index 4d66648..6521899 100644
--- a/sound/firewire/oxfw/oxfw_stream.c
+++ b/sound/firewire/oxfw/oxfw_stream.c
@@ -38,44 +38,73 @@ static const unsigned int avc_stream_rate_table[] = {
 	[5] = 0x07,
 };
 
-int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw)
+int snd_oxfw_stream_get_rate(struct snd_oxfw *oxfw, unsigned int *rate)
 {
+	unsigned int tx_rate, rx_rate;
 	int err;
 
-	err = cmp_connection_init(&oxfw->in_conn, oxfw->unit,
-				  CMP_INPUT, 0);
+	err = avc_general_get_sig_fmt(oxfw->unit, &rx_rate,
+				      AVC_GENERAL_PLUG_DIR_IN, 0);
 	if (err < 0)
 		goto end;
+	*rate = rx_rate;
 
-	err = amdtp_stream_init(&oxfw->rx_stream, oxfw->unit,
-				AMDTP_OUT_STREAM, CIP_NONBLOCKING);
-	if (err < 0) {
-		amdtp_stream_destroy(&oxfw->rx_stream);
-		cmp_connection_destroy(&oxfw->in_conn);
+	if (oxfw->has_output) {
+		err = avc_general_get_sig_fmt(oxfw->unit, &tx_rate,
+						AVC_GENERAL_PLUG_DIR_OUT, 0);
+		if ((err < 0) || (rx_rate == tx_rate))
+			goto end;
+
+		/* synchronize transmit stream rate to receive stream rate */
+		err = avc_general_set_sig_fmt(oxfw->unit, rx_rate,
+					AVC_GENERAL_PLUG_DIR_OUT, 0);
 	}
 end:
 	return err;
 }
 
-static int stop_stream(struct snd_oxfw *oxfw)
+int snd_oxfw_stream_set_rate(struct snd_oxfw *oxfw, unsigned int rate)
 {
-	amdtp_stream_pcm_abort(&oxfw->rx_stream);
-	amdtp_stream_stop(&oxfw->rx_stream);
-	cmp_connection_break(&oxfw->in_conn);
+	int err;
+
+	err = avc_general_set_sig_fmt(oxfw->unit, rate,
+				      AVC_GENERAL_PLUG_DIR_IN, 0);
+	if (err < 0)
+		goto end;
+
+	if (oxfw->has_output)
+		err = avc_general_set_sig_fmt(oxfw->unit, rate,
+					      AVC_GENERAL_PLUG_DIR_OUT, 0);
+end:
+	return err;
 }
 
-static int start_stream(struct snd_oxfw *oxfw, unsigned int rate,
-			unsigned int pcm_channels)
+static void stop_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream)
+{
+	amdtp_stream_pcm_abort(stream);
+	amdtp_stream_stop(stream);
+
+	if (stream == &oxfw->tx_stream)
+		cmp_connection_break(&oxfw->out_conn);
+	else
+		cmp_connection_break(&oxfw->in_conn);
+}
+
+static int start_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream,
+			unsigned int rate, unsigned int pcm_channels)
 {
 	struct snd_oxfw_stream_formation *formations;
 	unsigned int i, midi_ports;
-	struct amdtp_stream *stream;
 	struct cmp_connection *conn;
 	int err;
 
-	stream = &oxfw->rx_stream;
-	formations = oxfw->rx_stream_formations;
-	conn = &oxfw->in_conn;
+	if (stream == &oxfw->rx_stream) {
+		formations = oxfw->rx_stream_formations;
+		conn = &oxfw->in_conn;
+	} else {
+		formations = oxfw->tx_stream_formations;
+		conn = &oxfw->out_conn;
+	}
 
 	/* Get stream formation */
 	for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) {
@@ -118,20 +147,22 @@ static int start_stream(struct snd_oxfw *oxfw, unsigned int rate,
 	/* Wait first callback */
 	err = amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT);
 	if (err < 0)
-		stop_stream(oxfw);
+		stop_stream(oxfw, stream);
 end:
 	return err;
 }
 
-static int check_connection_used_by_others(struct snd_oxfw *oxfw)
+static int check_connection_used_by_others(struct snd_oxfw *oxfw,
+					   struct amdtp_stream *stream)
 {
 	struct cmp_connection *conn;
-	struct amdtp_stream *stream;
 	bool used;
 	int err;
 
-	stream = &oxfw->rx_stream;
-	conn = &oxfw->in_conn;
+	if (stream == &oxfw->tx_stream)
+		conn = &oxfw->out_conn;
+	else
+		conn = &oxfw->in_conn;
 
 	err = cmp_connection_check_used(conn, &used);
 	if ((err >= 0) && used && !amdtp_stream_running(stream)) {
@@ -145,48 +176,123 @@ static int check_connection_used_by_others(struct snd_oxfw *oxfw)
 	return err;
 }
 
-int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw, unsigned int rate,
-				  unsigned int pcm_channels)
+int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw,
+				 struct amdtp_stream *stream)
+{
+	struct cmp_connection *conn;
+	enum cmp_direction c_dir;
+	enum amdtp_stream_direction s_dir;
+	int err;
+
+	if (stream == &oxfw->tx_stream) {
+		conn = &oxfw->out_conn;
+		c_dir = CMP_OUTPUT;
+		s_dir = AMDTP_IN_STREAM;
+	} else {
+		conn = &oxfw->in_conn;
+		c_dir = CMP_INPUT;
+		s_dir = AMDTP_OUT_STREAM;
+	}
+
+	mutex_lock(&oxfw->mutex);
+
+	err = cmp_connection_init(conn, oxfw->unit, c_dir, 0);
+	if (err < 0)
+		goto end;
+
+	err = amdtp_stream_init(stream, oxfw->unit, s_dir, CIP_NONBLOCKING);
+	if (err < 0) {
+		amdtp_stream_destroy(stream);
+		cmp_connection_destroy(conn);
+		goto end;
+	}
+
+	/* OXFW starts to transmit packets with non-zero dbc. */
+	if (stream == &oxfw->tx_stream)
+		oxfw->tx_stream.flags |= CIP_SKIP_INIT_DBC_CHECK;
+end:
+	mutex_unlock(&oxfw->mutex);
+	return err;
+}
+
+int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw,
+				  struct amdtp_stream *stream,
+				  unsigned int rate, unsigned int pcm_channels)
 {
+	struct amdtp_stream *opposite;
 	unsigned int curr_rate;
+	atomic_t *substreams, *opposite_substreams;
 	int err = 0;
 
+	if (stream == &oxfw->tx_stream) {
+		substreams = &oxfw->capture_substreams;
+		opposite = &oxfw->rx_stream;
+		opposite_substreams = &oxfw->playback_substreams;
+	} else {
+		substreams = &oxfw->playback_substreams;
+		opposite_substreams = &oxfw->capture_substreams;
+
+		if (oxfw->has_output)
+			opposite = &oxfw->rx_stream;
+		else
+			opposite = NULL;
+	}
+
+	if (atomic_read(substreams) == 0)
+		goto end;
+
 	mutex_lock(&oxfw->mutex);
 
 	/*
 	 * Considering JACK/FFADO streaming:
 	 * TODO: This can be removed hwdep functionality becomes popular.
 	 */
-	err = check_connection_used_by_others(oxfw);
+	err = check_connection_used_by_others(oxfw, stream);
 	if (err < 0)
 		goto end;
 
 	/* packet queueing error */
-	if (amdtp_streaming_error(&oxfw->rx_stream))
-		stop_stream(oxfw);
+	if (amdtp_streaming_error(stream))
+		stop_stream(oxfw, stream);
 
-	/* arrange sampling rate */
-	err = avc_general_get_sig_fmt(oxfw->unit, &curr_rate,
-				      AVC_GENERAL_PLUG_DIR_IN, 0);
+	/* stop streams if rate is different */
+	err = snd_oxfw_stream_get_rate(oxfw, &curr_rate);
 	if (err < 0) {
 		dev_err(&oxfw->unit->device,
 			"fail to get sampling rate: %d\n", err);
 		goto end;
 	}
 	if (curr_rate != rate) {
-		stop_stream(oxfw);
+		/* Stop streams safely. */
+		if (opposite != NULL) {
+			err = check_connection_used_by_others(oxfw, opposite);
+			if (err < 0)
+				goto end;
+			stop_stream(oxfw, opposite);
+		}
+		stop_stream(oxfw, stream);
 
-		err = avc_general_set_sig_fmt(oxfw->unit, rate,
-					      AVC_GENERAL_PLUG_DIR_IN, 0);
+		err = snd_oxfw_stream_set_rate(oxfw, rate);
 		if (err < 0) {
 			dev_err(&oxfw->unit->device,
 				"fail to set sampling rate: %d\n", err);
 			goto end;
 		}
+
+		/* Start opposite stream if needed. */
+		if (opposite && !amdtp_stream_running(opposite) &&
+		    (atomic_read(opposite_substreams) > 0)) {
+			err = start_stream(oxfw, opposite, rate, 0);
+			if (err < 0) {
+				dev_err(&oxfw->unit->device,
+					"fail to restart stream: %d\n", err);
+				goto end;
+			}
+		}
 	}
 
-	if (!amdtp_stream_running(&oxfw->rx_stream)) {
-		err = start_stream(oxfw, rate, pcm_channels);
+	if ((atomic_read(substreams) > 0) && !amdtp_stream_running(stream)) {
+		err = start_stream(oxfw, stream, rate, pcm_channels);
 		if (err < 0)
 			dev_err(&oxfw->unit->device,
 				"fail to start stream: %d\n", err);
@@ -196,33 +302,56 @@ end:
 	return err;
 }
 
-void snd_oxfw_stream_stop_simplex(struct snd_oxfw *oxfw)
+void snd_oxfw_stream_stop_simplex(struct snd_oxfw *oxfw,
+				  struct amdtp_stream *stream)
 {
+	if (((stream == &oxfw->tx_stream) &&
+	     (atomic_read(&oxfw->capture_substreams) > 0)) ||
+	    ((stream == &oxfw->rx_stream) &&
+	     (atomic_read(&oxfw->playback_substreams) > 0)))
+		return;
+
 	mutex_lock(&oxfw->mutex);
-	stop_stream(oxfw);
+	stop_stream(oxfw, stream);
 	mutex_unlock(&oxfw->mutex);
 }
 
-void snd_oxfw_stream_destroy_simplex(struct snd_oxfw *oxfw)
+void snd_oxfw_stream_destroy_simplex(struct snd_oxfw *oxfw,
+				     struct amdtp_stream *stream)
 {
+	struct cmp_connection *conn;
+
+	if (stream == &oxfw->tx_stream)
+		conn = &oxfw->out_conn;
+	else
+		conn = &oxfw->in_conn;
+
 	mutex_lock(&oxfw->mutex);
 
-	stop_stream(oxfw);
+	stop_stream(oxfw, stream);
 
-	amdtp_stream_destroy(&oxfw->rx_stream);
-	cmp_connection_destroy(&oxfw->in_conn);
+	amdtp_stream_destroy(stream);
+	cmp_connection_destroy(conn);
 
 	mutex_unlock(&oxfw->mutex);
 }
 
-void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw)
+void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw,
+				    struct amdtp_stream *stream)
 {
-	if (cmp_connection_update(&oxfw->in_conn) < 0) {
+	struct cmp_connection *conn;
+
+	if (stream == &oxfw->tx_stream)
+		conn = &oxfw->out_conn;
+	else
+		conn = &oxfw->in_conn;
+
+	if (cmp_connection_update(conn) < 0) {
 		mutex_lock(&oxfw->mutex);
-		stop_stream(oxfw);
+		stop_stream(oxfw, stream);
 		mutex_unlock(&oxfw->mutex);
 	} else {
-		amdtp_stream_update(&oxfw->rx_stream);
+		amdtp_stream_update(stream);
 	}
 }
 
@@ -373,7 +502,10 @@ static int fill_stream_formations(struct snd_oxfw *oxfw,
 	if (buf == NULL)
 		return -ENOMEM;
 
-	formations = oxfw->rx_stream_formations;
+	if (dir == AVC_GENERAL_PLUG_DIR_OUT)
+		formations = oxfw->tx_stream_formations;
+	else
+		formations = oxfw->rx_stream_formations;
 
 	/* get first entry */
 	len = AVC_GENERIC_FRAME_MAXIMUM_BYTES;
@@ -452,11 +584,19 @@ int snd_oxfw_stream_discover(struct snd_oxfw *oxfw)
 		"fail to get info for isoc/external in/out plugs: %d\n",
 			err);
 		goto end;
-	} else if (plugs[0] == 0) {
+	} else if ((plugs[0] == 0) && (plugs[1] == 0)) {
 		err = -ENOSYS;
 		goto end;
 	}
 
+	/* use oPCR[0] if exists */
+	if (plugs[1] > 0) {
+		err = fill_stream_formations(oxfw, AVC_GENERAL_PLUG_DIR_OUT, 0);
+		if (err < 0)
+			goto end;
+		oxfw->has_output = true;
+	}
+
 	/* use iPCR[0] if exists */
 	if (plugs[0] > 0)
 		err = fill_stream_formations(oxfw, AVC_GENERAL_PLUG_DIR_IN, 0);
-- 
1.8.3.2


------------------------------------------------------------------------------
"Accelerate Dev Cycles with Automated Cross-Browser Testing - For FREE
Instantly run your Selenium tests across 300+ browser/OS combos.
Get unparalleled scalability from the best Selenium testing platform available
Simple to use. Nothing to install. Get started now for free."
http://p.sf.net/sfu/SauceLabs

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 14/15] oxfw: Add support for capture/playback MIDI messages
  2014-05-13 14:27 [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Takashi Sakamoto
                   ` (12 preceding siblings ...)
  2014-05-13 14:27 ` [PATCH 13/15] oxfw: Add support AMDTP in-stream and PCM capture Takashi Sakamoto
@ 2014-05-13 14:27 ` Takashi Sakamoto
  2014-05-13 14:27 ` [PATCH 15/15] oxfw: Add hwdep interface Takashi Sakamoto
                   ` (3 subsequent siblings)
  17 siblings, 0 replies; 35+ messages in thread
From: Takashi Sakamoto @ 2014-05-13 14:27 UTC (permalink / raw)
  To: clemens; +Cc: alsa-devel, fenlason, ffado-devel, linux1394-devel

This commit adds MIDI functionality with an assumption of 'if the device
has MIDI comformant data channels in its stream formation, the device has
one MIDI port'.

When no streams have already started, MIDI functionality starts stream
with current sampling rate.

When MIDI functionality has already starts some streams and PCM
functionality is going to start streams at different sampling rate,
this driver stops streams once and changes sampling rate, then restarts
streams for both PCM/MIDI substreams.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/Kconfig            |   1 +
 sound/firewire/oxfw/Makefile      |   2 +-
 sound/firewire/oxfw/oxfw.c        |   7 ++
 sound/firewire/oxfw/oxfw.h        |   7 ++
 sound/firewire/oxfw/oxfw_midi.c   | 148 ++++++++++++++++++++++++++++++++++++++
 sound/firewire/oxfw/oxfw_stream.c |  16 ++++-
 6 files changed, 179 insertions(+), 2 deletions(-)
 create mode 100644 sound/firewire/oxfw/oxfw_midi.c

diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig
index 38027bd..b5035b0 100644
--- a/sound/firewire/Kconfig
+++ b/sound/firewire/Kconfig
@@ -30,6 +30,7 @@ config SND_OXFW
 	tristate "Oxford OXFW970/971 chipset support"
 	select SND_PCM
 	select SND_FIREWIRE_LIB
+	select SND_RAWMIDI
 	help
 	  Say Y here to include support for Firewire devices based on
 	  Oxford Semiconductor OXFW970/971 chipset.
diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile
index 4ee2c97..5f65270 100644
--- a/sound/firewire/oxfw/Makefile
+++ b/sound/firewire/oxfw/Makefile
@@ -1,3 +1,3 @@
 snd-oxfw-objs := oxfw_command.o oxfw_stream.o oxfw_control.o oxfw_proc.o \
-		 oxfw_pcm.o oxfw.o
+		 oxfw_midi.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 a9e21c0..5640547 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -119,6 +119,7 @@ static int oxfw_probe(struct fw_unit *unit,
 	mutex_init(&oxfw->mutex);
 	oxfw->unit = unit;
 	oxfw->device_info = (const struct device_info *)id->driver_data;
+	spin_lock_init(&oxfw->lock);
 
 	err = snd_oxfw_stream_discover(oxfw);
 	if (err < 0)
@@ -140,6 +141,12 @@ static int oxfw_probe(struct fw_unit *unit,
 
 	snd_oxfw_proc_init(oxfw);
 
+	if ((oxfw->midi_input_ports > 0) || (oxfw->midi_output_ports > 0)) {
+		err = snd_oxfw_create_midi(oxfw);
+		if (err < 0)
+			goto error;
+	}
+
 	err = snd_oxfw_stream_init_simplex(oxfw, &oxfw->rx_stream);
 	if (err < 0)
 		goto error;
diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h
index 81b6627..bcaab01 100644
--- a/sound/firewire/oxfw/oxfw.h
+++ b/sound/firewire/oxfw/oxfw.h
@@ -19,6 +19,7 @@
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
 #include <sound/info.h>
+#include <sound/rawmidi.h>
 
 #include "../lib.h"
 #include "../fcp.h"
@@ -47,6 +48,7 @@ struct snd_oxfw {
 	struct fw_unit *unit;
 	const struct device_info *device_info;
 	struct mutex mutex;
+	spinlock_t lock;
 
 	bool has_output;
 	struct snd_oxfw_stream_formation
@@ -60,6 +62,9 @@ struct snd_oxfw {
 	atomic_t capture_substreams;
 	atomic_t playback_substreams;
 
+	unsigned int midi_input_ports;
+	unsigned int midi_output_ports;
+
 	bool mute;
 	s16 volume[6];
 	s16 volume_min;
@@ -119,3 +124,5 @@ int snd_oxfw_create_pcm(struct snd_oxfw *oxfw);
 int snd_oxfw_create_mixer(struct snd_oxfw *oxfw);
 
 void snd_oxfw_proc_init(struct snd_oxfw *oxfw);
+
+int snd_oxfw_create_midi(struct snd_oxfw *oxfw);
diff --git a/sound/firewire/oxfw/oxfw_midi.c b/sound/firewire/oxfw/oxfw_midi.c
new file mode 100644
index 0000000..04be550
--- /dev/null
+++ b/sound/firewire/oxfw/oxfw_midi.c
@@ -0,0 +1,148 @@
+/*
+ * oxfw_midi.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"
+
+static int midi_capture_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_oxfw *oxfw = substream->rmidi->private_data;
+
+	atomic_inc(&oxfw->capture_substreams);
+	return snd_oxfw_stream_start_simplex(oxfw, &oxfw->tx_stream, 0, 0);
+}
+
+static int midi_playback_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_oxfw *oxfw = substream->rmidi->private_data;
+
+	atomic_inc(&oxfw->playback_substreams);
+	return snd_oxfw_stream_start_simplex(oxfw, &oxfw->rx_stream, 0, 0);
+}
+
+static int midi_capture_close(struct snd_rawmidi_substream *substream)
+{
+	struct snd_oxfw *oxfw = substream->rmidi->private_data;
+
+	atomic_dec(&oxfw->capture_substreams);
+	snd_oxfw_stream_stop_simplex(oxfw, &oxfw->tx_stream);
+
+	return 0;
+}
+
+static int midi_playback_close(struct snd_rawmidi_substream *substream)
+{
+	struct snd_oxfw *oxfw = substream->rmidi->private_data;
+
+	atomic_dec(&oxfw->playback_substreams);
+	snd_oxfw_stream_stop_simplex(oxfw, &oxfw->rx_stream);
+
+	return 0;
+}
+
+static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
+{
+	struct snd_oxfw *oxfw = substrm->rmidi->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&oxfw->lock, flags);
+
+	if (up)
+		amdtp_stream_midi_trigger(&oxfw->tx_stream,
+					  substrm->number, substrm);
+	else
+		amdtp_stream_midi_trigger(&oxfw->tx_stream,
+					  substrm->number, NULL);
+
+	spin_unlock_irqrestore(&oxfw->lock, flags);
+}
+
+static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up)
+{
+	struct snd_oxfw *oxfw = substrm->rmidi->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&oxfw->lock, flags);
+
+	if (up)
+		amdtp_stream_midi_trigger(&oxfw->rx_stream,
+					  substrm->number, substrm);
+	else
+		amdtp_stream_midi_trigger(&oxfw->rx_stream,
+					  substrm->number, NULL);
+
+	spin_unlock_irqrestore(&oxfw->lock, flags);
+}
+
+static struct snd_rawmidi_ops midi_capture_ops = {
+	.open		= midi_capture_open,
+	.close		= midi_capture_close,
+	.trigger	= midi_capture_trigger,
+};
+
+static struct snd_rawmidi_ops midi_playback_ops = {
+	.open		= midi_playback_open,
+	.close		= midi_playback_close,
+	.trigger	= midi_playback_trigger,
+};
+
+static void set_midi_substream_names(struct snd_oxfw *oxfw,
+				     struct snd_rawmidi_str *str)
+{
+	struct snd_rawmidi_substream *subs;
+
+	list_for_each_entry(subs, &str->substreams, list) {
+		snprintf(subs->name, sizeof(subs->name),
+			 "%s MIDI %d",
+			 oxfw->card->shortname, subs->number + 1);
+	}
+}
+
+int snd_oxfw_create_midi(struct snd_oxfw *oxfw)
+{
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_str *str;
+	int err;
+
+	/* create midi ports */
+	err = snd_rawmidi_new(oxfw->card, oxfw->card->driver, 0,
+			      oxfw->midi_output_ports, oxfw->midi_input_ports,
+			      &rmidi);
+	if (err < 0)
+		return err;
+
+	snprintf(rmidi->name, sizeof(rmidi->name),
+		 "%s MIDI", oxfw->card->shortname);
+	rmidi->private_data = oxfw;
+
+	if (oxfw->midi_input_ports > 0) {
+		rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+
+		snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+				    &midi_capture_ops);
+
+		str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT];
+
+		set_midi_substream_names(oxfw, str);
+	}
+
+	if (oxfw->midi_output_ports > 0) {
+		rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
+
+		snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+				    &midi_playback_ops);
+
+		str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
+
+		set_midi_substream_names(oxfw, str);
+	}
+
+	if ((oxfw->midi_output_ports > 0) && (oxfw->midi_input_ports > 0))
+		rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
+
+	return 0;
+}
diff --git a/sound/firewire/oxfw/oxfw_stream.c b/sound/firewire/oxfw/oxfw_stream.c
index 6521899..858e1e6 100644
--- a/sound/firewire/oxfw/oxfw_stream.c
+++ b/sound/firewire/oxfw/oxfw_stream.c
@@ -262,6 +262,8 @@ int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw,
 			"fail to get sampling rate: %d\n", err);
 		goto end;
 	}
+	if (rate == 0)
+		rate = curr_rate;
 	if (curr_rate != rate) {
 		/* Stop streams safely. */
 		if (opposite != NULL) {
@@ -575,6 +577,7 @@ end:
 int snd_oxfw_stream_discover(struct snd_oxfw *oxfw)
 {
 	u8 plugs[AVC_PLUG_INFO_BUF_BYTES];
+	unsigned int i;
 	int err;
 
 	/* the number of plugs for isoc in/out, ext in/out  */
@@ -598,8 +601,19 @@ int snd_oxfw_stream_discover(struct snd_oxfw *oxfw)
 	}
 
 	/* use iPCR[0] if exists */
-	if (plugs[0] > 0)
+	if (plugs[0] > 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_STREAM_FORMAT_ENTRIES; i++) {
+		if (oxfw->tx_stream_formations[i].midi > 0)
+			oxfw->midi_input_ports = 1;
+		if (oxfw->rx_stream_formations[i].midi > 0)
+			oxfw->midi_output_ports = 1;
+	}
 end:
 	return err;
 }
-- 
1.8.3.2


------------------------------------------------------------------------------
"Accelerate Dev Cycles with Automated Cross-Browser Testing - For FREE
Instantly run your Selenium tests across 300+ browser/OS combos.
Get unparalleled scalability from the best Selenium testing platform available
Simple to use. Nothing to install. Get started now for free."
http://p.sf.net/sfu/SauceLabs

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 15/15] oxfw: Add hwdep interface
  2014-05-13 14:27 [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Takashi Sakamoto
                   ` (13 preceding siblings ...)
  2014-05-13 14:27 ` [PATCH 14/15] oxfw: Add support for capture/playback MIDI messages Takashi Sakamoto
@ 2014-05-13 14:27 ` Takashi Sakamoto
  2014-05-13 17:43 ` [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Stefan Richter
                   ` (2 subsequent siblings)
  17 siblings, 0 replies; 35+ messages in thread
From: Takashi Sakamoto @ 2014-05-13 14:27 UTC (permalink / raw)
  To: clemens; +Cc: alsa-devel, fenlason, ffado-devel, linux1394-devel

This interface is designed for mixer/control application. By using this
interface, an application can get information about firewire node, can
lock/unlock kernel streaming and can get notification at starting/stopping
kernel streaming.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 include/uapi/sound/asound.h       |   3 +-
 include/uapi/sound/firewire.h     |   3 +-
 sound/firewire/Kconfig            |   1 +
 sound/firewire/oxfw/Makefile      |   2 +-
 sound/firewire/oxfw/oxfw.c        |   5 +
 sound/firewire/oxfw/oxfw.h        |  13 +++
 sound/firewire/oxfw/oxfw_hwdep.c  | 190 ++++++++++++++++++++++++++++++++++++++
 sound/firewire/oxfw/oxfw_midi.c   |  24 ++++-
 sound/firewire/oxfw/oxfw_pcm.c    |  13 ++-
 sound/firewire/oxfw/oxfw_stream.c |  39 ++++++++
 10 files changed, 286 insertions(+), 7 deletions(-)
 create mode 100644 sound/firewire/oxfw/oxfw_hwdep.c

diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h
index 2249483..0c1872a 100644
--- a/include/uapi/sound/asound.h
+++ b/include/uapi/sound/asound.h
@@ -96,9 +96,10 @@ enum {
 	SNDRV_HWDEP_IFACE_FW_DICE,	/* TC DICE FireWire device */
 	SNDRV_HWDEP_IFACE_FW_FIREWORKS,	/* Echo Audio Fireworks based device */
 	SNDRV_HWDEP_IFACE_FW_BEBOB,	/* BridgeCo BeBoB based device */
+	SNDRV_HWDEP_IFACE_FW_OXFW,	/* Oxford OXFW970/971 based device */
 
 	/* Don't forget to change the following: */
-	SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_BEBOB
+	SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_OXFW
 };
 
 struct snd_hwdep_info {
diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h
index af4bd13..49122df 100644
--- a/include/uapi/sound/firewire.h
+++ b/include/uapi/sound/firewire.h
@@ -55,7 +55,8 @@ union snd_firewire_event {
 #define SNDRV_FIREWIRE_TYPE_DICE	1
 #define SNDRV_FIREWIRE_TYPE_FIREWORKS	2
 #define SNDRV_FIREWIRE_TYPE_BEBOB	3
-/* AV/C, RME, MOTU, ... */
+#define SNDRV_FIREWIRE_TYPE_OXFW	4
+/* RME, MOTU, ... */
 
 struct snd_firewire_get_info {
 	unsigned int type; /* SNDRV_FIREWIRE_TYPE_xxx */
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig
index b5035b0..92c92c6 100644
--- a/sound/firewire/Kconfig
+++ b/sound/firewire/Kconfig
@@ -31,6 +31,7 @@ config SND_OXFW
 	select SND_PCM
 	select SND_FIREWIRE_LIB
 	select SND_RAWMIDI
+	select SND_HWDEP
 	help
 	  Say Y here to include support for Firewire devices based on
 	  Oxford Semiconductor OXFW970/971 chipset.
diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile
index 5f65270..310b62d 100644
--- a/sound/firewire/oxfw/Makefile
+++ b/sound/firewire/oxfw/Makefile
@@ -1,3 +1,3 @@
 snd-oxfw-objs := oxfw_command.o oxfw_stream.o oxfw_control.o oxfw_proc.o \
-		 oxfw_midi.o oxfw_pcm.o oxfw.o
+		 oxfw_midi.o oxfw_pcm.o oxfw_hwdep.o oxfw.o
 obj-m += snd-oxfw.o
diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c
index 5640547..d830d0b 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -120,6 +120,7 @@ static int oxfw_probe(struct fw_unit *unit,
 	oxfw->unit = unit;
 	oxfw->device_info = (const struct device_info *)id->driver_data;
 	spin_lock_init(&oxfw->lock);
+	init_waitqueue_head(&oxfw->hwdep_wait);
 
 	err = snd_oxfw_stream_discover(oxfw);
 	if (err < 0)
@@ -147,6 +148,10 @@ static int oxfw_probe(struct fw_unit *unit,
 			goto error;
 	}
 
+	err = snd_oxfw_create_hwdep(oxfw);
+	if (err < 0)
+		goto error;
+
 	err = snd_oxfw_stream_init_simplex(oxfw, &oxfw->rx_stream);
 	if (err < 0)
 		goto error;
diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h
index bcaab01..8f274b4 100644
--- a/sound/firewire/oxfw/oxfw.h
+++ b/sound/firewire/oxfw/oxfw.h
@@ -12,6 +12,7 @@
 #include <linux/mod_devicetable.h>
 #include <linux/mutex.h>
 #include <linux/slab.h>
+#include <linux/compat.h>
 
 #include <sound/control.h>
 #include <sound/core.h>
@@ -20,6 +21,8 @@
 #include <sound/pcm_params.h>
 #include <sound/info.h>
 #include <sound/rawmidi.h>
+#include <sound/firewire.h>
+#include <sound/hwdep.h>
 
 #include "../lib.h"
 #include "../fcp.h"
@@ -69,6 +72,10 @@ struct snd_oxfw {
 	s16 volume[6];
 	s16 volume_min;
 	s16 volume_max;
+
+	int dev_lock_count;
+	bool dev_lock_changed;
+	wait_queue_head_t hwdep_wait;
 };
 
 /*
@@ -119,6 +126,10 @@ void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw,
 
 int snd_oxfw_stream_discover(struct snd_oxfw *oxfw);
 
+void snd_oxfw_stream_lock_changed(struct snd_oxfw *oxfw);
+int snd_oxfw_stream_lock_try(struct snd_oxfw *oxfw);
+void snd_oxfw_stream_lock_release(struct snd_oxfw *oxfw);
+
 int snd_oxfw_create_pcm(struct snd_oxfw *oxfw);
 
 int snd_oxfw_create_mixer(struct snd_oxfw *oxfw);
@@ -126,3 +137,5 @@ int snd_oxfw_create_mixer(struct snd_oxfw *oxfw);
 void snd_oxfw_proc_init(struct snd_oxfw *oxfw);
 
 int snd_oxfw_create_midi(struct snd_oxfw *oxfw);
+
+int snd_oxfw_create_hwdep(struct snd_oxfw *oxfw);
diff --git a/sound/firewire/oxfw/oxfw_hwdep.c b/sound/firewire/oxfw/oxfw_hwdep.c
new file mode 100644
index 0000000..bf79118
--- /dev/null
+++ b/sound/firewire/oxfw/oxfw_hwdep.c
@@ -0,0 +1,190 @@
+/*
+ * oxfw_hwdep.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.
+ */
+
+/*
+ * This codes give three functionality.
+ *
+ * 1.get firewire node infomation
+ * 2.get notification about starting/stopping stream
+ * 3.lock/unlock stream
+ */
+
+#include "oxfw.h"
+
+static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf,  long count,
+		       loff_t *offset)
+{
+	struct snd_oxfw *oxfw = hwdep->private_data;
+	DEFINE_WAIT(wait);
+	union snd_firewire_event event;
+
+	spin_lock_irq(&oxfw->lock);
+
+	while (!oxfw->dev_lock_changed) {
+		prepare_to_wait(&oxfw->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
+		spin_unlock_irq(&oxfw->lock);
+		schedule();
+		finish_wait(&oxfw->hwdep_wait, &wait);
+		if (signal_pending(current))
+			return -ERESTARTSYS;
+		spin_lock_irq(&oxfw->lock);
+	}
+
+	memset(&event, 0, sizeof(event));
+	if (oxfw->dev_lock_changed) {
+		event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
+		event.lock_status.status = (oxfw->dev_lock_count > 0);
+		oxfw->dev_lock_changed = false;
+
+		count = min_t(long, count, sizeof(event.lock_status));
+	}
+
+	spin_unlock_irq(&oxfw->lock);
+
+	if (copy_to_user(buf, &event, count))
+		return -EFAULT;
+
+	return count;
+}
+
+static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
+			       poll_table *wait)
+{
+	struct snd_oxfw *oxfw = hwdep->private_data;
+	unsigned int events;
+
+	poll_wait(file, &oxfw->hwdep_wait, wait);
+
+	spin_lock_irq(&oxfw->lock);
+	if (oxfw->dev_lock_changed)
+		events = POLLIN | POLLRDNORM;
+	else
+		events = 0;
+	spin_unlock_irq(&oxfw->lock);
+
+	return events;
+}
+
+static int hwdep_get_info(struct snd_oxfw *oxfw, void __user *arg)
+{
+	struct fw_device *dev = fw_parent_device(oxfw->unit);
+	struct snd_firewire_get_info info;
+
+	memset(&info, 0, sizeof(info));
+	info.type = SNDRV_FIREWIRE_TYPE_OXFW;
+	info.card = dev->card->index;
+	*(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);
+	*(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);
+	strlcpy(info.device_name, dev_name(&dev->device),
+		sizeof(info.device_name));
+
+	if (copy_to_user(arg, &info, sizeof(info)))
+		return -EFAULT;
+
+	return 0;
+}
+
+static int hwdep_lock(struct snd_oxfw *oxfw)
+{
+	int err;
+
+	spin_lock_irq(&oxfw->lock);
+
+	if (oxfw->dev_lock_count == 0) {
+		oxfw->dev_lock_count = -1;
+		err = 0;
+	} else {
+		err = -EBUSY;
+	}
+
+	spin_unlock_irq(&oxfw->lock);
+
+	return err;
+}
+
+static int hwdep_unlock(struct snd_oxfw *oxfw)
+{
+	int err;
+
+	spin_lock_irq(&oxfw->lock);
+
+	if (oxfw->dev_lock_count == -1) {
+		oxfw->dev_lock_count = 0;
+		err = 0;
+	} else {
+		err = -EBADFD;
+	}
+
+	spin_unlock_irq(&oxfw->lock);
+
+	return err;
+}
+
+static int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
+{
+	struct snd_oxfw *oxfw = hwdep->private_data;
+
+	spin_lock_irq(&oxfw->lock);
+	if (oxfw->dev_lock_count == -1)
+		oxfw->dev_lock_count = 0;
+	spin_unlock_irq(&oxfw->lock);
+
+	return 0;
+}
+
+static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
+		       unsigned int cmd, unsigned long arg)
+{
+	struct snd_oxfw *oxfw = hwdep->private_data;
+
+	switch (cmd) {
+	case SNDRV_FIREWIRE_IOCTL_GET_INFO:
+		return hwdep_get_info(oxfw, (void __user *)arg);
+	case SNDRV_FIREWIRE_IOCTL_LOCK:
+		return hwdep_lock(oxfw);
+	case SNDRV_FIREWIRE_IOCTL_UNLOCK:
+		return hwdep_unlock(oxfw);
+	default:
+		return -ENOIOCTLCMD;
+	}
+}
+
+#ifdef CONFIG_COMPAT
+static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
+			      unsigned int cmd, unsigned long arg)
+{
+	return hwdep_ioctl(hwdep, file, cmd,
+			   (unsigned long)compat_ptr(arg));
+}
+#else
+#define hwdep_compat_ioctl NULL
+#endif
+
+int snd_oxfw_create_hwdep(struct snd_oxfw *oxfw)
+{
+	static const struct snd_hwdep_ops hwdep_ops = {
+		.read		= hwdep_read,
+		.release	= hwdep_release,
+		.poll		= hwdep_poll,
+		.ioctl		= hwdep_ioctl,
+		.ioctl_compat	= hwdep_compat_ioctl,
+	};
+	struct snd_hwdep *hwdep;
+	int err;
+
+	err = snd_hwdep_new(oxfw->card, oxfw->card->driver, 0, &hwdep);
+	if (err < 0)
+		goto end;
+	strcpy(hwdep->name, oxfw->card->driver);
+	hwdep->iface = SNDRV_HWDEP_IFACE_FW_OXFW;
+	hwdep->ops = hwdep_ops;
+	hwdep->private_data = oxfw;
+	hwdep->exclusive = true;
+end:
+	return err;
+}
diff --git a/sound/firewire/oxfw/oxfw_midi.c b/sound/firewire/oxfw/oxfw_midi.c
index 04be550..f0f045d 100644
--- a/sound/firewire/oxfw/oxfw_midi.c
+++ b/sound/firewire/oxfw/oxfw_midi.c
@@ -11,17 +11,35 @@
 static int midi_capture_open(struct snd_rawmidi_substream *substream)
 {
 	struct snd_oxfw *oxfw = substream->rmidi->private_data;
+	int err;
+
+	err = snd_oxfw_stream_lock_try(oxfw);
+	if (err < 0)
+		goto end;
 
 	atomic_inc(&oxfw->capture_substreams);
-	return snd_oxfw_stream_start_simplex(oxfw, &oxfw->tx_stream, 0, 0);
+	err = snd_oxfw_stream_start_simplex(oxfw, &oxfw->tx_stream, 0, 0);
+	if (err < 0)
+		snd_oxfw_stream_lock_release(oxfw);
+end:
+	return err;
 }
 
 static int midi_playback_open(struct snd_rawmidi_substream *substream)
 {
 	struct snd_oxfw *oxfw = substream->rmidi->private_data;
+	int err;
+
+	err = snd_oxfw_stream_lock_try(oxfw);
+	if (err < 0)
+		goto end;
 
 	atomic_inc(&oxfw->playback_substreams);
-	return snd_oxfw_stream_start_simplex(oxfw, &oxfw->rx_stream, 0, 0);
+	err = snd_oxfw_stream_start_simplex(oxfw, &oxfw->rx_stream, 0, 0);
+	if (err < 0)
+		snd_oxfw_stream_lock_release(oxfw);
+end:
+	return err;
 }
 
 static int midi_capture_close(struct snd_rawmidi_substream *substream)
@@ -31,6 +49,7 @@ static int midi_capture_close(struct snd_rawmidi_substream *substream)
 	atomic_dec(&oxfw->capture_substreams);
 	snd_oxfw_stream_stop_simplex(oxfw, &oxfw->tx_stream);
 
+	snd_oxfw_stream_lock_release(oxfw);
 	return 0;
 }
 
@@ -41,6 +60,7 @@ static int midi_playback_close(struct snd_rawmidi_substream *substream)
 	atomic_dec(&oxfw->playback_substreams);
 	snd_oxfw_stream_stop_simplex(oxfw, &oxfw->rx_stream);
 
+	snd_oxfw_stream_lock_release(oxfw);
 	return 0;
 }
 
diff --git a/sound/firewire/oxfw/oxfw_pcm.c b/sound/firewire/oxfw/oxfw_pcm.c
index e763eef..7fee841 100644
--- a/sound/firewire/oxfw/oxfw_pcm.c
+++ b/sound/firewire/oxfw/oxfw_pcm.c
@@ -156,10 +156,14 @@ static int pcm_open(struct snd_pcm_substream *substream)
 	unsigned int rate;
 	int err;
 
-	err = init_hw_params(oxfw, substream);
+	err = snd_oxfw_stream_lock_try(oxfw);
 	if (err < 0)
 		goto end;
 
+	err = init_hw_params(oxfw, substream);
+	if (err < 0)
+		goto err_locked;
+
 	/*
 	 * When any PCM streams are already running, the available sampling
 	 * rate is limited at current value.
@@ -170,7 +174,7 @@ static int pcm_open(struct snd_pcm_substream *substream)
 		if (err < 0) {
 			dev_err(&oxfw->unit->device,
 				"fail to get sampling rate: %d\n", err);
-			goto end;
+			goto err_locked;
 		}
 		substream->runtime->hw.rate_min = rate;
 		substream->runtime->hw.rate_max = rate;
@@ -179,10 +183,15 @@ static int pcm_open(struct snd_pcm_substream *substream)
 	snd_pcm_set_sync(substream);
 end:
 	return err;
+err_locked:
+	snd_oxfw_stream_lock_release(oxfw);
+	return err;
 }
 
 static int pcm_close(struct snd_pcm_substream *substream)
 {
+	struct snd_oxfw *oxfw = substream->private_data;
+	snd_oxfw_stream_lock_release(oxfw);
 	return 0;
 }
 
diff --git a/sound/firewire/oxfw/oxfw_stream.c b/sound/firewire/oxfw/oxfw_stream.c
index 858e1e6..c9aab3f 100644
--- a/sound/firewire/oxfw/oxfw_stream.c
+++ b/sound/firewire/oxfw/oxfw_stream.c
@@ -617,3 +617,42 @@ int snd_oxfw_stream_discover(struct snd_oxfw *oxfw)
 end:
 	return err;
 }
+
+void snd_oxfw_stream_lock_changed(struct snd_oxfw *oxfw)
+{
+	oxfw->dev_lock_changed = true;
+	wake_up(&oxfw->hwdep_wait);
+}
+
+int snd_oxfw_stream_lock_try(struct snd_oxfw *oxfw)
+{
+	int err;
+
+	spin_lock_irq(&oxfw->lock);
+
+	/* user land lock this */
+	if (oxfw->dev_lock_count < 0) {
+		err = -EBUSY;
+		goto end;
+	}
+
+	/* this is the first time */
+	if (oxfw->dev_lock_count++ == 0)
+		snd_oxfw_stream_lock_changed(oxfw);
+	err = 0;
+end:
+	spin_unlock_irq(&oxfw->lock);
+	return err;
+}
+
+void snd_oxfw_stream_lock_release(struct snd_oxfw *oxfw)
+{
+	spin_lock_irq(&oxfw->lock);
+
+	if (WARN_ON(oxfw->dev_lock_count <= 0))
+		goto end;
+	if (--oxfw->dev_lock_count == 0)
+		snd_oxfw_stream_lock_changed(oxfw);
+end:
+	spin_unlock_irq(&oxfw->lock);
+}
-- 
1.8.3.2


------------------------------------------------------------------------------
"Accelerate Dev Cycles with Automated Cross-Browser Testing - For FREE
Instantly run your Selenium tests across 300+ browser/OS combos.
Get unparalleled scalability from the best Selenium testing platform available
Simple to use. Nothing to install. Get started now for free."
http://p.sf.net/sfu/SauceLabs

^ permalink raw reply related	[flat|nested] 35+ messages in thread

* Re: [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers
  2014-05-13 14:27 [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Takashi Sakamoto
                   ` (14 preceding siblings ...)
  2014-05-13 14:27 ` [PATCH 15/15] oxfw: Add hwdep interface Takashi Sakamoto
@ 2014-05-13 17:43 ` Stefan Richter
  2014-05-14 14:15   ` Takashi Sakamoto
  2014-08-10 15:54 ` Clemens Ladisch
  2014-10-26 12:00 ` Stefan Richter
  17 siblings, 1 reply; 35+ messages in thread
From: Stefan Richter @ 2014-05-13 17:43 UTC (permalink / raw)
  To: Takashi Sakamoto
  Cc: fenlason, alsa-devel, linux1394-devel, clemens, ffado-devel

On May 13 Takashi Sakamoto wrote:
> If you have Griffin FireWave, I would strongly request to test.
> Please see 'An issue for FireWave' in this message.
[...]

I will try to test your patches with my FireWave sometime soon, but I will
be traveling or at work for the entire rest of this week.

Note, current mainline snd-firewire-speakers does _not_ support FireWave
properly.  On one of the stereo channels, snd-firewire-speakers generates
a high background hiss.  Could be that Jay Fenlason had a solution for
this which hadn't made it into the mainline still.  Was it something
about signedness of the PCM data? 
-- 
Stefan Richter
-=====-====- -=-= -==-=
http://arcgraph.de/sr/

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers
  2014-05-13 17:43 ` [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Stefan Richter
@ 2014-05-14 14:15   ` Takashi Sakamoto
  0 siblings, 0 replies; 35+ messages in thread
From: Takashi Sakamoto @ 2014-05-14 14:15 UTC (permalink / raw)
  To: Stefan Richter
  Cc: clemens, alsa-devel, linux1394-devel, fenlason, ffado-devel

Hi Stefan,

(May 14 2014 02:43), Stefan Richter wrote:
> Note, current mainline snd-firewire-speakers does _not_ support FireWave
> properly.

Oh...

> On one of the stereo channels, snd-firewire-speakers generates
> a high background hiss.

Do you know which combinations of channels/rate?

> Could be that Jay Fenlason had a solution for this which hadn't made it into the mainline still. Was it something
> about signedness of the PCM data?

I don't know exactly. So here I describe my guess.

As long as seeing responses from FireWave which you and Jay sent to me, 
FireWave has two stream format for the same sampling rate. They're:
  - 6 channels for Multi Bit Linear Audio
  - 2 channels for IEC 60958-3 Conformant data

[alsa-devel] Griffin FireWave stream format information
http://mailman.alsa-project.org/pipermail/alsa-devel/2014-February/073561.html


In detail,
 > - 6ch PCM at 32.0/44.1/48.0/96.0kHz
 > - 2ch IEC 60958-3 at 32.0/44.1/48.0kHz

[alsa-devel] PCM rules for FireWave in speaker.c
http://mailman.alsa-project.org/pipermail/alsa-devel/2014-February/073562.html


I guess that 6ch PCM mode is primary and 2ch IEC 60958-3 mode is 
secondary. So I think that 'firewire-speakers' driver was merged with 
wrong idea that FireWave is 'two playback-only' FireWire device.

ALSA: add LaCie FireWire Speakers/Griffin FireWave Surround driver
http://git.kernel.org/cgit/linux/kernel/git/tiwai/sound.git/commit/sound/firewire/speakers.c?id=31ef9134eb52636d383a7d0626cbbd345cb94f2f


For the way to switch these two modes, we've already revealed to use 
'CONTROL' ctype of EXTENDED STREAM FORMAT INFORMATION' command with 
'SINGLE' subfunction in 'AV/C Stream Format Infomation Specification'.

But in this RFC, I don't implement this. As long as 'firewire-speakers' 
works fine, FireWave can detect mode automatically, typically by reading 
data block size in CIP headers.

...But if all of 2ch playbacks are failed, I should change codes to use 
this command...

...And current firewire-lib is not conformant to IEC 61883-6 data 
channel because firewire-lib transfers data with wrong AM824 label to 
this channel. I have two reasons for this implementation: one is 
Fireworks/BeBoB seems to ignore labels and eventually can handle data 
correctly, another is to keep the codes simply. If this issue is due to 
this implementation, I should do further work for firewire-lib...

Anyway, I wait for testers' report.


Regards

Takashi Sakamoto
o-takashi@sakamocchi.jp

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers
  2014-05-13 14:27 [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Takashi Sakamoto
                   ` (15 preceding siblings ...)
  2014-05-13 17:43 ` [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Stefan Richter
@ 2014-08-10 15:54 ` Clemens Ladisch
  2014-08-11 23:55   ` Takashi Sakamoto
  2014-10-26 12:00 ` Stefan Richter
  17 siblings, 1 reply; 35+ messages in thread
From: Clemens Ladisch @ 2014-08-10 15:54 UTC (permalink / raw)
  To: Takashi Sakamoto
  Cc: alsa-devel, stefanr, fenlason, ffado-devel, linux1394-devel

Takashi Sakamoto wrote:
> I have an issue for Griffin Firewave.
>
> This device can handle AV/C Stream Format Information command and
> driver can get to know stream formations via the command.
>
> According to these responses, supported formations are:
> Input Stream to device:
> 	Rate	PCM	MIDI
> 	32000	6	0
> 	44100	6	0
> 	48000	6	0
> 	96000	6	0
> 	32000	2	0
> 	44100	2	0
> 	48000	2	0
>
> But current 'firewire-speakers' has PCM constraints below:
>  - 6ch at 32/44.1/48/96 kHz
>  - 2ch at 48/96 kHz
> http://mailman.alsa-project.org/pipermail/alsa-devel/2014-February/073562.html
>
> So this OXFW driver may lost a support for 2ch/96.0kHz for FireWave
> and gain 2ch/32.0kHz and 2ch/44.1kHz.
>
> I don't know exactly the reason of these gaps.

The old driver configured nothing but the rate.  Is there any mechanism
to set the stream format?


Regards,
Clemens

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [alsa-devel] [PATCH 12/15] oxfw: Add support for Behringer/Mackie devices
  2014-05-13 14:27 ` [PATCH 12/15] oxfw: Add support for Behringer/Mackie devices Takashi Sakamoto
@ 2014-08-10 15:54   ` Clemens Ladisch
  2014-08-11 23:48     ` Takashi Sakamoto
  0 siblings, 1 reply; 35+ messages in thread
From: Clemens Ladisch @ 2014-08-10 15:54 UTC (permalink / raw)
  To: Takashi Sakamoto; +Cc: alsa-devel, fenlason, ffado-devel, linux1394-devel

Takashi Sakamoto wrote:
> Some devices produced by Behringer/Mackie are based on OXFW970/971. This
> commit adds support for them.
>
> +++ b/sound/firewire/oxfw/oxfw.c
> @@ -16,6 +16,8 @@
>
>  #define VENDOR_GRIFFIN		0x001292
>  #define VENDOR_LACIE		0x00d04b
> +#define VEN_BEHRINGER		0x001564
> +#define VEN_LOUD		0x000ff2

These IDs were supposed to be ordered. :)
And why "VEN_" instead of "VENDOR_"?

> +	...
> +	{
> +		.match_flags	= IEEE1394_MATCH_VENDOR_ID |
> +				  IEEE1394_MATCH_MODEL_ID,
> +		.vendor_id	= VEN_LOUD,
> +		.model_id	= 0x000460,
> +	},
> +	/* IDs are unknown but able to be supported */
> +	/*  Mackie(Loud), d.2 pro */
> +	/*  Mackie(Loud), d.4 pro */
> +	/*  Mackie(Loud), U.420 */
> +	/*  Mackie(Loud), U.420d */

To support all Mackie devices, use one ID entry with only the vendor ID,
and abort with -ENODEV from the _probe function if the hardware is not
actually an OXFW97x device (try to read the HARDWARE_ID and/or
FIRMWARE_ID registers).


Regards,
Clemens

------------------------------------------------------------------------------

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 12/15] oxfw: Add support for Behringer/Mackie devices
  2014-08-10 15:54   ` [alsa-devel] " Clemens Ladisch
@ 2014-08-11 23:48     ` Takashi Sakamoto
  2014-08-12  7:33       ` [alsa-devel] " Clemens Ladisch
  0 siblings, 1 reply; 35+ messages in thread
From: Takashi Sakamoto @ 2014-08-11 23:48 UTC (permalink / raw)
  To: Clemens Ladisch
  Cc: alsa-devel, stefanr, fenlason, ffado-devel, linux1394-devel

On Aug 11 2014 00:54, Clemens Ladisch wrote:
> Takashi Sakamoto wrote:
>> Some devices produced by Behringer/Mackie are based on OXFW970/971. This
>> commit adds support for them.
>>
>> +++ b/sound/firewire/oxfw/oxfw.c
>> @@ -16,6 +16,8 @@
>>
>>  #define VENDOR_GRIFFIN		0x001292
>>  #define VENDOR_LACIE		0x00d04b
>> +#define VEN_BEHRINGER		0x001564
>> +#define VEN_LOUD		0x000ff2
> 
> These IDs were supposed to be ordered. :)

OK, indeed.

> And why "VEN_" instead of "VENDOR_"?

I avoided to type three letters "DOR" and use the same way as BeBoB
driver. But I don't mind to use "VENDOR_" instead.

>> +	...
>> +	{
>> +		.match_flags	= IEEE1394_MATCH_VENDOR_ID |
>> +				  IEEE1394_MATCH_MODEL_ID,
>> +		.vendor_id	= VEN_LOUD,
>> +		.model_id	= 0x000460,
>> +	},
>> +	/* IDs are unknown but able to be supported */
>> +	/*  Mackie(Loud), d.2 pro */
>> +	/*  Mackie(Loud), d.4 pro */
>> +	/*  Mackie(Loud), U.420 */
>> +	/*  Mackie(Loud), U.420d */
> 
> To support all Mackie devices, use one ID entry with only the vendor ID,
> and abort with -ENODEV from the _probe function if the hardware is not
> actually an OXFW97x device (try to read the HARDWARE_ID and/or
> FIRMWARE_ID registers).

In this idea, OXFW driver transfers the transaction(s) for all of
Mackie(Loud) devices on IEEE 1394 bus when they're probed. I want to
avoid such changes possible to affects the other models on the same bus
because I'm afraid of side effects.


Thanks

Takashi Sakamoto
o-takashi@sakamocchi.jp

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers
  2014-08-10 15:54 ` Clemens Ladisch
@ 2014-08-11 23:55   ` Takashi Sakamoto
  2014-08-12  7:09     ` Clemens Ladisch
  0 siblings, 1 reply; 35+ messages in thread
From: Takashi Sakamoto @ 2014-08-11 23:55 UTC (permalink / raw)
  To: Clemens Ladisch; +Cc: alsa-devel, fenlason, ffado-devel, linux1394-devel

On Aug 11 2014 00:54, Clemens Ladisch wrote:
> Takashi Sakamoto wrote:
>> I have an issue for Griffin Firewave.
>>
>> This device can handle AV/C Stream Format Information command and
>> driver can get to know stream formations via the command.
>>
>> According to these responses, supported formations are:
>> Input Stream to device:
>> 	Rate	PCM	MIDI
>> 	32000	6	0
>> 	44100	6	0
>> 	48000	6	0
>> 	96000	6	0
>> 	32000	2	0
>> 	44100	2	0
>> 	48000	2	0
>>
>> But current 'firewire-speakers' has PCM constraints below:
>>  - 6ch at 32/44.1/48/96 kHz
>>  - 2ch at 48/96 kHz
>> http://mailman.alsa-project.org/pipermail/alsa-devel/2014-February/073562.html
>>
>> So this OXFW driver may lost a support for 2ch/96.0kHz for FireWave
>> and gain 2ch/32.0kHz and 2ch/44.1kHz.
>>
>> I don't know exactly the reason of these gaps.
> 
> The old driver configured nothing but the rate.  Is there any mechanism
> to set the stream format?

Yes. It's AV/C stream format information command.

The old driver uses AV/C plug signal format command. This command
basically get/set sampling rate.

But the stream format information command can be available to get/set
stream format and some models with OXFW, especially FireWave, uses this
command.

But in this series of patches, I don't use this command. I expected the
models to change stream format according to parameters in AMDTP packets
such like the value of dbs field.


Regards

Takashi Sakamoto
(In summer vacation)
o-takashi@sakamocchi.jp


------------------------------------------------------------------------------

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers
  2014-08-11 23:55   ` Takashi Sakamoto
@ 2014-08-12  7:09     ` Clemens Ladisch
  2014-08-12 22:45       ` Takashi Sakamoto
  2014-10-16 14:31       ` Takashi Sakamoto
  0 siblings, 2 replies; 35+ messages in thread
From: Clemens Ladisch @ 2014-08-12  7:09 UTC (permalink / raw)
  To: Takashi Sakamoto
  Cc: alsa-devel, stefanr, fenlason, ffado-devel, linux1394-devel

Takashi Sakamoto wrote:
> On Aug 11 2014 00:54, Clemens Ladisch wrote:
>> The old driver configured nothing but the rate.  Is there any mechanism
>> to set the stream format?
>
> Yes. It's AV/C stream format information command.
> [...]
> But in this series of patches, I don't use this command. I expected the
> models to change stream format according to parameters in AMDTP packets
> such like the value of dbs field.

Apparently, the firmware doesn't support automatic format detection
completely, and requires this command for the 2ch 32/44.1 kHz modes.

(That 2ch at 96 kHz actually works is even more curious.  There already
is that bug regarding listing the seventh format; it's possible that
they completely forgot to report an eighth format.)


Regards,
Clemens

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [alsa-devel] [PATCH 12/15] oxfw: Add support for Behringer/Mackie devices
  2014-08-11 23:48     ` Takashi Sakamoto
@ 2014-08-12  7:33       ` Clemens Ladisch
  2014-08-12 23:21         ` Takashi Sakamoto
  0 siblings, 1 reply; 35+ messages in thread
From: Clemens Ladisch @ 2014-08-12  7:33 UTC (permalink / raw)
  To: Takashi Sakamoto; +Cc: alsa-devel, fenlason, ffado-devel, linux1394-devel

Takashi Sakamoto wrote:
> On Aug 11 2014 00:54, Clemens Ladisch wrote:
>> Takashi Sakamoto wrote:
>>> +	/* IDs are unknown but able to be supported */
>>> +	/*  Mackie(Loud), d.2 pro */
>>> +	/*  Mackie(Loud), d.4 pro */
>>> +	/*  Mackie(Loud), U.420 */
>>> +	/*  Mackie(Loud), U.420d */
>>
>> To support all Mackie devices, use one ID entry with only the vendor ID,
>> and abort with -ENODEV from the _probe function if the hardware is not
>> actually an OXFW97x device (try to read the HARDWARE_ID and/or
>> FIRMWARE_ID registers).
>
> In this idea, OXFW driver transfers the transaction(s) for all of
> Mackie(Loud) devices on IEEE 1394 bus when they're probed. I want to
> avoid such changes possible to affects the other models on the same bus
> because I'm afraid of side effects.

The only other known Mackie devices are the 400F/1200F.  The Fireworks
firmware doesn't blow up if you try to read unsupported registers, but
if you want to be sure, you can blacklist these two IDs.

Or just do the detection like the Windows driver, through the vendor/
model name strings (this is what my old Fireworks driver did because
I didn't know all model IDs; see match_echofire_device_name).  The
Mackie driver's .inf file has this list of device identifiers:

"AVC\Loud_Technologies_Inc.&Onyxi&typ_1"
"AVC\Loud_Technologies_Inc.&Onyx_1640i&typ_1"
"AVC\Loud_Technologies_Inc.&Onyx-i&typ_1"
"AVC\Loud_Technologies_Inc.&d.Pro&typ_1"
"AVC\Loud_Technologies_Inc.&Mackie_Onyx_Satellite&typ_1"
"AVC\Loud_Technologies_Inc.&Tapco_LINK.firewire_4x6&typ_1"
"AVC\Loud_Technologies_Inc.&U.420&typ_1"
"AVC\Mackie&Onyx_Firewire&typ_1"

(This format is documented at <http://msdn.microsoft.com/en-us/library/windows/hardware/ff556371.aspx>;
apparently, spaces are replaced with "_".)


Regards,
Clemens

------------------------------------------------------------------------------

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers
  2014-08-12  7:09     ` Clemens Ladisch
@ 2014-08-12 22:45       ` Takashi Sakamoto
  2014-08-13  7:06         ` Clemens Ladisch
  2014-10-16 14:31       ` Takashi Sakamoto
  1 sibling, 1 reply; 35+ messages in thread
From: Takashi Sakamoto @ 2014-08-12 22:45 UTC (permalink / raw)
  To: Clemens Ladisch; +Cc: alsa-devel, stefanr, fenlason


On Aug 12 2014 16:09, Clemens Ladisch wrote:
> Takashi Sakamoto wrote:
>> On Aug 11 2014 00:54, Clemens Ladisch wrote:
>>> The old driver configured nothing but the rate.  Is there any mechanism
>>> to set the stream format?
>>
>> Yes. It's AV/C stream format information command.
>> [...]
>> But in this series of patches, I don't use this command. I expected the
>> models to change stream format according to parameters in AMDTP packets
>> such like the value of dbs field.
> 
> Apparently, the firmware doesn't support automatic format detection
> completely, and requires this command for the 2ch 32/44.1 kHz modes.

Apparently and completely, why? Please tell show reasons to judge about
a lack of automatic format detection. When you developed and committed
firewire-speakers driver, you confirmed all of combination for PCM
rate/channels, didn't you?

> (That 2ch at 96 kHz actually works is even more curious.  There already
> is that bug regarding listing the seventh format; it's possible that
> they completely forgot to report an eighth format.)

According to Griffin's manual for FireWave, 2ch/96kHz is not supported.
http://griffintechnology.com/support/firewave

> 32kHz, 44.1kHz, 48kHz and 96kHz playback in MBLA mode (non-Dolby)
> 32kHz, 44.1kHz, 48kHz in Dolby Pro Logic II
> 48kHz Encoded Digital in Dolby Digital

So it's natural that there is not the eight format. I think we're not
need to consider 2ch/96kHz mode.

Instead, we should consider the 2ch modes. This mode seems to be used
for Dolby Pro Logic II and Dolby Digital but I don't know about the
specification of Dolby something. If we drop these modes, the work to
develop OXFW driver becomes simpler.


Regards

Takashi Sakamoto
o-takashi@sakamocchi.jp

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 12/15] oxfw: Add support for Behringer/Mackie devices
  2014-08-12  7:33       ` [alsa-devel] " Clemens Ladisch
@ 2014-08-12 23:21         ` Takashi Sakamoto
  2014-08-13  6:41           ` Clemens Ladisch
  0 siblings, 1 reply; 35+ messages in thread
From: Takashi Sakamoto @ 2014-08-12 23:21 UTC (permalink / raw)
  To: Clemens Ladisch; +Cc: alsa-devel, stefanr, fen >> Jay Fenlason

On Aug 12 2014 16:33, Clemens Ladisch wrote:
> Takashi Sakamoto wrote:
>> On Aug 11 2014 00:54, Clemens Ladisch wrote:
>>> Takashi Sakamoto wrote:
>>>> +	/* IDs are unknown but able to be supported */
>>>> +	/*  Mackie(Loud), d.2 pro */
>>>> +	/*  Mackie(Loud), d.4 pro */
>>>> +	/*  Mackie(Loud), U.420 */
>>>> +	/*  Mackie(Loud), U.420d */
>>>
>>> To support all Mackie devices, use one ID entry with only the vendor ID,
>>> and abort with -ENODEV from the _probe function if the hardware is not
>>> actually an OXFW97x device (try to read the HARDWARE_ID and/or
>>> FIRMWARE_ID registers).
>>
>> In this idea, OXFW driver transfers the transaction(s) for all of
>> Mackie(Loud) devices on IEEE 1394 bus when they're probed. I want to
>> avoid such changes possible to affects the other models on the same bus
>> because I'm afraid of side effects.
> 
> The only other known Mackie devices are the 400F/1200F.  The Fireworks
> firmware doesn't blow up if you try to read unsupported registers, but
> if you want to be sure, you can blacklist these two IDs.

As long as I know, Mackie (Loud) produced FireWire sound devices with
Fireworks, OXFW and Dice. But I don't know all models at all. There may
be models with the other chipset.

Furthermore, hardware vendors sometimes add customization to firmware
for their models.

I think we should not stand on such assumption.

> Or just do the detection like the Windows driver, through the vendor/
> model name strings (this is what my old Fireworks driver did because
> I didn't know all model IDs; see match_echofire_device_name).  The
> Mackie driver's .inf file has this list of device identifiers:
> 
> "AVC\Loud_Technologies_Inc.&Onyxi&typ_1"
> "AVC\Loud_Technologies_Inc.&Onyx_1640i&typ_1"
> "AVC\Loud_Technologies_Inc.&Onyx-i&typ_1"
> "AVC\Loud_Technologies_Inc.&d.Pro&typ_1"
> "AVC\Loud_Technologies_Inc.&Mackie_Onyx_Satellite&typ_1"
> "AVC\Loud_Technologies_Inc.&Tapco_LINK.firewire_4x6&typ_1"
> "AVC\Loud_Technologies_Inc.&U.420&typ_1"
> "AVC\Mackie&Onyx_Firewire&typ_1"
> 
> (This format is documented at <http://msdn.microsoft.com/en-us/library/windows/hardware/ff556371.aspx>;
> apparently, spaces are replaced with "_".)

This is a good idea.

I note that Mackie Onyx-i series has two revisions. OXFW was used for
the former revision. Dice is used for the latter revision. See:
http://www.mackie.com/products/onyxiseries/drivers/

When using this idea, we have a need to distinguish these two revisions
for prevention from applying OXFW driver to the latter revision. Do you
have any good idea?


Regards

Takashi Sakamoto
(In summer vacation)
o-takashi@sakamocchi.jp

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 12/15] oxfw: Add support for Behringer/Mackie devices
  2014-08-12 23:21         ` Takashi Sakamoto
@ 2014-08-13  6:41           ` Clemens Ladisch
  2014-08-13  7:00             ` Takashi Sakamoto
  0 siblings, 1 reply; 35+ messages in thread
From: Clemens Ladisch @ 2014-08-13  6:41 UTC (permalink / raw)
  To: Takashi Sakamoto; +Cc: alsa-devel, stefanr, fen >> Jay Fenlason

Takashi Sakamoto wrote:
> On Aug 12 2014 16:33, Clemens Ladisch wrote:
>> Or just do the detection like the Windows driver, through the vendor/
>> model name strings (this is what my old Fireworks driver did because
>> I didn't know all model IDs; see match_echofire_device_name).  The
>> Mackie driver's .inf file has this list of device identifiers:
>>
>> "AVC\Loud_Technologies_Inc.&Onyxi&typ_1"
>> "AVC\Loud_Technologies_Inc.&Onyx_1640i&typ_1"
>> "AVC\Loud_Technologies_Inc.&Onyx-i&typ_1"
>> "AVC\Loud_Technologies_Inc.&d.Pro&typ_1"
>> "AVC\Loud_Technologies_Inc.&Mackie_Onyx_Satellite&typ_1"
>> "AVC\Loud_Technologies_Inc.&Tapco_LINK.firewire_4x6&typ_1"
>> "AVC\Loud_Technologies_Inc.&U.420&typ_1"
>> "AVC\Mackie&Onyx_Firewire&typ_1"
>
> This is a good idea.
>
> I note that Mackie Onyx-i series has two revisions. OXFW was used for
> the former revision. Dice is used for the latter revision. See:
> http://www.mackie.com/products/onyxiseries/drivers/
>
> When using this idea, we have a need to distinguish these two revisions
> for prevention from applying OXFW driver to the latter revision. Do you
> have any good idea?

The OXFW devices work with Core Audio, so they are marked as AV/C
devices; the DICE devices are not.  So it would be possible to use
different specifier_id/version entries.

Anyway, the Windows DICE driver uses detection by name, too; the .inf
entries show a different vendor name:

 1394\LOUD&Onyxi
 1394\LOUD&OnyxBlackbird


Regards,
Clemens

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 12/15] oxfw: Add support for Behringer/Mackie devices
  2014-08-13  6:41           ` Clemens Ladisch
@ 2014-08-13  7:00             ` Takashi Sakamoto
  2014-08-13  7:07               ` Clemens Ladisch
  0 siblings, 1 reply; 35+ messages in thread
From: Takashi Sakamoto @ 2014-08-13  7:00 UTC (permalink / raw)
  To: Clemens Ladisch; +Cc: alsa-devel, stefanr, fen >> Jay Fenlason

On Aug 13 2014 15:41, Clemens Ladisch wrote:
> The OXFW devices work with Core Audio, so they are marked as AV/C
> devices; the DICE devices are not.  So it would be possible to use
> different specifier_id/version entries.

OK. As long as using SPECIFIER_1394TA (0x00a02d) and
VERSION_AVC(0x010001), OXFW driver doesn't probe Mackie devices based on
Dice.

> Anyway, the Windows DICE driver uses detection by name, too; the .inf
> entries show a different vendor name:
> 
>  1394\LOUD&Onyxi
>  1394\LOUD&OnyxBlackbird

The latter revisions use "Onyxi", however the former revisons use
"Onyx-i". That's nice to distinguish.


Thanks

Takashi Sakamoto
(In summer vacation)
o-takashi@sakamocchi.jp

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers
  2014-08-12 22:45       ` Takashi Sakamoto
@ 2014-08-13  7:06         ` Clemens Ladisch
  2014-08-13 22:55           ` Takashi Sakamoto
  0 siblings, 1 reply; 35+ messages in thread
From: Clemens Ladisch @ 2014-08-13  7:06 UTC (permalink / raw)
  To: Takashi Sakamoto; +Cc: alsa-devel, stefanr, fenlason

Takashi Sakamoto wrote:
> On Aug 12 2014 16:09, Clemens Ladisch wrote:
>> Takashi Sakamoto wrote:
>>> On Aug 11 2014 00:54, Clemens Ladisch wrote:
>>>> The old driver configured nothing but the rate.  Is there any mechanism
>>>> to set the stream format?
>>>
>>> Yes. It's AV/C stream format information command.
>>> [...]
>>> But in this series of patches, I don't use this command. I expected the
>>> models to change stream format according to parameters in AMDTP packets
>>> such like the value of dbs field.
>>
>> Apparently, the firmware doesn't support automatic format detection
>> completely, and requires this command for the 2ch 32/44.1 kHz modes.
>
> Apparently and completely, why? Please tell show reasons to judge about
> a lack of automatic format detection. When you developed and committed
> firewire-speakers driver, you confirmed all of combination for PCM
> rate/channels, didn't you?

When I wrote the driver, Jay tested which combinations did actually
work (without any stream format information command; I don't have the
spec), so that's what the driver ended up supporting:
 - 6ch at 32/44.1/48/96 kHz
 - 2ch at 48/96 kHz

If snd-oxfw also does not send the stream format information command,
then I guess that 2ch at 32/44.1 kHz will not work either.

> [...]
> Instead, we should consider the 2ch modes. This mode seems to be used
> for Dolby Pro Logic II and Dolby Digital but I don't know about the
> specification of Dolby something. If we drop these modes, the work to
> develop OXFW driver becomes simpler.

The driver does not need care about the format itself; the data is just
sent as 16-bit words that pretend to be samples.  All the driver has to
do is to handle the "non-audio" bit in the "IEC958 Playback Default" or
"IEC958 Playback PCM Stream" mixer controls; this can be added later.


Regards,
Clemens

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 12/15] oxfw: Add support for Behringer/Mackie devices
  2014-08-13  7:00             ` Takashi Sakamoto
@ 2014-08-13  7:07               ` Clemens Ladisch
  2014-08-13  7:57                 ` Takashi Sakamoto
  0 siblings, 1 reply; 35+ messages in thread
From: Clemens Ladisch @ 2014-08-13  7:07 UTC (permalink / raw)
  To: Takashi Sakamoto; +Cc: alsa-devel, stefanr, fen >> Jay Fenlason

Takashi Sakamoto wrote:
> On Aug 13 2014 15:41, Clemens Ladisch wrote:
>> Anyway, the Windows DICE driver uses detection by name, too; the .inf
>> entries show a different vendor name:
>>
>>  1394\LOUD&Onyxi
>>  1394\LOUD&OnyxBlackbird
>
> The latter revisions use "Onyxi", however the former revisons use
> "Onyx-i". That's nice to distinguish.

The OXFW driver has entries for both names:

"AVC\Loud_Technologies_Inc.&Onyxi&typ_1"
"AVC\Loud_Technologies_Inc.&Onyx-i&typ_1"


Regards,
Clemens

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 12/15] oxfw: Add support for Behringer/Mackie devices
  2014-08-13  7:07               ` Clemens Ladisch
@ 2014-08-13  7:57                 ` Takashi Sakamoto
  0 siblings, 0 replies; 35+ messages in thread
From: Takashi Sakamoto @ 2014-08-13  7:57 UTC (permalink / raw)
  To: Clemens Ladisch; +Cc: alsa-devel, stefanr, fen >> Jay Fenlason

On 2014年08月13日 16:07, Clemens Ladisch wrote:
> The OXFW driver has entries for both names:
> 
> "AVC\Loud_Technologies_Inc.&Onyxi&typ_1"
> "AVC\Loud_Technologies_Inc.&Onyx-i&typ_1"

Oops, exactly! Thanks for your correction.


Takashi Sakamoto
o-takashi@sakamocchi.jp

_______________________________________________
Alsa-devel mailing list
Alsa-devel@alsa-project.org
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers
  2014-08-13  7:06         ` Clemens Ladisch
@ 2014-08-13 22:55           ` Takashi Sakamoto
  0 siblings, 0 replies; 35+ messages in thread
From: Takashi Sakamoto @ 2014-08-13 22:55 UTC (permalink / raw)
  To: Clemens Ladisch; +Cc: alsa-devel, stefanr, fenlason

On Aug 13 2014 16:06, Clemens Ladisch wrote:
> When I wrote the driver, Jay tested which combinations did actually
> work (without any stream format information command; I don't have the
> spec), so that's what the driver ended up supporting:
>  - 6ch at 32/44.1/48/96 kHz
>  - 2ch at 48/96 kHz
> 
> If snd-oxfw also does not send the stream format information command,
> then I guess that 2ch at 32/44.1 kHz will not work either.

If so, Jey's test included something wrong. I hope someone to test these
combinations again, especially 2ch/96kHz.

>> [...]
>> Instead, we should consider the 2ch modes. This mode seems to be used
>> for Dolby Pro Logic II and Dolby Digital but I don't know about the
>> specification of Dolby something. If we drop these modes, the work to
>> develop OXFW driver becomes simpler.
> 
> The driver does not need care about the format itself; the data is just
> sent as 16-bit words that pretend to be samples.  All the driver has to
> do is to handle the "non-audio" bit in the "IEC958 Playback Default" or
> "IEC958 Playback PCM Stream" mixer controls; this can be added later.

Hm. So the driver just transfers 2ch 16bit words in IEC 60958 compliant
data channels, then the device handles them for surround channels. I
misunderstand that Dolby something is a functionality in driver side.


Regards

Takashi Sakamoto
(In summer vacation)
o-takashi@sakamocchi.jp

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers
  2014-08-12  7:09     ` Clemens Ladisch
  2014-08-12 22:45       ` Takashi Sakamoto
@ 2014-10-16 14:31       ` Takashi Sakamoto
  1 sibling, 0 replies; 35+ messages in thread
From: Takashi Sakamoto @ 2014-10-16 14:31 UTC (permalink / raw)
  To: Clemens Ladisch; +Cc: alsa-devel, stefanr, fenlason

Clemens,

Today I got my show-stopper (Griffin FireWave) for OXFW driver and
investigate it with
easy script (see the end of this message).

Currently firewire-speakers driver handles this device but there're
known issues that:
1.the device doesn't generate sounds at some combinations between
channels and rates
2.96.0kHz/2ch seems not supported.

As a result of my investigation:
The device is known to have two modes: 5.1ch surround mode and Dolby Pro
Logic II mode.

At the former mode, stream transfers 6 PCM frames. At the latter mode,
stream stransfers 2 PCM frames. The driver can switch these modes to
execute AV/C Stream Format Information command with Single
subfunction[1]. The purpose of this command is to change internal
formation of a packet. Available formations can be retrieved by AV/C
Stream Format Information command with Single subfunction[1].

$ ./firewave.sh get
Supported stream formats:
response: 000: 0c ff bf c1 00 00 00 00 ff 00 00 90 40 04 02 01
............@...
response: 010: 06 06                                           ..
response: 000: 0c ff bf c1 00 00 00 00 ff 00 01 90 40 02 02 01
............@...
response: 010: 06 06                                           ..
response: 000: 0c ff bf c1 00 00 00 00 ff 00 02 90 40 03 02 01
............@...
response: 010: 06 06                                           ..
response: 000: 0c ff bf c1 00 00 00 00 ff 00 03 90 40 05 02 01
............@...
response: 010: 06 06                                           ..
response: 000: 0c ff bf c1 00 00 00 00 ff 00 04 90 40 02 02 01
............@...
response: 010: 02 00                                           ..
response: 000: 0c ff bf c1 00 00 00 00 ff 00 05 90 40 03 02 01
............@...
response: 010: 02 00                                           ..
response: 000: 0c ff bf c1 00 00 00 00 ff 00 06 90 40 04 02 01
............@...
response: 010: 02 00                                           ..
response: 000: 0a ff bf c1 00 00 00 00 ff ff 07 ff             ............
Current stream format:
response: 000: 0c ff bf c0 00 00 00 00 ff 00 90 40 04 02 01 06
...........@....
response: 010: 06                                              .
Supported sampling rates in current number of channels:
response: 000: 0c ff 19 00 90 00 ff ff                         ........
response: 000: 0c ff 19 00 90 01 ff ff                         ........
response: 000: 0c ff 19 00 90 02 ff ff                         ........
response: 000: 08 ff 19 00 90 03 ff ff                         ........
response: 000: 0c ff 19 00 90 04 ff ff                         ........
response: 000: 08 ff 19 00 90 05 ff ff                         ........
response: 000: 08 ff 19 00 90 06 ff ff                         ........

According to this output, the available formations of this device are:
Mode A. 6ch Multi Bit Linear Audio (MBLA) data at 32.0/44.1/48.0/96.0kHz
Mode B. 2ch IEC60958 Compliant data at 32.0/44.1/48.0kHz

For the detail of MBLA and IEC 60958 Compliant data, please refer to
[2]. The mode A is equivalent to 5.1ch surround mode, and mode B is
equivalent to Dolby Pro Logic II mode. FYI, the last commands are AV/C
Plug Signal Format commands[3] and they show the device generally
supports 32.0/44.1/48.0/96.0.

As long as I tested, the driver (firewire-speaker) can playback and the
device successfully generates sound unless the driver attempts to
transfer packets with different number of channels against current
setting. For example, when 2ch/48.0kHz is set, then playbacks at 2ch
32.0/44.1/48.0 are successful.
Or when 6ch/32.0kHz is set, then playbacks at 6ch 32.0/44.1/48.0/96.0
are successful.

But when the driver attempt to transfer packets with different number of
channels, this operation freezes the device. The device desn't react
transactions anymore. For example:

$ ./firewave.sh set 44100 6
response: 000: 09 ff bf c0 00 00 00 00 ff 00 90 40 03 02 01 06
...........@....
response: 010: 06                                              .
$ aplay -D hw:FireWave,0 -f S32_LE -c 2 -r 44100 /dev/urandom
(the application and the device freeze)

This causes a bad effect to userspace applications, especially
PulseAudio.When connecting sound devices, PulseAudio starts any streams
to detect its channel profiles. For FireWave, PulseAudio starts two
streams: 2ch and 6ch streams. These operations can freeze the device.

For 96.0kHz/2ch, I tried this:
$ ./firewave.sh set 48000 2
response: 000: 09 ff bf c0 00 00 00 00 ff 00 90 40 04 02 01 02
...........@....
response: 010: 00                                              .
$ aplay -D hw:FireWave -f S32_LE -c 2 -r 96000 /dev/urandom

The device is expected to generate noise but it sounds nothing. Thus I
think the device doesn't support this mode.

I have a plan to include improvements for this issue to my patchset for
OXFW driver. The patchset will be posted till the beginning of next
month, I think.

[1] TA Document 2004008, AV/C Stream Format Information Specification
1.1 (working draft) (April 15, 2005, 1394TA)
[2] IEC 61883-6:2005 Consumer audio/video equipment - Digital interface -
Part 6: Audio and Music Data Transmission Protocol, Edition 2.0
[2] TA Document 2004006, AV/C Digital Interface Command Set General
Specification Version 4.2 (September 1, 2004, 1394TA)


Regards

Takashi Sakamoto
o-takashi@sakamocchi.jp

----- 8< -----
$ cat ./firewave.sh

#!/bin/bash

if [ $# -lt 1 ] || [[ $1 != "get" ]] && [[ $1 != "set" ]]; then
	echo "firewave.sh 'get'"
	echo "firewave.sh 'set' (32000|44100|48000|96000) (2|6)"
	exit
fi

if [[ $1 == get ]]; then
	echo "Supported stream formats:"

	# AV/C Stream Format Information command with List subfunction
	for i in $(seq 0 7) ; do
		./firewire-request /dev/fw1 fcp $(printf
"0x01ffbfc100000000ffff%02xff" $i)
	done

	echo "Current stream format:"

	# AV/C Stream Format Information command with Single subfunction
	./firewire-request /dev/fw1 fcp 0x01ffbfc000000000ffffffff

	echo "Supported sampling rates in current number of channels:"

	# AV/C Plug Signal Format command
	for i in $(seq 0 6); do
		./firewire-request /dev/fw1 fcp $(printf "0x02ff190090%02xffff" $i)
	done

	exit
fi

if [ ! $# -eq 3 ]; then
	echo 'Invalid number of arguments.'
	exit
fi

if   [ $2 -eq 32000 ] && [ $3 -eq 6 ]; then
	rate=2
	ch=6
	format=6
elif [ $2 -eq 44100 ] && [ $3 -eq 6 ]; then
	rate=3
	ch=6
	format=6
elif [ $2 -eq 48000 ] && [ $3 -eq 6 ]; then
	rate=4
	ch=6
	format=6
elif [ $2 -eq 96000 ] && [ $3 -eq 6 ]; then
	rate=5
	ch=6
	format=6
elif [ $2 -eq 32000 ] && [ $3 -eq 2 ]; then
	rate=2
	ch=2
	format=0
elif [ $2 -eq 44100 ] && [ $3 -eq 2 ]; then
	rate=3
	ch=2
	format=0
elif [ $2 -eq 48000 ] && [ $3 -eq 2 ]; then
	rate=4
	ch=2
	format=0
elif [ $2 -eq 96000 ] && [ $3 -eq 2 ]; then
	rate=5
	ch=2
	format=0
else
	echo "Unsupported combination between rate and channel."
	exit
fi

# AV/C Stream Format Information command with Single subfunction
./firewire-request /dev/fw1 fcp $(printf
"0x00ffbfc000000000ff009040%02x0201%02x%02x" $rate $ch $format)

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers
  2014-05-13 14:27 [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Takashi Sakamoto
                   ` (16 preceding siblings ...)
  2014-08-10 15:54 ` Clemens Ladisch
@ 2014-10-26 12:00 ` Stefan Richter
  2014-10-26 14:15   ` Takashi Sakamoto
  17 siblings, 1 reply; 35+ messages in thread
From: Stefan Richter @ 2014-10-26 12:00 UTC (permalink / raw)
  To: Takashi Sakamoto, alsa-devel
  Cc: fenlason, linux1394-devel, clemens, ffado-devel

On May 13 Takashi Sakamoto wrote:
[...]
> Currently, in ALSA, 'firewire-speakers' driver supports OXFW970/971 chipset
> which Oxford Semiconductor produced. But this driver has some restrictions:
>  - Don't support MIDI substreams
>  - Don't support substreams for PCM/MIDI capturing
> There are several devices which are based on OXFW970/971. Especially, devices
> which Behringer and Mackie produced support playback/capture PCM samples/MIDI
> messages. So firewire-speakers needs re-work to support these devices.
> 
> This patchset renames 'firewire-speakers' to 'oxfw'. Maintainers already
> agreed to rename firewire-speakers to suitable name, in my first RFC in Jan
> 2014.
> 
> 
> == Supported devices
> Currently these devices are supported:
>  * Griffin Firewave
>  * LaCie Firewire Speakers
>  * Behringer F-Control Audio 202
>  * Mackie(Loud) Onyx-i series (former models)
>  * Mackie(Loud) Onyx Satellite
>  * Mackie(Loud) Tapco Link.Firewire
[...]
> == Features
>  - Capturing/Playbacking PCM samples/MIDI messages at supported sampling
>    rates via ALSA PCM/MIDI interfaces
>  - Retrieving firewire node information via ALSA hwdep interface
>  - Locking/Unlocking kernel streaming via ALSA hwdep interface
>  - Receiving notification at starting/stopping kernel streaming via ALSA
>    hwdep interface
[...]

Hi,

here is a test report with the devices
  - LaCie FireWire Speakers,
  - Griffin FireWave
and the software
  - alsamixer,
  - audacious (using ALSA backend or Jack backend),
  - mplayer   (using ALSA backend or Jack backend),
  - jack
on top of kernel 3.17(.1).  I tested playback of stereo audio only (44.1 kHz
PCM sources; a few symphonic, rock, and electronic music pieces; stereo only).
I did not test pulseaudio, as I do not have it yet installed on my test box.
So far I ran jack only with 44.1 kHz sample rate.  The FireWave was connected
to L/R line-in of a stereo amplifier.  To test the upper channels of the
FireWave, I simply replugged the cable between FireWave's L/R, C/S, and LS/RS
ports.

First I re-tested with stock 3.17.1 because I haven't used these two devices
in a while.  After that, I pulled the following patches from
https://github.com/takaswie/sound master branch, applied them on top of 3.17.1,
and repeated the tests.
    f7c9bc4c15ea ALSA: oxfw: Add hwdep interface
    787e571d3197 ALSA: oxfw: Add support for capture/playback MIDI messages
    1868471597e6 ALSA: oxfw: Add support AMDTP in-stream and PCM capture
    42483f846ff5 ALSA: oxfw: Add support for Behringer/Mackie devices
    51fadf6db12e ALSA: oxfw: Change the way to start stream
    54a1b23d6eba ALSA: oxfw: Add proc interface for debugging purpose
    f56001836575 ALSA: oxfw: Change the way to make PCM rules/constraints
    2d07cd88a870 ALSA: oxfw: Add support for AV/C stream format command to get/set supported stream formation
    b213d30cd408 ALSA: oxfw: Change the way to name card
    4255f0663f83 ALSA: oxfw: Split control functionality to a new file
    ed98704ccad7 ALSA: oxfw: Split PCM functionality to a new file
    646b7c0dfedd ALSA: oxfw: Split stream functionality to a new file and add a header file
    b09fa32b4063 ALSA: oxfw: Move to its own directory
    3479e7136ccb ALSA: speakers: Rename to oxfw and rename some members
Later I also switched back and forth between drivers without and with the
patches, with consistent results as described below.


Results with LaCie FireWire Speakers:
-------------------------------------

The only difference in behavior between 3.17.1 and 3.17.1 plus patches is
that alsamixer displays the speakers' card and chip name differently.
Stock, i.e. snd-firewire-speakers:     Patched, i.e. snd-oxfw:
    Card: FireWire Speakers                Card: FWSpeakers
    Chip: OXFW970                          Chip: FWSpeakers
I am not familiar enough with ALSA and its applications to tell whether
this change is good or bad or immaterial; for me it does not matter.

Otherwise, I noticed no difference between stock and patched; i.e.
alsamixer shows the same single PCM volume control, the two playback
applications times two backends behave the same, and the sound files sound
the same.


Results with Griffin FireWave:
------------------------------

With snd-firewire-speakers, only the blue "FireWire" LED lights up.
With snd-oxfw, the blue LED lights up, and as soon as the card is opened by
audacious' or mplayer's ALSA backend for the first time, the red "Dolby D+PL
II" LED lights up too and stays lit until power-off or until jackd is
started.

Alsamixer displays different chip names.
Stock, i.e. snd-firewire-speakers:     Patched, i.e. snd-oxfw:
    Card: FireWave                         Card: FireWave
    Chip: OXFW970                          Chip: FireWave

Playback with snd-firewire-speakers, using ALSA:

    Channel 1 (L) is subjectively correct.

    There is distortion on channel 2 (R) channel.  The type of distortion
    depends on the audio which is played back.  It can be pops, buzzing,
    or a high continuous hissing tone (I guess a 22.05 kHz tone).   This is
    the same behavior as I remember it from the time when
    snd-firewire-speakers was merged into the mainline in 2.6.39.

    Channels 3..6 (C, S, LS, RS) are mute.
    (As mentioned above, I only tested stereo sound sources.)

Playback with snd-oxfw, using ALSA:

    Subjectively correct stereo sound on channels 1-2 (L, R), mute on
    channel 3 (C), quiet bass sound on channel 4 (S), mute on channels 5-6
    (LS, RS).

    In other words, the patches fix ALSA stereo playback on FireWave.

Playback with snd-firewire-speakers, using Jack on the first 2 channels only
(i.e. I connected out_0 with playback 1, out_1 with playback_2, and left
playback_[3-6] unconnected):

    Subjectively correct stereo sound on channels 1-2 (L, R), mute on
    channel 3 (C), quiet bass sound on channel 4 (S), mute on channels 5-6
    (LS, RS).

Playback with snd-oxfw, Jack on the first 2 channels only:

   Same as snd-firewire-speakers.

Playback with snd-firewire-speakers, using Jack on all channels (I connected
out_0 to playback_[1,3,5] and out_1 with playback_[2,4,6]):

   The first three channels (L, R, C) are alright, the next channel (S) is
   distorted in the same manner as was channel 2 (R) with ALSA 2-channel
   playback, and the last channels (LS, RS) are alright.

Playback with snd-oxfw, using Jack on all channels:

   Same as snd-firewire-speakers.


Conclusion:
-----------

In my tests, the patch set does not regress with LaCie FireWire Speakers,
does not regress with Griffin FireWave + Jack, and it fixes FireWave for
stereo playback through ALSA client applications.  Therefore and for the
much extended device support and features (which I could not test myself
due to lack of hardware), I would be glad if these patches were to be
queued up for mainline merge and am offering a

Tested-by: Stefan Richter <stefanr@s5r6.in-berlin.de> (Speakers, FireWave)

tag. Thank you,
-- 
Stefan Richter
-=====-====- =-=- ==-=-
http://arcgraph.de/sr/

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers
  2014-10-26 12:00 ` Stefan Richter
@ 2014-10-26 14:15   ` Takashi Sakamoto
  0 siblings, 0 replies; 35+ messages in thread
From: Takashi Sakamoto @ 2014-10-26 14:15 UTC (permalink / raw)
  To: Stefan Richter, alsa-devel
  Cc: clemens, linux1394-devel, fenlason, ffado-devel

Hi Stefan,

On Oct 26 2014 21:00, Stefan Richter wrote:
> Conclusion:
> -----------
> 
> In my tests, the patch set does not regress with LaCie FireWire Speakers,
> does not regress with Griffin FireWave + Jack, and it fixes FireWave for
> stereo playback through ALSA client applications.  Therefore and for the
> much extended device support and features (which I could not test myself
> due to lack of hardware), I would be glad if these patches were to be
> queued up for mainline merge and am offering a
> 
> Tested-by: Stefan Richter <stefanr@s5r6.in-berlin.de> (Speakers, FireWave)
> 
> tag. Thank you,

Thanks for your test and report. And I'm sorry to notice your message
after I posted my patches to alsa-devel...

[alsa-devel] [PATCH 00/29 v2] ALSA: Enhancement for existed FireWire drivers
http://mailman.alsa-project.org/pipermail/alsa-devel/2014-October/082844.html


> Stock, i.e. snd-firewire-speakers:     Patched, i.e. snd-oxfw:
>     Card: FireWire Speakers                Card: FWSpeakers
>     Chip: OXFW970                          Chip: FWSpeakers
...
> Alsamixer displays different chip names.
> Stock, i.e. snd-firewire-speakers:     Patched, i.e. snd-oxfw:
>     Card: FireWave                         Card: FireWave
>     Chip: OXFW970                          Chip: FireWave

This is due to second parameter for snd_pcm_new().

[alsa-devel] [PATCH 18/29] ALSA: oxfw: Split PCM functionality to a new file
http://mailman.alsa-project.org/pipermail/alsa-devel/2014-October/082857.html

In my patchset, the parameter refers to 'struct snd_card.driver'. For
these two models, this member refers to 'struct device_info.driver_name'
and it's hard-coded. This may be a bit meddling.

>     There is distortion on channel 2 (R) channel.  The type of
>     distortion depends on the audio which is played back.  It can be
>     pops, buzzing, or a high continuous hissing tone (I guess a 22.05
>     kHz tone).

I heard it.

I understand it's due to the difference of stream format.
$ cat /proc/asound/FireWave/firewire/formation
Input Stream to device:
	Rate	PCM	MIDI
 	48000	6	0
 	32000	6	0
 	44100	6	0
 	96000	6	0
 	32000	2	0
*	44100	2	0
 	48000	2	0

Old snd-firewire-speakers just change sampling rate without changing
stream format. If the device wait for 6ch PCM samples in a data block of
AMDTP packet but the driver transfer 2ch PCM samples in a data block of
AMDTP packet (or vice versa), the device handles the samples badly and
sounds hiss noise.

>     This is the same behavior as I remember it from the time when
>     snd-firewire-speakers was merged into the mainline in 2.6.39.

If so, you and Clemens should have mentioned about it anywhere (maybe in
commit message). It brings a new device to my home ;)

Anyway, thanks for your and Clemens' work. Enjoy playing music!


Regards

Takashi Sakamoto
o-takashi@sakamocchi.jp

^ permalink raw reply	[flat|nested] 35+ messages in thread

end of thread, other threads:[~2014-10-26 14:15 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-05-13 14:27 [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Takashi Sakamoto
2014-05-13 14:27 ` [PATCH 01/15] speakers: Rename to oxfw and rename some members Takashi Sakamoto
2014-05-13 14:27 ` [PATCH 02/15] oxfw: Move to its own directory Takashi Sakamoto
2014-05-13 14:27 ` [PATCH 03/15] oxfw: Split stream functionality to a new file and add a header file Takashi Sakamoto
2014-05-13 14:27 ` [PATCH 04/15] oxfw: Split PCM functionality to a new file Takashi Sakamoto
2014-05-13 14:27 ` [PATCH 05/15] oxfw: Split control " Takashi Sakamoto
2014-05-13 14:27 ` [PATCH 06/15] oxfw: Change the way to name card Takashi Sakamoto
2014-05-13 14:27 ` [PATCH 07/15] oxfw: Change the way to make PCM rules/constraints Takashi Sakamoto
2014-05-13 14:27 ` [PATCH 08/15] oxfw: Add proc interface for debugging purpose Takashi Sakamoto
2014-05-13 14:27 ` [PATCH 09/15] oxfw: Change the way to start stream Takashi Sakamoto
2014-05-13 14:27 ` [PATCH 10/15] oxfw: Add support for AV/C stream format command to get supported stream formation Takashi Sakamoto
2014-05-13 14:27 ` [PATCH 11/15] oxfw: Add a quirk for Griffin FireWave Takashi Sakamoto
2014-05-13 14:27 ` [PATCH 12/15] oxfw: Add support for Behringer/Mackie devices Takashi Sakamoto
2014-08-10 15:54   ` [alsa-devel] " Clemens Ladisch
2014-08-11 23:48     ` Takashi Sakamoto
2014-08-12  7:33       ` [alsa-devel] " Clemens Ladisch
2014-08-12 23:21         ` Takashi Sakamoto
2014-08-13  6:41           ` Clemens Ladisch
2014-08-13  7:00             ` Takashi Sakamoto
2014-08-13  7:07               ` Clemens Ladisch
2014-08-13  7:57                 ` Takashi Sakamoto
2014-05-13 14:27 ` [PATCH 13/15] oxfw: Add support AMDTP in-stream and PCM capture Takashi Sakamoto
2014-05-13 14:27 ` [PATCH 14/15] oxfw: Add support for capture/playback MIDI messages Takashi Sakamoto
2014-05-13 14:27 ` [PATCH 15/15] oxfw: Add hwdep interface Takashi Sakamoto
2014-05-13 17:43 ` [RFC][PATCH 00/15 v4] OXFW driver, a succesor to firewire-speakers Stefan Richter
2014-05-14 14:15   ` Takashi Sakamoto
2014-08-10 15:54 ` Clemens Ladisch
2014-08-11 23:55   ` Takashi Sakamoto
2014-08-12  7:09     ` Clemens Ladisch
2014-08-12 22:45       ` Takashi Sakamoto
2014-08-13  7:06         ` Clemens Ladisch
2014-08-13 22:55           ` Takashi Sakamoto
2014-10-16 14:31       ` Takashi Sakamoto
2014-10-26 12:00 ` Stefan Richter
2014-10-26 14:15   ` Takashi Sakamoto

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.