/*
* linux/drivers/video/pm3fb.c -- 3DLabs Permedia3 frame buffer device
*
* Copyright (C) 2001 Romain Dolbeau <romain@dolbeau.org>.
*
* Ported to 2.6 kernel on 1 May 2007 by Krzysztof Helt <krzysztof.h1@wp.pl>
* based on pm2fb.c
*
* Based on code written by:
* Sven Luther, <luther@dpt-info.u-strasbg.fr>
* Alan Hourihane, <alanh@fairlite.demon.co.uk>
* Russell King, <rmk@arm.linux.org.uk>
* Based on linux/drivers/video/skeletonfb.c:
* Copyright (C) 1997 Geert Uytterhoeven
* Based on linux/driver/video/pm2fb.c:
* Copyright (C) 1998-1999 Ilario Nardinocchi (nardinoc@CS.UniBO.IT)
* Copyright (C) 1999 Jakub Jelinek (jakub@redhat.com)
*
* This file is subject to the terms and conditions of the GNU General Public
* License. See the file COPYING in the main directory of this archive for
* more details.
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/pci.h>
#ifdef CONFIG_MTRR
#include <asm/mtrr.h>
#endif
#include <video/pm3fb.h>
#if !defined(CONFIG_PCI)
#error "Only generic PCI cards supported."
#endif
#undef PM3FB_MASTER_DEBUG
#ifdef PM3FB_MASTER_DEBUG
#define DPRINTK(a, b...) \
printk(KERN_DEBUG "pm3fb: %s: " a, __FUNCTION__ , ## b)
#else
#define DPRINTK(a, b...)
#endif
#define PM3_PIXMAP_SIZE (2048 * 4)
/*
* Driver data
*/
static int hwcursor = 1;
static char *mode_option __devinitdata;
static int noaccel __devinitdata;
/* mtrr option */
#ifdef CONFIG_MTRR
static int nomtrr __devinitdata;
#endif
/*
* This structure defines the hardware state of the graphics card. Normally
* you place this in a header file in linux/include/video. This file usually
* also includes register information. That allows other driver subsystems
* and userland applications the ability to use the same header file to
* avoid duplicate work and easy porting of software.
*/
struct pm3_par {
unsigned char __iomem *v_regs;/* virtual address of p_regs */
u32 video; /* video flags before blanking */
u32 base; /* screen base in 128 bits unit */
u32 palette[16];
int mtrr_handle;
};
/*
* Here we define the default structs fb_fix_screeninfo and fb_var_screeninfo
* if we don't use modedb. If we do use modedb see pm3fb_init how to use it
* to get a fb_var_screeninfo. Otherwise define a default var as well.
*/
static struct fb_fix_screeninfo pm3fb_fix __devinitdata = {
.id = "Permedia3",
.type = FB_TYPE_PACKED_PIXELS,
.visual = FB_VISUAL_PSEUDOCOLOR,
.xpanstep = 1,
.ypanstep = 1,
.ywrapstep = 0,
.accel = FB_ACCEL_3DLABS_PERMEDIA3,
};
/*
* Utility functions
*/
static inline u32 PM3_READ_REG(struct pm3_par *par, s32 off)
{
return fb_readl(par->v_regs + off);
}
static inline void PM3_WRITE_REG(struct pm3_par *par, s32 off, u32 v)
{
fb_writel(v, par->v_regs + off);
}
static inline void PM3_WAIT(struct pm3_par *par, u32 n)
{
while (PM3_READ_REG(par, PM3InFIFOSpace) < n)
cpu_relax();
}
static inline void PM3_WRITE_DAC_REG(struct pm3_par *par, unsigned r, u8 v)
{
PM3_WAIT(par, 3);
PM3_WRITE_REG(par, PM3RD_IndexHigh, (r >> 8) & 0xff);
PM3_WRITE_REG(par, PM3RD_IndexLow, r & 0xff);
wmb();
PM3_WRITE_REG(par, PM3RD_IndexedData, v);
wmb();
}
static inline void pm3fb_set_color(struct pm3_par *par, unsigned char regno,
unsigned char r, unsigned char g, unsigned char b)
{
PM3_WAIT(par, 4);
PM3_WRITE_REG(par, PM3RD_PaletteWriteAddress, regno);
wmb();
PM3_WRITE_REG(par, PM3RD_PaletteData, r);
wmb();
PM3_WRITE_REG(par, PM3RD_PaletteData, g);
wmb();
PM3_WRITE_REG(par, PM3RD_PaletteData, b);
wmb();
}
static void pm3fb_clear_colormap(struct pm3_par *par,
unsigned char r, unsigned char g, unsigned char b)
{
int i;
for (i = 0; i < 256 ; i++)
pm3fb_set_color(par, i, r, g, b);
}
/* Calculating various clock parameters */
static void pm3fb_calculate_clock(unsigned long reqclock,
unsigned char *prescale,
unsigned char *feedback,
unsigned char *postscale)
{
int f, pre, post;
unsigned long freq;
long freqerr = 1000;
long currerr;
for (f = 1; f < 256; f++) {
for (pre = 1; pre < 256; pre++) {
for (post = 0; post < 5; post++) {
freq = ((2*PM3_REF_CLOCK * f) >> post) / pre;
currerr = (reqclock > freq)
? reqclock - freq
: freq - reqclock;
if (currerr < freqerr) {
freqerr = currerr;
*feedback = f;
*prescale = pre;
*postscale = post;
}
}
}
}
}
static inline int pm3fb_depth(const struct fb_var_screeninfo *var)
{
if (var->bits_per_pixel == 16)
return var->red.length + var->green.length
+ var->blue.length;
return var->bits_per_pixel;
}
static inline int pm3fb_shift_bpp(unsigned bpp, int v)
{
switch (bpp) {
case 8:
return (v >> 4);
case 16:
return (v >> 3);
case 32:
return (v >> 2);
}
DPRINTK("Unsupported depth %u\n", bpp);
return 0;
}
/* acceleration */
static int pm3fb_sync(struct fb_info *info)
{
struct pm3_par *par = info->par;
PM3_WAIT(par, 2);
PM3_WRITE_REG(par, PM3FilterMode, PM3FilterModeSync);
PM3_WRITE_REG(par, PM3Sync, 0);
mb();
do {
while ((PM3_READ_REG(par, PM3OutFIFOWords)) == 0)
cpu_relax();
} while ((PM3_READ_REG(par, PM3OutputFifo)) != PM3Sync_Tag);
return 0;
}
static void pm3fb_init_engine(struct fb_info *info)
{
struct pm3_par *par = info->par;
const u32 width = (info->var.xres_virtual + 7) & ~7;
PM3_WAIT(par, 50);
PM3_WRITE_REG(par, PM3FilterMode, PM3FilterModeSync);
PM3_WRITE_REG(par, PM3StatisticMode, 0x0);
PM3_WRITE_REG(par, PM3DeltaMode, 0x0);
PM3_WRITE_REG(par, PM3RasterizerMode, 0x0);
PM3_WRITE_REG(par, PM3ScissorMode, 0x0);
PM3_WRITE_REG(par, PM3LineStippleMode, 0x0);
PM3_WRITE_REG(par, PM3AreaStippleMode, 0x0);
PM3_WRITE_REG(par, PM3GIDMode, 0x0);
PM3_WRITE_REG(par, PM3DepthMode, 0x0);
PM3_WRITE_REG(par, PM3StencilMode, 0x0);
PM3_WRITE_REG(par, PM3StencilData, 0x0);
PM3_WRITE_REG(par, PM3ColorDDAMode, 0x0);
PM3_WRITE_REG(par, PM3TextureCoordMode, 0x0);
PM3_WRITE_REG(par, PM3TextureIndexMode0, 0x0);
PM3_WRITE_REG(par, PM3TextureIndexMode1, 0x0);
PM3_WRITE_REG(par, PM3TextureReadMode, 0x0);
PM3_WRITE_REG(par, PM3LUTMode, 0x0);
PM3_WRITE_REG(par, PM3TextureFilterMode, 0x0);
PM3_WRITE_REG(par, PM3TextureCompositeMode, 0x0);
PM3_WRITE_REG(par, PM3TextureApplicationMode, 0x0);
PM3_WRITE_REG(par, PM3TextureCompositeColorMode1, 0x0);
PM3_WRITE_REG(par, PM3TextureCompositeAlphaMode1, 0x0);
PM3_WRITE_REG(par, PM3TextureCompositeColorMode0, 0x0);
PM3_WRITE_REG(par, PM3TextureCompositeAlphaMode0, 0x0);
PM3_WRITE_REG(par, PM3FogMode, 0x0);
PM3_WRITE_REG(par, PM3ChromaTestMode, 0x0);
PM3_WRITE_REG(par, PM3AlphaTestMode, 0x0);
PM3_WRITE_REG(par, PM3AntialiasMode, 0x0);
PM3_WRITE_REG(par, PM3YUVMode, 0x0);
PM3_WRITE_REG(par, PM3AlphaBlendColorMode, 0x0);
PM3_WRITE_REG(par, PM3AlphaBlendAlphaMode, 0x0);
PM3_WRITE_REG(par, PM3DitherMode, 0x0);
PM3_WRITE_REG(par, PM3LogicalOpMode, 0x0);
PM3_WRITE_REG(par, PM3RouterMode, 0x0);
PM3_WRITE_REG(par, PM3Window, 0x0);
PM3_WRITE_REG(par, PM3Config2D, 0x0);
PM3_WRITE_REG(par, PM3SpanColorMask, 0xffffffff);
PM3_WRITE_REG(par, PM3XBias, 0x0);
PM3_WRITE_REG(par, PM3YBias, 0x0);
PM3_WRITE_REG(par, PM3DeltaControl, 0x0);
PM3_WRITE_REG(par, PM3BitMaskPattern, 0xffffffff);
PM3_WRITE_REG(par, PM3FBDestReadEnables,
PM3FBDestReadEnables_E(0xff) |
PM3FBDestReadEnables_R(0xff) |
PM3FBDestReadEnables_ReferenceAlpha(0xff));
PM3_WRITE_REG(par, PM3FBDestReadBufferAddr0, 0x0);
PM3_WRITE_REG(par, PM3FBDestReadBufferOffset0, 0x0);
PM3_WRITE_REG(par, PM3FBDestReadBufferWidth0,
PM3FBDestReadBufferWidth_Width(width));
PM3_WRITE_REG(par, PM3FBDestReadMode,
PM3FBDestReadMode_ReadEnable |
PM3FBDestReadMode_Enable0);
PM3_WRITE_REG(par, PM3FBSourceReadBufferAddr, 0x0);
PM3_WRITE_REG(par, PM3FBSourceReadBufferOffset, 0x0);
PM3_WRITE_REG(par, PM3FBSourceReadBufferWidth,
PM3FBSourceReadBufferWidth_Width(width));
PM3_WRITE_REG(par, PM3FBSourceReadMode,
PM3FBSourceReadMode_Blocking |
PM3FBSourceReadMode_ReadEnable);
PM3_WAIT(par, 2);
{
/* invert bits in bitmask */
unsigned long rm = 1 | (3 << 7);
switch (info->var.bits_per_pixel) {
case 8:
PM3_WRITE_REG(par, PM3PixelSize,
PM3PixelSize_GLOBAL_8BIT);
#ifdef __BIG_ENDIAN
rm |= 3 << 15;
#endif
break;
case 16:
PM3_WRITE_REG(par, PM3PixelSize,
PM3PixelSize_GLOBAL_16BIT);
#ifdef __BIG_ENDIAN
rm |= 2 << 15;
#endif
break;
case 32:
PM3_WRITE_REG(par, PM3PixelSize,
PM3PixelSize_GLOBAL_32BIT);
break;
default:
DPRINTK(1, "Unsupported depth %d\n",
info->var.bits_per_pixel);
break;
}
PM3_WRITE_REG(par, PM3RasterizerMode, rm);
}
PM3_WAIT(par, 20);
PM3_WRITE_REG(par, PM3FBSoftwareWriteMask, 0xffffffff);
PM3_WRITE_REG(par, PM3FBHardwareWriteMask, 0xffffffff);
PM3_WRITE_REG(par, PM3FBWriteMode,
PM3FBWriteMode_WriteEnable |
PM3FBWriteMode_OpaqueSpan |
PM3FBWriteMode_Enable0);
PM3_WRITE_REG(par, PM3FBWriteBufferAddr0, 0x0);
PM3_WRITE_REG(par, PM3FBWriteBufferOffset0, 0x0);
PM3_WRITE_REG(par, PM3FBWriteBufferWidth0,
PM3FBWriteBufferWidth_Width(width));
PM3_WRITE_REG(par, PM3SizeOfFramebuffer, 0x0);
{
/* size in lines of FB */
unsigned long sofb = info->screen_size /
info->fix.line_length;
if (sofb > 4095)
PM3_WRITE_REG(par, PM3SizeOfFramebuffer, 4095);
else
PM3_WRITE_REG(par, PM3SizeOfFramebuffer, sofb);
switch (info->var.bits_per_pixel) {
case 8:
PM3_WRITE_REG(par, PM3DitherMode,
(1 << 10) | (2 << 3));
break;
case 16:
PM3_WRITE_REG(par, PM3DitherMode,
(1 << 10) | (1 << 3));
break;
case 32:
PM3_WRITE_REG(par, PM3DitherMode,
(1 << 10) | (0 << 3));
break;