[alsa-devel] [PATCH 0/8] [RFC] new driver for Echo Audio's Fireworks based devices
From: Takashi Sakamoto o-takashi@sakamocchi.jp
This driver module is originally developed by Clemens Ladish 2010. But it includes duplicate codes which current snd-firewire-lib has and lack of automatical sampling rate/the number of channels arrangement. http://git.alsa-project.org/?p=alsa-kprivate.git;a=shortlog;h=refs/heads/fir...
This driver module is based on snd-firewire-lib with my previous patches.
[PATCH v2 0/3] snd-firewire-lib: add handling CMP output connection http://mailman.alsa-project.org/pipermail/alsa-devel/2013-April/061607.html
[PATCH v2 0/4] snd-firewire-lib: add handling AMDTP receive stream http://mailman.alsa-project.org/pipermail/alsa-devel/2013-April/061611.html
[PATCH 0/2] snd-firewire-lib: add MIDI stream support http://mailman.alsa-project.org/pipermail/alsa-devel/2013-June/062610.html
Currently this driver module support just PCM/MIDI kernel streaming via ALSA interfaces. So there are some issues about this driver. 1.whether adding each PCM devices for analog and digital interface 2.where the codes for device control like volume, routing and etc are 3.card definition files for ALSA applications like PulseAudio
I hope this driver module promote developers to discuss about how to implement Firewire devices which has many channels and options.
Takashi Sakamoto (8): add main file of driver module add device specific command add control interfaces add handling AMDTP stream add MIDI interface add PCM interface add proc interface modify Makefile and Kconfig to build this module
sound/firewire/Kconfig | 16 + sound/firewire/Makefile | 2 + sound/firewire/fireworks/Makefile | 2 + sound/firewire/fireworks/fireworks.c | 488 +++++++++++++++++++++++ sound/firewire/fireworks/fireworks.h | 241 ++++++++++++ sound/firewire/fireworks/fireworks_command.c | 535 +++++++++++++++++++++++++ sound/firewire/fireworks/fireworks_control.c | 537 ++++++++++++++++++++++++++ sound/firewire/fireworks/fireworks_midi.c | 236 +++++++++++ sound/firewire/fireworks/fireworks_pcm.c | 530 +++++++++++++++++++++++++ sound/firewire/fireworks/fireworks_proc.c | 183 +++++++++ sound/firewire/fireworks/fireworks_stream.c | 107 +++++ 11 files changed, 2877 insertions(+) create mode 100644 sound/firewire/fireworks/Makefile create mode 100644 sound/firewire/fireworks/fireworks.c create mode 100644 sound/firewire/fireworks/fireworks.h create mode 100644 sound/firewire/fireworks/fireworks_command.c create mode 100644 sound/firewire/fireworks/fireworks_control.c create mode 100644 sound/firewire/fireworks/fireworks_midi.c create mode 100644 sound/firewire/fireworks/fireworks_pcm.c create mode 100644 sound/firewire/fireworks/fireworks_proc.c create mode 100644 sound/firewire/fireworks/fireworks_stream.c
From: Takashi Sakamoto o-takashi@sakamocchi.jp
This driver module is an application of Firewire stack (Juju) and ALSA. This file includes prove() function to be a device driver for firewire bus.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/fireworks/fireworks.c | 488 ++++++++++++++++++++++++++++++++++ sound/firewire/fireworks/fireworks.h | 241 +++++++++++++++++ 2 files changed, 729 insertions(+) create mode 100644 sound/firewire/fireworks/fireworks.c create mode 100644 sound/firewire/fireworks/fireworks.h
diff --git a/sound/firewire/fireworks/fireworks.c b/sound/firewire/fireworks/fireworks.c new file mode 100644 index 0000000..c4cd0c0 --- /dev/null +++ b/sound/firewire/fireworks/fireworks.c @@ -0,0 +1,488 @@ +/* + * fireworks.c - driver for Firewire devices from Echo Digital Audio + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013 Takashi Sakamoto + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver; if not, see http://www.gnu.org/licenses/. + */ + +#include "fireworks.h" + +MODULE_DESCRIPTION("Echo Fireworks driver"); +MODULE_AUTHOR("Clemens Ladisch clemens@ladisch.de"); +MODULE_LICENSE("GPL v2"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "card index"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string"); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "enable Fireworks sound card"); + +static DEFINE_MUTEX(devices_mutex); +static unsigned int devices_used; + +#define FLAG_DYNADDR_SUPPORTED 0 +#define FLAG_MIRRORING_SUPPORTED 1 +#define FLAG_SPDIF_COAX_SUPPORTED 2 +#define FLAG_SPDIF_AES_EBU_XLR_SUPPORTED 3 +#define FLAG_HAS_DSP_MIXER 4 +#define FLAG_HAS_FPGA 5 +#define FLAG_HAS_PHANTOM 6 +/* other flags exist but unknown... */ + +static int +get_hardware_info(struct snd_efw *efw) +{ + int err; + + struct snd_efw_hwinfo *hwinfo; + char version[12]; + int size; + int i; + + hwinfo = kzalloc(sizeof(struct snd_efw_hwinfo), GFP_KERNEL); + if (hwinfo == NULL) + return -ENOMEM; + + err = snd_efw_command_get_hwinfo(efw, hwinfo); + if (err < 0) + goto end; + + /* capabilities */ + if (hwinfo->flags & (1 << FLAG_DYNADDR_SUPPORTED)) + efw->dynaddr_support = 1; + if (hwinfo->flags & (1 << FLAG_MIRRORING_SUPPORTED)) + efw->mirroring_support = 1; + if (hwinfo->flags & (1 << FLAG_SPDIF_AES_EBU_XLR_SUPPORTED)) + efw->aes_ebu_xlr_support = 1; + if (hwinfo->flags & (1 << FLAG_HAS_DSP_MIXER)) + efw->has_dsp_mixer = 1; + if (hwinfo->flags & (1 << FLAG_HAS_FPGA)) + efw->has_fpga = 1; + if (hwinfo->flags & (1 << FLAG_HAS_PHANTOM)) + efw->has_phantom = 1; + if (hwinfo->flags & (1 << FLAG_SPDIF_COAX_SUPPORTED)) { + efw->supported_digital_interface = BIT(2) | BIT(3); + /* TODO: find better way... */ + if (strcmp(hwinfo->model_name, "AudioFire8a") + || strcmp(hwinfo->model_name, "AudioFirePre8")) + efw->supported_digital_interface |= BIT(0); + } + + /* for input physical metering */ + if (hwinfo->nb_out_groups > 0) { + size = sizeof(struct snd_efw_phys_group) * + hwinfo->nb_out_groups; + efw->output_groups = kzalloc(size, GFP_KERNEL); + if (efw->output_groups == NULL) { + err = -ENOMEM; + goto error; + } + + efw->output_group_counts = hwinfo->nb_out_groups; + for (i = 0; i < efw->output_group_counts; i += 1) { + efw->output_groups[i].type = + hwinfo->out_groups[i].type; + efw->output_groups[i].count = + hwinfo->out_groups[i].count; + } + } + + /* for output physical metering */ + if (hwinfo->nb_in_groups > 0) { + size = sizeof(struct snd_efw_phys_group) * + hwinfo->nb_in_groups; + efw->input_groups = kzalloc(size, GFP_KERNEL); + if (efw->input_groups == NULL) { + err = -ENOMEM; + goto error; + } + + efw->input_group_counts = hwinfo->nb_out_groups; + for (i = 0; i < efw->input_group_counts; i += 1) { + efw->input_groups[i].type = + hwinfo->in_groups[i].type; + efw->input_groups[i].count = + hwinfo->in_groups[i].count; + } + } + + /* for mixer channels */ + efw->mixer_output_channels = hwinfo->mixer_playback_channels; + efw->mixer_input_channels = hwinfo->mixer_capture_channels; + + /* fill channels sets */ + efw->pcm_capture_channels[0] = hwinfo->nb_1394_capture_channels; + efw->pcm_capture_channels[1] = hwinfo->nb_1394_capture_channels_2x; + efw->pcm_capture_channels[2] = hwinfo->nb_1394_capture_channels_4x; + efw->pcm_playback_channels[0] = hwinfo->nb_1394_playback_channels; + efw->pcm_playback_channels[1] = hwinfo->nb_1394_playback_channels_2x; + efw->pcm_playback_channels[2] = hwinfo->nb_1394_playback_channels_4x; + + /* firmware version */ + err = sprintf(version, "%u.%u", + (hwinfo->arm_version >> 24) & 0xff, + (hwinfo->arm_version >> 16) & 0xff); + + /* set names */ + strcpy(efw->card->driver, "Fireworks"); + strcpy(efw->card->shortname, hwinfo->model_name); + snprintf(efw->card->longname, sizeof(efw->card->longname), + "%s %s v%s, GUID %08x%08x at %s, S%d", + hwinfo->vendor_name, hwinfo->model_name, version, + hwinfo->guid_hi, hwinfo->guid_lo, + dev_name(&efw->unit->device), 100 << efw->device->max_speed); + strcpy(efw->card->mixername, hwinfo->model_name); + + /* set flag for supported clock source */ + efw->supported_clock_source = hwinfo->supported_clocks; + + /* set flag for supported sampling rate */ + efw->supported_sampling_rate = 0; + if ((hwinfo->min_sample_rate <= 22050) + && (22050 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_22050; + if ((hwinfo->min_sample_rate <= 32000) + && (32000 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_32000; + if ((hwinfo->min_sample_rate <= 44100) + && (44100 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_44100; + if ((hwinfo->min_sample_rate <= 48000) + && (48000 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_48000; + if ((hwinfo->min_sample_rate <= 88200) + && (88200 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_88200; + if ((hwinfo->min_sample_rate <= 96000) + && (96000 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_96000; + if ((hwinfo->min_sample_rate <= 176400) + && (176400 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_176400; + if ((hwinfo->min_sample_rate <= 192000) + && (192000 <= hwinfo->max_sample_rate)) + efw->supported_sampling_rate |= SNDRV_PCM_RATE_192000; + + /* MIDI inputs and outputs */ + efw->midi_output_ports = hwinfo->nb_midi_out; + efw->midi_input_ports = hwinfo->nb_midi_in; + + err = 0; + goto end; + +error: + if (efw->input_group_counts > 0) + kfree(efw->input_groups); + if (efw->output_group_counts > 0) + kfree(efw->output_groups); +end: + kfree(hwinfo); + return err; +} + +static int +get_hardware_meters_count(struct snd_efw *efw) +{ + int err; + struct snd_efw_phys_meters *meters; + + meters = kzalloc(sizeof(struct snd_efw_phys_meters), GFP_KERNEL); + if (meters == NULL) + return -ENOMEM; + + err = snd_efw_command_get_phys_meters(efw, meters, + sizeof(struct snd_efw_phys_meters)); + if (err < 0) + goto end; + + efw->input_meter_counts = meters->nb_input_meters; + efw->output_meter_counts = meters->nb_output_meters; + + err = 0; +end: + kfree(meters); + return err; +} + +static void +snd_efw_update(struct fw_unit *unit) +{ + struct snd_card *card = dev_get_drvdata(&unit->device); + struct snd_efw *efw = card->private_data; + + fcp_bus_reset(efw->unit); + + /* bus reset for isochronous transmit stream */ + if (cmp_connection_update(&efw->output_connection) < 0) { + amdtp_stream_pcm_abort(&efw->receive_stream); + mutex_lock(&efw->mutex); + snd_efw_stream_stop(efw, &efw->receive_stream); + mutex_unlock(&efw->mutex); + } + amdtp_stream_update(&efw->receive_stream); + + /* bus reset for isochronous receive stream */ + if (cmp_connection_update(&efw->input_connection) < 0) { + amdtp_stream_pcm_abort(&efw->transmit_stream); + mutex_lock(&efw->mutex); + snd_efw_stream_stop(efw, &efw->transmit_stream); + mutex_unlock(&efw->mutex); + } + amdtp_stream_update(&efw->transmit_stream); + + return; +} + +static bool match_fireworks_device_name(struct fw_unit *unit) +{ + static const char *const models[] = { + /* Echo Digital Audio */ + "AudioFire2", + "AudioFire4", + "AudioFire8", + "AudioFire8a", + "AudioFirePre8", + "AudioFire12", + "Fireworks8", + "Fireworks HDMI", + /* Mackie */ + "Onyx 400F", + "Onyx 1200F", + /* Gibson */ + "RIP", + "Audiopunk", + "Goldtop", + }; + char name[16]; + unsigned int i; + + if (fw_csr_string(unit->directory, CSR_MODEL, name, sizeof(name)) < 0) + return false; + for (i = 0; i < ARRAY_SIZE(models); i++) + if (!strcasecmp(name, models[i])) + return true; + return false; +} + +static void +snd_efw_card_free(struct snd_card *card) +{ + struct snd_efw *efw = card->private_data; + + if (efw->card_index >= 0) { + mutex_lock(&devices_mutex); + devices_used &= ~(1 << efw->card_index); + mutex_unlock(&devices_mutex); + } + + if (efw->output_group_counts > 0) + kfree(efw->output_groups); + if (efw->input_group_counts > 0) + kfree(efw->input_groups); + + mutex_destroy(&efw->mutex); + + return; +} + +static int +snd_efw_probe(struct device *dev) +{ + struct fw_unit *unit = fw_unit(dev); + int card_index; + struct snd_card *card; + struct snd_efw *efw; + int err; + + mutex_lock(&devices_mutex); + + /* check device name */ + if (!match_fireworks_device_name(unit)) + return -ENODEV; + + /* check registered cards */ + for (card_index = 0; card_index < SNDRV_CARDS; ++card_index) + if (!(devices_used & (1 << card_index)) && enable[card_index]) + break; + if (card_index >= SNDRV_CARDS) { + err = -ENOENT; + goto end; + } + + /* create card */ + err = snd_card_create(index[card_index], id[card_index], + THIS_MODULE, sizeof(struct snd_efw), &card); + if (err < 0) + goto end; + card->private_free = snd_efw_card_free; + + /* initialize myself */ + efw = card->private_data; + efw->card = card; + efw->device = fw_parent_device(unit); + efw->unit = unit; + efw->card_index = -1; + mutex_init(&efw->mutex); + spin_lock_init(&efw->lock); + + /* identifing */ + err = snd_efw_command_identify(efw); + if (err < 0) + goto error; + + /* get hardware information */ + err = get_hardware_info(efw); + if (err < 0) + goto error; + + /* get the number of hardware meters */ + err = get_hardware_meters_count(efw); + if (err < 0) + goto error; + + /* create procfs interface */ + snd_efw_proc_init(efw); + + /* create control interface */ + err = snd_efw_create_control_devices(efw); + if (err < 0) + goto error; + + /* create PCM interface */ + err = snd_efw_create_pcm_devices(efw); + if (err < 0) + goto error; + + /* create midi interface */ + if (efw->midi_output_ports || efw->midi_input_ports) { + err = snd_efw_create_midi_devices(efw); + if (err < 0) + goto error; + } + + /* register card and device */ + snd_card_set_dev(card, dev); + err = snd_card_register(card); + if (err < 0) + goto error; + dev_set_drvdata(dev, card); + devices_used |= 1 << card_index; + efw->card_index = card_index; + + /* proved */ + err = 0; + goto end; + +error: + snd_card_free(card); + +end: + mutex_unlock(&devices_mutex); + return err; +} + +static int +snd_efw_remove(struct device *dev) +{ + struct snd_card *card = dev_get_drvdata(dev); + struct snd_efw *efw = card->private_data; + + snd_efw_destroy_pcm_devices(efw); + + snd_card_disconnect(card); + snd_card_free_when_closed(card); + + return 0; +} + +#define VENDOR_GIBSON 0x00075b +#define MODEL_GIBSON_RIP 0x00afb2 +/* #define MODEL_GIBSON_GOLDTOP 0x?????? */ + +#define VENDOR_LOUD 0x000ff2 +#define MODEL_MACKIE_400F 0x00400f +#define MODEL_MACKIE_1200F 0x01200f + +#define VENDOR_ECHO_DIGITAL_AUDIO 0x001486 +#define MODEL_ECHO_AUDIOFIRE_2 0x000af2 +#define MODEL_ECHO_AUDIOFIRE_4 0x000af4 +#define MODEL_ECHO_AUDIOFIRE_8 0x000af8 +/* #define MODEL_ECHO_AUDIOFIRE_8A 0x?????? */ +/* #define MODEL_ECHO_AUDIOFIRE_PRE8 0x?????? */ +#define MODEL_ECHO_AUDIOFIRE_12 0x00af12 +#define MODEL_ECHO_FIREWORKS_8 0x0000f8 +#define MODEL_ECHO_FIREWORKS_HDMI 0x00afd1 + +#define SPECIFIER_1394TA 0x00a02d + +static const struct ieee1394_device_id snd_efw_id_table[] = { + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID, + .vendor_id = VENDOR_ECHO_DIGITAL_AUDIO, + .specifier_id = SPECIFIER_1394TA, + }, + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID, + .vendor_id = VENDOR_GIBSON, + .specifier_id = SPECIFIER_1394TA, + }, + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = VENDOR_LOUD, + .model_id = MODEL_MACKIE_400F, + }, + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = VENDOR_LOUD, + .model_id = MODEL_MACKIE_1200F, + }, + {} +}; +MODULE_DEVICE_TABLE(ieee1394, snd_efw_id_table); + +static struct fw_driver snd_efw_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "snd-fireworks", + .bus = &fw_bus_type, + .probe = snd_efw_probe, + .remove = snd_efw_remove, + }, + .update = snd_efw_update, + .id_table = snd_efw_id_table, +}; + +static int __init snd_efw_init(void) +{ + return driver_register(&snd_efw_driver.driver); +} + +static void __exit snd_efw_exit(void) +{ + driver_unregister(&snd_efw_driver.driver); + mutex_destroy(&devices_mutex); +} + +module_init(snd_efw_init); +module_exit(snd_efw_exit); diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h new file mode 100644 index 0000000..ce117b4 --- /dev/null +++ b/sound/firewire/fireworks/fireworks.h @@ -0,0 +1,241 @@ +/* + * fireworks.h - driver for Firewire devices from Echo Digital Audio + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013 Takashi Sakamoto + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver; if not, see http://www.gnu.org/licenses/. + * + * mostly based on FFADO's efc_cmd.h, which is + * Copyright (C) 2005-2008 by Pieter Palmers + * + */ +#ifndef SOUND_FIREWORKS_H_INCLUDED +#define SOUND_FIREWORKS_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/delay.h> +#include <linux/slab.h> + +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/control.h> +#include <sound/rawmidi.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/info.h> +#include <sound/tlv.h> + +#include "../packets-buffer.h" +#include "../iso-resources.h" +#include "../amdtp.h" +#include "../cmp.h" +#include "../fcp.h" + +#define MAX_MIDI_OUTPUTS 2 +#define MAX_MIDI_INPUTS 2 + +#define SND_EFW_MUITIPLIER_MODES 3 +#define HWINFO_NAME_SIZE_BYTES 32 +#define HWINFO_MAX_CAPS_GROUPS 8 + +/* for physical metering */ +enum snd_efw_channel_type { + SND_EFW_CHANNEL_TYPE_ANALOG = 0, + SND_EFW_CHANNEL_TYPE_SPDIF = 1, + SND_EFW_CHANNEL_TYPE_ADAT = 2, + SND_EFW_CHANNEL_TYPE_SPDIF_OR_ADAT = 3, + SND_EFW_CHANNEL_TYPE_ANALOG_MIRRORING = 4, + SND_EFW_CHANNEL_TYPE_HEADPHONES = 5, + SND_EFW_CHANNEL_TYPE_I2S = 6 +}; +struct snd_efw_phys_group { + u8 type; /* enum snd_efw_channel_type */ + u8 count; +} __packed; + +struct snd_efw { + struct snd_card *card; + struct fw_device *device; + struct fw_unit *unit; + int card_index; + + struct mutex mutex; + spinlock_t lock; + + /* for EFC */ + u32 sequence_number; + + /* capabilities */ + unsigned int supported_sampling_rate; + unsigned int supported_clock_source; + unsigned int supported_digital_interface; + unsigned int has_phantom; + unsigned int has_dsp_mixer; + unsigned int has_fpga; + unsigned int aes_ebu_xlr_support; + unsigned int mirroring_support; + unsigned int dynaddr_support; + + /* physical metering */ + unsigned int input_group_counts; + struct snd_efw_phys_group *input_groups; + unsigned int output_group_counts; + struct snd_efw_phys_group *output_groups; + + /* meter parameters */ + unsigned int input_meter_counts; + unsigned int output_meter_counts; + + /* mixer parameters */ + unsigned int mixer_input_channels; + unsigned int mixer_output_channels; + + /* MIDI parameters */ + unsigned int midi_input_ports; + unsigned int midi_output_ports; + + /* PCM parameters */ + unsigned int pcm_capture_channels[SND_EFW_MUITIPLIER_MODES]; + unsigned int pcm_playback_channels[SND_EFW_MUITIPLIER_MODES]; + + /* notification to control components */ + struct snd_ctl_elem_id *control_id_sampling_rate; + + /* for IEC 61883-1 and -6 streaming */ + struct amdtp_stream receive_stream; + struct amdtp_stream transmit_stream; + /* Fireworks has only two plugs */ + struct cmp_connection output_connection; + struct cmp_connection input_connection; +}; + +struct snd_efw_hwinfo { + u32 flags; + u32 guid_hi; + u32 guid_lo; + u32 type; + u32 version; + char vendor_name[HWINFO_NAME_SIZE_BYTES]; + char model_name[HWINFO_NAME_SIZE_BYTES]; + u32 supported_clocks; + u32 nb_1394_playback_channels; + u32 nb_1394_capture_channels; + u32 nb_phys_audio_out; + u32 nb_phys_audio_in; + u32 nb_out_groups; + struct snd_efw_phys_group out_groups[HWINFO_MAX_CAPS_GROUPS]; + u32 nb_in_groups; + struct snd_efw_phys_group in_groups[HWINFO_MAX_CAPS_GROUPS]; + u32 nb_midi_out; + u32 nb_midi_in; + u32 max_sample_rate; + u32 min_sample_rate; + u32 dsp_version; + u32 arm_version; + u32 mixer_playback_channels; + u32 mixer_capture_channels; + u32 fpga_version; + u32 nb_1394_playback_channels_2x; + u32 nb_1394_capture_channels_2x; + u32 nb_1394_playback_channels_4x; + u32 nb_1394_capture_channels_4x; + u32 reserved[16]; +} __packed; + +/* for hardware metering */ +struct snd_efw_phys_meters { + u32 status; + u32 detect_spdif; + u32 detect_adat; + u32 reserved0; + u32 reserved1; + u32 nb_output_meters; + u32 nb_input_meters; + u32 reserved2; + u32 reserved3; + u32 values[0]; +} __packed; + +/* clock source parameters */ +enum snd_efw_clock_source { + SND_EFW_CLOCK_SOURCE_INTERNAL = 0, + SND_EFW_CLOCK_SOURCE_SYTMATCH = 1, + SND_EFW_CLOCK_SOURCE_WORDCLOCK = 2, + SND_EFW_CLOCK_SOURCE_SPDIF = 3, + SND_EFW_CLOCK_SOURCE_ADAT_1 = 4, + SND_EFW_CLOCK_SOURCE_ADAT_2 = 5, +}; + +/* digital interface parameters */ +enum snd_efw_digital_interface { + SND_EFW_DIGITAL_INTERFACE_SPDIF_COAXIAL = 0, + SND_EFW_DIGITAL_INTERFACE_ADAT_COAXIAL = 1, + SND_EFW_DIGITAL_INTERFACE_SPDIF_OPTICAL = 2, + SND_EFW_DIGITAL_INTERFACE_ADAT_OPTICAL = 3 +}; + +/* S/PDIF format parameters */ +enum snd_efw_iec60958_format { + SND_EFW_IEC60958_FORMAT_CONSUMER = 0, + SND_EFW_IEC60958_FORMAT_PROFESSIONAL = 1 +}; + +/* Echo Fireworks Command functions */ +int snd_efw_command_identify(struct snd_efw *efw); +int snd_efw_command_get_hwinfo(struct snd_efw *efw, + struct snd_efw_hwinfo *hwinfo); +int snd_efw_command_get_phys_meters(struct snd_efw *efw, + struct snd_efw_phys_meters *meters, + int len); +int snd_efw_command_get_clock_source(struct snd_efw *efw, + enum snd_efw_clock_source *source); +int snd_efw_command_set_clock_source(struct snd_efw *efw, + enum snd_efw_clock_source source); +int snd_efw_command_get_sampling_rate(struct snd_efw *efw, int *sampling_rate); +int snd_efw_command_set_sampling_rate(struct snd_efw *efw, int sampling_rate); +int snd_efw_command_get_iec60958_format(struct snd_efw *efw, + enum snd_efw_iec60958_format *format); +int snd_efw_command_set_iec60958_format(struct snd_efw *efw, + enum snd_efw_iec60958_format format); +int snd_efw_command_get_digital_interface(struct snd_efw *efw, + enum snd_efw_digital_interface *digital_interface); +int snd_efw_command_set_digital_interface(struct snd_efw *efw, + enum snd_efw_digital_interface digital_interface); + +/* for AMDTP stream and CMP */ +int snd_efw_stream_init(struct snd_efw *efw, struct amdtp_stream *stream); +int snd_efw_stream_start(struct snd_efw *efw, struct amdtp_stream *stream); +void snd_efw_stream_stop(struct snd_efw *efw, struct amdtp_stream *stream); +void snd_efw_stream_destroy(struct snd_efw *efw, struct amdtp_stream *stream); + +/* for procfs subsystem */ +void snd_efw_proc_init(struct snd_efw *efw); + +/* for control component */ +int snd_efw_create_control_devices(struct snd_efw *efw); + +/* for midi component */ +int snd_efw_create_midi_devices(struct snd_efw *ef); + +/* for pcm component */ +int snd_efw_create_pcm_devices(struct snd_efw *efw); +void snd_efw_destroy_pcm_devices(struct snd_efw *efw); +int snd_efw_get_multiplier_mode(int sampling_rate); + + +#endif
o-takashi@sakamocchi.jp wrote:
+#define VENDOR_ECHO_DIGITAL_AUDIO 0x001486 +#define MODEL_ECHO_AUDIOFIRE_2 0x000af2 +#define MODEL_ECHO_AUDIOFIRE_4 0x000af4 +#define MODEL_ECHO_AUDIOFIRE_8 0x000af8 +/* #define MODEL_ECHO_AUDIOFIRE_8A 0x?????? */ +/* #define MODEL_ECHO_AUDIOFIRE_PRE8 0x?????? */
The 8a has ID 0x000af9; please also add the Pre8 ID.
Regards, Clemens
From: Takashi Sakamoto o-takashi@sakamocchi.jp
Fireworks can be controlled by device specific commands over IEEE1394 TA's AV/C Digital Interface Command Set. This file add some functions to execute the command.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/fireworks/fireworks_command.c | 535 ++++++++++++++++++++++++++ 1 file changed, 535 insertions(+) create mode 100644 sound/firewire/fireworks/fireworks_command.c
diff --git a/sound/firewire/fireworks/fireworks_command.c b/sound/firewire/fireworks/fireworks_command.c new file mode 100644 index 0000000..0006be2 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_command.c @@ -0,0 +1,535 @@ +/* + * fireworks_command.c - driver for Firewire devices from Echo Digital Audio + * + * Copyright (c) 2013 Takashi Sakamoto o-takashi@sakmocchi.jp + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver; if not, see http://www.gnu.org/licenses/. + * + * mostly based on FFADO's souce, which is + * Copyright (C) 2005-2008 by Pieter Palmers + * + */ + +#include "./fireworks.h" + +/* + * According to AV/C command specification, vendors can define own command. + * + * 1394 Trade Association's AV/C Digital Interface Command Set General + * Specification 4.2 (September 1, 2004) + * 9.6 VENDOR-DEPENDENT commands + * (to page 55) + * opcode: 0x00 + * operand[0-2]: company ID + * operand[3-]: vendor dependent data + * + * Echo's Fireworks(TM) utilize this specification. + * This module calls it as 'Echo Fireworks Commands' (a.k.a EFC). + * + * In EFC, + * Company ID is always 0x00. + * operand[0]: 0x00 + * operand[1]: 0x00 + * operand[2]: 0x00 + * + * Two blank operands exists in the beginning of the 'vendor dependent data' + * This seems to be for data alignment of 32 bit. + * operand[3]: 0x00 + * operand[4]: 0x00 + * + * Following these operands, EFC substance exists. + * At first, 6 data exist. we call these data as 'EFC fields'. + * Following to the 6 data, parameters for each commands exists. + * Most of parameters are 32 bit. But exception exists according to command. + * data[0]: Length of EFC substance. + * data[1]: EFC version + * data[2]: Sequence number. This is incremented at return value + * data[3]: EFC category. If greater than 1, + * EFC_CAT_HWINFO return extended fields. + * data[4]: EFC command + * data[5]: EFC return value in EFC response. + * data[6-]: parameters + * + * As a result, Echo's Fireworks doesn't need generic command sets. + */ + +/* + * AV/C parameters for Vendor-Dependent command + */ +#define AVC_CTS 0x00 +#define AVC_CTYPE 0x00 +#define AVC_SUBUNIT_TYPE 0x1F +#define AVC_SUBUNIT_ID 0x07 +#define AVC_OPCODE 0x00 +#define AVC_COMPANY_ID 0x00 + +struct avc_fields { + unsigned short cts:4; + unsigned short ctype:4; + unsigned short subunit_type:5; + unsigned short subunit_id:3; + unsigned short opcode:8; + unsigned long company_id:24; +}; + +/* + * EFC command + * quadlet parameters following to these fields. + */ +struct efc_fields { + u32 length; + u32 version; + u32 seqnum; + u32 category; + u32 command; + u32 retval; +}; + +/* for clock source and sampling rate */ +struct efc_clock { + u32 source; + u32 sampling_rate; + u32 index; +}; + +/* command categories */ +enum efc_category { + EFC_CAT_HWINFO = 0, + EFC_CAT_FLASH = 1, + EFC_CAT_TRANSPORT = 2, + EFC_CAT_HWCTL = 3, + EFC_CAT_MIXER_PHYS_OUT = 4, + EFC_CAT_MIXER_PHYS_IN = 5, + EFC_CAT_MIXER_PLAYBACK = 6, + EFC_CAT_MIXER_CAPTURE = 7, + EFC_CAT_MIXER_MONITOR = 8, + EFC_CAT_IOCONF = 9, +}; + +/* hardware info category commands */ +enum efc_cmd_hwinfo { + EFC_CMD_HWINFO_GET_CAPS = 0, + EFC_CMD_HWINFO_GET_POLLED = 1, + EFC_CMD_HWINFO_SET_EFR_ADDRESS = 2, + EFC_CMD_HWINFO_READ_SESSION_BLOCK = 3, + EFC_CMD_HWINFO_GET_DEBUG_INFO = 4, + EFC_CMD_HWINFO_SET_DEBUG_TRACKING = 5 +}; + +/* flash category commands */ +enum efc_cmd_flash { + EFC_CMD_FLASH_ERASE = 0, + EFC_CMD_FLASH_READ = 1, + EFC_CMD_FLASH_WRITE = 2, + EFC_CMD_FLASH_GET_STATUS = 3, + EFC_CMD_FLASH_GET_SESSION_BASE = 4, + EFC_CMD_FLASH_LOCK = 5 +}; + +/* hardware control category commands */ +enum efc_cmd_hwctl { + EFC_CMD_HWCTL_SET_CLOCK = 0, + EFC_CMD_HWCTL_GET_CLOCK = 1, + EFC_CMD_HWCTL_BSX_HANDSHAKE = 2, + EFC_CMD_HWCTL_CHANGE_FLAGS = 3, + EFC_CMD_HWCTL_GET_FLAGS = 4, + EFC_CMD_HWCTL_IDENTIFY = 5, + EFC_CMD_HWCTL_RECONNECT_PHY = 6 +}; +/* for flags */ +#define EFC_HWCTL_FLAG_MIXER_UNUSABLE 0x00 +#define EFC_HWCTL_FLAG_MIXER_USABLE 0x01 +#define EFC_HWCTL_FLAG_DIGITAL_PRO 0x02 +#define EFC_HWCTL_FLAG_DIGITAL_RAW 0x04 + +/* I/O config category commands */ +enum efc_cmd_ioconf { + EFC_CMD_IOCONF_SET_MIRROR = 0, + EFC_CMD_IOCONF_GET_MIRROR = 1, + EFC_CMD_IOCONF_SET_DIGITAL_MODE = 2, + EFC_CMD_IOCONF_GET_DIGITAL_MODE = 3, + EFC_CMD_IOCONF_SET_PHANTOM = 4, + EFC_CMD_IOCONF_GET_PHANTOM = 5, + EFC_CMD_IOCONF_SET_ISOC_MAP = 6, + EFC_CMD_IOCONF_GET_ISOC_MAP = 7, +}; + +/* return values in response */ +enum efc_retval { + EFC_RETVAL_OK = 0, + EFC_RETVAL_BAD = 1, + EFC_RETVAL_BAD_COMMAND = 2, + EFC_RETVAL_COMM_ERR = 3, + EFC_RETVAL_BAD_QUAD_COUNT = 4, + EFC_RETVAL_UNSUPPORTED = 5, + EFC_RETVAL_1394_TIMEOUT = 6, + EFC_RETVAL_DSP_TIMEOUT = 7, + EFC_RETVAL_BAD_RATE = 8, + EFC_RETVAL_BAD_CLOCK = 9, + EFC_RETVAL_BAD_CHANNEL = 10, + EFC_RETVAL_BAD_PAN = 11, + EFC_RETVAL_FLASH_BUSY = 12, + EFC_RETVAL_BAD_MIRROR = 13, + EFC_RETVAL_BAD_LED = 14, + EFC_RETVAL_BAD_PARAMETER = 15, + EFC_RETVAL_INCOMPLETE = 0x80000000 +}; + +/* for phys_in/phys_out/playback/capture/monitor category commands */ +enum snd_efw_mixer_cmd { + SND_EFW_MIXER_SET_GAIN = 0, + SND_EFW_MIXER_GET_GAIN = 1, + SND_EFW_MIXER_SET_MUTE = 2, + SND_EFW_MIXER_GET_MUTE = 3, + SND_EFW_MIXER_SET_SOLO = 4, + SND_EFW_MIXER_GET_SOLO = 5, + SND_EFW_MIXER_SET_PAN = 6, + SND_EFW_MIXER_GET_PAN = 7, + SND_EFW_MIXER_SET_NOMINAL = 8, + SND_EFW_MIXER_GET_NOMINAL = 9 +}; + +static int +efc_over_avc(struct snd_efw *efw, unsigned int category, + unsigned int command, + const u32 *params, unsigned int param_count, + void *response, unsigned int response_quadlets) +{ + int err; + + unsigned int cmdbuf_bytes; + __be32 *cmdbuf; + struct efc_fields *efc_fields; + u32 sequence_number; + unsigned int i; + + /* AV/C fields */ + struct avc_fields avc_fields = { + .cts = AVC_CTS, + .ctype = AVC_CTYPE, + .subunit_type = AVC_SUBUNIT_TYPE, + .subunit_id = AVC_SUBUNIT_ID, + .opcode = AVC_OPCODE, + .company_id = AVC_COMPANY_ID + }; + + /* calcurate buffer size*/ + if (param_count > response_quadlets) + cmdbuf_bytes = 32 + param_count * 4; + else + cmdbuf_bytes = 32 + response_quadlets * 4; + + /* keep buffer */ + cmdbuf = kzalloc(cmdbuf_bytes, GFP_KERNEL); + if (cmdbuf == NULL) + return -ENOMEM; + + /* fill AV/C fields */ + cmdbuf[0] = (avc_fields.cts << 28) | + (avc_fields.ctype << 24) | + (avc_fields.subunit_type << 19) | + (avc_fields.subunit_id << 16) | + (avc_fields.opcode << 8) | + (avc_fields.company_id >> 16 & 0xFF); + cmdbuf[1] = ((avc_fields.company_id >> 8 & 0xFF) << 24) | + ((avc_fields.company_id & 0xFF) << 16) | + (0x00 << 8) | + 0x00; + + /* fill EFC fields */ + efc_fields = (struct efc_fields *)(cmdbuf + 2); + efc_fields->length = sizeof(struct efc_fields) / 4 + param_count; + efc_fields->version = 1; + efc_fields->category = category; + efc_fields->command = command; + efc_fields->retval = 0; + + /* sequence number should keep consistency */ + spin_lock(&efw->lock); + efc_fields->seqnum = efw->sequence_number++; + sequence_number = efw->sequence_number; + spin_unlock(&efw->lock); + + /* fill EFC parameters */ + for (i = 0; i < param_count; i += 1) + cmdbuf[8 + i] = params[i]; + + /* for endian-ness */ + for (i = 0; i < (cmdbuf_bytes / 4); i += 1) + cmdbuf[i] = cpu_to_be32(cmdbuf[i]); + + /* if return value is positive, it means return bytes */ + err = fcp_avc_transaction(efw->unit, + cmdbuf, cmdbuf_bytes, + cmdbuf, cmdbuf_bytes, 0x00); + if (err < 0) + goto end; + + /* for endian-ness */ + for (i = 0; i < (err / 4); i += 1) + cmdbuf[i] = cpu_to_be32(cmdbuf[i]); + + /* parse AV/C fields */ + avc_fields.cts = cmdbuf[0] >> 28; + avc_fields.ctype = cmdbuf[0] >> 24 & 0x0F; + avc_fields.subunit_type = cmdbuf[0] >> 19 & 0x1F; + avc_fields.subunit_id = cmdbuf[0] >> 16 & 0x07; + avc_fields.opcode = cmdbuf[0] >> 8 & 0xFF; + avc_fields.company_id = ((cmdbuf[0] & 0xFF) << 16) | + ((cmdbuf[1] >> 24 & 0xFF) << 8) | + (cmdbuf[1] >> 16 & 0xFF); + + /* check AV/C fields */ + if ((avc_fields.cts != AVC_CTS) || + (avc_fields.ctype != 0x09) || /* ACCEPTED */ + (avc_fields.subunit_type != AVC_SUBUNIT_TYPE) || + (avc_fields.subunit_id != AVC_SUBUNIT_ID) || + (avc_fields.opcode != AVC_OPCODE) || + (avc_fields.company_id != AVC_COMPANY_ID)) { + snd_printk(KERN_INFO "AV/C Failed: 0x%X 0x%X 0x%X 0x%X 0x%X, 0x%X\n", + avc_fields.cts, avc_fields.ctype, + avc_fields.subunit_type, avc_fields.subunit_id, + avc_fields.opcode, avc_fields.company_id); + err = -EIO; + goto end; + } + + /* check EFC response fields */ + efc_fields = (struct efc_fields *)(cmdbuf + 2); + if ((efc_fields->seqnum != sequence_number) | + (efc_fields->version < 1) | + (efc_fields->category != category) | + (efc_fields->command != command) | + (efc_fields->retval != EFC_RETVAL_OK)) { + snd_printk(KERN_INFO "EFC Failed [%u/%u/%u]: %X\n", + efc_fields->version, efc_fields->category, + efc_fields->command, efc_fields->retval); + err = -EIO; + goto end; + } + + /* fill response buffer */ + memset(response, 0, response_quadlets); + if (response_quadlets > efc_fields->length) + response_quadlets = efc_fields->length; + memcpy(response, cmdbuf + 8, response_quadlets * 4); + + err = 0; +end: + kfree(cmdbuf); + return err; +} + +int snd_efw_command_identify(struct snd_efw *efw) +{ + return efc_over_avc(efw, EFC_CAT_HWCTL, + EFC_CMD_HWCTL_IDENTIFY, + NULL, 0, NULL, 0); +} + +int snd_efw_command_get_hwinfo(struct snd_efw *efw, + struct snd_efw_hwinfo *hwinfo) +{ + u32 *tmp; + int i; + int count; + int err = efc_over_avc(efw, EFC_CAT_HWINFO, + EFC_CMD_HWINFO_GET_CAPS, + NULL, 0, hwinfo, sizeof(*hwinfo) / 4); + if (err < 0) + goto end; + + /* arrangement for endianness */ + count = HWINFO_NAME_SIZE_BYTES / 4; + tmp = (u32 *)&hwinfo->vendor_name; + for (i = 0; i < count; i += 1) + tmp[i] = cpu_to_be32(tmp[i]); + tmp = (u32 *)&hwinfo->model_name; + for (i = 0; i < count; i += 1) + tmp[i] = cpu_to_be32(tmp[i]); + + count = sizeof(struct snd_efw_phys_group) * HWINFO_MAX_CAPS_GROUPS / 4; + tmp = (u32 *)&hwinfo->out_groups; + for (i = 0; i < count; i += 1) + tmp[i] = cpu_to_be32(tmp[i]); + tmp = (u32 *)&hwinfo->in_groups; + for (i = 0; i < count; i += 1) + tmp[i] = cpu_to_be32(tmp[i]); + + /* ensure terminated */ + hwinfo->vendor_name[HWINFO_NAME_SIZE_BYTES - 1] = '\0'; + hwinfo->model_name[HWINFO_NAME_SIZE_BYTES - 1] = '\0'; + + err = 0; +end: + return err; +} + +int snd_efw_command_get_phys_meters(struct snd_efw *efw, + struct snd_efw_phys_meters *meters, + int len) +{ + return efc_over_avc(efw, EFC_CAT_HWINFO, + EFC_CMD_HWINFO_GET_POLLED, + NULL, 0, meters, len / 4); +} + +static int +command_get_clock(struct snd_efw *efw, struct efc_clock *clock) +{ + return efc_over_avc(efw, EFC_CAT_HWCTL, + EFC_CMD_HWCTL_GET_CLOCK, + NULL, 0, clock, sizeof(struct efc_clock) / 4); +} + +static int +command_set_clock(struct snd_efw *efw, + int source, int sampling_rate) +{ + int err; + + struct efc_clock clock = {0}; + + /* check arguments */ + if ((source < 0) && (sampling_rate < 0)) { + err = -EINVAL; + goto end; + } + + /* get current status */ + err = command_get_clock(efw, &clock); + if (err < 0) + goto end; + + /* no need */ + if ((clock.source == source) && + (clock.sampling_rate == sampling_rate)) + goto end; + + /* set params */ + if ((source >= 0) && (clock.source != source)) + clock.source = source; + if ((sampling_rate > 0) && (clock.sampling_rate != sampling_rate)) + clock.sampling_rate = sampling_rate; + clock.index = 0; + + err = efc_over_avc(efw, EFC_CAT_HWCTL, + EFC_CMD_HWCTL_SET_CLOCK, + (u32 *)&clock, 3, NULL, 0); + + err = 0; +end: + return err; +} + +int snd_efw_command_get_clock_source(struct snd_efw *efw, + enum snd_efw_clock_source *source) +{ + int err; + struct efc_clock clock = {0}; + + err = command_get_clock(efw, &clock); + if (err >= 0) + *source = clock.source; + + return err; +} + +int snd_efw_command_set_clock_source(struct snd_efw *efw, + enum snd_efw_clock_source source) +{ + return command_set_clock(efw, source, -1); +} + +int snd_efw_command_get_sampling_rate(struct snd_efw *efw, + int *sampling_rate) +{ + int err; + struct efc_clock clock = {0}; + + err = command_get_clock(efw, &clock); + if (err >= 0) + *sampling_rate = clock.sampling_rate; + + return err; +} + +int +snd_efw_command_set_sampling_rate(struct snd_efw *efw, int sampling_rate) +{ + return command_set_clock(efw, -1, sampling_rate); +} + +int snd_efw_command_get_iec60958_format(struct snd_efw *efw, + enum snd_efw_iec60958_format *format) +{ + int err; + u32 flag = {0}; + + err = efc_over_avc(efw, EFC_CAT_HWCTL, + EFC_CMD_HWCTL_GET_FLAGS, + NULL, 0, &flag, 1); + if (err >= 0) { + if (flag & EFC_HWCTL_FLAG_DIGITAL_PRO) + *format = SND_EFW_IEC60958_FORMAT_PROFESSIONAL; + else + *format = SND_EFW_IEC60958_FORMAT_CONSUMER; + } + + return err; +} + +int snd_efw_command_set_iec60958_format(struct snd_efw *efw, + enum snd_efw_iec60958_format format) +{ + /* + * mask[0]: for set + * mask[1]: for clear + */ + u32 mask[2] = {0}; + + if (format == SND_EFW_IEC60958_FORMAT_PROFESSIONAL) + mask[0] = EFC_HWCTL_FLAG_DIGITAL_PRO; + else + mask[1] = EFC_HWCTL_FLAG_DIGITAL_PRO; + + return efc_over_avc(efw, EFC_CAT_HWCTL, + EFC_CMD_HWCTL_CHANGE_FLAGS, + (u32 *)mask, 2, NULL, 0); +} + +int snd_efw_command_get_digital_interface(struct snd_efw *efw, + enum snd_efw_digital_interface *digital_interface) +{ + int err; + u32 value = 0; + + err = efc_over_avc(efw, EFC_CAT_IOCONF, + EFC_CMD_IOCONF_GET_DIGITAL_MODE, + NULL, 0, &value, 1); + + if (err >= 0) + *digital_interface = value; + + return err; +} + +int snd_efw_command_set_digital_interface(struct snd_efw *efw, + enum snd_efw_digital_interface digital_interface) +{ + u32 value = digital_interface; + + return efc_over_avc(efw, EFC_CAT_IOCONF, + EFC_CMD_IOCONF_SET_DIGITAL_MODE, + &value, 1, NULL, 0); +}
o-takashi@sakamocchi.jp wrote:
Fireworks can be controlled by device specific commands over IEEE1394 TA's AV/C Digital Interface Command Set.
It can also be controlled by sending the commands directly (without the AV/C header) to address 0xecc000000000. This is one of the differences between FFADO and the Echo drivers, and might be related to the problems that FFADO has with the latest Fireworks firmware versions.
Do you have the latest firmware in your AFPre8?
+struct avc_fields {
- unsigned short cts:4;
- unsigned short ctype:4; ...
The layout of bit fields are very unportable. And if you do *not* actually rely on the memory layout, you do not actually save space because the code to access the fields becomes more complex.
+efc_over_avc(struct snd_efw *efw, unsigned int category,
- /* AV/C fields */
- struct avc_fields avc_fields = {
.cts = AVC_CTS,
.ctype = AVC_CTYPE,
.subunit_type = AVC_SUBUNIT_TYPE,
.subunit_id = AVC_SUBUNIT_ID,
.opcode = AVC_OPCODE,
.company_id = AVC_COMPANY_ID
- };
The compiler has to construct this on the stack. Use "static const".
- /* calcurate buffer size*/
calculate
- if (param_count > response_quadlets)
cmdbuf_bytes = 32 + param_count * 4;
- else
cmdbuf_bytes = 32 + response_quadlets * 4;
max()
+int snd_efw_command_identify(struct snd_efw *efw) +{
- return efc_over_avc(efw, EFC_CAT_HWCTL,
EFC_CMD_HWCTL_IDENTIFY,
That was part of my old driver, but most if this device-specific stuff is not needed for a simple kernel streaming driver.
Regards, Clemens
Clemens,
Do you have the latest firmware in your AFPre8?
I use AFPre8 with firmware version 5.5 (0x5050000). I have never updated it after I bought so it's factory setting.
It can also be controlled by sending the commands directly (without the AV/C header) to address 0xecc000000000. This is one of the differences between FFADO and the Echo drivers,
I got packet dump and confirm there is no EFC over AVC. As you say, it commands to 0xecc0 00000 0000 and receives response at 0xecc 8000 0000.
Then I implemented it and it looks to work fine. I also think it good to use EFC without AVC.
and might be related to the problems that FFADO has with the latest Fireworks firmware versions.
Later I'll update firmware in my device and search for this issue. But it may be far future...
That was part of my old driver, but most if this device-specific stuff is not needed for a simple kernel streaming driver.
OK. I use this transaction to confirm the device can respond but it's needless. I'll remove it till posting improved patch.
Regards
Takashi Sakamoto o-takashi@sakamocchi.jp
(Jun 3 2013 20:18), Clemens Ladisch wrote:
o-takashi@sakamocchi.jp wrote:
Fireworks can be controlled by device specific commands over IEEE1394 TA's AV/C Digital Interface Command Set.
It can also be controlled by sending the commands directly (without the AV/C header) to address 0xecc000000000. This is one of the differences between FFADO and the Echo drivers, and might be related to the problems that FFADO has with the latest Fireworks firmware versions.
Do you have the latest firmware in your AFPre8?
+struct avc_fields {
- unsigned short cts:4;
- unsigned short ctype:4; ...
The layout of bit fields are very unportable. And if you do *not* actually rely on the memory layout, you do not actually save space because the code to access the fields becomes more complex.
+efc_over_avc(struct snd_efw *efw, unsigned int category,
- /* AV/C fields */
- struct avc_fields avc_fields = {
.cts = AVC_CTS,
.ctype = AVC_CTYPE,
.subunit_type = AVC_SUBUNIT_TYPE,
.subunit_id = AVC_SUBUNIT_ID,
.opcode = AVC_OPCODE,
.company_id = AVC_COMPANY_ID
- };
The compiler has to construct this on the stack. Use "static const".
- /* calcurate buffer size*/
calculate
- if (param_count > response_quadlets)
cmdbuf_bytes = 32 + param_count * 4;
- else
cmdbuf_bytes = 32 + response_quadlets * 4;
max()
+int snd_efw_command_identify(struct snd_efw *efw) +{
- return efc_over_avc(efw, EFC_CAT_HWCTL,
EFC_CMD_HWCTL_IDENTIFY,
That was part of my old driver, but most if this device-specific stuff is not needed for a simple kernel streaming driver.
Regards, Clemens
I got packet dump and confirm there is no EFC over AVC. As you say, it commands to 0xecc0 00000 0000 and receives response at 0xecc 8000 0000.
0xecc0 8000 0000 is correct address.
Regards
Takashi Sakamoto
(Jun 7 2013 02:33), Takashi Sakamoto wrote:
Clemens,
Do you have the latest firmware in your AFPre8?
I use AFPre8 with firmware version 5.5 (0x5050000). I have never updated it after I bought so it's factory setting.
It can also be controlled by sending the commands directly (without the AV/C header) to address 0xecc000000000. This is one of the differences between FFADO and the Echo drivers,
I got packet dump and confirm there is no EFC over AVC. As you say, it commands to 0xecc0 00000 0000 and receives response at 0xecc 8000 0000.
Then I implemented it and it looks to work fine. I also think it good to use EFC without AVC.
and might be related to the problems that FFADO has with the latest Fireworks firmware versions.
Later I'll update firmware in my device and search for this issue. But it may be far future...
That was part of my old driver, but most if this device-specific stuff is not needed for a simple kernel streaming driver.
OK. I use this transaction to confirm the device can respond but it's needless. I'll remove it till posting improved patch.
Regards
Takashi Sakamoto o-takashi@sakamocchi.jp
(Jun 3 2013 20:18), Clemens Ladisch wrote:
o-takashi@sakamocchi.jp wrote:
Fireworks can be controlled by device specific commands over IEEE1394 TA's AV/C Digital Interface Command Set.
It can also be controlled by sending the commands directly (without the AV/C header) to address 0xecc000000000. This is one of the differences between FFADO and the Echo drivers, and might be related to the problems that FFADO has with the latest Fireworks firmware versions.
Do you have the latest firmware in your AFPre8?
+struct avc_fields {
- unsigned short cts:4;
- unsigned short ctype:4; ...
The layout of bit fields are very unportable. And if you do *not* actually rely on the memory layout, you do not actually save space because the code to access the fields becomes more complex.
+efc_over_avc(struct snd_efw *efw, unsigned int category,
- /* AV/C fields */
- struct avc_fields avc_fields = {
.cts = AVC_CTS,
.ctype = AVC_CTYPE,
.subunit_type = AVC_SUBUNIT_TYPE,
.subunit_id = AVC_SUBUNIT_ID,
.opcode = AVC_OPCODE,
.company_id = AVC_COMPANY_ID
- };
The compiler has to construct this on the stack. Use "static const".
- /* calcurate buffer size*/
calculate
- if (param_count > response_quadlets)
cmdbuf_bytes = 32 + param_count * 4;
- else
cmdbuf_bytes = 32 + response_quadlets * 4;
max()
+int snd_efw_command_identify(struct snd_efw *efw) +{
- return efc_over_avc(efw, EFC_CAT_HWCTL,
EFC_CMD_HWCTL_IDENTIFY,
That was part of my old driver, but most if this device-specific stuff is not needed for a simple kernel streaming driver.
Regards, Clemens
From: Takashi Sakamoto o-takashi@sakamocchi.jp
This patch adds ALSA's control interfaces to control Fireworks by executing Echo Fireworks Command.
Currently the policy of module implementation hope much controls in user-land. So this module add any controls needed to decide the number of channels in AMDTP stream, clock source, and to get physical metering.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/fireworks/fireworks_control.c | 537 ++++++++++++++++++++++++++ 1 file changed, 537 insertions(+) create mode 100644 sound/firewire/fireworks/fireworks_control.c
diff --git a/sound/firewire/fireworks/fireworks_control.c b/sound/firewire/fireworks/fireworks_control.c new file mode 100644 index 0000000..1599579 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_control.c @@ -0,0 +1,537 @@ +/* + * fireworks/control.c - driver for Firewire devices from Echo Digital Audio + * + * Copyright (c) 2013 Takashi Sakamoto o-takashi@sakamocchi.jp + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver; if not, see http://www.gnu.org/licenses/. + */ + +#include "fireworks.h" + +/* + * Currently this module support any controls related to decision of channels + * in stream, hardware metering and digital format. Users should utilize tools + * which FFADO project developed. + */ + +/* + * Physical metering: + * the value in unavvailable channels is zero. + */ +static int +physical_metering_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + struct snd_efw *efw = ctl->private_data; + + info->type = SNDRV_CTL_ELEM_TYPE_BYTES; + info->count = (efw->input_meter_counts + efw->output_meter_counts) + * 4 + 2; + + return 0; +} +static int +physical_metering_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct snd_efw *efw = ctl->private_data; + struct snd_efw_phys_meters *meters; + int base = sizeof(struct snd_efw_phys_meters); + int count = efw->input_meter_counts + efw->output_meter_counts; + u32 *dst, *src; + int i, err; + + meters = kzalloc(base + count * 4, GFP_KERNEL); + if (meters == NULL) + return -ENOMEM; + + err = snd_efw_command_get_phys_meters(efw, meters, base + count * 4); + if (err < 0) + goto end; + + value->value.bytes.data[0] = efw->input_meter_counts; + value->value.bytes.data[1] = efw->output_meter_counts; + + dst = (u32 *)(value->value.bytes.data + 2); + src = meters->values; + + for (i = 0; i < efw->input_meter_counts; i += 1) + dst[i] = src[efw->output_meter_counts + i]; + + for (i = 0; i < efw->output_meter_counts; i += 1) + dst[i + efw->input_meter_counts] = src[i]; + + err = 0; +end: + kfree(meters); + return err; +} +static const +struct snd_kcontrol_new physical_metering = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "Physical Metering", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = physical_metering_info, + .get = physical_metering_get +}; + +/* + * Global Control: Digital capture and playback mode + * + * S/PDIF or ADAT, Coaxial or Optical + * snd_efw_hwinfo.flags include a flag for this control. + */ +static char *digital_iface_descs[] = {"S/PDIF Coaxial", "ADAT Coaxial", + "S/PDIF Optical", "ADAT Optical"}; +static int +control_digital_interface_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + struct snd_efw *efw = snd_kcontrol_chip(kctl); + int value, i; + + einf->value.enumerated.items = 0; + for (i = 0; i < ARRAY_SIZE(digital_iface_descs); i += 1) { + if ((1 << i) & efw->supported_digital_interface) + einf->value.enumerated.items += 1; + } + + einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + einf->count = 1; + + if (einf->value.enumerated.item >= einf->value.enumerated.items) + einf->value.enumerated.item = einf->value.enumerated.items - 1; + + /* skip unsupported clock source */ + value = einf->value.enumerated.item; + for (i = 0; i < ARRAY_SIZE(digital_iface_descs); i += 1) { + if (!((1 << i) & efw->supported_digital_interface)) + continue; + else if (value == 0) + break; + else + value -= 1; + } + + strcpy(einf->value.enumerated.name, digital_iface_descs[i]); + + return 0; +} +static int +control_digital_interface_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + int err = 0; + + struct snd_efw *efw = snd_kcontrol_chip(kctl); + enum snd_efw_digital_interface digital_interface; + int i; + + /* get current index */ + err = snd_efw_command_get_digital_interface(efw, &digital_interface); + if (err < 0) + goto end; + + /* check clock source */ + if ((digital_interface < 0) && + (ARRAY_SIZE(digital_iface_descs) < digital_interface)) + goto end; + + /* generate user value */ + uval->value.enumerated.item[0] = 0; + for (i = 0; i < digital_interface; i += 1) { + if ((1 << i) & efw->supported_digital_interface) + uval->value.enumerated.item[0] += 1; + } + +end: + return err; +} +static int +control_digital_interface_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + int changed = 0; + + struct snd_efw *efw = snd_kcontrol_chip(kctl); + int index, value; + + /* get index from user value */ + value = uval->value.enumerated.item[0]; + for (index = 0; index < ARRAY_SIZE(digital_iface_descs); index++) { + /* not supported */ + if (!((1 << index) & efw->supported_digital_interface)) + continue; + else if (value == 0) + break; + else + value -= 1; + } + + /* set clock */ + if (snd_efw_command_set_digital_interface(efw, index) < 0) + goto end; + + changed = 1; + +end: + return changed; +} + +/* + * Global Control: S/PDIF format are selectable from "Professional/Consumer". + * Consumer: IEC-60958 Digital audio interface + * Part 3:Consumer applications + * Professional: IEC-60958 Digital audio interface + * Part 4: Professional applications + * + * snd_efw_hwinfo.flags include a flag for this control + */ +static char *spdif_format_descs[] = {"Consumer", "Professional"}; +static int +control_spdif_format_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + einf->count = 1; + einf->value.enumerated.items = ARRAY_SIZE(spdif_format_descs); + + if (einf->value.enumerated.item >= einf->value.enumerated.items) + einf->value.enumerated.item = einf->value.enumerated.items - 1; + + strcpy(einf->value.enumerated.name, + spdif_format_descs[einf->value.enumerated.item]); + + return 0; +} +static int +control_spdif_format_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uvalue) +{ + int err = 0; + + struct snd_efw *efw = snd_kcontrol_chip(kctl); + enum snd_efw_iec60958_format format; + + err = snd_efw_command_get_iec60958_format(efw, &format); + if (err >= 0) + uvalue->value.enumerated.item[0] = format; + + return 0; +} +static int +control_spdif_format_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + int changed = 0; + + struct snd_efw *efw = snd_kcontrol_chip(kctl); + int value = uval->value.enumerated.item[0]; + + if (value < ARRAY_SIZE(spdif_format_descs)) { + if (snd_efw_command_set_iec60958_format(efw, value) < 0) + goto end; + changed = 1; + } + +end: + return changed; +} + +/* + * Global Control: Sampling Rate Control + * + * snd_efw_hwinfo.min_sample_rate and struct efc_hwinfo.max_sample_rate + * is a minimum and maximum sampling rate + */ +static char *sampling_rate_descs[] = {"5512Hz", "8000Hz", "11025Hz", + "16000Hz", "22050Hz", "32000Hz", + "44100Hz", "48000Hz", "64000Hz", + "88200Hz", "96000Hz", "176400Hz", + "192000Hz"}; +static int sampling_rates[] = {5512, 8000, 11025, 16000, 22500, 32000, 44100, + 48000, 64000, 88200, 96000, 176400, 192000}; +static int +control_sampling_rate_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + struct snd_efw *efw = snd_kcontrol_chip(kctl); + int value, i; + + /* maximum value for user */ + einf->value.enumerated.items = 0; + for (i = 0; i < ARRAY_SIZE(sampling_rate_descs); i += 1) { + if ((1 << i) & efw->supported_sampling_rate) + einf->value.enumerated.items += 1; + } + + einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + einf->count = 1; + + if (einf->value.enumerated.item >= einf->value.enumerated.items) + einf->value.enumerated.item = einf->value.enumerated.items - 1; + + /* skip unsupported clock source */ + value = einf->value.enumerated.item; + for (i = 0; i < ARRAY_SIZE(sampling_rate_descs); i += 1) { + if (!((1 << i) & efw->supported_sampling_rate)) + continue; + else if (value == 0) + break; + else + value -= 1; + } + + strcpy(einf->value.enumerated.name, sampling_rate_descs[i]); + + return 0; +} +static int +control_sampling_rate_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + int err = 0; + + struct snd_efw *efw = snd_kcontrol_chip(kctl); + int sampling_rate; + int index, i; + + /* get current sampling rate */ + err = snd_efw_command_get_sampling_rate(efw, &sampling_rate); + if (err < 0) + goto end; + + /* get index */ + for (index = 0; index < ARRAY_SIZE(sampling_rates); index += 1) { + if (sampling_rates[index] == sampling_rate) + break; + } + + /* get user value */ + uval->value.enumerated.item[0] = 0; + for (i = 0; i < index; i += 1) { + if ((1 << i) & efw->supported_sampling_rate) + uval->value.enumerated.item[0] += 1; + } + +end: + return err; +} +static int +control_sampling_rate_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + int changed = 0; + + struct snd_efw *efw = snd_kcontrol_chip(kctl); + int index, value; + + /* get index from user value */ + value = uval->value.enumerated.item[0]; + for (index = 0; index < ARRAY_SIZE(sampling_rates); index += 1) { + /* not supported */ + if (!((1 << index) & efw->supported_sampling_rate)) + continue; + else if (value == 0) + break; + else + value -= 1; + } + + /* set sampling rate */ + if (snd_efw_command_set_sampling_rate(efw, sampling_rates[index]) < 0) + goto end; + + changed = 1; + +end: + return changed; +} + +/* + * Global Control: Clock Source Control + * + * snd_efw_hwinfo.supported_clocks is a flags for this control + */ +static char *clock_src_descs[] = {"Internal", "SYT Match", "Word", + "S/PDIF", "ADAT1", "ADAT2"}; +static int +control_clock_source_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + struct snd_efw *efw = snd_kcontrol_chip(kctl); + int value, i; + + /* skip unsupported clock source */ + einf->value.enumerated.items = 0; + for (i = 0; i < ARRAY_SIZE(clock_src_descs); i += 1) { + if ((1 << i) & efw->supported_clock_source) + einf->value.enumerated.items += 1; + } + + einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + einf->count = 1; + + if (einf->value.enumerated.item >= einf->value.enumerated.items) + einf->value.enumerated.item = einf->value.enumerated.items - 1; + + /* skip unsupported clock source */ + value = einf->value.enumerated.item; + for (i = 0; i < ARRAY_SIZE(clock_src_descs); i += 1) { + if (!((1 << i) & efw->supported_clock_source)) + continue; + else if (value == 0) + break; + else + value -= 1; + } + + strcpy(einf->value.enumerated.name, clock_src_descs[i]); + + return 0; +} +static int +control_clock_source_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + int err = 0; + + struct snd_efw *efw = snd_kcontrol_chip(kctl); + enum snd_efw_clock_source clock_source; + int i; + + /* get current index */ + err = snd_efw_command_get_clock_source(efw, &clock_source); + if (err < 0) + goto end; + + /* check clock source */ + if ((clock_source < 0) && (ARRAY_SIZE(clock_src_descs) < clock_source)) + goto end; + + /* generate user value */ + uval->value.enumerated.item[0] = 0; + for (i = 0; i < clock_source; i += 1) { + if ((1 << i) & efw->supported_clock_source) + uval->value.enumerated.item[0] += 1; + } + +end: + return err; +} +static int +control_clock_source_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + int changed = 0; + + struct snd_efw *efw = snd_kcontrol_chip(kctl); + int index, value; + + /* get index from user value */ + value = uval->value.enumerated.item[0]; + for (index = 0; index < ARRAY_SIZE(clock_src_descs); index += 1) { + /* not supported */ + if (!((1 << index) & efw->supported_clock_source)) + continue; + else if (value == 0) + break; + else + value -= 1; + } + + /* set clock */ + if (snd_efw_command_set_clock_source(efw, index) < 0) + goto end; + + changed = 1; + +end: + return changed; +} + +static struct snd_kcontrol_new global_clock_source_control = { + .name = "Clock Source", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = control_clock_source_info, + .get = control_clock_source_get, + .put = control_clock_source_put +}; + +static struct snd_kcontrol_new global_sampling_rate_control = { + .name = "Sampling Rate", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = control_sampling_rate_info, + .get = control_sampling_rate_get, + .put = control_sampling_rate_put +}; + +static struct snd_kcontrol_new global_digital_interface_control = { + .name = "Digital Mode", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = control_digital_interface_info, + .get = control_digital_interface_get, + .put = control_digital_interface_put +}; + +static struct snd_kcontrol_new global_iec60958_format_control = { + .name = "S/PDIF Format", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = control_spdif_format_info, + .get = control_spdif_format_get, + .put = control_spdif_format_put +}; + +int snd_efw_create_control_devices(struct snd_efw *efw) +{ + int err; + struct snd_kcontrol *kctl; + + kctl = snd_ctl_new1(&physical_metering, efw); + err = snd_ctl_add(efw->card, kctl); + if (err < 0) + goto end; + + if (efw->supported_clock_source > 0) { + kctl = snd_ctl_new1(&global_clock_source_control, efw); + err = snd_ctl_add(efw->card, kctl); + if (err < 0) + goto end; + } + if (efw->supported_sampling_rate > 0) { + kctl = snd_ctl_new1(&global_sampling_rate_control, efw); + err = snd_ctl_add(efw->card, kctl); + if (err < 0) + goto end; + efw->control_id_sampling_rate = &kctl->id; + } + if (efw->supported_digital_interface > 0) { + kctl = snd_ctl_new1(&global_digital_interface_control, efw); + err = snd_ctl_add(efw->card, kctl); + if (err < 0) + goto end; + + kctl = snd_ctl_new1(&global_iec60958_format_control, efw); + err = snd_ctl_add(efw->card, kctl); + if (err < 0) + goto end; + } + + err = 0; +end: + return err; +}
From: Takashi Sakamoto o-takashi@sakamocchi.jp
Fireworks is IEC 61883-1 and 6 compliant framework (not fully). This file utilize snd-firewire-lib module to handle AMDTP stream.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/fireworks/fireworks_stream.c | 107 +++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 sound/firewire/fireworks/fireworks_stream.c
diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c new file mode 100644 index 0000000..35b7a8e --- /dev/null +++ b/sound/firewire/fireworks/fireworks_stream.c @@ -0,0 +1,107 @@ +/* + * fireworks_stream.c - driver for Firewire devices from Echo Digital Audio + * + * Copyright (c) 2013 Takashi Sakamoto + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver; if not, see http://www.gnu.org/licenses/. + */ +#include "./fireworks.h" + +int snd_efw_stream_init(struct snd_efw *efw, struct amdtp_stream *stream) +{ + struct cmp_connection *connection; + enum cmp_direction c_dir; + enum amdtp_stream_direction s_dir; + int err; + + if (stream == &efw->receive_stream) { + connection = &efw->output_connection; + c_dir = CMP_OUTPUT; + s_dir = AMDTP_STREAM_RECEIVE; + } else { + connection = &efw->input_connection; + c_dir = CMP_INPUT; + s_dir = AMDTP_STREAM_TRANSMIT; + } + + err = cmp_connection_init(connection, efw->unit, c_dir, 0); + if (err < 0) + goto end; + + err = amdtp_stream_init(stream, efw->unit, s_dir, CIP_NONBLOCKING); + if (err < 0) { + cmp_connection_destroy(connection); + goto end; + } + +end: + return err; +} + +int snd_efw_stream_start(struct snd_efw *efw, struct amdtp_stream *stream) +{ + struct cmp_connection *connection; + int err = 0; + + /* already running */ + if (amdtp_stream_running(stream)) + goto end; + + if (stream == &efw->receive_stream) + connection = &efw->output_connection; + else + connection = &efw->input_connection; + + /* establish connection via CMP */ + err = cmp_connection_establish(connection, + amdtp_stream_get_max_payload(stream)); + if (err < 0) + goto end; + + /* start amdtp stream */ + err = amdtp_stream_start(stream, + connection->resources.channel, + connection->speed); + if (err < 0) + cmp_connection_break(connection); + +end: + return err; +} + +void snd_efw_stream_stop(struct snd_efw *efw, struct amdtp_stream *stream) +{ + if (!amdtp_stream_running(stream)) + goto end; + + amdtp_stream_stop(stream); + + if (stream == &efw->receive_stream) + cmp_connection_break(&efw->output_connection); + else + cmp_connection_break(&efw->input_connection); +end: + return; +} + +void snd_efw_stream_destroy(struct snd_efw *efw, struct amdtp_stream *stream) +{ + snd_efw_stream_stop(efw, stream); + + if (stream == &efw->receive_stream) + cmp_connection_destroy(&efw->output_connection); + else + cmp_connection_destroy(&efw->input_connection); + + return; +}
From: Takashi Sakamoto o-takashi@sakamocchi.jp
MIDI stream is multiplexed into AMDTP stream with PCM stream. Then this module need to arrange the start of both streams. Currently the rules in comment of file is used.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/fireworks/fireworks_midi.c | 236 +++++++++++++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 sound/firewire/fireworks/fireworks_midi.c
diff --git a/sound/firewire/fireworks/fireworks_midi.c b/sound/firewire/fireworks/fireworks_midi.c new file mode 100644 index 0000000..2b8cef4 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_midi.c @@ -0,0 +1,236 @@ +/* + * fireworks_midi.c - driver for Firewire devices from Echo Digital Audio + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013 Takashi Sakamoto + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver; if not, see http://www.gnu.org/licenses/. + */ +#include "fireworks.h" + +/* + * According to MMA/AMEI-027, MIDI stream is multiplexed with PCM stream in an + * Firewire isochronous stream. And Fireworks need to handle input and output + * stream with the same sampling rate. Then this module use the rules below: + * + * [MIDI stream side] + * 1.When no stream in both direction is started, start stream with 48000 + * 2.When stream in opposite direction is started, start stream with the same + * sampling rate. + * 3.When stream in the same direction has PCM stream and request to stop MIDI + * stream, don't stop stream itself. + * [PCM stream side] + * 1.When stream in the both direction is started and has no PCM stream, stop + * the stream because it include just MIDI stream. Then restart it with + * requested sampling rate by PCM component. + * 2.When MIDI stream is going to be closed but PCM stream is still running, + * the stream is kept to be running. + */ + +static int +midi_open(struct snd_rawmidi_substream *substream) +{ + struct snd_efw *efw = substream->rmidi->private_data; + struct amdtp_stream *stream; + struct amdtp_stream *opposite; + int *pcm_channels; + int run, mode, sampling_rate = 48000; + int err; + + if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT) { + stream = &efw->receive_stream; + pcm_channels = efw->pcm_capture_channels; + opposite = &efw->transmit_stream; + } else { + stream = &efw->transmit_stream; + pcm_channels = efw->pcm_playback_channels; + opposite = &efw->receive_stream; + } + + /* check other streams */ + run = stream->midi_triggered; + + /* register pointer */ + amdtp_stream_midi_insert(stream, substream); + + /* the other MIDI streams running */ + if (run > 0) { + err = 0; + goto end; + } + /* PCM stream starts the stream */ + else if (amdtp_stream_running(stream)) { + err = 0; + goto end; + } + + /* opposite stream is running then use the same sampling rate */ + if (amdtp_stream_running(opposite)) + err = snd_efw_command_get_sampling_rate(efw, &sampling_rate); + else { + err = snd_efw_command_set_sampling_rate(efw, sampling_rate); + snd_ctl_notify(efw->card, SNDRV_CTL_EVENT_MASK_VALUE, + efw->control_id_sampling_rate); + } + if (err < 0) + goto end; + mode = snd_efw_get_multiplier_mode(sampling_rate); + amdtp_stream_set_rate(stream, sampling_rate); + amdtp_stream_set_pcm(stream, pcm_channels[mode]); + + /* start stream just for MIDI */ + err = snd_efw_stream_start(efw, stream); + if (err < 0) + goto end; + +end: + return err; +} + +static int +midi_close(struct snd_rawmidi_substream *substream) +{ + struct snd_efw *efw = substream->rmidi->private_data; + struct amdtp_stream *stream; + + if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT) + stream = &efw->receive_stream; + else + stream = &efw->transmit_stream; + + /* unregister pointer */ + amdtp_stream_midi_extract(stream, substream); + + /* the other streams is running */ + if (amdtp_stream_midi_running(stream) > 0) + goto end; + + /* PCM stream is running */ + if (stream->pcm) + goto end; + + /* stop stream */ + snd_efw_stream_stop(efw, stream); + +end: + return 0; +} + +static void +midi_trigger(struct snd_rawmidi_substream *substream, int up) +{ + struct snd_efw *efw = substream->rmidi->private_data; + unsigned long *midi_triggered; + unsigned long flags; + + if (substream->stream == SNDRV_RAWMIDI_STREAM_INPUT) + midi_triggered = &efw->receive_stream.midi_triggered; + else + midi_triggered = &efw->transmit_stream.midi_triggered; + + spin_lock_irqsave(&efw->lock, flags); + + /* bit table shows MIDI stream has data or not */ + if (up) + __set_bit(substream->number, midi_triggered); + else + __clear_bit(substream->number, midi_triggered); + + spin_unlock_irqrestore(&efw->lock, flags); + + return; +} + +static struct snd_rawmidi_ops midi_output_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_trigger, +}; + +static struct snd_rawmidi_ops midi_input_ops = { + .open = midi_open, + .close = midi_close, + .trigger = midi_trigger, +}; + +static void +set_midi_substream_names(struct snd_efw *efw, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + if (str->substream_count > 2) + return; + + list_for_each_entry(subs, &str->substreams, list) + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", efw->card->shortname, subs->number + 1); +} + +int snd_efw_create_midi_devices(struct snd_efw *efw) +{ + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + struct snd_rawmidi_substream *subs; + int err; + + /* check the number of midi stream */ + if ((efw->midi_input_ports > AMDTP_MAX_MIDI_STREAMS) | + (efw->midi_output_ports > AMDTP_MAX_MIDI_STREAMS)) + return -EINVAL; + + /* create midi ports */ + err = snd_rawmidi_new(efw->card, efw->card->driver, 0, + efw->midi_output_ports, efw->midi_input_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", efw->card->shortname); + rmidi->private_data = efw; + + if (efw->midi_input_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &midi_input_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + + set_midi_substream_names(efw, str); + amdtp_stream_set_midi(&efw->receive_stream, + efw->midi_input_ports); + } + + if (efw->midi_output_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &midi_output_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + list_for_each_entry(subs, &str->substreams, list) + efw->transmit_stream.midi[subs->number] = subs; + + set_midi_substream_names(efw, str); + amdtp_stream_set_midi(&efw->transmit_stream, + efw->midi_output_ports); + } + + if ((efw->midi_output_ports > 0) && (efw->midi_input_ports > 0)) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +}
o-takashi@sakamocchi.jp wrote:
- [MIDI stream side]
- 1.When no stream in both direction is started, start stream with 48000
- 2.When stream in opposite direction is started, start stream with the same
- sampling rate.
- 3.When stream in the same direction has PCM stream and request to stop MIDI
- stream, don't stop stream itself.
- [PCM stream side]
- 1.When stream in the both direction is started and has no PCM stream, stop
- the stream because it include just MIDI stream. Then restart it with
- requested sampling rate by PCM component.
- 2.When MIDI stream is going to be closed but PCM stream is still running,
- the stream is kept to be running.
- */
These rules will become even more complex when handling playback/capture stream synchronization.
I think it would be a better idea to add some reference counting scheme for determining when an AMDTP stream needs to be running.
Regards, Clemens
From: Takashi Sakamoto o-takashi@sakamocchi.jp
This file includes PCM callbacks.
With PCM rules, the number of channels and sampling rate are automatically arranged according to device property.
Currently this module deal with digital and analog interface in the same PCM device. So there is no way for ALSA application to distinguish these interfaces.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/fireworks/fireworks_pcm.c | 530 ++++++++++++++++++++++++++++++ 1 file changed, 530 insertions(+) create mode 100644 sound/firewire/fireworks/fireworks_pcm.c
diff --git a/sound/firewire/fireworks/fireworks_pcm.c b/sound/firewire/fireworks/fireworks_pcm.c new file mode 100644 index 0000000..e97bfd8 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_pcm.c @@ -0,0 +1,530 @@ +/* + * fireworks_pcm.c - driver for Firewire devices from Echo Digital Audio + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013 Takashi Sakamoto + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver; if not, see http://www.gnu.org/licenses/. + */ +#include "./fireworks.h" + +/* + * NOTE: + * Fireworks changes its PCM channels according to its sampling rate. + * There are three modes. Here "capture" or "playback" is appplied to XXX. + * 0: 32.0- 48.0 kHz then snd_efw_hwinfo.nb_1394_XXX_channels applied + * 1: 88.2- 96.0 kHz then snd_efw_hwinfo.nb_1394_XXX_channels_2x applied + * 2: 176.4-192.0 kHz then snd_efw_hwinfo.nb_1394_XXX_channels_4x applied + * + * Then the number of PCM channels for analog input and output are always fixed + * but the number of PCM channels for digital input and output are differed. + * + * Additionally, according to AudioFire Owner's Manual Version 2.2, + * the number of PCM channels for digital input has more restriction + * depending on which digital interface is selected. + * - S/PDIF coaxial and optical : use input 1-2 + * - ADAT optical with 32.0-48.0 kHz : use input 1-8 + * - ADAT optical with 88.2-96.0 kHz : use input 1-4 (S/MUX format) + * If these restriction is applied, the number of channels in stream is decided + * according to above modes. + * + * Currently this module doesn't have rules for the latter. + */ +static unsigned int freq_table[] = { + /* multiplier mode 0 */ + [0] = 32000, + [1] = 44100, + [2] = 48000, + /* multiplier mode 1 */ + [3] = 88200, + [4] = 96000, + /* multiplier mode 2 */ + [5] = 176400, + [6] = 192000, +}; + +static inline int +get_multiplier_mode_with_index(int index) +{ + return ((int)index - 1) / 2; +} + +int snd_efw_get_multiplier_mode(int sampling_rate) +{ + int i; + for (i = 0; i < sizeof(freq_table); i += 1) + if (freq_table[i] == sampling_rate) + return get_multiplier_mode_with_index(i); + + return -1; +} + +static int +hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule, + struct snd_efw *efw, unsigned int *channels) +{ + 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 rate_bit; + int mode, i; + + for (i = 0; i < ARRAY_SIZE(freq_table); i += 1) { + /* skip unsupported sampling rate */ + rate_bit = snd_pcm_rate_to_rate_bit(freq_table[i]); + if (!(efw->supported_sampling_rate & rate_bit)) + continue; + + mode = get_multiplier_mode_with_index(i); + if (!snd_interval_test(c, channels[mode])) + continue; + + t.min = min(t.min, freq_table[i]); + t.max = max(t.max, freq_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, + struct snd_efw *efw, unsigned int *channels) +{ + 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 rate_bit; + int mode, i; + + for (i = 0; i < ARRAY_SIZE(freq_table); i += 1) { + /* skip unsupported sampling rate */ + rate_bit = snd_pcm_rate_to_rate_bit(freq_table[i]); + if (!(efw->supported_sampling_rate & rate_bit)) + continue; + + mode = get_multiplier_mode_with_index(i); + if (!snd_interval_test(r, freq_table[i])) + continue; + + t.min = min(t.min, channels[mode]); + t.max = max(t.max, channels[mode]); + + } + + return snd_interval_refine(c, &t); +} + +static inline int +hw_rule_capture_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_efw *efw = rule->private; + return hw_rule_rate(params, rule, efw, + efw->pcm_capture_channels); +} + +static inline int +hw_rule_playback_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_efw *efw = rule->private; + return hw_rule_rate(params, rule, efw, + efw->pcm_playback_channels); +} + +static inline int +hw_rule_capture_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_efw *efw = rule->private; + return hw_rule_channels(params, rule, efw, + efw->pcm_capture_channels); +} + +static inline int +hw_rule_playback_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_efw *efw = rule->private; + return hw_rule_channels(params, rule, efw, + efw->pcm_playback_channels); +} + +static int +pcm_init_hw_params(struct snd_efw *efw, + struct snd_pcm_substream *substream) +{ + unsigned int *pcm_channels; + unsigned int rate_bit; + int mode, i; + int err; + + struct snd_pcm_hardware hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_FIFO_IN_FRAMES | + /* for Open Sound System compatibility */ + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .rates = efw->supported_sampling_rate, + /* set up later */ + .rate_min = UINT_MAX, + .rate_max = 0, + /* set up later */ + .channels_min = UINT_MAX, + .channels_max = 0, + .buffer_bytes_max = 1024 * 1024 * 1024, + .period_bytes_min = 256, + .period_bytes_max = 1024 * 1024 * 1024 / 2, + .periods_min = 2, + .periods_max = 32, + .fifo_size = 0, + }; + + substream->runtime->hw = hardware; + substream->runtime->delay = substream->runtime->hw.fifo_size; + + /* add rule between channels and sampling rate */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + substream->runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; + snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_capture_channels, efw, + SNDRV_PCM_HW_PARAM_RATE, -1); + snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + hw_rule_capture_rate, efw, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + pcm_channels = efw->pcm_capture_channels; + } else { + substream->runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS; + snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_playback_channels, efw, + SNDRV_PCM_HW_PARAM_RATE, -1); + snd_pcm_hw_rule_add(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_RATE, + hw_rule_playback_rate, efw, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + pcm_channels = efw->pcm_playback_channels; + } + + /* preparing min/max sampling rate */ + snd_pcm_limit_hw_rates(substream->runtime); + + /* preparing the number of channels */ + for (i = 0; i < ARRAY_SIZE(freq_table); i += 1) { + /* skip unsupported sampling rate */ + rate_bit = snd_pcm_rate_to_rate_bit(freq_table[i]); + if (!(efw->supported_sampling_rate & rate_bit)) + continue; + + mode = get_multiplier_mode_with_index(i); + if (pcm_channels[mode] == 0) + continue; + substream->runtime->hw.channels_min = + min(substream->runtime->hw.channels_min, + pcm_channels[mode]); + substream->runtime->hw.channels_max = + max(substream->runtime->hw.channels_max, + pcm_channels[mode]); + } + + /* AM824 in IEC 61883-6 can deliver 24bit data */ + err = snd_pcm_hw_constraint_msbits(substream->runtime, 0, 32, 24); + if (err < 0) + goto end; + + /* format of PCM samples is 16bit or 24bit inner 32bit */ + err = snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); + if (err < 0) + goto end; + err = snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32); + if (err < 0) + goto end; + + /* time for period constraint */ + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + 500, UINT_MAX); + if (err < 0) + goto end; + + err = 0; + +end: + return err; +} + +static int +pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + int sampling_rate; + int err; + + err = pcm_init_hw_params(efw, substream); + if (err < 0) + goto end; + + /* + * The same sampling rate must be used for transmit and receive stream + * as long as the streams include PCM samples + */ + if ((amdtp_stream_running(&efw->receive_stream) && + amdtp_stream_pcm_running(&efw->receive_stream)) || + (amdtp_stream_running(&efw->transmit_stream) && + amdtp_stream_pcm_running(&efw->transmit_stream))) { + err = snd_efw_command_get_sampling_rate(efw, &sampling_rate); + if (err < 0) + goto end; + substream->runtime->hw.rate_min = sampling_rate; + substream->runtime->hw.rate_max = sampling_rate; + } + + snd_pcm_set_sync(substream); + +end: + return err; +} + +static int +pcm_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int +pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_efw *efw = substream->private_data; + struct amdtp_stream *stream; + struct amdtp_stream *opposite; + unsigned int *pcm_channels; + int mode; + int err; + + /* keep PCM ring buffer */ + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + goto end; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + stream = &efw->receive_stream; + opposite = &efw->transmit_stream; + pcm_channels = efw->pcm_playback_channels; + } else { + stream = &efw->transmit_stream; + opposite = &efw->receive_stream; + pcm_channels = efw->pcm_capture_channels; + } + + /* stop stream if it's just for MIDI streams */ + if (amdtp_stream_running(stream) && + !amdtp_stream_pcm_running(stream) && + amdtp_stream_midi_running(stream)) + snd_efw_stream_stop(efw, stream); + if (amdtp_stream_running(opposite) && + !amdtp_stream_pcm_running(opposite) && + amdtp_stream_midi_running(opposite)) + snd_efw_stream_stop(efw, opposite); + + /* set sampling rate if both streams are not running */ + if (!amdtp_stream_running(stream) || + !amdtp_stream_running(opposite)) { + err = snd_efw_command_set_sampling_rate(efw, + params_rate(hw_params)); + if (err < 0) + return err; + snd_ctl_notify(efw->card, SNDRV_CTL_EVENT_MASK_VALUE, + efw->control_id_sampling_rate); + } + + /* set AMDTP parameters for transmit stream */ + amdtp_stream_set_rate(stream, params_rate(hw_params)); + amdtp_stream_set_pcm(stream, params_channels(hw_params)); + amdtp_stream_set_pcm_format(stream, params_format(hw_params)); + + /* need to start if opposite stream has MIDI stream */ + if (!opposite->pcm && amdtp_stream_midi_running(opposite)) { + amdtp_stream_set_rate(opposite, params_rate(hw_params)); + mode = snd_efw_get_multiplier_mode(params_rate(hw_params)); + amdtp_stream_set_pcm(opposite, pcm_channels[mode]); + err = snd_efw_stream_start(efw, opposite); + } + +end: + return err; +} + +static int +pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + struct amdtp_stream *stream; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + stream = &efw->receive_stream; + else + stream = &efw->transmit_stream; + + /* don't stop stream if MIDI stream is still running */ + if (!amdtp_stream_midi_running(stream)) + snd_efw_stream_stop(efw, stream); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int +pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + struct amdtp_stream *stream; + int err = 0; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + stream = &efw->receive_stream; + else + stream = &efw->transmit_stream; + + /* start stream */ + err = snd_efw_stream_start(efw, stream); + if (err < 0) + goto end; + + /* initialize buffer pointer */ + amdtp_stream_pcm_prepare(stream); + +end: + return err; +} + +static int +pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_efw *efw = substream->private_data; + struct snd_pcm_substream *pcm; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + pcm = substream; + break; + case SNDRV_PCM_TRIGGER_STOP: + pcm = NULL; + break; + default: + return -EINVAL; + } + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + amdtp_stream_pcm_trigger(&efw->receive_stream, pcm); + else + amdtp_stream_pcm_trigger(&efw->transmit_stream, pcm); + + return 0; +} + +static snd_pcm_uframes_t +pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_efw *efw = substream->private_data; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + return amdtp_stream_pcm_pointer(&efw->receive_stream); + else + return amdtp_stream_pcm_pointer(&efw->transmit_stream); +} + +static struct snd_pcm_ops pcm_playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_prepare, + .trigger = pcm_trigger, + .pointer = pcm_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, +}; + +static struct snd_pcm_ops pcm_capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_prepare, + .trigger = pcm_trigger, + .pointer = pcm_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; + +int snd_efw_create_pcm_devices(struct snd_efw *efw) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(efw->card, efw->card->driver, 0, 1, 1, &pcm); + if (err < 0) + goto end; + + pcm->private_data = efw; + snprintf(pcm->name, sizeof(pcm->name), "%s PCM", efw->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); + + /* for host transmit and target input */ + err = snd_efw_stream_init(efw, &efw->transmit_stream); + if (err < 0) + goto end; + + /* for target output and host receive */ + err = snd_efw_stream_init(efw, &efw->receive_stream); + if (err < 0) { + snd_efw_stream_destroy(efw, &efw->transmit_stream); + goto end; + } + +end: + return err; +} + +void snd_efw_destroy_pcm_devices(struct snd_efw *efw) +{ + amdtp_stream_pcm_abort(&efw->receive_stream); + amdtp_stream_stop(&efw->receive_stream); + snd_efw_stream_destroy(efw, &efw->receive_stream); + + amdtp_stream_pcm_abort(&efw->transmit_stream); + amdtp_stream_stop(&efw->transmit_stream); + snd_efw_stream_destroy(efw, &efw->transmit_stream); + + return; +}
o-takashi@sakamocchi.jp wrote:
- Additionally, according to AudioFire Owner's Manual Version 2.2,
- the number of PCM channels for digital input has more restriction
- depending on which digital interface is selected.
- S/PDIF coaxial and optical : use input 1-2
- ADAT optical with 32.0-48.0 kHz : use input 1-8
- ADAT optical with 88.2-96.0 kHz : use input 1-4 (S/MUX format)
- If these restriction is applied, the number of channels in stream is decided
- according to above modes.
- Currently this module doesn't have rules for the latter.
Does the number of channels in the AMDTP stream change, or are those channel still there, but filled with zeros?
+pcm_init_hw_params(struct snd_efw *efw,
struct snd_pcm_substream *substream)
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_BATCH |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_SYNC_START |
SNDRV_PCM_INFO_FIFO_IN_FRAMES |
/* for Open Sound System compatibility */
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_BLOCK_TRANSFER,
Set SNDRV_PCM_INFO_JOINT_DUPLEX to specify that the playback/capture streams are somehow related (must use the same rate).
- /* format of PCM samples is 16bit or 24bit inner 32bit */
- err = snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
- err = snd_pcm_hw_constraint_step(substream->runtime, 0,
SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);
This has nothing to do with the sample format.
The AMDTP code requires(?) buffers and periods to be aligned with packets, so you need constraints for 8/16/32 *frames*.
- /*
* The same sampling rate must be used for transmit and receive stream
* as long as the streams include PCM samples
*/
- if ((amdtp_stream_running(&efw->receive_stream) &&
amdtp_stream_pcm_running(&efw->receive_stream)) ||
(amdtp_stream_running(&efw->transmit_stream) &&
amdtp_stream_pcm_running(&efw->transmit_stream))) {
err = snd_efw_command_get_sampling_rate(efw, &sampling_rate);
if (err < 0)
goto end;
substream->runtime->hw.rate_min = sampling_rate;
substream->runtime->hw.rate_max = sampling_rate;
- }
What is the purpose of the "Sampling Rate" mixer control?
+pcm_pointer(struct snd_pcm_substream *substream) +{
- struct snd_efw *efw = substream->private_data;
- if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
return amdtp_stream_pcm_pointer(&efw->receive_stream);
- else
return amdtp_stream_pcm_pointer(&efw->transmit_stream);
+}
If the playback/capture callbacks have nothing in common, just use two functions.
Regards, Clemens
From: Takashi Sakamoto o-takashi@sakamocchi.jp
The purpose of this patch is adding proc interface mainly for debug. The proc interfaces gives us: - hardware property - clock information - physical metering
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/fireworks/fireworks_proc.c | 183 +++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 sound/firewire/fireworks/fireworks_proc.c
diff --git a/sound/firewire/fireworks/fireworks_proc.c b/sound/firewire/fireworks/fireworks_proc.c new file mode 100644 index 0000000..c092aa4 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_proc.c @@ -0,0 +1,183 @@ +/* + * fireworks_proc.c - driver for Firewire devices from Echo Digital Audio + * + * Copyright (c) 2009-2010 Clemens Ladisch + * Copyright (c) 2013 Takashi Sakamoto + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver; if not, see http://www.gnu.org/licenses/. + */ + +#include "./fireworks.h" + +static void +proc_read_hwinfo(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_efw *efw = entry->private_data; + unsigned short i; + struct snd_efw_hwinfo hwinfo; + + if (snd_efw_command_get_hwinfo(efw, &hwinfo) < 0) + goto end; + + snd_iprintf(buffer, "guid_hi: 0x%X\n", hwinfo.guid_hi); + snd_iprintf(buffer, "guid_lo: 0x%X\n", hwinfo.guid_lo); + snd_iprintf(buffer, "type: 0x%X\n", hwinfo.type); + snd_iprintf(buffer, "version: 0x%X\n", hwinfo.version); + snd_iprintf(buffer, "vendor_name: %s\n", hwinfo.vendor_name); + snd_iprintf(buffer, "model_name: %s\n", hwinfo.model_name); + + snd_iprintf(buffer, "dsp_version: 0x%X\n", hwinfo.dsp_version); + snd_iprintf(buffer, "arm_version: 0x%X\n", hwinfo.arm_version); + snd_iprintf(buffer, "fpga_version: 0x%X\n", hwinfo.fpga_version); + + snd_iprintf(buffer, "flags: 0x%X\n", hwinfo.flags); + + snd_iprintf(buffer, "max_sample_rate: 0x%X\n", hwinfo.max_sample_rate); + snd_iprintf(buffer, "min_sample_rate: 0x%X\n", hwinfo.min_sample_rate); + snd_iprintf(buffer, "supported_clock: 0x%X\n", + hwinfo.supported_clocks); + + snd_iprintf(buffer, "nb_phys_audio_out: 0x%X\n", + hwinfo.nb_phys_audio_out); + snd_iprintf(buffer, "nb_phys_audio_in: 0x%X\n", + hwinfo.nb_phys_audio_in); + + snd_iprintf(buffer, "nb_in_groups: 0x%X\n", hwinfo.nb_in_groups); + for (i = 0; i < hwinfo.nb_in_groups; i += 1) { + snd_iprintf(buffer, "in_group[0x%d]: type 0x%d, count 0x%d\n", + i, hwinfo.out_groups[i].type, + hwinfo.out_groups[i].count); + } + + snd_iprintf(buffer, "nb_out_groups: 0x%X\n", hwinfo.nb_out_groups); + for (i = 0; i < hwinfo.nb_out_groups; i += 1) { + snd_iprintf(buffer, "out_group[0x%d]: type 0x%d, count 0x%d\n", + i, hwinfo.out_groups[i].type, + hwinfo.out_groups[i].count); + } + + snd_iprintf(buffer, "nb_1394_playback_channels: 0x%X\n", + hwinfo.nb_1394_playback_channels); + snd_iprintf(buffer, "nb_1394_capture_channels: 0x%X\n", + hwinfo.nb_1394_capture_channels); + snd_iprintf(buffer, "nb_1394_playback_channels_2x: 0x%X\n", + hwinfo.nb_1394_playback_channels_2x); + snd_iprintf(buffer, "nb_1394_capture_channels_2x: 0x%X\n", + hwinfo.nb_1394_capture_channels_2x); + snd_iprintf(buffer, "nb_1394_playback_channels_4x: 0x%X\n", + hwinfo.nb_1394_playback_channels_4x); + snd_iprintf(buffer, "nb_1394_capture_channels_4x: 0x%X\n", + hwinfo.nb_1394_capture_channels_4x); + + snd_iprintf(buffer, "nb_midi_out: 0x%X\n", hwinfo.nb_midi_out); + snd_iprintf(buffer, "nb_midi_in: 0x%X\n", hwinfo.nb_midi_in); + + snd_iprintf(buffer, "mixer_playback_channels: 0x%X\n", + hwinfo.mixer_playback_channels); + snd_iprintf(buffer, "mixer_capture_channels: 0x%X\n", + hwinfo.mixer_capture_channels); + +end: + return; +} + +static void +proc_read_clock(struct snd_info_entry *entry, struct snd_info_buffer *buffer) +{ + struct snd_efw *efw = entry->private_data; + enum snd_efw_clock_source clock_source; + int sampling_rate; + + if (snd_efw_command_get_clock_source(efw, &clock_source) < 0) + goto end; + + if (snd_efw_command_get_sampling_rate(efw, &sampling_rate) < 0) + goto end; + + snd_iprintf(buffer, "Clock Source: %d\n", clock_source); + snd_iprintf(buffer, "Sampling Rate: %d\n", sampling_rate); + +end: + return; +} + +static void +proc_read_phys_meters(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_efw *efw = entry->private_data; + + char const *descs[] = {"Analog", "S/PDIF", "ADAT", "S/PDIF or ADAT", + "Analog Mirroring", "Headphones", "I2S"}; + + struct snd_efw_phys_meters *meters; + int i, g, c; + int base = sizeof(struct snd_efw_phys_meters); + int count = efw->input_meter_counts + efw->output_meter_counts; + int err; + + meters = kzalloc(base + count * 4, GFP_KERNEL); + if (meters == NULL) + return; + + err = snd_efw_command_get_phys_meters(efw, meters, base + count * 4); + if (err < 0) + goto end; + + snd_iprintf(buffer, "Physical Meters:\n"); + + snd_iprintf(buffer, " %d Inputs:\n", efw->input_meter_counts); + g = 0; + c = 0; + for (i = 0; i < efw->input_meter_counts; i += 1) { + if (c == efw->input_groups[g].count) { + g += 1; + c = 0; + } + snd_iprintf(buffer, "\t%s [%d]: %d\n", + descs[efw->input_groups[g].type], c, + meters->values[efw->output_meter_counts + i]); + c += 1; + } + + snd_iprintf(buffer, " %d Outputs:\n", efw->output_meter_counts); + g = 0; + c = 0; + for (i = 0; i < efw->output_meter_counts; i += 1) { + if (c == efw->output_groups[g].count) { + g += 1; + c = 0; + } + snd_iprintf(buffer, "\t%s [%d]: %d\n", + descs[efw->output_groups[g].type], c, + meters->values[i]); + c += 1; + } + +end: + kfree(meters); + return; +} + +void snd_efw_proc_init(struct snd_efw *efw) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(efw->card, "#hardware", &entry)) + snd_info_set_text_ops(entry, efw, proc_read_hwinfo); + if (!snd_card_proc_new(efw->card, "#clock", &entry)) + snd_info_set_text_ops(entry, efw, proc_read_clock); + if (!snd_card_proc_new(efw->card, "#meters", &entry)) + snd_info_set_text_ops(entry, efw, proc_read_phys_meters); + return; +}
From: Takashi Sakamoto o-takashi@sakamocchi.jp
The purpose of this file is building this module by Linux kernel's driver module build system.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 16 ++++++++++++++++ sound/firewire/Makefile | 2 ++ sound/firewire/fireworks/Makefile | 2 ++ 3 files changed, 20 insertions(+) create mode 100644 sound/firewire/fireworks/Makefile
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index ea063e1..b4cbad5 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -46,4 +46,20 @@ config SND_SCS1X To compile this driver as a module, choose M here: the module will be called snd-scs1x.
+config SND_FIREWORKS + tristate "Echo Fireworks chipset support" + select SND_PCM + select SND_RAWMIDI + select SND_FIREWIRE_LIB + help + Say Y here to include support for FireWire devices based + on Echo Digital Audio's Fireworks architecture: + * Echo AudioFire 2/4/8/8a/Pre8/12 + * Echo Fireworks 8/HDMI + * Gibson Goldtop/RIP + * Mackie Onyx 400F/1200F + + To compile this driver as a module, choose M here: the module + will be called snd-fireworks. + endif # SND_FIREWIRE diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index 460179d..9d9d9e8 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -8,3 +8,5 @@ obj-$(CONFIG_SND_FIREWIRE_LIB) += snd-firewire-lib.o obj-$(CONFIG_SND_FIREWIRE_SPEAKERS) += snd-firewire-speakers.o obj-$(CONFIG_SND_ISIGHT) += snd-isight.o obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o + +obj-$(CONFIG_SND) += fireworks/ diff --git a/sound/firewire/fireworks/Makefile b/sound/firewire/fireworks/Makefile new file mode 100644 index 0000000..3bdd77c --- /dev/null +++ b/sound/firewire/fireworks/Makefile @@ -0,0 +1,2 @@ +snd-fireworks-objs := fireworks_command.o fireworks_proc.o fireworks_control.o fireworks_pcm.o fireworks_midi.o fireworks_stream.o fireworks.o +obj-$(SND_FIREWORKS) += snd-fireworks.o
o-takashi@sakamocchi.jp wrote:
+config SND_FIREWORKS
- tristate "Echo Fireworks chipset support"
- select SND_PCM
- select SND_RAWMIDI
- select SND_FIREWIRE_LIB
- help
Say Y here to include support for FireWire devices based
on Echo Digital Audio's Fireworks architecture:
* Echo AudioFire 2/4/8/8a/Pre8/12
* Echo Fireworks 8/HDMI
* Gibson Goldtop/RIP
* Mackie Onyx 400F/1200F
To compile this driver as a module, choose M here: the module
will be called snd-fireworks.
We'll have to find some mechanism to prevent FFADO from blowing up just because a distribution update has introduced this driver.
Regards, Clemens
o-takashi@sakamocchi.jp wrote:
Currently this driver module support just PCM/MIDI kernel streaming via ALSA interfaces. So there are some issues about this driver. 1.whether adding each PCM devices for analog and digital interface
ALSA drivers should reflect the actual hardware design as much as possible; conversions should be done in user space by alsa-lib.
If analog and digital channels are part of the same AMDTP stream, then that is how the PCM device should look like. (This is also how other drivers like ice1712 work.)
There are other functions to describe and label channels. (And when I say "there are", I actually mean "there will be, eventually". ;-)
2.where the codes for device control like volume, routing and etc are
The idea was to let the kernel driver handle only the actual streaming (and everything else necessary for that, such as device enumeration, sample rate, and clock source management), and let user space handle everything else.
Volume and routing do not affect the streaming part in any way, so the kernel driver should not bother to implement them.
4. clock source handling
AMDTP itself was designed for consumer devices, where streaming is in only one direction, and where the receiving device always synchronizes to the playback device; this is the equivalent of the "SYT Match" clock source.
This is how the existing drivers (isight, firewire-speakers) work.
However, for full duplex and other clock sources, streaming becomes more complicated. When in "SYT match" mode, the playback stream should be running even when only a capture PCM device is open. In all other modes, the PC must synchronize to the device, so the capture stream must be running when _any_ PCM/MIDI stream is open (and, of course, the capture packets must be used to determine when to send packets).
Regards, Clemens
Clemens,
- clock source handling
I want to discuss about this issue in this mail.
I've never concerned about this synchronization so it took me a bit time to understand what you said...
Actually Fireworks supports severarl clock source, "Internal", "SYT Match", "Word", "S/PDIF", "ADAT1", "ADAT2". Then there is two options, one is for SYT match and another is for the others.
SYT match: PC is a clock master to transmit SYT in CIP. So receive stream require transmit stream in advance.
The others: Device is a clock master and PC should generate SYT for transmit stream from SYT in receive stream.
(and, of course, the capture packets must be used to determine when to send packets).
To achieve this, we need to change calculate_syt() or add new function to amdtp.c. I think it's enough to generate outgoing SYT from incoming SYT with TRANSFER_DELAY (479.17 micro seconds). This is for consumer use.
But I have no idea when playback starts. Do you have any idea?
Regards
Takashi Sakamoto o-takashi@sakamocchi.jp
(Jun 3 2013 20:18), Clemens Ladisch wrote:
o-takashi@sakamocchi.jp wrote:
Currently this driver module support just PCM/MIDI kernel streaming via ALSA interfaces. So there are some issues about this driver. 1.whether adding each PCM devices for analog and digital interface
ALSA drivers should reflect the actual hardware design as much as possible; conversions should be done in user space by alsa-lib.
If analog and digital channels are part of the same AMDTP stream, then that is how the PCM device should look like. (This is also how other drivers like ice1712 work.)
There are other functions to describe and label channels. (And when I say "there are", I actually mean "there will be, eventually". ;-)
2.where the codes for device control like volume, routing and etc are
The idea was to let the kernel driver handle only the actual streaming (and everything else necessary for that, such as device enumeration, sample rate, and clock source management), and let user space handle everything else.
Volume and routing do not affect the streaming part in any way, so the kernel driver should not bother to implement them.
- clock source handling
AMDTP itself was designed for consumer devices, where streaming is in only one direction, and where the receiving device always synchronizes to the playback device; this is the equivalent of the "SYT Match" clock source.
This is how the existing drivers (isight, firewire-speakers) work.
However, for full duplex and other clock sources, streaming becomes more complicated. When in "SYT match" mode, the playback stream should be running even when only a capture PCM device is open. In all other modes, the PC must synchronize to the device, so the capture stream must be running when _any_ PCM/MIDI stream is open (and, of course, the capture packets must be used to determine when to send packets).
Regards, Clemens
Takashi Sakamoto wrote:
Actually Fireworks supports severarl clock source, "Internal", "SYT Match", "Word", "S/PDIF", "ADAT1", "ADAT2". Then there is two options, one is for SYT match and another is for the others.
SYT match: PC is a clock master to transmit SYT in CIP. So receive stream require transmit stream in advance.
The others: Device is a clock master and PC should generate SYT for transmit stream from SYT in receive stream.
(and, of course, the capture packets must be used to determine when to send packets).
To achieve this, we need to change calculate_syt() or add new function to amdtp.c. I think it's enough to generate outgoing SYT from incoming SYT with TRANSFER_DELAY (479.17 micro seconds).
We want the playback stream to behave like the capture stream, and the SYT is interpreted relative to the frame in which the packet is actually transferred, so what the driver has to do is this: 1) compute the offset between the frame where the capture packet was received and its SYT; 2) compute the frame where the playback packet will be sent (i.e., add the queue length to the current frame); 3) add the offset to that frame.
The SYT field wraps around after 2 ms, so the corresponding capture/ playback SYT values will be identical only if the queue length is a multiple of 2 ms.
But there is another thing: the driver has to choose whether a packet to send contains SYT_INTERVAL samples or is a NO-DATA packet.
In SYT Match mode, the driver just checks whether enough samples are available for that frame.
In the other modes, the driver must copy the packet type from the capture stream, i.e., when it receives a NO-DATA packet, it submits a NO-DATA packet, and when it receives a data packet, it submits a data packet. This implies that the queueing of playback packets happens not in the playback packet completion callback, but in the *capture* packet completion callback.
But I have no idea when playback starts. Do you have any idea?
Playback packets can be submitted only when we already have a running capture stream with its packet completion callbacks. (This is similar to starting playback in the SYT Match case, where the driver submits a bunch of skip packets so that the playback packet completion callbacks arrive with the correct timing.)
Regards, Clemens
participants (3)
-
Clemens Ladisch
-
o-takashi@sakamocchi.jp
-
Takashi Sakamoto