Linux-2.6.12-rc2

Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.

Let it rip!
This commit is contained in:
Linus Torvalds 2005-04-16 15:20:36 -07:00
commit 1da177e4c3
17291 changed files with 6718755 additions and 0 deletions

32
sound/usb/Kconfig Normal file
View file

@ -0,0 +1,32 @@
# ALSA USB drivers
menu "USB devices"
depends on SND!=n && USB!=n
config SND_USB_AUDIO
tristate "USB Audio/MIDI driver"
depends on SND && USB
select SND_RAWMIDI
select SND_PCM
help
Say Y here to include support for USB audio and USB MIDI
devices.
To compile this driver as a module, choose M here: the module
will be called snd-usb-audio.
config SND_USB_USX2Y
tristate "Tascam US-122, US-224 and US-428 USB driver"
depends on SND && USB && (X86 || PPC || ALPHA)
select SND_HWDEP
select SND_RAWMIDI
select SND_PCM
help
Say Y here to include support for Tascam USB Audio/MIDI
interfaces or controllers US-122, US-224 and US-428.
To compile this driver as a module, choose M here: the module
will be called snd-usb-usx2y.
endmenu

12
sound/usb/Makefile Normal file
View file

@ -0,0 +1,12 @@
#
# Makefile for ALSA
#
snd-usb-audio-objs := usbaudio.o usbmixer.o
snd-usb-lib-objs := usbmidi.o
# Toplevel Module Dependency
obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o snd-usb-lib.o
obj-$(CONFIG_SND_USB_USX2Y) += snd-usb-lib.o
obj-$(CONFIG_SND) += usx2y/

3337
sound/usb/usbaudio.c Normal file

File diff suppressed because it is too large Load diff

251
sound/usb/usbaudio.h Normal file
View file

@ -0,0 +1,251 @@
#ifndef __USBAUDIO_H
#define __USBAUDIO_H
/*
* (Tentative) USB Audio Driver for ALSA
*
* Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
*/
#define USB_SUBCLASS_AUDIO_CONTROL 0x01
#define USB_SUBCLASS_AUDIO_STREAMING 0x02
#define USB_SUBCLASS_MIDI_STREAMING 0x03
#define USB_SUBCLASS_VENDOR_SPEC 0xff
#define CS_AUDIO_UNDEFINED 0x20
#define CS_AUDIO_DEVICE 0x21
#define CS_AUDIO_CONFIGURATION 0x22
#define CS_AUDIO_STRING 0x23
#define CS_AUDIO_INTERFACE 0x24
#define CS_AUDIO_ENDPOINT 0x25
#define HEADER 0x01
#define INPUT_TERMINAL 0x02
#define OUTPUT_TERMINAL 0x03
#define MIXER_UNIT 0x04
#define SELECTOR_UNIT 0x05
#define FEATURE_UNIT 0x06
#define PROCESSING_UNIT 0x07
#define EXTENSION_UNIT 0x08
#define AS_GENERAL 0x01
#define FORMAT_TYPE 0x02
#define FORMAT_SPECIFIC 0x03
#define EP_GENERAL 0x01
#define MS_GENERAL 0x01
#define MIDI_IN_JACK 0x02
#define MIDI_OUT_JACK 0x03
/* endpoint attributes */
#define EP_ATTR_MASK 0x0c
#define EP_ATTR_ASYNC 0x04
#define EP_ATTR_ADAPTIVE 0x08
#define EP_ATTR_SYNC 0x0c
/* cs endpoint attributes */
#define EP_CS_ATTR_SAMPLE_RATE 0x01
#define EP_CS_ATTR_PITCH_CONTROL 0x02
#define EP_CS_ATTR_FILL_MAX 0x80
/* Audio Class specific Request Codes */
#define SET_CUR 0x01
#define GET_CUR 0x81
#define SET_MIN 0x02
#define GET_MIN 0x82
#define SET_MAX 0x03
#define GET_MAX 0x83
#define SET_RES 0x04
#define GET_RES 0x84
#define SET_MEM 0x05
#define GET_MEM 0x85
#define GET_STAT 0xff
/* Terminal Control Selectors */
#define COPY_PROTECT_CONTROL 0x01
/* Endpoint Control Selectors */
#define SAMPLING_FREQ_CONTROL 0x01
#define PITCH_CONTROL 0x02
/* Format Types */
#define USB_FORMAT_TYPE_I 0x01
#define USB_FORMAT_TYPE_II 0x02
#define USB_FORMAT_TYPE_III 0x03
/* type I */
#define USB_AUDIO_FORMAT_PCM 0x01
#define USB_AUDIO_FORMAT_PCM8 0x02
#define USB_AUDIO_FORMAT_IEEE_FLOAT 0x03
#define USB_AUDIO_FORMAT_ALAW 0x04
#define USB_AUDIO_FORMAT_MU_LAW 0x05
/* type II */
#define USB_AUDIO_FORMAT_MPEG 0x1001
#define USB_AUDIO_FORMAT_AC3 0x1002
/* type III */
#define USB_AUDIO_FORMAT_IEC1937_AC3 0x2001
#define USB_AUDIO_FORMAT_IEC1937_MPEG1_LAYER1 0x2002
#define USB_AUDIO_FORMAT_IEC1937_MPEG2_NOEXT 0x2003
#define USB_AUDIO_FORMAT_IEC1937_MPEG2_EXT 0x2004
#define USB_AUDIO_FORMAT_IEC1937_MPEG2_LAYER1_LS 0x2005
#define USB_AUDIO_FORMAT_IEC1937_MPEG2_LAYER23_LS 0x2006
/* maximum number of endpoints per interface */
#define MIDI_MAX_ENDPOINTS 2
/*
*/
typedef struct snd_usb_audio snd_usb_audio_t;
struct snd_usb_audio {
int index;
struct usb_device *dev;
snd_card_t *card;
int shutdown;
int num_interfaces;
struct list_head pcm_list; /* list of pcm streams */
int pcm_devs;
struct list_head midi_list; /* list of midi interfaces */
int next_midi_device;
unsigned int ignore_ctl_error; /* for mixer */
};
/*
* Information about devices with broken descriptors
*/
/* special values for .ifnum */
#define QUIRK_NO_INTERFACE -2
#define QUIRK_ANY_INTERFACE -1
/* quirk type */
#define QUIRK_MIDI_FIXED_ENDPOINT 0
#define QUIRK_MIDI_YAMAHA 1
#define QUIRK_MIDI_MIDIMAN 2
#define QUIRK_COMPOSITE 3
#define QUIRK_AUDIO_FIXED_ENDPOINT 4
#define QUIRK_AUDIO_STANDARD_INTERFACE 5
#define QUIRK_MIDI_STANDARD_INTERFACE 6
#define QUIRK_AUDIO_EDIROL_UA700_UA25 7
#define QUIRK_AUDIO_EDIROL_UA1000 8
#define QUIRK_IGNORE_INTERFACE 9
#define QUIRK_MIDI_NOVATION 10
#define QUIRK_MIDI_MOTU 11
#define QUIRK_MIDI_EMAGIC 12
typedef struct snd_usb_audio_quirk snd_usb_audio_quirk_t;
typedef struct snd_usb_midi_endpoint_info snd_usb_midi_endpoint_info_t;
struct snd_usb_audio_quirk {
const char *vendor_name;
const char *product_name;
int16_t ifnum;
int16_t type;
const void *data;
};
/* data for QUIRK_MIDI_FIXED_ENDPOINT */
struct snd_usb_midi_endpoint_info {
int8_t out_ep; /* ep number, 0 autodetect */
uint8_t out_interval; /* interval for interrupt endpoints */
int8_t in_ep;
uint8_t in_interval;
uint16_t out_cables; /* bitmask */
uint16_t in_cables; /* bitmask */
};
/* for QUIRK_MIDI_YAMAHA, data is NULL */
/* for QUIRK_MIDI_MIDIMAN, data points to a snd_usb_midi_endpoint_info
* structure (out_cables and in_cables only) */
/* for QUIRK_COMPOSITE, data points to an array of snd_usb_audio_quirk
* structures, terminated with .ifnum = -1 */
/* for QUIRK_AUDIO_FIXED_ENDPOINT, data points to an audioformat structure */
/* for QUIRK_AUDIO/MIDI_STANDARD_INTERFACE, data is NULL */
/* for QUIRK_AUDIO_EDIROL_UA700_UA25/UA1000, data is NULL */
/* for QUIRK_IGNORE_INTERFACE, data is NULL */
/* for QUIRK_MIDI_NOVATION and _MOTU, data is NULL */
/* for QUIRK_MIDI_EMAGIC, data points to a snd_usb_midi_endpoint_info
* structure (out_cables and in_cables only) */
/*
*/
#define combine_word(s) ((*s) | ((unsigned int)(s)[1] << 8))
#define combine_triple(s) (combine_word(s) | ((unsigned int)(s)[2] << 16))
#define combine_quad(s) (combine_triple(s) | ((unsigned int)(s)[3] << 24))
unsigned int snd_usb_combine_bytes(unsigned char *bytes, int size);
void *snd_usb_find_desc(void *descstart, int desclen, void *after, u8 dtype);
void *snd_usb_find_csint_desc(void *descstart, int desclen, void *after, u8 dsubtype);
int snd_usb_ctl_msg(struct usb_device *dev, unsigned int pipe, __u8 request, __u8 requesttype, __u16 value, __u16 index, void *data, __u16 size, int timeout);
int snd_usb_create_mixer(snd_usb_audio_t *chip, int ctrlif);
int snd_usb_create_midi_interface(snd_usb_audio_t *chip, struct usb_interface *iface, const snd_usb_audio_quirk_t *quirk);
void snd_usbmidi_input_stop(struct list_head* p);
void snd_usbmidi_input_start(struct list_head* p);
void snd_usbmidi_disconnect(struct list_head *p, struct usb_driver *driver);
/*
* retrieve usb_interface descriptor from the host interface
* (conditional for compatibility with the older API)
*/
#ifndef get_iface_desc
#define get_iface_desc(iface) (&(iface)->desc)
#define get_endpoint(alt,ep) (&(alt)->endpoint[ep].desc)
#define get_ep_desc(ep) (&(ep)->desc)
#define get_cfg_desc(cfg) (&(cfg)->desc)
#endif
#ifndef usb_pipe_needs_resubmit
#define usb_pipe_needs_resubmit(pipe) 1
#endif
#ifndef snd_usb_complete_callback
#define snd_usb_complete_callback(x) (x)
#endif
#ifndef snd_usb_get_speed
#define snd_usb_get_speed(dev) ((dev)->speed)
#endif
#endif /* __USBAUDIO_H */

1564
sound/usb/usbmidi.c Normal file

File diff suppressed because it is too large Load diff

1545
sound/usb/usbmixer.c Normal file

File diff suppressed because it is too large Load diff

135
sound/usb/usbmixer_maps.c Normal file
View file

@ -0,0 +1,135 @@
/*
* Additional mixer mapping
*
* Copyright (c) 2002 by Takashi Iwai <tiwai@suse.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
struct usbmix_name_map {
int id;
const char *name;
int control;
};
struct usbmix_ctl_map {
int vendor;
int product;
const struct usbmix_name_map *map;
int ignore_ctl_error;
};
/*
* USB control mappers for SB Exitigy
*/
/*
* Topology of SB Extigy (see on the wide screen :)
USB_IN[1] --->FU[2]------------------------------+->MU[16]-->PU[17]-+->FU[18]--+->EU[27]--+->EU[21]-->FU[22]--+->FU[23] > Dig_OUT[24]
^ | | | |
USB_IN[3] -+->SU[5]-->FU[6]--+->MU[14] ->PU[15]->+ | | | +->FU[25] > Dig_OUT[26]
^ ^ | | | |
Dig_IN[4] -+ | | | | +->FU[28]---------------------> Spk_OUT[19]
| | | |
Lin-IN[7] -+-->FU[8]---------+ | | +----------------------------------------> Hph_OUT[20]
| | |
Mic-IN[9] --+->FU[10]----------------------------+ |
|| |
|| +----------------------------------------------------+
VV V
++--+->SU[11]-->FU[12] --------------------------------------------------------------------------------------> USB_OUT[13]
*/
static struct usbmix_name_map extigy_map[] = {
/* 1: IT pcm */
{ 2, "PCM Playback" }, /* FU */
/* 3: IT pcm */
/* 4: IT digital in */
{ 5, NULL }, /* DISABLED: this seems to be bogus on some firmware */
{ 6, "Digital In" }, /* FU */
/* 7: IT line */
{ 8, "Line Playback" }, /* FU */
/* 9: IT mic */
{ 10, "Mic Playback" }, /* FU */
{ 11, "Capture Input Source" }, /* SU */
{ 12, "Capture" }, /* FU */
/* 13: OT pcm capture */
/* 14: MU (w/o controls) */
/* 15: PU (3D enh) */
/* 16: MU (w/o controls) */
{ 17, NULL, 1 }, /* DISABLED: PU-switch (any effect?) */
{ 17, "Channel Routing", 2 }, /* PU: mode select */
{ 18, "Tone Control - Bass", USB_FEATURE_BASS }, /* FU */
{ 18, "Tone Control - Treble", USB_FEATURE_TREBLE }, /* FU */
{ 18, "Master Playback" }, /* FU; others */
/* 19: OT speaker */
/* 20: OT headphone */
{ 21, NULL }, /* DISABLED: EU (for what?) */
{ 22, "Digital Out Playback" }, /* FU */
{ 23, "Digital Out1 Playback" }, /* FU */ /* FIXME: corresponds to 24 */
/* 24: OT digital out */
{ 25, "IEC958 Optical Playback" }, /* FU */
{ 26, "IEC958 Optical Playback" }, /* OT */
{ 27, NULL }, /* DISABLED: EU (for what?) */
/* 28: FU speaker (mute) */
{ 29, NULL }, /* Digital Input Playback Source? */
{ 0 } /* terminator */
};
/* LineX FM Transmitter entry - needed to bypass controls bug */
static struct usbmix_name_map linex_map[] = {
/* 1: IT pcm */
/* 2: OT Speaker */
{ 3, "Master" }, /* FU: master volume - left / right / mute */
{ 0 } /* terminator */
};
/* Section "justlink_map" below added by James Courtier-Dutton <[email protected]>
* sourced from Maplin Electronics (http://www.maplin.co.uk), part number A56AK
* Part has 2 connectors that act as a single output. (TOSLINK Optical for digital out, and 3.5mm Jack for Analogue out.)
* The USB Mixer publishes a Microphone and extra Volume controls for it, but none exist on the device,
* so this map removes all unwanted sliders from alsamixer
*/
static struct usbmix_name_map justlink_map[] = {
/* 1: IT pcm playback */
/* 2: Not present */
{ 3, NULL}, /* IT mic (No mic input on device) */
/* 4: Not present */
/* 5: OT speacker */
/* 6: OT pcm capture */
{ 7, "Master Playback" }, /* Mute/volume for speaker */
{ 8, NULL }, /* Capture Switch (No capture inputs on device) */
{ 9, NULL }, /* Capture Mute/volume (No capture inputs on device */
/* 0xa: Not present */
/* 0xb: MU (w/o controls) */
{ 0xc, NULL }, /* Mic feedback Mute/volume (No capture inputs on device) */
{ 0 } /* terminator */
};
/*
* Control map entries
*/
static struct usbmix_ctl_map usbmix_ctl_maps[] = {
{ 0x41e, 0x3000, extigy_map, 1 },
{ 0x8bb, 0x2702, linex_map, 1 },
{ 0xc45, 0x1158, justlink_map, 0 },
{ 0 } /* terminator */
};

