All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/8][RFC] a driver for Fireworks based devices
@ 2013-12-11  9:15 Takashi Sakamoto
  2013-12-11  9:15 ` [PATCH 1/8] fireworks: Add skelton " Takashi Sakamoto
                   ` (7 more replies)
  0 siblings, 8 replies; 9+ messages in thread
From: Takashi Sakamoto @ 2013-12-11  9:15 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel

This series of patch is update of my previous one:
 [PATCH 0/8] [RFC] new driver for Echo Audio's Fireworks based devices
 http://mailman.alsa-project.org/pipermail/alsa-devel/2013-June/062614.html

This series of patch is based on this series:
 [RFC][PATCH 00/17] Enhancement for firewire-lib
 http://mailman.alsa-project.org/pipermail/alsa-devel/2013-November/069163.html

I hope to get comments, especially for hwdep interface for Fireworks
command/response which was discussed below:
 include/uapi/firewire.h for other firewire drivers
 http://mailman.alsa-project.org/pipermail/alsa-devel/2013-October/067743.html

Takashi Sakamoto (8):
  fireworks: Add skelton for Fireworks based devices
  fireworks: Add transaction and some commands
  fireworks: Add connection and stream management
  fireworks: Add proc interface for debugging purpose
  fireworks: Add MIDI interface
  fireworks: Add PCM interface
  fireworks: Add hwdep interface
  fireworks: Add command/response functionality into hwdep interface

 include/uapi/sound/asound.h                      |   3 +-
 include/uapi/sound/firewire.h                    |  19 +
 sound/firewire/Kconfig                           |  15 +
 sound/firewire/Makefile                          |   1 +
 sound/firewire/fireworks/Makefile                |   5 +
 sound/firewire/fireworks/fireworks.c             | 344 +++++++++++++++++
 sound/firewire/fireworks/fireworks.h             | 239 ++++++++++++
 sound/firewire/fireworks/fireworks_command.c     | 401 ++++++++++++++++++++
 sound/firewire/fireworks/fireworks_hwdep.c       | 310 +++++++++++++++
 sound/firewire/fireworks/fireworks_midi.c        | 176 +++++++++
 sound/firewire/fireworks/fireworks_pcm.c         | 459 +++++++++++++++++++++++
 sound/firewire/fireworks/fireworks_proc.c        | 216 +++++++++++
 sound/firewire/fireworks/fireworks_stream.c      | 364 ++++++++++++++++++
 sound/firewire/fireworks/fireworks_transaction.c | 340 +++++++++++++++++
 14 files changed, 2891 insertions(+), 1 deletion(-)
 create mode 100644 sound/firewire/fireworks/Makefile
 create mode 100644 sound/firewire/fireworks/fireworks.c
 create mode 100644 sound/firewire/fireworks/fireworks.h
 create mode 100644 sound/firewire/fireworks/fireworks_command.c
 create mode 100644 sound/firewire/fireworks/fireworks_hwdep.c
 create mode 100644 sound/firewire/fireworks/fireworks_midi.c
 create mode 100644 sound/firewire/fireworks/fireworks_pcm.c
 create mode 100644 sound/firewire/fireworks/fireworks_proc.c
 create mode 100644 sound/firewire/fireworks/fireworks_stream.c
 create mode 100644 sound/firewire/fireworks/fireworks_transaction.c

-- 
1.8.3.2

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

* [PATCH 1/8] fireworks: Add skelton for Fireworks based devices
  2013-12-11  9:15 [PATCH v2 0/8][RFC] a driver for Fireworks based devices Takashi Sakamoto
@ 2013-12-11  9:15 ` Takashi Sakamoto
  2013-12-11  9:15 ` [PATCH 2/8] fireworks: Add transaction and some commands Takashi Sakamoto
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Takashi Sakamoto @ 2013-12-11  9:15 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel

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

Current supported devices:
 - Mackie Onyx 400F/1200F
 - Echo AudioFire8(until 2009 July)/AudioFire12
 - Echo AudioFire2/4/8(since 2009 July)/Pre8
 - Echo Fireworks 8/HDMI
 - Gibson Robot Interface pack/GoldTop

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/Kconfig               |  15 +++
 sound/firewire/Makefile              |   1 +
 sound/firewire/fireworks/Makefile    |   2 +
 sound/firewire/fireworks/fireworks.c | 195 +++++++++++++++++++++++++++++++++++
 sound/firewire/fireworks/fireworks.h |  52 ++++++++++
 5 files changed, 265 insertions(+)
 create mode 100644 sound/firewire/fireworks/Makefile
 create mode 100644 sound/firewire/fireworks/fireworks.c
 create mode 100644 sound/firewire/fireworks/fireworks.h

diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig
index b3e274f..46f9f25 100644
--- a/sound/firewire/Kconfig
+++ b/sound/firewire/Kconfig
@@ -61,4 +61,19 @@ config SND_SCS1X
 	  To compile this driver as a module, choose M here: the module
 	  will be called snd-scs1x.
 
+config SND_FIREWORKS
+	tristate "Echo Fireworks board module support"
+	select SND_FIREWIRE_LIB
+	help
+	  Say Y here to include support for FireWire devices based
+	  on Echo Digital Audio Fireworks board:
+	   * Mackie Onyx 400F/1200F
+	   * Echo AudioFire8(until 2009 July)/AudioFire12
+	   * Echo AudioFire2/4/8(since 2009 July)/Pre8
+	   * Echo Fireworks 8/HDMI
+	   * Gibson Robot Interface Pack/GoldTop
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-fireworks.
+
 endif # SND_FIREWIRE
diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile
index 5099550..5cd39dc 100644
--- a/sound/firewire/Makefile
+++ b/sound/firewire/Makefile
@@ -10,3 +10,4 @@ obj-$(CONFIG_SND_DICE) += snd-dice.o
 obj-$(CONFIG_SND_FIREWIRE_SPEAKERS) += snd-firewire-speakers.o
 obj-$(CONFIG_SND_ISIGHT) += snd-isight.o
 obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o
+obj-$(CONFIG_SND_FIREWORKS) += fireworks/
diff --git a/sound/firewire/fireworks/Makefile b/sound/firewire/fireworks/Makefile
new file mode 100644
index 0000000..99f6fc3
--- /dev/null
+++ b/sound/firewire/fireworks/Makefile
@@ -0,0 +1,2 @@
+snd-fireworks-objs := fireworks.o
+obj-m += snd-fireworks.o
diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c
new file mode 100644
index 0000000..3b62387
--- /dev/null
+++ b/sound/firewire/fireworks/fireworks.c
@@ -0,0 +1,195 @@
+/*
+ * fireworks.c - a part of driver for Fireworks based devices
+ *
+ * Copyright (c) 2009-2010 Clemens Ladisch
+ * Copyright (c) 2013 Takashi Sakamoto
+ *
+ *
+ * This driver is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2.
+ *
+ * This driver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this driver; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Fireworks is a board module which Echo Audio produces. This board module
+ * consists of three chipsets:
+ *  - Communication chipset for IEEE1394 PHY/Link and IEC 61883-1/6
+ *  - DSP or/and FPGA for signal processing
+ *  - Flash Memory to store firmwares
+ */
+
+#include "fireworks.h"
+
+MODULE_DESCRIPTION("Echo Fireworks 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 Fireworks sound card");
+
+static DEFINE_MUTEX(devices_mutex);
+static unsigned int devices_used;
+
+#define VENDOR_LOUD			0x000ff2
+#define  MODEL_MACKIE_400F		0x00400f
+#define  MODEL_MACKIE_1200F		0x01200f
+
+#define VENDOR_ECHO			0x001486
+#define  MODEL_ECHO_AUDIOFIRE_12	0x00af12
+#define  MODEL_ECHO_AUDIOFIRE_12HD	0x0af12d
+#define  MODEL_ECHO_AUDIOFIRE_12_APPLE	0x0af12a
+/* This is applied for AudioFire8 (until 2009 July) */
+#define  MODEL_ECHO_AUDIOFIRE_8		0x000af8
+#define  MODEL_ECHO_AUDIOFIRE_2		0x000af2
+#define  MODEL_ECHO_AUDIOFIRE_4		0x000af4
+/* AudioFire9 is applied for AudioFire8(since 2009 July) and AudioFirePre8 */
+#define  MODEL_ECHO_AUDIOFIRE_9		0x000af9
+/* unknown as product */
+#define  MODEL_ECHO_FIREWORKS_8		0x0000f8
+#define  MODEL_ECHO_FIREWORKS_HDMI	0x00afd1
+
+#define VENDOR_GIBSON			0x00075b
+/* for Robot Interface Pack of Dark Fire, Dusk Tiger, Les Paul Standard 2010 */
+#define  MODEL_GIBSON_RIP		0x00afb2
+/* unknown as product */
+#define  MODEL_GIBSON_GOLDTOP		0x00afb9
+
+static void
+efw_card_free(struct snd_card *card)
+{
+	struct snd_efw *efw = card->private_data;
+
+	if (efw->card_index >= 0) {
+		mutex_lock(&devices_mutex);
+		devices_used &= ~BIT(efw->card_index);
+		mutex_unlock(&devices_mutex);
+	}
+
+	mutex_destroy(&efw->mutex);
+
+	return;
+}
+
+static int
+efw_probe(struct fw_unit *unit,
+	  const struct ieee1394_device_id *entry)
+{
+	struct snd_card *card;
+	struct snd_efw *efw;
+	int card_index, err;
+
+	mutex_lock(&devices_mutex);
+
+	/* check registered cards */
+	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_efw), &card);
+	if (err < 0)
+		goto end;
+	card->private_free = efw_card_free;
+
+	efw = card->private_data;
+	efw->card = card;
+	efw->device = fw_parent_device(unit);
+	efw->unit = unit;
+	efw->card_index = -1;
+	mutex_init(&efw->mutex);
+	spin_lock_init(&efw->lock);
+
+	strcpy(efw->card->driver, "Fireworks");
+	strcpy(efw->card->shortname, "Fireworks");
+	strcpy(efw->card->longname, "Fireworks");
+	strcpy(efw->card->mixername, "Fireworks");
+
+	snd_card_set_dev(card, &unit->device);
+	err = snd_card_register(card);
+	if (err < 0) {
+		snd_card_free(card);
+		goto end;
+	}
+	dev_set_drvdata(&unit->device, efw);
+	devices_used |= BIT(card_index);
+	efw->card_index = card_index;
+end:
+	mutex_unlock(&devices_mutex);
+	return err;
+}
+
+static void efw_update(struct fw_unit *unit)
+{
+	return;
+}
+
+static void efw_remove(struct fw_unit *unit)
+{
+	struct snd_efw *efw = dev_get_drvdata(&unit->device);
+	snd_card_disconnect(efw->card);
+	snd_card_free_when_closed(efw->card);
+	return;
+}
+
+static const struct ieee1394_device_id efw_id_table[] = {
+	SND_EFW_DEV_ENTRY(VENDOR_LOUD, MODEL_MACKIE_400F),
+	SND_EFW_DEV_ENTRY(VENDOR_LOUD, MODEL_MACKIE_1200F),
+	SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_8),
+	SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_12),
+	SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_12HD),
+	SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_12_APPLE),
+	SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_2),
+	SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_4),
+	SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_9),
+	SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_FIREWORKS_8),
+	SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_FIREWORKS_HDMI),
+	SND_EFW_DEV_ENTRY(VENDOR_GIBSON, MODEL_GIBSON_RIP),
+	SND_EFW_DEV_ENTRY(VENDOR_GIBSON, MODEL_GIBSON_GOLDTOP),
+	{}
+};
+MODULE_DEVICE_TABLE(ieee1394, efw_id_table);
+
+static struct fw_driver efw_driver = {
+	.driver = {
+		.owner = THIS_MODULE,
+		.name = "snd-fireworks",
+		.bus = &fw_bus_type,
+	},
+	.probe    = efw_probe,
+	.update   = efw_update,
+	.remove   = efw_remove,
+	.id_table = efw_id_table,
+};
+
+static int __init snd_efw_init(void)
+{
+	return driver_register(&efw_driver.driver);
+}
+
+static void __exit snd_efw_exit(void)
+{
+	driver_unregister(&efw_driver.driver);
+	mutex_destroy(&devices_mutex);
+}
+
+module_init(snd_efw_init);
+module_exit(snd_efw_exit);
diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h
new file mode 100644
index 0000000..ed745d0
--- /dev/null
+++ b/sound/firewire/fireworks/fireworks.h
@@ -0,0 +1,52 @@
+/*
+ * fireworks.h - a part of driver for Fireworks based devices
+ *
+ * Copyright (c) 2009-2010 Clemens Ladisch
+ * Copyright (c) 2013 Takashi Sakamoto
+ *
+ *
+ * This driver is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2.
+ *
+ * This driver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this driver; if not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef SOUND_FIREWORKS_H_INCLUDED
+#define SOUND_FIREWORKS_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_efw {
+	struct snd_card *card;
+	struct fw_device *device;
+	struct fw_unit *unit;
+	int card_index;
+
+	struct mutex mutex;
+	spinlock_t lock;
+};
+
+#define SND_EFW_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] 9+ messages in thread

* [PATCH 2/8] fireworks: Add transaction and some commands
  2013-12-11  9:15 [PATCH v2 0/8][RFC] a driver for Fireworks based devices Takashi Sakamoto
  2013-12-11  9:15 ` [PATCH 1/8] fireworks: Add skelton " Takashi Sakamoto
@ 2013-12-11  9:15 ` Takashi Sakamoto
  2013-12-11  9:15 ` [PATCH 3/8] fireworks: Add connection and stream management Takashi Sakamoto
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Takashi Sakamoto @ 2013-12-11  9:15 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel

Fireworks uses own command and response. This commit adds functionality to
transact and adds some commands required for sound card instance and kernel
streaming.

There are two ways to deliver substance of this transaction:
1.AV/C vendor dependent command for command/response
2.Specific addresses for command/response

By way 1, I confirm AudioFire12 cannot correctly response to some commands with
firmware version 5.0 or later. This is also confirmed by FFADO. So this module
implement way 2.

The address for response gives an issue. When this driver allocate own callback
function into the address, then no one can allocate its own callback function.
This situation is not good for applications in user-land. Currently this commit
don't solve this issue. This issue is solved in later commit.

I note there is a command to change the address for response if the device
supports. But this module uses default value. So users should not execute this
command as long as hoping this module works correctly.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/fireworks/Makefile                |   2 +-
 sound/firewire/fireworks/fireworks.c             |  74 ++++-
 sound/firewire/fireworks/fireworks.h             | 127 ++++++++
 sound/firewire/fireworks/fireworks_command.c     | 399 +++++++++++++++++++++++
 sound/firewire/fireworks/fireworks_transaction.c | 204 ++++++++++++
 5 files changed, 796 insertions(+), 10 deletions(-)
 create mode 100644 sound/firewire/fireworks/fireworks_command.c
 create mode 100644 sound/firewire/fireworks/fireworks_transaction.c

diff --git a/sound/firewire/fireworks/Makefile b/sound/firewire/fireworks/Makefile
index 99f6fc3..a6ce214 100644
--- a/sound/firewire/fireworks/Makefile
+++ b/sound/firewire/fireworks/Makefile
@@ -1,2 +1,2 @@
-snd-fireworks-objs := fireworks.o
+snd-fireworks-objs := fireworks_transaction.o fireworks_command.o fireworks.o
 obj-m += snd-fireworks.o
diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c
index 3b62387..ab60b60 100644
--- a/sound/firewire/fireworks/fireworks.c
+++ b/sound/firewire/fireworks/fireworks.c
@@ -69,6 +69,46 @@ static unsigned int devices_used;
 /* unknown as product */
 #define  MODEL_GIBSON_GOLDTOP		0x00afb9
 
