RE: [PATCH 2/2] USB: Gadget: Add Audio Class 2.0 Driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



 

> -----Original Message-----
> From: linux-usb-owner@xxxxxxxxxxxxxxx [mailto:linux-usb-
> owner@xxxxxxxxxxxxxxx] On Behalf Of Jassi Brar
> Sent: Friday, August 19, 2011 7:22 PM
> To: linux-usb@xxxxxxxxxxxxxxx; balbi@xxxxxx; gregkh@xxxxxxx
> Cc: alsa-devel@xxxxxxxxxxxxxxxx; zonque@xxxxxxxxx; yadi.brar01@xxxxxxxxx;
> Jassi Brar
> Subject: [PATCH 2/2] USB: Gadget: Add Audio Class 2.0 Driver
> 
> This is a flexible USB Audio Class 2.0 compliant gadget driver that
> implements a simple topology with a virtual sound card exposed at
> the function side.
> 
> The driver doesn't expect any real audio codec to be present on the
> function - the audio streams are simply sinked to and sourced from a
> virtual ALSA sound card created. The user-space application may choose
> to do whatever it wants with the data received from the USB Host and
> choose to provide whatever it wants as audio data to the USB Host.
> 
> Capture(USB-Out) and Playback(USB-In) can be run at independent
> configurations specified via module parameters while loading the driver.
> 

Have you a chance to test at Macbook (10.6.5+) which has USB Audio 2.0 support well?
 
