diff --git a/arch/arm/include/asm/dma-mapping.h b/arch/arm/include/asm/dma-mapping.h
index bdd80ddbca34..9bff6ad2d8c8 100644
--- a/arch/arm/include/asm/dma-mapping.h
+++ b/arch/arm/include/asm/dma-mapping.h
@@ -33,10 +33,31 @@ static inline const struct dma_map_ops *get_arch_dma_ops(struct bus_type *bus)
  * addresses. They must not be used by drivers.
  */
 #ifndef __arch_pfn_to_dma
+
+static inline unsigned long __phys_to_dma_pfn_offset(struct device *dev,
+						     phys_addr_t paddr)
+{
+	if (paddr >= dev->dma_ranges.paddr &&
+	    paddr <= (dev->dma_ranges.paddr + dev->dma_ranges.size))
+		return dev->dma_ranges.pfn_offset;
+
+	return 0;
+}
+
+static inline unsigned long __dma_to_phys_pfn_offset(struct device *dev,
+						     dma_addr_t dma_addr)
+{
+	if (dma_addr >= dev->dma_ranges.dma_addr &&
+	    dma_addr <= (dev->dma_ranges.dma_addr + dev->dma_ranges.size))
+		return dev->dma_ranges.pfn_offset;
+
+	return 0;
+}
+
 static inline dma_addr_t pfn_to_dma(struct device *dev, unsigned long pfn)
 {
 	if (dev)
-		pfn -= dev->dma_pfn_offset;
+		pfn -= __phys_to_dma_pfn_offset(dev, __pfn_to_phys(pfn));
 	return (dma_addr_t)__pfn_to_bus(pfn);
 }
 
@@ -45,7 +66,7 @@ static inline unsigned long dma_to_pfn(struct device *dev, dma_addr_t addr)
 	unsigned long pfn = __bus_to_pfn(addr);
 
 	if (dev)
-		pfn += dev->dma_pfn_offset;
+		pfn += __dma_to_phys_pfn_offset(dev, addr);
 
 	return pfn;
 }
diff --git a/drivers/of/device.c b/drivers/of/device.c
index 27203bfd0b22..07a8cc1a7d7f 100644
--- a/drivers/of/device.c
+++ b/drivers/of/device.c
@@ -105,7 +105,7 @@ int of_dma_configure(struct device *dev, struct device_node *np, bool force_dma)
 		if (!force_dma)
 			return ret == -ENODEV ? 0 : ret;
 
-		dma_addr = offset = 0;
+		dma_addr = offset = paddr = 0;
 	} else {
 		offset = PFN_DOWN(paddr - dma_addr);
 
@@ -144,6 +144,11 @@ int of_dma_configure(struct device *dev, struct device_node *np, bool force_dma)
 
 	dev->dma_pfn_offset = offset;
 
+	dev->dma_ranges.paddr = paddr;
+	dev->dma_ranges.dma_addr = dma_addr;
+	dev->dma_ranges.size = size;
+	dev->dma_ranges.pfn_offset = offset;
+
 	/*
 	 * Limit coherent and dma mask based on size and default mask
 	 * set by the driver.
diff --git a/include/linux/device.h b/include/linux/device.h
index ce6db68c3f29..57006b51a989 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -293,6 +293,13 @@ struct device_dma_parameters {
 	unsigned long segment_boundary_mask;
 };
 
+struct dma_ranges {
+	u64 paddr;
+	u64 dma_addr;
+	u64 size;
+	unsigned long pfn_offset;
+};
+
 /**
  * struct device_connection - Device Connection Descriptor
  * @fwnode: The device node of the connected device
@@ -581,6 +588,7 @@ struct device {
 					     allocations such descriptors. */
 	u64		bus_dma_limit;	/* upstream dma constraint */
 	unsigned long	dma_pfn_offset;
+	struct dma_ranges dma_ranges;
 
 	struct device_dma_parameters *dma_parms;
 
diff --git a/include/linux/dma-direct.h b/include/linux/dma-direct.h
index 24b8684aa21d..4a46a15945ea 100644
--- a/include/linux/dma-direct.h
+++ b/include/linux/dma-direct.h
@@ -11,18 +11,47 @@ extern unsigned int zone_dma_bits;
 #ifdef CONFIG_ARCH_HAS_PHYS_TO_DMA
 #include <asm/dma-direct.h>
 #else
+
+static inline unsigned long __phys_to_dma_pfn_offset(struct device *dev,
+						     phys_addr_t paddr)
+{
+	if (!dev)
+		return 0;
+
+	if (paddr >= dev->dma_ranges.paddr &&
+	    paddr <= (dev->dma_ranges.paddr + dev->dma_ranges.size))
+		return dev->dma_ranges.pfn_offset
+
+	return 0;
+}
+
+static inline unsigned long __dma_to_phys_pfn_offset(struct device *dev,
+						     dma_addr_t dma_addr)
+{
+	if (!dev)
+		return 0;
+
+	if (dma_addr >= dev->dma_ranges.dma_addr &&
+	    dma_addr <= (dev->dma_ranges.dma_addr + dev->dma_ranges.size))
+		return dev->dma_ranges.pfn_offset
+
+	return 0;
+}
+
 static inline dma_addr_t __phys_to_dma(struct device *dev, phys_addr_t paddr)
 {
 	dma_addr_t dev_addr = (dma_addr_t)paddr;
+	unsigned long offset = __phys_to_dma_pfn_offset(dev, paddr);
 
-	return dev_addr - ((dma_addr_t)dev->dma_pfn_offset << PAGE_SHIFT);
+	return dev_addr - ((dma_addr_t)offset << PAGE_SHIFT);
 }
 
 static inline phys_addr_t __dma_to_phys(struct device *dev, dma_addr_t dev_addr)
 {
 	phys_addr_t paddr = (phys_addr_t)dev_addr;
+	unsigned long offset = __dma_to_phys_pfn_offset(dev, dev_addr);
 
-	return paddr + ((phys_addr_t)dev->dma_pfn_offset << PAGE_SHIFT);
+	return paddr + ((phys_addr_t)offset << PAGE_SHIFT);
 }
 #endif /* !CONFIG_ARCH_HAS_PHYS_TO_DMA */
 
diff --git a/kernel/dma/coherent.c b/kernel/dma/coherent.c
index 551b0eb7028a..7a68fd09f5d0 100644
--- a/kernel/dma/coherent.c
+++ b/kernel/dma/coherent.c
@@ -31,10 +31,13 @@ static inline struct dma_coherent_mem *dev_get_coherent_memory(struct device *de
 static inline dma_addr_t dma_get_device_base(struct device *dev,
 					     struct dma_coherent_mem * mem)
 {
-	if (mem->use_dev_dma_pfn_offset)
-		return (mem->pfn_base - dev->dma_pfn_offset) << PAGE_SHIFT;
-	else
+	if (mem->use_dev_dma_pfn_offset) {
+		unsigned long offset = __phys_to_dma_pfn_offset(dev,
+						__pfn_to_phys(mem->pfn_base));
+		return (mem->pfn_base - offset) << PAGE_SHIFT;
+	} else {
 		return mem->device_base;
+	}
 }
 
 static int dma_init_coherent_memory(phys_addr_t phys_addr,
