162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) by Jaroslav Kysela <perex@perex.cz> 462306a36Sopenharmony_ci * Copyright (c) by Takashi Iwai <tiwai@suse.de> 562306a36Sopenharmony_ci * Copyright (c) by Scott McNab <sdm@fractalgraphics.com.au> 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Trident 4DWave-NX memory page allocation (TLB area) 862306a36Sopenharmony_ci * Trident chip can handle only 16MByte of the memory at the same time. 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/io.h> 1262306a36Sopenharmony_ci#include <linux/pci.h> 1362306a36Sopenharmony_ci#include <linux/time.h> 1462306a36Sopenharmony_ci#include <linux/mutex.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#include <sound/core.h> 1762306a36Sopenharmony_ci#include "trident.h" 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci/* page arguments of these two macros are Trident page (4096 bytes), not like 2062306a36Sopenharmony_ci * aligned pages in others 2162306a36Sopenharmony_ci */ 2262306a36Sopenharmony_ci#define __set_tlb_bus(trident,page,addr) \ 2362306a36Sopenharmony_ci (trident)->tlb.entries[page] = cpu_to_le32((addr) & ~(SNDRV_TRIDENT_PAGE_SIZE-1)) 2462306a36Sopenharmony_ci#define __tlb_to_addr(trident,page) \ 2562306a36Sopenharmony_ci (dma_addr_t)le32_to_cpu((trident->tlb.entries[page]) & ~(SNDRV_TRIDENT_PAGE_SIZE - 1)) 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#if PAGE_SIZE == 4096 2862306a36Sopenharmony_ci/* page size == SNDRV_TRIDENT_PAGE_SIZE */ 2962306a36Sopenharmony_ci#define ALIGN_PAGE_SIZE PAGE_SIZE /* minimum page size for allocation */ 3062306a36Sopenharmony_ci#define MAX_ALIGN_PAGES SNDRV_TRIDENT_MAX_PAGES /* maxmium aligned pages */ 3162306a36Sopenharmony_ci/* fill TLB entrie(s) corresponding to page with ptr */ 3262306a36Sopenharmony_ci#define set_tlb_bus(trident,page,addr) __set_tlb_bus(trident,page,addr) 3362306a36Sopenharmony_ci/* fill TLB entrie(s) corresponding to page with silence pointer */ 3462306a36Sopenharmony_ci#define set_silent_tlb(trident,page) __set_tlb_bus(trident, page, trident->tlb.silent_page->addr) 3562306a36Sopenharmony_ci/* get aligned page from offset address */ 3662306a36Sopenharmony_ci#define get_aligned_page(offset) ((offset) >> 12) 3762306a36Sopenharmony_ci/* get offset address from aligned page */ 3862306a36Sopenharmony_ci#define aligned_page_offset(page) ((page) << 12) 3962306a36Sopenharmony_ci/* get PCI physical address from aligned page */ 4062306a36Sopenharmony_ci#define page_to_addr(trident,page) __tlb_to_addr(trident, page) 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#elif PAGE_SIZE == 8192 4362306a36Sopenharmony_ci/* page size == SNDRV_TRIDENT_PAGE_SIZE x 2*/ 4462306a36Sopenharmony_ci#define ALIGN_PAGE_SIZE PAGE_SIZE 4562306a36Sopenharmony_ci#define MAX_ALIGN_PAGES (SNDRV_TRIDENT_MAX_PAGES / 2) 4662306a36Sopenharmony_ci#define get_aligned_page(offset) ((offset) >> 13) 4762306a36Sopenharmony_ci#define aligned_page_offset(page) ((page) << 13) 4862306a36Sopenharmony_ci#define page_to_addr(trident,page) __tlb_to_addr(trident, (page) << 1) 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci/* fill TLB entries -- we need to fill two entries */ 5162306a36Sopenharmony_cistatic inline void set_tlb_bus(struct snd_trident *trident, int page, 5262306a36Sopenharmony_ci dma_addr_t addr) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci page <<= 1; 5562306a36Sopenharmony_ci __set_tlb_bus(trident, page, addr); 5662306a36Sopenharmony_ci __set_tlb_bus(trident, page+1, addr + SNDRV_TRIDENT_PAGE_SIZE); 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_cistatic inline void set_silent_tlb(struct snd_trident *trident, int page) 5962306a36Sopenharmony_ci{ 6062306a36Sopenharmony_ci page <<= 1; 6162306a36Sopenharmony_ci __set_tlb_bus(trident, page, trident->tlb.silent_page->addr); 6262306a36Sopenharmony_ci __set_tlb_bus(trident, page+1, trident->tlb.silent_page->addr); 6362306a36Sopenharmony_ci} 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci#else 6662306a36Sopenharmony_ci/* arbitrary size */ 6762306a36Sopenharmony_ci#define UNIT_PAGES (PAGE_SIZE / SNDRV_TRIDENT_PAGE_SIZE) 6862306a36Sopenharmony_ci#define ALIGN_PAGE_SIZE (SNDRV_TRIDENT_PAGE_SIZE * UNIT_PAGES) 6962306a36Sopenharmony_ci#define MAX_ALIGN_PAGES (SNDRV_TRIDENT_MAX_PAGES / UNIT_PAGES) 7062306a36Sopenharmony_ci/* Note: if alignment doesn't match to the maximum size, the last few blocks 7162306a36Sopenharmony_ci * become unusable. To use such blocks, you'll need to check the validity 7262306a36Sopenharmony_ci * of accessing page in set_tlb_bus and set_silent_tlb. search_empty() 7362306a36Sopenharmony_ci * should also check it, too. 7462306a36Sopenharmony_ci */ 7562306a36Sopenharmony_ci#define get_aligned_page(offset) ((offset) / ALIGN_PAGE_SIZE) 7662306a36Sopenharmony_ci#define aligned_page_offset(page) ((page) * ALIGN_PAGE_SIZE) 7762306a36Sopenharmony_ci#define page_to_addr(trident,page) __tlb_to_addr(trident, (page) * UNIT_PAGES) 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci/* fill TLB entries -- UNIT_PAGES entries must be filled */ 8062306a36Sopenharmony_cistatic inline void set_tlb_bus(struct snd_trident *trident, int page, 8162306a36Sopenharmony_ci dma_addr_t addr) 8262306a36Sopenharmony_ci{ 8362306a36Sopenharmony_ci int i; 8462306a36Sopenharmony_ci page *= UNIT_PAGES; 8562306a36Sopenharmony_ci for (i = 0; i < UNIT_PAGES; i++, page++) { 8662306a36Sopenharmony_ci __set_tlb_bus(trident, page, addr); 8762306a36Sopenharmony_ci addr += SNDRV_TRIDENT_PAGE_SIZE; 8862306a36Sopenharmony_ci } 8962306a36Sopenharmony_ci} 9062306a36Sopenharmony_cistatic inline void set_silent_tlb(struct snd_trident *trident, int page) 9162306a36Sopenharmony_ci{ 9262306a36Sopenharmony_ci int i; 9362306a36Sopenharmony_ci page *= UNIT_PAGES; 9462306a36Sopenharmony_ci for (i = 0; i < UNIT_PAGES; i++, page++) 9562306a36Sopenharmony_ci __set_tlb_bus(trident, page, trident->tlb.silent_page->addr); 9662306a36Sopenharmony_ci} 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci#endif /* PAGE_SIZE */ 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci/* first and last (aligned) pages of memory block */ 10162306a36Sopenharmony_ci#define firstpg(blk) (((struct snd_trident_memblk_arg *)snd_util_memblk_argptr(blk))->first_page) 10262306a36Sopenharmony_ci#define lastpg(blk) (((struct snd_trident_memblk_arg *)snd_util_memblk_argptr(blk))->last_page) 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci/* 10562306a36Sopenharmony_ci * search empty pages which may contain given size 10662306a36Sopenharmony_ci */ 10762306a36Sopenharmony_cistatic struct snd_util_memblk * 10862306a36Sopenharmony_cisearch_empty(struct snd_util_memhdr *hdr, int size) 10962306a36Sopenharmony_ci{ 11062306a36Sopenharmony_ci struct snd_util_memblk *blk; 11162306a36Sopenharmony_ci int page, psize; 11262306a36Sopenharmony_ci struct list_head *p; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci psize = get_aligned_page(size + ALIGN_PAGE_SIZE -1); 11562306a36Sopenharmony_ci page = 0; 11662306a36Sopenharmony_ci list_for_each(p, &hdr->block) { 11762306a36Sopenharmony_ci blk = list_entry(p, struct snd_util_memblk, list); 11862306a36Sopenharmony_ci if (page + psize <= firstpg(blk)) 11962306a36Sopenharmony_ci goto __found_pages; 12062306a36Sopenharmony_ci page = lastpg(blk) + 1; 12162306a36Sopenharmony_ci } 12262306a36Sopenharmony_ci if (page + psize > MAX_ALIGN_PAGES) 12362306a36Sopenharmony_ci return NULL; 12462306a36Sopenharmony_ci 12562306a36Sopenharmony_ci__found_pages: 12662306a36Sopenharmony_ci /* create a new memory block */ 12762306a36Sopenharmony_ci blk = __snd_util_memblk_new(hdr, psize * ALIGN_PAGE_SIZE, p->prev); 12862306a36Sopenharmony_ci if (blk == NULL) 12962306a36Sopenharmony_ci return NULL; 13062306a36Sopenharmony_ci blk->offset = aligned_page_offset(page); /* set aligned offset */ 13162306a36Sopenharmony_ci firstpg(blk) = page; 13262306a36Sopenharmony_ci lastpg(blk) = page + psize - 1; 13362306a36Sopenharmony_ci return blk; 13462306a36Sopenharmony_ci} 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci/* 13862306a36Sopenharmony_ci * check if the given pointer is valid for pages 13962306a36Sopenharmony_ci */ 14062306a36Sopenharmony_cistatic int is_valid_page(unsigned long ptr) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci if (ptr & ~0x3fffffffUL) { 14362306a36Sopenharmony_ci snd_printk(KERN_ERR "max memory size is 1GB!!\n"); 14462306a36Sopenharmony_ci return 0; 14562306a36Sopenharmony_ci } 14662306a36Sopenharmony_ci if (ptr & (SNDRV_TRIDENT_PAGE_SIZE-1)) { 14762306a36Sopenharmony_ci snd_printk(KERN_ERR "page is not aligned\n"); 14862306a36Sopenharmony_ci return 0; 14962306a36Sopenharmony_ci } 15062306a36Sopenharmony_ci return 1; 15162306a36Sopenharmony_ci} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci/* 15462306a36Sopenharmony_ci * page allocation for DMA (Scatter-Gather version) 15562306a36Sopenharmony_ci */ 15662306a36Sopenharmony_cistatic struct snd_util_memblk * 15762306a36Sopenharmony_cisnd_trident_alloc_sg_pages(struct snd_trident *trident, 15862306a36Sopenharmony_ci struct snd_pcm_substream *substream) 15962306a36Sopenharmony_ci{ 16062306a36Sopenharmony_ci struct snd_util_memhdr *hdr; 16162306a36Sopenharmony_ci struct snd_util_memblk *blk; 16262306a36Sopenharmony_ci struct snd_pcm_runtime *runtime = substream->runtime; 16362306a36Sopenharmony_ci int idx, page; 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci if (snd_BUG_ON(runtime->dma_bytes <= 0 || 16662306a36Sopenharmony_ci runtime->dma_bytes > SNDRV_TRIDENT_MAX_PAGES * 16762306a36Sopenharmony_ci SNDRV_TRIDENT_PAGE_SIZE)) 16862306a36Sopenharmony_ci return NULL; 16962306a36Sopenharmony_ci hdr = trident->tlb.memhdr; 17062306a36Sopenharmony_ci if (snd_BUG_ON(!hdr)) 17162306a36Sopenharmony_ci return NULL; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci mutex_lock(&hdr->block_mutex); 17662306a36Sopenharmony_ci blk = search_empty(hdr, runtime->dma_bytes); 17762306a36Sopenharmony_ci if (blk == NULL) { 17862306a36Sopenharmony_ci mutex_unlock(&hdr->block_mutex); 17962306a36Sopenharmony_ci return NULL; 18062306a36Sopenharmony_ci } 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci /* set TLB entries */ 18362306a36Sopenharmony_ci idx = 0; 18462306a36Sopenharmony_ci for (page = firstpg(blk); page <= lastpg(blk); page++, idx++) { 18562306a36Sopenharmony_ci unsigned long ofs = idx << PAGE_SHIFT; 18662306a36Sopenharmony_ci dma_addr_t addr = snd_pcm_sgbuf_get_addr(substream, ofs); 18762306a36Sopenharmony_ci if (! is_valid_page(addr)) { 18862306a36Sopenharmony_ci __snd_util_mem_free(hdr, blk); 18962306a36Sopenharmony_ci mutex_unlock(&hdr->block_mutex); 19062306a36Sopenharmony_ci return NULL; 19162306a36Sopenharmony_ci } 19262306a36Sopenharmony_ci set_tlb_bus(trident, page, addr); 19362306a36Sopenharmony_ci } 19462306a36Sopenharmony_ci mutex_unlock(&hdr->block_mutex); 19562306a36Sopenharmony_ci return blk; 19662306a36Sopenharmony_ci} 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci/* 19962306a36Sopenharmony_ci * page allocation for DMA (contiguous version) 20062306a36Sopenharmony_ci */ 20162306a36Sopenharmony_cistatic struct snd_util_memblk * 20262306a36Sopenharmony_cisnd_trident_alloc_cont_pages(struct snd_trident *trident, 20362306a36Sopenharmony_ci struct snd_pcm_substream *substream) 20462306a36Sopenharmony_ci{ 20562306a36Sopenharmony_ci struct snd_util_memhdr *hdr; 20662306a36Sopenharmony_ci struct snd_util_memblk *blk; 20762306a36Sopenharmony_ci int page; 20862306a36Sopenharmony_ci struct snd_pcm_runtime *runtime = substream->runtime; 20962306a36Sopenharmony_ci dma_addr_t addr; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci if (snd_BUG_ON(runtime->dma_bytes <= 0 || 21262306a36Sopenharmony_ci runtime->dma_bytes > SNDRV_TRIDENT_MAX_PAGES * 21362306a36Sopenharmony_ci SNDRV_TRIDENT_PAGE_SIZE)) 21462306a36Sopenharmony_ci return NULL; 21562306a36Sopenharmony_ci hdr = trident->tlb.memhdr; 21662306a36Sopenharmony_ci if (snd_BUG_ON(!hdr)) 21762306a36Sopenharmony_ci return NULL; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci mutex_lock(&hdr->block_mutex); 22062306a36Sopenharmony_ci blk = search_empty(hdr, runtime->dma_bytes); 22162306a36Sopenharmony_ci if (blk == NULL) { 22262306a36Sopenharmony_ci mutex_unlock(&hdr->block_mutex); 22362306a36Sopenharmony_ci return NULL; 22462306a36Sopenharmony_ci } 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci /* set TLB entries */ 22762306a36Sopenharmony_ci addr = runtime->dma_addr; 22862306a36Sopenharmony_ci for (page = firstpg(blk); page <= lastpg(blk); page++, 22962306a36Sopenharmony_ci addr += SNDRV_TRIDENT_PAGE_SIZE) { 23062306a36Sopenharmony_ci if (! is_valid_page(addr)) { 23162306a36Sopenharmony_ci __snd_util_mem_free(hdr, blk); 23262306a36Sopenharmony_ci mutex_unlock(&hdr->block_mutex); 23362306a36Sopenharmony_ci return NULL; 23462306a36Sopenharmony_ci } 23562306a36Sopenharmony_ci set_tlb_bus(trident, page, addr); 23662306a36Sopenharmony_ci } 23762306a36Sopenharmony_ci mutex_unlock(&hdr->block_mutex); 23862306a36Sopenharmony_ci return blk; 23962306a36Sopenharmony_ci} 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci/* 24262306a36Sopenharmony_ci * page allocation for DMA 24362306a36Sopenharmony_ci */ 24462306a36Sopenharmony_cistruct snd_util_memblk * 24562306a36Sopenharmony_cisnd_trident_alloc_pages(struct snd_trident *trident, 24662306a36Sopenharmony_ci struct snd_pcm_substream *substream) 24762306a36Sopenharmony_ci{ 24862306a36Sopenharmony_ci if (snd_BUG_ON(!trident || !substream)) 24962306a36Sopenharmony_ci return NULL; 25062306a36Sopenharmony_ci if (substream->dma_buffer.dev.type == SNDRV_DMA_TYPE_DEV_SG) 25162306a36Sopenharmony_ci return snd_trident_alloc_sg_pages(trident, substream); 25262306a36Sopenharmony_ci else 25362306a36Sopenharmony_ci return snd_trident_alloc_cont_pages(trident, substream); 25462306a36Sopenharmony_ci} 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci/* 25862306a36Sopenharmony_ci * release DMA buffer from page table 25962306a36Sopenharmony_ci */ 26062306a36Sopenharmony_ciint snd_trident_free_pages(struct snd_trident *trident, 26162306a36Sopenharmony_ci struct snd_util_memblk *blk) 26262306a36Sopenharmony_ci{ 26362306a36Sopenharmony_ci struct snd_util_memhdr *hdr; 26462306a36Sopenharmony_ci int page; 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci if (snd_BUG_ON(!trident || !blk)) 26762306a36Sopenharmony_ci return -EINVAL; 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci hdr = trident->tlb.memhdr; 27062306a36Sopenharmony_ci mutex_lock(&hdr->block_mutex); 27162306a36Sopenharmony_ci /* reset TLB entries */ 27262306a36Sopenharmony_ci for (page = firstpg(blk); page <= lastpg(blk); page++) 27362306a36Sopenharmony_ci set_silent_tlb(trident, page); 27462306a36Sopenharmony_ci /* free memory block */ 27562306a36Sopenharmony_ci __snd_util_mem_free(hdr, blk); 27662306a36Sopenharmony_ci mutex_unlock(&hdr->block_mutex); 27762306a36Sopenharmony_ci return 0; 27862306a36Sopenharmony_ci} 279