All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC][PATCH 0/8] A new driver for OXFW970/971 based devices
@ 2014-01-05 11:13 Takashi Sakamoto
  2014-01-05 11:13 ` [PATCH 1/8] oxfw: Add skelton " Takashi Sakamoto
                   ` (9 more replies)
  0 siblings, 10 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-05 11:13 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

This series of patch is based on my previous series below:
http://mailman.alsa-project.org/pipermail/alsa-devel/2013-December/070424.html

This series of patch add a new driver, snd-oxfw, for OXFW970/971 based devices.
These chipsets are already supported by snd-firewire-speakers but this new
driver aim to support recording equipment.

Current supported devices:
 * Behringer F-Control Audio 202
 * Mackie Onyx-i series (former model)
 * Mackie Onyx Satellite

Devices possible to be supported if identifying IDs:
 * Mackie, d.2 pro
 * Mackie, d.4 pro
 * Mackie, U.420
 * Mackie, U.420d
 * Mackie, Tapco Link.Firewire

I worked with Behringer F-Control Audio 202. So I want someone to test with the other devices, especially about some quirks.


Clemens, would you gime me your opinions about issues below?
And if you know something about this chipset, would you share the information?

1.snd-firewire-speakers and snd-oxfw
With this series of patches, ALSA has two drivers for the same chipsets.
Merging these two drivers is a bit difficult because:
 - snd-firewire-speakers has control interface but snd-oxfw don't
 - snd-firewire-speakers don't support in-stream but snd-oxfw supports

2.currently snd-oxfw works with streams in SYT-Match
Due to invalid sequence of 'presentation timestamp' in transmitted packets.
In detail, please see 4th patch.

3.assumption of channnel formation
Some commands may not be implemented.
In detail, please see 3rd patch.

4.don't check source of clock
All of supported devices (including can be) don't have the functionality to switch source of clock. So I want to omit this.

For issue 3 and 4, Descriptor mechanism in 'Enhancements to the AV/C General
Specification 3.0 Version 1.1' may be a best solution but it costs much to implement against the number of available devices. I want to avoid this huge mechanism if possible.


Regards

Takashi Sakamoto

Takashi Sakamoto (8):
  oxfw: Add skelton for OXFW970/971 based devices
  oxfw: Read firmware version to name card
  oxfw: Add some AV/C commands for channel formation of AMDTP stream
  oxfw: Add connections and streams management
  oxfw: Add proc interface for debugging purpose
  oxfw: Add MIDI interface
  oxfw: Add PCM interface
  oxfw: Add hwdep interface

 include/uapi/sound/asound.h        |   3 +-
 include/uapi/sound/firewire.h      |   3 +-
 sound/firewire/Kconfig             |  17 +
 sound/firewire/Makefile            |   1 +
 sound/firewire/oxfw/Makefile       |   3 +
 sound/firewire/oxfw/oxfw.c         | 243 ++++++++++++++
 sound/firewire/oxfw/oxfw.h         | 140 ++++++++
 sound/firewire/oxfw/oxfw_command.c | 161 ++++++++++
 sound/firewire/oxfw/oxfw_hwdep.c   | 197 ++++++++++++
 sound/firewire/oxfw/oxfw_midi.c    | 156 +++++++++
 sound/firewire/oxfw/oxfw_pcm.c     | 421 ++++++++++++++++++++++++
 sound/firewire/oxfw/oxfw_proc.c    |  61 ++++
 sound/firewire/oxfw/oxfw_stream.c  | 639 +++++++++++++++++++++++++++++++++++++
 13 files changed, 2043 insertions(+), 2 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_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

-- 
1.8.3.2

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

* [PATCH 1/8] oxfw: Add skelton for OXFW970/971 based devices
  2014-01-05 11:13 [RFC][PATCH 0/8] A new driver for OXFW970/971 based devices Takashi Sakamoto
@ 2014-01-05 11:13 ` Takashi Sakamoto
  2014-01-05 11:13 ` [PATCH 2/8] oxfw: Read firmware version to name card Takashi Sakamoto
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-05 11:13 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

This commit add a new driver for BeBoB based devices with no functionality.
This driver just create/remove card instance according to callbacks.

OXFW970/971 are chipsets produced by Oxford Semiconductor for Multi-Channel
Isochronous Streaming FireWire Audio Controller.

Current supported devices:
 - Behringer F-Control Audio 202
 - Mackie Onyx-i series (former model)
 - Mackie Onyx Satellite

Devices possible to be supported if identifying IDs:
 - Mackie, d.2 pro
 - Mackie, d.4 pro
 - Mackie, U.420
 - Mackie, U.420d
 - Mackie, Tapco Link.Firewire/

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/Kconfig       |  17 +++++
 sound/firewire/Makefile      |   1 +
 sound/firewire/oxfw/Makefile |   2 +
 sound/firewire/oxfw/oxfw.c   | 162 +++++++++++++++++++++++++++++++++++++++++++
 sound/firewire/oxfw/oxfw.h   |  42 +++++++++++
 5 files changed, 224 insertions(+)
 create mode 100644 sound/firewire/oxfw/Makefile
 create mode 100644 sound/firewire/oxfw/oxfw.c
 create mode 100644 sound/firewire/oxfw/oxfw.h

diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig
index b2c5a7e..f5a466d 100644
--- a/sound/firewire/Kconfig
+++ b/sound/firewire/Kconfig
@@ -117,4 +117,21 @@ config SND_BEBOB
           To compile this driver as a module, choose M here: the module
           will be called snd-bebob.
 
+config SND_OXFW
+	tristate "Oxford Semiconductor OXFW970/971 support"
+	select SND_FIREWIRE_LIB
+	select SND_RAWMIDI
+	select SND_PCM
+	select SND_HWDEP
+        help
+	 Say Y here to include support for FireWire devices based on
+	 Oxford Semiconductor OXFW970/971. SND_FIREWIRE_SPEAKERS also
+	 supports this chipset but this driver supports recording devices:
+	  * Behringer F-Control Audio 202
+	  * Mackie Onyx-i series (former model)
+	  * Mackie Onyx Satellite
+
+          To compile this driver as a module, choose M here: the module
+          will be called snd-oxfw.
+
 endif # SND_FIREWIRE
diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile
index fad8d49..cbcd6ef 100644
--- a/sound/firewire/Makefile
+++ b/sound/firewire/Makefile
@@ -12,3 +12,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) += oxfw/
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..34be292
--- /dev/null
+++ b/sound/firewire/oxfw/oxfw.c
@@ -0,0 +1,162 @@
+/*
+ * oxfw.c - a part of driver for OXFW970/971 based devices
+ *
+ * Copyright (c) 2013 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+/*
+ * OXFW970/971 are chipsets produced by Oxford Semiconductor for Multi-Channel
+ * Isochronous Streaming FireWire Audio Controller.
+ */
+#include "oxfw.h"
+
+MODULE_DESCRIPTION("Oxford Semiconductor OXFW970/971 driver");
+MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
+MODULE_LICENSE("GPL v2");
+
+static int index[SNDRV_CARDS]	= SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS]	= SNDRV_DEFAULT_STR;
+static bool enable[SNDRV_CARDS]	= SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "card index");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "enable OXFW970/971 sound card");
+
+static DEFINE_MUTEX(devices_mutex);
+static unsigned int devices_used;
+
+#define	VEN_BEHRINGER	0x001564
+#define	VEN_LOUD	0x000ff2
+
+static void
+oxfw_card_free(struct snd_card *card)
+{
+	struct snd_oxfw *oxfw = card->private_data;
+
+	if (oxfw->card_index >= 0) {
+		mutex_lock(&devices_mutex);
+		devices_used &= ~BIT(oxfw->card_index);
+		mutex_unlock(&devices_mutex);
+	}
+
+	mutex_destroy(&oxfw->mutex);
+
+	return;
+}
+
+static int
+oxfw_probe(struct fw_unit *unit,
+	    const struct ieee1394_device_id *entry)
+{
+	struct snd_card *card;
+	struct snd_oxfw *oxfw;
+	unsigned int card_index;
+	int err;
+
+	mutex_lock(&devices_mutex);
+
+	for (card_index = 0; card_index < SNDRV_CARDS; card_index++) {
+		if (!(devices_used & BIT(card_index)) && enable[card_index])
+			break;
+	}
+	if (card_index >= SNDRV_CARDS) {
+		err = -ENOENT;
+		goto end;
+	}
+
+	err = snd_card_create(index[card_index], id[card_index],
+			      THIS_MODULE, sizeof(struct snd_oxfw), &card);
+	if (err < 0)
+		goto end;
+	card->private_free = oxfw_card_free;
+
+	oxfw = card->private_data;
+	oxfw->card = card;
+	oxfw->device = fw_parent_device(unit);
+	oxfw->unit = unit;
+	oxfw->card_index = -1;
+	mutex_init(&oxfw->mutex);
+	spin_lock_init(&oxfw->lock);
+
+	snd_card_set_dev(card, &unit->device);
+	err = snd_card_register(card);
+	if (err < 0) {
+		snd_card_free(card);
+		goto error;
+	}
+	dev_set_drvdata(&unit->device, oxfw);
+	devices_used |= BIT(card_index);
+	oxfw->card_index = card_index;
+end:
+	mutex_unlock(&devices_mutex);
+	return err;
+error:
+	snd_card_free(card);
+	mutex_unlock(&devices_mutex);
+	return err;
+}
+
+static void
+oxfw_update(struct fw_unit *unit)
+{
+	return;
+}
+
+static void
+oxfw_remove(struct fw_unit *unit)
+{
+	struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
+
+	snd_card_disconnect(oxfw->card);
+	snd_card_free_when_closed(oxfw->card);
+}
+
+static const struct ieee1394_device_id oxfw_id_table[] = {
+	/* Behringer, F-Control Audio 202 */
+	SND_OXFW_DEV_ENTRY(VEN_BEHRINGER, 0x00fc22),
+	/* Mackie, Onyx-i (former model) */
+	SND_OXFW_DEV_ENTRY(VEN_LOUD, 0x081216),
+	/* Mackie, Onyx Sattelite */
+	SND_OXFW_DEV_ENTRY(VEN_LOUD, 0x00200f),
+	/* IDs are unknown but able to be supported */
+	/*  Mackie, d.2 pro */
+	/*  Mackie, d.4 pro */
+	/*  Mackie, U.420 */
+	/*  Mackie, U.420d */
+	/*  Mackie, Tapco Link.Firewire */
+	{}
+};
+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_update,
+	.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);
+	mutex_destroy(&devices_mutex);
+}
+
+module_init(snd_oxfw_init);
+module_exit(snd_oxfw_exit);
diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h
new file mode 100644
index 0000000..3aee9e7
--- /dev/null
+++ b/sound/firewire/oxfw/oxfw.h
@@ -0,0 +1,42 @@
+/*
+ * oxford.h - a part of driver for OXFW970/971 based devices
+ *
+ * Copyright (c) 2013 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#ifndef SOUND_OXFW_H_INCLUDED
+#define SOUND_OXFW_H_INCLUDED
+
+#include <linux/compat.h>
+#include <linux/device.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+
+struct snd_oxfw {
+	struct snd_card *card;
+	struct fw_device *device;
+	struct fw_unit *unit;
+	int card_index;
+
+	struct mutex mutex;
+	spinlock_t lock;
+};
+
+#define SND_OXFW_DEV_ENTRY(vendor, model) \
+{ \
+	.match_flags	= IEEE1394_MATCH_VENDOR_ID | \
+			  IEEE1394_MATCH_MODEL_ID, \
+	.vendor_id	= vendor, \
+	.model_id	= model, \
+}
+
+#endif
-- 
1.8.3.2

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

* [PATCH 2/8] oxfw: Read firmware version to name card
  2014-01-05 11:13 [RFC][PATCH 0/8] A new driver for OXFW970/971 based devices Takashi Sakamoto
  2014-01-05 11:13 ` [PATCH 1/8] oxfw: Add skelton " Takashi Sakamoto
@ 2014-01-05 11:13 ` Takashi Sakamoto
  2014-01-05 11:13 ` [PATCH 3/8] oxfw: Add some AV/C commands for channel formation of AMDTP stream Takashi Sakamoto
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-05 11:13 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

There is already an driver for OXFW970/971, snd-firewire-speakers(speakers.c).
But this new driver aims to support recording equipment.

It's convinient to apply the same way for card name.

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

diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c
index 34be292..e90c53a 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -30,9 +30,56 @@ MODULE_PARM_DESC(enable, "enable OXFW970/971 sound card");
 static DEFINE_MUTEX(devices_mutex);
 static unsigned int devices_used;
 
+#define OXFW_FIRMWARE_ID_ADDRESS	(CSR_REGISTER_BASE + 0x50000)
+
+#define OXFW_HARDWARE_ID_ADDRESS	(CSR_REGISTER_BASE + 0x90020)
+#define OXFW_HARDWARE_ID_OXFW970	0x39443841
+#define OXFW_HARDWARE_ID_OXFW971	0x39373100
+
 #define	VEN_BEHRINGER	0x001564
 #define	VEN_LOUD	0x000ff2
 
+static int
+name_device(struct snd_oxfw *oxfw, unsigned int vendor_id)
+{
+	char vendor[24] = {0};
+	char model[24] = {0};
+	u32 version = 0;
+	int err;
+
+	/* get vendor name from root directory */
+	err = fw_csr_string(oxfw->device->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;
+
+	/* 0x970?vvvv or 0x971?vvvv, where vvvv = firmware version */
+	err = snd_fw_transaction(oxfw->unit, TCODE_READ_QUADLET_REQUEST,
+				 OXFW_FIRMWARE_ID_ADDRESS,
+				 &version, sizeof(u32), 0);
+	if (err < 0)
+		goto end;
+	be32_to_cpus(&version);
+
+	strcpy(oxfw->card->driver, "OXFW");
+	strcpy(oxfw->card->shortname, model);
+	snprintf(oxfw->card->longname, sizeof(oxfw->card->longname),
+		 "%s %s (%x %04x), GUID %08x%08x at %s, S%d",
+		 vendor, model, version >> 20, version & 0xffff,
+		 oxfw->device->config_rom[3], oxfw->device->config_rom[4],
+		 dev_name(&oxfw->unit->device),
+		 100 << oxfw->device->max_speed);
+	strcpy(oxfw->card->mixername, oxfw->card->driver);
+end:
+	return err;
+}
+
 static void
 oxfw_card_free(struct snd_card *card)
 {
@@ -83,6 +130,10 @@ oxfw_probe(struct fw_unit *unit,
 	mutex_init(&oxfw->mutex);
 	spin_lock_init(&oxfw->lock);
 
+	err = name_device(oxfw, entry->vendor_id);
+	if (err < 0)
+		goto error;
+
 	snd_card_set_dev(card, &unit->device);
 	err = snd_card_register(card);
 	if (err < 0) {
diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h
index 3aee9e7..e75eb70 100644
--- a/sound/firewire/oxfw/oxfw.h
+++ b/sound/firewire/oxfw/oxfw.h
@@ -21,6 +21,8 @@
 #include <sound/core.h>
 #include <sound/initval.h>
 
+#include "../lib.h"
+
 struct snd_oxfw {
 	struct snd_card *card;
 	struct fw_device *device;
-- 
1.8.3.2

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

* [PATCH 3/8] oxfw: Add some AV/C commands for channel formation of AMDTP stream
  2014-01-05 11:13 [RFC][PATCH 0/8] A new driver for OXFW970/971 based devices Takashi Sakamoto
  2014-01-05 11:13 ` [PATCH 1/8] oxfw: Add skelton " Takashi Sakamoto
  2014-01-05 11:13 ` [PATCH 2/8] oxfw: Read firmware version to name card Takashi Sakamoto
@ 2014-01-05 11:13 ` Takashi Sakamoto
  2014-01-05 11:13 ` [PATCH 4/8] oxfw: Add connections and streams management Takashi Sakamoto
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-05 11:13 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

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

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

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

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

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

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

diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile
index 9ca49c0..5eb3d87 100644
--- a/sound/firewire/oxfw/Makefile
+++ b/sound/firewire/oxfw/Makefile
@@ -1,2 +1,2 @@
-snd-oxfw-objs := oxfw.o
+snd-oxfw-objs := oxfw_command.o oxfw_stream.o oxfw.o
 obj-m += snd-oxfw.o
diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c
index e90c53a..c030fc0 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -155,7 +155,9 @@ error:
 static void
 oxfw_update(struct fw_unit *unit)
 {
-	return;
+	struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
+
+	fcp_bus_reset(oxfw->unit);
 }
 
 static void
diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h
index e75eb70..e82717a 100644
--- a/sound/firewire/oxfw/oxfw.h
+++ b/sound/firewire/oxfw/oxfw.h
@@ -22,6 +22,16 @@
 #include <sound/initval.h>
 
 #include "../lib.h"
+#include "../fcp.h"
+#include "../amdtp.h"
+
+#define	SND_OXFW_RATE_TABLE_ENTRIES	7
+struct snd_oxfw_stream_formation {
+	unsigned int pcm;
+	unsigned int midi;
+};
+/* this is a lookup table for index of stream formations */
+extern const unsigned int snd_oxfw_rate_table[SND_OXFW_RATE_TABLE_ENTRIES];
 
 struct snd_oxfw {
 	struct snd_card *card;
@@ -31,8 +41,53 @@ struct snd_oxfw {
 
 	struct mutex mutex;
 	spinlock_t lock;
+
+	struct snd_oxfw_stream_formation
+		tx_stream_formations[SND_OXFW_RATE_TABLE_ENTRIES];
+	struct snd_oxfw_stream_formation
+		rx_stream_formations[SND_OXFW_RATE_TABLE_ENTRIES];
+
+	unsigned int midi_input_ports;
+	unsigned int midi_output_ports;
 };
 
+/* AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) */
+#define AVC_GENERIC_FRAME_MAXIMUM_BYTES	512
+int avc_stream_get_format(struct fw_unit *unit,
+			  enum avc_general_plug_dir dir, unsigned int pid,
+			  u8 *buf, unsigned int *len,
+			  unsigned int eid);
+static inline int
+avc_stream_get_format_single(struct fw_unit *unit,
+			     enum avc_general_plug_dir dir, unsigned int pid,
+			     u8 *buf, unsigned int *len)
+{
+	return avc_stream_get_format(unit, dir, pid, buf, len, 0xff);
+}
+static inline int
+avc_stream_get_format_list(struct fw_unit *unit,
+			   enum avc_general_plug_dir dir, unsigned int pid,
+			   u8 *buf, unsigned int *len,
+			   unsigned int eid)
+{
+	return avc_stream_get_format(unit, dir, pid, buf, len, eid);
+}
+
+/*
+ * AV/C Digital Interface Command Set General Specification 4.2
+ * (Sep 2004, 1394TA)
+ */
+int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate,
+				enum avc_general_plug_dir dir,
+				unsigned short pid);
+int snd_oxfw_get_rate(struct snd_oxfw *oxfw, unsigned int *rate,
+		      enum avc_general_plug_dir dir);
+int snd_oxfw_set_rate(struct snd_oxfw *oxfw, unsigned int rate,
+		      enum avc_general_plug_dir dir);
+
+/* for AMDTP streaming */
+int snd_oxfw_stream_discover(struct snd_oxfw *oxfw);
+
 #define SND_OXFW_DEV_ENTRY(vendor, model) \
 { \
 	.match_flags	= IEEE1394_MATCH_VENDOR_ID | \
diff --git a/sound/firewire/oxfw/oxfw_command.c b/sound/firewire/oxfw/oxfw_command.c
new file mode 100644
index 0000000..526518f
--- /dev/null
+++ b/sound/firewire/oxfw/oxfw_command.c
@@ -0,0 +1,161 @@
+/*
+ * oxfw_command.c - driver for OXFW970/971 based devices
+ *
+ * Copyright (c) 2013 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "./oxfw.h"
+
+int avc_stream_get_format(struct fw_unit *unit,
+			  enum avc_general_plug_dir dir, unsigned int pid,
+			  u8 *buf, unsigned int *len,
+			  unsigned int eid)
+{
+	unsigned int subfunc;
+	int err;
+
+	/* check given buffer */
+	if ((buf == NULL) || (*len < 12)) {
+		err = -EINVAL;
+		goto end;
+	}
+
+	if (eid == 0xff)
+		subfunc = 0xc0;	/* SINGLE */
+	else
+		subfunc = 0xc1;	/* LIST */
+
+	buf[0] = 0x01;		/* STATUS */
+	buf[1] = 0xff;		/* UNIT */
+	buf[2] = 0xbf;		/* EXTENDED STREAM FORMAT INFORMATION */
+	buf[3] = subfunc;	/* SINGLE or LIST */
+	buf[4] = dir;		/* Plug Direction */
+	buf[5] = 0x00;		/* Unit */
+	buf[6] = 0x00;		/* PCR (Isochronous Plug) */
+	buf[7] = 0xff & pid;	/* Plug ID */
+	buf[8] = 0xff;		/* Padding */
+	buf[9] = 0xff;		/* support status in response */
+	buf[10] = 0xff & eid;	/* entry ID */
+	buf[11] = 0xff;		/* padding */
+
+	/* do transaction and check buf[1-7] are the same against command */
+	err = fcp_avc_transaction(unit, buf, 12, buf, *len,
+				  BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
+				  BIT(6) | BIT(7));
+	if (err < 0) {
+		goto end;
+	/* reach the end of entries */
+	} else if (buf[0] == 0x0a) {
+		err = 0;
+		*len = 0;
+		goto end;
+	} else if (buf[0] != 0x0c) {
+		err = -EINVAL;
+		goto end;
+	/* the content starts at 11th bytes */
+	} else if (err < 9) {
+		err = -EIO;
+		goto end;
+	} else if ((subfunc == 0xc1) && (buf[10] != eid)) {
+		err = -EIO;
+		goto end;
+	}
+
+	/* strip */
+	memmove(buf, buf + 10, err - 10);
+	*len = err - 10;
+	err = 0;
+end:
+	return err;
+}
+
+int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate,
+				enum avc_general_plug_dir dir,
+				unsigned short pid)
+{
+	unsigned int sfc;
+	u8 *buf;
+	int err;
+
+	for (sfc = 0; sfc < CIP_SFC_COUNT; sfc++) {
+		if (amdtp_rate_table[sfc] == rate)
+			break;
+	}
+	if (sfc == CIP_SFC_COUNT)
+		return -EINVAL;
+
+	buf = kzalloc(8, GFP_KERNEL);
+	if (buf == NULL)
+		return -ENOMEM;
+
+	buf[0] = 0x02;		/* SPECIFIC INQUIRY */
+	buf[1] = 0xff;		/* UNIT */
+	if (dir == AVC_GENERAL_PLUG_DIR_IN)
+		buf[2] = 0x19;	/* INPUT PLUG SIGNAL FORMAT */
+	else
+		buf[2] = 0x18;	/* OUTPUT PLUG SIGNAL FORMAT */
+	buf[3] = 0xff & pid;	/* plug id */
+	buf[4] = 0x90;		/* EOH_1, Form_1, FMT. AM824 */
+	buf[5] = 0x07 & sfc;	/* FDF-hi. AM824, frequency */
+	buf[6] = 0xff;		/* FDF-mid. AM824, SYT hi (not used)*/
+	buf[7] = 0xff;		/* FDF-low. AM824, SYT lo (not used) */
+
+	/* do transaction and check buf[1-5] are the same against command */
+	err = fcp_avc_transaction(unit, buf, 8, buf, 8,
+				  BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5));
+	if (err < 0)
+		goto end;
+
+	/* check length */
+	if (err != 8) {
+		dev_err(&unit->device, "failed to inquiry sample rate\n");
+		err = -EIO;
+		goto end;
+	}
+
+	/* return response code */
+	err = buf[0];
+end:
+	kfree(buf);
+	return err;
+}
+
+int snd_oxfw_get_rate(struct snd_oxfw *oxfw, unsigned int *rate,
+		      enum avc_general_plug_dir dir)
+{
+	int err;
+
+	err = avc_general_get_sig_fmt(oxfw->unit, rate, dir, 0);
+	if (err < 0)
+		goto end;
+
+	/* IMPLEMENTED/STABLE is OK */
+	if (err != 0x0c) {
+		dev_err(&oxfw->unit->device,
+			"failed to get sampling rate\n");
+		err = -EIO;
+	}
+end:
+	return err;
+}
+
+int snd_oxfw_set_rate(struct snd_oxfw *oxfw, unsigned int rate,
+		       enum avc_general_plug_dir dir)
+{
+	int err;
+
+	err = avc_general_set_sig_fmt(oxfw->unit, rate, dir, 0);
+	if (err < 0)
+		goto end;
+
+	/* ACCEPTED or INTERIM is OK */
+	if ((err != 0x0f) && (err != 0x09)) {
+		dev_err(&oxfw->unit->device,
+			"failed to set sampling rate\n");
+		err = -EIO;
+	}
+end:
+	return err;
+}
diff --git a/sound/firewire/oxfw/oxfw_stream.c b/sound/firewire/oxfw/oxfw_stream.c
new file mode 100644
index 0000000..60ad700
--- /dev/null
+++ b/sound/firewire/oxfw/oxfw_stream.c
@@ -0,0 +1,243 @@
+/*
+ * oxfw_stream.c - a part of driver for OXFW970/971 based devices
+ *
+ * Copyright (c) 2013 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "./oxfw.h"
+
+/*
+ * According to their datasheet:
+ *  OXFW970 32.0/44.1/48.0/96.0 Khz, 4 audio channels I/O
+ *  OXFW971: 32.0/44.1/48.0/88.2/96.0 kHz, 16 audio channels I/O, MIDI I/O
+ *
+ * OXFW970 seems not to implement 'LIST' subfunction for 'EXTENDED STREAM
+ * FORMAT INFORMATION' defined in 'AV/C Stream Format Information
+ * Specification 1.1 (Apr 2005, 1394TA)'. So this module uses an assumption
+ * that OXFW970 doesn't change its formation of channels in AMDTP stream.
+ */
+const unsigned int snd_oxfw_rate_table[SND_OXFW_RATE_TABLE_ENTRIES] = {
+	[0] = 32000,
+	[1] = 44100,
+	[2] = 48000,
+	[3] = 88200,
+	[4] = 96000,
+	[5] = 176400,
+	[6] = 192000,
+};
+
+/*
+ * See Table 5.7 – Sampling frequency for Multi-bit Audio
+ * at AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA)
+ */
+static const unsigned int avc_stream_rate_table[] = {
+	[0] = 0x02,
+	[1] = 0x03,
+	[2] = 0x04,
+	[3] = 0x0a,
+	[4] = 0x05,
+	[5] = 0x06,
+	[6] = 0x07,
+};
+
+/*
+ * See Table 6.16 - AM824 Stream Format
+ *     Figure 6.19 - format_information field for AM824 Compound
+ * at AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA)
+ */
+static int
+parse_stream_formation(u8 *buf, unsigned int len,
+		       struct snd_oxfw_stream_formation *formation,
+		       unsigned int *index)
+{
+	unsigned int e, channels, format;
+
+	/*
+	 * this module can support a hierarchy combination that:
+	 *  Root:	Audio and Music (0x90)
+	 *  Level 1:	AM824 Compound  (0x40)
+	 */
+	if ((buf[0] != 0x90) || (buf[1] != 0x40))
+		return -ENOSYS;
+
+	/* check the sampling rate */
+	for (*index = 0; *index < sizeof(avc_stream_rate_table); *index += 1) {
+		if (buf[2] == avc_stream_rate_table[*index])
+			break;
+	}
+	if (*index == sizeof(avc_stream_rate_table))
+		return -ENOSYS;
+
+	for (e = 0; e < buf[4]; e++) {
+		channels = buf[5 + e * 2];
+		format = buf[6 + e * 2];
+
+		switch (format) {
+		/* IEC 60958-3 */
+		case 0x00:
+		/* Multi Bit Linear Audio (Raw) */
+		case 0x06:
+			formation[*index].pcm += channels;
+			break;
+		/* MIDI comformant */
+		case 0x0d:
+			formation[*index].midi += channels;
+			break;
+		/* Multi Bit Linear Audio (DVD-audio) */
+		case 0x07:
+		/* IEC 61937-3 to 7 */
+		case 0x01:
+		case 0x02:
+		case 0x03:
+		case 0x04:
+		case 0x05:
+		/* One Bit Audio */
+		case 0x08:	/* (Plain) Raw */
+		case 0x09:	/* (Plain) SACD */
+		case 0x0a:	/* (Encoded) Raw */
+		case 0x0b:	/* (ENcoded) SACD */
+		/* High precision Multi-bit Linear Audio */
+		case 0x0c:
+		/* SMPTE Time-Code conformant */
+		case 0x0e:
+		/* Sample Count */
+		case 0x0f:
+		/* Anciliary Data */
+		case 0x10:
+		/* Synchronization Stream (Stereo Raw audio) */
+		case 0x40:
+		/* Don't care */
+		case 0xff:
+		default:
+			break;	/* not supported */
+		}
+	}
+
+	return 0;
+}
+
+static int
+assume_stream_formations(struct snd_oxfw *oxfw, enum avc_general_plug_dir dir,
+			 unsigned int pid, u8 *buf, unsigned int *len,
+			 struct snd_oxfw_stream_formation *formations)
+{
+	unsigned int i, pcm_channels, midi_channels;
+	int err;
+
+	/* get formation at current sampling rate */
+	err = avc_stream_get_format_single(oxfw->unit, dir, pid, buf, len);
+	if ((err < 0) || (err == 0x80) /* NOT IMPLEMENTED */)
+		goto end;
+
+	/* parse and set stream formation */
+	err = parse_stream_formation(buf, *len, formations, &i);
+	if (err < 0)
+		goto end;
+
+	pcm_channels = formations[i].pcm;
+	midi_channels = formations[i].midi;
+
+	/* apply the formation for each available sampling rate */
+	for (i = 0; i < SND_OXFW_RATE_TABLE_ENTRIES; i++) {
+		err = avc_general_inquiry_sig_fmt(oxfw->unit,
+						  snd_oxfw_rate_table[i],
+						  dir, pid);
+		if ((err < 0) || (err == 0x08) /* NOT IMPLEMENTED */)
+			continue;
+
+		formations[i].pcm = pcm_channels;
+		formations[i].midi = midi_channels;
+	}
+end:
+	return err;
+}
+
+static int
+fill_stream_formations(struct snd_oxfw *oxfw, enum avc_general_plug_dir dir,
+		       unsigned short pid)
+{
+	u8 *buf;
+	struct snd_oxfw_stream_formation *formations;
+	unsigned int i, len, eid;
+	int err;
+
+	buf = kmalloc(AVC_GENERIC_FRAME_MAXIMUM_BYTES, GFP_KERNEL);
+	if (buf == NULL)
+		return -ENOMEM;
+
+	if (dir == AVC_GENERAL_PLUG_DIR_IN)
+		formations = oxfw->rx_stream_formations;
+	else
+		formations = oxfw->tx_stream_formations;
+
+	/* initialize parameters here because of checking implementation */
+	eid = 0;
+	len = AVC_GENERIC_FRAME_MAXIMUM_BYTES;
+	memset(buf, 0, len);
+
+	/* get first entry */
+	err = avc_stream_get_format_list(oxfw->unit, dir, 0, buf, &len, eid);
+	if ((err < 0) || (len < 3)) {
+		/* LIST subfunction is not implemented */
+		err = assume_stream_formations(oxfw, dir, pid, buf, &len,
+					       formations);
+		goto end;
+	}
+
+	/* LIST subfunction is implemented */
+	do {
+		/* parse and set stream formation */
+		err = parse_stream_formation(buf, len, formations, &i);
+		if (err < 0)
+			continue;
+
+		/* get next entry */
+		len = AVC_GENERIC_FRAME_MAXIMUM_BYTES;
+		memset(buf, 0, len);
+		err = avc_stream_get_format_list(oxfw->unit, dir, 0,
+						 buf, &len, ++eid);
+		if ((err < 0) || (len < 3))
+			break;
+	} while (eid < SND_OXFW_RATE_TABLE_ENTRIES);
+end:
+	kfree(buf);
+	return err;
+}
+
+int snd_oxfw_stream_discover(struct snd_oxfw *oxfw)
+{
+	u8 plugs[AVC_PLUG_INFO_BUF_COUNT];
+	unsigned int i;
+	int err;
+
+	/* the number of plugs for isoc in/out, ext in/out  */
+	err = avc_general_get_plug_info(oxfw->unit, 0x1f, 0x07, 0x00, plugs);
+	if (err < 0)
+		goto end;
+	if ((plugs[0] == 0) || (plugs[0] == 0)) {
+		err = -EIO;
+		goto end;
+	}
+
+	/* use oPCR[0] */
+	err = fill_stream_formations(oxfw, AVC_GENERAL_PLUG_DIR_OUT, 0);
+	if (err < 0)
+		goto end;
+
+	/* use iPCR[0] */
+	err = fill_stream_formations(oxfw, AVC_GENERAL_PLUG_DIR_IN, 0);
+	if (err < 0)
+		goto end;
+
+	/* if its stream has MIDI conformant data channel, add one MIDI port */
+	for (i = 0; i < SND_OXFW_RATE_TABLE_ENTRIES; i++) {
+		if (oxfw->tx_stream_formations[i].midi > 0)
+			oxfw->midi_input_ports = 1;
+		else if (oxfw->rx_stream_formations[i].midi > 0)
+			oxfw->midi_output_ports = 1;
+	}
+end:
+	return err;
+}
-- 
1.8.3.2

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

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

* [PATCH 4/8] oxfw: Add connections and streams management
  2014-01-05 11:13 [RFC][PATCH 0/8] A new driver for OXFW970/971 based devices Takashi Sakamoto
                   ` (2 preceding siblings ...)
  2014-01-05 11:13 ` [PATCH 3/8] oxfw: Add some AV/C commands for channel formation of AMDTP stream Takashi Sakamoto
@ 2014-01-05 11:13 ` Takashi Sakamoto
  2014-01-05 11:13 ` [PATCH 5/8] oxfw: Add proc interface for debugging purpose Takashi Sakamoto
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-05 11:13 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

This commit adds management functionality for connections and streams.
OXFW970/971 use CMP to manage connections and uses AMDTP for streams.

They transmits AMDTP stream in non-blocking mode. This stream has a quirk for
'presentation timestamp'. The sequence of 'presentation timestamp' is invalid
even if header of packet shows 'CIP header with SYT field'. So this driver
can't reuse it for out-stream.

In this reason, this driver keep to send correct 'presentation timestamp' in
out-stream.

And I note that its fluctuation of 'presentation timestamp' is less when
out-stream exists (they receive AMDTP stream) than when no out-stream exists.

no out-stream exists:
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

out-stream exists:
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        |  10 ++
 sound/firewire/oxfw/oxfw.h        |  18 ++
 sound/firewire/oxfw/oxfw_stream.c | 357 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 385 insertions(+)

diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c
index c030fc0..f8320af 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -134,6 +134,14 @@ oxfw_probe(struct fw_unit *unit,
 	if (err < 0)
 		goto error;
 
+	err = snd_oxfw_stream_discover(oxfw);
+	if (err < 0)
+		goto error;
+
+	err = snd_oxfw_stream_init_duplex(oxfw);
+	if (err < 0)
+		goto error;
+
 	snd_card_set_dev(card, &unit->device);
 	err = snd_card_register(card);
 	if (err < 0) {
@@ -158,6 +166,7 @@ oxfw_update(struct fw_unit *unit)
 	struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
 
 	fcp_bus_reset(oxfw->unit);
+	snd_oxfw_stream_update_duplex(oxfw);
 }
 
 static void
@@ -165,6 +174,7 @@ oxfw_remove(struct fw_unit *unit)
 {
 	struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device);
 
+	snd_oxfw_stream_destroy_duplex(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
index e82717a..34ea0c8 100644
--- a/sound/firewire/oxfw/oxfw.h
+++ b/sound/firewire/oxfw/oxfw.h
@@ -24,6 +24,9 @@
 #include "../lib.h"
 #include "../fcp.h"
 #include "../amdtp.h"
+#include "../packets-buffer.h"
+#include "../iso-resources.h"
+#include "../cmp.h"
 
 #define	SND_OXFW_RATE_TABLE_ENTRIES	7
 struct snd_oxfw_stream_formation {
@@ -49,6 +52,11 @@ struct snd_oxfw {
 
 	unsigned int midi_input_ports;
 	unsigned int midi_output_ports;
+
+	struct cmp_connection out_conn;
+	struct amdtp_stream tx_stream;
+	struct cmp_connection in_conn;
+	struct amdtp_stream rx_stream;
 };
 
 /* AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) */
@@ -86,6 +94,16 @@ int snd_oxfw_set_rate(struct snd_oxfw *oxfw, unsigned int rate,
 		      enum avc_general_plug_dir dir);
 
 /* for AMDTP streaming */
+int snd_oxfw_stream_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_duplex(struct snd_oxfw *oxfw);
+int snd_oxfw_stream_start_duplex(struct snd_oxfw *oxfw,
+				  struct amdtp_stream *stream,
+				   unsigned int sampling_rate);
+int snd_oxfw_stream_stop_duplex(struct snd_oxfw *oxfw);
+void snd_oxfw_stream_update_duplex(struct snd_oxfw *oxfw);
+void snd_oxfw_stream_destroy_duplex(struct snd_oxfw *oxfw);
+
 int snd_oxfw_stream_discover(struct snd_oxfw *oxfw);
 
 #define SND_OXFW_DEV_ENTRY(vendor, model) \
diff --git a/sound/firewire/oxfw/oxfw_stream.c b/sound/firewire/oxfw/oxfw_stream.c
index 60ad700..ad2dd86 100644
--- a/sound/firewire/oxfw/oxfw_stream.c
+++ b/sound/firewire/oxfw/oxfw_stream.c
@@ -17,6 +17,15 @@
  * FORMAT INFORMATION' defined in 'AV/C Stream Format Information
  * Specification 1.1 (Apr 2005, 1394TA)'. So this module uses an assumption
  * that OXFW970 doesn't change its formation of channels in AMDTP stream.
+ *
+ * They transmit packet following to 'CIP header with  SYT field' defined in
+ * IEC 61883-1. But the sequence of value in SYT field is not compliant. So
+ * this module doesn't use the value of SYT field in in-packets. Then this
+ * module performs as a 'master of synchronization'. In this way, this module
+ * hopes the device to pick up the value of SYT value in out-packet which
+ * this module transmits. But the device seems not to use it for in-packet
+ * which the device transmits. Concluding, it doesn't matter whether this
+ * module perform as a master or slave.
  */
 const unsigned int snd_oxfw_rate_table[SND_OXFW_RATE_TABLE_ENTRIES] = {
 	[0] = 32000,
@@ -42,6 +51,354 @@ static const unsigned int avc_stream_rate_table[] = {
 	[6] = 0x07,
 };
 
+int snd_oxfw_stream_get_rate(struct snd_oxfw *oxfw, unsigned int *curr_rate)
+{
+	unsigned int tx_rate, rx_rate;
+	int err;
+
+	err = snd_oxfw_get_rate(oxfw, &tx_rate, AVC_GENERAL_PLUG_DIR_OUT);
+	if (err < 0)
+		goto end;
+
+	err = snd_oxfw_get_rate(oxfw, &rx_rate, AVC_GENERAL_PLUG_DIR_IN);
+	if (err < 0)
+		goto end;
+
+	*curr_rate = rx_rate;
+	if (rx_rate == tx_rate)
+		goto end;
+
+	/* synchronize receive stream rate to transmit stream rate */
+	err = snd_oxfw_set_rate(oxfw, rx_rate, AVC_GENERAL_PLUG_DIR_IN);
+end:
+	return err;
+}
+
+int snd_oxfw_stream_set_rate(struct snd_oxfw *oxfw, unsigned int rate)
+{
+	int err;
+
+	err = snd_oxfw_set_rate(oxfw, rate, AVC_GENERAL_PLUG_DIR_OUT);
+	if (err < 0)
+		goto end;
+
+	err = snd_oxfw_set_rate(oxfw, rate, AVC_GENERAL_PLUG_DIR_IN);
+end:
+	return err;
+}
+
+static int
+check_connection_used_by_others(struct snd_oxfw *oxfw,
+				struct amdtp_stream *s, bool *used)
+{
+	struct cmp_connection *conn;
+	int err;
+
+	if (s == &oxfw->tx_stream)
+		conn = &oxfw->out_conn;
+	else
+		conn = &oxfw->in_conn;
+
+	err = cmp_connection_check_used(conn, used);
+	if (err >= 0)
+		*used = (*used && !amdtp_stream_running(s));
+
+	return err;
+}
+
+static int
+init_stream(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;
+	}
+
+	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)
+		cmp_connection_destroy(conn);
+end:
+	return err;
+}
+
+
+static void
+stop_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream)
+{
+	if (amdtp_stream_running(stream))
+		amdtp_stream_stop(stream);
+
+	if (stream == &oxfw->tx_stream)
+		cmp_connection_break(&oxfw->out_conn);
+	else
+		cmp_connection_break(&oxfw->in_conn);
+
+	return;
+}
+
+static int
+start_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream,
+	     unsigned int sampling_rate)
+{
+	struct cmp_connection *conn;
+	unsigned int i, pcm_channels, midi_ports;
+	int err;
+
+	/* already running */
+	if (amdtp_stream_running(stream)) {
+		err = 0;
+		goto end;
+	}
+
+	for (i = 0; i < sizeof(snd_oxfw_rate_table); i++) {
+		if (snd_oxfw_rate_table[i] == sampling_rate)
+			break;
+	}
+	if (i == sizeof(snd_oxfw_rate_table)) {
+		err = -EINVAL;
+		goto end;
+	}
+
+	/* set stream formation */
+	if (stream == &oxfw->tx_stream) {
+		conn = &oxfw->out_conn;
+		pcm_channels = oxfw->tx_stream_formations[i].pcm;
+		midi_ports = oxfw->tx_stream_formations[i].midi * 8;
+	} else {
+		conn = &oxfw->in_conn;
+		pcm_channels = oxfw->rx_stream_formations[i].pcm;
+		midi_ports = oxfw->rx_stream_formations[i].midi * 8;
+	}
+	amdtp_stream_set_parameters(stream, sampling_rate,
+				    pcm_channels, midi_ports);
+
+	/*  establish connection via CMP */
+	err = cmp_connection_establish(conn,
+				amdtp_stream_get_max_payload(stream));
+	if (err < 0)
+		goto end;
+
+	/* start amdtp stream */
+	err = amdtp_stream_start(stream,
+				 conn->resources.channel,
+				 conn->speed);
+	if (err < 0)
+		stop_stream(oxfw, stream);
+
+	/* wait first callback */
+	if (!amdtp_stream_wait_callback(stream)) {
+		stop_stream(oxfw, stream);
+		err = -ETIMEDOUT;
+	}
+end:
+	return err;
+}
+
+static void
+update_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream)
+{
+	struct cmp_connection *conn;
+
+	if (&oxfw->tx_stream == stream)
+		conn = &oxfw->out_conn;
+	else
+		conn = &oxfw->in_conn;
+
+	if (cmp_connection_update(conn) < 0) {
+		amdtp_stream_pcm_abort(stream);
+		mutex_lock(&oxfw->mutex);
+		stop_stream(oxfw, stream);
+		mutex_unlock(&oxfw->mutex);
+		return;
+	}
+	amdtp_stream_update(stream);
+}
+
+static void
+destroy_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream)
+{
+	stop_stream(oxfw, stream);
+
+	if (stream == &oxfw->tx_stream)
+		cmp_connection_destroy(&oxfw->out_conn);
+	else
+		cmp_connection_destroy(&oxfw->in_conn);
+}
+
+static int
+get_roles(struct snd_oxfw *oxfw, enum cip_flags *sync_mode,
+	  struct amdtp_stream **master, struct amdtp_stream **slave)
+{
+	/* It doesn't matter. So this module perform as a sync master */
+	*sync_mode = 0x00;
+	*master = &oxfw->rx_stream;
+	*slave = &oxfw->tx_stream;
+
+	return 0;
+}
+
+int snd_oxfw_stream_init_duplex(struct snd_oxfw *oxfw)
+{
+	int err;
+
+	err = init_stream(oxfw, &oxfw->tx_stream);
+	if (err < 0)
+		goto end;
+
+	err = init_stream(oxfw, &oxfw->rx_stream);
+end:
+	return err;
+}
+
+int snd_oxfw_stream_start_duplex(struct snd_oxfw *oxfw,
+				  struct amdtp_stream *request,
+				  unsigned int rate)
+{
+	struct amdtp_stream *master, *slave;
+	enum cip_flags sync_mode;
+	unsigned int curr_rate;
+	bool slave_flag, used;
+	int err;
+
+	mutex_lock(&oxfw->mutex);
+
+	err = get_roles(oxfw, &sync_mode, &master, &slave);
+	if (err < 0)
+		goto end;
+
+	if ((request == slave) || amdtp_stream_running(slave))
+		slave_flag = true;
+	else
+		slave_flag = false;
+
+	/*
+	 * Considering JACK/FFADO streaming:
+	 * TODO: This can be removed hwdep functionality becomes popular.
+	 */
+	err = check_connection_used_by_others(oxfw, master, &used);
+	if (err < 0)
+		goto end;
+	if (used) {
+		dev_err(&oxfw->unit->device,
+			"connections established by others: %d\n",
+			used);
+		err = -EBUSY;
+		goto end;
+	}
+
+	/* get current rate */
+	err = snd_oxfw_stream_get_rate(oxfw, &curr_rate);
+	if (err < 0)
+		goto end;
+	if (rate == 0)
+		rate = curr_rate;
+
+	/* change sampling rate if needed */
+	if (rate != curr_rate) {
+		/* slave is just for MIDI stream */
+		if (amdtp_stream_running(slave) &&
+		    !amdtp_stream_pcm_running(slave))
+			amdtp_stream_stop(slave);
+
+		/* master is just for MIDI stream */
+		if (amdtp_stream_running(master) &&
+		    !amdtp_stream_pcm_running(master))
+			amdtp_stream_stop(master);
+
+		err = snd_oxfw_stream_set_rate(oxfw, rate);
+		if (err < 0)
+			goto end;
+	}
+
+	/* master should be always running */
+	if (!amdtp_stream_running(master)) {
+		err = start_stream(oxfw, master, rate);
+		if (err < 0) {
+			dev_err(&oxfw->unit->device,
+				"fail to run AMDTP master stream:%d\n", err);
+			goto end;
+		}
+	}
+
+	/* start slave if needed */
+	if (slave_flag && !amdtp_stream_running(slave)) {
+		err = start_stream(oxfw, slave, rate);
+		if (err < 0)
+			dev_err(&oxfw->unit->device,
+				"fail to run AMDTP slave stream:%d\n", err);
+	}
+end:
+	mutex_unlock(&oxfw->mutex);
+	return err;
+}
+
+int snd_oxfw_stream_stop_duplex(struct snd_oxfw *oxfw)
+{
+	struct amdtp_stream *master, *slave;
+	enum cip_flags sync_mode;
+	int err;
+
+	mutex_lock(&oxfw->mutex);
+
+	err = get_roles(oxfw, &sync_mode, &master, &slave);
+	if (err < 0)
+		goto end;
+
+	if (amdtp_stream_pcm_running(slave) ||
+	    amdtp_stream_midi_running(slave))
+		goto end;
+
+	stop_stream(oxfw, slave);
+
+	if (amdtp_stream_pcm_running(master) ||
+	    amdtp_stream_midi_running(master))
+		goto end;
+
+	stop_stream(oxfw, master);
+end:
+	mutex_unlock(&oxfw->mutex);
+	return err;
+}
+
+void snd_oxfw_stream_update_duplex(struct snd_oxfw *oxfw)
+{
+	mutex_lock(&oxfw->mutex);
+
+	update_stream(oxfw, &oxfw->rx_stream);
+	update_stream(oxfw, &oxfw->tx_stream);
+
+	mutex_unlock(&oxfw->mutex);
+}
+
+void snd_oxfw_stream_destroy_duplex(struct snd_oxfw *oxfw)
+{
+	mutex_lock(&oxfw->mutex);
+
+	if (amdtp_stream_pcm_running(&oxfw->rx_stream))
+		amdtp_stream_pcm_abort(&oxfw->rx_stream);
+	if (amdtp_stream_pcm_running(&oxfw->tx_stream))
+		amdtp_stream_pcm_abort(&oxfw->tx_stream);
+
+	destroy_stream(oxfw, &oxfw->rx_stream);
+	destroy_stream(oxfw, &oxfw->tx_stream);
+
+	mutex_unlock(&oxfw->mutex);
+}
+
 /*
  * See Table 6.16 - AM824 Stream Format
  *     Figure 6.19 - format_information field for AM824 Compound
-- 
1.8.3.2

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

* [PATCH 5/8] oxfw: Add proc interface for debugging purpose
  2014-01-05 11:13 [RFC][PATCH 0/8] A new driver for OXFW970/971 based devices Takashi Sakamoto
                   ` (3 preceding siblings ...)
  2014-01-05 11:13 ` [PATCH 4/8] oxfw: Add connections and streams management Takashi Sakamoto
@ 2014-01-05 11:13 ` Takashi Sakamoto
  2014-01-05 11:13 ` [PATCH 6/8] oxfw: Add MIDI interface Takashi Sakamoto
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-05 11:13 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

This commit adds proc interface to get information for debugging.
 - 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 | 61 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 67 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 5eb3d87..ab7865d 100644
--- a/sound/firewire/oxfw/Makefile
+++ b/sound/firewire/oxfw/Makefile
@@ -1,2 +1,2 @@
-snd-oxfw-objs := oxfw_command.o oxfw_stream.o oxfw.o
+snd-oxfw-objs := oxfw_command.o oxfw_stream.o oxfw_proc.o oxfw.o
 obj-m += snd-oxfw.o
diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c
index f8320af..8298cba 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -142,6 +142,8 @@ oxfw_probe(struct fw_unit *unit,
 	if (err < 0)
 		goto error;
 
+	snd_oxfw_proc_init(oxfw);
+
 	snd_card_set_dev(card, &unit->device);
 	err = snd_card_register(card);
 	if (err < 0) {
diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h
index 34ea0c8..7c02448e 100644
--- a/sound/firewire/oxfw/oxfw.h
+++ b/sound/firewire/oxfw/oxfw.h
@@ -20,6 +20,7 @@
 
 #include <sound/core.h>
 #include <sound/initval.h>
+#include <sound/info.h>
 
 #include "../lib.h"
 #include "../fcp.h"
@@ -106,6 +107,8 @@ void snd_oxfw_stream_destroy_duplex(struct snd_oxfw *oxfw);
 
 int snd_oxfw_stream_discover(struct snd_oxfw *oxfw);
 
+void snd_oxfw_proc_init(struct snd_oxfw *oxfw);
+
 #define SND_OXFW_DEV_ENTRY(vendor, model) \
 { \
 	.match_flags	= IEEE1394_MATCH_VENDOR_ID | \
diff --git a/sound/firewire/oxfw/oxfw_proc.c b/sound/firewire/oxfw/oxfw_proc.c
new file mode 100644
index 0000000..3c1aa96
--- /dev/null
+++ b/sound/firewire/oxfw/oxfw_proc.c
@@ -0,0 +1,61 @@
+/*
+ * oxfw_proc.c - a part of driver for OXFW970/971 based devices
+ *
+ * Copyright (c) 2013 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "./oxfw.h"
+
+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, "Output Stream from device:\n");
+	snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n");
+	formation = oxfw->tx_stream_formations;
+	for (i = 0; i < SND_OXFW_RATE_TABLE_ENTRIES; i++) {
+		snd_iprintf(buffer,
+			"\t%d\t%d\t%d\n", snd_oxfw_rate_table[i],
+			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;
+	for (i = 0; i < SND_OXFW_RATE_TABLE_ENTRIES; i++) {
+		snd_iprintf(buffer,
+			"\t%d\t%d\t%d\n", snd_oxfw_rate_table[i],
+			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;
+
+	if (snd_oxfw_stream_get_rate(oxfw, &rate) < 0)
+		return;
+	snd_iprintf(buffer, "Sampling rate: %d\n", rate);
+}
+
+void snd_oxfw_proc_init(struct snd_oxfw *oxfw)
+{
+	struct snd_info_entry *entry;
+
+	if (!snd_card_proc_new(oxfw->card, "#formation", &entry))
+		snd_info_set_text_ops(entry, oxfw, proc_read_formation);
+
+	if (!snd_card_proc_new(oxfw->card, "#clock", &entry))
+		snd_info_set_text_ops(entry, oxfw, proc_read_clock);
+
+	return;
+}
-- 
1.8.3.2

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

* [PATCH 6/8] oxfw: Add MIDI interface
  2014-01-05 11:13 [RFC][PATCH 0/8] A new driver for OXFW970/971 based devices Takashi Sakamoto
                   ` (4 preceding siblings ...)
  2014-01-05 11:13 ` [PATCH 5/8] oxfw: Add proc interface for debugging purpose Takashi Sakamoto
@ 2014-01-05 11:13 ` Takashi Sakamoto
  2014-01-05 11:13 ` [PATCH 7/8] oxfw: Add PCM interface Takashi Sakamoto
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-05 11:13 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

This commit adds a functionality to capture/playback MIDI messages.

When no AMDTP streams are running, this module starts AMDTP stream at current
sampling rate for MIDI stream.

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

diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile
index ab7865d..5430ec7 100644
--- a/sound/firewire/oxfw/Makefile
+++ b/sound/firewire/oxfw/Makefile
@@ -1,2 +1,3 @@
-snd-oxfw-objs := oxfw_command.o oxfw_stream.o oxfw_proc.o oxfw.o
+snd-oxfw-objs := oxfw_command.o oxfw_stream.o oxfw_proc.o oxfw_midi.o \
+		 oxfw.o
 obj-m += snd-oxfw.o
diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c
index 8298cba..ee817cd 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -144,6 +144,13 @@ 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_devices(oxfw);
+		if (err < 0)
+			goto error;
+	}
+
 	snd_card_set_dev(card, &unit->device);
 	err = snd_card_register(card);
 	if (err < 0) {
diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h
index 7c02448e..523ac40 100644
--- a/sound/firewire/oxfw/oxfw.h
+++ b/sound/firewire/oxfw/oxfw.h
@@ -21,6 +21,7 @@
 #include <sound/core.h>
 #include <sound/initval.h>
 #include <sound/info.h>
+#include <sound/rawmidi.h>
 
 #include "../lib.h"
 #include "../fcp.h"
@@ -109,6 +110,8 @@ int snd_oxfw_stream_discover(struct snd_oxfw *oxfw);
 
 void snd_oxfw_proc_init(struct snd_oxfw *oxfw);
 
+int snd_oxfw_create_midi_devices(struct snd_oxfw *oxfw);
+
 #define SND_OXFW_DEV_ENTRY(vendor, model) \
 { \
 	.match_flags	= IEEE1394_MATCH_VENDOR_ID | \
diff --git a/sound/firewire/oxfw/oxfw_midi.c b/sound/firewire/oxfw/oxfw_midi.c
new file mode 100644
index 0000000..41c14ec
--- /dev/null
+++ b/sound/firewire/oxfw/oxfw_midi.c
@@ -0,0 +1,135 @@
+/*
+ * oxfw_midi.c - a part of driver for OXFW970/971 based devices
+ *
+ * Copyright (c) 2013 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "oxfw.h"
+
+static int midi_capture_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_oxfw *oxfw = substream->rmidi->private_data;
+	return snd_oxfw_stream_start_duplex(oxfw, &oxfw->tx_stream, 0);
+}
+
+static int midi_playback_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_oxfw *oxfw = substream->rmidi->private_data;
+	return snd_oxfw_stream_start_duplex(oxfw, &oxfw->rx_stream, 0);
+}
+
+static int midi_close(struct snd_rawmidi_substream *substream)
+{
+	struct snd_oxfw *oxfw = substream->rmidi->private_data;
+	snd_oxfw_stream_stop_duplex(oxfw);
+	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);
+
+	return;
+}
+
+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);
+
+	return;
+}
+
+static struct snd_rawmidi_ops midi_capture_ops = {
+	.open		= midi_capture_open,
+	.close		= midi_close,
+	.trigger	= midi_capture_trigger,
+};
+
+static struct snd_rawmidi_ops midi_playback_ops = {
+	.open		= midi_playback_open,
+	.close		= midi_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_devices(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;
+}
-- 
1.8.3.2

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

* [PATCH 7/8] oxfw: Add PCM interface
  2014-01-05 11:13 [RFC][PATCH 0/8] A new driver for OXFW970/971 based devices Takashi Sakamoto
                   ` (5 preceding siblings ...)
  2014-01-05 11:13 ` [PATCH 6/8] oxfw: Add MIDI interface Takashi Sakamoto
@ 2014-01-05 11:13 ` Takashi Sakamoto
  2014-01-05 11:13 ` [PATCH 8/8] oxfw: Add hwdep interface Takashi Sakamoto
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-05 11:13 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

This commit adds a functionality to capture/playback PCM samples.

Currently this driver has no functionality to check current source of clock.
When AMDTP stream is already running for PCM, available sampling rate is
limited at current one.

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

diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile
index 5430ec7..d119c06 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_proc.o oxfw_midi.o \
-		 oxfw.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 ee817cd..d636463 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -151,6 +151,10 @@ oxfw_probe(struct fw_unit *unit,
 			goto error;
 	}
 
+	err = snd_oxfw_create_pcm_devices(oxfw);
+	if (err < 0)
+		goto error;
+
 	snd_card_set_dev(card, &unit->device);
 	err = snd_card_register(card);
 	if (err < 0) {
diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h
index 523ac40..4be43ae 100644
--- a/sound/firewire/oxfw/oxfw.h
+++ b/sound/firewire/oxfw/oxfw.h
@@ -22,6 +22,8 @@
 #include <sound/initval.h>
 #include <sound/info.h>
 #include <sound/rawmidi.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
 
 #include "../lib.h"
 #include "../fcp.h"
@@ -112,6 +114,8 @@ void snd_oxfw_proc_init(struct snd_oxfw *oxfw);
 
 int snd_oxfw_create_midi_devices(struct snd_oxfw *oxfw);
 
+int snd_oxfw_create_pcm_devices(struct snd_oxfw *oxfw);
+
 #define SND_OXFW_DEV_ENTRY(vendor, model) \
 { \
 	.match_flags	= IEEE1394_MATCH_VENDOR_ID | \
diff --git a/sound/firewire/oxfw/oxfw_pcm.c b/sound/firewire/oxfw/oxfw_pcm.c
new file mode 100644
index 0000000..dbd5731
--- /dev/null
+++ b/sound/firewire/oxfw/oxfw_pcm.c
@@ -0,0 +1,412 @@
+/*
+ * oxfw_pcm.c - a part of driver for OXFW970/971 based devices
+ *
+ * Copyright (c) 2013 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "./oxfw.h"
+
+static int
+hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule,
+	     struct snd_oxfw *oxfw,
+	     struct snd_oxfw_stream_formation *formations)
+{
+	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_RATE_TABLE_ENTRIES; i++) {
+		/* entry is invalid */
+		if (formations[i].pcm == 0)
+			continue;
+
+		if (!snd_interval_test(c, formations[i].pcm))
+			continue;
+
+		t.min = min(t.min, snd_oxfw_rate_table[i]);
+		t.max = max(t.max, snd_oxfw_rate_table[i]);
+
+	}
+	return snd_interval_refine(r, &t);
+}
+
+static int
+hw_rule_channels(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule,
+		 struct snd_oxfw *oxfw,
+		 struct snd_oxfw_stream_formation *formations)
+{
+	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);
+	struct snd_interval t = {
+		.min = UINT_MAX, .max = 0, .integer = 1
+	};
+
+	unsigned int i;
+
+	for (i = 0; i < SND_OXFW_RATE_TABLE_ENTRIES; i++) {
+		/* entry is invalid */
+		if (formations[i].pcm == 0)
+			continue;
+
+		if (!snd_interval_test(r, snd_oxfw_rate_table[i]))
+			continue;
+
+		t.min = min(t.min, formations[i].pcm);
+		t.max = max(t.max, formations[i].pcm);
+	}
+
+	return snd_interval_refine(c, &t);
+}
+
+static inline int
+hw_rule_capture_rate(struct snd_pcm_hw_params *params,
+				struct snd_pcm_hw_rule *rule)
+{
+	struct snd_oxfw *oxfw = rule->private;
+	return hw_rule_rate(params, rule, oxfw,
+				oxfw->tx_stream_formations);
+}
+
+static inline int
+hw_rule_playback_rate(struct snd_pcm_hw_params *params,
+				struct snd_pcm_hw_rule *rule)
+{
+	struct snd_oxfw *oxfw = rule->private;
+	return hw_rule_rate(params, rule, oxfw,
+				oxfw->rx_stream_formations);
+}
+
+static inline int
+hw_rule_capture_channels(struct snd_pcm_hw_params *params,
+				struct snd_pcm_hw_rule *rule)
+{
+	struct snd_oxfw *oxfw = rule->private;
+	return hw_rule_channels(params, rule, oxfw,
+				oxfw->tx_stream_formations);
+}
+
+static inline int
+hw_rule_playback_channels(struct snd_pcm_hw_params *params,
+				struct snd_pcm_hw_rule *rule)
+{
+	struct snd_oxfw *oxfw = rule->private;
+	return hw_rule_channels(params, rule, oxfw,
+				oxfw->rx_stream_formations);
+}
+
+static void
+prepare_channels(struct snd_pcm_hardware *hw,
+	  struct snd_oxfw_stream_formation *formations)
+{
+	unsigned int i;
+
+	for (i = 0; i < SND_OXFW_RATE_TABLE_ENTRIES; i++) {
+		/* entry has no PCM channels */
+		if (formations[i].pcm == 0)
+			continue;
+
+		hw->channels_min = min(hw->channels_min, formations[i].pcm);
+		hw->channels_max = max(hw->channels_max, formations[i].pcm);
+	}
+
+	return;
+}
+
+static void
+prepare_rates(struct snd_pcm_hardware *hw,
+	  struct snd_oxfw_stream_formation *formations)
+{
+	unsigned int i;
+
+	for (i = 0; i < SND_OXFW_RATE_TABLE_ENTRIES; i++) {
+		/* entry has no PCM channels */
+		if (formations[i].pcm == 0)
+			continue;
+
+		hw->rate_min = min(hw->rate_min, snd_oxfw_rate_table[i]);
+		hw->rate_max = max(hw->rate_max, snd_oxfw_rate_table[i]);
+		hw->rates |= snd_pcm_rate_to_rate_bit(snd_oxfw_rate_table[i]);
+	}
+
+	return;
+}
+
+static int
+pcm_init_hw_params(struct snd_oxfw *oxfw,
+			struct snd_pcm_substream *substream)
+{
+	int err;
+
+	static const struct snd_pcm_hardware hw = {
+		.info = SNDRV_PCM_INFO_MMAP |
+			SNDRV_PCM_INFO_BATCH |
+			SNDRV_PCM_INFO_INTERLEAVED |
+			SNDRV_PCM_INFO_SYNC_START |
+			SNDRV_PCM_INFO_FIFO_IN_FRAMES |
+			SNDRV_PCM_INFO_JOINT_DUPLEX |
+			/* for Open Sound System compatibility */
+			SNDRV_PCM_INFO_MMAP_VALID |
+			SNDRV_PCM_INFO_BLOCK_TRANSFER,
+		/* set up later */
+		.rates = 0,
+		.rate_min = UINT_MAX,
+		.rate_max = 0,
+		/* set up later */
+		.channels_min = UINT_MAX,
+		.channels_max = 0,
+		.buffer_bytes_max = 1024 * 1024 * 1024,
+		.period_bytes_min = 256,
+		.period_bytes_max = 1024 * 1024 * 1024 / 2,
+		.periods_min = 2,
+		.periods_max = 32,
+		.fifo_size = 0,
+	};
+
+	substream->runtime->hw = hw;
+	substream->runtime->delay = substream->runtime->hw.fifo_size;
+
+	/* add rule between channels and sampling rate */
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		prepare_rates(&substream->runtime->hw,
+			      oxfw->tx_stream_formations);
+		prepare_channels(&substream->runtime->hw,
+				 oxfw->tx_stream_formations);
+		substream->runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE;
+		snd_pcm_hw_rule_add(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_CHANNELS,
+				hw_rule_capture_channels, oxfw,
+				SNDRV_PCM_HW_PARAM_RATE, -1);
+		snd_pcm_hw_rule_add(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_RATE,
+				hw_rule_capture_rate, oxfw,
+				SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	} else {
+		prepare_rates(&substream->runtime->hw,
+			      oxfw->rx_stream_formations);
+		prepare_channels(&substream->runtime->hw,
+				 oxfw->rx_stream_formations);
+		substream->runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS;
+		snd_pcm_hw_rule_add(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_CHANNELS,
+				hw_rule_playback_channels, oxfw,
+				SNDRV_PCM_HW_PARAM_RATE, -1);
+		snd_pcm_hw_rule_add(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_RATE,
+				hw_rule_playback_rate, oxfw,
+				SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	}
+
+	/* AM824 in IEC 61883-6 can deliver 24bit data */
+	err = snd_pcm_hw_constraint_msbits(substream->runtime, 0, 32, 24);
+	if (err < 0)
+		goto end;
+
+	/*
+	 * AMDTP functionality in firewire-lib require periods to be aligned to
+	 * 16 bit, or 24bit inner 32bit.
+	 */
+	err = snd_pcm_hw_constraint_step(substream->runtime, 0,
+					 SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
+	if (err < 0)
+		goto end;
+
+	/* time for period constraint */
+	err = snd_pcm_hw_constraint_minmax(substream->runtime,
+					SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+					500, UINT_MAX);
+	if (err < 0)
+		goto end;
+end:
+	return err;
+}
+
+static int
+pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
+	unsigned int sampling_rate;
+	int err;
+
+	err = pcm_init_hw_params(oxfw, substream);
+	if (err < 0)
+		goto end;
+
+	/*
+	 * When any PCM stream are running, the available sampling rate is
+	 * limited at current sampling rate.
+	 */
+	if (amdtp_stream_pcm_running(&oxfw->tx_stream) ||
+	    amdtp_stream_pcm_running(&oxfw->rx_stream)) {
+		err = snd_oxfw_stream_get_rate(oxfw, &sampling_rate);
+		if (err < 0)
+			goto end;
+
+		substream->runtime->hw.rate_min = sampling_rate;
+		substream->runtime->hw.rate_max = sampling_rate;
+	}
+
+	snd_pcm_set_sync(substream);
+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)
+{
+	return snd_pcm_lib_alloc_vmalloc_buffer(substream,
+						params_buffer_bytes(hw_params));
+}
+
+static int
+pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_oxfw *oxfw = substream->private_data;
+
+	snd_oxfw_stream_stop_duplex(oxfw);
+
+	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_duplex(oxfw, &oxfw->tx_stream,
+					    runtime->rate);
+	if (err < 0)
+		goto end;
+
+	amdtp_stream_set_pcm_format(&oxfw->tx_stream, runtime->format);
+	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_duplex(oxfw, &oxfw->rx_stream,
+					    runtime->rate);
+	if (err < 0)
+		goto end;
+
+	amdtp_stream_set_pcm_format(&oxfw->rx_stream, runtime->format);
+	amdtp_stream_pcm_prepare(&oxfw->rx_stream);
+end:
+	return err;
+}
+
+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;
+
+	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_capture_pointer(struct snd_pcm_substream *sbstrm)
+{
+	struct snd_oxfw *oxfw = sbstrm->private_data;
+	return amdtp_stream_pcm_pointer(&oxfw->tx_stream);
+}
+static snd_pcm_uframes_t
+pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
+{
+	struct snd_oxfw *oxfw = sbstrm->private_data;
+	return amdtp_stream_pcm_pointer(&oxfw->rx_stream);
+}
+
+static struct snd_pcm_ops pcm_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_capture_prepare,
+	.trigger	= pcm_capture_trigger,
+	.pointer	= pcm_capture_pointer,
+	.page		= snd_pcm_lib_get_vmalloc_page,
+};
+static struct snd_pcm_ops pcm_playback_ops = {
+	.open		= pcm_open,
+	.close		= pcm_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= pcm_hw_params,
+	.hw_free	= pcm_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,
+};
+
+int snd_oxfw_create_pcm_devices(struct snd_oxfw *oxfw)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(oxfw->card, oxfw->card->driver, 0, 1, 1, &pcm);
+	if (err < 0)
+		goto end;
+
+	pcm->private_data = oxfw;
+	snprintf(pcm->name, sizeof(pcm->name),
+		 "%s PCM", oxfw->card->shortname);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_playback_ops);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops);
+
+end:
+	return err;
+}
-- 
1.8.3.2

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

