summaryrefslogtreecommitdiff
path: root/drivers/video/wmt/ge/ge_accel.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/wmt/ge/ge_accel.c')
-rw-r--r--drivers/video/wmt/ge/ge_accel.c551
1 files changed, 551 insertions, 0 deletions
diff --git a/drivers/video/wmt/ge/ge_accel.c b/drivers/video/wmt/ge/ge_accel.c
new file mode 100644
index 00000000..ef8ce133
--- /dev/null
+++ b/drivers/video/wmt/ge/ge_accel.c
@@ -0,0 +1,551 @@
+/*
+ * Copyright (c) 2008-2013 WonderMedia Technologies, 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, see <http://www.gnu.org/licenses/>.
+ *
+ * WonderMedia Technologies, Inc.
+ * 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C
+ */
+
+#include <asm/cacheflush.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/sched.h>
+#include <linux/semaphore.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/moduleparam.h>
+#include <mach/hardware.h>
+#include "ge_accel.h"
+
+unsigned int fb_egl_swap; /* useless */
+
+DECLARE_WAIT_QUEUE_HEAD(ge_wq);
+
+int flipcnt;
+int flipreq;
+int vbl;
+int vsync = 1;
+int sync2 = 1;
+int sync3;
+int debug;
+
+module_param(flipcnt, int, S_IRUSR | S_IWUSR | S_IWGRP | S_IRGRP | S_IROTH);
+MODULE_PARM_DESC(flipcnt, "Flip count");
+
+module_param(flipreq, int, S_IRUSR | S_IWUSR | S_IWGRP | S_IRGRP | S_IROTH);
+MODULE_PARM_DESC(flipreq, "Flip request count");
+
+module_param(vbl, int, S_IRUSR | S_IWUSR | S_IWGRP | S_IRGRP | S_IROTH);
+MODULE_PARM_DESC(vbl, "Wait vsync for each frame (0)");
+
+module_param(vsync, int, S_IRUSR | S_IWUSR | S_IWGRP | S_IRGRP | S_IROTH);
+MODULE_PARM_DESC(vsync, "Can use vsync (1)");
+
+module_param(sync2, int, S_IRUSR | S_IWUSR | S_IWGRP | S_IRGRP | S_IROTH);
+MODULE_PARM_DESC(sync2, "Only wait vsync if truly necessary");
+
+module_param(sync3, int, S_IRUSR | S_IWUSR | S_IWGRP | S_IRGRP | S_IROTH);
+MODULE_PARM_DESC(sync3, "Only wait vsync if truly necessary");
+
+module_param(debug, int, S_IRUSR | S_IWUSR | S_IWGRP | S_IRGRP | S_IROTH);
+MODULE_PARM_DESC(debug, "Show debug information");
+
+/**************************
+ * Export functions *
+ **************************/
+
+#define M(x) ((x)<<20)
+
+unsigned int phy_mem_end(void)
+{
+ unsigned int memsize = (num_physpages << PAGE_SHIFT);
+
+ if (memsize > M(3072)) { /* 4096M */
+ memsize = M(4096);
+ } else if (memsize > M(2048)) { /* 3072M */
+ memsize = M(3072);
+ } else if (memsize > M(1024)) { /* 2048M */
+ memsize = M(2048);
+ } else if (memsize > M(512)) { /* 1024M */
+ memsize = M(1024);
+ } else if (memsize > M(256)) { /* 512M */
+ memsize = M(512);
+ } else if (memsize > M(128)) { /* 256M */
+ memsize = M(256);
+ } else if (memsize > M(64)) { /* 128M */
+ memsize = M(128);
+ } else if (memsize > M(32)) { /* 64M */
+ memsize = M(64);
+ } else if (memsize > M(16)) { /* 32M */
+ memsize = M(32);
+ } else {
+ memsize = M(0);
+ }
+ printk(KERN_DEBUG "Detected RAM size %d MB\n", memsize>>20);
+
+ return memsize;
+}
+
+unsigned int phy_mem_end_sub(u32 size)
+{
+ return phy_mem_end() - M(size);
+}
+EXPORT_SYMBOL(phy_mem_end_sub);
+
+/* ge_vo_functions depends vpu to work */
+
+void ge_vo_get_default_var(struct fb_var_screeninfo *var)
+{
+#ifdef HAVE_VPP
+ vpp_get_info(0, var);
+#endif
+}
+
+void ge_vo_wait_vsync(void)
+{
+#ifdef HAVE_VPP
+ if (vsync)
+ vpp_wait_vsync(0, 1);
+#endif
+}
+
+static int ge_vo_pan_display(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+#ifdef HAVE_VPP
+ vpp_set_mutex(0, 1);
+ vpp_pan_display(var, info);
+ vpp_set_mutex(0, 0);
+#endif
+ flipcnt++;
+
+ return 0;
+}
+
+#if 0
+static void fbiomemcpy(struct fb_info *info,
+ unsigned long dst, unsigned long src, size_t len)
+{
+ void *psrc = info->screen_base + (src - info->fix.smem_start);
+ void *pdst = info->screen_base + (dst - info->fix.smem_start);
+ void *ptmp = info->screen_base + info->fix.smem_len - len;
+ unsigned long tmp = info->fix.smem_start + info->fix.smem_len - len;
+
+ if (src < info->fix.smem_start || src > tmp)
+ psrc = ioremap(src, len);
+ if (dst < info->fix.smem_start || dst > tmp)
+ pdst = ioremap(dst, len);
+ if (psrc && pdst && ptmp) {
+ memcpy(ptmp, psrc, len);
+ memcpy(pdst, ptmp, len);
+ }
+ if (psrc && (src < info->fix.smem_start || src > tmp))
+ iounmap(psrc);
+ if (pdst && (dst < info->fix.smem_start || dst > tmp))
+ iounmap(pdst);
+}
+#endif
+
+struct ge_var {
+ struct fb_info *info;
+ struct fb_var_screeninfo var[1];
+ struct fb_var_screeninfo new_var[1];
+ struct workqueue_struct *wq;
+ struct work_struct notifier;
+ struct timeval most_recent_flip_time;
+ int dirty;
+ int force_sync;
+ int vscnt; /* vsync counter */
+ spinlock_t lock[1];
+ void (*start)(struct ge_var *ge_var);
+ void (*stop)(struct ge_var *ge_var);
+ void (*get)(struct ge_var *ge_var, struct fb_var_screeninfo *var);
+ void (*set)(struct ge_var *ge_var, struct fb_var_screeninfo *var);
+ void (*sync)(struct ge_var *ge_var);
+};
+
+static struct ge_var *ge_var_s;
+
+static void ge_var_start(struct ge_var *ge_var);
+static void ge_var_stop(struct ge_var *ge_var);
+static void ge_var_get(struct ge_var *ge_var, struct fb_var_screeninfo *var);
+static void ge_var_set(struct ge_var *ge_var, struct fb_var_screeninfo *var);
+static void ge_var_sync(struct ge_var *ge_var);
+static void ge_var_sync1(struct ge_var *ge_var);
+static void ge_var_sync2(struct ge_var *ge_var);
+static void ge_var_sync3(struct ge_var *ge_var);
+
+static void ge_var_vsync_notifier(struct work_struct *work)
+{
+ struct ge_var *ge_var = container_of(work, struct ge_var, notifier);
+
+ ge_vo_wait_vsync();
+
+ spin_lock(ge_var->lock);
+ ge_var->vscnt++;
+ spin_unlock(ge_var->lock);
+
+ if (debug)
+ printk(KERN_DEBUG "vsync!\n");
+}
+
+static struct ge_var *create_ge_var(struct fb_info *info)
+{
+ struct ge_var *ge_var;
+
+ ge_var = (struct ge_var *)
+ kcalloc(1, sizeof(struct ge_var), GFP_KERNEL);
+
+ ge_var->wq = create_singlethread_workqueue("ge_var_wq");
+ ge_var->info = info;
+ ge_var->start = &ge_var_start;
+ ge_var->stop = &ge_var_stop;
+ ge_var->get = &ge_var_get;
+ ge_var->set = &ge_var_set;
+ ge_var->sync = &ge_var_sync;
+
+ do_gettimeofday(&ge_var->most_recent_flip_time);
+
+ INIT_WORK(&ge_var->notifier, ge_var_vsync_notifier);
+
+ ge_var->start(ge_var);
+
+ return ge_var;
+}
+
+static void release_ge_var(struct ge_var *ge_var)
+{
+ if (ge_var) {
+ ge_var->stop(ge_var);
+ flush_workqueue(ge_var->wq);
+ destroy_workqueue(ge_var->wq);
+ kfree(ge_var);
+ }
+}
+
+static void ge_var_start(struct ge_var *ge_var)
+{
+ spin_lock_init(ge_var->lock);
+ queue_work(ge_var->wq, &ge_var->notifier);
+}
+
+static void ge_var_stop(struct ge_var *ge_var)
+{
+}
+
+static void ge_var_get(struct ge_var *ge_var, struct fb_var_screeninfo *var)
+{
+ spin_lock(ge_var->lock);
+ memcpy(var, ge_var->var, sizeof(struct fb_var_screeninfo));
+ spin_unlock(ge_var->lock);
+}
+
+static void ge_var_set(struct ge_var *ge_var, struct fb_var_screeninfo *var)
+{
+ spin_lock(ge_var->lock);
+ if (memcmp(ge_var->new_var, var, sizeof(struct fb_var_screeninfo))) {
+ memcpy(ge_var->new_var, var, sizeof(struct fb_var_screeninfo));
+ ge_var->dirty++;
+ }
+ spin_unlock(ge_var->lock);
+
+ if (vbl || (var->activate & FB_ACTIVATE_VBL))
+ ge_var->sync(ge_var);
+ else
+ ge_var_sync1(ge_var);
+}
+
+static void ge_var_sync(struct ge_var *ge_var)
+{
+ if (sync3)
+ return ge_var_sync3(ge_var);
+
+ if (sync2)
+ return ge_var_sync2(ge_var);
+
+ ge_var_sync1(ge_var);
+}
+
+/* flip and don't wait */
+static void ge_var_sync1(struct ge_var *ge_var)
+{
+ spin_lock(ge_var->lock);
+
+ if (ge_var->dirty == 0) {
+ spin_unlock(ge_var->lock);
+ return;
+ }
+
+ memcpy(ge_var->var, ge_var->new_var, sizeof(struct fb_var_screeninfo));
+ spin_unlock(ge_var->lock);
+
+ ge_vo_pan_display(ge_var->var, ge_var->info);
+ ge_var->dirty = 0;
+}
+
+/* for double buffer */
+static void ge_var_sync2(struct ge_var *ge_var)
+{
+ struct timeval t;
+ struct timeval *mrft;
+ unsigned long us;
+
+ spin_lock(ge_var->lock);
+
+ if (ge_var->dirty == 0) {
+ spin_unlock(ge_var->lock);
+ return;
+ }
+
+ memcpy(ge_var->var, ge_var->new_var, sizeof(struct fb_var_screeninfo));
+
+ mrft = &ge_var->most_recent_flip_time;
+ do_gettimeofday(&t);
+
+ us = (t.tv_sec - mrft->tv_sec) * 1000000 +
+ (t.tv_usec - mrft->tv_usec);
+
+ spin_unlock(ge_var->lock);
+
+ ge_vo_pan_display(ge_var->var, ge_var->info);
+ ge_var->dirty = 0;
+
+ /* 60 fps */
+ if (us < 16667) {
+ if (debug) {
+ struct timeval t1;
+ struct timeval t2;
+ int ms;
+ do_gettimeofday(&t1);
+ ge_vo_wait_vsync();
+ do_gettimeofday(&t2);
+ ms = (t2.tv_sec - t1.tv_sec) * 1000 +
+ (t2.tv_usec - t1.tv_usec) / 1000;
+ printk(KERN_DEBUG "vsync2: wait vsync for %d ms\n", ms);
+ } else
+ ge_vo_wait_vsync();
+ }
+
+ do_gettimeofday(&ge_var->most_recent_flip_time);
+}
+
+/* for triple buffer */
+static void ge_var_sync3(struct ge_var *ge_var)
+{
+ spin_lock(ge_var->lock);
+
+ if (ge_var->dirty == 0) {
+ spin_unlock(ge_var->lock);
+ return;
+ }
+
+ if (ge_var->vscnt == 0) {
+ struct timeval t1;
+ struct timeval t2;
+ int ms;
+ int tmax = 16;
+
+ if (debug)
+ do_gettimeofday(&t1);
+
+ while (tmax && !ge_var->vscnt) {
+ usleep_range(1000, 2000);
+ tmax--;
+ }
+
+ if (debug) {
+ do_gettimeofday(&t2);
+ ms = (t2.tv_sec - t1.tv_sec) * 1000 +
+ (t2.tv_usec - t1.tv_usec) / 1000;
+ printk(KERN_DEBUG "vsync3: wait vsync for %d ms\n", ms);
+ }
+ }
+
+ memcpy(ge_var->var, ge_var->new_var, sizeof(struct fb_var_screeninfo));
+ spin_unlock(ge_var->lock);
+
+ ge_vo_pan_display(ge_var->var, ge_var->info);
+ ge_var->dirty = 0;
+ ge_var->vscnt = 0;
+
+ queue_work(ge_var->wq, &ge_var->notifier);
+}
+
+#if 0
+static unsigned long fb_get_phys_addr(struct fb_info *info,
+ struct fb_var_screeninfo *var)
+{
+ unsigned long offset;
+
+ if (!var)
+ var = &info->var;
+
+ offset = (var->yoffset * var->xres_virtual + var->xoffset);
+ offset *= var->bits_per_pixel >> 3;
+
+ return info->fix.smem_start + offset;
+}
+
+static unsigned long fb_get_disp_size(struct fb_info *info,
+ struct fb_var_screeninfo *var)
+{
+ unsigned long size;
+
+ if (!var)
+ var = &info->var;
+
+ size = (var->yres * var->xres_virtual);
+ size *= var->bits_per_pixel >> 3;
+
+ return size;
+}
+#endif
+
+static int fb_var_cmp(struct fb_var_screeninfo *var1,
+ struct fb_var_screeninfo *var2)
+{
+ /* Compare from xres to bits_per_pixel should be enough */
+ return memcmp(var1, var2, 28);
+}
+
+/**
+ * ge_init - Initial and display framebuffer.
+ *
+ * Fill the framebuffer with a default color, back.
+ * Display the framebuffer using GE AMX.
+ *
+ * Although VQ is supported in design, I just can't find any benefit
+ * from VQ. It wastes extra continuous physical memory, and runs much
+ * slower than direct register access. Moreover, the source code
+ * becomes more complex and is hard to maintain. Accessing VQ from
+ * the user space is also a nightmare. In brief, the overhead of VQ makes
+ * it useless. In order to gain the maximum performance
+ * from GE and to keep the driver simple, I'm going to stop using VQ.
+ * I will use VQ only when it is necessary.
+ *
+ * @info is the fb_info provided by framebuffer driver.
+ * @return zero on success.
+ */
+int ge_init(struct fb_info *info)
+{
+ static int boot_init; /* boot_init = 0 */
+ /*
+ * Booting time initialization
+ */
+ if (!boot_init) {
+ ge_var_s = create_ge_var(info);
+ boot_init = 1;
+ }
+
+ return 0;
+}
+
+/**
+ * ge_exit - Disable GE.
+ *
+ * No memory needs to be released here.
+ * Turn off the AMX to stop displaying framebuffer.
+ * Update the index of MMU.
+ *
+ * @info is fb_info from fbdev.
+ * @return zero on success.
+ */
+int ge_exit(struct fb_info *info)
+{
+ release_ge_var(ge_var_s);
+ release_mem_region(info->fix.mmio_start, info->fix.mmio_len);
+
+ return 0;
+}
+
+int ge_release(struct fb_info *info)
+{
+ struct ge_var *ge_var = ge_var_s;
+
+ ge_var->sync(ge_var); /* apply pending changes */
+
+ return 0;
+}
+
+/**
+ * ge_pan_display - Pans the display.
+ *
+ * Pan (or wrap, depending on the `vmode' field) the display using the
+ * `xoffset' and `yoffset' fields of the `var' structure.
+ * If the values don't fit, return -EINVAL.
+ *
+ * @var: frame buffer variable screen structure
+ * @info: frame buffer structure that represents a single frame buffer
+ *
+ * Returns negative errno on error, or zero on success.
+ */
+int ge_pan_display(struct fb_var_screeninfo *var, struct fb_info *info)
+{
+ struct ge_var *ge_var;
+
+ ge_var = ge_var_s;
+
+ if (debug)
+ printk(KERN_DEBUG "pan_display\n");
+ /*
+ printk(KERN_DEBUG "%s: xoff = %d, yoff = %d, xres = %d, yres = %d\n",
+ __func__, var->xoffset, var->yoffset,
+ info->var.xres, info->var.yres);
+ */
+ flipreq++;
+
+ if ((var->xoffset + info->var.xres > info->var.xres_virtual) ||
+ (var->yoffset + info->var.yres > info->var.yres_virtual)) {
+ /* Y-pan is used in most case.
+ * So please make sure that yres_virtual is
+ * greater than (yres + yoffset).
+ */
+ printk(KERN_ERR "%s: out of range\n", __func__);
+ return -EINVAL;
+ }
+
+ if ((var->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW &&
+ fb_var_cmp(ge_var->new_var, var))
+ ge_var->set(ge_var, var);
+
+ return 0;
+}
+
+int ge_sync(struct fb_info *info)
+{
+ return 0;
+}
+
+int ge_blank(int mode, struct fb_info *info)
+{
+#ifdef HAVE_VPP
+ return vpp_set_blank(info, mode);
+#else
+ return 0;
+#endif
+}
+
+int ge_suspend(struct fb_info *info)
+{
+ return 0;
+}
+
+int ge_resume(struct fb_info *info)
+{
+ return 0;
+}