diff options
author | Mauro Carvalho Chehab <mchehab@infradead.org> | 2008-01-05 09:57:31 -0300 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@infradead.org> | 2008-01-25 19:04:32 -0200 |
commit | 6d79468dd8537530f4150e76ed9b4b63f80571c6 (patch) | |
tree | f785aeeec131c092b334e458127eb44fcf4ded2d /drivers/media/video/em28xx/em28xx-audio.c | |
parent | 1a6f11e0e8db9e76ef34bc6731d5291e4df1ba37 (diff) | |
download | kernel_samsung_smdk4412-6d79468dd8537530f4150e76ed9b4b63f80571c6.zip kernel_samsung_smdk4412-6d79468dd8537530f4150e76ed9b4b63f80571c6.tar.gz kernel_samsung_smdk4412-6d79468dd8537530f4150e76ed9b4b63f80571c6.tar.bz2 |
V4L/DVB (6951): Integrates em28xx-audio.c into em28xx kernel module
Also fixes the remaining CodingStyle issues that Lindent didn't fix.
Signed-off-by: Mauro Carvalho Chehab <mchehab@infradead.org>
Diffstat (limited to 'drivers/media/video/em28xx/em28xx-audio.c')
-rw-r--r-- | drivers/media/video/em28xx/em28xx-audio.c | 507 |
1 files changed, 251 insertions, 256 deletions
diff --git a/drivers/media/video/em28xx/em28xx-audio.c b/drivers/media/video/em28xx/em28xx-audio.c index c631cac..c91ff52 100644 --- a/drivers/media/video/em28xx/em28xx-audio.c +++ b/drivers/media/video/em28xx/em28xx-audio.c @@ -3,6 +3,10 @@ * * Copyright (C) 2006 Markus Rechberger <mrechberger@gmail.com> * + * Copyright (C) 2007 Mauro Carvalho Chehab <mchehab@infradead.org> + * - Port to work with the in-kernel driver + * - Several cleanups + * * This driver is based on my previous au600 usb pstn audio driver * and inherits all the copyrights * @@ -30,7 +34,7 @@ #include <linux/slab.h> #include <linux/vmalloc.h> #include <linux/proc_fs.h> -#include <linux/moduleparam.h> +#include <linux/module.h> #include <sound/driver.h> #include <sound/core.h> #include <sound/pcm.h> @@ -38,222 +42,52 @@ #include <sound/info.h> #include <sound/initval.h> #include <sound/control.h> -//#include <linux/video_decoder.h> -//#include <media/tuner.h> #include <media/v4l2-common.h> #include "em28xx.h" -static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; -static int em28xx_cmd(struct em28xx *dev, int cmd, int arg); - -static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs, - size_t size) -{ - struct snd_pcm_runtime *runtime = subs->runtime; - if (runtime->dma_area) { - if (runtime->dma_bytes > size) - return 0; - vfree(runtime->dma_area); - } - runtime->dma_area = vmalloc(size); - if (!runtime->dma_area) - return -ENOMEM; - runtime->dma_bytes = size; - return 0; -} +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "activates debug info"); -static struct snd_pcm_hardware snd_em28xx_hw_capture = { - .info = - SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP | - SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_MMAP_VALID, - .formats = SNDRV_PCM_FMTBIT_S16_LE, - .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT, - .rate_min = 48000, - .rate_max = 48000, - .channels_min = 2, - .channels_max = 2, - .buffer_bytes_max = 62720 * 8, /* just about the value in usbaudio.c */ - .period_bytes_min = 64, //12544/2, - .period_bytes_max = 12544, - .periods_min = 2, - .periods_max = 98, //12544, -}; +#define dprintk(fmt, arg...) do { \ + if (debug) \ + printk(KERN_INFO "em28xx-audio %s: " fmt, \ + __FUNCTION__, ##arg); \ + } while (0) -static int snd_em28xx_capture_open(struct snd_pcm_substream *substream) -{ - int ret = 0; - int mode; - struct em28xx *dev = snd_pcm_substream_chip(substream); - struct snd_pcm_runtime *runtime = substream->runtime; - printk("opening radio device and trying to acquire exclusive lock\n"); - switch (dev->mode) { - case TUNER_STUB_DVBC_TV: - case TUNER_STUB_DVBT_TV: - case TUNER_STUB_ATSC_TV: - /* digital has no support for analog audio */ - if (ret != 0) { - printk("device is already in use by DVB-T\n"); - return -EINVAL; - } else { - struct v4l2_tuner tuner; - printk("switching device to FM mode\n"); - - mode = TUNER_STUB_RADIO; - memset(&tuner, 0x0, sizeof(struct v4l2_tuner)); - tuner.type = V4L2_TUNER_RADIO; - - /* enable GPIO for analog TV */ - dev->em28xx_gpio_control(dev, EM28XX_MODE, - (void *)mode); - dev->mode = mode; - /* upload firmware */ - tuner_run_cmd(dev->tobj, TUNER_CMD_INIT, (void *)mode); - - /* required for devices which have kerneldriver dependencies */ -// em28xx_config(dev); -// em28xx_config_i2c(dev); - - /* this is moreover to switch the decoder to FM */ - em28xx_i2c_call_clients(dev, VIDIOC_S_TUNER, &tuner); - - dev->em28xx_write_regs(dev, 0x0f, "\x87", 1); - ret = dev->em28xx_acquire(dev, EM28XX_RADIO, 1); - em28xx_i2c_call_clients(dev, VIDIOC_INT_RESET, 0); - /* TODO switch to FM mode */ - - printk("em28xx-audio: %d mode\n", mode); - tuner_run_cmd(dev->tobj, TUNER_CMD_G_MODE, &mode); - printk("retrieved mode from tuner: %d\n", mode); - } - break; - - case TUNER_STUB_ANALOG_TV: - printk("em28xx-audio: device is currently in analog TV mode\n"); - /* unmute by default */ - dev->em28xx_write_regs(dev, 0x0f, "\x87", 1); - break; - case TUNER_STUB_RADIO: - /* check current mode and put a hard lock onto it */ - printk - ("em28xx-audio: device is currently in analogue FM mode\n"); - /* unmute by default here */ - dev->em28xx_write_regs(dev, 0x0f, "\x87", 1); - ret = dev->em28xx_acquire(dev, EM28XX_RADIO, 1); - if (ret == 0) - printk("device is locked in fmradio mode now\n"); - break; - default: - printk("em28xx-audio: unhandled mode %d\n", dev->mode); - } - - runtime->hw = snd_em28xx_hw_capture; - if (dev->alt == 0 && dev->adev->users == 0) { - int errCode; - dev->alt = 7; - errCode = usb_set_interface(dev->udev, 0, 7); - printk("changing alternate number to 7\n"); - } - dev->adev->users++; - snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); - dev->adev->capture_pcm_substream = substream; - runtime->private_data = dev; - return 0; -} - -static int snd_em28xx_pcm_close(struct snd_pcm_substream *substream) -{ - struct em28xx *dev = snd_pcm_substream_chip(substream); - int amode = 0; - dev->adev->users--; - - /* decrease audio reference */ - switch (dev->mode) { - case TUNER_STUB_ANALOG_TV: - amode = EM28XX_VIDEO; - break; - case TUNER_STUB_RADIO: - amode = EM28XX_RADIO; - break; - default: - printk("invalid mode: %d\n", dev->mode); - break; - } - - dev->em28xx_acquire(dev, amode, 0); - - if (dev->adev->users == 0 && dev->adev->shutdown == 1) { - printk("audio users: %d\n", dev->adev->users); - printk("disabling audio stream!\n"); - dev->adev->shutdown = 0; - printk("released lock\n"); - em28xx_cmd(dev, EM28XX_CAPTURE_STREAM_EN, 0); - } - return 0; -} +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; -static int snd_em28xx_hw_capture_params(struct snd_pcm_substream *substream, - struct snd_pcm_hw_params *hw_params) +static int em28xx_isoc_audio_deinit(struct em28xx *dev) { - unsigned int channels, rate, format; - int ret; - ret = - snd_pcm_alloc_vmalloc_buffer(substream, - params_buffer_bytes(hw_params)); - format = params_format(hw_params); - rate = params_rate(hw_params); - channels = params_channels(hw_params); - /* TODO: set up em28xx audio chip to deliver the correct audio format, current default is 48000hz multiplexed => 96000hz mono - which shouldn't matter since analogue TV only supports mono */ - return 0; -} + int i; -static int snd_em28xx_hw_capture_free(struct snd_pcm_substream *substream) -{ - struct em28xx *dev = snd_pcm_substream_chip(substream); - if (dev->adev->capture_stream == STREAM_ON) { - em28xx_cmd(dev, EM28XX_CAPTURE_STREAM_EN, 0); + dprintk("Stopping isoc\n"); + for (i = 0; i < EM28XX_AUDIO_BUFS; i++) { + usb_kill_urb(dev->adev->urb[i]); + usb_free_urb(dev->adev->urb[i]); + dev->adev->urb[i] = NULL; } - return 0; -} -static int snd_em28xx_prepare(struct snd_pcm_substream *substream) -{ return 0; } -static int snd_em28xx_capture_trigger(struct snd_pcm_substream *substream, - int cmd) -{ - struct em28xx *dev = snd_pcm_substream_chip(substream); - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - em28xx_cmd(dev, EM28XX_CAPTURE_STREAM_EN, 1); - return 0; - case SNDRV_PCM_TRIGGER_STOP: - dev->adev->shutdown = 1; - return 0; - default: - return -EINVAL; - } -} - static void em28xx_audio_isocirq(struct urb *urb) { - struct em28xx *dev = urb->context; - int i; - unsigned int oldptr; - unsigned long flags; - int period_elapsed = 0; - int status; - unsigned char *cp; - unsigned int stride; + struct em28xx *dev = urb->context; + int i; + unsigned int oldptr; + unsigned long flags; + int period_elapsed = 0; + int status; + unsigned char *cp; + unsigned int stride; struct snd_pcm_substream *substream; - struct snd_pcm_runtime *runtime; + struct snd_pcm_runtime *runtime; if (dev->adev->capture_pcm_substream) { substream = dev->adev->capture_pcm_substream; runtime = substream->runtime; - stride = runtime->frame_bits >> 3; + for (i = 0; i < urb->number_of_packets; i++) { int length = urb->iso_frame_desc[i].actual_length / stride; @@ -264,6 +98,7 @@ static void em28xx_audio_isocirq(struct urb *urb) continue; spin_lock_irqsave(&dev->adev->slock, flags); + oldptr = dev->adev->hwptr_done_capture; dev->adev->hwptr_done_capture += length; if (dev->adev->hwptr_done_capture >= @@ -278,6 +113,7 @@ static void em28xx_audio_isocirq(struct urb *urb) runtime->period_size; period_elapsed = 1; } + spin_unlock_irqrestore(&dev->adev->slock, flags); if (oldptr + length >= runtime->buffer_size) { @@ -292,82 +128,79 @@ static void em28xx_audio_isocirq(struct urb *urb) length * stride); } } - if (period_elapsed) { + if (period_elapsed) snd_pcm_period_elapsed(substream); - } } urb->status = 0; if (dev->adev->shutdown) return; - if ((status = usb_submit_urb(urb, GFP_ATOMIC))) { + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status < 0) { em28xx_errdev("resubmit of audio urb failed (error=%i)\n", status); } return; } -static int em28xx_isoc_audio_deinit(struct em28xx *dev) -{ - int i; - for (i = 0; i < EM28XX_AUDIO_BUFS; i++) { - usb_kill_urb(dev->adev->urb[i]); - usb_free_urb(dev->adev->urb[i]); - dev->adev->urb[i] = NULL; - } - return 0; -} - static int em28xx_init_audio_isoc(struct em28xx *dev) { - int i; - int errCode; - const int sb_size = - EM28XX_NUM_AUDIO_PACKETS * EM28XX_AUDIO_MAX_PACKET_SIZE; + int i, errCode; + const int sb_size = EM28XX_NUM_AUDIO_PACKETS * + EM28XX_AUDIO_MAX_PACKET_SIZE; + + dprintk("Starting isoc transfers\n"); for (i = 0; i < EM28XX_AUDIO_BUFS; i++) { struct urb *urb; int j, k; + dev->adev->transfer_buffer[i] = kmalloc(sb_size, GFP_ATOMIC); - if (!dev->adev->transfer_buffer[i]) { + if (!dev->adev->transfer_buffer[i]) return -ENOMEM; - } + memset(dev->adev->transfer_buffer[i], 0x80, sb_size); urb = usb_alloc_urb(EM28XX_NUM_AUDIO_PACKETS, GFP_ATOMIC); - if (urb) { - urb->dev = dev->udev; - urb->context = dev; - urb->pipe = usb_rcvisocpipe(dev->udev, 0x83); - urb->transfer_flags = URB_ISO_ASAP; - urb->transfer_buffer = dev->adev->transfer_buffer[i]; - urb->interval = 1; - urb->complete = em28xx_audio_isocirq; - urb->number_of_packets = EM28XX_NUM_AUDIO_PACKETS; - urb->transfer_buffer_length = sb_size; - for (j = k = 0; j < EM28XX_NUM_AUDIO_PACKETS; - j++, k += EM28XX_AUDIO_MAX_PACKET_SIZE) { - urb->iso_frame_desc[j].offset = k; - urb->iso_frame_desc[j].length = - EM28XX_AUDIO_MAX_PACKET_SIZE; - } - dev->adev->urb[i] = urb; - } else { + if (!urb) return -ENOMEM; + + urb->dev = dev->udev; + urb->context = dev; + urb->pipe = usb_rcvisocpipe(dev->udev, 0x83); + urb->transfer_flags = URB_ISO_ASAP; + urb->transfer_buffer = dev->adev->transfer_buffer[i]; + urb->interval = 1; + urb->complete = em28xx_audio_isocirq; + urb->number_of_packets = EM28XX_NUM_AUDIO_PACKETS; + urb->transfer_buffer_length = sb_size; + + for (j = k = 0; j < EM28XX_NUM_AUDIO_PACKETS; + j++, k += EM28XX_AUDIO_MAX_PACKET_SIZE) { + urb->iso_frame_desc[j].offset = k; + urb->iso_frame_desc[j].length = + EM28XX_AUDIO_MAX_PACKET_SIZE; } + dev->adev->urb[i] = urb; } + for (i = 0; i < EM28XX_AUDIO_BUFS; i++) { errCode = usb_submit_urb(dev->adev->urb[i], GFP_ATOMIC); if (errCode) { em28xx_isoc_audio_deinit(dev); + return errCode; } } + return 0; } static int em28xx_cmd(struct em28xx *dev, int cmd, int arg) { + dprintk("%s transfer\n", (dev->adev->capture_stream == STREAM_ON)? + "stop" : "start"); + switch (cmd) { case EM28XX_CAPTURE_STREAM_EN: if (dev->adev->capture_stream == STREAM_OFF && arg == 1) { @@ -377,8 +210,8 @@ static int em28xx_cmd(struct em28xx *dev, int cmd, int arg) dev->adev->capture_stream = STREAM_OFF; em28xx_isoc_audio_deinit(dev); } else { - printk - ("An underrun occured very likely... ignoring it\n"); + printk(KERN_ERR "An underrun very likely occurred. " + "Ignoring it.\n"); } return 0; default: @@ -386,13 +219,167 @@ static int em28xx_cmd(struct em28xx *dev, int cmd, int arg) } } +static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs, + size_t size) +{ + struct snd_pcm_runtime *runtime = subs->runtime; + + dprintk("Alocating vbuffer\n"); + if (runtime->dma_area) { + if (runtime->dma_bytes > size) + return 0; + + vfree(runtime->dma_area); + } + runtime->dma_area = vmalloc(size); + if (!runtime->dma_area) + return -ENOMEM; + + runtime->dma_bytes = size; + + return 0; +} + +static struct snd_pcm_hardware snd_em28xx_hw_capture = { + .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID, + + .formats = SNDRV_PCM_FMTBIT_S16_LE, + + .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT, + + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 62720 * 8, /* just about the value in usbaudio.c */ + .period_bytes_min = 64, /* 12544/2, */ + .period_bytes_max = 12544, + .periods_min = 2, + .periods_max = 98, /* 12544, */ +}; + +static int snd_em28xx_capture_open(struct snd_pcm_substream *substream) +{ + struct em28xx *dev = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int ret = 0; + + dprintk("opening device and trying to acquire exclusive lock\n"); + + /* Sets volume, mute, etc */ + dev->mute = 0; + ret = em28xx_audio_analog_set(dev); + if (ret < 0) + goto err; + + runtime->hw = snd_em28xx_hw_capture; + if (dev->alt == 0 && dev->adev->users == 0) { + int errCode; + dev->alt = 7; + errCode = usb_set_interface(dev->udev, 0, 7); + dprintk("changing alternate number to 7\n"); + } + + dev->adev->users++; + + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + dev->adev->capture_pcm_substream = substream; + runtime->private_data = dev; + + return 0; +err: + printk(KERN_ERR "Error while configuring em28xx mixer\n"); + return ret; +} + +static int snd_em28xx_pcm_close(struct snd_pcm_substream *substream) +{ + struct em28xx *dev = snd_pcm_substream_chip(substream); + dev->adev->users--; + + dprintk("closing device\n"); + + dev->mute = 1; + em28xx_audio_analog_set(dev); + + if (dev->adev->users == 0 && dev->adev->shutdown == 1) { + dprintk("audio users: %d\n", dev->adev->users); + dprintk("disabling audio stream!\n"); + dev->adev->shutdown = 0; + dprintk("released lock\n"); + em28xx_cmd(dev, EM28XX_CAPTURE_STREAM_EN, 0); + } + return 0; +} + +static int snd_em28xx_hw_capture_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + unsigned int channels, rate, format; + int ret; + + dprintk("Setting capture parameters\n"); + + ret = snd_pcm_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + format = params_format(hw_params); + rate = params_rate(hw_params); + channels = params_channels(hw_params); + + /* TODO: set up em28xx audio chip to deliver the correct audio format, + current default is 48000hz multiplexed => 96000hz mono + which shouldn't matter since analogue TV only supports mono */ + return 0; +} + +static int snd_em28xx_hw_capture_free(struct snd_pcm_substream *substream) +{ + struct em28xx *dev = snd_pcm_substream_chip(substream); + + dprintk("Stop capture, if needed\n"); + + if (dev->adev->capture_stream == STREAM_ON) + em28xx_cmd(dev, EM28XX_CAPTURE_STREAM_EN, 0); + + return 0; +} + +static int snd_em28xx_prepare(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int snd_em28xx_capture_trigger(struct snd_pcm_substream *substream, + int cmd) +{ + struct em28xx *dev = snd_pcm_substream_chip(substream); + + dprintk("Should %s capture\n", (cmd == SNDRV_PCM_TRIGGER_START)? + "start": "stop"); + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + em28xx_cmd(dev, EM28XX_CAPTURE_STREAM_EN, 1); + return 0; + case SNDRV_PCM_TRIGGER_STOP: + dev->adev->shutdown = 1; + return 0; + default: + return -EINVAL; + } +} + static snd_pcm_uframes_t snd_em28xx_capture_pointer(struct snd_pcm_substream *substream) { struct em28xx *dev; + snd_pcm_uframes_t hwptr_done; dev = snd_pcm_substream_chip(substream); hwptr_done = dev->adev->hwptr_done_capture; + return hwptr_done; } @@ -400,34 +387,38 @@ static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs, unsigned long offset) { void *pageptr = subs->runtime->dma_area + offset; + return vmalloc_to_page(pageptr); } static struct snd_pcm_ops snd_em28xx_pcm_capture = { - .open = snd_em28xx_capture_open, - .close = snd_em28xx_pcm_close, - .ioctl = snd_pcm_lib_ioctl, + .open = snd_em28xx_capture_open, + .close = snd_em28xx_pcm_close, + .ioctl = snd_pcm_lib_ioctl, .hw_params = snd_em28xx_hw_capture_params, - .hw_free = snd_em28xx_hw_capture_free, - .prepare = snd_em28xx_prepare, - .trigger = snd_em28xx_capture_trigger, - .pointer = snd_em28xx_capture_pointer, - .page = snd_pcm_get_vmalloc_page, + .hw_free = snd_em28xx_hw_capture_free, + .prepare = snd_em28xx_prepare, + .trigger = snd_em28xx_capture_trigger, + .pointer = snd_em28xx_capture_pointer, + .page = snd_pcm_get_vmalloc_page, }; static int em28xx_audio_init(struct em28xx *dev) { struct em28xx_audio *adev; - struct snd_pcm *pcm; - struct snd_card *card; - static int devnr; - int ret; - int err; - printk("em28xx-audio.c: probing for em28x1 non standard usbaudio\n"); - printk("em28xx-audio.c: Copyright (C) 2006 Markus Rechberger\n"); + struct snd_pcm *pcm; + struct snd_card *card; + static int devnr; + int ret, err; + + printk(KERN_INFO "em28xx-audio.c: probing for em28x1 " + "non standard usbaudio\n"); + printk(KERN_INFO "em28xx-audio.c: Copyright (C) 2006 Markus " + "Rechberger\n"); + adev = kzalloc(sizeof(*adev), GFP_KERNEL); if (!adev) { - printk("em28xx-audio.c: out of memory\n"); + printk(KERN_ERR "em28xx-audio.c: out of memory\n"); return -1; } card = snd_card_new(index[devnr], "Em28xx Audio", THIS_MODULE, 0); @@ -446,13 +437,15 @@ static int em28xx_audio_init(struct em28xx *dev) strcpy(card->shortname, "Em28xx Audio"); strcpy(card->longname, "Empia Em28xx Audio"); - if ((err = snd_card_register(card)) < 0) { + err = snd_card_register(card); + if (err < 0) { snd_card_free(card); return -ENOMEM; } adev->sndcard = card; adev->udev = dev->udev; dev->adev = adev; + return 0; } @@ -460,16 +453,18 @@ static int em28xx_audio_fini(struct em28xx *dev) { if (dev == NULL) return 0; + if (dev->adev) { snd_card_free(dev->adev->sndcard); kfree(dev->adev); dev->adev = NULL; } + return 0; } static struct em28xx_ops audio_ops = { - .id = EM28XX_AUDIO, + .id = EM28XX_AUDIO, .name = "Em28xx Audio Extension", .init = em28xx_audio_init, .fini = em28xx_audio_fini, @@ -478,7 +473,6 @@ static struct em28xx_ops audio_ops = { static int __init em28xx_alsa_register(void) { request_module("em28xx"); - request_module("tuner"); return em28xx_register_extension(&audio_ops); } @@ -489,6 +483,7 @@ static void __exit em28xx_alsa_unregister(void) MODULE_LICENSE("GPL"); MODULE_AUTHOR("Markus Rechberger <mrechberger@gmail.com>"); +MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@infradead.org>"); MODULE_DESCRIPTION("Em28xx Audio driver"); module_init(em28xx_alsa_register); |