Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

View file

@ -0,0 +1,4 @@
snd-fireworks-objs := fireworks_transaction.o fireworks_command.o \
fireworks_stream.o fireworks_proc.o fireworks_midi.o \
fireworks_pcm.o fireworks_hwdep.o fireworks.o
obj-m += snd-fireworks.o

View file

@ -0,0 +1,352 @@
/*
* fireworks.c - a part of driver for Fireworks based devices
*
* Copyright (c) 2009-2010 Clemens Ladisch
* Copyright (c) 2013-2014 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
/*
* 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 v2");
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(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;
/* 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;
strcpy(efw->card->driver, "Fireworks");
strcpy(efw->card->shortname, hwinfo->model_name);
strcpy(efw->card->mixername, 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 << 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 > AMDTP_MAX_CHANNELS_FOR_PCM ||
hwinfo->amdtp_tx_pcm_channels_2x > AMDTP_MAX_CHANNELS_FOR_PCM ||
hwinfo->amdtp_tx_pcm_channels_4x > AMDTP_MAX_CHANNELS_FOR_PCM ||
hwinfo->amdtp_rx_pcm_channels > AMDTP_MAX_CHANNELS_FOR_PCM ||
hwinfo->amdtp_rx_pcm_channels_2x > AMDTP_MAX_CHANNELS_FOR_PCM ||
hwinfo->amdtp_rx_pcm_channels_4x > AMDTP_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);
end:
kfree(hwinfo);
return err;
}
static void
efw_card_free(struct snd_card *card)
{
struct snd_efw *efw = card->private_data;
if (efw->card_index >= 0) {
mutex_lock(&devices_mutex);
clear_bit(efw->card_index, devices_used);
mutex_unlock(&devices_mutex);
}
mutex_destroy(&efw->mutex);
kfree(efw->resp_buf);
}
static int
efw_probe(struct fw_unit *unit,
const struct ieee1394_device_id *entry)
{
struct snd_card *card;
struct snd_efw *efw;
int card_index, err;
mutex_lock(&devices_mutex);
/* check registered cards */
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) {
err = -ENOENT;
goto end;
}
err = snd_card_new(&unit->device, index[card_index], id[card_index],
THIS_MODULE, sizeof(struct snd_efw), &card);
if (err < 0)
goto end;
efw = card->private_data;
efw->card_index = card_index;
set_bit(card_index, devices_used);
card->private_free = efw_card_free;
efw->card = card;
efw->unit = unit;
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 = kzalloc(snd_efw_resp_buf_size, GFP_KERNEL);
if (efw->resp_buf == NULL) {
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;
if (entry->model_id == MODEL_ECHO_AUDIOFIRE_9)
efw->is_af9 = true;
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_efw_stream_init_duplex(efw);
if (err < 0)
goto error;
err = snd_card_register(card);
if (err < 0) {
snd_efw_stream_destroy_duplex(efw);
goto error;
}
dev_set_drvdata(&unit->device, efw);
end:
mutex_unlock(&devices_mutex);
return err;
error:
snd_efw_transaction_remove_instance(efw);
mutex_unlock(&devices_mutex);
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);
snd_efw_stream_update_duplex(efw);
}
static void efw_remove(struct fw_unit *unit)
{
struct snd_efw *efw = dev_get_drvdata(&unit->device);
snd_efw_stream_destroy_duplex(efw);
snd_efw_transaction_remove_instance(efw);
snd_card_disconnect(efw->card);
snd_card_free_when_closed(efw->card);
}
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 = "snd-fireworks",
.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);

View file

