MIPS: PowerTV: Use O(1) algorthm for phys_to_dma/dma_to_phys
authorDavid VomLehn <dvomlehn@cisco.com>
Fri, 21 May 2010 18:25:36 +0000 (11:25 -0700)
committerRalf Baechle <ralf@linux-mips.org>
Thu, 5 Aug 2010 12:25:40 +0000 (13:25 +0100)
Replace phys_to_dma()/dma_to_phys() looping algorithm with an O(1) algorithm
The approach taken is inspired by the sparse memory implementation: take a
certain number of high-order bits off the address them, use this as an
index into a table containing an offset to the desired address and add
it to the original value. There is a table for mapping physical addresses
to DMA addresses and another one for the reverse mapping. The table sizes
depend on how fine-grained the mappings need to be; Coarser granularity
less to smaller tables.  On a processor with 32-bit physical and DMA
addresses, with 4 MIB granularity, memory usage is two 2048-byte arrays.
Each 32-byte cache line thus covers 64 MiB of address space.

Also, renames phys_to_bus() to phys_to_dma() and bus_to_phys() to
dma_to_phys() to align with kernel usage.

[Ralf: Fixed silly build breakage due to stackoverflow warning caused by
huge array on stack.]

Signed-off-by: David VomLehn <dvomlehn@cisco.com>
To: linux-mips@linux-mips.org
Patchwork: https://patchwork.linux-mips.org/patch/1257/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
arch/mips/include/asm/mach-powertv/dma-coherence.h
arch/mips/include/asm/mach-powertv/ioremap.h
arch/mips/powertv/Makefile
arch/mips/powertv/asic/asic_devices.c
arch/mips/powertv/ioremap.c [new file with mode: 0644]
arch/mips/powertv/memory.c

index 5b8d5ebeb838add15df89cc5d87e4de6295e26bd..f76029c2406e6e33acae1edb2db73a3d0efb0344 100644 (file)
@@ -65,21 +65,21 @@ static inline dma_addr_t plat_map_dma_mem(struct device *dev, void *addr,
        size_t size)
 {
        if (is_kseg2(addr))
-               return phys_to_bus(virt_to_phys_from_pte(addr));
+               return phys_to_dma(virt_to_phys_from_pte(addr));
        else
-               return phys_to_bus(virt_to_phys(addr));
+               return phys_to_dma(virt_to_phys(addr));
 }
 
 static inline dma_addr_t plat_map_dma_mem_page(struct device *dev,
        struct page *page)
 {
-       return phys_to_bus(page_to_phys(page));
+       return phys_to_dma(page_to_phys(page));
 }
 
 static inline unsigned long plat_dma_addr_to_phys(struct device *dev,
        dma_addr_t dma_addr)
 {
-       return bus_to_phys(dma_addr);
+       return dma_to_phys(dma_addr);
 }
 
 static inline void plat_unmap_dma_mem(struct device *dev, dma_addr_t dma_addr,
index e6276d5146e80365a88f861997c21b0d2a1c1dec..076f2eeaa5759571835a51dadafb96dce41c3d26 100644 (file)
 #define __ASM_MACH_POWERTV_IOREMAP_H
 
 #include <linux/types.h>
+#include <linux/log2.h>
+#include <linux/compiler.h>
 
-#define LOW_MEM_BOUNDARY_PHYS  0x20000000
-#define LOW_MEM_BOUNDARY_MASK  (~(LOW_MEM_BOUNDARY_PHYS - 1))
+#include <asm/pgtable-bits.h>
+#include <asm/addrspace.h>
+
+/* We're going to mess with bits, so get sizes */
+#define IOR_BPC                        8                       /* Bits per char */
+#define IOR_PHYS_BITS          (IOR_BPC * sizeof(phys_addr_t))
+#define IOR_DMA_BITS           (IOR_BPC * sizeof(dma_addr_t))
 
 /*
- * The bus addresses are different than the physical addresses that
- * the processor sees by an offset. This offset varies by ASIC
- * version. Define a variable to hold the offset and some macros to
- * make the conversion simpler. */
-extern unsigned long phys_to_bus_offset;
-
-#ifdef CONFIG_HIGHMEM
-#define MEM_GAP_PHYS           0x60000000
+ * Define the granularity of physical/DMA mapping in terms of the number
+ * of bits that defines the offset within a grain. These will be the
+ * least significant bits of the address. The rest of a physical or DMA
+ * address will be used to index into an appropriate table to find the
+ * offset to add to the address to yield the corresponding DMA or physical
+ * address, respectively.
+ */
+#define IOR_LSBITS             22                      /* Bits in a grain */
+
 /*
- * TODO: We will use the hard code for conversion between physical and
- * bus until the bootloader releases their device tree to us.
+ * Compute the number of most significant address bits after removing those
+ * used for the offset within a grain and then compute the number of table
+ * entries for the conversion.
  */
-#define phys_to_bus(x) (((x) < LOW_MEM_BOUNDARY_PHYS) ? \
-       ((x) + phys_to_bus_offset) : (x))
-#define bus_to_phys(x) (((x) < MEM_GAP_PHYS_ADDR) ? \
-       ((x) - phys_to_bus_offset) : (x))
-#else
-#define phys_to_bus(x) ((x) + phys_to_bus_offset)
-#define bus_to_phys(x) ((x) - phys_to_bus_offset)
-#endif
+#define IOR_PHYS_MSBITS                (IOR_PHYS_BITS - IOR_LSBITS)
+#define IOR_NUM_PHYS_TO_DMA    ((phys_addr_t) 1 << IOR_PHYS_MSBITS)
+
+#define IOR_DMA_MSBITS         (IOR_DMA_BITS - IOR_LSBITS)
+#define IOR_NUM_DMA_TO_PHYS    ((dma_addr_t) 1 << IOR_DMA_MSBITS)
 
 /*
- * Determine whether the address we are given is for an ASIC device
- * Params:  addr    Address to check
- * Returns: Zero if the address is not for ASIC devices, non-zero
- *      if it is.
+ * Define data structures used as elements in the arrays for the conversion
+ * between physical and DMA addresses. We do some slightly fancy math to
+ * compute the width of the offset element of the conversion tables so
+ * that we can have the smallest conversion tables. Next, round up the
+ * sizes to the next higher power of two, i.e. the offset element will have
+ * 8, 16, 32, 64, etc. bits. This eliminates the need to mask off any
+ * bits.  Finally, we compute a shift value that puts the most significant
+ * bits of the offset into the most significant bits of the offset element.
+ * This makes it more efficient on processors without barrel shifters and
+ * easier to see the values if the conversion table is dumped in binary.
  */
