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

115
sound/firewire/Kconfig Normal file
View file

@ -0,0 +1,115 @@
menuconfig SND_FIREWIRE
bool "FireWire sound devices"
depends on FIREWIRE
default y
help
Support for IEEE-1394/FireWire/iLink sound devices.
if SND_FIREWIRE && FIREWIRE
config SND_FIREWIRE_LIB
tristate
select SND_PCM
select SND_RAWMIDI
config SND_DICE
tristate "DICE-based DACs (EXPERIMENTAL)"
select SND_HWDEP
select SND_FIREWIRE_LIB
help
Say Y here to include support for many DACs based on the DICE
chip family (DICE-II/Jr/Mini) from TC Applied Technologies.
At the moment, this driver supports playback only. If you
want to use devices that support capturing, use FFADO instead.
To compile this driver as a module, choose M here: the module
will be called snd-dice.
config SND_FIREWIRE_SPEAKERS
tristate "FireWire speakers"
select SND_FIREWIRE_LIB
help
Say Y here to include support for the Griffin FireWave Surround
and the LaCie FireWire Speakers.
To compile this driver as a module, choose M here: the module
will be called snd-firewire-speakers.
config SND_ISIGHT
tristate "Apple iSight microphone"
select SND_FIREWIRE_LIB
help
Say Y here to include support for the front and rear microphones
of the Apple iSight web camera.
To compile this driver as a module, choose M here: the module
will be called snd-isight.
config SND_SCS1X
tristate "Stanton Control System 1 MIDI"
select SND_FIREWIRE_LIB
help
Say Y here to include support for the MIDI ports of the Stanton
SCS.1d/SCS.1m DJ controllers. (SCS.1m audio is still handled
by FFADO.)
To compile this driver as a module, choose M here: the module
will be called snd-scs1x.
config SND_FIREWORKS
tristate "Echo Fireworks board module support"
select SND_FIREWIRE_LIB
select SND_HWDEP
help
Say Y here to include support for FireWire devices based
on Echo Digital Audio Fireworks board:
* Mackie Onyx 400F/1200F
* Echo AudioFire12/8(until 2009 July)
* Echo AudioFire2/4/Pre8/8(since 2009 July)
* Echo Fireworks 8/HDMI
* Gibson Robot Interface Pack/GoldTop
To compile this driver as a module, choose M here: the module
will be called snd-fireworks.
config SND_BEBOB
tristate "BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware"
select SND_FIREWIRE_LIB
select SND_HWDEP
help
Say Y here to include support for FireWire devices based
on BridgeCo DM1000/DM1100/DM1500 with BeBoB firmware:
* Edirol FA-66/FA-101
* PreSonus FIREBOX/FIREPOD/FP10/Inspire1394
* BridgeCo RDAudio1/Audio5
* Mackie Onyx 1220/1620/1640 (FireWire I/O Card)
* Mackie d.2 (FireWire Option)
* Stanton FinalScratch 2 (ScratchAmp)
* Tascam IF-FW/DM
* Behringer XENIX UFX 1204/1604
* Behringer Digital Mixer X32 series (X-UF Card)
* Apogee Rosetta 200/400 (X-FireWire card)
* Apogee DA/AD/DD-16X (X-FireWire card)
* Apogee Ensemble
* ESI QuataFire 610
* AcousticReality eARMasterOne
* CME MatrixKFW
* Phonic Helix Board 12 MkII/18 MkII/24 MkII
* Phonic Helix Board 12 Universal/18 Universal/24 Universal
* Lynx Aurora 8/16 (LT-FW)
* ICON FireXon
* PrismSound Orpheus/ADA-8XR
* TerraTec PHASE 24 FW/PHASE X24 FW/PHASE 88 Rack FW
* TerraTec EWS MIC2/EWS MIC8
* TerraTec Aureon 7.1 FireWire
* Yamaha GO44/GO46
* Focusrite Saffire/Saffire LE/SaffirePro10 IO/SaffirePro26 IO
* M-Audio FireWire410/AudioPhile/Solo
* M-Audio Ozonic/NRV10/ProfireLightBridge
* M-Audio FireWire 1814/ProjectMix IO
To compile this driver as a module, choose M here: the module
will be called snd-bebob.
endif # SND_FIREWIRE

14
sound/firewire/Makefile Normal file
View file

@ -0,0 +1,14 @@
snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \
fcp.o cmp.o amdtp.o
snd-dice-objs := dice.o
snd-firewire-speakers-objs := speakers.o
snd-isight-objs := isight.o
snd-scs1x-objs := scs1x.o
obj-$(CONFIG_SND_FIREWIRE_LIB) += snd-firewire-lib.o
obj-$(CONFIG_SND_DICE) += snd-dice.o
obj-$(CONFIG_SND_FIREWIRE_SPEAKERS) += snd-firewire-speakers.o
obj-$(CONFIG_SND_ISIGHT) += snd-isight.o
obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o
obj-$(CONFIG_SND_FIREWORKS) += fireworks/
obj-$(CONFIG_SND_BEBOB) += bebob/

1016
sound/firewire/amdtp.c Normal file

File diff suppressed because it is too large Load diff

273
sound/firewire/amdtp.h Normal file
View file

@ -0,0 +1,273 @@
#ifndef SOUND_FIREWIRE_AMDTP_H_INCLUDED
#define SOUND_FIREWIRE_AMDTP_H_INCLUDED
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <sound/asound.h>
#include "packets-buffer.h"
/**
* enum cip_flags - describes details of the streaming protocol
* @CIP_NONBLOCKING: In non-blocking mode, each packet contains
* sample_rate/8000 samples, with rounding up or down to adjust
* for clock skew and left-over fractional samples. This should
* be used if supported by the device.
* @CIP_BLOCKING: In blocking mode, each packet contains either zero or
* SYT_INTERVAL samples, with these two types alternating so that
* the overall sample rate comes out right.
* @CIP_SYNC_TO_DEVICE: In sync to device mode, time stamp in out packets is
* generated by in packets. Defaultly this driver generates timestamp.
* @CIP_EMPTY_WITH_TAG0: Only for in-stream. Empty in-packets have TAG0.
* @CIP_DBC_IS_END_EVENT: Only for in-stream. The value of dbc in an in-packet
* corresponds to the end of event in the packet. Out of IEC 61883.
* @CIP_WRONG_DBS: Only for in-stream. The value of dbs is wrong in in-packets.
* The value of data_block_quadlets is used instead of reported value.
* @SKIP_DBC_ZERO_CHECK: Only for in-stream. Packets with zero in dbc is
* skipped for detecting discontinuity.
* @CIP_SKIP_INIT_DBC_CHECK: Only for in-stream. The value of dbc in first
* packet is not continuous from an initial value.
* @CIP_EMPTY_HAS_WRONG_DBC: Only for in-stream. The value of dbc in empty
* packet is wrong but the others are correct.
*/
enum cip_flags {
CIP_NONBLOCKING = 0x00,
CIP_BLOCKING = 0x01,
CIP_SYNC_TO_DEVICE = 0x02,
CIP_EMPTY_WITH_TAG0 = 0x04,
CIP_DBC_IS_END_EVENT = 0x08,
CIP_WRONG_DBS = 0x10,
CIP_SKIP_DBC_ZERO_CHECK = 0x20,
CIP_SKIP_INIT_DBC_CHECK = 0x40,
CIP_EMPTY_HAS_WRONG_DBC = 0x80,
};
/**
* enum cip_sfc - a stream's sample rate
*/
enum cip_sfc {
CIP_SFC_32000 = 0,
CIP_SFC_44100 = 1,
CIP_SFC_48000 = 2,
CIP_SFC_88200 = 3,
CIP_SFC_96000 = 4,
CIP_SFC_176400 = 5,
CIP_SFC_192000 = 6,
CIP_SFC_COUNT
};
#define AMDTP_IN_PCM_FORMAT_BITS SNDRV_PCM_FMTBIT_S32
#define AMDTP_OUT_PCM_FORMAT_BITS (SNDRV_PCM_FMTBIT_S16 | \
SNDRV_PCM_FMTBIT_S32)
/*
* This module supports maximum 64 PCM channels for one PCM stream
* This is for our convenience.
*/
#define AMDTP_MAX_CHANNELS_FOR_PCM 64
/*
* AMDTP packet can include channels for MIDI conformant data.
* Each MIDI conformant data channel includes 8 MPX-MIDI data stream.
* Each MPX-MIDI data stream includes one data stream from/to MIDI ports.
*
* This module supports maximum 1 MIDI conformant data channels.
* Then this AMDTP packets can transfer maximum 8 MIDI data streams.
*/
#define AMDTP_MAX_CHANNELS_FOR_MIDI 1
struct fw_unit;
struct fw_iso_context;
struct snd_pcm_substream;
struct snd_pcm_runtime;
struct snd_rawmidi_substream;
enum amdtp_stream_direction {
AMDTP_OUT_STREAM = 0,
AMDTP_IN_STREAM
};
struct amdtp_stream {
struct fw_unit *unit;
enum cip_flags flags;
enum amdtp_stream_direction direction;
struct fw_iso_context *context;
struct mutex mutex;
enum cip_sfc sfc;
unsigned int data_block_quadlets;
unsigned int pcm_channels;
unsigned int midi_ports;
void (*transfer_samples)(struct amdtp_stream *s,
struct snd_pcm_substream *pcm,
__be32 *buffer, unsigned int frames);
u8 pcm_positions[AMDTP_MAX_CHANNELS_FOR_PCM];
u8 midi_position;
unsigned int syt_interval;
unsigned int transfer_delay;
unsigned int source_node_id_field;
struct iso_packets_buffer buffer;
struct snd_pcm_substream *pcm;
struct tasklet_struct period_tasklet;
int packet_index;
unsigned int data_block_counter;
unsigned int data_block_state;
unsigned int last_syt_offset;
unsigned int syt_offset_state;
unsigned int pcm_buffer_pointer;
unsigned int pcm_period_pointer;
bool pointer_flush;
bool double_pcm_frames;
struct snd_rawmidi_substream *midi[AMDTP_MAX_CHANNELS_FOR_MIDI * 8];
/* quirk: fixed interval of dbc between previos/current packets. */
unsigned int tx_dbc_interval;
/* quirk: the first count of data blocks in an rx packet for MIDI */
unsigned int rx_blocks_for_midi;
bool callbacked;
wait_queue_head_t callback_wait;
struct amdtp_stream *sync_slave;
};
int amdtp_stream_init(struct amdtp_stream *s, struct fw_unit *unit,
enum amdtp_stream_direction dir,
enum cip_flags flags);
void amdtp_stream_destroy(struct amdtp_stream *s);
void amdtp_stream_set_parameters(struct amdtp_stream *s,
unsigned int rate,
unsigned int pcm_channels,
unsigned int midi_ports);
unsigned int amdtp_stream_get_max_payload(struct amdtp_stream *s);
int amdtp_stream_start(struct amdtp_stream *s, int channel, int speed);
void amdtp_stream_update(struct amdtp_stream *s);
void amdtp_stream_stop(struct amdtp_stream *s);
int amdtp_stream_add_pcm_hw_constraints(struct amdtp_stream *s,
struct snd_pcm_runtime *runtime);
void amdtp_stream_set_pcm_format(struct amdtp_stream *s,
snd_pcm_format_t format);
void amdtp_stream_pcm_prepare(struct amdtp_stream *s);
unsigned long amdtp_stream_pcm_pointer(struct amdtp_stream *s);
void amdtp_stream_pcm_abort(struct amdtp_stream *s);
extern const unsigned int amdtp_syt_intervals[CIP_SFC_COUNT];
extern const unsigned int amdtp_rate_table[CIP_SFC_COUNT];
/**
* amdtp_stream_running - check stream is running or not
* @s: the AMDTP stream
*
* If this function returns true, the stream is running.
*/
static inline bool amdtp_stream_running(struct amdtp_stream *s)
{
return !IS_ERR(s->context);
}
/**
* amdtp_streaming_error - check for streaming error
* @s: the AMDTP stream
*
* If this function returns true, the stream's packet queue has stopped due to
* an asynchronous error.
*/
static inline bool amdtp_streaming_error(struct amdtp_stream *s)
{
return s->packet_index < 0;
}
/**
* amdtp_stream_pcm_running - check PCM substream is running or not
* @s: the AMDTP stream
*
* If this function returns true, PCM substream in the AMDTP stream is running.
*/
static inline bool amdtp_stream_pcm_running(struct amdtp_stream *s)
{
return !!s->pcm;
}
/**
* amdtp_stream_pcm_trigger - start/stop playback from a PCM device
* @s: the AMDTP stream
* @pcm: the PCM device to be started, or %NULL to stop the current device
*
* Call this function on a running isochronous stream to enable the actual
* transmission of PCM data. This function should be called from the PCM
* device's .trigger callback.
*/
static inline void amdtp_stream_pcm_trigger(struct amdtp_stream *s,
struct snd_pcm_substream *pcm)
{
ACCESS_ONCE(s->pcm) = pcm;
}
/**
* amdtp_stream_midi_trigger - start/stop playback/capture with a MIDI device
* @s: the AMDTP stream
* @port: index of MIDI port
* @midi: the MIDI device to be started, or %NULL to stop the current device
*
* Call this function on a running isochronous stream to enable the actual
* transmission of MIDI data. This function should be called from the MIDI
* device's .trigger callback.
*/
static inline void amdtp_stream_midi_trigger(struct amdtp_stream *s,
unsigned int port,
struct snd_rawmidi_substream *midi)
{
if (port < s->midi_ports)
ACCESS_ONCE(s->midi[port]) = midi;
}
static inline bool cip_sfc_is_base_44100(enum cip_sfc sfc)
{
return sfc & 1;
}
static inline void amdtp_stream_set_sync(enum cip_flags sync_mode,
struct amdtp_stream *master,
struct amdtp_stream *slave)
{
if (sync_mode == CIP_SYNC_TO_DEVICE) {
master->flags |= CIP_SYNC_TO_DEVICE;
slave->flags |= CIP_SYNC_TO_DEVICE;
master->sync_slave = slave;
} else {
master->flags &= ~CIP_SYNC_TO_DEVICE;
slave->flags &= ~CIP_SYNC_TO_DEVICE;
master->sync_slave = NULL;
}
slave->sync_slave = NULL;
}
/**
* amdtp_stream_wait_callback - sleep till callbacked or timeout
* @s: the AMDTP stream
* @timeout: msec till timeout
*
* If this function return false, the AMDTP stream should be stopped.
*/
static inline bool amdtp_stream_wait_callback(struct amdtp_stream *s,
unsigned int timeout)
{
return wait_event_timeout(s->callback_wait,
s->callbacked == true,
msecs_to_jiffies(timeout)) > 0;
}
#endif

View file

@ -0,0 +1,4 @@
snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob_midi.o \
bebob_pcm.o bebob_hwdep.o bebob_terratec.o bebob_yamaha.o \
bebob_focusrite.o bebob_maudio.o bebob.o
obj-m += snd-bebob.o

View file

@ -0,0 +1,471 @@
/*
* bebob.c - a part of driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
/*
* BeBoB is 'BridgeCo enhanced Breakout Box'. This is installed to firewire
* devices with DM1000/DM1100/DM1500 chipset. It gives common way for host
* system to handle BeBoB based devices.
*/
#include "bebob.h"
MODULE_DESCRIPTION("BridgeCo BeBoB 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;
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 BeBoB sound card");
static DEFINE_MUTEX(devices_mutex);
static DECLARE_BITMAP(devices_used, SNDRV_CARDS);
/* Offsets from information register. */
#define INFO_OFFSET_GUID 0x10
#define INFO_OFFSET_HW_MODEL_ID 0x18
#define INFO_OFFSET_HW_MODEL_REVISION 0x1c
#define VEN_EDIROL 0x000040ab
#define VEN_PRESONUS 0x00000a92
#define VEN_BRIDGECO 0x000007f5
#define VEN_MACKIE 0x0000000f
#define VEN_STANTON 0x00001260
#define VEN_TASCAM 0x0000022e
#define VEN_BEHRINGER 0x00001564
#define VEN_APOGEE 0x000003db
#define VEN_ESI 0x00000f1b
#define VEN_ACOUSTIC 0x00000002
#define VEN_CME 0x0000000a
#define VEN_PHONIC 0x00001496
#define VEN_LYNX 0x000019e5
#define VEN_ICON 0x00001a9e
#define VEN_PRISMSOUND 0x00001198
#define VEN_TERRATEC 0x00000aac
#define VEN_YAMAHA 0x0000a0de
#define VEN_FOCUSRITE 0x0000130e
#define VEN_MAUDIO1 0x00000d6c
#define VEN_MAUDIO2 0x000007f5
#define MODEL_FOCUSRITE_SAFFIRE_BOTH 0x00000000
#define MODEL_MAUDIO_AUDIOPHILE_BOTH 0x00010060
#define MODEL_MAUDIO_FW1814 0x00010071
#define MODEL_MAUDIO_PROJECTMIX 0x00010091
static int
name_device(struct snd_bebob *bebob, unsigned int vendor_id)
{
struct fw_device *fw_dev = fw_parent_device(bebob->unit);
char vendor[24] = {0};
char model[32] = {0};
u32 hw_id;
u32 data[2] = {0};
u32 revision;
int err;
/* get vendor name from root directory */
err = fw_csr_string(fw_dev->config_rom + 5, CSR_VENDOR,
vendor, sizeof(vendor));
if (err < 0)
goto end;
/* get model name from unit directory */
err = fw_csr_string(bebob->unit->directory, CSR_MODEL,
model, sizeof(model));
if (err < 0)
goto end;
/* get hardware id */
err = snd_bebob_read_quad(bebob->unit, INFO_OFFSET_HW_MODEL_ID,
&hw_id);
if (err < 0)
goto end;
/* get hardware revision */
err = snd_bebob_read_quad(bebob->unit, INFO_OFFSET_HW_MODEL_REVISION,
&revision);
if (err < 0)
goto end;
/* get GUID */
err = snd_bebob_read_block(bebob->unit, INFO_OFFSET_GUID,
data, sizeof(data));
if (err < 0)
goto end;
strcpy(bebob->card->driver, "BeBoB");
strcpy(bebob->card->shortname, model);
strcpy(bebob->card->mixername, model);
snprintf(bebob->card->longname, sizeof(bebob->card->longname),
"%s %s (id:%d, rev:%d), GUID %08x%08x at %s, S%d",
vendor, model, hw_id, revision,
data[0], data[1], dev_name(&bebob->unit->device),
100 << fw_dev->max_speed);
end:
return err;
}
static void
bebob_card_free(struct snd_card *card)
{
struct snd_bebob *bebob = card->private_data;
if (bebob->card_index >= 0) {
mutex_lock(&devices_mutex);
clear_bit(bebob->card_index, devices_used);
mutex_unlock(&devices_mutex);
}
mutex_destroy(&bebob->mutex);
}
static const struct snd_bebob_spec *
get_saffire_spec(struct fw_unit *unit)
{
char name[24] = {0};
if (fw_csr_string(unit->directory, CSR_MODEL, name, sizeof(name)) < 0)
return NULL;
if (strcmp(name, "SaffireLE") == 0)
return &saffire_le_spec;
else
return &saffire_spec;
}
static bool
check_audiophile_booted(struct fw_unit *unit)
{
char name[24] = {0};
if (fw_csr_string(unit->directory, CSR_MODEL, name, sizeof(name)) < 0)
return false;
return strncmp(name, "FW Audiophile Bootloader", 15) != 0;
}
static int
bebob_probe(struct fw_unit *unit,
const struct ieee1394_device_id *entry)
{
struct snd_card *card;
struct snd_bebob *bebob;
const struct snd_bebob_spec *spec;
unsigned int card_index;
int err;
mutex_lock(&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) {
err = -ENOENT;
goto end;
}
if ((entry->vendor_id == VEN_FOCUSRITE) &&
(entry->model_id == MODEL_FOCUSRITE_SAFFIRE_BOTH))
spec = get_saffire_spec(unit);
else if ((entry->vendor_id == VEN_MAUDIO1) &&
(entry->model_id == MODEL_MAUDIO_AUDIOPHILE_BOTH) &&
!check_audiophile_booted(unit))
spec = NULL;
else
spec = (const struct snd_bebob_spec *)entry->driver_data;
if (spec == NULL) {
if ((entry->vendor_id == VEN_MAUDIO1) ||
(entry->vendor_id == VEN_MAUDIO2))
err = snd_bebob_maudio_load_firmware(unit);
else
err = -ENOSYS;
goto end;
}
err = snd_card_new(&unit->device, index[card_index], id[card_index],
THIS_MODULE, sizeof(struct snd_bebob), &card);
if (err < 0)
goto end;
bebob = card->private_data;
bebob->card_index = card_index;
set_bit(card_index, devices_used);
card->private_free = bebob_card_free;
bebob->card = card;
bebob->unit = unit;
bebob->spec = spec;
mutex_init(&bebob->mutex);
spin_lock_init(&bebob->lock);
init_waitqueue_head(&bebob->hwdep_wait);
err = name_device(bebob, entry->vendor_id);
if (err < 0)
goto error;
if ((entry->vendor_id == VEN_MAUDIO1) &&
(entry->model_id == MODEL_MAUDIO_FW1814))
err = snd_bebob_maudio_special_discover(bebob, true);
else if ((entry->vendor_id == VEN_MAUDIO1) &&
(entry->model_id == MODEL_MAUDIO_PROJECTMIX))
err = snd_bebob_maudio_special_discover(bebob, false);
else
err = snd_bebob_stream_discover(bebob);
if (err < 0)
goto error;
snd_bebob_proc_init(bebob);
if ((bebob->midi_input_ports > 0) ||
(bebob->midi_output_ports > 0)) {
err = snd_bebob_create_midi_devices(bebob);
if (err < 0)
goto error;
}
err = snd_bebob_create_pcm_devices(bebob);
if (err < 0)
goto error;
err = snd_bebob_create_hwdep_device(bebob);
if (err < 0)
goto error;
err = snd_bebob_stream_init_duplex(bebob);
if (err < 0)
goto error;
if (!bebob->maudio_special_quirk) {
err = snd_card_register(card);
if (err < 0) {
snd_bebob_stream_destroy_duplex(bebob);
goto error;
}
} else {
/*
* This is a workaround. This bus reset seems to have an effect
* to make devices correctly handling transactions. Without
* this, the devices have gap_count mismatch. This causes much
* failure of transaction.
*
* Just after registration, user-land application receive
* signals from dbus and starts I/Os. To avoid I/Os till the
* future bus reset, registration is done in next update().
*/
bebob->deferred_registration = true;
fw_schedule_bus_reset(fw_parent_device(bebob->unit)->card,
false, true);
}
dev_set_drvdata(&unit->device, bebob);
end:
mutex_unlock(&devices_mutex);
return err;
error:
mutex_unlock(&devices_mutex);
snd_card_free(card);
return err;
}
static void
bebob_update(struct fw_unit *unit)
{
struct snd_bebob *bebob = dev_get_drvdata(&unit->device);
if (bebob == NULL)
return;
fcp_bus_reset(bebob->unit);
snd_bebob_stream_update_duplex(bebob);
if (bebob->deferred_registration) {
if (snd_card_register(bebob->card) < 0) {
snd_bebob_stream_destroy_duplex(bebob);
snd_card_free(bebob->card);
}
bebob->deferred_registration = false;
}
}
static void bebob_remove(struct fw_unit *unit)
{
struct snd_bebob *bebob = dev_get_drvdata(&unit->device);
if (bebob == NULL)
return;
kfree(bebob->maudio_special_quirk);
snd_bebob_stream_destroy_duplex(bebob);
snd_card_disconnect(bebob->card);
snd_card_free_when_closed(bebob->card);
}
static struct snd_bebob_rate_spec normal_rate_spec = {
.get = &snd_bebob_stream_get_rate,
.set = &snd_bebob_stream_set_rate
};
static const struct snd_bebob_spec spec_normal = {
.clock = NULL,
.rate = &normal_rate_spec,
.meter = NULL
};
static const struct ieee1394_device_id bebob_id_table[] = {
/* Edirol, FA-66 */
SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010049, &spec_normal),
/* Edirol, FA-101 */
SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010048, &spec_normal),
/* Presonus, FIREBOX */
SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010000, &spec_normal),
/* PreSonus, FIREPOD/FP10 */
SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010066, &spec_normal),
/* PreSonus, Inspire1394 */
SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010001, &spec_normal),
/* BridgeCo, RDAudio1 */
SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010048, &spec_normal),
/* BridgeCo, Audio5 */
SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010049, &spec_normal),
/* Mackie, Onyx 1220/1620/1640 (Firewire I/O Card) */
SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010065, &spec_normal),
/* Mackie, d.2 (Firewire Option) */
SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010067, &spec_normal),
/* Stanton, ScratchAmp */
SND_BEBOB_DEV_ENTRY(VEN_STANTON, 0x00000001, &spec_normal),
/* Tascam, IF-FW DM */
SND_BEBOB_DEV_ENTRY(VEN_TASCAM, 0x00010067, &spec_normal),
/* Behringer, XENIX UFX 1204 */
SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00001204, &spec_normal),
/* Behringer, XENIX UFX 1604 */
SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00001604, &spec_normal),
/* Behringer, Digital Mixer X32 series (X-UF Card) */
SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00000006, &spec_normal),
/* Apogee Electronics, Rosetta 200/400 (X-FireWire card) */
/* Apogee Electronics, DA/AD/DD-16X (X-FireWire card) */
SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x00010048, &spec_normal),
/* Apogee Electronics, Ensemble */
SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x00001eee, &spec_normal),
/* ESI, Quatafire610 */
SND_BEBOB_DEV_ENTRY(VEN_ESI, 0x00010064, &spec_normal),
/* AcousticReality, eARMasterOne */
SND_BEBOB_DEV_ENTRY(VEN_ACOUSTIC, 0x00000002, &spec_normal),
/* CME, MatrixKFW */
SND_BEBOB_DEV_ENTRY(VEN_CME, 0x00030000, &spec_normal),
/* Phonic, Helix Board 12 MkII */
SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00050000, &spec_normal),
/* Phonic, Helix Board 18 MkII */
SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00060000, &spec_normal),
/* Phonic, Helix Board 24 MkII */
SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00070000, &spec_normal),
/* Phonic, Helix Board 12 Universal/18 Universal/24 Universal */
SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00000000, &spec_normal),
/* Lynx, Aurora 8/16 (LT-FW) */
SND_BEBOB_DEV_ENTRY(VEN_LYNX, 0x00000001, &spec_normal),
/* ICON, FireXon */
SND_BEBOB_DEV_ENTRY(VEN_ICON, 0x00000001, &spec_normal),
/* PrismSound, Orpheus */
SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x00010048, &spec_normal),
/* PrismSound, ADA-8XR */
SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x0000ada8, &spec_normal),
/* TerraTec Electronic GmbH, PHASE 88 Rack FW */
SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000003, &phase88_rack_spec),
/* TerraTec Electronic GmbH, PHASE 24 FW */
SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000004, &phase24_series_spec),
/* TerraTec Electronic GmbH, Phase X24 FW */
SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000007, &phase24_series_spec),
/* TerraTec Electronic GmbH, EWS MIC2/MIC8 */
SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000005, &spec_normal),
/* Terratec Electronic GmbH, Aureon 7.1 Firewire */
SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000002, &spec_normal),
/* Yamaha, GO44 */
SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000b, &yamaha_go_spec),
/* YAMAHA, GO46 */
SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000c, &yamaha_go_spec),
/* Focusrite, SaffirePro 26 I/O */
SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, 0x00000003, &saffirepro_26_spec),
/* Focusrite, SaffirePro 10 I/O */
SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, 0x00000006, &saffirepro_10_spec),
/* Focusrite, Saffire(no label and LE) */
SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, MODEL_FOCUSRITE_SAFFIRE_BOTH,
&saffire_spec),
/* M-Audio, Firewire 410 */
SND_BEBOB_DEV_ENTRY(VEN_MAUDIO2, 0x00010058, NULL), /* bootloader */
SND_BEBOB_DEV_ENTRY(VEN_MAUDIO2, 0x00010046, &maudio_fw410_spec),
/* M-Audio, Firewire Audiophile */
SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_AUDIOPHILE_BOTH,
&maudio_audiophile_spec),
/* M-Audio, Firewire Solo */
SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010062, &maudio_solo_spec),
/* M-Audio, Ozonic */
SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x0000000a, &maudio_ozonic_spec),
/* M-Audio NRV10 */
SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010081, &maudio_nrv10_spec),
/* M-Audio, ProFireLightbridge */
SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x000100a1, &spec_normal),
/* Firewire 1814 */
SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010070, NULL), /* bootloader */
SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_FW1814,
&maudio_special_spec),
/* M-Audio ProjectMix */
SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_PROJECTMIX,
&maudio_special_spec),
/* IDs are unknown but able to be supported */
/* Apogee, Mini-ME Firewire */
/* Apogee, Mini-DAC Firewire */
/* Behringer, F-Control Audio 1616 */
/* Behringer, F-Control Audio 610 */
/* Cakawalk, Sonar Power Studio 66 */
/* CME, UF400e */
/* ESI, Quotafire XL */
/* Infrasonic, DewX */
/* Infrasonic, Windy6 */
/* Mackie, Digital X Bus x.200 */
/* Mackie, Digital X Bus x.400 */
/* Phonic, HB 12 */
/* Phonic, HB 24 */
/* Phonic, HB 18 */
/* Phonic, FireFly 202 */
/* Phonic, FireFly 302 */
/* Rolf Spuler, Firewire Guitar */
{}
};
MODULE_DEVICE_TABLE(ieee1394, bebob_id_table);
static struct fw_driver bebob_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "snd-bebob",
.bus = &fw_bus_type,
},
.probe = bebob_probe,
.update = bebob_update,
.remove = bebob_remove,
.id_table = bebob_id_table,
};
static int __init
snd_bebob_init(void)
{
return driver_register(&bebob_driver.driver);
}
static void __exit
snd_bebob_exit(void)
{
driver_unregister(&bebob_driver.driver);
}
module_init(snd_bebob_init);
module_exit(snd_bebob_exit);