* [PATCH 8/8] oxfw: Add hwdep interface
  2014-01-05 11:13 [RFC][PATCH 0/8] A new driver for OXFW970/971 based devices Takashi Sakamoto
                   ` (6 preceding siblings ...)
  2014-01-05 11:13 ` [PATCH 7/8] oxfw: Add PCM interface Takashi Sakamoto
@ 2014-01-05 11:13 ` Takashi Sakamoto
  2014-01-05 19:46 ` [RFC][PATCH 0/8] A new driver for OXFW970/971 based devices Takashi Iwai
  2014-01-10 15:29 ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Takashi Sakamoto
  9 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-05 11:13 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

This interface is designed for mixer/control application. To use hwdep
interface, the 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/oxfw/Makefile      |   2 +-
 sound/firewire/oxfw/oxfw.c        |   5 +
 sound/firewire/oxfw/oxfw.h        |  13 +++
 sound/firewire/oxfw/oxfw_hwdep.c  | 197 ++++++++++++++++++++++++++++++++++++++
 sound/firewire/oxfw/oxfw_midi.c   |  25 ++++-
 sound/firewire/oxfw/oxfw_pcm.c    |  13 ++-
 sound/firewire/oxfw/oxfw_stream.c |  39 ++++++++
 9 files changed, 293 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 d69c0b6..1894e32 100644
--- a/include/uapi/sound/firewire.h
+++ b/include/uapi/sound/firewire.h
@@ -54,7 +54,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/oxfw/Makefile b/sound/firewire/oxfw/Makefile
index d119c06..c325245 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_proc.o oxfw_midi.o \
-		 oxfw_pcm.o oxfw.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 d636463..b09d8ae 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -129,6 +129,7 @@ oxfw_probe(struct fw_unit *unit,
 	oxfw->card_index = -1;
 	mutex_init(&oxfw->mutex);
 	spin_lock_init(&oxfw->lock);
+	init_waitqueue_head(&oxfw->hwdep_wait);
 
 	err = name_device(oxfw, entry->vendor_id);
 	if (err < 0)
@@ -155,6 +156,10 @@ oxfw_probe(struct fw_unit *unit,
 	if (err < 0)
 		goto error;
 
+	err = snd_oxfw_create_hwdep_device(oxfw);
+	if (err < 0)
+		goto error;
+
 	snd_card_set_dev(card, &unit->device);
 	err = snd_card_register(card);
 	if (err < 0) {
diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h
index 4be43ae..01c9c32 100644
--- a/sound/firewire/oxfw/oxfw.h
+++ b/sound/firewire/oxfw/oxfw.h
@@ -24,6 +24,8 @@
 #include <sound/rawmidi.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
+#include <sound/hwdep.h>
+#include <sound/firewire.h>
 
 #include "../lib.h"
 #include "../fcp.h"
@@ -61,6 +63,11 @@ struct snd_oxfw {
 	struct amdtp_stream tx_stream;
 	struct cmp_connection in_conn;
 	struct amdtp_stream rx_stream;
+
+	/* for uapi */
+	int dev_lock_count;
+	bool dev_lock_changed;
+	wait_queue_head_t hwdep_wait;
 };
 
 /* AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) */
@@ -110,12 +117,18 @@ void snd_oxfw_stream_destroy_duplex(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);
+
 void snd_oxfw_proc_init(struct snd_oxfw *oxfw);
 
 int snd_oxfw_create_midi_devices(struct snd_oxfw *oxfw);
 
 int snd_oxfw_create_pcm_devices(struct snd_oxfw *oxfw);
 
+int snd_oxfw_create_hwdep_device(struct snd_oxfw *oxfw);
+
 #define SND_OXFW_DEV_ENTRY(vendor, model) \
 { \
 	.match_flags	= IEEE1394_MATCH_VENDOR_ID | \
diff --git a/sound/firewire/oxfw/oxfw_hwdep.c b/sound/firewire/oxfw/oxfw_hwdep.c
new file mode 100644
index 0000000..4289626
--- /dev/null
+++ b/sound/firewire/oxfw/oxfw_hwdep.c
@@ -0,0 +1,197 @@
+/*
+ * oxfw_hwdep.c - a part of driver for OXFW970/971 based devices
+ *
+ * Copyright (c) 2013 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+/*
+ * 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_BEBOB;
+	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
+
+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,
+};
+
+int snd_oxfw_create_hwdep_device(struct snd_oxfw *oxfw)
+{
+	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 41c14ec..d18c195 100644
--- a/sound/firewire/oxfw/oxfw_midi.c
+++ b/sound/firewire/oxfw/oxfw_midi.c
@@ -11,19 +11,40 @@
 static int midi_capture_open(struct snd_rawmidi_substream *substream)
 {
 	struct snd_oxfw *oxfw = substream->rmidi->private_data;
-	return snd_oxfw_stream_start_duplex(oxfw, &oxfw->tx_stream, 0);
+	int err;
+
+	err = snd_oxfw_stream_lock_try(oxfw);
+	if (err < 0)
+		goto end;
+
+	err = snd_oxfw_stream_start_duplex(oxfw, &oxfw->tx_stream, 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;
-	return snd_oxfw_stream_start_duplex(oxfw, &oxfw->rx_stream, 0);
+	int err;
+
+	err = snd_oxfw_stream_lock_try(oxfw);
+	if (err < 0)
+		goto end;
+
+	err = snd_oxfw_stream_start_duplex(oxfw, &oxfw->rx_stream, 0);
+	if (err < 0)
+		snd_oxfw_stream_lock_release(oxfw);
+end:
+	return err;
 }
 
 static int midi_close(struct snd_rawmidi_substream *substream)
 {
 	struct snd_oxfw *oxfw = substream->rmidi->private_data;
 	snd_oxfw_stream_stop_duplex(oxfw);
+	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 dbd5731..b911ddc 100644
--- a/sound/firewire/oxfw/oxfw_pcm.c
+++ b/sound/firewire/oxfw/oxfw_pcm.c
@@ -236,10 +236,14 @@ pcm_open(struct snd_pcm_substream *substream)
 	unsigned int sampling_rate;
 	int err;
 
-	err = pcm_init_hw_params(oxfw, substream);
+	err = snd_oxfw_stream_lock_try(oxfw);
 	if (err < 0)
 		goto end;
 
+	err = pcm_init_hw_params(oxfw, substream);
+	if (err < 0)
+		goto err_locked;
+
 	/*
 	 * When any PCM stream are running, the available sampling rate is
 	 * limited at current sampling rate.
@@ -248,7 +252,7 @@ pcm_open(struct snd_pcm_substream *substream)
 	    amdtp_stream_pcm_running(&oxfw->rx_stream)) {
 		err = snd_oxfw_stream_get_rate(oxfw, &sampling_rate);
 		if (err < 0)
-			goto end;
+			goto err_locked;
 
 		substream->runtime->hw.rate_min = sampling_rate;
 		substream->runtime->hw.rate_max = sampling_rate;
@@ -257,11 +261,16 @@ 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 ad2dd86..5a06fe4 100644
--- a/sound/firewire/oxfw/oxfw_stream.c
+++ b/sound/firewire/oxfw/oxfw_stream.c
@@ -598,3 +598,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

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

* Re: [RFC][PATCH 0/8] A new driver for OXFW970/971 based devices
  2014-01-05 11:13 [RFC][PATCH 0/8] A new driver for OXFW970/971 based devices Takashi Sakamoto
                   ` (7 preceding siblings ...)
  2014-01-05 11:13 ` [PATCH 8/8] oxfw: Add hwdep interface Takashi Sakamoto
@ 2014-01-05 19:46 ` Takashi Iwai
  2014-01-06  8:33   ` Takashi Sakamoto
  2014-01-10 15:29 ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Takashi Sakamoto
  9 siblings, 1 reply; 38+ messages in thread
From: Takashi Iwai @ 2014-01-05 19:46 UTC (permalink / raw)
  To: Takashi Sakamoto; +Cc: alsa-devel, clemens, ffado-devel

At Sun,  5 Jan 2014 20:13:29 +0900,
Takashi Sakamoto wrote:
> 
> This series of patch is based on my previous series below:
> http://mailman.alsa-project.org/pipermail/alsa-devel/2013-December/070424.html
> 
> This series of patch add a new driver, snd-oxfw, for OXFW970/971 based devices.
> These chipsets are already supported by snd-firewire-speakers but this new
> driver aim to support recording equipment.
> 
> Current supported devices:
>  * Behringer F-Control Audio 202
>  * Mackie Onyx-i series (former model)
>  * Mackie Onyx Satellite
> 
> Devices possible to be supported if identifying IDs:
>  * Mackie, d.2 pro
>  * Mackie, d.4 pro
>  * Mackie, U.420
>  * Mackie, U.420d
>  * Mackie, Tapco Link.Firewire
> 
> I worked with Behringer F-Control Audio 202. So I want someone to test with the other devices, especially about some quirks.
> 
> 
> Clemens, would you gime me your opinions about issues below?
> And if you know something about this chipset, would you share the information?
> 
> 1.snd-firewire-speakers and snd-oxfw
> With this series of patches, ALSA has two drivers for the same chipsets.
> Merging these two drivers is a bit difficult because:
>  - snd-firewire-speakers has control interface but snd-oxfw don't
>  - snd-firewire-speakers don't support in-stream but snd-oxfw supports

I don't see why these make things difficult to merge?
They sound rather like the functionalities don't conflict.

> 2.currently snd-oxfw works with streams in SYT-Match
> Due to invalid sequence of 'presentation timestamp' in transmitted packets.
> In detail, please see 4th patch.
> 
> 3.assumption of channnel formation
> Some commands may not be implemented.
> In detail, please see 3rd patch.
> 
> 4.don't check source of clock
> All of supported devices (including can be) don't have the functionality to switch source of clock. So I want to omit this.
> 
> For issue 3 and 4, Descriptor mechanism in 'Enhancements to the AV/C General
> Specification 3.0 Version 1.1' may be a best solution but it costs much to implement against the number of available devices. I want to avoid this huge mechanism if possible.

Note that since I haven't seen any ack from Clemens, I didn't take
previous patches yet.  And it's a bit too late for 3.14, we have
plenty of time for review and discussion for 3.15.


thanks,

Takashi

> 
> 
> Regards
> 
> Takashi Sakamoto
> 
> Takashi Sakamoto (8):
>   oxfw: Add skelton for OXFW970/971 based devices
>   oxfw: Read firmware version to name card
>   oxfw: Add some AV/C commands for channel formation of AMDTP stream
>   oxfw: Add connections and streams management
>   oxfw: Add proc interface for debugging purpose
>   oxfw: Add MIDI interface
>   oxfw: Add PCM interface
>   oxfw: Add hwdep interface
> 
>  include/uapi/sound/asound.h        |   3 +-
>  include/uapi/sound/firewire.h      |   3 +-
>  sound/firewire/Kconfig             |  17 +
>  sound/firewire/Makefile            |   1 +
>  sound/firewire/oxfw/Makefile       |   3 +
>  sound/firewire/oxfw/oxfw.c         | 243 ++++++++++++++
>  sound/firewire/oxfw/oxfw.h         | 140 ++++++++
>  sound/firewire/oxfw/oxfw_command.c | 161 ++++++++++
>  sound/firewire/oxfw/oxfw_hwdep.c   | 197 ++++++++++++
>  sound/firewire/oxfw/oxfw_midi.c    | 156 +++++++++
>  sound/firewire/oxfw/oxfw_pcm.c     | 421 ++++++++++++++++++++++++
>  sound/firewire/oxfw/oxfw_proc.c    |  61 ++++
>  sound/firewire/oxfw/oxfw_stream.c  | 639 +++++++++++++++++++++++++++++++++++++
>  13 files changed, 2043 insertions(+), 2 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_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
> 
> -- 
> 1.8.3.2
> 

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

* Re: [RFC][PATCH 0/8] A new driver for OXFW970/971 based devices
  2014-01-05 19:46 ` [RFC][PATCH 0/8] A new driver for OXFW970/971 based devices Takashi Iwai
@ 2014-01-06  8:33   ` Takashi Sakamoto
  2014-01-07 10:07     ` Takashi Iwai
  0 siblings, 1 reply; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-06  8:33 UTC (permalink / raw)
  To: Takashi Iwai; +Cc: alsa-devel, clemens, ffado-devel

Hi Iwai-san,

 >> 1.snd-firewire-speakers and snd-oxfw
 > I don't see why these make things difficult to merge?
 > They sound rather like the functionalities don't conflict.

I think it better not to touch existed drivers unless they have some bugs.

Actually I can merge two drivers with some selection statements, many 
changes for card structure and for PCM functionality, many codes for 
MIDI and hwdep functionaloty. But for snd-firewire-speakers these 
modifications are needless and not due to bugs.

...On the other hand, I also think it good to merge these two drivers 
because they support the same chipset. But this idea have a risk to put 
some new bugs into snd-firewire-speakers.


 > Note that since I haven't seen any ack from Clemens, I didn't take
 > previous patches yet.

Yes. I've posted my patches with RFC, without merge-request. I describe 
the reason.

Devices which snd-fireworks, snd-bebob and snd-oxfw supports are 
categorized in 'GenericAVC' by FFADO. This means the devices are 
controlled by the same way, AV/C commands. At first, I considered to 
develop 'snd-genericavc', which perform like OS X implementation. But 
there were some anxieties for device quirks.

In this reason, I decide to develop them in the same time. As a result, 
I confirm each chipset/firmware has it own quirk. So now I have a 
confidence that it's difficult to develop 'snd-genericavc'.


 >  And it's a bit too late for 3.14, we have plenty of time for review 
and discussion for 3.15.

Currently I have no will to merge my patches because I need more 
discussions between ALSA/FFADO developers.

My patches add ALSA to handle devices which FFADO supports  for its 
streaming functionality. It may bring a bit complexity to users. I need 
to adjust two projects in advance of merge-request.


Thanks

Takashi Sakamoto
o-takashi@sakamocchi.jp

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

* Re: [RFC][PATCH 0/8] A new driver for OXFW970/971 based devices
  2014-01-06  8:33   ` Takashi Sakamoto
@ 2014-01-07 10:07     ` Takashi Iwai
  0 siblings, 0 replies; 38+ messages in thread
From: Takashi Iwai @ 2014-01-07 10:07 UTC (permalink / raw)
  To: Takashi Sakamoto; +Cc: alsa-devel, clemens, ffado-devel

At Mon, 06 Jan 2014 17:33:47 +0900,
Takashi Sakamoto wrote:
> 
> Hi Iwai-san,
> 
>  >> 1.snd-firewire-speakers and snd-oxfw
>  > I don't see why these make things difficult to merge?
>  > They sound rather like the functionalities don't conflict.
> 
> I think it better not to touch existed drivers unless they have some bugs.
> 
> Actually I can merge two drivers with some selection statements, many 
> changes for card structure and for PCM functionality, many codes for 
> MIDI and hwdep functionaloty. But for snd-firewire-speakers these 
> modifications are needless and not due to bugs.
> 
> ...On the other hand, I also think it good to merge these two drivers 
> because they support the same chipset. But this idea have a risk to put 
> some new bugs into snd-firewire-speakers.

Yes, having two different drivers for the same hardware leads nothing
but confusion, judging from the past experience.

>  > Note that since I haven't seen any ack from Clemens, I didn't take
>  > previous patches yet.
> 
> Yes. I've posted my patches with RFC, without merge-request. I describe 
> the reason.

OK.

> Devices which snd-fireworks, snd-bebob and snd-oxfw supports are 
> categorized in 'GenericAVC' by FFADO. This means the devices are 
> controlled by the same way, AV/C commands. At first, I considered to 
> develop 'snd-genericavc', which perform like OS X implementation. But 
> there were some anxieties for device quirks.
> 
> In this reason, I decide to develop them in the same time. As a result, 
> I confirm each chipset/firmware has it own quirk. So now I have a 
> confidence that it's difficult to develop 'snd-genericavc'.
> 
> 
>  >  And it's a bit too late for 3.14, we have plenty of time for review 
> and discussion for 3.15.
> 
> Currently I have no will to merge my patches because I need more 
> discussions between ALSA/FFADO developers.
> 
> My patches add ALSA to handle devices which FFADO supports  for its 
> streaming functionality. It may bring a bit complexity to users. I need 
> to adjust two projects in advance of merge-request.

Fair enough.


thanks,

Takashi

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

* [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI
  2014-01-05 11:13 [RFC][PATCH 0/8] A new driver for OXFW970/971 based devices Takashi Sakamoto
                   ` (8 preceding siblings ...)
  2014-01-05 19:46 ` [RFC][PATCH 0/8] A new driver for OXFW970/971 based devices Takashi Iwai
@ 2014-01-10 15:29 ` Takashi Sakamoto
  2014-01-10 15:29   ` [PATCH 01/13] speakers: move to its own directory Takashi Sakamoto
                     ` (13 more replies)
  9 siblings, 14 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-10 15:29 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

In previous series of patch, I showed an idea to add a new driver for
OXFW970/971. This driver can support some recording equipments.
http://mailman.alsa-project.org/pipermail/alsa-devel/2014-January/070705.html

ALSA already has a driver, firewire-speakers for these chipsets. But it
supports PCM playback only. The devices need support for capture/playback of
PCM/MIDI.

According to maintainer's advice, I'll add these functionality to
firewire-speakers. Additionally, like the other firewire drivers, I'll add
hwdep interface.

As a result, this driver can support:
 * Oxford Semiconductor OXFW970/971.
 * Griffin Firewave
 * LaCie Firewire Speakers
 * Behringer F-Control Audio 202
 * Mackie Onyx-i series (former model)
 * Mackie Onyx Satellite

This driver can also support but IDs are unknown:
 * Mackie d.2 pro
 * Mackie d.4 pro
 * Mackie U.420
 * Mackie U.420d
 * Mackie Tapco Link.Firewire

Takashi Sakamoto (13):
  speakers: move to its own directory
  speakers: Split stream functionality to a new file and add a header
    file
  speakers: Split PCM functionality to a new file
  speakers: Split control functionality to a new file
  speakers: Change the way to name card
  speakers: Change the way to make PCM rules/constraints
  speakers: Add proc interface for debugging purpose
  speakers: Change the way to start stream
  speakers: Add some AV/C commands to get stream formation and supported
    sampling rates
  speakers: Add support for Behringer/Mackie devices
  speakers: Add support AMDTP in-stream and PCM capture
  speakers: Add support for capture/playback MIDI messages
  speakers: Add hwdep interface

 include/uapi/sound/asound.h                |   3 +-
 include/uapi/sound/firewire.h              |   3 +-
 sound/firewire/Kconfig                     |  11 +-
 sound/firewire/Makefile                    |   3 +-
 sound/firewire/speakers.c                  | 801 -----------------------------
 sound/firewire/speakers/Makefile           |   3 +
 sound/firewire/speakers/speakers.c         | 272 ++++++++++
 sound/firewire/speakers/speakers.h         | 140 +++++
 sound/firewire/speakers/speakers_command.c | 162 ++++++
 sound/firewire/speakers/speakers_control.c | 283 ++++++++++
 sound/firewire/speakers/speakers_hwdep.c   | 195 +++++++
 sound/firewire/speakers/speakers_midi.c    | 150 ++++++
 sound/firewire/speakers/speakers_pcm.c     | 402 +++++++++++++++
 sound/firewire/speakers/speakers_proc.c    |  60 +++
 sound/firewire/speakers/speakers_stream.c  | 534 +++++++++++++++++++
 15 files changed, 2214 insertions(+), 808 deletions(-)
 delete mode 100644 sound/firewire/speakers.c
 create mode 100644 sound/firewire/speakers/Makefile
 create mode 100644 sound/firewire/speakers/speakers.c
 create mode 100644 sound/firewire/speakers/speakers.h
 create mode 100644 sound/firewire/speakers/speakers_command.c
 create mode 100644 sound/firewire/speakers/speakers_control.c
 create mode 100644 sound/firewire/speakers/speakers_hwdep.c
 create mode 100644 sound/firewire/speakers/speakers_midi.c
 create mode 100644 sound/firewire/speakers/speakers_pcm.c
 create mode 100644 sound/firewire/speakers/speakers_proc.c
 create mode 100644 sound/firewire/speakers/speakers_stream.c

-- 
1.8.3.2

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

* [PATCH 01/13] speakers: move to its own directory
  2014-01-10 15:29 ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Takashi Sakamoto
@ 2014-01-10 15:29   ` Takashi Sakamoto
  2014-01-10 15:29   ` [PATCH 02/13] speakers: Split stream functionality to a new file and add a header file Takashi Sakamoto
                     ` (12 subsequent siblings)
  13 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-10 15:29 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

Followed commits add much codes. I'll split them to some files.
In this reason, this commit make own directory and move current
source to it.

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

diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile
index fad8d49..e2cd79f 100644
--- a/sound/firewire/Makefile
+++ b/sound/firewire/Makefile
@@ -1,13 +1,12 @@
 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-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_FIREWIRE_SPEAKERS) += speakers/
 obj-$(CONFIG_SND_ISIGHT) += snd-isight.o
 obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o
 obj-$(CONFIG_SND_FIREWORKS) += fireworks/
diff --git a/sound/firewire/speakers.c b/sound/firewire/speakers.c
deleted file mode 100644
index abb2eb1..0000000
--- a/sound/firewire/speakers.c
+++ /dev/null
@@ -1,801 +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 = snd_pcm_hw_constraint_minmax(runtime,
-					   SNDRV_PCM_HW_PARAM_PERIOD_TIME,
-					   5000, UINT_MAX);
-	if (err < 0)
-		return err;
-
-	err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
-	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)
-		goto err_buffer;
-	if (err != 0x09 /* ACCEPTED */) {
-		dev_err(&fwspk->unit->device, "failed to set sample rate\n");
-		err = -EIO;
-		goto error;
-	}
-
-	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_create(-1, NULL, THIS_MODULE, sizeof(*fwspk), &card);
-	if (err < 0)
-		return err;
-	snd_card_set_dev(card, &unit->device);
-
-	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);
diff --git a/sound/firewire/speakers/Makefile b/sound/firewire/speakers/Makefile
new file mode 100644
index 0000000..3db3c56a
--- /dev/null
+++ b/sound/firewire/speakers/Makefile
@@ -0,0 +1,2 @@
+snd-firewire-speakers-objs := speakers.o
+obj-m += snd-firewire-speakers.o
diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c
new file mode 100644
index 0000000..78eaec1
--- /dev/null
+++ b/sound/firewire/speakers/speakers.c
@@ -0,0 +1,801 @@
+/*
+ * 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 = snd_pcm_hw_constraint_minmax(runtime,
+					   SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+					   5000, UINT_MAX);
+	if (err < 0)
+		return err;
+
+	err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+	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)
+		goto err_buffer;
+	if (err != 0x09 /* ACCEPTED */) {
+		dev_err(&fwspk->unit->device, "failed to set sample rate\n");
+		err = -EIO;
+		goto error;
+	}
+
+	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_create(-1, NULL, THIS_MODULE, sizeof(*fwspk), &card);
+	if (err < 0)
+		return err;
+	snd_card_set_dev(card, &unit->device);
+
+	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

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

* [PATCH 02/13] speakers: Split stream functionality to a new file and add a header file
  2014-01-10 15:29 ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Takashi Sakamoto
  2014-01-10 15:29   ` [PATCH 01/13] speakers: move to its own directory Takashi Sakamoto
@ 2014-01-10 15:29   ` Takashi Sakamoto
  2014-01-10 15:29   ` [PATCH 03/13] speakers: Split PCM functionality to a new file Takashi Sakamoto
                     ` (11 subsequent siblings)
  13 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-10 15:29 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

To make it easy to work in followed commit.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/speakers/Makefile          |   2 +-
 sound/firewire/speakers/speakers.c        | 157 +++++++-----------------------
 sound/firewire/speakers/speakers.h        |  54 ++++++++++
 sound/firewire/speakers/speakers_stream.c |  72 ++++++++++++++
 4 files changed, 163 insertions(+), 122 deletions(-)
 create mode 100644 sound/firewire/speakers/speakers.h
 create mode 100644 sound/firewire/speakers/speakers_stream.c

diff --git a/sound/firewire/speakers/Makefile b/sound/firewire/speakers/Makefile
index 3db3c56a..f46ae1d 100644
--- a/sound/firewire/speakers/Makefile
+++ b/sound/firewire/speakers/Makefile
@@ -1,2 +1,2 @@
-snd-firewire-speakers-objs := speakers.o
+snd-firewire-speakers-objs := speakers_stream.o speakers.o
 obj-m += snd-firewire-speakers.o
diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c
index 78eaec1..47d3cc0 100644
--- a/sound/firewire/speakers/speakers.c
+++ b/sound/firewire/speakers/speakers.c
@@ -1,26 +1,11 @@
 /*
- * OXFW970-based speakers driver
+ * speakers.c - a part of OXFW970/971-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"
+#include "speakers.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 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");
@@ -185,14 +147,6 @@ 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)
 {
@@ -200,7 +154,7 @@ static int fwspk_hw_params(struct snd_pcm_substream *substream,
 	int err;
 
 	mutex_lock(&fwspk->mutex);
-	fwspk_stop_stream(fwspk);
+	snd_fwspk_stream_stop(fwspk);
 	mutex_unlock(&fwspk->mutex);
 
 	err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
@@ -208,12 +162,12 @@ static int fwspk_hw_params(struct snd_pcm_substream *substream,
 	if (err < 0)
 		goto error;
 
-	amdtp_stream_set_parameters(&fwspk->stream,
+	amdtp_stream_set_parameters(&fwspk->rx_stream,
 				    params_rate(hw_params),
 				    params_channels(hw_params),
 				    0);
 
-	amdtp_stream_set_pcm_format(&fwspk->stream,
+	amdtp_stream_set_pcm_format(&fwspk->rx_stream,
 				    params_format(hw_params));
 
 	err = avc_general_set_sig_fmt(fwspk->unit, params_rate(hw_params),
@@ -239,7 +193,7 @@ static int fwspk_hw_free(struct snd_pcm_substream *substream)
 	struct fwspk *fwspk = substream->private_data;
 
 	mutex_lock(&fwspk->mutex);
-	fwspk_stop_stream(fwspk);
+	snd_fwspk_stream_stop(fwspk);
 	mutex_unlock(&fwspk->mutex);
 
 	return snd_pcm_lib_free_vmalloc_buffer(substream);
@@ -252,33 +206,15 @@ static int fwspk_prepare(struct snd_pcm_substream *substream)
 
 	mutex_lock(&fwspk->mutex);
 
-	if (amdtp_streaming_error(&fwspk->stream))
-		fwspk_stop_stream(fwspk);
+	snd_fwspk_stream_stop(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 = snd_fwspk_stream_start(fwspk);
+	if (err < 0)
+		goto end;
 
-err_connection:
-	cmp_connection_break(&fwspk->connection);
-err_mutex:
+	amdtp_stream_pcm_prepare(&fwspk->rx_stream);
+end:
 	mutex_unlock(&fwspk->mutex);
-
 	return err;
 }
 
@@ -297,7 +233,7 @@ static int fwspk_trigger(struct snd_pcm_substream *substream, int cmd)
 	default:
 		return -EINVAL;
 	}
-	amdtp_stream_pcm_trigger(&fwspk->stream, pcm);
+	amdtp_stream_pcm_trigger(&fwspk->rx_stream, pcm);
 	return 0;
 }
 
@@ -305,7 +241,7 @@ 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);
+	return amdtp_stream_pcm_pointer(&fwspk->rx_stream);
 }
 
 static int fwspk_create_pcm(struct fwspk *fwspk)
@@ -623,9 +559,11 @@ 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_lock(&fwspk->mutex);
+	snd_fwspk_stream_destroy(fwspk);
+	mutex_unlock(&fwspk->mutex);
+
+	fw_unit_put(fwspk->unit);	/* dec reference counter */
 	mutex_destroy(&fwspk->mutex);
 }
 
