diff options
Diffstat (limited to 'drivers/video/wmt/ge')
-rw-r--r-- | drivers/video/wmt/ge/Makefile | 5 | ||||
-rw-r--r-- | drivers/video/wmt/ge/ge_accel.c | 551 | ||||
-rw-r--r-- | drivers/video/wmt/ge/ge_accel.h | 79 | ||||
-rw-r--r-- | drivers/video/wmt/ge/ge_main.c | 1003 |
4 files changed, 1638 insertions, 0 deletions
diff --git a/drivers/video/wmt/ge/Makefile b/drivers/video/wmt/ge/Makefile new file mode 100644 index 00000000..819c710a --- /dev/null +++ b/drivers/video/wmt/ge/Makefile @@ -0,0 +1,5 @@ +# +# Makefile for WonderMedia GE driver +# +obj-$(CONFIG_FB_WMT_GE) += ge.o +ge-objs := ge_main.o ge_accel.o 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; +} diff --git a/drivers/video/wmt/ge/ge_accel.h b/drivers/video/wmt/ge/ge_accel.h new file mode 100644 index 00000000..d47d30f7 --- /dev/null +++ b/drivers/video/wmt/ge/ge_accel.h @@ -0,0 +1,79 @@ +/* + * 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 + */ + +#ifndef GE_ACCEL_H +#define GE_ACCEL_H + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/fb.h> + +#ifndef __KERNEL__ +#define __KERNEL__ +#endif + +#define GE_DEBUG 0 +#define FB_ACCEL_WMT 0x8910 +#define MAX_XRES 1920 +#define MAX_YRES 1080 +#define GE_FB_NUM 3 + +#define HAVE_MALI +#define HAVE_VPP + +extern int vbl; +extern int vsync; +extern int sync2; + +unsigned int phy_mem_end(void); +unsigned int phy_mem_end_sub(unsigned int size); +int ge_init(struct fb_info *info); +int ge_exit(struct fb_info *info); +int ge_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg); +int ge_pan_display(struct fb_var_screeninfo *var, struct fb_info *info); +int ge_sync(struct fb_info *info); +int ge_release(struct fb_info *info); +int ge_blank(int mode, struct fb_info *info); +int ge_suspend(struct fb_info *info); +int ge_resume(struct fb_info *info); + +void ge_vo_get_default_var(struct fb_var_screeninfo *var); +void ge_vo_wait_vsync(void); + +#ifdef HAVE_VPP +#include "../vpp.h" +extern void vpp_wait_vsync(int idx, int cnt); +extern void vpp_set_mutex(int idx, int lock); +extern void vpp_get_info(int fbn, struct fb_var_screeninfo *var); +extern int vpp_pan_display(struct fb_var_screeninfo *var, struct fb_info *info); +extern int vpp_set_blank(struct fb_info *info, int blank); +extern int vpp_set_par(struct fb_info *info); +extern int wmtfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info); +#endif /* HAVE_VPP */ + +#ifdef HAVE_MALI +extern unsigned int mali_ump_secure_id; +extern unsigned int (*mali_get_ump_secure_id)(unsigned int addr, + unsigned int size); +extern void (*mali_put_ump_secure_id)(unsigned int ump_id); +#endif /* HAVE_MALI */ + +#endif diff --git a/drivers/video/wmt/ge/ge_main.c b/drivers/video/wmt/ge/ge_main.c new file mode 100644 index 00000000..1109ff5f --- /dev/null +++ b/drivers/video/wmt/ge/ge_main.c @@ -0,0 +1,1003 @@ +/* + * 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 <linux/init.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/errno.h> +#include <linux/uaccess.h> +#include <linux/kdev_t.h> +#include <linux/cdev.h> +#include <linux/fb.h> +#include <linux/dma-mapping.h> +#include <asm/page.h> +#include <linux/mm.h> +#include <linux/sched.h> + +#include "ge_accel.h" + +#define HAVE_MALI +#define WMT_MB + +#ifdef HAVE_MALI +#include "../mali.h" +static struct mali_device *malidev; +#define UMP_INVALID_SECURE_ID ((unsigned int)-1) +#define GET_UMP_SECURE_ID _IOWR('m', 310, unsigned int) +#define GET_UMP_SECURE_ID_BUF1 _IOWR('m', 311, unsigned int) +#define GET_UMP_SECURE_ID_BUF2 _IOWR('m', 312, unsigned int) +#define MALI_GET_UMP_SECURE_ID _IOWR('m', 320, unsigned int) +#define MALI_PUT_UMP_SECURE_ID _IOWR('m', 321, unsigned int) +#endif /* HAVE_MALI */ + +#define USE_SID_ALIAS +/* +#define DEBUG_SID_ALIAS +*/ + +#ifdef USE_SID_ALIAS +#define SID_IDX_MAX 16 +#define SID_GET_INDEX_FROM_ALIAS _IOWR('s', 100, unsigned int) +#define SID_SET_ALIAS _IOWR('s', 101, unsigned int) +#define SID_GET_ALIAS _IOWR('s', 102, unsigned int) +#define SID_GET_AND_RESET_ALIAS _IOWR('s', 103, unsigned int) +#define SID_DUMP _IOWR('s', 104, unsigned int) + +struct sid_alias { + int sid; + int alias; +}; + +static struct sid_alias sid_alias_buf[SID_IDX_MAX]; +static spinlock_t sid_lock; + +int sid_get_index_from_empty(int *index) +{ + int i; + + for (i = 0; i < SID_IDX_MAX; i++) { + if (!sid_alias_buf[i].sid && !sid_alias_buf[i].alias) { + *index = i; + return 0; + } + } + return -1; +} + +int sid_get_index_from_alias(int alias, int *index) +{ + int i; + + for (i = 0; i < SID_IDX_MAX; i++) { + if (sid_alias_buf[i].alias == alias) { + *index = i; + return 0; + } + } + return -1; +} + +int sid_clear_alias(int sid) +{ + int idx; + + for (idx = 0; idx < SID_IDX_MAX; idx++) { + if (sid == sid_alias_buf[idx].sid || + sid == sid_alias_buf[idx].alias) { + sid_alias_buf[idx].sid = 0; + sid_alias_buf[idx].alias = 0; + } + } + + return 0; +} + +int sid_set_alias(int sid, int alias) +{ + int idx; + + if (alias <= 0) + return sid_clear_alias(sid); + + sid_clear_alias(alias); + + if (sid_get_index_from_alias(sid, &idx) == 0) { + sid_alias_buf[idx].alias = alias; + if (alias <= 0) + sid_alias_buf[idx].sid = 0; + return 0; + } + + sid_clear_alias(sid); + + if (sid_get_index_from_empty(&idx) == 0) { + sid_alias_buf[idx].sid = sid; + sid_alias_buf[idx].alias = alias; + return 0; + } + + return -1; +} + +int sid_get_alias(int sid, int *alias) +{ + int i; + int val = -1; + + for (i = 0; i < SID_IDX_MAX; i++) { + if (sid_alias_buf[i].sid == sid) { + if (sid_alias_buf[i].alias > 0) { + val = sid_alias_buf[i].alias; + if (sid != val) + break; + } else { + /* remove invalid alias */ + sid_alias_buf[i].sid = 0; + sid_alias_buf[i].alias = 0; + } + } + } + + if (val > 0) { + *alias = val; + return 0; + } else + return -1; +} + +int sid_get_and_reset_alias(int sid, int *alias) +{ + int i; + int val = -1; + + for (i = 0; i < SID_IDX_MAX; i++) { + if (sid_alias_buf[i].sid == sid) { + if (sid_alias_buf[i].alias > 0) { + val = sid_alias_buf[i].alias; + sid_alias_buf[i].sid = val; + if (sid != val) + break; + } else { + /* remove invalid alias */ + sid_alias_buf[i].sid = 0; + sid_alias_buf[i].alias = 0; + } + } + } + + if (val > 0) { + *alias = val; + return 0; + } else + return -1; +} + +void sid_dump(void) +{ + int i; + + for (i = 0; i < SID_IDX_MAX; i++) + printk(KERN_ERR "sid_alias_buf[%d] = { %d, %d }\n", + i, sid_alias_buf[i].sid, sid_alias_buf[i].alias); +} +#endif + +#ifndef FBIO_WAITFORVSYNC +#define FBIO_WAITFORVSYNC _IOW('F', 0x20, u_int32_t) +#endif + +#define GEIO_MAGIC 0x69 + +#ifdef GEIO_MAGIC +#define GEIOGET_CHIP_ID _IOR(GEIO_MAGIC, 5, unsigned int) +#endif + +static int vtotal = 32; +static int mbsize; + +module_param(vtotal, int, S_IRUSR | S_IWUSR | S_IWGRP | S_IRGRP | S_IROTH); +MODULE_PARM_DESC(vtotal, "Maximum GE memory size in MiB"); + +module_param(mbsize, int, S_IRUSR | S_IWUSR | S_IWGRP | S_IRGRP | S_IROTH); +MODULE_PARM_DESC(mbsize, "WMT-MB size in MiB"); + +static struct fb_fix_screeninfo gefb_fix = { + .id = "gefb", + .smem_start = 0, + .smem_len = 0, + .type = FB_TYPE_PACKED_PIXELS, + .type_aux = 0, + .visual = FB_VISUAL_TRUECOLOR, + .xpanstep = 1, + .ypanstep = 1, + .ywrapstep = 1, + .line_length = 0, +#ifdef HAVE_MALI + .mmio_start = 0xd8080000, + .mmio_len = 0x10000, +#else + .mmio_start = 0, + .mmio_len = 0, +#endif + .accel = FB_ACCEL_WMT +}; + +static struct fb_var_screeninfo gefb_var = { + .xres = CONFIG_DEFAULT_RESX, + .yres = CONFIG_DEFAULT_RESY, + .xres_virtual = CONFIG_DEFAULT_RESX, + .yres_virtual = CONFIG_DEFAULT_RESY, + /* + .bits_per_pixel = 32, + .red = {16, 8, 0}, + .green = {8, 8, 0}, + .blue = {0, 8, 0}, + .transp = {0, 0, 0}, + */ + .bits_per_pixel = 16, + .red = {11, 5, 0}, + .green = {5, 6, 0}, + .blue = {0, 5, 0}, + .transp = {0, 0, 0}, + .activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE, + .height = -1, + .width = -1, + .pixclock = 39721, + .left_margin = 40, + .right_margin = 24, + .upper_margin = 32, + .lower_margin = 11, + .hsync_len = 96, + .vsync_len = 2, + .vmode = FB_VMODE_NONINTERLACED +}; + +static int gefb_open(struct fb_info *info, int user) +{ + return 0; +} + +static int gefb_release(struct fb_info *info, int user) +{ + return ge_release(info); +} + +static int gefb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ +#ifdef HAVE_VPP + return wmtfb_check_var(var, info); +#else + switch (var->bits_per_pixel) { + case 1: + case 8: + if (var->red.offset > 8) { + /* LUT8 */ + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + } + break; + case 16: + if (var->transp.length) { + /* ARGB 1555 */ + var->red.offset = 10; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 15; + var->transp.length = 1; + } else { + /* RGB 565 */ + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + } + break; + case 24: + /* RGB 888 */ + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 32: + /* ARGB 8888 */ + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + break; + } + return 0; +#endif +} + +static int gefb_set_par(struct fb_info *info) +{ + struct fb_var_screeninfo *var = &info->var; + + /* init your hardware here */ + if (var->bits_per_pixel == 8) + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + else + info->fix.visual = FB_VISUAL_TRUECOLOR; + + if (ge_init(info)) + return -ENOMEM; + +#ifdef HAVE_VPP + vpp_set_par(info); +#endif + + info->fix.line_length = var->xres_virtual * var->bits_per_pixel / 8; + + return 0; +} + +static int gefb_setcolreg(unsigned regno, unsigned red, + unsigned green, unsigned blue, + unsigned transp, struct fb_info *info) +{ + if (regno >= 256) /* no. of hw registers */ + return -EINVAL; + + /* grayscale */ + + if (info->var.grayscale) { + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; + } + + /* The following is for fbcon. */ + + if (info->fix.visual == FB_VISUAL_TRUECOLOR || + info->fix.visual == FB_VISUAL_DIRECTCOLOR) { + + if (regno >= 16) + return -EINVAL; + + switch (info->var.bits_per_pixel) { + case 16: + ((unsigned int *)(info->pseudo_palette))[regno] = + (red & 0xf800) | + ((green & 0xfc00) >> 5) | + ((blue & 0xf800) >> 11); + break; + case 24: + case 32: + red >>= 8; + green >>= 8; + blue >>= 8; + ((unsigned int *)(info->pseudo_palette))[regno] = + (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset); + break; + } + } + return 0; +} + +static int gefb_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + int retval = 0; + + switch (cmd) { + case FBIO_WAITFORVSYNC: + ge_vo_wait_vsync(); + break; +#ifdef HAVE_MALI + case GET_UMP_SECURE_ID: + case GET_UMP_SECURE_ID_BUF1: + case GET_UMP_SECURE_ID_BUF2: { + unsigned int ump_id; + if (mali_get_ump_secure_id) + ump_id = (*mali_get_ump_secure_id)(info->fix.smem_start, + info->fix.smem_len); + else + ump_id = UMP_INVALID_SECURE_ID; + return put_user((unsigned int) ump_id, + (unsigned int __user *) arg); + } + case MALI_GET_UMP_SECURE_ID: { + unsigned int args[3]; + unsigned int ump_id; + copy_from_user(args, (void *)arg, sizeof(unsigned int) * 3); + + if (mali_get_ump_secure_id) + ump_id = (*mali_get_ump_secure_id)(args[0], args[1]); + else + ump_id = UMP_INVALID_SECURE_ID; + + return put_user((unsigned int) ump_id, + (unsigned int __user *) args[2]); + } + case MALI_PUT_UMP_SECURE_ID: { + unsigned int ump_id = (unsigned int)arg; + if (mali_put_ump_secure_id) + (*mali_put_ump_secure_id)(ump_id); + break; + } +#endif /* HAVE_MALI */ +#ifdef GEIO_MAGIC + case GEIOGET_CHIP_ID: { + unsigned int chip_id = + (*(unsigned int *)SYSTEM_CFG_CTRL_BASE_ADDR); + copy_to_user((void *)arg, (void *)&chip_id, + sizeof(unsigned int)); + break; + } +#endif /* GEIO_MAGIC */ +#ifdef USE_SID_ALIAS + case SID_GET_INDEX_FROM_ALIAS: { + unsigned int args[2]; + unsigned int index = -1; + copy_from_user(args, (void *)arg, sizeof(unsigned int) * 2); + spin_lock(&sid_lock); + retval = sid_get_index_from_alias(args[0], &index); + spin_unlock(&sid_lock); + put_user(index, (unsigned int __user *)args[1]); + break; + } + case SID_SET_ALIAS: { + unsigned int args[2]; + copy_from_user(args, (void *)arg, sizeof(unsigned int) * 2); + spin_lock(&sid_lock); + retval = sid_set_alias(args[0], args[1]); +#ifdef DEBUG_SID_ALIAS + printk(KERN_DEBUG "sid_set_alias %d, %d, ret = %d\n", + args[0], args[1], retval); + sid_dump(); +#endif + spin_unlock(&sid_lock); + break; + } + case SID_GET_ALIAS: { + unsigned int args[2]; + unsigned int alias = -1; + copy_from_user(args, (void *)arg, sizeof(unsigned int) * 2); + spin_lock(&sid_lock); + retval = sid_get_alias(args[0], &alias); + spin_unlock(&sid_lock); + put_user(alias, (unsigned int __user *)args[1]); + break; + } + case SID_GET_AND_RESET_ALIAS: { + unsigned int args[2]; + unsigned int alias = -1; + copy_from_user(args, (void *)arg, sizeof(unsigned int) * 2); + spin_lock(&sid_lock); + retval = sid_get_and_reset_alias(args[0], &alias); +#ifdef DEBUG_SID_ALIAS + printk(KERN_DEBUG "sid_get_and_reset_alias %d, %d, ret = %d\n", + args[0], alias, retval); + sid_dump(); +#endif + spin_unlock(&sid_lock); + put_user(alias, (unsigned int __user *)args[1]); + break; + } + case SID_DUMP: { + spin_lock(&sid_lock); + copy_to_user((void *)arg, sid_alias_buf, + sizeof(struct sid_alias) * SID_IDX_MAX); + retval = 0; + spin_unlock(&sid_lock); + break; + } +#endif + default: + break; + } + + return retval; +} + +int gefb_hw_cursor(struct fb_info *info, struct fb_cursor *cursor) +{ + return 0; +} + +static int gefb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + unsigned long off; + unsigned long start; + u32 len; + int ismmio = 0; + + if (!info) + return -ENODEV; + if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) + return -EINVAL; + off = vma->vm_pgoff << PAGE_SHIFT; + + /* frame buffer memory */ + start = info->fix.smem_start; + len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.smem_len); + if (off >= len) { + /* memory mapped io */ + off -= len; + /* + if (info->var.accel_flags) { + return -EINVAL; + } + */ + start = info->fix.mmio_start; + len = PAGE_ALIGN((start & ~PAGE_MASK) + info->fix.mmio_len); + ismmio = 1; + } + start &= PAGE_MASK; + if ((vma->vm_end - vma->vm_start + off) > len) + return -EINVAL; + off += start; + vma->vm_pgoff = off >> PAGE_SHIFT; + /* This is an IO map - tell maydump to skip this VMA */ + vma->vm_flags |= VM_IO | VM_RESERVED; + vma->vm_page_prot = vm_get_page_prot(vma->vm_flags); + + if (ismmio) + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + else + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, vma->vm_page_prot)) + return -EAGAIN; + return 0; +} + +static struct fb_ops gefb_ops = { + .owner = THIS_MODULE, + .fb_open = gefb_open, + .fb_release = gefb_release, + .fb_check_var = gefb_check_var, + .fb_set_par = gefb_set_par, + .fb_setcolreg = gefb_setcolreg, + .fb_pan_display = ge_pan_display, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = ge_blank, + .fb_cursor = gefb_hw_cursor, + .fb_ioctl = gefb_ioctl, + .fb_sync = ge_sync, + .fb_mmap = gefb_mmap, +}; + +#define OPT_EQUAL(opt, name) (!strncmp(opt, name, strlen(name))) +#define OPT_INTVAL(opt, name) kstrtoul(opt + strlen(name) + 1, 0, NULL) +#define OPT_STRVAL(opt, name) (opt + strlen(name)) + +static inline char *get_opt_string(const char *this_opt, const char *name) +{ + const char *p; + int i; + char *ret; + + p = OPT_STRVAL(this_opt, name); + i = 0; + while (p[i] && p[i] != ' ' && p[i] != ',') + i++; + ret = kmalloc(i + 1, GFP_KERNEL); + if (ret) { + strncpy(ret, p, i); + ret[i] = '\0'; + } + return ret; +} + +static inline int get_opt_int(const char *this_opt, const char *name, + int *ret) +{ + if (!ret) + return 0; + + if (!OPT_EQUAL(this_opt, name)) + return 0; + + *ret = OPT_INTVAL(this_opt, name); + + return 1; +} + +static inline int get_opt_bool(const char *this_opt, const char *name, + int *ret) +{ + if (!ret) + return 0; + + if (OPT_EQUAL(this_opt, name)) { + if (this_opt[strlen(name)] == '=') + *ret = kstrtoul(this_opt + strlen(name) + 1, 0, NULL); + else + *ret = 1; + } else { + if (OPT_EQUAL(this_opt, "no") && OPT_EQUAL(this_opt + 2, name)) + *ret = 0; + else + return 0; + } + return 1; +} + +static int __init gefb_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + /* The syntax is: + * video=gefb:[<param>][,<param>=<val>] ... + * e.g., + * video=gefb:vtotal=12,sync2 + */ + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) + continue; + if (get_opt_int(this_opt, "vtotal", &vtotal)) + ; + else if (get_opt_int(this_opt, "mbsize", &mbsize)) + ; + else if (get_opt_bool(this_opt, "vbl", &vbl)) + ; + else if (get_opt_bool(this_opt, "vsync", &vsync)) + ; + else if (get_opt_bool(this_opt, "sync2", &sync2)) + ; + } + + return 0; +} + +#ifdef HAVE_MALI +static struct mali_device *add_mali_device(unsigned int *smem_start_ptr, + unsigned int *smem_len_ptr) +{ + unsigned int len; + struct mali_device *dev = create_mali_device(); + if (dev) { + dev->get_memory_base(smem_start_ptr); + dev->get_memory_size(&len); + *smem_start_ptr += len; + *smem_len_ptr -= len; + dev->set_mem_validation_base(*smem_start_ptr); + dev->set_mem_validation_size(*smem_len_ptr); + } + return dev; +} +#endif /* HAVE_MALI */ + +#ifdef WMT_MB +static int get_mbsize(void) +{ + /* It is bad to read U-Boot partition directly. + * I will remove this code soon. + * -- Vincent + */ + unsigned char buf[32]; + int varlen = 32; + int val; + + if (wmt_getsyspara("mbsize", buf, &varlen) == 0) + sscanf(buf, "%dM", &val); + else + val = 0; + + return val; +} + +static void add_mb_device(unsigned int *smem_start_ptr, + unsigned int *smem_len_ptr) +{ + unsigned int len = mbsize << 20; + + if (*smem_len_ptr > len) + *smem_len_ptr -= len; +} +#endif /* WMT_MB */ + +static int __devinit gefb_probe(struct platform_device *dev) +{ + struct fb_info *info; + int cmap_len, retval; + char mode_option[] = "1024x768@60"; + unsigned int smem_start; + unsigned int smem_len; + unsigned int len; + unsigned int min_smem_len; + + /* Allocate fb_info and par.*/ + info = framebuffer_alloc(sizeof(unsigned int) * 16, &dev->dev); + if (!info) + return -ENOMEM; + + /* Set default fb_info */ + info->fbops = &gefb_ops; + info->fix = gefb_fix; + + info->var = gefb_var; + ge_vo_get_default_var(&info->var); + + smem_start = (num_physpages << PAGE_SHIFT); + smem_len = phy_mem_end() - smem_start; + +#ifdef HAVE_MALI + malidev = add_mali_device(&smem_start, &smem_len); +#endif /* HAVE_MALI */ + +#ifdef WMT_MB + add_mb_device(&smem_start, &smem_len); +#endif /* WMT_MB */ + + /* Set frame buffer region */ + + len = info->var.xres * info->var.yres * + (info->var.bits_per_pixel >> 3); + len *= GE_FB_NUM; + min_smem_len = (len + PAGE_MASK) & ~PAGE_MASK; + + if (smem_len < min_smem_len) { + printk(KERN_ERR "%s: claim region of 0x%08x-0x%08x failed!\n", + __func__, smem_start, smem_start + min_smem_len); + return -EIO; + } + + info->fix.smem_start = smem_start; + + if (smem_len > (vtotal << 20)) + smem_len = (vtotal << 20); + + info->fix.smem_len = smem_len; + + if (!request_mem_region(info->fix.smem_start, + info->fix.smem_len, "gefb")) { + printk(KERN_WARNING + "%s: request memory region failed at 0x%08lx\n", + __func__, info->fix.smem_start); + } + + info->screen_base = ioremap(info->fix.smem_start, + info->fix.smem_len); + if (!info->screen_base) { + printk(KERN_ERR "%s: ioremap fail %d bytes at %p\n", + __func__, info->fix.smem_len, + (void *)info->fix.smem_start); + return -EIO; + } + + printk(KERN_INFO + "gefb: phys 0x%08lx, virt 0x%08lx, total %d KB\n", + info->fix.smem_start, (unsigned long)info->screen_base, + info->fix.smem_len >> 10); + + /* + * The pseudopalette is an 16-member array for fbcon. + */ + info->pseudo_palette = info->par; + info->par = NULL; + info->flags = FBINFO_DEFAULT; /* flag for fbcon */ + + /* + * This should give a reasonable default video mode. + */ + retval = fb_find_mode(&info->var, info, mode_option, + NULL, 0, NULL, 8); + + if (!retval || retval == 4) + return -EINVAL; + + /* + * This has to been done !!! + */ + cmap_len = 256; /* Be the same as VESA */ + retval = fb_alloc_cmap(&info->cmap, cmap_len, 0); + if (retval < 0) + printk(KERN_ERR "%s: fb_alloc_cmap fail.\n", __func__); + + /* + * The following is done in the case of + * having hardware with a static mode. + */ + info->var = gefb_var; + + /* + * Get setting from video output device. + */ + ge_vo_get_default_var(&info->var); + + /* + * For drivers that can... + */ + gefb_check_var(&info->var, info); + + /* + * Apply setting + */ + gefb_set_par(info); + ge_pan_display(&info->var, info); + + if (register_framebuffer(info) < 0) { + ge_exit(info); + return -EINVAL; + } + info->dev->power.async_suspend = 1; /* Add by Charles */ + dev_set_drvdata(&dev->dev, info); + +#ifdef USE_SID_ALIAS + spin_lock_init(&sid_lock); + memset(sid_alias_buf, 0, sizeof(struct sid_alias) * SID_IDX_MAX); +#endif + + return 0; +} + +static int gefb_remove(struct platform_device *dev) +{ + struct fb_info *info = dev_get_drvdata(&dev->dev); + + if (info) { + ge_exit(info); + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); + } + return 0; +} + +static int gefb_suspend(struct platform_device *dev, pm_message_t state) +{ + struct fb_info *info = dev_get_drvdata(&dev->dev); + + if (info) + ge_suspend(info); + +#ifdef HAVE_MALI + if (malidev) + malidev->suspend(1); +#endif + + return 0; +} + +static int gefb_resume(struct platform_device *dev) +{ + struct fb_info *info = dev_get_drvdata(&dev->dev); + + if (info) + ge_resume(info); + +#ifdef HAVE_MALI + if (malidev) + malidev->resume(1); +#endif + + return 0; +} + +static struct platform_driver gefb_driver = { + .driver.name = "gefb", + .probe = gefb_probe, + .remove = gefb_remove, + .suspend = gefb_suspend, + .resume = gefb_resume, +}; + +static u64 gefb_dma_mask = 0xffffffffUL; +static struct platform_device gefb_device = { + .name = "gefb", + .dev = { + .dma_mask = &gefb_dma_mask, + .coherent_dma_mask = ~0, + }, +}; + +#ifdef WMT_MB +static int __init mbsize_setup(char *options) +{ + char *this_opt; + + if (!options || !*options) + return 0; + + while ((this_opt = strsep(&options, ",")) != NULL) { + if (!*this_opt) + continue; + sscanf(this_opt, "%dM", &mbsize); + printk(KERN_DEBUG "gefb: detected mbsize = %d MiB\n", mbsize); + } + + return 0; +} +__setup("mbsize=", mbsize_setup); +#endif /* WMT_MB */ + +static int __init gefb_init(void) +{ + int ret; + char *option = NULL; + + fb_get_options("gefb", &option); + gefb_setup(option); + +#ifdef WMT_MB + /* It is bad to read U-Boot partition directly. + * I will remove this code soon. + * -- Vincent + */ + if (!mbsize) { + mbsize = get_mbsize(); + printk(KERN_ERR "Please add \'mbsize=%dM\' in bootargs!", + mbsize); + } +#endif /* WMT_MB */ + + ret = platform_driver_register(&gefb_driver); + if (!ret) { + ret = platform_device_register(&gefb_device); + if (ret) + platform_driver_unregister(&gefb_driver); + } + + return ret; +} +module_init(gefb_init); + +static void __exit gefb_exit(void) +{ + release_mali_device(malidev); + + platform_driver_unregister(&gefb_driver); + platform_device_unregister(&gefb_device); + return; +} + +module_exit(gefb_exit); + +MODULE_AUTHOR("WonderMedia Technologies, Inc."); +MODULE_DESCRIPTION("WMT GE driver"); +MODULE_LICENSE("GPL"); + |