@ -0,0 +1,232 @@
/*
* fireworks.h - a part of driver for Fireworks based devices
*
* Copyright (c) 2009-2010 Clemens Ladisch
* Copyright (c) 2013-2014 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#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/mod_devicetable.h>
#include <linux/delay.h>
#include <linux/slab.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.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;
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 *master;
struct amdtp_stream tx_stream;
struct amdtp_stream rx_stream;
struct cmp_connection out_conn;
struct cmp_connection in_conn;
atomic_t capture_substreams;
atomic_t playback_substreams;
/* 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;
unsigned int resp_queues;
};
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[0];
} __packed;
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,
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_start_duplex(struct snd_efw *efw, unsigned int rate);
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);
#define SND_EFW_DEV_ENTRY(vendor, model) \
{ \
.match_flags = IEEE1394_MATCH_VENDOR_ID | \
IEEE1394_MATCH_MODEL_ID, \
.vendor_id = vendor,\
.model_id = model \
}
#endif

View file

@ -0,0 +1,372 @@
/*
* fireworks_command.c - a part of driver for Fireworks based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#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 */
spin_lock(&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;
spin_unlock(&efw->lock);
/* 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)) {
dev_err(&efw->unit->device, "EFW command failed [%u/%u]: %s\n",
be32_to_cpu(header->category),
be32_to_cpu(header->command),
efr_status_names[be32_to_cpu(header->status)]);
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)
{
__be32 *buf = (__be32 *)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);
}

View file

@ -0,0 +1,298 @@
/*
* fireworks_hwdep.c - a part of driver for Fireworks based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
/*
* 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;
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;
remained -= sizeof(type);
buf += sizeof(type);
/* write into buffer as many responses as possible */
while (efw->resp_queues > 0) {
t = (struct snd_efw_transaction *)(efw->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)(efw->pull_ptr - efw->resp_buf);
till_end = min_t(unsigned int, length, till_end);
if (copy_to_user(buf, efw->pull_ptr, till_end))
return -EFAULT;
efw->pull_ptr += till_end;
if (efw->pull_ptr >= efw->resp_buf +
snd_efw_resp_buf_size)
efw->pull_ptr -= snd_efw_resp_buf_size;
length -= till_end;
buf += till_end;
count += till_end;
remained -= till_end;
}
efw->resp_queues--;
}
return count;
}
static long
hwdep_read_locked(struct snd_efw *efw, char __user *buf, long count,
loff_t *offset)
{
union snd_firewire_event event;
memset(&event, 0, sizeof(event));
event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
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);
spin_lock_irq(&efw->lock);
while ((!efw->dev_lock_changed) && (efw->resp_queues == 0)) {
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);
}
if (efw->dev_lock_changed)
count = hwdep_read_locked(efw, buf, count, offset);
else if (efw->resp_queues > 0)
count = hwdep_read_resp_buf(efw, buf, count, offset);
spin_unlock_irq(&efw->lock);
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 unsigned int
hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait)
{
struct snd_efw *efw = hwdep->private_data;
unsigned int events;
poll_wait(file, &efw->hwdep_wait, wait);
spin_lock_irq(&efw->lock);
if (efw->dev_lock_changed || (efw->resp_queues > 0))
events = POLLIN | POLLRDNORM;
else
events = 0;
spin_unlock_irq(&efw->lock);
return events | POLLOUT;
}
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]);
strlcpy(info.device_name, dev_name(&dev->device),
sizeof(info.device_name));
if (copy_to_user(arg, &info, sizeof(info)))
return -EFAULT;
return 0;
}
static int
hwdep_lock(struct snd_efw *efw)
{
int err;
spin_lock_irq(&efw->lock);
if (efw->dev_lock_count == 0) {
efw->dev_lock_count = -1;
err = 0;
} else {
err = -EBUSY;
}
spin_unlock_irq(&efw->lock);
return err;
}
static int
hwdep_unlock(struct snd_efw *efw)
{
int err;
spin_lock_irq(&efw->lock);
if (efw->dev_lock_count == -1) {
efw->dev_lock_count = 0;
err = 0;
} else {
err = -EBADFD;
}
spin_unlock_irq(&efw->lock);
return err;
}
static int
hwdep_release(struct snd_hwdep *hwdep, struct file *file)
{
struct snd_efw *efw = hwdep->private_data;
spin_lock_irq(&efw->lock);
if (efw->dev_lock_count == -1)
efw->dev_lock_count = 0;
spin_unlock_irq(&efw->lock);
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
static const struct snd_hwdep_ops hwdep_ops = {
.read = hwdep_read,
.write = hwdep_write,
.release = hwdep_release,
.poll = hwdep_poll,
.ioctl = hwdep_ioctl,
.ioctl_compat = hwdep_compat_ioctl,
};
int snd_efw_create_hwdep_device(struct snd_efw *efw)
{
struct snd_hwdep *hwdep;
int err;
err = snd_hwdep_new(efw->card, "Fireworks", 0, &hwdep);
if (err < 0)
goto end;
strcpy(hwdep->name, "Fireworks");
hwdep->iface = SNDRV_HWDEP_IFACE_FW_FIREWORKS;
hwdep->ops = hwdep_ops;
hwdep->private_data = efw;
hwdep->exclusive = true;
end:
return err;
}

View file

@ -0,0 +1,168 @@
/*
* fireworks_midi.c - a part of driver for Fireworks based devices
*
* Copyright (c) 2009-2010 Clemens Ladisch
* Copyright (c) 2013-2014 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "fireworks.h"
static int midi_capture_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)
goto end;
atomic_inc(&efw->capture_substreams);
err = snd_efw_stream_start_duplex(efw, 0);
if (err < 0)
snd_efw_stream_lock_release(efw);
end:
return err;
}
static int midi_playback_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)
goto end;
atomic_inc(&efw->playback_substreams);
err = snd_efw_stream_start_duplex(efw, 0);
if (err < 0)
snd_efw_stream_lock_release(efw);
end:
return err;
}
static int midi_capture_close(struct snd_rawmidi_substream *substream)
{
struct snd_efw *efw = substream->rmidi->private_data;
atomic_dec(&efw->capture_substreams);
snd_efw_stream_stop_duplex(efw);
snd_efw_stream_lock_release(efw);
return 0;
}
static int midi_playback_close(struct snd_rawmidi_substream *substream)
{
struct snd_efw *efw = substream->rmidi->private_data;
atomic_dec(&efw->playback_substreams);
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;
unsigned long flags;
spin_lock_irqsave(&efw->lock, flags);
if (up)
amdtp_stream_midi_trigger(&efw->tx_stream,
substrm->number, substrm);
else
amdtp_stream_midi_trigger(&efw->tx_stream,
substrm->number, NULL);
spin_unlock_irqrestore(&efw->lock, flags);
}
static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up)
{
struct snd_efw *efw = substrm->rmidi->private_data;
unsigned long flags;
spin_lock_irqsave(&efw->lock, flags);
if (up)
amdtp_stream_midi_trigger(&efw->rx_stream,
substrm->number, substrm);
else
amdtp_stream_midi_trigger(&efw->rx_stream,
substrm->number, NULL);
spin_unlock_irqrestore(&efw->lock, flags);
}
static struct snd_rawmidi_ops midi_capture_ops = {
.open = midi_capture_open,
.close = midi_capture_close,
.trigger = midi_capture_trigger,
};
static struct snd_rawmidi_ops midi_playback_ops = {
.open = midi_playback_open,
.close = midi_playback_close,
.trigger = midi_playback_trigger,
};
static void set_midi_substream_names(struct snd_efw *efw,
struct snd_rawmidi_str *str)
{
struct snd_rawmidi_substream *subs;
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;
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,
&midi_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,
&midi_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;
}

View file

@ -0,0 +1,403 @@
/*
* fireworks_pcm.c - a part of driver for Fireworks based devices
*
* Copyright (c) 2009-2010 Clemens Ladisch
* Copyright (c) 2013-2014 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#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 void
limit_period_and_buffer(struct snd_pcm_hardware *hw)
{
hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */
hw->periods_max = UINT_MAX;
hw->period_bytes_min = 4 * hw->channels_max; /* bytes for a frame */
/* Just to prevent from allocating much pages. */
hw->period_bytes_max = hw->period_bytes_min * 2048;
hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min;
}
static int
pcm_init_hw_params(struct snd_efw *efw,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct amdtp_stream *s;
unsigned int *pcm_channels;
int err;
runtime->hw.info = SNDRV_PCM_INFO_BATCH |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_JOINT_DUPLEX |
SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
runtime->hw.formats = AMDTP_IN_PCM_FORMAT_BITS;
s = &efw->tx_stream;
pcm_channels = efw->pcm_capture_channels;
} else {
runtime->hw.formats = AMDTP_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);
limit_period_and_buffer(&runtime->hw);
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_stream_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;
unsigned int sampling_rate;
enum snd_efw_clock_source clock_source;
int err;
err = snd_efw_stream_lock_try(efw);
if (err < 0)
goto end;
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;
/*
* When source of clock is not internal or any PCM streams are running,
* available sampling rate is limited at current sampling rate.
*/
if ((clock_source != SND_EFW_CLOCK_SOURCE_INTERNAL) ||
amdtp_stream_pcm_running(&efw->tx_stream) ||
amdtp_stream_pcm_running(&efw->rx_stream)) {
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;
}
snd_pcm_set_sync(substream);
end:
return err;
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_capture_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_efw *efw = substream->private_data;
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN)
atomic_inc(&efw->capture_substreams);
amdtp_stream_set_pcm_format(&efw->tx_stream, params_format(hw_params));
return snd_pcm_lib_alloc_vmalloc_buffer(substream,
params_buffer_bytes(hw_params));
}
static int pcm_playback_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_efw *efw = substream->private_data;
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN)
atomic_inc(&efw->playback_substreams);
amdtp_stream_set_pcm_format(&efw->rx_stream, params_format(hw_params));
return snd_pcm_lib_alloc_vmalloc_buffer(substream,
params_buffer_bytes(hw_params));
}
static int pcm_capture_hw_free(struct snd_pcm_substream *substream)
{
struct snd_efw *efw = substream->private_data;
if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
atomic_dec(&efw->capture_substreams);
snd_efw_stream_stop_duplex(efw);
return snd_pcm_lib_free_vmalloc_buffer(substream);
}
static int pcm_playback_hw_free(struct snd_pcm_substream *substream)
{
struct snd_efw *efw = substream->private_data;
if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
atomic_dec(&efw->playback_substreams);
snd_efw_stream_stop_duplex(efw);
return snd_pcm_lib_free_vmalloc_buffer(substream);
}
static int pcm_capture_prepare(struct snd_pcm_substream *substream)
{
struct snd_efw *efw = substream->private_data;
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
err = snd_efw_stream_start_duplex(efw, runtime->rate);
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;
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
err = snd_efw_stream_start_duplex(efw, runtime->rate);
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_stream_pcm_pointer(&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_stream_pcm_pointer(&efw->rx_stream);
}
static const struct snd_pcm_ops pcm_capture_ops = {
.open = pcm_open,
.close = pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = pcm_capture_hw_params,
.hw_free = pcm_capture_hw_free,
.prepare = pcm_capture_prepare,
.trigger = pcm_capture_trigger,
.pointer = pcm_capture_pointer,
.page = snd_pcm_lib_get_vmalloc_page,
};
static const struct snd_pcm_ops pcm_playback_ops = {
.open = pcm_open,
.close = pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = pcm_playback_hw_params,
.hw_free = pcm_playback_hw_free,
.prepare = pcm_playback_prepare,
.trigger = pcm_playback_trigger,
.pointer = pcm_playback_pointer,
.page = snd_pcm_lib_get_vmalloc_page,
.mmap = snd_pcm_lib_mmap_vmalloc,
};
int snd_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);
end:
return err;
}

