/*
* 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 <linux/init.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/usb/audio.h>
#include <sound/control.h>
#include <sound/core.h>
#include <sound/info.h>
#include <sound/pcm.h>
#include "usbaudio.h"
#include "card.h"
#include "mixer.h"
#include "mixer_quirks.h"
#include "midi.h"
#include "quirks.h"
#include "helper.h"
#include "endpoint.h"
#include "pcm.h"
#include "clock.h"
#include "stream.h"
/*
* handle the quirks for the contained interfaces
*/
static int create_composite_quirk(struct snd_usb_audio *chip,
struct usb_interface *iface,
struct usb_driver *driver,
const struct snd_usb_audio_quirk *quirk)
{
int probed_ifnum = get_iface_desc(iface->altsetting)->bInterfaceNumber;
int err;
for (quirk = quirk->data; quirk->ifnum >= 0; ++quirk) {
iface = usb_ifnum_to_if(chip->dev, quirk->ifnum);
if (!iface)
continue;
if (quirk->ifnum != probed_ifnum &&
usb_interface_claimed(iface))
continue;
err = snd_usb_create_quirk(chip, iface, driver, quirk);
if (err < 0)
return err;
if (quirk->ifnum != probed_ifnum)
usb_driver_claim_interface(driver, iface, (void *)-1L);
}
return 0;
}
static int ignore_interface_quirk(struct snd_usb_audio *chip,
struct usb_interface *iface,
struct usb_driver *driver,
const struct snd_usb_audio_quirk *quirk)
{
return 0;
}
/*
* Allow alignment on audio sub-slot (channel samples) rather than
* on audio slots (audio frames)
*/
static int create_align_transfer_quirk(struct snd_usb_audio *chip,
struct usb_interface *iface,
struct usb_driver *driver,
const struct snd_usb_audio_quirk *quirk)
{
chip->txfr_quirk = 1;
return 1; /* Continue with creating streams and mixer */
}
static int create_any_midi_quirk(struct snd_usb_audio *chip,
struct usb_interface *intf,
struct usb_driver *driver,
const struct snd_usb_audio_quirk *quirk)
{
return snd_usbmidi_create(chip->card, intf, &chip->midi_list, quirk);
}
/*
* create a stream for an interface with proper descriptors
*/
static int create_standard_audio_quirk(struct snd_usb_audio *chip,
struct usb_interface *iface,
struct usb_driver *driver,
const struct snd_usb_audio_quirk *quirk)
{
struct usb_host_interface *alts;
struct usb_interface_descriptor *altsd;
int err;
alts = &iface->altsetting[0];
altsd = get_iface_desc(alts);
err = snd_usb_parse_audio_interface(chip, altsd->bInterfaceNumber);
if (err < 0) {
snd_printk(KERN_ERR "cannot setup if %d: error %d\n",
altsd->bInterfaceNumber, err);
return err;
}
/* reset the current interface */
usb_set_interface(chip->dev, altsd->bInterfaceNumber, 0);
return 0;
}
/*
* create a stream for an endpoint/altsetting without proper descriptors
*/
static int create_fixed_stream_quirk(struct snd_usb_audio *chip,
struct usb_interface *iface,
struct usb_driver *driver,
const struct snd_usb_audio_quirk *quirk)
{
struct audioformat *fp;
struct usb_host_interface *alts;
int stream, err;
unsigned *rate_table = NULL;
fp = kmemdup(quirk->data, sizeof(*fp), GFP_KERNEL);
if (!fp) {
snd_printk(KERN_ERR "cannot memdup\n");
return -ENOMEM;
}
if (fp->nr_rates > MAX_NR_RATES) {
kfree(fp);
return -EINVAL;
}
if (fp->nr_rates > 0) {
rate_table = kmemdup(fp->rate_table,
sizeof(int) * fp->nr_rates, GFP_KERNEL);
if (!rate_table) {
kfree(fp);
return -ENOMEM;
}
fp->rate_table = rate_table;
}
stream = (fp->endpoint & USB_DIR_IN)
? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK;
err = snd_usb_add_audio_stream(chip, stream, fp);
if (err < 0) {
kfree(fp);
kfree(rate_table);
return err;
}
if (fp->iface != get_iface_desc(&iface->altsetting[0])->bInterfaceNumber