[alsa-devel] [GIT PULL] [00/29] playback-only DICE driver
Hi Takashi,
please pull this driver for FireWire DACs based on the DICE chip family. While the driver supports only PCM playback at the moment, it fills a gap left by FFADO, which supports only full- duplex devices.
Thanks, Clemens ----------------------------------------------------------------
The following changes since commit 6e4664525b1db28f8c4e1130957f70a94c19213e:
Linux 3.11 (2013-09-02 13:46:10 -0700)
are available in the git repository at:
git://git.alsa-project.org/alsa-kprivate.git dice-driver-playback-only
for you to fetch changes up to b20be8de1b3972ccf9af72850b045214faa8d830:
ALSA: dice: restrict the driver to playback-only devices (2013-10-20 22:07:57 +0200)
---------------------------------------------------------------- Clemens Ladisch (28): ALSA: add DICE driver ALSA: dice: optimize bus reset handling ALSA: dice: allow all sample rates ALSA: dice: reduce noisy logging ALSA: dice, firewire-lib: add blocking mode ALSA: dice: fix hang when unplugging a running device ALSA: dice: implement hwdep device ALSA: dice: clear device lock when closing hwdep device ALSA: firewire: introduce amdtp_out_stream_running() ALSA: dice: reorganize interface definitions ALSA: dice: fix device detection for other vendors ALSA: dice: support dual-wire stream format at 192 kHz ALSA: dice: optimize reading of consecutive registers ALSA: firewire: extend snd_fw_transaction() ALSA: dice: avoid superflous write at bus reset ALSA: dice: remove 10s period length limit ALSA: dice: remove superfluous field ALSA: dice: make amdtp_rates[] const ALSA: dice: get clock capabilities ALSA: dice: allow notifications during initialization ALSA: dice: get rate-dependent parameters ALSA: dice: dynamic sample rate selection ALSA: dice: check clock change timeout ALSA: dice: add a proc file to show device information ALSA: dice: document quadlet alignment ALSA: dice: dice_proc_read: remove wrong typecast ALSA: dice: fix detection of Weiss devices ALSA: dice: restrict the driver to playback-only devices
Stefan Richter (1): ALSA: dice: fix locking
Documentation/ioctl/ioctl-number.txt | 1 + include/uapi/sound/Kbuild | 1 + include/uapi/sound/asound.h | 3 +- include/uapi/sound/firewire.h | 51 ++ sound/firewire/Kconfig | 15 + sound/firewire/Makefile | 2 + sound/firewire/amdtp.c | 209 +++-- sound/firewire/amdtp.h | 46 +- sound/firewire/cmp.c | 50 +- sound/firewire/dice-interface.h | 371 +++++++++ sound/firewire/dice.c | 1494 ++++++++++++++++++++++++++++++++++ sound/firewire/fcp.c | 2 +- sound/firewire/isight.c | 43 +- sound/firewire/lib.c | 24 +- sound/firewire/lib.h | 7 +- sound/firewire/scs1x.c | 8 +- sound/firewire/speakers.c | 16 +- 17 files changed, 2192 insertions(+), 151 deletions(-) create mode 100644 include/uapi/sound/firewire.h create mode 100644 sound/firewire/dice-interface.h create mode 100644 sound/firewire/dice.c
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 | 1 include/uapi/sound/Kbuild | 1 include/uapi/sound/asound.h | 3 include/uapi/sound/firewire.h | 51 ++ sound/firewire/Kconfig | 13 sound/firewire/Makefile | 2 sound/firewire/dice.c | 1008 ++++++++++++++++++++++++++++++++++ 7 files changed, 1078 insertions(+), 1 deletion(-) create mode 100644 include/uapi/sound/firewire.h create mode 100644 sound/firewire/dice.c
diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt index 2a5f0e1..7cbfa3c 100644 --- a/Documentation/ioctl/ioctl-number.txt +++ b/Documentation/ioctl/ioctl-number.txt @@ -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! diff --git a/include/uapi/sound/Kbuild b/include/uapi/sound/Kbuild index 0f7d279..a7f2770 100644 --- a/include/uapi/sound/Kbuild +++ b/include/uapi/sound/Kbuild @@ -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 diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 041203f..9fc6219 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -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 index 0000000..e86131c --- /dev/null +++ b/include/uapi/sound/firewire.h @@ -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 diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index ea063e1..9153309 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -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 diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index 460179d..5099550 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -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 index 0000000..ac71b2b --- /dev/null +++ b/sound/firewire/dice.c @@ -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);
After a bus reset, do not stop the stream completely to avoid having to reconfigure the device when restarting the stream.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice.c | 148 +++++++++++++++++++++++++++++++------------------ 1 file changed, 93 insertions(+), 55 deletions(-)
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index ac71b2b..b081021 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -559,24 +559,92 @@ static int dice_close(struct snd_pcm_substream *substream) return 0; }
-static void dice_stop_stream(struct dice *dice) +static int dice_stream_start_packets(struct dice *dice) { - __be32 channel; + int err; + + if (dice->stream_running) + return 0;
- if (dice->stream_running) { - dice_enable_clear(dice); + err = amdtp_out_stream_start(&dice->stream, dice->resources.channel, + fw_parent_device(dice->unit)->max_speed); + if (err < 0) + return err;
+ err = dice_enable_set(dice); + if (err < 0) { amdtp_out_stream_stop(&dice->stream); + return err; + }
- channel = cpu_to_be32((u32)-1); - snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST, - rx_address(dice, RX_ISOCHRONOUS), - &channel, 4); + dice->stream_running = true;
- fw_iso_resources_free(&dice->resources); + return 0; +}
- dice->stream_running = false; +static int dice_stream_start(struct dice *dice) +{ + __be32 channel; + int err; + + if (!dice->resources.allocated) { + err = fw_iso_resources_allocate(&dice->resources, + amdtp_out_stream_get_max_payload(&dice->stream), + fw_parent_device(dice->unit)->max_speed); + if (err < 0) + goto error; + + 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 = dice_stream_start_packets(dice); + if (err < 0) + goto err_rx_channel; + + return 0; + +err_rx_channel: + channel = cpu_to_be32((u32)-1); + snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST, + rx_address(dice, RX_ISOCHRONOUS), &channel, 4); +err_resources: + fw_iso_resources_free(&dice->resources); +error: + return err; +} + +static void dice_stream_stop_packets(struct dice *dice) +{ + if (!dice->stream_running) + return; + + dice_enable_clear(dice); + + amdtp_out_stream_stop(&dice->stream); + + dice->stream_running = false; +} + +static void dice_stream_stop(struct dice *dice) +{ + __be32 channel; + + dice_stream_stop_packets(dice); + + if (!dice->resources.allocated) + return; + + 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); }
static int dice_hw_params(struct snd_pcm_substream *substream, @@ -586,7 +654,7 @@ static int dice_hw_params(struct snd_pcm_substream *substream, int err;
mutex_lock(&dice->mutex); - dice_stop_stream(dice); + dice_stream_stop(dice); mutex_unlock(&dice->mutex);
err = snd_pcm_lib_alloc_vmalloc_buffer(substream, @@ -608,7 +676,7 @@ static int dice_hw_free(struct snd_pcm_substream *substream) struct dice *dice = substream->private_data;
mutex_lock(&dice->mutex); - dice_stop_stream(dice); + dice_stream_stop(dice); mutex_unlock(&dice->mutex);
return snd_pcm_lib_free_vmalloc_buffer(substream); @@ -617,42 +685,17 @@ static int dice_hw_free(struct snd_pcm_substream *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_stop_packets(dice);
- dice->stream_running = true; + err = dice_stream_start(dice); + if (err < 0) { + mutex_unlock(&dice->mutex); + return err; }
mutex_unlock(&dice->mutex); @@ -660,15 +703,6 @@ static int dice_prepare(struct snd_pcm_substream *substream) 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) @@ -941,7 +975,7 @@ static void dice_remove(struct fw_unit *unit)
mutex_lock(&dice->mutex); amdtp_out_stream_pcm_abort(&dice->stream); - dice_stop_stream(dice); + dice_stream_stop(dice); dice_owner_clear(dice); mutex_unlock(&dice->mutex);
@@ -953,8 +987,8 @@ 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 @@ -962,9 +996,13 @@ static void dice_bus_reset(struct fw_unit *unit) * 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); + dice_stream_stop_packets(dice); + + dice_owner_update(dice); + + fw_iso_resources_update(&dice->resources); + mutex_unlock(&dice->mutex); }
Instead of forcing a constant 44.1 kHz, read the current sample rate from the device when opening the PCM device.
Actually changing the sample rate requires some separate controller application.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice.c | 55 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 13 deletions(-)
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index b081021..d3f3eb7 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -75,6 +75,7 @@ #define CLOCK_RATE_ANY_MID 0x00000800 #define CLOCK_RATE_ANY_HIGH 0x00000900 #define CLOCK_RATE_NONE 0x00000a00 +#define CLOCK_RATE_SHIFT 8 #define GLOBAL_ENABLE 0x050 #define ENABLE 0x00000001 #define GLOBAL_STATUS 0x054 @@ -248,6 +249,16 @@ MODULE_DESCRIPTION("DICE driver"); MODULE_AUTHOR("Clemens Ladisch clemens@ladisch.de"); MODULE_LICENSE("GPL v2");
+static const unsigned int dice_rates[] = { + [0] = 32000, + [1] = 44100, + [2] = 48000, + [3] = 88200, + [4] = 96000, + [5] = 176400, + [6] = 192000, +}; + static inline u64 global_address(struct dice *dice, unsigned int offset) { return DICE_PRIVATE_SPACE + dice->global_offset + offset; @@ -508,9 +519,6 @@ static int dice_open(struct snd_pcm_substream *substream) 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, @@ -519,10 +527,21 @@ static int dice_open(struct snd_pcm_substream *substream) }; struct dice *dice = substream->private_data; struct snd_pcm_runtime *runtime = substream->runtime; - __be32 number_audio, number_midi; + __be32 clock_sel, number_audio, number_midi; + unsigned int rate; int err;
err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, + global_address(dice, GLOBAL_CLOCK_SELECT), + &clock_sel, 4); + if (err < 0) + return err; + rate = (be32_to_cpu(clock_sel) & CLOCK_RATE_MASK) >> CLOCK_RATE_SHIFT; + if (rate >= ARRAY_SIZE(dice_rates)) + return -ENXIO; + rate = dice_rates[rate]; + + err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, rx_address(dice, RX_NUMBER_AUDIO), &number_audio, 4); if (err < 0) @@ -534,10 +553,14 @@ static int dice_open(struct snd_pcm_substream *substream) return err;
runtime->hw = hardware; + + runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate); + snd_pcm_limit_hw_rates(runtime); + 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_rate(&dice->stream, rate); amdtp_out_stream_set_pcm(&dice->stream, be32_to_cpu(number_audio)); amdtp_out_stream_set_midi(&dice->stream, be32_to_cpu(number_midi));
@@ -746,17 +769,9 @@ static int dice_create_pcm(struct dice *dice) .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; @@ -897,6 +912,7 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) { struct snd_card *card; struct dice *dice; + __be32 clock_sel; int err;
err = snd_card_create(-1, NULL, THIS_MODULE, sizeof(*dice), &card); @@ -938,6 +954,19 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id)
dice_card_strings(dice);
+ err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, + global_address(dice, GLOBAL_CLOCK_SELECT), + &clock_sel, 4); + if (err < 0) + goto error; + clock_sel &= cpu_to_be32(~CLOCK_SOURCE_MASK); + clock_sel |= cpu_to_be32(CLOCK_SOURCE_ARX1); + err = snd_fw_transaction(unit, TCODE_WRITE_QUADLET_REQUEST, + global_address(dice, GLOBAL_CLOCK_SELECT), + &clock_sel, 4); + if (err < 0) + goto error; + err = dice_create_pcm(dice); if (err < 0) goto error;
The notification bits are not of general interest; log them only when debugging.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index d3f3eb7..02c7b5a 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -505,8 +505,8 @@ static void dice_notification(struct fw_card *card, struct fw_request *request, fw_send_response(card, request, RCODE_ADDRESS_ERROR); return; } - dev_info(&dice->unit->device, - "notification: %08x\n", be32_to_cpup(data)); + dev_dbg(&dice->unit->device, + "notification: %08x\n", be32_to_cpup(data)); fw_send_response(card, request, RCODE_COMPLETE); }
Allow AMDTP output streams to use blocking mode.
Use it for DICE devices, because the old DICE-II chip will in some cases not be able to lock to non-blocking streams (erratum E7).
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/amdtp.c | 54 ++++++++++++++++++++++++++---------------------- sound/firewire/amdtp.h | 7 +++++- sound/firewire/dice.c | 2 +- 3 files changed, 36 insertions(+), 27 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index ea995af..efb2e29 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -42,9 +42,6 @@ static void pcm_period_tasklet(unsigned long data); int amdtp_out_stream_init(struct amdtp_out_stream *s, struct fw_unit *unit, enum cip_out_flags flags) { - if (flags != CIP_NONBLOCKING) - return -EINVAL; - s->unit = fw_unit_get(unit); s->flags = flags; s->context = ERR_PTR(-1); @@ -96,12 +93,20 @@ void amdtp_out_stream_set_rate(struct amdtp_out_stream *s, unsigned int rate) return;
for (sfc = 0; sfc < ARRAY_SIZE(rate_info); ++sfc) - if (rate_info[sfc].rate == rate) { - s->sfc = sfc; - s->syt_interval = rate_info[sfc].syt_interval; - return; - } + if (rate_info[sfc].rate == rate) + goto sfc_found; WARN_ON(1); + return; + +sfc_found: + s->sfc = sfc; + s->syt_interval = rate_info[sfc].syt_interval; + + /* default buffering in the device */ + s->transfer_delay = TRANSFER_DELAY_TICKS - TICKS_PER_CYCLE; + if (s->flags & CIP_BLOCKING) + /* additional buffering needed to adjust for no-data packets */ + s->transfer_delay += TICKS_PER_SECOND * s->syt_interval / rate; } EXPORT_SYMBOL(amdtp_out_stream_set_rate);
@@ -110,25 +115,15 @@ EXPORT_SYMBOL(amdtp_out_stream_set_rate); * @s: the AMDTP output stream * * This function must not be called before the stream has been configured - * with amdtp_out_stream_set_hw_params(), amdtp_out_stream_set_pcm(), and + * with amdtp_out_stream_set_rate(), amdtp_out_stream_set_pcm(), and * amdtp_out_stream_set_midi(). */ unsigned int amdtp_out_stream_get_max_payload(struct amdtp_out_stream *s) { - static const unsigned int max_data_blocks[] = { - [CIP_SFC_32000] = 4, - [CIP_SFC_44100] = 6, - [CIP_SFC_48000] = 6, - [CIP_SFC_88200] = 12, - [CIP_SFC_96000] = 12, - [CIP_SFC_176400] = 23, - [CIP_SFC_192000] = 24, - }; - s->data_block_quadlets = s->pcm_channels; s->data_block_quadlets += DIV_ROUND_UP(s->midi_ports, 8);
- return 8 + max_data_blocks[s->sfc] * 4 * s->data_block_quadlets; + return 8 + s->syt_interval * s->data_block_quadlets * 4; } EXPORT_SYMBOL(amdtp_out_stream_get_max_payload);
@@ -248,7 +243,7 @@ static unsigned int calculate_syt(struct amdtp_out_stream *s, s->last_syt_offset = syt_offset;
if (syt_offset < TICKS_PER_CYCLE) { - syt_offset += TRANSFER_DELAY_TICKS - TICKS_PER_CYCLE; + syt_offset += s->transfer_delay; syt = (cycle + syt_offset / TICKS_PER_CYCLE) << 12; syt += syt_offset % TICKS_PER_CYCLE;
@@ -344,8 +339,17 @@ static void queue_out_packet(struct amdtp_out_stream *s, unsigned int cycle) return; index = s->packet_index;
- data_blocks = calculate_data_blocks(s); syt = calculate_syt(s, cycle); + if (!(s->flags & CIP_BLOCKING)) { + data_blocks = calculate_data_blocks(s); + } else { + if (syt != 0xffff) { + data_blocks = s->syt_interval; + } else { + data_blocks = 0; + syt = 0xffffff; + } + }
buffer = s->buffer.packets[index].buffer; buffer[0] = cpu_to_be32(ACCESS_ONCE(s->source_node_id_field) | @@ -455,9 +459,9 @@ static int queue_initial_skip_packets(struct amdtp_out_stream *s) * @speed: firewire speed code * * The stream cannot be started until it has been configured with - * amdtp_out_stream_set_hw_params(), amdtp_out_stream_set_pcm(), and - * amdtp_out_stream_set_midi(); and it must be started before any - * PCM or MIDI device can be started. + * amdtp_out_stream_set_rate(), amdtp_out_stream_set_pcm(), + * amdtp_out_stream_set_midi(), and amdtp_out_stream_set_format(); + * and it must be started before any PCM or MIDI device can be started. */ int amdtp_out_stream_start(struct amdtp_out_stream *s, int channel, int speed) { diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index f6103d6..fd4ce30 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -11,9 +11,13 @@ * sample_rate/8000 samples, with rounding up or down to adjust * for clock skew and left-over fractional samples. This should * be used if supported by the device. + * @CIP_BLOCKING: In blocking mode, each packet contains either zero or + * SYT_INTERVAL samples, with these two types alternating so that + * the overall sample rate comes out right. */ enum cip_out_flags { - CIP_NONBLOCKING = 0, + CIP_NONBLOCKING = 0x00, + CIP_BLOCKING = 0x01, };
/** @@ -51,6 +55,7 @@ struct amdtp_out_stream { __be32 *buffer, unsigned int frames);
unsigned int syt_interval; + unsigned int transfer_delay; unsigned int source_node_id_field; struct iso_packets_buffer buffer;
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index 02c7b5a..63446f8 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -942,7 +942,7 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) goto err_notification_handler; dice->resources.channels_mask = 0x00000000ffffffffuLL;
- err = amdtp_out_stream_init(&dice->stream, unit, CIP_NONBLOCKING); + err = amdtp_out_stream_init(&dice->stream, unit, CIP_BLOCKING); if (err < 0) goto err_resources;
When aborting a PCM stream, the xrun is signaled only if the stream is running. When disconnecting a PCM stream, calling snd_card_disconnect() too early would change the stream into a non-running state and thus prevent the xrun from being noticed by user space.
To prevent this, move the snd_card_disconnect() call after the xrun.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index 63446f8..d0575a9 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -1000,12 +1000,15 @@ 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); + + snd_card_disconnect(dice->card); + dice_stream_stop(dice); dice_owner_clear(dice); + mutex_unlock(&dice->mutex);
snd_card_free_when_closed(dice->card);
Implement the hwdep locking and notification mechanisms.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice.c | 225 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 203 insertions(+), 22 deletions(-)
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index d0575a9..7225878 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -5,6 +5,7 @@ * Licensed under the terms of the GNU General Public License, version 2. */
+#include <linux/compat.h> #include <linux/delay.h> #include <linux/device.h> #include <linux/firewire.h> @@ -13,8 +14,11 @@ #include <linux/mod_devicetable.h> #include <linux/mutex.h> #include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/wait.h> #include <sound/control.h> #include <sound/core.h> +#include <sound/firewire.h> #include <sound/hwdep.h> #include <sound/initval.h> #include <sound/pcm.h> @@ -233,13 +237,18 @@ struct dice { struct snd_card *card; struct fw_unit *unit; + spinlock_t lock; struct mutex mutex; unsigned int global_offset; unsigned int rx_offset; struct fw_address_handler notification_handler; int owner_generation; + int dev_lock_count; /* > 0 driver, < 0 userspace */ + bool dev_lock_changed; bool global_enabled; bool stream_running; + wait_queue_head_t hwdep_wait; + u32 notification_bits; struct snd_pcm_substream *pcm; struct fw_iso_resources resources; struct amdtp_out_stream stream; @@ -259,6 +268,47 @@ static const unsigned int dice_rates[] = { [6] = 192000, };
+static void dice_lock_changed(struct dice *dice) +{ + dice->dev_lock_changed = true; + wake_up(&dice->hwdep_wait); +} + +static int dice_try_lock(struct dice *dice) +{ + int err; + + spin_lock_irq(&dice->lock); + + if (dice->dev_lock_count < 0) { + err = -EBUSY; + goto out; + } + + if (dice->dev_lock_count++ == 0) + dice_lock_changed(dice); + err = 0; + +out: + spin_unlock_irq(&dice->lock); + + return err; +} + +static void dice_unlock(struct dice *dice) +{ + spin_lock_irq(&dice->lock); + + if (WARN_ON(dice->dev_lock_count <= 0)) + goto out; + + if (--dice->dev_lock_count == 0) + dice_lock_changed(dice); + +out: + spin_unlock_irq(&dice->lock); +} + static inline u64 global_address(struct dice *dice, unsigned int offset) { return DICE_PRIVATE_SPACE + dice->global_offset + offset; @@ -496,6 +546,7 @@ static void dice_notification(struct fw_card *card, struct fw_request *request, void *data, size_t length, void *callback_data) { struct dice *dice = callback_data; + unsigned long flags;
if (tcode != TCODE_WRITE_QUADLET_REQUEST) { fw_send_response(card, request, RCODE_TYPE_ERROR); @@ -505,9 +556,11 @@ static void dice_notification(struct fw_card *card, struct fw_request *request, fw_send_response(card, request, RCODE_ADDRESS_ERROR); return; } - dev_dbg(&dice->unit->device, - "notification: %08x\n", be32_to_cpup(data)); + spin_lock_irqsave(&dice->lock, flags); + dice->notification_bits |= be32_to_cpup(data); + spin_unlock_irqrestore(&dice->lock, flags); fw_send_response(card, request, RCODE_COMPLETE); + wake_up(&dice->hwdep_wait); }
static int dice_open(struct snd_pcm_substream *substream) @@ -531,26 +584,32 @@ static int dice_open(struct snd_pcm_substream *substream) unsigned int rate; int err;
+ err = dice_try_lock(dice); + if (err < 0) + goto error; + err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, global_address(dice, GLOBAL_CLOCK_SELECT), &clock_sel, 4); if (err < 0) - return err; + goto err_lock; rate = (be32_to_cpu(clock_sel) & CLOCK_RATE_MASK) >> CLOCK_RATE_SHIFT; - if (rate >= ARRAY_SIZE(dice_rates)) - return -ENXIO; + if (rate >= ARRAY_SIZE(dice_rates)) { + err = -ENXIO; + goto err_lock; + } rate = dice_rates[rate];
err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, rx_address(dice, RX_NUMBER_AUDIO), &number_audio, 4); if (err < 0) - return err; + goto err_lock; err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, rx_address(dice, RX_NUMBER_MIDI), &number_midi, 4); if (err < 0) - return err; + goto err_lock;
runtime->hw = hardware;
@@ -568,17 +627,26 @@ static int dice_open(struct snd_pcm_substream *substream) SNDRV_PCM_HW_PARAM_PERIOD_TIME, 5000, 8192000); if (err < 0) - return err; + goto err_lock;
err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); if (err < 0) - return err; + goto err_lock;
return 0; + +err_lock: + dice_unlock(dice); +error: + return err; }
static int dice_close(struct snd_pcm_substream *substream) { + struct dice *dice = substream->private_data; + + dice_unlock(dice); + return 0; }
@@ -783,45 +851,156 @@ static int dice_create_pcm(struct dice *dice) return 0; }
-// TODO: implement these - static long dice_hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, loff_t *offset) { - return -EIO; + struct dice *dice = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&dice->lock); + + while (!dice->dev_lock_changed && dice->notification_bits == 0) { + prepare_to_wait(&dice->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&dice->lock); + schedule(); + finish_wait(&dice->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&dice->lock); + } + + memset(&event, 0, sizeof(event)); + if (dice->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = dice->dev_lock_count > 0; + dice->dev_lock_changed = false; + + count = min(count, (long)sizeof(event.lock_status)); + } else { + event.dice_notification.type = SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION; + event.dice_notification.notification = dice->notification_bits; + dice->notification_bits = 0; + + count = min(count, (long)sizeof(event.dice_notification)); + } + + spin_unlock_irq(&dice->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; }
-static int dice_hwdep_open(struct snd_hwdep *hwdep, struct file *file) +static unsigned int dice_hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) { - return -EIO; + struct dice *dice = hwdep->private_data; + unsigned int events; + + poll_wait(file, &dice->hwdep_wait, wait); + + spin_lock_irq(&dice->lock); + if (dice->dev_lock_changed || dice->notification_bits != 0) + events = POLLIN | POLLRDNORM; + else + events = 0; + spin_unlock_irq(&dice->lock); + + return events; }
-static int dice_hwdep_release(struct snd_hwdep *hwdep, struct file *file) +static int dice_hwdep_get_info(struct dice *dice, void __user *arg) { + struct fw_device *dev = fw_parent_device(dice->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_DICE; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + return 0; }
-static unsigned int dice_hwdep_poll(struct snd_hwdep *hwdep, struct file *file, - poll_table *wait) +static int dice_hwdep_lock(struct dice *dice) +{ + int err; + + spin_lock_irq(&dice->lock); + + if (dice->dev_lock_count == 0) { + dice->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&dice->lock); + + return err; +} + +static int dice_hwdep_unlock(struct dice *dice) { - return POLLERR | POLLHUP; + int err; + + spin_lock_irq(&dice->lock); + + if (dice->dev_lock_count == -1) { + dice->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&dice->lock); + + return err; }
static int dice_hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, unsigned int cmd, unsigned long arg) { - return -EIO; + struct dice *dice = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return dice_hwdep_get_info(dice, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return dice_hwdep_lock(dice); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return dice_hwdep_unlock(dice); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int dice_hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return dice_hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); } +#else +#define dice_hwdep_compat_ioctl NULL +#endif
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, + .ioctl_compat = dice_hwdep_compat_ioctl, }; struct snd_hwdep *hwdep; int err; @@ -922,8 +1101,10 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id)
dice = card->private_data; dice->card = card; + spin_lock_init(&dice->lock); mutex_init(&dice->mutex); dice->unit = unit; + init_waitqueue_head(&dice->hwdep_wait);
err = dice_init_offsets(dice); if (err < 0)
Ensure that misbehaving or aborted userspace programs do not accidentally keep the lock.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice.c | 13 +++++++++++++ 1 file changed, 13 insertions(+)
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index 7225878..ef04089 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -966,6 +966,18 @@ static int dice_hwdep_unlock(struct dice *dice) return err; }
+static int dice_hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct dice *dice = hwdep->private_data; + + spin_lock_irq(&dice->lock); + if (dice->dev_lock_count == -1) + dice->dev_lock_count = 0; + spin_unlock_irq(&dice->lock); + + return 0; +} + static int dice_hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, unsigned int cmd, unsigned long arg) { @@ -998,6 +1010,7 @@ static int dice_create_hwdep(struct dice *dice) { static const struct snd_hwdep_ops ops = { .read = dice_hwdep_read, + .release = dice_hwdep_release, .poll = dice_hwdep_poll, .ioctl = dice_hwdep_ioctl, .ioctl_compat = dice_hwdep_compat_ioctl,
Introduce the helper function amdtp_out_stream_running(). This makes many checks in amdtp.c clearer and frees the device drivers from having to track this with a separate variable.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/amdtp.c | 10 +++++----- sound/firewire/amdtp.h | 6 ++++++ sound/firewire/dice.c | 17 +++++------------ sound/firewire/speakers.c | 8 ++------ 4 files changed, 18 insertions(+), 23 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index efb2e29..d56b8e7 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -59,7 +59,7 @@ EXPORT_SYMBOL(amdtp_out_stream_init); */ void amdtp_out_stream_destroy(struct amdtp_out_stream *s) { - WARN_ON(!IS_ERR(s->context)); + WARN_ON(amdtp_out_stream_running(s)); mutex_destroy(&s->mutex); fw_unit_put(s->unit); } @@ -89,7 +89,7 @@ void amdtp_out_stream_set_rate(struct amdtp_out_stream *s, unsigned int rate) }; unsigned int sfc;
- if (WARN_ON(!IS_ERR(s->context))) + if (WARN_ON(amdtp_out_stream_running(s))) return;
for (sfc = 0; sfc < ARRAY_SIZE(rate_info); ++sfc) @@ -145,7 +145,7 @@ static void amdtp_write_s32(struct amdtp_out_stream *s, void amdtp_out_stream_set_pcm_format(struct amdtp_out_stream *s, snd_pcm_format_t format) { - if (WARN_ON(!IS_ERR(s->context))) + if (WARN_ON(amdtp_out_stream_running(s))) return;
switch (format) { @@ -481,7 +481,7 @@ int amdtp_out_stream_start(struct amdtp_out_stream *s, int channel, int speed)
mutex_lock(&s->mutex);
- if (WARN_ON(!IS_ERR(s->context) || + if (WARN_ON(amdtp_out_stream_running(s) || (!s->pcm_channels && !s->midi_ports))) { err = -EBADFD; goto err_unlock; @@ -577,7 +577,7 @@ void amdtp_out_stream_stop(struct amdtp_out_stream *s) { mutex_lock(&s->mutex);
- if (IS_ERR(s->context)) { + if (!amdtp_out_stream_running(s)) { mutex_unlock(&s->mutex); return; } diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index fd4ce30..28b1bf5 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -1,6 +1,7 @@ #ifndef SOUND_FIREWIRE_AMDTP_H_INCLUDED #define SOUND_FIREWIRE_AMDTP_H_INCLUDED
+#include <linux/err.h> #include <linux/interrupt.h> #include <linux/mutex.h> #include "packets-buffer.h" @@ -92,6 +93,11 @@ void amdtp_out_stream_pcm_prepare(struct amdtp_out_stream *s); unsigned long amdtp_out_stream_pcm_pointer(struct amdtp_out_stream *s); void amdtp_out_stream_pcm_abort(struct amdtp_out_stream *s);
+static inline bool amdtp_out_stream_running(struct amdtp_out_stream *s) +{ + return !IS_ERR(s->context); +} + /** * amdtp_out_stream_set_pcm - configure format of PCM samples * @s: the AMDTP output stream to be configured diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index ef04089..3591aeb 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -246,7 +246,6 @@ struct dice { int dev_lock_count; /* > 0 driver, < 0 userspace */ bool dev_lock_changed; bool global_enabled; - bool stream_running; wait_queue_head_t hwdep_wait; u32 notification_bits; struct snd_pcm_substream *pcm; @@ -654,7 +653,7 @@ static int dice_stream_start_packets(struct dice *dice) { int err;
- if (dice->stream_running) + if (amdtp_out_stream_running(&dice->stream)) return 0;
err = amdtp_out_stream_start(&dice->stream, dice->resources.channel, @@ -668,8 +667,6 @@ static int dice_stream_start_packets(struct dice *dice) return err; }
- dice->stream_running = true; - return 0; }
@@ -712,14 +709,10 @@ error:
static void dice_stream_stop_packets(struct dice *dice) { - if (!dice->stream_running) - return; - - dice_enable_clear(dice); - - amdtp_out_stream_stop(&dice->stream); - - dice->stream_running = false; + if (amdtp_out_stream_running(&dice->stream)) { + dice_enable_clear(dice); + amdtp_out_stream_stop(&dice->stream); + } }
static void dice_stream_stop(struct dice *dice) diff --git a/sound/firewire/speakers.c b/sound/firewire/speakers.c index 2c63865..0ac5630 100644 --- a/sound/firewire/speakers.c +++ b/sound/firewire/speakers.c @@ -53,7 +53,6 @@ struct fwspk { struct mutex mutex; struct cmp_connection connection; struct amdtp_out_stream stream; - bool stream_running; bool mute; s16 volume[6]; s16 volume_min; @@ -189,10 +188,9 @@ static int fwspk_close(struct snd_pcm_substream *substream)
static void fwspk_stop_stream(struct fwspk *fwspk) { - if (fwspk->stream_running) { + if (amdtp_out_stream_running(&fwspk->stream)) { amdtp_out_stream_stop(&fwspk->stream); cmp_connection_break(&fwspk->connection); - fwspk->stream_running = false; } }
@@ -286,7 +284,7 @@ static int fwspk_prepare(struct snd_pcm_substream *substream) if (amdtp_out_streaming_error(&fwspk->stream)) fwspk_stop_stream(fwspk);
- if (!fwspk->stream_running) { + if (!amdtp_out_stream_running(&fwspk->stream)) { err = cmp_connection_establish(&fwspk->connection, amdtp_out_stream_get_max_payload(&fwspk->stream)); if (err < 0) @@ -297,8 +295,6 @@ static int fwspk_prepare(struct snd_pcm_substream *substream) fwspk->connection.speed); if (err < 0) goto err_connection; - - fwspk->stream_running = true; }
mutex_unlock(&fwspk->mutex);
Move the DICE interface symbols into a separate header file, and add more documentation.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice-interface.h | 371 +++++++++++++++++++++++++++++++++++++++ sound/firewire/dice.c | 209 ---------------------- 2 files changed, 373 insertions(+), 207 deletions(-) create mode 100644 sound/firewire/dice-interface.h
diff --git a/sound/firewire/dice-interface.h b/sound/firewire/dice-interface.h new file mode 100644 index 0000000..af916b9 --- /dev/null +++ b/sound/firewire/dice-interface.h @@ -0,0 +1,371 @@ +#ifndef SOUND_FIREWIRE_DICE_INTERFACE_H_INCLUDED +#define SOUND_FIREWIRE_DICE_INTERFACE_H_INCLUDED + +/* + * DICE device interface definitions + */ + +/* + * Generally, all registers can be read like memory, i.e., with quadlet read or + * block read transactions with any alignment or length. Writes are not + * allowed except where noted; quadlet-sized registers must be written with + * a quadlet write transaction. + * + * All values are in big endian. The DICE firmware runs on a little-endian CPU + * and just byte-swaps _all_ quadlets on the bus, so values without endianness + * (e.g. strings) get scrambled and must be byte-swapped again by the driver. + */ + +/* + * Streaming is handled by the "DICE driver" interface. Its registers are + * located in this private address space. + */ +#define DICE_PRIVATE_SPACE 0xffffe0000000uLL + +/* + * The registers are organized in several sections, which are organized + * separately to allow them to be extended individually. Whether a register is + * supported can be detected by checking its offset against its section's size. + * + * The section offset values are relative to DICE_PRIVATE_SPACE; the offset/ + * size values are measured in quadlets. Read-only. + */ +#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 +#define DICE_EXT_SYNC_OFFSET 0x18 +#define DICE_EXT_SYNC_SIZE 0x1c +#define DICE_UNUSED2_OFFSET 0x20 +#define DICE_UNUSED2_SIZE 0x24 + +/* + * Global settings. + */ + +/* + * Stores the full 64-bit address (node ID and offset in the node's address + * space) where the device will send notifications. Must be changed with + * a compare/swap transaction by the owner. This register is automatically + * cleared on a bus reset. + */ +#define GLOBAL_OWNER 0x000 +#define OWNER_NO_OWNER 0xffff000000000000uLL +#define OWNER_NODE_SHIFT 48 + +/* + * A bitmask with asynchronous events; read-only. When any event(s) happen, + * the bits of previous events are cleared, and the value of this register is + * also written to the address stored in the owner register. + */ +#define GLOBAL_NOTIFICATION 0x008 +/* Some registers in the Rx/Tx sections may have changed. */ +#define NOTIFY_RX_CFG_CHG 0x00000001 +#define NOTIFY_TX_CFG_CHG 0x00000002 +/* Lock status of the current clock source may have changed. */ +#define NOTIFY_LOCK_CHG 0x00000010 +/* Write to the clock select register has been finished. */ +#define NOTIFY_CLOCK_ACCEPTED 0x00000020 +/* Lock status of some clock source has changed. */ +#define NOTIFY_EXT_STATUS 0x00000040 +/* Other bits may be used for device-specific events. */ + +/* + * A name that can be customized for each device; read/write. Padded with zero + * bytes. Quadlets are byte-swapped. The encoding is whatever the host driver + * happens to be using. + */ +#define GLOBAL_NICK_NAME 0x00c +#define NICK_NAME_SIZE 64 + +/* + * The current sample rate and clock source; read/write. Whether a clock + * source or sample rate is supported is device-specific; the internal clock + * source is always available. Low/mid/high = up to 48/96/192 kHz. This + * register can be changed even while streams are running. + */ +#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 CLOCK_RATE_SHIFT 8 + +/* + * Enable streaming; read/write. Writing a non-zero value (re)starts all + * streams that have a valid iso channel set; zero stops all streams. The + * streams' parameters must be configured before starting. This register is + * automatically cleared on a bus reset. + */ +#define GLOBAL_ENABLE 0x050 + +/* + * Status of the sample clock; read-only. + */ +#define GLOBAL_STATUS 0x054 +/* The current clock source is locked. */ +#define STATUS_SOURCE_LOCKED 0x00000001 +/* The actual sample rate; CLOCK_RATE_32000-_192000 or _NONE. */ +#define STATUS_NOMINAL_RATE_MASK 0x0000ff00 + +/* + * Status of all clock sources; read-only. + */ +#define GLOBAL_EXTENDED_STATUS 0x058 +/* + * The _LOCKED bits always show the current status; any change generates + * a notification. + */ +#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 +/* + * The _SLIP bits do not generate notifications; a set bit indicates that an + * error occurred since the last time when this register was read with + * a quadlet read transaction. + */ +#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 + +/* + * The measured rate of the current clock source, in Hz; read-only. + */ +#define GLOBAL_SAMPLE_RATE 0x05c + +/* + * The version of the DICE driver specification that this device conforms to; + * read-only. + */ +#define GLOBAL_VERSION 0x060 + +/* Some old firmware versions do not have the following global registers: */ + +/* + * Supported sample rates and clock sources; read-only. + */ +#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 + +/* + * Names of all clock sources; read-only. Quadlets are byte-swapped. Names + * are separated with one backslash, the list is terminated with two + * backslashes. Unused clock sources are included. + */ +#define GLOBAL_CLOCK_SOURCE_NAMES 0x068 +#define CLOCK_SOURCE_NAMES_SIZE 256 + +/* + * Capture stream settings. This section includes the number/size registers + * and the registers of all streams. + */ + +/* + * The number of supported capture streams; read-only. + */ +#define TX_NUMBER 0x000 + +/* + * The size of one stream's register block, in quadlets; read-only. The + * registers of the first stream follow immediately afterwards; the registers + * of the following streams are offset by this register's value. + */ +#define TX_SIZE 0x004 + +/* + * The isochronous channel number on which packets are sent, or -1 if the + * stream is not to be used; read/write. + */ +#define TX_ISOCHRONOUS 0x008 + +/* + * The number of audio channels; read-only. There will be one quadlet per + * channel; the first channel is the first quadlet in a data block. + */ +#define TX_NUMBER_AUDIO 0x00c + +/* + * The number of MIDI ports, 0-8; read-only. If > 0, there will be one + * additional quadlet in each data block, following the audio quadlets. + */ +#define TX_NUMBER_MIDI 0x010 + +/* + * The speed at which the packets are sent, SCODE_100-_400; read/write. + */ +#define TX_SPEED 0x014 + +/* + * Names of all audio channels; read-only. Quadlets are byte-swapped. Names + * are separated with one backslash, the list is terminated with two + * backslashes. + */ +#define TX_NAMES 0x018 +#define TX_NAMES_SIZE 256 + +/* + * Audio IEC60958 capabilities; read-only. Bitmask with one bit per audio + * channel. + */ +#define TX_AC3_CAPABILITIES 0x118 + +/* + * Send audio data with IEC60958 label; read/write. Bitmask with one bit per + * audio channel. This register can be changed even while the stream is + * running. + */ +#define TX_AC3_ENABLE 0x11c + +/* + * Playback stream settings. This section includes the number/size registers + * and the registers of all streams. + */ + +/* + * The number of supported playback streams; read-only. + */ +#define RX_NUMBER 0x000 + +/* + * The size of one stream's register block, in quadlets; read-only. The + * registers of the first stream follow immediately afterwards; the registers + * of the following streams are offset by this register's value. + */ +#define RX_SIZE 0x004 + +/* + * The isochronous channel number on which packets are received, or -1 if the + * stream is not to be used; read/write. + */ +#define RX_ISOCHRONOUS 0x008 + +/* + * Index of first quadlet to be interpreted; read/write. If > 0, that many + * quadlets at the beginning of each data block will be ignored, and all the + * audio and MIDI quadlets will follow. + */ +#define RX_SEQ_START 0x00c + +/* + * The number of audio channels; read-only. There will be one quadlet per + * channel. + */ +#define RX_NUMBER_AUDIO 0x010 + +/* + * The number of MIDI ports, 0-8; read-only. If > 0, there will be one + * additional quadlet in each data block, following the audio quadlets. + */ +#define RX_NUMBER_MIDI 0x014 + +/* + * Names of all audio channels; read-only. Quadlets are byte-swapped. Names + * are separated with one backslash, the list is terminated with two + * backslashes. + */ +#define RX_NAMES 0x018 +#define RX_NAMES_SIZE 256 + +/* + * Audio IEC60958 capabilities; read-only. Bitmask with one bit per audio + * channel. + */ +#define RX_AC3_CAPABILITIES 0x118 + +/* + * Receive audio data with IEC60958 label; read/write. Bitmask with one bit + * per audio channel. This register can be changed even while the stream is + * running. + */ +#define RX_AC3_ENABLE 0x11c + +/* + * Extended synchronization information. + * This section can be read completely with a block read request. + */ + +/* + * Current clock source; read-only. + */ +#define EXT_SYNC_CLOCK_SOURCE 0x000 + +/* + * Clock source is locked (boolean); read-only. + */ +#define EXT_SYNC_LOCKED 0x004 + +/* + * Current sample rate (CLOCK_RATE_* >> CLOCK_RATE_SHIFT), _32000-_192000 or + * _NONE; read-only. + */ +#define EXT_SYNC_RATE 0x008 + +/* + * ADAT user data bits; read-only. + */ +#define EXT_SYNC_ADAT_USER_DATA 0x00c +/* The data bits, if available. */ +#define ADAT_USER_DATA_MASK 0x0f +/* The data bits are not available. */ +#define ADAT_USER_DATA_NO_DATA 0x10 + +#endif diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index 3591aeb..1da1dde 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -26,212 +26,7 @@ #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 CLOCK_RATE_SHIFT 8 -#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 +#include "dice-interface.h"
struct dice { @@ -479,7 +274,7 @@ static int dice_enable_set(struct dice *dice) __be32 value; int rcode, err, errors = 0;
- value = cpu_to_be32(ENABLE); + value = cpu_to_be32(1); for (;;) { rcode = fw_run_transaction(device->card, TCODE_WRITE_QUADLET_REQUEST,
DICE devices do not have a unique specifier ID in their unit directory (it's always the same as the device vendor's ID), so rely on just the version ID for driver loading, and use a heuristic in the probe callback to detect actual DICE devices.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice.c | 102 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 84 insertions(+), 18 deletions(-)
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index 1da1dde..b4827ff 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -827,28 +827,93 @@ static void dice_card_free(struct snd_card *card) mutex_destroy(&dice->mutex); }
+#define DICE_CATEGORY_ID 0x04 + +static int dice_interface_check(struct fw_unit *unit) +{ + static const int min_values[10] = { + 10, 0x64 / 4, + 10, 0x18 / 4, + 10, 0x18 / 4, + 0, 0, + 0, 0, + }; + struct fw_device *device = fw_parent_device(unit); + struct fw_csr_iterator it; + int key, value, vendor = -1, model = -1, err; + unsigned int i; + __be32 pointers[ARRAY_SIZE(min_values)]; + __be32 version; + + /* + * Check that GUID and unit directory are constructed according to DICE + * rules, i.e., that the specifier ID is the GUID's OUI, and that the + * GUID chip ID consists of the 8-bit DICE category ID, the 10-bit + * product ID, and a 22-bit serial number. + */ + fw_csr_iterator_init(&it, unit->directory); + while (fw_csr_iterator_next(&it, &key, &value)) { + switch (key) { + case CSR_SPECIFIER_ID: + vendor = value; + break; + case CSR_MODEL: + model = value; + break; + } + } + if (device->config_rom[3] != ((vendor << 8) | DICE_CATEGORY_ID) || + device->config_rom[4] >> 22 != model) + return -ENODEV; + + /* + * Check that the sub address spaces exist and are located inside the + * private address space. The minimum values are chosen so that all + * minimally required registers are included. + */ + err = snd_fw_transaction(unit, TCODE_READ_BLOCK_REQUEST, + DICE_PRIVATE_SPACE, + pointers, sizeof(pointers)); + if (err < 0) + return -ENODEV; + for (i = 0; i < ARRAY_SIZE(pointers); ++i) { + value = be32_to_cpu(pointers[i]); + if (value < min_values[i] || value >= 0x40000) + return -ENODEV; + } + + /* + * Check that the implemented DICE driver specification major version + * number matches. + */ + err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, + DICE_PRIVATE_SPACE + + be32_to_cpu(pointers[0]) * 4 + GLOBAL_VERSION, + &version, 4); + if (err < 0) + return -ENODEV; + if ((version & cpu_to_be32(0xff000000)) != cpu_to_be32(0x01000000)) { + dev_err(&unit->device, + "unknown DICE version: 0x%08x\n", be32_to_cpu(version)); + return -ENODEV; + } + + return 0; +} + 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); + DICE_PRIVATE_SPACE, + pointers, sizeof(pointers)); 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; } @@ -881,8 +946,8 @@ static void dice_card_strings(struct dice *dice) 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], + "%s %s (serial %u) at %s, S%d", + vendor, model, dev->config_rom[4] & 0x3fffff, dev_name(&dice->unit->device), 100 << dev->max_speed);
strcpy(card->mixername, "DICE"); @@ -895,6 +960,10 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) __be32 clock_sel; int err;
+ err = dice_interface_check(unit); + if (err < 0) + return err; + err = snd_card_create(-1, NULL, THIS_MODULE, sizeof(*dice), &card); if (err < 0) return err; @@ -1020,15 +1089,12 @@ static void dice_bus_reset(struct fw_unit *unit) 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, + .match_flags = IEEE1394_MATCH_VERSION, + .version = DICE_INTERFACE, }, { } };
Change the AMDTP streaming code to handle the non-standard stream format that DICE devices use at sample rates greater than 96 kHz.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/amdtp.c | 155 +++++++++++++++++++++++++++++++++++++-------- sound/firewire/amdtp.h | 41 ++++-------- sound/firewire/dice.c | 29 ++++++-- sound/firewire/speakers.c | 6 +- 4 files changed, 164 insertions(+), 67 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index d56b8e7..a09c3b3 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -65,42 +65,66 @@ void amdtp_out_stream_destroy(struct amdtp_out_stream *s) } EXPORT_SYMBOL(amdtp_out_stream_destroy);
+unsigned int amdtp_syt_intervals[CIP_SFC_COUNT] = { + [CIP_SFC_32000] = 8, + [CIP_SFC_44100] = 8, + [CIP_SFC_48000] = 8, + [CIP_SFC_88200] = 16, + [CIP_SFC_96000] = 16, + [CIP_SFC_176400] = 32, + [CIP_SFC_192000] = 32, +}; +EXPORT_SYMBOL(amdtp_syt_intervals); + /** - * amdtp_out_stream_set_rate - set the sample rate + * amdtp_out_stream_set_parameters - set stream parameters * @s: the AMDTP output stream to configure * @rate: the sample rate + * @pcm_channels: the number of PCM samples in each data block, to be encoded + * as AM824 multi-bit linear audio + * @midi_ports: the number of MIDI ports (i.e., MPX-MIDI Data Channels) * - * The sample rate must be set before the stream is started, and must not be + * The parameters must be set before the stream is started, and must not be * changed while the stream is running. */ -void amdtp_out_stream_set_rate(struct amdtp_out_stream *s, unsigned int rate) +void amdtp_out_stream_set_parameters(struct amdtp_out_stream *s, + unsigned int rate, + unsigned int pcm_channels, + unsigned int midi_ports) { - static const struct { - unsigned int rate; - unsigned int syt_interval; - } rate_info[] = { - [CIP_SFC_32000] = { 32000, 8, }, - [CIP_SFC_44100] = { 44100, 8, }, - [CIP_SFC_48000] = { 48000, 8, }, - [CIP_SFC_88200] = { 88200, 16, }, - [CIP_SFC_96000] = { 96000, 16, }, - [CIP_SFC_176400] = { 176400, 32, }, - [CIP_SFC_192000] = { 192000, 32, }, + static const unsigned int rates[] = { + [CIP_SFC_32000] = 32000, + [CIP_SFC_44100] = 44100, + [CIP_SFC_48000] = 48000, + [CIP_SFC_88200] = 88200, + [CIP_SFC_96000] = 96000, + [CIP_SFC_176400] = 176400, + [CIP_SFC_192000] = 192000, }; unsigned int sfc;
if (WARN_ON(amdtp_out_stream_running(s))) return;
- for (sfc = 0; sfc < ARRAY_SIZE(rate_info); ++sfc) - if (rate_info[sfc].rate == rate) + for (sfc = 0; sfc < CIP_SFC_COUNT; ++sfc) + if (rates[sfc] == rate) goto sfc_found; WARN_ON(1); return;
sfc_found: + s->dual_wire = (s->flags & CIP_HI_DUALWIRE) && sfc > CIP_SFC_96000; + if (s->dual_wire) { + sfc -= 2; + rate /= 2; + pcm_channels *= 2; + } s->sfc = sfc; - s->syt_interval = rate_info[sfc].syt_interval; + s->data_block_quadlets = pcm_channels + DIV_ROUND_UP(midi_ports, 8); + s->pcm_channels = pcm_channels; + s->midi_ports = midi_ports; + + s->syt_interval = amdtp_syt_intervals[sfc];
/* default buffering in the device */ s->transfer_delay = TRANSFER_DELAY_TICKS - TICKS_PER_CYCLE; @@ -108,21 +132,17 @@ sfc_found: /* additional buffering needed to adjust for no-data packets */ s->transfer_delay += TICKS_PER_SECOND * s->syt_interval / rate; } -EXPORT_SYMBOL(amdtp_out_stream_set_rate); +EXPORT_SYMBOL(amdtp_out_stream_set_parameters);
/** * amdtp_out_stream_get_max_payload - get the stream's packet size * @s: the AMDTP output stream * * This function must not be called before the stream has been configured - * with amdtp_out_stream_set_rate(), amdtp_out_stream_set_pcm(), and - * amdtp_out_stream_set_midi(). + * with amdtp_out_stream_set_parameters(). */ unsigned int amdtp_out_stream_get_max_payload(struct amdtp_out_stream *s) { - s->data_block_quadlets = s->pcm_channels; - s->data_block_quadlets += DIV_ROUND_UP(s->midi_ports, 8); - return 8 + s->syt_interval * s->data_block_quadlets * 4; } EXPORT_SYMBOL(amdtp_out_stream_get_max_payload); @@ -133,14 +153,21 @@ static void amdtp_write_s16(struct amdtp_out_stream *s, static void amdtp_write_s32(struct amdtp_out_stream *s, struct snd_pcm_substream *pcm, __be32 *buffer, unsigned int frames); +static void amdtp_write_s16_dualwire(struct amdtp_out_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames); +static void amdtp_write_s32_dualwire(struct amdtp_out_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames);
/** * amdtp_out_stream_set_pcm_format - set the PCM format * @s: the AMDTP output stream to configure * @format: the format of the ALSA PCM device * - * The sample format must be set before the stream is started, and must not be - * changed while the stream is running. + * The sample format must be set after the other paramters (rate/PCM channels/ + * MIDI) and before the stream is started, and must not be changed while the + * stream is running. */ void amdtp_out_stream_set_pcm_format(struct amdtp_out_stream *s, snd_pcm_format_t format) @@ -153,10 +180,16 @@ void amdtp_out_stream_set_pcm_format(struct amdtp_out_stream *s, WARN_ON(1); /* fall through */ case SNDRV_PCM_FORMAT_S16: - s->transfer_samples = amdtp_write_s16; + if (s->dual_wire) + s->transfer_samples = amdtp_write_s16_dualwire; + else + s->transfer_samples = amdtp_write_s16; break; case SNDRV_PCM_FORMAT_S32: - s->transfer_samples = amdtp_write_s32; + if (s->dual_wire) + s->transfer_samples = amdtp_write_s32_dualwire; + else + s->transfer_samples = amdtp_write_s32; break; } } @@ -305,6 +338,68 @@ static void amdtp_write_s16(struct amdtp_out_stream *s, } }
+static void amdtp_write_s32_dualwire(struct amdtp_out_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames) +{ + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, frame_adjust_1, frame_adjust_2, i, c; + const u32 *src; + + channels = s->pcm_channels; + src = (void *)runtime->dma_area + + s->pcm_buffer_pointer * (runtime->frame_bits / 8); + frame_adjust_1 = channels - 1; + frame_adjust_2 = 1 - (s->data_block_quadlets - channels); + + channels /= 2; + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + *buffer = cpu_to_be32((*src >> 8) | 0x40000000); + src++; + buffer += 2; + } + buffer -= frame_adjust_1; + for (c = 0; c < channels; ++c) { + *buffer = cpu_to_be32((*src >> 8) | 0x40000000); + src++; + buffer += 2; + } + buffer -= frame_adjust_2; + } +} + +static void amdtp_write_s16_dualwire(struct amdtp_out_stream *s, + struct snd_pcm_substream *pcm, + __be32 *buffer, unsigned int frames) +{ + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, frame_adjust_1, frame_adjust_2, i, c; + const u16 *src; + + channels = s->pcm_channels; + src = (void *)runtime->dma_area + + s->pcm_buffer_pointer * (runtime->frame_bits / 8); + frame_adjust_1 = channels - 1; + frame_adjust_2 = 1 - (s->data_block_quadlets - channels); + + channels /= 2; + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + *buffer = cpu_to_be32((*src << 8) | 0x40000000); + src++; + buffer += 2; + } + buffer -= frame_adjust_1; + for (c = 0; c < channels; ++c) { + *buffer = cpu_to_be32((*src << 8) | 0x40000000); + src++; + buffer += 2; + } + buffer -= frame_adjust_2; + } +} + static void amdtp_fill_pcm_silence(struct amdtp_out_stream *s, __be32 *buffer, unsigned int frames) { @@ -390,6 +485,9 @@ static void queue_out_packet(struct amdtp_out_stream *s, unsigned int cycle) s->packet_index = index;
if (pcm) { + if (s->dual_wire) + data_blocks *= 2; + ptr = s->pcm_buffer_pointer + data_blocks; if (ptr >= pcm->runtime->buffer_size) ptr -= pcm->runtime->buffer_size; @@ -459,8 +557,7 @@ static int queue_initial_skip_packets(struct amdtp_out_stream *s) * @speed: firewire speed code * * The stream cannot be started until it has been configured with - * amdtp_out_stream_set_rate(), amdtp_out_stream_set_pcm(), - * amdtp_out_stream_set_midi(), and amdtp_out_stream_set_format(); + * amdtp_out_stream_set_parameters() and amdtp_out_stream_set_pcm_format(), * and it must be started before any PCM or MIDI device can be started. */ int amdtp_out_stream_start(struct amdtp_out_stream *s, int channel, int speed) diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index 28b1bf5..f3d03dd 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -15,10 +15,15 @@ * @CIP_BLOCKING: In blocking mode, each packet contains either zero or * SYT_INTERVAL samples, with these two types alternating so that * the overall sample rate comes out right. + * @CIP_HI_DUALWIRE: At rates above 96 kHz, pretend that the stream runs + * at half the actual sample rate with twice the number of channels; + * two samples of a channel are stored consecutively in the packet. + * Requires blocking mode and SYT_INTERVAL-aligned PCM buffer size. */ enum cip_out_flags { CIP_NONBLOCKING = 0x00, CIP_BLOCKING = 0x01, + CIP_HI_DUALWIRE = 0x02, };
/** @@ -32,6 +37,7 @@ enum cip_sfc { CIP_SFC_96000 = 4, CIP_SFC_176400 = 5, CIP_SFC_192000 = 6, + CIP_SFC_COUNT };
#define AMDTP_OUT_PCM_FORMAT_BITS (SNDRV_PCM_FMTBIT_S16 | \ @@ -48,6 +54,7 @@ struct amdtp_out_stream { struct mutex mutex;
enum cip_sfc sfc; + bool dual_wire; unsigned int data_block_quadlets; unsigned int pcm_channels; unsigned int midi_ports; @@ -80,7 +87,10 @@ int amdtp_out_stream_init(struct amdtp_out_stream *s, struct fw_unit *unit, enum cip_out_flags flags); void amdtp_out_stream_destroy(struct amdtp_out_stream *s);
-void amdtp_out_stream_set_rate(struct amdtp_out_stream *s, unsigned int rate); +void amdtp_out_stream_set_parameters(struct amdtp_out_stream *s, + unsigned int rate, + unsigned int pcm_channels, + unsigned int midi_ports); unsigned int amdtp_out_stream_get_max_payload(struct amdtp_out_stream *s);
int amdtp_out_stream_start(struct amdtp_out_stream *s, int channel, int speed); @@ -93,39 +103,14 @@ void amdtp_out_stream_pcm_prepare(struct amdtp_out_stream *s); unsigned long amdtp_out_stream_pcm_pointer(struct amdtp_out_stream *s); void amdtp_out_stream_pcm_abort(struct amdtp_out_stream *s);
+extern unsigned int amdtp_syt_intervals[CIP_SFC_COUNT]; + static inline bool amdtp_out_stream_running(struct amdtp_out_stream *s) { return !IS_ERR(s->context); }
/** - * amdtp_out_stream_set_pcm - configure format of PCM samples - * @s: the AMDTP output stream to be configured - * @pcm_channels: the number of PCM samples in each data block, to be encoded - * as AM824 multi-bit linear audio - * - * This function must not be called while the stream is running. - */ -static inline void amdtp_out_stream_set_pcm(struct amdtp_out_stream *s, - unsigned int pcm_channels) -{ - s->pcm_channels = pcm_channels; -} - -/** - * amdtp_out_stream_set_midi - configure format of MIDI data - * @s: the AMDTP output stream to be configured - * @midi_ports: the number of MIDI ports (i.e., MPX-MIDI Data Channels) - * - * This function must not be called while the stream is running. - */ -static inline void amdtp_out_stream_set_midi(struct amdtp_out_stream *s, - unsigned int midi_ports) -{ - s->midi_ports = midi_ports; -} - -/** * amdtp_out_streaming_error - check for streaming error * @s: the AMDTP output stream * diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index b4827ff..8804e42 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -375,7 +375,7 @@ static int dice_open(struct snd_pcm_substream *substream) struct dice *dice = substream->private_data; struct snd_pcm_runtime *runtime = substream->runtime; __be32 clock_sel, number_audio, number_midi; - unsigned int rate; + unsigned int rate_index, rate; int err;
err = dice_try_lock(dice); @@ -387,12 +387,13 @@ static int dice_open(struct snd_pcm_substream *substream) &clock_sel, 4); if (err < 0) goto err_lock; - rate = (be32_to_cpu(clock_sel) & CLOCK_RATE_MASK) >> CLOCK_RATE_SHIFT; - if (rate >= ARRAY_SIZE(dice_rates)) { + rate_index = (be32_to_cpu(clock_sel) & CLOCK_RATE_MASK) + >> CLOCK_RATE_SHIFT; + if (rate_index >= ARRAY_SIZE(dice_rates)) { err = -ENXIO; goto err_lock; } - rate = dice_rates[rate]; + rate = dice_rates[rate_index];
err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, rx_address(dice, RX_NUMBER_AUDIO), @@ -413,9 +414,20 @@ static int dice_open(struct snd_pcm_substream *substream) 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, rate); - amdtp_out_stream_set_pcm(&dice->stream, be32_to_cpu(number_audio)); - amdtp_out_stream_set_midi(&dice->stream, be32_to_cpu(number_midi)); + amdtp_out_stream_set_parameters(&dice->stream, rate, + be32_to_cpu(number_audio), + be32_to_cpu(number_midi)); + + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, + amdtp_syt_intervals[rate_index]); + if (err < 0) + goto err_lock; + err = snd_pcm_hw_constraint_step(runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, + amdtp_syt_intervals[rate_index]); + if (err < 0) + goto err_lock;
err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, @@ -993,7 +1005,8 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) goto err_notification_handler; dice->resources.channels_mask = 0x00000000ffffffffuLL;
- err = amdtp_out_stream_init(&dice->stream, unit, CIP_BLOCKING); + err = amdtp_out_stream_init(&dice->stream, unit, + CIP_BLOCKING | CIP_HI_DUALWIRE); if (err < 0) goto err_resources;
diff --git a/sound/firewire/speakers.c b/sound/firewire/speakers.c index 0ac5630..6a68caf 100644 --- a/sound/firewire/speakers.c +++ b/sound/firewire/speakers.c @@ -245,8 +245,10 @@ static int fwspk_hw_params(struct snd_pcm_substream *substream, if (err < 0) goto error;
- amdtp_out_stream_set_rate(&fwspk->stream, params_rate(hw_params)); - amdtp_out_stream_set_pcm(&fwspk->stream, params_channels(hw_params)); + amdtp_out_stream_set_parameters(&fwspk->stream, + params_rate(hw_params), + params_channels(hw_params), + 0);
amdtp_out_stream_set_pcm_format(&fwspk->stream, params_format(hw_params));
Instead of reading two consecutive register with two quadlet requests, use one block read request.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice.c | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-)
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index 8804e42..e1d8dff 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -374,8 +374,8 @@ static int dice_open(struct snd_pcm_substream *substream) }; struct dice *dice = substream->private_data; struct snd_pcm_runtime *runtime = substream->runtime; - __be32 clock_sel, number_audio, number_midi; - unsigned int rate_index, rate; + __be32 clock_sel, data[2]; + unsigned int rate_index, number_audio, number_midi; int err;
err = dice_try_lock(dice); @@ -393,30 +393,25 @@ static int dice_open(struct snd_pcm_substream *substream) err = -ENXIO; goto err_lock; } - rate = dice_rates[rate_index];
- err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, + err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, rx_address(dice, RX_NUMBER_AUDIO), - &number_audio, 4); - if (err < 0) - goto err_lock; - err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, - rx_address(dice, RX_NUMBER_MIDI), - &number_midi, 4); + data, 2 * 4); if (err < 0) goto err_lock; + number_audio = be32_to_cpu(data[0]); + number_midi = be32_to_cpu(data[1]);
runtime->hw = hardware;
- runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate); + runtime->hw.rates = snd_pcm_rate_to_rate_bit(dice_rates[rate_index]); snd_pcm_limit_hw_rates(runtime);
- runtime->hw.channels_min = be32_to_cpu(number_audio); - runtime->hw.channels_max = be32_to_cpu(number_audio); + runtime->hw.channels_min = number_audio; + runtime->hw.channels_max = number_audio;
- amdtp_out_stream_set_parameters(&dice->stream, rate, - be32_to_cpu(number_audio), - be32_to_cpu(number_midi)); + amdtp_out_stream_set_parameters(&dice->stream, dice_rates[rate_index], + number_audio, number_midi);
err = snd_pcm_hw_constraint_step(runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
Add a flag to snd_fw_transaction() to allow it to abort when a bus reset happens. This removes most of the duplicated error handling loops that were required around calls to the low-level fw_run_transaction().
Also add a flag to suppress error messages; errors are expected when we attempt to clean up after the device was unplugged.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/cmp.c | 50 +++++------ sound/firewire/dice.c | 207 ++++++++++++++------------------------------- sound/firewire/fcp.c | 2 sound/firewire/isight.c | 43 ++++----- sound/firewire/lib.c | 24 ++++- sound/firewire/lib.h | 7 +- sound/firewire/scs1x.c | 8 +- sound/firewire/speakers.c | 2 8 files changed, 137 insertions(+), 206 deletions(-)
diff --git a/sound/firewire/cmp.c b/sound/firewire/cmp.c index 645cb0b..efdbf58 100644 --- a/sound/firewire/cmp.c +++ b/sound/firewire/cmp.c @@ -48,9 +48,6 @@ static int pcr_modify(struct cmp_connection *c, int (*check)(struct cmp_connection *c, __be32 pcr), enum bus_reset_handling bus_reset_handling) { - struct fw_device *device = fw_parent_device(c->resources.unit); - int generation = c->resources.generation; - int rcode, errors = 0; __be32 old_arg, buffer[2]; int err;
@@ -59,36 +56,31 @@ static int pcr_modify(struct cmp_connection *c, old_arg = buffer[0]; buffer[1] = modify(c, buffer[0]);
- rcode = fw_run_transaction( - device->card, TCODE_LOCK_COMPARE_SWAP, - device->node_id, generation, device->max_speed, + err = snd_fw_transaction( + c->resources.unit, TCODE_LOCK_COMPARE_SWAP, CSR_REGISTER_BASE + CSR_IPCR(c->pcr_index), - buffer, 8); - - if (rcode == RCODE_COMPLETE) { - if (buffer[0] == old_arg) /* success? */ - break; - - if (check) { - err = check(c, buffer[0]); - if (err < 0) - return err; - } - } else if (rcode == RCODE_GENERATION) - goto bus_reset; - else if (rcode_is_permanent_error(rcode) || ++errors >= 3) - goto io_error; + buffer, 8, + FW_FIXED_GENERATION | c->resources.generation); + + if (err < 0) { + if (err == -EAGAIN && + bus_reset_handling == SUCCEED_ON_BUS_RESET) + err = 0; + return err; + } + + if (buffer[0] == old_arg) /* success? */ + break; + + if (check) { + err = check(c, buffer[0]); + if (err < 0) + return err; + } } c->last_pcr_value = buffer[1];
return 0; - -io_error: - cmp_error(c, "transaction failed: %s\n", fw_rcode_string(rcode)); - return -EIO; - -bus_reset: - return bus_reset_handling == ABORT_ON_BUS_RESET ? -EAGAIN : 0; }
@@ -108,7 +100,7 @@ int cmp_connection_init(struct cmp_connection *c,
err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, CSR_REGISTER_BASE + CSR_IMPR, - &impr_be, 4); + &impr_be, 4, 0); if (err < 0) return err; impr = be32_to_cpu(impr_be); diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index e1d8dff..59d5ca4 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -118,7 +118,7 @@ static int dice_owner_set(struct dice *dice) { struct fw_device *device = fw_parent_device(dice->unit); __be64 *buffer; - int rcode, err, errors = 0; + int err, errors = 0;
buffer = kmalloc(2 * 8, GFP_KERNEL); if (!buffer) @@ -132,31 +132,24 @@ static int dice_owner_set(struct dice *dice)
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 { + err = snd_fw_transaction(dice->unit, + TCODE_LOCK_COMPARE_SWAP, + global_address(dice, GLOBAL_OWNER), + buffer, 2 * 8, + FW_FIXED_GENERATION | + dice->owner_generation); + + if (err == 0) { + if (buffer[0] != cpu_to_be64(OWNER_NO_OWNER)) { 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; + if (err != -EAGAIN || ++errors >= 3) break; - } + msleep(20); }
@@ -169,7 +162,7 @@ static int dice_owner_update(struct dice *dice) { struct fw_device *device = fw_parent_device(dice->unit); __be64 *buffer; - int rcode, err, errors = 0; + int err;
if (dice->owner_generation == -1) return 0; @@ -178,44 +171,26 @@ static int dice_owner_update(struct dice *dice) 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); + 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) { + dice->owner_generation = device->generation; + smp_rmb(); /* node_id vs. generation */ + err = snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP, + global_address(dice, GLOBAL_OWNER), + buffer, 2 * 8, + FW_FIXED_GENERATION | dice->owner_generation); + + if (err == 0) { + if (buffer[0] != cpu_to_be64(OWNER_NO_OWNER)) { dev_err(&dice->unit->device, - "setting device owner failed: %s\n", - fw_rcode_string(rcode)); - err = -EIO; - break; + "device is already in use\n"); + err = -EBUSY; } - msleep(20); + } else if (err == -EAGAIN) { + err = 0; /* try again later */ }
kfree(buffer); @@ -230,38 +205,19 @@ 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); - } + 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); + snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP, + global_address(dice, GLOBAL_OWNER), + buffer, 2 * 8, FW_QUIET | + FW_FIXED_GENERATION | dice->owner_generation);
kfree(buffer);
@@ -270,67 +226,32 @@ static void dice_owner_clear(struct dice *dice)
static int dice_enable_set(struct dice *dice) { - struct fw_device *device = fw_parent_device(dice->unit); __be32 value; - int rcode, err, errors = 0; + int err;
value = cpu_to_be32(1); - 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); - } + err = snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST, + global_address(dice, GLOBAL_ENABLE), + &value, 4, + FW_FIXED_GENERATION | dice->owner_generation); + if (err < 0) + return err;
- return err; + dice->global_enabled = true; + + return 0; }
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); - } + snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST, + global_address(dice, GLOBAL_ENABLE), + &value, 4, FW_QUIET | + FW_FIXED_GENERATION | dice->owner_generation); + dice->global_enabled = false; }
@@ -384,7 +305,7 @@ static int dice_open(struct snd_pcm_substream *substream)
err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, global_address(dice, GLOBAL_CLOCK_SELECT), - &clock_sel, 4); + &clock_sel, 4, 0); if (err < 0) goto err_lock; rate_index = (be32_to_cpu(clock_sel) & CLOCK_RATE_MASK) @@ -396,7 +317,7 @@ static int dice_open(struct snd_pcm_substream *substream)
err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, rx_address(dice, RX_NUMBER_AUDIO), - data, 2 * 4); + data, 2 * 4, 0); if (err < 0) goto err_lock; number_audio = be32_to_cpu(data[0]); @@ -488,7 +409,7 @@ static int dice_stream_start(struct dice *dice) err = snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST, rx_address(dice, RX_ISOCHRONOUS), - &channel, 4); + &channel, 4, 0); if (err < 0) goto err_resources; } @@ -502,7 +423,7 @@ static int dice_stream_start(struct dice *dice) err_rx_channel: channel = cpu_to_be32((u32)-1); snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST, - rx_address(dice, RX_ISOCHRONOUS), &channel, 4); + rx_address(dice, RX_ISOCHRONOUS), &channel, 4, 0); err_resources: fw_iso_resources_free(&dice->resources); error: @@ -528,7 +449,7 @@ static void dice_stream_stop(struct dice *dice)
channel = cpu_to_be32((u32)-1); snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST, - rx_address(dice, RX_ISOCHRONOUS), &channel, 4); + rx_address(dice, RX_ISOCHRONOUS), &channel, 4, 0);
fw_iso_resources_free(&dice->resources); } @@ -880,7 +801,7 @@ static int dice_interface_check(struct fw_unit *unit) */ err = snd_fw_transaction(unit, TCODE_READ_BLOCK_REQUEST, DICE_PRIVATE_SPACE, - pointers, sizeof(pointers)); + pointers, sizeof(pointers), 0); if (err < 0) return -ENODEV; for (i = 0; i < ARRAY_SIZE(pointers); ++i) { @@ -896,7 +817,7 @@ static int dice_interface_check(struct fw_unit *unit) err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, DICE_PRIVATE_SPACE + be32_to_cpu(pointers[0]) * 4 + GLOBAL_VERSION, - &version, 4); + &version, 4, 0); if (err < 0) return -ENODEV; if ((version & cpu_to_be32(0xff000000)) != cpu_to_be32(0x01000000)) { @@ -915,7 +836,7 @@ static int dice_init_offsets(struct dice *dice)
err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, DICE_PRIVATE_SPACE, - pointers, sizeof(pointers)); + pointers, sizeof(pointers), 0); if (err < 0) return err;
@@ -939,7 +860,7 @@ static void dice_card_strings(struct dice *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)); + card->shortname, sizeof(card->shortname), 0); if (err >= 0) { /* DICE strings are returned in "always-wrong" endianness */ BUILD_BUG_ON(sizeof(card->shortname) % 4 != 0); @@ -1015,14 +936,14 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id)
err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, global_address(dice, GLOBAL_CLOCK_SELECT), - &clock_sel, 4); + &clock_sel, 4, 0); if (err < 0) goto error; clock_sel &= cpu_to_be32(~CLOCK_SOURCE_MASK); clock_sel |= cpu_to_be32(CLOCK_SOURCE_ARX1); err = snd_fw_transaction(unit, TCODE_WRITE_QUADLET_REQUEST, global_address(dice, GLOBAL_CLOCK_SELECT), - &clock_sel, 4); + &clock_sel, 4, 0); if (err < 0) goto error;
diff --git a/sound/firewire/fcp.c b/sound/firewire/fcp.c index ec578b5..860c080 100644 --- a/sound/firewire/fcp.c +++ b/sound/firewire/fcp.c @@ -90,7 +90,7 @@ int fcp_avc_transaction(struct fw_unit *unit, : TCODE_WRITE_BLOCK_REQUEST; ret = snd_fw_transaction(t.unit, tcode, CSR_REGISTER_BASE + CSR_FCP_COMMAND, - (void *)command, command_size); + (void *)command, command_size, 0); if (ret < 0) break;
diff --git a/sound/firewire/isight.c b/sound/firewire/isight.c index 58a5afe..fd42e6b 100644 --- a/sound/firewire/isight.c +++ b/sound/firewire/isight.c @@ -217,7 +217,7 @@ static void isight_packet(struct fw_iso_context *context, u32 cycle,
static int isight_connect(struct isight *isight) { - int ch, err, rcode, errors = 0; + int ch, err; __be32 value;
retry_after_bus_reset: @@ -230,27 +230,19 @@ retry_after_bus_reset: }
value = cpu_to_be32(ch | (isight->device->max_speed << SPEED_SHIFT)); - for (;;) { - rcode = fw_run_transaction( - isight->device->card, - TCODE_WRITE_QUADLET_REQUEST, - isight->device->node_id, - isight->resources.generation, - isight->device->max_speed, - isight->audio_base + REG_ISO_TX_CONFIG, - &value, 4); - if (rcode == RCODE_COMPLETE) { - return 0; - } else if (rcode == RCODE_GENERATION) { - fw_iso_resources_free(&isight->resources); - goto retry_after_bus_reset; - } else if (rcode_is_permanent_error(rcode) || ++errors >= 3) { - err = -EIO; - goto err_resources; - } - msleep(5); + err = snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, + isight->audio_base + REG_ISO_TX_CONFIG, + &value, 4, FW_FIXED_GENERATION | + isight->resources.generation); + if (err == -EAGAIN) { + fw_iso_resources_free(&isight->resources); + goto retry_after_bus_reset; + } else if (err < 0) { + goto err_resources; }
+ return 0; + err_resources: fw_iso_resources_free(&isight->resources); error: @@ -315,17 +307,19 @@ static int isight_hw_params(struct snd_pcm_substream *substream, static int reg_read(struct isight *isight, int offset, __be32 *value) { return snd_fw_transaction(isight->unit, TCODE_READ_QUADLET_REQUEST, - isight->audio_base + offset, value, 4); + isight->audio_base + offset, value, 4, 0); }
static int reg_write(struct isight *isight, int offset, __be32 value) { return snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, - isight->audio_base + offset, &value, 4); + isight->audio_base + offset, &value, 4, 0); }
static void isight_stop_streaming(struct isight *isight) { + __be32 value; + if (!isight->context) return;
@@ -333,7 +327,10 @@ static void isight_stop_streaming(struct isight *isight) fw_iso_context_destroy(isight->context); isight->context = NULL; fw_iso_resources_free(&isight->resources); - reg_write(isight, REG_AUDIO_ENABLE, 0); + value = 0; + snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, + isight->audio_base + REG_AUDIO_ENABLE, + &value, 4, FW_QUIET); }
static int isight_hw_free(struct snd_pcm_substream *substream) diff --git a/sound/firewire/lib.c b/sound/firewire/lib.c index 14eb414..7409edb 100644 --- a/sound/firewire/lib.c +++ b/sound/firewire/lib.c @@ -11,7 +11,7 @@ #include <linux/module.h> #include "lib.h"
-#define ERROR_RETRY_DELAY_MS 5 +#define ERROR_RETRY_DELAY_MS 20
/** * snd_fw_transaction - send a request and wait for its completion @@ -20,6 +20,9 @@ * @offset: the address in the target's address space * @buffer: input/output data * @length: length of @buffer + * @flags: use %FW_FIXED_GENERATION and add the generation value to attempt the + * request only in that generation; use %FW_QUIET to suppress error + * messages * * Submits an asynchronous request to the target device, and waits for the * response. The node ID and the current generation are derived from @unit. @@ -27,14 +30,18 @@ * Returns zero on success, or a negative error code. */ int snd_fw_transaction(struct fw_unit *unit, int tcode, - u64 offset, void *buffer, size_t length) + u64 offset, void *buffer, size_t length, + unsigned int flags) { struct fw_device *device = fw_parent_device(unit); int generation, rcode, tries = 0;
+ generation = flags & FW_GENERATION_MASK; for (;;) { - generation = device->generation; - smp_rmb(); /* node_id vs. generation */ + if (!(flags & FW_FIXED_GENERATION)) { + generation = device->generation; + smp_rmb(); /* node_id vs. generation */ + } rcode = fw_run_transaction(device->card, tcode, device->node_id, generation, device->max_speed, offset, @@ -43,9 +50,14 @@ int snd_fw_transaction(struct fw_unit *unit, int tcode, if (rcode == RCODE_COMPLETE) return 0;
+ if (rcode == RCODE_GENERATION && (flags & FW_FIXED_GENERATION)) + return -EAGAIN; + if (rcode_is_permanent_error(rcode) || ++tries >= 3) { - dev_err(&unit->device, "transaction failed: %s\n", - fw_rcode_string(rcode)); + if (!(flags & FW_QUIET)) + dev_err(&unit->device, + "transaction failed: %s\n", + fw_rcode_string(rcode)); return -EIO; }
diff --git a/sound/firewire/lib.h b/sound/firewire/lib.h index aef3014..02cfabc 100644 --- a/sound/firewire/lib.h +++ b/sound/firewire/lib.h @@ -6,8 +6,13 @@
struct fw_unit;
+#define FW_GENERATION_MASK 0x00ff +#define FW_FIXED_GENERATION 0x0100 +#define FW_QUIET 0x0200 + int snd_fw_transaction(struct fw_unit *unit, int tcode, - u64 offset, void *buffer, size_t length); + u64 offset, void *buffer, size_t length, + unsigned int flags);
/* returns true if retrying the transaction would not make sense */ static inline bool rcode_is_permanent_error(int rcode) diff --git a/sound/firewire/scs1x.c b/sound/firewire/scs1x.c index 505fc81..858023c 100644 --- a/sound/firewire/scs1x.c +++ b/sound/firewire/scs1x.c @@ -369,7 +369,7 @@ static int scs_init_hss_address(struct scs *scs) data = cpu_to_be64(((u64)HSS1394_TAG_CHANGE_ADDRESS << 56) | scs->hss_handler.offset); err = snd_fw_transaction(scs->unit, TCODE_WRITE_BLOCK_REQUEST, - HSS1394_ADDRESS, &data, 8); + HSS1394_ADDRESS, &data, 8, 0); if (err < 0) dev_err(&scs->unit->device, "HSS1394 communication failed\n");
@@ -455,12 +455,16 @@ err_card: static void scs_update(struct fw_unit *unit) { struct scs *scs = dev_get_drvdata(&unit->device); + int generation; __be64 data;
data = cpu_to_be64(((u64)HSS1394_TAG_CHANGE_ADDRESS << 56) | scs->hss_handler.offset); + generation = fw_parent_device(unit)->generation; + smp_rmb(); /* node_id vs. generation */ snd_fw_transaction(scs->unit, TCODE_WRITE_BLOCK_REQUEST, - HSS1394_ADDRESS, &data, 8); + HSS1394_ADDRESS, &data, 8, + FW_FIXED_GENERATION | generation); }
static void scs_remove(struct fw_unit *unit) diff --git a/sound/firewire/speakers.c b/sound/firewire/speakers.c index 6a68caf..eb3f7dc 100644 --- a/sound/firewire/speakers.c +++ b/sound/firewire/speakers.c @@ -647,7 +647,7 @@ static u32 fwspk_read_firmware_version(struct fw_unit *unit) int err;
err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, - OXFORD_FIRMWARE_ID_ADDRESS, &data, 4); + OXFORD_FIRMWARE_ID_ADDRESS, &data, 4, 0); return err >= 0 ? be32_to_cpu(data) : 0; }
When a bus reset happens, the enable register is automatically cleared, so we do not need to clear it manually when stopping the stream.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice.c | 5 +++++ 1 file changed, 5 insertions(+)
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index 59d5ca4..cfa98a8 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -246,6 +246,9 @@ static void dice_enable_clear(struct dice *dice) { __be32 value;
+ if (!dice->global_enabled) + return; + value = 0; snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST, global_address(dice, GLOBAL_ENABLE), @@ -1009,6 +1012,8 @@ static void dice_bus_reset(struct fw_unit *unit) * manner. */ amdtp_out_stream_pcm_abort(&dice->stream); + + dice->global_enabled = false; dice_stream_stop_packets(dice);
dice_owner_update(dice);
Since commit f2b3614cefb6 (Don't check DMA time-out too shortly), we need no longer to restrict the period length to less than 10 s.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index cfa98a8..57ceb13 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -350,7 +350,7 @@ static int dice_open(struct snd_pcm_substream *substream)
err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, - 5000, 8192000); + 5000, UINT_MAX); if (err < 0) goto err_lock;
The pcm field was not actually used.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index 57ceb13..2d198ae 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -43,7 +43,6 @@ struct dice { bool global_enabled; wait_queue_head_t hwdep_wait; u32 notification_bits; - struct snd_pcm_substream *pcm; struct fw_iso_resources resources; struct amdtp_out_stream stream; }; @@ -564,8 +563,7 @@ static int dice_create_pcm(struct dice *dice) 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; + pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream->ops = &ops;
return 0; }
From: Stefan Richter stefanr@s5r6.in-berlin.de
Avoid a lock inversion between dice->mutex and pcm->open_mutex.
Signed-off-by: Stefan Richter stefanr@s5r6.in-berlin.de Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index 2d198ae..2d3a04e 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -981,12 +981,12 @@ static void dice_remove(struct fw_unit *unit) { struct dice *dice = dev_get_drvdata(&unit->device);
- mutex_lock(&dice->mutex); - amdtp_out_stream_pcm_abort(&dice->stream);
snd_card_disconnect(dice->card);
+ mutex_lock(&dice->mutex); + dice_stream_stop(dice); dice_owner_clear(dice);
@@ -999,8 +999,6 @@ static void dice_bus_reset(struct fw_unit *unit) { struct dice *dice = dev_get_drvdata(&unit->device);
- mutex_lock(&dice->mutex); - /* * On a bus reset, the DICE firmware disables streaming and then goes * off contemplating its own navel for hundreds of milliseconds before @@ -1011,6 +1009,8 @@ static void dice_bus_reset(struct fw_unit *unit) */ amdtp_out_stream_pcm_abort(&dice->stream);
+ mutex_lock(&dice->mutex); + dice->global_enabled = false; dice_stream_stop_packets(dice);
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/amdtp.c | 2 +- sound/firewire/amdtp.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/sound/firewire/amdtp.c b/sound/firewire/amdtp.c index a09c3b3..5540f70 100644 --- a/sound/firewire/amdtp.c +++ b/sound/firewire/amdtp.c @@ -65,7 +65,7 @@ void amdtp_out_stream_destroy(struct amdtp_out_stream *s) } EXPORT_SYMBOL(amdtp_out_stream_destroy);
-unsigned int amdtp_syt_intervals[CIP_SFC_COUNT] = { +const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT] = { [CIP_SFC_32000] = 8, [CIP_SFC_44100] = 8, [CIP_SFC_48000] = 8, diff --git a/sound/firewire/amdtp.h b/sound/firewire/amdtp.h index f3d03dd..839ebf8 100644 --- a/sound/firewire/amdtp.h +++ b/sound/firewire/amdtp.h @@ -103,7 +103,7 @@ void amdtp_out_stream_pcm_prepare(struct amdtp_out_stream *s); unsigned long amdtp_out_stream_pcm_pointer(struct amdtp_out_stream *s); void amdtp_out_stream_pcm_abort(struct amdtp_out_stream *s);
-extern unsigned int amdtp_syt_intervals[CIP_SFC_COUNT]; +extern const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT];
static inline bool amdtp_out_stream_running(struct amdtp_out_stream *s) {
In preparation for sample rate selection support, ensure that the driver knows about the device's clock capabilities.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-)
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index 2d3a04e..06fef47 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -36,6 +36,7 @@ struct dice { struct mutex mutex; unsigned int global_offset; unsigned int rx_offset; + unsigned int clock_caps; struct fw_address_handler notification_handler; int owner_generation; int dev_lock_count; /* > 0 driver, < 0 userspace */ @@ -830,9 +831,10 @@ static int dice_interface_check(struct fw_unit *unit) return 0; }
-static int dice_init_offsets(struct dice *dice) +static int dice_read_params(struct dice *dice) { __be32 pointers[6]; + __be32 value; int err;
err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, @@ -844,6 +846,23 @@ static int dice_init_offsets(struct dice *dice) dice->global_offset = be32_to_cpu(pointers[0]) * 4; dice->rx_offset = be32_to_cpu(pointers[4]) * 4;
+ /* some very old firmwares don't tell about their clock support */ + if (be32_to_cpu(pointers[1]) * 4 >= GLOBAL_CLOCK_CAPABILITIES + 4) { + err = snd_fw_transaction( + dice->unit, TCODE_READ_QUADLET_REQUEST, + global_address(dice, GLOBAL_CLOCK_CAPABILITIES), + &value, 4, 0); + if (err < 0) + return err; + dice->clock_caps = be32_to_cpu(value); + } else { + /* this should be supported by any device */ + dice->clock_caps = CLOCK_CAP_RATE_44100 | + CLOCK_CAP_RATE_48000 | + CLOCK_CAP_SOURCE_ARX1 | + CLOCK_CAP_SOURCE_INTERNAL; + } + return 0; }
@@ -905,7 +924,7 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) dice->unit = unit; init_waitqueue_head(&dice->hwdep_wait);
- err = dice_init_offsets(dice); + err = dice_read_params(dice); if (err < 0) goto err_mutex;
Reorganize the initialization order so that the driver can receive notifications earlier.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index 06fef47..49b47ba 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -924,10 +924,6 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) dice->unit = unit; init_waitqueue_head(&dice->hwdep_wait);
- err = dice_read_params(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; @@ -936,9 +932,17 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) if (err < 0) goto err_mutex;
- err = fw_iso_resources_init(&dice->resources, unit); + err = dice_owner_set(dice); if (err < 0) goto err_notification_handler; + + err = dice_read_params(dice); + if (err < 0) + goto err_owner; + + err = fw_iso_resources_init(&dice->resources, unit); + if (err < 0) + goto err_owner; dice->resources.channels_mask = 0x00000000ffffffffuLL;
err = amdtp_out_stream_init(&dice->stream, unit, @@ -946,10 +950,6 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) 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); @@ -983,10 +983,10 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id)
return 0;
-err_stream: - amdtp_out_stream_destroy(&dice->stream); err_resources: fw_iso_resources_destroy(&dice->resources); +err_owner: + dice_owner_clear(dice); err_notification_handler: fw_core_remove_address_handler(&dice->notification_handler); err_mutex:
In preparation for sample rate selection support, read the stream parameters that might change when running at different sample rates.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice.c | 92 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 2 deletions(-)
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index 49b47ba..e6bba6d 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -6,10 +6,12 @@ */
#include <linux/compat.h> +#include <linux/completion.h> #include <linux/delay.h> #include <linux/device.h> #include <linux/firewire.h> #include <linux/firewire-constants.h> +#include <linux/jiffies.h> #include <linux/module.h> #include <linux/mod_devicetable.h> #include <linux/mutex.h> @@ -37,11 +39,14 @@ struct dice { unsigned int global_offset; unsigned int rx_offset; unsigned int clock_caps; + unsigned int rx_channels[3]; + unsigned int rx_midi_ports[3]; struct fw_address_handler notification_handler; int owner_generation; int dev_lock_count; /* > 0 driver, < 0 userspace */ bool dev_lock_changed; bool global_enabled; + struct completion clock_accepted; wait_queue_head_t hwdep_wait; u32 notification_bits; struct fw_iso_resources resources; @@ -53,15 +58,23 @@ MODULE_AUTHOR("Clemens Ladisch clemens@ladisch.de"); MODULE_LICENSE("GPL v2");
static const unsigned int dice_rates[] = { + /* mode 0 */ [0] = 32000, [1] = 44100, [2] = 48000, + /* mode 1 */ [3] = 88200, [4] = 96000, + /* mode 2 */ [5] = 176400, [6] = 192000, };
+static unsigned int rate_index_to_mode(unsigned int rate_index) +{ + return ((int)rate_index - 1) / 2; +} + static void dice_lock_changed(struct dice *dice) { dice->dev_lock_changed = true; @@ -264,6 +277,7 @@ static void dice_notification(struct fw_card *card, struct fw_request *request, void *data, size_t length, void *callback_data) { struct dice *dice = callback_data; + u32 bits; unsigned long flags;
if (tcode != TCODE_WRITE_QUADLET_REQUEST) { @@ -274,10 +288,17 @@ static void dice_notification(struct fw_card *card, struct fw_request *request, fw_send_response(card, request, RCODE_ADDRESS_ERROR); return; } + + bits = be32_to_cpup(data); + spin_lock_irqsave(&dice->lock, flags); - dice->notification_bits |= be32_to_cpup(data); + dice->notification_bits |= bits; spin_unlock_irqrestore(&dice->lock, flags); + fw_send_response(card, request, RCODE_COMPLETE); + + if (bits & NOTIFY_CLOCK_ACCEPTED) + complete(&dice->clock_accepted); wake_up(&dice->hwdep_wait); }
@@ -457,6 +478,26 @@ static void dice_stream_stop(struct dice *dice) fw_iso_resources_free(&dice->resources); }
+static int dice_change_rate(struct dice *dice, unsigned int clock_rate) +{ + __be32 value; + int err; + + INIT_COMPLETION(dice->clock_accepted); + + value = cpu_to_be32(clock_rate | CLOCK_SOURCE_ARX1); + err = snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST, + global_address(dice, GLOBAL_CLOCK_SELECT), + &value, 4, 0); + if (err < 0) + return err; + + wait_for_completion_timeout(&dice->clock_accepted, + msecs_to_jiffies(100)); + + return 0; +} + static int dice_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { @@ -831,11 +872,51 @@ static int dice_interface_check(struct fw_unit *unit) return 0; }
+static int highest_supported_mode_rate(struct dice *dice, unsigned int mode) +{ + int i; + + for (i = ARRAY_SIZE(dice_rates) - 1; i >= 0; --i) + if ((dice->clock_caps & (1 << i)) && + rate_index_to_mode(i) == mode) + return i; + + return -1; +} + +static int dice_read_mode_params(struct dice *dice, unsigned int mode) +{ + __be32 values[2]; + int rate_index, err; + + rate_index = highest_supported_mode_rate(dice, mode); + if (rate_index < 0) { + dice->rx_channels[mode] = 0; + dice->rx_midi_ports[mode] = 0; + return 0; + } + + err = dice_change_rate(dice, rate_index << CLOCK_RATE_SHIFT); + if (err < 0) + return err; + + err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, + rx_address(dice, RX_NUMBER_AUDIO), + values, 2 * 4, 0); + if (err < 0) + return err; + + dice->rx_channels[mode] = be32_to_cpu(values[0]); + dice->rx_midi_ports[mode] = be32_to_cpu(values[1]); + + return 0; +} + static int dice_read_params(struct dice *dice) { __be32 pointers[6]; __be32 value; - int err; + int mode, err;
err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, DICE_PRIVATE_SPACE, @@ -863,6 +944,12 @@ static int dice_read_params(struct dice *dice) CLOCK_CAP_SOURCE_INTERNAL; }
+ for (mode = 2; mode >= 0; --mode) { + err = dice_read_mode_params(dice, mode); + if (err < 0) + return err; + } + return 0; }
@@ -922,6 +1009,7 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) spin_lock_init(&dice->lock); mutex_init(&dice->mutex); dice->unit = unit; + init_completion(&dice->clock_accepted); init_waitqueue_head(&dice->hwdep_wait);
dice->notification_handler.length = 4;
Instead of relying of some control panel application to configure some fixed sample rate, allow applications to set it automatically.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice.c | 137 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 102 insertions(+), 35 deletions(-)
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index e6bba6d..61dd00c 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -70,6 +70,17 @@ static const unsigned int dice_rates[] = { [6] = 192000, };
+static unsigned int rate_to_index(unsigned int rate) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(dice_rates); ++i) + if (dice_rates[i] == rate) + return i; + + return 0; +} + static unsigned int rate_index_to_mode(unsigned int rate_index) { return ((int)rate_index - 1) / 2; @@ -302,6 +313,59 @@ static void dice_notification(struct fw_card *card, struct fw_request *request, wake_up(&dice->hwdep_wait); }
+static int dice_rate_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct dice *dice = rule->private; + const struct snd_interval *channels = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval *rate = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval allowed_rates = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, mode; + + for (i = 0; i < ARRAY_SIZE(dice_rates); ++i) { + mode = rate_index_to_mode(i); + if ((dice->clock_caps & (1 << i)) && + snd_interval_test(channels, dice->rx_channels[mode])) { + allowed_rates.min = min(allowed_rates.min, + dice_rates[i]); + allowed_rates.max = max(allowed_rates.max, + dice_rates[i]); + } + } + + return snd_interval_refine(rate, &allowed_rates); +} + +static int dice_channels_constraint(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct dice *dice = rule->private; + const struct snd_interval *rate = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval *channels = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval allowed_channels = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, mode; + + for (i = 0; i < ARRAY_SIZE(dice_rates); ++i) + if ((dice->clock_caps & (1 << i)) && + snd_interval_test(rate, dice_rates[i])) { + mode = rate_index_to_mode(i); + allowed_channels.min = min(allowed_channels.min, + dice->rx_channels[mode]); + allowed_channels.max = max(allowed_channels.max, + dice->rx_channels[mode]); + } + + return snd_interval_refine(channels, &allowed_channels); +} + static int dice_open(struct snd_pcm_substream *substream) { static const struct snd_pcm_hardware hardware = { @@ -311,6 +375,8 @@ static int dice_open(struct snd_pcm_substream *substream) SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER, .formats = AMDTP_OUT_PCM_FORMAT_BITS, + .channels_min = UINT_MAX, + .channels_max = 0, .buffer_bytes_max = 16 * 1024 * 1024, .period_bytes_min = 1, .period_bytes_max = UINT_MAX, @@ -319,53 +385,46 @@ static int dice_open(struct snd_pcm_substream *substream) }; struct dice *dice = substream->private_data; struct snd_pcm_runtime *runtime = substream->runtime; - __be32 clock_sel, data[2]; - unsigned int rate_index, number_audio, number_midi; + unsigned int i; int err;
err = dice_try_lock(dice); if (err < 0) goto error;
- err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, - global_address(dice, GLOBAL_CLOCK_SELECT), - &clock_sel, 4, 0); - if (err < 0) - goto err_lock; - rate_index = (be32_to_cpu(clock_sel) & CLOCK_RATE_MASK) - >> CLOCK_RATE_SHIFT; - if (rate_index >= ARRAY_SIZE(dice_rates)) { - err = -ENXIO; - goto err_lock; - } - - err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, - rx_address(dice, RX_NUMBER_AUDIO), - data, 2 * 4, 0); - if (err < 0) - goto err_lock; - number_audio = be32_to_cpu(data[0]); - number_midi = be32_to_cpu(data[1]); - runtime->hw = hardware;
- runtime->hw.rates = snd_pcm_rate_to_rate_bit(dice_rates[rate_index]); + for (i = 0; i < ARRAY_SIZE(dice_rates); ++i) + if (dice->clock_caps & (1 << i)) + runtime->hw.rates |= + snd_pcm_rate_to_rate_bit(dice_rates[i]); snd_pcm_limit_hw_rates(runtime);
- runtime->hw.channels_min = number_audio; - runtime->hw.channels_max = number_audio; + for (i = 0; i < 3; ++i) + if (dice->rx_channels[i]) { + runtime->hw.channels_min = min(runtime->hw.channels_min, + dice->rx_channels[i]); + runtime->hw.channels_max = max(runtime->hw.channels_max, + dice->rx_channels[i]); + }
- amdtp_out_stream_set_parameters(&dice->stream, dice_rates[rate_index], - number_audio, number_midi); + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + dice_rate_constraint, dice, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + goto err_lock; + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + dice_channels_constraint, dice, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + goto err_lock;
err = snd_pcm_hw_constraint_step(runtime, 0, - SNDRV_PCM_HW_PARAM_PERIOD_SIZE, - amdtp_syt_intervals[rate_index]); + SNDRV_PCM_HW_PARAM_PERIOD_SIZE, 32); if (err < 0) goto err_lock; err = snd_pcm_hw_constraint_step(runtime, 0, - SNDRV_PCM_HW_PARAM_BUFFER_SIZE, - amdtp_syt_intervals[rate_index]); + SNDRV_PCM_HW_PARAM_BUFFER_SIZE, 32); if (err < 0) goto err_lock;
@@ -502,6 +561,7 @@ static int dice_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { struct dice *dice = substream->private_data; + unsigned int rate_index, mode; int err;
mutex_lock(&dice->mutex); @@ -511,15 +571,22 @@ static int dice_hw_params(struct snd_pcm_substream *substream, err = snd_pcm_lib_alloc_vmalloc_buffer(substream, params_buffer_bytes(hw_params)); if (err < 0) - goto error; + return err;
+ rate_index = rate_to_index(params_rate(hw_params)); + err = dice_change_rate(dice, rate_index << CLOCK_RATE_SHIFT); + if (err < 0) + return err; + + mode = rate_index_to_mode(rate_index); + amdtp_out_stream_set_parameters(&dice->stream, + params_rate(hw_params), + params_channels(hw_params), + dice->rx_midi_ports[mode]); 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)
Output a warning if the wait for the clock change notification times out.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index 61dd00c..3395c8b 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -551,8 +551,9 @@ static int dice_change_rate(struct dice *dice, unsigned int clock_rate) if (err < 0) return err;
- wait_for_completion_timeout(&dice->clock_accepted, - msecs_to_jiffies(100)); + if (!wait_for_completion_timeout(&dice->clock_accepted, + msecs_to_jiffies(100))) + dev_warn(&dice->unit->device, "clock change timed out\n");
return 0; }
For easier debugging, add a proc file to show the device's capabilities and current status.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice.c | 246 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+)
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index 3395c8b..25a9636 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -22,6 +22,7 @@ #include <sound/core.h> #include <sound/firewire.h> #include <sound/hwdep.h> +#include <sound/info.h> #include <sound/initval.h> #include <sound/pcm.h> #include <sound/pcm_params.h> @@ -857,6 +858,249 @@ static int dice_create_hwdep(struct dice *dice) return 0; }
+static int dice_proc_read_mem(struct dice *dice, void *buffer, + unsigned int offset_q, unsigned int quadlets) +{ + unsigned int i; + int err; + + err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, + DICE_PRIVATE_SPACE + 4 * offset_q, + buffer, 4 * quadlets, 0); + if (err < 0) + return err; + + for (i = 0; i < quadlets; ++i) + be32_to_cpus(&((u32 *)buffer)[i]); + + return 0; +} + +static const char *str_from_array(const char *const strs[], unsigned int count, + unsigned int i) +{ + if (i < count) + return strs[i]; + else + return "(unknown)"; +} + +static void dice_proc_fixup_string(char *s, unsigned int size) +{ + unsigned int i; + + for (i = 0; i < size; i += 4) + cpu_to_le32s((u32 *)(s + i)); + + for (i = 0; i < size - 2; ++i) { + if (s[i] == '\0') + return; + if (s[i] == '\' && s[i + 1] == '\') { + s[i + 2] = '\0'; + return; + } + } + s[size - 1] = '\0'; +} + +static void dice_proc_read(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + static const char *const section_names[5] = { + "global", "tx", "rx", "ext_sync", "unused2" + }; + static const char *const clock_sources[] = { + "aes1", "aes2", "aes3", "aes4", "aes", "adat", "tdif", + "wc", "arx1", "arx2", "arx3", "arx4", "internal" + }; + static const char *const rates[] = { + "32000", "44100", "48000", "88200", "96000", "176400", "192000", + "any low", "any mid", "any high", "none" + }; + struct dice *dice = entry->private_data; + u32 sections[ARRAY_SIZE(section_names) * 2]; + struct { + u32 number; + u32 size; + } tx_rx_header; + union { + struct { + u32 owner_hi, owner_lo; + u32 notification; + char nick_name[NICK_NAME_SIZE]; + u32 clock_select; + u32 enable; + u32 status; + u32 extended_status; + u32 sample_rate; + u32 version; + u32 clock_caps; + char clock_source_names[CLOCK_SOURCE_NAMES_SIZE]; + } global; + struct { + u32 iso; + u32 number_audio; + u32 number_midi; + u32 speed; + char names[TX_NAMES_SIZE]; + u32 ac3_caps; + u32 ac3_enable; + } tx; + struct { + u32 iso; + u32 seq_start; + u32 number_audio; + u32 number_midi; + char names[RX_NAMES_SIZE]; + u32 ac3_caps; + u32 ac3_enable; + } rx; + struct { + u32 clock_source; + u32 locked; + u32 rate; + u32 adat_user_data; + } ext_sync; + } buf; + unsigned int quadlets, stream, i; + + if (dice_proc_read_mem(dice, sections, 0, ARRAY_SIZE(sections)) < 0) + return; + snd_iprintf(buffer, "sections:\n"); + for (i = 0; i < ARRAY_SIZE(section_names); ++i) + snd_iprintf(buffer, " %s: offset %u, size %u\n", + section_names[i], + sections[i * 2], sections[i * 2 + 1]); + + quadlets = min_t(u32, sections[1], sizeof(buf.global) / 4); + if (dice_proc_read_mem(dice, &buf.global, sections[0], quadlets) < 0) + return; + snd_iprintf(buffer, "global:\n"); + snd_iprintf(buffer, " owner: %04x:%04x%08x\n", + buf.global.owner_hi >> 16, + buf.global.owner_hi & 0xffff, buf.global.owner_lo); + snd_iprintf(buffer, " notification: %08x\n", buf.global.notification); + dice_proc_fixup_string(buf.global.nick_name, NICK_NAME_SIZE); + snd_iprintf(buffer, " nick name: %s\n", buf.global.nick_name); + snd_iprintf(buffer, " clock select: %s %s\n", + str_from_array(clock_sources, ARRAY_SIZE(clock_sources), + buf.global.clock_select & CLOCK_SOURCE_MASK), + str_from_array(rates, ARRAY_SIZE(rates), + (buf.global.clock_select & CLOCK_RATE_MASK) + >> CLOCK_RATE_SHIFT)); + snd_iprintf(buffer, " enable: %u\n", buf.global.enable); + snd_iprintf(buffer, " status: %slocked %s\n", + buf.global.status & STATUS_SOURCE_LOCKED ? "" : "un", + str_from_array(rates, ARRAY_SIZE(rates), + (buf.global.status & + STATUS_NOMINAL_RATE_MASK) + >> CLOCK_RATE_SHIFT)); + snd_iprintf(buffer, " ext status: %08x\n", buf.global.extended_status); + snd_iprintf(buffer, " sample rate: %u\n", buf.global.sample_rate); + snd_iprintf(buffer, " version: %u.%u.%u.%u\n", + (buf.global.version >> 24) & 0xff, + (buf.global.version >> 16) & 0xff, + (buf.global.version >> 8) & 0xff, + (buf.global.version >> 0) & 0xff); + if (quadlets >= 90) { + snd_iprintf(buffer, " clock caps:"); + for (i = 0; i <= 6; ++i) + if (buf.global.clock_caps & (1 << i)) + snd_iprintf(buffer, " %s", rates[i]); + for (i = 0; i <= 12; ++i) + if (buf.global.clock_caps & (1 << (16 + i))) + snd_iprintf(buffer, " %s", clock_sources[i]); + snd_iprintf(buffer, "\n"); + dice_proc_fixup_string(buf.global.clock_source_names, + CLOCK_SOURCE_NAMES_SIZE); + snd_iprintf(buffer, " clock source names: %s\n", + buf.global.clock_source_names); + } + + if (dice_proc_read_mem(dice, &tx_rx_header, sections[2], 2) < 0) + return; + quadlets = min_t(u32, tx_rx_header.size, sizeof(buf.tx)); + for (stream = 0; stream < tx_rx_header.number; ++stream) { + if (dice_proc_read_mem(dice, &buf.tx, sections[2] + 2 + + stream * tx_rx_header.size, + quadlets) < 0) + break; + snd_iprintf(buffer, "tx %u:\n", stream); + snd_iprintf(buffer, " iso channel: %d\n", (int)buf.tx.iso); + snd_iprintf(buffer, " audio channels: %u\n", + buf.tx.number_audio); + snd_iprintf(buffer, " midi ports: %u\n", buf.tx.number_midi); + snd_iprintf(buffer, " speed: S%u\n", 100u << buf.tx.speed); + if (quadlets >= 68) { + dice_proc_fixup_string(buf.tx.names, TX_NAMES_SIZE); + snd_iprintf(buffer, " names: %s\n", buf.tx.names); + } + if (quadlets >= 70) { + snd_iprintf(buffer, " ac3 caps: %08x\n", + buf.tx.ac3_caps); + snd_iprintf(buffer, " ac3 enable: %08x\n", + buf.tx.ac3_enable); + } + } + + if (dice_proc_read_mem(dice, &tx_rx_header, sections[4], 2) < 0) + return; + quadlets = min_t(u32, tx_rx_header.size, sizeof(buf.rx)); + for (stream = 0; stream < tx_rx_header.number; ++stream) { + if (dice_proc_read_mem(dice, &buf.rx, sections[4] + 2 + + stream * tx_rx_header.size, + quadlets) < 0) + break; + snd_iprintf(buffer, "rx %u:\n", stream); + snd_iprintf(buffer, " iso channel: %d\n", (int)buf.rx.iso); + snd_iprintf(buffer, " sequence start: %u\n", + (int)buf.rx.seq_start); + snd_iprintf(buffer, " audio channels: %u\n", + buf.rx.number_audio); + snd_iprintf(buffer, " midi ports: %u\n", buf.rx.number_midi); + if (quadlets >= 68) { + dice_proc_fixup_string(buf.rx.names, RX_NAMES_SIZE); + snd_iprintf(buffer, " names: %s\n", buf.rx.names); + } + if (quadlets >= 70) { + snd_iprintf(buffer, " ac3 caps: %08x\n", + buf.rx.ac3_caps); + snd_iprintf(buffer, " ac3 enable: %08x\n", + buf.rx.ac3_enable); + } + } + + quadlets = min_t(u32, sections[7], sizeof(buf.ext_sync) / 4); + if (quadlets >= 4) { + if (dice_proc_read_mem(dice, &buf.ext_sync, + sections[6], 4) < 0) + return; + snd_iprintf(buffer, "ext status:\n"); + snd_iprintf(buffer, " clock source: %s\n", + str_from_array(clock_sources, + ARRAY_SIZE(clock_sources), + buf.ext_sync.clock_source)); + snd_iprintf(buffer, " locked: %u\n", buf.ext_sync.locked); + snd_iprintf(buffer, " rate: %s\n", + str_from_array(rates, ARRAY_SIZE(rates), + buf.ext_sync.rate)); + snd_iprintf(buffer, " adat user data: "); + if (buf.ext_sync.adat_user_data & ADAT_USER_DATA_NO_DATA) + snd_iprintf(buffer, "-\n"); + else + snd_iprintf(buffer, "%x\n", + buf.ext_sync.adat_user_data); + } +} + +static void dice_create_proc(struct dice *dice) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(dice->card, "dice", &entry)) + snd_info_set_text_ops(entry, dice, dice_proc_read); +} + static void dice_card_free(struct snd_card *card) { struct dice *dice = card->private_data; @@ -1131,6 +1375,8 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) if (err < 0) goto error;
+ dice_create_proc(dice); + err = snd_card_register(card); if (err < 0) goto error;
Doing accesses without quadlet alignment is a bad idea because the firmware's byte-swapping would garble the data; clarify this in the documentation.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice-interface.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/sound/firewire/dice-interface.h b/sound/firewire/dice-interface.h index af916b9..27b044f 100644 --- a/sound/firewire/dice-interface.h +++ b/sound/firewire/dice-interface.h @@ -7,9 +7,9 @@
/* * Generally, all registers can be read like memory, i.e., with quadlet read or - * block read transactions with any alignment or length. Writes are not - * allowed except where noted; quadlet-sized registers must be written with - * a quadlet write transaction. + * block read transactions with at least quadlet-aligned offset and length. + * Writes are not allowed except where noted; quadlet-sized registers must be + * written with a quadlet write transaction. * * All values are in big endian. The DICE firmware runs on a little-endian CPU * and just byte-swaps _all_ quadlets on the bus, so values without endianness
Remove a wrong typecast that resulted from a copy-and-paste error.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index 25a9636..5f0f102 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -1053,8 +1053,7 @@ static void dice_proc_read(struct snd_info_entry *entry, break; snd_iprintf(buffer, "rx %u:\n", stream); snd_iprintf(buffer, " iso channel: %d\n", (int)buf.rx.iso); - snd_iprintf(buffer, " sequence start: %u\n", - (int)buf.rx.seq_start); + snd_iprintf(buffer, " sequence start: %u\n", buf.rx.seq_start); snd_iprintf(buffer, " audio channels: %u\n", buf.rx.number_audio); snd_iprintf(buffer, " midi ports: %u\n", buf.rx.number_midi);
While most DICE devices keep TCAT's default category ID of 0x04, Weiss devices identify themselves with 0x00.
Reported-by: Rolf Anderegg rolf.anderegg@weiss.ch Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/dice.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index 5f0f102..49d630b 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -1109,7 +1109,10 @@ static void dice_card_free(struct snd_card *card) mutex_destroy(&dice->mutex); }
-#define DICE_CATEGORY_ID 0x04 +#define OUI_WEISS 0x001c6a + +#define DICE_CATEGORY_ID 0x04 +#define WEISS_CATEGORY_ID 0x00
static int dice_interface_check(struct fw_unit *unit) { @@ -1123,15 +1126,15 @@ static int dice_interface_check(struct fw_unit *unit) struct fw_device *device = fw_parent_device(unit); struct fw_csr_iterator it; int key, value, vendor = -1, model = -1, err; - unsigned int i; + unsigned int category, i; __be32 pointers[ARRAY_SIZE(min_values)]; __be32 version;
/* * Check that GUID and unit directory are constructed according to DICE * rules, i.e., that the specifier ID is the GUID's OUI, and that the - * GUID chip ID consists of the 8-bit DICE category ID, the 10-bit - * product ID, and a 22-bit serial number. + * GUID chip ID consists of the 8-bit category ID, the 10-bit product + * ID, and a 22-bit serial number. */ fw_csr_iterator_init(&it, unit->directory); while (fw_csr_iterator_next(&it, &key, &value)) { @@ -1144,7 +1147,11 @@ static int dice_interface_check(struct fw_unit *unit) break; } } - if (device->config_rom[3] != ((vendor << 8) | DICE_CATEGORY_ID) || + if (vendor == OUI_WEISS) + category = WEISS_CATEGORY_ID; + else + category = DICE_CATEGORY_ID; + if (device->config_rom[3] != ((vendor << 8) | category) || device->config_rom[4] >> 22 != model) return -ENODEV;
At the moment, this driver supports only playback, while FFADO supports (only) full-duplex devices. So, prevent conflicts by not claiming devices that would be better handled by FFADO.
Signed-off-by: Clemens Ladisch clemens@ladisch.de --- sound/firewire/Kconfig | 10 ++++++---- sound/firewire/dice.c | 9 +++++++++ 2 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 9153309..b3e274f 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -12,14 +12,16 @@ config SND_FIREWIRE_LIB depends on SND_PCM
config SND_DICE - tristate "DICE devices (EXPERIMENTAL)" + tristate "DICE-based DACs (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. + Say Y here to include support for many DACs based on the DICE + chip family (DICE-II/Jr/Mini) from TC Applied Technologies. + + At the moment, this driver supports playback only. If you + want to use devices that support capturing, use FFADO instead.
To compile this driver as a module, choose M here: the module will be called snd-dice. diff --git a/sound/firewire/dice.c b/sound/firewire/dice.c index 49d630b..6feee66 100644 --- a/sound/firewire/dice.c +++ b/sound/firewire/dice.c @@ -1128,6 +1128,7 @@ static int dice_interface_check(struct fw_unit *unit) int key, value, vendor = -1, model = -1, err; unsigned int category, i; __be32 pointers[ARRAY_SIZE(min_values)]; + __be32 tx_data[4]; __be32 version;
/* @@ -1171,6 +1172,14 @@ static int dice_interface_check(struct fw_unit *unit) return -ENODEV; }
+ /* We support playback only. Let capture devices be handled by FFADO. */ + err = snd_fw_transaction(unit, TCODE_READ_BLOCK_REQUEST, + DICE_PRIVATE_SPACE + + be32_to_cpu(pointers[2]) * 4, + tx_data, sizeof(tx_data), 0); + if (err < 0 || (tx_data[0] && tx_data[3])) + return -ENODEV; + /* * Check that the implemented DICE driver specification major version * number matches.
At Mon, 21 Oct 2013 21:19:23 +0200, Clemens Ladisch wrote:
Hi Takashi,
please pull this driver for FireWire DACs based on the DICE chip family. While the driver supports only PCM playback at the moment, it fills a gap left by FFADO, which supports only full- duplex devices.
I already pulled it yesterday. Sorry, forgot to inform you. Thanks.
Takashi
Thanks, Clemens
The following changes since commit 6e4664525b1db28f8c4e1130957f70a94c19213e:
Linux 3.11 (2013-09-02 13:46:10 -0700)
are available in the git repository at:
git://git.alsa-project.org/alsa-kprivate.git dice-driver-playback-only
for you to fetch changes up to b20be8de1b3972ccf9af72850b045214faa8d830:
ALSA: dice: restrict the driver to playback-only devices (2013-10-20 22:07:57 +0200)
Clemens Ladisch (28): ALSA: add DICE driver ALSA: dice: optimize bus reset handling ALSA: dice: allow all sample rates ALSA: dice: reduce noisy logging ALSA: dice, firewire-lib: add blocking mode ALSA: dice: fix hang when unplugging a running device ALSA: dice: implement hwdep device ALSA: dice: clear device lock when closing hwdep device ALSA: firewire: introduce amdtp_out_stream_running() ALSA: dice: reorganize interface definitions ALSA: dice: fix device detection for other vendors ALSA: dice: support dual-wire stream format at 192 kHz ALSA: dice: optimize reading of consecutive registers ALSA: firewire: extend snd_fw_transaction() ALSA: dice: avoid superflous write at bus reset ALSA: dice: remove 10s period length limit ALSA: dice: remove superfluous field ALSA: dice: make amdtp_rates[] const ALSA: dice: get clock capabilities ALSA: dice: allow notifications during initialization ALSA: dice: get rate-dependent parameters ALSA: dice: dynamic sample rate selection ALSA: dice: check clock change timeout ALSA: dice: add a proc file to show device information ALSA: dice: document quadlet alignment ALSA: dice: dice_proc_read: remove wrong typecast ALSA: dice: fix detection of Weiss devices ALSA: dice: restrict the driver to playback-only devices
Stefan Richter (1): ALSA: dice: fix locking
Documentation/ioctl/ioctl-number.txt | 1 + include/uapi/sound/Kbuild | 1 + include/uapi/sound/asound.h | 3 +- include/uapi/sound/firewire.h | 51 ++ sound/firewire/Kconfig | 15 + sound/firewire/Makefile | 2 + sound/firewire/amdtp.c | 209 +++-- sound/firewire/amdtp.h | 46 +- sound/firewire/cmp.c | 50 +- sound/firewire/dice-interface.h | 371 +++++++++ sound/firewire/dice.c | 1494 ++++++++++++++++++++++++++++++++++ sound/firewire/fcp.c | 2 +- sound/firewire/isight.c | 43 +- sound/firewire/lib.c | 24 +- sound/firewire/lib.h | 7 +- sound/firewire/scs1x.c | 8 +- sound/firewire/speakers.c | 16 +- 17 files changed, 2192 insertions(+), 151 deletions(-) create mode 100644 include/uapi/sound/firewire.h create mode 100644 sound/firewire/dice-interface.h create mode 100644 sound/firewire/dice.c
participants (2)
-
Clemens Ladisch
-
Takashi Iwai