1202
sound/usb/usbquirks.h Normal file

File diff suppressed because it is too large Load diff

3
sound/usb/usx2y/Makefile Normal file
View file

@ -0,0 +1,3 @@
snd-usb-usx2y-objs := usbusx2y.o usX2Yhwdep.o usx2yhwdeppcm.o
obj-$(CONFIG_SND_USB_USX2Y) += snd-usb-usx2y.o

View file

@ -0,0 +1,280 @@
/*
* Driver for Tascam US-X2Y USB soundcards
*
* FPGA Loader + ALSA Startup
*
* Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <sound/driver.h>
#include <linux/interrupt.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"
int usX2Y_hwdep_pcm_new(snd_card_t* card);
static struct page * snd_us428ctls_vm_nopage(struct vm_area_struct *area, unsigned long address, int *type)
{
unsigned long offset;
struct page * page;
void *vaddr;
snd_printdd("ENTER, start %lXh, ofs %lXh, pgoff %ld, addr %lXh\n",
area->vm_start,
address - area->vm_start,
(address - area->vm_start) >> PAGE_SHIFT,
address);
offset = area->vm_pgoff << PAGE_SHIFT;
offset += address - area->vm_start;
snd_assert((offset % PAGE_SIZE) == 0, return NOPAGE_OOM);
vaddr = (char*)((usX2Ydev_t*)area->vm_private_data)->us428ctls_sharedmem + offset;
page = virt_to_page(vaddr);
get_page(page);
snd_printdd( "vaddr=%p made us428ctls_vm_nopage() return %p; offset=%lX\n", vaddr, page, offset);
if (type)
*type = VM_FAULT_MINOR;
return page;
}
static struct vm_operations_struct us428ctls_vm_ops = {
.nopage = snd_us428ctls_vm_nopage,
};
static int snd_us428ctls_mmap(snd_hwdep_t * hw, struct file *filp, struct vm_area_struct *area)
{
unsigned long size = (unsigned long)(area->vm_end - area->vm_start);
usX2Ydev_t *us428 = (usX2Ydev_t*)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 (!(((usX2Ydev_t*)hw->private_data)->chip_status & USX2Y_STAT_CHIP_INIT))
return -EBUSY;
/* if userspace tries to mmap beyond end of our buffer, fail */
if (size > ((PAGE_SIZE - 1 + sizeof(us428ctls_sharedmem_t)) / PAGE_SIZE) * PAGE_SIZE) {
snd_printd( "%lu > %lu\n", size, (unsigned long)sizeof(us428ctls_sharedmem_t));
return -EINVAL;
}
if (!us428->us428ctls_sharedmem) {
init_waitqueue_head(&us428->us428ctls_wait_queue_head);
if(!(us428->us428ctls_sharedmem = snd_malloc_pages(sizeof(us428ctls_sharedmem_t), GFP_KERNEL)))
return -ENOMEM;
memset(us428->us428ctls_sharedmem, -1, sizeof(us428ctls_sharedmem_t));
us428->us428ctls_sharedmem->CtlSnapShotLast = -2;
}
area->vm_ops = &us428ctls_vm_ops;
area->vm_flags |= VM_RESERVED;
area->vm_private_data = hw->private_data;
return 0;
}
static unsigned int snd_us428ctls_poll(snd_hwdep_t *hw, struct file *file, poll_table *wait)
{
unsigned int mask = 0;
usX2Ydev_t *us428 = (usX2Ydev_t*)hw->private_data;
us428ctls_sharedmem_t *shm = us428->us428ctls_sharedmem;
if (us428->chip_status & USX2Y_STAT_CHIP_HUP)
return POLLHUP;
poll_wait(file, &us428->us428ctls_wait_queue_head, wait);
if (shm != NULL && shm->CtlSnapShotLast != shm->CtlSnapShotRed)
mask |= POLLIN;
return mask;
}
static int snd_usX2Y_hwdep_open(snd_hwdep_t *hw, struct file *file)
{
return 0;
}
static int snd_usX2Y_hwdep_release(snd_hwdep_t *hw, struct file *file)
{
return 0;
}
static int snd_usX2Y_hwdep_dsp_status(snd_hwdep_t *hw, snd_hwdep_dsp_status_t *info)
{
static char *type_ids[USX2Y_TYPE_NUMS] = {
[USX2Y_TYPE_122] = "us122",
[USX2Y_TYPE_224] = "us224",
[USX2Y_TYPE_428] = "us428",
};
int id = -1;
switch (le16_to_cpu(((usX2Ydev_t*)hw->private_data)->chip.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 (0 > id)
return -ENODEV;
strcpy(info->id, type_ids[id]);
info->num_dsps = 2; // 0: Prepad Data, 1: FPGA Code
if (((usX2Ydev_t*)hw->private_data)->chip_status & USX2Y_STAT_CHIP_INIT)
info->chip_ready = 1;
info->version = USX2Y_DRIVER_VERSION;
return 0;
}
static int usX2Y_create_usbmidi(snd_card_t* card )
{
static snd_usb_midi_endpoint_info_t quirk_data_1 = {
.out_ep =0x06,
.in_ep = 0x06,
.out_cables = 0x001,
.in_cables = 0x001
};
static snd_usb_audio_quirk_t quirk_1 = {
.vendor_name = "TASCAM",
.product_name = NAME_ALLCAPS,
.ifnum = 0,
.type = QUIRK_MIDI_FIXED_ENDPOINT,
.data = &quirk_data_1
};
static snd_usb_midi_endpoint_info_t quirk_data_2 = {
.out_ep =0x06,
.in_ep = 0x06,
.out_cables = 0x003,
.in_cables = 0x003
};
static snd_usb_audio_quirk_t 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)->chip.dev;
struct usb_interface *iface = usb_ifnum_to_if(dev, 0);
snd_usb_audio_quirk_t *quirk = le16_to_cpu(dev->descriptor.idProduct) == USB_ID_US428 ? &quirk_2 : &quirk_1;
snd_printdd("usX2Y_create_usbmidi \n");
return snd_usb_create_midi_interface(&usX2Y(card)->chip, iface, quirk);
}
static int usX2Y_create_alsa_devices(snd_card_t* card)
{
int err;
do {
if ((err = usX2Y_create_usbmidi(card)) < 0) {
snd_printk("usX2Y_create_alsa_devices: usX2Y_create_usbmidi error %i \n", err);
break;
}
if ((err = usX2Y_audio_create(card)) < 0)
break;
if ((err = usX2Y_hwdep_pcm_new(card)) < 0)
break;
if ((err = snd_card_register(card)) < 0)
break;
} while (0);
return err;
}
static int snd_usX2Y_hwdep_dsp_load(snd_hwdep_t *hw, snd_hwdep_dsp_image_t *dsp)
{
usX2Ydev_t *priv = hw->private_data;
int lret, err = -EINVAL;
snd_printdd( "dsp_load %s\n", dsp->name);
if (access_ok(VERIFY_READ, dsp->image, dsp->length)) {
struct usb_device* dev = priv->chip.dev;
char *buf = kmalloc(dsp->length, GFP_KERNEL);
if (!buf)
return -ENOMEM;
if (copy_from_user(buf, dsp->image, dsp->length)) {
kfree(buf);
return -EFAULT;
}
err = usb_set_interface(dev, 0, 1);
if (err)
snd_printk("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) {
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(HZ/4); // give the device some time
err = usX2Y_AsyncSeq04_init(priv);
if (err) {
snd_printk("usX2Y_AsyncSeq04_init error \n");
return err;
}
err = usX2Y_In04_init(priv);
if (err) {
snd_printk("usX2Y_In04_init error \n");
return err;
}
err = usX2Y_create_alsa_devices(hw->card);
if (err) {
snd_printk("usX2Y_create_alsa_devices error %i \n", err);
snd_card_free(hw->card);
return err;
}
priv->chip_status |= USX2Y_STAT_CHIP_INIT;
snd_printdd("%s: alsa all started\n", hw->name);
}
return err;
}
int usX2Y_hwdep_new(snd_card_t* card, struct usb_device* device)
{
int err;
snd_hwdep_t *hw;
if ((err = snd_hwdep_new(card, SND_USX2Y_LOADER_ID, 0, &hw)) < 0)
return err;
hw->iface = SNDRV_HWDEP_IFACE_USX2Y;
hw->private_data = usX2Y(card);
hw->ops.open = snd_usX2Y_hwdep_open;
hw->ops.release = snd_usX2Y_hwdep_release;
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, "/proc/bus/usb/%03d/%03d", device->bus->busnum, device->devnum);
return 0;
}

View file

@ -0,0 +1,6 @@
#ifndef USX2YHWDEP_H
#define USX2YHWDEP_H
int usX2Y_hwdep_new(snd_card_t* card, struct usb_device* device);
#endif

View file

@ -0,0 +1,108 @@
/*
*
* Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
enum E_In84{
eFader0 = 0,
eFader1,
eFader2,
eFader3,
eFader4,
eFader5,
eFader6,
eFader7,
eFaderM,
eTransport,
eModifier = 10,
eFilterSelect,
eSelect,
eMute,
eSwitch = 15,
eWheelGain,
eWheelFreq,
eWheelQ,
eWheelPan,
eWheel = 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 FilterSelect;
unsigned char Select;
unsigned char Mute;
unsigned char UNKNOWN;
unsigned char Switch;
unsigned char Wheel[5];
};
typedef struct us428_ctls us428_ctls_t;
typedef struct us428_setByte{
unsigned char Offset,
Value;
}us428_setByte_t;
enum {
eLT_Volume = 0,
eLT_Light
};
typedef struct usX2Y_volume {
unsigned char Channel,
LH,
LL,
RH,
RL;
} usX2Y_volume_t;
struct us428_lights{
us428_setByte_t Light[7];
};
typedef struct us428_lights us428_lights_t;
typedef struct {
char type;
union {
usX2Y_volume_t vol;
us428_lights_t lights;
} val;
} us428_p4out_t;
#define N_us428_ctl_BUFS 16
#define N_us428_p4out_BUFS 16
struct us428ctls_sharedmem{
us428_ctls_t CtlSnapShot[N_us428_ctl_BUFS];
int CtlSnapShotDiffersAt[N_us428_ctl_BUFS];
int CtlSnapShotLast, CtlSnapShotRed;
us428_p4out_t p4out[N_us428_p4out_BUFS];
int p4outLast, p4outSent;
};
typedef struct us428ctls_sharedmem us428ctls_sharedmem_t;

461
sound/usb/usx2y/usbusx2y.c Normal file
View file

@ -0,0 +1,461 @@
/*
* usbusy2y.c - ALSA USB US-428 Driver
*
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
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <sound/driver.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.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 <[email protected]>");
MODULE_DESCRIPTION("TASCAM "NAME_ALLCAPS" Version 0.8.7.1");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("{{TASCAM(0x1604), "NAME_ALLCAPS"(0x8001)(0x8005)(0x8007) }}");
static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
static char* id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */
static int 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 usX2Y_usb_disconnect(struct usb_device* usb_device, void* ptr);
static void snd_usX2Y_card_private_free(snd_card_t *card);
/*
* pipe 4 is used for switching the lamps, setting samplerate, volumes ....
*/
static void i_usX2Y_Out04Int(struct urb* urb, struct pt_regs *regs)
{
#ifdef CONFIG_SND_DEBUG
if (urb->status) {
int i;
usX2Ydev_t* usX2Y = urb->context;
for (i = 0; i < 10 && usX2Y->AS04.urb[i] != urb; i++);
snd_printdd("i_usX2Y_Out04Int() urb %i status=%i\n", i, urb->status);
}
#endif
}
static void i_usX2Y_In04Int(struct urb* urb, struct pt_regs *regs)
{
int err = 0;
usX2Ydev_t *usX2Y = urb->context;
us428ctls_sharedmem_t *us428ctls = usX2Y->us428ctls_sharedmem;
usX2Y->In04IntCalls++;
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->In04Buf)[8]); Master volume shows 0 here if fader is at max during boot ?!?
if (us428ctls) {
int diff = -1;
if (-2 == us428ctls->CtlSnapShotLast) {
diff = 0;
memcpy(usX2Y->In04Last, usX2Y->In04Buf, sizeof(usX2Y->In04Last));
us428ctls->CtlSnapShotLast = -1;
} else {
int i;
for (i = 0; i < 21; i++) {
if (usX2Y->In04Last[i] != ((char*)usX2Y->In04Buf)[i]) {
if (diff < 0)
diff = i;
usX2Y->In04Last[i] = ((char*)usX2Y->In04Buf)[i];
}
}
}
if (0 <= diff) {
int n = us428ctls->CtlSnapShotLast + 1;
if (n >= N_us428_ctl_BUFS || n < 0)
n = 0;
memcpy(us428ctls->CtlSnapShot + n, usX2Y->In04Buf, sizeof(us428ctls->CtlSnapShot[0]));
us428ctls->CtlSnapShotDiffersAt[n] = diff;
us428ctls->CtlSnapShotLast = n;
wake_up(&usX2Y->us428ctls_wait_queue_head);
}
}
if (usX2Y->US04) {
if (0 == 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->p4outLast >= 0 && us428ctls->p4outLast < N_us428_p4out_BUFS) {
if (us428ctls->p4outLast != us428ctls->p4outSent) {
int j, send = us428ctls->p4outSent + 1;
if (send >= N_us428_p4out_BUFS)
send = 0;
for (j = 0; j < URBS_AsyncSeq && !err; ++j)
if (0 == usX2Y->AS04.urb[j]->status) {
us428_p4out_t *p4out = us428ctls->p4out + send; // FIXME if more then 1 p4out is new, 1 gets lost.
usb_fill_bulk_urb(usX2Y->AS04.urb[j], usX2Y->chip.dev,
usb_sndbulkpipe(usX2Y->chip.dev, 0x04), &p4out->val.vol,
p4out->type == eLT_Light ? sizeof(us428_lights_t) : 5,
i_usX2Y_Out04Int, usX2Y);
err = usb_submit_urb(usX2Y->AS04.urb[j], GFP_ATOMIC);
us428ctls->p4outSent = send;
break;
}
}
}
if (err) {
snd_printk("In04Int() usb_submit_urb err=%i\n", err);
}
urb->dev = usX2Y->chip.dev;
usb_submit_urb(urb, GFP_ATOMIC);
}
/*
* Prepare some urbs
*/
int usX2Y_AsyncSeq04_init(usX2Ydev_t* usX2Y)
{
int err = 0,
i;
if (NULL == (usX2Y->AS04.buffer = kmalloc(URB_DataLen_AsyncSeq*URBS_AsyncSeq, GFP_KERNEL))) {
err = -ENOMEM;
} else
for (i = 0; i < URBS_AsyncSeq; ++i) {
if (NULL == (usX2Y->AS04.urb[i] = usb_alloc_urb(0, GFP_KERNEL))) {
err = -ENOMEM;
break;
}
usb_fill_bulk_urb( usX2Y->AS04.urb[i], usX2Y->chip.dev,
usb_sndbulkpipe(usX2Y->chip.dev, 0x04),
usX2Y->AS04.buffer + URB_DataLen_AsyncSeq*i, 0,
i_usX2Y_Out04Int, usX2Y
);
}
return err;
}
int usX2Y_In04_init(usX2Ydev_t* usX2Y)
{
int err = 0;
if (! (usX2Y->In04urb = usb_alloc_urb(0, GFP_KERNEL)))
return -ENOMEM;
if (! (usX2Y->In04Buf = kmalloc(21, GFP_KERNEL))) {
usb_free_urb(usX2Y->In04urb);
return -ENOMEM;
}
init_waitqueue_head(&usX2Y->In04WaitQueue);
usb_fill_int_urb(usX2Y->In04urb, usX2Y->chip.dev, usb_rcvintpipe(usX2Y->chip.dev, 0x4),
usX2Y->In04Buf, 21,
i_usX2Y_In04Int, usX2Y,
10);
err = usb_submit_urb(usX2Y->In04urb, GFP_KERNEL);
return err;
}
static void usX2Y_unlinkSeq(snd_usX2Y_AsyncSeq_t* S)
{
int i;
for (i = 0; i < URBS_AsyncSeq; ++i) {
if (S[i].urb) {
usb_kill_urb(S->urb[i]);
usb_free_urb(S->urb[i]);
S->urb[i] = NULL;
}
}
kfree(S->buffer);
}
static 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 */ }
};
static snd_card_t* usX2Y_create_card(struct usb_device* device)
{
int dev;
snd_card_t* card;
for (dev = 0; dev < SNDRV_CARDS; ++dev)
if (enable[dev] && !snd_usX2Y_card_used[dev])
break;
if (dev >= SNDRV_CARDS)
return NULL;
card = snd_card_new(index[dev], id[dev], THIS_MODULE, sizeof(usX2Ydev_t));
if (!card)
return NULL;
snd_usX2Y_card_used[usX2Y(card)->chip.index = dev] = 1;
card->private_free = snd_usX2Y_card_private_free;
usX2Y(card)->chip.dev = device;
usX2Y(card)->chip.card = card;
init_waitqueue_head(&usX2Y(card)->prepare_wait_queue);
init_MUTEX (&usX2Y(card)->prepare_mutex);
INIT_LIST_HEAD(&usX2Y(card)->chip.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)->chip.dev->bus->busnum, usX2Y(card)->chip.dev->devnum
);
snd_card_set_dev(card, &device->dev);
return card;
}
static void* usX2Y_usb_probe(struct usb_device* device, struct usb_interface *intf, const struct usb_device_id* device_id)
{
int err;
snd_card_t* card;
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) ||
!(card = usX2Y_create_card(device)))
return NULL;
if ((err = usX2Y_hwdep_new(card, device)) < 0 ||
(err = snd_card_register(card)) < 0) {
snd_card_free(card);
return NULL;
}
return card;
}
/*
* new 2.5 USB kernel API
*/
static int snd_usX2Y_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
void *chip;
chip = usX2Y_usb_probe(interface_to_usbdev(intf), intf, id);
if (chip) {
dev_set_drvdata(&intf->dev, chip);
return 0;
} else
return -EIO;
}
static void snd_usX2Y_disconnect(struct usb_interface *intf)
{
usX2Y_usb_disconnect(interface_to_usbdev(intf),
dev_get_drvdata(&intf->dev));
}
MODULE_DEVICE_TABLE(usb, snd_usX2Y_usb_id_table);
static struct usb_driver snd_usX2Y_usb_driver = {
.owner = THIS_MODULE,
.name = "snd-usb-usx2y",
.probe = snd_usX2Y_probe,
.disconnect = snd_usX2Y_disconnect,
.id_table = snd_usX2Y_usb_id_table,
};
static void snd_usX2Y_card_private_free(snd_card_t *card)
{
kfree(usX2Y(card)->In04Buf);
usb_free_urb(usX2Y(card)->In04urb);
if (usX2Y(card)->us428ctls_sharedmem)
snd_free_pages(usX2Y(card)->us428ctls_sharedmem, sizeof(*usX2Y(card)->us428ctls_sharedmem));
if (usX2Y(card)->chip.index >= 0 && usX2Y(card)->chip.index < SNDRV_CARDS)
snd_usX2Y_card_used[usX2Y(card)->chip.index] = 0;
}
/*
* Frees the device.
*/
static void usX2Y_usb_disconnect(struct usb_device* device, void* ptr)
{
if (ptr) {
usX2Ydev_t* usX2Y = usX2Y((snd_card_t*)ptr);
struct list_head* p;
if (usX2Y->chip_status == USX2Y_STAT_CHIP_HUP) // on 2.6.1 kernel snd_usbmidi_disconnect()
return; // calls us back. better leave :-) .
usX2Y->chip.shutdown = 1;
usX2Y->chip_status = USX2Y_STAT_CHIP_HUP;
usX2Y_unlinkSeq(&usX2Y->AS04);
usb_kill_urb(usX2Y->In04urb);
snd_card_disconnect((snd_card_t*)ptr);
/* release the midi resources */
list_for_each(p, &usX2Y->chip.midi_list) {
snd_usbmidi_disconnect(p, &snd_usX2Y_usb_driver);
}
if (usX2Y->us428ctls_sharedmem)
wake_up(&usX2Y->us428ctls_wait_queue_head);
snd_card_free_in_thread((snd_card_t*)ptr);
}
}
static int __init snd_usX2Y_module_init(void)
{
return usb_register(&snd_usX2Y_usb_driver);
}
static void __exit snd_usX2Y_module_exit(void)
{
usb_deregister(&snd_usX2Y_usb_driver);
}
module_init(snd_usX2Y_module_init)
module_exit(snd_usX2Y_module_exit)

