]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
ALSA: add DICE driver
authorClemens Ladisch <clemens@ladisch.de>
Sun, 4 Sep 2011 20:04:49 +0000 (22:04 +0200)
committerClemens Ladisch <clemens@ladisch.de>
Thu, 17 Oct 2013 19:18:32 +0000 (21:18 +0200)
As a start point for further development, this is an incomplete driver
for DICE devices:
- only playback (so no clock source except the bus clock)
- only 44.1 kHz
- no MIDI
- recovery after bus reset is slow
- hwdep device is created, but not actually implemented

Contains compilation fixes by Stefan Richter.

Signed-off-by: Clemens Ladisch <clemens@ladisch.de>
Documentation/ioctl/ioctl-number.txt
include/uapi/sound/Kbuild
include/uapi/sound/asound.h
include/uapi/sound/firewire.h [new file with mode: 0644]
sound/firewire/Kconfig
sound/firewire/Makefile
sound/firewire/dice.c [new file with mode: 0644]

index 2a5f0e14efa351a73ef1eb53c392ecef7c9b4f6b..7cbfa3c4fc3d327c8b1d9c3ef8a4c1228998a69b 100644 (file)
@@ -138,6 +138,7 @@ Code  Seq#(hex)     Include File            Comments
 'H'    C0-DF   net/bluetooth/cmtp/cmtp.h       conflict!
 'H'    C0-DF   net/bluetooth/bnep/bnep.h       conflict!
 'H'    F1      linux/hid-roccat.h      <mailto:erazor_de@users.sourceforge.net>
+'H'    F8-FA   sound/firewire.h
 'I'    all     linux/isdn.h            conflict!
 'I'    00-0F   drivers/isdn/divert/isdn_divert.h       conflict!
 'I'    40-4F   linux/mISDNif.h         conflict!
index 0f7d279ebde35fd1be21cc922def324eaacb240f..a7f27704f9807e7b14eadb9b08cf09c2c183a6a7 100644 (file)
@@ -5,6 +5,7 @@ header-y += asound_fm.h
 header-y += compress_offload.h
 header-y += compress_params.h
 header-y += emu10k1.h
+header-y += firewire.h
 header-y += hdsp.h
 header-y += hdspm.h
 header-y += sb16_csp.h
index 041203f20f6d1cb3cac42df78723356550729054..9fc6219d38482cd3dc8a34f8653d24dfeee2a792 100644 (file)
@@ -93,9 +93,10 @@ enum {
        SNDRV_HWDEP_IFACE_SB_RC,        /* SB Extigy/Audigy2NX remote control */
        SNDRV_HWDEP_IFACE_HDA,          /* HD-audio */
        SNDRV_HWDEP_IFACE_USB_STREAM,   /* direct access to usb stream */
+       SNDRV_HWDEP_IFACE_FW_DICE,      /* TC DICE FireWire device */
 
        /* Don't forget to change the following: */
-       SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_USB_STREAM
+       SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_DICE
 };
 
 struct snd_hwdep_info {
diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h
new file mode 100644 (file)
index 0000000..e86131c
--- /dev/null
@@ -0,0 +1,51 @@
+#ifndef UAPI_SOUND_FIREWIRE_H_INCLUDED
+#define UAPI_SOUND_FIREWIRE_H_INCLUDED
+
+#include <linux/ioctl.h>
+
+/* events can be read() from the hwdep device */
+
+#define SNDRV_FIREWIRE_EVENT_LOCK_STATUS       0x000010cc
+#define SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION 0xd1ce004e
+
+struct snd_firewire_event_common {
+       unsigned int type; /* SNDRV_FIREWIRE_EVENT_xxx */
+};
+
+struct snd_firewire_event_lock_status {
+       unsigned int type;
+       unsigned int status; /* 0/1 = unlocked/locked */
+};
+
+struct snd_firewire_event_dice_notification {
+       unsigned int type;
+       unsigned int notification; /* DICE-specific bits */
+};
+
+union snd_firewire_event {
+       struct snd_firewire_event_common            common;
+       struct snd_firewire_event_lock_status       lock_status;
+       struct snd_firewire_event_dice_notification dice_notification;
+};
+
+
+#define SNDRV_FIREWIRE_IOCTL_GET_INFO _IOR('H', 0xf8, struct snd_firewire_get_info)
+#define SNDRV_FIREWIRE_IOCTL_LOCK      _IO('H', 0xf9)
+#define SNDRV_FIREWIRE_IOCTL_UNLOCK    _IO('H', 0xfa)
+
+#define SNDRV_FIREWIRE_TYPE_DICE       1
+/* Fireworks, AV/C, RME, MOTU, ... */
+
+struct snd_firewire_get_info {
+       unsigned int type; /* SNDRV_FIREWIRE_TYPE_xxx */
+       unsigned int card; /* same as fw_cdev_get_info.card */
+       unsigned char guid[8];
+       char device_name[16]; /* device node in /dev */
+};
+
+/*
+ * SNDRV_FIREWIRE_IOCTL_LOCK prevents the driver from streaming.
+ * Returns -EBUSY if the driver is already streaming.
+ */
+
+#endif
index ea063e1f87221e982558ec892024e5bdb7793b89..915330989412e0c04723ec1436d4aa79a12cdc46 100644 (file)
@@ -11,6 +11,19 @@ config SND_FIREWIRE_LIB
        tristate
        depends on SND_PCM
 
+config SND_DICE
+       tristate "DICE devices (EXPERIMENTAL)"
+       select SND_HWDEP
+       select SND_PCM
+       select SND_FIREWIRE_LIB
+       help
+         Say Y here to include support for many FireWire audio interfaces
+         based on the DICE chip family (DICE-II/Jr/Mini) from TC Applied
+         Technologies.
+
+         To compile this driver as a module, choose M here: the module
+         will be called snd-dice.
+
 config SND_FIREWIRE_SPEAKERS
        tristate "FireWire speakers"
        select SND_PCM
index 460179df5bb526ee06f90fa7ffd03ef638024e3b..509955061d30f62d44216fb9913282091d0bca93 100644 (file)
@@ -1,10 +1,12 @@
 snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \
                         fcp.o cmp.o amdtp.o
+snd-dice-objs := dice.o
 snd-firewire-speakers-objs := speakers.o
 snd-isight-objs := isight.o
 snd-scs1x-objs := scs1x.o
 
 obj-$(CONFIG_SND_FIREWIRE_LIB) += snd-firewire-lib.o
+obj-$(CONFIG_SND_DICE) += snd-dice.o
 obj-$(CONFIG_SND_FIREWIRE_SPEAKERS) += snd-firewire-speakers.o
 obj-$(CONFIG_SND_ISIGHT) += snd-isight.o
 obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c
new file mode 100644 (file)
index 0000000..ac71b2b
--- /dev/null
@@ -0,0 +1,1008 @@
+/*
+ * TC Applied Technologies Digital Interface Communications Engine driver
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firewire.h>
+#include <linux/firewire-constants.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/hwdep.h>
+#include <sound/initval.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include "amdtp.h"
+#include "iso-resources.h"
+#include "lib.h"
+
+#define DICE_PRIVATE_SPACE             0xffffe0000000uLL
+
+/* offset from DICE_PRIVATE_SPACE; offsets and sizes in quadlets */
+#define DICE_GLOBAL_OFFSET             0x00
+#define DICE_GLOBAL_SIZE               0x04
+#define DICE_TX_OFFSET                 0x08
+#define DICE_TX_SIZE                   0x0c
+#define DICE_RX_OFFSET                 0x10
+#define DICE_RX_SIZE                   0x14
+
+/* pointed to by DICE_GLOBAL_OFFSET */
+#define GLOBAL_OWNER                   0x000
+#define  OWNER_NO_OWNER                        0xffff000000000000uLL
+#define  OWNER_NODE_SHIFT              48
+#define GLOBAL_NOTIFICATION            0x008
+#define  NOTIFY_RX_CFG_CHG             0x00000001
+#define  NOTIFY_TX_CFG_CHG             0x00000002
+#define  NOTIFY_DUP_ISOC               0x00000004
+#define  NOTIFY_BW_ERR                 0x00000008
+#define  NOTIFY_LOCK_CHG               0x00000010
+#define  NOTIFY_CLOCK_ACCEPTED         0x00000020
+#define  NOTIFY_INTERFACE_CHG          0x00000040
+#define  NOTIFY_MESSAGE                        0x00100000
+#define GLOBAL_NICK_NAME               0x00c
+#define  NICK_NAME_SIZE                        64
+#define GLOBAL_CLOCK_SELECT            0x04c
+#define  CLOCK_SOURCE_MASK             0x000000ff
+#define  CLOCK_SOURCE_AES1             0x00000000
+#define  CLOCK_SOURCE_AES2             0x00000001
+#define  CLOCK_SOURCE_AES3             0x00000002
+#define  CLOCK_SOURCE_AES4             0x00000003
+#define  CLOCK_SOURCE_AES_ANY          0x00000004
+#define  CLOCK_SOURCE_ADAT             0x00000005
+#define  CLOCK_SOURCE_TDIF             0x00000006
+#define  CLOCK_SOURCE_WC               0x00000007
+#define  CLOCK_SOURCE_ARX1             0x00000008
+#define  CLOCK_SOURCE_ARX2             0x00000009
+#define  CLOCK_SOURCE_ARX3             0x0000000a
+#define  CLOCK_SOURCE_ARX4             0x0000000b
+#define  CLOCK_SOURCE_INTERNAL         0x0000000c
+#define  CLOCK_RATE_MASK               0x0000ff00
+#define  CLOCK_RATE_32000              0x00000000
+#define  CLOCK_RATE_44100              0x00000100
+#define  CLOCK_RATE_48000              0x00000200
+#define  CLOCK_RATE_88200              0x00000300
+#define  CLOCK_RATE_96000              0x00000400
+#define  CLOCK_RATE_176400             0x00000500
+#define  CLOCK_RATE_192000             0x00000600
+#define  CLOCK_RATE_ANY_LOW            0x00000700
+#define  CLOCK_RATE_ANY_MID            0x00000800
+#define  CLOCK_RATE_ANY_HIGH           0x00000900
+#define  CLOCK_RATE_NONE               0x00000a00
+#define GLOBAL_ENABLE                  0x050
+#define  ENABLE                                0x00000001
+#define GLOBAL_STATUS                  0x054
+#define  STATUS_SOURCE_LOCKED          0x00000001
+#define  STATUS_RATE_CONFLICT          0x00000002
+#define  STATUS_NOMINAL_RATE_MASK      0x0000ff00
+#define GLOBAL_EXTENDED_STATUS         0x058
+#define  EXT_STATUS_AES1_LOCKED                0x00000001
+#define  EXT_STATUS_AES2_LOCKED                0x00000002
+#define  EXT_STATUS_AES3_LOCKED                0x00000004
+#define  EXT_STATUS_AES4_LOCKED                0x00000008
+#define  EXT_STATUS_ADAT_LOCKED                0x00000010
+#define  EXT_STATUS_TDIF_LOCKED                0x00000020
+#define  EXT_STATUS_ARX1_LOCKED                0x00000040
+#define  EXT_STATUS_ARX2_LOCKED                0x00000080
+#define  EXT_STATUS_ARX3_LOCKED                0x00000100
+#define  EXT_STATUS_ARX4_LOCKED                0x00000200
+#define  EXT_STATUS_WC_LOCKED          0x00000400
+#define  EXT_STATUS_AES1_SLIP          0x00010000
+#define  EXT_STATUS_AES2_SLIP          0x00020000
+#define  EXT_STATUS_AES3_SLIP          0x00040000
+#define  EXT_STATUS_AES4_SLIP          0x00080000
+#define  EXT_STATUS_ADAT_SLIP          0x00100000
+#define  EXT_STATUS_TDIF_SLIP          0x00200000
+#define  EXT_STATUS_ARX1_SLIP          0x00400000
+#define  EXT_STATUS_ARX2_SLIP          0x00800000
+#define  EXT_STATUS_ARX3_SLIP          0x01000000
+#define  EXT_STATUS_ARX4_SLIP          0x02000000
+#define  EXT_STATUS_WC_SLIP            0x04000000
+#define GLOBAL_SAMPLE_RATE             0x05c
+#define GLOBAL_VERSION                 0x060
+#define GLOBAL_CLOCK_CAPABILITIES      0x064
+#define  CLOCK_CAP_RATE_32000          0x00000001
+#define  CLOCK_CAP_RATE_44100          0x00000002
+#define  CLOCK_CAP_RATE_48000          0x00000004
+#define  CLOCK_CAP_RATE_88200          0x00000008
+#define  CLOCK_CAP_RATE_96000          0x00000010
+#define  CLOCK_CAP_RATE_176400         0x00000020
+#define  CLOCK_CAP_RATE_192000         0x00000040
+#define  CLOCK_CAP_SOURCE_AES1         0x00010000
+#define  CLOCK_CAP_SOURCE_AES2         0x00020000
+#define  CLOCK_CAP_SOURCE_AES3         0x00040000
+#define  CLOCK_CAP_SOURCE_AES4         0x00080000
+#define  CLOCK_CAP_SOURCE_AES_ANY      0x00100000
+#define  CLOCK_CAP_SOURCE_ADAT         0x00200000
+#define  CLOCK_CAP_SOURCE_TDIF         0x00400000
+#define  CLOCK_CAP_SOURCE_WC           0x00800000
+#define  CLOCK_CAP_SOURCE_ARX1         0x01000000
+#define  CLOCK_CAP_SOURCE_ARX2         0x02000000
+#define  CLOCK_CAP_SOURCE_ARX3         0x04000000
+#define  CLOCK_CAP_SOURCE_ARX4         0x08000000
+#define  CLOCK_CAP_SOURCE_INTERNAL     0x10000000
+#define GLOBAL_CLOCK_SOURCE_NAMES      0x068
+#define  CLOCK_SOURCE_NAMES_SIZE       256
+
+/* pointed to by DICE_TX_OFFSET */
+#define TX_NUMBER                      0x000
+#define TX_SIZE                                0x004
+/* repeated TX_NUMBER times, offset by TX_SIZE quadlets */
+#define TX_ISOCHRONOUS                 0x008
+#define TX_NUMBER_AUDIO                        0x00c
+#define TX_NUMBER_MIDI                 0x010
+#define TX_SPEED                       0x014
+#define TX_NAMES                       0x018
+#define  TX_NAMES_SIZE                 256
+#define TX_AC3_CAPABILITIES            0x118
+#define TX_AC3_ENABLE                  0x11c
+
+/* pointed to by DICE_RX_OFFSET */
+#define RX_NUMBER                      0x000
+#define RX_SIZE                                0x004
+/* repeated RX_NUMBER times, offset by RX_SIZE quadlets */
+#define RX_ISOCHRONOUS                 0x008
+#define RX_SEQ_START                   0x00c
+#define RX_NUMBER_AUDIO                        0x010
+#define RX_NUMBER_MIDI                 0x014
+#define RX_NAMES                       0x018
+#define  RX_NAMES_SIZE                 256
+#define RX_AC3_CAPABILITIES            0x118
+#define RX_AC3_ENABLE                  0x11c
+
+
+#define FIRMWARE_LOAD_SPACE            0xffffe0100000uLL
+
+/* offset from FIRMWARE_LOAD_SPACE */
+#define FIRMWARE_VERSION               0x000
+#define FIRMWARE_OPCODE                        0x004
+#define  OPCODE_MASK                   0x00000fff
+#define  OPCODE_GET_IMAGE_DESC         0x00000000
+#define  OPCODE_DELETE_IMAGE           0x00000001
+#define  OPCODE_CREATE_IMAGE           0x00000002
+#define  OPCODE_UPLOAD                 0x00000003
+#define  OPCODE_UPLOAD_STAT            0x00000004
+#define  OPCODE_RESET_IMAGE            0x00000005
+#define  OPCODE_TEST_ACTION            0x00000006
+#define  OPCODE_GET_RUNNING_IMAGE_VINFO        0x0000000a
+#define  OPCODE_EXECUTE                        0x80000000
+#define FIRMWARE_RETURN_STATUS         0x008
+#define FIRMWARE_PROGRESS              0x00c
+#define  PROGRESS_CURR_MASK            0x00000fff
+#define  PROGRESS_MAX_MASK             0x00fff000
+#define  PROGRESS_TOUT_MASK            0x0f000000
+#define  PROGRESS_FLAG                 0x80000000
+#define FIRMWARE_CAPABILITIES          0x010
+#define  FL_CAP_AUTOERASE              0x00000001
+#define  FL_CAP_PROGRESS               0x00000002
+#define FIRMWARE_DATA                  0x02c
+#define  TEST_CMD_POKE                 0x00000001
+#define  TEST_CMD_PEEK                 0x00000002
+#define  CMD_GET_AVS_CNT               0x00000003
+#define  CMD_CLR_AVS_CNT               0x00000004
+#define  CMD_SET_MODE                  0x00000005
+#define  CMD_SET_MIDIBP                        0x00000006
+#define  CMD_GET_AVSPHASE              0x00000007
+#define  CMD_ENABLE_BNC_SYNC           0x00000008
+#define  CMD_PULSE_BNC_SYNC            0x00000009
+#define  CMD_EMUL_SLOW_CMD             0x0000000a
+#define FIRMWARE_TEST_DELAY            0xfd8
+#define FIRMWARE_TEST_BUF              0xfdc
+
+
+/* EAP */
+#define EAP_PRIVATE_SPACE              0xffffe0200000uLL
+
+#define EAP_CAPABILITY_OFFSET          0x000
+#define EAP_CAPABILITY_SIZE            0x004
+/* ... */
+
+#define EAP_ROUTER_CAPS                        0x000
+#define  ROUTER_EXPOSED                        0x00000001
+#define  ROUTER_READ_ONLY              0x00000002
+#define  ROUTER_FLASH                  0x00000004
+#define  MAX_ROUTES_MASK               0xffff0000
+#define EAP_MIXER_CAPS                 0x004
+#define  MIXER_EXPOSED                 0x00000001
+#define  MIXER_READ_ONLY               0x00000002
+#define  MIXER_FLASH                   0x00000004
+#define  MIXER_IN_DEV_MASK             0x000000f0
+#define  MIXER_OUT_DEV_MASK            0x00000f00
+#define  MIXER_INPUTS_MASK             0x00ff0000
+#define  MIXER_OUTPUTS_MASK            0xff000000
+#define EAP_GENERAL_CAPS               0x008
+#define  GENERAL_STREAM_CONFIG         0x00000001
+#define  GENERAL_FLASH                 0x00000002
+#define  GENERAL_PEAK                  0x00000004
+#define  GENERAL_MAX_TX_STREAMS_MASK   0x000000f0
+#define  GENERAL_MAX_RX_STREAMS_MASK   0x00000f00
+#define  GENERAL_STREAM_CONFIG_FLASH   0x00001000
+#define  GENERAL_CHIP_MASK             0x00ff0000
+#define  GENERAL_CHIP_DICE_II          0x00000000
+#define  GENERAL_CHIP_DICE_MINI                0x00010000
+#define  GENERAL_CHIP_DICE_JR          0x00020000
+
+
+struct dice {
+       struct snd_card *card;
+       struct fw_unit *unit;
+       struct mutex mutex;
+       unsigned int global_offset;
+       unsigned int rx_offset;
+       struct fw_address_handler notification_handler;
+       int owner_generation;
+       bool global_enabled;
+       bool stream_running;
+       struct snd_pcm_substream *pcm;
+       struct fw_iso_resources resources;
+       struct amdtp_out_stream stream;
+};
+
+MODULE_DESCRIPTION("DICE driver");
+MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
+MODULE_LICENSE("GPL v2");
+
+static inline u64 global_address(struct dice *dice, unsigned int offset)
+{
+       return DICE_PRIVATE_SPACE + dice->global_offset + offset;
+}
+
+// TODO: rx index
+static inline u64 rx_address(struct dice *dice, unsigned int offset)
+{
+       return DICE_PRIVATE_SPACE + dice->rx_offset + offset;
+}
+
+static int dice_owner_set(struct dice *dice)
+{
+       struct fw_device *device = fw_parent_device(dice->unit);
+       __be64 *buffer;
+       int rcode, err, errors = 0;
+
+       buffer = kmalloc(2 * 8, GFP_KERNEL);
+       if (!buffer)
+               return -ENOMEM;
+
+       for (;;) {
+               buffer[0] = cpu_to_be64(OWNER_NO_OWNER);
+               buffer[1] = cpu_to_be64(
+                       ((u64)device->card->node_id << OWNER_NODE_SHIFT) |
+                       dice->notification_handler.offset);
+
+               dice->owner_generation = device->generation;
+               smp_rmb(); /* node_id vs. generation */
+               rcode = fw_run_transaction(device->card,
+                                          TCODE_LOCK_COMPARE_SWAP,
+                                          device->node_id,
+                                          dice->owner_generation,
+                                          device->max_speed,
+                                          global_address(dice, GLOBAL_OWNER),
+                                          buffer, 2 * 8);
+
+               if (rcode == RCODE_COMPLETE) {
+                       if (buffer[0] == cpu_to_be64(OWNER_NO_OWNER)) {
+                               err = 0;
+                       } else {
+                               dev_err(&dice->unit->device,
+                                       "device is already in use\n");
+                               err = -EBUSY;
+                       }
+                       break;
+               }
+               if (rcode_is_permanent_error(rcode) || ++errors >= 3) {
+                       dev_err(&dice->unit->device,
+                               "setting device owner failed: %s\n",
+                               fw_rcode_string(rcode));
+                       err = -EIO;
+                       break;
+               }
+               msleep(20);
+       }
+
+       kfree(buffer);
+
+       return err;
+}
+
+static int dice_owner_update(struct dice *dice)
+{
+       struct fw_device *device = fw_parent_device(dice->unit);
+       __be64 *buffer;
+       int rcode, err, errors = 0;
+
+       if (dice->owner_generation == -1)
+               return 0;
+
+       buffer = kmalloc(2 * 8, GFP_KERNEL);
+       if (!buffer)
+               return -ENOMEM;
+
+       for (;;) {
+               buffer[0] = cpu_to_be64(OWNER_NO_OWNER);
+               buffer[1] = cpu_to_be64(
+                       ((u64)device->card->node_id << OWNER_NODE_SHIFT) |
+                       dice->notification_handler.offset);
+
+               dice->owner_generation = device->generation;
+               smp_rmb(); /* node_id vs. generation */
+               rcode = fw_run_transaction(device->card,
+                                          TCODE_LOCK_COMPARE_SWAP,
+                                          device->node_id,
+                                          dice->owner_generation,
+                                          device->max_speed,
+                                          global_address(dice, GLOBAL_OWNER),
+                                          buffer, 2 * 8);
+
+               if (rcode == RCODE_COMPLETE) {
+                       if (buffer[0] == cpu_to_be64(OWNER_NO_OWNER)) {
+                               err = 0;
+                       } else {
+                               dev_err(&dice->unit->device,
+                                       "device is already in use\n");
+                               err = -EBUSY;
+                       }
+                       break;
+               }
+               if (rcode == RCODE_GENERATION) {
+                       err = 0; /* try again later */
+                       break;
+               }
+               if (rcode_is_permanent_error(rcode) || ++errors >= 3) {
+                       dev_err(&dice->unit->device,
+                               "setting device owner failed: %s\n",
+                               fw_rcode_string(rcode));
+                       err = -EIO;
+                       break;
+               }
+               msleep(20);
+       }
+
+       kfree(buffer);
+
+       if (err < 0)
+               dice->owner_generation = -1;
+
+       return err;
+}
+
+static void dice_owner_clear(struct dice *dice)
+{
+       struct fw_device *device = fw_parent_device(dice->unit);
+       __be64 *buffer;
+       int rcode, errors = 0;
+
+       buffer = kmalloc(2 * 8, GFP_KERNEL);
+       if (!buffer)
+               return;
+
+       for (;;) {
+               buffer[0] = cpu_to_be64(
+                       ((u64)device->card->node_id << OWNER_NODE_SHIFT) |
+                       dice->notification_handler.offset);
+               buffer[1] = cpu_to_be64(OWNER_NO_OWNER);
+
+               rcode = fw_run_transaction(device->card,
+                                          TCODE_LOCK_COMPARE_SWAP,
+                                          device->node_id,
+                                          dice->owner_generation,
+                                          device->max_speed,
+                                          global_address(dice, GLOBAL_OWNER),
+                                          buffer, 2 * 8);
+
+               if (rcode == RCODE_COMPLETE)
+                       break;
+               if (rcode == RCODE_GENERATION)
+                       break;
+               if (rcode_is_permanent_error(rcode) || ++errors >= 3) {
+                       dev_err(&dice->unit->device,
+                               "clearing device owner failed: %s\n",
+                               fw_rcode_string(rcode));
+                       break;
+               }
+               msleep(20);
+       }
+
+       kfree(buffer);
+
+       dice->owner_generation = -1;
+}
+
+static int dice_enable_set(struct dice *dice)
+{
+       struct fw_device *device = fw_parent_device(dice->unit);
+       __be32 value;
+       int rcode, err, errors = 0;
+
+       value = cpu_to_be32(ENABLE);
+       for (;;) {
+               rcode = fw_run_transaction(device->card,
+                                          TCODE_WRITE_QUADLET_REQUEST,
+                                          device->node_id,
+                                          dice->owner_generation,
+                                          device->max_speed,
+                                          global_address(dice, GLOBAL_ENABLE),
+                                          &value, 4);
+               if (rcode == RCODE_COMPLETE) {
+                       dice->global_enabled = true;
+                       err = 0;
+                       break;
+               }
+               if (rcode == RCODE_GENERATION) {
+                       err = -EAGAIN;
+                       break;
+               }
+               if (rcode_is_permanent_error(rcode) || ++errors >= 3) {
+                       dev_err(&dice->unit->device,
+                               "device enabling failed: %s\n",
+                               fw_rcode_string(rcode));
+                       err = -EIO;
+                       break;
+               }
+               msleep(20);
+       }
+
+       return err;
+}
+
+static void dice_enable_clear(struct dice *dice)
+{
+       struct fw_device *device = fw_parent_device(dice->unit);
+       __be32 value;
+       int rcode, errors = 0;
+
+       value = 0;
+       for (;;) {
+               rcode = fw_run_transaction(device->card,
+                                          TCODE_WRITE_QUADLET_REQUEST,
+                                          device->node_id,
+                                          dice->owner_generation,
+                                          device->max_speed,
+                                          global_address(dice, GLOBAL_ENABLE),
+                                          &value, 4);
+               if (rcode == RCODE_COMPLETE ||
+                   rcode == RCODE_GENERATION)
+                       break;
+               if (rcode_is_permanent_error(rcode) || ++errors >= 3) {
+                       dev_err(&dice->unit->device,
+                               "device disabling failed: %s\n",
+                               fw_rcode_string(rcode));
+                       break;
+               }
+               msleep(20);
+       }
+       dice->global_enabled = false;
+}
+
+static void dice_notification(struct fw_card *card, struct fw_request *request,
+                             int tcode, int destination, int source,
+                             int generation, unsigned long long offset,
+                             void *data, size_t length, void *callback_data)
+{
+       struct dice *dice = callback_data;
+
+       if (tcode != TCODE_WRITE_QUADLET_REQUEST) {
+               fw_send_response(card, request, RCODE_TYPE_ERROR);
+               return;
+       }
+       if ((offset & 3) != 0) {
+               fw_send_response(card, request, RCODE_ADDRESS_ERROR);
+               return;
+       }
+       dev_info(&dice->unit->device,
+                "notification: %08x\n", be32_to_cpup(data));
+       fw_send_response(card, request, RCODE_COMPLETE);
+}
+
+static int dice_open(struct snd_pcm_substream *substream)
+{
+       static const struct snd_pcm_hardware hardware = {
+               .info = SNDRV_PCM_INFO_MMAP |
+                       SNDRV_PCM_INFO_MMAP_VALID |
+                       SNDRV_PCM_INFO_BATCH |
+                       SNDRV_PCM_INFO_INTERLEAVED |
+                       SNDRV_PCM_INFO_BLOCK_TRANSFER,
+               .formats = AMDTP_OUT_PCM_FORMAT_BITS,
+               .rates = SNDRV_PCM_RATE_44100,
+               .rate_min = 44100,
+               .rate_max = 44100,
+               .buffer_bytes_max = 16 * 1024 * 1024,
+               .period_bytes_min = 1,
+               .period_bytes_max = UINT_MAX,
+               .periods_min = 1,
+               .periods_max = UINT_MAX,
+       };
+       struct dice *dice = substream->private_data;
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       __be32 number_audio, number_midi;
+       int err;
+
+       err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST,
+                                rx_address(dice, RX_NUMBER_AUDIO),
+                                &number_audio, 4);
+       if (err < 0)
+               return err;
+       err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST,
+                                rx_address(dice, RX_NUMBER_MIDI),
+                                &number_midi, 4);
+       if (err < 0)
+               return err;
+
+       runtime->hw = hardware;
+       runtime->hw.channels_min = be32_to_cpu(number_audio);
+       runtime->hw.channels_max = be32_to_cpu(number_audio);
+
+       amdtp_out_stream_set_rate(&dice->stream, 44100);
+       amdtp_out_stream_set_pcm(&dice->stream, be32_to_cpu(number_audio));
+       amdtp_out_stream_set_midi(&dice->stream, be32_to_cpu(number_midi));
+
+       err = snd_pcm_hw_constraint_minmax(runtime,
+                                          SNDRV_PCM_HW_PARAM_PERIOD_TIME,
+                                          5000, 8192000);
+       if (err < 0)
+               return err;
+
+       err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24);
+       if (err < 0)
+               return err;
+
+       return 0;
+}
+
+static int dice_close(struct snd_pcm_substream *substream)
+{
+       return 0;
+}
+
+static void dice_stop_stream(struct dice *dice)
+{
+       __be32 channel;
+
+       if (dice->stream_running) {
+               dice_enable_clear(dice);
+
+               amdtp_out_stream_stop(&dice->stream);
+
+               channel = cpu_to_be32((u32)-1);
+               snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST,
+                                  rx_address(dice, RX_ISOCHRONOUS),
+                                  &channel, 4);
+
+               fw_iso_resources_free(&dice->resources);
+
+               dice->stream_running = false;
+       }
+}
+
+static int dice_hw_params(struct snd_pcm_substream *substream,
+                         struct snd_pcm_hw_params *hw_params)
+{
+       struct dice *dice = substream->private_data;
+       int err;
+
+       mutex_lock(&dice->mutex);
+       dice_stop_stream(dice);
+       mutex_unlock(&dice->mutex);
+
+       err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
+                                              params_buffer_bytes(hw_params));
+       if (err < 0)
+               goto error;
+
+       amdtp_out_stream_set_pcm_format(&dice->stream,
+                                       params_format(hw_params));
+
+       return 0;
+
+error:
+       return err;
+}
+
+static int dice_hw_free(struct snd_pcm_substream *substream)
+{
+       struct dice *dice = substream->private_data;
+
+       mutex_lock(&dice->mutex);
+       dice_stop_stream(dice);
+       mutex_unlock(&dice->mutex);
+
+       return snd_pcm_lib_free_vmalloc_buffer(substream);
+}
+
+static int dice_prepare(struct snd_pcm_substream *substream)
+{
+       struct dice *dice = substream->private_data;
+       struct fw_device *device = fw_parent_device(dice->unit);
+       __be32 channel;
+       int err;
+
+       mutex_lock(&dice->mutex);
+
+       if (amdtp_out_streaming_error(&dice->stream))
+               dice_stop_stream(dice);
+
+       if (!dice->stream_running) {
+               err = fw_iso_resources_allocate(&dice->resources,
+                               amdtp_out_stream_get_max_payload(&dice->stream),
+                               device->max_speed);
+               if (err < 0)
+                       goto error;
+
+               //TODO: RX_SEQ_START
+               channel = cpu_to_be32(dice->resources.channel);
+               err = snd_fw_transaction(dice->unit,
+                                        TCODE_WRITE_QUADLET_REQUEST,
+                                        rx_address(dice, RX_ISOCHRONOUS),
+                                        &channel, 4);
+               if (err < 0)
+                       goto err_resources;
+
+               err = amdtp_out_stream_start(&dice->stream,
+                                            dice->resources.channel,
+                                            device->max_speed);
+               if (err < 0)
+                       goto err_resources;
+
+               err = dice_enable_set(dice);
+               if (err < 0)
+                       goto err_stream;
+
+               dice->stream_running = true;
+       }
+
+       mutex_unlock(&dice->mutex);
+
+       amdtp_out_stream_pcm_prepare(&dice->stream);
+
+       return 0;
+
+err_stream:
+       amdtp_out_stream_stop(&dice->stream);
+err_resources:
+       fw_iso_resources_free(&dice->resources);
+error:
+       mutex_unlock(&dice->mutex);
+
+       return err;
+}
+
+static int dice_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+       struct dice *dice = substream->private_data;
+       struct snd_pcm_substream *pcm;
+
+       switch (cmd) {
+       case SNDRV_PCM_TRIGGER_START:
+               pcm = substream;
+               break;
+       case SNDRV_PCM_TRIGGER_STOP:
+               pcm = NULL;
+               break;
+       default:
+               return -EINVAL;
+       }
+       amdtp_out_stream_pcm_trigger(&dice->stream, pcm);
+
+       return 0;
+}
+
+static snd_pcm_uframes_t dice_pointer(struct snd_pcm_substream *substream)
+{
+       struct dice *dice = substream->private_data;
+
+       return amdtp_out_stream_pcm_pointer(&dice->stream);
+}
+
+static int dice_create_pcm(struct dice *dice)
+{
+       static struct snd_pcm_ops ops = {
+               .open      = dice_open,
+               .close     = dice_close,
+               .ioctl     = snd_pcm_lib_ioctl,
+               .hw_params = dice_hw_params,
+               .hw_free   = dice_hw_free,
+               .prepare   = dice_prepare,
+               .trigger   = dice_trigger,
+               .pointer   = dice_pointer,
+               .page      = snd_pcm_lib_get_vmalloc_page,
+               .mmap      = snd_pcm_lib_mmap_vmalloc,
+       };
+       __be32 clock;
+       struct snd_pcm *pcm;
+       int err;
+
+       clock = cpu_to_be32(CLOCK_SOURCE_ARX1 | CLOCK_RATE_44100);
+       err = snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST,
+                                global_address(dice, GLOBAL_CLOCK_SELECT),
+                                &clock, 4);
+       if (err < 0)
+               return err;
+
+       err = snd_pcm_new(dice->card, "DICE", 0, 1, 0, &pcm);
+       if (err < 0)
+               return err;
+       pcm->private_data = dice;
+       strcpy(pcm->name, dice->card->shortname);
+       dice->pcm = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+       dice->pcm->ops = &ops;
+
+       return 0;
+}
+
+// TODO: implement these
+
+static long dice_hwdep_read(struct snd_hwdep *hwdep, char __user *buf,
+                           long count, loff_t *offset)
+{
+       return -EIO;
+}
+
+static int dice_hwdep_open(struct snd_hwdep *hwdep, struct file *file)
+{
+       return -EIO;
+}
+
+static int dice_hwdep_release(struct snd_hwdep *hwdep, struct file *file)
+{
+       return 0;
+}
+
+static unsigned int dice_hwdep_poll(struct snd_hwdep *hwdep, struct file *file,
+                                   poll_table *wait)
+{
+       return POLLERR | POLLHUP;
+}
+
+static int dice_hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
+                           unsigned int cmd, unsigned long arg)
+{
+       return -EIO;
+}
+
+static int dice_create_hwdep(struct dice *dice)
+{
+       static const struct snd_hwdep_ops ops = {
+               .read         = dice_hwdep_read,
+               .open         = dice_hwdep_open,
+               .release      = dice_hwdep_release,
+               .poll         = dice_hwdep_poll,
+               .ioctl        = dice_hwdep_ioctl,
+               .ioctl_compat = dice_hwdep_ioctl,
+       };
+       struct snd_hwdep *hwdep;
+       int err;
+
+       err = snd_hwdep_new(dice->card, "DICE", 0, &hwdep);
+       if (err < 0)
+               return err;
+       strcpy(hwdep->name, "DICE");
+       hwdep->iface = SNDRV_HWDEP_IFACE_FW_DICE;
+       hwdep->ops = ops;
+       hwdep->private_data = dice;
+       hwdep->exclusive = true;
+
+       return 0;
+}
+
+static void dice_card_free(struct snd_card *card)
+{
+       struct dice *dice = card->private_data;
+
+       amdtp_out_stream_destroy(&dice->stream);
+       fw_core_remove_address_handler(&dice->notification_handler);
+       mutex_destroy(&dice->mutex);
+}
+
+static int dice_init_offsets(struct dice *dice)
+{
+       __be32 pointers[6];
+       unsigned int global_size, rx_size;
+       int err;
+
+       err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST,
+                                DICE_PRIVATE_SPACE, &pointers, 6 * 4);
+       if (err < 0)
+               return err;
+
+       dice->global_offset = be32_to_cpu(pointers[0]) * 4;
+       global_size = be32_to_cpu(pointers[1]);
+       dice->rx_offset = be32_to_cpu(pointers[4]) * 4;
+       rx_size = be32_to_cpu(pointers[5]);
+
+       /* some sanity checks to ensure that we actually have a DICE */
+       if (dice->global_offset < 10 * 4 || global_size < 0x168 / 4 ||
+           dice->rx_offset < 10 * 4 || rx_size < 0x120 / 4) {
+               dev_err(&dice->unit->device, "invalid register pointers\n");
+               return -ENXIO;
+       }
+
+       return 0;
+}
+
+static void dice_card_strings(struct dice *dice)
+{
+       struct snd_card *card = dice->card;
+       struct fw_device *dev = fw_parent_device(dice->unit);
+       char vendor[32], model[32];
+       unsigned int i;
+       int err;
+
+       strcpy(card->driver, "DICE");
+
+       strcpy(card->shortname, "DICE");
+       BUILD_BUG_ON(NICK_NAME_SIZE < sizeof(card->shortname));
+       err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST,
+                                global_address(dice, GLOBAL_NICK_NAME),
+                                card->shortname, sizeof(card->shortname));
+       if (err >= 0) {
+               /* DICE strings are returned in "always-wrong" endianness */
+               BUILD_BUG_ON(sizeof(card->shortname) % 4 != 0);
+               for (i = 0; i < sizeof(card->shortname); i += 4)
+                       swab32s((u32 *)&card->shortname[i]);
+               card->shortname[sizeof(card->shortname) - 1] = '\0';
+       }
+
+       strcpy(vendor, "?");
+       fw_csr_string(dev->config_rom + 5, CSR_VENDOR, vendor, sizeof(vendor));
+       strcpy(model, "?");
+       fw_csr_string(dice->unit->directory, CSR_MODEL, model, sizeof(model));
+       snprintf(card->longname, sizeof(card->longname),
+                "%s %s, GUID %08x%08x at %s, S%d",
+                vendor, model, dev->config_rom[3], dev->config_rom[4],
+                dev_name(&dice->unit->device), 100 << dev->max_speed);
+
+       strcpy(card->mixername, "DICE");
+}
+
+static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id)
+{
+       struct snd_card *card;
+       struct dice *dice;
+       int err;
+
+       err = snd_card_create(-1, NULL, THIS_MODULE, sizeof(*dice), &card);
+       if (err < 0)
+               return err;
+       snd_card_set_dev(card, &unit->device);
+
+       dice = card->private_data;
+       dice->card = card;
+       mutex_init(&dice->mutex);
+       dice->unit = unit;
+
+       err = dice_init_offsets(dice);
+       if (err < 0)
+               goto err_mutex;
+
+       dice->notification_handler.length = 4;
+       dice->notification_handler.address_callback = dice_notification;
+       dice->notification_handler.callback_data = dice;
+       err = fw_core_add_address_handler(&dice->notification_handler,
+                                         &fw_high_memory_region);
+       if (err < 0)
+               goto err_mutex;
+
+       err = fw_iso_resources_init(&dice->resources, unit);
+       if (err < 0)
+               goto err_notification_handler;
+       dice->resources.channels_mask = 0x00000000ffffffffuLL;
+
+       err = amdtp_out_stream_init(&dice->stream, unit, CIP_NONBLOCKING);
+       if (err < 0)
+               goto err_resources;
+
+       err = dice_owner_set(dice);
+       if (err < 0)
+               goto err_stream;
+
+       card->private_free = dice_card_free;
+
+       dice_card_strings(dice);
+
+       err = dice_create_pcm(dice);
+       if (err < 0)
+               goto error;
+
+       err = dice_create_hwdep(dice);
+       if (err < 0)
+               goto error;
+
+       err = snd_card_register(card);
+       if (err < 0)
+               goto error;
+
+       dev_set_drvdata(&unit->device, dice);
+
+       return 0;
+
+err_stream:
+       amdtp_out_stream_destroy(&dice->stream);
+err_resources:
+       fw_iso_resources_destroy(&dice->resources);
+err_notification_handler:
+       fw_core_remove_address_handler(&dice->notification_handler);
+err_mutex:
+       mutex_destroy(&dice->mutex);
+error:
+       snd_card_free(card);
+       return err;
+}
+
+static void dice_remove(struct fw_unit *unit)
+{
+       struct dice *dice = dev_get_drvdata(&unit->device);
+
+       snd_card_disconnect(dice->card);
+
+       mutex_lock(&dice->mutex);
+       amdtp_out_stream_pcm_abort(&dice->stream);
+       dice_stop_stream(dice);
+       dice_owner_clear(dice);
+       mutex_unlock(&dice->mutex);
+
+       snd_card_free_when_closed(dice->card);
+}
+
+static void dice_bus_reset(struct fw_unit *unit)
+{
+       struct dice *dice = dev_get_drvdata(&unit->device);
+
+       mutex_lock(&dice->mutex);
+       /*
+        * XXX is the following true?
+        * On a bus reset, the DICE firmware disables streaming and then goes
+        * off contemplating its own navel for hundreds of milliseconds before
+        * it can react to any of our attempts to reenable streaming.  This
+        * means that we lose synchronization anyway, so we force our streams
+        * to stop so that the application can restart them in an orderly
+        * manner.
+        */
+       dice_owner_update(dice);
+       amdtp_out_stream_pcm_abort(&dice->stream);
+       dice_stop_stream(dice);
+       mutex_unlock(&dice->mutex);
+}
+
+#define TC_OUI         0x000166
+#define DICE_INTERFACE 0x000001
+
+static const struct ieee1394_device_id dice_id_table[] = {
+       {
+               .match_flags  = IEEE1394_MATCH_SPECIFIER_ID |
+                               IEEE1394_MATCH_VERSION,
+               .specifier_id = TC_OUI,
+               .version      = DICE_INTERFACE,
+       },
+       { }
+};
+MODULE_DEVICE_TABLE(ieee1394, dice_id_table);
+
+static struct fw_driver dice_driver = {
+       .driver   = {
+               .owner  = THIS_MODULE,
+               .name   = KBUILD_MODNAME,
+               .bus    = &fw_bus_type,
+       },
+       .probe    = dice_probe,
+       .update   = dice_bus_reset,
+       .remove   = dice_remove,
+       .id_table = dice_id_table,
+};
+
+static int __init alsa_dice_init(void)
+{
+       return driver_register(&dice_driver.driver);
+}
+
+static void __exit alsa_dice_exit(void)
+{
+       driver_unregister(&dice_driver.driver);
+}
+
+module_init(alsa_dice_init);
+module_exit(alsa_dice_exit);