162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *  ISA DMA support functions
462306a36Sopenharmony_ci *  Copyright (c) by Jaroslav Kysela <perex@perex.cz>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci/*
862306a36Sopenharmony_ci * Defining following add some delay. Maybe this helps for some broken
962306a36Sopenharmony_ci * ISA DMA controllers.
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#undef HAVE_REALLY_SLOW_DMA_CONTROLLER
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#include <linux/export.h>
1562306a36Sopenharmony_ci#include <linux/isa-dma.h>
1662306a36Sopenharmony_ci#include <sound/core.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci/**
1962306a36Sopenharmony_ci * snd_dma_program - program an ISA DMA transfer
2062306a36Sopenharmony_ci * @dma: the dma number
2162306a36Sopenharmony_ci * @addr: the physical address of the buffer
2262306a36Sopenharmony_ci * @size: the DMA transfer size
2362306a36Sopenharmony_ci * @mode: the DMA transfer mode, DMA_MODE_XXX
2462306a36Sopenharmony_ci *
2562306a36Sopenharmony_ci * Programs an ISA DMA transfer for the given buffer.
2662306a36Sopenharmony_ci */
2762306a36Sopenharmony_civoid snd_dma_program(unsigned long dma,
2862306a36Sopenharmony_ci		     unsigned long addr, unsigned int size,
2962306a36Sopenharmony_ci                     unsigned short mode)
3062306a36Sopenharmony_ci{
3162306a36Sopenharmony_ci	unsigned long flags;
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci	flags = claim_dma_lock();
3462306a36Sopenharmony_ci	disable_dma(dma);
3562306a36Sopenharmony_ci	clear_dma_ff(dma);
3662306a36Sopenharmony_ci	set_dma_mode(dma, mode);
3762306a36Sopenharmony_ci	set_dma_addr(dma, addr);
3862306a36Sopenharmony_ci	set_dma_count(dma, size);
3962306a36Sopenharmony_ci	if (!(mode & DMA_MODE_NO_ENABLE))
4062306a36Sopenharmony_ci		enable_dma(dma);
4162306a36Sopenharmony_ci	release_dma_lock(flags);
4262306a36Sopenharmony_ci}
4362306a36Sopenharmony_ciEXPORT_SYMBOL(snd_dma_program);
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci/**
4662306a36Sopenharmony_ci * snd_dma_disable - stop the ISA DMA transfer
4762306a36Sopenharmony_ci * @dma: the dma number
4862306a36Sopenharmony_ci *
4962306a36Sopenharmony_ci * Stops the ISA DMA transfer.
5062306a36Sopenharmony_ci */
5162306a36Sopenharmony_civoid snd_dma_disable(unsigned long dma)
5262306a36Sopenharmony_ci{
5362306a36Sopenharmony_ci	unsigned long flags;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	flags = claim_dma_lock();
5662306a36Sopenharmony_ci	clear_dma_ff(dma);
5762306a36Sopenharmony_ci	disable_dma(dma);
5862306a36Sopenharmony_ci	release_dma_lock(flags);
5962306a36Sopenharmony_ci}
6062306a36Sopenharmony_ciEXPORT_SYMBOL(snd_dma_disable);
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci/**
6362306a36Sopenharmony_ci * snd_dma_pointer - return the current pointer to DMA transfer buffer in bytes
6462306a36Sopenharmony_ci * @dma: the dma number
6562306a36Sopenharmony_ci * @size: the dma transfer size
6662306a36Sopenharmony_ci *
6762306a36Sopenharmony_ci * Return: The current pointer in DMA transfer buffer in bytes.
6862306a36Sopenharmony_ci */
6962306a36Sopenharmony_ciunsigned int snd_dma_pointer(unsigned long dma, unsigned int size)
7062306a36Sopenharmony_ci{
7162306a36Sopenharmony_ci	unsigned long flags;
7262306a36Sopenharmony_ci	unsigned int result, result1;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	flags = claim_dma_lock();
7562306a36Sopenharmony_ci	clear_dma_ff(dma);
7662306a36Sopenharmony_ci	if (!isa_dma_bridge_buggy)
7762306a36Sopenharmony_ci		disable_dma(dma);
7862306a36Sopenharmony_ci	result = get_dma_residue(dma);
7962306a36Sopenharmony_ci	/*
8062306a36Sopenharmony_ci	 * HACK - read the counter again and choose higher value in order to
8162306a36Sopenharmony_ci	 * avoid reading during counter lower byte roll over if the
8262306a36Sopenharmony_ci	 * isa_dma_bridge_buggy is set.
8362306a36Sopenharmony_ci	 */
8462306a36Sopenharmony_ci	result1 = get_dma_residue(dma);
8562306a36Sopenharmony_ci	if (!isa_dma_bridge_buggy)
8662306a36Sopenharmony_ci		enable_dma(dma);
8762306a36Sopenharmony_ci	release_dma_lock(flags);
8862306a36Sopenharmony_ci	if (unlikely(result < result1))
8962306a36Sopenharmony_ci		result = result1;
9062306a36Sopenharmony_ci#ifdef CONFIG_SND_DEBUG
9162306a36Sopenharmony_ci	if (result > size)
9262306a36Sopenharmony_ci		pr_err("ALSA: pointer (0x%x) for DMA #%ld is greater than transfer size (0x%x)\n", result, dma, size);
9362306a36Sopenharmony_ci#endif
9462306a36Sopenharmony_ci	if (result >= size || result == 0)
9562306a36Sopenharmony_ci		return 0;
9662306a36Sopenharmony_ci	else
9762306a36Sopenharmony_ci		return size - result;
9862306a36Sopenharmony_ci}
9962306a36Sopenharmony_ciEXPORT_SYMBOL(snd_dma_pointer);
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistruct snd_dma_data {
10262306a36Sopenharmony_ci	int dma;
10362306a36Sopenharmony_ci};
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_cistatic void __snd_release_dma(struct device *dev, void *data)
10662306a36Sopenharmony_ci{
10762306a36Sopenharmony_ci	struct snd_dma_data *p = data;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	snd_dma_disable(p->dma);
11062306a36Sopenharmony_ci	free_dma(p->dma);
11162306a36Sopenharmony_ci}
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci/**
11462306a36Sopenharmony_ci * snd_devm_request_dma - the managed version of request_dma()
11562306a36Sopenharmony_ci * @dev: the device pointer
11662306a36Sopenharmony_ci * @dma: the dma number
11762306a36Sopenharmony_ci * @name: the name string of the requester
11862306a36Sopenharmony_ci *
11962306a36Sopenharmony_ci * The requested DMA will be automatically released at unbinding via devres.
12062306a36Sopenharmony_ci *
12162306a36Sopenharmony_ci * Return: zero on success, or a negative error code
12262306a36Sopenharmony_ci */
12362306a36Sopenharmony_ciint snd_devm_request_dma(struct device *dev, int dma, const char *name)
12462306a36Sopenharmony_ci{
12562306a36Sopenharmony_ci	struct snd_dma_data *p;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	if (request_dma(dma, name))
12862306a36Sopenharmony_ci		return -EBUSY;
12962306a36Sopenharmony_ci	p = devres_alloc(__snd_release_dma, sizeof(*p), GFP_KERNEL);
13062306a36Sopenharmony_ci	if (!p) {
13162306a36Sopenharmony_ci		free_dma(dma);
13262306a36Sopenharmony_ci		return -ENOMEM;
13362306a36Sopenharmony_ci	}
13462306a36Sopenharmony_ci	p->dma = dma;
13562306a36Sopenharmony_ci	devres_add(dev, p);
13662306a36Sopenharmony_ci	return 0;
13762306a36Sopenharmony_ci}
13862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(snd_devm_request_dma);
139