[alsa-devel] Help requested: new HSS1394 MIDI back-end
Hello everyone.
I am a developer for Mixxx, GPL cross-platform digital DJ software: http://www.mixxx.org. My focus is the controller subsystem. I am ready to finally add Linux support for HSS1394 DJ controllers such as Stanton's SCS.1m and SCS.1d.
HSS1394 is just MIDI over IEEE1394 (Firewire) at wire speed. The Windows and OSX library source code is LGPL and available on LaunchPad here: https://launchpad.net/hss1394
The Stanton SCS.1m is a sound interface integrated with a two-way control surface that speaks HSS1394. (The sound card uses an Oxford chip set and works fine with FFADO 2.0.1 today up to 96kHz. I use it with Mixxx via JACK.)
The Stanton SCS.1d is a two-way control surface (no sound card) that speaks HSS1394 and is capable of sending ALOT of latency-sensitive data: about 4000 messages per platter rotation. (Now imagine if you had four or even two of them on the same network running at +50% speed. This would quickly overwhelm USB, at least at the time these devices were designed.)
Read about them here: http://www.stantondj.com/stanton-controllers-systems/system1.html
After a lengthy discussion on the FFADO mailing list, we determined that the best (cleanest and most flexible) way to get Linux support for these devices is to write an ALSA kernel driver that speaks HSS1394 with the devices and presents an ALSA MIDI device (with no data rate limits) for each to Linux applications.
The advantage of doing this is that the devices will appear to all MIDI applications without them having to have specific support for HSS1394. This means they could be used in DAW applications as well.
(FWIW, FFADO trunk has a program called test-scs that does exactly this, but using that is impractical because it depends on various internal parts of the FFADO source so would require bundling all of FFADO with the application, and it's unable to see non-audio devices due to FFADO's intended use (so it sees the SCS.1m but not the .1d.))
So my questions are: where can I find documentation on writing a new MIDI driver for ALSA? And is anyone else interested in helping me do this?
Thank you very much for your time and work. Help me take the last step to my dream DJ setup! (Linux, FLAC and SCS.1 controllers.)
Sincerely, Sean M. Pappalardo "D.J. Pegasus" Mixxx Developer - Controller Specialist
Sean M. Pappalardo - D.J. Pegasus wrote:
where can I find documentation on writing a new MIDI driver for ALSA?
See the document "Writing an ALSA Driver", and look at the source code of other MIDI drivers, such as, for example, sound/usb/midi.c or the (untested) patch below.
BTW: What is the output of "lsfirewire -v" for these devices?
Regards, Clemens
--- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -33,4 +33,17 @@ 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_PCM + select SND_RAWMIDI + 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. + endif # SND_FIREWIRE --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -2,7 +2,9 @@ snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \ fcp.o cmp.o amdtp.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_FIREWIRE_SPEAKERS) += snd-firewire-speakers.o obj-$(CONFIG_SND_ISIGHT) += snd-isight.o +obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o --- /dev/null +++ b/sound/firewire/scs1x.c @@ -0,0 +1,447 @@ +/* + * 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/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 HSS1394_ADDRESS 0xc007dedadadaULL +#define HSS1394_MAX_PACKET_SIZE 64 + +#define HSS1394_TAG_USER_DATA 0x00 +#define HSS1394_TAG_CHANGE_ADDRESS 0xf1 +#define HSS1394_TAG_PING_RESPONSE 0xf3 + +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 midi_status; + u8 midi_bytes; + struct snd_rawmidi_substream *output; + struct snd_rawmidi_substream *input; + struct tasklet_struct tasklet; + wait_queue_head_t idle_wait; + u8 *buffer; +}; + +static int scs_output_open(struct snd_rawmidi_substream *stream) +{ + struct scs *scs = stream->rmidi->private_data; + + scs->midi_status = 0; + scs->midi_bytes = 0; + + 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; +} + +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->midi_bytes; + for (;;) { + if (snd_rawmidi_transmit(stream, &byte, 1) != 1) { + scs->midi_bytes = i; + scs->output_idle = true; + wake_up(&scs->idle_wait); + return; + } + /* + * Convert from real MIDI to what the device expects (no + * running status, one command per packet). + */ + if (byte < 0x80) { + if (i == 0) { + if (!is_valid_running_status(scs->midi_status)) + continue; + scs->buffer[++i] = scs->midi_status; + } + scs->buffer[++i] = byte; + if ((i == 2 && is_two_bytes_cmd(scs->midi_status)) || + (i == 3 && is_three_bytes_cmd(scs->midi_status))) + break; + if (i >= HSS1394_MAX_PACKET_SIZE - 1) + i = 0; + } else if (byte == 0xf7) { + if (i > 0 && scs->midi_status == 0xf0) { + scs->buffer[++i] = 0xf7; + break; + } + } else if (!is_invalid_cmd(byte) && + byte < 0xf8) { + i = 0; + scs->buffer[++i] = byte; + scs->midi_status = byte; + if (is_one_byte_cmd(byte)) + break; + } + } + scs->midi_bytes = 0; + + 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, 1 + 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) +{ + 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 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 && ((const u8 *)data)[0] == HSS1394_TAG_USER_DATA) { + stream = ACCESS_ONCE(scs->input); + if (stream) + snd_rawmidi_receive(stream, data + 1, length - 1); + } + + fw_send_response(card, request, RCODE_COMPLETE); +} + +static int scs_check_version(struct scs *scs) +{ + u8 data[4]; + int err; + + err = snd_fw_transaction(scs->unit, TCODE_READ_BLOCK_REQUEST, + HSS1394_ADDRESS, data, 4); + if (err < 0) + return err; + + if (data[0] != HSS1394_TAG_PING_RESPONSE) { + dev_err(&scs->unit->device, + "wrong ping response: expected %#02x, got %#02x\n", + HSS1394_TAG_PING_RESPONSE, data[0]); + return -ENXIO; + } + + return 0; +} + +static int scs_init_hss_address(struct scs *scs) +{ + u8 data[8]; + + *(__be64 *)data = cpu_to_be64(scs->hss_handler.offset); + data[0] = HSS1394_TAG_CHANGE_ADDRESS; + return snd_fw_transaction(scs->unit, TCODE_WRITE_BLOCK_REQUEST, + HSS1394_ADDRESS, data, 8); +} + +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 device *unit_dev) +{ + struct fw_unit *unit = fw_unit(unit_dev); + struct fw_device *fw_dev = fw_parent_device(unit); + struct snd_card *card; + struct scs *scs; + int err; + + err = snd_card_create(-16, NULL, THIS_MODULE, sizeof(*scs), &card); + if (err < 0) + return err; + snd_card_set_dev(card, unit_dev); + + 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) + goto err_card; + scs->buffer[0] = HSS1394_TAG_USER_DATA; + + err = scs_check_version(scs); + if (err < 0) + goto err_buffer; + + 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 %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_dev, scs); + + return 0; + +err_buffer: + kfree(scs->buffer); +err_card: + snd_card_free(card); + return err; +} + +static int scs_remove(struct device *dev) +{ + struct scs *scs = dev_get_drvdata(dev); + + 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); + + return 0; +} + +static void scs_update(struct fw_unit *unit) +{ + struct scs *scs = dev_get_drvdata(&unit->device); + u8 data[8]; + + *(__be64 *)data = cpu_to_be64(scs->hss_handler.offset); + data[0] = HSS1394_TAG_CHANGE_ADDRESS; + snd_fw_transaction(scs->unit, TCODE_WRITE_BLOCK_REQUEST, + HSS1394_ADDRESS, data, 8); +} + +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, + }, + /* TODO: add ID for 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, + .remove = scs_remove, + }, + .update = scs_update, + .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);
Hello again.
On 05/27/2012 03:50 PM, Clemens Ladisch wrote:
See the document "Writing an ALSA Driver", and look at the source code of other MIDI drivers, such as, for example, sound/usb/midi.c or the (untested) patch below.
Holy crap!! Thank you so much for that patch, Clemens! I just updated my kernel and tried it with the SCS.1m but unfortunately it reports an error:
firewire_ohci: isochronous cycle inconsistent firewire_core: created device fw1: GUID 0012600000000000, S400 firewire_core: phy config: card 0, new root=ffc0, gap_count=5 snd_scs1x fw1.0: transaction failed: type error snd_scs1x: probe of fw1.0 failed with error -5
Turning on the SCS.1d then yields: firewire_core: phy config: card 0, new root=ffc2, gap_count=7 firewire_core: phy config: card 0, new root=ffc2, gap_count=7 firewire_core: created device fw2: GUID 0012600100000000, S400
I'm guessing the type error is around line 254 in scs1x.c, but where does tcode come from? (I'm totally green with systems and Firewire programming.) I glanced at the MIDI section of that "writing a driver" PDF before I wrote to this list. I guess I'll go actually read it now. :)
FYI, I'm now on kernel 3.2.18-rt23 #2 SMP PREEMPT x86_64.
BTW: What is the output of "lsfirewire -v" for these devices?
device fw1: vendor ID: 0x001260 model ID: 0x001000 vendor: Stanton DJ model: SCS.1m guid: 0x0012600000000000 units: 0x00a02d:0x010001 unit fw1.0: model ID: 0x001000 model: SCS.1m specifier ID: 0x00a02d version: 0x010001 device fw2: vendor ID: 0x001260 model ID: 0x002000 vendor: Stanton DJ model: SCS.1d guid: 0x0012600100000000 units: 0x00a02d:0x010001 unit fw2.0: model ID: 0x002000 model: SCS.1d specifier ID: 0x00a02d version: 0x010001
bus 0, node 0: 080028:424296 Texas Instruments TSB41AB1/2 bus 0, node 1: 080028:424296 Texas Instruments TSB41AB1/2
I tried poking around with firewire-request read but didn't find anything interesting. (Then again, I don't really know what I'm looking at or for.)
Sincerely, Sean M. Pappalardo "D.J. Pegasus" Mixxx Developer - Controller Specialist
Sean M. Pappalardo - D.J. Pegasus wrote:
I just updated my kernel and tried it with the SCS.1m but unfortunately it reports an error:
firewire_core: created device fw1: GUID 0012600000000000, S400 snd_scs1x fw1.0: transaction failed: type error
This is from a snd_fw_transaction call, either the ping or the address setting.
Please check if these commands work:
firewire-request /dev/fwX read c007dedadada 4 firewire-request /dev/fwX write c007dedadada f100123456789abc
In theory, you should be able to write MIDI commands by prefixing them with 00, like this for a note-on: firewire-request /dev/fwX write c007dedadada 00912345
Turning on the SCS.1d then yields: firewire_core: created device fw2: GUID 0012600100000000, S400
FFADO had only the 1m's ID, so the driver does not yet know about the 1d.
Regards, Clemens
On 05/28/2012 04:58 PM, Clemens Ladisch wrote:
Please check if these commands work:
firewire-request /dev/fwX read c007dedadada 4
type error (on both devices)
firewire-request /dev/fwX write c007dedadada f100123456789abc
no response (on both devices)
with 00, like this for a note-on: firewire-request /dev/fwX write c007dedadada 00912345
That worked just fine. I lit up a few LEDs in different colors on both devices, and was able to stop the platter and move the pitch slider on the SCS.1d (yes, its slider is motorized) using CC messages. (I need sysex for the LCD displays though.)
So I feel like we're very close!
Sincerely, Sean M. Pappalardo "D.J. Pegasus" Mixxx Developer - Controller Specialist
Sean M. Pappalardo - D.J. Pegasus wrote:
firewire-request /dev/fwX write c007dedadada f100123456789abc
no response (on both devices)
No error, which is good.
firewire-request /dev/fwX read c007dedadada 4
type error (on both devices)
This is supposed to be used to confirm that this is indeed a HSS1394 device. In theory. But we can just omit this check. :)
New patch below (also with .1d support).
Regards, Clemens
--- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -33,4 +33,17 @@ 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_PCM + select SND_RAWMIDI + 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. + endif # SND_FIREWIRE --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -2,7 +2,9 @@ snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \ fcp.o cmp.o amdtp.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_FIREWIRE_SPEAKERS) += snd-firewire-speakers.o obj-$(CONFIG_SND_ISIGHT) += snd-isight.o +obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o --- /dev/null +++ b/sound/firewire/scs1x.c @@ -0,0 +1,436 @@ +/* + * 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/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 midi_status; + u8 midi_bytes; + struct snd_rawmidi_substream *output; + struct snd_rawmidi_substream *input; + struct tasklet_struct tasklet; + wait_queue_head_t idle_wait; + u8 *buffer; +}; + +static int scs_output_open(struct snd_rawmidi_substream *stream) +{ + struct scs *scs = stream->rmidi->private_data; + + scs->midi_status = 0; + scs->midi_bytes = 0; + + 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; +} + +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->midi_bytes; + for (;;) { + if (snd_rawmidi_transmit(stream, &byte, 1) != 1) { + scs->midi_bytes = i; + scs->output_idle = true; + wake_up(&scs->idle_wait); + return; + } + /* + * Convert from real MIDI to what the device expects (no + * running status, one command per packet). + */ + if (byte < 0x80) { + if (i == 0) { + if (!is_valid_running_status(scs->midi_status)) + continue; + scs->buffer[++i] = scs->midi_status; + } + scs->buffer[++i] = byte; + if ((i == 2 && is_two_bytes_cmd(scs->midi_status)) || + (i == 3 && is_three_bytes_cmd(scs->midi_status))) + break; + if (i >= HSS1394_MAX_PACKET_SIZE - 1) + i = 0; + } else if (byte == 0xf7) { + if (i > 0 && scs->midi_status == 0xf0) { + scs->buffer[++i] = 0xf7; + break; + } + } else if (!is_invalid_cmd(byte) && + byte < 0xf8) { + i = 0; + scs->buffer[++i] = byte; + scs->midi_status = byte; + if (is_one_byte_cmd(byte)) + break; + } + } + scs->midi_bytes = 0; + + 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, 1 + 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) +{ + 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 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 && ((const u8 *)data)[0] == HSS1394_TAG_USER_DATA) { + stream = ACCESS_ONCE(scs->input); + if (stream) + snd_rawmidi_receive(stream, data + 1, length - 1); + } + + fw_send_response(card, request, RCODE_COMPLETE); +} + +static int scs_init_hss_address(struct scs *scs) +{ + u8 data[8]; + int err; + + *(__be64 *)data = cpu_to_be64(scs->hss_handler.offset); + data[0] = HSS1394_TAG_CHANGE_ADDRESS; + err = snd_fw_transaction(scs->unit, TCODE_WRITE_BLOCK_REQUEST, + HSS1394_ADDRESS, data, 8); + 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 device *unit_dev) +{ + struct fw_unit *unit = fw_unit(unit_dev); + struct fw_device *fw_dev = fw_parent_device(unit); + struct snd_card *card; + struct scs *scs; + int err; + + err = snd_card_create(-16, NULL, THIS_MODULE, sizeof(*scs), &card); + if (err < 0) + return err; + snd_card_set_dev(card, unit_dev); + + 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) + goto err_card; + scs->buffer[0] = HSS1394_TAG_USER_DATA; + + 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_dev, scs); + + return 0; + +err_buffer: + kfree(scs->buffer); +err_card: + snd_card_free(card); + return err; +} + +static int scs_remove(struct device *dev) +{ + struct scs *scs = dev_get_drvdata(dev); + + 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); + + return 0; +} + +static void scs_update(struct fw_unit *unit) +{ + struct scs *scs = dev_get_drvdata(&unit->device); + int generation; + u8 data[8]; + + *(__be64 *)data = cpu_to_be64(scs->hss_handler.offset); + data[0] = HSS1394_TAG_CHANGE_ADDRESS; + 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); +} + +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, + .remove = scs_remove, + }, + .update = scs_update, + .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);
On 05/29/2012 07:05 PM, Clemens Ladisch wrote:
New patch below (also with .1d support).
Sweet! It works perfectly with the SCS.1m!! The .1d works, but some of the non-standard data it sends appears to be getting corrupted. In particular, the device sends four-byte platter messages that start with 0xF9 and the remaining three can range from 0x00 to 0xFF. These are coming across as random three- or one-byte messages which cause all kinds of mayhem. (See attached amidi dump. Each line should begin with F9 and have three more bytes after it.)
Outbound SYSEX seems fine on both devices.
Sincerely, Sean M. Pappalardo "D.J. Pegasus" Mixxx Developer - Controller Specialist
I wrote:
I wrote:
Sean M. Pappalardo - D.J. Pegasus wrote:
I also see an F9 message slip through once in awhile according to amidi -d:
The driver currently assumes that there is exactly one MIDI message per packet. It appears that this is not true, and if F9 and real MIDI commands are mixed in one packet, the result is not correct.
New patch below.
Ping? Does it work? Wouldn't it be time to move it into the kernel? Would you be OK with this tag on the patch? Tested-by: Sean M. Pappalardo - D.J. Pegasus spappalardo@mixxx.org
Regards, Clemens
Hello again.
On 07/24/2012 01:44 PM, Clemens Ladisch wrote:
Ping? Does it work? Wouldn't it be time to move it into the kernel?
I haven't tested the latest one fully and won't be able to for a couple of weeks due to traveling.
Would you be OK with this tag on the patch? Tested-by: Sean M. Pappalardo - D.J. Pegasusspappalardo@mixxx.org
That's fine. I'm happy to provide help to other users if needed.
Sincerely, Sean M. Pappalardo "D.J. Pegasus" Mixxx Developer - Controller Specialist
participants (2)
-
Clemens Ladisch
-
Sean M. Pappalardo - D.J. Pegasus