import PULS_20160108
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / drivers / misc / mediatek / mtkpasr / mtkpasr_preinit.c
diff --git a/drivers/misc/mediatek/mtkpasr/mtkpasr_preinit.c b/drivers/misc/mediatek/mtkpasr/mtkpasr_preinit.c
new file mode 100644 (file)
index 0000000..f99ccb6
--- /dev/null
@@ -0,0 +1,773 @@
+#define pr_fmt(fmt) "["KBUILD_MODNAME"]" fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/memory.h>
+#include <linux/memblock.h>
+#include <linux/printk.h>
+#include <linux/sched.h>
+#include <mach/emi_mpu.h>
+
+#ifdef CONFIG_ARM_LPAE
+#include <mach/mt_lpae.h>
+#endif
+
+#define CONFIG_MTKPASR_MINDIESIZE_PFN          (0x20000)       /* 512MB */
+#define MTKPASR_1GB_PFNS                       (0x40000)       /* 1GB */
+#define MTKPASR_2GB_PFNS                       (0x80000)       /* 2GB */
+#define MTKPASR_3GB_PFNS                       (0xC0000)       /* 3GB */
+#define MTKPASR_4GB_PFNS                       (0x100000)      /* 4GB */
+#define MTKPASR_DRAM_MINSIZE                   MTKPASR_2GB_PFNS
+
+/* #define NO_UART_CONSOLE */
+#ifndef NO_UART_CONSOLE
+#define PRINT(len, string, args...)    printk(KERN_ALERT string, ##args)
+#else
+unsigned char mtkpasr_log_buf[4096];
+static int log_stored;
+#define PRINT(len, string, args...)    do { sprintf(mtkpasr_log_buf + log_stored, string, ##args); log_stored += len; } while (0)
+#endif
+
+/* Struct for parsing rank information (SW view) */
+struct view_rank {
+       unsigned long start_pfn;        /* The 1st pfn */
+       unsigned long end_pfn;          /* The pfn after the last valid one */
+       unsigned long bank_pfn_size;    /* Bank size in PFN */
+       unsigned long valid_channel;    /* Channels: 0x00000101 means there are 2 valid channels - 1st & 2nd (MAX: 4 channels) */
+};
+static struct view_rank rank_info[MAX_RANKS];
+
+/* Basic DRAM configuration */
+static struct basic_dram_setting pasrdpd;
+/* PASR/DPD imposed start rank */
+static int mtkpasr_start_rank;
+/* MAX Banksize in pfns (from SW view) */
+unsigned long pasrbank_pfns = 0;
+/*
+ * We can't guarantee HIGHMEM zone is bank alignment, so we need another variable to represent it.
+ * (mtkpasr_pfn_start, mtkpasr_pfn_end) is bank-alignment!
+ */
+static unsigned long mtkpasr_pfn_start;
+static unsigned long mtkpasr_pfn_end;
+
+/* Segment mask */
+static unsigned long valid_segment = 0x0;
+
+/* Set pageblock's mobility */
+extern void set_pageblock_mobility(struct page *page, int mobility);
+
+/* From dram_overclock.c */
+extern bool pasr_is_valid(void)__attribute__((weak));
+/* To confirm PASR is valid */
+static inline bool could_do_mtkpasr(void)
+{
+       return false;
+#if 0
+       if (pasr_is_valid) {
+               return pasr_is_valid();
+       }
+
+       return false;
+#endif
+}
+
+#ifdef CONFIG_ARM_LPAE
+#define MAX_RANK_PFN   (0x1FFFFF)
+#define MAX_KERNEL_PFN (0x13FFFF)
+#define MAX_KPFN_MASK  (0x0FFFFF)
+#define KPFN_TO_VIRT   (0x100000)
+static unsigned long __init virt_to_kernel_pfn(unsigned long virt)
+{
+       unsigned long ret = virt;
+
+       if (enable_4G()) {
+               if (virt > MAX_RANK_PFN) {
+                       ret = virt - KPFN_TO_VIRT;
+               } else if (virt > MAX_KERNEL_PFN) {
+                       ret = virt & MAX_KPFN_MASK;
+               }
+       }
+
+       return ret;
+}
+static unsigned long __init kernel_pfn_to_virt(unsigned long kpfn, bool is_end)
+{
+       unsigned long ret = kpfn;
+
+       if (enable_4G()) {
+               if (is_end) {
+                       ret = kpfn + KPFN_TO_VIRT;
+               } else {
+                       ret = kpfn | KPFN_TO_VIRT;
+               }
+       }
+
+       return ret;
+}
+static unsigned long __init rank_pfn_offset(void)
+{
+       unsigned long ret = ARCH_PFN_OFFSET;
+
+       if (enable_4G()) {
+               ret = KPFN_TO_VIRT;
+       }
+
+       return ret;
+}
+#else
+#define virt_to_kernel_pfn(x)          (x)
+#define kernel_pfn_to_virt(x, y)       (x)
+#define rank_pfn_offset()              ((unsigned long)ARCH_PFN_OFFSET)
+#endif
+
+/* Round up by "base" from "offset" */
+static unsigned long __init round_up_base_offset(unsigned long input, unsigned long base, unsigned long offset)
+{
+       return ((input - offset + base - 1) / base) * base + offset;
+}
+
+/* Round down by "base" from "offset" */
+static unsigned long __init round_down_base_offset(unsigned long input, unsigned long base, unsigned long offset)
+{
+       return ((input - offset) / base) * base + offset;
+}
+
+/*
+ * Parse DRAM setting - transform DRAM setting to temporary bank structure.
+ */
+extern void acquire_dram_setting(struct basic_dram_setting *pasrdpd)__attribute__((weak));
+static bool __init parse_dram_setting(unsigned long hint)
+{
+       int channel_num, chan, rank, check_segment_num;
+       unsigned long valid_channel;
+       unsigned long check_rank_size, rank_pfn, start_pfn = rank_pfn_offset();
+
+       PRINT(29, "rank_pfn_offset() [0x%8lx]\n", rank_pfn_offset());
+
+       if (acquire_dram_setting) {
+               hint = 0;
+               acquire_dram_setting(&pasrdpd);
+               channel_num = pasrdpd.channel_nr;
+               /* By ranks */
+               for (rank = 0; rank < MAX_RANKS; ++rank) {
+                       rank_pfn = 0;
+                       rank_info[rank].valid_channel = 0x0;
+                       valid_channel = 0x1;
+                       check_rank_size = 0x0;
+                       check_segment_num = 0x0;
+                       for (chan = 0; chan < channel_num; ++chan) {
+                               if (pasrdpd.channel[chan].rank[rank].valid_rank) {
+                                       rank_pfn += (pasrdpd.channel[chan].rank[rank].rank_size << (27 - PAGE_SHIFT));
+                                       rank_info[rank].valid_channel |= valid_channel;
+                                       /* Sanity check for rank size */
+                                       if (!check_rank_size) {
+                                               check_rank_size = pasrdpd.channel[chan].rank[rank].rank_size;
+                                       } else {
+                                               /* We only support ranks with equal size */
+                                               if (check_rank_size != pasrdpd.channel[chan].rank[rank].rank_size) {
+                                                       return false;
+                                               }
+                                       }
+                                       /* Sanity check for segment number */
+                                       if (!check_segment_num) {
+                                               check_segment_num = pasrdpd.channel[chan].rank[rank].segment_nr;
+                                       } else {
+                                               /* We only support ranks with equal segment number */
+                                               if (check_segment_num != pasrdpd.channel[chan].rank[rank].segment_nr) {
+                                                       return false;
+                                               }
+                                       }
+                               }
+                               valid_channel <<= 8;
+                       }
+                       /* Have we found a valid rank */
+                       if (check_rank_size != 0 && check_segment_num != 0) {
+                               rank_info[rank].start_pfn = virt_to_kernel_pfn(start_pfn);
+                               rank_info[rank].end_pfn = virt_to_kernel_pfn(start_pfn + rank_pfn);
+                               rank_info[rank].bank_pfn_size = rank_pfn/check_segment_num;
+                               start_pfn = kernel_pfn_to_virt(rank_info[rank].end_pfn, true);
+                               PRINT(96, "Rank[%d] start_pfn[%8lu] end_pfn[%8lu] bank_pfn_size[%8lu] valid_channel[0x%-8lx]\n",
+                                               rank, rank_info[rank].start_pfn, rank_info[rank].end_pfn,
+                                               rank_info[rank].bank_pfn_size, rank_info[rank].valid_channel);
+                       } else {
+                               rank_info[rank].start_pfn = virt_to_kernel_pfn(rank_pfn_offset());
+                               rank_info[rank].end_pfn = virt_to_kernel_pfn(rank_pfn_offset());
+                               rank_info[rank].bank_pfn_size = 0;
+                               rank_info[rank].valid_channel = 0x0;
+                       }
+                       /* Calculate total pfns */
+                       hint += rank_pfn;
+               }
+       } else {
+               /* Single channel, dual ranks, 8 segments per rank - Get a hint from system */
+               rank_pfn = (hint + CONFIG_MTKPASR_MINDIESIZE_PFN - 1) & ~(CONFIG_MTKPASR_MINDIESIZE_PFN - 1);
+               rank_pfn >>= 1;
+               for (rank = 0; rank < 2; ++rank) {
+                       rank_info[rank].start_pfn = virt_to_kernel_pfn(start_pfn);
+                       rank_info[rank].end_pfn = virt_to_kernel_pfn(start_pfn + rank_pfn);
+                       rank_info[rank].bank_pfn_size = rank_pfn >> 3;
+                       rank_info[rank].valid_channel = 0x1;
+                       start_pfn = kernel_pfn_to_virt(rank_info[rank].end_pfn, true);
+                       PRINT(96, "Rank[%d] start_pfn[%8lu] end_pfn[%8lu] bank_pfn_size[%8lu] valid_channel[0x%-8lx]\n",
+                                       rank, rank_info[rank].start_pfn, rank_info[rank].end_pfn,
+                                       rank_info[rank].bank_pfn_size, rank_info[rank].valid_channel);
+               }
+               /* Reset remaining ranks */
+               for (; rank < MAX_RANKS; ++rank) {
+                       rank_info[rank].start_pfn = virt_to_kernel_pfn(rank_pfn_offset());
+                       rank_info[rank].end_pfn = virt_to_kernel_pfn(rank_pfn_offset());
+                       rank_info[rank].bank_pfn_size = 0;
+                       rank_info[rank].valid_channel = 0x0;
+               }
+       }
+
+       /* Check whether it is suitable to enable PASR */
+       if (hint < MTKPASR_DRAM_MINSIZE) {
+               printk(KERN_ALERT "[MTKPASR] Total memory: %lu < 1GB\n", (hint << PAGE_SHIFT));
+               return false;
+       }
+
+       return true;
+}
+
+/* Check whether it is a valid rank */
+static bool __init is_valid_rank(int rank)
+{
+       /* Check start/end pfn */
+       if (rank_info[rank].start_pfn == rank_info[rank].end_pfn) {
+               return false;
+       }
+
+       /* Check valid_channel */
+       if (rank_info[rank].valid_channel == 0x0) {
+               return false;
+       }
+
+       return true;
+}
+
+#if 0
+/* Show memblock */
+void show_memblock(void)
+{
+       struct memblock_region *reg;
+       phys_addr_t start;
+       phys_addr_t end;
+
+       for_each_memblock(memory, reg) {
+               start = reg->base;
+               end = start + reg->size;
+               printk(KERN_EMERG"[PHY layout]kernel   :   0x%08llx - 0x%08llx (0x%08llx)\n",
+                               (unsigned long long)start,
+                               (unsigned long long)end - 1,
+                               (unsigned long long)reg->size);
+       }
+
+       for_each_memblock(reserved, reg) {
+               start = reg->base;
+               end = start + reg->size;
+               printk(KERN_EMERG"[PHY layout]reserved   :   0x%08llx - 0x%08llx (0x%08llx)\n",
+                               (unsigned long long)start,
+                               (unsigned long long)end - 1,
+                               (unsigned long long)reg->size);
+
+       }
+}
+#endif
+
+#define PHYS_TO_PFN(x) __phys_to_pfn(x)
+/* Fill valid_segment */
+static void __init mark_valid_segment(unsigned long start, unsigned long end, bool last)
+{
+       int num_segment, rank;
+       unsigned long spfn, epfn;
+       unsigned long rspfn, repfn;
+
+       num_segment = 0;
+       for (rank = 0; rank < MAX_RANKS; ++rank) {
+               spfn = kernel_pfn_to_virt(start, false);
+               epfn = kernel_pfn_to_virt(end, true);
+               rspfn = kernel_pfn_to_virt(rank_info[rank].start_pfn, false);
+               repfn = kernel_pfn_to_virt(rank_info[rank].end_pfn, true);
+               if (is_valid_rank(rank)) {
+                       spfn = max(spfn, rspfn);
+                       if (repfn > spfn) {
+                               if (last) {
+                                       spfn = round_down_base_offset(spfn, rank_info[rank].bank_pfn_size, rank_pfn_offset());          /* Round-down */
+                               } else {
+                                       spfn = round_up_base_offset(spfn, rank_info[rank].bank_pfn_size, rank_pfn_offset());            /* Round-up */
+                               }
+                               epfn = min(epfn, repfn);
+                               while (epfn >= (spfn + rank_info[rank].bank_pfn_size)) {
+                                       valid_segment |= (1 << ((spfn - rspfn) / rank_info[rank].bank_pfn_size + num_segment));
+                                       spfn += rank_info[rank].bank_pfn_size;
+                               }
+                       }
+                       num_segment += (repfn - rspfn) / rank_info[rank].bank_pfn_size;
+               }
+       }
+}
+
+/* Set page mobility to MIGRATE_MTKPASR */
+static void __init set_page_mobility_mtkpasr(unsigned long start, unsigned long end, bool last)
+{
+       int rank;
+       unsigned long spfn, epfn, espfn, vpfn, pfn;
+       unsigned long rspfn, repfn;
+       struct page *page;
+
+       for (rank = 0; rank < MAX_RANKS; ++rank) {
+               spfn = kernel_pfn_to_virt(start, false);
+               epfn = kernel_pfn_to_virt(end, true);
+               rspfn = kernel_pfn_to_virt(rank_info[rank].start_pfn, false);
+               repfn = kernel_pfn_to_virt(rank_info[rank].end_pfn, true);
+               if (is_valid_rank(rank)) {
+                       spfn = max(spfn, rspfn);
+                       if (repfn > spfn) {
+                               if (last) {
+                                       spfn = round_down_base_offset(spfn, rank_info[rank].bank_pfn_size, rank_pfn_offset());          /* Round-down */
+                               } else {
+                                       spfn = round_up_base_offset(spfn, rank_info[rank].bank_pfn_size, rank_pfn_offset());            /* Round-up */
+                               }
+                               epfn = min(epfn, repfn);
+                               espfn = spfn + rank_info[rank].bank_pfn_size;
+                               while (epfn >= espfn) {
+                                       /* Set page mobility to MIGRATE_MTKPASR */
+                                       for (vpfn = spfn; vpfn < espfn; vpfn++) {
+                                               pfn = virt_to_kernel_pfn(vpfn);
+                                               /* If invalid - Use pfn_valid instead of early_pfn_valid which depends on CONFIG_SPARSEMEM! */
+                                               if (!pfn_valid(pfn))
+                                                       continue;
+                                               /* Set it as MIGRATE_MTKPASR */
+                                               page = pfn_to_page(pfn);
+                                               if (!(pfn & (pageblock_nr_pages - 1)))
+                                                       set_pageblock_mobility(page, MIGRATE_MTKPASR);
+                                       }
+                                       spfn += rank_info[rank].bank_pfn_size;
+                                       espfn = spfn + rank_info[rank].bank_pfn_size;
+                               }
+                       }
+               }
+       }
+}
+
+/* Fix to accommodate some feature-reserved memblocks */
+static void __init fix_memblock_region(unsigned long *start, unsigned long *end)
+{
+       int rank;
+       unsigned long total_pfn_size = 0;
+       unsigned long rspfn, repfn, spfn, epfn;
+
+       spfn = *start;
+       epfn = *end;
+
+       /* DVFS reserved */
+       for (rank = 0; rank < MAX_RANKS; ++rank) {
+               if (is_valid_rank(rank)) {
+                       rspfn = kernel_pfn_to_virt(rank_info[rank].start_pfn, false);
+                       repfn = kernel_pfn_to_virt(rank_info[rank].end_pfn, true);
+                       total_pfn_size += (repfn - rspfn);
+               }
+       }
+       repfn = kernel_pfn_to_virt(rank_info[0].start_pfn, false) + (total_pfn_size >> 1);
+       repfn = virt_to_kernel_pfn(repfn);
+       if (spfn == (repfn + 1)) {
+               spfn = repfn;
+       }
+       if (epfn == (repfn - 1)) {
+               epfn = repfn;
+       }
+
+       /* Update Result */
+       *start = spfn;
+       *end = epfn;
+}
+
+/* Fill valid_segment & set page mobility */
+static void __init construct_mtkpasr_range(void)
+{
+       unsigned long vstart, vend;
+       struct memblock_region *reg;
+       unsigned long start = 0;
+       unsigned long end = ~(unsigned long)0;
+
+       /* memblock should be sorted! */
+       for_each_memblock(memory, reg) {
+               vstart = mtkpasr_pfn_start;
+               vend = mtkpasr_pfn_end;
+               start = PHYS_TO_PFN(reg->base);
+               end = PHYS_TO_PFN(reg->base + reg->size);
+               /* Fix memblock region */
+               fix_memblock_region(&start, &end);
+               /* Intersect */
+               if (end > vstart && start < vend) {
+                       vstart = max(start, vstart);
+                       vend = min(end, vend);
+                       /* Mark valid segment */
+                       mark_valid_segment(vstart, vend, false);
+                       /* Set page mobility */
+                       set_page_mobility_mtkpasr(vstart, vend, false);
+               }
+       }
+
+       /* Last bank - TODO */
+
+       /*    ,-------------
+        *    |             |
+        *    .-------------, -> Last valid kernel memblock        (CONFIG_MTKPASR_NO_LASTBANK=y)
+        *    |          ---| -> mtkpasr_pfn_end (HERE)
+        *    .-------------, (AFTER ROUND-UP) new mtkpasr_pfn_end (CONFIG_MTKPASR_NO_LASTBANK=n)
+        */
+
+#ifndef CONFIG_MTKPASR_NO_LASTBANK
+       vend = mtkpasr_pfn_end;
+       /* There still exists some region not initialized */
+       if (end < vend) {
+               vstart = max(end, mtkpasr_pfn_start);
+               /* Mark valid segment */
+               mark_valid_segment(vstart, vend, true);
+               /* Set page mobility */
+               set_page_mobility_mtkpasr(vstart, vend, true);
+       }
+#endif
+}
+
+/*
+ * We will set an offset on which active PASR will be imposed.
+ * This is done by setting those pages as MIGRATE_MTKPASR type.
+ * It only takes effect on HIGHMEM zone now!
+ */
+void __init init_mtkpasr_range(struct zone *zone)
+{
+       struct pglist_data *pgdat;
+       int rank;
+       unsigned long start_pfn;
+       unsigned long end_pfn;
+       unsigned long pfn_bank_alignment = 0;
+       unsigned long shift_size = 0;
+
+       /* Check whether our platform supports PASR */
+       if (!could_do_mtkpasr()) {
+               /* Can't support PASR */
+               return;
+       }
+
+       /* Indicate node */
+       pgdat = zone->zone_pgdat;
+
+       /* Parsing DRAM setting */
+       if (parse_dram_setting(pgdat->node_spanned_pages) == false) {
+               /* Can't support PASR */
+               return;
+       }
+
+#ifdef CONFIG_HIGHMEM
+       /* Start from HIGHMEM zone if we have CONFIG_HIGHMEM defined. */
+       zone = zone + ZONE_HIGHMEM;
+#else
+       zone = zone + ZONE_NORMAL;
+#endif
+
+       /* Sanity check - Is this zone empty? */
+       if (!populated_zone(zone)) {
+               /* Can't support PASR */
+               return;
+       }
+
+       /* Mark the end pfn */
+       end_pfn = zone->zone_start_pfn + zone->spanned_pages;
+
+       /* Don't let end_pfn cross last rank to the beginning */
+       for (rank = MAX_RANKS - 1; rank >= 0; --rank) {
+               if (is_valid_rank(rank)) {
+                       end_pfn = min(end_pfn, rank_info[rank].end_pfn);
+                       break;
+               }
+       }
+
+       /* Indicate the beginning pfn of PASR/DPD */
+       for (rank = 0; rank < MAX_RANKS; ++rank) {
+               if (is_valid_rank(rank)) {
+                       shift_size += (kernel_pfn_to_virt(rank_info[rank].end_pfn, true) - kernel_pfn_to_virt(rank_info[rank].start_pfn, false));
+               }
+       }
+
+       /* Start from the half total DRAM size */
+       start_pfn = rank_pfn_offset() + (shift_size >> 1);
+       if (shift_size <= MTKPASR_1GB_PFNS) {
+               start_pfn += (shift_size >> 4);
+       }
+       if (shift_size >= MTKPASR_2GB_PFNS) {
+               start_pfn -= (shift_size >> 4);
+       }
+       if (shift_size >= MTKPASR_3GB_PFNS) {
+               start_pfn -= (shift_size >> 4);
+       }
+       if (shift_size >= MTKPASR_4GB_PFNS) {
+               start_pfn -= (shift_size >> 4);
+       }
+
+       /* Max start_pfn */
+       start_pfn = max(start_pfn, kernel_pfn_to_virt(zone->zone_start_pfn, false));
+       start_pfn = virt_to_kernel_pfn(start_pfn);
+
+       /* Find out which rank "start_pfn" belongs to */
+       for (rank = 0; rank < MAX_RANKS; ++rank) {
+               if (kernel_pfn_to_virt(start_pfn, false) < kernel_pfn_to_virt(rank_info[rank].end_pfn, true) &&
+                               kernel_pfn_to_virt(start_pfn, false) >= kernel_pfn_to_virt(rank_info[rank].start_pfn, false)) {
+                       mtkpasr_start_rank = rank;
+                       pfn_bank_alignment = rank_info[rank].bank_pfn_size;
+                       break;
+               }
+       }
+
+       /* Sanity check */
+       if (!pfn_bank_alignment) {
+               /* Can't support PASR */
+               return;
+       }
+
+       /* 1st attempted bank size */
+       pasrbank_pfns = pfn_bank_alignment;
+
+       /* Round up to bank alignment */
+       start_pfn = round_up_base_offset(start_pfn, pfn_bank_alignment, ARCH_PFN_OFFSET);
+
+       /* Find out which rank "end_pfn" belongs to */
+       for (rank = 0; rank < MAX_RANKS; ++rank) {
+               if (kernel_pfn_to_virt(end_pfn, true) <= kernel_pfn_to_virt(rank_info[rank].end_pfn, true) &&
+                               kernel_pfn_to_virt(end_pfn, true) > kernel_pfn_to_virt(rank_info[rank].start_pfn, false)) {
+                       pfn_bank_alignment = rank_info[rank].bank_pfn_size;
+                       break;
+               }
+       }
+
+       /* Determine the final bank size */
+       if (pasrbank_pfns < pfn_bank_alignment) {
+               pasrbank_pfns = pfn_bank_alignment;
+       }
+
+       /* Find out MTKPASR Start/End PFN */
+       mtkpasr_pfn_start = start_pfn;
+       mtkpasr_pfn_end = end_pfn;
+
+       /* Round up mtkpasr_pfn_end (a little tricky, affected by CONFIG_MTKPASR_NO_LASTBANK) */
+       mtkpasr_pfn_end = round_up_base_offset(mtkpasr_pfn_end, pfn_bank_alignment, ARCH_PFN_OFFSET);
+
+       /* Fix up - allow holes existing in the PASR range */
+       construct_mtkpasr_range();
+
+       PRINT(138, "[MTKPASR] @@@@@@ Start_pfn[%8lu] End_pfn[%8lu] (MTKPASR) start_pfn[%8lu] end_pfn[%8lu] Valid_segment[0x%8lx] @@@@@@\n",
+                       start_pfn, end_pfn, mtkpasr_pfn_start, mtkpasr_pfn_end, valid_segment);
+}
+
+/* Reserve NOT-MIGRATE_MTKPASR pages in PASR range */
+static void mtkpasr_reserve_reserved(void)
+{
+       int order = MAX_ORDER - 1, t;
+       struct list_head *curr, *tmp;
+       struct page *spage;
+       unsigned long flags;
+       unsigned long pfn;
+
+       /* Move pages */
+       for_each_migratetype_order(order, t) {
+               if (t != MIGRATE_MTKPASR) {
+                       list_for_each_safe(curr, tmp, &MTKPASR_ZONE->free_area[order].free_list[t]) {
+                               spage = list_entry(curr, struct page, lru);
+                               pfn = page_to_pfn(spage);
+                               /* NON-MTKPASR in PASR range */
+                               if (pfn >= mtkpasr_pfn_start && pfn < mtkpasr_pfn_end) {
+                                       printk(KERN_ALERT "\norder[%d] t[%d] pfn[%lu]\n", order, t, pfn);
+                                       spin_lock_irqsave(&MTKPASR_ZONE->lock, flags);
+                                       /* Move it from original mobility to MIGRATE_MTKPASR */
+                                       list_move(&spage->lru, &MTKPASR_ZONE->free_area[order].free_list[MIGRATE_MTKPASR]);
+                                       /* Set it to MIGRATE_MTKPASR */
+                                       set_pageblock_mobility(spage, MIGRATE_MTKPASR);
+                                       spin_unlock_irqrestore(&MTKPASR_ZONE->lock, flags);
+                               }
+                       }
+               }
+       }
+}
+
+/*
+ * Helper of constructing Memory (Virtual) Rank & Bank Information -
+ *
+ * start_pfn     - Pfn of the 1st page in that pasr range (Should be bank-aligned)
+ * end_pfn       - Pfn of the one after the last page in that pasr range (Should be bank-aligned)
+ *                 (A hole may exist between end_pfn & bank-aligned(last_valid_pfn))
+ * banks_per_rank - Number of banks in a rank
+ *
+ * Return    - The number of memory (virtual) banks, -1 means no valid range for PASR
+ */
+int __init compute_valid_pasr_range(unsigned long *start_pfn, unsigned long *end_pfn, int *num_ranks)
+{
+       int num_banks, rank, seg_num;
+       unsigned long vseg = valid_segment;
+       bool contain_rank;
+
+       /* Check whether our platform supports PASR */
+       if (!could_do_mtkpasr()) {
+               /* Can't support PASR */
+               return -1;
+       }
+
+       /* Check whether we have a valid PASR range */
+       if (mtkpasr_pfn_start == mtkpasr_pfn_end) {
+               /* Can't support PASR */
+               return -1;
+       }
+
+       /* Set PASR/DPD range */
+       *start_pfn = mtkpasr_pfn_start;
+       *end_pfn = mtkpasr_pfn_end;
+
+       /* Compute number of banks & ranks*/
+       num_banks = 0;
+       *num_ranks = 0;
+       for (rank = 0; rank < MAX_RANKS; ++rank) {
+               if (is_valid_rank(rank)) {
+                       contain_rank = true;
+                       seg_num = (kernel_pfn_to_virt(rank_info[rank].end_pfn, true) - kernel_pfn_to_virt(rank_info[rank].start_pfn, false)) /
+                               rank_info[rank].bank_pfn_size;
+                       while (seg_num--) {
+                               if (vseg & 0x1) {
+                                       num_banks++;
+                               } else {
+                                       contain_rank = false;
+                               }
+                               vseg >>= 1;
+                       }
+                       if (contain_rank) {
+                               *num_ranks += 1;
+                       }
+               }
+       }
+
+       /* No valid banks */
+       if (num_banks == 0) {
+               return -1;
+       }
+
+       /* Reserve NOT-MIGRATE_MTKPASR pages in PASR range */
+       mtkpasr_reserve_reserved();
+
+       return num_banks;
+}
+
+/*
+ * Give bank, this function will return its (start_pfn, end_pfn) and corresponding rank
+ * ("fully == true" means we should identify whether whole bank's rank is in a PASRDPD-imposed range)
+ */
+int __init query_bank_information(int bank, unsigned long *spfn, unsigned long *epfn, bool fully)
+{
+       int seg_num = 0, rank, num_segment = 0;
+       unsigned long vseg = valid_segment, valid_mask;
+
+       /* Reset */
+       *spfn = 0;
+       *epfn = 0;
+
+       /* Which segment */
+       do {
+               if (vseg & 0x1) {
+                       if (!bank) {
+                               /* Found! */
+                               break;
+                       }
+                       bank--;
+               }
+               vseg >>= 1;
+               seg_num++;
+       } while (seg_num < BITS_PER_LONG);
+
+       /* Sanity check */
+       if (seg_num == BITS_PER_LONG) {
+               return -1;
+       }
+
+       /* Which rank */
+       vseg = valid_segment;
+       for (rank = 0; rank < MAX_RANKS; ++rank) {
+               if (is_valid_rank(rank)) {
+                       num_segment = (kernel_pfn_to_virt(rank_info[rank].end_pfn, true) - kernel_pfn_to_virt(rank_info[rank].start_pfn, false)) /
+                               rank_info[rank].bank_pfn_size;
+                       if (seg_num < num_segment) {
+                               *spfn = virt_to_kernel_pfn(kernel_pfn_to_virt(rank_info[rank].start_pfn, false) + seg_num * rank_info[rank].bank_pfn_size);
+                               *epfn = virt_to_kernel_pfn(kernel_pfn_to_virt(*spfn, false) + rank_info[rank].bank_pfn_size);
+                               /* Fixup to meet bank range definition */
+                               if (*epfn <= *spfn) {
+                                       *epfn = kernel_pfn_to_virt(*epfn, true);
+                               }
+                               break;
+                       }
+                       seg_num -= num_segment;
+                       vseg >>= num_segment;
+               }
+       }
+
+       /* Sanity check */
+       if (rank == MAX_RANKS) {
+               return -1;
+       }
+
+       /* Should acquire rank information according to "rank" */
+       if (fully) {
+               valid_mask = (1 << num_segment) - 1;
+               if ((vseg & valid_mask) == valid_mask) {
+                       return rank;
+               }
+       }
+
+       return -1;
+}
+
+/*
+ * Translate sw bank to physical dram segment.
+ * This will output different translation results depends on what dram model our platform uses.
+ * non-interleaving(1-channel) vs. interleaving(n-channel, n > 1)
+ *
+ * Now it only supports full-interleaving translation.
+ */
+u32 __init pasr_bank_to_segment(unsigned long start_pfn, unsigned long end_pfn)
+{
+       int num_segment, rank;
+       unsigned long spfn, epfn;
+       unsigned long rspfn, repfn;
+
+       spfn = kernel_pfn_to_virt(start_pfn, false);
+       epfn = kernel_pfn_to_virt(end_pfn, true);
+       rspfn = kernel_pfn_to_virt(rank_info[0].start_pfn, false);
+
+       num_segment = 0;
+       for (rank = 0; rank < MAX_RANKS; ++rank) {
+               if (is_valid_rank(rank)) {
+                       rspfn = kernel_pfn_to_virt(rank_info[rank].start_pfn, false);
+                       repfn = kernel_pfn_to_virt(rank_info[rank].end_pfn, true);
+                       if (rspfn <= spfn && repfn >= epfn) {
+                               break;
+                       }
+                       num_segment += (repfn - rspfn) / rank_info[rank].bank_pfn_size;
+               }
+       }
+
+       /* Sanity check */
+       if (rank == MAX_RANKS) {
+               return (0x1F);
+       }
+
+       return ((spfn - rspfn) / rank_info[rank].bank_pfn_size + num_segment);
+
+       /*
+        *  Symmetric Interleaving
+        *  segment = (start_pfn - CONFIG_MEMPHYS_OFFSET) / pasrbank_pfns + dram_segment_offset_ch0;
+        *  // Dual-Channel   (n+n)
+        *  return segment | (segment << 8);
+        *  // Triple-Channel (n+n+n)
+        *  return segment | (segment << 8) | (segment << 16);
+        *  // Quad-Channel   (n+n+n+n)
+        *  return segment | (segment << 8) | (segment << 16) | (segment << 24);
+        */
+}