> Signed-off-by: Yadi Brar <yadi.brar01@xxxxxxxxx>
> Signed-off-by: Jassi Brar <jassisinghbrar@xxxxxxxxx>
> ---
> 
> ===========
> How To Test
> ===========
>   USB_DEV$ insmod g_audio2.ko c_srate=64000 p_srate=48000 ; say
>     gadget: high speed config #1: UAC2
> 
>   USB_DEV$ aplay -l
> **** List of PLAYBACK Hardware Devices ****
> card 0: uac2 [uac2], device 0: UAC2 PCM [UAC2 PCM]
>   Subdevices: 1/1
>   Subdevice #0: subdevice #0
> 
>   USB_DEV$ arecord -l
> **** List of CAPTURE Hardware Devices ****
> card 0: uac2 [uac2], device 0: UAC2 PCM [UAC2 PCM]
>   Subdevices: 1/1
>   Subdevice #0: subdevice #0
> 
>   USB_HOST$ aplay -l
> **** List of PLAYBACK Hardware Devices ****
> card 0: PCH [HDA Intel PCH], device 0: ALC665 Analog [ALC665 Analog]
>   Subdevices: 0/1
>   Subdevice #0: subdevice #0
> card 1: Gadget [UAC2 Gadget], device 0: USB Audio [USB Audio]  <<<<<<
>   Subdevices: 1/1
>   Subdevice #0: subdevice #0
> 
>   USB_HOST$ arecord -l
> **** List of CAPTURE Hardware Devices ****
> card 0: PCH [HDA Intel PCH], device 0: ALC665 Analog [ALC665 Analog]
>   Subdevices: 1/1
>   Subdevice #0: subdevice #0
> card 1: Gadget [UAC2 Gadget], device 0: USB Audio [USB Audio] <<<<<<<
>   Subdevices: 1/1
>   Subdevice #0: subdevice #0
> 
>  After this confirmation, you should be able to use it just like another
> sound card in your system. That is, a new sound card should appear in the
> sound preferences which you can select to route your default audio to and
> from.
> 
> ============
> Test Results
> ============
>  The driver has been tested for Half as well as Full Duplex at
> Stereo, S16_LE at 44.1KHz, 48KHz and 64KHz. It should also
> work for other configurations as well, except for different
> sample-size(TODO).
> 
> Obviously, much depends upon the underlying UDC driver.
> 
> While testing with OMAP's implementation of Inventra HDRC, I observed
> some noise for configurations for which the USB-OUT packet size chosen
> by the Host is not a power of 2. USB-IN works just fine.
> For ex, on both Beagle/Panda board, 48KHz USB-OUT has some noise while
> 64KHz is smooth. Also, with full duplex, it occasioanally shows noise.
> 
> Taking benefit of doubt, I assume this driver has unmasked some subtle
> bug in the underlying UDC's ISOCH handling code ;)
> Please feel free to find issue with this driver by testing it over
> some other UDC.
> 
>  drivers/usb/gadget/Kconfig    |   20 +
>  drivers/usb/gadget/Makefile   |    2 +
>  drivers/usb/gadget/audio2.c   |  155 +++++
>  drivers/usb/gadget/f_audio2.c | 1448
> +++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 1625 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/usb/gadget/audio2.c
>  create mode 100644 drivers/usb/gadget/f_audio2.c
> 
> diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig
> index e35dfef..95c65d8 100644
> --- a/drivers/usb/gadget/Kconfig
> +++ b/drivers/usb/gadget/Kconfig
> @@ -599,6 +599,26 @@ config USB_ZERO_HNPTEST
>  	  the "B-Peripheral" role, that device will use HNP to let this
>  	  one serve as the USB host instead (in the "B-Host" role).
> 
> +config USB_AUDIO_2
> +	tristate "Audio Gadget Class 2.0 (EXPERIMENTAL)"
> +	depends on SND && EXPERIMENTAL
> +	select SND_PCM
> +	help
> +	  This Gadget Audio driver is compatible with USB Audio Class
> +	  specification 2.0. It implements 1 AudioControl interface,
> +	  1 AudioStreaming Interface each for USB-OUT and USB-IN.
> +	  Number of channels, sample rate and sample size can be
> +	  specified as module parameters.
> +	  This driver doesn't expect any real Audio codec to be present
> +	  on the device - the audio streams are simply sinked to and
> +	  sourced from a virtual ALSA sound card created. The user-space
> +	  application may choose to do whatever it wants with the data
> +	  received from the USB Host and choose to provide whatever it
> +	  wants as audio data to the USB Host.
> +
> +	  Say "y" to link the driver statically, or "m" to build a
> +	  dynamically linked module called "g_audio2".
> +
>  config USB_AUDIO
>  	tristate "Audio Gadget (EXPERIMENTAL)"
>  	depends on SND
> diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
> index 7409338..fa8472e 100644
> --- a/drivers/usb/gadget/Makefile
> +++ b/drivers/usb/gadget/Makefile
> @@ -40,6 +40,7 @@ obj-$(CONFIG_USB_COMPOSITE)	+= usb-composite.o
>  #
>  g_zero-y			:= zero.o
>  g_audio-y			:= audio.o
> +g_audio2-y			:= audio2.o
>  g_ether-y			:= ether.o
>  g_serial-y			:= serial.o
>  g_midi-y			:= gmidi.o
> @@ -57,6 +58,7 @@ g_ncm-y				:= ncm.o
> 
>  obj-$(CONFIG_USB_ZERO)		+= g_zero.o
>  obj-$(CONFIG_USB_AUDIO)		+= g_audio.o
> +obj-$(CONFIG_USB_AUDIO_2)	+= g_audio2.o
>  obj-$(CONFIG_USB_ETH)		+= g_ether.o
>  obj-$(CONFIG_USB_GADGETFS)	+= gadgetfs.o
>  obj-$(CONFIG_USB_FUNCTIONFS)	+= g_ffs.o
> diff --git a/drivers/usb/gadget/audio2.c b/drivers/usb/gadget/audio2.c
> new file mode 100644
> index 0000000..92b6d64
> --- /dev/null
> +++ b/drivers/usb/gadget/audio2.c
> @@ -0,0 +1,155 @@
> +/*
> + * audio2.c -- USB Audio Class 2.0 gadget driver
> + *
> + * Copyright (C) 2011
> + *    Yadwinder Singh (yadi.brar01@xxxxxxxxx)
> + *    Jaswinder Singh (jassisinghbrar@xxxxxxxxx)
> + *
> + * 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.
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/utsname.h>
> +#include <linux/platform_device.h>
> +#include <linux/usb/composite.h>
> +
> +/*
> + * Kbuild is not very cooperative with respect to linking separately
> + * compiled library objects into one module.  So for now we won't use
> + * separate compilation ... ensuring init/exit sections work to shrink
> + * the runtime footprint, and giving us at least some parts of what
> + * a "gcc --combine ... part1.c part2.c part3.c ... " build would.
> + */
> +#include "f_audio2.c"
> +
> +enum {
> +	STR_MANUFACTURER = 0,
> +	STR_PRODUCT,
> +	STR_CONFIG,
> +};
> +
> +/* Thanks to Linux Foundation for donating this product ID. */
> +#define UAC2_VENDOR	0x1d6b	/* Linux Foundation */
> +#define UAC2_PRODUCT	0x0102	/* Linux-USB Audio Gadget */
> +
> +static const char manufacturer[] = "Linux Foundation";
> +static const char product_desc[] = "UAC2 Gadget";
> +static const char config[] = "Simple Source/Sink";
> +
> +static struct usb_string strings_cdev[] = {
> +	[STR_MANUFACTURER].s = manufacturer,
> +	[STR_PRODUCT].s = product_desc,
> +	[STR_CONFIG].s = config,
> +	{},
> +};
> +
> +static struct usb_gadget_strings stringtab = {
> +	.language = 0x0409,	/* en-us */
> +	.strings = strings_cdev,
> +};
> +
> +static struct usb_gadget_strings *cdev_strings[] = {
> +	&stringtab,
> +	NULL,
> +};
> +
> +static struct usb_device_descriptor device_desc = {
> +	.bLength = sizeof device_desc,
> +	.bDescriptorType = USB_DT_DEVICE,
> +
> +	.bcdUSB = cpu_to_le16(0x200),
> +	.bDeviceClass = USB_CLASS_MISC,
> +	.bDeviceSubClass = 0x02,
> +	.bDeviceProtocol = 0x01,
> +	.idVendor = cpu_to_le16(UAC2_VENDOR),
> +	.idProduct = cpu_to_le16(UAC2_PRODUCT),
> +	/*
> +	 * Implemented features of USB Audio Device class solely
> +	 * depend on this driver, so this is where we decide the
> +	 * version of the device.
> +	 * Currently, the lowest version of simplest driver - 00.01
> +	 */
> +	.bcdDevice = cpu_to_le16(0x0001),
> +	.bNumConfigurations = 1,
> +};
> +
> +static struct usb_otg_descriptor otg_desc = {
> +	.bLength = sizeof otg_desc,
> +	.bDescriptorType = USB_DT_OTG,
> +
> +	.bmAttributes = USB_OTG_SRP,
> +};
> +
> +const struct usb_descriptor_header *otgd[] = {
> +	(struct usb_descriptor_header *) &otg_desc,
> +	NULL,
> +};
> +
> +static struct usb_configuration audio_config = {
> +	.label = "UAC2",
> +	.unbind = uac2_unbind_config,
> +	.bConfigurationValue = 1,
> +	.bmAttributes = USB_CONFIG_ATT_SELFPOWER,
> +};
> +
> +static int __init
> +audio_bind(struct usb_composite_dev *cdev)
> +{
> +	int  id;
> +
> +	/* Allocate string descriptor numbers */
> +	id = usb_string_id(cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_cdev[STR_MANUFACTURER].id = id;
> +	device_desc.iManufacturer = id;
> +
> +	id = usb_string_id(cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_cdev[STR_PRODUCT].id = id;
> +	device_desc.iProduct = id;
> +
> +	id = usb_string_id(cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_cdev[STR_CONFIG].id = id;
> +	audio_config.iConfiguration = id;
> +
> +	if (gadget_is_otg(cdev->gadget)) {
> +		audio_config.descriptors = otgd;
> +		otg_desc.bmAttributes |= USB_OTG_HNP;
> +		audio_config.bmAttributes |= USB_CONFIG_ATT_WAKEUP;
> +	}
> +
> +	return usb_add_config(cdev, &audio_config, uac2_bind_config);
> +}
> +
> +static struct usb_composite_driver audio_driver = {
> +	.name = "USB Audio Class 2.0",
> +	.dev = &device_desc,
> +	.strings = cdev_strings,
> +	.max_speed = USB_SPEED_HIGH,
> +};
> +
> +static int __init init(void)
> +{
> +	return usb_composite_probe(&audio_driver, audio_bind);
> +}
> +module_init(init);
> +
> +static void __exit cleanup(void)
> +{
> +	usb_composite_unregister(&audio_driver);
> +}
> +module_exit(cleanup);
> +
> +MODULE_DESCRIPTION("USB Audio Class 2.0 Gadget Driver");
> +MODULE_AUTHOR("Yadi Brar, Jassi Brar");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/usb/gadget/f_audio2.c
> b/drivers/usb/gadget/f_audio2.c
> new file mode 100644
> index 0000000..261d587
> --- /dev/null
> +++ b/drivers/usb/gadget/f_audio2.c
> @@ -0,0 +1,1448 @@
> +/*
> + * f_audio2.c -- USB Audio Class 2.0 Function
> + *
> + * Copyright (C) 2011
> + *    Yadwinder Singh (yadi.brar01@xxxxxxxxx)
> + *    Jaswinder Singh (jassisinghbrar@xxxxxxxxx)
> + *
> + * 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.
> + */
> +
> +#include <linux/usb/audio.h>
> +#include <linux/usb/audio-v2.h>
> +
> +#include <sound/core.h>
> +#include <sound/pcm.h>
> +#include <sound/pcm_params.h>
> +
> +/* Playback(USB-IN) Default Stereo - Fl/Fr */
> +static int p_chmask = 0x3;
> +module_param(p_chmask, uint, S_IRUGO);
> +MODULE_PARM_DESC(p_chmask, "Playback Channel Mask");
> +
> +/* Playback Default 48 KHz */
> +static int p_srate = 48000;
> +module_param(p_srate, uint, S_IRUGO);
> +MODULE_PARM_DESC(p_srate, "Playback Sampling Rate");
> +
> +/* Playback Default 16bits/sample */
> +static int p_ssize = 2;
> +module_param(p_ssize, uint, S_IRUGO);
> +MODULE_PARM_DESC(p_ssize, "Playback Sample Size(bytes)");
> +
> +/* Capture(USB-OUT) Default Stereo - Fl/Fr */
> +static int c_chmask = 0x3;
> +module_param(c_chmask, uint, S_IRUGO);
> +MODULE_PARM_DESC(c_chmask, "Capture Channel Mask");
> +
> +/* Capture Default 48 KHz */
> +static int c_srate = 48000;
> +module_param(c_srate, uint, S_IRUGO);
> +MODULE_PARM_DESC(c_srate, "Capture Sampling Rate");
> +
> +/* Capture Default 16bits/sample */
> +static int c_ssize = 2;
> +module_param(c_ssize, uint, S_IRUGO);
> +MODULE_PARM_DESC(c_ssize, "Capture Sample Size(bytes)");
> +
> +#define DMA_ADDR_INVALID	(~(dma_addr_t)0)
> +
> +#define ALT_SET(x, a)	do {(x) &= ~0xff; (x) |= (a); } while (0)
> +#define ALT_GET(x)	((x) & 0xff)
> +#define INTF_SET(x, i)	do {(x) &= 0xff; (x) |= ((i) << 8); } while (0)
> +#define INTF_GET(x)	((x >> 8) & 0xff)
> +
> +/* Keep everyone on toes */
> +#define USB_XFERS	2
> +
> +/*
> + * The driver implements a simple UAC_2 topology.
> + * USB-OUT -> IT_1 -> OT_3 -> ALSA_Capture
> + * ALSA_Playback -> IT_2 -> OT_4 -> USB-IN
> + * Capture and Playback sampling rates are independently
> + *  controlled by two clock sources :
> + *    CLK_5 := c_srate, and CLK_6 := p_srate
> + */
> +#define USB_OUT_IT_ID	1
> +#define IO_IN_IT_ID	2
> +#define IO_OUT_OT_ID	3
> +#define USB_IN_OT_ID	4
> +#define USB_OUT_CLK_ID	5
> +#define USB_IN_CLK_ID	6
> +
> +#define CONTROL_ABSENT	0
> +#define CONTROL_RDONLY	1
> +#define CONTROL_RDWR	3
> +
> +#define CLK_FREQ_CTRL	0
> +#define CLK_VLD_CTRL	2
> +
> +#define COPY_CTRL	0
> +#define CONN_CTRL	2
> +#define OVRLD_CTRL	4
> +#define CLSTR_CTRL	6
> +#define UNFLW_CTRL	8
> +#define OVFLW_CTRL	10
> +
> +const char *uac2_name = "snd_uac2";
> +
> +struct uac2_req {
> +	struct uac2_rtd_params *pp; /* parent param */
> +	struct usb_request *req;
> +};
> +
> +struct uac2_rtd_params {
> +	bool ep_enabled; /* if the ep is enabled */
> +	/* Size of the ring buffer */
> +	size_t dma_bytes;
> +	unsigned char *dma_area;
> +
> +	struct snd_pcm_substream *ss;
> +
> +	/* Ring buffer */
> +	ssize_t hw_ptr;
> +
> +	void *rbuf;
> +
> +	size_t period_size;
> +
> +	unsigned max_psize;
> +	struct uac2_req ureq[USB_XFERS];
> +
> +	spinlock_t lock;
> +};
> +
> +struct snd_uac2_chip {
> +	struct platform_device pdev;
> +	struct platform_driver pdrv;
> +
> +	struct uac2_rtd_params p_prm;
> +	struct uac2_rtd_params c_prm;
> +
> +	struct snd_card *card;
> +	struct snd_pcm *pcm;
> +};
> +
> +#define BUFF_SIZE_MAX	(PAGE_SIZE * 16)
> +#define PRD_SIZE_MAX	PAGE_SIZE
> +#define MIN_PERIODS	4
> +
> +static struct snd_pcm_hardware uac2_pcm_hardware = {
> +	.info = SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER
> +		 | SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID
> +		 | SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
> +	.rates = SNDRV_PCM_RATE_CONTINUOUS,
> +	.periods_max = BUFF_SIZE_MAX / PRD_SIZE_MAX,
> +	.buffer_bytes_max = BUFF_SIZE_MAX,
> +	.period_bytes_max = PRD_SIZE_MAX,
> +	.periods_min = MIN_PERIODS,
> +};
> +
> +struct audio_dev {
> +	/* Currently active {Interface[15:8] | AltSettings[7:0]} */
> +	__u16 ac_alt, as_out_alt, as_in_alt;
> +
> +	struct usb_ep *in_ep, *out_ep;
> +	struct usb_function func;
> +
> +	/* The ALSA Sound Card it represents on the USB-Client side */
> +	struct snd_uac2_chip uac2;
> +};
> +
> +static struct audio_dev *agdev_g;
> +
> +static inline
> +struct audio_dev *func_to_agdev(struct usb_function *f)
> +{
> +	return container_of(f, struct audio_dev, func);
> +}
> +
> +static inline
> +struct audio_dev *uac2_to_agdev(struct snd_uac2_chip *u)
> +{
> +	return container_of(u, struct audio_dev, uac2);
> +}
> +
> +static inline
> +struct snd_uac2_chip *pdev_to_uac2(struct platform_device *p)
> +{
> +	return container_of(p, struct snd_uac2_chip, pdev);
> +}
> +
> +static inline
> +struct snd_uac2_chip *prm_to_uac2(struct uac2_rtd_params *r)
> +{
> +	struct snd_uac2_chip *uac2 = container_of(r,
> +					struct snd_uac2_chip, c_prm);
> +
> +	if (&uac2->c_prm != r)
> +		uac2 = container_of(r, struct snd_uac2_chip, p_prm);
> +
> +	return uac2;
> +}
> +
> +static inline
> +uint num_channels(uint chanmask)
> +{
> +	uint num = 0;
> +
> +	while (chanmask) {
> +		num += (chanmask & 1);
> +		chanmask >>= 1;
> +	}
> +
> +	return num;
> +}
> +
> +static void
> +agdev_iso_complete(struct usb_ep *ep, struct usb_request *req)
> +{
> +	unsigned pending;
> +	unsigned long flags;
> +	bool update_alsa = false;
> +	unsigned char *src, *dst;
> +	int status = req->status;
> +	struct uac2_req *ur = req->context;
> +	struct snd_pcm_substream *substream;
> +	struct uac2_rtd_params *prm = ur->pp;
> +	struct snd_uac2_chip *uac2 = prm_to_uac2(prm);
> +
> +	/* i/f shutting down */
> +	if (!prm->ep_enabled)
> +		return;
> +
> +	/*
> +	 * We can't really do much about bad xfers.
> +	 * Afterall, the ISOCH xfers could fail legitimately.
> +	 */
> +	if (status)
> +		pr_debug("%s: iso_complete status(%d) %d/%d\n",
> +			__func__, status, req->actual, req->length);
> +
> +	substream = prm->ss;
> +
> +	/* Do nothing if ALSA isn't active */
> +	if (!substream)
> +		goto exit;
> +
> +	spin_lock_irqsave(&prm->lock, flags);
> +
> +	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
> +		src = prm->dma_area + prm->hw_ptr;
> +		req->actual = req->length;
> +		dst = req->buf;
> +	} else {
> +		dst = prm->dma_area + prm->hw_ptr;
> +		src = req->buf;
> +	}
> +
> +	pending = prm->hw_ptr % prm->period_size;
> +	pending += req->actual;
> +	if (pending >= prm->period_size)
> +		update_alsa = true;
> +
> +	prm->hw_ptr = (prm->hw_ptr + req->actual) % prm->dma_bytes;
> +
> +	spin_unlock_irqrestore(&prm->lock, flags);
> +
> +	/* Pack USB load in ALSA ring buffer */
> +	memcpy(dst, src, req->actual);
> +exit:
> +	if (usb_ep_queue(ep, req, GFP_ATOMIC))
> +		dev_err(&uac2->pdev.dev, "%d Error!\n", __LINE__);
> +
> +	if (update_alsa)
> +		snd_pcm_period_elapsed(substream);
> +
> +	return;
> +}
> +
> +static int
> +uac2_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
> +{
> +	struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
> +	struct audio_dev *agdev = uac2_to_agdev(uac2);
> +	struct uac2_rtd_params *prm;
> +	unsigned long flags;
> +	struct usb_ep *ep;
> +	int err = 0;
> +
> +	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
> +		ep = agdev->in_ep;
> +		prm = &uac2->p_prm;
> +	} else {
> +		ep = agdev->out_ep;
> +		prm = &uac2->c_prm;
> +	}
> +
> +	spin_lock_irqsave(&prm->lock, flags);
> +
> +	/* Reset */
> +	prm->hw_ptr = 0;
> +
> +	switch (cmd) {
> +	case SNDRV_PCM_TRIGGER_START:
> +	case SNDRV_PCM_TRIGGER_RESUME:
> +		prm->ss = substream;
> +		break;
> +	case SNDRV_PCM_TRIGGER_STOP:
> +	case SNDRV_PCM_TRIGGER_SUSPEND:
> +		prm->ss = NULL;
> +		break;
> +	default:
> +		err = -EINVAL;
> +	}
> +
> +	spin_unlock_irqrestore(&prm->lock, flags);
> +
> +	/* Clear buffer after Play stops */
> +	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK && !prm->ss)
> +		memset(prm->rbuf, 0, prm->max_psize * USB_XFERS);
> +
> +	return err;
> +}
> +
> +static snd_pcm_uframes_t uac2_pcm_pointer(struct snd_pcm_substream
> *substream)
> +{
> +	struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
> +	struct uac2_rtd_params *prm;
> +
> +	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
> +		prm = &uac2->p_prm;
> +	else
> +		prm = &uac2->c_prm;
> +
> +	return bytes_to_frames(substream->runtime, prm->hw_ptr);
> +}
> +
> +static int uac2_pcm_hw_params(struct snd_pcm_substream *substream,
> +			       struct snd_pcm_hw_params *hw_params)
> +{
> +	struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
> +	struct uac2_rtd_params *prm;
> +	int err;
> +
> +	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
> +		prm = &uac2->p_prm;
> +	else
> +		prm = &uac2->c_prm;
> +
> +	err = snd_pcm_lib_malloc_pages(substream,
> +					params_buffer_bytes(hw_params));
> +	if (err >= 0) {
> +		prm->dma_bytes = substream->runtime->dma_bytes;
> +		prm->dma_area = substream->runtime->dma_area;
> +		prm->period_size = params_period_bytes(hw_params);
> +	}
> +
> +	return err;
> +}
> +
> +static int uac2_pcm_hw_free(struct snd_pcm_substream *substream)
> +{
> +	struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
> +	struct uac2_rtd_params *prm;
> +
> +	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
> +		prm = &uac2->p_prm;
> +	else
> +		prm = &uac2->c_prm;
> +
> +	prm->dma_area = NULL;
> +	prm->dma_bytes = 0;
> +	prm->period_size = 0;
> +
> +	return snd_pcm_lib_free_pages(substream);
> +}
> +
> +static int uac2_pcm_open(struct snd_pcm_substream *substream)
> +{
> +	struct snd_uac2_chip *uac2 = snd_pcm_substream_chip(substream);
> +	struct snd_pcm_runtime *runtime = substream->runtime;
> +
> +	runtime->hw = uac2_pcm_hardware;
> +
> +	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
> +		spin_lock_init(&uac2->p_prm.lock);
> +		runtime->hw.rate_min = p_srate;
> +		runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; /* ! p_ssize !
> */
> +		runtime->hw.channels_min = num_channels(p_chmask);
> +		runtime->hw.period_bytes_min = 2 * uac2->p_prm.max_psize
> +						/ runtime->hw.periods_min;
> +	} else {
> +		spin_lock_init(&uac2->c_prm.lock);
> +		runtime->hw.rate_min = c_srate;
> +		runtime->hw.formats = SNDRV_PCM_FMTBIT_S16_LE; /* ! c_ssize !
> */
> +		runtime->hw.channels_min = num_channels(c_chmask);
> +		runtime->hw.period_bytes_min = 2 * uac2->c_prm.max_psize
> +						/ runtime->hw.periods_min;
> +	}
> +
> +	runtime->hw.rate_max = runtime->hw.rate_min;
> +	runtime->hw.channels_max = runtime->hw.channels_min;
> +
> +	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
> +
> +	return 0;
> +}
> +
> +/* ALSA cries without these function pointers */
> +static int uac2_pcm_null(struct snd_pcm_substream *substream)
> +{
> +	return 0;
> +}
> +
> +static struct snd_pcm_ops uac2_pcm_ops = {
> +	.open = uac2_pcm_open,
> +	.close = uac2_pcm_null,
> +	.ioctl = snd_pcm_lib_ioctl,
> +	.hw_params = uac2_pcm_hw_params,
> +	.hw_free = uac2_pcm_hw_free,
> +	.trigger = uac2_pcm_trigger,
> +	.pointer = uac2_pcm_pointer,
> +	.prepare = uac2_pcm_null,
> +};
> +
> +static int __devinit snd_uac2_probe(struct platform_device *pdev)
> +{
> +	struct snd_uac2_chip *uac2 = pdev_to_uac2(pdev);
> +	struct snd_card *card;
> +	struct snd_pcm *pcm;
> +	int err;
> +
> +	/* Choose any slot, with no id */
> +	err = snd_card_create(-1, NULL, THIS_MODULE, 0, &card);
> +	if (err < 0)
> +		return err;
> +
> +	uac2->card = card;
> +
> +	/*
> +	 * Create first PCM device
> +	 * Create a substream only for non-zero channel streams
> +	 */
> +	err = snd_pcm_new(uac2->card, "UAC2 PCM", 0,
> +			       p_chmask ? 1 : 0, c_chmask ? 1 : 0, &pcm);
> +	if (err < 0)
> +		goto snd_fail;
> +
> +	strcpy(pcm->name, "UAC2 PCM");
> +	pcm->private_data = uac2;
> +
> +	uac2->pcm = pcm;
> +
> +	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &uac2_pcm_ops);
> +	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &uac2_pcm_ops);
> +
> +	strcpy(card->driver, "uac2");
> +	strcpy(card->shortname, "uac2");
> +	sprintf(card->longname, "uac2 %i", pdev->id);
> +
> +	snd_card_set_dev(card, &pdev->dev);
> +
> +	snd_pcm_lib_preallocate_pages_for_all(pcm,
> SNDRV_DMA_TYPE_CONTINUOUS,
> +		snd_dma_continuous_data(GFP_KERNEL), 0, BUFF_SIZE_MAX);
> +
> +	err = snd_card_register(card);
> +	if (!err) {
> +		platform_set_drvdata(pdev, card);
> +		return 0;
> +	}
> +
> +snd_fail:
> +	snd_card_free(card);
> +
> +	uac2->pcm = NULL;
> +	uac2->card = NULL;
> +
> +	return err;
> +}
> +
> +static int __devexit snd_uac2_remove(struct platform_device *pdev)
> +{
> +	struct snd_card *card = platform_get_drvdata(pdev);
> +
> +	platform_set_drvdata(pdev, NULL);
> +
> +	if (card)
> +		return snd_card_free(card);
> +
> +	return 0;
> +}
> +
> +static int alsa_uac2_init(struct audio_dev *agdev)
> +{
> +	struct snd_uac2_chip *uac2 = &agdev->uac2;
> +	int err;
> +
> +	uac2->pdrv.probe = snd_uac2_probe;
> +	uac2->pdrv.remove = snd_uac2_remove;
> +	uac2->pdrv.driver.name = uac2_name;
> +
> +	uac2->pdev.id = 0;
> +	uac2->pdev.name = uac2_name;
> +
> +	/* Register snd_uac2 driver */
> +	err = platform_driver_register(&uac2->pdrv);
> +	if (err)
> +		return err;
> +
> +	/* Register snd_uac2 device */
> +	err = platform_device_register(&uac2->pdev);
> +	if (err)
> +		platform_driver_unregister(&uac2->pdrv);
> +
> +	return err;
> +}
> +
> +static void alsa_uac2_exit(struct audio_dev *agdev)
> +{
> +	struct snd_uac2_chip *uac2 = &agdev->uac2;
> +
> +	platform_driver_unregister(&uac2->pdrv);
> +	platform_device_unregister(&uac2->pdev);
> +}
> +
> +
> +/* --------- USB Function Interface ------------- */
> +
> +enum {
> +	STR_ASSOC,
> +	STR_IF_CTRL,
> +	STR_CLKSRC_IN,
> +	STR_CLKSRC_OUT,
> +	STR_USB_IT,
> +	STR_IO_IT,
> +	STR_USB_OT,
> +	STR_IO_OT,
> +	STR_AS_OUT_ALT0,
> +	STR_AS_OUT_ALT1,
> +	STR_AS_IN_ALT0,
> +	STR_AS_IN_ALT1,
> +};
> +
> +static const char ifassoc[] = "Source/Sink";
> +static const char ifctrl[] = "Topology Control";
> +static char clksrc_in[8];
> +static char clksrc_out[8];
> +static const char usb_it[] = "USBH Out";
> +static const char io_it[] = "USBD Out";
> +static const char usb_ot[] = "USBH In";
> +static const char io_ot[] = "USBD In";
> +static const char out_alt0[] = "Playback Inactive";
> +static const char out_alt1[] = "Playback Active";
> +static const char in_alt0[] = "Capture Inactive";
> +static const char in_alt1[] = "Capture Active";
> +
> +static struct usb_string strings_fn[] = {
> +	[STR_ASSOC].s = ifassoc,
> +	[STR_IF_CTRL].s = ifctrl,
> +	[STR_CLKSRC_IN].s = clksrc_in,
> +	[STR_CLKSRC_OUT].s = clksrc_out,
> +	[STR_USB_IT].s = usb_it,
> +	[STR_IO_IT].s = io_it,
> +	[STR_USB_OT].s = usb_ot,
> +	[STR_IO_OT].s = io_ot,
> +	[STR_AS_OUT_ALT0].s = out_alt0,
> +	[STR_AS_OUT_ALT1].s = out_alt1,
> +	[STR_AS_IN_ALT0].s = in_alt0,
> +	[STR_AS_IN_ALT1].s = in_alt1,
> +	{ },
> +};
> +
> +static struct usb_gadget_strings str_fn = {
> +	.language = 0x0409,	/* en-us */
> +	.strings = strings_fn,
> +};
> +
> +static struct usb_gadget_strings *fn_strings[] = {
> +	&str_fn,
> +	NULL,
> +};
> +
> +static struct usb_qualifier_descriptor devqual_desc = {
> +	.bLength = sizeof devqual_desc,
> +	.bDescriptorType = USB_DT_DEVICE_QUALIFIER,
> +
> +	.bcdUSB = cpu_to_le16(0x200),
> +	.bDeviceClass = USB_CLASS_MISC,
> +	.bDeviceSubClass = 0x02,
> +	.bDeviceProtocol = 0x01,
> +	.bNumConfigurations = 1,
> +	.bRESERVED = 0,
> +};
> +
> +static struct usb_interface_assoc_descriptor iad_desc = {
> +	.bLength = sizeof iad_desc,
> +	.bDescriptorType = USB_DT_INTERFACE_ASSOCIATION,
> +
> +	.bFirstInterface = 0,
> +	.bInterfaceCount = 3,
> +	.bFunctionClass = USB_CLASS_AUDIO,
> +	.bFunctionSubClass = UAC2_FUNCTION_SUBCLASS_UNDEFINED,
> +	.bFunctionProtocol = UAC_VERSION_2,
> +};
> +
> +/* Audio Control Interface */
> +static struct usb_interface_descriptor std_ac_if_desc = {
> +	.bLength = sizeof std_ac_if_desc,
> +	.bDescriptorType = USB_DT_INTERFACE,
> +
> +	.bAlternateSetting = 0,
> +	.bNumEndpoints = 0,
> +	.bInterfaceClass = USB_CLASS_AUDIO,
> +	.bInterfaceSubClass = USB_SUBCLASS_AUDIOCONTROL,
> +	.bInterfaceProtocol = UAC_VERSION_2,
> +};
> +
> +/* Clock source for IN traffic */
> +struct uac_clock_source_descriptor in_clk_src_desc = {
> +	.bLength = sizeof in_clk_src_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +
> +	.bDescriptorSubtype = UAC2_CLOCK_SOURCE,
> +	.bClockID = USB_IN_CLK_ID,
> +	.bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED,
> +	.bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL),
> +	.bAssocTerminal = 0,
> +};
> +
> +/* Clock source for OUT traffic */
> +struct uac_clock_source_descriptor out_clk_src_desc = {
> +	.bLength = sizeof out_clk_src_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +
> +	.bDescriptorSubtype = UAC2_CLOCK_SOURCE,
> +	.bClockID = USB_OUT_CLK_ID,
> +	.bmAttributes = UAC_CLOCK_SOURCE_TYPE_INT_FIXED,
> +	.bmControls = (CONTROL_RDONLY << CLK_FREQ_CTRL),
> +	.bAssocTerminal = 0,
> +};
> +
> +/* Input Terminal for USB_OUT */
> +struct uac2_input_terminal_descriptor usb_out_it_desc = {
> +	.bLength = sizeof usb_out_it_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +
> +	.bDescriptorSubtype = UAC_INPUT_TERMINAL,
> +	.bTerminalID = USB_OUT_IT_ID,
> +	.wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING),
> +	.bAssocTerminal = 0,
> +	.bCSourceID = USB_OUT_CLK_ID,
> +	.iChannelNames = 0,
> +	.bmControls = (CONTROL_RDWR << COPY_CTRL),
> +};
> +
> +/* Input Terminal for I/O-In */
> +struct uac2_input_terminal_descriptor io_in_it_desc = {
> +	.bLength = sizeof io_in_it_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +
> +	.bDescriptorSubtype = UAC_INPUT_TERMINAL,
> +	.bTerminalID = IO_IN_IT_ID,
> +	.wTerminalType = cpu_to_le16(UAC_INPUT_TERMINAL_UNDEFINED),
> +	.bAssocTerminal = 0,
> +	.bCSourceID = USB_IN_CLK_ID,
> +	.iChannelNames = 0,
> +	.bmControls = (CONTROL_RDWR << COPY_CTRL),
> +};
> +
> +/* Ouput Terminal for USB_IN */
> +struct uac2_output_terminal_descriptor usb_in_ot_desc = {
> +	.bLength = sizeof usb_in_ot_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +
> +	.bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
> +	.bTerminalID = USB_IN_OT_ID,
> +	.wTerminalType = cpu_to_le16(UAC_TERMINAL_STREAMING),
> +	.bAssocTerminal = 0,
> +	.bSourceID = IO_IN_IT_ID,
> +	.bCSourceID = USB_IN_CLK_ID,
> +	.bmControls = (CONTROL_RDWR << COPY_CTRL),
> +};
> +
> +/* Ouput Terminal for I/O-Out */
> +struct uac2_output_terminal_descriptor io_out_ot_desc = {
> +	.bLength = sizeof io_out_ot_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +
> +	.bDescriptorSubtype = UAC_OUTPUT_TERMINAL,
> +	.bTerminalID = IO_OUT_OT_ID,
> +	.wTerminalType = cpu_to_le16(UAC_OUTPUT_TERMINAL_UNDEFINED),
> +	.bAssocTerminal = 0,
> +	.bSourceID = USB_OUT_IT_ID,
> +	.bCSourceID = USB_OUT_CLK_ID,
> +	.bmControls = (CONTROL_RDWR << COPY_CTRL),
> +};
> +
> +struct uac2_ac_header_descriptor ac_hdr_desc = {
> +	.bLength = sizeof ac_hdr_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +
> +	.bDescriptorSubtype = UAC_MS_HEADER,
> +	.bcdADC = cpu_to_le16(0x200),
> +	.bCategory = UAC2_FUNCTION_IO_BOX,
> +	.wTotalLength = sizeof in_clk_src_desc + sizeof out_clk_src_desc
> +			 + sizeof usb_out_it_desc + sizeof io_in_it_desc
> +			+ sizeof usb_in_ot_desc + sizeof io_out_ot_desc,
> +	.bmControls = 0,
> +};
> +
> +/* Audio Streaming OUT Interface - Alt0 */
> +static struct usb_interface_descriptor std_as_out_if0_desc = {
> +	.bLength = sizeof std_as_out_if0_desc,
> +	.bDescriptorType = USB_DT_INTERFACE,
> +
> +	.bAlternateSetting = 0,
> +	.bNumEndpoints = 0,
> +	.bInterfaceClass = USB_CLASS_AUDIO,
> +	.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
> +	.bInterfaceProtocol = UAC_VERSION_2,
> +};
> +
> +/* Audio Streaming OUT Interface - Alt1 */
> +static struct usb_interface_descriptor std_as_out_if1_desc = {
> +	.bLength = sizeof std_as_out_if1_desc,
> +	.bDescriptorType = USB_DT_INTERFACE,
> +
> +	.bAlternateSetting = 1,
> +	.bNumEndpoints = 1,
> +	.bInterfaceClass = USB_CLASS_AUDIO,
> +	.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
> +	.bInterfaceProtocol = UAC_VERSION_2,
> +};
> +
> +/* Audio Stream OUT Intface Desc */
> +struct uac2_as_header_descriptor as_out_hdr_desc = {
> +	.bLength = sizeof as_out_hdr_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +
> +	.bDescriptorSubtype = UAC_AS_GENERAL,
> +	.bTerminalLink = USB_OUT_IT_ID,
> +	.bmControls = 0,
> +	.bFormatType = UAC_FORMAT_TYPE_I,
> +	.bmFormats = cpu_to_le32(UAC_FORMAT_TYPE_I_PCM),
> +	.iChannelNames = 0,
> +};
> +
> +/* Audio USB_OUT Format */
> +struct uac2_format_type_i_descriptor as_out_fmt1_desc = {
> +	.bLength = sizeof as_out_fmt1_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +	.bDescriptorSubtype = UAC_FORMAT_TYPE,
> +	.bFormatType = UAC_FORMAT_TYPE_I,
> +};
> +
> +/* STD AS ISO OUT Endpoint */
> +struct usb_endpoint_descriptor fs_epout_desc = {
> +	.bLength = USB_DT_ENDPOINT_SIZE,
> +	.bDescriptorType = USB_DT_ENDPOINT,
> +
> +	.bEndpointAddress = USB_DIR_OUT,
> +	.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
> +	.bInterval = 1,
> +};
> +
> +struct usb_endpoint_descriptor hs_epout_desc = {
> +	.bLength = USB_DT_ENDPOINT_SIZE,
> +	.bDescriptorType = USB_DT_ENDPOINT,
> +
> +	.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
> +	.bInterval = 4,
> +};
The sync type is asynchronous, do you have plan to add feedback for handling
overflow and underflow?

