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