-static inline int asic_is_device_addr(phys_t addr)
+#define _IOR_OFFSET_WIDTH(n)   (1 << order_base_2(n))
+#define IOR_OFFSET_WIDTH(n) \
+       (_IOR_OFFSET_WIDTH(n) < 8 ? 8 : _IOR_OFFSET_WIDTH(n))
+
+#define IOR_PHYS_OFFSET_BITS   IOR_OFFSET_WIDTH(IOR_PHYS_MSBITS)
+#define IOR_PHYS_SHIFT         (IOR_PHYS_BITS - IOR_PHYS_OFFSET_BITS)
+
+#define IOR_DMA_OFFSET_BITS    IOR_OFFSET_WIDTH(IOR_DMA_MSBITS)
+#define IOR_DMA_SHIFT          (IOR_DMA_BITS - IOR_DMA_OFFSET_BITS)
+
+struct ior_phys_to_dma {
+       dma_addr_t offset:IOR_DMA_OFFSET_BITS __packed
+               __aligned((IOR_DMA_OFFSET_BITS / IOR_BPC));
+};
+
+struct ior_dma_to_phys {
+       dma_addr_t offset:IOR_PHYS_OFFSET_BITS __packed
+               __aligned((IOR_PHYS_OFFSET_BITS / IOR_BPC));
+};
+
+extern struct ior_phys_to_dma _ior_phys_to_dma[IOR_NUM_PHYS_TO_DMA];
+extern struct ior_dma_to_phys _ior_dma_to_phys[IOR_NUM_DMA_TO_PHYS];
+
+static inline dma_addr_t _phys_to_dma_offset_raw(phys_addr_t phys)
 {
-       return !((phys_t)addr & (phys_t) LOW_MEM_BOUNDARY_MASK);
+       return (dma_addr_t)_ior_phys_to_dma[phys >> IOR_LSBITS].offset;
 }
 
-/*
- * Determine whether the address we are given is external RAM mappable
- * into KSEG1.
- * Params:  addr    Address to check
- * Returns: Zero if the address is not for external RAM and
- */
-static inline int asic_is_lowmem_ram_addr(phys_t addr)
+static inline dma_addr_t _dma_to_phys_offset_raw(dma_addr_t dma)
 {
-       /*
-        * The RAM always starts at the following address in the processor's
-        * physical address space
-        */
-       static const phys_t phys_ram_base = 0x10000000;
-       phys_t bus_ram_base;
+       return (dma_addr_t)_ior_dma_to_phys[dma >> IOR_LSBITS].offset;
+}
 
