All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC v3][PATCH 00/13] ALSA: fireface: new driver for RME Fireface series
@ 2017-02-11 14:24 Takashi Sakamoto
  2017-02-11 14:24 ` [PATCH 01/13] ALSA: fireface: add skeleton " Takashi Sakamoto
                   ` (12 more replies)
  0 siblings, 13 replies; 14+ messages in thread
From: Takashi Sakamoto @ 2017-02-11 14:24 UTC (permalink / raw)
  To: clemens, tiwai; +Cc: alsa-devel, ffado-devel

Hi,

This patchset is a revised version of my former one:

[alsa-devel] [RFC v2][PATCH 00/11] ALSA: fireface: new driver for RME Fireface series
http://mailman.alsa-project.org/pipermail/alsa-devel/2015-December/102261.html

Since the end of 2015, ALSA firewire stack got many changes. This revision
adapt the former one to current stack. Furthermore, I add an abstraction layer
of Fireface protocol to help developers to add support for the rest of models.
(I'm unwilling to work further for this direction, because of its cost.)

Changes:
 * Rebase to current ALSA firewire stack and add optimization.
 * Rename local structure with shorter words.
 * Delegate a task to configure settings on write-only registers to user space
   applications.
 * Switch mode to fetch PCM frames from data blocks to sampling data processing
   layer voluntarily.
 * Revise comments with technical information from my analysis to Fireface 400
   packets.

The rebasing is actually done on my proposal of ALSA MOTU driver. Please apply
this patchset to it to prevent conflicts.
[alsa-devel] [PATCH 00/19][RFC v2] ALSA: firewire-motu: new driver for MOTU FireWire series
http://mailman.alsa-project.org/pipermail/alsa-devel/2017-January/117211.html

Currently, I have a plan to post this patchset to merge into Linux 4.12.
Corresponding merge window will be estimated to open this April. If you're
willing to test this module, please report the result till then.

Unfortunately, I did this work just with my notes and log dumps, thus without
any test device. I'm happy if you test this patchset with your Fireface 400.
For testers, I prepare for backport codes in my repository in github.com.
Please use 'topic/fireface' branch.
https://github.com/takaswie/snd-firewire-improve/tree/topic/fireface

If you have any question, please reply to me even if it's trivial; e.g. typo
in comments, as long as its' technical issue.


Regards

Takashi Sakamoto (13):
  ALSA: fireface: add skeleton for RME Fireface series
  ALSA: fireface: postpone sound card registration
  ALSA: fireface: add model specific structure
  ALSA: fireface: add an abstraction layer for model-specific protocols
  ALSA: fireface: add transaction support
  ALSA: fireface: add support for MIDI functionality
  ALSA: fireface: add proc node to help debugging
  ALSA: firewire-lib: add no-header packet processing
  ALSA: fireface: add unique data processing layer
  ALSA: fireface: add stream management functionality
  ALSA: fireface: add support for PCM functionality
  ALSA: fireface: add hwdep interface
  ALSA: fireface: add support for Fireface 400

 include/uapi/sound/asound.h                 |   3 +-
 include/uapi/sound/firewire.h               |   2 +-
 sound/firewire/Kconfig                      |   8 +
 sound/firewire/Makefile                     |   1 +
 sound/firewire/amdtp-stream.c               |  84 +++++-
 sound/firewire/amdtp-stream.h               |   2 +
 sound/firewire/fireface/Makefile            |   3 +
 sound/firewire/fireface/amdtp-ff.c          | 155 +++++++++++
 sound/firewire/fireface/ff-hwdep.c          | 191 +++++++++++++
 sound/firewire/fireface/ff-midi.c           | 131 +++++++++
 sound/firewire/fireface/ff-pcm.c            | 404 ++++++++++++++++++++++++++++
 sound/firewire/fireface/ff-proc.c           |  63 +++++
 sound/firewire/fireface/ff-protocol-ff400.c | 371 +++++++++++++++++++++++++
 sound/firewire/fireface/ff-stream.c         | 282 +++++++++++++++++++
 sound/firewire/fireface/ff-transaction.c    | 295 ++++++++++++++++++++
 sound/firewire/fireface/ff.c                | 209 ++++++++++++++
 sound/firewire/fireface/ff.h                | 145 ++++++++++
 17 files changed, 2342 insertions(+), 7 deletions(-)
 create mode 100644 sound/firewire/fireface/Makefile
 create mode 100644 sound/firewire/fireface/amdtp-ff.c
 create mode 100644 sound/firewire/fireface/ff-hwdep.c
 create mode 100644 sound/firewire/fireface/ff-midi.c
 create mode 100644 sound/firewire/fireface/ff-pcm.c
 create mode 100644 sound/firewire/fireface/ff-proc.c
 create mode 100644 sound/firewire/fireface/ff-protocol-ff400.c
 create mode 100644 sound/firewire/fireface/ff-stream.c
 create mode 100644 sound/firewire/fireface/ff-transaction.c
 create mode 100644 sound/firewire/fireface/ff.c
 create mode 100644 sound/firewire/fireface/ff.h

-- 
2.9.3

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

* [PATCH 01/13] ALSA: fireface: add skeleton for RME Fireface series
  2017-02-11 14:24 [RFC v3][PATCH 00/13] ALSA: fireface: new driver for RME Fireface series Takashi Sakamoto
@ 2017-02-11 14:24 ` Takashi Sakamoto
  2017-02-11 14:24 ` [PATCH 02/13] ALSA: fireface: postpone sound card registration Takashi Sakamoto
                   ` (11 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Takashi Sakamoto @ 2017-02-11 14:24 UTC (permalink / raw)
  To: clemens, tiwai; +Cc: alsa-devel, ffado-devel

This commit adds a new driver for RME Fireface series. This commit just
creates/removes card instance according to IEEE 1394 bus event. More
functions will be added in following commits.

Three types of firmware have released by RME GmbH; for Fireface 400, for
Fireface 800 and for UCX/802/UFX. It's reasonable that these models use
different protocol for communication. Currently, I've investigated
Fireface 400 and nothing others.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/Kconfig           |   6 +++
 sound/firewire/Makefile          |   1 +
 sound/firewire/fireface/Makefile |   2 +
 sound/firewire/fireface/ff.c     | 113 +++++++++++++++++++++++++++++++++++++++
 sound/firewire/fireface/ff.h     |  28 ++++++++++
 5 files changed, 150 insertions(+)
 create mode 100644 sound/firewire/fireface/Makefile
 create mode 100644 sound/firewire/fireface/ff.c
 create mode 100644 sound/firewire/fireface/ff.h

diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig
index 2a91fe8..077e03c 100644
--- a/sound/firewire/Kconfig
+++ b/sound/firewire/Kconfig
@@ -153,4 +153,10 @@ config SND_FIREWIRE_MOTU
 	 To compile this driver as a module, choose M here: the module
 	 will be called snd-firewire-motu.
 
+config SND_FIREFACE
+	tristate "RME Fireface series support"
+	select SND_FIREWIRE_LIB
+	help
+	 Say Y here to include support for RME fireface series.
+
 endif # SND_FIREWIRE
diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile
index 9388ded..1b98fa3 100644
--- a/sound/firewire/Makefile
+++ b/sound/firewire/Makefile
@@ -14,3 +14,4 @@ obj-$(CONFIG_SND_BEBOB) += bebob/
 obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += digi00x/
 obj-$(CONFIG_SND_FIREWIRE_TASCAM) += tascam/
 obj-$(CONFIG_SND_FIREWIRE_MOTU) += motu/
+obj-$(CONFIG_SND_FIREFACE) += fireface/
diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile
new file mode 100644
index 0000000..2c64ef6
--- /dev/null
+++ b/sound/firewire/fireface/Makefile
@@ -0,0 +1,2 @@
+snd-fireface-objs := ff.o
+obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o
diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c
new file mode 100644
index 0000000..358bba2
--- /dev/null
+++ b/sound/firewire/fireface/ff.c
@@ -0,0 +1,113 @@
+/*
+ * ff.c - a part of driver for RME Fireface series
+ *
+ * Copyright (c) 2015-2017 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "ff.h"
+
+#define OUI_RME	0x000a35
+
+MODULE_DESCRIPTION("RME Fireface series Driver");
+MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
+MODULE_LICENSE("GPL v2");
+
+static void name_card(struct snd_ff *ff)
+{
+	struct fw_device *fw_dev = fw_parent_device(ff->unit);
+	const char *const model = "Fireface Skeleton";
+
+	strcpy(ff->card->driver, "Fireface");
+	strcpy(ff->card->shortname, model);
+	strcpy(ff->card->mixername, model);
+	snprintf(ff->card->longname, sizeof(ff->card->longname),
+		 "RME %s, GUID %08x%08x at %s, S%d", model,
+		 fw_dev->config_rom[3], fw_dev->config_rom[4],
+		 dev_name(&ff->unit->device), 100 << fw_dev->max_speed);
+}
+
+static void ff_card_free(struct snd_card *card)
+{
+	struct snd_ff *ff = card->private_data;
+
+	fw_unit_put(ff->unit);
+
+	mutex_destroy(&ff->mutex);
+}
+
+static int snd_ff_probe(struct fw_unit *unit,
+			   const struct ieee1394_device_id *entry)
+{
+	struct snd_card *card;
+	struct snd_ff *ff;
+	int err;
+
+	err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE,
+			   sizeof(struct snd_ff), &card);
+	if (err < 0)
+		return err;
+	card->private_free = ff_card_free;
+
+	/* initialize myself */
+	ff = card->private_data;
+	ff->card = card;
+	ff->unit = fw_unit_get(unit);
+	dev_set_drvdata(&unit->device, ff);
+
+	mutex_init(&ff->mutex);
+
+	name_card(ff);
+
+	err = snd_card_register(card);
+	if (err < 0) {
+		snd_card_free(card);
+		return err;
+	}
+
+	return 0;
+}
+
+static void snd_ff_update(struct fw_unit *unit)
+{
+	return;
+}
+
+static void snd_ff_remove(struct fw_unit *unit)
+{
+	struct snd_ff *ff = dev_get_drvdata(&unit->device);
+
+	/* No need to wait for releasing card object in this context. */
+	snd_card_free_when_closed(ff->card);
+}
+
+static const struct ieee1394_device_id snd_ff_id_table[] = {
+	{}
+};
+MODULE_DEVICE_TABLE(ieee1394, snd_ff_id_table);
+
+static struct fw_driver ff_driver = {
+	.driver = {
+		.owner	= THIS_MODULE,
+		.name	= "snd-fireface",
+		.bus	= &fw_bus_type,
+	},
+	.probe    = snd_ff_probe,
+	.update   = snd_ff_update,
+	.remove   = snd_ff_remove,
+	.id_table = snd_ff_id_table,
+};
+
+static int __init snd_ff_init(void)
+{
+	return driver_register(&ff_driver.driver);
+}
+
+static void __exit snd_ff_exit(void)
+{
+	driver_unregister(&ff_driver.driver);
+}
+
+module_init(snd_ff_init);
+module_exit(snd_ff_exit);
diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h
new file mode 100644
index 0000000..64d488e
--- /dev/null
+++ b/sound/firewire/fireface/ff.h
@@ -0,0 +1,28 @@
+/*
+ * ff.h - a part of driver for RME Fireface series
+ *
+ * Copyright (c) 2015-2017 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#ifndef SOUND_FIREFACE_H_INCLUDED
+#define SOUND_FIREFACE_H_INCLUDED
+
+#include <linux/device.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/compat.h>
+
+#include <sound/core.h>
+
+struct snd_ff {
+	struct snd_card *card;
+	struct fw_unit *unit;
+	struct mutex mutex;
+};
+#endif
-- 
2.9.3

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

* [PATCH 02/13] ALSA: fireface: postpone sound card registration
  2017-02-11 14:24 [RFC v3][PATCH 00/13] ALSA: fireface: new driver for RME Fireface series Takashi Sakamoto
  2017-02-11 14:24 ` [PATCH 01/13] ALSA: fireface: add skeleton " Takashi Sakamoto
@ 2017-02-11 14:24 ` Takashi Sakamoto
  2017-02-11 14:24 ` [PATCH 03/13] ALSA: fireface: add model specific structure Takashi Sakamoto
                   ` (10 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Takashi Sakamoto @ 2017-02-11 14:24 UTC (permalink / raw)
  To: clemens, tiwai; +Cc: alsa-devel, ffado-devel

Just after appearing on IEEE 1394 bus, this unit generates several bus
resets. This is due to loading firmware from on-board flash memory and
initialize hardware. It's better to postpone sound card registration.

This commit schedules workqueue to process actual probe processing
2 seconds after the last bus-reset. The card instance is kept at unit
probe callback and released at card free callback. Therefore, when the
actual probe processing fails, the memory block is wasted. This is due to
simplify driver implementation.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/fireface/ff.c | 84 ++++++++++++++++++++++++++++++++------------
 sound/firewire/fireface/ff.h |  5 +++
 2 files changed, 67 insertions(+), 22 deletions(-)

diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c
index 358bba2..7c02639 100644
--- a/sound/firewire/fireface/ff.c
+++ b/sound/firewire/fireface/ff.c
@@ -28,58 +28,98 @@ static void name_card(struct snd_ff *ff)
 		 dev_name(&ff->unit->device), 100 << fw_dev->max_speed);
 }
 
-static void ff_card_free(struct snd_card *card)
+static void ff_free(struct snd_ff *ff)
 {
-	struct snd_ff *ff = card->private_data;
-
 	fw_unit_put(ff->unit);
 
 	mutex_destroy(&ff->mutex);
+	kfree(ff);
+}
+
+static void ff_card_free(struct snd_card *card)
+{
+	ff_free(card->private_data);
+}
+
+static void do_registration(struct work_struct *work)
+{
+	struct snd_ff *ff = container_of(work, struct snd_ff, dwork.work);
+	int err;
+
+	if (ff->registered)
+		return;
+
+	err = snd_card_new(&ff->unit->device, -1, NULL, THIS_MODULE, 0,
+			   &ff->card);
+	if (err < 0)
+		return;
+
+	name_card(ff);
+
+	err = snd_card_register(ff->card);
+	if (err < 0)
+		goto error;
+
+	ff->card->private_free = ff_card_free;
+	ff->card->private_data = ff;
+	ff->registered = true;
+
+	return;
+error:
+	snd_card_free(ff->card);
+	dev_info(&ff->unit->device,
+		 "Sound card registration failed: %d\n", err);
 }
 
 static int snd_ff_probe(struct fw_unit *unit,
 			   const struct ieee1394_device_id *entry)
 {
-	struct snd_card *card;
 	struct snd_ff *ff;
-	int err;
 
-	err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE,
-			   sizeof(struct snd_ff), &card);
-	if (err < 0)
-		return err;
-	card->private_free = ff_card_free;
+	ff = kzalloc(sizeof(struct snd_ff), GFP_KERNEL);
+	if (ff == NULL)
+		return -ENOMEM;
 
 	/* initialize myself */
-	ff = card->private_data;
-	ff->card = card;
 	ff->unit = fw_unit_get(unit);
 	dev_set_drvdata(&unit->device, ff);
 
 	mutex_init(&ff->mutex);
 
-	name_card(ff);
-
-	err = snd_card_register(card);
-	if (err < 0) {
-		snd_card_free(card);
-		return err;
-	}
+	/* Register this sound card later. */
+	INIT_DEFERRABLE_WORK(&ff->dwork, do_registration);
+	snd_fw_schedule_registration(unit, &ff->dwork);
 
 	return 0;
 }
 
 static void snd_ff_update(struct fw_unit *unit)
 {
-	return;
+	struct snd_ff *ff = dev_get_drvdata(&unit->device);
+
+	/* Postpone a workqueue for deferred registration. */
+	if (!ff->registered)
+		snd_fw_schedule_registration(unit, &ff->dwork);
 }
 
 static void snd_ff_remove(struct fw_unit *unit)
 {
 	struct snd_ff *ff = dev_get_drvdata(&unit->device);
 
-	/* No need to wait for releasing card object in this context. */
-	snd_card_free_when_closed(ff->card);
+	/*
+	 * Confirm to stop the work for registration before the sound card is
+	 * going to be released. The work is not scheduled again because bus
+	 * reset handler is not called anymore.
+	 */
+	cancel_work_sync(&ff->dwork.work);
+
+	if (ff->registered) {
+		/* No need to wait for releasing card object in this context. */
+		snd_card_free_when_closed(ff->card);
+	} else {
+		/* Don't forget this case. */
+		ff_free(ff);
+	}
 }
 
 static const struct ieee1394_device_id snd_ff_id_table[] = {
diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h
index 64d488e..a0faae1 100644
--- a/sound/firewire/fireface/ff.h
+++ b/sound/firewire/fireface/ff.h
@@ -20,9 +20,14 @@
 
 #include <sound/core.h>
 
+#include "../lib.h"
+
 struct snd_ff {
 	struct snd_card *card;
 	struct fw_unit *unit;
 	struct mutex mutex;
+
+	bool registered;
+	struct delayed_work dwork;
 };
 #endif
-- 
2.9.3

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

* [PATCH 03/13] ALSA: fireface: add model specific structure
  2017-02-11 14:24 [RFC v3][PATCH 00/13] ALSA: fireface: new driver for RME Fireface series Takashi Sakamoto
  2017-02-11 14:24 ` [PATCH 01/13] ALSA: fireface: add skeleton " Takashi Sakamoto
  2017-02-11 14:24 ` [PATCH 02/13] ALSA: fireface: postpone sound card registration Takashi Sakamoto
@ 2017-02-11 14:24 ` Takashi Sakamoto
  2017-02-11 14:25 ` [PATCH 04/13] ALSA: fireface: add an abstraction layer for model-specific protocols Takashi Sakamoto
                   ` (9 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Takashi Sakamoto @ 2017-02-11 14:24 UTC (permalink / raw)
  To: clemens, tiwai; +Cc: alsa-devel, ffado-devel

RME Fireface series has several models and their specifications are
different. Currently, we find no way to retrieve the specifications
from actual devices and need to implement them in this driver.

This commit adds a structure to describe model specific data. This
structure has an identical name for each unit, and maximum number of
data channels in each mode. I'll descibe about the mode in following
commits.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/fireface/ff.c |  9 +++++----
 sound/firewire/fireface/ff.h | 14 ++++++++++++++
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c
index 7c02639..5c2bd92 100644
--- a/sound/firewire/fireface/ff.c
+++ b/sound/firewire/fireface/ff.c
@@ -17,13 +17,12 @@ MODULE_LICENSE("GPL v2");
 static void name_card(struct snd_ff *ff)
 {
 	struct fw_device *fw_dev = fw_parent_device(ff->unit);
-	const char *const model = "Fireface Skeleton";
 
 	strcpy(ff->card->driver, "Fireface");
-	strcpy(ff->card->shortname, model);
-	strcpy(ff->card->mixername, model);
+	strcpy(ff->card->shortname, ff->spec->name);
+	strcpy(ff->card->mixername, ff->spec->name);
 	snprintf(ff->card->longname, sizeof(ff->card->longname),
-		 "RME %s, GUID %08x%08x at %s, S%d", model,
+		 "RME %s, GUID %08x%08x at %s, S%d", ff->spec->name,
 		 fw_dev->config_rom[3], fw_dev->config_rom[4],
 		 dev_name(&ff->unit->device), 100 << fw_dev->max_speed);
 }
@@ -86,6 +85,8 @@ static int snd_ff_probe(struct fw_unit *unit,
 
 	mutex_init(&ff->mutex);
 
+	ff->spec = (const struct snd_ff_spec *)entry->driver_data;
+
 	/* Register this sound card later. */
 	INIT_DEFERRABLE_WORK(&ff->dwork, do_registration);
 	snd_fw_schedule_registration(unit, &ff->dwork);
diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h
index a0faae1..269fa25 100644
--- a/sound/firewire/fireface/ff.h
+++ b/sound/firewire/fireface/ff.h
@@ -22,6 +22,18 @@
 
 #include "../lib.h"
 
+#define SND_FF_STREAM_MODES		3
+
+struct snd_ff_spec {
+	const char *const name;
+
+	const unsigned int pcm_capture_channels[SND_FF_STREAM_MODES];
+	const unsigned int pcm_playback_channels[SND_FF_STREAM_MODES];
+
+	unsigned int midi_in_ports;
+	unsigned int midi_out_ports;
+};
+
 struct snd_ff {
 	struct snd_card *card;
 	struct fw_unit *unit;
@@ -29,5 +41,7 @@ struct snd_ff {
 
 	bool registered;
 	struct delayed_work dwork;
+
+	const struct snd_ff_spec *spec;
 };
 #endif
-- 
2.9.3

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

* [PATCH 04/13] ALSA: fireface: add an abstraction layer for model-specific protocols
  2017-02-11 14:24 [RFC v3][PATCH 00/13] ALSA: fireface: new driver for RME Fireface series Takashi Sakamoto
                   ` (2 preceding siblings ...)
  2017-02-11 14:24 ` [PATCH 03/13] ALSA: fireface: add model specific structure Takashi Sakamoto
@ 2017-02-11 14:25 ` Takashi Sakamoto
  2017-02-11 14:25 ` [PATCH 05/13] ALSA: fireface: add transaction support Takashi Sakamoto
                   ` (8 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Takashi Sakamoto @ 2017-02-11 14:25 UTC (permalink / raw)
  To: clemens, tiwai; +Cc: alsa-devel, ffado-devel

As of 2016, RME discontinued its Fireface series, thus it's OK for us
to drive known units with three types of firmware.

As long as investigating Fireface 400 with Windows driver and comparing
the result to FFADO implementation, these firmwares have different register
assignments. On the other hand, according to manuals of each models,
features relevant to packet streaming seem to be common. It's reasonable
to assume an abstraction layer of protocols to communicate to each models.

This commit adds an abstraction layer for the protocols. This layer
includes some functions to operate common features of models in this
series.

In IEC 61883-1/6, the sequence of packet can transfer timing information
to synchronize transmitters/receivers. Units of each node on IEEE 1394
bus can generate transmitter's timing clock by handling value of SYT field
in CIP header with high-precision clock. For audio and music units on
IEEE 1394 bus, this recovered clock is designed to used for sampling clock
to capture/generate PCM frames on DSP/ADC/DAC. (Actually, there's no units
to implement this specification correctly in this world, as long as I
know).

Fireface series doesn't use this mechanism and isochronous packet with
CIP header. It uses internal crystal unit as its initial sampling clock.
When detecting input signals which can be available for sampling clock
(e.g. ADAT input), drivers can configure units to use the signals as
source of sampling clock. When something goes wrong, e.g. frequency
mismatching between the signal and configured value, units switch to
the other detected signals alternatively. When detecting no alternatives,
internal crystal unit is used as source of sampling clock. On manual of
Fireface 400, this mechanism is described as 'Autosync'.

For packet streaming layer, it's enough to get current selection of
source signals for the sampling clock and its frequency. When internal
crystal is selected, drivers can sets arbitrary sampling frequency,
else they should follow configured frequency. For this purpose,
.get_clock is added.

On the units, packet streaming is controlled by write transactions to
certain registers. Format of the packet, e.g. the number of data channels
in a data block, is also configured by the same manner. For this purpose,
.begin_session and .finish_session is added.

The remarkable point of this protocol is to allow drivers to configure
arbitrary sampling transmission frequency; e.g. 123.456 Hz. As long as I
know, there's no DAC/ADC chips which support this kind of capability.
I think a pair of packet streaming/data block processing layer is isolated
from sampling data processing layer in a point of governed clock. In
short, between these parts, resampling layer exists between these parts.
Actually, for Fireface 400, write transactions to 0x'0000'8010'051c has
an effect to change sampling clock frequency with base frequency of
32.0/44.1/48.0 kHz and its multipliers (x2/x4).

For this reason, the abstraction layer has no function to set sampling
clock. Instead, each implementation of .begin_session is expected to
configure sampling transmission frequency.

Drivers are allows to bank up data fetching from a pair of packet
streaming/data block processing layer and sampling data processing layer.
This feature seems to suppress noises at starting/stopping packet
streaming. For this purpose, .switch_fetching_mode is added.

As I described in the above, units have remarkable mechanism to manage
sampling clock and process sampling data. For debugging purpose,
.dump_sync_status and .dump_clock_config are added. I don't have a need
to common interface to represent the status and configuration,
developers can add actual implementation of the abstraction layer as he
or she likes.

Unlike PCM frames, MIDI messages are transferred by asynchronous
communication over IEEE 1394 bus, thus target addresses are important for
this feature. The .midi_high_addr_reg, .midi_rx_port_0_reg and
.midi_rx_port_1_reg are for this purpose. I'll describe them in following
commit.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/fireface/ff.h | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h
index 269fa25..7be0ea4 100644
--- a/sound/firewire/fireface/ff.h
+++ b/sound/firewire/fireface/ff.h
@@ -19,11 +19,13 @@
 #include <linux/compat.h>
 
 #include <sound/core.h>
+#include <sound/info.h>
 
 #include "../lib.h"
 
 #define SND_FF_STREAM_MODES		3
 
+struct snd_ff_protocol;
 struct snd_ff_spec {
 	const char *const name;
 
@@ -32,6 +34,8 @@ struct snd_ff_spec {
 
 	unsigned int midi_in_ports;
 	unsigned int midi_out_ports;
+
+	struct snd_ff_protocol *protocol;
 };
 
 struct snd_ff {
@@ -44,4 +48,31 @@ struct snd_ff {
 
 	const struct snd_ff_spec *spec;
 };
+
+enum snd_ff_clock_src {
+	SND_FF_CLOCK_SRC_INTERNAL,
+	SND_FF_CLOCK_SRC_SPDIF,
+	SND_FF_CLOCK_SRC_ADAT,
+	SND_FF_CLOCK_SRC_WORD,
+	SND_FF_CLOCK_SRC_LTC,
+	/* TODO: perhaps ADAT2 and TCO exists. */
+};
+
+struct snd_ff_protocol {
+	int (*get_clock)(struct snd_ff *ff, unsigned int *rate,
+			 enum snd_ff_clock_src *src);
+	int (*begin_session)(struct snd_ff *ff, unsigned int rate);
+	void (*finish_session)(struct snd_ff *ff);
+	int (*switch_fetching_mode)(struct snd_ff *ff, bool enable);
+
+	void (*dump_sync_status)(struct snd_ff *ff,
+				 struct snd_info_buffer *buffer);
+	void (*dump_clock_config)(struct snd_ff *ff,
+				  struct snd_info_buffer *buffer);
+
+	u64 midi_high_addr_reg;
+	u64 midi_rx_port_0_reg;
+	u64 midi_rx_port_1_reg;
+};
+
 #endif
-- 
2.9.3

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

* [PATCH 05/13] ALSA: fireface: add transaction support
  2017-02-11 14:24 [RFC v3][PATCH 00/13] ALSA: fireface: new driver for RME Fireface series Takashi Sakamoto
                   ` (3 preceding siblings ...)
  2017-02-11 14:25 ` [PATCH 04/13] ALSA: fireface: add an abstraction layer for model-specific protocols Takashi Sakamoto
@ 2017-02-11 14:25 ` Takashi Sakamoto
  2017-02-11 14:25 ` [PATCH 06/13] ALSA: fireface: add support for MIDI functionality Takashi Sakamoto
                   ` (7 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Takashi Sakamoto @ 2017-02-11 14:25 UTC (permalink / raw)
  To: clemens, tiwai; +Cc: alsa-devel, ffado-devel

As long as investigating Fireface 400, MIDI messages are transferred by
asynchronous communication over IEEE 1394 bus.

Fireface 400 receives MIDI messages by write transactions to two addresses;
0x'0000'0801'8000 and 0x'0000'0801'9000. Each of two seems to correspond to
MIDI port 1 and 2.

Fireface 400 transfers MIDI messages by write transactions to certain
addresses which configured by drivers. The drivers can decide upper 4 byte
of the addresses by write transactions to 0x'0000'0801'03f4. For the rest
part of the address, drivers can select from below options:
 * 0x'0000'0000
 * 0x'0000'0080
 * 0x'0000'0100
 * 0x'0000'0180

Selected options are represented in register 0x'0000'0801'051c as bit
flags. Due to this mechanism, drivers are restricted to use addresses on
'Memory space' of IEEE 1222, even if transactions to the address have
some side effects.

This commit adds transaction support for MIDI messaging, based on my
assumption that the similar mechanism is used on the other protocols. To
receive asynchronous transactions, the driver allocates a range of address
in 'Memory space'. I apply a strategy to use 0x'0000'0000 as lower 4 byte
of the address. This driver retries to allocate when getting failure from
Linux FireWire subsystem.

Unfortunately, read transaction to address 0x'0000'0801'051c returns zero
always, however write transactions have effects to the other features such
as status of sampling clock. For this reason, this commit delegates the
task to configure this register to user space applications. The
applications should set 3rd bit in LSB in little endian order.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/fireface/Makefile         |   2 +-
 sound/firewire/fireface/ff-transaction.c | 295 +++++++++++++++++++++++++++++++
 sound/firewire/fireface/ff.c             |   9 +
 sound/firewire/fireface/ff.h             |  23 +++
 4 files changed, 328 insertions(+), 1 deletion(-)
 create mode 100644 sound/firewire/fireface/ff-transaction.c

diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile
index 2c64ef6..864aacc 100644
--- a/sound/firewire/fireface/Makefile
+++ b/sound/firewire/fireface/Makefile
@@ -1,2 +1,2 @@
-snd-fireface-objs := ff.o
+snd-fireface-objs := ff.o ff-transaction.o
 obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o
diff --git a/sound/firewire/fireface/ff-transaction.c b/sound/firewire/fireface/ff-transaction.c
new file mode 100644
index 0000000..dd6c8e8
--- /dev/null
+++ b/sound/firewire/fireface/ff-transaction.c
@@ -0,0 +1,295 @@
+/*
+ * ff-transaction.c - a part of driver for RME Fireface series
+ *
+ * Copyright (c) 2015-2017 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "ff.h"
+
+static void finish_transmit_midi_msg(struct snd_ff *ff, unsigned int port,
+				     int rcode)
+{
+	struct snd_rawmidi_substream *substream =
+				ACCESS_ONCE(ff->rx_midi_substreams[port]);
+
+	if (rcode_is_permanent_error(rcode)) {
+		ff->rx_midi_error[port] = true;
+		return;
+	}
+
+	if (rcode != RCODE_COMPLETE) {
+		/* Transfer the message again, immediately. */
+		ff->next_ktime[port] = 0;
+		schedule_work(&ff->rx_midi_work[port]);
+		return;
+	}
+
+	snd_rawmidi_transmit_ack(substream, ff->rx_bytes[port]);
+	ff->rx_bytes[port] = 0;
+
+	if (!snd_rawmidi_transmit_empty(substream))
+		schedule_work(&ff->rx_midi_work[port]);
+}
+
+static void finish_transmit_midi0_msg(struct fw_card *card, int rcode,
+				      void *data, size_t length,
+				      void *callback_data)
+{
+	struct snd_ff *ff =
+		container_of(callback_data, struct snd_ff, transactions[0]);
+	finish_transmit_midi_msg(ff, 0, rcode);
+}
+
+static void finish_transmit_midi1_msg(struct fw_card *card, int rcode,
+				      void *data, size_t length,
+				      void *callback_data)
+{
+	struct snd_ff *ff =
+		container_of(callback_data, struct snd_ff, transactions[1]);
+	finish_transmit_midi_msg(ff, 1, rcode);
+}
+
+static inline void fill_midi_buf(struct snd_ff *ff, unsigned int port,
+				 unsigned int index, u8 byte)
+{
+	ff->msg_buf[port][index] = cpu_to_le32(byte);
+}
+
+static void transmit_midi_msg(struct snd_ff *ff, unsigned int port)
+{
+	struct snd_rawmidi_substream *substream =
+			ACCESS_ONCE(ff->rx_midi_substreams[port]);
+	u8 *buf = (u8 *)ff->msg_buf[port];
+	int i, len;
+
+	struct fw_device *fw_dev = fw_parent_device(ff->unit);
+	unsigned long long addr;
+	int generation;
+	fw_transaction_callback_t callback;
+
+	if (substream == NULL || snd_rawmidi_transmit_empty(substream))
+		return;
+
+	if (ff->rx_bytes[port] > 0 || ff->rx_midi_error[port])
+		return;
+
+	/* Do it in next chance. */
+	if (ktime_after(ff->next_ktime[port], ktime_get())) {
+		schedule_work(&ff->rx_midi_work[port]);
+		return;
+	}
+
+	len = snd_rawmidi_transmit_peek(substream, buf,
+					SND_FF_MAXIMIM_MIDI_QUADS);
+	if (len <= 0)
+		return;
+
+	for (i = len - 1; i >= 0; i--)
+		fill_midi_buf(ff, port, i, buf[i]);
+
+	if (port == 0) {
+		addr = ff->spec->protocol->midi_rx_port_0_reg;
+		callback = finish_transmit_midi0_msg;
+	} else {
+		addr = ff->spec->protocol->midi_rx_port_1_reg;
+		callback = finish_transmit_midi1_msg;
+	}
+
+	/* Set interval to next transaction. */
+	ff->next_ktime[port] = ktime_add_ns(ktime_get(),
+					    len * 8 * NSEC_PER_SEC / 31250);
+	ff->rx_bytes[port] = len;
+
+	/*
+	 * In Linux FireWire core, when generation is updated with memory
+	 * barrier, node id has already been updated. In this module, After
+	 * this smp_rmb(), load/store instructions to memory are completed.
+	 * Thus, both of generation and node id are available with recent
+	 * values. This is a light-serialization solution to handle bus reset
+	 * events on IEEE 1394 bus.
+	 */
+	generation = fw_dev->generation;
+	smp_rmb();
+	fw_send_request(fw_dev->card, &ff->transactions[port],
+			TCODE_WRITE_BLOCK_REQUEST,
+			fw_dev->node_id, generation, fw_dev->max_speed,
+			addr, &ff->msg_buf[port], len * 4,
+			callback, &ff->transactions[port]);
+}
+
+static void transmit_midi0_msg(struct work_struct *work)
+{
+	struct snd_ff *ff = container_of(work, struct snd_ff, rx_midi_work[0]);
+
+	transmit_midi_msg(ff, 0);
+}
+
+static void transmit_midi1_msg(struct work_struct *work)
+{
+	struct snd_ff *ff = container_of(work, struct snd_ff, rx_midi_work[1]);
+
+	transmit_midi_msg(ff, 1);
+}
+
+static void handle_midi_msg(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 snd_ff *ff = callback_data;
+	__le32 *buf = data;
+	u32 quad;
+	u8 byte;
+	unsigned int index;
+	struct snd_rawmidi_substream *substream;
+	int i;
+
+	fw_send_response(card, request, RCODE_COMPLETE);
+
+	for (i = 0; i < length / 4; i++) {
+		quad = le32_to_cpu(buf[i]);
+
+		/* Message in first port. */
+		/*
+		 * This value may represent the index of this unit when the same
+		 * units are on the same IEEE 1394 bus. This driver doesn't use
+		 * it.
+		 */
+		index = (quad >> 8) & 0xff;
+		if (index > 0) {
+			substream = ACCESS_ONCE(ff->tx_midi_substreams[0]);
+			if (substream != NULL) {
+				byte = quad & 0xff;
+				snd_rawmidi_receive(substream, &byte, 1);
+			}
+		}
+
+		/* Message in second port. */
+		index = (quad >> 24) & 0xff;
+		if (index > 0) {
+			substream = ACCESS_ONCE(ff->tx_midi_substreams[1]);
+			if (substream != NULL) {
+				byte = (quad >> 16) & 0xff;
+				snd_rawmidi_receive(substream, &byte, 1);
+			}
+		}
+	}
+}
+
+static int allocate_own_address(struct snd_ff *ff, int i)
+{
+	struct fw_address_region midi_msg_region;
+	int err;
+
+	ff->async_handler.length = SND_FF_MAXIMIM_MIDI_QUADS * 4;
+	ff->async_handler.address_callback = handle_midi_msg;
+	ff->async_handler.callback_data = ff;
+
+	midi_msg_region.start = 0x000100000000ull * i;
+	midi_msg_region.end = midi_msg_region.start + ff->async_handler.length;
+
+	err = fw_core_add_address_handler(&ff->async_handler, &midi_msg_region);
+	if (err >= 0) {
+		/* Controllers are allowed to register this region. */
+		if (ff->async_handler.offset & 0x0000ffffffff) {
+			fw_core_remove_address_handler(&ff->async_handler);
+			err = -EAGAIN;
+		}
+	}
+
+	return err;
+}
+
+/*
+ * The configuration to start asynchronous transactions for MIDI messages is in
+ * 0x'0000'8010'051c. This register includes the other options, thus this driver
+ * doesn't touch it and leaves the decision to userspace. The userspace MUST add
+ * 0x04000000 to write transactions to the register to receive any MIDI
+ * messages.
+ *
+ * Here, I just describe MIDI-related offsets of the register, in little-endian
+ * order.
+ *
+ * Controllers are allowed to register higher 4 bytes of address to receive
+ * the transactions. The register is 0x'0000'8010'03f4. On the other hand, the
+ * controllers are not allowed to register lower 4 bytes of the address. They
+ * are forced to select from 4 options by writing corresponding bits to
+ * 0x'0000'8010'051c.
+ *
+ * The 3rd-6th bits in MSB of this register are used to indicate lower 4 bytes
+ * of address to which the device transferrs the transactions.
+ *  - 6th: 0x'....'....'0000'0180
+ *  - 5th: 0x'....'....'0000'0100
+ *  - 4th: 0x'....'....'0000'0080
+ *  - 3rd: 0x'....'....'0000'0000
+ *
+ * This driver configure 0x'....'....'0000'0000 for units to receive MIDI
+ * messages. 3rd bit of the register should be configured, however this driver
+ * deligates this task to user space applications due to a restriction that
+ * this register is write-only and the other bits have own effects.
+ *
+ * The 1st and 2nd bits in LSB of this register are used to cancel transferring
+ * asynchronous transactions. These two bits have the same effect.
+ *  - 1st/2nd: cancel transferring
+ */
+int snd_ff_transaction_reregister(struct snd_ff *ff)
+{
+	struct fw_card *fw_card = fw_parent_device(ff->unit)->card;
+	u32 addr;
+	__le32 reg;
+
+	/*
+	 * Controllers are allowed to register its node ID and upper 2 byte of
+	 * local address to listen asynchronous transactions.
+	 */
+	addr = (fw_card->node_id << 16) | (ff->async_handler.offset >> 32);
+	reg = cpu_to_le32(addr);
+	return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
+				  ff->spec->protocol->midi_high_addr_reg,
+				  &reg, sizeof(reg), 0);
+}
+
+int snd_ff_transaction_register(struct snd_ff *ff)
+{
+	int i, err;
+
+	/*
+	 * Allocate in Memory Space of IEC 13213, but lower 4 byte in LSB should
+	 * be zero due to device specification.
+	 */
+	for (i = 0; i < 0xffff; i++) {
+		err = allocate_own_address(ff, i);
+		if (err != -EBUSY && err != -EAGAIN)
+			break;
+	}
+	if (err < 0)
+		return err;
+
+	err = snd_ff_transaction_reregister(ff);
+	if (err < 0)
+		return err;
+
+	INIT_WORK(&ff->rx_midi_work[0], transmit_midi0_msg);
+	INIT_WORK(&ff->rx_midi_work[1], transmit_midi1_msg);
+
+	return 0;
+}
+
+void snd_ff_transaction_unregister(struct snd_ff *ff)
+{
+	__le32 reg;
+
+	if (ff->async_handler.callback_data == NULL)
+		return;
+	ff->async_handler.callback_data = NULL;
+
+	/* Release higher 4 bytes of address. */
+	reg = cpu_to_le32(0x00000000);
+	snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
+			   ff->spec->protocol->midi_high_addr_reg,
+			   &reg, sizeof(reg), 0);
+
+	fw_core_remove_address_handler(&ff->async_handler);
+}
diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c
index 5c2bd92..4db630f 100644
--- a/sound/firewire/fireface/ff.c
+++ b/sound/firewire/fireface/ff.c
@@ -29,6 +29,8 @@ static void name_card(struct snd_ff *ff)
 
 static void ff_free(struct snd_ff *ff)
 {
+	snd_ff_transaction_unregister(ff);
+
 	fw_unit_put(ff->unit);
 
 	mutex_destroy(&ff->mutex);
@@ -53,6 +55,10 @@ static void do_registration(struct work_struct *work)
 	if (err < 0)
 		return;
 
+	err = snd_ff_transaction_register(ff);
+	if (err < 0)
+		goto error;
+
 	name_card(ff);
 
 	err = snd_card_register(ff->card);
@@ -65,6 +71,7 @@ static void do_registration(struct work_struct *work)
 
 	return;
 error:
+	snd_ff_transaction_unregister(ff);
 	snd_card_free(ff->card);
 	dev_info(&ff->unit->device,
 		 "Sound card registration failed: %d\n", err);
@@ -101,6 +108,8 @@ static void snd_ff_update(struct fw_unit *unit)
 	/* Postpone a workqueue for deferred registration. */
 	if (!ff->registered)
 		snd_fw_schedule_registration(unit, &ff->dwork);
+
+	snd_ff_transaction_reregister(ff);
 }
 
 static void snd_ff_remove(struct fw_unit *unit)
diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h
index 7be0ea4..bac2e58 100644
--- a/sound/firewire/fireface/ff.h
+++ b/sound/firewire/fireface/ff.h
@@ -20,11 +20,16 @@
 
 #include <sound/core.h>
 #include <sound/info.h>
+#include <sound/rawmidi.h>
 
 #include "../lib.h"
 
 #define SND_FF_STREAM_MODES		3
 
+#define SND_FF_MAXIMIM_MIDI_QUADS	9
+#define SND_FF_IN_MIDI_PORTS		2
+#define SND_FF_OUT_MIDI_PORTS		2
+
 struct snd_ff_protocol;
 struct snd_ff_spec {
 	const char *const name;
@@ -47,6 +52,20 @@ struct snd_ff {
 	struct delayed_work dwork;
 
 	const struct snd_ff_spec *spec;
+
+	/* To handle MIDI tx. */
+	struct snd_rawmidi_substream *tx_midi_substreams[SND_FF_IN_MIDI_PORTS];
+	struct fw_address_handler async_handler;
+
+	/* TO handle MIDI rx. */
+	struct snd_rawmidi_substream *rx_midi_substreams[SND_FF_OUT_MIDI_PORTS];
+	u8 running_status[SND_FF_OUT_MIDI_PORTS];
+	__le32 msg_buf[SND_FF_OUT_MIDI_PORTS][SND_FF_MAXIMIM_MIDI_QUADS];
+	struct work_struct rx_midi_work[SND_FF_OUT_MIDI_PORTS];
+	struct fw_transaction transactions[SND_FF_OUT_MIDI_PORTS];
+	ktime_t next_ktime[SND_FF_OUT_MIDI_PORTS];
+	bool rx_midi_error[SND_FF_OUT_MIDI_PORTS];
+	unsigned int rx_bytes[SND_FF_OUT_MIDI_PORTS];
 };
 
 enum snd_ff_clock_src {
@@ -75,4 +94,8 @@ struct snd_ff_protocol {
 	u64 midi_rx_port_1_reg;
 };
 
+int snd_ff_transaction_register(struct snd_ff *ff);
+int snd_ff_transaction_reregister(struct snd_ff *ff);
+void snd_ff_transaction_unregister(struct snd_ff *ff);
+
 #endif
-- 
2.9.3

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

* [PATCH 06/13] ALSA: fireface: add support for MIDI functionality
  2017-02-11 14:24 [RFC v3][PATCH 00/13] ALSA: fireface: new driver for RME Fireface series Takashi Sakamoto
                   ` (4 preceding siblings ...)
  2017-02-11 14:25 ` [PATCH 05/13] ALSA: fireface: add transaction support Takashi Sakamoto
@ 2017-02-11 14:25 ` Takashi Sakamoto
  2017-02-11 14:25 ` [PATCH 07/13] ALSA: fireface: add proc node to help debugging Takashi Sakamoto
                   ` (6 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Takashi Sakamoto @ 2017-02-11 14:25 UTC (permalink / raw)
  To: clemens, tiwai; +Cc: alsa-devel, ffado-devel

In previous commit, fireface driver supports unique transaction for MIDI
feature. This commit adds MIDI functionality for userspace.

As I wrote in a followed commit, user space applications have some request
from this driver. It should not touch a register to which units transmit
MIDI messages. It should configure a register in which MIDI transmission
is controlled.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/fireface/Makefile  |   2 +-
 sound/firewire/fireface/ff-midi.c | 131 ++++++++++++++++++++++++++++++++++++++
 sound/firewire/fireface/ff.c      |   5 ++
 sound/firewire/fireface/ff.h      |   3 +
 4 files changed, 140 insertions(+), 1 deletion(-)
 create mode 100644 sound/firewire/fireface/ff-midi.c

diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile
index 864aacc..8e465e4 100644
--- a/sound/firewire/fireface/Makefile
+++ b/sound/firewire/fireface/Makefile
@@ -1,2 +1,2 @@
-snd-fireface-objs := ff.o ff-transaction.o
+snd-fireface-objs := ff.o ff-transaction.o ff-midi.o
 obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o
diff --git a/sound/firewire/fireface/ff-midi.c b/sound/firewire/fireface/ff-midi.c
new file mode 100644
index 0000000..29ee0a7
--- /dev/null
+++ b/sound/firewire/fireface/ff-midi.c
@@ -0,0 +1,131 @@
+/*
+ * ff-midi.c - a part of driver for RME Fireface series
+ *
+ * Copyright (c) 2015-2017 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "ff.h"
+
+static int midi_capture_open(struct snd_rawmidi_substream *substream)
+{
+	/* Do nothing. */
+	return 0;
+}
+
+static int midi_playback_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_ff *ff = substream->rmidi->private_data;
+
+	/* Initialize internal status. */
+	ff->running_status[substream->number] = 0;
+	ff->rx_midi_error[substream->number] = false;
+
+	ACCESS_ONCE(ff->rx_midi_substreams[substream->number]) = substream;
+
+	return 0;
+}
+
+static int midi_capture_close(struct snd_rawmidi_substream *substream)
+{
+	/* Do nothing. */
+	return 0;
+}
+
+static int midi_playback_close(struct snd_rawmidi_substream *substream)
+{
+	struct snd_ff *ff = substream->rmidi->private_data;
+
+	cancel_work_sync(&ff->rx_midi_work[substream->number]);
+	ACCESS_ONCE(ff->rx_midi_substreams[substream->number]) = NULL;
+
+	return 0;
+}
+
+static void midi_capture_trigger(struct snd_rawmidi_substream *substream,
+				 int up)
+{
+	struct snd_ff *ff = substream->rmidi->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ff->lock, flags);
+
+	if (up)
+		ACCESS_ONCE(ff->tx_midi_substreams[substream->number]) =
+								substream;
+	else
+		ACCESS_ONCE(ff->tx_midi_substreams[substream->number]) = NULL;
+
+	spin_unlock_irqrestore(&ff->lock, flags);
+}
+
+static void midi_playback_trigger(struct snd_rawmidi_substream *substream,
+				  int up)
+{
+	struct snd_ff *ff = substream->rmidi->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ff->lock, flags);
+
+	if (up || !ff->rx_midi_error[substream->number])
+		schedule_work(&ff->rx_midi_work[substream->number]);
+
+	spin_unlock_irqrestore(&ff->lock, flags);
+}
+
+static struct snd_rawmidi_ops midi_capture_ops = {
+	.open		= midi_capture_open,
+	.close		= midi_capture_close,
+	.trigger	= midi_capture_trigger,
+};
+
+static struct snd_rawmidi_ops midi_playback_ops = {
+	.open		= midi_playback_open,
+	.close		= midi_playback_close,
+	.trigger	= midi_playback_trigger,
+};
+
+static void set_midi_substream_names(struct snd_rawmidi_str *stream,
+				     const char *const name)
+{
+	struct snd_rawmidi_substream *substream;
+
+	list_for_each_entry(substream, &stream->substreams, list) {
+		snprintf(substream->name, sizeof(substream->name),
+			 "%s MIDI %d", name, substream->number + 1);
+	}
+}
+
+int snd_ff_create_midi_devices(struct snd_ff *ff)
+{
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_str *stream;
+	int err;
+
+	err = snd_rawmidi_new(ff->card, ff->card->driver, 0,
+			      ff->spec->midi_out_ports, ff->spec->midi_in_ports,
+			      &rmidi);
+	if (err < 0)
+		return err;
+
+	snprintf(rmidi->name, sizeof(rmidi->name),
+		 "%s MIDI", ff->card->shortname);
+	rmidi->private_data = ff;
+
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+			    &midi_capture_ops);
+	stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT];
+	set_midi_substream_names(stream, ff->card->shortname);
+
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+			    &midi_playback_ops);
+	stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
+	set_midi_substream_names(stream, ff->card->shortname);
+
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
+
+	return 0;
+}
diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c
index 4db630f..11d76b3 100644
--- a/sound/firewire/fireface/ff.c
+++ b/sound/firewire/fireface/ff.c
@@ -61,6 +61,10 @@ static void do_registration(struct work_struct *work)
 
 	name_card(ff);
 
+	err = snd_ff_create_midi_devices(ff);
+	if (err < 0)
+		goto error;
+
 	err = snd_card_register(ff->card);
 	if (err < 0)
 		goto error;
@@ -91,6 +95,7 @@ static int snd_ff_probe(struct fw_unit *unit,
 	dev_set_drvdata(&unit->device, ff);
 
 	mutex_init(&ff->mutex);
+	spin_lock_init(&ff->lock);
 
 	ff->spec = (const struct snd_ff_spec *)entry->driver_data;
 
diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h
index bac2e58..2944bde 100644
--- a/sound/firewire/fireface/ff.h
+++ b/sound/firewire/fireface/ff.h
@@ -47,6 +47,7 @@ struct snd_ff {
 	struct snd_card *card;
 	struct fw_unit *unit;
 	struct mutex mutex;
+	spinlock_t lock;
 
 	bool registered;
 	struct delayed_work dwork;
@@ -98,4 +99,6 @@ int snd_ff_transaction_register(struct snd_ff *ff);
 int snd_ff_transaction_reregister(struct snd_ff *ff);
 void snd_ff_transaction_unregister(struct snd_ff *ff);
 
+int snd_ff_create_midi_devices(struct snd_ff *ff);
+
 #endif
-- 
2.9.3

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

* [PATCH 07/13] ALSA: fireface: add proc node to help debugging
  2017-02-11 14:24 [RFC v3][PATCH 00/13] ALSA: fireface: new driver for RME Fireface series Takashi Sakamoto
                   ` (5 preceding siblings ...)
  2017-02-11 14:25 ` [PATCH 06/13] ALSA: fireface: add support for MIDI functionality Takashi Sakamoto
@ 2017-02-11 14:25 ` Takashi Sakamoto
  2017-02-11 14:25 ` [PATCH 08/13] ALSA: firewire-lib: add no-header packet processing Takashi Sakamoto
                   ` (5 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Takashi Sakamoto @ 2017-02-11 14:25 UTC (permalink / raw)
  To: clemens, tiwai; +Cc: alsa-devel, ffado-devel

Drivers retrieve the state and configuration of clock by read transactions.

This commit adds proc node to dump the information for debugging.

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

diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile
index 8e465e4..e88ff9e 100644
--- a/sound/firewire/fireface/Makefile
+++ b/sound/firewire/fireface/Makefile
@@ -1,2 +1,2 @@
-snd-fireface-objs := ff.o ff-transaction.o ff-midi.o
+snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o
 obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o
diff --git a/sound/firewire/fireface/ff-proc.c b/sound/firewire/fireface/ff-proc.c
new file mode 100644
index 0000000..69441d1
--- /dev/null
+++ b/sound/firewire/fireface/ff-proc.c
@@ -0,0 +1,63 @@
+/*
+ * ff-proc.c - a part of driver for RME Fireface series
+ *
+ * Copyright (c) 2015-2017 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "./ff.h"
+
+static void proc_dump_clock_config(struct snd_info_entry *entry,
+				   struct snd_info_buffer *buffer)
+{
+	struct snd_ff *ff = entry->private_data;
+
+	ff->spec->protocol->dump_clock_config(ff, buffer);
+}
+
+static void proc_dump_sync_status(struct snd_info_entry *entry,
+				  struct snd_info_buffer *buffer)
+{
+	struct snd_ff *ff = entry->private_data;
+
+	ff->spec->protocol->dump_sync_status(ff, buffer);
+}
+
+static void add_node(struct snd_ff *ff, struct snd_info_entry *root,
+		     const char *name,
+		     void (*op)(struct snd_info_entry *e,
+				struct snd_info_buffer *b))
+{
+	struct snd_info_entry *entry;
+
+	entry = snd_info_create_card_entry(ff->card, name, root);
+	if (entry == NULL)
+		return;
+
+	snd_info_set_text_ops(entry, ff, op);
+	if (snd_info_register(entry) < 0)
+		snd_info_free_entry(entry);
+}
+
+void snd_ff_proc_init(struct snd_ff *ff)
+{
+	struct snd_info_entry *root;
+
+	/*
+	 * All nodes are automatically removed at snd_card_disconnect(),
+	 * by following to link list.
+	 */
+	root = snd_info_create_card_entry(ff->card, "firewire",
+					  ff->card->proc_root);
+	if (root == NULL)
+		return;
+	root->mode = S_IFDIR | S_IRUGO | S_IXUGO;
+	if (snd_info_register(root) < 0) {
+		snd_info_free_entry(root);
+		return;
+	}
+
+	add_node(ff, root, "clock-config", proc_dump_clock_config);
+	add_node(ff, root, "sync-status", proc_dump_sync_status);
+}
diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c
index 11d76b3..22e7bcb 100644
--- a/sound/firewire/fireface/ff.c
+++ b/sound/firewire/fireface/ff.c
@@ -61,6 +61,8 @@ static void do_registration(struct work_struct *work)
 
 	name_card(ff);
 
+	snd_ff_proc_init(ff);
+
 	err = snd_ff_create_midi_devices(ff);
 	if (err < 0)
 		goto error;
diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h
index 2944bde..2d1fab2 100644
--- a/sound/firewire/fireface/ff.h
+++ b/sound/firewire/fireface/ff.h
@@ -99,6 +99,8 @@ int snd_ff_transaction_register(struct snd_ff *ff);
 int snd_ff_transaction_reregister(struct snd_ff *ff);
 void snd_ff_transaction_unregister(struct snd_ff *ff);
 
+void snd_ff_proc_init(struct snd_ff *ff);
+
 int snd_ff_create_midi_devices(struct snd_ff *ff);
 
 #endif
-- 
2.9.3

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

* [PATCH 08/13] ALSA: firewire-lib: add no-header packet processing
  2017-02-11 14:24 [RFC v3][PATCH 00/13] ALSA: fireface: new driver for RME Fireface series Takashi Sakamoto
                   ` (6 preceding siblings ...)
  2017-02-11 14:25 ` [PATCH 07/13] ALSA: fireface: add proc node to help debugging Takashi Sakamoto
@ 2017-02-11 14:25 ` Takashi Sakamoto
  2017-02-11 14:25 ` [PATCH 09/13] ALSA: fireface: add unique data processing layer Takashi Sakamoto
                   ` (4 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Takashi Sakamoto @ 2017-02-11 14:25 UTC (permalink / raw)
  To: clemens, tiwai; +Cc: alsa-devel, ffado-devel

As long as invesigating Fireface 400, IEC 61883-1/6 is not applied to
its packet streaming protocol. Remarks of the specific protocol are:
 * Each packet doesn't include CIP headers.
 * 64,0 and 128,0 kHz are supported.
 * The device doesn't transmit 8,000 packets per second.
 * 0, 1, 2, 3 are used as tag for rx isochronous packets, however 0 is
   used for tx isochronous packets.

On the other hand, there's a common feature. The number of data blocks
transferred in a second is the same as sampling transmission frequency.
Current AMDTP stream layer already has a method to calculate it and
this driver can utilize it for rx packets.

This commit adds support for the transferring protocol. CIP_NO_HEADERS
flag is newly added. When this flag is set:
 * Both of 0 (without CIP header) and 1 (with CIP header) are used as tag
   to handle incoming isochronous packet.
 * 0 (without CIP header) is used as tag to transfer outgoing isochronous
   packet.
 * Skip CIP header evaluation.
 * Use proper way to calculate the quadlets of isochronous packet payload.

In ALSA userspace interface, 128.0 kHz is not supported, and the AMDTP
stream layer doesn't support 64.0 kHz. These modes are dropped.

The sequence of rx packet has a remarkable quirk about tag. This will be
described in later commits.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/amdtp-stream.c | 84 ++++++++++++++++++++++++++++++++++++++++---
 sound/firewire/amdtp-stream.h |  2 ++
 2 files changed, 81 insertions(+), 5 deletions(-)

diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c
index 112ad03..74c0ba9 100644
--- a/sound/firewire/amdtp-stream.c
+++ b/sound/firewire/amdtp-stream.c
@@ -27,6 +27,7 @@
 
 /* isochronous header parameters */
 #define ISO_DATA_LENGTH_SHIFT	16
+#define TAG_NO_CIP_HEADER	0
 #define TAG_CIP			1
 
 /* common isochronous packet header parameters */
@@ -234,11 +235,15 @@ EXPORT_SYMBOL(amdtp_stream_set_parameters);
 unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s)
 {
 	unsigned int multiplier = 1;
+	unsigned int header_size = 0;
 
 	if (s->flags & CIP_JUMBO_PAYLOAD)
 		multiplier = 5;
+	if (!(s->flags & CIP_NO_HEADER))
+		header_size = 8;
 
-	return 8 + s->syt_interval * s->data_block_quadlets * 4 * multiplier;
+	return header_size +
+		s->syt_interval * s->data_block_quadlets * 4 * multiplier;
 }
 EXPORT_SYMBOL(amdtp_stream_get_max_payload);
 
@@ -380,7 +385,10 @@ static int queue_packet(struct amdtp_stream *s, unsigned int header_length,
 		goto end;
 
 	p.interrupt = IS_ALIGNED(s->packet_index + 1, INTERRUPT_INTERVAL);
-	p.tag = TAG_CIP;
+	if (s->flags & CIP_NO_HEADER)
+		p.tag = TAG_NO_CIP_HEADER;
+	else
+		p.tag = TAG_CIP;
 	p.header_length = header_length;
 	if (payload_length > 0)
 		p.payload_length = payload_length;
@@ -457,6 +465,34 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle,
 	return 0;
 }
 
+static int handle_out_packet_without_header(struct amdtp_stream *s,
+					unsigned int cycle, unsigned int index)
+{
+	__be32 *buffer;
+	unsigned int syt;
+	unsigned int data_blocks;
+	unsigned int payload_length;
+	unsigned int pcm_frames;
+	struct snd_pcm_substream *pcm;
+
+	buffer = s->buffer.packets[s->packet_index].buffer;
+	syt = calculate_syt(s, cycle);
+	data_blocks = calculate_data_blocks(s, syt);
+	pcm_frames = s->process_data_blocks(s, buffer, data_blocks, &syt);
+	s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff;
+
+	payload_length = data_blocks * 4 * s->data_block_quadlets;
+	if (queue_out_packet(s, payload_length) < 0)
+		return -EIO;
+
+	pcm = ACCESS_ONCE(s->pcm);
+	if (pcm && pcm_frames > 0)
+		update_pcm_pointers(s, pcm, pcm_frames);
+
+	/* No need to return the number of handled data blocks. */
+	return 0;
+}
+
 static int handle_in_packet(struct amdtp_stream *s,
 			    unsigned int payload_quadlets, unsigned int cycle,
 			    unsigned int index)
@@ -572,6 +608,30 @@ static int handle_in_packet(struct amdtp_stream *s,
 	return 0;
 }
 
+static int handle_in_packet_without_header(struct amdtp_stream *s,
+			unsigned int payload_quadlets, unsigned int cycle,
+			unsigned int index)
+{
+	__be32 *buffer;
+	unsigned int data_blocks;
+	struct snd_pcm_substream *pcm;
+	unsigned int pcm_frames;
+
+	buffer = s->buffer.packets[s->packet_index].buffer;
+	data_blocks = payload_quadlets / s->data_block_quadlets;
+	pcm_frames = s->process_data_blocks(s, buffer, data_blocks, NULL);
+	s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff;
+
+	if (queue_in_packet(s) < 0)
+		return -EIO;
+
+	pcm = ACCESS_ONCE(s->pcm);
+	if (pcm && pcm_frames > 0)
+		update_pcm_pointers(s, pcm, pcm_frames);
+
+	return 0;
+}
+
 /*
  * In CYCLE_TIMER register of IEEE 1394, 7 bits are used to represent second. On
  * the other hand, in DMA descriptors of 1394 OHCI, 3 bits are used to represent
@@ -603,6 +663,8 @@ static void out_stream_callback(struct fw_iso_context *context, u32 tstamp,
 {
 	struct amdtp_stream *s = private_data;
 	unsigned int i, packets = header_length / 4;
+	int (*handler)(struct amdtp_stream *s, unsigned int cycle,
+		       unsigned int index);
 	u32 cycle;
 
 	if (s->packet_index < 0)
@@ -613,9 +675,14 @@ static void out_stream_callback(struct fw_iso_context *context, u32 tstamp,
 	/* Align to actual cycle count for the last packet. */
 	cycle = increment_cycle_count(cycle, QUEUE_LENGTH - packets);
 
+	if (s->flags & CIP_NO_HEADER)
+		handler = handle_out_packet_without_header;
+	else
+		handler = handle_out_packet;
+
 	for (i = 0; i < packets; ++i) {
 		cycle = increment_cycle_count(cycle, 1);
-		if (handle_out_packet(s, cycle, i) < 0) {
+		if (handler(s, cycle, i) < 0) {
 			s->packet_index = -1;
 			amdtp_stream_pcm_abort(s);
 			return;
@@ -633,6 +700,8 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp,
 	unsigned int i, packets;
 	unsigned int payload_quadlets, max_payload_quadlets;
 	__be32 *headers = header;
+	int (*handler)(struct amdtp_stream *s, unsigned int payload_quadlets,
+		       unsigned int cycle, unsigned int index);
 	u32 cycle;
 
 	if (s->packet_index < 0)
@@ -649,6 +718,11 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp,
 	/* For buffer-over-run prevention. */
 	max_payload_quadlets = amdtp_stream_get_max_payload(s) / 4;
 
+	if (s->flags & CIP_NO_HEADER)
+		handler = handle_in_packet_without_header;
+	else
+		handler = handle_in_packet;
+
 	for (i = 0; i < packets; i++) {
 		cycle = increment_cycle_count(cycle, 1);
 
@@ -662,7 +736,7 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp,
 			break;
 		}
 
-		if (handle_in_packet(s, payload_quadlets, cycle, i) < 0)
+		if (handler(s, payload_quadlets, cycle, i) < 0)
 			break;
 	}
 
@@ -793,7 +867,7 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
 
 	/* NOTE: TAG1 matches CIP. This just affects in stream. */
 	tag = FW_ISO_CONTEXT_MATCH_TAG1;
-	if (s->flags & CIP_EMPTY_WITH_TAG0)
+	if ((s->flags & CIP_EMPTY_WITH_TAG0) || (s->flags & CIP_NO_HEADER))
 		tag |= FW_ISO_CONTEXT_MATCH_TAG0;
 
 	s->callbacked = false;
diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h
index a31dfd8..b155955 100644
--- a/sound/firewire/amdtp-stream.h
+++ b/sound/firewire/amdtp-stream.h
@@ -31,6 +31,7 @@
  *	allows 5 times as large as IEC 61883-6 defines.
  * @CIP_HEADER_WITHOUT_EOH: Only for in-stream. CIP Header doesn't include
  *	valid EOH.
+ * @CIP_NO_HEADERS: a lack of headers in packets
  */
 enum cip_flags {
 	CIP_NONBLOCKING		= 0x00,
@@ -42,6 +43,7 @@ enum cip_flags {
 	CIP_EMPTY_HAS_WRONG_DBC	= 0x20,
 	CIP_JUMBO_PAYLOAD	= 0x40,
 	CIP_HEADER_WITHOUT_EOH	= 0x80,
+	CIP_NO_HEADER		= 0x100,
 };
 
 /**
-- 
2.9.3

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

* [PATCH 09/13] ALSA: fireface: add unique data processing layer
  2017-02-11 14:24 [RFC v3][PATCH 00/13] ALSA: fireface: new driver for RME Fireface series Takashi Sakamoto
                   ` (7 preceding siblings ...)
  2017-02-11 14:25 ` [PATCH 08/13] ALSA: firewire-lib: add no-header packet processing Takashi Sakamoto
@ 2017-02-11 14:25 ` Takashi Sakamoto
  2017-02-11 14:25 ` [PATCH 10/13] ALSA: fireface: add stream management functionality Takashi Sakamoto
                   ` (3 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Takashi Sakamoto @ 2017-02-11 14:25 UTC (permalink / raw)
  To: clemens, tiwai; +Cc: alsa-devel, ffado-devel

As long as investigating Fireface 400, format of payload of each
isochronous packet is not IEC 61883-1/6. The remarkable points of the
format are:
 * The payload consists of some data channels of quadlet size.
 * Each data channels includes data aligned to little endian order.
 * One data channel consists of two parts; 8 bit ancillary field and 24 bit
   PCM frame.

Especially, rx/tx packets includes no CIP headers and different way to
check packet discontinuity. For tx packet, the ancillary field is used for
counter. However, the way of counting is different depending on positions
of data channels. At 44.1 kHz, ancillary field in:
 * 1st/6th/9th/10th/14th/17th data channels: not used for this purpose.
 * 2nd/18th data channels: incremented every data block (0x00-0xff).
 * 3rd/4th/5th/11th/12th/13th data channels: incremented every 256 data
   blocks (0x00-0x07).
 * 7th/8th/15th/16th data channels: incremented per the number of data
   blocks in a packet. The increment can occur inner a packet (0x00-0xff).

For tx packet, tag of each isochronous packet is used for this purpose.
The value of tag cyclically changes between 0, 1, 2 and 3 in this order.
The interval is different depending on sampling transmission frequency.
At 44.1/48.0 kHz, it's 256 data blocks. At 88.2 kHz, it's 96 data blocks.

The number of data blocks in tx packet is exactly the same as
SYT_INTERVAL. There's no empty packet or no-data packet, thus the
throughput is not 8,000 packets per sec. On the other hand, the one in
rx packet is 8,000 packets per sec, thus the number of data blocks is
different between each packet, depending on sampling transmission
frequency:
 * 44.1 kHz: 5 or 6
 * 48.0 kHz: 5 or 6 or 7
 * 88.2 kHz: 10 or 11 or 12

This commit adds data processing layer to satisfy the above specification
in a policy of 'best effort'. Although PCM frames are handled for
intermediate buffer to user space, the ancillary data is not handled at all
to reduce CPU usage, thus counter is not checked. The tag is handled in
packet streaming layer and currently 0 is always used. Furthermore, the
packet streaming layer is responsible for calculation of the number of data
blocks for each packet, thus it's not exactly the same sequence of the
above observation.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/fireface/Makefile   |   2 +-
 sound/firewire/fireface/amdtp-ff.c | 155 +++++++++++++++++++++++++++++++++++++
 sound/firewire/fireface/ff.h       |   8 ++
 3 files changed, 164 insertions(+), 1 deletion(-)
 create mode 100644 sound/firewire/fireface/amdtp-ff.c

diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile
index e88ff9e..e06e9da 100644
--- a/sound/firewire/fireface/Makefile
+++ b/sound/firewire/fireface/Makefile
@@ -1,2 +1,2 @@
-snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o
+snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o
 obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o
diff --git a/sound/firewire/fireface/amdtp-ff.c b/sound/firewire/fireface/amdtp-ff.c
new file mode 100644
index 0000000..780da9d
--- /dev/null
+++ b/sound/firewire/fireface/amdtp-ff.c
@@ -0,0 +1,155 @@
+/*
+ * amdtp-ff.c - a part of driver for RME Fireface series
+ *
+ * Copyright (c) 2015-2017 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include <sound/pcm.h>
+#include "ff.h"
+
+struct amdtp_ff {
+	unsigned int pcm_channels;
+};
+
+int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate,
+			    unsigned int pcm_channels)
+{
+	struct amdtp_ff *p = s->protocol;
+	unsigned int data_channels;
+
+	if (amdtp_stream_running(s))
+		return -EBUSY;
+
+	p->pcm_channels = pcm_channels;
+	data_channels = pcm_channels;
+
+	return amdtp_stream_set_parameters(s, rate, data_channels);
+}
+
+static void write_pcm_s32(struct amdtp_stream *s,
+			  struct snd_pcm_substream *pcm,
+			  __le32 *buffer, unsigned int frames)
+{
+	struct amdtp_ff *p = s->protocol;
+	struct snd_pcm_runtime *runtime = pcm->runtime;
+	unsigned int channels, remaining_frames, i, c;
+	const u32 *src;
+
+	channels = p->pcm_channels;
+	src = (void *)runtime->dma_area +
+			frames_to_bytes(runtime, s->pcm_buffer_pointer);
+	remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
+
+	for (i = 0; i < frames; ++i) {
+		for (c = 0; c < channels; ++c) {
+			buffer[c] = cpu_to_le32(*src);
+			src++;
+		}
+		buffer += s->data_block_quadlets;
+		if (--remaining_frames == 0)
+			src = (void *)runtime->dma_area;
+	}
+}
+
+static void read_pcm_s32(struct amdtp_stream *s,
+			 struct snd_pcm_substream *pcm,
+			 __le32 *buffer, unsigned int frames)
+{
+	struct amdtp_ff *p = s->protocol;
+	struct snd_pcm_runtime *runtime = pcm->runtime;
+	unsigned int channels, remaining_frames, i, c;
+	u32 *dst;
+
+	channels = p->pcm_channels;
+	dst  = (void *)runtime->dma_area +
+			frames_to_bytes(runtime, s->pcm_buffer_pointer);
+	remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer;
+
+	for (i = 0; i < frames; ++i) {
+		for (c = 0; c < channels; ++c) {
+			*dst = le32_to_cpu(buffer[c]) & 0xffffff00;
+			dst++;
+		}
+		buffer += s->data_block_quadlets;
+		if (--remaining_frames == 0)
+			dst = (void *)runtime->dma_area;
+	}
+}
+
+static void write_pcm_silence(struct amdtp_stream *s,
+			      __le32 *buffer, unsigned int frames)
+{
+	struct amdtp_ff *p = s->protocol;
+	unsigned int i, c, channels = p->pcm_channels;
+
+	for (i = 0; i < frames; ++i) {
+		for (c = 0; c < channels; ++c)
+			buffer[c] = cpu_to_le32(0x00000000);
+		buffer += s->data_block_quadlets;
+	}
+}
+
+int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s,
+				    struct snd_pcm_runtime *runtime)
+{
+	int err;
+
+	err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+	if (err < 0)
+		return err;
+
+	return amdtp_stream_add_pcm_hw_constraints(s, runtime);
+}
+
+static unsigned int process_rx_data_blocks(struct amdtp_stream *s,
+					   __be32 *buffer,
+					   unsigned int data_blocks,
+					   unsigned int *syt)
+{
+	struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm);
+	unsigned int pcm_frames;
+
+	if (pcm) {
+		write_pcm_s32(s, pcm, (__le32 *)buffer, data_blocks);
+		pcm_frames = data_blocks;
+	} else {
+		write_pcm_silence(s, (__le32 *)buffer, data_blocks);
+		pcm_frames = 0;
+	}
+
+	return pcm_frames;
+}
+
+static unsigned int process_tx_data_blocks(struct amdtp_stream *s,
+					   __be32 *buffer,
+					   unsigned int data_blocks,
+					   unsigned int *syt)
+{
+	struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm);
+	unsigned int pcm_frames;
+
+	if (pcm) {
+		read_pcm_s32(s, pcm, (__le32 *)buffer, data_blocks);
+		pcm_frames = data_blocks;
+	} else {
+		pcm_frames = 0;
+	}
+
+	return pcm_frames;
+}
+
+int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit,
+		  enum amdtp_stream_direction dir)
+{
+	amdtp_stream_process_data_blocks_t process_data_blocks;
+
+	if (dir == AMDTP_IN_STREAM)
+		process_data_blocks = process_tx_data_blocks;
+	else
+		process_data_blocks = process_rx_data_blocks;
+
+	return amdtp_stream_init(s, unit, dir, CIP_NO_HEADER, 0,
+				 process_data_blocks, sizeof(struct amdtp_ff));
+}
diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h
index 2d1fab2..fa7242f 100644
--- a/sound/firewire/fireface/ff.h
+++ b/sound/firewire/fireface/ff.h
@@ -23,6 +23,7 @@
 #include <sound/rawmidi.h>
 
 #include "../lib.h"
+#include "../amdtp-stream.h"
 
 #define SND_FF_STREAM_MODES		3
 
@@ -99,6 +100,13 @@ int snd_ff_transaction_register(struct snd_ff *ff);
 int snd_ff_transaction_reregister(struct snd_ff *ff);
 void snd_ff_transaction_unregister(struct snd_ff *ff);
 
+int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate,
+			    unsigned int pcm_channels);
+int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s,
+				    struct snd_pcm_runtime *runtime);
+int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit,
+		  enum amdtp_stream_direction dir);
+
 void snd_ff_proc_init(struct snd_ff *ff);
 
 int snd_ff_create_midi_devices(struct snd_ff *ff);
-- 
2.9.3

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

* [PATCH 10/13] ALSA: fireface: add stream management functionality
  2017-02-11 14:24 [RFC v3][PATCH 00/13] ALSA: fireface: new driver for RME Fireface series Takashi Sakamoto
                   ` (8 preceding siblings ...)
  2017-02-11 14:25 ` [PATCH 09/13] ALSA: fireface: add unique data processing layer Takashi Sakamoto
@ 2017-02-11 14:25 ` Takashi Sakamoto
  2017-02-11 14:25 ` [PATCH 11/13] ALSA: fireface: add support for PCM functionality Takashi Sakamoto
                   ` (2 subsequent siblings)
  12 siblings, 0 replies; 14+ messages in thread
From: Takashi Sakamoto @ 2017-02-11 14:25 UTC (permalink / raw)
  To: clemens, tiwai; +Cc: alsa-devel, ffado-devel

As long as investigating Fireface 400, there're three modes depending
on sampling transmission frequency. The number of data channels in each
data block is different depending on the mode. The set of available
data channels for each mode might be different for each protocol and
model.

The length of registers for the number of isochronous channel is just
three bits, therefore 0-7ch are available.

When bus reset occurs on IEEE 1394 bus, the device discontinues to
transmit packets. This commit aborts PCM substreams at bus reset handler.

This commit adds management functionality for the above pacekt streaming.

The device manages its sampling clock independently of sampling
transmission frequency against IEC 61883-6. Thus, it's a lower cost to
change the sampling transmission frequency, while data fetch between
streaming layer and DSP require larger buffer for resampling. As a result,
device latency might tend to be larger than ASICs for IEC 61883-1/6 such
as DM1000/DM1100/DM1500 (BeBoB), DiceII/TCD2210/TCD2220/TCD3070 and
OXFW970/971.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/fireface/Makefile    |   3 +-
 sound/firewire/fireface/ff-stream.c | 243 ++++++++++++++++++++++++++++++++++++
 sound/firewire/fireface/ff.c        |   9 ++
 sound/firewire/fireface/ff.h        |  13 ++
 4 files changed, 267 insertions(+), 1 deletion(-)
 create mode 100644 sound/firewire/fireface/ff-stream.c

diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile
index e06e9da..b772fdc 100644
--- a/sound/firewire/fireface/Makefile
+++ b/sound/firewire/fireface/Makefile
@@ -1,2 +1,3 @@
-snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o
+snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \
+		     ff-stream.o
 obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o
diff --git a/sound/firewire/fireface/ff-stream.c b/sound/firewire/fireface/ff-stream.c
new file mode 100644
index 0000000..0ef6177
--- /dev/null
+++ b/sound/firewire/fireface/ff-stream.c
@@ -0,0 +1,243 @@
+/*
+ * ff-stream.c - a part of driver for RME Fireface series
+ *
+ * Copyright (c) 2015-2017 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "ff.h"
+
+#define CALLBACK_TIMEOUT_MS	200
+
+static int get_rate_mode(unsigned int rate, unsigned int *mode)
+{
+	int i;
+
+	for (i = 0; i < CIP_SFC_COUNT; i++) {
+		if (amdtp_rate_table[i] == rate)
+			break;
+	}
+
+	if (i == CIP_SFC_COUNT)
+		return -EINVAL;
+
+	*mode = ((int)i - 1) / 2;
+
+	return 0;
+}
+
+/*
+ * Fireface 400 manages isochronous channel number in 3 bit field. Therefore,
+ * we can allocate between 0 and 7 channel.
+ */
+static int keep_resources(struct snd_ff *ff, unsigned int rate)
+{
+	int mode;
+	int err;
+
+	err = get_rate_mode(rate, &mode);
+	if (err < 0)
+		return err;
+
+	/* Keep resources for in-stream. */
+	err = amdtp_ff_set_parameters(&ff->tx_stream, rate,
+				      ff->spec->pcm_capture_channels[mode]);
+	if (err < 0)
+		return err;
+	ff->tx_resources.channels_mask = 0x00000000000000ffuLL;
+	err = fw_iso_resources_allocate(&ff->tx_resources,
+			amdtp_stream_get_max_payload(&ff->tx_stream),
+			fw_parent_device(ff->unit)->max_speed);
+	if (err < 0)
+		return err;
+
+	/* Keep resources for out-stream. */
+	err = amdtp_ff_set_parameters(&ff->rx_stream, rate,
+				      ff->spec->pcm_playback_channels[mode]);
+	if (err < 0)
+		return err;
+	ff->rx_resources.channels_mask = 0x00000000000000ffuLL;
+	err = fw_iso_resources_allocate(&ff->rx_resources,
+			amdtp_stream_get_max_payload(&ff->rx_stream),
+			fw_parent_device(ff->unit)->max_speed);
+	if (err < 0)
+		fw_iso_resources_free(&ff->tx_resources);
+
+	return err;
+}
+
+static void release_resources(struct snd_ff *ff)
+{
+	fw_iso_resources_free(&ff->tx_resources);
+	fw_iso_resources_free(&ff->rx_resources);
+}
+
+static inline void finish_session(struct snd_ff *ff)
+{
+	ff->spec->protocol->finish_session(ff);
+	ff->spec->protocol->switch_fetching_mode(ff, false);
+}
+
+static int init_stream(struct snd_ff *ff, enum amdtp_stream_direction dir)
+{
+	int err;
+	struct fw_iso_resources *resources;
+	struct amdtp_stream *stream;
+
+	if (dir == AMDTP_IN_STREAM) {
+		resources = &ff->tx_resources;
+		stream = &ff->tx_stream;
+	} else {
+		resources = &ff->rx_resources;
+		stream = &ff->rx_stream;
+	}
+
+	err = fw_iso_resources_init(resources, ff->unit);
+	if (err < 0)
+		return err;
+
+	err = amdtp_ff_init(stream, ff->unit, dir);
+	if (err < 0)
+		fw_iso_resources_destroy(resources);
+
+	return err;
+}
+
+static void destroy_stream(struct snd_ff *ff, enum amdtp_stream_direction dir)
+{
+	if (dir == AMDTP_IN_STREAM) {
+		amdtp_stream_destroy(&ff->tx_stream);
+		fw_iso_resources_destroy(&ff->tx_resources);
+	} else {
+		amdtp_stream_destroy(&ff->rx_stream);
+		fw_iso_resources_destroy(&ff->rx_resources);
+	}
+}
+
+int snd_ff_stream_init_duplex(struct snd_ff *ff)
+{
+	int err;
+
+	err = init_stream(ff, AMDTP_OUT_STREAM);
+	if (err < 0)
+		goto end;
+
+	err = init_stream(ff, AMDTP_IN_STREAM);
+	if (err < 0)
+		destroy_stream(ff, AMDTP_OUT_STREAM);
+end:
+	return err;
+}
+
+/*
+ * This function should be called before starting streams or after stopping
+ * streams.
+ */
+void snd_ff_stream_destroy_duplex(struct snd_ff *ff)
+{
+	destroy_stream(ff, AMDTP_IN_STREAM);
+	destroy_stream(ff, AMDTP_OUT_STREAM);
+}
+
+int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate)
+{
+	unsigned int curr_rate;
+	enum snd_ff_clock_src src;
+	int err;
+
+	if (ff->substreams_counter == 0)
+		return 0;
+
+	err = ff->spec->protocol->get_clock(ff, &curr_rate, &src);
+	if (err < 0)
+		return err;
+	if (curr_rate != rate ||
+	    amdtp_streaming_error(&ff->tx_stream) ||
+	    amdtp_streaming_error(&ff->rx_stream)) {
+		finish_session(ff);
+
+		amdtp_stream_stop(&ff->tx_stream);
+		amdtp_stream_stop(&ff->rx_stream);
+
+		release_resources(ff);
+	}
+
+	/*
+	 * Regardless of current source of clock signal, drivers transfer some
+	 * packets. Then, the device transfers packets.
+	 */
+	if (!amdtp_stream_running(&ff->rx_stream)) {
+		err = keep_resources(ff, rate);
+		if (err < 0)
+			goto error;
+
+		err = ff->spec->protocol->begin_session(ff, rate);
+		if (err < 0)
+			goto error;
+
+		err = amdtp_stream_start(&ff->rx_stream,
+					 ff->rx_resources.channel,
+					 fw_parent_device(ff->unit)->max_speed);
+		if (err < 0)
+			goto error;
+
+		if (!amdtp_stream_wait_callback(&ff->rx_stream,
+						CALLBACK_TIMEOUT_MS)) {
+			err = -ETIMEDOUT;
+			goto error;
+		}
+
+		err = ff->spec->protocol->switch_fetching_mode(ff, true);
+		if (err < 0)
+			goto error;
+	}
+
+	if (!amdtp_stream_running(&ff->tx_stream)) {
+		err = amdtp_stream_start(&ff->tx_stream,
+					 ff->tx_resources.channel,
+					 fw_parent_device(ff->unit)->max_speed);
+		if (err < 0)
+			goto error;
+
+		if (!amdtp_stream_wait_callback(&ff->tx_stream,
+						CALLBACK_TIMEOUT_MS)) {
+			err = -ETIMEDOUT;
+			goto error;
+		}
+	}
+
+	return 0;
+error:
+	amdtp_stream_stop(&ff->tx_stream);
+	amdtp_stream_stop(&ff->rx_stream);
+
+	finish_session(ff);
+	release_resources(ff);
+
+	return err;
+}
+
+void snd_ff_stream_stop_duplex(struct snd_ff *ff)
+{
+	if (ff->substreams_counter > 0)
+		return;
+
+	amdtp_stream_stop(&ff->tx_stream);
+	amdtp_stream_stop(&ff->rx_stream);
+	finish_session(ff);
+	release_resources(ff);
+}
+
+void snd_ff_stream_update_duplex(struct snd_ff *ff)
+{
+	/* The device discontinue to transfer packets.  */
+	amdtp_stream_pcm_abort(&ff->tx_stream);
+	amdtp_stream_stop(&ff->tx_stream);
+
+	amdtp_stream_pcm_abort(&ff->rx_stream);
+	amdtp_stream_stop(&ff->rx_stream);
+
+	fw_iso_resources_update(&ff->tx_resources);
+	fw_iso_resources_update(&ff->rx_resources);
+}
diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c
index 22e7bcb..6bdbebd 100644
--- a/sound/firewire/fireface/ff.c
+++ b/sound/firewire/fireface/ff.c
@@ -29,6 +29,7 @@ static void name_card(struct snd_ff *ff)
 
 static void ff_free(struct snd_ff *ff)
 {
+	snd_ff_stream_destroy_duplex(ff);
 	snd_ff_transaction_unregister(ff);
 
 	fw_unit_put(ff->unit);
@@ -61,6 +62,10 @@ static void do_registration(struct work_struct *work)
 
 	name_card(ff);
 
+	err = snd_ff_stream_init_duplex(ff);
+	if (err < 0)
+		goto error;
+
 	snd_ff_proc_init(ff);
 
 	err = snd_ff_create_midi_devices(ff);
@@ -78,6 +83,7 @@ static void do_registration(struct work_struct *work)
 	return;
 error:
 	snd_ff_transaction_unregister(ff);
+	snd_ff_stream_destroy_duplex(ff);
 	snd_card_free(ff->card);
 	dev_info(&ff->unit->device,
 		 "Sound card registration failed: %d\n", err);
@@ -117,6 +123,9 @@ static void snd_ff_update(struct fw_unit *unit)
 		snd_fw_schedule_registration(unit, &ff->dwork);
 
 	snd_ff_transaction_reregister(ff);
+
+	if (ff->registered)
+		snd_ff_stream_update_duplex(ff);
 }
 
 static void snd_ff_remove(struct fw_unit *unit)
diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h
index fa7242f..6599c11 100644
--- a/sound/firewire/fireface/ff.h
+++ b/sound/firewire/fireface/ff.h
@@ -24,6 +24,7 @@
 
 #include "../lib.h"
 #include "../amdtp-stream.h"
+#include "../iso-resources.h"
 
 #define SND_FF_STREAM_MODES		3
 
@@ -68,6 +69,12 @@ struct snd_ff {
 	ktime_t next_ktime[SND_FF_OUT_MIDI_PORTS];
 	bool rx_midi_error[SND_FF_OUT_MIDI_PORTS];
 	unsigned int rx_bytes[SND_FF_OUT_MIDI_PORTS];
+
+	unsigned int substreams_counter;
+	struct amdtp_stream tx_stream;
+	struct amdtp_stream rx_stream;
+	struct fw_iso_resources tx_resources;
+	struct fw_iso_resources rx_resources;
 };
 
 enum snd_ff_clock_src {
@@ -107,6 +114,12 @@ int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s,
 int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit,
 		  enum amdtp_stream_direction dir);
 
+int snd_ff_stream_init_duplex(struct snd_ff *ff);
+void snd_ff_stream_destroy_duplex(struct snd_ff *ff);
+int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate);
+void snd_ff_stream_stop_duplex(struct snd_ff *ff);
+void snd_ff_stream_update_duplex(struct snd_ff *ff);
+
 void snd_ff_proc_init(struct snd_ff *ff);
 
 int snd_ff_create_midi_devices(struct snd_ff *ff);
-- 
2.9.3

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

* [PATCH 11/13] ALSA: fireface: add support for PCM functionality
  2017-02-11 14:24 [RFC v3][PATCH 00/13] ALSA: fireface: new driver for RME Fireface series Takashi Sakamoto
                   ` (9 preceding siblings ...)
  2017-02-11 14:25 ` [PATCH 10/13] ALSA: fireface: add stream management functionality Takashi Sakamoto
@ 2017-02-11 14:25 ` Takashi Sakamoto
  2017-02-11 14:25 ` [PATCH 12/13] ALSA: fireface: add hwdep interface Takashi Sakamoto
  2017-02-11 14:25 ` [PATCH 13/13] ALSA: fireface: add support for Fireface 400 Takashi Sakamoto
  12 siblings, 0 replies; 14+ messages in thread
From: Takashi Sakamoto @ 2017-02-11 14:25 UTC (permalink / raw)
  To: clemens, tiwai; +Cc: alsa-devel, ffado-devel

This commit adds PCM functionality to transmit/receive PCM samples on
isochronous packet streaming. This commit enables userspace applications
to start/stop packet streaming via ALSA PCM interface.

Sampling rate requested by applications is used as sampling transmission
frequency of AMDTP packet streaming. As I described in followed commits,
units in this series manages sampling clock independently of sampling
transmission frequency, and they supports resampling between their packet
streaming/data block processing layer and sampling data processing layer.
This commit take this driver to utilize these features for usability.

When internal clock is selected as source signal of sampling clock, this
driver allows user space applications to start PCM substreams at any rate
supported by packet streaming engine as sampling transmission frequency.
In this case, this driver expects units to perform resampling PCM frames
for rx/tx packets when sampling rate and sampling transmission frequency
are mismatched. This is for daily use cases.

When any external clock is selected as the source signal, this driver
gets configured sampling rate from units, then restricts available
sampling rate to the rate for PCM applications. This is for studio use
cases.

Models in this series support 64.0/128.0 kHz of sampling rate, however
these frequencies are not supported in IEC 61883-6 as sampling transmission
frequency. Therefore, Packet streaming engine of ALSA firewire stack can't
handle them. When units are configured to use any external clock as source
signal of sampling clock and one of these rate is configured as rate of
the sampling clock, this driver returns EIO to user space applications.

Anyway, this driver doesn't voluntarily configure parameters of sampling
clock because it's not a part of packet streaming functionality. It's
better for users to work with appropriate user space implementations to
configure the parameters in advance of usage.

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

diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile
index b772fdc..e626938 100644
--- a/sound/firewire/fireface/Makefile
+++ b/sound/firewire/fireface/Makefile
@@ -1,3 +1,3 @@
 snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \
-		     ff-stream.o
+		     ff-stream.o ff-pcm.o
 obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o
diff --git a/sound/firewire/fireface/ff-pcm.c b/sound/firewire/fireface/ff-pcm.c
new file mode 100644
index 0000000..c2a7c3a
--- /dev/null
+++ b/sound/firewire/fireface/ff-pcm.c
@@ -0,0 +1,390 @@
+/*
+ * ff-pcm.c - a part of driver for RME Fireface series
+ *
+ * Copyright (c) 2015-2017 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "ff.h"
+
+static inline unsigned int get_multiplier_mode_with_index(unsigned int index)
+{
+	return ((int)index - 1) / 2;
+}
+
+static int hw_rule_rate(struct snd_pcm_hw_params *params,
+			struct snd_pcm_hw_rule *rule)
+{
+	const unsigned int *pcm_channels = rule->private;
+	struct snd_interval *r =
+		hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	const struct snd_interval *c =
+		hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_interval t = {
+		.min = UINT_MAX, .max = 0, .integer = 1
+	};
+	unsigned int i, mode;
+
+	for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) {
+		mode = get_multiplier_mode_with_index(i);
+		if (!snd_interval_test(c, pcm_channels[mode]))
+			continue;
+
+		t.min = min(t.min, amdtp_rate_table[i]);
+		t.max = max(t.max, amdtp_rate_table[i]);
+	}
+
+	return snd_interval_refine(r, &t);
+}
+
+static int hw_rule_channels(struct snd_pcm_hw_params *params,
+			    struct snd_pcm_hw_rule *rule)
+{
+	const unsigned int *pcm_channels = rule->private;
+	struct snd_interval *c =
+		hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	const struct snd_interval *r =
+		hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE);
+	struct snd_interval t = {
+		.min = UINT_MAX, .max = 0, .integer = 1
+	};
+	unsigned int i, mode;
+
+	for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) {
+		mode = get_multiplier_mode_with_index(i);
+		if (!snd_interval_test(r, amdtp_rate_table[i]))
+			continue;
+
+		t.min = min(t.min, pcm_channels[mode]);
+		t.max = max(t.max, pcm_channels[mode]);
+	}
+
+	return snd_interval_refine(c, &t);
+}
+
+static void limit_channels_and_rates(struct snd_pcm_hardware *hw,
+				     const unsigned int *pcm_channels)
+{
+	unsigned int mode;
+	unsigned int rate, channels;
+	int i;
+
+	hw->channels_min = UINT_MAX;
+	hw->channels_max = 0;
+	hw->rate_min = UINT_MAX;
+	hw->rate_max = 0;
+
+	for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) {
+		mode = get_multiplier_mode_with_index(i);
+
+		channels = pcm_channels[mode];
+		if (pcm_channels[mode] == 0)
+			continue;
+		hw->channels_min = min(hw->channels_min, channels);
+		hw->channels_max = max(hw->channels_max, channels);
+
+		rate = amdtp_rate_table[i];
+		hw->rates |= snd_pcm_rate_to_rate_bit(rate);
+		hw->rate_min = min(hw->rate_min, rate);
+		hw->rate_max = max(hw->rate_max, rate);
+	}
+}
+
+static void limit_period_and_buffer(struct snd_pcm_hardware *hw)
+{
+	hw->periods_min = 2;		/* SNDRV_PCM_INFO_BATCH */
+	hw->periods_max = UINT_MAX;
+
+	hw->period_bytes_min = 4 * hw->channels_max;	/* bytes for a frame */
+
+	/* Just to prevent from allocating much pages. */
+	hw->period_bytes_max = hw->period_bytes_min * 2048;
+	hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min;
+}
+
+static int pcm_init_hw_params(struct snd_ff *ff,
+			      struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct amdtp_stream *s;
+	const unsigned int *pcm_channels;
+	int err;
+
+	runtime->hw.info = SNDRV_PCM_INFO_BATCH |
+			   SNDRV_PCM_INFO_BLOCK_TRANSFER |
+			   SNDRV_PCM_INFO_INTERLEAVED |
+			   SNDRV_PCM_INFO_JOINT_DUPLEX |
+			   SNDRV_PCM_INFO_MMAP |
+			   SNDRV_PCM_INFO_MMAP_VALID;
+
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_S32;
+		s = &ff->tx_stream;
+		pcm_channels = ff->spec->pcm_capture_channels;
+	} else {
+		runtime->hw.formats = SNDRV_PCM_FMTBIT_S32;
+		s = &ff->rx_stream;
+		pcm_channels = ff->spec->pcm_playback_channels;
+	}
+
+	/* limit rates */
+	limit_channels_and_rates(&runtime->hw, pcm_channels);
+	limit_period_and_buffer(&runtime->hw);
+
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+				  hw_rule_channels, (void *)pcm_channels,
+				  SNDRV_PCM_HW_PARAM_RATE, -1);
+	if (err < 0)
+		return err;
+
+	err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+				  hw_rule_rate, (void *)pcm_channels,
+				  SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	if (err < 0)
+		return err;
+
+	return amdtp_ff_add_pcm_hw_constraints(s, runtime);
+}
+
+static int pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_ff *ff = substream->private_data;
+	unsigned int rate;
+	enum snd_ff_clock_src src;
+	int i, err;
+
+	err = pcm_init_hw_params(ff, substream);
+	if (err < 0)
+		return err;
+
+	err = ff->spec->protocol->get_clock(ff, &rate, &src);
+	if (err < 0)
+		return err;
+
+	if (src != SND_FF_CLOCK_SRC_INTERNAL ||
+	    amdtp_stream_pcm_running(&ff->rx_stream) ||
+	    amdtp_stream_pcm_running(&ff->tx_stream)) {
+		for (i = 0; i < CIP_SFC_COUNT; ++i) {
+			if (amdtp_rate_table[i] == rate)
+				break;
+		}
+		/*
+		 * The unit is configured at sampling frequency which packet
+		 * streaming engine can't support.
+		 */
+		if (i >= CIP_SFC_COUNT)
+			return -EIO;
+
+		substream->runtime->hw.rate_min = rate;
+		substream->runtime->hw.rate_max = rate;
+	}
+
+	snd_pcm_set_sync(substream);
+
+	return 0;
+}
+
+static int pcm_close(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+static int pcm_capture_hw_params(struct snd_pcm_substream *substream,
+				 struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_ff *ff = substream->private_data;
+	int err;
+
+	err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
+					       params_buffer_bytes(hw_params));
+	if (err < 0)
+		return err;
+
+	if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+		mutex_lock(&ff->mutex);
+		ff->substreams_counter++;
+		mutex_unlock(&ff->mutex);
+	}
+
+	return 0;
+}
+
+static int pcm_playback_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_ff *ff = substream->private_data;
+	int err;
+
+	err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
+					       params_buffer_bytes(hw_params));
+	if (err < 0)
+		return err;
+
+	if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+		mutex_lock(&ff->mutex);
+		ff->substreams_counter++;
+		mutex_unlock(&ff->mutex);
+	}
+
+	return 0;
+}
+
+static int pcm_capture_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_ff *ff = substream->private_data;
+
+	mutex_lock(&ff->mutex);
+
+	if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
+		ff->substreams_counter--;
+
+	snd_ff_stream_stop_duplex(ff);
+
+	mutex_unlock(&ff->mutex);
+
+	return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int pcm_playback_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_ff *ff = substream->private_data;
+
+	mutex_lock(&ff->mutex);
+
+	if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
+		ff->substreams_counter--;
+
+	snd_ff_stream_stop_duplex(ff);
+
+	mutex_unlock(&ff->mutex);
+
+	return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int pcm_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_ff *ff = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	mutex_lock(&ff->mutex);
+
+	err = snd_ff_stream_start_duplex(ff, runtime->rate);
+	if (err >= 0)
+		amdtp_stream_pcm_prepare(&ff->tx_stream);
+
+	mutex_unlock(&ff->mutex);
+
+	return err;
+}
+
+static int pcm_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_ff *ff = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	mutex_lock(&ff->mutex);
+
+	err = snd_ff_stream_start_duplex(ff, runtime->rate);
+	if (err >= 0)
+		amdtp_stream_pcm_prepare(&ff->rx_stream);
+
+	mutex_unlock(&ff->mutex);
+
+	return err;
+}
+
+static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_ff *ff = substream->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		amdtp_stream_pcm_trigger(&ff->tx_stream, substream);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		amdtp_stream_pcm_trigger(&ff->tx_stream, NULL);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_ff *ff = substream->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		amdtp_stream_pcm_trigger(&ff->rx_stream, substream);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		amdtp_stream_pcm_trigger(&ff->rx_stream, NULL);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
+{
+	struct snd_ff *ff = sbstrm->private_data;
+
+	return amdtp_stream_pcm_pointer(&ff->tx_stream);
+}
+
+static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
+{
+	struct snd_ff *ff = sbstrm->private_data;
+
+	return amdtp_stream_pcm_pointer(&ff->rx_stream);
+}
+
+static struct snd_pcm_ops pcm_capture_ops = {
+	.open		= pcm_open,
+	.close		= pcm_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= pcm_capture_hw_params,
+	.hw_free	= pcm_capture_hw_free,
+	.prepare	= pcm_capture_prepare,
+	.trigger	= pcm_capture_trigger,
+	.pointer	= pcm_capture_pointer,
+	.page		= snd_pcm_lib_get_vmalloc_page,
+};
+
+static struct snd_pcm_ops pcm_playback_ops = {
+	.open		= pcm_open,
+	.close		= pcm_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= pcm_playback_hw_params,
+	.hw_free	= pcm_playback_hw_free,
+	.prepare	= pcm_playback_prepare,
+	.trigger	= pcm_playback_trigger,
+	.pointer	= pcm_playback_pointer,
+	.page		= snd_pcm_lib_get_vmalloc_page,
+	.mmap		= snd_pcm_lib_mmap_vmalloc,
+};
+
+int snd_ff_create_pcm_devices(struct snd_ff *ff)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(ff->card, ff->card->driver, 0, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	pcm->private_data = ff;
+	snprintf(pcm->name, sizeof(pcm->name),
+		 "%s PCM", ff->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);
+
+	return 0;
+}
diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c
index 6bdbebd..ff62d16 100644
--- a/sound/firewire/fireface/ff.c
+++ b/sound/firewire/fireface/ff.c
@@ -72,6 +72,10 @@ static void do_registration(struct work_struct *work)
 	if (err < 0)
 		goto error;
 
+	err = snd_ff_create_pcm_devices(ff);
+	if (err < 0)
+		goto error;
+
 	err = snd_card_register(ff->card);
 	if (err < 0)
 		goto error;
diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h
index 6599c11..0d5228c 100644
--- a/sound/firewire/fireface/ff.h
+++ b/sound/firewire/fireface/ff.h
@@ -21,6 +21,8 @@
 #include <sound/core.h>
 #include <sound/info.h>
 #include <sound/rawmidi.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
 
 #include "../lib.h"
 #include "../amdtp-stream.h"
@@ -124,4 +126,6 @@ void snd_ff_proc_init(struct snd_ff *ff);
 
 int snd_ff_create_midi_devices(struct snd_ff *ff);
 
+int snd_ff_create_pcm_devices(struct snd_ff *ff);
+
 #endif
-- 
2.9.3

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

* [PATCH 12/13] ALSA: fireface: add hwdep interface
  2017-02-11 14:24 [RFC v3][PATCH 00/13] ALSA: fireface: new driver for RME Fireface series Takashi Sakamoto
                   ` (10 preceding siblings ...)
  2017-02-11 14:25 ` [PATCH 11/13] ALSA: fireface: add support for PCM functionality Takashi Sakamoto
@ 2017-02-11 14:25 ` Takashi Sakamoto
  2017-02-11 14:25 ` [PATCH 13/13] ALSA: fireface: add support for Fireface 400 Takashi Sakamoto
  12 siblings, 0 replies; 14+ messages in thread
From: Takashi Sakamoto @ 2017-02-11 14:25 UTC (permalink / raw)
  To: clemens, tiwai; +Cc: alsa-devel, ffado-devel

This commit adds hwdep interface so as the other drivers for audio and
music units on IEEE 1394 have.

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

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 include/uapi/sound/asound.h         |   3 +-
 include/uapi/sound/firewire.h       |   2 +-
 sound/firewire/Kconfig              |   1 +
 sound/firewire/fireface/Makefile    |   2 +-
 sound/firewire/fireface/ff-hwdep.c  | 191 ++++++++++++++++++++++++++++++++++++
 sound/firewire/fireface/ff-pcm.c    |  20 +++-
 sound/firewire/fireface/ff-stream.c |  39 ++++++++
 sound/firewire/fireface/ff.c        |   5 +
 sound/firewire/fireface/ff.h        |  12 +++
 9 files changed, 269 insertions(+), 6 deletions(-)
 create mode 100644 sound/firewire/fireface/ff-hwdep.c

diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h
index fd7b561..fd41697 100644
--- a/include/uapi/sound/asound.h
+++ b/include/uapi/sound/asound.h
@@ -108,9 +108,10 @@ enum {
 	SNDRV_HWDEP_IFACE_FW_TASCAM,	/* TASCAM FireWire series */
 	SNDRV_HWDEP_IFACE_LINE6,	/* Line6 USB processors */
 	SNDRV_HWDEP_IFACE_FW_MOTU,	/* MOTU FireWire series */
+	SNDRV_HWDEP_IFACE_FW_FIREFACE,	/* RME Fireface series */
 
 	/* Don't forget to change the following: */
-	SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_MOTU
+	SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_FIREFACE
 };
 
 struct snd_hwdep_info {
diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h
index 29afc5e..6229004 100644
--- a/include/uapi/sound/firewire.h
+++ b/include/uapi/sound/firewire.h
@@ -73,7 +73,7 @@ union snd_firewire_event {
 #define SNDRV_FIREWIRE_TYPE_DIGI00X	5
 #define SNDRV_FIREWIRE_TYPE_TASCAM	6
 #define SNDRV_FIREWIRE_TYPE_MOTU	7
-/* RME... */
+#define SNDRV_FIREWIRE_TYPE_FIREFACE	8
 
 struct snd_firewire_get_info {
 	unsigned int type; /* SNDRV_FIREWIRE_TYPE_xxx */
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig
index 077e03c..806d234 100644
--- a/sound/firewire/Kconfig
+++ b/sound/firewire/Kconfig
@@ -156,6 +156,7 @@ config SND_FIREWIRE_MOTU
 config SND_FIREFACE
 	tristate "RME Fireface series support"
 	select SND_FIREWIRE_LIB
+	select SND_HWDEP
 	help
 	 Say Y here to include support for RME fireface series.
 
diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile
index e626938..8d6c612 100644
--- a/sound/firewire/fireface/Makefile
+++ b/sound/firewire/fireface/Makefile
@@ -1,3 +1,3 @@
 snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \
-		     ff-stream.o ff-pcm.o
+		     ff-stream.o ff-pcm.o ff-hwdep.o
 obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o
diff --git a/sound/firewire/fireface/ff-hwdep.c b/sound/firewire/fireface/ff-hwdep.c
new file mode 100644
index 0000000..3ee04b0
--- /dev/null
+++ b/sound/firewire/fireface/ff-hwdep.c
@@ -0,0 +1,191 @@
+/*
+ * ff-hwdep.c - a part of driver for RME Fireface series
+ *
+ * Copyright (c) 2015-2017 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+/*
+ * This codes give three functionality.
+ *
+ * 1.get firewire node information
+ * 2.get notification about starting/stopping stream
+ * 3.lock/unlock stream
+ */
+
+#include "ff.h"
+
+static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf,  long count,
+		       loff_t *offset)
+{
+	struct snd_ff *ff = hwdep->private_data;
+	DEFINE_WAIT(wait);
+	union snd_firewire_event event;
+
+	spin_lock_irq(&ff->lock);
+
+	while (!ff->dev_lock_changed) {
+		prepare_to_wait(&ff->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
+		spin_unlock_irq(&ff->lock);
+		schedule();
+		finish_wait(&ff->hwdep_wait, &wait);
+		if (signal_pending(current))
+			return -ERESTARTSYS;
+		spin_lock_irq(&ff->lock);
+	}
+
+	memset(&event, 0, sizeof(event));
+	if (ff->dev_lock_changed) {
+		event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
+		event.lock_status.status = (ff->dev_lock_count > 0);
+		ff->dev_lock_changed = false;
+
+		count = min_t(long, count, sizeof(event.lock_status));
+	}
+
+	spin_unlock_irq(&ff->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_ff *ff = hwdep->private_data;
+	unsigned int events;
+
+	poll_wait(file, &ff->hwdep_wait, wait);
+
+	spin_lock_irq(&ff->lock);
+	if (ff->dev_lock_changed)
+		events = POLLIN | POLLRDNORM;
+	else
+		events = 0;
+	spin_unlock_irq(&ff->lock);
+
+	return events;
+}
+
+static int hwdep_get_info(struct snd_ff *ff, void __user *arg)
+{
+	struct fw_device *dev = fw_parent_device(ff->unit);
+	struct snd_firewire_get_info info;
+
+	memset(&info, 0, sizeof(info));
+	info.type = SNDRV_FIREWIRE_TYPE_FIREFACE;
+	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_ff *ff)
+{
+	int err;
+
+	spin_lock_irq(&ff->lock);
+
+	if (ff->dev_lock_count == 0) {
+		ff->dev_lock_count = -1;
+		err = 0;
+	} else {
+		err = -EBUSY;
+	}
+
+	spin_unlock_irq(&ff->lock);
+
+	return err;
+}
+
+static int hwdep_unlock(struct snd_ff *ff)
+{
+	int err;
+
+	spin_lock_irq(&ff->lock);
+
+	if (ff->dev_lock_count == -1) {
+		ff->dev_lock_count = 0;
+		err = 0;
+	} else {
+		err = -EBADFD;
+	}
+
+	spin_unlock_irq(&ff->lock);
+
+	return err;
+}
+
+static int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
+{
+	struct snd_ff *ff = hwdep->private_data;
+
+	spin_lock_irq(&ff->lock);
+	if (ff->dev_lock_count == -1)
+		ff->dev_lock_count = 0;
+	spin_unlock_irq(&ff->lock);
+
+	return 0;
+}
+
+static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
+		       unsigned int cmd, unsigned long arg)
+{
+	struct snd_ff *ff = hwdep->private_data;
+
+	switch (cmd) {
+	case SNDRV_FIREWIRE_IOCTL_GET_INFO:
+		return hwdep_get_info(ff, (void __user *)arg);
+	case SNDRV_FIREWIRE_IOCTL_LOCK:
+		return hwdep_lock(ff);
+	case SNDRV_FIREWIRE_IOCTL_UNLOCK:
+		return hwdep_unlock(ff);
+	default:
+		return -ENOIOCTLCMD;
+	}
+}
+
+#ifdef CONFIG_COMPAT
+static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
+			      unsigned int cmd, unsigned long arg)
+{
+	return hwdep_ioctl(hwdep, file, cmd,
+			   (unsigned long)compat_ptr(arg));
+}
+#else
+#define hwdep_compat_ioctl NULL
+#endif
+
+int snd_ff_create_hwdep_devices(struct snd_ff *ff)
+{
+	static const struct snd_hwdep_ops hwdep_ops = {
+		.read		= hwdep_read,
+		.release	= hwdep_release,
+		.poll		= hwdep_poll,
+		.ioctl		= hwdep_ioctl,
+		.ioctl_compat	= hwdep_compat_ioctl,
+	};
+	struct snd_hwdep *hwdep;
+	int err;
+
+	err = snd_hwdep_new(ff->card, ff->card->driver, 0, &hwdep);
+	if (err < 0)
+		return err;
+
+	strcpy(hwdep->name, ff->card->driver);
+	hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREFACE;
+	hwdep->ops = hwdep_ops;
+	hwdep->private_data = ff;
+	hwdep->exclusive = true;
+
+	return 0;
+}
diff --git a/sound/firewire/fireface/ff-pcm.c b/sound/firewire/fireface/ff-pcm.c
index c2a7c3a..e631522 100644
--- a/sound/firewire/fireface/ff-pcm.c
+++ b/sound/firewire/fireface/ff-pcm.c
@@ -154,13 +154,21 @@ static int pcm_open(struct snd_pcm_substream *substream)
 	enum snd_ff_clock_src src;
 	int i, err;
 
-	err = pcm_init_hw_params(ff, substream);
+	err = snd_ff_stream_lock_try(ff);
 	if (err < 0)
 		return err;
 
+	err = pcm_init_hw_params(ff, substream);
+	if (err < 0) {
+		snd_ff_stream_lock_release(ff);
+		return err;
+	}
+
 	err = ff->spec->protocol->get_clock(ff, &rate, &src);
-	if (err < 0)
+	if (err < 0) {
+		snd_ff_stream_lock_release(ff);
 		return err;
+	}
 
 	if (src != SND_FF_CLOCK_SRC_INTERNAL ||
 	    amdtp_stream_pcm_running(&ff->rx_stream) ||
@@ -173,8 +181,10 @@ static int pcm_open(struct snd_pcm_substream *substream)
 		 * The unit is configured at sampling frequency which packet
 		 * streaming engine can't support.
 		 */
-		if (i >= CIP_SFC_COUNT)
+		if (i >= CIP_SFC_COUNT) {
+			snd_ff_stream_lock_release(ff);
 			return -EIO;
+		}
 
 		substream->runtime->hw.rate_min = rate;
 		substream->runtime->hw.rate_max = rate;
@@ -187,6 +197,10 @@ static int pcm_open(struct snd_pcm_substream *substream)
 
 static int pcm_close(struct snd_pcm_substream *substream)
 {
+	struct snd_ff *ff = substream->private_data;
+
+	snd_ff_stream_lock_release(ff);
+
 	return 0;
 }
 
diff --git a/sound/firewire/fireface/ff-stream.c b/sound/firewire/fireface/ff-stream.c
index 0ef6177..7888092 100644
--- a/sound/firewire/fireface/ff-stream.c
+++ b/sound/firewire/fireface/ff-stream.c
@@ -241,3 +241,42 @@ void snd_ff_stream_update_duplex(struct snd_ff *ff)
 	fw_iso_resources_update(&ff->tx_resources);
 	fw_iso_resources_update(&ff->rx_resources);
 }
+
+void snd_ff_stream_lock_changed(struct snd_ff *ff)
+{
+	ff->dev_lock_changed = true;
+	wake_up(&ff->hwdep_wait);
+}
+
+int snd_ff_stream_lock_try(struct snd_ff *ff)
+{
+	int err;
+
+	spin_lock_irq(&ff->lock);
+
+	/* user land lock this */
+	if (ff->dev_lock_count < 0) {
+		err = -EBUSY;
+		goto end;
+	}
+
+	/* this is the first time */
+	if (ff->dev_lock_count++ == 0)
+		snd_ff_stream_lock_changed(ff);
+	err = 0;
+end:
+	spin_unlock_irq(&ff->lock);
+	return err;
+}
+
+void snd_ff_stream_lock_release(struct snd_ff *ff)
+{
+	spin_lock_irq(&ff->lock);
+
+	if (WARN_ON(ff->dev_lock_count <= 0))
+		goto end;
+	if (--ff->dev_lock_count == 0)
+		snd_ff_stream_lock_changed(ff);
+end:
+	spin_unlock_irq(&ff->lock);
+}
diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c
index ff62d16..f57b434 100644
--- a/sound/firewire/fireface/ff.c
+++ b/sound/firewire/fireface/ff.c
@@ -76,6 +76,10 @@ static void do_registration(struct work_struct *work)
 	if (err < 0)
 		goto error;
 
+	err = snd_ff_create_hwdep_devices(ff);
+	if (err < 0)
+		goto error;
+
 	err = snd_card_register(ff->card);
 	if (err < 0)
 		goto error;
@@ -108,6 +112,7 @@ static int snd_ff_probe(struct fw_unit *unit,
 
 	mutex_init(&ff->mutex);
 	spin_lock_init(&ff->lock);
+	init_waitqueue_head(&ff->hwdep_wait);
 
 	ff->spec = (const struct snd_ff_spec *)entry->driver_data;
 
diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h
index 0d5228c..16839b7 100644
--- a/sound/firewire/fireface/ff.h
+++ b/sound/firewire/fireface/ff.h
@@ -23,6 +23,8 @@
 #include <sound/rawmidi.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
+#include <sound/hwdep.h>
+#include <sound/firewire.h>
 
 #include "../lib.h"
 #include "../amdtp-stream.h"
@@ -77,6 +79,10 @@ struct snd_ff {
 	struct amdtp_stream rx_stream;
 	struct fw_iso_resources tx_resources;
 	struct fw_iso_resources rx_resources;
+
+	int dev_lock_count;
+	bool dev_lock_changed;
+	wait_queue_head_t hwdep_wait;
 };
 
 enum snd_ff_clock_src {
@@ -122,10 +128,16 @@ int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate);
 void snd_ff_stream_stop_duplex(struct snd_ff *ff);
 void snd_ff_stream_update_duplex(struct snd_ff *ff);
 
+void snd_ff_stream_lock_changed(struct snd_ff *ff);
+int snd_ff_stream_lock_try(struct snd_ff *ff);
+void snd_ff_stream_lock_release(struct snd_ff *ff);
+
 void snd_ff_proc_init(struct snd_ff *ff);
 
 int snd_ff_create_midi_devices(struct snd_ff *ff);
 
 int snd_ff_create_pcm_devices(struct snd_ff *ff);
 
+int snd_ff_create_hwdep_devices(struct snd_ff *ff);
+
 #endif
-- 
2.9.3

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

* [PATCH 13/13] ALSA: fireface: add support for Fireface 400
  2017-02-11 14:24 [RFC v3][PATCH 00/13] ALSA: fireface: new driver for RME Fireface series Takashi Sakamoto
                   ` (11 preceding siblings ...)
  2017-02-11 14:25 ` [PATCH 12/13] ALSA: fireface: add hwdep interface Takashi Sakamoto
@ 2017-02-11 14:25 ` Takashi Sakamoto
  12 siblings, 0 replies; 14+ messages in thread
From: Takashi Sakamoto @ 2017-02-11 14:25 UTC (permalink / raw)
  To: clemens, tiwai; +Cc: alsa-devel, ffado-devel

Fireface 400 is a second model of RME Fireface series, released in 2006.
This commit adds support for this model.

This model supports 8 analog channels, 2 S/PDIF channels and 8 ADAT
channels in both of tx/rx packet. The number of ADAT channels differs
depending on each mode of sampling transmission frequency.

$ python2 linux-firewire-utils/src/crpp < /sys/bus/firewire/devices/fw1/config_rom
               ROM header and bus information block
               -----------------------------------------------------------------
400  04107768  bus_info_length 4, crc_length 16, crc 30568 (should be 61311)
404  31333934  bus_name "1394"
408  20009002  irmc 0, cmc 0, isc 1, bmc 0, cyc_clk_acc 0, max_rec 9 (1024)
40c  000a3501  company_id 000a35     |
410  1bd0862a  device_id 011bd0862a  | EUI-64 000a35011bd0862a

               root directory
               -----------------------------------------------------------------
414  000485ec  directory_length 4, crc 34284
418  03000a35  vendor
41c  0c0083c0  node capabilities per IEEE 1394
420  8d000006  --> eui-64 leaf at 438
424  d1000001  --> unit directory at 428

               unit directory at 428
               -----------------------------------------------------------------
428  000314c4  directory_length 3, crc 5316
42c  12000a35  specifier id
430  13000002  version
434  17101800  model

               eui-64 leaf at 438
               -----------------------------------------------------------------
438  000261a8  leaf_length 2, crc 25000
43c  000a3501  company_id 000a35     |
440  1bd0862a  device_id 011bd0862a  | EUI-64 000a35011bd0862a

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/Kconfig                      |   1 +
 sound/firewire/fireface/Makefile            |   2 +-
 sound/firewire/fireface/ff-protocol-ff400.c | 371 ++++++++++++++++++++++++++++
 sound/firewire/fireface/ff.c                |  21 ++
 sound/firewire/fireface/ff.h                |   2 +
 5 files changed, 396 insertions(+), 1 deletion(-)
 create mode 100644 sound/firewire/fireface/ff-protocol-ff400.c

diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig
index 806d234..fc0a70c 100644
--- a/sound/firewire/Kconfig
+++ b/sound/firewire/Kconfig
@@ -159,5 +159,6 @@ config SND_FIREFACE
 	select SND_HWDEP
 	help
 	 Say Y here to include support for RME fireface series.
+	  * Fireface 400
 
 endif # SND_FIREWIRE
diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile
index 8d6c612..8f80728 100644
--- a/sound/firewire/fireface/Makefile
+++ b/sound/firewire/fireface/Makefile
@@ -1,3 +1,3 @@
 snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \
-		     ff-stream.o ff-pcm.o ff-hwdep.o
+		     ff-stream.o ff-pcm.o ff-hwdep.o ff-protocol-ff400.o
 obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o
diff --git a/sound/firewire/fireface/ff-protocol-ff400.c b/sound/firewire/fireface/ff-protocol-ff400.c
new file mode 100644
index 0000000..fcec6de
--- /dev/null
+++ b/sound/firewire/fireface/ff-protocol-ff400.c
@@ -0,0 +1,371 @@
+/*
+ * ff-protocol-ff400.c - a part of driver for RME Fireface series
+ *
+ * Copyright (c) 2015-2017 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include <linux/delay.h>
+#include "ff.h"
+
+#define FF400_STF		0x000080100500ull
+#define FF400_RX_PACKET_FORMAT	0x000080100504ull
+#define FF400_ISOC_COMM_START	0x000080100508ull
+#define FF400_TX_PACKET_FORMAT	0x00008010050cull
+#define FF400_ISOC_COMM_STOP	0x000080100510ull
+#define FF400_SYNC_STATUS	0x0000801c0000ull
+#define FF400_FETCH_PCM_FRAMES	0x0000801c0000ull	/* For block request. */
+#define FF400_CLOCK_CONFIG	0x0000801c0004ull
+
+#define FF400_MIDI_HIGH_ADDR	0x0000801003f4ull
+#define FF400_MIDI_RX_PORT_0	0x000080180000ull
+#define FF400_MIDI_RX_PORT_1	0x000080190000ull
+
+static int ff400_get_clock(struct snd_ff *ff, unsigned int *rate,
+			   enum snd_ff_clock_src *src)
+{
+	__le32 reg;
+	u32 data;
+	int err;
+
+	err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST,
+				 FF400_SYNC_STATUS, &reg, sizeof(reg), 0);
+	if (err < 0)
+		return err;
+	data = le32_to_cpu(reg);
+
+	/* Calculate sampling rate. */
+	switch ((data >> 1) & 0x03) {
+	case 0x01:
+		*rate = 32000;
+		break;
+	case 0x00:
+		*rate = 44100;
+		break;
+	case 0x03:
+		*rate = 48000;
+		break;
+	case 0x02:
+	default:
+		return -EIO;
+	}
+
+	if (data & 0x08)
+		*rate *= 2;
+	else if (data & 0x10)
+		*rate *= 4;
+
+	/* Calculate source of clock. */
+	if (data & 0x01) {
+		*src = SND_FF_CLOCK_SRC_INTERNAL;
+	} else {
+		/* TODO: 0x00, 0x01, 0x02, 0x06, 0x07? */
+		switch ((data >> 10) & 0x07) {
+		case 0x03:
+			*src = SND_FF_CLOCK_SRC_SPDIF;
+			break;
+		case 0x04:
+			*src = SND_FF_CLOCK_SRC_WORD;
+			break;
+		case 0x05:
+			*src = SND_FF_CLOCK_SRC_LTC;
+			break;
+		case 0x00:
+		default:
+			*src = SND_FF_CLOCK_SRC_ADAT;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static int ff400_begin_session(struct snd_ff *ff, unsigned int rate)
+{
+	__le32 reg;
+	int i, err;
+
+	/* Check whether the given value is supported or not. */
+	for (i = 0; i < CIP_SFC_COUNT; i++) {
+		if (amdtp_rate_table[i] == rate)
+			break;
+	}
+	if (i == CIP_SFC_COUNT)
+		return -EINVAL;
+
+	/* Set the number of data blocks transferred in a second. */
+	reg = cpu_to_le32(rate);
+	err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
+				 FF400_STF, &reg, sizeof(reg), 0);
+	if (err < 0)
+		return err;
+
+	msleep(100);
+
+	/*
+	 * Set isochronous channel and the number of quadlets of received
+	 * packets.
+	 */
+	reg = cpu_to_le32(((ff->rx_stream.data_block_quadlets << 3) << 8) |
+			  ff->rx_resources.channel);
+	err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
+				 FF400_RX_PACKET_FORMAT, &reg, sizeof(reg), 0);
+	if (err < 0)
+		return err;
+
+	/*
+	 * Set isochronous channel and the number of quadlets of transmitted
+	 * packet.
+	 */
+	/* TODO: investigate the purpose of this 0x80. */
+	reg = cpu_to_le32((0x80 << 24) |
+			  (ff->tx_resources.channel << 5) |
+			  (ff->tx_stream.data_block_quadlets));
+	err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
+				 FF400_TX_PACKET_FORMAT, &reg, sizeof(reg), 0);
+	if (err < 0)
+		return err;
+
+	/* Allow to transmit packets. */
+	reg = cpu_to_le32(0x00000001);
+	return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
+				 FF400_ISOC_COMM_START, &reg, sizeof(reg), 0);
+}
+
+static void ff400_finish_session(struct snd_ff *ff)
+{
+	__le32 reg;
+
+	reg = cpu_to_le32(0x80000000);
+	snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST,
+			   FF400_ISOC_COMM_STOP, &reg, sizeof(reg), 0);
+}
+
+static int ff400_switch_fetching_mode(struct snd_ff *ff, bool enable)
+{
+	__le32 *reg;
+	int i;
+
+	reg = kzalloc(sizeof(__le32) * 18, GFP_KERNEL);
+	if (reg == NULL)
+		return -ENOMEM;
+
+	if (enable) {
+		/*
+		 * Each quadlet is corresponding to data channels in a data
+		 * blocks in reverse order. Precisely, quadlets for available
+		 * data channels should be enabled. Here, I take second best
+		 * to fetch PCM frames from all of data channels regardless of
+		 * stf.
+		 */
+		for (i = 0; i < 18; ++i)
+			reg[i] = cpu_to_le32(0x00000001);
+	}
+
+	return snd_fw_transaction(ff->unit, TCODE_WRITE_BLOCK_REQUEST,
+				  FF400_FETCH_PCM_FRAMES, reg,
+				  sizeof(__le32) * 18, 0);
+}
+
+static void ff400_dump_sync_status(struct snd_ff *ff,
+				   struct snd_info_buffer *buffer)
+{
+	__le32 reg;
+	u32 data;
+	int err;
+
+	err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST,
+				 FF400_SYNC_STATUS, &reg, sizeof(reg), 0);
+	if (err < 0)
+		return;
+
+	data = le32_to_cpu(reg);
+
+	snd_iprintf(buffer, "External source detection:\n");
+
+	snd_iprintf(buffer, "Word Clock:");
+	if ((data >> 24) & 0x20) {
+		if ((data >> 24) & 0x40)
+			snd_iprintf(buffer, "sync\n");
+		else
+			snd_iprintf(buffer, "lock\n");
+	} else {
+		snd_iprintf(buffer, "none\n");
+	}
+
+	snd_iprintf(buffer, "S/PDIF:");
+	if ((data >> 16) & 0x10) {
+		if ((data >> 16) & 0x04)
+			snd_iprintf(buffer, "sync\n");
+		else
+			snd_iprintf(buffer, "lock\n");
+	} else {
+		snd_iprintf(buffer, "none\n");
+	}
+
+	snd_iprintf(buffer, "ADAT:");
+	if ((data >> 8) & 0x04) {
+		if ((data >> 8) & 0x10)
+			snd_iprintf(buffer, "sync\n");
+		else
+			snd_iprintf(buffer, "lock\n");
+	} else {
+		snd_iprintf(buffer, "none\n");
+	}
+
+	snd_iprintf(buffer, "\nUsed external source:\n");
+
+	if (((data >> 22) & 0x07) == 0x07) {
+		snd_iprintf(buffer, "None\n");
+	} else {
+		switch ((data >> 22) & 0x07) {
+		case 0x00:
+			snd_iprintf(buffer, "ADAT:");
+			break;
+		case 0x03:
+			snd_iprintf(buffer, "S/PDIF:");
+			break;
+		case 0x04:
+			snd_iprintf(buffer, "Word:");
+			break;
+		case 0x07:
+			snd_iprintf(buffer, "Nothing:");
+			break;
+		case 0x01:
+		case 0x02:
+		case 0x05:
+		case 0x06:
+		default:
+			snd_iprintf(buffer, "unknown:");
+			break;
+		}
+
+		if ((data >> 25) & 0x07) {
+			switch ((data >> 25) & 0x07) {
+			case 0x01:
+				snd_iprintf(buffer, "32000\n");
+				break;
+			case 0x02:
+				snd_iprintf(buffer, "44100\n");
+				break;
+			case 0x03:
+				snd_iprintf(buffer, "48000\n");
+				break;
+			case 0x04:
+				snd_iprintf(buffer, "64000\n");
+				break;
+			case 0x05:
+				snd_iprintf(buffer, "88200\n");
+				break;
+			case 0x06:
+				snd_iprintf(buffer, "96000\n");
+				break;
+			case 0x07:
+				snd_iprintf(buffer, "128000\n");
+				break;
+			case 0x08:
+				snd_iprintf(buffer, "176400\n");
+				break;
+			case 0x09:
+				snd_iprintf(buffer, "192000\n");
+				break;
+			case 0x00:
+				snd_iprintf(buffer, "unknown\n");
+				break;
+			}
+		}
+	}
+
+	snd_iprintf(buffer, "Multiplied:");
+	snd_iprintf(buffer, "%d\n", (data & 0x3ff) * 250);
+}
+
+static void ff400_dump_clock_config(struct snd_ff *ff,
+				    struct snd_info_buffer *buffer)
+{
+	__le32 reg;
+	u32 data;
+	unsigned int rate;
+	const char *src;
+	int err;
+
+	err = snd_fw_transaction(ff->unit, TCODE_READ_BLOCK_REQUEST,
+				 FF400_CLOCK_CONFIG, &reg, sizeof(reg), 0);
+	if (err < 0)
+		return;
+
+	data = le32_to_cpu(reg);
+
+	snd_iprintf(buffer, "Output S/PDIF format: %s (Emphasis: %s)\n",
+		    (data & 0x20) ? "Professional" : "Consumer",
+		    (data & 0x40) ? "on" : "off");
+
+	snd_iprintf(buffer, "Optical output interface format: %s\n",
+		    ((data >> 8) & 0x01) ? "S/PDIF" : "ADAT");
+
+	snd_iprintf(buffer, "Word output single speed: %s\n",
+		    ((data >> 8) & 0x20) ? "on" : "off");
+
+	snd_iprintf(buffer, "S/PDIF input interface: %s\n",
+		    ((data >> 8) & 0x02) ? "Optical" : "Coaxial");
+
+	switch ((data >> 1) & 0x03) {
+	case 0x01:
+		rate = 32000;
+		break;
+	case 0x00:
+		rate = 44100;
+		break;
+	case 0x03:
+		rate = 48000;
+		break;
+	case 0x02:
+	default:
+		return;
+	}
+
+	if (data & 0x08)
+		rate *= 2;
+	else if (data & 0x10)
+		rate *= 4;
+
+	snd_iprintf(buffer, "Sampling rate: %d\n", rate);
+
+	if (data & 0x01) {
+		src = "Internal";
+	} else {
+		switch ((data >> 10) & 0x07) {
+		case 0x00:
+			src = "ADAT";
+			break;
+		case 0x03:
+			src = "S/PDIF";
+			break;
+		case 0x04:
+			src = "Word";
+			break;
+		case 0x05:
+			src = "LTC";
+			break;
+		default:
+			return;
+		}
+	}
+
+	snd_iprintf(buffer, "Sync to clock source: %s\n", src);
+}
+
+struct snd_ff_protocol snd_ff_protocol_ff400 = {
+	.get_clock		= ff400_get_clock,
+	.begin_session		= ff400_begin_session,
+	.finish_session		= ff400_finish_session,
+	.switch_fetching_mode	= ff400_switch_fetching_mode,
+
+	.dump_sync_status	= ff400_dump_sync_status,
+	.dump_clock_config	= ff400_dump_clock_config,
+
+	.midi_high_addr_reg	= FF400_MIDI_HIGH_ADDR,
+	.midi_rx_port_0_reg	= FF400_MIDI_RX_PORT_0,
+	.midi_rx_port_1_reg	= FF400_MIDI_RX_PORT_1,
+};
diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c
index f57b434..3f66fa3 100644
--- a/sound/firewire/fireface/ff.c
+++ b/sound/firewire/fireface/ff.c
@@ -157,7 +157,28 @@ static void snd_ff_remove(struct fw_unit *unit)
 	}
 }
 