View file

@ -0,0 +1,232 @@
/*
* fireworks_proc.c - a part of driver for Fireworks based devices
*
* Copyright (c) 2009-2010 Clemens Ladisch
* Copyright (c) 2013-2014 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "./fireworks.h"
static inline const char*
get_phys_name(struct snd_efw_phys_grp *grp, bool input)
{
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(sizeof(struct snd_efw_hwinfo), GFP_KERNEL);
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/%d\n",
efw->resp_queues, 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 == NULL)
return;
snd_info_set_text_ops(entry, efw, op);
if (snd_info_register(entry) < 0)
snd_info_free_entry(entry);
}
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 | S_IRUGO | S_IXUGO;
if (snd_info_register(root) < 0) {
snd_info_free_entry(root);
return;
}
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);
}

View file

@ -0,0 +1,372 @@
/*
* fireworks_stream.c - a part of driver for Fireworks based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "./fireworks.h"
#define CALLBACK_TIMEOUT 100
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)
goto end;
err = amdtp_stream_init(stream, efw->unit, s_dir, CIP_BLOCKING);
if (err < 0) {
amdtp_stream_destroy(stream);
cmp_connection_destroy(conn);
}
end:
return err;
}
static void
stop_stream(struct snd_efw *efw, struct amdtp_stream *stream)
{
amdtp_stream_pcm_abort(stream);
amdtp_stream_stop(stream);
if (stream == &efw->tx_stream)
cmp_connection_break(&efw->out_conn);
else
cmp_connection_break(&efw->in_conn);
}
static int
start_stream(struct snd_efw *efw, struct amdtp_stream *stream,
unsigned int sampling_rate)
{
struct cmp_connection *conn;
unsigned int mode, pcm_channels, midi_ports;
int err;
err = snd_efw_get_multiplier_mode(sampling_rate, &mode);
if (err < 0)
goto end;
if (stream == &efw->tx_stream) {
conn = &efw->out_conn;
pcm_channels = efw->pcm_capture_channels[mode];
midi_ports = efw->midi_out_ports;
} else {
conn = &efw->in_conn;
pcm_channels = efw->pcm_playback_channels[mode];
midi_ports = efw->midi_in_ports;
}
amdtp_stream_set_parameters(stream, sampling_rate,
pcm_channels, midi_ports);
/* establish connection via CMP */
err = cmp_connection_establish(conn,
amdtp_stream_get_max_payload(stream));
if (err < 0)
goto end;
/* start amdtp stream */
err = amdtp_stream_start(stream,
conn->resources.channel,
conn->speed);
if (err < 0) {
stop_stream(efw, stream);
goto end;
}
/* wait first callback */
if (!amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT)) {
stop_stream(efw, stream);
err = -ETIMEDOUT;
}
end:
return err;
}
static void
destroy_stream(struct snd_efw *efw, struct amdtp_stream *stream)
{
stop_stream(efw, 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
get_sync_mode(struct snd_efw *efw, enum cip_flags *sync_mode)
{
enum snd_efw_clock_source clock_source;
int err;
err = snd_efw_command_get_clock_source(efw, &clock_source);
if (err < 0)
return err;
if (clock_source == SND_EFW_CLOCK_SOURCE_SYTMATCH)
return -ENOSYS;
*sync_mode = CIP_SYNC_TO_DEVICE;
return 0;
}
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)
goto end;
/* 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;
/* AudioFire9 always reports wrong dbs. */
if (efw->is_af9)
efw->tx_stream.flags |= CIP_WRONG_DBS;
/* Firmware version 5.5 reports fixed interval for dbc. */
if (efw->firmware_version == 0x5050000)
efw->tx_stream.tx_dbc_interval = 8;
err = init_stream(efw, &efw->rx_stream);
if (err < 0) {
destroy_stream(efw, &efw->tx_stream);
goto end;
}
/*
* Fireworks ignores MIDI messages in more than first 8 data
* blocks of an received AMDTP packet.
*/
efw->rx_stream.rx_blocks_for_midi = 8;
/* 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);
}
end:
return err;
}
int snd_efw_stream_start_duplex(struct snd_efw *efw, unsigned int rate)
{
struct amdtp_stream *master, *slave;
atomic_t *slave_substreams;
enum cip_flags sync_mode;
unsigned int curr_rate;
int err = 0;
mutex_lock(&efw->mutex);
/* Need no substreams */
if ((atomic_read(&efw->playback_substreams) == 0) &&
(atomic_read(&efw->capture_substreams) == 0))
goto end;
err = get_sync_mode(efw, &sync_mode);
if (err < 0)
goto end;
if (sync_mode == CIP_SYNC_TO_DEVICE) {
master = &efw->tx_stream;
slave = &efw->rx_stream;
slave_substreams = &efw->playback_substreams;
} else {
master = &efw->rx_stream;
slave = &efw->tx_stream;
slave_substreams = &efw->capture_substreams;
}
/*
* Considering JACK/FFADO streaming:
* TODO: This can be removed hwdep functionality becomes popular.
*/
err = check_connection_used_by_others(efw, master);
if (err < 0)
goto end;
/* packet queueing error */
if (amdtp_streaming_error(slave))
stop_stream(efw, slave);
if (amdtp_streaming_error(master))
stop_stream(efw, master);
/* stop streams if rate is different */
err = snd_efw_command_get_sampling_rate(efw, &curr_rate);
if (err < 0)
goto end;
if (rate == 0)
rate = curr_rate;
if (rate != curr_rate) {
stop_stream(efw, slave);
stop_stream(efw, master);
}
/* master should be always running */
if (!amdtp_stream_running(master)) {
amdtp_stream_set_sync(sync_mode, master, slave);
efw->master = master;
err = snd_efw_command_set_sampling_rate(efw, rate);
if (err < 0)
goto end;
err = start_stream(efw, master, rate);
if (err < 0) {
dev_err(&efw->unit->device,
"fail to start AMDTP master stream:%d\n", err);
goto end;
}
}
/* start slave if needed */
if (atomic_read(slave_substreams) > 0 && !amdtp_stream_running(slave)) {
err = start_stream(efw, slave, rate);
if (err < 0) {
dev_err(&efw->unit->device,
"fail to start AMDTP slave stream:%d\n", err);
stop_stream(efw, master);
}
}
end:
mutex_unlock(&efw->mutex);
return err;
}
void snd_efw_stream_stop_duplex(struct snd_efw *efw)
{
struct amdtp_stream *master, *slave;
atomic_t *master_substreams, *slave_substreams;
if (efw->master == &efw->rx_stream) {
slave = &efw->tx_stream;
master = &efw->rx_stream;
slave_substreams = &efw->capture_substreams;
master_substreams = &efw->playback_substreams;
} else {
slave = &efw->rx_stream;
master = &efw->tx_stream;
slave_substreams = &efw->playback_substreams;
master_substreams = &efw->capture_substreams;
}
mutex_lock(&efw->mutex);
if (atomic_read(slave_substreams) == 0) {
stop_stream(efw, slave);
if (atomic_read(master_substreams) == 0)
stop_stream(efw, master);
}
mutex_unlock(&efw->mutex);
}
void snd_efw_stream_update_duplex(struct snd_efw *efw)
{
if ((cmp_connection_update(&efw->out_conn) < 0) ||
(cmp_connection_update(&efw->in_conn) < 0)) {
mutex_lock(&efw->mutex);
stop_stream(efw, &efw->rx_stream);
stop_stream(efw, &efw->tx_stream);
mutex_unlock(&efw->mutex);
} else {
amdtp_stream_update(&efw->rx_stream);
amdtp_stream_update(&efw->tx_stream);
}
}
void snd_efw_stream_destroy_duplex(struct snd_efw *efw)
{
mutex_lock(&efw->mutex);
destroy_stream(efw, &efw->rx_stream);
destroy_stream(efw, &efw->tx_stream);
mutex_unlock(&efw->mutex);
}
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)
{
int err;
spin_lock_irq(&efw->lock);
/* user land lock this */
if (efw->dev_lock_count < 0) {
err = -EBUSY;
goto end;
}
/* this is the first time */
if (efw->dev_lock_count++ == 0)
snd_efw_stream_lock_changed(efw);
err = 0;
end:
spin_unlock_irq(&efw->lock);
return err;
}
void snd_efw_stream_lock_release(struct snd_efw *efw)
{
spin_lock_irq(&efw->lock);
if (WARN_ON(efw->dev_lock_count <= 0))
goto end;
if (--efw->dev_lock_count == 0)
snd_efw_stream_lock_changed(efw);
end:
spin_unlock_irq(&efw->lock);
}