@@ -641,24 +579,13 @@ static int fwspk_probe(struct fw_unit *unit,
 	err = snd_card_create(-1, NULL, THIS_MODULE, sizeof(*fwspk), &card);
 	if (err < 0)
 		return err;
-	snd_card_set_dev(card, &unit->device);
 
+	card->private_free = fwspk_card_free;
 	fwspk = card->private_data;
 	fwspk->card = card;
-	mutex_init(&fwspk->mutex);
-	fwspk->unit = fw_unit_get(unit);
+	fwspk->unit = fw_unit_get(unit);	/* inc reference counter */
 	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;
+	mutex_init(&fwspk->mutex);
 
 	strcpy(card->driver, fwspk->device_info->driver_name);
 	strcpy(card->shortname, fwspk->device_info->short_name);
@@ -671,28 +598,26 @@ static int fwspk_probe(struct fw_unit *unit,
 		 dev_name(&unit->device), 100 << fw_dev->max_speed);
 	strcpy(card->mixername, "OXFW970");
 
+	err = snd_fwspk_stream_init(fwspk);
+	if (err < 0)
+		goto err_card;
+
 	err = fwspk_create_pcm(fwspk);
 	if (err < 0)
-		goto error;
+		goto err_card;
 
 	err = fwspk_create_mixer(fwspk);
 	if (err < 0)
-		goto error;
+		goto err_card;
 
+	snd_card_set_dev(card, &unit->device);
 	err = snd_card_register(card);
 	if (err < 0)
-		goto error;
-
+		goto err_card;
 	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:
+err_card:
 	snd_card_free(card);
 	return err;
 }
@@ -701,30 +626,20 @@ static void fwspk_bus_reset(struct fw_unit *unit)
 {
 	struct fwspk *fwspk = dev_get_drvdata(&unit->device);
 
-	fcp_bus_reset(fwspk->unit);
+	mutex_lock(&fwspk->mutex);
 
-	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;
-	}
+	fcp_bus_reset(fwspk->unit);
+	snd_fwspk_stream_update(fwspk);
 
-	amdtp_stream_update(&fwspk->stream);
+	mutex_unlock(&fwspk->mutex);
 }
 
 static void fwspk_remove(struct fw_unit *unit)
 {
 	struct fwspk *fwspk = dev_get_drvdata(&unit->device);
 
-	amdtp_stream_pcm_abort(&fwspk->stream);
+	snd_fwspk_stream_destroy(fwspk);
 	snd_card_disconnect(fwspk->card);
-
-	mutex_lock(&fwspk->mutex);
-	fwspk_stop_stream(fwspk);
-	mutex_unlock(&fwspk->mutex);
-
 	snd_card_free_when_closed(fwspk->card);
 }
 
diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h
new file mode 100644
index 0000000..d322b8b
--- /dev/null
+++ b/sound/firewire/speakers/speakers.h
@@ -0,0 +1,54 @@
+/*
+ * speakers.h - a part of OXFW970/971-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"
+
+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 in_conn;
+	struct amdtp_stream rx_stream;
+	bool mute;
+	s16 volume[6];
+	s16 volume_min;
+	s16 volume_max;
+};
+
+int snd_fwspk_stream_init(struct fwspk *fwspk);
+int snd_fwspk_stream_start(struct fwspk *fwspk);
+void snd_fwspk_stream_stop(struct fwspk *fwspk);
+void snd_fwspk_stream_destroy(struct fwspk *fwspk);
+void snd_fwspk_stream_update(struct fwspk *fwspk);
diff --git a/sound/firewire/speakers/speakers_stream.c b/sound/firewire/speakers/speakers_stream.c
new file mode 100644
index 0000000..9cc0ffb
--- /dev/null
+++ b/sound/firewire/speakers/speakers_stream.c
@@ -0,0 +1,72 @@
+/*
+ * speakers_stream.c - a part of OXFW970/971-based speakers driver
+ *
+ * Copyright (c) Takashi Sakamoto <o-takashi@sakamocchi.jp>
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "speakers.h"
+
+int snd_fwspk_stream_init(struct fwspk *fwspk)
+{
+	int err;
+
+	err = cmp_connection_init(&fwspk->in_conn, fwspk->unit,
+				  CMP_INPUT, 0);
+	if (err < 0) {
+		fw_unit_put(fwspk->unit);
+		goto end;
+	}
+
+	err = amdtp_stream_init(&fwspk->rx_stream, fwspk->unit,
+				AMDTP_OUT_STREAM, CIP_NONBLOCKING);
+	if (err < 0)
+		cmp_connection_destroy(&fwspk->in_conn);
+end:
+	return err;
+}
+
+int snd_fwspk_stream_start(struct fwspk *fwspk)
+{
+	int err = 0;
+
+	if (amdtp_stream_running(&fwspk->rx_stream))
+		goto end;
+
+	err = cmp_connection_establish(&fwspk->in_conn,
+		amdtp_stream_get_max_payload(&fwspk->rx_stream));
+	if (err < 0)
+		goto end;
+
+	err = amdtp_stream_start(&fwspk->rx_stream,
+				 fwspk->in_conn.resources.channel,
+				 fwspk->in_conn.speed);
+	if (err < 0)
+		cmp_connection_break(&fwspk->in_conn);
+end:
+	return err;
+}
+
+void snd_fwspk_stream_stop(struct fwspk *fwspk)
+{
+	if (amdtp_stream_running(&fwspk->rx_stream))
+		amdtp_stream_stop(&fwspk->rx_stream);
+
+	cmp_connection_break(&fwspk->in_conn);
+}
+
+void snd_fwspk_stream_destroy(struct fwspk *fwspk)
+{
+	amdtp_stream_pcm_abort(&fwspk->rx_stream);
+	snd_fwspk_stream_stop(fwspk);
+}
+
+void snd_fwspk_stream_update(struct fwspk *fwspk)
+{
+	if (cmp_connection_update(&fwspk->in_conn) < 0) {
+		amdtp_stream_pcm_abort(&fwspk->rx_stream);
+		snd_fwspk_stream_stop(fwspk);
+	} else {
+		amdtp_stream_update(&fwspk->rx_stream);
+	}
+}
-- 
1.8.3.2

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

* [PATCH 03/13] speakers: Split PCM functionality to a new file
  2014-01-10 15:29 ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Takashi Sakamoto
  2014-01-10 15:29   ` [PATCH 01/13] speakers: move to its own directory Takashi Sakamoto
  2014-01-10 15:29   ` [PATCH 02/13] speakers: Split stream functionality to a new file and add a header file Takashi Sakamoto
@ 2014-01-10 15:29   ` Takashi Sakamoto
  2014-01-10 15:29   ` [PATCH 04/13] speakers: Split control " Takashi Sakamoto
                     ` (10 subsequent siblings)
  13 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-10 15:29 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

To make it easy to work in followed commit.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/speakers/Makefile       |   2 +-
 sound/firewire/speakers/speakers.c     | 248 +------------------------------
 sound/firewire/speakers/speakers.h     |   5 +
 sound/firewire/speakers/speakers_pcm.c | 259 +++++++++++++++++++++++++++++++++
 4 files changed, 266 insertions(+), 248 deletions(-)
 create mode 100644 sound/firewire/speakers/speakers_pcm.c

diff --git a/sound/firewire/speakers/Makefile b/sound/firewire/speakers/Makefile
index f46ae1d..a970ccf 100644
--- a/sound/firewire/speakers/Makefile
+++ b/sound/firewire/speakers/Makefile
@@ -1,2 +1,2 @@
-snd-firewire-speakers-objs := speakers_stream.o speakers.o
+snd-firewire-speakers-objs := speakers_stream.o speakers_pcm.o speakers.o
 obj-m += snd-firewire-speakers.o
diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c
index 47d3cc0..20bba46 100644
--- a/sound/firewire/speakers/speakers.c
+++ b/sound/firewire/speakers/speakers.c
@@ -24,252 +24,6 @@ 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 = snd_pcm_hw_constraint_minmax(runtime,
-					   SNDRV_PCM_HW_PARAM_PERIOD_TIME,
-					   5000, UINT_MAX);
-	if (err < 0)
-		return err;
-
-	err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
-	if (err < 0)
-		return err;
-
-	return 0;
-}
-
-static int fwspk_close(struct snd_pcm_substream *substream)
-{
-	return 0;
-}
-
-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);
-	snd_fwspk_stream_stop(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->rx_stream,
-				    params_rate(hw_params),
-				    params_channels(hw_params),
-				    0);
-
-	amdtp_stream_set_pcm_format(&fwspk->rx_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)
-		goto err_buffer;
-	if (err != 0x09 /* ACCEPTED */) {
-		dev_err(&fwspk->unit->device, "failed to set sample rate\n");
-		err = -EIO;
-		goto error;
-	}
-
-	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);
-	snd_fwspk_stream_stop(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);
-
-	snd_fwspk_stream_stop(fwspk);
-
-	err = snd_fwspk_stream_start(fwspk);
-	if (err < 0)
-		goto end;
-
-	amdtp_stream_pcm_prepare(&fwspk->rx_stream);
-end:
-	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->rx_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->rx_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,
@@ -602,7 +356,7 @@ static int fwspk_probe(struct fw_unit *unit,
 	if (err < 0)
 		goto err_card;
 
-	err = fwspk_create_pcm(fwspk);
+	err = snd_fwspk_create_pcm(fwspk);
 	if (err < 0)
 		goto err_card;
 
diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h
index d322b8b..379196d 100644
--- a/sound/firewire/speakers/speakers.h
+++ b/sound/firewire/speakers/speakers.h
@@ -52,3 +52,8 @@ int snd_fwspk_stream_start(struct fwspk *fwspk);
 void snd_fwspk_stream_stop(struct fwspk *fwspk);
 void snd_fwspk_stream_destroy(struct fwspk *fwspk);
 void snd_fwspk_stream_update(struct fwspk *fwspk);
+
+int snd_fwspk_create_pcm(struct fwspk *fwspk);
+
+int firewave_constraints(struct snd_pcm_runtime *runtime);
+int lacie_speakers_constraints(struct snd_pcm_runtime *runtime);
diff --git a/sound/firewire/speakers/speakers_pcm.c b/sound/firewire/speakers/speakers_pcm.c
new file mode 100644
index 0000000..8fa437f
--- /dev/null
+++ b/sound/firewire/speakers/speakers_pcm.c
@@ -0,0 +1,259 @@
+/*
+ * speakers_pcm.c - a part of OXFW970/971-based speakers driver
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "speakers.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 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;
+	bool used;
+	int err;
+
+	err = cmp_connection_check_used(&fwspk->in_conn, &used);
+	if ((err < 0) || used)
+		goto end;
+
+	runtime->hw = hardware;
+
+	err = fwspk->device_info->pcm_constraints(runtime);
+	if (err < 0)
+		goto end;
+	err = snd_pcm_limit_hw_rates(runtime);
+	if (err < 0)
+		goto end;
+
+	err = snd_pcm_hw_constraint_minmax(runtime,
+					   SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+					   5000, UINT_MAX);
+	if (err < 0)
+		goto end;
+
+	err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+	if (err < 0)
+		goto end;
+end:
+	return err;
+}
+
+static int fwspk_close(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+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);
+	snd_fwspk_stream_stop(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->rx_stream,
+				    params_rate(hw_params),
+				    params_channels(hw_params),
+				    0);
+
+	amdtp_stream_set_pcm_format(&fwspk->rx_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)
+		goto err_buffer;
+	if (err != 0x09 /* ACCEPTED */) {
+		dev_err(&fwspk->unit->device, "failed to set sample rate\n");
+		err = -EIO;
+		goto error;
+	}
+
+	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);
+	snd_fwspk_stream_stop(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);
+
+	snd_fwspk_stream_stop(fwspk);
+
+	err = snd_fwspk_stream_start(fwspk);
+	if (err < 0)
+		goto end;
+
+	amdtp_stream_pcm_prepare(&fwspk->rx_stream);
+end:
+	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->rx_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->rx_stream);
+}
+
+int snd_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, fwspk->card->driver, 0, 1, 0, &pcm);
+	if (err < 0)
+		return err;
+	pcm->private_data = fwspk;
+	strcpy(pcm->name, fwspk->card->shortname);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &ops);
+	return 0;
+}
-- 
1.8.3.2

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

* [PATCH 04/13] speakers: Split control functionality to a new file
  2014-01-10 15:29 ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Takashi Sakamoto
                     ` (2 preceding siblings ...)
  2014-01-10 15:29   ` [PATCH 03/13] speakers: Split PCM functionality to a new file Takashi Sakamoto
@ 2014-01-10 15:29   ` Takashi Sakamoto
  2014-01-10 15:29   ` [PATCH 05/13] speakers: Change the way to name card Takashi Sakamoto
                     ` (9 subsequent siblings)
  13 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-10 15:29 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

To make it easy to work in followed commit.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/speakers/Makefile           |   3 +-
 sound/firewire/speakers/speakers.c         | 277 +---------------------------
 sound/firewire/speakers/speakers.h         |   2 +
 sound/firewire/speakers/speakers_control.c | 283 +++++++++++++++++++++++++++++
 4 files changed, 288 insertions(+), 277 deletions(-)
 create mode 100644 sound/firewire/speakers/speakers_control.c

diff --git a/sound/firewire/speakers/Makefile b/sound/firewire/speakers/Makefile
index a970ccf..8ee55ee 100644
--- a/sound/firewire/speakers/Makefile
+++ b/sound/firewire/speakers/Makefile
@@ -1,2 +1,3 @@
-snd-firewire-speakers-objs := speakers_stream.o speakers_pcm.o speakers.o
+snd-firewire-speakers-objs := speakers_stream.o speakers_pcm.o speakers_control.o \
+			      speakers.o
 obj-m += snd-firewire-speakers.o
diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c
index 20bba46..187377e 100644
--- a/sound/firewire/speakers/speakers.c
+++ b/sound/firewire/speakers/speakers.c
@@ -24,281 +24,6 @@ MODULE_DESCRIPTION("FireWire speakers driver");
 MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
 MODULE_LICENSE("GPL v2");
 
-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;
@@ -360,7 +85,7 @@ static int fwspk_probe(struct fw_unit *unit,
 	if (err < 0)
 		goto err_card;
 
-	err = fwspk_create_mixer(fwspk);
+	err = snd_fwspk_create_mixer(fwspk);
 	if (err < 0)
 		goto err_card;
 
diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h
index 379196d..c921714 100644
--- a/sound/firewire/speakers/speakers.h
+++ b/sound/firewire/speakers/speakers.h
@@ -55,5 +55,7 @@ void snd_fwspk_stream_update(struct fwspk *fwspk);
 
 int snd_fwspk_create_pcm(struct fwspk *fwspk);
 
+int snd_fwspk_create_mixer(struct fwspk *fwspk);
+
 int firewave_constraints(struct snd_pcm_runtime *runtime);
 int lacie_speakers_constraints(struct snd_pcm_runtime *runtime);
diff --git a/sound/firewire/speakers/speakers_control.c b/sound/firewire/speakers/speakers_control.c
new file mode 100644
index 0000000..450632e
--- /dev/null
+++ b/sound/firewire/speakers/speakers_control.c
@@ -0,0 +1,283 @@
+/*
+ * speakers_control.c - a part of OXFW970/971-based speakers driver
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "speakers.h"
+
+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;
+}
+
+int snd_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;
+}
-- 
1.8.3.2

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

* [PATCH 05/13] speakers: Change the way to name card
  2014-01-10 15:29 ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Takashi Sakamoto
                     ` (3 preceding siblings ...)
  2014-01-10 15:29   ` [PATCH 04/13] speakers: Split control " Takashi Sakamoto
@ 2014-01-10 15:29   ` Takashi Sakamoto
  2014-01-10 15:29   ` [PATCH 06/13] speakers: Change the way to make PCM rules/constraints Takashi Sakamoto
                     ` (8 subsequent siblings)
  13 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-10 15:29 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

'struct snd_card' has four members for name, 'driver', 'shortname',
'longname' and 'mixername'. The 'driver' is used to detect card configuration
file but the others are not used for important functionality. So I name them
with the information from config ROM.

This commit adds 'name_card()' function and change the way to name card.
The 'driver' is still given by 'struct device_info'. The 'shortname' and
'mixername' is given by 'model' and the 'longname' is given by 'vendor'
and 'model'.

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

diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c
index 187377e..1b5556b 100644
--- a/sound/firewire/speakers/speakers.c
+++ b/sound/firewire/speakers/speakers.c
@@ -24,14 +24,44 @@ MODULE_DESCRIPTION("FireWire speakers driver");
 MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
 MODULE_LICENSE("GPL v2");
 
-static u32 fwspk_read_firmware_version(struct fw_unit *unit)
+static int name_card(struct fwspk *fwspk)
 {
-	__be32 data;
+	struct fw_device *fw_dev = fw_parent_device(fwspk->unit);
+	char vendor[24] = {0};
+	char model[24] = {0};
+	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;
+	/* 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(fwspk->unit->directory, CSR_MODEL,
+			    model, sizeof(model));
+	if (err < 0)
+		goto end;
+
+	err = snd_fw_transaction(fwspk->unit, TCODE_READ_QUADLET_REQUEST,
+				 OXFORD_FIRMWARE_ID_ADDRESS, &firmware, 4, 0);
+	if (err < 0)
+		goto end;
+	be32_to_cpus(&firmware);
+
+	strcpy(fwspk->card->driver, fwspk->device_info->driver_name);
+	strcpy(fwspk->card->shortname, model);
+
+	snprintf(fwspk->card->longname, sizeof(fwspk->card->longname),
+		 "%s %s (OXFW%x %04x), GUID %08x%08x at %s, S%d",
+		 vendor, model, firmware >> 20, firmware & 0xffff,
+		 fw_dev->config_rom[3], fw_dev->config_rom[4],
+		 dev_name(&fwspk->unit->device), 100 << fw_dev->max_speed);
+
+	strcpy(fwspk->card->mixername, fwspk->card->shortname);
+end:
+	return err;
 }
 
 static void fwspk_card_free(struct snd_card *card)
@@ -49,10 +79,8 @@ static void fwspk_card_free(struct snd_card *card)
 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_create(-1, NULL, THIS_MODULE, sizeof(*fwspk), &card);
@@ -66,16 +94,9 @@ static int fwspk_probe(struct fw_unit *unit,
 	fwspk->device_info = (const struct device_info *)id->driver_data;
 	mutex_init(&fwspk->mutex);
 
-	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 = name_card(fwspk);
+	if (err < 0)
+		goto err_card;
 
 	err = snd_fwspk_stream_init(fwspk);
 	if (err < 0)
@@ -124,8 +145,6 @@ static void fwspk_remove(struct fw_unit *unit)
 
 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,
@@ -134,8 +153,6 @@ static const struct device_info griffin_firewave = {
 
 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,
-- 
1.8.3.2

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

* [PATCH 06/13] speakers: Change the way to make PCM rules/constraints
  2014-01-10 15:29 ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Takashi Sakamoto
                     ` (4 preceding siblings ...)
  2014-01-10 15:29   ` [PATCH 05/13] speakers: Change the way to name card Takashi Sakamoto
@ 2014-01-10 15:29   ` Takashi Sakamoto
  2014-01-12 12:18     ` Stefan Richter
  2014-01-10 15:29   ` [PATCH 07/13] speakers: Add proc interface for debugging purpose Takashi Sakamoto
                     ` (7 subsequent siblings)
  13 siblings, 1 reply; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-10 15:29 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

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

According to datasheet of OXFW970/971, they support 32.0kHz to 196.0kHz.
So this commit adds 'rx_stream_formations' member to 'struct fwspk'. It's
an array with 7 elements. Each element corresponds to each sampling rate.

With these data structure, this commit changes the way to make PCM
rules/constraints.

I don't know whether Griffin/LaCie devices supports SINGLE/LIST subfunction of
'AV/C Stream Format Information' command. So this commit adds codes to
generate formations for them.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/speakers/speakers.c        |  37 +++---
 sound/firewire/speakers/speakers.h        |  16 ++-
 sound/firewire/speakers/speakers_pcm.c    | 203 +++++++++++++++++++-----------
 sound/firewire/speakers/speakers_stream.c |  36 ++++++
 4 files changed, 198 insertions(+), 94 deletions(-)

diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c
index 1b5556b..e07ed5f 100644
--- a/sound/firewire/speakers/speakers.c
+++ b/sound/firewire/speakers/speakers.c
@@ -24,6 +24,20 @@ MODULE_DESCRIPTION("FireWire speakers driver");
 MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
 MODULE_LICENSE("GPL v2");
 
+static const struct device_info griffin_firewave = {
+	.driver_name = "FireWave",
+	.mixer_channels = 6,
+	.mute_fb_id   = 0x01,
+	.volume_fb_id = 0x02,
+};
+
+static const struct device_info lacie_speakers = {
+	.driver_name = "FWSpeakers",
+	.mixer_channels = 1,
+	.mute_fb_id   = 0x01,
+	.volume_fb_id = 0x01,
+};
+
 static int name_card(struct fwspk *fwspk)
 {
 	struct fw_device *fw_dev = fw_parent_device(fwspk->unit);
@@ -94,6 +108,13 @@ static int fwspk_probe(struct fw_unit *unit,
 	fwspk->device_info = (const struct device_info *)id->driver_data;
 	mutex_init(&fwspk->mutex);
 
+	if (fwspk->device_info == &griffin_firewave)
+		err = firewave_stream_discover(fwspk);
+	else
+		err = lacie_speakers_stream_discover(fwspk);
+	if (err < 0)
+		goto err_card;
+
 	err = name_card(fwspk);
 	if (err < 0)
 		goto err_card;
@@ -143,22 +164,6 @@ static void fwspk_remove(struct fw_unit *unit)
 	snd_card_free_when_closed(fwspk->card);
 }
 
-static const struct device_info griffin_firewave = {
-	.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 = {
-	.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 fwspk_id_table[] = {
 	{
 		.match_flags  = IEEE1394_MATCH_VENDOR_ID |
diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h
index c921714..d3029f0 100644
--- a/sound/firewire/speakers/speakers.h
+++ b/sound/firewire/speakers/speakers.h
@@ -28,19 +28,28 @@ 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;
 };
 
+#define	FWSPK_STREAM_TABLE_ENTRIES	7
+struct fwspk_stream_formation {
+	unsigned int pcm;
+	unsigned int midi;
+};
+extern const unsigned int fwspk_rate_table[FWSPK_STREAM_TABLE_ENTRIES];
 struct fwspk {
 	struct snd_card *card;
 	struct fw_unit *unit;
 	const struct device_info *device_info;
 	struct mutex mutex;
+
+	struct fwspk_stream_formation
+		rx_stream_formations[FWSPK_STREAM_TABLE_ENTRIES];
 	struct cmp_connection in_conn;
 	struct amdtp_stream rx_stream;
+
 	bool mute;
 	s16 volume[6];
 	s16 volume_min;
@@ -53,9 +62,10 @@ void snd_fwspk_stream_stop(struct fwspk *fwspk);
 void snd_fwspk_stream_destroy(struct fwspk *fwspk);
 void snd_fwspk_stream_update(struct fwspk *fwspk);
 
+int firewave_stream_discover(struct fwspk *fwspk);
+int lacie_speakers_stream_discover(struct fwspk *fwspk);
+
 int snd_fwspk_create_pcm(struct fwspk *fwspk);
 
 int snd_fwspk_create_mixer(struct fwspk *fwspk);
 
-int firewave_constraints(struct snd_pcm_runtime *runtime);
-int lacie_speakers_constraints(struct snd_pcm_runtime *runtime);
diff --git a/sound/firewire/speakers/speakers_pcm.c b/sound/firewire/speakers/speakers_pcm.c
index 8fa437f..cc4b610 100644
--- a/sound/firewire/speakers/speakers_pcm.c
+++ b/sound/firewire/speakers/speakers_pcm.c
@@ -7,92 +7,132 @@
 
 #include "speakers.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,
+			struct fwspk *fwspk,
+			struct fwspk_stream_formation *formations)
 {
-	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_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 < FWSPK_STREAM_TABLE_ENTRIES; i++) {
+		/* entry is invalid */
+		if (formations[i].pcm == 0)
+			continue;
+
+		if (!snd_interval_test(c, formations[i].pcm))
+			continue;
+
+		t.min = min(t.min, fwspk_rate_table[i]);
+		t.max = max(t.max, fwspk_rate_table[i]);
+
+	}
+	return snd_interval_refine(r, &t);
 }
 
