alsa-devel.alsa-project.org archive mirror
 help / color / mirror / Atom feed
* [RFC v2][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family
@ 2015-03-29 15:05 Takashi Sakamoto
  2015-03-29 15:05 ` [PATCH 01/11] ALSA: digi00x: add skeleton for Digi 002/003 device driver Takashi Sakamoto
                   ` (11 more replies)
  0 siblings, 12 replies; 15+ messages in thread
From: Takashi Sakamoto @ 2015-03-29 15:05 UTC (permalink / raw)
  To: clemens; +Cc: damien, robin, alsa-devel, ffado-devel

This patchset is revised version of my previous set.

[alsa-devel] [RFC][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family
http://mailman.alsa-project.org/pipermail/alsa-devel/2015-March/089141.html

Improvements from the previous set:
 * improve double-oh-three (DOT) application
 * extend hwdep interface for asynchronous messaging
 * limit sampling rate for PCM substream when clock source is external or
   any PCM substreams are running
 * support s16 samples for PCM playback

In this time, I test PCM functionality with my test programs. As a result,
I can confirm that S/PDIF and ADAT output from my Digi 002 Rack includes
expected PCM samples as my programs generates. I think we can judge the
implementation of DOT is proper for Digi 002 rack.

Currently, several issues remains:
 * The port for MIDI machine control message is not tested yet, because
   002/003 console model are required.
 * When allocates 2 or more channel numbers for the device, after 15 to 20
   seconds from playbacking, any PCM samples causes noisy sound. Then, all
   of LED on the front panel light. The streaming still continues correctly.
 * The actual effects of external clock source is not clear. When set the
   clock source is somewhat external, even if stopping the clock source,
   the device continues to sound PCM samples against my expectation.
 * The meaning of asynchronous messages is unknown. This patchset adds
   a functionality to receive it in userspace. You can test it with
   updated libhinawa sample script.
   https://github.com/takaswie/libhinawa

Well, I'll stop my work for this driver in this developing period
(for Linux 4.0), because this work is not in my original plan. I hope any
users test this driver and report the result till Linux 4.2 merge window.
If not, this driver will not be merged into upstream, naturally.

I prepare for DKMS package to test this patchset. Please use it if needed.
https://github.com/takaswie/snd-firewire-improve

Takashi Sakamoto (11):
  ALSA: digi00x: add skeleton for Digi 002/003 device driver
  ALSA: digi00x: add streaming functionality
  ALSA: digi00x: add proc node for clock status
  ALSA: digi00x: add PCM functionality
  ALSA: digi00x: add MIDI functionality
  ALSA: digi00x: add hwdep interface
  ALSA: digi00x: support unknown asynchronous message
  ALSA: digi00x: support MIDI ports for device control
  ALSA: firewire-lib: allows to implement external MIDI callback function
  ALSA: digi00x: improve MIDI capture/playback
  ALSA: digi00x: apply double-oh-three algorithm to multiplex PCM samples

 include/uapi/sound/asound.h               |   3 +-
 include/uapi/sound/firewire.h             |   8 +
 sound/firewire/Kconfig                    |  10 +
 sound/firewire/Makefile                   |   1 +
 sound/firewire/amdtp.c                    |  30 ++-
 sound/firewire/amdtp.h                    |   5 +
 sound/firewire/digi00x/Makefile           |   3 +
 sound/firewire/digi00x/digi00x-hwdep.c    | 200 +++++++++++++++
 sound/firewire/digi00x/digi00x-midi.c     | 199 +++++++++++++++
 sound/firewire/digi00x/digi00x-pcm.c      | 344 +++++++++++++++++++++++++
 sound/firewire/digi00x/digi00x-proc.c     |  86 +++++++
 sound/firewire/digi00x/digi00x-protocol.c | 406 ++++++++++++++++++++++++++++++
 sound/firewire/digi00x/digi00x-stream.c   | 393 +++++++++++++++++++++++++++++
 sound/firewire/digi00x/digi00x.c          | 179 +++++++++++++
 sound/firewire/digi00x/digi00x.h          | 167 ++++++++++++
 15 files changed, 2026 insertions(+), 8 deletions(-)
 create mode 100644 sound/firewire/digi00x/Makefile
 create mode 100644 sound/firewire/digi00x/digi00x-hwdep.c
 create mode 100644 sound/firewire/digi00x/digi00x-midi.c
 create mode 100644 sound/firewire/digi00x/digi00x-pcm.c
 create mode 100644 sound/firewire/digi00x/digi00x-proc.c
 create mode 100644 sound/firewire/digi00x/digi00x-protocol.c
 create mode 100644 sound/firewire/digi00x/digi00x-stream.c
 create mode 100644 sound/firewire/digi00x/digi00x.c
 create mode 100644 sound/firewire/digi00x/digi00x.h

-- 
2.1.0

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

* [PATCH 01/11] ALSA: digi00x: add skeleton for Digi 002/003 device driver
  2015-03-29 15:05 [RFC v2][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family Takashi Sakamoto
@ 2015-03-29 15:05 ` Takashi Sakamoto
  2015-03-29 15:05 ` [PATCH 02/11] ALSA: digi00x: add streaming functionality Takashi Sakamoto
                   ` (10 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Takashi Sakamoto @ 2015-03-29 15:05 UTC (permalink / raw)
  To: clemens; +Cc: damien, robin, alsa-devel, ffado-devel

This commit adds a new driver for Digidesign 002/003 family. Currently
this driver just creates/removes card instance according to bus event.

Digidesign 002/003 family consists of:
 * Agere FW802B for IEEE 1394 PHY layer
 * PDI 1394L40 for IEEE 1394 LINK layer and IEC 61883 interface
 * ALTERA ACEX EP1K50 for IEC 61883 layer and DSP controller
 * ADSP-21065L for signal processing

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/Kconfig           |   8 +++
 sound/firewire/Makefile          |   1 +
 sound/firewire/digi00x/Makefile  |   2 +
 sound/firewire/digi00x/digi00x.c | 136 +++++++++++++++++++++++++++++++++++++++
 sound/firewire/digi00x/digi00x.h |  33 ++++++++++
 5 files changed, 180 insertions(+)
 create mode 100644 sound/firewire/digi00x/Makefile
 create mode 100644 sound/firewire/digi00x/digi00x.c
 create mode 100644 sound/firewire/digi00x/digi00x.h

diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig
index ecec547..594b6d1 100644
--- a/sound/firewire/Kconfig
+++ b/sound/firewire/Kconfig
@@ -118,4 +118,12 @@ config SND_BEBOB
           To compile this driver as a module, choose M here: the module
           will be called snd-bebob.
 
+config SND_DIGI00X
+	tristate "Digidesign 002/003 family support"
+	help
+	 Say Y here to include support for Digidesign 002/003 family.
+
+         To compile this driver as a module, choose M here: the module
+         will be called snd-digi00x.
+
 endif # SND_FIREWIRE
diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile
index 8b37f08..9b99ab2 100644
--- a/sound/firewire/Makefile
+++ b/sound/firewire/Makefile
@@ -11,3 +11,4 @@ obj-$(CONFIG_SND_ISIGHT) += snd-isight.o
 obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o
 obj-$(CONFIG_SND_FIREWORKS) += fireworks/
 obj-$(CONFIG_SND_BEBOB) += bebob/
+obj-$(CONFIG_SND_DIGI00X) += digi00x/
diff --git a/sound/firewire/digi00x/Makefile b/sound/firewire/digi00x/Makefile
new file mode 100644
index 0000000..e30e233
--- /dev/null
+++ b/sound/firewire/digi00x/Makefile
@@ -0,0 +1,2 @@
+snd-digi00x-objs := digi00x.o
+obj-m += snd-digi00x.o
diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c
new file mode 100644
index 0000000..6f427a2
--- /dev/null
+++ b/sound/firewire/digi00x/digi00x.c
@@ -0,0 +1,136 @@
+/*
+ * digi00x.c - a part of driver for Digidesign Digi 002/003 family
+ *
+ * Copyright (c) 2014-2015 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "digi00x.h"
+
+MODULE_DESCRIPTION("Digidesign 002/003 Driver");
+MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
+MODULE_LICENSE("GPL v2");
+
+#define VENDOR_DIGIDESIGN	0x00a07e
+#define MODEL_DIGI00X		0x000002
+
+static int name_card(struct snd_dg00x *dg00x)
+{
+	struct fw_device *fw_dev = fw_parent_device(dg00x->unit);
+	char name[32] = {0};
+	char *model;
+	int err;
+
+	err = fw_csr_string(dg00x->unit->directory, CSR_MODEL, name,
+			    sizeof(name));
+	if (err < 0)
+		return err;
+
+	model = name;
+	if (model[0] == ' ')
+		model = strchr(model, ' ') + 1;
+
+	strcpy(dg00x->card->driver, "Digi00x");
+	strcpy(dg00x->card->shortname, model);
+	strcpy(dg00x->card->mixername, model);
+	snprintf(dg00x->card->longname, sizeof(dg00x->card->longname),
+		 "Digidesign %s, GUID %08x%08x at %s, S%d", model,
+		 cpu_to_be32(fw_dev->config_rom[3]),
+		 cpu_to_be32(fw_dev->config_rom[4]),
+		 dev_name(&dg00x->unit->device), 100 << fw_dev->max_speed);
+
+	return 0;
+}
+
+static void dg00x_card_free(struct snd_card *card)
+{
+	struct snd_dg00x *dg00x = card->private_data;
+
+	fw_unit_put(dg00x->unit);
+
+	mutex_destroy(&dg00x->mutex);
+}
+
+static int snd_dg00x_probe(struct fw_unit *unit,
+			   const struct ieee1394_device_id *entry)
+{
+	struct snd_card *card;
+	struct snd_dg00x *dg00x;
+	int err;
+
+	/* create card */
+	err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE,
+			   sizeof(struct snd_dg00x), &card);
+	if (err < 0)
+		return err;
+	card->private_free = dg00x_card_free;
+
+	/* initialize myself */
+	dg00x = card->private_data;
+	dg00x->card = card;
+	dg00x->unit = fw_unit_get(unit);
+
+	mutex_init(&dg00x->mutex);
+	spin_lock_init(&dg00x->lock);
+
+	err = name_card(dg00x);
+	if (err < 0)
+		goto error;
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto error;
+
+	dev_set_drvdata(&unit->device, dg00x);
+
+	return err;
+error:
+	snd_card_free(card);
+	return err;
+}
+
+static void snd_dg00x_remove(struct fw_unit *unit)
+{
+	struct snd_dg00x *dg00x = dev_get_drvdata(&unit->device);
+
+	/* No need to wait for releasing card object in this context. */
+	snd_card_free_when_closed(dg00x->card);
+}
+
+
+static const struct ieee1394_device_id snd_dg00x_id_table[] = {
+	/* Both of 002/003 use the same ID. */
+	{
+		.match_flags = IEEE1394_MATCH_VENDOR_ID |
+			       IEEE1394_MATCH_MODEL_ID,
+		.vendor_id = VENDOR_DIGIDESIGN,
+		.model_id = MODEL_DIGI00X,
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(ieee1394, snd_dg00x_id_table);
+
+static struct fw_driver dg00x_driver = {
+	.driver = {
+		.owner = THIS_MODULE,
+		.name = "snd-digi00x",
+		.bus = &fw_bus_type,
+	},
+	.probe    = snd_dg00x_probe,
+	.remove   = snd_dg00x_remove,
+	.id_table = snd_dg00x_id_table,
+};
+
+static int __init snd_dg00x_init(void)
+{
+	return driver_register(&dg00x_driver.driver);
+}
+
+static void __exit snd_dg00x_exit(void)
+{
+	driver_unregister(&dg00x_driver.driver);
+}
+
+module_init(snd_dg00x_init);
+module_exit(snd_dg00x_exit);
diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h
new file mode 100644
index 0000000..59425cd
--- /dev/null
+++ b/sound/firewire/digi00x/digi00x.h
@@ -0,0 +1,33 @@
+/*
+ * digi00x.h - a part of driver for Digidesign Digi 002/003 family
+ *
+ * Copyright (c) 2014-2015 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#ifndef SOUND_DIGI00X_H_INCLUDED
+#define SOUND_DIGI00X_H_INCLUDED
+
+#include <linux/compat.h>
+#include <linux/device.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+
+struct snd_dg00x {
+	struct snd_card *card;
+	struct fw_unit *unit;
+	int card_index;
+
+	struct mutex mutex;
+	spinlock_t lock;
+};
+
+#endif
-- 
2.1.0

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

* [PATCH 02/11] ALSA: digi00x: add streaming functionality
  2015-03-29 15:05 [RFC v2][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family Takashi Sakamoto
  2015-03-29 15:05 ` [PATCH 01/11] ALSA: digi00x: add skeleton for Digi 002/003 device driver Takashi Sakamoto
@ 2015-03-29 15:05 ` Takashi Sakamoto
  2015-03-29 15:05 ` [PATCH 03/11] ALSA: digi00x: add proc node for clock status Takashi Sakamoto
                   ` (9 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Takashi Sakamoto @ 2015-03-29 15:05 UTC (permalink / raw)
  To: clemens; +Cc: damien, robin, alsa-devel, ffado-devel

This commit adds a functionality to manage streaming and to handle packets.

As long as seeing Digi 002, the device uses usual CIP packets in IEC
61883-1/6. But the streaming is not controlled by CMP. It's controlled
by writing to certain addresses.

In Digi 002, several clock sources are available, while there're no
differences for packets in different clock sources. The value of SYT field
in transferred packets is always zero. Thus, streams in both direction
don't build synchronization. So this driver ignores the synchronization.

And the device always requires received-stream to transmit packets, thus
this driver transmits packets for both direction.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/Kconfig                  |   1 +
 sound/firewire/digi00x/Makefile         |   2 +-
 sound/firewire/digi00x/digi00x-stream.c | 349 ++++++++++++++++++++++++++++++++
 sound/firewire/digi00x/digi00x.c        |  16 ++
 sound/firewire/digi00x/digi00x.h        |  75 +++++++
 5 files changed, 442 insertions(+), 1 deletion(-)
 create mode 100644 sound/firewire/digi00x/digi00x-stream.c

diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig
index 594b6d1..6b9b0a1 100644
--- a/sound/firewire/Kconfig
+++ b/sound/firewire/Kconfig
@@ -120,6 +120,7 @@ config SND_BEBOB
 
 config SND_DIGI00X
 	tristate "Digidesign 002/003 family support"
+	select SND_FIREWIRE_LIB
 	help
 	 Say Y here to include support for Digidesign 002/003 family.
 
diff --git a/sound/firewire/digi00x/Makefile b/sound/firewire/digi00x/Makefile
index e30e233..fe8fceb 100644
--- a/sound/firewire/digi00x/Makefile
+++ b/sound/firewire/digi00x/Makefile
@@ -1,2 +1,2 @@
-snd-digi00x-objs := digi00x.o
+snd-digi00x-objs := digi00x.o digi00x-stream.o
 obj-m += snd-digi00x.o
diff --git a/sound/firewire/digi00x/digi00x-stream.c b/sound/firewire/digi00x/digi00x-stream.c
new file mode 100644
index 0000000..1d5ef01
--- /dev/null
+++ b/sound/firewire/digi00x/digi00x-stream.c
@@ -0,0 +1,349 @@
+/*
+ * digi00x-stream.c - a part of driver for Digidesign Digi 002/003 family
+ *
+ * Copyright (c) 2014-2015 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "digi00x.h"
+
+#define CALLBACK_TIMEOUT 500
+
+const unsigned int snd_dg00x_stream_rates[SND_DG00X_RATE_COUNT] = {
+	[0] = 44100,
+	[1] = 48000,
+	[2] = 88200,
+	[3] = 96000,
+};
+
+/* Multi Bit Linear Audio data channels for each sampling transfer frequency. */
+const unsigned int
+snd_dg00x_stream_mbla_data_channels[SND_DG00X_RATE_COUNT] = {
+	/* Analog/ADAT/SPDIF */
+	[0] = (8 + 8 + 2),
+	[1] = (8 + 8 + 2),
+	/* Analog/SPDIF */
+	[2] = (8 + 2),
+	[3] = (8 + 2),
+};
+
+int snd_dg00x_stream_get_rate(struct snd_dg00x *dg00x, unsigned int *rate)
+{
+	__be32 data;
+	int err;
+
+	err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST,
+				 DG00X_ADDR_BASE + DG00X_OFFSET_RATE_GET,
+				 &data, sizeof(data), 0);
+	if (err < 0)
+		goto end;
+
+	data = be32_to_cpu(data) & 0x0f;
+	if (data >= ARRAY_SIZE(snd_dg00x_stream_rates)) {
+		err = -EIO;
+		goto end;
+	}
+
+	*rate = snd_dg00x_stream_rates[data];
+end:
+	return err;
+}
+
+int snd_dg00x_stream_set_rate(struct snd_dg00x *dg00x, unsigned int rate)
+{
+	__be32 data;
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(snd_dg00x_stream_rates); i++) {
+		if (rate == snd_dg00x_stream_rates[i])
+			break;
+	}
+	if (i == ARRAY_SIZE(snd_dg00x_stream_rates))
+		return -EIO;
+
+	data = cpu_to_be32(i);
+	return snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST,
+				  DG00X_ADDR_BASE + DG00X_OFFSET_RATE_SET,
+				  &data, sizeof(data), 0);
+}
+
+int snd_dg00x_stream_get_clock(struct snd_dg00x *dg00x,
+			       enum snd_dg00x_clock *clock)
+{
+	__be32 data;
+	int err;
+
+	err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST,
+				 DG00X_ADDR_BASE + DG00X_OFFSET_CLOCK_SOURCE,
+				 &data, sizeof(data), 0);
+	if (err < 0)
+		return err;
+
+	*clock = be32_to_cpu(data) & 0x0f;
+	if (*clock >= SND_DG00X_CLOCK_COUNT)
+		err = -EIO;
+
+	return err;
+}
+
+static void finish_session(struct snd_dg00x *dg00x)
+{
+	__be32 data = cpu_to_be32(0x00000003);
+
+	snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST,
+			   DG00X_ADDR_BASE + DG00X_OFFSET_STREAMING_SET,
+			   &data, sizeof(data), 0);
+}
+
+static int begin_session(struct snd_dg00x *dg00x)
+{
+	__be32 data;
+	u32 curr;
+	int err;
+
+	err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST,
+				 DG00X_ADDR_BASE + DG00X_OFFSET_STREAMING_STATE,
+				 &data, sizeof(data), 0);
+	if (err < 0)
+		goto error;
+	curr = be32_to_cpu(data);
+
+	if (curr == 0)
+		curr = 2;
+
+	curr--;
+	while (curr > 0) {
+		data = cpu_to_be32(curr);
+		err = snd_fw_transaction(dg00x->unit,
+					 TCODE_WRITE_QUADLET_REQUEST,
+					 DG00X_ADDR_BASE +
+					 DG00X_OFFSET_STREAMING_SET,
+					 &data, sizeof(data), 0);
+		if (err < 0)
+			goto error;
+
+		msleep(20);
+		curr--;
+	}
+
+	return 0;
+error:
+	finish_session(dg00x);
+	return err;
+}
+
+static void release_resources(struct snd_dg00x *dg00x)
+{
+	__be32 data = 0;
+
+	/* Unregister isochronous channels for both direction. */
+	snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST,
+			   DG00X_ADDR_BASE + DG00X_OFFSET_ISOC_CHANNELS,
+			   &data, sizeof(data), 0);
+
+	/* Release isochronous resources. */
+	fw_iso_resources_free(&dg00x->tx_resources);
+	fw_iso_resources_free(&dg00x->rx_resources);
+}
+
+static int keep_resources(struct snd_dg00x *dg00x, unsigned int rate)
+{
+	unsigned int i, c;
+	__be32 data;
+	int err;
+
+	/* Check sampling rate. */
+	for (i = 0; i < SND_DG00X_RATE_COUNT; i++) {
+		if (snd_dg00x_stream_rates[i] == rate)
+			break;
+	}
+	if (i == SND_DG00X_RATE_COUNT)
+		return -EINVAL;
+
+	/* Keep resources for out-stream. */
+	amdtp_stream_set_parameters(&dg00x->rx_stream, rate,
+				    snd_dg00x_stream_mbla_data_channels[i], 2);
+	err = fw_iso_resources_allocate(&dg00x->rx_resources,
+				amdtp_stream_get_max_payload(&dg00x->rx_stream),
+				fw_parent_device(dg00x->unit)->max_speed);
+	if (err < 0)
+		return err;
+
+	/* Keep resources for in-stream. */
+	amdtp_stream_set_parameters(&dg00x->tx_stream, rate,
+				    snd_dg00x_stream_mbla_data_channels[i], 1);
+	err = fw_iso_resources_allocate(&dg00x->tx_resources,
+				amdtp_stream_get_max_payload(&dg00x->tx_stream),
+				fw_parent_device(dg00x->unit)->max_speed);
+	if (err < 0)
+		goto error;
+
+	/* Register isochronous channels for both direction. */
+	data = cpu_to_be32((dg00x->tx_resources.channel << 16) |
+			   dg00x->rx_resources.channel);
+	err = snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST,
+				 DG00X_ADDR_BASE + DG00X_OFFSET_ISOC_CHANNELS,
+				 &data, sizeof(data), 0);
+	if (err < 0)
+		goto error;
+
+	/* The first data channel in a packet is for MIDI conformant data. */
+	dg00x->rx_stream.midi_position = 0;
+	dg00x->tx_stream.midi_position = 0;
+	for (c = 0; c < snd_dg00x_stream_mbla_data_channels[i]; c++) {
+		dg00x->rx_stream.pcm_positions[c] = c + 1;
+		dg00x->tx_stream.pcm_positions[c] = c + 1;
+	}
+
+	return 0;
+error:
+	release_resources(dg00x);
+	return err;
+}
+
+int snd_dg00x_stream_init_duplex(struct snd_dg00x *dg00x)
+{
+	int err;
+
+	/* For out-stream. */
+	err = fw_iso_resources_init(&dg00x->rx_resources, dg00x->unit);
+	if (err < 0)
+		return err;
+	err = amdtp_stream_init(&dg00x->rx_stream, dg00x->unit,
+				AMDTP_OUT_STREAM, CIP_NONBLOCKING);
+
+	/* For in-stream. */
+	err = fw_iso_resources_init(&dg00x->tx_resources, dg00x->unit);
+	if (err < 0)
+		return err;
+	err = amdtp_stream_init(&dg00x->tx_stream, dg00x->unit,
+				AMDTP_IN_STREAM,
+				CIP_BLOCKING | CIP_SKIP_INIT_DBC_CHECK);
+	if (err < 0)
+		amdtp_stream_destroy(&dg00x->rx_stream);
+
+	return err;
+}
+
+/*
+ * This function should be called before starting streams or after stopping
+ * streams.
+ */
+void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x)
+{
+	amdtp_stream_destroy(&dg00x->rx_stream);
+	fw_iso_resources_destroy(&dg00x->rx_resources);
+
+	amdtp_stream_destroy(&dg00x->tx_stream);
+	fw_iso_resources_destroy(&dg00x->tx_resources);
+}
+
+int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x, unsigned int rate)
+{
+	unsigned int curr_rate;
+	int err = 0;
+
+	if (dg00x->playback_substreams == 0 &&
+	    dg00x->capture_substreams == 0)
+		goto end;
+
+	/* Check current sampling rate. */
+	err = snd_dg00x_stream_get_rate(dg00x, &curr_rate);
+	if (err < 0)
+		goto error;
+	if ((curr_rate != rate) |
+	    amdtp_streaming_error(&dg00x->tx_stream) |
+	    amdtp_streaming_error(&dg00x->rx_stream)) {
+		finish_session(dg00x);
+
+		amdtp_stream_stop(&dg00x->tx_stream);
+		amdtp_stream_stop(&dg00x->rx_stream);
+		release_resources(dg00x);
+	}
+
+	/* No streams are transmitted without receiving a stream. */
+	if (!amdtp_stream_running(&dg00x->rx_stream)) {
+		err = snd_dg00x_stream_set_rate(dg00x, rate);
+		if (err < 0)
+			goto error;
+
+		err = keep_resources(dg00x, rate);
+		if (err < 0)
+			goto error;
+
+		err = begin_session(dg00x);
+		if (err < 0)
+			goto error;
+
+		err = amdtp_stream_start(&dg00x->rx_stream,
+				dg00x->rx_resources.channel,
+				fw_parent_device(dg00x->unit)->max_speed);
+		if (err < 0)
+			goto error;
+
+		if (!amdtp_stream_wait_callback(&dg00x->rx_stream,
+						CALLBACK_TIMEOUT)) {
+			err = -ETIMEDOUT;
+			goto error;
+		}
+	}
+
+	/*
+	 * The value of SYT field in transmitted packets is always 0x0000. Thus,
+	 * duplex streams with timestamp synchronization cannot be built.
+	 */
+	if (dg00x->capture_substreams > 0 &&
+	    !amdtp_stream_running(&dg00x->tx_stream)) {
+		err = amdtp_stream_start(&dg00x->tx_stream,
+				dg00x->tx_resources.channel,
+				fw_parent_device(dg00x->unit)->max_speed);
+		if (err < 0)
+			goto error;
+
+		if (!amdtp_stream_wait_callback(&dg00x->tx_stream,
+						CALLBACK_TIMEOUT)) {
+			err = -ETIMEDOUT;
+			goto error;
+		}
+	}
+end:
+	return err;
+error:
+	finish_session(dg00x);
+
+	amdtp_stream_stop(&dg00x->tx_stream);
+	amdtp_stream_stop(&dg00x->rx_stream);
+	release_resources(dg00x);
+
+	return err;
+}
+
+void snd_dg00x_stream_stop_duplex(struct snd_dg00x *dg00x)
+{
+	if (dg00x->capture_substreams > 0)
+		return;
+	amdtp_stream_stop(&dg00x->tx_stream);
+
+	if (dg00x->playback_substreams > 0)
+		return;
+	amdtp_stream_stop(&dg00x->rx_stream);
+	finish_session(dg00x);
+	release_resources(dg00x);
+
+	/*
+	 * Just after finishing the session, the device may lost transmitting
+	 * functionality for a short time.
+	 */
+	msleep(50);
+}
+
+/* TODO: investigation. */
+void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x)
+{
+	fw_iso_resources_update(&dg00x->tx_resources);
+	fw_iso_resources_update(&dg00x->rx_resources);
+
+	amdtp_stream_update(&dg00x->tx_stream);
+	amdtp_stream_update(&dg00x->rx_stream);
+}
diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c
index 6f427a2..b82bce7 100644
--- a/sound/firewire/digi00x/digi00x.c
+++ b/sound/firewire/digi00x/digi00x.c
@@ -47,6 +47,8 @@ static void dg00x_card_free(struct snd_card *card)
 {
 	struct snd_dg00x *dg00x = card->private_data;
 
+	snd_dg00x_stream_destroy_duplex(dg00x);
+
 	fw_unit_put(dg00x->unit);
 
 	mutex_destroy(&dg00x->mutex);
@@ -78,6 +80,10 @@ static int snd_dg00x_probe(struct fw_unit *unit,
 	if (err < 0)
 		goto error;
 
+	err = snd_dg00x_stream_init_duplex(dg00x);
+	if (err < 0)
+		goto error;
+
 	err = snd_card_register(card);
 	if (err < 0)
 		goto error;
@@ -90,6 +96,15 @@ error:
 	return err;
 }
 
+static void snd_dg00x_update(struct fw_unit *unit)
+{
+	struct snd_dg00x *dg00x = dev_get_drvdata(&unit->device);
+
+	mutex_lock(&dg00x->mutex);
+	snd_dg00x_stream_update_duplex(dg00x);
+	mutex_unlock(&dg00x->mutex);
+}
+
 static void snd_dg00x_remove(struct fw_unit *unit)
 {
 	struct snd_dg00x *dg00x = dev_get_drvdata(&unit->device);
@@ -118,6 +133,7 @@ static struct fw_driver dg00x_driver = {
 		.bus = &fw_bus_type,
 	},
 	.probe    = snd_dg00x_probe,
+	.update   = snd_dg00x_update,
 	.remove   = snd_dg00x_remove,
 	.id_table = snd_dg00x_id_table,
 };
diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h
index 59425cd..2e960c7 100644
--- a/sound/firewire/digi00x/digi00x.h
+++ b/sound/firewire/digi00x/digi00x.h
@@ -21,6 +21,11 @@
 #include <sound/core.h>
 #include <sound/initval.h>
 
+#include "../lib.h"
+#include "../packets-buffer.h"
+#include "../iso-resources.h"
+#include "../amdtp.h"
+
 struct snd_dg00x {
 	struct snd_card *card;
 	struct fw_unit *unit;
@@ -28,6 +33,76 @@ struct snd_dg00x {
 
 	struct mutex mutex;
 	spinlock_t lock;
+
+	struct amdtp_stream tx_stream;
+	struct fw_iso_resources tx_resources;
+
+	struct amdtp_stream rx_stream;
+	struct fw_iso_resources rx_resources;
+
+	unsigned int playback_substreams;
+	unsigned int capture_substreams;
 };
 
+#define DG00X_ADDR_BASE		0xffffe0000000ull
+
+#define DG00X_OFFSET_STREAMING_STATE	0x0000
+#define DG00X_OFFSET_STREAMING_SET	0x0004
+#define DG00X_OFFSET_MIDI_CTL_ADDR	0x0008
+/* For LSB of the address		0x000c */
+/* unknown				0x0010 */
+#define DG00X_OFFSET_MESSAGE_ADDR	0x0014
+/* For LSB of the address		0x0018 */
+/* unknown				0x001c */
+/* unknown				0x0020 */
+/* not used			0x0024--0x00ff */
+#define DG00X_OFFSET_ISOC_CHANNELS	0x0100
+/* unknown				0x0104 */
+/* unknown				0x0118 */
+/* unknown				0x010c */
+#define DG00X_OFFSET_RATE_SET		0x0110
+#define DG00X_OFFSET_RATE_GET		0x0114
+#define DG00X_OFFSET_CLOCK_SOURCE	0x0118
+#define DG00X_OFFSET_OPT_IFACE_MODE	0x011c
+/* unknown				0x0120 */
+/* unknown				0x0124 */
+/* unknown				0x0128 */
+/* unknown				0x012c */
+/* unknown				0x0138 */
+
+enum snd_dg00x_rate {
+	SND_DG00X_RATE_44100 = 0,
+	SND_DG00X_RATE_48000,
+	SND_DG00X_RATE_88200,
+	SND_DG00X_RATE_96000,
+	SND_DG00X_RATE_COUNT,
+};
+
+enum snd_dg00x_clock {
+	SND_DG00X_CLOCK_INTERNAL = 0,
+	SND_DG00X_CLOCK_SPDIF,
+	SND_DG00X_CLOCK_ADAT,
+	SND_DG00X_CLOCK_WORD,
+	SND_DG00X_CLOCK_COUNT,
+};
+
+enum snd_dg00x_optical_mode {
+	SND_DG00X_OPT_IFACE_MODE_ADAT = 0,
+	SND_DG00X_OPT_IFACE_MODE_SPDIF,
+	SND_DG00X_OPT_IFACE_MODE_COUNT,
+};
+
+extern const unsigned int snd_dg00x_stream_rates[SND_DG00X_RATE_COUNT];
+extern const unsigned int
+snd_dg00x_stream_mbla_data_channels[SND_DG00X_RATE_COUNT];
+int snd_dg00x_stream_get_rate(struct snd_dg00x *dg00x, unsigned int *rate);
+int snd_dg00x_stream_set_rate(struct snd_dg00x *dg00x, unsigned int rate);
+int snd_dg00x_stream_get_clock(struct snd_dg00x *dg00x,
+			       enum snd_dg00x_clock *clock);
+int snd_dg00x_stream_init_duplex(struct snd_dg00x *dg00x);
+int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x, unsigned int rate);
+void snd_dg00x_stream_stop_duplex(struct snd_dg00x *dg00x);
+void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x);
+void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x);
+
 #endif
-- 
2.1.0

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

* [PATCH 03/11] ALSA: digi00x: add proc node for clock status
  2015-03-29 15:05 [RFC v2][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family Takashi Sakamoto
  2015-03-29 15:05 ` [PATCH 01/11] ALSA: digi00x: add skeleton for Digi 002/003 device driver Takashi Sakamoto
  2015-03-29 15:05 ` [PATCH 02/11] ALSA: digi00x: add streaming functionality Takashi Sakamoto
@ 2015-03-29 15:05 ` Takashi Sakamoto
  2015-03-29 15:05 ` [PATCH 04/11] ALSA: digi00x: add PCM functionality Takashi Sakamoto
                   ` (8 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Takashi Sakamoto @ 2015-03-29 15:05 UTC (permalink / raw)
  To: clemens; +Cc: damien, robin, alsa-devel, ffado-devel

This commit adds proc node to check current clock status for debugging.

The devices seem to be able to synchronize external clock source such
as S/PDIF, ADAT or word-clock. But even if setting the registers
related to clock source as one of externals, the device can work
without plug-in these sources actually.

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

diff --git a/sound/firewire/digi00x/Makefile b/sound/firewire/digi00x/Makefile
index fe8fceb..aaec823 100644
--- a/sound/firewire/digi00x/Makefile
+++ b/sound/firewire/digi00x/Makefile
@@ -1,2 +1,2 @@
-snd-digi00x-objs := digi00x.o digi00x-stream.o
+snd-digi00x-objs := digi00x.o digi00x-stream.o digi00x-proc.o
 obj-m += snd-digi00x.o
diff --git a/sound/firewire/digi00x/digi00x-proc.c b/sound/firewire/digi00x/digi00x-proc.c
new file mode 100644
index 0000000..0b14768
--- /dev/null
+++ b/sound/firewire/digi00x/digi00x-proc.c
@@ -0,0 +1,86 @@
+/*
+ * digi00x-proc.c - a part of driver for Digidesign Digi 002/003 family
+ *
+ * Copyright (c) 2014-2015 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "./digi00x.h"
+
+static int get_optical_iface_mode(struct snd_dg00x *dg00x,
+				  enum snd_dg00x_optical_mode *mode)
+{
+	__be32 data;
+	int err;
+
+	err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST,
+				 DG00X_ADDR_BASE + DG00X_OFFSET_OPT_IFACE_MODE,
+				 &data, sizeof(data), 0);
+	if (err >= 0)
+		*mode = be32_to_cpu(data) & 0x01;
+
+	return err;
+}
+
+static void proc_read_clock(struct snd_info_entry *entry,
+			    struct snd_info_buffer *buf)
+{
+	static const char *const source_name[] = {
+		[SND_DG00X_CLOCK_INTERNAL] = "internal",
+		[SND_DG00X_CLOCK_SPDIF] = "s/pdif",
+		[SND_DG00X_CLOCK_ADAT] = "adat",
+		[SND_DG00X_CLOCK_WORD] = "word clock",
+	};
+	static const char *const optical_name[] = {
+		[SND_DG00X_OPT_IFACE_MODE_ADAT] = "adat",
+		[SND_DG00X_OPT_IFACE_MODE_SPDIF] = "s/pdif",
+	};
+	struct snd_dg00x *dg00x = entry->private_data;
+	unsigned int rate;
+	enum snd_dg00x_clock clock;
+	enum snd_dg00x_optical_mode mode;
+
+	if (snd_dg00x_stream_get_rate(dg00x, &rate) < 0)
+		return;
+	if (snd_dg00x_stream_get_clock(dg00x, &clock) < 0)
+		return;
+	if (get_optical_iface_mode(dg00x, &mode) < 0)
+		return;
+
+	snd_iprintf(buf, "Sampling Rate: %d\n", rate);
+	snd_iprintf(buf, "Clock Source: %s\n", source_name[clock]);
+	snd_iprintf(buf, "Optical mode: %s\n", optical_name[mode]);
+}
+
+void snd_dg00x_proc_init(struct snd_dg00x *dg00x)
+{
+	struct snd_info_entry *root, *entry;
+
+	/*
+	 * All nodes are automatically removed at snd_card_disconnect(),
+	 * by following to link list.
+	 */
+	root = snd_info_create_card_entry(dg00x->card, "firewire",
+					  dg00x->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;
+	}
+
+	entry = snd_info_create_card_entry(dg00x->card, "clock", root);
+	if (entry == NULL) {
+		snd_info_free_entry(root);
+		return;
+	}
+
+	snd_info_set_text_ops(entry, dg00x, proc_read_clock);
+	if (snd_info_register(entry) < 0) {
+		snd_info_free_entry(entry);
+		snd_info_free_entry(root);
+	}
+}
diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c
index b82bce7..d9c9e14 100644
--- a/sound/firewire/digi00x/digi00x.c
+++ b/sound/firewire/digi00x/digi00x.c
@@ -84,6 +84,8 @@ static int snd_dg00x_probe(struct fw_unit *unit,
 	if (err < 0)
 		goto error;
 
+	snd_dg00x_proc_init(dg00x);
+
 	err = snd_card_register(card);
 	if (err < 0)
 		goto error;
diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h
index 2e960c7..a16a535 100644
--- a/sound/firewire/digi00x/digi00x.h
+++ b/sound/firewire/digi00x/digi00x.h
@@ -20,6 +20,7 @@
 
 #include <sound/core.h>
 #include <sound/initval.h>
+#include <sound/info.h>
 
 #include "../lib.h"
 #include "../packets-buffer.h"
@@ -105,4 +106,5 @@ void snd_dg00x_stream_stop_duplex(struct snd_dg00x *dg00x);
 void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x);
 void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x);
 
+void snd_dg00x_proc_init(struct snd_dg00x *dg00x);
 #endif
-- 
2.1.0

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

* [PATCH 04/11] ALSA: digi00x: add PCM functionality
  2015-03-29 15:05 [RFC v2][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family Takashi Sakamoto
                   ` (2 preceding siblings ...)
  2015-03-29 15:05 ` [PATCH 03/11] ALSA: digi00x: add proc node for clock status Takashi Sakamoto
@ 2015-03-29 15:05 ` Takashi Sakamoto
  2015-03-29 15:05 ` [PATCH 05/11] ALSA: digi00x: add MIDI functionality Takashi Sakamoto
                   ` (7 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Takashi Sakamoto @ 2015-03-29 15:05 UTC (permalink / raw)
  To: clemens; +Cc: damien, robin, alsa-devel, ffado-devel

As long as seeing Digi 002, any PCM samples are transferred or received
in Multi Bit Linear Audio data channel as AM824 format data. Actually,
the label is always 0x40, thus 24 bit sample is just supported.

But Windows driver transmit 16 bit samples in 0x40 label channel. This is
out of IEC 61883-6. Furthermore, these channels includes some multiplexed
bit pattern.

This commit adds PCM functionality to transmit/receive PCM samples with
current ALSA AMDTP engine, thus PCM playback substream causes noisy sound.

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

diff --git a/sound/firewire/digi00x/Makefile b/sound/firewire/digi00x/Makefile
index aaec823..f2ed472 100644
--- a/sound/firewire/digi00x/Makefile
+++ b/sound/firewire/digi00x/Makefile
@@ -1,2 +1,2 @@
-snd-digi00x-objs := digi00x.o digi00x-stream.o digi00x-proc.o
+snd-digi00x-objs := digi00x.o digi00x-stream.o digi00x-proc.o digi00x-pcm.o
 obj-m += snd-digi00x.o
diff --git a/sound/firewire/digi00x/digi00x-pcm.c b/sound/firewire/digi00x/digi00x-pcm.c
new file mode 100644
index 0000000..33db601
--- /dev/null
+++ b/sound/firewire/digi00x/digi00x-pcm.c
@@ -0,0 +1,330 @@
+/*
+ * digi00x-pcm.c - a part of driver for Digidesign Digi 002/003 family
+ *
+ * Copyright (c) 2014-2015 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "digi00x.h"
+
+static int hw_rule_rate(struct snd_pcm_hw_params *params,
+			struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval *r =
+		hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
+	const struct snd_interval *c =
+		hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_interval t = {
+		.min = UINT_MAX, .max = 0, .integer = 1,
+	};
+	unsigned int i;
+
+	for (i = 0; i < SND_DG00X_RATE_COUNT; i++) {
+		if (!snd_interval_test(c,
+				       snd_dg00x_stream_mbla_data_channels[i]))
+			continue;
+
+		t.min = min(t.min, snd_dg00x_stream_rates[i]);
+		t.max = max(t.max, snd_dg00x_stream_rates[i]);
+	}
+
+	return snd_interval_refine(r, &t);
+}
+
+static int hw_rule_channels(struct snd_pcm_hw_params *params,
+			    struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval *c =
+		hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
+	const struct snd_interval *r =
+		hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE);
+	struct snd_interval t = {
+		.min = UINT_MAX, .max = 0, .integer = 1,
+	};
+	unsigned int i;
+
+	for (i = 0; i < SND_DG00X_RATE_COUNT; i++) {
+		if (!snd_interval_test(r, snd_dg00x_stream_rates[i]))
+			continue;
+
+		t.min = min(t.min, snd_dg00x_stream_mbla_data_channels[i]);
+		t.max = max(t.max, snd_dg00x_stream_mbla_data_channels[i]);
+	}
+
+	return snd_interval_refine(c, &t);
+}
+
+static int pcm_init_hw_params(struct snd_dg00x *dg00x,
+			      struct snd_pcm_substream *substream)
+{
+	static const struct snd_pcm_hardware hardware = {
+		.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,
+		.rates = SNDRV_PCM_RATE_44100 |
+			 SNDRV_PCM_RATE_48000 |
+			 SNDRV_PCM_RATE_88200 |
+			 SNDRV_PCM_RATE_96000,
+		.rate_min = 44100,
+		.rate_max = 96000,
+		.channels_min = 10,
+		.channels_max = 18,
+		.period_bytes_min = 4 * 18,
+		.period_bytes_max = 4 * 18 * 2048,
+		.buffer_bytes_max = 4 * 18 * 2048 * 2,
+		.periods_min = 2,
+		.periods_max = UINT_MAX,
+	};
+	struct amdtp_stream *s;
+	int err;
+
+	substream->runtime->hw = hardware;
+
+	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+		substream->runtime->hw.formats = AMDTP_IN_PCM_FORMAT_BITS;
+		s = &dg00x->tx_stream;
+	} else {
+		substream->runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS;
+		s = &dg00x->rx_stream;
+	}
+
+	err = snd_pcm_hw_rule_add(substream->runtime, 0,
+				  SNDRV_PCM_HW_PARAM_CHANNELS,
+				  hw_rule_channels, NULL,
+				  SNDRV_PCM_HW_PARAM_RATE, -1);
+	if (err < 0)
+		goto end;
+
+	err = snd_pcm_hw_rule_add(substream->runtime, 0,
+				  SNDRV_PCM_HW_PARAM_RATE,
+				  hw_rule_rate, NULL,
+				  SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	if (err < 0)
+		goto end;
+
+	err = amdtp_stream_add_pcm_hw_constraints(s, substream->runtime);
+end:
+	return err;
+}
+
+static int pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_dg00x *dg00x = substream->private_data;
+	enum snd_dg00x_clock clock;
+	unsigned int rate;
+	int err;
+
+	err = pcm_init_hw_params(dg00x, substream);
+	if (err < 0)
+		return err;
+
+	err = snd_dg00x_stream_get_clock(dg00x, &clock);
+	if ((clock != SND_DG00X_CLOCK_INTERNAL) |
+	    amdtp_stream_pcm_running(&dg00x->rx_stream) |
+	    amdtp_stream_pcm_running(&dg00x->tx_stream)) {
+		err = snd_dg00x_stream_get_rate(dg00x, &rate);
+		if (err < 0)
+			return err;
+		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_dg00x *dg00x = substream->private_data;
+
+	if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+		mutex_lock(&dg00x->mutex);
+		dg00x->capture_substreams++;
+		mutex_unlock(&dg00x->mutex);
+	}
+	amdtp_stream_set_pcm_format(&dg00x->tx_stream,
+				    params_format(hw_params));
+	return snd_pcm_lib_alloc_vmalloc_buffer(substream,
+						params_buffer_bytes(hw_params));
+}
+static int pcm_playback_hw_params(struct snd_pcm_substream *substream,
+				  struct snd_pcm_hw_params *hw_params)
+{
+	struct snd_dg00x *dg00x = substream->private_data;
+
+	if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+		mutex_lock(&dg00x->mutex);
+		dg00x->playback_substreams++;
+		mutex_unlock(&dg00x->mutex);
+	}
+	amdtp_stream_set_pcm_format(&dg00x->rx_stream,
+				    params_format(hw_params));
+	return snd_pcm_lib_alloc_vmalloc_buffer(substream,
+						params_buffer_bytes(hw_params));
+}
+
+static int pcm_capture_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_dg00x *dg00x = substream->private_data;
+
+	mutex_lock(&dg00x->mutex);
+
+	if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
+		dg00x->capture_substreams--;
+
+	snd_dg00x_stream_stop_duplex(dg00x);
+
+	mutex_unlock(&dg00x->mutex);
+
+	return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+static int pcm_playback_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_dg00x *dg00x = substream->private_data;
+
+	mutex_lock(&dg00x->mutex);
+
+	if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
+		dg00x->playback_substreams--;
+
+	snd_dg00x_stream_stop_duplex(dg00x);
+
+	mutex_unlock(&dg00x->mutex);
+
+	return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int pcm_capture_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_dg00x *dg00x = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	mutex_lock(&dg00x->mutex);
+
+	err = snd_dg00x_stream_start_duplex(dg00x, runtime->rate);
+	if (err >= 0)
+		amdtp_stream_pcm_prepare(&dg00x->tx_stream);
+
+	mutex_unlock(&dg00x->mutex);
+
+	return err;
+}
+static int pcm_playback_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_dg00x *dg00x = substream->private_data;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	mutex_lock(&dg00x->mutex);
+
+	err = snd_dg00x_stream_start_duplex(dg00x, runtime->rate);
+	if (err >= 0)
+		amdtp_stream_pcm_prepare(&dg00x->rx_stream);
+
+	mutex_unlock(&dg00x->mutex);
+
+	return err;
+}
+
+static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_dg00x *dg00x = substream->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		amdtp_stream_pcm_trigger(&dg00x->tx_stream, substream);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		amdtp_stream_pcm_trigger(&dg00x->tx_stream, NULL);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_dg00x *dg00x = substream->private_data;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		amdtp_stream_pcm_trigger(&dg00x->rx_stream, substream);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		amdtp_stream_pcm_trigger(&dg00x->rx_stream, NULL);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
+{
+	struct snd_dg00x *dg00x = sbstrm->private_data;
+
+	return amdtp_stream_pcm_pointer(&dg00x->tx_stream);
+}
+static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
+{
+	struct snd_dg00x *dg00x = sbstrm->private_data;
+
+	return amdtp_stream_pcm_pointer(&dg00x->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_dg00x_create_pcm_devices(struct snd_dg00x *dg00x)
+{
+	struct snd_pcm *pcm;
+	int err;
+
+	err = snd_pcm_new(dg00x->card, dg00x->card->driver, 0, 1, 1, &pcm);
+	if (err < 0)
+		return err;
+
+	pcm->private_data = dg00x;
+	snprintf(pcm->name, sizeof(pcm->name),
+		 "%s PCM", dg00x->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/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c
index d9c9e14..493437a 100644
--- a/sound/firewire/digi00x/digi00x.c
+++ b/sound/firewire/digi00x/digi00x.c
@@ -86,6 +86,10 @@ static int snd_dg00x_probe(struct fw_unit *unit,
 
 	snd_dg00x_proc_init(dg00x);
 
+	err = snd_dg00x_create_pcm_devices(dg00x);
+	if (err < 0)
+		goto error;
+
 	err = snd_card_register(card);
 	if (err < 0)
 		goto error;
diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h
index a16a535..318b1b5 100644
--- a/sound/firewire/digi00x/digi00x.h
+++ b/sound/firewire/digi00x/digi00x.h
@@ -21,6 +21,8 @@
 #include <sound/core.h>
 #include <sound/initval.h>
 #include <sound/info.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
 
 #include "../lib.h"
 #include "../packets-buffer.h"
@@ -107,4 +109,6 @@ void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x);
 void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x);
 
 void snd_dg00x_proc_init(struct snd_dg00x *dg00x);
+
+int snd_dg00x_create_pcm_devices(struct snd_dg00x *dg00x);
 #endif
-- 
2.1.0

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

* [PATCH 05/11] ALSA: digi00x: add MIDI functionality
  2015-03-29 15:05 [RFC v2][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family Takashi Sakamoto
                   ` (3 preceding siblings ...)
  2015-03-29 15:05 ` [PATCH 04/11] ALSA: digi00x: add PCM functionality Takashi Sakamoto
@ 2015-03-29 15:05 ` Takashi Sakamoto
  2015-03-29 15:05 ` [PATCH 06/11] ALSA: digi00x: add hwdep interface Takashi Sakamoto
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Takashi Sakamoto @ 2015-03-29 15:05 UTC (permalink / raw)
  To: clemens; +Cc: damien, robin, alsa-devel, ffado-devel

Digi 002/003 uses AM824 format data to transfer MIDI messages. It's a
first data channel in each data block of CIP packet.

This commit adds MIDI functionality to transfer/receive MIDI messages
in the first data channel, with AMDTP functionality ALSA firewire stack
gives. But the device handles these MIDI message in disorder because
Digi 002/003 are not conformant to MMA/AMEI RP-027 or IEC 61883-6:2005.

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

diff --git a/sound/firewire/digi00x/Makefile b/sound/firewire/digi00x/Makefile
index f2ed472..8ae72b9 100644
--- a/sound/firewire/digi00x/Makefile
+++ b/sound/firewire/digi00x/Makefile
@@ -1,2 +1,3 @@
-snd-digi00x-objs := digi00x.o digi00x-stream.o digi00x-proc.o digi00x-pcm.o
+snd-digi00x-objs := digi00x.o digi00x-stream.o digi00x-proc.o digi00x-pcm.o \
+		    digi00x-midi.o
 obj-m += snd-digi00x.o
diff --git a/sound/firewire/digi00x/digi00x-midi.c b/sound/firewire/digi00x/digi00x-midi.c
new file mode 100644
index 0000000..9936c40
--- /dev/null
+++ b/sound/firewire/digi00x/digi00x-midi.c
@@ -0,0 +1,149 @@
+/*
+ * digi00x-midi.h - a part of driver for Digidesign Digi 002/003 family
+ *
+ * Copyright (c) 2014-2015 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "digi00x.h"
+
+static int midi_capture_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_dg00x *dg00x = substream->rmidi->private_data;
+	int err;
+
+	mutex_lock(&dg00x->mutex);
+	dg00x->capture_substreams++;
+	err = snd_dg00x_stream_start_duplex(dg00x, 0);
+	mutex_unlock(&dg00x->mutex);
+
+	return err;
+}
+
+static int midi_playback_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_dg00x *dg00x = substream->rmidi->private_data;
+	int err;
+
+	mutex_lock(&dg00x->mutex);
+	dg00x->playback_substreams++;
+	err = snd_dg00x_stream_start_duplex(dg00x, 0);
+	mutex_unlock(&dg00x->mutex);
+
+	return err;
+}
+
+static int midi_capture_close(struct snd_rawmidi_substream *substream)
+{
+	struct snd_dg00x *dg00x = substream->rmidi->private_data;
+
+	mutex_lock(&dg00x->mutex);
+	dg00x->capture_substreams--;
+	snd_dg00x_stream_stop_duplex(dg00x);
+	mutex_unlock(&dg00x->mutex);
+
+	return 0;
+}
+
+static int midi_playback_close(struct snd_rawmidi_substream *substream)
+{
+	struct snd_dg00x *dg00x = substream->rmidi->private_data;
+
+	mutex_lock(&dg00x->mutex);
+	dg00x->playback_substreams--;
+	snd_dg00x_stream_stop_duplex(dg00x);
+	mutex_unlock(&dg00x->mutex);
+
+	return 0;
+}
+
+static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
+{
+	struct snd_dg00x *dg00x = substrm->rmidi->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dg00x->lock, flags);
+
+	if (up)
+		amdtp_stream_midi_trigger(&dg00x->tx_stream,
+					  substrm->number, substrm);
+	else
+		amdtp_stream_midi_trigger(&dg00x->tx_stream,
+					  substrm->number, NULL);
+
+	spin_unlock_irqrestore(&dg00x->lock, flags);
+}
+
+static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up)
+{
+	struct snd_dg00x *dg00x = substrm->rmidi->private_data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dg00x->lock, flags);
+
+	if (up)
+		amdtp_stream_midi_trigger(&dg00x->rx_stream,
+					  substrm->number, substrm);
+	else
+		amdtp_stream_midi_trigger(&dg00x->rx_stream,
+					  substrm->number, NULL);
+
+	spin_unlock_irqrestore(&dg00x->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_dg00x *dg00x,
+				     struct snd_rawmidi_str *str)
+{
+	struct snd_rawmidi_substream *subs;
+
+	list_for_each_entry(subs, &str->substreams, list) {
+		snprintf(subs->name, sizeof(subs->name),
+			 "%s MIDI %d",
+			 dg00x->card->shortname, subs->number + 1);
+	}
+}
+
+int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x)
+{
+	struct snd_rawmidi *rmidi;
+	struct snd_rawmidi_str *str;
+	int err;
+
+	err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, 0,
+			      1, 2, &rmidi);
+	if (err < 0)
+		return err;
+
+	snprintf(rmidi->name, sizeof(rmidi->name),
+		 "%s MIDI", dg00x->card->shortname);
+	rmidi->private_data = dg00x;
+
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+			    &midi_capture_ops);
+	str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT];
+	set_midi_substream_names(dg00x, str);
+
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
+	snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+			    &midi_playback_ops);
+	str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
+	set_midi_substream_names(dg00x, str);
+
+	rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
+
+	return 0;
+}
diff --git a/sound/firewire/digi00x/digi00x-stream.c b/sound/firewire/digi00x/digi00x-stream.c
index 1d5ef01..abc9606 100644
--- a/sound/firewire/digi00x/digi00x-stream.c
+++ b/sound/firewire/digi00x/digi00x-stream.c
@@ -252,6 +252,9 @@ int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x, unsigned int rate)
 	err = snd_dg00x_stream_get_rate(dg00x, &curr_rate);
 	if (err < 0)
 		goto error;
+	/* For MIDI substreams. */
+	if (rate == 0)
+		rate = curr_rate;
 	if ((curr_rate != rate) |
 	    amdtp_streaming_error(&dg00x->tx_stream) |
 	    amdtp_streaming_error(&dg00x->rx_stream)) {
diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c
index 493437a..5b678ec 100644
--- a/sound/firewire/digi00x/digi00x.c
+++ b/sound/firewire/digi00x/digi00x.c
@@ -90,6 +90,10 @@ static int snd_dg00x_probe(struct fw_unit *unit,
 	if (err < 0)
 		goto error;
 
+	err = snd_dg00x_create_midi_devices(dg00x);
+	if (err < 0)
+		goto error;
+
 	err = snd_card_register(card);
 	if (err < 0)
 		goto error;
diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h
index 318b1b5..1ef5d9e 100644
--- a/sound/firewire/digi00x/digi00x.h
+++ b/sound/firewire/digi00x/digi00x.h
@@ -23,6 +23,7 @@
 #include <sound/info.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
+#include <sound/rawmidi.h>
 
 #include "../lib.h"
 #include "../packets-buffer.h"
@@ -111,4 +112,6 @@ void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x);
 void snd_dg00x_proc_init(struct snd_dg00x *dg00x);
 
 int snd_dg00x_create_pcm_devices(struct snd_dg00x *dg00x);
+
+int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x);
 #endif
-- 
2.1.0

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

* [PATCH 06/11] ALSA: digi00x: add hwdep interface
  2015-03-29 15:05 [RFC v2][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family Takashi Sakamoto
                   ` (4 preceding siblings ...)
  2015-03-29 15:05 ` [PATCH 05/11] ALSA: digi00x: add MIDI functionality Takashi Sakamoto
@ 2015-03-29 15:05 ` Takashi Sakamoto
  2015-03-29 15:05 ` [PATCH 07/11] ALSA: digi00x: support unknown asynchronous message Takashi Sakamoto
                   ` (5 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Takashi Sakamoto @ 2015-03-29 15:05 UTC (permalink / raw)
  To: clemens; +Cc: damien, robin, alsa-devel, ffado-devel

This commit adds hwdep interface so as the other firewire sound devices
has.

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

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 include/uapi/sound/asound.h             |   3 +-
 include/uapi/sound/firewire.h           |   1 +
 sound/firewire/Kconfig                  |   1 +
 sound/firewire/digi00x/Makefile         |   2 +-
 sound/firewire/digi00x/digi00x-hwdep.c  | 192 ++++++++++++++++++++++++++++++++
 sound/firewire/digi00x/digi00x-midi.c   |  14 +++
 sound/firewire/digi00x/digi00x-pcm.c    |  19 +++-
 sound/firewire/digi00x/digi00x-stream.c |  39 +++++++
 sound/firewire/digi00x/digi00x.c        |   5 +
 sound/firewire/digi00x/digi00x.h        |  13 +++
 10 files changed, 283 insertions(+), 6 deletions(-)
 create mode 100644 sound/firewire/digi00x/digi00x-hwdep.c

diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h
index 46145a5..cc24d0e 100644
--- a/include/uapi/sound/asound.h
+++ b/include/uapi/sound/asound.h
@@ -100,9 +100,10 @@ enum {
 	SNDRV_HWDEP_IFACE_FW_FIREWORKS,	/* Echo Audio Fireworks based device */
 	SNDRV_HWDEP_IFACE_FW_BEBOB,	/* BridgeCo BeBoB based device */
 	SNDRV_HWDEP_IFACE_FW_OXFW,	/* Oxford OXFW970/971 based device */
+	SNDRV_HWDEP_IFACE_FW_DIGI00X,	/* Digidesign 002/003 family */
 
 	/* Don't forget to change the following: */
-	SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_OXFW
+	SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_DIGI00X
 };
 
 struct snd_hwdep_info {
diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h
index 49122df..f67d228 100644
--- a/include/uapi/sound/firewire.h
+++ b/include/uapi/sound/firewire.h
@@ -56,6 +56,7 @@ union snd_firewire_event {
 #define SNDRV_FIREWIRE_TYPE_FIREWORKS	2
 #define SNDRV_FIREWIRE_TYPE_BEBOB	3
 #define SNDRV_FIREWIRE_TYPE_OXFW	4
+#define SNDRV_FIREWIRE_TYPE_DIGI00X	5
 /* RME, MOTU, ... */
 
 struct snd_firewire_get_info {
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig
index 6b9b0a1..804502b 100644
--- a/sound/firewire/Kconfig
+++ b/sound/firewire/Kconfig
@@ -121,6 +121,7 @@ config SND_BEBOB
 config SND_DIGI00X
 	tristate "Digidesign 002/003 family support"
 	select SND_FIREWIRE_LIB
+	select SND_HWDEP
 	help
 	 Say Y here to include support for Digidesign 002/003 family.
 
diff --git a/sound/firewire/digi00x/Makefile b/sound/firewire/digi00x/Makefile
index 8ae72b9..deb3683 100644
--- a/sound/firewire/digi00x/Makefile
+++ b/sound/firewire/digi00x/Makefile
@@ -1,3 +1,3 @@
 snd-digi00x-objs := digi00x.o digi00x-stream.o digi00x-proc.o digi00x-pcm.o \
-		    digi00x-midi.o
+		    digi00x-midi.o digi00x-hwdep.o
 obj-m += snd-digi00x.o
diff --git a/sound/firewire/digi00x/digi00x-hwdep.c b/sound/firewire/digi00x/digi00x-hwdep.c
new file mode 100644
index 0000000..d629e41
--- /dev/null
+++ b/sound/firewire/digi00x/digi00x-hwdep.c
@@ -0,0 +1,192 @@
+/*
+ * digi00x-hwdep.c - a part of driver for Digidesign Digi 002/003 family
+ *
+ * Copyright (c) 2014-2015 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 "digi00x.h"
+
+static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf,  long count,
+		       loff_t *offset)
+{
+	struct snd_dg00x *dg00x = hwdep->private_data;
+	DEFINE_WAIT(wait);
+	union snd_firewire_event event;
+
+	spin_lock_irq(&dg00x->lock);
+
+	while (!dg00x->dev_lock_changed) {
+		prepare_to_wait(&dg00x->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
+		spin_unlock_irq(&dg00x->lock);
+		schedule();
+		finish_wait(&dg00x->hwdep_wait, &wait);
+		if (signal_pending(current))
+			return -ERESTARTSYS;
+		spin_lock_irq(&dg00x->lock);
+	}
+
+	memset(&event, 0, sizeof(event));
+	if (dg00x->dev_lock_changed) {
+		event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
+		event.lock_status.status = (dg00x->dev_lock_count > 0);
+		dg00x->dev_lock_changed = false;
+
+		count = min_t(long, count, sizeof(event.lock_status));
+	}
+
+	spin_unlock_irq(&dg00x->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_dg00x *dg00x = hwdep->private_data;
+	unsigned int events;
+
+	poll_wait(file, &dg00x->hwdep_wait, wait);
+
+	spin_lock_irq(&dg00x->lock);
+	if (dg00x->dev_lock_changed)
+		events = POLLIN | POLLRDNORM;
+	else
+		events = 0;
+	spin_unlock_irq(&dg00x->lock);
+
+	return events;
+}
+
+static int hwdep_get_info(struct snd_dg00x *dg00x, void __user *arg)
+{
+	struct fw_device *dev = fw_parent_device(dg00x->unit);
+	struct snd_firewire_get_info info;
+
+	memset(&info, 0, sizeof(info));
+	info.type = SNDRV_FIREWIRE_TYPE_DIGI00X;
+	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_dg00x *dg00x)
+{
+	int err;
+
+	spin_lock_irq(&dg00x->lock);
+
+	if (dg00x->dev_lock_count == 0) {
+		dg00x->dev_lock_count = -1;
+		err = 0;
+	} else {
+		err = -EBUSY;
+	}
+
+	spin_unlock_irq(&dg00x->lock);
+
+	return err;
+}
+
+static int hwdep_unlock(struct snd_dg00x *dg00x)
+{
+	int err;
+
+	spin_lock_irq(&dg00x->lock);
+
+	if (dg00x->dev_lock_count == -1) {
+		dg00x->dev_lock_count = 0;
+		err = 0;
+	} else {
+		err = -EBADFD;
+	}
+
+	spin_unlock_irq(&dg00x->lock);
+
+	return err;
+}
+
+static int hwdep_release(struct snd_hwdep *hwdep, struct file *file)
+{
+	struct snd_dg00x *dg00x = hwdep->private_data;
+
+	spin_lock_irq(&dg00x->lock);
+	if (dg00x->dev_lock_count == -1)
+		dg00x->dev_lock_count = 0;
+	spin_unlock_irq(&dg00x->lock);
+
+	return 0;
+}
+
+static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
+	    unsigned int cmd, unsigned long arg)
+{
+	struct snd_dg00x *dg00x = hwdep->private_data;
+
+	switch (cmd) {
+	case SNDRV_FIREWIRE_IOCTL_GET_INFO:
+		return hwdep_get_info(dg00x, (void __user *)arg);
+	case SNDRV_FIREWIRE_IOCTL_LOCK:
+		return hwdep_lock(dg00x);
+	case SNDRV_FIREWIRE_IOCTL_UNLOCK:
+		return hwdep_unlock(dg00x);
+	default:
+		return -ENOIOCTLCMD;
+	}
+}
+
+#ifdef CONFIG_COMPAT
+static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
+			      unsigned int cmd, unsigned long arg)
+{
+	return hwdep_ioctl(hwdep, file, cmd,
+			   (unsigned long)compat_ptr(arg));
+}
+#else
+#define hwdep_compat_ioctl NULL
+#endif
+
+static const struct snd_hwdep_ops hwdep_ops = {
+	.read		= hwdep_read,
+	.release	= hwdep_release,
+	.poll		= hwdep_poll,
+	.ioctl		= hwdep_ioctl,
+	.ioctl_compat	= hwdep_compat_ioctl,
+};
+
+int snd_dg00x_create_hwdep_device(struct snd_dg00x *dg00x)
+{
+	struct snd_hwdep *hwdep;
+	int err;
+
+	err = snd_hwdep_new(dg00x->card, "Digi00x", 0, &hwdep);
+	if (err < 0)
+		return err;
+
+	strcpy(hwdep->name, "Digi00x");
+	hwdep->iface = SNDRV_HWDEP_IFACE_FW_DIGI00X;
+	hwdep->ops = hwdep_ops;
+	hwdep->private_data = dg00x;
+	hwdep->exclusive = true;
+
+	return err;
+}
diff --git a/sound/firewire/digi00x/digi00x-midi.c b/sound/firewire/digi00x/digi00x-midi.c
index 9936c40..460f8eb 100644
--- a/sound/firewire/digi00x/digi00x-midi.c
+++ b/sound/firewire/digi00x/digi00x-midi.c
@@ -13,10 +13,16 @@ static int midi_capture_open(struct snd_rawmidi_substream *substream)
 	struct snd_dg00x *dg00x = substream->rmidi->private_data;
 	int err;
 
+	err = snd_dg00x_stream_lock_try(dg00x);
+	if (err < 0)
+		return err;
+
 	mutex_lock(&dg00x->mutex);
 	dg00x->capture_substreams++;
 	err = snd_dg00x_stream_start_duplex(dg00x, 0);
 	mutex_unlock(&dg00x->mutex);
+	if (err < 0)
+		snd_dg00x_stream_lock_release(dg00x);
 
 	return err;
 }
@@ -26,10 +32,16 @@ static int midi_playback_open(struct snd_rawmidi_substream *substream)
 	struct snd_dg00x *dg00x = substream->rmidi->private_data;
 	int err;
 
+	err = snd_dg00x_stream_lock_try(dg00x);
+	if (err < 0)
+		return err;
+
 	mutex_lock(&dg00x->mutex);
 	dg00x->playback_substreams++;
 	err = snd_dg00x_stream_start_duplex(dg00x, 0);
 	mutex_unlock(&dg00x->mutex);
+	if (err < 0)
+		snd_dg00x_stream_lock_release(dg00x);
 
 	return err;
 }
@@ -43,6 +55,7 @@ static int midi_capture_close(struct snd_rawmidi_substream *substream)
 	snd_dg00x_stream_stop_duplex(dg00x);
 	mutex_unlock(&dg00x->mutex);
 
+	snd_dg00x_stream_lock_release(dg00x);
 	return 0;
 }
 
@@ -55,6 +68,7 @@ static int midi_playback_close(struct snd_rawmidi_substream *substream)
 	snd_dg00x_stream_stop_duplex(dg00x);
 	mutex_unlock(&dg00x->mutex);
 
+	snd_dg00x_stream_lock_release(dg00x);
 	return 0;
 }
 
diff --git a/sound/firewire/digi00x/digi00x-pcm.c b/sound/firewire/digi00x/digi00x-pcm.c
index 33db601..a050d67 100644
--- a/sound/firewire/digi00x/digi00x-pcm.c
+++ b/sound/firewire/digi00x/digi00x-pcm.c
@@ -118,9 +118,13 @@ static int pcm_open(struct snd_pcm_substream *substream)
 	unsigned int rate;
 	int err;
 
+	err = snd_dg00x_stream_lock_try(dg00x);
+	if (err < 0)
+		goto end;
+
 	err = pcm_init_hw_params(dg00x, substream);
 	if (err < 0)
-		return err;
+		goto err_locked;
 
 	err = snd_dg00x_stream_get_clock(dg00x, &clock);
 	if ((clock != SND_DG00X_CLOCK_INTERNAL) |
@@ -128,18 +132,25 @@ static int pcm_open(struct snd_pcm_substream *substream)
 	    amdtp_stream_pcm_running(&dg00x->tx_stream)) {
 		err = snd_dg00x_stream_get_rate(dg00x, &rate);
 		if (err < 0)
-			return err;
+			goto err_locked;
 		substream->runtime->hw.rate_min = rate;
 		substream->runtime->hw.rate_max = rate;
 	}
 
 	snd_pcm_set_sync(substream);
-
-	return 0;
+end:
+	return err;
+err_locked:
+	snd_dg00x_stream_lock_release(dg00x);
+	return err;
 }
 
 static int pcm_close(struct snd_pcm_substream *substream)
 {
+	struct snd_dg00x *dg00x = substream->private_data;
+
+	snd_dg00x_stream_lock_release(dg00x);
+
 	return 0;
 }
 
diff --git a/sound/firewire/digi00x/digi00x-stream.c b/sound/firewire/digi00x/digi00x-stream.c
index abc9606..5d72264 100644
--- a/sound/firewire/digi00x/digi00x-stream.c
+++ b/sound/firewire/digi00x/digi00x-stream.c
@@ -350,3 +350,42 @@ void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x)
 	amdtp_stream_update(&dg00x->tx_stream);
 	amdtp_stream_update(&dg00x->rx_stream);
 }
+
+void snd_dg00x_stream_lock_changed(struct snd_dg00x *dg00x)
+{
+	dg00x->dev_lock_changed = true;
+	wake_up(&dg00x->hwdep_wait);
+}
+
+int snd_dg00x_stream_lock_try(struct snd_dg00x *dg00x)
+{
+	int err;
+
+	spin_lock_irq(&dg00x->lock);
+
+	/* user land lock this */
+	if (dg00x->dev_lock_count < 0) {
+		err = -EBUSY;
+		goto end;
+	}
+
+	/* this is the first time */
+	if (dg00x->dev_lock_count++ == 0)
+		snd_dg00x_stream_lock_changed(dg00x);
+	err = 0;
+end:
+	spin_unlock_irq(&dg00x->lock);
+	return err;
+}
+
+void snd_dg00x_stream_lock_release(struct snd_dg00x *dg00x)
+{
+	spin_lock_irq(&dg00x->lock);
+
+	if (WARN_ON(dg00x->dev_lock_count <= 0))
+		goto end;
+	if (--dg00x->dev_lock_count == 0)
+		snd_dg00x_stream_lock_changed(dg00x);
+end:
+	spin_unlock_irq(&dg00x->lock);
+}
diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c
index 5b678ec..c170cbb 100644
--- a/sound/firewire/digi00x/digi00x.c
+++ b/sound/firewire/digi00x/digi00x.c
@@ -75,6 +75,7 @@ static int snd_dg00x_probe(struct fw_unit *unit,
 
 	mutex_init(&dg00x->mutex);
 	spin_lock_init(&dg00x->lock);
+	init_waitqueue_head(&dg00x->hwdep_wait);
 
 	err = name_card(dg00x);
 	if (err < 0)
@@ -94,6 +95,10 @@ static int snd_dg00x_probe(struct fw_unit *unit,
 	if (err < 0)
 		goto error;
 
+	err = snd_dg00x_create_hwdep_device(dg00x);
+	if (err < 0)
+		goto error;
+
 	err = snd_card_register(card);
 	if (err < 0)
 		goto error;
diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h
index 1ef5d9e..5d9beaf 100644
--- a/sound/firewire/digi00x/digi00x.h
+++ b/sound/firewire/digi00x/digi00x.h
@@ -24,6 +24,8 @@
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
 #include <sound/rawmidi.h>
+#include <sound/hwdep.h>
+#include <sound/firewire.h>
 
 #include "../lib.h"
 #include "../packets-buffer.h"
@@ -46,6 +48,11 @@ struct snd_dg00x {
 
 	unsigned int playback_substreams;
 	unsigned int capture_substreams;
+
+	/* for uapi */
+	int dev_lock_count;
+	bool dev_lock_changed;
+	wait_queue_head_t hwdep_wait;
 };
 
 #define DG00X_ADDR_BASE		0xffffe0000000ull
@@ -109,9 +116,15 @@ void snd_dg00x_stream_stop_duplex(struct snd_dg00x *dg00x);
 void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x);
 void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x);
 
+void snd_dg00x_stream_lock_changed(struct snd_dg00x *dg00x);
+int snd_dg00x_stream_lock_try(struct snd_dg00x *dg00x);
+void snd_dg00x_stream_lock_release(struct snd_dg00x *dg00x);
+
 void snd_dg00x_proc_init(struct snd_dg00x *dg00x);
 
 int snd_dg00x_create_pcm_devices(struct snd_dg00x *dg00x);
 
 int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x);
+
+int snd_dg00x_create_hwdep_device(struct snd_dg00x *dg00x);
 #endif
-- 
2.1.0

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

* [PATCH 07/11] ALSA: digi00x: support unknown asynchronous message
  2015-03-29 15:05 [RFC v2][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family Takashi Sakamoto
                   ` (5 preceding siblings ...)
  2015-03-29 15:05 ` [PATCH 06/11] ALSA: digi00x: add hwdep interface Takashi Sakamoto
@ 2015-03-29 15:05 ` Takashi Sakamoto
  2015-03-29 15:05 ` [PATCH 08/11] ALSA: digi00x: support MIDI ports for device control Takashi Sakamoto
                   ` (4 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Takashi Sakamoto @ 2015-03-29 15:05 UTC (permalink / raw)
  To: clemens; +Cc: damien, robin, alsa-devel, ffado-devel

Digi 002/003 family use asynchronous transaction for messaging.
The address to receive this message is stored on a certain register.

This commit allocates a certain range of address on OHCI 1394 host
controller, to handle this notification. Currently, the purpose of
this message is unknown.

Usually, these devices confirmedly notify these messages below:
 * Clock source is set as internal:
 - 0x00007051
 - 0x00007052
 - 0x00007054
 - 0x00007057
 - 0x00007058
 * Clock source is set as somewhat external:
 - 0x00009000
 - 0x00009010
 - 0x00009020
 - 0x00009021
 - 0x00009022

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 include/uapi/sound/firewire.h             |   7 ++
 sound/firewire/digi00x/Makefile           |   2 +-
 sound/firewire/digi00x/digi00x-hwdep.c    |  12 ++-
 sound/firewire/digi00x/digi00x-protocol.c | 131 ++++++++++++++++++++++++++++++
 sound/firewire/digi00x/digi00x.c          |  12 +++
 sound/firewire/digi00x/digi00x.h          |   8 ++
 6 files changed, 169 insertions(+), 3 deletions(-)
 create mode 100644 sound/firewire/digi00x/digi00x-protocol.c

diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h
index f67d228..8e32b41 100644
--- a/include/uapi/sound/firewire.h
+++ b/include/uapi/sound/firewire.h
@@ -9,6 +9,7 @@
 #define SNDRV_FIREWIRE_EVENT_LOCK_STATUS	0x000010cc
 #define SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION	0xd1ce004e
 #define SNDRV_FIREWIRE_EVENT_EFW_RESPONSE	0x4e617475
+#define SNDRV_FIREWIRE_EVENT_DIGI00x_MESSAGE	0x746e736c
 
 struct snd_firewire_event_common {
 	unsigned int type; /* SNDRV_FIREWIRE_EVENT_xxx */
@@ -40,11 +41,17 @@ struct snd_firewire_event_efw_response {
 	__be32 response[0];	/* some responses */
 };
 
+struct snd_firewire_event_digi00x_message {
+	unsigned int type;
+	__u32 message;	/* Digi00x-specific message */
+};
+
 union snd_firewire_event {
 	struct snd_firewire_event_common            common;
 	struct snd_firewire_event_lock_status       lock_status;
 	struct snd_firewire_event_dice_notification dice_notification;
 	struct snd_firewire_event_efw_response      efw_response;
+	struct snd_firewire_event_digi00x_message   digi00x_message;
 };
 
 
diff --git a/sound/firewire/digi00x/Makefile b/sound/firewire/digi00x/Makefile
index deb3683..e42b5cc 100644
--- a/sound/firewire/digi00x/Makefile
+++ b/sound/firewire/digi00x/Makefile
@@ -1,3 +1,3 @@
 snd-digi00x-objs := digi00x.o digi00x-stream.o digi00x-proc.o digi00x-pcm.o \
-		    digi00x-midi.o digi00x-hwdep.o
+		    digi00x-midi.o digi00x-hwdep.o digi00x-protocol.o
 obj-m += snd-digi00x.o
diff --git a/sound/firewire/digi00x/digi00x-hwdep.c b/sound/firewire/digi00x/digi00x-hwdep.c
index d629e41..a3e1c37 100644
--- a/sound/firewire/digi00x/digi00x-hwdep.c
+++ b/sound/firewire/digi00x/digi00x-hwdep.c
@@ -12,6 +12,7 @@
  * 1.get firewire node information
  * 2.get notification about starting/stopping stream
  * 3.lock/unlock stream
+ * 4.get asynchronous messaging
  */
 
 #include "digi00x.h"
@@ -25,7 +26,7 @@ static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf,  long count,
 
 	spin_lock_irq(&dg00x->lock);
 
-	while (!dg00x->dev_lock_changed) {
+	while (!dg00x->dev_lock_changed && dg00x->msg == 0) {
 		prepare_to_wait(&dg00x->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
 		spin_unlock_irq(&dg00x->lock);
 		schedule();
@@ -42,6 +43,13 @@ static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf,  long count,
 		dg00x->dev_lock_changed = false;
 
 		count = min_t(long, count, sizeof(event.lock_status));
+	} else {
+		event.digi00x_message.type =
+					SNDRV_FIREWIRE_EVENT_DIGI00x_MESSAGE;
+		event.digi00x_message.message = dg00x->msg;
+		dg00x->msg = 0;
+
+		count = min_t(long, count, sizeof(event.digi00x_message));
 	}
 
 	spin_unlock_irq(&dg00x->lock);
@@ -61,7 +69,7 @@ static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
 	poll_wait(file, &dg00x->hwdep_wait, wait);
 
 	spin_lock_irq(&dg00x->lock);
-	if (dg00x->dev_lock_changed)
+	if (dg00x->dev_lock_changed || dg00x->msg)
 		events = POLLIN | POLLRDNORM;
 	else
 		events = 0;
diff --git a/sound/firewire/digi00x/digi00x-protocol.c b/sound/firewire/digi00x/digi00x-protocol.c
new file mode 100644
index 0000000..72c942a
--- /dev/null
+++ b/sound/firewire/digi00x/digi00x-protocol.c
@@ -0,0 +1,131 @@
+/*
+ * digi00x-protocol.c - a part of driver for Digidesign Digi 002/003 family
+ *
+ * Copyright (c) 2014-2015 Takashi Sakamoto
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "digi00x.h"
+
+static struct snd_dg00x *instances[SNDRV_CARDS];
+static DEFINE_SPINLOCK(instances_lock);
+
+/*
+ * Use the same range of address for asynchronous messages from any devices, to
+ * save resources on host controller.
+ */
+static struct fw_address_handler async_handler;
+
+static void handle_unknown_message(struct snd_dg00x *dg00x,
+				   unsigned long long offset, u32 *buf)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&dg00x->lock, flags);
+	dg00x->msg = be32_to_cpu(*buf);
+	spin_unlock_irqrestore(&dg00x->lock, flags);
+
+	wake_up(&dg00x->hwdep_wait);
+}
+
+static void handle_message(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)
+{
+	u32 *buf = (__be32 *)data;
+	struct fw_device *device;
+	struct snd_dg00x *dg00x;
+	unsigned int i;
+
+	spin_lock_irq(&instances_lock);
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		dg00x = instances[i];
+		if (dg00x == NULL)
+			continue;
+		device = fw_parent_device(dg00x->unit);
+		if (device->card != card)
+			continue;
+		smp_rmb();	/* node id vs. generation */
+		if (device->node_id != source)
+			continue;
+		break;
+	}
+
+	if (offset == async_handler.offset)
+		handle_unknown_message(dg00x, offset, buf);
+	else if (offset == async_handler.offset + 4)
+		handle_midi_control(dg00x, buf, length);
+
+	spin_unlock_irq(&instances_lock);
+	fw_send_response(card, request, RCODE_COMPLETE);
+}
+
+int snd_dg00x_protocol_add_instance(struct snd_dg00x *dg00x)
+{
+	struct fw_device *device = fw_parent_device(dg00x->unit);
+	__be32 data[2];
+	unsigned int i;
+	int err;
+
+	/* Unknown. 4bytes. */
+	data[0] = cpu_to_be32((device->card->node_id << 16) |
+			      (async_handler.offset >> 32));
+	data[1] = cpu_to_be32(async_handler.offset);
+	err = snd_fw_transaction(dg00x->unit, TCODE_WRITE_BLOCK_REQUEST,
+				 DG00X_ADDR_BASE + DG00X_OFFSET_MESSAGE_ADDR,
+				 &data, sizeof(data), 0);
+	if (err < 0)
+		return err;
+
+	spin_lock_irq(&instances_lock);
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		if (instances[i] != NULL)
+			continue;
+		instances[i] = dg00x;
+		break;
+	}
+	spin_unlock_irq(&instances_lock);
+
+	return 0;
+}
+
+void snd_dg00x_protocol_remove_instance(struct snd_dg00x *dg00x)
+{
+	unsigned int i;
+
+	spin_lock_irq(&instances_lock);
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		if (instances[i] != dg00x)
+			continue;
+		instances[i] = NULL;
+		break;
+	}
+	spin_unlock_irq(&instances_lock);
+}
+
+int snd_dg00x_protocol_register(void)
+{
+	static const struct fw_address_region resp_register_region = {
+		.start	= 0xffffe0000000ull,
+		.end	= 0xffffe000ffffull,
+	};
+	int err;
+
+	async_handler.length = 4;
+	async_handler.address_callback = handle_message;
+	async_handler.callback_data = NULL;
+
+	err = fw_core_add_address_handler(&async_handler,
+					  &resp_register_region);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+void snd_dg00x_protocol_unregister(void)
+{
+	fw_core_remove_address_handler(&async_handler);
+}
diff --git a/sound/firewire/digi00x/digi00x.c b/sound/firewire/digi00x/digi00x.c
index c170cbb..db0997d 100644
--- a/sound/firewire/digi00x/digi00x.c
+++ b/sound/firewire/digi00x/digi00x.c
@@ -48,6 +48,7 @@ static void dg00x_card_free(struct snd_card *card)
 	struct snd_dg00x *dg00x = card->private_data;
 
 	snd_dg00x_stream_destroy_duplex(dg00x);
+	snd_dg00x_protocol_remove_instance(dg00x);
 
 	fw_unit_put(dg00x->unit);
 
@@ -99,6 +100,10 @@ static int snd_dg00x_probe(struct fw_unit *unit,
 	if (err < 0)
 		goto error;
 
+	err = snd_dg00x_protocol_add_instance(dg00x);
+	if (err < 0)
+		goto error;
+
 	err = snd_card_register(card);
 	if (err < 0)
 		goto error;
@@ -155,11 +160,18 @@ static struct fw_driver dg00x_driver = {
 
 static int __init snd_dg00x_init(void)
 {
+	int err;
+
+	err = snd_dg00x_protocol_register();
+	if (err < 0)
+		return err;
+
 	return driver_register(&dg00x_driver.driver);
 }
 
 static void __exit snd_dg00x_exit(void)
 {
+	snd_dg00x_protocol_unregister();
 	driver_unregister(&dg00x_driver.driver);
 }
 
diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h
index 5d9beaf..4e3d359 100644
--- a/sound/firewire/digi00x/digi00x.h
+++ b/sound/firewire/digi00x/digi00x.h
@@ -53,6 +53,9 @@ struct snd_dg00x {
 	int dev_lock_count;
 	bool dev_lock_changed;
 	wait_queue_head_t hwdep_wait;
+
+	/* For asynchronous messages. */
+	u32 msg;
 };
 
 #define DG00X_ADDR_BASE		0xffffe0000000ull
@@ -103,6 +106,11 @@ enum snd_dg00x_optical_mode {
 	SND_DG00X_OPT_IFACE_MODE_COUNT,
 };
 
+int snd_dg00x_protocol_add_instance(struct snd_dg00x *dg00x);
+void snd_dg00x_protocol_remove_instance(struct snd_dg00x *dg00x);
+int snd_dg00x_protocol_register(void);
+void snd_dg00x_protocol_unregister(void);
+
 extern const unsigned int snd_dg00x_stream_rates[SND_DG00X_RATE_COUNT];
 extern const unsigned int
 snd_dg00x_stream_mbla_data_channels[SND_DG00X_RATE_COUNT];
-- 
2.1.0

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

* [PATCH 08/11] ALSA: digi00x: support MIDI ports for device control
  2015-03-29 15:05 [RFC v2][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family Takashi Sakamoto
                   ` (6 preceding siblings ...)
  2015-03-29 15:05 ` [PATCH 07/11] ALSA: digi00x: support unknown asynchronous message Takashi Sakamoto
@ 2015-03-29 15:05 ` Takashi Sakamoto
  2015-03-29 15:05 ` [PATCH 09/11] ALSA: firewire-lib: allows to implement external MIDI callback function Takashi Sakamoto
                   ` (3 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Takashi Sakamoto @ 2015-03-29 15:05 UTC (permalink / raw)
  To: clemens; +Cc: damien, robin, alsa-devel, ffado-devel

Digi 002/003 family uses asynchronous transactions for MIDI
device control message. The address to transfer is stored on
a certain address, while the address to receive is 0xffffe0000040.

This commit supports MIDI ports for this purpose. For capture
MIDI message, the handler of notification is extended. For playback
MIDI message, a workqueue is used because Linux FireWire subsystem
uses 'complete' to wait response event and this context needs to sleep,
thus tasklet is not better.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/digi00x/digi00x-midi.c     | 68 ++++++++++++++++++++------
 sound/firewire/digi00x/digi00x-protocol.c | 81 ++++++++++++++++++++++++++++++-
 sound/firewire/digi00x/digi00x.h          |  7 +++
 3 files changed, 138 insertions(+), 18 deletions(-)

diff --git a/sound/firewire/digi00x/digi00x-midi.c b/sound/firewire/digi00x/digi00x-midi.c
index 460f8eb..adf31e2 100644
--- a/sound/firewire/digi00x/digi00x-midi.c
+++ b/sound/firewire/digi00x/digi00x-midi.c
@@ -13,6 +13,10 @@ static int midi_capture_open(struct snd_rawmidi_substream *substream)
 	struct snd_dg00x *dg00x = substream->rmidi->private_data;
 	int err;
 
+	/* This port is for Asynchronous transaction. */
+	if (substream->number == 0)
+		return 0;
+
 	err = snd_dg00x_stream_lock_try(dg00x);
 	if (err < 0)
 		return err;
@@ -32,6 +36,10 @@ static int midi_playback_open(struct snd_rawmidi_substream *substream)
 	struct snd_dg00x *dg00x = substream->rmidi->private_data;
 	int err;
 
+	/* This port is for Asynchronous transaction. */
+	if (substream->number == 0)
+		return 0;
+
 	err = snd_dg00x_stream_lock_try(dg00x);
 	if (err < 0)
 		return err;
@@ -50,6 +58,9 @@ static int midi_capture_close(struct snd_rawmidi_substream *substream)
 {
 	struct snd_dg00x *dg00x = substream->rmidi->private_data;
 
+	if (substream->number == 0)
+		return 0;
+
 	mutex_lock(&dg00x->mutex);
 	dg00x->capture_substreams--;
 	snd_dg00x_stream_stop_duplex(dg00x);
@@ -63,6 +74,9 @@ static int midi_playback_close(struct snd_rawmidi_substream *substream)
 {
 	struct snd_dg00x *dg00x = substream->rmidi->private_data;
 
+	if (substream->number == 0)
+		return 0;
+
 	mutex_lock(&dg00x->mutex);
 	dg00x->playback_substreams--;
 	snd_dg00x_stream_stop_duplex(dg00x);
@@ -79,12 +93,19 @@ static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
 
 	spin_lock_irqsave(&dg00x->lock, flags);
 
-	if (up)
-		amdtp_stream_midi_trigger(&dg00x->tx_stream,
-					  substrm->number, substrm);
-	else
-		amdtp_stream_midi_trigger(&dg00x->tx_stream,
-					  substrm->number, NULL);
+	if (substrm->number == 0) {
+		if (up)
+			dg00x->in_control = substrm;
+		else
+			dg00x->in_control = NULL;
+	} else {
+		if (up)
+			amdtp_stream_midi_trigger(&dg00x->tx_stream,
+						  substrm->number - 1, substrm);
+		else
+			amdtp_stream_midi_trigger(&dg00x->tx_stream,
+						  substrm->number - 1, NULL);
+	}
 
 	spin_unlock_irqrestore(&dg00x->lock, flags);
 }
@@ -96,12 +117,21 @@ static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up)
 
 	spin_lock_irqsave(&dg00x->lock, flags);
 
-	if (up)
-		amdtp_stream_midi_trigger(&dg00x->rx_stream,
-					  substrm->number, substrm);
-	else
-		amdtp_stream_midi_trigger(&dg00x->rx_stream,
-					  substrm->number, NULL);
+	if (substrm->number == 0) {
+		if (up) {
+			dg00x->out_control = substrm;
+			snd_dg00x_protocol_queue_midi_message(dg00x);
+		} else {
+			dg00x->out_control = NULL;
+		}
+	} else {
+		if (up)
+			amdtp_stream_midi_trigger(&dg00x->rx_stream,
+						  substrm->number - 1, substrm);
+		else
+			amdtp_stream_midi_trigger(&dg00x->rx_stream,
+						  substrm->number - 1, NULL);
+	}
 
 	spin_unlock_irqrestore(&dg00x->lock, flags);
 }
@@ -124,9 +154,15 @@ static void set_midi_substream_names(struct snd_dg00x *dg00x,
 	struct snd_rawmidi_substream *subs;
 
 	list_for_each_entry(subs, &str->substreams, list) {
-		snprintf(subs->name, sizeof(subs->name),
-			 "%s MIDI %d",
-			 dg00x->card->shortname, subs->number + 1);
+		/* This port is for device control. */
+		if (subs->number == 0) {
+			snprintf(subs->name, sizeof(subs->name),
+				 "%s control", dg00x->card->shortname);
+		} else {
+			snprintf(subs->name, sizeof(subs->name),
+				 "%s MIDI %d",
+				 dg00x->card->shortname, subs->number);
+		}
 	}
 }
 
@@ -137,7 +173,7 @@ int snd_dg00x_create_midi_devices(struct snd_dg00x *dg00x)
 	int err;
 
 	err = snd_rawmidi_new(dg00x->card, dg00x->card->driver, 0,
-			      1, 2, &rmidi);
+			      3, 2, &rmidi);
 	if (err < 0)
 		return err;
 
diff --git a/sound/firewire/digi00x/digi00x-protocol.c b/sound/firewire/digi00x/digi00x-protocol.c
index 72c942a..40aea36 100644
--- a/sound/firewire/digi00x/digi00x-protocol.c
+++ b/sound/firewire/digi00x/digi00x-protocol.c
@@ -8,6 +8,43 @@
 
 #include "digi00x.h"
 
+struct workqueue_struct *midi_wq;
+
+static void send_midi_control(struct work_struct *work)
+{
+	struct snd_dg00x *dg00x =
+			container_of(work, struct snd_dg00x, midi_control);
+	struct fw_device *device = fw_parent_device(dg00x->unit);
+
+	unsigned int len;
+	__be32 buf = 0;
+	u8 *b = (u8 *)&buf;
+
+	/* Send MIDI control. */
+	if (!dg00x->out_control)
+		return;
+
+	do {
+		len = snd_rawmidi_transmit(dg00x->out_control, b + 1, 2);
+		if (len > 0) {
+			b[0] = 0x80;
+			b[3] = 0xc0 | len;
+
+			/* Don't check transaction status. */
+			fw_run_transaction(device->card,
+					   TCODE_WRITE_QUADLET_REQUEST,
+					   device->node_id, device->generation,
+					   device->max_speed,
+					   0xffffe0000400, &buf, sizeof(buf));
+		}
+	} while (len > 0);
+}
+
+void snd_dg00x_protocol_queue_midi_message(struct snd_dg00x *dg00x)
+{
+	queue_work(midi_wq, &dg00x->midi_control);
+}
+
 static struct snd_dg00x *instances[SNDRV_CARDS];
 static DEFINE_SPINLOCK(instances_lock);
 
@@ -29,6 +66,26 @@ static void handle_unknown_message(struct snd_dg00x *dg00x,
 	wake_up(&dg00x->hwdep_wait);
 }
 
+static void handle_midi_control(struct snd_dg00x *dg00x, u32 *buf,
+				unsigned int length)
+{
+	unsigned int i;
+	unsigned int len;
+	u8 *b;
+
+	if (dg00x->in_control == NULL)
+		return;
+
+	length /= 4;
+
+	for (i = 0; i < length; i++) {
+		b = (u8 *)&buf[i];
+		len = b[3] & 0xf;
+		if (len > 0)
+			snd_rawmidi_receive(dg00x->in_control, b + 1, len);
+	}
+}
+
 static void handle_message(struct fw_card *card, struct fw_request *request,
 			   int tcode, int destination, int source,
 			   int generation, unsigned long long offset,
@@ -79,6 +136,16 @@ int snd_dg00x_protocol_add_instance(struct snd_dg00x *dg00x)
 	if (err < 0)
 		return err;
 
+	/* Asynchronous transactions for MIDI control message. */
+	data[0] = cpu_to_be32((device->card->node_id << 16) |
+			      (async_handler.offset >> 32));
+	data[1] = cpu_to_be32(async_handler.offset + 4);
+	err = snd_fw_transaction(dg00x->unit, TCODE_WRITE_BLOCK_REQUEST,
+				 DG00X_ADDR_BASE + DG00X_OFFSET_MIDI_CTL_ADDR,
+				 &data, sizeof(data), 0);
+	if (err < 0)
+		return err;
+
 	spin_lock_irq(&instances_lock);
 	for (i = 0; i < SNDRV_CARDS; i++) {
 		if (instances[i] != NULL)
@@ -88,6 +155,8 @@ int snd_dg00x_protocol_add_instance(struct snd_dg00x *dg00x)
 	}
 	spin_unlock_irq(&instances_lock);
 
+	INIT_WORK(&dg00x->midi_control, send_midi_control);
+
 	return 0;
 }
 
@@ -113,19 +182,27 @@ int snd_dg00x_protocol_register(void)
 	};
 	int err;
 
-	async_handler.length = 4;
+	midi_wq = alloc_workqueue("snd-digi00x",
+				  WQ_SYSFS | WQ_POWER_EFFICIENT, 0);
+	if (midi_wq == NULL)
+		return -ENOMEM;
+
+	async_handler.length = 12;
 	async_handler.address_callback = handle_message;
 	async_handler.callback_data = NULL;
 
 	err = fw_core_add_address_handler(&async_handler,
 					  &resp_register_region);
-	if (err < 0)
+	if (err < 0) {
+		destroy_workqueue(midi_wq);
 		return err;
+	}
 
 	return 0;
 }
 
 void snd_dg00x_protocol_unregister(void)
 {
+	destroy_workqueue(midi_wq);
 	fw_core_remove_address_handler(&async_handler);
 }
diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h
index 4e3d359..d534f90 100644
--- a/sound/firewire/digi00x/digi00x.h
+++ b/sound/firewire/digi00x/digi00x.h
@@ -17,6 +17,7 @@
 #include <linux/mod_devicetable.h>
 #include <linux/delay.h>
 #include <linux/slab.h>
+#include <linux/workqueue.h>
 
 #include <sound/core.h>
 #include <sound/initval.h>
@@ -56,6 +57,11 @@ struct snd_dg00x {
 
 	/* For asynchronous messages. */
 	u32 msg;
+
+	/* For asynchronous MIDI controls. */
+	struct work_struct midi_control;
+	struct snd_rawmidi_substream *in_control;
+	struct snd_rawmidi_substream *out_control;
 };
 
 #define DG00X_ADDR_BASE		0xffffe0000000ull
@@ -106,6 +112,7 @@ enum snd_dg00x_optical_mode {
 	SND_DG00X_OPT_IFACE_MODE_COUNT,
 };
 
+void snd_dg00x_protocol_queue_midi_message(struct snd_dg00x *dg00x);
 int snd_dg00x_protocol_add_instance(struct snd_dg00x *dg00x);
 void snd_dg00x_protocol_remove_instance(struct snd_dg00x *dg00x);
 int snd_dg00x_protocol_register(void);
-- 
2.1.0

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

* [PATCH 09/11] ALSA: firewire-lib: allows to implement external MIDI callback function
  2015-03-29 15:05 [RFC v2][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family Takashi Sakamoto
                   ` (7 preceding siblings ...)
  2015-03-29 15:05 ` [PATCH 08/11] ALSA: digi00x: support MIDI ports for device control Takashi Sakamoto
@ 2015-03-29 15:05 ` Takashi Sakamoto
  2015-03-29 15:05 ` [PATCH 10/11] ALSA: digi00x: improve MIDI capture/playback Takashi Sakamoto
                   ` (2 subsequent siblings)
  11 siblings, 0 replies; 15+ messages in thread
From: Takashi Sakamoto @ 2015-03-29 15:05 UTC (permalink / raw)
  To: clemens; +Cc: damien, robin, alsa-devel, ffado-devel

This commit is a preparation for devices which is not conformant to
IEC 61883-1:2005 or MMA/AMEI RP-027. This commit allows each driver to
implement own MIDI callback functions.

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

diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c
index e061355..2758fb8 100644
--- a/sound/firewire/amdtp.c
+++ b/sound/firewire/amdtp.c
@@ -68,6 +68,11 @@
 
 static void pcm_period_tasklet(unsigned long data);
 
+static void amdtp_fill_midi(struct amdtp_stream *s,
+			    __be32 *buffer, unsigned int frames);
+static void amdtp_pull_midi(struct amdtp_stream *s,
+			    __be32 *buffer, unsigned int frames);
+
 /**
  * amdtp_stream_init - initialize an AMDTP stream structure
  * @s: the AMDTP stream to initialize
@@ -236,7 +241,7 @@ sfc_found:
 	 * We do not know the actual MIDI FIFO size of most devices.  Just
 	 * assume two bytes, i.e., one byte can be received over the bus while
 	 * the previous one is transmitted over MIDI.
-	 * (The value here is adjusted for midi_ratelimit_per_packet().)
+	 * (The value here is adjusted for amdtp_midi_ratelimit_per_packet().)
 	 */
 	s->midi_fifo_limit = rate - MIDI_BYTES_PER_SECOND * s->syt_interval + 1;
 }
@@ -490,7 +495,7 @@ static void amdtp_fill_pcm_silence(struct amdtp_stream *s,
  * fractional values, the values in midi_fifo_used[] are measured in bytes
  * multiplied by the sample rate.
  */
-static bool midi_ratelimit_per_packet(struct amdtp_stream *s, unsigned int port)
+bool amdtp_midi_ratelimit_per_packet(struct amdtp_stream *s, unsigned int port)
 {
 	int used;
 
@@ -504,11 +509,13 @@ static bool midi_ratelimit_per_packet(struct amdtp_stream *s, unsigned int port)
 
 	return used < s->midi_fifo_limit;
 }
+EXPORT_SYMBOL(amdtp_midi_ratelimit_per_packet);
 
-static void midi_rate_use_one_byte(struct amdtp_stream *s, unsigned int port)
+void amdtp_midi_rate_use_one_byte(struct amdtp_stream *s, unsigned int port)
 {
 	s->midi_fifo_used[port] += amdtp_rate_table[s->sfc];
 }
+EXPORT_SYMBOL(amdtp_midi_rate_use_one_byte);
 
 static void amdtp_fill_midi(struct amdtp_stream *s,
 			    __be32 *buffer, unsigned int frames)
@@ -521,10 +528,10 @@ static void amdtp_fill_midi(struct amdtp_stream *s,
 
 		port = (s->data_block_counter + f) % 8;
 		if (f < MAX_MIDI_RX_BLOCKS &&
-		    midi_ratelimit_per_packet(s, port) &&
+		    amdtp_midi_ratelimit_per_packet(s, port) &&
 		    s->midi[port] != NULL &&
 		    snd_rawmidi_transmit(s->midi[port], &b[1], 1) == 1) {
-			midi_rate_use_one_byte(s, port);
+			amdtp_midi_rate_use_one_byte(s, port);
 			b[0] = 0x81;
 		} else {
 			b[0] = 0x80;
@@ -662,7 +669,7 @@ static void handle_out_packet(struct amdtp_stream *s, unsigned int syt)
 	else
 		amdtp_fill_pcm_silence(s, buffer, data_blocks);
 	if (s->midi_ports)
-		amdtp_fill_midi(s, buffer, data_blocks);
+		s->transfer_midi(s, buffer, data_blocks);
 
 	s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff;
 
@@ -760,7 +767,7 @@ static void handle_in_packet(struct amdtp_stream *s,
 			s->transfer_samples(s, pcm, buffer, data_blocks);
 
 		if (s->midi_ports)
-			amdtp_pull_midi(s, buffer, data_blocks);
+			s->transfer_midi(s, buffer, data_blocks);
 	}
 
 	if (s->flags & CIP_DBC_IS_END_EVENT)
@@ -925,6 +932,15 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
 	s->syt_offset_state = initial_state[s->sfc].syt_offset;
 	s->last_syt_offset = TICKS_PER_CYCLE;
 
+	/* Confirm MIDI callback function. */
+	if (s->transfer_midi == NULL) {
+		if (s->direction == AMDTP_OUT_STREAM)
+			s->transfer_midi = amdtp_fill_midi;
+		else
+			s->transfer_midi = amdtp_pull_midi;
+	}
+
+
 	/* initialize packet buffer */
 	if (s->direction == AMDTP_IN_STREAM) {
 		dir = DMA_FROM_DEVICE;
diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h
index 8a03a91..3999376 100644
--- a/sound/firewire/amdtp.h
+++ b/sound/firewire/amdtp.h
@@ -124,6 +124,8 @@ struct amdtp_stream {
 				 struct snd_pcm_substream *pcm,
 				 __be32 *buffer, unsigned int frames);
 	u8 pcm_positions[AMDTP_MAX_CHANNELS_FOR_PCM];
+	void (*transfer_midi)(struct amdtp_stream *s,
+			      __be32 *buffer, unsigned int frame);
 	u8 midi_position;
 
 	unsigned int syt_interval;
@@ -185,6 +187,9 @@ void amdtp_stream_pcm_abort(struct amdtp_stream *s);
 extern const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT];
 extern const unsigned int amdtp_rate_table[CIP_SFC_COUNT];
 
+bool amdtp_midi_ratelimit_per_packet(struct amdtp_stream *s, unsigned int port);
+void amdtp_midi_rate_use_one_byte(struct amdtp_stream *s, unsigned int port);
+
 /**
  * amdtp_stream_running - check stream is running or not
  * @s: the AMDTP stream
-- 
2.1.0

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

* [PATCH 10/11] ALSA: digi00x: improve MIDI capture/playback
  2015-03-29 15:05 [RFC v2][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family Takashi Sakamoto
                   ` (8 preceding siblings ...)
  2015-03-29 15:05 ` [PATCH 09/11] ALSA: firewire-lib: allows to implement external MIDI callback function Takashi Sakamoto
@ 2015-03-29 15:05 ` Takashi Sakamoto
  2015-03-29 15:05 ` [PATCH 11/11] ALSA: digi00x: apply double-oh-three algorithm to multiplex PCM samples Takashi Sakamoto
  2015-03-29 15:10 ` [RFC v2][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family Takashi Sakamoto
  11 siblings, 0 replies; 15+ messages in thread
From: Takashi Sakamoto @ 2015-03-29 15:05 UTC (permalink / raw)
  To: clemens; +Cc: damien, robin, alsa-devel, ffado-devel

Digi 002/003 family are not conformant to IEC 61883-6:2005 or MMA/AMEI
RP-027. In fact, they uses AM824 format data, but data channel includes
port number in its LSB. The MSB is always 0x80, even if the data channel
includes any MIDI messages. As a result, every MIDI conformant data
channel can transfer maximum 2 bytes.

This commit adds own callback functions to handle this quirk.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/digi00x/digi00x-protocol.c | 55 +++++++++++++++++++++++++++++++
 sound/firewire/digi00x/digi00x-stream.c   |  2 ++
 sound/firewire/digi00x/digi00x.h          |  1 +
 3 files changed, 58 insertions(+)

diff --git a/sound/firewire/digi00x/digi00x-protocol.c b/sound/firewire/digi00x/digi00x-protocol.c
index 40aea36..9104691 100644
--- a/sound/firewire/digi00x/digi00x-protocol.c
+++ b/sound/firewire/digi00x/digi00x-protocol.c
@@ -8,6 +8,61 @@
 
 #include "digi00x.h"
 
+static void fill_midi(struct amdtp_stream *s, __be32 *buffer,
+		      unsigned int frames)
+{
+	unsigned int f, port;
+	u8 *b;
+
+	for (f = 0; f < frames; f++) {
+		port = (s->data_block_counter + f) % 4;
+		b = (u8 *)&buffer[s->midi_position];
+
+		/*
+		 * The device allows to transfer MIDI messages by maximum two
+		 * bytes per data channel. But this module transfers one byte
+		 * one time because MIDI data rate is quite lower than IEEE
+		 * 1394 bus data rate.
+		 */
+		if (amdtp_midi_ratelimit_per_packet(s, port) &&
+		    s->midi[port] != NULL &&
+		    snd_rawmidi_transmit(s->midi[port], &b[1], 1) == 1) {
+			amdtp_midi_rate_use_one_byte(s, port);
+			b[3] = 0x01 | (0x10 << port);
+		} else {
+			b[1] = 0;
+			b[3] = 0;
+		}
+		b[0] = 0x80;
+		b[2] = 0;
+
+		buffer += s->data_block_quadlets;
+	}
+}
+
+static void pull_midi(struct amdtp_stream *s, __be32 *buffer,
+		      unsigned int frames)
+{
+	unsigned int f;
+	u8 *b;
+
+	for (f = 0; f < frames; f++) {
+		b = (u8 *)&buffer[s->midi_position];
+
+		if (s->midi[0] && (b[3] > 0))
+			snd_rawmidi_receive(s->midi[0], b + 1, b[3]);
+
+		buffer += s->data_block_quadlets;
+	}
+}
+
+/* Use own way to multiplex MIDI messages for data channels. */
+void snd_dg00x_protocol_set_midi_function(struct snd_dg00x *dg00x)
+{
+	dg00x->rx_stream.transfer_midi = fill_midi;
+	dg00x->tx_stream.transfer_midi = pull_midi;
+}
+
 struct workqueue_struct *midi_wq;
 
 static void send_midi_control(struct work_struct *work)
diff --git a/sound/firewire/digi00x/digi00x-stream.c b/sound/firewire/digi00x/digi00x-stream.c
index 5d72264..8220d53 100644
--- a/sound/firewire/digi00x/digi00x-stream.c
+++ b/sound/firewire/digi00x/digi00x-stream.c
@@ -196,6 +196,8 @@ static int keep_resources(struct snd_dg00x *dg00x, unsigned int rate)
 		dg00x->tx_stream.pcm_positions[c] = c + 1;
 	}
 
+	snd_dg00x_protocol_set_midi_function(dg00x);
+
 	return 0;
 error:
 	release_resources(dg00x);
diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h
index d534f90..b9d072c 100644
--- a/sound/firewire/digi00x/digi00x.h
+++ b/sound/firewire/digi00x/digi00x.h
@@ -112,6 +112,7 @@ enum snd_dg00x_optical_mode {
 	SND_DG00X_OPT_IFACE_MODE_COUNT,
 };
 
+void snd_dg00x_protocol_set_midi_function(struct snd_dg00x *dg00x);
 void snd_dg00x_protocol_queue_midi_message(struct snd_dg00x *dg00x);
 int snd_dg00x_protocol_add_instance(struct snd_dg00x *dg00x);
 void snd_dg00x_protocol_remove_instance(struct snd_dg00x *dg00x);
-- 
2.1.0

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

* [PATCH 11/11] ALSA: digi00x: apply double-oh-three algorithm to multiplex PCM samples
  2015-03-29 15:05 [RFC v2][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family Takashi Sakamoto
                   ` (9 preceding siblings ...)
  2015-03-29 15:05 ` [PATCH 10/11] ALSA: digi00x: improve MIDI capture/playback Takashi Sakamoto
@ 2015-03-29 15:05 ` Takashi Sakamoto
  2015-03-29 15:10 ` [RFC v2][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family Takashi Sakamoto
  11 siblings, 0 replies; 15+ messages in thread
From: Takashi Sakamoto @ 2015-03-29 15:05 UTC (permalink / raw)
  To: clemens; +Cc: damien, robin, alsa-devel, ffado-devel

Digi 002/003 family uses own way to multiplex PCM samples into data
blocks of CIP payload for received stream, thus AMDTP-conformant
implementation causes noisy sound.

This commit applies double-oh-three algorithm, which discovered by Robin
Gareus and Damien Zammit in 2012.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
---
 sound/firewire/digi00x/digi00x-pcm.c      |   9 +-
 sound/firewire/digi00x/digi00x-protocol.c | 143 ++++++++++++++++++++++++++++++
 sound/firewire/digi00x/digi00x.h          |  21 +++++
 3 files changed, 170 insertions(+), 3 deletions(-)

diff --git a/sound/firewire/digi00x/digi00x-pcm.c b/sound/firewire/digi00x/digi00x-pcm.c
index a050d67..9b54ab7 100644
--- a/sound/firewire/digi00x/digi00x-pcm.c
+++ b/sound/firewire/digi00x/digi00x-pcm.c
@@ -179,8 +179,9 @@ static int pcm_playback_hw_params(struct snd_pcm_substream *substream,
 		dg00x->playback_substreams++;
 		mutex_unlock(&dg00x->mutex);
 	}
-	amdtp_stream_set_pcm_format(&dg00x->rx_stream,
-				    params_format(hw_params));
+	/* Apply double-oh-three algorism. */
+	snd_dg00x_protocol_set_pcm_function(&dg00x->rx_stream,
+					    params_format(hw_params));
 	return snd_pcm_lib_alloc_vmalloc_buffer(substream,
 						params_buffer_bytes(hw_params));
 }
@@ -241,8 +242,10 @@ static int pcm_playback_prepare(struct snd_pcm_substream *substream)
 	mutex_lock(&dg00x->mutex);
 
 	err = snd_dg00x_stream_start_duplex(dg00x, runtime->rate);
-	if (err >= 0)
+	if (err >= 0) {
 		amdtp_stream_pcm_prepare(&dg00x->rx_stream);
+		snd_dg00x_protocol_init_state(&dg00x->state);
+	}
 
 	mutex_unlock(&dg00x->mutex);
 
diff --git a/sound/firewire/digi00x/digi00x-protocol.c b/sound/firewire/digi00x/digi00x-protocol.c
index 9104691..19c4d8b 100644
--- a/sound/firewire/digi00x/digi00x-protocol.c
+++ b/sound/firewire/digi00x/digi00x-protocol.c
@@ -2,12 +2,136 @@
  * digi00x-protocol.c - a part of driver for Digidesign Digi 002/003 family
  *
  * Copyright (c) 2014-2015 Takashi Sakamoto
+ * Copyright (C) 2012 Robin Gareus <robin@gareus.org>
+ * Copyright (C) 2012 Damien Zammit <damien@zamaudio.com>
  *
  * Licensed under the terms of the GNU General Public License, version 2.
  */
 
+#include <sound/asound.h>
 #include "digi00x.h"
 
+#define BYTE_PER_SAMPLE (4)
+#define MAGIC_DOT_BYTE (2)
+
+#define MAGIC_BYTE_OFF(x) (((x) * BYTE_PER_SAMPLE) + MAGIC_DOT_BYTE)
+
+/*
+ * double-oh-three look up table
+ *
+ * @param idx index byte (audio-sample data) 0x00..0xff
+ * @param off channel offset shift
+ * @return salt to XOR with given data
+ */
+static const __u8 dot_scrt(const __u8 idx, const unsigned int off)
+{
+	/*
+	 * the length of the added pattern only depends on the lower nibble
+	 * of the last non-zero data
+	 */
+	static const __u8 len[16] = {0, 1, 3, 5, 7, 9, 11, 13, 14,
+				     12, 10, 8, 6, 4, 2, 0};
+
+	/*
+	 * the lower nibble of the salt. Interleaved sequence.
+	 * this is walked backwards according to len[]
+	 */
+	static const __u8 nib[15] = {0x8, 0x7, 0x9, 0x6, 0xa, 0x5, 0xb, 0x4,
+				     0xc, 0x3, 0xd, 0x2, 0xe, 0x1, 0xf};
+
+	/* circular list for the salt's hi nibble. */
+	static const __u8 hir[15] = {0x0, 0x6, 0xf, 0x8, 0x7, 0x5, 0x3, 0x4,
+				     0xc, 0xd, 0xe, 0x1, 0x2, 0xb, 0xa};
+
+	/*
+	 * start offset for upper nibble mapping.
+	 * note: 9 is /special/. In the case where the high nibble == 0x9,
+	 * hir[] is not used and - coincidentally - the salt's hi nibble is
+	 * 0x09 regardless of the offset.
+	 */
+	static const __u8 hio[16] = {0, 11, 12, 6, 7, 5, 1, 4,
+				     3, 0x00, 14, 13, 8, 9, 10, 2};
+
+	const __u8 ln = idx & 0xf;
+	const __u8 hn = (idx >> 4) & 0xf;
+	const __u8 hr = (hn == 0x9) ? 0x9 : hir[(hio[hn] + off) % 15];
+
+	if (len[ln] < off)
+		return 0x00;
+
+	return ((nib[14 + off - len[ln]]) | (hr << 4));
+}
+
+static void dot_encode_step(struct dot_state *state, __be32 *const buffer)
+{
+	__u8 * const data = (__u8 *) buffer;
+
+	if (data[MAGIC_DOT_BYTE] != 0x00) {
+		state->off = 0;
+		state->idx = data[MAGIC_DOT_BYTE] ^ state->carry;
+	}
+	data[MAGIC_DOT_BYTE] ^= state->carry;
+	state->carry = dot_scrt(state->idx, ++(state->off));
+}
+
+static void write_pcm_s16(struct amdtp_stream *s, struct snd_pcm_substream *pcm,
+			  __be32 *buffer, unsigned int frames)
+{
+	struct snd_dg00x *dg00x =
+			container_of(s, struct snd_dg00x, rx_stream);
+	struct snd_pcm_runtime *runtime = pcm->runtime;
+	unsigned int channels, remaining_frames, i, c;
+	const u16 *src;
+
+	channels = s->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[s->pcm_positions[c]] =
+					cpu_to_be32((*src << 8) | 0x40000000);
+			dot_encode_step(&dg00x->state,
+					&buffer[s->pcm_positions[c]]);
+			src++;
+		}
+
+		buffer += s->data_block_quadlets;
+		if (--remaining_frames == 0)
+			src = (void *)runtime->dma_area;
+	}
+}
+
+static void write_pcm_s32(struct amdtp_stream *s, struct snd_pcm_substream *pcm,
+			  __be32 *buffer, unsigned int frames)
+{
+	struct snd_dg00x *dg00x =
+			container_of(s, struct snd_dg00x, rx_stream);
+	struct snd_pcm_runtime *runtime = pcm->runtime;
+	unsigned int channels, remaining_frames, i, c;
+	const u32 *src;
+
+	channels = s->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[s->pcm_positions[c]] =
+					cpu_to_be32((*src >> 8) | 0x40000000);
+			dot_encode_step(&dg00x->state,
+					&buffer[s->pcm_positions[c]]);
+			src++;
+		}
+
+		buffer += s->data_block_quadlets;
+		if (--remaining_frames == 0)
+			src = (void *)runtime->dma_area;
+	}
+}
+
 static void fill_midi(struct amdtp_stream *s, __be32 *buffer,
 		      unsigned int frames)
 {
@@ -56,6 +180,25 @@ static void pull_midi(struct amdtp_stream *s, __be32 *buffer,
 	}
 }
 
+void snd_dg00x_protocol_set_pcm_function(struct amdtp_stream *s,
+					 snd_pcm_format_t format)
+{
+	if (WARN_ON(amdtp_stream_pcm_running(s)))
+		return;
+
+	switch (format) {
+	default:
+		WARN_ON(1);
+		/* fall through */
+	case SNDRV_PCM_FORMAT_S16:
+		s->transfer_samples = write_pcm_s16;
+		break;
+	case SNDRV_PCM_FORMAT_S32:
+		s->transfer_samples = write_pcm_s32;
+		break;
+	}
+}
+
 /* Use own way to multiplex MIDI messages for data channels. */
 void snd_dg00x_protocol_set_midi_function(struct snd_dg00x *dg00x)
 {
diff --git a/sound/firewire/digi00x/digi00x.h b/sound/firewire/digi00x/digi00x.h
index b9d072c..96f9dea 100644
--- a/sound/firewire/digi00x/digi00x.h
+++ b/sound/firewire/digi00x/digi00x.h
@@ -33,6 +33,16 @@
 #include "../iso-resources.h"
 #include "../amdtp.h"
 
+/*
+ * The double-oh-three algorithm was discovered by Robin Gareus and Damien
+ * Zammit in 2012, with reverse-engineering for Digi 003 Rack.
+ */
+struct dot_state {
+	__u8 carry;
+	__u8 idx;
+	unsigned int off;
+};
+
 struct snd_dg00x {
 	struct snd_card *card;
 	struct fw_unit *unit;
@@ -44,6 +54,7 @@ struct snd_dg00x {
 	struct amdtp_stream tx_stream;
 	struct fw_iso_resources tx_resources;
 
+	struct dot_state state;
 	struct amdtp_stream rx_stream;
 	struct fw_iso_resources rx_resources;
 
@@ -112,6 +123,16 @@ enum snd_dg00x_optical_mode {
 	SND_DG00X_OPT_IFACE_MODE_COUNT,
 };
 
+/* Initialize dot status. */
+static inline void snd_dg00x_protocol_init_state(struct dot_state *state)
+{
+	state->carry = 0x00;
+	state->idx = 0x00;
+	state->off = 0;
+}
+
+void snd_dg00x_protocol_set_pcm_function(struct amdtp_stream *s,
+					 snd_pcm_format_t format);
 void snd_dg00x_protocol_set_midi_function(struct snd_dg00x *dg00x);
 void snd_dg00x_protocol_queue_midi_message(struct snd_dg00x *dg00x);
 int snd_dg00x_protocol_add_instance(struct snd_dg00x *dg00x);
-- 
2.1.0

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

* Re: [RFC v2][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family
  2015-03-29 15:05 [RFC v2][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family Takashi Sakamoto
                   ` (10 preceding siblings ...)
  2015-03-29 15:05 ` [PATCH 11/11] ALSA: digi00x: apply double-oh-three algorithm to multiplex PCM samples Takashi Sakamoto
@ 2015-03-29 15:10 ` Takashi Sakamoto
  2015-03-29 16:45   ` Damien Zammit
  11 siblings, 1 reply; 15+ messages in thread
From: Takashi Sakamoto @ 2015-03-29 15:10 UTC (permalink / raw)
  To: clemens; +Cc: damien, robin, alsa-devel, ffado-devel

On Mar 30 2015 00:05, Takashi Sakamoto wrote:
> Well, I'll stop my work for this driver in this developing period
> (for Linux 4.0),

Oops. '(for Linux 4.1)' is correct...


Regards

Takashi Sakamoto

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

* Re: [RFC v2][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family
  2015-03-29 15:10 ` [RFC v2][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family Takashi Sakamoto
@ 2015-03-29 16:45   ` Damien Zammit
  2015-03-31  0:23     ` Takashi Sakamoto
  0 siblings, 1 reply; 15+ messages in thread
From: Damien Zammit @ 2015-03-29 16:45 UTC (permalink / raw)
  To: Takashi Sakamoto, clemens; +Cc: alsa-devel, robin, ffado-devel

Hi folks,

I tested Takashi's latest commit 502849718c70ed0b from github on my
003Rack+ and the driver works.  I also confirm there is no hissing noise
on any channels with this driver so 003amdtp is correctly implemented.

Good job Takashi, is there any way we can push forward to get it upstream?

Damien

On 30/03/15 02:10, Takashi Sakamoto wrote:
> On Mar 30 2015 00:05, Takashi Sakamoto wrote:
>> Well, I'll stop my work for this driver in this developing period
>> (for Linux 4.0),
> 
> Oops. '(for Linux 4.1)' is correct...
> 
> 
> Regards
> 
> Takashi Sakamoto
> 

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

* Re: [RFC v2][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family
  2015-03-29 16:45   ` Damien Zammit
@ 2015-03-31  0:23     ` Takashi Sakamoto
  0 siblings, 0 replies; 15+ messages in thread
From: Takashi Sakamoto @ 2015-03-31  0:23 UTC (permalink / raw)
  To: alsa-devel

On Mar 30 2015 01:45, Damien Zammit wrote:
> Good job Takashi, is there any way we can push forward to get it upstream?

All of remained issues should be clear, somewhat. Please see the first
post of this patchset.


Regards

Takashi Sakamoto

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

end of thread, other threads:[~2015-03-31  0:23 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-03-29 15:05 [RFC v2][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family Takashi Sakamoto
2015-03-29 15:05 ` [PATCH 01/11] ALSA: digi00x: add skeleton for Digi 002/003 device driver Takashi Sakamoto
2015-03-29 15:05 ` [PATCH 02/11] ALSA: digi00x: add streaming functionality Takashi Sakamoto
2015-03-29 15:05 ` [PATCH 03/11] ALSA: digi00x: add proc node for clock status Takashi Sakamoto
2015-03-29 15:05 ` [PATCH 04/11] ALSA: digi00x: add PCM functionality Takashi Sakamoto
2015-03-29 15:05 ` [PATCH 05/11] ALSA: digi00x: add MIDI functionality Takashi Sakamoto
2015-03-29 15:05 ` [PATCH 06/11] ALSA: digi00x: add hwdep interface Takashi Sakamoto
2015-03-29 15:05 ` [PATCH 07/11] ALSA: digi00x: support unknown asynchronous message Takashi Sakamoto
2015-03-29 15:05 ` [PATCH 08/11] ALSA: digi00x: support MIDI ports for device control Takashi Sakamoto
2015-03-29 15:05 ` [PATCH 09/11] ALSA: firewire-lib: allows to implement external MIDI callback function Takashi Sakamoto
2015-03-29 15:05 ` [PATCH 10/11] ALSA: digi00x: improve MIDI capture/playback Takashi Sakamoto
2015-03-29 15:05 ` [PATCH 11/11] ALSA: digi00x: apply double-oh-three algorithm to multiplex PCM samples Takashi Sakamoto
2015-03-29 15:10 ` [RFC v2][PATCH 00/11] digi00x: new driver for Digidesign 002/003 family Takashi Sakamoto
2015-03-29 16:45   ` Damien Zammit
2015-03-31  0:23     ` Takashi Sakamoto

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).