+static struct snd_ff_spec spec_ff400 = {
+	.name = "Fireface400",
+	.pcm_capture_channels = {18, 14, 10},
+	.pcm_playback_channels = {18, 14, 10},
+	.midi_in_ports = 1,
+	.midi_out_ports = 1,
+	.protocol = &snd_ff_protocol_ff400,
+};
+
 static const struct ieee1394_device_id snd_ff_id_table[] = {
+	/* Fireface 400 */
+	{
+		.match_flags	= IEEE1394_MATCH_VENDOR_ID |
+				  IEEE1394_MATCH_SPECIFIER_ID |
+				  IEEE1394_MATCH_VERSION |
+				  IEEE1394_MATCH_MODEL_ID,
+		.vendor_id	= OUI_RME,
+		.specifier_id	= 0x000a35,
+		.version	= 0x000002,
+		.model_id	= 0x101800,
+		.driver_data	= (kernel_ulong_t)&spec_ff400,
+	},
 	{}
 };
 MODULE_DEVICE_TABLE(ieee1394, snd_ff_id_table);
diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h
index 16839b7..ccc0aa2 100644
--- a/sound/firewire/fireface/ff.h
+++ b/sound/firewire/fireface/ff.h
@@ -111,6 +111,8 @@ struct snd_ff_protocol {
 	u64 midi_rx_port_1_reg;
 };
 