-static int firewave_channels_constraint(struct snd_pcm_hw_params *params,
+static int hw_rule_channels(struct snd_pcm_hw_params *params,
+			    struct snd_pcm_hw_rule *rule,
+			    struct fwspk *fwspk,
+			    struct fwspk_stream_formation *formations)
+{
+	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);
+	struct snd_interval t = {
+		.min = UINT_MAX, .max = 0, .integer = 1
+	};
+
+	unsigned int i;
+
+	for (i = 0; i < FWSPK_STREAM_TABLE_ENTRIES; i++) {
+		/* entry is invalid */
+		if (formations[i].pcm == 0)
+			continue;
+
+		if (!snd_interval_test(r, fwspk_rate_table[i]))
+			continue;
+
+		t.min = min(t.min, formations[i].pcm);
+		t.max = max(t.max, formations[i].pcm);
+	}
+
+	return snd_interval_refine(c, &t);
+}
+
+static inline int hw_rule_playback_rate(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 fwspk *fwspk = rule->private;
+	return hw_rule_rate(params, rule, fwspk,
+			    fwspk->rx_stream_formations);
 }
 
-int firewave_constraints(struct snd_pcm_runtime *runtime)
+static inline int hw_rule_playback_channels(struct snd_pcm_hw_params *params,
+					    struct snd_pcm_hw_rule *rule)
 {
-	static unsigned int channels_list[] = { 2, 6 };
-	static struct snd_pcm_hw_constraint_list channels_list_constraint = {
-		.count = 2,
-		.list = channels_list,
-	};
-	int err;
+	struct fwspk *fwspk = rule->private;
+	return hw_rule_channels(params, rule, fwspk,
+				fwspk->rx_stream_formations);
+}
 
-	runtime->hw.rates = SNDRV_PCM_RATE_32000 |
-			    SNDRV_PCM_RATE_44100 |
-			    SNDRV_PCM_RATE_48000 |
-			    SNDRV_PCM_RATE_96000;
-	runtime->hw.channels_max = 6;
+static void prepare_channels(struct snd_pcm_hardware *hw,
+			     struct fwspk_stream_formation *formations)
+{
+	unsigned int i;
 
-	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;
+	for (i = 0; i < FWSPK_STREAM_TABLE_ENTRIES; i++) {
+		/* entry has no PCM channels */
+		if (formations[i].pcm == 0)
+			continue;
 
-	return 0;
+		hw->channels_min = min(hw->channels_min, formations[i].pcm);
+		hw->channels_max = max(hw->channels_max, formations[i].pcm);
+	}
+
+	return;
 }
 
-int lacie_speakers_constraints(struct snd_pcm_runtime *runtime)
+static void prepare_rates(struct snd_pcm_hardware *hw,
+			  struct fwspk_stream_formation *formations)
 {
-	runtime->hw.rates = SNDRV_PCM_RATE_32000 |
-			    SNDRV_PCM_RATE_44100 |
-			    SNDRV_PCM_RATE_48000 |
-			    SNDRV_PCM_RATE_88200 |
-			    SNDRV_PCM_RATE_96000;
+	unsigned int i;
 
-	return 0;
+	for (i = 0; i < FWSPK_STREAM_TABLE_ENTRIES; i++) {
+		/* entry has no PCM channels */
+		if (formations[i].pcm == 0)
+			continue;
+
+		hw->rate_min = min(hw->rate_min, fwspk_rate_table[i]);
+		hw->rate_max = max(hw->rate_max, fwspk_rate_table[i]);
+		hw->rates |= snd_pcm_rate_to_rate_bit(fwspk_rate_table[i]);
+	}
+
+	return;
 }
 
-static int fwspk_open(struct snd_pcm_substream *substream)
+int fwspk_open(struct snd_pcm_substream *substream)
 {
-	static const struct snd_pcm_hardware hardware = {
+	static const struct snd_pcm_hardware hw = {
 		.info = SNDRV_PCM_INFO_MMAP |
-			SNDRV_PCM_INFO_MMAP_VALID |
 			SNDRV_PCM_INFO_BATCH |
 			SNDRV_PCM_INFO_INTERLEAVED |
+			/* for Open Sound System compatibility */
+			SNDRV_PCM_INFO_MMAP_VALID |
 			SNDRV_PCM_INFO_BLOCK_TRANSFER,
-		.formats = AMDTP_OUT_PCM_FORMAT_BITS,
-		.channels_min = 2,
-		.channels_max = 2,
+		/* set up later */
+		.rates = 0,
+		.rate_min = UINT_MAX,
+		.rate_max = 0,
+		/* set up later */
+		.channels_min = UINT_MAX,
+		.channels_max = 0,
 		.buffer_bytes_max = 4 * 1024 * 1024,
 		.period_bytes_min = 1,
 		.period_bytes_max = UINT_MAX,
@@ -108,24 +148,37 @@ static int fwspk_open(struct snd_pcm_substream *substream)
 	if ((err < 0) || used)
 		goto end;
 
-	runtime->hw = hardware;
+	runtime->hw = hw;
 
-	err = fwspk->device_info->pcm_constraints(runtime);
-	if (err < 0)
-		goto end;
-	err = snd_pcm_limit_hw_rates(runtime);
-	if (err < 0)
-		goto end;
+	/* add rule between channels and sampling rate */
+	prepare_rates(&runtime->hw, fwspk->rx_stream_formations);
+	prepare_channels(&runtime->hw, fwspk->rx_stream_formations);
+	runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS;
+	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+			hw_rule_playback_channels, fwspk,
+			SNDRV_PCM_HW_PARAM_RATE, -1);
+	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+			hw_rule_playback_rate, fwspk,
+			SNDRV_PCM_HW_PARAM_CHANNELS, -1);
 
-	err = snd_pcm_hw_constraint_minmax(runtime,
-					   SNDRV_PCM_HW_PARAM_PERIOD_TIME,
-					   5000, UINT_MAX);
+	/* AM824 in IEC 61883-6 can deliver 24bit data */
+	err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
 	if (err < 0)
 		goto end;
 
-	err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+	/*
+	 * AMDTP functionality in firewire-lib require periods to be aligned to
+	 * 16 bit, or 24bit inner 32bit.
+	 */
+	err = snd_pcm_hw_constraint_step(runtime, 0,
+					 SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
 	if (err < 0)
 		goto end;
+
+	/* time for period constraint */
+	err = snd_pcm_hw_constraint_minmax(runtime,
+					SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+					5000, UINT_MAX);
 end:
 	return err;
 }
diff --git a/sound/firewire/speakers/speakers_stream.c b/sound/firewire/speakers/speakers_stream.c
index 9cc0ffb..17d83e4 100644
--- a/sound/firewire/speakers/speakers_stream.c
+++ b/sound/firewire/speakers/speakers_stream.c
@@ -7,6 +7,21 @@
 
 #include "speakers.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
+ */
+const unsigned int fwspk_rate_table[FWSPK_STREAM_TABLE_ENTRIES] = {
+	[0] = 32000,
+	[1] = 44100,
+	[2] = 48000,
+	[3] = 88200,
+	[4] = 96000,
+	[5] = 176400,
+	[6] = 192000,
+};
+
 int snd_fwspk_stream_init(struct fwspk *fwspk)
 {
 	int err;
@@ -70,3 +85,24 @@ void snd_fwspk_stream_update(struct fwspk *fwspk)
 		amdtp_stream_update(&fwspk->rx_stream);
 	}
 }
+
+int firewave_stream_discover(struct fwspk *fwspk)
+{
+	fwspk->rx_stream_formations[2].pcm = 6;
+	fwspk->rx_stream_formations[3].pcm = 6;
+	fwspk->rx_stream_formations[4].pcm = 2;
+	fwspk->rx_stream_formations[6].pcm = 2;
+
+	return 0;
+}
+
+int lacie_speakers_stream_discover(struct fwspk *fwspk)
+{
+	fwspk->rx_stream_formations[2].pcm = 2;
+	fwspk->rx_stream_formations[3].pcm = 2;
+	fwspk->rx_stream_formations[4].pcm = 2;
+	fwspk->rx_stream_formations[5].pcm = 2;
+	fwspk->rx_stream_formations[6].pcm = 2;
+
+	return 0;
+}
-- 
1.8.3.2

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

* [PATCH 07/13] speakers: Add proc interface for debugging purpose
  2014-01-10 15:29 ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Takashi Sakamoto
                     ` (5 preceding siblings ...)
  2014-01-10 15:29   ` [PATCH 06/13] speakers: Change the way to make PCM rules/constraints Takashi Sakamoto
@ 2014-01-10 15:29   ` Takashi Sakamoto
  2014-01-10 15:29   ` [PATCH 08/13] speakers: Change the way to start stream Takashi Sakamoto
                     ` (6 subsequent siblings)
  13 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-10 15:29 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

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

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

diff --git a/sound/firewire/speakers/Makefile b/sound/firewire/speakers/Makefile
index 8ee55ee..8936816 100644
--- a/sound/firewire/speakers/Makefile
+++ b/sound/firewire/speakers/Makefile
@@ -1,3 +1,3 @@
 snd-firewire-speakers-objs := speakers_stream.o speakers_pcm.o speakers_control.o \
-			      speakers.o
+			      speakers_proc.o speakers.o
 obj-m += snd-firewire-speakers.o
diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c
index e07ed5f..8c3bf4a 100644
--- a/sound/firewire/speakers/speakers.c
+++ b/sound/firewire/speakers/speakers.c
@@ -131,6 +131,8 @@ static int fwspk_probe(struct fw_unit *unit,
 	if (err < 0)
 		goto err_card;
 
+	snd_fwspk_proc_init(fwspk);
+
 	snd_card_set_dev(card, &unit->device);
 	err = snd_card_register(card);
 	if (err < 0)
diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h
index d3029f0..977db20 100644
--- a/sound/firewire/speakers/speakers.h
+++ b/sound/firewire/speakers/speakers.h
@@ -18,6 +18,7 @@
 #include <sound/initval.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
+#include <sound/info.h>
 
 #include "../cmp.h"
 #include "../fcp.h"
@@ -69,3 +70,4 @@ int snd_fwspk_create_pcm(struct fwspk *fwspk);
 
 int snd_fwspk_create_mixer(struct fwspk *fwspk);
 
+void snd_fwspk_proc_init(struct fwspk *fwspk);
diff --git a/sound/firewire/speakers/speakers_proc.c b/sound/firewire/speakers/speakers_proc.c
new file mode 100644
index 0000000..9721e52
--- /dev/null
+++ b/sound/firewire/speakers/speakers_proc.c
@@ -0,0 +1,54 @@
+/*
+ * speakers_proc.c - a part of driver for OXFW970/971 based devices
+ *
+ * Copyright (c) 2013 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "./speakers.h"
+
+static void
+proc_read_formation(struct snd_info_entry *entry,
+		    struct snd_info_buffer *buffer)
+{
+	struct fwspk *fwspk = entry->private_data;
+	struct fwspk_stream_formation *formation;
+	unsigned int i;
+
+	snd_iprintf(buffer, "Input Stream to device:\n");
+	snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n");
+	formation = fwspk->rx_stream_formations;
+	for (i = 0; i < FWSPK_STREAM_TABLE_ENTRIES; i++) {
+		snd_iprintf(buffer,
+			"\t%d\t%d\t%d\n", fwspk_rate_table[i],
+			formation[i].pcm, formation[i].midi);
+	}
+}
+
+static void
+proc_read_clock(struct snd_info_entry *entry,
+		struct snd_info_buffer *buffer)
+{
+	struct fwspk *fwspk = entry->private_data;
+	unsigned int rate;
+	int err;
+
+	err = avc_general_get_sig_fmt(fwspk->unit, &rate,
+				      AVC_GENERAL_PLUG_DIR_IN, 0);
+	if ((err < 0) && (err == 0x09))
+		snd_iprintf(buffer, "Sampling rate: %d\n", rate);
+}
+
+void snd_fwspk_proc_init(struct fwspk *fwspk)
+{
+	struct snd_info_entry *entry;
+
+	if (!snd_card_proc_new(fwspk->card, "#formation", &entry))
+		snd_info_set_text_ops(entry, fwspk, proc_read_formation);
+
+	if (!snd_card_proc_new(fwspk->card, "#clock", &entry))
+		snd_info_set_text_ops(entry, fwspk, proc_read_clock);
+
+	return;
+}
-- 
1.8.3.2

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

* [PATCH 08/13] speakers: Change the way to start stream
  2014-01-10 15:29 ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Takashi Sakamoto
                     ` (6 preceding siblings ...)
  2014-01-10 15:29   ` [PATCH 07/13] speakers: Add proc interface for debugging purpose Takashi Sakamoto
@ 2014-01-10 15:29   ` Takashi Sakamoto
  2014-01-10 15:29   ` [PATCH 09/13] speakers: Add some AV/C commands to get stream formation and supported sampling rates Takashi Sakamoto
                     ` (5 subsequent siblings)
  13 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-10 15:29 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

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

But PCM functionality still decide it. This commit move some codes to decide
stream formation from PCM functionality to stream functionality.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/speakers/speakers.h        |  2 +-
 sound/firewire/speakers/speakers_pcm.c    | 49 +++--------------------
 sound/firewire/speakers/speakers_stream.c | 66 +++++++++++++++++++++++++++++--
 3 files changed, 68 insertions(+), 49 deletions(-)

diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h
index 977db20..90c65f4 100644
--- a/sound/firewire/speakers/speakers.h
+++ b/sound/firewire/speakers/speakers.h
@@ -58,7 +58,7 @@ struct fwspk {
 };
 
 int snd_fwspk_stream_init(struct fwspk *fwspk);
-int snd_fwspk_stream_start(struct fwspk *fwspk);
+int snd_fwspk_stream_start(struct fwspk *fwspk, unsigned int rate);
 void snd_fwspk_stream_stop(struct fwspk *fwspk);
 void snd_fwspk_stream_destroy(struct fwspk *fwspk);
 void snd_fwspk_stream_update(struct fwspk *fwspk);
diff --git a/sound/firewire/speakers/speakers_pcm.c b/sound/firewire/speakers/speakers_pcm.c
index cc4b610..2258ddf 100644
--- a/sound/firewire/speakers/speakers_pcm.c
+++ b/sound/firewire/speakers/speakers_pcm.c
@@ -141,13 +141,8 @@ int fwspk_open(struct snd_pcm_substream *substream)
 	};
 	struct fwspk *fwspk = substream->private_data;
 	struct snd_pcm_runtime *runtime = substream->runtime;
-	bool used;
 	int err;
 
-	err = cmp_connection_check_used(&fwspk->in_conn, &used);
-	if ((err < 0) || used)
-		goto end;
-
 	runtime->hw = hw;
 
 	/* add rule between channels and sampling rate */
@@ -191,42 +186,8 @@ static int fwspk_close(struct snd_pcm_substream *substream)
 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);
-	snd_fwspk_stream_stop(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->rx_stream,
-				    params_rate(hw_params),
-				    params_channels(hw_params),
-				    0);
-
-	amdtp_stream_set_pcm_format(&fwspk->rx_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)
-		goto err_buffer;
-	if (err != 0x09 /* ACCEPTED */) {
-		dev_err(&fwspk->unit->device, "failed to set sample rate\n");
-		err = -EIO;
-		goto error;
-	}
-
-	return 0;
-
-err_buffer:
-	snd_pcm_lib_free_vmalloc_buffer(substream);
-error:
-	return err;
+	return snd_pcm_lib_alloc_vmalloc_buffer(substream,
+						params_buffer_bytes(hw_params));
 }
 
 static int fwspk_hw_free(struct snd_pcm_substream *substream)
@@ -243,16 +204,16 @@ static int fwspk_hw_free(struct snd_pcm_substream *substream)
 static int fwspk_prepare(struct snd_pcm_substream *substream)
 {
 	struct fwspk *fwspk = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
 	int err;
 
 	mutex_lock(&fwspk->mutex);
 
-	snd_fwspk_stream_stop(fwspk);
-
-	err = snd_fwspk_stream_start(fwspk);
+	err = snd_fwspk_stream_start(fwspk, runtime->rate);
 	if (err < 0)
 		goto end;
 
+	amdtp_stream_set_pcm_format(&fwspk->rx_stream, runtime->format);
 	amdtp_stream_pcm_prepare(&fwspk->rx_stream);
 end:
 	mutex_unlock(&fwspk->mutex);
diff --git a/sound/firewire/speakers/speakers_stream.c b/sound/firewire/speakers/speakers_stream.c
index 17d83e4..ef2ac9d 100644
--- a/sound/firewire/speakers/speakers_stream.c
+++ b/sound/firewire/speakers/speakers_stream.c
@@ -41,18 +41,76 @@ end:
 	return err;
 }
 
-int snd_fwspk_stream_start(struct fwspk *fwspk)
+int snd_fwspk_stream_start(struct fwspk *fwspk, unsigned int rate)
 {
-	int err = 0;
+	unsigned int i, curr_rate, pcm_channels, midi_ports;
+	bool used;
+	int err;
 
-	if (amdtp_stream_running(&fwspk->rx_stream))
+	/* already start or not */
+	if (amdtp_stream_running(&fwspk->rx_stream)) {
+		err = 0;
+		goto end;
+	}
+
+	/* check other's connection */
+	err = cmp_connection_check_used(&fwspk->in_conn, &used);
+	if (err < 0)
+		goto end;
+	else if (used) {
+		err = -EBUSY;
+		goto end;
+	};
+
+	/* arrange sampling rate */
+	err = avc_general_get_sig_fmt(fwspk->unit, &curr_rate,
+				      AVC_GENERAL_PLUG_DIR_IN, 0);
+	if (err < 0)
 		goto end;
+	if (err != 0x0c /* IMPLEMENTED/STABLE */) {
+		dev_err(&fwspk->unit->device,
+			"failed to get sample rate\n");
+		err = -EIO;
+		goto end;
+	}
+	if (curr_rate != rate) {
+		err = avc_general_set_sig_fmt(fwspk->unit, rate,
+					      AVC_GENERAL_PLUG_DIR_IN, 0);
+		if (err < 0)
+			goto end;
+		if (err != 0x09 /* ACCEPTED */) {
+			dev_err(&fwspk->unit->device,
+				"failed to set sample rate\n");
+			err = -EIO;
+			goto end;
+		}
+	}
+
+	/* set stream formation */
+	for (i = 0; i < FWSPK_STREAM_TABLE_ENTRIES; i++) {
+		if (fwspk_rate_table[i] == rate)
+			break;
+	}
+	if (i == FWSPK_STREAM_TABLE_ENTRIES) {
+		err = -EINVAL;
+		goto end;
+	}
+	pcm_channels = fwspk->rx_stream_formations[i].pcm;
+	midi_ports = fwspk->rx_stream_formations[i].midi;
+	if ((pcm_channels == 0) && (midi_ports == 0)) {
+		err = -EINVAL;
+		goto end;
+	}
+	amdtp_stream_set_parameters(&fwspk->rx_stream, rate,
+				    pcm_channels, midi_ports);
 
+	/* establish connection */
 	err = cmp_connection_establish(&fwspk->in_conn,
-		amdtp_stream_get_max_payload(&fwspk->rx_stream));
+			amdtp_stream_get_max_payload(&fwspk->rx_stream));
 	if (err < 0)
 		goto end;
 
+	/* start stream */
 	err = amdtp_stream_start(&fwspk->rx_stream,
 				 fwspk->in_conn.resources.channel,
 				 fwspk->in_conn.speed);
-- 
1.8.3.2

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

* [PATCH 09/13] speakers: Add some AV/C commands to get stream formation and supported sampling rates
  2014-01-10 15:29 ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Takashi Sakamoto
                     ` (7 preceding siblings ...)
  2014-01-10 15:29   ` [PATCH 08/13] speakers: Change the way to start stream Takashi Sakamoto
@ 2014-01-10 15:29   ` Takashi Sakamoto
  2014-01-10 15:29   ` [PATCH 10/13] speakers: Add support for Behringer/Mackie devices Takashi Sakamoto
                     ` (4 subsequent siblings)
  13 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-10 15:29 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

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

This command has two subfunctions, 'SINGLE' and 'LIST'. Drivers can use
'SINGLE' subfunction to know current formation of AMDTP stream, Drivers can
use 'LIST' subfunction to know an available formation of AMDTP stream in a
certain sampling rate. Followed commit adds parser for returned information.

This commit adds 'inquiry' version of 'PLUG SIGNAL FORMAT' command. When
device doesn't support 'LIST' subfunction of 'AV/C Stream Format Information'
command, drivers can use 'inquiry' command to get supported sampling rates.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/speakers/Makefile           |   2 +-
 sound/firewire/speakers/speakers.h         |  30 +++++++
 sound/firewire/speakers/speakers_command.c | 122 +++++++++++++++++++++++++++++
 3 files changed, 153 insertions(+), 1 deletion(-)
 create mode 100644 sound/firewire/speakers/speakers_command.c

diff --git a/sound/firewire/speakers/Makefile b/sound/firewire/speakers/Makefile
index 8936816..86b2b6c 100644
--- a/sound/firewire/speakers/Makefile
+++ b/sound/firewire/speakers/Makefile
@@ -1,3 +1,3 @@
-snd-firewire-speakers-objs := speakers_stream.o speakers_pcm.o speakers_control.o \
+snd-firewire-speakers-objs := speakers_command.o speakers_stream.o speakers_pcm.o speakers_control.o \
 			      speakers_proc.o speakers.o
 obj-m += snd-firewire-speakers.o
diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h
index 90c65f4..386e267 100644
--- a/sound/firewire/speakers/speakers.h
+++ b/sound/firewire/speakers/speakers.h
@@ -57,6 +57,36 @@ struct fwspk {
 	s16 volume_max;
 };
 
+/* AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) */
+#define AVC_GENERIC_FRAME_MAXIMUM_BYTES	512
+int avc_stream_get_format(struct fw_unit *unit,
+			  enum avc_general_plug_dir dir, unsigned int pid,
+			  u8 *buf, unsigned int *len,
+			  unsigned int eid);
+static inline int
+avc_stream_get_format_single(struct fw_unit *unit,
+			     enum avc_general_plug_dir dir, unsigned int pid,
+			     u8 *buf, unsigned int *len)
+{
+	return avc_stream_get_format(unit, dir, pid, buf, len, 0xff);
+}
+static inline int
+avc_stream_get_format_list(struct fw_unit *unit,
+			   enum avc_general_plug_dir dir, unsigned int pid,
+			   u8 *buf, unsigned int *len,
+			   unsigned int eid)
+{
+	return avc_stream_get_format(unit, dir, pid, buf, len, eid);
+}
+
+/*
+ * AV/C Digital Interface Command Set General Specification 4.2
+ * (Sep 2004, 1394TA)
+ */
+int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate,
+				enum avc_general_plug_dir dir,
+				unsigned short pid);
+
 int snd_fwspk_stream_init(struct fwspk *fwspk);
 int snd_fwspk_stream_start(struct fwspk *fwspk, unsigned int rate);
 void snd_fwspk_stream_stop(struct fwspk *fwspk);
diff --git a/sound/firewire/speakers/speakers_command.c b/sound/firewire/speakers/speakers_command.c
new file mode 100644
index 0000000..a13bc9e
--- /dev/null
+++ b/sound/firewire/speakers/speakers_command.c
@@ -0,0 +1,122 @@
+/*
+ * speakers_command.c - part of OXFW970/971-based speakers driver
+ *
+ * Copyright (c) Takashi Sakamoto <o-takashi@sakamocchi.jp>
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "speakers.h"
+
+int avc_stream_get_format(struct fw_unit *unit,
+			  enum avc_general_plug_dir dir, unsigned int pid,
+			  u8 *buf, unsigned int *len,
+			  unsigned int eid)
+{
+	unsigned int subfunc;
+	int err;
+
+	/* check given buffer */
+	if ((buf == NULL) || (*len < 12)) {
+		err = -EINVAL;
+		goto end;
+	}
+
+	if (eid == 0xff)
+		subfunc = 0xc0;	/* SINGLE */
+	else
+		subfunc = 0xc1;	/* LIST */
+
+	buf[0] = 0x01;		/* STATUS */
+	buf[1] = 0xff;		/* UNIT */
+	buf[2] = 0xbf;		/* EXTENDED STREAM FORMAT INFORMATION */
+	buf[3] = subfunc;	/* SINGLE or LIST */
+	buf[4] = dir;		/* Plug Direction */
+	buf[5] = 0x00;		/* Unit */
+	buf[6] = 0x00;		/* PCR (Isochronous Plug) */
+	buf[7] = 0xff & pid;	/* Plug ID */
+	buf[8] = 0xff;		/* Padding */
+	buf[9] = 0xff;		/* support status in response */
+	buf[10] = 0xff & eid;	/* entry ID */
+	buf[11] = 0xff;		/* padding */
+
+	/* do transaction and check buf[1-7] are the same against command */
+	err = fcp_avc_transaction(unit, buf, 12, buf, *len,
+				  BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
+				  BIT(6) | BIT(7));
+	if (err < 0) {
+		goto end;
+	/* reach the end of entries */
+	} else if (buf[0] == 0x0a) {
+		err = 0;
+		*len = 0;
+		goto end;
+	} else if (buf[0] != 0x0c) {
+		err = -EINVAL;
+		goto end;
+	/* the content starts at 11th bytes */
+	} else if (err < 9) {
+		err = -EIO;
+		goto end;
+	} else if ((subfunc == 0xc1) && (buf[10] != eid)) {
+		err = -EIO;
+		goto end;
+	}
+
+	/* strip */
+	memmove(buf, buf + 10, err - 10);
+	*len = err - 10;
+	err = 0;
+end:
+	return err;
+}
+
+int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate,
+				enum avc_general_plug_dir dir,
+				unsigned short pid)
+{
+	unsigned int sfc;
+	u8 *buf;
+	int err;
+
+	for (sfc = 0; sfc < CIP_SFC_COUNT; sfc++) {
+		if (amdtp_rate_table[sfc] == rate)
+			break;
+	}
+	if (sfc == CIP_SFC_COUNT)
+		return -EINVAL;
+
+	buf = kzalloc(8, GFP_KERNEL);
+	if (buf == NULL)
+		return -ENOMEM;
+
+	buf[0] = 0x02;		/* SPECIFIC INQUIRY */
+	buf[1] = 0xff;		/* UNIT */
+	if (dir == AVC_GENERAL_PLUG_DIR_IN)
+		buf[2] = 0x19;	/* INPUT PLUG SIGNAL FORMAT */
+	else
+		buf[2] = 0x18;	/* OUTPUT PLUG SIGNAL FORMAT */
+	buf[3] = 0xff & pid;	/* plug id */
+	buf[4] = 0x90;		/* EOH_1, Form_1, FMT. AM824 */
+	buf[5] = 0x07 & sfc;	/* FDF-hi. AM824, frequency */
+	buf[6] = 0xff;		/* FDF-mid. AM824, SYT hi (not used)*/
+	buf[7] = 0xff;		/* FDF-low. AM824, SYT lo (not used) */
+
+	/* do transaction and check buf[1-5] are the same against command */
+	err = fcp_avc_transaction(unit, buf, 8, buf, 8,
+				  BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5));
+	if (err < 0)
+		goto end;
+
+	/* check length */
+	if (err != 8) {
+		dev_err(&unit->device, "failed to inquiry sample rate\n");
+		err = -EIO;
+		goto end;
+	}
+
+	/* return response code */
+	err = buf[0];
+end:
+	kfree(buf);
+	return err;
+}
-- 
1.8.3.2

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

* [PATCH 10/13] speakers: Add support for Behringer/Mackie devices
  2014-01-10 15:29 ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Takashi Sakamoto
                     ` (8 preceding siblings ...)
  2014-01-10 15:29   ` [PATCH 09/13] speakers: Add some AV/C commands to get stream formation and supported sampling rates Takashi Sakamoto
@ 2014-01-10 15:29   ` Takashi Sakamoto
  2014-01-10 15:29   ` [PATCH 11/13] speakers: Add support AMDTP in-stream and PCM capture Takashi Sakamoto
                     ` (3 subsequent siblings)
  13 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-10 15:29 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

