summaryrefslogtreecommitdiff
path: root/drivers/bus/mvebu-mbus.c
diff options
context:
space:
mode:
authorThomas Petazzoni <thomas.petazzoni@free-electrons.com>2015-01-09 10:59:04 -0600
committerAndrew Lunn <andrew@lunn.ch>2015-01-19 16:09:16 -0600
commit1737cac6936938a9bc52c03c4a3ff2032c702fa5 (patch)
tree7b9fe5e057ca940bab429cd17eaf9a1173494828 /drivers/bus/mvebu-mbus.c
parent7fdf3d8a0316ce31f87513f903addcb8f3b0dfb2 (diff)
bus: mvebu-mbus: make sure SDRAM CS for DMA don't overlap the MBus bridge window
The mvebu-mbus driver reads the SDRAM window registers, and make the information about the DRAM CS configuration available to device drivers using the mv_mbus_dram_info() API. This information is used by the DMA-capable device drivers to program their address decoding windows. Until now, we were basically providing the SDRAM window register details as is. However, it turns out that the DMA capability of the CESA cryptographic engine consists in doing DMA being the DRAM and the crypto SRAM mapped as a MBus window. For this case, it is very important that the SDRAM CS information does not overlap with the MBus bridge window. Therefore, this commit improves the mvebu-mbus driver to make sure we adjust the SDRAM CS information so that it doesn't overlap with the MBus bridge window. This problem was reported by Boris Brezillon, while working on the mv_cesa driver for Armada 37x/38x/XP. We use the memblock memory information to know where the usable RAM is located, as this information is guaranteed to be correct on all SoC variants. We could have used the MBus bridge window registers on Armada 370/XP, but they are not really used on Armada 375/38x (Cortex-A9 based), since the PL310 L2 filtering is used instead to discriminate between RAM accesses and I/O accesses. Therefore, using the memblock information is more generic and works accross the different platforms. Reported-by: Boris Brezillon <boris.brezillon@free-electrons.com> Signed-off-by: Thomas Petazzoni <thomas.petazzoni@free-electrons.com> [Andrew Lunn <andrew@lunn.ch>: Fixed merge conflict] Signed-off-by: Andrew Lunn <andrew@lunn.ch>
Diffstat (limited to 'drivers/bus/mvebu-mbus.c')
-rw-r--r--drivers/bus/mvebu-mbus.c105
1 files changed, 89 insertions, 16 deletions
diff --git a/drivers/bus/mvebu-mbus.c b/drivers/bus/mvebu-mbus.c
index a62c8ae253c3..fb9ec6221730 100644
--- a/drivers/bus/mvebu-mbus.c
+++ b/drivers/bus/mvebu-mbus.c
@@ -58,6 +58,7 @@
#include <linux/debugfs.h>
#include <linux/log2.h>
#include <linux/syscore_ops.h>
+#include <linux/memblock.h>
/*
* DDR target is the same on all platforms.
@@ -101,7 +102,9 @@
/* Relative to mbusbridge_base */
#define MBUS_BRIDGE_CTRL_OFF 0x0
+#define MBUS_BRIDGE_SIZE_MASK 0xffff0000
#define MBUS_BRIDGE_BASE_OFF 0x4
+#define MBUS_BRIDGE_BASE_MASK 0xffff0000
/* Maximum number of windows, for all known platforms */
#define MBUS_WINS_MAX 20
@@ -574,36 +577,106 @@ static unsigned int armada_xp_mbus_win_remap_offset(int win)
return MVEBU_MBUS_NO_REMAP;
}
+/*
+ * Use the memblock information to find the MBus bridge hole in the
+ * physical address space.
+ */
+static void __init
+mvebu_mbus_find_bridge_hole(uint64_t *start, uint64_t *end)
+{
+ struct memblock_region *r;
+ uint64_t s = 0;
+
+ for_each_memblock(memory, r) {
+ /*
+ * This part of the memory is above 4 GB, so we don't
+ * care for the MBus bridge hole.
+ */
+ if (r->base >= 0x100000000)
+ continue;
+
+ /*
+ * The MBus bridge hole is at the end of the RAM under
+ * the 4 GB limit.
+ */
+ if (r->base + r->size > s)
+ s = r->base + r->size;
+ }
+
+ *start = s;
+ *end = 0x100000000;
+}
+
static void __init
mvebu_mbus_default_setup_cpu_target(struct mvebu_mbus_state *mbus)
{
int i;
int cs;
+ uint64_t mbus_bridge_base, mbus_bridge_end;
mvebu_mbus_dram_info.mbus_dram_target_id = TARGET_DDR;
+ mvebu_mbus_find_bridge_hole(&mbus_bridge_base, &mbus_bridge_end);
+
for (i = 0, cs = 0; i < 4; i++) {
- u32 base = readl(mbus->sdramwins_base + DDR_BASE_CS_OFF(i));
- u32 size = readl(mbus->sdramwins_base + DDR_SIZE_CS_OFF(i));
+ u64 base = readl(mbus->sdramwins_base + DDR_BASE_CS_OFF(i));
+ u64 size = readl(mbus->sdramwins_base + DDR_SIZE_CS_OFF(i));
+ u64 end;
+ struct mbus_dram_window *w;
+
+ /* Ignore entries that are not enabled */
+ if (!(size & DDR_SIZE_ENABLED))
+ continue;
/*
- * We only take care of entries for which the chip
- * select is enabled, and that don't have high base
- * address bits set (devices can only access the first
- * 32 bits of the memory).
+ * Ignore entries whose base address is above 2^32,
+ * since devices cannot DMA to such high addresses
*/
- if ((size & DDR_SIZE_ENABLED) &&
- !(base & DDR_BASE_CS_HIGH_MASK)) {
- struct mbus_dram_window *w;
+ if (base & DDR_BASE_CS_HIGH_MASK)
+ continue;
- w = &mvebu_mbus_dram_info.cs[cs++];
- w->cs_index = i;
- w->mbus_attr = 0xf & ~(1 << i);
- if (mbus->hw_io_coherency)
- w->mbus_attr |= ATTR_HW_COHERENCY;
- w->base = base & DDR_BASE_CS_LOW_MASK;
- w->size = (size | ~DDR_SIZE_MASK) + 1;
+ base = base & DDR_BASE_CS_LOW_MASK;
+ size = (size | ~DDR_SIZE_MASK) + 1;
+ end = base + size;
+
+ /*
+ * Adjust base/size of the current CS to make sure it
+ * doesn't overlap with the MBus bridge hole. This is
+ * particularly important for devices that do DMA from
+ * DRAM to a SRAM mapped in a MBus window, such as the
+ * CESA cryptographic engine.
+ */
+
+ /*
+ * The CS is fully enclosed inside the MBus bridge
+ * area, so ignore it.
+ */
+ if (base >= mbus_bridge_base && end <= mbus_bridge_end)
+ continue;
+
+ /*
+ * Beginning of CS overlaps with end of MBus, raise CS
+ * base address, and shrink its size.
+ */
+ if (base >= mbus_bridge_base && end > mbus_bridge_end) {
+ size -= mbus_bridge_end - base;
+ base = mbus_bridge_end;
}
+
+ /*
+ * End of CS overlaps with beginning of MBus, shrink
+ * CS size.
+ */
+ if (base < mbus_bridge_base && end > mbus_bridge_base)
+ size -= end - mbus_bridge_base;
+
+ w = &mvebu_mbus_dram_info.cs[cs++];
+ w->cs_index = i;
+ w->mbus_attr = 0xf & ~(1 << i);
+ if (mbus->hw_io_coherency)
+ w->mbus_attr |= ATTR_HW_COHERENCY;
+ w->base = base;
+ w->size = size;
}
mvebu_mbus_dram_info.num_cs = cs;
}