diff options
Diffstat (limited to 'sound/firewire/oxfw')
-rw-r--r-- | sound/firewire/oxfw/Makefile | 3 | ||||
-rw-r--r-- | sound/firewire/oxfw/oxfw-command.c | 159 | ||||
-rw-r--r-- | sound/firewire/oxfw/oxfw-hwdep.c | 190 | ||||
-rw-r--r-- | sound/firewire/oxfw/oxfw-midi.c | 187 | ||||
-rw-r--r-- | sound/firewire/oxfw/oxfw-pcm.c | 423 | ||||
-rw-r--r-- | sound/firewire/oxfw/oxfw-proc.c | 113 | ||||
-rw-r--r-- | sound/firewire/oxfw/oxfw-scs1x.c | 420 | ||||
-rw-r--r-- | sound/firewire/oxfw/oxfw-spkr.c | 319 | ||||
-rw-r--r-- | sound/firewire/oxfw/oxfw-stream.c | 730 | ||||
-rw-r--r-- | sound/firewire/oxfw/oxfw.c | 470 | ||||
-rw-r--r-- | sound/firewire/oxfw/oxfw.h | 141 |
11 files changed, 3155 insertions, 0 deletions
diff --git a/sound/firewire/oxfw/Makefile b/sound/firewire/oxfw/Makefile new file mode 100644 index 000000000..b474da7c6 --- /dev/null +++ b/sound/firewire/oxfw/Makefile @@ -0,0 +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-scs1x.o oxfw.o +obj-$(CONFIG_SND_OXFW) += snd-oxfw.o diff --git a/sound/firewire/oxfw/oxfw-command.c b/sound/firewire/oxfw/oxfw-command.c new file mode 100644 index 000000000..ac3e2e301 --- /dev/null +++ b/sound/firewire/oxfw/oxfw-command.c @@ -0,0 +1,159 @@ +/* + * oxfw_command.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "oxfw.h" + +int avc_stream_set_format(struct fw_unit *unit, enum avc_general_plug_dir dir, + unsigned int pid, u8 *format, unsigned int len) +{ + u8 *buf; + int err; + + buf = kmalloc(len + 10, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x00; /* CONTROL */ + buf[1] = 0xff; /* UNIT */ + buf[2] = 0xbf; /* EXTENDED STREAM FORMAT INFORMATION */ + buf[3] = 0xc0; /* SINGLE subfunction */ + buf[4] = dir; /* Plug Direction */ + buf[5] = 0x00; /* UNIT */ + buf[6] = 0x00; /* PCR (Isochronous Plug) */ + buf[7] = 0xff & pid; /* Plug ID */ + buf[8] = 0xff; /* Padding */ + buf[9] = 0xff; /* Support status in response */ + memcpy(buf + 10, format, len); + + /* do transaction and check buf[1-8] are the same against command */ + err = fcp_avc_transaction(unit, buf, len + 10, buf, len + 10, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(8)); + if (err < 0) + ; + else if (err < len + 10) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else + err = 0; + + kfree(buf); + + return err; +} + +int avc_stream_get_format(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len, unsigned int eid) +{ + unsigned int subfunc; + int err; + + if (eid == 0xff) + subfunc = 0xc0; /* SINGLE */ + else + subfunc = 0xc1; /* LIST */ + + buf[0] = 0x01; /* STATUS */ + buf[1] = 0xff; /* UNIT */ + buf[2] = 0xbf; /* EXTENDED STREAM FORMAT INFORMATION */ + buf[3] = subfunc; /* SINGLE or LIST */ + buf[4] = dir; /* Plug Direction */ + buf[5] = 0x00; /* Unit */ + buf[6] = 0x00; /* PCR (Isochronous Plug) */ + buf[7] = 0xff & pid; /* Plug ID */ + buf[8] = 0xff; /* Padding */ + buf[9] = 0xff; /* support status in response */ + buf[10] = 0xff & eid; /* entry ID for LIST subfunction */ + buf[11] = 0xff; /* padding */ + + /* do transaction and check buf[1-7] are the same against command */ + err = fcp_avc_transaction(unit, buf, 12, buf, *len, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7)); + if (err < 0) + ; + else if (err < 12) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + /* LIST subfunction has entry ID */ + else if ((subfunc == 0xc1) && (buf[10] != eid)) + err = -EIO; + if (err < 0) + goto end; + + /* keep just stream format information */ + if (subfunc == 0xc0) { + memmove(buf, buf + 10, err - 10); + *len = err - 10; + } else { + memmove(buf, buf + 11, err - 11); + *len = err - 11; + } + + err = 0; +end: + return err; +} + +int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate, + enum avc_general_plug_dir dir, + unsigned short pid) +{ + unsigned int sfc; + u8 *buf; + int err; + + for (sfc = 0; sfc < CIP_SFC_COUNT; sfc++) { + if (amdtp_rate_table[sfc] == rate) + break; + } + if (sfc == CIP_SFC_COUNT) + return -EINVAL; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x02; /* SPECIFIC INQUIRY */ + buf[1] = 0xff; /* UNIT */ + if (dir == AVC_GENERAL_PLUG_DIR_IN) + buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */ + else + buf[2] = 0x18; /* OUTPUT PLUG SIGNAL FORMAT */ + buf[3] = 0xff & pid; /* plug id */ + buf[4] = 0x90; /* EOH_1, Form_1, FMT. AM824 */ + buf[5] = 0x07 & sfc; /* FDF-hi. AM824, frequency */ + buf[6] = 0xff; /* FDF-mid. AM824, SYT hi (not used) */ + buf[7] = 0xff; /* FDF-low. AM824, SYT lo (not used) */ + + /* do transaction and check buf[1-5] are the same against command */ + err = fcp_avc_transaction(unit, buf, 8, buf, 8, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5)); + if (err < 0) + ; + else if (err < 8) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + if (err < 0) + goto end; + + err = 0; +end: + kfree(buf); + return err; +} diff --git a/sound/firewire/oxfw/oxfw-hwdep.c b/sound/firewire/oxfw/oxfw-hwdep.c new file mode 100644 index 000000000..50a1c03b4 --- /dev/null +++ b/sound/firewire/oxfw/oxfw-hwdep.c @@ -0,0 +1,190 @@ +/* + * oxfw_hwdep.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * This codes give three functionality. + * + * 1.get firewire node information + * 2.get notification about starting/stopping stream + * 3.lock/unlock stream + */ + +#include "oxfw.h" + +static long hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_oxfw *oxfw = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&oxfw->lock); + + while (!oxfw->dev_lock_changed) { + prepare_to_wait(&oxfw->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&oxfw->lock); + schedule(); + finish_wait(&oxfw->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&oxfw->lock); + } + + memset(&event, 0, sizeof(event)); + if (oxfw->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (oxfw->dev_lock_count > 0); + oxfw->dev_lock_changed = false; + + count = min_t(long, count, sizeof(event.lock_status)); + } + + spin_unlock_irq(&oxfw->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static __poll_t hwdep_poll(struct snd_hwdep *hwdep, struct file *file, + poll_table *wait) +{ + struct snd_oxfw *oxfw = hwdep->private_data; + __poll_t events; + + poll_wait(file, &oxfw->hwdep_wait, wait); + + spin_lock_irq(&oxfw->lock); + if (oxfw->dev_lock_changed) + events = EPOLLIN | EPOLLRDNORM; + else + events = 0; + spin_unlock_irq(&oxfw->lock); + + return events; +} + +static int hwdep_get_info(struct snd_oxfw *oxfw, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(oxfw->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_OXFW; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int hwdep_lock(struct snd_oxfw *oxfw) +{ + int err; + + spin_lock_irq(&oxfw->lock); + + if (oxfw->dev_lock_count == 0) { + oxfw->dev_lock_count = -1; + err = 0; + } else { + err = -EBUSY; + } + + spin_unlock_irq(&oxfw->lock); + + return err; +} + +static int hwdep_unlock(struct snd_oxfw *oxfw) +{ + int err; + + spin_lock_irq(&oxfw->lock); + + if (oxfw->dev_lock_count == -1) { + oxfw->dev_lock_count = 0; + err = 0; + } else { + err = -EBADFD; + } + + spin_unlock_irq(&oxfw->lock); + + return err; +} + +static int hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_oxfw *oxfw = hwdep->private_data; + + spin_lock_irq(&oxfw->lock); + if (oxfw->dev_lock_count == -1) + oxfw->dev_lock_count = 0; + spin_unlock_irq(&oxfw->lock); + + return 0; +} + +static int hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_oxfw *oxfw = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(oxfw, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(oxfw); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(oxfw); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +int snd_oxfw_create_hwdep(struct snd_oxfw *oxfw) +{ + static const struct snd_hwdep_ops hwdep_ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, + }; + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(oxfw->card, oxfw->card->driver, 0, &hwdep); + if (err < 0) + goto end; + strcpy(hwdep->name, oxfw->card->driver); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_OXFW; + hwdep->ops = hwdep_ops; + hwdep->private_data = oxfw; + hwdep->exclusive = true; +end: + return err; +} diff --git a/sound/firewire/oxfw/oxfw-midi.c b/sound/firewire/oxfw/oxfw-midi.c new file mode 100644 index 000000000..b7bbd77df --- /dev/null +++ b/sound/firewire/oxfw/oxfw-midi.c @@ -0,0 +1,187 @@ +/* + * oxfw_midi.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "oxfw.h" + +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + struct snd_oxfw *oxfw = substream->rmidi->private_data; + int err; + + err = snd_oxfw_stream_lock_try(oxfw); + if (err < 0) + return err; + + mutex_lock(&oxfw->mutex); + + oxfw->capture_substreams++; + err = snd_oxfw_stream_start_simplex(oxfw, &oxfw->tx_stream, 0, 0); + + mutex_unlock(&oxfw->mutex); + + if (err < 0) + snd_oxfw_stream_lock_release(oxfw); + + return err; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_oxfw *oxfw = substream->rmidi->private_data; + int err; + + err = snd_oxfw_stream_lock_try(oxfw); + if (err < 0) + return err; + + mutex_lock(&oxfw->mutex); + + oxfw->playback_substreams++; + err = snd_oxfw_stream_start_simplex(oxfw, &oxfw->rx_stream, 0, 0); + + mutex_unlock(&oxfw->mutex); + + if (err < 0) + snd_oxfw_stream_lock_release(oxfw); + + return err; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + struct snd_oxfw *oxfw = substream->rmidi->private_data; + + mutex_lock(&oxfw->mutex); + + oxfw->capture_substreams--; + snd_oxfw_stream_stop_simplex(oxfw, &oxfw->tx_stream); + + mutex_unlock(&oxfw->mutex); + + snd_oxfw_stream_lock_release(oxfw); + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_oxfw *oxfw = substream->rmidi->private_data; + + mutex_lock(&oxfw->mutex); + + oxfw->playback_substreams--; + snd_oxfw_stream_stop_simplex(oxfw, &oxfw->rx_stream); + + mutex_unlock(&oxfw->mutex); + + snd_oxfw_stream_lock_release(oxfw); + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_oxfw *oxfw = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&oxfw->lock, flags); + + if (up) + amdtp_am824_midi_trigger(&oxfw->tx_stream, + substrm->number, substrm); + else + amdtp_am824_midi_trigger(&oxfw->tx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&oxfw->lock, flags); +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_oxfw *oxfw = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&oxfw->lock, flags); + + if (up) + amdtp_am824_midi_trigger(&oxfw->rx_stream, + substrm->number, substrm); + else + amdtp_am824_midi_trigger(&oxfw->rx_stream, + substrm->number, NULL); + + spin_unlock_irqrestore(&oxfw->lock, flags); +} + +static void set_midi_substream_names(struct snd_oxfw *oxfw, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", + oxfw->card->shortname, subs->number + 1); + } +} + +int snd_oxfw_create_midi(struct snd_oxfw *oxfw) +{ + static const struct snd_rawmidi_ops capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, + }; + static const struct snd_rawmidi_ops playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, + }; + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + if (oxfw->midi_input_ports == 0 && oxfw->midi_output_ports == 0) + return 0; + + /* create midi ports */ + err = snd_rawmidi_new(oxfw->card, oxfw->card->driver, 0, + oxfw->midi_output_ports, oxfw->midi_input_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", oxfw->card->shortname); + rmidi->private_data = oxfw; + + if (oxfw->midi_input_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &capture_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + + set_midi_substream_names(oxfw, str); + } + + if (oxfw->midi_output_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &playback_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + set_midi_substream_names(oxfw, str); + } + + if ((oxfw->midi_output_ports > 0) && (oxfw->midi_input_ports > 0)) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +} diff --git a/sound/firewire/oxfw/oxfw-pcm.c b/sound/firewire/oxfw/oxfw-pcm.c new file mode 100644 index 000000000..b3f6503dd --- /dev/null +++ b/sound/firewire/oxfw/oxfw-pcm.c @@ -0,0 +1,423 @@ +/* + * oxfw_pcm.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "oxfw.h" + +static int hw_rule_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + u8 **formats = rule->private; + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + struct snd_oxfw_stream_formation formation; + int i, err; + + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + if (formats[i] == NULL) + continue; + + err = snd_oxfw_stream_parse_format(formats[i], &formation); + if (err < 0) + continue; + if (!snd_interval_test(c, formation.pcm)) + continue; + + t.min = min(t.min, formation.rate); + t.max = max(t.max, formation.rate); + + } + return snd_interval_refine(r, &t); +} + +static int hw_rule_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + u8 **formats = rule->private; + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_oxfw_stream_formation formation; + int i, j, err; + unsigned int count, list[SND_OXFW_STREAM_FORMAT_ENTRIES] = {0}; + + count = 0; + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + if (formats[i] == NULL) + break; + + err = snd_oxfw_stream_parse_format(formats[i], &formation); + if (err < 0) + continue; + if (!snd_interval_test(r, formation.rate)) + continue; + if (list[count] == formation.pcm) + continue; + + for (j = 0; j < ARRAY_SIZE(list); j++) { + if (list[j] == formation.pcm) + break; + } + if (j == ARRAY_SIZE(list)) { + list[count] = formation.pcm; + if (++count == ARRAY_SIZE(list)) + break; + } + } + + return snd_interval_list(c, count, list, 0); +} + +static void limit_channels_and_rates(struct snd_pcm_hardware *hw, u8 **formats) +{ + struct snd_oxfw_stream_formation formation; + int i, err; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + hw->rate_min = UINT_MAX; + hw->rate_max = 0; + hw->rates = 0; + + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + if (formats[i] == NULL) + break; + + err = snd_oxfw_stream_parse_format(formats[i], &formation); + if (err < 0) + continue; + + hw->channels_min = min(hw->channels_min, formation.pcm); + hw->channels_max = max(hw->channels_max, formation.pcm); + + hw->rate_min = min(hw->rate_min, formation.rate); + hw->rate_max = max(hw->rate_max, formation.rate); + hw->rates |= snd_pcm_rate_to_rate_bit(formation.rate); + } +} + +static int init_hw_params(struct snd_oxfw *oxfw, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + u8 **formats; + struct amdtp_stream *stream; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = AM824_IN_PCM_FORMAT_BITS; + stream = &oxfw->tx_stream; + formats = oxfw->tx_stream_formats; + } else { + runtime->hw.formats = AM824_OUT_PCM_FORMAT_BITS; + stream = &oxfw->rx_stream; + formats = oxfw->rx_stream_formats; + } + + limit_channels_and_rates(&runtime->hw, formats); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, formats, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + goto end; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, formats, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + goto end; + + err = amdtp_am824_add_pcm_hw_constraints(stream, runtime); +end: + return err; +} + +static int limit_to_current_params(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + struct snd_oxfw_stream_formation formation; + enum avc_general_plug_dir dir; + int err; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) + dir = AVC_GENERAL_PLUG_DIR_OUT; + else + dir = AVC_GENERAL_PLUG_DIR_IN; + + err = snd_oxfw_stream_get_current_formation(oxfw, dir, &formation); + if (err < 0) + goto end; + + substream->runtime->hw.channels_min = formation.pcm; + substream->runtime->hw.channels_max = formation.pcm; + substream->runtime->hw.rate_min = formation.rate; + substream->runtime->hw.rate_max = formation.rate; +end: + return err; +} + +static int pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + int err; + + err = snd_oxfw_stream_lock_try(oxfw); + if (err < 0) + goto end; + + err = init_hw_params(oxfw, substream); + if (err < 0) + goto err_locked; + + /* + * When any PCM streams are already running, the available sampling + * rate is limited at current value. + */ + if (amdtp_stream_pcm_running(&oxfw->tx_stream) || + amdtp_stream_pcm_running(&oxfw->rx_stream)) { + err = limit_to_current_params(substream); + if (err < 0) + goto end; + } + + snd_pcm_set_sync(substream); +end: + return err; +err_locked: + snd_oxfw_stream_lock_release(oxfw); + return err; +} + +static int pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + + snd_oxfw_stream_lock_release(oxfw); + return 0; +} + +static int pcm_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_oxfw *oxfw = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&oxfw->mutex); + oxfw->capture_substreams++; + mutex_unlock(&oxfw->mutex); + } + + return 0; +} +static int pcm_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_oxfw *oxfw = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { + mutex_lock(&oxfw->mutex); + oxfw->playback_substreams++; + mutex_unlock(&oxfw->mutex); + } + + return 0; +} + +static int pcm_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + + mutex_lock(&oxfw->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + oxfw->capture_substreams--; + + snd_oxfw_stream_stop_simplex(oxfw, &oxfw->tx_stream); + + mutex_unlock(&oxfw->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} +static int pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + + mutex_lock(&oxfw->mutex); + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + oxfw->playback_substreams--; + + snd_oxfw_stream_stop_simplex(oxfw, &oxfw->rx_stream); + + mutex_unlock(&oxfw->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&oxfw->mutex); + err = snd_oxfw_stream_start_simplex(oxfw, &oxfw->tx_stream, + runtime->rate, runtime->channels); + mutex_unlock(&oxfw->mutex); + if (err < 0) + goto end; + + amdtp_stream_pcm_prepare(&oxfw->tx_stream); +end: + return err; +} +static int pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + mutex_lock(&oxfw->mutex); + err = snd_oxfw_stream_start_simplex(oxfw, &oxfw->rx_stream, + runtime->rate, runtime->channels); + mutex_unlock(&oxfw->mutex); + if (err < 0) + goto end; + + amdtp_stream_pcm_prepare(&oxfw->rx_stream); +end: + return err; +} + +static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_oxfw *oxfw = substream->private_data; + struct snd_pcm_substream *pcm; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + pcm = substream; + break; + case SNDRV_PCM_TRIGGER_STOP: + pcm = NULL; + break; + default: + return -EINVAL; + } + amdtp_stream_pcm_trigger(&oxfw->tx_stream, pcm); + return 0; +} +static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_oxfw *oxfw = substream->private_data; + struct snd_pcm_substream *pcm; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + pcm = substream; + break; + case SNDRV_PCM_TRIGGER_STOP: + pcm = NULL; + break; + default: + return -EINVAL; + } + amdtp_stream_pcm_trigger(&oxfw->rx_stream, pcm); + return 0; +} + +static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstm) +{ + struct snd_oxfw *oxfw = sbstm->private_data; + + return amdtp_stream_pcm_pointer(&oxfw->tx_stream); +} +static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstm) +{ + struct snd_oxfw *oxfw = sbstm->private_data; + + return amdtp_stream_pcm_pointer(&oxfw->rx_stream); +} + +static int pcm_capture_ack(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + + return amdtp_stream_pcm_ack(&oxfw->tx_stream); +} + +static int pcm_playback_ack(struct snd_pcm_substream *substream) +{ + struct snd_oxfw *oxfw = substream->private_data; + + return amdtp_stream_pcm_ack(&oxfw->rx_stream); +} + +int snd_oxfw_create_pcm(struct snd_oxfw *oxfw) +{ + static const struct snd_pcm_ops capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_capture_hw_params, + .hw_free = pcm_capture_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .ack = pcm_capture_ack, + .page = snd_pcm_lib_get_vmalloc_page, + }; + static const struct snd_pcm_ops playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_playback_hw_params, + .hw_free = pcm_playback_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .ack = pcm_playback_ack, + .page = snd_pcm_lib_get_vmalloc_page, + }; + struct snd_pcm *pcm; + unsigned int cap = 0; + int err; + + if (oxfw->has_output) + cap = 1; + + err = snd_pcm_new(oxfw->card, oxfw->card->driver, 0, 1, cap, &pcm); + if (err < 0) + return err; + + pcm->private_data = oxfw; + strcpy(pcm->name, oxfw->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); + if (cap > 0) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); + + return 0; +} diff --git a/sound/firewire/oxfw/oxfw-proc.c b/sound/firewire/oxfw/oxfw-proc.c new file mode 100644 index 000000000..27dac071b --- /dev/null +++ b/sound/firewire/oxfw/oxfw-proc.c @@ -0,0 +1,113 @@ +/* + * oxfw_proc.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./oxfw.h" + +static void proc_read_formation(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_oxfw *oxfw = entry->private_data; + struct snd_oxfw_stream_formation formation, curr; + u8 *format; + char flag; + int i, err; + + /* Show input. */ + err = snd_oxfw_stream_get_current_formation(oxfw, + AVC_GENERAL_PLUG_DIR_IN, + &curr); + if (err < 0) + return; + + snd_iprintf(buffer, "Input Stream to device:\n"); + snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + format = oxfw->rx_stream_formats[i]; + if (format == NULL) + continue; + + err = snd_oxfw_stream_parse_format(format, &formation); + if (err < 0) + continue; + + if (memcmp(&formation, &curr, sizeof(curr)) == 0) + flag = '*'; + else + flag = ' '; + + snd_iprintf(buffer, "%c\t%d\t%d\t%d\n", flag, + formation.rate, formation.pcm, formation.midi); + } + + if (!oxfw->has_output) + return; + + /* Show output. */ + err = snd_oxfw_stream_get_current_formation(oxfw, + AVC_GENERAL_PLUG_DIR_OUT, + &curr); + if (err < 0) + return; + + snd_iprintf(buffer, "Output Stream from device:\n"); + snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + format = oxfw->tx_stream_formats[i]; + if (format == NULL) + continue; + + err = snd_oxfw_stream_parse_format(format, &formation); + if (err < 0) + continue; + + if (memcmp(&formation, &curr, sizeof(curr)) == 0) + flag = '*'; + else + flag = ' '; + + snd_iprintf(buffer, "%c\t%d\t%d\t%d\n", flag, + formation.rate, formation.pcm, formation.midi); + } +} + +static void add_node(struct snd_oxfw *oxfw, struct snd_info_entry *root, + const char *name, + void (*op)(struct snd_info_entry *e, + struct snd_info_buffer *b)) +{ + struct snd_info_entry *entry; + + entry = snd_info_create_card_entry(oxfw->card, name, root); + if (entry == NULL) + return; + + snd_info_set_text_ops(entry, oxfw, op); + if (snd_info_register(entry) < 0) + snd_info_free_entry(entry); +} + +void snd_oxfw_proc_init(struct snd_oxfw *oxfw) +{ + struct snd_info_entry *root; + + /* + * All nodes are automatically removed at snd_card_disconnect(), + * by following to link list. + */ + root = snd_info_create_card_entry(oxfw->card, "firewire", + oxfw->card->proc_root); + if (root == NULL) + return; + root->mode = S_IFDIR | 0555; + if (snd_info_register(root) < 0) { + snd_info_free_entry(root); + return; + } + + add_node(oxfw, root, "formation", proc_read_formation); +} diff --git a/sound/firewire/oxfw/oxfw-scs1x.c b/sound/firewire/oxfw/oxfw-scs1x.c new file mode 100644 index 000000000..f33497cdc --- /dev/null +++ b/sound/firewire/oxfw/oxfw-scs1x.c @@ -0,0 +1,420 @@ +/* + * oxfw-scs1x.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * Copyright (c) 2015 Takashi Sakamoto <o-takashi@sakamocchi.jp> + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "oxfw.h" + +#define HSS1394_ADDRESS 0xc007dedadadaULL +#define HSS1394_MAX_PACKET_SIZE 64 +#define HSS1394_TAG_USER_DATA 0x00 +#define HSS1394_TAG_CHANGE_ADDRESS 0xf1 + +struct fw_scs1x { + struct fw_address_handler hss_handler; + u8 input_escape_count; + struct snd_rawmidi_substream *input; + + /* For MIDI playback. */ + struct snd_rawmidi_substream *output; + bool output_idle; + u8 output_status; + u8 output_bytes; + bool output_escaped; + bool output_escape_high_nibble; + struct work_struct work; + wait_queue_head_t idle_wait; + u8 buffer[HSS1394_MAX_PACKET_SIZE]; + bool transaction_running; + struct fw_transaction transaction; + unsigned int transaction_bytes; + bool error; + struct fw_device *fw_dev; +}; + +static const u8 sysex_escape_prefix[] = { + 0xf0, /* SysEx begin */ + 0x00, 0x01, 0x60, /* Stanton DJ */ + 0x48, 0x53, 0x53, /* "HSS" */ +}; + +static void midi_input_escaped_byte(struct snd_rawmidi_substream *stream, + u8 byte) +{ + u8 nibbles[2]; + + nibbles[0] = byte >> 4; + nibbles[1] = byte & 0x0f; + snd_rawmidi_receive(stream, nibbles, 2); +} + +static void midi_input_byte(struct fw_scs1x *scs, + struct snd_rawmidi_substream *stream, u8 byte) +{ + const u8 eox = 0xf7; + + if (scs->input_escape_count > 0) { + midi_input_escaped_byte(stream, byte); + scs->input_escape_count--; + if (scs->input_escape_count == 0) + snd_rawmidi_receive(stream, &eox, sizeof(eox)); + } else if (byte == 0xf9) { + snd_rawmidi_receive(stream, sysex_escape_prefix, + ARRAY_SIZE(sysex_escape_prefix)); + midi_input_escaped_byte(stream, 0x00); + midi_input_escaped_byte(stream, 0xf9); + scs->input_escape_count = 3; + } else { + snd_rawmidi_receive(stream, &byte, 1); + } +} + +static void midi_input_packet(struct fw_scs1x *scs, + struct snd_rawmidi_substream *stream, + const u8 *data, unsigned int bytes) +{ + unsigned int i; + const u8 eox = 0xf7; + + if (data[0] == HSS1394_TAG_USER_DATA) { + for (i = 1; i < bytes; ++i) + midi_input_byte(scs, stream, data[i]); + } else { + snd_rawmidi_receive(stream, sysex_escape_prefix, + ARRAY_SIZE(sysex_escape_prefix)); + for (i = 0; i < bytes; ++i) + midi_input_escaped_byte(stream, data[i]); + snd_rawmidi_receive(stream, &eox, sizeof(eox)); + } +} + +static void handle_hss(struct fw_card *card, struct fw_request *request, + int tcode, int destination, int source, int generation, + unsigned long long offset, void *data, size_t length, + void *callback_data) +{ + struct fw_scs1x *scs = callback_data; + struct snd_rawmidi_substream *stream; + int rcode; + + if (offset != scs->hss_handler.offset) { + rcode = RCODE_ADDRESS_ERROR; + goto end; + } + if (tcode != TCODE_WRITE_QUADLET_REQUEST && + tcode != TCODE_WRITE_BLOCK_REQUEST) { + rcode = RCODE_TYPE_ERROR; + goto end; + } + + if (length >= 1) { + stream = READ_ONCE(scs->input); + if (stream) + midi_input_packet(scs, stream, data, length); + } + + rcode = RCODE_COMPLETE; +end: + fw_send_response(card, request, rcode); +} + +static void scs_write_callback(struct fw_card *card, int rcode, + void *data, size_t length, void *callback_data) +{ + struct fw_scs1x *scs = callback_data; + + if (!rcode_is_permanent_error(rcode)) { + /* Don't retry for this data. */ + if (rcode == RCODE_COMPLETE) + scs->transaction_bytes = 0; + } else { + scs->error = true; + } + + scs->transaction_running = false; + schedule_work(&scs->work); +} + +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_work(struct work_struct *work) +{ + struct fw_scs1x *scs = container_of(work, struct fw_scs1x, work); + struct snd_rawmidi_substream *stream; + unsigned int i; + u8 byte; + int generation; + + if (scs->transaction_running) + return; + + stream = READ_ONCE(scs->output); + if (!stream || scs->error) { + scs->output_idle = true; + wake_up(&scs->idle_wait); + return; + } + + if (scs->transaction_bytes > 0) + goto retry; + + 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_bytes = i; +retry: + scs->transaction_running = true; + generation = scs->fw_dev->generation; + smp_rmb(); /* node_id vs. generation */ + fw_send_request(scs->fw_dev->card, &scs->transaction, + TCODE_WRITE_BLOCK_REQUEST, scs->fw_dev->node_id, + generation, scs->fw_dev->max_speed, HSS1394_ADDRESS, + scs->buffer, scs->transaction_bytes, + scs_write_callback, scs); +} + +static int midi_capture_open(struct snd_rawmidi_substream *stream) +{ + return 0; +} + +static int midi_capture_close(struct snd_rawmidi_substream *stream) +{ + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *stream, int up) +{ + struct fw_scs1x *scs = stream->rmidi->private_data; + + if (up) { + scs->input_escape_count = 0; + WRITE_ONCE(scs->input, stream); + } else { + WRITE_ONCE(scs->input, NULL); + } +} + +static int midi_playback_open(struct snd_rawmidi_substream *stream) +{ + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *stream) +{ + return 0; +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *stream, int up) +{ + struct fw_scs1x *scs = stream->rmidi->private_data; + + if (up) { + scs->output_status = 0; + scs->output_bytes = 1; + scs->output_escaped = false; + scs->output_idle = false; + scs->transaction_bytes = 0; + scs->error = false; + + WRITE_ONCE(scs->output, stream); + schedule_work(&scs->work); + } else { + WRITE_ONCE(scs->output, NULL); + } +} +static void midi_playback_drain(struct snd_rawmidi_substream *stream) +{ + struct fw_scs1x *scs = stream->rmidi->private_data; + + wait_event(scs->idle_wait, scs->output_idle); +} + +static int register_address(struct snd_oxfw *oxfw) +{ + struct fw_scs1x *scs = oxfw->spec; + __be64 data; + + data = cpu_to_be64(((u64)HSS1394_TAG_CHANGE_ADDRESS << 56) | + scs->hss_handler.offset); + return snd_fw_transaction(oxfw->unit, TCODE_WRITE_BLOCK_REQUEST, + HSS1394_ADDRESS, &data, sizeof(data), 0); +} + +static void remove_scs1x(struct snd_rawmidi *rmidi) +{ + struct fw_scs1x *scs = rmidi->private_data; + + fw_core_remove_address_handler(&scs->hss_handler); +} + +void snd_oxfw_scs1x_update(struct snd_oxfw *oxfw) +{ + register_address(oxfw); +} + +int snd_oxfw_scs1x_add(struct snd_oxfw *oxfw) +{ + static const struct snd_rawmidi_ops midi_capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, + }; + static const struct snd_rawmidi_ops midi_playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, + .drain = midi_playback_drain, + }; + struct snd_rawmidi *rmidi; + struct fw_scs1x *scs; + int err; + + scs = kzalloc(sizeof(struct fw_scs1x), GFP_KERNEL); + if (scs == NULL) + return -ENOMEM; + scs->fw_dev = fw_parent_device(oxfw->unit); + oxfw->spec = scs; + + /* Allocate own handler for imcoming asynchronous transaction. */ + scs->hss_handler.length = HSS1394_MAX_PACKET_SIZE; + scs->hss_handler.address_callback = handle_hss; + scs->hss_handler.callback_data = scs; + err = fw_core_add_address_handler(&scs->hss_handler, + &fw_high_memory_region); + if (err < 0) + return err; + + err = register_address(oxfw); + if (err < 0) + goto err_allocated; + + /* Use unique name for backward compatibility to scs1x module. */ + err = snd_rawmidi_new(oxfw->card, "SCS.1x", 0, 1, 1, &rmidi); + if (err < 0) + goto err_allocated; + rmidi->private_data = scs; + rmidi->private_free = remove_scs1x; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", oxfw->card->shortname); + + rmidi->info_flags = SNDRV_RAWMIDI_INFO_INPUT | + SNDRV_RAWMIDI_INFO_OUTPUT | + SNDRV_RAWMIDI_INFO_DUPLEX; + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &midi_capture_ops); + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &midi_playback_ops); + + INIT_WORK(&scs->work, scs_output_work); + init_waitqueue_head(&scs->idle_wait); + scs->output_idle = true; + + return 0; +err_allocated: + fw_core_remove_address_handler(&scs->hss_handler); + return err; +} diff --git a/sound/firewire/oxfw/oxfw-spkr.c b/sound/firewire/oxfw/oxfw-spkr.c new file mode 100644 index 000000000..cb905af06 --- /dev/null +++ b/sound/firewire/oxfw/oxfw-spkr.c @@ -0,0 +1,319 @@ +/* + * oxfw-spkr.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "oxfw.h" + +struct fw_spkr { + bool mute; + s16 volume[6]; + s16 volume_min; + s16 volume_max; + + unsigned int mixer_channels; + u8 mute_fb_id; + u8 volume_fb_id; +}; + +enum control_action { CTL_READ, CTL_WRITE }; +enum control_attribute { + CTL_MIN = 0x02, + CTL_MAX = 0x03, + CTL_CURRENT = 0x10, +}; + +static int avc_audio_feature_mute(struct fw_unit *unit, u8 fb_id, bool *value, + enum control_action action) +{ + u8 *buf; + u8 response_ok; + int err; + + buf = kmalloc(11, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (action == CTL_READ) { + buf[0] = 0x01; /* AV/C, STATUS */ + response_ok = 0x0c; /* STABLE */ + } else { + buf[0] = 0x00; /* AV/C, CONTROL */ + response_ok = 0x09; /* ACCEPTED */ + } + buf[1] = 0x08; /* audio unit 0 */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x81; /* function block type: feature */ + buf[4] = fb_id; /* function block ID */ + buf[5] = 0x10; /* control attribute: current */ + buf[6] = 0x02; /* selector length */ + buf[7] = 0x00; /* audio channel number */ + buf[8] = 0x01; /* control selector: mute */ + buf[9] = 0x01; /* control data length */ + if (action == CTL_READ) + buf[10] = 0xff; + else + buf[10] = *value ? 0x70 : 0x60; + + err = fcp_avc_transaction(unit, buf, 11, buf, 11, 0x3fe); + if (err < 0) + goto error; + if (err < 11) { + dev_err(&unit->device, "short FCP response\n"); + err = -EIO; + goto error; + } + if (buf[0] != response_ok) { + dev_err(&unit->device, "mute command failed\n"); + err = -EIO; + goto error; + } + if (action == CTL_READ) + *value = buf[10] == 0x70; + + err = 0; + +error: + kfree(buf); + + return err; +} + +static int avc_audio_feature_volume(struct fw_unit *unit, u8 fb_id, s16 *value, + unsigned int channel, + enum control_attribute attribute, + enum control_action action) +{ + u8 *buf; + u8 response_ok; + int err; + + buf = kmalloc(12, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + if (action == CTL_READ) { + buf[0] = 0x01; /* AV/C, STATUS */ + response_ok = 0x0c; /* STABLE */ + } else { + buf[0] = 0x00; /* AV/C, CONTROL */ + response_ok = 0x09; /* ACCEPTED */ + } + buf[1] = 0x08; /* audio unit 0 */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x81; /* function block type: feature */ + buf[4] = fb_id; /* function block ID */ + buf[5] = attribute; /* control attribute */ + buf[6] = 0x02; /* selector length */ + buf[7] = channel; /* audio channel number */ + buf[8] = 0x02; /* control selector: volume */ + buf[9] = 0x02; /* control data length */ + if (action == CTL_READ) { + buf[10] = 0xff; + buf[11] = 0xff; + } else { + buf[10] = *value >> 8; + buf[11] = *value; + } + + err = fcp_avc_transaction(unit, buf, 12, buf, 12, 0x3fe); + if (err < 0) + goto error; + if (err < 12) { + dev_err(&unit->device, "short FCP response\n"); + err = -EIO; + goto error; + } + if (buf[0] != response_ok) { + dev_err(&unit->device, "volume command failed\n"); + err = -EIO; + goto error; + } + if (action == CTL_READ) + *value = (buf[10] << 8) | buf[11]; + + err = 0; + +error: + kfree(buf); + + return err; +} + +static int spkr_mute_get(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct snd_oxfw *oxfw = control->private_data; + struct fw_spkr *spkr = oxfw->spec; + + value->value.integer.value[0] = !spkr->mute; + + return 0; +} + +static int spkr_mute_put(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct snd_oxfw *oxfw = control->private_data; + struct fw_spkr *spkr = oxfw->spec; + bool mute; + int err; + + mute = !value->value.integer.value[0]; + + if (mute == spkr->mute) + return 0; + + err = avc_audio_feature_mute(oxfw->unit, spkr->mute_fb_id, &mute, + CTL_WRITE); + if (err < 0) + return err; + spkr->mute = mute; + + return 1; +} + +static int spkr_volume_info(struct snd_kcontrol *control, + struct snd_ctl_elem_info *info) +{ + struct snd_oxfw *oxfw = control->private_data; + struct fw_spkr *spkr = oxfw->spec; + + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = spkr->mixer_channels; + info->value.integer.min = spkr->volume_min; + info->value.integer.max = spkr->volume_max; + + return 0; +} + +static const u8 channel_map[6] = { 0, 1, 4, 5, 2, 3 }; + +static int spkr_volume_get(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct snd_oxfw *oxfw = control->private_data; + struct fw_spkr *spkr = oxfw->spec; + unsigned int i; + + for (i = 0; i < spkr->mixer_channels; ++i) + value->value.integer.value[channel_map[i]] = spkr->volume[i]; + + return 0; +} + +static int spkr_volume_put(struct snd_kcontrol *control, + struct snd_ctl_elem_value *value) +{ + struct snd_oxfw *oxfw = control->private_data; + struct fw_spkr *spkr = oxfw->spec; + unsigned int i, changed_channels; + bool equal_values = true; + s16 volume; + int err; + + for (i = 0; i < 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]) + equal_values = false; + } + + changed_channels = 0; + for (i = 0; i < spkr->mixer_channels; ++i) + if (value->value.integer.value[channel_map[i]] != + spkr->volume[i]) + changed_channels |= 1 << (i + 1); + + if (equal_values && changed_channels != 0) + changed_channels = 1 << 0; + + 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 = avc_audio_feature_volume(oxfw->unit, + spkr->volume_fb_id, &volume, + i, CTL_CURRENT, CTL_WRITE); + if (err < 0) + return err; + } + if (i > 0) + spkr->volume[i - 1] = volume; + } + + return changed_channels != 0; +} + +int snd_oxfw_add_spkr(struct snd_oxfw *oxfw, bool is_lacie) +{ + static const struct snd_kcontrol_new controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Switch", + .info = snd_ctl_boolean_mono_info, + .get = spkr_mute_get, + .put = spkr_mute_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "PCM Playback Volume", + .info = spkr_volume_info, + .get = spkr_volume_get, + .put = spkr_volume_put, + }, + }; + struct fw_spkr *spkr; + unsigned int i, first_ch; + int err; + + spkr = kzalloc(sizeof(struct fw_spkr), GFP_KERNEL); + if (spkr == NULL) + return -ENOMEM; + oxfw->spec = spkr; + + if (is_lacie) { + spkr->mixer_channels = 1; + spkr->mute_fb_id = 0x01; + spkr->volume_fb_id = 0x01; + } else { + spkr->mixer_channels = 6; + spkr->mute_fb_id = 0x01; + spkr->volume_fb_id = 0x02; + } + + err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id, + &spkr->volume_min, 0, CTL_MIN, CTL_READ); + if (err < 0) + return err; + err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id, + &spkr->volume_max, 0, CTL_MAX, CTL_READ); + if (err < 0) + return err; + + err = avc_audio_feature_mute(oxfw->unit, spkr->mute_fb_id, &spkr->mute, + CTL_READ); + if (err < 0) + return err; + + first_ch = spkr->mixer_channels == 1 ? 0 : 1; + for (i = 0; i < spkr->mixer_channels; ++i) { + err = avc_audio_feature_volume(oxfw->unit, spkr->volume_fb_id, + &spkr->volume[i], first_ch + i, + CTL_CURRENT, CTL_READ); + if (err < 0) + return err; + } + + for (i = 0; i < ARRAY_SIZE(controls); ++i) { + err = snd_ctl_add(oxfw->card, + snd_ctl_new1(&controls[i], oxfw)); + if (err < 0) + return err; + } + + return 0; +} diff --git a/sound/firewire/oxfw/oxfw-stream.c b/sound/firewire/oxfw/oxfw-stream.c new file mode 100644 index 000000000..d9361f352 --- /dev/null +++ b/sound/firewire/oxfw/oxfw-stream.c @@ -0,0 +1,730 @@ +/* + * oxfw_stream.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "oxfw.h" +#include <linux/delay.h> + +#define AVC_GENERIC_FRAME_MAXIMUM_BYTES 512 +#define CALLBACK_TIMEOUT 200 + +/* + * According to datasheet of Oxford Semiconductor: + * OXFW970: 32.0/44.1/48.0/96.0 Khz, 8 audio channels I/O + * OXFW971: 32.0/44.1/48.0/88.2/96.0/192.0 kHz, 16 audio channels I/O, MIDI I/O + */ +static const unsigned int oxfw_rate_table[] = { + [0] = 32000, + [1] = 44100, + [2] = 48000, + [3] = 88200, + [4] = 96000, + [5] = 192000, +}; + +/* + * See Table 5.7 – Sampling frequency for Multi-bit Audio + * in AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) + */ +static const unsigned int avc_stream_rate_table[] = { + [0] = 0x02, + [1] = 0x03, + [2] = 0x04, + [3] = 0x0a, + [4] = 0x05, + [5] = 0x07, +}; + +static int set_rate(struct snd_oxfw *oxfw, unsigned int rate) +{ + int err; + + err = avc_general_set_sig_fmt(oxfw->unit, rate, + AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) + goto end; + + if (oxfw->has_output) + err = avc_general_set_sig_fmt(oxfw->unit, rate, + AVC_GENERAL_PLUG_DIR_OUT, 0); +end: + return err; +} + +static int set_stream_format(struct snd_oxfw *oxfw, struct amdtp_stream *s, + unsigned int rate, unsigned int pcm_channels) +{ + u8 **formats; + struct snd_oxfw_stream_formation formation; + enum avc_general_plug_dir dir; + unsigned int len; + int i, err; + + if (s == &oxfw->tx_stream) { + formats = oxfw->tx_stream_formats; + dir = AVC_GENERAL_PLUG_DIR_OUT; + } else { + formats = oxfw->rx_stream_formats; + dir = AVC_GENERAL_PLUG_DIR_IN; + } + + /* Seek stream format for requirements. */ + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + err = snd_oxfw_stream_parse_format(formats[i], &formation); + if (err < 0) + return err; + + if ((formation.rate == rate) && (formation.pcm == pcm_channels)) + break; + } + if (i == SND_OXFW_STREAM_FORMAT_ENTRIES) + return -EINVAL; + + /* If assumed, just change rate. */ + if (oxfw->assumed) + return set_rate(oxfw, rate); + + /* Calculate format length. */ + len = 5 + formats[i][4] * 2; + + err = avc_stream_set_format(oxfw->unit, dir, 0, formats[i], len); + if (err < 0) + return err; + + /* Some requests just after changing format causes freezing. */ + msleep(100); + + return 0; +} + +static void stop_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream) +{ + amdtp_stream_pcm_abort(stream); + amdtp_stream_stop(stream); + + if (stream == &oxfw->tx_stream) + cmp_connection_break(&oxfw->out_conn); + else + cmp_connection_break(&oxfw->in_conn); +} + +static int start_stream(struct snd_oxfw *oxfw, struct amdtp_stream *stream, + unsigned int rate, unsigned int pcm_channels) +{ + u8 **formats; + struct cmp_connection *conn; + struct snd_oxfw_stream_formation formation; + unsigned int i, midi_ports; + int err; + + if (stream == &oxfw->rx_stream) { + formats = oxfw->rx_stream_formats; + conn = &oxfw->in_conn; + } else { + formats = oxfw->tx_stream_formats; + conn = &oxfw->out_conn; + } + + /* Get stream format */ + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + if (formats[i] == NULL) + break; + + err = snd_oxfw_stream_parse_format(formats[i], &formation); + if (err < 0) + goto end; + if (rate != formation.rate) + continue; + if (pcm_channels == 0 || pcm_channels == formation.pcm) + break; + } + if (i == SND_OXFW_STREAM_FORMAT_ENTRIES) { + err = -EINVAL; + goto end; + } + + pcm_channels = formation.pcm; + midi_ports = formation.midi * 8; + + /* The stream should have one pcm channels at least */ + if (pcm_channels == 0) { + err = -EINVAL; + goto end; + } + err = amdtp_am824_set_parameters(stream, rate, pcm_channels, midi_ports, + false); + if (err < 0) + goto end; + + err = cmp_connection_establish(conn, + amdtp_stream_get_max_payload(stream)); + if (err < 0) + goto end; + + err = amdtp_stream_start(stream, + conn->resources.channel, + conn->speed); + if (err < 0) { + cmp_connection_break(conn); + goto end; + } + + /* Wait first packet */ + if (!amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT)) { + stop_stream(oxfw, stream); + err = -ETIMEDOUT; + } +end: + return err; +} + +static int check_connection_used_by_others(struct snd_oxfw *oxfw, + struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + bool used; + int err; + + if (stream == &oxfw->tx_stream) + conn = &oxfw->out_conn; + else + conn = &oxfw->in_conn; + + err = cmp_connection_check_used(conn, &used); + if ((err >= 0) && used && !amdtp_stream_running(stream)) { + dev_err(&oxfw->unit->device, + "Connection established by others: %cPCR[%d]\n", + (conn->direction == CMP_OUTPUT) ? 'o' : 'i', + conn->pcr_index); + err = -EBUSY; + } + + return err; +} + +int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + enum cmp_direction c_dir; + enum amdtp_stream_direction s_dir; + int err; + + if (stream == &oxfw->tx_stream) { + conn = &oxfw->out_conn; + c_dir = CMP_OUTPUT; + s_dir = AMDTP_IN_STREAM; + } else { + conn = &oxfw->in_conn; + c_dir = CMP_INPUT; + s_dir = AMDTP_OUT_STREAM; + } + + err = cmp_connection_init(conn, oxfw->unit, c_dir, 0); + if (err < 0) + goto end; + + err = amdtp_am824_init(stream, oxfw->unit, s_dir, CIP_NONBLOCKING); + if (err < 0) { + amdtp_stream_destroy(stream); + cmp_connection_destroy(conn); + goto end; + } + + /* + * OXFW starts to transmit packets with non-zero dbc. + * OXFW postpone transferring packets till handling any asynchronous + * packets. As a result, next isochronous packet includes more data + * blocks than IEC 61883-6 defines. + */ + if (stream == &oxfw->tx_stream) { + oxfw->tx_stream.flags |= CIP_JUMBO_PAYLOAD; + if (oxfw->wrong_dbs) + oxfw->tx_stream.flags |= CIP_WRONG_DBS; + } +end: + return err; +} + +int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream, + unsigned int rate, unsigned int pcm_channels) +{ + struct amdtp_stream *opposite; + struct snd_oxfw_stream_formation formation; + enum avc_general_plug_dir dir; + unsigned int substreams, opposite_substreams; + int err = 0; + + if (stream == &oxfw->tx_stream) { + substreams = oxfw->capture_substreams; + opposite = &oxfw->rx_stream; + opposite_substreams = oxfw->playback_substreams; + dir = AVC_GENERAL_PLUG_DIR_OUT; + } else { + substreams = oxfw->playback_substreams; + opposite_substreams = oxfw->capture_substreams; + + if (oxfw->has_output) + opposite = &oxfw->rx_stream; + else + opposite = NULL; + + dir = AVC_GENERAL_PLUG_DIR_IN; + } + + if (substreams == 0) + goto end; + + /* + * Considering JACK/FFADO streaming: + * TODO: This can be removed hwdep functionality becomes popular. + */ + err = check_connection_used_by_others(oxfw, stream); + if (err < 0) + goto end; + + /* packet queueing error */ + if (amdtp_streaming_error(stream)) + stop_stream(oxfw, stream); + + err = snd_oxfw_stream_get_current_formation(oxfw, dir, &formation); + if (err < 0) + goto end; + if (rate == 0) + rate = formation.rate; + if (pcm_channels == 0) + pcm_channels = formation.pcm; + + if ((formation.rate != rate) || (formation.pcm != pcm_channels)) { + if (opposite != NULL) { + err = check_connection_used_by_others(oxfw, opposite); + if (err < 0) + goto end; + stop_stream(oxfw, opposite); + } + stop_stream(oxfw, stream); + + err = set_stream_format(oxfw, stream, rate, pcm_channels); + if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to set stream format: %d\n", err); + goto end; + } + + /* Start opposite stream if needed. */ + if (opposite && !amdtp_stream_running(opposite) && + (opposite_substreams > 0)) { + err = start_stream(oxfw, opposite, rate, 0); + if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to restart stream: %d\n", err); + goto end; + } + } + } + + /* Start requested stream. */ + if (!amdtp_stream_running(stream)) { + err = start_stream(oxfw, stream, rate, pcm_channels); + if (err < 0) + dev_err(&oxfw->unit->device, + "fail to start stream: %d\n", err); + } +end: + return err; +} + +void snd_oxfw_stream_stop_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream) +{ + if (((stream == &oxfw->tx_stream) && (oxfw->capture_substreams > 0)) || + ((stream == &oxfw->rx_stream) && (oxfw->playback_substreams > 0))) + return; + + stop_stream(oxfw, stream); +} + +/* + * This function should be called before starting the stream or after stopping + * the streams. + */ +void snd_oxfw_stream_destroy_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + + if (stream == &oxfw->tx_stream) + conn = &oxfw->out_conn; + else + conn = &oxfw->in_conn; + + amdtp_stream_destroy(stream); + cmp_connection_destroy(conn); +} + +void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream) +{ + struct cmp_connection *conn; + + if (stream == &oxfw->tx_stream) + conn = &oxfw->out_conn; + else + conn = &oxfw->in_conn; + + if (cmp_connection_update(conn) < 0) + stop_stream(oxfw, stream); + else + amdtp_stream_update(stream); +} + +int snd_oxfw_stream_get_current_formation(struct snd_oxfw *oxfw, + enum avc_general_plug_dir dir, + struct snd_oxfw_stream_formation *formation) +{ + u8 *format; + unsigned int len; + int err; + + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + format = kmalloc(len, GFP_KERNEL); + if (format == NULL) + return -ENOMEM; + + err = avc_stream_get_format_single(oxfw->unit, dir, 0, format, &len); + if (err < 0) + goto end; + if (len < 3) { + err = -EIO; + goto end; + } + + err = snd_oxfw_stream_parse_format(format, formation); +end: + kfree(format); + return err; +} + +/* + * See Table 6.16 - AM824 Stream Format + * Figure 6.19 - format_information field for AM824 Compound + * in AV/C Stream Format Information Specification 1.1 (Apr 2005, 1394TA) + * Also 'Clause 12 AM824 sequence adaption layers' in IEC 61883-6:2005 + */ +int snd_oxfw_stream_parse_format(u8 *format, + struct snd_oxfw_stream_formation *formation) +{ + unsigned int i, e, channels, type; + + memset(formation, 0, sizeof(struct snd_oxfw_stream_formation)); + + /* + * this module can support a hierarchy combination that: + * Root: Audio and Music (0x90) + * Level 1: AM824 Compound (0x40) + */ + if ((format[0] != 0x90) || (format[1] != 0x40)) + return -ENOSYS; + + /* check the sampling rate */ + for (i = 0; i < ARRAY_SIZE(avc_stream_rate_table); i++) { + if (format[2] == avc_stream_rate_table[i]) + break; + } + if (i == ARRAY_SIZE(avc_stream_rate_table)) + return -ENOSYS; + + formation->rate = oxfw_rate_table[i]; + + for (e = 0; e < format[4]; e++) { + channels = format[5 + e * 2]; + type = format[6 + e * 2]; + + switch (type) { + /* IEC 60958 Conformant, currently handled as MBLA */ + case 0x00: + /* Multi Bit Linear Audio (Raw) */ + case 0x06: + formation->pcm += channels; + break; + /* MIDI Conformant */ + case 0x0d: + formation->midi = channels; + break; + /* IEC 61937-3 to 7 */ + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + /* Multi Bit Linear Audio */ + case 0x07: /* DVD-Audio */ + case 0x0c: /* High Precision */ + /* One Bit Audio */ + case 0x08: /* (Plain) Raw */ + case 0x09: /* (Plain) SACD */ + case 0x0a: /* (Encoded) Raw */ + case 0x0b: /* (Encoded) SACD */ + /* SMPTE Time-Code conformant */ + case 0x0e: + /* Sample Count */ + case 0x0f: + /* Anciliary Data */ + case 0x10: + /* Synchronization Stream (Stereo Raw audio) */ + case 0x40: + /* Don't care */ + case 0xff: + default: + return -ENOSYS; /* not supported */ + } + } + + if (formation->pcm > AM824_MAX_CHANNELS_FOR_PCM || + formation->midi > AM824_MAX_CHANNELS_FOR_MIDI) + return -ENOSYS; + + return 0; +} + +static int +assume_stream_formats(struct snd_oxfw *oxfw, enum avc_general_plug_dir dir, + unsigned int pid, u8 *buf, unsigned int *len, + u8 **formats) +{ + struct snd_oxfw_stream_formation formation; + unsigned int i, eid; + int err; + + /* get format at current sampling rate */ + err = avc_stream_get_format_single(oxfw->unit, dir, pid, buf, len); + if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to get current stream format for isoc %s plug %d:%d\n", + (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" : "out", + pid, err); + goto end; + } + + /* parse and set stream format */ + eid = 0; + err = snd_oxfw_stream_parse_format(buf, &formation); + if (err < 0) + goto end; + + formats[eid] = kmemdup(buf, *len, GFP_KERNEL); + if (formats[eid] == NULL) { + err = -ENOMEM; + goto end; + } + + /* apply the format for each available sampling rate */ + for (i = 0; i < ARRAY_SIZE(oxfw_rate_table); i++) { + if (formation.rate == oxfw_rate_table[i]) + continue; + + err = avc_general_inquiry_sig_fmt(oxfw->unit, + oxfw_rate_table[i], + dir, pid); + if (err < 0) + continue; + + eid++; + formats[eid] = kmemdup(buf, *len, GFP_KERNEL); + if (formats[eid] == NULL) { + err = -ENOMEM; + goto end; + } + formats[eid][2] = avc_stream_rate_table[i]; + } + + err = 0; + oxfw->assumed = true; +end: + return err; +} + +static int fill_stream_formats(struct snd_oxfw *oxfw, + enum avc_general_plug_dir dir, + unsigned short pid) +{ + u8 *buf, **formats; + unsigned int len, eid = 0; + struct snd_oxfw_stream_formation dummy; + int err; + + buf = kmalloc(AVC_GENERIC_FRAME_MAXIMUM_BYTES, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + if (dir == AVC_GENERAL_PLUG_DIR_OUT) + formats = oxfw->tx_stream_formats; + else + formats = oxfw->rx_stream_formats; + + /* get first entry */ + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + err = avc_stream_get_format_list(oxfw->unit, dir, 0, buf, &len, 0); + if (err == -ENOSYS) { + /* LIST subfunction is not implemented */ + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + err = assume_stream_formats(oxfw, dir, pid, buf, &len, + formats); + goto end; + } else if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to get stream format %d for isoc %s plug %d:%d\n", + eid, (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" : "out", + pid, err); + goto end; + } + + /* LIST subfunction is implemented */ + while (eid < SND_OXFW_STREAM_FORMAT_ENTRIES) { + /* The format is too short. */ + if (len < 3) { + err = -EIO; + break; + } + + /* parse and set stream format */ + err = snd_oxfw_stream_parse_format(buf, &dummy); + if (err < 0) + break; + + formats[eid] = kmemdup(buf, len, GFP_KERNEL); + if (formats[eid] == NULL) { + err = -ENOMEM; + break; + } + + /* get next entry */ + len = AVC_GENERIC_FRAME_MAXIMUM_BYTES; + err = avc_stream_get_format_list(oxfw->unit, dir, 0, + buf, &len, ++eid); + /* No entries remained. */ + if (err == -EINVAL) { + err = 0; + break; + } else if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to get stream format %d for isoc %s plug %d:%d\n", + eid, (dir == AVC_GENERAL_PLUG_DIR_IN) ? "in" : + "out", + pid, err); + break; + } + } +end: + kfree(buf); + return err; +} + +int snd_oxfw_stream_discover(struct snd_oxfw *oxfw) +{ + u8 plugs[AVC_PLUG_INFO_BUF_BYTES]; + struct snd_oxfw_stream_formation formation; + u8 *format; + unsigned int i; + int err; + + /* the number of plugs for isoc in/out, ext in/out */ + err = avc_general_get_plug_info(oxfw->unit, 0x1f, 0x07, 0x00, plugs); + if (err < 0) { + dev_err(&oxfw->unit->device, + "fail to get info for isoc/external in/out plugs: %d\n", + err); + goto end; + } else if ((plugs[0] == 0) && (plugs[1] == 0)) { + err = -ENOSYS; + goto end; + } + + /* use oPCR[0] if exists */ + if (plugs[1] > 0) { + err = fill_stream_formats(oxfw, AVC_GENERAL_PLUG_DIR_OUT, 0); + if (err < 0) + goto end; + + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + format = oxfw->tx_stream_formats[i]; + if (format == NULL) + continue; + err = snd_oxfw_stream_parse_format(format, &formation); + if (err < 0) + continue; + + /* Add one MIDI port. */ + if (formation.midi > 0) + oxfw->midi_input_ports = 1; + } + + oxfw->has_output = true; + } + + /* use iPCR[0] if exists */ + if (plugs[0] > 0) { + err = fill_stream_formats(oxfw, AVC_GENERAL_PLUG_DIR_IN, 0); + if (err < 0) + goto end; + + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + format = oxfw->rx_stream_formats[i]; + if (format == NULL) + continue; + err = snd_oxfw_stream_parse_format(format, &formation); + if (err < 0) + continue; + + /* Add one MIDI port. */ + if (formation.midi > 0) + oxfw->midi_output_ports = 1; + } + } +end: + return err; +} + +void snd_oxfw_stream_lock_changed(struct snd_oxfw *oxfw) +{ + oxfw->dev_lock_changed = true; + wake_up(&oxfw->hwdep_wait); +} + +int snd_oxfw_stream_lock_try(struct snd_oxfw *oxfw) +{ + int err; + + spin_lock_irq(&oxfw->lock); + + /* user land lock this */ + if (oxfw->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (oxfw->dev_lock_count++ == 0) + snd_oxfw_stream_lock_changed(oxfw); + err = 0; +end: + spin_unlock_irq(&oxfw->lock); + return err; +} + +void snd_oxfw_stream_lock_release(struct snd_oxfw *oxfw) +{ + spin_lock_irq(&oxfw->lock); + + if (WARN_ON(oxfw->dev_lock_count <= 0)) + goto end; + if (--oxfw->dev_lock_count == 0) + snd_oxfw_stream_lock_changed(oxfw); +end: + spin_unlock_irq(&oxfw->lock); +} diff --git a/sound/firewire/oxfw/oxfw.c b/sound/firewire/oxfw/oxfw.c new file mode 100644 index 000000000..59c05c5dc --- /dev/null +++ b/sound/firewire/oxfw/oxfw.c @@ -0,0 +1,470 @@ +/* + * oxfw.c - a part of driver for OXFW970/971 based devices + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "oxfw.h" + +#define OXFORD_FIRMWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x50000) +/* 0x970?vvvv or 0x971?vvvv, where vvvv = firmware version */ + +#define OXFORD_HARDWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x90020) +#define OXFORD_HARDWARE_ID_OXFW970 0x39443841 +#define OXFORD_HARDWARE_ID_OXFW971 0x39373100 + +#define VENDOR_LOUD 0x000ff2 +#define VENDOR_GRIFFIN 0x001292 +#define VENDOR_BEHRINGER 0x001564 +#define VENDOR_LACIE 0x00d04b +#define VENDOR_TASCAM 0x00022e +#define OUI_STANTON 0x001260 +#define OUI_APOGEE 0x0003db + +#define MODEL_SATELLITE 0x00200f + +#define SPECIFIER_1394TA 0x00a02d +#define VERSION_AVC 0x010001 + +MODULE_DESCRIPTION("Oxford Semiconductor FW970/971 driver"); +MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("snd-firewire-speakers"); +MODULE_ALIAS("snd-scs1x"); + +struct compat_info { + const char *driver_name; + const char *vendor_name; + const char *model_name; +}; + +static bool detect_loud_models(struct fw_unit *unit) +{ + const char *const models[] = { + "Onyxi", + "Onyx-i", + "Onyx 1640i", + "d.Pro", + "Mackie Onyx Satellite", + "Tapco LINK.firewire 4x6", + "U.420"}; + char model[32]; + int err; + + err = fw_csr_string(unit->directory, CSR_MODEL, + model, sizeof(model)); + if (err < 0) + return false; + + return match_string(models, ARRAY_SIZE(models), model) >= 0; +} + +static int name_card(struct snd_oxfw *oxfw) +{ + struct fw_device *fw_dev = fw_parent_device(oxfw->unit); + const struct compat_info *info; + char vendor[24]; + char model[32]; + const char *d, *v, *m; + u32 firmware; + int err; + + /* get vendor name from root directory */ + err = fw_csr_string(fw_dev->config_rom + 5, CSR_VENDOR, + vendor, sizeof(vendor)); + if (err < 0) + goto end; + + /* get model name from unit directory */ + err = fw_csr_string(oxfw->unit->directory, CSR_MODEL, + model, sizeof(model)); + if (err < 0) + goto end; + + err = snd_fw_transaction(oxfw->unit, TCODE_READ_QUADLET_REQUEST, + OXFORD_FIRMWARE_ID_ADDRESS, &firmware, 4, 0); + if (err < 0) + goto end; + be32_to_cpus(&firmware); + + /* to apply card definitions */ + if (oxfw->entry->vendor_id == VENDOR_GRIFFIN || + oxfw->entry->vendor_id == VENDOR_LACIE) { + info = (const struct compat_info *)oxfw->entry->driver_data; + d = info->driver_name; + v = info->vendor_name; + m = info->model_name; + } else { + d = "OXFW"; + v = vendor; + m = model; + } + + strcpy(oxfw->card->driver, d); + strcpy(oxfw->card->mixername, m); + strcpy(oxfw->card->shortname, m); + + snprintf(oxfw->card->longname, sizeof(oxfw->card->longname), + "%s %s (OXFW%x %04x), GUID %08x%08x at %s, S%d", + v, m, firmware >> 20, firmware & 0xffff, + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&oxfw->unit->device), 100 << fw_dev->max_speed); +end: + return err; +} + +static void oxfw_free(struct snd_oxfw *oxfw) +{ + unsigned int i; + + snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->rx_stream); + if (oxfw->has_output) + snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->tx_stream); + + fw_unit_put(oxfw->unit); + + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; i++) { + kfree(oxfw->tx_stream_formats[i]); + kfree(oxfw->rx_stream_formats[i]); + } + + kfree(oxfw->spec); + mutex_destroy(&oxfw->mutex); + kfree(oxfw); +} + +/* + * This module releases the FireWire unit data after all ALSA character devices + * are released by applications. This is for releasing stream data or finishing + * transactions safely. Thus at returning from .remove(), this module still keep + * references for the unit. + */ +static void oxfw_card_free(struct snd_card *card) +{ + oxfw_free(card->private_data); +} + +static int detect_quirks(struct snd_oxfw *oxfw) +{ + struct fw_device *fw_dev = fw_parent_device(oxfw->unit); + struct fw_csr_iterator it; + int key, val; + int vendor, model; + + /* + * Add ALSA control elements for two models to keep compatibility to + * old firewire-speaker module. + */ + if (oxfw->entry->vendor_id == VENDOR_GRIFFIN) + return snd_oxfw_add_spkr(oxfw, false); + if (oxfw->entry->vendor_id == VENDOR_LACIE) + return snd_oxfw_add_spkr(oxfw, true); + + /* + * Stanton models supports asynchronous transactions for unique MIDI + * messages. + */ + if (oxfw->entry->vendor_id == OUI_STANTON) { + /* No physical MIDI ports. */ + oxfw->midi_input_ports = 0; + oxfw->midi_output_ports = 0; + + return snd_oxfw_scs1x_add(oxfw); + } + + /* + * TASCAM FireOne has physical control and requires a pair of additional + * MIDI ports. + */ + if (oxfw->entry->vendor_id == VENDOR_TASCAM) { + oxfw->midi_input_ports++; + oxfw->midi_output_ports++; + return 0; + } + + /* Seek from Root Directory of Config ROM. */ + vendor = model = 0; + fw_csr_iterator_init(&it, fw_dev->config_rom + 5); + while (fw_csr_iterator_next(&it, &key, &val)) { + if (key == CSR_VENDOR) + vendor = val; + else if (key == CSR_MODEL) + model = val; + } + + /* + * Mackie Onyx Satellite with base station has a quirk to report a wrong + * value in 'dbs' field of CIP header against its format information. + */ + if (vendor == VENDOR_LOUD && model == MODEL_SATELLITE) + oxfw->wrong_dbs = true; + + return 0; +} + +static void do_registration(struct work_struct *work) +{ + struct snd_oxfw *oxfw = container_of(work, struct snd_oxfw, dwork.work); + int i; + int err; + + if (oxfw->registered) + return; + + err = snd_card_new(&oxfw->unit->device, -1, NULL, THIS_MODULE, 0, + &oxfw->card); + if (err < 0) + return; + + err = name_card(oxfw); + if (err < 0) + goto error; + + err = snd_oxfw_stream_discover(oxfw); + if (err < 0) + goto error; + + err = detect_quirks(oxfw); + if (err < 0) + goto error; + + err = snd_oxfw_stream_init_simplex(oxfw, &oxfw->rx_stream); + if (err < 0) + goto error; + if (oxfw->has_output) { + err = snd_oxfw_stream_init_simplex(oxfw, &oxfw->tx_stream); + if (err < 0) + goto error; + } + + err = snd_oxfw_create_pcm(oxfw); + if (err < 0) + goto error; + + snd_oxfw_proc_init(oxfw); + + err = snd_oxfw_create_midi(oxfw); + if (err < 0) + goto error; + + err = snd_oxfw_create_hwdep(oxfw); + if (err < 0) + goto error; + + err = snd_card_register(oxfw->card); + if (err < 0) + goto error; + + /* + * After registered, oxfw instance can be released corresponding to + * releasing the sound card instance. + */ + oxfw->card->private_free = oxfw_card_free; + oxfw->card->private_data = oxfw; + oxfw->registered = true; + + return; +error: + snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->rx_stream); + if (oxfw->has_output) + snd_oxfw_stream_destroy_simplex(oxfw, &oxfw->tx_stream); + for (i = 0; i < SND_OXFW_STREAM_FORMAT_ENTRIES; ++i) { + kfree(oxfw->tx_stream_formats[i]); + oxfw->tx_stream_formats[i] = NULL; + kfree(oxfw->rx_stream_formats[i]); + oxfw->rx_stream_formats[i] = NULL; + } + snd_card_free(oxfw->card); + kfree(oxfw->spec); + oxfw->spec = NULL; + dev_info(&oxfw->unit->device, + "Sound card registration failed: %d\n", err); +} + +static int oxfw_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_oxfw *oxfw; + + if (entry->vendor_id == VENDOR_LOUD && !detect_loud_models(unit)) + return -ENODEV; + + /* Allocate this independent of sound card instance. */ + oxfw = kzalloc(sizeof(struct snd_oxfw), GFP_KERNEL); + if (oxfw == NULL) + return -ENOMEM; + + oxfw->entry = entry; + oxfw->unit = fw_unit_get(unit); + dev_set_drvdata(&unit->device, oxfw); + + mutex_init(&oxfw->mutex); + spin_lock_init(&oxfw->lock); + init_waitqueue_head(&oxfw->hwdep_wait); + + /* Allocate and register this sound card later. */ + INIT_DEFERRABLE_WORK(&oxfw->dwork, do_registration); + snd_fw_schedule_registration(unit, &oxfw->dwork); + + return 0; +} + +static void oxfw_bus_reset(struct fw_unit *unit) +{ + struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device); + + if (!oxfw->registered) + snd_fw_schedule_registration(unit, &oxfw->dwork); + + fcp_bus_reset(oxfw->unit); + + if (oxfw->registered) { + mutex_lock(&oxfw->mutex); + + snd_oxfw_stream_update_simplex(oxfw, &oxfw->rx_stream); + if (oxfw->has_output) + snd_oxfw_stream_update_simplex(oxfw, &oxfw->tx_stream); + + mutex_unlock(&oxfw->mutex); + + if (oxfw->entry->vendor_id == OUI_STANTON) + snd_oxfw_scs1x_update(oxfw); + } +} + +static void oxfw_remove(struct fw_unit *unit) +{ + struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device); + + /* + * Confirm to stop the work for registration before the sound card is + * going to be released. The work is not scheduled again because bus + * reset handler is not called anymore. + */ + cancel_delayed_work_sync(&oxfw->dwork); + + if (oxfw->registered) { + /* No need to wait for releasing card object in this context. */ + snd_card_free_when_closed(oxfw->card); + } else { + /* Don't forget this case. */ + oxfw_free(oxfw); + } +} + +static const struct compat_info griffin_firewave = { + .driver_name = "FireWave", + .vendor_name = "Griffin", + .model_name = "FireWave", +}; + +static const struct compat_info lacie_speakers = { + .driver_name = "FWSpeakers", + .vendor_name = "LaCie", + .model_name = "FireWire Speakers", +}; + +static const struct ieee1394_device_id oxfw_id_table[] = { + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = VENDOR_GRIFFIN, + .model_id = 0x00f970, + .specifier_id = SPECIFIER_1394TA, + .version = VERSION_AVC, + .driver_data = (kernel_ulong_t)&griffin_firewave, + }, + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = VENDOR_LACIE, + .model_id = 0x00f970, + .specifier_id = SPECIFIER_1394TA, + .version = VERSION_AVC, + .driver_data = (kernel_ulong_t)&lacie_speakers, + }, + /* Behringer,F-Control Audio 202 */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = VENDOR_BEHRINGER, + .model_id = 0x00fc22, + }, + /* + * Any Mackie(Loud) models (name string/model id): + * Onyx-i series (former models): 0x081216 + * Mackie Onyx Satellite: 0x00200f + * Tapco LINK.firewire 4x6: 0x000460 + * d.2 pro/d.4 pro (built-in card): Unknown + * U.420: Unknown + * U.420d: Unknown + */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .vendor_id = VENDOR_LOUD, + .specifier_id = SPECIFIER_1394TA, + .version = VERSION_AVC, + }, + /* TASCAM, FireOne */ + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .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, + }, + // APOGEE, duet FireWire + { + .match_flags = IEEE1394_MATCH_VENDOR_ID | + IEEE1394_MATCH_MODEL_ID, + .vendor_id = OUI_APOGEE, + .model_id = 0x01dddd, + }, + { } +}; +MODULE_DEVICE_TABLE(ieee1394, oxfw_id_table); + +static struct fw_driver oxfw_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + }, + .probe = oxfw_probe, + .update = oxfw_bus_reset, + .remove = oxfw_remove, + .id_table = oxfw_id_table, +}; + +static int __init snd_oxfw_init(void) +{ + return driver_register(&oxfw_driver.driver); +} + +static void __exit snd_oxfw_exit(void) +{ + driver_unregister(&oxfw_driver.driver); +} + +module_init(snd_oxfw_init); +module_exit(snd_oxfw_exit); diff --git a/sound/firewire/oxfw/oxfw.h b/sound/firewire/oxfw/oxfw.h new file mode 100644 index 000000000..d54d4a9ac --- /dev/null +++ b/sound/firewire/oxfw/oxfw.h @@ -0,0 +1,141 @@ +/* + * oxfw.h - a part of driver for OXFW970/971 based devices + * + * Copyright (c) Clemens Ladisch <clemens@ladisch.de> + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/compat.h> +#include <linux/sched/signal.h> + +#include <sound/control.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/info.h> +#include <sound/rawmidi.h> +#include <sound/firewire.h> +#include <sound/hwdep.h> + +#include "../lib.h" +#include "../fcp.h" +#include "../packets-buffer.h" +#include "../iso-resources.h" +#include "../amdtp-am824.h" +#include "../cmp.h" + +/* This is an arbitrary number for convinience. */ +#define SND_OXFW_STREAM_FORMAT_ENTRIES 10 +struct snd_oxfw { + struct snd_card *card; + struct fw_unit *unit; + struct mutex mutex; + spinlock_t lock; + + bool registered; + struct delayed_work dwork; + + bool wrong_dbs; + bool has_output; + u8 *tx_stream_formats[SND_OXFW_STREAM_FORMAT_ENTRIES]; + u8 *rx_stream_formats[SND_OXFW_STREAM_FORMAT_ENTRIES]; + bool assumed; + struct cmp_connection out_conn; + struct cmp_connection in_conn; + struct amdtp_stream tx_stream; + struct amdtp_stream rx_stream; + unsigned int capture_substreams; + unsigned int playback_substreams; + + unsigned int midi_input_ports; + unsigned int midi_output_ports; + + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; + + const struct ieee1394_device_id *entry; + void *spec; +}; + +/* + * AV/C Stream Format Information Specification 1.1 Working Draft + * (Apr 2005, 1394TA) + */ +int avc_stream_set_format(struct fw_unit *unit, enum avc_general_plug_dir dir, + unsigned int pid, u8 *format, unsigned int len); +int avc_stream_get_format(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len, unsigned int eid); +static inline int +avc_stream_get_format_single(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len) +{ + return avc_stream_get_format(unit, dir, pid, buf, len, 0xff); +} +static inline int +avc_stream_get_format_list(struct fw_unit *unit, + enum avc_general_plug_dir dir, unsigned int pid, + u8 *buf, unsigned int *len, + unsigned int eid) +{ + return avc_stream_get_format(unit, dir, pid, buf, len, eid); +} + +/* + * AV/C Digital Interface Command Set General Specification 4.2 + * (Sep 2004, 1394TA) + */ +int avc_general_inquiry_sig_fmt(struct fw_unit *unit, unsigned int rate, + enum avc_general_plug_dir dir, + unsigned short pid); + +int snd_oxfw_stream_init_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream); +int snd_oxfw_stream_start_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream, + unsigned int rate, unsigned int pcm_channels); +void snd_oxfw_stream_stop_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream); +void snd_oxfw_stream_destroy_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream); +void snd_oxfw_stream_update_simplex(struct snd_oxfw *oxfw, + struct amdtp_stream *stream); + +struct snd_oxfw_stream_formation { + unsigned int rate; + unsigned int pcm; + unsigned int midi; +}; +int snd_oxfw_stream_parse_format(u8 *format, + struct snd_oxfw_stream_formation *formation); +int snd_oxfw_stream_get_current_formation(struct snd_oxfw *oxfw, + enum avc_general_plug_dir dir, + struct snd_oxfw_stream_formation *formation); + +int snd_oxfw_stream_discover(struct snd_oxfw *oxfw); + +void snd_oxfw_stream_lock_changed(struct snd_oxfw *oxfw); +int snd_oxfw_stream_lock_try(struct snd_oxfw *oxfw); +void snd_oxfw_stream_lock_release(struct snd_oxfw *oxfw); + +int snd_oxfw_create_pcm(struct snd_oxfw *oxfw); + +void snd_oxfw_proc_init(struct snd_oxfw *oxfw); + +int snd_oxfw_create_midi(struct snd_oxfw *oxfw); + +int snd_oxfw_create_hwdep(struct snd_oxfw *oxfw); + +int snd_oxfw_add_spkr(struct snd_oxfw *oxfw, bool is_lacie); +int snd_oxfw_scs1x_add(struct snd_oxfw *oxfw); +void snd_oxfw_scs1x_update(struct snd_oxfw *oxfw); |