View file

@ -0,0 +1,255 @@
/*
* bebob.h - a part of driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#ifndef SOUND_BEBOB_H_INCLUDED
#define SOUND_BEBOB_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/info.h>
#include <sound/rawmidi.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/firewire.h>
#include <sound/hwdep.h>
#include "../lib.h"
#include "../fcp.h"
#include "../packets-buffer.h"
#include "../iso-resources.h"
#include "../amdtp.h"
#include "../cmp.h"
/* basic register addresses on DM1000/DM1100/DM1500 */
#define BEBOB_ADDR_REG_INFO 0xffffc8020000ULL
#define BEBOB_ADDR_REG_REQ 0xffffc8021000ULL
struct snd_bebob;
#define SND_BEBOB_STRM_FMT_ENTRIES 7
struct snd_bebob_stream_formation {
unsigned int pcm;
unsigned int midi;
};
/* this is a lookup table for index of stream formations */
extern const unsigned int snd_bebob_rate_table[SND_BEBOB_STRM_FMT_ENTRIES];
/* device specific operations */
#define SND_BEBOB_CLOCK_INTERNAL "Internal"
struct snd_bebob_clock_spec {
unsigned int num;
char *const *labels;
int (*get)(struct snd_bebob *bebob, unsigned int *id);
};
struct snd_bebob_rate_spec {
int (*get)(struct snd_bebob *bebob, unsigned int *rate);
int (*set)(struct snd_bebob *bebob, unsigned int rate);
};
struct snd_bebob_meter_spec {
unsigned int num;
char *const *labels;
int (*get)(struct snd_bebob *bebob, u32 *target, unsigned int size);
};
struct snd_bebob_spec {
struct snd_bebob_clock_spec *clock;
struct snd_bebob_rate_spec *rate;
struct snd_bebob_meter_spec *meter;
};
struct snd_bebob {
struct snd_card *card;
struct fw_unit *unit;
int card_index;
struct mutex mutex;
spinlock_t lock;
const struct snd_bebob_spec *spec;
unsigned int midi_input_ports;
unsigned int midi_output_ports;
/* for bus reset quirk */
struct completion bus_reset;
bool connected;
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;
struct snd_bebob_stream_formation
tx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES];
struct snd_bebob_stream_formation
rx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES];
int sync_input_plug;
/* for uapi */
int dev_lock_count;
bool dev_lock_changed;
wait_queue_head_t hwdep_wait;
/* for M-Audio special devices */
void *maudio_special_quirk;
bool deferred_registration;
};
static inline int
snd_bebob_read_block(struct fw_unit *unit, u64 addr, void *buf, int size)
{
return snd_fw_transaction(unit, TCODE_READ_BLOCK_REQUEST,
BEBOB_ADDR_REG_INFO + addr,
buf, size, 0);
}
static inline int
snd_bebob_read_quad(struct fw_unit *unit, u64 addr, u32 *buf)
{
return snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST,
BEBOB_ADDR_REG_INFO + addr,
(void *)buf, sizeof(u32), 0);
}
/* AV/C Audio Subunit Specification 1.0 (Oct 2000, 1394TA) */
int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id,
unsigned int fb_id, unsigned int num);
int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id,
unsigned int fb_id, unsigned int *num);
/*
* AVC command extensions, AV/C Unit and Subunit, Revision 17
* (Nov 2003, BridgeCo)
*/
#define AVC_BRIDGECO_ADDR_BYTES 6
enum avc_bridgeco_plug_dir {
AVC_BRIDGECO_PLUG_DIR_IN = 0x00,
AVC_BRIDGECO_PLUG_DIR_OUT = 0x01
};
enum avc_bridgeco_plug_mode {
AVC_BRIDGECO_PLUG_MODE_UNIT = 0x00,
AVC_BRIDGECO_PLUG_MODE_SUBUNIT = 0x01,
AVC_BRIDGECO_PLUG_MODE_FUNCTION_BLOCK = 0x02
};
enum avc_bridgeco_plug_unit {
AVC_BRIDGECO_PLUG_UNIT_ISOC = 0x00,
AVC_BRIDGECO_PLUG_UNIT_EXT = 0x01,
AVC_BRIDGECO_PLUG_UNIT_ASYNC = 0x02
};
enum avc_bridgeco_plug_type {
AVC_BRIDGECO_PLUG_TYPE_ISOC = 0x00,
AVC_BRIDGECO_PLUG_TYPE_ASYNC = 0x01,
AVC_BRIDGECO_PLUG_TYPE_MIDI = 0x02,
AVC_BRIDGECO_PLUG_TYPE_SYNC = 0x03,
AVC_BRIDGECO_PLUG_TYPE_ANA = 0x04,
AVC_BRIDGECO_PLUG_TYPE_DIG = 0x05
};
static inline void
avc_bridgeco_fill_unit_addr(u8 buf[AVC_BRIDGECO_ADDR_BYTES],
enum avc_bridgeco_plug_dir dir,
enum avc_bridgeco_plug_unit unit,
unsigned int pid)
{
buf[0] = 0xff; /* Unit */
buf[1] = dir;
buf[2] = AVC_BRIDGECO_PLUG_MODE_UNIT;
buf[3] = unit;
buf[4] = 0xff & pid;
buf[5] = 0xff; /* reserved */
}
static inline void
avc_bridgeco_fill_msu_addr(u8 buf[AVC_BRIDGECO_ADDR_BYTES],
enum avc_bridgeco_plug_dir dir,
unsigned int pid)
{
buf[0] = 0x60; /* Music subunit */
buf[1] = dir;
buf[2] = AVC_BRIDGECO_PLUG_MODE_SUBUNIT;
buf[3] = 0xff & pid;
buf[4] = 0xff; /* reserved */
buf[5] = 0xff; /* reserved */
}
int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES],
u8 *buf, unsigned int len);
int avc_bridgeco_get_plug_type(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES],
enum avc_bridgeco_plug_type *type);
int avc_bridgeco_get_plug_section_type(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES],
unsigned int id, u8 *type);
int avc_bridgeco_get_plug_input(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES],
u8 input[7]);
int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 *buf,
unsigned int *len, unsigned int eid);
/* for AMDTP streaming */
int snd_bebob_stream_get_rate(struct snd_bebob *bebob, unsigned int *rate);
int snd_bebob_stream_set_rate(struct snd_bebob *bebob, unsigned int rate);
int snd_bebob_stream_check_internal_clock(struct snd_bebob *bebob,
bool *internal);
int snd_bebob_stream_discover(struct snd_bebob *bebob);
int snd_bebob_stream_init_duplex(struct snd_bebob *bebob);
int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, unsigned int rate);
void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob);
void snd_bebob_stream_update_duplex(struct snd_bebob *bebob);
void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob);
void snd_bebob_stream_lock_changed(struct snd_bebob *bebob);
int snd_bebob_stream_lock_try(struct snd_bebob *bebob);
void snd_bebob_stream_lock_release(struct snd_bebob *bebob);
void snd_bebob_proc_init(struct snd_bebob *bebob);
int snd_bebob_create_midi_devices(struct snd_bebob *bebob);
int snd_bebob_create_pcm_devices(struct snd_bebob *bebob);
int snd_bebob_create_hwdep_device(struct snd_bebob *bebob);
/* model specific operations */
extern struct snd_bebob_spec phase88_rack_spec;
extern struct snd_bebob_spec phase24_series_spec;
extern struct snd_bebob_spec yamaha_go_spec;
extern struct snd_bebob_spec saffirepro_26_spec;
extern struct snd_bebob_spec saffirepro_10_spec;
extern struct snd_bebob_spec saffire_le_spec;
extern struct snd_bebob_spec saffire_spec;
extern struct snd_bebob_spec maudio_fw410_spec;
extern struct snd_bebob_spec maudio_audiophile_spec;
extern struct snd_bebob_spec maudio_solo_spec;
extern struct snd_bebob_spec maudio_ozonic_spec;
extern struct snd_bebob_spec maudio_nrv10_spec;
extern struct snd_bebob_spec maudio_special_spec;
int snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814);
int snd_bebob_maudio_load_firmware(struct fw_unit *unit);
#define SND_BEBOB_DEV_ENTRY(vendor, model, data) \
{ \
.match_flags = IEEE1394_MATCH_VENDOR_ID | \
IEEE1394_MATCH_MODEL_ID, \
.vendor_id = vendor, \
.model_id = model, \
.driver_data = (kernel_ulong_t)data \
}
#endif

View file

@ -0,0 +1,282 @@
/*
* bebob_command.c - driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "./bebob.h"
int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id,
unsigned int fb_id, unsigned int num)
{
u8 *buf;
int err;
buf = kzalloc(12, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
buf[0] = 0x00; /* AV/C CONTROL */
buf[1] = 0x08 | (0x07 & subunit_id); /* AUDIO SUBUNIT ID */
buf[2] = 0xb8; /* FUNCTION BLOCK */
buf[3] = 0x80; /* type is 'selector'*/
buf[4] = 0xff & fb_id; /* function block id */
buf[5] = 0x10; /* control attribute is CURRENT */
buf[6] = 0x02; /* selector length is 2 */
buf[7] = 0xff & num; /* input function block plug number */
buf[8] = 0x01; /* control selector is SELECTOR_CONTROL */
err = fcp_avc_transaction(unit, buf, 12, buf, 12,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
BIT(6) | BIT(7) | BIT(8));
if (err > 0 && err < 9)
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;
else if (buf[0] == 0x0a) /* REJECTED */
err = -EINVAL;
else if (err > 0)
err = 0;
kfree(buf);
return err;
}
int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id,
unsigned int fb_id, unsigned int *num)
{
u8 *buf;
int err;
buf = kzalloc(12, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
buf[0] = 0x01; /* AV/C STATUS */
buf[1] = 0x08 | (0x07 & subunit_id); /* AUDIO SUBUNIT ID */
buf[2] = 0xb8; /* FUNCTION BLOCK */
buf[3] = 0x80; /* type is 'selector'*/
buf[4] = 0xff & fb_id; /* function block id */
buf[5] = 0x10; /* control attribute is CURRENT */
buf[6] = 0x02; /* selector length is 2 */
buf[7] = 0xff; /* input function block plug number */
buf[8] = 0x01; /* control selector is SELECTOR_CONTROL */
err = fcp_avc_transaction(unit, buf, 12, buf, 12,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
BIT(6) | BIT(8));
if (err > 0 && err < 9)
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;
else if (buf[0] == 0x0a) /* REJECTED */
err = -EINVAL;
else if (buf[0] == 0x0b) /* IN TRANSITION */
err = -EAGAIN;
if (err < 0)
goto end;
*num = buf[7];
err = 0;
end:
kfree(buf);
return err;
}
static inline void
avc_bridgeco_fill_extension_addr(u8 *buf, u8 *addr)
{
buf[1] = addr[0];
memcpy(buf + 4, addr + 1, 5);
}
static inline void
avc_bridgeco_fill_plug_info_extension_command(u8 *buf, u8 *addr,
unsigned int itype)
{
buf[0] = 0x01; /* AV/C STATUS */
buf[2] = 0x02; /* AV/C GENERAL PLUG INFO */
buf[3] = 0xc0; /* BridgeCo extension */
avc_bridgeco_fill_extension_addr(buf, addr);
buf[9] = itype; /* info type */
}
int avc_bridgeco_get_plug_type(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES],
enum avc_bridgeco_plug_type *type)
{
u8 *buf;
int err;
buf = kzalloc(12, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
/* Info type is 'plug type'. */
avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x00);
err = fcp_avc_transaction(unit, buf, 12, buf, 12,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
BIT(6) | BIT(7) | BIT(9));
if ((err >= 0) && (err < 8))
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;
else if (buf[0] == 0x0a) /* REJECTED */
err = -EINVAL;
else if (buf[0] == 0x0b) /* IN TRANSITION */
err = -EAGAIN;
if (err < 0)
goto end;
*type = buf[10];
err = 0;
end:
kfree(buf);
return err;
}
int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES],
u8 *buf, unsigned int len)
{
int err;
/* Info type is 'channel position'. */
avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x03);
err = fcp_avc_transaction(unit, buf, 12, buf, 256,
BIT(1) | BIT(2) | BIT(3) | BIT(4) |
BIT(5) | BIT(6) | BIT(7) | BIT(9));
if ((err >= 0) && (err < 8))
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;
else if (buf[0] == 0x0a) /* REJECTED */
err = -EINVAL;
else if (buf[0] == 0x0b) /* IN TRANSITION */
err = -EAGAIN;
if (err < 0)
goto end;
/* Pick up specific data. */
memmove(buf, buf + 10, err - 10);
err = 0;
end:
return err;
}
int avc_bridgeco_get_plug_section_type(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES],
unsigned int id, u8 *type)
{
u8 *buf;
int err;
/* section info includes charactors but this module don't need it */
buf = kzalloc(12, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
/* Info type is 'section info'. */
avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x07);
buf[10] = 0xff & ++id; /* section id */
err = fcp_avc_transaction(unit, buf, 12, buf, 12,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
BIT(6) | BIT(7) | BIT(9) | BIT(10));
if ((err >= 0) && (err < 8))
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;
else if (buf[0] == 0x0a) /* REJECTED */
err = -EINVAL;
else if (buf[0] == 0x0b) /* IN TRANSITION */
err = -EAGAIN;
if (err < 0)
goto end;
*type = buf[11];
err = 0;
end:
kfree(buf);
return err;
}
int avc_bridgeco_get_plug_input(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 input[7])
{
int err;
u8 *buf;
buf = kzalloc(18, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
/* Info type is 'plug input'. */
avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x05);
err = fcp_avc_transaction(unit, buf, 16, buf, 16,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
BIT(6) | BIT(7));
if ((err >= 0) && (err < 8))
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;
else if (buf[0] == 0x0a) /* REJECTED */
err = -EINVAL;
else if (buf[0] == 0x0b) /* IN TRANSITION */
err = -EAGAIN;
if (err < 0)
goto end;
memcpy(input, buf + 10, 5);
err = 0;
end:
kfree(buf);
return err;
}
int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 *buf,
unsigned int *len, unsigned int eid)
{
int err;
/* check given buffer */
if ((buf == NULL) || (*len < 12)) {
err = -EINVAL;
goto end;
}
buf[0] = 0x01; /* AV/C STATUS */
buf[2] = 0x2f; /* AV/C STREAM FORMAT SUPPORT */
buf[3] = 0xc1; /* Bridgeco extension - List Request */
avc_bridgeco_fill_extension_addr(buf, addr);
buf[10] = 0xff & eid; /* Entry ID */
err = fcp_avc_transaction(unit, buf, 12, buf, *len,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
BIT(6) | BIT(7) | BIT(10));
if ((err >= 0) && (err < 12))
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;
else if (buf[0] == 0x0a) /* REJECTED */
err = -EINVAL;
else if (buf[0] == 0x0b) /* IN TRANSITION */
err = -EAGAIN;
else if (buf[10] != eid)
err = -EIO;
if (err < 0)
goto end;
/* Pick up 'stream format info'. */
memmove(buf, buf + 11, err - 11);
*len = err - 11;
err = 0;
end:
return err;
}

View file

@ -0,0 +1,313 @@
/*
* bebob_focusrite.c - a part of driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "./bebob.h"
#define ANA_IN "Analog In"
#define DIG_IN "Digital In"
#define ANA_OUT "Analog Out"
#define DIG_OUT "Digital Out"
#define STM_IN "Stream In"
#define SAFFIRE_ADDRESS_BASE 0x000100000000ULL
#define SAFFIRE_OFFSET_CLOCK_SOURCE 0x00f8
#define SAFFIREPRO_OFFSET_CLOCK_SOURCE 0x0174
/* whether sync to external device or not */
#define SAFFIRE_OFFSET_CLOCK_SYNC_EXT 0x013c
#define SAFFIRE_LE_OFFSET_CLOCK_SYNC_EXT 0x0432
#define SAFFIREPRO_OFFSET_CLOCK_SYNC_EXT 0x0164
#define SAFFIRE_CLOCK_SOURCE_INTERNAL 0
#define SAFFIRE_CLOCK_SOURCE_SPDIF 1
/* clock sources as returned from register of Saffire Pro 10 and 26 */
#define SAFFIREPRO_CLOCK_SOURCE_INTERNAL 0
#define SAFFIREPRO_CLOCK_SOURCE_SKIP 1 /* never used on hardware */
#define SAFFIREPRO_CLOCK_SOURCE_SPDIF 2
#define SAFFIREPRO_CLOCK_SOURCE_ADAT1 3 /* not used on s.pro. 10 */
#define SAFFIREPRO_CLOCK_SOURCE_ADAT2 4 /* not used on s.pro. 10 */
#define SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK 5
#define SAFFIREPRO_CLOCK_SOURCE_COUNT 6
/* S/PDIF, ADAT1, ADAT2 is enabled or not. three quadlets */
#define SAFFIREPRO_ENABLE_DIG_IFACES 0x01a4
/* saffirepro has its own parameter for sampling frequency */
#define SAFFIREPRO_RATE_NOREBOOT 0x01cc
/* index is the value for this register */
static const unsigned int rates[] = {
[0] = 0,
[1] = 44100,
[2] = 48000,
[3] = 88200,
[4] = 96000,
[5] = 176400,
[6] = 192000
};
/* saffire(no label)/saffire LE has metering */
#define SAFFIRE_OFFSET_METER 0x0100
#define SAFFIRE_LE_OFFSET_METER 0x0168
static inline int
saffire_read_block(struct snd_bebob *bebob, u64 offset,
u32 *buf, unsigned int size)
{
unsigned int i;
int err;
__be32 *tmp = (__be32 *)buf;
err = snd_fw_transaction(bebob->unit, TCODE_READ_BLOCK_REQUEST,
SAFFIRE_ADDRESS_BASE + offset,
tmp, size, 0);
if (err < 0)
goto end;
for (i = 0; i < size / sizeof(u32); i++)
buf[i] = be32_to_cpu(tmp[i]);
end:
return err;
}
static inline int
saffire_read_quad(struct snd_bebob *bebob, u64 offset, u32 *value)
{
int err;
__be32 tmp;
err = snd_fw_transaction(bebob->unit, TCODE_READ_QUADLET_REQUEST,
SAFFIRE_ADDRESS_BASE + offset,
&tmp, sizeof(__be32), 0);
if (err < 0)
goto end;
*value = be32_to_cpu(tmp);
end:
return err;
}
static inline int
saffire_write_quad(struct snd_bebob *bebob, u64 offset, u32 value)
{
__be32 data = cpu_to_be32(value);
return snd_fw_transaction(bebob->unit, TCODE_WRITE_QUADLET_REQUEST,
SAFFIRE_ADDRESS_BASE + offset,
&data, sizeof(__be32), 0);
}
static char *const saffirepro_10_clk_src_labels[] = {
SND_BEBOB_CLOCK_INTERNAL, "S/PDIF", "Word Clock"
};
static char *const saffirepro_26_clk_src_labels[] = {
SND_BEBOB_CLOCK_INTERNAL, "S/PDIF", "ADAT1", "ADAT2", "Word Clock"
};
/* Value maps between registers and labels for SaffirePro 10/26. */
static const signed char saffirepro_clk_maps[][SAFFIREPRO_CLOCK_SOURCE_COUNT] = {
/* SaffirePro 10 */
[0] = {
[SAFFIREPRO_CLOCK_SOURCE_INTERNAL] = 0,
[SAFFIREPRO_CLOCK_SOURCE_SKIP] = -1, /* not supported */
[SAFFIREPRO_CLOCK_SOURCE_SPDIF] = 1,
[SAFFIREPRO_CLOCK_SOURCE_ADAT1] = -1, /* not supported */
[SAFFIREPRO_CLOCK_SOURCE_ADAT2] = -1, /* not supported */
[SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK] = 2,
},
/* SaffirePro 26 */
[1] = {
[SAFFIREPRO_CLOCK_SOURCE_INTERNAL] = 0,
[SAFFIREPRO_CLOCK_SOURCE_SKIP] = -1, /* not supported */
[SAFFIREPRO_CLOCK_SOURCE_SPDIF] = 1,
[SAFFIREPRO_CLOCK_SOURCE_ADAT1] = 2,
[SAFFIREPRO_CLOCK_SOURCE_ADAT2] = 3,
[SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK] = 4,
}
};
static int
saffirepro_both_clk_freq_get(struct snd_bebob *bebob, unsigned int *rate)
{
u32 id;
int err;
err = saffire_read_quad(bebob, SAFFIREPRO_RATE_NOREBOOT, &id);
if (err < 0)
goto end;
if (id >= ARRAY_SIZE(rates))
err = -EIO;
else
*rate = rates[id];
end:
return err;
}
static int
saffirepro_both_clk_freq_set(struct snd_bebob *bebob, unsigned int rate)
{
u32 id;
for (id = 0; id < ARRAY_SIZE(rates); id++) {
if (rates[id] == rate)
break;
}
if (id == ARRAY_SIZE(rates))
return -EINVAL;
return saffire_write_quad(bebob, SAFFIREPRO_RATE_NOREBOOT, id);
}
/*
* query hardware for current clock source, return our internally
* used clock index in *id, depending on hardware.
*/
static int
saffirepro_both_clk_src_get(struct snd_bebob *bebob, unsigned int *id)
{
int err;
u32 value; /* clock source read from hw register */
const signed char *map;
err = saffire_read_quad(bebob, SAFFIREPRO_OFFSET_CLOCK_SOURCE, &value);
if (err < 0)
goto end;
/* depending on hardware, use a different mapping */
if (bebob->spec->clock->labels == saffirepro_10_clk_src_labels)
map = saffirepro_clk_maps[0];
else
map = saffirepro_clk_maps[1];
/* In a case that this driver cannot handle the value of register. */
if (value >= SAFFIREPRO_CLOCK_SOURCE_COUNT || map[value] < 0) {
err = -EIO;
goto end;
}
*id = (unsigned int)map[value];
end:
return err;
}
struct snd_bebob_spec saffire_le_spec;
static char *const saffire_both_clk_src_labels[] = {
SND_BEBOB_CLOCK_INTERNAL, "S/PDIF"
};
static int
saffire_both_clk_src_get(struct snd_bebob *bebob, unsigned int *id)
{
int err;
u32 value;
err = saffire_read_quad(bebob, SAFFIRE_OFFSET_CLOCK_SOURCE, &value);
if (err >= 0)
*id = 0xff & value;
return err;
};
static char *const saffire_le_meter_labels[] = {
ANA_IN, ANA_IN, DIG_IN,
ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT,
STM_IN, STM_IN
};
static char *const saffire_meter_labels[] = {
ANA_IN, ANA_IN,
STM_IN, STM_IN, STM_IN, STM_IN, STM_IN,
};
static int
saffire_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size)
{
struct snd_bebob_meter_spec *spec = bebob->spec->meter;
unsigned int channels;
u64 offset;
int err;
if (spec->labels == saffire_le_meter_labels)
offset = SAFFIRE_LE_OFFSET_METER;
else
offset = SAFFIRE_OFFSET_METER;
channels = spec->num * 2;
if (size < channels * sizeof(u32))
return -EIO;
err = saffire_read_block(bebob, offset, buf, size);
if (err >= 0 && spec->labels == saffire_le_meter_labels) {
swap(buf[1], buf[3]);
swap(buf[2], buf[3]);
swap(buf[3], buf[4]);
swap(buf[7], buf[10]);
swap(buf[8], buf[10]);
swap(buf[9], buf[11]);
swap(buf[11], buf[12]);
swap(buf[15], buf[16]);
}
return err;
}
static struct snd_bebob_rate_spec saffirepro_both_rate_spec = {
.get = &saffirepro_both_clk_freq_get,
.set = &saffirepro_both_clk_freq_set,
};
/* Saffire Pro 26 I/O */
static struct snd_bebob_clock_spec saffirepro_26_clk_spec = {
.num = ARRAY_SIZE(saffirepro_26_clk_src_labels),
.labels = saffirepro_26_clk_src_labels,
.get = &saffirepro_both_clk_src_get,
};
struct snd_bebob_spec saffirepro_26_spec = {
.clock = &saffirepro_26_clk_spec,
.rate = &saffirepro_both_rate_spec,
.meter = NULL
};
/* Saffire Pro 10 I/O */
static struct snd_bebob_clock_spec saffirepro_10_clk_spec = {
.num = ARRAY_SIZE(saffirepro_10_clk_src_labels),
.labels = saffirepro_10_clk_src_labels,
.get = &saffirepro_both_clk_src_get,
};
struct snd_bebob_spec saffirepro_10_spec = {
.clock = &saffirepro_10_clk_spec,
.rate = &saffirepro_both_rate_spec,
.meter = NULL
};
static struct snd_bebob_rate_spec saffire_both_rate_spec = {
.get = &snd_bebob_stream_get_rate,
.set = &snd_bebob_stream_set_rate,
};
static struct snd_bebob_clock_spec saffire_both_clk_spec = {
.num = ARRAY_SIZE(saffire_both_clk_src_labels),
.labels = saffire_both_clk_src_labels,
.get = &saffire_both_clk_src_get,
};
/* Saffire LE */
static struct snd_bebob_meter_spec saffire_le_meter_spec = {
.num = ARRAY_SIZE(saffire_le_meter_labels),
.labels = saffire_le_meter_labels,
.get = &saffire_meter_get,
};
struct snd_bebob_spec saffire_le_spec = {
.clock = &saffire_both_clk_spec,
.rate = &saffire_both_rate_spec,
.meter = &saffire_le_meter_spec
};
/* Saffire */
static struct snd_bebob_meter_spec saffire_meter_spec = {
.num = ARRAY_SIZE(saffire_meter_labels),
.labels = saffire_meter_labels,
.get = &saffire_meter_get,
};
struct snd_bebob_spec saffire_spec = {
.clock = &saffire_both_clk_spec,
.rate = &saffire_both_rate_spec,
.meter = &saffire_meter_spec
};

