📄 sstfb.c
字号:
/* * linux/drivers/video/sstfb.c -- voodoo graphics frame buffer * * Copyright (c) 2000-2002 Ghozlane Toumi <gtoumi@laposte.net> * * Created 15 Jan 2000 by Ghozlane Toumi * * Contributions (and many thanks) : * * 03/2001 James Simmons <jsimmons@infradead.org> * 04/2001 Paul Mundt <lethal@chaoticdreams.org> * 05/2001 Urs Ganse <ursg@uni.de> * (initial work on voodoo2 port, interlace) * 09/2002 Helge Deller <deller@gmx.de> * (enable driver on big-endian machines (hppa), ioctl fixes) * 12/2002 Helge Deller <deller@gmx.de> * (port driver to new frambuffer infrastructure) * 01/2003 Helge Deller <deller@gmx.de> * (initial work on fb hardware acceleration for voodoo2) * 08/2006 Alan Cox <alan@redhat.com> * Remove never finished and bogus 24/32bit support * Clean up macro abuse * Minor tidying for format. * 12/2006 Helge Deller <deller@gmx.de> * add /sys/class/graphics/fbX/vgapass sysfs-interface * add module option "mode_option" to set initial screen mode * use fbdev default videomode database * remove debug functions from ioctl *//* * The voodoo1 has the following memory mapped address space: * 0x000000 - 0x3fffff : registers (4MB) * 0x400000 - 0x7fffff : linear frame buffer (4MB) * 0x800000 - 0xffffff : texture memory (8MB) *//* * misc notes, TODOs, toASKs, and deep thoughts-TODO: at one time or another test that the mode is acceptable by the monitor-ASK: Can I choose different ordering for the color bitfields (rgba argb ...) which one should i use ? is there any preferred one ? It seems ARGB is the one ...-TODO: in set_var check the validity of timings (hsync vsync)...-TODO: check and recheck the use of sst_wait_idle : we don't flush the fifo via a nop command. so it's ok as long as the commands we pass don't go through the fifo. warning: issuing a nop command seems to need pci_fifo-FIXME: in case of failure in the init sequence, be sure we return to a safe state.- FIXME: Use accelerator for 2D scroll-FIXME: 4MB boards have banked memory (FbiInit2 bits 1 & 20) *//* * debug info * SST_DEBUG : enable debugging * SST_DEBUG_REG : debug registers * 0 : no debug * 1 : dac calls, [un]set_bits, FbiInit * 2 : insane debug level (log every register read/write) * SST_DEBUG_FUNC : functions * 0 : no debug * 1 : function call / debug ioctl * 2 : variables * 3 : flood . you don't want to do that. trust me. * SST_DEBUG_VAR : debug display/var structs * 0 : no debug * 1 : dumps display, fb_var * * sstfb specific ioctls: * toggle vga (0x46db) : toggle vga_pass_through */#undef SST_DEBUG/* * Includes */#include <linux/string.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/fb.h>#include <linux/pci.h>#include <linux/delay.h>#include <linux/init.h>#include <linux/slab.h>#include <asm/io.h>#include <linux/uaccess.h>#include <video/sstfb.h>/* initialized by setup */static int vgapass; /* enable VGA passthrough cable */static int mem; /* mem size in MB, 0 = autodetect */static int clipping = 1; /* use clipping (slower, safer) */static int gfxclk; /* force FBI freq in Mhz . Dangerous */static int slowpci; /* slow PCI settings *//* Possible default video modes: 800x600@60, 640x480@75, 1024x768@76, 640x480@60*/#define DEFAULT_VIDEO_MODE "640x480@60"static char *mode_option __devinitdata = DEFAULT_VIDEO_MODE;enum { ID_VOODOO1 = 0, ID_VOODOO2 = 1,};#define IS_VOODOO2(par) ((par)->type == ID_VOODOO2)static struct sst_spec voodoo_spec[] __devinitdata = { { .name = "Voodoo Graphics", .default_gfx_clock = 50000, .max_gfxclk = 60 }, { .name = "Voodoo2", .default_gfx_clock = 75000, .max_gfxclk = 85 },};/* * debug functions */#if (SST_DEBUG_REG > 0)static void sst_dbg_print_read_reg(u32 reg, u32 val) { const char *regname; switch (reg) { case FBIINIT0: regname = "FbiInit0"; break; case FBIINIT1: regname = "FbiInit1"; break; case FBIINIT2: regname = "FbiInit2"; break; case FBIINIT3: regname = "FbiInit3"; break; case FBIINIT4: regname = "FbiInit4"; break; case FBIINIT5: regname = "FbiInit5"; break; case FBIINIT6: regname = "FbiInit6"; break; default: regname = NULL; break; } if (regname == NULL) r_ddprintk("sst_read(%#x): %#x\n", reg, val); else r_dprintk(" sst_read(%s): %#x\n", regname, val);}static void sst_dbg_print_write_reg(u32 reg, u32 val) { const char *regname; switch (reg) { case FBIINIT0: regname = "FbiInit0"; break; case FBIINIT1: regname = "FbiInit1"; break; case FBIINIT2: regname = "FbiInit2"; break; case FBIINIT3: regname = "FbiInit3"; break; case FBIINIT4: regname = "FbiInit4"; break; case FBIINIT5: regname = "FbiInit5"; break; case FBIINIT6: regname = "FbiInit6"; break; default: regname = NULL; break; } if (regname == NULL) r_ddprintk("sst_write(%#x, %#x)\n", reg, val); else r_dprintk(" sst_write(%s, %#x)\n", regname, val);}#else /* (SST_DEBUG_REG > 0) */# define sst_dbg_print_read_reg(reg, val) do {} while(0)# define sst_dbg_print_write_reg(reg, val) do {} while(0)#endif /* (SST_DEBUG_REG > 0) *//* * hardware access functions *//* register access */#define sst_read(reg) __sst_read(par->mmio_vbase, reg)#define sst_write(reg,val) __sst_write(par->mmio_vbase, reg, val)#define sst_set_bits(reg,val) __sst_set_bits(par->mmio_vbase, reg, val)#define sst_unset_bits(reg,val) __sst_unset_bits(par->mmio_vbase, reg, val)#define sst_dac_read(reg) __sst_dac_read(par->mmio_vbase, reg)#define sst_dac_write(reg,val) __sst_dac_write(par->mmio_vbase, reg, val)#define dac_i_read(reg) __dac_i_read(par->mmio_vbase, reg)#define dac_i_write(reg,val) __dac_i_write(par->mmio_vbase, reg, val)static inline u32 __sst_read(u8 __iomem *vbase, u32 reg){ u32 ret = readl(vbase + reg); sst_dbg_print_read_reg(reg, ret); return ret;}static inline void __sst_write(u8 __iomem *vbase, u32 reg, u32 val){ sst_dbg_print_write_reg(reg, val); writel(val, vbase + reg);}static inline void __sst_set_bits(u8 __iomem *vbase, u32 reg, u32 val){ r_dprintk("sst_set_bits(%#x, %#x)\n", reg, val); __sst_write(vbase, reg, __sst_read(vbase, reg) | val);}static inline void __sst_unset_bits(u8 __iomem *vbase, u32 reg, u32 val){ r_dprintk("sst_unset_bits(%#x, %#x)\n", reg, val); __sst_write(vbase, reg, __sst_read(vbase, reg) & ~val);}/* * wait for the fbi chip. ASK: what happens if the fbi is stuck ? * * the FBI is supposed to be ready if we receive 5 time * in a row a "idle" answer to our requests */#define sst_wait_idle() __sst_wait_idle(par->mmio_vbase)static int __sst_wait_idle(u8 __iomem *vbase){ int count = 0; /* if (doFBINOP) __sst_write(vbase, NOPCMD, 0); */ while(1) { if (__sst_read(vbase, STATUS) & STATUS_FBI_BUSY) { f_dddprintk("status: busy\n");/* FIXME basicaly, this is a busy wait. maybe not that good. oh well; * this is a small loop after all. * Or maybe we should use mdelay() or udelay() here instead ? */ count = 0; } else { count++; f_dddprintk("status: idle(%d)\n", count); } if (count >= 5) return 1;/* XXX do something to avoid hanging the machine if the voodoo is out */ }}/* dac access *//* dac_read should be remaped to FbiInit2 (via the pci reg init_enable) */static u8 __sst_dac_read(u8 __iomem *vbase, u8 reg){ u8 ret; reg &= 0x07; __sst_write(vbase, DAC_DATA, ((u32)reg << 8) | DAC_READ_CMD ); __sst_wait_idle(vbase); /* udelay(10); */ ret = __sst_read(vbase, DAC_READ) & 0xff; r_dprintk("sst_dac_read(%#x): %#x\n", reg, ret); return ret;}static void __sst_dac_write(u8 __iomem *vbase, u8 reg, u8 val){ r_dprintk("sst_dac_write(%#x, %#x)\n", reg, val); reg &= 0x07; __sst_write(vbase, DAC_DATA,(((u32)reg << 8)) | (u32)val); __sst_wait_idle(vbase);}/* indexed access to ti/att dacs */static u32 __dac_i_read(u8 __iomem *vbase, u8 reg){ u32 ret; __sst_dac_write(vbase, DACREG_ADDR_I, reg); ret = __sst_dac_read(vbase, DACREG_DATA_I); r_dprintk("sst_dac_read_i(%#x): %#x\n", reg, ret); return ret;}static void __dac_i_write(u8 __iomem *vbase, u8 reg,u8 val){ r_dprintk("sst_dac_write_i(%#x, %#x)\n", reg, val); __sst_dac_write(vbase, DACREG_ADDR_I, reg); __sst_dac_write(vbase, DACREG_DATA_I, val);}/* compute the m,n,p , returns the real freq * (ics datasheet : N <-> N1 , P <-> N2) * * Fout= Fref * (M+2)/( 2^P * (N+2)) * we try to get close to the asked freq * with P as high, and M as low as possible * range: * ti/att : 0 <= M <= 255; 0 <= P <= 3; 0<= N <= 63 * ics : 1 <= M <= 127; 0 <= P <= 3; 1<= N <= 31 * we'll use the lowest limitation, should be precise enouth */static int sst_calc_pll(const int freq, int *freq_out, struct pll_timing *t){ int m, m2, n, p, best_err, fout; int best_n = -1; int best_m = -1; best_err = freq; p = 3; /* f * 2^P = vco should be less than VCOmax ~ 250 MHz for ics*/ while (((1 << p) * freq > VCO_MAX) && (p >= 0)) p--; if (p == -1) return -EINVAL; for (n = 1; n < 32; n++) { /* calc 2 * m so we can round it later*/ m2 = (2 * freq * (1 << p) * (n + 2) ) / DAC_FREF - 4 ; m = (m2 % 2 ) ? m2/2+1 : m2/2 ; if (m >= 128) break; fout = (DAC_FREF * (m + 2)) / ((1 << p) * (n + 2)); if ((abs(fout - freq) < best_err) && (m > 0)) { best_n = n; best_m = m; best_err = abs(fout - freq); /* we get the lowest m , allowing 0.5% error in freq*/ if (200*best_err < freq) break; } } if (best_n == -1) /* unlikely, but who knows ? */ return -EINVAL; t->p = p; t->n = best_n; t->m = best_m; *freq_out = (DAC_FREF * (t->m + 2)) / ((1 << t->p) * (t->n + 2)); f_ddprintk ("m: %d, n: %d, p: %d, F: %dKhz\n", t->m, t->n, t->p, *freq_out); return 0;}/* * clear lfb screen */static void sstfb_clear_screen(struct fb_info *info){ /* clear screen */ fb_memset(info->screen_base, 0, info->fix.smem_len);}/** * sstfb_check_var - Optional function. Validates a var passed in. * @var: frame buffer variable screen structure * @info: frame buffer structure that represents a single frame buffer * * Limit to the abilities of a single chip as SLI is not supported * by this driver. */static int sstfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info){ struct sstfb_par *par = info->par; int hSyncOff = var->xres + var->right_margin + var->left_margin; int vSyncOff = var->yres + var->lower_margin + var->upper_margin; int vBackPorch = var->left_margin, yDim = var->yres; int vSyncOn = var->vsync_len; int tiles_in_X, real_length; unsigned int freq; if (sst_calc_pll(PICOS2KHZ(var->pixclock), &freq, &par->pll)) { printk(KERN_ERR "sstfb: Pixclock at %ld KHZ out of range\n", PICOS2KHZ(var->pixclock)); return -EINVAL; } var->pixclock = KHZ2PICOS(freq); if (var->vmode & FB_VMODE_INTERLACED) vBackPorch += (vBackPorch % 2); if (var->vmode & FB_VMODE_DOUBLE) { vBackPorch <<= 1; yDim <<=1; vSyncOn <<=1; vSyncOff <<=1; } switch (var->bits_per_pixel) { case 0 ... 16 : var->bits_per_pixel = 16; break; default : printk(KERN_ERR "sstfb: Unsupported bpp %d\n", var->bits_per_pixel); return -EINVAL; } /* validity tests */ if (var->xres <= 1 || yDim <= 0 || var->hsync_len <= 1 || hSyncOff <= 1 || var->left_margin <= 2 || vSyncOn <= 0 || vSyncOff <= 0 || vBackPorch <= 0) { return -EINVAL; } if (IS_VOODOO2(par)) { /* Voodoo 2 limits */ tiles_in_X = (var->xres + 63 ) / 64 * 2; if (var->xres > POW2(11) || yDim >= POW2(11)) { printk(KERN_ERR "sstfb: Unsupported resolution %dx%d\n", var->xres, var->yres); return -EINVAL; } if (var->hsync_len > POW2(9) || hSyncOff > POW2(11) || var->left_margin - 2 >= POW2(9) || vSyncOn >= POW2(13) || vSyncOff >= POW2(13) || vBackPorch >= POW2(9) || tiles_in_X >= POW2(6) || tiles_in_X <= 0) { printk(KERN_ERR "sstfb: Unsupported timings\n"); return -EINVAL; } } else { /* Voodoo limits */ tiles_in_X = (var->xres + 63 ) / 64; if (var->vmode) { printk(KERN_ERR "sstfb: Interlace/doublescan not supported %#x\n", var->vmode); return -EINVAL; } if (var->xres > POW2(10) || var->yres >= POW2(10)) { printk(KERN_ERR "sstfb: Unsupported resolution %dx%d\n", var->xres, var->yres); return -EINVAL; } if (var->hsync_len > POW2(8) || hSyncOff - 1 > POW2(10) || var->left_margin - 2 >= POW2(8) || vSyncOn >= POW2(12) || vSyncOff >= POW2(12) || vBackPorch >= POW2(8) || tiles_in_X >= POW2(4) || tiles_in_X <= 0) { printk(KERN_ERR "sstfb: Unsupported timings\n"); return -EINVAL; } } /* it seems that the fbi uses tiles of 64x16 pixels to "map" the mem */ /* FIXME: i don't like this... looks wrong */ real_length = tiles_in_X * (IS_VOODOO2(par) ? 32 : 64 ) * ((var->bits_per_pixel == 16) ? 2 : 4); if (real_length * yDim > info->fix.smem_len) { printk(KERN_ERR "sstfb: Not enough video memory\n"); return -ENOMEM; } var->sync &= (FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT); var->vmode &= (FB_VMODE_INTERLACED | FB_VMODE_DOUBLE); var->xoffset = 0; var->yoffset = 0; var->height = -1; var->width = -1; /* * correct the color bit fields */ /* var->{red|green|blue}.msb_right = 0; */ switch (var->bits_per_pixel) { case 16: /* RGB 565 LfbMode 0 */ var->red.length = 5; var->green.length = 6; var->blue.length = 5; var->transp.length = 0; var->red.offset = 11; var->green.offset = 5; var->blue.offset = 0; var->transp.offset = 0; break; default: return -EINVAL; } return 0;}/** * sstfb_set_par - Optional function. Alters the hardware state. * @info: frame buffer structure that represents a single frame buffer */static int sstfb_set_par(struct fb_info *info){ struct sstfb_par *par = info->par; u32 lfbmode, fbiinit1, fbiinit2, fbiinit3, fbiinit5, fbiinit6=0; struct pci_dev *sst_dev = par->dev; unsigned int freq; int ntiles; par->hSyncOff = info->var.xres + info->var.right_margin + info->var.left_margin; par->yDim = info->var.yres; par->vSyncOn = info->var.vsync_len; par->vSyncOff = info->var.yres + info->var.lower_margin + info->var.upper_margin; par->vBackPorch = info->var.upper_margin; /* We need par->pll */ sst_calc_pll(PICOS2KHZ(info->var.pixclock), &freq, &par->pll); if (info->var.vmode & FB_VMODE_INTERLACED) par->vBackPorch += (par->vBackPorch % 2); if (info->var.vmode & FB_VMODE_DOUBLE) { par->vBackPorch <<= 1; par->yDim <<=1; par->vSyncOn <<=1; par->vSyncOff <<=1; } if (IS_VOODOO2(par)) { /* voodoo2 has 32 pixel wide tiles , BUT stange things happen with odd number of tiles */ par->tiles_in_X = (info->var.xres + 63 ) / 64 * 2; } else { /* voodoo1 has 64 pixels wide tiles. */ par->tiles_in_X = (info->var.xres + 63 ) / 64; }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -