forked from Minki/linux
2164235da9
There was some conditionalizing for the LWMON5 boards in kernel source. However infrastructure for enabling this isn't here so probably the special case code can go as well. Signed-off-by: Christoph Egger <siccegge@stud.informatik.uni-erlangen.de> Acked-by: Alexander Shishkin <virtuoso@slind.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
1063 lines
26 KiB
C
1063 lines
26 KiB
C
/*
|
|
* drivers/mb862xx/mb862xxfb.c
|
|
*
|
|
* Fujitsu Carmine/Coral-P(A)/Lime framebuffer driver
|
|
*
|
|
* (C) 2008 Anatolij Gustschin <agust@denx.de>
|
|
* DENX Software Engineering
|
|
*
|
|
* 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.
|
|
*
|
|
*/
|
|
|
|
#undef DEBUG
|
|
|
|
#include <linux/fb.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/pci.h>
|
|
#if defined(CONFIG_OF)
|
|
#include <linux/of_platform.h>
|
|
#endif
|
|
#include "mb862xxfb.h"
|
|
#include "mb862xx_reg.h"
|
|
|
|
#define NR_PALETTE 256
|
|
#define MB862XX_MEM_SIZE 0x1000000
|
|
#define CORALP_MEM_SIZE 0x4000000
|
|
#define CARMINE_MEM_SIZE 0x8000000
|
|
#define DRV_NAME "mb862xxfb"
|
|
|
|
#if defined(CONFIG_SOCRATES)
|
|
static struct mb862xx_gc_mode socrates_gc_mode = {
|
|
/* Mode for Prime View PM070WL4 TFT LCD Panel */
|
|
{ "800x480", 45, 800, 480, 40000, 86, 42, 33, 10, 128, 2, 0, 0, 0 },
|
|
/* 16 bits/pixel, 16MB, 133MHz, SDRAM memory mode value */
|
|
16, 0x1000000, GC_CCF_COT_133, 0x4157ba63
|
|
};
|
|
#endif
|
|
|
|
/* Helpers */
|
|
static inline int h_total(struct fb_var_screeninfo *var)
|
|
{
|
|
return var->xres + var->left_margin +
|
|
var->right_margin + var->hsync_len;
|
|
}
|
|
|
|
static inline int v_total(struct fb_var_screeninfo *var)
|
|
{
|
|
return var->yres + var->upper_margin +
|
|
var->lower_margin + var->vsync_len;
|
|
}
|
|
|
|
static inline int hsp(struct fb_var_screeninfo *var)
|
|
{
|
|
return var->xres + var->right_margin - 1;
|
|
}
|
|
|
|
static inline int vsp(struct fb_var_screeninfo *var)
|
|
{
|
|
return var->yres + var->lower_margin - 1;
|
|
}
|
|
|
|
static inline int d_pitch(struct fb_var_screeninfo *var)
|
|
{
|
|
return var->xres * var->bits_per_pixel / 8;
|
|
}
|
|
|
|
static inline unsigned int chan_to_field(unsigned int chan,
|
|
struct fb_bitfield *bf)
|
|
{
|
|
chan &= 0xffff;
|
|
chan >>= 16 - bf->length;
|
|
return chan << bf->offset;
|
|
}
|
|
|
|
static int mb862xxfb_setcolreg(unsigned regno,
|
|
unsigned red, unsigned green, unsigned blue,
|
|
unsigned transp, struct fb_info *info)
|
|
{
|
|
struct mb862xxfb_par *par = info->par;
|
|
unsigned int val;
|
|
|
|
switch (info->fix.visual) {
|
|
case FB_VISUAL_TRUECOLOR:
|
|
if (regno < 16) {
|
|
val = chan_to_field(red, &info->var.red);
|
|
val |= chan_to_field(green, &info->var.green);
|
|
val |= chan_to_field(blue, &info->var.blue);
|
|
par->pseudo_palette[regno] = val;
|
|
}
|
|
break;
|
|
case FB_VISUAL_PSEUDOCOLOR:
|
|
if (regno < 256) {
|
|
val = (red >> 8) << 16;
|
|
val |= (green >> 8) << 8;
|
|
val |= blue >> 8;
|
|
outreg(disp, GC_L0PAL0 + (regno * 4), val);
|
|
}
|
|
break;
|
|
default:
|
|
return 1; /* unsupported type */
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int mb862xxfb_check_var(struct fb_var_screeninfo *var,
|
|
struct fb_info *fbi)
|
|
{
|
|
unsigned long tmp;
|
|
|
|
if (fbi->dev)
|
|
dev_dbg(fbi->dev, "%s\n", __func__);
|
|
|
|
/* check if these values fit into the registers */
|
|
if (var->hsync_len > 255 || var->vsync_len > 255)
|
|
return -EINVAL;
|
|
|
|
if ((var->xres + var->right_margin) >= 4096)
|
|
return -EINVAL;
|
|
|
|
if ((var->yres + var->lower_margin) > 4096)
|
|
return -EINVAL;
|
|
|
|
if (h_total(var) > 4096 || v_total(var) > 4096)
|
|
return -EINVAL;
|
|
|
|
if (var->xres_virtual > 4096 || var->yres_virtual > 4096)
|
|
return -EINVAL;
|
|
|
|
if (var->bits_per_pixel <= 8)
|
|
var->bits_per_pixel = 8;
|
|
else if (var->bits_per_pixel <= 16)
|
|
var->bits_per_pixel = 16;
|
|
else if (var->bits_per_pixel <= 32)
|
|
var->bits_per_pixel = 32;
|
|
|
|
/*
|
|
* can cope with 8,16 or 24/32bpp if resulting
|
|
* pitch is divisible by 64 without remainder
|
|
*/
|
|
if (d_pitch(&fbi->var) % GC_L0M_L0W_UNIT) {
|
|
int r;
|
|
|
|
var->bits_per_pixel = 0;
|
|
do {
|
|
var->bits_per_pixel += 8;
|
|
r = d_pitch(&fbi->var) % GC_L0M_L0W_UNIT;
|
|
} while (r && var->bits_per_pixel <= 32);
|
|
|
|
if (d_pitch(&fbi->var) % GC_L0M_L0W_UNIT)
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* line length is going to be 128 bit aligned */
|
|
tmp = (var->xres * var->bits_per_pixel) / 8;
|
|
if ((tmp & 15) != 0)
|
|
return -EINVAL;
|
|
|
|
/* set r/g/b positions and validate bpp */
|
|
switch (var->bits_per_pixel) {
|
|
case 8:
|
|
var->red.length = var->bits_per_pixel;
|
|
var->green.length = var->bits_per_pixel;
|
|
var->blue.length = var->bits_per_pixel;
|
|
var->red.offset = 0;
|
|
var->green.offset = 0;
|
|
var->blue.offset = 0;
|
|
var->transp.length = 0;
|
|
break;
|
|
case 16:
|
|
var->red.length = 5;
|
|
var->green.length = 5;
|
|
var->blue.length = 5;
|
|
var->red.offset = 10;
|
|
var->green.offset = 5;
|
|
var->blue.offset = 0;
|
|
var->transp.length = 0;
|
|
break;
|
|
case 24:
|
|
case 32:
|
|
var->transp.length = 8;
|
|
var->red.length = 8;
|
|
var->green.length = 8;
|
|
var->blue.length = 8;
|
|
var->transp.offset = 24;
|
|
var->red.offset = 16;
|
|
var->green.offset = 8;
|
|
var->blue.offset = 0;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* set display parameters
|
|
*/
|
|
static int mb862xxfb_set_par(struct fb_info *fbi)
|
|
{
|
|
struct mb862xxfb_par *par = fbi->par;
|
|
unsigned long reg, sc;
|
|
|
|
dev_dbg(par->dev, "%s\n", __func__);
|
|
if (par->type == BT_CORALP)
|
|
mb862xxfb_init_accel(fbi, fbi->var.xres);
|
|
|
|
if (par->pre_init)
|
|
return 0;
|
|
|
|
/* disp off */
|
|
reg = inreg(disp, GC_DCM1);
|
|
reg &= ~GC_DCM01_DEN;
|
|
outreg(disp, GC_DCM1, reg);
|
|
|
|
/* set display reference clock div. */
|
|
sc = par->refclk / (1000000 / fbi->var.pixclock) - 1;
|
|
reg = inreg(disp, GC_DCM1);
|
|
reg &= ~(GC_DCM01_CKS | GC_DCM01_RESV | GC_DCM01_SC);
|
|
reg |= sc << 8;
|
|
outreg(disp, GC_DCM1, reg);
|
|
dev_dbg(par->dev, "SC 0x%lx\n", sc);
|
|
|
|
/* disp dimension, format */
|
|
reg = pack(d_pitch(&fbi->var) / GC_L0M_L0W_UNIT,
|
|
(fbi->var.yres - 1));
|
|
if (fbi->var.bits_per_pixel == 16)
|
|
reg |= GC_L0M_L0C_16;
|
|
outreg(disp, GC_L0M, reg);
|
|
|
|
if (fbi->var.bits_per_pixel == 32) {
|
|
reg = inreg(disp, GC_L0EM);
|
|
outreg(disp, GC_L0EM, reg | GC_L0EM_L0EC_24);
|
|
}
|
|
outreg(disp, GC_WY_WX, 0);
|
|
reg = pack(fbi->var.yres - 1, fbi->var.xres);
|
|
outreg(disp, GC_WH_WW, reg);
|
|
outreg(disp, GC_L0OA0, 0);
|
|
outreg(disp, GC_L0DA0, 0);
|
|
outreg(disp, GC_L0DY_L0DX, 0);
|
|
outreg(disp, GC_L0WY_L0WX, 0);
|
|
outreg(disp, GC_L0WH_L0WW, reg);
|
|
|
|
/* both HW-cursors off */
|
|
reg = inreg(disp, GC_CPM_CUTC);
|
|
reg &= ~(GC_CPM_CEN0 | GC_CPM_CEN1);
|
|
outreg(disp, GC_CPM_CUTC, reg);
|
|
|
|
/* timings */
|
|
reg = pack(fbi->var.xres - 1, fbi->var.xres - 1);
|
|
outreg(disp, GC_HDB_HDP, reg);
|
|
reg = pack((fbi->var.yres - 1), vsp(&fbi->var));
|
|
outreg(disp, GC_VDP_VSP, reg);
|
|
reg = ((fbi->var.vsync_len - 1) << 24) |
|
|
pack((fbi->var.hsync_len - 1), hsp(&fbi->var));
|
|
outreg(disp, GC_VSW_HSW_HSP, reg);
|
|
outreg(disp, GC_HTP, pack(h_total(&fbi->var) - 1, 0));
|
|
outreg(disp, GC_VTR, pack(v_total(&fbi->var) - 1, 0));
|
|
|
|
/* display on */
|
|
reg = inreg(disp, GC_DCM1);
|
|
reg |= GC_DCM01_DEN | GC_DCM01_L0E;
|
|
reg &= ~GC_DCM01_ESY;
|
|
outreg(disp, GC_DCM1, reg);
|
|
return 0;
|
|
}
|
|
|
|
static int mb862xxfb_pan(struct fb_var_screeninfo *var,
|
|
struct fb_info *info)
|
|
{
|
|
struct mb862xxfb_par *par = info->par;
|
|
unsigned long reg;
|
|
|
|
reg = pack(var->yoffset, var->xoffset);
|
|
outreg(disp, GC_L0WY_L0WX, reg);
|
|
|
|
reg = pack(var->yres_virtual, var->xres_virtual);
|
|
outreg(disp, GC_L0WH_L0WW, reg);
|
|
return 0;
|
|
}
|
|
|
|
static int mb862xxfb_blank(int mode, struct fb_info *fbi)
|
|
{
|
|
struct mb862xxfb_par *par = fbi->par;
|
|
unsigned long reg;
|
|
|
|
dev_dbg(fbi->dev, "blank mode=%d\n", mode);
|
|
|
|
switch (mode) {
|
|
case FB_BLANK_POWERDOWN:
|
|
reg = inreg(disp, GC_DCM1);
|
|
reg &= ~GC_DCM01_DEN;
|
|
outreg(disp, GC_DCM1, reg);
|
|
break;
|
|
case FB_BLANK_UNBLANK:
|
|
reg = inreg(disp, GC_DCM1);
|
|
reg |= GC_DCM01_DEN;
|
|
outreg(disp, GC_DCM1, reg);
|
|
break;
|
|
case FB_BLANK_NORMAL:
|
|
case FB_BLANK_VSYNC_SUSPEND:
|
|
case FB_BLANK_HSYNC_SUSPEND:
|
|
default:
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* framebuffer ops */
|
|
static struct fb_ops mb862xxfb_ops = {
|
|
.owner = THIS_MODULE,
|
|
.fb_check_var = mb862xxfb_check_var,
|
|
.fb_set_par = mb862xxfb_set_par,
|
|
.fb_setcolreg = mb862xxfb_setcolreg,
|
|
.fb_blank = mb862xxfb_blank,
|
|
.fb_pan_display = mb862xxfb_pan,
|
|
.fb_fillrect = cfb_fillrect,
|
|
.fb_copyarea = cfb_copyarea,
|
|
.fb_imageblit = cfb_imageblit,
|
|
};
|
|
|
|
/* initialize fb_info data */
|
|
static int mb862xxfb_init_fbinfo(struct fb_info *fbi)
|
|
{
|
|
struct mb862xxfb_par *par = fbi->par;
|
|
struct mb862xx_gc_mode *mode = par->gc_mode;
|
|
unsigned long reg;
|
|
|
|
fbi->fbops = &mb862xxfb_ops;
|
|
fbi->pseudo_palette = par->pseudo_palette;
|
|
fbi->screen_base = par->fb_base;
|
|
fbi->screen_size = par->mapped_vram;
|
|
|
|
strcpy(fbi->fix.id, DRV_NAME);
|
|
fbi->fix.smem_start = (unsigned long)par->fb_base_phys;
|
|
fbi->fix.smem_len = par->mapped_vram;
|
|
fbi->fix.mmio_start = (unsigned long)par->mmio_base_phys;
|
|
fbi->fix.mmio_len = par->mmio_len;
|
|
fbi->fix.accel = FB_ACCEL_NONE;
|
|
fbi->fix.type = FB_TYPE_PACKED_PIXELS;
|
|
fbi->fix.type_aux = 0;
|
|
fbi->fix.xpanstep = 1;
|
|
fbi->fix.ypanstep = 1;
|
|
fbi->fix.ywrapstep = 0;
|
|
|
|
reg = inreg(disp, GC_DCM1);
|
|
if (reg & GC_DCM01_DEN && reg & GC_DCM01_L0E) {
|
|
/* get the disp mode from active display cfg */
|
|
unsigned long sc = ((reg & GC_DCM01_SC) >> 8) + 1;
|
|
unsigned long hsp, vsp, ht, vt;
|
|
|
|
dev_dbg(par->dev, "using bootloader's disp. mode\n");
|
|
fbi->var.pixclock = (sc * 1000000) / par->refclk;
|
|
fbi->var.xres = (inreg(disp, GC_HDB_HDP) & 0x0fff) + 1;
|
|
reg = inreg(disp, GC_VDP_VSP);
|
|
fbi->var.yres = ((reg >> 16) & 0x0fff) + 1;
|
|
vsp = (reg & 0x0fff) + 1;
|
|
fbi->var.xres_virtual = fbi->var.xres;
|
|
fbi->var.yres_virtual = fbi->var.yres;
|
|
reg = inreg(disp, GC_L0EM);
|
|
if (reg & GC_L0EM_L0EC_24) {
|
|
fbi->var.bits_per_pixel = 32;
|
|
} else {
|
|
reg = inreg(disp, GC_L0M);
|
|
if (reg & GC_L0M_L0C_16)
|
|
fbi->var.bits_per_pixel = 16;
|
|
else
|
|
fbi->var.bits_per_pixel = 8;
|
|
}
|
|
reg = inreg(disp, GC_VSW_HSW_HSP);
|
|
fbi->var.hsync_len = ((reg & 0xff0000) >> 16) + 1;
|
|
fbi->var.vsync_len = ((reg & 0x3f000000) >> 24) + 1;
|
|
hsp = (reg & 0xffff) + 1;
|
|
ht = ((inreg(disp, GC_HTP) & 0xfff0000) >> 16) + 1;
|
|
fbi->var.right_margin = hsp - fbi->var.xres;
|
|
fbi->var.left_margin = ht - hsp - fbi->var.hsync_len;
|
|
vt = ((inreg(disp, GC_VTR) & 0xfff0000) >> 16) + 1;
|
|
fbi->var.lower_margin = vsp - fbi->var.yres;
|
|
fbi->var.upper_margin = vt - vsp - fbi->var.vsync_len;
|
|
} else if (mode) {
|
|
dev_dbg(par->dev, "using supplied mode\n");
|
|
fb_videomode_to_var(&fbi->var, (struct fb_videomode *)mode);
|
|
fbi->var.bits_per_pixel = mode->def_bpp ? mode->def_bpp : 8;
|
|
} else {
|
|
int ret;
|
|
|
|
ret = fb_find_mode(&fbi->var, fbi, "640x480-16@60",
|
|
NULL, 0, NULL, 16);
|
|
if (ret == 0 || ret == 4) {
|
|
dev_err(par->dev,
|
|
"failed to get initial mode\n");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
fbi->var.xoffset = 0;
|
|
fbi->var.yoffset = 0;
|
|
fbi->var.grayscale = 0;
|
|
fbi->var.nonstd = 0;
|
|
fbi->var.height = -1;
|
|
fbi->var.width = -1;
|
|
fbi->var.accel_flags = 0;
|
|
fbi->var.vmode = FB_VMODE_NONINTERLACED;
|
|
fbi->var.activate = FB_ACTIVATE_NOW;
|
|
fbi->flags = FBINFO_DEFAULT |
|
|
#ifdef __BIG_ENDIAN
|
|
FBINFO_FOREIGN_ENDIAN |
|
|
#endif
|
|
FBINFO_HWACCEL_XPAN |
|
|
FBINFO_HWACCEL_YPAN;
|
|
|
|
/* check and possibly fix bpp */
|
|
if ((fbi->fbops->fb_check_var)(&fbi->var, fbi))
|
|
dev_err(par->dev, "check_var() failed on initial setup?\n");
|
|
|
|
fbi->fix.visual = fbi->var.bits_per_pixel == 8 ?
|
|
FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR;
|
|
fbi->fix.line_length = (fbi->var.xres_virtual *
|
|
fbi->var.bits_per_pixel) / 8;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* show some display controller and cursor registers
|
|
*/
|
|
static ssize_t mb862xxfb_show_dispregs(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct fb_info *fbi = dev_get_drvdata(dev);
|
|
struct mb862xxfb_par *par = fbi->par;
|
|
char *ptr = buf;
|
|
unsigned int reg;
|
|
|
|
for (reg = GC_DCM0; reg <= GC_L0DY_L0DX; reg += 4)
|
|
ptr += sprintf(ptr, "%08x = %08x\n",
|
|
reg, inreg(disp, reg));
|
|
|
|
for (reg = GC_CPM_CUTC; reg <= GC_CUY1_CUX1; reg += 4)
|
|
ptr += sprintf(ptr, "%08x = %08x\n",
|
|
reg, inreg(disp, reg));
|
|
|
|
for (reg = GC_DCM1; reg <= GC_L0WH_L0WW; reg += 4)
|
|
ptr += sprintf(ptr, "%08x = %08x\n",
|
|
reg, inreg(disp, reg));
|
|
|
|
for (reg = 0x400; reg <= 0x410; reg += 4)
|
|
ptr += sprintf(ptr, "geo %08x = %08x\n",
|
|
reg, inreg(geo, reg));
|
|
|
|
for (reg = 0x400; reg <= 0x410; reg += 4)
|
|
ptr += sprintf(ptr, "draw %08x = %08x\n",
|
|
reg, inreg(draw, reg));
|
|
|
|
for (reg = 0x440; reg <= 0x450; reg += 4)
|
|
ptr += sprintf(ptr, "draw %08x = %08x\n",
|
|
reg, inreg(draw, reg));
|
|
|
|
return ptr - buf;
|
|
}
|
|
|
|
static DEVICE_ATTR(dispregs, 0444, mb862xxfb_show_dispregs, NULL);
|
|
|
|
irqreturn_t mb862xx_intr(int irq, void *dev_id)
|
|
{
|
|
struct mb862xxfb_par *par = (struct mb862xxfb_par *) dev_id;
|
|
unsigned long reg_ist, mask;
|
|
|
|
if (!par)
|
|
return IRQ_NONE;
|
|
|
|
if (par->type == BT_CARMINE) {
|
|
/* Get Interrupt Status */
|
|
reg_ist = inreg(ctrl, GC_CTRL_STATUS);
|
|
mask = inreg(ctrl, GC_CTRL_INT_MASK);
|
|
if (reg_ist == 0)
|
|
return IRQ_HANDLED;
|
|
|
|
reg_ist &= mask;
|
|
if (reg_ist == 0)
|
|
return IRQ_HANDLED;
|
|
|
|
/* Clear interrupt status */
|
|
outreg(ctrl, 0x0, reg_ist);
|
|
} else {
|
|
/* Get status */
|
|
reg_ist = inreg(host, GC_IST);
|
|
mask = inreg(host, GC_IMASK);
|
|
|
|
reg_ist &= mask;
|
|
if (reg_ist == 0)
|
|
return IRQ_HANDLED;
|
|
|
|
/* Clear status */
|
|
outreg(host, GC_IST, ~reg_ist);
|
|
}
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
#if defined(CONFIG_FB_MB862XX_LIME)
|
|
/*
|
|
* GDC (Lime, Coral(B/Q), Mint, ...) on host bus
|
|
*/
|
|
static int mb862xx_gdc_init(struct mb862xxfb_par *par)
|
|
{
|
|
unsigned long ccf, mmr;
|
|
unsigned long ver, rev;
|
|
|
|
if (!par)
|
|
return -ENODEV;
|
|
|
|
#if defined(CONFIG_FB_PRE_INIT_FB)
|
|
par->pre_init = 1;
|
|
#endif
|
|
par->host = par->mmio_base;
|
|
par->i2c = par->mmio_base + MB862XX_I2C_BASE;
|
|
par->disp = par->mmio_base + MB862XX_DISP_BASE;
|
|
par->cap = par->mmio_base + MB862XX_CAP_BASE;
|
|
par->draw = par->mmio_base + MB862XX_DRAW_BASE;
|
|
par->geo = par->mmio_base + MB862XX_GEO_BASE;
|
|
par->pio = par->mmio_base + MB862XX_PIO_BASE;
|
|
|
|
par->refclk = GC_DISP_REFCLK_400;
|
|
|
|
ver = inreg(host, GC_CID);
|
|
rev = inreg(pio, GC_REVISION);
|
|
if ((ver == 0x303) && (rev & 0xffffff00) == 0x20050100) {
|
|
dev_info(par->dev, "Fujitsu Lime v1.%d found\n",
|
|
(int)rev & 0xff);
|
|
par->type = BT_LIME;
|
|
ccf = par->gc_mode ? par->gc_mode->ccf : GC_CCF_COT_100;
|
|
mmr = par->gc_mode ? par->gc_mode->mmr : 0x414fb7f2;
|
|
} else {
|
|
dev_info(par->dev, "? GDC, CID/Rev.: 0x%lx/0x%lx \n", ver, rev);
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (!par->pre_init) {
|
|
outreg(host, GC_CCF, ccf);
|
|
udelay(200);
|
|
outreg(host, GC_MMR, mmr);
|
|
udelay(10);
|
|
}
|
|
|
|
/* interrupt status */
|
|
outreg(host, GC_IST, 0);
|
|
outreg(host, GC_IMASK, GC_INT_EN);
|
|
return 0;
|
|
}
|
|
|
|
static int __devinit of_platform_mb862xx_probe(struct of_device *ofdev,
|
|
const struct of_device_id *id)
|
|
{
|
|
struct device_node *np = ofdev->node;
|
|
struct device *dev = &ofdev->dev;
|
|
struct mb862xxfb_par *par;
|
|
struct fb_info *info;
|
|
struct resource res;
|
|
resource_size_t res_size;
|
|
unsigned long ret = -ENODEV;
|
|
|
|
if (of_address_to_resource(np, 0, &res)) {
|
|
dev_err(dev, "Invalid address\n");
|
|
return -ENXIO;
|
|
}
|
|
|
|
info = framebuffer_alloc(sizeof(struct mb862xxfb_par), dev);
|
|
if (info == NULL) {
|
|
dev_err(dev, "cannot allocate framebuffer\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
par = info->par;
|
|
par->info = info;
|
|
par->dev = dev;
|
|
|
|
par->irq = irq_of_parse_and_map(np, 0);
|
|
if (par->irq == NO_IRQ) {
|
|
dev_err(dev, "failed to map irq\n");
|
|
ret = -ENODEV;
|
|
goto fbrel;
|
|
}
|
|
|
|
res_size = 1 + res.end - res.start;
|
|
par->res = request_mem_region(res.start, res_size, DRV_NAME);
|
|
if (par->res == NULL) {
|
|
dev_err(dev, "Cannot claim framebuffer/mmio\n");
|
|
ret = -ENXIO;
|
|
goto irqdisp;
|
|
}
|
|
|
|
#if defined(CONFIG_SOCRATES)
|
|
par->gc_mode = &socrates_gc_mode;
|
|
#endif
|
|
|
|
par->fb_base_phys = res.start;
|
|
par->mmio_base_phys = res.start + MB862XX_MMIO_BASE;
|
|
par->mmio_len = MB862XX_MMIO_SIZE;
|
|
if (par->gc_mode)
|
|
par->mapped_vram = par->gc_mode->max_vram;
|
|
else
|
|
par->mapped_vram = MB862XX_MEM_SIZE;
|
|
|
|
par->fb_base = ioremap(par->fb_base_phys, par->mapped_vram);
|
|
if (par->fb_base == NULL) {
|
|
dev_err(dev, "Cannot map framebuffer\n");
|
|
goto rel_reg;
|
|
}
|
|
|
|
par->mmio_base = ioremap(par->mmio_base_phys, par->mmio_len);
|
|
if (par->mmio_base == NULL) {
|
|
dev_err(dev, "Cannot map registers\n");
|
|
goto fb_unmap;
|
|
}
|
|
|
|
dev_dbg(dev, "fb phys 0x%llx 0x%lx\n",
|
|
(u64)par->fb_base_phys, (ulong)par->mapped_vram);
|
|
dev_dbg(dev, "mmio phys 0x%llx 0x%lx, (irq = %d)\n",
|
|
(u64)par->mmio_base_phys, (ulong)par->mmio_len, par->irq);
|
|
|
|
if (mb862xx_gdc_init(par))
|
|
goto io_unmap;
|
|
|
|
if (request_irq(par->irq, mb862xx_intr, IRQF_DISABLED,
|
|
DRV_NAME, (void *)par)) {
|
|
dev_err(dev, "Cannot request irq\n");
|
|
goto io_unmap;
|
|
}
|
|
|
|
mb862xxfb_init_fbinfo(info);
|
|
|
|
if (fb_alloc_cmap(&info->cmap, NR_PALETTE, 0) < 0) {
|
|
dev_err(dev, "Could not allocate cmap for fb_info.\n");
|
|
goto free_irq;
|
|
}
|
|
|
|
if ((info->fbops->fb_set_par)(info))
|
|
dev_err(dev, "set_var() failed on initial setup?\n");
|
|
|
|
if (register_framebuffer(info)) {
|
|
dev_err(dev, "failed to register framebuffer\n");
|
|
goto rel_cmap;
|
|
}
|
|
|
|
dev_set_drvdata(dev, info);
|
|
|
|
if (device_create_file(dev, &dev_attr_dispregs))
|
|
dev_err(dev, "Can't create sysfs regdump file\n");
|
|
return 0;
|
|
|
|
rel_cmap:
|
|
fb_dealloc_cmap(&info->cmap);
|
|
free_irq:
|
|
outreg(host, GC_IMASK, 0);
|
|
free_irq(par->irq, (void *)par);
|
|
io_unmap:
|
|
iounmap(par->mmio_base);
|
|
fb_unmap:
|
|
iounmap(par->fb_base);
|
|
rel_reg:
|
|
release_mem_region(res.start, res_size);
|
|
irqdisp:
|
|
irq_dispose_mapping(par->irq);
|
|
fbrel:
|
|
dev_set_drvdata(dev, NULL);
|
|
framebuffer_release(info);
|
|
return ret;
|
|
}
|
|
|
|
static int __devexit of_platform_mb862xx_remove(struct of_device *ofdev)
|
|
{
|
|
struct fb_info *fbi = dev_get_drvdata(&ofdev->dev);
|
|
struct mb862xxfb_par *par = fbi->par;
|
|
resource_size_t res_size = 1 + par->res->end - par->res->start;
|
|
unsigned long reg;
|
|
|
|
dev_dbg(fbi->dev, "%s release\n", fbi->fix.id);
|
|
|
|
/* display off */
|
|
reg = inreg(disp, GC_DCM1);
|
|
reg &= ~(GC_DCM01_DEN | GC_DCM01_L0E);
|
|
outreg(disp, GC_DCM1, reg);
|
|
|
|
/* disable interrupts */
|
|
outreg(host, GC_IMASK, 0);
|
|
|
|
free_irq(par->irq, (void *)par);
|
|
irq_dispose_mapping(par->irq);
|
|
|
|
device_remove_file(&ofdev->dev, &dev_attr_dispregs);
|
|
|
|
unregister_framebuffer(fbi);
|
|
fb_dealloc_cmap(&fbi->cmap);
|
|
|
|
iounmap(par->mmio_base);
|
|
iounmap(par->fb_base);
|
|
|
|
dev_set_drvdata(&ofdev->dev, NULL);
|
|
release_mem_region(par->res->start, res_size);
|
|
framebuffer_release(fbi);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* common types
|
|
*/
|
|
static struct of_device_id __devinitdata of_platform_mb862xx_tbl[] = {
|
|
{ .compatible = "fujitsu,MB86276", },
|
|
{ .compatible = "fujitsu,lime", },
|
|
{ .compatible = "fujitsu,MB86277", },
|
|
{ .compatible = "fujitsu,mint", },
|
|
{ .compatible = "fujitsu,MB86293", },
|
|
{ .compatible = "fujitsu,MB86294", },
|
|
{ .compatible = "fujitsu,coral", },
|
|
{ /* end */ }
|
|
};
|
|
|
|
static struct of_platform_driver of_platform_mb862xxfb_driver = {
|
|
.owner = THIS_MODULE,
|
|
.name = DRV_NAME,
|
|
.match_table = of_platform_mb862xx_tbl,
|
|
.probe = of_platform_mb862xx_probe,
|
|
.remove = __devexit_p(of_platform_mb862xx_remove),
|
|
};
|
|
#endif
|
|
|
|
#if defined(CONFIG_FB_MB862XX_PCI_GDC)
|
|
static int coralp_init(struct mb862xxfb_par *par)
|
|
{
|
|
int cn, ver;
|
|
|
|
par->host = par->mmio_base;
|
|
par->i2c = par->mmio_base + MB862XX_I2C_BASE;
|
|
par->disp = par->mmio_base + MB862XX_DISP_BASE;
|
|
par->cap = par->mmio_base + MB862XX_CAP_BASE;
|
|
par->draw = par->mmio_base + MB862XX_DRAW_BASE;
|
|
par->geo = par->mmio_base + MB862XX_GEO_BASE;
|
|
par->pio = par->mmio_base + MB862XX_PIO_BASE;
|
|
|
|
par->refclk = GC_DISP_REFCLK_400;
|
|
|
|
ver = inreg(host, GC_CID);
|
|
cn = (ver & GC_CID_CNAME_MSK) >> 8;
|
|
ver = ver & GC_CID_VERSION_MSK;
|
|
if (cn == 3) {
|
|
dev_info(par->dev, "Fujitsu Coral-%s GDC Rev.%d found\n",\
|
|
(ver == 6) ? "P" : (ver == 8) ? "PA" : "?",
|
|
par->pdev->revision);
|
|
outreg(host, GC_CCF, GC_CCF_CGE_166 | GC_CCF_COT_133);
|
|
udelay(200);
|
|
outreg(host, GC_MMR, GC_MMR_CORALP_EVB_VAL);
|
|
udelay(10);
|
|
/* Clear interrupt status */
|
|
outreg(host, GC_IST, 0);
|
|
} else {
|
|
return -ENODEV;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int init_dram_ctrl(struct mb862xxfb_par *par)
|
|
{
|
|
unsigned long i = 0;
|
|
|
|
/*
|
|
* Set io mode first! Spec. says IC may be destroyed
|
|
* if not set to SSTL2/LVCMOS before init.
|
|
*/
|
|
outreg(dram_ctrl, GC_DCTL_IOCONT1_IOCONT0, GC_EVB_DCTL_IOCONT1_IOCONT0);
|
|
|
|
/* DRAM init */
|
|
outreg(dram_ctrl, GC_DCTL_MODE_ADD, GC_EVB_DCTL_MODE_ADD);
|
|
outreg(dram_ctrl, GC_DCTL_SETTIME1_EMODE, GC_EVB_DCTL_SETTIME1_EMODE);
|
|
outreg(dram_ctrl, GC_DCTL_REFRESH_SETTIME2,
|
|
GC_EVB_DCTL_REFRESH_SETTIME2);
|
|
outreg(dram_ctrl, GC_DCTL_RSV2_RSV1, GC_EVB_DCTL_RSV2_RSV1);
|
|
outreg(dram_ctrl, GC_DCTL_DDRIF2_DDRIF1, GC_EVB_DCTL_DDRIF2_DDRIF1);
|
|
outreg(dram_ctrl, GC_DCTL_RSV0_STATES, GC_EVB_DCTL_RSV0_STATES);
|
|
|
|
/* DLL reset done? */
|
|
while ((inreg(dram_ctrl, GC_DCTL_RSV0_STATES) & GC_DCTL_STATES_MSK)) {
|
|
udelay(GC_DCTL_INIT_WAIT_INTERVAL);
|
|
if (i++ > GC_DCTL_INIT_WAIT_CNT) {
|
|
dev_err(par->dev, "VRAM init failed.\n");
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
outreg(dram_ctrl, GC_DCTL_MODE_ADD, GC_EVB_DCTL_MODE_ADD_AFT_RST);
|
|
outreg(dram_ctrl, GC_DCTL_RSV0_STATES, GC_EVB_DCTL_RSV0_STATES_AFT_RST);
|
|
return 0;
|
|
}
|
|
|
|
static int carmine_init(struct mb862xxfb_par *par)
|
|
{
|
|
unsigned long reg;
|
|
|
|
par->ctrl = par->mmio_base + MB86297_CTRL_BASE;
|
|
par->i2c = par->mmio_base + MB86297_I2C_BASE;
|
|
par->disp = par->mmio_base + MB86297_DISP0_BASE;
|
|
par->disp1 = par->mmio_base + MB86297_DISP1_BASE;
|
|
par->cap = par->mmio_base + MB86297_CAP0_BASE;
|
|
par->cap1 = par->mmio_base + MB86297_CAP1_BASE;
|
|
par->draw = par->mmio_base + MB86297_DRAW_BASE;
|
|
par->dram_ctrl = par->mmio_base + MB86297_DRAMCTRL_BASE;
|
|
par->wrback = par->mmio_base + MB86297_WRBACK_BASE;
|
|
|
|
par->refclk = GC_DISP_REFCLK_533;
|
|
|
|
/* warm up */
|
|
reg = GC_CTRL_CLK_EN_DRAM | GC_CTRL_CLK_EN_2D3D | GC_CTRL_CLK_EN_DISP0;
|
|
outreg(ctrl, GC_CTRL_CLK_ENABLE, reg);
|
|
|
|
/* check for engine module revision */
|
|
if (inreg(draw, GC_2D3D_REV) == GC_RE_REVISION)
|
|
dev_info(par->dev, "Fujitsu Carmine GDC Rev.%d found\n",
|
|
par->pdev->revision);
|
|
else
|
|
goto err_init;
|
|
|
|
reg &= ~GC_CTRL_CLK_EN_2D3D;
|
|
outreg(ctrl, GC_CTRL_CLK_ENABLE, reg);
|
|
|
|
/* set up vram */
|
|
if (init_dram_ctrl(par) < 0)
|
|
goto err_init;
|
|
|
|
outreg(ctrl, GC_CTRL_INT_MASK, 0);
|
|
return 0;
|
|
|
|
err_init:
|
|
outreg(ctrl, GC_CTRL_CLK_ENABLE, 0);
|
|
return -EINVAL;
|
|
}
|
|
|
|
static inline int mb862xx_pci_gdc_init(struct mb862xxfb_par *par)
|
|
{
|
|
switch (par->type) {
|
|
case BT_CORALP:
|
|
return coralp_init(par);
|
|
case BT_CARMINE:
|
|
return carmine_init(par);
|
|
default:
|
|
return -ENODEV;
|
|
}
|
|
}
|
|
|
|
#define CHIP_ID(id) \
|
|
{ PCI_DEVICE(PCI_VENDOR_ID_FUJITSU_LIMITED, id) }
|
|
|
|
static struct pci_device_id mb862xx_pci_tbl[] __devinitdata = {
|
|
/* MB86295/MB86296 */
|
|
CHIP_ID(PCI_DEVICE_ID_FUJITSU_CORALP),
|
|
CHIP_ID(PCI_DEVICE_ID_FUJITSU_CORALPA),
|
|
/* MB86297 */
|
|
CHIP_ID(PCI_DEVICE_ID_FUJITSU_CARMINE),
|
|
{ 0, }
|
|
};
|
|
|
|
MODULE_DEVICE_TABLE(pci, mb862xx_pci_tbl);
|
|
|
|
static int __devinit mb862xx_pci_probe(struct pci_dev *pdev,
|
|
const struct pci_device_id *ent)
|
|
{
|
|
struct mb862xxfb_par *par;
|
|
struct fb_info *info;
|
|
struct device *dev = &pdev->dev;
|
|
int ret;
|
|
|
|
ret = pci_enable_device(pdev);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Cannot enable PCI device\n");
|
|
goto out;
|
|
}
|
|
|
|
info = framebuffer_alloc(sizeof(struct mb862xxfb_par), dev);
|
|
if (!info) {
|
|
dev_err(dev, "framebuffer alloc failed\n");
|
|
ret = -ENOMEM;
|
|
goto dis_dev;
|
|
}
|
|
|
|
par = info->par;
|
|
par->info = info;
|
|
par->dev = dev;
|
|
par->pdev = pdev;
|
|
par->irq = pdev->irq;
|
|
|
|
ret = pci_request_regions(pdev, DRV_NAME);
|
|
if (ret < 0) {
|
|
dev_err(dev, "Cannot reserve region(s) for PCI device\n");
|
|
goto rel_fb;
|
|
}
|
|
|
|
switch (pdev->device) {
|
|
case PCI_DEVICE_ID_FUJITSU_CORALP:
|
|
case PCI_DEVICE_ID_FUJITSU_CORALPA:
|
|
par->fb_base_phys = pci_resource_start(par->pdev, 0);
|
|
par->mapped_vram = CORALP_MEM_SIZE;
|
|
par->mmio_base_phys = par->fb_base_phys + MB862XX_MMIO_BASE;
|
|
par->mmio_len = MB862XX_MMIO_SIZE;
|
|
par->type = BT_CORALP;
|
|
break;
|
|
case PCI_DEVICE_ID_FUJITSU_CARMINE:
|
|
par->fb_base_phys = pci_resource_start(par->pdev, 2);
|
|
par->mmio_base_phys = pci_resource_start(par->pdev, 3);
|
|
par->mmio_len = pci_resource_len(par->pdev, 3);
|
|
par->mapped_vram = CARMINE_MEM_SIZE;
|
|
par->type = BT_CARMINE;
|
|
break;
|
|
default:
|
|
/* should never occur */
|
|
goto rel_reg;
|
|
}
|
|
|
|
par->fb_base = ioremap(par->fb_base_phys, par->mapped_vram);
|
|
if (par->fb_base == NULL) {
|
|
dev_err(dev, "Cannot map framebuffer\n");
|
|
goto rel_reg;
|
|
}
|
|
|
|
par->mmio_base = ioremap(par->mmio_base_phys, par->mmio_len);
|
|
if (par->mmio_base == NULL) {
|
|
dev_err(dev, "Cannot map registers\n");
|
|
ret = -EIO;
|
|
goto fb_unmap;
|
|
}
|
|
|
|
dev_dbg(dev, "fb phys 0x%llx 0x%lx\n",
|
|
(unsigned long long)par->fb_base_phys, (ulong)par->mapped_vram);
|
|
dev_dbg(dev, "mmio phys 0x%llx 0x%lx\n",
|
|
(unsigned long long)par->mmio_base_phys, (ulong)par->mmio_len);
|
|
|
|
if (mb862xx_pci_gdc_init(par))
|
|
goto io_unmap;
|
|
|
|
if (request_irq(par->irq, mb862xx_intr, IRQF_DISABLED | IRQF_SHARED,
|
|
DRV_NAME, (void *)par)) {
|
|
dev_err(dev, "Cannot request irq\n");
|
|
goto io_unmap;
|
|
}
|
|
|
|
mb862xxfb_init_fbinfo(info);
|
|
|
|
if (fb_alloc_cmap(&info->cmap, NR_PALETTE, 0) < 0) {
|
|
dev_err(dev, "Could not allocate cmap for fb_info.\n");
|
|
ret = -ENOMEM;
|
|
goto free_irq;
|
|
}
|
|
|
|
if ((info->fbops->fb_set_par)(info))
|
|
dev_err(dev, "set_var() failed on initial setup?\n");
|
|
|
|
ret = register_framebuffer(info);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to register framebuffer\n");
|
|
goto rel_cmap;
|
|
}
|
|
|
|
pci_set_drvdata(pdev, info);
|
|
|
|
if (device_create_file(dev, &dev_attr_dispregs))
|
|
dev_err(dev, "Can't create sysfs regdump file\n");
|
|
|
|
if (par->type == BT_CARMINE)
|
|
outreg(ctrl, GC_CTRL_INT_MASK, GC_CARMINE_INT_EN);
|
|
else
|
|
outreg(host, GC_IMASK, GC_INT_EN);
|
|
|
|
return 0;
|
|
|
|
rel_cmap:
|
|
fb_dealloc_cmap(&info->cmap);
|
|
free_irq:
|
|
free_irq(par->irq, (void *)par);
|
|
io_unmap:
|
|
iounmap(par->mmio_base);
|
|
fb_unmap:
|
|
iounmap(par->fb_base);
|
|
rel_reg:
|
|
pci_release_regions(pdev);
|
|
rel_fb:
|
|
framebuffer_release(info);
|
|
dis_dev:
|
|
pci_disable_device(pdev);
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
static void __devexit mb862xx_pci_remove(struct pci_dev *pdev)
|
|
{
|
|
struct fb_info *fbi = pci_get_drvdata(pdev);
|
|
struct mb862xxfb_par *par = fbi->par;
|
|
unsigned long reg;
|
|
|
|
dev_dbg(fbi->dev, "%s release\n", fbi->fix.id);
|
|
|
|
/* display off */
|
|
reg = inreg(disp, GC_DCM1);
|
|
reg &= ~(GC_DCM01_DEN | GC_DCM01_L0E);
|
|
outreg(disp, GC_DCM1, reg);
|
|
|
|
if (par->type == BT_CARMINE) {
|
|
outreg(ctrl, GC_CTRL_INT_MASK, 0);
|
|
outreg(ctrl, GC_CTRL_CLK_ENABLE, 0);
|
|
} else {
|
|
outreg(host, GC_IMASK, 0);
|
|
}
|
|
|
|
device_remove_file(&pdev->dev, &dev_attr_dispregs);
|
|
|
|
pci_set_drvdata(pdev, NULL);
|
|
unregister_framebuffer(fbi);
|
|
fb_dealloc_cmap(&fbi->cmap);
|
|
|
|
free_irq(par->irq, (void *)par);
|
|
iounmap(par->mmio_base);
|
|
iounmap(par->fb_base);
|
|
|
|
pci_release_regions(pdev);
|
|
framebuffer_release(fbi);
|
|
pci_disable_device(pdev);
|
|
}
|
|
|
|
static struct pci_driver mb862xxfb_pci_driver = {
|
|
.name = DRV_NAME,
|
|
.id_table = mb862xx_pci_tbl,
|
|
.probe = mb862xx_pci_probe,
|
|
.remove = __devexit_p(mb862xx_pci_remove),
|
|
};
|
|
#endif
|
|
|
|
static int __devinit mb862xxfb_init(void)
|
|
{
|
|
int ret = -ENODEV;
|
|
|
|
#if defined(CONFIG_FB_MB862XX_LIME)
|
|
ret = of_register_platform_driver(&of_platform_mb862xxfb_driver);
|
|
#endif
|
|
#if defined(CONFIG_FB_MB862XX_PCI_GDC)
|
|
ret = pci_register_driver(&mb862xxfb_pci_driver);
|
|
#endif
|
|
return ret;
|
|
}
|
|
|
|
static void __exit mb862xxfb_exit(void)
|
|
{
|
|
#if defined(CONFIG_FB_MB862XX_LIME)
|
|
of_unregister_platform_driver(&of_platform_mb862xxfb_driver);
|
|
#endif
|
|
#if defined(CONFIG_FB_MB862XX_PCI_GDC)
|
|
pci_unregister_driver(&mb862xxfb_pci_driver);
|
|
#endif
|
|
}
|
|
|
|
module_init(mb862xxfb_init);
|
|
module_exit(mb862xxfb_exit);
|
|
|
|
MODULE_DESCRIPTION("Fujitsu MB862xx Framebuffer driver");
|
|
MODULE_AUTHOR("Anatolij Gustschin <agust@denx.de>");
|
|
MODULE_LICENSE("GPL v2");
|