/* * linux/drivers/video/stifb.c - * Low level Frame buffer driver for HP workstations with * STI (standard text interface) video firmware. * * Copyright (C) 2001-2006 Helge Deller <deller@gmx.de> * Portions Copyright (C) 2001 Thomas Bogendoerfer <tsbogend@alpha.franken.de> * * Based on: * - linux/drivers/video/artistfb.c -- Artist frame buffer driver * Copyright (C) 2000 Philipp Rumpf <prumpf@tux.org> * - based on skeletonfb, which was * Created 28 Dec 1997 by Geert Uytterhoeven * - HP Xhp cfb-based X11 window driver for XFree86 * (c)Copyright 1992 Hewlett-Packard Co. * * * The following graphics display devices (NGLE family) are supported by this driver: * * HPA4070A known as "HCRX", a 1280x1024 color device with 8 planes * HPA4071A known as "HCRX24", a 1280x1024 color device with 24 planes, * optionally available with a hardware accelerator as HPA4071A_Z * HPA1659A known as "CRX", a 1280x1024 color device with 8 planes * HPA1439A known as "CRX24", a 1280x1024 color device with 24 planes, * optionally available with a hardware accelerator. * HPA1924A known as "GRX", a 1280x1024 grayscale device with 8 planes * HPA2269A known as "Dual CRX", a 1280x1024 color device with 8 planes, * implements support for two displays on a single graphics card. * HP710C internal graphics support optionally available on the HP9000s710 SPU, * supports 1280x1024 color displays with 8 planes. * HP710G same as HP710C, 1280x1024 grayscale only * HP710L same as HP710C, 1024x768 color only * HP712 internal graphics support on HP9000s712 SPU, supports 640x480, * 1024x768 or 1280x1024 color displays on 8 planes (Artist) * * 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. */ /* TODO: * - 1bpp mode is completely untested * - add support for h/w acceleration * - add hardware cursor * - automatically disable double buffering (e.g. on RDI precisionbook laptop) */ /* on supported graphic devices you may: * #define FALLBACK_TO_1BPP to fall back to 1 bpp, or * #undef FALLBACK_TO_1BPP to reject support for unsupported cards */ #undef FALLBACK_TO_1BPP #undef DEBUG_STIFB_REGS /* debug sti register accesses */ #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/ioport.h> #include <linux/io.h> #include <asm/grfioctl.h> /* for HP-UX compatibility */ #include <linux/uaccess.h> #include <video/sticore.h> /* REGION_BASE(fb_info, index) returns the physical address for region <index> */ #define REGION_BASE(fb_info, index) \ F_EXTEND(fb_info->sti->regions_phys[index]) #define NGLEDEVDEPROM_CRT_REGION 1 #define NR_PALETTE 256 typedef struct { __s32 video_config_reg; __s32 misc_video_start; __s32 horiz_timing_fmt; __s32 serr_timing_fmt; __s32 vert_timing_fmt; __s32 horiz_state; __s32 vert_state; __s32 vtg_state_elements; __s32 pipeline_delay; __s32 misc_video_end; } video_setup_t; typedef struct { __s16 sizeof_ngle_data; __s16 x_size_visible; /* visible screen dim in pixels */ __s16 y_size_visible; __s16 pad2[15]; __s16 cursor_pipeline_delay; __s16 video_interleaves; __s32 pad3[11]; } ngle_rom_t; struct stifb_info { struct fb_info info; unsigned int id; ngle_rom_t ngle_rom; struct sti_struct *sti; int deviceSpecificConfig; u32 pseudo_palette[16]; }; static int __initdata stifb_bpp_pref[MAX_STI_ROMS]; /* ------------------- chipset specific functions -------------------------- */ /* offsets to graphic-chip internal registers */ #define REG_1 0x000118 #define REG_2 0x000480 #define REG_3 0x0004a0 #define REG_4 0x000600 #define REG_6 0x000800 #define REG_7 0x000804 #define REG_8 0x000820 #define REG_9 0x000a04 #define REG_10 0x018000 #define REG_11 0x018004 #define REG_12 0x01800c #define REG_13 0x018018 #define REG_14 0x01801c #define REG_15 0x200000 #define REG_15b0 0x200000 #define REG_16b1 0x200005 #define REG_16b3 0x200007 #define REG_21 0x200218 #define REG_22 0x0005a0 #define REG_23 0x0005c0 #define REG_24 0x000808 #define REG_25 0x000b00 #define REG_26 0x200118 #define REG_27 0x200308 #define REG_32 0x21003c #define REG_33 0x210040 #define REG_34 0x200008 #define REG_35 0x018010 #define REG_38 0x210020 #define REG_39 0x210120 #define REG_40 0x210130 #define REG_42 0x210028 #define REG_43 0x21002c #define REG_44 0x210030 #define REG_45 0x210034 #define READ_BYTE(fb,reg) gsc_readb((fb)->info.fix.mmio_start + (reg)) #define READ_WORD(fb,reg) gsc_readl((fb)->info.fix.mmio_start + (reg)) #ifndef DEBUG_STIFB_REGS # define DEBUG_OFF() # define DEBUG_ON() # define WRITE_BYTE(value,fb,reg) gsc_writeb((value),(fb)->info.fix.mmio_start + (reg)) # define WRITE_WORD(value,fb,reg) gsc_writel((value),(fb)->info.fix.mmio_start + (reg)) #else static int debug_on = 1; # define DEBUG_OFF() debug_on=0 # define DEBUG_ON() debug_on=1 # define WRITE_BYTE(value,fb,reg) do { if (debug_on) \ printk(KERN_DEBUG "%30s: WRITE_BYTE(0x%06x) = 0x%02x (old=0x%02x)\n", \ __func__, reg, value, READ_BYTE(fb,reg)); \ gsc_writeb((value),(fb)->info.fix.mmio_start + (reg)); } while (0) # define WRITE_WORD(value,fb,reg) do { if (debug_on) \ printk(KERN_DEBUG "%30s: WRITE_WORD(0x%06x) = 0x%08x (old=0x%08x)\n", \ __func__, reg, value, READ_WORD(fb,reg)); \ gsc_writel((value),(fb)->info.fix.mmio_start + (reg)); } while (0) #endif /* DEBUG_STIFB_REGS */ #define ENABLE 1 /* for enabling/disabling screen */ #define DISABLE 0 #define NGLE_LOCK(fb_info) do { } while (0) #define NGLE_UNLOCK(fb_info) do { } while (0) static void SETUP_HW(struct stifb_info *fb) { char stat; do { stat = READ_BYTE(fb, REG_15b0); if (!stat) stat = READ_BYTE(fb, REG_15b0); } while (stat); } static void SETUP_FB(struct stifb_info *fb) { unsigned int reg10_value = 0; SETUP_HW(fb); switch (fb->id) { case CRT_ID_VISUALIZE_EG: case S9000_ID_ARTIST: case S9000_ID_A1659A: reg10_value = 0x13601000; break; case S9000_ID_A1439A: if (fb->info.var.bits_per_pixel == 32) reg10_value = 0xBBA0A000; else reg10_value = 0x13601000; break; case S9000_ID_HCRX: if (fb->info.var.bits_per_pixel == 32) reg10_value = 0xBBA0A000; else reg10_value = 0x13602000; break; case S9000_ID_TIMBER: case CRX24_OVERLAY_PLANES: reg10_value = 0x13602000; break; } if (reg10_value) WRITE_WORD(reg10_value, fb, REG_10); WRITE_WORD(0x83000300, fb, REG_14); SETUP_HW(fb); WRITE_BYTE(1, fb, REG_16b1); } static void START_IMAGE_COLORMAP_ACCESS(struct stifb_info *fb) { SETUP_HW(fb); WRITE_WORD(0xBBE0F000, fb, REG_10); WRITE_WORD(0x03000300, fb, REG_14); WRITE_WORD(~0, fb, REG_13); } static void WRITE_IMAGE_COLOR(struct stifb_info *fb, int index, int color) { SETUP_HW(fb); WRITE_WORD(((0x100+index)<<2), fb, REG_3); WRITE_WORD(color, fb, REG_4); } static void FINISH_IMAGE_COLORMAP_ACCESS(struct stifb_info *fb) { WRITE_WORD(0x400, fb, REG_2); if (fb->info.var.bits_per_pixel == 32) { WRITE_WORD(0x83000100, fb, REG_1); } else { if (fb->id == S9000_ID_ARTIST || fb->id == CRT_ID_VISUALIZE_EG) WRITE_WORD(0x80000100, fb, REG_26); else WRITE_WORD(0x80000100, fb, REG_1); } SETUP_FB(fb); } static void SETUP_RAMDAC(struct stifb_info *fb) { SETUP_HW(fb); WRITE_WORD(0x04000000, fb, 0x1020); WRITE_WORD(0xff000000, fb, 0x1028); } static void CRX24_SETUP_RAMDAC(struct stifb_info *fb) { SETUP_HW(fb); WRITE_WORD(0x04000000, fb, 0x1000); WRITE_WORD(0x02000000, fb, 0x1004); WRITE_WORD(0xff000000, fb, 0x1008); WRITE_WORD(0x05000000, fb, 0x1000); WRITE_WORD(0x02000000, fb, 0x1004); WRITE_WORD(0x03000000, fb, 0x1008); } #if 0 static void HCRX_SETUP_RAMDAC(struct stifb_info *fb) { WRITE_WORD(0xffffffff, fb, REG_32); } #endif static void CRX24_SET_OVLY_MASK(struct stifb_info *fb) { SETUP_HW(fb); WRITE_WORD(0x13a02000, fb, REG_11); WRITE_WORD(0x03000300, fb, REG_14); WRITE_WORD(0x000017f0, fb, REG_3); WRITE_WORD(0xffffffff, fb, REG_13); WRITE_WORD(0xffffffff, fb, REG_22); WRITE_WORD(0x00000000, fb, REG_23); } static void ENABLE_DISABLE_DISPLAY(struct stifb_info *fb, int enable) { unsigned int value = enable ? 0x43000000 : 0x03000000; SETUP_HW(fb); WRITE_WORD(0x06000000, fb, 0x1030); WRITE_WORD(value, fb, 0x1038); } static void CRX24_ENABLE_DISABLE_DISPLAY(struct stifb_info *fb, int enable) { unsigned int value = enable ? 0x10000000 : 0x30000000; SETUP_HW(fb); WRITE_WORD(0x01000000, fb, 0x1000); WRITE_WORD(0x02000000, fb, 0x1004); WRITE_WORD(value, fb, 0x1008); } static void ARTIST_ENABLE_DISABLE_DISPLAY(struct stifb_info *fb, int enable) { u32 DregsMiscVideo = REG_21; u32 DregsMiscCtl = REG_27; SETUP_HW(fb); if (enable) { WRITE_WORD(READ_WORD(fb, DregsMiscVideo) | 0x0A000000, fb, DregsMiscVideo); WRITE_WORD(READ_WORD(fb, DregsMiscCtl) | 0x00800000, fb, DregsMiscCtl); } else { WRITE_WORD(READ_WORD(fb, DregsMiscVideo) & ~0x0A000000, fb, DregsMiscVideo); WRITE_WORD(READ_WORD(fb, DregsMiscCtl) & ~0x00800000, fb, DregsMiscCtl); } } #define GET_ROMTABLE_INDEX(fb) \ (READ_BYTE(fb, REG_16b3) - 1) #define HYPER_CONFIG_PLANES_24 0x00000100 #define IS_24_DEVICE(fb) \ (fb->deviceSpecificConfig & HYPER_CONFIG_PLANES_24) #define IS_888_DEVICE(fb) \ (!(IS_24_DEVICE(fb))) #define GET_FIFO_SLOTS(fb, cnt, numslots) \ { while (cnt < numslots) \ cnt = READ_WORD(fb, REG_34); \ cnt -= numslots; \ } #define IndexedDcd 0 /* Pixel data is indexed (pseudo) color */ #define Otc04 2 /* Pixels in each longword transfer (4) */ #define Otc32 5 /* Pixels in each longword transfer (32) */ #define Ots08 3 /* Each pixel is size (8)d transfer (1) */ #define OtsIndirect 6 /* Each bit goes through FG/BG color(8) */ #define AddrLong 5 /* FB address is Long aligned (pixel) */ #define BINovly 0x2 /* 8 bit overlay */ #define BINapp0I 0x0 /* Application Buffer 0, Indexed */ #define BINapp1I 0x1 /* Application Buffer 1, Indexed */ #define BINapp0F8 0xa /* Application Buffer 0, Fractional 8-8-8 */ #define BINattr 0xd /* Attribute Bitmap */ #define RopSrc 0x3 #define BitmapExtent08 3 /* Each write hits ( 8) bits in depth */ #define BitmapExtent32 5 /* Each write hits (32) bits in depth */ #define DataDynamic 0 /* Data register reloaded by direct access */ #define MaskDynamic 1 /* Mask register reloaded by direct access */ #define MaskOtc 0 /* Mask contains Object Count valid bits */ #define MaskAddrOffset(offset) (offset) #define StaticReg(en) (en) #define BGx(en) (en) #define FGx(en) (en) #define BAJustPoint(offset) (offset) #define BAIndexBase(base) (base) #define BA(F,C,S,A,J,B,I) \ (((F)<<31)|((C)<<27)|((S)<<24)|((A)<<21)|((J)<<16)|((B)<<12)|(I)) #define IBOvals(R,M,X,S,D,L,B,F) \ (((R)<<8)|((M)<<16)|((X)<<24)|((S)<<29)|((D)<<28)|((L)<<31)|((B)<<1)|(F)) #define NGLE_QUICK_SET_IMAGE_BITMAP_OP(fb, val) \ WRITE_WORD(val, fb, REG_14) #define NGLE_QUICK_SET_DST_BM_ACCESS(fb, val) \ WRITE_WORD(val, fb, REG_11) #define NGLE_QUICK_SET_CTL_PLN_REG(fb, val) \ WRITE_WORD(val, fb, REG_12) #define NGLE_REALLY_SET_IMAGE_PLANEMASK(fb, plnmsk32) \ WRITE_WORD(plnmsk32, fb, REG_13) #define NGLE_REALLY_SET_IMAGE_FG_COLOR(fb, fg32) \ WRITE_WORD(fg32, fb, REG_35) #define NGLE_SET_TRANSFERDATA(fb, val) \ WRITE_WORD(val, fb, REG_8) #define NGLE_SET_DSTXY(fb, val) \ WRITE_WORD(val, fb, REG_6) #define NGLE_LONG_FB_ADDRESS(fbaddrbase, x, y) ( \ (u32) (fbaddrbase) + \ ( (unsigned int) ( (y) << 13 ) | \ (unsigned int) ( (x) << 2 ) ) \ ) #define NGLE_BINC_SET_DSTADDR(fb, addr) \ WRITE_WORD(addr, fb, REG_3) #define NGLE_BINC_SET_SRCADDR(fb, addr) \ WRITE_WORD(addr, fb, REG_2) #define NGLE_BINC_SET_DSTMASK(fb, mask) \ WRITE_WORD(mask, fb, REG_22) #define NGLE_BINC_WRITE32(fb, data32) \ WRITE_WORD(data32, fb, REG_23) #define START_COLORMAPLOAD(fb, cmapBltCtlData32) \ WRITE_WORD((cmapBltCtlData32), fb, REG_38) #define SET_LENXY_START_RECFILL(fb, lenxy) \ WRITE_WORD(lenxy, fb, REG_9) #define SETUP_COPYAREA(fb) \ WRITE_BYTE(0, fb, REG_16b1) static void HYPER_ENABLE_DISABLE_DISPLAY(struct stifb_info *fb, int enable) { u32 DregsHypMiscVideo = REG_33; unsigned int value; SETUP_HW(fb); value = READ_WORD(fb, DregsHypMiscVideo); if (enable) value |= 0x0A000000; else value &= ~0x0A000000; WRITE_WORD(value, fb, DregsHypMiscVideo); } /* BufferNumbers used by SETUP_ATTR_ACCESS() */ #define BUFF0_CMAP0 0x00001e02 #define BUFF1_CMAP0 0x02001e02 #define BUFF1_CMAP3 0x0c001e02 #define ARTIST_CMAP0 0x00000102 #define HYPER_CMAP8 0x00000100 #define HYPER_CMAP24 0x00000800 static void SETUP_ATTR_ACCESS(struct stifb_info *fb, unsigned BufferNumber) { SETUP_HW(fb); WRITE_WORD(0x2EA0D000, fb, REG_11); WRITE_WORD(0x23000302, fb, REG_14); WRITE_WORD(BufferNumber, fb, REG_12); WRITE_WORD(0xffffffff, fb, REG_8); } static void SET_ATTR_SIZE(struct stifb_info *fb, int width, int height) { /* REG_6 seems to have special values when run on a RDI precisionbook parisc laptop (INTERNAL_EG_DX1024 or INTERNAL_EG_X1024). The values are: 0x2f0: internal (LCD) & external display enabled 0x2a0: external display only 0x000: zero on standard artist graphic cards */ WRITE_WORD(0x00000000, fb, REG_6); WRITE_WORD((width<<16) | height, fb, REG_9); WRITE_WORD(0x05000000, fb, REG_6); WRITE_WORD(0x00040001, fb, REG_9); } static void FINISH_ATTR_ACCESS(struct stifb_info *fb) { SETUP_HW(fb); WRITE_WORD(0x00000000, fb, REG_12); } static void elkSetupPlanes(struct stifb_info *fb) { SETUP_RAMDAC(fb); SETUP_FB(fb); } static void ngleSetupAttrPlanes(struct stifb_info *fb, int BufferNumber) { SETUP_ATTR_ACCESS(fb, BufferNumber); SET_ATTR_SIZE(fb, fb->info.var.xres, fb->info.var.yres); FINISH_ATTR_ACCESS(fb); SETUP_FB(fb); } static void rattlerSetupPlanes(struct stifb_info *fb) { int saved_id, y; /* Write RAMDAC pixel read mask register so all overlay * planes are display-enabled. (CRX24 uses Bt462 pixel * read mask register for overlay planes, not image planes). */ CRX24_SETUP_RAMDAC(fb); /* change fb->id temporarily to fool SETUP_FB() */ saved_id = fb->id; fb->id = CRX24_OVERLAY_PLANES; SETUP_FB(fb); fb->id = saved_id; for (y = 0; y < fb->info.var.yres; ++y) fb_memset_io(fb->info.screen_base + y * fb->info.fix.line_length, 0xff, fb->info.var.xres * fb->info.var.bits_per_pixel/8); CRX24_SET_OVLY_MASK(fb); SETUP_FB(fb); } #define HYPER_CMAP_TYPE 0 #define NGLE_CMAP_INDEXED0_TYPE 0 #define NGLE_CMAP_OVERLAY_TYPE 3 /* typedef of LUT (Colormap) BLT Control Register */ typedef union /* Note assumption that fields are packed left-to-right */ { u32 all; struct { unsigned enable : 1; unsigned waitBlank : 1; unsigned reserved1 : 4; unsigned lutOffset : 10; /* Within destination LUT */ unsigned lutType : 2; /* Cursor, image, overlay */ unsigned reserved2 : 4; unsigned length : 10; } fields; } NgleLutBltCtl; #if 0 static NgleLutBltCtl setNgleLutBltCtl(struct stifb_info *fb, int offsetWithinLut, int length) { NgleLutBltCtl lutBltCtl; /* set enable, zero reserved fields */ lutBltCtl.all = 0x80000000; lutBltCtl.fields.length = length; switch (fb->id) { case S9000_ID_A1439A: /* CRX24 */ if (fb->var.bits_per_pixel == 8) { lutBltCtl.fields.lutType = NGLE_CMAP_OVERLAY_TYPE; lutBltCtl.fields.lutOffset = 0; } else { lutBltCtl.fields.lutType = NGLE_CMAP_INDEXED0_TYPE; lutBltCtl.fields.lutOffset = 0 * 256; } break; case S9000_ID_ARTIST: lutBltCtl.fields.lutType = NGLE_CMAP_INDEXED0_TYPE; lutBltCtl.fields.lutOffset = 0 * 256; break; default: lutBltCtl.fields.lutType = NGLE_CMAP_INDEXED0_TYPE; lutBltCtl.fields.lutOffset = 0; break; } /* Offset points to start of LUT. Adjust for within LUT */ lutBltCtl.fields.lutOffset += offsetWithinLut; return lutBltCtl; } #endif static NgleLutBltCtl setHyperLutBltCtl(struct stifb_info *fb, int offsetWithinLut, int length) { NgleLutBltCtl lutBltCtl; /* set enable, zero reserved fields */ lutBltCtl.all = 0x80000000; lutBltCtl.fields.length = length; lutBltCtl.fields.lutType = HYPER_CMAP_TYPE; /* Expect lutIndex to be 0 or 1 for image cmaps, 2 or 3 for overlay cmaps */ if (fb->info.var.bits_per_pixel == 8) lutBltCtl.fields.lutOffset = 2 * 256; else lutBltCtl.fields.lutOffset = 0 * 256; /* Offset points to start of LUT. Adjust for within LUT */ lutBltCtl.fields.lutOffset += offsetWithinLut; return lutBltCtl; } static void hyperUndoITE(struct stifb_info *fb) { int nFreeFifoSlots = 0; u32 fbAddr; NGLE_LOCK(fb); GET_FIFO_SLOTS(fb, nFreeFifoSlots, 1); WRITE_WORD(0xffffffff, fb, REG_32); /* Write overlay transparency mask so only entry 255 is transparent */ /* Hardware setup for full-depth write to "magic" location */ GET_FIFO_SLOTS(fb, nFreeFifoSlots, 7); NGLE_QUICK_SET_DST_BM_ACCESS(fb, BA(IndexedDcd, Otc04, Ots08, AddrLong, BAJustPoint(0), BINovly, BAIndexBase(0))); NGLE_QUICK_SET_IMAGE_BITMAP_OP(fb, IBOvals(RopSrc, MaskAddrOffset(0), BitmapExtent08, StaticReg(0), DataDynamic, MaskOtc, BGx(0), FGx(0))); /* Now prepare to write to the "magic" location */ fbAddr = NGLE_LONG_FB_ADDRESS(0, 1532, 0); NGLE_BINC_SET_DSTADDR(fb, fbAddr); NGLE_REALLY_SET_IMAGE_PLANEMASK(fb, 0xffffff); NGLE_BINC_SET_DSTMASK(fb, 0xffffffff); /* Finally, write a zero to clear the mask */ NGLE_BINC_WRITE32(fb, 0); NGLE_UNLOCK(fb); } static void ngleDepth8_ClearImagePlanes(struct stifb_info *fb) { /* FIXME! */ } static void ngleDepth24_ClearImagePlanes(struct stifb_info *fb) { /* FIXME! */ } static void ngleResetAttrPlanes(struct stifb_info *fb, unsigned int ctlPlaneReg) { int nFreeFifoSlots = 0; u32 packed_dst; u32 packed_len; NGLE_LOCK(fb); GET_FIFO_SLOTS(fb, nFreeFifoSlots, 4); NGLE_QUICK_SET_DST_BM_ACCESS(fb, BA(IndexedDcd, Otc32, OtsIndirect, AddrLong, BAJustPoint(0), BINattr, BAIndexBase(0))); NGLE_QUICK_SET_CTL_PLN_REG(fb, ctlPlaneReg); NGLE_SET_TRANSFERDATA(fb, 0xffffffff); NGLE_QUICK_SET_IMAGE_BITMAP_OP(fb, IBOvals(RopSrc, MaskAddrOffset(0), BitmapExtent08, StaticReg(1), DataDynamic, MaskOtc, BGx(0), FGx(0))); packed_dst = 0; packed_len = (fb->info.var.xres << 16) | fb->info.var.yres; GET_FIFO_SLOTS(fb, nFreeFifoSlots, 2); NGLE_SET_DSTXY(fb, packed_dst); SET_LENXY_START_RECFILL(fb, packed_len); /* * In order to work around an ELK hardware problem (Buffy doesn't * always flush it's buffers when writing to the attribute * planes), at least 4 pixels must be written to the attribute * planes starting at (X == 1280) and (Y != to the last Y written * by BIF): */ if (fb->id == S9000_ID_A1659A) { /* ELK_DEVICE_ID */ /* It's safe to use scanline zero: */ packed_dst = (1280 << 16); GET_FIFO_SLOTS(fb, nFreeFifoSlots, 2); NGLE_SET_DSTXY(fb, packed_dst); packed_len = (4 << 16) | 1; SET_LENXY_START_RECFILL(fb, packed_len); } /* ELK Hardware Kludge */ /**** Finally, set the Control Plane Register back to zero: ****/ GET_FIFO_SLOTS(fb, nFreeFifoSlots, 1); NGLE_QUICK_SET_CTL_PLN_REG(fb, 0); NGLE_UNLOCK(fb); } static void ngleClearOverlayPlanes(struct stifb_info *fb, int mask, int data) { int nFreeFifoSlots = 0; u32 packed_dst; u32 packed_len; NGLE_LOCK(fb); /* Hardware setup */ GET_FIFO_SLOTS(fb, nFreeFifoSlots, 8); NGLE_QUICK_SET_DST_BM_ACCESS(fb, BA(IndexedDcd, Otc04, Ots08, AddrLong, BAJustPoint(0), BINovly, BAIndexBase(0))); NGLE_SET_TRANSFERDATA(fb, 0xffffffff); /* Write foreground color */ NGLE_REALLY_SET_IMAGE_FG_COLOR(fb, data); NGLE_REALLY_SET_IMAGE_PLANEMASK(fb, mask); packed_dst = 0; packed_len = (fb->info.var.xres << 16) | fb->info.var.yres; NGLE_SET_DSTXY(fb, packed_dst); /* Write zeroes to overlay planes */ NGLE_QUICK_SET_IMAGE_BITMAP_OP(fb, IBOvals(RopSrc, MaskAddrOffset(0), BitmapExtent08, StaticReg(0), DataDynamic, MaskOtc, BGx(0), FGx(0))); SET_LENXY_START_RECFILL(fb, packed_len); NGLE_UNLOCK(fb); } static void hyperResetPlanes(struct stifb_info *fb, int enable) { unsigned int controlPlaneReg; NGLE_LOCK(fb); if (IS_24_DEVICE(fb)) if (fb->info.var.bits_per_pixel == 32) controlPlaneReg = 0x04000F00; else controlPlaneReg = 0x00000F00; /* 0x00000800 should be enough, but lets clear all 4 bits */ else controlPlaneReg = 0x00000F00; /* 0x00000100 should be enough, but lets clear all 4 bits */ switch (enable) { case ENABLE: /* clear screen */ if (IS_24_DEVICE(fb)) ngleDepth24_ClearImagePlanes(fb); else ngleDepth8_ClearImagePlanes(fb); /* Paint attribute planes for default case. * On Hyperdrive, this means all windows using overlay cmap 0. */ ngleResetAttrPlanes(fb, controlPlaneReg); /* clear overlay planes */ ngleClearOverlayPlanes(fb, 0xff, 255); /************************************************** ** Also need to counteract ITE settings **************************************************/ hyperUndoITE(fb); break; case DISABLE: /* clear screen */ if (IS_24_DEVICE(fb)) ngleDepth24_ClearImagePlanes(fb); else ngleDepth8_ClearImagePlanes(fb); ngleResetAttrPlanes(fb, controlPlaneReg); ngleClearOverlayPlanes(fb, 0xff, 0); break; case -1: /* RESET */ hyperUndoITE(fb); ngleResetAttrPlanes(fb, controlPlaneReg); break; } NGLE_UNLOCK(fb); } /* Return pointer to in-memory structure holding ELK device-dependent ROM values. */ static void ngleGetDeviceRomData(struct stifb_info *fb) { #if 0 XXX: FIXME: !!! int *pBytePerLongDevDepData;/* data byte == LSB */ int *pRomTable; NgleDevRomData *pPackedDevRomData; int sizePackedDevRomData = sizeof(*pPackedDevRomData); char *pCard8; int i; char *mapOrigin = NULL; int romTableIdx; pPackedDevRomData = fb->ngle_rom; SETUP_HW(fb); if (fb->id == S9000_ID_ARTIST) { pPackedDevRomData->cursor_pipeline_delay = 4; pPackedDevRomData->video_interleaves = 4; } else { /* Get pointer to unpacked byte/long data in ROM */ pBytePerLongDevDepData = fb->sti->regions[NGLEDEVDEPROM_CRT_REGION]; /* Tomcat supports several resolutions: 1280x1024, 1024x768, 640x480 */ if (fb->id == S9000_ID_TOMCAT) { /* jump to the correct ROM table */ GET_ROMTABLE_INDEX(romTableIdx); while (romTableIdx > 0) { pCard8 = (Card8 *) pPackedDevRomData; pRomTable = pBytePerLongDevDepData; /* Pack every fourth byte from ROM into structure */ for (i = 0; i < sizePackedDevRomData; i++) { *pCard8++ = (Card8) (*pRomTable++); } pBytePerLongDevDepData = (Card32 *) ((Card8 *) pBytePerLongDevDepData + pPackedDevRomData->sizeof_ngle_data); romTableIdx--; } } pCard8 = (Card8 *) pPackedDevRomData; /* Pack every fourth byte from ROM into structure */ for (i = 0; i < sizePackedDevRomData; i++) { *pCard8++ = (Card8) (*pBytePerLongDevDepData++); } } SETUP_FB(fb); #endif } #define HYPERBOWL_MODE_FOR_8_OVER_88_LUT0_NO_TRANSPARENCIES 4 #define HYPERBOWL_MODE01_8_24_LUT0_TRANSPARENT_LUT1_OPAQUE 8 #define HYPERBOWL_MODE01_8_24_LUT0_OPAQUE_LUT1_OPAQUE 10 #define HYPERBOWL_MODE2_8_24 15 /* HCRX specific boot-time initialization */ static void __init SETUP_HCRX(struct stifb_info *fb) { int hyperbowl; int nFreeFifoSlots = 0; if (fb->id != S9000_ID_HCRX) return; /* Initialize Hyperbowl registers */ GET_FIFO_SLOTS(fb, nFreeFifoSlots, 7); if (IS_24_DEVICE(fb)) { hyperbowl = (fb->info.var.bits_per_pixel == 32) ? HYPERBOWL_MODE01_8_24_LUT0_TRANSPARENT_LUT1_OPAQUE : HYPERBOWL_MODE01_8_24_LUT0_OPAQUE_LUT1_OPAQUE; /* First write to Hyperbowl must happen twice (bug) */ WRITE_WORD(hyperbowl, fb, REG_40); WRITE_WORD(hyperbowl, fb, REG_40); WRITE_WORD(HYPERBOWL_MODE2_8_24, fb, REG_39); WRITE_WORD(0x014c0148, fb, REG_42); /* Set lut 0 to be the direct color */ WRITE_WORD(0x404c4048, fb, REG_43); WRITE_WORD(0x034c0348, fb, REG_44); WRITE_WORD(0x444c4448, fb, REG_45); } else { hyperbowl = HYPERBOWL_MODE_FOR_8_OVER_88_LUT0_NO_TRANSPARENCIES; /* First write to Hyperbowl must happen twice (bug) */ WRITE_WORD(hyperbowl, fb, REG_40); WRITE_WORD(hyperbowl, fb, REG_40); WRITE_WORD(0x00000000, fb, REG_42); WRITE_WORD(0x00000000, fb, REG_43); WRITE_WORD(0x00000000, fb, REG_44); WRITE_WORD(0x444c4048, fb, REG_45); } } /* ------------------- driver specific functions --------------------------- */ static int stifb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) { struct stifb_info *fb = container_of(info, struct stifb_info, info); if (var->xres != fb->info.var.xres || var->yres != fb->info.var.yres || var->bits_per_pixel != fb->info.var.bits_per_pixel) return -EINVAL; var->xres_virtual = var->xres; var->yres_virtual = var->yres; var->xoffset = 0; var->yoffset = 0; var->grayscale = fb->info.var.grayscale; var->red.length = fb->info.var.red.length; var->green.length = fb->info.var.green.length; var->blue.length = fb->info.var.blue.length; return 0; } static int stifb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, u_int transp, struct fb_info *info) { struct stifb_info *fb = container_of(info, struct stifb_info, info); u32 color; if (regno >= NR_PALETTE) return 1; red >>= 8; green >>= 8; blue >>= 8; DEBUG_OFF(); START_IMAGE_COLORMAP_ACCESS(fb); if (unlikely(fb->info.var.grayscale)) { /* gray = 0.30*R + 0.59*G + 0.11*B */ color = ((red * 77) + (green * 151) + (blue * 28)) >> 8; } else { color = ((red << 16) | (green << 8) | (blue)); } if (fb->info.fix.visual == FB_VISUAL_DIRECTCOLOR) { struct fb_var_screeninfo *var = &fb->info.var; if (regno < 16) ((u32 *)fb->info.pseudo_palette)[regno] = regno << var->red.offset | regno << var->green.offset | regno << var->blue.offset; } WRITE_IMAGE_COLOR(fb, regno, color); if (fb->id == S9000_ID_HCRX) { NgleLutBltCtl lutBltCtl; lutBltCtl = setHyperLutBltCtl(fb, 0, /* Offset w/i LUT */ 256); /* Load entire LUT */ NGLE_BINC_SET_SRCADDR(fb, NGLE_LONG_FB_ADDRESS(0, 0x100, 0)); /* 0x100 is same as used in WRITE_IMAGE_COLOR() */ START_COLORMAPLOAD(fb, lutBltCtl.all); SETUP_FB(fb); } else { /* cleanup colormap hardware */ FINISH_IMAGE_COLORMAP_ACCESS(fb); } DEBUG_ON(); return 0; } static int stifb_blank(int blank_mode, struct fb_info *info) { struct stifb_info *fb = container_of(info, struct stifb_info, info); int enable = (blank_mode == 0) ? ENABLE : DISABLE; switch (fb->id) { case S9000_ID_A1439A: CRX24_ENABLE_DISABLE_DISPLAY(fb, enable); break; case CRT_ID_VISUALIZE_EG: case S9000_ID_ARTIST: ARTIST_ENABLE_DISABLE_DISPLAY(fb, enable); break; case S9000_ID_HCRX: HYPER_ENABLE_DISABLE_DISPLAY(fb, enable); break; case S9000_ID_A1659A: case S9000_ID_TIMBER: case CRX24_OVERLAY_PLANES: default: ENABLE_DISABLE_DISPLAY(fb, enable); break; } SETUP_FB(fb); return 0; } static void stifb_copyarea(struct fb_info *info, const struct fb_copyarea *area) { struct stifb_info *fb = container_of(info, struct stifb_info, info); SETUP_COPYAREA(fb); SETUP_HW(fb); if (fb->info.var.bits_per_pixel == 32) { WRITE_WORD(0xBBA0A000, fb, REG_10); NGLE_REALLY_SET_IMAGE_PLANEMASK(fb, 0xffffffff); } else { WRITE_WORD(fb->id == S9000_ID_HCRX ? 0x13a02000 : 0x13a01000, fb, REG_10); NGLE_REALLY_SET_IMAGE_PLANEMASK(fb, 0xff); } NGLE_QUICK_SET_IMAGE_BITMAP_OP(fb, IBOvals(RopSrc, MaskAddrOffset(0), BitmapExtent08, StaticReg(1), DataDynamic, MaskOtc, BGx(0), FGx(0))); WRITE_WORD(((area->sx << 16) | area->sy), fb, REG_24); WRITE_WORD(((area->width << 16) | area->height), fb, REG_7); WRITE_WORD(((area->dx << 16) | area->dy), fb, REG_25); SETUP_FB(fb); } #define ARTIST_VRAM_SIZE 0x000804 #define ARTIST_VRAM_SRC 0x000808 #define ARTIST_VRAM_SIZE_TRIGGER_WINFILL 0x000a04 #define ARTIST_VRAM_DEST_TRIGGER_BLOCKMOVE 0x000b00 #define ARTIST_SRC_BM_ACCESS 0x018008 #define ARTIST_FGCOLOR 0x018010 #define ARTIST_BGCOLOR 0x018014 #define ARTIST_BITMAP_OP 0x01801c static void stifb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) { struct stifb_info *fb = container_of(info, struct stifb_info, info); if (rect->rop != ROP_COPY || (fb->id == S9000_ID_HCRX && fb->info.var.bits_per_pixel == 32)) return cfb_fillrect(info, rect); SETUP_HW(fb); if (fb->info.var.bits_per_pixel == 32) { WRITE_WORD(0xBBA0A000, fb, REG_10); NGLE_REALLY_SET_IMAGE_PLANEMASK(fb, 0xffffffff); } else { WRITE_WORD(fb->id == S9000_ID_HCRX ? 0x13a02000 : 0x13a01000, fb, REG_10); NGLE_REALLY_SET_IMAGE_PLANEMASK(fb, 0xff); } WRITE_WORD(0x03000300, fb, ARTIST_BITMAP_OP); WRITE_WORD(0x2ea01000, fb, ARTIST_SRC_BM_ACCESS); NGLE_QUICK_SET_DST_BM_ACCESS(fb, 0x2ea01000); NGLE_REALLY_SET_IMAGE_FG_COLOR(fb, rect->color); WRITE_WORD(0, fb, ARTIST_BGCOLOR); NGLE_SET_DSTXY(fb, (rect->dx << 16) | (rect->dy)); SET_LENXY_START_RECFILL(fb, (rect->width << 16) | (rect->height)); SETUP_FB(fb); } static void __init stifb_init_display(struct stifb_info *fb) { int id = fb->id; SETUP_FB(fb); /* HCRX specific initialization */ SETUP_HCRX(fb); /* if (id == S9000_ID_HCRX) hyperInitSprite(fb); else ngleInitSprite(fb); */ /* Initialize the image planes. */ switch (id) { case S9000_ID_HCRX: hyperResetPlanes(fb, ENABLE); break; case S9000_ID_A1439A: rattlerSetupPlanes(fb); break; case S9000_ID_A1659A: case S9000_ID_ARTIST: case CRT_ID_VISUALIZE_EG: elkSetupPlanes(fb); break; } /* Clear attribute planes on non HCRX devices. */ switch (id) { case S9000_ID_A1659A: case S9000_ID_A1439A: if (fb->info.var.bits_per_pixel == 32) ngleSetupAttrPlanes(fb, BUFF1_CMAP3); else { ngleSetupAttrPlanes(fb, BUFF1_CMAP0); } if (id == S9000_ID_A1439A) ngleClearOverlayPlanes(fb, 0xff, 0); break; case S9000_ID_ARTIST: case CRT_ID_VISUALIZE_EG: if (fb->info.var.bits_per_pixel == 32) ngleSetupAttrPlanes(fb, BUFF1_CMAP3); else { ngleSetupAttrPlanes(fb, ARTIST_CMAP0); } break; } stifb_blank(0, (struct fb_info *)fb); /* 0=enable screen */ SETUP_FB(fb); } /* ------------ Interfaces to hardware functions ------------ */ static const struct fb_ops stifb_ops = { .owner = THIS_MODULE, .fb_check_var = stifb_check_var, .fb_setcolreg = stifb_setcolreg, .fb_blank = stifb_blank, .fb_fillrect = stifb_fillrect, .fb_copyarea = stifb_copyarea, .fb_imageblit = cfb_imageblit, }; /* * Initialization */ static int __init stifb_init_fb(struct sti_struct *sti, int bpp_pref) { struct fb_fix_screeninfo *fix; struct fb_var_screeninfo *var; struct stifb_info *fb; struct fb_info *info; unsigned long sti_rom_address; char modestr[32]; char *dev_name; int bpp, xres, yres; fb = kzalloc(sizeof(*fb), GFP_ATOMIC); if (!fb) return -ENOMEM; info = &fb->info; /* set struct to a known state */ fix = &info->fix; var = &info->var; fb->sti = sti; dev_name = sti->sti_data->inq_outptr.dev_name; /* store upper 32bits of the graphics id */ fb->id = fb->sti->graphics_id[0]; /* only supported cards are allowed */ switch (fb->id) { case CRT_ID_VISUALIZE_EG: /* Visualize cards can run either in "double buffer" or "standard" mode. Depending on the mode, the card reports a different device name, e.g. "INTERNAL_EG_DX1024" in double buffer mode and "INTERNAL_EG_X1024" in standard mode. Since this driver only supports standard mode, we check if the device name contains the string "DX" and tell the user how to reconfigure the card. */ if (strstr(dev_name, "DX")) { printk(KERN_WARNING "WARNING: stifb framebuffer driver does not support '%s' in double-buffer mode.\n" "WARNING: Please disable the double-buffer mode in IPL menu (the PARISC-BIOS).\n", dev_name); goto out_err0; } fallthrough; case S9000_ID_ARTIST: case S9000_ID_HCRX: case S9000_ID_TIMBER: case S9000_ID_A1659A: case S9000_ID_A1439A: break; default: printk(KERN_WARNING "stifb: '%s' (id: 0x%08x) not supported.\n", dev_name, fb->id); goto out_err0; } /* default to 8 bpp on most graphic chips */ bpp = 8; xres = sti_onscreen_x(fb->sti); yres = sti_onscreen_y(fb->sti); ngleGetDeviceRomData(fb); /* get (virtual) io region base addr */ fix->mmio_start = REGION_BASE(fb,2); fix->mmio_len = 0x400000; /* Reject any device not in the NGLE family */ switch (fb->id) { case S9000_ID_A1659A: /* CRX/A1659A */ break; case S9000_ID_ELM: /* GRX, grayscale but else same as A1659A */ var->grayscale = 1; fb->id = S9000_ID_A1659A; break; case S9000_ID_TIMBER: /* HP9000/710 Any (may be a grayscale device) */ if (strstr(dev_name, "GRAYSCALE") || strstr(dev_name, "Grayscale") || strstr(dev_name, "grayscale")) var->grayscale = 1; break; case S9000_ID_TOMCAT: /* Dual CRX, behaves else like a CRX */ /* FIXME: TomCat supports two heads: * fb.iobase = REGION_BASE(fb_info,3); * fb.screen_base = ioremap(REGION_BASE(fb_info,2),xxx); * for now we only support the left one ! */ xres = fb->ngle_rom.x_size_visible; yres = fb->ngle_rom.y_size_visible; fb->id = S9000_ID_A1659A; break; case S9000_ID_A1439A: /* CRX24/A1439A */ bpp = 32; break; case S9000_ID_HCRX: /* Hyperdrive/HCRX */ memset(&fb->ngle_rom, 0, sizeof(fb->ngle_rom)); if ((fb->sti->regions_phys[0] & 0xfc000000) == (fb->sti->regions_phys[2] & 0xfc000000)) sti_rom_address = F_EXTEND(fb->sti->regions_phys[0]); else sti_rom_address = F_EXTEND(fb->sti->regions_phys[1]); fb->deviceSpecificConfig = gsc_readl(sti_rom_address); if (IS_24_DEVICE(fb)) { if (bpp_pref == 8 || bpp_pref == 32) bpp = bpp_pref; else bpp = 32; } else bpp = 8; READ_WORD(fb, REG_15); SETUP_HW(fb); break; case CRT_ID_VISUALIZE_EG: case S9000_ID_ARTIST: /* Artist */ break; default: #ifdef FALLBACK_TO_1BPP printk(KERN_WARNING "stifb: Unsupported graphics card (id=0x%08x) " "- now trying 1bpp mode instead\n", fb->id); bpp = 1; /* default to 1 bpp */ break; #else printk(KERN_WARNING "stifb: Unsupported graphics card (id=0x%08x) " "- skipping.\n", fb->id); goto out_err0; #endif } /* get framebuffer physical and virtual base addr & len (64bit ready) */ fix->smem_start = F_EXTEND(fb->sti->regions_phys[1]); fix->smem_len = fb->sti->regions[1].region_desc.length * 4096; fix->line_length = (fb->sti->glob_cfg->total_x * bpp) / 8; if (!fix->line_length) fix->line_length = 2048; /* default */ /* limit fbsize to max visible screen size */ if (fix->smem_len > yres*fix->line_length) fix->smem_len = ALIGN(yres*fix->line_length, 4*1024*1024); fix->accel = FB_ACCEL_NONE; switch (bpp) { case 1: fix->type = FB_TYPE_PLANES; /* well, sort of */ fix->visual = FB_VISUAL_MONO10; var->red.length = var->green.length = var->blue.length = 1; break; case 8: fix->type = FB_TYPE_PACKED_PIXELS; fix->visual = FB_VISUAL_PSEUDOCOLOR; var->red.length = var->green.length = var->blue.length = 8; break; case 32: fix->type = FB_TYPE_PACKED_PIXELS; fix->visual = FB_VISUAL_DIRECTCOLOR; var->red.length = var->green.length = var->blue.length = var->transp.length = 8; var->blue.offset = 0; var->green.offset = 8; var->red.offset = 16; var->transp.offset = 24; break; default: break; } var->xres = var->xres_virtual = xres; var->yres = var->yres_virtual = yres; var->bits_per_pixel = bpp; strcpy(fix->id, "stifb"); info->fbops = &stifb_ops; info->screen_base = ioremap(REGION_BASE(fb,1), fix->smem_len); if (!info->screen_base) { printk(KERN_ERR "stifb: failed to map memory\n"); goto out_err0; } info->screen_size = fix->smem_len; info->flags = FBINFO_HWACCEL_COPYAREA | FBINFO_HWACCEL_FILLRECT; info->pseudo_palette = &fb->pseudo_palette; scnprintf(modestr, sizeof(modestr), "%dx%d-%d", xres, yres, bpp); fb_find_mode(&info->var, info, modestr, NULL, 0, NULL, bpp); /* This has to be done !!! */ if (fb_alloc_cmap(&info->cmap, NR_PALETTE, 0)) goto out_err1; stifb_init_display(fb); if (!request_mem_region(fix->smem_start, fix->smem_len, "stifb fb")) { printk(KERN_ERR "stifb: cannot reserve fb region 0x%04lx-0x%04lx\n", fix->smem_start, fix->smem_start+fix->smem_len); goto out_err2; } if (!request_mem_region(fix->mmio_start, fix->mmio_len, "stifb mmio")) { printk(KERN_ERR "stifb: cannot reserve sti mmio region 0x%04lx-0x%04lx\n", fix->mmio_start, fix->mmio_start+fix->mmio_len); goto out_err3; } /* save for primary gfx device detection & unregister_framebuffer() */ sti->info = info; if (register_framebuffer(&fb->info) < 0) goto out_err4; fb_info(&fb->info, "%s %dx%d-%d frame buffer device, %s, id: %04x, mmio: 0x%04lx\n", fix->id, var->xres, var->yres, var->bits_per_pixel, dev_name, fb->id, fix->mmio_start); return 0; out_err4: release_mem_region(fix->mmio_start, fix->mmio_len); out_err3: release_mem_region(fix->smem_start, fix->smem_len); out_err2: fb_dealloc_cmap(&info->cmap); out_err1: iounmap(info->screen_base); out_err0: kfree(fb); sti->info = NULL; return -ENXIO; } static int stifb_disabled __initdata; int __init stifb_setup(char *options); static int __init stifb_init(void) { struct sti_struct *sti; struct sti_struct *def_sti; int i; #ifndef MODULE char *option = NULL; if (fb_get_options("stifb", &option)) return -ENODEV; stifb_setup(option); #endif if (stifb_disabled) { printk(KERN_INFO "stifb: disabled by \"stifb=off\" kernel parameter\n"); return -ENXIO; } def_sti = sti_get_rom(0); if (def_sti) { for (i = 1; i <= MAX_STI_ROMS; i++) { sti = sti_get_rom(i); if (!sti) break; if (sti == def_sti) { stifb_init_fb(sti, stifb_bpp_pref[i - 1]); break; } } } for (i = 1; i <= MAX_STI_ROMS; i++) { sti = sti_get_rom(i); if (!sti) break; if (sti == def_sti) continue; stifb_init_fb(sti, stifb_bpp_pref[i - 1]); } return 0; } /* * Cleanup */ static void __exit stifb_cleanup(void) { struct sti_struct *sti; int i; for (i = 1; i <= MAX_STI_ROMS; i++) { sti = sti_get_rom(i); if (!sti) break; if (sti->info) { struct fb_info *info = sti->info; unregister_framebuffer(sti->info); release_mem_region(info->fix.mmio_start, info->fix.mmio_len); release_mem_region(info->fix.smem_start, info->fix.smem_len); if (info->screen_base) iounmap(info->screen_base); fb_dealloc_cmap(&info->cmap); framebuffer_release(info); } sti->info = NULL; } } int __init stifb_setup(char *options) { int i; if (!options || !*options) return 1; if (strncmp(options, "off", 3) == 0) { stifb_disabled = 1; options += 3; } if (strncmp(options, "bpp", 3) == 0) { options += 3; for (i = 0; i < MAX_STI_ROMS; i++) { if (*options++ != ':') break; stifb_bpp_pref[i] = simple_strtoul(options, &options, 10); } } return 1; } __setup("stifb=", stifb_setup); module_init(stifb_init); module_exit(stifb_cleanup); MODULE_AUTHOR("Helge Deller <deller@gmx.de>, Thomas Bogendoerfer <tsbogend@alpha.franken.de>"); MODULE_DESCRIPTION("Framebuffer driver for HP's NGLE series graphics cards in HP PARISC machines"); MODULE_LICENSE("GPL v2");