-       bus_ram_base = phys_to_bus_offset + phys_ram_base;
+/* These are not portable and should not be used in drivers. Drivers should
+ * be using ioremap() and friends to map physical addreses to virtual
+ * addresses and dma_map*() and friends to map virtual addresses into DMA
+ * addresses and back.
+ */
+static inline dma_addr_t phys_to_dma(phys_addr_t phys)
+{
+       return phys + (_phys_to_dma_offset_raw(phys) << IOR_PHYS_SHIFT);
+}
 
-       return addr >= bus_ram_base &&
-               addr < (bus_ram_base + (LOW_MEM_BOUNDARY_PHYS - phys_ram_base));
+static inline phys_addr_t dma_to_phys(dma_addr_t dma)
+{
+       return dma + (_dma_to_phys_offset_raw(dma) << IOR_DMA_SHIFT);
 }
 
+extern void ioremap_add_map(dma_addr_t phys, phys_addr_t alias,
+       dma_addr_t size);
+
 /*
  * Allow physical addresses to be fixed up to help peripherals located
  * outside the low 32-bit range -- generic pass-through version.
@@ -77,10 +114,50 @@ static inline phys_t fixup_bigphys_addr(phys_t phys_addr, phys_t size)
        return phys_addr;
 }
 
-static inline void __iomem *plat_ioremap(phys_t offset, unsigned long size,
+/*
+ * Handle the special case of addresses the area aliased into the first
+ * 512 MiB of the processor's physical address space. These turn into either
+ * kseg0 or kseg1 addresses, depending on flags.
+ */
+static inline void __iomem *plat_ioremap(phys_t start, unsigned long size,
        unsigned long flags)
 {
-       return NULL;
+       phys_addr_t start_offset;
+       void __iomem *result = NULL;
+
+       /* Start by checking to see whether this is an aliased address */
+       start_offset = _dma_to_phys_offset_raw(start);
+
+       /*
+        * If:
+        * o    the memory is aliased into the first 512 MiB, and
+        * o    the start and end are in the same RAM bank, and
+        * o    we don't have a zero size or wrap around, and
+        * o    we are supposed to create an uncached mapping,
+        *      handle this is a kseg0 or kseg1 address
+        */
+       if (start_offset != 0) {
+               phys_addr_t last;
+               dma_addr_t dma_to_phys_offset;
+
+               last = start + size - 1;
+               dma_to_phys_offset =
+                       _dma_to_phys_offset_raw(last) << IOR_DMA_SHIFT;
+
+               if (dma_to_phys_offset == start_offset &&
+                       size != 0 && start <= last) {
+                       phys_t adjusted_start;
+                       adjusted_start = start + start_offset;
+                       if (flags == _CACHE_UNCACHED)
+                               result = (void __iomem *) (unsigned long)
+                                       CKSEG1ADDR(adjusted_start);
+                       else
+                               result = (void __iomem *) (unsigned long)
+                                       CKSEG0ADDR(adjusted_start);
+               }
+       }
+
+       return result;
 }
 
 static inline int plat_iounmap(const volatile void __iomem *addr)
index 0a0d73c0564fa9d4743d6417f73c653ae38b754d..e9fe1c6efe163017644b941360d388e0ac30ada5 100644 (file)
@@ -23,6 +23,7 @@
 # under Linux.
 #
 
-obj-y += init.o memory.o reset.o time.o powertv_setup.o asic/ pci/
+obj-y += init.o ioremap.o memory.o powertv_setup.o reset.o time.o \
+       asic/ pci/
 
 EXTRA_CFLAGS += -Wall -Werror
index 9ec523e4dd060afb93ebd5fea93c07450daf933f..c81dd497ed7ba1895f87ec14e56a2d298f4bca8a 100644 (file)
@@ -80,8 +80,8 @@ static bool usb_configured;
  * Don't recommend to use it directly, it is usually used by kernel internally.
  * Portable code should be using interfaces such as ioremp, dma_map_single, etc.
  */
-unsigned long phys_to_bus_offset;
-EXPORT_SYMBOL(phys_to_bus_offset);
+unsigned long phys_to_dma_offset;
+EXPORT_SYMBOL(phys_to_dma_offset);
 
 /*
  *
@@ -533,10 +533,10 @@ void __init configure_platform(void)
 
        switch (asic) {
        case ASIC_ZEUS:
-               phys_to_bus_offset = 0x30000000;
+               phys_to_dma_offset = 0x30000000;
                break;
        case ASIC_CALLIOPE:
-               phys_to_bus_offset = 0x10000000;
+               phys_to_dma_offset = 0x10000000;
                break;
        case ASIC_CRONUSLITE:
                /* Fall through */