+extern struct snd_ff_protocol snd_ff_protocol_ff400;
+
 int snd_ff_transaction_register(struct snd_ff *ff);
 int snd_ff_transaction_reregister(struct snd_ff *ff);
 void snd_ff_transaction_unregister(struct snd_ff *ff);
-- 
2.9.3

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

end of thread, other threads:[~2017-02-11 14:25 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-02-11 14:24 [RFC v3][PATCH 00/13] ALSA: fireface: new driver for RME Fireface series Takashi Sakamoto
2017-02-11 14:24 ` [PATCH 01/13] ALSA: fireface: add skeleton " Takashi Sakamoto
2017-02-11 14:24 ` [PATCH 02/13] ALSA: fireface: postpone sound card registration Takashi Sakamoto
2017-02-11 14:24 ` [PATCH 03/13] ALSA: fireface: add model specific structure Takashi Sakamoto
2017-02-11 14:25 ` [PATCH 04/13] ALSA: fireface: add an abstraction layer for model-specific protocols Takashi Sakamoto
2017-02-11 14:25 ` [PATCH 05/13] ALSA: fireface: add transaction support Takashi Sakamoto
2017-02-11 14:25 ` [PATCH 06/13] ALSA: fireface: add support for MIDI functionality Takashi Sakamoto
2017-02-11 14:25 ` [PATCH 07/13] ALSA: fireface: add proc node to help debugging Takashi Sakamoto
2017-02-11 14:25 ` [PATCH 08/13] ALSA: firewire-lib: add no-header packet processing Takashi Sakamoto
2017-02-11 14:25 ` [PATCH 09/13] ALSA: fireface: add unique data processing layer Takashi Sakamoto
2017-02-11 14:25 ` [PATCH 10/13] ALSA: fireface: add stream management functionality Takashi Sakamoto
2017-02-11 14:25 ` [PATCH 11/13] ALSA: fireface: add support for PCM functionality Takashi Sakamoto
2017-02-11 14:25 ` [PATCH 12/13] ALSA: fireface: add hwdep interface Takashi Sakamoto
2017-02-11 14:25 ` [PATCH 13/13] ALSA: fireface: add support for Fireface 400 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.