/*
* Digital Audio (PCM) abstract layer
* Copyright (c) by Jaroslav Kysela <perex@suse.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
*
*/
#define __NO_VERSION__
#include <sound/driver.h>
#include <asm/io.h>
#include <linux/time.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/info.h>
#include <sound/initval.h>
static int snd_preallocate_dma = 1;
MODULE_PARM(snd_preallocate_dma, "i");
MODULE_PARM_DESC(snd_preallocate_dma, "Preallocate DMA memory when the PCM devices are initialized.");
MODULE_PARM_SYNTAX(snd_preallocate_dma, SNDRV_BOOLEAN_TRUE_DESC);
static int snd_maximum_substreams = 4;
MODULE_PARM(snd_maximum_substreams, "i");
MODULE_PARM_DESC(snd_maximum_substreams, "Maximum substreams with preallocated DMA memory.");
MODULE_PARM_SYNTAX(snd_maximum_substreams, SNDRV_BOOLEAN_TRUE_DESC);
static int snd_minimum_buffer = 16384;
static void snd_pcm_lib_preallocate_dma_free(snd_pcm_substream_t *substream)
{
if (substream->dma_area == NULL)
return;
switch (substream->dma_type) {
case SNDRV_PCM_DMA_TYPE_CONTINUOUS:
snd_free_pages(substream->dma_area, substream->dma_bytes);
break;
#ifdef CONFIG_ISA
case SNDRV_PCM_DMA_TYPE_ISA:
snd_free_isa_pages(substream->dma_bytes, substream->dma_area, substream->dma_addr);
break;
#endif
#ifdef CONFIG_PCI
case SNDRV_PCM_DMA_TYPE_PCI:
snd_free_pci_pages((struct pci_dev *)substream->dma_private, substream->dma_bytes, substream->dma_area, substream->dma_addr);
break;
#endif
}
substream->dma_area = NULL;
}
int snd_pcm_lib_preallocate_free(snd_pcm_substream_t *substream)
{
snd_pcm_lib_preallocate_dma_free(substream);
if (substream->proc_prealloc_entry) {
snd_info_unregister(substream->proc_prealloc_entry);
substream->proc_prealloc_entry = NULL;
}
return 0;
}
int snd_pcm_lib_preallocate_free_for_all(snd_pcm_t *pcm)
{
snd_pcm_substream_t *substream;
int stream;
for (stream = 0; stream < 2; stream++)
for (substream = pcm->streams[stream].substream; substream; substream = substream->next)
snd_pcm_lib_preallocate_free(substream);
return 0;
}
static void snd_pcm_lib_preallocate_proc_read(snd_info_entry_t *entry,
snd_info_buffer_t *buffer)
{
snd_pcm_substream_t *substream = (snd_pcm_substream_t *)entry->private_data;
snd_iprintf(buffer, "%lu\n", (unsigned long) substream->dma_bytes / 1024);
}
static void snd_pcm_lib_preallocate_proc_write(snd_info_entry_t *entry,
snd_info_buffer_t *buffer)
{
snd_pcm_substream_t *substream = (snd_pcm_substream_t *)entry->private_data;
char line[64], str[64];
size_t size;
void *dma_area;
dma_addr_t dma_addr;
if (substream->runtime) {
buffer->error = -EBUSY;
return;
}
if (!snd_info_get_line(buffer, line, sizeof(line))) {
snd_info_get_str(str, line, sizeof(str));
size = simple_strtoul(str, NULL, 10) * 1024;
if ((size != 0 && size < 8192) || size > substream->dma_max) {
buffer->error = -EINVAL;
return;
}
if (substream->dma_bytes == size)
return;
if (size > 0) {
switch (substream->dma_type) {
case SNDRV_PCM_DMA_TYPE_CONTINUOUS:
dma_area = snd_malloc_pages(size, (unsigned int)((unsigned long)substream->dma_private & 0xffffffff));
dma_addr = 0UL; /* not valid */
break;
#ifdef CONFIG_ISA
case SNDRV_PCM_DMA_TYPE_ISA:
dma_area = snd_malloc_isa_pages(size, &dma_addr);
break;
#endif
#ifdef CONFIG_PCI
case SNDRV_PCM_DMA_TYPE_PCI:
dma_area = snd_malloc_pci_pages((struct pci_dev *)substream->dma_private, size, &dma_addr);
break;
#endif
default:
dma_area = NULL;
dma_addr = 0UL;
}
if (dma_area == NULL) {
buffer->error = -ENOMEM;
return;
}
substream->buffer_bytes_max = size;
} else {
dma_area = NULL;
substream->buffer_bytes_max = UINT_MAX;
}
snd_pcm_lib_preallocate_dma_free(substream);
substream->dma_area = dma_area;
substream->dma_addr = dma_addr;
substream->dma_bytes = size;
} else {
buffer->error = -EINVAL;
}
}
static int snd_pcm_lib_preallocate_pages1(snd_pcm_substream_t *substream,
size_t size, size_t max)
{
unsigned long rsize = 0;
void *dma_area = NULL;
dma_addr_t dma_addr = 0UL;
snd_info_entry_t *entry;
if (!size || !snd_preallocate_dma || substream->number >= snd_maximum_substreams) {
size = 0;
} else {
switch (substream->dma_type) {
case SNDRV_PCM_DMA_TYPE_CONTINUOUS:
dma_area = snd_malloc_pages_fallback(size, (unsigned int)((unsigned long)substream->dma_private & 0xffffffff), &rsize);
dma_addr = 0UL; /* not valid */
break;
#ifdef CONFIG_ISA
case SNDRV_PCM_DMA_TYPE_ISA:
dma_area = snd_malloc_isa_pages_fallback(size, &dma_addr, &rsize);
break;
#endif
#ifdef CONFIG_PCI
case SNDRV_PCM_DMA_TYPE_PCI:
dma_area = snd_malloc_pci_pages_fallback((struct pci_dev *)substream->dma_private, size, &dma_addr, &rsize);
break;
#endif
default:
size = 0;
}
if (rsize < snd_minimum_buffer) {
snd_pcm_lib_preallocate_dma_free(substream);
size = 0;
}
}
substream->dma_area = dma_area;
substream->dma_addr = dma_addr;
substream->dma_bytes = rsize;
if (rsize > 0)
substream->buffer_bytes_max = rsize;
substream->dma_max = max;
if ((entry = snd_info_create_card_entry(substream->pcm->card, "prealloc", substream->proc_root)) != NULL) {
entry->c.text.read_size = 64;
entry->c.text.read = snd_pcm_lib_preallocate_proc_read;
entry->c.text.write_size = 64;
entry->c.text.write = snd_pcm_lib_preallocate_proc_write;
entry->private_data = substream;
if (snd_info_register(entry) < 0) {
snd_info_free_entry(entry);
entry = NULL;
}
}
substream->proc_prealloc_entry = entry;
return 0;
}
int snd_pcm_lib_preallocate_pages(snd_pcm_substream_t *substream,
size_t size, size_t max,
unsigned int flags)
{
substream->dma_type = SNDRV_PCM_DMA_TYPE_CONTINUOUS;
substream->dma_private = (void *)(unsigned long)flags;
return snd_pcm_lib_preallocate_pages1(substream, size, max);
}
int snd_pcm_lib_preallocate_pages_for_all(snd_pcm_t *pcm,
size_t size, size_t max,
unsigned int flags)
{
snd_pcm_substream_t *substream;
int stream, err;
for (stream = 0; stream < 2; stream++)
for (substream = pcm->streams[stream].substream; substream; substream = substream->next)
if ((err = snd_pcm_lib_preallocate_pages(substream, size, max, flags)) < 0)
return err;
return 0;
}
#ifdef CONFIG_ISA
int snd_pcm_lib_preallocate_isa_pages(snd_pcm_substream_t *substream,
size_t size, size_t max)
{
substream->dma_type = SNDRV_PCM_DMA_TYPE_ISA;
substream->dma_private = NULL;
return snd_pcm_lib_preallocate_pages1(substream, size, max);
}
int snd_pcm_lib_preallocate_isa_pages_for_all(snd_pcm_t *pcm,
size_t size, size_t max)
{
snd_pcm_substream_t *substream;
int stream, err;
for (stream = 0; stream < 2; stream++)
for (substream = pcm->streams[stream].substream; substream; substream = substream->next)
if ((err = snd_pcm_lib_preallocate_isa_pages(substream, size, max)) < 0)
return err;
return 0;
}
#endif /* CONFIG_ISA */
int snd_pcm_lib_malloc_pages(snd_pcm_substream_t *substream, size_t size)
{
snd_pcm_runtime_t *runtime;
void *dma_area = NULL;
dma_addr_t dma_addr = 0UL;
snd_assert(substream != NULL, return -EINVAL);
runtime = substream->runtime;
snd_assert(runtime != NULL, return -EINVAL);
if (runtime->dma_area != NULL) {
/* perphaps, we might free the large DMA memory region
to save some space here, but the actual solution
costs us less time */
if (runtime->dma_bytes >= size)
return 0; /* ok, do not change */
snd_pcm_lib_free_pages(substream);
}
if (substream->dma_area != NULL && substream->dma_bytes >= size) {
dma_area = substream->dma_area;
dma_addr = substream->dma_addr;
} else {
switch (substream->dma_type) {
case SNDRV_PCM_DMA_TYPE_CONTINUOUS:
dma_area = snd_malloc_pages(size, (unsigned int)((unsigned long)substream->dma_private & 0xffffffff));
dma_addr = 0UL; /* not valid */
break;
#ifdef CONFIG_ISA
case SNDRV_PCM_DMA_TYPE_ISA:
dma_area = snd_malloc_isa_pages(size, &dma_addr);
break;
#endif
#ifdef CONFIG_PCI
case SNDRV_PCM_DMA_TYPE_PCI:
dma_area = snd_malloc_pci_pages((struct pci_dev *)substream->dma_private, size, &dma_addr);
break;
#endif
default:
return -ENXIO;
}
}
if (! dma_area)
return -ENOMEM;
runtime->dma_area = dma_area;
runtime->dma_addr = dma_addr;
runtime->dma_bytes = size;
return 1; /* area was changed */
}
int snd_pcm_lib_free_pages(snd_pcm_substream_t *substream)
{
snd_pcm_runtime_t *runtime;
snd_assert(substream != NULL, return -EINVAL);
runtime = substream->runtime;
snd_assert(runtime != NULL, return -EINVAL);
if (runtime->dma_area == NULL)
return 0;
if (runtime->dma_area != substream->dma_area) {
switch (substream->dma_type) {
#ifdef CONFIG_ISA
case SNDRV_PCM_DMA_TYPE_ISA:
snd_free_isa_pages(runtime->dma_bytes, runtime->dma_area, runtime->dma_addr);
break;
#endif
#ifdef CONFIG_PCI
case SNDRV_PCM_DMA_TYPE_PCI:
snd_free_pci_pages((struct pci_dev *)substream->dma_private, runtime->dma_bytes, runtime->dma_area, runtime->dma_addr);
break;
#endif
}
}
runtime->dma_area = NULL;
runtime->dma_addr = 0UL;
runtime->dma_bytes = 0;
return 0;
}
#ifdef CONFIG_PCI
int snd_pcm_lib_preallocate_pci_pages(struct pci_dev *pci,
snd_pcm_substream_t *substream,
size_t size, size_t max)
{
substream->dma_type = SNDRV_PCM_DMA_TYPE_PCI;
substream->dma_private = pci;
return snd_pcm_lib_preallocate_pages1(substream, size, max);
}
int snd_pcm_lib_preallocate_pci_pages_for_all(struct pci_dev *pci,
snd_pcm_t *pcm,
size_t size, size_t max)
{
snd_pcm_substream_t *substream;
int stream, err;
for (stream = 0; stream < 2; stream++)
for (substream = pcm->streams[stream].substream; substream; substream = substream->next)
if ((err = snd_pcm_lib_preallocate_pci_pages(pci, substream, size, max)) < 0)
return err;
return 0;
}
#endif /* CONFIG_PCI */