--- a/sound/usb/Kconfig +++ b/sound/usb/Kconfig @@ -106,5 +106,15 @@ config SND_USB_US122L To compile this driver as a module, choose M here: the module will be called snd-usb-us122l. +config SND_USB_UA1A + tristate "Edirol UA-1A driver" + select SND_PCM + help + Say Y here to include support for the Edirol UA-1A audio + interface. + + To compile this driver as a module, choose M here: the module + will be called snd-usb-ua1a. + endif # SND_USB --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -1922,6 +1922,13 @@ Prior to version 0.9.0rc4 options had a This module supports multiple devices, autoprobe and hotplugging. + Module snd-usb-ua1a + ------------------- + + Module for the Edirol UA-1A audio interface. + + This module supports multiple devices, autoprobe and hotplugging. + Module snd-usb-usx2y -------------------- --- a/sound/usb/misc/Makefile +++ b/sound/usb/misc/Makefile @@ -1,2 +1,4 @@ snd-ua101-objs := ua101.o +snd-ua1a-objs := ua1a.o obj-$(CONFIG_SND_USB_UA101) += snd-ua101.o +obj-$(CONFIG_SND_USB_UA1A) += snd-ua1a.o --- /dev/null +++ b/sound/usb/misc/ua1a.c @@ -0,0 +1,738 @@ +/* + * Edirol UA-1A driver + * Copyright (c) Clemens Ladisch + * + * This driver is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this driver. If not, see . + */ + +/* + * This driver is somewhat pointless because the standard snd-usb-audio driver + * handles the UA-1A just fine; I'm using it only for testing or debugging. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Clemens Ladisch "); +MODULE_DESCRIPTION("Edirol UA-1A driver"); +MODULE_LICENSE("GPL v2"); +MODULE_SUPPORTED_DEVICE("{{Edirol,UA-1A}}"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "card index"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string"); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "enable card"); + +/* how many URBs are queued */ +#define QUEUE_LENGTH 8 + +#define PLAYBACK_ENDPOINT 1 +#define PLAYBACK_BUFFER_SIZE (45 * 4) +#define CAPTURE_ENDPOINT 2 +#define CAPTURE_BUFFER_SIZE (48 * 4) + +/* these INTF_* symbols are both USB interface numbers and array indices */ +enum { + INTF_CONTROL, + INTF_PLAYBACK, + INTF_CAPTURE, + + INTF_COUNT +}; + +struct ua1a { + struct usb_device *dev; + struct snd_card *card; + struct usb_interface *intf[INTF_COUNT]; + struct snd_pcm *pcm; + unsigned int slot_index; + bool disconnect; + struct ua1a_stream { + spinlock_t lock; + struct snd_pcm_substream *substream; + unsigned int buffer_pos; + unsigned int period_pos; + bool running; + u8 altsetting; + struct urb *urbs[QUEUE_LENGTH]; + } playback, capture; + unsigned int playback_phase; +}; + +static DEFINE_MUTEX(devices_mutex); +static DEFINE_MUTEX(free_urbs_mutex); +static unsigned int used_slots; +static struct usb_driver ua1a_driver; + +static void ua1a_playback_complete(struct urb *urb) +{ + struct ua1a *ua = urb->context; + struct ua1a_stream *stream = &ua->playback; + struct snd_pcm_runtime *runtime; + unsigned long flags; + unsigned int frames, frames1; + const u8 *source; + int err; + bool period_elapsed = 0; + + if (urb->status == -ENOENT || /* unlinked */ + urb->status == -ENODEV || /* device removed */ + urb->status == -ECONNRESET || /* unlinked */ + urb->status == -ESHUTDOWN) /* device disabled */ + goto xrun; + + spin_lock_irqsave(&stream->lock, flags); + + /* + * determine how many frames to send in the next packet: + * 44100 Hz => 44.1 audio frames per USB frame, on average + * => every tenth packet gets one more frame + */ + frames = ua->playback_phase == 0 ? 45 : 44; + ua->playback_phase = (ua->playback_phase + 1) % 10; + urb->iso_frame_desc[0].length = frames * 4; + + if (stream->running) { + /* copy data from the ALSA ring buffer into the URB buffer */ + runtime = stream->substream->runtime; + source = runtime->dma_area + stream->buffer_pos * 4; + if (stream->buffer_pos + frames <= runtime->buffer_size) { + memcpy(urb->transfer_buffer, source, frames * 4); + } else { + /* wrap around at end of ring buffer */ + frames1 = runtime->buffer_size - stream->buffer_pos; + memcpy(urb->transfer_buffer, source, frames1 * 4); + memcpy(urb->transfer_buffer + frames1 * 4, + runtime->dma_area, (frames - frames1) * 4); + } + + stream->buffer_pos += frames; + if (stream->buffer_pos >= runtime->buffer_size) + stream->buffer_pos -= runtime->buffer_size; + stream->period_pos += frames; + if (stream->period_pos >= runtime->period_size) { + stream->period_pos -= runtime->period_size; + period_elapsed = 1; + } + } else { + memset(urb->transfer_buffer, 0, PLAYBACK_BUFFER_SIZE); + } + + spin_unlock_irqrestore(&stream->lock, flags); + + urb->dev = ua->dev; + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) + goto xrun; + + if (period_elapsed) + snd_pcm_period_elapsed(stream->substream); + return; + +xrun: + snd_pcm_stop(stream->substream, SNDRV_PCM_STATE_XRUN); +} + +static void ua1a_capture_complete(struct urb *urb) +{ + struct ua1a *ua = urb->context; + struct ua1a_stream *stream = &ua->capture; + struct snd_pcm_runtime *runtime; + unsigned long flags; + unsigned int frames, frames1; + u8 *dest; + int err; + bool period_elapsed = 0; + + if (urb->status == -ENOENT || /* unlinked */ + urb->status == -ENODEV || /* device removed */ + urb->status == -ECONNRESET || /* unlinked */ + urb->status == -ESHUTDOWN) /* device disabled */ + goto xrun; + + spin_lock_irqsave(&stream->lock, flags); + + if (urb->status >= 0 && + urb->iso_frame_desc[0].status >= 0 && + stream->running) { + /* copy data from the URB buffer into the ALSA ring buffer */ + runtime = stream->substream->runtime; + frames = urb->iso_frame_desc[0].actual_length / 4; + dest = runtime->dma_area + stream->buffer_pos * 4; + if (stream->buffer_pos + frames <= runtime->buffer_size) { + memcpy(dest, urb->transfer_buffer, frames * 4); + } else { + /* wrap around at end of ring buffer */ + frames1 = runtime->buffer_size - stream->buffer_pos; + memcpy(dest, urb->transfer_buffer, frames1 * 4); + memcpy(runtime->dma_area, + urb->transfer_buffer + frames1 * 4, + (frames - frames1) * 4); + } + + stream->buffer_pos += frames; + if (stream->buffer_pos >= runtime->buffer_size) + stream->buffer_pos -= runtime->buffer_size; + stream->period_pos += frames; + if (stream->period_pos >= runtime->period_size) { + stream->period_pos -= runtime->period_size; + period_elapsed = 1; + } + } + + spin_unlock_irqrestore(&stream->lock, flags); + + urb->dev = ua->dev; + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) + goto xrun; + + if (period_elapsed) + snd_pcm_period_elapsed(stream->substream); + return; + +xrun: + snd_pcm_stop(stream->substream, SNDRV_PCM_STATE_XRUN); +} + +static struct snd_pcm_hardware ua1a_pcm_hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_44100, + .rate_min = 44100, + .rate_max = 44100, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 1024 * 1024, + .period_bytes_min = 66 * 4, + .period_bytes_max = 512 * 1024, + .periods_min = 2, + .periods_max = 2048, +}; + +static void free_urbs(struct ua1a *ua, struct ua1a_stream *stream) +{ + unsigned int i; + + mutex_lock(&free_urbs_mutex); + for (i = 0; i < QUEUE_LENGTH; ++i) + if (stream->urbs[i]) { + usb_free_coherent(ua->dev, + stream->urbs[i]->transfer_buffer_length, + stream->urbs[i]->transfer_buffer, + stream->urbs[i]->transfer_dma); + usb_free_urb(stream->urbs[i]); + stream->urbs[i] = NULL; + } + mutex_unlock(&free_urbs_mutex); +} + +static int ua1a_open(struct snd_pcm_substream *substream, + struct ua1a_stream *stream, unsigned int pipe, + unsigned int buffer_size, usb_complete_t complete) +{ + struct ua1a *ua = substream->private_data; + struct urb *urb; + unsigned int i; + + for (i = 0; i < QUEUE_LENGTH; ++i) { + urb = usb_alloc_urb(1, GFP_KERNEL); + if (!urb) + goto out_of_memory; + stream->urbs[i] = urb; + urb->pipe = pipe; + urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + urb->transfer_buffer = usb_alloc_coherent(ua->dev, buffer_size, + GFP_KERNEL, + &urb->transfer_dma); + if (!urb->transfer_buffer) + goto out_of_memory; + urb->transfer_buffer_length = buffer_size; + urb->number_of_packets = 1; + urb->interval = 1; + urb->context = ua; + urb->complete = complete; + urb->iso_frame_desc[0].offset = 0; + urb->iso_frame_desc[0].length = buffer_size; + } + stream->substream = substream; + return 0; + +out_of_memory: + free_urbs(ua, stream); + return -ENOMEM; +} + +static int ua1a_playback_open(struct snd_pcm_substream *substream) +{ + struct ua1a *ua = substream->private_data; + + substream->runtime->hw = ua1a_pcm_hardware; + return ua1a_open(substream, &ua->playback, + usb_sndisocpipe(ua->dev, PLAYBACK_ENDPOINT), + PLAYBACK_BUFFER_SIZE, ua1a_playback_complete); +} + +static int ua1a_capture_open(struct snd_pcm_substream *substream) +{ + struct ua1a *ua = substream->private_data; + + substream->runtime->hw = ua1a_pcm_hardware; + return ua1a_open(substream, &ua->capture, + usb_rcvisocpipe(ua->dev, CAPTURE_ENDPOINT), + CAPTURE_BUFFER_SIZE, ua1a_capture_complete); +} + +static int ua1a_playback_close(struct snd_pcm_substream *substream) +{ + struct ua1a *ua = substream->private_data; + + ua->playback.substream = NULL; + free_urbs(ua, &ua->playback); + return 0; +} + +static int ua1a_capture_close(struct snd_pcm_substream *substream) +{ + struct ua1a *ua = substream->private_data; + + ua->capture.substream = NULL; + free_urbs(ua, &ua->capture); + return 0; +} + +static void ua1a_kill_urbs(struct ua1a_stream *stream) +{ + unsigned int i; + + for (i = 0; i < QUEUE_LENGTH; ++i) + usb_kill_urb(stream->urbs[i]); +} + +static void ua1a_unlink_urbs(struct ua1a_stream *stream) +{ + unsigned int i; + + for (i = 0; i < QUEUE_LENGTH; ++i) + usb_unlink_urb(stream->urbs[i]); +} + +static int ua1a_hw_params(struct snd_pcm_substream *substream, + struct ua1a_stream *stream, unsigned int intf_number, + struct snd_pcm_hw_params *hw_params) +{ + struct ua1a *ua = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + if (stream->altsetting != 1) { + ua1a_kill_urbs(stream); + + err = usb_set_interface(ua->dev, intf_number, 1); + if (err < 0) { + dev_err(&ua->dev->dev, + "cannot initialize interface; error %d\n", err); + return err; + } + stream->altsetting = 1; + } + return 0; +} + +static int ua1a_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct ua1a *ua = substream->private_data; + + return ua1a_hw_params(substream, &ua->playback, + INTF_PLAYBACK, hw_params); +} + +static int ua1a_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct ua1a *ua = substream->private_data; + + return ua1a_hw_params(substream, &ua->capture, + INTF_CAPTURE, hw_params); +} + +static void ua1a_hw_free(struct snd_pcm_substream *substream, + struct ua1a_stream *stream, unsigned int intf_number) +{ + struct ua1a *ua = substream->private_data; + int err; + + ua1a_kill_urbs(stream); + + if (!ua->disconnect && stream->altsetting != 0) { + err = usb_set_interface(ua->dev, intf_number, 0); + if (err < 0) + dev_warn(&ua->dev->dev, + "interface reset failed; error %d\n", err); + stream->altsetting = 0; + } + + snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int ua1a_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct ua1a *ua = substream->private_data; + + ua1a_hw_free(substream, &ua->playback, INTF_PLAYBACK); + return 0; +} + +static int ua1a_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct ua1a *ua = substream->private_data; + + ua1a_hw_free(substream, &ua->capture, INTF_CAPTURE); + return 0; +} + +static int ua1a_submit_urbs(struct ua1a *ua, struct ua1a_stream *stream) +{ + unsigned int i; + int err; + + for (i = 0; i < QUEUE_LENGTH; ++i) { + stream->urbs[i]->dev = ua->dev; + err = usb_submit_urb(stream->urbs[i], GFP_KERNEL); + if (err < 0) { + dev_err(&ua->dev->dev, + "cannot submit URBs; error %d\n", err); + ua1a_kill_urbs(stream); + return err; + } + } + return 0; +} + +static int ua1a_playback_prepare(struct snd_pcm_substream *substream) +{ + struct ua1a *ua = substream->private_data; + unsigned int i; + + ua1a_kill_urbs(&ua->playback); + + ua->playback.buffer_pos = 0; + ua->playback.period_pos = 0; + for (i = 0; i < QUEUE_LENGTH; ++i) { + memset(ua->playback.urbs[i]->transfer_buffer, + 0, PLAYBACK_BUFFER_SIZE); + ua->playback.urbs[i]->iso_frame_desc[0].length = + (44 + ((i % 10) == 0)) * 4; + } + ua->playback_phase = QUEUE_LENGTH % 10; + + return ua1a_submit_urbs(ua, &ua->playback); +} + +static int ua1a_capture_prepare(struct snd_pcm_substream *substream) +{ + struct ua1a *ua = substream->private_data; + + ua1a_kill_urbs(&ua->capture); + + ua->capture.buffer_pos = 0; + ua->capture.period_pos = 0; + + return ua1a_submit_urbs(ua, &ua->capture); +} + +static void ua1a_start(struct ua1a_stream *stream) +{ + spin_lock(&stream->lock); + stream->running = 1; + spin_unlock(&stream->lock); +} + +static void ua1a_stop(struct ua1a_stream *stream) +{ + spin_lock(&stream->lock); + stream->running = 0; + spin_unlock(&stream->lock); + ua1a_unlink_urbs(stream); +} + +static int ua1a_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct ua1a *ua = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + ua1a_start(&ua->playback); + break; + case SNDRV_PCM_TRIGGER_STOP: + ua1a_stop(&ua->playback); + break; + default: + return -EINVAL; + } + return 0; +} + +static int ua1a_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct ua1a *ua = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + ua1a_start(&ua->capture); + break; + case SNDRV_PCM_TRIGGER_STOP: + ua1a_stop(&ua->capture); + break; + default: + return -EINVAL; + } + return 0; +} + +static inline snd_pcm_uframes_t ua1a_pointer(struct ua1a_stream *stream) +{ + unsigned long flags; + unsigned int pos; + + spin_lock_irqsave(&stream->lock, flags); + pos = stream->buffer_pos; + spin_unlock_irqrestore(&stream->lock, flags); + return pos; +} + +static snd_pcm_uframes_t +ua1a_playback_pointer(struct snd_pcm_substream *substream) +{ + struct ua1a *ua = substream->private_data; + + return ua1a_pointer(&ua->playback); +} + +static snd_pcm_uframes_t +ua1a_capture_pointer(struct snd_pcm_substream *substream) +{ + struct ua1a *ua = substream->private_data; + + return ua1a_pointer(&ua->capture); +} + +static struct snd_pcm_ops ua1a_playback_ops = { + .open = ua1a_playback_open, + .close = ua1a_playback_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = ua1a_playback_hw_params, + .hw_free = ua1a_playback_hw_free, + .prepare = ua1a_playback_prepare, + .trigger = ua1a_playback_trigger, + .pointer = ua1a_playback_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, +}; + +static struct snd_pcm_ops ua1a_capture_ops = { + .open = ua1a_capture_open, + .close = ua1a_capture_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = ua1a_capture_hw_params, + .hw_free = ua1a_capture_hw_free, + .prepare = ua1a_capture_prepare, + .trigger = ua1a_capture_trigger, + .pointer = ua1a_capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, +}; + +static void ua1a_card_free(struct snd_card *card) +{ +#if 0 + struct ua1a *ua = card->private_data; +#endif +} + +static int ua1a_probe(struct usb_interface *interface, + const struct usb_device_id *usb_id) +{ + struct snd_card *card; + struct ua1a *ua; + unsigned int intf_claimed = 0; + unsigned int slot_index, i; + int err; + + mutex_lock(&devices_mutex); + + for (slot_index = 0; slot_index < SNDRV_CARDS; ++slot_index) + if (enable[slot_index] && !(used_slots & (1 << slot_index))) + break; + if (slot_index >= SNDRV_CARDS) { + mutex_unlock(&devices_mutex); + return -ENOENT; + } + err = snd_card_create(index[slot_index], id[slot_index], THIS_MODULE, + sizeof(*ua), &card); + if (err < 0) { + mutex_unlock(&devices_mutex); + return err; + } + card->private_free = ua1a_card_free; + ua = card->private_data; + ua->dev = interface_to_usbdev(interface); + ua->card = card; + ua->slot_index = slot_index; + spin_lock_init(&ua->playback.lock); + spin_lock_init(&ua->capture.lock); + + strcpy(card->driver, "UA-1A"); + strcpy(card->shortname, "UA-1A"); + strcpy(card->longname, "Edirol UA-1A at "); + i = strlen(card->longname); + usb_make_path(ua->dev, card->longname + i, sizeof(card->longname) - i); + + for (i = 0; i < INTF_COUNT; ++i) { + ua->intf[i] = usb_ifnum_to_if(ua->dev, i); + if (!ua->intf[i]) { + err = -ENXIO; + goto probe_error; + } + if (ua->intf[i] != interface) { + err = usb_driver_claim_interface(&ua1a_driver, ua->intf[i], ua); + if (err < 0) { + err = -EBUSY; + goto probe_error; + } + intf_claimed |= 1 << i; + } + } + + snd_card_set_dev(card, &interface->dev); + + err = snd_pcm_new(card, "UA-1A", 0, 1, 1, &ua->pcm); + if (err < 0) + goto probe_error; + ua->pcm->private_data = ua; + strcpy(ua->pcm->name, "UA-1A"); + snd_pcm_set_ops(ua->pcm, + SNDRV_PCM_STREAM_PLAYBACK, &ua1a_playback_ops); + snd_pcm_set_ops(ua->pcm, + SNDRV_PCM_STREAM_CAPTURE, &ua1a_capture_ops); + + err = snd_card_register(card); + if (err < 0) + goto probe_error; + usb_set_intfdata(interface, ua); + used_slots |= 1 << slot_index; + + mutex_unlock(&devices_mutex); + return 0; + +probe_error: + for (i = 0; i < INTF_COUNT; ++i) + if (intf_claimed & (1 << i)) { + usb_set_intfdata(ua->intf[i], NULL); + usb_driver_release_interface(&ua1a_driver, ua->intf[i]); + } + mutex_unlock(&devices_mutex); + snd_card_free(card); + return err; +} + +static void ua1a_disconnect(struct usb_interface *interface) +{ + struct ua1a *ua = usb_get_intfdata(interface); + unsigned int i; + + if (!ua) + return; + + mutex_lock(&devices_mutex); + + ua->disconnect = 1; + + /* make sure that userspace cannot create new requests */ + snd_card_disconnect(ua->card); + + /* make sure that there are no pending USB requests */ + mutex_lock(&free_urbs_mutex); + if (ua->playback.urbs[0]) + ua1a_kill_urbs(&ua->playback); + if (ua->capture.urbs[0]) + ua1a_kill_urbs(&ua->capture); + mutex_unlock(&free_urbs_mutex); + + for (i = 0; i < INTF_COUNT; ++i) + if (ua->intf[i] != interface) { + usb_set_intfdata(ua->intf[i], NULL); + usb_driver_release_interface(&ua1a_driver, ua->intf[i]); + } + usb_set_intfdata(interface, NULL); + + used_slots &= ~(1 << ua->slot_index); + + snd_card_free_when_closed(ua->card); + + mutex_unlock(&devices_mutex); +} + +static struct usb_device_id ua1a_ids[] = { + { USB_DEVICE(0x0582, 0x0018) }, + { } +}; +MODULE_DEVICE_TABLE(usb, ua1a_ids); + +static struct usb_driver ua1a_driver = { + .name = "UA-1A", + .id_table = ua1a_ids, + .probe = ua1a_probe, + .disconnect = ua1a_disconnect, +#if 0 + .suspend = ua1a_suspend, + .resume = ua1a_resume, +#endif +}; + +static int __init alsa_card_ua1a_init(void) +{ + return usb_register(&ua1a_driver); +} + +static void __exit alsa_card_ua1a_exit(void) +{ + usb_deregister(&ua1a_driver); + mutex_destroy(&devices_mutex); + mutex_destroy(&free_urbs_mutex); +} + +module_init(alsa_card_ua1a_init); +module_exit(alsa_card_ua1a_exit);