/*
* 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 .
*
* WonderMedia Technologies, Inc.
* 4F, 533, Chung-Cheng Road, Hsin-Tien, Taipei 231, R.O.C
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "ge_accel.h"
int flipcnt;
int flipreq;
int vbl;
int vsync = 1;
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(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);
unsigned int n = 32 << 20;
while (n <= memsize)
n <<= 1;
memsize = n;
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
wmtfb_set_mutex(info, 1);
vpp_pan_display(var, info);
wmtfb_set_mutex(info, 0);
#endif
flipcnt++;
return 0;
}
struct ge_var {
struct fb_info *info;
struct fb_var_screeninfo var[1];
struct fb_var_screeninfo new_var[1];
struct timeval most_recent_flip_time;
int dirty;
int force_sync;
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, int wait);
};
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, int wait);
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->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);
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);
kfree(ge_var);
}
}
static void ge_var_start(struct ge_var *ge_var)
{
spin_lock_init(ge_var->lock);
}
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)
{
int wait;
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);
wait = vbl || (var->activate & FB_ACTIVATE_VBL);
ge_var->sync(ge_var, wait);
}
static void ge_var_sync(struct ge_var *ge_var, int wait)
{
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 (wait && 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 "ge: wait vsync for %d ms\n", ms);
} else
ge_vo_wait_vsync();
}
do_gettimeofday(&ge_var->most_recent_flip_time);
}
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, 0); /* 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;
}