View file

@ -0,0 +1,84 @@
#ifndef USBUSX2Y_H
#define USBUSX2Y_H
#include "../usbaudio.h"
#include "usbus428ctldefs.h"
#define NRURBS 2
#define URBS_AsyncSeq 10
#define URB_DataLen_AsyncSeq 32
typedef struct {
struct urb* urb[URBS_AsyncSeq];
char* buffer;
} snd_usX2Y_AsyncSeq_t;
typedef struct {
int submitted;
int len;
struct urb* urb[0];
} snd_usX2Y_urbSeq_t;
typedef struct snd_usX2Y_substream snd_usX2Y_substream_t;
#include "usx2yhwdeppcm.h"
typedef struct {
snd_usb_audio_t chip;
int stride;
struct urb *In04urb;
void *In04Buf;
char In04Last[24];
unsigned In04IntCalls;
snd_usX2Y_urbSeq_t *US04;
wait_queue_head_t In04WaitQueue;
snd_usX2Y_AsyncSeq_t AS04;
unsigned int rate,
format;
int chip_status;
struct semaphore prepare_mutex;
us428ctls_sharedmem_t *us428ctls_sharedmem;
int wait_iso_frame;
wait_queue_head_t us428ctls_wait_queue_head;
snd_usX2Y_hwdep_pcm_shm_t *hwdep_pcm_shm;
snd_usX2Y_substream_t *subs[4];
snd_usX2Y_substream_t * volatile prepare_subs;
wait_queue_head_t prepare_wait_queue;
} usX2Ydev_t;
struct snd_usX2Y_substream {
usX2Ydev_t *usX2Y;
snd_pcm_substream_t *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) ((usX2Ydev_t*)(c)->private_data)
int usX2Y_audio_create(snd_card_t* card);
int usX2Y_AsyncSeq04_init(usX2Ydev_t* usX2Y);
int usX2Y_In04_init(usX2Ydev_t* usX2Y);
#define NAME_ALLCAPS "US-X2Y"
#endif

