[alsa-devel] [RFC][PATCH 0/9] ALSA: oxfw: merge scs1x module
Hi,
This patchset is for merging ALSA scs1x module to oxfw module.
Models which scs1x supports are based on OXFW971. You can see it in HSS1394 codes. HSS1394 was a project to investigate protocols for Stanton Controllers and Systems 1. http://bazaar.launchpad.net/~mixxxdevelopers/hss1394/trunk/view/head:/code/s...
ALSA firewire stack already has oxfw driver for OXFW970/971, therefore it's better to merge scs1x module to it. Then, the stack supports SCS.1m with additional PCM functionality.
This patchset consists of three parts: * 01: add a framework to support model-dependent functionalities * 02-04: apply the framework for firewire-speakers * 05-09: merge scs1x module with the framework
As I describe in patch 05, there's an issue to SCS.1d. Please see the commit comment.
Takashi Sakamoto (9): ALSA: oxfw: add a small framework for model-unique functionalities ALSA: oxfw: rename a file for control elements so that it means being model-specific ALSA: oxfw: rename local functions for control elements so that they represent as local ALSA: oxfw: apply model-specific functionality framework to firewire-speakers ALSA: oxfw: add scs1x layer ALSA: oxfw: copy MIDI capture functionality to scs1x layer ALSA: oxfw: copy MIDI playback functionality to scs1x layer ALSA: oxfw: optimize MIDI operations to model-specific framework ALSA: oxfw: obsolete scs1x module
sound/firewire/Kconfig | 12 +- sound/firewire/Makefile | 2 - sound/firewire/oxfw/Makefile | 4 +- sound/firewire/oxfw/oxfw-scs1x.c | 413 ++++++++++++++++ .../firewire/oxfw/{oxfw-control.c => oxfw-spkr.c} | 116 +++-- sound/firewire/oxfw/oxfw.c | 103 ++-- sound/firewire/oxfw/oxfw.h | 25 +- sound/firewire/scs1x.c | 530 --------------------- 8 files changed, 575 insertions(+), 630 deletions(-) create mode 100644 sound/firewire/oxfw/oxfw-scs1x.c rename sound/firewire/oxfw/{oxfw-control.c => oxfw-spkr.c} (62%) delete mode 100644 sound/firewire/scs1x.c
Some models based on OXFW970/971 have unique functionalities, which kernel driver should support.
This commit adds a small framework to assist implementation of the functionalities. The 'struct snd_oxfw_spec' have callbacks of addition, removal and bus update. The structure also has a member for the size of private data to allocate memory block.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/oxfw.c | 25 +++++++++++++++++++++++++ sound/firewire/oxfw/oxfw.h | 10 ++++++++++ 2 files changed, 35 insertions(+)
diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index 588b93f..a9599ce 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -122,6 +122,13 @@ static void oxfw_card_free(struct snd_card *card) if (oxfw->has_output) snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->tx_stream);
+ if (oxfw->spec) { + if (oxfw->spec->remove) + oxfw->spec->remove(oxfw); + if (oxfw->spec->private_size) + kfree(oxfw->spec->private_data); + } + fw_unit_put(oxfw->unit);
for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { @@ -196,10 +203,25 @@ static int oxfw_probe(struct fw_unit *unit,
detect_quirks(oxfw);
+ if (oxfw->spec && oxfw->spec->private_size > 0) { + oxfw->spec->private_data = + kzalloc(oxfw->spec->private_size, GFP_KERNEL); + if (oxfw->spec->private_data == NULL) { + err = -ENOMEM; + goto error; + } + } + err = name_card(oxfw); if (err < 0) goto error;
+ if (oxfw->spec && oxfw->spec->add) { + err = oxfw->spec->add(oxfw); + if (err < 0) + goto error; + } + err = snd_oxfw_create_pcm(oxfw); if (err < 0) goto error; @@ -250,6 +272,9 @@ static void oxfw_bus_reset(struct fw_unit *unit)
fcp_bus_reset(oxfw->unit);
+ if (oxfw->spec && oxfw->spec->update) + oxfw->spec->update(oxfw); + mutex_lock(&oxfw->mutex);
snd_oxfw_stream_update_simplex(oxfw, &oxfw->rx_stream); diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index 8392c42..922e5da 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -40,6 +40,15 @@ struct device_info { u8 volume_fb_id; };
+struct snd_oxfw; +struct snd_oxfw_spec { + int (*add)(struct snd_oxfw *oxfw); + void (*update)(struct snd_oxfw *oxfw); + void (*remove)(struct snd_oxfw *oxfw); + void *private_data; + unsigned int private_size; +}; + /* This is an arbitrary number for convinience. */ #define SND_OXFW_STREAM_FORMAT_ENTRIES 10 struct snd_oxfw { @@ -68,6 +77,7 @@ struct snd_oxfw { s16 volume[6]; s16 volume_min; s16 volume_max; + struct snd_oxfw_spec *spec;
int dev_lock_count; bool dev_lock_changed;
Basically, drivers in ALSA firewire stack implement no control elements, because they can be implemented in userspace as Linux FireWire subsystem interface applications. Nevertheless, ALSA oxfw driver has some control elements. This is due to backward compatibility to old firewire-speakers module. The control elements are model-specific, while the file name gives an impression of oxfw generic functionality.
This commit renames the file so that it's specific for two models.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/Makefile | 4 ++-- sound/firewire/oxfw/{oxfw-control.c => oxfw-spkr.c} | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) rename sound/firewire/oxfw/{oxfw-control.c => oxfw-spkr.c} (98%)
diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile index 06ff50f..4e54ba9 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.o obj-$(CONFIG_SND_OXFW) += snd-oxfw.o diff --git a/sound/firewire/oxfw/oxfw-control.c b/sound/firewire/oxfw/oxfw-spkr.c similarity index 98% rename from sound/firewire/oxfw/oxfw-control.c rename to sound/firewire/oxfw/oxfw-spkr.c index 02a1cb9..ba766be 100644 --- a/sound/firewire/oxfw/oxfw-control.c +++ b/sound/firewire/oxfw/oxfw-spkr.c @@ -1,7 +1,9 @@ /* - * 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 + * Copyright (c) 2015 Takashi Sakamoto o-takashi@sakamocchi.jp + * * Licensed under the terms of the GNU General Public License, version 2. */
This commit renames local functions with prefix 'spkr_', so that they're in firewire-speaker layer.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/oxfw-spkr.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-)
diff --git a/sound/firewire/oxfw/oxfw-spkr.c b/sound/firewire/oxfw/oxfw-spkr.c index ba766be..515468a 100644 --- a/sound/firewire/oxfw/oxfw-spkr.c +++ b/sound/firewire/oxfw/oxfw-spkr.c @@ -16,7 +16,7 @@ enum control_attribute { CTL_CURRENT = 0x10, };
-static int oxfw_mute_command(struct snd_oxfw *oxfw, bool *value, +static int spkr_mute_command(struct snd_oxfw *oxfw, bool *value, enum control_action action) { u8 *buf; @@ -72,7 +72,7 @@ error: return err; }
-static int oxfw_volume_command(struct snd_oxfw *oxfw, s16 *value, +static int spkr_volume_command(struct snd_oxfw *oxfw, s16 *value, unsigned int channel, enum control_attribute attribute, enum control_action action) @@ -133,7 +133,7 @@ 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; @@ -143,7 +143,7 @@ static int oxfw_mute_get(struct snd_kcontrol *control, 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; @@ -155,7 +155,7 @@ static int oxfw_mute_put(struct snd_kcontrol *control, if (mute == oxfw->mute) return 0;
- err = oxfw_mute_command(oxfw, &mute, CTL_WRITE); + err = spkr_mute_command(oxfw, &mute, CTL_WRITE); if (err < 0) return err; oxfw->mute = mute; @@ -163,7 +163,7 @@ static int oxfw_mute_put(struct snd_kcontrol *control, 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; @@ -178,7 +178,7 @@ static int oxfw_volume_info(struct snd_kcontrol *control,
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; @@ -190,7 +190,7 @@ static int oxfw_volume_get(struct snd_kcontrol *control, 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; @@ -220,7 +220,7 @@ static int oxfw_volume_put(struct snd_kcontrol *control, for (i = 0; i <= oxfw->device_info->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, + err = spkr_volume_command(oxfw, &volume, i, CTL_CURRENT, CTL_WRITE); if (err < 0) return err; @@ -239,36 +239,36 @@ int snd_oxfw_create_mixer(struct snd_oxfw *oxfw) .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, }, }; unsigned int i, first_ch; int err;
- err = oxfw_volume_command(oxfw, &oxfw->volume_min, + err = spkr_volume_command(oxfw, &oxfw->volume_min, 0, CTL_MIN, CTL_READ); if (err < 0) return err; - err = oxfw_volume_command(oxfw, &oxfw->volume_max, + err = spkr_volume_command(oxfw, &oxfw->volume_max, 0, CTL_MAX, CTL_READ); if (err < 0) return err;
- err = oxfw_mute_command(oxfw, &oxfw->mute, CTL_READ); + err = spkr_mute_command(oxfw, &oxfw->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], + err = spkr_volume_command(oxfw, &oxfw->volume[i], first_ch + i, CTL_CURRENT, CTL_READ); if (err < 0) return err;
In former commits, ALSA oxfw driver got a framework for model-specific functionalities. This commit applies the framework to control elements for firewire-speakers.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/oxfw-spkr.c | 84 +++++++++++++++++++++++++++++------------ sound/firewire/oxfw/oxfw.c | 50 +++++++++--------------- sound/firewire/oxfw/oxfw.h | 18 +-------- 3 files changed, 79 insertions(+), 73 deletions(-)
diff --git a/sound/firewire/oxfw/oxfw-spkr.c b/sound/firewire/oxfw/oxfw-spkr.c index 515468a..6527a16 100644 --- a/sound/firewire/oxfw/oxfw-spkr.c +++ b/sound/firewire/oxfw/oxfw-spkr.c @@ -9,6 +9,18 @@
#include "oxfw.h"
+/* Old firewire-speakers module supports some control elements. */ +struct fw_spkr { + unsigned int mixer_channels; + u8 mute_fb_id; + u8 volume_fb_id; + + bool mute; + s16 volume[6]; + s16 volume_min; + s16 volume_max; +}; + enum control_action { CTL_READ, CTL_WRITE }; enum control_attribute { CTL_MIN = 0x02, @@ -19,6 +31,7 @@ enum control_attribute { static int spkr_mute_command(struct snd_oxfw *oxfw, bool *value, enum control_action action) { + struct fw_spkr *spkr = oxfw->spec->private_data; u8 *buf; u8 response_ok; int err; @@ -37,7 +50,7 @@ static int spkr_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] = spkr->mute_fb_id; /* function block ID */ buf[5] = 0x10; /* control attribute: current */ buf[6] = 0x02; /* selector length */ buf[7] = 0x00; /* audio channel number */ @@ -77,6 +90,7 @@ static int spkr_volume_command(struct snd_oxfw *oxfw, s16 *value, enum control_attribute attribute, enum control_action action) { + struct fw_spkr *spkr = oxfw->spec->private_data; u8 *buf; u8 response_ok; int err; @@ -95,7 +109,7 @@ static int spkr_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] = spkr->volume_fb_id; /* function block ID */ buf[5] = attribute; /* control attribute */ buf[6] = 0x02; /* selector length */ buf[7] = channel; /* audio channel number */ @@ -137,8 +151,9 @@ 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->private_data;
- value->value.integer.value[0] = !oxfw->mute; + value->value.integer.value[0] = !spkr->mute;
return 0; } @@ -147,18 +162,19 @@ 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->private_data; bool mute; int err;
mute = !value->value.integer.value[0];
- if (mute == oxfw->mute) + if (mute == spkr->mute) return 0;
err = spkr_mute_command(oxfw, &mute, CTL_WRITE); if (err < 0) return err; - oxfw->mute = mute; + spkr->mute = mute;
return 1; } @@ -167,11 +183,12 @@ 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->private_data;
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; } @@ -182,10 +199,11 @@ 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->private_data; 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; } @@ -194,14 +212,15 @@ 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->private_data; 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]) @@ -209,15 +228,15 @@ static int spkr_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 = spkr_volume_command(oxfw, &volume, i, @@ -226,13 +245,13 @@ static int spkr_volume_put(struct snd_kcontrol *control, 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) +static int spkr_add(struct snd_oxfw *oxfw) { static const struct snd_kcontrol_new controls[] = { { @@ -250,25 +269,37 @@ int snd_oxfw_create_mixer(struct snd_oxfw *oxfw) .put = spkr_volume_put, }, }; + struct fw_spkr *spkr = oxfw->spec->private_data; unsigned int i, first_ch; int err;
- err = spkr_volume_command(oxfw, &oxfw->volume_min, + if (strcmp(oxfw->card->driver, "FireWave") == 0) { + spkr->mixer_channels = 6; + spkr->mute_fb_id = 0x01; + spkr->volume_fb_id = 0x02; + } + if (strcmp(oxfw->card->driver, "FWSpeakers") == 0) { + spkr->mixer_channels = 1; + spkr->mute_fb_id = 0x01; + spkr->volume_fb_id = 0x01; + } + + err = spkr_volume_command(oxfw, &spkr->volume_min, 0, CTL_MIN, CTL_READ); if (err < 0) return err; - err = spkr_volume_command(oxfw, &oxfw->volume_max, + err = spkr_volume_command(oxfw, &spkr->volume_max, 0, CTL_MAX, CTL_READ); if (err < 0) return err;
- err = spkr_mute_command(oxfw, &oxfw->mute, CTL_READ); + err = spkr_mute_command(oxfw, &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 = spkr_volume_command(oxfw, &oxfw->volume[i], + first_ch = spkr->mixer_channels == 1 ? 0 : 1; + for (i = 0; i < spkr->mixer_channels; ++i) { + err = spkr_volume_command(oxfw, &spkr->volume[i], first_ch + i, CTL_CURRENT, CTL_READ); if (err < 0) return err; @@ -283,3 +314,8 @@ int snd_oxfw_create_mixer(struct snd_oxfw *oxfw)
return 0; } + +struct snd_oxfw_spec snd_oxfw_spec_spkr = { + .add = spkr_add, + .private_size = sizeof(struct fw_spkr), +}; diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index a9599ce..ee7094b 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -56,7 +56,7 @@ static bool detect_loud_models(struct fw_unit *unit) return (i < ARRAY_SIZE(models)); }
-static int name_card(struct snd_oxfw *oxfw) +static int name_card(struct snd_oxfw *oxfw, const struct ieee1394_device_id *id) { struct fw_device *fw_dev = fw_parent_device(oxfw->unit); char vendor[24]; @@ -84,10 +84,14 @@ 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 (id->vendor_id == VENDOR_GRIFFIN) { + d = "FireWave"; + v = "Griffin"; + m = "FireWave"; + } else if (id->vendor_id == VENDOR_LACIE) { + d = "FWSpeakers"; + v = "LaCie"; + m = "FireWire Speakers"; } else { d = "OXFW"; v = vendor; @@ -171,6 +175,13 @@ static void detect_quirks(struct snd_oxfw *oxfw) oxfw->midi_input_ports++; oxfw->midi_output_ports++; } + + /* + * For compatibility that old firewire-speaker modules add ALSA control + * character devices for these two models. + */ + if (vendor == VENDOR_GRIFFIN || vendor == VENDOR_LACIE) + oxfw->spec = &snd_oxfw_spec_spkr; }
static int oxfw_probe(struct fw_unit *unit, @@ -193,7 +204,6 @@ 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; spin_lock_init(&oxfw->lock); init_waitqueue_head(&oxfw->hwdep_wait);
@@ -212,7 +222,7 @@ static int oxfw_probe(struct fw_unit *unit, } }
- err = name_card(oxfw); + err = name_card(oxfw, id); if (err < 0) goto error;
@@ -226,12 +236,6 @@ static int oxfw_probe(struct fw_unit *unit, if (err < 0) goto error;
- if (oxfw->device_info) { - err = snd_oxfw_create_mixer(oxfw); - if (err < 0) - goto error; - } - snd_oxfw_proc_init(oxfw);
err = snd_oxfw_create_midi(oxfw); @@ -292,24 +296,6 @@ static void oxfw_remove(struct fw_unit *unit) snd_card_free_when_closed(oxfw->card); }
-static const struct device_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 = { - .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[] = { { .match_flags = IEEE1394_MATCH_VENDOR_ID | @@ -320,7 +306,6 @@ static const struct ieee1394_device_id oxfw_id_table[] = { .model_id = 0x00f970, .specifier_id = SPECIFIER_1394TA, .version = VERSION_AVC, - .driver_data = (kernel_ulong_t)&griffin_firewave, }, { .match_flags = IEEE1394_MATCH_VENDOR_ID | @@ -331,7 +316,6 @@ static const struct ieee1394_device_id oxfw_id_table[] = { .model_id = 0x00f970, .specifier_id = SPECIFIER_1394TA, .version = VERSION_AVC, - .driver_data = (kernel_ulong_t)&lacie_speakers, }, /* Behringer,F-Control Audio 202 */ { diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index 922e5da..473faafbb 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; -}; - struct snd_oxfw; struct snd_oxfw_spec { int (*add)(struct snd_oxfw *oxfw); @@ -54,7 +45,6 @@ struct snd_oxfw_spec { struct snd_oxfw { struct snd_card *card; struct fw_unit *unit; - const struct device_info *device_info; struct mutex mutex; spinlock_t lock;
@@ -73,10 +63,6 @@ struct snd_oxfw { unsigned int midi_input_ports; unsigned int midi_output_ports;
- bool mute; - s16 volume[6]; - s16 volume_min; - s16 volume_max; struct snd_oxfw_spec *spec;
int dev_lock_count; @@ -148,10 +134,10 @@ 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); + +extern struct snd_oxfw_spec snd_oxfw_spec_spkr;
On Sun, 15 Nov 2015 10:26:00 +0100, Takashi Sakamoto wrote:
- err = spkr_volume_command(oxfw, &oxfw->volume_min,
- if (strcmp(oxfw->card->driver, "FireWave") == 0) {
spkr->mixer_channels = 6;
spkr->mute_fb_id = 0x01;
spkr->volume_fb_id = 0x02;
- }
- if (strcmp(oxfw->card->driver, "FWSpeakers") == 0) {
spkr->mixer_channels = 1;
spkr->mute_fb_id = 0x01;
spkr->volume_fb_id = 0x01;
- }
What's the merit of such explicit individual conditional over the constant table in the current implementation? The latter is more error-prone and simpler in general.
thanks,
Takashi
Hi,
On Nov 18 2015 23:17, Takashi Iwai wrote:
On Sun, 15 Nov 2015 10:26:00 +0100, Takashi Sakamoto wrote:
- err = spkr_volume_command(oxfw, &oxfw->volume_min,
- if (strcmp(oxfw->card->driver, "FireWave") == 0) {
spkr->mixer_channels = 6;
spkr->mute_fb_id = 0x01;
spkr->volume_fb_id = 0x02;
- }
- if (strcmp(oxfw->card->driver, "FWSpeakers") == 0) {
spkr->mixer_channels = 1;
spkr->mute_fb_id = 0x01;
spkr->volume_fb_id = 0x01;
- }
What's the merit of such explicit individual conditional over the constant table in the current implementation? The latter is more error-prone and simpler in general.
I'm also concerned about it. Yes, the usage of 'struct ieee1394_device_id.driver_data' is nicer than thse condition statements, in this point.
My intension of a part of this patch series is to enclose model-dependent parameters inner model-dependent files, instead of adding module-public structure. This idea, itself, is not so bad, I think.
There may be better ways to detect models and assign to structure but I don't still find it.
Thanks
Takashi Sakamoto
On Fri, 20 Nov 2015 03:28:39 +0100, Takashi Sakamoto wrote:
Hi,
On Nov 18 2015 23:17, Takashi Iwai wrote:
On Sun, 15 Nov 2015 10:26:00 +0100, Takashi Sakamoto wrote:
- err = spkr_volume_command(oxfw, &oxfw->volume_min,
- if (strcmp(oxfw->card->driver, "FireWave") == 0) {
spkr->mixer_channels = 6;
spkr->mute_fb_id = 0x01;
spkr->volume_fb_id = 0x02;
- }
- if (strcmp(oxfw->card->driver, "FWSpeakers") == 0) {
spkr->mixer_channels = 1;
spkr->mute_fb_id = 0x01;
spkr->volume_fb_id = 0x01;
- }
What's the merit of such explicit individual conditional over the constant table in the current implementation? The latter is more error-prone and simpler in general.
I'm also concerned about it. Yes, the usage of 'struct ieee1394_device_id.driver_data' is nicer than thse condition statements, in this point.
My intension of a part of this patch series is to enclose model-dependent parameters inner model-dependent files, instead of adding module-public structure. This idea, itself, is not so bad, I think.
But it's not worth to do open-code in each place, either.
If there were hundreds of different parameters per model, it would be good to hide somehow locally. But in this case, it's only two fields (and even optional), so no big matter, IMO.
Takashi
There may be better ways to detect models and assign to structure but I don't still find it.
Thanks
Takashi Sakamoto
Stanton Controllers and Systems 1 has MIDI functionality to control its surface state such as LED lighting. When operating physical knobs and faders, the models generate MIDI messages. These MIDI messages are transferred by asynchronous transactions.
The models are based on OXFW971 and ALSA OXFW driver can support them, although ALSA scs1x module currently supports them.
This commit adds scs1x layer. As long as I investigate SCS.1m, this model reports to transfer/receive PCM data channels/MIDI conformant data channels in tx/rx AMDTP packet. There's a contradiction that this model actually has no analog/digital capture port for PCM frames and no physical MIDI ports.
I guess that SCS.1d also has the contradiction. This model has no analog/digital ports for PCM frames and no physical MIDI ports, thus it requires no streaming functionality.
This commit also add some modification codes to handle the contradiction, as much as possible. Unfortunately, this module adds one PCM playback substream for SCS.1d so as SCS.1m.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/Makefile | 2 +- sound/firewire/oxfw/oxfw-scs1x.c | 30 ++++++++++++++++++++++++++++++ sound/firewire/oxfw/oxfw.c | 16 ++++++++++++++++ sound/firewire/oxfw/oxfw.h | 1 + 4 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/oxfw/oxfw-scs1x.c
diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile index 4e54ba9..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-pcm.o oxfw-proc.o \ - oxfw-midi.o oxfw-hwdep.o oxfw-spkr.o oxfw.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..c04c76e --- /dev/null +++ b/sound/firewire/oxfw/oxfw-scs1x.c @@ -0,0 +1,30 @@ +/* + * 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" + +static int scs1x_add(struct snd_oxfw *oxfw) +{ + struct snd_rawmidi *rmidi; + int err; + + /* For backward compatibility to scs1x module, use unique name. */ + err = snd_rawmidi_new(oxfw->card, "SCS.1x", 0, 0, 0, &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", oxfw->card->shortname); + + return err; +} + +struct snd_oxfw_spec snd_oxfw_spec_scs1x = { + .add = scs1x_add, +}; diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index ee7094b..97fb5ed 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
@@ -182,6 +183,21 @@ static void detect_quirks(struct snd_oxfw *oxfw) */ if (vendor == VENDOR_GRIFFIN || vendor == VENDOR_LACIE) oxfw->spec = &snd_oxfw_spec_spkr; + + /* + * Stanton models supports asynchronous transactions for unique MIDI + * messages. + */ + if (vendor == 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; + + oxfw->spec = &snd_oxfw_spec_scs1x; + } }
static int oxfw_probe(struct fw_unit *unit, diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h index 473faafbb..bf8b953 100644 --- a/sound/firewire/oxfw/oxfw.h +++ b/sound/firewire/oxfw/oxfw.h @@ -141,3 +141,4 @@ int snd_oxfw_create_midi(struct snd_oxfw *oxfw); int snd_oxfw_create_hwdep(struct snd_oxfw *oxfw);
extern struct snd_oxfw_spec snd_oxfw_spec_spkr; +extern struct snd_oxfw_spec snd_oxfw_spec_scs1x;
This commit copies MIDI capture functionality to merge scs1x module. The features of payload in asynchronous transaction are:
* System exclusive messages for SCS1 are encoded without ID data. In this encoding scheme, 4 bits in LSB are available. The bits are squashed in payload byte. Thus, one payload byte transfers two MIDI messages. * The first byte of payload byte means: * 0x00: depending on second payload byte * 0xf9: including escaped system exclusive messages for SCS1, up to 3 byte (= 6 MIDI messages) * the others: including MIDI 1.0 messages * the others: including escaped system exclusive messages for SCS1, up to 64 bytes
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/oxfw-scs1x.c | 182 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 180 insertions(+), 2 deletions(-)
diff --git a/sound/firewire/oxfw/oxfw-scs1x.c b/sound/firewire/oxfw/oxfw-scs1x.c index c04c76e..5bf52b7 100644 --- a/sound/firewire/oxfw/oxfw-scs1x.c +++ b/sound/firewire/oxfw/oxfw-scs1x.c @@ -9,22 +9,200 @@
#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 snd_oxfw *oxfw; + + /* For MIDI capture. */ + struct fw_address_handler hss_handler; + u8 input_escape_count; + struct snd_rawmidi_substream *input; +}; + +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 int midi_capture_open(struct snd_rawmidi_substream *stream) +{ + struct fw_scs1x *scs = stream->rmidi->private_data; + + scs->input_escape_count = 0; + + 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; + + ACCESS_ONCE(scs->input) = up ? stream : NULL; +} + +static struct snd_rawmidi_ops capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, +}; + +static int register_address(struct snd_oxfw *oxfw) +{ + struct fw_scs1x *scs = oxfw->spec->private_data; + __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 int scs1x_add(struct snd_oxfw *oxfw) { + struct fw_scs1x *scs = oxfw->spec->private_data; struct snd_rawmidi *rmidi; int err;
- /* For backward compatibility to scs1x module, use unique name. */ - err = snd_rawmidi_new(oxfw->card, "SCS.1x", 0, 0, 0, &rmidi); + /* 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;
+ /* Register the address. */ + err = register_address(oxfw); + if (err < 0) { + fw_core_remove_address_handler(&scs->hss_handler); + return err; + } + + /* For backward compatibility to scs1x module, use unique name. */ + err = snd_rawmidi_new(oxfw->card, "SCS.1x", 0, 0, 1, &rmidi); + if (err < 0) { + fw_core_remove_address_handler(&scs->hss_handler); + return err; + } + snprintf(rmidi->name, sizeof(rmidi->name), "%s MIDI", oxfw->card->shortname); + rmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &capture_ops); + rmidi->private_data = scs;
return err; }
+static void scs1x_update(struct snd_oxfw *oxfw) +{ + register_address(oxfw); +} + +static void scs1x_remove(struct snd_oxfw *oxfw) +{ + struct fw_scs1x *scs = oxfw->spec->private_data; + + ACCESS_ONCE(scs->input) = NULL; + + fw_core_remove_address_handler(&scs->hss_handler); +} + struct snd_oxfw_spec snd_oxfw_spec_scs1x = { .add = scs1x_add, + .update = scs1x_update, + .remove = scs1x_remove, + .private_size = sizeof(struct fw_scs1x), };
This commit copies MIDI playback functionality to merge scs1x module. The features of payload in asynchronous transaction are the same as captured MIDI messages.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/oxfw-scs1x.c | 208 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 206 insertions(+), 2 deletions(-)
diff --git a/sound/firewire/oxfw/oxfw-scs1x.c b/sound/firewire/oxfw/oxfw-scs1x.c index 5bf52b7..89ba8f6 100644 --- a/sound/firewire/oxfw/oxfw-scs1x.c +++ b/sound/firewire/oxfw/oxfw-scs1x.c @@ -22,6 +22,19 @@ 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; };
static const u8 sysex_escape_prefix[] = { @@ -110,6 +123,151 @@ 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_oxfw *oxfw = scs->oxfw; + 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(oxfw->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 int midi_capture_open(struct snd_rawmidi_substream *stream) { struct fw_scs1x *scs = stream->rmidi->private_data; @@ -137,6 +295,39 @@ static struct snd_rawmidi_ops capture_ops = { .trigger = midi_capture_trigger, };
+static int midi_playback_open(struct snd_rawmidi_substream *stream) +{ + struct fw_scs1x *scs = stream->rmidi->private_data; + + scs->output_status = 0; + scs->output_bytes = 1; + scs->output_escaped = false; + + 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; + + ACCESS_ONCE(scs->output) = up ? stream : NULL; + if (up) { + scs->output_idle = false; + tasklet_schedule(&scs->tasklet); + } +} + +static struct snd_rawmidi_ops playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, +}; + static int register_address(struct snd_oxfw *oxfw) { struct fw_scs1x *scs = oxfw->spec->private_data; @@ -171,7 +362,7 @@ static int scs1x_add(struct snd_oxfw *oxfw) }
/* For backward compatibility to scs1x module, use unique name. */ - err = snd_rawmidi_new(oxfw->card, "SCS.1x", 0, 0, 1, &rmidi); + err = snd_rawmidi_new(oxfw->card, "SCS.1x", 0, 1, 1, &rmidi); if (err < 0) { fw_core_remove_address_handler(&scs->hss_handler); return err; @@ -179,10 +370,18 @@ static int scs1x_add(struct snd_oxfw *oxfw)
snprintf(rmidi->name, sizeof(rmidi->name), "%s MIDI", oxfw->card->shortname); - rmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT; + rmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, &capture_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, &playback_ops); rmidi->private_data = scs;
+ scs->oxfw = oxfw; + tasklet_init(&scs->tasklet, scs_output_tasklet, (unsigned long)scs); + init_waitqueue_head(&scs->idle_wait); + scs->output_idle = true; + return err; }
@@ -195,8 +394,13 @@ static void scs1x_remove(struct snd_oxfw *oxfw) { struct fw_scs1x *scs = oxfw->spec->private_data;
+ ACCESS_ONCE(scs->output) = NULL; ACCESS_ONCE(scs->input) = NULL;
+ wait_event(scs->idle_wait, scs->output_idle); + + tasklet_kill(&scs->tasklet); + fw_core_remove_address_handler(&scs->hss_handler); }
In scs1x module, MIDI substream caches are released at .remove callback of unit driver on IEEE 1394 bus, while oxfw model-specific framework allows it in ALSA card free callback. MIDI close operations should be changed.
This commit applies a small optimization for MIDI operations.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/oxfw/oxfw-scs1x.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-)
diff --git a/sound/firewire/oxfw/oxfw-scs1x.c b/sound/firewire/oxfw/oxfw-scs1x.c index 89ba8f6..47d346c 100644 --- a/sound/firewire/oxfw/oxfw-scs1x.c +++ b/sound/firewire/oxfw/oxfw-scs1x.c @@ -303,11 +303,20 @@ static int midi_playback_open(struct snd_rawmidi_substream *stream) scs->output_bytes = 1; scs->output_escaped = false;
+ ACCESS_ONCE(scs->output) = stream; + return 0; }
static int midi_playback_close(struct snd_rawmidi_substream *stream) { + struct fw_scs1x *scs = stream->rmidi->private_data; + + ACCESS_ONCE(scs->output) = NULL; + + wait_event(scs->idle_wait, scs->output_idle); + tasklet_kill(&scs->tasklet); + return 0; }
@@ -315,7 +324,6 @@ static void midi_playback_trigger(struct snd_rawmidi_substream *stream, int up) { struct fw_scs1x *scs = stream->rmidi->private_data;
- ACCESS_ONCE(scs->output) = up ? stream : NULL; if (up) { scs->output_idle = false; tasklet_schedule(&scs->tasklet); @@ -394,13 +402,6 @@ static void scs1x_remove(struct snd_oxfw *oxfw) { struct fw_scs1x *scs = oxfw->spec->private_data;
- ACCESS_ONCE(scs->output) = NULL; - ACCESS_ONCE(scs->input) = NULL; - - wait_event(scs->idle_wait, scs->output_idle); - - tasklet_kill(&scs->tasklet); - fw_core_remove_address_handler(&scs->hss_handler); }
Now ALSA oxfw driver gains functionalities which scs1x module has.
This commit obsoletes the scs1x module. This commit also adds a line of MODULE_ALIAS to load oxfw module instead of scs1x module.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 12 +- sound/firewire/Makefile | 2 - sound/firewire/oxfw/oxfw.c | 14 ++ sound/firewire/scs1x.c | 530 --------------------------------------------- 4 files changed, 15 insertions(+), 543 deletions(-) delete mode 100644 sound/firewire/scs1x.c
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/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c index 97fb5ed..0850ca4 100644 --- a/sound/firewire/oxfw/oxfw.c +++ b/sound/firewire/oxfw/oxfw.c @@ -365,6 +365,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/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);
participants (2)
-
Takashi Iwai
-
Takashi Sakamoto