diff options
Diffstat (limited to 'sound/core/isadma.c')
-rw-r--r-- | sound/core/isadma.c | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/sound/core/isadma.c b/sound/core/isadma.c new file mode 100644 index 000000000..28768061d --- /dev/null +++ b/sound/core/isadma.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ISA DMA support functions + * Copyright (c) by Jaroslav Kysela <perex@perex.cz> + */ + +/* + * Defining following add some delay. Maybe this helps for some broken + * ISA DMA controllers. + */ + +#undef HAVE_REALLY_SLOW_DMA_CONTROLLER + +#include <linux/export.h> +#include <linux/isa-dma.h> +#include <sound/core.h> + +/** + * snd_dma_program - program an ISA DMA transfer + * @dma: the dma number + * @addr: the physical address of the buffer + * @size: the DMA transfer size + * @mode: the DMA transfer mode, DMA_MODE_XXX + * + * Programs an ISA DMA transfer for the given buffer. + */ +void snd_dma_program(unsigned long dma, + unsigned long addr, unsigned int size, + unsigned short mode) +{ + unsigned long flags; + + flags = claim_dma_lock(); + disable_dma(dma); + clear_dma_ff(dma); + set_dma_mode(dma, mode); + set_dma_addr(dma, addr); + set_dma_count(dma, size); + if (!(mode & DMA_MODE_NO_ENABLE)) + enable_dma(dma); + release_dma_lock(flags); +} +EXPORT_SYMBOL(snd_dma_program); + +/** + * snd_dma_disable - stop the ISA DMA transfer + * @dma: the dma number + * + * Stops the ISA DMA transfer. + */ +void snd_dma_disable(unsigned long dma) +{ + unsigned long flags; + + flags = claim_dma_lock(); + clear_dma_ff(dma); + disable_dma(dma); + release_dma_lock(flags); +} +EXPORT_SYMBOL(snd_dma_disable); + +/** + * snd_dma_pointer - return the current pointer to DMA transfer buffer in bytes + * @dma: the dma number + * @size: the dma transfer size + * + * Return: The current pointer in DMA transfer buffer in bytes. + */ +unsigned int snd_dma_pointer(unsigned long dma, unsigned int size) +{ + unsigned long flags; + unsigned int result, result1; + + flags = claim_dma_lock(); + clear_dma_ff(dma); + if (!isa_dma_bridge_buggy) + disable_dma(dma); + result = get_dma_residue(dma); + /* + * HACK - read the counter again and choose higher value in order to + * avoid reading during counter lower byte roll over if the + * isa_dma_bridge_buggy is set. + */ + result1 = get_dma_residue(dma); + if (!isa_dma_bridge_buggy) + enable_dma(dma); + release_dma_lock(flags); + if (unlikely(result < result1)) + result = result1; +#ifdef CONFIG_SND_DEBUG + if (result > size) + pr_err("ALSA: pointer (0x%x) for DMA #%ld is greater than transfer size (0x%x)\n", result, dma, size); +#endif + if (result >= size || result == 0) + return 0; + else + return size - result; +} +EXPORT_SYMBOL(snd_dma_pointer); + +struct snd_dma_data { + int dma; +}; + +static void __snd_release_dma(struct device *dev, void *data) +{ + struct snd_dma_data *p = data; + + snd_dma_disable(p->dma); + free_dma(p->dma); +} + +/** + * snd_devm_request_dma - the managed version of request_dma() + * @dev: the device pointer + * @dma: the dma number + * @name: the name string of the requester + * + * The requested DMA will be automatically released at unbinding via devres. + * + * Return: zero on success, or a negative error code + */ +int snd_devm_request_dma(struct device *dev, int dma, const char *name) +{ + struct snd_dma_data *p; + + if (request_dma(dma, name)) + return -EBUSY; + p = devres_alloc(__snd_release_dma, sizeof(*p), GFP_KERNEL); + if (!p) { + free_dma(dma); + return -ENOMEM; + } + p->dma = dma; + devres_add(dev, p); + return 0; +} +EXPORT_SYMBOL_GPL(snd_devm_request_dma); |