162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci User DMA 462306a36Sopenharmony_ci 562306a36Sopenharmony_ci Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> 662306a36Sopenharmony_ci Copyright (C) 2004 Chris Kennedy <c@groovy.org> 762306a36Sopenharmony_ci Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include "ivtv-driver.h" 1262306a36Sopenharmony_ci#include "ivtv-udma.h" 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_civoid ivtv_udma_get_page_info(struct ivtv_dma_page_info *dma_page, unsigned long first, unsigned long size) 1562306a36Sopenharmony_ci{ 1662306a36Sopenharmony_ci dma_page->uaddr = first & PAGE_MASK; 1762306a36Sopenharmony_ci dma_page->offset = first & ~PAGE_MASK; 1862306a36Sopenharmony_ci dma_page->tail = 1 + ((first+size-1) & ~PAGE_MASK); 1962306a36Sopenharmony_ci dma_page->first = (first & PAGE_MASK) >> PAGE_SHIFT; 2062306a36Sopenharmony_ci dma_page->last = ((first+size-1) & PAGE_MASK) >> PAGE_SHIFT; 2162306a36Sopenharmony_ci dma_page->page_count = dma_page->last - dma_page->first + 1; 2262306a36Sopenharmony_ci if (dma_page->page_count == 1) dma_page->tail -= dma_page->offset; 2362306a36Sopenharmony_ci} 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ciint ivtv_udma_fill_sg_list (struct ivtv_user_dma *dma, struct ivtv_dma_page_info *dma_page, int map_offset) 2662306a36Sopenharmony_ci{ 2762306a36Sopenharmony_ci int i, offset; 2862306a36Sopenharmony_ci unsigned long flags; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci if (map_offset < 0) 3162306a36Sopenharmony_ci return map_offset; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci offset = dma_page->offset; 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci /* Fill SG Array with new values */ 3662306a36Sopenharmony_ci for (i = 0; i < dma_page->page_count; i++) { 3762306a36Sopenharmony_ci unsigned int len = (i == dma_page->page_count - 1) ? 3862306a36Sopenharmony_ci dma_page->tail : PAGE_SIZE - offset; 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci if (PageHighMem(dma->map[map_offset])) { 4162306a36Sopenharmony_ci void *src; 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci if (dma->bouncemap[map_offset] == NULL) 4462306a36Sopenharmony_ci dma->bouncemap[map_offset] = alloc_page(GFP_KERNEL); 4562306a36Sopenharmony_ci if (dma->bouncemap[map_offset] == NULL) 4662306a36Sopenharmony_ci return -1; 4762306a36Sopenharmony_ci local_irq_save(flags); 4862306a36Sopenharmony_ci src = kmap_atomic(dma->map[map_offset]) + offset; 4962306a36Sopenharmony_ci memcpy(page_address(dma->bouncemap[map_offset]) + offset, src, len); 5062306a36Sopenharmony_ci kunmap_atomic(src); 5162306a36Sopenharmony_ci local_irq_restore(flags); 5262306a36Sopenharmony_ci sg_set_page(&dma->SGlist[map_offset], dma->bouncemap[map_offset], len, offset); 5362306a36Sopenharmony_ci } 5462306a36Sopenharmony_ci else { 5562306a36Sopenharmony_ci sg_set_page(&dma->SGlist[map_offset], dma->map[map_offset], len, offset); 5662306a36Sopenharmony_ci } 5762306a36Sopenharmony_ci offset = 0; 5862306a36Sopenharmony_ci map_offset++; 5962306a36Sopenharmony_ci } 6062306a36Sopenharmony_ci return map_offset; 6162306a36Sopenharmony_ci} 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_civoid ivtv_udma_fill_sg_array (struct ivtv_user_dma *dma, u32 buffer_offset, u32 buffer_offset_2, u32 split) { 6462306a36Sopenharmony_ci int i; 6562306a36Sopenharmony_ci struct scatterlist *sg; 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ci for_each_sg(dma->SGlist, sg, dma->SG_length, i) { 6862306a36Sopenharmony_ci dma->SGarray[i].size = cpu_to_le32(sg_dma_len(sg)); 6962306a36Sopenharmony_ci dma->SGarray[i].src = cpu_to_le32(sg_dma_address(sg)); 7062306a36Sopenharmony_ci dma->SGarray[i].dst = cpu_to_le32(buffer_offset); 7162306a36Sopenharmony_ci buffer_offset += sg_dma_len(sg); 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci split -= sg_dma_len(sg); 7462306a36Sopenharmony_ci if (split == 0) 7562306a36Sopenharmony_ci buffer_offset = buffer_offset_2; 7662306a36Sopenharmony_ci } 7762306a36Sopenharmony_ci} 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci/* User DMA Buffers */ 8062306a36Sopenharmony_civoid ivtv_udma_alloc(struct ivtv *itv) 8162306a36Sopenharmony_ci{ 8262306a36Sopenharmony_ci if (itv->udma.SG_handle == 0) { 8362306a36Sopenharmony_ci /* Map DMA Page Array Buffer */ 8462306a36Sopenharmony_ci itv->udma.SG_handle = dma_map_single(&itv->pdev->dev, 8562306a36Sopenharmony_ci itv->udma.SGarray, 8662306a36Sopenharmony_ci sizeof(itv->udma.SGarray), 8762306a36Sopenharmony_ci DMA_TO_DEVICE); 8862306a36Sopenharmony_ci ivtv_udma_sync_for_cpu(itv); 8962306a36Sopenharmony_ci } 9062306a36Sopenharmony_ci} 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ciint ivtv_udma_setup(struct ivtv *itv, unsigned long ivtv_dest_addr, 9362306a36Sopenharmony_ci void __user *userbuf, int size_in_bytes) 9462306a36Sopenharmony_ci{ 9562306a36Sopenharmony_ci struct ivtv_dma_page_info user_dma; 9662306a36Sopenharmony_ci struct ivtv_user_dma *dma = &itv->udma; 9762306a36Sopenharmony_ci int err; 9862306a36Sopenharmony_ci 9962306a36Sopenharmony_ci IVTV_DEBUG_DMA("ivtv_udma_setup, dst: 0x%08x\n", (unsigned int)ivtv_dest_addr); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci /* Still in USE */ 10262306a36Sopenharmony_ci if (dma->SG_length || dma->page_count) { 10362306a36Sopenharmony_ci IVTV_DEBUG_WARN("ivtv_udma_setup: SG_length %d page_count %d still full?\n", 10462306a36Sopenharmony_ci dma->SG_length, dma->page_count); 10562306a36Sopenharmony_ci return -EBUSY; 10662306a36Sopenharmony_ci } 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci ivtv_udma_get_page_info(&user_dma, (unsigned long)userbuf, size_in_bytes); 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci if (user_dma.page_count <= 0) { 11162306a36Sopenharmony_ci IVTV_DEBUG_WARN("ivtv_udma_setup: Error %d page_count from %d bytes %d offset\n", 11262306a36Sopenharmony_ci user_dma.page_count, size_in_bytes, user_dma.offset); 11362306a36Sopenharmony_ci return -EINVAL; 11462306a36Sopenharmony_ci } 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci /* Pin user pages for DMA Xfer */ 11762306a36Sopenharmony_ci err = pin_user_pages_unlocked(user_dma.uaddr, user_dma.page_count, 11862306a36Sopenharmony_ci dma->map, 0); 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci if (user_dma.page_count != err) { 12162306a36Sopenharmony_ci IVTV_DEBUG_WARN("failed to map user pages, returned %d instead of %d\n", 12262306a36Sopenharmony_ci err, user_dma.page_count); 12362306a36Sopenharmony_ci if (err >= 0) { 12462306a36Sopenharmony_ci unpin_user_pages(dma->map, err); 12562306a36Sopenharmony_ci return -EINVAL; 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci return err; 12862306a36Sopenharmony_ci } 12962306a36Sopenharmony_ci 13062306a36Sopenharmony_ci dma->page_count = user_dma.page_count; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci /* Fill SG List with new values */ 13362306a36Sopenharmony_ci if (ivtv_udma_fill_sg_list(dma, &user_dma, 0) < 0) { 13462306a36Sopenharmony_ci unpin_user_pages(dma->map, dma->page_count); 13562306a36Sopenharmony_ci dma->page_count = 0; 13662306a36Sopenharmony_ci return -ENOMEM; 13762306a36Sopenharmony_ci } 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci /* Map SG List */ 14062306a36Sopenharmony_ci dma->SG_length = dma_map_sg(&itv->pdev->dev, dma->SGlist, 14162306a36Sopenharmony_ci dma->page_count, DMA_TO_DEVICE); 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci /* Fill SG Array with new values */ 14462306a36Sopenharmony_ci ivtv_udma_fill_sg_array (dma, ivtv_dest_addr, 0, -1); 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci /* Tag SG Array with Interrupt Bit */ 14762306a36Sopenharmony_ci dma->SGarray[dma->SG_length - 1].size |= cpu_to_le32(0x80000000); 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci ivtv_udma_sync_for_device(itv); 15062306a36Sopenharmony_ci return dma->page_count; 15162306a36Sopenharmony_ci} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_civoid ivtv_udma_unmap(struct ivtv *itv) 15462306a36Sopenharmony_ci{ 15562306a36Sopenharmony_ci struct ivtv_user_dma *dma = &itv->udma; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci IVTV_DEBUG_INFO("ivtv_unmap_user_dma\n"); 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci /* Nothing to free */ 16062306a36Sopenharmony_ci if (dma->page_count == 0) 16162306a36Sopenharmony_ci return; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci /* Unmap Scatterlist */ 16462306a36Sopenharmony_ci if (dma->SG_length) { 16562306a36Sopenharmony_ci dma_unmap_sg(&itv->pdev->dev, dma->SGlist, dma->page_count, 16662306a36Sopenharmony_ci DMA_TO_DEVICE); 16762306a36Sopenharmony_ci dma->SG_length = 0; 16862306a36Sopenharmony_ci } 16962306a36Sopenharmony_ci /* sync DMA */ 17062306a36Sopenharmony_ci ivtv_udma_sync_for_cpu(itv); 17162306a36Sopenharmony_ci 17262306a36Sopenharmony_ci unpin_user_pages(dma->map, dma->page_count); 17362306a36Sopenharmony_ci dma->page_count = 0; 17462306a36Sopenharmony_ci} 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_civoid ivtv_udma_free(struct ivtv *itv) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci int i; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci /* Unmap SG Array */ 18162306a36Sopenharmony_ci if (itv->udma.SG_handle) { 18262306a36Sopenharmony_ci dma_unmap_single(&itv->pdev->dev, itv->udma.SG_handle, 18362306a36Sopenharmony_ci sizeof(itv->udma.SGarray), DMA_TO_DEVICE); 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci /* Unmap Scatterlist */ 18762306a36Sopenharmony_ci if (itv->udma.SG_length) { 18862306a36Sopenharmony_ci dma_unmap_sg(&itv->pdev->dev, itv->udma.SGlist, 18962306a36Sopenharmony_ci itv->udma.page_count, DMA_TO_DEVICE); 19062306a36Sopenharmony_ci } 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci for (i = 0; i < IVTV_DMA_SG_OSD_ENT; i++) { 19362306a36Sopenharmony_ci if (itv->udma.bouncemap[i]) 19462306a36Sopenharmony_ci __free_page(itv->udma.bouncemap[i]); 19562306a36Sopenharmony_ci } 19662306a36Sopenharmony_ci} 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_civoid ivtv_udma_start(struct ivtv *itv) 19962306a36Sopenharmony_ci{ 20062306a36Sopenharmony_ci IVTV_DEBUG_DMA("start UDMA\n"); 20162306a36Sopenharmony_ci write_reg(itv->udma.SG_handle, IVTV_REG_DECDMAADDR); 20262306a36Sopenharmony_ci write_reg_sync(read_reg(IVTV_REG_DMAXFER) | 0x01, IVTV_REG_DMAXFER); 20362306a36Sopenharmony_ci set_bit(IVTV_F_I_DMA, &itv->i_flags); 20462306a36Sopenharmony_ci set_bit(IVTV_F_I_UDMA, &itv->i_flags); 20562306a36Sopenharmony_ci clear_bit(IVTV_F_I_UDMA_PENDING, &itv->i_flags); 20662306a36Sopenharmony_ci} 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_civoid ivtv_udma_prepare(struct ivtv *itv) 20962306a36Sopenharmony_ci{ 21062306a36Sopenharmony_ci unsigned long flags; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci spin_lock_irqsave(&itv->dma_reg_lock, flags); 21362306a36Sopenharmony_ci if (!test_bit(IVTV_F_I_DMA, &itv->i_flags)) 21462306a36Sopenharmony_ci ivtv_udma_start(itv); 21562306a36Sopenharmony_ci else 21662306a36Sopenharmony_ci set_bit(IVTV_F_I_UDMA_PENDING, &itv->i_flags); 21762306a36Sopenharmony_ci spin_unlock_irqrestore(&itv->dma_reg_lock, flags); 21862306a36Sopenharmony_ci} 219