View file

@ -0,0 +1,199 @@
/*
* bebob_hwdep.c - a part of driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
/*
* This codes give three functionality.
*
* 1.get firewire node infomation
* 2.get notification about starting/stopping stream
* 3.lock/unlock stream
*/
#include "bebob.h"
static long
hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count,
loff_t *offset)
{
struct snd_bebob *bebob = hwdep->private_data;
DEFINE_WAIT(wait);
union snd_firewire_event event;
spin_lock_irq(&bebob->lock);
while (!bebob->dev_lock_changed) {
prepare_to_wait(&bebob->hwdep_wait, &wait, TASK_INTERRUPTIBLE);
spin_unlock_irq(&bebob->lock);
schedule();
finish_wait(&bebob->hwdep_wait, &wait);
if (signal_pending(current))
return -ERESTARTSYS;
spin_lock_irq(&bebob->lock);
}
memset(&event, 0, sizeof(event));
if (bebob->dev_lock_changed) {
event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
event.lock_status.status = (bebob->dev_lock_count > 0);
bebob->dev_lock_changed = false;
count = min_t(long, count, sizeof(event.lock_status));
}
spin_unlock_irq(&bebob->lock);
if (copy_to_user(buf, &event, count))
return -EFAULT;
return count;
}
static unsigned int
hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait)
{
struct snd_bebob *bebob = hwdep->private_data;
unsigned int events;
poll_wait(file, &bebob->hwdep_wait, wait);
spin_lock_irq(&bebob->lock);
if (bebob->dev_lock_changed)
events = POLLIN | POLLRDNORM;
else
events = 0;
spin_unlock_irq(&bebob->lock);
return events;
}
static int
hwdep_get_info(struct snd_bebob *bebob, void __user *arg)
{
struct fw_device *dev = fw_parent_device(bebob->unit);
struct snd_firewire_get_info info;
memset(&info, 0, sizeof(info));
info.type = SNDRV_FIREWIRE_TYPE_BEBOB;
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_bebob *bebob)
{
int err;
spin_lock_irq(&bebob->lock);
if (bebob->dev_lock_count == 0) {
bebob->dev_lock_count = -1;
err = 0;
} else {
err = -EBUSY;
}
spin_unlock_irq(&bebob->lock);
return err;
}
static int
hwdep_unlock(struct snd_bebob *bebob)
{
int err;
spin_lock_irq(&bebob->lock);
if (bebob->dev_lock_count == -1) {
bebob->dev_lock_count = 0;
err = 0;
} else {
err = -EBADFD;
}
spin_unlock_irq(&bebob->lock);
return err;
}
static int
hwdep_release(struct snd_hwdep *hwdep, struct file *file)
{
struct snd_bebob *bebob = hwdep->private_data;
spin_lock_irq(&bebob->lock);
if (bebob->dev_lock_count == -1)
bebob->dev_lock_count = 0;
spin_unlock_irq(&bebob->lock);
return 0;
}
static int
hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct snd_bebob *bebob = hwdep->private_data;
switch (cmd) {
case SNDRV_FIREWIRE_IOCTL_GET_INFO:
return hwdep_get_info(bebob, (void __user *)arg);
case SNDRV_FIREWIRE_IOCTL_LOCK:
return hwdep_lock(bebob);
case SNDRV_FIREWIRE_IOCTL_UNLOCK:
return hwdep_unlock(bebob);
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,
.release = hwdep_release,
.poll = hwdep_poll,
.ioctl = hwdep_ioctl,
.ioctl_compat = hwdep_compat_ioctl,
};
int snd_bebob_create_hwdep_device(struct snd_bebob *bebob)
{
struct snd_hwdep *hwdep;
int err;
err = snd_hwdep_new(bebob->card, "BeBoB", 0, &hwdep);
if (err < 0)
goto end;
strcpy(hwdep->name, "BeBoB");
hwdep->iface = SNDRV_HWDEP_IFACE_FW_BEBOB;
hwdep->ops = hwdep_ops;
hwdep->private_data = bebob;
hwdep->exclusive = true;
end:
return err;
}

View file

@ -0,0 +1,813 @@
/*
* bebob_maudio.c - a part of driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "./bebob.h"
#include <sound/control.h>
/*
* Just powering on, Firewire 410/Audiophile/1814 and ProjectMix I/O wait to
* download firmware blob. To enable these devices, drivers should upload
* firmware blob and send a command to initialize configuration to factory
* settings when completing uploading. Then these devices generate bus reset
* and are recognized as new devices with the firmware.
*
* But with firmware version 5058 or later, the firmware is stored to flash
* memory in the device and drivers can tell bootloader to load the firmware
* by sending a cue. This cue must be sent one time.
*
* For streaming, both of output and input streams are needed for Firewire 410
* and Ozonic. The single stream is OK for the other devices even if the clock
* source is not SYT-Match (I note no devices use SYT-Match).
*
* Without streaming, the devices except for Firewire Audiophile can mix any
* input and output. For this reason, Audiophile cannot be used as standalone
* mixer.
*
* Firewire 1814 and ProjectMix I/O uses special firmware. It will be freezed
* when receiving any commands which the firmware can't understand. These
* devices utilize completely different system to control. It is some
* write-transaction directly into a certain address. All of addresses for mixer
* functionality is between 0xffc700700000 to 0xffc70070009c.
*/
/* Offset from information register */
#define INFO_OFFSET_SW_DATE 0x20
/* Bootloader Protocol Version 1 */
#define MAUDIO_BOOTLOADER_CUE1 0x00000001
/*
* Initializing configuration to factory settings (= 0x1101), (swapped in line),
* Command code is zero (= 0x00),
* the number of operands is zero (= 0x00)(at least significant byte)
*/
#define MAUDIO_BOOTLOADER_CUE2 0x01110000
/* padding */
#define MAUDIO_BOOTLOADER_CUE3 0x00000000
#define MAUDIO_SPECIFIC_ADDRESS 0xffc700000000ULL
#define METER_OFFSET 0x00600000
/* some device has sync info after metering data */
#define METER_SIZE_SPECIAL 84 /* with sync info */
#define METER_SIZE_FW410 76 /* with sync info */
#define METER_SIZE_AUDIOPHILE 60 /* with sync info */
#define METER_SIZE_SOLO 52 /* with sync info */
#define METER_SIZE_OZONIC 48
#define METER_SIZE_NRV10 80
/* labels for metering */
#define ANA_IN "Analog In"
#define ANA_OUT "Analog Out"
#define DIG_IN "Digital In"
#define SPDIF_IN "S/PDIF In"
#define ADAT_IN "ADAT In"
#define DIG_OUT "Digital Out"
#define SPDIF_OUT "S/PDIF Out"
#define ADAT_OUT "ADAT Out"
#define STRM_IN "Stream In"
#define AUX_OUT "Aux Out"
#define HP_OUT "HP Out"
/* for NRV */
#define UNKNOWN_METER "Unknown"
struct special_params {
bool is1814;
unsigned int clk_src;
unsigned int dig_in_fmt;
unsigned int dig_out_fmt;
unsigned int clk_lock;
struct snd_ctl_elem_id *ctl_id_sync;
};
/*
* For some M-Audio devices, this module just send cue to load firmware. After
* loading, the device generates bus reset and newly detected.
*
* If we make any transactions to load firmware, the operation may failed.
*/
int snd_bebob_maudio_load_firmware(struct fw_unit *unit)
{
struct fw_device *device = fw_parent_device(unit);
int err, rcode;
u64 date;
__le32 cues[3] = {
cpu_to_le32(MAUDIO_BOOTLOADER_CUE1),
cpu_to_le32(MAUDIO_BOOTLOADER_CUE2),
cpu_to_le32(MAUDIO_BOOTLOADER_CUE3)
};
/* check date of software used to build */
err = snd_bebob_read_block(unit, INFO_OFFSET_SW_DATE,
&date, sizeof(u64));
if (err < 0)
goto end;
/*
* firmware version 5058 or later has date later than "20070401", but
* 'date' is not null-terminated.
*/
if (date < 0x3230303730343031LL) {
dev_err(&unit->device,
"Use firmware version 5058 or later\n");
err = -ENOSYS;
goto end;
}
rcode = fw_run_transaction(device->card, TCODE_WRITE_BLOCK_REQUEST,
device->node_id, device->generation,
device->max_speed, BEBOB_ADDR_REG_REQ,
cues, sizeof(cues));
if (rcode != RCODE_COMPLETE) {
dev_err(&unit->device,
"Failed to send a cue to load firmware\n");
err = -EIO;
}
end:
return err;
}
static inline int
get_meter(struct snd_bebob *bebob, void *buf, unsigned int size)
{
return snd_fw_transaction(bebob->unit, TCODE_READ_BLOCK_REQUEST,
MAUDIO_SPECIFIC_ADDRESS + METER_OFFSET,
buf, size, 0);
}
static int
check_clk_sync(struct snd_bebob *bebob, unsigned int size, bool *sync)
{
int err;
u8 *buf;
buf = kmalloc(size, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
err = get_meter(bebob, buf, size);
if (err < 0)
goto end;
/* if synced, this value is the same as SFC of FDF in CIP header */
*sync = (buf[size - 2] != 0xff);
end:
kfree(buf);
return err;
}
/*
* dig_fmt: 0x00:S/PDIF, 0x01:ADAT
* clk_lock: 0x00:unlock, 0x01:lock
*/
static int
avc_maudio_set_special_clk(struct snd_bebob *bebob, unsigned int clk_src,
unsigned int dig_in_fmt, unsigned int dig_out_fmt,
unsigned int clk_lock)
{
struct special_params *params = bebob->maudio_special_quirk;
int err;
u8 *buf;
if (amdtp_stream_running(&bebob->rx_stream) ||
amdtp_stream_running(&bebob->tx_stream))
return -EBUSY;
buf = kmalloc(12, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
buf[0] = 0x00; /* CONTROL */
buf[1] = 0xff; /* UNIT */
buf[2] = 0x00; /* vendor dependent */
buf[3] = 0x04; /* company ID high */
buf[4] = 0x00; /* company ID middle */
buf[5] = 0x04; /* company ID low */
buf[6] = 0xff & clk_src; /* clock source */
buf[7] = 0xff & dig_in_fmt; /* input digital format */
buf[8] = 0xff & dig_out_fmt; /* output digital format */
buf[9] = 0xff & clk_lock; /* lock these settings */
buf[10] = 0x00; /* padding */
buf[11] = 0x00; /* padding */
err = fcp_avc_transaction(bebob->unit, buf, 12, buf, 12,
BIT(1) | BIT(2) | BIT(3) | BIT(4) |
BIT(5) | BIT(6) | BIT(7) | BIT(8) |
BIT(9));
if ((err > 0) && (err < 10))
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;
else if (buf[0] == 0x0a) /* REJECTED */
err = -EINVAL;
if (err < 0)
goto end;
params->clk_src = buf[6];
params->dig_in_fmt = buf[7];
params->dig_out_fmt = buf[8];
params->clk_lock = buf[9];
if (params->ctl_id_sync)
snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE,
params->ctl_id_sync);
err = 0;
end:
kfree(buf);
return err;
}
static void
special_stream_formation_set(struct snd_bebob *bebob)
{
static const unsigned int ch_table[2][2][3] = {
/* AMDTP_OUT_STREAM */
{ { 6, 6, 4 }, /* SPDIF */
{ 12, 8, 4 } }, /* ADAT */
/* AMDTP_IN_STREAM */
{ { 10, 10, 2 }, /* SPDIF */
{ 16, 12, 2 } } /* ADAT */
};
struct special_params *params = bebob->maudio_special_quirk;
unsigned int i, max;
max = SND_BEBOB_STRM_FMT_ENTRIES - 1;
if (!params->is1814)
max -= 2;
for (i = 0; i < max; i++) {
bebob->tx_stream_formations[i + 1].pcm =
ch_table[AMDTP_IN_STREAM][params->dig_in_fmt][i / 2];
bebob->tx_stream_formations[i + 1].midi = 1;
bebob->rx_stream_formations[i + 1].pcm =
ch_table[AMDTP_OUT_STREAM][params->dig_out_fmt][i / 2];
bebob->rx_stream_formations[i + 1].midi = 1;
}
}
static int add_special_controls(struct snd_bebob *bebob);
int
snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814)
{
struct special_params *params;
int err;
params = kzalloc(sizeof(struct special_params), GFP_KERNEL);
if (params == NULL)
return -ENOMEM;
mutex_lock(&bebob->mutex);
bebob->maudio_special_quirk = (void *)params;
params->is1814 = is1814;
/* initialize these parameters because driver is not allowed to ask */
bebob->rx_stream.context = ERR_PTR(-1);
bebob->tx_stream.context = ERR_PTR(-1);
err = avc_maudio_set_special_clk(bebob, 0x03, 0x00, 0x00, 0x00);
if (err < 0) {
dev_err(&bebob->unit->device,
"fail to initialize clock params: %d\n", err);
goto end;
}
err = add_special_controls(bebob);
if (err < 0)
goto end;
special_stream_formation_set(bebob);
if (params->is1814) {
bebob->midi_input_ports = 1;
bebob->midi_output_ports = 1;
} else {
bebob->midi_input_ports = 2;
bebob->midi_output_ports = 2;
}
end:
if (err < 0) {
kfree(params);
bebob->maudio_special_quirk = NULL;
}
mutex_unlock(&bebob->mutex);
return err;
}
/* Input plug shows actual rate. Output plug is needless for this purpose. */
static int special_get_rate(struct snd_bebob *bebob, unsigned int *rate)
{
int err, trials;
trials = 0;
do {
err = avc_general_get_sig_fmt(bebob->unit, rate,
AVC_GENERAL_PLUG_DIR_IN, 0);
} while (err == -EAGAIN && ++trials < 3);
return err;
}
static int special_set_rate(struct snd_bebob *bebob, unsigned int rate)
{
struct special_params *params = bebob->maudio_special_quirk;
int err;
err = avc_general_set_sig_fmt(bebob->unit, rate,
AVC_GENERAL_PLUG_DIR_OUT, 0);
if (err < 0)
goto end;
/*
* Just after changing sampling rate for output, a followed command
* for input is easy to fail. This is a workaround fot this issue.
*/
msleep(100);
err = avc_general_set_sig_fmt(bebob->unit, rate,
AVC_GENERAL_PLUG_DIR_IN, 0);
if (err < 0)
goto end;
if (params->ctl_id_sync)
snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE,
params->ctl_id_sync);
end:
return err;
}
/* Clock source control for special firmware */
static char *const special_clk_labels[] = {
SND_BEBOB_CLOCK_INTERNAL " with Digital Mute", "Digital",
"Word Clock", SND_BEBOB_CLOCK_INTERNAL};
static int special_clk_get(struct snd_bebob *bebob, unsigned int *id)
{
struct special_params *params = bebob->maudio_special_quirk;
*id = params->clk_src;
return 0;
}
static int special_clk_ctl_info(struct snd_kcontrol *kctl,
struct snd_ctl_elem_info *einf)
{
einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
einf->count = 1;
einf->value.enumerated.items = ARRAY_SIZE(special_clk_labels);
if (einf->value.enumerated.item >= einf->value.enumerated.items)
einf->value.enumerated.item = einf->value.enumerated.items - 1;
strcpy(einf->value.enumerated.name,
special_clk_labels[einf->value.enumerated.item]);
return 0;
}
static int special_clk_ctl_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uval)
{
struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
struct special_params *params = bebob->maudio_special_quirk;
uval->value.enumerated.item[0] = params->clk_src;
return 0;
}
static int special_clk_ctl_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uval)
{
struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
struct special_params *params = bebob->maudio_special_quirk;
int err, id;
id = uval->value.enumerated.item[0];
if (id >= ARRAY_SIZE(special_clk_labels))
return -EINVAL;
mutex_lock(&bebob->mutex);
err = avc_maudio_set_special_clk(bebob, id,
params->dig_in_fmt,
params->dig_out_fmt,
params->clk_lock);
mutex_unlock(&bebob->mutex);
if (err >= 0)
err = 1;
return err;
}
static struct snd_kcontrol_new special_clk_ctl = {
.name = "Clock Source",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = special_clk_ctl_info,
.get = special_clk_ctl_get,
.put = special_clk_ctl_put
};
/* Clock synchronization control for special firmware */
static int special_sync_ctl_info(struct snd_kcontrol *kctl,
struct snd_ctl_elem_info *einf)
{
einf->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
einf->count = 1;
einf->value.integer.min = 0;
einf->value.integer.max = 1;
return 0;
}
static int special_sync_ctl_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uval)
{
struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
int err;
bool synced = 0;
err = check_clk_sync(bebob, METER_SIZE_SPECIAL, &synced);
if (err >= 0)
uval->value.integer.value[0] = synced;
return 0;
}
static struct snd_kcontrol_new special_sync_ctl = {
.name = "Sync Status",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.info = special_sync_ctl_info,
.get = special_sync_ctl_get,
};
/* Digital input interface control for special firmware */
static char *const special_dig_in_iface_labels[] = {
"S/PDIF Optical", "S/PDIF Coaxial", "ADAT Optical"
};
static int special_dig_in_iface_ctl_info(struct snd_kcontrol *kctl,
struct snd_ctl_elem_info *einf)
{
einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
einf->count = 1;
einf->value.enumerated.items = ARRAY_SIZE(special_dig_in_iface_labels);
if (einf->value.enumerated.item >= einf->value.enumerated.items)
einf->value.enumerated.item = einf->value.enumerated.items - 1;
strcpy(einf->value.enumerated.name,
special_dig_in_iface_labels[einf->value.enumerated.item]);
return 0;
}
static int special_dig_in_iface_ctl_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uval)
{
struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
struct special_params *params = bebob->maudio_special_quirk;
unsigned int dig_in_iface;
int err, val;
mutex_lock(&bebob->mutex);
err = avc_audio_get_selector(bebob->unit, 0x00, 0x04,
&dig_in_iface);
if (err < 0) {
dev_err(&bebob->unit->device,
"fail to get digital input interface: %d\n", err);
goto end;
}
/* encoded id for user value */
val = (params->dig_in_fmt << 1) | (dig_in_iface & 0x01);
/* for ADAT Optical */
if (val > 2)
val = 2;
uval->value.enumerated.item[0] = val;
end:
mutex_unlock(&bebob->mutex);
return err;
}
static int special_dig_in_iface_ctl_set(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uval)
{
struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
struct special_params *params = bebob->maudio_special_quirk;
unsigned int id, dig_in_fmt, dig_in_iface;
int err;
id = uval->value.enumerated.item[0];
if (id >= ARRAY_SIZE(special_dig_in_iface_labels))
return -EINVAL;
/* decode user value */
dig_in_fmt = (id >> 1) & 0x01;
dig_in_iface = id & 0x01;
mutex_lock(&bebob->mutex);
err = avc_maudio_set_special_clk(bebob,
params->clk_src,
dig_in_fmt,
params->dig_out_fmt,
params->clk_lock);
if (err < 0)
goto end;
/* For ADAT, optical interface is only available. */
if (params->dig_in_fmt > 0) {
err = 1;
goto end;
}
/* For S/PDIF, optical/coaxial interfaces are selectable. */
err = avc_audio_set_selector(bebob->unit, 0x00, 0x04, dig_in_iface);
if (err < 0)
dev_err(&bebob->unit->device,
"fail to set digital input interface: %d\n", err);
err = 1;
end:
special_stream_formation_set(bebob);
mutex_unlock(&bebob->mutex);
return err;
}
static struct snd_kcontrol_new special_dig_in_iface_ctl = {
.name = "Digital Input Interface",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = special_dig_in_iface_ctl_info,
.get = special_dig_in_iface_ctl_get,
.put = special_dig_in_iface_ctl_set
};
/* Digital output interface control for special firmware */
static char *const special_dig_out_iface_labels[] = {
"S/PDIF Optical and Coaxial", "ADAT Optical"
};
static int special_dig_out_iface_ctl_info(struct snd_kcontrol *kctl,
struct snd_ctl_elem_info *einf)
{
einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
einf->count = 1;
einf->value.enumerated.items = ARRAY_SIZE(special_dig_out_iface_labels);
if (einf->value.enumerated.item >= einf->value.enumerated.items)
einf->value.enumerated.item = einf->value.enumerated.items - 1;
strcpy(einf->value.enumerated.name,
special_dig_out_iface_labels[einf->value.enumerated.item]);
return 0;
}
static int special_dig_out_iface_ctl_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uval)
{
struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
struct special_params *params = bebob->maudio_special_quirk;
mutex_lock(&bebob->mutex);
uval->value.enumerated.item[0] = params->dig_out_fmt;
mutex_unlock(&bebob->mutex);
return 0;
}
static int special_dig_out_iface_ctl_set(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uval)
{
struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
struct special_params *params = bebob->maudio_special_quirk;
unsigned int id;
int err;
id = uval->value.enumerated.item[0];
if (id >= ARRAY_SIZE(special_dig_out_iface_labels))
return -EINVAL;
mutex_lock(&bebob->mutex);
err = avc_maudio_set_special_clk(bebob,
params->clk_src,
params->dig_in_fmt,
id, params->clk_lock);
if (err >= 0) {
special_stream_formation_set(bebob);
err = 1;
}
mutex_unlock(&bebob->mutex);
return err;
}
static struct snd_kcontrol_new special_dig_out_iface_ctl = {
.name = "Digital Output Interface",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = special_dig_out_iface_ctl_info,
.get = special_dig_out_iface_ctl_get,
.put = special_dig_out_iface_ctl_set
};
static int add_special_controls(struct snd_bebob *bebob)
{
struct snd_kcontrol *kctl;
struct special_params *params = bebob->maudio_special_quirk;
int err;
kctl = snd_ctl_new1(&special_clk_ctl, bebob);
err = snd_ctl_add(bebob->card, kctl);
if (err < 0)
goto end;
kctl = snd_ctl_new1(&special_sync_ctl, bebob);
err = snd_ctl_add(bebob->card, kctl);
if (err < 0)
goto end;
params->ctl_id_sync = &kctl->id;
kctl = snd_ctl_new1(&special_dig_in_iface_ctl, bebob);
err = snd_ctl_add(bebob->card, kctl);
if (err < 0)
goto end;
kctl = snd_ctl_new1(&special_dig_out_iface_ctl, bebob);
err = snd_ctl_add(bebob->card, kctl);
end:
return err;
}
/* Hardware metering for special firmware */
static char *const special_meter_labels[] = {
ANA_IN, ANA_IN, ANA_IN, ANA_IN,
SPDIF_IN,
ADAT_IN, ADAT_IN, ADAT_IN, ADAT_IN,
ANA_OUT, ANA_OUT,
SPDIF_OUT,
ADAT_OUT, ADAT_OUT, ADAT_OUT, ADAT_OUT,
HP_OUT, HP_OUT,
AUX_OUT
};
static int
special_meter_get(struct snd_bebob *bebob, u32 *target, unsigned int size)
{
u16 *buf;
unsigned int i, c, channels;
int err;
channels = ARRAY_SIZE(special_meter_labels) * 2;
if (size < channels * sizeof(u32))
return -EINVAL;
/* omit last 4 bytes because it's clock info. */
buf = kmalloc(METER_SIZE_SPECIAL - 4, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
err = get_meter(bebob, (void *)buf, METER_SIZE_SPECIAL - 4);
if (err < 0)
goto end;
/* Its format is u16 and some channels are unknown. */
i = 0;
for (c = 2; c < channels + 2; c++)
target[i++] = be16_to_cpu(buf[c]) << 16;
end:
kfree(buf);
return err;
}
/* last 4 bytes are omitted because it's clock info. */
static char *const fw410_meter_labels[] = {
ANA_IN, DIG_IN,
ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, DIG_OUT,
HP_OUT
};
static char *const audiophile_meter_labels[] = {
ANA_IN, DIG_IN,
ANA_OUT, ANA_OUT, DIG_OUT,
HP_OUT, AUX_OUT,
};
static char *const solo_meter_labels[] = {
ANA_IN, DIG_IN,
STRM_IN, STRM_IN,
ANA_OUT, DIG_OUT
};
/* no clock info */
static char *const ozonic_meter_labels[] = {
ANA_IN, ANA_IN,
STRM_IN, STRM_IN,
ANA_OUT, ANA_OUT
};
/* TODO: need testers. these positions are based on authour's assumption */
static char *const nrv10_meter_labels[] = {
ANA_IN, ANA_IN, ANA_IN, ANA_IN,
DIG_IN,
ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT,
DIG_IN
};
static int
normal_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size)
{
struct snd_bebob_meter_spec *spec = bebob->spec->meter;
unsigned int c, channels;
int err;
channels = spec->num * 2;
if (size < channels * sizeof(u32))
return -EINVAL;
err = get_meter(bebob, (void *)buf, size);
if (err < 0)
goto end;
for (c = 0; c < channels; c++)
be32_to_cpus(&buf[c]);
/* swap stream channels because inverted */
if (spec->labels == solo_meter_labels) {
swap(buf[4], buf[6]);
swap(buf[5], buf[7]);
}
end:
return err;
}
/* for special customized devices */
static struct snd_bebob_rate_spec special_rate_spec = {
.get = &special_get_rate,
.set = &special_set_rate,
};
static struct snd_bebob_clock_spec special_clk_spec = {
.num = ARRAY_SIZE(special_clk_labels),
.labels = special_clk_labels,
.get = &special_clk_get,
};
static struct snd_bebob_meter_spec special_meter_spec = {
.num = ARRAY_SIZE(special_meter_labels),
.labels = special_meter_labels,
.get = &special_meter_get
};
struct snd_bebob_spec maudio_special_spec = {
.clock = &special_clk_spec,
.rate = &special_rate_spec,
.meter = &special_meter_spec
};
/* Firewire 410 specification */
static struct snd_bebob_rate_spec usual_rate_spec = {
.get = &snd_bebob_stream_get_rate,
.set = &snd_bebob_stream_set_rate,
};
static struct snd_bebob_meter_spec fw410_meter_spec = {
.num = ARRAY_SIZE(fw410_meter_labels),
.labels = fw410_meter_labels,
.get = &normal_meter_get
};
struct snd_bebob_spec maudio_fw410_spec = {
.clock = NULL,
.rate = &usual_rate_spec,
.meter = &fw410_meter_spec
};
/* Firewire Audiophile specification */
static struct snd_bebob_meter_spec audiophile_meter_spec = {
.num = ARRAY_SIZE(audiophile_meter_labels),
.labels = audiophile_meter_labels,
.get = &normal_meter_get
};
struct snd_bebob_spec maudio_audiophile_spec = {
.clock = NULL,
.rate = &usual_rate_spec,
.meter = &audiophile_meter_spec
};
/* Firewire Solo specification */
static struct snd_bebob_meter_spec solo_meter_spec = {
.num = ARRAY_SIZE(solo_meter_labels),
.labels = solo_meter_labels,
.get = &normal_meter_get
};
struct snd_bebob_spec maudio_solo_spec = {
.clock = NULL,
.rate = &usual_rate_spec,
.meter = &solo_meter_spec
};
/* Ozonic specification */
static struct snd_bebob_meter_spec ozonic_meter_spec = {
.num = ARRAY_SIZE(ozonic_meter_labels),
.labels = ozonic_meter_labels,
.get = &normal_meter_get
};
struct snd_bebob_spec maudio_ozonic_spec = {
.clock = NULL,
.rate = &usual_rate_spec,
.meter = &ozonic_meter_spec
};
/* NRV10 specification */
static struct snd_bebob_meter_spec nrv10_meter_spec = {
.num = ARRAY_SIZE(nrv10_meter_labels),
.labels = nrv10_meter_labels,
.get = &normal_meter_get
};
struct snd_bebob_spec maudio_nrv10_spec = {
.clock = NULL,
.rate = &usual_rate_spec,
.meter = &nrv10_meter_spec
};

