blob: 1ca02c913c4e1c7f51bceb4fa4f564e32bbfe7f4 [file] [log] [blame]
/*
* drivers/drivers/video/ambarella/ambarella_fb.c
*
* 2008/07/22 - [linnsong] Create
* 2009/03/03 - [Anthony Ginger] Port to 2.6.28
* 2009/12/15 - [Zhenwu Xue] Change fb_setcmap
*
* Copyright (C) 2004-2009, Ambarella, 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/mm.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/ioport.h>
#include <linux/list.h>
#include <linux/amba/bus.h>
#include <linux/amba/clcd.h>
#include <linux/clk.h>
#include <linux/proc_fs.h>
#include <linux/platform_device.h>
#include <asm/sizes.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/page.h>
#include <asm/pgtable.h>
#include <plat/fb.h>
/* video=amb0fb:<x_res>x<y_res>,<x_virtual>x<y_virtual>,
<color_format>,<conversion_buffer>[,<prealloc_start>,<prealloc_length>] */
static const struct ambarella_fb_color_table_s {
enum ambarella_fb_color_format color_format;
int bits_per_pixel;
struct fb_bitfield red;
struct fb_bitfield green;
struct fb_bitfield blue;
struct fb_bitfield transp;
} ambarella_fb_color_format_table[] =
{
{
.color_format = AMBFB_COLOR_CLUT_8BPP,
.bits_per_pixel = 8,
.red =
{
.offset = 0,
.length = 0,
.msb_right = 0,
},
.green =
{
.offset = 0,
.length = 0,
.msb_right = 0,
},
.blue =
{
.offset = 0,
.length = 0,
.msb_right = 0,
},
.transp =
{
.offset = 0,
.length = 0,
.msb_right = 0,
},
},
{
.color_format = AMBFB_COLOR_RGB565,
.bits_per_pixel = 16,
.red =
{
.offset = 11,
.length = 5,
.msb_right = 0,
},
.green =
{
.offset = 5,
.length = 6,
.msb_right = 0,
},
.blue =
{
.offset = 0,
.length = 5,
.msb_right = 0,
},
.transp =
{
.offset = 0,
.length = 0,
.msb_right = 0,
},
},
{
.color_format = AMBFB_COLOR_BGR565,
.bits_per_pixel = 16,
.red =
{
.offset = 0,
.length = 5,
.msb_right = 0,
},
.green =
{
.offset = 5,
.length = 6,
.msb_right = 0,
},
.blue =
{
.offset = 11,
.length = 5,
.msb_right = 0,
},
.transp =
{
.offset = 0,
.length = 0,
.msb_right = 0,
},
},
{
.color_format = AMBFB_COLOR_AGBR4444,
.bits_per_pixel = 16,
.red =
{
.offset = 0,
.length = 4,
.msb_right = 0,
},
.green =
{
.offset = 8,
.length = 4,
.msb_right = 0,
},
.blue =
{
.offset = 4,
.length = 4,
.msb_right = 0,
},
.transp =
{
.offset = 12,
.length = 4,
.msb_right = 0,
},
},
{
.color_format = AMBFB_COLOR_RGBA4444,
.bits_per_pixel = 16,
.red =
{
.offset = 12,
.length = 4,
.msb_right = 0,
},
.green =
{
.offset = 8,
.length = 4,
.msb_right = 0,
},
.blue =
{
.offset = 4,
.length = 4,
.msb_right = 0,
},
.transp =
{
.offset = 0,
.length = 4,
.msb_right = 0,
},
},
{
.color_format = AMBFB_COLOR_BGRA4444,
.bits_per_pixel = 16,
.red =
{
.offset = 4,
.length = 4,
.msb_right = 0,
},
.green =
{
.offset = 8,
.length = 4,
.msb_right = 0,
},
.blue =
{
.offset = 12,
.length = 4,
.msb_right = 0,
},
.transp =
{
.offset = 0,
.length = 4,
.msb_right = 0,
},
},
{
.color_format = AMBFB_COLOR_ABGR4444,
.bits_per_pixel = 16,
.red =
{
.offset = 0,
.length = 4,
.msb_right = 0,
},
.green =
{
.offset = 4,
.length = 4,
.msb_right = 0,
},
.blue =
{
.offset = 8,
.length = 4,
.msb_right = 0,
},
.transp =
{
.offset = 12,
.length = 4,
.msb_right = 0,
},
},
{
.color_format = AMBFB_COLOR_ARGB4444,
.bits_per_pixel = 16,
.red =
{
.offset = 8,
.length = 4,
.msb_right = 0,
},
.green =
{
.offset = 4,
.length = 4,
.msb_right = 0,
},
.blue =
{
.offset = 0,
.length = 4,
.msb_right = 0,
},
.transp =
{
.offset = 12,
.length = 4,
.msb_right = 0,
},
},
{
.color_format = AMBFB_COLOR_AGBR1555,
.bits_per_pixel = 16,
.red =
{
.offset = 0,
.length = 5,
.msb_right = 0,
},
.green =
{
.offset = 10,
.length = 5,
.msb_right = 0,
},
.blue =
{
.offset = 5,
.length = 5,
.msb_right = 0,
},
.transp =
{
.offset = 15,
.length = 1,
.msb_right = 0,
},
},
{
.color_format = AMBFB_COLOR_GBR1555,
.bits_per_pixel = 16,
.red =
{
.offset = 0,
.length = 5,
.msb_right = 0,
},
.green =
{
.offset = 10,
.length = 5,
.msb_right = 0,
},
.blue =
{
.offset = 5,
.length = 5,
.msb_right = 0,
},
.transp =
{
.offset = 0,
.length = 0,
.msb_right = 0,
},
},
{
.color_format = AMBFB_COLOR_RGBA5551,
.bits_per_pixel = 16,
.red =
{
.offset = 11,
.length = 5,
.msb_right = 0,
},
.green =
{
.offset = 6,
.length = 5,
.msb_right = 0,
},
.blue =
{
.offset = 1,
.length = 5,
.msb_right = 0,
},
.transp =
{
.offset = 0,
.length = 1,
.msb_right = 0,
},
},
{
.color_format = AMBFB_COLOR_BGRA5551,
.bits_per_pixel = 16,
.red =
{
.offset = 1,
.length = 5,
.msb_right = 0,
},
.green =
{
.offset = 6,
.length = 5,
.msb_right = 0,
},
.blue =
{
.offset = 11,
.length = 5,
.msb_right = 0,
},
.transp =
{
.offset = 0,
.length = 1,
.msb_right = 0,
},
},
{
.color_format = AMBFB_COLOR_ABGR1555,
.bits_per_pixel = 16,
.red =
{
.offset = 0,
.length = 5,
.msb_right = 0,
},
.green =
{
.offset = 5,
.length = 5,
.msb_right = 0,
},
.blue =
{
.offset = 10,
.length = 5,
.msb_right = 0,
},
.transp =
{
.offset = 15,
.length = 1,
.msb_right = 0,
},
},
{
.color_format = AMBFB_COLOR_ARGB1555,
.bits_per_pixel = 16,
.red =
{
.offset = 10,
.length = 5,
.msb_right = 0,
},
.green =
{
.offset = 5,
.length = 5,
.msb_right = 0,
},
.blue =
{
.offset = 0,
.length = 5,
.msb_right = 0,
},
.transp =
{
.offset = 15,
.length = 1,
.msb_right = 0,
},
},
{
.color_format = AMBFB_COLOR_AGBR8888,
.bits_per_pixel = 32,
.red =
{
.offset = 0,
.length = 8,
.msb_right = 0,
},
.green =
{
.offset = 16,
.length = 8,
.msb_right = 0,
},
.blue =
{
.offset = 8,
.length = 8,
.msb_right = 0,
},
.transp =
{
.offset = 24,
.length = 8,
.msb_right = 0,
},
},
{
.color_format = AMBFB_COLOR_RGBA8888,
.bits_per_pixel = 32,
.red =
{
.offset = 24,
.length = 8,
.msb_right = 0,
},
.green =
{
.offset = 16,
.length = 8,
.msb_right = 0,
},
.blue =
{
.offset = 8,
.length = 8,
.msb_right = 0,
},
.transp =
{
.offset = 0,
.length = 8,
.msb_right = 0,
},
},
{
.color_format = AMBFB_COLOR_BGRA8888,
.bits_per_pixel = 32,
.red =
{
.offset = 8,
.length = 8,
.msb_right = 0,
},
.green =
{
.offset = 16,
.length = 8,
.msb_right = 0,
},
.blue =
{
.offset = 24,
.length = 8,
.msb_right = 0,
},
.transp =
{
.offset = 0,
.length = 8,
.msb_right = 0,
},
},
{
.color_format = AMBFB_COLOR_ABGR8888,
.bits_per_pixel = 32,
.red =
{
.offset = 0,
.length = 8,
.msb_right = 0,
},
.green =
{
.offset = 8,
.length = 8,
.msb_right = 0,
},
.blue =
{
.offset = 16,
.length = 8,
.msb_right = 0,
},
.transp =
{
.offset = 24,
.length = 8,
.msb_right = 0,
},
},
{
.color_format = AMBFB_COLOR_ARGB8888,
.bits_per_pixel = 32,
.red =
{
.offset = 16,
.length = 8,
.msb_right = 0,
},
.green =
{
.offset = 8,
.length = 8,
.msb_right = 0,
},
.blue =
{
.offset = 0,
.length = 8,
.msb_right = 0,
},
.transp =
{
.offset = 24,
.length = 8,
.msb_right = 0,
},
},
};
/* ========================================================================== */
static int ambarella_fb_proc_read(char *page, char **start,
off_t off, int count, int *eof, void *data)
{
int len;
struct ambarella_platform_fb *ambfb_data;
struct fb_info *info;
ambfb_data = (struct ambarella_platform_fb *)data;
info = ambfb_data->proc_fb_info;
*start = page + off;
wait_event_interruptible(ambfb_data->proc_wait,
(ambfb_data->proc_wait_flag > 0));
ambfb_data->proc_wait_flag = 0;
dev_dbg(info->device, "%s:%d %d.\n", __func__, __LINE__,
ambfb_data->proc_wait_flag);
len = sprintf(*start, "%04d %04d %04d %04d %04d %04d\n",
info->var.xres, info->var.yres,
info->fix.line_length,
info->var.xoffset, info->var.yoffset,
ambfb_data->color_format);
if (off + len > count) {
*eof = 1;
len = 0;
}
return len;
}
/* ========================================================================== */
static int ambfb_setcmap(struct fb_cmap *cmap, struct fb_info *info)
{
int errorCode = 0;
struct ambarella_platform_fb *ambfb_data;
int pos;
u16 *r, *g, *b, *t;
u8 *pclut_table, *pblend_table;
if (cmap == &info->cmap) {
errorCode = 0;
goto ambfb_setcmap_exit;
}
if (cmap->start != 0 || cmap->len != 256) {
dev_dbg(info->device,
"%s: Incorrect parameters: start = %d, len = %d\n",
__func__, cmap->start, cmap->len);
errorCode = -1;
goto ambfb_setcmap_exit;
}
if (!cmap->red || !cmap->green || !cmap->blue) {
dev_dbg(info->device, "%s: Incorrect rgb pointers!\n",
__func__);
errorCode = -1;
goto ambfb_setcmap_exit;
}
ambfb_data = (struct ambarella_platform_fb *)info->par;
mutex_lock(&ambfb_data->lock);
r = cmap->red;
g = cmap->green;
b = cmap->blue;
t = cmap->transp;
pclut_table = ambfb_data->clut_table;
pblend_table = ambfb_data->blend_table;
for (pos = 0; pos < 256; pos++) {
*pclut_table++ = *r++;
*pclut_table++ = *g++;
*pclut_table++ = *b++;
if (t) *pblend_table++ = *t++;
}
if (ambfb_data->setcmap) {
mutex_unlock(&ambfb_data->lock);
errorCode = ambfb_data->setcmap(cmap, info);
} else {
mutex_unlock(&ambfb_data->lock);
}
ambfb_setcmap_exit:
dev_dbg(info->device, "%s:%d %d.\n", __func__, __LINE__, errorCode);
return errorCode;
}
static int ambfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
int errorCode = 0;
struct ambarella_platform_fb *ambfb_data;
u32 framesize = 0;
ambfb_data = (struct ambarella_platform_fb *)info->par;
mutex_lock(&ambfb_data->lock);
if (ambfb_data->check_var) {
errorCode = ambfb_data->check_var(var, info);
}
mutex_unlock(&ambfb_data->lock);
if (var->xres_virtual * var->bits_per_pixel / 8 > info->fix.line_length) {
errorCode = -ENOMEM;
dev_err(info->device, "%s: xres_virtual[%d] too big [%d]!\n",
__func__, var->xres_virtual * var->bits_per_pixel / 8,
info->fix.line_length);
}
framesize = info->fix.line_length * var->yres_virtual;
if (framesize > info->fix.smem_len) {
errorCode = -ENOMEM;
dev_err(info->device, "%s: framesize[%d] too big [%d]!\n",
__func__, framesize, info->fix.smem_len);
}
dev_dbg(info->device, "%s:%d %d.\n", __func__, __LINE__, errorCode);
return errorCode;
}
static int ambfb_set_par(struct fb_info *info)
{
int errorCode = 0, i;
struct ambarella_platform_fb *ambfb_data;
struct fb_var_screeninfo *pvar;
static struct fb_var_screeninfo *cur_var;
int res_changed = 0;
int color_format_changed = 0;
enum ambarella_fb_color_format new_color_format;
const struct ambarella_fb_color_table_s *pTable;
ambfb_data = (struct ambarella_platform_fb *)info->par;
mutex_lock(&ambfb_data->lock);
cur_var = &ambfb_data->screen_var;
pvar = &info->var;
if (!ambfb_data->set_par)
goto ambfb_set_par_quick_exit;
/* Resolution changed */
if (pvar->xres != cur_var->xres || pvar->yres != cur_var->yres) {
res_changed = 1;
}
/* Color format changed */
if (pvar->bits_per_pixel != cur_var->bits_per_pixel ||
pvar->red.offset != cur_var->red.offset ||
pvar->red.length != cur_var->red.length ||
pvar->red.msb_right != cur_var->red.msb_right ||
pvar->green.offset != cur_var->green.offset ||
pvar->green.length != cur_var->green.length ||
pvar->green.msb_right != cur_var->green.msb_right ||
pvar->blue.offset != cur_var->blue.offset ||
pvar->blue.length != cur_var->blue.length ||
pvar->blue.msb_right != cur_var->blue.msb_right ||
pvar->transp.offset != cur_var->transp.offset ||
pvar->transp.length != cur_var->transp.length ||
pvar->transp.msb_right != cur_var->transp.msb_right) {
color_format_changed = 1;
}
if (!res_changed && !color_format_changed)
goto ambfb_set_par_quick_exit;
/* Find color format */
new_color_format = ambfb_data->color_format;
pTable = ambarella_fb_color_format_table;
for (i = 0; i < ARRAY_SIZE(ambarella_fb_color_format_table); i++) {
if (pTable->bits_per_pixel == pvar->bits_per_pixel &&
pTable->red.offset == pvar->red.offset &&
pTable->red.length == pvar->red.length &&
pTable->red.msb_right == pvar->red.msb_right &&
pTable->green.offset == pvar->green.offset &&
pTable->green.length == pvar->green.length &&
pTable->green.msb_right == pvar->green.msb_right &&
pTable->blue.offset == pvar->blue.offset &&
pTable->blue.length == pvar->blue.length &&
pTable->blue.msb_right == pvar->blue.msb_right &&
pTable->transp.offset == pvar->transp.offset &&
pTable->transp.length == pvar->transp.length &&
pTable->transp.msb_right == pvar->transp.msb_right) {
new_color_format = pTable->color_format;
break;
}
pTable++;
}
ambfb_data->color_format = new_color_format;
memcpy(cur_var, pvar, sizeof(*pvar));
mutex_unlock(&ambfb_data->lock);
errorCode = ambfb_data->set_par(info);
goto ambfb_set_par_normal_exit;
ambfb_set_par_quick_exit:
memcpy(cur_var, pvar, sizeof(*pvar));
mutex_unlock(&ambfb_data->lock);
ambfb_set_par_normal_exit:
dev_dbg(info->device, "%s:%d %d.\n", __func__, __LINE__, errorCode);
return errorCode;
}
static int ambfb_pan_display(struct fb_var_screeninfo *var,
struct fb_info *info)
{
int errorCode = 0;
struct ambarella_platform_fb *ambfb_data;
ambfb_data = (struct ambarella_platform_fb *)info->par;
ambfb_data->proc_wait_flag++;
wake_up(&(ambfb_data->proc_wait));
mutex_lock(&ambfb_data->lock);
ambfb_data->screen_var.xoffset = var->xoffset;
ambfb_data->screen_var.yoffset = var->yoffset;
if (ambfb_data->pan_display) {
errorCode = ambfb_data->pan_display(var, info);
}
mutex_unlock(&ambfb_data->lock);
dev_dbg(info->device, "%s:%d %d.\n", __func__, __LINE__, errorCode);
return 0;
}
static int ambfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
unsigned long size = vma->vm_end - vma->vm_start;
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
if (offset + size > info->fix.smem_len)
return -EINVAL;
offset += info->fix.smem_start;
if (remap_pfn_range(vma, vma->vm_start, offset >> PAGE_SHIFT,
size, vma->vm_page_prot))
return -EAGAIN;
dev_dbg(info->device, "%s: P(0x%08lx)->V(0x%08lx), size = 0x%08lx.\n",
__func__, offset, vma->vm_start, size);
return 0;
}
static int ambfb_blank(int blank_mode, struct fb_info *info)
{
int errorCode = 0;
struct ambarella_platform_fb *ambfb_data;
ambfb_data = (struct ambarella_platform_fb *)info->par;
mutex_lock(&ambfb_data->lock);
if (ambfb_data->set_blank) {
errorCode = ambfb_data->set_blank(blank_mode, info);
}
mutex_unlock(&ambfb_data->lock);
dev_dbg(info->device, "%s:%d %d.\n", __func__, __LINE__, errorCode);
return errorCode;
}
static struct fb_ops ambfb_ops = {
.owner = THIS_MODULE,
.fb_check_var = ambfb_check_var,
.fb_set_par = ambfb_set_par,
.fb_pan_display = ambfb_pan_display,
.fb_fillrect = sys_fillrect,
.fb_copyarea = sys_copyarea,
.fb_imageblit = sys_imageblit,
.fb_mmap = ambfb_mmap,
.fb_setcmap = ambfb_setcmap,
.fb_blank = ambfb_blank,
};
static int __init ambfb_setup(struct device *dev, char *options,
struct ambarella_platform_fb *ambfb_data)
{
int retval = -1;
int ssret;
int cl_xres;
int cl_yres;
int cl_xvirtual;
int cl_yvirtual;
int cl_format;
int cl_cvs_buf;
unsigned int cl_prealloc_start;
unsigned int cl_prealloc_length;
if (!options || !*options) {
goto ambfb_setup_exit;
}
ssret = sscanf(options, "%dx%d,%dx%d,%d,%d,%x,%x", &cl_xres, &cl_yres,
&cl_xvirtual, &cl_yvirtual, &cl_format, &cl_cvs_buf,
&cl_prealloc_start, &cl_prealloc_length);
if (ssret == 6) {
ambfb_data->screen_var.xres = cl_xres;
ambfb_data->screen_var.yres = cl_yres;
ambfb_data->screen_var.xres_virtual = cl_xvirtual;
ambfb_data->screen_var.yres_virtual = cl_yvirtual;
ambfb_data->color_format = cl_format;
ambfb_data->conversion_buf.available = cl_cvs_buf;
dev_dbg(dev, "%dx%d,%dx%d,%d,%d\n", cl_xres, cl_yres,
cl_xvirtual, cl_yvirtual, cl_format, cl_cvs_buf);
retval = 0;
} else
if (ssret == 8) {
ambfb_data->screen_var.xres = cl_xres;
ambfb_data->screen_var.yres = cl_yres;
ambfb_data->screen_var.xres_virtual = cl_xvirtual;
ambfb_data->screen_var.yres_virtual = cl_yvirtual;
ambfb_data->color_format = cl_format;
ambfb_data->conversion_buf.available = cl_cvs_buf;
ambfb_data->screen_fix.smem_start = cl_prealloc_start;
ambfb_data->screen_fix.smem_len = cl_prealloc_length;
ambfb_data->use_prealloc = 1;
dev_dbg(dev, "%dx%d,%dx%d,%d,%d,%x,%x\n", cl_xres, cl_yres,
cl_xvirtual, cl_yvirtual, cl_format, cl_cvs_buf,
cl_prealloc_start, cl_prealloc_length);
retval = 0;
} else {
dev_err(dev, "Can not support %s@%d!\n", options, ssret);
}
ambfb_setup_exit:
return retval;
}
static int __devinit ambfb_probe(struct platform_device *pdev)
{
int errorCode = 0;
struct fb_info *info;
char fb_name[64];
char *option;
struct ambarella_platform_fb *ambfb_data = NULL;
u32 i;
u32 framesize;
u32 line_length;
ambfb_data = (struct ambarella_platform_fb *)pdev->dev.platform_data;
if (ambfb_data == NULL) {
dev_err(&pdev->dev, "%s: ambfb_data is NULL!\n", __func__);
errorCode = -ENODEV;
goto ambfb_probe_exit;
}
if (ambfb_data->use_prealloc) {
dev_info(&pdev->dev, "%s: use prealloc.\n", __func__);
} else {
snprintf(fb_name, sizeof(fb_name), "amb%dfb", pdev->id);
if (fb_get_options(fb_name, &option)) {
errorCode = -ENODEV;
goto ambfb_probe_exit;
}
if (ambfb_setup(&pdev->dev, option, ambfb_data)) {
errorCode = -ENODEV;
goto ambfb_probe_exit;
}
}
info = framebuffer_alloc(sizeof(ambfb_data), &pdev->dev);
if (info == NULL) {
dev_err(&pdev->dev, "%s: framebuffer_alloc fail!\n", __func__);
errorCode = -ENOMEM;
goto ambfb_probe_exit;
}
mutex_lock(&ambfb_data->lock);
info->fbops = &ambfb_ops;
info->par = ambfb_data;
info->var = ambfb_data->screen_var;
info->fix = ambfb_data->screen_fix;
info->flags = FBINFO_FLAG_DEFAULT;
/* Fill Color-related Variables */
for (i = 0; i < ARRAY_SIZE(ambarella_fb_color_format_table); i++) {
if (ambarella_fb_color_format_table[i].color_format ==
ambfb_data->color_format)
break;
}
if (i < ARRAY_SIZE(ambarella_fb_color_format_table)) {
info->var.bits_per_pixel =
ambarella_fb_color_format_table[i].bits_per_pixel;
info->var.red = ambarella_fb_color_format_table[i].red;
info->var.green = ambarella_fb_color_format_table[i].green;
info->var.blue = ambarella_fb_color_format_table[i].blue;
info->var.transp = ambarella_fb_color_format_table[i].transp;
}
/* Malloc Framebuffer Memory */
line_length = (info->var.xres_virtual *
(info->var.bits_per_pixel / 8) + 31) & 0xffffffe0;
if (ambfb_data->use_prealloc == 0) {
info->fix.line_length = line_length;
} else {
info->fix.line_length =
(line_length > ambfb_data->prealloc_line_length) ?
line_length : ambfb_data->prealloc_line_length;
}
framesize = info->fix.line_length * info->var.yres_virtual;
if (framesize % PAGE_SIZE) {
framesize /= PAGE_SIZE;
framesize++;
framesize *= PAGE_SIZE;
}
if (ambfb_data->use_prealloc == 0) {
info->screen_base = kzalloc(framesize, GFP_KERNEL);
if (info->screen_base == NULL) {
dev_err(&pdev->dev, "%s(%d): Can't get fbmem!\n",
__func__, __LINE__);
errorCode = -ENOMEM;
goto ambfb_probe_release_framebuffer;
}
if (ambfb_data->conversion_buf.available) {
ambfb_data->conversion_buf.ping_buf =
kzalloc(framesize, GFP_KERNEL);
if (ambfb_data->conversion_buf.ping_buf == NULL) {
ambfb_data->conversion_buf.available = 0;
dev_err(&pdev->dev, "%s(%d): Can't get fbmem!\n",
__func__, __LINE__);
errorCode = -ENOMEM;
goto ambfb_probe_release_framebuffer;
} else {
ambfb_data->conversion_buf.ping_buf_size
= framesize;
}
ambfb_data->conversion_buf.pong_buf =
kzalloc(framesize, GFP_KERNEL);
if (ambfb_data->conversion_buf.pong_buf == NULL) {
ambfb_data->conversion_buf.available = 0;
dev_err(&pdev->dev, "%s(%d): Can't get fbmem!\n",
__func__, __LINE__);
errorCode = -ENOMEM;
goto ambfb_probe_release_framebuffer;
} else {
ambfb_data->conversion_buf.pong_buf_size
= framesize;
}
}
info->fix.smem_start = virt_to_phys(info->screen_base);
info->fix.smem_len = framesize;
} else {
if ((info->fix.smem_start == 0) ||
(info->fix.smem_len < framesize)) {
dev_err(&pdev->dev, "%s: prealloc[0x%08x < 0x%08x]!\n",
__func__, info->fix.smem_len, framesize);
errorCode = -ENOMEM;
goto ambfb_probe_release_framebuffer;
}
info->screen_base = (char __iomem *)
ambarella_phys_to_virt(info->fix.smem_start);
//memset(info->screen_base, 0, info->fix.smem_len);
if (ambfb_data->conversion_buf.available) {
if (info->fix.smem_len < 3 * framesize) {
ambfb_data->conversion_buf.available = 0;
dev_err(&pdev->dev,
"%s: prealloc[0x%08x < 0x%08x]!\n",
__func__, info->fix.smem_len,
3 * framesize);
errorCode = -ENOMEM;
goto ambfb_probe_release_framebuffer;
} else {
ambfb_data->conversion_buf.ping_buf =
info->screen_base + framesize;
ambfb_data->conversion_buf.ping_buf_size =
framesize;
ambfb_data->conversion_buf.ping_buf =
info->screen_base + framesize;
ambfb_data->conversion_buf.ping_buf_size =
framesize;
}
}
}
errorCode = fb_alloc_cmap(&info->cmap, 256, 1);
if (errorCode < 0) {
dev_err(&pdev->dev, "%s: fb_alloc_cmap fail!\n", __func__);
errorCode = -ENOMEM;
goto ambfb_probe_release_framebuffer;
}
for (i = info->cmap.start; i < info->cmap.len; i++) {
info->cmap.red[i] =
ambfb_data->clut_table[i * AMBARELLA_CLUT_BYTES + 0];
info->cmap.red[i] <<= 8;
info->cmap.green[i] =
ambfb_data->clut_table[i * AMBARELLA_CLUT_BYTES + 1];
info->cmap.green[i] <<= 8;
info->cmap.blue[i] =
ambfb_data->clut_table[i * AMBARELLA_CLUT_BYTES + 2];
info->cmap.blue[i] <<= 8;
if (info->cmap.transp) {
info->cmap.transp[i] = ambfb_data->blend_table[i];
info->cmap.transp[i] <<= 8;
}
}
platform_set_drvdata(pdev, info);
ambfb_data->fb_status = AMBFB_ACTIVE_MODE;
mutex_unlock(&ambfb_data->lock);
errorCode = register_framebuffer(info);
if (errorCode < 0) {
dev_err(&pdev->dev, "%s: register_framebuffer fail!\n",
__func__);
errorCode = -ENOMEM;
mutex_lock(&ambfb_data->lock);
goto ambfb_probe_dealloc_cmap;
}
ambfb_data->proc_fb_info = info;
ambfb_data->proc_file = create_proc_entry(dev_name(&pdev->dev),
S_IRUGO | S_IWUSR, get_ambarella_proc_dir());
if (ambfb_data->proc_file == NULL) {
dev_err(&pdev->dev, "Register %s failed!\n",
dev_name(&pdev->dev));
errorCode = -ENOMEM;
goto ambfb_probe_unregister_framebuffer;
} else {
ambfb_data->proc_file->read_proc = ambarella_fb_proc_read;
ambfb_data->proc_file->data = ambfb_data;
}
mutex_lock(&ambfb_data->lock);
ambfb_data->screen_var = info->var;
ambfb_data->screen_fix = info->fix;
mutex_unlock(&ambfb_data->lock);
dev_info(&pdev->dev,
"probe p[%dx%d] v[%dx%d] c[%d] b[%d] l[%d] @ [0x%08lx:0x%08x]!\n",
info->var.xres, info->var.yres, info->var.xres_virtual,
info->var.yres_virtual, ambfb_data->color_format,
ambfb_data->conversion_buf.available,
info->fix.line_length,
info->fix.smem_start, info->fix.smem_len);
goto ambfb_probe_exit;
ambfb_probe_unregister_framebuffer:
unregister_framebuffer(info);
ambfb_probe_dealloc_cmap:
fb_dealloc_cmap(&info->cmap);
ambfb_probe_release_framebuffer:
framebuffer_release(info);
ambfb_data->fb_status = AMBFB_STOP_MODE;
mutex_unlock(&ambfb_data->lock);
ambfb_probe_exit:
return errorCode;
}
static int __devexit ambfb_remove(struct platform_device *pdev)
{
struct fb_info *info;
struct ambarella_platform_fb *ambfb_data = NULL;
info = platform_get_drvdata(pdev);
if (info) {
remove_proc_entry(dev_name(&pdev->dev),
get_ambarella_proc_dir());
unregister_framebuffer(info);
ambfb_data = (struct ambarella_platform_fb *)info->par;
mutex_lock(&ambfb_data->lock);
ambfb_data->fb_status = AMBFB_STOP_MODE;
fb_dealloc_cmap(&info->cmap);
if (ambfb_data->use_prealloc == 0) {
if (info->screen_base) {
kfree(info->screen_base);
}
if (ambfb_data->conversion_buf.available) {
if (ambfb_data->conversion_buf.ping_buf) {
kfree(ambfb_data->conversion_buf.ping_buf);
}
if (ambfb_data->conversion_buf.pong_buf) {
kfree(ambfb_data->conversion_buf.pong_buf);
}
}
ambfb_data->screen_fix.smem_start = 0;
ambfb_data->screen_fix.smem_len = 0;
}
platform_set_drvdata(pdev, NULL);
framebuffer_release(info);
mutex_unlock(&ambfb_data->lock);
}
return 0;
}
#ifdef CONFIG_PM
static int ambfb_suspend(struct platform_device *pdev, pm_message_t state)
{
int errorCode = 0;
dev_dbg(&pdev->dev, "%s exit with %d @ %d\n",
__func__, errorCode, state.event);
return errorCode;
}
static int ambfb_resume(struct platform_device *pdev)
{
int errorCode = 0;
dev_dbg(&pdev->dev, "%s exit with %d\n", __func__, errorCode);
return errorCode;
}
#endif
static struct platform_driver ambfb_driver = {
.probe = ambfb_probe,
.remove = __devexit_p(ambfb_remove),
#ifdef CONFIG_PM
.suspend = ambfb_suspend,
.resume = ambfb_resume,
#endif
.driver = {
.name = "ambarella-fb",
},
};
static int __init ambavoutfb_init(void)
{
return platform_driver_register(&ambfb_driver);
}
static void __exit ambavoutfb_exit(void)
{
platform_driver_unregister(&ambfb_driver);
}
MODULE_LICENSE("GPL");
module_init(ambavoutfb_init);
module_exit(ambavoutfb_exit);