> +
> +/* CS AS ISO OUT Endpoint */
> +static struct uac2_iso_endpoint_descriptor as_iso_out_desc = {
> +	.bLength = sizeof as_iso_out_desc,
> +	.bDescriptorType = USB_DT_CS_ENDPOINT,
> +
> +	.bDescriptorSubtype = UAC_EP_GENERAL,
> +	.bmAttributes = 0,
> +	.bmControls = 0,
> +	.bLockDelayUnits = 0,
> +	.wLockDelay = 0,
> +};
> +
> +/* Audio Streaming IN Interface - Alt0 */
> +static struct usb_interface_descriptor std_as_in_if0_desc = {
> +	.bLength = sizeof std_as_in_if0_desc,
> +	.bDescriptorType = USB_DT_INTERFACE,
> +
> +	.bAlternateSetting = 0,
> +	.bNumEndpoints = 0,
> +	.bInterfaceClass = USB_CLASS_AUDIO,
> +	.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
> +	.bInterfaceProtocol = UAC_VERSION_2,
> +};
> +
> +/* Audio Streaming IN Interface - Alt1 */
> +static struct usb_interface_descriptor std_as_in_if1_desc = {
> +	.bLength = sizeof std_as_in_if1_desc,
> +	.bDescriptorType = USB_DT_INTERFACE,
> +
> +	.bAlternateSetting = 1,
> +	.bNumEndpoints = 1,
> +	.bInterfaceClass = USB_CLASS_AUDIO,
> +	.bInterfaceSubClass = USB_SUBCLASS_AUDIOSTREAMING,
> +	.bInterfaceProtocol = UAC_VERSION_2,
> +};
> +
> +/* Audio Stream IN Intface Desc */
> +struct uac2_as_header_descriptor as_in_hdr_desc = {
> +	.bLength = sizeof as_in_hdr_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +
> +	.bDescriptorSubtype = UAC_AS_GENERAL,
> +	.bTerminalLink = USB_IN_OT_ID,
> +	.bmControls = 0,
> +	.bFormatType = UAC_FORMAT_TYPE_I,
> +	.bmFormats = cpu_to_le32(UAC_FORMAT_TYPE_I_PCM),
> +	.iChannelNames = 0,
> +};
> +
> +/* Audio USB_IN Format */
> +struct uac2_format_type_i_descriptor as_in_fmt1_desc = {
> +	.bLength = sizeof as_in_fmt1_desc,
> +	.bDescriptorType = USB_DT_CS_INTERFACE,
> +	.bDescriptorSubtype = UAC_FORMAT_TYPE,
> +	.bFormatType = UAC_FORMAT_TYPE_I,
> +};
> +
> +/* STD AS ISO IN Endpoint */
> +struct usb_endpoint_descriptor fs_epin_desc = {
> +	.bLength = USB_DT_ENDPOINT_SIZE,
> +	.bDescriptorType = USB_DT_ENDPOINT,
> +
> +	.bEndpointAddress = USB_DIR_IN,
> +	.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
> +	.bInterval = 1,
> +};
> +
> +struct usb_endpoint_descriptor hs_epin_desc = {
> +	.bLength = USB_DT_ENDPOINT_SIZE,
> +	.bDescriptorType = USB_DT_ENDPOINT,
> +
> +	.bmAttributes = USB_ENDPOINT_XFER_ISOC | USB_ENDPOINT_SYNC_ASYNC,
> +	.bInterval = 4,
> +};
> +
> +/* CS AS ISO IN Endpoint */
> +static struct uac2_iso_endpoint_descriptor as_iso_in_desc = {
> +	.bLength = sizeof as_iso_in_desc,
> +	.bDescriptorType = USB_DT_CS_ENDPOINT,
> +
> +	.bDescriptorSubtype = UAC_EP_GENERAL,
> +	.bmAttributes = 0,
> +	.bmControls = 0,
> +	.bLockDelayUnits = 0,
> +	.wLockDelay = 0,
> +};
> +
> +static struct usb_descriptor_header *fs_audio_desc[] = {
> +	(struct usb_descriptor_header *)&iad_desc,
> +	(struct usb_descriptor_header *)&std_ac_if_desc,
> +
> +	(struct usb_descriptor_header *)&ac_hdr_desc,
> +	(struct usb_descriptor_header *)&in_clk_src_desc,
> +	(struct usb_descriptor_header *)&out_clk_src_desc,
> +	(struct usb_descriptor_header *)&usb_out_it_desc,
> +	(struct usb_descriptor_header *)&io_in_it_desc,
> +	(struct usb_descriptor_header *)&usb_in_ot_desc,
> +	(struct usb_descriptor_header *)&io_out_ot_desc,
> +
> +	(struct usb_descriptor_header *)&std_as_out_if0_desc,
> +	(struct usb_descriptor_header *)&std_as_out_if1_desc,
> +
> +	(struct usb_descriptor_header *)&as_out_hdr_desc,
> +	(struct usb_descriptor_header *)&as_out_fmt1_desc,
> +	(struct usb_descriptor_header *)&fs_epout_desc,
> +	(struct usb_descriptor_header *)&as_iso_out_desc,
> +
> +	(struct usb_descriptor_header *)&std_as_in_if0_desc,
> +	(struct usb_descriptor_header *)&std_as_in_if1_desc,
> +
> +	(struct usb_descriptor_header *)&as_in_hdr_desc,
> +	(struct usb_descriptor_header *)&as_in_fmt1_desc,
> +	(struct usb_descriptor_header *)&fs_epin_desc,
> +	(struct usb_descriptor_header *)&as_iso_in_desc,
> +	NULL,
> +};
> +
> +static struct usb_descriptor_header *hs_audio_desc[] = {
> +	(struct usb_descriptor_header *)&iad_desc,
> +	(struct usb_descriptor_header *)&std_ac_if_desc,
> +
> +	(struct usb_descriptor_header *)&ac_hdr_desc,
> +	(struct usb_descriptor_header *)&in_clk_src_desc,
> +	(struct usb_descriptor_header *)&out_clk_src_desc,
> +	(struct usb_descriptor_header *)&usb_out_it_desc,
> +	(struct usb_descriptor_header *)&io_in_it_desc,
> +	(struct usb_descriptor_header *)&usb_in_ot_desc,
> +	(struct usb_descriptor_header *)&io_out_ot_desc,
> +
> +	(struct usb_descriptor_header *)&std_as_out_if0_desc,
> +	(struct usb_descriptor_header *)&std_as_out_if1_desc,
> +
> +	(struct usb_descriptor_header *)&as_out_hdr_desc,
> +	(struct usb_descriptor_header *)&as_out_fmt1_desc,
> +	(struct usb_descriptor_header *)&hs_epout_desc,
> +	(struct usb_descriptor_header *)&as_iso_out_desc,
> +
> +	(struct usb_descriptor_header *)&std_as_in_if0_desc,
> +	(struct usb_descriptor_header *)&std_as_in_if1_desc,
> +
> +	(struct usb_descriptor_header *)&as_in_hdr_desc,
> +	(struct usb_descriptor_header *)&as_in_fmt1_desc,
> +	(struct usb_descriptor_header *)&hs_epin_desc,
> +	(struct usb_descriptor_header *)&as_iso_in_desc,
> +	NULL,
> +};
> +
> +struct cntrl_cur_lay3 {
> +	__u32	dCUR;
> +};
> +
> +struct cntrl_range_lay3 {
> +	__u16	wNumSubRanges;
> +	__u32	dMIN;
> +	__u32	dMAX;
> +	__u32	dRES;
> +} __packed;
> +
> +static inline void
> +free_ep(struct uac2_rtd_params *prm, struct usb_ep *ep)
> +{
> +	struct snd_uac2_chip *uac2 = prm_to_uac2(prm);
> +	int i;
> +
> +	prm->ep_enabled = false;
> +
> +	for (i = 0; i < USB_XFERS; i++) {
> +		if (prm->ureq[i].req) {
> +			usb_ep_dequeue(ep, prm->ureq[i].req);
> +			usb_ep_free_request(ep, prm->ureq[i].req);
> +			prm->ureq[i].req = NULL;
> +		}
> +	}
> +
> +	if (usb_ep_disable(ep))
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +}
> +
> +static int __init
> +afunc_bind(struct usb_configuration *cfg, struct usb_function *fn)
> +{
> +	struct audio_dev *agdev = func_to_agdev(fn);
> +	struct snd_uac2_chip *uac2 = &agdev->uac2;
> +	struct usb_composite_dev *cdev = cfg->cdev;
> +	struct usb_gadget *gadget = cdev->gadget;
> +	struct uac2_rtd_params *prm;
> +	int ret;
> +
> +	ret = usb_interface_id(cfg, fn);
> +	if (ret < 0) {
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +		return ret;
> +	}
> +	std_ac_if_desc.bInterfaceNumber = ret;
> +	ALT_SET(agdev->ac_alt, 0);
> +	INTF_SET(agdev->ac_alt, ret);
> +
> +	ret = usb_interface_id(cfg, fn);
> +	if (ret < 0) {
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +		return ret;
> +	}
> +	std_as_out_if0_desc.bInterfaceNumber = ret;
> +	std_as_out_if1_desc.bInterfaceNumber = ret;
> +	ALT_SET(agdev->as_out_alt, 0);
> +	INTF_SET(agdev->as_out_alt, ret);
> +
> +	ret = usb_interface_id(cfg, fn);
> +	if (ret < 0) {
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +		return ret;
> +	}
> +	std_as_in_if0_desc.bInterfaceNumber = ret;
> +	std_as_in_if1_desc.bInterfaceNumber = ret;
> +	ALT_SET(agdev->as_in_alt, 0);
> +	INTF_SET(agdev->as_in_alt, ret);
> +
> +	agdev->out_ep = usb_ep_autoconfig(gadget, &fs_epout_desc);
> +	if (!agdev->out_ep)
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +	agdev->out_ep->driver_data = agdev;
> +
> +	agdev->in_ep = usb_ep_autoconfig(gadget, &fs_epin_desc);
> +	if (!agdev->in_ep)
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +	agdev->in_ep->driver_data = agdev;
> +
> +	hs_epout_desc.bEndpointAddress = fs_epout_desc.bEndpointAddress;
> +	hs_epout_desc.wMaxPacketSize = fs_epout_desc.wMaxPacketSize;
> +	hs_epin_desc.bEndpointAddress = fs_epin_desc.bEndpointAddress;
> +	hs_epin_desc.wMaxPacketSize = fs_epin_desc.wMaxPacketSize;
> +
> +	fn->descriptors = usb_copy_descriptors(fs_audio_desc);
> +	if (gadget_is_dualspeed(gadget))
> +		fn->hs_descriptors = usb_copy_descriptors(hs_audio_desc);
> +
> +	prm = &agdev->uac2.c_prm;
> +	prm->max_psize = hs_epout_desc.wMaxPacketSize;
> +	prm->rbuf = kzalloc(prm->max_psize * USB_XFERS, GFP_KERNEL);
> +	if (!prm->rbuf) {
> +		prm->max_psize = 0;
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +	}
> +
> +	prm = &agdev->uac2.p_prm;
> +	prm->max_psize = hs_epin_desc.wMaxPacketSize;
> +	prm->rbuf = kzalloc(prm->max_psize * USB_XFERS, GFP_KERNEL);
> +	if (!prm->rbuf) {
> +		prm->max_psize = 0;
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +	}
> +
> +	return alsa_uac2_init(agdev);
> +}
> +
> +static void
> +afunc_unbind(struct usb_configuration *cfg, struct usb_function *fn)
> +{
> +	struct audio_dev *agdev = func_to_agdev(fn);
> +	struct usb_composite_dev *cdev = cfg->cdev;
> +	struct usb_gadget *gadget = cdev->gadget;
> +	struct uac2_rtd_params *prm;
> +
> +	alsa_uac2_exit(agdev);
> +
> +	prm = &agdev->uac2.p_prm;
> +	kfree(prm->rbuf);
> +
> +	prm = &agdev->uac2.c_prm;
> +	kfree(prm->rbuf);
> +
> +	if (gadget_is_dualspeed(gadget))
> +		usb_free_descriptors(fn->hs_descriptors);
> +	usb_free_descriptors(fn->descriptors);
> +
> +	if (agdev->in_ep)
> +		agdev->in_ep->driver_data = NULL;
> +	if (agdev->out_ep)
> +		agdev->out_ep->driver_data = NULL;
> +}
> +
> +static int
> +afunc_set_alt(struct usb_function *fn, unsigned intf, unsigned alt)
> +{
> +	struct usb_composite_dev *cdev = fn->config->cdev;
> +	struct audio_dev *agdev = func_to_agdev(fn);
> +	struct snd_uac2_chip *uac2 = &agdev->uac2;
> +	struct usb_gadget *gadget = cdev->gadget;
> +	struct usb_request *req;
> +	struct usb_ep *ep;
> +	struct uac2_rtd_params *prm;
> +	int i;
> +
> +	/* No i/f has more than 2 alt settings */
> +	if (alt > 1) {
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +		return -EINVAL;
> +	}
> +
> +	if (intf == INTF_GET(agdev->ac_alt)) {
> +		/* Control I/f has only 1 AltSetting - 0 */
> +		if (alt) {
> +			dev_err(&uac2->pdev.dev,
> +				"%s:%d Error!\n", __func__, __LINE__);
> +			return -EINVAL;
> +		}
> +		return 0;
> +	}
> +
> +	if (intf == INTF_GET(agdev->as_out_alt)) {
> +		ep = agdev->out_ep;
> +		prm = &uac2->c_prm;
> +		config_ep_by_speed(gadget, fn, ep);
> +		ALT_SET(agdev->as_out_alt, alt);
> +	} else if (intf == INTF_GET(agdev->as_in_alt)) {
> +		ep = agdev->in_ep;
> +		prm = &uac2->p_prm;
> +		config_ep_by_speed(gadget, fn, ep);
> +		ALT_SET(agdev->as_in_alt, alt);
> +	} else {
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +		return -EINVAL;
> +	}
> +
> +	if (alt == 0) {
> +		free_ep(prm, ep);
> +		return 0;
> +	}
> +
> +	prm->ep_enabled = true;
> +	usb_ep_enable(ep);
> +
> +	for (i = 0; i < USB_XFERS; i++) {
> +		if (prm->ureq[i].req) {
> +			if (usb_ep_queue(ep, prm->ureq[i].req, GFP_ATOMIC))
> +				dev_err(&uac2->pdev.dev, "%d Error!\n",
> +					__LINE__);
> +			continue;
> +		}
> +
> +		req = usb_ep_alloc_request(ep, GFP_ATOMIC);
> +		if (req == NULL) {
> +			dev_err(&uac2->pdev.dev,
> +				"%s:%d Error!\n", __func__, __LINE__);
> +			return -EINVAL;
> +		}
> +
> +		prm->ureq[i].req = req;
> +		prm->ureq[i].pp = prm;
> +
> +		req->zero = 0;
> +		req->dma = DMA_ADDR_INVALID;
> +		req->context = &prm->ureq[i];
> +		req->length = prm->max_psize;
> +		req->complete =	agdev_iso_complete;
> +		req->buf = prm->rbuf + i * req->length;
> +
> +		if (usb_ep_queue(ep, req, GFP_ATOMIC))
> +			dev_err(&uac2->pdev.dev, "%d Error!\n", __LINE__);
> +	}
> +
> +	return 0;
> +}
> +
> +static int
> +afunc_get_alt(struct usb_function *fn, unsigned intf)
> +{
> +	struct audio_dev *agdev = func_to_agdev(fn);
> +	struct snd_uac2_chip *uac2 = &agdev->uac2;
> +
> +	if (intf == INTF_GET(agdev->ac_alt))
> +		return ALT_GET(agdev->ac_alt);
> +	else if (intf == INTF_GET(agdev->as_out_alt))
> +		return ALT_GET(agdev->as_out_alt);
> +	else if (intf == INTF_GET(agdev->as_in_alt))
> +		return ALT_GET(agdev->as_in_alt);
> +	else
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Invalid Interface %d!\n",
> +			__func__, __LINE__, intf);
> +
> +	return -EINVAL;
> +}
> +
> +static void
> +afunc_disable(struct usb_function *fn)
> +{
> +	struct audio_dev *agdev = func_to_agdev(fn);
> +	struct snd_uac2_chip *uac2 = &agdev->uac2;
> +
> +	free_ep(&uac2->p_prm, agdev->in_ep);
> +	ALT_SET(agdev->as_in_alt, 0);
> +
> +	free_ep(&uac2->c_prm, agdev->out_ep);
> +	ALT_SET(agdev->as_out_alt, 0);
> +}
> +
> +static int
> +in_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
> +{
> +	struct usb_request *req = fn->config->cdev->req;
> +	struct audio_dev *agdev = func_to_agdev(fn);
> +	struct snd_uac2_chip *uac2 = &agdev->uac2;
> +	u16 w_length = le16_to_cpu(cr->wLength);
> +	u16 w_index = le16_to_cpu(cr->wIndex);
> +	u16 w_value = le16_to_cpu(cr->wValue);
> +	u8 entity_id = (w_index >> 8) & 0xff;
> +	u8 control_selector = w_value >> 8;
> +	int value = -EOPNOTSUPP;
> +
> +	if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
> +		struct cntrl_cur_lay3 c;
> +
> +		if (entity_id == USB_IN_CLK_ID)
> +			c.dCUR = p_srate;
> +		else if (entity_id == USB_OUT_CLK_ID)
> +			c.dCUR = c_srate;
> +
> +		value = min_t(unsigned, w_length, sizeof c);
> +		memcpy(req->buf, &c, value);
> +	} else if (control_selector == UAC2_CS_CONTROL_CLOCK_VALID) {
> +		*(u8 *)req->buf = 1;
> +		value = min(w_length, (u16) 1);
> +	} else {
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d control_selector=%d TODO!\n",
> +			__func__, __LINE__, control_selector);
> +	}
> +
> +	return value;
> +}
> +
> +static int
> +in_rq_range(struct usb_function *fn, const struct usb_ctrlrequest *cr)
> +{
> +	struct usb_request *req = fn->config->cdev->req;
> +	struct audio_dev *agdev = func_to_agdev(fn);
> +	struct snd_uac2_chip *uac2 = &agdev->uac2;
> +	u16 w_length = le16_to_cpu(cr->wLength);
> +	u16 w_index = le16_to_cpu(cr->wIndex);
> +	u16 w_value = le16_to_cpu(cr->wValue);
> +	u8 entity_id = (w_index >> 8) & 0xff;
> +	u8 control_selector = w_value >> 8;
> +	struct cntrl_range_lay3 r;
> +	int value = -EOPNOTSUPP;
> +
> +	if (control_selector == UAC2_CS_CONTROL_SAM_FREQ) {
> +		if (entity_id == USB_IN_CLK_ID)
> +			r.dMIN = p_srate;
> +		else if (entity_id == USB_OUT_CLK_ID)
> +			r.dMIN = c_srate;
> +		else
> +			return -EOPNOTSUPP;
> +
> +		r.dMAX = r.dMIN;
> +		r.dRES = 0;
> +		r.wNumSubRanges = 1;
> +
> +		value = min_t(unsigned, w_length, sizeof r);
> +		memcpy(req->buf, &r, value);
> +	} else {
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d control_selector=%d TODO!\n",
> +			__func__, __LINE__, control_selector);
> +	}
> +
> +	return value;
> +}
> +
> +static int
> +ac_rq_in(struct usb_function *fn, const struct usb_ctrlrequest *cr)
> +{
> +	if (cr->bRequest == UAC2_CS_CUR)
> +		return in_rq_cur(fn, cr);
> +	else if (cr->bRequest == UAC2_CS_RANGE)
> +		return in_rq_range(fn, cr);
> +	else
> +		return -EOPNOTSUPP;
> +}
> +
> +static int
> +out_rq_cur(struct usb_function *fn, const struct usb_ctrlrequest *cr)
> +{
> +	u16 w_length = le16_to_cpu(cr->wLength);
> +	u16 w_value = le16_to_cpu(cr->wValue);
> +	u8 control_selector = w_value >> 8;
> +
> +	if (control_selector == UAC2_CS_CONTROL_SAM_FREQ)
> +		return w_length;
> +
> +	return -EOPNOTSUPP;
> +}
> +
> +static int
> +setup_rq_inf(struct usb_function *fn, const struct usb_ctrlrequest *cr)
> +{
> +	struct audio_dev *agdev = func_to_agdev(fn);
> +	struct snd_uac2_chip *uac2 = &agdev->uac2;
> +	u16 w_index = le16_to_cpu(cr->wIndex);
> +	u8 intf = w_index & 0xff;
> +
> +	if (intf != INTF_GET(agdev->ac_alt)) {
> +		dev_err(&uac2->pdev.dev,
> +			"%s:%d Error!\n", __func__, __LINE__);
> +		return -EOPNOTSUPP;
> +	}
> +
> +	if (cr->bRequestType & USB_DIR_IN)
> +		return ac_rq_in(fn, cr);
> +	else if (cr->bRequest == UAC2_CS_CUR)
> +		return out_rq_cur(fn, cr);
> +
> +	return -EOPNOTSUPP;
> +}
> +
> +static int
> +afunc_setup(struct usb_function *fn, const struct usb_ctrlrequest *cr)
> +{
> +	struct usb_composite_dev *cdev = fn->config->cdev;
> +	struct audio_dev *agdev = func_to_agdev(fn);
> +	struct snd_uac2_chip *uac2 = &agdev->uac2;
> +	struct usb_request *req = cdev->req;
> +	u16 w_length = le16_to_cpu(cr->wLength);
> +	int value = -EOPNOTSUPP;
> +
> +	/* Only Class specific requests are supposed to reach here */
> +	if ((cr->bRequestType & USB_TYPE_MASK) != USB_TYPE_CLASS)
> +		return -EOPNOTSUPP;
> +
> +	if ((cr->bRequestType & USB_RECIP_MASK) == USB_RECIP_INTERFACE)
> +		value = setup_rq_inf(fn, cr);
> +	else
> +		dev_err(&uac2->pdev.dev, "%s:%d Error!\n", __func__,
> __LINE__);
> +
> +	if (value >= 0) {
> +		req->length = value;
> +		req->zero = value < w_length;
> +		value = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
> +		if (value < 0) {
> +			dev_err(&uac2->pdev.dev,
> +				"%s:%d Error!\n", __func__, __LINE__);
> +			req->status = 0;
> +		}
> +	}
> +
> +	return value;
> +}
> +
> +static int
> +uac2_bind_config(struct usb_configuration *cfg)
> +{
> +	int id, res;
> +
> +	agdev_g = kzalloc(sizeof *agdev_g, GFP_KERNEL);
> +	if (agdev_g == NULL) {
> +		printk(KERN_ERR "Unable to allocate audio gadget\n");
> +		return -ENOMEM;
> +	}
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_ASSOC].id = id;
> +	iad_desc.iFunction = id,
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_IF_CTRL].id = id;
> +	std_ac_if_desc.iInterface = id,
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_CLKSRC_IN].id = id;
> +	in_clk_src_desc.iClockSource = id,
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_CLKSRC_OUT].id = id;
> +	out_clk_src_desc.iClockSource = id,
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_USB_IT].id = id;
> +	usb_out_it_desc.iTerminal = id,
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_IO_IT].id = id;
> +	io_in_it_desc.iTerminal = id;
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_USB_OT].id = id;
> +	usb_in_ot_desc.iTerminal = id;
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_IO_OT].id = id;
> +	io_out_ot_desc.iTerminal = id;
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_AS_OUT_ALT0].id = id;
> +	std_as_out_if0_desc.iInterface = id;
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_AS_OUT_ALT1].id = id;
> +	std_as_out_if1_desc.iInterface = id;
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_AS_IN_ALT0].id = id;
> +	std_as_in_if0_desc.iInterface = id;
> +
> +	id = usb_string_id(cfg->cdev);
> +	if (id < 0)
> +		return id;
> +
> +	strings_fn[STR_AS_IN_ALT1].id = id;
> +	std_as_in_if1_desc.iInterface = id;
> +
> +	agdev_g->func.name = "uac2_func";
> +	agdev_g->func.strings = fn_strings;
> +	agdev_g->func.bind = afunc_bind;
> +	agdev_g->func.unbind = afunc_unbind;
> +	agdev_g->func.set_alt = afunc_set_alt;
> +	agdev_g->func.get_alt = afunc_get_alt;
> +	agdev_g->func.disable = afunc_disable;
> +	agdev_g->func.setup = afunc_setup;
> +
> +	/* Initialize the configurable parameters */
> +	usb_out_it_desc.bNrChannels = num_channels(c_chmask);
> +	usb_out_it_desc.bmChannelConfig = cpu_to_le32(c_chmask);
> +	io_in_it_desc.bNrChannels = num_channels(p_chmask);
> +	io_in_it_desc.bmChannelConfig = cpu_to_le32(p_chmask);
> +	as_out_hdr_desc.bNrChannels = num_channels(c_chmask);
> +	as_out_hdr_desc.bmChannelConfig = cpu_to_le32(c_chmask);
> +	as_in_hdr_desc.bNrChannels = num_channels(p_chmask);
> +	as_in_hdr_desc.bmChannelConfig = cpu_to_le32(p_chmask);
> +	as_out_fmt1_desc.bSubslotSize = c_ssize;
> +	as_out_fmt1_desc.bBitResolution = c_ssize * 8;
> +	as_in_fmt1_desc.bSubslotSize = p_ssize;
> +	as_in_fmt1_desc.bBitResolution = p_ssize * 8;
> +
> +	snprintf(clksrc_in, sizeof(clksrc_in), "%uHz", p_srate);
> +	snprintf(clksrc_out, sizeof(clksrc_out), "%uHz", c_srate);
> +
> +	res = usb_add_function(cfg, &agdev_g->func);
> +	if (res < 0)
> +		kfree(agdev_g);
> +
> +	return res;
> +}
> +
> +static void
> +uac2_unbind_config(struct usb_configuration *cfg)
> +{
> +	kfree(agdev_g);
> +	agdev_g = NULL;
> +}
> --
> 1.7.4.1
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-usb" in
> the body of a message to majordomo@xxxxxxxxxxxxxxx
> More majordomo info at  http://vger.kernel.org/majordomo-info.html


--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux