diff options
Diffstat (limited to 'sound/usb/usx2y')
-rw-r--r-- | sound/usb/usx2y/Makefile | 6 | ||||
-rw-r--r-- | sound/usb/usx2y/us122l.c | 740 | ||||
-rw-r--r-- | sound/usb/usx2y/us122l.h | 34 | ||||
-rw-r--r-- | sound/usb/usx2y/usX2Yhwdep.c | 249 | ||||
-rw-r--r-- | sound/usb/usx2y/usX2Yhwdep.h | 7 | ||||
-rw-r--r-- | sound/usb/usx2y/usb_stream.c | 782 | ||||
-rw-r--r-- | sound/usb/usx2y/usb_stream.h | 46 | ||||
-rw-r--r-- | sound/usb/usx2y/usbus428ctldefs.h | 93 | ||||
-rw-r--r-- | sound/usb/usx2y/usbusx2y.c | 466 | ||||
-rw-r--r-- | sound/usb/usx2y/usbusx2y.h | 88 | ||||
-rw-r--r-- | sound/usb/usx2y/usbusx2yaudio.c | 1033 | ||||
-rw-r--r-- | sound/usb/usx2y/usx2y.h | 38 | ||||
-rw-r--r-- | sound/usb/usx2y/usx2yhwdeppcm.c | 775 | ||||
-rw-r--r-- | sound/usb/usx2y/usx2yhwdeppcm.h | 23 |
14 files changed, 4380 insertions, 0 deletions
diff --git a/sound/usb/usx2y/Makefile b/sound/usb/usx2y/Makefile new file mode 100644 index 0000000000..cc4c2f1efa --- /dev/null +++ b/sound/usb/usx2y/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 +snd-usb-usx2y-objs := usbusx2y.o usX2Yhwdep.o usx2yhwdeppcm.o +snd-usb-us122l-objs := us122l.o + +obj-$(CONFIG_SND_USB_USX2Y) += snd-usb-usx2y.o +obj-$(CONFIG_SND_USB_US122L) += snd-usb-us122l.o diff --git a/sound/usb/usx2y/us122l.c b/sound/usb/usx2y/us122l.c new file mode 100644 index 0000000000..709ccad972 --- /dev/null +++ b/sound/usb/usx2y/us122l.c @@ -0,0 +1,740 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007, 2008 Karsten Wiese <fzu@wemgehoertderstaat.de> + */ + +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/usb/audio.h> +#include <linux/module.h> +#include <sound/core.h> +#include <sound/hwdep.h> +#include <sound/pcm.h> +#include <sound/initval.h> +#define MODNAME "US122L" +#include "usb_stream.c" +#include "../usbaudio.h" +#include "../midi.h" +#include "us122l.h" + +MODULE_AUTHOR("Karsten Wiese <fzu@wemgehoertderstaat.de>"); +MODULE_DESCRIPTION("TASCAM "NAME_ALLCAPS" Version 0.5"); +MODULE_LICENSE("GPL"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */ + /* Enable this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for "NAME_ALLCAPS"."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for "NAME_ALLCAPS"."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable "NAME_ALLCAPS"."); + +/* driver_info flags */ +#define US122L_FLAG_US144 BIT(0) + +static int snd_us122l_card_used[SNDRV_CARDS]; + +static int us122l_create_usbmidi(struct snd_card *card) +{ + static const struct snd_usb_midi_endpoint_info quirk_data = { + .out_ep = 4, + .in_ep = 3, + .out_cables = 0x001, + .in_cables = 0x001 + }; + static const struct snd_usb_audio_quirk quirk = { + .vendor_name = "US122L", + .product_name = NAME_ALLCAPS, + .ifnum = 1, + .type = QUIRK_MIDI_US122L, + .data = &quirk_data + }; + struct usb_device *dev = US122L(card)->dev; + struct usb_interface *iface = usb_ifnum_to_if(dev, 1); + + return snd_usbmidi_create(card, iface, + &US122L(card)->midi_list, &quirk); +} + +static int us144_create_usbmidi(struct snd_card *card) +{ + static const struct snd_usb_midi_endpoint_info quirk_data = { + .out_ep = 4, + .in_ep = 3, + .out_cables = 0x001, + .in_cables = 0x001 + }; + static const struct snd_usb_audio_quirk quirk = { + .vendor_name = "US144", + .product_name = NAME_ALLCAPS, + .ifnum = 0, + .type = QUIRK_MIDI_US122L, + .data = &quirk_data + }; + struct usb_device *dev = US122L(card)->dev; + struct usb_interface *iface = usb_ifnum_to_if(dev, 0); + + return snd_usbmidi_create(card, iface, + &US122L(card)->midi_list, &quirk); +} + +static void pt_info_set(struct usb_device *dev, u8 v) +{ + int ret; + + ret = usb_control_msg_send(dev, 0, 'I', + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + v, 0, NULL, 0, 1000, GFP_NOIO); + snd_printdd(KERN_DEBUG "%i\n", ret); +} + +static void usb_stream_hwdep_vm_open(struct vm_area_struct *area) +{ + struct us122l *us122l = area->vm_private_data; + + atomic_inc(&us122l->mmap_count); + snd_printdd(KERN_DEBUG "%i\n", atomic_read(&us122l->mmap_count)); +} + +static vm_fault_t usb_stream_hwdep_vm_fault(struct vm_fault *vmf) +{ + unsigned long offset; + struct page *page; + void *vaddr; + struct us122l *us122l = vmf->vma->vm_private_data; + struct usb_stream *s; + + mutex_lock(&us122l->mutex); + s = us122l->sk.s; + if (!s) + goto unlock; + + offset = vmf->pgoff << PAGE_SHIFT; + if (offset < PAGE_ALIGN(s->read_size)) { + vaddr = (char *)s + offset; + } else { + offset -= PAGE_ALIGN(s->read_size); + if (offset >= PAGE_ALIGN(s->write_size)) + goto unlock; + + vaddr = us122l->sk.write_page + offset; + } + page = virt_to_page(vaddr); + + get_page(page); + mutex_unlock(&us122l->mutex); + + vmf->page = page; + + return 0; +unlock: + mutex_unlock(&us122l->mutex); + return VM_FAULT_SIGBUS; +} + +static void usb_stream_hwdep_vm_close(struct vm_area_struct *area) +{ + struct us122l *us122l = area->vm_private_data; + + atomic_dec(&us122l->mmap_count); + snd_printdd(KERN_DEBUG "%i\n", atomic_read(&us122l->mmap_count)); +} + +static const struct vm_operations_struct usb_stream_hwdep_vm_ops = { + .open = usb_stream_hwdep_vm_open, + .fault = usb_stream_hwdep_vm_fault, + .close = usb_stream_hwdep_vm_close, +}; + +static int usb_stream_hwdep_open(struct snd_hwdep *hw, struct file *file) +{ + struct us122l *us122l = hw->private_data; + struct usb_interface *iface; + + snd_printdd(KERN_DEBUG "%p %p\n", hw, file); + if (hw->used >= 2) + return -EBUSY; + + if (!us122l->first) + us122l->first = file; + + if (us122l->is_us144) { + iface = usb_ifnum_to_if(us122l->dev, 0); + usb_autopm_get_interface(iface); + } + iface = usb_ifnum_to_if(us122l->dev, 1); + usb_autopm_get_interface(iface); + return 0; +} + +static int usb_stream_hwdep_release(struct snd_hwdep *hw, struct file *file) +{ + struct us122l *us122l = hw->private_data; + struct usb_interface *iface; + + snd_printdd(KERN_DEBUG "%p %p\n", hw, file); + + if (us122l->is_us144) { + iface = usb_ifnum_to_if(us122l->dev, 0); + usb_autopm_put_interface(iface); + } + iface = usb_ifnum_to_if(us122l->dev, 1); + usb_autopm_put_interface(iface); + if (us122l->first == file) + us122l->first = NULL; + mutex_lock(&us122l->mutex); + if (us122l->master == file) + us122l->master = us122l->slave; + + us122l->slave = NULL; + mutex_unlock(&us122l->mutex); + return 0; +} + +static int usb_stream_hwdep_mmap(struct snd_hwdep *hw, + struct file *filp, struct vm_area_struct *area) +{ + unsigned long size = area->vm_end - area->vm_start; + struct us122l *us122l = hw->private_data; + unsigned long offset; + struct usb_stream *s; + int err = 0; + bool read; + + offset = area->vm_pgoff << PAGE_SHIFT; + mutex_lock(&us122l->mutex); + s = us122l->sk.s; + read = offset < s->read_size; + if (read && area->vm_flags & VM_WRITE) { + err = -EPERM; + goto out; + } + snd_printdd(KERN_DEBUG "%lu %u\n", size, + read ? s->read_size : s->write_size); + /* if userspace tries to mmap beyond end of our buffer, fail */ + if (size > PAGE_ALIGN(read ? s->read_size : s->write_size)) { + snd_printk(KERN_WARNING "%lu > %u\n", size, + read ? s->read_size : s->write_size); + err = -EINVAL; + goto out; + } + + area->vm_ops = &usb_stream_hwdep_vm_ops; + vm_flags_set(area, VM_DONTDUMP); + if (!read) + vm_flags_set(area, VM_DONTEXPAND); + area->vm_private_data = us122l; + atomic_inc(&us122l->mmap_count); +out: + mutex_unlock(&us122l->mutex); + return err; +} + +static __poll_t usb_stream_hwdep_poll(struct snd_hwdep *hw, + struct file *file, poll_table *wait) +{ + struct us122l *us122l = hw->private_data; + unsigned int *polled; + __poll_t mask; + + poll_wait(file, &us122l->sk.sleep, wait); + + mask = EPOLLIN | EPOLLOUT | EPOLLWRNORM | EPOLLERR; + if (mutex_trylock(&us122l->mutex)) { + struct usb_stream *s = us122l->sk.s; + + if (s && s->state == usb_stream_ready) { + if (us122l->first == file) + polled = &s->periods_polled; + else + polled = &us122l->second_periods_polled; + if (*polled != s->periods_done) { + *polled = s->periods_done; + mask = EPOLLIN | EPOLLOUT | EPOLLWRNORM; + } else { + mask = 0; + } + } + mutex_unlock(&us122l->mutex); + } + return mask; +} + +static void us122l_stop(struct us122l *us122l) +{ + struct list_head *p; + + list_for_each(p, &us122l->midi_list) + snd_usbmidi_input_stop(p); + + usb_stream_stop(&us122l->sk); + usb_stream_free(&us122l->sk); +} + +static int us122l_set_sample_rate(struct usb_device *dev, int rate) +{ + unsigned int ep = 0x81; + unsigned char data[3]; + int err; + + data[0] = rate; + data[1] = rate >> 8; + data[2] = rate >> 16; + err = usb_control_msg_send(dev, 0, UAC_SET_CUR, + USB_TYPE_CLASS | USB_RECIP_ENDPOINT | USB_DIR_OUT, + UAC_EP_CS_ATTR_SAMPLE_RATE << 8, ep, data, 3, + 1000, GFP_NOIO); + if (err) + snd_printk(KERN_ERR "%d: cannot set freq %d to ep 0x%x\n", + dev->devnum, rate, ep); + return err; +} + +static bool us122l_start(struct us122l *us122l, + unsigned int rate, unsigned int period_frames) +{ + struct list_head *p; + int err; + unsigned int use_packsize = 0; + bool success = false; + + if (us122l->dev->speed == USB_SPEED_HIGH) { + /* The us-122l's descriptor defaults to iso max_packsize 78, + which isn't needed for samplerates <= 48000. + Lets save some memory: + */ + switch (rate) { + case 44100: + use_packsize = 36; + break; + case 48000: + use_packsize = 42; + break; + case 88200: + use_packsize = 72; + break; + } + } + if (!usb_stream_new(&us122l->sk, us122l->dev, 1, 2, + rate, use_packsize, period_frames, 6)) + goto out; + + err = us122l_set_sample_rate(us122l->dev, rate); + if (err < 0) { + us122l_stop(us122l); + snd_printk(KERN_ERR "us122l_set_sample_rate error\n"); + goto out; + } + err = usb_stream_start(&us122l->sk); + if (err < 0) { + us122l_stop(us122l); + snd_printk(KERN_ERR "%s error %i\n", __func__, err); + goto out; + } + list_for_each(p, &us122l->midi_list) + snd_usbmidi_input_start(p); + success = true; +out: + return success; +} + +static int usb_stream_hwdep_ioctl(struct snd_hwdep *hw, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct usb_stream_config cfg; + struct us122l *us122l = hw->private_data; + struct usb_stream *s; + unsigned int min_period_frames; + int err = 0; + bool high_speed; + + if (cmd != SNDRV_USB_STREAM_IOCTL_SET_PARAMS) + return -ENOTTY; + + if (copy_from_user(&cfg, (void __user *)arg, sizeof(cfg))) + return -EFAULT; + + if (cfg.version != USB_STREAM_INTERFACE_VERSION) + return -ENXIO; + + high_speed = us122l->dev->speed == USB_SPEED_HIGH; + if ((cfg.sample_rate != 44100 && cfg.sample_rate != 48000 && + (!high_speed || + (cfg.sample_rate != 88200 && cfg.sample_rate != 96000))) || + cfg.frame_size != 6 || + cfg.period_frames > 0x3000) + return -EINVAL; + + switch (cfg.sample_rate) { + case 44100: + min_period_frames = 48; + break; + case 48000: + min_period_frames = 52; + break; + default: + min_period_frames = 104; + break; + } + if (!high_speed) + min_period_frames <<= 1; + if (cfg.period_frames < min_period_frames) + return -EINVAL; + + snd_power_wait(hw->card); + + mutex_lock(&us122l->mutex); + s = us122l->sk.s; + if (!us122l->master) { + us122l->master = file; + } else if (us122l->master != file) { + if (!s || memcmp(&cfg, &s->cfg, sizeof(cfg))) { + err = -EIO; + goto unlock; + } + us122l->slave = file; + } + if (!s || memcmp(&cfg, &s->cfg, sizeof(cfg)) || + s->state == usb_stream_xrun) { + us122l_stop(us122l); + if (!us122l_start(us122l, cfg.sample_rate, cfg.period_frames)) + err = -EIO; + else + err = 1; + } +unlock: + mutex_unlock(&us122l->mutex); + wake_up_all(&us122l->sk.sleep); + return err; +} + +#define SND_USB_STREAM_ID "USB STREAM" +static int usb_stream_hwdep_new(struct snd_card *card) +{ + int err; + struct snd_hwdep *hw; + struct usb_device *dev = US122L(card)->dev; + + err = snd_hwdep_new(card, SND_USB_STREAM_ID, 0, &hw); + if (err < 0) + return err; + + hw->iface = SNDRV_HWDEP_IFACE_USB_STREAM; + hw->private_data = US122L(card); + hw->ops.open = usb_stream_hwdep_open; + hw->ops.release = usb_stream_hwdep_release; + hw->ops.ioctl = usb_stream_hwdep_ioctl; + hw->ops.ioctl_compat = usb_stream_hwdep_ioctl; + hw->ops.mmap = usb_stream_hwdep_mmap; + hw->ops.poll = usb_stream_hwdep_poll; + + sprintf(hw->name, "/dev/bus/usb/%03d/%03d/hwdeppcm", + dev->bus->busnum, dev->devnum); + return 0; +} + +static bool us122l_create_card(struct snd_card *card) +{ + int err; + struct us122l *us122l = US122L(card); + + if (us122l->is_us144) { + err = usb_set_interface(us122l->dev, 0, 1); + if (err) { + snd_printk(KERN_ERR "usb_set_interface error\n"); + return false; + } + } + err = usb_set_interface(us122l->dev, 1, 1); + if (err) { + snd_printk(KERN_ERR "usb_set_interface error\n"); + return false; + } + + pt_info_set(us122l->dev, 0x11); + pt_info_set(us122l->dev, 0x10); + + if (!us122l_start(us122l, 44100, 256)) + return false; + + if (us122l->is_us144) + err = us144_create_usbmidi(card); + else + err = us122l_create_usbmidi(card); + if (err < 0) { + snd_printk(KERN_ERR "us122l_create_usbmidi error %i\n", err); + goto stop; + } + err = usb_stream_hwdep_new(card); + if (err < 0) { + /* release the midi resources */ + struct list_head *p; + + list_for_each(p, &us122l->midi_list) + snd_usbmidi_disconnect(p); + + goto stop; + } + return true; + +stop: + us122l_stop(us122l); + return false; +} + +static void snd_us122l_free(struct snd_card *card) +{ + struct us122l *us122l = US122L(card); + int index = us122l->card_index; + + if (index >= 0 && index < SNDRV_CARDS) + snd_us122l_card_used[index] = 0; +} + +static int usx2y_create_card(struct usb_device *device, + struct usb_interface *intf, + struct snd_card **cardp, + unsigned long flags) +{ + int dev; + struct snd_card *card; + int err; + + for (dev = 0; dev < SNDRV_CARDS; ++dev) + if (enable[dev] && !snd_us122l_card_used[dev]) + break; + if (dev >= SNDRV_CARDS) + return -ENODEV; + err = snd_card_new(&intf->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct us122l), &card); + if (err < 0) + return err; + snd_us122l_card_used[US122L(card)->card_index = dev] = 1; + card->private_free = snd_us122l_free; + US122L(card)->dev = device; + mutex_init(&US122L(card)->mutex); + init_waitqueue_head(&US122L(card)->sk.sleep); + US122L(card)->is_us144 = flags & US122L_FLAG_US144; + INIT_LIST_HEAD(&US122L(card)->midi_list); + strcpy(card->driver, "USB "NAME_ALLCAPS""); + sprintf(card->shortname, "TASCAM "NAME_ALLCAPS""); + sprintf(card->longname, "%s (%x:%x if %d at %03d/%03d)", + card->shortname, + le16_to_cpu(device->descriptor.idVendor), + le16_to_cpu(device->descriptor.idProduct), + 0, + US122L(card)->dev->bus->busnum, + US122L(card)->dev->devnum + ); + *cardp = card; + return 0; +} + +static int us122l_usb_probe(struct usb_interface *intf, + const struct usb_device_id *device_id, + struct snd_card **cardp) +{ + struct usb_device *device = interface_to_usbdev(intf); + struct snd_card *card; + int err; + + err = usx2y_create_card(device, intf, &card, device_id->driver_info); + if (err < 0) + return err; + + if (!us122l_create_card(card)) { + snd_card_free(card); + return -EINVAL; + } + + err = snd_card_register(card); + if (err < 0) { + snd_card_free(card); + return err; + } + + usb_get_intf(usb_ifnum_to_if(device, 0)); + usb_get_dev(device); + *cardp = card; + return 0; +} + +static int snd_us122l_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *device = interface_to_usbdev(intf); + struct snd_card *card; + int err; + + if (id->driver_info & US122L_FLAG_US144 && + device->speed == USB_SPEED_HIGH) { + snd_printk(KERN_ERR "disable ehci-hcd to run US-144\n"); + return -ENODEV; + } + + snd_printdd(KERN_DEBUG"%p:%i\n", + intf, intf->cur_altsetting->desc.bInterfaceNumber); + if (intf->cur_altsetting->desc.bInterfaceNumber != 1) + return 0; + + err = us122l_usb_probe(usb_get_intf(intf), id, &card); + if (err < 0) { + usb_put_intf(intf); + return err; + } + + usb_set_intfdata(intf, card); + return 0; +} + +static void snd_us122l_disconnect(struct usb_interface *intf) +{ + struct snd_card *card; + struct us122l *us122l; + struct list_head *p; + + card = usb_get_intfdata(intf); + if (!card) + return; + + snd_card_disconnect(card); + + us122l = US122L(card); + mutex_lock(&us122l->mutex); + us122l_stop(us122l); + mutex_unlock(&us122l->mutex); + + /* release the midi resources */ + list_for_each(p, &us122l->midi_list) { + snd_usbmidi_disconnect(p); + } + + usb_put_intf(usb_ifnum_to_if(us122l->dev, 0)); + usb_put_intf(usb_ifnum_to_if(us122l->dev, 1)); + usb_put_dev(us122l->dev); + + while (atomic_read(&us122l->mmap_count)) + msleep(500); + + snd_card_free(card); +} + +static int snd_us122l_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct snd_card *card; + struct us122l *us122l; + struct list_head *p; + + card = usb_get_intfdata(intf); + if (!card) + return 0; + snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); + + us122l = US122L(card); + if (!us122l) + return 0; + + list_for_each(p, &us122l->midi_list) + snd_usbmidi_input_stop(p); + + mutex_lock(&us122l->mutex); + usb_stream_stop(&us122l->sk); + mutex_unlock(&us122l->mutex); + + return 0; +} + +static int snd_us122l_resume(struct usb_interface *intf) +{ + struct snd_card *card; + struct us122l *us122l; + struct list_head *p; + int err; + + card = usb_get_intfdata(intf); + if (!card) + return 0; + + us122l = US122L(card); + if (!us122l) + return 0; + + mutex_lock(&us122l->mutex); + /* needed, doesn't restart without: */ + if (us122l->is_us144) { + err = usb_set_interface(us122l->dev, 0, 1); + if (err) { + snd_printk(KERN_ERR "usb_set_interface error\n"); + goto unlock; + } + } + err = usb_set_interface(us122l->dev, 1, 1); + if (err) { + snd_printk(KERN_ERR "usb_set_interface error\n"); + goto unlock; + } + + pt_info_set(us122l->dev, 0x11); + pt_info_set(us122l->dev, 0x10); + + err = us122l_set_sample_rate(us122l->dev, + us122l->sk.s->cfg.sample_rate); + if (err < 0) { + snd_printk(KERN_ERR "us122l_set_sample_rate error\n"); + goto unlock; + } + err = usb_stream_start(&us122l->sk); + if (err) + goto unlock; + + list_for_each(p, &us122l->midi_list) + snd_usbmidi_input_start(p); +unlock: + mutex_unlock(&us122l->mutex); + snd_power_change_state(card, SNDRV_CTL_POWER_D0); + return err; +} + +static const struct usb_device_id snd_us122l_usb_id_table[] = { + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x0644, + .idProduct = USB_ID_US122L + }, + { /* US-144 only works at USB1.1! Disable module ehci-hcd. */ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x0644, + .idProduct = USB_ID_US144, + .driver_info = US122L_FLAG_US144 + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x0644, + .idProduct = USB_ID_US122MKII + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x0644, + .idProduct = USB_ID_US144MKII, + .driver_info = US122L_FLAG_US144 + }, + { /* terminator */ } +}; +MODULE_DEVICE_TABLE(usb, snd_us122l_usb_id_table); + +static struct usb_driver snd_us122l_usb_driver = { + .name = "snd-usb-us122l", + .probe = snd_us122l_probe, + .disconnect = snd_us122l_disconnect, + .suspend = snd_us122l_suspend, + .resume = snd_us122l_resume, + .reset_resume = snd_us122l_resume, + .id_table = snd_us122l_usb_id_table, + .supports_autosuspend = 1 +}; + +module_usb_driver(snd_us122l_usb_driver); diff --git a/sound/usb/usx2y/us122l.h b/sound/usb/usx2y/us122l.h new file mode 100644 index 0000000000..c32ae5e981 --- /dev/null +++ b/sound/usb/usx2y/us122l.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef US122L_H +#define US122L_H + + +struct us122l { + struct usb_device *dev; + int card_index; + int stride; + struct usb_stream_kernel sk; + + struct mutex mutex; + struct file *first; + unsigned int second_periods_polled; + struct file *master; + struct file *slave; + struct list_head midi_list; + + atomic_t mmap_count; + + bool is_us144; +}; + + +#define US122L(c) ((struct us122l *)(c)->private_data) + +#define NAME_ALLCAPS "US-122L" + +#define USB_ID_US122L 0x800E +#define USB_ID_US144 0x800F +#define USB_ID_US122MKII 0x8021 +#define USB_ID_US144MKII 0x8020 + +#endif diff --git a/sound/usb/usx2y/usX2Yhwdep.c b/sound/usb/usx2y/usX2Yhwdep.c new file mode 100644 index 0000000000..4937ede0b5 --- /dev/null +++ b/sound/usb/usx2y/usX2Yhwdep.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for Tascam US-X2Y USB soundcards + * + * FPGA Loader + ALSA Startup + * + * Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de> + */ + +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/usb.h> +#include <sound/core.h> +#include <sound/memalloc.h> +#include <sound/pcm.h> +#include <sound/hwdep.h> +#include "usx2y.h" +#include "usbusx2y.h" +#include "usX2Yhwdep.h" + +static vm_fault_t snd_us428ctls_vm_fault(struct vm_fault *vmf) +{ + unsigned long offset; + struct page *page; + void *vaddr; + + snd_printdd("ENTER, start %lXh, pgoff %ld\n", + vmf->vma->vm_start, + vmf->pgoff); + + offset = vmf->pgoff << PAGE_SHIFT; + vaddr = (char *)((struct usx2ydev *)vmf->vma->vm_private_data)->us428ctls_sharedmem + offset; + page = virt_to_page(vaddr); + get_page(page); + vmf->page = page; + + snd_printdd("vaddr=%p made us428ctls_vm_fault() page %p\n", + vaddr, page); + + return 0; +} + +static const struct vm_operations_struct us428ctls_vm_ops = { + .fault = snd_us428ctls_vm_fault, +}; + +static int snd_us428ctls_mmap(struct snd_hwdep *hw, struct file *filp, struct vm_area_struct *area) +{ + unsigned long size = (unsigned long)(area->vm_end - area->vm_start); + struct usx2ydev *us428 = hw->private_data; + + // FIXME this hwdep interface is used twice: fpga download and mmap for controlling Lights etc. Maybe better using 2 hwdep devs? + // so as long as the device isn't fully initialised yet we return -EBUSY here. + if (!(us428->chip_status & USX2Y_STAT_CHIP_INIT)) + return -EBUSY; + + /* if userspace tries to mmap beyond end of our buffer, fail */ + if (size > US428_SHAREDMEM_PAGES) { + snd_printd("%lu > %lu\n", size, (unsigned long)US428_SHAREDMEM_PAGES); + return -EINVAL; + } + + area->vm_ops = &us428ctls_vm_ops; + vm_flags_set(area, VM_DONTEXPAND | VM_DONTDUMP); + area->vm_private_data = hw->private_data; + return 0; +} + +static __poll_t snd_us428ctls_poll(struct snd_hwdep *hw, struct file *file, poll_table *wait) +{ + __poll_t mask = 0; + struct usx2ydev *us428 = hw->private_data; + struct us428ctls_sharedmem *shm = us428->us428ctls_sharedmem; + + if (us428->chip_status & USX2Y_STAT_CHIP_HUP) + return EPOLLHUP; + + poll_wait(file, &us428->us428ctls_wait_queue_head, wait); + + if (shm && shm->ctl_snapshot_last != shm->ctl_snapshot_red) + mask |= EPOLLIN; + + return mask; +} + + +static int snd_usx2y_hwdep_dsp_status(struct snd_hwdep *hw, + struct snd_hwdep_dsp_status *info) +{ + static const char * const type_ids[USX2Y_TYPE_NUMS] = { + [USX2Y_TYPE_122] = "us122", + [USX2Y_TYPE_224] = "us224", + [USX2Y_TYPE_428] = "us428", + }; + struct usx2ydev *us428 = hw->private_data; + int id = -1; + + switch (le16_to_cpu(us428->dev->descriptor.idProduct)) { + case USB_ID_US122: + id = USX2Y_TYPE_122; + break; + case USB_ID_US224: + id = USX2Y_TYPE_224; + break; + case USB_ID_US428: + id = USX2Y_TYPE_428; + break; + } + if (id < 0) + return -ENODEV; + strcpy(info->id, type_ids[id]); + info->num_dsps = 2; // 0: Prepad Data, 1: FPGA Code + if (us428->chip_status & USX2Y_STAT_CHIP_INIT) + info->chip_ready = 1; + info->version = USX2Y_DRIVER_VERSION; + return 0; +} + +static int usx2y_create_usbmidi(struct snd_card *card) +{ + static const struct snd_usb_midi_endpoint_info quirk_data_1 = { + .out_ep = 0x06, + .in_ep = 0x06, + .out_cables = 0x001, + .in_cables = 0x001 + }; + static const struct snd_usb_audio_quirk quirk_1 = { + .vendor_name = "TASCAM", + .product_name = NAME_ALLCAPS, + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = &quirk_data_1 + }; + static const struct snd_usb_midi_endpoint_info quirk_data_2 = { + .out_ep = 0x06, + .in_ep = 0x06, + .out_cables = 0x003, + .in_cables = 0x003 + }; + static const struct snd_usb_audio_quirk quirk_2 = { + .vendor_name = "TASCAM", + .product_name = "US428", + .ifnum = 0, + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = &quirk_data_2 + }; + struct usb_device *dev = usx2y(card)->dev; + struct usb_interface *iface = usb_ifnum_to_if(dev, 0); + const struct snd_usb_audio_quirk *quirk = + le16_to_cpu(dev->descriptor.idProduct) == USB_ID_US428 ? + &quirk_2 : &quirk_1; + + snd_printdd("%s\n", __func__); + return snd_usbmidi_create(card, iface, &usx2y(card)->midi_list, quirk); +} + +static int usx2y_create_alsa_devices(struct snd_card *card) +{ + int err; + + err = usx2y_create_usbmidi(card); + if (err < 0) { + snd_printk(KERN_ERR "%s: usx2y_create_usbmidi error %i\n", __func__, err); + return err; + } + err = usx2y_audio_create(card); + if (err < 0) + return err; + err = usx2y_hwdep_pcm_new(card); + if (err < 0) + return err; + err = snd_card_register(card); + if (err < 0) + return err; + return 0; +} + +static int snd_usx2y_hwdep_dsp_load(struct snd_hwdep *hw, + struct snd_hwdep_dsp_image *dsp) +{ + struct usx2ydev *priv = hw->private_data; + struct usb_device *dev = priv->dev; + int lret, err; + char *buf; + + snd_printdd("dsp_load %s\n", dsp->name); + + buf = memdup_user(dsp->image, dsp->length); + if (IS_ERR(buf)) + return PTR_ERR(buf); + + err = usb_set_interface(dev, 0, 1); + if (err) + snd_printk(KERN_ERR "usb_set_interface error\n"); + else + err = usb_bulk_msg(dev, usb_sndbulkpipe(dev, 2), buf, dsp->length, &lret, 6000); + kfree(buf); + if (err) + return err; + if (dsp->index == 1) { + msleep(250); // give the device some time + err = usx2y_async_seq04_init(priv); + if (err) { + snd_printk(KERN_ERR "usx2y_async_seq04_init error\n"); + return err; + } + err = usx2y_in04_init(priv); + if (err) { + snd_printk(KERN_ERR "usx2y_in04_init error\n"); + return err; + } + err = usx2y_create_alsa_devices(hw->card); + if (err) { + snd_printk(KERN_ERR "usx2y_create_alsa_devices error %i\n", err); + return err; + } + priv->chip_status |= USX2Y_STAT_CHIP_INIT; + snd_printdd("%s: alsa all started\n", hw->name); + } + return err; +} + +int usx2y_hwdep_new(struct snd_card *card, struct usb_device *device) +{ + int err; + struct snd_hwdep *hw; + struct usx2ydev *us428 = usx2y(card); + + err = snd_hwdep_new(card, SND_USX2Y_LOADER_ID, 0, &hw); + if (err < 0) + return err; + + hw->iface = SNDRV_HWDEP_IFACE_USX2Y; + hw->private_data = us428; + hw->ops.dsp_status = snd_usx2y_hwdep_dsp_status; + hw->ops.dsp_load = snd_usx2y_hwdep_dsp_load; + hw->ops.mmap = snd_us428ctls_mmap; + hw->ops.poll = snd_us428ctls_poll; + hw->exclusive = 1; + sprintf(hw->name, "/dev/bus/usb/%03d/%03d", device->bus->busnum, device->devnum); + + us428->us428ctls_sharedmem = alloc_pages_exact(US428_SHAREDMEM_PAGES, GFP_KERNEL); + if (!us428->us428ctls_sharedmem) + return -ENOMEM; + memset(us428->us428ctls_sharedmem, -1, US428_SHAREDMEM_PAGES); + us428->us428ctls_sharedmem->ctl_snapshot_last = -2; + + return 0; +} diff --git a/sound/usb/usx2y/usX2Yhwdep.h b/sound/usb/usx2y/usX2Yhwdep.h new file mode 100644 index 0000000000..0c9946d9cd --- /dev/null +++ b/sound/usb/usx2y/usX2Yhwdep.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef USX2YHWDEP_H +#define USX2YHWDEP_H + +int usx2y_hwdep_new(struct snd_card *card, struct usb_device *device); + +#endif diff --git a/sound/usb/usx2y/usb_stream.c b/sound/usb/usx2y/usb_stream.c new file mode 100644 index 0000000000..a4d32e8a1d --- /dev/null +++ b/sound/usb/usx2y/usb_stream.c @@ -0,0 +1,782 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007, 2008 Karsten Wiese <fzu@wemgehoertderstaat.de> + */ + +#include <linux/usb.h> +#include <linux/gfp.h> + +#include "usb_stream.h" + +/* setup */ + +static unsigned int usb_stream_next_packet_size(struct usb_stream_kernel *sk) +{ + struct usb_stream *s = sk->s; + + sk->out_phase_peeked = (sk->out_phase & 0xffff) + sk->freqn; + return (sk->out_phase_peeked >> 16) * s->cfg.frame_size; +} + +static void playback_prep_freqn(struct usb_stream_kernel *sk, struct urb *urb) +{ + struct usb_stream *s = sk->s; + int pack, lb = 0; + + for (pack = 0; pack < sk->n_o_ps; pack++) { + int l = usb_stream_next_packet_size(sk); + + if (s->idle_outsize + lb + l > s->period_size) + goto check; + + sk->out_phase = sk->out_phase_peeked; + urb->iso_frame_desc[pack].offset = lb; + urb->iso_frame_desc[pack].length = l; + lb += l; + } + snd_printdd(KERN_DEBUG "%i\n", lb); + +check: + urb->number_of_packets = pack; + urb->transfer_buffer_length = lb; + s->idle_outsize += lb - s->period_size; + snd_printdd(KERN_DEBUG "idle=%i ul=%i ps=%i\n", s->idle_outsize, + lb, s->period_size); +} + +static int init_pipe_urbs(struct usb_stream_kernel *sk, + unsigned int use_packsize, + struct urb **urbs, char *transfer, + struct usb_device *dev, int pipe) +{ + int u, p; + int maxpacket = use_packsize ? + use_packsize : usb_maxpacket(dev, pipe); + int transfer_length = maxpacket * sk->n_o_ps; + + for (u = 0; u < USB_STREAM_NURBS; + ++u, transfer += transfer_length) { + struct urb *urb = urbs[u]; + struct usb_iso_packet_descriptor *desc; + + urb->transfer_buffer = transfer; + urb->dev = dev; + urb->pipe = pipe; + urb->number_of_packets = sk->n_o_ps; + urb->context = sk; + urb->interval = 1; + if (usb_pipeout(pipe)) + continue; + if (usb_urb_ep_type_check(urb)) + return -EINVAL; + + urb->transfer_buffer_length = transfer_length; + desc = urb->iso_frame_desc; + desc->offset = 0; + desc->length = maxpacket; + for (p = 1; p < sk->n_o_ps; ++p) { + desc[p].offset = desc[p - 1].offset + maxpacket; + desc[p].length = maxpacket; + } + } + + return 0; +} + +static int init_urbs(struct usb_stream_kernel *sk, unsigned int use_packsize, + struct usb_device *dev, int in_pipe, int out_pipe) +{ + struct usb_stream *s = sk->s; + char *indata = + (char *)s + sizeof(*s) + sizeof(struct usb_stream_packet) * s->inpackets; + int u; + + for (u = 0; u < USB_STREAM_NURBS; ++u) { + sk->inurb[u] = usb_alloc_urb(sk->n_o_ps, GFP_KERNEL); + if (!sk->inurb[u]) + return -ENOMEM; + + sk->outurb[u] = usb_alloc_urb(sk->n_o_ps, GFP_KERNEL); + if (!sk->outurb[u]) + return -ENOMEM; + } + + if (init_pipe_urbs(sk, use_packsize, sk->inurb, indata, dev, in_pipe) || + init_pipe_urbs(sk, use_packsize, sk->outurb, sk->write_page, dev, + out_pipe)) + return -EINVAL; + + return 0; +} + +/* + * convert a sampling rate into our full speed format (fs/1000 in Q16.16) + * this will overflow at approx 524 kHz + */ +static inline unsigned int get_usb_full_speed_rate(unsigned int rate) +{ + return ((rate << 13) + 62) / 125; +} + +/* + * convert a sampling rate into USB high speed format (fs/8000 in Q16.16) + * this will overflow at approx 4 MHz + */ +static inline unsigned int get_usb_high_speed_rate(unsigned int rate) +{ + return ((rate << 10) + 62) / 125; +} + +void usb_stream_free(struct usb_stream_kernel *sk) +{ + struct usb_stream *s; + unsigned int u; + + for (u = 0; u < USB_STREAM_NURBS; ++u) { + usb_free_urb(sk->inurb[u]); + sk->inurb[u] = NULL; + usb_free_urb(sk->outurb[u]); + sk->outurb[u] = NULL; + } + + s = sk->s; + if (!s) + return; + + if (sk->write_page) { + free_pages_exact(sk->write_page, s->write_size); + sk->write_page = NULL; + } + + free_pages_exact(s, s->read_size); + sk->s = NULL; +} + +struct usb_stream *usb_stream_new(struct usb_stream_kernel *sk, + struct usb_device *dev, + unsigned int in_endpoint, + unsigned int out_endpoint, + unsigned int sample_rate, + unsigned int use_packsize, + unsigned int period_frames, + unsigned int frame_size) +{ + int packets, max_packsize; + int in_pipe, out_pipe; + int read_size = sizeof(struct usb_stream); + int write_size; + int usb_frames = dev->speed == USB_SPEED_HIGH ? 8000 : 1000; + + in_pipe = usb_rcvisocpipe(dev, in_endpoint); + out_pipe = usb_sndisocpipe(dev, out_endpoint); + + max_packsize = use_packsize ? + use_packsize : usb_maxpacket(dev, in_pipe); + + /* + t_period = period_frames / sample_rate + iso_packs = t_period / t_iso_frame + = (period_frames / sample_rate) * (1 / t_iso_frame) + */ + + packets = period_frames * usb_frames / sample_rate + 1; + + if (dev->speed == USB_SPEED_HIGH) + packets = (packets + 7) & ~7; + + read_size += packets * USB_STREAM_URBDEPTH * + (max_packsize + sizeof(struct usb_stream_packet)); + + max_packsize = usb_maxpacket(dev, out_pipe); + write_size = max_packsize * packets * USB_STREAM_URBDEPTH; + + if (read_size >= 256*PAGE_SIZE || write_size >= 256*PAGE_SIZE) { + snd_printk(KERN_WARNING "a size exceeds 128*PAGE_SIZE\n"); + goto out; + } + + sk->s = alloc_pages_exact(read_size, + GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN); + if (!sk->s) { + pr_warn("us122l: couldn't allocate read buffer\n"); + goto out; + } + sk->s->cfg.version = USB_STREAM_INTERFACE_VERSION; + + sk->s->read_size = read_size; + + sk->s->cfg.sample_rate = sample_rate; + sk->s->cfg.frame_size = frame_size; + sk->n_o_ps = packets; + sk->s->inpackets = packets * USB_STREAM_URBDEPTH; + sk->s->cfg.period_frames = period_frames; + sk->s->period_size = frame_size * period_frames; + + sk->s->write_size = write_size; + + sk->write_page = alloc_pages_exact(write_size, + GFP_KERNEL | __GFP_ZERO | __GFP_NOWARN); + if (!sk->write_page) { + pr_warn("us122l: couldn't allocate write buffer\n"); + usb_stream_free(sk); + return NULL; + } + + /* calculate the frequency in 16.16 format */ + if (dev->speed == USB_SPEED_FULL) + sk->freqn = get_usb_full_speed_rate(sample_rate); + else + sk->freqn = get_usb_high_speed_rate(sample_rate); + + if (init_urbs(sk, use_packsize, dev, in_pipe, out_pipe) < 0) { + usb_stream_free(sk); + return NULL; + } + + sk->s->state = usb_stream_stopped; +out: + return sk->s; +} + +/* start */ + +static bool balance_check(struct usb_stream_kernel *sk, struct urb *urb) +{ + bool r; + + if (unlikely(urb->status)) { + if (urb->status != -ESHUTDOWN && urb->status != -ENOENT) + snd_printk(KERN_WARNING "status=%i\n", urb->status); + sk->iso_frame_balance = 0x7FFFFFFF; + return false; + } + r = sk->iso_frame_balance == 0; + if (!r) + sk->i_urb = urb; + return r; +} + +static bool balance_playback(struct usb_stream_kernel *sk, struct urb *urb) +{ + sk->iso_frame_balance += urb->number_of_packets; + return balance_check(sk, urb); +} + +static bool balance_capture(struct usb_stream_kernel *sk, struct urb *urb) +{ + sk->iso_frame_balance -= urb->number_of_packets; + return balance_check(sk, urb); +} + +static void subs_set_complete(struct urb **urbs, void (*complete)(struct urb *)) +{ + int u; + + for (u = 0; u < USB_STREAM_NURBS; u++) { + struct urb *urb = urbs[u]; + + urb->complete = complete; + } +} + +static int usb_stream_prepare_playback(struct usb_stream_kernel *sk, + struct urb *inurb) +{ + struct usb_stream *s = sk->s; + struct urb *io; + struct usb_iso_packet_descriptor *id, *od; + int p = 0, lb = 0, l = 0; + + io = sk->idle_outurb; + od = io->iso_frame_desc; + + for (; s->sync_packet < 0; ++p, ++s->sync_packet) { + struct urb *ii = sk->completed_inurb; + + id = ii->iso_frame_desc + + ii->number_of_packets + s->sync_packet; + l = id->actual_length; + + od[p].length = l; + od[p].offset = lb; + lb += l; + } + + for (; + s->sync_packet < inurb->number_of_packets && p < sk->n_o_ps; + ++p, ++s->sync_packet) { + l = inurb->iso_frame_desc[s->sync_packet].actual_length; + + if (s->idle_outsize + lb + l > s->period_size) + goto check_ok; + + od[p].length = l; + od[p].offset = lb; + lb += l; + } + +check_ok: + s->sync_packet -= inurb->number_of_packets; + if (unlikely(s->sync_packet < -2 || s->sync_packet > 0)) { + snd_printk(KERN_WARNING "invalid sync_packet = %i;" + " p=%i nop=%i %i %x %x %x > %x\n", + s->sync_packet, p, inurb->number_of_packets, + s->idle_outsize + lb + l, + s->idle_outsize, lb, l, + s->period_size); + return -1; + } + if (unlikely(lb % s->cfg.frame_size)) { + snd_printk(KERN_WARNING"invalid outsize = %i\n", + lb); + return -1; + } + s->idle_outsize += lb - s->period_size; + io->number_of_packets = p; + io->transfer_buffer_length = lb; + if (s->idle_outsize <= 0) + return 0; + + snd_printk(KERN_WARNING "idle=%i\n", s->idle_outsize); + return -1; +} + +static void prepare_inurb(int number_of_packets, struct urb *iu) +{ + struct usb_iso_packet_descriptor *id; + int p; + + iu->number_of_packets = number_of_packets; + id = iu->iso_frame_desc; + id->offset = 0; + for (p = 0; p < iu->number_of_packets - 1; ++p) + id[p + 1].offset = id[p].offset + id[p].length; + + iu->transfer_buffer_length = + id[0].length * iu->number_of_packets; +} + +static int submit_urbs(struct usb_stream_kernel *sk, + struct urb *inurb, struct urb *outurb) +{ + int err; + + prepare_inurb(sk->idle_outurb->number_of_packets, sk->idle_inurb); + err = usb_submit_urb(sk->idle_inurb, GFP_ATOMIC); + if (err < 0) + goto report_failure; + + sk->idle_inurb = sk->completed_inurb; + sk->completed_inurb = inurb; + err = usb_submit_urb(sk->idle_outurb, GFP_ATOMIC); + if (err < 0) + goto report_failure; + + sk->idle_outurb = sk->completed_outurb; + sk->completed_outurb = outurb; + return 0; + +report_failure: + snd_printk(KERN_ERR "%i\n", err); + return err; +} + +#ifdef DEBUG_LOOP_BACK +/* + This loop_back() shows how to read/write the period data. + */ +static void loop_back(struct usb_stream *s) +{ + char *i, *o; + int il, ol, l, p; + struct urb *iu; + struct usb_iso_packet_descriptor *id; + + o = s->playback1st_to; + ol = s->playback1st_size; + l = 0; + + if (s->insplit_pack >= 0) { + iu = sk->idle_inurb; + id = iu->iso_frame_desc; + p = s->insplit_pack; + } else + goto second; +loop: + for (; p < iu->number_of_packets && l < s->period_size; ++p) { + i = iu->transfer_buffer + id[p].offset; + il = id[p].actual_length; + if (l + il > s->period_size) + il = s->period_size - l; + if (il <= ol) { + memcpy(o, i, il); + o += il; + ol -= il; + } else { + memcpy(o, i, ol); + singen_6pack(o, ol); + o = s->playback_to; + memcpy(o, i + ol, il - ol); + o += il - ol; + ol = s->period_size - s->playback1st_size; + } + l += il; + } + if (iu == sk->completed_inurb) { + if (l != s->period_size) + printk(KERN_DEBUG"%s:%i %i\n", __func__, __LINE__, + l/(int)s->cfg.frame_size); + + return; + } +second: + iu = sk->completed_inurb; + id = iu->iso_frame_desc; + p = 0; + goto loop; + +} +#else +static void loop_back(struct usb_stream *s) +{ +} +#endif + +static void stream_idle(struct usb_stream_kernel *sk, + struct urb *inurb, struct urb *outurb) +{ + struct usb_stream *s = sk->s; + int l, p; + int insize = s->idle_insize; + int urb_size = 0; + + s->inpacket_split = s->next_inpacket_split; + s->inpacket_split_at = s->next_inpacket_split_at; + s->next_inpacket_split = -1; + s->next_inpacket_split_at = 0; + + for (p = 0; p < inurb->number_of_packets; ++p) { + struct usb_iso_packet_descriptor *id = inurb->iso_frame_desc; + + l = id[p].actual_length; + if (unlikely(l == 0 || id[p].status)) { + snd_printk(KERN_WARNING "underrun, status=%u\n", + id[p].status); + goto err_out; + } + s->inpacket_head++; + s->inpacket_head %= s->inpackets; + if (s->inpacket_split == -1) + s->inpacket_split = s->inpacket_head; + + s->inpacket[s->inpacket_head].offset = + id[p].offset + (inurb->transfer_buffer - (void *)s); + s->inpacket[s->inpacket_head].length = l; + if (insize + l > s->period_size && + s->next_inpacket_split == -1) { + s->next_inpacket_split = s->inpacket_head; + s->next_inpacket_split_at = s->period_size - insize; + } + insize += l; + urb_size += l; + } + s->idle_insize += urb_size - s->period_size; + if (s->idle_insize < 0) { + snd_printk(KERN_WARNING "%i\n", + (s->idle_insize)/(int)s->cfg.frame_size); + goto err_out; + } + s->insize_done += urb_size; + + l = s->idle_outsize; + s->outpacket[0].offset = (sk->idle_outurb->transfer_buffer - + sk->write_page) - l; + + if (usb_stream_prepare_playback(sk, inurb) < 0) + goto err_out; + + s->outpacket[0].length = sk->idle_outurb->transfer_buffer_length + l; + s->outpacket[1].offset = sk->completed_outurb->transfer_buffer - + sk->write_page; + + if (submit_urbs(sk, inurb, outurb) < 0) + goto err_out; + + loop_back(s); + s->periods_done++; + wake_up_all(&sk->sleep); + return; +err_out: + s->state = usb_stream_xrun; + wake_up_all(&sk->sleep); +} + +static void i_capture_idle(struct urb *urb) +{ + struct usb_stream_kernel *sk = urb->context; + + if (balance_capture(sk, urb)) + stream_idle(sk, urb, sk->i_urb); +} + +static void i_playback_idle(struct urb *urb) +{ + struct usb_stream_kernel *sk = urb->context; + + if (balance_playback(sk, urb)) + stream_idle(sk, sk->i_urb, urb); +} + +static void stream_start(struct usb_stream_kernel *sk, + struct urb *inurb, struct urb *outurb) +{ + struct usb_stream *s = sk->s; + + if (s->state >= usb_stream_sync1) { + int l, p, max_diff, max_diff_0; + int urb_size = 0; + unsigned int frames_per_packet, min_frames = 0; + + frames_per_packet = (s->period_size - s->idle_insize); + frames_per_packet <<= 8; + frames_per_packet /= + s->cfg.frame_size * inurb->number_of_packets; + frames_per_packet++; + + max_diff_0 = s->cfg.frame_size; + if (s->cfg.period_frames >= 256) + max_diff_0 <<= 1; + if (s->cfg.period_frames >= 1024) + max_diff_0 <<= 1; + max_diff = max_diff_0; + for (p = 0; p < inurb->number_of_packets; ++p) { + int diff; + + l = inurb->iso_frame_desc[p].actual_length; + urb_size += l; + + min_frames += frames_per_packet; + diff = urb_size - + (min_frames >> 8) * s->cfg.frame_size; + if (diff < max_diff) { + snd_printdd(KERN_DEBUG "%i %i %i %i\n", + s->insize_done, + urb_size / (int)s->cfg.frame_size, + inurb->number_of_packets, diff); + max_diff = diff; + } + } + s->idle_insize -= max_diff - max_diff_0; + s->idle_insize += urb_size - s->period_size; + if (s->idle_insize < 0) { + snd_printk(KERN_WARNING "%i %i %i\n", + s->idle_insize, urb_size, s->period_size); + return; + } else if (s->idle_insize == 0) { + s->next_inpacket_split = + (s->inpacket_head + 1) % s->inpackets; + s->next_inpacket_split_at = 0; + } else { + unsigned int split = s->inpacket_head; + + l = s->idle_insize; + while (l > s->inpacket[split].length) { + l -= s->inpacket[split].length; + if (split == 0) + split = s->inpackets - 1; + else + split--; + } + s->next_inpacket_split = split; + s->next_inpacket_split_at = + s->inpacket[split].length - l; + } + + s->insize_done += urb_size; + + if (usb_stream_prepare_playback(sk, inurb) < 0) + return; + + } else + playback_prep_freqn(sk, sk->idle_outurb); + + if (submit_urbs(sk, inurb, outurb) < 0) + return; + + if (s->state == usb_stream_sync1 && s->insize_done > 360000) { + /* just guesswork ^^^^^^ */ + s->state = usb_stream_ready; + subs_set_complete(sk->inurb, i_capture_idle); + subs_set_complete(sk->outurb, i_playback_idle); + } +} + +static void i_capture_start(struct urb *urb) +{ + struct usb_iso_packet_descriptor *id = urb->iso_frame_desc; + struct usb_stream_kernel *sk = urb->context; + struct usb_stream *s = sk->s; + int p; + int empty = 0; + + if (urb->status) { + snd_printk(KERN_WARNING "status=%i\n", urb->status); + return; + } + + for (p = 0; p < urb->number_of_packets; ++p) { + int l = id[p].actual_length; + + if (l < s->cfg.frame_size) { + ++empty; + if (s->state >= usb_stream_sync0) { + snd_printk(KERN_WARNING "%i\n", l); + return; + } + } + s->inpacket_head++; + s->inpacket_head %= s->inpackets; + s->inpacket[s->inpacket_head].offset = + id[p].offset + (urb->transfer_buffer - (void *)s); + s->inpacket[s->inpacket_head].length = l; + } +#ifdef SHOW_EMPTY + if (empty) { + printk(KERN_DEBUG"%s:%i: %i", __func__, __LINE__, + urb->iso_frame_desc[0].actual_length); + for (pack = 1; pack < urb->number_of_packets; ++pack) { + int l = urb->iso_frame_desc[pack].actual_length; + + printk(KERN_CONT " %i", l); + } + printk(KERN_CONT "\n"); + } +#endif + if (!empty && s->state < usb_stream_sync1) + ++s->state; + + if (balance_capture(sk, urb)) + stream_start(sk, urb, sk->i_urb); +} + +static void i_playback_start(struct urb *urb) +{ + struct usb_stream_kernel *sk = urb->context; + + if (balance_playback(sk, urb)) + stream_start(sk, sk->i_urb, urb); +} + +int usb_stream_start(struct usb_stream_kernel *sk) +{ + struct usb_stream *s = sk->s; + int frame = 0, iters = 0; + int u, err; + int try = 0; + + if (s->state != usb_stream_stopped) + return -EAGAIN; + + subs_set_complete(sk->inurb, i_capture_start); + subs_set_complete(sk->outurb, i_playback_start); + memset(sk->write_page, 0, s->write_size); +dotry: + s->insize_done = 0; + s->idle_insize = 0; + s->idle_outsize = 0; + s->sync_packet = -1; + s->inpacket_head = -1; + sk->iso_frame_balance = 0; + ++try; + for (u = 0; u < 2; u++) { + struct urb *inurb = sk->inurb[u]; + struct urb *outurb = sk->outurb[u]; + + playback_prep_freqn(sk, outurb); + inurb->number_of_packets = outurb->number_of_packets; + inurb->transfer_buffer_length = + inurb->number_of_packets * + inurb->iso_frame_desc[0].length; + + if (u == 0) { + int now; + struct usb_device *dev = inurb->dev; + + frame = usb_get_current_frame_number(dev); + do { + now = usb_get_current_frame_number(dev); + ++iters; + } while (now > -1 && now == frame); + } + err = usb_submit_urb(inurb, GFP_ATOMIC); + if (err < 0) { + snd_printk(KERN_ERR + "usb_submit_urb(sk->inurb[%i]) returned %i\n", + u, err); + return err; + } + err = usb_submit_urb(outurb, GFP_ATOMIC); + if (err < 0) { + snd_printk(KERN_ERR + "usb_submit_urb(sk->outurb[%i]) returned %i\n", + u, err); + return err; + } + + if (inurb->start_frame != outurb->start_frame) { + snd_printd(KERN_DEBUG + "u[%i] start_frames differ in:%u out:%u\n", + u, inurb->start_frame, outurb->start_frame); + goto check_retry; + } + } + snd_printdd(KERN_DEBUG "%i %i\n", frame, iters); + try = 0; +check_retry: + if (try) { + usb_stream_stop(sk); + if (try < 5) { + msleep(1500); + snd_printd(KERN_DEBUG "goto dotry;\n"); + goto dotry; + } + snd_printk(KERN_WARNING + "couldn't start all urbs on the same start_frame.\n"); + return -EFAULT; + } + + sk->idle_inurb = sk->inurb[USB_STREAM_NURBS - 2]; + sk->idle_outurb = sk->outurb[USB_STREAM_NURBS - 2]; + sk->completed_inurb = sk->inurb[USB_STREAM_NURBS - 1]; + sk->completed_outurb = sk->outurb[USB_STREAM_NURBS - 1]; + +/* wait, check */ + { + int wait_ms = 3000; + + while (s->state != usb_stream_ready && wait_ms > 0) { + snd_printdd(KERN_DEBUG "%i\n", s->state); + msleep(200); + wait_ms -= 200; + } + } + + return s->state == usb_stream_ready ? 0 : -EFAULT; +} + + +/* stop */ + +void usb_stream_stop(struct usb_stream_kernel *sk) +{ + int u; + + if (!sk->s) + return; + for (u = 0; u < USB_STREAM_NURBS; ++u) { + usb_kill_urb(sk->inurb[u]); + usb_kill_urb(sk->outurb[u]); + } + sk->s->state = usb_stream_stopped; + msleep(400); +} diff --git a/sound/usb/usx2y/usb_stream.h b/sound/usb/usx2y/usb_stream.h new file mode 100644 index 0000000000..73e57b341a --- /dev/null +++ b/sound/usb/usx2y/usb_stream.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __USB_STREAM_H +#define __USB_STREAM_H + +#include <uapi/sound/usb_stream.h> + +#define USB_STREAM_NURBS 4 +#define USB_STREAM_URBDEPTH 4 + +struct usb_stream_kernel { + struct usb_stream *s; + + void *write_page; + + unsigned int n_o_ps; + + struct urb *inurb[USB_STREAM_NURBS]; + struct urb *idle_inurb; + struct urb *completed_inurb; + struct urb *outurb[USB_STREAM_NURBS]; + struct urb *idle_outurb; + struct urb *completed_outurb; + struct urb *i_urb; + + int iso_frame_balance; + + wait_queue_head_t sleep; + + unsigned int out_phase; + unsigned int out_phase_peeked; + unsigned int freqn; +}; + +struct usb_stream *usb_stream_new(struct usb_stream_kernel *sk, + struct usb_device *dev, + unsigned int in_endpoint, + unsigned int out_endpoint, + unsigned int sample_rate, + unsigned int use_packsize, + unsigned int period_frames, + unsigned int frame_size); +void usb_stream_free(struct usb_stream_kernel *sk); +int usb_stream_start(struct usb_stream_kernel *sk); +void usb_stream_stop(struct usb_stream_kernel *sk); + +#endif /* __USB_STREAM_H */ diff --git a/sound/usb/usx2y/usbus428ctldefs.h b/sound/usb/usx2y/usbus428ctldefs.h new file mode 100644 index 0000000000..9ba15d974e --- /dev/null +++ b/sound/usb/usx2y/usbus428ctldefs.h @@ -0,0 +1,93 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * + * Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de> + */ + +enum E_IN84 { + E_FADER_0 = 0, + E_FADER_1, + E_FADER_2, + E_FADER_3, + E_FADER_4, + E_FADER_5, + E_FADER_6, + E_FADER_7, + E_FADER_M, + E_TRANSPORT, + E_MODIFIER = 10, + E_FILTER_SELECT, + E_SELECT, + E_MUTE, + + E_SWITCH = 15, + E_WHEEL_GAIN, + E_WHEEL_FREQ, + E_WHEEL_Q, + E_WHEEL_PAN, + E_WHEEL = 20 +}; + +#define T_RECORD 1 +#define T_PLAY 2 +#define T_STOP 4 +#define T_F_FWD 8 +#define T_REW 0x10 +#define T_SOLO 0x20 +#define T_REC 0x40 +#define T_NULL 0x80 + + +struct us428_ctls { + unsigned char fader[9]; + unsigned char transport; + unsigned char modifier; + unsigned char filters_elect; + unsigned char select; + unsigned char mute; + unsigned char unknown; + unsigned char wswitch; + unsigned char wheel[5]; +}; + +struct us428_set_byte { + unsigned char offset, + value; +}; + +enum { + ELT_VOLUME = 0, + ELT_LIGHT +}; + +struct usx2y_volume { + unsigned char channel, + lh, + ll, + rh, + rl; +}; + +struct us428_lights { + struct us428_set_byte light[7]; +}; + +struct us428_p4out { + char type; + union { + struct usx2y_volume vol; + struct us428_lights lights; + } val; +}; + +#define N_US428_CTL_BUFS 16 +#define N_US428_P4OUT_BUFS 16 +struct us428ctls_sharedmem { + struct us428_ctls ctl_snapshot[N_US428_CTL_BUFS]; + int ctl_snapshot_differs_at[N_US428_CTL_BUFS]; + int ctl_snapshot_last, ctl_snapshot_red; + struct us428_p4out p4out[N_US428_P4OUT_BUFS]; + int p4out_last, p4out_sent; +}; + +#define US428_SHAREDMEM_PAGES PAGE_ALIGN(sizeof(struct us428ctls_sharedmem)) diff --git a/sound/usb/usx2y/usbusx2y.c b/sound/usb/usx2y/usbusx2y.c new file mode 100644 index 0000000000..52f4e66524 --- /dev/null +++ b/sound/usb/usx2y/usbusx2y.c @@ -0,0 +1,466 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * usbusx2y.c - ALSA USB US-428 Driver + * +2005-04-14 Karsten Wiese + Version 0.8.7.2: + Call snd_card_free() instead of snd_card_free_in_thread() to prevent oops with dead keyboard symptom. + Tested ok with kernel 2.6.12-rc2. + +2004-12-14 Karsten Wiese + Version 0.8.7.1: + snd_pcm_open for rawusb pcm-devices now returns -EBUSY if called without rawusb's hwdep device being open. + +2004-12-02 Karsten Wiese + Version 0.8.7: + Use macro usb_maxpacket() for portability. + +2004-10-26 Karsten Wiese + Version 0.8.6: + wake_up() process waiting in usx2y_urbs_start() on error. + +2004-10-21 Karsten Wiese + Version 0.8.5: + nrpacks is runtime or compiletime configurable now with tested values from 1 to 4. + +2004-10-03 Karsten Wiese + Version 0.8.2: + Avoid any possible racing while in prepare callback. + +2004-09-30 Karsten Wiese + Version 0.8.0: + Simplified things and made ohci work again. + +2004-09-20 Karsten Wiese + Version 0.7.3: + Use usb_kill_urb() instead of deprecated (kernel 2.6.9) usb_unlink_urb(). + +2004-07-13 Karsten Wiese + Version 0.7.1: + Don't sleep in START/STOP callbacks anymore. + us428 channels C/D not handled just for this version, sorry. + +2004-06-21 Karsten Wiese + Version 0.6.4: + Temporarely suspend midi input + to sanely call usb_set_interface() when setting format. + +2004-06-12 Karsten Wiese + Version 0.6.3: + Made it thus the following rule is enforced: + "All pcm substreams of one usx2y have to operate at the same rate & format." + +2004-04-06 Karsten Wiese + Version 0.6.0: + Runs on 2.6.5 kernel without any "--with-debug=" things. + us224 reported running. + +2004-01-14 Karsten Wiese + Version 0.5.1: + Runs with 2.6.1 kernel. + +2003-12-30 Karsten Wiese + Version 0.4.1: + Fix 24Bit 4Channel capturing for the us428. + +2003-11-27 Karsten Wiese, Martin Langer + Version 0.4: + us122 support. + us224 could be tested by uncommenting the sections containing USB_ID_US224 + +2003-11-03 Karsten Wiese + Version 0.3: + 24Bit support. + "arecord -D hw:1 -c 2 -r 48000 -M -f S24_3LE|aplay -D hw:1 -c 2 -r 48000 -M -f S24_3LE" works. + +2003-08-22 Karsten Wiese + Version 0.0.8: + Removed EZUSB Firmware. First Stage Firmwaredownload is now done by tascam-firmware downloader. + See: + http://usb-midi-fw.sourceforge.net/tascam-firmware.tar.gz + +2003-06-18 Karsten Wiese + Version 0.0.5: + changed to compile with kernel 2.4.21 and alsa 0.9.4 + +2002-10-16 Karsten Wiese + Version 0.0.4: + compiles again with alsa-current. + USB_ISO_ASAP not used anymore (most of the time), instead + urb->start_frame is calculated here now, some calls inside usb-driver don't need to happen anymore. + + To get the best out of this: + Disable APM-support in the kernel as APM-BIOS calls (once each second) hard disable interrupt for many precious milliseconds. + This helped me much on my slowish PII 400 & PIII 500. + ACPI yet untested but might cause the same bad behaviour. + Use a kernel with lowlatency and preemptiv patches applied. + To autoload snd-usb-midi append a line + post-install snd-usb-us428 modprobe snd-usb-midi + to /etc/modules.conf. + + known problems: + sliders, knobs, lights not yet handled except MASTER Volume slider. + "pcm -c 2" doesn't work. "pcm -c 2 -m direct_interleaved" does. + KDE3: "Enable full duplex operation" deadlocks. + +2002-08-31 Karsten Wiese + Version 0.0.3: audio also simplex; + simplifying: iso urbs only 1 packet, melted structs. + ASYNC_UNLINK not used anymore: no more crashes so far..... + for alsa 0.9 rc3. + +2002-08-09 Karsten Wiese + Version 0.0.2: midi works with snd-usb-midi, audio (only fullduplex now) with i.e. bristol. + The firmware has been sniffed from win2k us-428 driver 3.09. + + * Copyright (c) 2002 - 2004 Karsten Wiese + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/usb.h> +#include <sound/core.h> +#include <sound/initval.h> +#include <sound/pcm.h> + +#include <sound/rawmidi.h> +#include "usx2y.h" +#include "usbusx2y.h" +#include "usX2Yhwdep.h" + +MODULE_AUTHOR("Karsten Wiese <annabellesgarden@yahoo.de>"); +MODULE_DESCRIPTION("TASCAM "NAME_ALLCAPS" Version 0.8.7.2"); +MODULE_LICENSE("GPL"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */ +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */ + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for "NAME_ALLCAPS"."); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string for "NAME_ALLCAPS"."); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable "NAME_ALLCAPS"."); + +static int snd_usx2y_card_used[SNDRV_CARDS]; + +static void snd_usx2y_card_private_free(struct snd_card *card); +static void usx2y_unlinkseq(struct snd_usx2y_async_seq *s); + +/* + * pipe 4 is used for switching the lamps, setting samplerate, volumes .... + */ +static void i_usx2y_out04_int(struct urb *urb) +{ +#ifdef CONFIG_SND_DEBUG + if (urb->status) { + int i; + struct usx2ydev *usx2y = urb->context; + + for (i = 0; i < 10 && usx2y->as04.urb[i] != urb; i++) + ; + snd_printdd("%s urb %i status=%i\n", __func__, i, urb->status); + } +#endif +} + +static void i_usx2y_in04_int(struct urb *urb) +{ + int err = 0; + struct usx2ydev *usx2y = urb->context; + struct us428ctls_sharedmem *us428ctls = usx2y->us428ctls_sharedmem; + struct us428_p4out *p4out; + int i, j, n, diff, send; + + usx2y->in04_int_calls++; + + if (urb->status) { + snd_printdd("Interrupt Pipe 4 came back with status=%i\n", urb->status); + return; + } + + // printk("%i:0x%02X ", 8, (int)((unsigned char*)usx2y->in04_buf)[8]); Master volume shows 0 here if fader is at max during boot ?!? + if (us428ctls) { + diff = -1; + if (us428ctls->ctl_snapshot_last == -2) { + diff = 0; + memcpy(usx2y->in04_last, usx2y->in04_buf, sizeof(usx2y->in04_last)); + us428ctls->ctl_snapshot_last = -1; + } else { + for (i = 0; i < 21; i++) { + if (usx2y->in04_last[i] != ((char *)usx2y->in04_buf)[i]) { + if (diff < 0) + diff = i; + usx2y->in04_last[i] = ((char *)usx2y->in04_buf)[i]; + } + } + } + if (diff >= 0) { + n = us428ctls->ctl_snapshot_last + 1; + if (n >= N_US428_CTL_BUFS || n < 0) + n = 0; + memcpy(us428ctls->ctl_snapshot + n, usx2y->in04_buf, sizeof(us428ctls->ctl_snapshot[0])); + us428ctls->ctl_snapshot_differs_at[n] = diff; + us428ctls->ctl_snapshot_last = n; + wake_up(&usx2y->us428ctls_wait_queue_head); + } + } + + if (usx2y->us04) { + if (!usx2y->us04->submitted) { + do { + err = usb_submit_urb(usx2y->us04->urb[usx2y->us04->submitted++], GFP_ATOMIC); + } while (!err && usx2y->us04->submitted < usx2y->us04->len); + } + } else { + if (us428ctls && us428ctls->p4out_last >= 0 && us428ctls->p4out_last < N_US428_P4OUT_BUFS) { + if (us428ctls->p4out_last != us428ctls->p4out_sent) { + send = us428ctls->p4out_sent + 1; + if (send >= N_US428_P4OUT_BUFS) + send = 0; + for (j = 0; j < URBS_ASYNC_SEQ && !err; ++j) { + if (!usx2y->as04.urb[j]->status) { + p4out = us428ctls->p4out + send; // FIXME if more than 1 p4out is new, 1 gets lost. + usb_fill_bulk_urb(usx2y->as04.urb[j], usx2y->dev, + usb_sndbulkpipe(usx2y->dev, 0x04), &p4out->val.vol, + p4out->type == ELT_LIGHT ? sizeof(struct us428_lights) : 5, + i_usx2y_out04_int, usx2y); + err = usb_submit_urb(usx2y->as04.urb[j], GFP_ATOMIC); + us428ctls->p4out_sent = send; + break; + } + } + } + } + } + + if (err) + snd_printk(KERN_ERR "in04_int() usb_submit_urb err=%i\n", err); + + urb->dev = usx2y->dev; + usb_submit_urb(urb, GFP_ATOMIC); +} + +/* + * Prepare some urbs + */ +int usx2y_async_seq04_init(struct usx2ydev *usx2y) +{ + int err = 0, i; + + if (WARN_ON(usx2y->as04.buffer)) + return -EBUSY; + + usx2y->as04.buffer = kmalloc_array(URBS_ASYNC_SEQ, + URB_DATA_LEN_ASYNC_SEQ, GFP_KERNEL); + if (!usx2y->as04.buffer) { + err = -ENOMEM; + } else { + for (i = 0; i < URBS_ASYNC_SEQ; ++i) { + usx2y->as04.urb[i] = usb_alloc_urb(0, GFP_KERNEL); + if (!usx2y->as04.urb[i]) { + err = -ENOMEM; + break; + } + usb_fill_bulk_urb(usx2y->as04.urb[i], usx2y->dev, + usb_sndbulkpipe(usx2y->dev, 0x04), + usx2y->as04.buffer + URB_DATA_LEN_ASYNC_SEQ * i, 0, + i_usx2y_out04_int, usx2y); + err = usb_urb_ep_type_check(usx2y->as04.urb[i]); + if (err < 0) + break; + } + } + if (err) + usx2y_unlinkseq(&usx2y->as04); + return err; +} + +int usx2y_in04_init(struct usx2ydev *usx2y) +{ + int err; + + if (WARN_ON(usx2y->in04_urb)) + return -EBUSY; + + usx2y->in04_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!usx2y->in04_urb) { + err = -ENOMEM; + goto error; + } + + usx2y->in04_buf = kmalloc(21, GFP_KERNEL); + if (!usx2y->in04_buf) { + err = -ENOMEM; + goto error; + } + + init_waitqueue_head(&usx2y->in04_wait_queue); + usb_fill_int_urb(usx2y->in04_urb, usx2y->dev, usb_rcvintpipe(usx2y->dev, 0x4), + usx2y->in04_buf, 21, + i_usx2y_in04_int, usx2y, + 10); + if (usb_urb_ep_type_check(usx2y->in04_urb)) { + err = -EINVAL; + goto error; + } + return usb_submit_urb(usx2y->in04_urb, GFP_KERNEL); + + error: + kfree(usx2y->in04_buf); + usb_free_urb(usx2y->in04_urb); + usx2y->in04_buf = NULL; + usx2y->in04_urb = NULL; + return err; +} + +static void usx2y_unlinkseq(struct snd_usx2y_async_seq *s) +{ + int i; + + for (i = 0; i < URBS_ASYNC_SEQ; ++i) { + if (!s->urb[i]) + continue; + usb_kill_urb(s->urb[i]); + usb_free_urb(s->urb[i]); + s->urb[i] = NULL; + } + kfree(s->buffer); + s->buffer = NULL; +} + +static const struct usb_device_id snd_usx2y_usb_id_table[] = { + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x1604, + .idProduct = USB_ID_US428 + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x1604, + .idProduct = USB_ID_US122 + }, + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE, + .idVendor = 0x1604, + .idProduct = USB_ID_US224 + }, + { /* terminator */ } +}; +MODULE_DEVICE_TABLE(usb, snd_usx2y_usb_id_table); + +static int usx2y_create_card(struct usb_device *device, + struct usb_interface *intf, + struct snd_card **cardp) +{ + int dev; + struct snd_card *card; + int err; + + for (dev = 0; dev < SNDRV_CARDS; ++dev) + if (enable[dev] && !snd_usx2y_card_used[dev]) + break; + if (dev >= SNDRV_CARDS) + return -ENODEV; + err = snd_card_new(&intf->dev, index[dev], id[dev], THIS_MODULE, + sizeof(struct usx2ydev), &card); + if (err < 0) + return err; + snd_usx2y_card_used[usx2y(card)->card_index = dev] = 1; + card->private_free = snd_usx2y_card_private_free; + usx2y(card)->dev = device; + init_waitqueue_head(&usx2y(card)->prepare_wait_queue); + init_waitqueue_head(&usx2y(card)->us428ctls_wait_queue_head); + mutex_init(&usx2y(card)->pcm_mutex); + INIT_LIST_HEAD(&usx2y(card)->midi_list); + strcpy(card->driver, "USB "NAME_ALLCAPS""); + sprintf(card->shortname, "TASCAM "NAME_ALLCAPS""); + sprintf(card->longname, "%s (%x:%x if %d at %03d/%03d)", + card->shortname, + le16_to_cpu(device->descriptor.idVendor), + le16_to_cpu(device->descriptor.idProduct), + 0,//us428(card)->usbmidi.ifnum, + usx2y(card)->dev->bus->busnum, usx2y(card)->dev->devnum); + *cardp = card; + return 0; +} + +static void snd_usx2y_card_private_free(struct snd_card *card) +{ + struct usx2ydev *usx2y = usx2y(card); + + kfree(usx2y->in04_buf); + usb_free_urb(usx2y->in04_urb); + if (usx2y->us428ctls_sharedmem) + free_pages_exact(usx2y->us428ctls_sharedmem, + US428_SHAREDMEM_PAGES); + if (usx2y->card_index >= 0 && usx2y->card_index < SNDRV_CARDS) + snd_usx2y_card_used[usx2y->card_index] = 0; +} + +static void snd_usx2y_disconnect(struct usb_interface *intf) +{ + struct snd_card *card; + struct usx2ydev *usx2y; + struct list_head *p; + + card = usb_get_intfdata(intf); + if (!card) + return; + usx2y = usx2y(card); + usx2y->chip_status = USX2Y_STAT_CHIP_HUP; + usx2y_unlinkseq(&usx2y->as04); + usb_kill_urb(usx2y->in04_urb); + snd_card_disconnect(card); + + /* release the midi resources */ + list_for_each(p, &usx2y->midi_list) { + snd_usbmidi_disconnect(p); + } + if (usx2y->us428ctls_sharedmem) + wake_up(&usx2y->us428ctls_wait_queue_head); + snd_card_free(card); +} + +static int snd_usx2y_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *device = interface_to_usbdev(intf); + struct snd_card *card; + int err; + + if (le16_to_cpu(device->descriptor.idVendor) != 0x1604 || + (le16_to_cpu(device->descriptor.idProduct) != USB_ID_US122 && + le16_to_cpu(device->descriptor.idProduct) != USB_ID_US224 && + le16_to_cpu(device->descriptor.idProduct) != USB_ID_US428)) + return -EINVAL; + + err = usx2y_create_card(device, intf, &card); + if (err < 0) + return err; + err = usx2y_hwdep_new(card, device); + if (err < 0) + goto error; + err = snd_card_register(card); + if (err < 0) + goto error; + + dev_set_drvdata(&intf->dev, card); + return 0; + + error: + snd_card_free(card); + return err; +} + +static struct usb_driver snd_usx2y_usb_driver = { + .name = "snd-usb-usx2y", + .probe = snd_usx2y_probe, + .disconnect = snd_usx2y_disconnect, + .id_table = snd_usx2y_usb_id_table, +}; +module_usb_driver(snd_usx2y_usb_driver); diff --git a/sound/usb/usx2y/usbusx2y.h b/sound/usb/usx2y/usbusx2y.h new file mode 100644 index 0000000000..8d82f5cc2f --- /dev/null +++ b/sound/usb/usx2y/usbusx2y.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef USBUSX2Y_H +#define USBUSX2Y_H +#include "../usbaudio.h" +#include "../midi.h" +#include "usbus428ctldefs.h" + +#define NRURBS 2 + + +#define URBS_ASYNC_SEQ 10 +#define URB_DATA_LEN_ASYNC_SEQ 32 +struct snd_usx2y_async_seq { + struct urb *urb[URBS_ASYNC_SEQ]; + char *buffer; +}; + +struct snd_usx2y_urb_seq { + int submitted; + int len; + struct urb *urb[]; +}; + +#include "usx2yhwdeppcm.h" + +struct usx2ydev { + struct usb_device *dev; + int card_index; + int stride; + struct urb *in04_urb; + void *in04_buf; + char in04_last[24]; + unsigned int in04_int_calls; + struct snd_usx2y_urb_seq *us04; + wait_queue_head_t in04_wait_queue; + struct snd_usx2y_async_seq as04; + unsigned int rate, + format; + int chip_status; + struct mutex pcm_mutex; + struct us428ctls_sharedmem *us428ctls_sharedmem; + int wait_iso_frame; + wait_queue_head_t us428ctls_wait_queue_head; + struct snd_usx2y_hwdep_pcm_shm *hwdep_pcm_shm; + struct snd_usx2y_substream *subs[4]; + struct snd_usx2y_substream * volatile prepare_subs; + wait_queue_head_t prepare_wait_queue; + struct list_head midi_list; + int pcm_devs; +}; + + +struct snd_usx2y_substream { + struct usx2ydev *usx2y; + struct snd_pcm_substream *pcm_substream; + + int endpoint; + unsigned int maxpacksize; /* max packet size in bytes */ + + atomic_t state; +#define STATE_STOPPED 0 +#define STATE_STARTING1 1 +#define STATE_STARTING2 2 +#define STATE_STARTING3 3 +#define STATE_PREPARED 4 +#define STATE_PRERUNNING 6 +#define STATE_RUNNING 8 + + int hwptr; /* free frame position in the buffer (only for playback) */ + int hwptr_done; /* processed frame position in the buffer */ + int transfer_done; /* processed frames since last period update */ + + struct urb *urb[NRURBS]; /* data urb table */ + struct urb *completed_urb; + char *tmpbuf; /* temporary buffer for playback */ +}; + + +#define usx2y(c) ((struct usx2ydev *)(c)->private_data) + +int usx2y_audio_create(struct snd_card *card); + +int usx2y_async_seq04_init(struct usx2ydev *usx2y); +int usx2y_in04_init(struct usx2ydev *usx2y); + +#define NAME_ALLCAPS "US-X2Y" + +#endif diff --git a/sound/usb/usx2y/usbusx2yaudio.c b/sound/usb/usx2y/usbusx2yaudio.c new file mode 100644 index 0000000000..5197599e7a --- /dev/null +++ b/sound/usb/usx2y/usbusx2yaudio.c @@ -0,0 +1,1033 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * US-X2Y AUDIO + * Copyright (c) 2002-2004 by Karsten Wiese + * + * based on + * + * (Tentative) USB Audio Driver for ALSA + * + * Main and PCM part + * + * Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de> + * + * Many codes borrowed from audio.c by + * Alan Cox (alan@lxorguk.ukuu.org.uk) + * Thomas Sailer (sailer@ife.ee.ethz.ch) + */ + + +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/moduleparam.h> +#include <sound/core.h> +#include <sound/info.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include "usx2y.h" +#include "usbusx2y.h" + +/* Default value used for nr of packs per urb. + * 1 to 4 have been tested ok on uhci. + * To use 3 on ohci, you'd need a patch: + * look for "0000425-linux-2.6.9-rc4-mm1_ohci-hcd.patch.gz" on + * "https://bugtrack.alsa-project.org/alsa-bug/bug_view_page.php?bug_id=0000425" + * + * 1, 2 and 4 work out of the box on ohci, if I recall correctly. + * Bigger is safer operation, smaller gives lower latencies. + */ +#define USX2Y_NRPACKS 4 + +/* If your system works ok with this module's parameter + * nrpacks set to 1, you might as well comment + * this define out, and thereby produce smaller, faster code. + * You'd also set USX2Y_NRPACKS to 1 then. + */ +#define USX2Y_NRPACKS_VARIABLE 1 + +#ifdef USX2Y_NRPACKS_VARIABLE +static int nrpacks = USX2Y_NRPACKS; /* number of packets per urb */ +#define nr_of_packs() nrpacks +module_param(nrpacks, int, 0444); +MODULE_PARM_DESC(nrpacks, "Number of packets per URB."); +#else +#define nr_of_packs() USX2Y_NRPACKS +#endif + +static int usx2y_urb_capt_retire(struct snd_usx2y_substream *subs) +{ + struct urb *urb = subs->completed_urb; + struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; + unsigned char *cp; + int i, len, lens = 0, hwptr_done = subs->hwptr_done; + int cnt, blen; + struct usx2ydev *usx2y = subs->usx2y; + + for (i = 0; i < nr_of_packs(); i++) { + cp = (unsigned char *)urb->transfer_buffer + urb->iso_frame_desc[i].offset; + if (urb->iso_frame_desc[i].status) { /* active? hmm, skip this */ + snd_printk(KERN_ERR + "active frame status %i. Most probably some hardware problem.\n", + urb->iso_frame_desc[i].status); + return urb->iso_frame_desc[i].status; + } + len = urb->iso_frame_desc[i].actual_length / usx2y->stride; + if (!len) { + snd_printd("0 == len ERROR!\n"); + continue; + } + + /* copy a data chunk */ + if ((hwptr_done + len) > runtime->buffer_size) { + cnt = runtime->buffer_size - hwptr_done; + blen = cnt * usx2y->stride; + memcpy(runtime->dma_area + hwptr_done * usx2y->stride, cp, blen); + memcpy(runtime->dma_area, cp + blen, len * usx2y->stride - blen); + } else { + memcpy(runtime->dma_area + hwptr_done * usx2y->stride, cp, + len * usx2y->stride); + } + lens += len; + hwptr_done += len; + if (hwptr_done >= runtime->buffer_size) + hwptr_done -= runtime->buffer_size; + } + + subs->hwptr_done = hwptr_done; + subs->transfer_done += lens; + /* update the pointer, call callback if necessary */ + if (subs->transfer_done >= runtime->period_size) { + subs->transfer_done -= runtime->period_size; + snd_pcm_period_elapsed(subs->pcm_substream); + } + return 0; +} + +/* + * prepare urb for playback data pipe + * + * we copy the data directly from the pcm buffer. + * the current position to be copied is held in hwptr field. + * since a urb can handle only a single linear buffer, if the total + * transferred area overflows the buffer boundary, we cannot send + * it directly from the buffer. thus the data is once copied to + * a temporary buffer and urb points to that. + */ +static int usx2y_urb_play_prepare(struct snd_usx2y_substream *subs, + struct urb *cap_urb, + struct urb *urb) +{ + struct usx2ydev *usx2y = subs->usx2y; + struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; + int count, counts, pack, len; + + count = 0; + for (pack = 0; pack < nr_of_packs(); pack++) { + /* calculate the size of a packet */ + counts = cap_urb->iso_frame_desc[pack].actual_length / usx2y->stride; + count += counts; + if (counts < 43 || counts > 50) { + snd_printk(KERN_ERR "should not be here with counts=%i\n", counts); + return -EPIPE; + } + /* set up descriptor */ + urb->iso_frame_desc[pack].offset = pack ? + urb->iso_frame_desc[pack - 1].offset + + urb->iso_frame_desc[pack - 1].length : + 0; + urb->iso_frame_desc[pack].length = cap_urb->iso_frame_desc[pack].actual_length; + } + if (atomic_read(&subs->state) >= STATE_PRERUNNING) { + if (subs->hwptr + count > runtime->buffer_size) { + /* err, the transferred area goes over buffer boundary. + * copy the data to the temp buffer. + */ + len = runtime->buffer_size - subs->hwptr; + urb->transfer_buffer = subs->tmpbuf; + memcpy(subs->tmpbuf, runtime->dma_area + + subs->hwptr * usx2y->stride, len * usx2y->stride); + memcpy(subs->tmpbuf + len * usx2y->stride, + runtime->dma_area, (count - len) * usx2y->stride); + subs->hwptr += count; + subs->hwptr -= runtime->buffer_size; + } else { + /* set the buffer pointer */ + urb->transfer_buffer = runtime->dma_area + subs->hwptr * usx2y->stride; + subs->hwptr += count; + if (subs->hwptr >= runtime->buffer_size) + subs->hwptr -= runtime->buffer_size; + } + } else { + urb->transfer_buffer = subs->tmpbuf; + } + urb->transfer_buffer_length = count * usx2y->stride; + return 0; +} + +/* + * process after playback data complete + * + * update the current position and call callback if a period is processed. + */ +static void usx2y_urb_play_retire(struct snd_usx2y_substream *subs, struct urb *urb) +{ + struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; + int len = urb->actual_length / subs->usx2y->stride; + + subs->transfer_done += len; + subs->hwptr_done += len; + if (subs->hwptr_done >= runtime->buffer_size) + subs->hwptr_done -= runtime->buffer_size; + if (subs->transfer_done >= runtime->period_size) { + subs->transfer_done -= runtime->period_size; + snd_pcm_period_elapsed(subs->pcm_substream); + } +} + +static int usx2y_urb_submit(struct snd_usx2y_substream *subs, struct urb *urb, int frame) +{ + int err; + + if (!urb) + return -ENODEV; + urb->start_frame = frame + NRURBS * nr_of_packs(); // let hcd do rollover sanity checks + urb->hcpriv = NULL; + urb->dev = subs->usx2y->dev; /* we need to set this at each time */ + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { + snd_printk(KERN_ERR "usb_submit_urb() returned %i\n", err); + return err; + } + return 0; +} + +static int usx2y_usbframe_complete(struct snd_usx2y_substream *capsubs, + struct snd_usx2y_substream *playbacksubs, + int frame) +{ + int err, state; + struct urb *urb = playbacksubs->completed_urb; + + state = atomic_read(&playbacksubs->state); + if (urb) { + if (state == STATE_RUNNING) + usx2y_urb_play_retire(playbacksubs, urb); + else if (state >= STATE_PRERUNNING) + atomic_inc(&playbacksubs->state); + } else { + switch (state) { + case STATE_STARTING1: + urb = playbacksubs->urb[0]; + atomic_inc(&playbacksubs->state); + break; + case STATE_STARTING2: + urb = playbacksubs->urb[1]; + atomic_inc(&playbacksubs->state); + break; + } + } + if (urb) { + err = usx2y_urb_play_prepare(playbacksubs, capsubs->completed_urb, urb); + if (err) + return err; + err = usx2y_urb_submit(playbacksubs, urb, frame); + if (err) + return err; + } + + playbacksubs->completed_urb = NULL; + + state = atomic_read(&capsubs->state); + if (state >= STATE_PREPARED) { + if (state == STATE_RUNNING) { + err = usx2y_urb_capt_retire(capsubs); + if (err) + return err; + } else if (state >= STATE_PRERUNNING) { + atomic_inc(&capsubs->state); + } + err = usx2y_urb_submit(capsubs, capsubs->completed_urb, frame); + if (err) + return err; + } + capsubs->completed_urb = NULL; + return 0; +} + +static void usx2y_clients_stop(struct usx2ydev *usx2y) +{ + struct snd_usx2y_substream *subs; + struct urb *urb; + int s, u; + + for (s = 0; s < 4; s++) { + subs = usx2y->subs[s]; + if (subs) { + snd_printdd("%i %p state=%i\n", s, subs, atomic_read(&subs->state)); + atomic_set(&subs->state, STATE_STOPPED); + } + } + for (s = 0; s < 4; s++) { + subs = usx2y->subs[s]; + if (subs) { + if (atomic_read(&subs->state) >= STATE_PRERUNNING) + snd_pcm_stop_xrun(subs->pcm_substream); + for (u = 0; u < NRURBS; u++) { + urb = subs->urb[u]; + if (urb) + snd_printdd("%i status=%i start_frame=%i\n", + u, urb->status, urb->start_frame); + } + } + } + usx2y->prepare_subs = NULL; + wake_up(&usx2y->prepare_wait_queue); +} + +static void usx2y_error_urb_status(struct usx2ydev *usx2y, + struct snd_usx2y_substream *subs, struct urb *urb) +{ + snd_printk(KERN_ERR "ep=%i stalled with status=%i\n", subs->endpoint, urb->status); + urb->status = 0; + usx2y_clients_stop(usx2y); +} + +static void i_usx2y_urb_complete(struct urb *urb) +{ + struct snd_usx2y_substream *subs = urb->context; + struct usx2ydev *usx2y = subs->usx2y; + struct snd_usx2y_substream *capsubs, *playbacksubs; + + if (unlikely(atomic_read(&subs->state) < STATE_PREPARED)) { + snd_printdd("hcd_frame=%i ep=%i%s status=%i start_frame=%i\n", + usb_get_current_frame_number(usx2y->dev), + subs->endpoint, usb_pipein(urb->pipe) ? "in" : "out", + urb->status, urb->start_frame); + return; + } + if (unlikely(urb->status)) { + usx2y_error_urb_status(usx2y, subs, urb); + return; + } + + subs->completed_urb = urb; + + capsubs = usx2y->subs[SNDRV_PCM_STREAM_CAPTURE]; + playbacksubs = usx2y->subs[SNDRV_PCM_STREAM_PLAYBACK]; + + if (capsubs->completed_urb && + atomic_read(&capsubs->state) >= STATE_PREPARED && + (playbacksubs->completed_urb || + atomic_read(&playbacksubs->state) < STATE_PREPARED)) { + if (!usx2y_usbframe_complete(capsubs, playbacksubs, urb->start_frame)) { + usx2y->wait_iso_frame += nr_of_packs(); + } else { + snd_printdd("\n"); + usx2y_clients_stop(usx2y); + } + } +} + +static void usx2y_urbs_set_complete(struct usx2ydev *usx2y, + void (*complete)(struct urb *)) +{ + struct snd_usx2y_substream *subs; + struct urb *urb; + int s, u; + + for (s = 0; s < 4; s++) { + subs = usx2y->subs[s]; + if (subs) { + for (u = 0; u < NRURBS; u++) { + urb = subs->urb[u]; + if (urb) + urb->complete = complete; + } + } + } +} + +static void usx2y_subs_startup_finish(struct usx2ydev *usx2y) +{ + usx2y_urbs_set_complete(usx2y, i_usx2y_urb_complete); + usx2y->prepare_subs = NULL; +} + +static void i_usx2y_subs_startup(struct urb *urb) +{ + struct snd_usx2y_substream *subs = urb->context; + struct usx2ydev *usx2y = subs->usx2y; + struct snd_usx2y_substream *prepare_subs = usx2y->prepare_subs; + + if (prepare_subs) { + if (urb->start_frame == prepare_subs->urb[0]->start_frame) { + usx2y_subs_startup_finish(usx2y); + atomic_inc(&prepare_subs->state); + wake_up(&usx2y->prepare_wait_queue); + } + } + + i_usx2y_urb_complete(urb); +} + +static void usx2y_subs_prepare(struct snd_usx2y_substream *subs) +{ + snd_printdd("usx2y_substream_prepare(%p) ep=%i urb0=%p urb1=%p\n", + subs, subs->endpoint, subs->urb[0], subs->urb[1]); + /* reset the pointer */ + subs->hwptr = 0; + subs->hwptr_done = 0; + subs->transfer_done = 0; +} + +static void usx2y_urb_release(struct urb **urb, int free_tb) +{ + if (*urb) { + usb_kill_urb(*urb); + if (free_tb) + kfree((*urb)->transfer_buffer); + usb_free_urb(*urb); + *urb = NULL; + } +} + +/* + * release a substreams urbs + */ +static void usx2y_urbs_release(struct snd_usx2y_substream *subs) +{ + int i; + + snd_printdd("%s %i\n", __func__, subs->endpoint); + for (i = 0; i < NRURBS; i++) + usx2y_urb_release(subs->urb + i, + subs != subs->usx2y->subs[SNDRV_PCM_STREAM_PLAYBACK]); + + kfree(subs->tmpbuf); + subs->tmpbuf = NULL; +} + +/* + * initialize a substream's urbs + */ +static int usx2y_urbs_allocate(struct snd_usx2y_substream *subs) +{ + int i; + unsigned int pipe; + int is_playback = subs == subs->usx2y->subs[SNDRV_PCM_STREAM_PLAYBACK]; + struct usb_device *dev = subs->usx2y->dev; + struct urb **purb; + + pipe = is_playback ? usb_sndisocpipe(dev, subs->endpoint) : + usb_rcvisocpipe(dev, subs->endpoint); + subs->maxpacksize = usb_maxpacket(dev, pipe); + if (!subs->maxpacksize) + return -EINVAL; + + if (is_playback && !subs->tmpbuf) { /* allocate a temporary buffer for playback */ + subs->tmpbuf = kcalloc(nr_of_packs(), subs->maxpacksize, GFP_KERNEL); + if (!subs->tmpbuf) + return -ENOMEM; + } + /* allocate and initialize data urbs */ + for (i = 0; i < NRURBS; i++) { + purb = subs->urb + i; + if (*purb) { + usb_kill_urb(*purb); + continue; + } + *purb = usb_alloc_urb(nr_of_packs(), GFP_KERNEL); + if (!*purb) { + usx2y_urbs_release(subs); + return -ENOMEM; + } + if (!is_playback && !(*purb)->transfer_buffer) { + /* allocate a capture buffer per urb */ + (*purb)->transfer_buffer = + kmalloc_array(subs->maxpacksize, + nr_of_packs(), GFP_KERNEL); + if (!(*purb)->transfer_buffer) { + usx2y_urbs_release(subs); + return -ENOMEM; + } + } + (*purb)->dev = dev; + (*purb)->pipe = pipe; + (*purb)->number_of_packets = nr_of_packs(); + (*purb)->context = subs; + (*purb)->interval = 1; + (*purb)->complete = i_usx2y_subs_startup; + } + return 0; +} + +static void usx2y_subs_startup(struct snd_usx2y_substream *subs) +{ + struct usx2ydev *usx2y = subs->usx2y; + + usx2y->prepare_subs = subs; + subs->urb[0]->start_frame = -1; + wmb(); + usx2y_urbs_set_complete(usx2y, i_usx2y_subs_startup); +} + +static int usx2y_urbs_start(struct snd_usx2y_substream *subs) +{ + int i, err; + struct usx2ydev *usx2y = subs->usx2y; + struct urb *urb; + unsigned long pack; + + err = usx2y_urbs_allocate(subs); + if (err < 0) + return err; + subs->completed_urb = NULL; + for (i = 0; i < 4; i++) { + struct snd_usx2y_substream *subs = usx2y->subs[i]; + + if (subs && atomic_read(&subs->state) >= STATE_PREPARED) + goto start; + } + + start: + usx2y_subs_startup(subs); + for (i = 0; i < NRURBS; i++) { + urb = subs->urb[i]; + if (usb_pipein(urb->pipe)) { + if (!i) + atomic_set(&subs->state, STATE_STARTING3); + urb->dev = usx2y->dev; + for (pack = 0; pack < nr_of_packs(); pack++) { + urb->iso_frame_desc[pack].offset = subs->maxpacksize * pack; + urb->iso_frame_desc[pack].length = subs->maxpacksize; + } + urb->transfer_buffer_length = subs->maxpacksize * nr_of_packs(); + err = usb_submit_urb(urb, GFP_ATOMIC); + if (err < 0) { + snd_printk(KERN_ERR "cannot submit datapipe for urb %d, err = %d\n", i, err); + err = -EPIPE; + goto cleanup; + } else { + if (!i) + usx2y->wait_iso_frame = urb->start_frame; + } + urb->transfer_flags = 0; + } else { + atomic_set(&subs->state, STATE_STARTING1); + break; + } + } + err = 0; + wait_event(usx2y->prepare_wait_queue, !usx2y->prepare_subs); + if (atomic_read(&subs->state) != STATE_PREPARED) + err = -EPIPE; + + cleanup: + if (err) { + usx2y_subs_startup_finish(usx2y); + usx2y_clients_stop(usx2y); // something is completely wrong > stop everything + } + return err; +} + +/* + * return the current pcm pointer. just return the hwptr_done value. + */ +static snd_pcm_uframes_t snd_usx2y_pcm_pointer(struct snd_pcm_substream *substream) +{ + struct snd_usx2y_substream *subs = substream->runtime->private_data; + + return subs->hwptr_done; +} + +/* + * start/stop substream + */ +static int snd_usx2y_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_usx2y_substream *subs = substream->runtime->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + snd_printdd("%s(START)\n", __func__); + if (atomic_read(&subs->state) == STATE_PREPARED && + atomic_read(&subs->usx2y->subs[SNDRV_PCM_STREAM_CAPTURE]->state) >= STATE_PREPARED) { + atomic_set(&subs->state, STATE_PRERUNNING); + } else { + snd_printdd("\n"); + return -EPIPE; + } + break; + case SNDRV_PCM_TRIGGER_STOP: + snd_printdd("%s(STOP)\n", __func__); + if (atomic_read(&subs->state) >= STATE_PRERUNNING) + atomic_set(&subs->state, STATE_PREPARED); + break; + default: + return -EINVAL; + } + return 0; +} + +/* + * allocate a buffer, setup samplerate + * + * so far we use a physically linear buffer although packetize transfer + * doesn't need a continuous area. + * if sg buffer is supported on the later version of alsa, we'll follow + * that. + */ +struct s_c2 { + char c1, c2; +}; + +static const struct s_c2 setrate_44100[] = { + { 0x14, 0x08}, // this line sets 44100, well actually a little less + { 0x18, 0x40}, // only tascam / frontier design knows the further lines ....... + { 0x18, 0x42}, + { 0x18, 0x45}, + { 0x18, 0x46}, + { 0x18, 0x48}, + { 0x18, 0x4A}, + { 0x18, 0x4C}, + { 0x18, 0x4E}, + { 0x18, 0x50}, + { 0x18, 0x52}, + { 0x18, 0x54}, + { 0x18, 0x56}, + { 0x18, 0x58}, + { 0x18, 0x5A}, + { 0x18, 0x5C}, + { 0x18, 0x5E}, + { 0x18, 0x60}, + { 0x18, 0x62}, + { 0x18, 0x64}, + { 0x18, 0x66}, + { 0x18, 0x68}, + { 0x18, 0x6A}, + { 0x18, 0x6C}, + { 0x18, 0x6E}, + { 0x18, 0x70}, + { 0x18, 0x72}, + { 0x18, 0x74}, + { 0x18, 0x76}, + { 0x18, 0x78}, + { 0x18, 0x7A}, + { 0x18, 0x7C}, + { 0x18, 0x7E} +}; + +static const struct s_c2 setrate_48000[] = { + { 0x14, 0x09}, // this line sets 48000, well actually a little less + { 0x18, 0x40}, // only tascam / frontier design knows the further lines ....... + { 0x18, 0x42}, + { 0x18, 0x45}, + { 0x18, 0x46}, + { 0x18, 0x48}, + { 0x18, 0x4A}, + { 0x18, 0x4C}, + { 0x18, 0x4E}, + { 0x18, 0x50}, + { 0x18, 0x52}, + { 0x18, 0x54}, + { 0x18, 0x56}, + { 0x18, 0x58}, + { 0x18, 0x5A}, + { 0x18, 0x5C}, + { 0x18, 0x5E}, + { 0x18, 0x60}, + { 0x18, 0x62}, + { 0x18, 0x64}, + { 0x18, 0x66}, + { 0x18, 0x68}, + { 0x18, 0x6A}, + { 0x18, 0x6C}, + { 0x18, 0x6E}, + { 0x18, 0x70}, + { 0x18, 0x73}, + { 0x18, 0x74}, + { 0x18, 0x76}, + { 0x18, 0x78}, + { 0x18, 0x7A}, + { 0x18, 0x7C}, + { 0x18, 0x7E} +}; + +#define NOOF_SETRATE_URBS ARRAY_SIZE(setrate_48000) + +static void i_usx2y_04int(struct urb *urb) +{ + struct usx2ydev *usx2y = urb->context; + + if (urb->status) + snd_printk(KERN_ERR "snd_usx2y_04int() urb->status=%i\n", urb->status); + if (!--usx2y->us04->len) + wake_up(&usx2y->in04_wait_queue); +} + +static int usx2y_rate_set(struct usx2ydev *usx2y, int rate) +{ + int err = 0, i; + struct snd_usx2y_urb_seq *us = NULL; + int *usbdata = NULL; + const struct s_c2 *ra = rate == 48000 ? setrate_48000 : setrate_44100; + struct urb *urb; + + if (usx2y->rate != rate) { + us = kzalloc(struct_size(us, urb, NOOF_SETRATE_URBS), + GFP_KERNEL); + if (!us) { + err = -ENOMEM; + goto cleanup; + } + usbdata = kmalloc_array(NOOF_SETRATE_URBS, sizeof(int), + GFP_KERNEL); + if (!usbdata) { + err = -ENOMEM; + goto cleanup; + } + for (i = 0; i < NOOF_SETRATE_URBS; ++i) { + us->urb[i] = usb_alloc_urb(0, GFP_KERNEL); + if (!us->urb[i]) { + err = -ENOMEM; + goto cleanup; + } + ((char *)(usbdata + i))[0] = ra[i].c1; + ((char *)(usbdata + i))[1] = ra[i].c2; + usb_fill_bulk_urb(us->urb[i], usx2y->dev, usb_sndbulkpipe(usx2y->dev, 4), + usbdata + i, 2, i_usx2y_04int, usx2y); + } + err = usb_urb_ep_type_check(us->urb[0]); + if (err < 0) + goto cleanup; + us->submitted = 0; + us->len = NOOF_SETRATE_URBS; + usx2y->us04 = us; + wait_event_timeout(usx2y->in04_wait_queue, !us->len, HZ); + usx2y->us04 = NULL; + if (us->len) + err = -ENODEV; + cleanup: + if (us) { + us->submitted = 2*NOOF_SETRATE_URBS; + for (i = 0; i < NOOF_SETRATE_URBS; ++i) { + urb = us->urb[i]; + if (!urb) + continue; + if (urb->status) { + if (!err) + err = -ENODEV; + usb_kill_urb(urb); + } + usb_free_urb(urb); + } + usx2y->us04 = NULL; + kfree(usbdata); + kfree(us); + if (!err) + usx2y->rate = rate; + } + } + + return err; +} + +static int usx2y_format_set(struct usx2ydev *usx2y, snd_pcm_format_t format) +{ + int alternate, err; + struct list_head *p; + + if (format == SNDRV_PCM_FORMAT_S24_3LE) { + alternate = 2; + usx2y->stride = 6; + } else { + alternate = 1; + usx2y->stride = 4; + } + list_for_each(p, &usx2y->midi_list) { + snd_usbmidi_input_stop(p); + } + usb_kill_urb(usx2y->in04_urb); + err = usb_set_interface(usx2y->dev, 0, alternate); + if (err) { + snd_printk(KERN_ERR "usb_set_interface error\n"); + return err; + } + usx2y->in04_urb->dev = usx2y->dev; + err = usb_submit_urb(usx2y->in04_urb, GFP_KERNEL); + list_for_each(p, &usx2y->midi_list) { + snd_usbmidi_input_start(p); + } + usx2y->format = format; + usx2y->rate = 0; + return err; +} + + +static int snd_usx2y_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + int err = 0; + unsigned int rate = params_rate(hw_params); + snd_pcm_format_t format = params_format(hw_params); + struct snd_card *card = substream->pstr->pcm->card; + struct usx2ydev *dev = usx2y(card); + struct snd_usx2y_substream *subs; + struct snd_pcm_substream *test_substream; + int i; + + mutex_lock(&usx2y(card)->pcm_mutex); + snd_printdd("snd_usx2y_hw_params(%p, %p)\n", substream, hw_params); + /* all pcm substreams off one usx2y have to operate at the same + * rate & format + */ + for (i = 0; i < dev->pcm_devs * 2; i++) { + subs = dev->subs[i]; + if (!subs) + continue; + test_substream = subs->pcm_substream; + if (!test_substream || test_substream == substream || + !test_substream->runtime) + continue; + if ((test_substream->runtime->format && + test_substream->runtime->format != format) || + (test_substream->runtime->rate && + test_substream->runtime->rate != rate)) { + err = -EINVAL; + goto error; + } + } + + error: + mutex_unlock(&usx2y(card)->pcm_mutex); + return err; +} + +/* + * free the buffer + */ +static int snd_usx2y_pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_usx2y_substream *subs = runtime->private_data; + struct snd_usx2y_substream *cap_subs, *playback_subs; + + mutex_lock(&subs->usx2y->pcm_mutex); + snd_printdd("snd_usx2y_hw_free(%p)\n", substream); + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + cap_subs = subs->usx2y->subs[SNDRV_PCM_STREAM_CAPTURE]; + atomic_set(&subs->state, STATE_STOPPED); + usx2y_urbs_release(subs); + if (!cap_subs->pcm_substream || + !cap_subs->pcm_substream->runtime || + cap_subs->pcm_substream->runtime->state < SNDRV_PCM_STATE_PREPARED) { + atomic_set(&cap_subs->state, STATE_STOPPED); + usx2y_urbs_release(cap_subs); + } + } else { + playback_subs = subs->usx2y->subs[SNDRV_PCM_STREAM_PLAYBACK]; + if (atomic_read(&playback_subs->state) < STATE_PREPARED) { + atomic_set(&subs->state, STATE_STOPPED); + usx2y_urbs_release(subs); + } + } + mutex_unlock(&subs->usx2y->pcm_mutex); + return 0; +} + +/* + * prepare callback + * + * set format and initialize urbs + */ +static int snd_usx2y_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_usx2y_substream *subs = runtime->private_data; + struct usx2ydev *usx2y = subs->usx2y; + struct snd_usx2y_substream *capsubs = subs->usx2y->subs[SNDRV_PCM_STREAM_CAPTURE]; + int err = 0; + + snd_printdd("%s(%p)\n", __func__, substream); + + mutex_lock(&usx2y->pcm_mutex); + usx2y_subs_prepare(subs); + // Start hardware streams + // SyncStream first.... + if (atomic_read(&capsubs->state) < STATE_PREPARED) { + if (usx2y->format != runtime->format) { + err = usx2y_format_set(usx2y, runtime->format); + if (err < 0) + goto up_prepare_mutex; + } + if (usx2y->rate != runtime->rate) { + err = usx2y_rate_set(usx2y, runtime->rate); + if (err < 0) + goto up_prepare_mutex; + } + snd_printdd("starting capture pipe for %s\n", subs == capsubs ? "self" : "playpipe"); + err = usx2y_urbs_start(capsubs); + if (err < 0) + goto up_prepare_mutex; + } + + if (subs != capsubs && atomic_read(&subs->state) < STATE_PREPARED) + err = usx2y_urbs_start(subs); + + up_prepare_mutex: + mutex_unlock(&usx2y->pcm_mutex); + return err; +} + +static const struct snd_pcm_hardware snd_usx2y_2c = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH), + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE, + .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = (2*128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = 1024, + .fifo_size = 0 +}; + +static int snd_usx2y_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_usx2y_substream *subs = + ((struct snd_usx2y_substream **) + snd_pcm_substream_chip(substream))[substream->stream]; + struct snd_pcm_runtime *runtime = substream->runtime; + + if (subs->usx2y->chip_status & USX2Y_STAT_CHIP_MMAP_PCM_URBS) + return -EBUSY; + + runtime->hw = snd_usx2y_2c; + runtime->private_data = subs; + subs->pcm_substream = substream; + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 1000, 200000); + return 0; +} + +static int snd_usx2y_pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_usx2y_substream *subs = runtime->private_data; + + subs->pcm_substream = NULL; + + return 0; +} + +static const struct snd_pcm_ops snd_usx2y_pcm_ops = { + .open = snd_usx2y_pcm_open, + .close = snd_usx2y_pcm_close, + .hw_params = snd_usx2y_pcm_hw_params, + .hw_free = snd_usx2y_pcm_hw_free, + .prepare = snd_usx2y_pcm_prepare, + .trigger = snd_usx2y_pcm_trigger, + .pointer = snd_usx2y_pcm_pointer, +}; + +/* + * free a usb stream instance + */ +static void usx2y_audio_stream_free(struct snd_usx2y_substream **usx2y_substream) +{ + int stream; + + for_each_pcm_streams(stream) { + kfree(usx2y_substream[stream]); + usx2y_substream[stream] = NULL; + } +} + +static void snd_usx2y_pcm_private_free(struct snd_pcm *pcm) +{ + struct snd_usx2y_substream **usx2y_stream = pcm->private_data; + + if (usx2y_stream) + usx2y_audio_stream_free(usx2y_stream); +} + +static int usx2y_audio_stream_new(struct snd_card *card, int playback_endpoint, int capture_endpoint) +{ + struct snd_pcm *pcm; + int err, i; + struct snd_usx2y_substream **usx2y_substream = + usx2y(card)->subs + 2 * usx2y(card)->pcm_devs; + + for (i = playback_endpoint ? SNDRV_PCM_STREAM_PLAYBACK : SNDRV_PCM_STREAM_CAPTURE; + i <= SNDRV_PCM_STREAM_CAPTURE; ++i) { + usx2y_substream[i] = kzalloc(sizeof(struct snd_usx2y_substream), GFP_KERNEL); + if (!usx2y_substream[i]) + return -ENOMEM; + + usx2y_substream[i]->usx2y = usx2y(card); + } + + if (playback_endpoint) + usx2y_substream[SNDRV_PCM_STREAM_PLAYBACK]->endpoint = playback_endpoint; + usx2y_substream[SNDRV_PCM_STREAM_CAPTURE]->endpoint = capture_endpoint; + + err = snd_pcm_new(card, NAME_ALLCAPS" Audio", usx2y(card)->pcm_devs, + playback_endpoint ? 1 : 0, 1, + &pcm); + if (err < 0) { + usx2y_audio_stream_free(usx2y_substream); + return err; + } + + if (playback_endpoint) + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_usx2y_pcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usx2y_pcm_ops); + + pcm->private_data = usx2y_substream; + pcm->private_free = snd_usx2y_pcm_private_free; + pcm->info_flags = 0; + + sprintf(pcm->name, NAME_ALLCAPS" Audio #%d", usx2y(card)->pcm_devs); + + if (playback_endpoint) { + snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream, + SNDRV_DMA_TYPE_CONTINUOUS, + NULL, + 64*1024, 128*1024); + } + + snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream, + SNDRV_DMA_TYPE_CONTINUOUS, + NULL, + 64*1024, 128*1024); + usx2y(card)->pcm_devs++; + + return 0; +} + +/* + * create a chip instance and set its names. + */ +int usx2y_audio_create(struct snd_card *card) +{ + int err; + + err = usx2y_audio_stream_new(card, 0xA, 0x8); + if (err < 0) + return err; + if (le16_to_cpu(usx2y(card)->dev->descriptor.idProduct) == USB_ID_US428) { + err = usx2y_audio_stream_new(card, 0, 0xA); + if (err < 0) + return err; + } + if (le16_to_cpu(usx2y(card)->dev->descriptor.idProduct) != USB_ID_US122) + err = usx2y_rate_set(usx2y(card), 44100); // Lets us428 recognize output-volume settings, disturbs us122. + return err; +} diff --git a/sound/usb/usx2y/usx2y.h b/sound/usb/usx2y/usx2y.h new file mode 100644 index 0000000000..780071ddd9 --- /dev/null +++ b/sound/usb/usx2y/usx2y.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Driver for Tascam US-X2Y USB soundcards + * + * Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de> + */ + +#ifndef __SOUND_USX2Y_COMMON_H +#define __SOUND_USX2Y_COMMON_H + + +#define USX2Y_DRIVER_VERSION 0x0100 /* 0.1.0 */ + + +/* hwdep id string */ +#define SND_USX2Y_LOADER_ID "USX2Y Loader" +#define SND_USX2Y_USBPCM_ID "USX2Y USBPCM" + +/* hardware type */ +enum { + USX2Y_TYPE_122, + USX2Y_TYPE_224, + USX2Y_TYPE_428, + USX2Y_TYPE_NUMS +}; + +#define USB_ID_US122 0x8007 +#define USB_ID_US224 0x8005 +#define USB_ID_US428 0x8001 + +/* chip status */ +enum { + USX2Y_STAT_CHIP_INIT = (1 << 0), /* all operational */ + USX2Y_STAT_CHIP_MMAP_PCM_URBS = (1 << 1), /* pcm transport over mmaped urbs */ + USX2Y_STAT_CHIP_HUP = (1 << 31), /* all operational */ +}; + +#endif /* __SOUND_USX2Y_COMMON_H */ diff --git a/sound/usb/usx2y/usx2yhwdeppcm.c b/sound/usb/usx2y/usx2yhwdeppcm.c new file mode 100644 index 0000000000..36f2e31168 --- /dev/null +++ b/sound/usb/usx2y/usx2yhwdeppcm.c @@ -0,0 +1,775 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + */ + +/* USX2Y "rawusb" aka hwdep_pcm implementation + + Its usb's unableness to atomically handle power of 2 period sized data chuncs + at standard samplerates, + what led to this part of the usx2y module: + It provides the alsa kernel half of the usx2y-alsa-jack driver pair. + The pair uses a hardware dependent alsa-device for mmaped pcm transport. + Advantage achieved: + The usb_hc moves pcm data from/into memory via DMA. + That memory is mmaped by jack's usx2y driver. + Jack's usx2y driver is the first/last to read/write pcm data. + Read/write is a combination of power of 2 period shaping and + float/int conversation. + Compared to mainline alsa/jack we leave out power of 2 period shaping inside + snd-usb-usx2y which needs memcpy() and additional buffers. + As a side effect possible unwanted pcm-data coruption resulting of + standard alsa's snd-usb-usx2y period shaping scheme falls away. + Result is sane jack operation at buffering schemes down to 128frames, + 2 periods. + plain usx2y alsa mode is able to achieve 64frames, 4periods, but only at the + cost of easier triggered i.e. aeolus xruns (128 or 256frames, + 2periods works but is useless cause of crackling). + + This is a first "proof of concept" implementation. + Later, functionalities should migrate to more appropriate places: + Userland: + - The jackd could mmap its float-pcm buffers directly from alsa-lib. + - alsa-lib could provide power of 2 period sized shaping combined with int/float + conversation. + Currently the usx2y jack driver provides above 2 services. + Kernel: + - rawusb dma pcm buffer transport should go to snd-usb-lib, so also snd-usb-audio + devices can use it. + Currently rawusb dma pcm buffer transport (this file) is only available to snd-usb-usx2y. +*/ + +#include <linux/delay.h> +#include <linux/gfp.h> +#include "usbusx2yaudio.c" + +#if defined(USX2Y_NRPACKS_VARIABLE) || USX2Y_NRPACKS == 1 + +#include <sound/hwdep.h> + +static int usx2y_usbpcm_urb_capt_retire(struct snd_usx2y_substream *subs) +{ + struct urb *urb = subs->completed_urb; + struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; + int i, lens = 0, hwptr_done = subs->hwptr_done; + struct usx2ydev *usx2y = subs->usx2y; + int head; + + if (usx2y->hwdep_pcm_shm->capture_iso_start < 0) { //FIXME + head = usx2y->hwdep_pcm_shm->captured_iso_head + 1; + if (head >= ARRAY_SIZE(usx2y->hwdep_pcm_shm->captured_iso)) + head = 0; + usx2y->hwdep_pcm_shm->capture_iso_start = head; + snd_printdd("cap start %i\n", head); + } + for (i = 0; i < nr_of_packs(); i++) { + if (urb->iso_frame_desc[i].status) { /* active? hmm, skip this */ + snd_printk(KERN_ERR + "active frame status %i. Most probably some hardware problem.\n", + urb->iso_frame_desc[i].status); + return urb->iso_frame_desc[i].status; + } + lens += urb->iso_frame_desc[i].actual_length / usx2y->stride; + } + hwptr_done += lens; + if (hwptr_done >= runtime->buffer_size) + hwptr_done -= runtime->buffer_size; + subs->hwptr_done = hwptr_done; + subs->transfer_done += lens; + /* update the pointer, call callback if necessary */ + if (subs->transfer_done >= runtime->period_size) { + subs->transfer_done -= runtime->period_size; + snd_pcm_period_elapsed(subs->pcm_substream); + } + return 0; +} + +static int usx2y_iso_frames_per_buffer(struct snd_pcm_runtime *runtime, + struct usx2ydev *usx2y) +{ + return (runtime->buffer_size * 1000) / usx2y->rate + 1; //FIXME: so far only correct period_size == 2^x ? +} + +/* + * prepare urb for playback data pipe + * + * we copy the data directly from the pcm buffer. + * the current position to be copied is held in hwptr field. + * since a urb can handle only a single linear buffer, if the total + * transferred area overflows the buffer boundary, we cannot send + * it directly from the buffer. thus the data is once copied to + * a temporary buffer and urb points to that. + */ +static int usx2y_hwdep_urb_play_prepare(struct snd_usx2y_substream *subs, + struct urb *urb) +{ + int count, counts, pack; + struct usx2ydev *usx2y = subs->usx2y; + struct snd_usx2y_hwdep_pcm_shm *shm = usx2y->hwdep_pcm_shm; + struct snd_pcm_runtime *runtime = subs->pcm_substream->runtime; + + if (shm->playback_iso_start < 0) { + shm->playback_iso_start = shm->captured_iso_head - + usx2y_iso_frames_per_buffer(runtime, usx2y); + if (shm->playback_iso_start < 0) + shm->playback_iso_start += ARRAY_SIZE(shm->captured_iso); + shm->playback_iso_head = shm->playback_iso_start; + } + + count = 0; + for (pack = 0; pack < nr_of_packs(); pack++) { + /* calculate the size of a packet */ + counts = shm->captured_iso[shm->playback_iso_head].length / usx2y->stride; + if (counts < 43 || counts > 50) { + snd_printk(KERN_ERR "should not be here with counts=%i\n", counts); + return -EPIPE; + } + /* set up descriptor */ + urb->iso_frame_desc[pack].offset = shm->captured_iso[shm->playback_iso_head].offset; + urb->iso_frame_desc[pack].length = shm->captured_iso[shm->playback_iso_head].length; + if (atomic_read(&subs->state) != STATE_RUNNING) + memset((char *)urb->transfer_buffer + urb->iso_frame_desc[pack].offset, 0, + urb->iso_frame_desc[pack].length); + if (++shm->playback_iso_head >= ARRAY_SIZE(shm->captured_iso)) + shm->playback_iso_head = 0; + count += counts; + } + urb->transfer_buffer_length = count * usx2y->stride; + return 0; +} + +static void usx2y_usbpcm_urb_capt_iso_advance(struct snd_usx2y_substream *subs, + struct urb *urb) +{ + struct usb_iso_packet_descriptor *desc; + struct snd_usx2y_hwdep_pcm_shm *shm; + int pack, head; + + for (pack = 0; pack < nr_of_packs(); ++pack) { + desc = urb->iso_frame_desc + pack; + if (subs) { + shm = subs->usx2y->hwdep_pcm_shm; + head = shm->captured_iso_head + 1; + if (head >= ARRAY_SIZE(shm->captured_iso)) + head = 0; + shm->captured_iso[head].frame = urb->start_frame + pack; + shm->captured_iso[head].offset = desc->offset; + shm->captured_iso[head].length = desc->actual_length; + shm->captured_iso_head = head; + shm->captured_iso_frames++; + } + desc->offset += desc->length * NRURBS * nr_of_packs(); + if (desc->offset + desc->length >= SSS) + desc->offset -= (SSS - desc->length); + } +} + +static int usx2y_usbpcm_usbframe_complete(struct snd_usx2y_substream *capsubs, + struct snd_usx2y_substream *capsubs2, + struct snd_usx2y_substream *playbacksubs, + int frame) +{ + int err, state; + struct urb *urb = playbacksubs->completed_urb; + + state = atomic_read(&playbacksubs->state); + if (urb) { + if (state == STATE_RUNNING) + usx2y_urb_play_retire(playbacksubs, urb); + else if (state >= STATE_PRERUNNING) + atomic_inc(&playbacksubs->state); + } else { + switch (state) { + case STATE_STARTING1: + urb = playbacksubs->urb[0]; + atomic_inc(&playbacksubs->state); + break; + case STATE_STARTING2: + urb = playbacksubs->urb[1]; + atomic_inc(&playbacksubs->state); + break; + } + } + if (urb) { + err = usx2y_hwdep_urb_play_prepare(playbacksubs, urb); + if (err) + return err; + err = usx2y_hwdep_urb_play_prepare(playbacksubs, urb); + if (err) + return err; + } + + playbacksubs->completed_urb = NULL; + + state = atomic_read(&capsubs->state); + if (state >= STATE_PREPARED) { + if (state == STATE_RUNNING) { + err = usx2y_usbpcm_urb_capt_retire(capsubs); + if (err) + return err; + } else if (state >= STATE_PRERUNNING) { + atomic_inc(&capsubs->state); + } + usx2y_usbpcm_urb_capt_iso_advance(capsubs, capsubs->completed_urb); + if (capsubs2) + usx2y_usbpcm_urb_capt_iso_advance(NULL, capsubs2->completed_urb); + err = usx2y_urb_submit(capsubs, capsubs->completed_urb, frame); + if (err) + return err; + if (capsubs2) { + err = usx2y_urb_submit(capsubs2, capsubs2->completed_urb, frame); + if (err) + return err; + } + } + capsubs->completed_urb = NULL; + if (capsubs2) + capsubs2->completed_urb = NULL; + return 0; +} + +static void i_usx2y_usbpcm_urb_complete(struct urb *urb) +{ + struct snd_usx2y_substream *subs = urb->context; + struct usx2ydev *usx2y = subs->usx2y; + struct snd_usx2y_substream *capsubs, *capsubs2, *playbacksubs; + + if (unlikely(atomic_read(&subs->state) < STATE_PREPARED)) { + snd_printdd("hcd_frame=%i ep=%i%s status=%i start_frame=%i\n", + usb_get_current_frame_number(usx2y->dev), + subs->endpoint, usb_pipein(urb->pipe) ? "in" : "out", + urb->status, urb->start_frame); + return; + } + if (unlikely(urb->status)) { + usx2y_error_urb_status(usx2y, subs, urb); + return; + } + + subs->completed_urb = urb; + capsubs = usx2y->subs[SNDRV_PCM_STREAM_CAPTURE]; + capsubs2 = usx2y->subs[SNDRV_PCM_STREAM_CAPTURE + 2]; + playbacksubs = usx2y->subs[SNDRV_PCM_STREAM_PLAYBACK]; + if (capsubs->completed_urb && atomic_read(&capsubs->state) >= STATE_PREPARED && + (!capsubs2 || capsubs2->completed_urb) && + (playbacksubs->completed_urb || atomic_read(&playbacksubs->state) < STATE_PREPARED)) { + if (!usx2y_usbpcm_usbframe_complete(capsubs, capsubs2, playbacksubs, urb->start_frame)) { + usx2y->wait_iso_frame += nr_of_packs(); + } else { + snd_printdd("\n"); + usx2y_clients_stop(usx2y); + } + } +} + +static void usx2y_hwdep_urb_release(struct urb **urb) +{ + usb_kill_urb(*urb); + usb_free_urb(*urb); + *urb = NULL; +} + +/* + * release a substream + */ +static void usx2y_usbpcm_urbs_release(struct snd_usx2y_substream *subs) +{ + int i; + + snd_printdd("snd_usx2y_urbs_release() %i\n", subs->endpoint); + for (i = 0; i < NRURBS; i++) + usx2y_hwdep_urb_release(subs->urb + i); +} + +static void usx2y_usbpcm_subs_startup_finish(struct usx2ydev *usx2y) +{ + usx2y_urbs_set_complete(usx2y, i_usx2y_usbpcm_urb_complete); + usx2y->prepare_subs = NULL; +} + +static void i_usx2y_usbpcm_subs_startup(struct urb *urb) +{ + struct snd_usx2y_substream *subs = urb->context; + struct usx2ydev *usx2y = subs->usx2y; + struct snd_usx2y_substream *prepare_subs = usx2y->prepare_subs; + struct snd_usx2y_substream *cap_subs2; + + if (prepare_subs && + urb->start_frame == prepare_subs->urb[0]->start_frame) { + atomic_inc(&prepare_subs->state); + if (prepare_subs == usx2y->subs[SNDRV_PCM_STREAM_CAPTURE]) { + cap_subs2 = usx2y->subs[SNDRV_PCM_STREAM_CAPTURE + 2]; + if (cap_subs2) + atomic_inc(&cap_subs2->state); + } + usx2y_usbpcm_subs_startup_finish(usx2y); + wake_up(&usx2y->prepare_wait_queue); + } + + i_usx2y_usbpcm_urb_complete(urb); +} + +/* + * initialize a substream's urbs + */ +static int usx2y_usbpcm_urbs_allocate(struct snd_usx2y_substream *subs) +{ + int i; + unsigned int pipe; + int is_playback = subs == subs->usx2y->subs[SNDRV_PCM_STREAM_PLAYBACK]; + struct usb_device *dev = subs->usx2y->dev; + struct urb **purb; + + pipe = is_playback ? usb_sndisocpipe(dev, subs->endpoint) : + usb_rcvisocpipe(dev, subs->endpoint); + subs->maxpacksize = usb_maxpacket(dev, pipe); + if (!subs->maxpacksize) + return -EINVAL; + + /* allocate and initialize data urbs */ + for (i = 0; i < NRURBS; i++) { + purb = subs->urb + i; + if (*purb) { + usb_kill_urb(*purb); + continue; + } + *purb = usb_alloc_urb(nr_of_packs(), GFP_KERNEL); + if (!*purb) { + usx2y_usbpcm_urbs_release(subs); + return -ENOMEM; + } + (*purb)->transfer_buffer = is_playback ? + subs->usx2y->hwdep_pcm_shm->playback : ( + subs->endpoint == 0x8 ? + subs->usx2y->hwdep_pcm_shm->capture0x8 : + subs->usx2y->hwdep_pcm_shm->capture0xA); + + (*purb)->dev = dev; + (*purb)->pipe = pipe; + (*purb)->number_of_packets = nr_of_packs(); + (*purb)->context = subs; + (*purb)->interval = 1; + (*purb)->complete = i_usx2y_usbpcm_subs_startup; + } + return 0; +} + +/* + * free the buffer + */ +static int snd_usx2y_usbpcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_usx2y_substream *subs = runtime->private_data; + struct snd_usx2y_substream *cap_subs; + struct snd_usx2y_substream *playback_subs; + struct snd_usx2y_substream *cap_subs2; + + mutex_lock(&subs->usx2y->pcm_mutex); + snd_printdd("%s(%p)\n", __func__, substream); + + cap_subs2 = subs->usx2y->subs[SNDRV_PCM_STREAM_CAPTURE + 2]; + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + cap_subs = subs->usx2y->subs[SNDRV_PCM_STREAM_CAPTURE]; + atomic_set(&subs->state, STATE_STOPPED); + usx2y_usbpcm_urbs_release(subs); + if (!cap_subs->pcm_substream || + !cap_subs->pcm_substream->runtime || + cap_subs->pcm_substream->runtime->state < SNDRV_PCM_STATE_PREPARED) { + atomic_set(&cap_subs->state, STATE_STOPPED); + if (cap_subs2) + atomic_set(&cap_subs2->state, STATE_STOPPED); + usx2y_usbpcm_urbs_release(cap_subs); + if (cap_subs2) + usx2y_usbpcm_urbs_release(cap_subs2); + } + } else { + playback_subs = subs->usx2y->subs[SNDRV_PCM_STREAM_PLAYBACK]; + if (atomic_read(&playback_subs->state) < STATE_PREPARED) { + atomic_set(&subs->state, STATE_STOPPED); + if (cap_subs2) + atomic_set(&cap_subs2->state, STATE_STOPPED); + usx2y_usbpcm_urbs_release(subs); + if (cap_subs2) + usx2y_usbpcm_urbs_release(cap_subs2); + } + } + mutex_unlock(&subs->usx2y->pcm_mutex); + return 0; +} + +static void usx2y_usbpcm_subs_startup(struct snd_usx2y_substream *subs) +{ + struct usx2ydev *usx2y = subs->usx2y; + + usx2y->prepare_subs = subs; + subs->urb[0]->start_frame = -1; + smp_wmb(); // Make sure above modifications are seen by i_usx2y_subs_startup() + usx2y_urbs_set_complete(usx2y, i_usx2y_usbpcm_subs_startup); +} + +static int usx2y_usbpcm_urbs_start(struct snd_usx2y_substream *subs) +{ + int p, u, err, stream = subs->pcm_substream->stream; + struct usx2ydev *usx2y = subs->usx2y; + struct urb *urb; + unsigned long pack; + + if (stream == SNDRV_PCM_STREAM_CAPTURE) { + usx2y->hwdep_pcm_shm->captured_iso_head = -1; + usx2y->hwdep_pcm_shm->captured_iso_frames = 0; + } + + for (p = 0; 3 >= (stream + p); p += 2) { + struct snd_usx2y_substream *subs = usx2y->subs[stream + p]; + if (subs) { + err = usx2y_usbpcm_urbs_allocate(subs); + if (err < 0) + return err; + subs->completed_urb = NULL; + } + } + + for (p = 0; p < 4; p++) { + struct snd_usx2y_substream *subs = usx2y->subs[p]; + + if (subs && atomic_read(&subs->state) >= STATE_PREPARED) + goto start; + } + + start: + usx2y_usbpcm_subs_startup(subs); + for (u = 0; u < NRURBS; u++) { + for (p = 0; 3 >= (stream + p); p += 2) { + struct snd_usx2y_substream *subs = usx2y->subs[stream + p]; + + if (!subs) + continue; + urb = subs->urb[u]; + if (usb_pipein(urb->pipe)) { + if (!u) + atomic_set(&subs->state, STATE_STARTING3); + urb->dev = usx2y->dev; + for (pack = 0; pack < nr_of_packs(); pack++) { + urb->iso_frame_desc[pack].offset = subs->maxpacksize * (pack + u * nr_of_packs()); + urb->iso_frame_desc[pack].length = subs->maxpacksize; + } + urb->transfer_buffer_length = subs->maxpacksize * nr_of_packs(); + err = usb_submit_urb(urb, GFP_KERNEL); + if (err < 0) { + snd_printk(KERN_ERR "cannot usb_submit_urb() for urb %d, err = %d\n", u, err); + err = -EPIPE; + goto cleanup; + } else { + snd_printdd("%i\n", urb->start_frame); + if (!u) + usx2y->wait_iso_frame = urb->start_frame; + } + urb->transfer_flags = 0; + } else { + atomic_set(&subs->state, STATE_STARTING1); + break; + } + } + } + err = 0; + wait_event(usx2y->prepare_wait_queue, !usx2y->prepare_subs); + if (atomic_read(&subs->state) != STATE_PREPARED) + err = -EPIPE; + + cleanup: + if (err) { + usx2y_subs_startup_finish(usx2y); // Call it now + usx2y_clients_stop(usx2y); // something is completely wrong > stop everything + } + return err; +} + +#define USX2Y_HWDEP_PCM_PAGES \ + PAGE_ALIGN(sizeof(struct snd_usx2y_hwdep_pcm_shm)) + +/* + * prepare callback + * + * set format and initialize urbs + */ +static int snd_usx2y_usbpcm_prepare(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_usx2y_substream *subs = runtime->private_data; + struct usx2ydev *usx2y = subs->usx2y; + struct snd_usx2y_substream *capsubs = subs->usx2y->subs[SNDRV_PCM_STREAM_CAPTURE]; + int err = 0; + + snd_printdd("snd_usx2y_pcm_prepare(%p)\n", substream); + + mutex_lock(&usx2y->pcm_mutex); + + if (!usx2y->hwdep_pcm_shm) { + usx2y->hwdep_pcm_shm = alloc_pages_exact(USX2Y_HWDEP_PCM_PAGES, + GFP_KERNEL); + if (!usx2y->hwdep_pcm_shm) { + err = -ENOMEM; + goto up_prepare_mutex; + } + memset(usx2y->hwdep_pcm_shm, 0, USX2Y_HWDEP_PCM_PAGES); + } + + usx2y_subs_prepare(subs); + // Start hardware streams + // SyncStream first.... + if (atomic_read(&capsubs->state) < STATE_PREPARED) { + if (usx2y->format != runtime->format) { + err = usx2y_format_set(usx2y, runtime->format); + if (err < 0) + goto up_prepare_mutex; + } + if (usx2y->rate != runtime->rate) { + err = usx2y_rate_set(usx2y, runtime->rate); + if (err < 0) + goto up_prepare_mutex; + } + snd_printdd("starting capture pipe for %s\n", subs == capsubs ? + "self" : "playpipe"); + err = usx2y_usbpcm_urbs_start(capsubs); + if (err < 0) + goto up_prepare_mutex; + } + + if (subs != capsubs) { + usx2y->hwdep_pcm_shm->playback_iso_start = -1; + if (atomic_read(&subs->state) < STATE_PREPARED) { + while (usx2y_iso_frames_per_buffer(runtime, usx2y) > + usx2y->hwdep_pcm_shm->captured_iso_frames) { + snd_printdd("Wait: iso_frames_per_buffer=%i,captured_iso_frames=%i\n", + usx2y_iso_frames_per_buffer(runtime, usx2y), + usx2y->hwdep_pcm_shm->captured_iso_frames); + if (msleep_interruptible(10)) { + err = -ERESTARTSYS; + goto up_prepare_mutex; + } + } + err = usx2y_usbpcm_urbs_start(subs); + if (err < 0) + goto up_prepare_mutex; + } + snd_printdd("Ready: iso_frames_per_buffer=%i,captured_iso_frames=%i\n", + usx2y_iso_frames_per_buffer(runtime, usx2y), + usx2y->hwdep_pcm_shm->captured_iso_frames); + } else { + usx2y->hwdep_pcm_shm->capture_iso_start = -1; + } + + up_prepare_mutex: + mutex_unlock(&usx2y->pcm_mutex); + return err; +} + +static const struct snd_pcm_hardware snd_usx2y_4c = { + .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID), + .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S24_3LE, + .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 4, + .buffer_bytes_max = (2*128*1024), + .period_bytes_min = 64, + .period_bytes_max = (128*1024), + .periods_min = 2, + .periods_max = 1024, + .fifo_size = 0 +}; + +static int snd_usx2y_usbpcm_open(struct snd_pcm_substream *substream) +{ + struct snd_usx2y_substream *subs = + ((struct snd_usx2y_substream **) + snd_pcm_substream_chip(substream))[substream->stream]; + struct snd_pcm_runtime *runtime = substream->runtime; + + if (!(subs->usx2y->chip_status & USX2Y_STAT_CHIP_MMAP_PCM_URBS)) + return -EBUSY; + + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) + runtime->hw = snd_usx2y_2c; + else + runtime->hw = (subs->usx2y->subs[3] ? snd_usx2y_4c : snd_usx2y_2c); + runtime->private_data = subs; + subs->pcm_substream = substream; + snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, 1000, 200000); + return 0; +} + +static int snd_usx2y_usbpcm_close(struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct snd_usx2y_substream *subs = runtime->private_data; + + subs->pcm_substream = NULL; + return 0; +} + +static const struct snd_pcm_ops snd_usx2y_usbpcm_ops = { + .open = snd_usx2y_usbpcm_open, + .close = snd_usx2y_usbpcm_close, + .hw_params = snd_usx2y_pcm_hw_params, + .hw_free = snd_usx2y_usbpcm_hw_free, + .prepare = snd_usx2y_usbpcm_prepare, + .trigger = snd_usx2y_pcm_trigger, + .pointer = snd_usx2y_pcm_pointer, +}; + +static int usx2y_pcms_busy_check(struct snd_card *card) +{ + struct usx2ydev *dev = usx2y(card); + struct snd_usx2y_substream *subs; + int i; + + for (i = 0; i < dev->pcm_devs * 2; i++) { + subs = dev->subs[i]; + if (subs && subs->pcm_substream && + SUBSTREAM_BUSY(subs->pcm_substream)) + return -EBUSY; + } + return 0; +} + +static int snd_usx2y_hwdep_pcm_open(struct snd_hwdep *hw, struct file *file) +{ + struct snd_card *card = hw->card; + int err; + + mutex_lock(&usx2y(card)->pcm_mutex); + err = usx2y_pcms_busy_check(card); + if (!err) + usx2y(card)->chip_status |= USX2Y_STAT_CHIP_MMAP_PCM_URBS; + mutex_unlock(&usx2y(card)->pcm_mutex); + return err; +} + +static int snd_usx2y_hwdep_pcm_release(struct snd_hwdep *hw, struct file *file) +{ + struct snd_card *card = hw->card; + int err; + + mutex_lock(&usx2y(card)->pcm_mutex); + err = usx2y_pcms_busy_check(card); + if (!err) + usx2y(hw->card)->chip_status &= ~USX2Y_STAT_CHIP_MMAP_PCM_URBS; + mutex_unlock(&usx2y(card)->pcm_mutex); + return err; +} + +static void snd_usx2y_hwdep_pcm_vm_open(struct vm_area_struct *area) +{ +} + +static void snd_usx2y_hwdep_pcm_vm_close(struct vm_area_struct *area) +{ +} + +static vm_fault_t snd_usx2y_hwdep_pcm_vm_fault(struct vm_fault *vmf) +{ + unsigned long offset; + void *vaddr; + + offset = vmf->pgoff << PAGE_SHIFT; + vaddr = (char *)((struct usx2ydev *)vmf->vma->vm_private_data)->hwdep_pcm_shm + offset; + vmf->page = virt_to_page(vaddr); + get_page(vmf->page); + return 0; +} + +static const struct vm_operations_struct snd_usx2y_hwdep_pcm_vm_ops = { + .open = snd_usx2y_hwdep_pcm_vm_open, + .close = snd_usx2y_hwdep_pcm_vm_close, + .fault = snd_usx2y_hwdep_pcm_vm_fault, +}; + +static int snd_usx2y_hwdep_pcm_mmap(struct snd_hwdep *hw, struct file *filp, struct vm_area_struct *area) +{ + unsigned long size = (unsigned long)(area->vm_end - area->vm_start); + struct usx2ydev *usx2y = hw->private_data; + + if (!(usx2y->chip_status & USX2Y_STAT_CHIP_INIT)) + return -EBUSY; + + /* if userspace tries to mmap beyond end of our buffer, fail */ + if (size > USX2Y_HWDEP_PCM_PAGES) { + snd_printd("%lu > %lu\n", size, (unsigned long)USX2Y_HWDEP_PCM_PAGES); + return -EINVAL; + } + + if (!usx2y->hwdep_pcm_shm) + return -ENODEV; + + area->vm_ops = &snd_usx2y_hwdep_pcm_vm_ops; + vm_flags_set(area, VM_DONTEXPAND | VM_DONTDUMP); + area->vm_private_data = hw->private_data; + return 0; +} + +static void snd_usx2y_hwdep_pcm_private_free(struct snd_hwdep *hwdep) +{ + struct usx2ydev *usx2y = hwdep->private_data; + + if (usx2y->hwdep_pcm_shm) + free_pages_exact(usx2y->hwdep_pcm_shm, USX2Y_HWDEP_PCM_PAGES); +} + +int usx2y_hwdep_pcm_new(struct snd_card *card) +{ + int err; + struct snd_hwdep *hw; + struct snd_pcm *pcm; + struct usb_device *dev = usx2y(card)->dev; + + if (nr_of_packs() != 1) + return 0; + + err = snd_hwdep_new(card, SND_USX2Y_USBPCM_ID, 1, &hw); + if (err < 0) + return err; + + hw->iface = SNDRV_HWDEP_IFACE_USX2Y_PCM; + hw->private_data = usx2y(card); + hw->private_free = snd_usx2y_hwdep_pcm_private_free; + hw->ops.open = snd_usx2y_hwdep_pcm_open; + hw->ops.release = snd_usx2y_hwdep_pcm_release; + hw->ops.mmap = snd_usx2y_hwdep_pcm_mmap; + hw->exclusive = 1; + sprintf(hw->name, "/dev/bus/usb/%03d/%03d/hwdeppcm", dev->bus->busnum, dev->devnum); + + err = snd_pcm_new(card, NAME_ALLCAPS" hwdep Audio", 2, 1, 1, &pcm); + if (err < 0) + return err; + + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_usx2y_usbpcm_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usx2y_usbpcm_ops); + + pcm->private_data = usx2y(card)->subs; + pcm->info_flags = 0; + + sprintf(pcm->name, NAME_ALLCAPS" hwdep Audio"); + snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream, + SNDRV_DMA_TYPE_CONTINUOUS, + NULL, + 64*1024, 128*1024); + snd_pcm_set_managed_buffer(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream, + SNDRV_DMA_TYPE_CONTINUOUS, + NULL, + 64*1024, 128*1024); + + return 0; +} + +#else + +int usx2y_hwdep_pcm_new(struct snd_card *card) +{ + return 0; +} + +#endif diff --git a/sound/usb/usx2y/usx2yhwdeppcm.h b/sound/usb/usx2y/usx2yhwdeppcm.h new file mode 100644 index 0000000000..731b1c5a34 --- /dev/null +++ b/sound/usb/usx2y/usx2yhwdeppcm.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#define MAXPACK 50 +#define MAXBUFFERMS 100 +#define MAXSTRIDE 3 + +#define SSS (((MAXPACK*MAXBUFFERMS*MAXSTRIDE + 4096) / 4096) * 4096) +struct snd_usx2y_hwdep_pcm_shm { + char playback[SSS]; + char capture0x8[SSS]; + char capture0xA[SSS]; + volatile int playback_iso_head; + int playback_iso_start; + struct { + int frame, + offset, + length; + } captured_iso[128]; + volatile int captured_iso_head; + volatile unsigned captured_iso_frames; + int capture_iso_start; +}; + +int usx2y_hwdep_pcm_new(struct snd_card *card); |