All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] Add M2Tech hiFace USB-SPDIF driver
@ 2013-02-10 23:11 Antonio Ospite
  2013-02-11  8:53 ` Clemens Ladisch
                   ` (2 more replies)
  0 siblings, 3 replies; 25+ messages in thread
From: Antonio Ospite @ 2013-02-10 23:11 UTC (permalink / raw)
  To: alsa-devel
  Cc: Takashi Iwai, Antonio Ospite, fchecconi, Pietro Cipriano,
	alberto, Michael Trimarchi

Add driver for M2Tech hiFace USB-SPDIF interface and compatible devices.

The supported products are:

  * M2Tech Young
  * M2Tech hiFace
  * M2Tech North Star
  * M2Tech W4S Young
  * M2Tech Corrson
  * M2Tech AUDIA
  * M2Tech SL Audio
  * M2Tech Empirical
  * M2Tech Rockna
  * M2Tech Pathos
  * M2Tech Metronome
  * M2Tech CAD
  * M2Tech Audio Esclusive
  * M2Tech Rotel
  * M2Tech Eeaudio
  * The Chord Company CHORD
  * AVA Group A/S Vitus

Signed-off-by: Antonio Ospite <ao2@amarulasolutions.com>
---

Hi,

this is my first ALSA driver I still have some doubts and I marked them in the
comments below with a XXX prefix, could someone please take a look at them?

I plan on submitting a version 2 of the driver after I get some feedback,
especially on those doubts.

I also noticed that many drivers under sound/usb/ have text in the Kconfig
entry stating "Say Y here to include support for ..." but I only see "[N/m/?]"
as options when I run "make oldconfig" maybe because of some dependency, I am
not sure, should such text be removed in order to avoid confusion?

Thanks,
   Antonio

 sound/usb/Kconfig         |   33 +++
 sound/usb/Makefile        |    2 +-
 sound/usb/hiface/Makefile |    2 +
 sound/usb/hiface/chip.c   |  331 ++++++++++++++++++++++
 sound/usb/hiface/chip.h   |   34 +++
 sound/usb/hiface/pcm.c    |  666 +++++++++++++++++++++++++++++++++++++++++++++
 sound/usb/hiface/pcm.h    |   24 ++
 7 files changed, 1091 insertions(+), 1 deletion(-)
 create mode 100644 sound/usb/hiface/Makefile
 create mode 100644 sound/usb/hiface/chip.c
 create mode 100644 sound/usb/hiface/chip.h
 create mode 100644 sound/usb/hiface/pcm.c
 create mode 100644 sound/usb/hiface/pcm.h

diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig
index 225dfd7..2b206e8 100644
--- a/sound/usb/Kconfig
+++ b/sound/usb/Kconfig
@@ -115,5 +115,38 @@ config SND_USB_6FIRE
           and further help can be found at
           http://sixfireusb.sourceforge.net
 
+config SND_USB_HIFACE
+        tristate "M2Tech hiFace USB-SPDIF driver"
+        select SND_PCM
+        help
+	  Say Y here to include support for M2Tech hiFace USB-SPDIF interface.
+
+	  M2Tech hiFace and compatible devices offer a Hi-End S/PDIF Output
+	  Interface, see http://www.m2tech.biz/hiface.html
+
+	  This driver supports the original M2Tech hiFace and some other
+	  compatible devices. The supported products are:
+
+	    * M2Tech Young
+	    * M2Tech hiFace
+	    * M2Tech North Star
+	    * M2Tech W4S Young
+	    * M2Tech Corrson
+	    * M2Tech AUDIA
+	    * M2Tech SL Audio
+	    * M2Tech Empirical
+	    * M2Tech Rockna
+	    * M2Tech Pathos
+	    * M2Tech Metronome
+	    * M2Tech CAD
+	    * M2Tech Audio Esclusive
+	    * M2Tech Rotel
+	    * M2Tech Eeaudio
+	    * The Chord Company CHORD
+	    * AVA Group A/S Vitus
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-usb-hiface.
+
 endif	# SND_USB
 
diff --git a/sound/usb/Makefile b/sound/usb/Makefile
index ac256dc..abe668f 100644
--- a/sound/usb/Makefile
+++ b/sound/usb/Makefile
@@ -23,4 +23,4 @@ obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o
 obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o
 obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o
 
-obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/
+obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/
diff --git a/sound/usb/hiface/Makefile b/sound/usb/hiface/Makefile
new file mode 100644
index 0000000..463b136
--- /dev/null
+++ b/sound/usb/hiface/Makefile
@@ -0,0 +1,2 @@
+snd-usb-hiface-objs := chip.o pcm.o
+obj-$(CONFIG_SND_USB_HIFACE) += snd-usb-hiface.o
diff --git a/sound/usb/hiface/chip.c b/sound/usb/hiface/chip.c
new file mode 100644
index 0000000..dce74e9
--- /dev/null
+++ b/sound/usb/hiface/chip.c
@@ -0,0 +1,331 @@
+/*
+ * Linux driver for M2Tech HiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
+ *           Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <sound/initval.h>
+
+#include "chip.h"
+#include "pcm.h"
+
+MODULE_AUTHOR("Michael Trimarchi <michael@amarulasolutions.com>");
+MODULE_AUTHOR("Antonio Ospite <ao2@amarulasolutions.com>");
+MODULE_DESCRIPTION("M2Tech HiFace USB audio driver");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{HiFace, Evo}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
+
+#define DRIVER_NAME "snd-usb-hiface"
+#define CARD_NAME "HiFace"
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
+
+static struct hiface_chip *chips[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+static DEFINE_MUTEX(register_mutex);
+
+struct hiface_vendor_quirk {
+	const char *device_name;
+	u8 extra_freq;
+};
+
+static int hiface_dev_free(struct snd_device *device)
+{
+	struct hiface_chip *chip = device->device_data;
+	kfree(chip);
+	return 0;
+}
+
+static int hiface_chip_create(struct usb_device *device, int idx,
+			      const struct hiface_vendor_quirk *quirk,
+			      struct hiface_chip **rchip)
+{
+	struct snd_card *card = NULL;
+	struct hiface_chip *chip;
+	int ret;
+	static struct snd_device_ops ops = {
+		.dev_free =	hiface_dev_free,
+	};
+
+	*rchip = NULL;
+
+	/* if we are here, card can be registered in alsa. */
+	ret = snd_card_create(index[idx], id[idx], THIS_MODULE, 0, &card);
+	if (ret < 0) {
+		snd_printk(KERN_ERR "cannot create alsa card.\n");
+		return ret;
+	}
+
+	strcpy(card->driver, DRIVER_NAME);
+
+	if (quirk && quirk->device_name)
+		strcpy(card->shortname, quirk->device_name);
+	else
+		strcpy(card->shortname, "M2Tech generic audio");
+
+	sprintf(card->longname, "%s at %d:%d", card->shortname,
+			device->bus->busnum, device->devnum);
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (!chip) {
+		snd_card_free(card);
+		return -ENOMEM;
+	}
+
+	chip->dev = device;
+	chip->index = idx;
+	chip->card = card;
+
+	ret = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (ret < 0) {
+			kfree(chip);
+			snd_card_free(card);
+			return ret;
+	}
+
+	*rchip = chip;
+	return 0;
+}
+
+static int hiface_chip_probe(struct usb_interface *intf,
+			     const struct usb_device_id *usb_id)
+{
+	const struct hiface_vendor_quirk *quirk = (struct hiface_vendor_quirk *)usb_id->driver_info;
+	int ret;
+	int i;
+	struct hiface_chip *chip;
+	struct usb_device *device = interface_to_usbdev(intf);
+
+	pr_info("Probe " DRIVER_NAME " driver.\n");
+
+	ret = usb_set_interface(device, 0, 0);
+	if (ret != 0) {
+		snd_printk(KERN_ERR "can't set first interface for " CARD_NAME " device.\n");
+		return -EIO;
+	}
+
+	/* check whether the card is already registered */
+	chip = NULL;
+	mutex_lock(&register_mutex);
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		if (chips[i] && chips[i]->dev == device) {
+			if (chips[i]->shutdown) {
+				snd_printk(KERN_ERR CARD_NAME " device is in the shutdown state, cannot create a card instance\n");
+				ret = -ENODEV;
+				goto err;
+			}
+			chip = chips[i];
+			break;
+		}
+	}
+	if (!chip) {
+		/* it's a fresh one.
+		 * now look for an empty slot and create a new card instance
+		 */
+		for (i = 0; i < SNDRV_CARDS; i++)
+			if (enable[i] && !chips[i]) {
+				ret = hiface_chip_create(device, i, quirk,
+							 &chip);
+				if (ret < 0)
+					goto err;
+
+				snd_card_set_dev(chip->card, &intf->dev);
+				break;
+			}
+		if (!chip) {
+			snd_printk(KERN_ERR "no available " CARD_NAME " audio device\n");
+			ret = -ENODEV;
+			goto err;
+		}
+	}
+
+	ret = hiface_pcm_init(chip, quirk ? quirk->extra_freq : 0);
+	if (ret < 0)
+		goto err_chip_destroy;
+
+	ret = snd_card_register(chip->card);
+	if (ret < 0) {
+		snd_printk(KERN_ERR "cannot register " CARD_NAME " card\n");
+		goto err_chip_destroy;
+	}
+
+	chips[chip->index] = chip;
+	chip->intf_count++;
+
+	mutex_unlock(&register_mutex);
+
+	usb_set_intfdata(intf, chip);
+	return 0;
+
+err_chip_destroy:
+	snd_card_free(chip->card);
+err:
+	mutex_unlock(&register_mutex);
+	return ret;
+}
+
+static void hiface_chip_disconnect(struct usb_interface *intf)
+{
+	struct hiface_chip *chip;
+	struct snd_card *card;
+
+	pr_debug("%s: called.\n", __func__);
+
+	chip = usb_get_intfdata(intf);
+	if (!chip)
+		return;
+
+	card = chip->card;
+	chip->intf_count--;
+	if (chip->intf_count <= 0) {
+		/* Make sure that the userspace cannot create new request */
+		snd_card_disconnect(card);
+
+		mutex_lock(&register_mutex);
+		chips[chip->index] = NULL;
+		mutex_unlock(&register_mutex);
+
+		chip->shutdown = true;
+		hiface_pcm_abort(chip);
+		snd_card_free_when_closed(card);
+	}
+}
+
+static struct usb_device_id device_table[] = {
+	{
+		USB_DEVICE(0x04b4, 0x0384),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Young",
+			.extra_freq = 1,
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x930b),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "hiFace",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931b),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "North Star",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931c),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "W4S Young",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931d),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Corrson",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931e),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "AUDIA",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931f),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "SL Audio",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x9320),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Empirical",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x9321),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Rockna",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x9001),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Pathos",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x9002),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Metronome",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x9006),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "CAD",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x9008),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Audio Esclusive",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x931c),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Rotel",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x932c),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Eeaudio",
+		}
+	},
+	{
+		USB_DEVICE(0x245f, 0x931c),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "CHORD",
+		}
+	},
+	{
+		USB_DEVICE(0x25c6, 0x9002),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Vitus",
+		}
+	},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+static struct usb_driver hiface_usb_driver = {
+	.name = DRIVER_NAME,
+	.probe = hiface_chip_probe,
+	.disconnect = hiface_chip_disconnect,
+	.id_table = device_table,
+};
+
+module_usb_driver(hiface_usb_driver);
diff --git a/sound/usb/hiface/chip.h b/sound/usb/hiface/chip.h
new file mode 100644
index 0000000..c2e9fda
--- /dev/null
+++ b/sound/usb/hiface/chip.h
@@ -0,0 +1,34 @@
+/*
+ * Linux driver for M2Tech HiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
+ *           Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef HIFACE_CHIP_H
+#define HIFACE_CHIP_H
+
+#include <linux/usb.h>
+#include <sound/core.h>
+
+struct pcm_runtime;
+
+struct hiface_chip {
+	struct usb_device *dev;
+	struct snd_card *card;
+	int intf_count; /* number of registered interfaces */
+	int index; /* index in module parameter arrays */
+	bool shutdown;
+
+	struct pcm_runtime *pcm;
+};
+#endif /* HIFACE_CHIP_H */
diff --git a/sound/usb/hiface/pcm.c b/sound/usb/hiface/pcm.c
new file mode 100644
index 0000000..34fe0ed
--- /dev/null
+++ b/sound/usb/hiface/pcm.c
@@ -0,0 +1,666 @@
+/*
+ * Linux driver for M2Tech HiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
+ *           Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/slab.h>
+#include <sound/pcm.h>
+
+#include "pcm.h"
+#include "chip.h"
+
+#define OUT_EP              0x2
+#define PCM_N_URBS          8
+#define PCM_MAX_PACKET_SIZE 4096
+#define MAX_BUFSIZE         (2 * PCM_N_URBS * PCM_MAX_PACKET_SIZE)
+
+struct pcm_urb {
+	struct hiface_chip *chip;
+
+	struct urb instance;
+	struct usb_anchor submitted;
+	u8 *buffer;
+};
+
+struct pcm_substream {
+	spinlock_t lock;
+	struct snd_pcm_substream *instance;
+
+	bool active;
+	snd_pcm_uframes_t dma_off;    /* current position in alsa dma_area */
+	snd_pcm_uframes_t period_off; /* current position in current period */
+};
+
+enum { /* pcm streaming states */
+	STREAM_DISABLED, /* no pcm streaming */
+	STREAM_STARTING, /* pcm streaming requested, waiting to become ready */
+	STREAM_RUNNING,  /* pcm streaming running */
+	STREAM_STOPPING
+};
+
+struct pcm_runtime {
+	struct hiface_chip *chip;
+	struct snd_pcm *instance;
+
+	struct pcm_substream playback;
+	bool panic; /* if set driver won't do anymore pcm on device */
+
+	struct pcm_urb out_urbs[PCM_N_URBS];
+
+	struct mutex stream_mutex;
+	u8 stream_state; /* one of STREAM_XXX */
+	u8 rate; /* one of PCM_RATE_XXX */
+	u8 extra_freq;
+	wait_queue_head_t stream_wait_queue;
+	bool stream_wait_cond;
+};
+
+static const unsigned int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000,
+			     352800, 384000 };
+static struct snd_pcm_hw_constraint_list constraints_rates = {
+	.count = ARRAY_SIZE(rates) - 2, /* by default rates up to 192000 are supported */
+	.list = rates,
+	.mask = 0,
+};
+
+static const int rates_alsaid[] = {
+	SNDRV_PCM_RATE_44100, SNDRV_PCM_RATE_48000,
+	SNDRV_PCM_RATE_88200, SNDRV_PCM_RATE_96000,
+	SNDRV_PCM_RATE_176400, SNDRV_PCM_RATE_192000,
+	SNDRV_PCM_RATE_KNOT, SNDRV_PCM_RATE_KNOT };
+
+static const struct snd_pcm_hardware pcm_hw = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_BATCH,
+
+	.formats = SNDRV_PCM_FMTBIT_S32_LE,
+
+	.rates = SNDRV_PCM_RATE_44100 |
+		SNDRV_PCM_RATE_48000 |
+		SNDRV_PCM_RATE_88200 |
+		SNDRV_PCM_RATE_96000 |
+		SNDRV_PCM_RATE_176400 |
+		SNDRV_PCM_RATE_192000 |
+		SNDRV_PCM_RATE_KNOT,
+
+	.rate_min = 44100,
+	.rate_max = 192000, /* changes in hiface_pcm_open to support extra rates */
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = MAX_BUFSIZE,
+	.period_bytes_min = PCM_MAX_PACKET_SIZE,
+	.period_bytes_max = MAX_BUFSIZE,
+	.periods_min = 2,
+	.periods_max = 1024
+};
+
+static int hiface_pcm_set_rate(struct pcm_runtime *rt, unsigned int rate)
+{
+	u8 rate_value[] = { 0x43, 0x4b, 0x42, 0x4a, 0x40, 0x48, 0x58, 0x68 };
+	int ret;
+	struct usb_device *device = rt->chip->dev;
+
+	/* We are already sure that the rate is supported here thanks to the
+	 * contraints set in hiface_pcm_open(), but we have to retrieve the
+	 * index to access rate_value.
+	 */
+	for (rt->rate = 0; rt->rate < ARRAY_SIZE(rates); rt->rate++)
+		if (rate == rates[rt->rate])
+			break;
+
+	if (unlikely(rt->rate == ARRAY_SIZE(rates))) {
+		pr_err("Unsupported rate %d\n", rate);
+		return -EINVAL;
+	}
+
+	/*
+	 * USBIO: Vendor 0xb0(wValue=0x0043, wIndex=0x0000)
+	 * 43 b0 43 00 00 00 00 00
+	 * USBIO: Vendor 0xb0(wValue=0x004b, wIndex=0x0000)
+	 * 43 b0 4b 00 00 00 00 00
+	 * This control message doesn't have any ack from the
+	 * other side
+	 */
+	ret = usb_control_msg(device, usb_sndctrlpipe(device, 0),
+				0xb0,
+				USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+				rate_value[rt->rate], 0, NULL, 0, 100);
+	if (ret < 0) {
+		snd_printk(KERN_ERR "Error setting samplerate %d.\n",
+				rates[rt->rate]);
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct pcm_substream *hiface_pcm_get_substream(
+		struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+
+	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		return &rt->playback;
+
+	pr_debug("Error getting pcm substream slot.\n");
+	return NULL;
+}
+
+/* call with stream_mutex locked */
+static void hiface_pcm_stream_stop(struct pcm_runtime *rt)
+{
+	int i, time;
+
+	if (rt->stream_state != STREAM_DISABLED) {
+		rt->stream_state = STREAM_STOPPING;
+
+		for (i = 0; i < PCM_N_URBS; i++) {
+			time = usb_wait_anchor_empty_timeout(
+					&rt->out_urbs[i].submitted, 100);
+			if (!time)
+				usb_kill_anchored_urbs(
+					&rt->out_urbs[i].submitted);
+			usb_kill_urb(&rt->out_urbs[i].instance);
+		}
+
+		rt->stream_state = STREAM_DISABLED;
+	}
+}
+
+/* call with stream_mutex locked */
+static int hiface_pcm_stream_start(struct pcm_runtime *rt)
+{
+	int ret = 0, i;
+
+	if (rt->stream_state == STREAM_DISABLED) {
+		/* submit our out urbs zero init */
+		rt->stream_state = STREAM_STARTING;
+		for (i = 0; i < PCM_N_URBS; i++) {
+			memset(rt->out_urbs[i].buffer, 0, PCM_MAX_PACKET_SIZE);
+			usb_anchor_urb(&rt->out_urbs[i].instance,
+				       &rt->out_urbs[i].submitted);
+			ret = usb_submit_urb(&rt->out_urbs[i].instance,
+					GFP_ATOMIC);
+			if (ret) {
+				hiface_pcm_stream_stop(rt);
+				return ret;
+			}
+		}
+
+		/* wait for first out urb to return (sent in in urb handler) */
+		wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond,
+				HZ);
+		if (rt->stream_wait_cond) {
+			pr_debug("%s: Stream is running wakeup event\n",
+				 __func__);
+			rt->stream_state = STREAM_RUNNING;
+		} else {
+			hiface_pcm_stream_stop(rt);
+			return -EIO;
+		}
+	}
+	return ret;
+}
+
+
+/* The hardware wants word-swapped 32-bit values */
+void memcpy_swahw32(u8 *dest, u8 *src, unsigned int n)
+{
+	unsigned int i;
+
+	for (i = 0; i < n / 4; i++)
+		((u32 *)dest)[i] = swahw32(((u32 *)src)[i]);
+}
+
+/* call with substream locked */
+static int hiface_pcm_playback(struct pcm_substream *sub,
+		struct pcm_urb *urb)
+{
+	struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
+	u8 *source;
+	unsigned int pcm_buffer_size;
+
+	/* XXX Can an invalid format make it to here?
+	 * Isn't ALSA checking pcm_hw.formats ?
+	 */
+	if (alsa_rt->format != SNDRV_PCM_FORMAT_S32_LE) {
+		pr_err("Unsupported sample format\n");
+		return -EINVAL;
+	}
+
+	pcm_buffer_size = snd_pcm_lib_buffer_bytes(sub->instance);
+
+	if (sub->dma_off + PCM_MAX_PACKET_SIZE <= pcm_buffer_size) {
+		pr_debug("%s: (1) buffer_size %#x dma_offset %#x\n", __func__,
+			 (unsigned int) pcm_buffer_size,
+			 (unsigned int) sub->dma_off);
+
+		source = alsa_rt->dma_area + sub->dma_off;
+		memcpy_swahw32(urb->buffer, source, PCM_MAX_PACKET_SIZE);
+	} else {
+		/* wrap around at end of ring buffer */
+		unsigned int len;
+
+		pr_debug("%s: (2) buffer_size %#x dma_offset %#x\n", __func__,
+			 (unsigned int) pcm_buffer_size,
+			 (unsigned int) sub->dma_off);
+
+		len = pcm_buffer_size - sub->dma_off;
+
+		source = alsa_rt->dma_area + sub->dma_off;
+		memcpy_swahw32(urb->buffer, source, len);
+
+		source = alsa_rt->dma_area;
+		memcpy_swahw32(urb->buffer + len, source,
+			       PCM_MAX_PACKET_SIZE - len);
+	}
+	sub->dma_off += PCM_MAX_PACKET_SIZE;
+	if (sub->dma_off >= pcm_buffer_size)
+		sub->dma_off -= pcm_buffer_size;
+
+	sub->period_off += PCM_MAX_PACKET_SIZE;
+
+	return 0;
+}
+
+static void hiface_pcm_out_urb_handler(struct urb *usb_urb)
+{
+	struct pcm_urb *out_urb = usb_urb->context;
+	struct pcm_runtime *rt = out_urb->chip->pcm;
+	struct pcm_substream *sub;
+	unsigned long flags;
+
+	pr_debug("%s: called.\n", __func__);
+
+	if (usb_urb->status || rt->panic || rt->stream_state == STREAM_STOPPING)
+		return;
+
+	if (rt->stream_state == STREAM_STARTING) {
+		rt->stream_wait_cond = true;
+		wake_up(&rt->stream_wait_queue);
+	}
+
+	/* now send our playback data (if a free out urb was found) */
+	sub = &rt->playback;
+	spin_lock_irqsave(&sub->lock, flags);
+	if (sub->active) {
+		int ret;
+
+		ret = hiface_pcm_playback(sub, out_urb);
+		if (ret < 0) {
+			spin_unlock_irqrestore(&sub->lock, flags);
+			goto out_fail;
+		}
+		if (sub->period_off >= sub->instance->runtime->period_size) {
+			sub->period_off %= sub->instance->runtime->period_size;
+			spin_unlock_irqrestore(&sub->lock, flags);
+			snd_pcm_period_elapsed(sub->instance);
+		} else {
+			spin_unlock_irqrestore(&sub->lock, flags);
+		}
+	} else {
+		memset(out_urb->buffer, 0, PCM_MAX_PACKET_SIZE);
+		spin_unlock_irqrestore(&sub->lock, flags);
+	}
+out_fail:
+	usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
+}
+
+static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	struct pcm_substream *sub = NULL;
+	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
+	int ret;
+
+	pr_debug("%s: called.\n", __func__);
+
+	if (rt->panic)
+		return -EPIPE;
+
+	mutex_lock(&rt->stream_mutex);
+	alsa_rt->hw = pcm_hw;
+
+	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+
+		/* XXX can we avoid setting hw.rates below?
+		 *
+		 * FWIU setting alsa_rt->hw.rates in the open callback can be
+		 * useful in two cases:
+		 *
+		 *   - different hardware revisions have different
+		 *     capabilities
+		 *
+		 *   - different stream types must have the same capabilities
+		 *
+		 * but in our case it should not be needed as we only have one
+		 * playback stream and we define the capabilities via
+		 * constraints already to avoid invalid rates to be used.
+		 *
+		 * Moreover, following the code stolen from 6fire it seems
+		 * impossible to get to setting alsa_rt->hw.rates here:
+		 *
+		 *   hiface_pcm_init:
+		 *     rt->rate = ARRAY_SIZE(rates);
+		 *
+		 *   hiface_pcm_open:
+		 *     if (rt->rate < ARRAY_SIZE(rates))
+		 *       alsa_rt->hw.rates = rates_alsaid[rt->rate];
+		 *       // So it does not get here on the first open
+		 *
+		 *   hiface_pcm_prepare:
+		 *     hiface_pcm_set_rate:
+		 *       for (rt->rate = 0; rt->rate < ARRAY_SIZE(rates); rt->rate++)
+		 *         if (rate == rates[rt->rate])
+		 *           break;
+		 *
+		 *   hiface_pcm_close:
+		 *     rt->rate = ARRAY_SIZE(rates);
+		 *     // So it won't set alsa_rt->hw.rates at the next open either
+		 *
+		 * So the only way to set alsa_rt->hw.rates is to have two
+		 * calls to the open callback, is that even possible? ALSA
+		 * does not allow sharing PCM streams AFAIK and we use only
+		 * one playback stream.
+		 *
+		 * Maybe the 6fire driver was setting alsa_rt->hw.rates in
+		 * order to have the playback stream and the capture stream
+		 * use the same rate? I.e. the first open,prepare,set_rate()
+		 * sequence on one type of stream will decide the rate and the
+		 * second open on the other type would restrict the supported
+		 * rate to the one already in use, but I am not sure.
+		 *
+		 * If the rationale makes sense then we don't need to set
+		 * alsa_rt->hw.rates and we can remove this and related code.
+		 */
+		if (rt->rate < ARRAY_SIZE(rates))
+			alsa_rt->hw.rates = rates_alsaid[rt->rate];
+
+		sub = &rt->playback;
+	}
+
+	if (!sub) {
+		mutex_unlock(&rt->stream_mutex);
+		pr_err("Invalid stream type\n");
+		return -EINVAL;
+	}
+
+	if (rt->extra_freq) {
+		alsa_rt->hw.rate_max = 384000;
+		constraints_rates.count = ARRAY_SIZE(rates);
+	}
+
+	ret = snd_pcm_hw_constraint_list(alsa_sub->runtime, 0,
+					 SNDRV_PCM_HW_PARAM_RATE,
+					 &constraints_rates);
+	if (ret < 0) {
+		mutex_unlock(&rt->stream_mutex);
+		return ret;
+	}
+
+	sub->instance = alsa_sub;
+	sub->active = false;
+	mutex_unlock(&rt->stream_mutex);
+	return 0;
+}
+
+static int hiface_pcm_close(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+	unsigned long flags;
+
+	if (rt->panic)
+		return 0;
+
+	pr_debug("%s: called.\n", __func__);
+
+	mutex_lock(&rt->stream_mutex);
+	if (sub) {
+		/* deactivate substream */
+		spin_lock_irqsave(&sub->lock, flags);
+		sub->instance = NULL;
+		sub->active = false;
+		spin_unlock_irqrestore(&sub->lock, flags);
+
+		/* all substreams closed? if so, stop streaming */
+		if (!rt->playback.instance) {
+			hiface_pcm_stream_stop(rt);
+			rt->rate = ARRAY_SIZE(rates);
+		}
+	}
+	mutex_unlock(&rt->stream_mutex);
+	return 0;
+}
+
+static int hiface_pcm_hw_params(struct snd_pcm_substream *alsa_sub,
+		struct snd_pcm_hw_params *hw_params)
+{
+	pr_debug("%s: called.\n", __func__);
+	return snd_pcm_lib_malloc_pages(alsa_sub,
+			params_buffer_bytes(hw_params));
+}
+
+static int hiface_pcm_hw_free(struct snd_pcm_substream *alsa_sub)
+{
+	pr_debug("%s: called.\n", __func__);
+	return snd_pcm_lib_free_pages(alsa_sub);
+}
+
+static int hiface_pcm_prepare(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
+	int ret;
+
+	pr_debug("%s: called.\n", __func__);
+
+	if (rt->panic)
+		return -EPIPE;
+	if (!sub)
+		return -ENODEV;
+
+	mutex_lock(&rt->stream_mutex);
+
+	sub->dma_off = 0;
+	sub->period_off = 0;
+
+	if (rt->stream_state == STREAM_DISABLED) {
+
+		ret = hiface_pcm_set_rate(rt, alsa_rt->rate);
+		if (ret) {
+			mutex_unlock(&rt->stream_mutex);
+			return ret;
+		}
+		ret = hiface_pcm_stream_start(rt);
+		if (ret) {
+			mutex_unlock(&rt->stream_mutex);
+			return ret;
+		}
+	}
+	mutex_unlock(&rt->stream_mutex);
+	return 0;
+}
+
+static int hiface_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
+{
+	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	unsigned long flags;
+
+	pr_debug("%s: called.\n", __func__);
+
+	if (rt->panic)
+		return -EPIPE;
+	if (!sub)
+		return -ENODEV;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		spin_lock_irqsave(&sub->lock, flags);
+		sub->active = true;
+		spin_unlock_irqrestore(&sub->lock, flags);
+		return 0;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		spin_lock_irqsave(&sub->lock, flags);
+		sub->active = false;
+		spin_unlock_irqrestore(&sub->lock, flags);
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static snd_pcm_uframes_t hiface_pcm_pointer(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	unsigned long flags;
+	snd_pcm_uframes_t dma_offset;
+
+	if (rt->panic || !sub)
+		return SNDRV_PCM_STATE_XRUN;
+
+	spin_lock_irqsave(&sub->lock, flags);
+	dma_offset = sub->dma_off;
+	spin_unlock_irqrestore(&sub->lock, flags);
+	return bytes_to_frames(alsa_sub->runtime, dma_offset);
+}
+
+static struct snd_pcm_ops pcm_ops = {
+	.open = hiface_pcm_open,
+	.close = hiface_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = hiface_pcm_hw_params,
+	.hw_free = hiface_pcm_hw_free,
+	.prepare = hiface_pcm_prepare,
+	.trigger = hiface_pcm_trigger,
+	.pointer = hiface_pcm_pointer,
+};
+
+static int hiface_pcm_init_urb(struct pcm_urb *urb,
+			       struct hiface_chip *chip,
+			       unsigned int ep,
+			       void (*handler)(struct urb *))
+{
+	urb->chip = chip;
+	usb_init_urb(&urb->instance);
+
+	urb->buffer = kzalloc(PCM_MAX_PACKET_SIZE, GFP_KERNEL);
+	if (!urb->buffer)
+		return -ENOMEM;
+
+	usb_fill_bulk_urb(&urb->instance, chip->dev,
+			  usb_sndbulkpipe(chip->dev, ep), (void *)urb->buffer,
+			  PCM_MAX_PACKET_SIZE, handler, urb);
+	init_usb_anchor(&urb->submitted);
+
+	return 0;
+}
+
+void hiface_pcm_abort(struct hiface_chip *chip)
+{
+	struct pcm_runtime *rt = chip->pcm;
+
+	if (rt) {
+		rt->panic = true;
+
+		if (rt->playback.instance) {
+			snd_pcm_stop(rt->playback.instance,
+					SNDRV_PCM_STATE_XRUN);
+		}
+		mutex_lock(&rt->stream_mutex);
+		hiface_pcm_stream_stop(rt);
+		mutex_unlock(&rt->stream_mutex);
+	}
+}
+
+static void hiface_pcm_destroy(struct hiface_chip *chip)
+{
+	struct pcm_runtime *rt = chip->pcm;
+	int i;
+
+	for (i = 0; i < PCM_N_URBS; i++)
+		kfree(rt->out_urbs[i].buffer);
+
+	kfree(chip->pcm);
+	chip->pcm = NULL;
+}
+
+static void hiface_pcm_free(struct snd_pcm *pcm)
+{
+	struct pcm_runtime *rt = pcm->private_data;
+
+	pr_debug("%s: called.\n", __func__);
+
+	if (rt)
+		hiface_pcm_destroy(rt->chip);
+}
+
+int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq)
+{
+	int i;
+	int ret;
+	struct snd_pcm *pcm;
+	struct pcm_runtime *rt;
+
+	rt = kzalloc(sizeof(*rt), GFP_KERNEL);
+	if (!rt)
+		return -ENOMEM;
+
+	rt->chip = chip;
+	rt->stream_state = STREAM_DISABLED;
+	rt->rate = ARRAY_SIZE(rates);
+	if (extra_freq)
+		rt->extra_freq = 1;
+
+	init_waitqueue_head(&rt->stream_wait_queue);
+	mutex_init(&rt->stream_mutex);
+	spin_lock_init(&rt->playback.lock);
+
+	for (i = 0; i < PCM_N_URBS; i++)
+		hiface_pcm_init_urb(&rt->out_urbs[i], chip, OUT_EP,
+				hiface_pcm_out_urb_handler);
+
+	ret = snd_pcm_new(chip->card, "USB-SPDIF Audio", 0, 1, 0, &pcm);
+	if (ret < 0) {
+		kfree(rt);
+		pr_err("Cannot create pcm instance\n");
+		return ret;
+	}
+
+	pcm->private_data = rt;
+	pcm->private_free = hiface_pcm_free;
+
+	strcpy(pcm->name, "USB-SPDIF Audio");
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm,
+					SNDRV_DMA_TYPE_CONTINUOUS,
+					snd_dma_continuous_data(GFP_KERNEL),
+					MAX_BUFSIZE, MAX_BUFSIZE);
+	rt->instance = pcm;
+
+	chip->pcm = rt;
+	return 0;
+}
diff --git a/sound/usb/hiface/pcm.h b/sound/usb/hiface/pcm.h
new file mode 100644
index 0000000..ddc9fe7
--- /dev/null
+++ b/sound/usb/hiface/pcm.h
@@ -0,0 +1,24 @@
+/*
+ * Linux driver for M2Tech HiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
+ *           Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef HIFACE_PCM_H
+#define HIFACE_PCM_H
+
+struct hiface_chip;
+
+int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq);
+void hiface_pcm_abort(struct hiface_chip *chip);
+#endif /* HIFACE_PCM_H */
-- 
1.7.10.4

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

* Re: [PATCH] Add M2Tech hiFace USB-SPDIF driver
  2013-02-10 23:11 [PATCH] Add M2Tech hiFace USB-SPDIF driver Antonio Ospite
@ 2013-02-11  8:53 ` Clemens Ladisch
  2013-02-12 12:35   ` Antonio Ospite
  2013-02-13 17:11 ` [PATCH v2] " Antonio Ospite
  2013-06-21 22:14 ` [PATCH v3] " Antonio Ospite
  2 siblings, 1 reply; 25+ messages in thread
From: Clemens Ladisch @ 2013-02-11  8:53 UTC (permalink / raw)
  To: Antonio Ospite
  Cc: alsa-devel, Takashi Iwai, fchecconi, Pietro Cipriano, alberto,
	Michael Trimarchi

Antonio Ospite wrote:
> I also noticed that many drivers under sound/usb/ have text in the Kconfig
> entry stating "Say Y here to include support for ..." but I only see "[N/m/?]"
> as options when I run "make oldconfig" maybe because of some dependency, I am
> not sure, should such text be removed in order to avoid confusion?

It allows only "m" for "module" because some dependencies already
are modules.

> +++ b/sound/usb/Kconfig
> +...
> +	  M2Tech hiFace and compatible devices offer a Hi-End S/PDIF Output
> +	  Interface, see http://www.m2tech.biz/hiface.html

This sentence is not necessary for someone to decide whether to enable
this Kconfig option, so it doesn't belong here.

> +++ b/sound/usb/hiface/chip.c
> ...
> +MODULE_SUPPORTED_DEVICE("{{HiFace, Evo}}");

MODULE_SUPPORTED_DEVICE isn't actually used at the moment, but if you
use it, you should include all supported devices.  Or is this a name
for the entire family?

(But I guess the name is "Evo", not " Evo".)

> +	if (quirk && quirk->device_name)
> +		strcpy(card->shortname, quirk->device_name);
> +	else
> +		strcpy(card->shortname, "M2Tech generic audio");

Don't the devices have their name in the device descriptor?

> +	sprintf(card->longname, "%s at %d:%d", card->shortname,
> +			device->bus->busnum, device->devnum);

It is common to use usb_make_path() to show where the device is
connected, but this is more or less meaningless either way.  :)

> +	chip = kzalloc(sizeof(*chip), GFP_KERNEL);

If you pass sizeof(*chip) as the fourth parameter to snd_card_create(),
it will be allocated and freed automatically together with the card
(as card->private_data) so that you don't need to create a separate
device.

> +static int hiface_chip_probe(struct usb_interface *intf,
> +			     const struct usb_device_id *usb_id)
> +...
> +	pr_info("Probe " DRIVER_NAME " driver.\n");

This doesn't belong into the finished driver.

> +static struct usb_device_id device_table[] = {

This can be made const.

> +++ b/sound/usb/hiface/pcm.c
> +#define PCM_MAX_PACKET_SIZE 4096

Why "MAX" when the packets are never smaller?

> +static struct snd_pcm_hw_constraint_list constraints_rates = {

This should be const.

> +static int hiface_pcm_set_rate(struct pcm_runtime *rt, unsigned int rate)
> +{
> +	u8 rate_value[] = { 0x43, 0x4b, 0x42, 0x4a, 0x40, 0x48, 0x58, 0x68 };

static const

> +	ret = usb_control_msg(device, usb_sndctrlpipe(device, 0),
> +				0xb0,
> +				USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
> +				rate_value[rt->rate], 0, NULL, 0, 100);

You must not do DMA from either the stack or from static module data, so
you have to copy this byte into kmalloc'ed memory (like snd_usb_ctl_msg()
does).

> +void memcpy_swahw32(u8 *dest, u8 *src, unsigned int n)

static

> +static int hiface_pcm_playback(struct pcm_substream *sub,
> +		struct pcm_urb *urb)
> +{
> +	/* XXX Can an invalid format make it to here?

No.

> +static void hiface_pcm_out_urb_handler(struct urb *usb_urb)
> +{
> +	if (usb_urb->status || rt->panic || rt->stream_state == STREAM_STOPPING)
> +		return;

In some error cases, you might consider stopping the stream.

> +		ret = hiface_pcm_playback(sub, out_urb);
> +		if (ret < 0) {
> +			spin_unlock_irqrestore(&sub->lock, flags);
> +			goto out_fail;
> +		}
> +...
> +out_fail:
> +	usb_submit_urb(&out_urb->instance, GFP_ATOMIC);

Same here.

> +static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub)
> +{
> +		/* XXX can we avoid setting hw.rates below?

You must set hw.rates to all the rates supported by this particular
device.

The KNOT flag overrides all others flags and requires a separate
constraints to describe the valid rates.

You could either

for 192 kHz devices: set the 44100...192000 flags and install no
                     constraint, while
for 384 kHz devices: set the KNOT flag and install the 44.1...384 rate
                     constraint;

or, alternatively,

for 192 kHz devices: set the KNOT flag and install a 44.1...192 rate
                     constraint, while
for 384 kHz devices: set the KNOT flag and install a 44.1...384 rate
                     constraint.

> +		 * Maybe the 6fire driver was setting alsa_rt->hw.rates in
> +		 * order to have the playback stream and the capture stream
> +		 * use the same rate?

Yes.

> +		constraints_rates.count = ARRAY_SIZE(rates);

This does not work if contraints_rates is shared by two different
devices.  Use two snd_pcm_hw_constraint_list instances.

> +void hiface_pcm_abort(struct hiface_chip *chip)
> +{
> +			snd_pcm_stop(rt->playback.instance,
> +					SNDRV_PCM_STATE_XRUN);

This requres locking the stream with something like
snd_pcm_stream_lock_irq().


Regards,
Clemens

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

* Re: [PATCH] Add M2Tech hiFace USB-SPDIF driver
  2013-02-11  8:53 ` Clemens Ladisch
@ 2013-02-12 12:35   ` Antonio Ospite
  2013-02-12 14:29     ` Clemens Ladisch
  0 siblings, 1 reply; 25+ messages in thread
From: Antonio Ospite @ 2013-02-12 12:35 UTC (permalink / raw)
  To: Clemens Ladisch
  Cc: alsa-devel, Takashi Iwai, Antonio Ospite, fchecconi,
	Pietro Cipriano, alberto, Michael Trimarchi

On Mon, 11 Feb 2013 09:53:26 +0100
Clemens Ladisch <clemens@ladisch.de> wrote:

> Antonio Ospite wrote:
> > I also noticed that many drivers under sound/usb/ have text in the Kconfig
> > entry stating "Say Y here to include support for ..." but I only see "[N/m/?]"
> > as options when I run "make oldconfig" maybe because of some dependency, I am
> > not sure, should such text be removed in order to avoid confusion?
> 
> It allows only "m" for "module" because some dependencies already
> are modules.
>

Right, but should the sentence about "Say Y..." be removed here and in
the other drivers? It's not alway true and it may sound a little off
when "Y" is not actually available; not a big deal anyway, I noticed it
and I just wanted to mention it.

> > +++ b/sound/usb/Kconfig
> > +...
> > +	  M2Tech hiFace and compatible devices offer a Hi-End S/PDIF Output
> > +	  Interface, see http://www.m2tech.biz/hiface.html
> 
> This sentence is not necessary for someone to decide whether to enable
> this Kconfig option, so it doesn't belong here.
>

OK, I'll put that in the commit message only.

> > +++ b/sound/usb/hiface/chip.c
> > ...
> > +MODULE_SUPPORTED_DEVICE("{{HiFace, Evo}}");
> 
> MODULE_SUPPORTED_DEVICE isn't actually used at the moment, but if you
> use it, you should include all supported devices.  Or is this a name
> for the entire family?
> 
> (But I guess the name is "Evo", not " Evo".)
>

OK, I'll list all supported devices following the format used in
sound/usb/caiaq/device.c, which AFAICS is "{Manufacturer, Device Name}"
for each entry, that is still with a leading space before the device
name, or is that wrong in your opinion?

> > +	if (quirk && quirk->device_name)
> > +		strcpy(card->shortname, quirk->device_name);
> > +	else
> > +		strcpy(card->shortname, "M2Tech generic audio");
> 
> Don't the devices have their name in the device descriptor?
>

It looks like the iProduct field is not reliable enough, different
devices could have the same value there.

> > +	sprintf(card->longname, "%s at %d:%d", card->shortname,
> > +			device->bus->busnum, device->devnum);
> 
> It is common to use usb_make_path() to show where the device is
> connected, but this is more or less meaningless either way.  :)
>

:) right, I'll use usb_make_path(), tho; thanks.
 
> > +	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
> 
> If you pass sizeof(*chip) as the fourth parameter to snd_card_create(),
> it will be allocated and freed automatically together with the card
> (as card->private_data) so that you don't need to create a separate
> device.
>

OK, during development I had some other cleanups done in
hiface_dev_free(), that's why I started using the explicit
mechanism; then hiface_dev_free() was later simplified to just call
kfree(), so now I can use the implicit mechanism, thanks for pointing
that out.

> > +static int hiface_chip_probe(struct usb_interface *intf,
> > +			     const struct usb_device_id *usb_id)
> > +...
> > +	pr_info("Probe " DRIVER_NAME " driver.\n");
> 
> This doesn't belong into the finished driver.
>

OK. I can downgrade it to pr_debug().

> > +static struct usb_device_id device_table[] = {
> 
> This can be made const.
>

OK.

> > +++ b/sound/usb/hiface/pcm.c
> > +#define PCM_MAX_PACKET_SIZE 4096
> 
> Why "MAX" when the packets are never smaller?
>

I will change the name PCM_MAX_PACKET_SIZE to PCM_PACKET_SIZE and while
at it I will also change MAX_BUFSIZE to PCM_BUFFER_SIZE.

> > +static struct snd_pcm_hw_constraint_list constraints_rates = {
> 
> This should be const.
>

OK, this can be const indeed when using the new logic about constraints
that you explain below.

> > +static int hiface_pcm_set_rate(struct pcm_runtime *rt, unsigned int rate)
> > +{
> > +	u8 rate_value[] = { 0x43, 0x4b, 0x42, 0x4a, 0x40, 0x48, 0x58, 0x68 };
> 
> static const
>

I will put it outside of the function body too, using symbolic
constants in order to make it more readable.

> > +	ret = usb_control_msg(device, usb_sndctrlpipe(device, 0),
> > +				0xb0,
> > +				USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
> > +				rate_value[rt->rate], 0, NULL, 0, 100);
> 
> You must not do DMA from either the stack or from static module data, so
> you have to copy this byte into kmalloc'ed memory (like snd_usb_ctl_msg()
> does).
>

Well, your remark is valid for when the "void *data" argument of
usb_control_msg() is used, but I am just using the "value" argument here
which is a single u16 value, not a buffer address. So I think I can
leave this part as is, can't I? The value will be eventually copied into
a usb_ctrlrequest. snd_snd_usb_ctl_msg() dos not touch the "value"
argument either.

> > +void memcpy_swahw32(u8 *dest, u8 *src, unsigned int n)
> 
> static
>

Right, thanks.

> > +static int hiface_pcm_playback(struct pcm_substream *sub,
> > +		struct pcm_urb *urb)
> > +{
> > +	/* XXX Can an invalid format make it to here?
> 
> No.
>

So I can remove the check, thanks, or would it be OK to make it a
WARN_ON or a BUG_ON?

> > +static void hiface_pcm_out_urb_handler(struct urb *usb_urb)
> > +{
> > +	if (usb_urb->status || rt->panic || rt->stream_state == STREAM_STOPPING)
> > +		return;
> 
> In some error cases, you might consider stopping the stream.
>

You mean I should check explicitly for some values of usb_urb->status
and stop the stream if they happen? Like you do in
sound/usb/misc/ua101.c::capture_urb_complete()?

In the other cases this is not needed:

  * if rt->panic is true, then hiface_pcm_abort() has been called
    already which will stop the usb stream too;
  * if rt->stream_state == STREAM_STOPPING then hiface_pcm_stream_stop()
    has been called already.

> > +		ret = hiface_pcm_playback(sub, out_urb);
> > +		if (ret < 0) {
> > +			spin_unlock_irqrestore(&sub->lock, flags);
> > +			goto out_fail;
> > +		}
> > +...
> > +out_fail:
> > +	usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
> 
> Same here.
>

If I remove the check for format in hiface_pcm_playback() then this
label would go away too and this won't be an error path anymore, but
maybe you meant that I need to check the return value of usb_submit_urb
() and stop the stream when the latter fails?

> > +static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub)
> > +{
> > +		/* XXX can we avoid setting hw.rates below?
> 
> You must set hw.rates to all the rates supported by this particular
> device.
> 
> The KNOT flag overrides all others flags and requires a separate
> constraints to describe the valid rates.

I now see how this works in
sound/core/pcm_native.c::snd_pcm_hw_constraints_complete():
when KNOT or CONTINUOUS are specified the constraint on the rates is not
applied.

> You could either
> 
> for 192 kHz devices: set the 44100...192000 flags and install no
>                      constraint, while
> for 384 kHz devices: set the KNOT flag and install the 44.1...384 rate
>                      constraint;
>

I like this one better (default behavior + particular case).

> or, alternatively,
> 
> for 192 kHz devices: set the KNOT flag and install a 44.1...192 rate
>                      constraint, while
> for 384 kHz devices: set the KNOT flag and install a 44.1...384 rate
>                      constraint.
> 
> > +		 * Maybe the 6fire driver was setting alsa_rt->hw.rates in
> > +		 * order to have the playback stream and the capture stream
> > +		 * use the same rate?
> 
> Yes.
>

Either way I do not need to restrict alsa_rt->hw.rates to the
_single_value_ in use in my case, as you specified above; I will just
set it so it contains _all_ the rates supported by the device.

> > +		constraints_rates.count = ARRAY_SIZE(rates);
> 
> This does not work if contraints_rates is shared by two different
> devices.  Use two snd_pcm_hw_constraint_list instances.
>

I didn't consider this case indeed.

> > +void hiface_pcm_abort(struct hiface_chip *chip)
> > +{
> > +			snd_pcm_stop(rt->playback.instance,
> > +					SNDRV_PCM_STATE_XRUN);
> 
> This requres locking the stream with something like
> snd_pcm_stream_lock_irq().
> 

OK, but would you mind elaborating a little more why this is needed?

I do not see other drivers doing that, and BTW I see also that some
other drivers not calling snd_pcm_stop() at all, e.g. it is commented in
sound/usb/endpoint.c::snd_complete_urb().

Thanks for the review Clemens, it's appreciated.

Regards,
   Antonio

-- 
Antonio Ospite
http://ao2.it

A: Because it messes up the order in which people normally read text.
   See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?

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

* Re: [PATCH] Add M2Tech hiFace USB-SPDIF driver
  2013-02-12 12:35   ` Antonio Ospite
@ 2013-02-12 14:29     ` Clemens Ladisch
  2013-02-12 15:29       ` Takashi Iwai
  2013-02-13 14:09       ` Antonio Ospite
  0 siblings, 2 replies; 25+ messages in thread
From: Clemens Ladisch @ 2013-02-12 14:29 UTC (permalink / raw)
  To: Antonio Ospite
  Cc: alsa-devel, Takashi Iwai, Antonio Ospite, fchecconi,
	Pietro Cipriano, alberto, Michael Trimarchi

Antonio Ospite wrote:
> Clemens Ladisch <clemens@ladisch.de> wrote:
>> Antonio Ospite wrote:
>>> I also noticed that many drivers under sound/usb/ have text in the Kconfig
>>> entry stating "Say Y here to include support for ..." but I only see "[N/m/?]"
>>> as options when I run "make oldconfig" maybe because of some dependency, I am
>>> not sure, should such text be removed in order to avoid confusion?
>>
>> It allows only "m" for "module" because some dependencies already
>> are modules.
>
> Right, but should the sentence about "Say Y..." be removed here and in
> the other drivers?

If you want to, replace it with "Select this ...".

>>> +MODULE_SUPPORTED_DEVICE("{{HiFace, Evo}}");
>>
>> MODULE_SUPPORTED_DEVICE isn't actually used at the moment, but if you
>> use it, you should include all supported devices.  Or is this a name
>> for the entire family?
>>
>> (But I guess the name is "Evo", not " Evo".)
>
> OK, I'll list all supported devices following the format used in
> sound/usb/caiaq/device.c, which AFAICS is "{Manufacturer, Device Name}"
> for each entry, that is still with a leading space before the device
> name, or is that wrong in your opinion?

Most driver take care to have no spaces.

>>> +	if (quirk && quirk->device_name)
>>> +		strcpy(card->shortname, quirk->device_name);
>>> +	else
>>> +		strcpy(card->shortname, "M2Tech generic audio");
>>
>> Don't the devices have their name in the device descriptor?
>
> It looks like the iProduct field is not reliable enough, different
> devices could have the same value there.

The iProduct field is the number of the string descriptor.
That string might be useful at least in the non-quirk case
(which shouldn't happen in the first case AFAICS).

>>> +	ret = usb_control_msg(device, usb_sndctrlpipe(device, 0),
>>> +				0xb0,
>>> +				USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
>>> +				rate_value[rt->rate], 0, NULL, 0, 100);
>>
>> You must not do DMA from either the stack or from static module data, so
>> you have to copy this byte into kmalloc'ed memory (like snd_usb_ctl_msg()
>> does).
>
> Well, your remark is valid for when the "void *data" argument of
> usb_control_msg() is used, but I am just using the "value" argument here
> which is a single u16 value, not a buffer address. So I think I can
> leave this part as is, can't I?

Yes; sorry.

>>> +static int hiface_pcm_playback(struct pcm_substream *sub,
>>> +		struct pcm_urb *urb)
>>> +{
>>> +	/* XXX Can an invalid format make it to here?
>>
>> No.
>
> So I can remove the check, thanks, or would it be OK to make it a
> WARN_ON or a BUG_ON?

BUG_ON would blow up the kernel, and is appropriate when you
cannot continue at all.  Use at most a WARN_ON, but it would be
perfectly reasonable to not check at all.

>>> +static void hiface_pcm_out_urb_handler(struct urb *usb_urb)
>>> +{
>>> +	if (usb_urb->status || rt->panic || rt->stream_state == STREAM_STOPPING)
>>> +		return;
>>
>> In some error cases, you might consider stopping the stream.
>
> You mean I should check explicitly for some values of usb_urb->status
> and stop the stream if they happen? Like you do in
> sound/usb/misc/ua101.c::capture_urb_complete()?

If only one URB doesn't get delivered for whatever reason, how should
the driver react?  If you just exit from the completion handler, the
driver will continue to fill and queue all the other URBs.
And what happens when the device is unplugged?

>>> +		ret = hiface_pcm_playback(sub, out_urb);
>>> +		if (ret < 0) {
>>> +			spin_unlock_irqrestore(&sub->lock, flags);
>>> +			goto out_fail;
>>> +		}
>>> +...
>>> +out_fail:
>>> +	usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
>>
>> Same here.
>
> If I remove the check for format in hiface_pcm_playback() then this
> label would go away too and this won't be an error path anymore, but
> maybe you meant that I need to check the return value of usb_submit_urb
> () and stop the stream when the latter fails?

At the moment, you are trying to queue a URB with invalid samples.

>>> +			snd_pcm_stop(rt->playback.instance,
>>> +					SNDRV_PCM_STATE_XRUN);
>>
>> This requres locking the stream with something like
>> snd_pcm_stream_lock_irq().
>
> OK, but would you mind elaborating a little more why this is needed?
>
> I do not see other drivers doing that,

sound/firewire/{amdtp,isight}.c do.  I've never said that all those
other drivers would be correct.  (And you have to be careful to avoid
deadlocks.)

> and BTW I see also that some other drivers not calling snd_pcm_stop()
> at all, e.g. it is commented in sound/usb/endpoint.c::snd_complete_urb().

It's commented because it would crash.  That driver isn't in a very
good state regarding state changes.


Regards,
Clemens

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

* Re: [PATCH] Add M2Tech hiFace USB-SPDIF driver
  2013-02-12 14:29     ` Clemens Ladisch
@ 2013-02-12 15:29       ` Takashi Iwai
  2013-02-13 14:09       ` Antonio Ospite
  1 sibling, 0 replies; 25+ messages in thread
From: Takashi Iwai @ 2013-02-12 15:29 UTC (permalink / raw)
  To: Clemens Ladisch
  Cc: alsa-devel, Antonio Ospite, fchecconi, Pietro Cipriano, alberto,
	Michael Trimarchi, Antonio Ospite

At Tue, 12 Feb 2013 15:29:19 +0100,
Clemens Ladisch wrote:
> 
> >>> +			snd_pcm_stop(rt->playback.instance,
> >>> +					SNDRV_PCM_STATE_XRUN);
> >>
> >> This requres locking the stream with something like
> >> snd_pcm_stream_lock_irq().
> >
> > OK, but would you mind elaborating a little more why this is needed?
> >
> > I do not see other drivers doing that,
> 
> sound/firewire/{amdtp,isight}.c do.  I've never said that all those
> other drivers would be correct.  (And you have to be careful to avoid
> deadlocks.)
> 
> > and BTW I see also that some other drivers not calling snd_pcm_stop()
> > at all, e.g. it is commented in sound/usb/endpoint.c::snd_complete_urb().
> 
> It's commented because it would crash.  That driver isn't in a very
> good state regarding state changes.

The problem is that you can't kill the urb itself gracefully while in
the complete callback.  But a patch like below could work a little bit
better, although it doesn't notify the XRUN at that moment.


Takashi

---
diff --git a/sound/usb/card.h b/sound/usb/card.h
index 8a751b4..d98e7bc 100644
--- a/sound/usb/card.h
+++ b/sound/usb/card.h
@@ -118,6 +118,7 @@ struct snd_usb_substream {
 	unsigned int fmt_type;		/* USB audio format type (1-3) */
 
 	unsigned int running: 1;	/* running status */
+	unsigned int xrun:1;
 
 	unsigned int hwptr_done;	/* processed byte position in the buffer */
 	unsigned int transfer_done;		/* processed frames since last period update */
diff --git a/sound/usb/endpoint.c b/sound/usb/endpoint.c
index 21049b8..e52aee9 100644
--- a/sound/usb/endpoint.c
+++ b/sound/usb/endpoint.c
@@ -357,6 +357,9 @@ static void snd_complete_urb(struct urb *urb)
 		     ep->chip->shutdown))		/* device disconnected */
 		goto exit_clear;
 
+	if (ep->data_subs && ep->data_subs->xrun)
+		goto exit_clear;
+
 	if (usb_pipeout(ep->pipe)) {
 		retire_outbound_urb(ep, ctx);
 		/* can be stopped during retire callback */
@@ -389,7 +392,8 @@ static void snd_complete_urb(struct urb *urb)
 		return;
 
 	snd_printk(KERN_ERR "cannot submit urb (err = %d)\n", err);
-	//snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
+	if (ep->data_subs)
+		ep->data_subs->xrun = 1;
 
 exit_clear:
 	clear_bit(ctx->index, &ep->active_mask);
diff --git a/sound/usb/pcm.c b/sound/usb/pcm.c
index f94397b..6be6662 100644
--- a/sound/usb/pcm.c
+++ b/sound/usb/pcm.c
@@ -79,7 +79,7 @@ static snd_pcm_uframes_t snd_usb_pcm_pointer(struct snd_pcm_substream *substream
 	unsigned int hwptr_done;
 
 	subs = (struct snd_usb_substream *)substream->runtime->private_data;
-	if (subs->stream->chip->shutdown)
+	if (subs->xrun || subs->stream->chip->shutdown)
 		return SNDRV_PCM_POS_XRUN;
 	spin_lock(&subs->lock);
 	hwptr_done = subs->hwptr_done;
@@ -726,6 +726,7 @@ static int snd_usb_pcm_prepare(struct snd_pcm_substream *substream)
 	subs->transfer_done = 0;
 	subs->last_delay = 0;
 	subs->last_frame_number = 0;
+	subs->xrun = 0;
 	runtime->delay = 0;
 
 	/* for playback, submit the URBs now; otherwise, the first hwptr_done

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

* Re: [PATCH] Add M2Tech hiFace USB-SPDIF driver
  2013-02-12 14:29     ` Clemens Ladisch
  2013-02-12 15:29       ` Takashi Iwai
@ 2013-02-13 14:09       ` Antonio Ospite
  1 sibling, 0 replies; 25+ messages in thread
From: Antonio Ospite @ 2013-02-13 14:09 UTC (permalink / raw)
  To: Clemens Ladisch
  Cc: alsa-devel, Takashi Iwai, Antonio Ospite, fchecconi,
	Pietro Cipriano, alberto, Michael Trimarchi

On Tue, 12 Feb 2013 15:29:19 +0100
Clemens Ladisch <clemens@ladisch.de> wrote:

> Antonio Ospite wrote:
> > Clemens Ladisch <clemens@ladisch.de> wrote:
> >> Antonio Ospite wrote:

[...]
> >>> +	if (quirk && quirk->device_name)
> >>> +		strcpy(card->shortname, quirk->device_name);
> >>> +	else
> >>> +		strcpy(card->shortname, "M2Tech generic audio");
> >>
> >> Don't the devices have their name in the device descriptor?
> >
> > It looks like the iProduct field is not reliable enough, different
> > devices could have the same value there.
> 
> The iProduct field is the number of the string descriptor.
> That string might be useful at least in the non-quirk case
> (which shouldn't happen in the first case AFAICS).
> 

The original authors of the driver preferred to have all the device
names hardcoded in the driver, I guess they had a reason for that; for
now I'll leave it this way as I do not have all the supported devices to
verify the product strings for all of them. If Pietro can provide this
info I will update the driver to rely on info from the devices
themselves.

> >>> +static void hiface_pcm_out_urb_handler(struct urb *usb_urb)
> >>> +{
> >>> +	if (usb_urb->status || rt->panic || rt->stream_state == STREAM_STOPPING)
> >>> +		return;
> >>
> >> In some error cases, you might consider stopping the stream.
> >
> > You mean I should check explicitly for some values of usb_urb->status
> > and stop the stream if they happen? Like you do in
> > sound/usb/misc/ua101.c::capture_urb_complete()?
> 
> If only one URB doesn't get delivered for whatever reason, how should
> the driver react?  If you just exit from the completion handler, the
> driver will continue to fill and queue all the other URBs.
> And what happens when the device is unplugged?
>

I see, since —as Takashi said— killing URBs is not safe in the
complete callback I'll just set rt->panic to true when URBs fail:

https://github.com/panicking/snd-usb-asyncaudio/commit/0b7415e559e1a868240ac87a54ac805ca69fe4e7

> >>> +		ret = hiface_pcm_playback(sub, out_urb);
> >>> +		if (ret < 0) {
> >>> +			spin_unlock_irqrestore(&sub->lock, flags);
> >>> +			goto out_fail;
> >>> +		}
> >>> +...
> >>> +out_fail:
> >>> +	usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
> >>
> >> Same here.
> >
> > If I remove the check for format in hiface_pcm_playback() then this
> > label would go away too and this won't be an error path anymore, but
> > maybe you meant that I need to check the return value of usb_submit_urb
> > () and stop the stream when the latter fails?
> 
> At the moment, you are trying to queue a URB with invalid samples.
> 

I am going to send a version 2 soon.

Thanks again,
   Antonio

-- 
Antonio Ospite
http://ao2.it

A: Because it messes up the order in which people normally read text.
   See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?
_______________________________________________
Alsa-devel mailing list
Alsa-devel@alsa-project.org
http://mailman.alsa-project.org/mailman/listinfo/alsa-devel

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

* [PATCH v2] Add M2Tech hiFace USB-SPDIF driver
  2013-02-10 23:11 [PATCH] Add M2Tech hiFace USB-SPDIF driver Antonio Ospite
  2013-02-11  8:53 ` Clemens Ladisch
@ 2013-02-13 17:11 ` Antonio Ospite
  2013-02-22 10:48   ` Antonio Ospite
                     ` (3 more replies)
  2013-06-21 22:14 ` [PATCH v3] " Antonio Ospite
  2 siblings, 4 replies; 25+ messages in thread
From: Antonio Ospite @ 2013-02-13 17:11 UTC (permalink / raw)
  To: patch
  Cc: alsa-devel, Takashi Iwai, Antonio Ospite, Clemens Ladisch,
	fchecconi, Pietro Cipriano, alberto, Michael Trimarchi

Add driver for M2Tech hiFace USB-SPDIF interface and compatible devices.

M2Tech hiFace and compatible devices offer a Hi-End S/PDIF Output
Interface, see http://www.m2tech.biz/hiface.html

The supported products are:

  * M2Tech Young
  * M2Tech hiFace
  * M2Tech North Star
  * M2Tech W4S Young
  * M2Tech Corrson
  * M2Tech AUDIA
  * M2Tech SL Audio
  * M2Tech Empirical
  * M2Tech Rockna
  * M2Tech Pathos
  * M2Tech Metronome
  * M2Tech CAD
  * M2Tech Audio Esclusive
  * M2Tech Rotel
  * M2Tech Eeaudio
  * The Chord Company CHORD
  * AVA Group A/S Vitus

Signed-off-by: Antonio Ospite <ao2@amarulasolutions.com>
---

Changes since v1:

  * Change the first sentence of the Kconfig entry into "Select this..."
  * Remove a useless sentence from the Kconfig entry
  * Don't set alsa_rt->hw.rates in hiface_pcm_open()
  * Style cleanup, no braces needed in single statement conditional
  * Remove the rate field from pcm_runtime
  * Use the hiFace name with the lowercase 'h' everywhere
  * List actually supported devices in MODULE_SUPPORTED_DEVICE()
  * Cosmetics, align values in the rates array
  * Use an explicit switch instead of the rate_value array in
    hiface_pcm_set_rate()
  * Use usb_make_path() when building card->longname
  * Use again the implicit mechanism to allocate the card private data
  * Downgrade a pr_info to pr_debug in hiface_chip_probe()
  * Make device_table const
  * Rename PCM_MAX_PACKET_SIZE to PCM_PACKET_SIZE
  * Rename MAX_BUFSIZE to PCM_BUFFER_SIZE
  * Cosmetics, align symbolic constant values
  * Add SNDRV_PCM_RATE_KNOT only when needed
  * Declare memcpy_swahw32() as static
  * Protect snd_pcm_stop() with snd_pcm_stream_lock_irq()
  * Make hiface_pcm_playback() not returning anything
  * Move the period elapsed check into hiface_pcm_playback()
  * Handle the case of failing URBs in hiface_pcm_out_urb_handler()
  * Fix a couple of checkpatch.pl issues

The incremental changes can be seen individually at
https://github.com/panicking/snd-usb-asyncaudio/commits/master
Commits from Feb. 10th and later.

Thanks,
   Antonio

 sound/usb/Kconfig         |   31 +++
 sound/usb/Makefile        |    2 +-
 sound/usb/hiface/Makefile |    2 +
 sound/usb/hiface/chip.h   |   34 +++
 sound/usb/hiface/chip.c   |  329 +++++++++++++++++++++++
 sound/usb/hiface/pcm.h    |   24 ++
 sound/usb/hiface/pcm.c    |  638 +++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 1059 insertions(+), 1 deletion(-)
 create mode 100644 sound/usb/hiface/Makefile
 create mode 100644 sound/usb/hiface/chip.h
 create mode 100644 sound/usb/hiface/chip.c
 create mode 100644 sound/usb/hiface/pcm.h
 create mode 100644 sound/usb/hiface/pcm.c

diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig
index 225dfd7..de9408b 100644
--- a/sound/usb/Kconfig
+++ b/sound/usb/Kconfig
@@ -115,5 +115,36 @@ config SND_USB_6FIRE
           and further help can be found at
           http://sixfireusb.sourceforge.net
 
+config SND_USB_HIFACE
+        tristate "M2Tech hiFace USB-SPDIF driver"
+        select SND_PCM
+        help
+	  Select this option to include support for M2Tech hiFace USB-SPDIF
+	  interface.
+
+	  This driver supports the original M2Tech hiFace and some other
+	  compatible devices. The supported products are:
+
+	    * M2Tech Young
+	    * M2Tech hiFace
+	    * M2Tech North Star
+	    * M2Tech W4S Young
+	    * M2Tech Corrson
+	    * M2Tech AUDIA
+	    * M2Tech SL Audio
+	    * M2Tech Empirical
+	    * M2Tech Rockna
+	    * M2Tech Pathos
+	    * M2Tech Metronome
+	    * M2Tech CAD
+	    * M2Tech Audio Esclusive
+	    * M2Tech Rotel
+	    * M2Tech Eeaudio
+	    * The Chord Company CHORD
+	    * AVA Group A/S Vitus
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-usb-hiface.
+
 endif	# SND_USB
 
diff --git a/sound/usb/Makefile b/sound/usb/Makefile
index ac256dc..abe668f 100644
--- a/sound/usb/Makefile
+++ b/sound/usb/Makefile
@@ -23,4 +23,4 @@ obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o
 obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o
 obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o
 
-obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/
+obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/
diff --git a/sound/usb/hiface/Makefile b/sound/usb/hiface/Makefile
new file mode 100644
index 0000000..463b136
--- /dev/null
+++ b/sound/usb/hiface/Makefile
@@ -0,0 +1,2 @@
+snd-usb-hiface-objs := chip.o pcm.o
+obj-$(CONFIG_SND_USB_HIFACE) += snd-usb-hiface.o
diff --git a/sound/usb/hiface/chip.h b/sound/usb/hiface/chip.h
new file mode 100644
index 0000000..cd615a0
--- /dev/null
+++ b/sound/usb/hiface/chip.h
@@ -0,0 +1,34 @@
+/*
+ * Linux driver for M2Tech hiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
+ *           Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef HIFACE_CHIP_H
+#define HIFACE_CHIP_H
+
+#include <linux/usb.h>
+#include <sound/core.h>
+
+struct pcm_runtime;
+
+struct hiface_chip {
+	struct usb_device *dev;
+	struct snd_card *card;
+	int intf_count; /* number of registered interfaces */
+	int index; /* index in module parameter arrays */
+	bool shutdown;
+
+	struct pcm_runtime *pcm;
+};
+#endif /* HIFACE_CHIP_H */
diff --git a/sound/usb/hiface/chip.c b/sound/usb/hiface/chip.c
new file mode 100644
index 0000000..f1293a5
--- /dev/null
+++ b/sound/usb/hiface/chip.c
@@ -0,0 +1,329 @@
+/*
+ * Linux driver for M2Tech hiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
+ *           Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <sound/initval.h>
+
+#include "chip.h"
+#include "pcm.h"
+
+MODULE_AUTHOR("Michael Trimarchi <michael@amarulasolutions.com>");
+MODULE_AUTHOR("Antonio Ospite <ao2@amarulasolutions.com>");
+MODULE_DESCRIPTION("M2Tech hiFace USB-SPDIF audio driver");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{M2Tech,Young},"
+			 "{M2Tech,hiFace},"
+			 "{M2Tech,North Star},"
+			 "{M2Tech,W4S Young},"
+			 "{M2Tech,Corrson},"
+			 "{M2Tech,AUDIA},"
+			 "{M2Tech,SL Audio},"
+			 "{M2Tech,Empirical},"
+			 "{M2Tech,Rockna},"
+			 "{M2Tech,Pathos},"
+			 "{M2Tech,Metronome},"
+			 "{M2Tech,CAD},"
+			 "{M2Tech,Audio Esclusive},"
+			 "{M2Tech,Rotel},"
+			 "{M2Tech,Eeaudio},"
+			 "{The Chord Company,CHORD},"
+			 "{AVA Group A/S,Vitus}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
+
+#define DRIVER_NAME "snd-usb-hiface"
+#define CARD_NAME "hiFace"
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
+
+static struct hiface_chip *chips[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+static DEFINE_MUTEX(register_mutex);
+
+struct hiface_vendor_quirk {
+	const char *device_name;
+	u8 extra_freq;
+};
+
+static int hiface_chip_create(struct usb_device *device, int idx,
+			      const struct hiface_vendor_quirk *quirk,
+			      struct hiface_chip **rchip)
+{
+	struct snd_card *card = NULL;
+	struct hiface_chip *chip;
+	int ret;
+	int len;
+
+	*rchip = NULL;
+
+	/* if we are here, card can be registered in alsa. */
+	ret = snd_card_create(index[idx], id[idx], THIS_MODULE, sizeof(*chip), &card);
+	if (ret < 0) {
+		snd_printk(KERN_ERR "cannot create alsa card.\n");
+		return ret;
+	}
+
+	strcpy(card->driver, DRIVER_NAME);
+
+	if (quirk && quirk->device_name)
+		strcpy(card->shortname, quirk->device_name);
+	else
+		strcpy(card->shortname, "M2Tech generic audio");
+
+	strlcat(card->longname, card->shortname, sizeof(card->longname));
+	len = strlcat(card->longname, " at ", sizeof(card->longname));
+	if (len < sizeof(card->longname))
+		usb_make_path(device, card->longname + len,
+			      sizeof(card->longname) - len);
+
+	chip = card->private_data;
+	chip->dev = device;
+	chip->index = idx;
+	chip->card = card;
+
+	*rchip = chip;
+	return 0;
+}
+
+static int hiface_chip_probe(struct usb_interface *intf,
+			     const struct usb_device_id *usb_id)
+{
+	const struct hiface_vendor_quirk *quirk = (struct hiface_vendor_quirk *)usb_id->driver_info;
+	int ret;
+	int i;
+	struct hiface_chip *chip;
+	struct usb_device *device = interface_to_usbdev(intf);
+
+	pr_debug("Probe " DRIVER_NAME " driver.\n");
+
+	ret = usb_set_interface(device, 0, 0);
+	if (ret != 0) {
+		snd_printk(KERN_ERR "can't set first interface for " CARD_NAME " device.\n");
+		return -EIO;
+	}
+
+	/* check whether the card is already registered */
+	chip = NULL;
+	mutex_lock(&register_mutex);
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		if (chips[i] && chips[i]->dev == device) {
+			if (chips[i]->shutdown) {
+				snd_printk(KERN_ERR CARD_NAME " device is in the shutdown state, cannot create a card instance\n");
+				ret = -ENODEV;
+				goto err;
+			}
+			chip = chips[i];
+			break;
+		}
+	}
+	if (!chip) {
+		/* it's a fresh one.
+		 * now look for an empty slot and create a new card instance
+		 */
+		for (i = 0; i < SNDRV_CARDS; i++)
+			if (enable[i] && !chips[i]) {
+				ret = hiface_chip_create(device, i, quirk,
+							 &chip);
+				if (ret < 0)
+					goto err;
+
+				snd_card_set_dev(chip->card, &intf->dev);
+				break;
+			}
+		if (!chip) {
+			snd_printk(KERN_ERR "no available " CARD_NAME " audio device\n");
+			ret = -ENODEV;
+			goto err;
+		}
+	}
+
+	ret = hiface_pcm_init(chip, quirk ? quirk->extra_freq : 0);
+	if (ret < 0)
+		goto err_chip_destroy;
+
+	ret = snd_card_register(chip->card);
+	if (ret < 0) {
+		snd_printk(KERN_ERR "cannot register " CARD_NAME " card\n");
+		goto err_chip_destroy;
+	}
+
+	chips[chip->index] = chip;
+	chip->intf_count++;
+
+	mutex_unlock(&register_mutex);
+
+	usb_set_intfdata(intf, chip);
+	return 0;
+
+err_chip_destroy:
+	snd_card_free(chip->card);
+err:
+	mutex_unlock(&register_mutex);
+	return ret;
+}
+
+static void hiface_chip_disconnect(struct usb_interface *intf)
+{
+	struct hiface_chip *chip;
+	struct snd_card *card;
+
+	pr_debug("%s: called.\n", __func__);
+
+	chip = usb_get_intfdata(intf);
+	if (!chip)
+		return;
+
+	card = chip->card;
+	chip->intf_count--;
+	if (chip->intf_count <= 0) {
+		/* Make sure that the userspace cannot create new request */
+		snd_card_disconnect(card);
+
+		mutex_lock(&register_mutex);
+		chips[chip->index] = NULL;
+		mutex_unlock(&register_mutex);
+
+		chip->shutdown = true;
+		hiface_pcm_abort(chip);
+		snd_card_free_when_closed(card);
+	}
+}
+
+static const struct usb_device_id device_table[] = {
+	{
+		USB_DEVICE(0x04b4, 0x0384),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Young",
+			.extra_freq = 1,
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x930b),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "hiFace",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931b),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "North Star",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931c),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "W4S Young",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931d),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Corrson",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931e),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "AUDIA",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931f),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "SL Audio",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x9320),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Empirical",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x9321),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Rockna",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x9001),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Pathos",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x9002),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Metronome",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x9006),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "CAD",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x9008),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Audio Esclusive",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x931c),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Rotel",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x932c),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Eeaudio",
+		}
+	},
+	{
+		USB_DEVICE(0x245f, 0x931c),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "CHORD",
+		}
+	},
+	{
+		USB_DEVICE(0x25c6, 0x9002),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Vitus",
+		}
+	},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+static struct usb_driver hiface_usb_driver = {
+	.name = DRIVER_NAME,
+	.probe = hiface_chip_probe,
+	.disconnect = hiface_chip_disconnect,
+	.id_table = device_table,
+};
+
+module_usb_driver(hiface_usb_driver);
diff --git a/sound/usb/hiface/pcm.h b/sound/usb/hiface/pcm.h
new file mode 100644
index 0000000..77edd7c
--- /dev/null
+++ b/sound/usb/hiface/pcm.h
@@ -0,0 +1,24 @@
+/*
+ * Linux driver for M2Tech hiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
+ *           Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef HIFACE_PCM_H
+#define HIFACE_PCM_H
+
+struct hiface_chip;
+
+int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq);
+void hiface_pcm_abort(struct hiface_chip *chip);
+#endif /* HIFACE_PCM_H */
diff --git a/sound/usb/hiface/pcm.c b/sound/usb/hiface/pcm.c
new file mode 100644
index 0000000..d0c3c58
--- /dev/null
+++ b/sound/usb/hiface/pcm.c
@@ -0,0 +1,638 @@
+/*
+ * Linux driver for M2Tech hiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
+ *           Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/slab.h>
+#include <sound/pcm.h>
+
+#include "pcm.h"
+#include "chip.h"
+
+#define OUT_EP          0x2
+#define PCM_N_URBS      8
+#define PCM_PACKET_SIZE 4096
+#define PCM_BUFFER_SIZE (2 * PCM_N_URBS * PCM_PACKET_SIZE)
+
+struct pcm_urb {
+	struct hiface_chip *chip;
+
+	struct urb instance;
+	struct usb_anchor submitted;
+	u8 *buffer;
+};
+
+struct pcm_substream {
+	spinlock_t lock;
+	struct snd_pcm_substream *instance;
+
+	bool active;
+	snd_pcm_uframes_t dma_off;    /* current position in alsa dma_area */
+	snd_pcm_uframes_t period_off; /* current position in current period */
+};
+
+enum { /* pcm streaming states */
+	STREAM_DISABLED, /* no pcm streaming */
+	STREAM_STARTING, /* pcm streaming requested, waiting to become ready */
+	STREAM_RUNNING,  /* pcm streaming running */
+	STREAM_STOPPING
+};
+
+struct pcm_runtime {
+	struct hiface_chip *chip;
+	struct snd_pcm *instance;
+
+	struct pcm_substream playback;
+	bool panic; /* if set driver won't do anymore pcm on device */
+
+	struct pcm_urb out_urbs[PCM_N_URBS];
+
+	struct mutex stream_mutex;
+	u8 stream_state; /* one of STREAM_XXX */
+	u8 extra_freq;
+	wait_queue_head_t stream_wait_queue;
+	bool stream_wait_cond;
+};
+
+static const unsigned int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000,
+				      352800, 384000 };
+static const struct snd_pcm_hw_constraint_list constraints_extra_rates = {
+	.count = ARRAY_SIZE(rates),
+	.list = rates,
+	.mask = 0,
+};
+
+static const struct snd_pcm_hardware pcm_hw = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_BATCH,
+
+	.formats = SNDRV_PCM_FMTBIT_S32_LE,
+
+	.rates = SNDRV_PCM_RATE_44100 |
+		SNDRV_PCM_RATE_48000 |
+		SNDRV_PCM_RATE_88200 |
+		SNDRV_PCM_RATE_96000 |
+		SNDRV_PCM_RATE_176400 |
+		SNDRV_PCM_RATE_192000,
+
+	.rate_min = 44100,
+	.rate_max = 192000, /* changes in hiface_pcm_open to support extra rates */
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = PCM_BUFFER_SIZE,
+	.period_bytes_min = PCM_PACKET_SIZE,
+	.period_bytes_max = PCM_BUFFER_SIZE,
+	.periods_min = 2,
+	.periods_max = 1024
+};
+
+/* message values used to change the sample rate */
+#define HIFACE_SET_RATE_REQUEST 0xb0
+
+#define HIFACE_RATE_44100  0x43
+#define HIFACE_RATE_48000  0x4b
+#define HIFACE_RATE_88200  0x42
+#define HIFACE_RATE_96000  0x4a
+#define HIFACE_RATE_176400 0x40
+#define HIFACE_RATE_192000 0x48
+#define HIFACE_RATE_352000 0x58
+#define HIFACE_RATE_384000 0x68
+
+static int hiface_pcm_set_rate(struct pcm_runtime *rt, unsigned int rate)
+{
+	struct usb_device *device = rt->chip->dev;
+	u16 rate_value;
+	int ret;
+
+	/* We are already sure that the rate is supported here thanks to
+	 * ALSA constraints
+	 */
+	switch (rate) {
+	case 44100:
+		rate_value = HIFACE_RATE_44100;
+		break;
+	case 48000:
+		rate_value = HIFACE_RATE_48000;
+		break;
+	case 88200:
+		rate_value = HIFACE_RATE_88200;
+		break;
+	case 96000:
+		rate_value = HIFACE_RATE_96000;
+		break;
+	case 176400:
+		rate_value = HIFACE_RATE_176400;
+		break;
+	case 192000:
+		rate_value = HIFACE_RATE_192000;
+		break;
+	case 352000:
+		rate_value = HIFACE_RATE_352000;
+		break;
+	case 384000:
+		rate_value = HIFACE_RATE_384000;
+		break;
+	default:
+		snd_printk(KERN_ERR "Unsupported rate %d\n", rate);
+		return -EINVAL;
+	}
+
+	/*
+	 * USBIO: Vendor 0xb0(wValue=0x0043, wIndex=0x0000)
+	 * 43 b0 43 00 00 00 00 00
+	 * USBIO: Vendor 0xb0(wValue=0x004b, wIndex=0x0000)
+	 * 43 b0 4b 00 00 00 00 00
+	 * This control message doesn't have any ack from the
+	 * other side
+	 */
+	ret = usb_control_msg(device, usb_sndctrlpipe(device, 0),
+				HIFACE_SET_RATE_REQUEST,
+				USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+				rate_value, 0, NULL, 0, 100);
+	if (ret < 0) {
+		snd_printk(KERN_ERR "Error setting samplerate %d.\n", rate);
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct pcm_substream *hiface_pcm_get_substream(
+		struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+
+	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		return &rt->playback;
+
+	pr_debug("Error getting pcm substream slot.\n");
+	return NULL;
+}
+
+/* call with stream_mutex locked */
+static void hiface_pcm_stream_stop(struct pcm_runtime *rt)
+{
+	int i, time;
+
+	if (rt->stream_state != STREAM_DISABLED) {
+		rt->stream_state = STREAM_STOPPING;
+
+		for (i = 0; i < PCM_N_URBS; i++) {
+			time = usb_wait_anchor_empty_timeout(
+					&rt->out_urbs[i].submitted, 100);
+			if (!time)
+				usb_kill_anchored_urbs(
+					&rt->out_urbs[i].submitted);
+			usb_kill_urb(&rt->out_urbs[i].instance);
+		}
+
+		rt->stream_state = STREAM_DISABLED;
+	}
+}
+
+/* call with stream_mutex locked */
+static int hiface_pcm_stream_start(struct pcm_runtime *rt)
+{
+	int ret = 0, i;
+
+	if (rt->stream_state == STREAM_DISABLED) {
+		/* submit our out urbs zero init */
+		rt->stream_state = STREAM_STARTING;
+		for (i = 0; i < PCM_N_URBS; i++) {
+			memset(rt->out_urbs[i].buffer, 0, PCM_PACKET_SIZE);
+			usb_anchor_urb(&rt->out_urbs[i].instance,
+				       &rt->out_urbs[i].submitted);
+			ret = usb_submit_urb(&rt->out_urbs[i].instance,
+					GFP_ATOMIC);
+			if (ret) {
+				hiface_pcm_stream_stop(rt);
+				return ret;
+			}
+		}
+
+		/* wait for first out urb to return (sent in in urb handler) */
+		wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond,
+				HZ);
+		if (rt->stream_wait_cond) {
+			pr_debug("%s: Stream is running wakeup event\n",
+				 __func__);
+			rt->stream_state = STREAM_RUNNING;
+		} else {
+			hiface_pcm_stream_stop(rt);
+			return -EIO;
+		}
+	}
+	return ret;
+}
+
+
+/* The hardware wants word-swapped 32-bit values */
+static void memcpy_swahw32(u8 *dest, u8 *src, unsigned int n)
+{
+	unsigned int i;
+
+	for (i = 0; i < n / 4; i++)
+		((u32 *)dest)[i] = swahw32(((u32 *)src)[i]);
+}
+
+/* call with substream locked */
+/* returns true if a period elapsed */
+static bool hiface_pcm_playback(struct pcm_substream *sub,
+		struct pcm_urb *urb)
+{
+	struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
+	u8 *source;
+	unsigned int pcm_buffer_size;
+
+	WARN_ON(alsa_rt->format != SNDRV_PCM_FORMAT_S32_LE);
+
+	pcm_buffer_size = snd_pcm_lib_buffer_bytes(sub->instance);
+
+	if (sub->dma_off + PCM_PACKET_SIZE <= pcm_buffer_size) {
+		pr_debug("%s: (1) buffer_size %#x dma_offset %#x\n", __func__,
+			 (unsigned int) pcm_buffer_size,
+			 (unsigned int) sub->dma_off);
+
+		source = alsa_rt->dma_area + sub->dma_off;
+		memcpy_swahw32(urb->buffer, source, PCM_PACKET_SIZE);
+	} else {
+		/* wrap around at end of ring buffer */
+		unsigned int len;
+
+		pr_debug("%s: (2) buffer_size %#x dma_offset %#x\n", __func__,
+			 (unsigned int) pcm_buffer_size,
+			 (unsigned int) sub->dma_off);
+
+		len = pcm_buffer_size - sub->dma_off;
+
+		source = alsa_rt->dma_area + sub->dma_off;
+		memcpy_swahw32(urb->buffer, source, len);
+
+		source = alsa_rt->dma_area;
+		memcpy_swahw32(urb->buffer + len, source,
+			       PCM_PACKET_SIZE - len);
+	}
+	sub->dma_off += PCM_PACKET_SIZE;
+	if (sub->dma_off >= pcm_buffer_size)
+		sub->dma_off -= pcm_buffer_size;
+
+	sub->period_off += PCM_PACKET_SIZE;
+	if (sub->period_off >= alsa_rt->period_size) {
+		sub->period_off %= alsa_rt->period_size;
+		return true;
+	}
+	return false;
+}
+
+static void hiface_pcm_out_urb_handler(struct urb *usb_urb)
+{
+	struct pcm_urb *out_urb = usb_urb->context;
+	struct pcm_runtime *rt = out_urb->chip->pcm;
+	struct pcm_substream *sub;
+	bool do_period_elapsed = false;
+	unsigned long flags;
+	int ret;
+
+	pr_debug("%s: called.\n", __func__);
+
+	if (rt->panic || rt->stream_state == STREAM_STOPPING)
+		return;
+
+	if (unlikely(usb_urb->status == -ENOENT ||	/* unlinked */
+		     usb_urb->status == -ENODEV ||	/* device removed */
+		     usb_urb->status == -ECONNRESET ||	/* unlinked */
+		     usb_urb->status == -ESHUTDOWN)) {	/* device disabled */
+		goto out_fail;
+	}
+
+	if (rt->stream_state == STREAM_STARTING) {
+		rt->stream_wait_cond = true;
+		wake_up(&rt->stream_wait_queue);
+	}
+
+	/* now send our playback data (if a free out urb was found) */
+	sub = &rt->playback;
+	spin_lock_irqsave(&sub->lock, flags);
+	if (sub->active)
+		do_period_elapsed = hiface_pcm_playback(sub, out_urb);
+	else
+		memset(out_urb->buffer, 0, PCM_PACKET_SIZE);
+
+	spin_unlock_irqrestore(&sub->lock, flags);
+
+	if (do_period_elapsed)
+		snd_pcm_period_elapsed(sub->instance);
+
+	ret = usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
+	if (ret < 0)
+		goto out_fail;
+
+	return;
+
+out_fail:
+	rt->panic = true;
+}
+
+static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	struct pcm_substream *sub = NULL;
+	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
+	int ret;
+
+	pr_debug("%s: called.\n", __func__);
+
+	if (rt->panic)
+		return -EPIPE;
+
+	mutex_lock(&rt->stream_mutex);
+	alsa_rt->hw = pcm_hw;
+
+	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		sub = &rt->playback;
+
+	if (!sub) {
+		mutex_unlock(&rt->stream_mutex);
+		pr_err("Invalid stream type\n");
+		return -EINVAL;
+	}
+
+	if (rt->extra_freq) {
+		alsa_rt->hw.rates |= SNDRV_PCM_RATE_KNOT;
+		alsa_rt->hw.rate_max = 384000;
+
+		/* explicit constraints needed as we added SNDRV_PCM_RATE_KNOT */
+		ret = snd_pcm_hw_constraint_list(alsa_sub->runtime, 0,
+						 SNDRV_PCM_HW_PARAM_RATE,
+						 &constraints_extra_rates);
+		if (ret < 0) {
+			mutex_unlock(&rt->stream_mutex);
+			return ret;
+		}
+	}
+
+	sub->instance = alsa_sub;
+	sub->active = false;
+	mutex_unlock(&rt->stream_mutex);
+	return 0;
+}
+
+static int hiface_pcm_close(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+	unsigned long flags;
+
+	if (rt->panic)
+		return 0;
+
+	pr_debug("%s: called.\n", __func__);
+
+	mutex_lock(&rt->stream_mutex);
+	if (sub) {
+		/* deactivate substream */
+		spin_lock_irqsave(&sub->lock, flags);
+		sub->instance = NULL;
+		sub->active = false;
+		spin_unlock_irqrestore(&sub->lock, flags);
+
+		/* all substreams closed? if so, stop streaming */
+		if (!rt->playback.instance)
+			hiface_pcm_stream_stop(rt);
+	}
+	mutex_unlock(&rt->stream_mutex);
+	return 0;
+}
+
+static int hiface_pcm_hw_params(struct snd_pcm_substream *alsa_sub,
+		struct snd_pcm_hw_params *hw_params)
+{
+	pr_debug("%s: called.\n", __func__);
+	return snd_pcm_lib_malloc_pages(alsa_sub,
+			params_buffer_bytes(hw_params));
+}
+
+static int hiface_pcm_hw_free(struct snd_pcm_substream *alsa_sub)
+{
+	pr_debug("%s: called.\n", __func__);
+	return snd_pcm_lib_free_pages(alsa_sub);
+}
+
+static int hiface_pcm_prepare(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
+	int ret;
+
+	pr_debug("%s: called.\n", __func__);
+
+	if (rt->panic)
+		return -EPIPE;
+	if (!sub)
+		return -ENODEV;
+
+	mutex_lock(&rt->stream_mutex);
+
+	sub->dma_off = 0;
+	sub->period_off = 0;
+
+	if (rt->stream_state == STREAM_DISABLED) {
+
+		ret = hiface_pcm_set_rate(rt, alsa_rt->rate);
+		if (ret) {
+			mutex_unlock(&rt->stream_mutex);
+			return ret;
+		}
+		ret = hiface_pcm_stream_start(rt);
+		if (ret) {
+			mutex_unlock(&rt->stream_mutex);
+			return ret;
+		}
+	}
+	mutex_unlock(&rt->stream_mutex);
+	return 0;
+}
+
+static int hiface_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
+{
+	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	unsigned long flags;
+
+	pr_debug("%s: called.\n", __func__);
+
+	if (rt->panic)
+		return -EPIPE;
+	if (!sub)
+		return -ENODEV;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		spin_lock_irqsave(&sub->lock, flags);
+		sub->active = true;
+		spin_unlock_irqrestore(&sub->lock, flags);
+		return 0;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		spin_lock_irqsave(&sub->lock, flags);
+		sub->active = false;
+		spin_unlock_irqrestore(&sub->lock, flags);
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static snd_pcm_uframes_t hiface_pcm_pointer(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	unsigned long flags;
+	snd_pcm_uframes_t dma_offset;
+
+	if (rt->panic || !sub)
+		return SNDRV_PCM_STATE_XRUN;
+
+	spin_lock_irqsave(&sub->lock, flags);
+	dma_offset = sub->dma_off;
+	spin_unlock_irqrestore(&sub->lock, flags);
+	return bytes_to_frames(alsa_sub->runtime, dma_offset);
+}
+
+static struct snd_pcm_ops pcm_ops = {
+	.open = hiface_pcm_open,
+	.close = hiface_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = hiface_pcm_hw_params,
+	.hw_free = hiface_pcm_hw_free,
+	.prepare = hiface_pcm_prepare,
+	.trigger = hiface_pcm_trigger,
+	.pointer = hiface_pcm_pointer,
+};
+
+static int hiface_pcm_init_urb(struct pcm_urb *urb,
+			       struct hiface_chip *chip,
+			       unsigned int ep,
+			       void (*handler)(struct urb *))
+{
+	urb->chip = chip;
+	usb_init_urb(&urb->instance);
+
+	urb->buffer = kzalloc(PCM_PACKET_SIZE, GFP_KERNEL);
+	if (!urb->buffer)
+		return -ENOMEM;
+
+	usb_fill_bulk_urb(&urb->instance, chip->dev,
+			  usb_sndbulkpipe(chip->dev, ep), (void *)urb->buffer,
+			  PCM_PACKET_SIZE, handler, urb);
+	init_usb_anchor(&urb->submitted);
+
+	return 0;
+}
+
+void hiface_pcm_abort(struct hiface_chip *chip)
+{
+	struct pcm_runtime *rt = chip->pcm;
+
+	if (rt) {
+		rt->panic = true;
+
+		if (rt->playback.instance) {
+			snd_pcm_stream_lock_irq(rt->playback.instance);
+			snd_pcm_stop(rt->playback.instance,
+					SNDRV_PCM_STATE_XRUN);
+			snd_pcm_stream_unlock_irq(rt->playback.instance);
+		}
+		mutex_lock(&rt->stream_mutex);
+		hiface_pcm_stream_stop(rt);
+		mutex_unlock(&rt->stream_mutex);
+	}
+}
+
+static void hiface_pcm_destroy(struct hiface_chip *chip)
+{
+	struct pcm_runtime *rt = chip->pcm;
+	int i;
+
+	for (i = 0; i < PCM_N_URBS; i++)
+		kfree(rt->out_urbs[i].buffer);
+
+	kfree(chip->pcm);
+	chip->pcm = NULL;
+}
+
+static void hiface_pcm_free(struct snd_pcm *pcm)
+{
+	struct pcm_runtime *rt = pcm->private_data;
+
+	pr_debug("%s: called.\n", __func__);
+
+	if (rt)
+		hiface_pcm_destroy(rt->chip);
+}
+
+int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq)
+{
+	int i;
+	int ret;
+	struct snd_pcm *pcm;
+	struct pcm_runtime *rt;
+
+	rt = kzalloc(sizeof(*rt), GFP_KERNEL);
+	if (!rt)
+		return -ENOMEM;
+
+	rt->chip = chip;
+	rt->stream_state = STREAM_DISABLED;
+	if (extra_freq)
+		rt->extra_freq = 1;
+
+	init_waitqueue_head(&rt->stream_wait_queue);
+	mutex_init(&rt->stream_mutex);
+	spin_lock_init(&rt->playback.lock);
+
+	for (i = 0; i < PCM_N_URBS; i++)
+		hiface_pcm_init_urb(&rt->out_urbs[i], chip, OUT_EP,
+				hiface_pcm_out_urb_handler);
+
+	ret = snd_pcm_new(chip->card, "USB-SPDIF Audio", 0, 1, 0, &pcm);
+	if (ret < 0) {
+		kfree(rt);
+		pr_err("Cannot create pcm instance\n");
+		return ret;
+	}
+
+	pcm->private_data = rt;
+	pcm->private_free = hiface_pcm_free;
+
+	strcpy(pcm->name, "USB-SPDIF Audio");
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm,
+					SNDRV_DMA_TYPE_CONTINUOUS,
+					snd_dma_continuous_data(GFP_KERNEL),
+					PCM_BUFFER_SIZE, PCM_BUFFER_SIZE);
+	rt->instance = pcm;
+
+	chip->pcm = rt;
+	return 0;
+}
-- 
1.7.10.4

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

* Re: [PATCH v2] Add M2Tech hiFace USB-SPDIF driver
  2013-02-13 17:11 ` [PATCH v2] " Antonio Ospite
@ 2013-02-22 10:48   ` Antonio Ospite
  2013-02-22 12:52     ` Takashi Iwai
  2013-02-22 12:53   ` Takashi Iwai
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 25+ messages in thread
From: Antonio Ospite @ 2013-02-22 10:48 UTC (permalink / raw)
  Cc: alsa-devel, Takashi Iwai, Clemens Ladisch, fchecconi,
	Pietro Cipriano, alberto, Michael Trimarchi

On Wed, 13 Feb 2013 18:11:45 +0100
Antonio Ospite <ao2@amarulasolutions.com> wrote:

> Add driver for M2Tech hiFace USB-SPDIF interface and compatible devices.
> 
> M2Tech hiFace and compatible devices offer a Hi-End S/PDIF Output
> Interface, see http://www.m2tech.biz/hiface.html
> 
> The supported products are:
> 
>   * M2Tech Young
>   * M2Tech hiFace
>   * M2Tech North Star
>   * M2Tech W4S Young
>   * M2Tech Corrson
>   * M2Tech AUDIA
>   * M2Tech SL Audio
>   * M2Tech Empirical
>   * M2Tech Rockna
>   * M2Tech Pathos
>   * M2Tech Metronome
>   * M2Tech CAD
>   * M2Tech Audio Esclusive
>   * M2Tech Rotel
>   * M2Tech Eeaudio
>   * The Chord Company CHORD
>   * AVA Group A/S Vitus
> 
> Signed-off-by: Antonio Ospite <ao2@amarulasolutions.com>
> ---
> 
> Changes since v1:
> 
>   * Change the first sentence of the Kconfig entry into "Select this..."
>   * Remove a useless sentence from the Kconfig entry
>   * Don't set alsa_rt->hw.rates in hiface_pcm_open()
>   * Style cleanup, no braces needed in single statement conditional
>   * Remove the rate field from pcm_runtime
>   * Use the hiFace name with the lowercase 'h' everywhere
>   * List actually supported devices in MODULE_SUPPORTED_DEVICE()
>   * Cosmetics, align values in the rates array
>   * Use an explicit switch instead of the rate_value array in
>     hiface_pcm_set_rate()
>   * Use usb_make_path() when building card->longname
>   * Use again the implicit mechanism to allocate the card private data
>   * Downgrade a pr_info to pr_debug in hiface_chip_probe()
>   * Make device_table const
>   * Rename PCM_MAX_PACKET_SIZE to PCM_PACKET_SIZE
>   * Rename MAX_BUFSIZE to PCM_BUFFER_SIZE
>   * Cosmetics, align symbolic constant values
>   * Add SNDRV_PCM_RATE_KNOT only when needed
>   * Declare memcpy_swahw32() as static
>   * Protect snd_pcm_stop() with snd_pcm_stream_lock_irq()
>   * Make hiface_pcm_playback() not returning anything
>   * Move the period elapsed check into hiface_pcm_playback()
>   * Handle the case of failing URBs in hiface_pcm_out_urb_handler()
>   * Fix a couple of checkpatch.pl issues
> 
> The incremental changes can be seen individually at
> https://github.com/panicking/snd-usb-asyncaudio/commits/master
> Commits from Feb. 10th and later.
>

Ping.

Hi, I don't see this one in:
http://git.kernel.org/?p=linux/kernel/git/tiwai/sound.git

If the code is OK, are we still in time for 3.9?

> Thanks,
>    Antonio
> 
>  sound/usb/Kconfig         |   31 +++
>  sound/usb/Makefile        |    2 +-
>  sound/usb/hiface/Makefile |    2 +
>  sound/usb/hiface/chip.h   |   34 +++
>  sound/usb/hiface/chip.c   |  329 +++++++++++++++++++++++
>  sound/usb/hiface/pcm.h    |   24 ++
>  sound/usb/hiface/pcm.c    |  638 +++++++++++++++++++++++++++++++++++++++++++++
>  7 files changed, 1059 insertions(+), 1 deletion(-)
>  create mode 100644 sound/usb/hiface/Makefile
>  create mode 100644 sound/usb/hiface/chip.h
>  create mode 100644 sound/usb/hiface/chip.c
>  create mode 100644 sound/usb/hiface/pcm.h
>  create mode 100644 sound/usb/hiface/pcm.c
> 
> diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig
> index 225dfd7..de9408b 100644
> --- a/sound/usb/Kconfig
> +++ b/sound/usb/Kconfig
> @@ -115,5 +115,36 @@ config SND_USB_6FIRE
>            and further help can be found at
>            http://sixfireusb.sourceforge.net
>  
> +config SND_USB_HIFACE
> +        tristate "M2Tech hiFace USB-SPDIF driver"
> +        select SND_PCM
> +        help
> +	  Select this option to include support for M2Tech hiFace USB-SPDIF
> +	  interface.
> +
> +	  This driver supports the original M2Tech hiFace and some other
> +	  compatible devices. The supported products are:
> +
> +	    * M2Tech Young
> +	    * M2Tech hiFace
> +	    * M2Tech North Star
> +	    * M2Tech W4S Young
> +	    * M2Tech Corrson
> +	    * M2Tech AUDIA
> +	    * M2Tech SL Audio
> +	    * M2Tech Empirical
> +	    * M2Tech Rockna
> +	    * M2Tech Pathos
> +	    * M2Tech Metronome
> +	    * M2Tech CAD
> +	    * M2Tech Audio Esclusive
> +	    * M2Tech Rotel
> +	    * M2Tech Eeaudio
> +	    * The Chord Company CHORD
> +	    * AVA Group A/S Vitus
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called snd-usb-hiface.
> +
>  endif	# SND_USB
>  
> diff --git a/sound/usb/Makefile b/sound/usb/Makefile
> index ac256dc..abe668f 100644
> --- a/sound/usb/Makefile
> +++ b/sound/usb/Makefile
> @@ -23,4 +23,4 @@ obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o
>  obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o
>  obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o
>  
> -obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/
> +obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/
> diff --git a/sound/usb/hiface/Makefile b/sound/usb/hiface/Makefile
> new file mode 100644
> index 0000000..463b136
> --- /dev/null
> +++ b/sound/usb/hiface/Makefile
> @@ -0,0 +1,2 @@
> +snd-usb-hiface-objs := chip.o pcm.o
> +obj-$(CONFIG_SND_USB_HIFACE) += snd-usb-hiface.o
> diff --git a/sound/usb/hiface/chip.h b/sound/usb/hiface/chip.h
> new file mode 100644
> index 0000000..cd615a0
> --- /dev/null
> +++ b/sound/usb/hiface/chip.h
> @@ -0,0 +1,34 @@
> +/*
> + * Linux driver for M2Tech hiFace compatible devices
> + *
> + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> + *
> + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> + *           Antonio Ospite <ao2@amarulasolutions.com>
> + *
> + * The driver is based on the work done in TerraTec DMX 6Fire USB
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#ifndef HIFACE_CHIP_H
> +#define HIFACE_CHIP_H
> +
> +#include <linux/usb.h>
> +#include <sound/core.h>
> +
> +struct pcm_runtime;
> +
> +struct hiface_chip {
> +	struct usb_device *dev;
> +	struct snd_card *card;
> +	int intf_count; /* number of registered interfaces */
> +	int index; /* index in module parameter arrays */
> +	bool shutdown;
> +
> +	struct pcm_runtime *pcm;
> +};
> +#endif /* HIFACE_CHIP_H */
> diff --git a/sound/usb/hiface/chip.c b/sound/usb/hiface/chip.c
> new file mode 100644
> index 0000000..f1293a5
> --- /dev/null
> +++ b/sound/usb/hiface/chip.c
> @@ -0,0 +1,329 @@
> +/*
> + * Linux driver for M2Tech hiFace compatible devices
> + *
> + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> + *
> + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> + *           Antonio Ospite <ao2@amarulasolutions.com>
> + *
> + * The driver is based on the work done in TerraTec DMX 6Fire USB
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <sound/initval.h>
> +
> +#include "chip.h"
> +#include "pcm.h"
> +
> +MODULE_AUTHOR("Michael Trimarchi <michael@amarulasolutions.com>");
> +MODULE_AUTHOR("Antonio Ospite <ao2@amarulasolutions.com>");
> +MODULE_DESCRIPTION("M2Tech hiFace USB-SPDIF audio driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_SUPPORTED_DEVICE("{{M2Tech,Young},"
> +			 "{M2Tech,hiFace},"
> +			 "{M2Tech,North Star},"
> +			 "{M2Tech,W4S Young},"
> +			 "{M2Tech,Corrson},"
> +			 "{M2Tech,AUDIA},"
> +			 "{M2Tech,SL Audio},"
> +			 "{M2Tech,Empirical},"
> +			 "{M2Tech,Rockna},"
> +			 "{M2Tech,Pathos},"
> +			 "{M2Tech,Metronome},"
> +			 "{M2Tech,CAD},"
> +			 "{M2Tech,Audio Esclusive},"
> +			 "{M2Tech,Rotel},"
> +			 "{M2Tech,Eeaudio},"
> +			 "{The Chord Company,CHORD},"
> +			 "{AVA Group A/S,Vitus}}");
> +
> +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
> +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */
> +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
> +
> +#define DRIVER_NAME "snd-usb-hiface"
> +#define CARD_NAME "hiFace"
> +
> +module_param_array(index, int, NULL, 0444);
> +MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
> +module_param_array(id, charp, NULL, 0444);
> +MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
> +module_param_array(enable, bool, NULL, 0444);
> +MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
> +
> +static struct hiface_chip *chips[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
> +
> +static DEFINE_MUTEX(register_mutex);
> +
> +struct hiface_vendor_quirk {
> +	const char *device_name;
> +	u8 extra_freq;
> +};
> +
> +static int hiface_chip_create(struct usb_device *device, int idx,
> +			      const struct hiface_vendor_quirk *quirk,
> +			      struct hiface_chip **rchip)
> +{
> +	struct snd_card *card = NULL;
> +	struct hiface_chip *chip;
> +	int ret;
> +	int len;
> +
> +	*rchip = NULL;
> +
> +	/* if we are here, card can be registered in alsa. */
> +	ret = snd_card_create(index[idx], id[idx], THIS_MODULE, sizeof(*chip), &card);
> +	if (ret < 0) {
> +		snd_printk(KERN_ERR "cannot create alsa card.\n");
> +		return ret;
> +	}
> +
> +	strcpy(card->driver, DRIVER_NAME);
> +
> +	if (quirk && quirk->device_name)
> +		strcpy(card->shortname, quirk->device_name);
> +	else
> +		strcpy(card->shortname, "M2Tech generic audio");
> +
> +	strlcat(card->longname, card->shortname, sizeof(card->longname));
> +	len = strlcat(card->longname, " at ", sizeof(card->longname));
> +	if (len < sizeof(card->longname))
> +		usb_make_path(device, card->longname + len,
> +			      sizeof(card->longname) - len);
> +
> +	chip = card->private_data;
> +	chip->dev = device;
> +	chip->index = idx;
> +	chip->card = card;
> +
> +	*rchip = chip;
> +	return 0;
> +}
> +
> +static int hiface_chip_probe(struct usb_interface *intf,
> +			     const struct usb_device_id *usb_id)
> +{
> +	const struct hiface_vendor_quirk *quirk = (struct hiface_vendor_quirk *)usb_id->driver_info;
> +	int ret;
> +	int i;
> +	struct hiface_chip *chip;
> +	struct usb_device *device = interface_to_usbdev(intf);
> +
> +	pr_debug("Probe " DRIVER_NAME " driver.\n");
> +
> +	ret = usb_set_interface(device, 0, 0);
> +	if (ret != 0) {
> +		snd_printk(KERN_ERR "can't set first interface for " CARD_NAME " device.\n");
> +		return -EIO;
> +	}
> +
> +	/* check whether the card is already registered */
> +	chip = NULL;
> +	mutex_lock(&register_mutex);
> +	for (i = 0; i < SNDRV_CARDS; i++) {
> +		if (chips[i] && chips[i]->dev == device) {
> +			if (chips[i]->shutdown) {
> +				snd_printk(KERN_ERR CARD_NAME " device is in the shutdown state, cannot create a card instance\n");
> +				ret = -ENODEV;
> +				goto err;
> +			}
> +			chip = chips[i];
> +			break;
> +		}
> +	}
> +	if (!chip) {
> +		/* it's a fresh one.
> +		 * now look for an empty slot and create a new card instance
> +		 */
> +		for (i = 0; i < SNDRV_CARDS; i++)
> +			if (enable[i] && !chips[i]) {
> +				ret = hiface_chip_create(device, i, quirk,
> +							 &chip);
> +				if (ret < 0)
> +					goto err;
> +
> +				snd_card_set_dev(chip->card, &intf->dev);
> +				break;
> +			}
> +		if (!chip) {
> +			snd_printk(KERN_ERR "no available " CARD_NAME " audio device\n");
> +			ret = -ENODEV;
> +			goto err;
> +		}
> +	}
> +
> +	ret = hiface_pcm_init(chip, quirk ? quirk->extra_freq : 0);
> +	if (ret < 0)
> +		goto err_chip_destroy;
> +
> +	ret = snd_card_register(chip->card);
> +	if (ret < 0) {
> +		snd_printk(KERN_ERR "cannot register " CARD_NAME " card\n");
> +		goto err_chip_destroy;
> +	}
> +
> +	chips[chip->index] = chip;
> +	chip->intf_count++;
> +
> +	mutex_unlock(&register_mutex);
> +
> +	usb_set_intfdata(intf, chip);
> +	return 0;
> +
> +err_chip_destroy:
> +	snd_card_free(chip->card);
> +err:
> +	mutex_unlock(&register_mutex);
> +	return ret;
> +}
> +
> +static void hiface_chip_disconnect(struct usb_interface *intf)
> +{
> +	struct hiface_chip *chip;
> +	struct snd_card *card;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	chip = usb_get_intfdata(intf);
> +	if (!chip)
> +		return;
> +
> +	card = chip->card;
> +	chip->intf_count--;
> +	if (chip->intf_count <= 0) {
> +		/* Make sure that the userspace cannot create new request */
> +		snd_card_disconnect(card);
> +
> +		mutex_lock(&register_mutex);
> +		chips[chip->index] = NULL;
> +		mutex_unlock(&register_mutex);
> +
> +		chip->shutdown = true;
> +		hiface_pcm_abort(chip);
> +		snd_card_free_when_closed(card);
> +	}
> +}
> +
> +static const struct usb_device_id device_table[] = {
> +	{
> +		USB_DEVICE(0x04b4, 0x0384),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Young",
> +			.extra_freq = 1,
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x930b),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "hiFace",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x931b),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "North Star",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x931c),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "W4S Young",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x931d),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Corrson",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x931e),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "AUDIA",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x931f),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "SL Audio",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x9320),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Empirical",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x9321),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Rockna",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x9001),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Pathos",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x9002),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Metronome",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x9006),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "CAD",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x9008),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Audio Esclusive",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x931c),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Rotel",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x932c),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Eeaudio",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x245f, 0x931c),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "CHORD",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x25c6, 0x9002),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Vitus",
> +		}
> +	},
> +	{}
> +};
> +
> +MODULE_DEVICE_TABLE(usb, device_table);
> +
> +static struct usb_driver hiface_usb_driver = {
> +	.name = DRIVER_NAME,
> +	.probe = hiface_chip_probe,
> +	.disconnect = hiface_chip_disconnect,
> +	.id_table = device_table,
> +};
> +
> +module_usb_driver(hiface_usb_driver);
> diff --git a/sound/usb/hiface/pcm.h b/sound/usb/hiface/pcm.h
> new file mode 100644
> index 0000000..77edd7c
> --- /dev/null
> +++ b/sound/usb/hiface/pcm.h
> @@ -0,0 +1,24 @@
> +/*
> + * Linux driver for M2Tech hiFace compatible devices
> + *
> + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> + *
> + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> + *           Antonio Ospite <ao2@amarulasolutions.com>
> + *
> + * The driver is based on the work done in TerraTec DMX 6Fire USB
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#ifndef HIFACE_PCM_H
> +#define HIFACE_PCM_H
> +
> +struct hiface_chip;
> +
> +int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq);
> +void hiface_pcm_abort(struct hiface_chip *chip);
> +#endif /* HIFACE_PCM_H */
> diff --git a/sound/usb/hiface/pcm.c b/sound/usb/hiface/pcm.c
> new file mode 100644
> index 0000000..d0c3c58
> --- /dev/null
> +++ b/sound/usb/hiface/pcm.c
> @@ -0,0 +1,638 @@
> +/*
> + * Linux driver for M2Tech hiFace compatible devices
> + *
> + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> + *
> + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> + *           Antonio Ospite <ao2@amarulasolutions.com>
> + *
> + * The driver is based on the work done in TerraTec DMX 6Fire USB
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#include <linux/slab.h>
> +#include <sound/pcm.h>
> +
> +#include "pcm.h"
> +#include "chip.h"
> +
> +#define OUT_EP          0x2
> +#define PCM_N_URBS      8
> +#define PCM_PACKET_SIZE 4096
> +#define PCM_BUFFER_SIZE (2 * PCM_N_URBS * PCM_PACKET_SIZE)
> +
> +struct pcm_urb {
> +	struct hiface_chip *chip;
> +
> +	struct urb instance;
> +	struct usb_anchor submitted;
> +	u8 *buffer;
> +};
> +
> +struct pcm_substream {
> +	spinlock_t lock;
> +	struct snd_pcm_substream *instance;
> +
> +	bool active;
> +	snd_pcm_uframes_t dma_off;    /* current position in alsa dma_area */
> +	snd_pcm_uframes_t period_off; /* current position in current period */
> +};
> +
> +enum { /* pcm streaming states */
> +	STREAM_DISABLED, /* no pcm streaming */
> +	STREAM_STARTING, /* pcm streaming requested, waiting to become ready */
> +	STREAM_RUNNING,  /* pcm streaming running */
> +	STREAM_STOPPING
> +};
> +
> +struct pcm_runtime {
> +	struct hiface_chip *chip;
> +	struct snd_pcm *instance;
> +
> +	struct pcm_substream playback;
> +	bool panic; /* if set driver won't do anymore pcm on device */
> +
> +	struct pcm_urb out_urbs[PCM_N_URBS];
> +
> +	struct mutex stream_mutex;
> +	u8 stream_state; /* one of STREAM_XXX */
> +	u8 extra_freq;
> +	wait_queue_head_t stream_wait_queue;
> +	bool stream_wait_cond;
> +};
> +
> +static const unsigned int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000,
> +				      352800, 384000 };
> +static const struct snd_pcm_hw_constraint_list constraints_extra_rates = {
> +	.count = ARRAY_SIZE(rates),
> +	.list = rates,
> +	.mask = 0,
> +};
> +
> +static const struct snd_pcm_hardware pcm_hw = {
> +	.info = SNDRV_PCM_INFO_MMAP |
> +		SNDRV_PCM_INFO_INTERLEAVED |
> +		SNDRV_PCM_INFO_BLOCK_TRANSFER |
> +		SNDRV_PCM_INFO_PAUSE |
> +		SNDRV_PCM_INFO_MMAP_VALID |
> +		SNDRV_PCM_INFO_BATCH,
> +
> +	.formats = SNDRV_PCM_FMTBIT_S32_LE,
> +
> +	.rates = SNDRV_PCM_RATE_44100 |
> +		SNDRV_PCM_RATE_48000 |
> +		SNDRV_PCM_RATE_88200 |
> +		SNDRV_PCM_RATE_96000 |
> +		SNDRV_PCM_RATE_176400 |
> +		SNDRV_PCM_RATE_192000,
> +
> +	.rate_min = 44100,
> +	.rate_max = 192000, /* changes in hiface_pcm_open to support extra rates */
> +	.channels_min = 2,
> +	.channels_max = 2,
> +	.buffer_bytes_max = PCM_BUFFER_SIZE,
> +	.period_bytes_min = PCM_PACKET_SIZE,
> +	.period_bytes_max = PCM_BUFFER_SIZE,
> +	.periods_min = 2,
> +	.periods_max = 1024
> +};
> +
> +/* message values used to change the sample rate */
> +#define HIFACE_SET_RATE_REQUEST 0xb0
> +
> +#define HIFACE_RATE_44100  0x43
> +#define HIFACE_RATE_48000  0x4b
> +#define HIFACE_RATE_88200  0x42
> +#define HIFACE_RATE_96000  0x4a
> +#define HIFACE_RATE_176400 0x40
> +#define HIFACE_RATE_192000 0x48
> +#define HIFACE_RATE_352000 0x58
> +#define HIFACE_RATE_384000 0x68
> +
> +static int hiface_pcm_set_rate(struct pcm_runtime *rt, unsigned int rate)
> +{
> +	struct usb_device *device = rt->chip->dev;
> +	u16 rate_value;
> +	int ret;
> +
> +	/* We are already sure that the rate is supported here thanks to
> +	 * ALSA constraints
> +	 */
> +	switch (rate) {
> +	case 44100:
> +		rate_value = HIFACE_RATE_44100;
> +		break;
> +	case 48000:
> +		rate_value = HIFACE_RATE_48000;
> +		break;
> +	case 88200:
> +		rate_value = HIFACE_RATE_88200;
> +		break;
> +	case 96000:
> +		rate_value = HIFACE_RATE_96000;
> +		break;
> +	case 176400:
> +		rate_value = HIFACE_RATE_176400;
> +		break;
> +	case 192000:
> +		rate_value = HIFACE_RATE_192000;
> +		break;
> +	case 352000:
> +		rate_value = HIFACE_RATE_352000;
> +		break;
> +	case 384000:
> +		rate_value = HIFACE_RATE_384000;
> +		break;
> +	default:
> +		snd_printk(KERN_ERR "Unsupported rate %d\n", rate);
> +		return -EINVAL;
> +	}
> +
> +	/*
> +	 * USBIO: Vendor 0xb0(wValue=0x0043, wIndex=0x0000)
> +	 * 43 b0 43 00 00 00 00 00
> +	 * USBIO: Vendor 0xb0(wValue=0x004b, wIndex=0x0000)
> +	 * 43 b0 4b 00 00 00 00 00
> +	 * This control message doesn't have any ack from the
> +	 * other side
> +	 */
> +	ret = usb_control_msg(device, usb_sndctrlpipe(device, 0),
> +				HIFACE_SET_RATE_REQUEST,
> +				USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
> +				rate_value, 0, NULL, 0, 100);
> +	if (ret < 0) {
> +		snd_printk(KERN_ERR "Error setting samplerate %d.\n", rate);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static struct pcm_substream *hiface_pcm_get_substream(
> +		struct snd_pcm_substream *alsa_sub)
> +{
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +
> +	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
> +		return &rt->playback;
> +
> +	pr_debug("Error getting pcm substream slot.\n");
> +	return NULL;
> +}
> +
> +/* call with stream_mutex locked */
> +static void hiface_pcm_stream_stop(struct pcm_runtime *rt)
> +{
> +	int i, time;
> +
> +	if (rt->stream_state != STREAM_DISABLED) {
> +		rt->stream_state = STREAM_STOPPING;
> +
> +		for (i = 0; i < PCM_N_URBS; i++) {
> +			time = usb_wait_anchor_empty_timeout(
> +					&rt->out_urbs[i].submitted, 100);
> +			if (!time)
> +				usb_kill_anchored_urbs(
> +					&rt->out_urbs[i].submitted);
> +			usb_kill_urb(&rt->out_urbs[i].instance);
> +		}
> +
> +		rt->stream_state = STREAM_DISABLED;
> +	}
> +}
> +
> +/* call with stream_mutex locked */
> +static int hiface_pcm_stream_start(struct pcm_runtime *rt)
> +{
> +	int ret = 0, i;
> +
> +	if (rt->stream_state == STREAM_DISABLED) {
> +		/* submit our out urbs zero init */
> +		rt->stream_state = STREAM_STARTING;
> +		for (i = 0; i < PCM_N_URBS; i++) {
> +			memset(rt->out_urbs[i].buffer, 0, PCM_PACKET_SIZE);
> +			usb_anchor_urb(&rt->out_urbs[i].instance,
> +				       &rt->out_urbs[i].submitted);
> +			ret = usb_submit_urb(&rt->out_urbs[i].instance,
> +					GFP_ATOMIC);
> +			if (ret) {
> +				hiface_pcm_stream_stop(rt);
> +				return ret;
> +			}
> +		}
> +
> +		/* wait for first out urb to return (sent in in urb handler) */
> +		wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond,
> +				HZ);
> +		if (rt->stream_wait_cond) {
> +			pr_debug("%s: Stream is running wakeup event\n",
> +				 __func__);
> +			rt->stream_state = STREAM_RUNNING;
> +		} else {
> +			hiface_pcm_stream_stop(rt);
> +			return -EIO;
> +		}
> +	}
> +	return ret;
> +}
> +
> +
> +/* The hardware wants word-swapped 32-bit values */
> +static void memcpy_swahw32(u8 *dest, u8 *src, unsigned int n)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < n / 4; i++)
> +		((u32 *)dest)[i] = swahw32(((u32 *)src)[i]);
> +}
> +
> +/* call with substream locked */
> +/* returns true if a period elapsed */
> +static bool hiface_pcm_playback(struct pcm_substream *sub,
> +		struct pcm_urb *urb)
> +{
> +	struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
> +	u8 *source;
> +	unsigned int pcm_buffer_size;
> +
> +	WARN_ON(alsa_rt->format != SNDRV_PCM_FORMAT_S32_LE);
> +
> +	pcm_buffer_size = snd_pcm_lib_buffer_bytes(sub->instance);
> +
> +	if (sub->dma_off + PCM_PACKET_SIZE <= pcm_buffer_size) {
> +		pr_debug("%s: (1) buffer_size %#x dma_offset %#x\n", __func__,
> +			 (unsigned int) pcm_buffer_size,
> +			 (unsigned int) sub->dma_off);
> +
> +		source = alsa_rt->dma_area + sub->dma_off;
> +		memcpy_swahw32(urb->buffer, source, PCM_PACKET_SIZE);
> +	} else {
> +		/* wrap around at end of ring buffer */
> +		unsigned int len;
> +
> +		pr_debug("%s: (2) buffer_size %#x dma_offset %#x\n", __func__,
> +			 (unsigned int) pcm_buffer_size,
> +			 (unsigned int) sub->dma_off);
> +
> +		len = pcm_buffer_size - sub->dma_off;
> +
> +		source = alsa_rt->dma_area + sub->dma_off;
> +		memcpy_swahw32(urb->buffer, source, len);
> +
> +		source = alsa_rt->dma_area;
> +		memcpy_swahw32(urb->buffer + len, source,
> +			       PCM_PACKET_SIZE - len);
> +	}
> +	sub->dma_off += PCM_PACKET_SIZE;
> +	if (sub->dma_off >= pcm_buffer_size)
> +		sub->dma_off -= pcm_buffer_size;
> +
> +	sub->period_off += PCM_PACKET_SIZE;
> +	if (sub->period_off >= alsa_rt->period_size) {
> +		sub->period_off %= alsa_rt->period_size;
> +		return true;
> +	}
> +	return false;
> +}
> +
> +static void hiface_pcm_out_urb_handler(struct urb *usb_urb)
> +{
> +	struct pcm_urb *out_urb = usb_urb->context;
> +	struct pcm_runtime *rt = out_urb->chip->pcm;
> +	struct pcm_substream *sub;
> +	bool do_period_elapsed = false;
> +	unsigned long flags;
> +	int ret;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	if (rt->panic || rt->stream_state == STREAM_STOPPING)
> +		return;
> +
> +	if (unlikely(usb_urb->status == -ENOENT ||	/* unlinked */
> +		     usb_urb->status == -ENODEV ||	/* device removed */
> +		     usb_urb->status == -ECONNRESET ||	/* unlinked */
> +		     usb_urb->status == -ESHUTDOWN)) {	/* device disabled */
> +		goto out_fail;
> +	}
> +
> +	if (rt->stream_state == STREAM_STARTING) {
> +		rt->stream_wait_cond = true;
> +		wake_up(&rt->stream_wait_queue);
> +	}
> +
> +	/* now send our playback data (if a free out urb was found) */
> +	sub = &rt->playback;
> +	spin_lock_irqsave(&sub->lock, flags);
> +	if (sub->active)
> +		do_period_elapsed = hiface_pcm_playback(sub, out_urb);
> +	else
> +		memset(out_urb->buffer, 0, PCM_PACKET_SIZE);
> +
> +	spin_unlock_irqrestore(&sub->lock, flags);
> +
> +	if (do_period_elapsed)
> +		snd_pcm_period_elapsed(sub->instance);
> +
> +	ret = usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
> +	if (ret < 0)
> +		goto out_fail;
> +
> +	return;
> +
> +out_fail:
> +	rt->panic = true;
> +}
> +
> +static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub)
> +{
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	struct pcm_substream *sub = NULL;
> +	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
> +	int ret;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	if (rt->panic)
> +		return -EPIPE;
> +
> +	mutex_lock(&rt->stream_mutex);
> +	alsa_rt->hw = pcm_hw;
> +
> +	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
> +		sub = &rt->playback;
> +
> +	if (!sub) {
> +		mutex_unlock(&rt->stream_mutex);
> +		pr_err("Invalid stream type\n");
> +		return -EINVAL;
> +	}
> +
> +	if (rt->extra_freq) {
> +		alsa_rt->hw.rates |= SNDRV_PCM_RATE_KNOT;
> +		alsa_rt->hw.rate_max = 384000;
> +
> +		/* explicit constraints needed as we added SNDRV_PCM_RATE_KNOT */
> +		ret = snd_pcm_hw_constraint_list(alsa_sub->runtime, 0,
> +						 SNDRV_PCM_HW_PARAM_RATE,
> +						 &constraints_extra_rates);
> +		if (ret < 0) {
> +			mutex_unlock(&rt->stream_mutex);
> +			return ret;
> +		}
> +	}
> +
> +	sub->instance = alsa_sub;
> +	sub->active = false;
> +	mutex_unlock(&rt->stream_mutex);
> +	return 0;
> +}
> +
> +static int hiface_pcm_close(struct snd_pcm_substream *alsa_sub)
> +{
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> +	unsigned long flags;
> +
> +	if (rt->panic)
> +		return 0;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	mutex_lock(&rt->stream_mutex);
> +	if (sub) {
> +		/* deactivate substream */
> +		spin_lock_irqsave(&sub->lock, flags);
> +		sub->instance = NULL;
> +		sub->active = false;
> +		spin_unlock_irqrestore(&sub->lock, flags);
> +
> +		/* all substreams closed? if so, stop streaming */
> +		if (!rt->playback.instance)
> +			hiface_pcm_stream_stop(rt);
> +	}
> +	mutex_unlock(&rt->stream_mutex);
> +	return 0;
> +}
> +
> +static int hiface_pcm_hw_params(struct snd_pcm_substream *alsa_sub,
> +		struct snd_pcm_hw_params *hw_params)
> +{
> +	pr_debug("%s: called.\n", __func__);
> +	return snd_pcm_lib_malloc_pages(alsa_sub,
> +			params_buffer_bytes(hw_params));
> +}
> +
> +static int hiface_pcm_hw_free(struct snd_pcm_substream *alsa_sub)
> +{
> +	pr_debug("%s: called.\n", __func__);
> +	return snd_pcm_lib_free_pages(alsa_sub);
> +}
> +
> +static int hiface_pcm_prepare(struct snd_pcm_substream *alsa_sub)
> +{
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> +	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
> +	int ret;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	if (rt->panic)
> +		return -EPIPE;
> +	if (!sub)
> +		return -ENODEV;
> +
> +	mutex_lock(&rt->stream_mutex);
> +
> +	sub->dma_off = 0;
> +	sub->period_off = 0;
> +
> +	if (rt->stream_state == STREAM_DISABLED) {
> +
> +		ret = hiface_pcm_set_rate(rt, alsa_rt->rate);
> +		if (ret) {
> +			mutex_unlock(&rt->stream_mutex);
> +			return ret;
> +		}
> +		ret = hiface_pcm_stream_start(rt);
> +		if (ret) {
> +			mutex_unlock(&rt->stream_mutex);
> +			return ret;
> +		}
> +	}
> +	mutex_unlock(&rt->stream_mutex);
> +	return 0;
> +}
> +
> +static int hiface_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
> +{
> +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	unsigned long flags;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	if (rt->panic)
> +		return -EPIPE;
> +	if (!sub)
> +		return -ENODEV;
> +
> +	switch (cmd) {
> +	case SNDRV_PCM_TRIGGER_START:
> +	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
> +		spin_lock_irqsave(&sub->lock, flags);
> +		sub->active = true;
> +		spin_unlock_irqrestore(&sub->lock, flags);
> +		return 0;
> +
> +	case SNDRV_PCM_TRIGGER_STOP:
> +	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
> +		spin_lock_irqsave(&sub->lock, flags);
> +		sub->active = false;
> +		spin_unlock_irqrestore(&sub->lock, flags);
> +		return 0;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static snd_pcm_uframes_t hiface_pcm_pointer(struct snd_pcm_substream *alsa_sub)
> +{
> +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	unsigned long flags;
> +	snd_pcm_uframes_t dma_offset;
> +
> +	if (rt->panic || !sub)
> +		return SNDRV_PCM_STATE_XRUN;
> +
> +	spin_lock_irqsave(&sub->lock, flags);
> +	dma_offset = sub->dma_off;
> +	spin_unlock_irqrestore(&sub->lock, flags);
> +	return bytes_to_frames(alsa_sub->runtime, dma_offset);
> +}
> +
> +static struct snd_pcm_ops pcm_ops = {
> +	.open = hiface_pcm_open,
> +	.close = hiface_pcm_close,
> +	.ioctl = snd_pcm_lib_ioctl,
> +	.hw_params = hiface_pcm_hw_params,
> +	.hw_free = hiface_pcm_hw_free,
> +	.prepare = hiface_pcm_prepare,
> +	.trigger = hiface_pcm_trigger,
> +	.pointer = hiface_pcm_pointer,
> +};
> +
> +static int hiface_pcm_init_urb(struct pcm_urb *urb,
> +			       struct hiface_chip *chip,
> +			       unsigned int ep,
> +			       void (*handler)(struct urb *))
> +{
> +	urb->chip = chip;
> +	usb_init_urb(&urb->instance);
> +
> +	urb->buffer = kzalloc(PCM_PACKET_SIZE, GFP_KERNEL);
> +	if (!urb->buffer)
> +		return -ENOMEM;
> +
> +	usb_fill_bulk_urb(&urb->instance, chip->dev,
> +			  usb_sndbulkpipe(chip->dev, ep), (void *)urb->buffer,
> +			  PCM_PACKET_SIZE, handler, urb);
> +	init_usb_anchor(&urb->submitted);
> +
> +	return 0;
> +}
> +
> +void hiface_pcm_abort(struct hiface_chip *chip)
> +{
> +	struct pcm_runtime *rt = chip->pcm;
> +
> +	if (rt) {
> +		rt->panic = true;
> +
> +		if (rt->playback.instance) {
> +			snd_pcm_stream_lock_irq(rt->playback.instance);
> +			snd_pcm_stop(rt->playback.instance,
> +					SNDRV_PCM_STATE_XRUN);
> +			snd_pcm_stream_unlock_irq(rt->playback.instance);
> +		}
> +		mutex_lock(&rt->stream_mutex);
> +		hiface_pcm_stream_stop(rt);
> +		mutex_unlock(&rt->stream_mutex);
> +	}
> +}
> +
> +static void hiface_pcm_destroy(struct hiface_chip *chip)
> +{
> +	struct pcm_runtime *rt = chip->pcm;
> +	int i;
> +
> +	for (i = 0; i < PCM_N_URBS; i++)
> +		kfree(rt->out_urbs[i].buffer);
> +
> +	kfree(chip->pcm);
> +	chip->pcm = NULL;
> +}
> +
> +static void hiface_pcm_free(struct snd_pcm *pcm)
> +{
> +	struct pcm_runtime *rt = pcm->private_data;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	if (rt)
> +		hiface_pcm_destroy(rt->chip);
> +}
> +
> +int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq)
> +{
> +	int i;
> +	int ret;
> +	struct snd_pcm *pcm;
> +	struct pcm_runtime *rt;
> +
> +	rt = kzalloc(sizeof(*rt), GFP_KERNEL);
> +	if (!rt)
> +		return -ENOMEM;
> +
> +	rt->chip = chip;
> +	rt->stream_state = STREAM_DISABLED;
> +	if (extra_freq)
> +		rt->extra_freq = 1;
> +
> +	init_waitqueue_head(&rt->stream_wait_queue);
> +	mutex_init(&rt->stream_mutex);
> +	spin_lock_init(&rt->playback.lock);
> +
> +	for (i = 0; i < PCM_N_URBS; i++)
> +		hiface_pcm_init_urb(&rt->out_urbs[i], chip, OUT_EP,
> +				hiface_pcm_out_urb_handler);
> +
> +	ret = snd_pcm_new(chip->card, "USB-SPDIF Audio", 0, 1, 0, &pcm);
> +	if (ret < 0) {
> +		kfree(rt);
> +		pr_err("Cannot create pcm instance\n");
> +		return ret;
> +	}
> +
> +	pcm->private_data = rt;
> +	pcm->private_free = hiface_pcm_free;
> +
> +	strcpy(pcm->name, "USB-SPDIF Audio");
> +	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops);
> +
> +	snd_pcm_lib_preallocate_pages_for_all(pcm,
> +					SNDRV_DMA_TYPE_CONTINUOUS,
> +					snd_dma_continuous_data(GFP_KERNEL),
> +					PCM_BUFFER_SIZE, PCM_BUFFER_SIZE);
> +	rt->instance = pcm;
> +
> +	chip->pcm = rt;
> +	return 0;
> +}
> -- 
> 1.7.10.4
> 
> _______________________________________________
> Alsa-devel mailing list
> Alsa-devel@alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel


-- 
Antonio Ospite
http://ao2.it

A: Because it messes up the order in which people normally read text.
   See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?

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

* Re: [PATCH v2] Add M2Tech hiFace USB-SPDIF driver
  2013-02-22 10:48   ` Antonio Ospite
@ 2013-02-22 12:52     ` Takashi Iwai
  2013-02-22 13:31       ` Antonio Ospite
  0 siblings, 1 reply; 25+ messages in thread
From: Takashi Iwai @ 2013-02-22 12:52 UTC (permalink / raw)
  To: Antonio Ospite
  Cc: alsa-devel, Clemens Ladisch, fchecconi, Pietro Cipriano, alberto,
	Michael Trimarchi

At Fri, 22 Feb 2013 11:48:32 +0100,
Antonio Ospite wrote:
> 
> On Wed, 13 Feb 2013 18:11:45 +0100
> Antonio Ospite <ao2@amarulasolutions.com> wrote:
> 
> > Add driver for M2Tech hiFace USB-SPDIF interface and compatible devices.
> > 
> > M2Tech hiFace and compatible devices offer a Hi-End S/PDIF Output
> > Interface, see http://www.m2tech.biz/hiface.html
> > 
> > The supported products are:
> > 
> >   * M2Tech Young
> >   * M2Tech hiFace
> >   * M2Tech North Star
> >   * M2Tech W4S Young
> >   * M2Tech Corrson
> >   * M2Tech AUDIA
> >   * M2Tech SL Audio
> >   * M2Tech Empirical
> >   * M2Tech Rockna
> >   * M2Tech Pathos
> >   * M2Tech Metronome
> >   * M2Tech CAD
> >   * M2Tech Audio Esclusive
> >   * M2Tech Rotel
> >   * M2Tech Eeaudio
> >   * The Chord Company CHORD
> >   * AVA Group A/S Vitus
> > 
> > Signed-off-by: Antonio Ospite <ao2@amarulasolutions.com>
> > ---
> > 
> > Changes since v1:
> > 
> >   * Change the first sentence of the Kconfig entry into "Select this..."
> >   * Remove a useless sentence from the Kconfig entry
> >   * Don't set alsa_rt->hw.rates in hiface_pcm_open()
> >   * Style cleanup, no braces needed in single statement conditional
> >   * Remove the rate field from pcm_runtime
> >   * Use the hiFace name with the lowercase 'h' everywhere
> >   * List actually supported devices in MODULE_SUPPORTED_DEVICE()
> >   * Cosmetics, align values in the rates array
> >   * Use an explicit switch instead of the rate_value array in
> >     hiface_pcm_set_rate()
> >   * Use usb_make_path() when building card->longname
> >   * Use again the implicit mechanism to allocate the card private data
> >   * Downgrade a pr_info to pr_debug in hiface_chip_probe()
> >   * Make device_table const
> >   * Rename PCM_MAX_PACKET_SIZE to PCM_PACKET_SIZE
> >   * Rename MAX_BUFSIZE to PCM_BUFFER_SIZE
> >   * Cosmetics, align symbolic constant values
> >   * Add SNDRV_PCM_RATE_KNOT only when needed
> >   * Declare memcpy_swahw32() as static
> >   * Protect snd_pcm_stop() with snd_pcm_stream_lock_irq()
> >   * Make hiface_pcm_playback() not returning anything
> >   * Move the period elapsed check into hiface_pcm_playback()
> >   * Handle the case of failing URBs in hiface_pcm_out_urb_handler()
> >   * Fix a couple of checkpatch.pl issues
> > 
> > The incremental changes can be seen individually at
> > https://github.com/panicking/snd-usb-asyncaudio/commits/master
> > Commits from Feb. 10th and later.
> >
> 
> Ping.
> 
> Hi, I don't see this one in:
> http://git.kernel.org/?p=linux/kernel/git/tiwai/sound.git
> 
> If the code is OK, are we still in time for 3.9?

Sorry, I haven't reviewed the code yet (since I thought Clemens would
re-review at first :)  I'm inclined to merge only fix patches at this
point for 3.9, so the merge will be postponed to 3.10.

I'll take a look at the v2 patch.


Takashi


> 
> > Thanks,
> >    Antonio
> > 
> >  sound/usb/Kconfig         |   31 +++
> >  sound/usb/Makefile        |    2 +-
> >  sound/usb/hiface/Makefile |    2 +
> >  sound/usb/hiface/chip.h   |   34 +++
> >  sound/usb/hiface/chip.c   |  329 +++++++++++++++++++++++
> >  sound/usb/hiface/pcm.h    |   24 ++
> >  sound/usb/hiface/pcm.c    |  638 +++++++++++++++++++++++++++++++++++++++++++++
> >  7 files changed, 1059 insertions(+), 1 deletion(-)
> >  create mode 100644 sound/usb/hiface/Makefile
> >  create mode 100644 sound/usb/hiface/chip.h
> >  create mode 100644 sound/usb/hiface/chip.c
> >  create mode 100644 sound/usb/hiface/pcm.h
> >  create mode 100644 sound/usb/hiface/pcm.c
> > 
> > diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig
> > index 225dfd7..de9408b 100644
> > --- a/sound/usb/Kconfig
> > +++ b/sound/usb/Kconfig
> > @@ -115,5 +115,36 @@ config SND_USB_6FIRE
> >            and further help can be found at
> >            http://sixfireusb.sourceforge.net
> >  
> > +config SND_USB_HIFACE
> > +        tristate "M2Tech hiFace USB-SPDIF driver"
> > +        select SND_PCM
> > +        help
> > +	  Select this option to include support for M2Tech hiFace USB-SPDIF
> > +	  interface.
> > +
> > +	  This driver supports the original M2Tech hiFace and some other
> > +	  compatible devices. The supported products are:
> > +
> > +	    * M2Tech Young
> > +	    * M2Tech hiFace
> > +	    * M2Tech North Star
> > +	    * M2Tech W4S Young
> > +	    * M2Tech Corrson
> > +	    * M2Tech AUDIA
> > +	    * M2Tech SL Audio
> > +	    * M2Tech Empirical
> > +	    * M2Tech Rockna
> > +	    * M2Tech Pathos
> > +	    * M2Tech Metronome
> > +	    * M2Tech CAD
> > +	    * M2Tech Audio Esclusive
> > +	    * M2Tech Rotel
> > +	    * M2Tech Eeaudio
> > +	    * The Chord Company CHORD
> > +	    * AVA Group A/S Vitus
> > +
> > +	  To compile this driver as a module, choose M here: the module
> > +	  will be called snd-usb-hiface.
> > +
> >  endif	# SND_USB
> >  
> > diff --git a/sound/usb/Makefile b/sound/usb/Makefile
> > index ac256dc..abe668f 100644
> > --- a/sound/usb/Makefile
> > +++ b/sound/usb/Makefile
> > @@ -23,4 +23,4 @@ obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o
> >  obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o
> >  obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o
> >  
> > -obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/
> > +obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/
> > diff --git a/sound/usb/hiface/Makefile b/sound/usb/hiface/Makefile
> > new file mode 100644
> > index 0000000..463b136
> > --- /dev/null
> > +++ b/sound/usb/hiface/Makefile
> > @@ -0,0 +1,2 @@
> > +snd-usb-hiface-objs := chip.o pcm.o
> > +obj-$(CONFIG_SND_USB_HIFACE) += snd-usb-hiface.o
> > diff --git a/sound/usb/hiface/chip.h b/sound/usb/hiface/chip.h
> > new file mode 100644
> > index 0000000..cd615a0
> > --- /dev/null
> > +++ b/sound/usb/hiface/chip.h
> > @@ -0,0 +1,34 @@
> > +/*
> > + * Linux driver for M2Tech hiFace compatible devices
> > + *
> > + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> > + *
> > + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> > + *           Antonio Ospite <ao2@amarulasolutions.com>
> > + *
> > + * The driver is based on the work done in TerraTec DMX 6Fire USB
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License as published by
> > + * the Free Software Foundation; either version 2 of the License, or
> > + * (at your option) any later version.
> > + */
> > +
> > +#ifndef HIFACE_CHIP_H
> > +#define HIFACE_CHIP_H
> > +
> > +#include <linux/usb.h>
> > +#include <sound/core.h>
> > +
> > +struct pcm_runtime;
> > +
> > +struct hiface_chip {
> > +	struct usb_device *dev;
> > +	struct snd_card *card;
> > +	int intf_count; /* number of registered interfaces */
> > +	int index; /* index in module parameter arrays */
> > +	bool shutdown;
> > +
> > +	struct pcm_runtime *pcm;
> > +};
> > +#endif /* HIFACE_CHIP_H */
> > diff --git a/sound/usb/hiface/chip.c b/sound/usb/hiface/chip.c
> > new file mode 100644
> > index 0000000..f1293a5
> > --- /dev/null
> > +++ b/sound/usb/hiface/chip.c
> > @@ -0,0 +1,329 @@
> > +/*
> > + * Linux driver for M2Tech hiFace compatible devices
> > + *
> > + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> > + *
> > + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> > + *           Antonio Ospite <ao2@amarulasolutions.com>
> > + *
> > + * The driver is based on the work done in TerraTec DMX 6Fire USB
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License as published by
> > + * the Free Software Foundation; either version 2 of the License, or
> > + * (at your option) any later version.
> > + */
> > +
> > +#include <linux/module.h>
> > +#include <linux/slab.h>
> > +#include <sound/initval.h>
> > +
> > +#include "chip.h"
> > +#include "pcm.h"
> > +
> > +MODULE_AUTHOR("Michael Trimarchi <michael@amarulasolutions.com>");
> > +MODULE_AUTHOR("Antonio Ospite <ao2@amarulasolutions.com>");
> > +MODULE_DESCRIPTION("M2Tech hiFace USB-SPDIF audio driver");
> > +MODULE_LICENSE("GPL v2");
> > +MODULE_SUPPORTED_DEVICE("{{M2Tech,Young},"
> > +			 "{M2Tech,hiFace},"
> > +			 "{M2Tech,North Star},"
> > +			 "{M2Tech,W4S Young},"
> > +			 "{M2Tech,Corrson},"
> > +			 "{M2Tech,AUDIA},"
> > +			 "{M2Tech,SL Audio},"
> > +			 "{M2Tech,Empirical},"
> > +			 "{M2Tech,Rockna},"
> > +			 "{M2Tech,Pathos},"
> > +			 "{M2Tech,Metronome},"
> > +			 "{M2Tech,CAD},"
> > +			 "{M2Tech,Audio Esclusive},"
> > +			 "{M2Tech,Rotel},"
> > +			 "{M2Tech,Eeaudio},"
> > +			 "{The Chord Company,CHORD},"
> > +			 "{AVA Group A/S,Vitus}}");
> > +
> > +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
> > +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */
> > +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
> > +
> > +#define DRIVER_NAME "snd-usb-hiface"
> > +#define CARD_NAME "hiFace"
> > +
> > +module_param_array(index, int, NULL, 0444);
> > +MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
> > +module_param_array(id, charp, NULL, 0444);
> > +MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
> > +module_param_array(enable, bool, NULL, 0444);
> > +MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
> > +
> > +static struct hiface_chip *chips[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
> > +
> > +static DEFINE_MUTEX(register_mutex);
> > +
> > +struct hiface_vendor_quirk {
> > +	const char *device_name;
> > +	u8 extra_freq;
> > +};
> > +
> > +static int hiface_chip_create(struct usb_device *device, int idx,
> > +			      const struct hiface_vendor_quirk *quirk,
> > +			      struct hiface_chip **rchip)
> > +{
> > +	struct snd_card *card = NULL;
> > +	struct hiface_chip *chip;
> > +	int ret;
> > +	int len;
> > +
> > +	*rchip = NULL;
> > +
> > +	/* if we are here, card can be registered in alsa. */
> > +	ret = snd_card_create(index[idx], id[idx], THIS_MODULE, sizeof(*chip), &card);
> > +	if (ret < 0) {
> > +		snd_printk(KERN_ERR "cannot create alsa card.\n");
> > +		return ret;
> > +	}
> > +
> > +	strcpy(card->driver, DRIVER_NAME);
> > +
> > +	if (quirk && quirk->device_name)
> > +		strcpy(card->shortname, quirk->device_name);
> > +	else
> > +		strcpy(card->shortname, "M2Tech generic audio");
> > +
> > +	strlcat(card->longname, card->shortname, sizeof(card->longname));
> > +	len = strlcat(card->longname, " at ", sizeof(card->longname));
> > +	if (len < sizeof(card->longname))
> > +		usb_make_path(device, card->longname + len,
> > +			      sizeof(card->longname) - len);
> > +
> > +	chip = card->private_data;
> > +	chip->dev = device;
> > +	chip->index = idx;
> > +	chip->card = card;
> > +
> > +	*rchip = chip;
> > +	return 0;
> > +}
> > +
> > +static int hiface_chip_probe(struct usb_interface *intf,
> > +			     const struct usb_device_id *usb_id)
> > +{
> > +	const struct hiface_vendor_quirk *quirk = (struct hiface_vendor_quirk *)usb_id->driver_info;
> > +	int ret;
> > +	int i;
> > +	struct hiface_chip *chip;
> > +	struct usb_device *device = interface_to_usbdev(intf);
> > +
> > +	pr_debug("Probe " DRIVER_NAME " driver.\n");
> > +
> > +	ret = usb_set_interface(device, 0, 0);
> > +	if (ret != 0) {
> > +		snd_printk(KERN_ERR "can't set first interface for " CARD_NAME " device.\n");
> > +		return -EIO;
> > +	}
> > +
> > +	/* check whether the card is already registered */
> > +	chip = NULL;
> > +	mutex_lock(&register_mutex);
> > +	for (i = 0; i < SNDRV_CARDS; i++) {
> > +		if (chips[i] && chips[i]->dev == device) {
> > +			if (chips[i]->shutdown) {
> > +				snd_printk(KERN_ERR CARD_NAME " device is in the shutdown state, cannot create a card instance\n");
> > +				ret = -ENODEV;
> > +				goto err;
> > +			}
> > +			chip = chips[i];
> > +			break;
> > +		}
> > +	}
> > +	if (!chip) {
> > +		/* it's a fresh one.
> > +		 * now look for an empty slot and create a new card instance
> > +		 */
> > +		for (i = 0; i < SNDRV_CARDS; i++)
> > +			if (enable[i] && !chips[i]) {
> > +				ret = hiface_chip_create(device, i, quirk,
> > +							 &chip);
> > +				if (ret < 0)
> > +					goto err;
> > +
> > +				snd_card_set_dev(chip->card, &intf->dev);
> > +				break;
> > +			}
> > +		if (!chip) {
> > +			snd_printk(KERN_ERR "no available " CARD_NAME " audio device\n");
> > +			ret = -ENODEV;
> > +			goto err;
> > +		}
> > +	}
> > +
> > +	ret = hiface_pcm_init(chip, quirk ? quirk->extra_freq : 0);
> > +	if (ret < 0)
> > +		goto err_chip_destroy;
> > +
> > +	ret = snd_card_register(chip->card);
> > +	if (ret < 0) {
> > +		snd_printk(KERN_ERR "cannot register " CARD_NAME " card\n");
> > +		goto err_chip_destroy;
> > +	}
> > +
> > +	chips[chip->index] = chip;
> > +	chip->intf_count++;
> > +
> > +	mutex_unlock(&register_mutex);
> > +
> > +	usb_set_intfdata(intf, chip);
> > +	return 0;
> > +
> > +err_chip_destroy:
> > +	snd_card_free(chip->card);
> > +err:
> > +	mutex_unlock(&register_mutex);
> > +	return ret;
> > +}
> > +
> > +static void hiface_chip_disconnect(struct usb_interface *intf)
> > +{
> > +	struct hiface_chip *chip;
> > +	struct snd_card *card;
> > +
> > +	pr_debug("%s: called.\n", __func__);
> > +
> > +	chip = usb_get_intfdata(intf);
> > +	if (!chip)
> > +		return;
> > +
> > +	card = chip->card;
> > +	chip->intf_count--;
> > +	if (chip->intf_count <= 0) {
> > +		/* Make sure that the userspace cannot create new request */
> > +		snd_card_disconnect(card);
> > +
> > +		mutex_lock(&register_mutex);
> > +		chips[chip->index] = NULL;
> > +		mutex_unlock(&register_mutex);
> > +
> > +		chip->shutdown = true;
> > +		hiface_pcm_abort(chip);
> > +		snd_card_free_when_closed(card);
> > +	}
> > +}
> > +
> > +static const struct usb_device_id device_table[] = {
> > +	{
> > +		USB_DEVICE(0x04b4, 0x0384),
> > +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> > +			.device_name = "Young",
> > +			.extra_freq = 1,
> > +		}
> > +	},
> > +	{
> > +		USB_DEVICE(0x04b4, 0x930b),
> > +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> > +			.device_name = "hiFace",
> > +		}
> > +	},
> > +	{
> > +		USB_DEVICE(0x04b4, 0x931b),
> > +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> > +			.device_name = "North Star",
> > +		}
> > +	},
> > +	{
> > +		USB_DEVICE(0x04b4, 0x931c),
> > +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> > +			.device_name = "W4S Young",
> > +		}
> > +	},
> > +	{
> > +		USB_DEVICE(0x04b4, 0x931d),
> > +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> > +			.device_name = "Corrson",
> > +		}
> > +	},
> > +	{
> > +		USB_DEVICE(0x04b4, 0x931e),
> > +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> > +			.device_name = "AUDIA",
> > +		}
> > +	},
> > +	{
> > +		USB_DEVICE(0x04b4, 0x931f),
> > +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> > +			.device_name = "SL Audio",
> > +		}
> > +	},
> > +	{
> > +		USB_DEVICE(0x04b4, 0x9320),
> > +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> > +			.device_name = "Empirical",
> > +		}
> > +	},
> > +	{
> > +		USB_DEVICE(0x04b4, 0x9321),
> > +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> > +			.device_name = "Rockna",
> > +		}
> > +	},
> > +	{
> > +		USB_DEVICE(0x249c, 0x9001),
> > +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> > +			.device_name = "Pathos",
> > +		}
> > +	},
> > +	{
> > +		USB_DEVICE(0x249c, 0x9002),
> > +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> > +			.device_name = "Metronome",
> > +		}
> > +	},
> > +	{
> > +		USB_DEVICE(0x249c, 0x9006),
> > +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> > +			.device_name = "CAD",
> > +		}
> > +	},
> > +	{
> > +		USB_DEVICE(0x249c, 0x9008),
> > +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> > +			.device_name = "Audio Esclusive",
> > +		}
> > +	},
> > +	{
> > +		USB_DEVICE(0x249c, 0x931c),
> > +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> > +			.device_name = "Rotel",
> > +		}
> > +	},
> > +	{
> > +		USB_DEVICE(0x249c, 0x932c),
> > +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> > +			.device_name = "Eeaudio",
> > +		}
> > +	},
> > +	{
> > +		USB_DEVICE(0x245f, 0x931c),
> > +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> > +			.device_name = "CHORD",
> > +		}
> > +	},
> > +	{
> > +		USB_DEVICE(0x25c6, 0x9002),
> > +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> > +			.device_name = "Vitus",
> > +		}
> > +	},
> > +	{}
> > +};
> > +
> > +MODULE_DEVICE_TABLE(usb, device_table);
> > +
> > +static struct usb_driver hiface_usb_driver = {
> > +	.name = DRIVER_NAME,
> > +	.probe = hiface_chip_probe,
> > +	.disconnect = hiface_chip_disconnect,
> > +	.id_table = device_table,
> > +};
> > +
> > +module_usb_driver(hiface_usb_driver);
> > diff --git a/sound/usb/hiface/pcm.h b/sound/usb/hiface/pcm.h
> > new file mode 100644
> > index 0000000..77edd7c
> > --- /dev/null
> > +++ b/sound/usb/hiface/pcm.h
> > @@ -0,0 +1,24 @@
> > +/*
> > + * Linux driver for M2Tech hiFace compatible devices
> > + *
> > + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> > + *
> > + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> > + *           Antonio Ospite <ao2@amarulasolutions.com>
> > + *
> > + * The driver is based on the work done in TerraTec DMX 6Fire USB
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License as published by
> > + * the Free Software Foundation; either version 2 of the License, or
> > + * (at your option) any later version.
> > + */
> > +
> > +#ifndef HIFACE_PCM_H
> > +#define HIFACE_PCM_H
> > +
> > +struct hiface_chip;
> > +
> > +int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq);
> > +void hiface_pcm_abort(struct hiface_chip *chip);
> > +#endif /* HIFACE_PCM_H */
> > diff --git a/sound/usb/hiface/pcm.c b/sound/usb/hiface/pcm.c
> > new file mode 100644
> > index 0000000..d0c3c58
> > --- /dev/null
> > +++ b/sound/usb/hiface/pcm.c
> > @@ -0,0 +1,638 @@
> > +/*
> > + * Linux driver for M2Tech hiFace compatible devices
> > + *
> > + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> > + *
> > + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> > + *           Antonio Ospite <ao2@amarulasolutions.com>
> > + *
> > + * The driver is based on the work done in TerraTec DMX 6Fire USB
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License as published by
> > + * the Free Software Foundation; either version 2 of the License, or
> > + * (at your option) any later version.
> > + */
> > +
> > +#include <linux/slab.h>
> > +#include <sound/pcm.h>
> > +
> > +#include "pcm.h"
> > +#include "chip.h"
> > +
> > +#define OUT_EP          0x2
> > +#define PCM_N_URBS      8
> > +#define PCM_PACKET_SIZE 4096
> > +#define PCM_BUFFER_SIZE (2 * PCM_N_URBS * PCM_PACKET_SIZE)
> > +
> > +struct pcm_urb {
> > +	struct hiface_chip *chip;
> > +
> > +	struct urb instance;
> > +	struct usb_anchor submitted;
> > +	u8 *buffer;
> > +};
> > +
> > +struct pcm_substream {
> > +	spinlock_t lock;
> > +	struct snd_pcm_substream *instance;
> > +
> > +	bool active;
> > +	snd_pcm_uframes_t dma_off;    /* current position in alsa dma_area */
> > +	snd_pcm_uframes_t period_off; /* current position in current period */
> > +};
> > +
> > +enum { /* pcm streaming states */
> > +	STREAM_DISABLED, /* no pcm streaming */
> > +	STREAM_STARTING, /* pcm streaming requested, waiting to become ready */
> > +	STREAM_RUNNING,  /* pcm streaming running */
> > +	STREAM_STOPPING
> > +};
> > +
> > +struct pcm_runtime {
> > +	struct hiface_chip *chip;
> > +	struct snd_pcm *instance;
> > +
> > +	struct pcm_substream playback;
> > +	bool panic; /* if set driver won't do anymore pcm on device */
> > +
> > +	struct pcm_urb out_urbs[PCM_N_URBS];
> > +
> > +	struct mutex stream_mutex;
> > +	u8 stream_state; /* one of STREAM_XXX */
> > +	u8 extra_freq;
> > +	wait_queue_head_t stream_wait_queue;
> > +	bool stream_wait_cond;
> > +};
> > +
> > +static const unsigned int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000,
> > +				      352800, 384000 };
> > +static const struct snd_pcm_hw_constraint_list constraints_extra_rates = {
> > +	.count = ARRAY_SIZE(rates),
> > +	.list = rates,
> > +	.mask = 0,
> > +};
> > +
> > +static const struct snd_pcm_hardware pcm_hw = {
> > +	.info = SNDRV_PCM_INFO_MMAP |
> > +		SNDRV_PCM_INFO_INTERLEAVED |
> > +		SNDRV_PCM_INFO_BLOCK_TRANSFER |
> > +		SNDRV_PCM_INFO_PAUSE |
> > +		SNDRV_PCM_INFO_MMAP_VALID |
> > +		SNDRV_PCM_INFO_BATCH,
> > +
> > +	.formats = SNDRV_PCM_FMTBIT_S32_LE,
> > +
> > +	.rates = SNDRV_PCM_RATE_44100 |
> > +		SNDRV_PCM_RATE_48000 |
> > +		SNDRV_PCM_RATE_88200 |
> > +		SNDRV_PCM_RATE_96000 |
> > +		SNDRV_PCM_RATE_176400 |
> > +		SNDRV_PCM_RATE_192000,
> > +
> > +	.rate_min = 44100,
> > +	.rate_max = 192000, /* changes in hiface_pcm_open to support extra rates */
> > +	.channels_min = 2,
> > +	.channels_max = 2,
> > +	.buffer_bytes_max = PCM_BUFFER_SIZE,
> > +	.period_bytes_min = PCM_PACKET_SIZE,
> > +	.period_bytes_max = PCM_BUFFER_SIZE,
> > +	.periods_min = 2,
> > +	.periods_max = 1024
> > +};
> > +
> > +/* message values used to change the sample rate */
> > +#define HIFACE_SET_RATE_REQUEST 0xb0
> > +
> > +#define HIFACE_RATE_44100  0x43
> > +#define HIFACE_RATE_48000  0x4b
> > +#define HIFACE_RATE_88200  0x42
> > +#define HIFACE_RATE_96000  0x4a
> > +#define HIFACE_RATE_176400 0x40
> > +#define HIFACE_RATE_192000 0x48
> > +#define HIFACE_RATE_352000 0x58
> > +#define HIFACE_RATE_384000 0x68
> > +
> > +static int hiface_pcm_set_rate(struct pcm_runtime *rt, unsigned int rate)
> > +{
> > +	struct usb_device *device = rt->chip->dev;
> > +	u16 rate_value;
> > +	int ret;
> > +
> > +	/* We are already sure that the rate is supported here thanks to
> > +	 * ALSA constraints
> > +	 */
> > +	switch (rate) {
> > +	case 44100:
> > +		rate_value = HIFACE_RATE_44100;
> > +		break;
> > +	case 48000:
> > +		rate_value = HIFACE_RATE_48000;
> > +		break;
> > +	case 88200:
> > +		rate_value = HIFACE_RATE_88200;
> > +		break;
> > +	case 96000:
> > +		rate_value = HIFACE_RATE_96000;
> > +		break;
> > +	case 176400:
> > +		rate_value = HIFACE_RATE_176400;
> > +		break;
> > +	case 192000:
> > +		rate_value = HIFACE_RATE_192000;
> > +		break;
> > +	case 352000:
> > +		rate_value = HIFACE_RATE_352000;
> > +		break;
> > +	case 384000:
> > +		rate_value = HIFACE_RATE_384000;
> > +		break;
> > +	default:
> > +		snd_printk(KERN_ERR "Unsupported rate %d\n", rate);
> > +		return -EINVAL;
> > +	}
> > +
> > +	/*
> > +	 * USBIO: Vendor 0xb0(wValue=0x0043, wIndex=0x0000)
> > +	 * 43 b0 43 00 00 00 00 00
> > +	 * USBIO: Vendor 0xb0(wValue=0x004b, wIndex=0x0000)
> > +	 * 43 b0 4b 00 00 00 00 00
> > +	 * This control message doesn't have any ack from the
> > +	 * other side
> > +	 */
> > +	ret = usb_control_msg(device, usb_sndctrlpipe(device, 0),
> > +				HIFACE_SET_RATE_REQUEST,
> > +				USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
> > +				rate_value, 0, NULL, 0, 100);
> > +	if (ret < 0) {
> > +		snd_printk(KERN_ERR "Error setting samplerate %d.\n", rate);
> > +		return ret;
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static struct pcm_substream *hiface_pcm_get_substream(
> > +		struct snd_pcm_substream *alsa_sub)
> > +{
> > +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> > +
> > +	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
> > +		return &rt->playback;
> > +
> > +	pr_debug("Error getting pcm substream slot.\n");
> > +	return NULL;
> > +}
> > +
> > +/* call with stream_mutex locked */
> > +static void hiface_pcm_stream_stop(struct pcm_runtime *rt)
> > +{
> > +	int i, time;
> > +
> > +	if (rt->stream_state != STREAM_DISABLED) {
> > +		rt->stream_state = STREAM_STOPPING;
> > +
> > +		for (i = 0; i < PCM_N_URBS; i++) {
> > +			time = usb_wait_anchor_empty_timeout(
> > +					&rt->out_urbs[i].submitted, 100);
> > +			if (!time)
> > +				usb_kill_anchored_urbs(
> > +					&rt->out_urbs[i].submitted);
> > +			usb_kill_urb(&rt->out_urbs[i].instance);
> > +		}
> > +
> > +		rt->stream_state = STREAM_DISABLED;
> > +	}
> > +}
> > +
> > +/* call with stream_mutex locked */
> > +static int hiface_pcm_stream_start(struct pcm_runtime *rt)
> > +{
> > +	int ret = 0, i;
> > +
> > +	if (rt->stream_state == STREAM_DISABLED) {
> > +		/* submit our out urbs zero init */
> > +		rt->stream_state = STREAM_STARTING;
> > +		for (i = 0; i < PCM_N_URBS; i++) {
> > +			memset(rt->out_urbs[i].buffer, 0, PCM_PACKET_SIZE);
> > +			usb_anchor_urb(&rt->out_urbs[i].instance,
> > +				       &rt->out_urbs[i].submitted);
> > +			ret = usb_submit_urb(&rt->out_urbs[i].instance,
> > +					GFP_ATOMIC);
> > +			if (ret) {
> > +				hiface_pcm_stream_stop(rt);
> > +				return ret;
> > +			}
> > +		}
> > +
> > +		/* wait for first out urb to return (sent in in urb handler) */
> > +		wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond,
> > +				HZ);
> > +		if (rt->stream_wait_cond) {
> > +			pr_debug("%s: Stream is running wakeup event\n",
> > +				 __func__);
> > +			rt->stream_state = STREAM_RUNNING;
> > +		} else {
> > +			hiface_pcm_stream_stop(rt);
> > +			return -EIO;
> > +		}
> > +	}
> > +	return ret;
> > +}
> > +
> > +
> > +/* The hardware wants word-swapped 32-bit values */
> > +static void memcpy_swahw32(u8 *dest, u8 *src, unsigned int n)
> > +{
> > +	unsigned int i;
> > +
> > +	for (i = 0; i < n / 4; i++)
> > +		((u32 *)dest)[i] = swahw32(((u32 *)src)[i]);
> > +}
> > +
> > +/* call with substream locked */
> > +/* returns true if a period elapsed */
> > +static bool hiface_pcm_playback(struct pcm_substream *sub,
> > +		struct pcm_urb *urb)
> > +{
> > +	struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
> > +	u8 *source;
> > +	unsigned int pcm_buffer_size;
> > +
> > +	WARN_ON(alsa_rt->format != SNDRV_PCM_FORMAT_S32_LE);
> > +
> > +	pcm_buffer_size = snd_pcm_lib_buffer_bytes(sub->instance);
> > +
> > +	if (sub->dma_off + PCM_PACKET_SIZE <= pcm_buffer_size) {
> > +		pr_debug("%s: (1) buffer_size %#x dma_offset %#x\n", __func__,
> > +			 (unsigned int) pcm_buffer_size,
> > +			 (unsigned int) sub->dma_off);
> > +
> > +		source = alsa_rt->dma_area + sub->dma_off;
> > +		memcpy_swahw32(urb->buffer, source, PCM_PACKET_SIZE);
> > +	} else {
> > +		/* wrap around at end of ring buffer */
> > +		unsigned int len;
> > +
> > +		pr_debug("%s: (2) buffer_size %#x dma_offset %#x\n", __func__,
> > +			 (unsigned int) pcm_buffer_size,
> > +			 (unsigned int) sub->dma_off);
> > +
> > +		len = pcm_buffer_size - sub->dma_off;
> > +
> > +		source = alsa_rt->dma_area + sub->dma_off;
> > +		memcpy_swahw32(urb->buffer, source, len);
> > +
> > +		source = alsa_rt->dma_area;
> > +		memcpy_swahw32(urb->buffer + len, source,
> > +			       PCM_PACKET_SIZE - len);
> > +	}
> > +	sub->dma_off += PCM_PACKET_SIZE;
> > +	if (sub->dma_off >= pcm_buffer_size)
> > +		sub->dma_off -= pcm_buffer_size;
> > +
> > +	sub->period_off += PCM_PACKET_SIZE;
> > +	if (sub->period_off >= alsa_rt->period_size) {
> > +		sub->period_off %= alsa_rt->period_size;
> > +		return true;
> > +	}
> > +	return false;
> > +}
> > +
> > +static void hiface_pcm_out_urb_handler(struct urb *usb_urb)
> > +{
> > +	struct pcm_urb *out_urb = usb_urb->context;
> > +	struct pcm_runtime *rt = out_urb->chip->pcm;
> > +	struct pcm_substream *sub;
> > +	bool do_period_elapsed = false;
> > +	unsigned long flags;
> > +	int ret;
> > +
> > +	pr_debug("%s: called.\n", __func__);
> > +
> > +	if (rt->panic || rt->stream_state == STREAM_STOPPING)
> > +		return;
> > +
> > +	if (unlikely(usb_urb->status == -ENOENT ||	/* unlinked */
> > +		     usb_urb->status == -ENODEV ||	/* device removed */
> > +		     usb_urb->status == -ECONNRESET ||	/* unlinked */
> > +		     usb_urb->status == -ESHUTDOWN)) {	/* device disabled */
> > +		goto out_fail;
> > +	}
> > +
> > +	if (rt->stream_state == STREAM_STARTING) {
> > +		rt->stream_wait_cond = true;
> > +		wake_up(&rt->stream_wait_queue);
> > +	}
> > +
> > +	/* now send our playback data (if a free out urb was found) */
> > +	sub = &rt->playback;
> > +	spin_lock_irqsave(&sub->lock, flags);
> > +	if (sub->active)
> > +		do_period_elapsed = hiface_pcm_playback(sub, out_urb);
> > +	else
> > +		memset(out_urb->buffer, 0, PCM_PACKET_SIZE);
> > +
> > +	spin_unlock_irqrestore(&sub->lock, flags);
> > +
> > +	if (do_period_elapsed)
> > +		snd_pcm_period_elapsed(sub->instance);
> > +
> > +	ret = usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
> > +	if (ret < 0)
> > +		goto out_fail;
> > +
> > +	return;
> > +
> > +out_fail:
> > +	rt->panic = true;
> > +}
> > +
> > +static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub)
> > +{
> > +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> > +	struct pcm_substream *sub = NULL;
> > +	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
> > +	int ret;
> > +
> > +	pr_debug("%s: called.\n", __func__);
> > +
> > +	if (rt->panic)
> > +		return -EPIPE;
> > +
> > +	mutex_lock(&rt->stream_mutex);
> > +	alsa_rt->hw = pcm_hw;
> > +
> > +	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
> > +		sub = &rt->playback;
> > +
> > +	if (!sub) {
> > +		mutex_unlock(&rt->stream_mutex);
> > +		pr_err("Invalid stream type\n");
> > +		return -EINVAL;
> > +	}
> > +
> > +	if (rt->extra_freq) {
> > +		alsa_rt->hw.rates |= SNDRV_PCM_RATE_KNOT;
> > +		alsa_rt->hw.rate_max = 384000;
> > +
> > +		/* explicit constraints needed as we added SNDRV_PCM_RATE_KNOT */
> > +		ret = snd_pcm_hw_constraint_list(alsa_sub->runtime, 0,
> > +						 SNDRV_PCM_HW_PARAM_RATE,
> > +						 &constraints_extra_rates);
> > +		if (ret < 0) {
> > +			mutex_unlock(&rt->stream_mutex);
> > +			return ret;
> > +		}
> > +	}
> > +
> > +	sub->instance = alsa_sub;
> > +	sub->active = false;
> > +	mutex_unlock(&rt->stream_mutex);
> > +	return 0;
> > +}
> > +
> > +static int hiface_pcm_close(struct snd_pcm_substream *alsa_sub)
> > +{
> > +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> > +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> > +	unsigned long flags;
> > +
> > +	if (rt->panic)
> > +		return 0;
> > +
> > +	pr_debug("%s: called.\n", __func__);
> > +
> > +	mutex_lock(&rt->stream_mutex);
> > +	if (sub) {
> > +		/* deactivate substream */
> > +		spin_lock_irqsave(&sub->lock, flags);
> > +		sub->instance = NULL;
> > +		sub->active = false;
> > +		spin_unlock_irqrestore(&sub->lock, flags);
> > +
> > +		/* all substreams closed? if so, stop streaming */
> > +		if (!rt->playback.instance)
> > +			hiface_pcm_stream_stop(rt);
> > +	}
> > +	mutex_unlock(&rt->stream_mutex);
> > +	return 0;
> > +}
> > +
> > +static int hiface_pcm_hw_params(struct snd_pcm_substream *alsa_sub,
> > +		struct snd_pcm_hw_params *hw_params)
> > +{
> > +	pr_debug("%s: called.\n", __func__);
> > +	return snd_pcm_lib_malloc_pages(alsa_sub,
> > +			params_buffer_bytes(hw_params));
> > +}
> > +
> > +static int hiface_pcm_hw_free(struct snd_pcm_substream *alsa_sub)
> > +{
> > +	pr_debug("%s: called.\n", __func__);
> > +	return snd_pcm_lib_free_pages(alsa_sub);
> > +}
> > +
> > +static int hiface_pcm_prepare(struct snd_pcm_substream *alsa_sub)
> > +{
> > +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> > +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> > +	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
> > +	int ret;
> > +
> > +	pr_debug("%s: called.\n", __func__);
> > +
> > +	if (rt->panic)
> > +		return -EPIPE;
> > +	if (!sub)
> > +		return -ENODEV;
> > +
> > +	mutex_lock(&rt->stream_mutex);
> > +
> > +	sub->dma_off = 0;
> > +	sub->period_off = 0;
> > +
> > +	if (rt->stream_state == STREAM_DISABLED) {
> > +
> > +		ret = hiface_pcm_set_rate(rt, alsa_rt->rate);
> > +		if (ret) {
> > +			mutex_unlock(&rt->stream_mutex);
> > +			return ret;
> > +		}
> > +		ret = hiface_pcm_stream_start(rt);
> > +		if (ret) {
> > +			mutex_unlock(&rt->stream_mutex);
> > +			return ret;
> > +		}
> > +	}
> > +	mutex_unlock(&rt->stream_mutex);
> > +	return 0;
> > +}
> > +
> > +static int hiface_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
> > +{
> > +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> > +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> > +	unsigned long flags;
> > +
> > +	pr_debug("%s: called.\n", __func__);
> > +
> > +	if (rt->panic)
> > +		return -EPIPE;
> > +	if (!sub)
> > +		return -ENODEV;
> > +
> > +	switch (cmd) {
> > +	case SNDRV_PCM_TRIGGER_START:
> > +	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
> > +		spin_lock_irqsave(&sub->lock, flags);
> > +		sub->active = true;
> > +		spin_unlock_irqrestore(&sub->lock, flags);
> > +		return 0;
> > +
> > +	case SNDRV_PCM_TRIGGER_STOP:
> > +	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
> > +		spin_lock_irqsave(&sub->lock, flags);
> > +		sub->active = false;
> > +		spin_unlock_irqrestore(&sub->lock, flags);
> > +		return 0;
> > +
> > +	default:
> > +		return -EINVAL;
> > +	}
> > +}
> > +
> > +static snd_pcm_uframes_t hiface_pcm_pointer(struct snd_pcm_substream *alsa_sub)
> > +{
> > +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> > +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> > +	unsigned long flags;
> > +	snd_pcm_uframes_t dma_offset;
> > +
> > +	if (rt->panic || !sub)
> > +		return SNDRV_PCM_STATE_XRUN;
> > +
> > +	spin_lock_irqsave(&sub->lock, flags);
> > +	dma_offset = sub->dma_off;
> > +	spin_unlock_irqrestore(&sub->lock, flags);
> > +	return bytes_to_frames(alsa_sub->runtime, dma_offset);
> > +}
> > +
> > +static struct snd_pcm_ops pcm_ops = {
> > +	.open = hiface_pcm_open,
> > +	.close = hiface_pcm_close,
> > +	.ioctl = snd_pcm_lib_ioctl,
> > +	.hw_params = hiface_pcm_hw_params,
> > +	.hw_free = hiface_pcm_hw_free,
> > +	.prepare = hiface_pcm_prepare,
> > +	.trigger = hiface_pcm_trigger,
> > +	.pointer = hiface_pcm_pointer,
> > +};
> > +
> > +static int hiface_pcm_init_urb(struct pcm_urb *urb,
> > +			       struct hiface_chip *chip,
> > +			       unsigned int ep,
> > +			       void (*handler)(struct urb *))
> > +{
> > +	urb->chip = chip;
> > +	usb_init_urb(&urb->instance);
> > +
> > +	urb->buffer = kzalloc(PCM_PACKET_SIZE, GFP_KERNEL);
> > +	if (!urb->buffer)
> > +		return -ENOMEM;
> > +
> > +	usb_fill_bulk_urb(&urb->instance, chip->dev,
> > +			  usb_sndbulkpipe(chip->dev, ep), (void *)urb->buffer,
> > +			  PCM_PACKET_SIZE, handler, urb);
> > +	init_usb_anchor(&urb->submitted);
> > +
> > +	return 0;
> > +}
> > +
> > +void hiface_pcm_abort(struct hiface_chip *chip)
> > +{
> > +	struct pcm_runtime *rt = chip->pcm;
> > +
> > +	if (rt) {
> > +		rt->panic = true;
> > +
> > +		if (rt->playback.instance) {
> > +			snd_pcm_stream_lock_irq(rt->playback.instance);
> > +			snd_pcm_stop(rt->playback.instance,
> > +					SNDRV_PCM_STATE_XRUN);
> > +			snd_pcm_stream_unlock_irq(rt->playback.instance);
> > +		}
> > +		mutex_lock(&rt->stream_mutex);
> > +		hiface_pcm_stream_stop(rt);
> > +		mutex_unlock(&rt->stream_mutex);
> > +	}
> > +}
> > +
> > +static void hiface_pcm_destroy(struct hiface_chip *chip)
> > +{
> > +	struct pcm_runtime *rt = chip->pcm;
> > +	int i;
> > +
> > +	for (i = 0; i < PCM_N_URBS; i++)
> > +		kfree(rt->out_urbs[i].buffer);
> > +
> > +	kfree(chip->pcm);
> > +	chip->pcm = NULL;
> > +}
> > +
> > +static void hiface_pcm_free(struct snd_pcm *pcm)
> > +{
> > +	struct pcm_runtime *rt = pcm->private_data;
> > +
> > +	pr_debug("%s: called.\n", __func__);
> > +
> > +	if (rt)
> > +		hiface_pcm_destroy(rt->chip);
> > +}
> > +
> > +int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq)
> > +{
> > +	int i;
> > +	int ret;
> > +	struct snd_pcm *pcm;
> > +	struct pcm_runtime *rt;
> > +
> > +	rt = kzalloc(sizeof(*rt), GFP_KERNEL);
> > +	if (!rt)
> > +		return -ENOMEM;
> > +
> > +	rt->chip = chip;
> > +	rt->stream_state = STREAM_DISABLED;
> > +	if (extra_freq)
> > +		rt->extra_freq = 1;
> > +
> > +	init_waitqueue_head(&rt->stream_wait_queue);
> > +	mutex_init(&rt->stream_mutex);
> > +	spin_lock_init(&rt->playback.lock);
> > +
> > +	for (i = 0; i < PCM_N_URBS; i++)
> > +		hiface_pcm_init_urb(&rt->out_urbs[i], chip, OUT_EP,
> > +				hiface_pcm_out_urb_handler);
> > +
> > +	ret = snd_pcm_new(chip->card, "USB-SPDIF Audio", 0, 1, 0, &pcm);
> > +	if (ret < 0) {
> > +		kfree(rt);
> > +		pr_err("Cannot create pcm instance\n");
> > +		return ret;
> > +	}
> > +
> > +	pcm->private_data = rt;
> > +	pcm->private_free = hiface_pcm_free;
> > +
> > +	strcpy(pcm->name, "USB-SPDIF Audio");
> > +	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops);
> > +
> > +	snd_pcm_lib_preallocate_pages_for_all(pcm,
> > +					SNDRV_DMA_TYPE_CONTINUOUS,
> > +					snd_dma_continuous_data(GFP_KERNEL),
> > +					PCM_BUFFER_SIZE, PCM_BUFFER_SIZE);
> > +	rt->instance = pcm;
> > +
> > +	chip->pcm = rt;
> > +	return 0;
> > +}
> > -- 
> > 1.7.10.4
> > 
> > _______________________________________________
> > Alsa-devel mailing list
> > Alsa-devel@alsa-project.org
> > http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
> 
> 
> -- 
> Antonio Ospite
> http://ao2.it
> 
> A: Because it messes up the order in which people normally read text.
>    See http://en.wikipedia.org/wiki/Posting_style
> Q: Why is top-posting such a bad thing?
> 

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

* Re: [PATCH v2] Add M2Tech hiFace USB-SPDIF driver
  2013-02-13 17:11 ` [PATCH v2] " Antonio Ospite
  2013-02-22 10:48   ` Antonio Ospite
@ 2013-02-22 12:53   ` Takashi Iwai
  2013-04-28 21:09     ` Antonio Ospite
  2013-02-22 16:12   ` Daniel Mack
  2013-04-20 20:15   ` Daniel Mack
  3 siblings, 1 reply; 25+ messages in thread
From: Takashi Iwai @ 2013-02-22 12:53 UTC (permalink / raw)
  To: Antonio Ospite
  Cc: alsa-devel, patch, Clemens Ladisch, fchecconi, Pietro Cipriano,
	alberto, Michael Trimarchi

At Wed, 13 Feb 2013 18:11:45 +0100,
Antonio Ospite wrote:
> new file mode 100644
> index 0000000..d0c3c58
> --- /dev/null
> +++ b/sound/usb/hiface/pcm.c
> @@ -0,0 +1,638 @@
....
> +struct pcm_runtime {
> +	struct hiface_chip *chip;
> +	struct snd_pcm *instance;
> +
> +	struct pcm_substream playback;
> +	bool panic; /* if set driver won't do anymore pcm on device */

Once when rt->panic is set, how can you recover?
I see no code resetting rt->panic.

> +/* The hardware wants word-swapped 32-bit values */
> +static void memcpy_swahw32(u8 *dest, u8 *src, unsigned int n)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < n / 4; i++)
> +		((u32 *)dest)[i] = swahw32(((u32 *)src)[i]);
> +}
> +
> +/* call with substream locked */
> +/* returns true if a period elapsed */
> +static bool hiface_pcm_playback(struct pcm_substream *sub,
> +		struct pcm_urb *urb)
> +{
> +	struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
> +	u8 *source;
> +	unsigned int pcm_buffer_size;
> +
> +	WARN_ON(alsa_rt->format != SNDRV_PCM_FORMAT_S32_LE);

Can't we use simply SNDRV_PCM_FORMAT_S32_BE without the byte swapping
above?


> +static void hiface_pcm_out_urb_handler(struct urb *usb_urb)
> +{
> +	struct pcm_urb *out_urb = usb_urb->context;
> +	struct pcm_runtime *rt = out_urb->chip->pcm;
> +	struct pcm_substream *sub;
> +	bool do_period_elapsed = false;
> +	unsigned long flags;
> +	int ret;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	if (rt->panic || rt->stream_state == STREAM_STOPPING)
> +		return;
> +
> +	if (unlikely(usb_urb->status == -ENOENT ||	/* unlinked */
> +		     usb_urb->status == -ENODEV ||	/* device removed */
> +		     usb_urb->status == -ECONNRESET ||	/* unlinked */
> +		     usb_urb->status == -ESHUTDOWN)) {	/* device disabled */
> +		goto out_fail;
> +	}
> +
> +	if (rt->stream_state == STREAM_STARTING) {
> +		rt->stream_wait_cond = true;
> +		wake_up(&rt->stream_wait_queue);
> +	}
> +
> +	/* now send our playback data (if a free out urb was found) */
> +	sub = &rt->playback;
> +	spin_lock_irqsave(&sub->lock, flags);
> +	if (sub->active)
> +		do_period_elapsed = hiface_pcm_playback(sub, out_urb);
> +	else
> +		memset(out_urb->buffer, 0, PCM_PACKET_SIZE);
> +
> +	spin_unlock_irqrestore(&sub->lock, flags);
> +
> +	if (do_period_elapsed)
> +		snd_pcm_period_elapsed(sub->instance);

This looks like a small open race.  sub->instance might be set to NULL
after the unlock above?

> +
> +	ret = usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
> +	if (ret < 0)
> +		goto out_fail;

Maybe better to check the state again before resubmission, e.g. when
XRUN is detected in the pointer callback.

> +
> +	return;
> +
> +out_fail:
> +	rt->panic = true;
> +}
> +
> +static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub)
> +{
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	struct pcm_substream *sub = NULL;
> +	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
> +	int ret;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	if (rt->panic)
> +		return -EPIPE;
> +
> +	mutex_lock(&rt->stream_mutex);
> +	alsa_rt->hw = pcm_hw;
> +
> +	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
> +		sub = &rt->playback;
> +
> +	if (!sub) {
> +		mutex_unlock(&rt->stream_mutex);
> +		pr_err("Invalid stream type\n");
> +		return -EINVAL;
> +	}
> +
> +	if (rt->extra_freq) {
> +		alsa_rt->hw.rates |= SNDRV_PCM_RATE_KNOT;
> +		alsa_rt->hw.rate_max = 384000;
> +
> +		/* explicit constraints needed as we added SNDRV_PCM_RATE_KNOT */
> +		ret = snd_pcm_hw_constraint_list(alsa_sub->runtime, 0,
> +						 SNDRV_PCM_HW_PARAM_RATE,
> +						 &constraints_extra_rates);
> +		if (ret < 0) {
> +			mutex_unlock(&rt->stream_mutex);
> +			return ret;
> +		}
> +	}
> +
> +	sub->instance = alsa_sub;
> +	sub->active = false;
> +	mutex_unlock(&rt->stream_mutex);
> +	return 0;
> +}
> +
> +static int hiface_pcm_close(struct snd_pcm_substream *alsa_sub)
> +{
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> +	unsigned long flags;
> +
> +	if (rt->panic)
> +		return 0;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	mutex_lock(&rt->stream_mutex);
> +	if (sub) {
> +		/* deactivate substream */
> +		spin_lock_irqsave(&sub->lock, flags);
> +		sub->instance = NULL;
> +		sub->active = false;
> +		spin_unlock_irqrestore(&sub->lock, flags);
> +
> +		/* all substreams closed? if so, stop streaming */
> +		if (!rt->playback.instance)
> +			hiface_pcm_stream_stop(rt);

Can this condition be ever false...?

> +static int hiface_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
> +{
> +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	unsigned long flags;

For trigger callback, you need no irqsave but simply use
spin_lock_irq().

> +void hiface_pcm_abort(struct hiface_chip *chip)
> +{
> +	struct pcm_runtime *rt = chip->pcm;
> +
> +	if (rt) {
> +		rt->panic = true;
> +
> +		if (rt->playback.instance) {
> +			snd_pcm_stream_lock_irq(rt->playback.instance);
> +			snd_pcm_stop(rt->playback.instance,
> +					SNDRV_PCM_STATE_XRUN);
> +			snd_pcm_stream_unlock_irq(rt->playback.instance);
> +		}

Hmm... this is called after snd_card_disconnect(), thus it will reset
the PCM stream state from DISCONNECTED to XRUN.  It doesn't sound
right.

> +int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq)
> +{
....
> +	snd_pcm_lib_preallocate_pages_for_all(pcm,
> +					SNDRV_DMA_TYPE_CONTINUOUS,
> +					snd_dma_continuous_data(GFP_KERNEL),
> +					PCM_BUFFER_SIZE, PCM_BUFFER_SIZE);

Any reason not to use vmalloc buffer?


thanks,

Takashi

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

* Re: [PATCH v2] Add M2Tech hiFace USB-SPDIF driver
  2013-02-22 12:52     ` Takashi Iwai
@ 2013-02-22 13:31       ` Antonio Ospite
  2013-02-22 14:09         ` Takashi Iwai
  0 siblings, 1 reply; 25+ messages in thread
From: Antonio Ospite @ 2013-02-22 13:31 UTC (permalink / raw)
  To: alsa-devel
  Cc: Takashi Iwai, Clemens Ladisch, fchecconi, Pietro Cipriano,
	alberto, Michael Trimarchi

On Fri, 22 Feb 2013 13:52:38 +0100
Takashi Iwai <tiwai@suse.de> wrote:

> At Fri, 22 Feb 2013 11:48:32 +0100,
> Antonio Ospite wrote:
> > 
> > On Wed, 13 Feb 2013 18:11:45 +0100
> > Antonio Ospite <ao2@amarulasolutions.com> wrote:
> > 
> > > Add driver for M2Tech hiFace USB-SPDIF interface and compatible devices.
> > > 
> > > M2Tech hiFace and compatible devices offer a Hi-End S/PDIF Output
> > > Interface, see http://www.m2tech.biz/hiface.html
> > > 
> > > The supported products are:
> > > 
> > >   * M2Tech Young
> > >   * M2Tech hiFace
> > >   * M2Tech North Star
> > >   * M2Tech W4S Young
> > >   * M2Tech Corrson
> > >   * M2Tech AUDIA
> > >   * M2Tech SL Audio
> > >   * M2Tech Empirical
> > >   * M2Tech Rockna
> > >   * M2Tech Pathos
> > >   * M2Tech Metronome
> > >   * M2Tech CAD
> > >   * M2Tech Audio Esclusive
> > >   * M2Tech Rotel
> > >   * M2Tech Eeaudio
> > >   * The Chord Company CHORD
> > >   * AVA Group A/S Vitus
> > > 
> > > Signed-off-by: Antonio Ospite <ao2@amarulasolutions.com>
> > > ---
> > > 
> > > Changes since v1:
> > > 
> > >   * Change the first sentence of the Kconfig entry into "Select this..."
> > >   * Remove a useless sentence from the Kconfig entry
> > >   * Don't set alsa_rt->hw.rates in hiface_pcm_open()
> > >   * Style cleanup, no braces needed in single statement conditional
> > >   * Remove the rate field from pcm_runtime
> > >   * Use the hiFace name with the lowercase 'h' everywhere
> > >   * List actually supported devices in MODULE_SUPPORTED_DEVICE()
> > >   * Cosmetics, align values in the rates array
> > >   * Use an explicit switch instead of the rate_value array in
> > >     hiface_pcm_set_rate()
> > >   * Use usb_make_path() when building card->longname
> > >   * Use again the implicit mechanism to allocate the card private data
> > >   * Downgrade a pr_info to pr_debug in hiface_chip_probe()
> > >   * Make device_table const
> > >   * Rename PCM_MAX_PACKET_SIZE to PCM_PACKET_SIZE
> > >   * Rename MAX_BUFSIZE to PCM_BUFFER_SIZE
> > >   * Cosmetics, align symbolic constant values
> > >   * Add SNDRV_PCM_RATE_KNOT only when needed
> > >   * Declare memcpy_swahw32() as static
> > >   * Protect snd_pcm_stop() with snd_pcm_stream_lock_irq()
> > >   * Make hiface_pcm_playback() not returning anything
> > >   * Move the period elapsed check into hiface_pcm_playback()
> > >   * Handle the case of failing URBs in hiface_pcm_out_urb_handler()
> > >   * Fix a couple of checkpatch.pl issues
> > > 
> > > The incremental changes can be seen individually at
> > > https://github.com/panicking/snd-usb-asyncaudio/commits/master
> > > Commits from Feb. 10th and later.
> > >
> > 
> > Ping.
> > 
> > Hi, I don't see this one in:
> > http://git.kernel.org/?p=linux/kernel/git/tiwai/sound.git
> > 
> > If the code is OK, are we still in time for 3.9?
> 
> Sorry, I haven't reviewed the code yet (since I thought Clemens would
> re-review at first :)  I'm inclined to merge only fix patches at this
> point for 3.9, so the merge will be postponed to 3.10.
> 
> I'll take a look at the v2 patch.
> 

Thanks Takashi,

I'll reply to your review in the next days, since we are now targeting
3.10 I'll take some more time to think about your comments.

Regards,
   Antonio

-- 
Antonio Ospite
http://ao2.it

A: Because it messes up the order in which people normally read text.
   See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?

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

* Re: [PATCH v2] Add M2Tech hiFace USB-SPDIF driver
  2013-02-22 13:31       ` Antonio Ospite
@ 2013-02-22 14:09         ` Takashi Iwai
  0 siblings, 0 replies; 25+ messages in thread
From: Takashi Iwai @ 2013-02-22 14:09 UTC (permalink / raw)
  To: Antonio Ospite
  Cc: alsa-devel, Clemens Ladisch, fchecconi, Pietro Cipriano, alberto,
	Michael Trimarchi

At Fri, 22 Feb 2013 14:31:26 +0100,
Antonio Ospite wrote:
> 
> On Fri, 22 Feb 2013 13:52:38 +0100
> Takashi Iwai <tiwai@suse.de> wrote:
> 
> > At Fri, 22 Feb 2013 11:48:32 +0100,
> > Antonio Ospite wrote:
> > > 
> > > On Wed, 13 Feb 2013 18:11:45 +0100
> > > Antonio Ospite <ao2@amarulasolutions.com> wrote:
> > > 
> > > > Add driver for M2Tech hiFace USB-SPDIF interface and compatible devices.
> > > > 
> > > > M2Tech hiFace and compatible devices offer a Hi-End S/PDIF Output
> > > > Interface, see http://www.m2tech.biz/hiface.html
> > > > 
> > > > The supported products are:
> > > > 
> > > >   * M2Tech Young
> > > >   * M2Tech hiFace
> > > >   * M2Tech North Star
> > > >   * M2Tech W4S Young
> > > >   * M2Tech Corrson
> > > >   * M2Tech AUDIA
> > > >   * M2Tech SL Audio
> > > >   * M2Tech Empirical
> > > >   * M2Tech Rockna
> > > >   * M2Tech Pathos
> > > >   * M2Tech Metronome
> > > >   * M2Tech CAD
> > > >   * M2Tech Audio Esclusive
> > > >   * M2Tech Rotel
> > > >   * M2Tech Eeaudio
> > > >   * The Chord Company CHORD
> > > >   * AVA Group A/S Vitus
> > > > 
> > > > Signed-off-by: Antonio Ospite <ao2@amarulasolutions.com>
> > > > ---
> > > > 
> > > > Changes since v1:
> > > > 
> > > >   * Change the first sentence of the Kconfig entry into "Select this..."
> > > >   * Remove a useless sentence from the Kconfig entry
> > > >   * Don't set alsa_rt->hw.rates in hiface_pcm_open()
> > > >   * Style cleanup, no braces needed in single statement conditional
> > > >   * Remove the rate field from pcm_runtime
> > > >   * Use the hiFace name with the lowercase 'h' everywhere
> > > >   * List actually supported devices in MODULE_SUPPORTED_DEVICE()
> > > >   * Cosmetics, align values in the rates array
> > > >   * Use an explicit switch instead of the rate_value array in
> > > >     hiface_pcm_set_rate()
> > > >   * Use usb_make_path() when building card->longname
> > > >   * Use again the implicit mechanism to allocate the card private data
> > > >   * Downgrade a pr_info to pr_debug in hiface_chip_probe()
> > > >   * Make device_table const
> > > >   * Rename PCM_MAX_PACKET_SIZE to PCM_PACKET_SIZE
> > > >   * Rename MAX_BUFSIZE to PCM_BUFFER_SIZE
> > > >   * Cosmetics, align symbolic constant values
> > > >   * Add SNDRV_PCM_RATE_KNOT only when needed
> > > >   * Declare memcpy_swahw32() as static
> > > >   * Protect snd_pcm_stop() with snd_pcm_stream_lock_irq()
> > > >   * Make hiface_pcm_playback() not returning anything
> > > >   * Move the period elapsed check into hiface_pcm_playback()
> > > >   * Handle the case of failing URBs in hiface_pcm_out_urb_handler()
> > > >   * Fix a couple of checkpatch.pl issues
> > > > 
> > > > The incremental changes can be seen individually at
> > > > https://github.com/panicking/snd-usb-asyncaudio/commits/master
> > > > Commits from Feb. 10th and later.
> > > >
> > > 
> > > Ping.
> > > 
> > > Hi, I don't see this one in:
> > > http://git.kernel.org/?p=linux/kernel/git/tiwai/sound.git
> > > 
> > > If the code is OK, are we still in time for 3.9?
> > 
> > Sorry, I haven't reviewed the code yet (since I thought Clemens would
> > re-review at first :)  I'm inclined to merge only fix patches at this
> > point for 3.9, so the merge will be postponed to 3.10.
> > 
> > I'll take a look at the v2 patch.
> > 
> 
> Thanks Takashi,
> 
> I'll reply to your review in the next days, since we are now targeting
> 3.10 I'll take some more time to think about your comments.

OK, thanks!


Takashi

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

* Re: [PATCH v2] Add M2Tech hiFace USB-SPDIF driver
  2013-02-13 17:11 ` [PATCH v2] " Antonio Ospite
  2013-02-22 10:48   ` Antonio Ospite
  2013-02-22 12:53   ` Takashi Iwai
@ 2013-02-22 16:12   ` Daniel Mack
  2013-02-22 16:18     ` Takashi Iwai
  2013-04-28 20:59     ` Antonio Ospite
  2013-04-20 20:15   ` Daniel Mack
  3 siblings, 2 replies; 25+ messages in thread
From: Daniel Mack @ 2013-02-22 16:12 UTC (permalink / raw)
  To: Antonio Ospite
  Cc: alsa-devel, patch, Takashi Iwai, Clemens Ladisch, fchecconi,
	Pietro Cipriano, alberto, Michael Trimarchi

Hi Antonio,

a couple of more things that I stumbled over.


Thanks,
Daniel


On 13.02.2013 18:11, Antonio Ospite wrote:
> Add driver for M2Tech hiFace USB-SPDIF interface and compatible devices.
> 
> M2Tech hiFace and compatible devices offer a Hi-End S/PDIF Output
> Interface, see http://www.m2tech.biz/hiface.html
> 
> The supported products are:
> 
>   * M2Tech Young
>   * M2Tech hiFace
>   * M2Tech North Star
>   * M2Tech W4S Young
>   * M2Tech Corrson
>   * M2Tech AUDIA
>   * M2Tech SL Audio
>   * M2Tech Empirical
>   * M2Tech Rockna
>   * M2Tech Pathos
>   * M2Tech Metronome
>   * M2Tech CAD
>   * M2Tech Audio Esclusive
>   * M2Tech Rotel
>   * M2Tech Eeaudio
>   * The Chord Company CHORD
>   * AVA Group A/S Vitus
> 
> Signed-off-by: Antonio Ospite <ao2@amarulasolutions.com>
> ---
> 
> Changes since v1:
> 
>   * Change the first sentence of the Kconfig entry into "Select this..."
>   * Remove a useless sentence from the Kconfig entry
>   * Don't set alsa_rt->hw.rates in hiface_pcm_open()
>   * Style cleanup, no braces needed in single statement conditional
>   * Remove the rate field from pcm_runtime
>   * Use the hiFace name with the lowercase 'h' everywhere
>   * List actually supported devices in MODULE_SUPPORTED_DEVICE()
>   * Cosmetics, align values in the rates array
>   * Use an explicit switch instead of the rate_value array in
>     hiface_pcm_set_rate()
>   * Use usb_make_path() when building card->longname
>   * Use again the implicit mechanism to allocate the card private data
>   * Downgrade a pr_info to pr_debug in hiface_chip_probe()
>   * Make device_table const
>   * Rename PCM_MAX_PACKET_SIZE to PCM_PACKET_SIZE
>   * Rename MAX_BUFSIZE to PCM_BUFFER_SIZE
>   * Cosmetics, align symbolic constant values
>   * Add SNDRV_PCM_RATE_KNOT only when needed
>   * Declare memcpy_swahw32() as static
>   * Protect snd_pcm_stop() with snd_pcm_stream_lock_irq()
>   * Make hiface_pcm_playback() not returning anything
>   * Move the period elapsed check into hiface_pcm_playback()
>   * Handle the case of failing URBs in hiface_pcm_out_urb_handler()
>   * Fix a couple of checkpatch.pl issues
> 
> The incremental changes can be seen individually at
> https://github.com/panicking/snd-usb-asyncaudio/commits/master
> Commits from Feb. 10th and later.
> 
> Thanks,
>    Antonio
> 
>  sound/usb/Kconfig         |   31 +++
>  sound/usb/Makefile        |    2 +-
>  sound/usb/hiface/Makefile |    2 +
>  sound/usb/hiface/chip.h   |   34 +++
>  sound/usb/hiface/chip.c   |  329 +++++++++++++++++++++++
>  sound/usb/hiface/pcm.h    |   24 ++
>  sound/usb/hiface/pcm.c    |  638 +++++++++++++++++++++++++++++++++++++++++++++
>  7 files changed, 1059 insertions(+), 1 deletion(-)
>  create mode 100644 sound/usb/hiface/Makefile
>  create mode 100644 sound/usb/hiface/chip.h
>  create mode 100644 sound/usb/hiface/chip.c
>  create mode 100644 sound/usb/hiface/pcm.h
>  create mode 100644 sound/usb/hiface/pcm.c
> 
> diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig
> index 225dfd7..de9408b 100644
> --- a/sound/usb/Kconfig
> +++ b/sound/usb/Kconfig
> @@ -115,5 +115,36 @@ config SND_USB_6FIRE
>            and further help can be found at
>            http://sixfireusb.sourceforge.net
>  
> +config SND_USB_HIFACE
> +        tristate "M2Tech hiFace USB-SPDIF driver"
> +        select SND_PCM
> +        help
> +	  Select this option to include support for M2Tech hiFace USB-SPDIF
> +	  interface.
> +
> +	  This driver supports the original M2Tech hiFace and some other
> +	  compatible devices. The supported products are:
> +
> +	    * M2Tech Young
> +	    * M2Tech hiFace
> +	    * M2Tech North Star
> +	    * M2Tech W4S Young
> +	    * M2Tech Corrson
> +	    * M2Tech AUDIA
> +	    * M2Tech SL Audio
> +	    * M2Tech Empirical
> +	    * M2Tech Rockna
> +	    * M2Tech Pathos
> +	    * M2Tech Metronome
> +	    * M2Tech CAD
> +	    * M2Tech Audio Esclusive
> +	    * M2Tech Rotel
> +	    * M2Tech Eeaudio
> +	    * The Chord Company CHORD
> +	    * AVA Group A/S Vitus
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called snd-usb-hiface.
> +
>  endif	# SND_USB
>  
> diff --git a/sound/usb/Makefile b/sound/usb/Makefile
> index ac256dc..abe668f 100644
> --- a/sound/usb/Makefile
> +++ b/sound/usb/Makefile
> @@ -23,4 +23,4 @@ obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o
>  obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o
>  obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o
>  
> -obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/
> +obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/
> diff --git a/sound/usb/hiface/Makefile b/sound/usb/hiface/Makefile
> new file mode 100644
> index 0000000..463b136
> --- /dev/null
> +++ b/sound/usb/hiface/Makefile
> @@ -0,0 +1,2 @@
> +snd-usb-hiface-objs := chip.o pcm.o
> +obj-$(CONFIG_SND_USB_HIFACE) += snd-usb-hiface.o
> diff --git a/sound/usb/hiface/chip.h b/sound/usb/hiface/chip.h
> new file mode 100644
> index 0000000..cd615a0
> --- /dev/null
> +++ b/sound/usb/hiface/chip.h
> @@ -0,0 +1,34 @@
> +/*
> + * Linux driver for M2Tech hiFace compatible devices
> + *
> + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> + *
> + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> + *           Antonio Ospite <ao2@amarulasolutions.com>
> + *
> + * The driver is based on the work done in TerraTec DMX 6Fire USB
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#ifndef HIFACE_CHIP_H
> +#define HIFACE_CHIP_H
> +
> +#include <linux/usb.h>
> +#include <sound/core.h>
> +
> +struct pcm_runtime;
> +
> +struct hiface_chip {
> +	struct usb_device *dev;
> +	struct snd_card *card;
> +	int intf_count; /* number of registered interfaces */
> +	int index; /* index in module parameter arrays */
> +	bool shutdown;
> +
> +	struct pcm_runtime *pcm;
> +};
> +#endif /* HIFACE_CHIP_H */
> diff --git a/sound/usb/hiface/chip.c b/sound/usb/hiface/chip.c
> new file mode 100644
> index 0000000..f1293a5
> --- /dev/null
> +++ b/sound/usb/hiface/chip.c
> @@ -0,0 +1,329 @@
> +/*
> + * Linux driver for M2Tech hiFace compatible devices
> + *
> + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> + *
> + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> + *           Antonio Ospite <ao2@amarulasolutions.com>
> + *
> + * The driver is based on the work done in TerraTec DMX 6Fire USB
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <sound/initval.h>
> +
> +#include "chip.h"
> +#include "pcm.h"
> +
> +MODULE_AUTHOR("Michael Trimarchi <michael@amarulasolutions.com>");
> +MODULE_AUTHOR("Antonio Ospite <ao2@amarulasolutions.com>");
> +MODULE_DESCRIPTION("M2Tech hiFace USB-SPDIF audio driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_SUPPORTED_DEVICE("{{M2Tech,Young},"
> +			 "{M2Tech,hiFace},"
> +			 "{M2Tech,North Star},"
> +			 "{M2Tech,W4S Young},"
> +			 "{M2Tech,Corrson},"
> +			 "{M2Tech,AUDIA},"
> +			 "{M2Tech,SL Audio},"
> +			 "{M2Tech,Empirical},"
> +			 "{M2Tech,Rockna},"
> +			 "{M2Tech,Pathos},"
> +			 "{M2Tech,Metronome},"
> +			 "{M2Tech,CAD},"
> +			 "{M2Tech,Audio Esclusive},"
> +			 "{M2Tech,Rotel},"
> +			 "{M2Tech,Eeaudio},"
> +			 "{The Chord Company,CHORD},"
> +			 "{AVA Group A/S,Vitus}}");
> +
> +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
> +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */
> +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
> +
> +#define DRIVER_NAME "snd-usb-hiface"
> +#define CARD_NAME "hiFace"
> +
> +module_param_array(index, int, NULL, 0444);
> +MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
> +module_param_array(id, charp, NULL, 0444);
> +MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
> +module_param_array(enable, bool, NULL, 0444);
> +MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
> +
> +static struct hiface_chip *chips[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
> +
> +static DEFINE_MUTEX(register_mutex);
> +
> +struct hiface_vendor_quirk {
> +	const char *device_name;
> +	u8 extra_freq;
> +};
> +
> +static int hiface_chip_create(struct usb_device *device, int idx,
> +			      const struct hiface_vendor_quirk *quirk,
> +			      struct hiface_chip **rchip)
> +{
> +	struct snd_card *card = NULL;
> +	struct hiface_chip *chip;
> +	int ret;
> +	int len;
> +
> +	*rchip = NULL;
> +
> +	/* if we are here, card can be registered in alsa. */
> +	ret = snd_card_create(index[idx], id[idx], THIS_MODULE, sizeof(*chip), &card);
> +	if (ret < 0) {
> +		snd_printk(KERN_ERR "cannot create alsa card.\n");
> +		return ret;
> +	}
> +
> +	strcpy(card->driver, DRIVER_NAME);
> +
> +	if (quirk && quirk->device_name)
> +		strcpy(card->shortname, quirk->device_name);
> +	else
> +		strcpy(card->shortname, "M2Tech generic audio");

Use strncpy() or strlcpy() please, even if you're sure.

> +
> +	strlcat(card->longname, card->shortname, sizeof(card->longname));
> +	len = strlcat(card->longname, " at ", sizeof(card->longname));
> +	if (len < sizeof(card->longname))
> +		usb_make_path(device, card->longname + len,
> +			      sizeof(card->longname) - len);
> +
> +	chip = card->private_data;
> +	chip->dev = device;
> +	chip->index = idx;
> +	chip->card = card;
> +
> +	*rchip = chip;
> +	return 0;
> +}
> +
> +static int hiface_chip_probe(struct usb_interface *intf,
> +			     const struct usb_device_id *usb_id)
> +{
> +	const struct hiface_vendor_quirk *quirk = (struct hiface_vendor_quirk *)usb_id->driver_info;
> +	int ret;
> +	int i;
> +	struct hiface_chip *chip;
> +	struct usb_device *device = interface_to_usbdev(intf);
> +
> +	pr_debug("Probe " DRIVER_NAME " driver.\n");

As Clemens said, you should drop this.

> +
> +	ret = usb_set_interface(device, 0, 0);
> +	if (ret != 0) {
> +		snd_printk(KERN_ERR "can't set first interface for " CARD_NAME " device.\n");
> +		return -EIO;
> +	}
> +
> +	/* check whether the card is already registered */
> +	chip = NULL;
> +	mutex_lock(&register_mutex);
> +	for (i = 0; i < SNDRV_CARDS; i++) {
> +		if (chips[i] && chips[i]->dev == device) {
> +			if (chips[i]->shutdown) {
> +				snd_printk(KERN_ERR CARD_NAME " device is in the shutdown state, cannot create a card instance\n");
> +				ret = -ENODEV;
> +				goto err;
> +			}
> +			chip = chips[i];
> +			break;
> +		}
> +	}
> +	if (!chip) {
> +		/* it's a fresh one.
> +		 * now look for an empty slot and create a new card instance
> +		 */
> +		for (i = 0; i < SNDRV_CARDS; i++)
> +			if (enable[i] && !chips[i]) {
> +				ret = hiface_chip_create(device, i, quirk,
> +							 &chip);
> +				if (ret < 0)
> +					goto err;
> +
> +				snd_card_set_dev(chip->card, &intf->dev);
> +				break;
> +			}
> +		if (!chip) {
> +			snd_printk(KERN_ERR "no available " CARD_NAME " audio device\n");
> +			ret = -ENODEV;
> +			goto err;
> +		}
> +	}

The generic driver does the above in order to group multiple interfaces
on one device into one snd_card. It's needed because it is probed on
interface level.

Your driver seems to operate on device-level only, so you can drop this.

> +	ret = hiface_pcm_init(chip, quirk ? quirk->extra_freq : 0);
> +	if (ret < 0)
> +		goto err_chip_destroy;
> +
> +	ret = snd_card_register(chip->card);
> +	if (ret < 0) {
> +		snd_printk(KERN_ERR "cannot register " CARD_NAME " card\n");

Not sure, but I think it might be better to use dev_{err,info,dgb} here,
so the logging can be grouped to individual devices. Takashi, any
opinion on that?

> +		goto err_chip_destroy;
> +	}
> +
> +	chips[chip->index] = chip;
> +	chip->intf_count++;
> +
> +	mutex_unlock(&register_mutex);
> +
> +	usb_set_intfdata(intf, chip);
> +	return 0;
> +
> +err_chip_destroy:
> +	snd_card_free(chip->card);
> +err:
> +	mutex_unlock(&register_mutex);
> +	return ret;
> +}
> +
> +static void hiface_chip_disconnect(struct usb_interface *intf)
> +{
> +	struct hiface_chip *chip;
> +	struct snd_card *card;
> +
> +	pr_debug("%s: called.\n", __func__);

See above, and there a more places below ...

> +
> +	chip = usb_get_intfdata(intf);
> +	if (!chip)
> +		return;
> +
> +	card = chip->card;
> +	chip->intf_count--;
> +	if (chip->intf_count <= 0) {
> +		/* Make sure that the userspace cannot create new request */
> +		snd_card_disconnect(card);
> +
> +		mutex_lock(&register_mutex);
> +		chips[chip->index] = NULL;
> +		mutex_unlock(&register_mutex);
> +
> +		chip->shutdown = true;
> +		hiface_pcm_abort(chip);
> +		snd_card_free_when_closed(card);
> +	}
> +}
> +
> +static const struct usb_device_id device_table[] = {
> +	{
> +		USB_DEVICE(0x04b4, 0x0384),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Young",
> +			.extra_freq = 1,
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x930b),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "hiFace",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x931b),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "North Star",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x931c),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "W4S Young",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x931d),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Corrson",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x931e),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "AUDIA",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x931f),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "SL Audio",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x9320),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Empirical",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x9321),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Rockna",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x9001),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Pathos",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x9002),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Metronome",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x9006),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "CAD",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x9008),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Audio Esclusive",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x931c),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Rotel",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x932c),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Eeaudio",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x245f, 0x931c),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "CHORD",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x25c6, 0x9002),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Vitus",
> +		}
> +	},
> +	{}
> +};
> +
> +MODULE_DEVICE_TABLE(usb, device_table);
> +
> +static struct usb_driver hiface_usb_driver = {
> +	.name = DRIVER_NAME,
> +	.probe = hiface_chip_probe,
> +	.disconnect = hiface_chip_disconnect,
> +	.id_table = device_table,
> +};
> +
> +module_usb_driver(hiface_usb_driver);
> diff --git a/sound/usb/hiface/pcm.h b/sound/usb/hiface/pcm.h
> new file mode 100644
> index 0000000..77edd7c
> --- /dev/null
> +++ b/sound/usb/hiface/pcm.h
> @@ -0,0 +1,24 @@
> +/*
> + * Linux driver for M2Tech hiFace compatible devices
> + *
> + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> + *
> + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> + *           Antonio Ospite <ao2@amarulasolutions.com>
> + *
> + * The driver is based on the work done in TerraTec DMX 6Fire USB
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#ifndef HIFACE_PCM_H
> +#define HIFACE_PCM_H
> +
> +struct hiface_chip;
> +
> +int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq);
> +void hiface_pcm_abort(struct hiface_chip *chip);
> +#endif /* HIFACE_PCM_H */
> diff --git a/sound/usb/hiface/pcm.c b/sound/usb/hiface/pcm.c
> new file mode 100644
> index 0000000..d0c3c58
> --- /dev/null
> +++ b/sound/usb/hiface/pcm.c
> @@ -0,0 +1,638 @@
> +/*
> + * Linux driver for M2Tech hiFace compatible devices
> + *
> + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> + *
> + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> + *           Antonio Ospite <ao2@amarulasolutions.com>
> + *
> + * The driver is based on the work done in TerraTec DMX 6Fire USB
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#include <linux/slab.h>
> +#include <sound/pcm.h>
> +
> +#include "pcm.h"
> +#include "chip.h"
> +
> +#define OUT_EP          0x2
> +#define PCM_N_URBS      8
> +#define PCM_PACKET_SIZE 4096
> +#define PCM_BUFFER_SIZE (2 * PCM_N_URBS * PCM_PACKET_SIZE)
> +
> +struct pcm_urb {
> +	struct hiface_chip *chip;
> +
> +	struct urb instance;
> +	struct usb_anchor submitted;
> +	u8 *buffer;
> +};
> +
> +struct pcm_substream {
> +	spinlock_t lock;
> +	struct snd_pcm_substream *instance;
> +
> +	bool active;
> +	snd_pcm_uframes_t dma_off;    /* current position in alsa dma_area */
> +	snd_pcm_uframes_t period_off; /* current position in current period */
> +};
> +
> +enum { /* pcm streaming states */
> +	STREAM_DISABLED, /* no pcm streaming */
> +	STREAM_STARTING, /* pcm streaming requested, waiting to become ready */
> +	STREAM_RUNNING,  /* pcm streaming running */
> +	STREAM_STOPPING
> +};
> +
> +struct pcm_runtime {
> +	struct hiface_chip *chip;
> +	struct snd_pcm *instance;
> +
> +	struct pcm_substream playback;
> +	bool panic; /* if set driver won't do anymore pcm on device */
> +
> +	struct pcm_urb out_urbs[PCM_N_URBS];
> +
> +	struct mutex stream_mutex;
> +	u8 stream_state; /* one of STREAM_XXX */
> +	u8 extra_freq;
> +	wait_queue_head_t stream_wait_queue;
> +	bool stream_wait_cond;
> +};
> +
> +static const unsigned int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000,
> +				      352800, 384000 };
> +static const struct snd_pcm_hw_constraint_list constraints_extra_rates = {
> +	.count = ARRAY_SIZE(rates),
> +	.list = rates,
> +	.mask = 0,
> +};
> +
> +static const struct snd_pcm_hardware pcm_hw = {
> +	.info = SNDRV_PCM_INFO_MMAP |
> +		SNDRV_PCM_INFO_INTERLEAVED |
> +		SNDRV_PCM_INFO_BLOCK_TRANSFER |
> +		SNDRV_PCM_INFO_PAUSE |
> +		SNDRV_PCM_INFO_MMAP_VALID |
> +		SNDRV_PCM_INFO_BATCH,
> +
> +	.formats = SNDRV_PCM_FMTBIT_S32_LE,
> +
> +	.rates = SNDRV_PCM_RATE_44100 |
> +		SNDRV_PCM_RATE_48000 |
> +		SNDRV_PCM_RATE_88200 |
> +		SNDRV_PCM_RATE_96000 |
> +		SNDRV_PCM_RATE_176400 |
> +		SNDRV_PCM_RATE_192000,
> +
> +	.rate_min = 44100,
> +	.rate_max = 192000, /* changes in hiface_pcm_open to support extra rates */
> +	.channels_min = 2,
> +	.channels_max = 2,
> +	.buffer_bytes_max = PCM_BUFFER_SIZE,
> +	.period_bytes_min = PCM_PACKET_SIZE,
> +	.period_bytes_max = PCM_BUFFER_SIZE,
> +	.periods_min = 2,
> +	.periods_max = 1024
> +};
> +
> +/* message values used to change the sample rate */
> +#define HIFACE_SET_RATE_REQUEST 0xb0
> +
> +#define HIFACE_RATE_44100  0x43
> +#define HIFACE_RATE_48000  0x4b
> +#define HIFACE_RATE_88200  0x42
> +#define HIFACE_RATE_96000  0x4a
> +#define HIFACE_RATE_176400 0x40
> +#define HIFACE_RATE_192000 0x48
> +#define HIFACE_RATE_352000 0x58
> +#define HIFACE_RATE_384000 0x68
> +
> +static int hiface_pcm_set_rate(struct pcm_runtime *rt, unsigned int rate)
> +{
> +	struct usb_device *device = rt->chip->dev;
> +	u16 rate_value;
> +	int ret;
> +
> +	/* We are already sure that the rate is supported here thanks to
> +	 * ALSA constraints
> +	 */
> +	switch (rate) {
> +	case 44100:
> +		rate_value = HIFACE_RATE_44100;
> +		break;
> +	case 48000:
> +		rate_value = HIFACE_RATE_48000;
> +		break;
> +	case 88200:
> +		rate_value = HIFACE_RATE_88200;
> +		break;
> +	case 96000:
> +		rate_value = HIFACE_RATE_96000;
> +		break;
> +	case 176400:
> +		rate_value = HIFACE_RATE_176400;
> +		break;
> +	case 192000:
> +		rate_value = HIFACE_RATE_192000;
> +		break;
> +	case 352000:
> +		rate_value = HIFACE_RATE_352000;
> +		break;
> +	case 384000:
> +		rate_value = HIFACE_RATE_384000;
> +		break;
> +	default:
> +		snd_printk(KERN_ERR "Unsupported rate %d\n", rate);
> +		return -EINVAL;
> +	}
> +
> +	/*
> +	 * USBIO: Vendor 0xb0(wValue=0x0043, wIndex=0x0000)
> +	 * 43 b0 43 00 00 00 00 00
> +	 * USBIO: Vendor 0xb0(wValue=0x004b, wIndex=0x0000)
> +	 * 43 b0 4b 00 00 00 00 00
> +	 * This control message doesn't have any ack from the
> +	 * other side
> +	 */
> +	ret = usb_control_msg(device, usb_sndctrlpipe(device, 0),
> +				HIFACE_SET_RATE_REQUEST,
> +				USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
> +				rate_value, 0, NULL, 0, 100);
> +	if (ret < 0) {
> +		snd_printk(KERN_ERR "Error setting samplerate %d.\n", rate);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static struct pcm_substream *hiface_pcm_get_substream(
> +		struct snd_pcm_substream *alsa_sub)
> +{
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +
> +	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
> +		return &rt->playback;
> +
> +	pr_debug("Error getting pcm substream slot.\n");
> +	return NULL;
> +}
> +
> +/* call with stream_mutex locked */
> +static void hiface_pcm_stream_stop(struct pcm_runtime *rt)
> +{
> +	int i, time;
> +
> +	if (rt->stream_state != STREAM_DISABLED) {
> +		rt->stream_state = STREAM_STOPPING;
> +
> +		for (i = 0; i < PCM_N_URBS; i++) {
> +			time = usb_wait_anchor_empty_timeout(
> +					&rt->out_urbs[i].submitted, 100);
> +			if (!time)
> +				usb_kill_anchored_urbs(
> +					&rt->out_urbs[i].submitted);
> +			usb_kill_urb(&rt->out_urbs[i].instance);
> +		}
> +
> +		rt->stream_state = STREAM_DISABLED;
> +	}
> +}
> +
> +/* call with stream_mutex locked */
> +static int hiface_pcm_stream_start(struct pcm_runtime *rt)
> +{
> +	int ret = 0, i;
> +
> +	if (rt->stream_state == STREAM_DISABLED) {
> +		/* submit our out urbs zero init */
> +		rt->stream_state = STREAM_STARTING;
> +		for (i = 0; i < PCM_N_URBS; i++) {
> +			memset(rt->out_urbs[i].buffer, 0, PCM_PACKET_SIZE);
> +			usb_anchor_urb(&rt->out_urbs[i].instance,
> +				       &rt->out_urbs[i].submitted);
> +			ret = usb_submit_urb(&rt->out_urbs[i].instance,
> +					GFP_ATOMIC);
> +			if (ret) {
> +				hiface_pcm_stream_stop(rt);
> +				return ret;
> +			}
> +		}
> +
> +		/* wait for first out urb to return (sent in in urb handler) */
> +		wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond,
> +				HZ);
> +		if (rt->stream_wait_cond) {
> +			pr_debug("%s: Stream is running wakeup event\n",
> +				 __func__);
> +			rt->stream_state = STREAM_RUNNING;
> +		} else {
> +			hiface_pcm_stream_stop(rt);
> +			return -EIO;
> +		}
> +	}
> +	return ret;
> +}
> +
> +
> +/* The hardware wants word-swapped 32-bit values */
> +static void memcpy_swahw32(u8 *dest, u8 *src, unsigned int n)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < n / 4; i++)
> +		((u32 *)dest)[i] = swahw32(((u32 *)src)[i]);
> +}
> +
> +/* call with substream locked */
> +/* returns true if a period elapsed */
> +static bool hiface_pcm_playback(struct pcm_substream *sub,
> +		struct pcm_urb *urb)
> +{
> +	struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
> +	u8 *source;
> +	unsigned int pcm_buffer_size;
> +
> +	WARN_ON(alsa_rt->format != SNDRV_PCM_FORMAT_S32_LE);
> +
> +	pcm_buffer_size = snd_pcm_lib_buffer_bytes(sub->instance);
> +
> +	if (sub->dma_off + PCM_PACKET_SIZE <= pcm_buffer_size) {
> +		pr_debug("%s: (1) buffer_size %#x dma_offset %#x\n", __func__,
> +			 (unsigned int) pcm_buffer_size,
> +			 (unsigned int) sub->dma_off);
> +
> +		source = alsa_rt->dma_area + sub->dma_off;
> +		memcpy_swahw32(urb->buffer, source, PCM_PACKET_SIZE);
> +	} else {
> +		/* wrap around at end of ring buffer */
> +		unsigned int len;
> +
> +		pr_debug("%s: (2) buffer_size %#x dma_offset %#x\n", __func__,
> +			 (unsigned int) pcm_buffer_size,
> +			 (unsigned int) sub->dma_off);
> +
> +		len = pcm_buffer_size - sub->dma_off;
> +
> +		source = alsa_rt->dma_area + sub->dma_off;
> +		memcpy_swahw32(urb->buffer, source, len);
> +
> +		source = alsa_rt->dma_area;
> +		memcpy_swahw32(urb->buffer + len, source,
> +			       PCM_PACKET_SIZE - len);
> +	}
> +	sub->dma_off += PCM_PACKET_SIZE;
> +	if (sub->dma_off >= pcm_buffer_size)
> +		sub->dma_off -= pcm_buffer_size;
> +
> +	sub->period_off += PCM_PACKET_SIZE;
> +	if (sub->period_off >= alsa_rt->period_size) {
> +		sub->period_off %= alsa_rt->period_size;
> +		return true;
> +	}
> +	return false;
> +}
> +
> +static void hiface_pcm_out_urb_handler(struct urb *usb_urb)
> +{
> +	struct pcm_urb *out_urb = usb_urb->context;
> +	struct pcm_runtime *rt = out_urb->chip->pcm;
> +	struct pcm_substream *sub;
> +	bool do_period_elapsed = false;
> +	unsigned long flags;
> +	int ret;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	if (rt->panic || rt->stream_state == STREAM_STOPPING)
> +		return;
> +
> +	if (unlikely(usb_urb->status == -ENOENT ||	/* unlinked */
> +		     usb_urb->status == -ENODEV ||	/* device removed */
> +		     usb_urb->status == -ECONNRESET ||	/* unlinked */
> +		     usb_urb->status == -ESHUTDOWN)) {	/* device disabled */
> +		goto out_fail;
> +	}
> +
> +	if (rt->stream_state == STREAM_STARTING) {
> +		rt->stream_wait_cond = true;
> +		wake_up(&rt->stream_wait_queue);
> +	}
> +
> +	/* now send our playback data (if a free out urb was found) */
> +	sub = &rt->playback;
> +	spin_lock_irqsave(&sub->lock, flags);
> +	if (sub->active)
> +		do_period_elapsed = hiface_pcm_playback(sub, out_urb);
> +	else
> +		memset(out_urb->buffer, 0, PCM_PACKET_SIZE);
> +
> +	spin_unlock_irqrestore(&sub->lock, flags);
> +
> +	if (do_period_elapsed)
> +		snd_pcm_period_elapsed(sub->instance);
> +
> +	ret = usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
> +	if (ret < 0)
> +		goto out_fail;
> +
> +	return;
> +
> +out_fail:
> +	rt->panic = true;
> +}
> +
> +static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub)
> +{
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	struct pcm_substream *sub = NULL;
> +	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
> +	int ret;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	if (rt->panic)
> +		return -EPIPE;
> +
> +	mutex_lock(&rt->stream_mutex);
> +	alsa_rt->hw = pcm_hw;
> +
> +	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
> +		sub = &rt->playback;
> +
> +	if (!sub) {
> +		mutex_unlock(&rt->stream_mutex);
> +		pr_err("Invalid stream type\n");
> +		return -EINVAL;
> +	}
> +
> +	if (rt->extra_freq) {
> +		alsa_rt->hw.rates |= SNDRV_PCM_RATE_KNOT;
> +		alsa_rt->hw.rate_max = 384000;
> +
> +		/* explicit constraints needed as we added SNDRV_PCM_RATE_KNOT */
> +		ret = snd_pcm_hw_constraint_list(alsa_sub->runtime, 0,
> +						 SNDRV_PCM_HW_PARAM_RATE,
> +						 &constraints_extra_rates);
> +		if (ret < 0) {
> +			mutex_unlock(&rt->stream_mutex);
> +			return ret;
> +		}
> +	}
> +
> +	sub->instance = alsa_sub;
> +	sub->active = false;
> +	mutex_unlock(&rt->stream_mutex);
> +	return 0;
> +}
> +
> +static int hiface_pcm_close(struct snd_pcm_substream *alsa_sub)
> +{
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> +	unsigned long flags;
> +
> +	if (rt->panic)
> +		return 0;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	mutex_lock(&rt->stream_mutex);
> +	if (sub) {
> +		/* deactivate substream */
> +		spin_lock_irqsave(&sub->lock, flags);
> +		sub->instance = NULL;
> +		sub->active = false;
> +		spin_unlock_irqrestore(&sub->lock, flags);
> +
> +		/* all substreams closed? if so, stop streaming */
> +		if (!rt->playback.instance)
> +			hiface_pcm_stream_stop(rt);
> +	}
> +	mutex_unlock(&rt->stream_mutex);
> +	return 0;
> +}
> +
> +static int hiface_pcm_hw_params(struct snd_pcm_substream *alsa_sub,
> +		struct snd_pcm_hw_params *hw_params)
> +{
> +	pr_debug("%s: called.\n", __func__);
> +	return snd_pcm_lib_malloc_pages(alsa_sub,
> +			params_buffer_bytes(hw_params));
> +}
> +
> +static int hiface_pcm_hw_free(struct snd_pcm_substream *alsa_sub)
> +{
> +	pr_debug("%s: called.\n", __func__);
> +	return snd_pcm_lib_free_pages(alsa_sub);
> +}
> +
> +static int hiface_pcm_prepare(struct snd_pcm_substream *alsa_sub)
> +{
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> +	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
> +	int ret;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	if (rt->panic)
> +		return -EPIPE;
> +	if (!sub)
> +		return -ENODEV;
> +
> +	mutex_lock(&rt->stream_mutex);
> +
> +	sub->dma_off = 0;
> +	sub->period_off = 0;
> +
> +	if (rt->stream_state == STREAM_DISABLED) {
> +
> +		ret = hiface_pcm_set_rate(rt, alsa_rt->rate);
> +		if (ret) {
> +			mutex_unlock(&rt->stream_mutex);
> +			return ret;
> +		}
> +		ret = hiface_pcm_stream_start(rt);
> +		if (ret) {
> +			mutex_unlock(&rt->stream_mutex);
> +			return ret;
> +		}
> +	}
> +	mutex_unlock(&rt->stream_mutex);
> +	return 0;
> +}
> +
> +static int hiface_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
> +{
> +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	unsigned long flags;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	if (rt->panic)
> +		return -EPIPE;
> +	if (!sub)
> +		return -ENODEV;
> +
> +	switch (cmd) {
> +	case SNDRV_PCM_TRIGGER_START:
> +	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
> +		spin_lock_irqsave(&sub->lock, flags);
> +		sub->active = true;
> +		spin_unlock_irqrestore(&sub->lock, flags);
> +		return 0;
> +
> +	case SNDRV_PCM_TRIGGER_STOP:
> +	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
> +		spin_lock_irqsave(&sub->lock, flags);
> +		sub->active = false;
> +		spin_unlock_irqrestore(&sub->lock, flags);
> +		return 0;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static snd_pcm_uframes_t hiface_pcm_pointer(struct snd_pcm_substream *alsa_sub)
> +{
> +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	unsigned long flags;
> +	snd_pcm_uframes_t dma_offset;
> +
> +	if (rt->panic || !sub)
> +		return SNDRV_PCM_STATE_XRUN;
> +
> +	spin_lock_irqsave(&sub->lock, flags);
> +	dma_offset = sub->dma_off;
> +	spin_unlock_irqrestore(&sub->lock, flags);
> +	return bytes_to_frames(alsa_sub->runtime, dma_offset);
> +}
> +
> +static struct snd_pcm_ops pcm_ops = {
> +	.open = hiface_pcm_open,
> +	.close = hiface_pcm_close,
> +	.ioctl = snd_pcm_lib_ioctl,
> +	.hw_params = hiface_pcm_hw_params,
> +	.hw_free = hiface_pcm_hw_free,
> +	.prepare = hiface_pcm_prepare,
> +	.trigger = hiface_pcm_trigger,
> +	.pointer = hiface_pcm_pointer,
> +};
> +
> +static int hiface_pcm_init_urb(struct pcm_urb *urb,
> +			       struct hiface_chip *chip,
> +			       unsigned int ep,
> +			       void (*handler)(struct urb *))
> +{
> +	urb->chip = chip;
> +	usb_init_urb(&urb->instance);
> +
> +	urb->buffer = kzalloc(PCM_PACKET_SIZE, GFP_KERNEL);
> +	if (!urb->buffer)
> +		return -ENOMEM;
> +
> +	usb_fill_bulk_urb(&urb->instance, chip->dev,
> +			  usb_sndbulkpipe(chip->dev, ep), (void *)urb->buffer,
> +			  PCM_PACKET_SIZE, handler, urb);
> +	init_usb_anchor(&urb->submitted);
> +
> +	return 0;
> +}
> +
> +void hiface_pcm_abort(struct hiface_chip *chip)
> +{
> +	struct pcm_runtime *rt = chip->pcm;
> +
> +	if (rt) {
> +		rt->panic = true;
> +
> +		if (rt->playback.instance) {
> +			snd_pcm_stream_lock_irq(rt->playback.instance);
> +			snd_pcm_stop(rt->playback.instance,
> +					SNDRV_PCM_STATE_XRUN);
> +			snd_pcm_stream_unlock_irq(rt->playback.instance);
> +		}
> +		mutex_lock(&rt->stream_mutex);
> +		hiface_pcm_stream_stop(rt);
> +		mutex_unlock(&rt->stream_mutex);
> +	}
> +}
> +
> +static void hiface_pcm_destroy(struct hiface_chip *chip)
> +{
> +	struct pcm_runtime *rt = chip->pcm;
> +	int i;
> +
> +	for (i = 0; i < PCM_N_URBS; i++)
> +		kfree(rt->out_urbs[i].buffer);
> +
> +	kfree(chip->pcm);
> +	chip->pcm = NULL;
> +}
> +
> +static void hiface_pcm_free(struct snd_pcm *pcm)
> +{
> +	struct pcm_runtime *rt = pcm->private_data;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	if (rt)
> +		hiface_pcm_destroy(rt->chip);
> +}
> +
> +int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq)
> +{
> +	int i;
> +	int ret;
> +	struct snd_pcm *pcm;
> +	struct pcm_runtime *rt;
> +
> +	rt = kzalloc(sizeof(*rt), GFP_KERNEL);
> +	if (!rt)
> +		return -ENOMEM;
> +
> +	rt->chip = chip;
> +	rt->stream_state = STREAM_DISABLED;
> +	if (extra_freq)
> +		rt->extra_freq = 1;
> +
> +	init_waitqueue_head(&rt->stream_wait_queue);
> +	mutex_init(&rt->stream_mutex);
> +	spin_lock_init(&rt->playback.lock);
> +
> +	for (i = 0; i < PCM_N_URBS; i++)
> +		hiface_pcm_init_urb(&rt->out_urbs[i], chip, OUT_EP,
> +				hiface_pcm_out_urb_handler);
> +
> +	ret = snd_pcm_new(chip->card, "USB-SPDIF Audio", 0, 1, 0, &pcm);
> +	if (ret < 0) {
> +		kfree(rt);
> +		pr_err("Cannot create pcm instance\n");
> +		return ret;
> +	}
> +
> +	pcm->private_data = rt;
> +	pcm->private_free = hiface_pcm_free;
> +
> +	strcpy(pcm->name, "USB-SPDIF Audio");
> +	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops);
> +
> +	snd_pcm_lib_preallocate_pages_for_all(pcm,
> +					SNDRV_DMA_TYPE_CONTINUOUS,
> +					snd_dma_continuous_data(GFP_KERNEL),
> +					PCM_BUFFER_SIZE, PCM_BUFFER_SIZE);
> +	rt->instance = pcm;
> +
> +	chip->pcm = rt;
> +	return 0;
> +}
> 

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

* Re: [PATCH v2] Add M2Tech hiFace USB-SPDIF driver
  2013-02-22 16:12   ` Daniel Mack
@ 2013-02-22 16:18     ` Takashi Iwai
  2013-04-28 20:59     ` Antonio Ospite
  1 sibling, 0 replies; 25+ messages in thread
From: Takashi Iwai @ 2013-02-22 16:18 UTC (permalink / raw)
  To: Daniel Mack
  Cc: alsa-devel, patch, Antonio Ospite, Clemens Ladisch, fchecconi,
	Pietro Cipriano, alberto, Michael Trimarchi

At Fri, 22 Feb 2013 17:12:36 +0100,
Daniel Mack wrote:
> 
> > +	ret = hiface_pcm_init(chip, quirk ? quirk->extra_freq : 0);
> > +	if (ret < 0)
> > +		goto err_chip_destroy;
> > +
> > +	ret = snd_card_register(chip->card);
> > +	if (ret < 0) {
> > +		snd_printk(KERN_ERR "cannot register " CARD_NAME " card\n");
> 
> Not sure, but I think it might be better to use dev_{err,info,dgb} here,
> so the logging can be grouped to individual devices. Takashi, any
> opinion on that?

Yeah, I think we should move to dev_*() somehow.  At least, for
most of generic outputs with snd_printk() can be converted with more
standard functions.  It's fine that a new driver starts using
dev_*(), then we'll convert the old ones eventually later.

Debug prints with snd_printd() or snd_printdd() are different
questions, though.  We have another control factor over the behavior
of these functions, so conversions should be done carefully.


Takashi

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

* Re: [PATCH v2] Add M2Tech hiFace USB-SPDIF driver
  2013-02-13 17:11 ` [PATCH v2] " Antonio Ospite
                     ` (2 preceding siblings ...)
  2013-02-22 16:12   ` Daniel Mack
@ 2013-04-20 20:15   ` Daniel Mack
  2013-04-22  7:40     ` Pavel Hofman
  2013-04-22  9:14     ` Antonio Ospite
  3 siblings, 2 replies; 25+ messages in thread
From: Daniel Mack @ 2013-04-20 20:15 UTC (permalink / raw)
  To: Antonio Ospite
  Cc: alsa-devel, patch, Takashi Iwai, Clemens Ladisch, fchecconi,
	Pietro Cipriano, alberto, Michael Trimarchi

On 13.02.2013 18:11, Antonio Ospite wrote:
> Add driver for M2Tech hiFace USB-SPDIF interface and compatible devices.
> 
> M2Tech hiFace and compatible devices offer a Hi-End S/PDIF Output
> Interface, see http://www.m2tech.biz/hiface.html

Just curious: is there a new version of this patch set coming?


Thanks,
Daniel




> 
> The supported products are:
> 
>   * M2Tech Young
>   * M2Tech hiFace
>   * M2Tech North Star
>   * M2Tech W4S Young
>   * M2Tech Corrson
>   * M2Tech AUDIA
>   * M2Tech SL Audio
>   * M2Tech Empirical
>   * M2Tech Rockna
>   * M2Tech Pathos
>   * M2Tech Metronome
>   * M2Tech CAD
>   * M2Tech Audio Esclusive
>   * M2Tech Rotel
>   * M2Tech Eeaudio
>   * The Chord Company CHORD
>   * AVA Group A/S Vitus
> 
> Signed-off-by: Antonio Ospite <ao2@amarulasolutions.com>
> ---
> 
> Changes since v1:
> 
>   * Change the first sentence of the Kconfig entry into "Select this..."
>   * Remove a useless sentence from the Kconfig entry
>   * Don't set alsa_rt->hw.rates in hiface_pcm_open()
>   * Style cleanup, no braces needed in single statement conditional
>   * Remove the rate field from pcm_runtime
>   * Use the hiFace name with the lowercase 'h' everywhere
>   * List actually supported devices in MODULE_SUPPORTED_DEVICE()
>   * Cosmetics, align values in the rates array
>   * Use an explicit switch instead of the rate_value array in
>     hiface_pcm_set_rate()
>   * Use usb_make_path() when building card->longname
>   * Use again the implicit mechanism to allocate the card private data
>   * Downgrade a pr_info to pr_debug in hiface_chip_probe()
>   * Make device_table const
>   * Rename PCM_MAX_PACKET_SIZE to PCM_PACKET_SIZE
>   * Rename MAX_BUFSIZE to PCM_BUFFER_SIZE
>   * Cosmetics, align symbolic constant values
>   * Add SNDRV_PCM_RATE_KNOT only when needed
>   * Declare memcpy_swahw32() as static
>   * Protect snd_pcm_stop() with snd_pcm_stream_lock_irq()
>   * Make hiface_pcm_playback() not returning anything
>   * Move the period elapsed check into hiface_pcm_playback()
>   * Handle the case of failing URBs in hiface_pcm_out_urb_handler()
>   * Fix a couple of checkpatch.pl issues
> 
> The incremental changes can be seen individually at
> https://github.com/panicking/snd-usb-asyncaudio/commits/master
> Commits from Feb. 10th and later.
> 
> Thanks,
>    Antonio
> 
>  sound/usb/Kconfig         |   31 +++
>  sound/usb/Makefile        |    2 +-
>  sound/usb/hiface/Makefile |    2 +
>  sound/usb/hiface/chip.h   |   34 +++
>  sound/usb/hiface/chip.c   |  329 +++++++++++++++++++++++
>  sound/usb/hiface/pcm.h    |   24 ++
>  sound/usb/hiface/pcm.c    |  638 +++++++++++++++++++++++++++++++++++++++++++++
>  7 files changed, 1059 insertions(+), 1 deletion(-)
>  create mode 100644 sound/usb/hiface/Makefile
>  create mode 100644 sound/usb/hiface/chip.h
>  create mode 100644 sound/usb/hiface/chip.c
>  create mode 100644 sound/usb/hiface/pcm.h
>  create mode 100644 sound/usb/hiface/pcm.c
> 
> diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig
> index 225dfd7..de9408b 100644
> --- a/sound/usb/Kconfig
> +++ b/sound/usb/Kconfig
> @@ -115,5 +115,36 @@ config SND_USB_6FIRE
>            and further help can be found at
>            http://sixfireusb.sourceforge.net
>  
> +config SND_USB_HIFACE
> +        tristate "M2Tech hiFace USB-SPDIF driver"
> +        select SND_PCM
> +        help
> +	  Select this option to include support for M2Tech hiFace USB-SPDIF
> +	  interface.
> +
> +	  This driver supports the original M2Tech hiFace and some other
> +	  compatible devices. The supported products are:
> +
> +	    * M2Tech Young
> +	    * M2Tech hiFace
> +	    * M2Tech North Star
> +	    * M2Tech W4S Young
> +	    * M2Tech Corrson
> +	    * M2Tech AUDIA
> +	    * M2Tech SL Audio
> +	    * M2Tech Empirical
> +	    * M2Tech Rockna
> +	    * M2Tech Pathos
> +	    * M2Tech Metronome
> +	    * M2Tech CAD
> +	    * M2Tech Audio Esclusive
> +	    * M2Tech Rotel
> +	    * M2Tech Eeaudio
> +	    * The Chord Company CHORD
> +	    * AVA Group A/S Vitus
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called snd-usb-hiface.
> +
>  endif	# SND_USB
>  
> diff --git a/sound/usb/Makefile b/sound/usb/Makefile
> index ac256dc..abe668f 100644
> --- a/sound/usb/Makefile
> +++ b/sound/usb/Makefile
> @@ -23,4 +23,4 @@ obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o
>  obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o
>  obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o
>  
> -obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/
> +obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/
> diff --git a/sound/usb/hiface/Makefile b/sound/usb/hiface/Makefile
> new file mode 100644
> index 0000000..463b136
> --- /dev/null
> +++ b/sound/usb/hiface/Makefile
> @@ -0,0 +1,2 @@
> +snd-usb-hiface-objs := chip.o pcm.o
> +obj-$(CONFIG_SND_USB_HIFACE) += snd-usb-hiface.o
> diff --git a/sound/usb/hiface/chip.h b/sound/usb/hiface/chip.h
> new file mode 100644
> index 0000000..cd615a0
> --- /dev/null
> +++ b/sound/usb/hiface/chip.h
> @@ -0,0 +1,34 @@
> +/*
> + * Linux driver for M2Tech hiFace compatible devices
> + *
> + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> + *
> + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> + *           Antonio Ospite <ao2@amarulasolutions.com>
> + *
> + * The driver is based on the work done in TerraTec DMX 6Fire USB
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#ifndef HIFACE_CHIP_H
> +#define HIFACE_CHIP_H
> +
> +#include <linux/usb.h>
> +#include <sound/core.h>
> +
> +struct pcm_runtime;
> +
> +struct hiface_chip {
> +	struct usb_device *dev;
> +	struct snd_card *card;
> +	int intf_count; /* number of registered interfaces */
> +	int index; /* index in module parameter arrays */
> +	bool shutdown;
> +
> +	struct pcm_runtime *pcm;
> +};
> +#endif /* HIFACE_CHIP_H */
> diff --git a/sound/usb/hiface/chip.c b/sound/usb/hiface/chip.c
> new file mode 100644
> index 0000000..f1293a5
> --- /dev/null
> +++ b/sound/usb/hiface/chip.c
> @@ -0,0 +1,329 @@
> +/*
> + * Linux driver for M2Tech hiFace compatible devices
> + *
> + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> + *
> + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> + *           Antonio Ospite <ao2@amarulasolutions.com>
> + *
> + * The driver is based on the work done in TerraTec DMX 6Fire USB
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <sound/initval.h>
> +
> +#include "chip.h"
> +#include "pcm.h"
> +
> +MODULE_AUTHOR("Michael Trimarchi <michael@amarulasolutions.com>");
> +MODULE_AUTHOR("Antonio Ospite <ao2@amarulasolutions.com>");
> +MODULE_DESCRIPTION("M2Tech hiFace USB-SPDIF audio driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_SUPPORTED_DEVICE("{{M2Tech,Young},"
> +			 "{M2Tech,hiFace},"
> +			 "{M2Tech,North Star},"
> +			 "{M2Tech,W4S Young},"
> +			 "{M2Tech,Corrson},"
> +			 "{M2Tech,AUDIA},"
> +			 "{M2Tech,SL Audio},"
> +			 "{M2Tech,Empirical},"
> +			 "{M2Tech,Rockna},"
> +			 "{M2Tech,Pathos},"
> +			 "{M2Tech,Metronome},"
> +			 "{M2Tech,CAD},"
> +			 "{M2Tech,Audio Esclusive},"
> +			 "{M2Tech,Rotel},"
> +			 "{M2Tech,Eeaudio},"
> +			 "{The Chord Company,CHORD},"
> +			 "{AVA Group A/S,Vitus}}");
> +
> +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
> +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */
> +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
> +
> +#define DRIVER_NAME "snd-usb-hiface"
> +#define CARD_NAME "hiFace"
> +
> +module_param_array(index, int, NULL, 0444);
> +MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
> +module_param_array(id, charp, NULL, 0444);
> +MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
> +module_param_array(enable, bool, NULL, 0444);
> +MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
> +
> +static struct hiface_chip *chips[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
> +
> +static DEFINE_MUTEX(register_mutex);
> +
> +struct hiface_vendor_quirk {
> +	const char *device_name;
> +	u8 extra_freq;
> +};
> +
> +static int hiface_chip_create(struct usb_device *device, int idx,
> +			      const struct hiface_vendor_quirk *quirk,
> +			      struct hiface_chip **rchip)
> +{
> +	struct snd_card *card = NULL;
> +	struct hiface_chip *chip;
> +	int ret;
> +	int len;
> +
> +	*rchip = NULL;
> +
> +	/* if we are here, card can be registered in alsa. */
> +	ret = snd_card_create(index[idx], id[idx], THIS_MODULE, sizeof(*chip), &card);
> +	if (ret < 0) {
> +		snd_printk(KERN_ERR "cannot create alsa card.\n");
> +		return ret;
> +	}
> +
> +	strcpy(card->driver, DRIVER_NAME);
> +
> +	if (quirk && quirk->device_name)
> +		strcpy(card->shortname, quirk->device_name);
> +	else
> +		strcpy(card->shortname, "M2Tech generic audio");
> +
> +	strlcat(card->longname, card->shortname, sizeof(card->longname));
> +	len = strlcat(card->longname, " at ", sizeof(card->longname));
> +	if (len < sizeof(card->longname))
> +		usb_make_path(device, card->longname + len,
> +			      sizeof(card->longname) - len);
> +
> +	chip = card->private_data;
> +	chip->dev = device;
> +	chip->index = idx;
> +	chip->card = card;
> +
> +	*rchip = chip;
> +	return 0;
> +}
> +
> +static int hiface_chip_probe(struct usb_interface *intf,
> +			     const struct usb_device_id *usb_id)
> +{
> +	const struct hiface_vendor_quirk *quirk = (struct hiface_vendor_quirk *)usb_id->driver_info;
> +	int ret;
> +	int i;
> +	struct hiface_chip *chip;
> +	struct usb_device *device = interface_to_usbdev(intf);
> +
> +	pr_debug("Probe " DRIVER_NAME " driver.\n");
> +
> +	ret = usb_set_interface(device, 0, 0);
> +	if (ret != 0) {
> +		snd_printk(KERN_ERR "can't set first interface for " CARD_NAME " device.\n");
> +		return -EIO;
> +	}
> +
> +	/* check whether the card is already registered */
> +	chip = NULL;
> +	mutex_lock(&register_mutex);
> +	for (i = 0; i < SNDRV_CARDS; i++) {
> +		if (chips[i] && chips[i]->dev == device) {
> +			if (chips[i]->shutdown) {
> +				snd_printk(KERN_ERR CARD_NAME " device is in the shutdown state, cannot create a card instance\n");
> +				ret = -ENODEV;
> +				goto err;
> +			}
> +			chip = chips[i];
> +			break;
> +		}
> +	}
> +	if (!chip) {
> +		/* it's a fresh one.
> +		 * now look for an empty slot and create a new card instance
> +		 */
> +		for (i = 0; i < SNDRV_CARDS; i++)
> +			if (enable[i] && !chips[i]) {
> +				ret = hiface_chip_create(device, i, quirk,
> +							 &chip);
> +				if (ret < 0)
> +					goto err;
> +
> +				snd_card_set_dev(chip->card, &intf->dev);
> +				break;
> +			}
> +		if (!chip) {
> +			snd_printk(KERN_ERR "no available " CARD_NAME " audio device\n");
> +			ret = -ENODEV;
> +			goto err;
> +		}
> +	}
> +
> +	ret = hiface_pcm_init(chip, quirk ? quirk->extra_freq : 0);
> +	if (ret < 0)
> +		goto err_chip_destroy;
> +
> +	ret = snd_card_register(chip->card);
> +	if (ret < 0) {
> +		snd_printk(KERN_ERR "cannot register " CARD_NAME " card\n");
> +		goto err_chip_destroy;
> +	}
> +
> +	chips[chip->index] = chip;
> +	chip->intf_count++;
> +
> +	mutex_unlock(&register_mutex);
> +
> +	usb_set_intfdata(intf, chip);
> +	return 0;
> +
> +err_chip_destroy:
> +	snd_card_free(chip->card);
> +err:
> +	mutex_unlock(&register_mutex);
> +	return ret;
> +}
> +
> +static void hiface_chip_disconnect(struct usb_interface *intf)
> +{
> +	struct hiface_chip *chip;
> +	struct snd_card *card;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	chip = usb_get_intfdata(intf);
> +	if (!chip)
> +		return;
> +
> +	card = chip->card;
> +	chip->intf_count--;
> +	if (chip->intf_count <= 0) {
> +		/* Make sure that the userspace cannot create new request */
> +		snd_card_disconnect(card);
> +
> +		mutex_lock(&register_mutex);
> +		chips[chip->index] = NULL;
> +		mutex_unlock(&register_mutex);
> +
> +		chip->shutdown = true;
> +		hiface_pcm_abort(chip);
> +		snd_card_free_when_closed(card);
> +	}
> +}
> +
> +static const struct usb_device_id device_table[] = {
> +	{
> +		USB_DEVICE(0x04b4, 0x0384),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Young",
> +			.extra_freq = 1,
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x930b),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "hiFace",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x931b),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "North Star",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x931c),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "W4S Young",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x931d),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Corrson",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x931e),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "AUDIA",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x931f),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "SL Audio",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x9320),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Empirical",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x9321),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Rockna",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x9001),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Pathos",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x9002),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Metronome",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x9006),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "CAD",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x9008),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Audio Esclusive",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x931c),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Rotel",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x932c),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Eeaudio",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x245f, 0x931c),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "CHORD",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x25c6, 0x9002),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Vitus",
> +		}
> +	},
> +	{}
> +};
> +
> +MODULE_DEVICE_TABLE(usb, device_table);
> +
> +static struct usb_driver hiface_usb_driver = {
> +	.name = DRIVER_NAME,
> +	.probe = hiface_chip_probe,
> +	.disconnect = hiface_chip_disconnect,
> +	.id_table = device_table,
> +};
> +
> +module_usb_driver(hiface_usb_driver);
> diff --git a/sound/usb/hiface/pcm.h b/sound/usb/hiface/pcm.h
> new file mode 100644
> index 0000000..77edd7c
> --- /dev/null
> +++ b/sound/usb/hiface/pcm.h
> @@ -0,0 +1,24 @@
> +/*
> + * Linux driver for M2Tech hiFace compatible devices
> + *
> + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> + *
> + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> + *           Antonio Ospite <ao2@amarulasolutions.com>
> + *
> + * The driver is based on the work done in TerraTec DMX 6Fire USB
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#ifndef HIFACE_PCM_H
> +#define HIFACE_PCM_H
> +
> +struct hiface_chip;
> +
> +int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq);
> +void hiface_pcm_abort(struct hiface_chip *chip);
> +#endif /* HIFACE_PCM_H */
> diff --git a/sound/usb/hiface/pcm.c b/sound/usb/hiface/pcm.c
> new file mode 100644
> index 0000000..d0c3c58
> --- /dev/null
> +++ b/sound/usb/hiface/pcm.c
> @@ -0,0 +1,638 @@
> +/*
> + * Linux driver for M2Tech hiFace compatible devices
> + *
> + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> + *
> + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> + *           Antonio Ospite <ao2@amarulasolutions.com>
> + *
> + * The driver is based on the work done in TerraTec DMX 6Fire USB
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#include <linux/slab.h>
> +#include <sound/pcm.h>
> +
> +#include "pcm.h"
> +#include "chip.h"
> +
> +#define OUT_EP          0x2
> +#define PCM_N_URBS      8
> +#define PCM_PACKET_SIZE 4096
> +#define PCM_BUFFER_SIZE (2 * PCM_N_URBS * PCM_PACKET_SIZE)
> +
> +struct pcm_urb {
> +	struct hiface_chip *chip;
> +
> +	struct urb instance;
> +	struct usb_anchor submitted;
> +	u8 *buffer;
> +};
> +
> +struct pcm_substream {
> +	spinlock_t lock;
> +	struct snd_pcm_substream *instance;
> +
> +	bool active;
> +	snd_pcm_uframes_t dma_off;    /* current position in alsa dma_area */
> +	snd_pcm_uframes_t period_off; /* current position in current period */
> +};
> +
> +enum { /* pcm streaming states */
> +	STREAM_DISABLED, /* no pcm streaming */
> +	STREAM_STARTING, /* pcm streaming requested, waiting to become ready */
> +	STREAM_RUNNING,  /* pcm streaming running */
> +	STREAM_STOPPING
> +};
> +
> +struct pcm_runtime {
> +	struct hiface_chip *chip;
> +	struct snd_pcm *instance;
> +
> +	struct pcm_substream playback;
> +	bool panic; /* if set driver won't do anymore pcm on device */
> +
> +	struct pcm_urb out_urbs[PCM_N_URBS];
> +
> +	struct mutex stream_mutex;
> +	u8 stream_state; /* one of STREAM_XXX */
> +	u8 extra_freq;
> +	wait_queue_head_t stream_wait_queue;
> +	bool stream_wait_cond;
> +};
> +
> +static const unsigned int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000,
> +				      352800, 384000 };
> +static const struct snd_pcm_hw_constraint_list constraints_extra_rates = {
> +	.count = ARRAY_SIZE(rates),
> +	.list = rates,
> +	.mask = 0,
> +};
> +
> +static const struct snd_pcm_hardware pcm_hw = {
> +	.info = SNDRV_PCM_INFO_MMAP |
> +		SNDRV_PCM_INFO_INTERLEAVED |
> +		SNDRV_PCM_INFO_BLOCK_TRANSFER |
> +		SNDRV_PCM_INFO_PAUSE |
> +		SNDRV_PCM_INFO_MMAP_VALID |
> +		SNDRV_PCM_INFO_BATCH,
> +
> +	.formats = SNDRV_PCM_FMTBIT_S32_LE,
> +
> +	.rates = SNDRV_PCM_RATE_44100 |
> +		SNDRV_PCM_RATE_48000 |
> +		SNDRV_PCM_RATE_88200 |
> +		SNDRV_PCM_RATE_96000 |
> +		SNDRV_PCM_RATE_176400 |
> +		SNDRV_PCM_RATE_192000,
> +
> +	.rate_min = 44100,
> +	.rate_max = 192000, /* changes in hiface_pcm_open to support extra rates */
> +	.channels_min = 2,
> +	.channels_max = 2,
> +	.buffer_bytes_max = PCM_BUFFER_SIZE,
> +	.period_bytes_min = PCM_PACKET_SIZE,
> +	.period_bytes_max = PCM_BUFFER_SIZE,
> +	.periods_min = 2,
> +	.periods_max = 1024
> +};
> +
> +/* message values used to change the sample rate */
> +#define HIFACE_SET_RATE_REQUEST 0xb0
> +
> +#define HIFACE_RATE_44100  0x43
> +#define HIFACE_RATE_48000  0x4b
> +#define HIFACE_RATE_88200  0x42
> +#define HIFACE_RATE_96000  0x4a
> +#define HIFACE_RATE_176400 0x40
> +#define HIFACE_RATE_192000 0x48
> +#define HIFACE_RATE_352000 0x58
> +#define HIFACE_RATE_384000 0x68
> +
> +static int hiface_pcm_set_rate(struct pcm_runtime *rt, unsigned int rate)
> +{
> +	struct usb_device *device = rt->chip->dev;
> +	u16 rate_value;
> +	int ret;
> +
> +	/* We are already sure that the rate is supported here thanks to
> +	 * ALSA constraints
> +	 */
> +	switch (rate) {
> +	case 44100:
> +		rate_value = HIFACE_RATE_44100;
> +		break;
> +	case 48000:
> +		rate_value = HIFACE_RATE_48000;
> +		break;
> +	case 88200:
> +		rate_value = HIFACE_RATE_88200;
> +		break;
> +	case 96000:
> +		rate_value = HIFACE_RATE_96000;
> +		break;
> +	case 176400:
> +		rate_value = HIFACE_RATE_176400;
> +		break;
> +	case 192000:
> +		rate_value = HIFACE_RATE_192000;
> +		break;
> +	case 352000:
> +		rate_value = HIFACE_RATE_352000;
> +		break;
> +	case 384000:
> +		rate_value = HIFACE_RATE_384000;
> +		break;
> +	default:
> +		snd_printk(KERN_ERR "Unsupported rate %d\n", rate);
> +		return -EINVAL;
> +	}
> +
> +	/*
> +	 * USBIO: Vendor 0xb0(wValue=0x0043, wIndex=0x0000)
> +	 * 43 b0 43 00 00 00 00 00
> +	 * USBIO: Vendor 0xb0(wValue=0x004b, wIndex=0x0000)
> +	 * 43 b0 4b 00 00 00 00 00
> +	 * This control message doesn't have any ack from the
> +	 * other side
> +	 */
> +	ret = usb_control_msg(device, usb_sndctrlpipe(device, 0),
> +				HIFACE_SET_RATE_REQUEST,
> +				USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
> +				rate_value, 0, NULL, 0, 100);
> +	if (ret < 0) {
> +		snd_printk(KERN_ERR "Error setting samplerate %d.\n", rate);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static struct pcm_substream *hiface_pcm_get_substream(
> +		struct snd_pcm_substream *alsa_sub)
> +{
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +
> +	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
> +		return &rt->playback;
> +
> +	pr_debug("Error getting pcm substream slot.\n");
> +	return NULL;
> +}
> +
> +/* call with stream_mutex locked */
> +static void hiface_pcm_stream_stop(struct pcm_runtime *rt)
> +{
> +	int i, time;
> +
> +	if (rt->stream_state != STREAM_DISABLED) {
> +		rt->stream_state = STREAM_STOPPING;
> +
> +		for (i = 0; i < PCM_N_URBS; i++) {
> +			time = usb_wait_anchor_empty_timeout(
> +					&rt->out_urbs[i].submitted, 100);
> +			if (!time)
> +				usb_kill_anchored_urbs(
> +					&rt->out_urbs[i].submitted);
> +			usb_kill_urb(&rt->out_urbs[i].instance);
> +		}
> +
> +		rt->stream_state = STREAM_DISABLED;
> +	}
> +}
> +
> +/* call with stream_mutex locked */
> +static int hiface_pcm_stream_start(struct pcm_runtime *rt)
> +{
> +	int ret = 0, i;
> +
> +	if (rt->stream_state == STREAM_DISABLED) {
> +		/* submit our out urbs zero init */
> +		rt->stream_state = STREAM_STARTING;
> +		for (i = 0; i < PCM_N_URBS; i++) {
> +			memset(rt->out_urbs[i].buffer, 0, PCM_PACKET_SIZE);
> +			usb_anchor_urb(&rt->out_urbs[i].instance,
> +				       &rt->out_urbs[i].submitted);
> +			ret = usb_submit_urb(&rt->out_urbs[i].instance,
> +					GFP_ATOMIC);
> +			if (ret) {
> +				hiface_pcm_stream_stop(rt);
> +				return ret;
> +			}
> +		}
> +
> +		/* wait for first out urb to return (sent in in urb handler) */
> +		wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond,
> +				HZ);
> +		if (rt->stream_wait_cond) {
> +			pr_debug("%s: Stream is running wakeup event\n",
> +				 __func__);
> +			rt->stream_state = STREAM_RUNNING;
> +		} else {
> +			hiface_pcm_stream_stop(rt);
> +			return -EIO;
> +		}
> +	}
> +	return ret;
> +}
> +
> +
> +/* The hardware wants word-swapped 32-bit values */
> +static void memcpy_swahw32(u8 *dest, u8 *src, unsigned int n)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < n / 4; i++)
> +		((u32 *)dest)[i] = swahw32(((u32 *)src)[i]);
> +}
> +
> +/* call with substream locked */
> +/* returns true if a period elapsed */
> +static bool hiface_pcm_playback(struct pcm_substream *sub,
> +		struct pcm_urb *urb)
> +{
> +	struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
> +	u8 *source;
> +	unsigned int pcm_buffer_size;
> +
> +	WARN_ON(alsa_rt->format != SNDRV_PCM_FORMAT_S32_LE);
> +
> +	pcm_buffer_size = snd_pcm_lib_buffer_bytes(sub->instance);
> +
> +	if (sub->dma_off + PCM_PACKET_SIZE <= pcm_buffer_size) {
> +		pr_debug("%s: (1) buffer_size %#x dma_offset %#x\n", __func__,
> +			 (unsigned int) pcm_buffer_size,
> +			 (unsigned int) sub->dma_off);
> +
> +		source = alsa_rt->dma_area + sub->dma_off;
> +		memcpy_swahw32(urb->buffer, source, PCM_PACKET_SIZE);
> +	} else {
> +		/* wrap around at end of ring buffer */
> +		unsigned int len;
> +
> +		pr_debug("%s: (2) buffer_size %#x dma_offset %#x\n", __func__,
> +			 (unsigned int) pcm_buffer_size,
> +			 (unsigned int) sub->dma_off);
> +
> +		len = pcm_buffer_size - sub->dma_off;
> +
> +		source = alsa_rt->dma_area + sub->dma_off;
> +		memcpy_swahw32(urb->buffer, source, len);
> +
> +		source = alsa_rt->dma_area;
> +		memcpy_swahw32(urb->buffer + len, source,
> +			       PCM_PACKET_SIZE - len);
> +	}
> +	sub->dma_off += PCM_PACKET_SIZE;
> +	if (sub->dma_off >= pcm_buffer_size)
> +		sub->dma_off -= pcm_buffer_size;
> +
> +	sub->period_off += PCM_PACKET_SIZE;
> +	if (sub->period_off >= alsa_rt->period_size) {
> +		sub->period_off %= alsa_rt->period_size;
> +		return true;
> +	}
> +	return false;
> +}
> +
> +static void hiface_pcm_out_urb_handler(struct urb *usb_urb)
> +{
> +	struct pcm_urb *out_urb = usb_urb->context;
> +	struct pcm_runtime *rt = out_urb->chip->pcm;
> +	struct pcm_substream *sub;
> +	bool do_period_elapsed = false;
> +	unsigned long flags;
> +	int ret;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	if (rt->panic || rt->stream_state == STREAM_STOPPING)
> +		return;
> +
> +	if (unlikely(usb_urb->status == -ENOENT ||	/* unlinked */
> +		     usb_urb->status == -ENODEV ||	/* device removed */
> +		     usb_urb->status == -ECONNRESET ||	/* unlinked */
> +		     usb_urb->status == -ESHUTDOWN)) {	/* device disabled */
> +		goto out_fail;
> +	}
> +
> +	if (rt->stream_state == STREAM_STARTING) {
> +		rt->stream_wait_cond = true;
> +		wake_up(&rt->stream_wait_queue);
> +	}
> +
> +	/* now send our playback data (if a free out urb was found) */
> +	sub = &rt->playback;
> +	spin_lock_irqsave(&sub->lock, flags);
> +	if (sub->active)
> +		do_period_elapsed = hiface_pcm_playback(sub, out_urb);
> +	else
> +		memset(out_urb->buffer, 0, PCM_PACKET_SIZE);
> +
> +	spin_unlock_irqrestore(&sub->lock, flags);
> +
> +	if (do_period_elapsed)
> +		snd_pcm_period_elapsed(sub->instance);
> +
> +	ret = usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
> +	if (ret < 0)
> +		goto out_fail;
> +
> +	return;
> +
> +out_fail:
> +	rt->panic = true;
> +}
> +
> +static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub)
> +{
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	struct pcm_substream *sub = NULL;
> +	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
> +	int ret;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	if (rt->panic)
> +		return -EPIPE;
> +
> +	mutex_lock(&rt->stream_mutex);
> +	alsa_rt->hw = pcm_hw;
> +
> +	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
> +		sub = &rt->playback;
> +
> +	if (!sub) {
> +		mutex_unlock(&rt->stream_mutex);
> +		pr_err("Invalid stream type\n");
> +		return -EINVAL;
> +	}
> +
> +	if (rt->extra_freq) {
> +		alsa_rt->hw.rates |= SNDRV_PCM_RATE_KNOT;
> +		alsa_rt->hw.rate_max = 384000;
> +
> +		/* explicit constraints needed as we added SNDRV_PCM_RATE_KNOT */
> +		ret = snd_pcm_hw_constraint_list(alsa_sub->runtime, 0,
> +						 SNDRV_PCM_HW_PARAM_RATE,
> +						 &constraints_extra_rates);
> +		if (ret < 0) {
> +			mutex_unlock(&rt->stream_mutex);
> +			return ret;
> +		}
> +	}
> +
> +	sub->instance = alsa_sub;
> +	sub->active = false;
> +	mutex_unlock(&rt->stream_mutex);
> +	return 0;
> +}
> +
> +static int hiface_pcm_close(struct snd_pcm_substream *alsa_sub)
> +{
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> +	unsigned long flags;
> +
> +	if (rt->panic)
> +		return 0;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	mutex_lock(&rt->stream_mutex);
> +	if (sub) {
> +		/* deactivate substream */
> +		spin_lock_irqsave(&sub->lock, flags);
> +		sub->instance = NULL;
> +		sub->active = false;
> +		spin_unlock_irqrestore(&sub->lock, flags);
> +
> +		/* all substreams closed? if so, stop streaming */
> +		if (!rt->playback.instance)
> +			hiface_pcm_stream_stop(rt);
> +	}
> +	mutex_unlock(&rt->stream_mutex);
> +	return 0;
> +}
> +
> +static int hiface_pcm_hw_params(struct snd_pcm_substream *alsa_sub,
> +		struct snd_pcm_hw_params *hw_params)
> +{
> +	pr_debug("%s: called.\n", __func__);
> +	return snd_pcm_lib_malloc_pages(alsa_sub,
> +			params_buffer_bytes(hw_params));
> +}
> +
> +static int hiface_pcm_hw_free(struct snd_pcm_substream *alsa_sub)
> +{
> +	pr_debug("%s: called.\n", __func__);
> +	return snd_pcm_lib_free_pages(alsa_sub);
> +}
> +
> +static int hiface_pcm_prepare(struct snd_pcm_substream *alsa_sub)
> +{
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> +	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
> +	int ret;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	if (rt->panic)
> +		return -EPIPE;
> +	if (!sub)
> +		return -ENODEV;
> +
> +	mutex_lock(&rt->stream_mutex);
> +
> +	sub->dma_off = 0;
> +	sub->period_off = 0;
> +
> +	if (rt->stream_state == STREAM_DISABLED) {
> +
> +		ret = hiface_pcm_set_rate(rt, alsa_rt->rate);
> +		if (ret) {
> +			mutex_unlock(&rt->stream_mutex);
> +			return ret;
> +		}
> +		ret = hiface_pcm_stream_start(rt);
> +		if (ret) {
> +			mutex_unlock(&rt->stream_mutex);
> +			return ret;
> +		}
> +	}
> +	mutex_unlock(&rt->stream_mutex);
> +	return 0;
> +}
> +
> +static int hiface_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
> +{
> +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	unsigned long flags;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	if (rt->panic)
> +		return -EPIPE;
> +	if (!sub)
> +		return -ENODEV;
> +
> +	switch (cmd) {
> +	case SNDRV_PCM_TRIGGER_START:
> +	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
> +		spin_lock_irqsave(&sub->lock, flags);
> +		sub->active = true;
> +		spin_unlock_irqrestore(&sub->lock, flags);
> +		return 0;
> +
> +	case SNDRV_PCM_TRIGGER_STOP:
> +	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
> +		spin_lock_irqsave(&sub->lock, flags);
> +		sub->active = false;
> +		spin_unlock_irqrestore(&sub->lock, flags);
> +		return 0;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static snd_pcm_uframes_t hiface_pcm_pointer(struct snd_pcm_substream *alsa_sub)
> +{
> +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	unsigned long flags;
> +	snd_pcm_uframes_t dma_offset;
> +
> +	if (rt->panic || !sub)
> +		return SNDRV_PCM_STATE_XRUN;
> +
> +	spin_lock_irqsave(&sub->lock, flags);
> +	dma_offset = sub->dma_off;
> +	spin_unlock_irqrestore(&sub->lock, flags);
> +	return bytes_to_frames(alsa_sub->runtime, dma_offset);
> +}
> +
> +static struct snd_pcm_ops pcm_ops = {
> +	.open = hiface_pcm_open,
> +	.close = hiface_pcm_close,
> +	.ioctl = snd_pcm_lib_ioctl,
> +	.hw_params = hiface_pcm_hw_params,
> +	.hw_free = hiface_pcm_hw_free,
> +	.prepare = hiface_pcm_prepare,
> +	.trigger = hiface_pcm_trigger,
> +	.pointer = hiface_pcm_pointer,
> +};
> +
> +static int hiface_pcm_init_urb(struct pcm_urb *urb,
> +			       struct hiface_chip *chip,
> +			       unsigned int ep,
> +			       void (*handler)(struct urb *))
> +{
> +	urb->chip = chip;
> +	usb_init_urb(&urb->instance);
> +
> +	urb->buffer = kzalloc(PCM_PACKET_SIZE, GFP_KERNEL);
> +	if (!urb->buffer)
> +		return -ENOMEM;
> +
> +	usb_fill_bulk_urb(&urb->instance, chip->dev,
> +			  usb_sndbulkpipe(chip->dev, ep), (void *)urb->buffer,
> +			  PCM_PACKET_SIZE, handler, urb);
> +	init_usb_anchor(&urb->submitted);
> +
> +	return 0;
> +}
> +
> +void hiface_pcm_abort(struct hiface_chip *chip)
> +{
> +	struct pcm_runtime *rt = chip->pcm;
> +
> +	if (rt) {
> +		rt->panic = true;
> +
> +		if (rt->playback.instance) {
> +			snd_pcm_stream_lock_irq(rt->playback.instance);
> +			snd_pcm_stop(rt->playback.instance,
> +					SNDRV_PCM_STATE_XRUN);
> +			snd_pcm_stream_unlock_irq(rt->playback.instance);
> +		}
> +		mutex_lock(&rt->stream_mutex);
> +		hiface_pcm_stream_stop(rt);
> +		mutex_unlock(&rt->stream_mutex);
> +	}
> +}
> +
> +static void hiface_pcm_destroy(struct hiface_chip *chip)
> +{
> +	struct pcm_runtime *rt = chip->pcm;
> +	int i;
> +
> +	for (i = 0; i < PCM_N_URBS; i++)
> +		kfree(rt->out_urbs[i].buffer);
> +
> +	kfree(chip->pcm);
> +	chip->pcm = NULL;
> +}
> +
> +static void hiface_pcm_free(struct snd_pcm *pcm)
> +{
> +	struct pcm_runtime *rt = pcm->private_data;
> +
> +	pr_debug("%s: called.\n", __func__);
> +
> +	if (rt)
> +		hiface_pcm_destroy(rt->chip);
> +}
> +
> +int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq)
> +{
> +	int i;
> +	int ret;
> +	struct snd_pcm *pcm;
> +	struct pcm_runtime *rt;
> +
> +	rt = kzalloc(sizeof(*rt), GFP_KERNEL);
> +	if (!rt)
> +		return -ENOMEM;
> +
> +	rt->chip = chip;
> +	rt->stream_state = STREAM_DISABLED;
> +	if (extra_freq)
> +		rt->extra_freq = 1;
> +
> +	init_waitqueue_head(&rt->stream_wait_queue);
> +	mutex_init(&rt->stream_mutex);
> +	spin_lock_init(&rt->playback.lock);
> +
> +	for (i = 0; i < PCM_N_URBS; i++)
> +		hiface_pcm_init_urb(&rt->out_urbs[i], chip, OUT_EP,
> +				hiface_pcm_out_urb_handler);
> +
> +	ret = snd_pcm_new(chip->card, "USB-SPDIF Audio", 0, 1, 0, &pcm);
> +	if (ret < 0) {
> +		kfree(rt);
> +		pr_err("Cannot create pcm instance\n");
> +		return ret;
> +	}
> +
> +	pcm->private_data = rt;
> +	pcm->private_free = hiface_pcm_free;
> +
> +	strcpy(pcm->name, "USB-SPDIF Audio");
> +	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops);
> +
> +	snd_pcm_lib_preallocate_pages_for_all(pcm,
> +					SNDRV_DMA_TYPE_CONTINUOUS,
> +					snd_dma_continuous_data(GFP_KERNEL),
> +					PCM_BUFFER_SIZE, PCM_BUFFER_SIZE);
> +	rt->instance = pcm;
> +
> +	chip->pcm = rt;
> +	return 0;
> +}
> 

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

* Re: [PATCH v2] Add M2Tech hiFace USB-SPDIF driver
  2013-04-20 20:15   ` Daniel Mack
@ 2013-04-22  7:40     ` Pavel Hofman
       [not found]       ` <5174F560.8050502@m2tech.biz>
  2013-04-22  9:14     ` Antonio Ospite
  1 sibling, 1 reply; 25+ messages in thread
From: Pavel Hofman @ 2013-04-22  7:40 UTC (permalink / raw)
  To: Daniel Mack
  Cc: alsa-devel, Takashi Iwai, Antonio Ospite, Clemens Ladisch,
	fchecconi, Pietro Cipriano, alberto, Michael Trimarchi



On 20.4.2013 22:15, Daniel Mack wrote:
> On 13.02.2013 18:11, Antonio Ospite wrote:
>> Add driver for M2Tech hiFace USB-SPDIF interface and compatible devices.
>>
>> M2Tech hiFace and compatible devices offer a Hi-End S/PDIF Output
>> Interface, see http://www.m2tech.biz/hiface.html
> 
> Just curious: is there a new version of this patch set coming?

I have read users report large latencies (unable to watch movies) of
hiFace1 in OSX while reportedly they have not identified any major
latency in windows. Perhaps the fixed PCM_PACKET_SIZE
https://github.com/panicking/snd-usb-asyncaudio/blob/master/pcm.c#L25
limiting the period size
https://github.com/panicking/snd-usb-asyncaudio/blob/master/pcm.c#L98
does not have to be a single relatively large value?

Thanks,

Pavel.

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

* Re: [PATCH v2] Add M2Tech hiFace USB-SPDIF driver
       [not found]       ` <5174F560.8050502@m2tech.biz>
@ 2013-04-22  8:37         ` Pavel Hofman
  0 siblings, 0 replies; 25+ messages in thread
From: Pavel Hofman @ 2013-04-22  8:37 UTC (permalink / raw)
  To: Pietro Cipriano
  Cc: alsa-devel, Takashi Iwai, Antonio Ospite, Clemens Ladisch,
	Daniel Mack, fchecconi, alberto, Michael Trimarchi



On 22.4.2013 10:31, Pietro Cipriano wrote:
> Il 22/04/2013 09:40, Pavel Hofman ha scritto:
>>
>>
>> On 20.4.2013 22:15, Daniel Mack wrote:
>>> On 13.02.2013 18:11, Antonio Ospite wrote:
>>>> Add driver for M2Tech hiFace USB-SPDIF interface and compatible
>>>> devices.
>>>>
>>>> M2Tech hiFace and compatible devices offer a Hi-End S/PDIF Output
>>>> Interface, see http://www.m2tech.biz/hiface.html
>>>
>>> Just curious: is there a new version of this patch set coming?
>>
>> I have read users report large latencies (unable to watch movies) of
>> hiFace1 in OSX while reportedly they have not identified any major
>> latency in windows. Perhaps the fixed PCM_PACKET_SIZE
> The issue is related to a no controllable buffer dimension, which
> depends by Audio Core thus present only on OSx. A easy workaround is of
> to set up a higher output sampling frequency, for instance at 192KHz the
> delay is no more perceptible, obviously the DAC must be compliant with
> this setting.
> 
> Pietro

Thanks for very fast reponse. So is it possible to reduce the period
size in the linux driver? Right now if I calculate correctly it is 4096
bytes / 32bits / 2 channels / 48000 Hz = 10 ms.

Thanks,

Pavel.

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

* Re: [PATCH v2] Add M2Tech hiFace USB-SPDIF driver
  2013-04-20 20:15   ` Daniel Mack
  2013-04-22  7:40     ` Pavel Hofman
@ 2013-04-22  9:14     ` Antonio Ospite
  1 sibling, 0 replies; 25+ messages in thread
From: Antonio Ospite @ 2013-04-22  9:14 UTC (permalink / raw)
  To: Daniel Mack
  Cc: alsa-devel, patch, Takashi Iwai, Antonio Ospite, Clemens Ladisch,
	fchecconi, Pietro Cipriano, alberto, Michael Trimarchi

On Sat, 20 Apr 2013 22:15:40 +0200
Daniel Mack <zonque@gmail.com> wrote:

> On 13.02.2013 18:11, Antonio Ospite wrote:
> > Add driver for M2Tech hiFace USB-SPDIF interface and compatible devices.
> > 
> > M2Tech hiFace and compatible devices offer a Hi-End S/PDIF Output
> > Interface, see http://www.m2tech.biz/hiface.html
> 
> Just curious: is there a new version of this patch set coming?
> 

Hi Daniel,

I hope to find the time to send it in the next few days.

Regards,
   Antonio

-- 
Antonio Ospite
http://ao2.it

A: Because it messes up the order in which people normally read text.
   See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?

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

* Re: [PATCH v2] Add M2Tech hiFace USB-SPDIF driver
  2013-02-22 16:12   ` Daniel Mack
  2013-02-22 16:18     ` Takashi Iwai
@ 2013-04-28 20:59     ` Antonio Ospite
  1 sibling, 0 replies; 25+ messages in thread
From: Antonio Ospite @ 2013-04-28 20:59 UTC (permalink / raw)
  To: Daniel Mack
  Cc: alsa-devel, patch, Takashi Iwai, Antonio Ospite, Clemens Ladisch,
	fchecconi, Pietro Cipriano, alberto, Michael Trimarchi

On Fri, 22 Feb 2013 17:12:36 +0100
Daniel Mack <zonque@gmail.com> wrote:

> Hi Antonio,
> 
> a couple of more things that I stumbled over.
> 

Thanks, some comments inlined below.

> 
> 
> On 13.02.2013 18:11, Antonio Ospite wrote:
> > Add driver for M2Tech hiFace USB-SPDIF interface and compatible devices.
> > 
> > M2Tech hiFace and compatible devices offer a Hi-End S/PDIF Output
> > Interface, see http://www.m2tech.biz/hiface.html
> > 
> > The supported products are:
> > 
> >   * M2Tech Young
> >   * M2Tech hiFace
> >   * M2Tech North Star
> >   * M2Tech W4S Young
> >   * M2Tech Corrson
> >   * M2Tech AUDIA
> >   * M2Tech SL Audio
> >   * M2Tech Empirical
> >   * M2Tech Rockna
> >   * M2Tech Pathos
> >   * M2Tech Metronome
> >   * M2Tech CAD
> >   * M2Tech Audio Esclusive
> >   * M2Tech Rotel
> >   * M2Tech Eeaudio
> >   * The Chord Company CHORD
> >   * AVA Group A/S Vitus
> > 
> > Signed-off-by: Antonio Ospite <ao2@amarulasolutions.com>
> > ---
> > 
> > Changes since v1:
> > 
> >   * Change the first sentence of the Kconfig entry into "Select this..."
> >   * Remove a useless sentence from the Kconfig entry
> >   * Don't set alsa_rt->hw.rates in hiface_pcm_open()
> >   * Style cleanup, no braces needed in single statement conditional
> >   * Remove the rate field from pcm_runtime
> >   * Use the hiFace name with the lowercase 'h' everywhere
> >   * List actually supported devices in MODULE_SUPPORTED_DEVICE()
> >   * Cosmetics, align values in the rates array
> >   * Use an explicit switch instead of the rate_value array in
> >     hiface_pcm_set_rate()
> >   * Use usb_make_path() when building card->longname
> >   * Use again the implicit mechanism to allocate the card private data
> >   * Downgrade a pr_info to pr_debug in hiface_chip_probe()
> >   * Make device_table const
> >   * Rename PCM_MAX_PACKET_SIZE to PCM_PACKET_SIZE
> >   * Rename MAX_BUFSIZE to PCM_BUFFER_SIZE
> >   * Cosmetics, align symbolic constant values
> >   * Add SNDRV_PCM_RATE_KNOT only when needed
> >   * Declare memcpy_swahw32() as static
> >   * Protect snd_pcm_stop() with snd_pcm_stream_lock_irq()
> >   * Make hiface_pcm_playback() not returning anything
> >   * Move the period elapsed check into hiface_pcm_playback()
> >   * Handle the case of failing URBs in hiface_pcm_out_urb_handler()
> >   * Fix a couple of checkpatch.pl issues
> > 
> > The incremental changes can be seen individually at
> > https://github.com/panicking/snd-usb-asyncaudio/commits/master
> > Commits from Feb. 10th and later.
> > 
> > Thanks,
> >    Antonio
> > 
> >  sound/usb/Kconfig         |   31 +++
> >  sound/usb/Makefile        |    2 +-
> >  sound/usb/hiface/Makefile |    2 +
> >  sound/usb/hiface/chip.h   |   34 +++
> >  sound/usb/hiface/chip.c   |  329 +++++++++++++++++++++++
> >  sound/usb/hiface/pcm.h    |   24 ++
> >  sound/usb/hiface/pcm.c    |  638 +++++++++++++++++++++++++++++++++++++++++++++
> >  7 files changed, 1059 insertions(+), 1 deletion(-)
> >  create mode 100644 sound/usb/hiface/Makefile
> >  create mode 100644 sound/usb/hiface/chip.h
> >  create mode 100644 sound/usb/hiface/chip.c
> >  create mode 100644 sound/usb/hiface/pcm.h
> >  create mode 100644 sound/usb/hiface/pcm.c
> > 
> > diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig
> > index 225dfd7..de9408b 100644
> > --- a/sound/usb/Kconfig
> > +++ b/sound/usb/Kconfig
> > @@ -115,5 +115,36 @@ config SND_USB_6FIRE
> >            and further help can be found at
> >            http://sixfireusb.sourceforge.net
> >  
> > +config SND_USB_HIFACE
> > +        tristate "M2Tech hiFace USB-SPDIF driver"
> > +        select SND_PCM
> > +        help
> > +	  Select this option to include support for M2Tech hiFace USB-SPDIF
> > +	  interface.
> > +
> > +	  This driver supports the original M2Tech hiFace and some other
> > +	  compatible devices. The supported products are:
> > +
> > +	    * M2Tech Young
> > +	    * M2Tech hiFace
> > +	    * M2Tech North Star
> > +	    * M2Tech W4S Young
> > +	    * M2Tech Corrson
> > +	    * M2Tech AUDIA
> > +	    * M2Tech SL Audio
> > +	    * M2Tech Empirical
> > +	    * M2Tech Rockna
> > +	    * M2Tech Pathos
> > +	    * M2Tech Metronome
> > +	    * M2Tech CAD
> > +	    * M2Tech Audio Esclusive
> > +	    * M2Tech Rotel
> > +	    * M2Tech Eeaudio
> > +	    * The Chord Company CHORD
> > +	    * AVA Group A/S Vitus
> > +
> > +	  To compile this driver as a module, choose M here: the module
> > +	  will be called snd-usb-hiface.
> > +
> >  endif	# SND_USB
> >  
> > diff --git a/sound/usb/Makefile b/sound/usb/Makefile
> > index ac256dc..abe668f 100644
> > --- a/sound/usb/Makefile
> > +++ b/sound/usb/Makefile
> > @@ -23,4 +23,4 @@ obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o
> >  obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o
> >  obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o
> >  
> > -obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/
> > +obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/
> > diff --git a/sound/usb/hiface/Makefile b/sound/usb/hiface/Makefile
> > new file mode 100644
> > index 0000000..463b136
> > --- /dev/null
> > +++ b/sound/usb/hiface/Makefile
> > @@ -0,0 +1,2 @@
> > +snd-usb-hiface-objs := chip.o pcm.o
> > +obj-$(CONFIG_SND_USB_HIFACE) += snd-usb-hiface.o
> > diff --git a/sound/usb/hiface/chip.h b/sound/usb/hiface/chip.h
> > new file mode 100644
> > index 0000000..cd615a0
> > --- /dev/null
> > +++ b/sound/usb/hiface/chip.h
> > @@ -0,0 +1,34 @@
> > +/*
> > + * Linux driver for M2Tech hiFace compatible devices
> > + *
> > + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> > + *
> > + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> > + *           Antonio Ospite <ao2@amarulasolutions.com>
> > + *
> > + * The driver is based on the work done in TerraTec DMX 6Fire USB
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License as published by
> > + * the Free Software Foundation; either version 2 of the License, or
> > + * (at your option) any later version.
> > + */
> > +
> > +#ifndef HIFACE_CHIP_H
> > +#define HIFACE_CHIP_H
> > +
> > +#include <linux/usb.h>
> > +#include <sound/core.h>
> > +
> > +struct pcm_runtime;
> > +
> > +struct hiface_chip {
> > +	struct usb_device *dev;
> > +	struct snd_card *card;
> > +	int intf_count; /* number of registered interfaces */
> > +	int index; /* index in module parameter arrays */
> > +	bool shutdown;
> > +
> > +	struct pcm_runtime *pcm;
> > +};
> > +#endif /* HIFACE_CHIP_H */
> > diff --git a/sound/usb/hiface/chip.c b/sound/usb/hiface/chip.c
> > new file mode 100644
> > index 0000000..f1293a5
> > --- /dev/null
> > +++ b/sound/usb/hiface/chip.c
> > @@ -0,0 +1,329 @@
> > +/*
> > + * Linux driver for M2Tech hiFace compatible devices
> > + *
> > + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> > + *
> > + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> > + *           Antonio Ospite <ao2@amarulasolutions.com>
> > + *
> > + * The driver is based on the work done in TerraTec DMX 6Fire USB
> > + *
> > + * This program is free software; you can redistribute it and/or modify
> > + * it under the terms of the GNU General Public License as published by
> > + * the Free Software Foundation; either version 2 of the License, or
> > + * (at your option) any later version.
> > + */
> > +
> > +#include <linux/module.h>
> > +#include <linux/slab.h>
> > +#include <sound/initval.h>
> > +
> > +#include "chip.h"
> > +#include "pcm.h"
> > +
> > +MODULE_AUTHOR("Michael Trimarchi <michael@amarulasolutions.com>");
> > +MODULE_AUTHOR("Antonio Ospite <ao2@amarulasolutions.com>");
> > +MODULE_DESCRIPTION("M2Tech hiFace USB-SPDIF audio driver");
> > +MODULE_LICENSE("GPL v2");
> > +MODULE_SUPPORTED_DEVICE("{{M2Tech,Young},"
> > +			 "{M2Tech,hiFace},"
> > +			 "{M2Tech,North Star},"
> > +			 "{M2Tech,W4S Young},"
> > +			 "{M2Tech,Corrson},"
> > +			 "{M2Tech,AUDIA},"
> > +			 "{M2Tech,SL Audio},"
> > +			 "{M2Tech,Empirical},"
> > +			 "{M2Tech,Rockna},"
> > +			 "{M2Tech,Pathos},"
> > +			 "{M2Tech,Metronome},"
> > +			 "{M2Tech,CAD},"
> > +			 "{M2Tech,Audio Esclusive},"
> > +			 "{M2Tech,Rotel},"
> > +			 "{M2Tech,Eeaudio},"
> > +			 "{The Chord Company,CHORD},"
> > +			 "{AVA Group A/S,Vitus}}");
> > +
> > +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
> > +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */
> > +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
> > +
> > +#define DRIVER_NAME "snd-usb-hiface"
> > +#define CARD_NAME "hiFace"
> > +
> > +module_param_array(index, int, NULL, 0444);
> > +MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
> > +module_param_array(id, charp, NULL, 0444);
> > +MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
> > +module_param_array(enable, bool, NULL, 0444);
> > +MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
> > +
> > +static struct hiface_chip *chips[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
> > +
> > +static DEFINE_MUTEX(register_mutex);
> > +
> > +struct hiface_vendor_quirk {
> > +	const char *device_name;
> > +	u8 extra_freq;
> > +};
> > +
> > +static int hiface_chip_create(struct usb_device *device, int idx,
> > +			      const struct hiface_vendor_quirk *quirk,
> > +			      struct hiface_chip **rchip)
> > +{
> > +	struct snd_card *card = NULL;
> > +	struct hiface_chip *chip;
> > +	int ret;
> > +	int len;
> > +
> > +	*rchip = NULL;
> > +
> > +	/* if we are here, card can be registered in alsa. */
> > +	ret = snd_card_create(index[idx], id[idx], THIS_MODULE, sizeof(*chip), &card);
> > +	if (ret < 0) {
> > +		snd_printk(KERN_ERR "cannot create alsa card.\n");
> > +		return ret;
> > +	}
> > +
> > +	strcpy(card->driver, DRIVER_NAME);
> > +
> > +	if (quirk && quirk->device_name)
> > +		strcpy(card->shortname, quirk->device_name);
> > +	else
> > +		strcpy(card->shortname, "M2Tech generic audio");
> 
> Use strncpy() or strlcpy() please, even if you're sure.
>

Will do.

> > +
> > +	strlcat(card->longname, card->shortname, sizeof(card->longname));
> > +	len = strlcat(card->longname, " at ", sizeof(card->longname));
> > +	if (len < sizeof(card->longname))
> > +		usb_make_path(device, card->longname + len,
> > +			      sizeof(card->longname) - len);
> > +
> > +	chip = card->private_data;
> > +	chip->dev = device;
> > +	chip->index = idx;
> > +	chip->card = card;
> > +
> > +	*rchip = chip;
> > +	return 0;
> > +}
> > +
> > +static int hiface_chip_probe(struct usb_interface *intf,
> > +			     const struct usb_device_id *usb_id)
> > +{
> > +	const struct hiface_vendor_quirk *quirk = (struct hiface_vendor_quirk *)usb_id->driver_info;
> > +	int ret;
> > +	int i;
> > +	struct hiface_chip *chip;
> > +	struct usb_device *device = interface_to_usbdev(intf);
> > +
> > +	pr_debug("Probe " DRIVER_NAME " driver.\n");
> 
> As Clemens said, you should drop this.
>

OK, the USB enumeration messages are enough, I see.

> > +
> > +	ret = usb_set_interface(device, 0, 0);
> > +	if (ret != 0) {
> > +		snd_printk(KERN_ERR "can't set first interface for " CARD_NAME " device.\n");
> > +		return -EIO;
> > +	}
> > +
> > +	/* check whether the card is already registered */
> > +	chip = NULL;
> > +	mutex_lock(&register_mutex);
> > +	for (i = 0; i < SNDRV_CARDS; i++) {
> > +		if (chips[i] && chips[i]->dev == device) {
> > +			if (chips[i]->shutdown) {
> > +				snd_printk(KERN_ERR CARD_NAME " device is in the shutdown state, cannot create a card instance\n");
> > +				ret = -ENODEV;
> > +				goto err;
> > +			}
> > +			chip = chips[i];
> > +			break;
> > +		}
> > +	}
> > +	if (!chip) {
> > +		/* it's a fresh one.
> > +		 * now look for an empty slot and create a new card instance
> > +		 */
> > +		for (i = 0; i < SNDRV_CARDS; i++)
> > +			if (enable[i] && !chips[i]) {
> > +				ret = hiface_chip_create(device, i, quirk,
> > +							 &chip);
> > +				if (ret < 0)
> > +					goto err;
> > +
> > +				snd_card_set_dev(chip->card, &intf->dev);
> > +				break;
> > +			}
> > +		if (!chip) {
> > +			snd_printk(KERN_ERR "no available " CARD_NAME " audio device\n");
> > +			ret = -ENODEV;
> > +			goto err;
> > +		}
> > +	}
> 
> The generic driver does the above in order to group multiple interfaces
> on one device into one snd_card. It's needed because it is probed on
> interface level.
> 
> Your driver seems to operate on device-level only, so you can drop this.
>

For the first loop I agree, it's not strictly necessary to check if the
device was registered already if there is only one "sound card" per
device, but the second loop is still needed in some form in order to
respect the "enable" option. I'll simplify that part, thanks.

BTW looking at how caiaq/device.c does it, I noticed that the
snd_card_used[] array is only read but never written, unless I am
missing anything :)

> > +	ret = hiface_pcm_init(chip, quirk ? quirk->extra_freq : 0);
> > +	if (ret < 0)
> > +		goto err_chip_destroy;
> > +
> > +	ret = snd_card_register(chip->card);
> > +	if (ret < 0) {
> > +		snd_printk(KERN_ERR "cannot register " CARD_NAME " card\n");
> 
> Not sure, but I think it might be better to use dev_{err,info,dgb} here,
> so the logging can be grouped to individual devices. Takashi, any
> opinion on that?
>

Will do, thanks.

Regards,
   Antonio

-- 
Antonio Ospite
http://ao2.it

A: Because it messes up the order in which people normally read text.
   See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?

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

* Re: [PATCH v2] Add M2Tech hiFace USB-SPDIF driver
  2013-02-22 12:53   ` Takashi Iwai
@ 2013-04-28 21:09     ` Antonio Ospite
  2013-05-29 12:24       ` Takashi Iwai
  0 siblings, 1 reply; 25+ messages in thread
From: Antonio Ospite @ 2013-04-28 21:09 UTC (permalink / raw)
  To: Takashi Iwai
  Cc: alsa-devel, patch, Antonio Ospite, Clemens Ladisch, fchecconi,
	Pietro Cipriano, alberto, Michael Trimarchi

On Fri, 22 Feb 2013 13:53:06 +0100
Takashi Iwai <tiwai@suse.de> wrote:

Hi Takashi, I know, it's a huge delay.

> At Wed, 13 Feb 2013 18:11:45 +0100,
> Antonio Ospite wrote:
> > new file mode 100644
> > index 0000000..d0c3c58
> > --- /dev/null
> > +++ b/sound/usb/hiface/pcm.c
> > @@ -0,0 +1,638 @@
> ....
> > +struct pcm_runtime {
> > +	struct hiface_chip *chip;
> > +	struct snd_pcm *instance;
> > +
> > +	struct pcm_substream playback;
> > +	bool panic; /* if set driver won't do anymore pcm on device */
> 
> Once when rt->panic is set, how can you recover?
> I see no code resetting rt->panic.
>

The logic behind this is that rt->panic is set to true if there was some
USB errors (or a disconnect), so a USB replug is expected anyways;
rt->panic will be initialized in the hiface_pcm_init() to 0 with a
kzalloc().

> > +/* The hardware wants word-swapped 32-bit values */
> > +static void memcpy_swahw32(u8 *dest, u8 *src, unsigned int n)
> > +{
> > +	unsigned int i;
> > +
> > +	for (i = 0; i < n / 4; i++)
> > +		((u32 *)dest)[i] = swahw32(((u32 *)src)[i]);
> > +}
> > +
> > +/* call with substream locked */
> > +/* returns true if a period elapsed */
> > +static bool hiface_pcm_playback(struct pcm_substream *sub,
> > +		struct pcm_urb *urb)
> > +{
> > +	struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
> > +	u8 *source;
> > +	unsigned int pcm_buffer_size;
> > +
> > +	WARN_ON(alsa_rt->format != SNDRV_PCM_FORMAT_S32_LE);
> 
> Can't we use simply SNDRV_PCM_FORMAT_S32_BE without the byte swapping
> above?

The code above calls swahw32() it swaps words, it's some kind of mixed
endian format which the device expects, maybe it's because it handles
16 bits at time internally, we don't know for sure, but this is how it
is.

> 
> > +static void hiface_pcm_out_urb_handler(struct urb *usb_urb)
> > +{
> > +	struct pcm_urb *out_urb = usb_urb->context;
> > +	struct pcm_runtime *rt = out_urb->chip->pcm;
> > +	struct pcm_substream *sub;
> > +	bool do_period_elapsed = false;
> > +	unsigned long flags;
> > +	int ret;
> > +
> > +	pr_debug("%s: called.\n", __func__);
> > +
> > +	if (rt->panic || rt->stream_state == STREAM_STOPPING)
> > +		return;
> > +
> > +	if (unlikely(usb_urb->status == -ENOENT ||	/* unlinked */
> > +		     usb_urb->status == -ENODEV ||	/* device removed */
> > +		     usb_urb->status == -ECONNRESET ||	/* unlinked */
> > +		     usb_urb->status == -ESHUTDOWN)) {	/* device disabled */
> > +		goto out_fail;
> > +	}
> > +
> > +	if (rt->stream_state == STREAM_STARTING) {
> > +		rt->stream_wait_cond = true;
> > +		wake_up(&rt->stream_wait_queue);
> > +	}
> > +
> > +	/* now send our playback data (if a free out urb was found) */
> > +	sub = &rt->playback;
> > +	spin_lock_irqsave(&sub->lock, flags);
> > +	if (sub->active)
> > +		do_period_elapsed = hiface_pcm_playback(sub, out_urb);
> > +	else
> > +		memset(out_urb->buffer, 0, PCM_PACKET_SIZE);
> > +
> > +	spin_unlock_irqrestore(&sub->lock, flags);
> > +
> > +	if (do_period_elapsed)
> > +		snd_pcm_period_elapsed(sub->instance);
> 
> This looks like a small open race.  sub->instance might be set to NULL
> after the unlock above?
>

Let's take a look at hiface_pcm_close():
 * sub->instance is set to NULL
 * the URBs are killed _after_ sub->instance is set to NULL

So if a URB is completed between these actions
hiface_pcm_out_urb_handler() gets called and there could be a problem.

If in hiface_pcm_close() I kill the URBs first and then set
sub->instance to NULL the race window gets closed, right?

> > +
> > +	ret = usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
> > +	if (ret < 0)
> > +		goto out_fail;
> 
> Maybe better to check the state again before resubmission, e.g. when
> XRUN is detected in the pointer callback.
>

I am not sure I understand what you mean here, should I check the
state of ALSA PCM stream or should I keep another state to avoid
transmissions on overruns?

Maybe you mean that I need to check that:
 sub->instance->runtime->status->state == SNDRV_PCM_STATE_RUNNING
?

> > +
> > +	return;
> > +
> > +out_fail:
> > +	rt->panic = true;
> > +}
> > +
> > +static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub)
> > +{
> > +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> > +	struct pcm_substream *sub = NULL;
> > +	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
> > +	int ret;
> > +
> > +	pr_debug("%s: called.\n", __func__);
> > +
> > +	if (rt->panic)
> > +		return -EPIPE;
> > +
> > +	mutex_lock(&rt->stream_mutex);
> > +	alsa_rt->hw = pcm_hw;
> > +
> > +	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
> > +		sub = &rt->playback;
> > +
> > +	if (!sub) {
> > +		mutex_unlock(&rt->stream_mutex);
> > +		pr_err("Invalid stream type\n");
> > +		return -EINVAL;
> > +	}
> > +
> > +	if (rt->extra_freq) {
> > +		alsa_rt->hw.rates |= SNDRV_PCM_RATE_KNOT;
> > +		alsa_rt->hw.rate_max = 384000;
> > +
> > +		/* explicit constraints needed as we added SNDRV_PCM_RATE_KNOT */
> > +		ret = snd_pcm_hw_constraint_list(alsa_sub->runtime, 0,
> > +						 SNDRV_PCM_HW_PARAM_RATE,
> > +						 &constraints_extra_rates);
> > +		if (ret < 0) {
> > +			mutex_unlock(&rt->stream_mutex);
> > +			return ret;
> > +		}
> > +	}
> > +
> > +	sub->instance = alsa_sub;
> > +	sub->active = false;
> > +	mutex_unlock(&rt->stream_mutex);
> > +	return 0;
> > +}
> > +
> > +static int hiface_pcm_close(struct snd_pcm_substream *alsa_sub)
> > +{
> > +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> > +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> > +	unsigned long flags;
> > +
> > +	if (rt->panic)
> > +		return 0;
> > +
> > +	pr_debug("%s: called.\n", __func__);
> > +
> > +	mutex_lock(&rt->stream_mutex);
> > +	if (sub) {
> > +		/* deactivate substream */
> > +		spin_lock_irqsave(&sub->lock, flags);
> > +		sub->instance = NULL;
> > +		sub->active = false;
> > +		spin_unlock_irqrestore(&sub->lock, flags);
> > +
> > +		/* all substreams closed? if so, stop streaming */
> > +		if (!rt->playback.instance)
> > +			hiface_pcm_stream_stop(rt);
> 
> Can this condition be ever false...?
>

You mean that if we got to the .close() callback then the .open() one
succeeded and so we have a valid snd_pcm_substream? I guess that's
right and I can skip the check.

The driver was based on a driver which handled multiple streams so
something must have slipped during the cleanup.

BTW, I think that cleaning up this hiFace driver further could be useful
even after mainline inclusion, it's a fairly simple driver which can be
used as an example driver, but my inexperience with ALSA, and audio in
general, was holding me back a bit to revolutionize it's initial
structure. Help is always appreciated by more experienced people.

> > +static int hiface_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
> > +{
> > +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> > +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> > +	unsigned long flags;
> 
> For trigger callback, you need no irqsave but simply use
> spin_lock_irq().
>

OK, will do, thanks.

> > +void hiface_pcm_abort(struct hiface_chip *chip)
> > +{
> > +	struct pcm_runtime *rt = chip->pcm;
> > +
> > +	if (rt) {
> > +		rt->panic = true;
> > +
> > +		if (rt->playback.instance) {
> > +			snd_pcm_stream_lock_irq(rt->playback.instance);
> > +			snd_pcm_stop(rt->playback.instance,
> > +					SNDRV_PCM_STATE_XRUN);
> > +			snd_pcm_stream_unlock_irq(rt->playback.instance);
> > +		}
> 
> Hmm... this is called after snd_card_disconnect(), thus it will reset
> the PCM stream state from DISCONNECTED to XRUN.  It doesn't sound
> right.
>

Mmh, some other USB drivers do that too in their abort, _after_
snd_card_disconnect() so they must be wrong too.
Can I just skip calling snd_pcm_stop() altogether since the PCM stream
state is in DISCONNECTED state already?

> > +int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq)
> > +{
> ....
> > +	snd_pcm_lib_preallocate_pages_for_all(pcm,
> > +					SNDRV_DMA_TYPE_CONTINUOUS,
> > +					snd_dma_continuous_data(GFP_KERNEL),
> > +					PCM_BUFFER_SIZE, PCM_BUFFER_SIZE);
> 
> Any reason not to use vmalloc buffer?
> 

I don't know, I just shamelessly copied from other drivers here, what'd
be the difference?

Thanks,
   Antonio

-- 
Antonio Ospite
http://ao2.it

A: Because it messes up the order in which people normally read text.
   See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?

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

* Re: [PATCH v2] Add M2Tech hiFace USB-SPDIF driver
  2013-04-28 21:09     ` Antonio Ospite
@ 2013-05-29 12:24       ` Takashi Iwai
  2013-06-03 21:40         ` Antonio Ospite
  0 siblings, 1 reply; 25+ messages in thread
From: Takashi Iwai @ 2013-05-29 12:24 UTC (permalink / raw)
  To: Antonio Ospite
  Cc: alsa-devel, Antonio Ospite, Clemens Ladisch, fchecconi,
	Pietro Cipriano, alberto, Michael Trimarchi

At Sun, 28 Apr 2013 23:09:59 +0200,
Antonio Ospite wrote:
> 
> On Fri, 22 Feb 2013 13:53:06 +0100
> Takashi Iwai <tiwai@suse.de> wrote:
> 
> Hi Takashi, I know, it's a huge delay.
> 
> > At Wed, 13 Feb 2013 18:11:45 +0100,
> > Antonio Ospite wrote:
> > > new file mode 100644
> > > index 0000000..d0c3c58
> > > --- /dev/null
> > > +++ b/sound/usb/hiface/pcm.c
> > > @@ -0,0 +1,638 @@
> > ....
> > > +struct pcm_runtime {
> > > +	struct hiface_chip *chip;
> > > +	struct snd_pcm *instance;
> > > +
> > > +	struct pcm_substream playback;
> > > +	bool panic; /* if set driver won't do anymore pcm on device */
> > 
> > Once when rt->panic is set, how can you recover?
> > I see no code resetting rt->panic.
> >
> 
> The logic behind this is that rt->panic is set to true if there was some
> USB errors (or a disconnect), so a USB replug is expected anyways;
> rt->panic will be initialized in the hiface_pcm_init() to 0 with a
> kzalloc().

Hmm, it doesn't sound good.


> > > +/* The hardware wants word-swapped 32-bit values */
> > > +static void memcpy_swahw32(u8 *dest, u8 *src, unsigned int n)
> > > +{
> > > +	unsigned int i;
> > > +
> > > +	for (i = 0; i < n / 4; i++)
> > > +		((u32 *)dest)[i] = swahw32(((u32 *)src)[i]);
> > > +}
> > > +
> > > +/* call with substream locked */
> > > +/* returns true if a period elapsed */
> > > +static bool hiface_pcm_playback(struct pcm_substream *sub,
> > > +		struct pcm_urb *urb)
> > > +{
> > > +	struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
> > > +	u8 *source;
> > > +	unsigned int pcm_buffer_size;
> > > +
> > > +	WARN_ON(alsa_rt->format != SNDRV_PCM_FORMAT_S32_LE);
> > 
> > Can't we use simply SNDRV_PCM_FORMAT_S32_BE without the byte swapping
> > above?
> 
> The code above calls swahw32() it swaps words, it's some kind of mixed
> endian format which the device expects, maybe it's because it handles
> 16 bits at time internally, we don't know for sure, but this is how it
> is.

OK, then I read it wrongly.  In general, we don't want to do such
conversions in the driver code but it's too exotic, and maybe not
worth to add the new format in the common definition.


> > > +static void hiface_pcm_out_urb_handler(struct urb *usb_urb)
> > > +{
> > > +	struct pcm_urb *out_urb = usb_urb->context;
> > > +	struct pcm_runtime *rt = out_urb->chip->pcm;
> > > +	struct pcm_substream *sub;
> > > +	bool do_period_elapsed = false;
> > > +	unsigned long flags;
> > > +	int ret;
> > > +
> > > +	pr_debug("%s: called.\n", __func__);
> > > +
> > > +	if (rt->panic || rt->stream_state == STREAM_STOPPING)
> > > +		return;
> > > +
> > > +	if (unlikely(usb_urb->status == -ENOENT ||	/* unlinked */
> > > +		     usb_urb->status == -ENODEV ||	/* device removed */
> > > +		     usb_urb->status == -ECONNRESET ||	/* unlinked */
> > > +		     usb_urb->status == -ESHUTDOWN)) {	/* device disabled */
> > > +		goto out_fail;
> > > +	}
> > > +
> > > +	if (rt->stream_state == STREAM_STARTING) {
> > > +		rt->stream_wait_cond = true;
> > > +		wake_up(&rt->stream_wait_queue);
> > > +	}
> > > +
> > > +	/* now send our playback data (if a free out urb was found) */
> > > +	sub = &rt->playback;
> > > +	spin_lock_irqsave(&sub->lock, flags);
> > > +	if (sub->active)
> > > +		do_period_elapsed = hiface_pcm_playback(sub, out_urb);
> > > +	else
> > > +		memset(out_urb->buffer, 0, PCM_PACKET_SIZE);
> > > +
> > > +	spin_unlock_irqrestore(&sub->lock, flags);
> > > +
> > > +	if (do_period_elapsed)
> > > +		snd_pcm_period_elapsed(sub->instance);
> > 
> > This looks like a small open race.  sub->instance might be set to NULL
> > after the unlock above?
> >
> 
> Let's take a look at hiface_pcm_close():
>  * sub->instance is set to NULL
>  * the URBs are killed _after_ sub->instance is set to NULL
> 
> So if a URB is completed between these actions
> hiface_pcm_out_urb_handler() gets called and there could be a problem.
> 
> If in hiface_pcm_close() I kill the URBs first and then set
> sub->instance to NULL the race window gets closed, right?

Should be, yes.

> > > +
> > > +	ret = usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
> > > +	if (ret < 0)
> > > +		goto out_fail;
> > 
> > Maybe better to check the state again before resubmission, e.g. when
> > XRUN is detected in the pointer callback.
> >
> 
> I am not sure I understand what you mean here, should I check the
> state of ALSA PCM stream or should I keep another state to avoid
> transmissions on overruns?

ALSA state check should be enough for XRUN.

> Maybe you mean that I need to check that:
>  sub->instance->runtime->status->state == SNDRV_PCM_STATE_RUNNING
> ?

Yes, something like that.


> > > +
> > > +	return;
> > > +
> > > +out_fail:
> > > +	rt->panic = true;
> > > +}
> > > +
> > > +static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub)
> > > +{
> > > +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> > > +	struct pcm_substream *sub = NULL;
> > > +	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
> > > +	int ret;
> > > +
> > > +	pr_debug("%s: called.\n", __func__);
> > > +
> > > +	if (rt->panic)
> > > +		return -EPIPE;
> > > +
> > > +	mutex_lock(&rt->stream_mutex);
> > > +	alsa_rt->hw = pcm_hw;
> > > +
> > > +	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
> > > +		sub = &rt->playback;
> > > +
> > > +	if (!sub) {
> > > +		mutex_unlock(&rt->stream_mutex);
> > > +		pr_err("Invalid stream type\n");
> > > +		return -EINVAL;
> > > +	}
> > > +
> > > +	if (rt->extra_freq) {
> > > +		alsa_rt->hw.rates |= SNDRV_PCM_RATE_KNOT;
> > > +		alsa_rt->hw.rate_max = 384000;
> > > +
> > > +		/* explicit constraints needed as we added SNDRV_PCM_RATE_KNOT */
> > > +		ret = snd_pcm_hw_constraint_list(alsa_sub->runtime, 0,
> > > +						 SNDRV_PCM_HW_PARAM_RATE,
> > > +						 &constraints_extra_rates);
> > > +		if (ret < 0) {
> > > +			mutex_unlock(&rt->stream_mutex);
> > > +			return ret;
> > > +		}
> > > +	}
> > > +
> > > +	sub->instance = alsa_sub;
> > > +	sub->active = false;
> > > +	mutex_unlock(&rt->stream_mutex);
> > > +	return 0;
> > > +}
> > > +
> > > +static int hiface_pcm_close(struct snd_pcm_substream *alsa_sub)
> > > +{
> > > +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> > > +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> > > +	unsigned long flags;
> > > +
> > > +	if (rt->panic)
> > > +		return 0;
> > > +
> > > +	pr_debug("%s: called.\n", __func__);
> > > +
> > > +	mutex_lock(&rt->stream_mutex);
> > > +	if (sub) {
> > > +		/* deactivate substream */
> > > +		spin_lock_irqsave(&sub->lock, flags);
> > > +		sub->instance = NULL;
> > > +		sub->active = false;
> > > +		spin_unlock_irqrestore(&sub->lock, flags);
> > > +
> > > +		/* all substreams closed? if so, stop streaming */
> > > +		if (!rt->playback.instance)
> > > +			hiface_pcm_stream_stop(rt);
> > 
> > Can this condition be ever false...?
> >
> 
> You mean that if we got to the .close() callback then the .open() one
> succeeded and so we have a valid snd_pcm_substream? I guess that's
> right and I can skip the check.

What does stream_mutex protect?  I don't have any glance over the
whole code, and the code was so old, thus I don't remember at all...

> The driver was based on a driver which handled multiple streams so
> something must have slipped during the cleanup.
> 
> BTW, I think that cleaning up this hiFace driver further could be useful
> even after mainline inclusion, it's a fairly simple driver which can be
> used as an example driver, but my inexperience with ALSA, and audio in
> general, was holding me back a bit to revolutionize it's initial
> structure. Help is always appreciated by more experienced people.
> 
> > > +static int hiface_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
> > > +{
> > > +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> > > +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> > > +	unsigned long flags;
> > 
> > For trigger callback, you need no irqsave but simply use
> > spin_lock_irq().
> >
> 
> OK, will do, thanks.
> 
> > > +void hiface_pcm_abort(struct hiface_chip *chip)
> > > +{
> > > +	struct pcm_runtime *rt = chip->pcm;
> > > +
> > > +	if (rt) {
> > > +		rt->panic = true;
> > > +
> > > +		if (rt->playback.instance) {
> > > +			snd_pcm_stream_lock_irq(rt->playback.instance);
> > > +			snd_pcm_stop(rt->playback.instance,
> > > +					SNDRV_PCM_STATE_XRUN);
> > > +			snd_pcm_stream_unlock_irq(rt->playback.instance);
> > > +		}
> > 
> > Hmm... this is called after snd_card_disconnect(), thus it will reset
> > the PCM stream state from DISCONNECTED to XRUN.  It doesn't sound
> > right.
> >
> 
> Mmh, some other USB drivers do that too in their abort, _after_
> snd_card_disconnect() so they must be wrong too.

Which one?  The common usb-audio driver has the check beforehand.

> Can I just skip calling snd_pcm_stop() altogether since the PCM stream
> state is in DISCONNECTED state already?
> 
> > > +int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq)
> > > +{
> > ....
> > > +	snd_pcm_lib_preallocate_pages_for_all(pcm,
> > > +					SNDRV_DMA_TYPE_CONTINUOUS,
> > > +					snd_dma_continuous_data(GFP_KERNEL),
> > > +					PCM_BUFFER_SIZE, PCM_BUFFER_SIZE);
> > 
> > Any reason not to use vmalloc buffer?
> > 
> 
> I don't know, I just shamelessly copied from other drivers here, what'd
> be the difference?

A huge difference.  Imagine allocating a 1MB buffer.


thanks,

Takashi

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

* Re: [PATCH v2] Add M2Tech hiFace USB-SPDIF driver
  2013-05-29 12:24       ` Takashi Iwai
@ 2013-06-03 21:40         ` Antonio Ospite
  0 siblings, 0 replies; 25+ messages in thread
From: Antonio Ospite @ 2013-06-03 21:40 UTC (permalink / raw)
  To: Takashi Iwai
  Cc: alsa-devel, Antonio Ospite, Clemens Ladisch, fchecconi,
	Pietro Cipriano, alberto, Michael Trimarchi

On Wed, 29 May 2013 14:24:50 +0200
Takashi Iwai <tiwai@suse.de> wrote:

Hi again,

> At Sun, 28 Apr 2013 23:09:59 +0200,
> Antonio Ospite wrote:
> > 
> > On Fri, 22 Feb 2013 13:53:06 +0100
> > Takashi Iwai <tiwai@suse.de> wrote:
> > 
> > Hi Takashi, I know, it's a huge delay.
> > 
> > > At Wed, 13 Feb 2013 18:11:45 +0100,
> > > Antonio Ospite wrote:
> > > > new file mode 100644
> > > > index 0000000..d0c3c58
> > > > --- /dev/null
> > > > +++ b/sound/usb/hiface/pcm.c
> > > > @@ -0,0 +1,638 @@
> > > ....
> > > > +struct pcm_runtime {
> > > > +	struct hiface_chip *chip;
> > > > +	struct snd_pcm *instance;
> > > > +
> > > > +	struct pcm_substream playback;
> > > > +	bool panic; /* if set driver won't do anymore pcm on device */
> > > 
> > > Once when rt->panic is set, how can you recover?
> > > I see no code resetting rt->panic.
> > >
> > 
> > The logic behind this is that rt->panic is set to true if there was some
> > USB errors (or a disconnect), so a USB replug is expected anyways;
> > rt->panic will be initialized in the hiface_pcm_init() to 0 with a
> > kzalloc().
> 
> Hmm, it doesn't sound good.
> 

In some other drivers like caiaq the "panic" is a condition independent
from the USB communication itself.
In the 6fire driver instead the panic is triggered by the URB status
itself, so the scheme is more or less the same as in this hiFace driver,
which was based on the 6fire one.

I may very well skip the panic logic altogether if the assumption is
that the USB communication works, and if it does not it is
responsibility of the USB stack to report that.

> > > > +/* The hardware wants word-swapped 32-bit values */
> > > > +static void memcpy_swahw32(u8 *dest, u8 *src, unsigned int n)
> > > > +{
> > > > +	unsigned int i;
> > > > +
> > > > +	for (i = 0; i < n / 4; i++)
> > > > +		((u32 *)dest)[i] = swahw32(((u32 *)src)[i]);
> > > > +}
> > > > +
> > > > +/* call with substream locked */
> > > > +/* returns true if a period elapsed */
> > > > +static bool hiface_pcm_playback(struct pcm_substream *sub,
> > > > +		struct pcm_urb *urb)
> > > > +{
> > > > +	struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
> > > > +	u8 *source;
> > > > +	unsigned int pcm_buffer_size;
> > > > +
> > > > +	WARN_ON(alsa_rt->format != SNDRV_PCM_FORMAT_S32_LE);
> > > 
> > > Can't we use simply SNDRV_PCM_FORMAT_S32_BE without the byte swapping
> > > above?
> > 
> > The code above calls swahw32() it swaps words, it's some kind of mixed
> > endian format which the device expects, maybe it's because it handles
> > 16 bits at time internally, we don't know for sure, but this is how it
> > is.
> 
> OK, then I read it wrongly.  In general, we don't want to do such
> conversions in the driver code but it's too exotic, and maybe not
> worth to add the new format in the common definition.
>

Probably not worth it, I'll leave this as it is.

> > > > +static void hiface_pcm_out_urb_handler(struct urb *usb_urb)
> > > > +{
> > > > +	struct pcm_urb *out_urb = usb_urb->context;
> > > > +	struct pcm_runtime *rt = out_urb->chip->pcm;
> > > > +	struct pcm_substream *sub;
> > > > +	bool do_period_elapsed = false;
> > > > +	unsigned long flags;
> > > > +	int ret;
> > > > +
> > > > +	pr_debug("%s: called.\n", __func__);
> > > > +
> > > > +	if (rt->panic || rt->stream_state == STREAM_STOPPING)
> > > > +		return;
> > > > +
> > > > +	if (unlikely(usb_urb->status == -ENOENT ||	/* unlinked */
> > > > +		     usb_urb->status == -ENODEV ||	/* device removed */
> > > > +		     usb_urb->status == -ECONNRESET ||	/* unlinked */
> > > > +		     usb_urb->status == -ESHUTDOWN)) {	/* device disabled */
> > > > +		goto out_fail;
> > > > +	}
> > > > +
> > > > +	if (rt->stream_state == STREAM_STARTING) {
> > > > +		rt->stream_wait_cond = true;
> > > > +		wake_up(&rt->stream_wait_queue);
> > > > +	}
> > > > +
> > > > +	/* now send our playback data (if a free out urb was found) */
> > > > +	sub = &rt->playback;
> > > > +	spin_lock_irqsave(&sub->lock, flags);
> > > > +	if (sub->active)
> > > > +		do_period_elapsed = hiface_pcm_playback(sub, out_urb);
> > > > +	else
> > > > +		memset(out_urb->buffer, 0, PCM_PACKET_SIZE);
> > > > +
> > > > +	spin_unlock_irqrestore(&sub->lock, flags);
> > > > +
> > > > +	if (do_period_elapsed)
> > > > +		snd_pcm_period_elapsed(sub->instance);
> > > 
> > > This looks like a small open race.  sub->instance might be set to NULL
> > > after the unlock above?
> > >
> > 
> > Let's take a look at hiface_pcm_close():
> >  * sub->instance is set to NULL
> >  * the URBs are killed _after_ sub->instance is set to NULL
> > 
> > So if a URB is completed between these actions
> > hiface_pcm_out_urb_handler() gets called and there could be a problem.
> > 
> > If in hiface_pcm_close() I kill the URBs first and then set
> > sub->instance to NULL the race window gets closed, right?
> 
> Should be, yes.
>

Done, thanks.

> > > > +
> > > > +	ret = usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
> > > > +	if (ret < 0)
> > > > +		goto out_fail;
> > > 
> > > Maybe better to check the state again before resubmission, e.g. when
> > > XRUN is detected in the pointer callback.
> > >
> > 
> > I am not sure I understand what you mean here, should I check the
> > state of ALSA PCM stream or should I keep another state to avoid
> > transmissions on overruns?
> 
> ALSA state check should be enough for XRUN.
> 
> > Maybe you mean that I need to check that:
> >  sub->instance->runtime->status->state == SNDRV_PCM_STATE_RUNNING
> > ?
> 
> Yes, something like that.
>

Mmh, checking the ALSA state in the URB complete callback looks a bit
off, I don't see the other drives doing it, and if I add a check like
the above then I cannot resume from pause.

Is the driver supposed to keep sending URBs even when the stream is in
SNDRV_PCM_STATE_PAUSED? I'll check with the other drivers.

> > > > +
> > > > +	return;
> > > > +
> > > > +out_fail:
> > > > +	rt->panic = true;
> > > > +}
> > > > +
> > > > +static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub)
> > > > +{
> > > > +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> > > > +	struct pcm_substream *sub = NULL;
> > > > +	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
> > > > +	int ret;
> > > > +
> > > > +	pr_debug("%s: called.\n", __func__);
> > > > +
> > > > +	if (rt->panic)
> > > > +		return -EPIPE;
> > > > +
> > > > +	mutex_lock(&rt->stream_mutex);
> > > > +	alsa_rt->hw = pcm_hw;
> > > > +
> > > > +	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
> > > > +		sub = &rt->playback;
> > > > +
> > > > +	if (!sub) {
> > > > +		mutex_unlock(&rt->stream_mutex);
> > > > +		pr_err("Invalid stream type\n");
> > > > +		return -EINVAL;
> > > > +	}
> > > > +
> > > > +	if (rt->extra_freq) {
> > > > +		alsa_rt->hw.rates |= SNDRV_PCM_RATE_KNOT;
> > > > +		alsa_rt->hw.rate_max = 384000;
> > > > +
> > > > +		/* explicit constraints needed as we added SNDRV_PCM_RATE_KNOT */
> > > > +		ret = snd_pcm_hw_constraint_list(alsa_sub->runtime, 0,
> > > > +						 SNDRV_PCM_HW_PARAM_RATE,
> > > > +						 &constraints_extra_rates);
> > > > +		if (ret < 0) {
> > > > +			mutex_unlock(&rt->stream_mutex);
> > > > +			return ret;
> > > > +		}
> > > > +	}
> > > > +
> > > > +	sub->instance = alsa_sub;
> > > > +	sub->active = false;
> > > > +	mutex_unlock(&rt->stream_mutex);
> > > > +	return 0;
> > > > +}
> > > > +
> > > > +static int hiface_pcm_close(struct snd_pcm_substream *alsa_sub)
> > > > +{
> > > > +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> > > > +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> > > > +	unsigned long flags;
> > > > +
> > > > +	if (rt->panic)
> > > > +		return 0;
> > > > +
> > > > +	pr_debug("%s: called.\n", __func__);
> > > > +
> > > > +	mutex_lock(&rt->stream_mutex);
> > > > +	if (sub) {
> > > > +		/* deactivate substream */
> > > > +		spin_lock_irqsave(&sub->lock, flags);
> > > > +		sub->instance = NULL;
> > > > +		sub->active = false;
> > > > +		spin_unlock_irqrestore(&sub->lock, flags);
> > > > +
> > > > +		/* all substreams closed? if so, stop streaming */
> > > > +		if (!rt->playback.instance)
> > > > +			hiface_pcm_stream_stop(rt);
> > > 
> > > Can this condition be ever false...?
> > >
> > 
> > You mean that if we got to the .close() callback then the .open() one
> > succeeded and so we have a valid snd_pcm_substream? I guess that's
> > right and I can skip the check.
> 
> What does stream_mutex protect?  I don't have any glance over the
> whole code, and the code was so old, thus I don't remember at all...
>

OK, now I get it, the check must go away and the hiface_pcm_stream_stop
() must be called, otherwise the driver will keep sending URBs even
after a .close(). Thanks again.

> > The driver was based on a driver which handled multiple streams so
> > something must have slipped during the cleanup.
> > 
> > BTW, I think that cleaning up this hiFace driver further could be useful
> > even after mainline inclusion, it's a fairly simple driver which can be
> > used as an example driver, but my inexperience with ALSA, and audio in
> > general, was holding me back a bit to revolutionize it's initial
> > structure. Help is always appreciated by more experienced people.
> > 
> > > > +static int hiface_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
> > > > +{
> > > > +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> > > > +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> > > > +	unsigned long flags;
> > > 
> > > For trigger callback, you need no irqsave but simply use
> > > spin_lock_irq().
> > >
> > 
> > OK, will do, thanks.
> > 
> > > > +void hiface_pcm_abort(struct hiface_chip *chip)
> > > > +{
> > > > +	struct pcm_runtime *rt = chip->pcm;
> > > > +
> > > > +	if (rt) {
> > > > +		rt->panic = true;
> > > > +
> > > > +		if (rt->playback.instance) {
> > > > +			snd_pcm_stream_lock_irq(rt->playback.instance);
> > > > +			snd_pcm_stop(rt->playback.instance,
> > > > +					SNDRV_PCM_STATE_XRUN);
> > > > +			snd_pcm_stream_unlock_irq(rt->playback.instance);
> > > > +		}
> > > 
> > > Hmm... this is called after snd_card_disconnect(), thus it will reset
> > > the PCM stream state from DISCONNECTED to XRUN.  It doesn't sound
> > > right.
> > >
> > 
> > Mmh, some other USB drivers do that too in their abort, _after_
> > snd_card_disconnect() so they must be wrong too.
> 
> Which one?  The common usb-audio driver has the check beforehand.

I was referring to ua101 and to 6fire, but I see that the caiaq driver
and the usb generic one do not call snd_pcm_stop() at all so I am going
to drop it too.

> > Can I just skip calling snd_pcm_stop() altogether since the PCM stream
> > state is in DISCONNECTED state already?
> > 
> > > > +int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq)
> > > > +{
> > > ....
> > > > +	snd_pcm_lib_preallocate_pages_for_all(pcm,
> > > > +					SNDRV_DMA_TYPE_CONTINUOUS,
> > > > +					snd_dma_continuous_data(GFP_KERNEL),
> > > > +					PCM_BUFFER_SIZE, PCM_BUFFER_SIZE);
> > > 
> > > Any reason not to use vmalloc buffer?
> > > 
> > 
> > I don't know, I just shamelessly copied from other drivers here, what'd
> > be the difference?
> 
> A huge difference.  Imagine allocating a 1MB buffer.

I think I get what you meant now: for USB devices
snd_pcm_lib_alloc_vmalloc_buffer() can be used because an USB device
does not have the DMA and contiguousness requirements that a PCI device
has, right?

https://github.com/panicking/snd-usb-asyncaudio/commit/7cc042dda89e3705dbca6edb6744ea068e01a115

Daniel, maybe you want to change this in the caiaq driver too?
Should I send a patch for it?

The doubts about the panic condition and about checking the ALSA state
before resubmitting the URBs are the only two issue which are
keeping me from sending a v3 targeted for inclusion.

Michael I'll drop all the pr_debug("%s: called!", __func__) too in the
final version.

Thanks again,
   Antonio

-- 
Antonio Ospite
http://ao2.it

A: Because it messes up the order in which people normally read text.
   See http://en.wikipedia.org/wiki/Posting_style
Q: Why is top-posting such a bad thing?

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

* [PATCH v3] Add M2Tech hiFace USB-SPDIF driver
  2013-02-10 23:11 [PATCH] Add M2Tech hiFace USB-SPDIF driver Antonio Ospite
  2013-02-11  8:53 ` Clemens Ladisch
  2013-02-13 17:11 ` [PATCH v2] " Antonio Ospite
@ 2013-06-21 22:14 ` Antonio Ospite
  2013-06-24  7:45   ` Takashi Iwai
  2 siblings, 1 reply; 25+ messages in thread
From: Antonio Ospite @ 2013-06-21 22:14 UTC (permalink / raw)
  To: alsa-devel, patch
  Cc: Takashi Iwai, Antonio Ospite, Clemens Ladisch, Fabio Checconi,
	Daniel Mack, Pietro Cipriano, Alberto Panizzo, Michael Trimarchi

Add driver for M2Tech hiFace USB-SPDIF interface and compatible devices.

M2Tech hiFace and compatible devices offer a Hi-End S/PDIF Output
Interface, see http://www.m2tech.biz/hiface.html

The supported products are:

  * M2Tech Young
  * M2Tech hiFace
  * M2Tech North Star
  * M2Tech W4S Young
  * M2Tech Corrson
  * M2Tech AUDIA
  * M2Tech SL Audio
  * M2Tech Empirical
  * M2Tech Rockna
  * M2Tech Pathos
  * M2Tech Metronome
  * M2Tech CAD
  * M2Tech Audio Esclusive
  * M2Tech Rotel
  * M2Tech Eeaudio
  * The Chord Company CHORD
  * AVA Group A/S Vitus

Signed-off-by: Antonio Ospite <ao2@amarulasolutions.com>
---

Hi,

here is the v3 for the hiFace driver, the code is a lot cleaner than v1,
thanks to all the reviewers. 

I hope we can make it for 3.11 merge window.

The incremental changes for v3 can be seen one by one at
https://github.com/panicking/snd-usb-asyncaudio/commits/master
in commits from Jun 03, 2013 on; they are also listed below.

Changes since v2:

  * Replace snd_printk() with the standard dev_err() and friends
  * Replace strcpy() with strlcpy()
  * Drop some useless pr_debug() calls
  * hiface_pcm_trigger(): use spin_lock_irq()
  * hiface_pcm_get_substream(): replace pr_debug with dev_err()
  * hiface_pcm_close(): close a race window
  * hiface_pcm_abort(): don't stop the alsa stream here
  * Simplify the probing scheme to be per-device instead of per-interface
  * Drop the index field of hiface_chip as it's not used anywhere
  * Use vmalloc buffers
  * Cosmetics: adjust indentation of continuation lines
  * Don't declare multiple variables in the same statement
  * Replace pr_debug() with dev_dbg() and pr_err() with dev_err()
  * Stop USB stream unconditionally in hiface_pcm_close()
  * Reset panic state to false in hiface_pcm_stream_start()
  * Remove a stale comment
  * Remove pr_debug("%s: called.\n", __func__) statements


Changes since v1:

  * Change the first sentence of the Kconfig entry into "Select this..."
  * Remove a useless sentence from the Kconfig entry
  * Don't set alsa_rt->hw.rates in hiface_pcm_open()
  * Style cleanup, no braces needed in single statement conditional
  * Remove the rate field from pcm_runtime
  * Use the hiFace name with the lowercase 'h' everywhere
  * List actually supported devices in MODULE_SUPPORTED_DEVICE()
  * Cosmetics, align values in the rates array
  * Use an explicit switch instead of the rate_value array in
    hiface_pcm_set_rate()
  * Use usb_make_path() when building card->longname
  * Use again the implicit mechanism to allocate the card private data
  * Downgrade a pr_info to pr_debug in hiface_chip_probe()
  * Make device_table const
  * Rename PCM_MAX_PACKET_SIZE to PCM_PACKET_SIZE
  * Rename MAX_BUFSIZE to PCM_BUFFER_SIZE
  * Cosmetics, align symbolic constant values
  * Add SNDRV_PCM_RATE_KNOT only when needed
  * Declare memcpy_swahw32() as static
  * Protect snd_pcm_stop() with snd_pcm_stream_lock_irq()
  * Make hiface_pcm_playback() not returning anything
  * Move the period elapsed check into hiface_pcm_playback()
  * Handle the case of failing URBs in hiface_pcm_out_urb_handler()
  * Fix a couple of checkpatch.pl issues

Thanks,
   Antonio

 sound/usb/Kconfig         |  31 +++
 sound/usb/Makefile        |   2 +-
 sound/usb/hiface/Makefile |   2 +
 sound/usb/hiface/chip.c   | 297 ++++++++++++++++++++++
 sound/usb/hiface/chip.h   |  30 +++
 sound/usb/hiface/pcm.c    | 621 ++++++++++++++++++++++++++++++++++++++++++++++
 sound/usb/hiface/pcm.h    |  24 ++
 7 files changed, 1006 insertions(+), 1 deletion(-)
 create mode 100644 sound/usb/hiface/Makefile
 create mode 100644 sound/usb/hiface/chip.c
 create mode 100644 sound/usb/hiface/chip.h
 create mode 100644 sound/usb/hiface/pcm.c
 create mode 100644 sound/usb/hiface/pcm.h

diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig
index 225dfd7..de9408b 100644
--- a/sound/usb/Kconfig
+++ b/sound/usb/Kconfig
@@ -115,5 +115,36 @@ config SND_USB_6FIRE
           and further help can be found at
           http://sixfireusb.sourceforge.net
 
+config SND_USB_HIFACE
+        tristate "M2Tech hiFace USB-SPDIF driver"
+        select SND_PCM
+        help
+	  Select this option to include support for M2Tech hiFace USB-SPDIF
+	  interface.
+
+	  This driver supports the original M2Tech hiFace and some other
+	  compatible devices. The supported products are:
+
+	    * M2Tech Young
+	    * M2Tech hiFace
+	    * M2Tech North Star
+	    * M2Tech W4S Young
+	    * M2Tech Corrson
+	    * M2Tech AUDIA
+	    * M2Tech SL Audio
+	    * M2Tech Empirical
+	    * M2Tech Rockna
+	    * M2Tech Pathos
+	    * M2Tech Metronome
+	    * M2Tech CAD
+	    * M2Tech Audio Esclusive
+	    * M2Tech Rotel
+	    * M2Tech Eeaudio
+	    * The Chord Company CHORD
+	    * AVA Group A/S Vitus
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-usb-hiface.
+
 endif	# SND_USB
 
diff --git a/sound/usb/Makefile b/sound/usb/Makefile
index ac256dc..abe668f 100644
--- a/sound/usb/Makefile
+++ b/sound/usb/Makefile
@@ -23,4 +23,4 @@ obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o
 obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o
 obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o
 
-obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/
+obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/
diff --git a/sound/usb/hiface/Makefile b/sound/usb/hiface/Makefile
new file mode 100644
index 0000000..463b136
--- /dev/null
+++ b/sound/usb/hiface/Makefile
@@ -0,0 +1,2 @@
+snd-usb-hiface-objs := chip.o pcm.o
+obj-$(CONFIG_SND_USB_HIFACE) += snd-usb-hiface.o
diff --git a/sound/usb/hiface/chip.c b/sound/usb/hiface/chip.c
new file mode 100644
index 0000000..b0dcb39
--- /dev/null
+++ b/sound/usb/hiface/chip.c
@@ -0,0 +1,297 @@
+/*
+ * Linux driver for M2Tech hiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
+ *           Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <sound/initval.h>
+
+#include "chip.h"
+#include "pcm.h"
+
+MODULE_AUTHOR("Michael Trimarchi <michael@amarulasolutions.com>");
+MODULE_AUTHOR("Antonio Ospite <ao2@amarulasolutions.com>");
+MODULE_DESCRIPTION("M2Tech hiFace USB-SPDIF audio driver");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{M2Tech,Young},"
+			 "{M2Tech,hiFace},"
+			 "{M2Tech,North Star},"
+			 "{M2Tech,W4S Young},"
+			 "{M2Tech,Corrson},"
+			 "{M2Tech,AUDIA},"
+			 "{M2Tech,SL Audio},"
+			 "{M2Tech,Empirical},"
+			 "{M2Tech,Rockna},"
+			 "{M2Tech,Pathos},"
+			 "{M2Tech,Metronome},"
+			 "{M2Tech,CAD},"
+			 "{M2Tech,Audio Esclusive},"
+			 "{M2Tech,Rotel},"
+			 "{M2Tech,Eeaudio},"
+			 "{The Chord Company,CHORD},"
+			 "{AVA Group A/S,Vitus}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
+
+#define DRIVER_NAME "snd-usb-hiface"
+#define CARD_NAME "hiFace"
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
+
+static DEFINE_MUTEX(register_mutex);
+
+struct hiface_vendor_quirk {
+	const char *device_name;
+	u8 extra_freq;
+};
+
+static int hiface_chip_create(struct usb_device *device, int idx,
+			      const struct hiface_vendor_quirk *quirk,
+			      struct hiface_chip **rchip)
+{
+	struct snd_card *card = NULL;
+	struct hiface_chip *chip;
+	int ret;
+	int len;
+
+	*rchip = NULL;
+
+	/* if we are here, card can be registered in alsa. */
+	ret = snd_card_create(index[idx], id[idx], THIS_MODULE, sizeof(*chip), &card);
+	if (ret < 0) {
+		dev_err(&device->dev, "cannot create alsa card.\n");
+		return ret;
+	}
+
+	strlcpy(card->driver, DRIVER_NAME, sizeof(card->driver));
+
+	if (quirk && quirk->device_name)
+		strlcpy(card->shortname, quirk->device_name, sizeof(card->shortname));
+	else
+		strlcpy(card->shortname, "M2Tech generic audio", sizeof(card->shortname));
+
+	strlcat(card->longname, card->shortname, sizeof(card->longname));
+	len = strlcat(card->longname, " at ", sizeof(card->longname));
+	if (len < sizeof(card->longname))
+		usb_make_path(device, card->longname + len,
+			      sizeof(card->longname) - len);
+
+	chip = card->private_data;
+	chip->dev = device;
+	chip->card = card;
+
+	*rchip = chip;
+	return 0;
+}
+
+static int hiface_chip_probe(struct usb_interface *intf,
+			     const struct usb_device_id *usb_id)
+{
+	const struct hiface_vendor_quirk *quirk = (struct hiface_vendor_quirk *)usb_id->driver_info;
+	int ret;
+	int i;
+	struct hiface_chip *chip;
+	struct usb_device *device = interface_to_usbdev(intf);
+
+	ret = usb_set_interface(device, 0, 0);
+	if (ret != 0) {
+		dev_err(&device->dev, "can't set first interface for " CARD_NAME " device.\n");
+		return -EIO;
+	}
+
+	/* check whether the card is already registered */
+	chip = NULL;
+	mutex_lock(&register_mutex);
+
+	for (i = 0; i < SNDRV_CARDS; i++)
+		if (enable[i])
+			break;
+
+	if (i >= SNDRV_CARDS) {
+		dev_err(&device->dev, "no available " CARD_NAME " audio device\n");
+		ret = -ENODEV;
+		goto err;
+	}
+
+	ret = hiface_chip_create(device, i, quirk, &chip);
+	if (ret < 0)
+		goto err;
+
+	snd_card_set_dev(chip->card, &intf->dev);
+
+	ret = hiface_pcm_init(chip, quirk ? quirk->extra_freq : 0);
+	if (ret < 0)
+		goto err_chip_destroy;
+
+	ret = snd_card_register(chip->card);
+	if (ret < 0) {
+		dev_err(&device->dev, "cannot register " CARD_NAME " card\n");
+		goto err_chip_destroy;
+	}
+
+	mutex_unlock(&register_mutex);
+
+	usb_set_intfdata(intf, chip);
+	return 0;
+
+err_chip_destroy:
+	snd_card_free(chip->card);
+err:
+	mutex_unlock(&register_mutex);
+	return ret;
+}
+
+static void hiface_chip_disconnect(struct usb_interface *intf)
+{
+	struct hiface_chip *chip;
+	struct snd_card *card;
+
+	chip = usb_get_intfdata(intf);
+	if (!chip)
+		return;
+
+	card = chip->card;
+
+	/* Make sure that the userspace cannot create new request */
+	snd_card_disconnect(card);
+
+	hiface_pcm_abort(chip);
+	snd_card_free_when_closed(card);
+}
+
+static const struct usb_device_id device_table[] = {
+	{
+		USB_DEVICE(0x04b4, 0x0384),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Young",
+			.extra_freq = 1,
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x930b),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "hiFace",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931b),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "North Star",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931c),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "W4S Young",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931d),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Corrson",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931e),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "AUDIA",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931f),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "SL Audio",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x9320),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Empirical",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x9321),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Rockna",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x9001),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Pathos",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x9002),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Metronome",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x9006),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "CAD",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x9008),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Audio Esclusive",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x931c),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Rotel",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x932c),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Eeaudio",
+		}
+	},
+	{
+		USB_DEVICE(0x245f, 0x931c),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "CHORD",
+		}
+	},
+	{
+		USB_DEVICE(0x25c6, 0x9002),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Vitus",
+		}
+	},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+static struct usb_driver hiface_usb_driver = {
+	.name = DRIVER_NAME,
+	.probe = hiface_chip_probe,
+	.disconnect = hiface_chip_disconnect,
+	.id_table = device_table,
+};
+
+module_usb_driver(hiface_usb_driver);
diff --git a/sound/usb/hiface/chip.h b/sound/usb/hiface/chip.h
new file mode 100644
index 0000000..189a137
--- /dev/null
+++ b/sound/usb/hiface/chip.h
@@ -0,0 +1,30 @@
+/*
+ * Linux driver for M2Tech hiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
+ *           Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef HIFACE_CHIP_H
+#define HIFACE_CHIP_H
+
+#include <linux/usb.h>
+#include <sound/core.h>
+
+struct pcm_runtime;
+
+struct hiface_chip {
+	struct usb_device *dev;
+	struct snd_card *card;
+	struct pcm_runtime *pcm;
+};
+#endif /* HIFACE_CHIP_H */
diff --git a/sound/usb/hiface/pcm.c b/sound/usb/hiface/pcm.c
new file mode 100644
index 0000000..6430ed2
--- /dev/null
+++ b/sound/usb/hiface/pcm.c
@@ -0,0 +1,621 @@
+/*
+ * Linux driver for M2Tech hiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
+ *           Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/slab.h>
+#include <sound/pcm.h>
+
+#include "pcm.h"
+#include "chip.h"
+
+#define OUT_EP          0x2
+#define PCM_N_URBS      8
+#define PCM_PACKET_SIZE 4096
+#define PCM_BUFFER_SIZE (2 * PCM_N_URBS * PCM_PACKET_SIZE)
+
+struct pcm_urb {
+	struct hiface_chip *chip;
+
+	struct urb instance;
+	struct usb_anchor submitted;
+	u8 *buffer;
+};
+
+struct pcm_substream {
+	spinlock_t lock;
+	struct snd_pcm_substream *instance;
+
+	bool active;
+	snd_pcm_uframes_t dma_off;    /* current position in alsa dma_area */
+	snd_pcm_uframes_t period_off; /* current position in current period */
+};
+
+enum { /* pcm streaming states */
+	STREAM_DISABLED, /* no pcm streaming */
+	STREAM_STARTING, /* pcm streaming requested, waiting to become ready */
+	STREAM_RUNNING,  /* pcm streaming running */
+	STREAM_STOPPING
+};
+
+struct pcm_runtime {
+	struct hiface_chip *chip;
+	struct snd_pcm *instance;
+
+	struct pcm_substream playback;
+	bool panic; /* if set driver won't do anymore pcm on device */
+
+	struct pcm_urb out_urbs[PCM_N_URBS];
+
+	struct mutex stream_mutex;
+	u8 stream_state; /* one of STREAM_XXX */
+	u8 extra_freq;
+	wait_queue_head_t stream_wait_queue;
+	bool stream_wait_cond;
+};
+
+static const unsigned int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000,
+				      352800, 384000 };
+static const struct snd_pcm_hw_constraint_list constraints_extra_rates = {
+	.count = ARRAY_SIZE(rates),
+	.list = rates,
+	.mask = 0,
+};
+
+static const struct snd_pcm_hardware pcm_hw = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_BATCH,
+
+	.formats = SNDRV_PCM_FMTBIT_S32_LE,
+
+	.rates = SNDRV_PCM_RATE_44100 |
+		SNDRV_PCM_RATE_48000 |
+		SNDRV_PCM_RATE_88200 |
+		SNDRV_PCM_RATE_96000 |
+		SNDRV_PCM_RATE_176400 |
+		SNDRV_PCM_RATE_192000,
+
+	.rate_min = 44100,
+	.rate_max = 192000, /* changes in hiface_pcm_open to support extra rates */
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = PCM_BUFFER_SIZE,
+	.period_bytes_min = PCM_PACKET_SIZE,
+	.period_bytes_max = PCM_BUFFER_SIZE,
+	.periods_min = 2,
+	.periods_max = 1024
+};
+
+/* message values used to change the sample rate */
+#define HIFACE_SET_RATE_REQUEST 0xb0
+
+#define HIFACE_RATE_44100  0x43
+#define HIFACE_RATE_48000  0x4b
+#define HIFACE_RATE_88200  0x42
+#define HIFACE_RATE_96000  0x4a
+#define HIFACE_RATE_176400 0x40
+#define HIFACE_RATE_192000 0x48
+#define HIFACE_RATE_352000 0x58
+#define HIFACE_RATE_384000 0x68
+
+static int hiface_pcm_set_rate(struct pcm_runtime *rt, unsigned int rate)
+{
+	struct usb_device *device = rt->chip->dev;
+	u16 rate_value;
+	int ret;
+
+	/* We are already sure that the rate is supported here thanks to
+	 * ALSA constraints
+	 */
+	switch (rate) {
+	case 44100:
+		rate_value = HIFACE_RATE_44100;
+		break;
+	case 48000:
+		rate_value = HIFACE_RATE_48000;
+		break;
+	case 88200:
+		rate_value = HIFACE_RATE_88200;
+		break;
+	case 96000:
+		rate_value = HIFACE_RATE_96000;
+		break;
+	case 176400:
+		rate_value = HIFACE_RATE_176400;
+		break;
+	case 192000:
+		rate_value = HIFACE_RATE_192000;
+		break;
+	case 352000:
+		rate_value = HIFACE_RATE_352000;
+		break;
+	case 384000:
+		rate_value = HIFACE_RATE_384000;
+		break;
+	default:
+		dev_err(&device->dev, "Unsupported rate %d\n", rate);
+		return -EINVAL;
+	}
+
+	/*
+	 * USBIO: Vendor 0xb0(wValue=0x0043, wIndex=0x0000)
+	 * 43 b0 43 00 00 00 00 00
+	 * USBIO: Vendor 0xb0(wValue=0x004b, wIndex=0x0000)
+	 * 43 b0 4b 00 00 00 00 00
+	 * This control message doesn't have any ack from the
+	 * other side
+	 */
+	ret = usb_control_msg(device, usb_sndctrlpipe(device, 0),
+			      HIFACE_SET_RATE_REQUEST,
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+			      rate_value, 0, NULL, 0, 100);
+	if (ret < 0) {
+		dev_err(&device->dev, "Error setting samplerate %d.\n", rate);
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct pcm_substream *hiface_pcm_get_substream(struct snd_pcm_substream
+						      *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	struct device *device = &rt->chip->dev->dev;
+
+	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		return &rt->playback;
+
+	dev_err(device, "Error getting pcm substream slot.\n");
+	return NULL;
+}
+
+/* call with stream_mutex locked */
+static void hiface_pcm_stream_stop(struct pcm_runtime *rt)
+{
+	int i, time;
+
+	if (rt->stream_state != STREAM_DISABLED) {
+		rt->stream_state = STREAM_STOPPING;
+
+		for (i = 0; i < PCM_N_URBS; i++) {
+			time = usb_wait_anchor_empty_timeout(
+					&rt->out_urbs[i].submitted, 100);
+			if (!time)
+				usb_kill_anchored_urbs(
+					&rt->out_urbs[i].submitted);
+			usb_kill_urb(&rt->out_urbs[i].instance);
+		}
+
+		rt->stream_state = STREAM_DISABLED;
+	}
+}
+
+/* call with stream_mutex locked */
+static int hiface_pcm_stream_start(struct pcm_runtime *rt)
+{
+	int ret = 0;
+	int i;
+
+	if (rt->stream_state == STREAM_DISABLED) {
+
+		/* reset panic state when starting a new stream */
+		rt->panic = false;
+
+		/* submit our out urbs zero init */
+		rt->stream_state = STREAM_STARTING;
+		for (i = 0; i < PCM_N_URBS; i++) {
+			memset(rt->out_urbs[i].buffer, 0, PCM_PACKET_SIZE);
+			usb_anchor_urb(&rt->out_urbs[i].instance,
+				       &rt->out_urbs[i].submitted);
+			ret = usb_submit_urb(&rt->out_urbs[i].instance,
+					     GFP_ATOMIC);
+			if (ret) {
+				hiface_pcm_stream_stop(rt);
+				return ret;
+			}
+		}
+
+		/* wait for first out urb to return (sent in in urb handler) */
+		wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond,
+				   HZ);
+		if (rt->stream_wait_cond) {
+			struct device *device = &rt->chip->dev->dev;
+			dev_dbg(device, "%s: Stream is running wakeup event\n",
+				 __func__);
+			rt->stream_state = STREAM_RUNNING;
+		} else {
+			hiface_pcm_stream_stop(rt);
+			return -EIO;
+		}
+	}
+	return ret;
+}
+
+/* The hardware wants word-swapped 32-bit values */
+static void memcpy_swahw32(u8 *dest, u8 *src, unsigned int n)
+{
+	unsigned int i;
+
+	for (i = 0; i < n / 4; i++)
+		((u32 *)dest)[i] = swahw32(((u32 *)src)[i]);
+}
+
+/* call with substream locked */
+/* returns true if a period elapsed */
+static bool hiface_pcm_playback(struct pcm_substream *sub, struct pcm_urb *urb)
+{
+	struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
+	struct device *device = &urb->chip->dev->dev;
+	u8 *source;
+	unsigned int pcm_buffer_size;
+
+	WARN_ON(alsa_rt->format != SNDRV_PCM_FORMAT_S32_LE);
+
+	pcm_buffer_size = snd_pcm_lib_buffer_bytes(sub->instance);
+
+	if (sub->dma_off + PCM_PACKET_SIZE <= pcm_buffer_size) {
+		dev_dbg(device, "%s: (1) buffer_size %#x dma_offset %#x\n", __func__,
+			 (unsigned int) pcm_buffer_size,
+			 (unsigned int) sub->dma_off);
+
+		source = alsa_rt->dma_area + sub->dma_off;
+		memcpy_swahw32(urb->buffer, source, PCM_PACKET_SIZE);
+	} else {
+		/* wrap around at end of ring buffer */
+		unsigned int len;
+
+		dev_dbg(device, "%s: (2) buffer_size %#x dma_offset %#x\n", __func__,
+			 (unsigned int) pcm_buffer_size,
+			 (unsigned int) sub->dma_off);
+
+		len = pcm_buffer_size - sub->dma_off;
+
+		source = alsa_rt->dma_area + sub->dma_off;
+		memcpy_swahw32(urb->buffer, source, len);
+
+		source = alsa_rt->dma_area;
+		memcpy_swahw32(urb->buffer + len, source,
+			       PCM_PACKET_SIZE - len);
+	}
+	sub->dma_off += PCM_PACKET_SIZE;
+	if (sub->dma_off >= pcm_buffer_size)
+		sub->dma_off -= pcm_buffer_size;
+
+	sub->period_off += PCM_PACKET_SIZE;
+	if (sub->period_off >= alsa_rt->period_size) {
+		sub->period_off %= alsa_rt->period_size;
+		return true;
+	}
+	return false;
+}
+
+static void hiface_pcm_out_urb_handler(struct urb *usb_urb)
+{
+	struct pcm_urb *out_urb = usb_urb->context;
+	struct pcm_runtime *rt = out_urb->chip->pcm;
+	struct pcm_substream *sub;
+	bool do_period_elapsed = false;
+	unsigned long flags;
+	int ret;
+
+	if (rt->panic || rt->stream_state == STREAM_STOPPING)
+		return;
+
+	if (unlikely(usb_urb->status == -ENOENT ||	/* unlinked */
+		     usb_urb->status == -ENODEV ||	/* device removed */
+		     usb_urb->status == -ECONNRESET ||	/* unlinked */
+		     usb_urb->status == -ESHUTDOWN)) {	/* device disabled */
+		goto out_fail;
+	}
+
+	if (rt->stream_state == STREAM_STARTING) {
+		rt->stream_wait_cond = true;
+		wake_up(&rt->stream_wait_queue);
+	}
+
+	/* now send our playback data (if a free out urb was found) */
+	sub = &rt->playback;
+	spin_lock_irqsave(&sub->lock, flags);
+	if (sub->active)
+		do_period_elapsed = hiface_pcm_playback(sub, out_urb);
+	else
+		memset(out_urb->buffer, 0, PCM_PACKET_SIZE);
+
+	spin_unlock_irqrestore(&sub->lock, flags);
+
+	if (do_period_elapsed)
+		snd_pcm_period_elapsed(sub->instance);
+
+	ret = usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
+	if (ret < 0)
+		goto out_fail;
+
+	return;
+
+out_fail:
+	rt->panic = true;
+}
+
+static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	struct pcm_substream *sub = NULL;
+	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
+	int ret;
+
+	if (rt->panic)
+		return -EPIPE;
+
+	mutex_lock(&rt->stream_mutex);
+	alsa_rt->hw = pcm_hw;
+
+	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		sub = &rt->playback;
+
+	if (!sub) {
+		struct device *device = &rt->chip->dev->dev;
+		mutex_unlock(&rt->stream_mutex);
+		dev_err(device, "Invalid stream type\n");
+		return -EINVAL;
+	}
+
+	if (rt->extra_freq) {
+		alsa_rt->hw.rates |= SNDRV_PCM_RATE_KNOT;
+		alsa_rt->hw.rate_max = 384000;
+
+		/* explicit constraints needed as we added SNDRV_PCM_RATE_KNOT */
+		ret = snd_pcm_hw_constraint_list(alsa_sub->runtime, 0,
+						 SNDRV_PCM_HW_PARAM_RATE,
+						 &constraints_extra_rates);
+		if (ret < 0) {
+			mutex_unlock(&rt->stream_mutex);
+			return ret;
+		}
+	}
+
+	sub->instance = alsa_sub;
+	sub->active = false;
+	mutex_unlock(&rt->stream_mutex);
+	return 0;
+}
+
+static int hiface_pcm_close(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+	unsigned long flags;
+
+	if (rt->panic)
+		return 0;
+
+	mutex_lock(&rt->stream_mutex);
+	if (sub) {
+		hiface_pcm_stream_stop(rt);
+
+		/* deactivate substream */
+		spin_lock_irqsave(&sub->lock, flags);
+		sub->instance = NULL;
+		sub->active = false;
+		spin_unlock_irqrestore(&sub->lock, flags);
+
+	}
+	mutex_unlock(&rt->stream_mutex);
+	return 0;
+}
+
+static int hiface_pcm_hw_params(struct snd_pcm_substream *alsa_sub,
+				struct snd_pcm_hw_params *hw_params)
+{
+	return snd_pcm_lib_alloc_vmalloc_buffer(alsa_sub,
+						params_buffer_bytes(hw_params));
+}
+
+static int hiface_pcm_hw_free(struct snd_pcm_substream *alsa_sub)
+{
+	return snd_pcm_lib_free_vmalloc_buffer(alsa_sub);
+}
+
+static int hiface_pcm_prepare(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
+	int ret;
+
+	if (rt->panic)
+		return -EPIPE;
+	if (!sub)
+		return -ENODEV;
+
+	mutex_lock(&rt->stream_mutex);
+
+	sub->dma_off = 0;
+	sub->period_off = 0;
+
+	if (rt->stream_state == STREAM_DISABLED) {
+
+		ret = hiface_pcm_set_rate(rt, alsa_rt->rate);
+		if (ret) {
+			mutex_unlock(&rt->stream_mutex);
+			return ret;
+		}
+		ret = hiface_pcm_stream_start(rt);
+		if (ret) {
+			mutex_unlock(&rt->stream_mutex);
+			return ret;
+		}
+	}
+	mutex_unlock(&rt->stream_mutex);
+	return 0;
+}
+
+static int hiface_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
+{
+	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+
+	if (rt->panic)
+		return -EPIPE;
+	if (!sub)
+		return -ENODEV;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		spin_lock_irq(&sub->lock);
+		sub->active = true;
+		spin_unlock_irq(&sub->lock);
+		return 0;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		spin_lock_irq(&sub->lock);
+		sub->active = false;
+		spin_unlock_irq(&sub->lock);
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static snd_pcm_uframes_t hiface_pcm_pointer(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	unsigned long flags;
+	snd_pcm_uframes_t dma_offset;
+
+	if (rt->panic || !sub)
+		return SNDRV_PCM_STATE_XRUN;
+
+	spin_lock_irqsave(&sub->lock, flags);
+	dma_offset = sub->dma_off;
+	spin_unlock_irqrestore(&sub->lock, flags);
+	return bytes_to_frames(alsa_sub->runtime, dma_offset);
+}
+
+static struct snd_pcm_ops pcm_ops = {
+	.open = hiface_pcm_open,
+	.close = hiface_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = hiface_pcm_hw_params,
+	.hw_free = hiface_pcm_hw_free,
+	.prepare = hiface_pcm_prepare,
+	.trigger = hiface_pcm_trigger,
+	.pointer = hiface_pcm_pointer,
+	.page = snd_pcm_lib_get_vmalloc_page,
+	.mmap = snd_pcm_lib_mmap_vmalloc,
+};
+
+static int hiface_pcm_init_urb(struct pcm_urb *urb,
+			       struct hiface_chip *chip,
+			       unsigned int ep,
+			       void (*handler)(struct urb *))
+{
+	urb->chip = chip;
+	usb_init_urb(&urb->instance);
+
+	urb->buffer = kzalloc(PCM_PACKET_SIZE, GFP_KERNEL);
+	if (!urb->buffer)
+		return -ENOMEM;
+
+	usb_fill_bulk_urb(&urb->instance, chip->dev,
+			  usb_sndbulkpipe(chip->dev, ep), (void *)urb->buffer,
+			  PCM_PACKET_SIZE, handler, urb);
+	init_usb_anchor(&urb->submitted);
+
+	return 0;
+}
+
+void hiface_pcm_abort(struct hiface_chip *chip)
+{
+	struct pcm_runtime *rt = chip->pcm;
+
+	if (rt) {
+		rt->panic = true;
+
+		mutex_lock(&rt->stream_mutex);
+		hiface_pcm_stream_stop(rt);
+		mutex_unlock(&rt->stream_mutex);
+	}
+}
+
+static void hiface_pcm_destroy(struct hiface_chip *chip)
+{
+	struct pcm_runtime *rt = chip->pcm;
+	int i;
+
+	for (i = 0; i < PCM_N_URBS; i++)
+		kfree(rt->out_urbs[i].buffer);
+
+	kfree(chip->pcm);
+	chip->pcm = NULL;
+}
+
+static void hiface_pcm_free(struct snd_pcm *pcm)
+{
+	struct pcm_runtime *rt = pcm->private_data;
+
+	if (rt)
+		hiface_pcm_destroy(rt->chip);
+}
+
+int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq)
+{
+	int i;
+	int ret;
+	struct snd_pcm *pcm;
+	struct pcm_runtime *rt;
+
+	rt = kzalloc(sizeof(*rt), GFP_KERNEL);
+	if (!rt)
+		return -ENOMEM;
+
+	rt->chip = chip;
+	rt->stream_state = STREAM_DISABLED;
+	if (extra_freq)
+		rt->extra_freq = 1;
+
+	init_waitqueue_head(&rt->stream_wait_queue);
+	mutex_init(&rt->stream_mutex);
+	spin_lock_init(&rt->playback.lock);
+
+	for (i = 0; i < PCM_N_URBS; i++)
+		hiface_pcm_init_urb(&rt->out_urbs[i], chip, OUT_EP,
+				    hiface_pcm_out_urb_handler);
+
+	ret = snd_pcm_new(chip->card, "USB-SPDIF Audio", 0, 1, 0, &pcm);
+	if (ret < 0) {
+		kfree(rt);
+		dev_err(&chip->dev->dev, "Cannot create pcm instance\n");
+		return ret;
+	}
+
+	pcm->private_data = rt;
+	pcm->private_free = hiface_pcm_free;
+
+	strlcpy(pcm->name, "USB-SPDIF Audio", sizeof(pcm->name));
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops);
+
+	rt->instance = pcm;
+
+	chip->pcm = rt;
+	return 0;
+}
diff --git a/sound/usb/hiface/pcm.h b/sound/usb/hiface/pcm.h
new file mode 100644
index 0000000..77edd7c
--- /dev/null
+++ b/sound/usb/hiface/pcm.h
@@ -0,0 +1,24 @@
+/*
+ * Linux driver for M2Tech hiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
+ *           Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef HIFACE_PCM_H
+#define HIFACE_PCM_H
+
+struct hiface_chip;
+
+int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq);
+void hiface_pcm_abort(struct hiface_chip *chip);
+#endif /* HIFACE_PCM_H */
-- 
1.8.3.1

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

* Re: [PATCH v3] Add M2Tech hiFace USB-SPDIF driver
  2013-06-21 22:14 ` [PATCH v3] " Antonio Ospite
@ 2013-06-24  7:45   ` Takashi Iwai
  0 siblings, 0 replies; 25+ messages in thread
From: Takashi Iwai @ 2013-06-24  7:45 UTC (permalink / raw)
  To: Antonio Ospite
  Cc: alsa-devel, Clemens Ladisch, Fabio Checconi, Daniel Mack,
	Pietro Cipriano, Alberto Panizzo, Michael Trimarchi

At Sat, 22 Jun 2013 00:14:46 +0200,
Antonio Ospite wrote:
> 
> Add driver for M2Tech hiFace USB-SPDIF interface and compatible devices.
> 
> M2Tech hiFace and compatible devices offer a Hi-End S/PDIF Output
> Interface, see http://www.m2tech.biz/hiface.html
> 
> The supported products are:
> 
>   * M2Tech Young
>   * M2Tech hiFace
>   * M2Tech North Star
>   * M2Tech W4S Young
>   * M2Tech Corrson
>   * M2Tech AUDIA
>   * M2Tech SL Audio
>   * M2Tech Empirical
>   * M2Tech Rockna
>   * M2Tech Pathos
>   * M2Tech Metronome
>   * M2Tech CAD
>   * M2Tech Audio Esclusive
>   * M2Tech Rotel
>   * M2Tech Eeaudio
>   * The Chord Company CHORD
>   * AVA Group A/S Vitus
> 
> Signed-off-by: Antonio Ospite <ao2@amarulasolutions.com>
> ---
> 
> Hi,
> 
> here is the v3 for the hiFace driver, the code is a lot cleaner than v1,
> thanks to all the reviewers. 
> 
> I hope we can make it for 3.11 merge window.
> 
> The incremental changes for v3 can be seen one by one at
> https://github.com/panicking/snd-usb-asyncaudio/commits/master
> in commits from Jun 03, 2013 on; they are also listed below.
> 
> Changes since v2:
> 
>   * Replace snd_printk() with the standard dev_err() and friends
>   * Replace strcpy() with strlcpy()
>   * Drop some useless pr_debug() calls
>   * hiface_pcm_trigger(): use spin_lock_irq()
>   * hiface_pcm_get_substream(): replace pr_debug with dev_err()
>   * hiface_pcm_close(): close a race window
>   * hiface_pcm_abort(): don't stop the alsa stream here
>   * Simplify the probing scheme to be per-device instead of per-interface
>   * Drop the index field of hiface_chip as it's not used anywhere
>   * Use vmalloc buffers
>   * Cosmetics: adjust indentation of continuation lines
>   * Don't declare multiple variables in the same statement
>   * Replace pr_debug() with dev_dbg() and pr_err() with dev_err()
>   * Stop USB stream unconditionally in hiface_pcm_close()
>   * Reset panic state to false in hiface_pcm_stream_start()
>   * Remove a stale comment
>   * Remove pr_debug("%s: called.\n", __func__) statements
> 
> 
> Changes since v1:
> 
>   * Change the first sentence of the Kconfig entry into "Select this..."
>   * Remove a useless sentence from the Kconfig entry
>   * Don't set alsa_rt->hw.rates in hiface_pcm_open()
>   * Style cleanup, no braces needed in single statement conditional
>   * Remove the rate field from pcm_runtime
>   * Use the hiFace name with the lowercase 'h' everywhere
>   * List actually supported devices in MODULE_SUPPORTED_DEVICE()
>   * Cosmetics, align values in the rates array
>   * Use an explicit switch instead of the rate_value array in
>     hiface_pcm_set_rate()
>   * Use usb_make_path() when building card->longname
>   * Use again the implicit mechanism to allocate the card private data
>   * Downgrade a pr_info to pr_debug in hiface_chip_probe()
>   * Make device_table const
>   * Rename PCM_MAX_PACKET_SIZE to PCM_PACKET_SIZE
>   * Rename MAX_BUFSIZE to PCM_BUFFER_SIZE
>   * Cosmetics, align symbolic constant values
>   * Add SNDRV_PCM_RATE_KNOT only when needed
>   * Declare memcpy_swahw32() as static
>   * Protect snd_pcm_stop() with snd_pcm_stream_lock_irq()
>   * Make hiface_pcm_playback() not returning anything
>   * Move the period elapsed check into hiface_pcm_playback()
>   * Handle the case of failing URBs in hiface_pcm_out_urb_handler()
>   * Fix a couple of checkpatch.pl issues

Looks good now, so I applied it.
Let's fix the code in the git tree if anyone find a bug.


thanks,

Takashi


> 
> Thanks,
>    Antonio
> 
>  sound/usb/Kconfig         |  31 +++
>  sound/usb/Makefile        |   2 +-
>  sound/usb/hiface/Makefile |   2 +
>  sound/usb/hiface/chip.c   | 297 ++++++++++++++++++++++
>  sound/usb/hiface/chip.h   |  30 +++
>  sound/usb/hiface/pcm.c    | 621 ++++++++++++++++++++++++++++++++++++++++++++++
>  sound/usb/hiface/pcm.h    |  24 ++
>  7 files changed, 1006 insertions(+), 1 deletion(-)
>  create mode 100644 sound/usb/hiface/Makefile
>  create mode 100644 sound/usb/hiface/chip.c
>  create mode 100644 sound/usb/hiface/chip.h
>  create mode 100644 sound/usb/hiface/pcm.c
>  create mode 100644 sound/usb/hiface/pcm.h
> 
> diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig
> index 225dfd7..de9408b 100644
> --- a/sound/usb/Kconfig
> +++ b/sound/usb/Kconfig
> @@ -115,5 +115,36 @@ config SND_USB_6FIRE
>            and further help can be found at
>            http://sixfireusb.sourceforge.net
>  
> +config SND_USB_HIFACE
> +        tristate "M2Tech hiFace USB-SPDIF driver"
> +        select SND_PCM
> +        help
> +	  Select this option to include support for M2Tech hiFace USB-SPDIF
> +	  interface.
> +
> +	  This driver supports the original M2Tech hiFace and some other
> +	  compatible devices. The supported products are:
> +
> +	    * M2Tech Young
> +	    * M2Tech hiFace
> +	    * M2Tech North Star
> +	    * M2Tech W4S Young
> +	    * M2Tech Corrson
> +	    * M2Tech AUDIA
> +	    * M2Tech SL Audio
> +	    * M2Tech Empirical
> +	    * M2Tech Rockna
> +	    * M2Tech Pathos
> +	    * M2Tech Metronome
> +	    * M2Tech CAD
> +	    * M2Tech Audio Esclusive
> +	    * M2Tech Rotel
> +	    * M2Tech Eeaudio
> +	    * The Chord Company CHORD
> +	    * AVA Group A/S Vitus
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called snd-usb-hiface.
> +
>  endif	# SND_USB
>  
> diff --git a/sound/usb/Makefile b/sound/usb/Makefile
> index ac256dc..abe668f 100644
> --- a/sound/usb/Makefile
> +++ b/sound/usb/Makefile
> @@ -23,4 +23,4 @@ obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o
>  obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o
>  obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o
>  
> -obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/
> +obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/
> diff --git a/sound/usb/hiface/Makefile b/sound/usb/hiface/Makefile
> new file mode 100644
> index 0000000..463b136
> --- /dev/null
> +++ b/sound/usb/hiface/Makefile
> @@ -0,0 +1,2 @@
> +snd-usb-hiface-objs := chip.o pcm.o
> +obj-$(CONFIG_SND_USB_HIFACE) += snd-usb-hiface.o
> diff --git a/sound/usb/hiface/chip.c b/sound/usb/hiface/chip.c
> new file mode 100644
> index 0000000..b0dcb39
> --- /dev/null
> +++ b/sound/usb/hiface/chip.c
> @@ -0,0 +1,297 @@
> +/*
> + * Linux driver for M2Tech hiFace compatible devices
> + *
> + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> + *
> + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> + *           Antonio Ospite <ao2@amarulasolutions.com>
> + *
> + * The driver is based on the work done in TerraTec DMX 6Fire USB
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/slab.h>
> +#include <sound/initval.h>
> +
> +#include "chip.h"
> +#include "pcm.h"
> +
> +MODULE_AUTHOR("Michael Trimarchi <michael@amarulasolutions.com>");
> +MODULE_AUTHOR("Antonio Ospite <ao2@amarulasolutions.com>");
> +MODULE_DESCRIPTION("M2Tech hiFace USB-SPDIF audio driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_SUPPORTED_DEVICE("{{M2Tech,Young},"
> +			 "{M2Tech,hiFace},"
> +			 "{M2Tech,North Star},"
> +			 "{M2Tech,W4S Young},"
> +			 "{M2Tech,Corrson},"
> +			 "{M2Tech,AUDIA},"
> +			 "{M2Tech,SL Audio},"
> +			 "{M2Tech,Empirical},"
> +			 "{M2Tech,Rockna},"
> +			 "{M2Tech,Pathos},"
> +			 "{M2Tech,Metronome},"
> +			 "{M2Tech,CAD},"
> +			 "{M2Tech,Audio Esclusive},"
> +			 "{M2Tech,Rotel},"
> +			 "{M2Tech,Eeaudio},"
> +			 "{The Chord Company,CHORD},"
> +			 "{AVA Group A/S,Vitus}}");
> +
> +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
> +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */
> +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
> +
> +#define DRIVER_NAME "snd-usb-hiface"
> +#define CARD_NAME "hiFace"
> +
> +module_param_array(index, int, NULL, 0444);
> +MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
> +module_param_array(id, charp, NULL, 0444);
> +MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
> +module_param_array(enable, bool, NULL, 0444);
> +MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
> +
> +static DEFINE_MUTEX(register_mutex);
> +
> +struct hiface_vendor_quirk {
> +	const char *device_name;
> +	u8 extra_freq;
> +};
> +
> +static int hiface_chip_create(struct usb_device *device, int idx,
> +			      const struct hiface_vendor_quirk *quirk,
> +			      struct hiface_chip **rchip)
> +{
> +	struct snd_card *card = NULL;
> +	struct hiface_chip *chip;
> +	int ret;
> +	int len;
> +
> +	*rchip = NULL;
> +
> +	/* if we are here, card can be registered in alsa. */
> +	ret = snd_card_create(index[idx], id[idx], THIS_MODULE, sizeof(*chip), &card);
> +	if (ret < 0) {
> +		dev_err(&device->dev, "cannot create alsa card.\n");
> +		return ret;
> +	}
> +
> +	strlcpy(card->driver, DRIVER_NAME, sizeof(card->driver));
> +
> +	if (quirk && quirk->device_name)
> +		strlcpy(card->shortname, quirk->device_name, sizeof(card->shortname));
> +	else
> +		strlcpy(card->shortname, "M2Tech generic audio", sizeof(card->shortname));
> +
> +	strlcat(card->longname, card->shortname, sizeof(card->longname));
> +	len = strlcat(card->longname, " at ", sizeof(card->longname));
> +	if (len < sizeof(card->longname))
> +		usb_make_path(device, card->longname + len,
> +			      sizeof(card->longname) - len);
> +
> +	chip = card->private_data;
> +	chip->dev = device;
> +	chip->card = card;
> +
> +	*rchip = chip;
> +	return 0;
> +}
> +
> +static int hiface_chip_probe(struct usb_interface *intf,
> +			     const struct usb_device_id *usb_id)
> +{
> +	const struct hiface_vendor_quirk *quirk = (struct hiface_vendor_quirk *)usb_id->driver_info;
> +	int ret;
> +	int i;
> +	struct hiface_chip *chip;
> +	struct usb_device *device = interface_to_usbdev(intf);
> +
> +	ret = usb_set_interface(device, 0, 0);
> +	if (ret != 0) {
> +		dev_err(&device->dev, "can't set first interface for " CARD_NAME " device.\n");
> +		return -EIO;
> +	}
> +
> +	/* check whether the card is already registered */
> +	chip = NULL;
> +	mutex_lock(&register_mutex);
> +
> +	for (i = 0; i < SNDRV_CARDS; i++)
> +		if (enable[i])
> +			break;
> +
> +	if (i >= SNDRV_CARDS) {
> +		dev_err(&device->dev, "no available " CARD_NAME " audio device\n");
> +		ret = -ENODEV;
> +		goto err;
> +	}
> +
> +	ret = hiface_chip_create(device, i, quirk, &chip);
> +	if (ret < 0)
> +		goto err;
> +
> +	snd_card_set_dev(chip->card, &intf->dev);
> +
> +	ret = hiface_pcm_init(chip, quirk ? quirk->extra_freq : 0);
> +	if (ret < 0)
> +		goto err_chip_destroy;
> +
> +	ret = snd_card_register(chip->card);
> +	if (ret < 0) {
> +		dev_err(&device->dev, "cannot register " CARD_NAME " card\n");
> +		goto err_chip_destroy;
> +	}
> +
> +	mutex_unlock(&register_mutex);
> +
> +	usb_set_intfdata(intf, chip);
> +	return 0;
> +
> +err_chip_destroy:
> +	snd_card_free(chip->card);
> +err:
> +	mutex_unlock(&register_mutex);
> +	return ret;
> +}
> +
> +static void hiface_chip_disconnect(struct usb_interface *intf)
> +{
> +	struct hiface_chip *chip;
> +	struct snd_card *card;
> +
> +	chip = usb_get_intfdata(intf);
> +	if (!chip)
> +		return;
> +
> +	card = chip->card;
> +
> +	/* Make sure that the userspace cannot create new request */
> +	snd_card_disconnect(card);
> +
> +	hiface_pcm_abort(chip);
> +	snd_card_free_when_closed(card);
> +}
> +
> +static const struct usb_device_id device_table[] = {
> +	{
> +		USB_DEVICE(0x04b4, 0x0384),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Young",
> +			.extra_freq = 1,
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x930b),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "hiFace",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x931b),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "North Star",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x931c),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "W4S Young",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x931d),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Corrson",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x931e),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "AUDIA",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x931f),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "SL Audio",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x9320),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Empirical",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x04b4, 0x9321),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Rockna",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x9001),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Pathos",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x9002),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Metronome",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x9006),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "CAD",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x9008),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Audio Esclusive",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x931c),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Rotel",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x249c, 0x932c),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Eeaudio",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x245f, 0x931c),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "CHORD",
> +		}
> +	},
> +	{
> +		USB_DEVICE(0x25c6, 0x9002),
> +		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
> +			.device_name = "Vitus",
> +		}
> +	},
> +	{}
> +};
> +
> +MODULE_DEVICE_TABLE(usb, device_table);
> +
> +static struct usb_driver hiface_usb_driver = {
> +	.name = DRIVER_NAME,
> +	.probe = hiface_chip_probe,
> +	.disconnect = hiface_chip_disconnect,
> +	.id_table = device_table,
> +};
> +
> +module_usb_driver(hiface_usb_driver);
> diff --git a/sound/usb/hiface/chip.h b/sound/usb/hiface/chip.h
> new file mode 100644
> index 0000000..189a137
> --- /dev/null
> +++ b/sound/usb/hiface/chip.h
> @@ -0,0 +1,30 @@
> +/*
> + * Linux driver for M2Tech hiFace compatible devices
> + *
> + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> + *
> + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> + *           Antonio Ospite <ao2@amarulasolutions.com>
> + *
> + * The driver is based on the work done in TerraTec DMX 6Fire USB
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#ifndef HIFACE_CHIP_H
> +#define HIFACE_CHIP_H
> +
> +#include <linux/usb.h>
> +#include <sound/core.h>
> +
> +struct pcm_runtime;
> +
> +struct hiface_chip {
> +	struct usb_device *dev;
> +	struct snd_card *card;
> +	struct pcm_runtime *pcm;
> +};
> +#endif /* HIFACE_CHIP_H */
> diff --git a/sound/usb/hiface/pcm.c b/sound/usb/hiface/pcm.c
> new file mode 100644
> index 0000000..6430ed2
> --- /dev/null
> +++ b/sound/usb/hiface/pcm.c
> @@ -0,0 +1,621 @@
> +/*
> + * Linux driver for M2Tech hiFace compatible devices
> + *
> + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> + *
> + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> + *           Antonio Ospite <ao2@amarulasolutions.com>
> + *
> + * The driver is based on the work done in TerraTec DMX 6Fire USB
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#include <linux/slab.h>
> +#include <sound/pcm.h>
> +
> +#include "pcm.h"
> +#include "chip.h"
> +
> +#define OUT_EP          0x2
> +#define PCM_N_URBS      8
> +#define PCM_PACKET_SIZE 4096
> +#define PCM_BUFFER_SIZE (2 * PCM_N_URBS * PCM_PACKET_SIZE)
> +
> +struct pcm_urb {
> +	struct hiface_chip *chip;
> +
> +	struct urb instance;
> +	struct usb_anchor submitted;
> +	u8 *buffer;
> +};
> +
> +struct pcm_substream {
> +	spinlock_t lock;
> +	struct snd_pcm_substream *instance;
> +
> +	bool active;
> +	snd_pcm_uframes_t dma_off;    /* current position in alsa dma_area */
> +	snd_pcm_uframes_t period_off; /* current position in current period */
> +};
> +
> +enum { /* pcm streaming states */
> +	STREAM_DISABLED, /* no pcm streaming */
> +	STREAM_STARTING, /* pcm streaming requested, waiting to become ready */
> +	STREAM_RUNNING,  /* pcm streaming running */
> +	STREAM_STOPPING
> +};
> +
> +struct pcm_runtime {
> +	struct hiface_chip *chip;
> +	struct snd_pcm *instance;
> +
> +	struct pcm_substream playback;
> +	bool panic; /* if set driver won't do anymore pcm on device */
> +
> +	struct pcm_urb out_urbs[PCM_N_URBS];
> +
> +	struct mutex stream_mutex;
> +	u8 stream_state; /* one of STREAM_XXX */
> +	u8 extra_freq;
> +	wait_queue_head_t stream_wait_queue;
> +	bool stream_wait_cond;
> +};
> +
> +static const unsigned int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000,
> +				      352800, 384000 };
> +static const struct snd_pcm_hw_constraint_list constraints_extra_rates = {
> +	.count = ARRAY_SIZE(rates),
> +	.list = rates,
> +	.mask = 0,
> +};
> +
> +static const struct snd_pcm_hardware pcm_hw = {
> +	.info = SNDRV_PCM_INFO_MMAP |
> +		SNDRV_PCM_INFO_INTERLEAVED |
> +		SNDRV_PCM_INFO_BLOCK_TRANSFER |
> +		SNDRV_PCM_INFO_PAUSE |
> +		SNDRV_PCM_INFO_MMAP_VALID |
> +		SNDRV_PCM_INFO_BATCH,
> +
> +	.formats = SNDRV_PCM_FMTBIT_S32_LE,
> +
> +	.rates = SNDRV_PCM_RATE_44100 |
> +		SNDRV_PCM_RATE_48000 |
> +		SNDRV_PCM_RATE_88200 |
> +		SNDRV_PCM_RATE_96000 |
> +		SNDRV_PCM_RATE_176400 |
> +		SNDRV_PCM_RATE_192000,
> +
> +	.rate_min = 44100,
> +	.rate_max = 192000, /* changes in hiface_pcm_open to support extra rates */
> +	.channels_min = 2,
> +	.channels_max = 2,
> +	.buffer_bytes_max = PCM_BUFFER_SIZE,
> +	.period_bytes_min = PCM_PACKET_SIZE,
> +	.period_bytes_max = PCM_BUFFER_SIZE,
> +	.periods_min = 2,
> +	.periods_max = 1024
> +};
> +
> +/* message values used to change the sample rate */
> +#define HIFACE_SET_RATE_REQUEST 0xb0
> +
> +#define HIFACE_RATE_44100  0x43
> +#define HIFACE_RATE_48000  0x4b
> +#define HIFACE_RATE_88200  0x42
> +#define HIFACE_RATE_96000  0x4a
> +#define HIFACE_RATE_176400 0x40
> +#define HIFACE_RATE_192000 0x48
> +#define HIFACE_RATE_352000 0x58
> +#define HIFACE_RATE_384000 0x68
> +
> +static int hiface_pcm_set_rate(struct pcm_runtime *rt, unsigned int rate)
> +{
> +	struct usb_device *device = rt->chip->dev;
> +	u16 rate_value;
> +	int ret;
> +
> +	/* We are already sure that the rate is supported here thanks to
> +	 * ALSA constraints
> +	 */
> +	switch (rate) {
> +	case 44100:
> +		rate_value = HIFACE_RATE_44100;
> +		break;
> +	case 48000:
> +		rate_value = HIFACE_RATE_48000;
> +		break;
> +	case 88200:
> +		rate_value = HIFACE_RATE_88200;
> +		break;
> +	case 96000:
> +		rate_value = HIFACE_RATE_96000;
> +		break;
> +	case 176400:
> +		rate_value = HIFACE_RATE_176400;
> +		break;
> +	case 192000:
> +		rate_value = HIFACE_RATE_192000;
> +		break;
> +	case 352000:
> +		rate_value = HIFACE_RATE_352000;
> +		break;
> +	case 384000:
> +		rate_value = HIFACE_RATE_384000;
> +		break;
> +	default:
> +		dev_err(&device->dev, "Unsupported rate %d\n", rate);
> +		return -EINVAL;
> +	}
> +
> +	/*
> +	 * USBIO: Vendor 0xb0(wValue=0x0043, wIndex=0x0000)
> +	 * 43 b0 43 00 00 00 00 00
> +	 * USBIO: Vendor 0xb0(wValue=0x004b, wIndex=0x0000)
> +	 * 43 b0 4b 00 00 00 00 00
> +	 * This control message doesn't have any ack from the
> +	 * other side
> +	 */
> +	ret = usb_control_msg(device, usb_sndctrlpipe(device, 0),
> +			      HIFACE_SET_RATE_REQUEST,
> +			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
> +			      rate_value, 0, NULL, 0, 100);
> +	if (ret < 0) {
> +		dev_err(&device->dev, "Error setting samplerate %d.\n", rate);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static struct pcm_substream *hiface_pcm_get_substream(struct snd_pcm_substream
> +						      *alsa_sub)
> +{
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	struct device *device = &rt->chip->dev->dev;
> +
> +	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
> +		return &rt->playback;
> +
> +	dev_err(device, "Error getting pcm substream slot.\n");
> +	return NULL;
> +}
> +
> +/* call with stream_mutex locked */
> +static void hiface_pcm_stream_stop(struct pcm_runtime *rt)
> +{
> +	int i, time;
> +
> +	if (rt->stream_state != STREAM_DISABLED) {
> +		rt->stream_state = STREAM_STOPPING;
> +
> +		for (i = 0; i < PCM_N_URBS; i++) {
> +			time = usb_wait_anchor_empty_timeout(
> +					&rt->out_urbs[i].submitted, 100);
> +			if (!time)
> +				usb_kill_anchored_urbs(
> +					&rt->out_urbs[i].submitted);
> +			usb_kill_urb(&rt->out_urbs[i].instance);
> +		}
> +
> +		rt->stream_state = STREAM_DISABLED;
> +	}
> +}
> +
> +/* call with stream_mutex locked */
> +static int hiface_pcm_stream_start(struct pcm_runtime *rt)
> +{
> +	int ret = 0;
> +	int i;
> +
> +	if (rt->stream_state == STREAM_DISABLED) {
> +
> +		/* reset panic state when starting a new stream */
> +		rt->panic = false;
> +
> +		/* submit our out urbs zero init */
> +		rt->stream_state = STREAM_STARTING;
> +		for (i = 0; i < PCM_N_URBS; i++) {
> +			memset(rt->out_urbs[i].buffer, 0, PCM_PACKET_SIZE);
> +			usb_anchor_urb(&rt->out_urbs[i].instance,
> +				       &rt->out_urbs[i].submitted);
> +			ret = usb_submit_urb(&rt->out_urbs[i].instance,
> +					     GFP_ATOMIC);
> +			if (ret) {
> +				hiface_pcm_stream_stop(rt);
> +				return ret;
> +			}
> +		}
> +
> +		/* wait for first out urb to return (sent in in urb handler) */
> +		wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond,
> +				   HZ);
> +		if (rt->stream_wait_cond) {
> +			struct device *device = &rt->chip->dev->dev;
> +			dev_dbg(device, "%s: Stream is running wakeup event\n",
> +				 __func__);
> +			rt->stream_state = STREAM_RUNNING;
> +		} else {
> +			hiface_pcm_stream_stop(rt);
> +			return -EIO;
> +		}
> +	}
> +	return ret;
> +}
> +
> +/* The hardware wants word-swapped 32-bit values */
> +static void memcpy_swahw32(u8 *dest, u8 *src, unsigned int n)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < n / 4; i++)
> +		((u32 *)dest)[i] = swahw32(((u32 *)src)[i]);
> +}
> +
> +/* call with substream locked */
> +/* returns true if a period elapsed */
> +static bool hiface_pcm_playback(struct pcm_substream *sub, struct pcm_urb *urb)
> +{
> +	struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
> +	struct device *device = &urb->chip->dev->dev;
> +	u8 *source;
> +	unsigned int pcm_buffer_size;
> +
> +	WARN_ON(alsa_rt->format != SNDRV_PCM_FORMAT_S32_LE);
> +
> +	pcm_buffer_size = snd_pcm_lib_buffer_bytes(sub->instance);
> +
> +	if (sub->dma_off + PCM_PACKET_SIZE <= pcm_buffer_size) {
> +		dev_dbg(device, "%s: (1) buffer_size %#x dma_offset %#x\n", __func__,
> +			 (unsigned int) pcm_buffer_size,
> +			 (unsigned int) sub->dma_off);
> +
> +		source = alsa_rt->dma_area + sub->dma_off;
> +		memcpy_swahw32(urb->buffer, source, PCM_PACKET_SIZE);
> +	} else {
> +		/* wrap around at end of ring buffer */
> +		unsigned int len;
> +
> +		dev_dbg(device, "%s: (2) buffer_size %#x dma_offset %#x\n", __func__,
> +			 (unsigned int) pcm_buffer_size,
> +			 (unsigned int) sub->dma_off);
> +
> +		len = pcm_buffer_size - sub->dma_off;
> +
> +		source = alsa_rt->dma_area + sub->dma_off;
> +		memcpy_swahw32(urb->buffer, source, len);
> +
> +		source = alsa_rt->dma_area;
> +		memcpy_swahw32(urb->buffer + len, source,
> +			       PCM_PACKET_SIZE - len);
> +	}
> +	sub->dma_off += PCM_PACKET_SIZE;
> +	if (sub->dma_off >= pcm_buffer_size)
> +		sub->dma_off -= pcm_buffer_size;
> +
> +	sub->period_off += PCM_PACKET_SIZE;
> +	if (sub->period_off >= alsa_rt->period_size) {
> +		sub->period_off %= alsa_rt->period_size;
> +		return true;
> +	}
> +	return false;
> +}
> +
> +static void hiface_pcm_out_urb_handler(struct urb *usb_urb)
> +{
> +	struct pcm_urb *out_urb = usb_urb->context;
> +	struct pcm_runtime *rt = out_urb->chip->pcm;
> +	struct pcm_substream *sub;
> +	bool do_period_elapsed = false;
> +	unsigned long flags;
> +	int ret;
> +
> +	if (rt->panic || rt->stream_state == STREAM_STOPPING)
> +		return;
> +
> +	if (unlikely(usb_urb->status == -ENOENT ||	/* unlinked */
> +		     usb_urb->status == -ENODEV ||	/* device removed */
> +		     usb_urb->status == -ECONNRESET ||	/* unlinked */
> +		     usb_urb->status == -ESHUTDOWN)) {	/* device disabled */
> +		goto out_fail;
> +	}
> +
> +	if (rt->stream_state == STREAM_STARTING) {
> +		rt->stream_wait_cond = true;
> +		wake_up(&rt->stream_wait_queue);
> +	}
> +
> +	/* now send our playback data (if a free out urb was found) */
> +	sub = &rt->playback;
> +	spin_lock_irqsave(&sub->lock, flags);
> +	if (sub->active)
> +		do_period_elapsed = hiface_pcm_playback(sub, out_urb);
> +	else
> +		memset(out_urb->buffer, 0, PCM_PACKET_SIZE);
> +
> +	spin_unlock_irqrestore(&sub->lock, flags);
> +
> +	if (do_period_elapsed)
> +		snd_pcm_period_elapsed(sub->instance);
> +
> +	ret = usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
> +	if (ret < 0)
> +		goto out_fail;
> +
> +	return;
> +
> +out_fail:
> +	rt->panic = true;
> +}
> +
> +static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub)
> +{
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	struct pcm_substream *sub = NULL;
> +	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
> +	int ret;
> +
> +	if (rt->panic)
> +		return -EPIPE;
> +
> +	mutex_lock(&rt->stream_mutex);
> +	alsa_rt->hw = pcm_hw;
> +
> +	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
> +		sub = &rt->playback;
> +
> +	if (!sub) {
> +		struct device *device = &rt->chip->dev->dev;
> +		mutex_unlock(&rt->stream_mutex);
> +		dev_err(device, "Invalid stream type\n");
> +		return -EINVAL;
> +	}
> +
> +	if (rt->extra_freq) {
> +		alsa_rt->hw.rates |= SNDRV_PCM_RATE_KNOT;
> +		alsa_rt->hw.rate_max = 384000;
> +
> +		/* explicit constraints needed as we added SNDRV_PCM_RATE_KNOT */
> +		ret = snd_pcm_hw_constraint_list(alsa_sub->runtime, 0,
> +						 SNDRV_PCM_HW_PARAM_RATE,
> +						 &constraints_extra_rates);
> +		if (ret < 0) {
> +			mutex_unlock(&rt->stream_mutex);
> +			return ret;
> +		}
> +	}
> +
> +	sub->instance = alsa_sub;
> +	sub->active = false;
> +	mutex_unlock(&rt->stream_mutex);
> +	return 0;
> +}
> +
> +static int hiface_pcm_close(struct snd_pcm_substream *alsa_sub)
> +{
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> +	unsigned long flags;
> +
> +	if (rt->panic)
> +		return 0;
> +
> +	mutex_lock(&rt->stream_mutex);
> +	if (sub) {
> +		hiface_pcm_stream_stop(rt);
> +
> +		/* deactivate substream */
> +		spin_lock_irqsave(&sub->lock, flags);
> +		sub->instance = NULL;
> +		sub->active = false;
> +		spin_unlock_irqrestore(&sub->lock, flags);
> +
> +	}
> +	mutex_unlock(&rt->stream_mutex);
> +	return 0;
> +}
> +
> +static int hiface_pcm_hw_params(struct snd_pcm_substream *alsa_sub,
> +				struct snd_pcm_hw_params *hw_params)
> +{
> +	return snd_pcm_lib_alloc_vmalloc_buffer(alsa_sub,
> +						params_buffer_bytes(hw_params));
> +}
> +
> +static int hiface_pcm_hw_free(struct snd_pcm_substream *alsa_sub)
> +{
> +	return snd_pcm_lib_free_vmalloc_buffer(alsa_sub);
> +}
> +
> +static int hiface_pcm_prepare(struct snd_pcm_substream *alsa_sub)
> +{
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> +	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
> +	int ret;
> +
> +	if (rt->panic)
> +		return -EPIPE;
> +	if (!sub)
> +		return -ENODEV;
> +
> +	mutex_lock(&rt->stream_mutex);
> +
> +	sub->dma_off = 0;
> +	sub->period_off = 0;
> +
> +	if (rt->stream_state == STREAM_DISABLED) {
> +
> +		ret = hiface_pcm_set_rate(rt, alsa_rt->rate);
> +		if (ret) {
> +			mutex_unlock(&rt->stream_mutex);
> +			return ret;
> +		}
> +		ret = hiface_pcm_stream_start(rt);
> +		if (ret) {
> +			mutex_unlock(&rt->stream_mutex);
> +			return ret;
> +		}
> +	}
> +	mutex_unlock(&rt->stream_mutex);
> +	return 0;
> +}
> +
> +static int hiface_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
> +{
> +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +
> +	if (rt->panic)
> +		return -EPIPE;
> +	if (!sub)
> +		return -ENODEV;
> +
> +	switch (cmd) {
> +	case SNDRV_PCM_TRIGGER_START:
> +	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
> +		spin_lock_irq(&sub->lock);
> +		sub->active = true;
> +		spin_unlock_irq(&sub->lock);
> +		return 0;
> +
> +	case SNDRV_PCM_TRIGGER_STOP:
> +	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
> +		spin_lock_irq(&sub->lock);
> +		sub->active = false;
> +		spin_unlock_irq(&sub->lock);
> +		return 0;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static snd_pcm_uframes_t hiface_pcm_pointer(struct snd_pcm_substream *alsa_sub)
> +{
> +	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
> +	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
> +	unsigned long flags;
> +	snd_pcm_uframes_t dma_offset;
> +
> +	if (rt->panic || !sub)
> +		return SNDRV_PCM_STATE_XRUN;
> +
> +	spin_lock_irqsave(&sub->lock, flags);
> +	dma_offset = sub->dma_off;
> +	spin_unlock_irqrestore(&sub->lock, flags);
> +	return bytes_to_frames(alsa_sub->runtime, dma_offset);
> +}
> +
> +static struct snd_pcm_ops pcm_ops = {
> +	.open = hiface_pcm_open,
> +	.close = hiface_pcm_close,
> +	.ioctl = snd_pcm_lib_ioctl,
> +	.hw_params = hiface_pcm_hw_params,
> +	.hw_free = hiface_pcm_hw_free,
> +	.prepare = hiface_pcm_prepare,
> +	.trigger = hiface_pcm_trigger,
> +	.pointer = hiface_pcm_pointer,
> +	.page = snd_pcm_lib_get_vmalloc_page,
> +	.mmap = snd_pcm_lib_mmap_vmalloc,
> +};
> +
> +static int hiface_pcm_init_urb(struct pcm_urb *urb,
> +			       struct hiface_chip *chip,
> +			       unsigned int ep,
> +			       void (*handler)(struct urb *))
> +{
> +	urb->chip = chip;
> +	usb_init_urb(&urb->instance);
> +
> +	urb->buffer = kzalloc(PCM_PACKET_SIZE, GFP_KERNEL);
> +	if (!urb->buffer)
> +		return -ENOMEM;
> +
> +	usb_fill_bulk_urb(&urb->instance, chip->dev,
> +			  usb_sndbulkpipe(chip->dev, ep), (void *)urb->buffer,
> +			  PCM_PACKET_SIZE, handler, urb);
> +	init_usb_anchor(&urb->submitted);
> +
> +	return 0;
> +}
> +
> +void hiface_pcm_abort(struct hiface_chip *chip)
> +{
> +	struct pcm_runtime *rt = chip->pcm;
> +
> +	if (rt) {
> +		rt->panic = true;
> +
> +		mutex_lock(&rt->stream_mutex);
> +		hiface_pcm_stream_stop(rt);
> +		mutex_unlock(&rt->stream_mutex);
> +	}
> +}
> +
> +static void hiface_pcm_destroy(struct hiface_chip *chip)
> +{
> +	struct pcm_runtime *rt = chip->pcm;
> +	int i;
> +
> +	for (i = 0; i < PCM_N_URBS; i++)
> +		kfree(rt->out_urbs[i].buffer);
> +
> +	kfree(chip->pcm);
> +	chip->pcm = NULL;
> +}
> +
> +static void hiface_pcm_free(struct snd_pcm *pcm)
> +{
> +	struct pcm_runtime *rt = pcm->private_data;
> +
> +	if (rt)
> +		hiface_pcm_destroy(rt->chip);
> +}
> +
> +int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq)
> +{
> +	int i;
> +	int ret;
> +	struct snd_pcm *pcm;
> +	struct pcm_runtime *rt;
> +
> +	rt = kzalloc(sizeof(*rt), GFP_KERNEL);
> +	if (!rt)
> +		return -ENOMEM;
> +
> +	rt->chip = chip;
> +	rt->stream_state = STREAM_DISABLED;
> +	if (extra_freq)
> +		rt->extra_freq = 1;
> +
> +	init_waitqueue_head(&rt->stream_wait_queue);
> +	mutex_init(&rt->stream_mutex);
> +	spin_lock_init(&rt->playback.lock);
> +
> +	for (i = 0; i < PCM_N_URBS; i++)
> +		hiface_pcm_init_urb(&rt->out_urbs[i], chip, OUT_EP,
> +				    hiface_pcm_out_urb_handler);
> +
> +	ret = snd_pcm_new(chip->card, "USB-SPDIF Audio", 0, 1, 0, &pcm);
> +	if (ret < 0) {
> +		kfree(rt);
> +		dev_err(&chip->dev->dev, "Cannot create pcm instance\n");
> +		return ret;
> +	}
> +
> +	pcm->private_data = rt;
> +	pcm->private_free = hiface_pcm_free;
> +
> +	strlcpy(pcm->name, "USB-SPDIF Audio", sizeof(pcm->name));
> +	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops);
> +
> +	rt->instance = pcm;
> +
> +	chip->pcm = rt;
> +	return 0;
> +}
> diff --git a/sound/usb/hiface/pcm.h b/sound/usb/hiface/pcm.h
> new file mode 100644
> index 0000000..77edd7c
> --- /dev/null
> +++ b/sound/usb/hiface/pcm.h
> @@ -0,0 +1,24 @@
> +/*
> + * Linux driver for M2Tech hiFace compatible devices
> + *
> + * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
> + *
> + * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
> + *           Antonio Ospite <ao2@amarulasolutions.com>
> + *
> + * The driver is based on the work done in TerraTec DMX 6Fire USB
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + */
> +
> +#ifndef HIFACE_PCM_H
> +#define HIFACE_PCM_H
> +
> +struct hiface_chip;
> +
> +int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq);
> +void hiface_pcm_abort(struct hiface_chip *chip);
> +#endif /* HIFACE_PCM_H */
> -- 
> 1.8.3.1
> 

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

* [PATCH] Add M2Tech hiFace USB-SPDIF driver
@ 2013-02-10 23:06 Antonio Ospite
  0 siblings, 0 replies; 25+ messages in thread
From: Antonio Ospite @ 2013-02-10 23:06 UTC (permalink / raw)
  To: alsa-devel
  Cc: Takashi Iwai, Antonio Ospite, fchecconi, Pietro Cipriano,
	alberto, Michael Trimarchi

Add driver for M2Tech hiFace USB-SPDIF interface and compatible devices.

The supported products are:

  * M2Tech Young
  * M2Tech hiFace
  * M2Tech North Star
  * M2Tech W4S Young
  * M2Tech Corrson
  * M2Tech AUDIA
  * M2Tech SL Audio
  * M2Tech Empirical
  * M2Tech Rockna
  * M2Tech Pathos
  * M2Tech Metronome
  * M2Tech CAD
  * M2Tech Audio Esclusive
  * M2Tech Rotel
  * M2Tech Eeaudio
  * The Chord Company CHORD
  * AVA Group A/S Vitus

Signed-off-by: Antonio Ospite <ao2@amarulasolutions.com>
---

Hi,

this is my first ALSA driver I still have some doubts and I marked them in the
comments below with a XXX prefix, could someone please take a look at them?

I plan on submitting a version 2 of the driver after I get some feedback,
especially on those doubts.

I also noticed that many drivers under sound/usb/ have text in the Kconfig
entry stating "Say Y here to include support for ..." but I only see "[N/m/?]"
as options when I run "make oldconfig" maybe because of some dependency, I am
not sure, should such text be removed in order to avoid confusion?

Thanks,
   Antonio

 sound/usb/Kconfig         |   33 +++
 sound/usb/Makefile        |    2 +-
 sound/usb/hiface/Makefile |    2 +
 sound/usb/hiface/chip.c   |  331 ++++++++++++++++++++++
 sound/usb/hiface/chip.h   |   34 +++
 sound/usb/hiface/pcm.c    |  666 +++++++++++++++++++++++++++++++++++++++++++++
 sound/usb/hiface/pcm.h    |   24 ++
 7 files changed, 1091 insertions(+), 1 deletion(-)
 create mode 100644 sound/usb/hiface/Makefile
 create mode 100644 sound/usb/hiface/chip.c
 create mode 100644 sound/usb/hiface/chip.h
 create mode 100644 sound/usb/hiface/pcm.c
 create mode 100644 sound/usb/hiface/pcm.h

diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig
index 225dfd7..2b206e8 100644
--- a/sound/usb/Kconfig
+++ b/sound/usb/Kconfig
@@ -115,5 +115,38 @@ config SND_USB_6FIRE
           and further help can be found at
           http://sixfireusb.sourceforge.net
 
+config SND_USB_HIFACE
+        tristate "M2Tech hiFace USB-SPDIF driver"
+        select SND_PCM
+        help
+	  Say Y here to include support for M2Tech hiFace USB-SPDIF interface.
+
+	  M2Tech hiFace and compatible devices offer a Hi-End S/PDIF Output
+	  Interface, see http://www.m2tech.biz/hiface.html
+
+	  This driver supports the original M2Tech hiFace and some other
+	  compatible devices. The supported products are:
+
+	    * M2Tech Young
+	    * M2Tech hiFace
+	    * M2Tech North Star
+	    * M2Tech W4S Young
+	    * M2Tech Corrson
+	    * M2Tech AUDIA
+	    * M2Tech SL Audio
+	    * M2Tech Empirical
+	    * M2Tech Rockna
+	    * M2Tech Pathos
+	    * M2Tech Metronome
+	    * M2Tech CAD
+	    * M2Tech Audio Esclusive
+	    * M2Tech Rotel
+	    * M2Tech Eeaudio
+	    * The Chord Company CHORD
+	    * AVA Group A/S Vitus
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called snd-usb-hiface.
+
 endif	# SND_USB
 
diff --git a/sound/usb/Makefile b/sound/usb/Makefile
index ac256dc..abe668f 100644
--- a/sound/usb/Makefile
+++ b/sound/usb/Makefile
@@ -23,4 +23,4 @@ obj-$(CONFIG_SND_USB_UA101) += snd-usbmidi-lib.o
 obj-$(CONFIG_SND_USB_USX2Y) += snd-usbmidi-lib.o
 obj-$(CONFIG_SND_USB_US122L) += snd-usbmidi-lib.o
 
-obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/
+obj-$(CONFIG_SND) += misc/ usx2y/ caiaq/ 6fire/ hiface/
diff --git a/sound/usb/hiface/Makefile b/sound/usb/hiface/Makefile
new file mode 100644
index 0000000..463b136
--- /dev/null
+++ b/sound/usb/hiface/Makefile
@@ -0,0 +1,2 @@
+snd-usb-hiface-objs := chip.o pcm.o
+obj-$(CONFIG_SND_USB_HIFACE) += snd-usb-hiface.o
diff --git a/sound/usb/hiface/chip.c b/sound/usb/hiface/chip.c
new file mode 100644
index 0000000..dce74e9
--- /dev/null
+++ b/sound/usb/hiface/chip.c
@@ -0,0 +1,331 @@
+/*
+ * Linux driver for M2Tech HiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
+ *           Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <sound/initval.h>
+
+#include "chip.h"
+#include "pcm.h"
+
+MODULE_AUTHOR("Michael Trimarchi <michael@amarulasolutions.com>");
+MODULE_AUTHOR("Antonio Ospite <ao2@amarulasolutions.com>");
+MODULE_DESCRIPTION("M2Tech HiFace USB audio driver");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{HiFace, Evo}}");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for card */
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
+
+#define DRIVER_NAME "snd-usb-hiface"
+#define CARD_NAME "HiFace"
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for " CARD_NAME " soundcard.");
+module_param_array(id, charp, NULL, 0444);
+MODULE_PARM_DESC(id, "ID string for " CARD_NAME " soundcard.");
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable " CARD_NAME " soundcard.");
+
+static struct hiface_chip *chips[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
+
+static DEFINE_MUTEX(register_mutex);
+
+struct hiface_vendor_quirk {
+	const char *device_name;
+	u8 extra_freq;
+};
+
+static int hiface_dev_free(struct snd_device *device)
+{
+	struct hiface_chip *chip = device->device_data;
+	kfree(chip);
+	return 0;
+}
+
+static int hiface_chip_create(struct usb_device *device, int idx,
+			      const struct hiface_vendor_quirk *quirk,
+			      struct hiface_chip **rchip)
+{
+	struct snd_card *card = NULL;
+	struct hiface_chip *chip;
+	int ret;
+	static struct snd_device_ops ops = {
+		.dev_free =	hiface_dev_free,
+	};
+
+	*rchip = NULL;
+
+	/* if we are here, card can be registered in alsa. */
+	ret = snd_card_create(index[idx], id[idx], THIS_MODULE, 0, &card);
+	if (ret < 0) {
+		snd_printk(KERN_ERR "cannot create alsa card.\n");
+		return ret;
+	}
+
+	strcpy(card->driver, DRIVER_NAME);
+
+	if (quirk && quirk->device_name)
+		strcpy(card->shortname, quirk->device_name);
+	else
+		strcpy(card->shortname, "M2Tech generic audio");
+
+	sprintf(card->longname, "%s at %d:%d", card->shortname,
+			device->bus->busnum, device->devnum);
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (!chip) {
+		snd_card_free(card);
+		return -ENOMEM;
+	}
+
+	chip->dev = device;
+	chip->index = idx;
+	chip->card = card;
+
+	ret = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops);
+	if (ret < 0) {
+			kfree(chip);
+			snd_card_free(card);
+			return ret;
+	}
+
+	*rchip = chip;
+	return 0;
+}
+
+static int hiface_chip_probe(struct usb_interface *intf,
+			     const struct usb_device_id *usb_id)
+{
+	const struct hiface_vendor_quirk *quirk = (struct hiface_vendor_quirk *)usb_id->driver_info;
+	int ret;
+	int i;
+	struct hiface_chip *chip;
+	struct usb_device *device = interface_to_usbdev(intf);
+
+	pr_info("Probe " DRIVER_NAME " driver.\n");
+
+	ret = usb_set_interface(device, 0, 0);
+	if (ret != 0) {
+		snd_printk(KERN_ERR "can't set first interface for " CARD_NAME " device.\n");
+		return -EIO;
+	}
+
+	/* check whether the card is already registered */
+	chip = NULL;
+	mutex_lock(&register_mutex);
+	for (i = 0; i < SNDRV_CARDS; i++) {
+		if (chips[i] && chips[i]->dev == device) {
+			if (chips[i]->shutdown) {
+				snd_printk(KERN_ERR CARD_NAME " device is in the shutdown state, cannot create a card instance\n");
+				ret = -ENODEV;
+				goto err;
+			}
+			chip = chips[i];
+			break;
+		}
+	}
+	if (!chip) {
+		/* it's a fresh one.
+		 * now look for an empty slot and create a new card instance
+		 */
+		for (i = 0; i < SNDRV_CARDS; i++)
+			if (enable[i] && !chips[i]) {
+				ret = hiface_chip_create(device, i, quirk,
+							 &chip);
+				if (ret < 0)
+					goto err;
+
+				snd_card_set_dev(chip->card, &intf->dev);
+				break;
+			}
+		if (!chip) {
+			snd_printk(KERN_ERR "no available " CARD_NAME " audio device\n");
+			ret = -ENODEV;
+			goto err;
+		}
+	}
+
+	ret = hiface_pcm_init(chip, quirk ? quirk->extra_freq : 0);
+	if (ret < 0)
+		goto err_chip_destroy;
+
+	ret = snd_card_register(chip->card);
+	if (ret < 0) {
+		snd_printk(KERN_ERR "cannot register " CARD_NAME " card\n");
+		goto err_chip_destroy;
+	}
+
+	chips[chip->index] = chip;
+	chip->intf_count++;
+
+	mutex_unlock(&register_mutex);
+
+	usb_set_intfdata(intf, chip);
+	return 0;
+
+err_chip_destroy:
+	snd_card_free(chip->card);
+err:
+	mutex_unlock(&register_mutex);
+	return ret;
+}
+
+static void hiface_chip_disconnect(struct usb_interface *intf)
+{
+	struct hiface_chip *chip;
+	struct snd_card *card;
+
+	pr_debug("%s: called.\n", __func__);
+
+	chip = usb_get_intfdata(intf);
+	if (!chip)
+		return;
+
+	card = chip->card;
+	chip->intf_count--;
+	if (chip->intf_count <= 0) {
+		/* Make sure that the userspace cannot create new request */
+		snd_card_disconnect(card);
+
+		mutex_lock(&register_mutex);
+		chips[chip->index] = NULL;
+		mutex_unlock(&register_mutex);
+
+		chip->shutdown = true;
+		hiface_pcm_abort(chip);
+		snd_card_free_when_closed(card);
+	}
+}
+
+static struct usb_device_id device_table[] = {
+	{
+		USB_DEVICE(0x04b4, 0x0384),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Young",
+			.extra_freq = 1,
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x930b),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "hiFace",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931b),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "North Star",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931c),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "W4S Young",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931d),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Corrson",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931e),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "AUDIA",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x931f),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "SL Audio",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x9320),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Empirical",
+		}
+	},
+	{
+		USB_DEVICE(0x04b4, 0x9321),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Rockna",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x9001),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Pathos",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x9002),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Metronome",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x9006),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "CAD",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x9008),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Audio Esclusive",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x931c),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Rotel",
+		}
+	},
+	{
+		USB_DEVICE(0x249c, 0x932c),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Eeaudio",
+		}
+	},
+	{
+		USB_DEVICE(0x245f, 0x931c),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "CHORD",
+		}
+	},
+	{
+		USB_DEVICE(0x25c6, 0x9002),
+		.driver_info = (unsigned long)&(const struct hiface_vendor_quirk) {
+			.device_name = "Vitus",
+		}
+	},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+static struct usb_driver hiface_usb_driver = {
+	.name = DRIVER_NAME,
+	.probe = hiface_chip_probe,
+	.disconnect = hiface_chip_disconnect,
+	.id_table = device_table,
+};
+
+module_usb_driver(hiface_usb_driver);
diff --git a/sound/usb/hiface/chip.h b/sound/usb/hiface/chip.h
new file mode 100644
index 0000000..c2e9fda
--- /dev/null
+++ b/sound/usb/hiface/chip.h
@@ -0,0 +1,34 @@
+/*
+ * Linux driver for M2Tech HiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
+ *           Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef HIFACE_CHIP_H
+#define HIFACE_CHIP_H
+
+#include <linux/usb.h>
+#include <sound/core.h>
+
+struct pcm_runtime;
+
+struct hiface_chip {
+	struct usb_device *dev;
+	struct snd_card *card;
+	int intf_count; /* number of registered interfaces */
+	int index; /* index in module parameter arrays */
+	bool shutdown;
+
+	struct pcm_runtime *pcm;
+};
+#endif /* HIFACE_CHIP_H */
diff --git a/sound/usb/hiface/pcm.c b/sound/usb/hiface/pcm.c
new file mode 100644
index 0000000..34fe0ed
--- /dev/null
+++ b/sound/usb/hiface/pcm.c
@@ -0,0 +1,666 @@
+/*
+ * Linux driver for M2Tech HiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
+ *           Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#include <linux/slab.h>
+#include <sound/pcm.h>
+
+#include "pcm.h"
+#include "chip.h"
+
+#define OUT_EP              0x2
+#define PCM_N_URBS          8
+#define PCM_MAX_PACKET_SIZE 4096
+#define MAX_BUFSIZE         (2 * PCM_N_URBS * PCM_MAX_PACKET_SIZE)
+
+struct pcm_urb {
+	struct hiface_chip *chip;
+
+	struct urb instance;
+	struct usb_anchor submitted;
+	u8 *buffer;
+};
+
+struct pcm_substream {
+	spinlock_t lock;
+	struct snd_pcm_substream *instance;
+
+	bool active;
+	snd_pcm_uframes_t dma_off;    /* current position in alsa dma_area */
+	snd_pcm_uframes_t period_off; /* current position in current period */
+};
+
+enum { /* pcm streaming states */
+	STREAM_DISABLED, /* no pcm streaming */
+	STREAM_STARTING, /* pcm streaming requested, waiting to become ready */
+	STREAM_RUNNING,  /* pcm streaming running */
+	STREAM_STOPPING
+};
+
+struct pcm_runtime {
+	struct hiface_chip *chip;
+	struct snd_pcm *instance;
+
+	struct pcm_substream playback;
+	bool panic; /* if set driver won't do anymore pcm on device */
+
+	struct pcm_urb out_urbs[PCM_N_URBS];
+
+	struct mutex stream_mutex;
+	u8 stream_state; /* one of STREAM_XXX */
+	u8 rate; /* one of PCM_RATE_XXX */
+	u8 extra_freq;
+	wait_queue_head_t stream_wait_queue;
+	bool stream_wait_cond;
+};
+
+static const unsigned int rates[] = { 44100, 48000, 88200, 96000, 176400, 192000,
+			     352800, 384000 };
+static struct snd_pcm_hw_constraint_list constraints_rates = {
+	.count = ARRAY_SIZE(rates) - 2, /* by default rates up to 192000 are supported */
+	.list = rates,
+	.mask = 0,
+};
+
+static const int rates_alsaid[] = {
+	SNDRV_PCM_RATE_44100, SNDRV_PCM_RATE_48000,
+	SNDRV_PCM_RATE_88200, SNDRV_PCM_RATE_96000,
+	SNDRV_PCM_RATE_176400, SNDRV_PCM_RATE_192000,
+	SNDRV_PCM_RATE_KNOT, SNDRV_PCM_RATE_KNOT };
+
+static const struct snd_pcm_hardware pcm_hw = {
+	.info = SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_PAUSE |
+		SNDRV_PCM_INFO_MMAP_VALID |
+		SNDRV_PCM_INFO_BATCH,
+
+	.formats = SNDRV_PCM_FMTBIT_S32_LE,
+
+	.rates = SNDRV_PCM_RATE_44100 |
+		SNDRV_PCM_RATE_48000 |
+		SNDRV_PCM_RATE_88200 |
+		SNDRV_PCM_RATE_96000 |
+		SNDRV_PCM_RATE_176400 |
+		SNDRV_PCM_RATE_192000 |
+		SNDRV_PCM_RATE_KNOT,
+
+	.rate_min = 44100,
+	.rate_max = 192000, /* changes in hiface_pcm_open to support extra rates */
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = MAX_BUFSIZE,
+	.period_bytes_min = PCM_MAX_PACKET_SIZE,
+	.period_bytes_max = MAX_BUFSIZE,
+	.periods_min = 2,
+	.periods_max = 1024
+};
+
+static int hiface_pcm_set_rate(struct pcm_runtime *rt, unsigned int rate)
+{
+	u8 rate_value[] = { 0x43, 0x4b, 0x42, 0x4a, 0x40, 0x48, 0x58, 0x68 };
+	int ret;
+	struct usb_device *device = rt->chip->dev;
+
+	/* We are already sure that the rate is supported here thanks to the
+	 * contraints set in hiface_pcm_open(), but we have to retrieve the
+	 * index to access rate_value.
+	 */
+	for (rt->rate = 0; rt->rate < ARRAY_SIZE(rates); rt->rate++)
+		if (rate == rates[rt->rate])
+			break;
+
+	if (unlikely(rt->rate == ARRAY_SIZE(rates))) {
+		pr_err("Unsupported rate %d\n", rate);
+		return -EINVAL;
+	}
+
+	/*
+	 * USBIO: Vendor 0xb0(wValue=0x0043, wIndex=0x0000)
+	 * 43 b0 43 00 00 00 00 00
+	 * USBIO: Vendor 0xb0(wValue=0x004b, wIndex=0x0000)
+	 * 43 b0 4b 00 00 00 00 00
+	 * This control message doesn't have any ack from the
+	 * other side
+	 */
+	ret = usb_control_msg(device, usb_sndctrlpipe(device, 0),
+				0xb0,
+				USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+				rate_value[rt->rate], 0, NULL, 0, 100);
+	if (ret < 0) {
+		snd_printk(KERN_ERR "Error setting samplerate %d.\n",
+				rates[rt->rate]);
+		return ret;
+	}
+
+	return 0;
+}
+
+static struct pcm_substream *hiface_pcm_get_substream(
+		struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+
+	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		return &rt->playback;
+
+	pr_debug("Error getting pcm substream slot.\n");
+	return NULL;
+}
+
+/* call with stream_mutex locked */
+static void hiface_pcm_stream_stop(struct pcm_runtime *rt)
+{
+	int i, time;
+
+	if (rt->stream_state != STREAM_DISABLED) {
+		rt->stream_state = STREAM_STOPPING;
+
+		for (i = 0; i < PCM_N_URBS; i++) {
+			time = usb_wait_anchor_empty_timeout(
+					&rt->out_urbs[i].submitted, 100);
+			if (!time)
+				usb_kill_anchored_urbs(
+					&rt->out_urbs[i].submitted);
+			usb_kill_urb(&rt->out_urbs[i].instance);
+		}
+
+		rt->stream_state = STREAM_DISABLED;
+	}
+}
+
+/* call with stream_mutex locked */
+static int hiface_pcm_stream_start(struct pcm_runtime *rt)
+{
+	int ret = 0, i;
+
+	if (rt->stream_state == STREAM_DISABLED) {
+		/* submit our out urbs zero init */
+		rt->stream_state = STREAM_STARTING;
+		for (i = 0; i < PCM_N_URBS; i++) {
+			memset(rt->out_urbs[i].buffer, 0, PCM_MAX_PACKET_SIZE);
+			usb_anchor_urb(&rt->out_urbs[i].instance,
+				       &rt->out_urbs[i].submitted);
+			ret = usb_submit_urb(&rt->out_urbs[i].instance,
+					GFP_ATOMIC);
+			if (ret) {
+				hiface_pcm_stream_stop(rt);
+				return ret;
+			}
+		}
+
+		/* wait for first out urb to return (sent in in urb handler) */
+		wait_event_timeout(rt->stream_wait_queue, rt->stream_wait_cond,
+				HZ);
+		if (rt->stream_wait_cond) {
+			pr_debug("%s: Stream is running wakeup event\n",
+				 __func__);
+			rt->stream_state = STREAM_RUNNING;
+		} else {
+			hiface_pcm_stream_stop(rt);
+			return -EIO;
+		}
+	}
+	return ret;
+}
+
+
+/* The hardware wants word-swapped 32-bit values */
+void memcpy_swahw32(u8 *dest, u8 *src, unsigned int n)
+{
+	unsigned int i;
+
+	for (i = 0; i < n / 4; i++)
+		((u32 *)dest)[i] = swahw32(((u32 *)src)[i]);
+}
+
+/* call with substream locked */
+static int hiface_pcm_playback(struct pcm_substream *sub,
+		struct pcm_urb *urb)
+{
+	struct snd_pcm_runtime *alsa_rt = sub->instance->runtime;
+	u8 *source;
+	unsigned int pcm_buffer_size;
+
+	/* XXX Can an invalid format make it to here?
+	 * Isn't ALSA checking pcm_hw.formats ?
+	 */
+	if (alsa_rt->format != SNDRV_PCM_FORMAT_S32_LE) {
+		pr_err("Unsupported sample format\n");
+		return -EINVAL;
+	}
+
+	pcm_buffer_size = snd_pcm_lib_buffer_bytes(sub->instance);
+
+	if (sub->dma_off + PCM_MAX_PACKET_SIZE <= pcm_buffer_size) {
+		pr_debug("%s: (1) buffer_size %#x dma_offset %#x\n", __func__,
+			 (unsigned int) pcm_buffer_size,
+			 (unsigned int) sub->dma_off);
+
+		source = alsa_rt->dma_area + sub->dma_off;
+		memcpy_swahw32(urb->buffer, source, PCM_MAX_PACKET_SIZE);
+	} else {
+		/* wrap around at end of ring buffer */
+		unsigned int len;
+
+		pr_debug("%s: (2) buffer_size %#x dma_offset %#x\n", __func__,
+			 (unsigned int) pcm_buffer_size,
+			 (unsigned int) sub->dma_off);
+
+		len = pcm_buffer_size - sub->dma_off;
+
+		source = alsa_rt->dma_area + sub->dma_off;
+		memcpy_swahw32(urb->buffer, source, len);
+
+		source = alsa_rt->dma_area;
+		memcpy_swahw32(urb->buffer + len, source,
+			       PCM_MAX_PACKET_SIZE - len);
+	}
+	sub->dma_off += PCM_MAX_PACKET_SIZE;
+	if (sub->dma_off >= pcm_buffer_size)
+		sub->dma_off -= pcm_buffer_size;
+
+	sub->period_off += PCM_MAX_PACKET_SIZE;
+
+	return 0;
+}
+
+static void hiface_pcm_out_urb_handler(struct urb *usb_urb)
+{
+	struct pcm_urb *out_urb = usb_urb->context;
+	struct pcm_runtime *rt = out_urb->chip->pcm;
+	struct pcm_substream *sub;
+	unsigned long flags;
+
+	pr_debug("%s: called.\n", __func__);
+
+	if (usb_urb->status || rt->panic || rt->stream_state == STREAM_STOPPING)
+		return;
+
+	if (rt->stream_state == STREAM_STARTING) {
+		rt->stream_wait_cond = true;
+		wake_up(&rt->stream_wait_queue);
+	}
+
+	/* now send our playback data (if a free out urb was found) */
+	sub = &rt->playback;
+	spin_lock_irqsave(&sub->lock, flags);
+	if (sub->active) {
+		int ret;
+
+		ret = hiface_pcm_playback(sub, out_urb);
+		if (ret < 0) {
+			spin_unlock_irqrestore(&sub->lock, flags);
+			goto out_fail;
+		}
+		if (sub->period_off >= sub->instance->runtime->period_size) {
+			sub->period_off %= sub->instance->runtime->period_size;
+			spin_unlock_irqrestore(&sub->lock, flags);
+			snd_pcm_period_elapsed(sub->instance);
+		} else {
+			spin_unlock_irqrestore(&sub->lock, flags);
+		}
+	} else {
+		memset(out_urb->buffer, 0, PCM_MAX_PACKET_SIZE);
+		spin_unlock_irqrestore(&sub->lock, flags);
+	}
+out_fail:
+	usb_submit_urb(&out_urb->instance, GFP_ATOMIC);
+}
+
+static int hiface_pcm_open(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	struct pcm_substream *sub = NULL;
+	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
+	int ret;
+
+	pr_debug("%s: called.\n", __func__);
+
+	if (rt->panic)
+		return -EPIPE;
+
+	mutex_lock(&rt->stream_mutex);
+	alsa_rt->hw = pcm_hw;
+
+	if (alsa_sub->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+
+		/* XXX can we avoid setting hw.rates below?
+		 *
+		 * FWIU setting alsa_rt->hw.rates in the open callback can be
+		 * useful in two cases:
+		 *
+		 *   - different hardware revisions have different
+		 *     capabilities
+		 *
+		 *   - different stream types must have the same capabilities
+		 *
+		 * but in our case it should not be needed as we only have one
+		 * playback stream and we define the capabilities via
+		 * constraints already to avoid invalid rates to be used.
+		 *
+		 * Moreover, following the code stolen from 6fire it seems
+		 * impossible to get to setting alsa_rt->hw.rates here:
+		 *
+		 *   hiface_pcm_init:
+		 *     rt->rate = ARRAY_SIZE(rates);
+		 *
+		 *   hiface_pcm_open:
+		 *     if (rt->rate < ARRAY_SIZE(rates))
+		 *       alsa_rt->hw.rates = rates_alsaid[rt->rate];
+		 *       // So it does not get here on the first open
+		 *
+		 *   hiface_pcm_prepare:
+		 *     hiface_pcm_set_rate:
+		 *       for (rt->rate = 0; rt->rate < ARRAY_SIZE(rates); rt->rate++)
+		 *         if (rate == rates[rt->rate])
+		 *           break;
+		 *
+		 *   hiface_pcm_close:
+		 *     rt->rate = ARRAY_SIZE(rates);
+		 *     // So it won't set alsa_rt->hw.rates at the next open either
+		 *
+		 * So the only way to set alsa_rt->hw.rates is to have two
+		 * calls to the open callback, is that even possible? ALSA
+		 * does not allow sharing PCM streams AFAIK and we use only
+		 * one playback stream.
+		 *
+		 * Maybe the 6fire driver was setting alsa_rt->hw.rates in
+		 * order to have the playback stream and the capture stream
+		 * use the same rate? I.e. the first open,prepare,set_rate()
+		 * sequence on one type of stream will decide the rate and the
+		 * second open on the other type would restrict the supported
+		 * rate to the one already in use, but I am not sure.
+		 *
+		 * If the rationale makes sense then we don't need to set
+		 * alsa_rt->hw.rates and we can remove this and related code.
+		 */
+		if (rt->rate < ARRAY_SIZE(rates))
+			alsa_rt->hw.rates = rates_alsaid[rt->rate];
+
+		sub = &rt->playback;
+	}
+
+	if (!sub) {
+		mutex_unlock(&rt->stream_mutex);
+		pr_err("Invalid stream type\n");
+		return -EINVAL;
+	}
+
+	if (rt->extra_freq) {
+		alsa_rt->hw.rate_max = 384000;
+		constraints_rates.count = ARRAY_SIZE(rates);
+	}
+
+	ret = snd_pcm_hw_constraint_list(alsa_sub->runtime, 0,
+					 SNDRV_PCM_HW_PARAM_RATE,
+					 &constraints_rates);
+	if (ret < 0) {
+		mutex_unlock(&rt->stream_mutex);
+		return ret;
+	}
+
+	sub->instance = alsa_sub;
+	sub->active = false;
+	mutex_unlock(&rt->stream_mutex);
+	return 0;
+}
+
+static int hiface_pcm_close(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+	unsigned long flags;
+
+	if (rt->panic)
+		return 0;
+
+	pr_debug("%s: called.\n", __func__);
+
+	mutex_lock(&rt->stream_mutex);
+	if (sub) {
+		/* deactivate substream */
+		spin_lock_irqsave(&sub->lock, flags);
+		sub->instance = NULL;
+		sub->active = false;
+		spin_unlock_irqrestore(&sub->lock, flags);
+
+		/* all substreams closed? if so, stop streaming */
+		if (!rt->playback.instance) {
+			hiface_pcm_stream_stop(rt);
+			rt->rate = ARRAY_SIZE(rates);
+		}
+	}
+	mutex_unlock(&rt->stream_mutex);
+	return 0;
+}
+
+static int hiface_pcm_hw_params(struct snd_pcm_substream *alsa_sub,
+		struct snd_pcm_hw_params *hw_params)
+{
+	pr_debug("%s: called.\n", __func__);
+	return snd_pcm_lib_malloc_pages(alsa_sub,
+			params_buffer_bytes(hw_params));
+}
+
+static int hiface_pcm_hw_free(struct snd_pcm_substream *alsa_sub)
+{
+	pr_debug("%s: called.\n", __func__);
+	return snd_pcm_lib_free_pages(alsa_sub);
+}
+
+static int hiface_pcm_prepare(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+	struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime;
+	int ret;
+
+	pr_debug("%s: called.\n", __func__);
+
+	if (rt->panic)
+		return -EPIPE;
+	if (!sub)
+		return -ENODEV;
+
+	mutex_lock(&rt->stream_mutex);
+
+	sub->dma_off = 0;
+	sub->period_off = 0;
+
+	if (rt->stream_state == STREAM_DISABLED) {
+
+		ret = hiface_pcm_set_rate(rt, alsa_rt->rate);
+		if (ret) {
+			mutex_unlock(&rt->stream_mutex);
+			return ret;
+		}
+		ret = hiface_pcm_stream_start(rt);
+		if (ret) {
+			mutex_unlock(&rt->stream_mutex);
+			return ret;
+		}
+	}
+	mutex_unlock(&rt->stream_mutex);
+	return 0;
+}
+
+static int hiface_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd)
+{
+	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	unsigned long flags;
+
+	pr_debug("%s: called.\n", __func__);
+
+	if (rt->panic)
+		return -EPIPE;
+	if (!sub)
+		return -ENODEV;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		spin_lock_irqsave(&sub->lock, flags);
+		sub->active = true;
+		spin_unlock_irqrestore(&sub->lock, flags);
+		return 0;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		spin_lock_irqsave(&sub->lock, flags);
+		sub->active = false;
+		spin_unlock_irqrestore(&sub->lock, flags);
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static snd_pcm_uframes_t hiface_pcm_pointer(struct snd_pcm_substream *alsa_sub)
+{
+	struct pcm_substream *sub = hiface_pcm_get_substream(alsa_sub);
+	struct pcm_runtime *rt = snd_pcm_substream_chip(alsa_sub);
+	unsigned long flags;
+	snd_pcm_uframes_t dma_offset;
+
+	if (rt->panic || !sub)
+		return SNDRV_PCM_STATE_XRUN;
+
+	spin_lock_irqsave(&sub->lock, flags);
+	dma_offset = sub->dma_off;
+	spin_unlock_irqrestore(&sub->lock, flags);
+	return bytes_to_frames(alsa_sub->runtime, dma_offset);
+}
+
+static struct snd_pcm_ops pcm_ops = {
+	.open = hiface_pcm_open,
+	.close = hiface_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = hiface_pcm_hw_params,
+	.hw_free = hiface_pcm_hw_free,
+	.prepare = hiface_pcm_prepare,
+	.trigger = hiface_pcm_trigger,
+	.pointer = hiface_pcm_pointer,
+};
+
+static int hiface_pcm_init_urb(struct pcm_urb *urb,
+			       struct hiface_chip *chip,
+			       unsigned int ep,
+			       void (*handler)(struct urb *))
+{
+	urb->chip = chip;
+	usb_init_urb(&urb->instance);
+
+	urb->buffer = kzalloc(PCM_MAX_PACKET_SIZE, GFP_KERNEL);
+	if (!urb->buffer)
+		return -ENOMEM;
+
+	usb_fill_bulk_urb(&urb->instance, chip->dev,
+			  usb_sndbulkpipe(chip->dev, ep), (void *)urb->buffer,
+			  PCM_MAX_PACKET_SIZE, handler, urb);
+	init_usb_anchor(&urb->submitted);
+
+	return 0;
+}
+
+void hiface_pcm_abort(struct hiface_chip *chip)
+{
+	struct pcm_runtime *rt = chip->pcm;
+
+	if (rt) {
+		rt->panic = true;
+
+		if (rt->playback.instance) {
+			snd_pcm_stop(rt->playback.instance,
+					SNDRV_PCM_STATE_XRUN);
+		}
+		mutex_lock(&rt->stream_mutex);
+		hiface_pcm_stream_stop(rt);
+		mutex_unlock(&rt->stream_mutex);
+	}
+}
+
+static void hiface_pcm_destroy(struct hiface_chip *chip)
+{
+	struct pcm_runtime *rt = chip->pcm;
+	int i;
+
+	for (i = 0; i < PCM_N_URBS; i++)
+		kfree(rt->out_urbs[i].buffer);
+
+	kfree(chip->pcm);
+	chip->pcm = NULL;
+}
+
+static void hiface_pcm_free(struct snd_pcm *pcm)
+{
+	struct pcm_runtime *rt = pcm->private_data;
+
+	pr_debug("%s: called.\n", __func__);
+
+	if (rt)
+		hiface_pcm_destroy(rt->chip);
+}
+
+int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq)
+{
+	int i;
+	int ret;
+	struct snd_pcm *pcm;
+	struct pcm_runtime *rt;
+
+	rt = kzalloc(sizeof(*rt), GFP_KERNEL);
+	if (!rt)
+		return -ENOMEM;
+
+	rt->chip = chip;
+	rt->stream_state = STREAM_DISABLED;
+	rt->rate = ARRAY_SIZE(rates);
+	if (extra_freq)
+		rt->extra_freq = 1;
+
+	init_waitqueue_head(&rt->stream_wait_queue);
+	mutex_init(&rt->stream_mutex);
+	spin_lock_init(&rt->playback.lock);
+
+	for (i = 0; i < PCM_N_URBS; i++)
+		hiface_pcm_init_urb(&rt->out_urbs[i], chip, OUT_EP,
+				hiface_pcm_out_urb_handler);
+
+	ret = snd_pcm_new(chip->card, "USB-SPDIF Audio", 0, 1, 0, &pcm);
+	if (ret < 0) {
+		kfree(rt);
+		pr_err("Cannot create pcm instance\n");
+		return ret;
+	}
+
+	pcm->private_data = rt;
+	pcm->private_free = hiface_pcm_free;
+
+	strcpy(pcm->name, "USB-SPDIF Audio");
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops);
+
+	snd_pcm_lib_preallocate_pages_for_all(pcm,
+					SNDRV_DMA_TYPE_CONTINUOUS,
+					snd_dma_continuous_data(GFP_KERNEL),
+					MAX_BUFSIZE, MAX_BUFSIZE);
+	rt->instance = pcm;
+
+	chip->pcm = rt;
+	return 0;
+}
diff --git a/sound/usb/hiface/pcm.h b/sound/usb/hiface/pcm.h
new file mode 100644
index 0000000..ddc9fe7
--- /dev/null
+++ b/sound/usb/hiface/pcm.h
@@ -0,0 +1,24 @@
+/*
+ * Linux driver for M2Tech HiFace compatible devices
+ *
+ * Copyright 2012-2013 (C) M2TECH S.r.l and Amarula Solutions B.V.
+ *
+ * Authors:  Michael Trimarchi <michael@amarulasolutions.com>
+ *           Antonio Ospite <ao2@amarulasolutions.com>
+ *
+ * The driver is based on the work done in TerraTec DMX 6Fire USB
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ */
+
+#ifndef HIFACE_PCM_H
+#define HIFACE_PCM_H
+
+struct hiface_chip;
+
+int hiface_pcm_init(struct hiface_chip *chip, u8 extra_freq);
+void hiface_pcm_abort(struct hiface_chip *chip);
+#endif /* HIFACE_PCM_H */
-- 
1.7.10.4

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

end of thread, other threads:[~2013-06-24  7:44 UTC | newest]

Thread overview: 25+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-02-10 23:11 [PATCH] Add M2Tech hiFace USB-SPDIF driver Antonio Ospite
2013-02-11  8:53 ` Clemens Ladisch
2013-02-12 12:35   ` Antonio Ospite
2013-02-12 14:29     ` Clemens Ladisch
2013-02-12 15:29       ` Takashi Iwai
2013-02-13 14:09       ` Antonio Ospite
2013-02-13 17:11 ` [PATCH v2] " Antonio Ospite
2013-02-22 10:48   ` Antonio Ospite
2013-02-22 12:52     ` Takashi Iwai
2013-02-22 13:31       ` Antonio Ospite
2013-02-22 14:09         ` Takashi Iwai
2013-02-22 12:53   ` Takashi Iwai
2013-04-28 21:09     ` Antonio Ospite
2013-05-29 12:24       ` Takashi Iwai
2013-06-03 21:40         ` Antonio Ospite
2013-02-22 16:12   ` Daniel Mack
2013-02-22 16:18     ` Takashi Iwai
2013-04-28 20:59     ` Antonio Ospite
2013-04-20 20:15   ` Daniel Mack
2013-04-22  7:40     ` Pavel Hofman
     [not found]       ` <5174F560.8050502@m2tech.biz>
2013-04-22  8:37         ` Pavel Hofman
2013-04-22  9:14     ` Antonio Ospite
2013-06-21 22:14 ` [PATCH v3] " Antonio Ospite
2013-06-24  7:45   ` Takashi Iwai
  -- strict thread matches above, loose matches on Subject: below --
2013-02-10 23:06 [PATCH] " Antonio Ospite

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