View file

@ -0,0 +1,168 @@
/*
* bebob_midi.c - a part of driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "bebob.h"
static int midi_capture_open(struct snd_rawmidi_substream *substream)
{
struct snd_bebob *bebob = substream->rmidi->private_data;
int err;
err = snd_bebob_stream_lock_try(bebob);
if (err < 0)
goto end;
atomic_inc(&bebob->capture_substreams);
err = snd_bebob_stream_start_duplex(bebob, 0);
if (err < 0)
snd_bebob_stream_lock_release(bebob);
end:
return err;
}
static int midi_playback_open(struct snd_rawmidi_substream *substream)
{
struct snd_bebob *bebob = substream->rmidi->private_data;
int err;
err = snd_bebob_stream_lock_try(bebob);
if (err < 0)
goto end;
atomic_inc(&bebob->playback_substreams);
err = snd_bebob_stream_start_duplex(bebob, 0);
if (err < 0)
snd_bebob_stream_lock_release(bebob);
end:
return err;
}
static int midi_capture_close(struct snd_rawmidi_substream *substream)
{
struct snd_bebob *bebob = substream->rmidi->private_data;
atomic_dec(&bebob->capture_substreams);
snd_bebob_stream_stop_duplex(bebob);
snd_bebob_stream_lock_release(bebob);
return 0;
}
static int midi_playback_close(struct snd_rawmidi_substream *substream)
{
struct snd_bebob *bebob = substream->rmidi->private_data;
atomic_dec(&bebob->playback_substreams);
snd_bebob_stream_stop_duplex(bebob);
snd_bebob_stream_lock_release(bebob);
return 0;
}
static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
{
struct snd_bebob *bebob = substrm->rmidi->private_data;
unsigned long flags;
spin_lock_irqsave(&bebob->lock, flags);
if (up)
amdtp_stream_midi_trigger(&bebob->tx_stream,
substrm->number, substrm);
else
amdtp_stream_midi_trigger(&bebob->tx_stream,
substrm->number, NULL);
spin_unlock_irqrestore(&bebob->lock, flags);
}
static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up)
{
struct snd_bebob *bebob = substrm->rmidi->private_data;
unsigned long flags;
spin_lock_irqsave(&bebob->lock, flags);
if (up)
amdtp_stream_midi_trigger(&bebob->rx_stream,
substrm->number, substrm);
else
amdtp_stream_midi_trigger(&bebob->rx_stream,
substrm->number, NULL);
spin_unlock_irqrestore(&bebob->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_bebob *bebob,
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",
bebob->card->shortname, subs->number + 1);
}
}
int snd_bebob_create_midi_devices(struct snd_bebob *bebob)
{
struct snd_rawmidi *rmidi;
struct snd_rawmidi_str *str;
int err;
/* create midi ports */
err = snd_rawmidi_new(bebob->card, bebob->card->driver, 0,
bebob->midi_output_ports, bebob->midi_input_ports,
&rmidi);
if (err < 0)
return err;
snprintf(rmidi->name, sizeof(rmidi->name),
"%s MIDI", bebob->card->shortname);
rmidi->private_data = bebob;
if (bebob->midi_input_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(bebob, str);
}
if (bebob->midi_output_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(bebob, str);
}
if ((bebob->midi_output_ports > 0) && (bebob->midi_input_ports > 0))
rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX;
return 0;
}

View file