FFADO project have already identified that some devices produced by
Behringer/Mackie are based on OXFW970/971.

They support 'AV/C Stream Format Information' command. But some of them
don't implement 'LIST' subfunction. So this commit uses an assumption
that 'if they don't implement it, they don't change formation of stream
for each sampling rate'.

With this assumption, this driver generate formations for the devices by:
 1.getting current formation by SINGLE subfunction
 2.getting supported sampling rates
 3.applying current formation for all of supported sampling rates

Behringer/Mackie devices support capture/playback of PCM-samples and some
of them supports capture/playback of MIDI-messages. Followed commits will
add these functionalities.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/Kconfig                    |  11 +-
 sound/firewire/speakers/speakers.c        |  48 +++++++-
 sound/firewire/speakers/speakers.h        |   1 +
 sound/firewire/speakers/speakers_stream.c | 197 ++++++++++++++++++++++++++++++
 4 files changed, 249 insertions(+), 8 deletions(-)

diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig
index b2c5a7e..1cf371f 100644
--- a/sound/firewire/Kconfig
+++ b/sound/firewire/Kconfig
@@ -27,12 +27,17 @@ config SND_DICE
 	  will be called snd-dice.
 
 config SND_FIREWIRE_SPEAKERS
-	tristate "FireWire speakers"
+	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.
+	   * Griffin Firewave
+	   * LaCie Firewire Speakers
+	   * Behringer F-Control Audio 202
+	   * Mackie Onyx-i series (former model)
+	   * Mackie Onyx Satellite
 
 	  To compile this driver as a module, choose M here: the module
 	  will be called snd-firewire-speakers.
diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c
index 8c3bf4a..e68046c 100644
--- a/sound/firewire/speakers/speakers.c
+++ b/sound/firewire/speakers/speakers.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
@@ -64,7 +66,12 @@ static int name_card(struct fwspk *fwspk)
 		goto end;
 	be32_to_cpus(&firmware);
 
