summaryrefslogtreecommitdiff
path: root/sound/firewire
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2016-01-17 20:05:31 (GMT)
committerLinus Torvalds <torvalds@linux-foundation.org>2016-01-17 20:05:31 (GMT)
commita016af2e70bfca23f2f5de7d8708157b86ea374d (patch)
treebfe3c0c6ea9d52d4ec6ea021b0626a53c83e7d9f /sound/firewire
parente535d74bc50df2357d3253f8f3ca48c66d0d892a (diff)
parentc3b1681375dc6e71d89a3ae00cc3ce9e775a8917 (diff)
downloadlinux-a016af2e70bfca23f2f5de7d8708157b86ea374d.tar.xz
Merge tag 'sound-4.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound
Pull sound updates from Takashi Iwai: "We've had quite busy weeks in this cycle. Looking at ALSA core, the significant changes are a few fixes wrt timer and sequencer ioctls that have been revealed by fuzzer recently. Other than that, ASoC core got a few updates about DAI link handling, but these are rather straightforward refactoring. In drivers scene, ASoC received quite lots of new drivers in addition to bunch of updates for still ongoing Intel Skylake support and topology API. HD-audio gained a new HDMI/DP hotplug notification via component. FireWire got a pile of code refactoring/updates with SCS.1x driver integration. More highlights are shown below. [ NOTE: this contains also many commits for DRM. This is due to the pull of drm stable branch into sound tree, as the base of i915 audio component work for HD-audio. The highlights below don't contain these DRM changes, as these are supposed to be pulled via drm tree in anyway sooner or later. ] Core: - Handful fixes to harden ALSA timer and sequencer ioctls against races reported by syzkaller fuzzer - Irq description string can be unique to each card; only for HD-audio for now ASoC: - Conversion of the array of DAI links to a list for supporting dynamically adding and removing DAI links - Topology API enhancements to make everything more component based and being able to specify PCM links via topology - Some more fixes for the topology code, though it is still not final and ready for enabling in production; we really need to get to the point where that can be done - A pile of changes for Intel SkyLake drivers which hopefully deliver some useful initial functionality for systems with this chipset, though there is more work still to come - Lots of new features and cleanups for the Renesas drivers - ANC support for WM5110 - New drivers: Imagination Technologies IPs, Atmel class D speaker, Cirrus CS47L24 and WM1831, Dialog DA7128, Realtek RT5659 and RT56156, Rockchip RK3036, TI PC3168A, and AMD ACP - Rename PCM1792a driver to be generic pcm179x HD-Audio: - Use audio component for i915 HDMI/DP hotplug handling - On-demand binding with i915 driver - bdl_pos_adj parameter adjustment for Baytrail controllers - Enable power_save_node for CX20722; this shouldn't lead to regression, hopefully - Kabylake HDMI/DP codec support - Quirks for Lenovo E50-80, Dell Latitude E-series, and other Dell machines - A few code refactoring FireWire: - Lots of code cleanup and refactoring - Integrate the support of SCS.1x devices into snd-oxfw driver; snd-scs1x driver is obsoleted USB-audio: - Fix possible NULL dereference at disconnection - A regression fix for Native Instruments devices Misc: - A few code cleanups of fm801 driver" * tag 'sound-4.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound: (722 commits) ALSA: timer: Code cleanup ALSA: timer: Harden slave timer list handling ALSA: hda - Add fixup for Dell Latitidue E6540 ALSA: timer: Fix race among timer ioctls ALSA: hda - add codec support for Kabylake display audio codec ALSA: timer: Fix double unlink of active_list ALSA: usb-audio: Fix mixer ctl regression of Native Instrument devices ALSA: hda - fix the headset mic detection problem for a Dell laptop ALSA: hda - Fix white noise on Dell Latitude E5550 ALSA: hda_intel: add card number to irq description ALSA: seq: Fix race at timer setup and close ALSA: seq: Fix missing NULL check at remove_events ioctl ALSA: usb-audio: Avoid calling usb_autopm_put_interface() at disconnect ASoC: hdac_hdmi: remove unused hdac_hdmi_query_pin_connlist ASoC: AMD: Add missing include file ALSA: hda - Fixup inverted internal mic for Lenovo E50-80 ALSA: usb: Add native DSD support for Oppo HA-1 ASoC: Make aux_dev more like a generic component ASoC: bcm2835: cleanup includes by ordering them alphabetically ASoC: AMD: Manage ACP 2.x SRAM banks power ...
Diffstat (limited to 'sound/firewire')
-rw-r--r--sound/firewire/Kconfig12
-rw-r--r--sound/firewire/Makefile2
-rw-r--r--sound/firewire/dice/dice-transaction.c123
-rw-r--r--sound/firewire/dice/dice.c227
-rw-r--r--sound/firewire/dice/dice.h3
-rw-r--r--sound/firewire/fireworks/fireworks.h4
-rw-r--r--sound/firewire/fireworks/fireworks_midi.c16
-rw-r--r--sound/firewire/fireworks/fireworks_pcm.c28
-rw-r--r--sound/firewire/fireworks/fireworks_stream.c32
-rw-r--r--sound/firewire/oxfw/Makefile4
-rw-r--r--sound/firewire/oxfw/oxfw-scs1x.c406
-rw-r--r--sound/firewire/oxfw/oxfw-spkr.c (renamed from sound/firewire/oxfw/oxfw-control.c)142
-rw-r--r--sound/firewire/oxfw/oxfw.c110
-rw-r--r--sound/firewire/oxfw/oxfw.h23
-rw-r--r--sound/firewire/scs1x.c530
15 files changed, 831 insertions, 831 deletions
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig
index e92a6d9..2a779c2 100644
--- a/sound/firewire/Kconfig
+++ b/sound/firewire/Kconfig
@@ -39,6 +39,7 @@ config SND_OXFW
* Mackie(Loud) d.2 pro/d.4 pro
* Mackie(Loud) U.420/U.420d
* TASCAM FireOne
+ * Stanton Controllers & Systems 1 Deck/Mixer
To compile this driver as a module, choose M here: the module
will be called snd-oxfw.
@@ -53,17 +54,6 @@ config SND_ISIGHT
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
diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile
index f5fb625..003c090 100644
--- a/sound/firewire/Makefile
+++ b/sound/firewire/Makefile
@@ -1,13 +1,11 @@
snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \
fcp.o cmp.o amdtp-stream.o amdtp-am824.o
snd-isight-objs := isight.o
-snd-scs1x-objs := scs1x.o
obj-$(CONFIG_SND_FIREWIRE_LIB) += snd-firewire-lib.o
obj-$(CONFIG_SND_DICE) += dice/
obj-$(CONFIG_SND_OXFW) += oxfw/
obj-$(CONFIG_SND_ISIGHT) += snd-isight.o
-obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o
obj-$(CONFIG_SND_FIREWORKS) += fireworks/
obj-$(CONFIG_SND_BEBOB) += bebob/
obj-$(CONFIG_SND_FIREWIRE_DIGI00X) += digi00x/
diff --git a/sound/firewire/dice/dice-transaction.c b/sound/firewire/dice/dice-transaction.c
index aee7461..a4ff4e0 100644
--- a/sound/firewire/dice/dice-transaction.c
+++ b/sound/firewire/dice/dice-transaction.c
@@ -9,7 +9,7 @@
#include "dice.h"
-#define NOTIFICATION_TIMEOUT_MS 100
+#define NOTIFICATION_TIMEOUT_MS (2 * MSEC_PER_SEC)
static u64 get_subaddr(struct snd_dice *dice, enum snd_dice_addr_type type,
u64 offset)
@@ -65,16 +65,15 @@ static unsigned int get_clock_info(struct snd_dice *dice, __be32 *info)
static int set_clock_info(struct snd_dice *dice,
unsigned int rate, unsigned int source)
{
- unsigned int retries = 3;
unsigned int i;
__be32 info;
u32 mask;
u32 clock;
int err;
-retry:
+
err = get_clock_info(dice, &info);
if (err < 0)
- goto end;
+ return err;
clock = be32_to_cpu(info);
if (source != UINT_MAX) {
@@ -87,10 +86,8 @@ retry:
if (snd_dice_rates[i] == rate)
break;
}
- if (i == ARRAY_SIZE(snd_dice_rates)) {
- err = -EINVAL;
- goto end;
- }
+ if (i == ARRAY_SIZE(snd_dice_rates))
+ return -EINVAL;
mask = CLOCK_RATE_MASK;
clock &= ~mask;
@@ -104,25 +101,13 @@ retry:
err = snd_dice_transaction_write_global(dice, GLOBAL_CLOCK_SELECT,
&info, 4);
if (err < 0)
- goto end;
+ return err;
- /* Timeout means it's invalid request, probably bus reset occurred. */
if (wait_for_completion_timeout(&dice->clock_accepted,
- msecs_to_jiffies(NOTIFICATION_TIMEOUT_MS)) == 0) {
- if (retries-- == 0) {
- err = -ETIMEDOUT;
- goto end;
- }
+ msecs_to_jiffies(NOTIFICATION_TIMEOUT_MS)) == 0)
+ return -ETIMEDOUT;
- err = snd_dice_transaction_reinit(dice);
- if (err < 0)
- goto end;
-
- msleep(500); /* arbitrary */
- goto retry;
- }
-end:
- return err;
+ return 0;
}
int snd_dice_transaction_get_clock_source(struct snd_dice *dice,
@@ -331,39 +316,60 @@ int snd_dice_transaction_reinit(struct snd_dice *dice)
return register_notification_address(dice, false);
}
-int snd_dice_transaction_init(struct snd_dice *dice)
+static int get_subaddrs(struct snd_dice *dice)
{
- struct fw_address_handler *handler = &dice->notification_handler;
+ static const int min_values[10] = {
+ 10, 0x64 / 4,
+ 10, 0x18 / 4,
+ 10, 0x18 / 4,
+ 0, 0,
+ 0, 0,
+ };
__be32 *pointers;
+ __be32 version;
+ u32 data;
+ unsigned int i;
int err;
- /* Use the same way which dice_interface_check() does. */
- pointers = kmalloc(sizeof(__be32) * 10, GFP_KERNEL);
+ pointers = kmalloc_array(ARRAY_SIZE(min_values), sizeof(__be32),
+ GFP_KERNEL);
if (pointers == NULL)
return -ENOMEM;
- /* Get offsets for sub-addresses */
+ /*
+ * Check that the sub address spaces exist and are located inside the
+ * private address space. The minimum values are chosen so that all
+ * minimally required registers are included.
+ */
err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST,
- DICE_PRIVATE_SPACE,
- pointers, sizeof(__be32) * 10, 0);
+ DICE_PRIVATE_SPACE, pointers,
+ sizeof(__be32) * ARRAY_SIZE(min_values), 0);
if (err < 0)
goto end;
- /* Allocation callback in address space over host controller */
- handler->length = 4;
- handler->address_callback = dice_notification;
- handler->callback_data = dice;
- err = fw_core_add_address_handler(handler, &fw_high_memory_region);
- if (err < 0) {
- handler->callback_data = NULL;
- goto end;
+ for (i = 0; i < ARRAY_SIZE(min_values); ++i) {
+ data = be32_to_cpu(pointers[i]);
+ if (data < min_values[i] || data >= 0x40000) {
+ err = -ENODEV;
+ goto end;
+ }
}
- /* Register the address space */
- err = register_notification_address(dice, true);
- if (err < 0) {
- fw_core_remove_address_handler(handler);
- handler->callback_data = NULL;
+ /*
+ * Check that the implemented DICE driver specification major version
+ * number matches.
+ */
+ err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST,
+ DICE_PRIVATE_SPACE +
+ be32_to_cpu(pointers[0]) * 4 + GLOBAL_VERSION,
+ &version, sizeof(version), 0);
+ if (err < 0)
+ goto end;
+
+ if ((version & cpu_to_be32(0xff000000)) != cpu_to_be32(0x01000000)) {
+ dev_err(&dice->unit->device,
+ "unknown DICE version: 0x%08x\n", be32_to_cpu(version));
+ err = -ENODEV;
goto end;
}
@@ -380,3 +386,32 @@ end:
kfree(pointers);
return err;
}
+
+int snd_dice_transaction_init(struct snd_dice *dice)
+{
+ struct fw_address_handler *handler = &dice->notification_handler;
+ int err;
+
+ err = get_subaddrs(dice);
+ if (err < 0)
+ return err;
+
+ /* Allocation callback in address space over host controller */
+ handler->length = 4;
+ handler->address_callback = dice_notification;
+ handler->callback_data = dice;
+ err = fw_core_add_address_handler(handler, &fw_high_memory_region);
+ if (err < 0) {
+ handler->callback_data = NULL;
+ return err;
+ }
+
+ /* Register the address space */
+ err = register_notification_address(dice, true);
+ if (err < 0) {
+ fw_core_remove_address_handler(handler);
+ handler->callback_data = NULL;
+ }
+
+ return err;
+}
diff --git a/sound/firewire/dice/dice.c b/sound/firewire/dice/dice.c
index 0cda05c..b91b373 100644
--- a/sound/firewire/dice/dice.c
+++ b/sound/firewire/dice/dice.c
@@ -18,27 +18,14 @@ MODULE_LICENSE("GPL v2");
#define WEISS_CATEGORY_ID 0x00
#define LOUD_CATEGORY_ID 0x10
-static int dice_interface_check(struct fw_unit *unit)
+#define PROBE_DELAY_MS (2 * MSEC_PER_SEC)
+
+static int check_dice_category(struct fw_unit *unit)
{
- static const int min_values[10] = {
- 10, 0x64 / 4,
- 10, 0x18 / 4,
- 10, 0x18 / 4,
- 0, 0,
- 0, 0,
- };
struct fw_device *device = fw_parent_device(unit);
struct fw_csr_iterator it;
- int key, val, vendor = -1, model = -1, err;
- unsigned int category, i;
- __be32 *pointers;
- u32 value;
- __be32 version;
-
- pointers = kmalloc_array(ARRAY_SIZE(min_values), sizeof(__be32),
- GFP_KERNEL);
- if (pointers == NULL)
- return -ENOMEM;
+ int key, val, vendor = -1, model = -1;
+ unsigned int category;
/*
* Check that GUID and unit directory are constructed according to DICE
@@ -64,51 +51,10 @@ static int dice_interface_check(struct fw_unit *unit)
else
category = DICE_CATEGORY_ID;
if (device->config_rom[3] != ((vendor << 8) | category) ||
- device->config_rom[4] >> 22 != model) {
- err = -ENODEV;
- goto end;
- }
-
- /*
- * Check that the sub address spaces exist and are located inside the
- * private address space. The minimum values are chosen so that all
- * minimally required registers are included.
- */
- err = snd_fw_transaction(unit, TCODE_READ_BLOCK_REQUEST,
- DICE_PRIVATE_SPACE, pointers,
- sizeof(__be32) * ARRAY_SIZE(min_values), 0);
- if (err < 0) {
- err = -ENODEV;
- goto end;
- }
- for (i = 0; i < ARRAY_SIZE(min_values); ++i) {
- value = be32_to_cpu(pointers[i]);
- if (value < min_values[i] || value >= 0x40000) {
- err = -ENODEV;
- goto end;
- }
- }
+ device->config_rom[4] >> 22 != model)
+ return -ENODEV;
- /*
- * Check that the implemented DICE driver specification major version
- * number matches.
- */
- err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST,
- DICE_PRIVATE_SPACE +
- be32_to_cpu(pointers[0]) * 4 + GLOBAL_VERSION,
- &version, 4, 0);
- if (err < 0) {
- err = -ENODEV;
- goto end;
- }
- if ((version & cpu_to_be32(0xff000000)) != cpu_to_be32(0x01000000)) {
- dev_err(&unit->device,
- "unknown DICE version: 0x%08x\n", be32_to_cpu(version));
- err = -ENODEV;
- goto end;
- }
-end:
- return err;
+ return 0;
}
static int highest_supported_mode_rate(struct snd_dice *dice,
@@ -231,6 +177,16 @@ static void dice_card_strings(struct snd_dice *dice)
strcpy(card->mixername, "DICE");
}
+static void dice_free(struct snd_dice *dice)
+{
+ snd_dice_stream_destroy_duplex(dice);
+ snd_dice_transaction_destroy(dice);
+ fw_unit_put(dice->unit);
+
+ mutex_destroy(&dice->mutex);
+ kfree(dice);
+}
+
/*
* This module releases the FireWire unit data after all ALSA character devices
* are released by applications. This is for releasing stream data or finishing
@@ -239,39 +195,21 @@ static void dice_card_strings(struct snd_dice *dice)
*/
static void dice_card_free(struct snd_card *card)
{
- struct snd_dice *dice = card->private_data;
-
- snd_dice_stream_destroy_duplex(dice);
- snd_dice_transaction_destroy(dice);
- fw_unit_put(dice->unit);
-
- mutex_destroy(&dice->mutex);
+ dice_free(card->private_data);
}
-static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id)
+static void do_registration(struct work_struct *work)
{
- struct snd_card *card;
- struct snd_dice *dice;
+ struct snd_dice *dice = container_of(work, struct snd_dice, dwork.work);
int err;
- err = dice_interface_check(unit);
- if (err < 0)
- goto end;
+ if (dice->registered)
+ return;
- err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE,
- sizeof(*dice), &card);
+ err = snd_card_new(&dice->unit->device, -1, NULL, THIS_MODULE, 0,
+ &dice->card);
if (err < 0)
- goto end;
-
- dice = card->private_data;
- dice->card = card;
- dice->unit = fw_unit_get(unit);
- card->private_free = dice_card_free;
-
- spin_lock_init(&dice->lock);
- mutex_init(&dice->mutex);
- init_completion(&dice->clock_accepted);
- init_waitqueue_head(&dice->hwdep_wait);
+ return;
err = snd_dice_transaction_init(dice);
if (err < 0)
@@ -283,56 +221,131 @@ static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id)
dice_card_strings(dice);
+ snd_dice_create_proc(dice);
+
err = snd_dice_create_pcm(dice);
if (err < 0)
goto error;
- err = snd_dice_create_hwdep(dice);
+ err = snd_dice_create_midi(dice);
if (err < 0)
goto error;
- snd_dice_create_proc(dice);
-
- err = snd_dice_create_midi(dice);
+ err = snd_dice_create_hwdep(dice);
if (err < 0)
goto error;
- err = snd_dice_stream_init_duplex(dice);
+ err = snd_card_register(dice->card);
if (err < 0)
goto error;
- err = snd_card_register(card);
+ /*
+ * After registered, dice instance can be released corresponding to
+ * releasing the sound card instance.
+ */
+ dice->card->private_free = dice_card_free;
+ dice->card->private_data = dice;
+ dice->registered = true;
+
+ return;
+error:
+ snd_dice_transaction_destroy(dice);
+ snd_card_free(dice->card);
+ dev_info(&dice->unit->device,
+ "Sound card registration failed: %d\n", err);
+}
+
+static void schedule_registration(struct snd_dice *dice)
+{
+ struct fw_card *fw_card = fw_parent_device(dice->unit)->card;
+ u64 now, delay;
+
+ now = get_jiffies_64();
+ delay = fw_card->reset_jiffies + msecs_to_jiffies(PROBE_DELAY_MS);
+
+ if (time_after64(delay, now))
+ delay -= now;
+ else
+ delay = 0;
+
+ mod_delayed_work(system_wq, &dice->dwork, delay);
+}
+
+static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id)
+{
+ struct snd_dice *dice;
+ int err;
+
+ err = check_dice_category(unit);
+ if (err < 0)
+ return -ENODEV;
+
+ /* Allocate this independent of sound card instance. */
+ dice = kzalloc(sizeof(struct snd_dice), GFP_KERNEL);
+ if (dice == NULL)
+ return -ENOMEM;
+
+ dice->unit = fw_unit_get(unit);
+ dev_set_drvdata(&unit->device, dice);
+
+ spin_lock_init(&dice->lock);
+ mutex_init(&dice->mutex);
+ init_completion(&dice->clock_accepted);
+ init_waitqueue_head(&dice->hwdep_wait);
+
+ err = snd_dice_stream_init_duplex(dice);
if (err < 0) {
- snd_dice_stream_destroy_duplex(dice);
- goto error;
+ dice_free(dice);
+ return err;
}
- dev_set_drvdata(&unit->device, dice);
-end:
- return err;
-error:
- snd_card_free(card);
- return err;
+ /* Allocate and register this sound card later. */
+ INIT_DEFERRABLE_WORK(&dice->dwork, do_registration);
+ schedule_registration(dice);
+
+ return 0;
}
static void dice_remove(struct fw_unit *unit)
{
struct snd_dice *dice = dev_get_drvdata(&unit->device);
- /* No need to wait for releasing card object in this context. */
- snd_card_free_when_closed(dice->card);
+ /*
+ * Confirm to stop the work for registration before the sound card is
+ * going to be released. The work is not scheduled again because bus
+ * reset handler is not called anymore.
+ */
+ cancel_delayed_work_sync(&dice->dwork);
+
+ if (dice->registered) {
+ /* No need to wait for releasing card object in this context. */
+ snd_card_free_when_closed(dice->card);
+ } else {
+ /* Don't forget this case. */
+ dice_free(dice);
+ }
}
static void dice_bus_reset(struct fw_unit *unit)
{
struct snd_dice *dice = dev_get_drvdata(&unit->device);
+ /* Postpone a workqueue for deferred registration. */
+ if (!dice->registered)
+ schedule_registration(dice);
+
/* The handler address register becomes initialized. */
snd_dice_transaction_reinit(dice);
- mutex_lock(&dice->mutex);
- snd_dice_stream_update_duplex(dice);
- mutex_unlock(&dice->mutex);
+ /*
+ * After registration, userspace can start packet streaming, then this
+ * code block works fine.
+ */
+ if (dice->registered) {
+ mutex_lock(&dice->mutex);
+ snd_dice_stream_update_duplex(dice);
+ mutex_unlock(&dice->mutex);
+ }
}
#define DICE_INTERFACE 0x000001
diff --git a/sound/firewire/dice/dice.h b/sound/firewire/dice/dice.h
index 101550ac..3d5ebeb 100644
--- a/sound/firewire/dice/dice.h
+++ b/sound/firewire/dice/dice.h
@@ -45,6 +45,9 @@ struct snd_dice {
spinlock_t lock;
struct mutex mutex;
+ bool registered;
+ struct delayed_work dwork;
+
/* Offsets for sub-addresses */
unsigned int global_offset;
unsigned int rx_offset;
diff --git a/sound/firewire/fireworks/fireworks.h b/sound/firewire/fireworks/fireworks.h
index c7cb7de..96c4e0c 100644
--- a/sound/firewire/fireworks/fireworks.h
+++ b/sound/firewire/fireworks/fireworks.h
@@ -86,8 +86,8 @@ struct snd_efw {
struct amdtp_stream rx_stream;
struct cmp_connection out_conn;
struct cmp_connection in_conn;
- atomic_t capture_substreams;
- atomic_t playback_substreams;
+ unsigned int capture_substreams;
+ unsigned int playback_substreams;
/* hardware metering parameters */
unsigned int phys_out;
diff --git a/sound/firewire/fireworks/fireworks_midi.c b/sound/firewire/fireworks/fireworks_midi.c
index fba01bb..3e8c4cf 100644
--- a/sound/firewire/fireworks/fireworks_midi.c
+++ b/sound/firewire/fireworks/fireworks_midi.c
@@ -17,8 +17,10 @@ static int midi_capture_open(struct snd_rawmidi_substream *substream)
if (err < 0)
goto end;
- atomic_inc(&efw->capture_substreams);
+ mutex_lock(&efw->mutex);
+ efw->capture_substreams++;
err = snd_efw_stream_start_duplex(efw, 0);
+ mutex_unlock(&efw->mutex);
if (err < 0)
snd_efw_stream_lock_release(efw);
@@ -35,8 +37,10 @@ static int midi_playback_open(struct snd_rawmidi_substream *substream)
if (err < 0)
goto end;
- atomic_inc(&efw->playback_substreams);
+ mutex_lock(&efw->mutex);
+ efw->playback_substreams++;
err = snd_efw_stream_start_duplex(efw, 0);
+ mutex_unlock(&efw->mutex);
if (err < 0)
snd_efw_stream_lock_release(efw);
end:
@@ -47,8 +51,10 @@ static int midi_capture_close(struct snd_rawmidi_substream *substream)
{
struct snd_efw *efw = substream->rmidi->private_data;
- atomic_dec(&efw->capture_substreams);
+ mutex_lock(&efw->mutex);
+ efw->capture_substreams--;
snd_efw_stream_stop_duplex(efw);
+ mutex_unlock(&efw->mutex);
snd_efw_stream_lock_release(efw);
return 0;
@@ -58,8 +64,10 @@ static int midi_playback_close(struct snd_rawmidi_substream *substream)
{
struct snd_efw *efw = substream->rmidi->private_data;
- atomic_dec(&efw->playback_substreams);
+ mutex_lock(&efw->mutex);
+ efw->playback_substreams--;
snd_efw_stream_stop_duplex(efw);
+ mutex_unlock(&efw->mutex);
snd_efw_stream_lock_release(efw);
return 0;
diff --git a/sound/firewire/fireworks/fireworks_pcm.c b/sound/firewire/fireworks/fireworks_pcm.c
index d27135b..f4fbf75 100644
--- a/sound/firewire/fireworks/fireworks_pcm.c
+++ b/sound/firewire/fireworks/fireworks_pcm.c
@@ -251,8 +251,11 @@ static int pcm_capture_hw_params(struct snd_pcm_substream *substream,
if (err < 0)
return err;
- if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN)
- atomic_inc(&efw->capture_substreams);
+ if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+ mutex_lock(&efw->mutex);
+ efw->capture_substreams++;
+ mutex_unlock(&efw->mutex);
+ }
amdtp_am824_set_pcm_format(&efw->tx_stream, params_format(hw_params));
@@ -269,8 +272,11 @@ static int pcm_playback_hw_params(struct snd_pcm_substream *substream,
if (err < 0)
return err;
- if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN)
- atomic_inc(&efw->playback_substreams);
+ if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
+ mutex_lock(&efw->mutex);
+ efw->playback_substreams++;
+ mutex_unlock(&efw->mutex);
+ }
amdtp_am824_set_pcm_format(&efw->rx_stream, params_format(hw_params));
@@ -281,8 +287,11 @@ 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);
+ if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) {
+ mutex_lock(&efw->mutex);
+ efw->capture_substreams--;
+ mutex_unlock(&efw->mutex);
+ }
snd_efw_stream_stop_duplex(efw);
@@ -292,8 +301,11 @@ 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);
+ if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) {
+ mutex_lock(&efw->mutex);
+ efw->playback_substreams--;
+ mutex_unlock(&efw->mutex);
+ }
snd_efw_stream_stop_duplex(efw);
diff --git a/sound/firewire/fireworks/fireworks_stream.c b/sound/firewire/fireworks/fireworks_stream.c
index 759f6e3..968a40a 100644
--- a/sound/firewire/fireworks/fireworks_stream.c
+++ b/sound/firewire/fireworks/fireworks_stream.c
@@ -209,16 +209,13 @@ end:
int snd_efw_stream_start_duplex(struct snd_efw *efw, unsigned int rate)
{
struct amdtp_stream *master, *slave;
- atomic_t *slave_substreams;
+ unsigned int 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))
+ if (efw->playback_substreams == 0 && efw->capture_substreams == 0)
goto end;
err = get_sync_mode(efw, &sync_mode);
@@ -227,11 +224,11 @@ int snd_efw_stream_start_duplex(struct snd_efw *efw, unsigned int rate)
if (sync_mode == CIP_SYNC_TO_DEVICE) {
master = &efw->tx_stream;
slave = &efw->rx_stream;
- slave_substreams = &efw->playback_substreams;
+ slave_substreams = efw->playback_substreams;
} else {
master = &efw->rx_stream;
slave = &efw->tx_stream;
- slave_substreams = &efw->capture_substreams;
+ slave_substreams = efw->capture_substreams;
}
/*
@@ -277,7 +274,7 @@ int snd_efw_stream_start_duplex(struct snd_efw *efw, unsigned int rate)
}
/* start slave if needed */
- if (atomic_read(slave_substreams) > 0 && !amdtp_stream_running(slave)) {
+ if (slave_substreams > 0 && !amdtp_stream_running(slave)) {
err = start_stream(efw, slave, rate);
if (err < 0) {
dev_err(&efw->unit->device,
@@ -286,37 +283,32 @@ int snd_efw_stream_start_duplex(struct snd_efw *efw, unsigned int rate)
}
}
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;
+ unsigned int 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;
+ 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;
+ slave_substreams = efw->playback_substreams;
+ master_substreams = efw->capture_substreams;
}
- mutex_lock(&efw->mutex);
-
- if (atomic_read(slave_substreams) == 0) {
+ if (slave_substreams == 0) {
stop_stream(efw, slave);
- if (atomic_read(master_substreams) == 0)
+ if (master_substreams == 0)
stop_stream(efw, master);
}
-
- mutex_unlock(&efw->mutex);
}
void snd_efw_stream_update_duplex(struct snd_efw *efw)
diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile
index 06ff50f..b474da7 100644
--- a/sound/firewire/oxfw/Makefile
+++ b/sound/firewire/oxfw/Makefile
@@ -1,3 +1,3 @@
-snd-oxfw-objs := oxfw-command.o oxfw-stream.o oxfw-control.o oxfw-pcm.o \
- oxfw-proc.o oxfw-midi.o oxfw-hwdep.o oxfw.o
+snd-oxfw-objs := oxfw-command.o oxfw-stream.o oxfw-pcm.o oxfw-proc.o \
+ oxfw-midi.o oxfw-hwdep.o oxfw-spkr.o oxfw-scs1x.o oxfw.o
obj-$(CONFIG_SND_OXFW) += snd-oxfw.o
diff --git a/sound/firewire/oxfw/oxfw-scs1x.c b/sound/firewire/oxfw/oxfw-scs1x.c
new file mode 100644
index 0000000..bb53eb3
--- /dev/null
+++ b/sound/firewire/oxfw/oxfw-scs1x.c
@@ -0,0 +1,406 @@
+/*
+ * oxfw-scs1x.c - a part of driver for OXFW970/971 based devices
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ * Copyright (c) 2015 Takashi Sakamoto <o-takashi@sakamocchi.jp>
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "oxfw.h"
+
+#define HSS1394_ADDRESS 0xc007dedadadaULL
+#define HSS1394_MAX_PACKET_SIZE 64
+#define HSS1394_TAG_USER_DATA 0x00
+#define HSS1394_TAG_CHANGE_ADDRESS 0xf1
+
+struct fw_scs1x {
+ struct fw_address_handler hss_handler;
+ u8 input_escape_count;
+ struct snd_rawmidi_substream *input;
+
+ /* For MIDI playback. */
+ struct snd_rawmidi_substream *output;
+ bool output_idle;
+ u8 output_status;
+ u8 output_bytes;
+ bool output_escaped;
+ bool output_escape_high_nibble;
+ struct tasklet_struct tasklet;
+ wait_queue_head_t idle_wait;
+ u8 buffer[HSS1394_MAX_PACKET_SIZE];
+ bool transaction_running;
+ struct fw_transaction transaction;
+ struct fw_device *fw_dev;
+};
+
+static const u8 sysex_escape_prefix[] = {
+ 0xf0, /* SysEx begin */
+ 0x00, 0x01, 0x60, /* Stanton DJ */
+ 0x48, 0x53, 0x53, /* "HSS" */
+};
+
+static void midi_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 midi_input_byte(struct fw_scs1x *scs,
+ struct snd_rawmidi_substream *stream, u8 byte)
+{
+ const u8 eox = 0xf7;
+
+ if (scs->input_escape_count > 0) {
+ midi_input_escaped_byte(stream, byte);
+ scs->input_escape_count--;
+ if (scs->input_escape_count == 0)
+ snd_rawmidi_receive(stream, &eox, sizeof(eox));
+ } else if (byte == 0xf9) {
+ snd_rawmidi_receive(stream, sysex_escape_prefix,
+ ARRAY_SIZE(sysex_escape_prefix));
+ midi_input_escaped_byte(stream, 0x00);
+ midi_input_escaped_byte(stream, 0xf9);
+ scs->input_escape_count = 3;
+ } else {
+ snd_rawmidi_receive(stream, &byte, 1);
+ }
+}
+
+static void midi_input_packet(struct fw_scs1x *scs,
+ struct snd_rawmidi_substream *stream,
+ const u8 *data, unsigned int bytes)
+{
+ unsigned int i;
+ const u8 eox = 0xf7;
+
+ if (data[0] == HSS1394_TAG_USER_DATA) {
+ for (i = 1; i < bytes; ++i)
+ midi_input_byte(scs, stream, data[i]);
+ } else {
+ snd_rawmidi_receive(stream, sysex_escape_prefix,
+ ARRAY_SIZE(sysex_escape_prefix));
+ for (i = 0; i < bytes; ++i)
+ midi_input_escaped_byte(stream, data[i]);
+ snd_rawmidi_receive(stream, &eox, sizeof(eox));
+ }
+}
+
+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 fw_scs1x *scs = callback_data;
+ struct snd_rawmidi_substream *stream;
+ int rcode;
+
+ if (offset != scs->hss_handler.offset) {
+ rcode = RCODE_ADDRESS_ERROR;
+ goto end;
+ }
+ if (tcode != TCODE_WRITE_QUADLET_REQUEST &&
+ tcode != TCODE_WRITE_BLOCK_REQUEST) {
+ rcode = RCODE_TYPE_ERROR;
+ goto end;
+ }
+
+ if (length >= 1) {
+ stream = ACCESS_ONCE(scs->input);
+ if (stream)
+ midi_input_packet(scs, stream, data, length);
+ }
+
+ rcode = RCODE_COMPLETE;
+end:
+ fw_send_response(card, request, rcode);
+}
+
+static void scs_write_callback(struct fw_card *card, int rcode,
+ void *data, size_t length, void *callback_data)
+{
+ struct fw_scs1x *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 fw_scs1x *scs = (struct fw_scs1x *)data;
+ struct snd_rawmidi_substream *stream;
+ unsigned int i;
+ u8 byte;
+ 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;
+ generation = scs->fw_dev->generation;
+ smp_rmb(); /* node_id vs. generation */
+ fw_send_request(scs->fw_dev->card, &scs->transaction,
+ TCODE_WRITE_BLOCK_REQUEST, scs->fw_dev->node_id,
+ generation, scs->fw_dev->max_speed, HSS1394_ADDRESS,
+ scs->buffer, i, scs_write_callback, scs);
+}
+
+static int midi_capture_open(struct snd_rawmidi_substream *stream)
+{
+ return 0;
+}
+
+static int midi_capture_close(struct snd_rawmidi_substream *stream)
+{
+ return 0;
+}
+
+static void midi_capture_trigger(struct snd_rawmidi_substream *stream, int up)
+{
+ struct fw_scs1x *scs = stream->rmidi->private_data;
+
+ if (up) {
+ scs->input_escape_count = 0;
+ ACCESS_ONCE(scs->input) = stream;
+ } else {
+ ACCESS_ONCE(scs->input) = NULL;
+ }
+}
+
+static struct snd_rawmidi_ops midi_capture_ops = {
+ .open = midi_capture_open,
+ .close = midi_capture_close,
+ .trigger = midi_capture_trigger,
+};
+
+static int midi_playback_open(struct snd_rawmidi_substream *stream)
+{
+ return 0;
+}
+
+static int midi_playback_close(struct snd_rawmidi_substream *stream)
+{
+ return 0;
+}
+
+static void midi_playback_trigger(struct snd_rawmidi_substream *stream, int up)
+{
+ struct fw_scs1x *scs = stream->rmidi->private_data;
+
+ if (up) {
+ scs->output_status = 0;
+ scs->output_bytes = 1;
+ scs->output_escaped = false;
+ scs->output_idle = false;
+
+ ACCESS_ONCE(scs->output) = stream;
+ tasklet_schedule(&scs->tasklet);
+ } else {
+ ACCESS_ONCE(scs->output) = NULL;
+ }
+}
+static void midi_playback_drain(struct snd_rawmidi_substream *stream)
+{
+ struct fw_scs1x *scs = stream->rmidi->private_data;
+
+ wait_event(scs->idle_wait, scs->output_idle);
+}
+
+static struct snd_rawmidi_ops midi_playback_ops = {
+ .open = midi_playback_open,
+ .close = midi_playback_close,
+ .trigger = midi_playback_trigger,
+ .drain = midi_playback_drain,
+};
+static int register_address(struct snd_oxfw *oxfw)
+{
+ struct fw_scs1x *scs = oxfw->spec;
+ __be64 data;
+
+ data = cpu_to_be64(((u64)HSS1394_TAG_CHANGE_ADDRESS << 56) |
+ scs->hss_handler.offset);
+ return snd_fw_transaction(oxfw->unit, TCODE_WRITE_BLOCK_REQUEST,
+ HSS1394_ADDRESS, &data, sizeof(data), 0);
+}
+
+static void remove_scs1x(struct snd_rawmidi *rmidi)
+{
+ struct fw_scs1x *scs = rmidi->private_data;
+
+ fw_core_remove_address_handler(&scs->hss_handler);
+}
+
+void snd_oxfw_scs1x_update(struct snd_oxfw *oxfw)
+{
+ register_address(oxfw);
+}
+
+int snd_oxfw_scs1x_add(struct snd_oxfw *oxfw)
+{
+ struct snd_rawmidi *rmidi;
+ struct fw_scs1x *scs;
+ int err;
+
+ scs = kzalloc(sizeof(struct fw_scs1x), GFP_KERNEL);
+ if (scs == NULL)
+ return -ENOMEM;
+ scs->fw_dev = fw_parent_device(oxfw->unit);
+ oxfw->spec = scs;
+
+ /* Allocate own handler for imcoming asynchronous transaction. */
+ 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)
+ return err;
+
+ err = register_address(oxfw);
+ if (err < 0)
+ goto err_allocated;
+
+ /* Use unique name for backward compatibility to scs1x module. */
+ err = snd_rawmidi_new(oxfw->card, "SCS.1x", 0, 1, 1, &rmidi);
+ if (err < 0)
+ goto err_allocated;
+ rmidi->private_data = scs;
+ rmidi->private_free = remove_scs1x;
+
+ snprintf(rmidi->name, sizeof(rmidi->name),
+ "%s MIDI", oxfw->card->shortname);
+
+ rmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT |
+ SNDRV_RAWMIDI_INFO_OUTPUT |
+ SNDRV_RAWMIDI_INFO_DUPLEX;
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT,
+ &midi_capture_ops);
+ snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT,
+ &midi_playback_ops);
+
+ tasklet_init(&scs->tasklet, scs_output_tasklet, (unsigned long)scs);
+ init_waitqueue_head(&scs->idle_wait);
+ scs->output_idle = true;
+
+ return 0;
+err_allocated:
+ fw_core_remove_address_handler(&scs->hss_handler);
+ return err;
+}
diff --git a/sound/firewire/oxfw/oxfw-control.c b/sound/firewire/oxfw/oxfw-spkr.c
index 02a1cb9..cb905af 100644
--- a/sound/firewire/oxfw/oxfw-control.c
+++ b/sound/firewire/oxfw/oxfw-spkr.c
@@ -1,5 +1,5 @@
/*
- * oxfw_stream.c - a part of driver for OXFW970/971 based devices
+ * oxfw-spkr.c - a part of driver for OXFW970/971 based devices
*
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
* Licensed under the terms of the GNU General Public License, version 2.
@@ -7,6 +7,17 @@
#include "oxfw.h"
+struct fw_spkr {
+ bool mute;
+ s16 volume[6];
+ s16 volume_min;
+ s16 volume_max;
+
+ unsigned int mixer_channels;
+ u8 mute_fb_id;
+ u8 volume_fb_id;
+};
+
enum control_action { CTL_READ, CTL_WRITE };
enum control_attribute {
CTL_MIN = 0x02,
@@ -14,8 +25,8 @@ enum control_attribute {
CTL_CURRENT = 0x10,
};
-static int oxfw_mute_command(struct snd_oxfw *oxfw, bool *value,
- enum control_action action)
+static int avc_audio_feature_mute(struct fw_unit *unit, u8 fb_id, bool *value,
+ enum control_action action)
{
u8 *buf;
u8 response_ok;
@@ -35,7 +46,7 @@ static int oxfw_mute_command(struct snd_oxfw *oxfw, bool *value,
buf[1] = 0x08; /* audio unit 0 */
buf[2] = 0xb8; /* FUNCTION BLOCK */
buf[3] = 0x81; /* function block type: feature */
- buf[4] = oxfw->device_info->mute_fb_id; /* function block ID */
+ buf[4] = fb_id; /* function block ID */
buf[5] = 0x10; /* control attribute: current */
buf[6] = 0x02; /* selector length */
buf[7] = 0x00; /* audio channel number */
@@ -46,16 +57,16 @@ static int oxfw_mute_command(struct snd_oxfw *oxfw, bool *value,
else
buf[10] = *value ? 0x70 : 0x60;
- err = fcp_avc_transaction(oxfw->unit, buf, 11, buf, 11, 0x3fe);
+ err = fcp_avc_transaction(unit, buf, 11, buf, 11, 0x3fe);
if (err < 0)
goto error;
if (err < 11) {
- dev_err(&oxfw->unit->device, "short FCP response\n");
+ dev_err(&unit->device, "short FCP response\n");
err = -EIO;
goto error;
}
if (buf[0] != response_ok) {
- dev_err(&oxfw->unit->device, "mute command failed\n");
+ dev_err(&unit->device, "mute command failed\n");
err = -EIO;
goto error;
}
@@ -70,10 +81,10 @@ error:
return err;
}
-static int oxfw_volume_command(struct snd_oxfw *oxfw, s16 *value,
- unsigned int channel,
- enum control_attribute attribute,
- enum control_action action)
+static int avc_audio_feature_volume(struct fw_unit *unit, u8 fb_id, s16 *value,
+ unsigned int channel,
+ enum control_attribute attribute,
+ enum control_action action)
{
u8 *buf;
u8 response_ok;
@@ -93,7 +104,7 @@ static int oxfw_volume_command(struct snd_oxfw *oxfw, s16 *value,
buf[1] = 0x08; /* audio unit 0 */
buf[2] = 0xb8; /* FUNCTION BLOCK */
buf[3] = 0x81; /* function block type: feature */
- buf[4] = oxfw->device_info->volume_fb_id; /* function block ID */
+ buf[4] = fb_id; /* function block ID */
buf[5] = attribute; /* control attribute */
buf[6] = 0x02; /* selector length */
buf[7] = channel; /* audio channel number */
@@ -107,16 +118,16 @@ static int oxfw_volume_command(struct snd_oxfw *oxfw, s16 *value,
buf[11] = *value;
}
- err = fcp_avc_transaction(oxfw->unit, buf, 12, buf, 12, 0x3fe);
+ err = fcp_avc_transaction(unit, buf, 12, buf, 12, 0x3fe);
if (err < 0)
goto error;
if (err < 12) {
- dev_err(&oxfw->unit->device, "short FCP response\n");
+ dev_err(&unit->device, "short FCP response\n");
err = -EIO;
goto error;
}
if (buf[0] != response_ok) {
- dev_err(&oxfw->unit->device, "volume command failed\n");
+ dev_err(&unit->device, "volume command failed\n");
err = -EIO;
goto error;
}
@@ -131,75 +142,81 @@ error:
return err;
}
-static int oxfw_mute_get(struct snd_kcontrol *control,
+static int spkr_mute_get(struct snd_kcontrol *control,
struct snd_ctl_elem_value *value)
{
struct snd_oxfw *oxfw = control->private_data;
+ struct fw_spkr *spkr = oxfw->spec;
- value->value.integer.value[0] = !oxfw->mute;
+ value->value.integer.value[0] = !spkr->mute;
return 0;
}
-static int oxfw_mute_put(struct snd_kcontrol *control,
+static int spkr_mute_put(struct snd_kcontrol *control,
struct snd_ctl_elem_value *value)
{
struct snd_oxfw *oxfw = control->private_data;
+ struct fw_spkr *spkr = oxfw->spec;
bool mute;
int err;
mute = !value->value.integer.value[0];
- if (mute == oxfw->mute)
+ if (mute == spkr->mute)
return 0;
- err = oxfw_mute_command(oxfw, &mute, CTL_WRITE);
+ err = avc_audio_feature_mute(oxfw->unit, spkr->mute_fb_id, &mute,
+ CTL_WRITE);
if (err < 0)
return err;
- oxfw->mute = mute;
+ spkr->mute = mute;
return 1;
}
-static int oxfw_volume_info(struct snd_kcontrol *control,
+static int spkr_volume_info(struct snd_kcontrol *control,
struct snd_ctl_elem_info *info)
{
struct snd_oxfw *oxfw = control->private_data;
+ struct fw_spkr *spkr = oxfw->spec;
info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
- info->count = oxfw->device_info->mixer_channels;
- info->value.integer.min = oxfw->volume_min;
- info->value.integer.max = oxfw->volume_max;
+ info->count = spkr->mixer_channels;
+ info->value.integer.min = spkr->volume_min;
+ info->value.integer.max = spkr->volume_max;
return 0;
}
static const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 };
-static int oxfw_volume_get(struct snd_kcontrol *control,
+static int spkr_volume_get(struct snd_kcontrol *control,
struct snd_ctl_elem_value *value)
{
struct snd_oxfw *oxfw = control->private_data;
+ struct fw_spkr *spkr = oxfw->spec;
unsigned int i;
- for (i = 0; i < oxfw->device_info->mixer_channels; ++i)
- value->value.integer.value[channel_map[i]] = oxfw->volume[i];
+ for (i = 0; i < spkr->mixer_channels; ++i)
+ value->value.integer.value[channel_map[i]] = spkr->volume[i];
return 0;
}
-static int oxfw_volume_put(struct snd_kcontrol *control,
+static int spkr_volume_put(struct snd_kcontrol *control,
struct snd_ctl_elem_value *value)
{
struct snd_oxfw *oxfw = control->private_data;
+ struct fw_spkr *spkr = oxfw->spec;
unsigned int i, changed_channels;
bool equal_values = true;
s16 volume;
int err;
- for (i = 0; i < oxfw->device_info->mixer_channels; ++i) {
- if (value->value.integer.value[i] < oxfw->volume_min ||
- value->value.integer.value[i] > oxfw->volume_max)
+ for (i = 0; i < spkr->mixer_channels; ++i) {
+ if (value->value.integer.value[i] < spkr->volume_min ||
+ value->value.integer.value[i] > spkr->volume_max)
return -EINVAL;
if (value->value.integer.value[i] !=
value->value.integer.value[0])
@@ -207,67 +224,86 @@ static int oxfw_volume_put(struct snd_kcontrol *control,
}
changed_channels = 0;
- for (i = 0; i < oxfw->device_info->mixer_channels; ++i)
+ for (i = 0; i < spkr->mixer_channels; ++i)
if (value->value.integer.value[channel_map[i]] !=
- oxfw->volume[i])
+ spkr->volume[i])
changed_channels |= 1 << (i + 1);
if (equal_values && changed_channels != 0)
changed_channels = 1 << 0;
- for (i = 0; i <= oxfw->device_info->mixer_channels; ++i) {
+ for (i = 0; i <= spkr->mixer_channels; ++i) {
volume = value->value.integer.value[channel_map[i ? i - 1 : 0]];
if (changed_channels & (1 << i)) {
- err = oxfw_volume_command(oxfw, &volume, i,
- CTL_CURRENT, CTL_WRITE);
+ err = avc_audio_feature_volume(oxfw->unit,
+ spkr->volume_fb_id, &volume,
+ i, CTL_CURRENT, CTL_WRITE);
if (err < 0)
return err;
}
if (i > 0)
- oxfw->volume[i - 1] = volume;
+ spkr->volume[i - 1] = volume;
}
return changed_channels != 0;
}
-int snd_oxfw_create_mixer(struct snd_oxfw *oxfw)
+int snd_oxfw_add_spkr(struct snd_oxfw *oxfw, bool is_lacie)
{
static const struct snd_kcontrol_new controls[] = {
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Playback Switch",
.info = snd_ctl_boolean_mono_info,
- .get = oxfw_mute_get,
- .put = oxfw_mute_put,
+ .get = spkr_mute_get,
+ .put = spkr_mute_put,
},
{
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.name = "PCM Playback Volume",
- .info = oxfw_volume_info,
- .get = oxfw_volume_get,
- .put = oxfw_volume_put,
+ .info = spkr_volume_info,
+ .get = spkr_volume_get,
+ .put = spkr_volume_put,
},
};
+ struct fw_spkr *spkr;
unsigned int i, first_ch;
int err;
- err = oxfw_volume_command(oxfw, &oxfw->volume_min,
- 0, CTL_MIN, CTL_READ);
+ spkr = kzalloc(sizeof(struct fw_spkr), GFP_KERNEL);
+ if (spkr == NULL)
+ return -ENOMEM;
+ oxfw->spec = spkr;
+
+ if (is_lacie) {
+ spkr->mixer_channels = 1;
+ spkr->mute_fb_id = 0x01;
+ spkr->volume_fb_id = 0x01;
+ } else {
+ spkr->mixer_channels = 6;
+ spkr->mute_fb_id = 0x01;
+ spkr->volume_fb_id = 0x02;
+ }
+
+ err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id,
+ &spkr->volume_min, 0, CTL_MIN, CTL_READ);
if (err < 0)
return err;
- err = oxfw_volume_command(oxfw, &oxfw->volume_max,
- 0, CTL_MAX, CTL_READ);
+ err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id,
+ &spkr->volume_max, 0, CTL_MAX, CTL_READ);
if (err < 0)
return err;
- err = oxfw_mute_command(oxfw, &oxfw->mute, CTL_READ);
+ err = avc_audio_feature_mute(oxfw->unit, spkr->mute_fb_id, &spkr->mute,
+ CTL_READ);
if (err < 0)
return err;
- first_ch = oxfw->device_info->mixer_channels == 1 ? 0 : 1;
- for (i = 0; i < oxfw->device_info->mixer_channels; ++i) {
- err = oxfw_volume_command(oxfw, &oxfw->volume[i],
- first_ch + i, CTL_CURRENT, CTL_READ);
+ first_ch = spkr->mixer_channels == 1 ? 0 : 1;
+ for (i = 0; i < spkr->mixer_channels; ++i) {
+ err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id,
+ &spkr->volume[i], first_ch + i,
+ CTL_CURRENT, CTL_READ);
if (err < 0)
return err;
}
diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c
index 588b93f..abedc22 100644
--- a/sound/firewire/oxfw/oxfw.c
+++ b/sound/firewire/oxfw/oxfw.c
@@ -19,6 +19,7 @@
#define VENDOR_BEHRINGER 0x001564
#define VENDOR_LACIE 0x00d04b
#define VENDOR_TASCAM 0x00022e
+#define OUI_STANTON 0x001260
#define MODEL_SATELLITE 0x00200f
@@ -29,6 +30,13 @@ MODULE_DESCRIPTION("Oxford Semiconductor FW970/971 driver");
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("snd-firewire-speakers");
+MODULE_ALIAS("snd-scs1x");
+
+struct compat_info {
+ const char *driver_name;
+ const char *vendor_name;
+ const char *model_name;
+};
static bool detect_loud_models(struct fw_unit *unit)
{
@@ -59,6 +67,7 @@ static bool detect_loud_models(struct fw_unit *unit)
static int name_card(struct snd_oxfw *oxfw)
{
struct fw_device *fw_dev = fw_parent_device(oxfw->unit);
+ const struct compat_info *info;
char vendor[24];
char model[32];
const char *d, *v, *m;
@@ -84,10 +93,12 @@ static int name_card(struct snd_oxfw *oxfw)
be32_to_cpus(&firmware);
/* to apply card definitions */
- if (oxfw->device_info) {
- d = oxfw->device_info->driver_name;
- v = oxfw->device_info->vendor_name;
- m = oxfw->device_info->model_name;
+ if (oxfw->entry->vendor_id == VENDOR_GRIFFIN ||
+ oxfw->entry->vendor_id == VENDOR_LACIE) {
+ info = (const struct compat_info *)oxfw->entry->driver_data;
+ d = info->driver_name;
+ v = info->vendor_name;
+ m = info->model_name;
} else {
d = "OXFW";
v = vendor;
@@ -129,16 +140,51 @@ static void oxfw_card_free(struct snd_card *card)
kfree(oxfw->rx_stream_formats[i]);
}
+ kfree(oxfw->spec);
mutex_destroy(&oxfw->mutex);
}
-static void detect_quirks(struct snd_oxfw *oxfw)
+static int detect_quirks(struct snd_oxfw *oxfw)
{
struct fw_device *fw_dev = fw_parent_device(oxfw->unit);
struct fw_csr_iterator it;
int key, val;
int vendor, model;
+ /*
+ * Add ALSA control elements for two models to keep compatibility to
+ * old firewire-speaker module.
+ */
+ if (oxfw->entry->vendor_id == VENDOR_GRIFFIN)
+ return snd_oxfw_add_spkr(oxfw, false);
+ if (oxfw->entry->vendor_id == VENDOR_LACIE)
+ return snd_oxfw_add_spkr(oxfw, true);
+
+ /*
+ * Stanton models supports asynchronous transactions for unique MIDI
+ * messages.
+ */
+ if (oxfw->entry->vendor_id == OUI_STANTON) {
+ /* No physical MIDI ports. */
+ oxfw->midi_input_ports = 0;
+ oxfw->midi_output_ports = 0;
+
+ /* Output stream exists but no data channels are useful. */
+ oxfw->has_output = false;
+
+ return snd_oxfw_scs1x_add(oxfw);
+ }
+
+ /*
+ * TASCAM FireOne has physical control and requires a pair of additional
+ * MIDI ports.
+ */
+ if (oxfw->entry->vendor_id == VENDOR_TASCAM) {
+ oxfw->midi_input_ports++;
+ oxfw->midi_output_ports++;
+ return 0;
+ }
+
/* Seek from Root Directory of Config ROM. */
vendor = model = 0;
fw_csr_iterator_init(&it, fw_dev->config_rom + 5);
@@ -156,24 +202,17 @@ static void detect_quirks(struct snd_oxfw *oxfw)
if (vendor == VENDOR_LOUD && model == MODEL_SATELLITE)
oxfw->wrong_dbs = true;
- /*
- * TASCAM FireOne has physical control and requires a pair of additional
- * MIDI ports.
- */
- if (vendor == VENDOR_TASCAM) {
- oxfw->midi_input_ports++;
- oxfw->midi_output_ports++;
- }
+ return 0;
}
static int oxfw_probe(struct fw_unit *unit,
- const struct ieee1394_device_id *id)
+ const struct ieee1394_device_id *entry)
{
struct snd_card *card;
struct snd_oxfw *oxfw;
int err;
- if ((id->vendor_id == VENDOR_LOUD) && !detect_loud_models(unit))
+ if (entry->vendor_id == VENDOR_LOUD && !detect_loud_models(unit))
return -ENODEV;
err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE,
@@ -186,7 +225,7 @@ static int oxfw_probe(struct fw_unit *unit,
oxfw->card = card;
mutex_init(&oxfw->mutex);
oxfw->unit = fw_unit_get(unit);
- oxfw->device_info = (const struct device_info *)id->driver_data;
+ oxfw->entry = entry;
spin_lock_init(&oxfw->lock);
init_waitqueue_head(&oxfw->hwdep_wait);
@@ -194,21 +233,17 @@ static int oxfw_probe(struct fw_unit *unit,
if (err < 0)
goto error;
- detect_quirks(oxfw);
-
err = name_card(oxfw);
if (err < 0)
goto error;
- err = snd_oxfw_create_pcm(oxfw);
+ err = detect_quirks(oxfw);
if (err < 0)
goto error;
- if (oxfw->device_info) {
- err = snd_oxfw_create_mixer(oxfw);
- if (err < 0)
- goto error;
- }
+ err = snd_oxfw_create_pcm(oxfw);
+ if (err < 0)
+ goto error;
snd_oxfw_proc_init(oxfw);
@@ -257,6 +292,9 @@ static void oxfw_bus_reset(struct fw_unit *unit)
snd_oxfw_stream_update_simplex(oxfw, &oxfw->tx_stream);
mutex_unlock(&oxfw->mutex);
+
+ if (oxfw->entry->vendor_id == OUI_STANTON)
+ snd_oxfw_scs1x_update(oxfw);
}
static void oxfw_remove(struct fw_unit *unit)
@@ -267,22 +305,16 @@ static void oxfw_remove(struct fw_unit *unit)
snd_card_free_when_closed(oxfw->card);
}
-static const struct device_info griffin_firewave = {
+static const struct compat_info griffin_firewave = {
.driver_name = "FireWave",
.vendor_name = "Griffin",
.model_name = "FireWave",
- .mixer_channels = 6,
- .mute_fb_id = 0x01,
- .volume_fb_id = 0x02,
};
-static const struct device_info lacie_speakers = {
+static const struct compat_info lacie_speakers = {
.driver_name = "FWSpeakers",
.vendor_name = "LaCie",
.model_name = "FireWire Speakers",
- .mixer_channels = 1,
- .mute_fb_id = 0x01,
- .volume_fb_id = 0x01,
};
static const struct ieee1394_device_id oxfw_id_table[] = {
@@ -340,6 +372,20 @@ static const struct ieee1394_device_id oxfw_id_table[] = {
.vendor_id = VENDOR_TASCAM,
.model_id = 0x800007,
},
+ /* Stanton, Stanton Controllers & Systems 1 Mixer (SCS.1m) */
+ {
+ .match_flags = IEEE1394_MATCH_VENDOR_ID |
+ IEEE1394_MATCH_MODEL_ID,
+ .vendor_id = OUI_STANTON,
+ .model_id = 0x001000,
+ },
+ /* Stanton, Stanton Controllers & Systems 1 Deck (SCS.1d) */
+ {
+ .match_flags = IEEE1394_MATCH_VENDOR_ID |
+ IEEE1394_MATCH_MODEL_ID,
+ .vendor_id = OUI_STANTON,
+ .model_id = 0x002000,
+ },
{ }
};
MODULE_DEVICE_TABLE(ieee1394, oxfw_id_table);
diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h
index 8392c42..9beecc2 100644
--- a/sound/firewire/oxfw/oxfw.h
+++ b/sound/firewire/oxfw/oxfw.h
@@ -31,15 +31,6 @@
#include "../amdtp-am824.h"
#include "../cmp.h"
-struct device_info {
- const char *driver_name;
- const char *vendor_name;
- const char *model_name;
- unsigned int mixer_channels;
- u8 mute_fb_id;
- u8 volume_fb_id;
-};
-
/* This is an arbitrary number for convinience. */
#define SND_OXFW_STREAM_FORMAT_ENTRIES 10
struct snd_oxfw {
@@ -64,14 +55,12 @@ struct snd_oxfw {
unsigned int midi_input_ports;
unsigned int midi_output_ports;
- bool mute;
- s16 volume[6];
- s16 volume_min;
- s16 volume_max;
-
int dev_lock_count;
bool dev_lock_changed;
wait_queue_head_t hwdep_wait;
+
+ const struct ieee1394_device_id *entry;
+ void *spec;
};
/*
@@ -138,10 +127,12 @@ void snd_oxfw_stream_lock_release(struct snd_oxfw *oxfw);
int snd_oxfw_create_pcm(struct snd_oxfw *oxfw);
-int snd_oxfw_create_mixer(struct snd_oxfw *oxfw);
-
void snd_oxfw_proc_init(struct snd_oxfw *oxfw);
int snd_oxfw_create_midi(struct snd_oxfw *oxfw);
int snd_oxfw_create_hwdep(struct snd_oxfw *oxfw);
+
+int snd_oxfw_add_spkr(struct snd_oxfw *oxfw, bool is_lacie);
+int snd_oxfw_scs1x_add(struct snd_oxfw *oxfw);
+void snd_oxfw_scs1x_update(struct snd_oxfw *oxfw);
diff --git a/sound/firewire/scs1x.c b/sound/firewire/scs1x.c
deleted file mode 100644
index 2dba848..0000000
--- a/sound/firewire/scs1x.c
+++ /dev/null
@@ -1,530 +0,0 @@
-/*
- * 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);