/*
* Digital Audio (PCM) abstract layer
* Copyright (c) by Jaroslav Kysela <perex@perex.cz>
*
*
* 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/module.h>
#include <linux/time.h>
#include <linux/mutex.h>
#include <linux/device.h>
#include <sound/core.h>
#include <sound/minors.h>
#include <sound/pcm.h>
#include <sound/control.h>
#include <sound/info.h>
MODULE_AUTHOR("Jaroslav Kysela <perex@perex.cz>, Abramo Bagnara <abramo@alsa-project.org>");
MODULE_DESCRIPTION("Midlevel PCM code for ALSA.");
MODULE_LICENSE("GPL");
static LIST_HEAD(snd_pcm_devices);
static LIST_HEAD(snd_pcm_notify_list);
static DEFINE_MUTEX(register_mutex);
static int snd_pcm_free(struct snd_pcm *pcm);
static int snd_pcm_dev_free(struct snd_device *device);
static int snd_pcm_dev_register(struct snd_device *device);
static int snd_pcm_dev_disconnect(struct snd_device *device);
static struct snd_pcm *snd_pcm_get(struct snd_card *card, int device)
{
struct snd_pcm *pcm;
list_for_each_entry(pcm, &snd_pcm_devices, list) {
if (pcm->card == card && pcm->device == device)
return pcm;
}
return NULL;
}
static int snd_pcm_next(struct snd_card *card, int device)
{
struct snd_pcm *pcm;
list_for_each_entry(pcm, &snd_pcm_devices, list) {
if (pcm->card == card && pcm->device > device)
return pcm->device;
else if (pcm->card->number > card->number)
return -1;
}
return -1;
}
static int snd_pcm_add(struct snd_pcm *newpcm)
{
struct snd_pcm *pcm;
list_for_each_entry(pcm, &snd_pcm_devices, list) {
if (pcm->card == newpcm->card && pcm->device == newpcm->device)
return -EBUSY;
if (pcm->card->number > newpcm->card->number ||
(pcm->card == newpcm->card &&
pcm->device > newpcm->device)) {
list_add(&newpcm->list, pcm->list.prev);
return 0;
}
}
list_add_tail(&newpcm->list, &snd_pcm_devices);
return 0;
}
static int snd_pcm_control_ioctl(struct snd_card *card,
struct snd_ctl_file *control,
unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case SNDRV_CTL_IOCTL_PCM_NEXT_DEVICE:
{
int device;
if (get_user(device, (int __user *)arg))
return -EFAULT;
mutex_lock(®ister_mutex);
device = snd_pcm_next(card, device);
mutex_unlock(®ister_mutex);
if (put_user(device, (int __user *)arg))
return -EFAULT;
return 0;
}
case SNDRV_CTL_IOCTL_PCM_INFO:
{
struct snd_pcm_info __user *info;
unsigned int device, subdevice;
int stream;
struct snd_pcm *pcm;
struct snd_pcm_str *pstr;
struct snd_pcm_substream *substream;
int err;
info = (struct snd_pcm_info __user *)arg;
if (get_user(device, &info->device))
return -EFAULT;
if (get_user(stream, &info->stream))
return -EFAULT;
if (stream < 0 || stream > 1)
return -EINVAL;
if (get_user(subdevice, &info->subdevice))
return -EFAULT;
mutex_lock(®ister_mutex);
pcm = snd_pcm_get(card, device);
if (pcm == NULL) {
err = -ENXIO;
goto _error;
}
pstr = &pcm->streams[stream];
if (pstr->substream_count == 0) {
err = -ENOENT;
goto _error;
}
if (subdevice >= pstr->substream_count) {
err = -ENXIO;
goto _error;
}
for (substream = pstr->substream; substream;
substream = substream->next)
if (substream->number == (int)subdevice)
break;
if (substream == NULL) {
err = -ENXIO;
goto _error;
}
err = snd_pcm_info_user(substream, info);
_error:
mutex_unlock(®ister_mutex);
return err;
}
case SNDRV_CTL_IOCTL_PCM_PREFER_SUBDEVICE:
{
int val;
if (get_user(val, (int __user *)arg))
return -EFAULT;
control->prefer_pcm_subdevice = val;
return 0;
}
}
return -ENOIOCTLCMD;
}
#define FORMAT(v) [SNDRV_PCM_FORMAT_##v] = #v
static char *snd_pcm_format_names[] = {
FORMAT(S8),
FORMAT(U8),
FORMAT(S16_LE),
FORMAT(S16_BE),
FORMAT(U16_LE),
FORMAT(U16_BE),
FORMAT(S24_LE),
FORMAT(S24_BE),
FORMAT(U24_LE),
FORMAT(U24_BE),
FORMAT(S32_LE),
FORMAT(S32_BE),
FORMAT(U32_LE),
FORMAT(U32_BE),
FORMAT(FLOAT_LE),
FORMAT(FLOAT_BE),
FORMAT(FLOAT64_LE),
FORMAT(FLOAT64_BE),
FORMAT(IEC958_SUBFRAME_LE
|