@ -0,0 +1,378 @@
/*
* bebob_pcm.c - a part of driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "./bebob.h"
static int
hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule)
{
struct snd_bebob_stream_formation *formations = 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;
for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) {
/* entry is invalid */
if (formations[i].pcm == 0)
continue;
if (!snd_interval_test(c, formations[i].pcm))
continue;
t.min = min(t.min, snd_bebob_rate_table[i]);
t.max = max(t.max, snd_bebob_rate_table[i]);
}
return snd_interval_refine(r, &t);
}
static int
hw_rule_channels(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule)
{
struct snd_bebob_stream_formation *formations = 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;
for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) {
/* entry is invalid */
if (formations[i].pcm == 0)
continue;
if (!snd_interval_test(r, snd_bebob_rate_table[i]))
continue;
t.min = min(t.min, formations[i].pcm);
t.max = max(t.max, formations[i].pcm);
}
return snd_interval_refine(c, &t);
}
static void
limit_channels_and_rates(struct snd_pcm_hardware *hw,
struct snd_bebob_stream_formation *formations)
{
unsigned int i;
hw->channels_min = UINT_MAX;
hw->channels_max = 0;
hw->rate_min = UINT_MAX;
hw->rate_max = 0;
hw->rates = 0;
for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) {
/* entry has no PCM channels */
if (formations[i].pcm == 0)
continue;
hw->channels_min = min(hw->channels_min, formations[i].pcm);
hw->channels_max = max(hw->channels_max, formations[i].pcm);
hw->rate_min = min(hw->rate_min, snd_bebob_rate_table[i]);
hw->rate_max = max(hw->rate_max, snd_bebob_rate_table[i]);
hw->rates |= snd_pcm_rate_to_rate_bit(snd_bebob_rate_table[i]);
}
}
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_bebob *bebob,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct amdtp_stream *s;
struct snd_bebob_stream_formation *formations;
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 = &bebob->tx_stream;
formations = bebob->tx_stream_formations;
} else {
runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS;
s = &bebob->rx_stream;
formations = bebob->rx_stream_formations;
}
limit_channels_and_rates(&runtime->hw, formations);
limit_period_and_buffer(&runtime->hw);
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
hw_rule_channels, formations,
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, formations,
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_bebob *bebob = substream->private_data;
struct snd_bebob_rate_spec *spec = bebob->spec->rate;
unsigned int sampling_rate;
bool internal;
int err;
err = snd_bebob_stream_lock_try(bebob);
if (err < 0)
goto end;
err = pcm_init_hw_params(bebob, substream);
if (err < 0)
goto err_locked;
err = snd_bebob_stream_check_internal_clock(bebob, &internal);
if (err < 0)
goto err_locked;
/*
* When source of clock is internal or any PCM stream are running,
* the available sampling rate is limited at current sampling rate.
*/
if (!internal ||
amdtp_stream_pcm_running(&bebob->tx_stream) ||
amdtp_stream_pcm_running(&bebob->rx_stream)) {
err = spec->get(bebob, &sampling_rate);
if (err < 0) {
dev_err(&bebob->unit->device,
"fail to get sampling rate: %d\n", err);
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_bebob_stream_lock_release(bebob);
return err;
}
static int
pcm_close(struct snd_pcm_substream *substream)
{
struct snd_bebob *bebob = substream->private_data;
snd_bebob_stream_lock_release(bebob);
return 0;
}
static int
pcm_capture_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_bebob *bebob = substream->private_data;
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN)
atomic_inc(&bebob->capture_substreams);
amdtp_stream_set_pcm_format(&bebob->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_bebob *bebob = substream->private_data;
if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN)
atomic_inc(&bebob->playback_substreams);
amdtp_stream_set_pcm_format(&bebob->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_bebob *bebob = substream->private_data;
if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
atomic_dec(&bebob->capture_substreams);
snd_bebob_stream_stop_duplex(bebob);
return snd_pcm_lib_free_vmalloc_buffer(substream);
}
static int
pcm_playback_hw_free(struct snd_pcm_substream *substream)
{
struct snd_bebob *bebob = substream->private_data;
if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
atomic_dec(&bebob->playback_substreams);
snd_bebob_stream_stop_duplex(bebob);
return snd_pcm_lib_free_vmalloc_buffer(substream);
}
static int
pcm_capture_prepare(struct snd_pcm_substream *substream)
{
struct snd_bebob *bebob = substream->private_data;
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
err = snd_bebob_stream_start_duplex(bebob, runtime->rate);
if (err >= 0)
amdtp_stream_pcm_prepare(&bebob->tx_stream);
return err;
}
static int
pcm_playback_prepare(struct snd_pcm_substream *substream)
{
struct snd_bebob *bebob = substream->private_data;
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
err = snd_bebob_stream_start_duplex(bebob, runtime->rate);
if (err >= 0)
amdtp_stream_pcm_prepare(&bebob->rx_stream);
return err;
}
static int
pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_bebob *bebob = substream->private_data;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
amdtp_stream_pcm_trigger(&bebob->tx_stream, substream);
break;
case SNDRV_PCM_TRIGGER_STOP:
amdtp_stream_pcm_trigger(&bebob->tx_stream, NULL);
break;
default:
return -EINVAL;
}
return 0;
}
static int
pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct snd_bebob *bebob = substream->private_data;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
amdtp_stream_pcm_trigger(&bebob->rx_stream, substream);
break;
case SNDRV_PCM_TRIGGER_STOP:
amdtp_stream_pcm_trigger(&bebob->rx_stream, NULL);
break;
default:
return -EINVAL;
}
return 0;
}
static snd_pcm_uframes_t
pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
{
struct snd_bebob *bebob = sbstrm->private_data;
return amdtp_stream_pcm_pointer(&bebob->tx_stream);
}
static snd_pcm_uframes_t
pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
{
struct snd_bebob *bebob = sbstrm->private_data;
return amdtp_stream_pcm_pointer(&bebob->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_bebob_create_pcm_devices(struct snd_bebob *bebob)
{
struct snd_pcm *pcm;
int err;
err = snd_pcm_new(bebob->card, bebob->card->driver, 0, 1, 1, &pcm);
if (err < 0)
goto end;
pcm->private_data = bebob;
snprintf(pcm->name, sizeof(pcm->name),
"%s PCM", bebob->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,196 @@
/*
* bebob_proc.c - a part of driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "./bebob.h"
/* contents of information register */
struct hw_info {
u64 manufacturer;
u32 protocol_ver;
u32 bld_ver;
u32 guid[2];
u32 model_id;
u32 model_rev;
u64 fw_date;
u64 fw_time;
u32 fw_id;
u32 fw_ver;
u32 base_addr;
u32 max_size;
u64 bld_date;
u64 bld_time;
/* may not used in product
u64 dbg_date;
u64 dbg_time;
u32 dbg_id;
u32 dbg_version;
*/
} __packed;
static void
proc_read_hw_info(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_bebob *bebob = entry->private_data;
struct hw_info *info;
info = kzalloc(sizeof(struct hw_info), GFP_KERNEL);
if (info == NULL)
return;
if (snd_bebob_read_block(bebob->unit, 0,
info, sizeof(struct hw_info)) < 0)
goto end;
snd_iprintf(buffer, "Manufacturer:\t%.8s\n",
(char *)&info->manufacturer);
snd_iprintf(buffer, "Protocol Ver:\t%d\n", info->protocol_ver);
snd_iprintf(buffer, "Build Ver:\t%d\n", info->bld_ver);
snd_iprintf(buffer, "GUID:\t\t0x%.8X%.8X\n",
info->guid[0], info->guid[1]);
snd_iprintf(buffer, "Model ID:\t0x%02X\n", info->model_id);
snd_iprintf(buffer, "Model Rev:\t%d\n", info->model_rev);
snd_iprintf(buffer, "Firmware Date:\t%.8s\n", (char *)&info->fw_date);
snd_iprintf(buffer, "Firmware Time:\t%.8s\n", (char *)&info->fw_time);
snd_iprintf(buffer, "Firmware ID:\t0x%X\n", info->fw_id);
snd_iprintf(buffer, "Firmware Ver:\t%d\n", info->fw_ver);
snd_iprintf(buffer, "Base Addr:\t0x%X\n", info->base_addr);
snd_iprintf(buffer, "Max Size:\t%d\n", info->max_size);
snd_iprintf(buffer, "Loader Date:\t%.8s\n", (char *)&info->bld_date);
snd_iprintf(buffer, "Loader Time:\t%.8s\n", (char *)&info->bld_time);
end:
kfree(info);
}
static void
proc_read_meters(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_bebob *bebob = entry->private_data;
struct snd_bebob_meter_spec *spec = bebob->spec->meter;
u32 *buf;
unsigned int i, c, channels, size;
if (spec == NULL)
return;
channels = spec->num * 2;
size = channels * sizeof(u32);
buf = kmalloc(size, GFP_KERNEL);
if (buf == NULL)
return;
if (spec->get(bebob, buf, size) < 0)
goto end;
for (i = 0, c = 1; i < channels; i++) {
snd_iprintf(buffer, "%s %d:\t%d\n",
spec->labels[i / 2], c++, buf[i]);
if ((i + 1 < channels - 1) &&
(strcmp(spec->labels[i / 2],
spec->labels[(i + 1) / 2]) != 0))
c = 1;
}
end:
kfree(buf);
}
static void
proc_read_formation(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_bebob *bebob = entry->private_data;
struct snd_bebob_stream_formation *formation;
unsigned int i;
snd_iprintf(buffer, "Output Stream from device:\n");
snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n");
formation = bebob->tx_stream_formations;
for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) {
snd_iprintf(buffer,
"\t%d\t%d\t%d\n", snd_bebob_rate_table[i],
formation[i].pcm, formation[i].midi);
}
snd_iprintf(buffer, "Input Stream to device:\n");
snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n");
formation = bebob->rx_stream_formations;
for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) {
snd_iprintf(buffer,
"\t%d\t%d\t%d\n", snd_bebob_rate_table[i],
formation[i].pcm, formation[i].midi);
}
}
static void
proc_read_clock(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_bebob *bebob = entry->private_data;
struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate;
struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
unsigned int rate, id;
bool internal;
if (rate_spec->get(bebob, &rate) >= 0)
snd_iprintf(buffer, "Sampling rate: %d\n", rate);
if (clk_spec) {
if (clk_spec->get(bebob, &id) >= 0)
snd_iprintf(buffer, "Clock Source: %s\n",
clk_spec->labels[id]);
} else {
if (snd_bebob_stream_check_internal_clock(bebob,
&internal) >= 0)
snd_iprintf(buffer, "Clock Source: %s (MSU-dest: %d)\n",
(internal) ? "Internal" : "External",
bebob->sync_input_plug);
}
}
static void
add_node(struct snd_bebob *bebob, 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(bebob->card, name, root);
if (entry == NULL)
return;
snd_info_set_text_ops(entry, bebob, op);
if (snd_info_register(entry) < 0)
snd_info_free_entry(entry);
}
void snd_bebob_proc_init(struct snd_bebob *bebob)
{
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(bebob->card, "firewire",
bebob->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(bebob, root, "clock", proc_read_clock);
add_node(bebob, root, "firmware", proc_read_hw_info);
add_node(bebob, root, "formation", proc_read_formation);
if (bebob->spec->meter != NULL)
add_node(bebob, root, "meter", proc_read_meters);
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,73 @@
/*
* bebob_terratec.c - a part of driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "./bebob.h"
static char *const phase88_rack_clk_src_labels[] = {
SND_BEBOB_CLOCK_INTERNAL, "Digital In", "Word Clock"
};
static int
phase88_rack_clk_src_get(struct snd_bebob *bebob, unsigned int *id)
{
unsigned int enable_ext, enable_word;
int err;
err = avc_audio_get_selector(bebob->unit, 0, 9, &enable_ext);
if (err < 0)
goto end;
err = avc_audio_get_selector(bebob->unit, 0, 8, &enable_word);
if (err < 0)
goto end;
if (enable_ext == 0)
*id = 0;
else if (enable_word == 0)
*id = 1;
else
*id = 2;
end:
return err;
}
static char *const phase24_series_clk_src_labels[] = {
SND_BEBOB_CLOCK_INTERNAL, "Digital In"
};
static int
phase24_series_clk_src_get(struct snd_bebob *bebob, unsigned int *id)
{
return avc_audio_get_selector(bebob->unit, 0, 4, id);
}
static struct snd_bebob_rate_spec phase_series_rate_spec = {
.get = &snd_bebob_stream_get_rate,
.set = &snd_bebob_stream_set_rate,
};
/* PHASE 88 Rack FW */
static struct snd_bebob_clock_spec phase88_rack_clk = {
.num = ARRAY_SIZE(phase88_rack_clk_src_labels),
.labels = phase88_rack_clk_src_labels,
.get = &phase88_rack_clk_src_get,
};
struct snd_bebob_spec phase88_rack_spec = {
.clock = &phase88_rack_clk,
.rate = &phase_series_rate_spec,
.meter = NULL
};
/* 'PHASE 24 FW' and 'PHASE X24 FW' */
static struct snd_bebob_clock_spec phase24_series_clk = {
.num = ARRAY_SIZE(phase24_series_clk_src_labels),
.labels = phase24_series_clk_src_labels,
.get = &phase24_series_clk_src_get,
};
struct snd_bebob_spec phase24_series_spec = {
.clock = &phase24_series_clk,
.rate = &phase_series_rate_spec,
.meter = NULL
};

View file

@ -0,0 +1,50 @@
/*
* bebob_yamaha.c - a part of driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "./bebob.h"
/*
* NOTE:
* Yamaha GO44 is not designed to be used as stand-alone mixer. So any streams
* must be accompanied. If changing the state, a LED on the device starts to
* blink and its sync status is false. In this state, the device sounds nothing
* even if streaming. To start streaming at the current sampling rate is only
* way to revocer this state. GO46 is better for stand-alone mixer.
*
* Both of them have a capability to change its sampling rate up to 192.0kHz.
* At 192.0kHz, the device reports 4 PCM-in, 1 MIDI-in, 6 PCM-out, 1 MIDI-out.
* But Yamaha's driver reduce 2 PCM-in, 1 MIDI-in, 2 PCM-out, 1 MIDI-out to use
* 'Extended Stream Format Information Command - Single Request' in 'Additional
* AVC commands' defined by BridgeCo.
* This ALSA driver don't do this because a bit tiresome. Then isochronous
* streaming with many asynchronous transactions brings sounds with noises.
* Unfortunately current 'ffado-mixer' generated many asynchronous transaction
* to observe device's state, mainly check cmp connection and signal format. I
* reccomend users to close ffado-mixer at 192.0kHz if mixer is needless.
*/
static char *const clk_src_labels[] = {SND_BEBOB_CLOCK_INTERNAL, "SPDIF"};
static int
clk_src_get(struct snd_bebob *bebob, unsigned int *id)
{
return avc_audio_get_selector(bebob->unit, 0, 4, id);
}
static struct snd_bebob_clock_spec clock_spec = {
.num = ARRAY_SIZE(clk_src_labels),
.labels = clk_src_labels,
.get = &clk_src_get,
};
static struct snd_bebob_rate_spec rate_spec = {
.get = &snd_bebob_stream_get_rate,
.set = &snd_bebob_stream_set_rate,
};
struct snd_bebob_spec yamaha_go_spec = {
.clock = &clock_spec,
.rate = &rate_spec,
.meter = NULL
};

402
sound/firewire/cmp.c Normal file
View file

@ -0,0 +1,402 @@
/*
* Connection Management Procedures (IEC 61883-1) helper functions
*
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include <linux/device.h>
#include <linux/firewire.h>
#include <linux/firewire-constants.h>
#include <linux/module.h>
#include <linux/sched.h>
#include "lib.h"
#include "iso-resources.h"
#include "cmp.h"
/* MPR common fields */
#define MPR_SPEED_MASK 0xc0000000
#define MPR_SPEED_SHIFT 30
#define MPR_XSPEED_MASK 0x00000060
#define MPR_XSPEED_SHIFT 5
#define MPR_PLUGS_MASK 0x0000001f
/* PCR common fields */
#define PCR_ONLINE 0x80000000
#define PCR_BCAST_CONN 0x40000000
#define PCR_P2P_CONN_MASK 0x3f000000
#define PCR_P2P_CONN_SHIFT 24
#define PCR_CHANNEL_MASK 0x003f0000
#define PCR_CHANNEL_SHIFT 16
/* oPCR specific fields */
#define OPCR_XSPEED_MASK 0x00C00000
#define OPCR_XSPEED_SHIFT 22
#define OPCR_SPEED_MASK 0x0000C000
#define OPCR_SPEED_SHIFT 14
#define OPCR_OVERHEAD_ID_MASK 0x00003C00
#define OPCR_OVERHEAD_ID_SHIFT 10
enum bus_reset_handling {
ABORT_ON_BUS_RESET,
SUCCEED_ON_BUS_RESET,
};
static __printf(2, 3)
void cmp_error(struct cmp_connection *c, const char *fmt, ...)
{
va_list va;
va_start(va, fmt);
dev_err(&c->resources.unit->device, "%cPCR%u: %pV",
(c->direction == CMP_INPUT) ? 'i' : 'o',
c->pcr_index, &(struct va_format){ fmt, &va });
va_end(va);
}
static u64 mpr_address(struct cmp_connection *c)
{
if (c->direction == CMP_INPUT)
return CSR_REGISTER_BASE + CSR_IMPR;
else
return CSR_REGISTER_BASE + CSR_OMPR;
}
static u64 pcr_address(struct cmp_connection *c)
{
if (c->direction == CMP_INPUT)
return CSR_REGISTER_BASE + CSR_IPCR(c->pcr_index);
else
return CSR_REGISTER_BASE + CSR_OPCR(c->pcr_index);
}
static int pcr_modify(struct cmp_connection *c,
__be32 (*modify)(struct cmp_connection *c, __be32 old),
int (*check)(struct cmp_connection *c, __be32 pcr),
enum bus_reset_handling bus_reset_handling)
{
__be32 old_arg, buffer[2];
int err;
buffer[0] = c->last_pcr_value;
for (;;) {
old_arg = buffer[0];
buffer[1] = modify(c, buffer[0]);
err = snd_fw_transaction(
c->resources.unit, TCODE_LOCK_COMPARE_SWAP,
pcr_address(c), buffer, 8,
FW_FIXED_GENERATION | c->resources.generation);
if (err < 0) {
if (err == -EAGAIN &&
bus_reset_handling == SUCCEED_ON_BUS_RESET)
err = 0;
return err;
}
if (buffer[0] == old_arg) /* success? */
break;
if (check) {
err = check(c, buffer[0]);
if (err < 0)
return err;
}
}
c->last_pcr_value = buffer[1];
return 0;
}
/**
* cmp_connection_init - initializes a connection manager
* @c: the connection manager to initialize
* @unit: a unit of the target device
* @pcr_index: the index of the iPCR/oPCR on the target device
*/
int cmp_connection_init(struct cmp_connection *c,
struct fw_unit *unit,
enum cmp_direction direction,
unsigned int pcr_index)
{
__be32 mpr_be;
u32 mpr;
int err;
c->direction = direction;
err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST,
mpr_address(c), &mpr_be, 4, 0);
if (err < 0)
return err;
mpr = be32_to_cpu(mpr_be);
if (pcr_index >= (mpr & MPR_PLUGS_MASK))
return -EINVAL;
err = fw_iso_resources_init(&c->resources, unit);
if (err < 0)
return err;
c->connected = false;
mutex_init(&c->mutex);
c->last_pcr_value = cpu_to_be32(0x80000000);
c->pcr_index = pcr_index;
c->max_speed = (mpr & MPR_SPEED_MASK) >> MPR_SPEED_SHIFT;
if (c->max_speed == SCODE_BETA)
c->max_speed += (mpr & MPR_XSPEED_MASK) >> MPR_XSPEED_SHIFT;
return 0;
}
EXPORT_SYMBOL(cmp_connection_init);
/**
* cmp_connection_check_used - check connection is already esablished or not
* @c: the connection manager to be checked
*/
int cmp_connection_check_used(struct cmp_connection *c, bool *used)
{
__be32 pcr;
int err;
err = snd_fw_transaction(
c->resources.unit, TCODE_READ_QUADLET_REQUEST,
pcr_address(c), &pcr, 4, 0);
if (err >= 0)
*used = !!(pcr & cpu_to_be32(PCR_BCAST_CONN |
PCR_P2P_CONN_MASK));
return err;
}
EXPORT_SYMBOL(cmp_connection_check_used);
/**
* cmp_connection_destroy - free connection manager resources
* @c: the connection manager
*/
void cmp_connection_destroy(struct cmp_connection *c)
{
WARN_ON(c->connected);
mutex_destroy(&c->mutex);
fw_iso_resources_destroy(&c->resources);
}
EXPORT_SYMBOL(cmp_connection_destroy);
static __be32 ipcr_set_modify(struct cmp_connection *c, __be32 ipcr)
{
ipcr &= ~cpu_to_be32(PCR_BCAST_CONN |
PCR_P2P_CONN_MASK |
PCR_CHANNEL_MASK);
ipcr |= cpu_to_be32(1 << PCR_P2P_CONN_SHIFT);
ipcr |= cpu_to_be32(c->resources.channel << PCR_CHANNEL_SHIFT);
return ipcr;
}
static int get_overhead_id(struct cmp_connection *c)
{
int id;
/*
* apply "oPCR overhead ID encoding"
* the encoding table can convert up to 512.
* here the value over 512 is converted as the same way as 512.
*/
for (id = 1; id < 16; id++) {
if (c->resources.bandwidth_overhead < (id << 5))
break;
}
if (id == 16)
id = 0;
return id;
}
static __be32 opcr_set_modify(struct cmp_connection *c, __be32 opcr)
{
unsigned int spd, xspd;
/* generate speed and extended speed field value */
if (c->speed > SCODE_400) {
spd = SCODE_800;
xspd = c->speed - SCODE_800;
} else {
spd = c->speed;
xspd = 0;
}
opcr &= ~cpu_to_be32(PCR_BCAST_CONN |
PCR_P2P_CONN_MASK |
OPCR_XSPEED_MASK |
PCR_CHANNEL_MASK |
OPCR_SPEED_MASK |
OPCR_OVERHEAD_ID_MASK);
opcr |= cpu_to_be32(1 << PCR_P2P_CONN_SHIFT);
opcr |= cpu_to_be32(xspd << OPCR_XSPEED_SHIFT);
opcr |= cpu_to_be32(c->resources.channel << PCR_CHANNEL_SHIFT);
opcr |= cpu_to_be32(spd << OPCR_SPEED_SHIFT);
opcr |= cpu_to_be32(get_overhead_id(c) << OPCR_OVERHEAD_ID_SHIFT);
return opcr;
}
static int pcr_set_check(struct cmp_connection *c, __be32 pcr)
{
if (pcr & cpu_to_be32(PCR_BCAST_CONN |
PCR_P2P_CONN_MASK)) {
cmp_error(c, "plug is already in use\n");
return -EBUSY;
}
if (!(pcr & cpu_to_be32(PCR_ONLINE))) {
cmp_error(c, "plug is not on-line\n");
return -ECONNREFUSED;
}
return 0;
}
/**
* cmp_connection_establish - establish a connection to the target
* @c: the connection manager
* @max_payload_bytes: the amount of data (including CIP headers) per packet
*
* This function establishes a point-to-point connection from the local
* computer to the target by allocating isochronous resources (channel and
* bandwidth) and setting the target's input/output plug control register.
* When this function succeeds, the caller is responsible for starting
* transmitting packets.
*/
int cmp_connection_establish(struct cmp_connection *c,
unsigned int max_payload_bytes)
{
int err;
if (WARN_ON(c->connected))
return -EISCONN;
c->speed = min(c->max_speed,
fw_parent_device(c->resources.unit)->max_speed);
mutex_lock(&c->mutex);
retry_after_bus_reset:
err = fw_iso_resources_allocate(&c->resources,
max_payload_bytes, c->speed);
if (err < 0)
goto err_mutex;
if (c->direction == CMP_OUTPUT)
err = pcr_modify(c, opcr_set_modify, pcr_set_check,
ABORT_ON_BUS_RESET);
else
err = pcr_modify(c, ipcr_set_modify, pcr_set_check,
ABORT_ON_BUS_RESET);
if (err == -EAGAIN) {
fw_iso_resources_free(&c->resources);
goto retry_after_bus_reset;
}
if (err < 0)
goto err_resources;
c->connected = true;
mutex_unlock(&c->mutex);
return 0;
err_resources:
fw_iso_resources_free(&c->resources);
err_mutex:
mutex_unlock(&c->mutex);
return err;
}
EXPORT_SYMBOL(cmp_connection_establish);
/**
* cmp_connection_update - update the connection after a bus reset
* @c: the connection manager
*
* This function must be called from the driver's .update handler to
* reestablish any connection that might have been active.
*
* Returns zero on success, or a negative error code. On an error, the
* connection is broken and the caller must stop transmitting iso packets.
*/
int cmp_connection_update(struct cmp_connection *c)
{
int err;
mutex_lock(&c->mutex);
if (!c->connected) {
mutex_unlock(&c->mutex);
return 0;
}
err = fw_iso_resources_update(&c->resources);
if (err < 0)
goto err_unconnect;
if (c->direction == CMP_OUTPUT)
err = pcr_modify(c, opcr_set_modify, pcr_set_check,
SUCCEED_ON_BUS_RESET);
else
err = pcr_modify(c, ipcr_set_modify, pcr_set_check,
SUCCEED_ON_BUS_RESET);
if (err < 0)
goto err_resources;
mutex_unlock(&c->mutex);
return 0;
err_resources:
fw_iso_resources_free(&c->resources);
err_unconnect:
c->connected = false;
mutex_unlock(&c->mutex);
return err;
}
EXPORT_SYMBOL(cmp_connection_update);
static __be32 pcr_break_modify(struct cmp_connection *c, __be32 pcr)
{
return pcr & ~cpu_to_be32(PCR_BCAST_CONN | PCR_P2P_CONN_MASK);
}
/**
* cmp_connection_break - break the connection to the target
* @c: the connection manager
*
* This function deactives the connection in the target's input/output plug
* control register, and frees the isochronous resources of the connection.
* Before calling this function, the caller should cease transmitting packets.
*/
void cmp_connection_break(struct cmp_connection *c)
{
int err;
mutex_lock(&c->mutex);
if (!c->connected) {
mutex_unlock(&c->mutex);
return;
}
err = pcr_modify(c, pcr_break_modify, NULL, SUCCEED_ON_BUS_RESET);
if (err < 0)
cmp_error(c, "plug is still connected\n");
fw_iso_resources_free(&c->resources);
c->connected = false;
mutex_unlock(&c->mutex);
}
EXPORT_SYMBOL(cmp_connection_break);

49
sound/firewire/cmp.h Normal file
View file

@ -0,0 +1,49 @@
#ifndef SOUND_FIREWIRE_CMP_H_INCLUDED
#define SOUND_FIREWIRE_CMP_H_INCLUDED
#include <linux/mutex.h>
#include <linux/types.h>
#include "iso-resources.h"
struct fw_unit;
enum cmp_direction {
CMP_INPUT = 0,
CMP_OUTPUT,
};
/**
* struct cmp_connection - manages an isochronous connection to a device
* @speed: the connection's actual speed
*
* This structure manages (using CMP) an isochronous stream between the local
* computer and a device's input plug (iPCR) and output plug (oPCR).
*
* There is no corresponding oPCR created on the local computer, so it is not
* possible to overlay connections on top of this one.
*/
struct cmp_connection {
int speed;
/* private: */
bool connected;
struct mutex mutex;
struct fw_iso_resources resources;
__be32 last_pcr_value;
unsigned int pcr_index;
unsigned int max_speed;
enum cmp_direction direction;
};
int cmp_connection_init(struct cmp_connection *connection,
struct fw_unit *unit,
enum cmp_direction direction,
unsigned int pcr_index);
int cmp_connection_check_used(struct cmp_connection *connection, bool *used);
void cmp_connection_destroy(struct cmp_connection *connection);
int cmp_connection_establish(struct cmp_connection *connection,
unsigned int max_payload);
int cmp_connection_update(struct cmp_connection *connection);
void cmp_connection_break(struct cmp_connection *connection);
#endif

View file

@ -0,0 +1,371 @@
#ifndef SOUND_FIREWIRE_DICE_INTERFACE_H_INCLUDED
#define SOUND_FIREWIRE_DICE_INTERFACE_H_INCLUDED
/*
* DICE device interface definitions
*/
/*
* Generally, all registers can be read like memory, i.e., with quadlet read or
* block read transactions with at least quadlet-aligned offset and length.
* Writes are not allowed except where noted; quadlet-sized registers must be
* written with a quadlet write transaction.
*
* All values are in big endian. The DICE firmware runs on a little-endian CPU
* and just byte-swaps _all_ quadlets on the bus, so values without endianness
* (e.g. strings) get scrambled and must be byte-swapped again by the driver.
*/
/*
* Streaming is handled by the "DICE driver" interface. Its registers are
* located in this private address space.
*/
#define DICE_PRIVATE_SPACE 0xffffe0000000uLL
/*
* The registers are organized in several sections, which are organized
* separately to allow them to be extended individually. Whether a register is
* supported can be detected by checking its offset against its section's size.
*
* The section offset values are relative to DICE_PRIVATE_SPACE; the offset/
* size values are measured in quadlets. Read-only.
*/
#define DICE_GLOBAL_OFFSET 0x00
#define DICE_GLOBAL_SIZE 0x04
#define DICE_TX_OFFSET 0x08
#define DICE_TX_SIZE 0x0c
#define DICE_RX_OFFSET 0x10
#define DICE_RX_SIZE 0x14
#define DICE_EXT_SYNC_OFFSET 0x18
#define DICE_EXT_SYNC_SIZE 0x1c
#define DICE_UNUSED2_OFFSET 0x20
#define DICE_UNUSED2_SIZE 0x24
/*
* Global settings.
*/
/*
* Stores the full 64-bit address (node ID and offset in the node's address
* space) where the device will send notifications. Must be changed with
* a compare/swap transaction by the owner. This register is automatically
* cleared on a bus reset.
*/
#define GLOBAL_OWNER 0x000
#define OWNER_NO_OWNER 0xffff000000000000uLL
#define OWNER_NODE_SHIFT 48
/*
* A bitmask with asynchronous events; read-only. When any event(s) happen,
* the bits of previous events are cleared, and the value of this register is
* also written to the address stored in the owner register.
*/
#define GLOBAL_NOTIFICATION 0x008
/* Some registers in the Rx/Tx sections may have changed. */
#define NOTIFY_RX_CFG_CHG 0x00000001
#define NOTIFY_TX_CFG_CHG 0x00000002
/* Lock status of the current clock source may have changed. */
#define NOTIFY_LOCK_CHG 0x00000010
/* Write to the clock select register has been finished. */
#define NOTIFY_CLOCK_ACCEPTED 0x00000020
/* Lock status of some clock source has changed. */
#define NOTIFY_EXT_STATUS 0x00000040
/* Other bits may be used for device-specific events. */
/*
* A name that can be customized for each device; read/write. Padded with zero
* bytes. Quadlets are byte-swapped. The encoding is whatever the host driver
* happens to be using.
*/
#define GLOBAL_NICK_NAME 0x00c
#define NICK_NAME_SIZE 64
/*
* The current sample rate and clock source; read/write. Whether a clock
* source or sample rate is supported is device-specific; the internal clock
* source is always available. Low/mid/high = up to 48/96/192 kHz. This
* register can be changed even while streams are running.
*/
#define GLOBAL_CLOCK_SELECT 0x04c
#define CLOCK_SOURCE_MASK 0x000000ff
#define CLOCK_SOURCE_AES1 0x00000000
#define CLOCK_SOURCE_AES2 0x00000001
#define CLOCK_SOURCE_AES3 0x00000002
#define CLOCK_SOURCE_AES4 0x00000003
#define CLOCK_SOURCE_AES_ANY 0x00000004
#define CLOCK_SOURCE_ADAT 0x00000005
#define CLOCK_SOURCE_TDIF 0x00000006
#define CLOCK_SOURCE_WC 0x00000007
#define CLOCK_SOURCE_ARX1 0x00000008
#define CLOCK_SOURCE_ARX2 0x00000009
#define CLOCK_SOURCE_ARX3 0x0000000a
#define CLOCK_SOURCE_ARX4 0x0000000b
#define CLOCK_SOURCE_INTERNAL 0x0000000c
#define CLOCK_RATE_MASK 0x0000ff00
#define CLOCK_RATE_32000 0x00000000
#define CLOCK_RATE_44100 0x00000100
#define CLOCK_RATE_48000 0x00000200
#define CLOCK_RATE_88200 0x00000300
#define CLOCK_RATE_96000 0x00000400
#define CLOCK_RATE_176400 0x00000500
#define CLOCK_RATE_192000 0x00000600
#define CLOCK_RATE_ANY_LOW 0x00000700
#define CLOCK_RATE_ANY_MID 0x00000800
#define CLOCK_RATE_ANY_HIGH 0x00000900
#define CLOCK_RATE_NONE 0x00000a00
#define CLOCK_RATE_SHIFT 8
/*
* Enable streaming; read/write. Writing a non-zero value (re)starts all
* streams that have a valid iso channel set; zero stops all streams. The
* streams' parameters must be configured before starting. This register is
* automatically cleared on a bus reset.
*/
#define GLOBAL_ENABLE 0x050
/*
* Status of the sample clock; read-only.
*/
#define GLOBAL_STATUS 0x054
/* The current clock source is locked. */
#define STATUS_SOURCE_LOCKED 0x00000001
/* The actual sample rate; CLOCK_RATE_32000-_192000 or _NONE. */
#define STATUS_NOMINAL_RATE_MASK 0x0000ff00
/*
* Status of all clock sources; read-only.
*/
#define GLOBAL_EXTENDED_STATUS 0x058
/*
* The _LOCKED bits always show the current status; any change generates
* a notification.
*/
#define EXT_STATUS_AES1_LOCKED 0x00000001
#define EXT_STATUS_AES2_LOCKED 0x00000002
#define EXT_STATUS_AES3_LOCKED 0x00000004
#define EXT_STATUS_AES4_LOCKED 0x00000008
#define EXT_STATUS_ADAT_LOCKED 0x00000010
#define EXT_STATUS_TDIF_LOCKED 0x00000020
#define EXT_STATUS_ARX1_LOCKED 0x00000040
#define EXT_STATUS_ARX2_LOCKED 0x00000080
#define EXT_STATUS_ARX3_LOCKED 0x00000100
#define EXT_STATUS_ARX4_LOCKED 0x00000200
#define EXT_STATUS_WC_LOCKED 0x00000400
/*
* The _SLIP bits do not generate notifications; a set bit indicates that an
* error occurred since the last time when this register was read with
* a quadlet read transaction.
*/
#define EXT_STATUS_AES1_SLIP 0x00010000
#define EXT_STATUS_AES2_SLIP 0x00020000
#define EXT_STATUS_AES3_SLIP 0x00040000
#define EXT_STATUS_AES4_SLIP 0x00080000
#define EXT_STATUS_ADAT_SLIP 0x00100000
#define EXT_STATUS_TDIF_SLIP 0x00200000
#define EXT_STATUS_ARX1_SLIP 0x00400000
#define EXT_STATUS_ARX2_SLIP 0x00800000
#define EXT_STATUS_ARX3_SLIP 0x01000000
#define EXT_STATUS_ARX4_SLIP 0x02000000
#define EXT_STATUS_WC_SLIP 0x04000000
/*
* The measured rate of the current clock source, in Hz; read-only.
*/
#define GLOBAL_SAMPLE_RATE 0x05c
/*
* The version of the DICE driver specification that this device conforms to;
* read-only.
*/
#define GLOBAL_VERSION 0x060
/* Some old firmware versions do not have the following global registers: */
/*
* Supported sample rates and clock sources; read-only.
*/
#define GLOBAL_CLOCK_CAPABILITIES 0x064
#define CLOCK_CAP_RATE_32000 0x00000001
#define CLOCK_CAP_RATE_44100 0x00000002
#define CLOCK_CAP_RATE_48000 0x00000004
#define CLOCK_CAP_RATE_88200 0x00000008
#define CLOCK_CAP_RATE_96000 0x00000010
#define CLOCK_CAP_RATE_176400 0x00000020
#define CLOCK_CAP_RATE_192000 0x00000040
#define CLOCK_CAP_SOURCE_AES1 0x00010000
#define CLOCK_CAP_SOURCE_AES2 0x00020000
#define CLOCK_CAP_SOURCE_AES3 0x00040000
#define CLOCK_CAP_SOURCE_AES4 0x00080000
#define CLOCK_CAP_SOURCE_AES_ANY 0x00100000
#define CLOCK_CAP_SOURCE_ADAT 0x00200000
#define CLOCK_CAP_SOURCE_TDIF 0x00400000
#define CLOCK_CAP_SOURCE_WC 0x00800000
#define CLOCK_CAP_SOURCE_ARX1 0x01000000
#define CLOCK_CAP_SOURCE_ARX2 0x02000000
#define CLOCK_CAP_SOURCE_ARX3 0x04000000
#define CLOCK_CAP_SOURCE_ARX4 0x08000000
#define CLOCK_CAP_SOURCE_INTERNAL 0x10000000
/*
* Names of all clock sources; read-only. Quadlets are byte-swapped. Names
* are separated with one backslash, the list is terminated with two
* backslashes. Unused clock sources are included.
*/
#define GLOBAL_CLOCK_SOURCE_NAMES 0x068
#define CLOCK_SOURCE_NAMES_SIZE 256
/*
* Capture stream settings. This section includes the number/size registers
* and the registers of all streams.
*/
/*
* The number of supported capture streams; read-only.
*/
#define TX_NUMBER 0x000
/*
* The size of one stream's register block, in quadlets; read-only. The
* registers of the first stream follow immediately afterwards; the registers
* of the following streams are offset by this register's value.
*/
#define TX_SIZE 0x004
/*
* The isochronous channel number on which packets are sent, or -1 if the
* stream is not to be used; read/write.
*/
#define TX_ISOCHRONOUS 0x008
/*
* The number of audio channels; read-only. There will be one quadlet per
* channel; the first channel is the first quadlet in a data block.
*/
#define TX_NUMBER_AUDIO 0x00c
/*
* The number of MIDI ports, 0-8; read-only. If > 0, there will be one
* additional quadlet in each data block, following the audio quadlets.
*/
#define TX_NUMBER_MIDI 0x010
/*
* The speed at which the packets are sent, SCODE_100-_400; read/write.
*/
#define TX_SPEED 0x014
/*
* Names of all audio channels; read-only. Quadlets are byte-swapped. Names
* are separated with one backslash, the list is terminated with two
* backslashes.
*/
#define TX_NAMES 0x018
#define TX_NAMES_SIZE 256
/*
* Audio IEC60958 capabilities; read-only. Bitmask with one bit per audio
* channel.
*/
#define TX_AC3_CAPABILITIES 0x118
/*
* Send audio data with IEC60958 label; read/write. Bitmask with one bit per
* audio channel. This register can be changed even while the stream is
* running.
*/
#define TX_AC3_ENABLE 0x11c
/*
* Playback stream settings. This section includes the number/size registers
* and the registers of all streams.
*/
/*
* The number of supported playback streams; read-only.
*/
#define RX_NUMBER 0x000
/*
* The size of one stream's register block, in quadlets; read-only. The
* registers of the first stream follow immediately afterwards; the registers
* of the following streams are offset by this register's value.
*/
#define RX_SIZE 0x004
/*
* The isochronous channel number on which packets are received, or -1 if the
* stream is not to be used; read/write.
*/
#define RX_ISOCHRONOUS 0x008
/*
* Index of first quadlet to be interpreted; read/write. If > 0, that many
* quadlets at the beginning of each data block will be ignored, and all the
* audio and MIDI quadlets will follow.
*/
#define RX_SEQ_START 0x00c
/*
* The number of audio channels; read-only. There will be one quadlet per
* channel.
*/
#define RX_NUMBER_AUDIO 0x010
/*
* The number of MIDI ports, 0-8; read-only. If > 0, there will be one
* additional quadlet in each data block, following the audio quadlets.
*/
#define RX_NUMBER_MIDI 0x014
/*
* Names of all audio channels; read-only. Quadlets are byte-swapped. Names
* are separated with one backslash, the list is terminated with two
* backslashes.
*/
#define RX_NAMES 0x018
#define RX_NAMES_SIZE 256
/*
* Audio IEC60958 capabilities; read-only. Bitmask with one bit per audio
* channel.
*/
#define RX_AC3_CAPABILITIES 0x118
/*
* Receive audio data with IEC60958 label; read/write. Bitmask with one bit
* per audio channel. This register can be changed even while the stream is
* running.
*/
#define RX_AC3_ENABLE 0x11c
/*
* Extended synchronization information.
* This section can be read completely with a block read request.
*/
/*
* Current clock source; read-only.
*/
#define EXT_SYNC_CLOCK_SOURCE 0x000
/*
* Clock source is locked (boolean); read-only.
*/
#define EXT_SYNC_LOCKED 0x004
/*
* Current sample rate (CLOCK_RATE_* >> CLOCK_RATE_SHIFT), _32000-_192000 or
* _NONE; read-only.
*/
#define EXT_SYNC_RATE 0x008
/*
* ADAT user data bits; read-only.
*/
#define EXT_SYNC_ADAT_USER_DATA 0x00c
/* The data bits, if available. */
#define ADAT_USER_DATA_MASK 0x0f
/* The data bits are not available. */
#define ADAT_USER_DATA_NO_DATA 0x10
#endif

1511
sound/firewire/dice.c Normal file

File diff suppressed because it is too large Load diff

397
sound/firewire/fcp.c Normal file
View file

@ -0,0 +1,397 @@
/*
* Function Control Protocol (IEC 61883-1) helper functions
*
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include <linux/device.h>
#include <linux/firewire.h>
#include <linux/firewire-constants.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include "fcp.h"
#include "lib.h"
#include "amdtp.h"
#define CTS_AVC 0x00
#define ERROR_RETRIES 3
#define ERROR_DELAY_MS 5
#define FCP_TIMEOUT_MS 125
int avc_general_set_sig_fmt(struct fw_unit *unit, unsigned int rate,
enum avc_general_plug_dir dir,
unsigned short pid)
{
unsigned int sfc;
u8 *buf;
bool flag;
int err;
flag = false;
for (sfc = 0; sfc < CIP_SFC_COUNT; sfc++) {
if (amdtp_rate_table[sfc] == rate) {
flag = true;
break;
}
}
if (!flag)
return -EINVAL;
buf = kzalloc(8, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
buf[0] = 0x00; /* AV/C CONTROL */
buf[1] = 0xff; /* UNIT */
if (dir == AVC_GENERAL_PLUG_DIR_IN)
buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */
else
buf[2] = 0x18; /* OUTPUT PLUG SIGNAL FORMAT */
buf[3] = 0xff & pid; /* plug id */
buf[4] = 0x90; /* EOH_1, Form_1, FMT. AM824 */
buf[5] = 0x07 & sfc; /* FDF-hi. AM824, frequency */
buf[6] = 0xff; /* FDF-mid. AM824, SYT hi (not used)*/
buf[7] = 0xff; /* FDF-low. AM824, SYT lo (not used) */
/* do transaction and check buf[1-5] are the same against command */
err = fcp_avc_transaction(unit, buf, 8, buf, 8,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5));
if (err >= 0 && err < 8)
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;
else if (buf[0] == 0x0a) /* REJECTED */
err = -EINVAL;
if (err < 0)
goto end;
err = 0;
end:
kfree(buf);
return err;
}
EXPORT_SYMBOL(avc_general_set_sig_fmt);
int avc_general_get_sig_fmt(struct fw_unit *unit, unsigned int *rate,
enum avc_general_plug_dir dir,
unsigned short pid)
{
unsigned int sfc;
u8 *buf;
int err;
buf = kzalloc(8, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
buf[0] = 0x01; /* AV/C STATUS */
buf[1] = 0xff; /* Unit */
if (dir == AVC_GENERAL_PLUG_DIR_IN)
buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */
else
buf[2] = 0x18; /* OUTPUT PLUG SIGNAL FORMAT */
buf[3] = 0xff & pid; /* plug id */
buf[4] = 0x90; /* EOH_1, Form_1, FMT. AM824 */
buf[5] = 0xff; /* FDF-hi. AM824, frequency */
buf[6] = 0xff; /* FDF-mid. AM824, SYT hi (not used) */
buf[7] = 0xff; /* FDF-low. AM824, SYT lo (not used) */
/* do transaction and check buf[1-4] are the same against command */
err = fcp_avc_transaction(unit, buf, 8, buf, 8,
BIT(1) | BIT(2) | BIT(3) | BIT(4));
if (err >= 0 && err < 8)
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;
else if (buf[0] == 0x0a) /* REJECTED */
err = -EINVAL;
else if (buf[0] == 0x0b) /* IN TRANSITION */
err = -EAGAIN;
if (err < 0)
goto end;
/* check sfc field and pick up rate */
sfc = 0x07 & buf[5];
if (sfc >= CIP_SFC_COUNT) {
err = -EAGAIN; /* also in transition */
goto end;
}
*rate = amdtp_rate_table[sfc];
err = 0;
end:
kfree(buf);
return err;
}
EXPORT_SYMBOL(avc_general_get_sig_fmt);
int avc_general_get_plug_info(struct fw_unit *unit, unsigned int subunit_type,
unsigned int subunit_id, unsigned int subfunction,
u8 info[AVC_PLUG_INFO_BUF_BYTES])
{
u8 *buf;
int err;
/* extended subunit in spec.4.2 is not supported */
if ((subunit_type == 0x1E) || (subunit_id == 5))
return -EINVAL;
buf = kzalloc(8, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
buf[0] = 0x01; /* AV/C STATUS */
/* UNIT or Subunit, Functionblock */
buf[1] = ((subunit_type & 0x1f) << 3) | (subunit_id & 0x7);
buf[2] = 0x02; /* PLUG INFO */
buf[3] = 0xff & subfunction;
err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2));
if (err >= 0 && err < 8)
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;
else if (buf[0] == 0x0a) /* REJECTED */
err = -EINVAL;
else if (buf[0] == 0x0b) /* IN TRANSITION */
err = -EAGAIN;
if (err < 0)
goto end;
info[0] = buf[4];
info[1] = buf[5];
info[2] = buf[6];
info[3] = buf[7];
err = 0;
end:
kfree(buf);
return err;
}
EXPORT_SYMBOL(avc_general_get_plug_info);
static DEFINE_SPINLOCK(transactions_lock);
static LIST_HEAD(transactions);
enum fcp_state {
STATE_PENDING,
STATE_BUS_RESET,
STATE_COMPLETE,
STATE_DEFERRED,
};
struct fcp_transaction {
struct list_head list;
struct fw_unit *unit;
void *response_buffer;
unsigned int response_size;
unsigned int response_match_bytes;
enum fcp_state state;
wait_queue_head_t wait;
bool deferrable;
};
/**
* fcp_avc_transaction - send an AV/C command and wait for its response
* @unit: a unit on the target device
* @command: a buffer containing the command frame; must be DMA-able
* @command_size: the size of @command
* @response: a buffer for the response frame
* @response_size: the maximum size of @response
* @response_match_bytes: a bitmap specifying the bytes used to detect the
* correct response frame
*
* This function sends a FCP command frame to the target and waits for the
* corresponding response frame to be returned.
*
* Because it is possible for multiple FCP transactions to be active at the
* same time, the correct response frame is detected by the value of certain
* bytes. These bytes must be set in @response before calling this function,
* and the corresponding bits must be set in @response_match_bytes.
*
* @command and @response can point to the same buffer.
*
* Returns the actual size of the response frame, or a negative error code.
*/
int fcp_avc_transaction(struct fw_unit *unit,
const void *command, unsigned int command_size,
void *response, unsigned int response_size,
unsigned int response_match_bytes)
{
struct fcp_transaction t;
int tcode, ret, tries = 0;
t.unit = unit;
t.response_buffer = response;
t.response_size = response_size;
t.response_match_bytes = response_match_bytes;
t.state = STATE_PENDING;
init_waitqueue_head(&t.wait);
if (*(const u8 *)command == 0x00 || *(const u8 *)command == 0x03)
t.deferrable = true;
spin_lock_irq(&transactions_lock);
list_add_tail(&t.list, &transactions);
spin_unlock_irq(&transactions_lock);
for (;;) {
tcode = command_size == 4 ? TCODE_WRITE_QUADLET_REQUEST
: TCODE_WRITE_BLOCK_REQUEST;
ret = snd_fw_transaction(t.unit, tcode,
CSR_REGISTER_BASE + CSR_FCP_COMMAND,
(void *)command, command_size, 0);
if (ret < 0)
break;
deferred:
wait_event_timeout(t.wait, t.state != STATE_PENDING,
msecs_to_jiffies(FCP_TIMEOUT_MS));
if (t.state == STATE_DEFERRED) {
/*
* 'AV/C General Specification' define no time limit
* on command completion once an INTERIM response has
* been sent. but we promise to finish this function
* for a caller. Here we use FCP_TIMEOUT_MS for next
* interval. This is not in the specification.
*/
t.state = STATE_PENDING;
goto deferred;
} else if (t.state == STATE_COMPLETE) {
ret = t.response_size;
break;
} else if (t.state == STATE_BUS_RESET) {
msleep(ERROR_DELAY_MS);
} else if (++tries >= ERROR_RETRIES) {
dev_err(&t.unit->device, "FCP command timed out\n");
ret = -EIO;
break;
}
}
spin_lock_irq(&transactions_lock);
list_del(&t.list);
spin_unlock_irq(&transactions_lock);
return ret;
}
EXPORT_SYMBOL(fcp_avc_transaction);
/**
* fcp_bus_reset - inform the target handler about a bus reset
* @unit: the unit that might be used by fcp_avc_transaction()
*
* This function must be called from the driver's .update handler to inform
* the FCP transaction handler that a bus reset has happened. Any pending FCP
* transactions are retried.
*/
void fcp_bus_reset(struct fw_unit *unit)
{
struct fcp_transaction *t;
spin_lock_irq(&transactions_lock);
list_for_each_entry(t, &transactions, list) {
if (t->unit == unit &&
(t->state == STATE_PENDING ||
t->state == STATE_DEFERRED)) {
t->state = STATE_BUS_RESET;
wake_up(&t->wait);
}
}
spin_unlock_irq(&transactions_lock);
}
EXPORT_SYMBOL(fcp_bus_reset);
/* checks whether the response matches the masked bytes in response_buffer */
static bool is_matching_response(struct fcp_transaction *transaction,
const void *response, size_t length)
{
const u8 *p1, *p2;
unsigned int mask, i;
p1 = response;
p2 = transaction->response_buffer;
mask = transaction->response_match_bytes;
for (i = 0; ; ++i) {
if ((mask & 1) && p1[i] != p2[i])
return false;
mask >>= 1;
if (!mask)
return true;
if (--length == 0)
return false;
}
}
static void fcp_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)
{
struct fcp_transaction *t;
unsigned long flags;
if (length < 1 || (*(const u8 *)data & 0xf0) != CTS_AVC)
return;
spin_lock_irqsave(&transactions_lock, flags);
list_for_each_entry(t, &transactions, list) {
struct fw_device *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 &&
is_matching_response(t, data, length)) {
if (t->deferrable && *(const u8 *)data == 0x0f) {
t->state = STATE_DEFERRED;
} else {
t->state = STATE_COMPLETE;
t->response_size = min_t(unsigned int, length,
t->response_size);
memcpy(t->response_buffer, data,
t->response_size);
}
wake_up(&t->wait);
}
}
spin_unlock_irqrestore(&transactions_lock, flags);
}
static struct fw_address_handler response_register_handler = {
.length = 0x200,
.address_callback = fcp_response,
};
static int __init fcp_module_init(void)
{
static const struct fw_address_region response_register_region = {
.start = CSR_REGISTER_BASE + CSR_FCP_RESPONSE,
.end = CSR_REGISTER_BASE + CSR_FCP_END,
};
fw_core_add_address_handler(&response_register_handler,
&response_register_region);
return 0;
}
static void __exit fcp_module_exit(void)
{
WARN_ON(!list_empty(&transactions));
fw_core_remove_address_handler(&response_register_handler);
}
module_init(fcp_module_init);
module_exit(fcp_module_exit);

33
sound/firewire/fcp.h Normal file
View file

@ -0,0 +1,33 @@
#ifndef SOUND_FIREWIRE_FCP_H_INCLUDED
#define SOUND_FIREWIRE_FCP_H_INCLUDED
#define AVC_PLUG_INFO_BUF_BYTES 4
struct fw_unit;
/*
* AV/C Digital Interface Command Set General Specification 4.2
* (Sep 2004, 1394TA)
*/
enum avc_general_plug_dir {
AVC_GENERAL_PLUG_DIR_IN = 0,
AVC_GENERAL_PLUG_DIR_OUT = 1,
AVC_GENERAL_PLUG_DIR_COUNT
};
int avc_general_set_sig_fmt(struct fw_unit *unit, unsigned int rate,
enum avc_general_plug_dir dir,
unsigned short plug);
int avc_general_get_sig_fmt(struct fw_unit *unit, unsigned int *rate,
enum avc_general_plug_dir dir,
unsigned short plug);
int avc_general_get_plug_info(struct fw_unit *unit, unsigned int subunit_type,
unsigned int subunit_id, unsigned int subfunction,
u8 info[AVC_PLUG_INFO_BUF_BYTES]);
int fcp_avc_transaction(struct fw_unit *unit,
const void *command, unsigned int command_size,
void *response, unsigned int response_size,
unsigned int response_match_bytes);
void fcp_bus_reset(struct fw_unit *unit);
#endif

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);
}

748
sound/firewire/isight.c Normal file
View file

@ -0,0 +1,748 @@
/*
* Apple iSight audio driver
*
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include <asm/byteorder.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/firewire.h>
#include <linux/firewire-constants.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/mutex.h>
#include <linux/string.h>
#include <sound/control.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/tlv.h>
#include "lib.h"
#include "iso-resources.h"
#include "packets-buffer.h"
#define OUI_APPLE 0x000a27
#define MODEL_APPLE_ISIGHT 0x000008
#define SW_ISIGHT_AUDIO 0x000010
#define REG_AUDIO_ENABLE 0x000
#define AUDIO_ENABLE 0x80000000
#define REG_DEF_AUDIO_GAIN 0x204
#define REG_GAIN_RAW_START 0x210
#define REG_GAIN_RAW_END 0x214
#define REG_GAIN_DB_START 0x218
#define REG_GAIN_DB_END 0x21c
#define REG_SAMPLE_RATE_INQUIRY 0x280
#define REG_ISO_TX_CONFIG 0x300
#define SPEED_SHIFT 16
#define REG_SAMPLE_RATE 0x400
#define RATE_48000 0x80000000
#define REG_GAIN 0x500
#define REG_MUTE 0x504
#define MAX_FRAMES_PER_PACKET 475
#define QUEUE_LENGTH 20
struct isight {
struct snd_card *card;
struct fw_unit *unit;
struct fw_device *device;
u64 audio_base;
struct snd_pcm_substream *pcm;
struct mutex mutex;
struct iso_packets_buffer buffer;
struct fw_iso_resources resources;
struct fw_iso_context *context;
bool pcm_active;
bool pcm_running;
bool first_packet;
int packet_index;
u32 total_samples;
unsigned int buffer_pointer;
unsigned int period_counter;
s32 gain_min, gain_max;
unsigned int gain_tlv[4];
};
struct audio_payload {
__be32 sample_count;
__be32 signature;
__be32 sample_total;
__be32 reserved;
__be16 samples[2 * MAX_FRAMES_PER_PACKET];
};
MODULE_DESCRIPTION("iSight audio driver");
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
MODULE_LICENSE("GPL v2");
static struct fw_iso_packet audio_packet = {
.payload_length = sizeof(struct audio_payload),
.interrupt = 1,
.header_length = 4,
};
static void isight_update_pointers(struct isight *isight, unsigned int count)
{
struct snd_pcm_runtime *runtime = isight->pcm->runtime;
unsigned int ptr;
smp_wmb(); /* update buffer data before buffer pointer */
ptr = isight->buffer_pointer;
ptr += count;
if (ptr >= runtime->buffer_size)
ptr -= runtime->buffer_size;
ACCESS_ONCE(isight->buffer_pointer) = ptr;
isight->period_counter += count;
if (isight->period_counter >= runtime->period_size) {
isight->period_counter -= runtime->period_size;
snd_pcm_period_elapsed(isight->pcm);
}
}
static void isight_samples(struct isight *isight,
const __be16 *samples, unsigned int count)
{
struct snd_pcm_runtime *runtime;
unsigned int count1;
if (!ACCESS_ONCE(isight->pcm_running))
return;
runtime = isight->pcm->runtime;
if (isight->buffer_pointer + count <= runtime->buffer_size) {
memcpy(runtime->dma_area + isight->buffer_pointer * 4,
samples, count * 4);
} else {
count1 = runtime->buffer_size - isight->buffer_pointer;
memcpy(runtime->dma_area + isight->buffer_pointer * 4,
samples, count1 * 4);
samples += count1 * 2;
memcpy(runtime->dma_area, samples, (count - count1) * 4);
}
isight_update_pointers(isight, count);
}
static void isight_pcm_abort(struct isight *isight)
{
unsigned long flags;
if (ACCESS_ONCE(isight->pcm_active)) {
snd_pcm_stream_lock_irqsave(isight->pcm, flags);
if (snd_pcm_running(isight->pcm))
snd_pcm_stop(isight->pcm, SNDRV_PCM_STATE_XRUN);
snd_pcm_stream_unlock_irqrestore(isight->pcm, flags);
}
}
static void isight_dropped_samples(struct isight *isight, unsigned int total)
{
struct snd_pcm_runtime *runtime;
u32 dropped;
unsigned int count1;
if (!ACCESS_ONCE(isight->pcm_running))
return;
runtime = isight->pcm->runtime;
dropped = total - isight->total_samples;
if (dropped < runtime->buffer_size) {
if (isight->buffer_pointer + dropped <= runtime->buffer_size) {
memset(runtime->dma_area + isight->buffer_pointer * 4,
0, dropped * 4);
} else {
count1 = runtime->buffer_size - isight->buffer_pointer;
memset(runtime->dma_area + isight->buffer_pointer * 4,
0, count1 * 4);
memset(runtime->dma_area, 0, (dropped - count1) * 4);
}
isight_update_pointers(isight, dropped);
} else {
isight_pcm_abort(isight);
}
}
static void isight_packet(struct fw_iso_context *context, u32 cycle,
size_t header_length, void *header, void *data)
{
struct isight *isight = data;
const struct audio_payload *payload;
unsigned int index, length, count, total;
int err;
if (isight->packet_index < 0)
return;
index = isight->packet_index;
payload = isight->buffer.packets[index].buffer;
length = be32_to_cpup(header) >> 16;
if (likely(length >= 16 &&
payload->signature == cpu_to_be32(0x73676874/*"sght"*/))) {
count = be32_to_cpu(payload->sample_count);
if (likely(count <= (length - 16) / 4)) {
total = be32_to_cpu(payload->sample_total);
if (unlikely(total != isight->total_samples)) {
if (!isight->first_packet)
isight_dropped_samples(isight, total);
isight->first_packet = false;
isight->total_samples = total;
}
isight_samples(isight, payload->samples, count);
isight->total_samples += count;
}
}
err = fw_iso_context_queue(isight->context, &audio_packet,
&isight->buffer.iso_buffer,
isight->buffer.packets[index].offset);
if (err < 0) {
dev_err(&isight->unit->device, "queueing error: %d\n", err);
isight_pcm_abort(isight);
isight->packet_index = -1;
return;
}
fw_iso_context_queue_flush(isight->context);
if (++index >= QUEUE_LENGTH)
index = 0;
isight->packet_index = index;
}
static int isight_connect(struct isight *isight)
{
int ch, err;
__be32 value;
retry_after_bus_reset:
ch = fw_iso_resources_allocate(&isight->resources,
sizeof(struct audio_payload),
isight->device->max_speed);
if (ch < 0) {
err = ch;
goto error;
}
value = cpu_to_be32(ch | (isight->device->max_speed << SPEED_SHIFT));
err = snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST,
isight->audio_base + REG_ISO_TX_CONFIG,
&value, 4, FW_FIXED_GENERATION |
isight->resources.generation);
if (err == -EAGAIN) {
fw_iso_resources_free(&isight->resources);
goto retry_after_bus_reset;
} else if (err < 0) {
goto err_resources;
}
return 0;
err_resources:
fw_iso_resources_free(&isight->resources);
error:
return err;
}
static int isight_open(struct snd_pcm_substream *substream)
{
static const struct snd_pcm_hardware hardware = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_BATCH |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER,
.formats = SNDRV_PCM_FMTBIT_S16_BE,
.rates = SNDRV_PCM_RATE_48000,
.rate_min = 48000,
.rate_max = 48000,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 4 * 1024 * 1024,
.period_bytes_min = MAX_FRAMES_PER_PACKET * 4,
.period_bytes_max = 1024 * 1024,
.periods_min = 2,
.periods_max = UINT_MAX,
};
struct isight *isight = substream->private_data;
substream->runtime->hw = hardware;
return iso_packets_buffer_init(&isight->buffer, isight->unit,
QUEUE_LENGTH,
sizeof(struct audio_payload),
DMA_FROM_DEVICE);
}
static int isight_close(struct snd_pcm_substream *substream)
{
struct isight *isight = substream->private_data;
iso_packets_buffer_destroy(&isight->buffer, isight->unit);
return 0;
}
static int isight_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct isight *isight = substream->private_data;
int err;
err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
params_buffer_bytes(hw_params));
if (err < 0)
return err;
ACCESS_ONCE(isight->pcm_active) = true;
return 0;
}
static int reg_read(struct isight *isight, int offset, __be32 *value)
{
return snd_fw_transaction(isight->unit, TCODE_READ_QUADLET_REQUEST,
isight->audio_base + offset, value, 4, 0);
}
static int reg_write(struct isight *isight, int offset, __be32 value)
{
return snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST,
isight->audio_base + offset, &value, 4, 0);
}
static void isight_stop_streaming(struct isight *isight)
{
__be32 value;
if (!isight->context)
return;
fw_iso_context_stop(isight->context);
fw_iso_context_destroy(isight->context);
isight->context = NULL;
fw_iso_resources_free(&isight->resources);
value = 0;
snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST,
isight->audio_base + REG_AUDIO_ENABLE,
&value, 4, FW_QUIET);
}
static int isight_hw_free(struct snd_pcm_substream *substream)
{
struct isight *isight = substream->private_data;
ACCESS_ONCE(isight->pcm_active) = false;
mutex_lock(&isight->mutex);
isight_stop_streaming(isight);
mutex_unlock(&isight->mutex);
return snd_pcm_lib_free_vmalloc_buffer(substream);
}
static int isight_start_streaming(struct isight *isight)
{
unsigned int i;
int err;
if (isight->context) {
if (isight->packet_index < 0)
isight_stop_streaming(isight);
else
return 0;
}
err = reg_write(isight, REG_SAMPLE_RATE, cpu_to_be32(RATE_48000));
if (err < 0)
goto error;
err = isight_connect(isight);
if (err < 0)
goto error;
err = reg_write(isight, REG_AUDIO_ENABLE, cpu_to_be32(AUDIO_ENABLE));
if (err < 0)
goto err_resources;
isight->context = fw_iso_context_create(isight->device->card,
FW_ISO_CONTEXT_RECEIVE,
isight->resources.channel,
isight->device->max_speed,
4, isight_packet, isight);
if (IS_ERR(isight->context)) {
err = PTR_ERR(isight->context);
isight->context = NULL;
goto err_resources;
}
for (i = 0; i < QUEUE_LENGTH; ++i) {
err = fw_iso_context_queue(isight->context, &audio_packet,
&isight->buffer.iso_buffer,
isight->buffer.packets[i].offset);
if (err < 0)
goto err_context;
}
isight->first_packet = true;
isight->packet_index = 0;
err = fw_iso_context_start(isight->context, -1, 0,
FW_ISO_CONTEXT_MATCH_ALL_TAGS/*?*/);
if (err < 0)
goto err_context;
return 0;
err_context:
fw_iso_context_destroy(isight->context);
isight->context = NULL;
err_resources:
fw_iso_resources_free(&isight->resources);
reg_write(isight, REG_AUDIO_ENABLE, 0);
error:
return err;
}
static int isight_prepare(struct snd_pcm_substream *substream)
{
struct isight *isight = substream->private_data;
int err;
isight->buffer_pointer = 0;
isight->period_counter = 0;
mutex_lock(&isight->mutex);
err = isight_start_streaming(isight);
mutex_unlock(&isight->mutex);
return err;
}
static int isight_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct isight *isight = substream->private_data;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
ACCESS_ONCE(isight->pcm_running) = true;
break;
case SNDRV_PCM_TRIGGER_STOP:
ACCESS_ONCE(isight->pcm_running) = false;
break;
default:
return -EINVAL;
}
return 0;
}
static snd_pcm_uframes_t isight_pointer(struct snd_pcm_substream *substream)
{
struct isight *isight = substream->private_data;
return ACCESS_ONCE(isight->buffer_pointer);
}
static int isight_create_pcm(struct isight *isight)
{
static struct snd_pcm_ops ops = {
.open = isight_open,
.close = isight_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = isight_hw_params,
.hw_free = isight_hw_free,
.prepare = isight_prepare,
.trigger = isight_trigger,
.pointer = isight_pointer,
.page = snd_pcm_lib_get_vmalloc_page,
.mmap = snd_pcm_lib_mmap_vmalloc,
};
struct snd_pcm *pcm;
int err;
err = snd_pcm_new(isight->card, "iSight", 0, 0, 1, &pcm);
if (err < 0)
return err;
pcm->private_data = isight;
strcpy(pcm->name, "iSight");
isight->pcm = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream;
isight->pcm->ops = &ops;
return 0;
}
static int isight_gain_info(struct snd_kcontrol *ctl,
struct snd_ctl_elem_info *info)
{
struct isight *isight = ctl->private_data;
info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
info->count = 1;
info->value.integer.min = isight->gain_min;
info->value.integer.max = isight->gain_max;
return 0;
}
static int isight_gain_get(struct snd_kcontrol *ctl,
struct snd_ctl_elem_value *value)
{
struct isight *isight = ctl->private_data;
__be32 gain;
int err;
err = reg_read(isight, REG_GAIN, &gain);
if (err < 0)
return err;
value->value.integer.value[0] = (s32)be32_to_cpu(gain);
return 0;
}
static int isight_gain_put(struct snd_kcontrol *ctl,
struct snd_ctl_elem_value *value)
{
struct isight *isight = ctl->private_data;
if (value->value.integer.value[0] < isight->gain_min ||
value->value.integer.value[0] > isight->gain_max)
return -EINVAL;
return reg_write(isight, REG_GAIN,
cpu_to_be32(value->value.integer.value[0]));
}
static int isight_mute_get(struct snd_kcontrol *ctl,
struct snd_ctl_elem_value *value)
{
struct isight *isight = ctl->private_data;
__be32 mute;
int err;
err = reg_read(isight, REG_MUTE, &mute);
if (err < 0)
return err;
value->value.integer.value[0] = !mute;
return 0;
}
static int isight_mute_put(struct snd_kcontrol *ctl,
struct snd_ctl_elem_value *value)
{
struct isight *isight = ctl->private_data;
return reg_write(isight, REG_MUTE,
(__force __be32)!value->value.integer.value[0]);
}
static int isight_create_mixer(struct isight *isight)
{
static const struct snd_kcontrol_new gain_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Mic Capture Volume",
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
SNDRV_CTL_ELEM_ACCESS_TLV_READ,
.info = isight_gain_info,
.get = isight_gain_get,
.put = isight_gain_put,
};
static const struct snd_kcontrol_new mute_control = {
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "Mic Capture Switch",
.info = snd_ctl_boolean_mono_info,
.get = isight_mute_get,
.put = isight_mute_put,
};
__be32 value;
struct snd_kcontrol *ctl;
int err;
err = reg_read(isight, REG_GAIN_RAW_START, &value);
if (err < 0)
return err;
isight->gain_min = be32_to_cpu(value);
err = reg_read(isight, REG_GAIN_RAW_END, &value);
if (err < 0)
return err;
isight->gain_max = be32_to_cpu(value);
isight->gain_tlv[0] = SNDRV_CTL_TLVT_DB_MINMAX;
isight->gain_tlv[1] = 2 * sizeof(unsigned int);
err = reg_read(isight, REG_GAIN_DB_START, &value);
if (err < 0)
return err;
isight->gain_tlv[2] = (s32)be32_to_cpu(value) * 100;
err = reg_read(isight, REG_GAIN_DB_END, &value);
if (err < 0)
return err;
isight->gain_tlv[3] = (s32)be32_to_cpu(value) * 100;
ctl = snd_ctl_new1(&gain_control, isight);
if (ctl)
ctl->tlv.p = isight->gain_tlv;
err = snd_ctl_add(isight->card, ctl);
if (err < 0)
return err;
err = snd_ctl_add(isight->card, snd_ctl_new1(&mute_control, isight));
if (err < 0)
return err;
return 0;
}
static void isight_card_free(struct snd_card *card)
{
struct isight *isight = card->private_data;
fw_iso_resources_destroy(&isight->resources);
fw_unit_put(isight->unit);
mutex_destroy(&isight->mutex);
}
static u64 get_unit_base(struct fw_unit *unit)
{
struct fw_csr_iterator i;
int key, value;
fw_csr_iterator_init(&i, unit->directory);
while (fw_csr_iterator_next(&i, &key, &value))
if (key == CSR_OFFSET)
return CSR_REGISTER_BASE + value * 4;
return 0;
}
static int isight_probe(struct fw_unit *unit,
const struct ieee1394_device_id *id)
{
struct fw_device *fw_dev = fw_parent_device(unit);
struct snd_card *card;
struct isight *isight;
int err;
err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE,
sizeof(*isight), &card);
if (err < 0)
return err;
isight = card->private_data;
isight->card = card;
mutex_init(&isight->mutex);
isight->unit = fw_unit_get(unit);
isight->device = fw_dev;
isight->audio_base = get_unit_base(unit);
if (!isight->audio_base) {
dev_err(&unit->device, "audio unit base not found\n");
err = -ENXIO;
goto err_unit;
}
fw_iso_resources_init(&isight->resources, unit);
card->private_free = isight_card_free;
strcpy(card->driver, "iSight");
strcpy(card->shortname, "Apple iSight");
snprintf(card->longname, sizeof(card->longname),
"Apple iSight (GUID %08x%08x) at %s, S%d",
fw_dev->config_rom[3], fw_dev->config_rom[4],
dev_name(&unit->device), 100 << fw_dev->max_speed);
strcpy(card->mixername, "iSight");
err = isight_create_pcm(isight);
if (err < 0)
goto error;
err = isight_create_mixer(isight);
if (err < 0)
goto error;
err = snd_card_register(card);
if (err < 0)
goto error;
dev_set_drvdata(&unit->device, isight);
return 0;
err_unit:
fw_unit_put(isight->unit);
mutex_destroy(&isight->mutex);
error:
snd_card_free(card);
return err;
}
static void isight_bus_reset(struct fw_unit *unit)
{
struct isight *isight = dev_get_drvdata(&unit->device);
if (fw_iso_resources_update(&isight->resources) < 0) {
isight_pcm_abort(isight);
mutex_lock(&isight->mutex);
isight_stop_streaming(isight);
mutex_unlock(&isight->mutex);
}
}
static void isight_remove(struct fw_unit *unit)
{
struct isight *isight = dev_get_drvdata(&unit->device);
isight_pcm_abort(isight);
snd_card_disconnect(isight->card);
mutex_lock(&isight->mutex);
isight_stop_streaming(isight);
mutex_unlock(&isight->mutex);
snd_card_free_when_closed(isight->card);
}
static const struct ieee1394_device_id isight_id_table[] = {
{
.match_flags = IEEE1394_MATCH_SPECIFIER_ID |
IEEE1394_MATCH_VERSION,
.specifier_id = OUI_APPLE,
.version = SW_ISIGHT_AUDIO,
},
{ }
};
MODULE_DEVICE_TABLE(ieee1394, isight_id_table);
static struct fw_driver isight_driver = {
.driver = {
.owner = THIS_MODULE,
.name = KBUILD_MODNAME,
.bus = &fw_bus_type,
},
.probe = isight_probe,
.update = isight_bus_reset,
.remove = isight_remove,
.id_table = isight_id_table,
};
static int __init alsa_isight_init(void)
{
return driver_register(&isight_driver.driver);
}
static void __exit alsa_isight_exit(void)
{
driver_unregister(&isight_driver.driver);
}
module_init(alsa_isight_init);
module_exit(alsa_isight_exit);

View file

@ -0,0 +1,232 @@
/*
* isochronous resources helper functions
*
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include <linux/device.h>
#include <linux/firewire.h>
#include <linux/firewire-constants.h>
#include <linux/export.h>
#include <linux/jiffies.h>
#include <linux/mutex.h>
#include <linux/sched.h>
#include <linux/spinlock.h>
#include "iso-resources.h"
/**
* fw_iso_resources_init - initializes a &struct fw_iso_resources
* @r: the resource manager to initialize
* @unit: the device unit for which the resources will be needed
*
* If the device does not support all channel numbers, change @r->channels_mask
* after calling this function.
*/
int fw_iso_resources_init(struct fw_iso_resources *r, struct fw_unit *unit)
{
r->channels_mask = ~0uLL;
r->unit = fw_unit_get(unit);
mutex_init(&r->mutex);
r->allocated = false;
return 0;
}
EXPORT_SYMBOL(fw_iso_resources_init);
/**
* fw_iso_resources_destroy - destroy a resource manager
* @r: the resource manager that is no longer needed
*/
void fw_iso_resources_destroy(struct fw_iso_resources *r)
{
WARN_ON(r->allocated);
mutex_destroy(&r->mutex);
fw_unit_put(r->unit);
}
EXPORT_SYMBOL(fw_iso_resources_destroy);
static unsigned int packet_bandwidth(unsigned int max_payload_bytes, int speed)
{
unsigned int bytes, s400_bytes;
/* iso packets have three header quadlets and quadlet-aligned payload */
bytes = 3 * 4 + ALIGN(max_payload_bytes, 4);
/* convert to bandwidth units (quadlets at S1600 = bytes at S400) */
if (speed <= SCODE_400)
s400_bytes = bytes * (1 << (SCODE_400 - speed));
else
s400_bytes = DIV_ROUND_UP(bytes, 1 << (speed - SCODE_400));
return s400_bytes;
}
static int current_bandwidth_overhead(struct fw_card *card)
{
/*
* Under the usual pessimistic assumption (cable length 4.5 m), the
* isochronous overhead for N cables is 1.797 µs + N * 0.494 µs, or
* 88.3 + N * 24.3 in bandwidth units.
*
* The calculation below tries to deduce N from the current gap count.
* If the gap count has been optimized by measuring the actual packet
* transmission time, this derived overhead should be near the actual
* overhead as well.
*/
return card->gap_count < 63 ? card->gap_count * 97 / 10 + 89 : 512;
}
static int wait_isoch_resource_delay_after_bus_reset(struct fw_card *card)
{
for (;;) {
s64 delay = (card->reset_jiffies + HZ) - get_jiffies_64();
if (delay <= 0)
return 0;
if (schedule_timeout_interruptible(delay) > 0)
return -ERESTARTSYS;
}
}
/**
* fw_iso_resources_allocate - allocate isochronous channel and bandwidth
* @r: the resource manager
* @max_payload_bytes: the amount of data (including CIP headers) per packet
* @speed: the speed (e.g., SCODE_400) at which the packets will be sent
*
* This function allocates one isochronous channel and enough bandwidth for the
* specified packet size.
*
* Returns the channel number that the caller must use for streaming, or
* a negative error code. Due to potentionally long delays, this function is
* interruptible and can return -ERESTARTSYS. On success, the caller is
* responsible for calling fw_iso_resources_update() on bus resets, and
* fw_iso_resources_free() when the resources are not longer needed.
*/
int fw_iso_resources_allocate(struct fw_iso_resources *r,
unsigned int max_payload_bytes, int speed)
{
struct fw_card *card = fw_parent_device(r->unit)->card;
int bandwidth, channel, err;
if (WARN_ON(r->allocated))
return -EBADFD;
r->bandwidth = packet_bandwidth(max_payload_bytes, speed);
retry_after_bus_reset:
spin_lock_irq(&card->lock);
r->generation = card->generation;
r->bandwidth_overhead = current_bandwidth_overhead(card);
spin_unlock_irq(&card->lock);
err = wait_isoch_resource_delay_after_bus_reset(card);
if (err < 0)
return err;
mutex_lock(&r->mutex);
bandwidth = r->bandwidth + r->bandwidth_overhead;
fw_iso_resource_manage(card, r->generation, r->channels_mask,
&channel, &bandwidth, true);
if (channel == -EAGAIN) {
mutex_unlock(&r->mutex);
goto retry_after_bus_reset;
}
if (channel >= 0) {
r->channel = channel;
r->allocated = true;
} else {
if (channel == -EBUSY)
dev_err(&r->unit->device,
"isochronous resources exhausted\n");
else
dev_err(&r->unit->device,
"isochronous resource allocation failed\n");
}
mutex_unlock(&r->mutex);
return channel;
}
EXPORT_SYMBOL(fw_iso_resources_allocate);
/**
* fw_iso_resources_update - update resource allocations after a bus reset
* @r: the resource manager
*
* This function must be called from the driver's .update handler to reallocate
* any resources that were allocated before the bus reset. It is safe to call
* this function if no resources are currently allocated.
*
* Returns a negative error code on failure. If this happens, the caller must
* stop streaming.
*/
int fw_iso_resources_update(struct fw_iso_resources *r)
{
struct fw_card *card = fw_parent_device(r->unit)->card;
int bandwidth, channel;
mutex_lock(&r->mutex);
if (!r->allocated) {
mutex_unlock(&r->mutex);
return 0;
}
spin_lock_irq(&card->lock);
r->generation = card->generation;
r->bandwidth_overhead = current_bandwidth_overhead(card);
spin_unlock_irq(&card->lock);
bandwidth = r->bandwidth + r->bandwidth_overhead;
fw_iso_resource_manage(card, r->generation, 1uLL << r->channel,
&channel, &bandwidth, true);
/*
* When another bus reset happens, pretend that the allocation
* succeeded; we will try again for the new generation later.
*/
if (channel < 0 && channel != -EAGAIN) {
r->allocated = false;
if (channel == -EBUSY)
dev_err(&r->unit->device,
"isochronous resources exhausted\n");
else
dev_err(&r->unit->device,
"isochronous resource allocation failed\n");
}
mutex_unlock(&r->mutex);
return channel;
}
EXPORT_SYMBOL(fw_iso_resources_update);
/**
* fw_iso_resources_free - frees allocated resources
* @r: the resource manager
*
* This function deallocates the channel and bandwidth, if allocated.
*/
void fw_iso_resources_free(struct fw_iso_resources *r)
{
struct fw_card *card = fw_parent_device(r->unit)->card;
int bandwidth, channel;
mutex_lock(&r->mutex);
if (r->allocated) {
bandwidth = r->bandwidth + r->bandwidth_overhead;
fw_iso_resource_manage(card, r->generation, 1uLL << r->channel,
&channel, &bandwidth, false);
if (channel < 0)
dev_err(&r->unit->device,
"isochronous resource deallocation failed\n");
r->allocated = false;
}
mutex_unlock(&r->mutex);
}
EXPORT_SYMBOL(fw_iso_resources_free);

View file

@ -0,0 +1,38 @@
#ifndef SOUND_FIREWIRE_ISO_RESOURCES_H_INCLUDED
#define SOUND_FIREWIRE_ISO_RESOURCES_H_INCLUDED
#include <linux/mutex.h>
#include <linux/types.h>
struct fw_unit;
/**
* struct fw_iso_resources - manages channel/bandwidth allocation
* @channels_mask: if the device does not support all channel numbers, set this
* bit mask to something else than the default (all ones)
*
* This structure manages (de)allocation of isochronous resources (channel and
* bandwidth) for one isochronous stream.
*/
struct fw_iso_resources {
u64 channels_mask;
/* private: */
struct fw_unit *unit;
struct mutex mutex;
unsigned int channel;
unsigned int bandwidth; /* in bandwidth units, without overhead */
unsigned int bandwidth_overhead;
int generation; /* in which allocation is valid */
bool allocated;
};
int fw_iso_resources_init(struct fw_iso_resources *r,
struct fw_unit *unit);
void fw_iso_resources_destroy(struct fw_iso_resources *r);
int fw_iso_resources_allocate(struct fw_iso_resources *r,
unsigned int max_payload_bytes, int speed);
int fw_iso_resources_update(struct fw_iso_resources *r);
void fw_iso_resources_free(struct fw_iso_resources *r);
#endif

71
sound/firewire/lib.c Normal file
View file

@ -0,0 +1,71 @@
/*
* miscellaneous helper functions
*
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/firewire.h>
#include <linux/module.h>
#include "lib.h"
#define ERROR_RETRY_DELAY_MS 20
/**
* snd_fw_transaction - send a request and wait for its completion
* @unit: the driver's unit on the target device
* @tcode: the transaction code
* @offset: the address in the target's address space
* @buffer: input/output data
* @length: length of @buffer
* @flags: use %FW_FIXED_GENERATION and add the generation value to attempt the
* request only in that generation; use %FW_QUIET to suppress error
* messages
*
* Submits an asynchronous request to the target device, and waits for the
* response. The node ID and the current generation are derived from @unit.
* On a bus reset or an error, the transaction is retried a few times.
* Returns zero on success, or a negative error code.
*/
int snd_fw_transaction(struct fw_unit *unit, int tcode,
u64 offset, void *buffer, size_t length,
unsigned int flags)
{
struct fw_device *device = fw_parent_device(unit);
int generation, rcode, tries = 0;
generation = flags & FW_GENERATION_MASK;
for (;;) {
if (!(flags & FW_FIXED_GENERATION)) {
generation = device->generation;
smp_rmb(); /* node_id vs. generation */
}
rcode = fw_run_transaction(device->card, tcode,
device->node_id, generation,
device->max_speed, offset,
buffer, length);
if (rcode == RCODE_COMPLETE)
return 0;
if (rcode == RCODE_GENERATION && (flags & FW_FIXED_GENERATION))
return -EAGAIN;
if (rcode_is_permanent_error(rcode) || ++tries >= 3) {
if (!(flags & FW_QUIET))
dev_err(&unit->device,
"transaction failed: %s\n",
fw_rcode_string(rcode));
return -EIO;
}
msleep(ERROR_RETRY_DELAY_MS);
}
}
EXPORT_SYMBOL(snd_fw_transaction);
MODULE_DESCRIPTION("FireWire audio helper functions");
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
MODULE_LICENSE("GPL v2");

23
sound/firewire/lib.h Normal file
View file

@ -0,0 +1,23 @@
#ifndef SOUND_FIREWIRE_LIB_H_INCLUDED
#define SOUND_FIREWIRE_LIB_H_INCLUDED
#include <linux/firewire-constants.h>
#include <linux/types.h>
struct fw_unit;
#define FW_GENERATION_MASK 0x00ff
#define FW_FIXED_GENERATION 0x0100
#define FW_QUIET 0x0200
int snd_fw_transaction(struct fw_unit *unit, int tcode,
u64 offset, void *buffer, size_t length,
unsigned int flags);
/* returns true if retrying the transaction would not make sense */
static inline bool rcode_is_permanent_error(int rcode)
{
return rcode == RCODE_TYPE_ERROR || rcode == RCODE_ADDRESS_ERROR;
}
#endif

View file

@ -0,0 +1,77 @@
/*
* helpers for managing a buffer for many packets
*
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include <linux/firewire.h>
#include <linux/export.h>
#include <linux/slab.h>
#include "packets-buffer.h"
/**
* iso_packets_buffer_init - allocates the memory for packets
* @b: the buffer structure to initialize
* @unit: the device at the other end of the stream
* @count: the number of packets
* @packet_size: the (maximum) size of a packet, in bytes
* @direction: %DMA_TO_DEVICE or %DMA_FROM_DEVICE
*/
int iso_packets_buffer_init(struct iso_packets_buffer *b, struct fw_unit *unit,
unsigned int count, unsigned int packet_size,
enum dma_data_direction direction)
{
unsigned int packets_per_page, pages;
unsigned int i, page_index, offset_in_page;
void *p;
int err;
b->packets = kmalloc(count * sizeof(*b->packets), GFP_KERNEL);
if (!b->packets) {
err = -ENOMEM;
goto error;
}
packet_size = L1_CACHE_ALIGN(packet_size);
packets_per_page = PAGE_SIZE / packet_size;
if (WARN_ON(!packets_per_page)) {
err = -EINVAL;
goto error;
}
pages = DIV_ROUND_UP(count, packets_per_page);
err = fw_iso_buffer_init(&b->iso_buffer, fw_parent_device(unit)->card,
pages, direction);
if (err < 0)
goto err_packets;
for (i = 0; i < count; ++i) {
page_index = i / packets_per_page;
p = page_address(b->iso_buffer.pages[page_index]);
offset_in_page = (i % packets_per_page) * packet_size;
b->packets[i].buffer = p + offset_in_page;
b->packets[i].offset = page_index * PAGE_SIZE + offset_in_page;
}
return 0;
err_packets:
kfree(b->packets);
error:
return err;
}
EXPORT_SYMBOL(iso_packets_buffer_init);
/**
* iso_packets_buffer_destroy - frees packet buffer resources
* @b: the buffer structure to free
* @unit: the device at the other end of the stream
*/
void iso_packets_buffer_destroy(struct iso_packets_buffer *b,
struct fw_unit *unit)
{
fw_iso_buffer_destroy(&b->iso_buffer, fw_parent_device(unit)->card);
kfree(b->packets);
}
EXPORT_SYMBOL(iso_packets_buffer_destroy);

View file

@ -0,0 +1,26 @@
#ifndef SOUND_FIREWIRE_PACKETS_BUFFER_H_INCLUDED
#define SOUND_FIREWIRE_PACKETS_BUFFER_H_INCLUDED
#include <linux/dma-mapping.h>
#include <linux/firewire.h>
/**
* struct iso_packets_buffer - manages a buffer for many packets
* @iso_buffer: the memory containing the packets
* @packets: an array, with each element pointing to one packet
*/
struct iso_packets_buffer {
struct fw_iso_buffer iso_buffer;
struct {
void *buffer;
unsigned int offset;
} *packets;
};
int iso_packets_buffer_init(struct iso_packets_buffer *b, struct fw_unit *unit,
unsigned int count, unsigned int packet_size,
enum dma_data_direction direction);
void iso_packets_buffer_destroy(struct iso_packets_buffer *b,
struct fw_unit *unit);
#endif

530
sound/firewire/scs1x.c Normal file
View file

@ -0,0 +1,530 @@
/*
* Stanton Control System 1 MIDI driver
*
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include <linux/device.h>
#include <linux/firewire.h>
#include <linux/firewire-constants.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/wait.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/rawmidi.h>
#include "lib.h"
#define OUI_STANTON 0x001260
#define MODEL_SCS_1M 0x001000
#define MODEL_SCS_1D 0x002000
#define HSS1394_ADDRESS 0xc007dedadadaULL
#define HSS1394_MAX_PACKET_SIZE 64
#define HSS1394_TAG_USER_DATA 0x00
#define HSS1394_TAG_CHANGE_ADDRESS 0xf1
struct scs {
struct snd_card *card;
struct fw_unit *unit;
struct fw_address_handler hss_handler;
struct fw_transaction transaction;
bool transaction_running;
bool output_idle;
u8 output_status;
u8 output_bytes;
bool output_escaped;
bool output_escape_high_nibble;
u8 input_escape_count;
struct snd_rawmidi_substream *output;
struct snd_rawmidi_substream *input;
struct tasklet_struct tasklet;
wait_queue_head_t idle_wait;
u8 *buffer;
};
static const u8 sysex_escape_prefix[] = {
0xf0, /* SysEx begin */
0x00, 0x01, 0x60, /* Stanton DJ */
0x48, 0x53, 0x53, /* "HSS" */
};
static int scs_output_open(struct snd_rawmidi_substream *stream)
{
struct scs *scs = stream->rmidi->private_data;
scs->output_status = 0;
scs->output_bytes = 1;
scs->output_escaped = false;
return 0;
}
static int scs_output_close(struct snd_rawmidi_substream *stream)
{
return 0;
}
static void scs_output_trigger(struct snd_rawmidi_substream *stream, int up)
{
struct scs *scs = stream->rmidi->private_data;
ACCESS_ONCE(scs->output) = up ? stream : NULL;
if (up) {
scs->output_idle = false;
tasklet_schedule(&scs->tasklet);
}
}
static void scs_write_callback(struct fw_card *card, int rcode,
void *data, size_t length, void *callback_data)
{
struct scs *scs = callback_data;
if (rcode == RCODE_GENERATION) {
/* TODO: retry this packet */
}
scs->transaction_running = false;
tasklet_schedule(&scs->tasklet);
}
static bool is_valid_running_status(u8 status)
{
return status >= 0x80 && status <= 0xef;
}
static bool is_one_byte_cmd(u8 status)
{
return status == 0xf6 ||
status >= 0xf8;
}
static bool is_two_bytes_cmd(u8 status)
{
return (status >= 0xc0 && status <= 0xdf) ||
status == 0xf1 ||
status == 0xf3;
}
static bool is_three_bytes_cmd(u8 status)
{
return (status >= 0x80 && status <= 0xbf) ||
(status >= 0xe0 && status <= 0xef) ||
status == 0xf2;
}
static bool is_invalid_cmd(u8 status)
{
return status == 0xf4 ||
status == 0xf5 ||
status == 0xf9 ||
status == 0xfd;
}
static void scs_output_tasklet(unsigned long data)
{
struct scs *scs = (void *)data;
struct snd_rawmidi_substream *stream;
unsigned int i;
u8 byte;
struct fw_device *dev;
int generation;
if (scs->transaction_running)
return;
stream = ACCESS_ONCE(scs->output);
if (!stream) {
scs->output_idle = true;
wake_up(&scs->idle_wait);
return;
}
i = scs->output_bytes;
for (;;) {
if (snd_rawmidi_transmit(stream, &byte, 1) != 1) {
scs->output_bytes = i;
scs->output_idle = true;
wake_up(&scs->idle_wait);
return;
}
/*
* Convert from real MIDI to what I think the device expects (no
* running status, one command per packet, unescaped SysExs).
*/
if (scs->output_escaped && byte < 0x80) {
if (scs->output_escape_high_nibble) {
if (i < HSS1394_MAX_PACKET_SIZE) {
scs->buffer[i] = byte << 4;
scs->output_escape_high_nibble = false;
}
} else {
scs->buffer[i++] |= byte & 0x0f;
scs->output_escape_high_nibble = true;
}
} else if (byte < 0x80) {
if (i == 1) {
if (!is_valid_running_status(scs->output_status))
continue;
scs->buffer[0] = HSS1394_TAG_USER_DATA;
scs->buffer[i++] = scs->output_status;
}
scs->buffer[i++] = byte;
if ((i == 3 && is_two_bytes_cmd(scs->output_status)) ||
(i == 4 && is_three_bytes_cmd(scs->output_status)))
break;
if (i == 1 + ARRAY_SIZE(sysex_escape_prefix) &&
!memcmp(scs->buffer + 1, sysex_escape_prefix,
ARRAY_SIZE(sysex_escape_prefix))) {
scs->output_escaped = true;
scs->output_escape_high_nibble = true;
i = 0;
}
if (i >= HSS1394_MAX_PACKET_SIZE)
i = 1;
} else if (byte == 0xf7) {
if (scs->output_escaped) {
if (i >= 1 && scs->output_escape_high_nibble &&
scs->buffer[0] != HSS1394_TAG_CHANGE_ADDRESS)
break;
} else {
if (i > 1 && scs->output_status == 0xf0) {
scs->buffer[i++] = 0xf7;
break;
}
}
i = 1;
scs->output_escaped = false;
} else if (!is_invalid_cmd(byte) &&
byte < 0xf8) {
i = 1;
scs->buffer[0] = HSS1394_TAG_USER_DATA;
scs->buffer[i++] = byte;
scs->output_status = byte;
scs->output_escaped = false;
if (is_one_byte_cmd(byte))
break;
}
}
scs->output_bytes = 1;
scs->output_escaped = false;
scs->transaction_running = true;
dev = fw_parent_device(scs->unit);
generation = dev->generation;
smp_rmb(); /* node_id vs. generation */
fw_send_request(dev->card, &scs->transaction, TCODE_WRITE_BLOCK_REQUEST,
dev->node_id, generation, dev->max_speed,
HSS1394_ADDRESS, scs->buffer, i,
scs_write_callback, scs);
}
static void scs_output_drain(struct snd_rawmidi_substream *stream)
{
struct scs *scs = stream->rmidi->private_data;
wait_event(scs->idle_wait, scs->output_idle);
}
static struct snd_rawmidi_ops output_ops = {
.open = scs_output_open,
.close = scs_output_close,
.trigger = scs_output_trigger,
.drain = scs_output_drain,
};
static int scs_input_open(struct snd_rawmidi_substream *stream)
{
struct scs *scs = stream->rmidi->private_data;
scs->input_escape_count = 0;
return 0;
}
static int scs_input_close(struct snd_rawmidi_substream *stream)
{
return 0;
}
static void scs_input_trigger(struct snd_rawmidi_substream *stream, int up)
{
struct scs *scs = stream->rmidi->private_data;
ACCESS_ONCE(scs->input) = up ? stream : NULL;
}
static void scs_input_escaped_byte(struct snd_rawmidi_substream *stream,
u8 byte)
{
u8 nibbles[2];
nibbles[0] = byte >> 4;
nibbles[1] = byte & 0x0f;
snd_rawmidi_receive(stream, nibbles, 2);
}
static void scs_input_midi_byte(struct scs *scs,
struct snd_rawmidi_substream *stream,
u8 byte)
{
if (scs->input_escape_count > 0) {
scs_input_escaped_byte(stream, byte);
scs->input_escape_count--;
if (scs->input_escape_count == 0)
snd_rawmidi_receive(stream, (const u8[]) { 0xf7 }, 1);
} else if (byte == 0xf9) {
snd_rawmidi_receive(stream, sysex_escape_prefix,
ARRAY_SIZE(sysex_escape_prefix));
scs_input_escaped_byte(stream, 0x00);
scs_input_escaped_byte(stream, 0xf9);
scs->input_escape_count = 3;
} else {
snd_rawmidi_receive(stream, &byte, 1);
}
}
static void scs_input_packet(struct scs *scs,
struct snd_rawmidi_substream *stream,
const u8 *data, unsigned int bytes)
{
unsigned int i;
if (data[0] == HSS1394_TAG_USER_DATA) {
for (i = 1; i < bytes; ++i)
scs_input_midi_byte(scs, stream, data[i]);
} else {
snd_rawmidi_receive(stream, sysex_escape_prefix,
ARRAY_SIZE(sysex_escape_prefix));
for (i = 0; i < bytes; ++i)
scs_input_escaped_byte(stream, data[i]);
snd_rawmidi_receive(stream, (const u8[]) { 0xf7 }, 1);
}
}
static struct snd_rawmidi_ops input_ops = {
.open = scs_input_open,
.close = scs_input_close,
.trigger = scs_input_trigger,
};
static int scs_create_midi(struct scs *scs)
{
struct snd_rawmidi *rmidi;
int err;
err = snd_rawmidi_new(scs->card, "SCS.1x", 0, 1, 1, &rmidi);
if (err < 0)
return err;
snprintf(rmidi->name, sizeof(rmidi->name),
"%s MIDI", scs->card->shortname);
rmidi->info_flags = SNDRV_RAWMIDI_INFO_OUTPUT |
SNDRV_RAWMIDI_INFO_INPUT |
SNDRV_RAWMIDI_INFO_DUPLEX;
rmidi->private_data = scs;
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &output_ops);
snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &input_ops);
return 0;
}
static void handle_hss(struct fw_card *card, struct fw_request *request,
int tcode, int destination, int source, int generation,
unsigned long long offset, void *data, size_t length,
void *callback_data)
{
struct scs *scs = callback_data;
struct snd_rawmidi_substream *stream;
if (offset != scs->hss_handler.offset) {
fw_send_response(card, request, RCODE_ADDRESS_ERROR);
return;
}
if (tcode != TCODE_WRITE_QUADLET_REQUEST &&
tcode != TCODE_WRITE_BLOCK_REQUEST) {
fw_send_response(card, request, RCODE_TYPE_ERROR);
return;
}
if (length >= 1) {
stream = ACCESS_ONCE(scs->input);
if (stream)
scs_input_packet(scs, stream, data, length);
}
fw_send_response(card, request, RCODE_COMPLETE);
}
static int scs_init_hss_address(struct scs *scs)
{
__be64 data;
int err;
data = cpu_to_be64(((u64)HSS1394_TAG_CHANGE_ADDRESS << 56) |
scs->hss_handler.offset);
err = snd_fw_transaction(scs->unit, TCODE_WRITE_BLOCK_REQUEST,
HSS1394_ADDRESS, &data, 8, 0);
if (err < 0)
dev_err(&scs->unit->device, "HSS1394 communication failed\n");
return err;
}
static void scs_card_free(struct snd_card *card)
{
struct scs *scs = card->private_data;
fw_core_remove_address_handler(&scs->hss_handler);
kfree(scs->buffer);
}
static int scs_probe(struct fw_unit *unit, const struct ieee1394_device_id *id)
{
struct fw_device *fw_dev = fw_parent_device(unit);
struct snd_card *card;
struct scs *scs;
int err;
err = snd_card_new(&unit->device, -16, NULL, THIS_MODULE,
sizeof(*scs), &card);
if (err < 0)
return err;
scs = card->private_data;
scs->card = card;
scs->unit = unit;
tasklet_init(&scs->tasklet, scs_output_tasklet, (unsigned long)scs);
init_waitqueue_head(&scs->idle_wait);
scs->output_idle = true;
scs->buffer = kmalloc(HSS1394_MAX_PACKET_SIZE, GFP_KERNEL);
if (!scs->buffer) {
err = -ENOMEM;
goto err_card;
}
scs->hss_handler.length = HSS1394_MAX_PACKET_SIZE;
scs->hss_handler.address_callback = handle_hss;
scs->hss_handler.callback_data = scs;
err = fw_core_add_address_handler(&scs->hss_handler,
&fw_high_memory_region);
if (err < 0)
goto err_buffer;
card->private_free = scs_card_free;
strcpy(card->driver, "SCS.1x");
strcpy(card->shortname, "SCS.1x");
fw_csr_string(unit->directory, CSR_MODEL,
card->shortname, sizeof(card->shortname));
snprintf(card->longname, sizeof(card->longname),
"Stanton DJ %s (GUID %08x%08x) at %s, S%d",
card->shortname, fw_dev->config_rom[3], fw_dev->config_rom[4],
dev_name(&unit->device), 100 << fw_dev->max_speed);
strcpy(card->mixername, card->shortname);
err = scs_init_hss_address(scs);
if (err < 0)
goto err_card;
err = scs_create_midi(scs);
if (err < 0)
goto err_card;
err = snd_card_register(card);
if (err < 0)
goto err_card;
dev_set_drvdata(&unit->device, scs);
return 0;
err_buffer:
kfree(scs->buffer);
err_card:
snd_card_free(card);
return err;
}
static void scs_update(struct fw_unit *unit)
{
struct scs *scs = dev_get_drvdata(&unit->device);
int generation;
__be64 data;
data = cpu_to_be64(((u64)HSS1394_TAG_CHANGE_ADDRESS << 56) |
scs->hss_handler.offset);
generation = fw_parent_device(unit)->generation;
smp_rmb(); /* node_id vs. generation */
snd_fw_transaction(scs->unit, TCODE_WRITE_BLOCK_REQUEST,
HSS1394_ADDRESS, &data, 8,
FW_FIXED_GENERATION | generation);
}
static void scs_remove(struct fw_unit *unit)
{
struct scs *scs = dev_get_drvdata(&unit->device);
snd_card_disconnect(scs->card);
ACCESS_ONCE(scs->output) = NULL;
ACCESS_ONCE(scs->input) = NULL;
wait_event(scs->idle_wait, scs->output_idle);
tasklet_kill(&scs->tasklet);
snd_card_free_when_closed(scs->card);
}
static const struct ieee1394_device_id scs_id_table[] = {
{
.match_flags = IEEE1394_MATCH_VENDOR_ID |
IEEE1394_MATCH_MODEL_ID,
.vendor_id = OUI_STANTON,
.model_id = MODEL_SCS_1M,
},
{
.match_flags = IEEE1394_MATCH_VENDOR_ID |
IEEE1394_MATCH_MODEL_ID,
.vendor_id = OUI_STANTON,
.model_id = MODEL_SCS_1D,
},
{}
};
MODULE_DEVICE_TABLE(ieee1394, scs_id_table);
MODULE_DESCRIPTION("SCS.1x MIDI driver");
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
MODULE_LICENSE("GPL v2");
static struct fw_driver scs_driver = {
.driver = {
.owner = THIS_MODULE,
.name = KBUILD_MODNAME,
.bus = &fw_bus_type,
},
.probe = scs_probe,
.update = scs_update,
.remove = scs_remove,
.id_table = scs_id_table,
};
static int __init alsa_scs1x_init(void)
{
return driver_register(&scs_driver.driver);
}
static void __exit alsa_scs1x_exit(void)
{
driver_unregister(&scs_driver.driver);
}
module_init(alsa_scs1x_init);
module_exit(alsa_scs1x_exit);

