18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci User DMA 48c2ecf20Sopenharmony_ci 58c2ecf20Sopenharmony_ci Copyright (C) 2003-2004 Kevin Thayer <nufan_wfk at yahoo.com> 68c2ecf20Sopenharmony_ci Copyright (C) 2004 Chris Kennedy <c@groovy.org> 78c2ecf20Sopenharmony_ci Copyright (C) 2005-2007 Hans Verkuil <hverkuil@xs4all.nl> 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include "ivtv-driver.h" 128c2ecf20Sopenharmony_ci#include "ivtv-udma.h" 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_civoid ivtv_udma_get_page_info(struct ivtv_dma_page_info *dma_page, unsigned long first, unsigned long size) 158c2ecf20Sopenharmony_ci{ 168c2ecf20Sopenharmony_ci dma_page->uaddr = first & PAGE_MASK; 178c2ecf20Sopenharmony_ci dma_page->offset = first & ~PAGE_MASK; 188c2ecf20Sopenharmony_ci dma_page->tail = 1 + ((first+size-1) & ~PAGE_MASK); 198c2ecf20Sopenharmony_ci dma_page->first = (first & PAGE_MASK) >> PAGE_SHIFT; 208c2ecf20Sopenharmony_ci dma_page->last = ((first+size-1) & PAGE_MASK) >> PAGE_SHIFT; 218c2ecf20Sopenharmony_ci dma_page->page_count = dma_page->last - dma_page->first + 1; 228c2ecf20Sopenharmony_ci if (dma_page->page_count == 1) dma_page->tail -= dma_page->offset; 238c2ecf20Sopenharmony_ci} 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ciint ivtv_udma_fill_sg_list (struct ivtv_user_dma *dma, struct ivtv_dma_page_info *dma_page, int map_offset) 268c2ecf20Sopenharmony_ci{ 278c2ecf20Sopenharmony_ci int i, offset; 288c2ecf20Sopenharmony_ci unsigned long flags; 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci if (map_offset < 0) 318c2ecf20Sopenharmony_ci return map_offset; 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci offset = dma_page->offset; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci /* Fill SG Array with new values */ 368c2ecf20Sopenharmony_ci for (i = 0; i < dma_page->page_count; i++) { 378c2ecf20Sopenharmony_ci unsigned int len = (i == dma_page->page_count - 1) ? 388c2ecf20Sopenharmony_ci dma_page->tail : PAGE_SIZE - offset; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci if (PageHighMem(dma->map[map_offset])) { 418c2ecf20Sopenharmony_ci void *src; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci if (dma->bouncemap[map_offset] == NULL) 448c2ecf20Sopenharmony_ci dma->bouncemap[map_offset] = alloc_page(GFP_KERNEL); 458c2ecf20Sopenharmony_ci if (dma->bouncemap[map_offset] == NULL) 468c2ecf20Sopenharmony_ci return -1; 478c2ecf20Sopenharmony_ci local_irq_save(flags); 488c2ecf20Sopenharmony_ci src = kmap_atomic(dma->map[map_offset]) + offset; 498c2ecf20Sopenharmony_ci memcpy(page_address(dma->bouncemap[map_offset]) + offset, src, len); 508c2ecf20Sopenharmony_ci kunmap_atomic(src); 518c2ecf20Sopenharmony_ci local_irq_restore(flags); 528c2ecf20Sopenharmony_ci sg_set_page(&dma->SGlist[map_offset], dma->bouncemap[map_offset], len, offset); 538c2ecf20Sopenharmony_ci } 548c2ecf20Sopenharmony_ci else { 558c2ecf20Sopenharmony_ci sg_set_page(&dma->SGlist[map_offset], dma->map[map_offset], len, offset); 568c2ecf20Sopenharmony_ci } 578c2ecf20Sopenharmony_ci offset = 0; 588c2ecf20Sopenharmony_ci map_offset++; 598c2ecf20Sopenharmony_ci } 608c2ecf20Sopenharmony_ci return map_offset; 618c2ecf20Sopenharmony_ci} 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_civoid ivtv_udma_fill_sg_array (struct ivtv_user_dma *dma, u32 buffer_offset, u32 buffer_offset_2, u32 split) { 648c2ecf20Sopenharmony_ci int i; 658c2ecf20Sopenharmony_ci struct scatterlist *sg; 668c2ecf20Sopenharmony_ci 678c2ecf20Sopenharmony_ci for_each_sg(dma->SGlist, sg, dma->SG_length, i) { 688c2ecf20Sopenharmony_ci dma->SGarray[i].size = cpu_to_le32(sg_dma_len(sg)); 698c2ecf20Sopenharmony_ci dma->SGarray[i].src = cpu_to_le32(sg_dma_address(sg)); 708c2ecf20Sopenharmony_ci dma->SGarray[i].dst = cpu_to_le32(buffer_offset); 718c2ecf20Sopenharmony_ci buffer_offset += sg_dma_len(sg); 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_ci split -= sg_dma_len(sg); 748c2ecf20Sopenharmony_ci if (split == 0) 758c2ecf20Sopenharmony_ci buffer_offset = buffer_offset_2; 768c2ecf20Sopenharmony_ci } 778c2ecf20Sopenharmony_ci} 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci/* User DMA Buffers */ 808c2ecf20Sopenharmony_civoid ivtv_udma_alloc(struct ivtv *itv) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci if (itv->udma.SG_handle == 0) { 838c2ecf20Sopenharmony_ci /* Map DMA Page Array Buffer */ 848c2ecf20Sopenharmony_ci itv->udma.SG_handle = pci_map_single(itv->pdev, itv->udma.SGarray, 858c2ecf20Sopenharmony_ci sizeof(itv->udma.SGarray), PCI_DMA_TODEVICE); 868c2ecf20Sopenharmony_ci ivtv_udma_sync_for_cpu(itv); 878c2ecf20Sopenharmony_ci } 888c2ecf20Sopenharmony_ci} 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ciint ivtv_udma_setup(struct ivtv *itv, unsigned long ivtv_dest_addr, 918c2ecf20Sopenharmony_ci void __user *userbuf, int size_in_bytes) 928c2ecf20Sopenharmony_ci{ 938c2ecf20Sopenharmony_ci struct ivtv_dma_page_info user_dma; 948c2ecf20Sopenharmony_ci struct ivtv_user_dma *dma = &itv->udma; 958c2ecf20Sopenharmony_ci int err; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci IVTV_DEBUG_DMA("ivtv_udma_setup, dst: 0x%08x\n", (unsigned int)ivtv_dest_addr); 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci /* Still in USE */ 1008c2ecf20Sopenharmony_ci if (dma->SG_length || dma->page_count) { 1018c2ecf20Sopenharmony_ci IVTV_DEBUG_WARN("ivtv_udma_setup: SG_length %d page_count %d still full?\n", 1028c2ecf20Sopenharmony_ci dma->SG_length, dma->page_count); 1038c2ecf20Sopenharmony_ci return -EBUSY; 1048c2ecf20Sopenharmony_ci } 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci ivtv_udma_get_page_info(&user_dma, (unsigned long)userbuf, size_in_bytes); 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci if (user_dma.page_count <= 0) { 1098c2ecf20Sopenharmony_ci IVTV_DEBUG_WARN("ivtv_udma_setup: Error %d page_count from %d bytes %d offset\n", 1108c2ecf20Sopenharmony_ci user_dma.page_count, size_in_bytes, user_dma.offset); 1118c2ecf20Sopenharmony_ci return -EINVAL; 1128c2ecf20Sopenharmony_ci } 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci /* Pin user pages for DMA Xfer */ 1158c2ecf20Sopenharmony_ci err = pin_user_pages_unlocked(user_dma.uaddr, user_dma.page_count, 1168c2ecf20Sopenharmony_ci dma->map, FOLL_FORCE); 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci if (user_dma.page_count != err) { 1198c2ecf20Sopenharmony_ci IVTV_DEBUG_WARN("failed to map user pages, returned %d instead of %d\n", 1208c2ecf20Sopenharmony_ci err, user_dma.page_count); 1218c2ecf20Sopenharmony_ci if (err >= 0) { 1228c2ecf20Sopenharmony_ci unpin_user_pages(dma->map, err); 1238c2ecf20Sopenharmony_ci return -EINVAL; 1248c2ecf20Sopenharmony_ci } 1258c2ecf20Sopenharmony_ci return err; 1268c2ecf20Sopenharmony_ci } 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci dma->page_count = user_dma.page_count; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci /* Fill SG List with new values */ 1318c2ecf20Sopenharmony_ci if (ivtv_udma_fill_sg_list(dma, &user_dma, 0) < 0) { 1328c2ecf20Sopenharmony_ci unpin_user_pages(dma->map, dma->page_count); 1338c2ecf20Sopenharmony_ci dma->page_count = 0; 1348c2ecf20Sopenharmony_ci return -ENOMEM; 1358c2ecf20Sopenharmony_ci } 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci /* Map SG List */ 1388c2ecf20Sopenharmony_ci dma->SG_length = pci_map_sg(itv->pdev, dma->SGlist, dma->page_count, PCI_DMA_TODEVICE); 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci /* Fill SG Array with new values */ 1418c2ecf20Sopenharmony_ci ivtv_udma_fill_sg_array (dma, ivtv_dest_addr, 0, -1); 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci /* Tag SG Array with Interrupt Bit */ 1448c2ecf20Sopenharmony_ci dma->SGarray[dma->SG_length - 1].size |= cpu_to_le32(0x80000000); 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci ivtv_udma_sync_for_device(itv); 1478c2ecf20Sopenharmony_ci return dma->page_count; 1488c2ecf20Sopenharmony_ci} 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_civoid ivtv_udma_unmap(struct ivtv *itv) 1518c2ecf20Sopenharmony_ci{ 1528c2ecf20Sopenharmony_ci struct ivtv_user_dma *dma = &itv->udma; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci IVTV_DEBUG_INFO("ivtv_unmap_user_dma\n"); 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci /* Nothing to free */ 1578c2ecf20Sopenharmony_ci if (dma->page_count == 0) 1588c2ecf20Sopenharmony_ci return; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci /* Unmap Scatterlist */ 1618c2ecf20Sopenharmony_ci if (dma->SG_length) { 1628c2ecf20Sopenharmony_ci pci_unmap_sg(itv->pdev, dma->SGlist, dma->page_count, PCI_DMA_TODEVICE); 1638c2ecf20Sopenharmony_ci dma->SG_length = 0; 1648c2ecf20Sopenharmony_ci } 1658c2ecf20Sopenharmony_ci /* sync DMA */ 1668c2ecf20Sopenharmony_ci ivtv_udma_sync_for_cpu(itv); 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci unpin_user_pages(dma->map, dma->page_count); 1698c2ecf20Sopenharmony_ci dma->page_count = 0; 1708c2ecf20Sopenharmony_ci} 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_civoid ivtv_udma_free(struct ivtv *itv) 1738c2ecf20Sopenharmony_ci{ 1748c2ecf20Sopenharmony_ci int i; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci /* Unmap SG Array */ 1778c2ecf20Sopenharmony_ci if (itv->udma.SG_handle) { 1788c2ecf20Sopenharmony_ci pci_unmap_single(itv->pdev, itv->udma.SG_handle, 1798c2ecf20Sopenharmony_ci sizeof(itv->udma.SGarray), PCI_DMA_TODEVICE); 1808c2ecf20Sopenharmony_ci } 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci /* Unmap Scatterlist */ 1838c2ecf20Sopenharmony_ci if (itv->udma.SG_length) { 1848c2ecf20Sopenharmony_ci pci_unmap_sg(itv->pdev, itv->udma.SGlist, itv->udma.page_count, PCI_DMA_TODEVICE); 1858c2ecf20Sopenharmony_ci } 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci for (i = 0; i < IVTV_DMA_SG_OSD_ENT; i++) { 1888c2ecf20Sopenharmony_ci if (itv->udma.bouncemap[i]) 1898c2ecf20Sopenharmony_ci __free_page(itv->udma.bouncemap[i]); 1908c2ecf20Sopenharmony_ci } 1918c2ecf20Sopenharmony_ci} 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_civoid ivtv_udma_start(struct ivtv *itv) 1948c2ecf20Sopenharmony_ci{ 1958c2ecf20Sopenharmony_ci IVTV_DEBUG_DMA("start UDMA\n"); 1968c2ecf20Sopenharmony_ci write_reg(itv->udma.SG_handle, IVTV_REG_DECDMAADDR); 1978c2ecf20Sopenharmony_ci write_reg_sync(read_reg(IVTV_REG_DMAXFER) | 0x01, IVTV_REG_DMAXFER); 1988c2ecf20Sopenharmony_ci set_bit(IVTV_F_I_DMA, &itv->i_flags); 1998c2ecf20Sopenharmony_ci set_bit(IVTV_F_I_UDMA, &itv->i_flags); 2008c2ecf20Sopenharmony_ci clear_bit(IVTV_F_I_UDMA_PENDING, &itv->i_flags); 2018c2ecf20Sopenharmony_ci} 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_civoid ivtv_udma_prepare(struct ivtv *itv) 2048c2ecf20Sopenharmony_ci{ 2058c2ecf20Sopenharmony_ci unsigned long flags; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci spin_lock_irqsave(&itv->dma_reg_lock, flags); 2088c2ecf20Sopenharmony_ci if (!test_bit(IVTV_F_I_DMA, &itv->i_flags)) 2098c2ecf20Sopenharmony_ci ivtv_udma_start(itv); 2108c2ecf20Sopenharmony_ci else 2118c2ecf20Sopenharmony_ci set_bit(IVTV_F_I_UDMA_PENDING, &itv->i_flags); 2128c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&itv->dma_reg_lock, flags); 2138c2ecf20Sopenharmony_ci} 214