View file

@ -0,0 +1,326 @@
/*
* fireworks_transaction.c - a part of driver for Fireworks based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
/*
* 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 alighed 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);
spin_lock_irq(&transaction_queues_lock);
list_add_tail(&t.list, &transaction_queues);
spin_unlock_irq(&transaction_queues_lock);
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);
spin_lock_irq(&transaction_queues_lock);
list_del(&t.list);
spin_unlock_irq(&transaction_queues_lock);
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;
spin_lock_irq(&efw->lock);
t = (struct snd_efw_transaction *)data;
length = min_t(size_t, be32_to_cpu(t->length) * sizeof(u32), length);
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;
goto end;
}
/* 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 */
efw->resp_queues++;
wake_up(&efw->hwdep_wait);
*rcode = RCODE_COMPLETE;
end:
spin_unlock_irq(&efw->lock);
}
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;
spin_lock_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)
goto end;
copy_resp_to_buf(efw, data, length, rcode);
end:
spin_unlock_irq(&instances_lock);
}
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;
unsigned long flags;
spin_lock_irqsave(&transaction_queues_lock, flags);
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;
}
}
spin_unlock_irqrestore(&transaction_queues_lock, flags);
}
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;
spin_lock_irq(&instances_lock);
for (i = 0; i < SNDRV_CARDS; i++) {
if (instances[i] != NULL)
continue;
instances[i] = efw;
break;
}
spin_unlock_irq(&instances_lock);
}
void snd_efw_transaction_remove_instance(struct snd_efw *efw)
{
unsigned int i;
spin_lock_irq(&instances_lock);
for (i = 0; i < SNDRV_CARDS; i++) {
if (instances[i] != efw)
continue;
instances[i] = NULL;
}
spin_unlock_irq(&instances_lock);
}
void snd_efw_transaction_bus_reset(struct fw_unit *unit)
{
struct transaction_queue *t;
spin_lock_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);
}
}
spin_unlock_irq(&transaction_queues_lock);
}
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);
}