@@ -546,10 +546,10 @@ void __init configure_platform(void)
                 * 0x2XXXXXXX. If 0x10000000 aliases into 0x60000000-
                 * 0x6XXXXXXX, the offset should be 0x50000000, not 0x10000000.
                 */
-               phys_to_bus_offset = 0x10000000;
+               phys_to_dma_offset = 0x10000000;
                break;
        default:
-               phys_to_bus_offset = 0x00000000;
+               phys_to_dma_offset = 0x00000000;
                break;
        }
 }
@@ -603,7 +603,7 @@ void __init platform_alloc_bootmem(void)
                int size = gp_resources[i].end - gp_resources[i].start + 1;
                if ((gp_resources[i].start != 0) &&
                        ((gp_resources[i].flags & IORESOURCE_MEM) != 0)) {
-                       reserve_bootmem(bus_to_phys(gp_resources[i].start),
+                       reserve_bootmem(dma_to_phys(gp_resources[i].start),
                                size, 0);
                        total += gp_resources[i].end -
                                gp_resources[i].start + 1;
@@ -627,7 +627,7 @@ void __init platform_alloc_bootmem(void)
 
                        else {
                                gp_resources[i].start =
-                                       phys_to_bus(virt_to_phys(mem));
+                                       phys_to_dma(virt_to_phys(mem));
                                gp_resources[i].end =
                                        gp_resources[i].start + size - 1;
                                total += size;
@@ -691,7 +691,7 @@ static void __init pmem_setup_resource(void)
        if (resource && pmemaddr && pmemlen) {
                /* The address provided by bootloader is in kseg0. Convert to
                 * a bus address. */
-               resource->start = phys_to_bus(pmemaddr - 0x80000000);
+               resource->start = phys_to_dma(pmemaddr - 0x80000000);
                resource->end = resource->start + pmemlen - 1;
 
                pr_info("persistent memory: start=0x%x  end=0x%x\n",
diff --git a/arch/mips/powertv/ioremap.c b/arch/mips/powertv/ioremap.c
new file mode 100644 (file)
index 0000000..a77c6f6
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ *                     ioremap.c
+ *
+ * Support for mapping between dma_addr_t values a phys_addr_t values.
+ *
+ * Copyright (C) 2005-2009 Scientific-Atlanta, Inc.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author:       David VomLehn <dvomlehn@cisco.com>
+ *
+ * Description:  Defines the platform resources for the SA settop.
+ *
+ * NOTE: The bootloader allocates persistent memory at an address which is
+ * 16 MiB below the end of the highest address in KSEG0. All fixed
+ * address memory reservations must avoid this region.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include <asm/mach-powertv/ioremap.h>
+
+/*
+ * Define the sizes of and masks for grains in physical and DMA space. The
+ * values are the same but the types are not.
+ */
+#define IOR_PHYS_GRAIN         ((phys_addr_t) 1 << IOR_LSBITS)
+#define IOR_PHYS_GRAIN_MASK    (IOR_PHYS_GRAIN - 1)
+
+#define IOR_DMA_GRAIN          ((dma_addr_t) 1 << IOR_LSBITS)
+#define IOR_DMA_GRAIN_MASK     (IOR_DMA_GRAIN - 1)
+
+/*
+ * Values that, when accessed by an index derived from a phys_addr_t and
+ * added to phys_addr_t value, yield a DMA address
+ */
+struct ior_phys_to_dma _ior_phys_to_dma[IOR_NUM_PHYS_TO_DMA];
+EXPORT_SYMBOL(_ior_phys_to_dma);
+
+/*
+ * Values that, when accessed by an index derived from a dma_addr_t and
+ * added to that dma_addr_t value, yield a physical address
+ */
+struct ior_dma_to_phys _ior_dma_to_phys[IOR_NUM_DMA_TO_PHYS];
+EXPORT_SYMBOL(_ior_dma_to_phys);
+
+/**
+ * setup_dma_to_phys - set up conversion from DMA to physical addresses
+ * @dma_idx:   Top IOR_LSBITS bits of the DMA address, i.e. an index
+ *             into the array _dma_to_phys.
+ * @delta:     Value that, when added to the DMA address, will yield the
+ *             physical address
+ * @s:         Number of bytes in the section of memory with the given delta
+ *             between DMA and physical addresses.
+ */
+static void setup_dma_to_phys(dma_addr_t dma, phys_addr_t delta, dma_addr_t s)
+{
+       int dma_idx, first_idx, last_idx;
+       phys_addr_t first, last;
+
+       /*
+        * Calculate the first and last indices, rounding the first up and
+        * the second down.
+        */
+       first = dma & ~IOR_DMA_GRAIN_MASK;
+       last = (dma + s - 1) & ~IOR_DMA_GRAIN_MASK;
+       first_idx = first >> IOR_LSBITS;                /* Convert to indices */
+       last_idx = last >> IOR_LSBITS;
+
+       for (dma_idx = first_idx; dma_idx <= last_idx; dma_idx++)
+               _ior_dma_to_phys[dma_idx].offset = delta >> IOR_DMA_SHIFT;
+}
+
+/**
+ * setup_phys_to_dma - set up conversion from DMA to physical addresses
+ * @phys_idx:  Top IOR_LSBITS bits of the DMA address, i.e. an index
+ *             into the array _phys_to_dma.
+ * @delta:     Value that, when added to the DMA address, will yield the
+ *             physical address
+ * @s:         Number of bytes in the section of memory with the given delta
+ *             between DMA and physical addresses.
+ */
+static void setup_phys_to_dma(phys_addr_t phys, dma_addr_t delta, phys_addr_t s)
+{
+       int phys_idx, first_idx, last_idx;
+       phys_addr_t first, last;
+
+       /*
+        * Calculate the first and last indices, rounding the first up and
+        * the second down.
+        */
+       first = phys & ~IOR_PHYS_GRAIN_MASK;
+       last = (phys + s - 1) & ~IOR_PHYS_GRAIN_MASK;
+       first_idx = first >> IOR_LSBITS;                /* Convert to indices */
+       last_idx = last >> IOR_LSBITS;
+
+       for (phys_idx = first_idx; phys_idx <= last_idx; phys_idx++)
+               _ior_phys_to_dma[phys_idx].offset = delta >> IOR_PHYS_SHIFT;
+}
+
+/**
+ * ioremap_add_map - add to the physical and DMA address conversion arrays
+ * @phys:      Process's view of the address of the start of the memory chunk
+ * @dma:       DMA address of the start of the memory chunk
+ * @size:      Size, in bytes, of the chunk of memory
+ *
+ * NOTE: It might be obvious, but the assumption is that all @size bytes have
+ * the same offset between the physical address and the DMA address.
+ */
+void ioremap_add_map(phys_addr_t phys, phys_addr_t dma, phys_addr_t size)
+{
+       if (size == 0)
+               return;
+
+       if ((dma & IOR_DMA_GRAIN_MASK) != 0 ||
+               (phys & IOR_PHYS_GRAIN_MASK) != 0 ||
+               (size & IOR_PHYS_GRAIN_MASK) != 0)
+               pr_crit("Memory allocation must be in chunks of 0x%x bytes\n",
+                       IOR_PHYS_GRAIN);
+
+       setup_dma_to_phys(dma, phys - dma, size);
+       setup_phys_to_dma(phys, dma - phys, size);
+}
index f49eb3d0358b87e7c1fb102a5600acd746ce9af0..73880ad29bc201ddc9870ab5b968f5341583e6c6 100644 (file)
 #include <asm/sections.h>
 
 #include <asm/mips-boards/prom.h>
+#include <asm/mach-powertv/asic.h>
+#include <asm/mach-powertv/ioremap.h>
 
 #include "init.h"
 
 /* Memory constants */
 #define KIBIBYTE(n)            ((n) * 1024)    /* Number of kibibytes */
 #define MEBIBYTE(n)            ((n) * KIBIBYTE(1024)) /* Number of mebibytes */
-#define DEFAULT_MEMSIZE                MEBIBYTE(256)   /* If no memsize provided */
-#define LOW_MEM_MAX            MEBIBYTE(252)   /* Max usable low mem */
-#define RES_BOOTLDR_MEMSIZE    MEBIBYTE(1)     /* Memory reserved for bldr */
-#define BOOT_MEM_SIZE          KIBIBYTE(256)   /* Memory reserved for bldr */
-#define PHYS_MEM_START         0x10000000      /* Start of physical memory */
+#define DEFAULT_MEMSIZE                MEBIBYTE(128)   /* If no memsize provided */
 
-char __initdata cmdline[COMMAND_LINE_SIZE];
+#define BLDR_SIZE      KIBIBYTE(256)           /* Memory reserved for bldr */
+#define RV_SIZE                MEBIBYTE(4)             /* Size of reset vector */
 
-void __init prom_meminit(void)
+#define LOW_MEM_END    0x20000000              /* Highest low memory address */
+#define BLDR_ALIAS     0x10000000              /* Bootloader address */
+#define RV_PHYS                0x1fc00000              /* Reset vector address */
+#define LOW_RAM_END    RV_PHYS                 /* End of real RAM in low mem */
+
+/*
+ * Very low-level conversion from processor physical address to device
+ * DMA address for the first bank of memory.
+ */
+#define PHYS_TO_DMA(paddr)     ((paddr) + (CONFIG_LOW_RAM_DMA - LOW_RAM_ALIAS))
+
+unsigned long ptv_memsize;
+
+/*
+ * struct low_mem_reserved - Items in low memmory that are reserved
+ * @start:     Physical address of item
+ * @size:      Size, in bytes, of this item
+ * @is_aliased:        True if this is RAM aliased from another location. If false,
+ *             it is something other than aliased RAM and the RAM in the
+ *             unaliased address is still visible outside of low memory.
+ */
+struct low_mem_reserved {
+       phys_addr_t     start;
+       phys_addr_t     size;
+       bool            is_aliased;
+};
+
+/*
+ * Must be in ascending address order
+ */
+struct low_mem_reserved low_mem_reserved[] = {
+       {BLDR_ALIAS, BLDR_SIZE, true},  /* Bootloader RAM */
+       {RV_PHYS, RV_SIZE, false},      /* Reset vector */
+};
+
+/*
+ * struct mem_layout - layout of a piece of the system RAM
+ * @phys:      Physical address of the start of this piece of RAM. This is the
+ *             address at which both the processor and I/O devices see the
+ *             RAM.
+ * @alias:     Alias of this piece of memory in order to make it appear in
+ *             the low memory part of the processor's address space. I/O
+ *             devices don't see anything here.
+ * @size:      Size, in bytes, of this piece of RAM
+ */
+struct mem_layout {
+       phys_addr_t     phys;
+       phys_addr_t     alias;
+       phys_addr_t     size;
+};
+
+/*
+ * struct mem_layout_list - list descriptor for layouts of system RAM pieces
+ * @family:    Specifies the family being described
+ * @n:         Number of &struct mem_layout elements
+ * @layout:    Pointer to the list of &mem_layout structures
+ */
+struct mem_layout_list {
+       enum family_type        family;
+       size_t                  n;
+       struct mem_layout       *layout;
+};
+
+static struct mem_layout f1500_layout[] = {
+       {0x20000000, 0x10000000, MEBIBYTE(256)},
+};
+
+static struct mem_layout f4500_layout[] = {
+       {0x40000000, 0x10000000, MEBIBYTE(256)},
+       {0x20000000, 0x20000000, MEBIBYTE(32)},
+};
+
+static struct mem_layout f8500_layout[] = {
+       {0x40000000, 0x10000000, MEBIBYTE(256)},
+       {0x20000000, 0x20000000, MEBIBYTE(32)},
+       {0x30000000, 0x30000000, MEBIBYTE(32)},
+};
+
+static struct mem_layout fx600_layout[] = {
+       {0x20000000, 0x10000000, MEBIBYTE(256)},
+       {0x60000000, 0x60000000, MEBIBYTE(128)},
+};
+
+static struct mem_layout_list layout_list[] = {
+       {FAMILY_1500, ARRAY_SIZE(f1500_layout), f1500_layout},
+       {FAMILY_1500VZE, ARRAY_SIZE(f1500_layout), f1500_layout},
+       {FAMILY_1500VZF, ARRAY_SIZE(f1500_layout), f1500_layout},
+       {FAMILY_4500, ARRAY_SIZE(f4500_layout), f4500_layout},
+       {FAMILY_8500, ARRAY_SIZE(f8500_layout), f8500_layout},
+       {FAMILY_8500RNG, ARRAY_SIZE(f8500_layout), f8500_layout},
+       {FAMILY_4600, ARRAY_SIZE(fx600_layout), fx600_layout},
+       {FAMILY_4600VZA, ARRAY_SIZE(fx600_layout), fx600_layout},
+       {FAMILY_8600, ARRAY_SIZE(fx600_layout), fx600_layout},
+       {FAMILY_8600VZB, ARRAY_SIZE(fx600_layout), fx600_layout},
+};
+
+/* If we can't determine the layout, use this */
+static struct mem_layout default_layout[] = {
+       {0x20000000, 0x10000000, MEBIBYTE(128)},
+};
+
+/**
+ * register_non_ram - register low memory not available for RAM usage
+ */
+static __init void register_non_ram(void)
+{
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(low_mem_reserved); i++)
+               add_memory_region(low_mem_reserved[i].start,
+                       low_mem_reserved[i].size, BOOT_MEM_RESERVED);
+}
+
+/**
+ * get_memsize - get the size of memory as a single bank
+ */
+static phys_addr_t get_memsize(void)
 {
+       static char cmdline[COMMAND_LINE_SIZE] __initdata;
+       phys_addr_t memsize = 0;
        char *memsize_str;
-       unsigned long memsize = 0;
-       unsigned int physend;
        char *ptr;
-       int low_mem;
-       int high_mem;
 
        /* Check the command line first for a memsize directive */
        strcpy(cmdline, arcs_cmdline);
@@ -73,96 +186,156 @@ void __init prom_meminit(void)
                if (memsize == 0) {
                        if (_prom_memsize != 0) {
                                memsize = _prom_memsize;
-                               pr_info("_prom_memsize = 0x%lx\n", memsize);
+                               pr_info("_prom_memsize = 0x%x\n", memsize);
                                /* add in memory that the bootloader doesn't
                                 * report */
-                               memsize += BOOT_MEM_SIZE;
+                               memsize += BLDR_SIZE;
                        } else {
                                memsize = DEFAULT_MEMSIZE;
                                pr_info("Memsize not passed by bootloader, "
-                                       "defaulting to 0x%lx\n", memsize);
+                                       "defaulting to 0x%x\n", memsize);
                        }
                }
        }
 
-       physend = PFN_ALIGN(&_end) - 0x80000000;
-       if (memsize > LOW_MEM_MAX) {
-               low_mem = LOW_MEM_MAX;
-               high_mem = memsize - low_mem;
-       } else {
-               low_mem = memsize;
-               high_mem = 0;
+       return memsize;
+}
+
+/**
+ * register_low_ram - register an aliased section of RAM
+ * @p:         Alias address of memory
+ * @n:         Number of bytes in this section of memory
+ *
+ * Returns the number of bytes registered
+ *
+ */
+static __init phys_addr_t register_low_ram(phys_addr_t p, phys_addr_t n)
+{
+       phys_addr_t s;
+       int i;
+       phys_addr_t orig_n;
+
+       orig_n = n;
+
+       BUG_ON(p + n > RV_PHYS);
+
+       for (i = 0; n != 0 && i < ARRAY_SIZE(low_mem_reserved); i++) {
+               phys_addr_t start;
+               phys_addr_t size;
+
+               start = low_mem_reserved[i].start;
+               size = low_mem_reserved[i].size;
+
+               /* Handle memory before this low memory section */
+               if (p < start) {
+                       phys_addr_t s;
+                       s = min(n, start - p);
+                       add_memory_region(p, s, BOOT_MEM_RAM);
+                       p += s;
+                       n -= s;
+               }
+
+               /* Handle the low memory section itself. If it's aliased,
+                * we reduce the number of byes left, but if not, the RAM
+                * is available elsewhere and we don't reduce the number of
+                * bytes remaining. */
+               if (p == start) {
+                       if (low_mem_reserved[i].is_aliased) {
+                               s = min(n, size);
+                               n -= s;
+                               p += s;
+                       } else
+                               p += n;
+               }
        }
 
+       return orig_n - n;
+}
+
 /*
- * TODO: We will use the hard code for memory configuration until
- * the bootloader releases their device tree to us.
+ * register_ram - register real RAM
+ * @p: Address of memory as seen by devices
+ * @alias:     If the memory is seen at an additional address by the processor,
+ *             this will be the address, otherwise it is the same as @p.
+ * @n:         Number of bytes in this section of memory
  */
+static __init void register_ram(phys_addr_t p, phys_addr_t alias,
+       phys_addr_t n)
+{
        /*
-        * Add the memory reserved for use by the bootloader to the
-        * memory map.
-        */
-       add_memory_region(PHYS_MEM_START, RES_BOOTLDR_MEMSIZE,
-               BOOT_MEM_RESERVED);
-#ifdef CONFIG_HIGHMEM_256_128
-       /*
-        * Add memory in low for general use by the kernel and its friends
-        * (like drivers, applications, etc).
-        */
-       add_memory_region(PHYS_MEM_START + RES_BOOTLDR_MEMSIZE,
-               LOW_MEM_MAX - RES_BOOTLDR_MEMSIZE, BOOT_MEM_RAM);
-       /*
-        * Add the memory reserved for reset vector.
-        */
-       add_memory_region(0x1fc00000, MEBIBYTE(4), BOOT_MEM_RESERVED);
-       /*
-        * Add the memory reserved.
-        */
-       add_memory_region(0x20000000, MEBIBYTE(1024 + 75), BOOT_MEM_RESERVED);
-       /*
-        * Add memory in high for general use by the kernel and its friends
-        * (like drivers, applications, etc).
-        *
-        * 75MB is reserved for devices which are using the memory in high.
-        */
-       add_memory_region(0x60000000 + MEBIBYTE(75), MEBIBYTE(128 - 75),
-               BOOT_MEM_RAM);
-#elif defined CONFIG_HIGHMEM_128_128
-       /*
-        * Add memory in low for general use by the kernel and its friends
-        * (like drivers, applications, etc).
-        */
-       add_memory_region(PHYS_MEM_START + RES_BOOTLDR_MEMSIZE,
-               MEBIBYTE(128) - RES_BOOTLDR_MEMSIZE, BOOT_MEM_RAM);
-       /*
-        * Add the memory reserved.
-        */
-       add_memory_region(PHYS_MEM_START + MEBIBYTE(128),
-               MEBIBYTE(128 + 1024 + 75), BOOT_MEM_RESERVED);
-       /*
-        * Add memory in high for general use by the kernel and its friends
-        * (like drivers, applications, etc).
-        *
-        * 75MB is reserved for devices which are using the memory in high.
-        */
-       add_memory_region(0x60000000 + MEBIBYTE(75), MEBIBYTE(128 - 75),
-               BOOT_MEM_RAM);
-#else
-       /* Add low memory regions for either:
-        *   - no-highmemory configuration case -OR-
-        *   - highmemory "HIGHMEM_LOWBANK_ONLY" case
-        */
-       /*
-        * Add memory for general use by the kernel and its friends
-        * (like drivers, applications, etc).
+        * If some or all of this memory has an alias, break it into the
+        * aliased and non-aliased portion.
         */
-       add_memory_region(PHYS_MEM_START + RES_BOOTLDR_MEMSIZE,
-               low_mem - RES_BOOTLDR_MEMSIZE, BOOT_MEM_RAM);
+       if (p != alias) {
+               phys_addr_t alias_size;
+               phys_addr_t registered;
+
+               alias_size = min(n, LOW_RAM_END - alias);
+               registered = register_low_ram(alias, alias_size);
+               ioremap_add_map(alias, p, n);
+               n -= registered;
+               p += registered;
+       }
+
+#ifdef CONFIG_HIGHMEM
+       if (n != 0) {
+               add_memory_region(p, n, BOOT_MEM_RAM);
+               ioremap_add_map(p, p, n);
+       }
+#endif
+}
+
+/**
+ * register_address_space - register things in the address space
+ * @memsize:   Number of bytes of RAM installed
+ *
+ * Takes the given number of bytes of RAM and registers as many of the regions,
+ * or partial regions, as it can. So, the default configuration might have
+ * two regions with 256 MiB each. If the memsize passed in on the command line
+ * is 384 MiB, it will register the first region with 256 MiB and the second
+ * with 128 MiB.
+ */
+static __init void register_address_space(phys_addr_t memsize)
+{
+       int i;
+       phys_addr_t size;
+       size_t n;
+       struct mem_layout *layout;
+       enum family_type family;
+
        /*
-        * Add the memory reserved for reset vector.
+        * Register all of the things that aren't available to the kernel as
+        * memory.
         */
-       add_memory_region(0x1fc00000, MEBIBYTE(4), BOOT_MEM_RESERVED);
-#endif
+       register_non_ram();
+
+       /* Find the appropriate memory description */
+       family = platform_get_family();
+
+       for (i = 0; i < ARRAY_SIZE(layout_list); i++) {
+               if (layout_list[i].family == family)
+                       break;
+       }
+
+       if (i == ARRAY_SIZE(layout_list)) {
+               n = ARRAY_SIZE(default_layout);
+               layout = default_layout;
+       } else {
+               n = layout_list[i].n;
+               layout = layout_list[i].layout;
+       }
+
+       for (i = 0; memsize != 0 && i < n; i++) {
+               size = min(memsize, layout[i].size);
+               register_ram(layout[i].phys, layout[i].alias, size);
+               memsize -= size;
+       }
+}
+
+void __init prom_meminit(void)
+{
+       ptv_memsize = get_memsize();
+       register_address_space(ptv_memsize);
 }
 
 void __init prom_free_prom_memory(void)