S5P-MFC driver relies on the way the ARM DMA-IOMMU glue code works: the IOVA allocator uses first-fit algorithm, so the first allocated buffer is at 0x0 DMA/IOVA address. This is not true for the generic IOMMU-DMA glue code that will be used for ARM architecture soon, so add the needed code to support it too. Signed-off-by: Marek Szyprowski <m.szyprowski@xxxxxxxxxxx> --- drivers/media/platform/s5p-mfc/s5p_mfc.c | 35 ++++++++++++++++++- .../media/platform/s5p-mfc/s5p_mfc_common.h | 2 ++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc.c b/drivers/media/platform/s5p-mfc/s5p_mfc.c index 4e50c342b322..63cbf1d29c43 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc.c +++ b/drivers/media/platform/s5p-mfc/s5p_mfc.c @@ -10,6 +10,7 @@ #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/io.h> +#include <linux/iommu.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/sched.h> @@ -1201,6 +1202,10 @@ static int s5p_mfc_configure_common_memory(struct s5p_mfc_dev *mfc_dev) if (!mfc_dev->mem_bitmap) return -ENOMEM; + /* MFC v5 can access memory only via the 128M window */ + if (exynos_is_iommu_available(dev) && !IS_MFCV6_PLUS(mfc_dev)) + dma_set_mask_and_coherent(dev, SZ_128M - 1); + mfc_dev->mem_virt = dma_alloc_coherent(dev, mem_size, &mfc_dev->mem_base, GFP_KERNEL); if (!mfc_dev->mem_virt) { @@ -1218,13 +1223,37 @@ static int s5p_mfc_configure_common_memory(struct s5p_mfc_dev *mfc_dev) * as used (to keep required base alignment) and adjust base address */ if (mfc_dev->mem_base == (dma_addr_t)0) { - unsigned int offset = 1 << MFC_BASE_ALIGN_ORDER; + unsigned int offset = MFC_MIN_VALID_BASE; bitmap_set(mfc_dev->mem_bitmap, 0, offset >> PAGE_SHIFT); mfc_dev->dma_base[BANK_L_CTX] += offset; mfc_dev->dma_base[BANK_R_CTX] += offset; } + /* + * Generic DMA-IOMMU use last-fit memory allocation algorithm, so + * remap the firmware to the lowest supported address for MFC v5 to + * let HW properly address buffers as an offset from the firmware. + */ + if (IS_ENABLED(CONFIG_IOMMU_DMA) && exynos_is_iommu_available(dev) && + !IS_MFCV6_PLUS(mfc_dev)) { + struct sg_table sgt; + int size; + + if (dma_get_sgtable(dev, &sgt, mfc_dev->mem_virt, + mfc_dev->mem_base, mfc_dev->mem_size) != 0) + return -ENOMEM; + size = iommu_map_sgtable(iommu_get_domain_for_dev(dev), + MFC_MIN_VALID_BASE, &sgt, + IOMMU_READ | IOMMU_WRITE); + sg_free_table(&sgt); + if (size != mem_size) + return -ENOMEM; + + mfc_dev->dma_base[BANK_L_CTX] = MFC_MIN_VALID_BASE; + mfc_dev->dma_base[BANK_R_CTX] = MFC_MIN_VALID_BASE; + } + /* Firmware allocation cannot fail in this case */ s5p_mfc_alloc_firmware(mfc_dev); @@ -1241,6 +1270,10 @@ static void s5p_mfc_unconfigure_common_memory(struct s5p_mfc_dev *mfc_dev) { struct device *dev = &mfc_dev->plat_dev->dev; + if (IS_ENABLED(CONFIG_IOMMU_DMA) && exynos_is_iommu_available(dev) && + !IS_MFCV6_PLUS(mfc_dev)) + iommu_unmap(iommu_get_domain_for_dev(dev), MFC_MIN_VALID_BASE, + mfc_dev->mem_size); dma_free_coherent(dev, mfc_dev->mem_size, mfc_dev->mem_virt, mfc_dev->mem_base); kfree(mfc_dev->mem_bitmap); diff --git a/drivers/media/platform/s5p-mfc/s5p_mfc_common.h b/drivers/media/platform/s5p-mfc/s5p_mfc_common.h index 96d1ecd1521b..f28c943b8426 100644 --- a/drivers/media/platform/s5p-mfc/s5p_mfc_common.h +++ b/drivers/media/platform/s5p-mfc/s5p_mfc_common.h @@ -37,6 +37,8 @@ #define MFC_BANK2_ALIGN_ORDER 13 #define MFC_BASE_ALIGN_ORDER 17 +#define MFC_MIN_VALID_BASE (1 << MFC_BASE_ALIGN_ORDER) + #define MFC_FW_MAX_VERSIONS 2 #include <media/videobuf2-dma-contig.h> -- 2.17.1