Commit a3954fd2 authored by Jaroslav Kysela's avatar Jaroslav Kysela Committed by Jaroslav Kysela

[PATCH] ALSA update [4/12] - 2002/08/14

  - added USB Audio and USB MIDI drivers
  - VIA686
    - AC97 cold reset only when AC-link is down
parent 8122d638
/* include/version.h. Generated automatically by configure. */
#define CONFIG_SND_VERSION "0.9.0rc2"
#define CONFIG_SND_DATE " (Tue Aug 13 16:29:18 2002 UTC)"
#define CONFIG_SND_DATE " (Wed Aug 14 17:22:18 2002 UTC)"
......@@ -5,7 +5,7 @@ export-objs := sound_core.o
obj-$(CONFIG_SOUND) += soundcore.o
obj-$(CONFIG_SOUND_PRIME) += oss/
obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ synth/ sparc/
obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ synth/ usb/ sparc/
ifeq ($(CONFIG_SND),y)
obj-y += last.o
......
......@@ -878,6 +878,16 @@ static int __devinit snd_via686a_chip_init(via686a_t *chip)
udelay(100);
}
/* Make sure VRA is enabled, in case we didn't do a
* complete codec reset, above */
pci_read_config_byte(chip->pci, 0x41, &pval);
if ((pval & 0xcc) != 0xcc) {
/* ACLink on, deassert ACLink reset, VSR, SGD data out */
/* note - FM data out has trouble with non VRA codecs !! */
pci_write_config_byte(chip->pci, 0x41, 0xcc);
udelay(100);
}
/* wait until codec ready */
max_count = ((3 * HZ) / 4) + 1;
do {
......
CONFIG_SND_USB_AUDIO
Say 'Y' or 'M' to include support for USB audio devices.
CONFIG_SND_USB_MIDI
Say 'Y' or 'M' to include support for MIDI devices connected via USB.
# ALSA USB drivers
mainmenu_option next_comment
comment 'ALSA USB devices'
dep_tristate 'USB Audio driver' CONFIG_SND_USB_AUDIO $CONFIG_SND
dep_tristate 'USB MIDI driver' CONFIG_SND_USB_MIDI $CONFIG_SND $CONFIG_SND_SEQUENCER
endmenu
#
# Makefile for ALSA
#
snd-usb-audio-objs := usbaudio.o usbmixer.o
snd-usb-midi-objs := usbmidi.o
# Toplevel Module Dependency
obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o
obj-$(CONFIG_SND_USB_MIDI) += snd-usb-midi.o
include $(TOPDIR)/Rules.make
/*
* (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)
*
*
* 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/bitops.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/usb.h>
#include <sound/core.h>
#include <sound/info.h>
#include <sound/pcm.h>
#define SNDRV_GET_ID
#include <sound/initval.h>
#include "usbaudio.h"
MODULE_AUTHOR("Takashi Iwai <tiwai@suse.de>");
MODULE_DESCRIPTION("USB Audio");
MODULE_LICENSE("GPL");
MODULE_CLASSES("{sound}");
MODULE_DEVICES("{{Generic,USB Audio}}");
static int snd_index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
static char *snd_id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
static int snd_enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
MODULE_PARM(snd_index, "1-" __MODULE_STRING(SNDRV_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for the USB audio adapter.");
MODULE_PARM_SYNTAX(snd_index, SNDRV_INDEX_DESC);
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SNDRV_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for the USB audio adapter.");
MODULE_PARM_SYNTAX(snd_id, SNDRV_ID_DESC);
MODULE_PARM(snd_enable, "1-" __MODULE_STRING(SNDRV_CARDS) "i");
MODULE_PARM_DESC(snd_enable, "Enable USB audio adapter.");
MODULE_PARM_SYNTAX(snd_enable, SNDRV_ENABLE_DESC);
/*
*
*/
#define NRPACKS 4 /* 4ms per urb */
#define MAX_URBS 5 /* max. 20ms long packets */
#define SYNC_URBS 2 /* always two urbs for sync */
#define MIN_PACKS_URB 1 /* minimum 1 packet per urb */
typedef struct snd_usb_substream snd_usb_substream_t;
typedef struct snd_usb_stream snd_usb_stream_t;
typedef struct snd_urb_ctx snd_urb_ctx_t;
struct audioformat {
struct list_head list;
snd_pcm_format_t format; /* format type */
int channels; /* # channels */
unsigned char altsetting; /* corresponding alternate setting */
unsigned char altset_idx; /* array index of altenate setting */
unsigned char attributes; /* corresponding attributes of cs endpoint */
unsigned char endpoint; /* endpoint */
unsigned char ep_attr; /* endpoint attributes */
unsigned int rates; /* rate bitmasks */
int rate_min, rate_max; /* min/max rates */
int nr_rates; /* number of rate table entries */
int *rate_table; /* rate table */
};
struct snd_urb_ctx {
struct urb *urb;
snd_usb_substream_t *subs;
int index; /* index for urb array */
int packets; /* number of packets per urb */
int transfer; /* transferred size */
char *buf; /* buffer for capture */
};
struct snd_urb_ops {
int (*prepare)(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime, struct urb *u);
int (*retire)(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime, struct urb *u);
int (*prepare_sync)(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime, struct urb *u);
int (*retire_sync)(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime, struct urb *u);
};
struct snd_usb_substream {
snd_usb_stream_t *stream;
struct usb_device *dev;
snd_pcm_substream_t *pcm_substream;
int interface; /* Interface number, -1 means not used */
unsigned int format; /* USB data format */
unsigned int datapipe; /* the data i/o pipe */
unsigned int syncpipe; /* 1 - async out or adaptive in */
unsigned int syncinterval; /* P for adaptive mode, 0 otherwise */
unsigned int freqn; /* nominal sampling rate in USB format, i.e. fs/1000 in Q10.14 */
unsigned int freqm; /* momentary sampling rate in USB format, i.e. fs/1000 in Q10.14 */
unsigned int freqmax; /* maximum sampling rate, used for buffer management */
unsigned int phase; /* phase accumulator */
unsigned int maxpacksize; /* max packet size in bytes */
unsigned int maxframesize; /* max packet size in frames */
unsigned int curpacksize; /* current packet size in bytes */
unsigned int curframesize; /* current packet size in frames */
unsigned int fill_max: 1; /* fill max packet size always */
unsigned int running: 1; /* running status */
int hwptr; /* free frame position in the buffer (only for playback) */
int hwptr_done; /* processed frame position in the buffer */
int transfer_sched; /* scheduled frames since last period (for playback) */
int transfer_done; /* processed frames since last period update */
unsigned long active_mask; /* bitmask of active urbs */
unsigned long unlink_mask; /* bitmask of unlinked urbs */
int nurbs; /* # urbs */
snd_urb_ctx_t dataurb[MAX_URBS]; /* data urb table */
snd_urb_ctx_t syncurb[SYNC_URBS]; /* sync urb table */
char syncbuf[SYNC_URBS * NRPACKS * 3]; /* sync buffer; it's so small - let's get static */
char *tmpbuf; /* temporary buffer for playback */
u64 formats; /* format bitmasks (all or'ed) */
int num_formats; /* number of supported audio formats (list) */
struct list_head fmt_list; /* format list */
spinlock_t lock;
struct snd_urb_ops ops; /* callbacks (must be filled at init) */
};
struct snd_usb_stream {
snd_usb_audio_t *chip;
snd_pcm_t *pcm;
int pcm_index;
snd_usb_substream_t substream[2];
struct list_head list;
snd_info_entry_t *proc_entry;
};
#define chip_t snd_usb_stream_t
/*
* we keep the snd_usb_audio_t instances by ourselves for merging
* the all interfaces on the same card as one sound device.
*/
static DECLARE_MUTEX(register_mutex);
static snd_usb_audio_t *usb_chip[SNDRV_CARDS];
/*
* convert a sampling rate into USB format (fs/1000 in Q10.14)
* this will overflow at approx 2MSPS
*/
inline static unsigned get_usb_rate(unsigned int rate)
{
return ((rate << 11) + 62) / 125;
}
/*
* prepare urb for capture sync pipe
*
* fill the length and offset of each urb descriptor.
* the fixed 10.14 frequency is passed through the pipe.
*/
static int prepare_capture_sync_urb(snd_usb_substream_t *subs,
snd_pcm_runtime_t *runtime,
struct urb *urb)
{
unsigned char *cp = urb->transfer_buffer;
snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context;
int i, offs;
urb->number_of_packets = ctx->packets;
urb->dev = ctx->subs->dev; /* we need to set this at each time */
for (i = offs = 0; i < urb->number_of_packets; i++, offs += 3, cp += 3) {
urb->iso_frame_desc[i].length = 3;
urb->iso_frame_desc[i].offset = offs;
cp[0] = subs->freqn;
cp[1] = subs->freqn >> 8;
cp[2] = subs->freqn >> 16;
}
urb->interval = 1;
return 0;
}
/*
* process after capture sync complete
* - nothing to do
*/
static int retire_capture_sync_urb(snd_usb_substream_t *subs,
snd_pcm_runtime_t *runtime,
struct urb *urb)
{
return 0;
}
/*
* prepare urb for capture data pipe
*
* fill the offset and length of each descriptor.
*
* we use a temporary buffer to write the captured data.
* since the length of written data is determined by host, we cannot
* write onto the pcm buffer directly... the data is thus copied
* later at complete callback to the global buffer.
*/
static int prepare_capture_urb(snd_usb_substream_t *subs,
snd_pcm_runtime_t *runtime,
struct urb *urb)
{
int i, offs;
unsigned long flags;
snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context;
offs = 0;
urb->dev = ctx->subs->dev; /* we need to set this at each time */
urb->number_of_packets = 0;
spin_lock_irqsave(&subs->lock, flags);
for (i = 0; i < ctx->packets; i++) {
urb->iso_frame_desc[i].offset = offs;
urb->iso_frame_desc[i].length = subs->curpacksize;
offs += subs->curpacksize;
urb->number_of_packets++;
subs->transfer_sched += subs->curframesize;
if (subs->transfer_sched >= runtime->period_size) {
subs->transfer_sched -= runtime->period_size;
break;
}
}
spin_unlock_irqrestore(&subs->lock, flags);
urb->transfer_buffer = ctx->buf;
urb->transfer_buffer_length = offs;
urb->interval = 1;
return 0;
}
/*
* process after capture complete
*
* copy the data from each desctiptor to the pcm buffer, and
* update the current position.
*/
static int retire_capture_urb(snd_usb_substream_t *subs,
snd_pcm_runtime_t *runtime,
struct urb *urb)
{
unsigned long flags;
unsigned char *cp;
int stride, i, len, oldptr;
stride = runtime->frame_bits >> 3;
for (i = 0; i < urb->number_of_packets; i++) {
cp = (unsigned char *)urb->transfer_buffer + urb->iso_frame_desc[i].offset;
if (urb->iso_frame_desc[i].status) /* active? hmm, skip this */
continue;
len = urb->iso_frame_desc[i].actual_length / stride;
if (! len)
continue;
/* update the current pointer */
spin_lock_irqsave(&subs->lock, flags);
oldptr = subs->hwptr_done;
subs->hwptr_done += len;
if (subs->hwptr_done >= runtime->buffer_size)
subs->hwptr_done -= runtime->buffer_size;
subs->transfer_done += len;
spin_unlock_irqrestore(&subs->lock, flags);
/* copy a data chunk */
if (oldptr + len > runtime->buffer_size) {
int cnt = runtime->buffer_size - oldptr;
int blen = cnt * stride;
memcpy(runtime->dma_area + oldptr * stride, cp, blen);
memcpy(runtime->dma_area, cp + blen, len * stride - blen);
} else {
memcpy(runtime->dma_area + oldptr * stride, cp, len * stride);
}
/* update the pointer, call callback if necessary */
spin_lock_irqsave(&subs->lock, flags);
if (subs->transfer_done >= runtime->period_size) {
subs->transfer_done -= runtime->period_size;
spin_unlock_irqrestore(&subs->lock, flags);
snd_pcm_period_elapsed(subs->pcm_substream);
} else
spin_unlock_irqrestore(&subs->lock, flags);
}
return 0;
}
/*
* prepare urb for playback sync pipe
*
* set up the offset and length to receive the current frequency.
*/
static int prepare_playback_sync_urb(snd_usb_substream_t *subs,
snd_pcm_runtime_t *runtime,
struct urb *urb)
{
int i, offs;
snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context;
urb->number_of_packets = ctx->packets;
urb->dev = ctx->subs->dev; /* we need to set this at each time */
for (i = offs = 0; i < urb->number_of_packets; i++, offs += 3) {
urb->iso_frame_desc[i].length = 3;
urb->iso_frame_desc[i].offset = offs;
}
return 0;
}
/*
* process after playback sync complete
*
* retrieve the current 10.14 frequency from pipe, and set it.
* the value is referred in prepare_playback_urb().
*/
static int retire_playback_sync_urb(snd_usb_substream_t *subs,
snd_pcm_runtime_t *runtime,
struct urb *urb)
{
unsigned int f, i, found;
unsigned char *cp = urb->transfer_buffer;
unsigned long flags;
found = 0;
for (i = 0; i < urb->number_of_packets; i++, cp += 3) {
if (urb->iso_frame_desc[i].status ||
urb->iso_frame_desc[i].actual_length < 3)
continue;
f = combine_triple(cp);
if (f < subs->freqn - (subs->freqn>>3) || f > subs->freqmax) {
snd_printd(KERN_WARNING "requested frequency %u (nominal %u) out of range!\n", f, subs->freqn);
continue;
}
found = f;
}
if (found) {
spin_lock_irqsave(&subs->lock, flags);
subs->freqm = found;
spin_unlock_irqrestore(&subs->lock, flags);
}
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 prepare_playback_urb(snd_usb_substream_t *subs,
snd_pcm_runtime_t *runtime,
struct urb *urb)
{
int i, stride, offs;
int counts;
unsigned long flags;
snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context;
stride = runtime->frame_bits >> 3;
offs = 0;
urb->dev = ctx->subs->dev; /* we need to set this at each time */
urb->number_of_packets = 0;
spin_lock_irqsave(&subs->lock, flags);
for (i = 0; i < ctx->packets; i++) {
/* calculate the size of a packet */
if (subs->fill_max)
counts = subs->maxframesize; /* fixed */
else {
subs->phase = (subs->phase & 0x3fff) + subs->freqm;
counts = subs->phase >> 14;
if (counts > subs->maxframesize)
counts = subs->maxframesize;
}
/* set up descriptor */
urb->iso_frame_desc[i].offset = offs * stride;
urb->iso_frame_desc[i].length = counts * stride;
offs += counts;
urb->number_of_packets++;
subs->transfer_sched += counts;
if (subs->transfer_sched >= runtime->period_size) {
subs->transfer_sched -= runtime->period_size;
break;
}
}
if (subs->hwptr + offs > runtime->buffer_size) {
/* err, the transferred area goes over buffer boundary.
* copy the data to the temp buffer.
*/
int len;
len = runtime->buffer_size - subs->hwptr;
urb->transfer_buffer = subs->tmpbuf;
memcpy(subs->tmpbuf, runtime->dma_area + subs->hwptr * stride, len * stride);
memcpy(subs->tmpbuf + len * stride, runtime->dma_area, (offs - len) * stride);
subs->hwptr += offs;
subs->hwptr -= runtime->buffer_size;
} else {
/* set the buffer pointer */
urb->transfer_buffer = runtime->dma_area + subs->hwptr * stride;
subs->hwptr += offs;
}
spin_unlock_irqrestore(&subs->lock, flags);
urb->transfer_buffer_length = offs * stride;
ctx->transfer = offs;
return 0;
}
/*
* process after playback data complete
*
* update the current position and call callback if a period is processed.
*/
static int retire_playback_urb(snd_usb_substream_t *subs,
snd_pcm_runtime_t *runtime,
struct urb *urb)
{
unsigned long flags;
snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context;
spin_lock_irqsave(&subs->lock, flags);
subs->transfer_done += ctx->transfer;
subs->hwptr_done += ctx->transfer;
ctx->transfer = 0;
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;
spin_unlock_irqrestore(&subs->lock, flags);
snd_pcm_period_elapsed(subs->pcm_substream);
} else
spin_unlock_irqrestore(&subs->lock, flags);
return 0;
}
/*
* complete callback from data urb
*/
static void snd_complete_urb(struct urb *urb)
{
snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context;
snd_usb_substream_t *subs = ctx->subs;
snd_pcm_substream_t *substream = ctx->subs->pcm_substream;
int err;
clear_bit(ctx->index, &subs->active_mask);
if (subs->running && subs->ops.retire(subs, substream->runtime, urb))
return;
if (! subs->running) /* can be stopped during retire callback */
return;
if ((err = subs->ops.prepare(subs, substream->runtime, urb) < 0) ||
(err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
snd_printd(KERN_ERR "cannot submit urb (err = %d)\n", err);
snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
return;
}
set_bit(ctx->index, &subs->active_mask);
}
/*
* complete callback from sync urb
*/
static void snd_complete_sync_urb(struct urb *urb)
{
snd_urb_ctx_t *ctx = (snd_urb_ctx_t *)urb->context;
snd_usb_substream_t *subs = ctx->subs;
snd_pcm_substream_t *substream = ctx->subs->pcm_substream;
int err;
clear_bit(ctx->index + 16, &subs->active_mask);
if (subs->running && subs->ops.retire_sync(subs, substream->runtime, urb))
return;
if (! subs->running) /* can be stopped during retire callback */
return;
if ((err = subs->ops.prepare_sync(subs, substream->runtime, urb)) < 0 ||
(err = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
snd_printd(KERN_ERR "cannot submit sync urb (err = %d)\n", err);
snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
return;
}
set_bit(ctx->index + 16, &subs->active_mask);
}
/*
* unlink active urbs.
* return the number of active urbs.
*/
static int deactivate_urbs(snd_usb_substream_t *subs)
{
int i, alive;
subs->running = 0;
alive = 0;
for (i = 0; i < subs->nurbs; i++) {
if (test_bit(i, &subs->active_mask)) {
alive++;
if (! test_and_set_bit(i, &subs->unlink_mask))
usb_unlink_urb(subs->dataurb[i].urb);
}
}
if (subs->syncpipe) {
for (i = 0; i < SYNC_URBS; i++) {
if (test_bit(i+16, &subs->active_mask)) {
alive++;
if (! test_and_set_bit(i+16, &subs->unlink_mask))
usb_unlink_urb(subs->syncurb[i].urb);
}
}
}
return alive;
}
/*
* set up and start data/sync urbs
*/
static int start_urbs(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime)
{
int i, err;
for (i = 0; i < subs->nurbs; i++) {
snd_assert(subs->dataurb[i].urb, return -EINVAL);
if (subs->ops.prepare(subs, runtime, subs->dataurb[i].urb) < 0) {
snd_printk(KERN_ERR "cannot prepare datapipe for urb %d\n", i);
goto __error;
}
}
if (subs->syncpipe) {
for (i = 0; i < SYNC_URBS; i++) {
snd_assert(subs->syncurb[i].urb, return -EINVAL);
if (subs->ops.prepare_sync(subs, runtime, subs->syncurb[i].urb) < 0) {
snd_printk(KERN_ERR "cannot prepare syncpipe for urb %d\n", i);
goto __error;
}
}
}
subs->running = 1;
for (i = 0; i < subs->nurbs; i++) {
if ((err = usb_submit_urb(subs->dataurb[i].urb, GFP_KERNEL)) < 0) {
snd_printk(KERN_ERR "cannot submit datapipe for urb %d, err = %d\n", i, err);
goto __error;
}
set_bit(i, &subs->active_mask);
}
if (subs->syncpipe) {
for (i = 0; i < SYNC_URBS; i++) {
if ((err = usb_submit_urb(subs->syncurb[i].urb, GFP_KERNEL)) < 0) {
snd_printk(KERN_ERR "cannot submit syncpipe for urb %d, err = %d\n", i, err);
goto __error;
}
set_bit(i + 16, &subs->active_mask);
}
}
return 0;
__error:
// snd_pcm_stop(subs->pcm_substream, SNDRV_PCM_STATE_XRUN);
deactivate_urbs(subs);
return -EPIPE;
}
/*
* wait until all urbs are processed.
*/
static int wait_clear_urbs(snd_usb_substream_t *subs)
{
int timeout = HZ;
int i, alive;
do {
alive = 0;
for (i = 0; i < subs->nurbs; i++) {
if (test_bit(i, &subs->active_mask))
alive++;
}
if (subs->syncpipe) {
for (i = 0; i < SYNC_URBS; i++) {
if (test_bit(i + 16, &subs->active_mask))
alive++;
}
}
if (! alive)
break;
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(1);
set_current_state(TASK_RUNNING);
} while (--timeout > 0);
if (alive)
snd_printk(KERN_ERR "timeout: still %d active urbs..\n", alive);
return 0;
}
/*
* return the current pcm pointer. just return the hwptr_done value.
*/
static snd_pcm_uframes_t snd_usb_pcm_pointer(snd_pcm_substream_t *substream)
{
snd_usb_substream_t *subs = (snd_usb_substream_t *)substream->runtime->private_data;
return subs->hwptr_done;
}
/*
* start/stop substream
*/
static int snd_usb_pcm_trigger(snd_pcm_substream_t *substream, int cmd)
{
snd_usb_substream_t *subs = (snd_usb_substream_t *)substream->runtime->private_data;
int err;
switch (cmd) {
case SNDRV_PCM_TRIGGER_START:
err = start_urbs(subs, substream->runtime);
break;
case SNDRV_PCM_TRIGGER_STOP:
err = deactivate_urbs(subs);
break;
default:
err = -EINVAL;
break;
}
return err < 0 ? err : 0;
}
/*
* release a urb data
*/
static void release_urb_ctx(snd_urb_ctx_t *u)
{
if (u->urb) {
usb_free_urb(u->urb);
u->urb = 0;
}
if (u->buf) {
kfree(u->buf);
u->buf = 0;
}
}
/*
* release a substream
*/
static void release_substream_urbs(snd_usb_substream_t *subs)
{
int i;
/* stop urbs (to be sure) */
if (deactivate_urbs(subs) > 0)
wait_clear_urbs(subs);
for (i = 0; i < MAX_URBS; i++)
release_urb_ctx(&subs->dataurb[i]);
for (i = 0; i < SYNC_URBS; i++)
release_urb_ctx(&subs->syncurb[i]);
if (subs->tmpbuf) {
kfree(subs->tmpbuf);
subs->tmpbuf = 0;
}
subs->nurbs = 0;
}
/*
* initialize a substream for plaback/capture
*/
static int init_substream_urbs(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime)
{
int maxsize, n, i;
int is_playback = subs->pcm_substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
int npacks[MAX_URBS], total_packs;
/* calculate the frequency in 10.14 format */
subs->freqn = subs->freqm = get_usb_rate(runtime->rate);
subs->freqmax = subs->freqn + (subs->freqn >> 2); /* max. allowed frequency */
subs->phase = 0;
/* reset the pointer */
subs->hwptr = 0;
subs->hwptr_done = 0;
subs->transfer_sched = 0;
subs->transfer_done = 0;
subs->active_mask = 0;
subs->unlink_mask = 0;
/* calculate the max. size of packet */
maxsize = ((subs->freqmax + 0x3fff) * (runtime->frame_bits >> 3)) >> 14;
if (subs->maxpacksize && maxsize > subs->maxpacksize) {
//snd_printd(KERN_DEBUG "maxsize %d is greater than defined size %d\n",
// maxsize, subs->maxpacksize);
maxsize = subs->maxpacksize;
}
if (subs->fill_max)
subs->curpacksize = subs->maxpacksize;
else
subs->curpacksize = maxsize;
/* allocate a temporary buffer for playback */
if (is_playback) {
subs->tmpbuf = kmalloc(maxsize * NRPACKS, GFP_KERNEL);
if (! subs->tmpbuf) {
snd_printk(KERN_ERR "cannot malloc tmpbuf\n");
return -ENOMEM;
}
}
/* decide how many packets to be used */
total_packs = (frames_to_bytes(runtime, runtime->period_size) + maxsize - 1) / maxsize;
if (total_packs < 2 * MIN_PACKS_URB)
total_packs = 2 * MIN_PACKS_URB;
subs->nurbs = (total_packs + NRPACKS - 1) / NRPACKS;
if (subs->nurbs > MAX_URBS) {
/* too much... */
subs->nurbs = MAX_URBS;
total_packs = MAX_URBS * NRPACKS;
}
n = total_packs;
for (i = 0; i < subs->nurbs; i++) {
npacks[i] = n > NRPACKS ? NRPACKS : n;
n -= NRPACKS;
}
if (subs->nurbs <= 1) {
/* too little - we need at least two packets
* to ensure contiguous playback/capture
*/
subs->nurbs = 2;
npacks[0] = (total_packs + 1) / 2;
npacks[1] = total_packs - npacks[0];
} else if (npacks[subs->nurbs-1] < MIN_PACKS_URB) {
/* the last packet is too small.. */
if (subs->nurbs > 2) {
/* merge to the first one */
npacks[0] += npacks[subs->nurbs - 1];
subs->nurbs--;
} else {
/* divide to two */
subs->nurbs = 2;
npacks[0] = (total_packs + 1) / 2;
npacks[1] = total_packs - npacks[0];
}
}
/* allocate and initialize data urbs */
for (i = 0; i < subs->nurbs; i++) {
snd_urb_ctx_t *u = &subs->dataurb[i];
u->index = i;
u->subs = subs;
u->transfer = 0;
u->packets = npacks[i];
if (! is_playback) {
/* allocate a capture buffer per urb */
u->buf = kmalloc(maxsize * u->packets, GFP_KERNEL);
if (! u->buf) {
release_substream_urbs(subs);
return -ENOMEM;
}
}
u->urb = usb_alloc_urb(u->packets, GFP_KERNEL);
if (! u->urb) {
release_substream_urbs(subs);
return -ENOMEM;
}
u->urb->dev = subs->dev;
u->urb->pipe = subs->datapipe;
u->urb->transfer_flags = USB_ISO_ASAP | USB_ASYNC_UNLINK;
u->urb->number_of_packets = u->packets;
u->urb->context = u;
u->urb->complete = snd_complete_urb;
}
if (subs->syncpipe) {
/* allocate and initialize sync urbs */
for (i = 0; i < SYNC_URBS; i++) {
snd_urb_ctx_t *u = &subs->syncurb[i];
u->index = i;
u->subs = subs;
u->packets = NRPACKS;
u->urb = usb_alloc_urb(u->packets, GFP_KERNEL);
if (! u->urb) {
release_substream_urbs(subs);
return -ENOMEM;
}
u->urb->transfer_buffer = subs->syncbuf + i * NRPACKS * 3;
u->urb->transfer_buffer_length = NRPACKS * 3;
u->urb->dev = subs->dev;
u->urb->pipe = subs->syncpipe;
u->urb->transfer_flags = USB_ISO_ASAP | USB_ASYNC_UNLINK;
u->urb->number_of_packets = u->packets;
u->urb->context = u;
u->urb->complete = snd_complete_sync_urb;
}
}
return 0;
}
/*
* find a matching audio format
*/
static struct audioformat *find_format(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime)
{
struct list_head *p;
list_for_each(p, &subs->fmt_list) {
struct audioformat *fp;
fp = list_entry(p, struct audioformat, list);
if (fp->format != runtime->format ||
fp->channels != runtime->channels)
continue;
if (runtime->rate < fp->rate_min || runtime->rate > fp->rate_max)
continue;
if (fp->rates & SNDRV_PCM_RATE_CONTINUOUS)
return fp;
else {
int i;
for (i = 0; i < fp->nr_rates; i++)
if (fp->rate_table[i] == runtime->rate)
return fp;
}
}
return NULL;
}
/*
* find a matching format and set up the interface
*/
static int set_format(snd_usb_substream_t *subs, snd_pcm_runtime_t *runtime)
{
struct usb_device *dev = subs->dev;
struct usb_config_descriptor *config = dev->actconfig;
struct usb_interface_descriptor *alts;
struct usb_interface *iface;
struct audioformat *fmt;
unsigned int ep, attr;
unsigned char data[3];
int is_playback = subs->pcm_substream->stream == SNDRV_PCM_STREAM_PLAYBACK;
int err;
fmt = find_format(subs, runtime);
if (! fmt) {
snd_printd(KERN_DEBUG "cannot set format: format = %s, rate = %d, channels = %d\n",
snd_pcm_format_name(runtime->format), runtime->rate, runtime->channels);
return -EINVAL;
}
iface = &config->interface[subs->interface];
alts = &iface->altsetting[fmt->altset_idx];
snd_assert(alts->bAlternateSetting == fmt->altsetting, return -EINVAL);
/* create a data pipe */
ep = alts->endpoint[0].bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
if (is_playback)
subs->datapipe = usb_sndisocpipe(dev, ep);
else
subs->datapipe = usb_rcvisocpipe(dev, ep);
subs->format = fmt->altset_idx;
subs->syncpipe = subs->syncinterval = 0;
subs->maxpacksize = alts->endpoint[0].wMaxPacketSize;
subs->maxframesize = bytes_to_frames(runtime, subs->maxpacksize);
subs->fill_max = 0;
/* we need a sync pipe in async OUT or adaptive IN mode */
attr = alts->endpoint[0].bmAttributes & EP_ATTR_MASK;
if ((is_playback && attr == EP_ATTR_ASYNC) ||
(! is_playback && attr == EP_ATTR_ADAPTIVE)) {
/* check endpoint */
if (alts->bNumEndpoints < 2 ||
alts->endpoint[1].bmAttributes != 0x01 ||
alts->endpoint[1].bSynchAddress != 0) {
snd_printk(KERN_ERR "%d:%d:%d : invalid synch pipe\n",
dev->devnum, subs->interface, fmt->altsetting);
return -EINVAL;
}
ep = alts->endpoint[1].bEndpointAddress;
if ((is_playback && ep != (alts->endpoint[0].bSynchAddress | USB_DIR_IN)) ||
(! is_playback && ep != (alts->endpoint[0].bSynchAddress & ~USB_DIR_IN))) {
snd_printk(KERN_ERR "%d:%d:%d : invalid synch pipe\n",
dev->devnum, subs->interface, fmt->altsetting);
return -EINVAL;
}
ep &= USB_ENDPOINT_NUMBER_MASK;
if (is_playback)
subs->syncpipe = usb_rcvisocpipe(dev, ep);
else
subs->syncpipe = usb_sndisocpipe(dev, ep);
subs->syncinterval = alts->endpoint[1].bRefresh;
}
/* set interface */
if (usb_set_interface(dev, subs->interface, fmt->altset_idx) < 0) {
snd_printk(KERN_ERR "%d:%d:%d: usb_set_interface failed\n",
dev->devnum, subs->interface, fmt->altsetting);
return -EIO;
}
ep = usb_pipeendpoint(subs->datapipe) | (subs->datapipe & USB_DIR_IN);
/* if endpoint has pitch control, enable it */
if (fmt->attributes & EP_CS_ATTR_PITCH_CONTROL) {
data[0] = 1;
if ((err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR,
USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT,
PITCH_CONTROL << 8, ep, data, 1, HZ)) < 0) {
snd_printk(KERN_ERR "%d:%d:%d: cannot set enable PITCH\n",
dev->devnum, subs->interface, ep);
return err;
}
}
/* if endpoint has sampling rate control, set it */
if (fmt->attributes & EP_CS_ATTR_SAMPLE_RATE) {
data[0] = runtime->rate;
data[1] = runtime->rate >> 8;
data[2] = runtime->rate >> 16;
if ((err = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), SET_CUR,
USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_OUT,
SAMPLING_FREQ_CONTROL << 8, ep, data, 3, HZ)) < 0) {
snd_printk(KERN_ERR "%d:%d:%d: cannot set freq %d to ep 0x%x\n",
dev->devnum, subs->interface, fmt->altsetting, runtime->rate, ep);
return err;
}
if ((err = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), GET_CUR,
USB_TYPE_CLASS|USB_RECIP_ENDPOINT|USB_DIR_IN,
SAMPLING_FREQ_CONTROL << 8, ep, data, 3, HZ)) < 0) {
snd_printk(KERN_ERR "%d:%d:%d: cannot get freq at ep 0x%x\n",
dev->devnum, subs->interface, fmt->altsetting, ep);
return err;
}
runtime->rate = data[0] | (data[1] << 8) | (data[2] << 16);
// printk("ok, getting back rate to %d\n", runtime->rate);
}
/* always fill max packet size */
if (fmt->attributes & EP_CS_ATTR_FILL_MAX)
subs->fill_max = 1;
#if 0
printk("setting done: format = %d, rate = %d, channels = %d\n",
runtime->format, runtime->rate, runtime->channels);
printk(" datapipe = 0x%0x, syncpipe = 0x%0x\n",
subs->datapipe, subs->syncpipe);
#endif
return 0;
}
/*
* allocate a buffer.
*
* 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.
*/
static int snd_usb_hw_params(snd_pcm_substream_t *substream,
snd_pcm_hw_params_t *hw_params)
{
return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));
}
/*
* free the buffer
*/
static int snd_usb_hw_free(snd_pcm_substream_t *substream)
{
return snd_pcm_lib_free_pages(substream);
}
/*
* prepare callback
*
* set format and initialize urbs
*/
static int snd_usb_pcm_prepare(snd_pcm_substream_t *substream)
{
snd_pcm_runtime_t *runtime = substream->runtime;
snd_usb_substream_t *subs = (snd_usb_substream_t *)runtime->private_data;
int err;
release_substream_urbs(subs);
if ((err = set_format(subs, runtime)) < 0)
return err;
return init_substream_urbs(subs, runtime);
}
static snd_pcm_hardware_t snd_usb_playback =
{
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID),
.buffer_bytes_max = (128*1024),
.period_bytes_min = 64,
.period_bytes_max = (128*1024),
.periods_min = 2,
.periods_max = 1024,
};
static snd_pcm_hardware_t snd_usb_capture =
{
.info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_BLOCK_TRANSFER |
SNDRV_PCM_INFO_MMAP_VALID),
.buffer_bytes_max = (128*1024),
.period_bytes_min = 64,
.period_bytes_max = (128*1024),
.periods_min = 2,
.periods_max = 1024,
};
/*
* set up the runtime hardware information.
*/
static void setup_hw_info(snd_pcm_runtime_t *runtime, snd_usb_substream_t *subs)
{
struct list_head *p;
runtime->hw.formats = subs->formats;
runtime->hw.rate_min = 0x7fffffff;
runtime->hw.rate_max = 0;
runtime->hw.channels_min = 256;
runtime->hw.channels_max = 0;
runtime->hw.rates = 0;
/* check min/max rates and channels */
list_for_each(p, &subs->fmt_list) {
struct audioformat *fp;
fp = list_entry(p, struct audioformat, list);
runtime->hw.rates |= fp->rates;
if (runtime->hw.rate_min > fp->rate_min)
runtime->hw.rate_min = fp->rate_min;
if (runtime->hw.rate_max < fp->rate_max)
runtime->hw.rate_max = fp->rate_max;
if (runtime->hw.channels_min > fp->channels)
runtime->hw.channels_min = fp->channels;
if (runtime->hw.channels_max < fp->channels)
runtime->hw.channels_max = fp->channels;
}
/* set the period time minimum 1ms */
snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME,
1000 * MIN_PACKS_URB,
/*(NRPACKS * MAX_URBS) * 1000*/ UINT_MAX);
/* FIXME: we need more constraints to restrict the format type,
* channels and rates according to the audioformat list!
*/
}
static int snd_usb_pcm_open(snd_pcm_substream_t *substream, int direction,
snd_pcm_hardware_t *hw)
{
snd_usb_stream_t *as = snd_pcm_substream_chip(substream);
snd_pcm_runtime_t *runtime = substream->runtime;
snd_usb_substream_t *subs = &as->substream[direction];
runtime->hw = *hw;
runtime->private_data = subs;
subs->pcm_substream = substream;
setup_hw_info(runtime, subs);
return 0;
}
static int snd_usb_pcm_close(snd_pcm_substream_t *substream, int direction)
{
snd_usb_stream_t *as = snd_pcm_substream_chip(substream);
snd_usb_substream_t *subs = &as->substream[direction];
release_substream_urbs(subs);
usb_set_interface(subs->dev, subs->interface, 0);
subs->pcm_substream = NULL;
return 0;
}
static int snd_usb_playback_open(snd_pcm_substream_t *substream)
{
return snd_usb_pcm_open(substream, SNDRV_PCM_STREAM_PLAYBACK, &snd_usb_playback);
}
static int snd_usb_playback_close(snd_pcm_substream_t *substream)
{
return snd_usb_pcm_close(substream, SNDRV_PCM_STREAM_PLAYBACK);
}
static int snd_usb_capture_open(snd_pcm_substream_t *substream)
{
return snd_usb_pcm_open(substream, SNDRV_PCM_STREAM_CAPTURE, &snd_usb_capture);
}
static int snd_usb_capture_close(snd_pcm_substream_t *substream)
{
return snd_usb_pcm_close(substream, SNDRV_PCM_STREAM_CAPTURE);
}
static snd_pcm_ops_t snd_usb_playback_ops = {
.open = snd_usb_playback_open,
.close = snd_usb_playback_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_usb_hw_params,
.hw_free = snd_usb_hw_free,
.prepare = snd_usb_pcm_prepare,
.trigger = snd_usb_pcm_trigger,
.pointer = snd_usb_pcm_pointer,
};
static snd_pcm_ops_t snd_usb_capture_ops = {
.open = snd_usb_capture_open,
.close = snd_usb_capture_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = snd_usb_hw_params,
.hw_free = snd_usb_hw_free,
.prepare = snd_usb_pcm_prepare,
.trigger = snd_usb_pcm_trigger,
.pointer = snd_usb_pcm_pointer,
};
/*
* helper functions
*/
/*
* combine bytes and get an integer value
*/
unsigned int snd_usb_combine_bytes(unsigned char *bytes, int size)
{
switch (size) {
case 1: return *bytes;
case 2: return combine_word(bytes);
case 3: return combine_triple(bytes);
case 4: return combine_quad(bytes);
default: return 0;
}
}
/*
* parse descriptor buffer and return the pointer starting the given
* descriptor type and interface.
* if altsetting is not -1, seek the buffer until the matching alternate
* setting is found.
*/
void *snd_usb_find_desc(void *descstart, int desclen, void *after,
u8 dtype, int iface, int altsetting)
{
u8 *p, *end, *next;
int ifc = -1, as = -1;
p = descstart;
end = p + desclen;
for (; p < end;) {
if (p[0] < 2)
return NULL;
next = p + p[0];
if (next > end)
return NULL;
if (p[1] == USB_DT_INTERFACE) {
/* minimum length of interface descriptor */
if (p[0] < 9)
return NULL;
ifc = p[2];
as = p[3];
}
if (p[1] == dtype && (!after || (void *)p > after) &&
(iface == -1 || iface == ifc) && (altsetting == -1 || altsetting == as)) {
return p;
}
p = next;
}
return NULL;
}
/*
* find a class-specified interface descriptor with the given subtype.
*/
void *snd_usb_find_csint_desc(void *buffer, int buflen, void *after, u8 dsubtype, int iface, int altsetting)
{
unsigned char *p = after;
while ((p = snd_usb_find_desc(buffer, buflen, p,
USB_DT_CS_INTERFACE, iface, altsetting)) != NULL) {
if (p[0] >= 3 && p[2] == dsubtype)
return p;
}
return NULL;
}
/*
* entry point for linux usb interface
*/
static void * usb_audio_probe(struct usb_device *dev, unsigned int ifnum,
const struct usb_device_id *id);
static void usb_audio_disconnect(struct usb_device *dev, void *ptr);
static struct usb_device_id usb_audio_ids [] = {
{ .match_flags = (USB_DEVICE_ID_MATCH_INT_CLASS | USB_DEVICE_ID_MATCH_INT_SUBCLASS),
.bInterfaceClass = USB_CLASS_AUDIO,
.bInterfaceSubClass = USB_SUBCLASS_AUDIO_CONTROL },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, usb_audio_ids);
static struct usb_driver usb_audio_driver = {
.name = "snd-usb-audio",
.probe = usb_audio_probe,
.disconnect = usb_audio_disconnect,
.driver_list = LIST_HEAD_INIT(usb_audio_driver.driver_list),
.id_table = usb_audio_ids,
};
/*
* intialize the substream instance.
*/
static void init_substream(snd_usb_stream_t *stream, snd_usb_substream_t *subs,
int iface_no, int is_input,
unsigned char *buffer, int buflen)
{
struct usb_device *dev;
struct usb_config_descriptor *config;
struct usb_interface *iface;
struct usb_interface_descriptor *alts;
int i, pcm_format, altno;
int format, channels, format_type, nr_rates;
struct audioformat *fp;
unsigned char *fmt, *csep;
dev = stream->chip->dev;
config = dev->actconfig;
subs->stream = stream;
subs->dev = dev;
subs->interface = iface_no;
INIT_LIST_HEAD(&subs->fmt_list);
spin_lock_init(&subs->lock);
if (is_input) {
subs->ops.prepare = prepare_capture_urb;
subs->ops.retire = retire_capture_urb;
subs->ops.prepare_sync = prepare_capture_sync_urb;
subs->ops.retire_sync = retire_capture_sync_urb;
} else {
subs->ops.prepare = prepare_playback_urb;
subs->ops.retire = retire_playback_urb;
subs->ops.prepare_sync = prepare_playback_sync_urb;
subs->ops.retire_sync = retire_playback_sync_urb;
}
if (iface_no < 0)
return;
/* parse the interface's altsettings */
iface = &config->interface[iface_no];
for (i = 0; i < iface->num_altsetting; i++) {
alts = &iface->altsetting[i];
/* skip invalid one */
if (alts->bInterfaceClass != USB_CLASS_AUDIO ||
alts->bInterfaceSubClass != USB_SUBCLASS_AUDIO_STREAMING ||
alts->bNumEndpoints < 1)
continue;
/* must be isochronous */
if ((alts->endpoint[0].bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) !=
USB_ENDPOINT_XFER_ISOC)
continue;
/* check direction */
if (alts->endpoint[0].bEndpointAddress & USB_DIR_IN) {
if (! is_input)
continue;
} else {
if (is_input)
continue;
}
altno = alts->bAlternateSetting;
/* get audio formats */
fmt = snd_usb_find_csint_desc(buffer, buflen, NULL, AS_GENERAL, iface_no, altno);
if (!fmt) {
snd_printk(KERN_ERR "%d:%u:%d : AS_GENERAL descriptor not found\n",
dev->devnum, iface_no, altno);
continue;
}
if (fmt[0] < 7) {
snd_printk(KERN_ERR "%d:%u:%d : invalid AS_GENERAL desc\n",
dev->devnum, iface_no, altno);
continue;
}
format = (fmt[6] << 8) | fmt[5]; /* remember the format value */
/* get format type */
fmt = snd_usb_find_csint_desc(buffer, buflen, NULL, FORMAT_TYPE, iface_no, altno);
if (!fmt) {
snd_printk(KERN_ERR "%d:%u:%d : no FORMAT_TYPE desc\n",
dev->devnum, iface_no, altno);
continue;
}
if (fmt[0] < 8) {
snd_printk(KERN_ERR "%d:%u:%d : invalid FORMAT_TYPE desc\n",
dev->devnum, iface_no, altno);
continue;
}
format_type = fmt[3];
/* FIXME: needed support for TYPE II and III */
if (format_type != USB_FORMAT_TYPE_I) {
snd_printd(KERN_INFO "%d:%u:%d : format type %d is not supported yet\n",
dev->devnum, iface_no, altno, format_type);
continue;
}
nr_rates = fmt[7];
if (fmt[0] < 8 + 3 * (nr_rates ? nr_rates : 2)) {
snd_printk(KERN_ERR "%d:%u:%d : invalid FORMAT_TYPE desc\n",
dev->devnum, iface_no, altno);
continue;
}
/* FIXME: correct endianess and sign? */
pcm_format = -1;
switch (format) {
case USB_AUDIO_FORMAT_PCM:
/* check the format byte size */
switch (fmt[6]) {
case 8:
subs->formats |= SNDRV_PCM_FMTBIT_U8;
pcm_format = SNDRV_PCM_FORMAT_U8;
break;
case 16:
subs->formats |= SNDRV_PCM_FMTBIT_S16_LE;
pcm_format = SNDRV_PCM_FORMAT_S16_LE;
break;
case 18:
case 20:
if (fmt[5] == 3) {
subs->formats |= SNDRV_PCM_FMTBIT_S24_3LE;
pcm_format = SNDRV_PCM_FORMAT_S24_3LE;
} else {
snd_printk(KERN_ERR "%d:%u:%d : non-supported sample bit %d in %d bytes\n",
dev->devnum, iface_no, altno, fmt[6], fmt[5]);
}
break;
case 24:
if (fmt[5] == 4) {
/* FIXME: correct? or S32_LE? */
subs->formats |= SNDRV_PCM_FMTBIT_S24_LE;
pcm_format = SNDRV_PCM_FORMAT_S24_LE;
} else if (fmt[5] == 3) {
subs->formats |= SNDRV_PCM_FMTBIT_S24_3LE;
pcm_format = SNDRV_PCM_FORMAT_S24_3LE;
} else {
snd_printk(KERN_ERR "%d:%u:%d : non-supported sample bit %d in %d bytes\n",
dev->devnum, iface_no, altno, format, fmt[5]);
}
break;
case 32:
subs->formats |= SNDRV_PCM_FMTBIT_S32_LE;
pcm_format = SNDRV_PCM_FORMAT_S32_LE;
break;
default:
snd_printk(KERN_INFO "%d:%u:%d : unsupported sample bitwidth %d in %d bytes\n",
dev->devnum, iface_no, altno, fmt[6], fmt[5]);
break;
}
break;
case USB_AUDIO_FORMAT_PCM8:
/* Dallas DS4201 workaround */
if (dev->descriptor.idVendor == 0x04fa && dev->descriptor.idProduct == 0x4201) {
subs->formats |= ~SNDRV_PCM_FMTBIT_S8;
pcm_format = SNDRV_PCM_FORMAT_S8;
} else {
subs->formats |= SNDRV_PCM_FMTBIT_U8;
pcm_format = SNDRV_PCM_FORMAT_U8;
}
break;
case USB_AUDIO_FORMAT_IEEE_FLOAT:
subs->formats |= SNDRV_PCM_FMTBIT_FLOAT_LE;
pcm_format = SNDRV_PCM_FORMAT_FLOAT_LE;
break;
case USB_AUDIO_FORMAT_ALAW:
subs->formats |= SNDRV_PCM_FMTBIT_A_LAW;
pcm_format = SNDRV_PCM_FORMAT_A_LAW;
break;
case USB_AUDIO_FORMAT_MU_LAW:
subs->formats |= SNDRV_PCM_FMTBIT_MU_LAW;
pcm_format = SNDRV_PCM_FORMAT_MU_LAW;
break;
default:
snd_printk(KERN_INFO "%d:%u:%d : unsupported format type %d\n",
dev->devnum, iface_no, altno, format);
break;
}
if (pcm_format < 0)
continue;
channels = fmt[4];
if (channels < 1) {
snd_printk(KERN_ERR "%d:%u:%d : invalid channels %d\n",
dev->devnum, iface_no, altno, channels);
continue;
}
csep = snd_usb_find_desc(buffer, buflen, NULL, USB_DT_CS_ENDPOINT, iface_no, altno);
if (!csep || csep[0] < 7 || csep[2] != EP_GENERAL) {
snd_printk(KERN_ERR "%d:%u:%d : no or invalid class specific endpoint descriptor\n",
dev->devnum, iface_no, altno);
continue;
}
fp = kmalloc(sizeof(*fp), GFP_KERNEL);
if (! fp) {
snd_printk(KERN_ERR "cannot malloc\n");
break;
}
memset(fp, 0, sizeof(*fp));
fp->format = pcm_format;
fp->altsetting = altno;
fp->altset_idx = i;
fp->endpoint = alts->endpoint[0].bEndpointAddress;
fp->ep_attr = alts->endpoint[0].bmAttributes;
fp->channels = channels;
fp->attributes = csep[3];
if (nr_rates) {
/*
* build the rate table and bitmap flags
*/
int r, idx, c;
/* this table corresponds to the SNDRV_PCM_RATE_XXX bit */
static int conv_rates[] = {
5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000,
64000, 88200, 96000, 176400, 192000
};
fp->rate_table = kmalloc(sizeof(int) * nr_rates, GFP_KERNEL);
if (fp->rate_table == NULL) {
snd_printk(KERN_ERR "cannot malloc\n");
kfree(fp);
break;
}
fp->nr_rates = nr_rates;
fp->rate_min = fp->rate_max = combine_triple(&fmt[8]);
for (r = 0, idx = 8; r < nr_rates; r++, idx += 3) {
int rate = fp->rate_table[r] = combine_triple(&fmt[idx]);
if (rate < fp->rate_min)
fp->rate_min = rate;
else if (rate > fp->rate_max)
fp->rate_max = rate;
for (c = 0; c < 13; c++) {
if (rate == conv_rates[c]) {
fp->rates |= (1 << c);
break;
}
}
#if 0 // FIXME - we need to define constraint
if (c >= 13)
fp->rates |= SNDRV_PCM_KNOT; /* unconventional rate */
#endif
}
} else {
/* continuous rates */
fp->rates = SNDRV_PCM_RATE_CONTINUOUS;
fp->rate_min = combine_triple(&fmt[8]);
fp->rate_max = combine_triple(&fmt[11]);
}
list_add_tail(&fp->list, &subs->fmt_list);
subs->num_formats++;
}
}
/*
* free a substream
*/
static void free_substream(snd_usb_substream_t *subs)
{
struct list_head *p, *n;
if (subs->interface < 0)
return;
list_for_each_safe(p, n, &subs->fmt_list) {
struct audioformat *fp = list_entry(p, struct audioformat, list);
if (fp->rate_table)
kfree(fp->rate_table);
kfree(fp);
}
}
/*
* proc interface for list the supported pcm formats
*/
static void proc_dump_substream_formats(snd_usb_substream_t *subs, snd_info_buffer_t *buffer)
{
struct list_head *p;
static char *sync_types[4] = {
"NONE", "ASYNC", "ADAPTIVE", "SYNC"
};
list_for_each(p, &subs->fmt_list) {
struct audioformat *fp;
fp = list_entry(p, struct audioformat, list);
snd_iprintf(buffer, " Altset %d\n", fp->altset_idx);
snd_iprintf(buffer, " Format: %s\n", snd_pcm_format_name(fp->format));
snd_iprintf(buffer, " Channels: %d\n", fp->channels);
snd_iprintf(buffer, " Endpoint: %d %s (%s)\n",
fp->endpoint & USB_ENDPOINT_NUMBER_MASK,
fp->endpoint & USB_DIR_IN ? "IN" : "OUT",
sync_types[(fp->ep_attr & EP_ATTR_MASK) >> 2]);
if (fp->rates & SNDRV_PCM_RATE_CONTINUOUS) {
snd_iprintf(buffer, " Rates: %d - %d (continous)\n",
fp->rate_min, fp->rate_max);
} else {
int i;
snd_iprintf(buffer, " Rates: ");
for (i = 0; i < fp->nr_rates; i++) {
if (i > 0)
snd_iprintf(buffer, ", ");
snd_iprintf(buffer, "%d", fp->rate_table[i]);
}
snd_iprintf(buffer, "\n");
}
}
}
static void proc_dump_substream_status(snd_usb_substream_t *subs, snd_info_buffer_t *buffer)
{
if (subs->running) {
int i;
snd_iprintf(buffer, " Status: Running\n");
snd_iprintf(buffer, " Altset = %d\n", subs->format);
snd_iprintf(buffer, " URBs = %d [ ", subs->nurbs);
for (i = 0; i < subs->nurbs; i++)
snd_iprintf(buffer, "%d ", subs->dataurb[i].packets);
snd_iprintf(buffer, "]\n");
snd_iprintf(buffer, " Packet Size = %d\n", subs->curpacksize);
snd_iprintf(buffer, " Momentary freq = %d,%03d Hz\n",
subs->freqm >> 14,
((subs->freqm & ((1 << 14) - 1)) * 1000) / ((1 << 14) - 1));
} else {
snd_iprintf(buffer, " Status: Stop\n");
}
}
static void proc_pcm_format_read(snd_info_entry_t *entry, snd_info_buffer_t *buffer)
{
snd_usb_stream_t *stream = snd_magic_cast(snd_usb_stream_t, entry->private_data, return);
snd_iprintf(buffer, "%s : %s\n", stream->chip->card->longname, stream->pcm->name);
if (stream->substream[SNDRV_PCM_STREAM_PLAYBACK].num_formats) {
snd_iprintf(buffer, "\nPlayback:\n");
proc_dump_substream_status(&stream->substream[SNDRV_PCM_STREAM_PLAYBACK], buffer);
proc_dump_substream_formats(&stream->substream[SNDRV_PCM_STREAM_PLAYBACK], buffer);
}
if (stream->substream[SNDRV_PCM_STREAM_CAPTURE].num_formats) {
snd_iprintf(buffer, "\nCapture:\n");
proc_dump_substream_status(&stream->substream[SNDRV_PCM_STREAM_CAPTURE], buffer);
proc_dump_substream_formats(&stream->substream[SNDRV_PCM_STREAM_CAPTURE], buffer);
}
}
static void proc_pcm_format_add(snd_usb_stream_t *stream)
{
snd_info_entry_t *entry;
char name[32];
snd_card_t *card = stream->chip->card;
sprintf(name, "stream%d", stream->pcm_index);
if ((entry = snd_info_create_card_entry(card, name, card->proc_root)) != NULL) {
entry->content = SNDRV_INFO_CONTENT_TEXT;
entry->private_data = stream;
entry->mode = S_IFREG | S_IRUGO | S_IWUSR;
entry->c.text.read_size = 4096;
entry->c.text.read = proc_pcm_format_read;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
stream->proc_entry = entry;
}
/*
* free a usb stream instance
*/
static void snd_usb_audio_stream_free(snd_usb_stream_t *stream)
{
if (stream->proc_entry) {
snd_info_unregister(stream->proc_entry);
stream->proc_entry = NULL;
}
free_substream(&stream->substream[0]);
free_substream(&stream->substream[1]);
list_del(&stream->list);
snd_magic_kfree(stream);
}
static void snd_usb_audio_pcm_free(snd_pcm_t *pcm)
{
snd_usb_stream_t *stream = pcm->private_data;
if (stream) {
stream->pcm = NULL;
snd_pcm_lib_preallocate_free_for_all(pcm);
snd_usb_audio_stream_free(stream);
}
}
static int snd_usb_audio_stream_new(snd_usb_audio_t *chip, unsigned char *buffer, int buflen, int asifin, int asifout)
{
snd_usb_stream_t *as;
snd_pcm_t *pcm;
char name[32];
int err;
as = snd_magic_kmalloc(snd_usb_stream_t, 0, GFP_KERNEL);
if (as == NULL) {
snd_printk(KERN_ERR "cannot malloc\n");
return -ENOMEM;
}
memset(as, 0, sizeof(*as));
as->chip = chip;
INIT_LIST_HEAD(&as->list);
init_substream(as, &as->substream[SNDRV_PCM_STREAM_PLAYBACK], asifout, 0, buffer, buflen);
init_substream(as, &as->substream[SNDRV_PCM_STREAM_CAPTURE], asifin, 1, buffer, buflen);
if (as->substream[0].num_formats == 0 && as->substream[1].num_formats == 0) {
snd_usb_audio_stream_free(as);
return 0;
}
if (chip->pcm_devs > 0)
sprintf(name, "USB Audio #%d", chip->pcm_devs);
else
strcpy(name, "USB Audio");
err = snd_pcm_new(chip->card, "USB Audio", chip->pcm_devs,
as->substream[SNDRV_PCM_STREAM_PLAYBACK].num_formats ? 1 : 0,
as->substream[SNDRV_PCM_STREAM_CAPTURE].num_formats ? 1 : 0,
&pcm);
if (err < 0) {
snd_usb_audio_stream_free(as);
return err;
}
as->pcm = pcm;
as->pcm_index = chip->pcm_devs;
if (as->substream[SNDRV_PCM_STREAM_PLAYBACK].num_formats)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_usb_playback_ops);
if (as->substream[SNDRV_PCM_STREAM_CAPTURE].num_formats)
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usb_capture_ops);
pcm->private_data = as;
pcm->private_free = snd_usb_audio_pcm_free;
pcm->info_flags = 0;
strcpy(pcm->name, name);
snd_pcm_lib_preallocate_pages_for_all(pcm, 64*1024, 128*1024, GFP_ATOMIC);
list_add(&as->list, &chip->pcm_list);
chip->pcm_devs++;
proc_pcm_format_add(as);
return 0;
}
/*
* parse audio control descriptor and create pcm streams
*/
static int snd_usb_create_pcm(snd_usb_audio_t *chip, int ctrlif,
unsigned char *buffer, int buflen)
{
struct usb_device *dev = chip->dev;
struct usb_config_descriptor *config;
struct usb_interface *iface;
unsigned char *p1;
unsigned char ifin[USB_MAXINTERFACES], ifout[USB_MAXINTERFACES];
int numifin = 0, numifout = 0;
int i, j, k;
/* find audiocontrol interface */
if (!(p1 = snd_usb_find_csint_desc(buffer, buflen, NULL, HEADER, ctrlif, -1))) {
snd_printk(KERN_ERR "cannot find HEADER\n");
return -EINVAL;
}
if (! p1[7] || p1[0] < 8 + p1[7]) {
snd_printk(KERN_ERR "invalid HEADER\n");
return -EINVAL;
}
/*
* parse all USB audio streaming interfaces
*/
config = dev->actconfig;
for (i = 0; i < p1[7]; i++) {
j = p1[8 + i];
if (j >= config->bNumInterfaces) {
snd_printk(KERN_ERR "%d:%u:%d : does not exist\n",
dev->devnum, ctrlif, j);
continue;
}
iface = &config->interface[j];
if (iface->altsetting[0].bInterfaceClass != USB_CLASS_AUDIO ||
iface->altsetting[0].bInterfaceSubClass != USB_SUBCLASS_AUDIO_STREAMING) {
snd_printdd(KERN_ERR "%d:%u:%d: skipping non-supported interface %d\n", dev->devnum, ctrlif, j, iface->altsetting[0].bInterfaceClass);
/* skip non-supported classes */
continue;
}
if (iface->num_altsetting < 2) {
snd_printdd(KERN_ERR "%d:%u:%d: skipping - no valid interface.\n",
dev->devnum, ctrlif, j);
continue;
}
if (iface->altsetting[0].bNumEndpoints > 0) {
/* Check all endpoints; should they all have a bandwidth of 0 ? */
for (k = 0; k < iface->altsetting[0].bNumEndpoints; k++) {
if (iface->altsetting[0].endpoint[k].wMaxPacketSize > 0) {
snd_printk(KERN_ERR "%d:%u:%d ep%d : have no bandwith at alt[0]\n", dev->devnum, ctrlif, j, k);
break;
}
}
if (k < iface->altsetting[0].bNumEndpoints)
continue;
}
if (iface->altsetting[1].bNumEndpoints < 1) {
snd_printk(KERN_ERR "%d:%u:%d : has no endpoint\n",
dev->devnum, ctrlif, j);
continue;
}
/* note: this requires the data endpoint to be ep0 and
* the optional sync ep to be ep1, which seems to be the case
*/
if (iface->altsetting[1].endpoint[0].bEndpointAddress & USB_DIR_IN) {
if (numifin < USB_MAXINTERFACES) {
snd_printdd(KERN_INFO "adding an input interface %d:%u:%d\n", dev->devnum, ctrlif, j);
ifin[numifin++] = j;
usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1);
}
} else {
if (numifout < USB_MAXINTERFACES) {
snd_printdd(KERN_INFO "adding an output interface %d:%u:%d\n", dev->devnum, ctrlif, j);
ifout[numifout++] = j;
usb_driver_claim_interface(&usb_audio_driver, iface, (void *)-1);
}
}
}
/* all endpoints are parsed. now create pcm streams */
for (i = 0; i < numifin && i < numifout; i++)
snd_usb_audio_stream_new(chip, buffer, buflen, ifin[i], ifout[i]);
for (j = i; j < numifin; j++)
snd_usb_audio_stream_new(chip, buffer, buflen, ifin[i], -1);
for (j = i; j < numifout; j++)
snd_usb_audio_stream_new(chip, buffer, buflen, -1, ifout[i]);
return 0;
}
/*
* free the chip instance
*
* here we have to do not much, since pcm and controls are already freed
*
*/
static int snd_usb_audio_free(snd_usb_audio_t *chip)
{
down(&register_mutex);
usb_chip[chip->index] = NULL;
up(&register_mutex);
snd_magic_kfree(chip);
return 0;
}
static int snd_usb_audio_dev_free(snd_device_t *device)
{
snd_usb_audio_t *chip = snd_magic_cast(snd_usb_audio_t, device->device_data, return -ENXIO);
return snd_usb_audio_free(chip);
}
/*
* create a chip instance and set its names.
*/
static int snd_usb_audio_create(snd_card_t *card, struct usb_device *dev, snd_usb_audio_t **rchip)
{
snd_usb_audio_t *chip;
int err, len;
static snd_device_ops_t ops = {
dev_free: snd_usb_audio_dev_free,
};
*rchip = NULL;
chip = snd_magic_kcalloc(snd_usb_audio_t, 0, GFP_KERNEL);
if (! chip)
return -ENOMEM;
chip->dev = dev;
chip->card = card;
INIT_LIST_HEAD(&chip->pcm_list);
if ((err = snd_device_new(card, SNDRV_DEV_LOWLEVEL, chip, &ops)) < 0) {
snd_usb_audio_free(chip);
return err;
}
strcpy(card->driver, "USB-Audio");
strcpy(card->shortname, "USB Audio Driver");
/* retrieve the vendor and device strings as longname */
len = usb_string(dev, 1, card->longname, sizeof(card->longname) - 1);
if (len <= 0)
len = 0;
else {
card->longname[len] = ' ';
len++;
}
card->longname[len] = 0;
usb_string(dev, 2, card->longname + len, sizeof(card->longname) - len);
*rchip = chip;
return 0;
}
/*
* allocate and get description buffer
* must be freed later.
*/
static int alloc_desc_buffer(struct usb_device *dev, int index, unsigned char **bufptr)
{
int err, buflen;
unsigned char buf[8];
unsigned char *buffer;
*bufptr = 0;
err = usb_get_descriptor(dev, USB_DT_CONFIG, index, buf, 8);
if (err < 0) {
snd_printk(KERN_ERR "%d:%d: cannot get first 8 bytes\n", index, dev->devnum);
return err;
}
if (buf[1] != USB_DT_CONFIG || buf[0] < 9) {
snd_printk(KERN_ERR "%d:%d: invalid config desc\n", index, dev->devnum);
return -EINVAL;
}
buflen = combine_word(&buf[2]);
if (!(buffer = kmalloc(buflen, GFP_KERNEL))) {
snd_printk(KERN_ERR "cannot malloc descriptor (size = %d)\n", buflen);
return -ENOMEM;
}
err = usb_get_descriptor(dev, USB_DT_CONFIG, index, buffer, buflen);
if (err < 0) {
snd_printk(KERN_ERR "%d:%d: cannot get DT_CONFIG: error %d\n", index, dev->devnum, err);
kfree(buffer);
return err;
}
*bufptr = buffer;
return buflen;
}
/*
* probe the active usb device
*
* note that this can be called multiple times per a device, when it
* includes multiple audio control interfaces.
*
* thus we check the usb device pointer and creates the card instance
* only at the first time. the successive calls of this function will
* append the pcm interface to the corresponding card.
*/
static void *usb_audio_probe(struct usb_device *dev, unsigned int ifnum,
const struct usb_device_id *id)
{
struct usb_config_descriptor *config = dev->actconfig;
unsigned char *buffer;
unsigned int index;
int i, buflen;
snd_card_t *card;
snd_usb_audio_t *chip;
if (usb_set_configuration(dev, config->bConfigurationValue) < 0) {
snd_printk(KERN_ERR "cannot set configuration (value 0x%x)\n", config->bConfigurationValue);
return NULL;
}
index = dev->actconfig - config;
buflen = alloc_desc_buffer(dev, index, &buffer);
if (buflen <= 0)
return NULL;
/*
* found a config. now register to ALSA
*/
/* check whether it's already registered */
chip = NULL;
down(&register_mutex);
for (i = 0; i < SNDRV_CARDS; i++) {
if (usb_chip[i] && usb_chip[i]->dev == dev) {
chip = usb_chip[i];
break;
}
}
if (! chip) {
/* it's a fresh one.
* now look for an empty slot and create a new card instance
*/
for (i = 0; i < SNDRV_CARDS; i++)
if (snd_enable[i] && ! usb_chip[i]) {
card = snd_card_new(snd_index[i], snd_id[i], THIS_MODULE, 0);
if (card == NULL) {
snd_printk(KERN_ERR "cannot create a card instance %d\n", i);
goto __error;
}
if (snd_usb_audio_create(card, dev, &chip) < 0) {
snd_card_free(card);
goto __error;
}
chip->index = i;
usb_chip[i] = chip;
break;
}
if (! chip) {
snd_printk(KERN_ERR "no available usb audio device\n");
goto __error;
}
}
if (snd_usb_create_pcm(chip, ifnum, buffer, buflen) < 0)
goto __error;
if (snd_usb_create_mixer(chip, ifnum, buffer, buflen) < 0)
goto __error;
/* we are allowed to call snd_card_register() many times */
if (snd_card_register(chip->card) < 0) {
if (! chip->num_interfaces)
snd_card_free(chip->card);
goto __error;
}
chip->num_interfaces++;
up(&register_mutex);
kfree(buffer);
return chip;
__error:
up(&register_mutex);
kfree(buffer);
return NULL;
}
/*
* we need to take care of counter, since disconnection can be called also
* many times as well as usb_audio_probe().
*/
static void usb_audio_disconnect(struct usb_device *dev, void *ptr)
{
snd_usb_audio_t *chip;
if (ptr == (void *)-1)
return;
chip = snd_magic_cast(snd_usb_audio_t, ptr, return);
chip->num_interfaces--;
if (chip->num_interfaces <= 0)
snd_card_free(chip->card);
}
static int __init snd_usb_audio_init(void)
{
usb_register(&usb_audio_driver);
return 0;
}
static void __exit snd_usb_audio_cleanup(void)
{
usb_deregister(&usb_audio_driver);
}
module_init(snd_usb_audio_init);
module_exit(snd_usb_audio_cleanup);
#ifndef MODULE
/*
* format is snd-usb-audio=snd_enable,snd_index,snd_id
*/
static int __init snd_usb_audio_module_setup(char* str)
{
static unsigned __initdata nr_dev = 0;
if (nr_dev >= SNDRV_CARDS)
return 0;
(void)(get_option(&str, &snd_enable[nr_dev]) == 2 &&
get_option(&str, &snd_index[nr_dev]) == 2 &&
get_id(&str, &snd_id[nr_dev]) == 2);
++nr_dev;
return 1;
}
__setup("snd-usb-audio=", snd_usb_audio_module_setup);
#endif /* !MODULE */
#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_DT_CS_DEVICE 0x21
#define USB_DT_CS_CONFIG 0x22
#define USB_DT_CS_STRING 0x23
#define USB_DT_CS_INTERFACE 0x24
#define USB_DT_CS_ENDPOINT 0x25
#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
/* 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
/*
*/
typedef struct snd_usb_audio snd_usb_audio_t;
struct snd_usb_audio {
int index;
struct usb_device *dev;
snd_card_t *card;
int num_interfaces;
struct list_head pcm_list; /* list of pcm streams */
int pcm_devs;
};
/*
*/
#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, int iface, int altsetting);
void *snd_usb_find_csint_desc(void *descstart, int desclen, void *after, u8 dsubtype, int iface, int altsetting);
int snd_usb_create_mixer(snd_usb_audio_t *chip, int ctrlif, unsigned char *buffer, int buflen);
#endif /* __USBAUDIO_H */
/*
* usbmidi.c - ALSA USB MIDI driver
*
* Copyright (c) 2002 Clemens Ladisch
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer,
* without modification.
* 2. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* Alternatively, this software may be distributed and/or modified 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 SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sound/driver.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <asm/semaphore.h>
#include <sound/core.h>
#include <sound/minors.h>
#include <sound/asequencer.h>
#include <sound/seq_device.h>
#include <sound/seq_kernel.h>
#include <sound/seq_virmidi.h>
#include <sound/seq_midi_event.h>
#define SNDRV_GET_ID
#include <sound/initval.h>
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
MODULE_DESCRIPTION("USB MIDI");
MODULE_LICENSE("Dual BSD/GPL");
MODULE_CLASSES("{sound}");
MODULE_DEVICES("{{Generic,USB MIDI},"
"{Roland/EDIROL,PC-300},"
"{Roland/EDIROL,SC-8820},"
"{Roland/EDIROL,SC-8850},"
"{Roland/EDIROL,SC-D70},"
"{Roland/EDIROL,SD-90},"
"{Roland/EDIROL,SK-500},"
"{Roland/EDIROL,U-8},"
"{Roland/EDIROL,UA-100(G)},"
"{Roland/EDIROL,UM-1(S)},"
"{Roland/EDIROL,UM-2(E)},"
"{Roland/EDIROL,UM-4},"
"{Roland/EDIROL,UM-550},"
"{Roland/EDIROL,UM-880},"
"{Roland/EDIROL,XV-5050},"
"{Yamaha,MU1000},"
"{Yamaha,UX256}}");
static int snd_index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-max */
static char* snd_id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* Id for this card */
static int snd_enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */
static int snd_vid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 }; /* Vendor id of this card */
static int snd_pid[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = -1 }; /* Product id of this card */
static int snd_int_transfer[SNDRV_CARDS] = { [0 ... (SNDRV_CARDS-1)] = 0 }; /* Use interrupt transfers for this card */
MODULE_PARM(snd_index, "1-" __MODULE_STRING(SNDRV_CARDS) "i");
MODULE_PARM_DESC(snd_index, "Index value for USB MIDI.");
MODULE_PARM_SYNTAX(snd_index, SNDRV_INDEX_DESC);
MODULE_PARM(snd_id, "1-" __MODULE_STRING(SNDRV_CARDS) "s");
MODULE_PARM_DESC(snd_id, "ID string for USB MIDI.");
MODULE_PARM_SYNTAX(snd_id, SNDRV_ID_DESC);
MODULE_PARM(snd_enable, "1-" __MODULE_STRING(SNDRV_CARDS) "i");
MODULE_PARM_DESC(snd_enable, "Enable USB MIDI.");
MODULE_PARM_SYNTAX(snd_enable, SNDRV_ENABLE_DESC);
MODULE_PARM(snd_vid, "1-" __MODULE_STRING(SNDRV_CARDS) "i");
MODULE_PARM_DESC(snd_vid, "USB Vendor ID for USB MIDI.");
MODULE_PARM_SYNTAX(snd_vid, SNDRV_ENABLED ",allows:{{-1,0xffff}},base:16");
MODULE_PARM(snd_pid, "1-" __MODULE_STRING(SNDRV_CARDS) "i");
MODULE_PARM_DESC(snd_pid, "USB Product ID for USB MIDI.");
MODULE_PARM_SYNTAX(snd_pid, SNDRV_ENABLED ",allows:{{-1,0xffff}},base:16");
MODULE_PARM(snd_int_transfer, "1-" __MODULE_STRING(SNDRV_CARDS) "i");
MODULE_PARM_DESC(snd_int_transfer, "Use interrupt transfers for USB MIDI input.");
MODULE_PARM_SYNTAX(snd_int_transfer, SNDRV_ENABLED "," SNDRV_BOOLEAN_FALSE_DESC ",skill:advanced");
/* size of the per-endpoint output buffer, must be a multiple of 4 */
#define OUTPUT_BUFFER_SIZE 0x400
/* max. size of incoming sysex messages */
#define INPUT_BUFFER_SIZE 0x200
#define MAX_ENDPOINTS 2
#define SNDRV_SEQ_DEV_ID_USBMIDI "usb-midi"
#ifndef USB_SUBCLASS_MIDISTREAMING
#define USB_SUBCLASS_MIDISTREAMING 3
#endif
#ifndef USB_DT_CS_INTERFACE
#define USB_DT_CS_INTERFACE (USB_TYPE_CLASS | USB_DT_INTERFACE)
#define USB_DT_CS_ENDPOINT (USB_TYPE_CLASS | USB_DT_ENDPOINT)
#endif
#ifndef USB_DST_MS_HEADER
#define USB_DST_MS_HEADER 0x01
#define USB_DST_MS_GENERAL 0x01
#define USB_DST_MS_HEADER_SIZE 7
#define USB_DST_MS_GENERAL_SIZE 4
#endif
typedef struct usb_driver usb_driver_t;
typedef struct usb_device usb_device_t;
typedef struct usb_device_id usb_device_id_t;
typedef struct usb_interface usb_interface_t;
typedef struct usb_interface_descriptor usb_interface_descriptor_t;
typedef struct usb_ms_header_descriptor usb_ms_header_descriptor_t;
typedef struct usb_endpoint_descriptor usb_endpoint_descriptor_t;
typedef struct usb_ms_endpoint_descriptor usb_ms_endpoint_descriptor_t;
struct usb_ms_header_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__u8 bDescriptorSubtype;
__u8 bcdMSC[2];
__u16 wTotalLength;
} __attribute__ ((packed));
struct usb_ms_endpoint_descriptor {
__u8 bLength;
__u8 bDescriptorType;
__u8 bDescriptorSubtype;
__u8 bNumEmbMIDIJack;
__u8 baAssocJackID[0];
} __attribute__ ((packed));
typedef struct usbmidi usbmidi_t;
typedef struct usbmidi_device_info usbmidi_device_info_t;
typedef struct usbmidi_endpoint_info usbmidi_endpoint_info_t;
typedef struct usbmidi_endpoint usbmidi_endpoint_t;
typedef struct usbmidi_out_endpoint usbmidi_out_endpoint_t;
typedef struct usbmidi_out_port usbmidi_out_port_t;
typedef struct usbmidi_in_endpoint usbmidi_in_endpoint_t;
typedef struct usbmidi_in_port usbmidi_in_port_t;
/*
* Describes the capabilities of a USB MIDI device.
* This structure is filled after parsing the USB descriptors,
* or is supplied explicitly for broken devices.
*/
struct usbmidi_device_info {
char vendor[32]; /* vendor name */
char product[32]; /* device name */
int16_t ifnum; /* interface number */
struct usbmidi_endpoint_info {
int16_t epnum; /* endpoint number,
-1: autodetect (first ep only) */
uint16_t out_cables; /* bitmask */
uint16_t in_cables; /* bitmask */
} endpoints[MAX_ENDPOINTS];
};
struct usbmidi {
snd_card_t* card;
usb_device_t* usb_device;
int dev;
int seq_client;
usbmidi_device_info_t device_info;
struct usbmidi_endpoint {
usbmidi_out_endpoint_t* out;
usbmidi_in_endpoint_t* in;
snd_rawmidi_t* rmidi[0x10];
} endpoints[MAX_ENDPOINTS];
};
struct usbmidi_out_endpoint {
usbmidi_t* umidi;
urb_t* urb;
int max_transfer; /* size of urb buffer */
struct tasklet_struct tasklet;
uint8_t buffer[OUTPUT_BUFFER_SIZE]; /* ring buffer */
int data_begin;
int data_size;
spinlock_t buffer_lock;
struct usbmidi_out_port {
usbmidi_out_endpoint_t* ep;
uint8_t cable; /* cable number << 4 */
uint8_t sysex_len;
uint8_t sysex[2];
} ports[0x10];
};
struct usbmidi_in_endpoint {
usbmidi_t* umidi;
usbmidi_endpoint_t* ep;
urb_t* urb;
struct usbmidi_in_port {
int seq_port;
snd_midi_event_t* midi_event;
} ports[0x10];
};
static int snd_usbmidi_card_used[SNDRV_CARDS];
static DECLARE_MUTEX(snd_usbmidi_open_mutex);
static void snd_usbmidi_do_output(usbmidi_out_endpoint_t* ep);
/*
* Submits the URB, with error handling.
*/
static int snd_usbmidi_submit_urb(urb_t* urb, int flags)
{
int err = usb_submit_urb(urb, flags);
if (err < 0 && err != -ENODEV)
printk(KERN_ERR "snd-usb-midi: usb_submit_urb: %d\n", err);
return err;
}
/*
* Error handling for URB completion functions.
*/
static int snd_usbmidi_urb_error(int status)
{
if (status == -ENOENT)
return status; /* killed */
if (status == -ENODEV ||
status == -EILSEQ ||
status == -ETIMEDOUT)
return -ENODEV; /* device removed */
printk(KERN_ERR "snd-usb-midi: urb status %d\n", status);
return 0; /* continue */
}
/*
* Converts a USB MIDI packet into an ALSA sequencer event.
*/
static void snd_usbmidi_input_packet(usbmidi_in_endpoint_t* ep,
uint8_t packet[4])
{
static const uint8_t cin_length[] = {
0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1
};
int cable = packet[0] >> 4;
usbmidi_in_port_t* port = &ep->ports[cable];
snd_seq_event_t ev;
if (!port->midi_event)
return;
memset(&ev, 0, sizeof(ev));
if (snd_midi_event_encode(port->midi_event, &packet[1],
cin_length[packet[0] & 0x0f], &ev) > 0
&& ev.type != SNDRV_SEQ_EVENT_NONE) {
ev.source.port = port->seq_port;
ev.dest.client = SNDRV_SEQ_ADDRESS_SUBSCRIBERS;
snd_seq_kernel_client_dispatch(ep->umidi->seq_client,
&ev, 1, 0);
if (ep->ep->rmidi[cable])
snd_virmidi_receive(ep->ep->rmidi[cable], &ev);
}
}
/*
* Processes the data read from the device.
*/
static void snd_usbmidi_in_urb_complete(urb_t* urb)
{
usbmidi_in_endpoint_t* ep = snd_magic_cast(usbmidi_in_endpoint_t, urb->context, return);
if (urb->status == 0) {
uint8_t* buffer = (uint8_t*)ep->urb->transfer_buffer;
int i;
for (i = 0; i + 4 <= urb->actual_length; i += 4)
if (buffer[i] != 0)
snd_usbmidi_input_packet(ep, &buffer[i]);
} else {
if (snd_usbmidi_urb_error(urb->status) < 0)
return;
}
if (!usb_pipeint(urb->pipe)) {
urb->dev = ep->umidi->usb_device;
snd_usbmidi_submit_urb(urb, GFP_ATOMIC);
}
}
static void snd_usbmidi_out_urb_complete(urb_t* urb)
{
usbmidi_out_endpoint_t* ep = snd_magic_cast(usbmidi_out_endpoint_t, urb->context, return);
unsigned long flags;
if (urb->status < 0) {
if (snd_usbmidi_urb_error(urb->status) < 0)
return;
}
spin_lock_irqsave(&ep->buffer_lock, flags);
snd_usbmidi_do_output(ep);
spin_unlock_irqrestore(&ep->buffer_lock, flags);
}
/*
* This is called when some data should be transferred to the device
* (after the reception of one or more sequencer events, or after completion
* of the previous transfer). ep->buffer_lock must be held.
*/
static void snd_usbmidi_do_output(usbmidi_out_endpoint_t* ep)
{
int len;
uint8_t* buffer;
if (ep->urb->status == -EINPROGRESS ||
ep->data_size == 0)
return;
buffer = (uint8_t*)ep->urb->transfer_buffer;
/* first chunk, up to the end of the buffer */
len = OUTPUT_BUFFER_SIZE - ep->data_begin;
if (len > ep->data_size)
len = ep->data_size;
if (len > ep->max_transfer)
len = ep->max_transfer;
if (len > 0) {
memcpy(buffer, ep->buffer + ep->data_begin, len);
ep->data_begin = (ep->data_begin + len) % OUTPUT_BUFFER_SIZE;
ep->data_size -= len;
buffer += len;
ep->urb->transfer_buffer_length = len;
}
/* second chunk (after wraparound) */
if (ep->data_begin == 0 && ep->data_size > 0 &&
len < ep->max_transfer) {
len = ep->max_transfer - len;
if (len > ep->data_size)
len = ep->data_size;
memcpy(buffer, ep->buffer, len);
ep->data_begin = len;
ep->data_size -= len;
ep->urb->transfer_buffer_length += len;
}
if (len > 0) {
ep->urb->dev = ep->umidi->usb_device;
snd_usbmidi_submit_urb(ep->urb, GFP_ATOMIC);
}
}
static void snd_usbmidi_out_tasklet(unsigned long data)
{
usbmidi_out_endpoint_t* ep = snd_magic_cast(usbmidi_out_endpoint_t, (void*)data, return);
unsigned long flags;
spin_lock_irqsave(&ep->buffer_lock, flags);
snd_usbmidi_do_output(ep);
spin_unlock_irqrestore(&ep->buffer_lock, flags);
}
/*
* Adds one USB MIDI packet to the output buffer.
*/
static void output_packet(usbmidi_out_port_t* port,
uint8_t p0, uint8_t p1, uint8_t p2, uint8_t p3)
{
usbmidi_out_endpoint_t* ep = port->ep;
unsigned long flags;
spin_lock_irqsave(&ep->buffer_lock, flags);
if (ep->data_size < OUTPUT_BUFFER_SIZE) {
uint8_t* buf = ep->buffer + (ep->data_begin + ep->data_size) % OUTPUT_BUFFER_SIZE;
buf[0] = p0;
buf[1] = p1;
buf[2] = p2;
buf[3] = p3;
ep->data_size += 4;
if (ep->data_size == ep->max_transfer)
snd_usbmidi_do_output(ep);
}
spin_unlock_irqrestore(&ep->buffer_lock, flags);
}
/*
* Callback for snd_seq_dump_var_event.
*/
static int snd_usbmidi_sysex_dump(void* ptr, void* buf, int count)
{
usbmidi_out_port_t* port = (usbmidi_out_port_t*)ptr;
const uint8_t* dump = (const uint8_t*)buf;
for (; count; --count) {
uint8_t byte = *dump++;
if (byte == 0xf0 && port->sysex_len > 0) {
/*
* The previous SysEx wasn't terminated correctly.
* Send the last bytes anyway, and hope that the
* receiving device won't be too upset about the
* missing F7.
*/
output_packet(port,
port->cable | (0x04 + port->sysex_len),
port->sysex[0],
port->sysex_len >= 2 ? port->sysex[1] : 0,
0);
port->sysex_len = 0;
}
if (byte != 0xf7) {
if (port->sysex_len >= 2) {
output_packet(port,
port->cable | 0x04,
port->sysex[0],
port->sysex[1],
byte);
port->sysex_len = 0;
} else {
port->sysex[port->sysex_len++] = byte;
}
} else {
uint8_t cin, data[3];
int i;
for (i = 0; i < port->sysex_len; ++i)
data[i] = port->sysex[i];
data[i++] = 0xf7;
cin = port->cable | (0x04 + i);
for (; i < 3; ++i)
data[i] = 0;
/*
* cin,data[] is x5,{F7 00 00}
* or x6,{xx F7 00}
* or x7,{xx xx F7}
*/
output_packet(port, cin, data[0], data[1], data[2]);
port->sysex_len = 0;
}
}
return 0;
}
/*
* Converts an ALSA sequencer event into USB MIDI packets.
*/
static int snd_usbmidi_event_input(snd_seq_event_t* ev, int direct,
void* private_data, int atomic, int hop)
{
usbmidi_out_port_t* port = (usbmidi_out_port_t*)private_data;
int err;
uint8_t p0, p1;
p0 = port->cable;
p1 = ev->data.note.channel & 0xf;
switch (ev->type) {
case SNDRV_SEQ_EVENT_NOTEON:
output_packet(port, p0 | 0x09, p1 | 0x90,
ev->data.note.note & 0x7f,
ev->data.note.velocity & 0x7f);
break;
case SNDRV_SEQ_EVENT_NOTEOFF:
output_packet(port, p0 | 0x08, p1 | 0x80,
ev->data.note.note & 0x7f,
ev->data.note.velocity & 0x7f);
break;
case SNDRV_SEQ_EVENT_KEYPRESS:
output_packet(port, p0 | 0x0a, p1 | 0xa0,
ev->data.note.note & 0x7f,
ev->data.note.velocity & 0x7f);
break;
case SNDRV_SEQ_EVENT_CONTROLLER:
output_packet(port, p0 | 0x0b, p1 | 0xb0,
ev->data.control.param & 0x7f,
ev->data.control.value & 0x7f);
break;
case SNDRV_SEQ_EVENT_PGMCHANGE:
output_packet(port, p0 | 0x0c, p1 | 0xc0,
ev->data.control.value & 0x7f, 0);
break;
case SNDRV_SEQ_EVENT_CHANPRESS:
output_packet(port, p0 | 0x0d, p1 | 0xd0,
ev->data.control.value & 0x7f, 0);
break;
case SNDRV_SEQ_EVENT_PITCHBEND:
output_packet(port, p0 | 0x0e, p1 | 0xe0,
(ev->data.control.value + 0x2000) & 0x7f,
((ev->data.control.value + 0x2000) >> 7) & 0x7f);
break;
case SNDRV_SEQ_EVENT_CONTROL14:
if (ev->data.control.param < 0x20) {
output_packet(port, p0 | 0x0b, p1 | 0xb0,
ev->data.control.param,
(ev->data.control.value >> 7) & 0x7f);
output_packet(port, p0 | 0x0b, p1 | 0xb0,
ev->data.control.param + 0x20,
ev->data.control.value & 0x7f);
} else {
output_packet(port, p0 | 0x0b, p1 | 0xb0,
ev->data.control.param & 0x7f,
ev->data.control.value & 0x7f);
}
break;
case SNDRV_SEQ_EVENT_SONGPOS:
output_packet(port, p0 | 0x03, 0xf2,
ev->data.control.value & 0x7f,
(ev->data.control.value >> 7) & 0x7f);
break;
case SNDRV_SEQ_EVENT_SONGSEL:
output_packet(port, p0 | 0x02, 0xf3,
ev->data.control.value & 0x7f, 0);
break;
case SNDRV_SEQ_EVENT_QFRAME:
output_packet(port, p0 | 0x02, 0xf1,
ev->data.control.value & 0x7f, 0);
break;
case SNDRV_SEQ_EVENT_START:
output_packet(port, p0 | 0x0f, 0xfa, 0, 0);
break;
case SNDRV_SEQ_EVENT_CONTINUE:
output_packet(port, p0 | 0x0f, 0xfb, 0, 0);
break;
case SNDRV_SEQ_EVENT_STOP:
output_packet(port, p0 | 0x0f, 0xfc, 0, 0);
break;
case SNDRV_SEQ_EVENT_CLOCK:
output_packet(port, p0 | 0x0f, 0xf8, 0, 0);
break;
case SNDRV_SEQ_EVENT_TUNE_REQUEST:
output_packet(port, p0 | 0x05, 0xf6, 0, 0);
break;
case SNDRV_SEQ_EVENT_RESET:
output_packet(port, p0 | 0x0f, 0xff, 0, 0);
break;
case SNDRV_SEQ_EVENT_SENSING:
output_packet(port, p0 | 0x0f, 0xfe, 0, 0);
break;
case SNDRV_SEQ_EVENT_SYSEX:
err = snd_seq_dump_var_event(ev, snd_usbmidi_sysex_dump, port);
if (err < 0)
return err;
break;
default:
return 0;
}
tasklet_hi_schedule(&port->ep->tasklet);
return 0;
}
/*
* Frees an input endpoint.
* May be called when ep hasn't been initialized completely.
*/
static void snd_usbmidi_in_endpoint_delete(usbmidi_in_endpoint_t* ep)
{
int i;
if (ep->urb) {
if (ep->urb->transfer_buffer) {
usb_unlink_urb(ep->urb);
kfree(ep->urb->transfer_buffer);
}
usb_free_urb(ep->urb);
}
for (i = 0; i < 0x10; ++i)
if (ep->ports[i].midi_event)
snd_midi_event_free(ep->ports[i].midi_event);
snd_magic_kfree(ep);
}
/*
* Searches for an alternate setting in which the endpoint uses interrupt
* transfers for input.
*/
static int snd_usbmidi_get_int_ep(usbmidi_t* umidi, uint8_t epnum,
usb_endpoint_descriptor_t** descriptor)
{
usb_interface_t* intf;
int i, j;
*descriptor = NULL;
intf = usb_ifnum_to_if(umidi->usb_device, umidi->device_info.ifnum);
if (!intf)
return -ENXIO;
for (i = 0; i < intf->num_altsetting; ++i) {
usb_interface_descriptor_t* intfd = &intf->altsetting[i];
for (j = 0; j < intfd->bNumEndpoints; ++j) {
usb_endpoint_descriptor_t* epd = &intfd->endpoint[j];
if ((epd->bEndpointAddress & (USB_ENDPOINT_NUMBER_MASK | USB_ENDPOINT_DIR_MASK)) == (epnum | USB_DIR_IN) &&
(epd->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT) {
usb_set_interface(umidi->usb_device,
intfd->bInterfaceNumber,
intfd->bAlternateSetting);
*descriptor = &intfd->endpoint[j];
return 0;
}
}
}
return -ENXIO;
}
/*
* Creates an input endpoint, and initalizes input ports.
* ALSA ports are created later.
*/
static int snd_usbmidi_in_endpoint_create(usbmidi_t* umidi,
usbmidi_endpoint_info_t* ep_info,
usbmidi_endpoint_t* rep)
{
usbmidi_in_endpoint_t* ep;
int do_int_transfer;
usb_endpoint_descriptor_t* epd;
void* buffer;
unsigned int pipe;
int length, i, err;
rep->in = NULL;
ep = snd_magic_kcalloc(usbmidi_in_endpoint_t, 0, GFP_KERNEL);
if (!ep)
return -ENOMEM;
ep->umidi = umidi;
ep->ep = rep;
for (i = 0; i < 0x10; ++i)
ep->ports[i].seq_port = -1;
do_int_transfer = snd_int_transfer[umidi->dev];
if (do_int_transfer) {
if (snd_usbmidi_get_int_ep(umidi, ep_info->epnum, &epd) < 0) {
printk(KERN_WARNING "snd-usb-midi: interrupt endpoint not found\n");
do_int_transfer = 0;
}
}
ep->urb = usb_alloc_urb(0, GFP_KERNEL);
if (!ep->urb) {
snd_usbmidi_in_endpoint_delete(ep);
return -ENOMEM;
}
if (do_int_transfer)
pipe = usb_rcvintpipe(umidi->usb_device, ep_info->epnum);
else
pipe = usb_rcvbulkpipe(umidi->usb_device, ep_info->epnum);
length = usb_maxpacket(umidi->usb_device, pipe, 0);
buffer = kmalloc(length, GFP_KERNEL);
if (!buffer) {
snd_usbmidi_in_endpoint_delete(ep);
return -ENOMEM;
}
if (do_int_transfer)
FILL_INT_URB(ep->urb, umidi->usb_device, pipe, buffer, length,
snd_usbmidi_in_urb_complete, ep, epd->bInterval);
else
FILL_BULK_URB(ep->urb, umidi->usb_device, pipe, buffer, length,
snd_usbmidi_in_urb_complete, ep);
for (i = 0; i < 0x10; ++i)
if (ep_info->in_cables & (1 << i)) {
err = snd_midi_event_new(INPUT_BUFFER_SIZE,
&ep->ports[i].midi_event);
if (err < 0) {
snd_usbmidi_in_endpoint_delete(ep);
return -ENOMEM;
}
}
rep->in = ep;
return 0;
}
static int snd_usbmidi_count_bits(uint16_t x)
{
int i, bits = 0;
for (i = 0; i < 16; ++i)
bits += (x & (1 << i)) != 0;
return bits;
}
/*
* Frees an output endpoint.
* May be called when ep hasn't been initialized completely.
*/
static void snd_usbmidi_out_endpoint_delete(usbmidi_out_endpoint_t* ep)
{
if (ep->tasklet.func)
tasklet_kill(&ep->tasklet);
if (ep->urb) {
if (ep->urb->transfer_buffer) {
usb_unlink_urb(ep->urb);
kfree(ep->urb->transfer_buffer);
}
usb_free_urb(ep->urb);
}
snd_magic_kfree(ep);
}
/*
* Creates an output endpoint, and initializes output ports.
* ALSA ports are created later.
*/
static int snd_usbmidi_out_endpoint_create(usbmidi_t* umidi,
usbmidi_endpoint_info_t* ep_info,
usbmidi_endpoint_t* rep)
{
usbmidi_out_endpoint_t* ep;
int i;
unsigned int pipe;
void* buffer;
rep->out = NULL;
ep = snd_magic_kcalloc(usbmidi_out_endpoint_t, 0, GFP_KERNEL);
if (!ep)
return -ENOMEM;
ep->umidi = umidi;
ep->urb = usb_alloc_urb(0, GFP_KERNEL);
if (!ep->urb) {
snd_usbmidi_out_endpoint_delete(ep);
return -ENOMEM;
}
pipe = usb_sndbulkpipe(umidi->usb_device, ep_info->epnum);
ep->max_transfer = usb_maxpacket(umidi->usb_device, pipe, 1) & ~3;
buffer = kmalloc(ep->max_transfer, GFP_KERNEL);
if (!buffer) {
snd_usbmidi_out_endpoint_delete(ep);
return -ENOMEM;
}
FILL_BULK_URB(ep->urb, umidi->usb_device, pipe, buffer,
ep->max_transfer, snd_usbmidi_out_urb_complete, ep);
spin_lock_init(&ep->buffer_lock);
tasklet_init(&ep->tasklet, snd_usbmidi_out_tasklet, (unsigned long)ep);
for (i = 0; i < 0x10; ++i)
if (ep_info->out_cables & (1 << i)) {
ep->ports[i].ep = ep;
ep->ports[i].cable = i << 4;
}
rep->out = ep;
return 0;
}
/*
* Frees the sequencer client, endpoints and ports.
*/
static int snd_usbmidi_seq_device_delete(snd_seq_device_t* seq_device)
{
usbmidi_t* umidi;
int i, j;
umidi = (usbmidi_t*)SNDRV_SEQ_DEVICE_ARGPTR(seq_device);
if (umidi->seq_client >= 0) {
snd_seq_delete_kernel_client(umidi->seq_client);
umidi->seq_client = -1;
}
for (i = 0; i < MAX_ENDPOINTS; ++i) {
usbmidi_endpoint_t* ep = &umidi->endpoints[i];
if (ep->out) {
snd_usbmidi_out_endpoint_delete(ep->out);
ep->out = NULL;
}
if (ep->in) {
snd_usbmidi_in_endpoint_delete(ep->in);
ep->in = NULL;
}
for (j = 0; j < 0x10; ++j)
if (ep->rmidi[j]) {
snd_device_free(umidi->card, ep->rmidi[j]);
ep->rmidi[j] = NULL;
}
}
return 0;
}
/*
* After input and output endpoints have been initialized, create
* the ALSA port for each input/output port pair in the endpoint.
* *port_idx is the port number, which must be unique over all endpoints.
*/
static int snd_usbmidi_create_endpoint_ports(usbmidi_t* umidi, int ep,
int* port_idx)
{
usbmidi_endpoint_info_t* ep_info = &umidi->device_info.endpoints[ep];
int c, err;
int cap, type, port;
int out, in;
snd_seq_port_callback_t port_callback;
char port_name[48];
for (c = 0; c < 0x10; ++c) {
out = ep_info->out_cables & (1 << c);
in = ep_info->in_cables & (1 << c);
if (!(in || out))
continue;
cap = 0;
memset(&port_callback, 0, sizeof(port_callback));
port_callback.owner = THIS_MODULE;
if (out) {
port_callback.event_input = snd_usbmidi_event_input;
port_callback.private_data = &umidi->endpoints[ep].out->ports[c];
cap |= SNDRV_SEQ_PORT_CAP_WRITE |
SNDRV_SEQ_PORT_CAP_SUBS_WRITE;
}
if (in) {
cap |= SNDRV_SEQ_PORT_CAP_READ |
SNDRV_SEQ_PORT_CAP_SUBS_READ;
}
if (out && in) {
cap |= SNDRV_SEQ_PORT_CAP_DUPLEX;
}
/* TODO: read type bits from element descriptor */
type = SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC;
/* TODO: read port name from jack descriptor */
sprintf(port_name, "%s Port %d",
umidi->device_info.product, *port_idx);
port = snd_seq_event_port_attach(umidi->seq_client,
&port_callback,
cap, type, port_name);
if (port < 0) {
snd_printk(KERN_ERR "cannot create port (error code %d)\n", port);
return port;
}
if (in)
umidi->endpoints[ep].in->ports[c].seq_port = port;
if (*port_idx < SNDRV_MINOR_RAWMIDIS) {
snd_rawmidi_t *rmidi;
snd_virmidi_dev_t *rdev;
err = snd_virmidi_new(umidi->card, *port_idx, &rmidi);
if (err < 0)
return err;
rdev = snd_magic_cast(snd_virmidi_dev_t, rmidi->private_data, return -ENXIO);
strcpy(rmidi->name, port_name);
rdev->seq_mode = SNDRV_VIRMIDI_SEQ_ATTACH;
rdev->client = umidi->seq_client;
rdev->port = port;
err = snd_device_register(umidi->card, rmidi);
if (err < 0) {
snd_device_free(umidi->card, rmidi);
return err;
}
umidi->endpoints[ep].rmidi[c] = rmidi;
}
++*port_idx;
}
return 0;
}
/*
* Create the endpoints and their ports.
*/
static int snd_usbmidi_create_endpoints(usbmidi_t* umidi)
{
int i, err, port_idx = 0;
for (i = 0; i < MAX_ENDPOINTS; ++i) {
usbmidi_endpoint_info_t* ep_info = &umidi->device_info.endpoints[i];
if (!ep_info->epnum)
continue;
if (ep_info->out_cables) {
err = snd_usbmidi_out_endpoint_create(umidi, ep_info,
&umidi->endpoints[i]);
if (err < 0)
return err;
}
if (ep_info->in_cables) {
err = snd_usbmidi_in_endpoint_create(umidi, ep_info,
&umidi->endpoints[i]);
if (err < 0)
return err;
}
err = snd_usbmidi_create_endpoint_ports(umidi, i, &port_idx);
if (err < 0)
return err;
printk(KERN_INFO "snd-usb-midi: endpoint %d: created %d output and %d input ports\n",
ep_info->epnum,
snd_usbmidi_count_bits(ep_info->out_cables),
snd_usbmidi_count_bits(ep_info->in_cables));
}
return 0;
}
/*
* Initialize the sequencer device.
*/
static int snd_usbmidi_seq_device_new(snd_seq_device_t* seq_device)
{
usbmidi_t* umidi;
snd_seq_client_callback_t client_callback;
snd_seq_client_info_t client_info;
int i, err;
umidi = (usbmidi_t*)SNDRV_SEQ_DEVICE_ARGPTR(seq_device);
memset(&client_callback, 0, sizeof(client_callback));
client_callback.allow_output = 1;
client_callback.allow_input = 1;
umidi->seq_client = snd_seq_create_kernel_client(umidi->card, 0,
&client_callback);
if (umidi->seq_client < 0)
return umidi->seq_client;
memset(&client_info, 0, sizeof(client_info));
client_info.client = umidi->seq_client;
client_info.type = KERNEL_CLIENT;
sprintf(client_info.name, "%s %s",
umidi->device_info.vendor, umidi->device_info.product);
snd_seq_kernel_client_ctl(umidi->seq_client,
SNDRV_SEQ_IOCTL_SET_CLIENT_INFO,
&client_info);
err = snd_usbmidi_create_endpoints(umidi);
if (err < 0) {
snd_usbmidi_seq_device_delete(seq_device);
return err;
}
for (i = 0; i < MAX_ENDPOINTS; ++i)
if (umidi->endpoints[i].in)
snd_usbmidi_submit_urb(umidi->endpoints[i].in->urb,
GFP_KERNEL);
return 0;
}
static int snd_usbmidi_card_create(usb_device_t* usb_device,
usbmidi_device_info_t* device_info,
snd_card_t** rcard)
{
snd_card_t* card;
snd_seq_device_t* seq_device;
usbmidi_t* umidi;
int dev, err;
if (rcard)
*rcard = NULL;
down(&snd_usbmidi_open_mutex);
for (dev = 0; dev < SNDRV_CARDS; ++dev) {
if (snd_enable[dev] && !snd_usbmidi_card_used[dev] &&
(snd_vid[dev] == -1 ||
snd_vid[dev] == usb_device->descriptor.idVendor) &&
(snd_pid[dev] == -1 ||
snd_pid[dev] == usb_device->descriptor.idProduct))
break;
}
if (dev >= SNDRV_CARDS) {
up(&snd_usbmidi_open_mutex);
return -ENOENT;
}
card = snd_card_new(snd_index[dev], snd_id[dev], THIS_MODULE, 0);
if (!card) {
up(&snd_usbmidi_open_mutex);
return -ENOMEM;
}
strcpy(card->driver, "USB MIDI");
snprintf(card->shortname, sizeof(card->shortname), "%s %s",
device_info->vendor, device_info->product);
snprintf(card->longname, sizeof(card->longname), "%s %s at %03d/%03d if %d",
device_info->vendor, device_info->product,
usb_device->bus->busnum, usb_device->devnum,
device_info->ifnum);
card->private_data = (void*)dev;
err = snd_seq_device_new(card, 0, SNDRV_SEQ_DEV_ID_USBMIDI,
sizeof(usbmidi_t), &seq_device);
if (err < 0) {
snd_card_free(card);
up(&snd_usbmidi_open_mutex);
return err;
}
strcpy(seq_device->name, card->shortname);
umidi = (usbmidi_t*)SNDRV_SEQ_DEVICE_ARGPTR(seq_device);
umidi->card = card;
umidi->usb_device = usb_device;
umidi->dev = dev;
umidi->seq_client = -1;
umidi->device_info = *device_info;
err = snd_card_register(card);
if (err < 0) {
snd_card_free(card);
up(&snd_usbmidi_open_mutex);
return err;
}
snd_usbmidi_card_used[dev] = 1;
up(&snd_usbmidi_open_mutex);
if (rcard)
*rcard = card;
return 0;
}
/*
* If the first endpoint isn't specified, use the first endpoint in the
* first alternate setting of the interface.
*/
static int snd_usbmidi_detect_endpoint(usb_device_t* usb_device,
usbmidi_device_info_t* device_info)
{
usb_interface_t* intf;
usb_interface_descriptor_t* intfd;
usb_endpoint_descriptor_t* epd;
if (device_info->endpoints[0].epnum == -1) {
intf = usb_ifnum_to_if(usb_device, device_info->ifnum);
if (!intf || intf->num_altsetting < 1)
return -ENOENT;
intfd = intf->altsetting;
if (intfd->bNumEndpoints < 1)
return -ENOENT;
epd = intfd->endpoint;
device_info->endpoints[0].epnum = epd->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
}
return 0;
}
/*
* Searches for the alternate setting with the greatest number of bulk transfer
* endpoints.
*/
static usb_interface_descriptor_t* snd_usbmidi_get_altsetting(usb_device_t* usb_device,
usb_interface_t* intf)
{
int i, best = -1;
int best_out = 0, best_in = 0;
usb_interface_descriptor_t* intfd;
if (intf->num_altsetting == 1)
return &intf->altsetting[0];
for (i = 0; i < intf->num_altsetting; ++i) {
int out = 0, in = 0, j;
for (j = 0; j < intf->altsetting[i].bNumEndpoints; ++j) {
usb_endpoint_descriptor_t* ep = &intf->altsetting[i].endpoint[j];
if ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK) {
if (ep->bEndpointAddress & USB_DIR_IN)
++in;
else
++out;
}
}
if ((out >= best_out && in >= best_in) &&
(out > best_out || in > best_in)) {
best_out = out;
best_in = in;
best = i;
}
}
if (best < 0)
return NULL;
intfd = &intf->altsetting[best];
usb_set_interface(usb_device, intfd->bInterfaceNumber, intfd->bAlternateSetting);
return intfd;
}
/*
* Returns MIDIStreaming device capabilities in device_info.
*/
static int snd_usbmidi_get_ms_info(usb_device_t* usb_device,
unsigned int ifnum,
usbmidi_device_info_t* device_info)
{
usb_interface_t* intf;
usb_interface_descriptor_t* intfd;
usb_ms_header_descriptor_t* ms_header;
usb_endpoint_descriptor_t* ep;
usb_ms_endpoint_descriptor_t* ms_ep;
int i, epidx;
memset(device_info, 0, sizeof(*device_info));
if (usb_device->descriptor.iManufacturer == 0 ||
usb_string(usb_device, usb_device->descriptor.iManufacturer,
device_info->vendor, sizeof(device_info->vendor)) < 0)
sprintf(device_info->vendor, "Unknown Vendor %x", usb_device->descriptor.idVendor);
if (usb_device->descriptor.iProduct == 0 ||
usb_string(usb_device, usb_device->descriptor.iProduct,
device_info->product, sizeof(device_info->product)) < 0)
sprintf(device_info->product, "Unknown Device %x", usb_device->descriptor.idProduct);
intf = usb_ifnum_to_if(usb_device, ifnum);
if (!intf)
return -ENXIO;
device_info->ifnum = ifnum;
printk(KERN_INFO "snd-usb-midi: using interface %d\n",
intf->altsetting[0].bInterfaceNumber);
intfd = snd_usbmidi_get_altsetting(usb_device, intf);
if (!intfd) {
printk(KERN_ERR "snd-usb-midi: could not determine altsetting\n");
return -ENXIO;
}
ms_header = (usb_ms_header_descriptor_t*)intfd->extra;
if (intfd->extralen >= USB_DST_MS_HEADER_SIZE &&
ms_header->bLength >= USB_DST_MS_HEADER_SIZE &&
ms_header->bDescriptorType == USB_DT_CS_INTERFACE &&
ms_header->bDescriptorSubtype == USB_DST_MS_HEADER)
printk(KERN_INFO "snd-usb-midi: MIDIStreaming version %02x.%02x\n",
ms_header->bcdMSC[1], ms_header->bcdMSC[0]);
else
printk(KERN_WARNING "snd-usb-midi: MIDIStreaming interface descriptor not found\n");
epidx = 0;
for (i = 0; i < intfd->bNumEndpoints; ++i) {
ep = &intfd->endpoint[i];
if ((ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_BULK)
continue;
ms_ep = (usb_ms_endpoint_descriptor_t*)ep->extra;
if (ep->extralen < USB_DST_MS_GENERAL_SIZE ||
ms_ep->bLength < USB_DST_MS_GENERAL_SIZE ||
ms_ep->bDescriptorType != USB_DT_CS_ENDPOINT ||
ms_ep->bDescriptorSubtype != USB_DST_MS_GENERAL)
continue;
if (device_info->endpoints[epidx].epnum != 0 &&
device_info->endpoints[epidx].epnum != (ep->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK)) {
++epidx;
if (epidx >= MAX_ENDPOINTS) {
printk(KERN_WARNING "snd-usb-midi: too many endpoints\n");
break;
}
}
device_info->endpoints[epidx].epnum = ep->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK;
if (ep->bEndpointAddress & USB_DIR_IN) {
device_info->endpoints[epidx].in_cables = (1 << ms_ep->bNumEmbMIDIJack) - 1;
} else {
device_info->endpoints[epidx].out_cables = (1 << ms_ep->bNumEmbMIDIJack) - 1;
}
printk(KERN_INFO "snd-usb-midi: detected %d %s jack(s) on endpoint %d\n",
ms_ep->bNumEmbMIDIJack,
ep->bEndpointAddress & USB_DIR_IN ? "input" : "output",
device_info->endpoints[epidx].epnum);
}
return 0;
}
/*
* Returns device capabilities, either explicitly supplied or from the
* class-specific descriptors.
*/
static int snd_usbmidi_get_device_info(usb_device_t* usb_device,
unsigned int ifnum,
const usb_device_id_t* usb_device_id,
usbmidi_device_info_t* device_info)
{
if (usb_device_id->driver_info) {
usbmidi_device_info_t* id_info = (usbmidi_device_info_t*)usb_device_id->driver_info;
if (ifnum != id_info->ifnum)
return -ENXIO;
*device_info = *id_info;
if (snd_usbmidi_detect_endpoint(usb_device, device_info) < 0)
return -ENXIO;
} else {
if (snd_usbmidi_get_ms_info(usb_device, ifnum, device_info) < 0)
return -ENXIO;
}
return 0;
}
/*
* Probes for a supported device.
*/
static void* snd_usbmidi_usb_probe(usb_device_t* device,
unsigned int ifnum,
const usb_device_id_t* device_id)
{
usbmidi_device_info_t device_info;
snd_card_t* card = NULL;
int err;
if (snd_usbmidi_get_device_info(device, ifnum, device_id,
&device_info) == 0) {
printk(KERN_INFO "snd-usb-midi: detected %s %s\n",
device_info.vendor, device_info.product);
err = snd_usbmidi_card_create(device, &device_info, &card);
if (err < 0)
snd_printk(KERN_ERR "cannot create card (error code %d)\n", err);
}
return card;
}
/*
* Frees the device.
*/
static void snd_usbmidi_usb_disconnect(usb_device_t* usb_device, void* ptr)
{
snd_card_t* card = (snd_card_t*)ptr;
int dev = (int)card->private_data;
snd_card_free(card);
down(&snd_usbmidi_open_mutex);
snd_usbmidi_card_used[dev] = 0;
up(&snd_usbmidi_open_mutex);
}
/*
* Information about devices with broken descriptors.
*/
static usbmidi_device_info_t snd_usbmidi_yamaha_ux256_info = {
/* from NetBSD's umidi driver */
.vendor = "Yamaha", .product = "UX256",
.ifnum = 0,
.endpoints = {{ -1, 0xffff, 0x00ff }}
};
static usbmidi_device_info_t snd_usbmidi_yamaha_mu1000_info = {
/* from Nagano Daisuke's usb-midi driver */
.vendor = "Yamaha", .product = "MU1000",
.ifnum = 0,
.endpoints = {{ 1, 0x000f, 0x0001 }}
};
/*
* There ain't no such thing as a standard-compliant Roland device.
* Apparently, Roland decided not to risk to have wrong entries in the USB
* descriptors. The consequence is that class-specific descriptors are
* conspicuous by their absence.
*
* And now you may guess which company was responsible for writing the
* USB Device Class Definition for MIDI Devices.
*/
static usbmidi_device_info_t snd_usbmidi_roland_ua100_info = {
.vendor = "Roland", .product = "UA-100",
.ifnum = 2,
.endpoints = {{ -1, 0x0007, 0x0007 }}
};
static usbmidi_device_info_t snd_usbmidi_roland_um4_info = {
.vendor = "EDIROL", .product = "UM-4",
.ifnum = 2,
.endpoints = {{ -1, 0x000f, 0x000f }}
};
static usbmidi_device_info_t snd_usbmidi_roland_sc8850_info = {
.vendor = "Roland", .product = "SC-8850",
.ifnum = 2,
.endpoints = {{ -1, 0x003f, 0x003f }}
};
static usbmidi_device_info_t snd_usbmidi_roland_u8_info = {
.vendor = "Roland", .product = "U-8",
.ifnum = 2,
.endpoints = {{ -1, 0x0003, 0x0003 }}
};
static usbmidi_device_info_t snd_usbmidi_roland_um2_info = {
.vendor = "EDIROL", .product = "UM-2",
.ifnum = 2,
.endpoints = {{ -1, 0x0003, 0x0003 }}
};
static usbmidi_device_info_t snd_usbmidi_roland_sc8820_info = {
.vendor = "Roland", .product = "SC-8820",
.ifnum = 2,
.endpoints = {{ -1, 0x0013, 0x0013 }}
};
static usbmidi_device_info_t snd_usbmidi_roland_pc300_info = {
.vendor = "Roland", .product = "PC-300",
.ifnum = 2,
.endpoints = {{ -1, 0x0001, 0x0001 }}
};
static usbmidi_device_info_t snd_usbmidi_roland_um1_info = {
.vendor = "EDIROL", .product = "UM-1",
.ifnum = 2,
.endpoints = {{ -1, 0x0001, 0x0001 }}
};
static usbmidi_device_info_t snd_usbmidi_roland_sk500_info = {
.vendor = "Roland", .product = "SK-500",
.ifnum = 2,
.endpoints = {{ -1, 0x0013, 0x0013 }}
};
static usbmidi_device_info_t snd_usbmidi_roland_scd70_info = {
.vendor = "Roland", .product = "SC-D70",
.ifnum = 2,
.endpoints = {{ -1, 0x0007, 0x0007 }}
};
static usbmidi_device_info_t snd_usbmidi_roland_xv5050_info = {
.vendor = "Roland", .product = "XV-5050",
.ifnum = 0,
.endpoints = {{ -1, 0x0001, 0x0001 }}
};
static usbmidi_device_info_t snd_usbmidi_roland_um880_info = {
.vendor = "EDIROL", .product = "UM-880",
.ifnum = 0,
.endpoints = {{ -1, 0x01ff, 0x01ff }}
};
static usbmidi_device_info_t snd_usbmidi_roland_sd90_info = {
.vendor = "EDIROL", .product = "SD-90",
.ifnum = 2,
.endpoints = {{ -1, 0x000f, 0x000f }}
};
static usbmidi_device_info_t snd_usbmidi_roland_um550_info = {
.vendor = "EDIROL", .product = "UM-550",
.ifnum = 0,
.endpoints = {{ -1, 0x003f, 0x003f }}
};
#define USBMIDI_NONCOMPLIANT_DEVICE(vid, pid, name) \
USB_DEVICE(vid, pid), \
driver_info: (unsigned long)&snd_usbmidi_##name##_info
static usb_device_id_t snd_usbmidi_usb_id_table[] = {
{ match_flags: USB_DEVICE_ID_MATCH_INT_CLASS |
USB_DEVICE_ID_MATCH_INT_SUBCLASS,
bInterfaceClass: USB_CLASS_AUDIO,
bInterfaceSubClass: USB_SUBCLASS_MIDISTREAMING },
{ USBMIDI_NONCOMPLIANT_DEVICE(0x0499, 0x1000, yamaha_ux256) },
{ USBMIDI_NONCOMPLIANT_DEVICE(0x0499, 0x1001, yamaha_mu1000) },
{ USBMIDI_NONCOMPLIANT_DEVICE(0x0582, 0x0000, roland_ua100) },
{ USBMIDI_NONCOMPLIANT_DEVICE(0x0582, 0x0002, roland_um4) },
{ USBMIDI_NONCOMPLIANT_DEVICE(0x0582, 0x0003, roland_sc8850) },
{ USBMIDI_NONCOMPLIANT_DEVICE(0x0582, 0x0004, roland_u8) },
{ USBMIDI_NONCOMPLIANT_DEVICE(0x0582, 0x0005, roland_um2) },
{ USBMIDI_NONCOMPLIANT_DEVICE(0x0582, 0x0007, roland_sc8820) },
{ USBMIDI_NONCOMPLIANT_DEVICE(0x0582, 0x0008, roland_pc300) },
{ USBMIDI_NONCOMPLIANT_DEVICE(0x0582, 0x0009, roland_um1) },
{ USBMIDI_NONCOMPLIANT_DEVICE(0x0582, 0x000b, roland_sk500) },
{ USBMIDI_NONCOMPLIANT_DEVICE(0x0582, 0x000c, roland_scd70) },
{ USBMIDI_NONCOMPLIANT_DEVICE(0x0582, 0x0012, roland_xv5050) },
{ USBMIDI_NONCOMPLIANT_DEVICE(0x0582, 0x0014, roland_um880) },
{ USBMIDI_NONCOMPLIANT_DEVICE(0x0582, 0x0016, roland_sd90) },
{ USBMIDI_NONCOMPLIANT_DEVICE(0x0582, 0x0023, roland_um550) },
{ /* terminator */ }
};
MODULE_DEVICE_TABLE(usb, snd_usbmidi_usb_id_table);
static usb_driver_t snd_usbmidi_usb_driver = {
.name = "snd-usb-midi",
.probe = snd_usbmidi_usb_probe,
.disconnect = snd_usbmidi_usb_disconnect,
.id_table = snd_usbmidi_usb_id_table,
.driver_list = LIST_HEAD_INIT(snd_usbmidi_usb_driver.driver_list)
};
static int __init snd_usbmidi_module_init(void)
{
static snd_seq_dev_ops_t ops = {
snd_usbmidi_seq_device_new,
snd_usbmidi_seq_device_delete
};
int err;
err = snd_seq_device_register_driver(SNDRV_SEQ_DEV_ID_USBMIDI, &ops,
sizeof(usbmidi_t));
if (err < 0)
return err;
err = usb_register(&snd_usbmidi_usb_driver);
if (err < 0) {
snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_USBMIDI);
return err;
}
return 0;
}
static void __exit snd_usbmidi_module_exit(void)
{
usb_deregister(&snd_usbmidi_usb_driver);
snd_seq_device_unregister_driver(SNDRV_SEQ_DEV_ID_USBMIDI);
}
module_init(snd_usbmidi_module_init)
module_exit(snd_usbmidi_module_exit)
#ifndef MODULE
/*
* format is snd-usb-midi=snd_enable,snd_index,snd_id,
* snd_vid,snd_pid,snd_int_transfer
*/
static int __init snd_usbmidi_module_setup(char* str)
{
static unsigned __initdata nr_dev = 0;
if (nr_dev >= SNDRV_CARDS)
return 0;
(void)(get_option(&str, &snd_enable[nr_dev]) == 2 &&
get_option(&str, &snd_index[nr_dev]) == 2 &&
get_id(&str, &snd_id[nr_dev]) == 2 &&
get_option(&str, &snd_vid[nr_dev]) == 2 &&
get_option(&str, &snd_pid[nr_dev]) == 2 &&
get_option(&str, &snd_int_transfer[nr_dev]) == 2);
++nr_dev;
return 1;
}
__setup("snd-usb-midi=", snd_usbmidi_module_setup);
#endif /* !MODULE */
/*
* (Tentative) USB Audio Driver for ALSA
*
* Mixer control 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)
*
*
* 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/bitops.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/usb.h>
#include <sound/core.h>
#include <sound/control.h>
#include "usbaudio.h"
/*
*/
typedef struct usb_mixer_build mixer_build_t;
typedef struct usb_audio_term usb_audio_term_t;
typedef struct usb_mixer_elem_info usb_mixer_elem_info_t;
struct usb_audio_term {
int id;
int type;
int channels;
unsigned int chconfig;
int name;
};
struct usb_mixer_build {
snd_usb_audio_t *chip;
unsigned char *buffer;
unsigned int buflen;
unsigned int ctrlif;
unsigned long unitbitmap[32/sizeof(unsigned long)];
usb_audio_term_t oterm;
};
struct usb_mixer_elem_info {
snd_usb_audio_t *chip;
unsigned int ctrlif;
unsigned int id;
unsigned int control;
unsigned int cmask; /* channel mask bitmap: 0 = master */
int channels;
int val_type;
int min, max;
};
enum {
USB_FEATURE_MUTE = 0,
USB_FEATURE_VOLUME,
USB_FEATURE_BASS,
USB_FEATURE_MID,
USB_FEATURE_TREBLE,
USB_FEATURE_GEQ,
USB_FEATURE_AGC,
USB_FEATURE_DELAY,
USB_FEATURE_BASSBOOST,
FSB_FEATURE_LOUDNESS
};
enum {
USB_MIXER_BOOLEAN,
USB_MIXER_INV_BOOLEAN,
USB_MIXER_S8,
USB_MIXER_U8,
USB_MIXER_S16,
USB_MIXER_U16,
};
#define MAX_CHANNELS 10 /* max logical channels */
/*
* find an audio control unit with the given unit id
*/
static void *find_audio_control_unit(mixer_build_t *state, unsigned char unit)
{
unsigned char *p;
p = NULL;
while ((p = snd_usb_find_desc(state->buffer, state->buflen, p,
USB_DT_CS_INTERFACE, state->ctrlif, -1)) != NULL) {
if (p[0] >= 4 && p[2] >= INPUT_TERMINAL && p[2] <= EXTENSION_UNIT && p[3] == unit)
return p;
}
return NULL;
}
/*
* copy a string with the given id
*/
static int snd_usb_copy_string_desc(mixer_build_t *state, int index, char *buf, int maxlen)
{
int len = usb_string(state->chip->dev, index, buf, maxlen - 1);
buf[len] = 0;
return len;
}
/*
* convert from the byte/word on usb descriptor to the zero-based integer
*/
static int convert_signed_value(usb_mixer_elem_info_t *cval, int val)
{
switch (cval->val_type) {
case USB_MIXER_BOOLEAN:
return !!val;
case USB_MIXER_INV_BOOLEAN:
return !val;
case USB_MIXER_U8:
val &= 0xff;
break;
case USB_MIXER_S8:
val &= 0xff;
if (val >= 0x80)
val -= 0x100;
break;
case USB_MIXER_U16:
val &= 0xffff;
break;
case USB_MIXER_S16:
val &= 0xffff;
if (val >= 0x8000)
val -= 0x10000;
break;
}
return val;
}
/*
* convert from the zero-based int to the byte/word for usb descriptor
*/
static int convert_bytes_value(usb_mixer_elem_info_t *cval, int val)
{
switch (cval->val_type) {
case USB_MIXER_BOOLEAN:
return !!val;
case USB_MIXER_INV_BOOLEAN:
return !val;
case USB_MIXER_S8:
case USB_MIXER_U8:
return val & 0xff;
case USB_MIXER_S16:
case USB_MIXER_U16:
return val & 0xffff;
}
return 0; /* not reached */
}
static int get_relative_value(usb_mixer_elem_info_t *cval, int val)
{
if (val < cval->min)
return 0;
else if (val > cval->max)
return cval->max - cval->min;
else
return val - cval->min;
}
static int get_abs_value(usb_mixer_elem_info_t *cval, int val)
{
if (val < 0)
return cval->min;
val += cval->min;
if (val > cval->max)
return cval->max;
return val;
}
/*
* retrieve a mixer value
*/
static int get_ctl_value(usb_mixer_elem_info_t *cval, int request, int validx, int *value_ret)
{
unsigned char buf[2];
int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1;
if (usb_control_msg(cval->chip->dev, usb_rcvctrlpipe(cval->chip->dev, 0),
request,
USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_IN,
validx, cval->ctrlif | (cval->id << 8),
buf, val_len, HZ) < 0)
return -EINVAL;
*value_ret = convert_signed_value(cval, snd_usb_combine_bytes(buf, val_len));
return 0;
}
static int get_cur_ctl_value(usb_mixer_elem_info_t *cval, int validx, int *value)
{
return get_ctl_value(cval, GET_CUR, validx, value);
}
/* channel = 0: master, 1 = first channel */
inline static int get_cur_mix_value(usb_mixer_elem_info_t *cval, int channel, int *value)
{
return get_ctl_value(cval, GET_CUR, ((cval->control + 1) << 8) | channel, value);
}
/*
* set a mixer value
*/
static int set_ctl_value(usb_mixer_elem_info_t *cval, int request, int validx, int value_set)
{
unsigned char buf[2];
int val_len = cval->val_type >= USB_MIXER_S16 ? 2 : 1;
value_set = convert_bytes_value(cval, value_set);
buf[0] = value_set & 0xff;
buf[1] = (value_set >> 8) & 0xff;
return usb_control_msg(cval->chip->dev, usb_sndctrlpipe(cval->chip->dev, 0),
request,
USB_RECIP_INTERFACE | USB_TYPE_CLASS | USB_DIR_OUT,
validx, cval->ctrlif | (cval->id << 8),
buf, val_len, HZ);
}
static int set_cur_ctl_value(usb_mixer_elem_info_t *cval, int validx, int value)
{
return set_ctl_value(cval, SET_CUR, validx, value);
}
inline static int set_cur_mix_value(usb_mixer_elem_info_t *cval, int channel, int value)
{
return set_ctl_value(cval, SET_CUR, ((cval->control + 1) << 8) | channel, value);
}
/*
* parser routines begin here...
*/
static int parse_audio_unit(mixer_build_t *state, int unitid);
/*
* check if the input/output channel routing is enabled on the given bitmap.
* used for mixer unit parser
*/
static int check_matrix_bitmap(unsigned char *bmap, int ich, int och, int num_outs)
{
int idx = ich * num_outs + och;
return bmap[-(idx >> 3)] & (0x80 >> (idx & 7));
}
/*
* add an alsa control element
* search and increment the index until an empty slot is found.
*
* if failed, give up and free the control instance.
*/
static int add_control_to_empty(snd_card_t *card, snd_kcontrol_t *kctl)
{
int err;
while (snd_ctl_find_id(card, &kctl->id))
kctl->id.index++;
if ((err = snd_ctl_add(card, kctl)) < 0) {
snd_printk(KERN_ERR "cannot add control\n");
snd_ctl_free_one(kctl);
}
return err;
}
/*
* get a terminal name string
*/
static struct iterm_name_combo {
int type;
char *name;
} iterm_names[] = {
{ 0x0300, "Output" },
{ 0x0301, "Speaker" },
{ 0x0302, "Headphone" },
{ 0x0303, "HMD Audio" },
{ 0x0304, "Desktop Speaker" },
{ 0x0305, "Room Speaker" },
{ 0x0306, "Com Speaker" },
{ 0x0307, "LFE" },
{ 0x0600, "External In" },
{ 0x0601, "Analog In" },
{ 0x0602, "Digital In" },
{ 0x0603, "Line" },
{ 0x0604, "Legacy In" },
{ 0x0605, "IEC958 In" },
{ 0x0606, "1394 DA Stream" },
{ 0x0607, "1394 DV Stream" },
{ 0x0700, "Embedded" },
{ 0x0701, "Noise Source" },
{ 0x0702, "Equalization Noise" },
{ 0x0703, "CD" },
{ 0x0704, "DAT" },
{ 0x0705, "DCC" },
{ 0x0706, "MiniDisk" },
{ 0x0707, "Analog Tape" },
{ 0x0708, "Phonograph" },
{ 0x0709, "VCR Audio" },
{ 0x070a, "Video Disk Audio" },
{ 0x070b, "DVD Audio" },
{ 0x070c, "TV Tuner Audio" },
{ 0x070d, "Satellite Rec Audio" },
{ 0x070e, "Cable Tuner Audio" },
{ 0x070f, "DSS Audio" },
{ 0x0710, "Radio Receiver" },
{ 0x0711, "Radio Transmitter" },
{ 0x0712, "Multi-Track Recorder" },
{ 0x0713, "Synthesizer" },
{ 0 },
};
static int get_term_name(mixer_build_t *state, usb_audio_term_t *iterm,
unsigned char *name, int maxlen, int term_only)
{
struct iterm_name_combo *names;
if (iterm->name)
return snd_usb_copy_string_desc(state, iterm->name, name, maxlen);
/* virtual type - not a real terminal */
if (iterm->type >> 16) {
if (term_only)
return 0;
switch (iterm->type >> 16) {
case SELECTOR_UNIT:
strcpy(name, "Selector"); return 8;
case PROCESSING_UNIT:
strcpy(name, "Process Unit"); return 12;
case EXTENSION_UNIT:
strcpy(name, "Ext Unit"); return 8;
case MIXER_UNIT:
strcpy(name, "Mixer"); return 5;
default:
return sprintf(name, "Unit %d", iterm->id);
}
}
switch (iterm->type & 0xff00) {
case 0x0100:
strcpy(name, "PCM"); return 3;
case 0x0200:
strcpy(name, "Mic"); return 3;
case 0x0400:
strcpy(name, "Headset"); return 7;
case 0x0500:
strcpy(name, "Phone"); return 5;
}
for (names = iterm_names; names->type; names++)
if (names->type == iterm->type) {
strcpy(name, names->name);
return strlen(names->name);
}
return 0;
}
/*
* parse the source unit recursively until it reaches to a terminal
* or a branched unit.
*/
static int check_input_term(mixer_build_t *state, int id, usb_audio_term_t *term)
{
unsigned char *p1;
memset(term, 0, sizeof(*term));
while ((p1 = find_audio_control_unit(state, id)) != NULL) {
term->id = id;
switch (p1[2]) {
case INPUT_TERMINAL:
term->type = combine_word(p1 + 4);
term->channels = p1[7];
term->chconfig = combine_word(p1 + 8);
term->name = p1[11];
return 0;
case FEATURE_UNIT:
id = p1[4];
break; /* continue to parse */
case MIXER_UNIT:
term->type = p1[2] << 16; /* virtual type */
term->channels = p1[5 + p1[4]];
term->chconfig = combine_word(p1 + 6 + p1[4]);
term->name = p1[p1[0] - 1];
return 0;
case SELECTOR_UNIT:
/* call recursively to retrieve the channel info */
if (check_input_term(state, p1[5], term) < 0)
return -ENODEV;
term->type = p1[2] << 16; /* virtual type */
term->id = id;
term->name = p1[9 + p1[0] - 1];
return 0;
case PROCESSING_UNIT:
case EXTENSION_UNIT:
if (p1[6] == 1) {
id = p1[7];
break; /* continue to parse */
}
term->type = p1[2] << 16; /* virtual type */
term->channels = p1[7 + p1[6]];
term->chconfig = combine_word(p1 + 8 + p1[6]);
term->name = p1[12 + p1[6] + p1[11 + p1[6]]];
return 0;
default:
return -ENODEV;
}
}
return -ENODEV;
}
/*
* Feature Unit
*/
/* feature unit control information */
struct usb_feature_control_info {
const char *name;
unsigned int type; /* control type (mute, volume, etc.) */
};
static struct usb_feature_control_info audio_feature_info[] = {
{ "Mute", USB_MIXER_INV_BOOLEAN },
{ "Volume", USB_MIXER_S16 },
{ "Tone Control - Bass", USB_MIXER_S8 },
{ "Tone Control - Mid", USB_MIXER_S8 },
{ "Tone Control - Treble", USB_MIXER_S8 },
{ "Graphic Equalizer", USB_MIXER_S8 }, /* FIXME: not implemeted yet */
{ "Auto Gain Control", USB_MIXER_BOOLEAN },
{ "Delay Control", USB_MIXER_U16 },
{ "Bass Boost", USB_MIXER_BOOLEAN },
{ "Loudness", USB_MIXER_BOOLEAN },
};
/* private_free callback */
static void usb_mixer_elem_free(snd_kcontrol_t *kctl)
{
if (kctl->private_data) {
snd_magic_kfree((void *)kctl->private_data);
kctl->private_data = 0;
}
}
/*
* interface to ALSA control for feature/mixer units
*/
/* get a feature/mixer unit info */
static int mixer_ctl_feature_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
{
usb_mixer_elem_info_t *cval = snd_magic_cast(usb_mixer_elem_info_t, kcontrol->private_data, return -EINVAL);
if (cval->val_type == USB_MIXER_BOOLEAN ||
cval->val_type == USB_MIXER_INV_BOOLEAN)
uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
else
uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
uinfo->count = cval->channels;
if (cval->val_type == USB_MIXER_BOOLEAN ||
cval->val_type == USB_MIXER_INV_BOOLEAN) {
uinfo->value.integer.min = 0;
uinfo->value.integer.max = 1;
} else {
uinfo->value.integer.min = 0;
uinfo->value.integer.max = cval->max - cval->min;
}
return 0;
}
/* get the current value from feature/mixer unit */
static int mixer_ctl_feature_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
usb_mixer_elem_info_t *cval = snd_magic_cast(usb_mixer_elem_info_t, kcontrol->private_data, return -EINVAL);
int c, cnt, val, err;
if (cval->cmask) {
cnt = 0;
for (c = 0; c < MAX_CHANNELS; c++) {
if (cval->cmask & (1 << c)) {
err = get_cur_mix_value(cval, c + 1, &val);
if (err < 0)
return err;
val = get_relative_value(cval, val);
ucontrol->value.integer.value[cnt] = val;
cnt++;
}
}
} else {
/* master channel */
err = get_cur_mix_value(cval, 0, &val);
if (err < 0)
return err;
val = get_relative_value(cval, val);
ucontrol->value.integer.value[0] = val;
}
return 0;
}
/* put the current value to feature/mixer unit */
static int mixer_ctl_feature_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
usb_mixer_elem_info_t *cval = snd_magic_cast(usb_mixer_elem_info_t, kcontrol->private_data, return -EINVAL);
int c, cnt, val, oval, err;
int changed = 0;
if (cval->cmask) {
cnt = 0;
for (c = 0; c < MAX_CHANNELS; c++) {
if (cval->cmask & (1 << c)) {
err = get_cur_mix_value(cval, c + 1, &oval);
if (err < 0)
return err;
val = ucontrol->value.integer.value[cnt];
val = get_abs_value(cval, val);
if (oval != val) {
set_cur_mix_value(cval, c + 1, val);
changed = 1;
}
cnt++;
}
}
} else {
/* master channel */
err = get_cur_mix_value(cval, 0, &oval);
if (err < 0)
return err;
val = ucontrol->value.integer.value[0];
val = get_abs_value(cval, val);
if (val != oval) {
set_cur_mix_value(cval, 0, val);
changed = 1;
}
}
return changed;
}
static snd_kcontrol_new_t usb_feature_unit_ctl = {
iface: SNDRV_CTL_ELEM_IFACE_MIXER,
name: "", /* will be filled later manually */
info: mixer_ctl_feature_info,
get: mixer_ctl_feature_get,
put: mixer_ctl_feature_put,
};
/*
* build a feature control
*/
static void build_feature_ctl(mixer_build_t *state, unsigned char *desc,
unsigned int ctl_mask, int control,
usb_audio_term_t *iterm, int unitid)
{
int len = 0;
int nameid = desc[desc[0] - 1];
snd_kcontrol_t *kctl;
usb_mixer_elem_info_t *cval;
if (control == USB_FEATURE_GEQ) {
/* FIXME: not supported yet */
return;
}
cval = snd_magic_kcalloc(usb_mixer_elem_info_t, 0, GFP_KERNEL);
if (! cval) {
snd_printk(KERN_ERR "cannot malloc kcontrol");
return;
}
cval->chip = state->chip;
cval->ctrlif = state->ctrlif;
cval->id = unitid;
cval->control = control;
cval->cmask = ctl_mask;
cval->val_type = audio_feature_info[control].type;
if (ctl_mask == 0)
cval->channels = 1; /* master channel */
else {
int i, c = 0;
for (i = 0; i < 16; i++)
if (ctl_mask & (1 << i))
c++;
cval->channels = c;
}
/* get min/max values */
if (cval->val_type == USB_MIXER_BOOLEAN ||
cval->val_type == USB_MIXER_INV_BOOLEAN)
cval->max = 1;
else {
if (get_ctl_value(cval, GET_MAX, ((cval->control+1) << 8) | (ctl_mask ? 1 : 0), &cval->max) < 0 ||
get_ctl_value(cval, GET_MIN, ((cval->control+1) << 8) | (ctl_mask ? 1 : 0), &cval->min) < 0) {
snd_printk(KERN_ERR "%d:%d: cannot get min/max values for control %d\n", cval->id, cval->ctrlif, control);
snd_magic_kfree(cval);
return;
}
}
kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval);
if (! kctl) {
snd_printk(KERN_ERR "cannot malloc kcontrol");
snd_magic_kfree(cval);
return;
}
kctl->private_free = usb_mixer_elem_free;
if (nameid)
len = snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name));
switch (control) {
case USB_FEATURE_MUTE:
case USB_FEATURE_VOLUME:
/* determine the control name. the rule is:
* - if a name id is given in descriptor, use it.
* - if the connected input can be determined, then use the name
* of terminal type.
* - if the connected output can be determined, use it.
* - otherwise, anonymous name.
*/
if (! nameid) {
len = get_term_name(state, iterm, kctl->id.name, sizeof(kctl->id.name), 1);
if (! len)
len = get_term_name(state, &state->oterm, kctl->id.name, sizeof(kctl->id.name), 1);
if (! len)
len = sprintf(kctl->id.name, "Feature %d", unitid);
}
/* determine the stream direction:
* if the connected output is USB stream, then it's likely a
* capture stream. otherwise it should be playback (hopefully :)
*/
if (! (state->oterm.type >> 16)) {
if ((state->oterm.type & 0xff00) == 0x0100) {
strcpy(kctl->id.name + len, " Capture");
len += 8;
} else {
strcpy(kctl->id.name + len, " Playback");
len += 9;
}
}
strcpy(kctl->id.name + len, control == USB_FEATURE_MUTE ? " Switch" : " Volume");
break;
default:
if (! nameid)
strcpy(kctl->id.name, audio_feature_info[control].name);
break;
}
snd_printdd(KERN_INFO "[%d] FU [%s] ch = %d, val = %d/%d\n",
cval->id, kctl->id.name, cval->channels, cval->min, cval->max);
add_control_to_empty(state->chip->card, kctl);
}
/*
* parse a feature unit
*
* most of controlls are defined here.
*/
static int parse_audio_feature_unit(mixer_build_t *state, int unitid, unsigned char *ftr)
{
int channels, i, j;
usb_audio_term_t iterm;
unsigned int master_bits, first_ch_bits;
int err, csize;
if (ftr[0] < 7 || ! (csize = ftr[5]) || ftr[0] < 7 + csize) {
snd_printk(KERN_ERR "usbaudio: unit %u: invalid FEATURE_UNIT descriptor\n", unitid);
return -EINVAL;
}
/* parse the source unit */
if ((err = parse_audio_unit(state, ftr[4])) < 0)
return err;
/* determine the input source type and name */
if (check_input_term(state, ftr[4], &iterm) < 0)
return -EINVAL;
channels = (ftr[0] - 7) / csize - 1;
master_bits = snd_usb_combine_bytes(ftr + 6, csize);
if (channels > 0)
first_ch_bits = snd_usb_combine_bytes(ftr + 6 + csize, csize);
else
first_ch_bits = 0;
/* check all control types */
for (i = 0; i < 10; i++) {
unsigned int ch_bits = 0;
for (j = 0; j < channels; j++) {
unsigned int mask = snd_usb_combine_bytes(ftr + 6 + csize * (j+1), csize);
if (mask & (1 << i))
ch_bits |= (1 << j);
}
if (ch_bits & 1) /* the first channel must be set (for ease of programming) */
build_feature_ctl(state, ftr, ch_bits, i, &iterm, unitid);
if (master_bits & (1 << i))
build_feature_ctl(state, ftr, 0, i, &iterm, unitid);
}
return 0;
}
/*
* Mixer Unit
*/
/*
* build a mixer unit control
*
* the callbacks are identical with feature unit.
* input channel number (zero based) is given in control field instead.
*/
static void build_mixer_unit_ctl(mixer_build_t *state, unsigned char *desc,
int in_ch, int unitid)
{
usb_mixer_elem_info_t *cval;
int num_ins = desc[4];
int num_outs = desc[5 + num_ins];
int i, len;
snd_kcontrol_t *kctl;
usb_audio_term_t iterm;
cval = snd_magic_kcalloc(usb_mixer_elem_info_t, 0, GFP_KERNEL);
if (! cval)
return;
if (check_input_term(state, desc[5 + in_ch], &iterm) < 0)
return;
cval->chip = state->chip;
cval->ctrlif = state->ctrlif;
cval->id = unitid;
cval->control = in_ch;
cval->val_type = USB_MIXER_S16;
for (i = 0; i < num_outs; i++) {
if (check_matrix_bitmap(desc + 9 + num_ins, in_ch, i, num_outs)) {
cval->cmask |= (1 << i);
cval->channels++;
}
}
/* get min/max values */
if (get_ctl_value(cval, GET_MAX, ((in_ch+1) << 8) | 1, &cval->max) < 0 ||
get_ctl_value(cval, GET_MIN, ((in_ch+1) << 8) | 1, &cval->min) < 0) {
snd_printk(KERN_ERR "cannot get min/max values for mixer\n");
snd_magic_kfree(cval);
return;
}
kctl = snd_ctl_new1(&usb_feature_unit_ctl, cval);
if (! kctl) {
snd_printk(KERN_ERR "cannot malloc kcontrol");
snd_magic_kfree(cval);
return;
}
kctl->private_free = usb_mixer_elem_free;
len = get_term_name(state, &iterm, kctl->id.name, sizeof(kctl->id.name), 0);
if (! len)
len = sprintf(kctl->id.name, "Mixer Source %d", in_ch);
strcpy(kctl->id.name + len, " Volume");
snd_printdd(KERN_INFO "[%d] MU [%s] ch = %d, val = %d/%d\n",
cval->id, kctl->id.name, cval->channels, cval->min, cval->max);
add_control_to_empty(state->chip->card, kctl);
}
/*
* parse a mixer unit
*/
static int parse_audio_mixer_unit(mixer_build_t *state, int unitid, unsigned char *desc)
{
int num_ins, num_outs;
int i, err;
if (desc[0] < 12 || ! (num_ins = desc[4]) || ! (num_outs = desc[5 + num_ins]))
return -EINVAL;
for (i = 0; i < num_ins; i++) {
err = parse_audio_unit(state, desc[5 + i]);
if (err < 0)
return err;
if (check_matrix_bitmap(desc + 9 + num_ins, i, 0, num_outs))
build_mixer_unit_ctl(state, desc, i, unitid);
}
return 0;
}
/*
* Processing Unit / Extension Unit
*/
/* get callback for processing/extension unit */
static int mixer_ctl_procunit_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
usb_mixer_elem_info_t *cval = snd_magic_cast(usb_mixer_elem_info_t, kcontrol->private_data, return -EINVAL);
int err, val;
err = get_cur_ctl_value(cval, cval->control, &val);
if (err < 0)
return err;
val = get_relative_value(cval, val);
ucontrol->value.integer.value[0] = val;
return 0;
}
/* put callback for processing/extension unit */
static int mixer_ctl_procunit_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
usb_mixer_elem_info_t *cval = snd_magic_cast(usb_mixer_elem_info_t, kcontrol->private_data, return -EINVAL);
int val, oval, err;
err = get_cur_ctl_value(cval, cval->control, &oval);
if (err < 0)
return err;
val = ucontrol->value.integer.value[0];
val = get_abs_value(cval, val);
if (val != oval) {
set_cur_ctl_value(cval, cval->control, val);
return 1;
}
return 0;
}
/* alsa control interface for processing/extension unit */
static snd_kcontrol_new_t mixer_procunit_ctl = {
iface: SNDRV_CTL_ELEM_IFACE_MIXER,
name: "", /* will be filled later */
info: mixer_ctl_feature_info,
get: mixer_ctl_procunit_get,
put: mixer_ctl_procunit_put,
};
/*
* predefined data for processing units
*/
struct procunit_value_info {
int control;
char *suffix;
int val_type;
};
struct procunit_info {
int type;
char *name;
struct procunit_value_info *values;
};
static struct procunit_value_info updown_proc_info[] = {
{ 0x01, "Switch", USB_MIXER_BOOLEAN },
{ 0x02, "Mode Select", USB_MIXER_U8 },
{ 0 }
};
static struct procunit_value_info prologic_proc_info[] = {
{ 0x01, "Switch", USB_MIXER_BOOLEAN },
{ 0x02, "Mode Select", USB_MIXER_U8 },
{ 0 }
};
static struct procunit_value_info threed_enh_proc_info[] = {
{ 0x01, "Switch", USB_MIXER_BOOLEAN },
{ 0x02, "Spaciousness", USB_MIXER_U8 },
{ 0 }
};
static struct procunit_value_info reverb_proc_info[] = {
{ 0x01, "Switch", USB_MIXER_BOOLEAN },
{ 0x02, "Level", USB_MIXER_U8 },
{ 0x03, "Time", USB_MIXER_U16 },
{ 0x04, "Delay", USB_MIXER_U8 },
{ 0 }
};
static struct procunit_value_info chorus_proc_info[] = {
{ 0x01, "Switch", USB_MIXER_BOOLEAN },
{ 0x02, "Level", USB_MIXER_U8 },
{ 0x03, "Rate", USB_MIXER_U16 },
{ 0x04, "Depth", USB_MIXER_U16 },
{ 0 }
};
static struct procunit_value_info dcr_proc_info[] = {
{ 0x01, "Switch", USB_MIXER_BOOLEAN },
{ 0x02, "Ratio", USB_MIXER_U16 },
{ 0x03, "Max Amp", USB_MIXER_S16 },
{ 0x04, "Threshold", USB_MIXER_S16 },
{ 0x05, "Attack Time", USB_MIXER_U16 },
{ 0x06, "Release Time", USB_MIXER_U16 },
{ 0 }
};
static struct procunit_info procunits[] = {
{ 0x01, "Up Down", updown_proc_info },
{ 0x02, "Dolby Prologic", prologic_proc_info },
{ 0x03, "3D Stereo Extender", threed_enh_proc_info },
{ 0x04, "Reverb", reverb_proc_info },
{ 0x05, "Chorus", chorus_proc_info },
{ 0x06, "DCR", dcr_proc_info },
{ 0 },
};
/*
* build a processing/extension unit
*/
static int build_audio_procunit(mixer_build_t *state, int unitid, unsigned char *dsc, struct procunit_info *list, char *name)
{
int num_ins = dsc[6];
usb_mixer_elem_info_t *cval;
snd_kcontrol_t *kctl;
int i, err, nameid, type;
struct procunit_info *info;
struct procunit_value_info *valinfo;
static struct procunit_value_info default_value_info[] = {
{ 0x01, "Switch", USB_MIXER_BOOLEAN },
{ 0 }
};
static struct procunit_info default_info = {
0, NULL, default_value_info
};
if (dsc[0] < 13 || dsc[0] < 13 + num_ins || dsc[0] < num_ins + dsc[11 + num_ins]) {
snd_printk(KERN_ERR "invalid %s descriptor %d\n", name, unitid);
return -EINVAL;
}
for (i = 0; i < num_ins; i++) {
if ((err = parse_audio_unit(state, dsc[7 + i])) < 0)
return err;
}
type = combine_word(&dsc[4]);
if (! type)
return 0; /* undefined? */
for (info = list; info && info->type; info++)
if (info->type == type)
break;
if (! info || ! info->type)
info = &default_info;
for (valinfo = info->values; valinfo->control; valinfo++) {
/* FIXME: bitmap might be longer than 8bit */
if (! (dsc[12 + num_ins] & (1 << (valinfo->control - 1))))
continue;
cval = snd_magic_kcalloc(usb_mixer_elem_info_t, 0, GFP_KERNEL);
if (! cval) {
snd_printk(KERN_ERR "cannot malloc kcontrol");
return -ENOMEM;
}
cval->chip = state->chip;
cval->ctrlif = state->ctrlif;
cval->id = unitid;
cval->control = valinfo->control;
cval->val_type = valinfo->val_type;
cval->channels = 1;
/* get min/max values */
if (get_ctl_value(cval, GET_MAX, cval->control, &cval->max) < 0 ||
get_ctl_value(cval, GET_MIN, cval->control, &cval->min) < 0) {
snd_printk(KERN_ERR "cannot get min/max values for proc/ext unit\n");
snd_magic_kfree(cval);
continue;
}
kctl = snd_ctl_new1(&mixer_procunit_ctl, cval);
if (! kctl) {
snd_printk(KERN_ERR "cannot malloc kcontrol");
snd_magic_kfree(cval);
return -ENOMEM;
}
kctl->private_free = usb_mixer_elem_free;
if (info->name)
sprintf(kctl->id.name, "%s %s", info->name, valinfo->suffix);
else {
nameid = dsc[12 + num_ins + dsc[11 + num_ins]];
if (nameid) {
int len = snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name));
strcpy(kctl->id.name + len, valinfo->suffix);
} else
sprintf(kctl->id.name, "%s %s", name, valinfo->suffix);
}
snd_printdd(KERN_INFO "[%d] PU [%s] ch = %d, val = %d/%d\n",
cval->id, kctl->id.name, cval->channels, cval->min, cval->max);
if ((err = add_control_to_empty(state->chip->card, kctl)) < 0)
return err;
}
return 0;
}
static int parse_audio_processing_unit(mixer_build_t *state, int unitid, unsigned char *desc)
{
return build_audio_procunit(state, unitid, desc, procunits, "Processing Unit");
}
static int parse_audio_extension_unit(mixer_build_t *state, int unitid, unsigned char *desc)
{
return build_audio_procunit(state, unitid, desc, NULL, "Extension Unit");
}
/*
* Selector Unit
*/
/* info callback for selector unit
* use an enumerator type for routing
*/
static int mixer_ctl_selector_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
{
usb_mixer_elem_info_t *cval = snd_magic_cast(usb_mixer_elem_info_t, kcontrol->private_data, return -EINVAL);
char **itemlist = (char **)kcontrol->private_value;
snd_assert(itemlist, return -EINVAL);
uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
uinfo->count = 1;
uinfo->value.enumerated.items = cval->max;
if (uinfo->value.enumerated.item >= cval->max)
uinfo->value.enumerated.item = cval->max - 1;
strcpy(uinfo->value.enumerated.name, itemlist[uinfo->value.enumerated.item]);
return 0;
}
/* get callback for selector unit */
static int mixer_ctl_selector_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
usb_mixer_elem_info_t *cval = snd_magic_cast(usb_mixer_elem_info_t, kcontrol->private_data, return -EINVAL);
int val, err;
err = get_cur_ctl_value(cval, 0, &val);
if (err < 0)
return err;
val = get_relative_value(cval, val);
ucontrol->value.enumerated.item[0] = val;
return 0;
}
/* put callback for selector unit */
static int mixer_ctl_selector_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
{
usb_mixer_elem_info_t *cval = snd_magic_cast(usb_mixer_elem_info_t, kcontrol->private_data, return -EINVAL);
int val, oval, err;
err = get_cur_ctl_value(cval, 0, &oval);
if (err < 0)
return err;
val = ucontrol->value.enumerated.item[0];
val = get_abs_value(cval, val);
if (val != oval) {
set_cur_ctl_value(cval, 0, val);
return 1;
}
return 0;
}
/* alsa control interface for selector unit */
static snd_kcontrol_new_t mixer_selectunit_ctl = {
iface: SNDRV_CTL_ELEM_IFACE_MIXER,
name: "", /* will be filled later */
info: mixer_ctl_selector_info,
get: mixer_ctl_selector_get,
put: mixer_ctl_selector_put,
};
/* private free callback.
* free both private_data and private_value
*/
static void usb_mixer_selector_elem_free(snd_kcontrol_t *kctl)
{
int i, num_ins = 0;
if (kctl->private_data) {
usb_mixer_elem_info_t *cval = snd_magic_cast(usb_mixer_elem_info_t, kctl->private_data,);
num_ins = cval->max;
snd_magic_kfree(cval);
kctl->private_data = 0;
}
if (kctl->private_value) {
char **itemlist = (char **)kctl->private_value;
for (i = 0; i < num_ins; i++)
kfree(itemlist[i]);
kfree(itemlist);
kctl->private_value = 0;
}
}
/*
* parse a selector unit
*/
static int parse_audio_selector_unit(mixer_build_t *state, int unitid, unsigned char *desc)
{
int num_ins = desc[4];
int i, err, nameid;
usb_mixer_elem_info_t *cval;
snd_kcontrol_t *kctl;
char **namelist;
if (! num_ins || desc[0] < 6 + num_ins) {
snd_printk(KERN_ERR "invalid SELECTOR UNIT descriptor %d\n", unitid);
return -EINVAL;
}
for (i = 0; i < num_ins; i++) {
if ((err = parse_audio_unit(state, desc[5 + i])) < 0)
return err;
}
cval = snd_magic_kcalloc(usb_mixer_elem_info_t, 0, GFP_KERNEL);
if (! cval) {
snd_printk(KERN_ERR "cannot malloc kcontrol");
return -ENOMEM;
}
cval->chip = state->chip;
cval->ctrlif = state->ctrlif;
cval->id = unitid;
cval->val_type = USB_MIXER_U8;
cval->channels = 1;
cval->min = 1;
cval->max = num_ins;
namelist = kmalloc(sizeof(char *) * num_ins, GFP_KERNEL);
if (! namelist) {
snd_printk(KERN_ERR "cannot malloc\n");
snd_magic_kfree(cval);
return -ENOMEM;
}
#define MAX_ITEM_NAME_LEN 64
for (i = 0; i < num_ins; i++) {
usb_audio_term_t iterm;
int len = 0;
namelist[i] = kmalloc(MAX_ITEM_NAME_LEN, GFP_KERNEL);
if (! namelist[i]) {
snd_printk(KERN_ERR "cannot malloc\n");
while (--i > 0)
kfree(namelist[i]);
kfree(namelist);
snd_magic_kfree(cval);
return -ENOMEM;
}
if (check_input_term(state, desc[5 + i], &iterm) >= 0)
len = get_term_name(state, &iterm, namelist[i], MAX_ITEM_NAME_LEN, 0);
if (! len)
sprintf(namelist[i], "Input %d", i);
}
kctl = snd_ctl_new1(&mixer_selectunit_ctl, cval);
if (! kctl) {
snd_printk(KERN_ERR "cannot malloc kcontrol");
snd_magic_kfree(cval);
return -ENOMEM;
}
kctl->private_value = (unsigned long)namelist;
kctl->private_free = usb_mixer_selector_elem_free;
nameid = desc[desc[0] - 1];
if (nameid)
snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name));
else {
int len = get_term_name(state, &state->oterm,
kctl->id.name, sizeof(kctl->id.name), 0);
if (! len)
len = sprintf(kctl->id.name, "USB");
if ((state->oterm.type & 0xff00) == 0x0100)
strcpy(kctl->id.name + len, " Capture Source");
else
strcpy(kctl->id.name + len, " Playback Source");
}
snd_printdd(KERN_INFO "[%d] SU [%s] items = %d\n",
cval->id, kctl->id.name, num_ins);
if ((err = add_control_to_empty(state->chip->card, kctl)) < 0)
return err;
return 0;
}
/*
* parse an audio unit recursively
*/
static int parse_audio_unit(mixer_build_t *state, int unitid)
{
unsigned char *p1;
if (test_and_set_bit(unitid, &state->unitbitmap))
return 0; /* the unit already visited */
p1 = find_audio_control_unit(state, unitid);
if (!p1) {
snd_printk(KERN_ERR "usbaudio: unit %d not found!\n", unitid);
return -EINVAL;
}
switch (p1[2]) {
case INPUT_TERMINAL:
return 0; /* NOP */
case MIXER_UNIT:
return parse_audio_mixer_unit(state, unitid, p1);
case SELECTOR_UNIT:
return parse_audio_selector_unit(state, unitid, p1);
case FEATURE_UNIT:
return parse_audio_feature_unit(state, unitid, p1);
case PROCESSING_UNIT:
return parse_audio_processing_unit(state, unitid, p1);
case EXTENSION_UNIT:
return parse_audio_extension_unit(state, unitid, p1);
default:
snd_printk(KERN_ERR "usbaudio: unit %u: unexpected type 0x%02x\n", unitid, p1[2]);
return -EINVAL;
}
}
/*
* create mixer controls
*
* walk through all OUTPUT_TERMINAL descriptors to search for mixers
*/
int snd_usb_create_mixer(snd_usb_audio_t *chip, int ctrlif, unsigned char *buffer, int buflen)
{
unsigned char *desc;
mixer_build_t state;
int err;
strcpy(chip->card->mixername, "USB Mixer");
memset(&state, 0, sizeof(state));
state.chip = chip;
state.buffer = buffer;
state.buflen = buflen;
state.ctrlif = ctrlif;
desc = NULL;
while ((desc = snd_usb_find_csint_desc(buffer, buflen, desc, OUTPUT_TERMINAL, ctrlif, -1)) != NULL) {
if (desc[0] < 9)
continue; /* invalid descriptor? */
set_bit(desc[3], &state.unitbitmap); /* mark terminal ID as visited */
state.oterm.id = desc[3];
state.oterm.type = combine_word(&desc[4]);
state.oterm.name = desc[8];
err = parse_audio_unit(&state, desc[7]);
if (err < 0)
return err;
}
return 0;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment