[alsa-devel] [RFC v3][PATCH 00/13] ALSA: fireface: new driver for RME Fireface series
Hi,
This patchset is a revised version of my former one:
[alsa-devel] [RFC v2][PATCH 00/11] ALSA: fireface: new driver for RME Fireface series http://mailman.alsa-project.org/pipermail/alsa-devel/2015-December/102261.ht...
Since the end of 2015, ALSA firewire stack got many changes. This revision adapt the former one to current stack. Furthermore, I add an abstraction layer of Fireface protocol to help developers to add support for the rest of models. (I'm unwilling to work further for this direction, because of its cost.)
Changes: * Rebase to current ALSA firewire stack and add optimization. * Rename local structure with shorter words. * Delegate a task to configure settings on write-only registers to user space applications. * Switch mode to fetch PCM frames from data blocks to sampling data processing layer voluntarily. * Revise comments with technical information from my analysis to Fireface 400 packets.
The rebasing is actually done on my proposal of ALSA MOTU driver. Please apply this patchset to it to prevent conflicts. [alsa-devel] [PATCH 00/19][RFC v2] ALSA: firewire-motu: new driver for MOTU FireWire series http://mailman.alsa-project.org/pipermail/alsa-devel/2017-January/117211.htm...
Currently, I have a plan to post this patchset to merge into Linux 4.12. Corresponding merge window will be estimated to open this April. If you're willing to test this module, please report the result till then.
Unfortunately, I did this work just with my notes and log dumps, thus without any test device. I'm happy if you test this patchset with your Fireface 400. For testers, I prepare for backport codes in my repository in github.com. Please use 'topic/fireface' branch. https://github.com/takaswie/snd-firewire-improve/tree/topic/fireface
If you have any question, please reply to me even if it's trivial; e.g. typo in comments, as long as its' technical issue.
Regards
Takashi Sakamoto (13): ALSA: fireface: add skeleton for RME Fireface series ALSA: fireface: postpone sound card registration ALSA: fireface: add model specific structure ALSA: fireface: add an abstraction layer for model-specific protocols ALSA: fireface: add transaction support ALSA: fireface: add support for MIDI functionality ALSA: fireface: add proc node to help debugging ALSA: firewire-lib: add no-header packet processing ALSA: fireface: add unique data processing layer ALSA: fireface: add stream management functionality ALSA: fireface: add support for PCM functionality ALSA: fireface: add hwdep interface ALSA: fireface: add support for Fireface 400
include/uapi/sound/asound.h | 3 +- include/uapi/sound/firewire.h | 2 +- sound/firewire/Kconfig | 8 + sound/firewire/Makefile | 1 + sound/firewire/amdtp-stream.c | 84 +++++- sound/firewire/amdtp-stream.h | 2 + sound/firewire/fireface/Makefile | 3 + sound/firewire/fireface/amdtp-ff.c | 155 +++++++++++ sound/firewire/fireface/ff-hwdep.c | 191 +++++++++++++ sound/firewire/fireface/ff-midi.c | 131 +++++++++ sound/firewire/fireface/ff-pcm.c | 404 ++++++++++++++++++++++++++++ sound/firewire/fireface/ff-proc.c | 63 +++++ sound/firewire/fireface/ff-protocol-ff400.c | 371 +++++++++++++++++++++++++ sound/firewire/fireface/ff-stream.c | 282 +++++++++++++++++++ sound/firewire/fireface/ff-transaction.c | 295 ++++++++++++++++++++ sound/firewire/fireface/ff.c | 209 ++++++++++++++ sound/firewire/fireface/ff.h | 145 ++++++++++ 17 files changed, 2342 insertions(+), 7 deletions(-) create mode 100644 sound/firewire/fireface/Makefile create mode 100644 sound/firewire/fireface/amdtp-ff.c create mode 100644 sound/firewire/fireface/ff-hwdep.c create mode 100644 sound/firewire/fireface/ff-midi.c create mode 100644 sound/firewire/fireface/ff-pcm.c create mode 100644 sound/firewire/fireface/ff-proc.c create mode 100644 sound/firewire/fireface/ff-protocol-ff400.c create mode 100644 sound/firewire/fireface/ff-stream.c create mode 100644 sound/firewire/fireface/ff-transaction.c create mode 100644 sound/firewire/fireface/ff.c create mode 100644 sound/firewire/fireface/ff.h
This commit adds a new driver for RME Fireface series. This commit just creates/removes card instance according to IEEE 1394 bus event. More functions will be added in following commits.
Three types of firmware have released by RME GmbH; for Fireface 400, for Fireface 800 and for UCX/802/UFX. It's reasonable that these models use different protocol for communication. Currently, I've investigated Fireface 400 and nothing others.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 6 +++ sound/firewire/Makefile | 1 + sound/firewire/fireface/Makefile | 2 + sound/firewire/fireface/ff.c | 113 +++++++++++++++++++++++++++++++++++++++ sound/firewire/fireface/ff.h | 28 ++++++++++ 5 files changed, 150 insertions(+) create mode 100644 sound/firewire/fireface/Makefile create mode 100644 sound/firewire/fireface/ff.c create mode 100644 sound/firewire/fireface/ff.h
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 2a91fe8..077e03c 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -153,4 +153,10 @@ config SND_FIREWIRE_MOTU To compile this driver as a module, choose M here: the module will be called snd-firewire-motu.
+config SND_FIREFACE + tristate "RME Fireface series support" + select SND_FIREWIRE_LIB + help + Say Y here to include support for RME fireface series. + endif # SND_FIREWIRE diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index 9388ded..1b98fa3 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -14,3 +14,4 @@ obj-$(CONFIG_SND_BEBOB) += bebob/ obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += digi00x/ obj-$(CONFIG_SND_FIREWIRE_TASCAM) += tascam/ obj-$(CONFIG_SND_FIREWIRE_MOTU) += motu/ +obj-$(CONFIG_SND_FIREFACE) += fireface/ diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile new file mode 100644 index 0000000..2c64ef6 --- /dev/null +++ b/sound/firewire/fireface/Makefile @@ -0,0 +1,2 @@ +snd-fireface-objs := ff.o +obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c new file mode 100644 index 0000000..358bba2 --- /dev/null +++ b/sound/firewire/fireface/ff.c @@ -0,0 +1,113 @@ +/* + * ff.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +#define OUI_RME 0x000a35 + +MODULE_DESCRIPTION("RME Fireface series Driver"); +MODULE_AUTHOR("Takashi Sakamoto o-takashi@sakamocchi.jp"); +MODULE_LICENSE("GPL v2"); + +static void name_card(struct snd_ff *ff) +{ + struct fw_device *fw_dev = fw_parent_device(ff->unit); + const char *const model = "Fireface Skeleton"; + + strcpy(ff->card->driver, "Fireface"); + strcpy(ff->card->shortname, model); + strcpy(ff->card->mixername, model); + snprintf(ff->card->longname, sizeof(ff->card->longname), + "RME %s, GUID %08x%08x at %s, S%d", model, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&ff->unit->device), 100 << fw_dev->max_speed); +} + +static void ff_card_free(struct snd_card *card) +{ + struct snd_ff *ff = card->private_data; + + fw_unit_put(ff->unit); + + mutex_destroy(&ff->mutex); +} + +static int snd_ff_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_card *card; + struct snd_ff *ff; + int err; + + err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, + sizeof(struct snd_ff), &card); + if (err < 0) + return err; + card->private_free = ff_card_free; + + /* initialize myself */ + ff = card->private_data; + ff->card = card; + ff->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, ff); + + mutex_init(&ff->mutex); + + name_card(ff); + + err = snd_card_register(card); + if (err < 0) { + snd_card_free(card); + return err; + } + + return 0; +} + +static void snd_ff_update(struct fw_unit *unit) +{ + return; +} + +static void snd_ff_remove(struct fw_unit *unit) +{ + struct snd_ff *ff = dev_get_drvdata(&unit->device); + + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(ff->card); +} + +static const struct ieee1394_device_id snd_ff_id_table[] = { + {} +}; +MODULE_DEVICE_TABLE(ieee1394, snd_ff_id_table); + +static struct fw_driver ff_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "snd-fireface", + .bus = &fw_bus_type, + }, + .probe = snd_ff_probe, + .update = snd_ff_update, + .remove = snd_ff_remove, + .id_table = snd_ff_id_table, +}; + +static int __init snd_ff_init(void) +{ + return driver_register(&ff_driver.driver); +} + +static void __exit snd_ff_exit(void) +{ + driver_unregister(&ff_driver.driver); +} + +module_init(snd_ff_init); +module_exit(snd_ff_exit); diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h new file mode 100644 index 0000000..64d488e --- /dev/null +++ b/sound/firewire/fireface/ff.h @@ -0,0 +1,28 @@ +/* + * ff.h - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#ifndef SOUND_FIREFACE_H_INCLUDED +#define SOUND_FIREFACE_H_INCLUDED + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/compat.h> + +#include <sound/core.h> + +struct snd_ff { + struct snd_card *card; + struct fw_unit *unit; + struct mutex mutex; +}; +#endif
Just after appearing on IEEE 1394 bus, this unit generates several bus resets. This is due to loading firmware from on-board flash memory and initialize hardware. It's better to postpone sound card registration.
This commit schedules workqueue to process actual probe processing 2 seconds after the last bus-reset. The card instance is kept at unit probe callback and released at card free callback. Therefore, when the actual probe processing fails, the memory block is wasted. This is due to simplify driver implementation.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/fireface/ff.c | 84 ++++++++++++++++++++++++++++++++------------ sound/firewire/fireface/ff.h | 5 +++ 2 files changed, 67 insertions(+), 22 deletions(-)
diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c index 358bba2..7c02639 100644 --- a/sound/firewire/fireface/ff.c +++ b/sound/firewire/fireface/ff.c @@ -28,58 +28,98 @@ static void name_card(struct snd_ff *ff) dev_name(&ff->unit->device), 100 << fw_dev->max_speed); }
-static void ff_card_free(struct snd_card *card) +static void ff_free(struct snd_ff *ff) { - struct snd_ff *ff = card->private_data; - fw_unit_put(ff->unit);
mutex_destroy(&ff->mutex); + kfree(ff); +} + +static void ff_card_free(struct snd_card *card) +{ + ff_free(card->private_data); +} + +static void do_registration(struct work_struct *work) +{ + struct snd_ff *ff = container_of(work, struct snd_ff, dwork.work); + int err; + + if (ff->registered) + return; + + err = snd_card_new(&ff->unit->device, -1, NULL, THIS_MODULE, 0, + &ff->card); + if (err < 0) + return; + + name_card(ff); + + err = snd_card_register(ff->card); + if (err < 0) + goto error; + + ff->card->private_free = ff_card_free; + ff->card->private_data = ff; + ff->registered = true; + + return; +error: + snd_card_free(ff->card); + dev_info(&ff->unit->device, + "Sound card registration failed: %d\n", err); }
static int snd_ff_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry) { - struct snd_card *card; struct snd_ff *ff; - int err;
- err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, - sizeof(struct snd_ff), &card); - if (err < 0) - return err; - card->private_free = ff_card_free; + ff = kzalloc(sizeof(struct snd_ff), GFP_KERNEL); + if (ff == NULL) + return -ENOMEM;
/* initialize myself */ - ff = card->private_data; - ff->card = card; ff->unit = fw_unit_get(unit); dev_set_drvdata(&unit->device, ff);
mutex_init(&ff->mutex);
- name_card(ff); - - err = snd_card_register(card); - if (err < 0) { - snd_card_free(card); - return err; - } + /* Register this sound card later. */ + INIT_DEFERRABLE_WORK(&ff->dwork, do_registration); + snd_fw_schedule_registration(unit, &ff->dwork);
return 0; }
static void snd_ff_update(struct fw_unit *unit) { - return; + struct snd_ff *ff = dev_get_drvdata(&unit->device); + + /* Postpone a workqueue for deferred registration. */ + if (!ff->registered) + snd_fw_schedule_registration(unit, &ff->dwork); }
static void snd_ff_remove(struct fw_unit *unit) { struct snd_ff *ff = dev_get_drvdata(&unit->device);
- /* No need to wait for releasing card object in this context. */ - snd_card_free_when_closed(ff->card); + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_work_sync(&ff->dwork.work); + + if (ff->registered) { + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(ff->card); + } else { + /* Don't forget this case. */ + ff_free(ff); + } }
static const struct ieee1394_device_id snd_ff_id_table[] = { diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index 64d488e..a0faae1 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -20,9 +20,14 @@
#include <sound/core.h>
+#include "../lib.h" + struct snd_ff { struct snd_card *card; struct fw_unit *unit; struct mutex mutex; + + bool registered; + struct delayed_work dwork; }; #endif
RME Fireface series has several models and their specifications are different. Currently, we find no way to retrieve the specifications from actual devices and need to implement them in this driver.
This commit adds a structure to describe model specific data. This structure has an identical name for each unit, and maximum number of data channels in each mode. I'll descibe about the mode in following commits.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/fireface/ff.c | 9 +++++---- sound/firewire/fireface/ff.h | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c index 7c02639..5c2bd92 100644 --- a/sound/firewire/fireface/ff.c +++ b/sound/firewire/fireface/ff.c @@ -17,13 +17,12 @@ MODULE_LICENSE("GPL v2"); static void name_card(struct snd_ff *ff) { struct fw_device *fw_dev = fw_parent_device(ff->unit); - const char *const model = "Fireface Skeleton";
strcpy(ff->card->driver, "Fireface"); - strcpy(ff->card->shortname, model); - strcpy(ff->card->mixername, model); + strcpy(ff->card->shortname, ff->spec->name); + strcpy(ff->card->mixername, ff->spec->name); snprintf(ff->card->longname, sizeof(ff->card->longname), - "RME %s, GUID %08x%08x at %s, S%d", model, + "RME %s, GUID %08x%08x at %s, S%d", ff->spec->name, fw_dev->config_rom[3], fw_dev->config_rom[4], dev_name(&ff->unit->device), 100 << fw_dev->max_speed); } @@ -86,6 +85,8 @@ static int snd_ff_probe(struct fw_unit *unit,
mutex_init(&ff->mutex);
+ ff->spec = (const struct snd_ff_spec *)entry->driver_data; + /* Register this sound card later. */ INIT_DEFERRABLE_WORK(&ff->dwork, do_registration); snd_fw_schedule_registration(unit, &ff->dwork); diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index a0faae1..269fa25 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -22,6 +22,18 @@
#include "../lib.h"
+#define SND_FF_STREAM_MODES 3 + +struct snd_ff_spec { + const char *const name; + + const unsigned int pcm_capture_channels[SND_FF_STREAM_MODES]; + const unsigned int pcm_playback_channels[SND_FF_STREAM_MODES]; + + unsigned int midi_in_ports; + unsigned int midi_out_ports; +}; + struct snd_ff { struct snd_card *card; struct fw_unit *unit; @@ -29,5 +41,7 @@ struct snd_ff {
bool registered; struct delayed_work dwork; + + const struct snd_ff_spec *spec; }; #endif
As of 2016, RME discontinued its Fireface series, thus it's OK for us to drive known units with three types of firmware.
As long as investigating Fireface 400 with Windows driver and comparing the result to FFADO implementation, these firmwares have different register assignments. On the other hand, according to manuals of each models, features relevant to packet streaming seem to be common. It's reasonable to assume an abstraction layer of protocols to communicate to each models.
This commit adds an abstraction layer for the protocols. This layer includes some functions to operate common features of models in this series.
In IEC 61883-1/6, the sequence of packet can transfer timing information to synchronize transmitters/receivers. Units of each node on IEEE 1394 bus can generate transmitter's timing clock by handling value of SYT field in CIP header with high-precision clock. For audio and music units on IEEE 1394 bus, this recovered clock is designed to used for sampling clock to capture/generate PCM frames on DSP/ADC/DAC. (Actually, there's no units to implement this specification correctly in this world, as long as I know).
Fireface series doesn't use this mechanism and isochronous packet with CIP header. It uses internal crystal unit as its initial sampling clock. When detecting input signals which can be available for sampling clock (e.g. ADAT input), drivers can configure units to use the signals as source of sampling clock. When something goes wrong, e.g. frequency mismatching between the signal and configured value, units switch to the other detected signals alternatively. When detecting no alternatives, internal crystal unit is used as source of sampling clock. On manual of Fireface 400, this mechanism is described as 'Autosync'.
For packet streaming layer, it's enough to get current selection of source signals for the sampling clock and its frequency. When internal crystal is selected, drivers can sets arbitrary sampling frequency, else they should follow configured frequency. For this purpose, .get_clock is added.
On the units, packet streaming is controlled by write transactions to certain registers. Format of the packet, e.g. the number of data channels in a data block, is also configured by the same manner. For this purpose, .begin_session and .finish_session is added.
The remarkable point of this protocol is to allow drivers to configure arbitrary sampling transmission frequency; e.g. 123.456 Hz. As long as I know, there's no DAC/ADC chips which support this kind of capability. I think a pair of packet streaming/data block processing layer is isolated from sampling data processing layer in a point of governed clock. In short, between these parts, resampling layer exists between these parts. Actually, for Fireface 400, write transactions to 0x'0000'8010'051c has an effect to change sampling clock frequency with base frequency of 32.0/44.1/48.0 kHz and its multipliers (x2/x4).
For this reason, the abstraction layer has no function to set sampling clock. Instead, each implementation of .begin_session is expected to configure sampling transmission frequency.
Drivers are allows to bank up data fetching from a pair of packet streaming/data block processing layer and sampling data processing layer. This feature seems to suppress noises at starting/stopping packet streaming. For this purpose, .switch_fetching_mode is added.
As I described in the above, units have remarkable mechanism to manage sampling clock and process sampling data. For debugging purpose, .dump_sync_status and .dump_clock_config are added. I don't have a need to common interface to represent the status and configuration, developers can add actual implementation of the abstraction layer as he or she likes.
Unlike PCM frames, MIDI messages are transferred by asynchronous communication over IEEE 1394 bus, thus target addresses are important for this feature. The .midi_high_addr_reg, .midi_rx_port_0_reg and .midi_rx_port_1_reg are for this purpose. I'll describe them in following commit.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/fireface/ff.h | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+)
diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index 269fa25..7be0ea4 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -19,11 +19,13 @@ #include <linux/compat.h>
#include <sound/core.h> +#include <sound/info.h>
#include "../lib.h"
#define SND_FF_STREAM_MODES 3
+struct snd_ff_protocol; struct snd_ff_spec { const char *const name;
@@ -32,6 +34,8 @@ struct snd_ff_spec {
unsigned int midi_in_ports; unsigned int midi_out_ports; + + struct snd_ff_protocol *protocol; };
struct snd_ff { @@ -44,4 +48,31 @@ struct snd_ff {
const struct snd_ff_spec *spec; }; + +enum snd_ff_clock_src { + SND_FF_CLOCK_SRC_INTERNAL, + SND_FF_CLOCK_SRC_SPDIF, + SND_FF_CLOCK_SRC_ADAT, + SND_FF_CLOCK_SRC_WORD, + SND_FF_CLOCK_SRC_LTC, + /* TODO: perhaps ADAT2 and TCO exists. */ +}; + +struct snd_ff_protocol { + int (*get_clock)(struct snd_ff *ff, unsigned int *rate, + enum snd_ff_clock_src *src); + int (*begin_session)(struct snd_ff *ff, unsigned int rate); + void (*finish_session)(struct snd_ff *ff); + int (*switch_fetching_mode)(struct snd_ff *ff, bool enable); + + void (*dump_sync_status)(struct snd_ff *ff, + struct snd_info_buffer *buffer); + void (*dump_clock_config)(struct snd_ff *ff, + struct snd_info_buffer *buffer); + + u64 midi_high_addr_reg; + u64 midi_rx_port_0_reg; + u64 midi_rx_port_1_reg; +}; + #endif
As long as investigating Fireface 400, MIDI messages are transferred by asynchronous communication over IEEE 1394 bus.
Fireface 400 receives MIDI messages by write transactions to two addresses; 0x'0000'0801'8000 and 0x'0000'0801'9000. Each of two seems to correspond to MIDI port 1 and 2.
Fireface 400 transfers MIDI messages by write transactions to certain addresses which configured by drivers. The drivers can decide upper 4 byte of the addresses by write transactions to 0x'0000'0801'03f4. For the rest part of the address, drivers can select from below options: * 0x'0000'0000 * 0x'0000'0080 * 0x'0000'0100 * 0x'0000'0180
Selected options are represented in register 0x'0000'0801'051c as bit flags. Due to this mechanism, drivers are restricted to use addresses on 'Memory space' of IEEE 1222, even if transactions to the address have some side effects.
This commit adds transaction support for MIDI messaging, based on my assumption that the similar mechanism is used on the other protocols. To receive asynchronous transactions, the driver allocates a range of address in 'Memory space'. I apply a strategy to use 0x'0000'0000 as lower 4 byte of the address. This driver retries to allocate when getting failure from Linux FireWire subsystem.
Unfortunately, read transaction to address 0x'0000'0801'051c returns zero always, however write transactions have effects to the other features such as status of sampling clock. For this reason, this commit delegates the task to configure this register to user space applications. The applications should set 3rd bit in LSB in little endian order.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/fireface/Makefile | 2 +- sound/firewire/fireface/ff-transaction.c | 295 +++++++++++++++++++++++++++++++ sound/firewire/fireface/ff.c | 9 + sound/firewire/fireface/ff.h | 23 +++ 4 files changed, 328 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/fireface/ff-transaction.c
diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile index 2c64ef6..864aacc 100644 --- a/sound/firewire/fireface/Makefile +++ b/sound/firewire/fireface/Makefile @@ -1,2 +1,2 @@ -snd-fireface-objs := ff.o +snd-fireface-objs := ff.o ff-transaction.o obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o diff --git a/sound/firewire/fireface/ff-transaction.c b/sound/firewire/fireface/ff-transaction.c new file mode 100644 index 0000000..dd6c8e8 --- /dev/null +++ b/sound/firewire/fireface/ff-transaction.c @@ -0,0 +1,295 @@ +/* + * ff-transaction.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +static void finish_transmit_midi_msg(struct snd_ff *ff, unsigned int port, + int rcode) +{ + struct snd_rawmidi_substream *substream = + ACCESS_ONCE(ff->rx_midi_substreams[port]); + + if (rcode_is_permanent_error(rcode)) { + ff->rx_midi_error[port] = true; + return; + } + + if (rcode != RCODE_COMPLETE) { + /* Transfer the message again, immediately. */ + ff->next_ktime[port] = 0; + schedule_work(&ff->rx_midi_work[port]); + return; + } + + snd_rawmidi_transmit_ack(substream, ff->rx_bytes[port]); + ff->rx_bytes[port] = 0; + + if (!snd_rawmidi_transmit_empty(substream)) + schedule_work(&ff->rx_midi_work[port]); +} + +static void finish_transmit_midi0_msg(struct fw_card *card, int rcode, + void *data, size_t length, + void *callback_data) +{ + struct snd_ff *ff = + container_of(callback_data, struct snd_ff, transactions[0]); + finish_transmit_midi_msg(ff, 0, rcode); +} + +static void finish_transmit_midi1_msg(struct fw_card *card, int rcode, + void *data, size_t length, + void *callback_data) +{ + struct snd_ff *ff = + container_of(callback_data, struct snd_ff, transactions[1]); + finish_transmit_midi_msg(ff, 1, rcode); +} + +static inline void fill_midi_buf(struct snd_ff *ff, unsigned int port, + unsigned int index, u8 byte) +{ + ff->msg_buf[port][index] = cpu_to_le32(byte); +} + +static void transmit_midi_msg(struct snd_ff *ff, unsigned int port) +{ + struct snd_rawmidi_substream *substream = + ACCESS_ONCE(ff->rx_midi_substreams[port]); + u8 *buf = (u8 *)ff->msg_buf[port]; + int i, len; + + struct fw_device *fw_dev = fw_parent_device(ff->unit); + unsigned long long addr; + int generation; + fw_transaction_callback_t callback; + + if (substream == NULL || snd_rawmidi_transmit_empty(substream)) + return; + + if (ff->rx_bytes[port] > 0 || ff->rx_midi_error[port]) + return; + + /* Do it in next chance. */ + if (ktime_after(ff->next_ktime[port], ktime_get())) { + schedule_work(&ff->rx_midi_work[port]); + return; + } + + len = snd_rawmidi_transmit_peek(substream, buf, + SND_FF_MAXIMIM_MIDI_QUADS); + if (len <= 0) + return; + + for (i = len - 1; i >= 0; i--) + fill_midi_buf(ff, port, i, buf[i]); + + if (port == 0) { + addr = ff->spec->protocol->midi_rx_port_0_reg; + callback = finish_transmit_midi0_msg; + } else { + addr = ff->spec->protocol->midi_rx_port_1_reg; + callback = finish_transmit_midi1_msg; + } + + /* Set interval to next transaction. */ + ff->next_ktime[port] = ktime_add_ns(ktime_get(), + len * 8 * NSEC_PER_SEC / 31250); + ff->rx_bytes[port] = len; + + /* + * In Linux FireWire core, when generation is updated with memory + * barrier, node id has already been updated. In this module, After + * this smp_rmb(), load/store instructions to memory are completed. + * Thus, both of generation and node id are available with recent + * values. This is a light-serialization solution to handle bus reset + * events on IEEE 1394 bus. + */ + generation = fw_dev->generation; + smp_rmb(); + fw_send_request(fw_dev->card, &ff->transactions[port], + TCODE_WRITE_BLOCK_REQUEST, + fw_dev->node_id, generation, fw_dev->max_speed, + addr, &ff->msg_buf[port], len * 4, + callback, &ff->transactions[port]); +} + +static void transmit_midi0_msg(struct work_struct *work) +{ + struct snd_ff *ff = container_of(work, struct snd_ff, rx_midi_work[0]); + + transmit_midi_msg(ff, 0); +} + +static void transmit_midi1_msg(struct work_struct *work) +{ + struct snd_ff *ff = container_of(work, struct snd_ff, rx_midi_work[1]); + + transmit_midi_msg(ff, 1); +} + +static void handle_midi_msg(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, + int generation, unsigned long long offset, + void *data, size_t length, void *callback_data) +{ + struct snd_ff *ff = callback_data; + __le32 *buf = data; + u32 quad; + u8 byte; + unsigned int index; + struct snd_rawmidi_substream *substream; + int i; + + fw_send_response(card, request, RCODE_COMPLETE); + + for (i = 0; i < length / 4; i++) { + quad = le32_to_cpu(buf[i]); + + /* Message in first port. */ + /* + * This value may represent the index of this unit when the same + * units are on the same IEEE 1394 bus. This driver doesn't use + * it. + */ + index = (quad >> 8) & 0xff; + if (index > 0) { + substream = ACCESS_ONCE(ff->tx_midi_substreams[0]); + if (substream != NULL) { + byte = quad & 0xff; + snd_rawmidi_receive(substream, &byte, 1); + } + } + + /* Message in second port. */ + index = (quad >> 24) & 0xff; + if (index > 0) { + substream = ACCESS_ONCE(ff->tx_midi_substreams[1]); + if (substream != NULL) { + byte = (quad >> 16) & 0xff; + snd_rawmidi_receive(substream, &byte, 1); + } + } + } +} + +static int allocate_own_address(struct snd_ff *ff, int i) +{ + struct fw_address_region midi_msg_region; + int err; + + ff->async_handler.length = SND_FF_MAXIMIM_MIDI_QUADS * 4; + ff->async_handler.address_callback = handle_midi_msg; + ff->async_handler.callback_data = ff; + + midi_msg_region.start = 0x000100000000ull * i; + midi_msg_region.end = midi_msg_region.start + ff->async_handler.length; + + err = fw_core_add_address_handler(&ff->async_handler, &midi_msg_region); + if (err >= 0) { + /* Controllers are allowed to register this region. */ + if (ff->async_handler.offset & 0x0000ffffffff) { + fw_core_remove_address_handler(&ff->async_handler); + err = -EAGAIN; + } + } + + return err; +} + +/* + * The configuration to start asynchronous transactions for MIDI messages is in + * 0x'0000'8010'051c. This register includes the other options, thus this driver + * doesn't touch it and leaves the decision to userspace. The userspace MUST add + * 0x04000000 to write transactions to the register to receive any MIDI + * messages. + * + * Here, I just describe MIDI-related offsets of the register, in little-endian + * order. + * + * Controllers are allowed to register higher 4 bytes of address to receive + * the transactions. The register is 0x'0000'8010'03f4. On the other hand, the + * controllers are not allowed to register lower 4 bytes of the address. They + * are forced to select from 4 options by writing corresponding bits to + * 0x'0000'8010'051c. + * + * The 3rd-6th bits in MSB of this register are used to indicate lower 4 bytes + * of address to which the device transferrs the transactions. + * - 6th: 0x'....'....'0000'0180 + * - 5th: 0x'....'....'0000'0100 + * - 4th: 0x'....'....'0000'0080 + * - 3rd: 0x'....'....'0000'0000 + * + * This driver configure 0x'....'....'0000'0000 for units to receive MIDI + * messages. 3rd bit of the register should be configured, however this driver + * deligates this task to user space applications due to a restriction that + * this register is write-only and the other bits have own effects. + * + * The 1st and 2nd bits in LSB of this register are used to cancel transferring + * asynchronous transactions. These two bits have the same effect. + * - 1st/2nd: cancel transferring + */ +int snd_ff_transaction_reregister(struct snd_ff *ff) +{ + struct fw_card *fw_card = fw_parent_device(ff->unit)->card; + u32 addr; + __le32 reg; + + /* + * Controllers are allowed to register its node ID and upper 2 byte of + * local address to listen asynchronous transactions. + */ + addr = (fw_card->node_id << 16) | (ff->async_handler.offset >> 32); + reg = cpu_to_le32(addr); + return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + ff->spec->protocol->midi_high_addr_reg, + ®, sizeof(reg), 0); +} + +int snd_ff_transaction_register(struct snd_ff *ff) +{ + int i, err; + + /* + * Allocate in Memory Space of IEC 13213, but lower 4 byte in LSB should + * be zero due to device specification. + */ + for (i = 0; i < 0xffff; i++) { + err = allocate_own_address(ff, i); + if (err != -EBUSY && err != -EAGAIN) + break; + } + if (err < 0) + return err; + + err = snd_ff_transaction_reregister(ff); + if (err < 0) + return err; + + INIT_WORK(&ff->rx_midi_work[0], transmit_midi0_msg); + INIT_WORK(&ff->rx_midi_work[1], transmit_midi1_msg); + + return 0; +} + +void snd_ff_transaction_unregister(struct snd_ff *ff) +{ + __le32 reg; + + if (ff->async_handler.callback_data == NULL) + return; + ff->async_handler.callback_data = NULL; + + /* Release higher 4 bytes of address. */ + reg = cpu_to_le32(0x00000000); + snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + ff->spec->protocol->midi_high_addr_reg, + ®, sizeof(reg), 0); + + fw_core_remove_address_handler(&ff->async_handler); +} diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c index 5c2bd92..4db630f 100644 --- a/sound/firewire/fireface/ff.c +++ b/sound/firewire/fireface/ff.c @@ -29,6 +29,8 @@ static void name_card(struct snd_ff *ff)
static void ff_free(struct snd_ff *ff) { + snd_ff_transaction_unregister(ff); + fw_unit_put(ff->unit);
mutex_destroy(&ff->mutex); @@ -53,6 +55,10 @@ static void do_registration(struct work_struct *work) if (err < 0) return;
+ err = snd_ff_transaction_register(ff); + if (err < 0) + goto error; + name_card(ff);
err = snd_card_register(ff->card); @@ -65,6 +71,7 @@ static void do_registration(struct work_struct *work)
return; error: + snd_ff_transaction_unregister(ff); snd_card_free(ff->card); dev_info(&ff->unit->device, "Sound card registration failed: %d\n", err); @@ -101,6 +108,8 @@ static void snd_ff_update(struct fw_unit *unit) /* Postpone a workqueue for deferred registration. */ if (!ff->registered) snd_fw_schedule_registration(unit, &ff->dwork); + + snd_ff_transaction_reregister(ff); }
static void snd_ff_remove(struct fw_unit *unit) diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index 7be0ea4..bac2e58 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -20,11 +20,16 @@
#include <sound/core.h> #include <sound/info.h> +#include <sound/rawmidi.h>
#include "../lib.h"
#define SND_FF_STREAM_MODES 3
+#define SND_FF_MAXIMIM_MIDI_QUADS 9 +#define SND_FF_IN_MIDI_PORTS 2 +#define SND_FF_OUT_MIDI_PORTS 2 + struct snd_ff_protocol; struct snd_ff_spec { const char *const name; @@ -47,6 +52,20 @@ struct snd_ff { struct delayed_work dwork;
const struct snd_ff_spec *spec; + + /* To handle MIDI tx. */ + struct snd_rawmidi_substream *tx_midi_substreams[SND_FF_IN_MIDI_PORTS]; + struct fw_address_handler async_handler; + + /* TO handle MIDI rx. */ + struct snd_rawmidi_substream *rx_midi_substreams[SND_FF_OUT_MIDI_PORTS]; + u8 running_status[SND_FF_OUT_MIDI_PORTS]; + __le32 msg_buf[SND_FF_OUT_MIDI_PORTS][SND_FF_MAXIMIM_MIDI_QUADS]; + struct work_struct rx_midi_work[SND_FF_OUT_MIDI_PORTS]; + struct fw_transaction transactions[SND_FF_OUT_MIDI_PORTS]; + ktime_t next_ktime[SND_FF_OUT_MIDI_PORTS]; + bool rx_midi_error[SND_FF_OUT_MIDI_PORTS]; + unsigned int rx_bytes[SND_FF_OUT_MIDI_PORTS]; };
enum snd_ff_clock_src { @@ -75,4 +94,8 @@ struct snd_ff_protocol { u64 midi_rx_port_1_reg; };
+int snd_ff_transaction_register(struct snd_ff *ff); +int snd_ff_transaction_reregister(struct snd_ff *ff); +void snd_ff_transaction_unregister(struct snd_ff *ff); + #endif
In previous commit, fireface driver supports unique transaction for MIDI feature. This commit adds MIDI functionality for userspace.
As I wrote in a followed commit, user space applications have some request from this driver. It should not touch a register to which units transmit MIDI messages. It should configure a register in which MIDI transmission is controlled.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/fireface/Makefile | 2 +- sound/firewire/fireface/ff-midi.c | 131 ++++++++++++++++++++++++++++++++++++++ sound/firewire/fireface/ff.c | 5 ++ sound/firewire/fireface/ff.h | 3 + 4 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/fireface/ff-midi.c
diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile index 864aacc..8e465e4 100644 --- a/sound/firewire/fireface/Makefile +++ b/sound/firewire/fireface/Makefile @@ -1,2 +1,2 @@ -snd-fireface-objs := ff.o ff-transaction.o +snd-fireface-objs := ff.o ff-transaction.o ff-midi.o obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o diff --git a/sound/firewire/fireface/ff-midi.c b/sound/firewire/fireface/ff-midi.c new file mode 100644 index 0000000..29ee0a7 --- /dev/null +++ b/sound/firewire/fireface/ff-midi.c @@ -0,0 +1,131 @@ +/* + * ff-midi.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + /* Do nothing. */ + return 0; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_ff *ff = substream->rmidi->private_data; + + /* Initialize internal status. */ + ff->running_status[substream->number] = 0; + ff->rx_midi_error[substream->number] = false; + + ACCESS_ONCE(ff->rx_midi_substreams[substream->number]) = substream; + + return 0; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + /* Do nothing. */ + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_ff *ff = substream->rmidi->private_data; + + cancel_work_sync(&ff->rx_midi_work[substream->number]); + ACCESS_ONCE(ff->rx_midi_substreams[substream->number]) = NULL; + + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_ff *ff = substream->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&ff->lock, flags); + + if (up) + ACCESS_ONCE(ff->tx_midi_substreams[substream->number]) = + substream; + else + ACCESS_ONCE(ff->tx_midi_substreams[substream->number]) = NULL; + + spin_unlock_irqrestore(&ff->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substream, + int up) +{ + struct snd_ff *ff = substream->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&ff->lock, flags); + + if (up || !ff->rx_midi_error[substream->number]) + schedule_work(&ff->rx_midi_work[substream->number]); + + spin_unlock_irqrestore(&ff->lock, flags); +} + +static struct snd_rawmidi_ops midi_capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, +}; + +static struct snd_rawmidi_ops midi_playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, +}; + +static void set_midi_substream_names(struct snd_rawmidi_str *stream, + const char *const name) +{ + struct snd_rawmidi_substream *substream; + + list_for_each_entry(substream, &stream->substreams, list) { + snprintf(substream->name, sizeof(substream->name), + "%s MIDI %d", name, substream->number + 1); + } +} + +int snd_ff_create_midi_devices(struct snd_ff *ff) +{ + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *stream; + int err; + + err = snd_rawmidi_new(ff->card, ff->card->driver, 0, + ff->spec->midi_out_ports, ff->spec->midi_in_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", ff->card->shortname); + rmidi->private_data = ff; + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &midi_capture_ops); + stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + set_midi_substream_names(stream, ff->card->shortname); + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &midi_playback_ops); + stream = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + set_midi_substream_names(stream, ff->card->shortname); + + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c index 4db630f..11d76b3 100644 --- a/sound/firewire/fireface/ff.c +++ b/sound/firewire/fireface/ff.c @@ -61,6 +61,10 @@ static void do_registration(struct work_struct *work)
name_card(ff);
+ err = snd_ff_create_midi_devices(ff); + if (err < 0) + goto error; + err = snd_card_register(ff->card); if (err < 0) goto error; @@ -91,6 +95,7 @@ static int snd_ff_probe(struct fw_unit *unit, dev_set_drvdata(&unit->device, ff);
mutex_init(&ff->mutex); + spin_lock_init(&ff->lock);
ff->spec = (const struct snd_ff_spec *)entry->driver_data;
diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index bac2e58..2944bde 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -47,6 +47,7 @@ struct snd_ff { struct snd_card *card; struct fw_unit *unit; struct mutex mutex; + spinlock_t lock;
bool registered; struct delayed_work dwork; @@ -98,4 +99,6 @@ int snd_ff_transaction_register(struct snd_ff *ff); int snd_ff_transaction_reregister(struct snd_ff *ff); void snd_ff_transaction_unregister(struct snd_ff *ff);
+int snd_ff_create_midi_devices(struct snd_ff *ff); + #endif
Drivers retrieve the state and configuration of clock by read transactions.
This commit adds proc node to dump the information for debugging.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/fireface/Makefile | 2 +- sound/firewire/fireface/ff-proc.c | 63 +++++++++++++++++++++++++++++++++++++++ sound/firewire/fireface/ff.c | 2 ++ sound/firewire/fireface/ff.h | 2 ++ 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/fireface/ff-proc.c
diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile index 8e465e4..e88ff9e 100644 --- a/sound/firewire/fireface/Makefile +++ b/sound/firewire/fireface/Makefile @@ -1,2 +1,2 @@ -snd-fireface-objs := ff.o ff-transaction.o ff-midi.o +snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o diff --git a/sound/firewire/fireface/ff-proc.c b/sound/firewire/fireface/ff-proc.c new file mode 100644 index 0000000..69441d1 --- /dev/null +++ b/sound/firewire/fireface/ff-proc.c @@ -0,0 +1,63 @@ +/* + * ff-proc.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./ff.h" + +static void proc_dump_clock_config(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ff *ff = entry->private_data; + + ff->spec->protocol->dump_clock_config(ff, buffer); +} + +static void proc_dump_sync_status(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_ff *ff = entry->private_data; + + ff->spec->protocol->dump_sync_status(ff, buffer); +} + +static void add_node(struct snd_ff *ff, struct snd_info_entry *root, + const char *name, + void (*op)(struct snd_info_entry *e, + struct snd_info_buffer *b)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(ff->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, ff, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_ff_proc_init(struct snd_ff *ff) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(ff->card, "firewire", + ff->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | S_IRUGO | S_IXUGO; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(ff, root, "clock-config", proc_dump_clock_config); + add_node(ff, root, "sync-status", proc_dump_sync_status); +} diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c index 11d76b3..22e7bcb 100644 --- a/sound/firewire/fireface/ff.c +++ b/sound/firewire/fireface/ff.c @@ -61,6 +61,8 @@ static void do_registration(struct work_struct *work)
name_card(ff);
+ snd_ff_proc_init(ff); + err = snd_ff_create_midi_devices(ff); if (err < 0) goto error; diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index 2944bde..2d1fab2 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -99,6 +99,8 @@ int snd_ff_transaction_register(struct snd_ff *ff); int snd_ff_transaction_reregister(struct snd_ff *ff); void snd_ff_transaction_unregister(struct snd_ff *ff);
+void snd_ff_proc_init(struct snd_ff *ff); + int snd_ff_create_midi_devices(struct snd_ff *ff);
#endif
As long as invesigating Fireface 400, IEC 61883-1/6 is not applied to its packet streaming protocol. Remarks of the specific protocol are: * Each packet doesn't include CIP headers. * 64,0 and 128,0 kHz are supported. * The device doesn't transmit 8,000 packets per second. * 0, 1, 2, 3 are used as tag for rx isochronous packets, however 0 is used for tx isochronous packets.
On the other hand, there's a common feature. The number of data blocks transferred in a second is the same as sampling transmission frequency. Current AMDTP stream layer already has a method to calculate it and this driver can utilize it for rx packets.
This commit adds support for the transferring protocol. CIP_NO_HEADERS flag is newly added. When this flag is set: * Both of 0 (without CIP header) and 1 (with CIP header) are used as tag to handle incoming isochronous packet. * 0 (without CIP header) is used as tag to transfer outgoing isochronous packet. * Skip CIP header evaluation. * Use proper way to calculate the quadlets of isochronous packet payload.
In ALSA userspace interface, 128.0 kHz is not supported, and the AMDTP stream layer doesn't support 64.0 kHz. These modes are dropped.
The sequence of rx packet has a remarkable quirk about tag. This will be described in later commits.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/amdtp-stream.c | 84 ++++++++++++++++++++++++++++++++++++++++--- sound/firewire/amdtp-stream.h | 2 ++ 2 files changed, 81 insertions(+), 5 deletions(-)
diff --git a/sound/firewire/amdtp-stream.c b/sound/firewire/amdtp-stream.c index 112ad03..74c0ba9 100644 --- a/sound/firewire/amdtp-stream.c +++ b/sound/firewire/amdtp-stream.c @@ -27,6 +27,7 @@
/* isochronous header parameters */ #define ISO_DATA_LENGTH_SHIFT 16 +#define TAG_NO_CIP_HEADER 0 #define TAG_CIP 1
/* common isochronous packet header parameters */ @@ -234,11 +235,15 @@ EXPORT_SYMBOL(amdtp_stream_set_parameters); unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s) { unsigned int multiplier = 1; + unsigned int header_size = 0;
if (s->flags & CIP_JUMBO_PAYLOAD) multiplier = 5; + if (!(s->flags & CIP_NO_HEADER)) + header_size = 8;
- return 8 + s->syt_interval * s->data_block_quadlets * 4 * multiplier; + return header_size + + s->syt_interval * s->data_block_quadlets * 4 * multiplier; } EXPORT_SYMBOL(amdtp_stream_get_max_payload);
@@ -380,7 +385,10 @@ static int queue_packet(struct amdtp_stream *s, unsigned int header_length, goto end;
p.interrupt = IS_ALIGNED(s->packet_index + 1, INTERRUPT_INTERVAL); - p.tag = TAG_CIP; + if (s->flags & CIP_NO_HEADER) + p.tag = TAG_NO_CIP_HEADER; + else + p.tag = TAG_CIP; p.header_length = header_length; if (payload_length > 0) p.payload_length = payload_length; @@ -457,6 +465,34 @@ static int handle_out_packet(struct amdtp_stream *s, unsigned int cycle, return 0; }
+static int handle_out_packet_without_header(struct amdtp_stream *s, + unsigned int cycle, unsigned int index) +{ + __be32 *buffer; + unsigned int syt; + unsigned int data_blocks; + unsigned int payload_length; + unsigned int pcm_frames; + struct snd_pcm_substream *pcm; + + buffer = s->buffer.packets[s->packet_index].buffer; + syt = calculate_syt(s, cycle); + data_blocks = calculate_data_blocks(s, syt); + pcm_frames = s->process_data_blocks(s, buffer, data_blocks, &syt); + s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; + + payload_length = data_blocks * 4 * s->data_block_quadlets; + if (queue_out_packet(s, payload_length) < 0) + return -EIO; + + pcm = ACCESS_ONCE(s->pcm); + if (pcm && pcm_frames > 0) + update_pcm_pointers(s, pcm, pcm_frames); + + /* No need to return the number of handled data blocks. */ + return 0; +} + static int handle_in_packet(struct amdtp_stream *s, unsigned int payload_quadlets, unsigned int cycle, unsigned int index) @@ -572,6 +608,30 @@ static int handle_in_packet(struct amdtp_stream *s, return 0; }
+static int handle_in_packet_without_header(struct amdtp_stream *s, + unsigned int payload_quadlets, unsigned int cycle, + unsigned int index) +{ + __be32 *buffer; + unsigned int data_blocks; + struct snd_pcm_substream *pcm; + unsigned int pcm_frames; + + buffer = s->buffer.packets[s->packet_index].buffer; + data_blocks = payload_quadlets / s->data_block_quadlets; + pcm_frames = s->process_data_blocks(s, buffer, data_blocks, NULL); + s->data_block_counter = (s->data_block_counter + data_blocks) & 0xff; + + if (queue_in_packet(s) < 0) + return -EIO; + + pcm = ACCESS_ONCE(s->pcm); + if (pcm && pcm_frames > 0) + update_pcm_pointers(s, pcm, pcm_frames); + + return 0; +} + /* * In CYCLE_TIMER register of IEEE 1394, 7 bits are used to represent second. On * the other hand, in DMA descriptors of 1394 OHCI, 3 bits are used to represent @@ -603,6 +663,8 @@ static void out_stream_callback(struct fw_iso_context *context, u32 tstamp, { struct amdtp_stream *s = private_data; unsigned int i, packets = header_length / 4; + int (*handler)(struct amdtp_stream *s, unsigned int cycle, + unsigned int index); u32 cycle;
if (s->packet_index < 0) @@ -613,9 +675,14 @@ static void out_stream_callback(struct fw_iso_context *context, u32 tstamp, /* Align to actual cycle count for the last packet. */ cycle = increment_cycle_count(cycle, QUEUE_LENGTH - packets);
+ if (s->flags & CIP_NO_HEADER) + handler = handle_out_packet_without_header; + else + handler = handle_out_packet; + for (i = 0; i < packets; ++i) { cycle = increment_cycle_count(cycle, 1); - if (handle_out_packet(s, cycle, i) < 0) { + if (handler(s, cycle, i) < 0) { s->packet_index = -1; amdtp_stream_pcm_abort(s); return; @@ -633,6 +700,8 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp, unsigned int i, packets; unsigned int payload_quadlets, max_payload_quadlets; __be32 *headers = header; + int (*handler)(struct amdtp_stream *s, unsigned int payload_quadlets, + unsigned int cycle, unsigned int index); u32 cycle;
if (s->packet_index < 0) @@ -649,6 +718,11 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp, /* For buffer-over-run prevention. */ max_payload_quadlets = amdtp_stream_get_max_payload(s) / 4;
+ if (s->flags & CIP_NO_HEADER) + handler = handle_in_packet_without_header; + else + handler = handle_in_packet; + for (i = 0; i < packets; i++) { cycle = increment_cycle_count(cycle, 1);
@@ -662,7 +736,7 @@ static void in_stream_callback(struct fw_iso_context *context, u32 tstamp, break; }
- if (handle_in_packet(s, payload_quadlets, cycle, i) < 0) + if (handler(s, payload_quadlets, cycle, i) < 0) break; }
@@ -793,7 +867,7 @@ int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed)
/* NOTE: TAG1 matches CIP. This just affects in stream. */ tag = FW_ISO_CONTEXT_MATCH_TAG1; - if (s->flags & CIP_EMPTY_WITH_TAG0) + if ((s->flags & CIP_EMPTY_WITH_TAG0) || (s->flags & CIP_NO_HEADER)) tag |= FW_ISO_CONTEXT_MATCH_TAG0;
s->callbacked = false; diff --git a/sound/firewire/amdtp-stream.h b/sound/firewire/amdtp-stream.h index a31dfd8..b155955 100644 --- a/sound/firewire/amdtp-stream.h +++ b/sound/firewire/amdtp-stream.h @@ -31,6 +31,7 @@ * allows 5 times as large as IEC 61883-6 defines. * @CIP_HEADER_WITHOUT_EOH: Only for in-stream. CIP Header doesn't include * valid EOH. + * @CIP_NO_HEADERS: a lack of headers in packets */ enum cip_flags { CIP_NONBLOCKING = 0x00, @@ -42,6 +43,7 @@ enum cip_flags { CIP_EMPTY_HAS_WRONG_DBC = 0x20, CIP_JUMBO_PAYLOAD = 0x40, CIP_HEADER_WITHOUT_EOH = 0x80, + CIP_NO_HEADER = 0x100, };
/**
As long as investigating Fireface 400, format of payload of each isochronous packet is not IEC 61883-1/6. The remarkable points of the format are: * The payload consists of some data channels of quadlet size. * Each data channels includes data aligned to little endian order. * One data channel consists of two parts; 8 bit ancillary field and 24 bit PCM frame.
Especially, rx/tx packets includes no CIP headers and different way to check packet discontinuity. For tx packet, the ancillary field is used for counter. However, the way of counting is different depending on positions of data channels. At 44.1 kHz, ancillary field in: * 1st/6th/9th/10th/14th/17th data channels: not used for this purpose. * 2nd/18th data channels: incremented every data block (0x00-0xff). * 3rd/4th/5th/11th/12th/13th data channels: incremented every 256 data blocks (0x00-0x07). * 7th/8th/15th/16th data channels: incremented per the number of data blocks in a packet. The increment can occur inner a packet (0x00-0xff).
For tx packet, tag of each isochronous packet is used for this purpose. The value of tag cyclically changes between 0, 1, 2 and 3 in this order. The interval is different depending on sampling transmission frequency. At 44.1/48.0 kHz, it's 256 data blocks. At 88.2 kHz, it's 96 data blocks.
The number of data blocks in tx packet is exactly the same as SYT_INTERVAL. There's no empty packet or no-data packet, thus the throughput is not 8,000 packets per sec. On the other hand, the one in rx packet is 8,000 packets per sec, thus the number of data blocks is different between each packet, depending on sampling transmission frequency: * 44.1 kHz: 5 or 6 * 48.0 kHz: 5 or 6 or 7 * 88.2 kHz: 10 or 11 or 12
This commit adds data processing layer to satisfy the above specification in a policy of 'best effort'. Although PCM frames are handled for intermediate buffer to user space, the ancillary data is not handled at all to reduce CPU usage, thus counter is not checked. The tag is handled in packet streaming layer and currently 0 is always used. Furthermore, the packet streaming layer is responsible for calculation of the number of data blocks for each packet, thus it's not exactly the same sequence of the above observation.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/fireface/Makefile | 2 +- sound/firewire/fireface/amdtp-ff.c | 155 +++++++++++++++++++++++++++++++++++++ sound/firewire/fireface/ff.h | 8 ++ 3 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/fireface/amdtp-ff.c
diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile index e88ff9e..e06e9da 100644 --- a/sound/firewire/fireface/Makefile +++ b/sound/firewire/fireface/Makefile @@ -1,2 +1,2 @@ -snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o +snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o diff --git a/sound/firewire/fireface/amdtp-ff.c b/sound/firewire/fireface/amdtp-ff.c new file mode 100644 index 0000000..780da9d --- /dev/null +++ b/sound/firewire/fireface/amdtp-ff.c @@ -0,0 +1,155 @@ +/* + * amdtp-ff.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <sound/pcm.h> +#include "ff.h" + +struct amdtp_ff { + unsigned int pcm_channels; +}; + +int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels) +{ + struct amdtp_ff *p = s->protocol; + unsigned int data_channels; + + if (amdtp_stream_running(s)) + return -EBUSY; + + p->pcm_channels = pcm_channels; + data_channels = pcm_channels; + + return amdtp_stream_set_parameters(s, rate, data_channels); +} + +static void write_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __le32 *buffer, unsigned int frames) +{ + struct amdtp_ff *p = s->protocol; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, i, c; + const u32 *src; + + channels = p->pcm_channels; + src = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + buffer[c] = cpu_to_le32(*src); + src++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + src = (void *)runtime->dma_area; + } +} + +static void read_pcm_s32(struct amdtp_stream *s, + struct snd_pcm_substream *pcm, + __le32 *buffer, unsigned int frames) +{ + struct amdtp_ff *p = s->protocol; + struct snd_pcm_runtime *runtime = pcm->runtime; + unsigned int channels, remaining_frames, i, c; + u32 *dst; + + channels = p->pcm_channels; + dst = (void *)runtime->dma_area + + frames_to_bytes(runtime, s->pcm_buffer_pointer); + remaining_frames = runtime->buffer_size - s->pcm_buffer_pointer; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) { + *dst = le32_to_cpu(buffer[c]) & 0xffffff00; + dst++; + } + buffer += s->data_block_quadlets; + if (--remaining_frames == 0) + dst = (void *)runtime->dma_area; + } +} + +static void write_pcm_silence(struct amdtp_stream *s, + __le32 *buffer, unsigned int frames) +{ + struct amdtp_ff *p = s->protocol; + unsigned int i, c, channels = p->pcm_channels; + + for (i = 0; i < frames; ++i) { + for (c = 0; c < channels; ++c) + buffer[c] = cpu_to_le32(0x00000000); + buffer += s->data_block_quadlets; + } +} + +int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime) +{ + int err; + + err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); + if (err < 0) + return err; + + return amdtp_stream_add_pcm_hw_constraints(s, runtime); +} + +static unsigned int process_rx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt) +{ + struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm); + unsigned int pcm_frames; + + if (pcm) { + write_pcm_s32(s, pcm, (__le32 *)buffer, data_blocks); + pcm_frames = data_blocks; + } else { + write_pcm_silence(s, (__le32 *)buffer, data_blocks); + pcm_frames = 0; + } + + return pcm_frames; +} + +static unsigned int process_tx_data_blocks(struct amdtp_stream *s, + __be32 *buffer, + unsigned int data_blocks, + unsigned int *syt) +{ + struct snd_pcm_substream *pcm = ACCESS_ONCE(s->pcm); + unsigned int pcm_frames; + + if (pcm) { + read_pcm_s32(s, pcm, (__le32 *)buffer, data_blocks); + pcm_frames = data_blocks; + } else { + pcm_frames = 0; + } + + return pcm_frames; +} + +int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir) +{ + amdtp_stream_process_data_blocks_t process_data_blocks; + + if (dir == AMDTP_IN_STREAM) + process_data_blocks = process_tx_data_blocks; + else + process_data_blocks = process_rx_data_blocks; + + return amdtp_stream_init(s, unit, dir, CIP_NO_HEADER, 0, + process_data_blocks, sizeof(struct amdtp_ff)); +} diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index 2d1fab2..fa7242f 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -23,6 +23,7 @@ #include <sound/rawmidi.h>
#include "../lib.h" +#include "../amdtp-stream.h"
#define SND_FF_STREAM_MODES 3
@@ -99,6 +100,13 @@ int snd_ff_transaction_register(struct snd_ff *ff); int snd_ff_transaction_reregister(struct snd_ff *ff); void snd_ff_transaction_unregister(struct snd_ff *ff);
+int amdtp_ff_set_parameters(struct amdtp_stream *s, unsigned int rate, + unsigned int pcm_channels); +int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s, + struct snd_pcm_runtime *runtime); +int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit, + enum amdtp_stream_direction dir); + void snd_ff_proc_init(struct snd_ff *ff);
int snd_ff_create_midi_devices(struct snd_ff *ff);
As long as investigating Fireface 400, there're three modes depending on sampling transmission frequency. The number of data channels in each data block is different depending on the mode. The set of available data channels for each mode might be different for each protocol and model.
The length of registers for the number of isochronous channel is just three bits, therefore 0-7ch are available.
When bus reset occurs on IEEE 1394 bus, the device discontinues to transmit packets. This commit aborts PCM substreams at bus reset handler.
This commit adds management functionality for the above pacekt streaming.
The device manages its sampling clock independently of sampling transmission frequency against IEC 61883-6. Thus, it's a lower cost to change the sampling transmission frequency, while data fetch between streaming layer and DSP require larger buffer for resampling. As a result, device latency might tend to be larger than ASICs for IEC 61883-1/6 such as DM1000/DM1100/DM1500 (BeBoB), DiceII/TCD2210/TCD2220/TCD3070 and OXFW970/971.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/fireface/Makefile | 3 +- sound/firewire/fireface/ff-stream.c | 243 ++++++++++++++++++++++++++++++++++++ sound/firewire/fireface/ff.c | 9 ++ sound/firewire/fireface/ff.h | 13 ++ 4 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/fireface/ff-stream.c
diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile index e06e9da..b772fdc 100644 --- a/sound/firewire/fireface/Makefile +++ b/sound/firewire/fireface/Makefile @@ -1,2 +1,3 @@ -snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o +snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \ + ff-stream.o obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o diff --git a/sound/firewire/fireface/ff-stream.c b/sound/firewire/fireface/ff-stream.c new file mode 100644 index 0000000..0ef6177 --- /dev/null +++ b/sound/firewire/fireface/ff-stream.c @@ -0,0 +1,243 @@ +/* + * ff-stream.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +#define CALLBACK_TIMEOUT_MS 200 + +static int get_rate_mode(unsigned int rate, unsigned int *mode) +{ + int i; + + for (i = 0; i < CIP_SFC_COUNT; i++) { + if (amdtp_rate_table[i] == rate) + break; + } + + if (i == CIP_SFC_COUNT) + return -EINVAL; + + *mode = ((int)i - 1) / 2; + + return 0; +} + +/* + * Fireface 400 manages isochronous channel number in 3 bit field. Therefore, + * we can allocate between 0 and 7 channel. + */ +static int keep_resources(struct snd_ff *ff, unsigned int rate) +{ + int mode; + int err; + + err = get_rate_mode(rate, &mode); + if (err < 0) + return err; + + /* Keep resources for in-stream. */ + err = amdtp_ff_set_parameters(&ff->tx_stream, rate, + ff->spec->pcm_capture_channels[mode]); + if (err < 0) + return err; + ff->tx_resources.channels_mask = 0x00000000000000ffuLL; + err = fw_iso_resources_allocate(&ff->tx_resources, + amdtp_stream_get_max_payload(&ff->tx_stream), + fw_parent_device(ff->unit)->max_speed); + if (err < 0) + return err; + + /* Keep resources for out-stream. */ + err = amdtp_ff_set_parameters(&ff->rx_stream, rate, + ff->spec->pcm_playback_channels[mode]); + if (err < 0) + return err; + ff->rx_resources.channels_mask = 0x00000000000000ffuLL; + err = fw_iso_resources_allocate(&ff->rx_resources, + amdtp_stream_get_max_payload(&ff->rx_stream), + fw_parent_device(ff->unit)->max_speed); + if (err < 0) + fw_iso_resources_free(&ff->tx_resources); + + return err; +} + +static void release_resources(struct snd_ff *ff) +{ + fw_iso_resources_free(&ff->tx_resources); + fw_iso_resources_free(&ff->rx_resources); +} + +static inline void finish_session(struct snd_ff *ff) +{ + ff->spec->protocol->finish_session(ff); + ff->spec->protocol->switch_fetching_mode(ff, false); +} + +static int init_stream(struct snd_ff *ff, enum amdtp_stream_direction dir) +{ + int err; + struct fw_iso_resources *resources; + struct amdtp_stream *stream; + + if (dir == AMDTP_IN_STREAM) { + resources = &ff->tx_resources; + stream = &ff->tx_stream; + } else { + resources = &ff->rx_resources; + stream = &ff->rx_stream; + } + + err = fw_iso_resources_init(resources, ff->unit); + if (err < 0) + return err; + + err = amdtp_ff_init(stream, ff->unit, dir); + if (err < 0) + fw_iso_resources_destroy(resources); + + return err; +} + +static void destroy_stream(struct snd_ff *ff, enum amdtp_stream_direction dir) +{ + if (dir == AMDTP_IN_STREAM) { + amdtp_stream_destroy(&ff->tx_stream); + fw_iso_resources_destroy(&ff->tx_resources); + } else { + amdtp_stream_destroy(&ff->rx_stream); + fw_iso_resources_destroy(&ff->rx_resources); + } +} + +int snd_ff_stream_init_duplex(struct snd_ff *ff) +{ + int err; + + err = init_stream(ff, AMDTP_OUT_STREAM); + if (err < 0) + goto end; + + err = init_stream(ff, AMDTP_IN_STREAM); + if (err < 0) + destroy_stream(ff, AMDTP_OUT_STREAM); +end: + return err; +} + +/* + * This function should be called before starting streams or after stopping + * streams. + */ +void snd_ff_stream_destroy_duplex(struct snd_ff *ff) +{ + destroy_stream(ff, AMDTP_IN_STREAM); + destroy_stream(ff, AMDTP_OUT_STREAM); +} + +int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate) +{ + unsigned int curr_rate; + enum snd_ff_clock_src src; + int err; + + if (ff->substreams_counter == 0) + return 0; + + err = ff->spec->protocol->get_clock(ff, &curr_rate, &src); + if (err < 0) + return err; + if (curr_rate != rate || + amdtp_streaming_error(&ff->tx_stream) || + amdtp_streaming_error(&ff->rx_stream)) { + finish_session(ff); + + amdtp_stream_stop(&ff->tx_stream); + amdtp_stream_stop(&ff->rx_stream); + + release_resources(ff); + } + + /* + * Regardless of current source of clock signal, drivers transfer some + * packets. Then, the device transfers packets. + */ + if (!amdtp_stream_running(&ff->rx_stream)) { + err = keep_resources(ff, rate); + if (err < 0) + goto error; + + err = ff->spec->protocol->begin_session(ff, rate); + if (err < 0) + goto error; + + err = amdtp_stream_start(&ff->rx_stream, + ff->rx_resources.channel, + fw_parent_device(ff->unit)->max_speed); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&ff->rx_stream, + CALLBACK_TIMEOUT_MS)) { + err = -ETIMEDOUT; + goto error; + } + + err = ff->spec->protocol->switch_fetching_mode(ff, true); + if (err < 0) + goto error; + } + + if (!amdtp_stream_running(&ff->tx_stream)) { + err = amdtp_stream_start(&ff->tx_stream, + ff->tx_resources.channel, + fw_parent_device(ff->unit)->max_speed); + if (err < 0) + goto error; + + if (!amdtp_stream_wait_callback(&ff->tx_stream, + CALLBACK_TIMEOUT_MS)) { + err = -ETIMEDOUT; + goto error; + } + } + + return 0; +error: + amdtp_stream_stop(&ff->tx_stream); + amdtp_stream_stop(&ff->rx_stream); + + finish_session(ff); + release_resources(ff); + + return err; +} + +void snd_ff_stream_stop_duplex(struct snd_ff *ff) +{ + if (ff->substreams_counter > 0) + return; + + amdtp_stream_stop(&ff->tx_stream); + amdtp_stream_stop(&ff->rx_stream); + finish_session(ff); + release_resources(ff); +} + +void snd_ff_stream_update_duplex(struct snd_ff *ff) +{ + /* The device discontinue to transfer packets. */ + amdtp_stream_pcm_abort(&ff->tx_stream); + amdtp_stream_stop(&ff->tx_stream); + + amdtp_stream_pcm_abort(&ff->rx_stream); + amdtp_stream_stop(&ff->rx_stream); + + fw_iso_resources_update(&ff->tx_resources); + fw_iso_resources_update(&ff->rx_resources); +} diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c index 22e7bcb..6bdbebd 100644 --- a/sound/firewire/fireface/ff.c +++ b/sound/firewire/fireface/ff.c @@ -29,6 +29,7 @@ static void name_card(struct snd_ff *ff)
static void ff_free(struct snd_ff *ff) { + snd_ff_stream_destroy_duplex(ff); snd_ff_transaction_unregister(ff);
fw_unit_put(ff->unit); @@ -61,6 +62,10 @@ static void do_registration(struct work_struct *work)
name_card(ff);
+ err = snd_ff_stream_init_duplex(ff); + if (err < 0) + goto error; + snd_ff_proc_init(ff);
err = snd_ff_create_midi_devices(ff); @@ -78,6 +83,7 @@ static void do_registration(struct work_struct *work) return; error: snd_ff_transaction_unregister(ff); + snd_ff_stream_destroy_duplex(ff); snd_card_free(ff->card); dev_info(&ff->unit->device, "Sound card registration failed: %d\n", err); @@ -117,6 +123,9 @@ static void snd_ff_update(struct fw_unit *unit) snd_fw_schedule_registration(unit, &ff->dwork);
snd_ff_transaction_reregister(ff); + + if (ff->registered) + snd_ff_stream_update_duplex(ff); }
static void snd_ff_remove(struct fw_unit *unit) diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index fa7242f..6599c11 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -24,6 +24,7 @@
#include "../lib.h" #include "../amdtp-stream.h" +#include "../iso-resources.h"
#define SND_FF_STREAM_MODES 3
@@ -68,6 +69,12 @@ struct snd_ff { ktime_t next_ktime[SND_FF_OUT_MIDI_PORTS]; bool rx_midi_error[SND_FF_OUT_MIDI_PORTS]; unsigned int rx_bytes[SND_FF_OUT_MIDI_PORTS]; + + unsigned int substreams_counter; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + struct fw_iso_resources tx_resources; + struct fw_iso_resources rx_resources; };
enum snd_ff_clock_src { @@ -107,6 +114,12 @@ int amdtp_ff_add_pcm_hw_constraints(struct amdtp_stream *s, int amdtp_ff_init(struct amdtp_stream *s, struct fw_unit *unit, enum amdtp_stream_direction dir);
+int snd_ff_stream_init_duplex(struct snd_ff *ff); +void snd_ff_stream_destroy_duplex(struct snd_ff *ff); +int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate); +void snd_ff_stream_stop_duplex(struct snd_ff *ff); +void snd_ff_stream_update_duplex(struct snd_ff *ff); + void snd_ff_proc_init(struct snd_ff *ff);
int snd_ff_create_midi_devices(struct snd_ff *ff);
This commit adds PCM functionality to transmit/receive PCM samples on isochronous packet streaming. This commit enables userspace applications to start/stop packet streaming via ALSA PCM interface.
Sampling rate requested by applications is used as sampling transmission frequency of AMDTP packet streaming. As I described in followed commits, units in this series manages sampling clock independently of sampling transmission frequency, and they supports resampling between their packet streaming/data block processing layer and sampling data processing layer. This commit take this driver to utilize these features for usability.
When internal clock is selected as source signal of sampling clock, this driver allows user space applications to start PCM substreams at any rate supported by packet streaming engine as sampling transmission frequency. In this case, this driver expects units to perform resampling PCM frames for rx/tx packets when sampling rate and sampling transmission frequency are mismatched. This is for daily use cases.
When any external clock is selected as the source signal, this driver gets configured sampling rate from units, then restricts available sampling rate to the rate for PCM applications. This is for studio use cases.
Models in this series support 64.0/128.0 kHz of sampling rate, however these frequencies are not supported in IEC 61883-6 as sampling transmission frequency. Therefore, Packet streaming engine of ALSA firewire stack can't handle them. When units are configured to use any external clock as source signal of sampling clock and one of these rate is configured as rate of the sampling clock, this driver returns EIO to user space applications.
Anyway, this driver doesn't voluntarily configure parameters of sampling clock because it's not a part of packet streaming functionality. It's better for users to work with appropriate user space implementations to configure the parameters in advance of usage.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/fireface/Makefile | 2 +- sound/firewire/fireface/ff-pcm.c | 390 +++++++++++++++++++++++++++++++++++++++ sound/firewire/fireface/ff.c | 4 + sound/firewire/fireface/ff.h | 4 + 4 files changed, 399 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/fireface/ff-pcm.c
diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile index b772fdc..e626938 100644 --- a/sound/firewire/fireface/Makefile +++ b/sound/firewire/fireface/Makefile @@ -1,3 +1,3 @@ snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \ - ff-stream.o + ff-stream.o ff-pcm.o obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o diff --git a/sound/firewire/fireface/ff-pcm.c b/sound/firewire/fireface/ff-pcm.c new file mode 100644 index 0000000..c2a7c3a --- /dev/null +++ b/sound/firewire/fireface/ff-pcm.c @@ -0,0 +1,390 @@ +/* + * ff-pcm.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "ff.h" + +static inline unsigned int get_multiplier_mode_with_index(unsigned int index) +{ + return ((int)index - 1) / 2; +} + +static int hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + const unsigned int *pcm_channels = rule->private; + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, mode; + + for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (!snd_interval_test(c, pcm_channels[mode])) + continue; + + t.min = min(t.min, amdtp_rate_table[i]); + t.max = max(t.max, amdtp_rate_table[i]); + } + + return snd_interval_refine(r, &t); +} + +static int hw_rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + const unsigned int *pcm_channels = rule->private; + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i, mode; + + for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) { + mode = get_multiplier_mode_with_index(i); + if (!snd_interval_test(r, amdtp_rate_table[i])) + continue; + + t.min = min(t.min, pcm_channels[mode]); + t.max = max(t.max, pcm_channels[mode]); + } + + return snd_interval_refine(c, &t); +} + +static void limit_channels_and_rates(struct snd_pcm_hardware *hw, + const unsigned int *pcm_channels) +{ + unsigned int mode; + unsigned int rate, channels; + int i; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + hw->rate_min = UINT_MAX; + hw->rate_max = 0; + + for (i = 0; i < ARRAY_SIZE(amdtp_rate_table); i++) { + mode = get_multiplier_mode_with_index(i); + + channels = pcm_channels[mode]; + if (pcm_channels[mode] == 0) + continue; + hw->channels_min = min(hw->channels_min, channels); + hw->channels_max = max(hw->channels_max, channels); + + rate = amdtp_rate_table[i]; + hw->rates |= snd_pcm_rate_to_rate_bit(rate); + hw->rate_min = min(hw->rate_min, rate); + hw->rate_max = max(hw->rate_max, rate); + } +} + +static void limit_period_and_buffer(struct snd_pcm_hardware *hw) +{ + hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */ + hw->periods_max = UINT_MAX; + + hw->period_bytes_min = 4 * hw->channels_max; /* bytes for a frame */ + + /* Just to prevent from allocating much pages. */ + hw->period_bytes_max = hw->period_bytes_min * 2048; + hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min; +} + +static int pcm_init_hw_params(struct snd_ff *ff, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct amdtp_stream *s; + const unsigned int *pcm_channels; + int err; + + runtime->hw.info = SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + s = &ff->tx_stream; + pcm_channels = ff->spec->pcm_capture_channels; + } else { + runtime->hw.formats = SNDRV_PCM_FMTBIT_S32; + s = &ff->rx_stream; + pcm_channels = ff->spec->pcm_playback_channels; + } + + /* limit rates */ + limit_channels_and_rates(&runtime->hw, pcm_channels); + limit_period_and_buffer(&runtime->hw); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, (void *)pcm_channels, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + return err; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, (void *)pcm_channels, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + return err; + + return amdtp_ff_add_pcm_hw_constraints(s, runtime); +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + unsigned int rate; + enum snd_ff_clock_src src; + int i, err; + + err = pcm_init_hw_params(ff, substream); + if (err < 0) + return err; + + err = ff->spec->protocol->get_clock(ff, &rate, &src); + if (err < 0) + return err; + + if (src != SND_FF_CLOCK_SRC_INTERNAL || + amdtp_stream_pcm_running(&ff->rx_stream) || + amdtp_stream_pcm_running(&ff->tx_stream)) { + for (i = 0; i < CIP_SFC_COUNT; ++i) { + if (amdtp_rate_table[i] == rate) + break; + } + /* + * The unit is configured at sampling frequency which packet + * streaming engine can't support. + */ + if (i >= CIP_SFC_COUNT) + return -EIO; + + substream->runtime->hw.rate_min = rate; + substream->runtime->hw.rate_max = rate; + } + + snd_pcm_set_sync(substream); + + return 0; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int pcm_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_ff *ff = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&ff->mutex); + ff->substreams_counter++; + mutex_unlock(&ff->mutex); + } + + return 0; +} + +static int pcm_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_ff *ff = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&ff->mutex); + ff->substreams_counter++; + mutex_unlock(&ff->mutex); + } + + return 0; +} + +static int pcm_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + + mutex_lock(&ff->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + ff->substreams_counter--; + + snd_ff_stream_stop_duplex(ff); + + mutex_unlock(&ff->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + + mutex_lock(&ff->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + ff->substreams_counter--; + + snd_ff_stream_stop_duplex(ff); + + mutex_unlock(&ff->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&ff->mutex); + + err = snd_ff_stream_start_duplex(ff, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&ff->tx_stream); + + mutex_unlock(&ff->mutex); + + return err; +} + +static int pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_ff *ff = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&ff->mutex); + + err = snd_ff_stream_start_duplex(ff, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&ff->rx_stream); + + mutex_unlock(&ff->mutex); + + return err; +} + +static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_ff *ff = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&ff->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&ff->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_ff *ff = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&ff->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&ff->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_ff *ff = sbstrm->private_data; + + return amdtp_stream_pcm_pointer(&ff->tx_stream); +} + +static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_ff *ff = sbstrm->private_data; + + return amdtp_stream_pcm_pointer(&ff->rx_stream); +} + +static struct snd_pcm_ops pcm_capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_capture_hw_params, + .hw_free = pcm_capture_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +static struct snd_pcm_ops pcm_playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_playback_hw_params, + .hw_free = pcm_playback_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, +}; + +int snd_ff_create_pcm_devices(struct snd_ff *ff) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(ff->card, ff->card->driver, 0, 1, 1, &pcm); + if (err < 0) + return err; + + pcm->private_data = ff; + snprintf(pcm->name, sizeof(pcm->name), + "%s PCM", ff->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops); + + return 0; +} diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c index 6bdbebd..ff62d16 100644 --- a/sound/firewire/fireface/ff.c +++ b/sound/firewire/fireface/ff.c @@ -72,6 +72,10 @@ static void do_registration(struct work_struct *work) if (err < 0) goto error;
+ err = snd_ff_create_pcm_devices(ff); + if (err < 0) + goto error; + err = snd_card_register(ff->card); if (err < 0) goto error; diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index 6599c11..0d5228c 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -21,6 +21,8 @@ #include <sound/core.h> #include <sound/info.h> #include <sound/rawmidi.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h>
#include "../lib.h" #include "../amdtp-stream.h" @@ -124,4 +126,6 @@ void snd_ff_proc_init(struct snd_ff *ff);
int snd_ff_create_midi_devices(struct snd_ff *ff);
+int snd_ff_create_pcm_devices(struct snd_ff *ff); + #endif
This commit adds hwdep interface so as the other drivers for audio and music units on IEEE 1394 have.
This interface is designed for mixer/control applications. By using this interface, an application can get information about firewire node, can lock/unlock kernel streaming and can get notification at starting/stopping kernel streaming.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- include/uapi/sound/asound.h | 3 +- include/uapi/sound/firewire.h | 2 +- sound/firewire/Kconfig | 1 + sound/firewire/fireface/Makefile | 2 +- sound/firewire/fireface/ff-hwdep.c | 191 ++++++++++++++++++++++++++++++++++++ sound/firewire/fireface/ff-pcm.c | 20 +++- sound/firewire/fireface/ff-stream.c | 39 ++++++++ sound/firewire/fireface/ff.c | 5 + sound/firewire/fireface/ff.h | 12 +++ 9 files changed, 269 insertions(+), 6 deletions(-) create mode 100644 sound/firewire/fireface/ff-hwdep.c
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index fd7b561..fd41697 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -108,9 +108,10 @@ enum { SNDRV_HWDEP_IFACE_FW_TASCAM, /* TASCAM FireWire series */ SNDRV_HWDEP_IFACE_LINE6, /* Line6 USB processors */ SNDRV_HWDEP_IFACE_FW_MOTU, /* MOTU FireWire series */ + SNDRV_HWDEP_IFACE_FW_FIREFACE, /* RME Fireface series */
/* Don't forget to change the following: */ - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_MOTU + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_FIREFACE };
struct snd_hwdep_info { diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index 29afc5e..6229004 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -73,7 +73,7 @@ union snd_firewire_event { #define SNDRV_FIREWIRE_TYPE_DIGI00X 5 #define SNDRV_FIREWIRE_TYPE_TASCAM 6 #define SNDRV_FIREWIRE_TYPE_MOTU 7 -/* RME... */ +#define SNDRV_FIREWIRE_TYPE_FIREFACE 8
struct snd_firewire_get_info { unsigned int type; /* SNDRV_FIREWIRE_TYPE_xxx */ diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 077e03c..806d234 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -156,6 +156,7 @@ config SND_FIREWIRE_MOTU config SND_FIREFACE tristate "RME Fireface series support" select SND_FIREWIRE_LIB + select SND_HWDEP help Say Y here to include support for RME fireface series.
diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile index e626938..8d6c612 100644 --- a/sound/firewire/fireface/Makefile +++ b/sound/firewire/fireface/Makefile @@ -1,3 +1,3 @@ snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \ - ff-stream.o ff-pcm.o + ff-stream.o ff-pcm.o ff-hwdep.o obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o diff --git a/sound/firewire/fireface/ff-hwdep.c b/sound/firewire/fireface/ff-hwdep.c new file mode 100644 index 0000000..3ee04b0 --- /dev/null +++ b/sound/firewire/fireface/ff-hwdep.c @@ -0,0 +1,191 @@ +/* + * ff-hwdep.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes give three functionality. + * + * 1.get firewire node information + * 2.get notification about starting/stopping stream + * 3.lock/unlock stream + */ + +#include "ff.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_ff *ff = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&ff->lock); + + while (!ff->dev_lock_changed) { + prepare_to_wait(&ff->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&ff->lock); + schedule(); + finish_wait(&ff->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&ff->lock); + } + + memset(&event, 0, sizeof(event)); + if (ff->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (ff->dev_lock_count > 0); + ff->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } + + spin_unlock_irq(&ff->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static unsigned int hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_ff *ff = hwdep->private_data; + unsigned int events; + + poll_wait(file, &ff->hwdep_wait, wait); + + spin_lock_irq(&ff->lock); + if (ff->dev_lock_changed) + events = POLLIN | POLLRDNORM; + else + events = 0; + spin_unlock_irq(&ff->lock); + + return events; +} + +static int hwdep_get_info(struct snd_ff *ff, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(ff->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_FIREFACE; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int hwdep_lock(struct snd_ff *ff) +{ + int err; + + spin_lock_irq(&ff->lock); + + if (ff->dev_lock_count == 0) { + ff->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&ff->lock); + + return err; +} + +static int hwdep_unlock(struct snd_ff *ff) +{ + int err; + + spin_lock_irq(&ff->lock); + + if (ff->dev_lock_count == -1) { + ff->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&ff->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_ff *ff = hwdep->private_data; + + spin_lock_irq(&ff->lock); + if (ff->dev_lock_count == -1) + ff->dev_lock_count = 0; + spin_unlock_irq(&ff->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_ff *ff = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(ff, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(ff); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(ff); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +int snd_ff_create_hwdep_devices(struct snd_ff *ff) +{ + static const struct snd_hwdep_ops hwdep_ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(ff->card, ff->card->driver, 0, &hwdep); + if (err < 0) + return err; + + strcpy(hwdep->name, ff->card->driver); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREFACE; + hwdep->ops = hwdep_ops; + hwdep->private_data = ff; + hwdep->exclusive = true; + + return 0; +} diff --git a/sound/firewire/fireface/ff-pcm.c b/sound/firewire/fireface/ff-pcm.c index c2a7c3a..e631522 100644 --- a/sound/firewire/fireface/ff-pcm.c +++ b/sound/firewire/fireface/ff-pcm.c @@ -154,13 +154,21 @@ static int pcm_open(struct snd_pcm_substream *substream) enum snd_ff_clock_src src; int i, err;
- err = pcm_init_hw_params(ff, substream); + err = snd_ff_stream_lock_try(ff); if (err < 0) return err;
+ err = pcm_init_hw_params(ff, substream); + if (err < 0) { + snd_ff_stream_lock_release(ff); + return err; + } + err = ff->spec->protocol->get_clock(ff, &rate, &src); - if (err < 0) + if (err < 0) { + snd_ff_stream_lock_release(ff); return err; + }
if (src != SND_FF_CLOCK_SRC_INTERNAL || amdtp_stream_pcm_running(&ff->rx_stream) || @@ -173,8 +181,10 @@ static int pcm_open(struct snd_pcm_substream *substream) * The unit is configured at sampling frequency which packet * streaming engine can't support. */ - if (i >= CIP_SFC_COUNT) + if (i >= CIP_SFC_COUNT) { + snd_ff_stream_lock_release(ff); return -EIO; + }
substream->runtime->hw.rate_min = rate; substream->runtime->hw.rate_max = rate; @@ -187,6 +197,10 @@ static int pcm_open(struct snd_pcm_substream *substream)
static int pcm_close(struct snd_pcm_substream *substream) { + struct snd_ff *ff = substream->private_data; + + snd_ff_stream_lock_release(ff); + return 0; }
diff --git a/sound/firewire/fireface/ff-stream.c b/sound/firewire/fireface/ff-stream.c index 0ef6177..7888092 100644 --- a/sound/firewire/fireface/ff-stream.c +++ b/sound/firewire/fireface/ff-stream.c @@ -241,3 +241,42 @@ void snd_ff_stream_update_duplex(struct snd_ff *ff) fw_iso_resources_update(&ff->tx_resources); fw_iso_resources_update(&ff->rx_resources); } + +void snd_ff_stream_lock_changed(struct snd_ff *ff) +{ + ff->dev_lock_changed = true; + wake_up(&ff->hwdep_wait); +} + +int snd_ff_stream_lock_try(struct snd_ff *ff) +{ + int err; + + spin_lock_irq(&ff->lock); + + /* user land lock this */ + if (ff->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (ff->dev_lock_count++ == 0) + snd_ff_stream_lock_changed(ff); + err = 0; +end: + spin_unlock_irq(&ff->lock); + return err; +} + +void snd_ff_stream_lock_release(struct snd_ff *ff) +{ + spin_lock_irq(&ff->lock); + + if (WARN_ON(ff->dev_lock_count <= 0)) + goto end; + if (--ff->dev_lock_count == 0) + snd_ff_stream_lock_changed(ff); +end: + spin_unlock_irq(&ff->lock); +} diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c index ff62d16..f57b434 100644 --- a/sound/firewire/fireface/ff.c +++ b/sound/firewire/fireface/ff.c @@ -76,6 +76,10 @@ static void do_registration(struct work_struct *work) if (err < 0) goto error;
+ err = snd_ff_create_hwdep_devices(ff); + if (err < 0) + goto error; + err = snd_card_register(ff->card); if (err < 0) goto error; @@ -108,6 +112,7 @@ static int snd_ff_probe(struct fw_unit *unit,
mutex_init(&ff->mutex); spin_lock_init(&ff->lock); + init_waitqueue_head(&ff->hwdep_wait);
ff->spec = (const struct snd_ff_spec *)entry->driver_data;
diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index 0d5228c..16839b7 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -23,6 +23,8 @@ #include <sound/rawmidi.h> #include <sound/pcm.h> #include <sound/pcm_params.h> +#include <sound/hwdep.h> +#include <sound/firewire.h>
#include "../lib.h" #include "../amdtp-stream.h" @@ -77,6 +79,10 @@ struct snd_ff { struct amdtp_stream rx_stream; struct fw_iso_resources tx_resources; struct fw_iso_resources rx_resources; + + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; };
enum snd_ff_clock_src { @@ -122,10 +128,16 @@ int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate); void snd_ff_stream_stop_duplex(struct snd_ff *ff); void snd_ff_stream_update_duplex(struct snd_ff *ff);
+void snd_ff_stream_lock_changed(struct snd_ff *ff); +int snd_ff_stream_lock_try(struct snd_ff *ff); +void snd_ff_stream_lock_release(struct snd_ff *ff); + void snd_ff_proc_init(struct snd_ff *ff);
int snd_ff_create_midi_devices(struct snd_ff *ff);
int snd_ff_create_pcm_devices(struct snd_ff *ff);
+int snd_ff_create_hwdep_devices(struct snd_ff *ff); + #endif
Fireface 400 is a second model of RME Fireface series, released in 2006. This commit adds support for this model.
This model supports 8 analog channels, 2 S/PDIF channels and 8 ADAT channels in both of tx/rx packet. The number of ADAT channels differs depending on each mode of sampling transmission frequency.
$ python2 linux-firewire-utils/src/crpp < /sys/bus/firewire/devices/fw1/config_rom ROM header and bus information block ----------------------------------------------------------------- 400 04107768 bus_info_length 4, crc_length 16, crc 30568 (should be 61311) 404 31333934 bus_name "1394" 408 20009002 irmc 0, cmc 0, isc 1, bmc 0, cyc_clk_acc 0, max_rec 9 (1024) 40c 000a3501 company_id 000a35 | 410 1bd0862a device_id 011bd0862a | EUI-64 000a35011bd0862a
root directory ----------------------------------------------------------------- 414 000485ec directory_length 4, crc 34284 418 03000a35 vendor 41c 0c0083c0 node capabilities per IEEE 1394 420 8d000006 --> eui-64 leaf at 438 424 d1000001 --> unit directory at 428
unit directory at 428 ----------------------------------------------------------------- 428 000314c4 directory_length 3, crc 5316 42c 12000a35 specifier id 430 13000002 version 434 17101800 model
eui-64 leaf at 438 ----------------------------------------------------------------- 438 000261a8 leaf_length 2, crc 25000 43c 000a3501 company_id 000a35 | 440 1bd0862a device_id 011bd0862a | EUI-64 000a35011bd0862a
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 1 + sound/firewire/fireface/Makefile | 2 +- sound/firewire/fireface/ff-protocol-ff400.c | 371 ++++++++++++++++++++++++++++ sound/firewire/fireface/ff.c | 21 ++ sound/firewire/fireface/ff.h | 2 + 5 files changed, 396 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/fireface/ff-protocol-ff400.c
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 806d234..fc0a70c 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -159,5 +159,6 @@ config SND_FIREFACE select SND_HWDEP help Say Y here to include support for RME fireface series. + * Fireface 400
endif # SND_FIREWIRE diff --git a/sound/firewire/fireface/Makefile b/sound/firewire/fireface/Makefile index 8d6c612..8f80728 100644 --- a/sound/firewire/fireface/Makefile +++ b/sound/firewire/fireface/Makefile @@ -1,3 +1,3 @@ snd-fireface-objs := ff.o ff-transaction.o ff-midi.o ff-proc.o amdtp-ff.o \ - ff-stream.o ff-pcm.o ff-hwdep.o + ff-stream.o ff-pcm.o ff-hwdep.o ff-protocol-ff400.o obj-$(CONFIG_SND_FIREFACE) += snd-fireface.o diff --git a/sound/firewire/fireface/ff-protocol-ff400.c b/sound/firewire/fireface/ff-protocol-ff400.c new file mode 100644 index 0000000..fcec6de --- /dev/null +++ b/sound/firewire/fireface/ff-protocol-ff400.c @@ -0,0 +1,371 @@ +/* + * ff-protocol-ff400.c - a part of driver for RME Fireface series + * + * Copyright (c) 2015-2017 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/delay.h> +#include "ff.h" + +#define FF400_STF 0x000080100500ull +#define FF400_RX_PACKET_FORMAT 0x000080100504ull +#define FF400_ISOC_COMM_START 0x000080100508ull +#define FF400_TX_PACKET_FORMAT 0x00008010050cull +#define FF400_ISOC_COMM_STOP 0x000080100510ull +#define FF400_SYNC_STATUS 0x0000801c0000ull +#define FF400_FETCH_PCM_FRAMES 0x0000801c0000ull /* For block request. */ +#define FF400_CLOCK_CONFIG 0x0000801c0004ull + +#define FF400_MIDI_HIGH_ADDR 0x0000801003f4ull +#define FF400_MIDI_RX_PORT_0 0x000080180000ull +#define FF400_MIDI_RX_PORT_1 0x000080190000ull + +static int ff400_get_clock(struct snd_ff *ff, unsigned int *rate, + enum snd_ff_clock_src *src) +{ + __le32 reg; + u32 data; + int err; + + err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST, + FF400_SYNC_STATUS, ®, sizeof(reg), 0); + if (err < 0) + return err; + data = le32_to_cpu(reg); + + /* Calculate sampling rate. */ + switch ((data >> 1) & 0x03) { + case 0x01: + *rate = 32000; + break; + case 0x00: + *rate = 44100; + break; + case 0x03: + *rate = 48000; + break; + case 0x02: + default: + return -EIO; + } + + if (data & 0x08) + *rate *= 2; + else if (data & 0x10) + *rate *= 4; + + /* Calculate source of clock. */ + if (data & 0x01) { + *src = SND_FF_CLOCK_SRC_INTERNAL; + } else { + /* TODO: 0x00, 0x01, 0x02, 0x06, 0x07? */ + switch ((data >> 10) & 0x07) { + case 0x03: + *src = SND_FF_CLOCK_SRC_SPDIF; + break; + case 0x04: + *src = SND_FF_CLOCK_SRC_WORD; + break; + case 0x05: + *src = SND_FF_CLOCK_SRC_LTC; + break; + case 0x00: + default: + *src = SND_FF_CLOCK_SRC_ADAT; + break; + } + } + + return 0; +} + +static int ff400_begin_session(struct snd_ff *ff, unsigned int rate) +{ + __le32 reg; + int i, err; + + /* Check whether the given value is supported or not. */ + for (i = 0; i < CIP_SFC_COUNT; i++) { + if (amdtp_rate_table[i] == rate) + break; + } + if (i == CIP_SFC_COUNT) + return -EINVAL; + + /* Set the number of data blocks transferred in a second. */ + reg = cpu_to_le32(rate); + err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_STF, ®, sizeof(reg), 0); + if (err < 0) + return err; + + msleep(100); + + /* + * Set isochronous channel and the number of quadlets of received + * packets. + */ + reg = cpu_to_le32(((ff->rx_stream.data_block_quadlets << 3) << 8) | + ff->rx_resources.channel); + err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_RX_PACKET_FORMAT, ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* + * Set isochronous channel and the number of quadlets of transmitted + * packet. + */ + /* TODO: investigate the purpose of this 0x80. */ + reg = cpu_to_le32((0x80 << 24) | + (ff->tx_resources.channel << 5) | + (ff->tx_stream.data_block_quadlets)); + err = snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_TX_PACKET_FORMAT, ®, sizeof(reg), 0); + if (err < 0) + return err; + + /* Allow to transmit packets. */ + reg = cpu_to_le32(0x00000001); + return snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_ISOC_COMM_START, ®, sizeof(reg), 0); +} + +static void ff400_finish_session(struct snd_ff *ff) +{ + __le32 reg; + + reg = cpu_to_le32(0x80000000); + snd_fw_transaction(ff->unit, TCODE_WRITE_QUADLET_REQUEST, + FF400_ISOC_COMM_STOP, ®, sizeof(reg), 0); +} + +static int ff400_switch_fetching_mode(struct snd_ff *ff, bool enable) +{ + __le32 *reg; + int i; + + reg = kzalloc(sizeof(__le32) * 18, GFP_KERNEL); + if (reg == NULL) + return -ENOMEM; + + if (enable) { + /* + * Each quadlet is corresponding to data channels in a data + * blocks in reverse order. Precisely, quadlets for available + * data channels should be enabled. Here, I take second best + * to fetch PCM frames from all of data channels regardless of + * stf. + */ + for (i = 0; i < 18; ++i) + reg[i] = cpu_to_le32(0x00000001); + } + + return snd_fw_transaction(ff->unit, TCODE_WRITE_BLOCK_REQUEST, + FF400_FETCH_PCM_FRAMES, reg, + sizeof(__le32) * 18, 0); +} + +static void ff400_dump_sync_status(struct snd_ff *ff, + struct snd_info_buffer *buffer) +{ + __le32 reg; + u32 data; + int err; + + err = snd_fw_transaction(ff->unit, TCODE_READ_QUADLET_REQUEST, + FF400_SYNC_STATUS, ®, sizeof(reg), 0); + if (err < 0) + return; + + data = le32_to_cpu(reg); + + snd_iprintf(buffer, "External source detection:\n"); + + snd_iprintf(buffer, "Word Clock:"); + if ((data >> 24) & 0x20) { + if ((data >> 24) & 0x40) + snd_iprintf(buffer, "sync\n"); + else + snd_iprintf(buffer, "lock\n"); + } else { + snd_iprintf(buffer, "none\n"); + } + + snd_iprintf(buffer, "S/PDIF:"); + if ((data >> 16) & 0x10) { + if ((data >> 16) & 0x04) + snd_iprintf(buffer, "sync\n"); + else + snd_iprintf(buffer, "lock\n"); + } else { + snd_iprintf(buffer, "none\n"); + } + + snd_iprintf(buffer, "ADAT:"); + if ((data >> 8) & 0x04) { + if ((data >> 8) & 0x10) + snd_iprintf(buffer, "sync\n"); + else + snd_iprintf(buffer, "lock\n"); + } else { + snd_iprintf(buffer, "none\n"); + } + + snd_iprintf(buffer, "\nUsed external source:\n"); + + if (((data >> 22) & 0x07) == 0x07) { + snd_iprintf(buffer, "None\n"); + } else { + switch ((data >> 22) & 0x07) { + case 0x00: + snd_iprintf(buffer, "ADAT:"); + break; + case 0x03: + snd_iprintf(buffer, "S/PDIF:"); + break; + case 0x04: + snd_iprintf(buffer, "Word:"); + break; + case 0x07: + snd_iprintf(buffer, "Nothing:"); + break; + case 0x01: + case 0x02: + case 0x05: + case 0x06: + default: + snd_iprintf(buffer, "unknown:"); + break; + } + + if ((data >> 25) & 0x07) { + switch ((data >> 25) & 0x07) { + case 0x01: + snd_iprintf(buffer, "32000\n"); + break; + case 0x02: + snd_iprintf(buffer, "44100\n"); + break; + case 0x03: + snd_iprintf(buffer, "48000\n"); + break; + case 0x04: + snd_iprintf(buffer, "64000\n"); + break; + case 0x05: + snd_iprintf(buffer, "88200\n"); + break; + case 0x06: + snd_iprintf(buffer, "96000\n"); + break; + case 0x07: + snd_iprintf(buffer, "128000\n"); + break; + case 0x08: + snd_iprintf(buffer, "176400\n"); + break; + case 0x09: + snd_iprintf(buffer, "192000\n"); + break; + case 0x00: + snd_iprintf(buffer, "unknown\n"); + break; + } + } + } + + snd_iprintf(buffer, "Multiplied:"); + snd_iprintf(buffer, "%d\n", (data & 0x3ff) * 250); +} + +static void ff400_dump_clock_config(struct snd_ff *ff, + struct snd_info_buffer *buffer) +{ + __le32 reg; + u32 data; + unsigned int rate; + const char *src; + int err; + + err = snd_fw_transaction(ff->unit, TCODE_READ_BLOCK_REQUEST, + FF400_CLOCK_CONFIG, ®, sizeof(reg), 0); + if (err < 0) + return; + + data = le32_to_cpu(reg); + + snd_iprintf(buffer, "Output S/PDIF format: %s (Emphasis: %s)\n", + (data & 0x20) ? "Professional" : "Consumer", + (data & 0x40) ? "on" : "off"); + + snd_iprintf(buffer, "Optical output interface format: %s\n", + ((data >> 8) & 0x01) ? "S/PDIF" : "ADAT"); + + snd_iprintf(buffer, "Word output single speed: %s\n", + ((data >> 8) & 0x20) ? "on" : "off"); + + snd_iprintf(buffer, "S/PDIF input interface: %s\n", + ((data >> 8) & 0x02) ? "Optical" : "Coaxial"); + + switch ((data >> 1) & 0x03) { + case 0x01: + rate = 32000; + break; + case 0x00: + rate = 44100; + break; + case 0x03: + rate = 48000; + break; + case 0x02: + default: + return; + } + + if (data & 0x08) + rate *= 2; + else if (data & 0x10) + rate *= 4; + + snd_iprintf(buffer, "Sampling rate: %d\n", rate); + + if (data & 0x01) { + src = "Internal"; + } else { + switch ((data >> 10) & 0x07) { + case 0x00: + src = "ADAT"; + break; + case 0x03: + src = "S/PDIF"; + break; + case 0x04: + src = "Word"; + break; + case 0x05: + src = "LTC"; + break; + default: + return; + } + } + + snd_iprintf(buffer, "Sync to clock source: %s\n", src); +} + +struct snd_ff_protocol snd_ff_protocol_ff400 = { + .get_clock = ff400_get_clock, + .begin_session = ff400_begin_session, + .finish_session = ff400_finish_session, + .switch_fetching_mode = ff400_switch_fetching_mode, + + .dump_sync_status = ff400_dump_sync_status, + .dump_clock_config = ff400_dump_clock_config, + + .midi_high_addr_reg = FF400_MIDI_HIGH_ADDR, + .midi_rx_port_0_reg = FF400_MIDI_RX_PORT_0, + .midi_rx_port_1_reg = FF400_MIDI_RX_PORT_1, +}; diff --git a/sound/firewire/fireface/ff.c b/sound/firewire/fireface/ff.c index f57b434..3f66fa3 100644 --- a/sound/firewire/fireface/ff.c +++ b/sound/firewire/fireface/ff.c @@ -157,7 +157,28 @@ static void snd_ff_remove(struct fw_unit *unit) } }
+static struct snd_ff_spec spec_ff400 = { + .name = "Fireface400", + .pcm_capture_channels = {18, 14, 10}, + .pcm_playback_channels = {18, 14, 10}, + .midi_in_ports = 1, + .midi_out_ports = 1, + .protocol = &snd_ff_protocol_ff400, +}; + static const struct ieee1394_device_id snd_ff_id_table[] = { + /* Fireface 400 */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_RME, + .specifier_id = 0x000a35, + .version = 0x000002, + .model_id = 0x101800, + .driver_data = (kernel_ulong_t)&spec_ff400, + }, {} }; MODULE_DEVICE_TABLE(ieee1394, snd_ff_id_table); diff --git a/sound/firewire/fireface/ff.h b/sound/firewire/fireface/ff.h index 16839b7..ccc0aa2 100644 --- a/sound/firewire/fireface/ff.h +++ b/sound/firewire/fireface/ff.h @@ -111,6 +111,8 @@ struct snd_ff_protocol { u64 midi_rx_port_1_reg; };
+extern struct snd_ff_protocol snd_ff_protocol_ff400; + int snd_ff_transaction_register(struct snd_ff *ff); int snd_ff_transaction_reregister(struct snd_ff *ff); void snd_ff_transaction_unregister(struct snd_ff *ff);
participants (1)
-
Takashi Sakamoto