-	strcpy(fwspk->card->driver, fwspk->device_info->driver_name);
+	/* to apply card definitions */
+	if (fwspk->device_info)
+		strcpy(fwspk->card->driver, fwspk->device_info->driver_name);
+	else
+		strcpy(fwspk->card->driver, "OXFW");
+
 	strcpy(fwspk->card->shortname, model);
 
 	snprintf(fwspk->card->longname, sizeof(fwspk->card->longname),
@@ -110,8 +117,10 @@ static int fwspk_probe(struct fw_unit *unit,
 
 	if (fwspk->device_info == &griffin_firewave)
 		err = firewave_stream_discover(fwspk);
-	else
+	else if (fwspk->device_info == &lacie_speakers)
 		err = lacie_speakers_stream_discover(fwspk);
+	else
+		err = snd_fwspk_stream_discover(fwspk);
 	if (err < 0)
 		goto err_card;
 
@@ -127,9 +136,11 @@ static int fwspk_probe(struct fw_unit *unit,
 	if (err < 0)
 		goto err_card;
 
-	err = snd_fwspk_create_mixer(fwspk);
-	if (err < 0)
-		goto err_card;
+	if (fwspk->device_info) {
+		err = snd_fwspk_create_mixer(fwspk);
+		if (err < 0)
+			goto err_card;
+	}
 
 	snd_fwspk_proc_init(fwspk);
 
@@ -189,6 +200,33 @@ static const struct ieee1394_device_id fwspk_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, Onyx-i series (former models) */
+	{
+		.match_flags	= IEEE1394_MATCH_VENDOR_ID |
+				  IEEE1394_MATCH_MODEL_ID,
+		.vendor_id	= VEN_LOUD,
+		.model_id	= 0x081216,
+	},
+	/* Mackie, Onyx Satellite */
+	{
+		.match_flags	= IEEE1394_MATCH_VENDOR_ID |
+				  IEEE1394_MATCH_MODEL_ID,
+		.vendor_id	= VEN_LOUD,
+		.model_id	= 0x00200f,
+	},
+	/* 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 */
+	/*  Mackie(Loud), Tapco Link.Firewire */
 	{ }
 };
 MODULE_DEVICE_TABLE(ieee1394, fwspk_id_table);
diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h
index 386e267..017d972 100644
--- a/sound/firewire/speakers/speakers.h
+++ b/sound/firewire/speakers/speakers.h
@@ -95,6 +95,7 @@ void snd_fwspk_stream_update(struct fwspk *fwspk);
 
 int firewave_stream_discover(struct fwspk *fwspk);
 int lacie_speakers_stream_discover(struct fwspk *fwspk);
+int snd_fwspk_stream_discover(struct fwspk *fwspk);
 
 int snd_fwspk_create_pcm(struct fwspk *fwspk);
 
diff --git a/sound/firewire/speakers/speakers_stream.c b/sound/firewire/speakers/speakers_stream.c
index ef2ac9d..591e6bf 100644
--- a/sound/firewire/speakers/speakers_stream.c
+++ b/sound/firewire/speakers/speakers_stream.c
@@ -22,6 +22,20 @@ const unsigned int fwspk_rate_table[FWSPK_STREAM_TABLE_ENTRIES] = {
 	[6] = 192000,
 };
 
+/*
+ * See Table 5.7 – Sampling frequency for Multi-bit Audio
+ * at AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA)
+ */
+static const unsigned int avc_stream_rate_table[] = {
+	[0] = 0x02,
+	[1] = 0x03,
+	[2] = 0x04,
+	[3] = 0x0a,
+	[4] = 0x05,
+	[5] = 0x06,
+	[6] = 0x07,
+};
+
 int snd_fwspk_stream_init(struct fwspk *fwspk)
 {
 	int err;
@@ -164,3 +178,186 @@ int lacie_speakers_stream_discover(struct fwspk *fwspk)
 
 	return 0;
 }
+
+/*
+ * See Table 6.16 - AM824 Stream Format
+ *     Figure 6.19 - format_information field for AM824 Compound
+ * at AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA)
+ */
+static int
+parse_stream_formation(u8 *buf, unsigned int len,
+		       struct fwspk_stream_formation *formation,
+		       unsigned int *index)
+{
+	unsigned int e, channels, format;
+
+	/*
+	 * this module can support a hierarchy combination that:
+	 *  Root:	Audio and Music (0x90)
+	 *  Level 1:	AM824 Compound  (0x40)
+	 */
+	if ((buf[0] != 0x90) || (buf[1] != 0x40))
+		return -ENOSYS;
+
+	/* check the sampling rate */
+	for (*index = 0; *index < sizeof(avc_stream_rate_table); *index += 1) {
+		if (buf[2] == avc_stream_rate_table[*index])
+			break;
+	}
+	if (*index == sizeof(avc_stream_rate_table))
+		return -ENOSYS;
+
+	for (e = 0; e < buf[4]; e++) {
+		channels = buf[5 + e * 2];
+		format = buf[6 + e * 2];
+
+		switch (format) {
+		/* IEC 60958-3 */
+		case 0x00:
+		/* Multi Bit Linear Audio (Raw) */
+		case 0x06:
+			formation[*index].pcm += channels;
+			break;
+		/* MIDI comformant */
+		case 0x0d:
+			formation[*index].midi += channels;
+			break;
+		/* Multi Bit Linear Audio (DVD-audio) */
+		case 0x07:
+		/* IEC 61937-3 to 7 */
+		case 0x01:
+		case 0x02:
+		case 0x03:
+		case 0x04:
+		case 0x05:
+		/* One Bit Audio */
+		case 0x08:	/* (Plain) Raw */
+		case 0x09:	/* (Plain) SACD */
+		case 0x0a:	/* (Encoded) Raw */
+		case 0x0b:	/* (ENcoded) SACD */
+		/* High precision Multi-bit Linear Audio */
+		case 0x0c:
+		/* SMPTE Time-Code conformant */
+		case 0x0e:
+		/* Sample Count */
+		case 0x0f:
+		/* Anciliary Data */
+		case 0x10:
+		/* Synchronization Stream (Stereo Raw audio) */
+		case 0x40:
+		/* Don't care */
+		case 0xff:
+		default:
+			break;	/* not supported */
+		}
+	}
+
+	return 0;
+}
+
+static int
+assume_stream_formations(struct fwspk *fwspk, enum avc_general_plug_dir dir,
+			 unsigned int pid, u8 *buf, unsigned int *len,
+			 struct fwspk_stream_formation *formations)
+{
+	unsigned int i, pcm_channels, midi_channels;
+	int err;
+
+	/* get formation at current sampling rate */
+	err = avc_stream_get_format_single(fwspk->unit, dir, pid, buf, len);
+	if ((err < 0) || (err == 0x80) /* NOT IMPLEMENTED */)
+		goto end;
+
+	/* parse and set stream formation */
+	err = parse_stream_formation(buf, *len, formations, &i);
+	if (err < 0)
+		goto end;
+
+	pcm_channels = formations[i].pcm;
+	midi_channels = formations[i].midi;
+
+	/* apply the formation for each available sampling rate */
+	for (i = 0; i < FWSPK_STREAM_TABLE_ENTRIES; i++) {
+		err = avc_general_inquiry_sig_fmt(fwspk->unit,
+						  fwspk_rate_table[i],
+						  dir, pid);
+		if ((err < 0) || (err == 0x08) /* NOT IMPLEMENTED */)
+			continue;
+
+		formations[i].pcm = pcm_channels;
+		formations[i].midi = midi_channels;
+	}
+end:
+	return err;
+}
+
+static int
+fill_stream_formations(struct fwspk *fwspk, enum avc_general_plug_dir dir,
+		       unsigned short pid)
+{
+	u8 *buf;
+	struct fwspk_stream_formation *formations;
+	unsigned int i, len, eid;
+	int err;
+
+	buf = kmalloc(AVC_GENERIC_FRAME_MAXIMUM_BYTES, GFP_KERNEL);
+	if (buf == NULL)
+		return -ENOMEM;
+
+	formations = fwspk->rx_stream_formations;
+
+	/* initialize parameters here because of checking implementation */
+	eid = 0;
+	len = AVC_GENERIC_FRAME_MAXIMUM_BYTES;
+	memset(buf, 0, len);
+
+	/* get first entry */
+	err = avc_stream_get_format_list(fwspk->unit, dir, 0, buf, &len, eid);
+	if ((err < 0) || (len < 3)) {
+		/* LIST subfunction is not implemented */
+		err = assume_stream_formations(fwspk, dir, pid, buf, &len,
+					       formations);
+		goto end;
+	}
+
+	/* LIST subfunction is implemented */
+	do {
+		/* parse and set stream formation */
+		err = parse_stream_formation(buf, len, formations, &i);
+		if (err < 0)
+			continue;
+
+		/* get next entry */
+		len = AVC_GENERIC_FRAME_MAXIMUM_BYTES;
+		memset(buf, 0, len);
+		err = avc_stream_get_format_list(fwspk->unit, dir, 0,
+						 buf, &len, ++eid);
+		if ((err < 0) || (len < 3))
+			break;
+	} while (eid < FWSPK_STREAM_TABLE_ENTRIES);
+end:
+	kfree(buf);
+	return err;
+}
+
+int snd_fwspk_stream_discover(struct fwspk *fwspk)
+{
+	u8 plugs[AVC_PLUG_INFO_BUF_COUNT];
+	int err;
+
+	/* the number of plugs for isoc in/out, ext in/out  */
+	err = avc_general_get_plug_info(fwspk->unit, 0x1f, 0x07, 0x00, plugs);
+	if (err < 0)
+		goto end;
+	if ((plugs[0] == 0) || (plugs[0] == 0)) {
+		err = -EIO;
+		goto end;
+	}
+
+	/* use iPCR[0] */
+	err = fill_stream_formations(fwspk, AVC_GENERAL_PLUG_DIR_IN, 0);
+	if (err < 0)
+		goto end;
+end:
+	return err;
+}
-- 
1.8.3.2

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

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

* [PATCH 11/13] speakers: Add support AMDTP in-stream and PCM capture
  2014-01-10 15:29 ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Takashi Sakamoto
                     ` (9 preceding siblings ...)
  2014-01-10 15:29   ` [PATCH 10/13] speakers: Add support for Behringer/Mackie devices Takashi Sakamoto
@ 2014-01-10 15:29   ` Takashi Sakamoto
  2014-01-10 15:29   ` [PATCH 12/13] speakers: Add support for capture/playback MIDI messages Takashi Sakamoto
                     ` (2 subsequent siblings)
  13 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-10 15:29 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

Previous commit adds support for some devices which can capture PCM samples.
They transmits AMDTP stream in non-blocking mode. This stream has a quirk for
'presentation timestamp'.

The sequence of 'presentation timestamp' is invalid even if header of packet
shows 'CIP header with SYT field'. So this driver can't reuse it for out
stream.

In this reason, this driver handles both streams separately.

For your information, I note the sequence of CIP headers in stream transmitted
by  Behringer F-Control Audio 202:

When this driver gives no out-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 this driver gives out-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/speakers/speakers.c         |   9 +-
 sound/firewire/speakers/speakers.h         |  26 ++++-
 sound/firewire/speakers/speakers_command.c |  40 +++++++
 sound/firewire/speakers/speakers_pcm.c     | 179 +++++++++++++++++++++++++----
 sound/firewire/speakers/speakers_proc.c    |  14 ++-
 sound/firewire/speakers/speakers_stream.c  | 171 +++++++++++++++++++++------
 6 files changed, 365 insertions(+), 74 deletions(-)

diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c
index e68046c..76afce9 100644
--- a/sound/firewire/speakers/speakers.c
+++ b/sound/firewire/speakers/speakers.c
@@ -90,7 +90,8 @@ static void fwspk_card_free(struct snd_card *card)
 	struct fwspk *fwspk = card->private_data;
 
 	mutex_lock(&fwspk->mutex);
-	snd_fwspk_stream_destroy(fwspk);
+	snd_fwspk_stream_destroy(fwspk, &fwspk->rx_stream);
+	snd_fwspk_stream_destroy(fwspk, &fwspk->tx_stream);
 	mutex_unlock(&fwspk->mutex);
 
 	fw_unit_put(fwspk->unit);	/* dec reference counter */
@@ -163,7 +164,8 @@ static void fwspk_bus_reset(struct fw_unit *unit)
 	mutex_lock(&fwspk->mutex);
 
 	fcp_bus_reset(fwspk->unit);
-	snd_fwspk_stream_update(fwspk);
+	snd_fwspk_stream_update(fwspk, &fwspk->rx_stream);
+	snd_fwspk_stream_update(fwspk, &fwspk->tx_stream);
 
 	mutex_unlock(&fwspk->mutex);
 }
@@ -172,7 +174,8 @@ static void fwspk_remove(struct fw_unit *unit)
 {
 	struct fwspk *fwspk = dev_get_drvdata(&unit->device);
 
-	snd_fwspk_stream_destroy(fwspk);
+	snd_fwspk_stream_destroy(fwspk, &fwspk->rx_stream);
+	snd_fwspk_stream_destroy(fwspk, &fwspk->tx_stream);
 	snd_card_disconnect(fwspk->card);
 	snd_card_free_when_closed(fwspk->card);
 }
diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h
index 017d972..5d6c8e8 100644
--- a/sound/firewire/speakers/speakers.h
+++ b/sound/firewire/speakers/speakers.h
@@ -47,8 +47,12 @@ struct fwspk {
 	struct mutex mutex;
 
 	struct fwspk_stream_formation
+		tx_stream_formations[FWSPK_STREAM_TABLE_ENTRIES];
+	struct fwspk_stream_formation
 		rx_stream_formations[FWSPK_STREAM_TABLE_ENTRIES];
+	struct cmp_connection out_conn;
 	struct cmp_connection in_conn;
+	struct amdtp_stream tx_stream;
 	struct amdtp_stream rx_stream;
 
 	bool mute;
@@ -87,11 +91,23 @@ int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate,
 				enum avc_general_plug_dir dir,
 				unsigned short pid);
 
-int snd_fwspk_stream_init(struct fwspk *fwspk);
-int snd_fwspk_stream_start(struct fwspk *fwspk, unsigned int rate);
-void snd_fwspk_stream_stop(struct fwspk *fwspk);
-void snd_fwspk_stream_destroy(struct fwspk *fwspk);
-void snd_fwspk_stream_update(struct fwspk *fwspk);
+int snd_fwspk_command_set_rate(struct fwspk *fwspk,
+			       enum avc_general_plug_dir dir,
+			       unsigned int rate);
+int snd_fwspk_command_get_rate(struct fwspk *fwspk,
+			       enum avc_general_plug_dir dir,
+			       unsigned int *rate);
+
+int snd_fwspk_stream_get_rate(struct fwspk *fwspk, unsigned int *rate);
+int snd_fwspk_stream_set_rate(struct fwspk *fwspk, unsigned int rate);
+
+int snd_fwspk_stream_start(struct fwspk *fwspk,
+			   struct amdtp_stream *stream, unsigned int rate);
+void snd_fwspk_stream_stop(struct fwspk *fwspk, struct amdtp_stream *stream);
+void snd_fwspk_stream_destroy(struct fwspk *fwspk, struct amdtp_stream *stream);
+void snd_fwspk_stream_update(struct fwspk *fwspk, struct amdtp_stream *stream);
+
+int snd_fwspk_streams_init(struct fwspk *fwspk);
 
 int firewave_stream_discover(struct fwspk *fwspk);
 int lacie_speakers_stream_discover(struct fwspk *fwspk);
diff --git a/sound/firewire/speakers/speakers_command.c b/sound/firewire/speakers/speakers_command.c
index a13bc9e..274683e 100644
--- a/sound/firewire/speakers/speakers_command.c
+++ b/sound/firewire/speakers/speakers_command.c
@@ -120,3 +120,43 @@ end:
 	kfree(buf);
 	return err;
 }
+
+int snd_fwspk_command_set_rate(struct fwspk *fwspk,
+			       enum avc_general_plug_dir dir,
+			       unsigned int rate)
+{
+	int err;
+
+	err = avc_general_set_sig_fmt(fwspk->unit, rate, dir, 0);
+	if (err < 0)
+		goto end;
+
+	/* ACCEPTED or INTERIM is OK */
+	if ((err != 0x0f) && (err != 0x09)) {
+		dev_err(&fwspk->unit->device,
+			"failed to set sampling rate\n");
+		err = -EIO;
+	}
+end:
+	return err;
+}
+
+int snd_fwspk_command_get_rate(struct fwspk *fwspk,
+			       enum avc_general_plug_dir dir,
+			       unsigned int *rate)
+{
+	int err;
+
+	err = avc_general_get_sig_fmt(fwspk->unit, rate, dir, 0);
+	if (err < 0)
+		goto end;
+
+	/* IMPLEMENTED/STABLE is OK */
+	if (err != 0x0c) {
+		dev_err(&fwspk->unit->device,
+			"failed to get sampling rate\n");
+		err = -EIO;
+	}
+end:
+	return err;
+}
diff --git a/sound/firewire/speakers/speakers_pcm.c b/sound/firewire/speakers/speakers_pcm.c
index 2258ddf..da777d2 100644
--- a/sound/firewire/speakers/speakers_pcm.c
+++ b/sound/firewire/speakers/speakers_pcm.c
@@ -66,6 +66,13 @@ static int hw_rule_channels(struct snd_pcm_hw_params *params,
 	return snd_interval_refine(c, &t);
 }
 
+static inline int hw_rule_capture_rate(struct snd_pcm_hw_params *params,
+				       struct snd_pcm_hw_rule *rule)
+{
+	struct fwspk *fwspk = rule->private;
+	return hw_rule_rate(params, rule, fwspk,
+			    fwspk->tx_stream_formations);
+}
 static inline int hw_rule_playback_rate(struct snd_pcm_hw_params *params,
 					struct snd_pcm_hw_rule *rule)
 {
@@ -74,6 +81,14 @@ static inline int hw_rule_playback_rate(struct snd_pcm_hw_params *params,
 			    fwspk->rx_stream_formations);
 }
 
+static inline int hw_rule_capture_channels(struct snd_pcm_hw_params *params,
+					   struct snd_pcm_hw_rule *rule)
+{
+	struct fwspk *fwspk = rule->private;
+	return hw_rule_channels(params, rule, fwspk,
+				fwspk->tx_stream_formations);
+}
+
 static inline int hw_rule_playback_channels(struct snd_pcm_hw_params *params,
 					    struct snd_pcm_hw_rule *rule)
 {
@@ -117,12 +132,14 @@ static void prepare_rates(struct snd_pcm_hardware *hw,
 	return;
 }
 
-int fwspk_open(struct snd_pcm_substream *substream)
+static int init_hw_params(struct fwspk *fwspk,
+			  struct snd_pcm_substream *substream)
 {
 	static const struct snd_pcm_hardware hw = {
 		.info = SNDRV_PCM_INFO_MMAP |
 			SNDRV_PCM_INFO_BATCH |
 			SNDRV_PCM_INFO_INTERLEAVED |
+			SNDRV_PCM_INFO_JOINT_DUPLEX |
 			/* for Open Sound System compatibility */
 			SNDRV_PCM_INFO_MMAP_VALID |
 			SNDRV_PCM_INFO_BLOCK_TRANSFER,
@@ -139,22 +156,33 @@ int fwspk_open(struct snd_pcm_substream *substream)
 		.periods_min = 1,
 		.periods_max = UINT_MAX,
 	};
-	struct fwspk *fwspk = substream->private_data;
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	int err;
 
 	runtime->hw = hw;
 
 	/* add rule between channels and sampling rate */
-	prepare_rates(&runtime->hw, fwspk->rx_stream_formations);
-	prepare_channels(&runtime->hw, fwspk->rx_stream_formations);
-	runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS;
-	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
-			hw_rule_playback_channels, fwspk,
-			SNDRV_PCM_HW_PARAM_RATE, -1);
-	snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
-			hw_rule_playback_rate, fwspk,
-			SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		prepare_rates(&runtime->hw, fwspk->tx_stream_formations);
+		prepare_channels(&runtime->hw, fwspk->tx_stream_formations);
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE;
+		snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+				    hw_rule_capture_channels, fwspk,
+				    SNDRV_PCM_HW_PARAM_RATE, -1);
+		snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				    hw_rule_capture_rate, fwspk,
+				    SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	} else {
+		prepare_rates(&runtime->hw, fwspk->rx_stream_formations);
+		prepare_channels(&runtime->hw, fwspk->rx_stream_formations);
+		runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS;
+		snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+				    hw_rule_playback_channels, fwspk,
+				    SNDRV_PCM_HW_PARAM_RATE, -1);
+		snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				    hw_rule_playback_rate, fwspk,
+				    SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	}
 
 	/* AM824 in IEC 61883-6 can deliver 24bit data */
 	err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
@@ -178,6 +206,34 @@ end:
 	return err;
 }
 
+static int fwspk_open(struct snd_pcm_substream *substream)
+{
+	struct fwspk *fwspk = substream->private_data;
+	unsigned int rate;
+	int err;
+
+	err = init_hw_params(fwspk, substream);
+	if (err < 0)
+		goto end;
+
+	/*
+	 * When any PCM stream are already running, the available sampling rate
+	 *  is limited at current value.
+	 */
+	if (amdtp_stream_pcm_running(&fwspk->tx_stream) ||
+	    amdtp_stream_pcm_running(&fwspk->rx_stream)) {
+		err = snd_fwspk_stream_get_rate(fwspk, &rate);
+		if (err < 0)
+			goto end;
+		substream->runtime->hw.rate_min = rate;
+		substream->runtime->hw.rate_max = rate;
+	}
+
+	snd_pcm_set_sync(substream);
+end:
+	return err;
+}
+
 static int fwspk_close(struct snd_pcm_substream *substream)
 {
 	return 0;
@@ -190,18 +246,46 @@ static int fwspk_hw_params(struct snd_pcm_substream *substream,
 						params_buffer_bytes(hw_params));
 }
 
-static int fwspk_hw_free(struct snd_pcm_substream *substream)
+static int fwspk_hw_free_capture(struct snd_pcm_substream *substream)
 {
 	struct fwspk *fwspk = substream->private_data;
 
 	mutex_lock(&fwspk->mutex);
-	snd_fwspk_stream_stop(fwspk);
+	snd_fwspk_stream_stop(fwspk, &fwspk->tx_stream);
 	mutex_unlock(&fwspk->mutex);
 
 	return snd_pcm_lib_free_vmalloc_buffer(substream);
 }
+static int fwspk_hw_free_playback(struct snd_pcm_substream *substream)
+{
+	struct fwspk *fwspk = substream->private_data;
+
+	mutex_lock(&fwspk->mutex);
+	snd_fwspk_stream_stop(fwspk, &fwspk->rx_stream);
+	mutex_unlock(&fwspk->mutex);
+
+	return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int fwspk_prepare_capture(struct snd_pcm_substream *substream)
+{
+	struct fwspk *fwspk = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
 
-static int fwspk_prepare(struct snd_pcm_substream *substream)
+	mutex_lock(&fwspk->mutex);
+
+	err = snd_fwspk_stream_start(fwspk, &fwspk->tx_stream, runtime->rate);
+	if (err < 0)
+		goto end;
+
+	amdtp_stream_set_pcm_format(&fwspk->tx_stream, runtime->format);
+	amdtp_stream_pcm_prepare(&fwspk->tx_stream);
+end:
+	mutex_unlock(&fwspk->mutex);
+	return err;
+}
+static int fwspk_prepare_playback(struct snd_pcm_substream *substream)
 {
 	struct fwspk *fwspk = substream->private_data;
 	struct snd_pcm_runtime *runtime = substream->runtime;
@@ -209,7 +293,7 @@ static int fwspk_prepare(struct snd_pcm_substream *substream)
 
 	mutex_lock(&fwspk->mutex);
 
-	err = snd_fwspk_stream_start(fwspk, runtime->rate);
+	err = snd_fwspk_stream_start(fwspk, &fwspk->rx_stream, runtime->rate);
 	if (err < 0)
 		goto end;
 
@@ -220,7 +304,25 @@ end:
 	return err;
 }
 
-static int fwspk_trigger(struct snd_pcm_substream *substream, int cmd)
+static int fwspk_trigger_capture(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->tx_stream, pcm);
+	return 0;
+}
+static int fwspk_trigger_playback(struct snd_pcm_substream *substream, int cmd)
 {
 	struct fwspk *fwspk = substream->private_data;
 	struct snd_pcm_substream *pcm;
@@ -239,35 +341,62 @@ static int fwspk_trigger(struct snd_pcm_substream *substream, int cmd)
 	return 0;
 }
 
-static snd_pcm_uframes_t fwspk_pointer(struct snd_pcm_substream *substream)
+static snd_pcm_uframes_t fwspk_pointer_capture(struct snd_pcm_substream *sbstm)
 {
-	struct fwspk *fwspk = substream->private_data;
+	struct fwspk *fwspk = sbstm->private_data;
+
+	return amdtp_stream_pcm_pointer(&fwspk->tx_stream);
+}
+static snd_pcm_uframes_t fwspk_pointer_playback(struct snd_pcm_substream *sbstm)
+{
+	struct fwspk *fwspk = sbstm->private_data;
 
 	return amdtp_stream_pcm_pointer(&fwspk->rx_stream);
 }
 
 int snd_fwspk_create_pcm(struct fwspk *fwspk)
 {
-	static struct snd_pcm_ops ops = {
+	static struct snd_pcm_ops capture_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,
+		.hw_free   = fwspk_hw_free_capture,
+		.prepare   = fwspk_prepare_capture,
+		.trigger   = fwspk_trigger_capture,
+		.pointer   = fwspk_pointer_capture,
+		.page      = snd_pcm_lib_get_vmalloc_page,
+		.mmap      = snd_pcm_lib_mmap_vmalloc,
+	};
+	static struct snd_pcm_ops playback_ops = {
+		.open      = fwspk_open,
+		.close     = fwspk_close,
+		.ioctl     = snd_pcm_lib_ioctl,
+		.hw_params = fwspk_hw_params,
+		.hw_free   = fwspk_hw_free_playback,
+		.prepare   = fwspk_prepare_playback,
+		.trigger   = fwspk_trigger_playback,
+		.pointer   = fwspk_pointer_playback,
 		.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(fwspk->card, fwspk->card->driver, 0, 1, 0, &pcm);
+	/* 44.1kHz is the most popular */
+	if (fwspk->tx_stream_formations[1].pcm > 0)
+		cap = 1;
+
+	err = snd_pcm_new(fwspk->card, fwspk->card->driver, 0, 1, cap, &pcm);
 	if (err < 0)
 		return err;
+
 	pcm->private_data = fwspk;
 	strcpy(pcm->name, fwspk->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/speakers/speakers_proc.c b/sound/firewire/speakers/speakers_proc.c
index 9721e52..2865ce2 100644
--- a/sound/firewire/speakers/speakers_proc.c
+++ b/sound/firewire/speakers/speakers_proc.c
@@ -16,6 +16,15 @@ proc_read_formation(struct snd_info_entry *entry,
 	struct fwspk_stream_formation *formation;
 	unsigned int i;
 
+	snd_iprintf(buffer, "Output Stream from device:\n");
+	snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n");
+	formation = fwspk->tx_stream_formations;
+	for (i = 0; i < FWSPK_STREAM_TABLE_ENTRIES; i++) {
+		snd_iprintf(buffer,
+			"\t%d\t%d\t%d\n", fwspk_rate_table[i],
+			formation[i].pcm, formation[i].midi);
+	}
+
 	snd_iprintf(buffer, "Input Stream to device:\n");
 	snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n");
 	formation = fwspk->rx_stream_formations;
@@ -32,11 +41,8 @@ proc_read_clock(struct snd_info_entry *entry,
 {
 	struct fwspk *fwspk = entry->private_data;
 	unsigned int rate;
-	int err;
 
-	err = avc_general_get_sig_fmt(fwspk->unit, &rate,
-				      AVC_GENERAL_PLUG_DIR_IN, 0);
-	if ((err < 0) && (err == 0x09))
+	if (snd_fwspk_stream_get_rate(fwspk, &rate) >= 0)
 		snd_iprintf(buffer, "Sampling rate: %d\n", rate);
 }
 
diff --git a/sound/firewire/speakers/speakers_stream.c b/sound/firewire/speakers/speakers_stream.c
index 591e6bf..b02b03c 100644
--- a/sound/firewire/speakers/speakers_stream.c
+++ b/sound/firewire/speakers/speakers_stream.c
@@ -36,39 +36,101 @@ static const unsigned int avc_stream_rate_table[] = {
 	[6] = 0x07,
 };
 
-int snd_fwspk_stream_init(struct fwspk *fwspk)
+int snd_fwspk_stream_get_rate(struct fwspk *fwspk, unsigned int *rate)
 {
+	unsigned int tx_rate, rx_rate;
 	int err;
 
-	err = cmp_connection_init(&fwspk->in_conn, fwspk->unit,
-				  CMP_INPUT, 0);
+	err = snd_fwspk_command_get_rate(fwspk,
+					 AVC_GENERAL_PLUG_DIR_OUT, &tx_rate);
+	if (err < 0)
+		goto end;
+
+	err = snd_fwspk_command_get_rate(fwspk,
+				 AVC_GENERAL_PLUG_DIR_IN, &rx_rate);
+	if (err < 0)
+		goto end;
+
+	*rate = rx_rate;
+	if (rx_rate == tx_rate)
+		goto end;
+
+	/* synchronize receive stream rate to transmit stream rate */
+	err = snd_fwspk_command_set_rate(fwspk,
+					 AVC_GENERAL_PLUG_DIR_IN, rx_rate);
+end:
+	return err;
+}
+
+int snd_fwspk_stream_set_rate(struct fwspk *fwspk, unsigned int rate)
+{
+	int err;
+
+	err = snd_fwspk_command_set_rate(fwspk, AVC_GENERAL_PLUG_DIR_OUT, rate);
+	if (err < 0)
+		goto end;
+
+	err = snd_fwspk_command_set_rate(fwspk, AVC_GENERAL_PLUG_DIR_IN, rate);
+end:
+	return err;
+}
+
+static int stream_init(struct fwspk *fwspk, struct amdtp_stream *stream)
+{
+	struct cmp_connection *conn;
+	enum cmp_direction c_dir;
+	enum amdtp_stream_direction s_dir;
+	int err;
+
+	if (stream == &fwspk->tx_stream) {
+		conn = &fwspk->out_conn;
+		c_dir = CMP_OUTPUT;
+		s_dir = AMDTP_IN_STREAM;
+	} else {
+		conn = &fwspk->in_conn;
+		c_dir = CMP_INPUT;
+		s_dir = AMDTP_OUT_STREAM;
+	}
+
+	err = cmp_connection_init(conn, fwspk->unit, c_dir, 0);
 	if (err < 0) {
 		fw_unit_put(fwspk->unit);
 		goto end;
 	}
 
-	err = amdtp_stream_init(&fwspk->rx_stream, fwspk->unit,
-				AMDTP_OUT_STREAM, CIP_NONBLOCKING);
+	err = amdtp_stream_init(stream, fwspk->unit, s_dir, CIP_NONBLOCKING);
 	if (err < 0)
-		cmp_connection_destroy(&fwspk->in_conn);
+		cmp_connection_destroy(conn);
 end:
 	return err;
 }
 
-int snd_fwspk_stream_start(struct fwspk *fwspk, unsigned int rate)
+int snd_fwspk_stream_start(struct fwspk *fwspk,
+			   struct amdtp_stream *stream,
+			   unsigned int rate)
 {
 	unsigned int i, curr_rate, pcm_channels, midi_ports;
+	struct cmp_connection *conn;
+	enum avc_general_plug_dir dir;
 	bool used;
 	int err;
 
 	/* already start or not */
-	if (amdtp_stream_running(&fwspk->rx_stream)) {
+	if (amdtp_stream_running(stream)) {
 		err = 0;
 		goto end;
 	}
 
+	if (stream == &fwspk->tx_stream) {
+		conn = &fwspk->out_conn;
+		dir = AVC_GENERAL_PLUG_DIR_OUT;
+	} else {
+		conn = &fwspk->in_conn;
+		dir = AVC_GENERAL_PLUG_DIR_IN;
+	}
+
 	/* check other's connection */
-	err = cmp_connection_check_used(&fwspk->in_conn, &used);
+	err = cmp_connection_check_used(conn, &used);
 	if (err < 0)
 		goto end;
 	else if (used) {
@@ -77,8 +139,7 @@ int snd_fwspk_stream_start(struct fwspk *fwspk, unsigned int rate)
 	};
 
 	/* arrange sampling rate */
-	err = avc_general_get_sig_fmt(fwspk->unit, &curr_rate,
-				      AVC_GENERAL_PLUG_DIR_IN, 0);
+	err = avc_general_get_sig_fmt(fwspk->unit, &curr_rate, dir, 0);
 	if (err < 0)
 		goto end;
 	if (err != 0x0c /* IMPLEMENTED/STABLE */) {
@@ -88,8 +149,7 @@ int snd_fwspk_stream_start(struct fwspk *fwspk, unsigned int rate)
 		goto end;
 	}
 	if (curr_rate != rate) {
-		err = avc_general_set_sig_fmt(fwspk->unit, rate,
-					      AVC_GENERAL_PLUG_DIR_IN, 0);
+		err = avc_general_set_sig_fmt(fwspk->unit, rate, dir, 0);
 		if (err < 0)
 			goto end;
 		if (err != 0x09 /* ACCEPTED */) {
@@ -109,52 +169,66 @@ int snd_fwspk_stream_start(struct fwspk *fwspk, unsigned int rate)
 		err = -EINVAL;
 		goto end;
 	}
-	pcm_channels = fwspk->rx_stream_formations[i].pcm;
-	midi_ports = fwspk->rx_stream_formations[i].midi;
-	if ((pcm_channels == 0) && (midi_ports == 0)) {
+	if (stream == &fwspk->tx_stream) {
+		pcm_channels = fwspk->tx_stream_formations[i].pcm;
+		midi_ports = fwspk->tx_stream_formations[i].midi;
+	} else {
+		pcm_channels = fwspk->rx_stream_formations[i].pcm;
+		midi_ports = fwspk->rx_stream_formations[i].midi;
+	}
+	if (pcm_channels == 0) {
 		err = -EINVAL;
 		goto end;
 	}
-	amdtp_stream_set_parameters(&fwspk->rx_stream, rate,
-				    pcm_channels, midi_ports);
+	amdtp_stream_set_parameters(stream, rate, pcm_channels, midi_ports);
 
 	/* establish connection */
-	err = cmp_connection_establish(&fwspk->in_conn,
-			amdtp_stream_get_max_payload(&fwspk->rx_stream));
+	err = cmp_connection_establish(conn,
+			amdtp_stream_get_max_payload(stream));
 	if (err < 0)
 		goto end;
 
 	/* start stream */
-	err = amdtp_stream_start(&fwspk->rx_stream,
-				 fwspk->in_conn.resources.channel,
-				 fwspk->in_conn.speed);
+	err = amdtp_stream_start(stream,
+				 conn->resources.channel,
+				 conn->speed);
 	if (err < 0)
-		cmp_connection_break(&fwspk->in_conn);
+		cmp_connection_break(conn);
 end:
 	return err;
 }
 
-void snd_fwspk_stream_stop(struct fwspk *fwspk)
+void snd_fwspk_stream_stop(struct fwspk *fwspk, struct amdtp_stream *stream)
 {
-	if (amdtp_stream_running(&fwspk->rx_stream))
-		amdtp_stream_stop(&fwspk->rx_stream);
+	if (amdtp_stream_running(stream))
+		amdtp_stream_stop(stream);
 
-	cmp_connection_break(&fwspk->in_conn);
+	if (stream == &fwspk->tx_stream)
+		cmp_connection_break(&fwspk->out_conn);
+	else
+		cmp_connection_break(&fwspk->in_conn);
 }
 
-void snd_fwspk_stream_destroy(struct fwspk *fwspk)
+void snd_fwspk_stream_destroy(struct fwspk *fwspk, struct amdtp_stream *stream)
 {
-	amdtp_stream_pcm_abort(&fwspk->rx_stream);
-	snd_fwspk_stream_stop(fwspk);
+	amdtp_stream_pcm_abort(stream);
+	snd_fwspk_stream_stop(fwspk, stream);
 }
 
-void snd_fwspk_stream_update(struct fwspk *fwspk)
+void snd_fwspk_stream_update(struct fwspk *fwspk, struct amdtp_stream *stream)
 {
-	if (cmp_connection_update(&fwspk->in_conn) < 0) {
-		amdtp_stream_pcm_abort(&fwspk->rx_stream);
-		snd_fwspk_stream_stop(fwspk);
+	struct cmp_connection *conn;
+
+	if (stream == &fwspk->tx_stream)
+		conn = &fwspk->out_conn;
+	else
+		conn = &fwspk->in_conn;
+
+	if (cmp_connection_update(conn) < 0) {
+		amdtp_stream_pcm_abort(stream);
+		snd_fwspk_stream_stop(fwspk, stream);
 	} else {
-		amdtp_stream_update(&fwspk->rx_stream);
+		amdtp_stream_update(stream);
 	}
 }
 
@@ -304,7 +378,10 @@ fill_stream_formations(struct fwspk *fwspk, enum avc_general_plug_dir dir,
 	if (buf == NULL)
 		return -ENOMEM;
 
-	formations = fwspk->rx_stream_formations;
+	if (dir == AVC_GENERAL_PLUG_DIR_OUT)
+		formations = fwspk->tx_stream_formations;
+	else
+		formations = fwspk->rx_stream_formations;
 
 	/* initialize parameters here because of checking implementation */
 	eid = 0;
@@ -354,6 +431,11 @@ int snd_fwspk_stream_discover(struct fwspk *fwspk)
 		goto end;
 	}
 
+	/* use oPCR[0] */
+	err = fill_stream_formations(fwspk, AVC_GENERAL_PLUG_DIR_OUT, 0);
+	if (err < 0)
+		goto end;
+
 	/* use iPCR[0] */
 	err = fill_stream_formations(fwspk, AVC_GENERAL_PLUG_DIR_IN, 0);
 	if (err < 0)
@@ -361,3 +443,18 @@ int snd_fwspk_stream_discover(struct fwspk *fwspk)
 end:
 	return err;
 }
+
+int snd_fwspk_streams_init(struct fwspk *fwspk)
+{
+	int err;
+
+	err = stream_init(fwspk, &fwspk->rx_stream);
+	if (err < 0)
+		goto end;
+
+	/* 44.1kHz is the most popular */
+	if (fwspk->tx_stream_formations[1].pcm > 0)
+		err = stream_init(fwspk, &fwspk->tx_stream);
+end:
+	return err;
+}
-- 
1.8.3.2

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

* [PATCH 12/13] speakers: Add support for capture/playback MIDI messages
  2014-01-10 15:29 ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Takashi Sakamoto
                     ` (10 preceding siblings ...)
  2014-01-10 15:29   ` [PATCH 11/13] speakers: Add support AMDTP in-stream and PCM capture Takashi Sakamoto
@ 2014-01-10 15:29   ` Takashi Sakamoto
  2014-01-10 15:29   ` [PATCH 13/13] speakers: Add hwdep interface Takashi Sakamoto
  2014-01-10 15:45   ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Clemens Ladisch
  13 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-10 15:29 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

I assume that 'if device formations of stream have MIDI conformant data
channels, the device has one MIDI port'. With this assumption, the driver
create MIDI devices.

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

When MIDI functionality have 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.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/speakers/Makefile          |   2 +-
 sound/firewire/speakers/speakers.c        |   9 +-
 sound/firewire/speakers/speakers.h        |   7 ++
 sound/firewire/speakers/speakers_midi.c   | 150 ++++++++++++++++++++++++++++++
 sound/firewire/speakers/speakers_stream.c |  35 +++++++
 5 files changed, 201 insertions(+), 2 deletions(-)
 create mode 100644 sound/firewire/speakers/speakers_midi.c

diff --git a/sound/firewire/speakers/Makefile b/sound/firewire/speakers/Makefile
index 86b2b6c..d98d4f7 100644
--- a/sound/firewire/speakers/Makefile
+++ b/sound/firewire/speakers/Makefile
@@ -1,3 +1,3 @@
 snd-firewire-speakers-objs := speakers_command.o speakers_stream.o speakers_pcm.o speakers_control.o \
-			      speakers_proc.o speakers.o
+			      speakers_proc.o speakers_midi.o speakers.o
 obj-m += snd-firewire-speakers.o
diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c
index 76afce9..fd16e59 100644
--- a/sound/firewire/speakers/speakers.c
+++ b/sound/firewire/speakers/speakers.c
@@ -115,6 +115,7 @@ static int fwspk_probe(struct fw_unit *unit,
 	fwspk->unit = fw_unit_get(unit);	/* inc reference counter */
 	fwspk->device_info = (const struct device_info *)id->driver_data;
 	mutex_init(&fwspk->mutex);
+	spin_lock_init(&fwspk->lock);
 
 	if (fwspk->device_info == &griffin_firewave)
 		err = firewave_stream_discover(fwspk);
@@ -129,7 +130,7 @@ static int fwspk_probe(struct fw_unit *unit,
 	if (err < 0)
 		goto err_card;
 
-	err = snd_fwspk_stream_init(fwspk);
+	err = snd_fwspk_streams_init(fwspk);
 	if (err < 0)
 		goto err_card;
 
@@ -145,6 +146,12 @@ static int fwspk_probe(struct fw_unit *unit,
 
 	snd_fwspk_proc_init(fwspk);
 
+	if ((fwspk->midi_input_ports > 0) || (fwspk->midi_output_ports > 0)) {
+		err = snd_fwspk_create_midi(fwspk);
+		if (err < 0)
+			goto err_card;
+	}
+
 	snd_card_set_dev(card, &unit->device);
 	err = snd_card_register(card);
 	if (err < 0)
diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h
index 5d6c8e8..2c54946 100644
--- a/sound/firewire/speakers/speakers.h
+++ b/sound/firewire/speakers/speakers.h
@@ -19,6 +19,7 @@
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
 #include <sound/info.h>
+#include <sound/rawmidi.h>
 
 #include "../cmp.h"
 #include "../fcp.h"
@@ -45,6 +46,7 @@ struct fwspk {
 	struct fw_unit *unit;
 	const struct device_info *device_info;
 	struct mutex mutex;
+	spinlock_t lock;
 
 	struct fwspk_stream_formation
 		tx_stream_formations[FWSPK_STREAM_TABLE_ENTRIES];
@@ -55,6 +57,9 @@ struct fwspk {
 	struct amdtp_stream tx_stream;
 	struct amdtp_stream rx_stream;
 
+	unsigned int midi_input_ports;
+	unsigned int midi_output_ports;
+
 	bool mute;
 	s16 volume[6];
 	s16 volume_min;
@@ -118,3 +123,5 @@ int snd_fwspk_create_pcm(struct fwspk *fwspk);
 int snd_fwspk_create_mixer(struct fwspk *fwspk);
 
 void snd_fwspk_proc_init(struct fwspk *fwspk);
+
+int snd_fwspk_create_midi(struct fwspk *fwspk);
diff --git a/sound/firewire/speakers/speakers_midi.c b/sound/firewire/speakers/speakers_midi.c
new file mode 100644
index 0000000..83067a1
--- /dev/null
+++ b/sound/firewire/speakers/speakers_midi.c
@@ -0,0 +1,150 @@
+/*
+ * speakers_midi.c - a part of driver for OXFW970/971 based devices
+ *
+ * Copyright (c) 2013 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "speakers.h"
+
+static int midi_capture_open(struct snd_rawmidi_substream *substream)
+{
+	struct fwspk *fwspk = substream->rmidi->private_data;
+
+	return snd_fwspk_stream_start(fwspk, &fwspk->tx_stream, 0);
+}
+
+static int midi_playback_open(struct snd_rawmidi_substream *substream)
+{
+	struct fwspk *fwspk = substream->rmidi->private_data;
+
+	return snd_fwspk_stream_start(fwspk, &fwspk->rx_stream, 0);
+}
+
+static int midi_capture_close(struct snd_rawmidi_substream *substream)
+{
+	struct fwspk *fwspk = substream->rmidi->private_data;
+
+	if (amdtp_stream_running(&fwspk->rx_stream) &&
+	    !amdtp_stream_pcm_running(&fwspk->tx_stream))
+		snd_fwspk_stream_stop(fwspk, &fwspk->tx_stream);
+	return 0;
+}
+
+static int midi_playback_close(struct snd_rawmidi_substream *substream)
+{
+	struct fwspk *fwspk = substream->rmidi->private_data;
+
+	if (amdtp_stream_running(&fwspk->rx_stream) &&
+	    !amdtp_stream_pcm_running(&fwspk->rx_stream))
+		snd_fwspk_stream_stop(fwspk, &fwspk->rx_stream);
+	return 0;
+}
+
+static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
+{
+	struct fwspk *fwspk = substrm->rmidi->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&fwspk->lock, flags);
+
+	if (up)
+		amdtp_stream_midi_trigger(&fwspk->tx_stream,
+					  substrm->number, substrm);
+	else
+		amdtp_stream_midi_trigger(&fwspk->tx_stream,
+					  substrm->number, NULL);
+
+	spin_unlock_irqrestore(&fwspk->lock, flags);
+
+	return;
+}
+
+static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up)
+{
+	struct fwspk *fwspk = substrm->rmidi->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&fwspk->lock, flags);
+
+	if (up)
+		amdtp_stream_midi_trigger(&fwspk->rx_stream,
+					  substrm->number, substrm);
+	else
+		amdtp_stream_midi_trigger(&fwspk->rx_stream,
+					  substrm->number, NULL);
+
+	spin_unlock_irqrestore(&fwspk->lock, flags);
+
+	return;
+}
+
+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 fwspk *fwspk,
+				     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",
+			 fwspk->card->shortname, subs->number + 1);
+	}
+}
+
+int snd_fwspk_create_midi(struct fwspk *fwspk)
+{
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_str *str;
+	int err;
+
+	/* create midi ports */
+	err = snd_rawmidi_new(fwspk->card, fwspk->card->driver, 0,
+			      fwspk->midi_output_ports, fwspk->midi_input_ports,
+			      &rmidi);
+	if (err < 0)
+		return err;
+
+	snprintf(rmidi->name, sizeof(rmidi->name),
+		 "%s MIDI", fwspk->card->shortname);
+	rmidi->private_data = fwspk;
+
+	if (fwspk->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(fwspk, str);
+	}
+
+	if (fwspk->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(fwspk, str);
+	}
+
+	if ((fwspk->midi_output_ports > 0) && (fwspk->midi_input_ports > 0))
+		rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
+
+	return 0;
+}
diff --git a/sound/firewire/speakers/speakers_stream.c b/sound/firewire/speakers/speakers_stream.c
index b02b03c..2649f02 100644
--- a/sound/firewire/speakers/speakers_stream.c
+++ b/sound/firewire/speakers/speakers_stream.c
@@ -110,6 +110,7 @@ int snd_fwspk_stream_start(struct fwspk *fwspk,
 			   unsigned int rate)
 {
 	unsigned int i, curr_rate, pcm_channels, midi_ports;
+	struct amdtp_stream *opposite = NULL;
 	struct cmp_connection *conn;
 	enum avc_general_plug_dir dir;
 	bool used;
@@ -148,7 +149,21 @@ int snd_fwspk_stream_start(struct fwspk *fwspk,
 		err = -EIO;
 		goto end;
 	}
+	if (rate == 0)
+		rate = curr_rate;
 	if (curr_rate != rate) {
+		/* in case that AMDTP streams includes just MIDI */
+		if (amdtp_stream_running(&fwspk->tx_stream)) {
+			snd_fwspk_stream_stop(fwspk, &fwspk->tx_stream);
+			if (stream != &fwspk->tx_stream)
+				opposite = &fwspk->tx_stream;
+		}
+		if (amdtp_stream_running(&fwspk->rx_stream)) {
+			snd_fwspk_stream_stop(fwspk, &fwspk->rx_stream);
+			if (stream != &fwspk->rx_stream)
+				opposite = &fwspk->rx_stream;
+		}
+
 		err = avc_general_set_sig_fmt(fwspk->unit, rate, dir, 0);
 		if (err < 0)
 			goto end;
@@ -158,6 +173,17 @@ int snd_fwspk_stream_start(struct fwspk *fwspk,
 			err = -EIO;
 			goto end;
 		}
+
+		/*
+		 * NOTE:
+		 * No matter for recursive call because this opposite stream
+		 * will start at current sampling rate.
+		 */
+		if (opposite) {
+			err = snd_fwspk_stream_start(fwspk, opposite, 0);
+			if (err < 0)
+				goto end;
+		}
 	}
 
 	/* set stream formation */
@@ -420,6 +446,7 @@ end:
 int snd_fwspk_stream_discover(struct fwspk *fwspk)
 {
 	u8 plugs[AVC_PLUG_INFO_BUF_COUNT];
+	unsigned int i;
 	int err;
 
 	/* the number of plugs for isoc in/out, ext in/out  */
@@ -440,6 +467,14 @@ int snd_fwspk_stream_discover(struct fwspk *fwspk)
 	err = fill_stream_formations(fwspk, 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 < FWSPK_STREAM_TABLE_ENTRIES; i++) {
+		if (fwspk->tx_stream_formations[i].midi > 0)
+			fwspk->midi_input_ports = 1;
+		else if (fwspk->rx_stream_formations[i].midi > 0)
+			fwspk->midi_output_ports = 1;
+	}
 end:
 	return err;
 }
-- 
1.8.3.2

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

* [PATCH 13/13] speakers: Add hwdep interface
  2014-01-10 15:29 ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Takashi Sakamoto
                     ` (11 preceding siblings ...)
  2014-01-10 15:29   ` [PATCH 12/13] speakers: Add support for capture/playback MIDI messages Takashi Sakamoto
@ 2014-01-10 15:29   ` Takashi Sakamoto
  2014-01-10 15:45   ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Clemens Ladisch
  13 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-10 15:29 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel, ffado-devel

This interface is designed for mixer/control application. To use hwdep
interface, the 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/speakers/Makefile          |   2 +-
 sound/firewire/speakers/speakers.c        |   5 +
 sound/firewire/speakers/speakers.h        |  13 ++
 sound/firewire/speakers/speakers_hwdep.c  | 195 ++++++++++++++++++++++++++++++
 sound/firewire/speakers/speakers_stream.c |  39 ++++++
 7 files changed, 257 insertions(+), 3 deletions(-)
 create mode 100644 sound/firewire/speakers/speakers_hwdep.c

diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h
index 2249483..5d6fa42 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_FWSPK,	/* Oxford FW970/971 based devices */
 
 	/* Don't forget to change the following: */
-	SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_BEBOB
+	SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_FWSPK
 };
 
 struct snd_hwdep_info {
diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h
index d69c0b6..147349e 100644
--- a/include/uapi/sound/firewire.h
+++ b/include/uapi/sound/firewire.h
@@ -54,7 +54,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_FWSPK	4
+/* RME, MOTU, ... */
 
 struct snd_firewire_get_info {
 	unsigned int type; /* SNDRV_FIREWIRE_TYPE_xxx */
diff --git a/sound/firewire/speakers/Makefile b/sound/firewire/speakers/Makefile
index d98d4f7..b7b1e45 100644
--- a/sound/firewire/speakers/Makefile
+++ b/sound/firewire/speakers/Makefile
@@ -1,3 +1,3 @@
 snd-firewire-speakers-objs := speakers_command.o speakers_stream.o speakers_pcm.o speakers_control.o \
-			      speakers_proc.o speakers_midi.o speakers.o
+			      speakers_proc.o speakers_midi.o speakers_hwdep.o speakers.o
 obj-m += snd-firewire-speakers.o
diff --git a/sound/firewire/speakers/speakers.c b/sound/firewire/speakers/speakers.c
index fd16e59..4eff180 100644
--- a/sound/firewire/speakers/speakers.c
+++ b/sound/firewire/speakers/speakers.c
@@ -116,6 +116,7 @@ static int fwspk_probe(struct fw_unit *unit,
 	fwspk->device_info = (const struct device_info *)id->driver_data;
 	mutex_init(&fwspk->mutex);
 	spin_lock_init(&fwspk->lock);
+	init_waitqueue_head(&fwspk->hwdep_wait);
 
 	if (fwspk->device_info == &griffin_firewave)
 		err = firewave_stream_discover(fwspk);
@@ -152,6 +153,10 @@ static int fwspk_probe(struct fw_unit *unit,
 			goto err_card;
 	}
 
+	err = snd_fwspk_create_hwdep(fwspk);
+	if (err < 0)
+		goto err_card;
+
 	snd_card_set_dev(card, &unit->device);
 	err = snd_card_register(card);
 	if (err < 0)
diff --git a/sound/firewire/speakers/speakers.h b/sound/firewire/speakers/speakers.h
index 2c54946..73eecb0 100644
--- a/sound/firewire/speakers/speakers.h
+++ b/sound/firewire/speakers/speakers.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 "../cmp.h"
 #include "../fcp.h"
@@ -64,6 +67,10 @@ struct fwspk {
 	s16 volume[6];
 	s16 volume_min;
 	s16 volume_max;
+
+	int dev_lock_count;
+	bool dev_lock_changed;
+	wait_queue_head_t hwdep_wait;
 };
 
 /* AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) */
@@ -118,6 +125,10 @@ int firewave_stream_discover(struct fwspk *fwspk);
 int lacie_speakers_stream_discover(struct fwspk *fwspk);
 int snd_fwspk_stream_discover(struct fwspk *fwspk);
 
+void snd_fwspk_stream_lock_changed(struct fwspk *fwspk);
+int snd_fwspk_stream_lock_try(struct fwspk *fwspk);
+void snd_fwspk_stream_lock_release(struct fwspk *fwspk);
+
 int snd_fwspk_create_pcm(struct fwspk *fwspk);
 
 int snd_fwspk_create_mixer(struct fwspk *fwspk);
@@ -125,3 +136,5 @@ int snd_fwspk_create_mixer(struct fwspk *fwspk);
 void snd_fwspk_proc_init(struct fwspk *fwspk);
 
 int snd_fwspk_create_midi(struct fwspk *fwspk);
+
+int snd_fwspk_create_hwdep(struct fwspk *fwspk);
diff --git a/sound/firewire/speakers/speakers_hwdep.c b/sound/firewire/speakers/speakers_hwdep.c
new file mode 100644
index 0000000..eae7e184
--- /dev/null
+++ b/sound/firewire/speakers/speakers_hwdep.c
@@ -0,0 +1,195 @@
+/*
+ * speakers_hwdep.c - a part of driver for OXFW970/971 based devices
+ *
+ * Copyright (c) 2013 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+/*
+ * This codes give three functionality.
+ *
+ * 1.get firewire node infomation
+ * 2.get notification about starting/stopping stream
+ * 3.lock/unlock stream
+ */
+
+#include "speakers.h"
+
+static long
+hwdep_read(struct snd_hwdep *hwdep, char __user *buf,  long count,
+	   loff_t *offset)
+{
+	struct fwspk *fwspk = hwdep->private_data;
+	DEFINE_WAIT(wait);
+	union snd_firewire_event event;
+
+	spin_lock_irq(&fwspk->lock);
+
+	while (!fwspk->dev_lock_changed) {
+		prepare_to_wait(&fwspk->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
+		spin_unlock_irq(&fwspk->lock);
+		schedule();
+		finish_wait(&fwspk->hwdep_wait, &wait);
+		if (signal_pending(current))
+			return -ERESTARTSYS;
+		spin_lock_irq(&fwspk->lock);
+	}
+
+	memset(&event, 0, sizeof(event));
+	if (fwspk->dev_lock_changed) {
+		event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
+		event.lock_status.status = (fwspk->dev_lock_count > 0);
+		fwspk->dev_lock_changed = false;
+
+		count = min_t(long, count, sizeof(event.lock_status));
+	}
+
+	spin_unlock_irq(&fwspk->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 fwspk *fwspk = hwdep->private_data;
+	unsigned int events;
+
+	poll_wait(file, &fwspk->hwdep_wait, wait);
+
+	spin_lock_irq(&fwspk->lock);
+	if (fwspk->dev_lock_changed)
+		events = POLLIN | POLLRDNORM;
+	else
+		events = 0;
+	spin_unlock_irq(&fwspk->lock);
+
+	return events;
+}
+
+static int
+hwdep_get_info(struct fwspk *fwspk, void __user *arg)
+{
+	struct fw_device *dev = fw_parent_device(fwspk->unit);
+	struct snd_firewire_get_info info;
+
+	memset(&info, 0, sizeof(info));
+	info.type = SNDRV_FIREWIRE_TYPE_FWSPK;
+	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 fwspk *fwspk)
+{
+	int err;
+
+	spin_lock_irq(&fwspk->lock);
+
+	if (fwspk->dev_lock_count == 0) {
+		fwspk->dev_lock_count = -1;
+		err = 0;
+	} else
+		err = -EBUSY;
+
+	spin_unlock_irq(&fwspk->lock);
+
+	return err;
+}
+
+static int
+hwdep_unlock(struct fwspk *fwspk)
+{
+	int err;
+
+	spin_lock_irq(&fwspk->lock);
+
+	if (fwspk->dev_lock_count == -1) {
+		fwspk->dev_lock_count = 0;
+		err = 0;
+	} else
+		err = -EBADFD;
+
+	spin_unlock_irq(&fwspk->lock);
+
+	return err;
+}
+
+static int
+hwdep_release(struct snd_hwdep *hwdep, struct file *file)
+{
+	struct fwspk *fwspk = hwdep->private_data;
+
+	spin_lock_irq(&fwspk->lock);
+	if (fwspk->dev_lock_count == -1)
+		fwspk->dev_lock_count = 0;
+	spin_unlock_irq(&fwspk->lock);
+
+	return 0;
+}
+
+static int
+hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
+	    unsigned int cmd, unsigned long arg)
+{
+	struct fwspk *fwspk = hwdep->private_data;
+
+	switch (cmd) {
+	case SNDRV_FIREWIRE_IOCTL_GET_INFO:
+		return hwdep_get_info(fwspk, (void __user *)arg);
+	case SNDRV_FIREWIRE_IOCTL_LOCK:
+		return hwdep_lock(fwspk);
+	case SNDRV_FIREWIRE_IOCTL_UNLOCK:
+		return hwdep_unlock(fwspk);
+	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_fwspk_create_hwdep(struct fwspk *fwspk)
+{
+	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(fwspk->card, fwspk->card->driver, 0, &hwdep);
+	if (err < 0)
+		goto end;
+	strcpy(hwdep->name, fwspk->card->driver);
+	hwdep->iface = SNDRV_HWDEP_IFACE_FW_FWSPK;
+	hwdep->ops = hwdep_ops;
+	hwdep->private_data = fwspk;
+	hwdep->exclusive = true;
+end:
+	return err;
+}
diff --git a/sound/firewire/speakers/speakers_stream.c b/sound/firewire/speakers/speakers_stream.c
index 2649f02..cc91160 100644
--- a/sound/firewire/speakers/speakers_stream.c
+++ b/sound/firewire/speakers/speakers_stream.c
@@ -493,3 +493,42 @@ int snd_fwspk_streams_init(struct fwspk *fwspk)
 end:
 	return err;
 }
+
+void snd_fwspk_stream_lock_changed(struct fwspk *fwspk)
+{
+	fwspk->dev_lock_changed = true;
+	wake_up(&fwspk->hwdep_wait);
+}
+
+int snd_fwspk_stream_lock_try(struct fwspk *fwspk)
+{
+	int err;
+
+	spin_lock_irq(&fwspk->lock);
+
+	/* user land lock this */
+	if (fwspk->dev_lock_count < 0) {
+		err = -EBUSY;
+		goto end;
+	}
+
+	/* this is the first time */
+	if (fwspk->dev_lock_count++ == 0)
+		snd_fwspk_stream_lock_changed(fwspk);
+	err = 0;
+end:
+	spin_unlock_irq(&fwspk->lock);
+	return err;
+}
+
+void snd_fwspk_stream_lock_release(struct fwspk *fwspk)
+{
+	spin_lock_irq(&fwspk->lock);
+
+	if (WARN_ON(fwspk->dev_lock_count <= 0))
+		goto end;
+	if (--fwspk->dev_lock_count == 0)
+		snd_fwspk_stream_lock_changed(fwspk);
+end:
+	spin_unlock_irq(&fwspk->lock);
+}
-- 
1.8.3.2

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

* Re: [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI
  2014-01-10 15:29 ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Takashi Sakamoto
                     ` (12 preceding siblings ...)
  2014-01-10 15:29   ` [PATCH 13/13] speakers: Add hwdep interface Takashi Sakamoto
@ 2014-01-10 15:45   ` Clemens Ladisch
  2014-01-10 15:57     ` Takashi Iwai
  2014-01-10 15:59     ` Takashi Sakamoto
  13 siblings, 2 replies; 38+ messages in thread
From: Clemens Ladisch @ 2014-01-10 15:45 UTC (permalink / raw)
  To: Takashi Sakamoto, tiwai, perex; +Cc: alsa-devel, ffado-devel

Takashi Sakamoto wrote:
> In previous series of patch, I showed an idea to add a new driver for
> OXFW970/971. This driver can support some recording equipments.
> http://mailman.alsa-project.org/pipermail/alsa-devel/2014-January/070705.html
>
> ALSA already has a driver, firewire-speakers for these chipsets. But it
> supports PCM playback only. The devices need support for capture/playback of
> PCM/MIDI.
>
> According to maintainer's advice, I'll add these functionality to
> firewire-speakers.

That driver is called "firewire-speakers" because it is for playback-
only devices.

But there is exactly one known user, so I'd guess it would be okay
to rename it.


(And I really have to review all these patches some day ...)


Regards,
Clemens

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

* Re: [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI
  2014-01-10 15:45   ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Clemens Ladisch
@ 2014-01-10 15:57     ` Takashi Iwai
  2014-01-10 16:18       ` Takashi Sakamoto
  2014-01-10 15:59     ` Takashi Sakamoto
  1 sibling, 1 reply; 38+ messages in thread
From: Takashi Iwai @ 2014-01-10 15:57 UTC (permalink / raw)
  To: Clemens Ladisch; +Cc: alsa-devel, ffado-devel, Takashi Sakamoto

At Fri, 10 Jan 2014 16:45:10 +0100,
Clemens Ladisch wrote:
> 
> Takashi Sakamoto wrote:
> > In previous series of patch, I showed an idea to add a new driver for
> > OXFW970/971. This driver can support some recording equipments.
> > http://mailman.alsa-project.org/pipermail/alsa-devel/2014-January/070705.html
> >
> > ALSA already has a driver, firewire-speakers for these chipsets. But it
> > supports PCM playback only. The devices need support for capture/playback of
> > PCM/MIDI.
> >
> > According to maintainer's advice, I'll add these functionality to
> > firewire-speakers.
> 
> That driver is called "firewire-speakers" because it is for playback-
> only devices.
> 
> But there is exactly one known user, so I'd guess it would be okay
> to rename it.

Yeah, module names aren't that important.  And you can put
MODULE_ALIAS("snd-firewire-speakers"), if any.

> (And I really have to review all these patches some day ...)

Yes, pretty please :)


Takashi

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

* Re: [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI
  2014-01-10 15:45   ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Clemens Ladisch
  2014-01-10 15:57     ` Takashi Iwai
@ 2014-01-10 15:59     ` Takashi Sakamoto
  1 sibling, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-10 15:59 UTC (permalink / raw)
  To: Clemens Ladisch, tiwai, perex; +Cc: alsa-devel, ffado-devel

Hi Clemens,

(Jan 11 2013 00:45), Clemens Ladisch wrote:
> That driver is called "firewire-speakers" because it is for playback-
> only devices.
>
> But there is exactly one known user, so I'd guess it would be okay
> to rename it.

I've found the characters in firmware binary blob in general update 
tools produced by Oxford Semiconductor. So I guessed this is the reason 
of the name for this driver.

If possible, I really want to rename it because it's too long to write 
;) But I think renaming driver is a bit delicate issue.

For this issue, I need comments from the other maintainers. I guess I 
can do it if Iwai-san agrees.


Thanks

Takashi Sakamoto

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

* Re: [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI
  2014-01-10 15:57     ` Takashi Iwai
@ 2014-01-10 16:18       ` Takashi Sakamoto
  2014-01-12 12:35         ` Stefan Richter
  0 siblings, 1 reply; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-10 16:18 UTC (permalink / raw)
  To: Takashi Iwai, Clemens Ladisch; +Cc: alsa-devel, ffado-devel

Hi Iwai-san,

(Jan 11 2014 00:57), Takashi Iwai wrote:
 > Yeah, module names aren't that important.  And you can put
 > MODULE_ALIAS("snd-firewire-speakers"), if any.

OK. I decide to rename the driver to 'snd-oxfw, and add the alias.


Thanks

Takashi Sakamoto
o-takashi@sakamocchi.jp

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

* Re: [PATCH 06/13] speakers: Change the way to make PCM rules/constraints
  2014-01-10 15:29   ` [PATCH 06/13] speakers: Change the way to make PCM rules/constraints Takashi Sakamoto
@ 2014-01-12 12:18     ` Stefan Richter
  2014-01-12 14:44       ` Takashi Sakamoto
  0 siblings, 1 reply; 38+ messages in thread
From: Stefan Richter @ 2014-01-12 12:18 UTC (permalink / raw)
  To: Takashi Sakamoto; +Cc: tiwai, alsa-devel, clemens, ffado-devel

On Jan 11 Takashi Sakamoto wrote:
> I don't know whether Griffin/LaCie devices supports SINGLE/LIST subfunction of
> 'AV/C Stream Format Information' command. So this commit adds codes to
> generate formations for them.

I have got both devices.  I could perform specific tests with them if you
want and give me instructions.  It can be a very slow process though
because I have got almost no spare time, at least throughout January and
February.
-- 
Stefan Richter
-=====-====- ---= -==--
http://arcgraph.de/sr/

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

* Re: [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI
  2014-01-10 16:18       ` Takashi Sakamoto
@ 2014-01-12 12:35         ` Stefan Richter
  2014-01-12 14:53           ` Takashi Sakamoto
  0 siblings, 1 reply; 38+ messages in thread
From: Stefan Richter @ 2014-01-12 12:35 UTC (permalink / raw)
  To: Takashi Sakamoto; +Cc: Takashi Iwai, alsa-devel, Clemens Ladisch, ffado-devel

On Jan 11 Takashi Sakamoto wrote:
> (Jan 11 2014 00:57), Takashi Iwai wrote:
>  > Yeah, module names aren't that important.  And you can put
>  > MODULE_ALIAS("snd-firewire-speakers"), if any.
> 
> OK. I decide to rename the driver to 'snd-oxfw, and add the alias.

I agree that it should be renamed, and 'snd-oxfw' looks good to me.  The
module alias wouldn't even be strictly necessary for a driver like this
(probably not present in initrd images etc.), but it doesn't hurt to
include it.

The constants which patch 13/13 adds to include/uapi/sound/{asound,firewire}.h
should of course be named accordingly then.
-- 
Stefan Richter
-=====-====- ---= -==--
http://arcgraph.de/sr/

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

* Re: [PATCH 06/13] speakers: Change the way to make PCM rules/constraints
  2014-01-12 12:18     ` Stefan Richter
@ 2014-01-12 14:44       ` Takashi Sakamoto
  2014-01-30 14:21         ` Stefan Richter
  0 siblings, 1 reply; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-12 14:44 UTC (permalink / raw)
  To: Stefan Richter; +Cc: tiwai, alsa-devel, clemens, ffado-devel

Hi Stefan,

 > I have got both devices.  I could perform specific tests with them if
 > you want and give me instructions.  It can be a very slow process
 > though because I have got almost no spare time, at least throughout
 > January and February.

Thank you. Please confirm these items below with Clemens' jujuutils when 
you have a free  time:

1.They have both iPCR/oPCR.
  $ ./firewire-request /dev/fw1 read 0xfffff0000900 (oMPR)
  $ ./firewire-request /dev/fw1 read 0xfffff0000980 (iMPR)
If they have, I can apply the same way to change sampling rate for all 
of models.

2.They can respond to 'SINGLE' subfunction of 'AV/C Stream Format 
Information' command.
   $ ./firewire-request /dev/fw1 fcp  0x01ffbfc000000000ffffffff
    response: 000: 0c ff bf c0 00 00 00 00 ff 00 90 40 04 02 01 02 
...........@....
    response: 010: 06
   This is an example of Behringer F-Control A 202(OXFW970).

3.They can respond to 'LIST' subfunction of 'AV/C Stream Format 
Information' command.
   $ ./firewire-request /dev/fw1 fcp  0x01ffbfc100000000ffff01ff
   I have no devices which supports this command. I guess OXFW971 
supports this.

It's better to receive your results till the middle of February.

I have a plan to post all of my patches with many fixments in the end of 
January. This may be my last RFCs. Then I recall for testing to some 
mailing lists like LAD. If everything goes well, I hope to post 
merge-request in the end of February.


Thanks

Takashi Sakamoto
o-takashi@sakamocchi.jp

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

* Re: [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI
  2014-01-12 12:35         ` Stefan Richter
@ 2014-01-12 14:53           ` Takashi Sakamoto
  0 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-01-12 14:53 UTC (permalink / raw)
  To: Stefan Richter; +Cc: Takashi Iwai, alsa-devel, Clemens Ladisch, ffado-devel

Hi Stefan,

 > The constants which patch 13/13 adds to
 >  include/uapi/sound/{asound,firewire}.h
 > should of course be named accordingly then.

Yes. I forgot to rename it...

And I'm really sorry to maintainer but this patch (for hwdep) includes 
more bugs. I just added hwdep functionality but forgot to add codes to 
use it in PCM/MIDI functionality.

The codes are almost the same as the other drivers 
(snd-fireworks/snd-bebob) have. So in this time I hope you consider 
about the items which I mentioned in the first post.
http://mailman.alsa-project.org/pipermail/alsa-devel/2014-January/070705.html


Thanks

Takashi Sakamoto
o-takashi@sakamocchi.jp

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

* Re: [PATCH 06/13] speakers: Change the way to make PCM rules/constraints
  2014-01-12 14:44       ` Takashi Sakamoto
@ 2014-01-30 14:21         ` Stefan Richter
  2014-02-01  6:31           ` Takashi Sakamoto
  0 siblings, 1 reply; 38+ messages in thread
From: Stefan Richter @ 2014-01-30 14:21 UTC (permalink / raw)
  To: Takashi Sakamoto; +Cc: tiwai, alsa-devel, clemens, ffado-devel

On Jan 12 Takashi Sakamoto wrote:
> Hi Stefan,
> 
>  > I have got both devices.  I could perform specific tests with them if
>  > you want and give me instructions.  It can be a very slow process
>  > though because I have got almost no spare time, at least throughout
>  > January and February.
> 
> Thank you. Please confirm these items below with Clemens' jujuutils when 
> you have a free  time:

Thanks for these straightforward instructions.  Here are the results.

> 1.They have both iPCR/oPCR.
>   $ ./firewire-request /dev/fw1 read 0xfffff0000900 (oMPR)
>   $ ./firewire-request /dev/fw1 read 0xfffff0000980 (iMPR)
> If they have, I can apply the same way to change sampling rate for all 
> of models.

LaCie Speakers:
---------------
$ firewire-request /dev/fw7 read 0xfffff0000900
result: 80ff0000
$ firewire-request /dev/fw7 read 0xfffff0000980
result: 80ff0001

Griffin FireWave:
-----------------
$ firewire-request /dev/fw8 read 0xfffff0000900
result: 80ff0000
$ firewire-request /dev/fw8 read 0xfffff0000980
result: 80ff0001


> 2.They can respond to 'SINGLE' subfunction of 'AV/C Stream Format 
> Information' command.
>    $ ./firewire-request /dev/fw1 fcp  0x01ffbfc000000000ffffffff
>     response: 000: 0c ff bf c0 00 00 00 00 ff 00 90 40 04 02 01 02 ...........@....
>     response: 010: 06
>    This is an example of Behringer F-Control A 202(OXFW970).

LaCie Speakers:
---------------
$ firewire-request /dev/fw7 fcp 0x01ffbfc000000000ffffffff
response: 000: 0c ff bf c0 00 00 00 00 ff 00 90 40 04 02 01 02 ...........@....
response: 010: 06                                              .

Griffin FireWave:
-----------------
$ firewire-request /dev/fw8 fcp 0x01ffbfc000000000ffffffff
response: 000: 0c ff bf c0 00 00 00 00 ff 00 90 40 04 02 01 06 ...........@....
response: 010: 06                                              .


> 3.They can respond to 'LIST' subfunction of 'AV/C Stream Format 
> Information' command.
>    $ ./firewire-request /dev/fw1 fcp  0x01ffbfc100000000ffff01ff
>    I have no devices which supports this command. I guess OXFW971 
> supports this.

LaCie Speakers:
---------------
$ firewire-request /dev/fw7 fcp 0x01ffbfc100000000ffff01ff
response: 000: 0c ff bf c1 00 00 00 00 ff 00 01 90 40 03 02 01 ............@...
response: 010: 02 06                                           ..

Griffin FireWave:
-----------------
$ firewire-request /dev/fw8 fcp 0x01ffbfc100000000ffff01ff
response: 000: 0c ff bf c1 00 00 00 00 ff 00 01 90 40 02 02 01 ............@...
response: 010: 06 06                                           ..

-- 
Stefan Richter
-=====-====- ---= ====-
http://arcgraph.de/sr/

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

* Re: [PATCH 06/13] speakers: Change the way to make PCM rules/constraints
  2014-01-30 14:21         ` Stefan Richter
@ 2014-02-01  6:31           ` Takashi Sakamoto
  2014-02-02 15:45             ` Stefan Richter
  0 siblings, 1 reply; 38+ messages in thread
From: Takashi Sakamoto @ 2014-02-01  6:31 UTC (permalink / raw)
  To: Stefan Richter; +Cc: tiwai, alsa-devel, clemens, ffado-devel

Hi Stefan,

Thanks to get these logs. It's really helpful to me.

>> 1.They have both iPCR/oPCR.

They have no oPCRs, against my guess.

I wrote snd-oxfw with an assumption that devices have iPCR/oPCR. So I 
must write some patches for my last RFC:

http://mailman.alsa-project.org/pipermail/alsa-devel/2014-January/071820.html

>> 2.They can respond to 'SINGLE' subfunction of 'AV/C Stream Format
>> Information' command.

OK. They supports this subfunction.

>> 3.They can respond to 'LIST' subfunction of 'AV/C Stream Format
>> Information' command.

OK. They supports this subfunction. The snd-oxfw can detect 
channnels/rates for these devices instead of hard-coded parameters.


Thanks

Takashi Sakamoto
o-takashi@sakamocchi.jp

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

* Re: [PATCH 06/13] speakers: Change the way to make PCM rules/constraints
  2014-02-01  6:31           ` Takashi Sakamoto
@ 2014-02-02 15:45             ` Stefan Richter
  2014-02-03  3:07               ` Takashi Sakamoto
  0 siblings, 1 reply; 38+ messages in thread
From: Stefan Richter @ 2014-02-02 15:45 UTC (permalink / raw)
  To: Takashi Sakamoto; +Cc: tiwai, alsa-devel, clemens, ffado-devel

On Feb 01 Takashi Sakamoto wrote:
> Hi Stefan,
> 
> Thanks to get these logs. It's really helpful to me.
> 
> >> 1.They have both iPCR/oPCR.
> 
> They have no oPCRs, against my guess.
> 
> I wrote snd-oxfw with an assumption that devices have iPCR/oPCR. So I 
> must write some patches for my last RFC:

Neither LaCie FireWire Speakers nor Griffin FireWave have physical
recording inputs.  It is therefore sensible that they present
OUTPUT_MASTER_PLUG.output_plugs == 0.

By the way, while LaCie FireWire Speakers is a stereo playback device,
Griffin FireWave is a 6-channel playback device with six physical outputs
(three 3.5 mm stereo receptacles).  It also contains a "Dolby™ Digital
decoder and a Dolby Pro Logic II adaptive matrix surround decoder"
according to the user's manual.  I have no idea how much of that is
implemented in the FireWave itself and how much is done in the FireWave
driver for OS X.

Apple' OS X built-in FireWire audio driver presents the FireWave as a
output-only device without any control besides the global joint
volume control.  I don't know whether it works as a 6-channel device
with the stock driver; it evidently does work at least as a 2-channel
device with stereo sound.  In order to use Dolby features on OS X, a
custom driver from Griffin needs to be installed.  I haven't tried that
driver myself yet, but I have heard from somebody else that this driver
emits vendor-specific (FCP?) requests to the FireWave to set up 5.1
playback.

I do have a Mac which dual-boots to Apple OS X, and a PCILynx card which
I can put into a Linux box, so I could attempt to analyze those vendor-
specific requests that enable 5.1 output.  Though currently, live seems
too short to bother.

Just in case it tells anything useful, here are some parts of alsa-info's
output (from alsa-info.sh version 0.4.63 and kernel 3.13-rc5 without your
patches; I haven't tried your patches yet):

!!Soundcards recognised by ALSA
!!-----------------------------
[...]
 2 [FireWave       ]: FireWave - FireWave
                      Griffin FireWave Surround (OXFW970 0104), GUID 0012920600520991 at fw6.0, S400
 3 [Speakers       ]: FWSpeakers - FireWire Speakers
                      LaCie FireWire Speakers (OXFW970 0105), GUID 00d04b21010212c8 at fw7.0, S400

!!Aplay/Arecord output
!!--------------------

APLAY

**** List of PLAYBACK Hardware Devices ****
[...]
card 2: FireWave [FireWave], device 0: OXFW970 [FireWave]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 3: Speakers [FireWire Speakers], device 0: OXFW970 [FireWire Speakers]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

ARECORD

**** List of CAPTURE Hardware Devices ****
[...of course nothing from cards 2 and 3....]

!!Amixer output
!!-------------
[...]
!!-------Mixer controls for card 2 [FireWave]

Card hw:2 'FireWave'/'Griffin FireWave Surround (OXFW970 0104), GUID 0012920600520991 at fw6.0, S400'
  Mixer name	: 'OXFW970'
  Components	: ''
  Controls      : 2
  Simple ctrls  : 1
Simple mixer control 'PCM',0
  Capabilities: pvolume pswitch pswitch-joined
  Playback channels: Front Left - Front Right - Rear Left - Rear Right - Front Center - Woofer
  Limits: Playback -32768 - 0
  Mono:
  Front Left: Playback 0 [100%] [on]
  Front Right: Playback 0 [100%] [on]
  Rear Left: Playback 0 [100%] [on]
  Rear Right: Playback 0 [100%] [on]
  Front Center: Playback 0 [100%] [on]
  Woofer: Playback 0 [100%] [on]

!!-------Mixer controls for card 3 [Speakers]

Card hw:3 'Speakers'/'LaCie FireWire Speakers (OXFW970 0105), GUID 00d04b21010212c8 at fw7.0, S400'
  Mixer name	: 'OXFW970'
  Components	: ''
  Controls      : 2
  Simple ctrls  : 1
Simple mixer control 'PCM',0
  Capabilities: pvolume pvolume-joined pswitch pswitch-joined
  Playback channels: Mono
  Limits: Playback -11538 - 0
  Mono: Playback -3137 [73%] [on]

!!Alsactl output
!!--------------
[...]
state.FireWave {
	control.1 {
		iface MIXER
		name 'PCM Playback Switch'
		value true
		comment {
			access 'read write'
			type BOOLEAN
			count 1
		}
	}
	control.2 {
		iface MIXER
		name 'PCM Playback Volume'
		value.0 0
		value.1 0
		value.2 0
		value.3 0
		value.4 0
		value.5 0
		comment {
			access 'read write'
			type INTEGER
			count 6
			range '-32768 - 0'
		}
	}
}
state.Speakers {
	control.1 {
		iface MIXER
		name 'PCM Playback Switch'
		value true
		comment {
			access 'read write'
			type BOOLEAN
			count 1
		}
	}
	control.2 {
		iface MIXER
		name 'PCM Playback Volume'
		value -3137
		comment {
			access 'read write'
			type INTEGER
			count 1
			range '-11538 - 0'
		}
	}
}

-- 
Stefan Richter
-=====-====- --=- ---=-
http://arcgraph.de/sr/
_______________________________________________
Alsa-devel mailing list
Alsa-devel@alsa-project.org
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel

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

* Re: [PATCH 06/13] speakers: Change the way to make PCM rules/constraints
  2014-02-02 15:45             ` Stefan Richter
@ 2014-02-03  3:07               ` Takashi Sakamoto
  0 siblings, 0 replies; 38+ messages in thread
From: Takashi Sakamoto @ 2014-02-03  3:07 UTC (permalink / raw)
  To: Stefan Richter; +Cc: tiwai, alsa-devel, clemens, ffado-devel

Stefan,

 > Neither LaCie FireWire Speakers nor Griffin FireWave have physical
 > recording inputs. It is therefore sensible that they present
 > OUTPUT_MASTER_PLUG.output_plugs == 0.

In this case for Lacie/Griffin, this reasoning is true.

But it's not always true.

I did adhere to infrastructure for non 'SYT-Match' when I wrote snd-oxfw.

 > I haven't tried your patches yet):

The snd-oxfw with these patches cannot handle Lacie/Griffin.
It fails in probe() function. It's my fault.
I've update these patches and push them into my repository.
Please use my repository when you test.


Regards

Takashi Sakamoto

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

end of thread, other threads:[~2014-02-03  3:08 UTC | newest]

Thread overview: 38+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2014-01-05 11:13 [RFC][PATCH 0/8] A new driver for OXFW970/971 based devices Takashi Sakamoto
2014-01-05 11:13 ` [PATCH 1/8] oxfw: Add skelton " Takashi Sakamoto
2014-01-05 11:13 ` [PATCH 2/8] oxfw: Read firmware version to name card Takashi Sakamoto
2014-01-05 11:13 ` [PATCH 3/8] oxfw: Add some AV/C commands for channel formation of AMDTP stream Takashi Sakamoto
2014-01-05 11:13 ` [PATCH 4/8] oxfw: Add connections and streams management Takashi Sakamoto
2014-01-05 11:13 ` [PATCH 5/8] oxfw: Add proc interface for debugging purpose Takashi Sakamoto
2014-01-05 11:13 ` [PATCH 6/8] oxfw: Add MIDI interface Takashi Sakamoto
2014-01-05 11:13 ` [PATCH 7/8] oxfw: Add PCM interface Takashi Sakamoto
2014-01-05 11:13 ` [PATCH 8/8] oxfw: Add hwdep interface Takashi Sakamoto
2014-01-05 19:46 ` [RFC][PATCH 0/8] A new driver for OXFW970/971 based devices Takashi Iwai
2014-01-06  8:33   ` Takashi Sakamoto
2014-01-07 10:07     ` Takashi Iwai
2014-01-10 15:29 ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Takashi Sakamoto
2014-01-10 15:29   ` [PATCH 01/13] speakers: move to its own directory Takashi Sakamoto
2014-01-10 15:29   ` [PATCH 02/13] speakers: Split stream functionality to a new file and add a header file Takashi Sakamoto
2014-01-10 15:29   ` [PATCH 03/13] speakers: Split PCM functionality to a new file Takashi Sakamoto
2014-01-10 15:29   ` [PATCH 04/13] speakers: Split control " Takashi Sakamoto
2014-01-10 15:29   ` [PATCH 05/13] speakers: Change the way to name card Takashi Sakamoto
2014-01-10 15:29   ` [PATCH 06/13] speakers: Change the way to make PCM rules/constraints Takashi Sakamoto
2014-01-12 12:18     ` Stefan Richter
2014-01-12 14:44       ` Takashi Sakamoto
2014-01-30 14:21         ` Stefan Richter
2014-02-01  6:31           ` Takashi Sakamoto
2014-02-02 15:45             ` Stefan Richter
2014-02-03  3:07               ` Takashi Sakamoto
2014-01-10 15:29   ` [PATCH 07/13] speakers: Add proc interface for debugging purpose Takashi Sakamoto
2014-01-10 15:29   ` [PATCH 08/13] speakers: Change the way to start stream Takashi Sakamoto
2014-01-10 15:29   ` [PATCH 09/13] speakers: Add some AV/C commands to get stream formation and supported sampling rates Takashi Sakamoto
2014-01-10 15:29   ` [PATCH 10/13] speakers: Add support for Behringer/Mackie devices Takashi Sakamoto
2014-01-10 15:29   ` [PATCH 11/13] speakers: Add support AMDTP in-stream and PCM capture Takashi Sakamoto
2014-01-10 15:29   ` [PATCH 12/13] speakers: Add support for capture/playback MIDI messages Takashi Sakamoto
2014-01-10 15:29   ` [PATCH 13/13] speakers: Add hwdep interface Takashi Sakamoto
2014-01-10 15:45   ` [RFC][PATCH v2 00/13] speakers: Add support for capture/playback of PCM/MIDI Clemens Ladisch
2014-01-10 15:57     ` Takashi Iwai
2014-01-10 16:18       ` Takashi Sakamoto
2014-01-12 12:35         ` Stefan Richter
2014-01-12 14:53           ` Takashi Sakamoto
2014-01-10 15:59     ` 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.