792
sound/firewire/speakers.c Normal file
View file

@ -0,0 +1,792 @@
/*
* OXFW970-based speakers driver
*
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include <linux/device.h>
#include <linux/firewire.h>
#include <linux/firewire-constants.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <sound/control.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include "cmp.h"
#include "fcp.h"
#include "amdtp.h"
#include "lib.h"
#define OXFORD_FIRMWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x50000)
/* 0x970?vvvv or 0x971?vvvv, where vvvv = firmware version */
#define OXFORD_HARDWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x90020)
#define OXFORD_HARDWARE_ID_OXFW970 0x39443841
#define OXFORD_HARDWARE_ID_OXFW971 0x39373100
#define VENDOR_GRIFFIN 0x001292
#define VENDOR_LACIE 0x00d04b
#define SPECIFIER_1394TA 0x00a02d
#define VERSION_AVC 0x010001
struct device_info {
const char *driver_name;
const char *short_name;
const char *long_name;
int (*pcm_constraints)(struct snd_pcm_runtime *runtime);
unsigned int mixer_channels;
u8 mute_fb_id;
u8 volume_fb_id;
};
struct fwspk {
struct snd_card *card;
struct fw_unit *unit;
const struct device_info *device_info;
struct mutex mutex;
struct cmp_connection connection;
struct amdtp_stream stream;
bool mute;
s16 volume[6];
s16 volume_min;
s16 volume_max;
};
MODULE_DESCRIPTION("FireWire speakers driver");
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
MODULE_LICENSE("GPL v2");
static int firewave_rate_constraint(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
static unsigned int stereo_rates[] = { 48000, 96000 };
struct snd_interval *channels =
hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
struct snd_interval *rate =
hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
/* two channels work only at 48/96 kHz */
if (snd_interval_max(channels) < 6)
return snd_interval_list(rate, 2, stereo_rates, 0);
return 0;
}
static int firewave_channels_constraint(struct snd_pcm_hw_params *params,
struct snd_pcm_hw_rule *rule)
{
static const struct snd_interval all_channels = { .min = 6, .max = 6 };
struct snd_interval *rate =
hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE);
struct snd_interval *channels =
hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS);
/* 32/44.1 kHz work only with all six channels */
if (snd_interval_max(rate) < 48000)
return snd_interval_refine(channels, &all_channels);
return 0;
}
static int firewave_constraints(struct snd_pcm_runtime *runtime)
{
static unsigned int channels_list[] = { 2, 6 };
static struct snd_pcm_hw_constraint_list channels_list_constraint = {
.count = 2,
.list = channels_list,
};
int err;
runtime->hw.rates = SNDRV_PCM_RATE_32000 |
SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_96000;
runtime->hw.channels_max = 6;
err = snd_pcm_hw_constraint_list(runtime, 0,
SNDRV_PCM_HW_PARAM_CHANNELS,
&channels_list_constraint);
if (err < 0)
return err;
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
firewave_rate_constraint, NULL,
SNDRV_PCM_HW_PARAM_CHANNELS, -1);
if (err < 0)
return err;
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
firewave_channels_constraint, NULL,
SNDRV_PCM_HW_PARAM_RATE, -1);
if (err < 0)
return err;
return 0;
}
static int lacie_speakers_constraints(struct snd_pcm_runtime *runtime)
{
runtime->hw.rates = SNDRV_PCM_RATE_32000 |
SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_88200 |
SNDRV_PCM_RATE_96000;
return 0;
}
static int fwspk_open(struct snd_pcm_substream *substream)
{
static const struct snd_pcm_hardware hardware = {
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_MMAP_VALID |
SNDRV_PCM_INFO_BATCH |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER,
.formats = AMDTP_OUT_PCM_FORMAT_BITS,
.channels_min = 2,
.channels_max = 2,
.buffer_bytes_max = 4 * 1024 * 1024,
.period_bytes_min = 1,
.period_bytes_max = UINT_MAX,
.periods_min = 1,
.periods_max = UINT_MAX,
};
struct fwspk *fwspk = substream->private_data;
struct snd_pcm_runtime *runtime = substream->runtime;
int err;
runtime->hw = hardware;
err = fwspk->device_info->pcm_constraints(runtime);
if (err < 0)
return err;
err = snd_pcm_limit_hw_rates(runtime);
if (err < 0)
return err;
err = amdtp_stream_add_pcm_hw_constraints(&fwspk->stream, runtime);
if (err < 0)
return err;
return 0;
}
static int fwspk_close(struct snd_pcm_substream *substream)
{
return 0;
}
static void fwspk_stop_stream(struct fwspk *fwspk)
{
if (amdtp_stream_running(&fwspk->stream)) {
amdtp_stream_stop(&fwspk->stream);
cmp_connection_break(&fwspk->connection);
}
}
static int fwspk_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct fwspk *fwspk = substream->private_data;
int err;
mutex_lock(&fwspk->mutex);
fwspk_stop_stream(fwspk);
mutex_unlock(&fwspk->mutex);
err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
params_buffer_bytes(hw_params));
if (err < 0)
goto error;
amdtp_stream_set_parameters(&fwspk->stream,
params_rate(hw_params),
params_channels(hw_params),
0);
amdtp_stream_set_pcm_format(&fwspk->stream,
params_format(hw_params));
err = avc_general_set_sig_fmt(fwspk->unit, params_rate(hw_params),
AVC_GENERAL_PLUG_DIR_IN, 0);
if (err < 0) {
dev_err(&fwspk->unit->device, "failed to set sample rate\n");
goto err_buffer;
}
return 0;
err_buffer:
snd_pcm_lib_free_vmalloc_buffer(substream);
error:
return err;
}
static int fwspk_hw_free(struct snd_pcm_substream *substream)
{
struct fwspk *fwspk = substream->private_data;
mutex_lock(&fwspk->mutex);
fwspk_stop_stream(fwspk);
mutex_unlock(&fwspk->mutex);
return snd_pcm_lib_free_vmalloc_buffer(substream);
}
static int fwspk_prepare(struct snd_pcm_substream *substream)
{
struct fwspk *fwspk = substream->private_data;
int err;
mutex_lock(&fwspk->mutex);
if (amdtp_streaming_error(&fwspk->stream))
fwspk_stop_stream(fwspk);
if (!amdtp_stream_running(&fwspk->stream)) {
err = cmp_connection_establish(&fwspk->connection,
amdtp_stream_get_max_payload(&fwspk->stream));
if (err < 0)
goto err_mutex;
err = amdtp_stream_start(&fwspk->stream,
fwspk->connection.resources.channel,
fwspk->connection.speed);
if (err < 0)
goto err_connection;
}
mutex_unlock(&fwspk->mutex);
amdtp_stream_pcm_prepare(&fwspk->stream);
return 0;
err_connection:
cmp_connection_break(&fwspk->connection);
err_mutex:
mutex_unlock(&fwspk->mutex);
return err;
}
static int fwspk_trigger(struct snd_pcm_substream *substream, int cmd)
{
struct fwspk *fwspk = substream->private_data;
struct snd_pcm_substream *pcm;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
pcm = substream;
break;
case SNDRV_PCM_TRIGGER_STOP:
pcm = NULL;
break;
default:
return -EINVAL;
}
amdtp_stream_pcm_trigger(&fwspk->stream, pcm);
return 0;
}
static snd_pcm_uframes_t fwspk_pointer(struct snd_pcm_substream *substream)
{
struct fwspk *fwspk = substream->private_data;
return amdtp_stream_pcm_pointer(&fwspk->stream);
}
static int fwspk_create_pcm(struct fwspk *fwspk)
{
static struct snd_pcm_ops ops = {
.open = fwspk_open,
.close = fwspk_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = fwspk_hw_params,
.hw_free = fwspk_hw_free,
.prepare = fwspk_prepare,
.trigger = fwspk_trigger,
.pointer = fwspk_pointer,
.page = snd_pcm_lib_get_vmalloc_page,
.mmap = snd_pcm_lib_mmap_vmalloc,
};
struct snd_pcm *pcm;
int err;
err = snd_pcm_new(fwspk->card, "OXFW970", 0, 1, 0, &pcm);
if (err < 0)
return err;
pcm->private_data = fwspk;
strcpy(pcm->name, fwspk->device_info->short_name);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &ops);
return 0;
}
enum control_action { CTL_READ, CTL_WRITE };
enum control_attribute {
CTL_MIN = 0x02,
CTL_MAX = 0x03,
CTL_CURRENT = 0x10,
};
static int fwspk_mute_command(struct fwspk *fwspk, bool *value,
enum control_action action)
{
u8 *buf;
u8 response_ok;
int err;
buf = kmalloc(11, GFP_KERNEL);
if (!buf)
return -ENOMEM;
if (action == CTL_READ) {
buf[0] = 0x01; /* AV/C, STATUS */
response_ok = 0x0c; /* STABLE */
} else {
buf[0] = 0x00; /* AV/C, CONTROL */
response_ok = 0x09; /* ACCEPTED */
}
buf[1] = 0x08; /* audio unit 0 */
buf[2] = 0xb8; /* FUNCTION BLOCK */
buf[3] = 0x81; /* function block type: feature */
buf[4] = fwspk->device_info->mute_fb_id; /* function block ID */
buf[5] = 0x10; /* control attribute: current */
buf[6] = 0x02; /* selector length */
buf[7] = 0x00; /* audio channel number */
buf[8] = 0x01; /* control selector: mute */
buf[9] = 0x01; /* control data length */
if (action == CTL_READ)
buf[10] = 0xff;
else
buf[10] = *value ? 0x70 : 0x60;
err = fcp_avc_transaction(fwspk->unit, buf, 11, buf, 11, 0x3fe);
if (err < 0)
goto error;
if (err < 11) {
dev_err(&fwspk->unit->device, "short FCP response\n");
err = -EIO;
goto error;
}
if (buf[0] != response_ok) {
dev_err(&fwspk->unit->device, "mute command failed\n");
err = -EIO;
goto error;
}
if (action == CTL_READ)
*value = buf[10] == 0x70;
err = 0;
error:
kfree(buf);
return err;
}
static int fwspk_volume_command(struct fwspk *fwspk, s16 *value,
unsigned int channel,
enum control_attribute attribute,
enum control_action action)
{
u8 *buf;
u8 response_ok;
int err;
buf = kmalloc(12, GFP_KERNEL);
if (!buf)
return -ENOMEM;
if (action == CTL_READ) {
buf[0] = 0x01; /* AV/C, STATUS */
response_ok = 0x0c; /* STABLE */
} else {
buf[0] = 0x00; /* AV/C, CONTROL */
response_ok = 0x09; /* ACCEPTED */
}
buf[1] = 0x08; /* audio unit 0 */
buf[2] = 0xb8; /* FUNCTION BLOCK */
buf[3] = 0x81; /* function block type: feature */
buf[4] = fwspk->device_info->volume_fb_id; /* function block ID */
buf[5] = attribute; /* control attribute */
buf[6] = 0x02; /* selector length */
buf[7] = channel; /* audio channel number */
buf[8] = 0x02; /* control selector: volume */
buf[9] = 0x02; /* control data length */
if (action == CTL_READ) {
buf[10] = 0xff;
buf[11] = 0xff;
} else {
buf[10] = *value >> 8;
buf[11] = *value;
}
err = fcp_avc_transaction(fwspk->unit, buf, 12, buf, 12, 0x3fe);
if (err < 0)
goto error;
if (err < 12) {
dev_err(&fwspk->unit->device, "short FCP response\n");
err = -EIO;
goto error;
}
if (buf[0] != response_ok) {
dev_err(&fwspk->unit->device, "volume command failed\n");
err = -EIO;
goto error;
}
if (action == CTL_READ)
*value = (buf[10] << 8) | buf[11];
err = 0;
error:
kfree(buf);
return err;
}
static int fwspk_mute_get(struct snd_kcontrol *control,
struct snd_ctl_elem_value *value)
{
struct fwspk *fwspk = control->private_data;
value->value.integer.value[0] = !fwspk->mute;
return 0;
}
static int fwspk_mute_put(struct snd_kcontrol *control,
struct snd_ctl_elem_value *value)
{
struct fwspk *fwspk = control->private_data;
bool mute;
int err;
mute = !value->value.integer.value[0];
if (mute == fwspk->mute)
return 0;
err = fwspk_mute_command(fwspk, &mute, CTL_WRITE);
if (err < 0)
return err;
fwspk->mute = mute;
return 1;
}
static int fwspk_volume_info(struct snd_kcontrol *control,
struct snd_ctl_elem_info *info)
{
struct fwspk *fwspk = control->private_data;
info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
info->count = fwspk->device_info->mixer_channels;
info->value.integer.min = fwspk->volume_min;
info->value.integer.max = fwspk->volume_max;
return 0;
}
static const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 };
static int fwspk_volume_get(struct snd_kcontrol *control,
struct snd_ctl_elem_value *value)
{
struct fwspk *fwspk = control->private_data;
unsigned int i;
for (i = 0; i < fwspk->device_info->mixer_channels; ++i)
value->value.integer.value[channel_map[i]] = fwspk->volume[i];
return 0;
}
static int fwspk_volume_put(struct snd_kcontrol *control,
struct snd_ctl_elem_value *value)
{
struct fwspk *fwspk = control->private_data;
unsigned int i, changed_channels;
bool equal_values = true;
s16 volume;
int err;
for (i = 0; i < fwspk->device_info->mixer_channels; ++i) {
if (value->value.integer.value[i] < fwspk->volume_min ||
value->value.integer.value[i] > fwspk->volume_max)
return -EINVAL;
if (value->value.integer.value[i] !=
value->value.integer.value[0])
equal_values = false;
}
changed_channels = 0;
for (i = 0; i < fwspk->device_info->mixer_channels; ++i)
if (value->value.integer.value[channel_map[i]] !=
fwspk->volume[i])
changed_channels |= 1 << (i + 1);
if (equal_values && changed_channels != 0)
changed_channels = 1 << 0;
for (i = 0; i <= fwspk->device_info->mixer_channels; ++i) {
volume = value->value.integer.value[channel_map[i ? i - 1 : 0]];
if (changed_channels & (1 << i)) {
err = fwspk_volume_command(fwspk, &volume, i,
CTL_CURRENT, CTL_WRITE);
if (err < 0)
return err;
}
if (i > 0)
fwspk->volume[i - 1] = volume;
}
return changed_channels != 0;
}
static int fwspk_create_mixer(struct fwspk *fwspk)
{
static const struct snd_kcontrol_new controls[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Playback Switch",
.info = snd_ctl_boolean_mono_info,
.get = fwspk_mute_get,
.put = fwspk_mute_put,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Playback Volume",
.info = fwspk_volume_info,
.get = fwspk_volume_get,
.put = fwspk_volume_put,
},
};
unsigned int i, first_ch;
int err;
err = fwspk_volume_command(fwspk, &fwspk->volume_min,
0, CTL_MIN, CTL_READ);
if (err < 0)
return err;
err = fwspk_volume_command(fwspk, &fwspk->volume_max,
0, CTL_MAX, CTL_READ);
if (err < 0)
return err;
err = fwspk_mute_command(fwspk, &fwspk->mute, CTL_READ);
if (err < 0)
return err;
first_ch = fwspk->device_info->mixer_channels == 1 ? 0 : 1;
for (i = 0; i < fwspk->device_info->mixer_channels; ++i) {
err = fwspk_volume_command(fwspk, &fwspk->volume[i],
first_ch + i, CTL_CURRENT, CTL_READ);
if (err < 0)
return err;
}
for (i = 0; i < ARRAY_SIZE(controls); ++i) {
err = snd_ctl_add(fwspk->card,
snd_ctl_new1(&controls[i], fwspk));
if (err < 0)
return err;
}
return 0;
}
static u32 fwspk_read_firmware_version(struct fw_unit *unit)
{
__be32 data;
int err;
err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST,
OXFORD_FIRMWARE_ID_ADDRESS, &data, 4, 0);
return err >= 0 ? be32_to_cpu(data) : 0;
}
static void fwspk_card_free(struct snd_card *card)
{
struct fwspk *fwspk = card->private_data;
amdtp_stream_destroy(&fwspk->stream);
cmp_connection_destroy(&fwspk->connection);
fw_unit_put(fwspk->unit);
mutex_destroy(&fwspk->mutex);
}
static int fwspk_probe(struct fw_unit *unit,
const struct ieee1394_device_id *id)
{
struct fw_device *fw_dev = fw_parent_device(unit);
struct snd_card *card;
struct fwspk *fwspk;
u32 firmware;
int err;
err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE,
sizeof(*fwspk), &card);
if (err < 0)
return err;
fwspk = card->private_data;
fwspk->card = card;
mutex_init(&fwspk->mutex);
fwspk->unit = fw_unit_get(unit);
fwspk->device_info = (const struct device_info *)id->driver_data;
err = cmp_connection_init(&fwspk->connection, unit, CMP_INPUT, 0);
if (err < 0)
goto err_unit;
err = amdtp_stream_init(&fwspk->stream, unit, AMDTP_OUT_STREAM,
CIP_NONBLOCKING);
if (err < 0)
goto err_connection;
card->private_free = fwspk_card_free;
strcpy(card->driver, fwspk->device_info->driver_name);
strcpy(card->shortname, fwspk->device_info->short_name);
firmware = fwspk_read_firmware_version(unit);
snprintf(card->longname, sizeof(card->longname),
"%s (OXFW%x %04x), GUID %08x%08x at %s, S%d",
fwspk->device_info->long_name,
firmware >> 20, firmware & 0xffff,
fw_dev->config_rom[3], fw_dev->config_rom[4],
dev_name(&unit->device), 100 << fw_dev->max_speed);
strcpy(card->mixername, "OXFW970");
err = fwspk_create_pcm(fwspk);
if (err < 0)
goto error;
err = fwspk_create_mixer(fwspk);
if (err < 0)
goto error;
err = snd_card_register(card);
if (err < 0)
goto error;
dev_set_drvdata(&unit->device, fwspk);
return 0;
err_connection:
cmp_connection_destroy(&fwspk->connection);
err_unit:
fw_unit_put(fwspk->unit);
mutex_destroy(&fwspk->mutex);
error:
snd_card_free(card);
return err;
}
static void fwspk_bus_reset(struct fw_unit *unit)
{
struct fwspk *fwspk = dev_get_drvdata(&unit->device);
fcp_bus_reset(fwspk->unit);
if (cmp_connection_update(&fwspk->connection) < 0) {
amdtp_stream_pcm_abort(&fwspk->stream);
mutex_lock(&fwspk->mutex);
fwspk_stop_stream(fwspk);
mutex_unlock(&fwspk->mutex);
return;
}
amdtp_stream_update(&fwspk->stream);
}
static void fwspk_remove(struct fw_unit *unit)
{
struct fwspk *fwspk = dev_get_drvdata(&unit->device);
amdtp_stream_pcm_abort(&fwspk->stream);
snd_card_disconnect(fwspk->card);
mutex_lock(&fwspk->mutex);
fwspk_stop_stream(fwspk);
mutex_unlock(&fwspk->mutex);
snd_card_free_when_closed(fwspk->card);
}
static const struct device_info griffin_firewave = {
.driver_name = "FireWave",
.short_name = "FireWave",
.long_name = "Griffin FireWave Surround",
.pcm_constraints = firewave_constraints,
.mixer_channels = 6,
.mute_fb_id = 0x01,
.volume_fb_id = 0x02,
};
static const struct device_info lacie_speakers = {
.driver_name = "FWSpeakers",
.short_name = "FireWire Speakers",
.long_name = "LaCie FireWire Speakers",
.pcm_constraints = lacie_speakers_constraints,
.mixer_channels = 1,
.mute_fb_id = 0x01,
.volume_fb_id = 0x01,
};
static const struct ieee1394_device_id fwspk_id_table[] = {
{
.match_flags = IEEE1394_MATCH_VENDOR_ID |
IEEE1394_MATCH_MODEL_ID |
IEEE1394_MATCH_SPECIFIER_ID |
IEEE1394_MATCH_VERSION,
.vendor_id = VENDOR_GRIFFIN,
.model_id = 0x00f970,
.specifier_id = SPECIFIER_1394TA,
.version = VERSION_AVC,
.driver_data = (kernel_ulong_t)&griffin_firewave,
},
{
.match_flags = IEEE1394_MATCH_VENDOR_ID |
IEEE1394_MATCH_MODEL_ID |
IEEE1394_MATCH_SPECIFIER_ID |
IEEE1394_MATCH_VERSION,
.vendor_id = VENDOR_LACIE,
.model_id = 0x00f970,
.specifier_id = SPECIFIER_1394TA,
.version = VERSION_AVC,
.driver_data = (kernel_ulong_t)&lacie_speakers,
},
{ }
};
MODULE_DEVICE_TABLE(ieee1394, fwspk_id_table);
static struct fw_driver fwspk_driver = {
.driver = {
.owner = THIS_MODULE,
.name = KBUILD_MODNAME,
.bus = &fw_bus_type,
},
.probe = fwspk_probe,
.update = fwspk_bus_reset,
.remove = fwspk_remove,
.id_table = fwspk_id_table,
};
static int __init alsa_fwspk_init(void)
{
return driver_register(&fwspk_driver.driver);
}
static void __exit alsa_fwspk_exit(void)
{
driver_unregister(&fwspk_driver.driver);
}
module_init(alsa_fwspk_init);
module_exit(alsa_fwspk_exit);