+/* part of hardware capability flags */
+#define FLAG_RESP_ADDR_CHANGABLE	0
+
+static int
+get_hardware_info(struct snd_efw *efw)
+{
+	struct snd_efw_hwinfo *hwinfo;
+	char version[12] = {0};
+	int err;
+
+	hwinfo = kzalloc(sizeof(struct snd_efw_hwinfo), GFP_KERNEL);
+	if (hwinfo == NULL)
+		return -ENOMEM;
+
+	err = snd_efw_command_get_hwinfo(efw, hwinfo);
+	if (err < 0)
+		goto end;
+
+	/* firmware version */
+	err = sprintf(version, "%u.%u",
+		      (hwinfo->arm_version >> 24) & 0xff,
+		      (hwinfo->arm_version >> 16) & 0xff);
+
+	/* set names */
+	strcpy(efw->card->driver, "Fireworks");
+	strcpy(efw->card->shortname, hwinfo->model_name);
+	snprintf(efw->card->longname, sizeof(efw->card->longname),
+		 "%s %s v%s, GUID %08x%08x at %s, S%d",
+		 hwinfo->vendor_name, hwinfo->model_name, version,
+		 hwinfo->guid_hi, hwinfo->guid_lo,
+		 dev_name(&efw->unit->device), 100 << efw->device->max_speed);
+	strcpy(efw->card->mixername, hwinfo->model_name);
+
+	if (hwinfo->flags & BIT(FLAG_RESP_ADDR_CHANGABLE))
+		efw->resp_addr_changable = true;
+end:
+	kfree(hwinfo);
+	return err;
+}
+
 static void
 efw_card_free(struct snd_card *card)
 {
@@ -118,27 +158,31 @@ efw_probe(struct fw_unit *unit,
 	mutex_init(&efw->mutex);
 	spin_lock_init(&efw->lock);
 
-	strcpy(efw->card->driver, "Fireworks");
-	strcpy(efw->card->shortname, "Fireworks");
-	strcpy(efw->card->longname, "Fireworks");
-	strcpy(efw->card->mixername, "Fireworks");
+	err = get_hardware_info(efw);
+	if (err < 0)
+		goto error;
 
 	snd_card_set_dev(card, &unit->device);
 	err = snd_card_register(card);
-	if (err < 0) {
-		snd_card_free(card);
-		goto end;
-	}
+	if (err < 0)
+		goto error;
+
 	dev_set_drvdata(&unit->device, efw);
 	devices_used |= BIT(card_index);
 	efw->card_index = card_index;
 end:
 	mutex_unlock(&devices_mutex);
 	return err;
+error:
+	snd_card_free(card);
+	mutex_unlock(&devices_mutex);
+	return err;
 }
 
 static void efw_update(struct fw_unit *unit)
 {
+	struct snd_efw *efw = dev_get_drvdata(&unit->device);
+	snd_efw_transaction_bus_reset(efw->unit);
 	return;
 }
 
@@ -182,11 +226,23 @@ static struct fw_driver efw_driver = {
 
 static int __init snd_efw_init(void)
 {
-	return driver_register(&efw_driver.driver);
+	int err;
+
+	err = snd_efw_transaction_register();
+	if (err < 0)
+		goto end;
+
+	err = driver_register(&efw_driver.driver);
+	if (err < 0)
+		snd_efw_transaction_unregister();
+
+end:
+	return err;
 }
 
 static void __exit snd_efw_exit(void)
 {
+	snd_efw_transaction_unregister();
 	driver_unregister(&efw_driver.driver);
 	mutex_destroy(&devices_mutex);
 }
diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h
index ed745d0..f07b57a 100644
--- a/sound/firewire/fireworks/fireworks.h
+++ b/sound/firewire/fireworks/fireworks.h
@@ -30,6 +30,19 @@
 
 #include <sound/core.h>
 #include <sound/initval.h>
+#include <sound/pcm.h>
+
+#include "../cmp.h"
+#include "../lib.h"
+
+#define SND_EFW_MUITIPLIER_MODES	3
+#define HWINFO_NAME_SIZE_BYTES		32
+#define HWINFO_MAX_CAPS_GROUPS		8
+
+struct snd_efw_phys_grp {
+	u8 type;	/* see enum snd_efw_grp_type */
+	u8 count;
+} __packed;
 
 struct snd_efw {
 	struct snd_card *card;
@@ -39,8 +52,122 @@ struct snd_efw {
 
 	struct mutex mutex;
 	spinlock_t lock;
+
+	/* for transaction */
+	u32 seqnum;
+	bool resp_addr_changable;
+};
+
+/* for transaction */
+struct snd_efw_transaction {
+	u32 length;
+	u32 version;
+	u32 seqnum;
+	u32 category;
+	u32 command;
+	u32 status;
+	u32 params[0];
+};
+
+int snd_efw_transaction_run(struct fw_unit *unit,
+			    const void *cmd, unsigned int cmd_size,
+			    void *resp, unsigned int resp_size, u32 seqnum);
+int snd_efw_transaction_register(void);
+void snd_efw_transaction_unregister(void);
+void snd_efw_transaction_bus_reset(struct fw_unit *unit);
+
+/* for needed commands */
+struct snd_efw_hwinfo {
+	u32 flags;
+	u32 guid_hi;
+	u32 guid_lo;
+	u32 type;
+	u32 version;
+	char vendor_name[HWINFO_NAME_SIZE_BYTES];
+	char model_name[HWINFO_NAME_SIZE_BYTES];
+	u32 supported_clocks;
+	u32 amdtp_rx_pcm_channels;
+	u32 amdtp_tx_pcm_channels;
+	u32 phys_out;
+	u32 phys_in;
+	u32 phys_out_grp_count;
+	struct snd_efw_phys_grp phys_out_grps[HWINFO_MAX_CAPS_GROUPS];
+	u32 phys_in_grp_count;
+	struct snd_efw_phys_grp phys_in_grps[HWINFO_MAX_CAPS_GROUPS];
+	u32 midi_out_ports;
+	u32 midi_in_ports;
+	u32 max_sample_rate;
+	u32 min_sample_rate;
+	u32 dsp_version;
+	u32 arm_version;
+	u32 mixer_playback_channels;
+	u32 mixer_capture_channels;
+	u32 fpga_version;
+	u32 amdtp_rx_pcm_channels_2x;
+	u32 amdtp_tx_pcm_channels_2x;
+	u32 amdtp_rx_pcm_channels_4x;
+	u32 amdtp_tx_pcm_channels_4x;
+	u32 reserved[16];
+} __packed;
+
+enum snd_efw_grp_type {
+	SND_EFW_CH_TYPE_ANALOG			= 0,
+	SND_EFW_CH_TYPE_SPDIF			= 1,
+	SND_EFW_CH_TYPE_ADAT			= 2,
+	SND_EFW_CH_TYPE_SPDIF_OR_ADAT		= 3,
+	SND_EFW_CH_TYPE_ANALOG_MIRRORING	= 4,
+	SND_EFW_CH_TYPE_HEADPHONES		= 5,
+	SND_EFW_CH_TYPE_I2S			= 6,
+	SND_EFW_CH_TYPE_GUITAR			= 7,
+	SND_EFW_CH_TYPE_PIEZO_GUITAR		= 8,
+	SND_EFW_CH_TYPE_GUITAR_STRING		= 9,
+	SND_EFW_CH_TYPE_VIRTUAL			= 0x10000,
+	SND_EFW_CH_TYPE_DUMMY
+};
+
+struct snd_efw_phys_meters {
+	u32 clock_in;
+	u32 reserved0;
+	u32 reserved1;
+	u32 reserved2;
+	u32 reserved3;
+	u32 out_meters;
+	u32 in_meters;
+	u32 reserved4;
+	u32 reserved5;
+	u32 values[0];
+} __packed;
+
+enum snd_efw_clock_source {
+	SND_EFW_CLOCK_SOURCE_INTERNAL	= 0,
+	SND_EFW_CLOCK_SOURCE_SYTMATCH	= 1,
+	SND_EFW_CLOCK_SOURCE_WORDCLOCK	= 2,
+	SND_EFW_CLOCK_SOURCE_SPDIF	= 3,
+	SND_EFW_CLOCK_SOURCE_ADAT_1	= 4,
+	SND_EFW_CLOCK_SOURCE_ADAT_2	= 5,
+};
+
+enum snd_efw_transport_mode {
+	SND_EFW_TRANSPORT_MODE_WINDOWS	= 0,
+	SND_EFW_TRANSPORT_MODE_IEC61883	= 1,
 };
 
+int snd_efw_command_identify(struct snd_efw *efw);
+int snd_efw_command_set_resp_addr(struct snd_efw *efw,
+				  u16 addr_high, u32 addr_low);
+int snd_efw_command_set_tx_mode(struct snd_efw *efw, unsigned int mode);
+int snd_efw_command_get_hwinfo(struct snd_efw *efw,
+			       struct snd_efw_hwinfo *hwinfo);
+int snd_efw_command_get_phys_meters(struct snd_efw *efw,
+				    struct snd_efw_phys_meters *meters,
+				    unsigned int len);
+int snd_efw_command_get_clock_source(struct snd_efw *efw,
+				     enum snd_efw_clock_source *source);
+int snd_efw_command_set_clock_source(struct snd_efw *efw,
+				     enum snd_efw_clock_source source);
+int snd_efw_command_get_sampling_rate(struct snd_efw *efw, unsigned int *rate);
+int snd_efw_command_set_sampling_rate(struct snd_efw *efw, unsigned int rate);
+
 #define SND_EFW_DEV_ENTRY(vendor, model) \
 { \
 	.match_flags	= IEEE1394_MATCH_VENDOR_ID | \
diff --git a/sound/firewire/fireworks/fireworks_command.c b/sound/firewire/fireworks/fireworks_command.c
new file mode 100644
index 0000000..14906bb
--- /dev/null
+++ b/sound/firewire/fireworks/fireworks_command.c
@@ -0,0 +1,399 @@
+/*
+ * fireworks_command.c - a part of driver for Fireworks based devices
+ *
+ * Copyright (c) 2013 Takashi Sakamoto <o-takashi@sakmocchi.jp>
+ *
+ *
+ * This driver is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2.
+ *
+ * This driver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this driver; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "./fireworks.h"
+
+/*
+ * This driver uses transaction version 1 or later to use extended hardware
+ * information. Then too old devices are not available.
+ *
+ * Each commands are not required to have continuous sequence numbers. This
+ * number is just used to match command and response.
+ *
+ * NOTE: FFADO implementaion is EFC over AVC but device with firmware
+ * version 5.5 or later support it but don't use it. This module support a part
+ * of commands. Please see FFADO if you want to see whole commands. But I note
+ * there are some commands which FFADO don't implement
+ */
+
+#define EFW_TRANSACTION_SEQNUM_MAX	((u32)~0)
+
+/* for clock source and sampling rate */
+struct efc_clock {
+	u32 source;
+	u32 sampling_rate;
+	u32 index;
+};
+
+/* command categories */
+enum efc_category {
+	EFC_CAT_HWINFO		= 0,
+	EFC_CAT_TRANSPORT	= 2,
+	EFC_CAT_HWCTL		= 3,
+};
+
+/* hardware info category commands */
+enum efc_cmd_hwinfo {
+	EFC_CMD_HWINFO_GET_CAPS		= 0,
+	EFC_CMD_HWINFO_GET_POLLED	= 1,
+	EFC_CMD_HWINFO_SET_RESP_ADDR	= 2
+};
+
+enum efc_cmd_transport {
+	EFC_CMD_TRANSPORT_SET_TX_MODE	= 0
+};
+
+/* hardware control category commands */
+enum efc_cmd_hwctl {
+	EFC_CMD_HWCTL_SET_CLOCK		= 0,
+	EFC_CMD_HWCTL_GET_CLOCK		= 1,
+	EFC_CMD_HWCTL_IDENTIFY		= 5
+};
+
+/* return values in response */
+enum efr_status {
+	EFC_RETVAL_OK			= 0,
+	EFC_RETVAL_BAD			= 1,
+	EFC_RETVAL_BAD_COMMAND		= 2,
+	EFC_RETVAL_COMM_ERR		= 3,
+	EFC_RETVAL_BAD_QUAD_COUNT	= 4,
+	EFC_RETVAL_UNSUPPORTED		= 5,
+	EFC_RETVAL_1394_TIMEOUT		= 6,
+	EFC_RETVAL_DSP_TIMEOUT		= 7,
+	EFC_RETVAL_BAD_RATE		= 8,
+	EFC_RETVAL_BAD_CLOCK		= 9,
+	EFC_RETVAL_BAD_CHANNEL		= 10,
+	EFC_RETVAL_BAD_PAN		= 11,
+	EFC_RETVAL_FLASH_BUSY		= 12,
+	EFC_RETVAL_BAD_MIRROR		= 13,
+	EFC_RETVAL_BAD_LED		= 14,
+	EFC_RETVAL_BAD_PARAMETER	= 15,
+	EFC_RETVAL_INCOMPLETE		= 0x80000000
+};
+
+static const char *const efr_status_names[] = {
+	[EFC_RETVAL_OK]			= "OK",
+	[EFC_RETVAL_BAD]		= "bad",
+	[EFC_RETVAL_BAD_COMMAND]	= "bad command",
+	[EFC_RETVAL_COMM_ERR]		= "comm err",
+	[EFC_RETVAL_BAD_QUAD_COUNT]	= "bad quad count",
+	[EFC_RETVAL_UNSUPPORTED]	= "unsupported",
+	[EFC_RETVAL_1394_TIMEOUT]	= "1394 timeout",
+	[EFC_RETVAL_DSP_TIMEOUT]	= "DSP timeout",
+	[EFC_RETVAL_BAD_RATE]		= "bad rate",
+	[EFC_RETVAL_BAD_CLOCK]		= "bad clock",
+	[EFC_RETVAL_BAD_CHANNEL]	= "bad channel",
+	[EFC_RETVAL_BAD_PAN]		= "bad pan",
+	[EFC_RETVAL_FLASH_BUSY]		= "flash busy",
+	[EFC_RETVAL_BAD_MIRROR]		= "bad mirror",
+	[EFC_RETVAL_BAD_LED]		= "bad LED",
+	[EFC_RETVAL_BAD_PARAMETER]	= "bad parameter",
+	[EFC_RETVAL_BAD_PARAMETER + 1]	= "incomplete"
+};
+
+static int
+efw_transaction(struct snd_efw *efw, unsigned int category,
+		unsigned int command,
+		const __be32 *params, unsigned int param_quads,
+		const __be32 *resp, unsigned int resp_quads)
+{
+	struct snd_efw_transaction *header;
+	__be32 *buf;
+	u32 seqnum;
+	unsigned int i, buf_bytes, cmd_bytes;
+	int err;
+
+	/* calculate buffer size*/
+	buf_bytes = sizeof(struct snd_efw_transaction) +
+		    max(param_quads, resp_quads) * sizeof(u32);
+
+	/* keep buffer */
+	buf = kzalloc(buf_bytes, GFP_KERNEL);
+	if (buf == NULL)
+		return -ENOMEM;
+
+	/* to keep consistency of sequence number */
+	spin_lock(&efw->lock);
+	if (efw->seqnum + 1 >= EFW_TRANSACTION_SEQNUM_MAX)
+		efw->seqnum = 0;
+	else
+		efw->seqnum += 2;
+	seqnum = efw->seqnum;
+	spin_unlock(&efw->lock);
+
+	/* fill transaction header fields */
+	cmd_bytes = sizeof(struct snd_efw_transaction) +
+		    param_quads * sizeof(u32);
+	header = (struct snd_efw_transaction *)buf;
+	header->length	 = cmd_bytes / sizeof(u32);
+	header->version	 = 1;
+	header->seqnum	 = seqnum;
+	header->category = category;
+	header->command	 = command;
+	header->status	 = 0;
+	for (i = 0; i < sizeof(struct snd_efw_transaction) / sizeof(u32); i++)
+		cpu_to_be32s(&buf[i]);
+
+	/* fill transaction command parameters */
+	for (i = 0; i < param_quads; i++)
+		buf[6 + i] = params[i];
+
+	err = snd_efw_transaction_run(efw->unit, buf, cmd_bytes,
+				      buf, buf_bytes, seqnum);
+	if (err < 0)
+		goto end;
+
+	/* check transaction header fields */
+	for (i = 0; i < sizeof(struct snd_efw_transaction) / sizeof(u32); i++)
+		be32_to_cpus(&buf[i]);
+	if ((header->version  < 1) ||
+	    (header->category != category) ||
+	    (header->command  != command) ||
+	    (header->status   != EFC_RETVAL_OK)) {
+		dev_err(&efw->unit->device, "EFC failed [%u/%u]: %s\n",
+			be32_to_cpu(buf[3]), be32_to_cpu(buf[4]),
+			efr_status_names[be32_to_cpu(buf[5])]);
+		err = -EIO;
+		goto end;
+	}
+
+	/* fill transaction response parameters */
+	if (resp != NULL) {
+		memset((void *)resp, 0, resp_quads * sizeof(u32));
+		resp_quads = min_t(unsigned int, resp_quads,
+				   header->length -
+				    sizeof(struct snd_efw_transaction) /
+				    sizeof(u32));
+		memcpy((void *)resp, &buf[6], resp_quads * sizeof(u32));
+	}
+end:
+	kfree(buf);
+	return err;
+}
+
+int snd_efw_command_identify(struct snd_efw *efw)
+{
+	return efw_transaction(efw, EFC_CAT_HWCTL,
+			       EFC_CMD_HWCTL_IDENTIFY,
+			       NULL, 0, NULL, 0);
+}
+
+/*
+ * The address in host system for EFC response is changable when the device
+ * supports. struct hwinfo.flags includes its flag. The default is
+ * INITIAL_MEMORY_SPACE_EFC_RESPONSE
+ */
+int snd_efw_command_set_resp_addr(struct snd_efw *efw,
+				  u16 addr_high, u32 addr_low)
+{
+	__be32 addr[2];
+
+	addr[0] = cpu_to_be32(addr_high);
+	addr[1] = cpu_to_be32(addr_low);
+
+	if (!efw->resp_addr_changable)
+		return -ENOSYS;
+
+	return efw_transaction(efw, EFC_CAT_HWCTL,
+			       EFC_CMD_HWINFO_SET_RESP_ADDR,
+			       addr, 2, NULL, 0);
+}
+
+/*
+ * This is for timestamp processing. In Windows mode, all 32bit fields of second
+ * CIP header in AMDTP transmit packet is used for 'presentation timestamp'. In
+ * 'no data' packet the value of this field is 0x90ffffff.
+ */
+int snd_efw_command_set_tx_mode(struct snd_efw *efw,
+				enum snd_efw_transport_mode mode)
+{
+	__be32 param = cpu_to_be32(mode);
+	return efw_transaction(efw, EFC_CAT_TRANSPORT,
+			       EFC_CMD_TRANSPORT_SET_TX_MODE,
+			       &param, 1, NULL, 0);
+}
+
+int snd_efw_command_get_hwinfo(struct snd_efw *efw,
+			       struct snd_efw_hwinfo *hwinfo)
+{
+	int err;
+
+	err  = efw_transaction(efw, EFC_CAT_HWINFO,
+			       EFC_CMD_HWINFO_GET_CAPS,
+			       NULL, 0, (__be32 *)hwinfo, sizeof(*hwinfo) / 4);
+	if (err < 0)
+		goto end;
+
+	be32_to_cpus(&hwinfo->flags);
+	be32_to_cpus(&hwinfo->guid_hi);
+	be32_to_cpus(&hwinfo->guid_lo);
+	be32_to_cpus(&hwinfo->type);
+	be32_to_cpus(&hwinfo->version);
+	be32_to_cpus(&hwinfo->supported_clocks);
+	be32_to_cpus(&hwinfo->amdtp_rx_pcm_channels);
+	be32_to_cpus(&hwinfo->amdtp_tx_pcm_channels);
+	be32_to_cpus(&hwinfo->phys_out);
+	be32_to_cpus(&hwinfo->phys_in);
+	be32_to_cpus(&hwinfo->phys_out_grp_count);
+	be32_to_cpus(&hwinfo->phys_in_grp_count);
+	be32_to_cpus(&hwinfo->midi_out_ports);
+	be32_to_cpus(&hwinfo->midi_in_ports);
+	be32_to_cpus(&hwinfo->max_sample_rate);
+	be32_to_cpus(&hwinfo->min_sample_rate);
+	be32_to_cpus(&hwinfo->dsp_version);
+	be32_to_cpus(&hwinfo->arm_version);
+	be32_to_cpus(&hwinfo->mixer_playback_channels);
+	be32_to_cpus(&hwinfo->mixer_capture_channels);
+	be32_to_cpus(&hwinfo->fpga_version);
+	be32_to_cpus(&hwinfo->amdtp_rx_pcm_channels_2x);
+	be32_to_cpus(&hwinfo->amdtp_tx_pcm_channels_2x);
+	be32_to_cpus(&hwinfo->amdtp_rx_pcm_channels_4x);
+	be32_to_cpus(&hwinfo->amdtp_tx_pcm_channels_4x);
+
+	/* ensure terminated */
+	hwinfo->vendor_name[HWINFO_NAME_SIZE_BYTES - 1] = '\0';
+	hwinfo->model_name[HWINFO_NAME_SIZE_BYTES  - 1] = '\0';
+end:
+	return err;
+}
+
+int snd_efw_command_get_phys_meters(struct snd_efw *efw,
+				    struct snd_efw_phys_meters *meters,
+				    unsigned int len)
+{
+	__be32 *buf = (__be32 *)meters;
+	unsigned int i;
+	int err;
+
+	err = efw_transaction(efw, EFC_CAT_HWINFO,
+			      EFC_CMD_HWINFO_GET_POLLED,
+			      NULL, 0, (__be32 *)meters, len / 4);
+	if (err >= 0)
+		for (i = 0; i < len / 4; i++)
+			be32_to_cpus(&buf[i]);
+
+	return err;
+}
+
+static int
+command_get_clock(struct snd_efw *efw, struct efc_clock *clock)
+{
+	int err;
+
+	err = efw_transaction(efw, EFC_CAT_HWCTL,
+			      EFC_CMD_HWCTL_GET_CLOCK,
+			      NULL, 0,
+			      (__be32 *)clock, sizeof(struct efc_clock) / 4);
+	if (err >= 0) {
+		be32_to_cpus(&clock->source);
+		be32_to_cpus(&clock->sampling_rate);
+		be32_to_cpus(&clock->index);
+	}
+
+	return err;
+}
+
+/* give UINT_MAX if set nothing */
+static int
+command_set_clock(struct snd_efw *efw,
+		  unsigned int source, unsigned int rate)
+{
+	struct efc_clock clock = {0};
+	int err;
+
+	/* check arguments */
+	if ((source == UINT_MAX) && (rate == UINT_MAX)) {
+		err = -EINVAL;
+		goto end;
+	}
+
+	/* get current status */
+	err = command_get_clock(efw, &clock);
+	if (err < 0)
+		goto end;
+
+	/* no need */
+	if ((clock.source == source) && (clock.sampling_rate == rate))
+		goto end;
+
+	/* set params */
+	if ((source != UINT_MAX) && (clock.source != source))
+		clock.source = source;
+	if ((rate != UINT_MAX) && (clock.sampling_rate != rate))
+		clock.sampling_rate = rate;
+	clock.index = 0;
+
+	cpu_to_be32s(&clock.source);
+	cpu_to_be32s(&clock.sampling_rate);
+	cpu_to_be32s(&clock.index);
+
+	err = efw_transaction(efw, EFC_CAT_HWCTL,
+			      EFC_CMD_HWCTL_SET_CLOCK,
+			      (__be32 *)&clock, 3, NULL, 0);
+	if (err < 0)
+		goto end;
+
+	/*
+	 * With firmware version 5.8, just after changing clock state, these
+	 * parameters are not immediately retrieved by get command. In my
+	 * trial, there needs to be 100msec to get changed parameters.
+	 */
+	msleep(150);
+end:
+	return err;
+}
+
+int snd_efw_command_get_clock_source(struct snd_efw *efw,
+				     enum snd_efw_clock_source *source)
+{
+	int err;
+	struct efc_clock clock = {0};
+
+	err = command_get_clock(efw, &clock);
+	if (err >= 0)
+		*source = clock.source;
+
+	return err;
+}
+
+int snd_efw_command_set_clock_source(struct snd_efw *efw,
+				     enum snd_efw_clock_source source)
+{
+	return command_set_clock(efw, source, UINT_MAX);
+}
+
+int snd_efw_command_get_sampling_rate(struct snd_efw *efw,
+				      unsigned int *rate)
+{
+	int err;
+	struct efc_clock clock = {0};
+
+	err = command_get_clock(efw, &clock);
+	if (err >= 0)
+		*rate = clock.sampling_rate;
+
+	return err;
+}
+
+int
+snd_efw_command_set_sampling_rate(struct snd_efw *efw,
+				  unsigned int rate)
+{
+	return command_set_clock(efw, UINT_MAX, rate);
+}
+
diff --git a/sound/firewire/fireworks/fireworks_transaction.c b/sound/firewire/fireworks/fireworks_transaction.c
new file mode 100644
index 0000000..a701e76
--- /dev/null
+++ b/sound/firewire/fireworks/fireworks_transaction.c
@@ -0,0 +1,204 @@
+/*
+ * fireworks_transaction.c - a part of driver for Fireworks based devices
+ *
+ * Copyright (c) 2013 Takashi Sakamoto <o-takashi@sakmocchi.jp>
+ *
+ *
+ * This driver is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2.
+ *
+ * This driver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this driver; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Mostly based on FFADO's souce, which is licensed under GPL version 2 (and
+ * optionally version 3).
+ */
+
+/*
+ * Fireworks have its own transaction. Fireworks also support AV/C General
+ * commands and AV/C Stream Format Information commands. This module supports
+ * own commands.
+ *
+ * Transaction substance:
+ *  At first, 6 data exist. Following to the 6 data, parameters for each
+ *  commands exists. All of parameters are 32 bit alighed to big endian.
+ *   data[0]:	Length of transaction substance
+ *   data[1]:	Transaction version
+ *   data[2]:	Sequence number. This is incremented by the device
+ *   data[3]:	transaction category
+ *   data[4]:	transaction command
+ *   data[5]:	return value in response.
+ *   data[6-]:	parameters
+ *
+ * Transaction address:
+ *  command:	0xecc000000000
+ *  response:	0xecc080000000 (default)
+ *
+ * I note that the address for response can be changed by command. But this
+ * module uses the default address.
+ */
+#include "./fireworks.h"
+
+#define MEMORY_SPACE_EFW_COMMAND	0xecc000000000
+#define MEMORY_SPACE_EFW_RESPONSE	0xecc080000000
+/* this is for juju convinience? */
+#define MEMORY_SPACE_EFW_END		0xecc080000200
+
+#define ERROR_RETRIES 3
+#define ERROR_DELAY_MS 5
+#define EFC_TIMEOUT_MS 125
+
+static DEFINE_SPINLOCK(transaction_queues_lock);
+static LIST_HEAD(transaction_queues);
+
+enum transaction_queue_state {
+	STATE_PENDING,
+	STATE_BUS_RESET,
+	STATE_COMPLETE
+};
+
+struct transaction_queue {
+	struct list_head list;
+	struct fw_unit *unit;
+	void *buf;
+	unsigned int size;
+	u32 seqnum;
+	enum transaction_queue_state state;
+	wait_queue_head_t wait;
+};
+
+int snd_efw_transaction_run(struct fw_unit *unit,
+			    const void *cmd, unsigned int cmd_size,
+			    void *resp, unsigned int resp_size, u32 seqnum)
+{
+	struct transaction_queue t;
+	unsigned int tries;
+	int ret;
+
+	t.unit = unit;
+	t.buf = resp;
+	t.size = resp_size;
+	t.seqnum = seqnum + 1;
+	t.state = STATE_PENDING;
+	init_waitqueue_head(&t.wait);
+
+	spin_lock_irq(&transaction_queues_lock);
+	list_add_tail(&t.list, &transaction_queues);
+	spin_unlock_irq(&transaction_queues_lock);
+
+	tries = 0;
+	do {
+		ret = snd_fw_transaction(unit, TCODE_WRITE_BLOCK_REQUEST,
+					 MEMORY_SPACE_EFW_COMMAND,
+					 (void *)cmd, cmd_size, 0);
+		if (ret < 0)
+			break;
+
+		wait_event_timeout(t.wait, t.state != STATE_PENDING,
+				   msecs_to_jiffies(EFC_TIMEOUT_MS));
+
+		if (t.state == STATE_COMPLETE) {
+			ret = t.size;
+			break;
+		} else if (t.state == STATE_BUS_RESET) {
+			msleep(ERROR_DELAY_MS);
+		} else if (++tries >= ERROR_RETRIES) {
+			dev_err(&t.unit->device, "EFC command timed out\n");
+			ret = -EIO;
+			break;
+		}
+	} while (1);
+
+	spin_lock_irq(&transaction_queues_lock);
+	list_del(&t.list);
+	spin_unlock_irq(&transaction_queues_lock);
+
+	return ret;
+}
+
+static void
+efw_response(struct fw_card *card, struct fw_request *request,
+	     int tcode, int destination, int source,
+	     int generation, unsigned long long offset,
+	     void *data, size_t length, void *callback_data)
+{
+	struct fw_device *device;
+	struct transaction_queue *t;
+	unsigned long flags;
+	int rcode;
+	u32 seqnum;
+
+	rcode = RCODE_TYPE_ERROR;
+	if (length < sizeof(struct snd_efw_transaction)) {
+		rcode = RCODE_DATA_ERROR;
+		goto end;
+	} else if (offset != MEMORY_SPACE_EFW_RESPONSE) {
+		rcode = RCODE_ADDRESS_ERROR;
+		goto end;
+	}
+
+	seqnum = be32_to_cpu(((struct snd_efw_transaction *)data)->seqnum);
+
+	spin_lock_irqsave(&transaction_queues_lock, flags);
+	list_for_each_entry(t, &transaction_queues, list) {
+		device = fw_parent_device(t->unit);
+		if ((device->card != card) ||
+		    (device->generation != generation))
+			continue;
+		smp_rmb();	/* node_id vs. generation */
+		if (device->node_id != source)
+			continue;
+
+		if ((t->state == STATE_PENDING) && (t->seqnum == seqnum)) {
+			t->state = STATE_COMPLETE;
+			t->size = min_t(unsigned int, length, t->size);
+			memcpy(t->buf, data, t->size);
+			wake_up(&t->wait);
+			rcode = RCODE_COMPLETE;
+		}
+	}
+	spin_unlock_irqrestore(&transaction_queues_lock, flags);
+end:
+	fw_send_response(card, request, rcode);
+}
+
+void snd_efw_transaction_bus_reset(struct fw_unit *unit)
+{
+	struct transaction_queue *t;
+
+	spin_lock_irq(&transaction_queues_lock);
+	list_for_each_entry(t, &transaction_queues, list) {
+		if ((t->unit == unit) &&
+		    (t->state == STATE_PENDING)) {
+			t->state = STATE_BUS_RESET;
+			wake_up(&t->wait);
+		}
+	}
+	spin_unlock_irq(&transaction_queues_lock);
+}
+
+static struct fw_address_handler resp_register_handler = {
+	.length = MEMORY_SPACE_EFW_END - MEMORY_SPACE_EFW_RESPONSE,
+	.address_callback = efw_response
+};
+
+int snd_efw_transaction_register(void)
+{
+	static const struct fw_address_region resp_register_region = {
+		.start	= MEMORY_SPACE_EFW_RESPONSE,
+		.end	= MEMORY_SPACE_EFW_END
+	};
+	return fw_core_add_address_handler(&resp_register_handler,
+					   &resp_register_region);
+}
+
+void snd_efw_transaction_unregister(void)
+{
+	WARN_ON(!list_empty(&transaction_queues));
+	fw_core_remove_address_handler(&resp_register_handler);
+}
-- 
1.8.3.2

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

* [PATCH 3/8] fireworks: Add connection and stream management
  2013-12-11  9:15 [PATCH v2 0/8][RFC] a driver for Fireworks based devices Takashi Sakamoto
  2013-12-11  9:15 ` [PATCH 1/8] fireworks: Add skelton " Takashi Sakamoto
  2013-12-11  9:15 ` [PATCH 2/8] fireworks: Add transaction and some commands Takashi Sakamoto
@ 2013-12-11  9:15 ` Takashi Sakamoto
  2013-12-11  9:15 ` [PATCH 4/8] fireworks: Add proc interface for debugging purpose Takashi Sakamoto
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Takashi Sakamoto @ 2013-12-11  9:15 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel

Fireworks manages connections by CMP and can transmit/receive AMDTP stream with
a few quirks. This commit adds functionality to start/stop streams.

As long as I know, major Fireworks products don't support 'SYT-Match' mode,
except for AudioFire12/8(till 2009 July) with firmware version 1.0. So this
commit add support for no 'SYT-Match' mode.

I note that there is a short gap for MIDI streams when starting PCM data. When
AMDTP streams are running only for MIDI data and PCM data is going to be
multiplexed at different sampling rate, then AMDTP streams are stopped once and
started again after changing sampling rate.

Fireworks quirks I confirm with firmware version 4.8 or later with Windows:

1.Fireworks transmits packets with both TAG0 and TAG1
In IEC 61883-1, each common isochronous packet (CIP) has 'tag' field in its
packet header. The value is generally 0x01 ('CIP header is included' = TAG1).
But Fireworks transmits 'no data' packet with 0x00
('No CIP header is included' = TAG0) but the packet includes CIP structure.
To handle 'presentation timestamp'for duplex streams synchronization, this
packets must be handled.

2.Fireworks transmits 'no data' packet out of specification
Fireworks transmits 'no data' packet with 'NO-DATA' FDF and no dummy data. It
includes CIP headers only. In IEC 61883-6, 'no data' packet is generated by two
ways. One is empty CIP packet defined in IEC 61883-1. Another is
'special non-empty packet' defined in IEC 61883-6. Fireworks uses strange
combination of them.

3.Fireworks can handle MIDI messages in the first 8 data blocks of out-packet
Fireworks ignores MIDI messages in other data blocks.

4.AudioFirePre8 transmits packets with wrong data block size
One of Fireworks device, AudioFirePre8, always reports 8 data block size in
transmitted packets and increments the data block counter by 8. But this is not
actual value.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/fireworks/Makefile           |   3 +-
 sound/firewire/fireworks/fireworks.c        |  49 ++++
 sound/firewire/fireworks/fireworks.h        |  27 +++
 sound/firewire/fireworks/fireworks_stream.c | 348 ++++++++++++++++++++++++++++
 4 files changed, 426 insertions(+), 1 deletion(-)
 create mode 100644 sound/firewire/fireworks/fireworks_stream.c

diff --git a/sound/firewire/fireworks/Makefile b/sound/firewire/fireworks/Makefile
index a6ce214..1bccb65 100644
--- a/sound/firewire/fireworks/Makefile
+++ b/sound/firewire/fireworks/Makefile
@@ -1,2 +1,3 @@
-snd-fireworks-objs := fireworks_transaction.o fireworks_command.o fireworks.o
+snd-fireworks-objs := fireworks_transaction.o fireworks_command.o \
+		      fireworks_stream.o fireworks.o
 obj-m += snd-fireworks.o
diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c
index ab60b60..0c3fb34 100644
--- a/sound/firewire/fireworks/fireworks.c
+++ b/sound/firewire/fireworks/fireworks.c
@@ -104,6 +104,45 @@ get_hardware_info(struct snd_efw *efw)
 
 	if (hwinfo->flags & BIT(FLAG_RESP_ADDR_CHANGABLE))
 		efw->resp_addr_changable = true;
+
+	/* set flag for supported sampling rate */
+	efw->supported_sampling_rate = 0;
+	if ((hwinfo->min_sample_rate <= 22050)
+	 && (22050 <= hwinfo->max_sample_rate))
+		efw->supported_sampling_rate |= SNDRV_PCM_RATE_22050;
+	if ((hwinfo->min_sample_rate <= 32000)
+	 && (32000 <= hwinfo->max_sample_rate))
+		efw->supported_sampling_rate |= SNDRV_PCM_RATE_32000;
+	if ((hwinfo->min_sample_rate <= 44100)
+	 && (44100 <= hwinfo->max_sample_rate))
+		efw->supported_sampling_rate |= SNDRV_PCM_RATE_44100;
+	if ((hwinfo->min_sample_rate <= 48000)
+	 && (48000 <= hwinfo->max_sample_rate))
+		efw->supported_sampling_rate |= SNDRV_PCM_RATE_48000;
+	if ((hwinfo->min_sample_rate <= 88200)
+	 && (88200 <= hwinfo->max_sample_rate))
+		efw->supported_sampling_rate |= SNDRV_PCM_RATE_88200;
+	if ((hwinfo->min_sample_rate <= 96000)
+	 && (96000 <= hwinfo->max_sample_rate))
+		efw->supported_sampling_rate |= SNDRV_PCM_RATE_96000;
+	if ((hwinfo->min_sample_rate <= 176400)
+	 && (176400 <= hwinfo->max_sample_rate))
+		efw->supported_sampling_rate |= SNDRV_PCM_RATE_176400;
+	if ((hwinfo->min_sample_rate <= 192000)
+	 && (192000 <= hwinfo->max_sample_rate))
+		efw->supported_sampling_rate |= SNDRV_PCM_RATE_192000;
+
+	/* MIDI inputs and outputs */
+	efw->midi_out_ports = hwinfo->midi_out_ports;
+	efw->midi_in_ports = hwinfo->midi_in_ports;
+
+	/* PCM capture and playback channels */
+	efw->pcm_capture_channels[0] = hwinfo->amdtp_tx_pcm_channels;
+	efw->pcm_capture_channels[1] = hwinfo->amdtp_tx_pcm_channels_2x;
+	efw->pcm_capture_channels[2] = hwinfo->amdtp_tx_pcm_channels_4x;
+	efw->pcm_playback_channels[0] = hwinfo->amdtp_rx_pcm_channels;
+	efw->pcm_playback_channels[1] = hwinfo->amdtp_rx_pcm_channels_2x;
+	efw->pcm_playback_channels[2] = hwinfo->amdtp_rx_pcm_channels_4x;
 end:
 	kfree(hwinfo);
 	return err;
@@ -162,6 +201,10 @@ efw_probe(struct fw_unit *unit,
 	if (err < 0)
 		goto error;
 
+	err = snd_efw_stream_init_duplex(efw);
+	if (err < 0)
+		goto error;
+
 	snd_card_set_dev(card, &unit->device);
 	err = snd_card_register(card);
 	if (err < 0)
@@ -182,13 +225,19 @@ error:
 static void efw_update(struct fw_unit *unit)
 {
 	struct snd_efw *efw = dev_get_drvdata(&unit->device);
+
 	snd_efw_transaction_bus_reset(efw->unit);
+	snd_efw_stream_update_duplex(efw);
+
 	return;
 }
 
 static void efw_remove(struct fw_unit *unit)
 {
 	struct snd_efw *efw = dev_get_drvdata(&unit->device);
+
+	snd_efw_stream_destroy_duplex(efw);
+
 	snd_card_disconnect(efw->card);
 	snd_card_free_when_closed(efw->card);
 	return;
diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h
index f07b57a..4867264 100644
--- a/sound/firewire/fireworks/fireworks.h
+++ b/sound/firewire/fireworks/fireworks.h
@@ -32,9 +32,15 @@
 #include <sound/initval.h>
 #include <sound/pcm.h>
 
+#include "../packets-buffer.h"
+#include "../iso-resources.h"
+#include "../amdtp.h"
 #include "../cmp.h"
 #include "../lib.h"
 
+#define SND_EFW_MAX_MIDI_OUT_PORTS	2
+#define SND_EFW_MAX_MIDI_IN_PORTS	2
+
 #define SND_EFW_MUITIPLIER_MODES	3
 #define HWINFO_NAME_SIZE_BYTES		32
 #define HWINFO_MAX_CAPS_GROUPS		8
@@ -56,6 +62,18 @@ struct snd_efw {
 	/* for transaction */
 	u32 seqnum;
 	bool resp_addr_changable;
+
+	unsigned int midi_in_ports;
+	unsigned int midi_out_ports;
+
+	unsigned int supported_sampling_rate;
+	unsigned int pcm_capture_channels[SND_EFW_MUITIPLIER_MODES];
+	unsigned int pcm_playback_channels[SND_EFW_MUITIPLIER_MODES];
+
+	struct amdtp_stream tx_stream;
+	struct amdtp_stream rx_stream;
+	struct cmp_connection out_conn;
+	struct cmp_connection in_conn;
 };
 
 /* for transaction */
@@ -168,6 +186,15 @@ int snd_efw_command_set_clock_source(struct snd_efw *efw,
 int snd_efw_command_get_sampling_rate(struct snd_efw *efw, unsigned int *rate);
 int snd_efw_command_set_sampling_rate(struct snd_efw *efw, unsigned int rate);
 
+/* for AMDTP stream and CMP */
+int snd_efw_stream_init_duplex(struct snd_efw *efw);
+int snd_efw_stream_start_duplex(struct snd_efw *efw,
+				struct amdtp_stream *request,
+				int sampling_rate);
+int snd_efw_stream_stop_duplex(struct snd_efw *efw);
+void snd_efw_stream_update_duplex(struct snd_efw *efw);
+void snd_efw_stream_destroy_duplex(struct snd_efw *efw);
+
 #define SND_EFW_DEV_ENTRY(vendor, model) \
 { \
 	.match_flags	= IEEE1394_MATCH_VENDOR_ID | \
diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c
new file mode 100644
index 0000000..f0764538
--- /dev/null
+++ b/sound/firewire/fireworks/fireworks_stream.c
@@ -0,0 +1,348 @@
+/*
+ * fireworks_stream.c - a part of driver for Fireworks based devices
+ *
+ * Copyright (c) 2013 Takashi Sakamoto
+ *
+ *
+ * This driver is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2.
+ *
+ * This driver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this driver; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "./fireworks.h"
+
+static unsigned int freq_table[] = {
+	/* multiplier mode 0 */
+	[0] = 32000,
+	[1] = 44100,
+	[2] = 48000,
+	/* multiplier mode 1 */
+	[3] = 88200,
+	[4] = 96000,
+	/* multiplier mode 2 */
+	[5] = 176400,
+	[6] = 192000,
+};
+
+int snd_efw_get_multiplier_mode(int sampling_rate)
+{
+	unsigned int i;
+	for (i = 0; i < sizeof(freq_table); i++)
+		if (freq_table[i] == sampling_rate)
+			return (i - 1) / 2;
+
+	return -1;
+}
+
+static int
+init_stream(struct snd_efw *efw, struct amdtp_stream *stream)
+{
+	struct cmp_connection *conn;
+	enum cmp_direction c_dir;
+	enum amdtp_stream_direction s_dir;
+	int err;
+
+	if (stream == &efw->tx_stream) {
+		conn = &efw->out_conn;
+		c_dir = CMP_OUTPUT;
+		s_dir = AMDTP_IN_STREAM;
+	} else {
+		conn = &efw->in_conn;
+		c_dir = CMP_INPUT;
+		s_dir = AMDTP_OUT_STREAM;
+	}
+
+	err = cmp_connection_init(conn, efw->unit, c_dir, 0);
+	if (err < 0)
+		goto end;
+
+	err = amdtp_stream_init(stream, efw->unit, s_dir, CIP_BLOCKING);
+	if (err < 0) {
+		cmp_connection_destroy(conn);
+		goto end;
+	}
+
+end:
+	return err;
+}
+
+static void
+stop_stream(struct snd_efw *efw, struct amdtp_stream *stream)
+{
+	if (amdtp_stream_running(stream))
+		amdtp_stream_stop(stream);
+
+	if (stream == &efw->tx_stream)
+		cmp_connection_break(&efw->out_conn);
+	else
+		cmp_connection_break(&efw->in_conn);
+
+	return;
+}
+
+static int
+start_stream(struct snd_efw *efw, struct amdtp_stream *stream,
+	     unsigned int sampling_rate)
+{
+	struct cmp_connection *conn;
+	unsigned int pcm_channels, midi_ports;
+	int mode, err;
+
+	/* already running */
+	if (amdtp_stream_running(stream)) {
+		err = 0;
+		goto end;
+	}
+
+	mode = snd_efw_get_multiplier_mode(sampling_rate);
+	if (stream == &efw->tx_stream) {
+		conn = &efw->out_conn;
+		pcm_channels = efw->pcm_capture_channels[mode];
+		midi_ports = efw->midi_out_ports;
+	} else {
+		conn = &efw->in_conn;
+		pcm_channels = efw->pcm_playback_channels[mode];
+		midi_ports = efw->midi_in_ports;
+	}
+
+	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(efw, stream);
+
+	/* wait first callback */
+	if (!amdtp_stream_wait_callback(stream)) {
+		stop_stream(efw, stream);
+		err = -ETIMEDOUT;
+		goto end;
+	}
+end:
+	return err;
+}
+
+static void
+update_stream(struct snd_efw *efw, struct amdtp_stream *stream)
+{
+	struct cmp_connection *conn;
+
+	if (&efw->tx_stream == stream)
+		conn = &efw->out_conn;
+	else
+		conn = &efw->in_conn;
+
+	if (cmp_connection_update(conn) < 0) {
+		amdtp_stream_pcm_abort(stream);
+		mutex_lock(&efw->mutex);
+		stop_stream(efw, stream);
+		mutex_unlock(&efw->mutex);
+		return;
+	}
+	amdtp_stream_update(stream);
+}
+
+static void
+destroy_stream(struct snd_efw *efw, struct amdtp_stream *stream)
+{
+	stop_stream(efw, stream);
+
+	if (stream == &efw->tx_stream)
+		cmp_connection_destroy(&efw->out_conn);
+	else
+		cmp_connection_destroy(&efw->in_conn);
+}
+
+static int
+get_roles(struct snd_efw *efw, enum cip_flags *sync_mode,
+	  struct amdtp_stream **master, struct amdtp_stream **slave)
+{
+	enum snd_efw_clock_source clock_source;
+	int err;
+
+	err = snd_efw_command_get_clock_source(efw, &clock_source);
+	if (err < 0)
+		goto end;
+
+	if (clock_source != SND_EFW_CLOCK_SOURCE_SYTMATCH) {
+		*master = &efw->tx_stream;
+		*slave = &efw->rx_stream;
+		*sync_mode = CIP_SYNC_TO_DEVICE;
+	} else {
+		err = -ENOSYS;
+	}
+end:
+	return err;
+}
+
+static int
+check_connection_used_by_others(struct snd_efw *efw,
+				struct amdtp_stream *s, bool *used)
+{
+	struct cmp_connection *conn;
+	int err;
+
+	if (s == &efw->tx_stream)
+		conn = &efw->out_conn;
+	else
+		conn = &efw->in_conn;
+
+	err = cmp_connection_check_used(conn, used);
+	if (err >= 0)
+		*used = (*used && !amdtp_stream_running(s));
+
+	return err;
+}
+
+int snd_efw_stream_init_duplex(struct snd_efw *efw)
+{
+	int err;
+
+	err = init_stream(efw, &efw->tx_stream);
+	if (err < 0)
+		goto end;
+
+	err = init_stream(efw, &efw->rx_stream);
+	if (err < 0)
+		goto end;
+
+	/* set IEC61883 compliant mode */
+	err = snd_efw_command_set_tx_mode(efw, SND_EFW_TRANSPORT_MODE_IEC61883);
+end:
+	return err;
+}
+
+int snd_efw_stream_start_duplex(struct snd_efw *efw,
+				struct amdtp_stream *request,
+				int sampling_rate)
+{
+	struct amdtp_stream *master, *slave;
+	enum cip_flags sync_mode;
+	int err, curr_rate;
+	bool slave_flag, used;
+
+	err = get_roles(efw, &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(efw, master, &used);
+	if (err < 0)
+		goto end;
+	if (used) {
+		dev_err(&efw->unit->device,
+			"connections established by others: %d\n",
+			used);
+		err = -EBUSY;
+		goto end;
+	}
+
+	/* change sampling rate if possible */
+	err = snd_efw_command_get_sampling_rate(efw, &curr_rate);
+	if (err < 0)
+		goto end;
+	if (sampling_rate == 0)
+		sampling_rate = curr_rate;
+	if (sampling_rate != curr_rate) {
+		/* master is just for MIDI stream */
+		if (amdtp_stream_running(master) &&
+		    !amdtp_stream_pcm_running(master))
+			stop_stream(efw, master);
+
+		/* slave is just for MIDI stream */
+		if (amdtp_stream_running(slave) &&
+		    !amdtp_stream_pcm_running(slave))
+			stop_stream(efw, slave);
+
+		err = snd_efw_command_set_sampling_rate(efw, sampling_rate);
+		if (err < 0)
+			goto end;
+	}
+
+	/*  master should be always running */
+	if (!amdtp_stream_running(master)) {
+		amdtp_stream_set_sync(sync_mode, master, slave);
+		err = start_stream(efw, master, sampling_rate);
+		if (err < 0) {
+			dev_err(&efw->unit->device,
+				"fail to start AMDTP master stream:%d\n", err);
+			goto end;
+		}
+	}
+
+	/* start slave if needed */
+	if (slave_flag && !amdtp_stream_running(slave)) {
+		err = start_stream(efw, slave, sampling_rate);
+		if (err < 0) {
+			dev_err(&efw->unit->device,
+				"fail to start AMDTP slave stream:%d\n", err);
+			goto end;
+		}
+	}
+end:
+	return err;
+}
+
+int snd_efw_stream_stop_duplex(struct snd_efw *efw)
+{
+	struct amdtp_stream *master, *slave;
+	enum cip_flags sync_mode;
+	int err;
+
+	err = get_roles(efw, &sync_mode, &master, &slave);
+	if (err < 0)
+		goto end;
+
+	if (amdtp_stream_pcm_running(slave) ||
+	    amdtp_stream_midi_running(slave))
+		goto end;
+
+	stop_stream(efw, slave);
+
+	if (!amdtp_stream_pcm_running(master) &&
+	    !amdtp_stream_midi_running(master))
+		stop_stream(efw, master);
+
+end:
+	return err;
+}
+
+void snd_efw_stream_update_duplex(struct snd_efw *efw)
+{
+	update_stream(efw, &efw->rx_stream);
+	update_stream(efw, &efw->tx_stream);
+}
+
+void snd_efw_stream_destroy_duplex(struct snd_efw *efw)
+{
+	if (amdtp_stream_pcm_running(&efw->rx_stream))
+		amdtp_stream_pcm_abort(&efw->rx_stream);
+	if (amdtp_stream_pcm_running(&efw->tx_stream))
+		amdtp_stream_pcm_abort(&efw->tx_stream);
+
+	destroy_stream(efw, &efw->rx_stream);
+	destroy_stream(efw, &efw->tx_stream);
+}
-- 
1.8.3.2

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

* [PATCH 4/8] fireworks: Add proc interface for debugging purpose
  2013-12-11  9:15 [PATCH v2 0/8][RFC] a driver for Fireworks based devices Takashi Sakamoto
                   ` (2 preceding siblings ...)
  2013-12-11  9:15 ` [PATCH 3/8] fireworks: Add connection and stream management Takashi Sakamoto
@ 2013-12-11  9:15 ` Takashi Sakamoto
  2013-12-11  9:15 ` [PATCH 5/8] fireworks: Add MIDI interface Takashi Sakamoto
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Takashi Sakamoto @ 2013-12-11  9:15 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel

This commit adds proc interface to output infomation for debugging.
 - hardware capabilities
 - sampling rate and clock source
 - physical metering (linear value)

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/fireworks/Makefile         |   2 +-
 sound/firewire/fireworks/fireworks.c      |  12 ++
 sound/firewire/fireworks/fireworks.h      |  11 ++
 sound/firewire/fireworks/fireworks_proc.c | 198 ++++++++++++++++++++++++++++++
 4 files changed, 222 insertions(+), 1 deletion(-)
 create mode 100644 sound/firewire/fireworks/fireworks_proc.c

diff --git a/sound/firewire/fireworks/Makefile b/sound/firewire/fireworks/Makefile
index 1bccb65..52bd15e 100644
--- a/sound/firewire/fireworks/Makefile
+++ b/sound/firewire/fireworks/Makefile
@@ -1,3 +1,3 @@
 snd-fireworks-objs := fireworks_transaction.o fireworks_command.o \
-		      fireworks_stream.o fireworks.o
+		      fireworks_stream.o fireworks_proc.o fireworks.o
 obj-m += snd-fireworks.o
diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c
index 0c3fb34..adc4a91 100644
--- a/sound/firewire/fireworks/fireworks.c
+++ b/sound/firewire/fireworks/fireworks.c
@@ -143,6 +143,16 @@ get_hardware_info(struct snd_efw *efw)
 	efw->pcm_playback_channels[0] = hwinfo->amdtp_rx_pcm_channels;
 	efw->pcm_playback_channels[1] = hwinfo->amdtp_rx_pcm_channels_2x;
 	efw->pcm_playback_channels[2] = hwinfo->amdtp_rx_pcm_channels_4x;
+
+	/* hardware metering */
+	efw->phys_out = hwinfo->phys_out;
+	efw->phys_in = hwinfo->phys_in;
+	efw->phys_out_grp_count = hwinfo->phys_out_grp_count;
+	efw->phys_in_grp_count = hwinfo->phys_in_grp_count;
+	memcpy(&efw->phys_out_grps, hwinfo->phys_out_grps,
+	       sizeof(struct snd_efw_phys_grp) * HWINFO_MAX_CAPS_GROUPS);
+	memcpy(&efw->phys_in_grps, hwinfo->phys_in_grps,
+	       sizeof(struct snd_efw_phys_grp) * HWINFO_MAX_CAPS_GROUPS);
 end:
 	kfree(hwinfo);
 	return err;
@@ -205,6 +215,8 @@ efw_probe(struct fw_unit *unit,
 	if (err < 0)
 		goto error;
 
+	snd_efw_proc_init(efw);
+
 	snd_card_set_dev(card, &unit->device);
 	err = snd_card_register(card);
 	if (err < 0)
diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h
index 4867264..2b0d85c 100644
--- a/sound/firewire/fireworks/fireworks.h
+++ b/sound/firewire/fireworks/fireworks.h
@@ -31,6 +31,7 @@
 #include <sound/core.h>
 #include <sound/initval.h>
 #include <sound/pcm.h>
+#include <sound/info.h>
 
 #include "../packets-buffer.h"
 #include "../iso-resources.h"
@@ -74,6 +75,14 @@ struct snd_efw {
 	struct amdtp_stream rx_stream;
 	struct cmp_connection out_conn;
 	struct cmp_connection in_conn;
+
+	/* hardware metering parameters */
+	unsigned int phys_out;
+	unsigned int phys_in;
+	unsigned int phys_out_grp_count;
+	unsigned int phys_in_grp_count;
+	struct snd_efw_phys_grp phys_out_grps[HWINFO_MAX_CAPS_GROUPS];
+	struct snd_efw_phys_grp phys_in_grps[HWINFO_MAX_CAPS_GROUPS];
 };
 
 /* for transaction */
@@ -195,6 +204,8 @@ int snd_efw_stream_stop_duplex(struct snd_efw *efw);
 void snd_efw_stream_update_duplex(struct snd_efw *efw);
 void snd_efw_stream_destroy_duplex(struct snd_efw *efw);
 
+void snd_efw_proc_init(struct snd_efw *efw);
+
 #define SND_EFW_DEV_ENTRY(vendor, model) \
 { \
 	.match_flags	= IEEE1394_MATCH_VENDOR_ID | \
diff --git a/sound/firewire/fireworks/fireworks_proc.c b/sound/firewire/fireworks/fireworks_proc.c
new file mode 100644
index 0000000..2b0f936
--- /dev/null
+++ b/sound/firewire/fireworks/fireworks_proc.c
@@ -0,0 +1,198 @@
+/*
+ * fireworks_proc.c - a part of driver for Fireworks based devices
+ *
+ * Copyright (c) 2009-2010 Clemens Ladisch
+ * Copyright (c) 2013 Takashi Sakamoto
+ *
+ *
+ * This driver is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2.
+ *
+ * This driver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this driver; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "./fireworks.h"
+
+static inline const char*
+get_phys_name(struct snd_efw_phys_grp *grp)
+{
+	const char *ch_type[] = {
+		"Analog", "S/PDIF", "ADAT", "S/PDIF or ADAT",
+		"Mirroring", "Headphones", "I2S", "Guitar",
+		"Pirzo Guitar", "Guitar String", "Virtual", "Dummy"
+	};
+
+	if (grp->type < 10)
+		return ch_type[grp->type];
+	else if (grp->type == 0x10000)
+		return ch_type[10];
+	else
+		return ch_type[11];
+}
+
+static void
+proc_read_hwinfo(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct snd_efw *efw = entry->private_data;
+	unsigned short i;
+	struct snd_efw_hwinfo hwinfo;
+
+	if (snd_efw_command_get_hwinfo(efw, &hwinfo) < 0)
+		goto end;
+
+	snd_iprintf(buffer, "guid_hi: 0x%X\n", hwinfo.guid_hi);
+	snd_iprintf(buffer, "guid_lo: 0x%X\n", hwinfo.guid_lo);
+	snd_iprintf(buffer, "type: 0x%X\n", hwinfo.type);
+	snd_iprintf(buffer, "version: 0x%X\n", hwinfo.version);
+	snd_iprintf(buffer, "vendor_name: %s\n", hwinfo.vendor_name);
+	snd_iprintf(buffer, "model_name: %s\n", hwinfo.model_name);
+
+	snd_iprintf(buffer, "dsp_version: 0x%X\n", hwinfo.dsp_version);
+	snd_iprintf(buffer, "arm_version: 0x%X\n", hwinfo.arm_version);
+	snd_iprintf(buffer, "fpga_version: 0x%X\n", hwinfo.fpga_version);
+
+	snd_iprintf(buffer, "flags: 0x%X\n", hwinfo.flags);
+
+	snd_iprintf(buffer, "max_sample_rate: 0x%X\n", hwinfo.max_sample_rate);
+	snd_iprintf(buffer, "min_sample_rate: 0x%X\n", hwinfo.min_sample_rate);
+	snd_iprintf(buffer, "supported_clock: 0x%X\n",
+		    hwinfo.supported_clocks);
+
+	snd_iprintf(buffer, "phys out: 0x%X\n", hwinfo.phys_out);
+	snd_iprintf(buffer, "phys in: 0x%X\n", hwinfo.phys_in);
+
+	snd_iprintf(buffer, "phys in grps: 0x%X\n", hwinfo.phys_in_grp_count);
+	for (i = 0; i < hwinfo.phys_in_grp_count; i++) {
+		snd_iprintf(buffer,
+			    "phys in grp[0x%d]: type 0x%d, count 0x%d\n",
+			    i, hwinfo.phys_out_grps[i].type,
+			    hwinfo.phys_out_grps[i].count);
+	}
+
+	snd_iprintf(buffer, "phys out grps: 0x%X\n", hwinfo.phys_out_grp_count);
+	for (i = 0; i < hwinfo.phys_out_grp_count; i++) {
+		snd_iprintf(buffer,
+			    "phys out grps[0x%d]: type 0x%d, count 0x%d\n",
+			    i, hwinfo.phys_out_grps[i].type,
+			    hwinfo.phys_out_grps[i].count);
+	}
+
+	snd_iprintf(buffer, "amdtp rx pcm channels 1x: 0x%X\n",
+		    hwinfo.amdtp_rx_pcm_channels);
+	snd_iprintf(buffer, "amdtp tx pcm channels 1x: 0x%X\n",
+		    hwinfo.amdtp_tx_pcm_channels);
+	snd_iprintf(buffer, "amdtp rx pcm channels 2x: 0x%X\n",
+		    hwinfo.amdtp_rx_pcm_channels_2x);
+	snd_iprintf(buffer, "amdtp tx pcm channels 2x: 0x%X\n",
+		    hwinfo.amdtp_tx_pcm_channels_2x);
+	snd_iprintf(buffer, "amdtp rx pcm channels 4x: 0x%X\n",
+		    hwinfo.amdtp_rx_pcm_channels_4x);
+	snd_iprintf(buffer, "amdtp tx pcm channels 4x: 0x%X\n",
+		    hwinfo.amdtp_tx_pcm_channels_4x);
+
+	snd_iprintf(buffer, "midi out ports: 0x%X\n", hwinfo.midi_out_ports);
+	snd_iprintf(buffer, "midi in ports: 0x%X\n", hwinfo.midi_in_ports);
+
+	snd_iprintf(buffer, "num mixer_playback_channels: 0x%X\n",
+		    hwinfo.mixer_playback_channels);
+	snd_iprintf(buffer, "num mixer_capture_channels: 0x%X\n",
+		    hwinfo.mixer_capture_channels);
+end:
+	return;
+}
+
+static void
+proc_read_clock(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
+{
+	struct snd_efw *efw = entry->private_data;
+	enum snd_efw_clock_source clock_source;
+	unsigned int sampling_rate;
+
+	if (snd_efw_command_get_clock_source(efw, &clock_source) < 0)
+		goto end;
+
+	if (snd_efw_command_get_sampling_rate(efw, &sampling_rate) < 0)
+		goto end;
+
+	snd_iprintf(buffer, "Clock Source: %d\n", clock_source);
+	snd_iprintf(buffer, "Sampling Rate: %d\n", sampling_rate);
+
+end:
+	return;
+}
+
+/*
+ * NOTE:
+ *  dB = 20 * log10(linear / 0x01000000)
+ *  -144.0 dB when linear is 0
+ */
+static void
+proc_read_phys_meters(struct snd_info_entry *entry,
+		      struct snd_info_buffer *buffer)
+{
+	struct snd_efw *efw = entry->private_data;
+	struct snd_efw_phys_meters *meters;
+	unsigned int g, c, m, max, size;
+	const char *name;
+	u32 *linear;
+	int err;
+
+	size = sizeof(struct snd_efw_phys_meters) +
+	       (efw->phys_in + efw->phys_out) * sizeof(u32);
+	meters = kzalloc(size, GFP_KERNEL);
+	if (meters == NULL)
+		return;
+
+	err = snd_efw_command_get_phys_meters(efw, meters, size);
+	if (err < 0)
+		goto end;
+
+	snd_iprintf(buffer, "Physical Meters:\n");
+
+	m = 0;
+	max = min(efw->phys_out, meters->out_meters);
+	linear = meters->values;
+	snd_iprintf(buffer, " %d Outputs:\n", max);
+	for (g = 0; g < efw->phys_out_grp_count; g++) {
+		name = get_phys_name(&efw->phys_out_grps[g]);
+		for (c = 0; c < efw->phys_out_grps[g].count; c++) {
+			if (m < max)
+				snd_iprintf(buffer, "\t%s [%d]: %d\n",
+					    name, c, linear[m++]);
+		}
+	}
+
+	m = 0;
+	max = min(efw->phys_in, meters->in_meters);
+	linear = meters->values + meters->out_meters;
+	snd_iprintf(buffer, " %d Inputs:\n", max);
+	for (g = 0; g < efw->phys_in_grp_count; g++) {
+		name = get_phys_name(&efw->phys_in_grps[g]);
+		for (c = 0; c < efw->phys_in_grps[g].count; c++)
+			if (m < max)
+				snd_iprintf(buffer, "\t%s [%d]: %d\n",
+					    name, c, linear[m++]);
+	}
+end:
+	kfree(meters);
+	return;
+}
+
+void snd_efw_proc_init(struct snd_efw *efw)
+{
+	struct snd_info_entry *entry;
+
+	if (!snd_card_proc_new(efw->card, "#hardware", &entry))
+		snd_info_set_text_ops(entry, efw, proc_read_hwinfo);
+	if (!snd_card_proc_new(efw->card, "#clock", &entry))
+		snd_info_set_text_ops(entry, efw, proc_read_clock);
+	if (!snd_card_proc_new(efw->card, "#meters", &entry))
+		snd_info_set_text_ops(entry, efw, proc_read_phys_meters);
+	return;
+}
-- 
1.8.3.2

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

* [PATCH 5/8] fireworks: Add MIDI interface
  2013-12-11  9:15 [PATCH v2 0/8][RFC] a driver for Fireworks based devices Takashi Sakamoto
                   ` (3 preceding siblings ...)
  2013-12-11  9:15 ` [PATCH 4/8] fireworks: Add proc interface for debugging purpose Takashi Sakamoto
@ 2013-12-11  9:15 ` Takashi Sakamoto
  2013-12-11  9:15 ` [PATCH 6/8] fireworks: Add PCM interface Takashi Sakamoto
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Takashi Sakamoto @ 2013-12-11  9:15 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-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/fireworks/Makefile         |   3 +-
 sound/firewire/fireworks/fireworks.c      |   6 ++
 sound/firewire/fireworks/fireworks.h      |   3 +
 sound/firewire/fireworks/fireworks_midi.c | 155 ++++++++++++++++++++++++++++++
 4 files changed, 166 insertions(+), 1 deletion(-)
 create mode 100644 sound/firewire/fireworks/fireworks_midi.c

diff --git a/sound/firewire/fireworks/Makefile b/sound/firewire/fireworks/Makefile
index 52bd15e..6594bcc 100644
--- a/sound/firewire/fireworks/Makefile
+++ b/sound/firewire/fireworks/Makefile
@@ -1,3 +1,4 @@
 snd-fireworks-objs := fireworks_transaction.o fireworks_command.o \
-		      fireworks_stream.o fireworks_proc.o fireworks.o
+		      fireworks_stream.o fireworks_proc.o fireworks.o \
+		      fireworks_midi.o fireworks.o
 obj-m += snd-fireworks.o
diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c
index adc4a91..dff7d24 100644
--- a/sound/firewire/fireworks/fireworks.c
+++ b/sound/firewire/fireworks/fireworks.c
@@ -217,6 +217,12 @@ efw_probe(struct fw_unit *unit,
 
 	snd_efw_proc_init(efw);
 
+	if (efw->midi_out_ports || efw->midi_in_ports) {
+		err = snd_efw_create_midi_devices(efw);
+		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/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h
index 2b0d85c..0f892a8 100644
--- a/sound/firewire/fireworks/fireworks.h
+++ b/sound/firewire/fireworks/fireworks.h
@@ -32,6 +32,7 @@
 #include <sound/initval.h>
 #include <sound/pcm.h>
 #include <sound/info.h>
+#include <sound/rawmidi.h>
 
 #include "../packets-buffer.h"
 #include "../iso-resources.h"
@@ -206,6 +207,8 @@ void snd_efw_stream_destroy_duplex(struct snd_efw *efw);
 
 void snd_efw_proc_init(struct snd_efw *efw);
 
+int snd_efw_create_midi_devices(struct snd_efw *efw);
+
 #define SND_EFW_DEV_ENTRY(vendor, model) \
 { \
 	.match_flags	= IEEE1394_MATCH_VENDOR_ID | \
diff --git a/sound/firewire/fireworks/fireworks_midi.c b/sound/firewire/fireworks/fireworks_midi.c
new file mode 100644
index 0000000..6c320f1
--- /dev/null
+++ b/sound/firewire/fireworks/fireworks_midi.c
@@ -0,0 +1,155 @@
+/*
+ * fireworks_midi.c - a part of driver for Fireworks based devices
+ *
+ * Copyright (c) 2009-2010 Clemens Ladisch
+ * Copyright (c) 2013 Takashi Sakamoto
+ *
+ *
+ * This driver is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2.
+ *
+ * This driver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this driver; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "fireworks.h"
+
+static int midi_capture_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_efw *efw = substream->rmidi->private_data;
+	return snd_efw_stream_start_duplex(efw, &efw->tx_stream, 0);
+}
+
+static int midi_playback_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_efw *efw = substream->rmidi->private_data;
+	return snd_efw_stream_start_duplex(efw, &efw->rx_stream, 0);
+}
+
+static int midi_close(struct snd_rawmidi_substream *substream)
+{
+	struct snd_efw *efw = substream->rmidi->private_data;
+	snd_efw_stream_stop_duplex(efw);
+	return 0;
+}
+
+static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
+{
+	struct snd_efw *efw = substrm->rmidi->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&efw->lock, flags);
+
+	if (up)
+		amdtp_stream_midi_trigger(&efw->tx_stream,
+					  substrm->number, substrm);
+	else
+		amdtp_stream_midi_trigger(&efw->tx_stream,
+					  substrm->number, NULL);
+
+	spin_unlock_irqrestore(&efw->lock, flags);
+
+	return;
+}
+
+static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up)
+{
+	struct snd_efw *efw = substrm->rmidi->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&efw->lock, flags);
+
+	if (up)
+		amdtp_stream_midi_trigger(&efw->rx_stream,
+					  substrm->number, substrm);
+	else
+		amdtp_stream_midi_trigger(&efw->rx_stream,
+					  substrm->number, NULL);
+
+	spin_unlock_irqrestore(&efw->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_efw *efw,
+				     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", efw->card->shortname, subs->number + 1);
+	}
+}
+
+int snd_efw_create_midi_devices(struct snd_efw *efw)
+{
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_str *str;
+	int err;
+
+	/* check the number of midi stream */
+	if ((efw->midi_in_ports > SND_EFW_MAX_MIDI_IN_PORTS) |
+	    (efw->midi_out_ports > SND_EFW_MAX_MIDI_OUT_PORTS))
+		return -EIO;
+
+	/* create midi ports */
+	err = snd_rawmidi_new(efw->card, efw->card->driver, 0,
+			      efw->midi_out_ports, efw->midi_in_ports,
+			      &rmidi);
+	if (err < 0)
+		return err;
+
+	snprintf(rmidi->name, sizeof(rmidi->name),
+			"%s MIDI", efw->card->shortname);
+	rmidi->private_data = efw;
+
+	if (efw->midi_in_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(efw, str);
+	}
+
+	if (efw->midi_out_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(efw, str);
+
+		/*
+		 * Fireworks ignores MIDI messages in greater than first 8 data
+		 * blocks of an AMDTP packet.
+		 */
+		efw->rx_stream.blocks_for_midi = 8;
+	}
+
+	if ((efw->midi_out_ports > 0) && (efw->midi_in_ports > 0))
+		rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
+
+	return 0;
+}
-- 
1.8.3.2

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

* [PATCH 6/8] fireworks: Add PCM interface
  2013-12-11  9:15 [PATCH v2 0/8][RFC] a driver for Fireworks based devices Takashi Sakamoto
                   ` (4 preceding siblings ...)
  2013-12-11  9:15 ` [PATCH 5/8] fireworks: Add MIDI interface Takashi Sakamoto
@ 2013-12-11  9:15 ` Takashi Sakamoto
  2013-12-11  9:15 ` [PATCH 7/8] fireworks: Add hwdep interface Takashi Sakamoto
  2013-12-11  9:15 ` [PATCH 8/8] fireworks: Add command/response functionality into " Takashi Sakamoto
  7 siblings, 0 replies; 9+ messages in thread
From: Takashi Sakamoto @ 2013-12-11  9:15 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel

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

When AMDTP stream is already running for PCM or the source of clock is not
internal, available sampling rate is limited at current one.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/fireworks/Makefile           |   2 +-
 sound/firewire/fireworks/fireworks.c        |   4 +
 sound/firewire/fireworks/fireworks.h        |   4 +
 sound/firewire/fireworks/fireworks_pcm.c    | 450 ++++++++++++++++++++++++++++
 sound/firewire/fireworks/fireworks_stream.c |  23 --
 5 files changed, 459 insertions(+), 24 deletions(-)
 create mode 100644 sound/firewire/fireworks/fireworks_pcm.c

diff --git a/sound/firewire/fireworks/Makefile b/sound/firewire/fireworks/Makefile
index 6594bcc..a92876d 100644
--- a/sound/firewire/fireworks/Makefile
+++ b/sound/firewire/fireworks/Makefile
@@ -1,4 +1,4 @@
 snd-fireworks-objs := fireworks_transaction.o fireworks_command.o \
 		      fireworks_stream.o fireworks_proc.o fireworks.o \
-		      fireworks_midi.o fireworks.o
+		      fireworks_midi.o fireworks_pcm.o fireworks.o
 obj-m += snd-fireworks.o
diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c
index dff7d24..0ae669c 100644
--- a/sound/firewire/fireworks/fireworks.c
+++ b/sound/firewire/fireworks/fireworks.c
@@ -223,6 +223,10 @@ efw_probe(struct fw_unit *unit,
 			goto error;
 	}
 
+	err = snd_efw_create_pcm_devices(efw);
+	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/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h
index 0f892a8..2dc7008 100644
--- a/sound/firewire/fireworks/fireworks.h
+++ b/sound/firewire/fireworks/fireworks.h
@@ -33,6 +33,7 @@
 #include <sound/pcm.h>
 #include <sound/info.h>
 #include <sound/rawmidi.h>
+#include <sound/pcm_params.h>
 
 #include "../packets-buffer.h"
 #include "../iso-resources.h"
@@ -209,6 +210,9 @@ void snd_efw_proc_init(struct snd_efw *efw);
 
 int snd_efw_create_midi_devices(struct snd_efw *efw);
 
+int snd_efw_create_pcm_devices(struct snd_efw *efw);
+int snd_efw_get_multiplier_mode(int sampling_rate);
+
 #define SND_EFW_DEV_ENTRY(vendor, model) \
 { \
 	.match_flags	= IEEE1394_MATCH_VENDOR_ID | \
diff --git a/sound/firewire/fireworks/fireworks_pcm.c b/sound/firewire/fireworks/fireworks_pcm.c
new file mode 100644
index 0000000..4f25a78
--- /dev/null
+++ b/sound/firewire/fireworks/fireworks_pcm.c
@@ -0,0 +1,450 @@
+/*
+ * fireworks_pcm.c - a part of driver for Fireworks based devices
+ *
+ * Copyright (c) 2009-2010 Clemens Ladisch
+ * Copyright (c) 2013 Takashi Sakamoto
+ *
+ *
+ * This driver is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2.
+ *
+ * This driver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this driver; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include "./fireworks.h"
+
+/*
+ * NOTE:
+ * Fireworks changes its AMDTP channels for PCM data according to its sampling
+ * rate. There are three modes. Here _XX is either _rx or _tx.
+ *  0:  32.0- 48.0 kHz then snd_efw_hwinfo.amdtp_XX_pcm_channels applied
+ *  1:  88.2- 96.0 kHz then snd_efw_hwinfo.amdtp_XX_pcm_channels_2x applied
+ *  2: 176.4-192.0 kHz then snd_efw_hwinfo.amdtp_XX_pcm_channels_4x applied
+ *
+ * The number of PCM channels for analog input and output are always fixed but
+ * the number of PCM channels for digital input and output are differed.
+ *
+ * Additionally, according to "AudioFire Owner's Manual Version 2.2", in some
+ * model, the number of PCM channels for digital input has more restriction
+ * depending on which digital interface is selected.
+ *  - S/PDIF coaxial and optical	: use input 1-2
+ *  - ADAT optical at 32.0-48.0 kHz	: use input 1-8
+ *  - ADAT optical at 88.2-96.0 kHz	: use input 1-4 (S/MUX format)
+ *
+ * The data in AMDTP channels for blank PCM channels are zero.
+ */
+static unsigned int freq_table[] = {
+	/* multiplier mode 0 */
+	[0] = 32000,
+	[1] = 44100,
+	[2] = 48000,
+	/* multiplier mode 1 */
+	[3] = 88200,
+	[4] = 96000,
+	/* multiplier mode 2 */
+	[5] = 176400,
+	[6] = 192000,
+};
+
+static inline int
+get_multiplier_mode_with_index(int index)
+{
+	return ((int)index - 1) / 2;
+}
+
+int snd_efw_get_multiplier_mode(int sampling_rate)
+{
+	int i;
+	for (i = 0; i < sizeof(freq_table); i++)
+		if (freq_table[i] == sampling_rate)
+			return get_multiplier_mode_with_index(i);
+
+	return -1;
+}
+
+static int
+hw_rule_rate(struct snd_pcm_hw_params *params,
+	     struct snd_pcm_hw_rule *rule,
+	     struct snd_efw *efw, unsigned int *channels)
+{
+	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 rate_bit;
+	int mode, i;
+
+	for (i = 0; i < ARRAY_SIZE(freq_table); i++) {
+		/* skip unsupported sampling rate */
+		rate_bit = snd_pcm_rate_to_rate_bit(freq_table[i]);
+		if (!(efw->supported_sampling_rate & rate_bit))
+			continue;
+
+		mode = get_multiplier_mode_with_index(i);
+		if (!snd_interval_test(c, channels[mode]))
+			continue;
+
+		t.min = min(t.min, freq_table[i]);
+		t.max = max(t.max, freq_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_efw *efw, unsigned int *channels)
+{
+	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 rate_bit;
+	int mode, i;
+
+	for (i = 0; i < ARRAY_SIZE(freq_table); i++) {
+		/* skip unsupported sampling rate */
+		rate_bit = snd_pcm_rate_to_rate_bit(freq_table[i]);
+		if (!(efw->supported_sampling_rate & rate_bit))
+			continue;
+
+		mode = get_multiplier_mode_with_index(i);
+		if (!snd_interval_test(r, freq_table[i]))
+			continue;
+
+		t.min = min(t.min, channels[mode]);
+		t.max = max(t.max, channels[mode]);
+
+	}
+
+	return snd_interval_refine(c, &t);
+}
+
+static int
+hw_rule_capture_rate(struct snd_pcm_hw_params *params,
+		     struct snd_pcm_hw_rule *rule)
+{
+	struct snd_efw *efw = rule->private;
+	return hw_rule_rate(params, rule, efw,
+				efw->pcm_capture_channels);
+}
+
+static int
+hw_rule_playback_rate(struct snd_pcm_hw_params *params,
+		      struct snd_pcm_hw_rule *rule)
+{
+	struct snd_efw *efw = rule->private;
+	return hw_rule_rate(params, rule, efw,
+				efw->pcm_playback_channels);
+}
+
+static int
+hw_rule_capture_channels(struct snd_pcm_hw_params *params,
+			 struct snd_pcm_hw_rule *rule)
+{
+	struct snd_efw *efw = rule->private;
+	return hw_rule_channels(params, rule, efw,
+				efw->pcm_capture_channels);
+}
+
+static int
+hw_rule_playback_channels(struct snd_pcm_hw_params *params,
+			  struct snd_pcm_hw_rule *rule)
+{
+	struct snd_efw *efw = rule->private;
+	return hw_rule_channels(params, rule, efw,
+				efw->pcm_playback_channels);
+}
+
+static int
+pcm_init_hw_params(struct snd_efw *efw,
+		   struct snd_pcm_substream *substream)
+{
+	unsigned int *pcm_channels;
+	unsigned int rate_bit;
+	int mode, i;
+	int err;
+
+	struct snd_pcm_hardware hardware = {
+		.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,
+		.rates = efw->supported_sampling_rate,
+		.rate_min = UINT_MAX,
+		.rate_max = 0,
+		.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 = hardware;
+	substream->runtime->delay = substream->runtime->hw.fifo_size;
+
+	/* add rule between channels and sampling rate */
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		substream->runtime->hw.formats = SNDRV_PCM_FMTBIT_S32;
+		snd_pcm_hw_rule_add(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_CHANNELS,
+				hw_rule_capture_channels, efw,
+				SNDRV_PCM_HW_PARAM_RATE, -1);
+		snd_pcm_hw_rule_add(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_RATE,
+				hw_rule_capture_rate, efw,
+				SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+		pcm_channels = efw->pcm_capture_channels;
+	} else {
+		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, efw,
+				SNDRV_PCM_HW_PARAM_RATE, -1);
+		snd_pcm_hw_rule_add(substream->runtime, 0,
+				SNDRV_PCM_HW_PARAM_RATE,
+				hw_rule_playback_rate, efw,
+				SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+		pcm_channels = efw->pcm_playback_channels;
+	}
+
+	/* limitation for min/max sampling rate */
+	snd_pcm_limit_hw_rates(substream->runtime);
+
+	/* limitation for the number of channels */
+	for (i = 0; i < ARRAY_SIZE(freq_table); i++) {
+		/* skip unsupported sampling rate */
+		rate_bit = snd_pcm_rate_to_rate_bit(freq_table[i]);
+		if (!(efw->supported_sampling_rate & rate_bit))
+			continue;
+
+		mode = get_multiplier_mode_with_index(i);
+		if (pcm_channels[mode] == 0)
+			continue;
+		substream->runtime->hw.channels_min =
+			min(substream->runtime->hw.channels_min,
+				pcm_channels[mode]);
+		substream->runtime->hw.channels_max =
+			max(substream->runtime->hw.channels_max,
+				pcm_channels[mode]);
+	}
+
+	/* 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);
+end:
+	return err;
+}
+
+static int pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_efw *efw = substream->private_data;
+	int sampling_rate;
+	unsigned int clock_source;
+	int err;
+
+	err = pcm_init_hw_params(efw, substream);
+	if (err < 0)
+		goto end;
+
+	err = snd_efw_command_get_clock_source(efw, &clock_source);
+	if (err < 0)
+		goto end;
+
+	/*
+	 * When source of clock is not internal or any PCM stream are running,
+	 * the available sampling rate is limited at current sampling rate.
+	 */
+	if ((clock_source != SND_EFW_CLOCK_SOURCE_INTERNAL) ||
+	    amdtp_stream_pcm_running(&efw->tx_stream) ||
+	    amdtp_stream_pcm_running(&efw->rx_stream)) {
+		err = snd_efw_command_get_sampling_rate(efw, &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_efw *efw = substream->private_data;
+
+	snd_efw_stream_stop_duplex(efw);
+
+	return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int pcm_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_efw *efw = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	err = snd_efw_stream_start_duplex(efw, &efw->tx_stream, runtime->rate);
+	if (err < 0)
+		goto end;
+
+	amdtp_stream_set_pcm_format(&efw->tx_stream, runtime->format);
+	amdtp_stream_pcm_prepare(&efw->tx_stream);
+end:
+	return err;
+}
+static int pcm_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_efw *efw = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	err = snd_efw_stream_start_duplex(efw, &efw->rx_stream, runtime->rate);
+	if (err < 0)
+		goto end;
+
+	amdtp_stream_set_pcm_format(&efw->rx_stream, runtime->format);
+	amdtp_stream_pcm_prepare(&efw->rx_stream);
+end:
+	return err;
+}
+
+static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_efw *efw = substream->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		amdtp_stream_pcm_trigger(&efw->tx_stream, substream);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		amdtp_stream_pcm_trigger(&efw->tx_stream, NULL);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_efw *efw = substream->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		amdtp_stream_pcm_trigger(&efw->rx_stream, substream);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		amdtp_stream_pcm_trigger(&efw->rx_stream, NULL);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
+{
+	struct snd_efw *efw = sbstrm->private_data;
+	return amdtp_stream_pcm_pointer(&efw->tx_stream);
+}
+static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
+{
+	struct snd_efw *efw = sbstrm->private_data;
+	return amdtp_stream_pcm_pointer(&efw->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_efw_create_pcm_devices(struct snd_efw *efw)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(efw->card, efw->card->driver, 0, 1, 1, &pcm);
+	if (err < 0)
+		goto end;
+
+	pcm->private_data = efw;
+	snprintf(pcm->name, sizeof(pcm->name), "%s PCM", efw->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;
+}
+
diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c
index f0764538..5fd2a12 100644
--- a/sound/firewire/fireworks/fireworks_stream.c
+++ b/sound/firewire/fireworks/fireworks_stream.c
@@ -17,29 +17,6 @@
  */
 #include "./fireworks.h"
 
-static unsigned int freq_table[] = {
-	/* multiplier mode 0 */
-	[0] = 32000,
-	[1] = 44100,
-	[2] = 48000,
-	/* multiplier mode 1 */
-	[3] = 88200,
-	[4] = 96000,
-	/* multiplier mode 2 */
-	[5] = 176400,
-	[6] = 192000,
-};
-
-int snd_efw_get_multiplier_mode(int sampling_rate)
-{
-	unsigned int i;
-	for (i = 0; i < sizeof(freq_table); i++)
-		if (freq_table[i] == sampling_rate)
-			return (i - 1) / 2;
-
-	return -1;
-}
-
 static int
 init_stream(struct snd_efw *efw, struct amdtp_stream *stream)
 {
-- 
1.8.3.2

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

* [PATCH 7/8] fireworks: Add hwdep interface
  2013-12-11  9:15 [PATCH v2 0/8][RFC] a driver for Fireworks based devices Takashi Sakamoto
                   ` (5 preceding siblings ...)
  2013-12-11  9:15 ` [PATCH 6/8] fireworks: Add PCM interface Takashi Sakamoto
@ 2013-12-11  9:15 ` Takashi Sakamoto
  2013-12-11  9:15 ` [PATCH 8/8] fireworks: Add command/response functionality into " Takashi Sakamoto
  7 siblings, 0 replies; 9+ messages in thread
From: Takashi Sakamoto @ 2013-12-11  9:15 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-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               |   1 +
 sound/firewire/fireworks/Makefile           |   3 +-
 sound/firewire/fireworks/fireworks.c        |   5 +
 sound/firewire/fireworks/fireworks.h        |  12 ++
 sound/firewire/fireworks/fireworks_hwdep.c  | 207 ++++++++++++++++++++++++++++
 sound/firewire/fireworks/fireworks_midi.c   |  25 +++-
 sound/firewire/fireworks/fireworks_pcm.c    |  13 +-
 sound/firewire/fireworks/fireworks_stream.c |  39 ++++++
 9 files changed, 302 insertions(+), 6 deletions(-)
 create mode 100644 sound/firewire/fireworks/fireworks_hwdep.c

diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h
index 9fc6219..5dfbfdf 100644
--- a/include/uapi/sound/asound.h
+++ b/include/uapi/sound/asound.h
@@ -94,9 +94,10 @@ enum {
 	SNDRV_HWDEP_IFACE_HDA,		/* HD-audio */
 	SNDRV_HWDEP_IFACE_USB_STREAM,	/* direct access to usb stream */
 	SNDRV_HWDEP_IFACE_FW_DICE,	/* TC DICE FireWire device */
+	SNDRV_HWDEP_IFACE_FW_FIREWORKS,	/* Echo Audio Fireworks based device */
 
 	/* Don't forget to change the following: */
-	SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_DICE
+	SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_FIREWORKS
 };
 
 struct snd_hwdep_info {
diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h
index 59f5961..ac4f7ea 100644
--- a/include/uapi/sound/firewire.h
+++ b/include/uapi/sound/firewire.h
@@ -34,6 +34,7 @@ union snd_firewire_event {
 #define SNDRV_FIREWIRE_IOCTL_UNLOCK    _IO('H', 0xfa)
 
 #define SNDRV_FIREWIRE_TYPE_DICE	1
+#define SNDRV_FIREWIRE_TYPE_FIREWORKS	2
 /* Fireworks, AV/C, RME, MOTU, ... */
 
 struct snd_firewire_get_info {
diff --git a/sound/firewire/fireworks/Makefile b/sound/firewire/fireworks/Makefile
index a92876d..3212c1f 100644
--- a/sound/firewire/fireworks/Makefile
+++ b/sound/firewire/fireworks/Makefile
@@ -1,4 +1,5 @@
 snd-fireworks-objs := fireworks_transaction.o fireworks_command.o \
 		      fireworks_stream.o fireworks_proc.o fireworks.o \
-		      fireworks_midi.o fireworks_pcm.o fireworks.o
+		      fireworks_midi.o fireworks_pcm.o fireworks_hwdep.o \
+		      fireworks.o
 obj-m += snd-fireworks.o
diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c
index 0ae669c..a44b0e5 100644
--- a/sound/firewire/fireworks/fireworks.c
+++ b/sound/firewire/fireworks/fireworks.c
@@ -206,6 +206,7 @@ efw_probe(struct fw_unit *unit,
 	efw->card_index = -1;
 	mutex_init(&efw->mutex);
 	spin_lock_init(&efw->lock);
+	init_waitqueue_head(&efw->hwdep_wait);
 
 	err = get_hardware_info(efw);
 	if (err < 0)
@@ -227,6 +228,10 @@ efw_probe(struct fw_unit *unit,
 	if (err < 0)
 		goto error;
 
+	err = snd_efw_create_hwdep_device(efw);
+	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/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h
index 2dc7008..2c08ad4 100644
--- a/sound/firewire/fireworks/fireworks.h
+++ b/sound/firewire/fireworks/fireworks.h
@@ -34,6 +34,8 @@
 #include <sound/info.h>
 #include <sound/rawmidi.h>
 #include <sound/pcm_params.h>
+#include <sound/firewire.h>
+#include <sound/hwdep.h>
 
 #include "../packets-buffer.h"
 #include "../iso-resources.h"
@@ -85,6 +87,11 @@ struct snd_efw {
 	unsigned int phys_in_grp_count;
 	struct snd_efw_phys_grp phys_out_grps[HWINFO_MAX_CAPS_GROUPS];
 	struct snd_efw_phys_grp phys_in_grps[HWINFO_MAX_CAPS_GROUPS];
+
+	/* for uapi */
+	int dev_lock_count;
+	bool dev_lock_changed;
+	wait_queue_head_t hwdep_wait;
 };
 
 /* for transaction */
@@ -205,6 +212,9 @@ int snd_efw_stream_start_duplex(struct snd_efw *efw,
 int snd_efw_stream_stop_duplex(struct snd_efw *efw);
 void snd_efw_stream_update_duplex(struct snd_efw *efw);
 void snd_efw_stream_destroy_duplex(struct snd_efw *efw);
+void snd_efw_stream_lock_changed(struct snd_efw *efw);
+int snd_efw_stream_lock_try(struct snd_efw *efw);
+void snd_efw_stream_lock_release(struct snd_efw *efw);
 
 void snd_efw_proc_init(struct snd_efw *efw);
 
@@ -213,6 +223,8 @@ int snd_efw_create_midi_devices(struct snd_efw *efw);
 int snd_efw_create_pcm_devices(struct snd_efw *efw);
 int snd_efw_get_multiplier_mode(int sampling_rate);
 
+int snd_efw_create_hwdep_device(struct snd_efw *efw);
+
 #define SND_EFW_DEV_ENTRY(vendor, model) \
 { \
 	.match_flags	= IEEE1394_MATCH_VENDOR_ID | \
diff --git a/sound/firewire/fireworks/fireworks_hwdep.c b/sound/firewire/fireworks/fireworks_hwdep.c
new file mode 100644
index 0000000..6f430fa
--- /dev/null
+++ b/sound/firewire/fireworks/fireworks_hwdep.c
@@ -0,0 +1,207 @@
+/*
+ * fireworks_hwdep.c - a part of driver for Fireworks based devices
+ *
+ * Copyright (c) 2013 Takashi Sakamoto
+ *
+ *
+ * This driver is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, version 2.
+ *
+ * This driver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this driver; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * This codes have three functionalities.
+ *
+ * 1.get information about firewire node
+ * 2.get notification about starting/stopping stream
+ * 3.lock/unlock streaming
+ */
+
+#include "fireworks.h"
+
+static long
+hwdep_read(struct snd_hwdep *hwdep, char __user *buf,  long count,
+	   loff_t *offset)
+{
+	struct snd_efw *efw = hwdep->private_data;
+	DEFINE_WAIT(wait);
+	union snd_firewire_event event;
+
+	spin_lock_irq(&efw->lock);
+
+	while (!efw->dev_lock_changed) {
+		prepare_to_wait(&efw->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
+		spin_unlock_irq(&efw->lock);
+		schedule();
+		finish_wait(&efw->hwdep_wait, &wait);
+		if (signal_pending(current))
+			return -ERESTARTSYS;
+		spin_lock_irq(&efw->lock);
+	}
+
+	memset(&event, 0, sizeof(event));
+	if (efw->dev_lock_changed) {
+		event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
+		event.lock_status.status = (efw->dev_lock_count > 0);
+		efw->dev_lock_changed = false;
+
+		count = min_t(long, count, sizeof(event.lock_status));
+	}
+
+	spin_unlock_irq(&efw->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_efw *efw = hwdep->private_data;
+	unsigned int events;
+
+	poll_wait(file, &efw->hwdep_wait, wait);
+
+	spin_lock_irq(&efw->lock);
+	if (efw->dev_lock_changed)
+		events = POLLIN | POLLRDNORM;
+	else
+		events = 0;
+	spin_unlock_irq(&efw->lock);
+
+	return events;
+}
+
+static int
+hwdep_get_info(struct snd_efw *efw, void __user *arg)
+{
+	struct fw_device *dev = fw_parent_device(efw->unit);
+	struct snd_firewire_get_info info;
+
+	memset(&info, 0, sizeof(info));
+	info.type = SNDRV_FIREWIRE_TYPE_FIREWORKS;
+	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_efw *efw)
+{
+	int err;
+
+	spin_lock_irq(&efw->lock);
+
+	if (efw->dev_lock_count == 0) {
+		efw->dev_lock_count = -1;
+		err = 0;
+	} else
+		err = -EBUSY;
+
+	spin_unlock_irq(&efw->lock);
+
+	return err;
+}
+
+static int
+hwdep_unlock(struct snd_efw *efw)
+{
+	int err;
+
+	spin_lock_irq(&efw->lock);
+
+	if (efw->dev_lock_count == -1) {
+		efw->dev_lock_count = 0;
+		err = 0;
+	} else
+		err = -EBADFD;
+
+	spin_unlock_irq(&efw->lock);
+
+	return err;
+}
+
+static int
+hwdep_release(struct snd_hwdep *hwdep, struct file *file)
+{
+	struct snd_efw *efw = hwdep->private_data;
+
+	spin_lock_irq(&efw->lock);
+	if (efw->dev_lock_count == -1)
+		efw->dev_lock_count = 0;
+	spin_unlock_irq(&efw->lock);
+
+	return 0;
+}
+
+static int
+hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
+	    unsigned int cmd, unsigned long arg)
+{
+	struct snd_efw *efw = hwdep->private_data;
+
+	switch (cmd) {
+	case SNDRV_FIREWIRE_IOCTL_GET_INFO:
+		return hwdep_get_info(efw, (void __user *)arg);
+	case SNDRV_FIREWIRE_IOCTL_LOCK:
+		return hwdep_lock(efw);
+	case SNDRV_FIREWIRE_IOCTL_UNLOCK:
+		return hwdep_unlock(efw);
+	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_efw_create_hwdep_device(struct snd_efw *efw)
+{
+	struct snd_hwdep *hwdep;
+	int err;
+
+	err = snd_hwdep_new(efw->card, "Fireworks", 0, &hwdep);
+	if (err < 0)
+		goto end;
+	strcpy(hwdep->name, "Fireworks");
+	hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREWORKS;
+	hwdep->ops = hwdep_ops;
+	hwdep->private_data = efw;
+	hwdep->exclusive = true;
+end:
+	return err;
+}
+
diff --git a/sound/firewire/fireworks/fireworks_midi.c b/sound/firewire/fireworks/fireworks_midi.c
index 6c320f1..a905f8f 100644
--- a/sound/firewire/fireworks/fireworks_midi.c
+++ b/sound/firewire/fireworks/fireworks_midi.c
@@ -21,19 +21,40 @@
 static int midi_capture_open(struct snd_rawmidi_substream *substream)
 {
 	struct snd_efw *efw = substream->rmidi->private_data;
-	return snd_efw_stream_start_duplex(efw, &efw->tx_stream, 0);
+	int err;
+
+	err = snd_efw_stream_lock_try(efw);
+	if (err < 0)
+		goto end;
+
+	err = snd_efw_stream_start_duplex(efw, &efw->tx_stream, 0);
+	if (err < 0)
+		snd_efw_stream_lock_release(efw);
+end:
+	return err;
 }
 
 static int midi_playback_open(struct snd_rawmidi_substream *substream)
 {
 	struct snd_efw *efw = substream->rmidi->private_data;
-	return snd_efw_stream_start_duplex(efw, &efw->rx_stream, 0);
+	int err;
+
+	err = snd_efw_stream_lock_try(efw);
+	if (err < 0)
+		goto end;
+
+	err = snd_efw_stream_start_duplex(efw, &efw->rx_stream, 0);
+	if (err < 0)
+		snd_efw_stream_lock_release(efw);
+end:
+	return err;
 }
 
 static int midi_close(struct snd_rawmidi_substream *substream)
 {
 	struct snd_efw *efw = substream->rmidi->private_data;
 	snd_efw_stream_stop_duplex(efw);
+	snd_efw_stream_lock_release(efw);
 	return 0;
 }
 
diff --git a/sound/firewire/fireworks/fireworks_pcm.c b/sound/firewire/fireworks/fireworks_pcm.c
index 4f25a78..09f1890 100644
--- a/sound/firewire/fireworks/fireworks_pcm.c
+++ b/sound/firewire/fireworks/fireworks_pcm.c
@@ -280,10 +280,14 @@ static int pcm_open(struct snd_pcm_substream *substream)
 	unsigned int clock_source;
 	int err;
 
-	err = pcm_init_hw_params(efw, substream);
+	err = snd_efw_stream_lock_try(efw);
 	if (err < 0)
 		goto end;
 
+	err = pcm_init_hw_params(efw, substream);
+	if (err < 0)
+		goto err_locked;
+
 	err = snd_efw_command_get_clock_source(efw, &clock_source);
 	if (err < 0)
 		goto end;
@@ -297,7 +301,7 @@ static int pcm_open(struct snd_pcm_substream *substream)
 	    amdtp_stream_pcm_running(&efw->rx_stream)) {
 		err = snd_efw_command_get_sampling_rate(efw, &sampling_rate);
 		if (err < 0)
-			goto end;
+			goto err_locked;
 		substream->runtime->hw.rate_min = sampling_rate;
 		substream->runtime->hw.rate_max = sampling_rate;
 	}
@@ -305,10 +309,15 @@ static int pcm_open(struct snd_pcm_substream *substream)
 	snd_pcm_set_sync(substream);
 end:
 	return err;
+err_locked:
+	snd_efw_stream_lock_release(efw);
+	return err;
 }
 
 static int pcm_close(struct snd_pcm_substream *substream)
 {
+	struct snd_efw *efw = substream->private_data;
+	snd_efw_stream_lock_release(efw);
 	return 0;
 }
 
diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c
index 5fd2a12..3c3f0b6 100644
--- a/sound/firewire/fireworks/fireworks_stream.c
+++ b/sound/firewire/fireworks/fireworks_stream.c
@@ -323,3 +323,42 @@ void snd_efw_stream_destroy_duplex(struct snd_efw *efw)
 	destroy_stream(efw, &efw->rx_stream);
 	destroy_stream(efw, &efw->tx_stream);
 }
+
+void snd_efw_stream_lock_changed(struct snd_efw *efw)
+{
+	efw->dev_lock_changed = true;
+	wake_up(&efw->hwdep_wait);
+}
+
+int snd_efw_stream_lock_try(struct snd_efw *efw)
+{
+	int err;
+
+	spin_lock_irq(&efw->lock);
+
+	/* user land lock this */
+	if (efw->dev_lock_count < 0) {
+		err = -EBUSY;
+		goto end;
+	}
+
+	/* this is the first time */
+	if (efw->dev_lock_count++ == 0)
+		snd_efw_stream_lock_changed(efw);
+	err = 0;
+end:
+	spin_unlock_irq(&efw->lock);
+	return err;
+}
+
+void snd_efw_stream_lock_release(struct snd_efw *efw)
+{
+	spin_lock_irq(&efw->lock);
+
+	if (WARN_ON(efw->dev_lock_count <= 0))
+		goto end;
+	if (--efw->dev_lock_count == 0)
+		snd_efw_stream_lock_changed(efw);
+end:
+	spin_unlock_irq(&efw->lock);
+}
-- 
1.8.3.2

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

* [PATCH 8/8] fireworks: Add command/response functionality into hwdep interface
  2013-12-11  9:15 [PATCH v2 0/8][RFC] a driver for Fireworks based devices Takashi Sakamoto
                   ` (6 preceding siblings ...)
  2013-12-11  9:15 ` [PATCH 7/8] fireworks: Add hwdep interface Takashi Sakamoto
@ 2013-12-11  9:15 ` Takashi Sakamoto
  7 siblings, 0 replies; 9+ messages in thread
From: Takashi Sakamoto @ 2013-12-11  9:15 UTC (permalink / raw)
  To: clemens, tiwai, perex; +Cc: alsa-devel

This commit adds two functionality for hwdep interface, adds two parameters for
this module, add a node for proc interface.

To receive responses from devices, this module already allocate own callback
into private address area in host controller. This means no one can allocate
its own callback to the address. So this module must give a way for user
applications to receive responses.

This commit adds a functionality to receive responses via hwdep interface. The
application can receive responses to read from this interface. To achieve this,
this commit adds a buffer to queue responses. The default size of this buffer is
1024 bytes. This size can be changed to give preferrable size to
'resp_buf_size' parameter for this module. The application should notice rest
of space in this buffer because this module don't push responses when this
buffer has no space.

Additionaly, this commit adds a functionality to transmit commands via hwdep
interface. The application can transmit commands to write into this interface.
I note that the application can transmit one command at once, but can receive
as many responses as possible untill the user-buffer is full.

When using these interfaces, the application must keep maximum number of
sequence number within the number in firewire.h because this module uses this
number to distinguish the response is against the command by the application or
this module.

Usually responses against commands which the application transmits are pushed
into this buffer. But to enable 'resp_buf_debug' parameter for this module, all
responses are pushed into the buffer. When using this mode, I reccomend to
expand the size of buffer.

Finally this commit adds a new node into proc interface to output status of the
buffer.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 include/uapi/sound/firewire.h                    |  18 +++
 sound/firewire/fireworks/fireworks.c             |  17 +++
 sound/firewire/fireworks/fireworks.h             |  23 +--
 sound/firewire/fireworks/fireworks_command.c     |   6 +-
 sound/firewire/fireworks/fireworks_hwdep.c       | 133 +++++++++++++++--
 sound/firewire/fireworks/fireworks_proc.c        |  20 ++-
 sound/firewire/fireworks/fireworks_transaction.c | 176 ++++++++++++++++++++---
 7 files changed, 345 insertions(+), 48 deletions(-)

diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h
index ac4f7ea..48dadb8 100644
--- a/include/uapi/sound/firewire.h
+++ b/include/uapi/sound/firewire.h
@@ -7,6 +7,7 @@
 
 #define SNDRV_FIREWIRE_EVENT_LOCK_STATUS	0x000010cc
 #define SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION	0xd1ce004e
+#define SNDRV_FIREWIRE_EVENT_EFW_RESPONSE	0x57616269
 
 struct snd_firewire_event_common {
 	unsigned int type; /* SNDRV_FIREWIRE_EVENT_xxx */
@@ -22,10 +23,27 @@ struct snd_firewire_event_dice_notification {
 	unsigned int notification; /* DICE-specific bits */
 };
 
+#define SND_EFW_TRANSACTION_SEQNUM_MAX	((uint32_t)(BIT(28) - 1))
+/* each field should be in big endian */
+struct snd_efw_transaction {
+	uint32_t length;
+	uint32_t version;
+	uint32_t seqnum;
+	uint32_t category;
+	uint32_t command;
+	uint32_t status;
+	uint32_t params[0];
+};
+struct snd_firewire_event_efw_response {
+	unsigned int type;
+	uint32_t response[0];	/* some responses */
+};
+
 union snd_firewire_event {
 	struct snd_firewire_event_common            common;
 	struct snd_firewire_event_lock_status       lock_status;
 	struct snd_firewire_event_dice_notification dice_notification;
+	struct snd_firewire_event_efw_response      efw_response;
 };
 
 
diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c
index a44b0e5..4f9120c 100644
--- a/sound/firewire/fireworks/fireworks.c
+++ b/sound/firewire/fireworks/fireworks.c
@@ -34,6 +34,8 @@ 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;
+unsigned int resp_buf_size	= 1024;
+bool resp_buf_debug		= false;
 
 module_param_array(index, int, NULL, 0444);
 MODULE_PARM_DESC(index, "card index");
@@ -41,6 +43,10 @@ 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 Fireworks sound card");
+module_param(resp_buf_size, uint, 0444);
+MODULE_PARM_DESC(resp_buf_size, "response buffer size (default 1024)");
+module_param(resp_buf_debug, bool, 0444);
+MODULE_PARM_DESC(resp_buf_debug, "store all responses to buffer");
 
 static DEFINE_MUTEX(devices_mutex);
 static unsigned int devices_used;
@@ -180,6 +186,7 @@ efw_probe(struct fw_unit *unit,
 {
 	struct snd_card *card;
 	struct snd_efw *efw;
+	void *resp_buf;
 	int card_index, err;
 
 	mutex_lock(&devices_mutex);
@@ -193,6 +200,13 @@ efw_probe(struct fw_unit *unit,
 		goto end;
 	}
 
+	/* prepare response buffer */
+	resp_buf = kzalloc(resp_buf_size, GFP_KERNEL);
+	if (resp_buf == NULL) {
+		err = -ENOMEM;
+		goto end;
+	}
+
 	err = snd_card_create(index[card_index], id[card_index],
 			      THIS_MODULE, sizeof(struct snd_efw), &card);
 	if (err < 0)
@@ -207,6 +221,7 @@ efw_probe(struct fw_unit *unit,
 	mutex_init(&efw->mutex);
 	spin_lock_init(&efw->lock);
 	init_waitqueue_head(&efw->hwdep_wait);
+	efw->resp_buf = efw->pull_ptr = efw->push_ptr = resp_buf;
 
 	err = get_hardware_info(efw);
 	if (err < 0)
@@ -228,6 +243,7 @@ efw_probe(struct fw_unit *unit,
 	if (err < 0)
 		goto error;
 
+	snd_efw_transaction_register_instance(efw);
 	err = snd_efw_create_hwdep_device(efw);
 	if (err < 0)
 		goto error;
@@ -264,6 +280,7 @@ static void efw_remove(struct fw_unit *unit)
 	struct snd_efw *efw = dev_get_drvdata(&unit->device);
 
 	snd_efw_stream_destroy_duplex(efw);
+	snd_efw_transaction_unregister_instance(efw);
 
 	snd_card_disconnect(efw->card);
 	snd_card_free_when_closed(efw->card);
diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h
index 2c08ad4..dd51a22 100644
--- a/sound/firewire/fireworks/fireworks.h
+++ b/sound/firewire/fireworks/fireworks.h
@@ -50,6 +50,9 @@
 #define HWINFO_NAME_SIZE_BYTES		32
 #define HWINFO_MAX_CAPS_GROUPS		8
 
+extern unsigned int resp_buf_size;
+extern bool resp_buf_debug;
+
 struct snd_efw_phys_grp {
 	u8 type;	/* see enum snd_efw_grp_type */
 	u8 count;
@@ -92,25 +95,25 @@ struct snd_efw {
 	int dev_lock_count;
 	bool dev_lock_changed;
 	wait_queue_head_t hwdep_wait;
-};
 
-/* for transaction */
-struct snd_efw_transaction {
-	u32 length;
-	u32 version;
-	u32 seqnum;
-	u32 category;
-	u32 command;
-	u32 status;
-	u32 params[0];
+	/* response queue */
+	u8 *resp_buf;
+	u8 *pull_ptr;
+	u8 *push_ptr;
+	unsigned int resp_queues;
 };
 
+/* for transaction */
+int snd_efw_transaction_cmd(struct fw_unit *unit,
+			    const void *cmd, unsigned int size);
 int snd_efw_transaction_run(struct fw_unit *unit,
 			    const void *cmd, unsigned int cmd_size,
 			    void *resp, unsigned int resp_size, u32 seqnum);
 int snd_efw_transaction_register(void);
 void snd_efw_transaction_unregister(void);
 void snd_efw_transaction_bus_reset(struct fw_unit *unit);
+void snd_efw_transaction_register_instance(struct snd_efw *efw);
+void snd_efw_transaction_unregister_instance(struct snd_efw *efw);
 
 /* for needed commands */
 struct snd_efw_hwinfo {
diff --git a/sound/firewire/fireworks/fireworks_command.c b/sound/firewire/fireworks/fireworks_command.c
index 14906bb..c5b0904 100644
--- a/sound/firewire/fireworks/fireworks_command.c
+++ b/sound/firewire/fireworks/fireworks_command.c
@@ -31,6 +31,7 @@
  * there are some commands which FFADO don't implement
  */
 
+#define EFW_TRANSACTION_SEQNUM_MIN	(SND_EFW_TRANSACTION_SEQNUM_MAX + 1)
 #define EFW_TRANSACTION_SEQNUM_MAX	((u32)~0)
 
 /* for clock source and sampling rate */
@@ -129,8 +130,9 @@ efw_transaction(struct snd_efw *efw, unsigned int category,
 
 	/* to keep consistency of sequence number */
 	spin_lock(&efw->lock);
-	if (efw->seqnum + 1 >= EFW_TRANSACTION_SEQNUM_MAX)
-		efw->seqnum = 0;
+	if ((efw->seqnum < EFW_TRANSACTION_SEQNUM_MIN) ||
+	    (efw->seqnum >= EFW_TRANSACTION_SEQNUM_MAX - 2))
+		efw->seqnum = EFW_TRANSACTION_SEQNUM_MIN;
 	else
 		efw->seqnum += 2;
 	seqnum = efw->seqnum;
diff --git a/sound/firewire/fireworks/fireworks_hwdep.c b/sound/firewire/fireworks/fireworks_hwdep.c
index 6f430fa..be004b3 100644
--- a/sound/firewire/fireworks/fireworks_hwdep.c
+++ b/sound/firewire/fireworks/fireworks_hwdep.c
@@ -17,26 +17,101 @@
  */
 
 /*
- * This codes have three functionalities.
+ * This codes have five functionalities.
  *
  * 1.get information about firewire node
  * 2.get notification about starting/stopping stream
  * 3.lock/unlock streaming
+ * 4.transmit command of EFW transaction
+ * 5.receive response of EFW transaction
+ *
  */
 
 #include "fireworks.h"
 
 static long
-hwdep_read(struct snd_hwdep *hwdep, char __user *buf,  long count,
+hwdep_read_resp_buf(struct snd_efw *efw, char __user *buf, long remained,
+		    loff_t *offset)
+{
+	unsigned int length, till_end, type;
+	struct snd_efw_transaction *t;
+	long count = 0;
+
+	/* data type is SNDRV_FIREWIRE_EVENT_EFW_RESPONSE */
+	if (remained < sizeof(type))
+		goto err;
+	type = SNDRV_FIREWIRE_EVENT_EFW_RESPONSE;
+	if (copy_to_user(buf, &type, sizeof(type)))
+		goto err;
+	remained -= sizeof(type);
+	buf += sizeof(type);
+
+	/* write into buffer as many responses as possible */
+	while (efw->resp_queues > 0) {
+		t = (struct snd_efw_transaction *)(efw->pull_ptr);
+		length = be32_to_cpu(t->length) * sizeof(t->length);
+
+		/* confirm enough space for this response */
+		if (remained < length)
+			break;
+
+		/* copy from ring buffer to user buffer */
+		while (length > 0) {
+			till_end = resp_buf_size -
+				(unsigned int)(efw->pull_ptr - efw->resp_buf);
+			till_end = min_t(unsigned int, length, till_end);
+
+			if (copy_to_user(buf, efw->pull_ptr, till_end))
+				goto err;
+
+			efw->pull_ptr += till_end;
+			if (efw->pull_ptr >= efw->resp_buf + resp_buf_size)
+				efw->pull_ptr = efw->resp_buf;
+
+			length -= till_end;
+			buf += till_end;
+			count += till_end;
+			remained -= till_end;
+		}
+
+		efw->resp_queues--;
+	}
+
+	return count;
+err:
+	return -EFAULT;
+}
+
+static long
+hwdep_read_locked(struct snd_efw *efw, char __user *buf, long count,
+		  loff_t *offset)
+{
+	union snd_firewire_event event;
+
+	memset(&event, 0, sizeof(event));
+
+	event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
+	event.lock_status.status = (efw->dev_lock_count > 0);
+	efw->dev_lock_changed = false;
+
+	count = min_t(long, count, sizeof(event.lock_status));
+
+	if (copy_to_user(buf, &event, count))
+		return -EFAULT;
+
+	return count;
+}
+
+static long
+hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
 	   loff_t *offset)
 {
 	struct snd_efw *efw = hwdep->private_data;
 	DEFINE_WAIT(wait);
-	union snd_firewire_event event;
 
 	spin_lock_irq(&efw->lock);
 
-	while (!efw->dev_lock_changed) {
+	while ((!efw->dev_lock_changed) && (efw->resp_queues == 0)) {
 		prepare_to_wait(&efw->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
 		spin_unlock_irq(&efw->lock);
 		schedule();
@@ -46,20 +121,47 @@ hwdep_read(struct snd_hwdep *hwdep, char __user *buf,  long count,
 		spin_lock_irq(&efw->lock);
 	}
 
-	memset(&event, 0, sizeof(event));
-	if (efw->dev_lock_changed) {
-		event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
-		event.lock_status.status = (efw->dev_lock_count > 0);
-		efw->dev_lock_changed = false;
-
-		count = min_t(long, count, sizeof(event.lock_status));
-	}
+	if (efw->dev_lock_changed)
+		count = hwdep_read_locked(efw, buf, count, offset);
+	else if (efw->resp_queues > 0)
+		count = hwdep_read_resp_buf(efw, buf, count, offset);
 
 	spin_unlock_irq(&efw->lock);
 
-	if (copy_to_user(buf, &event, count))
+	return count;
+}
+
+static long
+hwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count,
+	    loff_t *offset)
+{
+	struct snd_efw *efw = hwdep->private_data;
+	u32 seqnum;
+	u8 *buf;
+
+	if (count < sizeof(struct snd_efw_transaction))
+		return -EFAULT;
+
+	buf = kmalloc(count, GFP_KERNEL);
+	if (buf == NULL)
 		return -EFAULT;
 
+	if (copy_from_user(buf, data, count)) {
+		count = -EFAULT;
+		goto end;
+	}
+
+	/* check seqnum is not for kernel-land */
+	seqnum = ((struct snd_efw_transaction *)buf)->seqnum;
+	if (seqnum + 2 > SND_EFW_TRANSACTION_SEQNUM_MAX) {
+		count = -EFAULT;
+		goto end;
+	}
+
+	if (snd_efw_transaction_cmd(efw->unit, buf, count) < 0)
+		count = -EFAULT;
+end:
+	kfree(buf);
 	return count;
 }
 
@@ -72,13 +174,13 @@ hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait)
 	poll_wait(file, &efw->hwdep_wait, wait);
 
 	spin_lock_irq(&efw->lock);
-	if (efw->dev_lock_changed)
+	if (efw->dev_lock_changed || (efw->resp_queues > 0))
 		events = POLLIN | POLLRDNORM;
 	else
 		events = 0;
 	spin_unlock_irq(&efw->lock);
 
-	return events;
+	return events | POLLOUT;
 }
 
 static int
@@ -182,6 +284,7 @@ hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
 
 static const struct snd_hwdep_ops hwdep_ops = {
 	.read		= hwdep_read,
+	.write		= hwdep_write,
 	.release	= hwdep_release,
 	.poll		= hwdep_poll,
 	.ioctl		= hwdep_ioctl,
diff --git a/sound/firewire/fireworks/fireworks_proc.c b/sound/firewire/fireworks/fireworks_proc.c
index 2b0f936..d33eb90 100644
--- a/sound/firewire/fireworks/fireworks_proc.c
+++ b/sound/firewire/fireworks/fireworks_proc.c
@@ -122,7 +122,6 @@ proc_read_clock(struct snd_info_entry *entry, struct snd_info_buffer *buffer)
 
 	snd_iprintf(buffer, "Clock Source: %d\n", clock_source);
 	snd_iprintf(buffer, "Sampling Rate: %d\n", sampling_rate);
-
 end:
 	return;
 }
@@ -184,12 +183,31 @@ end:
 	return;
 }
 
+static void
+proc_read_queues_state(struct snd_info_entry *entry,
+		       struct snd_info_buffer *buffer)
+{
+	struct snd_efw *efw = entry->private_data;
+	unsigned int consumed;
+
+	if (efw->pull_ptr > efw->push_ptr)
+		consumed = resp_buf_size -
+			   (unsigned int)(efw->pull_ptr - efw->push_ptr);
+	else
+		consumed = (unsigned int)(efw->push_ptr - efw->pull_ptr);
+
+	snd_iprintf(buffer, "%d %d/%d\n",
+		    efw->resp_queues, consumed, resp_buf_size);
+}
+
 void snd_efw_proc_init(struct snd_efw *efw)
 {
 	struct snd_info_entry *entry;
 
 	if (!snd_card_proc_new(efw->card, "#hardware", &entry))
 		snd_info_set_text_ops(entry, efw, proc_read_hwinfo);
+	if (!snd_card_proc_new(efw->card, "#queues", &entry))
+		snd_info_set_text_ops(entry, efw, proc_read_queues_state);
 	if (!snd_card_proc_new(efw->card, "#clock", &entry))
 		snd_info_set_text_ops(entry, efw, proc_read_clock);
 	if (!snd_card_proc_new(efw->card, "#meters", &entry))
diff --git a/sound/firewire/fireworks/fireworks_transaction.c b/sound/firewire/fireworks/fireworks_transaction.c
index a701e76..f28a673 100644
--- a/sound/firewire/fireworks/fireworks_transaction.c
+++ b/sound/firewire/fireworks/fireworks_transaction.c
@@ -53,6 +53,9 @@
 #define ERROR_DELAY_MS 5
 #define EFC_TIMEOUT_MS 125
 
+static DEFINE_SPINLOCK(instances_lock);
+static struct snd_efw *instances[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
 static DEFINE_SPINLOCK(transaction_queues_lock);
 static LIST_HEAD(transaction_queues);
 
@@ -72,6 +75,14 @@ struct transaction_queue {
 	wait_queue_head_t wait;
 };
 
+int snd_efw_transaction_cmd(struct fw_unit *unit,
+			    const void *cmd, unsigned int size)
+{
+	return snd_fw_transaction(unit, TCODE_WRITE_BLOCK_REQUEST,
+				  MEMORY_SPACE_EFW_COMMAND,
+				  (void *)cmd, size, 0);
+}
+
 int snd_efw_transaction_run(struct fw_unit *unit,
 			    const void *cmd, unsigned int cmd_size,
 			    void *resp, unsigned int resp_size, u32 seqnum)
@@ -93,9 +104,7 @@ int snd_efw_transaction_run(struct fw_unit *unit,
 
 	tries = 0;
 	do {
-		ret = snd_fw_transaction(unit, TCODE_WRITE_BLOCK_REQUEST,
-					 MEMORY_SPACE_EFW_COMMAND,
-					 (void *)cmd, cmd_size, 0);
+		ret = snd_efw_transaction_cmd(t.unit, (void *)cmd, cmd_size);
 		if (ret < 0)
 			break;
 
@@ -122,27 +131,92 @@ int snd_efw_transaction_run(struct fw_unit *unit,
 }
 
 static void
-efw_response(struct fw_card *card, struct fw_request *request,
-	     int tcode, int destination, int source,
-	     int generation, unsigned long long offset,
-	     void *data, size_t length, void *callback_data)
+copy_resp_to_buf(struct snd_efw *efw, void *data, size_t length, int *rcode)
 {
-	struct fw_device *device;
-	struct transaction_queue *t;
-	unsigned long flags;
-	int rcode;
-	u32 seqnum;
+	size_t capacity, till_end;
+	struct snd_efw_transaction *t;
 
-	rcode = RCODE_TYPE_ERROR;
-	if (length < sizeof(struct snd_efw_transaction)) {
-		rcode = RCODE_DATA_ERROR;
-		goto end;
-	} else if (offset != MEMORY_SPACE_EFW_RESPONSE) {
-		rcode = RCODE_ADDRESS_ERROR;
+	spin_lock_irq(&efw->lock);
+
+	t = (struct snd_efw_transaction *)data;
+	length = min_t(size_t, t->length * sizeof(t->length), length);
+
+	if (efw->push_ptr < efw->pull_ptr)
+		capacity = (unsigned int)(efw->pull_ptr - efw->push_ptr);
+	else
+		capacity = resp_buf_size -
+			   (unsigned int)(efw->push_ptr - efw->pull_ptr);
+
+	/* confirm enough space for this response */
+	if (capacity < length) {
+		*rcode = RCODE_CONFLICT_ERROR;
 		goto end;
 	}
 
-	seqnum = be32_to_cpu(((struct snd_efw_transaction *)data)->seqnum);
+	/* copy to ring buffer */
+	while (length > 0) {
+		till_end = resp_buf_size -
+			   (unsigned int)(efw->push_ptr - efw->resp_buf);
+		till_end = min_t(unsigned int, length, till_end);
+
+		memcpy(efw->push_ptr, data, till_end);
+
+		efw->push_ptr += till_end;
+		if (efw->push_ptr >= efw->resp_buf + resp_buf_size)
+			efw->push_ptr = efw->resp_buf;
+
+		length -= till_end;
+		data += till_end;
+	}
+
+	/* for hwdep */
+	efw->resp_queues++;
+	wake_up(&efw->hwdep_wait);
+
+	*rcode = RCODE_COMPLETE;
+end:
+	spin_unlock_irq(&efw->lock);
+}
+
+static void
+handle_resp_for_user(struct fw_card *card, int generation, int source,
+		     void *data, size_t length, int *rcode)
+{
+	struct fw_device *device;
+	struct snd_efw *efw;
+	unsigned int i;
+
+	spin_lock_irq(&instances_lock);
+
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		efw = instances[i];
+		if (efw == NULL)
+			continue;
+		device = fw_parent_device(efw->unit);
+		if ((device->card != card) ||
+		    (device->generation != generation))
+			continue;
+		smp_rmb();	/* node id vs.generation */
+		if (device->node_id != source)
+			continue;
+
+		break;
+	}
+	if (i == SNDRV_CARDS)
+		goto end;
+
+	copy_resp_to_buf(efw, data, length, rcode);
+end:
+	spin_unlock_irq(&instances_lock);
+}
+
+static void
+handle_resp_for_kernel(struct fw_card *card, int generation, int source,
+		       void *data, size_t length, int *rcode, u32 seqnum)
+{
+	struct fw_device *device;
+	struct transaction_queue *t;
+	unsigned long flags;
 
 	spin_lock_irqsave(&transaction_queues_lock, flags);
 	list_for_each_entry(t, &transaction_queues, list) {
@@ -159,14 +233,76 @@ efw_response(struct fw_card *card, struct fw_request *request,
 			t->size = min_t(unsigned int, length, t->size);
 			memcpy(t->buf, data, t->size);
 			wake_up(&t->wait);
-			rcode = RCODE_COMPLETE;
+			*rcode = RCODE_COMPLETE;
 		}
 	}
 	spin_unlock_irqrestore(&transaction_queues_lock, flags);
+}
+
+static void
+efw_response(struct fw_card *card, struct fw_request *request,
+	     int tcode, int destination, int source,
+	     int generation, unsigned long long offset,
+	     void *data, size_t length, void *callback_data)
+{
+	int rcode, dummy;
+	u32 seqnum;
+
+	rcode = RCODE_TYPE_ERROR;
+	if (length < sizeof(struct snd_efw_transaction)) {
+		rcode = RCODE_DATA_ERROR;
+		goto end;
+	} else if (offset != MEMORY_SPACE_EFW_RESPONSE) {
+		rcode = RCODE_ADDRESS_ERROR;
+		goto end;
+	}
+
+	seqnum = be32_to_cpu(((struct snd_efw_transaction *)data)->seqnum);
+	if (seqnum > SND_EFW_TRANSACTION_SEQNUM_MAX) {
+		handle_resp_for_kernel(card, generation, source,
+				       data, length, &rcode, seqnum);
+		if (resp_buf_debug)
+			handle_resp_for_user(card, generation, source,
+					     data, length, &dummy);
+	} else {
+		handle_resp_for_user(card, generation, source,
+				     data, length, &rcode);
+	}
 end:
 	fw_send_response(card, request, rcode);
 }
 
+void snd_efw_transaction_register_instance(struct snd_efw *efw)
+{
+	unsigned int i;
+
+	spin_lock_irq(&instances_lock);
+
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		if (instances[i] != NULL)
+			continue;
+		instances[i] = efw;
+		break;
+	}
+
+	spin_unlock_irq(&instances_lock);
+}
+
+void snd_efw_transaction_unregister_instance(struct snd_efw *efw)
+{
+	unsigned int i;
+
+	spin_lock_irq(&instances_lock);
+
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		if (instances[i] != efw)
+			continue;
+		instances[i] = NULL;
+	}
+
+	spin_unlock_irq(&instances_lock);
+}
+
 void snd_efw_transaction_bus_reset(struct fw_unit *unit)
 {
 	struct transaction_queue *t;
-- 
1.8.3.2

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

end of thread, other threads:[~2013-12-11  9:16 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-12-11  9:15 [PATCH v2 0/8][RFC] a driver for Fireworks based devices Takashi Sakamoto
2013-12-11  9:15 ` [PATCH 1/8] fireworks: Add skelton " Takashi Sakamoto
2013-12-11  9:15 ` [PATCH 2/8] fireworks: Add transaction and some commands Takashi Sakamoto
2013-12-11  9:15 ` [PATCH 3/8] fireworks: Add connection and stream management Takashi Sakamoto
2013-12-11  9:15 ` [PATCH 4/8] fireworks: Add proc interface for debugging purpose Takashi Sakamoto
2013-12-11  9:15 ` [PATCH 5/8] fireworks: Add MIDI interface Takashi Sakamoto
2013-12-11  9:15 ` [PATCH 6/8] fireworks: Add PCM interface Takashi Sakamoto
2013-12-11  9:15 ` [PATCH 7/8] fireworks: Add hwdep interface Takashi Sakamoto
2013-12-11  9:15 ` [PATCH 8/8] fireworks: Add command/response functionality into " 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.