Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
snd-fireworks-y := fireworks_transaction.o fireworks_command.o \
fireworks_stream.o fireworks_proc.o fireworks_midi.o \
fireworks_pcm.o fireworks_hwdep.o fireworks.o
obj-$(CONFIG_SND_FIREWORKS) += snd-fireworks.o
@@ -0,0 +1,366 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* fireworks.c - a part of driver for Fireworks based devices
*
* Copyright (c) 2009-2010 Clemens Ladisch
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
/*
* Fireworks is a board module which Echo Audio produced. This module consists
* of three chipsets:
* - Communication chipset for IEEE1394 PHY/Link and IEC 61883-1/6
* - DSP or/and FPGA for signal processing
* - Flash Memory to store firmwares
*/
#include "fireworks.h"
MODULE_DESCRIPTION("Echo Fireworks driver");
MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
MODULE_LICENSE("GPL");
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
unsigned int snd_efw_resp_buf_size = 1024;
bool snd_efw_resp_buf_debug = false;
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");
module_param_named(resp_buf_size, snd_efw_resp_buf_size, uint, 0444);
MODULE_PARM_DESC(resp_buf_size,
"response buffer size (max 4096, default 1024)");
module_param_named(resp_buf_debug, snd_efw_resp_buf_debug, bool, 0444);
MODULE_PARM_DESC(resp_buf_debug, "store all responses to buffer");
static DEFINE_MUTEX(devices_mutex);
static DECLARE_BITMAP(devices_used, SNDRV_CARDS);
#define VENDOR_LOUD 0x000ff2
#define MODEL_MACKIE_400F 0x00400f
#define MODEL_MACKIE_1200F 0x01200f
#define VENDOR_ECHO 0x001486
#define MODEL_ECHO_AUDIOFIRE_12 0x00af12
#define MODEL_ECHO_AUDIOFIRE_12HD 0x0af12d
#define MODEL_ECHO_AUDIOFIRE_12_APPLE 0x0af12a
/* This is applied for AudioFire8 (until 2009 July) */
#define MODEL_ECHO_AUDIOFIRE_8 0x000af8
#define MODEL_ECHO_AUDIOFIRE_2 0x000af2
#define MODEL_ECHO_AUDIOFIRE_4 0x000af4
/* AudioFire9 is applied for AudioFire8(since 2009 July) and AudioFirePre8 */
#define MODEL_ECHO_AUDIOFIRE_9 0x000af9
/* unknown as product */
#define MODEL_ECHO_FIREWORKS_8 0x0000f8
#define MODEL_ECHO_FIREWORKS_HDMI 0x00afd1
#define VENDOR_GIBSON 0x00075b
/* for Robot Interface Pack of Dark Fire, Dusk Tiger, Les Paul Standard 2010 */
#define MODEL_GIBSON_RIP 0x00afb2
/* unknown as product */
#define MODEL_GIBSON_GOLDTOP 0x00afb9
/* part of hardware capability flags */
#define FLAG_RESP_ADDR_CHANGABLE 0
static int
get_hardware_info(struct snd_efw *efw)
{
struct fw_device *fw_dev = fw_parent_device(efw->unit);
struct snd_efw_hwinfo *hwinfo;
char version[12] = {0};
int err;
hwinfo = kzalloc_obj(struct snd_efw_hwinfo);
if (hwinfo == NULL)
return -ENOMEM;
err = snd_efw_command_get_hwinfo(efw, hwinfo);
if (err < 0)
goto end;
/* firmware version for communication chipset */
snprintf(version, sizeof(version), "%u.%u",
(hwinfo->arm_version >> 24) & 0xff,
(hwinfo->arm_version >> 16) & 0xff);
efw->firmware_version = hwinfo->arm_version;
strscpy(efw->card->driver, "Fireworks");
strscpy(efw->card->shortname, hwinfo->model_name);
strscpy(efw->card->mixername, hwinfo->model_name);
scnprintf(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 << fw_dev->max_speed);
if (hwinfo->flags & BIT(FLAG_RESP_ADDR_CHANGABLE))
efw->resp_addr_changable = true;
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;
/* the number of MIDI ports, not of MIDI conformant data channels */
if (hwinfo->midi_out_ports > SND_EFW_MAX_MIDI_OUT_PORTS ||
hwinfo->midi_in_ports > SND_EFW_MAX_MIDI_IN_PORTS) {
err = -EIO;
goto end;
}
efw->midi_out_ports = hwinfo->midi_out_ports;
efw->midi_in_ports = hwinfo->midi_in_ports;
if (hwinfo->amdtp_tx_pcm_channels > AM824_MAX_CHANNELS_FOR_PCM ||
hwinfo->amdtp_tx_pcm_channels_2x > AM824_MAX_CHANNELS_FOR_PCM ||
hwinfo->amdtp_tx_pcm_channels_4x > AM824_MAX_CHANNELS_FOR_PCM ||
hwinfo->amdtp_rx_pcm_channels > AM824_MAX_CHANNELS_FOR_PCM ||
hwinfo->amdtp_rx_pcm_channels_2x > AM824_MAX_CHANNELS_FOR_PCM ||
hwinfo->amdtp_rx_pcm_channels_4x > AM824_MAX_CHANNELS_FOR_PCM) {
err = -ENOSYS;
goto end;
}
efw->pcm_capture_channels[0] = hwinfo->amdtp_tx_pcm_channels;
efw->pcm_capture_channels[1] = hwinfo->amdtp_tx_pcm_channels_2x;
efw->pcm_capture_channels[2] = hwinfo->amdtp_tx_pcm_channels_4x;
efw->pcm_playback_channels[0] = hwinfo->amdtp_rx_pcm_channels;
efw->pcm_playback_channels[1] = hwinfo->amdtp_rx_pcm_channels_2x;
efw->pcm_playback_channels[2] = hwinfo->amdtp_rx_pcm_channels_4x;
/* Hardware metering. */
if (hwinfo->phys_in_grp_count > HWINFO_MAX_CAPS_GROUPS ||
hwinfo->phys_out_grp_count > HWINFO_MAX_CAPS_GROUPS) {
err = -EIO;
goto end;
}
efw->phys_in = hwinfo->phys_in;
efw->phys_out = hwinfo->phys_out;
efw->phys_in_grp_count = hwinfo->phys_in_grp_count;
efw->phys_out_grp_count = hwinfo->phys_out_grp_count;
memcpy(&efw->phys_in_grps, hwinfo->phys_in_grps,
sizeof(struct snd_efw_phys_grp) * hwinfo->phys_in_grp_count);
memcpy(&efw->phys_out_grps, hwinfo->phys_out_grps,
sizeof(struct snd_efw_phys_grp) * hwinfo->phys_out_grp_count);
/* AudioFire8 (since 2009) and AudioFirePre8 */
if (hwinfo->type == MODEL_ECHO_AUDIOFIRE_9)
efw->is_af9 = true;
/* These models uses the same firmware. */
if (hwinfo->type == MODEL_ECHO_AUDIOFIRE_2 ||
hwinfo->type == MODEL_ECHO_AUDIOFIRE_4 ||
hwinfo->type == MODEL_ECHO_AUDIOFIRE_9 ||
hwinfo->type == MODEL_GIBSON_RIP ||
hwinfo->type == MODEL_GIBSON_GOLDTOP)
efw->is_fireworks3 = true;
end:
kfree(hwinfo);
return err;
}
static void
efw_card_free(struct snd_card *card)
{
struct snd_efw *efw = card->private_data;
scoped_guard(mutex, &devices_mutex) {
clear_bit(efw->card_index, devices_used);
}
snd_efw_stream_destroy_duplex(efw);
snd_efw_transaction_remove_instance(efw);
mutex_destroy(&efw->mutex);
fw_unit_put(efw->unit);
}
static int efw_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry)
{
unsigned int card_index;
struct snd_card *card;
struct snd_efw *efw;
int err;
// check registered cards.
scoped_guard(mutex, &devices_mutex) {
for (card_index = 0; card_index < SNDRV_CARDS; ++card_index) {
if (!test_bit(card_index, devices_used) && enable[card_index])
break;
}
if (card_index >= SNDRV_CARDS)
return -ENOENT;
err = snd_card_new(&unit->device, index[card_index], id[card_index], THIS_MODULE,
sizeof(*efw), &card);
if (err < 0)
return err;
card->private_free = efw_card_free;
set_bit(card_index, devices_used);
}
efw = card->private_data;
efw->unit = fw_unit_get(unit);
dev_set_drvdata(&unit->device, efw);
efw->card = card;
efw->card_index = card_index;
mutex_init(&efw->mutex);
spin_lock_init(&efw->lock);
init_waitqueue_head(&efw->hwdep_wait);
// prepare response buffer.
snd_efw_resp_buf_size = clamp(snd_efw_resp_buf_size, SND_EFW_RESPONSE_MAXIMUM_BYTES, 4096U);
efw->resp_buf = devm_kzalloc(&card->card_dev, snd_efw_resp_buf_size, GFP_KERNEL);
if (!efw->resp_buf) {
err = -ENOMEM;
goto error;
}
efw->pull_ptr = efw->push_ptr = efw->resp_buf;
snd_efw_transaction_add_instance(efw);
err = get_hardware_info(efw);
if (err < 0)
goto error;
err = snd_efw_stream_init_duplex(efw);
if (err < 0)
goto error;
snd_efw_proc_init(efw);
if (efw->midi_out_ports || efw->midi_in_ports) {
err = snd_efw_create_midi_devices(efw);
if (err < 0)
goto error;
}
err = snd_efw_create_pcm_devices(efw);
if (err < 0)
goto error;
err = snd_efw_create_hwdep_device(efw);
if (err < 0)
goto error;
err = snd_card_register(card);
if (err < 0)
goto error;
return 0;
error:
snd_card_free(card);
return err;
}
static void efw_update(struct fw_unit *unit)
{
struct snd_efw *efw = dev_get_drvdata(&unit->device);
snd_efw_transaction_bus_reset(efw->unit);
guard(mutex)(&efw->mutex);
snd_efw_stream_update_duplex(efw);
}
static void efw_remove(struct fw_unit *unit)
{
struct snd_efw *efw = dev_get_drvdata(&unit->device);
// Block till all of ALSA character devices are released.
snd_card_free(efw->card);
}
#define SPECIFIER_1394TA 0x00a02d
#define VERSION_EFW 0x010000
#define SND_EFW_DEV_ENTRY(vendor, model) \
{ \
.match_flags = IEEE1394_MATCH_VENDOR_ID | \
IEEE1394_MATCH_MODEL_ID | \
IEEE1394_MATCH_SPECIFIER_ID | \
IEEE1394_MATCH_VERSION, \
.vendor_id = vendor,\
.model_id = model, \
.specifier_id = SPECIFIER_1394TA, \
.version = VERSION_EFW, \
}
static const struct ieee1394_device_id efw_id_table[] = {
SND_EFW_DEV_ENTRY(VENDOR_LOUD, MODEL_MACKIE_400F),
SND_EFW_DEV_ENTRY(VENDOR_LOUD, MODEL_MACKIE_1200F),
SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_8),
SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_12),
SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_12HD),
SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_12_APPLE),
SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_2),
SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_4),
SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_AUDIOFIRE_9),
SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_FIREWORKS_8),
SND_EFW_DEV_ENTRY(VENDOR_ECHO, MODEL_ECHO_FIREWORKS_HDMI),
SND_EFW_DEV_ENTRY(VENDOR_GIBSON, MODEL_GIBSON_RIP),
SND_EFW_DEV_ENTRY(VENDOR_GIBSON, MODEL_GIBSON_GOLDTOP),
{}
};
MODULE_DEVICE_TABLE(ieee1394, efw_id_table);
static struct fw_driver efw_driver = {
.driver = {
.owner = THIS_MODULE,
.name = KBUILD_MODNAME,
.bus = &fw_bus_type,
},
.probe = efw_probe,
.update = efw_update,
.remove = efw_remove,
.id_table = efw_id_table,
};
static int __init snd_efw_init(void)
{
int err;
err = snd_efw_transaction_register();
if (err < 0)
goto end;
err = driver_register(&efw_driver.driver);
if (err < 0)
snd_efw_transaction_unregister();
end:
return err;
}
static void __exit snd_efw_exit(void)
{
snd_efw_transaction_unregister();
driver_unregister(&efw_driver.driver);
}
module_init(snd_efw_init);
module_exit(snd_efw_exit);
@@ -0,0 +1,226 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* fireworks.h - a part of driver for Fireworks based devices
*
* Copyright (c) 2009-2010 Clemens Ladisch
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
#ifndef SOUND_FIREWORKS_H_INCLUDED
#define SOUND_FIREWORKS_H_INCLUDED
#include <linux/compat.h>
#include <linux/device.h>
#include <linux/firewire.h>
#include <linux/firewire-constants.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/sched/signal.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/info.h>
#include <sound/rawmidi.h>
#include <sound/pcm_params.h>
#include <sound/firewire.h>
#include <sound/hwdep.h>
#include "../packets-buffer.h"
#include "../iso-resources.h"
#include "../amdtp-am824.h"
#include "../cmp.h"
#include "../lib.h"
#define SND_EFW_MAX_MIDI_OUT_PORTS 2
#define SND_EFW_MAX_MIDI_IN_PORTS 2
#define SND_EFW_MULTIPLIER_MODES 3
#define HWINFO_NAME_SIZE_BYTES 32
#define HWINFO_MAX_CAPS_GROUPS 8
/*
* This should be greater than maximum bytes for EFW response content.
* Currently response against command for isochronous channel mapping is
* confirmed to be the maximum one. But for flexibility, use maximum data
* payload for asynchronous primary packets at S100 (Cable base rate) in
* IEEE Std 1394-1995.
*/
#define SND_EFW_RESPONSE_MAXIMUM_BYTES 0x200U
extern unsigned int snd_efw_resp_buf_size;
extern bool snd_efw_resp_buf_debug;
struct snd_efw_phys_grp {
u8 type; /* see enum snd_efw_grp_type */
u8 count;
} __packed;
struct snd_efw {
struct snd_card *card;
struct fw_unit *unit;
int card_index;
struct mutex mutex;
spinlock_t lock;
/* for transaction */
u32 seqnum;
bool resp_addr_changable;
/* for quirks */
bool is_af9;
bool is_fireworks3;
u32 firmware_version;
unsigned int midi_in_ports;
unsigned int midi_out_ports;
unsigned int supported_sampling_rate;
unsigned int pcm_capture_channels[SND_EFW_MULTIPLIER_MODES];
unsigned int pcm_playback_channels[SND_EFW_MULTIPLIER_MODES];
struct amdtp_stream tx_stream;
struct amdtp_stream rx_stream;
struct cmp_connection out_conn;
struct cmp_connection in_conn;
unsigned int substreams_counter;
/* hardware metering parameters */
unsigned int phys_out;
unsigned int phys_in;
unsigned int phys_out_grp_count;
unsigned int phys_in_grp_count;
struct snd_efw_phys_grp phys_out_grps[HWINFO_MAX_CAPS_GROUPS];
struct snd_efw_phys_grp phys_in_grps[HWINFO_MAX_CAPS_GROUPS];
/* for uapi */
int dev_lock_count;
bool dev_lock_changed;
wait_queue_head_t hwdep_wait;
/* response queue */
u8 *resp_buf;
u8 *pull_ptr;
u8 *push_ptr;
struct amdtp_domain domain;
};
int snd_efw_transaction_cmd(struct fw_unit *unit,
const void *cmd, unsigned int size);
int snd_efw_transaction_run(struct fw_unit *unit,
const void *cmd, unsigned int cmd_size,
void *resp, unsigned int resp_size);
int snd_efw_transaction_register(void);
void snd_efw_transaction_unregister(void);
void snd_efw_transaction_bus_reset(struct fw_unit *unit);
void snd_efw_transaction_add_instance(struct snd_efw *efw);
void snd_efw_transaction_remove_instance(struct snd_efw *efw);
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 amdtp_rx_pcm_channels;
u32 amdtp_tx_pcm_channels;
u32 phys_out;
u32 phys_in;
u32 phys_out_grp_count;
struct snd_efw_phys_grp phys_out_grps[HWINFO_MAX_CAPS_GROUPS];
u32 phys_in_grp_count;
struct snd_efw_phys_grp phys_in_grps[HWINFO_MAX_CAPS_GROUPS];
u32 midi_out_ports;
u32 midi_in_ports;
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 amdtp_rx_pcm_channels_2x;
u32 amdtp_tx_pcm_channels_2x;
u32 amdtp_rx_pcm_channels_4x;
u32 amdtp_tx_pcm_channels_4x;
u32 reserved[16];
} __packed;
enum snd_efw_grp_type {
SND_EFW_CH_TYPE_ANALOG = 0,
SND_EFW_CH_TYPE_SPDIF = 1,
SND_EFW_CH_TYPE_ADAT = 2,
SND_EFW_CH_TYPE_SPDIF_OR_ADAT = 3,
SND_EFW_CH_TYPE_ANALOG_MIRRORING = 4,
SND_EFW_CH_TYPE_HEADPHONES = 5,
SND_EFW_CH_TYPE_I2S = 6,
SND_EFW_CH_TYPE_GUITAR = 7,
SND_EFW_CH_TYPE_PIEZO_GUITAR = 8,
SND_EFW_CH_TYPE_GUITAR_STRING = 9,
SND_EFW_CH_TYPE_DUMMY
};
struct snd_efw_phys_meters {
u32 status; /* guitar state/midi signal/clock input detect */
u32 reserved0;
u32 reserved1;
u32 reserved2;
u32 reserved3;
u32 out_meters;
u32 in_meters;
u32 reserved4;
u32 reserved5;
u32 values[];
} __packed;
enum snd_efw_clock_source {
SND_EFW_CLOCK_SOURCE_INTERNAL = 0,
// Unused.
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,
SND_EFW_CLOCK_SOURCE_CONTINUOUS = 6 /* internal variable clock */
};
enum snd_efw_transport_mode {
SND_EFW_TRANSPORT_MODE_WINDOWS = 0,
SND_EFW_TRANSPORT_MODE_IEC61883 = 1,
};
int snd_efw_command_set_resp_addr(struct snd_efw *efw,
u16 addr_high, u32 addr_low);
int snd_efw_command_set_tx_mode(struct snd_efw *efw,
enum snd_efw_transport_mode mode);
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,
unsigned int len);
int snd_efw_command_get_clock_source(struct snd_efw *efw,
enum snd_efw_clock_source *source);
int snd_efw_command_get_sampling_rate(struct snd_efw *efw, unsigned int *rate);
int snd_efw_command_set_sampling_rate(struct snd_efw *efw, unsigned int rate);
int snd_efw_stream_init_duplex(struct snd_efw *efw);
int snd_efw_stream_reserve_duplex(struct snd_efw *efw, unsigned int rate,
unsigned int frames_per_period,
unsigned int frames_per_buffer);
int snd_efw_stream_start_duplex(struct snd_efw *efw);
void snd_efw_stream_stop_duplex(struct snd_efw *efw);
void snd_efw_stream_update_duplex(struct snd_efw *efw);
void snd_efw_stream_destroy_duplex(struct snd_efw *efw);
void snd_efw_stream_lock_changed(struct snd_efw *efw);
int snd_efw_stream_lock_try(struct snd_efw *efw);
void snd_efw_stream_lock_release(struct snd_efw *efw);
void snd_efw_proc_init(struct snd_efw *efw);
int snd_efw_create_midi_devices(struct snd_efw *efw);
int snd_efw_create_pcm_devices(struct snd_efw *efw);
int snd_efw_get_multiplier_mode(unsigned int sampling_rate, unsigned int *mode);
int snd_efw_create_hwdep_device(struct snd_efw *efw);
#endif
@@ -0,0 +1,374 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* fireworks_command.c - a part of driver for Fireworks based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
#include "./fireworks.h"
/*
* This driver uses transaction version 1 or later to use extended hardware
* information. Then too old devices are not available.
*
* Each commands are not required to have continuous sequence numbers. This
* number is just used to match command and response.
*
* This module support a part of commands. Please see FFADO if you want to see
* whole commands. But there are some commands which FFADO don't implement.
*
* Fireworks also supports AV/C general commands and AV/C Stream Format
* Information commands. But this module don't use them.
*/
#define KERNEL_SEQNUM_MIN (SND_EFW_TRANSACTION_USER_SEQNUM_MAX + 2)
#define KERNEL_SEQNUM_MAX ((u32)~0)
/* 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_TRANSPORT = 2,
EFC_CAT_HWCTL = 3,
};
/* hardware info category commands */
enum efc_cmd_hwinfo {
EFC_CMD_HWINFO_GET_CAPS = 0,
EFC_CMD_HWINFO_GET_POLLED = 1,
EFC_CMD_HWINFO_SET_RESP_ADDR = 2
};
enum efc_cmd_transport {
EFC_CMD_TRANSPORT_SET_TX_MODE = 0
};
/* hardware control category commands */
enum efc_cmd_hwctl {
EFC_CMD_HWCTL_SET_CLOCK = 0,
EFC_CMD_HWCTL_GET_CLOCK = 1,
EFC_CMD_HWCTL_IDENTIFY = 5
};
/* return values in response */
enum efr_status {
EFR_STATUS_OK = 0,
EFR_STATUS_BAD = 1,
EFR_STATUS_BAD_COMMAND = 2,
EFR_STATUS_COMM_ERR = 3,
EFR_STATUS_BAD_QUAD_COUNT = 4,
EFR_STATUS_UNSUPPORTED = 5,
EFR_STATUS_1394_TIMEOUT = 6,
EFR_STATUS_DSP_TIMEOUT = 7,
EFR_STATUS_BAD_RATE = 8,
EFR_STATUS_BAD_CLOCK = 9,
EFR_STATUS_BAD_CHANNEL = 10,
EFR_STATUS_BAD_PAN = 11,
EFR_STATUS_FLASH_BUSY = 12,
EFR_STATUS_BAD_MIRROR = 13,
EFR_STATUS_BAD_LED = 14,
EFR_STATUS_BAD_PARAMETER = 15,
EFR_STATUS_INCOMPLETE = 0x80000000
};
static const char *const efr_status_names[] = {
[EFR_STATUS_OK] = "OK",
[EFR_STATUS_BAD] = "bad",
[EFR_STATUS_BAD_COMMAND] = "bad command",
[EFR_STATUS_COMM_ERR] = "comm err",
[EFR_STATUS_BAD_QUAD_COUNT] = "bad quad count",
[EFR_STATUS_UNSUPPORTED] = "unsupported",
[EFR_STATUS_1394_TIMEOUT] = "1394 timeout",
[EFR_STATUS_DSP_TIMEOUT] = "DSP timeout",
[EFR_STATUS_BAD_RATE] = "bad rate",
[EFR_STATUS_BAD_CLOCK] = "bad clock",
[EFR_STATUS_BAD_CHANNEL] = "bad channel",
[EFR_STATUS_BAD_PAN] = "bad pan",
[EFR_STATUS_FLASH_BUSY] = "flash busy",
[EFR_STATUS_BAD_MIRROR] = "bad mirror",
[EFR_STATUS_BAD_LED] = "bad LED",
[EFR_STATUS_BAD_PARAMETER] = "bad parameter",
[EFR_STATUS_BAD_PARAMETER + 1] = "incomplete"
};
static int
efw_transaction(struct snd_efw *efw, unsigned int category,
unsigned int command,
const __be32 *params, unsigned int param_bytes,
const __be32 *resp, unsigned int resp_bytes)
{
struct snd_efw_transaction *header;
__be32 *buf;
u32 seqnum;
unsigned int buf_bytes, cmd_bytes;
int err;
/* calculate buffer size*/
buf_bytes = sizeof(struct snd_efw_transaction) +
max(param_bytes, resp_bytes);
/* keep buffer */
buf = kzalloc(buf_bytes, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
/* to keep consistency of sequence number */
scoped_guard(spinlock, &efw->lock) {
if ((efw->seqnum < KERNEL_SEQNUM_MIN) ||
(efw->seqnum >= KERNEL_SEQNUM_MAX - 2))
efw->seqnum = KERNEL_SEQNUM_MIN;
else
efw->seqnum += 2;
seqnum = efw->seqnum;
}
/* fill transaction header fields */
cmd_bytes = sizeof(struct snd_efw_transaction) + param_bytes;
header = (struct snd_efw_transaction *)buf;
header->length = cpu_to_be32(cmd_bytes / sizeof(__be32));
header->version = cpu_to_be32(1);
header->seqnum = cpu_to_be32(seqnum);
header->category = cpu_to_be32(category);
header->command = cpu_to_be32(command);
header->status = 0;
/* fill transaction command parameters */
memcpy(header->params, params, param_bytes);
err = snd_efw_transaction_run(efw->unit, buf, cmd_bytes,
buf, buf_bytes);
if (err < 0)
goto end;
/* check transaction header fields */
if ((be32_to_cpu(header->version) < 1) ||
(be32_to_cpu(header->category) != category) ||
(be32_to_cpu(header->command) != command) ||
(be32_to_cpu(header->status) != EFR_STATUS_OK)) {
u32 st = be32_to_cpu(header->status);
dev_err(&efw->unit->device, "EFW command failed [%u/%u]: %s\n",
be32_to_cpu(header->category),
be32_to_cpu(header->command),
st < ARRAY_SIZE(efr_status_names) ?
efr_status_names[st] : "unknown");
err = -EIO;
goto end;
}
if (resp == NULL)
goto end;
/* fill transaction response parameters */
memset((void *)resp, 0, resp_bytes);
resp_bytes = min_t(unsigned int, resp_bytes,
be32_to_cpu(header->length) * sizeof(__be32) -
sizeof(struct snd_efw_transaction));
memcpy((void *)resp, &buf[6], resp_bytes);
end:
kfree(buf);
return err;
}
/*
* The address in host system for transaction response is changable when the
* device supports. struct hwinfo.flags includes its flag. The default is
* MEMORY_SPACE_EFW_RESPONSE.
*/
int snd_efw_command_set_resp_addr(struct snd_efw *efw,
u16 addr_high, u32 addr_low)
{
__be32 addr[2];
addr[0] = cpu_to_be32(addr_high);
addr[1] = cpu_to_be32(addr_low);
if (!efw->resp_addr_changable)
return -ENOSYS;
return efw_transaction(efw, EFC_CAT_HWCTL,
EFC_CMD_HWINFO_SET_RESP_ADDR,
addr, sizeof(addr), NULL, 0);
}
/*
* This is for timestamp processing. In Windows mode, all 32bit fields of second
* CIP header in AMDTP transmit packet is used for 'presentation timestamp'. In
* 'no data' packet the value of this field is 0x90ffffff.
*/
int snd_efw_command_set_tx_mode(struct snd_efw *efw,
enum snd_efw_transport_mode mode)
{
__be32 param = cpu_to_be32(mode);
return efw_transaction(efw, EFC_CAT_TRANSPORT,
EFC_CMD_TRANSPORT_SET_TX_MODE,
&param, sizeof(param), NULL, 0);
}
int snd_efw_command_get_hwinfo(struct snd_efw *efw,
struct snd_efw_hwinfo *hwinfo)
{
int err;
err = efw_transaction(efw, EFC_CAT_HWINFO,
EFC_CMD_HWINFO_GET_CAPS,
NULL, 0, (__be32 *)hwinfo, sizeof(*hwinfo));
if (err < 0)
goto end;
be32_to_cpus(&hwinfo->flags);
be32_to_cpus(&hwinfo->guid_hi);
be32_to_cpus(&hwinfo->guid_lo);
be32_to_cpus(&hwinfo->type);
be32_to_cpus(&hwinfo->version);
be32_to_cpus(&hwinfo->supported_clocks);
be32_to_cpus(&hwinfo->amdtp_rx_pcm_channels);
be32_to_cpus(&hwinfo->amdtp_tx_pcm_channels);
be32_to_cpus(&hwinfo->phys_out);
be32_to_cpus(&hwinfo->phys_in);
be32_to_cpus(&hwinfo->phys_out_grp_count);
be32_to_cpus(&hwinfo->phys_in_grp_count);
be32_to_cpus(&hwinfo->midi_out_ports);
be32_to_cpus(&hwinfo->midi_in_ports);
be32_to_cpus(&hwinfo->max_sample_rate);
be32_to_cpus(&hwinfo->min_sample_rate);
be32_to_cpus(&hwinfo->dsp_version);
be32_to_cpus(&hwinfo->arm_version);
be32_to_cpus(&hwinfo->mixer_playback_channels);
be32_to_cpus(&hwinfo->mixer_capture_channels);
be32_to_cpus(&hwinfo->fpga_version);
be32_to_cpus(&hwinfo->amdtp_rx_pcm_channels_2x);
be32_to_cpus(&hwinfo->amdtp_tx_pcm_channels_2x);
be32_to_cpus(&hwinfo->amdtp_rx_pcm_channels_4x);
be32_to_cpus(&hwinfo->amdtp_tx_pcm_channels_4x);
/* ensure terminated */
hwinfo->vendor_name[HWINFO_NAME_SIZE_BYTES - 1] = '\0';
hwinfo->model_name[HWINFO_NAME_SIZE_BYTES - 1] = '\0';
end:
return err;
}
int snd_efw_command_get_phys_meters(struct snd_efw *efw,
struct snd_efw_phys_meters *meters,
unsigned int len)
{
u32 *buf = (u32 *)meters;
unsigned int i;
int err;
err = efw_transaction(efw, EFC_CAT_HWINFO,
EFC_CMD_HWINFO_GET_POLLED,
NULL, 0, (__be32 *)meters, len);
if (err >= 0)
for (i = 0; i < len / sizeof(u32); i++)
be32_to_cpus(&buf[i]);
return err;
}
static int
command_get_clock(struct snd_efw *efw, struct efc_clock *clock)
{
int err;
err = efw_transaction(efw, EFC_CAT_HWCTL,
EFC_CMD_HWCTL_GET_CLOCK,
NULL, 0,
(__be32 *)clock, sizeof(struct efc_clock));
if (err >= 0) {
be32_to_cpus(&clock->source);
be32_to_cpus(&clock->sampling_rate);
be32_to_cpus(&clock->index);
}
return err;
}
/* give UINT_MAX if set nothing */
static int
command_set_clock(struct snd_efw *efw,
unsigned int source, unsigned int rate)
{
struct efc_clock clock = {0};
int err;
/* check arguments */
if ((source == UINT_MAX) && (rate == UINT_MAX)) {
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 == rate))
goto end;
/* set params */
if ((source != UINT_MAX) && (clock.source != source))
clock.source = source;
if ((rate != UINT_MAX) && (clock.sampling_rate != rate))
clock.sampling_rate = rate;
clock.index = 0;
cpu_to_be32s(&clock.source);
cpu_to_be32s(&clock.sampling_rate);
cpu_to_be32s(&clock.index);
err = efw_transaction(efw, EFC_CAT_HWCTL,
EFC_CMD_HWCTL_SET_CLOCK,
(__be32 *)&clock, sizeof(struct efc_clock),
NULL, 0);
if (err < 0)
goto end;
/*
* With firmware version 5.8, just after changing clock state, these
* parameters are not immediately retrieved by get command. In my
* trial, there needs to be 100msec to get changed parameters.
*/
msleep(150);
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_get_sampling_rate(struct snd_efw *efw, unsigned int *rate)
{
int err;
struct efc_clock clock = {0};
err = command_get_clock(efw, &clock);
if (err >= 0)
*rate = clock.sampling_rate;
return err;
}
int snd_efw_command_set_sampling_rate(struct snd_efw *efw, unsigned int rate)
{
return command_set_clock(efw, UINT_MAX, rate);
}
@@ -0,0 +1,313 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* fireworks_hwdep.c - a part of driver for Fireworks based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
/*
* This codes have five functionalities.
*
* 1.get information about firewire node
* 2.get notification about starting/stopping stream
* 3.lock/unlock streaming
* 4.transmit command of EFW transaction
* 5.receive response of EFW transaction
*
*/
#include "fireworks.h"
static long
hwdep_read_resp_buf(struct snd_efw *efw, char __user *buf, long remained,
loff_t *offset)
{
unsigned int length, till_end, type;
struct snd_efw_transaction *t;
u8 *pull_ptr;
long count = 0;
if (remained < sizeof(type) + sizeof(struct snd_efw_transaction))
return -ENOSPC;
/* data type is SNDRV_FIREWIRE_EVENT_EFW_RESPONSE */
type = SNDRV_FIREWIRE_EVENT_EFW_RESPONSE;
if (copy_to_user(buf, &type, sizeof(type)))
return -EFAULT;
count += sizeof(type);
remained -= sizeof(type);
buf += sizeof(type);
/* write into buffer as many responses as possible */
spin_lock_irq(&efw->lock);
/*
* When another task reaches here during this task's access to user
* space, it picks up current position in buffer and can read the same
* series of responses.
*/
pull_ptr = efw->pull_ptr;
while (efw->push_ptr != pull_ptr) {
t = (struct snd_efw_transaction *)(pull_ptr);
length = be32_to_cpu(t->length) * sizeof(__be32);
/* confirm enough space for this response */
if (remained < length)
break;
/* copy from ring buffer to user buffer */
while (length > 0) {
till_end = snd_efw_resp_buf_size -
(unsigned int)(pull_ptr - efw->resp_buf);
till_end = min_t(unsigned int, length, till_end);
spin_unlock_irq(&efw->lock);
if (copy_to_user(buf, pull_ptr, till_end))
return -EFAULT;
spin_lock_irq(&efw->lock);
pull_ptr += till_end;
if (pull_ptr >= efw->resp_buf + snd_efw_resp_buf_size)
pull_ptr -= snd_efw_resp_buf_size;
length -= till_end;
buf += till_end;
count += till_end;
remained -= till_end;
}
}
/*
* All of tasks can read from the buffer nearly simultaneously, but the
* last position for each task is different depending on the length of
* given buffer. Here, for simplicity, a position of buffer is set by
* the latest task. It's better for a listening application to allow one
* thread to read from the buffer. Unless, each task can read different
* sequence of responses depending on variation of buffer length.
*/
efw->pull_ptr = pull_ptr;
spin_unlock_irq(&efw->lock);
return count;
}
static long
hwdep_read_locked(struct snd_efw *efw, char __user *buf, long count,
loff_t *offset)
{
union snd_firewire_event event = {
.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS,
};
scoped_guard(spinlock_irq, &efw->lock) {
event.lock_status.status = (efw->dev_lock_count > 0);
efw->dev_lock_changed = false;
}
count = min_t(long, count, sizeof(event.lock_status));
if (copy_to_user(buf, &event, count))
return -EFAULT;
return count;
}
static long
hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
loff_t *offset)
{
struct snd_efw *efw = hwdep->private_data;
DEFINE_WAIT(wait);
bool dev_lock_changed;
bool queued;
spin_lock_irq(&efw->lock);
dev_lock_changed = efw->dev_lock_changed;
queued = efw->push_ptr != efw->pull_ptr;
while (!dev_lock_changed && !queued) {
prepare_to_wait(&efw->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
spin_unlock_irq(&efw->lock);
schedule();
finish_wait(&efw->hwdep_wait, &wait);
if (signal_pending(current))
return -ERESTARTSYS;
spin_lock_irq(&efw->lock);
dev_lock_changed = efw->dev_lock_changed;
queued = efw->push_ptr != efw->pull_ptr;
}
spin_unlock_irq(&efw->lock);
if (dev_lock_changed)
count = hwdep_read_locked(efw, buf, count, offset);
else if (queued)
count = hwdep_read_resp_buf(efw, buf, count, offset);
return count;
}
static long
hwdep_write(struct snd_hwdep *hwdep, const char __user *data, long count,
loff_t *offset)
{
struct snd_efw *efw = hwdep->private_data;
u32 seqnum;
u8 *buf;
if (count < sizeof(struct snd_efw_transaction) ||
SND_EFW_RESPONSE_MAXIMUM_BYTES < count)
return -EINVAL;
buf = memdup_user(data, count);
if (IS_ERR(buf))
return PTR_ERR(buf);
/* check seqnum is not for kernel-land */
seqnum = be32_to_cpu(((struct snd_efw_transaction *)buf)->seqnum);
if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX) {
count = -EINVAL;
goto end;
}
if (snd_efw_transaction_cmd(efw->unit, buf, count) < 0)
count = -EIO;
end:
kfree(buf);
return count;
}
static __poll_t
hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait)
{
struct snd_efw *efw = hwdep->private_data;
__poll_t events;
poll_wait(file, &efw->hwdep_wait, wait);
guard(spinlock_irq)(&efw->lock);
if (efw->dev_lock_changed || efw->pull_ptr != efw->push_ptr)
events = EPOLLIN | EPOLLRDNORM;
else
events = 0;
return events | EPOLLOUT;
}
static int
hwdep_get_info(struct snd_efw *efw, void __user *arg)
{
struct fw_device *dev = fw_parent_device(efw->unit);
struct snd_firewire_get_info info;
memset(&info, 0, sizeof(info));
info.type = SNDRV_FIREWIRE_TYPE_FIREWORKS;
info.card = dev->card->index;
*(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]);
*(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]);
strscpy(info.device_name, dev_name(&dev->device),
sizeof(info.device_name));
if (copy_to_user(arg, &info, sizeof(info)))
return -EFAULT;
return 0;
}
static int
hwdep_lock(struct snd_efw *efw)
{
guard(spinlock_irq)(&efw->lock);
if (efw->dev_lock_count == 0) {
efw->dev_lock_count = -1;
return 0;
} else {
return -EBUSY;
}
}
static int
hwdep_unlock(struct snd_efw *efw)
{
guard(spinlock_irq)(&efw->lock);
if (efw->dev_lock_count == -1) {
efw->dev_lock_count = 0;
return 0;
} else {
return -EBADFD;
}
}
static int
hwdep_release(struct snd_hwdep *hwdep, struct file *file)
{
struct snd_efw *efw = hwdep->private_data;
guard(spinlock_irq)(&efw->lock);
if (efw->dev_lock_count == -1)
efw->dev_lock_count = 0;
return 0;
}
static int
hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct snd_efw *efw = hwdep->private_data;
switch (cmd) {
case SNDRV_FIREWIRE_IOCTL_GET_INFO:
return hwdep_get_info(efw, (void __user *)arg);
case SNDRV_FIREWIRE_IOCTL_LOCK:
return hwdep_lock(efw);
case SNDRV_FIREWIRE_IOCTL_UNLOCK:
return hwdep_unlock(efw);
default:
return -ENOIOCTLCMD;
}
}
#ifdef CONFIG_COMPAT
static int
hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file,
unsigned int cmd, unsigned long arg)
{
return hwdep_ioctl(hwdep, file, cmd,
(unsigned long)compat_ptr(arg));
}
#else
#define hwdep_compat_ioctl NULL
#endif
int snd_efw_create_hwdep_device(struct snd_efw *efw)
{
static const struct snd_hwdep_ops ops = {
.read = hwdep_read,
.write = hwdep_write,
.release = hwdep_release,
.poll = hwdep_poll,
.ioctl = hwdep_ioctl,
.ioctl_compat = hwdep_compat_ioctl,
};
struct snd_hwdep *hwdep;
int err;
err = snd_hwdep_new(efw->card, "Fireworks", 0, &hwdep);
if (err < 0)
goto end;
strscpy(hwdep->name, "Fireworks");
hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREWORKS;
hwdep->ops = ops;
hwdep->private_data = efw;
hwdep->exclusive = true;
end:
return err;
}
@@ -0,0 +1,138 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* fireworks_midi.c - a part of driver for Fireworks based devices
*
* Copyright (c) 2009-2010 Clemens Ladisch
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
#include "fireworks.h"
static int midi_open(struct snd_rawmidi_substream *substream)
{
struct snd_efw *efw = substream->rmidi->private_data;
int err;
err = snd_efw_stream_lock_try(efw);
if (err < 0)
return err;
scoped_guard(mutex, &efw->mutex) {
err = snd_efw_stream_reserve_duplex(efw, 0, 0, 0);
if (err >= 0) {
++efw->substreams_counter;
err = snd_efw_stream_start_duplex(efw);
if (err < 0)
--efw->substreams_counter;
}
}
if (err < 0)
snd_efw_stream_lock_release(efw);
return err;
}
static int midi_close(struct snd_rawmidi_substream *substream)
{
struct snd_efw *efw = substream->rmidi->private_data;
scoped_guard(mutex, &efw->mutex) {
--efw->substreams_counter;
snd_efw_stream_stop_duplex(efw);
}
snd_efw_stream_lock_release(efw);
return 0;
}
static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
{
struct snd_efw *efw = substrm->rmidi->private_data;
guard(spinlock_irqsave)(&efw->lock);
if (up)
amdtp_am824_midi_trigger(&efw->tx_stream,
substrm->number, substrm);
else
amdtp_am824_midi_trigger(&efw->tx_stream,
substrm->number, NULL);
}
static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up)
{
struct snd_efw *efw = substrm->rmidi->private_data;
guard(spinlock_irqsave)(&efw->lock);
if (up)
amdtp_am824_midi_trigger(&efw->rx_stream,
substrm->number, substrm);
else
amdtp_am824_midi_trigger(&efw->rx_stream,
substrm->number, NULL);
}
static void set_midi_substream_names(struct snd_efw *efw,
struct snd_rawmidi_str *str)
{
struct snd_rawmidi_substream *subs;
list_for_each_entry(subs, &str->substreams, list) {
scnprintf(subs->name, sizeof(subs->name),
"%s MIDI %d", efw->card->shortname, subs->number + 1);
}
}
int snd_efw_create_midi_devices(struct snd_efw *efw)
{
static const struct snd_rawmidi_ops capture_ops = {
.open = midi_open,
.close = midi_close,
.trigger = midi_capture_trigger,
};
static const struct snd_rawmidi_ops playback_ops = {
.open = midi_open,
.close = midi_close,
.trigger = midi_playback_trigger,
};
struct snd_rawmidi *rmidi;
struct snd_rawmidi_str *str;
int err;
/* create midi ports */
err = snd_rawmidi_new(efw->card, efw->card->driver, 0,
efw->midi_out_ports, efw->midi_in_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_in_ports > 0) {
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT;
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
&capture_ops);
str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT];
set_midi_substream_names(efw, str);
}
if (efw->midi_out_ports > 0) {
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT;
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
&playback_ops);
str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT];
set_midi_substream_names(efw, str);
}
if ((efw->midi_out_ports > 0) && (efw->midi_in_ports > 0))
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
return 0;
}
@@ -0,0 +1,397 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* fireworks_pcm.c - a part of driver for Fireworks based devices
*
* Copyright (c) 2009-2010 Clemens Ladisch
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
#include "./fireworks.h"
/*
* NOTE:
* Fireworks changes its AMDTP channels for PCM data according to its sampling
* rate. There are three modes. Here _XX is either _rx or _tx.
* 0: 32.0- 48.0 kHz then snd_efw_hwinfo.amdtp_XX_pcm_channels applied
* 1: 88.2- 96.0 kHz then snd_efw_hwinfo.amdtp_XX_pcm_channels_2x applied
* 2: 176.4-192.0 kHz then snd_efw_hwinfo.amdtp_XX_pcm_channels_4x applied
*
* 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", in some
* model, 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 at 32.0-48.0 kHz : use input 1-8
* - ADAT optical at 88.2-96.0 kHz : use input 1-4 (S/MUX format)
*
* The data in AMDTP channels for blank PCM channels are zero.
*/
static const 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 unsigned int
get_multiplier_mode_with_index(unsigned int index)
{
return ((int)index - 1) / 2;
}
int snd_efw_get_multiplier_mode(unsigned int sampling_rate, unsigned int *mode)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(freq_table); i++) {
if (freq_table[i] == sampling_rate) {
*mode = get_multiplier_mode_with_index(i);
return 0;
}
}
return -EINVAL;
}
static int
hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule)
{
unsigned int *pcm_channels = rule->private;
struct snd_interval *r =
hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
const struct snd_interval *c =
hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS);
struct snd_interval t = {
.min = UINT_MAX, .max = 0, .integer = 1
};
unsigned int i, mode;
for (i = 0; i < ARRAY_SIZE(freq_table); i++) {
mode = get_multiplier_mode_with_index(i);
if (!snd_interval_test(c, pcm_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)
{
unsigned int *pcm_channels = rule->private;
struct snd_interval *c =
hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
const struct snd_interval *r =
hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE);
struct snd_interval t = {
.min = UINT_MAX, .max = 0, .integer = 1
};
unsigned int i, mode;
for (i = 0; i < ARRAY_SIZE(freq_table); i++) {
mode = get_multiplier_mode_with_index(i);
if (!snd_interval_test(r, freq_table[i]))
continue;
t.min = min(t.min, pcm_channels[mode]);
t.max = max(t.max, pcm_channels[mode]);
}
return snd_interval_refine(c, &t);
}
static void
limit_channels(struct snd_pcm_hardware *hw, unsigned int *pcm_channels)
{
unsigned int i, mode;
hw->channels_min = UINT_MAX;
hw->channels_max = 0;
for (i = 0; i < ARRAY_SIZE(freq_table); i++) {
mode = get_multiplier_mode_with_index(i);
if (pcm_channels[mode] == 0)
continue;
hw->channels_min = min(hw->channels_min, pcm_channels[mode]);
hw->channels_max = max(hw->channels_max, pcm_channels[mode]);
}
}
static int
pcm_init_hw_params(struct snd_efw *efw,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct amdtp_stream *s;
unsigned int *pcm_channels;
int err;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
runtime->hw.formats = AM824_IN_PCM_FORMAT_BITS;
s = &efw->tx_stream;
pcm_channels = efw->pcm_capture_channels;
} else {
runtime->hw.formats = AM824_OUT_PCM_FORMAT_BITS;
s = &efw->rx_stream;
pcm_channels = efw->pcm_playback_channels;
}
/* limit rates */
runtime->hw.rates = efw->supported_sampling_rate;
snd_pcm_limit_hw_rates(runtime);
limit_channels(&runtime->hw, pcm_channels);
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
hw_rule_channels, pcm_channels,
SNDRV_PCM_HW_PARAM_RATE, -1);
if (err < 0)
goto end;
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
hw_rule_rate, pcm_channels,
SNDRV_PCM_HW_PARAM_CHANNELS, -1);
if (err < 0)
goto end;
err = amdtp_am824_add_pcm_hw_constraints(s, runtime);
end:
return err;
}
static int pcm_open(struct snd_pcm_substream *substream)
{
struct snd_efw *efw = substream->private_data;
struct amdtp_domain *d = &efw->domain;
enum snd_efw_clock_source clock_source;
int err;
err = snd_efw_stream_lock_try(efw);
if (err < 0)
return err;
err = pcm_init_hw_params(efw, substream);
if (err < 0)
goto err_locked;
err = snd_efw_command_get_clock_source(efw, &clock_source);
if (err < 0)
goto err_locked;
scoped_guard(mutex, &efw->mutex) {
// When source of clock is not internal or any stream is reserved for
// transmission of PCM frames, the available sampling rate is limited
// at current one.
if ((clock_source != SND_EFW_CLOCK_SOURCE_INTERNAL) ||
(efw->substreams_counter > 0 && d->events_per_period > 0)) {
unsigned int frames_per_period = d->events_per_period;
unsigned int frames_per_buffer = d->events_per_buffer;
unsigned int sampling_rate;
err = snd_efw_command_get_sampling_rate(efw, &sampling_rate);
if (err < 0)
goto err_locked;
substream->runtime->hw.rate_min = sampling_rate;
substream->runtime->hw.rate_max = sampling_rate;
if (frames_per_period > 0) {
err = snd_pcm_hw_constraint_minmax(substream->runtime,
SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
frames_per_period, frames_per_period);
if (err < 0)
goto err_locked;
err = snd_pcm_hw_constraint_minmax(substream->runtime,
SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
frames_per_buffer, frames_per_buffer);
if (err < 0)
goto err_locked;
}
}
}
snd_pcm_set_sync(substream);
return 0;
err_locked:
snd_efw_stream_lock_release(efw);
return err;
}
static int pcm_close(struct snd_pcm_substream *substream)
{
struct snd_efw *efw = substream->private_data;
snd_efw_stream_lock_release(efw);
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;
int err = 0;
if (substream->runtime->state == SNDRV_PCM_STATE_OPEN) {
unsigned int rate = params_rate(hw_params);
unsigned int frames_per_period = params_period_size(hw_params);
unsigned int frames_per_buffer = params_buffer_size(hw_params);
guard(mutex)(&efw->mutex);
err = snd_efw_stream_reserve_duplex(efw, rate,
frames_per_period, frames_per_buffer);
if (err >= 0)
++efw->substreams_counter;
}
return err;
}
static int pcm_hw_free(struct snd_pcm_substream *substream)
{
struct snd_efw *efw = substream->private_data;
guard(mutex)(&efw->mutex);
if (substream->runtime->state != SNDRV_PCM_STATE_OPEN)
--efw->substreams_counter;
snd_efw_stream_stop_duplex(efw);
return 0;
}
static int pcm_capture_prepare(struct snd_pcm_substream *substream)
{
struct snd_efw *efw = substream->private_data;
int err;
err = snd_efw_stream_start_duplex(efw);
if (err >= 0)
amdtp_stream_pcm_prepare(&efw->tx_stream);
return err;
}
static int pcm_playback_prepare(struct snd_pcm_substream *substream)
{
struct snd_efw *efw = substream->private_data;
int err;
err = snd_efw_stream_start_duplex(efw);
if (err >= 0)
amdtp_stream_pcm_prepare(&efw->rx_stream);
return err;
}
static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_efw *efw = substream->private_data;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
amdtp_stream_pcm_trigger(&efw->tx_stream, substream);
break;
case SNDRV_PCM_TRIGGER_STOP:
amdtp_stream_pcm_trigger(&efw->tx_stream, NULL);
break;
default:
return -EINVAL;
}
return 0;
}
static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_efw *efw = substream->private_data;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
amdtp_stream_pcm_trigger(&efw->rx_stream, substream);
break;
case SNDRV_PCM_TRIGGER_STOP:
amdtp_stream_pcm_trigger(&efw->rx_stream, NULL);
break;
default:
return -EINVAL;
}
return 0;
}
static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
{
struct snd_efw *efw = sbstrm->private_data;
return amdtp_domain_stream_pcm_pointer(&efw->domain, &efw->tx_stream);
}
static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
{
struct snd_efw *efw = sbstrm->private_data;
return amdtp_domain_stream_pcm_pointer(&efw->domain, &efw->rx_stream);
}
static int pcm_capture_ack(struct snd_pcm_substream *substream)
{
struct snd_efw *efw = substream->private_data;
return amdtp_domain_stream_pcm_ack(&efw->domain, &efw->tx_stream);
}
static int pcm_playback_ack(struct snd_pcm_substream *substream)
{
struct snd_efw *efw = substream->private_data;
return amdtp_domain_stream_pcm_ack(&efw->domain, &efw->rx_stream);
}
int snd_efw_create_pcm_devices(struct snd_efw *efw)
{
static const struct snd_pcm_ops capture_ops = {
.open = pcm_open,
.close = pcm_close,
.hw_params = pcm_hw_params,
.hw_free = pcm_hw_free,
.prepare = pcm_capture_prepare,
.trigger = pcm_capture_trigger,
.pointer = pcm_capture_pointer,
.ack = pcm_capture_ack,
};
static const struct snd_pcm_ops playback_ops = {
.open = pcm_open,
.close = pcm_close,
.hw_params = pcm_hw_params,
.hw_free = pcm_hw_free,
.prepare = pcm_playback_prepare,
.trigger = pcm_playback_trigger,
.pointer = pcm_playback_pointer,
.ack = pcm_playback_ack,
};
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;
pcm->nonatomic = true;
snprintf(pcm->name, sizeof(pcm->name), "%s PCM", efw->card->shortname);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops);
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0);
end:
return err;
}
@@ -0,0 +1,223 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* fireworks_proc.c - a part of driver for Fireworks based devices
*
* Copyright (c) 2009-2010 Clemens Ladisch
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
#include "./fireworks.h"
static inline const char*
get_phys_name(struct snd_efw_phys_grp *grp, bool input)
{
static const char *const ch_type[] = {
"Analog", "S/PDIF", "ADAT", "S/PDIF or ADAT", "Mirroring",
"Headphones", "I2S", "Guitar", "Pirzo Guitar", "Guitar String",
};
if (grp->type < ARRAY_SIZE(ch_type))
return ch_type[grp->type];
else if (input)
return "Input";
else
return "Output";
}
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;
hwinfo = kmalloc_obj(struct snd_efw_hwinfo);
if (hwinfo == NULL)
return;
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, "phys out: 0x%X\n", hwinfo->phys_out);
snd_iprintf(buffer, "phys in: 0x%X\n", hwinfo->phys_in);
snd_iprintf(buffer, "phys in grps: 0x%X\n",
hwinfo->phys_in_grp_count);
for (i = 0; i < hwinfo->phys_in_grp_count; i++) {
snd_iprintf(buffer,
"phys in grp[%d]: type 0x%X, count 0x%X\n",
i, hwinfo->phys_out_grps[i].type,
hwinfo->phys_out_grps[i].count);
}
snd_iprintf(buffer, "phys out grps: 0x%X\n",
hwinfo->phys_out_grp_count);
for (i = 0; i < hwinfo->phys_out_grp_count; i++) {
snd_iprintf(buffer,
"phys out grps[%d]: type 0x%X, count 0x%X\n",
i, hwinfo->phys_out_grps[i].type,
hwinfo->phys_out_grps[i].count);
}
snd_iprintf(buffer, "amdtp rx pcm channels 1x: 0x%X\n",
hwinfo->amdtp_rx_pcm_channels);
snd_iprintf(buffer, "amdtp tx pcm channels 1x: 0x%X\n",
hwinfo->amdtp_tx_pcm_channels);
snd_iprintf(buffer, "amdtp rx pcm channels 2x: 0x%X\n",
hwinfo->amdtp_rx_pcm_channels_2x);
snd_iprintf(buffer, "amdtp tx pcm channels 2x: 0x%X\n",
hwinfo->amdtp_tx_pcm_channels_2x);
snd_iprintf(buffer, "amdtp rx pcm channels 4x: 0x%X\n",
hwinfo->amdtp_rx_pcm_channels_4x);
snd_iprintf(buffer, "amdtp tx pcm channels 4x: 0x%X\n",
hwinfo->amdtp_tx_pcm_channels_4x);
snd_iprintf(buffer, "midi out ports: 0x%X\n", hwinfo->midi_out_ports);
snd_iprintf(buffer, "midi in ports: 0x%X\n", hwinfo->midi_in_ports);
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:
kfree(hwinfo);
}
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;
unsigned int sampling_rate;
if (snd_efw_command_get_clock_source(efw, &clock_source) < 0)
return;
if (snd_efw_command_get_sampling_rate(efw, &sampling_rate) < 0)
return;
snd_iprintf(buffer, "Clock Source: %d\n", clock_source);
snd_iprintf(buffer, "Sampling Rate: %d\n", sampling_rate);
}
/*
* NOTE:
* dB = 20 * log10(linear / 0x01000000)
* -144.0 dB when linear is 0
*/
static void
proc_read_phys_meters(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_efw *efw = entry->private_data;
struct snd_efw_phys_meters *meters;
unsigned int g, c, m, max, size;
const char *name;
u32 *linear;
int err;
size = sizeof(struct snd_efw_phys_meters) +
(efw->phys_in + efw->phys_out) * sizeof(u32);
meters = kzalloc(size, GFP_KERNEL);
if (meters == NULL)
return;
err = snd_efw_command_get_phys_meters(efw, meters, size);
if (err < 0)
goto end;
snd_iprintf(buffer, "Physical Meters:\n");
m = 0;
max = min(efw->phys_out, meters->out_meters);
linear = meters->values;
snd_iprintf(buffer, " %d Outputs:\n", max);
for (g = 0; g < efw->phys_out_grp_count; g++) {
name = get_phys_name(&efw->phys_out_grps[g], false);
for (c = 0; c < efw->phys_out_grps[g].count; c++) {
if (m < max)
snd_iprintf(buffer, "\t%s [%d]: %d\n",
name, c, linear[m++]);
}
}
m = 0;
max = min(efw->phys_in, meters->in_meters);
linear = meters->values + meters->out_meters;
snd_iprintf(buffer, " %d Inputs:\n", max);
for (g = 0; g < efw->phys_in_grp_count; g++) {
name = get_phys_name(&efw->phys_in_grps[g], true);
for (c = 0; c < efw->phys_in_grps[g].count; c++)
if (m < max)
snd_iprintf(buffer, "\t%s [%d]: %d\n",
name, c, linear[m++]);
}
end:
kfree(meters);
}
static void
proc_read_queues_state(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_efw *efw = entry->private_data;
unsigned int consumed;
if (efw->pull_ptr > efw->push_ptr)
consumed = snd_efw_resp_buf_size -
(unsigned int)(efw->pull_ptr - efw->push_ptr);
else
consumed = (unsigned int)(efw->push_ptr - efw->pull_ptr);
snd_iprintf(buffer, "%d/%d\n",
consumed, snd_efw_resp_buf_size);
}
static void
add_node(struct snd_efw *efw, struct snd_info_entry *root, const char *name,
void (*op)(struct snd_info_entry *e, struct snd_info_buffer *b))
{
struct snd_info_entry *entry;
entry = snd_info_create_card_entry(efw->card, name, root);
if (entry)
snd_info_set_text_ops(entry, efw, op);
}
void snd_efw_proc_init(struct snd_efw *efw)
{
struct snd_info_entry *root;
/*
* All nodes are automatically removed at snd_card_disconnect(),
* by following to link list.
*/
root = snd_info_create_card_entry(efw->card, "firewire",
efw->card->proc_root);
if (root == NULL)
return;
root->mode = S_IFDIR | 0555;
add_node(efw, root, "clock", proc_read_clock);
add_node(efw, root, "firmware", proc_read_hwinfo);
add_node(efw, root, "meters", proc_read_phys_meters);
add_node(efw, root, "queues", proc_read_queues_state);
}
@@ -0,0 +1,368 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* fireworks_stream.c - a part of driver for Fireworks based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
#include "./fireworks.h"
#define READY_TIMEOUT_MS 1000
static int init_stream(struct snd_efw *efw, struct amdtp_stream *stream)
{
struct cmp_connection *conn;
enum cmp_direction c_dir;
enum amdtp_stream_direction s_dir;
int err;
if (stream == &efw->tx_stream) {
conn = &efw->out_conn;
c_dir = CMP_OUTPUT;
s_dir = AMDTP_IN_STREAM;
} else {
conn = &efw->in_conn;
c_dir = CMP_INPUT;
s_dir = AMDTP_OUT_STREAM;
}
err = cmp_connection_init(conn, efw->unit, c_dir, 0);
if (err < 0)
return err;
err = amdtp_am824_init(stream, efw->unit, s_dir, CIP_BLOCKING | CIP_UNAWARE_SYT);
if (err < 0) {
amdtp_stream_destroy(stream);
cmp_connection_destroy(conn);
return err;
}
if (stream == &efw->tx_stream) {
// Fireworks transmits NODATA packets with TAG0.
efw->tx_stream.flags |= CIP_EMPTY_WITH_TAG0;
// Fireworks has its own meaning for dbc.
efw->tx_stream.flags |= CIP_DBC_IS_END_EVENT;
// Fireworks reset dbc at bus reset.
efw->tx_stream.flags |= CIP_SKIP_DBC_ZERO_CHECK;
// But Recent firmwares starts packets with non-zero dbc.
// Driver version 5.7.6 installs firmware version 5.7.3.
if (efw->is_fireworks3 &&
(efw->firmware_version == 0x5070000 ||
efw->firmware_version == 0x5070300 ||
efw->firmware_version == 0x5080000))
efw->tx_stream.flags |= CIP_UNALIGHED_DBC;
// AudioFire9 always reports wrong dbs. Onyx 1200F with the latest firmware (v4.6.0)
// also report wrong dbs at 88.2 kHz or greater.
if (efw->is_af9 || efw->firmware_version == 0x4060000)
efw->tx_stream.flags |= CIP_WRONG_DBS;
// Firmware version 5.5 reports fixed interval for dbc.
if (efw->firmware_version == 0x5050000)
efw->tx_stream.ctx_data.tx.dbc_interval = 8;
}
return err;
}
static int start_stream(struct snd_efw *efw, struct amdtp_stream *stream,
unsigned int rate)
{
struct cmp_connection *conn;
int err;
if (stream == &efw->tx_stream)
conn = &efw->out_conn;
else
conn = &efw->in_conn;
// Establish connection via CMP.
err = cmp_connection_establish(conn);
if (err < 0)
return err;
// Start amdtp stream.
err = amdtp_domain_add_stream(&efw->domain, stream,
conn->resources.channel, conn->speed);
if (err < 0) {
cmp_connection_break(conn);
return err;
}
return 0;
}
// This function should be called before starting the stream or after stopping
// the streams.
static void destroy_stream(struct snd_efw *efw, struct amdtp_stream *stream)
{
amdtp_stream_destroy(stream);
if (stream == &efw->tx_stream)
cmp_connection_destroy(&efw->out_conn);
else
cmp_connection_destroy(&efw->in_conn);
}
static int
check_connection_used_by_others(struct snd_efw *efw, struct amdtp_stream *s)
{
struct cmp_connection *conn;
bool used;
int err;
if (s == &efw->tx_stream)
conn = &efw->out_conn;
else
conn = &efw->in_conn;
err = cmp_connection_check_used(conn, &used);
if ((err >= 0) && used && !amdtp_stream_running(s)) {
dev_err(&efw->unit->device,
"Connection established by others: %cPCR[%d]\n",
(conn->direction == CMP_OUTPUT) ? 'o' : 'i',
conn->pcr_index);
err = -EBUSY;
}
return err;
}
int snd_efw_stream_init_duplex(struct snd_efw *efw)
{
int err;
err = init_stream(efw, &efw->tx_stream);
if (err < 0)
return err;
err = init_stream(efw, &efw->rx_stream);
if (err < 0) {
destroy_stream(efw, &efw->tx_stream);
return err;
}
err = amdtp_domain_init(&efw->domain);
if (err < 0) {
destroy_stream(efw, &efw->tx_stream);
destroy_stream(efw, &efw->rx_stream);
return err;
}
// set IEC61883 compliant mode (actually not fully compliant...).
err = snd_efw_command_set_tx_mode(efw, SND_EFW_TRANSPORT_MODE_IEC61883);
if (err < 0) {
destroy_stream(efw, &efw->tx_stream);
destroy_stream(efw, &efw->rx_stream);
}
return err;
}
static int keep_resources(struct snd_efw *efw, struct amdtp_stream *stream,
unsigned int rate, unsigned int mode)
{
unsigned int pcm_channels;
unsigned int midi_ports;
struct cmp_connection *conn;
int err;
if (stream == &efw->tx_stream) {
pcm_channels = efw->pcm_capture_channels[mode];
midi_ports = efw->midi_out_ports;
conn = &efw->out_conn;
} else {
pcm_channels = efw->pcm_playback_channels[mode];
midi_ports = efw->midi_in_ports;
conn = &efw->in_conn;
}
err = amdtp_am824_set_parameters(stream, rate, pcm_channels,
midi_ports, false);
if (err < 0)
return err;
return cmp_connection_reserve(conn, amdtp_stream_get_max_payload(stream));
}
int snd_efw_stream_reserve_duplex(struct snd_efw *efw, unsigned int rate,
unsigned int frames_per_period,
unsigned int frames_per_buffer)
{
unsigned int curr_rate;
int err;
// Considering JACK/FFADO streaming:
// TODO: This can be removed hwdep functionality becomes popular.
err = check_connection_used_by_others(efw, &efw->rx_stream);
if (err < 0)
return err;
// stop streams if rate is different.
err = snd_efw_command_get_sampling_rate(efw, &curr_rate);
if (err < 0)
return err;
if (rate == 0)
rate = curr_rate;
if (rate != curr_rate) {
amdtp_domain_stop(&efw->domain);
cmp_connection_break(&efw->out_conn);
cmp_connection_break(&efw->in_conn);
cmp_connection_release(&efw->out_conn);
cmp_connection_release(&efw->in_conn);
}
if (efw->substreams_counter == 0 || rate != curr_rate) {
unsigned int mode;
err = snd_efw_command_set_sampling_rate(efw, rate);
if (err < 0)
return err;
err = snd_efw_get_multiplier_mode(rate, &mode);
if (err < 0)
return err;
err = keep_resources(efw, &efw->tx_stream, rate, mode);
if (err < 0)
return err;
err = keep_resources(efw, &efw->rx_stream, rate, mode);
if (err < 0) {
cmp_connection_release(&efw->in_conn);
return err;
}
err = amdtp_domain_set_events_per_period(&efw->domain,
frames_per_period, frames_per_buffer);
if (err < 0) {
cmp_connection_release(&efw->in_conn);
cmp_connection_release(&efw->out_conn);
return err;
}
}
return 0;
}
int snd_efw_stream_start_duplex(struct snd_efw *efw)
{
unsigned int rate;
int err = 0;
// Need no substreams.
if (efw->substreams_counter == 0)
return -EIO;
if (amdtp_streaming_error(&efw->rx_stream) ||
amdtp_streaming_error(&efw->tx_stream)) {
amdtp_domain_stop(&efw->domain);
cmp_connection_break(&efw->out_conn);
cmp_connection_break(&efw->in_conn);
}
err = snd_efw_command_get_sampling_rate(efw, &rate);
if (err < 0)
return err;
if (!amdtp_stream_running(&efw->rx_stream)) {
unsigned int tx_init_skip_cycles;
// Audiofire 2/4 skip an isochronous cycle several thousands after starting
// packet transmission.
if (efw->is_fireworks3 && !efw->is_af9)
tx_init_skip_cycles = 6000;
else
tx_init_skip_cycles = 0;
err = start_stream(efw, &efw->rx_stream, rate);
if (err < 0)
goto error;
err = start_stream(efw, &efw->tx_stream, rate);
if (err < 0)
goto error;
// NOTE: The device ignores presentation time expressed by the value of syt field
// of CIP header in received packets. The sequence of the number of data blocks per
// packet is important for media clock recovery.
err = amdtp_domain_start(&efw->domain, tx_init_skip_cycles, true, false);
if (err < 0)
goto error;
if (!amdtp_domain_wait_ready(&efw->domain, READY_TIMEOUT_MS)) {
err = -ETIMEDOUT;
goto error;
}
}
return 0;
error:
amdtp_domain_stop(&efw->domain);
cmp_connection_break(&efw->out_conn);
cmp_connection_break(&efw->in_conn);
return err;
}
void snd_efw_stream_stop_duplex(struct snd_efw *efw)
{
if (efw->substreams_counter == 0) {
amdtp_domain_stop(&efw->domain);
cmp_connection_break(&efw->out_conn);
cmp_connection_break(&efw->in_conn);
cmp_connection_release(&efw->out_conn);
cmp_connection_release(&efw->in_conn);
}
}
void snd_efw_stream_update_duplex(struct snd_efw *efw)
{
amdtp_domain_stop(&efw->domain);
cmp_connection_break(&efw->out_conn);
cmp_connection_break(&efw->in_conn);
amdtp_stream_pcm_abort(&efw->rx_stream);
amdtp_stream_pcm_abort(&efw->tx_stream);
}
void snd_efw_stream_destroy_duplex(struct snd_efw *efw)
{
amdtp_domain_destroy(&efw->domain);
destroy_stream(efw, &efw->rx_stream);
destroy_stream(efw, &efw->tx_stream);
}
void snd_efw_stream_lock_changed(struct snd_efw *efw)
{
efw->dev_lock_changed = true;
wake_up(&efw->hwdep_wait);
}
int snd_efw_stream_lock_try(struct snd_efw *efw)
{
guard(spinlock_irq)(&efw->lock);
/* user land lock this */
if (efw->dev_lock_count < 0)
return -EBUSY;
/* this is the first time */
if (efw->dev_lock_count++ == 0)
snd_efw_stream_lock_changed(efw);
return 0;
}
void snd_efw_stream_lock_release(struct snd_efw *efw)
{
guard(spinlock_irq)(&efw->lock);
if (WARN_ON(efw->dev_lock_count <= 0))
return;
if (--efw->dev_lock_count == 0)
snd_efw_stream_lock_changed(efw);
}
@@ -0,0 +1,313 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* fireworks_transaction.c - a part of driver for Fireworks based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
/*
* Fireworks have its own transaction. The transaction can be delivered by AV/C
* Vendor Specific command frame or usual asynchronous transaction. At least,
* Windows driver and firmware version 5.5 or later don't use AV/C command.
*
* Transaction substance:
* At first, 6 data exist. Following to the data, parameters for each command
* exist. All of the parameters are 32 bit aligned to big endian.
* data[0]: Length of transaction substance
* data[1]: Transaction version
* data[2]: Sequence number. This is incremented by the device
* data[3]: Transaction category
* data[4]: Transaction command
* data[5]: Return value in response.
* data[6-]: Parameters
*
* Transaction address:
* command: 0xecc000000000
* response: 0xecc080000000 (default)
*
* I note that the address for response can be changed by command. But this
* module uses the default address.
*/
#include "./fireworks.h"
#define MEMORY_SPACE_EFW_COMMAND 0xecc000000000ULL
#define MEMORY_SPACE_EFW_RESPONSE 0xecc080000000ULL
#define ERROR_RETRIES 3
#define ERROR_DELAY_MS 5
#define EFC_TIMEOUT_MS 125
static DEFINE_SPINLOCK(instances_lock);
static struct snd_efw *instances[SNDRV_CARDS] = SNDRV_DEFAULT_PTR;
static DEFINE_SPINLOCK(transaction_queues_lock);
static LIST_HEAD(transaction_queues);
enum transaction_queue_state {
STATE_PENDING,
STATE_BUS_RESET,
STATE_COMPLETE
};
struct transaction_queue {
struct list_head list;
struct fw_unit *unit;
void *buf;
unsigned int size;
u32 seqnum;
enum transaction_queue_state state;
wait_queue_head_t wait;
};
int snd_efw_transaction_cmd(struct fw_unit *unit,
const void *cmd, unsigned int size)
{
return snd_fw_transaction(unit, TCODE_WRITE_BLOCK_REQUEST,
MEMORY_SPACE_EFW_COMMAND,
(void *)cmd, size, 0);
}
int snd_efw_transaction_run(struct fw_unit *unit,
const void *cmd, unsigned int cmd_size,
void *resp, unsigned int resp_size)
{
struct transaction_queue t;
unsigned int tries;
int ret;
t.unit = unit;
t.buf = resp;
t.size = resp_size;
t.seqnum = be32_to_cpu(((struct snd_efw_transaction *)cmd)->seqnum) + 1;
t.state = STATE_PENDING;
init_waitqueue_head(&t.wait);
scoped_guard(spinlock_irq, &transaction_queues_lock) {
list_add_tail(&t.list, &transaction_queues);
}
tries = 0;
do {
ret = snd_efw_transaction_cmd(t.unit, (void *)cmd, cmd_size);
if (ret < 0)
break;
wait_event_timeout(t.wait, t.state != STATE_PENDING,
msecs_to_jiffies(EFC_TIMEOUT_MS));
if (t.state == STATE_COMPLETE) {
ret = t.size;
break;
} else if (t.state == STATE_BUS_RESET) {
msleep(ERROR_DELAY_MS);
} else if (++tries >= ERROR_RETRIES) {
dev_err(&t.unit->device, "EFW transaction timed out\n");
ret = -EIO;
break;
}
} while (1);
scoped_guard(spinlock_irq, &transaction_queues_lock) {
list_del(&t.list);
}
return ret;
}
static void
copy_resp_to_buf(struct snd_efw *efw, void *data, size_t length, int *rcode)
{
size_t capacity, till_end;
struct snd_efw_transaction *t;
t = (struct snd_efw_transaction *)data;
length = min_t(size_t, be32_to_cpu(t->length) * sizeof(u32), length);
guard(spinlock)(&efw->lock);
if (efw->push_ptr < efw->pull_ptr)
capacity = (unsigned int)(efw->pull_ptr - efw->push_ptr);
else
capacity = snd_efw_resp_buf_size -
(unsigned int)(efw->push_ptr - efw->pull_ptr);
/* confirm enough space for this response */
if (capacity < length) {
*rcode = RCODE_CONFLICT_ERROR;
return;
}
/* copy to ring buffer */
while (length > 0) {
till_end = snd_efw_resp_buf_size -
(unsigned int)(efw->push_ptr - efw->resp_buf);
till_end = min_t(unsigned int, length, till_end);
memcpy(efw->push_ptr, data, till_end);
efw->push_ptr += till_end;
if (efw->push_ptr >= efw->resp_buf + snd_efw_resp_buf_size)
efw->push_ptr -= snd_efw_resp_buf_size;
length -= till_end;
data += till_end;
}
/* for hwdep */
wake_up(&efw->hwdep_wait);
*rcode = RCODE_COMPLETE;
}
static void
handle_resp_for_user(struct fw_card *card, int generation, int source,
void *data, size_t length, int *rcode)
{
struct fw_device *device;
struct snd_efw *efw;
unsigned int i;
guard(spinlock_irq)(&instances_lock);
for (i = 0; i < SNDRV_CARDS; i++) {
efw = instances[i];
if (efw == NULL)
continue;
device = fw_parent_device(efw->unit);
if ((device->card != card) ||
(device->generation != generation))
continue;
smp_rmb(); /* node id vs. generation */
if (device->node_id != source)
continue;
break;
}
if (i == SNDRV_CARDS)
return;
copy_resp_to_buf(efw, data, length, rcode);
}
static void
handle_resp_for_kernel(struct fw_card *card, int generation, int source,
void *data, size_t length, int *rcode, u32 seqnum)
{
struct fw_device *device;
struct transaction_queue *t;
guard(spinlock_irqsave)(&transaction_queues_lock);
list_for_each_entry(t, &transaction_queues, list) {
device = fw_parent_device(t->unit);
if ((device->card != card) ||
(device->generation != generation))
continue;
smp_rmb(); /* node_id vs. generation */
if (device->node_id != source)
continue;
if ((t->state == STATE_PENDING) && (t->seqnum == seqnum)) {
t->state = STATE_COMPLETE;
t->size = min_t(unsigned int, length, t->size);
memcpy(t->buf, data, t->size);
wake_up(&t->wait);
*rcode = RCODE_COMPLETE;
}
}
}
static void
efw_response(struct fw_card *card, struct fw_request *request,
int tcode, int destination, int source,
int generation, unsigned long long offset,
void *data, size_t length, void *callback_data)
{
int rcode, dummy;
u32 seqnum;
rcode = RCODE_TYPE_ERROR;
if (length < sizeof(struct snd_efw_transaction)) {
rcode = RCODE_DATA_ERROR;
goto end;
} else if (offset != MEMORY_SPACE_EFW_RESPONSE) {
rcode = RCODE_ADDRESS_ERROR;
goto end;
}
seqnum = be32_to_cpu(((struct snd_efw_transaction *)data)->seqnum);
if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX + 1) {
handle_resp_for_kernel(card, generation, source,
data, length, &rcode, seqnum);
if (snd_efw_resp_buf_debug)
handle_resp_for_user(card, generation, source,
data, length, &dummy);
} else {
handle_resp_for_user(card, generation, source,
data, length, &rcode);
}
end:
fw_send_response(card, request, rcode);
}
void snd_efw_transaction_add_instance(struct snd_efw *efw)
{
unsigned int i;
guard(spinlock_irq)(&instances_lock);
for (i = 0; i < SNDRV_CARDS; i++) {
if (instances[i] != NULL)
continue;
instances[i] = efw;
break;
}
}
void snd_efw_transaction_remove_instance(struct snd_efw *efw)
{
unsigned int i;
guard(spinlock_irq)(&instances_lock);
for (i = 0; i < SNDRV_CARDS; i++) {
if (instances[i] != efw)
continue;
instances[i] = NULL;
}
}
void snd_efw_transaction_bus_reset(struct fw_unit *unit)
{
struct transaction_queue *t;
guard(spinlock_irq)(&transaction_queues_lock);
list_for_each_entry(t, &transaction_queues, list) {
if ((t->unit == unit) &&
(t->state == STATE_PENDING)) {
t->state = STATE_BUS_RESET;
wake_up(&t->wait);
}
}
}
static struct fw_address_handler resp_register_handler = {
.length = SND_EFW_RESPONSE_MAXIMUM_BYTES,
.address_callback = efw_response
};
int snd_efw_transaction_register(void)
{
static const struct fw_address_region resp_register_region = {
.start = MEMORY_SPACE_EFW_RESPONSE,
.end = MEMORY_SPACE_EFW_RESPONSE +
SND_EFW_RESPONSE_MAXIMUM_BYTES
};
return fw_core_add_address_handler(&resp_register_handler,
&resp_register_region);
}
void snd_efw_transaction_unregister(void)
{
WARN_ON(!list_empty(&transaction_queues));
fw_core_remove_address_handler(&resp_register_handler);
}