summaryrefslogtreecommitdiff
path: root/drivers/usb/gadget/u_audio.c
diff options
context:
space:
mode:
authorBryan Wu <cooloney@kernel.org>2009-06-03 17:17:58 +0400
committerGreg Kroah-Hartman <gregkh@suse.de>2009-06-16 08:44:47 +0400
commitc6994e6f067cf0fc4c6cca3d164018b1150916f8 (patch)
treed971c529cbd4684e5c1c5fcb67f3f09d1cca4295 /drivers/usb/gadget/u_audio.c
parentc47d7b09891abb4f8b222317c89c7469bed8db3a (diff)
downloadlinux-c6994e6f067cf0fc4c6cca3d164018b1150916f8.tar.xz
USB: gadget: add USB Audio Gadget driver
Funtions added: - setup all the USB audio class device descriptors - handle class specific setup request - receive data from USB host by ISO transfer - play audio data by ALSA sound card - open and setup playback PCM interface - set default playback PCM parameters - provide playback functions for USB audio driver - provide PCM parameters set/get functions Test on: - Host: Ubuntu 8.10, kernel 2.6.27 - Gadget: EZKIT-BF548 with ASoC AD1980 codec Todo: - add real Mute control code - add real Volume control code - maybe find another way to replace dynamic buffer handling with static buffer allocation - test on Windows system - provide control interface to handle mute/volume control - provide capture interface in the future - test on BF527, other USB device controler and other audio codec Signed-off-by: Bryan Wu <cooloney@kernel.org> Signed-off-by: Mike Frysinger <vapier@gentoo.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/gadget/u_audio.c')
-rw-r--r--drivers/usb/gadget/u_audio.c319
1 files changed, 319 insertions, 0 deletions
diff --git a/drivers/usb/gadget/u_audio.c b/drivers/usb/gadget/u_audio.c
new file mode 100644
index 000000000000..0f3d22fc030e
--- /dev/null
+++ b/drivers/usb/gadget/u_audio.c
@@ -0,0 +1,319 @@
+/*
+ * u_audio.c -- ALSA audio utilities for Gadget stack
+ *
+ * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org>
+ * Copyright (C) 2008 Analog Devices, Inc
+ *
+ * Enter bugs at http://blackfin.uclinux.org/
+ *
+ * Licensed under the GPL-2 or later.
+ */
+
+#include <linux/kernel.h>
+#include <linux/utsname.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/ctype.h>
+#include <linux/random.h>
+#include <linux/syscalls.h>
+
+#include "u_audio.h"
+
+/*
+ * This component encapsulates the ALSA devices for USB audio gadget
+ */
+
+#define FILE_PCM_PLAYBACK "/dev/snd/pcmC0D0p"
+#define FILE_PCM_CAPTURE "/dev/snd/pcmC0D0c"
+#define FILE_CONTROL "/dev/snd/controlC0"
+
+static char *fn_play = FILE_PCM_PLAYBACK;
+module_param(fn_play, charp, S_IRUGO);
+MODULE_PARM_DESC(fn_play, "Playback PCM device file name");
+
+static char *fn_cap = FILE_PCM_CAPTURE;
+module_param(fn_cap, charp, S_IRUGO);
+MODULE_PARM_DESC(fn_cap, "Capture PCM device file name");
+
+static char *fn_cntl = FILE_CONTROL;
+module_param(fn_cntl, charp, S_IRUGO);
+MODULE_PARM_DESC(fn_cntl, "Control device file name");
+
+/*-------------------------------------------------------------------------*/
+
+/**
+ * Some ALSA internal helper functions
+ */
+static int snd_interval_refine_set(struct snd_interval *i, unsigned int val)
+{
+ struct snd_interval t;
+ t.empty = 0;
+ t.min = t.max = val;
+ t.openmin = t.openmax = 0;
+ t.integer = 1;
+ return snd_interval_refine(i, &t);
+}
+
+static int _snd_pcm_hw_param_set(struct snd_pcm_hw_params *params,
+ snd_pcm_hw_param_t var, unsigned int val,
+ int dir)
+{
+ int changed;
+ if (hw_is_mask(var)) {
+ struct snd_mask *m = hw_param_mask(params, var);
+ if (val == 0 && dir < 0) {
+ changed = -EINVAL;
+ snd_mask_none(m);
+ } else {
+ if (dir > 0)
+ val++;
+ else if (dir < 0)
+ val--;
+ changed = snd_mask_refine_set(
+ hw_param_mask(params, var), val);
+ }
+ } else if (hw_is_interval(var)) {
+ struct snd_interval *i = hw_param_interval(params, var);
+ if (val == 0 && dir < 0) {
+ changed = -EINVAL;
+ snd_interval_none(i);
+ } else if (dir == 0)
+ changed = snd_interval_refine_set(i, val);
+ else {
+ struct snd_interval t;
+ t.openmin = 1;
+ t.openmax = 1;
+ t.empty = 0;
+ t.integer = 0;
+ if (dir < 0) {
+ t.min = val - 1;
+ t.max = val;
+ } else {
+ t.min = val;
+ t.max = val+1;
+ }
+ changed = snd_interval_refine(i, &t);
+ }
+ } else
+ return -EINVAL;
+ if (changed) {
+ params->cmask |= 1 << var;
+ params->rmask |= 1 << var;
+ }
+ return changed;
+}
+/*-------------------------------------------------------------------------*/
+
+/**
+ * Set default hardware params
+ */
+static int playback_default_hw_params(struct gaudio_snd_dev *snd)
+{
+ struct snd_pcm_substream *substream = snd->substream;
+ struct snd_pcm_hw_params *params;
+ snd_pcm_sframes_t result;
+
+ /*
+ * SNDRV_PCM_ACCESS_RW_INTERLEAVED,
+ * SNDRV_PCM_FORMAT_S16_LE
+ * CHANNELS: 2
+ * RATE: 48000
+ */
+ snd->access = SNDRV_PCM_ACCESS_RW_INTERLEAVED;
+ snd->format = SNDRV_PCM_FORMAT_S16_LE;
+ snd->channels = 2;
+ snd->rate = 48000;
+
+ params = kzalloc(sizeof(*params), GFP_KERNEL);
+ if (!params)
+ return -ENOMEM;
+
+ _snd_pcm_hw_params_any(params);
+ _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS,
+ snd->access, 0);
+ _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT,
+ snd->format, 0);
+ _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS,
+ snd->channels, 0);
+ _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE,
+ snd->rate, 0);
+
+ snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL);
+ snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_HW_PARAMS, params);
+
+ result = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_PREPARE, NULL);
+ if (result < 0) {
+ ERROR(snd->card,
+ "Preparing sound card failed: %d\n", (int)result);
+ kfree(params);
+ return result;
+ }
+
+ /* Store the hardware parameters */
+ snd->access = params_access(params);
+ snd->format = params_format(params);
+ snd->channels = params_channels(params);
+ snd->rate = params_rate(params);
+
+ kfree(params);
+
+ INFO(snd->card,
+ "Hardware params: access %x, format %x, channels %d, rate %d\n",
+ snd->access, snd->format, snd->channels, snd->rate);
+
+ return 0;
+}
+
+/**
+ * Playback audio buffer data by ALSA PCM device
+ */
+static size_t u_audio_playback(struct gaudio *card, void *buf, size_t count)
+{
+ struct gaudio_snd_dev *snd = &card->playback;
+ struct snd_pcm_substream *substream = snd->substream;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ mm_segment_t old_fs;
+ ssize_t result;
+ snd_pcm_sframes_t frames;
+
+try_again:
+ if (runtime->status->state == SNDRV_PCM_STATE_XRUN ||
+ runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) {
+ result = snd_pcm_kernel_ioctl(substream,
+ SNDRV_PCM_IOCTL_PREPARE, NULL);
+ if (result < 0) {
+ ERROR(card, "Preparing sound card failed: %d\n",
+ (int)result);
+ return result;
+ }
+ }
+
+ frames = bytes_to_frames(runtime, count);
+ old_fs = get_fs();
+ set_fs(KERNEL_DS);
+ result = snd_pcm_lib_write(snd->substream, buf, frames);
+ if (result != frames) {
+ ERROR(card, "Playback error: %d\n", (int)result);
+ set_fs(old_fs);
+ goto try_again;
+ }
+ set_fs(old_fs);
+
+ return 0;
+}
+
+static int u_audio_get_playback_channels(struct gaudio *card)
+{
+ return card->playback.channels;
+}
+
+static int u_audio_get_playback_rate(struct gaudio *card)
+{
+ return card->playback.rate;
+}
+
+/**
+ * Open ALSA PCM and control device files
+ * Initial the PCM or control device
+ */
+static int gaudio_open_snd_dev(struct gaudio *card)
+{
+ struct snd_pcm_file *pcm_file;
+ struct gaudio_snd_dev *snd;
+
+ if (!card)
+ return -ENODEV;
+
+ /* Open control device */
+ snd = &card->control;
+ snd->filp = filp_open(fn_cntl, O_RDWR, 0);
+ if (IS_ERR(snd->filp)) {
+ int ret = PTR_ERR(snd->filp);
+ ERROR(card, "unable to open sound control device file: %s\n",
+ fn_cntl);
+ snd->filp = NULL;
+ return ret;
+ }
+ snd->card = card;
+
+ /* Open PCM playback device and setup substream */
+ snd = &card->playback;
+ snd->filp = filp_open(fn_play, O_WRONLY, 0);
+ if (IS_ERR(snd->filp)) {
+ ERROR(card, "No such PCM playback device: %s\n", fn_play);
+ snd->filp = NULL;
+ }
+ pcm_file = snd->filp->private_data;
+ snd->substream = pcm_file->substream;
+ snd->card = card;
+ playback_default_hw_params(snd);
+
+ /* Open PCM capture device and setup substream */
+ snd = &card->capture;
+ snd->filp = filp_open(fn_cap, O_RDONLY, 0);
+ if (IS_ERR(snd->filp)) {
+ ERROR(card, "No such PCM capture device: %s\n", fn_cap);
+ snd->filp = NULL;
+ }
+ pcm_file = snd->filp->private_data;
+ snd->substream = pcm_file->substream;
+ snd->card = card;
+
+ return 0;
+}
+
+/**
+ * Close ALSA PCM and control device files
+ */
+static int gaudio_close_snd_dev(struct gaudio *gau)
+{
+ struct gaudio_snd_dev *snd;
+
+ /* Close control device */
+ snd = &gau->control;
+ if (!IS_ERR(snd->filp))
+ filp_close(snd->filp, current->files);
+
+ /* Close PCM playback device and setup substream */
+ snd = &gau->playback;
+ if (!IS_ERR(snd->filp))
+ filp_close(snd->filp, current->files);
+
+ /* Close PCM capture device and setup substream */
+ snd = &gau->capture;
+ if (!IS_ERR(snd->filp))
+ filp_close(snd->filp, current->files);
+
+ return 0;
+}
+
+/**
+ * gaudio_setup - setup ALSA interface and preparing for USB transfer
+ *
+ * This sets up PCM, mixer or MIDI ALSA devices fore USB gadget using.
+ *
+ * Returns negative errno, or zero on success
+ */
+int __init gaudio_setup(struct gaudio *card)
+{
+ int ret;
+
+ ret = gaudio_open_snd_dev(card);
+ if (ret)
+ ERROR(card, "we need at least one control device\n");
+
+ return ret;
+
+}
+
+/**
+ * gaudio_cleanup - remove ALSA device interface
+ *
+ * This is called to free all resources allocated by @gaudio_setup().
+ */
+void gaudio_cleanup(struct gaudio *card)
+{
+ if (card)
+ gaudio_close_snd_dev(card);
+}
+