File diff suppressed because it is too large Load diff

51
sound/usb/usx2y/usx2y.h Normal file
View file

@ -0,0 +1,51 @@
/*
* Driver for Tascam US-X2Y USB soundcards
*
* Copyright (c) 2003 by Karsten Wiese <annabellesgarden@yahoo.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#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 */

View file

@ -0,0 +1,807 @@
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* 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 dependant 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, funcionalities should migrate to more apropriate 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 "usbusx2yaudio.c"
#if defined(USX2Y_NRPACKS_VARIABLE) || (!defined(USX2Y_NRPACKS_VARIABLE) && USX2Y_NRPACKS == 1)
#include <sound/hwdep.h>
static int usX2Y_usbpcm_urb_capt_retire(snd_usX2Y_substream_t *subs)
{
struct urb *urb = subs->completed_urb;
snd_pcm_runtime_t *runtime = subs->pcm_substream->runtime;
int i, lens = 0, hwptr_done = subs->hwptr_done;
usX2Ydev_t *usX2Y = subs->usX2Y;
if (0 > usX2Y->hwdep_pcm_shm->capture_iso_start) { //FIXME
int 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("activ frame status %i. Most propably 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;
}
if ((hwptr_done += lens) >= 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 inline int usX2Y_iso_frames_per_buffer(snd_pcm_runtime_t *runtime, usX2Ydev_t * 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(snd_usX2Y_substream_t *subs,
struct urb *urb)
{
int count, counts, pack;
usX2Ydev_t *usX2Y = subs->usX2Y;
struct snd_usX2Y_hwdep_pcm_shm *shm = usX2Y->hwdep_pcm_shm;
snd_pcm_runtime_t *runtime = subs->pcm_substream->runtime;
if (0 > shm->playback_iso_start) {
shm->playback_iso_start = shm->captured_iso_head -
usX2Y_iso_frames_per_buffer(runtime, usX2Y);
if (0 > shm->playback_iso_start)
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("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 inline void usX2Y_usbpcm_urb_capt_iso_advance(snd_usX2Y_substream_t *subs, struct urb *urb)
{
int pack;
for (pack = 0; pack < nr_of_packs(); ++pack) {
struct usb_iso_packet_descriptor *desc = urb->iso_frame_desc + pack;
if (NULL != subs) {
snd_usX2Y_hwdep_pcm_shm_t *shm = subs->usX2Y->hwdep_pcm_shm;
int 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++;
}
if ((desc->offset += desc->length * NRURBS*nr_of_packs()) +
desc->length >= SSS)
desc->offset -= (SSS - desc->length);
}
}
static inline int usX2Y_usbpcm_usbframe_complete(snd_usX2Y_substream_t *capsubs,
snd_usX2Y_substream_t *capsubs2,
snd_usX2Y_substream_t *playbacksubs, int frame)
{
int err, state;
struct urb *urb = playbacksubs->completed_urb;
state = atomic_read(&playbacksubs->state);
if (NULL != 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) {
if ((err = usX2Y_hwdep_urb_play_prepare(playbacksubs, urb)) ||
(err = usX2Y_urb_submit(playbacksubs, urb, frame))) {
return err;
}
}
playbacksubs->completed_urb = NULL;
state = atomic_read(&capsubs->state);
if (state >= state_PREPARED) {
if (state == state_RUNNING) {
if ((err = usX2Y_usbpcm_urb_capt_retire(capsubs)))
return err;
} else {
if (state >= state_PRERUNNING)
atomic_inc(&capsubs->state);
}
usX2Y_usbpcm_urb_capt_iso_advance(capsubs, capsubs->completed_urb);
if (NULL != capsubs2)
usX2Y_usbpcm_urb_capt_iso_advance(NULL, capsubs2->completed_urb);
if ((err = usX2Y_urb_submit(capsubs, capsubs->completed_urb, frame)))
return err;
if (NULL != capsubs2)
if ((err = usX2Y_urb_submit(capsubs2, capsubs2->completed_urb, frame)))
return err;
}
capsubs->completed_urb = NULL;
if (NULL != capsubs2)
capsubs2->completed_urb = NULL;
return 0;
}
static void i_usX2Y_usbpcm_urb_complete(struct urb *urb, struct pt_regs *regs)
{
snd_usX2Y_substream_t *subs = (snd_usX2Y_substream_t*)urb->context;
usX2Ydev_t *usX2Y = subs->usX2Y;
snd_usX2Y_substream_t *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->chip.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;
}
if (likely((0xFFFF & urb->start_frame) == usX2Y->wait_iso_frame))
subs->completed_urb = urb;
else {
usX2Y_error_sequence(usX2Y, subs, urb);
return;
}
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 &&
(NULL == capsubs2 || capsubs2->completed_urb) &&
(playbacksubs->completed_urb || atomic_read(&playbacksubs->state) < state_PREPARED)) {
if (!usX2Y_usbpcm_usbframe_complete(capsubs, capsubs2, playbacksubs, urb->start_frame)) {
if (nr_of_packs() <= urb->start_frame &&
urb->start_frame <= (2 * nr_of_packs() - 1)) // uhci and ohci
usX2Y->wait_iso_frame = urb->start_frame - nr_of_packs();
else
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(snd_usX2Y_substream_t *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(usX2Ydev_t * 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 pt_regs *regs)
{
snd_usX2Y_substream_t *subs = (snd_usX2Y_substream_t*)urb->context;
usX2Ydev_t *usX2Y = subs->usX2Y;
snd_usX2Y_substream_t *prepare_subs = usX2Y->prepare_subs;
if (NULL != 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]) {
snd_usX2Y_substream_t *cap_subs2 = usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE + 2];
if (cap_subs2 != NULL)
atomic_inc(&cap_subs2->state);
}
usX2Y_usbpcm_subs_startup_finish(usX2Y);
wake_up(&usX2Y->prepare_wait_queue);
}
i_usX2Y_usbpcm_urb_complete(urb, regs);
}
/*
* initialize a substream's urbs
*/
static int usX2Y_usbpcm_urbs_allocate(snd_usX2Y_substream_t *subs)
{
int i;
unsigned int pipe;
int is_playback = subs == subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
struct usb_device *dev = subs->usX2Y->chip.dev;
pipe = is_playback ? usb_sndisocpipe(dev, subs->endpoint) :
usb_rcvisocpipe(dev, subs->endpoint);
subs->maxpacksize = usb_maxpacket(dev, pipe, is_playback);
if (!subs->maxpacksize)
return -EINVAL;
/* allocate and initialize data urbs */
for (i = 0; i < NRURBS; i++) {
struct urb** purb = subs->urb + i;
if (*purb) {
usb_kill_urb(*purb);
continue;
}
*purb = usb_alloc_urb(nr_of_packs(), GFP_KERNEL);
if (NULL == *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(snd_pcm_substream_t *substream)
{
snd_pcm_runtime_t *runtime = substream->runtime;
snd_usX2Y_substream_t *subs = (snd_usX2Y_substream_t *)runtime->private_data,
*cap_subs2 = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE + 2];
down(&subs->usX2Y->prepare_mutex);
snd_printdd("snd_usX2Y_usbpcm_hw_free(%p)\n", substream);
if (SNDRV_PCM_STREAM_PLAYBACK == substream->stream) {
snd_usX2Y_substream_t *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->status ||
cap_subs->pcm_substream->runtime->status->state < SNDRV_PCM_STATE_PREPARED) {
atomic_set(&cap_subs->state, state_STOPPED);
if (NULL != cap_subs2)
atomic_set(&cap_subs2->state, state_STOPPED);
usX2Y_usbpcm_urbs_release(cap_subs);
if (NULL != cap_subs2)
usX2Y_usbpcm_urbs_release(cap_subs2);
}
} else {
snd_usX2Y_substream_t *playback_subs = subs->usX2Y->subs[SNDRV_PCM_STREAM_PLAYBACK];
if (atomic_read(&playback_subs->state) < state_PREPARED) {
atomic_set(&subs->state, state_STOPPED);
if (NULL != cap_subs2)
atomic_set(&cap_subs2->state, state_STOPPED);
usX2Y_usbpcm_urbs_release(subs);
if (NULL != cap_subs2)
usX2Y_usbpcm_urbs_release(cap_subs2);
}
}
up(&subs->usX2Y->prepare_mutex);
return snd_pcm_lib_free_pages(substream);
}
static void usX2Y_usbpcm_subs_startup(snd_usX2Y_substream_t *subs)
{
usX2Ydev_t * usX2Y = subs->usX2Y;
usX2Y->prepare_subs = subs;
subs->urb[0]->start_frame = -1;
smp_wmb(); // Make shure 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(snd_usX2Y_substream_t *subs)
{
int p, u, err,
stream = subs->pcm_substream->stream;
usX2Ydev_t *usX2Y = subs->usX2Y;
if (SNDRV_PCM_STREAM_CAPTURE == stream) {
usX2Y->hwdep_pcm_shm->captured_iso_head = -1;
usX2Y->hwdep_pcm_shm->captured_iso_frames = 0;
}
for (p = 0; 3 >= (stream + p); p += 2) {
snd_usX2Y_substream_t *subs = usX2Y->subs[stream + p];
if (subs != NULL) {
if ((err = usX2Y_usbpcm_urbs_allocate(subs)) < 0)
return err;
subs->completed_urb = NULL;
}
}
for (p = 0; p < 4; p++) {
snd_usX2Y_substream_t *subs = usX2Y->subs[p];
if (subs != NULL && atomic_read(&subs->state) >= state_PREPARED)
goto start;
}
usX2Y->wait_iso_frame = -1;
start:
usX2Y_usbpcm_subs_startup(subs);
for (u = 0; u < NRURBS; u++) {
for (p = 0; 3 >= (stream + p); p += 2) {
snd_usX2Y_substream_t *subs = usX2Y->subs[stream + p];
if (subs != NULL) {
struct urb *urb = subs->urb[u];
if (usb_pipein(urb->pipe)) {
unsigned long pack;
if (0 == u)
atomic_set(&subs->state, state_STARTING3);
urb->dev = usX2Y->chip.dev;
urb->transfer_flags = URB_ISO_ASAP;
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();
if ((err = usb_submit_urb(urb, GFP_KERNEL)) < 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 (0 > usX2Y->wait_iso_frame)
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, NULL == 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 wroong > stop evrything
}
return err;
}
/*
* prepare callback
*
* set format and initialize urbs
*/
static int snd_usX2Y_usbpcm_prepare(snd_pcm_substream_t *substream)
{
snd_pcm_runtime_t *runtime = substream->runtime;
snd_usX2Y_substream_t *subs = (snd_usX2Y_substream_t *)runtime->private_data;
usX2Ydev_t *usX2Y = subs->usX2Y;
snd_usX2Y_substream_t *capsubs = subs->usX2Y->subs[SNDRV_PCM_STREAM_CAPTURE];
int err = 0;
snd_printdd("snd_usX2Y_pcm_prepare(%p)\n", substream);
if (NULL == usX2Y->hwdep_pcm_shm) {
if (NULL == (usX2Y->hwdep_pcm_shm = snd_malloc_pages(sizeof(snd_usX2Y_hwdep_pcm_shm_t), GFP_KERNEL)))
return -ENOMEM;
memset(usX2Y->hwdep_pcm_shm, 0, sizeof(snd_usX2Y_hwdep_pcm_shm_t));
}
down(&usX2Y->prepare_mutex);
usX2Y_subs_prepare(subs);
// Start hardware streams
// SyncStream first....
if (atomic_read(&capsubs->state) < state_PREPARED) {
if (usX2Y->format != runtime->format)
if ((err = usX2Y_format_set(usX2Y, runtime->format)) < 0)
goto up_prepare_mutex;
if (usX2Y->rate != runtime->rate)
if ((err = usX2Y_rate_set(usX2Y, runtime->rate)) < 0)
goto up_prepare_mutex;
snd_printdd("starting capture pipe for %s\n", subs == capsubs ? "self" : "playpipe");
if (0 > (err = usX2Y_usbpcm_urbs_start(capsubs)))
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) {
signed long timeout;
snd_printd("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);
set_current_state(TASK_INTERRUPTIBLE);
timeout = schedule_timeout(HZ/100 + 1);
if (signal_pending(current)) {
err = -ERESTARTSYS;
goto up_prepare_mutex;
}
}
if (0 > (err = usX2Y_usbpcm_urbs_start(subs)))
goto up_prepare_mutex;
}
snd_printd("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:
up(&usX2Y->prepare_mutex);
return err;
}
static snd_pcm_hardware_t 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(snd_pcm_substream_t *substream)
{
snd_usX2Y_substream_t *subs = ((snd_usX2Y_substream_t **)
snd_pcm_substream_chip(substream))[substream->stream];
snd_pcm_runtime_t *runtime = substream->runtime;
if (!(subs->usX2Y->chip_status & USX2Y_STAT_CHIP_MMAP_PCM_URBS))
return -EBUSY;
runtime->hw = SNDRV_PCM_STREAM_PLAYBACK == substream->stream ? snd_usX2Y_2c :
(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(snd_pcm_substream_t *substream)
{
snd_pcm_runtime_t *runtime = substream->runtime;
snd_usX2Y_substream_t *subs = (snd_usX2Y_substream_t *)runtime->private_data;
int err = 0;
snd_printd("\n");
subs->pcm_substream = NULL;
return err;
}
static snd_pcm_ops_t snd_usX2Y_usbpcm_ops =
{
.open = snd_usX2Y_usbpcm_open,
.close = snd_usX2Y_usbpcm_close,
.ioctl = snd_pcm_lib_ioctl,
.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_lock_check(snd_card_t *card)
{
struct list_head *list;
snd_device_t *dev;
snd_pcm_t *pcm;
int err = 0;
list_for_each(list, &card->devices) {
dev = snd_device(list);
if (dev->type != SNDRV_DEV_PCM)
continue;
pcm = dev->device_data;
down(&pcm->open_mutex);
}
list_for_each(list, &card->devices) {
int s;
dev = snd_device(list);
if (dev->type != SNDRV_DEV_PCM)
continue;
pcm = dev->device_data;
for (s = 0; s < 2; ++s) {
snd_pcm_substream_t *substream;
substream = pcm->streams[s].substream;
if (substream && substream->open_flag)
err = -EBUSY;
}
}
return err;
}
static void usX2Y_pcms_unlock(snd_card_t *card)
{
struct list_head *list;
snd_device_t *dev;
snd_pcm_t *pcm;
list_for_each(list, &card->devices) {
dev = snd_device(list);
if (dev->type != SNDRV_DEV_PCM)
continue;
pcm = dev->device_data;
up(&pcm->open_mutex);
}
}
static int snd_usX2Y_hwdep_pcm_open(snd_hwdep_t *hw, struct file *file)
{
// we need to be the first
snd_card_t *card = hw->card;
int err = usX2Y_pcms_lock_check(card);
if (0 == err)
usX2Y(card)->chip_status |= USX2Y_STAT_CHIP_MMAP_PCM_URBS;
usX2Y_pcms_unlock(card);
return err;
}
static int snd_usX2Y_hwdep_pcm_release(snd_hwdep_t *hw, struct file *file)
{
snd_card_t *card = hw->card;
int err = usX2Y_pcms_lock_check(card);
if (0 == err)
usX2Y(hw->card)->chip_status &= ~USX2Y_STAT_CHIP_MMAP_PCM_URBS;
usX2Y_pcms_unlock(card);
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 struct page * snd_usX2Y_hwdep_pcm_vm_nopage(struct vm_area_struct *area, unsigned long address, int *type)
{
unsigned long offset;
struct page *page;
void *vaddr;
offset = area->vm_pgoff << PAGE_SHIFT;
offset += address - area->vm_start;
snd_assert((offset % PAGE_SIZE) == 0, return NOPAGE_OOM);
vaddr = (char*)((usX2Ydev_t*)area->vm_private_data)->hwdep_pcm_shm + offset;
page = virt_to_page(vaddr);
if (type)
*type = VM_FAULT_MINOR;
return page;
}
static struct vm_operations_struct snd_usX2Y_hwdep_pcm_vm_ops = {
.open = snd_usX2Y_hwdep_pcm_vm_open,
.close = snd_usX2Y_hwdep_pcm_vm_close,
.nopage = snd_usX2Y_hwdep_pcm_vm_nopage,
};
static int snd_usX2Y_hwdep_pcm_mmap(snd_hwdep_t * hw, struct file *filp, struct vm_area_struct *area)
{
unsigned long size = (unsigned long)(area->vm_end - area->vm_start);
usX2Ydev_t *usX2Y = (usX2Ydev_t*)hw->private_data;
if (!(((usX2Ydev_t*)hw->private_data)->chip_status & USX2Y_STAT_CHIP_INIT))
return -EBUSY;
/* if userspace tries to mmap beyond end of our buffer, fail */
if (size > PAGE_ALIGN(sizeof(snd_usX2Y_hwdep_pcm_shm_t))) {
snd_printd("%lu > %lu\n", size, (unsigned long)sizeof(snd_usX2Y_hwdep_pcm_shm_t));
return -EINVAL;
}
if (!usX2Y->hwdep_pcm_shm) {
return -ENODEV;
}
area->vm_ops = &snd_usX2Y_hwdep_pcm_vm_ops;
area->vm_flags |= VM_RESERVED;
snd_printd("vm_flags=0x%lX\n", area->vm_flags);
area->vm_private_data = hw->private_data;
return 0;
}
static void snd_usX2Y_hwdep_pcm_private_free(snd_hwdep_t *hwdep)
{
usX2Ydev_t *usX2Y = (usX2Ydev_t *)hwdep->private_data;
if (NULL != usX2Y->hwdep_pcm_shm)
snd_free_pages(usX2Y->hwdep_pcm_shm, sizeof(snd_usX2Y_hwdep_pcm_shm_t));
}
static void snd_usX2Y_usbpcm_private_free(snd_pcm_t *pcm)
{
snd_pcm_lib_preallocate_free_for_all(pcm);
}
int usX2Y_hwdep_pcm_new(snd_card_t* card)
{
int err;
snd_hwdep_t *hw;
snd_pcm_t *pcm;
struct usb_device *dev = usX2Y(card)->chip.dev;
if (1 != nr_of_packs())
return 0;
if ((err = snd_hwdep_new(card, SND_USX2Y_USBPCM_ID, 1, &hw)) < 0) {
snd_printd("\n");
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, "/proc/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->private_free = snd_usX2Y_usbpcm_private_free;
pcm->info_flags = 0;
sprintf(pcm->name, NAME_ALLCAPS" hwdep Audio");
if (0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream,
SNDRV_DMA_TYPE_CONTINUOUS,
snd_dma_continuous_data(GFP_KERNEL),
64*1024, 128*1024)) ||
0 > (err = snd_pcm_lib_preallocate_pages(pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream,
SNDRV_DMA_TYPE_CONTINUOUS,
snd_dma_continuous_data(GFP_KERNEL),
64*1024, 128*1024))) {
snd_usX2Y_usbpcm_private_free(pcm);
return err;
}
return 0;
}
#else
int usX2Y_hwdep_pcm_new(snd_card_t* card)
{
return 0;
}
#endif

View file

@ -0,0 +1,21 @@
#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;
};
typedef struct snd_usX2Y_hwdep_pcm_shm snd_usX2Y_hwdep_pcm_shm_t;