/* * linux/drivers/mmc/host/mshci-s3c-dma.c * Mobile Storage Host Controller Interface driver * * Copyright (c) 2011 Samsung Electronics Co., Ltd. * http://www.samsung.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mshci.h" static void mshci_s3c_dma_cache_maint_page(struct page *page, unsigned long offset, size_t size, enum dma_data_direction dir, void (*op)(const void *, size_t, int), int flush_type, int enable) { /* * A single sg entry may refer to multiple physically contiguous * pages. But we still need to process highmem pages individually. * If highmem is not configured then the bulk of this loop gets * optimized out. */ size_t left = size; do { size_t len = left; void *vaddr; if (PageHighMem(page)) { if (len + offset > PAGE_SIZE) { if (offset >= PAGE_SIZE) { page += offset / PAGE_SIZE; offset %= PAGE_SIZE; } len = PAGE_SIZE - offset; } vaddr = kmap_high_get(page); if (vaddr) { vaddr += offset; if (flush_type == 0 && enable) op(vaddr, len, dir); kunmap_high(page); } else if (cache_is_vipt()) { /* unmapped pages might still be cached */ vaddr = kmap_atomic(page); op(vaddr + offset, len, dir); kunmap_atomic(vaddr); } } else { vaddr = page_address(page) + offset; if (flush_type == 0 && enable) op(vaddr, len, dir); } offset = 0; page++; left -= len; } while (left); } void mshci_s3c_dma_page_cpu_to_dev(struct page *page, unsigned long off, size_t size, enum dma_data_direction dir, int flush_type) { unsigned long paddr; if (dir != DMA_FROM_DEVICE) { mshci_s3c_dma_cache_maint_page(page, off, size, dir, dmac_map_area, flush_type, 1); paddr = page_to_phys(page) + off; if (flush_type != 2) { outer_clean_range(paddr, paddr + size); } /* FIXME: non-speculating: flush on bidirectional mappings? */ } else { paddr = page_to_phys(page) + off; /* if flush all L1 cache, L2 cache dose not neet to be clean. because, all buffer dose not have split space */ if (flush_type != 2) { outer_clean_range(paddr, paddr + size); outer_inv_range(paddr, paddr + size); } /* FIXME: non-speculating: flush on bidirectional mappings? */ mshci_s3c_dma_cache_maint_page(page, off, size, dir, dmac_unmap_area, flush_type, 1); } } static inline dma_addr_t mshci_s3c_dma_map_page(struct device *dev, struct page *page, unsigned long offset, size_t size, enum dma_data_direction dir, int flush_type) { BUG_ON(!valid_dma_direction(dir)); mshci_s3c_dma_page_cpu_to_dev(page, offset, size, dir, flush_type); return pfn_to_dma(dev, page_to_pfn(page)) + offset; } int mshci_s3c_dma_map_sg(struct mshci_host *host, struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction dir, int flush_type) { struct scatterlist *s; int i, j; BUG_ON(!valid_dma_direction(dir)); if (flush_type == 2) { spin_unlock_irqrestore(&host->lock, host->sl_flags); flush_all_cpu_caches(); outer_flush_all(); spin_lock_irqsave(&host->lock, host->sl_flags); } else if(flush_type == 1) { spin_unlock_irqrestore(&host->lock, host->sl_flags); flush_all_cpu_caches(); spin_lock_irqsave(&host->lock, host->sl_flags); } for_each_sg(sg, s, nents, i) { s->dma_address = mshci_s3c_dma_map_page(dev, sg_page(s), s->offset, s->length, dir, flush_type); if (dma_mapping_error(dev, s->dma_address)) { goto bad_mapping; } } debug_dma_map_sg(dev, sg, nents, nents, dir); /* in case of invaldating cache, invaldating L2 cache must be done prior to invaldating L1 cache */ #if 0 if (dir == DMA_FROM_DEVICE) { if (flush_type == 1) { spin_unlock_irqrestore(&host->lock, host->sl_flags); flush_all_cpu_caches(); spin_lock_irqsave(&host->lock, host->sl_flags); } } #endif return nents; bad_mapping: for_each_sg(sg, s, i, j) dma_unmap_page(dev, sg_dma_address(s), sg_dma_len(s), dir); return 0; } void mshci_s3c_dma_page_dev_to_cpu(struct page *page, unsigned long off, size_t size, enum dma_data_direction dir, int flush_type) { unsigned long paddr = page_to_phys(page) + off; /* FIXME: non-speculating: not required */ /* don't bother invalidating if DMA to device */ mshci_s3c_dma_cache_maint_page(page, off, size, dir, dmac_unmap_area, flush_type, 0); } static inline void mshci_s3c_dma_unmap_page(struct device *dev, dma_addr_t handle, size_t size, enum dma_data_direction dir, int flush_type) { mshci_s3c_dma_page_dev_to_cpu(pfn_to_page(dma_to_pfn(dev, handle)), \ handle & ~PAGE_MASK, size, dir, flush_type); } void mshci_s3c_dma_unmap_sg(struct mshci_host *host, struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction dir, int flush_type) { #if 1 struct scatterlist *s; int i; if (dir == DMA_TO_DEVICE) for_each_sg(sg, s, nents, i) mshci_s3c_dma_unmap_page(dev, sg_dma_address(s), sg_dma_len(s),dir, flush_type); #endif } MODULE_DESCRIPTION("Samsung MSHCI (HSMMC) own dma map functions"); MODULE_AUTHOR("Hyunsung Jang, "); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:s3c-mshci");