Maemo patchset 20101501+0m5
[h-e-n] / arch / arm / plat-omap / vram.c
diff --git a/arch/arm/plat-omap/vram.c b/arch/arm/plat-omap/vram.c
new file mode 100644 (file)
index 0000000..efce760
--- /dev/null
@@ -0,0 +1,705 @@
+/*
+ * linux/arch/arm/plat-omap/vram.c
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com>
+ *
+ * Some code and ideas taken from drivers/video/omap/ driver
+ * by Imre Deak.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published by
+ * the Free Software Foundation.
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/*#define DEBUG*/
+
+#include <linux/vmalloc.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/list.h>
+#include <linux/dma-mapping.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/bootmem.h>
+#include <linux/omapfb.h>
+#include <linux/completion.h>
+
+#include <asm/setup.h>
+
+#include <mach/sram.h>
+#include <mach/vram.h>
+#include <mach/dma.h>
+
+#ifdef DEBUG
+#define DBG(format, ...) printk(KERN_DEBUG "VRAM: " format, ## __VA_ARGS__)
+#else
+#define DBG(format, ...)
+#endif
+
+#define OMAP2_SRAM_START               0x40200000
+/* Maximum size, in reality this is smaller if SRAM is partially locked. */
+#define OMAP2_SRAM_SIZE                        0xa0000         /* 640k */
+
+#define REG_MAP_SIZE(_page_cnt) \
+       ((_page_cnt + (sizeof(unsigned long) * 8) - 1) / 8)
+#define REG_MAP_PTR(_rg, _page_nr) \
+       (((_rg)->map) + (_page_nr) / (sizeof(unsigned long) * 8))
+#define REG_MAP_MASK(_page_nr) \
+       (1 << ((_page_nr) & (sizeof(unsigned long) * 8 - 1)))
+
+#if defined(CONFIG_FB_OMAP2) || defined(CONFIG_FB_OMAP2_MODULE)
+
+/* postponed regions are used to temporarily store region information at boot
+ * time when we cannot yet allocate the region list */
+#define MAX_POSTPONED_REGIONS 10
+
+static bool vram_initialized;
+static int postponed_cnt;
+static struct {
+       unsigned long paddr;
+       size_t size;
+} postponed_regions[MAX_POSTPONED_REGIONS];
+
+struct vram_alloc {
+       struct list_head list;
+       unsigned long paddr;
+       unsigned pages;
+};
+
+struct vram_region {
+       struct list_head list;
+       struct list_head alloc_list;
+       unsigned long paddr;
+       unsigned pages;
+};
+
+static DEFINE_MUTEX(region_mutex);
+static LIST_HEAD(region_list);
+
+static inline int region_mem_type(unsigned long paddr)
+{
+       if (paddr >= OMAP2_SRAM_START &&
+           paddr < OMAP2_SRAM_START + OMAP2_SRAM_SIZE)
+               return OMAPFB_MEMTYPE_SRAM;
+       else
+               return OMAPFB_MEMTYPE_SDRAM;
+}
+
+static struct vram_region *omap_vram_create_region(unsigned long paddr,
+               unsigned pages)
+{
+       struct vram_region *rm;
+
+       rm = kzalloc(sizeof(*rm), GFP_KERNEL);
+
+       if (rm) {
+               INIT_LIST_HEAD(&rm->alloc_list);
+               rm->paddr = paddr;
+               rm->pages = pages;
+       }
+
+       return rm;
+}
+
+#if 0
+static void omap_vram_free_region(struct vram_region *vr)
+{
+       list_del(&vr->list);
+       kfree(vr);
+}
+#endif
+
+static struct vram_alloc *omap_vram_create_allocation(struct vram_region *vr,
+               unsigned long paddr, unsigned pages)
+{
+       struct vram_alloc *va;
+       struct vram_alloc *new;
+
+       new = kzalloc(sizeof(*va), GFP_KERNEL);
+
+       if (!new)
+               return NULL;
+
+       new->paddr = paddr;
+       new->pages = pages;
+
+       list_for_each_entry(va, &vr->alloc_list, list) {
+               if (va->paddr > new->paddr)
+                       break;
+       }
+
+       list_add_tail(&new->list, &va->list);
+
+       return new;
+}
+
+static void omap_vram_free_allocation(struct vram_alloc *va)
+{
+       list_del(&va->list);
+       kfree(va);
+}
+
+int omap_vram_add_region(unsigned long paddr, size_t size)
+{
+       struct vram_region *rm;
+       unsigned pages;
+
+       if (vram_initialized) {
+               DBG("adding region paddr %08lx size %d\n",
+                               paddr, size);
+
+               size &= PAGE_MASK;
+               pages = size >> PAGE_SHIFT;
+
+               rm = omap_vram_create_region(paddr, pages);
+               if (rm == NULL)
+                       return -ENOMEM;
+
+               list_add(&rm->list, &region_list);
+       } else {
+               if (postponed_cnt == MAX_POSTPONED_REGIONS)
+                       return -ENOMEM;
+
+               postponed_regions[postponed_cnt].paddr = paddr;
+               postponed_regions[postponed_cnt].size = size;
+
+               ++postponed_cnt;
+       }
+       return 0;
+}
+
+int omap_vram_free(unsigned long paddr, size_t size)
+{
+       struct vram_region *rm;
+       struct vram_alloc *alloc;
+       unsigned start, end;
+
+       DBG("free mem paddr %08lx size %d\n", paddr, size);
+
+       size = PAGE_ALIGN(size);
+
+       mutex_lock(&region_mutex);
+
+       list_for_each_entry(rm, &region_list, list) {
+               list_for_each_entry(alloc, &rm->alloc_list, list) {
+                       start = alloc->paddr;
+                       end = alloc->paddr + (alloc->pages >> PAGE_SHIFT);
+
+                       if (start >= paddr && end < paddr + size)
+                               goto found;
+               }
+       }
+
+       mutex_unlock(&region_mutex);
+       return -EINVAL;
+
+found:
+       omap_vram_free_allocation(alloc);
+
+       mutex_unlock(&region_mutex);
+       return 0;
+}
+EXPORT_SYMBOL(omap_vram_free);
+
+static int _omap_vram_reserve(unsigned long paddr, unsigned pages)
+{
+       struct vram_region *rm;
+       struct vram_alloc *alloc;
+       size_t size;
+
+       size = pages << PAGE_SHIFT;
+
+       list_for_each_entry(rm, &region_list, list) {
+               unsigned long start, end;
+
+               DBG("checking region %lx %d\n", rm->paddr, rm->pages);
+
+               if (region_mem_type(rm->paddr) != region_mem_type(paddr))
+                       continue;
+
+               start = rm->paddr;
+               end = start + (rm->pages << PAGE_SHIFT) - 1;
+               if (start > paddr || end < paddr + size - 1)
+                       continue;
+
+               DBG("block ok, checking allocs\n");
+
+               list_for_each_entry(alloc, &rm->alloc_list, list) {
+                       end = alloc->paddr - 1;
+
+                       if (start <= paddr && end >= paddr + size - 1)
+                               goto found;
+
+                       start = alloc->paddr + (alloc->pages << PAGE_SHIFT);
+               }
+
+               end = rm->paddr + (rm->pages << PAGE_SHIFT) - 1;
+
+               if (!(start <= paddr && end >= paddr + size - 1))
+                       continue;
+found:
+               DBG("FOUND area start %lx, end %lx\n", start, end);
+
+               if (omap_vram_create_allocation(rm, paddr, pages) == NULL)
+                       return -ENOMEM;
+
+               return 0;
+       }
+
+       return -ENOMEM;
+}
+
+int omap_vram_reserve(unsigned long paddr, size_t size)
+{
+       unsigned pages;
+       int r;
+
+       DBG("reserve mem paddr %08lx size %d\n", paddr, size);
+
+       size = PAGE_ALIGN(size);
+       pages = size >> PAGE_SHIFT;
+
+       mutex_lock(&region_mutex);
+
+       r = _omap_vram_reserve(paddr, pages);
+
+       mutex_unlock(&region_mutex);
+
+       return r;
+}
+EXPORT_SYMBOL(omap_vram_reserve);
+
+static void _omap_vram_dma_cb(int lch, u16 ch_status, void *data)
+{
+       struct completion *compl = data;
+       complete(compl);
+}
+
+static int _omap_vram_clear(u32 paddr, unsigned pages)
+{
+       struct completion compl;
+       unsigned elem_count;
+       unsigned frame_count;
+       int r;
+       int lch;
+
+       init_completion(&compl);
+
+       r = omap_request_dma(OMAP_DMA_NO_DEVICE, "VRAM DMA",
+                       _omap_vram_dma_cb,
+                       &compl, &lch);
+       if (r) {
+               pr_err("VRAM: request_dma failed for memory clear\n");
+               return -EBUSY;
+       }
+
+       elem_count = pages * PAGE_SIZE / 4;
+       frame_count = 1;
+
+       omap_set_dma_transfer_params(lch, OMAP_DMA_DATA_TYPE_S32,
+                       elem_count, frame_count,
+                       OMAP_DMA_SYNC_ELEMENT,
+                       0, 0);
+
+       omap_set_dma_dest_params(lch, 0, OMAP_DMA_AMODE_POST_INC,
+                       paddr, 0, 0);
+
+       omap_set_dma_color_mode(lch, OMAP_DMA_CONSTANT_FILL, 0x000000);
+
+       omap_start_dma(lch);
+
+       if (wait_for_completion_timeout(&compl, msecs_to_jiffies(1000)) == 0) {
+               omap_stop_dma(lch);
+               pr_err("VRAM: dma timeout while clearing memory\n");
+               r = -EIO;
+               goto err;
+       }
+
+       r = 0;
+err:
+       omap_free_dma(lch);
+
+       return r;
+}
+
+static int _omap_vram_alloc(int mtype, unsigned pages, unsigned long *paddr)
+{
+       struct vram_region *rm;
+       struct vram_alloc *alloc;
+
+       list_for_each_entry(rm, &region_list, list) {
+               unsigned long start, end;
+
+               DBG("checking region %lx %d\n", rm->paddr, rm->pages);
+
+               if (region_mem_type(rm->paddr) != mtype)
+                       continue;
+
+               start = rm->paddr;
+
+               list_for_each_entry(alloc, &rm->alloc_list, list) {
+                       end = alloc->paddr;
+
+                       if (end - start >= pages << PAGE_SHIFT)
+                               goto found;
+
+                       start = alloc->paddr + (alloc->pages << PAGE_SHIFT);
+               }
+
+               end = rm->paddr + (rm->pages << PAGE_SHIFT);
+found:
+               if (end - start < pages << PAGE_SHIFT)
+                       continue;
+
+               DBG("FOUND %lx, end %lx\n", start, end);
+
+               alloc = omap_vram_create_allocation(rm, start, pages);
+               if (alloc == NULL)
+                       return -ENOMEM;
+
+               *paddr = start;
+
+               _omap_vram_clear(start, pages);
+
+               return 0;
+       }
+
+       return -ENOMEM;
+}
+
+int omap_vram_alloc(int mtype, size_t size, unsigned long *paddr)
+{
+       unsigned pages;
+       int r;
+
+       BUG_ON(mtype > OMAPFB_MEMTYPE_MAX || !size);
+
+       DBG("alloc mem type %d size %d\n", mtype, size);
+
+       size = PAGE_ALIGN(size);
+       pages = size >> PAGE_SHIFT;
+
+       mutex_lock(&region_mutex);
+
+       r = _omap_vram_alloc(mtype, pages, paddr);
+
+       mutex_unlock(&region_mutex);
+
+       return r;
+}
+EXPORT_SYMBOL(omap_vram_alloc);
+
+void omap_vram_get_info(unsigned long *vram,
+               unsigned long *free_vram,
+               unsigned long *largest_free_block)
+{
+       struct vram_region *vr;
+       struct vram_alloc *va;
+
+       *vram = 0;
+       *free_vram = 0;
+       *largest_free_block = 0;
+
+       mutex_lock(&region_mutex);
+
+       list_for_each_entry(vr, &region_list, list) {
+               unsigned free;
+               unsigned long pa;
+
+               pa = vr->paddr;
+               *vram += vr->pages << PAGE_SHIFT;
+
+               list_for_each_entry(va, &vr->alloc_list, list) {
+                       free = va->paddr - pa;
+                       *free_vram += free;
+                       if (free > *largest_free_block)
+                               *largest_free_block = free;
+                       pa = va->paddr + (va->pages << PAGE_SHIFT);
+               }
+
+               free = vr->paddr + (vr->pages << PAGE_SHIFT) - pa;
+               *free_vram += free;
+               if (free > *largest_free_block)
+                       *largest_free_block = free;
+       }
+
+       mutex_unlock(&region_mutex);
+}
+EXPORT_SYMBOL(omap_vram_get_info);
+
+#ifdef CONFIG_PROC_FS
+static void *r_next(struct seq_file *m, void *v, loff_t *pos)
+{
+       struct list_head *l = v;
+
+       (*pos)++;
+
+       if (list_is_last(l, &region_list))
+               return NULL;
+
+       return l->next;
+}
+
+static void *r_start(struct seq_file *m, loff_t *pos)
+{
+       loff_t p = *pos;
+       struct list_head *l = &region_list;
+
+       mutex_lock(&region_mutex);
+
+       do {
+               l = l->next;
+               if (l == &region_list)
+                       return NULL;
+       } while (p--);
+
+       return l;
+}
+
+static void r_stop(struct seq_file *m, void *v)
+{
+       mutex_unlock(&region_mutex);
+}
+
+static int r_show(struct seq_file *m, void *v)
+{
+       struct vram_region *vr;
+       struct vram_alloc *va;
+       unsigned size;
+
+       vr = list_entry(v, struct vram_region, list);
+
+       size = vr->pages << PAGE_SHIFT;
+
+       seq_printf(m, "%08lx-%08lx (%d bytes)\n",
+                       vr->paddr, vr->paddr + size - 1,
+                       size);
+
+       list_for_each_entry(va, &vr->alloc_list, list) {
+               size = va->pages << PAGE_SHIFT;
+               seq_printf(m, "    %08lx-%08lx (%d bytes)\n",
+                               va->paddr, va->paddr + size - 1,
+                               size);
+       }
+
+
+
+       return 0;
+}
+
+static const struct seq_operations resource_op = {
+       .start  = r_start,
+       .next   = r_next,
+       .stop   = r_stop,
+       .show   = r_show,
+};
+
+static int vram_open(struct inode *inode, struct file *file)
+{
+       return seq_open(file, &resource_op);
+}
+
+static const struct file_operations proc_vram_operations = {
+       .open           = vram_open,
+       .read           = seq_read,
+       .llseek         = seq_lseek,
+       .release        = seq_release,
+};
+
+static int __init omap_vram_create_proc(void)
+{
+       proc_create("omap-vram", 0, NULL, &proc_vram_operations);
+
+       return 0;
+}
+#endif
+
+static __init int omap_vram_init(void)
+{
+       int i, r;
+
+       vram_initialized = 1;
+
+       for (i = 0; i < postponed_cnt; i++)
+               omap_vram_add_region(postponed_regions[i].paddr,
+                               postponed_regions[i].size);
+
+#ifdef CONFIG_PROC_FS
+       r = omap_vram_create_proc();
+       if (r)
+               return -ENOMEM;
+#endif
+
+       return 0;
+}
+
+arch_initcall(omap_vram_init);
+
+/* boottime vram alloc stuff */
+
+/* set from board file */
+static u32 omapfb_sram_vram_start __initdata;
+static u32 omapfb_sram_vram_size __initdata;
+
+/* set from board file */
+static u32 omapfb_sdram_vram_start __initdata;
+static u32 omapfb_sdram_vram_size __initdata;
+
+/* set from kernel cmdline */
+static u32 omapfb_def_sdram_vram_size __initdata;
+static u32 omapfb_def_sdram_vram_start __initdata;
+
+static void __init omapfb_early_vram(char **p)
+{
+       omapfb_def_sdram_vram_size = memparse(*p, p);
+       if (**p == ',')
+               omapfb_def_sdram_vram_start = simple_strtoul((*p) + 1, p, 16);
+}
+__early_param("vram=", omapfb_early_vram);
+
+/*
+ * Called from map_io. We need to call to this early enough so that we
+ * can reserve the fixed SDRAM regions before VM could get hold of them.
+ */
+void __init omapfb_reserve_sdram(void)
+{
+       struct bootmem_data     *bdata;
+       unsigned long           sdram_start, sdram_size;
+       u32 paddr;
+       u32 size = 0;
+
+       /* cmdline arg overrides the board file definition */
+       if (omapfb_def_sdram_vram_size) {
+               size = omapfb_def_sdram_vram_size;
+               paddr = omapfb_def_sdram_vram_start;
+       }
+
+       if (!size) {
+               size = omapfb_sdram_vram_size;
+               paddr = omapfb_sdram_vram_start;
+       }
+
+#ifdef CONFIG_OMAP2_DSS_VRAM_SIZE
+       if (!size) {
+               size = CONFIG_OMAP2_DSS_VRAM_SIZE * 1024 * 1024;
+               paddr = 0;
+       }
+#endif
+
+       if (!size)
+               return;
+
+       size = PAGE_ALIGN(size);
+
+       bdata = NODE_DATA(0)->bdata;
+       sdram_start = bdata->node_min_pfn << PAGE_SHIFT;
+       sdram_size = (bdata->node_low_pfn << PAGE_SHIFT) - sdram_start;
+
+       if (paddr) {
+               if ((paddr & ~PAGE_MASK) || paddr < sdram_start ||
+                               paddr + size > sdram_start + sdram_size) {
+                       printk(KERN_ERR "Illegal SDRAM region for VRAM\n");
+                       return;
+               }
+
+               if (reserve_bootmem(paddr, size, BOOTMEM_EXCLUSIVE) < 0) {
+                       pr_err("FB: failed to reserve VRAM\n");
+                       return;
+               }
+       } else {
+               if (size > sdram_size) {
+                       printk(KERN_ERR "Illegal SDRAM size for VRAM\n");
+                       return;
+               }
+
+               paddr = virt_to_phys(alloc_bootmem_pages(size));
+               BUG_ON(paddr & ~PAGE_MASK);
+       }
+
+       omap_vram_add_region(paddr, size);
+
+       pr_info("Reserving %u bytes SDRAM for VRAM\n", size);
+}
+
+/*
+ * Called at sram init time, before anything is pushed to the SRAM stack.
+ * Because of the stack scheme, we will allocate everything from the
+ * start of the lowest address region to the end of SRAM. This will also
+ * include padding for page alignment and possible holes between regions.
+ *
+ * As opposed to the SDRAM case, we'll also do any dynamic allocations at
+ * this point, since the driver built as a module would have problem with
+ * freeing / reallocating the regions.
+ */
+unsigned long __init omapfb_reserve_sram(unsigned long sram_pstart,
+                                 unsigned long sram_vstart,
+                                 unsigned long sram_size,
+                                 unsigned long pstart_avail,
+                                 unsigned long size_avail)
+{
+       unsigned long                   pend_avail;
+       unsigned long                   reserved;
+       u32 paddr;
+       u32 size;
+
+       paddr = omapfb_sram_vram_start;
+       size = omapfb_sram_vram_size;
+
+       if (!size)
+               return 0;
+
+       reserved = 0;
+       pend_avail = pstart_avail + size_avail;
+
+       if (!paddr) {
+               /* Dynamic allocation */
+               if ((size_avail & PAGE_MASK) < size) {
+                       printk(KERN_ERR "Not enough SRAM for VRAM\n");
+                       return 0;
+               }
+               size_avail = (size_avail - size) & PAGE_MASK;
+               paddr = pstart_avail + size_avail;
+       }
+
+       if (paddr < sram_pstart ||
+                       paddr + size > sram_pstart + sram_size) {
+               printk(KERN_ERR "Illegal SRAM region for VRAM\n");
+               return 0;
+       }
+
+       /* Reserve everything above the start of the region. */
+       if (pend_avail - paddr > reserved)
+               reserved = pend_avail - paddr;
+       size_avail = pend_avail - reserved - pstart_avail;
+
+       omap_vram_add_region(paddr, size);
+
+       if (reserved)
+               pr_info("Reserving %lu bytes SRAM for VRAM\n", reserved);
+
+       return reserved;
+}
+
+void __init omap2_set_sdram_vram(u32 size, u32 start)
+{
+       omapfb_sdram_vram_start = start;
+       omapfb_sdram_vram_size = size;
+}
+
+void __init omap2_set_sram_vram(u32 size, u32 start)
+{
+       omapfb_sram_vram_start = start;
+       omapfb_sram_vram_size = size;
+}
+
+#endif
+